mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-01 11:31:15 +00:00
Merge #2709
2709: Work around synchrnonisation issue r=matklad a=matklad Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
commit
6d23140ba0
90
editors/code/src/client.ts
Normal file
90
editors/code/src/client.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import { homedir } from 'os';
|
||||||
|
import * as lc from 'vscode-languageclient';
|
||||||
|
|
||||||
|
import { window, workspace } from 'vscode';
|
||||||
|
import { Config } from './config';
|
||||||
|
|
||||||
|
export function createClient(config: Config): lc.LanguageClient {
|
||||||
|
// '.' Is the fallback if no folder is open
|
||||||
|
// TODO?: Workspace folders support Uri's (eg: file://test.txt). It might be a good idea to test if the uri points to a file.
|
||||||
|
let folder: string = '.';
|
||||||
|
if (workspace.workspaceFolders !== undefined) {
|
||||||
|
folder = workspace.workspaceFolders[0].uri.fsPath.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const command = expandPathResolving(config.raLspServerPath);
|
||||||
|
const run: lc.Executable = {
|
||||||
|
command,
|
||||||
|
options: { cwd: folder },
|
||||||
|
};
|
||||||
|
const serverOptions: lc.ServerOptions = {
|
||||||
|
run,
|
||||||
|
debug: run,
|
||||||
|
};
|
||||||
|
const traceOutputChannel = window.createOutputChannel(
|
||||||
|
'Rust Analyzer Language Server Trace',
|
||||||
|
);
|
||||||
|
const clientOptions: lc.LanguageClientOptions = {
|
||||||
|
documentSelector: [{ scheme: 'file', language: 'rust' }],
|
||||||
|
initializationOptions: {
|
||||||
|
publishDecorations: true,
|
||||||
|
lruCapacity: config.lruCapacity,
|
||||||
|
maxInlayHintLength: config.maxInlayHintLength,
|
||||||
|
cargoWatchEnable: config.cargoWatchOptions.enable,
|
||||||
|
cargoWatchArgs: config.cargoWatchOptions.arguments,
|
||||||
|
cargoWatchCommand: config.cargoWatchOptions.command,
|
||||||
|
cargoWatchAllTargets:
|
||||||
|
config.cargoWatchOptions.allTargets,
|
||||||
|
excludeGlobs: config.excludeGlobs,
|
||||||
|
useClientWatching: config.useClientWatching,
|
||||||
|
featureFlags: config.featureFlags,
|
||||||
|
withSysroot: config.withSysroot,
|
||||||
|
cargoFeatures: config.cargoFeatures,
|
||||||
|
},
|
||||||
|
traceOutputChannel,
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = new lc.LanguageClient(
|
||||||
|
'rust-analyzer',
|
||||||
|
'Rust Analyzer Language Server',
|
||||||
|
serverOptions,
|
||||||
|
clientOptions,
|
||||||
|
);
|
||||||
|
|
||||||
|
// HACK: This is an awful way of filtering out the decorations notifications
|
||||||
|
// However, pending proper support, this is the most effecitve approach
|
||||||
|
// Proper support for this would entail a change to vscode-languageclient to allow not notifying on certain messages
|
||||||
|
// Or the ability to disable the serverside component of highlighting (but this means that to do tracing we need to disable hihlighting)
|
||||||
|
// This also requires considering our settings strategy, which is work which needs doing
|
||||||
|
// @ts-ignore The tracer is private to vscode-languageclient, but we need access to it to not log publishDecorations requests
|
||||||
|
res._tracer = {
|
||||||
|
log: (messageOrDataObject: string | any, data?: string) => {
|
||||||
|
if (typeof messageOrDataObject === 'string') {
|
||||||
|
if (
|
||||||
|
messageOrDataObject.includes(
|
||||||
|
'rust-analyzer/publishDecorations',
|
||||||
|
) ||
|
||||||
|
messageOrDataObject.includes(
|
||||||
|
'rust-analyzer/decorationsRequest',
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// Don't log publish decorations requests
|
||||||
|
} else {
|
||||||
|
// @ts-ignore This is just a utility function
|
||||||
|
res.logTrace(messageOrDataObject, data);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
res.logObjectTrace(messageOrDataObject);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
res.registerProposedFeatures()
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
function expandPathResolving(path: string) {
|
||||||
|
if (path.startsWith('~/')) {
|
||||||
|
return path.replace('~', homedir());
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
@ -49,9 +49,10 @@ class TextDocumentContentProvider
|
|||||||
_uri: vscode.Uri,
|
_uri: vscode.Uri,
|
||||||
): vscode.ProviderResult<string> {
|
): vscode.ProviderResult<string> {
|
||||||
const editor = vscode.window.activeTextEditor;
|
const editor = vscode.window.activeTextEditor;
|
||||||
if (editor == null) return '';
|
const client = this.ctx.client
|
||||||
|
if (!editor || !client) return '';
|
||||||
|
|
||||||
return this.ctx.client.sendRequest<string>(
|
return client.sendRequest<string>(
|
||||||
'rust-analyzer/analyzerStatus',
|
'rust-analyzer/analyzerStatus',
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
@ -52,14 +52,15 @@ class TextDocumentContentProvider
|
|||||||
|
|
||||||
async provideTextDocumentContent(_uri: vscode.Uri): Promise<string> {
|
async provideTextDocumentContent(_uri: vscode.Uri): Promise<string> {
|
||||||
const editor = vscode.window.activeTextEditor;
|
const editor = vscode.window.activeTextEditor;
|
||||||
if (editor == null) return '';
|
const client = this.ctx.client
|
||||||
|
if (!editor || !client) return '';
|
||||||
|
|
||||||
const position = editor.selection.active;
|
const position = editor.selection.active;
|
||||||
const request: lc.TextDocumentPositionParams = {
|
const request: lc.TextDocumentPositionParams = {
|
||||||
textDocument: { uri: editor.document.uri.toString() },
|
textDocument: { uri: editor.document.uri.toString() },
|
||||||
position,
|
position,
|
||||||
};
|
};
|
||||||
const expanded = await this.ctx.client.sendRequest<ExpandedMacro>(
|
const expanded = await client.sendRequest<ExpandedMacro>(
|
||||||
'rust-analyzer/expandMacro',
|
'rust-analyzer/expandMacro',
|
||||||
request,
|
request,
|
||||||
);
|
);
|
||||||
|
@ -15,18 +15,21 @@ import { run, runSingle } from './runnables';
|
|||||||
|
|
||||||
function collectGarbage(ctx: Ctx): Cmd {
|
function collectGarbage(ctx: Ctx): Cmd {
|
||||||
return async () => {
|
return async () => {
|
||||||
ctx.client.sendRequest<null>('rust-analyzer/collectGarbage', null);
|
ctx.client?.sendRequest<null>('rust-analyzer/collectGarbage', null);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function showReferences(ctx: Ctx): Cmd {
|
function showReferences(ctx: Ctx): Cmd {
|
||||||
return (uri: string, position: lc.Position, locations: lc.Location[]) => {
|
return (uri: string, position: lc.Position, locations: lc.Location[]) => {
|
||||||
|
let client = ctx.client;
|
||||||
|
if (client) {
|
||||||
vscode.commands.executeCommand(
|
vscode.commands.executeCommand(
|
||||||
'editor.action.showReferences',
|
'editor.action.showReferences',
|
||||||
vscode.Uri.parse(uri),
|
vscode.Uri.parse(uri),
|
||||||
ctx.client.protocol2CodeConverter.asPosition(position),
|
client.protocol2CodeConverter.asPosition(position),
|
||||||
locations.map(ctx.client.protocol2CodeConverter.asLocation),
|
locations.map(client.protocol2CodeConverter.asLocation),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,6 +39,13 @@ function applySourceChange(ctx: Ctx): Cmd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function reload(ctx: Ctx): Cmd {
|
||||||
|
return async () => {
|
||||||
|
vscode.window.showInformationMessage('Reloading rust-analyzer...');
|
||||||
|
await ctx.restartServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
analyzerStatus,
|
analyzerStatus,
|
||||||
expandMacro,
|
expandMacro,
|
||||||
@ -49,4 +59,5 @@ export {
|
|||||||
runSingle,
|
runSingle,
|
||||||
showReferences,
|
showReferences,
|
||||||
applySourceChange,
|
applySourceChange,
|
||||||
|
reload
|
||||||
};
|
};
|
||||||
|
@ -6,13 +6,14 @@ import { applySourceChange, SourceChange } from '../source_change';
|
|||||||
export function joinLines(ctx: Ctx): Cmd {
|
export function joinLines(ctx: Ctx): Cmd {
|
||||||
return async () => {
|
return async () => {
|
||||||
const editor = ctx.activeRustEditor;
|
const editor = ctx.activeRustEditor;
|
||||||
if (!editor) return;
|
const client = ctx.client;
|
||||||
|
if (!editor || !client) return;
|
||||||
|
|
||||||
const request: JoinLinesParams = {
|
const request: JoinLinesParams = {
|
||||||
range: ctx.client.code2ProtocolConverter.asRange(editor.selection),
|
range: client.code2ProtocolConverter.asRange(editor.selection),
|
||||||
textDocument: { uri: editor.document.uri.toString() },
|
textDocument: { uri: editor.document.uri.toString() },
|
||||||
};
|
};
|
||||||
const change = await ctx.client.sendRequest<SourceChange>(
|
const change = await client.sendRequest<SourceChange>(
|
||||||
'rust-analyzer/joinLines',
|
'rust-analyzer/joinLines',
|
||||||
request,
|
request,
|
||||||
);
|
);
|
||||||
|
@ -16,25 +16,25 @@ export interface CargoFeatures {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Config {
|
export class Config {
|
||||||
public highlightingOn = true;
|
highlightingOn = true;
|
||||||
public rainbowHighlightingOn = false;
|
rainbowHighlightingOn = false;
|
||||||
public enableEnhancedTyping = true;
|
enableEnhancedTyping = true;
|
||||||
public raLspServerPath = RA_LSP_DEBUG || 'ra_lsp_server';
|
raLspServerPath = RA_LSP_DEBUG || 'ra_lsp_server';
|
||||||
public lruCapacity: null | number = null;
|
lruCapacity: null | number = null;
|
||||||
public displayInlayHints = true;
|
displayInlayHints = true;
|
||||||
public maxInlayHintLength: null | number = null;
|
maxInlayHintLength: null | number = null;
|
||||||
public excludeGlobs = [];
|
excludeGlobs = [];
|
||||||
public useClientWatching = true;
|
useClientWatching = true;
|
||||||
public featureFlags = {};
|
featureFlags = {};
|
||||||
// for internal use
|
// for internal use
|
||||||
public withSysroot: null | boolean = null;
|
withSysroot: null | boolean = null;
|
||||||
public cargoWatchOptions: CargoWatchOptions = {
|
cargoWatchOptions: CargoWatchOptions = {
|
||||||
enable: true,
|
enable: true,
|
||||||
arguments: [],
|
arguments: [],
|
||||||
command: '',
|
command: '',
|
||||||
allTargets: true,
|
allTargets: true,
|
||||||
};
|
};
|
||||||
public cargoFeatures: CargoFeatures = {
|
cargoFeatures: CargoFeatures = {
|
||||||
noDefaultFeatures: false,
|
noDefaultFeatures: false,
|
||||||
allFeatures: true,
|
allFeatures: true,
|
||||||
features: [],
|
features: [],
|
||||||
@ -43,14 +43,12 @@ export class Config {
|
|||||||
private prevEnhancedTyping: null | boolean = null;
|
private prevEnhancedTyping: null | boolean = null;
|
||||||
private prevCargoFeatures: null | CargoFeatures = null;
|
private prevCargoFeatures: null | CargoFeatures = null;
|
||||||
|
|
||||||
constructor() {
|
constructor(ctx: vscode.ExtensionContext) {
|
||||||
vscode.workspace.onDidChangeConfiguration(_ =>
|
vscode.workspace.onDidChangeConfiguration(_ => this.refresh(), ctx.subscriptions);
|
||||||
this.userConfigChanged(),
|
this.refresh();
|
||||||
);
|
|
||||||
this.userConfigChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public userConfigChanged() {
|
private refresh() {
|
||||||
const config = vscode.workspace.getConfiguration('rust-analyzer');
|
const config = vscode.workspace.getConfiguration('rust-analyzer');
|
||||||
|
|
||||||
let requireReloadMessage = null;
|
let requireReloadMessage = null;
|
||||||
|
@ -1,21 +1,38 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as lc from 'vscode-languageclient';
|
import * as lc from 'vscode-languageclient';
|
||||||
import { Server } from './server';
|
|
||||||
import { Config } from './config';
|
import { Config } from './config';
|
||||||
|
import { createClient } from './client'
|
||||||
|
|
||||||
export class Ctx {
|
export class Ctx {
|
||||||
|
readonly config: Config;
|
||||||
|
// Because we have "reload server" action, various listeners **will** face a
|
||||||
|
// situation where the client is not ready yet, and should be prepared to
|
||||||
|
// deal with it.
|
||||||
|
//
|
||||||
|
// Ideally, this should be replaced with async getter though.
|
||||||
|
client: lc.LanguageClient | null = null
|
||||||
private extCtx: vscode.ExtensionContext;
|
private extCtx: vscode.ExtensionContext;
|
||||||
|
private onDidRestartHooks: Array<(client: lc.LanguageClient) => void> = [];
|
||||||
|
|
||||||
constructor(extCtx: vscode.ExtensionContext) {
|
constructor(extCtx: vscode.ExtensionContext) {
|
||||||
|
this.config = new Config(extCtx)
|
||||||
this.extCtx = extCtx;
|
this.extCtx = extCtx;
|
||||||
}
|
}
|
||||||
|
|
||||||
get client(): lc.LanguageClient {
|
async restartServer() {
|
||||||
return Server.client;
|
let old = this.client;
|
||||||
|
if (old) {
|
||||||
|
await old.stop()
|
||||||
}
|
}
|
||||||
|
this.client = null;
|
||||||
|
const client = createClient(this.config);
|
||||||
|
this.pushCleanup(client.start());
|
||||||
|
await client.onReady();
|
||||||
|
|
||||||
get config(): Config {
|
this.client = client
|
||||||
return Server.config;
|
for (const hook of this.onDidRestartHooks) {
|
||||||
|
hook(client)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get activeRustEditor(): vscode.TextEditor | undefined {
|
get activeRustEditor(): vscode.TextEditor | undefined {
|
||||||
@ -62,15 +79,22 @@ export class Ctx {
|
|||||||
this.extCtx.subscriptions.push(d);
|
this.extCtx.subscriptions.push(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendRequestWithRetry<R>(
|
onDidRestart(hook: (client: lc.LanguageClient) => void) {
|
||||||
|
this.onDidRestartHooks.push(hook)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Cmd = (...args: any[]) => any;
|
||||||
|
|
||||||
|
export async function sendRequestWithRetry<R>(
|
||||||
|
client: lc.LanguageClient,
|
||||||
method: string,
|
method: string,
|
||||||
param: any,
|
param: any,
|
||||||
token?: vscode.CancellationToken,
|
token?: vscode.CancellationToken,
|
||||||
): Promise<R> {
|
): Promise<R> {
|
||||||
await this.client.onReady();
|
|
||||||
for (const delay of [2, 4, 6, 8, 10, null]) {
|
for (const delay of [2, 4, 6, 8, 10, null]) {
|
||||||
try {
|
try {
|
||||||
return await (token ? this.client.sendRequest(method, param, token) : this.client.sendRequest(method, param));
|
return await (token ? client.sendRequest(method, param, token) : client.sendRequest(method, param));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (
|
if (
|
||||||
e.code === lc.ErrorCodes.ContentModified &&
|
e.code === lc.ErrorCodes.ContentModified &&
|
||||||
@ -84,8 +108,5 @@ export class Ctx {
|
|||||||
}
|
}
|
||||||
throw 'unreachable';
|
throw 'unreachable';
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export type Cmd = (...args: any[]) => any;
|
|
||||||
|
|
||||||
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
@ -5,13 +5,12 @@ const seedrandom = seedrandom_; // https://github.com/jvandemo/generator-angular
|
|||||||
|
|
||||||
import { ColorTheme, TextMateRuleSettings } from './color_theme';
|
import { ColorTheme, TextMateRuleSettings } from './color_theme';
|
||||||
|
|
||||||
import { Ctx } from './ctx';
|
import { Ctx, sendRequestWithRetry } from './ctx';
|
||||||
|
|
||||||
export function activateHighlighting(ctx: Ctx) {
|
export function activateHighlighting(ctx: Ctx) {
|
||||||
const highlighter = new Highlighter(ctx);
|
const highlighter = new Highlighter(ctx);
|
||||||
|
ctx.onDidRestart(client => {
|
||||||
ctx.client.onReady().then(() => {
|
client.onNotification(
|
||||||
ctx.client.onNotification(
|
|
||||||
'rust-analyzer/publishDecorations',
|
'rust-analyzer/publishDecorations',
|
||||||
(params: PublishDecorationsParams) => {
|
(params: PublishDecorationsParams) => {
|
||||||
if (!ctx.config.highlightingOn) return;
|
if (!ctx.config.highlightingOn) return;
|
||||||
@ -31,7 +30,7 @@ export function activateHighlighting(ctx: Ctx) {
|
|||||||
highlighter.setHighlights(targetEditor, params.decorations);
|
highlighter.setHighlights(targetEditor, params.decorations);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
})
|
||||||
|
|
||||||
vscode.workspace.onDidChangeConfiguration(
|
vscode.workspace.onDidChangeConfiguration(
|
||||||
_ => highlighter.removeHighlights(),
|
_ => highlighter.removeHighlights(),
|
||||||
@ -42,11 +41,14 @@ export function activateHighlighting(ctx: Ctx) {
|
|||||||
async (editor: vscode.TextEditor | undefined) => {
|
async (editor: vscode.TextEditor | undefined) => {
|
||||||
if (!editor || editor.document.languageId !== 'rust') return;
|
if (!editor || editor.document.languageId !== 'rust') return;
|
||||||
if (!ctx.config.highlightingOn) return;
|
if (!ctx.config.highlightingOn) return;
|
||||||
|
let client = ctx.client;
|
||||||
|
if (!client) return;
|
||||||
|
|
||||||
const params: lc.TextDocumentIdentifier = {
|
const params: lc.TextDocumentIdentifier = {
|
||||||
uri: editor.document.uri.toString(),
|
uri: editor.document.uri.toString(),
|
||||||
};
|
};
|
||||||
const decorations = await ctx.sendRequestWithRetry<Decoration[]>(
|
const decorations = await sendRequestWithRetry<Decoration[]>(
|
||||||
|
client,
|
||||||
'rust-analyzer/decorationsRequest',
|
'rust-analyzer/decorationsRequest',
|
||||||
params,
|
params,
|
||||||
);
|
);
|
||||||
@ -105,6 +107,8 @@ class Highlighter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public setHighlights(editor: vscode.TextEditor, highlights: Decoration[]) {
|
public setHighlights(editor: vscode.TextEditor, highlights: Decoration[]) {
|
||||||
|
let client = this.ctx.client;
|
||||||
|
if (!client) return;
|
||||||
// Initialize decorations if necessary
|
// Initialize decorations if necessary
|
||||||
//
|
//
|
||||||
// Note: decoration objects need to be kept around so we can dispose them
|
// Note: decoration objects need to be kept around so we can dispose them
|
||||||
@ -137,13 +141,13 @@ class Highlighter {
|
|||||||
colorfulIdents
|
colorfulIdents
|
||||||
.get(d.bindingHash)![0]
|
.get(d.bindingHash)![0]
|
||||||
.push(
|
.push(
|
||||||
this.ctx.client.protocol2CodeConverter.asRange(d.range),
|
client.protocol2CodeConverter.asRange(d.range),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
byTag
|
byTag
|
||||||
.get(d.tag)!
|
.get(d.tag)!
|
||||||
.push(
|
.push(
|
||||||
this.ctx.client.protocol2CodeConverter.asRange(d.range),
|
client.protocol2CodeConverter.asRange(d.range),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as lc from 'vscode-languageclient';
|
import * as lc from 'vscode-languageclient';
|
||||||
|
|
||||||
import { Ctx } from './ctx';
|
import { Ctx, sendRequestWithRetry } from './ctx';
|
||||||
|
|
||||||
export function activateInlayHints(ctx: Ctx) {
|
export function activateInlayHints(ctx: Ctx) {
|
||||||
const hintsUpdater = new HintsUpdater(ctx);
|
const hintsUpdater = new HintsUpdater(ctx);
|
||||||
@ -19,9 +19,7 @@ export function activateInlayHints(ctx: Ctx) {
|
|||||||
hintsUpdater.setEnabled(ctx.config.displayInlayHints);
|
hintsUpdater.setEnabled(ctx.config.displayInlayHints);
|
||||||
}, ctx.subscriptions);
|
}, ctx.subscriptions);
|
||||||
|
|
||||||
// XXX: don't await here;
|
ctx.onDidRestart(_ => hintsUpdater.setEnabled(ctx.config.displayInlayHints))
|
||||||
// Who knows what happens if an exception is thrown here...
|
|
||||||
hintsUpdater.refresh();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface InlayHintsParams {
|
interface InlayHintsParams {
|
||||||
@ -97,6 +95,8 @@ class HintsUpdater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async queryHints(documentUri: string): Promise<InlayHint[] | null> {
|
private async queryHints(documentUri: string): Promise<InlayHint[] | null> {
|
||||||
|
let client = this.ctx.client;
|
||||||
|
if (!client) return null
|
||||||
const request: InlayHintsParams = {
|
const request: InlayHintsParams = {
|
||||||
textDocument: { uri: documentUri },
|
textDocument: { uri: documentUri },
|
||||||
};
|
};
|
||||||
@ -105,7 +105,8 @@ class HintsUpdater {
|
|||||||
if (prev) prev.cancel();
|
if (prev) prev.cancel();
|
||||||
this.pending.set(documentUri, tokenSource);
|
this.pending.set(documentUri, tokenSource);
|
||||||
try {
|
try {
|
||||||
return await this.ctx.sendRequestWithRetry<InlayHint[] | null>(
|
return await sendRequestWithRetry<InlayHint[] | null>(
|
||||||
|
client,
|
||||||
'rust-analyzer/inlayHints',
|
'rust-analyzer/inlayHints',
|
||||||
request,
|
request,
|
||||||
tokenSource.token,
|
tokenSource.token,
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as lc from 'vscode-languageclient';
|
|
||||||
|
|
||||||
import * as commands from './commands';
|
import * as commands from './commands';
|
||||||
import { activateInlayHints } from './inlay_hints';
|
import { activateInlayHints } from './inlay_hints';
|
||||||
import { StatusDisplay } from './status_display';
|
import { activateStatusDisplay } from './status_display';
|
||||||
import { Server } from './server';
|
|
||||||
import { Ctx } from './ctx';
|
import { Ctx } from './ctx';
|
||||||
import { activateHighlighting } from './highlighting';
|
import { activateHighlighting } from './highlighting';
|
||||||
|
|
||||||
@ -13,6 +11,17 @@ let ctx!: Ctx;
|
|||||||
export async function activate(context: vscode.ExtensionContext) {
|
export async function activate(context: vscode.ExtensionContext) {
|
||||||
ctx = new Ctx(context);
|
ctx = new Ctx(context);
|
||||||
|
|
||||||
|
// Note: we try to start the server before we register various commands, so
|
||||||
|
// that it registers its `onDidChangeDocument` handler before us.
|
||||||
|
//
|
||||||
|
// This a horribly, horribly wrong way to deal with this problem.
|
||||||
|
try {
|
||||||
|
await ctx.restartServer();
|
||||||
|
} catch (e) {
|
||||||
|
vscode.window.showErrorMessage(e.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Commands which invokes manually via command pallet, shortcut, etc.
|
// Commands which invokes manually via command pallet, shortcut, etc.
|
||||||
ctx.registerCommand('analyzerStatus', commands.analyzerStatus);
|
ctx.registerCommand('analyzerStatus', commands.analyzerStatus);
|
||||||
ctx.registerCommand('collectGarbage', commands.collectGarbage);
|
ctx.registerCommand('collectGarbage', commands.collectGarbage);
|
||||||
@ -22,6 +31,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||||||
ctx.registerCommand('syntaxTree', commands.syntaxTree);
|
ctx.registerCommand('syntaxTree', commands.syntaxTree);
|
||||||
ctx.registerCommand('expandMacro', commands.expandMacro);
|
ctx.registerCommand('expandMacro', commands.expandMacro);
|
||||||
ctx.registerCommand('run', commands.run);
|
ctx.registerCommand('run', commands.run);
|
||||||
|
ctx.registerCommand('reload', commands.reload);
|
||||||
|
|
||||||
// Internal commands which are invoked by the server.
|
// Internal commands which are invoked by the server.
|
||||||
ctx.registerCommand('runSingle', commands.runSingle);
|
ctx.registerCommand('runSingle', commands.runSingle);
|
||||||
@ -31,48 +41,11 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||||||
if (ctx.config.enableEnhancedTyping) {
|
if (ctx.config.enableEnhancedTyping) {
|
||||||
ctx.overrideCommand('type', commands.onEnter);
|
ctx.overrideCommand('type', commands.onEnter);
|
||||||
}
|
}
|
||||||
|
activateStatusDisplay(ctx);
|
||||||
const watchStatus = new StatusDisplay(ctx.config.cargoWatchOptions.command);
|
|
||||||
ctx.pushCleanup(watchStatus);
|
|
||||||
|
|
||||||
// Notifications are events triggered by the language server
|
|
||||||
const allNotifications: [string, lc.GenericNotificationHandler][] = [
|
|
||||||
[
|
|
||||||
'$/progress',
|
|
||||||
params => watchStatus.handleProgressNotification(params),
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
const startServer = () => Server.start(allNotifications);
|
|
||||||
const reloadCommand = () => reloadServer(startServer);
|
|
||||||
|
|
||||||
vscode.commands.registerCommand('rust-analyzer.reload', reloadCommand);
|
|
||||||
|
|
||||||
// Start the language server, finally!
|
|
||||||
try {
|
|
||||||
await startServer();
|
|
||||||
} catch (e) {
|
|
||||||
vscode.window.showErrorMessage(e.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
activateHighlighting(ctx);
|
activateHighlighting(ctx);
|
||||||
|
|
||||||
if (ctx.config.displayInlayHints) {
|
|
||||||
activateInlayHints(ctx);
|
activateInlayHints(ctx);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export function deactivate(): Thenable<void> {
|
export async function deactivate() {
|
||||||
if (!Server.client) {
|
await ctx?.client?.stop();
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
return Server.client.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function reloadServer(startServer: () => Promise<void>) {
|
|
||||||
if (Server.client != null) {
|
|
||||||
vscode.window.showInformationMessage('Reloading rust-analyzer...');
|
|
||||||
await Server.client.stop();
|
|
||||||
await startServer();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,102 +0,0 @@
|
|||||||
import { homedir } from 'os';
|
|
||||||
import * as lc from 'vscode-languageclient';
|
|
||||||
|
|
||||||
import { window, workspace } from 'vscode';
|
|
||||||
import { Config } from './config';
|
|
||||||
|
|
||||||
function expandPathResolving(path: string) {
|
|
||||||
if (path.startsWith('~/')) {
|
|
||||||
return path.replace('~', homedir());
|
|
||||||
}
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Server {
|
|
||||||
public static config = new Config();
|
|
||||||
public static client: lc.LanguageClient;
|
|
||||||
|
|
||||||
public static async start(
|
|
||||||
notificationHandlers: Iterable<[string, lc.GenericNotificationHandler]>,
|
|
||||||
) {
|
|
||||||
// '.' Is the fallback if no folder is open
|
|
||||||
// TODO?: Workspace folders support Uri's (eg: file://test.txt). It might be a good idea to test if the uri points to a file.
|
|
||||||
let folder: string = '.';
|
|
||||||
if (workspace.workspaceFolders !== undefined) {
|
|
||||||
folder = workspace.workspaceFolders[0].uri.fsPath.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
const command = expandPathResolving(this.config.raLspServerPath);
|
|
||||||
const run: lc.Executable = {
|
|
||||||
command,
|
|
||||||
options: { cwd: folder },
|
|
||||||
};
|
|
||||||
const serverOptions: lc.ServerOptions = {
|
|
||||||
run,
|
|
||||||
debug: run,
|
|
||||||
};
|
|
||||||
const traceOutputChannel = window.createOutputChannel(
|
|
||||||
'Rust Analyzer Language Server Trace',
|
|
||||||
);
|
|
||||||
const clientOptions: lc.LanguageClientOptions = {
|
|
||||||
documentSelector: [{ scheme: 'file', language: 'rust' }],
|
|
||||||
initializationOptions: {
|
|
||||||
publishDecorations: true,
|
|
||||||
lruCapacity: Server.config.lruCapacity,
|
|
||||||
maxInlayHintLength: Server.config.maxInlayHintLength,
|
|
||||||
cargoWatchEnable: Server.config.cargoWatchOptions.enable,
|
|
||||||
cargoWatchArgs: Server.config.cargoWatchOptions.arguments,
|
|
||||||
cargoWatchCommand: Server.config.cargoWatchOptions.command,
|
|
||||||
cargoWatchAllTargets:
|
|
||||||
Server.config.cargoWatchOptions.allTargets,
|
|
||||||
excludeGlobs: Server.config.excludeGlobs,
|
|
||||||
useClientWatching: Server.config.useClientWatching,
|
|
||||||
featureFlags: Server.config.featureFlags,
|
|
||||||
withSysroot: Server.config.withSysroot,
|
|
||||||
cargoFeatures: Server.config.cargoFeatures,
|
|
||||||
},
|
|
||||||
traceOutputChannel,
|
|
||||||
};
|
|
||||||
|
|
||||||
Server.client = new lc.LanguageClient(
|
|
||||||
'rust-analyzer',
|
|
||||||
'Rust Analyzer Language Server',
|
|
||||||
serverOptions,
|
|
||||||
clientOptions,
|
|
||||||
);
|
|
||||||
// HACK: This is an awful way of filtering out the decorations notifications
|
|
||||||
// However, pending proper support, this is the most effecitve approach
|
|
||||||
// Proper support for this would entail a change to vscode-languageclient to allow not notifying on certain messages
|
|
||||||
// Or the ability to disable the serverside component of highlighting (but this means that to do tracing we need to disable hihlighting)
|
|
||||||
// This also requires considering our settings strategy, which is work which needs doing
|
|
||||||
// @ts-ignore The tracer is private to vscode-languageclient, but we need access to it to not log publishDecorations requests
|
|
||||||
Server.client._tracer = {
|
|
||||||
log: (messageOrDataObject: string | any, data?: string) => {
|
|
||||||
if (typeof messageOrDataObject === 'string') {
|
|
||||||
if (
|
|
||||||
messageOrDataObject.includes(
|
|
||||||
'rust-analyzer/publishDecorations',
|
|
||||||
) ||
|
|
||||||
messageOrDataObject.includes(
|
|
||||||
'rust-analyzer/decorationsRequest',
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// Don't log publish decorations requests
|
|
||||||
} else {
|
|
||||||
// @ts-ignore This is just a utility function
|
|
||||||
Server.client.logTrace(messageOrDataObject, data);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// @ts-ignore
|
|
||||||
Server.client.logObjectTrace(messageOrDataObject);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
Server.client.registerProposedFeatures();
|
|
||||||
Server.client.onReady().then(() => {
|
|
||||||
for (const [type, handler] of notificationHandlers) {
|
|
||||||
Server.client.onNotification(type, handler);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Server.client.start();
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,7 +10,10 @@ export interface SourceChange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function applySourceChange(ctx: Ctx, change: SourceChange) {
|
export async function applySourceChange(ctx: Ctx, change: SourceChange) {
|
||||||
const wsEdit = ctx.client.protocol2CodeConverter.asWorkspaceEdit(
|
const client = ctx.client;
|
||||||
|
if (!client) return
|
||||||
|
|
||||||
|
const wsEdit = client.protocol2CodeConverter.asWorkspaceEdit(
|
||||||
change.workspaceEdit,
|
change.workspaceEdit,
|
||||||
);
|
);
|
||||||
let created;
|
let created;
|
||||||
@ -32,10 +35,10 @@ export async function applySourceChange(ctx: Ctx, change: SourceChange) {
|
|||||||
const doc = await vscode.workspace.openTextDocument(toOpenUri);
|
const doc = await vscode.workspace.openTextDocument(toOpenUri);
|
||||||
await vscode.window.showTextDocument(doc);
|
await vscode.window.showTextDocument(doc);
|
||||||
} else if (toReveal) {
|
} else if (toReveal) {
|
||||||
const uri = ctx.client.protocol2CodeConverter.asUri(
|
const uri = client.protocol2CodeConverter.asUri(
|
||||||
toReveal.textDocument.uri,
|
toReveal.textDocument.uri,
|
||||||
);
|
);
|
||||||
const position = ctx.client.protocol2CodeConverter.asPosition(
|
const position = client.protocol2CodeConverter.asPosition(
|
||||||
toReveal.position,
|
toReveal.position,
|
||||||
);
|
);
|
||||||
const editor = vscode.window.activeTextEditor;
|
const editor = vscode.window.activeTextEditor;
|
||||||
|
@ -1,8 +1,18 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
|
import { Ctx } from './ctx';
|
||||||
|
|
||||||
const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
||||||
|
|
||||||
export class StatusDisplay implements vscode.Disposable {
|
export function activateStatusDisplay(ctx: Ctx) {
|
||||||
|
const statusDisplay = new StatusDisplay(ctx.config.cargoWatchOptions.command);
|
||||||
|
ctx.pushCleanup(statusDisplay);
|
||||||
|
ctx.onDidRestart(client => {
|
||||||
|
client.onNotification('$/progress', params => statusDisplay.handleProgressNotification(params));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
class StatusDisplay implements vscode.Disposable {
|
||||||
packageName?: string;
|
packageName?: string;
|
||||||
|
|
||||||
private i = 0;
|
private i = 0;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user