Skip to content

Commit

Permalink
implemented LSP in cli
Browse files Browse the repository at this point in the history
  • Loading branch information
ascandone committed Jan 18, 2024
1 parent 3c1e779 commit 6d49966
Show file tree
Hide file tree
Showing 5 changed files with 392 additions and 9 deletions.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"format:write": "prettier --write --cache ./**/*.ts"
},
"dependencies": {
"commander": "^11.1.0"
"commander": "^11.1.0",
"vscode-languageserver": "^9.0.1"
},
"devDependencies": {
"@ohm-js/cli": "^2.0.0",
Expand All @@ -24,6 +25,7 @@
"ohm-js": "^17.1.0",
"prettier": "^3.2.1",
"typescript": "^5.3.3",
"vitest": "^1.2.0"
"vitest": "^1.2.0",
"vscode-languageserver-textdocument": "^1.0.11"
}
}
176 changes: 175 additions & 1 deletion src/cli/commands/lsp.ts
Original file line number Diff line number Diff line change
@@ -1 +1,175 @@
export function lspCmd() {}
import {
DiagnosticSeverity,
MarkupKind,
TextDocumentSyncKind,
TextDocuments,
createConnection,
} from "vscode-languageserver";

import { TextDocument } from "vscode-languageserver-textdocument";
import { Span, SpanMeta, parse } from "../../parser";
import { Type } from "../../typecheck/unify";
import { typeErrorPPrint, typePPrint } from "../../typecheck/pretty-printer";
import { TypeMeta, typecheck } from "../../typecheck/typecheck";
import { prelude } from "../../typecheck/prelude";
import { Expr, Program } from "../../ast";

const documents = new TextDocuments(TextDocument);
const docs = new Map<string, [TextDocument, Program<SpanMeta & TypeMeta>]>();

function spanContains([start, end]: Span, offset: number) {
return start <= offset && end >= offset;
}

function findTypeByOffsetE<T>(
ast: Expr<SpanMeta & TypeMeta & T>,
offset: number,
): Type | undefined {
if (!spanContains(ast.span, offset)) {
return;
}

switch (ast.type) {
case "constant":
case "identifier":
return ast.$.asType();
case "application":
for (const arg of ast.args) {
const t = findTypeByOffsetE(arg, offset);
if (t !== undefined) {
return t;
}
}
return findTypeByOffsetE(ast.caller, offset);

case "let":
if (spanContains(ast.binding.span, offset)) {
return ast.binding.$.asType();
}
return (
findTypeByOffsetE(ast.value, offset) ??
findTypeByOffsetE(ast.body, offset)
);

case "fn":
for (const param of ast.params) {
if (spanContains(param.span, offset)) {
return param.$.asType();
}
}
return findTypeByOffsetE(ast.body, offset);

case "if":
return (
findTypeByOffsetE(ast.condition, offset) ??
findTypeByOffsetE(ast.then, offset) ??
findTypeByOffsetE(ast.else, offset)
);
}
}

function findTypeByOffsetP<T>(
program: Program<SpanMeta & TypeMeta & T>,
offset: number,
): Type | undefined {
for (const st of program.statements) {
if (spanContains(st.binding.span, offset)) {
return st.binding.$.asType();
}
const e = findTypeByOffsetE(st.value, offset);
if (e !== undefined) {
return e;
}
}

return undefined;
}

export function lspCmd() {
const connection =
// @ts-ignore
createConnection();

connection.onInitialize(() => ({
capabilities: {
textDocumentSync: TextDocumentSyncKind.Incremental,
hoverProvider: true,
// inlayHintProvider: true,
// codeLensProvider: {
// resolveProvider: true,
// },
// documentSymbolProvider: true,
},
}));

documents.onDidChangeContent((change) => {
const src = change.document.getText();
const parsed = parse(src);
if (!parsed.ok) {
const interval = parsed.matchResult.getInterval();

connection.sendDiagnostics({
uri: change.document.uri,
diagnostics: [
{
message: parsed.matchResult.message ?? "Parsing error",
source: "Parsing",
severity: DiagnosticSeverity.Error,
range: {
start: change.document.positionAt(interval.startIdx),
end: change.document.positionAt(interval.endIdx),
},
},
],
});
return;
}

const [typed, errors] = typecheck(parsed.value, prelude);
docs.set(change.document.uri, [change.document, typed]);
connection.sendDiagnostics({
uri: change.document.uri,
diagnostics: errors.map((e) => {
const [start, end] = e.node.span;

return {
message: typeErrorPPrint(e),
source: "Typecheck",
severity: DiagnosticSeverity.Error,
range: {
start: change.document.positionAt(start),
end: change.document.positionAt(end),
},
};
}),
});
});

connection.onHover(({ textDocument, position }) => {
const pair = docs.get(textDocument.uri);
if (pair === undefined) {
return undefined;
}

const [doc, ast] = pair;

const offset = doc.offsetAt(position);
const $ = findTypeByOffsetP(ast, offset);
if ($ === undefined) {
return undefined;
}
const tpp = typePPrint($);

return {
contents: {
kind: MarkupKind.Markdown,
value: `\`\`\`
${tpp}
\`\`\``,
},
};
});

documents.listen(connection);
connection.listen();
}
1 change: 0 additions & 1 deletion src/cli/commands/typecheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export function typecheckCmd(path: string) {
const [, errors] = typecheck(untyped, prelude);

for (const error of errors) {
// const x: number = "hi";
const msg = typeErrorPPrint(error);
console.log(`${FgRed}Error:${Reset} ${msg}`);
console.log(showErrorLine(src, error.node.span), "\n\n");
Expand Down
8 changes: 8 additions & 0 deletions src/cli/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { Command } from "commander";
import { typecheckCmd } from "./commands/typecheck";
import { lspCmd } from "./commands/lsp";
const packageJson = require("../../package.json");

const program = new Command();
Expand All @@ -15,4 +16,11 @@ program
typecheckCmd(path);
});

program
.command("lsp")
.description("Run the language server")
.action(() => {
lspCmd();
});

program.parse();
Loading

0 comments on commit 6d49966

Please sign in to comment.