aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--editors/code/src/client.ts90
-rw-r--r--editors/code/src/commands/analyzer_status.ts5
-rw-r--r--editors/code/src/commands/expand_macro.ts5
-rw-r--r--editors/code/src/commands/index.ts25
-rw-r--r--editors/code/src/commands/join_lines.ts7
-rw-r--r--editors/code/src/ctx.ts76
-rw-r--r--editors/code/src/highlighting.ts56
-rw-r--r--editors/code/src/inlay_hints.ts11
-rw-r--r--editors/code/src/main.ts35
-rw-r--r--editors/code/src/server.ts96
-rw-r--r--editors/code/src/source_change.ts9
-rw-r--r--editors/code/src/status_display.ts4
12 files changed, 218 insertions, 201 deletions
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
new file mode 100644
index 000000000..94948b10f
--- /dev/null
+++ b/editors/code/src/client.ts
@@ -0,0 +1,90 @@
1import { homedir } from 'os';
2import * as lc from 'vscode-languageclient';
3
4import { window, workspace } from 'vscode';
5import { Config } from './config';
6
7export 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}
85function expandPathResolving(path: string) {
86 if (path.startsWith('~/')) {
87 return path.replace('~', homedir());
88 }
89 return path;
90}
diff --git a/editors/code/src/commands/analyzer_status.ts b/editors/code/src/commands/analyzer_status.ts
index 2c8362286..cf37dc6f0 100644
--- a/editors/code/src/commands/analyzer_status.ts
+++ b/editors/code/src/commands/analyzer_status.ts
@@ -49,9 +49,10 @@ class TextDocumentContentProvider
49 _uri: vscode.Uri, 49 _uri: vscode.Uri,
50 ): vscode.ProviderResult<string> { 50 ): vscode.ProviderResult<string> {
51 const editor = vscode.window.activeTextEditor; 51 const editor = vscode.window.activeTextEditor;
52 if (editor == null) return ''; 52 const client = this.ctx.client
53 if (!editor || !client) return '';
53 54
54 return this.ctx.client.sendRequest<string>( 55 return client.sendRequest<string>(
55 'rust-analyzer/analyzerStatus', 56 'rust-analyzer/analyzerStatus',
56 null, 57 null,
57 ); 58 );
diff --git a/editors/code/src/commands/expand_macro.ts b/editors/code/src/commands/expand_macro.ts
index da208257a..472f43b8d 100644
--- a/editors/code/src/commands/expand_macro.ts
+++ b/editors/code/src/commands/expand_macro.ts
@@ -52,14 +52,15 @@ class TextDocumentContentProvider
52 52
53 async provideTextDocumentContent(_uri: vscode.Uri): Promise<string> { 53 async provideTextDocumentContent(_uri: vscode.Uri): Promise<string> {
54 const editor = vscode.window.activeTextEditor; 54 const editor = vscode.window.activeTextEditor;
55 if (editor == null) return ''; 55 const client = this.ctx.client
56 if (!editor || !client) return '';
56 57
57 const position = editor.selection.active; 58 const position = editor.selection.active;
58 const request: lc.TextDocumentPositionParams = { 59 const request: lc.TextDocumentPositionParams = {
59 textDocument: { uri: editor.document.uri.toString() }, 60 textDocument: { uri: editor.document.uri.toString() },
60 position, 61 position,
61 }; 62 };
62 const expanded = await this.ctx.client.sendRequest<ExpandedMacro>( 63 const expanded = await client.sendRequest<ExpandedMacro>(
63 'rust-analyzer/expandMacro', 64 'rust-analyzer/expandMacro',
64 request, 65 request,
65 ); 66 );
diff --git a/editors/code/src/commands/index.ts b/editors/code/src/commands/index.ts
index c28709c8a..4431fdcf6 100644
--- a/editors/code/src/commands/index.ts
+++ b/editors/code/src/commands/index.ts
@@ -15,18 +15,21 @@ import { run, runSingle } from './runnables';
15 15
16function collectGarbage(ctx: Ctx): Cmd { 16function collectGarbage(ctx: Ctx): Cmd {
17 return async () => { 17 return async () => {
18 ctx.client.sendRequest<null>('rust-analyzer/collectGarbage', null); 18 ctx.client?.sendRequest<null>('rust-analyzer/collectGarbage', null);
19 }; 19 };
20} 20}
21 21
22function showReferences(ctx: Ctx): Cmd { 22function showReferences(ctx: Ctx): Cmd {
23 return (uri: string, position: lc.Position, locations: lc.Location[]) => { 23 return (uri: string, position: lc.Position, locations: lc.Location[]) => {
24 vscode.commands.executeCommand( 24 let client = ctx.client;
25 'editor.action.showReferences', 25 if (client) {
26 vscode.Uri.parse(uri), 26 vscode.commands.executeCommand(
27 ctx.client.protocol2CodeConverter.asPosition(position), 27 'editor.action.showReferences',
28 locations.map(ctx.client.protocol2CodeConverter.asLocation), 28 vscode.Uri.parse(uri),
29 ); 29 client.protocol2CodeConverter.asPosition(position),
30 locations.map(client.protocol2CodeConverter.asLocation),
31 );
32 }
30 }; 33 };
31} 34}
32 35
@@ -36,6 +39,13 @@ function applySourceChange(ctx: Ctx): Cmd {
36 } 39 }
37} 40}
38 41
42function reload(ctx: Ctx): Cmd {
43 return async () => {
44 vscode.window.showInformationMessage('Reloading rust-analyzer...');
45 await ctx.restartServer();
46 }
47}
48
39export { 49export {
40 analyzerStatus, 50 analyzerStatus,
41 expandMacro, 51 expandMacro,
@@ -49,4 +59,5 @@ export {
49 runSingle, 59 runSingle,
50 showReferences, 60 showReferences,
51 applySourceChange, 61 applySourceChange,
62 reload
52}; 63};
diff --git a/editors/code/src/commands/join_lines.ts b/editors/code/src/commands/join_lines.ts
index f4f902cf9..7b08c3255 100644
--- a/editors/code/src/commands/join_lines.ts
+++ b/editors/code/src/commands/join_lines.ts
@@ -6,13 +6,14 @@ import { applySourceChange, SourceChange } from '../source_change';
6export function joinLines(ctx: Ctx): Cmd { 6export function joinLines(ctx: Ctx): Cmd {
7 return async () => { 7 return async () => {
8 const editor = ctx.activeRustEditor; 8 const editor = ctx.activeRustEditor;
9 if (!editor) return; 9 const client = ctx.client;
10 if (!editor || !client) return;
10 11
11 const request: JoinLinesParams = { 12 const request: JoinLinesParams = {
12 range: ctx.client.code2ProtocolConverter.asRange(editor.selection), 13 range: client.code2ProtocolConverter.asRange(editor.selection),
13 textDocument: { uri: editor.document.uri.toString() }, 14 textDocument: { uri: editor.document.uri.toString() },
14 }; 15 };
15 const change = await ctx.client.sendRequest<SourceChange>( 16 const change = await client.sendRequest<SourceChange>(
16 'rust-analyzer/joinLines', 17 'rust-analyzer/joinLines',
17 request, 18 request,
18 ); 19 );
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts
index 393d6a602..13988056a 100644
--- a/editors/code/src/ctx.ts
+++ b/editors/code/src/ctx.ts
@@ -1,19 +1,38 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient'; 2import * as lc from 'vscode-languageclient';
3import { Server } from './server';
4import { Config } from './config'; 3import { Config } from './config';
4import { createClient } from './client'
5 5
6export class Ctx { 6export class Ctx {
7 readonly config: Config; 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
8 private extCtx: vscode.ExtensionContext; 14 private extCtx: vscode.ExtensionContext;
15 private onDidRestartHooks: Array<(client: lc.LanguageClient) => void> = [];
9 16
10 constructor(extCtx: vscode.ExtensionContext) { 17 constructor(extCtx: vscode.ExtensionContext) {
11 this.config = new Config(extCtx) 18 this.config = new Config(extCtx)
12 this.extCtx = extCtx; 19 this.extCtx = extCtx;
13 } 20 }
14 21
15 get client(): lc.LanguageClient { 22 async restartServer() {
16 return Server.client; 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();
31
32 this.client = client
33 for (const hook of this.onDidRestartHooks) {
34 hook(client)
35 }
17 } 36 }
18 37
19 get activeRustEditor(): vscode.TextEditor | undefined { 38 get activeRustEditor(): vscode.TextEditor | undefined {
@@ -60,35 +79,34 @@ export class Ctx {
60 this.extCtx.subscriptions.push(d); 79 this.extCtx.subscriptions.push(d);
61 } 80 }
62 81
63 async sendRequestWithRetry<R>( 82 onDidRestart(hook: (client: lc.LanguageClient) => void) {
64 method: string, 83 this.onDidRestartHooks.push(hook)
65 param: any,
66 token?: vscode.CancellationToken,
67 ): Promise<R> {
68 await this.client.onReady();
69 for (const delay of [2, 4, 6, 8, 10, null]) {
70 try {
71 return await (token ? this.client.sendRequest(method, param, token) : this.client.sendRequest(method, param));
72 } catch (e) {
73 if (
74 e.code === lc.ErrorCodes.ContentModified &&
75 delay !== null
76 ) {
77 await sleep(10 * (1 << delay));
78 continue;
79 }
80 throw e;
81 }
82 }
83 throw 'unreachable';
84 }
85
86 onNotification(method: string, handler: lc.GenericNotificationHandler) {
87 this.client.onReady()
88 .then(() => this.client.onNotification(method, handler))
89 } 84 }
90} 85}
91 86
92export type Cmd = (...args: any[]) => any; 87export type Cmd = (...args: any[]) => any;
93 88
89export 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
94const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); 112const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts
index d4e961b5b..f9d2e9d90 100644
--- a/editors/code/src/highlighting.ts
+++ b/editors/code/src/highlighting.ts
@@ -5,31 +5,32 @@ const seedrandom = seedrandom_; // https://github.com/jvandemo/generator-angular
5 5
6import { ColorTheme, TextMateRuleSettings } from './color_theme'; 6import { ColorTheme, TextMateRuleSettings } from './color_theme';
7 7
8import { Ctx } from './ctx'; 8import { Ctx, sendRequestWithRetry } from './ctx';
9 9
10export function activateHighlighting(ctx: Ctx) { 10export function activateHighlighting(ctx: Ctx) {
11 const highlighter = new Highlighter(ctx); 11 const highlighter = new Highlighter(ctx);
12 12 ctx.onDidRestart(client => {
13 ctx.onNotification( 13 client.onNotification(
14 'rust-analyzer/publishDecorations', 14 'rust-analyzer/publishDecorations',
15 (params: PublishDecorationsParams) => { 15 (params: PublishDecorationsParams) => {
16 if (!ctx.config.highlightingOn) return; 16 if (!ctx.config.highlightingOn) return;
17 17
18 const targetEditor = vscode.window.visibleTextEditors.find( 18 const targetEditor = vscode.window.visibleTextEditors.find(
19 editor => { 19 editor => {
20 const unescapedUri = unescape( 20 const unescapedUri = unescape(
21 editor.document.uri.toString(), 21 editor.document.uri.toString(),
22 ); 22 );
23 // Unescaped URI looks like: 23 // Unescaped URI looks like:
24 // file:///c:/Workspace/ra-test/src/main.rs 24 // file:///c:/Workspace/ra-test/src/main.rs
25 return unescapedUri === params.uri; 25 return unescapedUri === params.uri;
26 }, 26 },
27 ); 27 );
28 if (!targetEditor) return; 28 if (!targetEditor) return;
29 29
30 highlighter.setHighlights(targetEditor, params.decorations); 30 highlighter.setHighlights(targetEditor, params.decorations);
31 }, 31 },
32 ); 32 );
33 })
33 34
34 vscode.workspace.onDidChangeConfiguration( 35 vscode.workspace.onDidChangeConfiguration(
35 _ => highlighter.removeHighlights(), 36 _ => highlighter.removeHighlights(),
@@ -40,11 +41,14 @@ export function activateHighlighting(ctx: Ctx) {
40 async (editor: vscode.TextEditor | undefined) => { 41 async (editor: vscode.TextEditor | undefined) => {
41 if (!editor || editor.document.languageId !== 'rust') return; 42 if (!editor || editor.document.languageId !== 'rust') return;
42 if (!ctx.config.highlightingOn) return; 43 if (!ctx.config.highlightingOn) return;
44 let client = ctx.client;
45 if (!client) return;
43 46
44 const params: lc.TextDocumentIdentifier = { 47 const params: lc.TextDocumentIdentifier = {
45 uri: editor.document.uri.toString(), 48 uri: editor.document.uri.toString(),
46 }; 49 };
47 const decorations = await ctx.sendRequestWithRetry<Decoration[]>( 50 const decorations = await sendRequestWithRetry<Decoration[]>(
51 client,
48 'rust-analyzer/decorationsRequest', 52 'rust-analyzer/decorationsRequest',
49 params, 53 params,
50 ); 54 );
@@ -103,6 +107,8 @@ class Highlighter {
103 } 107 }
104 108
105 public setHighlights(editor: vscode.TextEditor, highlights: Decoration[]) { 109 public setHighlights(editor: vscode.TextEditor, highlights: Decoration[]) {
110 let client = this.ctx.client;
111 if (!client) return;
106 // Initialize decorations if necessary 112 // Initialize decorations if necessary
107 // 113 //
108 // Note: decoration objects need to be kept around so we can dispose them 114 // Note: decoration objects need to be kept around so we can dispose them
@@ -135,13 +141,13 @@ class Highlighter {
135 colorfulIdents 141 colorfulIdents
136 .get(d.bindingHash)![0] 142 .get(d.bindingHash)![0]
137 .push( 143 .push(
138 this.ctx.client.protocol2CodeConverter.asRange(d.range), 144 client.protocol2CodeConverter.asRange(d.range),
139 ); 145 );
140 } else { 146 } else {
141 byTag 147 byTag
142 .get(d.tag)! 148 .get(d.tag)!
143 .push( 149 .push(
144 this.ctx.client.protocol2CodeConverter.asRange(d.range), 150 client.protocol2CodeConverter.asRange(d.range),
145 ); 151 );
146 } 152 }
147 } 153 }
diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts
index b6eb70168..e74d6996f 100644
--- a/editors/code/src/inlay_hints.ts
+++ b/editors/code/src/inlay_hints.ts
@@ -1,7 +1,7 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient'; 2import * as lc from 'vscode-languageclient';
3 3
4import { Ctx } from './ctx'; 4import { Ctx, sendRequestWithRetry } from './ctx';
5 5
6export function activateInlayHints(ctx: Ctx) { 6export function activateInlayHints(ctx: Ctx) {
7 const hintsUpdater = new HintsUpdater(ctx); 7 const hintsUpdater = new HintsUpdater(ctx);
@@ -19,9 +19,7 @@ export function activateInlayHints(ctx: Ctx) {
19 hintsUpdater.setEnabled(ctx.config.displayInlayHints); 19 hintsUpdater.setEnabled(ctx.config.displayInlayHints);
20 }, ctx.subscriptions); 20 }, ctx.subscriptions);
21 21
22 // XXX: don't await here; 22 ctx.onDidRestart(_ => hintsUpdater.setEnabled(ctx.config.displayInlayHints))
23 // Who knows what happens if an exception is thrown here...
24 hintsUpdater.refresh();
25} 23}
26 24
27interface InlayHintsParams { 25interface InlayHintsParams {
@@ -97,6 +95,8 @@ class HintsUpdater {
97 } 95 }
98 96
99 private async queryHints(documentUri: string): Promise<InlayHint[] | null> { 97 private async queryHints(documentUri: string): Promise<InlayHint[] | null> {
98 let client = this.ctx.client;
99 if (!client) return null
100 const request: InlayHintsParams = { 100 const request: InlayHintsParams = {
101 textDocument: { uri: documentUri }, 101 textDocument: { uri: documentUri },
102 }; 102 };
@@ -105,7 +105,8 @@ class HintsUpdater {
105 if (prev) prev.cancel(); 105 if (prev) prev.cancel();
106 this.pending.set(documentUri, tokenSource); 106 this.pending.set(documentUri, tokenSource);
107 try { 107 try {
108 return await this.ctx.sendRequestWithRetry<InlayHint[] | null>( 108 return await sendRequestWithRetry<InlayHint[] | null>(
109 client,
109 'rust-analyzer/inlayHints', 110 'rust-analyzer/inlayHints',
110 request, 111 request,
111 tokenSource.token, 112 tokenSource.token,
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 3d9107927..22450060b 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -3,7 +3,6 @@ import * as vscode from 'vscode';
3import * as commands from './commands'; 3import * as commands from './commands';
4import { activateInlayHints } from './inlay_hints'; 4import { activateInlayHints } from './inlay_hints';
5import { activateStatusDisplay } from './status_display'; 5import { activateStatusDisplay } from './status_display';
6import { Server } from './server';
7import { Ctx } from './ctx'; 6import { Ctx } from './ctx';
8import { activateHighlighting } from './highlighting'; 7import { activateHighlighting } from './highlighting';
9 8
@@ -21,6 +20,7 @@ export async function activate(context: vscode.ExtensionContext) {
21 ctx.registerCommand('syntaxTree', commands.syntaxTree); 20 ctx.registerCommand('syntaxTree', commands.syntaxTree);
22 ctx.registerCommand('expandMacro', commands.expandMacro); 21 ctx.registerCommand('expandMacro', commands.expandMacro);
23 ctx.registerCommand('run', commands.run); 22 ctx.registerCommand('run', commands.run);
23 ctx.registerCommand('reload', commands.reload);
24 24
25 // Internal commands which are invoked by the server. 25 // Internal commands which are invoked by the server.
26 ctx.registerCommand('runSingle', commands.runSingle); 26 ctx.registerCommand('runSingle', commands.runSingle);
@@ -30,38 +30,17 @@ export async function activate(context: vscode.ExtensionContext) {
30 if (ctx.config.enableEnhancedTyping) { 30 if (ctx.config.enableEnhancedTyping) {
31 ctx.overrideCommand('type', commands.onEnter); 31 ctx.overrideCommand('type', commands.onEnter);
32 } 32 }
33 33 activateStatusDisplay(ctx);
34 const startServer = () => Server.start(ctx.config); 34 activateHighlighting(ctx);
35 const reloadCommand = () => reloadServer(startServer); 35 activateInlayHints(ctx);
36
37 vscode.commands.registerCommand('rust-analyzer.reload', reloadCommand);
38
39 // Start the language server, finally! 36 // Start the language server, finally!
40 try { 37 try {
41 await startServer(); 38 await ctx.restartServer();
42 } catch (e) { 39 } catch (e) {
43 vscode.window.showErrorMessage(e.message); 40 vscode.window.showErrorMessage(e.message);
44 } 41 }
45
46 activateStatusDisplay(ctx);
47 activateHighlighting(ctx);
48
49 if (ctx.config.displayInlayHints) {
50 activateInlayHints(ctx);
51 }
52} 42}
53 43
54export function deactivate(): Thenable<void> { 44export async function deactivate() {
55 if (!Server.client) { 45 await ctx?.client?.stop();
56 return Promise.resolve();
57 }
58 return Server.client.stop();
59}
60
61async function reloadServer(startServer: () => Promise<void>) {
62 if (Server.client != null) {
63 vscode.window.showInformationMessage('Reloading rust-analyzer...');
64 await Server.client.stop();
65 await startServer();
66 }
67} 46}
diff --git a/editors/code/src/server.ts b/editors/code/src/server.ts
deleted file mode 100644
index ab9f3bfa6..000000000
--- a/editors/code/src/server.ts
+++ /dev/null
@@ -1,96 +0,0 @@
1import { homedir } from 'os';
2import * as lc from 'vscode-languageclient';
3
4import { window, workspace } from 'vscode';
5import { Config } from './config';
6
7function expandPathResolving(path: string) {
8 if (path.startsWith('~/')) {
9 return path.replace('~', homedir());
10 }
11 return path;
12}
13
14export class Server {
15 static config: Config;
16 public static client: lc.LanguageClient;
17
18 public static async start(config: Config) {
19 // '.' Is the fallback if no folder is open
20 // 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.
21 let folder: string = '.';
22 if (workspace.workspaceFolders !== undefined) {
23 folder = workspace.workspaceFolders[0].uri.fsPath.toString();
24 }
25
26 this.config = config;
27 const command = expandPathResolving(this.config.raLspServerPath);
28 const run: lc.Executable = {
29 command,
30 options: { cwd: folder },
31 };
32 const serverOptions: lc.ServerOptions = {
33 run,
34 debug: run,
35 };
36 const traceOutputChannel = window.createOutputChannel(
37 'Rust Analyzer Language Server Trace',
38 );
39 const clientOptions: lc.LanguageClientOptions = {
40 documentSelector: [{ scheme: 'file', language: 'rust' }],
41 initializationOptions: {
42 publishDecorations: true,
43 lruCapacity: Server.config.lruCapacity,
44 maxInlayHintLength: Server.config.maxInlayHintLength,
45 cargoWatchEnable: Server.config.cargoWatchOptions.enable,
46 cargoWatchArgs: Server.config.cargoWatchOptions.arguments,
47 cargoWatchCommand: Server.config.cargoWatchOptions.command,
48 cargoWatchAllTargets:
49 Server.config.cargoWatchOptions.allTargets,
50 excludeGlobs: Server.config.excludeGlobs,
51 useClientWatching: Server.config.useClientWatching,
52 featureFlags: Server.config.featureFlags,
53 withSysroot: Server.config.withSysroot,
54 cargoFeatures: Server.config.cargoFeatures,
55 },
56 traceOutputChannel,
57 };
58
59 Server.client = new lc.LanguageClient(
60 'rust-analyzer',
61 'Rust Analyzer Language Server',
62 serverOptions,
63 clientOptions,
64 );
65 // HACK: This is an awful way of filtering out the decorations notifications
66 // However, pending proper support, this is the most effecitve approach
67 // Proper support for this would entail a change to vscode-languageclient to allow not notifying on certain messages
68 // Or the ability to disable the serverside component of highlighting (but this means that to do tracing we need to disable hihlighting)
69 // This also requires considering our settings strategy, which is work which needs doing
70 // @ts-ignore The tracer is private to vscode-languageclient, but we need access to it to not log publishDecorations requests
71 Server.client._tracer = {
72 log: (messageOrDataObject: string | any, data?: string) => {
73 if (typeof messageOrDataObject === 'string') {
74 if (
75 messageOrDataObject.includes(
76 'rust-analyzer/publishDecorations',
77 ) ||
78 messageOrDataObject.includes(
79 'rust-analyzer/decorationsRequest',
80 )
81 ) {
82 // Don't log publish decorations requests
83 } else {
84 // @ts-ignore This is just a utility function
85 Server.client.logTrace(messageOrDataObject, data);
86 }
87 } else {
88 // @ts-ignore
89 Server.client.logObjectTrace(messageOrDataObject);
90 }
91 },
92 };
93 Server.client.registerProposedFeatures();
94 Server.client.start();
95 }
96}
diff --git a/editors/code/src/source_change.ts b/editors/code/src/source_change.ts
index a4f9068b2..887191d9e 100644
--- a/editors/code/src/source_change.ts
+++ b/editors/code/src/source_change.ts
@@ -10,7 +10,10 @@ export interface SourceChange {
10} 10}
11 11
12export async function applySourceChange(ctx: Ctx, change: SourceChange) { 12export async function applySourceChange(ctx: Ctx, change: SourceChange) {
13 const wsEdit = ctx.client.protocol2CodeConverter.asWorkspaceEdit( 13 const client = ctx.client;
14 if (!client) return
15
16 const wsEdit = client.protocol2CodeConverter.asWorkspaceEdit(
14 change.workspaceEdit, 17 change.workspaceEdit,
15 ); 18 );
16 let created; 19 let created;
@@ -32,10 +35,10 @@ export async function applySourceChange(ctx: Ctx, change: SourceChange) {
32 const doc = await vscode.workspace.openTextDocument(toOpenUri); 35 const doc = await vscode.workspace.openTextDocument(toOpenUri);
33 await vscode.window.showTextDocument(doc); 36 await vscode.window.showTextDocument(doc);
34 } else if (toReveal) { 37 } else if (toReveal) {
35 const uri = ctx.client.protocol2CodeConverter.asUri( 38 const uri = client.protocol2CodeConverter.asUri(
36 toReveal.textDocument.uri, 39 toReveal.textDocument.uri,
37 ); 40 );
38 const position = ctx.client.protocol2CodeConverter.asPosition( 41 const position = client.protocol2CodeConverter.asPosition(
39 toReveal.position, 42 toReveal.position,
40 ); 43 );
41 const editor = vscode.window.activeTextEditor; 44 const editor = vscode.window.activeTextEditor;
diff --git a/editors/code/src/status_display.ts b/editors/code/src/status_display.ts
index e3719075b..1454bf8b0 100644
--- a/editors/code/src/status_display.ts
+++ b/editors/code/src/status_display.ts
@@ -7,7 +7,9 @@ const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '
7export function activateStatusDisplay(ctx: Ctx) { 7export function activateStatusDisplay(ctx: Ctx) {
8 const statusDisplay = new StatusDisplay(ctx.config.cargoWatchOptions.command); 8 const statusDisplay = new StatusDisplay(ctx.config.cargoWatchOptions.command);
9 ctx.pushCleanup(statusDisplay); 9 ctx.pushCleanup(statusDisplay);
10 ctx.onNotification('$/progress', params => statusDisplay.handleProgressNotification(params)); 10 ctx.onDidRestart(client => {
11 client.onNotification('$/progress', params => statusDisplay.handleProgressNotification(params));
12 })
11} 13}
12 14
13class StatusDisplay implements vscode.Disposable { 15class StatusDisplay implements vscode.Disposable {