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 +++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 editors/code/src/commands/inlay_hints.ts (limited to 'editors/code/src/commands') 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 + )); + } +} -- cgit v1.2.3