Initial commit

This commit is contained in:
Kolja Lampe
2018-07-09 02:05:22 +02:00
commit 27626b9b4d
31 changed files with 3493 additions and 0 deletions

3
.gitattributes vendored Normal file
View File

@@ -0,0 +1,3 @@
# Set default behavior to automatically normalize line endings.
* text=auto

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
out
node_modules
client/server
.vscode-test

58
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,58 @@
// A launch configuration that compiles the extension and then opens it inside a new window
{
"version": "0.2.0",
"configurations": [
{
"type": "extensionHost",
"request": "launch",
"name": "Launch Client",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceRoot}"
],
"stopOnEntry": false,
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/client/out/**/*.js"
],
"preLaunchTask": "npm: watch:client"
},
{
"type": "node",
"request": "attach",
"name": "Attach to Server",
"address": "localhost",
"protocol": "inspector",
"port": 6009,
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/server/out/**/*.js"
]
},
{
"name": "Language Server E2E Test",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceRoot}",
"--extensionTestsPath=${workspaceRoot}/client/out/test",
"${workspaceRoot}/client/testFixture"
],
"stopOnEntry": false,
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/client/out/test/**/*.js"
]
}
],
"compounds": [
{
"name": "Client + Server",
"configurations": [
"Launch Client",
"Attach to Server"
]
}
]
}

12
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,12 @@
{
"files.exclude": {
"out": false // set this to true to hide the "out" folder with the compiled JS files
},
"search.exclude": {
"out": true // set this to false to include "out" folder in search results
},
"typescript.tsdk": "./node_modules/typescript/lib",
"typescript.tsc.autoDetect": "off",
"npm.exclude": ["**/client", "**/server"],
"npm.enableScriptExplorer": true
}

75
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,75 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "compile",
"dependsOn": [
"compile:client",
"compile:server"
],
"problemMatcher": []
},
{
"type": "npm",
"script": "compile:client",
"group": "build",
"presentation": {
"panel": "dedicated",
"reveal": "never"
},
"problemMatcher": [
"$tsc"
]
},
{
"type": "npm",
"script": "compile:server",
"group": "build",
"presentation": {
"panel": "dedicated",
"reveal": "never"
},
"problemMatcher": [
"$tsc"
]
},
{
"label": "watch",
"dependsOn": [
"npm: watch:client",
"npm: watch:server"
],
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": []
},
{
"type": "npm",
"script": "watch:client",
"isBackground": true,
"group": "build",
"presentation": {
"panel": "dedicated",
"reveal": "never"
},
"problemMatcher": [
"$tsc-watch"
]
},
{
"type": "npm",
"script": "watch:server",
"isBackground": true,
"group": "build",
"presentation": {
"panel": "dedicated",
"reveal": "never"
},
"problemMatcher": [
"$tsc-watch"
]
}
]
}

13
.vscodeignore Normal file
View File

