AngelscriptLanguageServer/server/src/server.ts

268 lines
9.5 KiB
TypeScript
Raw Normal View History

2020-09-17 19:59:10 +00:00
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import {
createConnection,
TextDocuments,
Diagnostic,
DiagnosticSeverity,
ProposedFeatures,
InitializeParams,
DidChangeConfigurationNotification,
TextDocumentSyncKind,
InitializeResult,
Position,
2020-09-17 19:59:10 +00:00
} from 'vscode-languageserver';
import {
TextDocument
2020-09-17 19:59:10 +00:00
} from 'vscode-languageserver-textdocument';
2020-09-17 22:26:14 +00:00
import * as NativeWrapper from './wrapper'
2020-09-18 15:48:45 +00:00
import * as fs from 'fs';
import * as path from 'path';
2020-09-17 22:26:14 +00:00
2020-09-17 19:59:10 +00:00
// Create a connection for the server, using Node's IPC as a transport.
// Also include all preview / proposed LSP features.
2020-09-18 15:48:45 +00:00
const connection = createConnection(ProposedFeatures.all);
const database = NativeWrapper.BuildDatabase();
//TODO: change these based on user settings
database.setEngineProperty(NativeWrapper.ASEngineProperty.asEP_DISALLOW_EMPTY_LIST_ELEMENTS, 1);
database.setEngineProperty(NativeWrapper.ASEngineProperty.asEP_DISALLOW_VALUE_ASSIGN_FOR_REF_TYPE, 0);
database.setEngineProperty(NativeWrapper.ASEngineProperty.asEP_ALLOW_UNSAFE_REFERENCES, 1);
database.setEngineProperty(NativeWrapper.ASEngineProperty.asEP_ALWAYS_IMPL_DEFAULT_CONSTRUCT, 1);
database.setEngineProperty(NativeWrapper.ASEngineProperty.asEP_AUTO_GARBAGE_COLLECT, 0);
database.setEngineProperty(NativeWrapper.ASEngineProperty.asEP_REQUIRE_ENUM_SCOPE, 1);
database.setEngineProperty(NativeWrapper.ASEngineProperty.asEP_PROPERTY_ACCESSOR_MODE, 2);
database.setEngineProperty(NativeWrapper.ASEngineProperty.asEP_COMPILER_WARNINGS, 2);
2020-09-17 19:59:10 +00:00
// Create a simple text document manager.
let documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);
2020-09-18 15:48:45 +00:00
let allFiles: Map<string, TextDocument> = new Map<string, TextDocument>();
2020-09-17 19:59:10 +00:00
let hasConfigurationCapability: boolean = false;
let hasWorkspaceFolderCapability: boolean = false;
let hasDiagnosticRelatedInformationCapability: boolean = false;
2020-09-18 15:48:45 +00:00
function registerFiles(directoryName: string) {
let files = fs.readdirSync(directoryName);
files.forEach(function (file) {
let f = fs.statSync(directoryName + path.sep + file);
if (f.isDirectory()) {
registerFiles(directoryName + path.sep + file)
} else {
if (path.extname(file) == ".as" || path.extname(file) == ".astypedef") {
const filepath = directoryName + path.sep + file;
var uri = "file://" + filepath;
if (!allFiles.get(uri)) {
let td = TextDocument.create(uri, "Angelscript", 0, fs.readFileSync(filepath, 'utf8'));
allFiles.set(uri, td);
}
}
}
})
2020-09-18 15:48:45 +00:00
}
2020-09-17 19:59:10 +00:00
connection.onInitialize((params: InitializeParams) => {
let capabilities = params.capabilities;
hasConfigurationCapability = !!(
capabilities.workspace && !!capabilities.workspace.configuration
);
hasWorkspaceFolderCapability = !!(
capabilities.workspace && !!capabilities.workspace.workspaceFolders
);
hasDiagnosticRelatedInformationCapability = !!(
capabilities.textDocument &&
capabilities.textDocument.publishDiagnostics &&
capabilities.textDocument.publishDiagnostics.relatedInformation
);
const result: InitializeResult = {
capabilities: {
textDocumentSync: TextDocumentSyncKind.Incremental,
}
};
if (hasWorkspaceFolderCapability) {
result.capabilities.workspace = {
workspaceFolders: {
supported: true
}
};
}
return result;
2020-09-17 19:59:10 +00:00
});
let isInitialized =false;
2020-09-17 19:59:10 +00:00
connection.onInitialized(() => {
if (hasConfigurationCapability) {
// Register for all configuration changes.
connection.client.register(DidChangeConfigurationNotification.type, undefined);
}
if (hasWorkspaceFolderCapability) {
connection.workspace.connection.workspace.onDidChangeWorkspaceFolders(_event => {
connection.console.log('Workspace folder change event received.');
});
}
connection.workspace.getConfiguration('angelscript-languageserver.defines').then((config) => {
let defines = config as string[];
for (let index = 0; index < defines.length; index++) {
const define = defines[index];
database.addDefine(define);
}
});
connection.workspace.getConfiguration('angelscript-languageserver.engine-settings').then((config) => {
for (const key in config) {
if (Object.prototype.hasOwnProperty.call(config, key)) {
const element = config[key];
let keyValue : NativeWrapper.ASEngineProperty = NativeWrapper.ASEngineProperty[key as keyof typeof NativeWrapper.ASEngineProperty];
database.setEngineProperty(keyValue, element);
}
}
});
connection.workspace.getWorkspaceFolders().then((ws) => {
ws?.forEach(element => {
registerFiles(element.uri.substr(7))
});
database.reset();
allFiles.forEach(loadScript);
validateBuild().then(() => {
isInitialized = true;
});
})
2020-09-17 19:59:10 +00:00
});
2020-09-18 15:48:45 +00:00
connection.onDidChangeConfiguration(change => {
2020-09-17 19:59:10 +00:00
});
// The content of a text document has changed. This event is emitted
// when the text document first opened or when its content has changed.
documents.onDidChangeContent(change => {
if (!isInitialized){
return;
}
if (change.document.languageId != "Angelscript" && change.document.languageId != "AngelscriptTypeDefinition")
return;
var current = allFiles.get(change.document.uri);
if (current != null && current != change.document) {
if (current.version == 0) {
allFiles.set(change.document.uri, change.document);
return;
}
} else if (current == null) {
allFiles.set(change.document.uri, change.document);
}
database.reset();
allFiles.forEach(loadScript);
validateBuild();
2020-09-17 19:59:10 +00:00
});
2020-09-17 22:26:14 +00:00
function convertSeverity(type: NativeWrapper.MessageType): DiagnosticSeverity {
switch (type) {
case NativeWrapper.MessageType.Information:
return DiagnosticSeverity.Information;
case NativeWrapper.MessageType.Warning:
return DiagnosticSeverity.Warning;
case NativeWrapper.MessageType.Error:
return DiagnosticSeverity.Error;
}
2020-09-17 22:26:14 +00:00
}
2020-09-18 15:48:45 +00:00
function loadScript(textDocument: TextDocument): void {
if (textDocument.languageId != "Angelscript" && textDocument.languageId != "AngelscriptTypeDefinition")
return;
if (!fs.existsSync(textDocument.uri.substr(7))) {
allFiles.delete(textDocument.uri);
return;
}
let text = textDocument.getText();
if (path.extname(textDocument.uri) == ".astypedef") {
database.loadTypeDef(textDocument.uri, text);
} else {
database.loadScript(textDocument.uri, text);
}
2020-09-18 15:48:45 +00:00
}
async function validateBuild(): Promise<void> {
console.log("Building");
var r = database.build();
if (r <= -1) {
console.log(r.toString());
}
var messages = database.messages();
let diagnostics: Map<string, Diagnostic[]> = new Map<string, Diagnostic[]>();
for (let i = 0; i < messages.length; i++) {
let msg = messages[i];
if (msg.type == NativeWrapper.MessageType.Information)
continue;
console.log('[ ' + msg.section + ' ] ' + msg.message);
if (msg.section == "") {
if (msg.type == NativeWrapper.MessageType.Error) {
connection.window.showErrorMessage(msg.message);
} else if (msg.type == NativeWrapper.MessageType.Warning) {
connection.window.showWarningMessage(msg.message);
}
continue;
}
let startPos = Position.create(msg.row - 1, msg.col - 1);
if (startPos.line < 0) startPos.line = 0;
if (startPos.character < 0) startPos.character = 0;
let textDocument = documents.get(msg.section);
if (textDocument == undefined)
continue;
let offset = textDocument.offsetAt(startPos);
let diagnostic: Diagnostic = {
severity: convertSeverity(msg.type),
range: {
start: startPos,
end: textDocument.positionAt(offset + 1)
},
message: msg.message,
source: 'Angelscript'
};
if (msg.type == NativeWrapper.MessageType.Error) {
connection.console.error(`[${msg.section}:${msg.row - 1},${msg.col - 1}] ${msg.message}`)
}
var diag = diagnostics.get(msg.section);
if (diag) {
diag.push(diagnostic)
} else {
diagnostics.set(msg.section, [diagnostic]);
}
}
documents.all().forEach(element => {
let v = diagnostics.get(element.uri);
if (v)
connection.sendDiagnostics({uri: element.uri, diagnostics: v});
else
connection.sendDiagnostics({uri: element.uri, diagnostics: []});
});
console.log("Finished building");
// Send the computed diagnostics to VSCode.
2020-09-17 19:59:10 +00:00
}
connection.onDidChangeWatchedFiles(_change => {
// Monitored files have change in VSCode
connection.console.log('We received an file change event');
2020-09-17 19:59:10 +00:00
});
// Make the text document manager listen on the connection
// for open, change and close text document events
documents.listen(connection);
// Listen on the connection
connection.listen();