3299: vscode: migrate to request type api r=matklad a=Veetaha

More type-safety to the god of type-safety.

Co-authored-by: Veetaha <gerzoh1@gmail.com>
This commit is contained in:
bors[bot] 2020-02-25 09:55:56 +00:00 committed by GitHub
commit 5e6f4ca690
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 202 additions and 197 deletions

View File

@ -1,5 +1,6 @@
import * as vscode from 'vscode';
import * as ra from '../rust-analyzer-api';
import { Ctx, Cmd } from '../ctx';
// Shows status of rust-analyzer (for debugging)
@ -50,10 +51,7 @@ class TextDocumentContentProvider
const client = this.ctx.client;
if (!editor || !client) return '';
return client.sendRequest<string>(
'rust-analyzer/analyzerStatus',
null,
);
return client.sendRequest(ra.analyzerStatus, null);
}
get onDidChange(): vscode.Event<vscode.Uri> {

View File

@ -1,5 +1,5 @@
import * as vscode from 'vscode';
import * as lc from 'vscode-languageclient';
import * as ra from '../rust-analyzer-api';
import { Ctx, Cmd } from '../ctx';
@ -26,12 +26,7 @@ export function expandMacro(ctx: Ctx): Cmd {
};
}
interface ExpandedMacro {
name: string;
expansion: string;
}
function codeFormat(expanded: ExpandedMacro): string {
function codeFormat(expanded: ra.ExpandedMacro): string {
let result = `// Recursive expansion of ${expanded.name}! macro\n`;
result += '// ' + '='.repeat(result.length - 3);
result += '\n\n';
@ -54,14 +49,11 @@ class TextDocumentContentProvider
if (!editor || !client) return '';
const position = editor.selection.active;
const request: lc.TextDocumentPositionParams = {
const expanded = await client.sendRequest(ra.expandMacro, {
textDocument: { uri: editor.document.uri.toString() },
position,
};
const expanded = await client.sendRequest<ExpandedMacro>(
'rust-analyzer/expandMacro',
request,
);
});
if (expanded == null) return 'Not available';

View File

@ -1,5 +1,6 @@
import * as vscode from 'vscode';
import * as lc from 'vscode-languageclient';
import * as ra from '../rust-analyzer-api';
import { Ctx, Cmd } from '../ctx';
import * as sourceChange from '../source_change';
@ -16,9 +17,7 @@ export * from './ssr';
export * from './server_version';
export function collectGarbage(ctx: Ctx): Cmd {
return async () => {
await ctx.client?.sendRequest<null>('rust-analyzer/collectGarbage', null);
};
return async () => ctx.client.sendRequest(ra.collectGarbage, null);
}
export function showReferences(ctx: Ctx): Cmd {
@ -36,13 +35,13 @@ export function showReferences(ctx: Ctx): Cmd {
}
export function applySourceChange(ctx: Ctx): Cmd {
return async (change: sourceChange.SourceChange) => {
return async (change: ra.SourceChange) => {
await sourceChange.applySourceChange(ctx, change);
};
}
export function selectAndApplySourceChange(ctx: Ctx): Cmd {
return async (changes: sourceChange.SourceChange[]) => {
return async (changes: ra.SourceChange[]) => {
if (changes.length === 1) {
await sourceChange.applySourceChange(ctx, changes[0]);
} else if (changes.length > 0) {

View File

@ -1,7 +1,7 @@
import * as lc from 'vscode-languageclient';
import * as ra from '../rust-analyzer-api';
import { Ctx, Cmd } from '../ctx';
import { applySourceChange, SourceChange } from '../source_change';
import { applySourceChange } from '../source_change';
export function joinLines(ctx: Ctx): Cmd {
return async () => {
@ -9,19 +9,10 @@ export function joinLines(ctx: Ctx): Cmd {
const client = ctx.client;
if (!editor || !client) return;
const request: JoinLinesParams = {
const change = await client.sendRequest(ra.joinLines, {
range: client.code2ProtocolConverter.asRange(editor.selection),
textDocument: { uri: editor.document.uri.toString() },
};
const change = await client.sendRequest<SourceChange>(
'rust-analyzer/joinLines',
request,
);
});
await applySourceChange(ctx, change);
};
}
interface JoinLinesParams {
textDocument: lc.TextDocumentIdentifier;
range: lc.Range;
}

View File

@ -1,5 +1,5 @@
import * as vscode from 'vscode';
import * as lc from 'vscode-languageclient';
import * as ra from '../rust-analyzer-api';
import { Ctx, Cmd } from '../ctx';
@ -9,16 +9,12 @@ export function matchingBrace(ctx: Ctx): Cmd {
const client = ctx.client;
if (!editor || !client) return;
const request: FindMatchingBraceParams = {
const response = await client.sendRequest(ra.findMatchingBrace, {
textDocument: { uri: editor.document.uri.toString() },
offsets: editor.selections.map(s =>
client.code2ProtocolConverter.asPosition(s.active),
),
};
const response = await client.sendRequest<lc.Position[]>(
'rust-analyzer/findMatchingBrace',
request,
);
});
editor.selections = editor.selections.map((sel, idx) => {
const active = client.protocol2CodeConverter.asPosition(
response[idx],
@ -29,8 +25,3 @@ export function matchingBrace(ctx: Ctx): Cmd {
editor.revealRange(editor.selection);
};
}
interface FindMatchingBraceParams {
textDocument: lc.TextDocumentIdentifier;
offsets: lc.Position[];
}

View File

@ -1,7 +1,7 @@
import * as vscode from 'vscode';
import * as lc from 'vscode-languageclient';
import * as ra from '../rust-analyzer-api';
import { applySourceChange, SourceChange } from '../source_change';
import { applySourceChange } from '../source_change';
import { Cmd, Ctx } from '../ctx';
async function handleKeypress(ctx: Ctx) {
@ -10,22 +10,15 @@ async function handleKeypress(ctx: Ctx) {
if (!editor || !client) return false;
const request: lc.TextDocumentPositionParams = {
const change = await client.sendRequest(ra.onEnter, {
textDocument: { uri: editor.document.uri.toString() },
position: client.code2ProtocolConverter.asPosition(
editor.selection.active,
),
};
const change = await client.sendRequest<undefined | SourceChange>(
'rust-analyzer/onEnter',
request,
).catch(
(_error: any) => {
// FIXME: switch to the more modern (?) typed request infrastructure
// client.logFailedRequest(OnEnterRequest.type, error);
return Promise.resolve(null);
}
);
}).catch(_error => {
// client.logFailedRequest(OnEnterRequest.type, error);
return null;
});
if (!change) return false;
await applySourceChange(ctx, change);

View File

@ -1,5 +1,5 @@
import * as vscode from 'vscode';
import * as lc from 'vscode-languageclient';
import * as ra from '../rust-analyzer-api';
import { Ctx, Cmd } from '../ctx';
@ -9,16 +9,12 @@ export function parentModule(ctx: Ctx): Cmd {
const client = ctx.client;
if (!editor || !client) return;
const request: lc.TextDocumentPositionParams = {
const response = await client.sendRequest(ra.parentModule, {
textDocument: { uri: editor.document.uri.toString() },
position: client.code2ProtocolConverter.asPosition(
editor.selection.active,
),
};
const response = await client.sendRequest<lc.Location[]>(
'rust-analyzer/parentModule',
request,
);
});
const loc = response[0];
if (loc == null) return;

View File

@ -1,5 +1,6 @@
import * as vscode from 'vscode';
import * as lc from 'vscode-languageclient';
import * as ra from '../rust-analyzer-api';
import { Ctx, Cmd } from '../ctx';
@ -14,16 +15,13 @@ export function run(ctx: Ctx): Cmd {
const textDocument: lc.TextDocumentIdentifier = {
uri: editor.document.uri.toString(),
};
const params: RunnablesParams = {
const runnables = await client.sendRequest(ra.runnables, {
textDocument,
position: client.code2ProtocolConverter.asPosition(
editor.selection.active,
),
};
const runnables = await client.sendRequest<Runnable[]>(
'rust-analyzer/runnables',
params,
);
});
const items: RunnableQuickPick[] = [];
if (prevRunnable) {
items.push(prevRunnable);
@ -48,7 +46,7 @@ export function run(ctx: Ctx): Cmd {
}
export function runSingle(ctx: Ctx): Cmd {
return async (runnable: Runnable) => {
return async (runnable: ra.Runnable) => {
const editor = ctx.activeRustEditor;
if (!editor) return;
@ -64,26 +62,13 @@ export function runSingle(ctx: Ctx): Cmd {
};
}
interface RunnablesParams {
textDocument: lc.TextDocumentIdentifier;
position?: lc.Position;
}
interface Runnable {
label: string;
bin: string;
args: string[];
env: { [index: string]: string };
cwd?: string;
}
class RunnableQuickPick implements vscode.QuickPickItem {
public label: string;
public description?: string | undefined;
public detail?: string | undefined;
public picked?: boolean | undefined;
constructor(public runnable: Runnable) {
constructor(public runnable: ra.Runnable) {
this.label = runnable.label;
}
}
@ -96,7 +81,7 @@ interface CargoTaskDefinition extends vscode.TaskDefinition {
env?: { [key: string]: string };
}
function createTask(spec: Runnable): vscode.Task {
function createTask(spec: ra.Runnable): vscode.Task {
const TASK_SOURCE = 'Rust';
const definition: CargoTaskDefinition = {
type: 'cargo',

View File

@ -1,6 +1,8 @@
import { Ctx, Cmd } from '../ctx';
import { applySourceChange, SourceChange } from '../source_change';
import * as vscode from 'vscode';
import * as ra from "../rust-analyzer-api";
import { Ctx, Cmd } from '../ctx';
import { applySourceChange } from '../source_change';
export function ssr(ctx: Ctx): Cmd {
return async () => {
@ -21,16 +23,8 @@ export function ssr(ctx: Ctx): Cmd {
if (!request) return;
const ssrRequest: SsrRequest = { arg: request };
const change = await client.sendRequest<SourceChange>(
'rust-analyzer/ssr',
ssrRequest,
);
const change = await client.sendRequest(ra.ssr, { arg: request });
await applySourceChange(ctx, change);
};
}
interface SsrRequest {
arg: string;
}

View File

@ -1,5 +1,5 @@
import * as vscode from 'vscode';
import * as lc from 'vscode-languageclient';
import * as ra from '../rust-analyzer-api';
import { Ctx, Cmd } from '../ctx';
@ -61,13 +61,8 @@ function afterLs(f: () => void) {
setTimeout(f, 10);
}
interface SyntaxTreeParams {
textDocument: lc.TextDocumentIdentifier;
range?: lc.Range;
}
class TextDocumentContentProvider
implements vscode.TextDocumentContentProvider {
class TextDocumentContentProvider implements vscode.TextDocumentContentProvider {
uri = vscode.Uri.parse('rust-analyzer://syntaxtree');
eventEmitter = new vscode.EventEmitter<vscode.Uri>();
@ -79,23 +74,15 @@ class TextDocumentContentProvider
const client = this.ctx.client;
if (!editor || !client) return '';
let range: lc.Range | undefined;
// When the range based query is enabled we take the range of the selection
if (uri.query === 'range=true') {
range = editor.selection.isEmpty
? undefined
: client.code2ProtocolConverter.asRange(editor.selection);
}
const range = uri.query === 'range=true' && !editor.selection.isEmpty
? client.code2ProtocolConverter.asRange(editor.selection)
: null;
const request: SyntaxTreeParams = {
return client.sendRequest(ra.syntaxTree, {
textDocument: { uri: editor.document.uri.toString() },
range,
};
return client.sendRequest<string>(
'rust-analyzer/syntaxTree',
request,
);
});
}
get onDidChange(): vscode.Event<vscode.Uri> {

View File

@ -1,5 +1,5 @@
import * as vscode from 'vscode';
import * as lc from 'vscode-languageclient';
import * as ra from './rust-analyzer-api';
import { ColorTheme, TextMateRuleSettings } from './color_theme';
@ -8,29 +8,25 @@ import { sendRequestWithRetry } from './util';
export function activateHighlighting(ctx: Ctx) {
const highlighter = new Highlighter(ctx);
const client = ctx.client;
if (client != null) {
client.onNotification(
'rust-analyzer/publishDecorations',
(params: PublishDecorationsParams) => {
if (!ctx.config.highlightingOn) return;
const targetEditor = vscode.window.visibleTextEditors.find(
editor => {
const unescapedUri = unescape(
editor.document.uri.toString(),
);
// Unescaped URI looks like:
// file:///c:/Workspace/ra-test/src/main.rs
return unescapedUri === params.uri;
},
ctx.client.onNotification(ra.publishDecorations, params => {
if (!ctx.config.highlightingOn) return;
const targetEditor = vscode.window.visibleTextEditors.find(
editor => {
const unescapedUri = unescape(
editor.document.uri.toString(),
);
if (!targetEditor) return;
highlighter.setHighlights(targetEditor, params.decorations);
// Unescaped URI looks like:
// file:///c:/Workspace/ra-test/src/main.rs
return unescapedUri === params.uri;
},
);
}
if (!targetEditor) return;
highlighter.setHighlights(targetEditor, params.decorations);
});
vscode.workspace.onDidChangeConfiguration(
_ => highlighter.removeHighlights(),
@ -45,13 +41,10 @@ export function activateHighlighting(ctx: Ctx) {
const client = ctx.client;
if (!client) return;
const params: lc.TextDocumentIdentifier = {
uri: editor.document.uri.toString(),
};
const decorations = await sendRequestWithRetry<Decoration[]>(
const decorations = await sendRequestWithRetry(
client,
'rust-analyzer/decorationsRequest',
params,
ra.decorationsRequest,
{ uri: editor.document.uri.toString() },
);
highlighter.setHighlights(editor, decorations);
},
@ -60,17 +53,6 @@ export function activateHighlighting(ctx: Ctx) {
);
}
interface PublishDecorationsParams {
uri: string;
decorations: Decoration[];
}
interface Decoration {
range: lc.Range;
tag: string;
bindingHash?: string;
}
// Based on this HSL-based color generator: https://gist.github.com/bendc/76c48ce53299e6078a76
function fancify(seed: string, shade: 'light' | 'dark') {
const random = randomU32Numbers(hashString(seed));
@ -108,7 +90,7 @@ class Highlighter {
this.decorations = null;
}
public setHighlights(editor: vscode.TextEditor, highlights: Decoration[]) {
public setHighlights(editor: vscode.TextEditor, highlights: ra.Decoration[]) {
const client = this.ctx.client;
if (!client) return;
// Initialize decorations if necessary

View File

@ -1,5 +1,5 @@
import * as vscode from 'vscode';
import * as lc from 'vscode-languageclient';
import * as ra from './rust-analyzer-api';
import { Ctx } from './ctx';
import { log, sendRequestWithRetry } from './util';
@ -39,16 +39,6 @@ export function activateInlayHints(ctx: Ctx) {
void hintsUpdater.setEnabled(ctx.config.displayInlayHints);
}
interface InlayHintsParams {
textDocument: lc.TextDocumentIdentifier;
}
interface InlayHint {
range: vscode.Range;
kind: "TypeHint" | "ParameterHint";
label: string;
}
const typeHintDecorationType = vscode.window.createTextEditorDecorationType({
after: {
color: new vscode.ThemeColor('rust_analyzer.inlayHint'),
@ -107,9 +97,9 @@ class HintsUpdater {
if (newHints == null) return;
const newTypeDecorations = newHints
.filter(hint => hint.kind === 'TypeHint')
.filter(hint => hint.kind === ra.InlayKind.TypeHint)
.map(hint => ({
range: hint.range,
range: this.ctx.client.protocol2CodeConverter.asRange(hint.range),
renderOptions: {
after: {
contentText: `: ${hint.label}`,
@ -119,9 +109,9 @@ class HintsUpdater {
this.setTypeDecorations(editor, newTypeDecorations);
const newParameterDecorations = newHints
.filter(hint => hint.kind === 'ParameterHint')
.filter(hint => hint.kind === ra.InlayKind.ParameterHint)
.map(hint => ({
range: hint.range,
range: this.ctx.client.protocol2CodeConverter.asRange(hint.range),
renderOptions: {
before: {
contentText: `${hint.label}: `,
@ -151,20 +141,15 @@ class HintsUpdater {
);
}
private async queryHints(documentUri: string): Promise<InlayHint[] | null> {
private async queryHints(documentUri: string): Promise<ra.InlayHint[] | null> {
this.pending.get(documentUri)?.cancel();
const tokenSource = new vscode.CancellationTokenSource();
this.pending.set(documentUri, tokenSource);
const request: InlayHintsParams = { textDocument: { uri: documentUri } };
const request = { textDocument: { uri: documentUri } };
return sendRequestWithRetry<InlayHint[]>(
this.ctx.client,
'rust-analyzer/inlayHints',
request,
tokenSource.token
)
return sendRequestWithRetry(this.ctx.client, ra.inlayHints, request, tokenSource.token)
.catch(_ => null)
.finally(() => {
if (!tokenSource.token.isCancellationRequested) {

View File

@ -0,0 +1,117 @@
/**
* This file mirrors `crates/rust-analyzer/src/req.rs` declarations.
*/
import * as lc from "vscode-languageclient";
type Option<T> = null | T;
type Vec<T> = T[];
type FxHashMap<K extends PropertyKey, V> = Record<K, V>;
function request<TParams, TResult>(method: string) {
return new lc.RequestType<TParams, TResult, unknown>(`rust-analyzer/${method}`);
}
function notification<TParam>(method: string) {
return new lc.NotificationType<TParam>(method);
}
export const analyzerStatus = request<null, string>("analyzerStatus");
export const collectGarbage = request<null, null>("collectGarbage");
export interface SyntaxTreeParams {
textDocument: lc.TextDocumentIdentifier;
range: Option<lc.Range>;
}
export const syntaxTree = request<SyntaxTreeParams, string>("syntaxTree");
export interface ExpandMacroParams {
textDocument: lc.TextDocumentIdentifier;
position: Option<lc.Position>;
}
export interface ExpandedMacro {
name: string;
expansion: string;
}
export const expandMacro = request<ExpandMacroParams, Option<ExpandedMacro>>("expandMacro");
export interface FindMatchingBraceParams {
textDocument: lc.TextDocumentIdentifier;
offsets: Vec<lc.Position>;
}
export const findMatchingBrace = request<FindMatchingBraceParams, Vec<lc.Position>>("findMatchingBrace");
export interface PublishDecorationsParams {
uri: string;
decorations: Vec<Decoration>;
}
export interface Decoration {
range: lc.Range;
tag: string;
bindingHash: Option<string>;
}
export const decorationsRequest = request<lc.TextDocumentIdentifier, Vec<Decoration>>("decorationsRequest");
export const parentModule = request<lc.TextDocumentPositionParams, Vec<lc.Location>>("parentModule");
export interface JoinLinesParams {
textDocument: lc.TextDocumentIdentifier;
range: lc.Range;
}
export const joinLines = request<JoinLinesParams, SourceChange>("joinLines");
export const onEnter = request<lc.TextDocumentPositionParams, Option<SourceChange>>("onEnter");
export interface RunnablesParams {
textDocument: lc.TextDocumentIdentifier;
position: Option<lc.Position>;
}
export interface Runnable {
range: lc.Range;
label: string;
bin: string;
args: Vec<string>;
env: FxHashMap<string, string>;
cwd: Option<string>;
}
export const runnables = request<RunnablesParams, Vec<Runnable>>("runnables");
export const enum InlayKind {
TypeHint = "TypeHint",
ParameterHint = "ParameterHint",
}
export interface InlayHint {
range: lc.Range;
kind: InlayKind;
label: string;
}
export interface InlayHintsParams {
textDocument: lc.TextDocumentIdentifier;
}
export const inlayHints = request<InlayHintsParams, Vec<InlayHint>>("inlayHints");
export interface SsrParams {
arg: string;
}
export const ssr = request<SsrParams, SourceChange>("ssr");
export const publishDecorations = notification<PublishDecorationsParams>("publishDecorations");
export interface SourceChange {
label: string;
workspaceEdit: lc.WorkspaceEdit;
cursorPosition: Option<lc.TextDocumentPositionParams>;
}

View File

@ -1,15 +1,10 @@
import * as vscode from 'vscode';
import * as lc from 'vscode-languageclient';
import * as ra from './rust-analyzer-api';
import { Ctx } from './ctx';
export interface SourceChange {
label: string;
workspaceEdit: lc.WorkspaceEdit;
cursorPosition?: lc.TextDocumentPositionParams;
}
export async function applySourceChange(ctx: Ctx, change: SourceChange) {
export async function applySourceChange(ctx: Ctx, change: ra.SourceChange) {
const client = ctx.client;
if (!client) return;

View File

@ -20,21 +20,21 @@ export const log = {
}
};
export async function sendRequestWithRetry<R>(
export async function sendRequestWithRetry<TParam, TRet>(
client: lc.LanguageClient,
method: string,
param: unknown,
reqType: lc.RequestType<TParam, TRet, unknown>,
param: TParam,
token?: vscode.CancellationToken,
): Promise<R> {
): Promise<TRet> {
for (const delay of [2, 4, 6, 8, 10, null]) {
try {
return await (token
? client.sendRequest(method, param, token)
: client.sendRequest(method, param)
? client.sendRequest(reqType, param, token)
: client.sendRequest(reqType, param)
);
} catch (error) {
if (delay === null) {
log.error("LSP request timed out", { method, param, error });
log.error("LSP request timed out", { method: reqType.method, param, error });
throw error;
}
@ -43,7 +43,7 @@ export async function sendRequestWithRetry<R>(
}
if (error.code !== lc.ErrorCodes.ContentModified) {
log.error("LSP request failed", { method, param, error });
log.error("LSP request failed", { method: reqType.method, param, error });
throw error;
}