Find elm file

This commit is contained in:
Kolja Lampe
2019-01-29 00:31:43 +01:00
parent fc8e8a4212
commit 09acac0690
3 changed files with 207 additions and 165 deletions

View File

@@ -1,34 +1,41 @@
import {
createConnection,
IConnection,
InitializeParams,
ProposedFeatures,
createConnection,
IConnection,
InitializeParams,
ProposedFeatures,
WorkspaceFolder,
} from "vscode-languageserver";
import { ILanguageServer } from "./server";
import { rebuildTreeSitter } from "./util/rebuilder";
const connection: IConnection = createConnection(ProposedFeatures.all);
let workspaceFolders: WorkspaceFolder[];
connection.onInitialize(async (params: InitializeParams) => {
connection.console.info("Initializing Elm language server...");
workspaceFolders = params.workspaceFolders;
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;
}
return null;
}
connection.console.info("Rebuild succeeded!");
}
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,
workspaceFolders,
params,
);
return server.capabilities;
return server.capabilities;
});
// Listen on the connection

View File

@@ -5,147 +5,157 @@ import * as utils from "../util/elmUtils";
import URI from "vscode-uri";
import {
Diagnostic,
DiagnosticSeverity,
IConnection,
Range,
Diagnostic,
DiagnosticSeverity,
IConnection,
Range,
} from "vscode-languageserver";
export interface IElmIssueRegion {
start: { line: number; column: number };
end: { line: number; column: number };
start: { line: number; column: number };
end: { line: number; column: number };
}
export interface IElmIssue {
tag: string;
overview: string;
subregion: string;
details: string;
region: IElmIssueRegion;
type: string;
file: string;
tag: string;
overview: string;
subregion: string;
details: string;
region: IElmIssueRegion;
type: string;
file: string;
}
export class DiagnosticsProvider {
private connection: IConnection;
private connection: IConnection;
private elmWorkspaceFolder: URI;
constructor(connection: IConnection) {
this.connection = connection;
constructor(connection: IConnection, elmWorkspaceFolder: URI) {
this.connection = connection;
this.elmWorkspaceFolder = elmWorkspaceFolder;
this.connection.onDidSaveTextDocument(this.handleTextdocumentChanged);
}
this.connection.onDidSaveTextDocument(this.handleTextdocumentChanged);
}
protected handleTextdocumentChanged = async (
param,
) => {
const b = param.textDocument.uri;
const diagnostics: Diagnostic[] = [];
protected handleTextdocumentChanged = async (param) => {
const uri: URI = URI.parse(param.textDocument.uri);
// this.connection.sendDiagnostics({
// uri: document.uri,
// diagnostics: issue.map((error) => elmMakeIssueToDiagnostic(error)));
this.checkForErrors(
this.connection,
this.elmWorkspaceFolder.fsPath,
uri.fsPath,
)
.then((compilerErrors: IElmIssue[]) => {
const cwd: string = this.elmWorkspaceFolder.fsPath;
const splitCompilerErrors: Map<string, IElmIssue[]> = new Map();
const compileErrors: Diagnostic[] = [];
const uri: URI = URI.parse(param.textDocument.uri);
this.checkForErrors(this.connection, "", uri.fsPath)
.then((compilerErrors: IElmIssue[]) => {
// const cwd: string = rootPath;
const cwd: string = uri.fsPath;
const splitCompilerErrors: Map<string, IElmIssue[]> = new Map();
compilerErrors.forEach((issue: IElmIssue) => {
// If provided path is relative, make it absolute
if (issue.file.startsWith(".")) {
issue.file = cwd + issue.file.slice(1);
}
if (splitCompilerErrors.has(issue.file)) {
splitCompilerErrors.get(issue.file).push(issue);
} else {
splitCompilerErrors.set(issue.file, [issue]);
}
});
// Turn split arrays into diagnostics and associate them with correct files in VS
splitCompilerErrors.forEach((issue: IElmIssue[], path: string) => {
this.connection.sendDiagnostics({
diagnostics: issue.map((error) => this.elmMakeIssueToDiagnostic(error)),
uri: param.textDocument.uri,
});
});
})
.catch((error) => {
this.connection.console.error("Error when creating diagnostics.");
});
}
// todo fix rootpath
private checkForErrors(connection: IConnection, rootPath: string, filename: string) {
return new Promise((resolve, reject) => {
const makeCommand: string = "elm";
const cwd: string = rootPath;
let make: cp.ChildProcess;
if (utils.isWindows) {
filename = "\"" + filename + "\"";
}
const args = ["make", filename, "--report", "json", "--output", "/dev/null"];
if (utils.isWindows) {
make = cp.exec(makeCommand + " " + args.join(" "), { cwd });
} else {
make = cp.spawn(makeCommand, args, { cwd });
}
// output is actually optional
// (fixed in https://github.com/Microsoft/vscode/commit/b4917afe9bdee0e9e67f4094e764f6a72a997c70,
// but unreleased at this time)
const errorLinesFromElmMake: readline.ReadLine = readline.createInterface({
input: make.stderr,
output: undefined,
});
const lines: IElmIssue[] = [];
errorLinesFromElmMake.on("line", (line: string) => {
lines.push(...(JSON.parse(line) as IElmIssue[]));
});
make.on("error", (err: Error) => {
errorLinesFromElmMake.close();
if (err && (err as any).code === "ENOENT") {
connection.console.log(
"The 'elm make' compiler is not available. Install Elm from http://elm-lang.org/.",
);
resolve([]);
} else {
reject(err);
}
});
make.on("close", (code: number, signal: string) => {
errorLinesFromElmMake.close();
resolve(lines);
});
compilerErrors.forEach((issue: IElmIssue) => {
// If provided path is relative, make it absolute
if (issue.file.startsWith(".")) {
issue.file = cwd + issue.file.slice(1);
}
if (splitCompilerErrors.has(issue.file)) {
splitCompilerErrors.get(issue.file).push(issue);
} else {
splitCompilerErrors.set(issue.file, [issue]);
}
});
}
private severityStringToDiagnosticSeverity(
severity: string,
): DiagnosticSeverity {
switch (severity) {
case "error":
return DiagnosticSeverity.Error;
case "warning":
return DiagnosticSeverity.Warning;
default:
return DiagnosticSeverity.Error;
// Turn split arrays into diagnostics and associate them with correct files in VS
splitCompilerErrors.forEach((issue: IElmIssue[], path: string) => {
this.connection.sendDiagnostics({
diagnostics: issue.map((error) =>
this.elmMakeIssueToDiagnostic(error),
),
uri: param.textDocument.uri,
});
});
})
.catch((error) => {
this.connection.console.error("Error when creating diagnostics.");
});
}
// todo fix rootpath
private checkForErrors(
connection: IConnection,
rootPath: string,
filename: string,
) {
return new Promise((resolve, reject) => {
const makeCommand: string = "elm";
const cwd: string = rootPath;
let make: cp.ChildProcess;
if (utils.isWindows) {
filename = '"' + filename + '"';
}
const args = [
"make",
filename,
"--report",
"json",
"--output",
"/dev/null",
];
if (utils.isWindows) {
make = cp.exec(makeCommand + " " + args.join(" "), { cwd });
} else {
make = cp.spawn(makeCommand, args, { cwd });
}
// output is actually optional
// (fixed in https://github.com/Microsoft/vscode/commit/b4917afe9bdee0e9e67f4094e764f6a72a997c70,
// but unreleased at this time)
const errorLinesFromElmMake: readline.ReadLine = readline.createInterface(
{
input: make.stderr,
output: undefined,
},
);
const lines: IElmIssue[] = [];
errorLinesFromElmMake.on("line", (line: string) => {
lines.push(...(JSON.parse(line) as IElmIssue[]));
});
make.on("error", (err: Error) => {
errorLinesFromElmMake.close();
if (err && (err as any).code === "ENOENT") {
connection.console.log(
"The 'elm make' compiler is not available. Install Elm from http://elm-lang.org/.",
);
resolve([]);
} else {
reject(err);
}
}
});
make.on("close", (code: number, signal: string) => {
errorLinesFromElmMake.close();
private elmMakeIssueToDiagnostic(issue: IElmIssue): Diagnostic {
const lineRange: Range = Range.create(
issue.region.start.line - 1,
issue.region.start.column - 1,
issue.region.end.line - 1,
issue.region.end.column - 1,
);
return Diagnostic.create(
lineRange,
issue.overview + " - " + issue.details.replace(/\[\d+m/g, ""),
this.severityStringToDiagnosticSeverity(issue.type),
);
resolve(lines);
});
});
}
private severityStringToDiagnosticSeverity(
severity: string,
): DiagnosticSeverity {
switch (severity) {
case "error":
return DiagnosticSeverity.Error;
case "warning":
return DiagnosticSeverity.Warning;
default:
return DiagnosticSeverity.Error;
}
}
private elmMakeIssueToDiagnostic(issue: IElmIssue): Diagnostic {
const lineRange: Range = Range.create(
issue.region.start.line - 1,
issue.region.start.column - 1,
issue.region.end.line - 1,
issue.region.end.column - 1,
);
return Diagnostic.create(
lineRange,
issue.overview + " - " + issue.details.replace(/\[\d+m/g, ""),
this.severityStringToDiagnosticSeverity(issue.type),
);
}
}

View File

@@ -1,5 +1,12 @@
import { Connection, InitializeParams, InitializeResult } from "vscode-languageserver";
import {
Connection,
InitializeParams,
InitializeResult,
WorkspaceFolder,
} from "vscode-languageserver";
import fs = require("fs");
import URI from "vscode-uri";
import { CapabilityCalculator } from "./capabilityCalculator";
import { Forest } from "./forest";
import { ASTProvider } from "./providers/astProvider";
@@ -8,33 +15,51 @@ import { DiagnosticsProvider } from "./providers/diagnosticsProvider";
import { FoldingRangeProvider } from "./providers/foldingProvider";
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;
public workspaceFolders: WorkspaceFolder[];
public elmWorkspaceFolder: URI;
private calculator: CapabilityCalculator;
private forest: Forest;
constructor(connection: Connection, params: InitializeParams) {
this.connection = connection;
this.calculator = new CapabilityCalculator(params.capabilities);
this.forest = new Forest();
constructor(
connection: Connection,
workspaceFolders: WorkspaceFolder[],
params: InitializeParams,
) {
this.connection = connection;
this.workspaceFolders = workspaceFolders;
this.elmWorkspaceFolder = this.findElmWorkspace();
this.calculator = new CapabilityCalculator(params.capabilities);
this.forest = new Forest();
this.registerProviders();
this.registerProviders();
}
get capabilities(): InitializeResult {
return {
capabilities: this.calculator.capabilities,
};
}
private findElmWorkspace() {
const elmFile = fs.readdirSync(this.workspaceFolders[0].name).find((value) => value === "elm.json");
if (elmFile) {
return URI.parse(elmFile);
} else {
this.connection.console.error("Cannot find elm.json in workspace.");
return null;
}
}
get capabilities(): InitializeResult {
return {
capabilities: this.calculator.capabilities,
};
}
private registerProviders(): void {
// tslint:disable:no-unused-expression
new ASTProvider(this.connection, this.forest);
new FoldingRangeProvider(this.connection, this.forest);
new CompletionProvider(this.connection, this.forest);
new DiagnosticsProvider(this.connection);
}
private registerProviders(): void {
// tslint:disable:no-unused-expression
new ASTProvider(this.connection, this.forest);
new FoldingRangeProvider(this.connection, this.forest);
new CompletionProvider(this.connection, this.forest);
new DiagnosticsProvider(this.connection, this.elmWorkspaceFolder);
}
}