Skip to content

Commit

Permalink
feat: elementary hover support
Browse files Browse the repository at this point in the history
  • Loading branch information
Myriad-Dreamin committed Sep 20, 2024
1 parent 46be1a6 commit 9d3e493
Show file tree
Hide file tree
Showing 24 changed files with 543 additions and 127 deletions.
2 changes: 1 addition & 1 deletion Venv.ps1
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
function RunCosmo {
node ./cmd/cosmo/main.js $args
node '--enable-source-maps' ./cmd/cosmo/main.js $args
}

Set-Alias cosmo RunCosmo
9 changes: 9 additions & 0 deletions editors/vscode/.vscodeignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
*
!package.json
!LICENSE
!out/cosmo.tmLanguage.json
!out/extension.js
!out/extension.js.map
!out/lsp-server.js
!out/lsp-server.js.map
!syntaxes/**/*
11 changes: 6 additions & 5 deletions editors/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,15 @@
"activationEvents": [],
"scripts": {
"build:syntax": "cd ../../syntaxes/textmate && yarn run compile && yarn run bundle",
"build-base": "esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node --target=node16",
"build-server": "esbuild ./src/lsp-server.ts --bundle --outfile=out/lsp-server.js --external:vscode --format=cjs --platform=node --target=node16",
"vscode:prepublish": "yarn run build-base -- --minify && yarn run build-server -- --minify && node scripts/postinstall.cjs",
"build-base": "esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node --sourcemap --target=node16",
"build-server": "esbuild ./src/lsp-server.ts --bundle --outfile=out/lsp-server.js --external:vscode --format=cjs --platform=node --sourcemap --target=node16",
"vscode:prepublish": "yarn run build-base -- --minify && yarn run build-server -- --minify",
"package": "vsce package --yarn",
"compile": "yarn run build-base -- --sourcemap && yarn run build:syntax && node scripts/postinstall.cjs",
"watch": "yarn run build-base -- --sourcemap --watch"
"compile": "yarn run build-base && yarn run build:syntax",
"watch": "yarn run build-base --watch"
},
"dependencies": {
"vscode-uri": "^3.0.8",
"vscode-languageclient": "^9.0.1",
"vscode-languageserver": "^9.0.1",
"vscode-languageserver-textdocument": "^1.0.11",
Expand Down
Empty file.
99 changes: 99 additions & 0 deletions editors/vscode/src/language-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import * as cosmo from "../../../packages/cosmo/target/scala-3.3.3/cosmo-opt/main.js";
import * as fs from "fs";
import { resolve } from "path";
import { URI } from "vscode-uri";
import { TextDocuments } from "vscode-languageserver";
import { TextDocument } from "vscode-languageserver-textdocument";

export class CosmoLanguageService {
readonly releaseDir = resolve("target/cosmo/release");

readonly system: SystemAdaptor;
readonly cosmoSystem: any = cosmo.CosmoJsPhysicalSystem();
readonly cc: Cosmoc;

constructor(documents: TextDocuments<TextDocument>) {
this.system = new SystemAdaptor(documents);
this.cosmoSystem.proxy(freezeThis(this.system, SystemAdaptor));
this.cc = cosmo.Cosmo(this.cosmoSystem) as any as Cosmoc;

this.cc.configure(this.releaseDir);

this.cc.loadPackageByPath(resolve("library/std"));
this.cc.preloadPackage("std");
}
}

interface Cosmoc {
loadPackageByPath(path: string): void;

service: CosmocService;

[x: string | number | symbol]: any;
}

interface LspRange {
start: number;
end: number;
}

interface HoverResult {
content: string;
range: LspRange;
}

interface CosmocService {
hover(path: string, offset: number): HoverResult;

[x: string | number | symbol]: any;
}

class SystemAdaptor {
onReload: (path: string) => void = undefined!;

constructor(private documents: TextDocuments<TextDocument>) {}

reload(uri: string): void {
if (this.onReload) {
this.onReload(URI.parse(uri).fsPath);
}
}

readFile(path: string): string {
const uri = URI.file(path).toString();
const doc = this.documents.get(uri);
if (doc) {
console.log("reading file in memory", doc.uri, doc.lineCount);
return doc.getText();
}

return fs.readFileSync(path, "utf-8");
}

readDir(path: string): string[] {
return fs.readdirSync(path); // todo: read directory from memory
}

exists(path: string): boolean {
const uri = URI.file(path).toString();
const doc = this.documents.get(uri);
if (doc) {
console.log("reading exists in memory", doc.uri, doc.lineCount);
return true;
}

return fs.existsSync(path);
}
}

function freezeThis<T>(ins: T, cls: any): T {
for (const name of Object.getOwnPropertyNames(Object.getPrototypeOf(ins))) {
let method = ins[name];
if (!(!(method instanceof Function) || method === cls)) {
continue;
}
ins[name] = ins[name].bind(ins);
}

return ins;
}
126 changes: 63 additions & 63 deletions editors/vscode/src/lsp-server.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,49 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import {
createConnection,
TextDocuments,
Diagnostic,
DiagnosticSeverity,
ProposedFeatures,
InitializeParams,
DidChangeConfigurationNotification,
CompletionItem,
CompletionItemKind,
TextDocumentPositionParams,
TextDocumentSyncKind,
InitializeResult,
DocumentDiagnosticReportKind,
type DocumentDiagnosticReport,
Hover,
Range,
Position,
} from "vscode-languageserver/node";

import { TextDocument } from "vscode-languageserver-textdocument";
import { CosmoLanguageService } from "./language-service";
import { URI } from "vscode-uri";

// Create a simple text document manager.
const documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);

