/* -------------------------------------------------------------------------------------------- * 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 = new TextDocuments(TextDocument); let allFiles: Map = new Map(); 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" || 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); } } } }) } 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; }); let isInitialized =false; 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().then(() => { isInitialized = true; }); }) }); 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 (!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(); }); 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" && 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); } } async function validateBuild(): Promise { console.log("Building"); var r = database.build(); if (r <= -1) { console.log(r.toString()); } var messages = database.messages(); let diagnostics: Map = new Map(); 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. } 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();