diff options
-rw-r--r-- | crates/rust-analyzer/src/config.rs | 2 | ||||
-rw-r--r-- | crates/rust-analyzer/src/global_state.rs | 2 | ||||
-rw-r--r-- | crates/rust-analyzer/src/lsp_ext.rs | 18 | ||||
-rw-r--r-- | crates/rust-analyzer/src/main_loop.rs | 56 | ||||
-rw-r--r-- | crates/rust-analyzer/src/reload.rs | 2 | ||||
-rw-r--r-- | crates/rust-analyzer/tests/heavy_tests/support.rs | 17 | ||||
-rw-r--r-- | docs/dev/lsp-extensions.md | 12 | ||||
-rw-r--r-- | editors/code/src/client.ts | 1 | ||||
-rw-r--r-- | editors/code/src/ctx.ts | 43 | ||||
-rw-r--r-- | editors/code/src/lsp_ext.ts | 3 |
10 files changed, 144 insertions, 12 deletions
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 6c311648a..21acfe644 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs | |||
@@ -130,6 +130,7 @@ pub struct ClientCapsConfig { | |||
130 | pub code_action_group: bool, | 130 | pub code_action_group: bool, |
131 | pub resolve_code_action: bool, | 131 | pub resolve_code_action: bool, |
132 | pub hover_actions: bool, | 132 | pub hover_actions: bool, |
133 | pub status_notification: bool, | ||
133 | } | 134 | } |
134 | 135 | ||
135 | impl Config { | 136 | impl Config { |
@@ -365,6 +366,7 @@ impl Config { | |||
365 | self.client_caps.code_action_group = get_bool("codeActionGroup"); | 366 | self.client_caps.code_action_group = get_bool("codeActionGroup"); |
366 | self.client_caps.resolve_code_action = get_bool("resolveCodeAction"); | 367 | self.client_caps.resolve_code_action = get_bool("resolveCodeAction"); |
367 | self.client_caps.hover_actions = get_bool("hoverActions"); | 368 | self.client_caps.hover_actions = get_bool("hoverActions"); |
369 | self.client_caps.status_notification = get_bool("statusNotification"); | ||
368 | } | 370 | } |
369 | } | 371 | } |
370 | } | 372 | } |
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index b7b4edf66..640b3959d 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs | |||
@@ -31,6 +31,8 @@ use crate::{ | |||
31 | pub(crate) enum Status { | 31 | pub(crate) enum Status { |
32 | Loading, | 32 | Loading, |
33 | Ready, | 33 | Ready, |
34 | Invalid, | ||
35 | NeedsReload, | ||
34 | } | 36 | } |
35 | 37 | ||
36 | impl Default for Status { | 38 | impl Default for Status { |
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index 82207bbb8..d225ad5ff 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs | |||
@@ -3,7 +3,7 @@ | |||
3 | use std::{collections::HashMap, path::PathBuf}; | 3 | use std::{collections::HashMap, path::PathBuf}; |
4 | 4 | ||
5 | use lsp_types::request::Request; | 5 | use lsp_types::request::Request; |
6 | use lsp_types::{Position, Range, TextDocumentIdentifier}; | 6 | use lsp_types::{notification::Notification, Position, Range, TextDocumentIdentifier}; |
7 | use serde::{Deserialize, Serialize}; | 7 | use serde::{Deserialize, Serialize}; |
8 | 8 | ||
9 | pub enum AnalyzerStatus {} | 9 | pub enum AnalyzerStatus {} |
@@ -208,6 +208,22 @@ pub struct SsrParams { | |||
208 | pub parse_only: bool, | 208 | pub parse_only: bool, |
209 | } | 209 | } |
210 | 210 | ||
211 | pub enum StatusNotification {} | ||
212 | |||
213 | #[serde(rename_all = "camelCase")] | ||
214 | #[derive(Serialize, Deserialize)] | ||
215 | pub enum Status { | ||
216 | Loading, | ||
217 | Ready, | ||
218 | NeedsReload, | ||
219 | Invalid, | ||
220 | } | ||
221 | |||
222 | impl Notification for StatusNotification { | ||
223 | type Params = Status; | ||
224 | const METHOD: &'static str = "rust-analyzer/status"; | ||
225 | } | ||
226 | |||
211 | pub enum CodeActionRequest {} | 227 | pub enum CodeActionRequest {} |
212 | 228 | ||
213 | impl Request for CodeActionRequest { | 229 | impl Request for CodeActionRequest { |
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index e03038b25..d4d18a808 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs | |||
@@ -111,6 +111,35 @@ impl GlobalState { | |||
111 | } | 111 | } |
112 | 112 | ||
113 | fn run(mut self, inbox: Receiver<lsp_server::Message>) -> Result<()> { | 113 | fn run(mut self, inbox: Receiver<lsp_server::Message>) -> Result<()> { |
114 | let registration_options = lsp_types::TextDocumentRegistrationOptions { | ||
115 | document_selector: Some(vec![ | ||
116 | lsp_types::DocumentFilter { | ||
117 | language: None, | ||
118 | scheme: None, | ||
119 | pattern: Some("**/*.rs".into()), | ||
120 | }, | ||
121 | lsp_types::DocumentFilter { | ||
122 | language: None, | ||
123 | scheme: None, | ||
124 | pattern: Some("**/Cargo.toml".into()), | ||
125 | }, | ||
126 | lsp_types::DocumentFilter { | ||
127 | language: None, | ||
128 | scheme: None, | ||
129 | pattern: Some("**/Cargo.lock".into()), | ||
130 | }, | ||
131 | ]), | ||
132 | }; | ||
133 | let registration = lsp_types::Registration { | ||
134 | id: "textDocument/didSave".to_string(), | ||
135 | method: "textDocument/didSave".to_string(), | ||
136 | register_options: Some(serde_json::to_value(registration_options).unwrap()), | ||
137 | }; | ||
138 | self.send_request::<lsp_types::request::RegisterCapability>( | ||
139 | lsp_types::RegistrationParams { registrations: vec![registration] }, | ||
140 | |_, _| (), | ||
141 | ); | ||
142 | |||
114 | self.reload(); | 143 | self.reload(); |
115 | 144 | ||
116 | while let Some(event) = self.next_event(&inbox) { | 145 | while let Some(event) = self.next_event(&inbox) { |
@@ -169,16 +198,16 @@ impl GlobalState { | |||
169 | } | 198 | } |
170 | vfs::loader::Message::Progress { n_total, n_done } => { | 199 | vfs::loader::Message::Progress { n_total, n_done } => { |
171 | if n_total == 0 { | 200 | if n_total == 0 { |
172 | self.status = Status::Ready; | 201 | self.transition(Status::Invalid); |
173 | } else { | 202 | } else { |
174 | let state = if n_done == 0 { | 203 | let state = if n_done == 0 { |
175 | self.status = Status::Loading; | 204 | self.transition(Status::Loading); |
176 | Progress::Begin | 205 | Progress::Begin |
177 | } else if n_done < n_total { | 206 | } else if n_done < n_total { |
178 | Progress::Report | 207 | Progress::Report |
179 | } else { | 208 | } else { |
180 | assert_eq!(n_done, n_total); | 209 | assert_eq!(n_done, n_total); |
181 | self.status = Status::Ready; | 210 | self.transition(Status::Ready); |
182 | Progress::End | 211 | Progress::End |
183 | }; | 212 | }; |
184 | self.report_progress( | 213 | self.report_progress( |
@@ -274,6 +303,19 @@ impl GlobalState { | |||
274 | Ok(()) | 303 | Ok(()) |
275 | } | 304 | } |
276 | 305 | ||
306 | fn transition(&mut self, new_status: Status) { | ||
307 | self.status = Status::Ready; | ||
308 | if self.config.client_caps.status_notification { | ||
309 | let lsp_status = match new_status { | ||
310 | Status::Loading => lsp_ext::Status::Loading, | ||
311 | Status::Ready => lsp_ext::Status::Ready, | ||
312 | Status::Invalid => lsp_ext::Status::Invalid, | ||
313 | Status::NeedsReload => lsp_ext::Status::NeedsReload, | ||
314 | }; | ||
315 | self.send_notification::<lsp_ext::StatusNotification>(lsp_status); | ||
316 | } | ||
317 | } | ||
318 | |||
277 | fn on_request(&mut self, request_received: Instant, req: Request) -> Result<()> { | 319 | fn on_request(&mut self, request_received: Instant, req: Request) -> Result<()> { |
278 | self.register_request(&req, request_received); | 320 | self.register_request(&req, request_received); |
279 | 321 | ||
@@ -383,10 +425,16 @@ impl GlobalState { | |||
383 | ); | 425 | ); |
384 | Ok(()) | 426 | Ok(()) |
385 | })? | 427 | })? |
386 | .on::<lsp_types::notification::DidSaveTextDocument>(|this, _params| { | 428 | .on::<lsp_types::notification::DidSaveTextDocument>(|this, params| { |
387 | if let Some(flycheck) = &this.flycheck { | 429 | if let Some(flycheck) = &this.flycheck { |
388 | flycheck.handle.update(); | 430 | flycheck.handle.update(); |
389 | } | 431 | } |
432 | let uri = params.text_document.uri.as_str(); | ||
433 | if uri.ends_with("Cargo.toml") || uri.ends_with("Cargo.lock") { | ||
434 | if matches!(this.status, Status::Ready | Status::Invalid) { | ||
435 | this.transition(Status::NeedsReload); | ||
436 | } | ||
437 | } | ||
390 | Ok(()) | 438 | Ok(()) |
391 | })? | 439 | })? |
392 | .on::<lsp_types::notification::DidChangeConfiguration>(|this, _params| { | 440 | .on::<lsp_types::notification::DidChangeConfiguration>(|this, _params| { |
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index 0c1fd1b8b..523b04b97 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs | |||
@@ -78,7 +78,7 @@ impl GlobalState { | |||
78 | .collect(), | 78 | .collect(), |
79 | }; | 79 | }; |
80 | let registration = lsp_types::Registration { | 80 | let registration = lsp_types::Registration { |
81 | id: "file-watcher".to_string(), | 81 | id: "workspace/didChangeWatchedFiles".to_string(), |
82 | method: "workspace/didChangeWatchedFiles".to_string(), | 82 | method: "workspace/didChangeWatchedFiles".to_string(), |
83 | register_options: Some(serde_json::to_value(registration_options).unwrap()), | 83 | register_options: Some(serde_json::to_value(registration_options).unwrap()), |
84 | }; | 84 | }; |
diff --git a/crates/rust-analyzer/tests/heavy_tests/support.rs b/crates/rust-analyzer/tests/heavy_tests/support.rs index 49f194f7e..7bf687794 100644 --- a/crates/rust-analyzer/tests/heavy_tests/support.rs +++ b/crates/rust-analyzer/tests/heavy_tests/support.rs | |||
@@ -176,12 +176,19 @@ impl Server { | |||
176 | while let Some(msg) = self.recv() { | 176 | while let Some(msg) = self.recv() { |
177 | match msg { | 177 | match msg { |
178 | Message::Request(req) => { | 178 | Message::Request(req) => { |
179 | if req.method != "window/workDoneProgress/create" | 179 | if req.method == "window/workDoneProgress/create" { |
180 | && !(req.method == "client/registerCapability" | 180 | continue; |
181 | && req.params.to_string().contains("workspace/didChangeWatchedFiles")) | ||
182 | { | ||
183 | panic!("unexpected request: {:?}", req) | ||
184 | } | 181 | } |
182 | if req.method == "client/registerCapability" { | ||
183 | let params = req.params.to_string(); | ||
184 | if ["workspace/didChangeWatchedFiles", "textDocument/didSave"] | ||
185 | .iter() | ||
186 | .any(|&it| params.contains(it)) | ||
187 | { | ||
188 | continue; | ||
189 | } | ||
190 | } | ||
191 | panic!("unexpected request: {:?}", req) | ||
185 | } | 192 | } |
186 | Message::Notification(_) => (), | 193 | Message::Notification(_) => (), |
187 | Message::Response(res) => { | 194 | Message::Response(res) => { |
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index c0afb16d3..6d6bbac7c 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md | |||
@@ -399,6 +399,18 @@ Returns internal status message, mostly for debugging purposes. | |||
399 | 399 | ||
400 | Reloads project information (that is, re-executes `cargo metadata`). | 400 | Reloads project information (that is, re-executes `cargo metadata`). |
401 | 401 | ||
402 | ## Status Notification | ||
403 | |||
404 | **Client Capability:** `{ "statusNotification": boolean }` | ||
405 | |||
406 | **Method:** `rust-analyzer/status` | ||
407 | |||
408 | **Notification:** `"loading" | "ready" | "invalid" | "needsReload"` | ||
409 | |||
410 | This notification is sent from server to client. | ||
411 | The client can use it to display persistent status to the user (in modline). | ||
412 | For `needsReload` state, the client can provide a context-menu action to run `rust-analyzer/reloadWorkspace` request. | ||
413 | |||
402 | ## Syntax Tree | 414 | ## Syntax Tree |
403 | 415 | ||
404 | **Method:** `rust-analyzer/syntaxTree` | 416 | **Method:** `rust-analyzer/syntaxTree` |
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 65ad573d8..3e5915c28 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts | |||
@@ -161,6 +161,7 @@ class ExperimentalFeatures implements lc.StaticFeature { | |||
161 | caps.codeActionGroup = true; | 161 | caps.codeActionGroup = true; |
162 | caps.resolveCodeAction = true; | 162 | caps.resolveCodeAction = true; |
163 | caps.hoverActions = true; | 163 | caps.hoverActions = true; |
164 | caps.statusNotification = true; | ||
164 | capabilities.experimental = caps; | 165 | capabilities.experimental = caps; |
165 | } | 166 | } |
166 | initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void { | 167 | initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void { |
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 41df11991..6e767babf 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts | |||
@@ -1,9 +1,11 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | import * as lc from 'vscode-languageclient'; | 2 | import * as lc from 'vscode-languageclient'; |
3 | import * as ra from './lsp_ext'; | ||
3 | 4 | ||
4 | import { Config } from './config'; | 5 | import { Config } from './config'; |
5 | import { createClient } from './client'; | 6 | import { createClient } from './client'; |
6 | import { isRustEditor, RustEditor } from './util'; | 7 | import { isRustEditor, RustEditor } from './util'; |
8 | import { Status } from './lsp_ext'; | ||
7 | 9 | ||
8 | export class Ctx { | 10 | export class Ctx { |
9 | private constructor( | 11 | private constructor( |
@@ -11,6 +13,7 @@ export class Ctx { | |||
11 | private readonly extCtx: vscode.ExtensionContext, | 13 | private readonly extCtx: vscode.ExtensionContext, |
12 | readonly client: lc.LanguageClient, | 14 | readonly client: lc.LanguageClient, |
13 | readonly serverPath: string, | 15 | readonly serverPath: string, |
16 | readonly statusBar: vscode.StatusBarItem, | ||
14 | ) { | 17 | ) { |
15 | 18 | ||
16 | } | 19 | } |
@@ -22,9 +25,18 @@ export class Ctx { | |||
22 | cwd: string, | 25 | cwd: string, |
23 | ): Promise<Ctx> { | 26 | ): Promise<Ctx> { |
24 | const client = createClient(serverPath, cwd); | 27 | const client = createClient(serverPath, cwd); |
25 | const res = new Ctx(config, extCtx, client, serverPath); | 28 | |
29 | const statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); | ||
30 | extCtx.subscriptions.push(statusBar); | ||
31 | statusBar.text = "rust-analyzer"; | ||
32 | statusBar.tooltip = "ready"; | ||
33 | statusBar.show(); | ||
34 | |||
35 | const res = new Ctx(config, extCtx, client, serverPath, statusBar); | ||
36 | |||
26 | res.pushCleanup(client.start()); | 37 | res.pushCleanup(client.start()); |
27 | await client.onReady(); | 38 | await client.onReady(); |
39 | client.onNotification(ra.status, (status) => res.setStatus(status)); | ||
28 | return res; | 40 | return res; |
29 | } | 41 | } |
30 | 42 | ||
@@ -54,6 +66,35 @@ export class Ctx { | |||
54 | return this.extCtx.subscriptions; | 66 | return this.extCtx.subscriptions; |
55 | } | 67 | } |
56 | 68 | ||
69 | setStatus(status: Status) { | ||
70 | switch (status) { | ||
71 | case "loading": | ||
72 | this.statusBar.text = "$(sync~spin) rust-analyzer"; | ||
73 | this.statusBar.tooltip = "Loading the project"; | ||
74 | this.statusBar.command = undefined; | ||
75 | this.statusBar.color = undefined; | ||
76 | break; | ||
77 | case "ready": | ||
78 | this.statusBar.text = "rust-analyzer"; | ||
79 | this.statusBar.tooltip = "Ready"; | ||
80 | this.statusBar.command = undefined; | ||
81 | this.statusBar.color = undefined; | ||
82 | break; | ||
83 | case "invalid": | ||
84 | this.statusBar.text = "$(error) rust-analyzer"; | ||
85 | this.statusBar.tooltip = "Failed to load the project"; | ||
86 | this.statusBar.command = undefined; | ||
87 | this.statusBar.color = new vscode.ThemeColor("notificationsErrorIcon.foreground"); | ||
88 | break; | ||
89 | case "needsReload": | ||
90 | this.statusBar.text = "$(warning) rust-analyzer"; | ||
91 | this.statusBar.tooltip = "Click to reload"; | ||
92 | this.statusBar.command = "rust-analyzer.reloadWorkspace"; | ||
93 | this.statusBar.color = new vscode.ThemeColor("notificationsWarningIcon.foreground"); | ||
94 | break; | ||
95 | } | ||
96 | } | ||
97 | |||
57 | pushCleanup(d: Disposable) { | 98 | pushCleanup(d: Disposable) { |
58 | this.extCtx.subscriptions.push(d); | 99 | this.extCtx.subscriptions.push(d); |
59 | } | 100 | } |
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index 981b6f40e..bf4703239 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts | |||
@@ -6,6 +6,9 @@ import * as lc from "vscode-languageclient"; | |||
6 | 6 | ||
7 | export const analyzerStatus = new lc.RequestType<null, string, void>("rust-analyzer/analyzerStatus"); | 7 | export const analyzerStatus = new lc.RequestType<null, string, void>("rust-analyzer/analyzerStatus"); |
8 | 8 | ||
9 | export type Status = "loading" | "ready" | "invalid" | "needsReload"; | ||
10 | export const status = new lc.NotificationType<Status>("rust-analyzer/status"); | ||
11 | |||
9 | export const reloadWorkspace = new lc.RequestType<null, null, void>("rust-analyzer/reloadWorkspace"); | 12 | export const reloadWorkspace = new lc.RequestType<null, null, void>("rust-analyzer/reloadWorkspace"); |
10 | 13 | ||
11 | export interface SyntaxTreeParams { | 14 | export interface SyntaxTreeParams { |