2020-02-29 17:28:26 +00:00
|
|
|
import * as lc from "vscode-languageclient";
|
2019-07-23 13:38:21 +00:00
|
|
|
import * as vscode from 'vscode';
|
2020-02-24 22:57:49 +00:00
|
|
|
import * as ra from './rust-analyzer-api';
|
2019-12-30 20:28:38 +00:00
|
|
|
|
2020-03-07 12:08:08 +00:00
|
|
|
import { Ctx, Disposable } from './ctx';
|
2020-03-07 12:34:09 +00:00
|
|
|
import { sendRequestWithRetry, isRustDocument, RustDocument, RustEditor } from './util';
|
2020-03-01 15:46:32 +00:00
|
|
|
|
2020-02-29 17:28:26 +00:00
|
|
|
|
2020-03-07 12:08:08 +00:00
|
|
|
export function activateInlayHints(ctx: Ctx) {
|
|
|
|
const maybeUpdater = {
|
|
|
|
updater: null as null | HintsUpdater,
|
|
|
|
onConfigChange() {
|
2020-03-23 22:32:50 +00:00
|
|
|
if (
|
|
|
|
!ctx.config.inlayHints.typeHints &&
|
|
|
|
!ctx.config.inlayHints.parameterHints &&
|
|
|
|
!ctx.config.inlayHints.chainingHints
|
|
|
|
) {
|
2020-03-07 12:08:08 +00:00
|
|
|
return this.dispose();
|
|
|
|
}
|
2020-03-07 12:39:42 +00:00
|
|
|
if (!this.updater) this.updater = new HintsUpdater(ctx);
|
2020-03-20 22:01:47 +00:00
|
|
|
|
|
|
|
this.updater.syncCacheAndRenderHints();
|
2020-02-05 20:39:47 +00:00
|
|
|
},
|
2020-03-07 12:08:08 +00:00
|
|
|
dispose() {
|
|
|
|
this.updater?.dispose();
|
|
|
|
this.updater = null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
ctx.pushCleanup(maybeUpdater);
|
2020-02-05 20:39:47 +00:00
|
|
|
|
|
|
|
vscode.workspace.onDidChangeConfiguration(
|
2020-03-07 12:08:08 +00:00
|
|
|
maybeUpdater.onConfigChange, maybeUpdater, ctx.subscriptions
|
2020-02-05 20:39:47 +00:00
|
|
|
);
|
2019-12-30 20:28:38 +00:00
|
|
|
|
2020-03-07 12:08:08 +00:00
|
|
|
maybeUpdater.onConfigChange();
|
2019-12-30 19:21:25 +00:00
|
|
|
}
|
2019-07-23 13:38:21 +00:00
|
|
|
|
|
|
|
|
2020-02-29 17:28:26 +00:00
|
|
|
const typeHints = {
|
|
|
|
decorationType: vscode.window.createTextEditorDecorationType({
|
|
|
|
after: {
|
|
|
|
color: new vscode.ThemeColor('rust_analyzer.inlayHint'),
|
|
|
|
fontStyle: "normal",
|
|
|
|
}
|
|
|
|
}),
|
2020-01-14 17:02:01 +00:00
|
|
|
|
2020-02-29 17:28:26 +00:00
|
|
|
toDecoration(hint: ra.InlayHint.TypeHint, conv: lc.Protocol2CodeConverter): vscode.DecorationOptions {
|
|
|
|
return {
|
|
|
|
range: conv.asRange(hint.range),
|
|
|
|
renderOptions: { after: { contentText: `: ${hint.label}` } }
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const paramHints = {
|
|
|
|
decorationType: vscode.window.createTextEditorDecorationType({
|
|
|
|
before: {
|
|
|
|
color: new vscode.ThemeColor('rust_analyzer.inlayHint'),
|
|
|
|
fontStyle: "normal",
|
|
|
|
}
|
|
|
|
}),
|
2019-07-23 13:38:21 +00:00
|
|
|
|
2020-02-29 17:28:26 +00:00
|
|
|
toDecoration(hint: ra.InlayHint.ParamHint, conv: lc.Protocol2CodeConverter): vscode.DecorationOptions {
|
|
|
|
return {
|
|
|
|
range: conv.asRange(hint.range),
|
|
|
|
renderOptions: { before: { contentText: `${hint.label}: ` } }
|
|
|
|
};
|
2019-08-05 19:31:12 +00:00
|
|
|
}
|
2020-02-29 17:28:26 +00:00
|
|
|
};
|
|
|
|
|
2020-03-23 22:32:50 +00:00
|
|
|
const chainingHints = {
|
|
|
|
decorationType: vscode.window.createTextEditorDecorationType({
|
|
|
|
after: {
|
|
|
|
color: new vscode.ThemeColor('rust_analyzer.inlayHint'),
|
|
|
|
fontStyle: "normal",
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
|
|
|
|
toDecoration(hint: ra.InlayHint.ChainingHint, conv: lc.Protocol2CodeConverter): vscode.DecorationOptions {
|
|
|
|
return {
|
|
|
|
range: conv.asRange(hint.range),
|
|
|
|
renderOptions: { after: { contentText: ` ${hint.label}` } }
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-03-07 12:08:08 +00:00
|
|
|
class HintsUpdater implements Disposable {
|
|
|
|
private sourceFiles = new Map<string, RustSourceFile>(); // map Uri -> RustSourceFile
|
|
|
|
private readonly disposables: Disposable[] = [];
|
2020-02-17 11:17:01 +00:00
|
|
|
|
2020-03-07 12:39:42 +00:00
|
|
|
constructor(private readonly ctx: Ctx) {
|
2020-03-07 12:08:08 +00:00
|
|
|
vscode.window.onDidChangeVisibleTextEditors(
|
2020-03-07 12:39:42 +00:00
|
|
|
this.onDidChangeVisibleTextEditors,
|
|
|
|
this,
|
|
|
|
this.disposables
|
2020-03-07 12:08:08 +00:00
|
|
|
);
|
2020-02-29 17:28:26 +00:00
|
|
|
|
2020-03-07 12:08:08 +00:00
|
|
|
vscode.workspace.onDidChangeTextDocument(
|
2020-03-07 12:39:42 +00:00
|
|
|
this.onDidChangeTextDocument,
|
|
|
|
this,
|
|
|
|
this.disposables
|
2020-02-29 21:19:58 +00:00
|
|
|
);
|
2020-02-22 18:58:00 +00:00
|
|
|
|
2020-03-07 12:08:08 +00:00
|
|
|
// Set up initial cache shape
|
2020-03-07 12:39:42 +00:00
|
|
|
ctx.visibleRustEditors.forEach(editor => this.sourceFiles.set(
|
2020-03-07 12:34:09 +00:00
|
|
|
editor.document.uri.toString(),
|
|
|
|
{
|
2020-03-07 12:08:08 +00:00
|
|
|
document: editor.document,
|
|
|
|
inlaysRequest: null,
|
|
|
|
cachedDecorations: null
|
|
|
|
}
|
|
|
|
));
|
2020-02-22 18:58:00 +00:00
|
|
|
|
2020-03-07 12:39:42 +00:00
|
|
|
this.syncCacheAndRenderHints();
|
2020-02-29 17:28:26 +00:00
|
|
|
}
|
|
|
|
|
2020-03-07 12:08:08 +00:00
|
|
|
dispose() {
|
|
|
|
this.sourceFiles.forEach(file => file.inlaysRequest?.cancel());
|
2020-03-23 22:32:50 +00:00
|
|
|
this.ctx.visibleRustEditors.forEach(editor => this.renderDecorations(editor, { param: [], type: [], chaining: [] }));
|
2020-03-07 12:08:08 +00:00
|
|
|
this.disposables.forEach(d => d.dispose());
|
|
|
|
}
|
2020-02-29 17:28:26 +00:00
|
|
|
|
2020-03-07 12:34:09 +00:00
|
|
|
onDidChangeTextDocument({ contentChanges, document }: vscode.TextDocumentChangeEvent) {
|
2020-03-07 12:08:08 +00:00
|
|
|
if (contentChanges.length === 0 || !isRustDocument(document)) return;
|
|
|
|
this.syncCacheAndRenderHints();
|
|
|
|
}
|
2020-02-29 17:28:26 +00:00
|
|
|
|
2020-03-20 22:01:47 +00:00
|
|
|
public syncCacheAndRenderHints() {
|
2020-03-07 12:08:08 +00:00
|
|
|
// FIXME: make inlayHints request pass an array of files?
|
|
|
|
this.sourceFiles.forEach((file, uri) => this.fetchHints(file).then(hints => {
|
|
|
|
if (!hints) return;
|
2020-02-29 17:28:26 +00:00
|
|
|
|
2020-03-07 12:08:08 +00:00
|
|
|
file.cachedDecorations = this.hintsToDecorations(hints);
|
2020-02-29 17:28:26 +00:00
|
|
|
|
2020-03-07 12:08:08 +00:00
|
|
|
for (const editor of this.ctx.visibleRustEditors) {
|
|
|
|
if (editor.document.uri.toString() === uri) {
|
|
|
|
this.renderDecorations(editor, file.cachedDecorations);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}));
|
2020-02-29 17:28:26 +00:00
|
|
|
}
|
|
|
|
|
2020-03-07 12:08:08 +00:00
|
|
|
onDidChangeVisibleTextEditors() {
|
|
|
|
const newSourceFiles = new Map<string, RustSourceFile>();
|
2020-02-29 17:28:26 +00:00
|
|
|
|
2020-03-07 12:08:08 +00:00
|
|
|
// 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);
|
2020-02-29 17:28:26 +00:00
|
|
|
|
2020-03-07 12:08:08 +00:00
|
|
|
// No text documents changed, so we may try to use the cache
|
|
|
|
if (!file.cachedDecorations) {
|
|
|
|
const hints = await this.fetchHints(file);
|
|
|
|
if (!hints) return;
|
2020-02-29 17:28:26 +00:00
|
|
|
|
2020-03-07 12:08:08 +00:00
|
|
|
file.cachedDecorations = this.hintsToDecorations(hints);
|
|
|
|
}
|
2020-02-29 17:28:26 +00:00
|
|
|
|
2020-03-07 12:08:08 +00:00
|
|
|
this.renderDecorations(editor, file.cachedDecorations);
|
|
|
|
});
|
2020-02-29 17:28:26 +00:00
|
|
|
|
2020-03-07 12:08:08 +00:00
|
|
|
// Cancel requests for no longer visible (disposed) source files
|
|
|
|
this.sourceFiles.forEach((file, uri) => {
|
|
|
|
if (!newSourceFiles.has(uri)) file.inlaysRequest?.cancel();
|
|
|
|
});
|
2020-02-29 17:28:26 +00:00
|
|
|
|
2020-03-07 12:08:08 +00:00
|
|
|
this.sourceFiles = newSourceFiles;
|
2020-02-29 17:28:26 +00:00
|
|
|
}
|
|
|
|
|
2020-03-07 12:08:08 +00:00
|
|
|
private renderDecorations(editor: RustEditor, decorations: InlaysDecorations) {
|
|
|
|
editor.setDecorations(typeHints.decorationType, decorations.type);
|
|
|
|
editor.setDecorations(paramHints.decorationType, decorations.param);
|
2020-03-23 22:32:50 +00:00
|
|
|
editor.setDecorations(chainingHints.decorationType, decorations.chaining);
|
2020-02-29 17:28:26 +00:00
|
|
|
}
|
|
|
|
|
2020-03-07 12:08:08 +00:00
|
|
|
private hintsToDecorations(hints: ra.InlayHint[]): InlaysDecorations {
|
2020-03-23 22:32:50 +00:00
|
|
|
const decorations: InlaysDecorations = { type: [], param: [], chaining: [] };
|
2020-03-07 12:08:08 +00:00
|
|
|
const conv = this.ctx.client.protocol2CodeConverter;
|
2020-02-29 17:28:26 +00:00
|
|
|
|
|
|
|
for (const hint of hints) {
|
|
|
|
switch (hint.kind) {
|
|
|
|
case ra.InlayHint.Kind.TypeHint: {
|
2020-03-07 12:08:08 +00:00
|
|
|
decorations.type.push(typeHints.toDecoration(hint, conv));
|
2020-02-29 17:28:26 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
case ra.InlayHint.Kind.ParamHint: {
|
2020-03-07 12:08:08 +00:00
|
|
|
decorations.param.push(paramHints.toDecoration(hint, conv));
|
2020-02-29 17:28:26 +00:00
|
|
|
continue;
|
2020-02-22 18:58:00 +00:00
|
|
|
}
|
2020-03-23 22:32:50 +00:00
|
|
|
case ra.InlayHint.Kind.ChainingHint: {
|
|
|
|
decorations.chaining.push(chainingHints.toDecoration(hint, conv));
|
|
|
|
continue;
|
|
|
|
}
|
2020-02-29 17:28:26 +00:00
|
|
|
}
|
|
|
|
}
|
2020-03-07 12:08:08 +00:00
|
|
|
return decorations;
|
2020-02-29 17:28:26 +00:00
|
|
|
}
|
|
|
|
|
2020-03-07 12:08:08 +00:00
|
|
|
private async fetchHints(file: RustSourceFile): Promise<null | ra.InlayHint[]> {
|
|
|
|
file.inlaysRequest?.cancel();
|
2020-02-29 17:28:26 +00:00
|
|
|
|
|
|
|
const tokenSource = new vscode.CancellationTokenSource();
|
2020-03-07 12:08:08 +00:00
|
|
|
file.inlaysRequest = tokenSource;
|
|
|
|
|
|
|
|
const request = { textDocument: { uri: file.document.uri.toString() } };
|
|
|
|
|
|
|
|
return sendRequestWithRetry(this.ctx.client, ra.inlayHints, request, tokenSource.token)
|
2020-03-07 12:34:09 +00:00
|
|
|
.catch(_ => null)
|
2020-03-07 12:08:08 +00:00
|
|
|
.finally(() => {
|
|
|
|
if (file.inlaysRequest === tokenSource) {
|
|
|
|
file.inlaysRequest = null;
|
|
|
|
}
|
2020-03-07 12:34:09 +00:00
|
|
|
});
|
2019-07-23 13:38:21 +00:00
|
|
|
}
|
|
|
|
}
|
2020-02-29 17:28:26 +00:00
|
|
|
|
2020-03-07 12:08:08 +00:00
|
|
|
interface InlaysDecorations {
|
|
|
|
type: vscode.DecorationOptions[];
|
|
|
|
param: vscode.DecorationOptions[];
|
2020-03-23 22:32:50 +00:00
|
|
|
chaining: vscode.DecorationOptions[];
|
2020-02-29 17:28:26 +00:00
|
|
|
}
|
|
|
|
|
2020-03-07 12:08:08 +00:00
|
|
|
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;
|
2020-02-29 17:28:26 +00:00
|
|
|
|
2020-03-07 12:34:09 +00:00
|
|
|
document: RustDocument;
|
2020-02-29 17:28:26 +00:00
|
|
|
}
|