From 169e69d217600062f6299f7f9521f3f2776d0333 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 23 Jul 2019 16:38:21 +0300 Subject: Show type decorators --- editors/code/src/commands/index.ts | 4 +- editors/code/src/commands/inlay_hints.ts | 142 +++++++++++++++++++++++++++++++ editors/code/src/config.ts | 5 ++ editors/code/src/extension.ts | 11 +++ 4 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 editors/code/src/commands/inlay_hints.ts (limited to 'editors/code/src') diff --git a/editors/code/src/commands/index.ts b/editors/code/src/commands/index.ts index 194658497..d17f702e8 100644 --- a/editors/code/src/commands/index.ts +++ b/editors/code/src/commands/index.ts @@ -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 index 000000000..2780e9326 --- /dev/null +++ b/editors/code/src/commands/inlay_hints.ts @@ -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(); + private displayHints = true; + + public async loadHints(editor: vscode.TextEditor | undefined): Promise { + 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 { + 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 { + 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 { + 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 { + const request: InlayHintsParams = { textDocument: { uri: documentUri } }; + const client = Server.client; + return client.onReady().then(() => client.sendRequest( + 'rust-analyzer/inlayHints', + request + )); + } +} diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 3f1b482e3..4d58a1a93 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -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; + } } } diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts index c8c3004a7..a0b897385 100644 --- a/editors/code/src/extension.ts +++ b/editors/code/src/extension.ts @@ -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 { -- cgit v1.2.3