@@ -0,0 +1,13 @@
.vscode/**
**/*.ts
**/*.map
.gitignore
**/tsconfig.json
**/tsconfig.base.json
contributing.md
.travis.yml
client/node_modules/**
!client/node_modules/vscode-jsonrpc/**
!client/node_modules/vscode-languageclient/**
!client/node_modules/vscode-languageserver-protocol/**
!client/node_modules/vscode-languageserver-types/**

7
CHANGELOG.md Normal file
View File

@@ -0,0 +1,7 @@
# Change Log
All notable changes to the "elm-language-server" extension will be documented in this file.
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
## [Unreleased]
- Initial release

17
License.txt Normal file
View File

@@ -0,0 +1,17 @@
Copyright (c) Microsoft Corporation
All rights reserved.
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

38
README.md Normal file
View File

@@ -0,0 +1,38 @@
# LSP Example
Heavily documented sample code for https://code.visualstudio.com/docs/extensions/example-language-server.
## Functionality
This Language Server works for plain text file. It has the following language features:
- Completions
- Diagnostics regenerated on each file change or configuration change
It also includes an End-to-End test.
## Structure
```
.
├── client // Language Client
│ ├── src
│ │ ├── test // End to End tests for Language Client / Server
│ │ └── extension.ts // Language Client entry point
├── package.json // The extension manifest.
└── server // Language Server
└── src
└── server.ts // Language Server entry point
```
## Compile and Run
- Run `npm install` in this folder. This installs all necessary npm modules in both the client and server folder
- Open VS Code on this folder.
- Press Ctrl+Shift+B to compile the client and server.
- Switch to the Debug viewlet.
- Select `Launch Client` from the drop down.
- Run the lauch config.
- If you want to debug the server as well use the launch configuration `Attach to Server`
- In the [Extension Development Host] instance of VSCode, open a document in 'plain text' language mode.
- Type `j` or `t` to see `Javascript` and `TypeScript` completion.
- Enter text content such as `AAA aaa BBB`. The extension will emit diagnostics for all words in all-uppercase.

View File

@@ -0,0 +1,62 @@
{
"comments": {
"lineComment": "--",
"blockComment": [
"{-",
"-}"
]
},
"brackets": [
[
"{",
"}"
],
[
"[",
"]"
],
[
"(",
")"
]
],
"surroundingPairs": [
[
"{",
"}"
],
[
"[",
"]"
],
[
"(",
")"
],
[
"\"",
"\""
]
],
"autoClosingPairs": [
[
"(",
")"
],
[
"{",
"}"
],
[
"[",
"]"
],
[
"\"",
"\""
]
],
"folding": {
"offSide": true
}
}

1795
client/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

23
client/package.json Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "elm-language-server-client",
"description": "VSCode client for the elm language server",
"author": "Kolja Lampe",
"license": "MIT",
"publisher": "Kolja Lampe",
"version": "0.0.1",
"repository": {
"type": "git",
"url": "https://github.com/razzeee/elm-language-server"
},
"engines": {
"vscode": "^1.23.0"
},
"scripts": {
"update-vscode": "node ./node_modules/vscode/bin/install",
"postinstall": "node ./node_modules/vscode/bin/install"
},
"dependencies": {
"vscode": "^1.1.18",
"vscode-languageclient": "^4.2.1"
}
}

62
client/src/extension.ts Normal file
View File

@@ -0,0 +1,62 @@
'use strict';
import * as path from 'path';
import { workspace, ExtensionContext } from 'vscode';
import {
LanguageClient,
LanguageClientOptions,
ServerOptions,
TransportKind
} from 'vscode-languageclient';
let client: LanguageClient;
export function activate(context: ExtensionContext) {
// The server is implemented in node
let serverModule = context.asAbsolutePath(
path.join('server', 'out', 'server.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'] };
// 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
}
};
// Options to control the language client
let clientOptions: LanguageClientOptions = {
// Register the server for plain text documents
documentSelector: [{ scheme: 'file', language: 'elm' }],
synchronize: {
// Notify the server about file changes to '.clientrc files contained in the workspace
fileEvents: workspace.createFileSystemWatcher('**/.clientrc')
}
};
// 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();
}
export function deactivate(): Thenable<void> {
if (!client) {
return undefined;
}
return client.stop();
}

View File

@@ -0,0 +1,44 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
'use strict';
import * as vscode from 'vscode';
import * as assert from 'assert';
import { getDocUri, activate } from './helper';
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 }
]
});
});
});
async function testCompletion(
docUri: vscode.Uri,
position: vscode.Position,
expectedCompletionList: vscode.CompletionList
) {
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;
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

@@ -0,0 +1,42 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
'use strict';
import * as vscode from 'vscode'
import * as assert from 'assert'
import { getDocUri, activate } from './helper'
describe('Should get diagnostics', () => {
const docUri = getDocUri('diagnostics.txt')
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' }
])
})
})
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)
}
async function testDiagnostics(docUri: vscode.Uri, expectedDiagnostics: vscode.Diagnostic[]) {
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)
})
}

48
client/src/test/helper.ts Normal file
View File

@@ -0,0 +1,48 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
'use strict';
import * as vscode from 'vscode';
import * as path from 'path';
export let doc: vscode.TextDocument;
export let editor: vscode.TextEditor;
export let documentEol: string;
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);
}
}
async function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
export const getDocPath = (p: string) => {
return path.resolve(__dirname, '../../testFixture', p);
};
export const getDocUri = (p: string) => {
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));
}

15
client/src/test/index.ts Normal file
View File

@@ -0,0 +1,15 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
'use strict';
import * as testRunner from 'vscode/lib/testrunner';
testRunner.configure({
ui: 'bdd',
useColors: true,
timeout: 100000
});
module.exports = testRunner;

View File

View File

@@ -0,0 +1 @@
ANY browsers, ANY OS.

17
client/tsconfig.json Normal file
View File

