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:
Razzeee
2019-05-16 14:57:03 +02:00
committed by GitHub
10 changed files with 186 additions and 103 deletions

View File

@@ -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> |

View File

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

View File

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

View File

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

View File

@@ -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) {

View File

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

View File

@@ -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 =>

View File

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

View 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;
}
}

View File

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