Fix linting

This commit is contained in:
Kolja Lampe
2019-01-27 17:16:55 +01:00
parent e44fec2c8b
commit d87a6da1b4
17 changed files with 566 additions and 535 deletions

23
.vscode/launch.json vendored
View File

@@ -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"
]
}
]
}
}

View File

@@ -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<string, LanguageClient> = new Map();
const clients: Map<string, LanguageClient> = 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<void> {
let promises: Thenable<void>[] = [];
for (let client of clients.values()) {
promises.push(client.stop());
}
return Promise.all(promises).then(() => undefined);
const promises: Array<Thenable<void>> = [];
for (const client of clients.values()) {
promises.push(client.stop());
}
return Promise.all(promises).then(() => undefined);
}

View File

@@ -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);
});
}

View File

@@ -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)
})
}
const actualDiagnostic = actualDiagnostics[i];
assert.equal(actualDiagnostic.message, expectedDiagnostic.message);
assert.deepEqual(actualDiagnostic.range, expectedDiagnostic.range);
assert.equal(actualDiagnostic.severity, expectedDiagnostic.severity);
});
}

View File

@@ -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<boolean> {
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));
}

View File

@@ -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;
module.exports = testRunner;

View File

@@ -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"
]
}

View File

@@ -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,
};
}
}

View File

@@ -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<string, Tree>;
private trees: Map<string, Tree>;
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);
}
}
public removeTree(uri: string): boolean {
return this.trees.delete(uri);
}
}

View File

@@ -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();
connection.listen();

View File

@@ -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,
};
}
}

View File

@@ -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<void> => {
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<void> => {
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<void> => {
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<void> => {
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<void> => {
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<void> => {
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;
};
}
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;
}
}

View File

@@ -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);
}
}
private registerProviders(): void {
new ASTProvider(this.connection, this.forest);
// new DocumentHighlightProvider(this.connection, this.forest);
// new FoldingRangeProvider(this.connection, this.forest);
}
}

View File

@@ -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<void> {
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<string>((resolve, reject) => {
const cmd = cp.exec('elm-format --stdin', (err, stdout) => {
err ? reject(err) : resolve(stdout);
});
return new Promise<string>((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 ((<string>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 ((<string>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

View File

@@ -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<void | Error> {
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)]);
}
return Promise.all([fetchPrebuild("tree-sitter", true), fetchPrebuild("tree-sitter-elm", false)]);
}

View File

@@ -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"
]
}

View File

@@ -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"
}
]
}