@@ -0,0 +1,17 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "out",
"rootDir": "src",
"lib": [ "es6" ],
"sourceMap": true
},
"include": [
"src"
],
"exclude": [
"node_modules"
]
}

View File

@@ -0,0 +1,30 @@
{
"comments": {
// symbol used for single line comment. Remove this entry if your language does not support line comments
"lineComment": "//",
// symbols used for start and end a block comment. Remove this entry if your language does not support block comments
"blockComment": [ "/*", "*/" ]
},
// symbols used as brackets
"brackets": [
["{", "}"],
["[", "]"],
["(", ")"]
],
// symbols that are auto closed when typing
"autoClosingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
],
// symbols that that can be used to surround a selection
"surroundingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
]
}

26
package-lock.json generated Normal file
View File

@@ -0,0 +1,26 @@
{
"name": "elm-language-server",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@types/mocha": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.4.tgz",
"integrity": "sha512-XMHApnKWI0jvXU5gLcSTsRjJBpSzP0BG+2oGv98JFyS4a5R0tRy0oshHBRndb3BuHb9AwDKaUL8Ja7GfUvsG4g==",
"dev": true
},
"@types/node": {
"version": "8.10.21",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.21.tgz",
"integrity": "sha512-87XkD9qDXm8fIax+5y7drx84cXsu34ZZqfB7Cial3Q/2lxSoJ/+DRaWckkCbxP41wFSIrrb939VhzaNxj4eY1w==",
"dev": true
},
"typescript": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.8.3.tgz",
"integrity": "sha512-K7g15Bb6Ra4lKf7Iq2l/I5/En+hLIHmxWZGq3D4DIRNFxMNV6j2SHSvDOqs2tGd4UvD/fJvrwopzQXjLrT7Itw==",
"dev": true
}
}
}

78
package.json Normal file
View File

@@ -0,0 +1,78 @@
{
"name": "elm-language-server",
"displayName": "Elm",
"description": "A language server for Elm",
"author": "Kolja Lampe",
"license": "MIT",
"version": "1.0.0",
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/vscode-extension-samples"
},
"publisher": "vscode",
"categories": [],
"keywords": [
"multi-root ready"
],
"engines": {
"vscode": "^1.25.0"
},
"activationEvents": [
"onLanguage:plaintext"
],
"main": "./client/out/extension",
"contributes": {
"languages": [
{
"id": "elm",
"aliases": [
"Elm",
"elm"
],
"extensions": [
".elm"
],
"configuration": "./language-configuration.json"
}
],
"grammars": [
{
"language": "elm",
"scopeName": "source.elm",
"path": "./syntaxes/elm.tmLanguage"
}
],
"configuration": {
"type": "object",
"title": "Example configuration",
"properties": {
"languageServerExample.trace.server": {
"scope": "window",
"type": "string",
"enum": [
"off",
"messages",
"verbose"
],
"default": "off",
"description": "Traces the communication between VS Code and the language server."
}
}
}
},
"scripts": {
"vscode:prepublish": "cd client && npm run update-vscode && cd .. && npm run compile",
"compile:client": "tsc -p ./client/tsconfig.json",
"compile:server": "tsc -p ./server/tsconfig.json",
"watch:client": "tsc -w -p ./client/tsconfig.json",
"watch:server": "tsc -w -p ./server/tsconfig.json",
"compile": "npm run compile:client && npm run compile:server",
"postinstall": "cd client && npm install && cd ../server && npm install && cd ..",
"test": "sh ./scripts/e2e.sh"
},
"devDependencies": {
"@types/mocha": "^5.2.0",
"@types/node": "^8.0.0",
"typescript": "2.8.3"
}
}

6
scripts/e2e.sh Normal file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
export CODE_TESTS_PATH="$(pwd)/client/out/test"
export CODE_TESTS_WORKSPACE="$(pwd)/client/testFixture"
node "$(pwd)/client/node_modules/vscode/bin/test"

131
server/package-lock.json generated Normal file
View File

