aboutsummaryrefslogtreecommitdiff
path: root/editors/code
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2019-12-31 17:38:55 +0000
committerGitHub <[email protected]>2019-12-31 17:38:55 +0000
commit6d23140ba03c77b28d94e042c94155899baba9da (patch)
tree3efa5daf54fe08cd1b310fa42c2ef469503fcedd /editors/code
parent1327aed7f6289043091aa9179282030c6f13ddbe (diff)
parent6368b40dd98b208da3758d4d1eed34cf276e3b09 (diff)
Merge #2709
2709: Work around synchrnonisation issue r=matklad a=matklad Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'editors/code')
-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/config.ts36
-rw-r--r--editors/code/src/ctx.ts75
-rw-r--r--editors/code/src/highlighting.ts20
-rw-r--r--editors/code/src/inlay_hints.ts11
-rw-r--r--editors/code/src/main.ts61
-rw-r--r--editors/code/src/server.ts102
-rw-r--r--editors/code/src/source_change.ts9
-rw-r--r--editors/code/src/status_display.ts12
13 files changed, 235 insertions, 223 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/config.ts b/editors/code/src/config.ts
index ccb0ee2b7..ec2790b63 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -16,25 +16,25 @@ export interface CargoFeatures {
16} 16}
17 17
18export class Config { 18export class Config {
19 public highlightingOn = true; 19 highlightingOn = true;
20 public rainbowHighlightingOn = false; 20 rainbowHighlightingOn = false;
21 public enableEnhancedTyping = true; 21 enableEnhancedTyping = true;
22 public raLspServerPath = RA_LSP_DEBUG || 'ra_lsp_server'; 22 raLspServerPath = RA_LSP_DEBUG || 'ra_lsp_server';
23 public lruCapacity: null | number = null; 23 lruCapacity: null | number = null;
24 public displayInlayHints = true; 24 displayInlayHints = true;
25 public maxInlayHintLength: null | number = null; 25 maxInlayHintLength: null | number = null;
26 public excludeGlobs = []; 26 excludeGlobs = [];
27 public useClientWatching = true; 27 useClientWatching = true;
28 public featureFlags = {}; 28 featureFlags = {};
29 // for internal use 29 // for internal use
30 public withSysroot: null | boolean = null; 30 withSysroot: null | boolean = null;
31 public cargoWatchOptions: CargoWatchOptions = { 31 cargoWatchOptions: CargoWatchOptions = {
32 enable: true, 32 enable: true,
33 arguments: [], 33 arguments: [],
34 command: '', 34 command: '',
35 allTargets: true, 35 allTargets: true,
36 }; 36 };
37 public cargoFeatures: CargoFeatures = { 37 cargoFeatures: CargoFeatures = {
38 noDefaultFeatures: false, 38 noDefaultFeatures: false,
39 allFeatures: true, 39 allFeatures: true,
40 features: [], 40 features: [],
@@ -43,14 +43,12 @@ export class Config {
43 private prevEnhancedTyping: null | boolean = null; 43 private prevEnhancedTyping: null | boolean = null;
44 private prevCargoFeatures: null | CargoFeatures = null; 44 private prevCargoFeatures: null | CargoFeatures = null;
45 45
46 constructor() { 46 constructor(ctx: vscode.ExtensionContext) {
47 vscode.workspace.onDidChangeConfiguration(_ => 47 vscode.workspace.onDidChangeConfiguration(_ => this.refresh(), ctx.subscriptions);
48 this.userConfigChanged(), 48 this.refresh();
49 );
50 this.userConfigChanged();
51 } 49 }
52 50
53 public userConfigChanged() { 51 private refresh() {
54 const config = vscode.workspace.getConfiguration('rust-analyzer'); 52 const config = vscode.workspace.getConfiguration('rust-analyzer');
55 53
56 let requireReloadMessage = null; 54 let requireReloadMessage = null;
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts
index 693ce05ed..13988056a 100644
--- a/editors/code/src/ctx.ts
+++ b/editors/code/src/ctx.ts
@@ -1,21 +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;
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
7 private extCtx: vscode.ExtensionContext; 14 private extCtx: vscode.ExtensionContext;
15 private onDidRestartHooks: Array<(client: lc.LanguageClient) => void> = [];
8 16
9 constructor(extCtx: vscode.ExtensionContext) { 17 constructor(extCtx: vscode.ExtensionContext) {
18 this.config = new Config(extCtx)
10 this.extCtx = extCtx; 19 this.extCtx = extCtx;
11 } 20 }
12 21
13 get client(): lc.LanguageClient { 22 async restartServer() {
14 return Server.client; 23 let old = this.client;
15 } 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();
16 31
17 get config(): Config { 32 this.client = client
18 return Server.config; 33 for (const hook of this.onDidRestartHooks) {
34 hook(client)
35 }
19 } 36 }
20 37
21 get activeRustEditor(): vscode.TextEditor | undefined { 38 get activeRustEditor(): vscode.TextEditor | undefined {
@@ -62,30 +79,34 @@ export class Ctx {
62 this.extCtx.subscriptions.push(d); 79 this.extCtx.subscriptions.push(d);
63 } 80 }
64 81
65 async sendRequestWithRetry<R>( 82 onDidRestart(hook: (client: lc.LanguageClient) => void) {
66 method: string, 83 this.onDidRestartHooks.push(hook)
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';
86 } 84 }
87} 85}
88 86
89export type Cmd = (...args: any[]) => any; 87export type Cmd = (...args: any[]) => any;
90 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
91const 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 d383d87ef..f9d2e9d90 100644
--- a/editors/code/src/highlighting.ts
+++ b/editors/code/src/highlighting.ts
@@ -5,13 +5,12 @@ 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.client.onReady().then(() => { 13 client.onNotification(
14 ctx.client.onNotification(
15 'rust-analyzer/publishDecorations', 14 'rust-analyzer/publishDecorations',
16 (params: PublishDecorationsParams) => { 15 (params: PublishDecorationsParams) => {
17 if (!ctx.config.highlightingOn) return; 16 if (!ctx.config.highlightingOn) return;
@@ -31,7 +30,7 @@ export function activateHighlighting(ctx: Ctx) {
31 highlighter.setHighlights(targetEditor, params.decorations); 30 highlighter.setHighlights(targetEditor, params.decorations);
32 }, 31 },
33 ); 32 );
34 }); 33 })
35 34
36 vscode.workspace.onDidChangeConfiguration( 35 vscode.workspace.onDidChangeConfiguration(
37 _ => highlighter.removeHighlights(), 36 _ => highlighter.removeHighlights(),
@@ -42,11 +41,14 @@ export function activateHighlighting(ctx: Ctx) {
42 async (editor: vscode.TextEditor | undefined) => { 41 async (editor: vscode.TextEditor | undefined) => {
43 if (!editor || editor.document.languageId !== 'rust') return; 42 if (!editor || editor.document.languageId !== 'rust') return;
44 if (!ctx.config.highlightingOn) return; 43 if (!ctx.config.highlightingOn) return;
44 let client = ctx.client;
45 if (!client) return;
45 46
46 const params: lc.TextDocumentIdentifier = { 47 const params: lc.TextDocumentIdentifier = {
47 uri: editor.document.uri.toString(), 48 uri: editor.document.uri.toString(),
48 }; 49 };
49 const decorations = await ctx.sendRequestWithRetry<Decoration[]>( 50 const decorations = await sendRequestWithRetry<Decoration[]>(
51 client,
50 'rust-analyzer/decorationsRequest', 52 'rust-analyzer/decorationsRequest',
51 params, 53 params,
52 ); 54 );
@@ -105,6 +107,8 @@ class Highlighter {
105 } 107 }
106 108
107 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;
108 // Initialize decorations if necessary 112 // Initialize decorations if necessary
109 // 113 //
110 // 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
@@ -137,13 +141,13 @@ class Highlighter {
137 colorfulIdents 141 colorfulIdents
138 .get(d.bindingHash)![0] 142 .get(d.bindingHash)![0]
139 .push( 143 .push(
140 this.ctx.client.protocol2CodeConverter.asRange(d.range), 144 client.protocol2CodeConverter.asRange(d.range),
141 ); 145 );
142 } else { 146 } else {
143 byTag 147 byTag
144 .get(d.tag)! 148 .get(d.tag)!
145 .push( 149 .push(
146 this.ctx.client.protocol2CodeConverter.asRange(d.range), 150 client.protocol2CodeConverter.asRange(d.range),
147 ); 151 );
148 } 152 }
149 } 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 0c4abdac8..51dedd5ef 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -1,10 +1,8 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient';
3 2
4import * as commands from './commands'; 3import * as commands from './commands';
5import { activateInlayHints } from './inlay_hints'; 4import { activateInlayHints } from './inlay_hints';
6import { StatusDisplay } from './status_display'; 5import { activateStatusDisplay } from './status_display';
7import { Server } from './server';
8import { Ctx } from './ctx'; 6import { Ctx } from './ctx';
9import { activateHighlighting } from './highlighting'; 7import { activateHighlighting } from './highlighting';
10 8
@@ -13,6 +11,17 @@ let ctx!: Ctx;
13export async function activate(context: vscode.ExtensionContext) { 11export async function activate(context: vscode.ExtensionContext) {
14 ctx = new Ctx(context); 12 ctx = new Ctx(context);
15 13
14 // Note: we try to start the server before we register various commands, so
15 // that it registers its `onDidChangeDocument` handler before us.
16 //
17 // This a horribly, horribly wrong way to deal with this problem.
18 try {
19 await ctx.restartServer();
20 } catch (e) {
21 vscode.window.showErrorMessage(e.message);
22 }
23
24
16 // Commands which invokes manually via command pallet, shortcut, etc. 25 // Commands which invokes manually via command pallet, shortcut, etc.
17 ctx.registerCommand('analyzerStatus', commands.analyzerStatus); 26 ctx.registerCommand('analyzerStatus', commands.analyzerStatus);
18 ctx.registerCommand('collectGarbage', commands.collectGarbage); 27 ctx.registerCommand('collectGarbage', commands.collectGarbage);
@@ -22,6 +31,7 @@ export async function activate(context: vscode.ExtensionContext) {
22 ctx.registerCommand('syntaxTree', commands.syntaxTree); 31 ctx.registerCommand('syntaxTree', commands.syntaxTree);
23 ctx.registerCommand('expandMacro', commands.expandMacro); 32 ctx.registerCommand('expandMacro', commands.expandMacro);
24 ctx.registerCommand('run', commands.run); 33 ctx.registerCommand('run', commands.run);
34 ctx.registerCommand('reload', commands.reload);
25 35
26 // Internal commands which are invoked by the server. 36 // Internal commands which are invoked by the server.
27 ctx.registerCommand('runSingle', commands.runSingle); 37 ctx.registerCommand('runSingle', commands.runSingle);
@@ -31,48 +41,11 @@ export async function activate(context: vscode.ExtensionContext) {
31 if (ctx.config.enableEnhancedTyping) { 41 if (ctx.config.enableEnhancedTyping) {
32 ctx.overrideCommand('type', commands.onEnter); 42 ctx.overrideCommand('type', commands.onEnter);
33 } 43 }
34 44 activateStatusDisplay(ctx);
35 const watchStatus = new StatusDisplay(ctx.config.cargoWatchOptions.command);
36 ctx.pushCleanup(watchStatus);
37
38 // Notifications are events triggered by the language server
39 const allNotifications: [string, lc.GenericNotificationHandler][] = [
40 [
41 '$/progress',
42 params => watchStatus.handleProgressNotification(params),
43 ],
44 ];
45
46 const startServer = () => Server.start(allNotifications);
47 const reloadCommand = () => reloadServer(startServer);
48
49 vscode.commands.registerCommand('rust-analyzer.reload', reloadCommand);
50
51 // Start the language server, finally!
52 try {
53 await startServer();
54 } catch (e) {
55 vscode.window.showErrorMessage(e.message);
56 }
57
58 activateHighlighting(ctx); 45 activateHighlighting(ctx);
59 46 activateInlayHints(ctx);
60 if (ctx.config.displayInlayHints) {
61 activateInlayHints(ctx);
62 }
63} 47}
64 48
65export function deactivate(): Thenable<void> { 49export async function deactivate() {
66 if (!Server.client) { 50 await ctx?.client?.stop();
67 return Promise.resolve();
68 }
69 return Server.client.stop();
70}
71
72async function reloadServer(startServer: () => Promise<void>) {
73 if (Server.client != null) {
74 vscode.window.showInformationMessage('Reloading rust-analyzer...');
75 await Server.client.stop();
76 await startServer();
77 }
78} 51}
diff --git a/editors/code/src/server.ts b/editors/code/src/server.ts
deleted file mode 100644
index 2bb21da6b..000000000
--- a/editors/code/src/server.ts
+++ /dev/null
@@ -1,102 +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 public static config = new Config();
16 public static client: lc.LanguageClient;
17
18 public static async start(
19 notificationHandlers: Iterable<[string, lc.GenericNotificationHandler]>,
20 ) {
21 // '.' Is the fallback if no folder is open
22 // 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.
23 let folder: string = '.';
24 if (workspace.workspaceFolders !== undefined) {
25 folder = workspace.workspaceFolders[0].uri.fsPath.toString();
26 }
27
28 const command = expandPathResolving(this.config.raLspServerPath);
29 const run: lc.Executable = {
30 command,
31 options: { cwd: folder },
32 };
33 const serverOptions: lc.ServerOptions = {
34 run,
35 debug: run,
36 };
37 const traceOutputChannel = window.createOutputChannel(
38 'Rust Analyzer Language Server Trace',
39 );
40 const clientOptions: lc.LanguageClientOptions = {
41 documentSelector: [{ scheme: 'file', language: 'rust' }],
42 initializationOptions: {
43 publishDecorations: true,
44 lruCapacity: Server.config.lruCapacity,
45 maxInlayHintLength: Server.config.maxInlayHintLength,
46 cargoWatchEnable: Server.config.cargoWatchOptions.enable,
47 cargoWatchArgs: Server.config.cargoWatchOptions.arguments,
48 cargoWatchCommand: Server.config.cargoWatchOptions.command,
49 cargoWatchAllTargets:
50 Server.config.cargoWatchOptions.allTargets,
51 excludeGlobs: Server.config.excludeGlobs,
52 useClientWatching: Server.config.useClientWatching,
53 featureFlags: Server.config.featureFlags,
54 withSysroot: Server.config.withSysroot,
55 cargoFeatures: Server.config.cargoFeatures,
56 },
57 traceOutputChannel,
58 };
59
60 Server.client = new lc.LanguageClient(
61 'rust-analyzer',
62 'Rust Analyzer Language Server',
63 serverOptions,
64 clientOptions,
65 );
66 // HACK: This is an awful way of filtering out the decorations notifications
67 // However, pending proper support, this is the most effecitve approach
68 // Proper support for this would entail a change to vscode-languageclient to allow not notifying on certain messages
69 // Or the ability to disable the serverside component of highlighting (but this means that to do tracing we need to disable hihlighting)
70 // This also requires considering our settings strategy, which is work which needs doing
71 // @ts-ignore The tracer is private to vscode-languageclient, but we need access to it to not log publishDecorations requests
72 Server.client._tracer = {
73 log: (messageOrDataObject: string | any, data?: string) => {
74 if (typeof messageOrDataObject === 'string') {
75 if (
76 messageOrDataObject.includes(
77 'rust-analyzer/publishDecorations',
78 ) ||
79 messageOrDataObject.includes(
80 'rust-analyzer/decorationsRequest',
81 )
82 ) {
83 // Don't log publish decorations requests
84 } else {
85 // @ts-ignore This is just a utility function
86 Server.client.logTrace(messageOrDataObject, data);
87 }
88 } else {
89 // @ts-ignore
90 Server.client.logObjectTrace(messageOrDataObject);
91 }
92 },
93 };
94 Server.client.registerProposedFeatures();
95 Server.client.onReady().then(() => {
96 for (const [type, handler] of notificationHandlers) {
97 Server.client.onNotification(type, handler);
98 }
99 });
100 Server.client.start();
101 }
102}
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 48cf0655b..1454bf8b0 100644
--- a/editors/code/src/status_display.ts
+++ b/editors/code/src/status_display.ts
@@ -1,8 +1,18 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2 2
3import { Ctx } from './ctx';
4
3const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; 5const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
4 6
5export class StatusDisplay implements vscode.Disposable { 7export function activateStatusDisplay(ctx: Ctx) {
8 const statusDisplay = new StatusDisplay(ctx.config.cargoWatchOptions.command);
9 ctx.pushCleanup(statusDisplay);
10 ctx.onDidRestart(client => {
11 client.onNotification('$/progress', params => statusDisplay.handleProgressNotification(params));
12 })
13}
14
15class StatusDisplay implements vscode.Disposable {
6 packageName?: string; 16 packageName?: string;
7 17
8 private i = 0; 18 private i = 0;