diff --git a/.vscode/launch.json b/.vscode/launch.json index 99b8279..53af377 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,8 +7,12 @@ "request": "launch", "name": "Launch Client", "runtimeExecutable": "${execPath}", - "args": ["--extensionDevelopmentPath=${workspaceRoot}"], - "outFiles": ["${workspaceRoot}/client/out/**/*.js"], + "args": [ + "--extensionDevelopmentPath=${workspaceRoot}" + ], + "outFiles": [ + "${workspaceRoot}/client/out/**/*.js" + ], "preLaunchTask": { "type": "npm", "script": "watch" @@ -20,7 +24,9 @@ "name": "Attach to Server", "port": 6009, "restart": true, - "outFiles": ["${workspaceRoot}/server/out/**/*.js"] + "outFiles": [ + "${workspaceRoot}/server/out/**/*.js" + ] }, { "name": "Language Server E2E Test", @@ -32,13 +38,18 @@ "--extensionTestsPath=${workspaceRoot}/client/out/test", "${workspaceRoot}/client/testFixture" ], - "outFiles": ["${workspaceRoot}/client/out/test/**/*.js"] + "outFiles": [ + "${workspaceRoot}/client/out/test/**/*.js" + ] } ], "compounds": [ { "name": "Client + Server", - "configurations": ["Launch Client", "Attach to Server"] + "configurations": [ + "Launch Client", + "Attach to Server" + ] } ] -} +} \ No newline at end of file diff --git a/client/src/extension.ts b/client/src/extension.ts index b61d127..b6ba57d 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -1,89 +1,88 @@ -'use strict'; +"use strict"; -import { workspace, ExtensionContext } from 'vscode'; +import { ExtensionContext, workspace } from "vscode"; import { - LanguageClient, - LanguageClientOptions, - ServerOptions, - TransportKind -} from 'vscode-languageclient'; + LanguageClient, + LanguageClientOptions, + ServerOptions, + TransportKind, +} from "vscode-languageclient"; -import * as path from 'path'; +import * as path from "path"; let client: LanguageClient; export async function activate(context: ExtensionContext) { - // We get activated if there is one or more elm.json file in the workspace - // Start one server for each directory with an elm.json - // and watch Elm files in those directories. - let elmJsons = await workspace.findFiles('**/elm.json'); - for (let uri of elmJsons) { - startClient(path.dirname(uri.fsPath), context); - } - // TODO: watch for addition and removal of 'elm.json' files - // and start and stop clients for those directories. + // We get activated if there is one or more elm.json file in the workspace + // Start one server for each directory with an elm.json + // and watch Elm files in those directories. + const elmJsons = await workspace.findFiles("**/elm.json"); + for (const uri of elmJsons) { + startClient(path.dirname(uri.fsPath), context); + } + // TODO: watch for addition and removal of 'elm.json' files + // and start and stop clients for those directories. } - -let clients: Map = new Map(); +const clients: Map = new Map(); function startClient(dir: string, context: ExtensionContext) { - if (clients.has(dir)) { - // Client was already started for this directory - return; - } + if (clients.has(dir)) { + // Client was already started for this directory + return; + } - let serverModule = context.asAbsolutePath( - path.join('server', 'out', 'index.js') - ); - // The debug options for the server - // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging - let debugOptions = { execArgv: ['--nolazy', '--inspect=6009'] }; + const serverModule = context.asAbsolutePath( + path.join("server", "out", "index.js"), + ); + // The debug options for the server + // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging + const debugOptions = { execArgv: ["--nolazy", "--inspect=6009"] }; - // If the extension is launched in debug mode then the debug server options are used - // Otherwise the run options are used - let serverOptions: ServerOptions = { - run: { module: serverModule, transport: TransportKind.ipc }, - debug: { - module: serverModule, - transport: TransportKind.ipc, - options: debugOptions - } - }; + // If the extension is launched in debug mode then the debug server options are used + // Otherwise the run options are used + const serverOptions: ServerOptions = { + debug: { + module: serverModule, + options: debugOptions, + transport: TransportKind.ipc, + }, + run: { module: serverModule, transport: TransportKind.ipc }, + }; - // Options to control the language client - let clientOptions: LanguageClientOptions = { - // Register the server for Elm documents in the directory - documentSelector: [ - { - scheme: 'file', - pattern: path.join(dir, '**', '*.elm') - } - ], - // Notify the server about file changes to 'elm.json' - synchronize: { - fileEvents: workspace.createFileSystemWatcher(path.join(dir, 'elm.json')) - } - }; + // Options to control the language client + const clientOptions: LanguageClientOptions = { + // Register the server for Elm documents in the directory + documentSelector: [ + { + pattern: path.join(dir, "**", "*.elm"), + scheme: "file", + }, + ], + // Notify the server about file changes to 'elm.json' + synchronize: { + fileEvents: workspace.createFileSystemWatcher(path.join(dir, "elm.json")), + }, + }; - // Create the language client and start the client. - client = new LanguageClient( - 'elmLanguageServer', - 'Elm Language Server', - serverOptions, - clientOptions - ); + // Create the language client and start the client. + client = new LanguageClient( + "elmLanguageServer", + "Elm Language Server", + serverOptions, + clientOptions, + ); - // Start the client. This will also launch the server - client.start(); - client.info(`Starting language server for ${dir}`); - clients.set(dir, client); + // Start the client. This will also launch the server + client.start(); + client.info(`Starting language server for ${dir}`); + clients.set(dir, client); } export function deactivate(): Thenable { - let promises: Thenable[] = []; - for (let client of clients.values()) { - promises.push(client.stop()); - } - return Promise.all(promises).then(() => undefined); + const promises: Array> = []; + for (const client of clients.values()) { + promises.push(client.stop()); + } + return Promise.all(promises).then(() => undefined); } diff --git a/client/src/test/completion.test.ts b/client/src/test/completion.test.ts index 2cb6564..ce727a1 100644 --- a/client/src/test/completion.test.ts +++ b/client/src/test/completion.test.ts @@ -2,43 +2,43 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ -'use strict'; +"use strict"; -import * as vscode from 'vscode'; -import * as assert from 'assert'; -import { getDocUri, activate } from './helper'; +import * as assert from "assert"; +import * as vscode from "vscode"; +import { activate, getDocUri } from "./helper"; -describe('Should do completion', () => { - const docUri = getDocUri('completion.txt'); +describe("Should do completion", () => { + const docUri = getDocUri("completion.txt"); - it('Completes JS/TS in txt file', async () => { - await testCompletion(docUri, new vscode.Position(0, 0), { - items: [ - { label: 'JavaScript', kind: vscode.CompletionItemKind.Text }, - { label: 'TypeScript', kind: vscode.CompletionItemKind.Text } - ] - }); - }); + it("Completes JS/TS in txt file", async () => { + await testCompletion(docUri, new vscode.Position(0, 0), { + items: [ + { label: "JavaScript", kind: vscode.CompletionItemKind.Text }, + { label: "TypeScript", kind: vscode.CompletionItemKind.Text }, + ], + }); + }); }); async function testCompletion( - docUri: vscode.Uri, - position: vscode.Position, - expectedCompletionList: vscode.CompletionList + docUri: vscode.Uri, + position: vscode.Position, + expectedCompletionList: vscode.CompletionList, ) { - await activate(docUri); + await activate(docUri); - // Executing the command `vscode.executeCompletionItemProvider` to simulate triggering completion - const actualCompletionList = (await vscode.commands.executeCommand( - 'vscode.executeCompletionItemProvider', - docUri, - position - )) as vscode.CompletionList; + // Executing the command `vscode.executeCompletionItemProvider` to simulate triggering completion + const actualCompletionList = (await vscode.commands.executeCommand( + "vscode.executeCompletionItemProvider", + docUri, + position, + )) as vscode.CompletionList; - assert.equal(actualCompletionList.items.length, expectedCompletionList.items.length); - expectedCompletionList.items.forEach((expectedItem, i) => { - const actualItem = actualCompletionList.items[i]; - assert.equal(actualItem.label, expectedItem.label); - assert.equal(actualItem.kind, expectedItem.kind); - }); + assert.equal(actualCompletionList.items.length, expectedCompletionList.items.length); + expectedCompletionList.items.forEach((expectedItem, i) => { + const actualItem = actualCompletionList.items[i]; + assert.equal(actualItem.label, expectedItem.label); + assert.equal(actualItem.kind, expectedItem.kind); + }); } diff --git a/client/src/test/diagnostics.test.ts b/client/src/test/diagnostics.test.ts index 1eee499..e21db23 100644 --- a/client/src/test/diagnostics.test.ts +++ b/client/src/test/diagnostics.test.ts @@ -2,41 +2,41 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ -'use strict'; +"use strict"; -import * as vscode from 'vscode' -import * as assert from 'assert' -import { getDocUri, activate } from './helper' +import * as assert from "assert"; +import * as vscode from "vscode"; +import { activate, getDocUri } from "./helper"; -describe('Should get diagnostics', () => { - const docUri = getDocUri('diagnostics.txt') +describe("Should get diagnostics", () => { + const docUri = getDocUri("diagnostics.txt"); - it('Diagnoses uppercase texts', async () => { + it("Diagnoses uppercase texts", async () => { await testDiagnostics(docUri, [ - { message: 'ANY is all uppercase.', range: toRange(0, 0, 0, 3), severity: vscode.DiagnosticSeverity.Warning, source: 'ex' }, - { message: 'ANY is all uppercase.', range: toRange(0, 14, 0, 17), severity: vscode.DiagnosticSeverity.Warning, source: 'ex' }, - { message: 'OS is all uppercase.', range: toRange(0, 18, 0, 20), severity: vscode.DiagnosticSeverity.Warning, source: 'ex' } - ]) - }) -}) + { message: "ANY is all uppercase.", range: toRange(0, 0, 0, 3), severity: vscode.DiagnosticSeverity.Warning, source: "ex" }, + { message: "ANY is all uppercase.", range: toRange(0, 14, 0, 17), severity: vscode.DiagnosticSeverity.Warning, source: "ex" }, + { message: "OS is all uppercase.", range: toRange(0, 18, 0, 20), severity: vscode.DiagnosticSeverity.Warning, source: "ex" }, + ]); + }); +}); function toRange(sLine: number, sChar: number, eLine: number, eChar: number) { - const start = new vscode.Position(sLine, sChar) - const end = new vscode.Position(eLine, eChar) - return new vscode.Range(start, end) + const start = new vscode.Position(sLine, sChar); + const end = new vscode.Position(eLine, eChar); + return new vscode.Range(start, end); } async function testDiagnostics(docUri: vscode.Uri, expectedDiagnostics: vscode.Diagnostic[]) { - await activate(docUri) + await activate(docUri); const actualDiagnostics = vscode.languages.getDiagnostics(docUri); assert.equal(actualDiagnostics.length, expectedDiagnostics.length); expectedDiagnostics.forEach((expectedDiagnostic, i) => { - const actualDiagnostic = actualDiagnostics[i] - assert.equal(actualDiagnostic.message, expectedDiagnostic.message) - assert.deepEqual(actualDiagnostic.range, expectedDiagnostic.range) - assert.equal(actualDiagnostic.severity, expectedDiagnostic.severity) - }) -} \ No newline at end of file + const actualDiagnostic = actualDiagnostics[i]; + assert.equal(actualDiagnostic.message, expectedDiagnostic.message); + assert.deepEqual(actualDiagnostic.range, expectedDiagnostic.range); + assert.equal(actualDiagnostic.severity, expectedDiagnostic.severity); + }); +} diff --git a/client/src/test/helper.ts b/client/src/test/helper.ts index 22121c1..ef4b3d1 100644 --- a/client/src/test/helper.ts +++ b/client/src/test/helper.ts @@ -2,10 +2,10 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ -'use strict'; +"use strict"; -import * as vscode from 'vscode'; -import * as path from 'path'; +import * as path from "path"; +import * as vscode from "vscode"; export let doc: vscode.TextDocument; export let editor: vscode.TextEditor; @@ -16,33 +16,33 @@ export let platformEol: string; * Activates the vscode.lsp-sample extension */ export async function activate(docUri: vscode.Uri) { - // The extensionId is `publisher.name` from package.json - const ext = vscode.extensions.getExtension('vscode.lsp-sample'); - await ext.activate(); - try { - doc = await vscode.workspace.openTextDocument(docUri); - editor = await vscode.window.showTextDocument(doc); - await sleep(2000); // Wait for server activation - } catch (e) { - console.error(e); - } + // The extensionId is `publisher.name` from package.json + const ext = vscode.extensions.getExtension("vscode.lsp-sample"); + await ext.activate(); + try { + doc = await vscode.workspace.openTextDocument(docUri); + editor = await vscode.window.showTextDocument(doc); + await sleep(2000); // Wait for server activation + } catch (e) { + console.error(e); + } } async function sleep(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)); } export const getDocPath = (p: string) => { - return path.resolve(__dirname, '../../testFixture', p); + return path.resolve(__dirname, "../../testFixture", p); }; export const getDocUri = (p: string) => { - return vscode.Uri.file(getDocPath(p)); + return vscode.Uri.file(getDocPath(p)); }; export async function setTestContent(content: string): Promise { - const all = new vscode.Range( - doc.positionAt(0), - doc.positionAt(doc.getText().length) - ); - return editor.edit(eb => eb.replace(all, content)); + const all = new vscode.Range( + doc.positionAt(0), + doc.positionAt(doc.getText().length), + ); + return editor.edit((eb) => eb.replace(all, content)); } diff --git a/client/src/test/index.ts b/client/src/test/index.ts index 7fedf55..5426047 100644 --- a/client/src/test/index.ts +++ b/client/src/test/index.ts @@ -2,14 +2,14 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ -'use strict'; +"use strict"; -import * as testRunner from 'vscode/lib/testrunner'; +import * as testRunner from "vscode/lib/testrunner"; testRunner.configure({ - ui: 'bdd', + timeout: 100000, + ui: "bdd", useColors: true, - timeout: 100000 }); -module.exports = testRunner; \ No newline at end of file +module.exports = testRunner; diff --git a/client/tsconfig.json b/client/tsconfig.json index 4a9daa3..0da3c65 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -4,9 +4,16 @@ "target": "es6", "outDir": "out", "rootDir": "src", - "lib": ["es6"], + "lib": [ + "es6" + ], "sourceMap": true }, - "include": ["src"], - "exclude": ["node_modules", ".vscode-test"] -} + "include": [ + "src" + ], + "exclude": [ + "node_modules", + ".vscode-test" + ] +} \ No newline at end of file diff --git a/server/src/capabilityCalculator.ts b/server/src/capabilityCalculator.ts index d12c1aa..980fdfd 100644 --- a/server/src/capabilityCalculator.ts +++ b/server/src/capabilityCalculator.ts @@ -1,27 +1,27 @@ import { - ClientCapabilities, - ServerCapabilities, - TextDocumentSyncKind, -} from 'vscode-languageserver'; + ClientCapabilities, + ServerCapabilities, + TextDocumentSyncKind, +} from "vscode-languageserver"; export class CapabilityCalculator { - private clientCapabilities: ClientCapabilities; + private clientCapabilities: ClientCapabilities; - constructor(clientCapabilities: ClientCapabilities) { - this.clientCapabilities = clientCapabilities; - } + constructor(clientCapabilities: ClientCapabilities) { + this.clientCapabilities = clientCapabilities; + } - get capabilities(): ServerCapabilities { - this.clientCapabilities; + get capabilities(): ServerCapabilities { + this.clientCapabilities; - return { - // Perform incremental syncs - // Incremental sync is disabled for now due to not being able to get the - // old text in ASTProvider - // textDocumentSync: TextDocumentSyncKind.Incremental, - textDocumentSync: TextDocumentSyncKind.Full, - // documentHighlightProvider: true, - // foldingRangeProvider: true, - }; - } + return { + // Perform incremental syncs + // Incremental sync is disabled for now due to not being able to get the + // old text in ASTProvider + // textDocumentSync: TextDocumentSyncKind.Incremental, + textDocumentSync: TextDocumentSyncKind.Full, + // documentHighlightProvider: true, + // foldingRangeProvider: true, + }; + } } \ No newline at end of file diff --git a/server/src/forest.ts b/server/src/forest.ts index 053794a..87d7c24 100644 --- a/server/src/forest.ts +++ b/server/src/forest.ts @@ -1,27 +1,27 @@ -import { Tree } from 'tree-sitter'; +import { Tree } from "tree-sitter"; export interface IForest { - getTree(uri: string): Tree; - setTree(uri: string, tree: Tree): void; - removeTree(uri: string): boolean; + getTree(uri: string): Tree; + setTree(uri: string, tree: Tree): void; + removeTree(uri: string): boolean; } export class Forest implements IForest { - private trees: Map; + private trees: Map; - constructor() { - this.trees = new Map(); - } + constructor() { + this.trees = new Map(); + } - public getTree(uri: string): Tree { - return this.trees.get(uri); - } + public getTree(uri: string): Tree { + return this.trees.get(uri); + } - public setTree(uri: string, tree: Tree): void { - this.trees.set(uri, tree); - } + public setTree(uri: string, tree: Tree): void { + this.trees.set(uri, tree); + } - public removeTree(uri: string): boolean { - return this.trees.delete(uri); - } -} \ No newline at end of file + public removeTree(uri: string): boolean { + return this.trees.delete(uri); + } +} diff --git a/server/src/index.ts b/server/src/index.ts index b7bf7e8..1da4636 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,35 +1,35 @@ import { - createConnection, - IConnection, - InitializeParams, - ProposedFeatures, -} from 'vscode-languageserver'; + createConnection, + IConnection, + InitializeParams, + ProposedFeatures, +} from "vscode-languageserver"; -import { ILanguageServer } from './server'; -import { rebuildTreeSitter } from './util/rebuilder'; +import { ILanguageServer } from "./server"; +import { rebuildTreeSitter } from "./util/rebuilder"; const connection: IConnection = createConnection(ProposedFeatures.all); connection.onInitialize(async (params: InitializeParams) => { - connection.console.info('Initializing Elm language server...'); + connection.console.info("Initializing Elm language server..."); - connection.console.info('Rebuilding tree-sitter for local Electron version'); - const rebuildResult: [void | Error, void | Error] = await rebuildTreeSitter(); - for (const result of rebuildResult) { - if (result) { - connection.console.error('Rebuild failed!'); - connection.console.error(result.toString()); + connection.console.info("Rebuilding tree-sitter for local Electron version"); + const rebuildResult: [void | Error, void | Error] = await rebuildTreeSitter(); + for (const result of rebuildResult) { + if (result) { + connection.console.error("Rebuild failed!"); + connection.console.error(result.toString()); - return null; - } - } - connection.console.info('Rebuild succeeded!'); + return null; + } + } + connection.console.info("Rebuild succeeded!"); - const { Server } = await import('./server'); - const server: ILanguageServer = new Server(connection, params); + const { Server } = await import("./server"); + const server: ILanguageServer = new Server(connection, params); - return server.capabilities; + return server.capabilities; }); // Listen on the connection -connection.listen(); \ No newline at end of file +connection.listen(); diff --git a/server/src/position.ts b/server/src/position.ts index d6a9594..3914ba2 100644 --- a/server/src/position.ts +++ b/server/src/position.ts @@ -2,30 +2,30 @@ import { Point as TSPosition } from 'tree-sitter'; import { Position as VSPosition } from 'vscode-languageserver'; export class Position { - private row: number; - private col: number; + private row: number; + private col: number; - constructor(row: number, col: number) { - this.row = row; - this.col = col; - } + constructor(row: number, col: number) { + this.row = row; + this.col = col; + } - public static FROM_VS_POSITION(position: VSPosition): Position { - return new Position(position.line, position.character); - } + public static FROM_VS_POSITION(position: VSPosition): Position { + return new Position(position.line, position.character); + } - public static FROM_TS_POSITION(position: TSPosition): Position { - return new Position(position.row, position.column); - } + public static FROM_TS_POSITION(position: TSPosition): Position { + return new Position(position.row, position.column); + } - public toVSPosition(): VSPosition { - return VSPosition.create(this.row, this.col); - } + public toVSPosition(): VSPosition { + return VSPosition.create(this.row, this.col); + } - public toTSPosition(): TSPosition { - return { - row: this.row, - column: this.col, - }; - } + public toTSPosition(): TSPosition { + return { + row: this.row, + column: this.col, + }; + } } \ No newline at end of file diff --git a/server/src/providers/astProvider.ts b/server/src/providers/astProvider.ts index 87433b3..5b09893 100644 --- a/server/src/providers/astProvider.ts +++ b/server/src/providers/astProvider.ts @@ -1,91 +1,91 @@ -import * as Parser from 'tree-sitter'; +import * as Parser from "tree-sitter"; // tslint:disable-next-line no-duplicate-imports -import { Point, SyntaxNode, Tree } from 'tree-sitter'; -import * as TreeSitterElm from 'tree-sitter-elm'; +import { Point, SyntaxNode, Tree } from "tree-sitter"; +import * as TreeSitterElm from "tree-sitter-elm"; import { - DidChangeTextDocumentParams, - DidCloseTextDocumentParams, - DidOpenTextDocumentParams, - IConnection, - TextDocumentIdentifier, - TextDocumentItem, - VersionedTextDocumentIdentifier, -} from 'vscode-languageserver'; + DidChangeTextDocumentParams, + DidCloseTextDocumentParams, + DidOpenTextDocumentParams, + IConnection, + TextDocumentIdentifier, + TextDocumentItem, + VersionedTextDocumentIdentifier, +} from "vscode-languageserver"; -import { IForest } from '../forest'; -import { Position } from '../position'; +import { IForest } from "../forest"; +import { Position } from "../position"; export class ASTProvider { - private connection: IConnection; - private forest: IForest; - private parser: Parser; + private connection: IConnection; + private forest: IForest; + private parser: Parser; - constructor(connection: IConnection, forest: IForest) { - this.connection = connection; - this.forest = forest; - this.parser = new Parser(); - this.parser.setLanguage(TreeSitterElm); + constructor(connection: IConnection, forest: IForest) { + this.connection = connection; + this.forest = forest; + this.parser = new Parser(); + this.parser.setLanguage(TreeSitterElm); - this.connection.onDidOpenTextDocument(this.handleOpenTextDocument); - this.connection.onDidChangeTextDocument(this.handleChangeTextDocument); - this.connection.onDidCloseTextDocument(this.handleCloseTextDocument); - } + this.connection.onDidOpenTextDocument(this.handleOpenTextDocument); + this.connection.onDidChangeTextDocument(this.handleChangeTextDocument); + this.connection.onDidCloseTextDocument(this.handleCloseTextDocument); + } - protected handleOpenTextDocument = async (params: DidOpenTextDocumentParams): Promise => { - this.connection.console.log('Opened text document, going to parse it'); - const document: TextDocumentItem = params.textDocument; - const tree: Tree = this.parser.parse(document.text); - this.forest.setTree(document.uri, tree); - }; - - protected handleChangeTextDocument = async ( - params: DidChangeTextDocumentParams - ): Promise => { - this.connection.console.log('Changed text document, going to parse it'); - const document: VersionedTextDocumentIdentifier = params.textDocument; - let tree: Tree = this.forest.getTree(document.uri); - if (tree !== undefined) { - for (const changeEvent of params.contentChanges) { - if (changeEvent.range && changeEvent.rangeLength) { - // range is range of the change. end is exclusive - // rangeLength is length of text removed - // text is new text - const { range, rangeLength, text } = changeEvent; - const startIndex: number = range.start.line * range.start.character; - const oldEndIndex: number = startIndex + rangeLength - 1; - tree.edit({ - startIndex, // index in old doc the change started - oldEndIndex, // end index for old version of text - newEndIndex: range.end.line * range.end.character - 1, // end index for new version of text + protected handleOpenTextDocument = async (params: DidOpenTextDocumentParams): Promise => { + this.connection.console.log("Opened text document, going to parse it"); + const document: TextDocumentItem = params.textDocument; + const tree: Tree = this.parser.parse(document.text); + this.forest.setTree(document.uri, tree); + } - startPosition: Position.FROM_VS_POSITION(range.start).toTSPosition(), // position in old doc change started - oldEndPosition: this.computeEndPosition(startIndex, oldEndIndex, tree), // position in old doc change ended. - newEndPosition: Position.FROM_VS_POSITION(range.end).toTSPosition(), // position in new doc change ended - }); - tree = this.parser.parse(text, tree); - } else { - tree = this.buildTree(changeEvent.text); - } - } - } + protected handleChangeTextDocument = async ( + params: DidChangeTextDocumentParams, + ): Promise => { + this.connection.console.log("Changed text document, going to parse it"); + const document: VersionedTextDocumentIdentifier = params.textDocument; + let tree: Tree = this.forest.getTree(document.uri); + if (tree !== undefined) { + for (const changeEvent of params.contentChanges) { + if (changeEvent.range && changeEvent.rangeLength) { + // range is range of the change. end is exclusive + // rangeLength is length of text removed + // text is new text + const { range, rangeLength, text } = changeEvent; + const startIndex: number = range.start.line * range.start.character; + const oldEndIndex: number = startIndex + rangeLength - 1; + tree.edit({ + startIndex, // index in old doc the change started + oldEndIndex, // end index for old version of text + newEndIndex: range.end.line * range.end.character - 1, // end index for new version of text - this.forest.setTree(document.uri, tree); - }; + startPosition: Position.FROM_VS_POSITION(range.start).toTSPosition(), // position in old doc change started + oldEndPosition: this.computeEndPosition(startIndex, oldEndIndex, tree), // position in old doc change ended. + newEndPosition: Position.FROM_VS_POSITION(range.end).toTSPosition(), // position in new doc change ended + }); + tree = this.parser.parse(text, tree); + } else { + tree = this.buildTree(changeEvent.text); + } + } + } - protected handleCloseTextDocument = async (params: DidCloseTextDocumentParams): Promise => { - const document: TextDocumentIdentifier = params.textDocument; - this.forest.removeTree(document.uri); - }; + this.forest.setTree(document.uri, tree); + } - private buildTree = (text: string): Tree => { - return this.parser.parse(text); - }; + protected handleCloseTextDocument = async (params: DidCloseTextDocumentParams): Promise => { + const document: TextDocumentIdentifier = params.textDocument; + this.forest.removeTree(document.uri); + } - private computeEndPosition = (startIndex: number, endIndex: number, tree: Tree): Point => { - // TODO handle case where this method call fails for whatever reason - const node: SyntaxNode = tree.rootNode.descendantForIndex(startIndex, endIndex); + private buildTree = (text: string): Tree => { + return this.parser.parse(text); + } - return node.endPosition; - }; -} \ No newline at end of file + private computeEndPosition = (startIndex: number, endIndex: number, tree: Tree): Point => { + // TODO handle case where this method call fails for whatever reason + const node: SyntaxNode = tree.rootNode.descendantForIndex(startIndex, endIndex); + + return node.endPosition; + } +} diff --git a/server/src/server.ts b/server/src/server.ts index 91a8456..cca50f2 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -1,34 +1,34 @@ -import { Connection, InitializeParams, InitializeResult } from 'vscode-languageserver'; +import { Connection, InitializeParams, InitializeResult } from "vscode-languageserver"; -import { CapabilityCalculator } from './capabilityCalculator'; -import { Forest } from './forest'; -import { ASTProvider } from './providers/astProvider'; +import { CapabilityCalculator } from "./capabilityCalculator"; +import { Forest } from "./forest"; +import { ASTProvider } from "./providers/astProvider"; export interface ILanguageServer { - readonly capabilities: InitializeResult; + readonly capabilities: InitializeResult; } export class Server implements ILanguageServer { - public connection: Connection; - private calculator: CapabilityCalculator; - private forest: Forest; + public connection: Connection; + private calculator: CapabilityCalculator; + private forest: Forest; - constructor(connection: Connection, params: InitializeParams) { - this.connection = connection; - this.forest = new Forest(); + constructor(connection: Connection, params: InitializeParams) { + this.connection = connection; + this.forest = new Forest(); - this.registerProviders(); - } + this.registerProviders(); + } - get capabilities(): InitializeResult { - return { - capabilities: this.calculator.capabilities, - }; - } + get capabilities(): InitializeResult { + return { + capabilities: this.calculator.capabilities, + }; + } - private registerProviders(): void { - new ASTProvider(this.connection, this.forest); - // new DocumentHighlightProvider(this.connection, this.forest); - // new FoldingRangeProvider(this.connection, this.forest); - } -} \ No newline at end of file + private registerProviders(): void { + new ASTProvider(this.connection, this.forest); + // new DocumentHighlightProvider(this.connection, this.forest); + // new FoldingRangeProvider(this.connection, this.forest); + } +} diff --git a/server/src/server.ts.bak b/server/src/server.ts.bak index ff36555..6295de2 100644 --- a/server/src/server.ts.bak +++ b/server/src/server.ts.bak @@ -2,20 +2,20 @@ import * as cp from 'child_process'; import Uri from 'vscode-uri/lib/umd' import { - createConnection, - TextDocuments, - TextDocument, - Diagnostic, - DiagnosticSeverity, - ProposedFeatures, - InitializeParams, - DidChangeConfigurationNotification, - CompletionItem, - CompletionItemKind, - TextDocumentPositionParams, - Range, - Position, - TextEdit + createConnection, + TextDocuments, + TextDocument, + Diagnostic, + DiagnosticSeverity, + ProposedFeatures, + InitializeParams, + DidChangeConfigurationNotification, + CompletionItem, + CompletionItemKind, + TextDocumentPositionParams, + Range, + Position, + TextEdit } from 'vscode-languageserver'; // import { ElmAnalyse } from './elmAnalyse'; @@ -32,187 +32,187 @@ let hasWorkspaceFolderCapability: boolean = false; let rootPath: string = undefined; connection.onInitialize((params: InitializeParams) => { - let capabilities = params.capabilities; - this.rootPath = params.rootPath; + let capabilities = params.capabilities; + this.rootPath = params.rootPath; - // Does the client support the `workspace/configuration` request? - // If not, we will fall back using global settings - hasConfigurationCapability = - capabilities.workspace && !!capabilities.workspace.configuration; - hasWorkspaceFolderCapability = - capabilities.workspace && !!capabilities.workspace.workspaceFolders; + // Does the client support the `workspace/configuration` request? + // If not, we will fall back using global settings + hasConfigurationCapability = + capabilities.workspace && !!capabilities.workspace.configuration; + hasWorkspaceFolderCapability = + capabilities.workspace && !!capabilities.workspace.workspaceFolders; - return { - capabilities: { - textDocumentSync: documents.syncKind, - // Tell the client that the server supports code completion - completionProvider: { - resolveProvider: true - } - } - }; + return { + capabilities: { + textDocumentSync: documents.syncKind, + // Tell the client that the server supports code completion + completionProvider: { + resolveProvider: true + } + } + }; }); connection.onInitialized(() => { - if (hasConfigurationCapability) { - // Register for all configuration changes. - connection.client.register( - DidChangeConfigurationNotification.type, - undefined - ); - } - if (hasWorkspaceFolderCapability) { - connection.workspace.onDidChangeWorkspaceFolders(_event => { - connection.console.log('Workspace folder change event received.'); - }); - } + if (hasConfigurationCapability) { + // Register for all configuration changes. + connection.client.register( + DidChangeConfigurationNotification.type, + undefined + ); + } + if (hasWorkspaceFolderCapability) { + connection.workspace.onDidChangeWorkspaceFolders(_event => { + connection.console.log('Workspace folder change event received.'); + }); + } }); documents.onDidOpen(params => { - // const elmAnalyseIssues: IElmIssue[] = []; - // const elmAnalyse = new ElmAnalyse(elmAnalyseIssues); - // runLinter(connection, this.rootPath, params.document); - validateTextDocument(params.document); + // const elmAnalyseIssues: IElmIssue[] = []; + // const elmAnalyse = new ElmAnalyse(elmAnalyseIssues); + // runLinter(connection, this.rootPath, params.document); + validateTextDocument(params.document); }); documents.onDidSave(params => { - // const elmAnalyseIssues: IElmIssue[] = []; - // const elmAnalyse = new ElmAnalyse(elmAnalyseIssues); - // runLinter(connection, this.rootPath, params.document); + // const elmAnalyseIssues: IElmIssue[] = []; + // const elmAnalyse = new ElmAnalyse(elmAnalyseIssues); + // runLinter(connection, this.rootPath, params.document); - validateTextDocument(params.document); + validateTextDocument(params.document); }); async function validateTextDocument(textDocument: TextDocument): Promise { - connection.console.log('Validate text'); - // let uri = Uri.parse(textDocument.uri); + connection.console.log('Validate text'); + // let uri = Uri.parse(textDocument.uri); - // let diagnostics: Diagnostic[] = [] - // try { - // await Compiler.compileToString(uri.fsPath, { report: 'json' }) + // let diagnostics: Diagnostic[] = [] + // try { + // await Compiler.compileToString(uri.fsPath, { report: 'json' }) - // var x = await Compiler.findAllDependencies(uri.fsPath); - // connection.console.log(x); - - // } catch (err) { - // const issues = JSON.parse(err.message.split('\n')[1]); - // const byFile = issues.reduce((acc: any, issue: any) => { - // if (acc[issue.file]) { - // acc[issue.file].push(issue); - // } else { - // acc[issue.file] = [issue]; - // } - - // return acc; - // }, {}); - - // Object.keys(byFile).forEach((file: string) => { - // byFile[file].map((issue: any) => { - // diagnostics.push( { - // severity: DiagnosticSeverity.Error, - // source: "Elm", - // message: issue.details, - // range: { - // start: { - // line: issue.region.start.line - 1, - // character: issue.region.start.column - 1, - // }, - // end: { - // line: issue.region.end.line - 1, - // character: issue.region.end.column - 1, - // }, - // }, - // }); - // }); - - // }); - // } - // finally { - // connection.sendDiagnostics({ - // uri: textDocument.uri, - // diagnostics: diagnostics, - // }); - // } + // var x = await Compiler.findAllDependencies(uri.fsPath); + // connection.console.log(x); + + // } catch (err) { + // const issues = JSON.parse(err.message.split('\n')[1]); + // const byFile = issues.reduce((acc: any, issue: any) => { + // if (acc[issue.file]) { + // acc[issue.file].push(issue); + // } else { + // acc[issue.file] = [issue]; + // } + + // return acc; + // }, {}); + + // Object.keys(byFile).forEach((file: string) => { + // byFile[file].map((issue: any) => { + // diagnostics.push( { + // severity: DiagnosticSeverity.Error, + // source: "Elm", + // message: issue.details, + // range: { + // start: { + // line: issue.region.start.line - 1, + // character: issue.region.start.column - 1, + // }, + // end: { + // line: issue.region.end.line - 1, + // character: issue.region.end.column - 1, + // }, + // }, + // }); + // }); + + // }); + // } + // finally { + // connection.sendDiagnostics({ + // uri: textDocument.uri, + // diagnostics: diagnostics, + // }); + // } }; connection.onDidChangeWatchedFiles(_change => { - // Monitored files have change in VSCode - connection.console.log('We received an file change event'); + // Monitored files have change in VSCode + connection.console.log('We received an file change event'); }); // This handler provides the initial list of the completion items. connection.onCompletion( - (_textDocumentPosition: TextDocumentPositionParams): CompletionItem[] => { - // The pass parameter contains the position of the text document in - // which code complete got requested. For the example we ignore this - // info and always provide the same completion items. - return [ - { - label: 'TypeScript', - kind: CompletionItemKind.Text, - data: 1 - }, - { - label: 'JavaScript', - kind: CompletionItemKind.Text, - data: 2 - } - ]; - } + (_textDocumentPosition: TextDocumentPositionParams): CompletionItem[] => { + // The pass parameter contains the position of the text document in + // which code complete got requested. For the example we ignore this + // info and always provide the same completion items. + return [ + { + label: 'TypeScript', + kind: CompletionItemKind.Text, + data: 1 + }, + { + label: 'JavaScript', + kind: CompletionItemKind.Text, + data: 2 + } + ]; + } ); // This handler resolve additional information for the item selected in // the completion list. connection.onCompletionResolve( - (item: CompletionItem): CompletionItem => { - if (item.data === 1) { - (item.detail = 'TypeScript details'), - (item.documentation = 'TypeScript documentation'); - } else if (item.data === 2) { - (item.detail = 'JavaScript details'), - (item.documentation = 'JavaScript documentation'); - } - return item; - } + (item: CompletionItem): CompletionItem => { + if (item.data === 1) { + (item.detail = 'TypeScript details'), + (item.documentation = 'TypeScript documentation'); + } else if (item.data === 2) { + (item.detail = 'JavaScript details'), + (item.documentation = 'JavaScript documentation'); + } + return item; + } ); connection.onDocumentFormatting(params => { - const document = documents.get(params.textDocument.uri); - const text = document.getText(); + const document = documents.get(params.textDocument.uri); + const text = document.getText(); - const wholeDocument = Range.create( - Position.create(0, 0), - document.positionAt(text.length - 1), - ); + const wholeDocument = Range.create( + Position.create(0, 0), + document.positionAt(text.length - 1), + ); - return new Promise((resolve, reject) => { - const cmd = cp.exec('elm-format --stdin', (err, stdout) => { - err ? reject(err) : resolve(stdout); - }); + return new Promise((resolve, reject) => { + const cmd = cp.exec('elm-format --stdin', (err, stdout) => { + err ? reject(err) : resolve(stdout); + }); - cmd.stdin.write(text); - cmd.stdin.end(); - }) - .then(formattedText => { - return [TextEdit.replace(wholeDocument, formattedText)]; - }) - .catch(_err => { - // if ((err.message).indexOf('SYNTAX PROBLEM') >= 0) { - // return new LServer.ResponseError( - // LServer.ErrorCodes.ParseError, - // 'Running elm-format failed. Check the file for syntax errors.', - // ); - // } else { - // return new LServer.ResponseError( - // LServer.ErrorCodes.InternalError, - // 'Running elm-format failed. Install from ' + - // "https://github.com/avh4/elm-format and make sure it's on your path", - // ); - // } - return []; - }); + cmd.stdin.write(text); + cmd.stdin.end(); + }) + .then(formattedText => { + return [TextEdit.replace(wholeDocument, formattedText)]; + }) + .catch(_err => { + // if ((err.message).indexOf('SYNTAX PROBLEM') >= 0) { + // return new LServer.ResponseError( + // LServer.ErrorCodes.ParseError, + // 'Running elm-format failed. Check the file for syntax errors.', + // ); + // } else { + // return new LServer.ResponseError( + // LServer.ErrorCodes.InternalError, + // 'Running elm-format failed. Install from ' + + // "https://github.com/avh4/elm-format and make sure it's on your path", + // ); + // } + return []; + }); }); // Make the text document manager listen on the connection diff --git a/server/src/util/rebuilder.ts b/server/src/util/rebuilder.ts index be21c8d..f3e73b8 100644 --- a/server/src/util/rebuilder.ts +++ b/server/src/util/rebuilder.ts @@ -1,45 +1,46 @@ -import * as path from 'path'; -import * as prebuildInstall from 'prebuild-install'; +import * as path from "path"; +import * as prebuildInstall from "prebuild-install"; +import { RemoteClient, RemoteConsole } from "vscode-languageserver"; function packageToGithubRepo(name: string): string { - let repo: string; - switch (name) { - case 'tree-sitter': - repo = 'node-tree-sitter'; - break; - default: - repo = name; - } + let repo: string; + switch (name) { + case "tree-sitter": + repo = "node-tree-sitter"; + break; + default: + repo = name; + } - return repo; + return repo; } function downloadUrl(name: string, version: string, treeSitterRepo: boolean): string { - const repo: string = packageToGithubRepo(name); - let urlBase: string = `https://github.com/tree-sitter/${repo}/releases/download/v${version}/`; - if (!treeSitterRepo) { - urlBase = `https://github.com/razzeee/${repo}/releases/download/v${version}/`; - } - const prebuild: string = `${name}-v${version}-electron-v${process.versions.modules}-${ - process.platform - }-${process.arch}.tar.gz`; + const repo: string = packageToGithubRepo(name); + let urlBase: string = `https://github.com/tree-sitter/${repo}/releases/download/v${version}/`; + if (!treeSitterRepo) { + urlBase = `https://github.com/razzeee/${repo}/releases/download/v${version}/`; + } + const prebuild: string = `${name}-v${version}-electron-v${process.versions.modules}-${ + process.platform + }-${process.arch}.tar.gz`; - return `${urlBase}${prebuild}`; + return `${urlBase}${prebuild}`; } function fetchPrebuild(name: string, treeSitterRepo: boolean): Promise { - const pkgRoot: string = path.resolve(path.join(__dirname, '../../node_modules', name)); - //tslint:disable-next-line non-literal-require - const pkg: { name: string; version: string } = require(`${pkgRoot}/package.json`); - const url: string = downloadUrl(pkg.name, pkg.version, treeSitterRepo); + const pkgRoot: string = path.resolve(path.join(__dirname, "../../node_modules", name)); + // tslint:disable-next-line non-literal-require + const pkg: { name: string; version: string } = require(`${pkgRoot}/package.json`); + const url: string = downloadUrl(pkg.name, pkg.version, treeSitterRepo); - return new Promise((res, rej) => { - prebuildInstall.download(url, { pkg, path: pkgRoot }, (err: Error) => { - err ? rej(err) : res(); - }); - }); + return new Promise((res, rej) => { + prebuildInstall.download(url, { pkg, path: pkgRoot }, (err: Error) => { + err ? rej(err) : res(); + }); + }); } export function rebuildTreeSitter(): Promise<[void | Error, void | Error]> { - return Promise.all([fetchPrebuild('tree-sitter', true), fetchPrebuild('tree-sitter-elm', false)]); -} \ No newline at end of file + return Promise.all([fetchPrebuild("tree-sitter", true), fetchPrebuild("tree-sitter-elm", false)]); +} diff --git a/server/tsconfig.json b/server/tsconfig.json index 33df5ef..345495b 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -6,8 +6,15 @@ "sourceMap": true, "outDir": "out", "rootDir": "src", - "lib": ["es6"] + "lib": [ + "es6" + ] }, - "include": ["src"], - "exclude": ["node_modules", ".vscode-test"] -} + "include": [ + "src" + ], + "exclude": [ + "node_modules", + ".vscode-test" + ] +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 40b3887..fd270dc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,9 @@ "target": "es6", "outDir": "out", "rootDir": "src", - "lib": [ "es6" ], + "lib": [ + "es6" + ], "sourceMap": true }, "include": [ @@ -15,7 +17,11 @@ ".vscode-test" ], "references": [ - { "path": "./client" }, - { "path": "./server" } + { + "path": "./client" + }, + { + "path": "./server" + } ] } \ No newline at end of file