const languageService = new CosmoLanguageService(documents);

const offsetOf = (doc: TextDocument | undefined, pos: Position) =>
doc?.offsetAt(pos) ?? 0;

const s = <T, U>(f: (t: T) => U) => {
return (t: T) => {
try {
return f(t);
} catch (e) {
const err = e as Error;
console.error(err.stack);
throw err;
}
};
};

// Create a connection for the server, using Node's IPC as a transport.
// Also include all preview / proposed LSP features.
const connection = createConnection(ProposedFeatures.all);

// Create a simple text document manager.
const documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);

let hasConfigurationCapability = false;
let hasWorkspaceFolderCapability = false;
let hasDiagnosticRelatedInformationCapability = false;
Expand Down Expand Up @@ -56,6 +72,7 @@ connection.onInitialize((params: InitializeParams) => {
completionProvider: {
resolveProvider: true,
},
hoverProvider: true,
diagnosticProvider: {
interFileDependencies: false,
workspaceDiagnostics: false,
Expand Down Expand Up @@ -141,55 +158,17 @@ connection.languages.diagnostics.on(async (params) => {
// The content of a text document has changed. This event is emitted
// when the text document first opened or when its content has changed.
documents.onDidChangeContent((change) => {
languageService.system.reload(change.document.uri);

validateTextDocument(change.document);
});

async function validateTextDocument(
textDocument: TextDocument
): Promise<Diagnostic[]> {
// In this simple example we get the settings for every validate run.
const settings = {
maxNumberOfProblems: 1000,
};

// The validator creates diagnostics for all uppercase words length 2 and more
const text = textDocument.getText();
const pattern = /\b[A-Z]{2,}\b/g;
let m: RegExpExecArray | null;
void textDocument;

let problems = 0;
const diagnostics: Diagnostic[] = [];
// while ((m = pattern.exec(text)) && problems < settings.maxNumberOfProblems) {
// problems++;
// const diagnostic: Diagnostic = {
// severity: DiagnosticSeverity.Warning,
// range: {
// start: textDocument.positionAt(m.index),
// end: textDocument.positionAt(m.index + m[0].length),
// },
// message: `${m[0]} is all uppercase.`,
// source: "ex",
// };
// if (hasDiagnosticRelatedInformationCapability) {
// diagnostic.relatedInformation = [
// {
// location: {
// uri: textDocument.uri,
// range: Object.assign({}, diagnostic.range),
// },
// message: "Spelling matters",
// },
// {
// location: {
// uri: textDocument.uri,
// range: Object.assign({}, diagnostic.range),
// },
// message: "Particularly for names",
// },
// ];
// }
// diagnostics.push(diagnostic);
// }
return diagnostics;
}

Expand All @@ -200,25 +179,46 @@ connection.onDidChangeWatchedFiles((_change) => {

// This handler provides the initial list of the completion items.
connection.onCompletion(
(_textDocumentPosition: TextDocumentPositionParams): CompletionItem[] => {
(
textDocumentPosition: TextDocumentPositionParams
): CompletionItem[] | undefined => {
// The pass parameter contains the position of the text document in
// which code complete got requested. For the example we ignore this
// info and always provide the same completion items.
return [
{
label: "TypeScript",
kind: CompletionItemKind.Text,
data: 1,
},
{
label: "JavaScript",
kind: CompletionItemKind.Text,
data: 2,
},
];

const doc = documents.get(textDocumentPosition.textDocument.uri);
if (doc === undefined) {
return undefined;
}

return undefined;
}
);

connection.onHover(
s((pos: TextDocumentPositionParams): Hover | undefined => {
const path = URI.parse(pos.textDocument.uri).fsPath;
const doc = documents.get(pos.textDocument.uri);
const offset = offsetOf(doc, pos.position);

console.log("hover", path, offset);
const result = languageService.cc.service.hover(path, offset);
if (!result) {
return;
}

let range: Range = undefined!;
if (result?.range && doc) {
range = {
start: doc.positionAt(result.range.start),
end: doc.positionAt(result.range.end),
};
}

return { contents: { kind: "markdown", value: result.content }, range };
})
);

// This handler resolves additional information for the item selected in
// the completion list.
connection.onCompletionResolve((item: CompletionItem): CompletionItem => {
Expand Down
16 changes: 11 additions & 5 deletions editors/vscode/src/lsp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
LanguageClientOptions,
ServerOptions,
TransportKind,
NodeModule,
} from "vscode-languageclient/node";

let client: LanguageClient;
Expand All @@ -16,14 +17,19 @@ export function activateLsp(context: ExtensionContext) {
path.join("out", "lsp-server.js")
);

const run: NodeModule = {
module: serverModule,
options: {
execArgv: ["--enable-source-maps"],
},
transport: TransportKind.ipc,
};

// If the extension is launched in debug mode then the debug server options are used
// Otherwise the run options are used
const serverOptions: ServerOptions = {
run: { module: serverModule, transport: TransportKind.ipc },
debug: {
module: serverModule,
transport: TransportKind.ipc,
},
run,
debug: run,
};

// Options to control the language client
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"cosmo": "node ./cmd/cosmo/main.js",
"docs": "shiroa serve --font-path ./assets/typst-fonts/ --font-path ./assets/fonts/ -w . docs/cosmo",
"build:syntax": "cd editors/vscode && yarn build:syntax",
"build:vscode": "cd editors/vscode && yarn build:syntax && yarn package"
"build:vscode": "cd editors/vscode && yarn build:syntax && yarn package",
"local:vscode": "yarn build:vscode && code --install-extension editors/vscode/cosmo-0.1.0.vsix"
},
"devDependencies": {
"eslint-config-prettier": "^9.0.0",
Expand Down
4 changes: 2 additions & 2 deletions packages/cosmo/src/main/scala/cosmo/CodeGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ class CodeGen(implicit val env: Env) {
}
}

def storeTy(ty: Type): String = env.storeTy(ty)(false)
def storeTy(ty: Type): String = env.storeTy(ty)

def solveDict(items: Map[String, ir.Item]): String = {

Expand Down Expand Up @@ -421,7 +421,7 @@ class CodeGen(implicit val env: Env) {
case recv => s"return ${exprWith(value, recv)}"
}
}
case v: Term => v.id.env.varByRef(v)(false)
case v: Term => v.id.env.varByRef(v)
case v: Fn => v.id.defName()
case ir.Loop(body) =>
return s"for(;;) ${blockizeExpr(body, ValRecv.None)}"
Expand Down
Loading

0 comments on commit 9d3e493

Please sign in to comment.