Skip to content

Commit 6d23140

Browse files
bors[bot]matklad
andauthored
Merge #2709
2709: Work around synchrnonisation issue r=matklad a=matklad Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
2 parents 1327aed + 6368b40 commit 6d23140

File tree

13 files changed

+235
-223
lines changed

13 files changed

+235
-223
lines changed

editors/code/src/client.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { homedir } from 'os';
2+
import * as lc from 'vscode-languageclient';
3+
4+
import { window, workspace } from 'vscode';
5+
import { Config } from './config';
6+
7+
export function createClient(config: Config): lc.LanguageClient {
8+
// '.' Is the fallback if no folder is open
9+
// 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.
10+
let folder: string = '.';
11+
if (workspace.workspaceFolders !== undefined) {
12+
folder = workspace.workspaceFolders[0].uri.fsPath.toString();
13+
}
14+
15+
const command = expandPathResolving(config.raLspServerPath);
16+
const run: lc.Executable = {
17+
command,
18+
options: { cwd: folder },
19+
};
20+
const serverOptions: lc.ServerOptions = {
21+
run,
22+
debug: run,
23+
};
24+
const traceOutputChannel = window.createOutputChannel(
25+
'Rust Analyzer Language Server Trace',
26+
);
27+
const clientOptions: lc.LanguageClientOptions = {
28+
documentSelector: [{ scheme: 'file', language: 'rust' }],
29+
initializationOptions: {
30+
publishDecorations: true,
31+
lruCapacity: config.lruCapacity,
32+
maxInlayHintLength: config.maxInlayHintLength,
33+
cargoWatchEnable: config.cargoWatchOptions.enable,
34+
cargoWatchArgs: config.cargoWatchOptions.arguments,
35+
cargoWatchCommand: config.cargoWatchOptions.command,
36+
cargoWatchAllTargets:
37+
config.cargoWatchOptions.allTargets,
38+
excludeGlobs: config.excludeGlobs,
39+
useClientWatching: config.useClientWatching,
40+
featureFlags: config.featureFlags,
41+
withSysroot: config.withSysroot,
42+
cargoFeatures: config.cargoFeatures,
43+
},
44+
traceOutputChannel,
45+
};
46+
47+
const res = new lc.LanguageClient(
48+
'rust-analyzer',
49+
'Rust Analyzer Language Server',
50+
serverOptions,
51+
clientOptions,
52+
);
53+
54+
// HACK: This is an awful way of filtering out the decorations notifications
55+
// However, pending proper support, this is the most effecitve approach
56+
// Proper support for this would entail a change to vscode-languageclient to allow not notifying on certain messages
57+
// Or the ability to disable the serverside component of highlighting (but this means that to do tracing we need to disable hihlighting)
58+
// This also requires considering our settings strategy, which is work which needs doing
59+
// @ts-ignore The tracer is private to vscode-languageclient, but we need access to it to not log publishDecorations requests
60+
res._tracer = {
61+
log: (messageOrDataObject: string | any, data?: string) => {
62+
if (typeof messageOrDataObject === 'string') {
63+
if (
64+
messageOrDataObject.includes(
65+
'rust-analyzer/publishDecorations',
66+
) ||
67+
messageOrDataObject.includes(
68+
'rust-analyzer/decorationsRequest',
69+
)
70+
) {
71+
// Don't log publish decorations requests
72+
} else {
73+
// @ts-ignore This is just a utility function
74+
res.logTrace(messageOrDataObject, data);
75+
}
76+
} else {
77+
// @ts-ignore
78+
res.logObjectTrace(messageOrDataObject);
79+
}
80+
},
81+
};
82+
res.registerProposedFeatures()
83+
return res;
84+
}
85+
function expandPathResolving(path: string) {
86+
if (path.startsWith('~/')) {
87+
return path.replace('~', homedir());
88+
}
89+
return path;
90+
}

