mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 05:23:24 +00:00
Refactor server lifecycle
This commit is contained in:
parent
0849f7001c
commit
087af54069
12 changed files with 216 additions and 199 deletions
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[]) => {
|
||||||
vscode.commands.executeCommand(
|
let client = ctx.client;
|
||||||
'editor.action.showReferences',
|
if (client) {
|
||||||
vscode.Uri.parse(uri),
|
vscode.commands.executeCommand(
|
||||||
ctx.client.protocol2CodeConverter.asPosition(position),
|
'editor.action.showReferences',
|
||||||
locations.map(ctx.client.protocol2CodeConverter.asLocation),
|
vscode.Uri.parse(uri),
|
||||||
);
|
client.protocol2CodeConverter.asPosition(position),
|
||||||
|
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,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,19 +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;
|
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.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();
|
||||||
|
|
||||||
|
this.client = client
|
||||||
|
for (const hook of this.onDidRestartHooks) {
|
||||||
|
hook(client)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get activeRustEditor(): vscode.TextEditor | undefined {
|
get activeRustEditor(): vscode.TextEditor | undefined {
|
||||||
|
@ -60,35 +79,34 @@ export class Ctx {
|
||||||
this.extCtx.subscriptions.push(d);
|
this.extCtx.subscriptions.push(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendRequestWithRetry<R>(
|
onDidRestart(hook: (client: lc.LanguageClient) => void) {
|
||||||
method: string,
|
this.onDidRestartHooks.push(hook)
|
||||||
param: any,
|
|
||||||
token?: vscode.CancellationToken,
|
|
||||||
): Promise<R> {
|
|
||||||
await this.client.onReady();
|
|
||||||
for (const delay of [2, 4, 6, 8, 10, null]) {
|
|
||||||
try {
|
|
||||||
return await (token ? this.client.sendRequest(method, param, token) : this.client.sendRequest(method, param));
|
|
||||||
} catch (e) {
|
|
||||||
if (
|
|
||||||
e.code === lc.ErrorCodes.ContentModified &&
|
|
||||||
delay !== null
|
|
||||||
) {
|
|
||||||
await sleep(10 * (1 << delay));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw 'unreachable';
|
|
||||||
}
|
|
||||||
|
|
||||||
onNotification(method: string, handler: lc.GenericNotificationHandler) {
|
|
||||||
this.client.onReady()
|
|
||||||
.then(() => this.client.onNotification(method, handler))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Cmd = (...args: any[]) => any;
|
export type Cmd = (...args: any[]) => any;
|
||||||
|
|
||||||
|
export async function sendRequestWithRetry<R>(
|
||||||
|
client: lc.LanguageClient,
|
||||||
|
method: string,
|
||||||
|
param: any,
|
||||||
|
token?: vscode.CancellationToken,
|
||||||
|
): Promise<R> {
|
||||||
|
for (const delay of [2, 4, 6, 8, 10, null]) {
|
||||||
|
try {
|
||||||
|
return await (token ? client.sendRequest(method, param, token) : client.sendRequest(method, param));
|
||||||
|
} catch (e) {
|
||||||
|
if (
|
||||||
|
e.code === lc.ErrorCodes.ContentModified &&
|
||||||
|
delay !== null
|
||||||
|
) {
|
||||||
|
await sleep(10 * (1 << delay));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw 'unreachable';
|
||||||
|
}
|
||||||
|
|
||||||
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
|
|
@ -5,31 +5,32 @@ 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 => {
|
||||||
|
client.onNotification(
|
||||||
|
'rust-analyzer/publishDecorations',
|
||||||
|
(params: PublishDecorationsParams) => {
|
||||||
|
if (!ctx.config.highlightingOn) return;
|
||||||
|
|
||||||
ctx.onNotification(
|
const targetEditor = vscode.window.visibleTextEditors.find(
|
||||||
'rust-analyzer/publishDecorations',
|
editor => {
|
||||||
(params: PublishDecorationsParams) => {
|
const unescapedUri = unescape(
|
||||||
if (!ctx.config.highlightingOn) return;
|
editor.document.uri.toString(),
|
||||||
|
);
|
||||||
|
// Unescaped URI looks like:
|
||||||
|
// file:///c:/Workspace/ra-test/src/main.rs
|
||||||
|
return unescapedUri === params.uri;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (!targetEditor) return;
|
||||||
|
|
||||||
const targetEditor = vscode.window.visibleTextEditors.find(
|
highlighter.setHighlights(targetEditor, params.decorations);
|
||||||
editor => {
|
},
|
||||||
const unescapedUri = unescape(
|
);
|
||||||
editor.document.uri.toString(),
|
})
|
||||||
);
|
|
||||||
// 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(
|
vscode.workspace.onDidChangeConfiguration(
|
||||||
_ => highlighter.removeHighlights(),
|
_ => highlighter.removeHighlights(),
|
||||||
|
@ -40,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,
|
||||||
);
|
);
|
||||||
|
@ -103,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
|
||||||
|
@ -135,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,
|
||||||
|
|
|
@ -3,7 +3,6 @@ import * as vscode from 'vscode';
|
||||||
import * as commands from './commands';
|
import * as commands from './commands';
|
||||||
import { activateInlayHints } from './inlay_hints';
|
import { activateInlayHints } from './inlay_hints';
|
||||||
import { activateStatusDisplay } 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';
|
||||||
|
|
||||||
|
@ -21,6 +20,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);
|
||||||
|
@ -30,38 +30,17 @@ 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 startServer = () => Server.start(ctx.config);
|
activateHighlighting(ctx);
|
||||||
const reloadCommand = () => reloadServer(startServer);
|
activateInlayHints(ctx);
|
||||||
|
|
||||||
vscode.commands.registerCommand('rust-analyzer.reload', reloadCommand);
|
|
||||||
|
|
||||||
// Start the language server, finally!
|
// Start the language server, finally!
|
||||||
try {
|
try {
|
||||||
await startServer();
|
await ctx.restartServer();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
vscode.window.showErrorMessage(e.message);
|
vscode.window.showErrorMessage(e.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
activateStatusDisplay(ctx);
|
|
||||||
activateHighlighting(ctx);
|
|
||||||
|
|
||||||
if (ctx.config.displayInlayHints) {
|
|
||||||
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,96 +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 {
|
|
||||||
static config: Config;
|
|
||||||
public static client: lc.LanguageClient;
|
|
||||||
|
|
||||||
public static async start(config: Config) {
|
|
||||||
// '.' 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.config = config;
|
|
||||||
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.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;
|
||||||
|
|
|
@ -7,7 +7,9 @@ const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '
|
||||||
export function activateStatusDisplay(ctx: Ctx) {
|
export function activateStatusDisplay(ctx: Ctx) {
|
||||||
const statusDisplay = new StatusDisplay(ctx.config.cargoWatchOptions.command);
|
const statusDisplay = new StatusDisplay(ctx.config.cargoWatchOptions.command);
|
||||||
ctx.pushCleanup(statusDisplay);
|
ctx.pushCleanup(statusDisplay);
|
||||||
ctx.onNotification('$/progress', params => statusDisplay.handleProgressNotification(params));
|
ctx.onDidRestart(client => {
|
||||||
|
client.onNotification('$/progress', params => statusDisplay.handleProgressNotification(params));
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
class StatusDisplay implements vscode.Disposable {
|
class StatusDisplay implements vscode.Disposable {
|
||||||
|
|
Loading…
Reference in a new issue