@@ -0,0 +1,131 @@
{
"name": "elm-language-server",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"cross-spawn": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.0.tgz",
"integrity": "sha1-glR3SrR4a4xbPPTfumbOVjkywlI=",
"requires": {
"lru-cache": "^4.0.1",
"which": "^1.2.9"
}
},
"find-elm-dependencies": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/find-elm-dependencies/-/find-elm-dependencies-1.0.2.tgz",
"integrity": "sha512-gnvu2zAKFEHd76zV/JkRvof7HNyM2X8yW5vflCfWbXeo9hmXMndz/SrEsTQFSXXgNqf0AdjhQSRPnG8JYR92oQ==",
"requires": {
"firstline": "1.2.0",
"lodash": "4.14.2"
}
},
"firstline": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/firstline/-/firstline-1.2.0.tgz",
"integrity": "sha1-yfSIbn9/vwr8EtcZQdzgaxkq6gU="
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
},
"lodash": {
"version": "4.14.2",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.14.2.tgz",
"integrity": "sha1-u8zOY3OkAPv9CoxnykL20e9BZDI="
},
"lru-cache": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz",
"integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==",
"requires": {
"pseudomap": "^1.0.2",
"yallist": "^2.1.2"
}
},
"node-elm-compiler": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/node-elm-compiler/-/node-elm-compiler-4.5.0.tgz",
"integrity": "sha512-XlyiHxqBizqEHaYj4UaO5/qmxeh1Ir/M02RLKsIgHBR7Z8snwoXfdpVntlfF64mcAGkuA1KY0CJsqk0IpAfyLQ==",
"requires": {
"cross-spawn": "4.0.0",
"find-elm-dependencies": "1.0.2",
"lodash": "4.14.2",
"temp": "^0.8.3"
}
},
"os-tmpdir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
},
"pseudomap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
},
"rimraf": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz",
"integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI="
},
"temp": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz",
"integrity": "sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=",
"requires": {
"os-tmpdir": "^1.0.0",
"rimraf": "~2.2.6"
}
},
"vscode-jsonrpc": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.6.2.tgz",
"integrity": "sha512-T24Jb5V48e4VgYliUXMnZ379ItbrXgOimweKaJshD84z+8q7ZOZjJan0MeDe+Ugb+uqERDVV8SBmemaGMSMugA=="
},
"vscode-languageserver": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-4.2.1.tgz",
"integrity": "sha512-5WAxaK1nEpe52ZaWNMqmd6rO5CIE72J/7UkGKPUTdGa0l0haWHS69tpRz+LetBlgTpP7PYacl4xhDaLZv82a+g==",
"requires": {
"vscode-languageserver-protocol": "^3.8.1",
"vscode-uri": "^1.0.3"
}
},
"vscode-languageserver-protocol": {
"version": "3.8.1",
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.8.1.tgz",
"integrity": "sha512-KdeetvQ2JavRiuE9afNrV5+xJZocj7NGPQwH4kpSFw5cp+0wijv87qgXfSEvmwPUaknhMBoSuSrSIG/LRrzWJQ==",
"requires": {
"vscode-jsonrpc": "^3.6.2",
"vscode-languageserver-types": "^3.8.1"
}
},
"vscode-languageserver-types": {
"version": "3.8.2",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.8.2.tgz",
"integrity": "sha512-2RMkyt1O1czGPCnkjKZWSio2D8oh3XlQ4zi4W2xL8q2Dvi4hB3/DEt+wYyzo4hmE2ZFP0RB8PXyzHyed7p1hbw=="
},
"vscode-uri": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.5.tgz",
"integrity": "sha1-O4majvccN/MFTXm9vdoxx7828g0="
},
"which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"requires": {
"isexe": "^2.0.0"
}
},
"yallist": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
}
}
}

21
server/package.json Normal file
View File

@@ -0,0 +1,21 @@
{
"name": "elm-language-server",
"description": "Implementation of a elm language server in node.",
"version": "1.0.0",
"author": "Kolja Lampe",
"license": "MIT",
"publisher": "Kolja Lampe",
"engines": {
"node": "*"
},
"repository": {
"type": "git",
"url": "https://github.com/razzeee/elm-language-server"
},
"dependencies": {
"vscode-languageserver": "^4.2.1",
"node-elm-compiler": "^4.5.0",
"vscode-uri": "^1.0.5"
},
"scripts": {}
}

210
server/src/server.ts Normal file
View File

