]> git.lizzy.rs Git - rust.git/commitdiff
Show type decorators
authorKirill Bulatov <mail4score@gmail.com>
Tue, 23 Jul 2019 13:38:21 +0000 (16:38 +0300)
committerKirill Bulatov <mail4score@gmail.com>
Thu, 25 Jul 2019 12:17:28 +0000 (15:17 +0300)
editors/code/package.json
editors/code/src/commands/index.ts
editors/code/src/commands/inlay_hints.ts [new file with mode: 0644]
editors/code/src/config.ts
editors/code/src/extension.ts

index fd30c7946669c01fb663b0c3119434f1abddb97a..060a3a247532236857a20c0a23484d6e8d8f20e2 100644 (file)
                     "type": "number",
                     "default": null,
                     "description": "Number of syntax trees rust-analyzer keeps in memory"
+                },
+                "rust-analyzer.displayInlayHints": {
+                    "type": "boolean",
+                    "default": true,
+                    "description": "Display additional type information in the editor"
                 }
             }
         },
                     "light": "#000000",
                     "highContrast": "#FFFFFF"
                 }
+            },
+            {
+                "id": "ralsp.inlayHint",
+                "description": "Color for inlay hints",
+                "defaults": {
+                    "dark": "#A0A0A0F0",
+                    "light": "#747474",
+                    "highContrast": "#BEBEBE"
+                }
             }
         ]
     }
index 19465849762fa9d073f18d8325061dc06d27a34c..d17f702e8d76ddd8dfad90f03f83a7863e11cddd 100644 (file)
@@ -6,6 +6,7 @@ import * as onEnter from './on_enter';
 import * as parentModule from './parent_module';
 import * as runnables from './runnables';
 import * as syntaxTree from './syntaxTree';
