AngelscriptLanguageServer/server/src/server.ts

241 lines
7.4 KiB
TypeScript

/* --------------------------------------------------------------------------------------------
* 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,
} from 'vscode-languageserver';
import {
TextDocument
} from 'vscode-languageserver-textdocument';
import * as NativeWrapper from './wrapper'
import * as fs from 'fs';
import * as path from 'path';
// Create a connection for the server, using Node's IPC as a transport.
// Also include all preview / proposed LSP features.
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);
// Create a simple text document manager.
let documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);
let allFiles: Map<string, TextDocument> = new Map<string, TextDocument>();
let hasConfigurationCapability: boolean = false;
let hasWorkspaceFolderCapability: boolean = false;
let hasDiagnosticRelatedInformationCapability: boolean = false;
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") {
const filepath = directoryName + path.sep + file;
var uri = "file://" + filepath;
if (!allFiles.get(uri)) {
connection.console.log("Loaded new file at uri " + uri);
let td = TextDocument.create(uri, "Angelscript", 0, fs.readFileSync(filepath, 'utf8'));
allFiles.set(uri, td);
}
}
}
})
}
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;
});
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.getWorkspaceFolders().then((ws) => {
ws?.forEach(element => {
registerFiles(element.uri.substr(7))
});
database.reset();
allFiles.forEach(loadScript);
validateBuild();
})
});
connection.onDidChangeConfiguration(change => {
});
// 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 (change.document.languageId != "Angelscript")
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();
});
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;
}
}
function loadScript(textDocument: TextDocument): void {
if (textDocument.languageId != "Angelscript")
return;
if (!fs.existsSync(textDocument.uri.substr(7))){
allFiles.delete(textDocument.uri);
return;
}
let text = textDocument.getText();
database.loadScript(textDocument.uri, text);
}
async function validateBuild(): Promise<void> {
var r = database.build();
if (r < -1) {
connection.console.log(r.toString());
}
connection.console.log("Building");
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;
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: [] });
});
// Send the computed diagnostics to VSCode.
}
connection.onDidChangeWatchedFiles(_change => {
// Monitored files have change in VSCode
connection.console.log('We received an file change event');
});
// 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();