diff options
Diffstat (limited to 'editors/code')
-rw-r--r-- | editors/code/package.json | 14 | ||||
-rw-r--r-- | editors/code/src/commands/index.ts | 4 | ||||
-rw-r--r-- | editors/code/src/commands/inlay_hints.ts | 142 | ||||
-rw-r--r-- | editors/code/src/config.ts | 5 | ||||
-rw-r--r-- | editors/code/src/extension.ts | 11 |
5 files changed, 175 insertions, 1 deletions
diff --git a/editors/code/package.json b/editors/code/package.json index fd30c7946..060a3a247 100644 --- a/editors/code/package.json +++ b/editors/code/package.json | |||
@@ -238,6 +238,11 @@ | |||
238 | "type": "number", | 238 | "type": "number", |
239 | "default": null, | 239 | "default": null, |
240 | "description": "Number of syntax trees rust-analyzer keeps in memory" | 240 | "description": "Number of syntax trees rust-analyzer keeps in memory" |
241 | }, | ||
242 | "rust-analyzer.displayInlayHints": { | ||
243 | "type": "boolean", | ||
244 | "default": true, | ||
245 | "description": "Display additional type information in the editor" | ||
241 | } | 246 | } |
242 | } | 247 | } |
243 | }, | 248 | }, |
@@ -444,6 +449,15 @@ | |||
444 | "light": "#000000", | 449 | "light": "#000000", |
445 | "highContrast": "#FFFFFF" | 450 | "highContrast": "#FFFFFF" |
446 | } | 451 | } |
452 | }, | ||
453 | { | ||
454 | "id": "ralsp.inlayHint", | ||
455 | "description": "Color for inlay hints", | ||
456 | "defaults": { | ||
457 | "dark": "#A0A0A0F0", | ||
458 | "light": "#747474", | ||
459 | "highContrast": "#BEBEBE" | ||
460 | } | ||
447 | } | 461 | } |
448 | ] | 462 | ] |
449 | } | 463 | } |
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'; | |||
6 | import * as parentModule from './parent_module'; | 6 | import * as parentModule from './parent_module'; |
7 | import * as runnables from './runnables'; | 7 | import * as runnables from './runnables'; |
8 | import * as syntaxTree from './syntaxTree'; | 8 | import * as syntaxTree from './syntaxTree'; |
9 | import * as inlayHints from './inlay_hints'; | ||
9 | 10 | ||
10 | export { | 11 | export { |
11 | analyzerStatus, | 12 | analyzerStatus, |
@@ -15,5 +16,6 @@ export { | |||
15 | parentModule, | 16 | parentModule, |
16 | runnables, | 17 | runnables, |
17 | syntaxTree, | 18 | syntaxTree, |
18 | onEnter | 19 | onEnter, |
20 | inlayHints, | ||
19 | }; | 21 | }; |
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 @@ | |||
1 | import * as vscode from 'vscode'; | ||
2 | import { DecorationOptions, Range, TextDocumentChangeEvent, TextDocumentContentChangeEvent, TextEditor } from 'vscode'; | ||
3 | import { TextDocumentIdentifier } from 'vscode-languageclient'; | ||
4 | import { Server } from '../server'; | ||
5 | |||
6 | interface InlayHintsParams { | ||
7 | textDocument: TextDocumentIdentifier; | ||
8 | } | ||
9 | |||
10 | interface InlayHint { | ||
11 | range: Range, | ||
12 | kind: string, | ||
13 | label: string, | ||
14 | } | ||
15 | |||
16 | const typeHintDecorationType = vscode.window.createTextEditorDecorationType({ | ||
17 | after: { | ||
18 | color: new vscode.ThemeColor('ralsp.inlayHint'), | ||
19 | }, | ||
20 | }); | ||
21 | |||
22 | export class HintsUpdater { | ||
23 | private currentDecorations = new Map<string, DecorationOptions[]>(); | ||
24 | private displayHints = true; | ||
25 | |||
26 | public async loadHints(editor: vscode.TextEditor | undefined): Promise<void> { | ||
27 | if (this.displayHints && editor !== undefined) { | ||
28 | await this.updateDecorationsFromServer(editor.document.uri.toString(), editor); | ||
29 | } | ||
30 | } | ||
31 | |||
32 | public dropHints(document: vscode.TextDocument) { | ||
33 | if (this.displayHints) { | ||
34 | this.currentDecorations.delete(document.uri.toString()); | ||
35 | } | ||
36 | } | ||
37 | |||
38 | public async toggleHintsDisplay(displayHints: boolean): Promise<void> { | ||
39 | if (this.displayHints !== displayHints) { | ||
40 | this.displayHints = displayHints; | ||
41 | this.currentDecorations.clear(); | ||
42 | |||
43 | if (displayHints) { | ||
44 | return this.updateHints(); | ||
45 | } else { | ||
46 | const editor = vscode.window.activeTextEditor; | ||
47 | if (editor != null) { | ||
48 | return editor.setDecorations(typeHintDecorationType, []) | ||
49 | } | ||
50 | } | ||
51 | } | ||
52 | } | ||
53 | |||
54 | public async updateHints(cause?: TextDocumentChangeEvent): Promise<void> { | ||
55 | if (!this.displayHints) { | ||
56 | return; | ||
57 | } | ||
58 | const editor = vscode.window.activeTextEditor; | ||
59 | if (editor == null) { | ||
60 | return; | ||
61 | } | ||
62 | const document = cause == null ? editor.document : cause.document; | ||
63 | if (document.languageId !== 'rust') { | ||
64 | return; | ||
65 | } | ||
66 | |||
67 | const documentUri = document.uri.toString(); | ||
68 | const documentDecorators = this.currentDecorations.get(documentUri) || []; | ||
69 | |||
70 | if (documentDecorators.length > 0) { | ||
71 | // FIXME a dbg! in the handlers.rs of the server causes | ||
72 | // an endless storm of events with `cause.contentChanges` with the dbg messages, why? | ||
73 | const changesFromFile = cause !== undefined ? cause.contentChanges.filter(changeEvent => this.isEventInFile(document.lineCount, changeEvent)) : []; | ||
74 | if (changesFromFile.length === 0) { | ||
75 | return; | ||
76 | } | ||
77 | |||
78 | const firstShiftedLine = this.getFirstShiftedLine(changesFromFile); | ||
79 | if (firstShiftedLine !== null) { | ||
80 | const unchangedDecorations = documentDecorators.filter(decoration => decoration.range.start.line < firstShiftedLine); | ||
81 | if (unchangedDecorations.length !== documentDecorators.length) { | ||
82 | await editor.setDecorations(typeHintDecorationType, unchangedDecorations); | ||
83 | } | ||
84 | } | ||
85 | } | ||
86 | return await this.updateDecorationsFromServer(documentUri, editor); | ||
87 | } | ||
88 | |||
89 | private isEventInFile(documentLineCount: number, event: TextDocumentContentChangeEvent): boolean { | ||
90 | const eventText = event.text; | ||
91 | if (eventText.length === 0) { | ||
92 | return event.range.start.line <= documentLineCount || event.range.end.line <= documentLineCount; | ||
93 | } else { | ||
94 | return event.range.start.line <= documentLineCount && event.range.end.line <= documentLineCount; | ||
95 | } | ||
96 | } | ||
97 | |||
98 | private getFirstShiftedLine(changeEvents: TextDocumentContentChangeEvent[]): number | null { | ||
99 | let topmostUnshiftedLine: number | null = null; | ||
100 | |||
101 | changeEvents | ||
102 | .filter(event => this.isShiftingChange(event)) | ||
103 | .forEach(event => { | ||
104 | const shiftedLineNumber = event.range.start.line; | ||
105 | if (topmostUnshiftedLine === null || topmostUnshiftedLine > shiftedLineNumber) { | ||
106 | topmostUnshiftedLine = shiftedLineNumber; | ||
107 | } | ||
108 | }); | ||
109 | |||
110 | return topmostUnshiftedLine; | ||
111 | } | ||
112 | |||
113 | private isShiftingChange(event: TextDocumentContentChangeEvent) { | ||
114 | const eventText = event.text; | ||
115 | if (eventText.length === 0) { | ||
116 | return !event.range.isSingleLine; | ||
117 | } else { | ||
118 | return eventText.indexOf('\n') >= 0 || eventText.indexOf('\r') >= 0; | ||
119 | } | ||
120 | } | ||
121 | |||
122 | private async updateDecorationsFromServer(documentUri: string, editor: TextEditor): Promise<void> { | ||
123 | const newHints = await this.queryHints(documentUri) || []; | ||
124 | const newDecorations = newHints.map(hint => ( | ||
125 | { | ||
126 | range: hint.range, | ||
127 | renderOptions: { after: { contentText: `: ${hint.label}` } }, | ||
128 | } | ||
129 | )); | ||
130 | this.currentDecorations.set(documentUri, newDecorations); | ||
131 | return editor.setDecorations(typeHintDecorationType, newDecorations); | ||
132 | } | ||
133 | |||
134 | private async queryHints(documentUri: string): Promise<InlayHint[] | null> { | ||
135 | const request: InlayHintsParams = { textDocument: { uri: documentUri } }; | ||
136 | const client = Server.client; | ||
137 | return client.onReady().then(() => client.sendRequest<InlayHint[] | null>( | ||
138 | 'rust-analyzer/inlayHints', | ||
139 | request | ||
140 | )); | ||
141 | } | ||
142 | } | ||
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 { | |||
21 | public raLspServerPath = RA_LSP_DEBUG || 'ra_lsp_server'; | 21 | public raLspServerPath = RA_LSP_DEBUG || 'ra_lsp_server'; |
22 | public showWorkspaceLoadedNotification = true; | 22 | public showWorkspaceLoadedNotification = true; |
23 | public lruCapacity: null | number = null; | 23 | public lruCapacity: null | number = null; |
24 | public displayInlayHints = true; | ||
24 | public cargoWatchOptions: CargoWatchOptions = { | 25 | public cargoWatchOptions: CargoWatchOptions = { |
25 | enableOnStartup: 'ask', | 26 | enableOnStartup: 'ask', |
26 | trace: 'off', | 27 | trace: 'off', |
@@ -123,5 +124,9 @@ export class Config { | |||
123 | if (config.has('lruCapacity')) { | 124 | if (config.has('lruCapacity')) { |
124 | this.lruCapacity = config.get('lruCapacity') as number; | 125 | this.lruCapacity = config.get('lruCapacity') as number; |
125 | } | 126 | } |
127 | |||
128 | if (config.has('displayInlayHints')) { | ||
129 | this.displayInlayHints = config.get('displayInlayHints') as boolean; | ||
130 | } | ||
126 | } | 131 | } |
127 | } | 132 | } |
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'; | |||
3 | 3 | ||
4 | import * as commands from './commands'; | 4 | import * as commands from './commands'; |
5 | import { CargoWatchProvider } from './commands/cargo_watch'; | 5 | import { CargoWatchProvider } from './commands/cargo_watch'; |
6 | import { HintsUpdater } from './commands/inlay_hints'; | ||
6 | import { | 7 | import { |
7 | interactivelyStartCargoWatch, | 8 | interactivelyStartCargoWatch, |
8 | startCargoWatch | 9 | startCargoWatch |
@@ -147,6 +148,16 @@ export function activate(context: vscode.ExtensionContext) { | |||
147 | 148 | ||
148 | // Start the language server, finally! | 149 | // Start the language server, finally! |
149 | startServer(); | 150 | startServer(); |
151 | |||
152 | if (Server.config.displayInlayHints) { | ||
153 | const hintsUpdater = new HintsUpdater(); | ||
154 | hintsUpdater.loadHints(vscode.window.activeTextEditor).then(() => { | ||
155 | vscode.window.onDidChangeActiveTextEditor(editor => hintsUpdater.loadHints(editor)); | ||
156 | vscode.workspace.onDidChangeTextDocument(e => hintsUpdater.updateHints(e)); | ||
157 | vscode.workspace.onDidCloseTextDocument(document => hintsUpdater.dropHints(document)); | ||
158 | vscode.workspace.onDidChangeConfiguration(_ => hintsUpdater.toggleHintsDisplay(Server.config.displayInlayHints)); | ||
159 | }); | ||
160 | } | ||
150 | } | 161 | } |
151 | 162 | ||
152 | export function deactivate(): Thenable<void> { | 163 | export function deactivate(): Thenable<void> { |