@@ -0,0 +1,210 @@
'use strict';
import * as cp from 'child_process';
import Uri from 'vscode-uri'
const Compiler = require('node-elm-compiler');
import {
createConnection,
TextDocuments,
TextDocument,
Diagnostic,
DiagnosticSeverity,
ProposedFeatures,
InitializeParams,
DidChangeConfigurationNotification,
CompletionItem,
CompletionItemKind,
TextDocumentPositionParams,
Range,
Position,
TextEdit
} from 'vscode-languageserver';
// Create a connection for the server. The connection uses Node's IPC as a transport.
// Also include all preview / proposed LSP features.
let connection = createConnection(ProposedFeatures.all);
// Create a simple text document manager. The text document manager
// supports full document sync only
let documents: TextDocuments = new TextDocuments();
let hasConfigurationCapability: boolean = false;
let hasWorkspaceFolderCapability: boolean = false;
connection.onInitialize((params: InitializeParams) => {
let capabilities = params.capabilities;
// 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
}
}
};
});
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.');
});
}
});
documents.onDidOpen(params => {
validateTextDocument(params.document);
});
documents.onDidSave(params => {
validateTextDocument(params.document);
});
async function validateTextDocument(textDocument: TextDocument): Promise<void> {
connection.console.log('Validate text');
let uri = Uri.parse(textDocument.uri);
let diagnostics: Diagnostic[] = []
try {
await Compiler.compileToString(uri.fsPath, { report: 'json' })
} 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');
});
// 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
}
];
}
);
// 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;
}
);
connection.onDocumentFormatting(params => {
const document = documents.get(params.textDocument.uri);
const text = document.getText();
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);
});
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
// for open, change and close text document events
documents.listen(connection);
// Listen on the connection
connection.listen();

18
server/tsconfig.json Normal file
View File

@@ -0,0 +1,18 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"outDir": "out",
"rootDir": "src",
"lib": [ "es6" ]
},
"include": [
"src"
],
"exclude": [
"node_modules"
]
}

602
syntaxes/elm.tmLanguage Normal file
View File

