Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'editors/code/src/inlay_hints.ts')
-rw-r--r--editors/code/src/inlay_hints.ts277
1 files changed, 32 insertions, 245 deletions
diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts
index c23d6f7384..441370a677 100644
--- a/editors/code/src/inlay_hints.ts
+++ b/editors/code/src/inlay_hints.ts
@@ -1,267 +1,54 @@
-import * as lc from "vscode-languageclient";
import * as vscode from 'vscode';
import * as ra from './lsp_ext';
import { Ctx, Disposable } from './ctx';
-import { sendRequestWithRetry, isRustDocument, RustDocument, RustEditor, sleep } from './util';
-
-interface InlayHintStyle {
- decorationType: vscode.TextEditorDecorationType;
- toDecoration(hint: ra.InlayHint, conv: lc.Protocol2CodeConverter): vscode.DecorationOptions;
-};
-
-interface InlayHintsStyles {
- typeHints: InlayHintStyle;
- paramHints: InlayHintStyle;
- chainingHints: InlayHintStyle;
-}
-
+import { sendRequestWithRetry, isRustDocument } from './util';
export function activateInlayHints(ctx: Ctx) {
const maybeUpdater = {
- updater: null as null | HintsUpdater,
+ hintsProvider: null as Disposable | null,
+ updateHintsEventEmitter: new vscode.EventEmitter<void>(),
+
async onConfigChange() {
+ this.dispose();
+
const anyEnabled = ctx.config.inlayHints.typeHints
|| ctx.config.inlayHints.parameterHints
|| ctx.config.inlayHints.chainingHints;
const enabled = ctx.config.inlayHints.enable && anyEnabled;
+ if (!enabled) return;
+
+ const event = this.updateHintsEventEmitter.event;
+ this.hintsProvider = vscode.languages.registerInlayHintsProvider({ scheme: 'file', language: 'rust' }, new class implements vscode.InlayHintsProvider {
+ onDidChangeInlayHints = event;
+ async provideInlayHints(document: vscode.TextDocument, range: vscode.Range, token: vscode.CancellationToken): Promise<vscode.InlayHint[]> {
+ const request = { textDocument: { uri: document.uri.toString() }, range: { start: range.start, end: range.end } };
+ const hints = await sendRequestWithRetry(ctx.client, ra.inlayHints, request, token).catch(_ => null);
+ if (hints == null) {
+ return [];
+ } else {
+ return hints;
+ }
+ }
+ });
+ },
- if (!enabled) return this.dispose();
-
- await sleep(100);
- if (this.updater) {
- this.updater.updateInlayHintsStyles();
- this.updater.syncCacheAndRenderHints();
- } else {
- this.updater = new HintsUpdater(ctx);
- }
+ onDidChangeTextDocument({ contentChanges, document }: vscode.TextDocumentChangeEvent) {
+ if (contentChanges.length === 0 || !isRustDocument(document)) return;
+ this.updateHintsEventEmitter.fire();
},
+
dispose() {
- this.updater?.dispose();
- this.updater = null;
- }
+ this.hintsProvider?.dispose();
+ this.hintsProvider = null;
+ this.updateHintsEventEmitter.dispose();
+ },
};
ctx.pushCleanup(maybeUpdater);
- vscode.workspace.onDidChangeConfiguration(
- maybeUpdater.onConfigChange, maybeUpdater, ctx.subscriptions
- );
+ vscode.workspace.onDidChangeConfiguration(maybeUpdater.onConfigChange, maybeUpdater, ctx.subscriptions);
+ vscode.workspace.onDidChangeTextDocument(maybeUpdater.onDidChangeTextDocument, maybeUpdater, ctx.subscriptions);
maybeUpdater.onConfigChange().catch(console.error);
}
-
-function createHintStyle(hintKind: "type" | "parameter" | "chaining", smallerHints: boolean): InlayHintStyle {
- // U+200C is a zero-width non-joiner to prevent the editor from forming a ligature
- // between code and type hints
- const [pos, render] = ({
- type: ["after", (label: string) => `\u{200c}: ${label}`],
- parameter: ["before", (label: string) => `${label}: `],
- chaining: ["after", (label: string) => `\u{200c}: ${label}`],
- } as const)[hintKind];
-
- const fg = new vscode.ThemeColor(`rust_analyzer.inlayHints.foreground.${hintKind}Hints`);
- const bg = new vscode.ThemeColor(`rust_analyzer.inlayHints.background.${hintKind}Hints`);
- return {
- decorationType: vscode.window.createTextEditorDecorationType({
- [pos]: {
- color: fg,
- backgroundColor: bg,
- fontStyle: "normal",
- fontWeight: "normal",
- textDecoration: smallerHints ? ";font-size:smaller" : "none",
- },
- }),
- toDecoration(hint: ra.InlayHint, conv: lc.Protocol2CodeConverter): vscode.DecorationOptions {
- return {
- range: conv.asRange(hint.range),
- renderOptions: { [pos]: { contentText: render(hint.label) } }
- };
- }
- };
-}
-
-const smallHintsStyles = {
- typeHints: createHintStyle("type", true),
- paramHints: createHintStyle("parameter", true),
- chainingHints: createHintStyle("chaining", true),
-};
-
-const biggerHintsStyles = {
- typeHints: createHintStyle("type", false),
- paramHints: createHintStyle("parameter", false),
- chainingHints: createHintStyle("chaining", false),
-};
-
-class HintsUpdater implements Disposable {
- private sourceFiles = new Map<string, RustSourceFile>(); // map Uri -> RustSourceFile
- private readonly disposables: Disposable[] = [];
- private pendingDisposeDecorations: undefined | InlayHintsStyles = undefined;
- private inlayHintsStyles!: InlayHintsStyles;
-
- constructor(private readonly ctx: Ctx) {
- vscode.window.onDidChangeVisibleTextEditors(
- this.onDidChangeVisibleTextEditors,
- this,
- this.disposables
- );
-
- vscode.workspace.onDidChangeTextDocument(
- this.onDidChangeTextDocument,
- this,
- this.disposables
- );
-
- // Set up initial cache shape
- ctx.visibleRustEditors.forEach(editor => this.sourceFiles.set(
- editor.document.uri.toString(),
- {
- document: editor.document,
- inlaysRequest: null,
- cachedDecorations: null
- }
- ));
-
- this.updateInlayHintsStyles();
- this.syncCacheAndRenderHints();
- }
-
- dispose() {
- this.sourceFiles.forEach(file => file.inlaysRequest?.cancel());
- this.ctx.visibleRustEditors.forEach(editor => this.renderDecorations(editor, { param: [], type: [], chaining: [] }));
- this.disposables.forEach(d => d.dispose());
- }
-
- onDidChangeTextDocument({ contentChanges, document }: vscode.TextDocumentChangeEvent) {
- if (contentChanges.length === 0 || !isRustDocument(document)) return;
- this.syncCacheAndRenderHints();
- }
-
- updateInlayHintsStyles() {
- const inlayHintsStyles = this.ctx.config.inlayHints.smallerHints ? smallHintsStyles : biggerHintsStyles;
-
- if (inlayHintsStyles !== this.inlayHintsStyles) {
- this.pendingDisposeDecorations = this.inlayHintsStyles;
- this.inlayHintsStyles = inlayHintsStyles;
- }
- }
-
- syncCacheAndRenderHints() {
- this.sourceFiles.forEach((file, uri) => this.fetchHints(file).then(hints => {
- if (!hints) return;
-
- file.cachedDecorations = this.hintsToDecorations(hints);
-
- for (const editor of this.ctx.visibleRustEditors) {
- if (editor.document.uri.toString() === uri) {
- this.renderDecorations(editor, file.cachedDecorations);
- }
- }
- }));
- }
-
- onDidChangeVisibleTextEditors() {
- const newSourceFiles = new Map<string, RustSourceFile>();
-
- // Rerendering all, even up-to-date editors for simplicity
- this.ctx.visibleRustEditors.forEach(async editor => {
- const uri = editor.document.uri.toString();
- const file = this.sourceFiles.get(uri) ?? {
- document: editor.document,
- inlaysRequest: null,
- cachedDecorations: 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;
- }
-
- private renderDecorations(editor: RustEditor, decorations: InlaysDecorations) {
- const { typeHints, paramHints, chainingHints } = this.inlayHintsStyles;
- if (this.pendingDisposeDecorations !== undefined) {
- const { typeHints, paramHints, chainingHints } = this.pendingDisposeDecorations;
- editor.setDecorations(typeHints.decorationType, []);
- editor.setDecorations(paramHints.decorationType, []);
- editor.setDecorations(chainingHints.decorationType, []);
- }
- editor.setDecorations(typeHints.decorationType, decorations.type);
- editor.setDecorations(paramHints.decorationType, decorations.param);
- editor.setDecorations(chainingHints.decorationType, decorations.chaining);
- }
-
- private hintsToDecorations(hints: ra.InlayHint[]): InlaysDecorations {
- const { typeHints, paramHints, chainingHints } = this.inlayHintsStyles;
- const decorations: InlaysDecorations = { type: [], param: [], chaining: [] };
- const conv = this.ctx.client.protocol2CodeConverter;
-
- for (const hint of hints) {
- switch (hint.kind) {
- case ra.InlayHint.Kind.TypeHint: {
- decorations.type.push(typeHints.toDecoration(hint, conv));
- continue;
- }
- case ra.InlayHint.Kind.ParamHint: {
- decorations.param.push(paramHints.toDecoration(hint, conv));
- continue;
- }
- case ra.InlayHint.Kind.ChainingHint: {
- decorations.chaining.push(chainingHints.toDecoration(hint, conv));
- continue;
- }
- }
- }
- return decorations;
- }
-
- private async fetchHints(file: RustSourceFile): Promise<null | ra.InlayHint[]> {
- file.inlaysRequest?.cancel();
-
- const tokenSource = new vscode.CancellationTokenSource();
- file.inlaysRequest = tokenSource;
-
- const request = { textDocument: { uri: file.document.uri.toString() } };
-
- return sendRequestWithRetry(this.ctx.client, ra.inlayHints, request, tokenSource.token)
- .catch(_ => null)
- .finally(() => {
- if (file.inlaysRequest === tokenSource) {
- file.inlaysRequest = null;
- }
- });
- }
-}
-
-interface InlaysDecorations {
- type: vscode.DecorationOptions[];
- param: vscode.DecorationOptions[];
- chaining: vscode.DecorationOptions[];
-}
-
-interface RustSourceFile {
- /**
- * Source of the token to cancel in-flight inlay hints request if any.
- */
- inlaysRequest: null | vscode.CancellationTokenSource;
- /**
- * Last applied decorations.
- */
- cachedDecorations: null | InlaysDecorations;
-
- document: RustDocument;
-}