mirror of
https://github.com/jlengrand/elm-language-client-vscode.git
synced 2026-03-10 08:11:17 +00:00
Find elm file
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user