@@ -0,0 +1,602 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>fileTypes</key>
<array>
<string>elm</string>
</array>
<key>name</key>
<string>Elm</string>
<key>patterns</key>
<array>
<dict>
<key>captures</key>
<dict>
<key>1</key>
<dict>
<key>name</key>
<string>punctuation.definition.entity.elm</string>
</dict>
<key>2</key>
<dict>
<key>name</key>
<string>punctuation.definition.entity.elm</string>
</dict>
</dict>
<key>match</key>
<string>(`)[a-zA-Z_']*?(`)</string>
<key>name</key>
<string>keyword.operator.function.infix.elm</string>
</dict>
<dict>
<key>match</key>
<string>\(\)</string>
<key>name</key>
<string>constant.language.unit.elm</string>
</dict>
<dict>
<key>begin</key>
<string>^\b((effect|port)\s+)?(module)\s+</string>
<key>beginCaptures</key>
<dict>
<key>1</key>
<dict>
<key>name</key>
<string>keyword.other.elm</string>
</dict>
<key>3</key>
<dict>
<key>name</key>
<string>keyword.other.elm</string>
</dict>
</dict>
<key>end</key>
<string>$|;</string>
<key>endCaptures</key>
<dict>
<key>1</key>
<dict>
<key>name</key>
<string>keyword.other.elm</string>
</dict>
</dict>
<key>name</key>
<string>meta.declaration.module.elm</string>
<key>patterns</key>
<array>
<dict>
<key>include</key>
<string>#module_name</string>
</dict>
<dict>
<key>begin</key>
<string>(where)\s*\{</string>
<key>beginCaptures</key>
<dict>
<key>1</key>
<dict>
<key>name</key>
<string>keyword.other.elm</string>
</dict>
</dict>
<key>end</key>
<string>\}</string>
<key>patterns</key>
<array>
<dict>
<key>include</key>
<string>#type_signature</string>
</dict>
</array>
</dict>
<dict>
<key>match</key>
<string>(exposing)</string>
<key>name</key>
<string>keyword.other.elm</string>
</dict>
<dict>
<key>include</key>
<string>#module_exports</string>
</dict>
<dict>
<key>match</key>
<string>(where)</string>
<key>name</key>
<string>keyword.other.elm</string>
</dict>
<dict>
<key>match</key>
<string>[a-z]+</string>
<key>name</key>
<string>invalid</string>
</dict>
</array>
</dict>
<dict>
<key>begin</key>
<string>^\b(import)\s+((open)\s+)?</string>
<key>beginCaptures</key>
<dict>
<key>1</key>
<dict>
<key>name</key>
<string>keyword.other.elm</string>
</dict>
<key>3</key>
<dict>
<key>name</key>
<string>invalid</string>
</dict>
</dict>
<key>end</key>
<string>($|;)</string>
<key>name</key>
<string>meta.import.elm</string>
<key>patterns</key>
<array>
<dict>
<key>match</key>
<string>(as|exposing)</string>
<key>name</key>
<string>keyword.import.elm</string>
</dict>
<dict>
<key>include</key>
<string>#module_name</string>
</dict>
<dict>
<key>include</key>
<string>#module_exports</string>
</dict>
</array>
</dict>
<dict>
<key>begin</key>
<string>(\[)(glsl)(\|)</string>
<key>beginCaptures</key>
<dict>
<key>1</key>
<dict>
<key>name</key>
<string>keyword.other.elm</string>
</dict>
<key>2</key>
<dict>
<key>name</key>
<string>support.function.prelude.elm</string>
</dict>
<key>3</key>
<dict>
<key>name</key>
<string>keyword.other.elm</string>
</dict>
</dict>
<key>end</key>
<string>(\|\])</string>
<key>endCaptures</key>
<dict>
<key>1</key>
<dict>
<key>name</key>
<string>keyword.other.elm</string>
</dict>
</dict>
<key>name</key>
<string>entity.glsl.elm</string>
<key>patterns</key>
<array>
<dict>
<key>include</key>
<string>source.glsl</string>
</dict>
</array>
</dict>
<dict>
<key>match</key>
<string>\b(type alias|type|case|of|let|in|as)\s+</string>
<key>name</key>
<string>keyword.other.elm</string>
</dict>
<dict>
<key>match</key>
<string>\b(if|then|else)\s+</string>
<key>name</key>
<string>keyword.control.elm</string>
</dict>
<dict>
<key>comment</key>
<string>Floats are always decimal</string>
<key>match</key>
<string>\b([0-9]+\.[0-9]+([eE][+-]?[0-9]+)?|[0-9]+[eE][+-]?[0-9]+)\b</string>
<key>name</key>
<string>constant.numeric.float.elm</string>
</dict>
<dict>
<key>match</key>
<string>\b([0-9]+)\b</string>
<key>name</key>
<string>constant.numeric.elm</string>
</dict>
<dict>
<key>begin</key>
<string>"""</string>
<key>beginCaptures</key>
<dict>
<key>0</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.begin.elm</string>
</dict>
</dict>
<key>end</key>
<string>"""</string>
<key>endCaptures</key>
<dict>
<key>0</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.end.elm</string>
</dict>
</dict>
<key>name</key>
<string>string.quoted.double.elm</string>
<key>patterns</key>
<array>
<dict>
<key>match</key>
<string>\\(NUL|SOH|STX|ETX|EOT|ENQ|ACK|BEL|BS|HT|LF|VT|FF|CR|SO|SI|DLE|DC1|DC2|DC3|DC4|NAK|SYN|ETB|CAN|EM|SUB|ESC|FS|GS|RS|US|SP|DEL|[abfnrtv\\'\&amp;])</string>
<key>name</key>
<string>constant.character.escape.elm</string>
</dict>
<dict>
<key>match</key>
<string>\^[A-Z@\[\]\\\^_]</string>
<key>name</key>
<string>constant.character.escape.control.elm</string>
</dict>
</array>
</dict>
<dict>
<key>begin</key>
<string>"</string>
<key>beginCaptures</key>
<dict>
<key>0</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.begin.elm</string>
</dict>
</dict>
<key>end</key>
<string>"</string>
<key>endCaptures</key>
<dict>
<key>0</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.end.elm</string>
</dict>
</dict>
<key>name</key>
<string>string.quoted.double.elm</string>
<key>patterns</key>
<array>
<dict>
<key>match</key>
<string>\\(NUL|SOH|STX|ETX|EOT|ENQ|ACK|BEL|BS|HT|LF|VT|FF|CR|SO|SI|DLE|DC1|DC2|DC3|DC4|NAK|SYN|ETB|CAN|EM|SUB|ESC|FS|GS|RS|US|SP|DEL|[abfnrtv\\\"'\&amp;])</string>
<key>name</key>
<string>constant.character.escape.elm</string>
</dict>
<dict>
<key>match</key>
<string>\^[A-Z@\[\]\\\^_]</string>
<key>name</key>
<string>constant.character.escape.control.elm</string>
</dict>
</array>
</dict>
<dict>
<key>captures</key>
<dict>
<key>1</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.begin.elm</string>
</dict>
<key>2</key>
<dict>
<key>name</key>
<string>constant.character.escape.elm</string>
</dict>
<key>3</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.end.elm</string>
</dict>
</dict>
<key>match</key>
<string>(?x)
(')
(?:
[\ -\[\]-~] # Basic Char
| (\\(?:NUL|SOH|STX|ETX|EOT|ENQ|ACK|BEL|BS|HT|LF|VT|FF|CR|SO|SI|DLE
|DC1|DC2|DC3|DC4|NAK|SYN|ETB|CAN|EM|SUB|ESC|FS|GS|RS
|US|SP|DEL|[abfnrtv\\\"'\&amp;])) # Escapes
| (\^[A-Z@\[\]\\\^_]) # Control Chars
)
(')</string>
<key>name</key>
<string>string.quoted.single.elm</string>
</dict>
<dict>
<key>begin</key>
<string>^(port\s+)?([a-z_][a-zA-Z0-9_']*|\([|!%$+\-.,=&lt;/&gt;]+\))\s*((:)([:]+)?)</string>
<key>beginCaptures</key>
<dict>
<key>1</key>
<dict>
<key>name</key>
<string>keyword.other.port.elm</string>
</dict>
<key>2</key>
<dict>
<key>name</key>
<string>entity.name.function.elm</string>
</dict>
<key>4</key>
<dict>
<key>name</key>
<string>keyword.other.colon.elm</string>
</dict>
<key>5</key>
<dict>
<key>name</key>
<string>invalid</string>
</dict>
</dict>
<key>end</key>
<string>$\n?</string>
<key>name</key>
<string>meta.function.type-declaration.elm</string>
<key>patterns</key>
<array>
<dict>
<key>include</key>
<string>#type_signature</string>
</dict>
</array>
</dict>
<dict>
<key>match</key>
<string>\bport\s+</string>
<key>name</key>
<string>keyword.other.port.elm</string>
</dict>
<dict>
<key>match</key>
<string>\b[A-Z]\w*\b</string>
<key>name</key>
<string>constant.other.elm</string>
</dict>
<dict>
<key>include</key>
<string>#comments</string>
</dict>
<dict>
<key>match</key>
<string>^[a-z][A-Za-z0-9_']*\s+</string>
<key>name</key>
<string>entity.name.function.elm</string>
</dict>
<dict>
<key>include</key>
<string>#infix_op</string>
</dict>
<dict>
<key>match</key>
<string>[|!%$?~+:\-.=&lt;/&gt;&amp;\\*^]+</string>
<key>name</key>
<string>keyword.operator.elm</string>
</dict>
<dict>
<key>captures</key>
<dict>
<key>1</key>
<dict>
<key>name</key>
<string>support.function.delimiter.elm</string>
</dict>
</dict>
<key>match</key>
<string>([\[\]\{\},])</string>
<key>name</key>
<string>constant.language.delimiter.elm</string>
</dict>
<dict>
<key>match</key>
<string>([\(\)])</string>
<key>name</key>
<string>keyword.other.parenthesis.elm</string>
</dict>
</array>
<key>repository</key>
<dict>
<key>block_comment</key>
<dict>
<key>applyEndPatternLast</key>
<integer>1</integer>
<key>begin</key>
<string>\{-(?!#)</string>
<key>captures</key>
<dict>
<key>0</key>
<dict>
<key>name</key>
<string>punctuation.definition.comment.elm</string>
</dict>
</dict>
<key>end</key>
<string>-\}</string>
<key>name</key>
<string>comment.block.elm</string>
<key>patterns</key>
<array>
<dict>
<key>include</key>
<string>#block_comment</string>
</dict>
</array>
</dict>
<key>comments</key>
<dict>
<key>patterns</key>
<array>
<dict>
<key>captures</key>
<dict>
<key>1</key>
<dict>
<key>name</key>
<string>punctuation.definition.comment.elm</string>
</dict>
</dict>
<key>match</key>
<string>(--).*$\n?</string>
<key>name</key>
<string>comment.line.double-dash.elm</string>
</dict>
<dict>
<key>include</key>
<string>#block_comment</string>
</dict>
</array>
</dict>
<key>infix_op</key>
<dict>
<key>match</key>
<string>(\([|!%$+:\-.=&lt;/&gt;]+\)|\(,+\))</string>
<key>name</key>
<string>entity.name.function.infix.elm</string>
</dict>
<key>module_exports</key>
<dict>
<key>begin</key>
<string>\(</string>
<key>end</key>
<string>\)</string>
<key>name</key>
<string>meta.declaration.exports.elm</string>
<key>patterns</key>
<array>
<dict>
<key>match</key>
<string>\b[a-z][a-zA-Z_'0-9]*</string>
<key>name</key>
<string>entity.name.function.elm</string>
</dict>
<dict>
<key>match</key>
<string>\b[A-Z][A-Za-z_'0-9]*</string>
<key>name</key>
<string>storage.type.elm</string>
</dict>
<dict>
<key>match</key>
<string>,</string>
<key>name</key>
<string>punctuation.separator.comma.elm</string>
</dict>
<dict>
<key>include</key>
<string>#infix_op</string>
</dict>
<dict>
<key>comment</key>
<string>So named because I don't know what to call this.</string>
<key>match</key>
<string>\(.*?\)</string>
<key>name</key>
<string>meta.other.unknown.elm</string>
</dict>
</array>
</dict>
<key>module_name</key>
<dict>
<key>match</key>
<string>[A-Z][A-Za-z._']*</string>
<key>name</key>
<string>support.other.module.elm</string>
</dict>
<key>type_signature</key>
<dict>
<key>patterns</key>
<array>
<dict>
<key>captures</key>
<dict>
<key>1</key>
<dict>
<key>name</key>
<string>entity.other.inherited-class.elm</string>
</dict>
<key>2</key>
<dict>
<key>name</key>
<string>variable.other.generic-type.elm</string>
</dict>
<key>3</key>
<dict>
<key>name</key>
<string>keyword.other.big-arrow.elm</string>
</dict>
</dict>
<key>match</key>
<string>\(\s*([A-Z][A-Za-z]*)\s+([a-z][A-Za-z_']*)\)\s*(=&gt;)</string>
<key>name</key>
<string>meta.class-constraint.elm</string>
</dict>
<dict>
<key>match</key>
<string>-&gt;</string>
<key>name</key>
<string>keyword.other.arrow.elm</string>
</dict>
<dict>
<key>match</key>
<string>=&gt;</string>
<key>name</key>
<string>keyword.other.big-arrow.elm</string>
</dict>
<dict>
<key>match</key>
<string>\b[a-z][a-zA-Z0-9_']*\b</string>
<key>name</key>
<string>variable.other.generic-type.elm</string>
</dict>
<dict>
<key>match</key>
<string>\b[A-Z][a-zA-Z0-9_']*\b</string>
<key>name</key>
<string>storage.type.elm</string>
</dict>
<dict>
<key>match</key>
<string>\(\)</string>
<key>name</key>
<string>support.constant.unit.elm</string>
</dict>
<dict>
<key>include</key>
<string>#comments</string>
</dict>
</array>
</dict>
</dict>
<key>scopeName</key>
<string>source.elm</string>
<key>uuid</key>
<string>2cb90e5e-6e98-456d-9a8a-b59935dbc4b0</string>
</dict>
</plist>

8
tsconfig.base.json Normal file
View File

@@ -0,0 +1,8 @@
{
"compilerOptions": {
"noImplicitAny": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
}

View File

@@ -0,0 +1,27 @@
# Welcome to your VS Code Extension
## What's in the folder
* This folder contains all of the files necessary for your extension.
* `package.json` - this is the manifest file in which you declare your language support and define
the location of the grammar file that has been copied into your extension.
* `syntaxes/elm.tmLanguage` - this is the Text mate grammar file that is used for tokenization.
* `language-configuration.json` - this the language configuration, defining the tokens that are used for
comments and brackets.
## Get up and running straight away
* Make sure the language configuration settings in `language-configuration.json` are accurate.
* Press `F5` to open a new window with your extension loaded.
* Create a new file with a file name suffix matching your language.
* Verify that syntax highlighting works and that the language configuration settings are working.
## Make changes
* You can relaunch the extension from the debug toolbar after making changes to the files listed above.
* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes.
## Add more language features
* To add features such as intellisense, hovers and validators check out the VS Code extenders documentation at
https://code.visualstudio.com/docs
## Install your extension
* To start using your extension with Visual Studio Code copy it into the `<user home>/.vscode/extensions` folder and restart Code.
* To share your extension with the world, read on https://code.visualstudio.com/docs about publishing an extension.