mirror of
https://github.com/jlengrand/elm-language-client-vscode.git
synced 2026-03-10 08:11:17 +00:00
Merge pull request #24 from elm-tooling/make-elm-and-elm-format-configurable
Make elm and elm-format configurable. Compute editor input with elm f…
This commit is contained in:
17
README.md
17
README.md
@@ -8,15 +8,19 @@ This vscode extension is in development and might be lacking features you know w
|
||||
- Formatting via elm-format
|
||||
|
||||
## Extension Settings
|
||||
|
||||
This extension contributes the following settings:
|
||||
|
||||
* `elmLS.trace.server`: enable/disable trace logging of client and server communication
|
||||
- `elmLS.trace.server`: Enable/disable trace logging of client and server communication
|
||||
- `elmLS.elmPath`: The path to your elm executeable.
|
||||
- `elmLS.elmFormatPath`: The path to your elm-format executeable.
|
||||
|
||||
## Editor Support
|
||||
|
||||
### Vim
|
||||
|
||||
#### coc.nvim
|
||||
|
||||
To enable support with [coc.nvim](https://github.com/neoclide/coc.nvim), run `:CocConfig` and add the language server config below.
|
||||
|
||||
```
|
||||
@@ -37,10 +41,11 @@ To enable support with [coc.nvim](https://github.com/neoclide/coc.nvim), run `:C
|
||||
```
|
||||
|
||||
#### ALE
|
||||
|
||||
For [ALE](https://github.com/w0rp/ale) support.
|
||||
|
||||
| Package Manager | Command |
|
||||
|---|---|
|
||||
|[Vim-Plug](https://github.com/junegunn/vim-plug)|`Plug 'antew/vim-elm-language-server'`|
|
||||
|[Vundle](https://github.com/VundleVim/Vundle.vim)|`Plugin 'antew/vim-elm-language-server'`|
|
||||
|[Pathogen](https://github.com/tpope/vim-pathogen)|<pre>cd ~/.vim/bundle<br>git clone https://github.com/antew/vim-elm-language-server.git</pre>|
|
||||
| Package Manager | Command |
|
||||
| ------------------------------------------------- | --------------------------------------------------------------------------------------------- |
|
||||
| [Vim-Plug](https://github.com/junegunn/vim-plug) | `Plug 'antew/vim-elm-language-server'` |
|
||||
| [Vundle](https://github.com/VundleVim/Vundle.vim) | `Plugin 'antew/vim-elm-language-server'` |
|
||||
| [Pathogen](https://github.com/tpope/vim-pathogen) | <pre>cd ~/.vim/bundle<br>git clone https://github.com/antew/vim-elm-language-server.git</pre> |
|
||||
|
||||
14
package.json
14
package.json
@@ -59,7 +59,7 @@
|
||||
],
|
||||
"configuration": {
|
||||
"type": "object",
|
||||
"title": "Debug configuration",
|
||||
"title": "ElmLS",
|
||||
"properties": {
|
||||
"elmLS.trace.server": {
|
||||
"scope": "window",
|
||||
@@ -71,6 +71,18 @@
|
||||
],
|
||||
"default": "off",
|
||||
"description": "Traces the communication between VS Code and the language server."
|
||||
},
|
||||
"elmLS.elmPath": {
|
||||
"scope": "window",
|
||||
"type": "string",
|
||||
"default": "elm",
|
||||
"description": "The path to your elm executeable."
|
||||
},
|
||||
"elmLS.elmFormatPath": {
|
||||
"scope": "window",
|
||||
"type": "string",
|
||||
"default": "elm-format",
|
||||
"description": "The path to your elm-format executeable."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import {
|
||||
createConnection,
|
||||
DidChangeConfigurationNotification,
|
||||
IConnection,
|
||||
InitializeParams,
|
||||
InitializeResult,
|
||||
@@ -46,5 +47,13 @@ connection.onInitialize(
|
||||
},
|
||||
);
|
||||
|
||||
connection.onInitialized(() => {
|
||||
// Register for all configuration changes.
|
||||
connection.client.register(
|
||||
DidChangeConfigurationNotification.type,
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
// Listen on the connection
|
||||
connection.listen();
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
TextDocument,
|
||||
} from "vscode-languageserver";
|
||||
import URI from "vscode-uri";
|
||||
import { DocumentEvents } from '../../util/documentEvents';
|
||||
import { TextDocumentEvents } from '../../util/textDocumentEvents';
|
||||
import { DocumentEvents } from "../../util/documentEvents";
|
||||
import { TextDocumentEvents } from "../../util/textDocumentEvents";
|
||||
import { ElmAnalyseDiagnostics } from "./elmAnalyseDiagnostics";
|
||||
import { ElmMakeDiagnostics } from "./elmMakeDiagnostics";
|
||||
|
||||
@@ -30,14 +30,16 @@ export class DiagnosticsProvider {
|
||||
private events: TextDocumentEvents;
|
||||
private elmMakeDiagnostics: ElmMakeDiagnostics;
|
||||
private elmAnalyseDiagnostics: ElmAnalyseDiagnostics;
|
||||
private connection: IConnection;
|
||||
private elmWorkspaceFolder: URI;
|
||||
private currentDiagnostics: {
|
||||
elmMake: Map<string, Diagnostic[]>;
|
||||
elmAnalyse: Map<string, Diagnostic[]>;
|
||||
};
|
||||
|
||||
constructor(connection: IConnection, elmWorkspaceFolder: URI, documentEvents: DocumentEvents) {
|
||||
constructor(
|
||||
private connection: IConnection,
|
||||
private elmWorkspaceFolder: URI,
|
||||
documentEvents: DocumentEvents,
|
||||
) {
|
||||
this.getDiagnostics = this.getDiagnostics.bind(this);
|
||||
this.newElmAnalyseDiagnostics = this.newElmAnalyseDiagnostics.bind(this);
|
||||
this.elmMakeIssueToDiagnostic = this.elmMakeIssueToDiagnostic.bind(this);
|
||||
@@ -58,9 +60,9 @@ export class DiagnosticsProvider {
|
||||
|
||||
this.currentDiagnostics = { elmMake: new Map(), elmAnalyse: new Map() };
|
||||
|
||||
this.events.on('open', this.getDiagnostics)
|
||||
this.events.on('change', this.getDiagnostics)
|
||||
this.events.on('save', this.getDiagnostics)
|
||||
this.events.on("open", this.getDiagnostics);
|
||||
this.events.on("change", this.getDiagnostics);
|
||||
this.events.on("save", this.getDiagnostics);
|
||||
}
|
||||
|
||||
private newElmAnalyseDiagnostics(diagnostics: Map<string, Diagnostic[]>) {
|
||||
@@ -152,4 +154,4 @@ export class DiagnosticsProvider {
|
||||
return DiagnosticSeverity.Error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,13 +8,14 @@ import {
|
||||
} from "vscode-languageserver";
|
||||
import URI from "vscode-uri";
|
||||
import * as utils from "../../util/elmUtils";
|
||||
import { Settings } from "../../util/settings";
|
||||
import { IElmIssue } from "./diagnosticsProvider";
|
||||
|
||||
export class ElmMakeDiagnostics {
|
||||
private connection: IConnection;
|
||||
private elmWorkspaceFolder: URI;
|
||||
|
||||
constructor(connection: IConnection, elmWorkspaceFolder: URI) {
|
||||
constructor(
|
||||
private connection: IConnection,
|
||||
private elmWorkspaceFolder: URI,
|
||||
) {
|
||||
this.connection = connection;
|
||||
this.elmWorkspaceFolder = elmWorkspaceFolder;
|
||||
}
|
||||
@@ -27,13 +28,15 @@ export class ElmMakeDiagnostics {
|
||||
);
|
||||
};
|
||||
|
||||
private checkForErrors(
|
||||
private async checkForErrors(
|
||||
connection: IConnection,
|
||||
rootPath: string,
|
||||
filename: string,
|
||||
): Promise<IElmIssue[]> {
|
||||
const settings = await Settings.getSettings(connection);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const makeCommand: string = "elm";
|
||||
const makeCommand: string = settings.elmPath;
|
||||
const cwd: string = rootPath;
|
||||
let make: cp.ChildProcess;
|
||||
if (utils.isWindows) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import diff from "fast-diff";
|
||||
import * as fs from "fs";
|
||||
import {
|
||||
DocumentFormattingParams,
|
||||
IConnection,
|
||||
@@ -7,15 +6,22 @@ import {
|
||||
TextEdit,
|
||||
} from "vscode-languageserver";
|
||||
import URI from "vscode-uri";
|
||||
import { DocumentEvents } from "../util/documentEvents";
|
||||
import { execCmd } from "../util/elmUtils";
|
||||
import { Settings } from "../util/settings";
|
||||
import { TextDocumentEvents } from "../util/textDocumentEvents";
|
||||
|
||||
export class ElmFormatProvider {
|
||||
private connection: IConnection;
|
||||
private elmWorkspaceFolder: URI;
|
||||
private events: TextDocumentEvents;
|
||||
|
||||
constructor(connection: IConnection, elmWorkspaceFolder: URI) {
|
||||
constructor(
|
||||
private connection: IConnection,
|
||||
private elmWorkspaceFolder: URI,
|
||||
documentEvents: DocumentEvents,
|
||||
) {
|
||||
this.connection = connection;
|
||||
this.elmWorkspaceFolder = elmWorkspaceFolder;
|
||||
this.events = new TextDocumentEvents(documentEvents);
|
||||
|
||||
this.connection.onDocumentFormatting(this.handleFormattingRequest);
|
||||
}
|
||||
@@ -24,19 +30,25 @@ export class ElmFormatProvider {
|
||||
params: DocumentFormattingParams,
|
||||
) => {
|
||||
try {
|
||||
const text = fs.readFileSync(URI.parse(params.textDocument.uri).fsPath);
|
||||
const settings = await Settings.getSettings(this.connection);
|
||||
const text = this.events.get(params.textDocument.uri);
|
||||
if (!text) {
|
||||
this.connection.console.error("Can't find file for formatting.");
|
||||
return;
|
||||
}
|
||||
|
||||
const options = {
|
||||
cmdArguments: ["--stdin", "--elm-version 0.19", "--yes"],
|
||||
notFoundText: "Install Elm-format via 'npm install -g elm-format",
|
||||
};
|
||||
const format = execCmd(
|
||||
"elm-format",
|
||||
settings.elmFormatPath,
|
||||
options,
|
||||
this.elmWorkspaceFolder,
|
||||
this.connection,
|
||||
);
|
||||
|
||||
format.stdin.write(text);
|
||||
format.stdin.write(text.getText());
|
||||
format.stdin.end();
|
||||
|
||||
const stdout = await format;
|
||||
|
||||
@@ -2,8 +2,8 @@ import { SyntaxNode, Tree } from "tree-sitter";
|
||||
import {
|
||||
IConnection,
|
||||
Location,
|
||||
Range,
|
||||
Position,
|
||||
Range,
|
||||
ReferenceParams,
|
||||
} from "vscode-languageserver";
|
||||
import { IForest } from "../forest";
|
||||
@@ -25,9 +25,9 @@ export class ReferencesProvider {
|
||||
const tree: Tree | undefined = this.forest.getTree(param.textDocument.uri);
|
||||
|
||||
if (tree) {
|
||||
let nodeAtPosition = tree.rootNode.namedDescendantForPosition({
|
||||
row: param.position.line,
|
||||
const nodeAtPosition = tree.rootNode.namedDescendantForPosition({
|
||||
column: param.position.character,
|
||||
row: param.position.line,
|
||||
});
|
||||
|
||||
// let nameNode: SyntaxNode | null = null;
|
||||
@@ -38,7 +38,7 @@ export class ReferencesProvider {
|
||||
// }
|
||||
|
||||
if (nodeAtPosition) {
|
||||
let references = tree.rootNode
|
||||
const references = tree.rootNode
|
||||
.descendantsOfType("value_expr")
|
||||
.filter(
|
||||
a =>
|
||||
@@ -48,7 +48,7 @@ export class ReferencesProvider {
|
||||
a.firstNamedChild.lastNamedChild.text === nodeAtPosition.text,
|
||||
);
|
||||
|
||||
let declaration = tree.rootNode
|
||||
const declaration = tree.rootNode
|
||||
.descendantsOfType("function_declaration_left")
|
||||
.find(
|
||||
a =>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
Connection,
|
||||
IConnection,
|
||||
InitializeParams,
|
||||
InitializeResult,
|
||||
} from "vscode-languageserver";
|
||||
@@ -55,7 +56,7 @@ export class Server implements ILanguageServer {
|
||||
}
|
||||
|
||||
private registerProviders(
|
||||
connection: Connection,
|
||||
connection: IConnection,
|
||||
forest: Forest,
|
||||
elmWorkspace: URI,
|
||||
virtualImports: VirtualImports,
|
||||
@@ -73,7 +74,7 @@ export class Server implements ILanguageServer {
|
||||
new CompletionProvider(connection, forest, virtualImports);
|
||||
new HoverProvider(connection, forest);
|
||||
new DiagnosticsProvider(connection, elmWorkspace, documentEvents);
|
||||
new ElmFormatProvider(connection, elmWorkspace);
|
||||
new ElmFormatProvider(connection, elmWorkspace, documentEvents);
|
||||
new DefinitionProvider(connection, forest);
|
||||
new ReferencesProvider(connection, forest);
|
||||
new DocumentSymbolProvider(connection, forest);
|
||||
|
||||
17
server/src/util/settings.ts
Normal file
17
server/src/util/settings.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { IConnection } from "vscode-languageserver";
|
||||
|
||||
export interface IClientSettings {
|
||||
elmPath: string;
|
||||
elmFormatPath: string;
|
||||
}
|
||||
|
||||
export class Settings {
|
||||
public static getSettings(
|
||||
connection: IConnection,
|
||||
): Thenable<IClientSettings> {
|
||||
const result = connection.workspace.getConfiguration({
|
||||
section: "elmLS",
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,83 +1,105 @@
|
||||
import {
|
||||
DidChangeTextDocumentParams,
|
||||
DidOpenTextDocumentParams,
|
||||
DidSaveTextDocumentParams,
|
||||
TextDocument,
|
||||
TextDocumentContentChangeEvent,
|
||||
DidCloseTextDocumentParams,
|
||||
} from 'vscode-languageserver';
|
||||
import { EventEmitter } from 'ws';
|
||||
import { DocumentEvents } from './documentEvents';
|
||||
DidChangeTextDocumentParams,
|
||||
DidCloseTextDocumentParams,
|
||||
DidOpenTextDocumentParams,
|
||||
DidSaveTextDocumentParams,
|
||||
TextDocument,
|
||||
TextDocumentContentChangeEvent,
|
||||
} from "vscode-languageserver";
|
||||
import { EventEmitter } from "ws";
|
||||
import { DocumentEvents } from "./documentEvents";
|
||||
|
||||
type DidChangeCallback = ((document: TextDocument) => void)
|
||||
type DidCloseCallback = ((document: TextDocument) => void)
|
||||
type DidOpenCallback = ((document: TextDocument) => void)
|
||||
type DidSaveCallback = ((document: TextDocument) => void)
|
||||
type DidChangeCallback = (document: TextDocument) => void;
|
||||
type DidCloseCallback = (document: TextDocument) => void;
|
||||
type DidOpenCallback = (document: TextDocument) => void;
|
||||
type DidSaveCallback = (document: TextDocument) => void;
|
||||
|
||||
export interface ITextDocumentEvents {
|
||||
on(event: 'change', listener: DidChangeCallback): this;
|
||||
on(event: 'close', listener: DidCloseCallback): this;
|
||||
on(event: 'open', listener: DidOpenCallback): this;
|
||||
on(event: 'save', listener: DidSaveCallback): this;
|
||||
on(event: "change", listener: DidChangeCallback): this;
|
||||
on(event: "close", listener: DidCloseCallback): this;
|
||||
on(event: "open", listener: DidOpenCallback): this;
|
||||
on(event: "save", listener: DidSaveCallback): this;
|
||||
}
|
||||
|
||||
interface IUpdateableDocument extends TextDocument {
|
||||
update(event: TextDocumentContentChangeEvent, version: number): void;
|
||||
update(event: TextDocumentContentChangeEvent, version: number): void;
|
||||
}
|
||||
|
||||
// This is loosely based on https://github.com/Microsoft/vscode-languageserver-node/blob/73180893ca/server/src/main.ts#L124
|
||||
// With some simplifications and the ability to support multiple listeners
|
||||
export class TextDocumentEvents extends EventEmitter implements ITextDocumentEvents {
|
||||
public static isUpdateableDocument(value: TextDocument): value is IUpdateableDocument {
|
||||
return typeof (value as IUpdateableDocument).update === "function";
|
||||
}
|
||||
export class TextDocumentEvents extends EventEmitter
|
||||
implements ITextDocumentEvents {
|
||||
public static isUpdateableDocument(
|
||||
value: TextDocument,
|
||||
): value is IUpdateableDocument {
|
||||
return typeof (value as IUpdateableDocument).update === "function";
|
||||
}
|
||||
|
||||
private documents: { [uri: string]: TextDocument };
|
||||
private documents: { [uri: string]: TextDocument };
|
||||
|
||||
constructor(events: DocumentEvents) {
|
||||
super();
|
||||
this.documents = {};
|
||||
constructor(events: DocumentEvents) {
|
||||
super();
|
||||
this.documents = {};
|
||||
|
||||
events.on('open', (event: DidOpenTextDocumentParams) => {
|
||||
const td = event.textDocument;
|
||||
const document = TextDocument.create(td.uri, td.languageId, td.version, td.text);
|
||||
this.documents[event.textDocument.uri] = document;
|
||||
const frozen = Object.freeze({ document });
|
||||
this.emit('open', frozen.document);
|
||||
});
|
||||
events.on("open", (event: DidOpenTextDocumentParams) => {
|
||||
const td = event.textDocument;
|
||||
const document = TextDocument.create(
|
||||
td.uri,
|
||||
td.languageId,
|
||||
td.version,
|
||||
td.text,
|
||||
);
|
||||
this.documents[event.textDocument.uri] = document;
|
||||
const frozen = Object.freeze({ document });
|
||||
this.emit("open", frozen.document);
|
||||
});
|
||||
|
||||
events.on('change', (event: DidChangeTextDocumentParams) => {
|
||||
const td = event.textDocument;
|
||||
const changes = event.contentChanges;
|
||||
const last: TextDocumentContentChangeEvent | undefined = changes.length > 0 ? changes[changes.length - 1] : undefined;
|
||||
if (last) {
|
||||
const document = this.documents[td.uri];
|
||||
if (document && TextDocumentEvents.isUpdateableDocument(document)) {
|
||||
if (td.version === null || td.version === void 0) {
|
||||
throw new Error(`Received document change event for ${td.uri} without valid version identifier`);
|
||||
}
|
||||
document.update(last, td.version);
|
||||
const frozen = Object.freeze({ document });
|
||||
this.emit('change', frozen.document)
|
||||
}
|
||||
}
|
||||
events.on("change", (event: DidChangeTextDocumentParams) => {
|
||||
const td = event.textDocument;
|
||||
const changes = event.contentChanges;
|
||||
const last: TextDocumentContentChangeEvent | undefined =
|
||||
changes.length > 0 ? changes[changes.length - 1] : undefined;
|
||||
if (last) {
|
||||
const document = this.documents[td.uri];
|
||||
if (document && TextDocumentEvents.isUpdateableDocument(document)) {
|
||||
if (td.version === null || td.version === void 0) {
|
||||
throw new Error(
|
||||
`Received document change event for ${
|
||||
td.uri
|
||||
} without valid version identifier`,
|
||||
);
|
||||
}
|
||||
document.update(last, td.version);
|
||||
const frozen = Object.freeze({ document });
|
||||
this.emit("change", frozen.document);
|
||||
}
|
||||
}
|
||||
});
|
||||
events.on("save", (event: DidSaveTextDocumentParams) => {
|
||||
const document = this.documents[event.textDocument.uri];
|
||||
if (document) {
|
||||
const frozen = Object.freeze({ document });
|
||||
this.emit("save", frozen.document);
|
||||
}
|
||||
});
|
||||
events.on("close", (event: DidCloseTextDocumentParams) => {
|
||||
const document = this.documents[event.textDocument.uri];
|
||||
if (document) {
|
||||
delete this.documents[event.textDocument.uri];
|
||||
const frozen = Object.freeze({ document });
|
||||
this.emit("close", frozen.document);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
events.on('save', (event: DidSaveTextDocumentParams) => {
|
||||
const document = this.documents[event.textDocument.uri];
|
||||
if (document) {
|
||||
const frozen = Object.freeze({ document });
|
||||
this.emit('save', frozen.document);
|
||||
}
|
||||
});
|
||||
events.on('close', (event: DidCloseTextDocumentParams) => {
|
||||
const document = this.documents[event.textDocument.uri];
|
||||
if (document) {
|
||||
delete this.documents[event.textDocument.uri];
|
||||
const frozen = Object.freeze({ document });
|
||||
this.emit('close', frozen.document)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Returns the document for the given URI. Returns undefined if
|
||||
* the document is not mananged by this instance.
|
||||
*
|
||||
* @param uri The text document's URI to retrieve.
|
||||
* @return the text document or `undefined`.
|
||||
*/
|
||||
public get(uri: string): TextDocument | undefined {
|
||||
return this.documents[uri];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user