editors/code/src/commands/analyzer_status.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,10 @@ class TextDocumentContentProvider
4949
_uri: vscode.Uri,
5050
): vscode.ProviderResult<string> {
5151
const editor = vscode.window.activeTextEditor;
52-
if (editor == null) return '';
52+
const client = this.ctx.client
53+
if (!editor || !client) return '';
5354

54-
return this.ctx.client.sendRequest<string>(
55+
return client.sendRequest<string>(
5556
'rust-analyzer/analyzerStatus',
5657
null,
5758
);

editors/code/src/commands/expand_macro.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,15 @@ class TextDocumentContentProvider
5252

5353
async provideTextDocumentContent(_uri: vscode.Uri): Promise<string> {
5454
const editor = vscode.window.activeTextEditor;
55-
if (editor == null) return '';
55+
const client = this.ctx.client
56+
if (!editor || !client) return '';
5657

5758
const position = editor.selection.active;
5859
const request: lc.TextDocumentPositionParams = {
5960
textDocument: { uri: editor.document.uri.toString() },
6061
position,
6162
};
62-
const expanded = await this.ctx.client.sendRequest<ExpandedMacro>(
63+
const expanded = await client.sendRequest<ExpandedMacro>(
6364
'rust-analyzer/expandMacro',
6465
request,
6566
);

editors/code/src/commands/index.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,21 @@ import { run, runSingle } from './runnables';
1515

1616
function collectGarbage(ctx: Ctx): Cmd {
1717
return async () => {
18-
ctx.client.sendRequest<null>('rust-analyzer/collectGarbage', null);
18+
ctx.client?.sendRequest<null>('rust-analyzer/collectGarbage', null);
1919
};
2020
}
2121

2222
function showReferences(ctx: Ctx): Cmd {
2323
return (uri: string, position: lc.Position, locations: lc.Location[]) => {
24-
vscode.commands.executeCommand(
25-
'editor.action.showReferences',
26-
vscode.Uri.parse(uri),
27-
ctx.client.protocol2CodeConverter.asPosition(position),
28-
locations.map(ctx.client.protocol2CodeConverter.asLocation),
29-
);
24+
let client = ctx.client;
25+
if (client) {
26+
vscode.commands.executeCommand(
27+
'editor.action.showReferences',
28+
vscode.Uri.parse(uri),
29+
client.protocol2CodeConverter.asPosition(position),
30+
locations.map(client.protocol2CodeConverter.asLocation),
31+
);
32+
}
3033
};
3134
}
3235

@@ -36,6 +39,13 @@ function applySourceChange(ctx: Ctx): Cmd {
3639
}
3740
}
3841

42+
function reload(ctx: Ctx): Cmd {
43+
return async () => {
44+
vscode.window.showInformationMessage('Reloading rust-analyzer...');
45+
await ctx.restartServer();
46+
}
47+
}
48+
3949
export {
4050
analyzerStatus,
4151
expandMacro,
@@ -49,4 +59,5 @@ export {
4959
runSingle,
5060
showReferences,
5161
applySourceChange,
62+
reload
5263
};

editors/code/src/commands/join_lines.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ import { applySourceChange, SourceChange } from '../source_change';
66
export function joinLines(ctx: Ctx): Cmd {
77
return async () => {
88
const editor = ctx.activeRustEditor;
9-
if (!editor) return;
9+
const client = ctx.client;
10+
if (!editor || !client) return;
1011

1112
const request: JoinLinesParams = {
12-
range: ctx.client.code2ProtocolConverter.asRange(editor.selection),
13+
range: client.code2ProtocolConverter.asRange(editor.selection),
1314
textDocument: { uri: editor.document.uri.toString() },
1415
};
15-
const change = await ctx.client.sendRequest<SourceChange>(
16+
const change = await client.sendRequest<SourceChange>(
1617
'rust-analyzer/joinLines',
1718
request,
1819
);

editors/code/src/config.ts

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,25 @@ export interface CargoFeatures {
1616
}
1717

1818
export class Config {
19-
public highlightingOn = true;
20-
public rainbowHighlightingOn = false;
21-
public enableEnhancedTyping = true;
22-
public raLspServerPath = RA_LSP_DEBUG || 'ra_lsp_server';
23-
public lruCapacity: null | number = null;
24-
public displayInlayHints = true;
25-
public maxInlayHintLength: null | number = null;
26-
public excludeGlobs = [];
27-
public useClientWatching = true;
28-
public featureFlags = {};
19+
highlightingOn = true;
20+
rainbowHighlightingOn = false;
21+
enableEnhancedTyping = true;
22+
raLspServerPath = RA_LSP_DEBUG || 'ra_lsp_server';
23+
lruCapacity: null | number = null;
24+
displayInlayHints = true;
25+
maxInlayHintLength: null | number = null;
26+
excludeGlobs = [];
27+
useClientWatching = true;
28+
featureFlags = {};
2929
// for internal use
30-
public withSysroot: null | boolean = null;
31-
public cargoWatchOptions: CargoWatchOptions = {
30+
withSysroot: null | boolean = null;
31+
cargoWatchOptions: CargoWatchOptions = {
3232
enable: true,
3333
arguments: [],
3434
command: '',
3535
allTargets: true,
3636
};
37-
public cargoFeatures: CargoFeatures = {
37+
cargoFeatures: CargoFeatures = {
3838
noDefaultFeatures: false,
3939
allFeatures: true,
4040
features: [],
@@ -43,14 +43,12 @@ export class Config {
4343
private prevEnhancedTyping: null | boolean = null;
4444
private prevCargoFeatures: null | CargoFeatures = null;
4545

46-
constructor() {
47-
vscode.workspace.onDidChangeConfiguration(_ =>
48-
this.userConfigChanged(),
49-
);
50-
this.userConfigChanged();
46+
constructor(ctx: vscode.ExtensionContext) {
47+
vscode.workspace.onDidChangeConfiguration(_ => this.refresh(), ctx.subscriptions);
48+
this.refresh();
5149
}
5250

53-
public userConfigChanged() {
51+
private refresh() {
5452
const config = vscode.workspace.getConfiguration('rust-analyzer');
5553

5654
let requireReloadMessage = null;

editors/code/src/ctx.ts

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,38 @@
11
import * as vscode from 'vscode';
22
import * as lc from 'vscode-languageclient';
3-
import { Server } from './server';
43
import { Config } from './config';
4+
import { createClient } from './client'
55

66
export class Ctx {
7+
readonly config: Config;
8+
// Because we have "reload server" action, various listeners **will** face a
9+
// situation where the client is not ready yet, and should be prepared to
10+
// deal with it.
11+
//
12+
// Ideally, this should be replaced with async getter though.
13+
client: lc.LanguageClient | null = null
714
private extCtx: vscode.ExtensionContext;
15+
private onDidRestartHooks: Array<(client: lc.LanguageClient) => void> = [];
816

917
constructor(extCtx: vscode.ExtensionContext) {
18+
this.config = new Config(extCtx)
1019
this.extCtx = extCtx;
1120
}
1221

13-
get client(): lc.LanguageClient {
14-
return Server.client;
15-
}
22+
async restartServer() {
23+
let old = this.client;
24+
if (old) {
25+
await old.stop()
26+
}
27+
this.client = null;
28+
const client = createClient(this.config);
29+
this.pushCleanup(client.start());
30+
await client.onReady();
1631

17-
get config(): Config {
18-
return Server.config;
32+
this.client = client
33+
for (const hook of this.onDidRestartHooks) {
34+
hook(client)
35+
}
1936
}
2037

2138
get activeRustEditor(): vscode.TextEditor | undefined {
@@ -62,30 +79,34 @@ export class Ctx {
6279
this.extCtx.subscriptions.push(d);
6380
}
6481

65-
async sendRequestWithRetry<R>(
66-
method: string,
67-
param: any,
68-
token?: vscode.CancellationToken,
69-
): Promise<R> {
70-
await this.client.onReady();
71-
for (const delay of [2, 4, 6, 8, 10, null]) {
72-
try {
73-
return await (token ? this.client.sendRequest(method, param, token) : this.client.sendRequest(method, param));
74-
} catch (e) {
75-
if (
76-
e.code === lc.ErrorCodes.ContentModified &&
77-
delay !== null
78-
) {
79-
await sleep(10 * (1 << delay));
80-
continue;
81-
}
82-
throw e;
83-
}
84-
}
85-
throw 'unreachable';
82+
onDidRestart(hook: (client: lc.LanguageClient) => void) {
83+
this.onDidRestartHooks.push(hook)
8684
}
8785
}
8886

8987
export type Cmd = (...args: any[]) => any;
9088

89+
export async function sendRequestWithRetry<R>(
90+
client: lc.LanguageClient,
91+
method: string,
92+
param: any,
93+
token?: vscode.CancellationToken,
94+
): Promise<R> {
95+
for (const delay of [2, 4, 6, 8, 10, null]) {
96+
try {
97+
return await (token ? client.sendRequest(method, param, token) : client.sendRequest(method, param));
98+
} catch (e) {
99+
if (
100+
e.code === lc.ErrorCodes.ContentModified &&
101+
delay !== null
102+
) {
103+
await sleep(10 * (1 << delay));
104+
continue;
105+
}
106+
throw e;
107+
}
108+
}
109+
throw 'unreachable';
110+
}
111+
91112
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

0 commit comments

Comments
 (0)