aboutsummaryrefslogtreecommitdiff
path: root/crates/rust-analyzer/src/main_loop.rs
diff options
context:
space:
mode:
authorZac Pullar-Strecker <[email protected]>2020-07-31 03:12:44 +0100
committerZac Pullar-Strecker <[email protected]>2020-07-31 03:12:44 +0100
commitf05d7b41a719d848844b054a16477b29d0f063c6 (patch)
tree0a8a0946e8aef2ce64d4c13d0035ba41cce2daf3 /crates/rust-analyzer/src/main_loop.rs
parent73ff610e41959e3e7c78a2b4b25b086883132956 (diff)
parent6b7cb8b5ab539fc4333ce34bc29bf77c976f232a (diff)
Merge remote-tracking branch 'upstream/master' into 503-hover-doc-links
Hasn't fixed tests yet.
Diffstat (limited to 'crates/rust-analyzer/src/main_loop.rs')
-rw-r--r--crates/rust-analyzer/src/main_loop.rs218
1 files changed, 155 insertions, 63 deletions
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 9fd16ef3b..0ace4cb45 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -1,13 +1,14 @@
1//! The main loop of `rust-analyzer` responsible for dispatching LSP 1//! The main loop of `rust-analyzer` responsible for dispatching LSP
2//! requests/replies and notifications back to the client. 2//! requests/replies and notifications back to the client.
3use std::{ 3use std::{
4 borrow::Cow,
4 env, fmt, panic, 5 env, fmt, panic,
5 time::{Duration, Instant}, 6 time::{Duration, Instant},
6}; 7};
7 8
8use crossbeam_channel::{never, select, Receiver}; 9use crossbeam_channel::{select, Receiver};
9use lsp_server::{Connection, Notification, Request, Response}; 10use lsp_server::{Connection, Notification, Request, Response};
10use lsp_types::notification::Notification as _; 11use lsp_types::{notification::Notification as _, DidChangeTextDocumentParams};
11use ra_db::VfsPath; 12use ra_db::VfsPath;
12use ra_ide::{Canceled, FileId}; 13use ra_ide::{Canceled, FileId};
13use ra_prof::profile; 14use ra_prof::profile;
@@ -15,12 +16,15 @@ use ra_prof::profile;
15use crate::{ 16use crate::{
16 config::Config, 17 config::Config,
17 dispatch::{NotificationDispatcher, RequestDispatcher}, 18 dispatch::{NotificationDispatcher, RequestDispatcher},
19 document::DocumentData,
18 from_proto, 20 from_proto,
19 global_state::{file_id_to_url, url_to_file_id, GlobalState, Status}, 21 global_state::{file_id_to_url, url_to_file_id, GlobalState, Status},
20 handlers, lsp_ext, 22 handlers, lsp_ext,
21 lsp_utils::{apply_document_changes, is_canceled, notification_is, Progress}, 23 lsp_utils::{apply_document_changes, is_canceled, notification_is, Progress},
22 Result, 24 Result,
23}; 25};
26use ra_project_model::ProjectWorkspace;
27use vfs::ChangeKind;
24 28
25pub fn main_loop(config: Config, connection: Connection) -> Result<()> { 29pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
26 log::info!("initial config: {:#?}", config); 30 log::info!("initial config: {:#?}", config);
@@ -58,6 +62,7 @@ enum Event {
58pub(crate) enum Task { 62pub(crate) enum Task {
59 Response(Response), 63 Response(Response),
60 Diagnostics(Vec<(FileId, Vec<lsp_types::Diagnostic>)>), 64 Diagnostics(Vec<(FileId, Vec<lsp_types::Diagnostic>)>),
65 Workspaces(Vec<anyhow::Result<ProjectWorkspace>>),
61 Unit, 66 Unit,
62} 67}
63 68
@@ -94,24 +99,49 @@ impl fmt::Debug for Event {
94} 99}
95 100
96impl GlobalState { 101impl GlobalState {
97 fn next_event(&self, inbox: &Receiver<lsp_server::Message>) -> Option<Event> { 102 fn run(mut self, inbox: Receiver<lsp_server::Message>) -> Result<()> {
98 select! { 103 if self.config.linked_projects.is_empty() && self.config.notifications.cargo_toml_not_found
99 recv(inbox) -> msg => 104 {
100 msg.ok().map(Event::Lsp), 105 self.show_message(
101 106 lsp_types::MessageType::Error,
102 recv(self.task_pool.receiver) -> task => 107 "rust-analyzer failed to discover workspace".to_string(),
103 Some(Event::Task(task.unwrap())), 108 );
109 };
104 110
105 recv(self.loader.receiver) -> task => 111 let save_registration_options = lsp_types::TextDocumentSaveRegistrationOptions {
106 Some(Event::Vfs(task.unwrap())), 112 include_text: Some(false),
113 text_document_registration_options: lsp_types::TextDocumentRegistrationOptions {
114 document_selector: Some(vec![
115 lsp_types::DocumentFilter {
116 language: None,
117 scheme: None,
118 pattern: Some("**/*.rs".into()),
119 },
120 lsp_types::DocumentFilter {
121 language: None,
122 scheme: None,
123 pattern: Some("**/Cargo.toml".into()),
124 },
125 lsp_types::DocumentFilter {
126 language: None,
127 scheme: None,
128 pattern: Some("**/Cargo.lock".into()),
129 },
130 ]),
131 },
132 };
107 133
108 recv(self.flycheck.as_ref().map_or(&never(), |it| &it.receiver)) -> task => 134 let registration = lsp_types::Registration {
109 Some(Event::Flycheck(task.unwrap())), 135 id: "textDocument/didSave".to_string(),
110 } 136 method: "textDocument/didSave".to_string(),
111 } 137 register_options: Some(serde_json::to_value(save_registration_options).unwrap()),
138 };
139 self.send_request::<lsp_types::request::RegisterCapability>(
140 lsp_types::RegistrationParams { registrations: vec![registration] },
141 |_, _| (),
142 );
112 143
113 fn run(mut self, inbox: Receiver<lsp_server::Message>) -> Result<()> { 144 self.fetch_workspaces();
114 self.reload();
115 145
116 while let Some(event) = self.next_event(&inbox) { 146 while let Some(event) = self.next_event(&inbox) {
117 if let Event::Lsp(lsp_server::Message::Notification(not)) = &event { 147 if let Event::Lsp(lsp_server::Message::Notification(not)) = &event {
@@ -125,6 +155,22 @@ impl GlobalState {
125 Err("client exited without proper shutdown sequence")? 155 Err("client exited without proper shutdown sequence")?
126 } 156 }
127 157
158 fn next_event(&self, inbox: &Receiver<lsp_server::Message>) -> Option<Event> {
159 select! {
160 recv(inbox) -> msg =>
161 msg.ok().map(Event::Lsp),
162
163 recv(self.task_pool.receiver) -> task =>
164 Some(Event::Task(task.unwrap())),
165
166 recv(self.loader.receiver) -> task =>
167 Some(Event::Vfs(task.unwrap())),
168
169 recv(self.flycheck_receiver) -> task =>
170 Some(Event::Flycheck(task.unwrap())),
171 }
172 }
173
128 fn handle_event(&mut self, event: Event) -> Result<()> { 174 fn handle_event(&mut self, event: Event) -> Result<()> {
129 let loop_start = Instant::now(); 175 let loop_start = Instant::now();
130 // NOTE: don't count blocking select! call as a loop-turn time 176 // NOTE: don't count blocking select! call as a loop-turn time
@@ -136,7 +182,7 @@ impl GlobalState {
136 log::info!("queued count = {}", queue_count); 182 log::info!("queued count = {}", queue_count);
137 } 183 }
138 184
139 let mut became_ready = false; 185 let prev_status = self.status;
140 match event { 186 match event {
141 Event::Lsp(msg) => match msg { 187 Event::Lsp(msg) => match msg {
142 lsp_server::Message::Request(req) => self.on_request(loop_start, req)?, 188 lsp_server::Message::Request(req) => self.on_request(loop_start, req)?,
@@ -153,39 +199,54 @@ impl GlobalState {
153 self.diagnostics.set_native_diagnostics(file_id, diagnostics) 199 self.diagnostics.set_native_diagnostics(file_id, diagnostics)
154 } 200 }
155 } 201 }
202 Task::Workspaces(workspaces) => self.switch_workspaces(workspaces),
156 Task::Unit => (), 203 Task::Unit => (),
157 } 204 }
158 self.analysis_host.maybe_collect_garbage(); 205 self.analysis_host.maybe_collect_garbage();
159 } 206 }
160 Event::Vfs(task) => match task { 207 Event::Vfs(mut task) => {
161 vfs::loader::Message::Loaded { files } => { 208 let _p = profile("GlobalState::handle_event/vfs");
162 let vfs = &mut self.vfs.write().0; 209 loop {
163 for (path, contents) in files { 210 match task {
164 let path = VfsPath::from(path); 211 vfs::loader::Message::Loaded { files } => {
165 if !self.mem_docs.contains(&path) { 212 let vfs = &mut self.vfs.write().0;
166 vfs.set_file_contents(path, contents) 213 for (path, contents) in files {
214 let path = VfsPath::from(path);
215 if !self.mem_docs.contains_key(&path) {
216 vfs.set_file_contents(path, contents)
217 }
218 }
219 }
220 vfs::loader::Message::Progress { n_total, n_done } => {
221 if n_total == 0 {
222 self.transition(Status::Invalid);
223 } else {
224 let state = if n_done == 0 {
225 self.transition(Status::Loading);
226 Progress::Begin
227 } else if n_done < n_total {
228 Progress::Report
229 } else {
230 assert_eq!(n_done, n_total);
231 self.transition(Status::Ready);
232 Progress::End
233 };
234 self.report_progress(
235 "roots scanned",
236 state,
237 Some(format!("{}/{}", n_done, n_total)),
238 Some(Progress::percentage(n_done, n_total)),
239 )
240 }
167 } 241 }
168 } 242 }
243 // Coalesce many VFS event into a single loop turn
244 task = match self.loader.receiver.try_recv() {
245 Ok(task) => task,
246 Err(_) => break,
247 }
169 } 248 }
170 vfs::loader::Message::Progress { n_total, n_done } => { 249 }
171 let state = if n_done == 0 {
172 Progress::Begin
173 } else if n_done < n_total {
174 Progress::Report
175 } else {
176 assert_eq!(n_done, n_total);
177 self.status = Status::Ready;
178 became_ready = true;
179 Progress::End
180 };
181 self.report_progress(
182 "roots scanned",
183 state,
184 Some(format!("{}/{}", n_done, n_total)),
185 Some(Progress::percentage(n_done, n_total)),
186 )
187 }
188 },
189 Event::Flycheck(task) => match task { 250 Event::Flycheck(task) => match task {
190 flycheck::Message::AddDiagnostic { workspace_root, diagnostic } => { 251 flycheck::Message::AddDiagnostic { workspace_root, diagnostic } => {
191 let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp( 252 let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp(
@@ -194,7 +255,7 @@ impl GlobalState {
194 &workspace_root, 255 &workspace_root,
195 ); 256 );
196 for diag in diagnostics { 257 for diag in diagnostics {
197 match url_to_file_id(&self.vfs.read().0, &diag.location.uri) { 258 match url_to_file_id(&self.vfs.read().0, &diag.url) {
198 Ok(file_id) => self.diagnostics.add_check_diagnostic( 259 Ok(file_id) => self.diagnostics.add_check_diagnostic(
199 file_id, 260 file_id,
200 diag.diagnostic, 261 diag.diagnostic,
@@ -231,16 +292,16 @@ impl GlobalState {
231 } 292 }
232 293
233 let state_changed = self.process_changes(); 294 let state_changed = self.process_changes();
234 if became_ready { 295 if prev_status == Status::Loading && self.status == Status::Ready {
235 if let Some(flycheck) = &self.flycheck { 296 if let Some(flycheck) = &self.flycheck {
236 flycheck.handle.update(); 297 flycheck.update();
237 } 298 }
238 } 299 }
239 300
240 if self.status == Status::Ready && (state_changed || became_ready) { 301 if self.status == Status::Ready && (state_changed || prev_status == Status::Loading) {
241 let subscriptions = self 302 let subscriptions = self
242 .mem_docs 303 .mem_docs
243 .iter() 304 .keys()
244 .map(|path| self.vfs.read().0.file_id(&path).unwrap()) 305 .map(|path| self.vfs.read().0.file_id(&path).unwrap())
245 .collect::<Vec<_>>(); 306 .collect::<Vec<_>>();
246 307
@@ -251,8 +312,12 @@ impl GlobalState {
251 for file_id in diagnostic_changes { 312 for file_id in diagnostic_changes {
252 let url = file_id_to_url(&self.vfs.read().0, file_id); 313 let url = file_id_to_url(&self.vfs.read().0, file_id);
253 let diagnostics = self.diagnostics.diagnostics_for(file_id).cloned().collect(); 314 let diagnostics = self.diagnostics.diagnostics_for(file_id).cloned().collect();
315 let version = from_proto::vfs_path(&url)
316 .map(|path| self.mem_docs.get(&path)?.version)
317 .unwrap_or_default();
318
254 self.send_notification::<lsp_types::notification::PublishDiagnostics>( 319 self.send_notification::<lsp_types::notification::PublishDiagnostics>(
255 lsp_types::PublishDiagnosticsParams { uri: url, diagnostics, version: None }, 320 lsp_types::PublishDiagnosticsParams { uri: url, diagnostics, version },
256 ); 321 );
257 } 322 }
258 } 323 }
@@ -274,7 +339,7 @@ impl GlobalState {
274 self.register_request(&req, request_received); 339 self.register_request(&req, request_received);
275 340
276 RequestDispatcher { req: Some(req), global_state: self } 341 RequestDispatcher { req: Some(req), global_state: self }
277 .on_sync::<lsp_ext::CollectGarbage>(|s, ()| Ok(s.analysis_host.collect_garbage()))? 342 .on_sync::<lsp_ext::ReloadWorkspace>(|s, ()| Ok(s.fetch_workspaces()))?
278 .on_sync::<lsp_ext::JoinLines>(|s, p| handlers::handle_join_lines(s.snapshot(), p))? 343 .on_sync::<lsp_ext::JoinLines>(|s, p| handlers::handle_join_lines(s.snapshot(), p))?
279 .on_sync::<lsp_ext::OnEnter>(|s, p| handlers::handle_on_enter(s.snapshot(), p))? 344 .on_sync::<lsp_ext::OnEnter>(|s, p| handlers::handle_on_enter(s.snapshot(), p))?
280 .on_sync::<lsp_types::request::Shutdown>(|_, ()| Ok(()))? 345 .on_sync::<lsp_types::request::Shutdown>(|_, ()| Ok(()))?
@@ -284,6 +349,7 @@ impl GlobalState {
284 .on_sync::<lsp_ext::MatchingBrace>(|s, p| { 349 .on_sync::<lsp_ext::MatchingBrace>(|s, p| {
285 handlers::handle_matching_brace(s.snapshot(), p) 350 handlers::handle_matching_brace(s.snapshot(), p)
286 })? 351 })?
352 .on_sync::<lsp_ext::MemoryUsage>(|s, p| handlers::handle_memory_usage(s, p))?
287 .on::<lsp_ext::AnalyzerStatus>(handlers::handle_analyzer_status)? 353 .on::<lsp_ext::AnalyzerStatus>(handlers::handle_analyzer_status)?
288 .on::<lsp_ext::SyntaxTree>(handlers::handle_syntax_tree)? 354 .on::<lsp_ext::SyntaxTree>(handlers::handle_syntax_tree)?
289 .on::<lsp_ext::ExpandMacro>(handlers::handle_expand_macro)? 355 .on::<lsp_ext::ExpandMacro>(handlers::handle_expand_macro)?
@@ -340,7 +406,11 @@ impl GlobalState {
340 })? 406 })?
341 .on::<lsp_types::notification::DidOpenTextDocument>(|this, params| { 407 .on::<lsp_types::notification::DidOpenTextDocument>(|this, params| {
342 if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) { 408 if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
343 if !this.mem_docs.insert(path.clone()) { 409 if this
410 .mem_docs
411 .insert(path.clone(), DocumentData::new(params.text_document.version))
412 .is_some()
413 {
344 log::error!("duplicate DidOpenTextDocument: {}", path) 414 log::error!("duplicate DidOpenTextDocument: {}", path)
345 } 415 }
346 this.vfs 416 this.vfs
@@ -352,36 +422,56 @@ impl GlobalState {
352 })? 422 })?
353 .on::<lsp_types::notification::DidChangeTextDocument>(|this, params| { 423 .on::<lsp_types::notification::DidChangeTextDocument>(|this, params| {
354 if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) { 424 if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
355 assert!(this.mem_docs.contains(&path)); 425 let DidChangeTextDocumentParams { text_document, content_changes } = params;
356 let vfs = &mut this.vfs.write().0; 426 let vfs = &mut this.vfs.write().0;
427 let world = this.snapshot();
357 let file_id = vfs.file_id(&path).unwrap(); 428 let file_id = vfs.file_id(&path).unwrap();
429
430 // let file_id = vfs.file_id(&path).unwrap();
358 let mut text = String::from_utf8(vfs.file_contents(file_id).to_vec()).unwrap(); 431 let mut text = String::from_utf8(vfs.file_contents(file_id).to_vec()).unwrap();
359 apply_document_changes(&mut text, params.content_changes); 432 let line_index = world.analysis.file_line_index(file_id)?;
360 vfs.set_file_contents(path, Some(text.into_bytes())) 433 apply_document_changes(&mut text, content_changes, Cow::Borrowed(&line_index));
434
435 // The version passed in DidChangeTextDocument is the version after all edits are applied
436 // so we should apply it before the vfs is notified.
437 let doc = this.mem_docs.get_mut(&path).unwrap();
438 doc.version = text_document.version;
439
440 vfs.set_file_contents(path.clone(), Some(text.into_bytes()));
361 } 441 }
362 Ok(()) 442 Ok(())
363 })? 443 })?
364 .on::<lsp_types::notification::DidCloseTextDocument>(|this, params| { 444 .on::<lsp_types::notification::DidCloseTextDocument>(|this, params| {
445 let mut version = None;
365 if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) { 446 if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
366 if !this.mem_docs.remove(&path) { 447 match this.mem_docs.remove(&path) {
367 log::error!("orphan DidCloseTextDocument: {}", path) 448 Some(doc) => version = doc.version,
449 None => log::error!("orphan DidCloseTextDocument: {}", path),
368 } 450 }
451
369 if let Some(path) = path.as_path() { 452 if let Some(path) = path.as_path() {
370 this.loader.handle.invalidate(path.to_path_buf()); 453 this.loader.handle.invalidate(path.to_path_buf());
371 } 454 }
372 } 455 }
456
457 // Clear the diagnostics for the previously known version of the file.
458 // This prevents stale "cargo check" diagnostics if the file is
459 // closed, "cargo check" is run and then the file is reopened.
373 this.send_notification::<lsp_types::notification::PublishDiagnostics>( 460 this.send_notification::<lsp_types::notification::PublishDiagnostics>(
374 lsp_types::PublishDiagnosticsParams { 461 lsp_types::PublishDiagnosticsParams {
375 uri: params.text_document.uri, 462 uri: params.text_document.uri,
376 diagnostics: Vec::new(), 463 diagnostics: Vec::new(),
377 version: None, 464 version,
378 }, 465 },
379 ); 466 );
380 Ok(()) 467 Ok(())
381 })? 468 })?
382 .on::<lsp_types::notification::DidSaveTextDocument>(|this, _params| { 469 .on::<lsp_types::notification::DidSaveTextDocument>(|this, params| {
383 if let Some(flycheck) = &this.flycheck { 470 if let Some(flycheck) = &this.flycheck {
384 flycheck.handle.update(); 471 flycheck.update();
472 }
473 if let Ok(abs_path) = from_proto::abs_path(&params.text_document.uri) {
474 this.maybe_refresh(&[(abs_path, ChangeKind::Modify)]);
385 } 475 }
386 Ok(()) 476 Ok(())
387 })? 477 })?
@@ -403,10 +493,12 @@ impl GlobalState {
403 (Some(err), _) => { 493 (Some(err), _) => {
404 log::error!("failed to fetch the server settings: {:?}", err) 494 log::error!("failed to fetch the server settings: {:?}", err)
405 } 495 }
406 (None, Some(configs)) => { 496 (None, Some(mut configs)) => {
407 if let Some(new_config) = configs.get(0) { 497 if let Some(json) = configs.get_mut(0) {
498 // Note that json can be null according to the spec if the client can't
499 // provide a configuration. This is handled in Config::update below.
408 let mut config = this.config.clone(); 500 let mut config = this.config.clone();
409 config.update(&new_config); 501 config.update(json.take());
410 this.update_configuration(config); 502 this.update_configuration(config);
411 } 503 }
412 } 504 }