aboutsummaryrefslogtreecommitdiff
path: root/editors/code/src/inlay_hints.ts
blob: a7be97db87681aabdde102ad31455148831a9a28 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import * as vscode from 'vscode';
import * as lc from 'vscode-languageclient';

import { Ctx } from './ctx';

export function activateInlayHints(ctx: Ctx) {
    const hintsUpdater = new HintsUpdater(ctx);
    vscode.window.onDidChangeVisibleTextEditors(async _ => {
        await hintsUpdater.refresh();
    }, ctx.subscriptions);

    vscode.workspace.onDidChangeTextDocument(async e => {
        if (e.contentChanges.length === 0) return;
        if (e.document.languageId !== 'rust') return;
        await hintsUpdater.refresh();
    }, ctx.subscriptions);

    vscode.workspace.onDidChangeConfiguration(_ => {
        hintsUpdater.setEnabled(ctx.config.displayInlayHints);
    }, ctx.subscriptions);

    // XXX: don't await here;
    // Who knows what happens if an exception is thrown here...
    hintsUpdater.refresh();
}

interface InlayHintsParams {
    textDocument: lc.TextDocumentIdentifier;
}

interface InlayHint {
    range: vscode.Range;
    kind: string;
    label: string;
}

const typeHintDecorationType = vscode.window.createTextEditorDecorationType({
    after: {
        color: new vscode.ThemeColor('ralsp.inlayHint'),
    },
});

class HintsUpdater {
    private pending: Map<string, vscode.CancellationTokenSource> = new Map();
    private ctx: Ctx;
    private enabled = true;

    constructor(ctx: Ctx) {
        this.ctx = ctx;
    }

    async setEnabled(enabled: boolean) {
        if (this.enabled == enabled) return;
        this.enabled = enabled;

        if (this.enabled) {
            await this.refresh();
        } else {
            this.allEditors.forEach(it => this.setDecorations(it, []));
        }
    }

    async refresh() {
        if (!this.enabled) return;
        const promises = this.allEditors.map(it => this.refreshEditor(it));
        await Promise.all(promises);
    }

    private async refreshEditor(editor: vscode.TextEditor): Promise<void> {
        const newHints = await this.queryHints(editor.document.uri.toString());
        if (newHints == null) return;
        const newDecorations = newHints.map(hint => ({
            range: hint.range,
            renderOptions: {
                after: {
                    contentText: `: ${hint.label}`,
                },
            },
        }));
        this.setDecorations(editor, newDecorations);
    }

    private get allEditors(): vscode.TextEditor[] {
        return vscode.window.visibleTextEditors.filter(
            editor => editor.document.languageId === 'rust',
        );
    }

    private setDecorations(
        editor: vscode.TextEditor,
        decorations: vscode.DecorationOptions[],
    ) {
        editor.setDecorations(
            typeHintDecorationType,
            this.enabled ? decorations : [],
        );
    }

    private async queryHints(documentUri: string): Promise<InlayHint[] | null> {
        const request: InlayHintsParams = {
            textDocument: { uri: documentUri },
        };
        let tokenSource = new vscode.CancellationTokenSource();
        let prev = this.pending.get(documentUri);
        if (prev) prev.cancel();
        this.pending.set(documentUri, tokenSource);
        try {
            return await this.ctx.sendRequestWithRetry<InlayHint[] | null>(
                'rust-analyzer/inlayHints',
                request,
                tokenSource.token,
            );
        } finally {
            if (!tokenSource.token.isCancellationRequested) {
                this.pending.delete(documentUri);
            }
        }
    }
}