2694: Refactor inlay hints r=matklad a=matklad



Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
bors[bot] 2019-12-30 21:57:08 +00:00 committed by GitHub
commit c3d74744cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 134 additions and 158 deletions

View file

@ -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())
}

View file

@ -13,7 +13,7 @@ export default {
commonjs({
namedExports: {
// squelch missing import warnings
'vscode-languageclient': ['CreateFile', 'RenameFile']
'vscode-languageclient': ['CreateFile', 'RenameFile', 'ErrorCodes']
}
})
],

View file

@ -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))

View file

@ -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);
};
}

View file

@ -1,3 +0,0 @@
import * as changeActiveTextEditor from './change_active_text_editor';
export { changeActiveTextEditor };

View file

@ -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;

View file

@ -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)
}
}
}
}

View file

@ -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);
}
}