+import * as inlayHints from './inlay_hints';
 
 export {
     analyzerStatus,
@@ -15,5 +16,6 @@ export {
     parentModule,
     runnables,
     syntaxTree,
-    onEnter
+    onEnter,
+    inlayHints,
 };
diff --git a/editors/code/src/commands/inlay_hints.ts b/editors/code/src/commands/inlay_hints.ts
new file mode 100644 (file)
index 0000000..2780e93
--- /dev/null
@@ -0,0 +1,142 @@
+import * as vscode from 'vscode';
+import { DecorationOptions, Range, TextDocumentChangeEvent, TextDocumentContentChangeEvent, TextEditor } from 'vscode';
+import { TextDocumentIdentifier } from 'vscode-languageclient';
+import { Server } from '../server';
+
+interface InlayHintsParams {
+    textDocument: TextDocumentIdentifier;
+}
+
+interface InlayHint {
+    range: Range,
+    kind: string,
+    label: string,
+}
+
+const typeHintDecorationType = vscode.window.createTextEditorDecorationType({
+    after: {
+        color: new vscode.ThemeColor('ralsp.inlayHint'),
+    },
+});
+
+export class HintsUpdater {
+    private currentDecorations = new Map<string, DecorationOptions[]>();
+    private displayHints = true;
+
+    public async loadHints(editor: vscode.TextEditor | undefined): Promise<void> {
+        if (this.displayHints && editor !== undefined) {
+            await this.updateDecorationsFromServer(editor.document.uri.toString(), editor);
+        }
+    }
+
+    public dropHints(document: vscode.TextDocument) {
+        if (this.displayHints) {
+            this.currentDecorations.delete(document.uri.toString());
+        }
+    }
+
+    public async toggleHintsDisplay(displayHints: boolean): Promise<void> {
+        if (this.displayHints !== displayHints) {
+            this.displayHints = displayHints;
+            this.currentDecorations.clear();
+
+            if (displayHints) {
+                return this.updateHints();
+            } else {
+                const editor = vscode.window.activeTextEditor;
+                if (editor != null) {
+                    return editor.setDecorations(typeHintDecorationType, [])
+                }
+            }
+        }
+    }
+
+    public async updateHints(cause?: TextDocumentChangeEvent): Promise<void> {
+        if (!this.displayHints) {
+            return;
+        }
+        const editor = vscode.window.activeTextEditor;
+        if (editor == null) {
+            return;
+        }
+        const document = cause == null ? editor.document : cause.document;
+        if (document.languageId !== 'rust') {
+            return;
+        }
+
+        const documentUri = document.uri.toString();
+        const documentDecorators = this.currentDecorations.get(documentUri) || [];
+
+        if (documentDecorators.length > 0) {
+            // FIXME a dbg! in the handlers.rs of the server causes
+            // an endless storm of events with `cause.contentChanges` with the dbg messages, why?
+            const changesFromFile = cause !== undefined ? cause.contentChanges.filter(changeEvent => this.isEventInFile(document.lineCount, changeEvent)) : [];
+            if (changesFromFile.length === 0) {
+                return;
+            }
+
+            const firstShiftedLine = this.getFirstShiftedLine(changesFromFile);
+            if (firstShiftedLine !== null) {
+                const unchangedDecorations = documentDecorators.filter(decoration => decoration.range.start.line < firstShiftedLine);
+                if (unchangedDecorations.length !== documentDecorators.length) {
+                    await editor.setDecorations(typeHintDecorationType, unchangedDecorations);
+                }
+            }
+        }
+        return await this.updateDecorationsFromServer(documentUri, editor);
+    }
+
+    private isEventInFile(documentLineCount: number, event: TextDocumentContentChangeEvent): boolean {
+        const eventText = event.text;
+        if (eventText.length === 0) {
+            return event.range.start.line <= documentLineCount || event.range.end.line <= documentLineCount;
+        } else {
+            return event.range.start.line <= documentLineCount && event.range.end.line <= documentLineCount;
+        }
+    }
+
+    private getFirstShiftedLine(changeEvents: TextDocumentContentChangeEvent[]): number | null {
+        let topmostUnshiftedLine: number | null = null;
+
+        changeEvents
+            .filter(event => this.isShiftingChange(event))
+            .forEach(event => {
+                const shiftedLineNumber = event.range.start.line;
+                if (topmostUnshiftedLine === null || topmostUnshiftedLine > shiftedLineNumber) {
+                    topmostUnshiftedLine = shiftedLineNumber;
+                }
+            });
+
+        return topmostUnshiftedLine;
+    }
+
+    private isShiftingChange(event: TextDocumentContentChangeEvent) {
+        const eventText = event.text;
+        if (eventText.length === 0) {
+            return !event.range.isSingleLine;
+        } else {
+            return eventText.indexOf('\n') >= 0 || eventText.indexOf('\r') >= 0;
+        }
+    }
+
+    private async updateDecorationsFromServer(documentUri: string, editor: TextEditor): Promise<void> {
+        const newHints = await this.queryHints(documentUri) || [];
+        const newDecorations = newHints.map(hint => (
+            {
+                range: hint.range,
+                renderOptions: { after: { contentText: `: ${hint.label}` } },
+            }
+        ));
+        this.currentDecorations.set(documentUri, newDecorations);
+        return editor.setDecorations(typeHintDecorationType, newDecorations);
+    }
+
+    private async queryHints(documentUri: string): Promise<InlayHint[] | null> {
+        const request: InlayHintsParams = { textDocument: { uri: documentUri } };
+        const client = Server.client;
+        return client.onReady().then(() => client.sendRequest<InlayHint[] | null>(
+            'rust-analyzer/inlayHints',
+            request
+        ));
+    }
+}
index 3f1b482e396042a1c8d92ebb0a20432d8d953296..4d58a1a9361f22ae6d2733fc4eeda5d8e0fd299e 100644 (file)
@@ -21,6 +21,7 @@ export class Config {
     public raLspServerPath = RA_LSP_DEBUG || 'ra_lsp_server';
     public showWorkspaceLoadedNotification = true;
     public lruCapacity: null | number = null;
+    public displayInlayHints = true;
     public cargoWatchOptions: CargoWatchOptions = {
         enableOnStartup: 'ask',
         trace: 'off',
@@ -123,5 +124,9 @@ export class Config {
         if (config.has('lruCapacity')) {
             this.lruCapacity = config.get('lruCapacity') as number;
         }
+
+        if (config.has('displayInlayHints')) {
+            this.displayInlayHints = config.get('displayInlayHints') as boolean;
+        }
     }
 }
index c8c3004a7f637debe966e9062944c71188f689bd..a0b897385e7b3b4c2311d0fdd3bfd8e41d669c27 100644 (file)
@@ -3,6 +3,7 @@ import * as lc from 'vscode-languageclient';
 
 import * as commands from './commands';
 import { CargoWatchProvider } from './commands/cargo_watch';
+import { HintsUpdater } from './commands/inlay_hints';
 import {
     interactivelyStartCargoWatch,
     startCargoWatch
@@ -147,6 +148,16 @@ export function activate(context: vscode.ExtensionContext) {
 
     // Start the language server, finally!
     startServer();
+
+    if (Server.config.displayInlayHints) {
+        const hintsUpdater = new HintsUpdater();
+        hintsUpdater.loadHints(vscode.window.activeTextEditor).then(() => {
+            vscode.window.onDidChangeActiveTextEditor(editor => hintsUpdater.loadHints(editor));
+            vscode.workspace.onDidChangeTextDocument(e => hintsUpdater.updateHints(e));
+            vscode.workspace.onDidCloseTextDocument(document => hintsUpdater.dropHints(document));
+            vscode.workspace.onDidChangeConfiguration(_ => hintsUpdater.toggleHintsDisplay(Server.config.displayInlayHints));
+        });
+    }
 }
 
 export function deactivate(): Thenable<void> {