mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-11-17 02:08:30 +00:00
Merge #2694
2694: Refactor inlay hints r=matklad a=matklad Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
commit
c3d74744cd
8 changed files with 134 additions and 158 deletions
|
@ -709,16 +709,11 @@ where
|
|||
Ok(lsp_error) => Response::new_err(id, lsp_error.code, lsp_error.message),
|
||||
Err(e) => {
|
||||
if is_canceled(&e) {
|
||||
// FIXME: When https://github.com/Microsoft/vscode-languageserver-node/issues/457
|
||||
// gets fixed, we can return the proper response.
|
||||
// This works around the issue where "content modified" error would continuously
|
||||
// show an message pop-up in VsCode
|
||||
// Response::err(
|
||||
// id,
|
||||
// ErrorCode::ContentModified as i32,
|
||||
// "content modified".to_string(),
|
||||
// )
|
||||
Response::new_ok(id, ())
|
||||
Response::new_err(
|
||||
id,
|
||||
ErrorCode::ContentModified as i32,
|
||||
"content modified".to_string(),
|
||||
)
|
||||
} else {
|
||||
Response::new_err(id, ErrorCode::InternalError as i32, e.to_string())
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ export default {
|
|||
commonjs({
|
||||
namedExports: {
|
||||
// squelch missing import warnings
|
||||
'vscode-languageclient': ['CreateFile', 'RenameFile']
|
||||
'vscode-languageclient': ['CreateFile', 'RenameFile', 'ErrorCodes']
|
||||
}
|
||||
})
|
||||
],
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import * as vscode from 'vscode';
|
||||
import * as lc from 'vscode-languageclient';
|
||||
import { Server } from './server';
|
||||
import { Config } from './config';
|
||||
|
||||
export class Ctx {
|
||||
private extCtx: vscode.ExtensionContext;
|
||||
|
@ -13,6 +14,10 @@ export class Ctx {
|
|||
return Server.client;
|
||||
}
|
||||
|
||||
get config(): Config {
|
||||
return Server.config;
|
||||
}
|
||||
|
||||
get activeRustEditor(): vscode.TextEditor | undefined {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
return editor && editor.document.languageId === 'rust'
|
||||
|
@ -56,6 +61,24 @@ export class Ctx {
|
|||
pushCleanup(d: { dispose(): any }) {
|
||||
this.extCtx.subscriptions.push(d);
|
||||
}
|
||||
|
||||
async sendRequestWithRetry<R>(method: string, param: any, token: vscode.CancellationToken): Promise<R> {
|
||||
await this.client.onReady();
|
||||
for (const delay of [2, 4, 6, 8, 10, null]) {
|
||||
try {
|
||||
return await this.client.sendRequest(method, param, token);
|
||||
} catch (e) {
|
||||
if (e.code === lc.ErrorCodes.ContentModified && delay !== null) {
|
||||
await sleep(10 * (1 << delay))
|
||||
continue;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
throw 'unreachable'
|
||||
}
|
||||
}
|
||||
|
||||
export type Cmd = (...args: any[]) => any;
|
||||
|
||||
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
import { TextEditor } from 'vscode';
|
||||
import { TextDocumentIdentifier } from 'vscode-languageclient';
|
||||
import { Decoration } from '../highlighting';
|
||||
import { Server } from '../server';
|
||||
|
||||
export function makeHandler() {
|
||||
return async function handle(editor: TextEditor | undefined) {
|
||||
if (!editor || editor.document.languageId !== 'rust') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Server.config.highlightingOn) {
|
||||
return;
|
||||
}
|
||||
|
||||
const params: TextDocumentIdentifier = {
|
||||
uri: editor.document.uri.toString(),
|
||||
};
|
||||
const decorations = await Server.client.sendRequest<Decoration[]>(
|
||||
'rust-analyzer/decorationsRequest',
|
||||
params,
|
||||
);
|
||||
Server.highlighter.setHighlights(editor, decorations);
|
||||
};
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
import * as changeActiveTextEditor from './change_active_text_editor';
|
||||
|
||||
export { changeActiveTextEditor };
|
|
@ -1,10 +1,31 @@
|
|||
import seedrandom = require('seedrandom');
|
||||
import * as vscode from 'vscode';
|
||||
import * as lc from 'vscode-languageclient';
|
||||
import * as seedrandom_ from 'seedrandom';
|
||||
const seedrandom = seedrandom_; // https://github.com/jvandemo/generator-angular2-library/issues/221#issuecomment-355945207
|
||||
|
||||
import * as scopes from './scopes';
|
||||
import * as scopesMapper from './scopes_mapper';
|
||||
|
||||
import { Server } from './server';
|
||||
import { Ctx } from './ctx';
|
||||
|
||||
export function activateHighlighting(ctx: Ctx) {
|
||||
vscode.window.onDidChangeActiveTextEditor(
|
||||
async (editor: vscode.TextEditor | undefined) => {
|
||||
if (!editor || editor.document.languageId !== 'rust') return;
|
||||
if (!ctx.config.highlightingOn) return;
|
||||
|
||||
const params: lc.TextDocumentIdentifier = {
|
||||
uri: editor.document.uri.toString(),
|
||||
};
|
||||
const decorations = await ctx.client.sendRequest<Decoration[]>(
|
||||
'rust-analyzer/decorationsRequest',
|
||||
params,
|
||||
);
|
||||
Server.highlighter.setHighlights(editor, decorations);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export interface Decoration {
|
||||
range: lc.Range;
|
||||
|
|
|
@ -1,41 +1,27 @@
|
|||
import * as vscode from 'vscode';
|
||||
import * as lc from 'vscode-languageclient';
|
||||
import { Server } from './server';
|
||||
|
||||
import { Ctx } from './ctx';
|
||||
|
||||
export function activateInlayHints(ctx: Ctx) {
|
||||
const hintsUpdater = new HintsUpdater();
|
||||
hintsUpdater.refreshHintsForVisibleEditors().then(() => {
|
||||
// vscode may ignore top level hintsUpdater.refreshHintsForVisibleEditors()
|
||||
// so update the hints once when the focus changes to guarantee their presence
|
||||
let editorChangeDisposable: vscode.Disposable | null = null;
|
||||
editorChangeDisposable = vscode.window.onDidChangeActiveTextEditor(
|
||||
_ => {
|
||||
if (editorChangeDisposable !== null) {
|
||||
editorChangeDisposable.dispose();
|
||||
}
|
||||
return hintsUpdater.refreshHintsForVisibleEditors();
|
||||
},
|
||||
);
|
||||
const hintsUpdater = new HintsUpdater(ctx);
|
||||
vscode.window.onDidChangeVisibleTextEditors(async _ => {
|
||||
await hintsUpdater.refresh();
|
||||
}, ctx.subscriptions);
|
||||
|
||||
ctx.pushCleanup(
|
||||
vscode.window.onDidChangeVisibleTextEditors(_ =>
|
||||
hintsUpdater.refreshHintsForVisibleEditors(),
|
||||
),
|
||||
);
|
||||
ctx.pushCleanup(
|
||||
vscode.workspace.onDidChangeTextDocument(e =>
|
||||
hintsUpdater.refreshHintsForVisibleEditors(e),
|
||||
),
|
||||
);
|
||||
ctx.pushCleanup(
|
||||
vscode.workspace.onDidChangeConfiguration(_ =>
|
||||
hintsUpdater.toggleHintsDisplay(
|
||||
Server.config.displayInlayHints,
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
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 {
|
||||
|
@ -55,95 +41,79 @@ const typeHintDecorationType = vscode.window.createTextEditorDecorationType({
|
|||
});
|
||||
|
||||
class HintsUpdater {
|
||||
private displayHints = true;
|
||||
private pending: Map<string, vscode.CancellationTokenSource> = new Map();
|
||||
private ctx: Ctx;
|
||||
private enabled = true;
|
||||
|
||||
public async toggleHintsDisplay(displayHints: boolean): Promise<void> {
|
||||
if (this.displayHints !== displayHints) {
|
||||
this.displayHints = displayHints;
|
||||
return this.refreshVisibleEditorsHints(
|
||||
displayHints ? undefined : [],
|
||||
);
|
||||
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, []));
|
||||
}
|
||||
}
|
||||
|
||||
public async refreshHintsForVisibleEditors(
|
||||
cause?: vscode.TextDocumentChangeEvent,
|
||||
): Promise<void> {
|
||||
if (!this.displayHints) return;
|
||||
|
||||
if (
|
||||
cause !== undefined &&
|
||||
(cause.contentChanges.length === 0 ||
|
||||
!this.isRustDocument(cause.document))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
return this.refreshVisibleEditorsHints();
|
||||
async refresh() {
|
||||
if (!this.enabled) return;
|
||||
const promises = this.allEditors.map(it => this.refreshEditor(it));
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
private async refreshVisibleEditorsHints(
|
||||
newDecorations?: vscode.DecorationOptions[],
|
||||
) {
|
||||
const promises: Array<Promise<void>> = [];
|
||||
|
||||
for (const rustEditor of vscode.window.visibleTextEditors.filter(
|
||||
editor => this.isRustDocument(editor.document),
|
||||
)) {
|
||||
if (newDecorations !== undefined) {
|
||||
promises.push(
|
||||
Promise.resolve(
|
||||
rustEditor.setDecorations(
|
||||
typeHintDecorationType,
|
||||
newDecorations,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
promises.push(this.updateDecorationsFromServer(rustEditor));
|
||||
}
|
||||
}
|
||||
|
||||
for (const promise of promises) {
|
||||
await promise;
|
||||
}
|
||||
}
|
||||
|
||||
private isRustDocument(document: vscode.TextDocument): boolean {
|
||||
return document && document.languageId === 'rust';
|
||||
}
|
||||
|
||||
private async updateDecorationsFromServer(
|
||||
editor: vscode.TextEditor,
|
||||
): Promise<void> {
|
||||
private async refreshEditor(editor: vscode.TextEditor): Promise<void> {
|
||||
const newHints = await this.queryHints(editor.document.uri.toString());
|
||||
if (newHints !== null) {
|
||||
const newDecorations = newHints.map(hint => ({
|
||||
range: hint.range,
|
||||
renderOptions: {
|
||||
after: {
|
||||
contentText: `: ${hint.label}`,
|
||||
},
|
||||
if (newHints == null) return;
|
||||
const newDecorations = newHints.map(hint => ({
|
||||
range: hint.range,
|
||||
renderOptions: {
|
||||
after: {
|
||||
contentText: `: ${hint.label}`,
|
||||
},
|
||||
}));
|
||||
return editor.setDecorations(
|
||||
typeHintDecorationType,
|
||||
newDecorations,
|
||||
);
|
||||
}
|
||||
},
|
||||
}));
|
||||
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 },
|
||||
};
|
||||
const client = Server.client;
|
||||
return client
|
||||
.onReady()
|
||||
.then(() =>
|
||||
client.sendRequest<InlayHint[] | null>(
|
||||
'rust-analyzer/inlayHints',
|
||||
request,
|
||||
),
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,10 @@ import * as lc from 'vscode-languageclient';
|
|||
import * as commands from './commands';
|
||||
import { activateInlayHints } from './inlay_hints';
|
||||
import { StatusDisplay } from './status_display';
|
||||
import * as events from './events';
|
||||
import * as notifications from './notifications';
|
||||
import { Server } from './server';
|
||||
import { Ctx } from './ctx';
|
||||
import { activateHighlighting } from './highlighting';
|
||||
|
||||
let ctx!: Ctx;
|
||||
|
||||
|
@ -28,15 +28,15 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||
ctx.registerCommand('runSingle', commands.runSingle);
|
||||
ctx.registerCommand('showReferences', commands.showReferences);
|
||||
|
||||
if (Server.config.enableEnhancedTyping) {
|
||||
if (ctx.config.enableEnhancedTyping) {
|
||||
ctx.overrideCommand('type', commands.onEnter);
|
||||
}
|
||||
|
||||
const watchStatus = new StatusDisplay(
|
||||
Server.config.cargoWatchOptions.command,
|
||||
);
|
||||
const watchStatus = new StatusDisplay(ctx.config.cargoWatchOptions.command);
|
||||
ctx.pushCleanup(watchStatus);
|
||||
|
||||
activateHighlighting(ctx);
|
||||
|
||||
// Notifications are events triggered by the language server
|
||||
const allNotifications: [string, lc.GenericNotificationHandler][] = [
|
||||
[
|
||||
|
@ -49,11 +49,6 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||
],
|
||||
];
|
||||
|
||||
// The events below are plain old javascript events, triggered and handled by vscode
|
||||
vscode.window.onDidChangeActiveTextEditor(
|
||||
events.changeActiveTextEditor.makeHandler(),
|
||||
);
|
||||
|
||||
const startServer = () => Server.start(allNotifications);
|
||||
const reloadCommand = () => reloadServer(startServer);
|
||||
|
||||
|
@ -66,7 +61,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||
vscode.window.showErrorMessage(e.message);
|
||||
}
|
||||
|
||||
if (Server.config.displayInlayHints) {
|
||||
if (ctx.config.displayInlayHints) {
|
||||
activateInlayHints(ctx);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue