aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--editors/vscode/package.json31
-rw-r--r--editors/vscode/src/ctx.ts9
-rw-r--r--editors/vscode/src/extension.ts7
-rw-r--r--editors/vscode/src/inlay_hints.ts244
-rw-r--r--src/analysis/analysis.odin29
-rw-r--r--src/server/inlay_hints.odin82
-rw-r--r--src/server/requests.odin44
-rw-r--r--src/server/types.odin8
8 files changed, 445 insertions, 9 deletions
diff --git a/editors/vscode/package.json b/editors/vscode/package.json
index cf2db7c..678f058 100644
--- a/editors/vscode/package.json
+++ b/editors/vscode/package.json
@@ -34,6 +34,11 @@
"command": "ols.restart",
"title": "Restart Odin Language Server",
"category": "Odin Language Server"
+ },
+ {
+ "command": "ols.createOls",
+ "title": "Create ols.json file in project",
+ "category": "Odin Language Server"
}
],
"configuration": {
@@ -118,7 +123,27 @@
"scopeName": "source.odin",
"path": "./syntaxes/odin.tmLanguage.json"
}
- ]
+ ],
+ "colors": [
+ {
+ "id": "odin.inlayHints.foreground",
+ "description": "Foreground color of inlay hints",
+ "defaults": {
+ "dark": "#A0A0A0F0",
+ "light": "#747474",
+ "highContrast": "#BEBEBE"
+ }
+ },
+ {
+ "id": "odin.inlayHints.background",
+ "description": "Background color of inlay hints",
+ "defaults": {
+ "dark": "#11223300",
+ "light": "#11223300",
+ "highContrast": "#11223300"
+ }
+ }
+ ]
},
"scripts": {
"vscode:prepublish": "npm run compile",
@@ -130,10 +155,10 @@
},
"devDependencies": {
"@types/glob": "^7.1.3",
- "@types/mocha": "^8.2.2",
+ "@types/mocha": "^9.0.0",
"@types/node": "^14.14.43",
"@types/node-fetch": "^2.5.7",
- "@types/vscode": "^1.55.0",
+ "@types/vscode": "^1.60.0",
"@typescript-eslint/eslint-plugin": "^4.22.1",
"@typescript-eslint/parser": "^4.22.1",
"eslint": "^7.25.0",
diff --git a/editors/vscode/src/ctx.ts b/editors/vscode/src/ctx.ts
index 7abf9c4..a1bf5d4 100644
--- a/editors/vscode/src/ctx.ts
+++ b/editors/vscode/src/ctx.ts
@@ -4,6 +4,8 @@ import * as lc from 'vscode-languageclient/node';
import { Config } from './config';
import { isOdinEditor, OdinEditor } from './util';
+import * as inlayHints from './inlay_hints';
+
//modified from https://github.com/rust-analyzer/rust-analyzer/blob/master/editors/code/src/ctx.ts - 09.05.2021
export class Ctx {
@@ -24,6 +26,9 @@ export class Ctx {
cwd: string,
): Promise<Ctx> {
const res = new Ctx(config, extCtx, client, serverPath);
+
+ inlayHints.activate(res);
+
return res;
}
@@ -53,6 +58,10 @@ export class Ctx {
return this.extCtx.subscriptions;
}
+ isOdinDocument(document: vscode.TextDocument): number {
+ return vscode.languages.match({scheme: 'file', language: 'odin'}, document);
+ }
+
pushCleanup(d: Disposable) {
this.extCtx.subscriptions.push(d);
}
diff --git a/editors/vscode/src/extension.ts b/editors/vscode/src/extension.ts
index e7163c2..69dcd28 100644
--- a/editors/vscode/src/extension.ts
+++ b/editors/vscode/src/extension.ts
@@ -139,6 +139,10 @@ export async function activate(context: vscode.ExtensionContext) {
client.start();
});
+ vscode.commands.registerCommand("ols.createOls", async() => {
+ createOlsConfig(ctx);
+ });
+
client.start();
parseOlsFile(config, olsFile);
@@ -183,7 +187,8 @@ export function createOlsConfig(ctx: Ctx) {
collections: [{ name: "core", path: corePath }],
enable_document_symbols: true,
enable_semantic_tokens: false,
- enable_hover: true
+ enable_hover: true,
+ enable_snippets: true
};
const olsPath = vscode.workspace.workspaceFolders![0].uri.fsPath;
diff --git a/editors/vscode/src/inlay_hints.ts b/editors/vscode/src/inlay_hints.ts
new file mode 100644
index 0000000..441cd0c
--- /dev/null
+++ b/editors/vscode/src/inlay_hints.ts
@@ -0,0 +1,244 @@
+// modification of https://github.com/clangd/vscode-clangd/blob/master/src/inlay-hints.ts
+
+import * as vscode from 'vscode';
+import * as vscodelc from 'vscode-languageclient/node';
+
+import { Ctx } from './ctx';
+
+export function activate(context: Ctx) {
+ const feature = new InlayHintsFeature(context);
+ context.client.registerFeature(feature);
+}
+
+// Currently, only one hint kind (parameter hints) are supported,
+// but others (e.g. type hints) may be added in the future.
+enum InlayHintKind {
+ Parameter = 'parameter',
+ Type = 'type'
+}
+
+interface InlayHint {
+ range: vscodelc.Range;
+ kind: InlayHintKind | string;
+ label: string;
+}
+
+interface InlayHintsParams {
+ textDocument: vscodelc.TextDocumentIdentifier;
+}
+
+namespace InlayHintsRequest {
+ export const type =
+ new vscodelc.RequestType<InlayHintsParams, InlayHint[], void>(
+ 'odin/inlayHints');
+}
+
+interface InlayDecorations {
+ // Hints are grouped based on their InlayHintKind, because different kinds
+ // require different decoration types.
+ // A future iteration of the API may have free-form hint kinds, and instead
+ // specify style-related information (e.g. before vs. after) explicitly.
+ // With such an API, we could group hints based on unique presentation styles
+ // instead.
+ parameterHints: vscode.DecorationOptions[];
+ typeHints: vscode.DecorationOptions[];
+}
+
+interface HintStyle {
+ decorationType: vscode.TextEditorDecorationType;
+
+ toDecoration(hint: InlayHint,
+ conv: vscodelc.Protocol2CodeConverter): vscode.DecorationOptions;
+}
+
+const parameterHintStyle = createHintStyle('before');
+const typeHintStyle = createHintStyle('after');
+
+function createHintStyle(position: 'before' | 'after'): HintStyle {
+ const fg = new vscode.ThemeColor('odin.inlayHints.foreground');
+ const bg = new vscode.ThemeColor('odin.inlayHints.background');
+ return {
+ decorationType: vscode.window.createTextEditorDecorationType({
+ [position]: {
+ color: fg,
+ backgroundColor: bg,
+ fontStyle: 'normal',
+ fontWeight: 'normal',
+ textDecoration: ';font-size:smaller'
+ }
+ }),
+ toDecoration(hint: InlayHint, conv: vscodelc.Protocol2CodeConverter):
+ vscode.DecorationOptions {
+ return {
+ range: conv.asRange(hint.range),
+ renderOptions: { [position]: { contentText: hint.label } }
+ };
+ }
+ };
+}
+
+interface FileEntry {
+ document: vscode.TextDocument;
+
+ // Last applied decorations.
+ cachedDecorations: InlayDecorations | null;
+
+ // Source of the token to cancel in-flight inlay hints request if any.
+ inlaysRequest: vscode.CancellationTokenSource | null;
+}
+
+class InlayHintsFeature implements vscodelc.StaticFeature {
+ private enabled = false;
+ private sourceFiles = new Map<string, FileEntry>(); // keys are URIs
+ private readonly disposables: vscode.Disposable[] = [];
+
+ constructor(private readonly context: Ctx) { }
+
+ fillClientCapabilities(_capabilities: vscodelc.ClientCapabilities) { }
+ fillInitializeParams(_params: vscodelc.InitializeParams) { }
+
+ initialize(capabilities: vscodelc.ServerCapabilities,
+ _documentSelector: vscodelc.DocumentSelector | undefined) {
+ const serverCapabilities: vscodelc.ServerCapabilities &
+ { inlayHintsProvider?: boolean } = capabilities;
+ if (serverCapabilities.inlayHintsProvider) {
+ this.enabled = true;
+ this.startShowingHints();
+ }
+ }
+
+ onDidChangeVisibleTextEditors() {
+ if (!this.enabled) {
+ return;
+ }
+
+
+ const newSourceFiles = new Map<string, FileEntry>();
+
+ // Rerender all, even up-to-date editors for simplicity
+ this.context.visibleOdinEditors.forEach(async editor => {
+ const uri = editor.document.uri.toString();
+ const file = this.sourceFiles.get(uri) ?? {
+ document: editor.document,
+ cachedDecorations: null,
+ inlaysRequest: null
+ };
+ newSourceFiles.set(uri, file);
+
+ // No text documents changed, so we may try to use the cache
+ if (!file.cachedDecorations) {
+ const hints = await this.fetchHints(file);
+ if (!hints) {
+ return;
+ }
+
+ file.cachedDecorations = this.hintsToDecorations(hints);
+ }
+
+ this.renderDecorations(editor, file.cachedDecorations);
+ });
+
+ // Cancel requests for no longer visible (disposed) source files
+ this.sourceFiles.forEach((file, uri) => {
+ if (!newSourceFiles.has(uri)) {
+ file.inlaysRequest?.cancel();
+ }
+ });
+
+ this.sourceFiles = newSourceFiles;
+ }
+
+ onDidChangeTextDocument({ contentChanges,
+ document }: vscode.TextDocumentChangeEvent) {
+ if (!this.enabled || contentChanges.length === 0 ||
+ !this.context.isOdinDocument(document)) {
+ return;
+ }
+
+ this.syncCacheAndRenderHints();
+ }
+
+ dispose() { this.stopShowingHints(); }
+
+ private startShowingHints() {
+ vscode.window.onDidChangeVisibleTextEditors(
+ this.onDidChangeVisibleTextEditors, this, this.disposables);
+ vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this,
+ this.disposables);
+
+ // Set up initial cache shape
+ this.context.visibleOdinEditors.forEach(
+ editor => this.sourceFiles.set(editor.document.uri.toString(), {
+ document: editor.document,
+ inlaysRequest: null,
+ cachedDecorations: null
+ }));
+
+ this.syncCacheAndRenderHints();
+ }
+
+ private stopShowingHints() {
+ this.sourceFiles.forEach(file => file.inlaysRequest?.cancel());
+ this.context.visibleOdinEditors.forEach(
+ editor => this.renderDecorations(editor,
+ { parameterHints: [], typeHints: [] }));
+ this.disposables.forEach(d => d.dispose());
+ }
+
+ private renderDecorations(editor: vscode.TextEditor,
+ decorations: InlayDecorations) {
+ editor.setDecorations(parameterHintStyle.decorationType,
+ decorations.parameterHints);
+ editor.setDecorations(typeHintStyle.decorationType, decorations.typeHints);
+ }
+
+ private syncCacheAndRenderHints() {
+ this.sourceFiles.forEach(
+ (file, uri) => this.fetchHints(file).then(hints => {
+ if (!hints) {
+ return;
+ }
+
+ file.cachedDecorations = this.hintsToDecorations(hints);
+
+ for (const editor of this.context.visibleOdinEditors) {
+ if (editor.document.uri.toString() == uri) {
+ this.renderDecorations(editor, file.cachedDecorations);
+ }
+ }
+ }));
+ }
+
+ private hintsToDecorations(hints: InlayHint[]): InlayDecorations {
+ const decorations: InlayDecorations = { parameterHints: [], typeHints: [] };
+ const conv = this.context.client.protocol2CodeConverter;
+ for (const hint of hints) {
+ switch (hint.kind) {
+ case InlayHintKind.Parameter: {
+ decorations.parameterHints.push(
+ parameterHintStyle.toDecoration(hint, conv));
+ continue;
+ }
+ case InlayHintKind.Type: {
+ decorations.typeHints.push(typeHintStyle.toDecoration(hint, conv));
+ continue;
+ }
+ // Don't handle unknown hint kinds because we don't know how to style
+ // them. This may change in a future version of the protocol.
+ }
+ }
+ return decorations;
+ }
+
+ private async fetchHints(file: FileEntry): Promise<InlayHint[] | null> {
+ file.inlaysRequest?.cancel();
+
+ const tokenSource = new vscode.CancellationTokenSource();
+ file.inlaysRequest = tokenSource;
+
+ const request = { textDocument: { uri: file.document.uri.toString() } };
+
+ return this.context.client.sendRequest(InlayHintsRequest.type, request,
+ tokenSource.token);
+ }
+} \ No newline at end of file
diff --git a/src/analysis/analysis.odin b/src/analysis/analysis.odin
index 6fa8d67..c16bdf9 100644
--- a/src/analysis/analysis.odin
+++ b/src/analysis/analysis.odin
@@ -2044,7 +2044,7 @@ get_locals_for_range_stmt :: proc(file: ast.File, stmt: ast.Range_Stmt, ast_cont
if stmt.expr == nil {
return;
}
-
+
if symbol, ok := resolve_type_expression(ast_context, stmt.expr); ok {
#partial switch v in symbol.value {
case index.SymbolMapValue:
@@ -2237,6 +2237,33 @@ clear_locals :: proc(ast_context: ^AstContext) {
clear(&ast_context.usings);
}
+resolve_entire_file :: proc(document: ^common.Document, allocator := context.allocator) -> []^index.Symbol {
+ ast_context := make_ast_context(document.ast, document.imports, document.package_name, document.uri.uri);
+
+ get_globals(document.ast, &ast_context);
+
+ ast_context.current_package = ast_context.document_package;
+
+ symbols := make([]^index.Symbol, allocator);
+
+ for decl in document.ast.decls {
+ switch v in decl.derived {
+ case ast.Proc_Lit:
+ resolve_entire_procedure(v.type, &symbols, allocator);
+ }
+ }
+}
+
+resolve_entire_procedure :: proc(procedure: ^ast.Proc_Type, symbols: ^[]^index.Symbol, allocator := context.allocator) {
+ if procedure == nil {
+ return {};
+ }
+
+ get_locals()
+
+
+}
+
concatenate_symbols_information :: proc(ast_context: ^AstContext, symbol: index.Symbol, is_completion: bool) -> string {
pkg := path.base(symbol.pkg, false, context.temp_allocator);
diff --git a/src/server/inlay_hints.odin b/src/server/inlay_hints.odin
new file mode 100644
index 0000000..7d96c84
--- /dev/null
+++ b/src/server/inlay_hints.odin
@@ -0,0 +1,82 @@
+package server
+
+import "core:odin/ast"
+import "core:fmt"
+
+import "shared:common"
+import "shared:analysis"
+import "shared:index"
+
+//document
+get_inlay_hints :: proc(document: ^common.Document) -> ([]InlayHint, bool) {
+
+ using analysis;
+
+ hints := make([dynamic]InlayHint, context.temp_allocator);
+
+ ast_context := make_ast_context(document.ast, document.imports, document.package_name, document.uri.uri);
+
+ Visit_Data :: struct {
+ calls: [dynamic]ast.Call_Expr,
+ }
+
+ data := Visit_Data {
+ calls = make([dynamic]ast.Call_Expr, context.temp_allocator),
+ };
+
+ visit :: proc(visitor: ^ast.Visitor, node: ^ast.Node) -> ^ast.Visitor {
+ if node == nil || visitor == nil {
+ return nil;
+ }
+
+ data := cast(^Visit_Data)visitor.data;
+
+ if call, ok := node.derived.(ast.Call_Expr); ok {
+ append(&data.calls, call);
+ }
+
+ return visitor;
+ }
+
+ visitor := ast.Visitor {
+ data = &data,
+ visit = visit,
+ }
+
+ for decl in document.ast.decls {
+ ast.walk(&visitor, decl);
+ }
+
+ loop: for call in &data.calls {
+ symbol_arg_count := 0
+ for arg in call.args {
+ if _, ok := arg.derived.(ast.Field); ok {
+ continue loop;
+ }
+ }
+
+ if symbol, ok := resolve_type_expression(&ast_context, &call); ok {
+ if symbol_call, ok := symbol.value.(index.SymbolProcedureValue); ok {
+ for arg in symbol_call.arg_types {
+ for name in arg.names {
+ if symbol_arg_count >= len(call.args) {
+ continue loop;
+ }
+
+ if ident, ok := name.derived.(ast.Ident); ok {
+ hint := InlayHint {
+ kind = "parameter",
+ label = fmt.tprintf("%v:", ident.name),
+ range = common.get_token_range(call.args[symbol_arg_count], string(document.text)),
+ }
+ append(&hints, hint);
+ }
+ symbol_arg_count += 1;
+ }
+ }
+ }
+ }
+ }
+
+ return hints[:], true;
+} \ No newline at end of file
diff --git a/src/server/requests.odin b/src/server/requests.odin
index f4dc267..8fc0fa1 100644
--- a/src/server/requests.odin
+++ b/src/server/requests.odin
@@ -43,6 +43,7 @@ RequestType :: enum {
FormatDocument,
Hover,
CancelRequest,
+ InlayHint,
}
RequestInfo :: struct {
@@ -186,6 +187,7 @@ request_map: map[string]RequestType = {
"textDocument/hover" = .Hover,
"$/cancelRequest" = .CancelRequest,
"textDocument/formatting" = .FormatDocument,
+ "odin/inlayHints" = .InlayHint,
};
handle_error :: proc (err: common.Error, id: RequestId, writer: ^Writer) {
@@ -282,6 +284,8 @@ handle_request :: proc (request: json.Value, config: ^common.Config, writer: ^Wr
case .CancelRequest:
case .FormatDocument:
task_proc = request_format_document;
+ case .InlayHint:
+ task_proc = request_inlay_hint;
}
task := common.Task {
@@ -298,9 +302,9 @@ handle_request :: proc (request: json.Value, config: ^common.Config, writer: ^Wr
break;
}
}
- case .Initialize,.Initialized:
+ case .Initialize, .Initialized:
task_proc(&task);
- case .Completion,.Definition,.Hover,.FormatDocument:
+ case .Completion, .Definition, .Hover, .FormatDocument:
uri := root["params"].(json.Object)["textDocument"].(json.Object)["uri"].(json.String);
@@ -315,7 +319,7 @@ handle_request :: proc (request: json.Value, config: ^common.Config, writer: ^Wr
task_proc(&task);
- case .DidClose,.DidChange,.DidOpen,.DidSave:
+ case .DidClose, .DidChange, .DidOpen, .DidSave:
uri := root["params"].(json.Object)["textDocument"].(json.Object)["uri"].(json.String);
@@ -335,7 +339,7 @@ handle_request :: proc (request: json.Value, config: ^common.Config, writer: ^Wr
document_release(document);
case .Shutdown,.Exit:
task_proc(&task);
- case .SignatureHelp,.SemanticTokensFull,.SemanticTokensRange,.DocumentSymbol:
+ case .SignatureHelp, .SemanticTokensFull, .SemanticTokensRange, .DocumentSymbol, .InlayHint:
uri := root["params"].(json.Object)["textDocument"].(json.Object)["uri"].(json.String);
@@ -541,6 +545,7 @@ request_initialize :: proc (task: ^common.Task) {
tokenModifiers = token_modifiers,
},
},
+ inlayHintsProvider = true,
documentSymbolProvider = config.enable_document_symbols,
hoverProvider = config.enable_hover,
documentFormattingProvider = config.enable_format,
@@ -1108,3 +1113,34 @@ request_hover :: proc (task: ^common.Task) {
send_response(response, writer);
}
+
+request_inlay_hint :: proc (task: ^common.Task) {
+ info := get_request_info(task);
+
+ using info;
+
+ defer {
+ document_release(document);
+ json.destroy_value(root);
+ free(info);
+ }
+
+ params_object, ok := params.(json.Object);
+
+ if !ok {
+ handle_error(.ParseError, id, writer);
+ return;
+ }
+
+ hints: []InlayHint;
+ hints, ok = get_inlay_hints(document);
+
+ if !ok {
+ handle_error(.InternalError, id, writer);
+ return;
+ }
+
+ response := make_response_message(params = hints, id = id);
+
+ send_response(response, writer);
+} \ No newline at end of file
diff --git a/src/server/types.odin b/src/server/types.odin
index 9f3c866..d369b33 100644
--- a/src/server/types.odin
+++ b/src/server/types.odin
@@ -26,6 +26,7 @@ ResponseParams :: union {
SemanticTokens,
Hover,
[]TextEdit,
+ []InlayHint,
}
ResponseMessage :: struct {
@@ -91,6 +92,7 @@ ServerCapabilities :: struct {
documentSymbolProvider: bool,
hoverProvider: bool,
documentFormattingProvider: bool,
+ inlayHintsProvider: bool,
}
CompletionOptions :: struct {
@@ -357,4 +359,10 @@ Command :: struct {
title: string,
command: string,
arguments: []string,
+}
+
+InlayHint :: struct {
+ range: common.Range,
+ kind: string,
+ label: string,
} \ No newline at end of file