diff options
Diffstat (limited to 'crates/ra_lsp_server')
-rw-r--r-- | crates/ra_lsp_server/Cargo.toml | 5 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/diagnostics.rs | 85 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/lib.rs | 1 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop.rs | 139 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/handlers.rs | 121 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/world.rs | 11 |
6 files changed, 246 insertions, 116 deletions
diff --git a/crates/ra_lsp_server/Cargo.toml b/crates/ra_lsp_server/Cargo.toml index 4ee3fb49f..fdf81ed87 100644 --- a/crates/ra_lsp_server/Cargo.toml +++ b/crates/ra_lsp_server/Cargo.toml | |||
@@ -26,10 +26,13 @@ lsp-server = "0.3.0" | |||
26 | ra_project_model = { path = "../ra_project_model" } | 26 | ra_project_model = { path = "../ra_project_model" } |
27 | ra_prof = { path = "../ra_prof" } | 27 | ra_prof = { path = "../ra_prof" } |
28 | ra_vfs_glob = { path = "../ra_vfs_glob" } | 28 | ra_vfs_glob = { path = "../ra_vfs_glob" } |
29 | env_logger = { version = "0.7.1", default-features = false, features = ["humantime"] } | 29 | env_logger = { version = "0.7.1", default-features = false } |
30 | ra_cargo_watch = { path = "../ra_cargo_watch" } | 30 | ra_cargo_watch = { path = "../ra_cargo_watch" } |
31 | either = "1.5" | 31 | either = "1.5" |
32 | 32 | ||
33 | [target.'cfg(windows)'.dependencies] | ||
34 | winapi = "0.3" | ||
35 | |||
33 | [dev-dependencies] | 36 | [dev-dependencies] |
34 | tempfile = "3" | 37 | tempfile = "3" |
35 | test_utils = { path = "../test_utils" } | 38 | test_utils = { path = "../test_utils" } |
diff --git a/crates/ra_lsp_server/src/diagnostics.rs b/crates/ra_lsp_server/src/diagnostics.rs new file mode 100644 index 000000000..ea08bce24 --- /dev/null +++ b/crates/ra_lsp_server/src/diagnostics.rs | |||
@@ -0,0 +1,85 @@ | |||
1 | //! Book keeping for keeping diagnostics easily in sync with the client. | ||
2 | use lsp_types::{CodeActionOrCommand, Diagnostic, Range}; | ||
3 | use ra_ide::FileId; | ||
4 | use std::{collections::HashMap, sync::Arc}; | ||
5 | |||
6 | pub type CheckFixes = Arc<HashMap<FileId, Vec<Fix>>>; | ||
7 | |||
8 | #[derive(Debug, Default, Clone)] | ||
9 | pub struct DiagnosticCollection { | ||
10 | pub native: HashMap<FileId, Vec<Diagnostic>>, | ||
11 | pub check: HashMap<FileId, Vec<Diagnostic>>, | ||
12 | pub check_fixes: CheckFixes, | ||
13 | } | ||
14 | |||
15 | #[derive(Debug, Clone)] | ||
16 | pub struct Fix { | ||
17 | pub range: Range, | ||
18 | pub action: CodeActionOrCommand, | ||
19 | } | ||
20 | |||
21 | #[derive(Debug)] | ||
22 | pub enum DiagnosticTask { | ||
23 | ClearCheck, | ||
24 | AddCheck(FileId, Diagnostic, Vec<CodeActionOrCommand>), | ||
25 | SetNative(FileId, Vec<Diagnostic>), | ||
26 | } | ||
27 | |||
28 | impl DiagnosticCollection { | ||
29 | pub fn clear_check(&mut self) -> Vec<FileId> { | ||
30 | Arc::make_mut(&mut self.check_fixes).clear(); | ||
31 | self.check.drain().map(|(key, _value)| key).collect() | ||
32 | } | ||
33 | |||
34 | pub fn add_check_diagnostic( | ||
35 | &mut self, | ||
36 | file_id: FileId, | ||
37 | diagnostic: Diagnostic, | ||
38 | fixes: Vec<CodeActionOrCommand>, | ||
39 | ) { | ||
40 | let diagnostics = self.check.entry(file_id).or_default(); | ||
41 | for existing_diagnostic in diagnostics.iter() { | ||
42 | if are_diagnostics_equal(&existing_diagnostic, &diagnostic) { | ||
43 | return; | ||
44 | } | ||
45 | } | ||
46 | |||
47 | let check_fixes = Arc::make_mut(&mut self.check_fixes); | ||
48 | check_fixes | ||
49 | .entry(file_id) | ||
50 | .or_default() | ||
51 | .extend(fixes.into_iter().map(|action| Fix { range: diagnostic.range, action })); | ||
52 | diagnostics.push(diagnostic); | ||
53 | } | ||
54 | |||
55 | pub fn set_native_diagnostics(&mut self, file_id: FileId, diagnostics: Vec<Diagnostic>) { | ||
56 | self.native.insert(file_id, diagnostics); | ||
57 | } | ||
58 | |||
59 | pub fn diagnostics_for(&self, file_id: FileId) -> impl Iterator<Item = &Diagnostic> { | ||
60 | let native = self.native.get(&file_id).into_iter().flatten(); | ||
61 | let check = self.check.get(&file_id).into_iter().flatten(); | ||
62 | native.chain(check) | ||
63 | } | ||
64 | |||
65 | pub fn handle_task(&mut self, task: DiagnosticTask) -> Vec<FileId> { | ||
66 | match task { | ||
67 | DiagnosticTask::ClearCheck => self.clear_check(), | ||
68 | DiagnosticTask::AddCheck(file_id, diagnostic, fixes) => { | ||
69 | self.add_check_diagnostic(file_id, diagnostic, fixes); | ||
70 | vec![file_id] | ||
71 | } | ||
72 | DiagnosticTask::SetNative(file_id, diagnostics) => { | ||
73 | self.set_native_diagnostics(file_id, diagnostics); | ||
74 | vec![file_id] | ||
75 | } | ||
76 | } | ||
77 | } | ||
78 | } | ||
79 | |||
80 | fn are_diagnostics_equal(left: &Diagnostic, right: &Diagnostic) -> bool { | ||
81 | left.source == right.source | ||
82 | && left.severity == right.severity | ||
83 | && left.range == right.range | ||
84 | && left.message == right.message | ||
85 | } | ||
diff --git a/crates/ra_lsp_server/src/lib.rs b/crates/ra_lsp_server/src/lib.rs index 2ca149fd5..1208c1343 100644 --- a/crates/ra_lsp_server/src/lib.rs +++ b/crates/ra_lsp_server/src/lib.rs | |||
@@ -29,6 +29,7 @@ mod markdown; | |||
29 | pub mod req; | 29 | pub mod req; |
30 | mod config; | 30 | mod config; |
31 | mod world; | 31 | mod world; |
32 | mod diagnostics; | ||
32 | 33 | ||
33 | pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; | 34 | pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; |
34 | pub use crate::{ | 35 | pub use crate::{ |
diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs index 7822be2e2..ceff82fda 100644 --- a/crates/ra_lsp_server/src/main_loop.rs +++ b/crates/ra_lsp_server/src/main_loop.rs | |||
@@ -5,21 +5,29 @@ mod handlers; | |||
5 | mod subscriptions; | 5 | mod subscriptions; |
6 | pub(crate) mod pending_requests; | 6 | pub(crate) mod pending_requests; |
7 | 7 | ||
8 | use std::{error::Error, fmt, panic, path::PathBuf, sync::Arc, time::Instant}; | 8 | use std::{ |
9 | env, | ||
10 | error::Error, | ||
11 | fmt, panic, | ||
12 | path::PathBuf, | ||
13 | sync::Arc, | ||
14 | time::{Duration, Instant}, | ||
15 | }; | ||
9 | 16 | ||
10 | use crossbeam_channel::{select, unbounded, RecvError, Sender}; | 17 | use crossbeam_channel::{select, unbounded, RecvError, Sender}; |
11 | use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; | 18 | use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; |
12 | use lsp_types::{ClientCapabilities, NumberOrString, Url}; | 19 | use lsp_types::{ClientCapabilities, NumberOrString}; |
13 | use ra_cargo_watch::{CheckOptions, CheckTask}; | 20 | use ra_cargo_watch::{url_from_path_with_drive_lowercasing, CheckOptions, CheckTask}; |
14 | use ra_ide::{Canceled, FeatureFlags, FileId, LibraryData, SourceRootId}; | 21 | use ra_ide::{Canceled, FeatureFlags, FileId, LibraryData, SourceRootId}; |
15 | use ra_prof::profile; | 22 | use ra_prof::profile; |
16 | use ra_vfs::{VfsTask, Watch}; | 23 | use ra_vfs::{VfsFile, VfsTask, Watch}; |
17 | use relative_path::RelativePathBuf; | 24 | use relative_path::RelativePathBuf; |
18 | use rustc_hash::FxHashSet; | 25 | use rustc_hash::FxHashSet; |
19 | use serde::{de::DeserializeOwned, Serialize}; | 26 | use serde::{de::DeserializeOwned, Serialize}; |
20 | use threadpool::ThreadPool; | 27 | use threadpool::ThreadPool; |
21 | 28 | ||
22 | use crate::{ | 29 | use crate::{ |
30 | diagnostics::DiagnosticTask, | ||
23 | main_loop::{ | 31 | main_loop::{ |
24 | pending_requests::{PendingRequest, PendingRequests}, | 32 | pending_requests::{PendingRequest, PendingRequests}, |
25 | subscriptions::Subscriptions, | 33 | subscriptions::Subscriptions, |
@@ -29,9 +37,6 @@ use crate::{ | |||
29 | Result, ServerConfig, | 37 | Result, ServerConfig, |
30 | }; | 38 | }; |
31 | 39 | ||
32 | const THREADPOOL_SIZE: usize = 8; | ||
33 | const MAX_IN_FLIGHT_LIBS: usize = THREADPOOL_SIZE - 3; | ||
34 | |||
35 | #[derive(Debug)] | 40 | #[derive(Debug)] |
36 | pub struct LspError { | 41 | pub struct LspError { |
37 | pub code: i32, | 42 | pub code: i32, |
@@ -60,6 +65,25 @@ pub fn main_loop( | |||
60 | ) -> Result<()> { | 65 | ) -> Result<()> { |
61 | log::info!("server_config: {:#?}", config); | 66 | log::info!("server_config: {:#?}", config); |
62 | 67 | ||
68 | // Windows scheduler implements priority boosts: if thread waits for an | ||
69 | // event (like a condvar), and event fires, priority of the thread is | ||
70 | // temporary bumped. This optimization backfires in our case: each time the | ||
71 | // `main_loop` schedules a task to run on a threadpool, the worker threads | ||
72 | // gets a higher priority, and (on a machine with fewer cores) displaces the | ||
73 | // main loop! We work-around this by marking the main loop as a | ||
74 | // higher-priority thread. | ||
75 | // | ||
76 | // https://docs.microsoft.com/en-us/windows/win32/procthread/scheduling-priorities | ||
77 | // https://docs.microsoft.com/en-us/windows/win32/procthread/priority-boosts | ||
78 | // https://github.com/rust-analyzer/rust-analyzer/issues/2835 | ||
79 | #[cfg(windows)] | ||
80 | unsafe { | ||
81 | use winapi::um::processthreadsapi::*; | ||
82 | let thread = GetCurrentThread(); | ||
83 | let thread_priority_above_normal = 1; | ||
84 | SetThreadPriority(thread, thread_priority_above_normal); | ||
85 | } | ||
86 | |||
63 | let mut loop_state = LoopState::default(); | 87 | let mut loop_state = LoopState::default(); |
64 | let mut world_state = { | 88 | let mut world_state = { |
65 | let feature_flags = { | 89 | let feature_flags = { |
@@ -168,7 +192,7 @@ pub fn main_loop( | |||
168 | ) | 192 | ) |
169 | }; | 193 | }; |
170 | 194 | ||
171 | let pool = ThreadPool::new(THREADPOOL_SIZE); | 195 | let pool = ThreadPool::default(); |
172 | let (task_sender, task_receiver) = unbounded::<Task>(); | 196 | let (task_sender, task_receiver) = unbounded::<Task>(); |
173 | let (libdata_sender, libdata_receiver) = unbounded::<LibraryData>(); | 197 | let (libdata_sender, libdata_receiver) = unbounded::<LibraryData>(); |
174 | 198 | ||
@@ -210,7 +234,7 @@ pub fn main_loop( | |||
210 | )?; | 234 | )?; |
211 | } | 235 | } |
212 | } | 236 | } |
213 | 237 | world_state.analysis_host.request_cancellation(); | |
214 | log::info!("waiting for tasks to finish..."); | 238 | log::info!("waiting for tasks to finish..."); |
215 | task_receiver.into_iter().for_each(|task| { | 239 | task_receiver.into_iter().for_each(|task| { |
216 | on_task(task, &connection.sender, &mut loop_state.pending_requests, &mut world_state) | 240 | on_task(task, &connection.sender, &mut loop_state.pending_requests, &mut world_state) |
@@ -231,6 +255,7 @@ pub fn main_loop( | |||
231 | enum Task { | 255 | enum Task { |
232 | Respond(Response), | 256 | Respond(Response), |
233 | Notify(Notification), | 257 | Notify(Notification), |
258 | Diagnostic(DiagnosticTask), | ||
234 | } | 259 | } |
235 | 260 | ||
236 | enum Event { | 261 | enum Event { |
@@ -371,7 +396,8 @@ fn loop_turn( | |||
371 | loop_state.pending_libraries.extend(changes); | 396 | loop_state.pending_libraries.extend(changes); |
372 | } | 397 | } |
373 | 398 | ||
374 | while loop_state.in_flight_libraries < MAX_IN_FLIGHT_LIBS | 399 | let max_in_flight_libs = pool.max_count().saturating_sub(2).max(1); |
400 | while loop_state.in_flight_libraries < max_in_flight_libs | ||
375 | && !loop_state.pending_libraries.is_empty() | 401 | && !loop_state.pending_libraries.is_empty() |
376 | { | 402 | { |
377 | let (root, files) = loop_state.pending_libraries.pop().unwrap(); | 403 | let (root, files) = loop_state.pending_libraries.pop().unwrap(); |
@@ -379,7 +405,6 @@ fn loop_turn( | |||
379 | let sender = libdata_sender.clone(); | 405 | let sender = libdata_sender.clone(); |
380 | pool.execute(move || { | 406 | pool.execute(move || { |
381 | log::info!("indexing {:?} ... ", root); | 407 | log::info!("indexing {:?} ... ", root); |
382 | let _p = profile(&format!("indexed {:?}", root)); | ||
383 | let data = LibraryData::prepare(root, files); | 408 | let data = LibraryData::prepare(root, files); |
384 | sender.send(data).unwrap(); | 409 | sender.send(data).unwrap(); |
385 | }); | 410 | }); |
@@ -408,6 +433,19 @@ fn loop_turn( | |||
408 | loop_state.subscriptions.subscriptions(), | 433 | loop_state.subscriptions.subscriptions(), |
409 | ) | 434 | ) |
410 | } | 435 | } |
436 | |||
437 | let loop_duration = loop_start.elapsed(); | ||
438 | if loop_duration > Duration::from_millis(100) { | ||
439 | log::error!("overly long loop turn: {:?}", loop_duration); | ||
440 | if env::var("RA_PROFILE").is_ok() { | ||
441 | show_message( | ||
442 | req::MessageType::Error, | ||
443 | format!("overly long loop turn: {:?}", loop_duration), | ||
444 | &connection.sender, | ||
445 | ); | ||
446 | } | ||
447 | } | ||
448 | |||
411 | Ok(()) | 449 | Ok(()) |
412 | } | 450 | } |
413 | 451 | ||
@@ -428,6 +466,7 @@ fn on_task( | |||
428 | Task::Notify(n) => { | 466 | Task::Notify(n) => { |
429 | msg_sender.send(n.into()).unwrap(); | 467 | msg_sender.send(n.into()).unwrap(); |
430 | } | 468 | } |
469 | Task::Diagnostic(task) => on_diagnostic_task(task, msg_sender, state), | ||
431 | } | 470 | } |
432 | } | 471 | } |
433 | 472 | ||
@@ -435,7 +474,7 @@ fn on_request( | |||
435 | world: &mut WorldState, | 474 | world: &mut WorldState, |
436 | pending_requests: &mut PendingRequests, | 475 | pending_requests: &mut PendingRequests, |
437 | pool: &ThreadPool, | 476 | pool: &ThreadPool, |
438 | sender: &Sender<Task>, | 477 | task_sender: &Sender<Task>, |
439 | msg_sender: &Sender<Message>, | 478 | msg_sender: &Sender<Message>, |
440 | request_received: Instant, | 479 | request_received: Instant, |
441 | req: Request, | 480 | req: Request, |
@@ -444,7 +483,7 @@ fn on_request( | |||
444 | req: Some(req), | 483 | req: Some(req), |
445 | pool, | 484 | pool, |
446 | world, | 485 | world, |
447 | sender, | 486 | task_sender, |
448 | msg_sender, | 487 | msg_sender, |
449 | pending_requests, | 488 | pending_requests, |
450 | request_received, | 489 | request_received, |
@@ -586,30 +625,26 @@ fn on_notification( | |||
586 | 625 | ||
587 | fn on_check_task( | 626 | fn on_check_task( |
588 | task: CheckTask, | 627 | task: CheckTask, |
589 | world_state: &WorldState, | 628 | world_state: &mut WorldState, |
590 | task_sender: &Sender<Task>, | 629 | task_sender: &Sender<Task>, |
591 | ) -> Result<()> { | 630 | ) -> Result<()> { |
592 | match task { | 631 | match task { |
593 | CheckTask::ClearDiagnostics => { | 632 | CheckTask::ClearDiagnostics => { |
594 | let cleared_files = world_state.check_watcher.state.write().clear(); | 633 | task_sender.send(Task::Diagnostic(DiagnosticTask::ClearCheck))?; |
595 | |||
596 | // Send updated diagnostics for each cleared file | ||
597 | for url in cleared_files { | ||
598 | publish_diagnostics_for_url(&url, world_state, task_sender)?; | ||
599 | } | ||
600 | } | 634 | } |
601 | 635 | ||
602 | CheckTask::AddDiagnostic(url, diagnostic) => { | 636 | CheckTask::AddDiagnostic { url, diagnostic, fixes } => { |
603 | world_state | 637 | let path = url.to_file_path().map_err(|()| format!("invalid uri: {}", url))?; |
604 | .check_watcher | 638 | let file_id = match world_state.vfs.read().path2file(&path) { |
605 | .state | 639 | Some(file) => FileId(file.0), |
606 | .write() | 640 | None => { |
607 | .add_diagnostic_with_fixes(url.clone(), diagnostic); | 641 | log::error!("File with cargo diagnostic not found in VFS: {}", path.display()); |
608 | 642 | return Ok(()); | |
609 | // We manually send a diagnostic update when the watcher asks | 643 | } |
610 | // us to, to avoid the issue of having to change the file to | 644 | }; |
611 | // receive updated diagnostics. | 645 | |
612 | publish_diagnostics_for_url(&url, world_state, task_sender)?; | 646 | task_sender |
647 | .send(Task::Diagnostic(DiagnosticTask::AddCheck(file_id, diagnostic, fixes)))?; | ||
613 | } | 648 | } |
614 | 649 | ||
615 | CheckTask::Status(progress) => { | 650 | CheckTask::Status(progress) => { |
@@ -620,22 +655,29 @@ fn on_check_task( | |||
620 | let not = notification_new::<req::Progress>(params); | 655 | let not = notification_new::<req::Progress>(params); |
621 | task_sender.send(Task::Notify(not)).unwrap(); | 656 | task_sender.send(Task::Notify(not)).unwrap(); |
622 | } | 657 | } |
623 | } | 658 | }; |
659 | |||
624 | Ok(()) | 660 | Ok(()) |
625 | } | 661 | } |
626 | 662 | ||
627 | fn publish_diagnostics_for_url( | 663 | fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender<Message>, state: &mut WorldState) { |
628 | url: &Url, | 664 | let subscriptions = state.diagnostics.handle_task(task); |
629 | world_state: &WorldState, | 665 | |
630 | task_sender: &Sender<Task>, | 666 | for file_id in subscriptions { |
631 | ) -> Result<()> { | 667 | let path = state.vfs.read().file2path(VfsFile(file_id.0)); |
632 | let path = url.to_file_path().map_err(|()| format!("invalid uri: {}", url))?; | 668 | let uri = match url_from_path_with_drive_lowercasing(&path) { |
633 | if let Some(file_id) = world_state.vfs.read().path2file(&path) { | 669 | Ok(uri) => uri, |
634 | let params = handlers::publish_diagnostics(&world_state.snapshot(), FileId(file_id.0))?; | 670 | Err(err) => { |
671 | log::error!("Couldn't convert path to url ({}): {:?}", err, path.to_string_lossy()); | ||
672 | continue; | ||
673 | } | ||
674 | }; | ||
675 | |||
676 | let diagnostics = state.diagnostics.diagnostics_for(file_id).cloned().collect(); | ||
677 | let params = req::PublishDiagnosticsParams { uri, diagnostics, version: None }; | ||
635 | let not = notification_new::<req::PublishDiagnostics>(params); | 678 | let not = notification_new::<req::PublishDiagnostics>(params); |
636 | task_sender.send(Task::Notify(not)).unwrap(); | 679 | msg_sender.send(not.into()).unwrap(); |
637 | } | 680 | } |
638 | Ok(()) | ||
639 | } | 681 | } |
640 | 682 | ||
641 | struct PoolDispatcher<'a> { | 683 | struct PoolDispatcher<'a> { |
@@ -644,7 +686,7 @@ struct PoolDispatcher<'a> { | |||
644 | world: &'a mut WorldState, | 686 | world: &'a mut WorldState, |
645 | pending_requests: &'a mut PendingRequests, | 687 | pending_requests: &'a mut PendingRequests, |
646 | msg_sender: &'a Sender<Message>, | 688 | msg_sender: &'a Sender<Message>, |
647 | sender: &'a Sender<Task>, | 689 | task_sender: &'a Sender<Task>, |
648 | request_received: Instant, | 690 | request_received: Instant, |
649 | } | 691 | } |
650 | 692 | ||
@@ -691,7 +733,7 @@ impl<'a> PoolDispatcher<'a> { | |||
691 | 733 | ||
692 | self.pool.execute({ | 734 | self.pool.execute({ |
693 | let world = self.world.snapshot(); | 735 | let world = self.world.snapshot(); |
694 | let sender = self.sender.clone(); | 736 | let sender = self.task_sender.clone(); |
695 | move || { | 737 | move || { |
696 | let result = f(world, params); | 738 | let result = f(world, params); |
697 | let task = result_to_task::<R>(id, result); | 739 | let task = result_to_task::<R>(id, result); |
@@ -769,7 +811,7 @@ fn update_file_notifications_on_threadpool( | |||
769 | pool: &ThreadPool, | 811 | pool: &ThreadPool, |
770 | world: WorldSnapshot, | 812 | world: WorldSnapshot, |
771 | publish_decorations: bool, | 813 | publish_decorations: bool, |
772 | sender: Sender<Task>, | 814 | task_sender: Sender<Task>, |
773 | subscriptions: Vec<FileId>, | 815 | subscriptions: Vec<FileId>, |
774 | ) { | 816 | ) { |
775 | log::trace!("updating notifications for {:?}", subscriptions); | 817 | log::trace!("updating notifications for {:?}", subscriptions); |
@@ -783,9 +825,8 @@ fn update_file_notifications_on_threadpool( | |||
783 | log::error!("failed to compute diagnostics: {:?}", e); | 825 | log::error!("failed to compute diagnostics: {:?}", e); |
784 | } | 826 | } |
785 | } | 827 | } |
786 | Ok(params) => { | 828 | Ok(task) => { |
787 | let not = notification_new::<req::PublishDiagnostics>(params); | 829 | task_sender.send(Task::Diagnostic(task)).unwrap(); |
788 | sender.send(Task::Notify(not)).unwrap(); | ||
789 | } | 830 | } |
790 | } | 831 | } |
791 | } | 832 | } |
@@ -798,7 +839,7 @@ fn update_file_notifications_on_threadpool( | |||
798 | } | 839 | } |
799 | Ok(params) => { | 840 | Ok(params) => { |
800 | let not = notification_new::<req::PublishDecorations>(params); | 841 | let not = notification_new::<req::PublishDecorations>(params); |
801 | sender.send(Task::Notify(not)).unwrap(); | 842 | task_sender.send(Task::Notify(not)).unwrap(); |
802 | } | 843 | } |
803 | } | 844 | } |
804 | } | 845 | } |
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index 8e43f0575..65e8bc856 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs | |||
@@ -1,17 +1,22 @@ | |||
1 | //! This module is responsible for implementing handlers for Lanuage Server Protocol. | 1 | //! This module is responsible for implementing handlers for Lanuage Server Protocol. |
2 | //! The majority of requests are fulfilled by calling into the `ra_ide` crate. | 2 | //! The majority of requests are fulfilled by calling into the `ra_ide` crate. |
3 | 3 | ||
4 | use std::{fmt::Write as _, io::Write as _}; | 4 | use std::{ |
5 | collections::hash_map::Entry, | ||
6 | fmt::Write as _, | ||
7 | io::Write as _, | ||
8 | process::{self, Stdio}, | ||
9 | }; | ||
5 | 10 | ||
6 | use either::Either; | ||
7 | use lsp_server::ErrorCode; | 11 | use lsp_server::ErrorCode; |
8 | use lsp_types::{ | 12 | use lsp_types::{ |
9 | CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, | 13 | CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, |
10 | CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams, | 14 | CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams, |
11 | CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic, | 15 | CodeAction, CodeActionOrCommand, CodeActionResponse, CodeLens, Command, CompletionItem, |
12 | DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeParams, | 16 | Diagnostic, DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, |
13 | Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse, | 17 | FoldingRangeParams, Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, |
14 | Range, RenameParams, SymbolInformation, TextDocumentIdentifier, TextEdit, WorkspaceEdit, | 18 | PrepareRenameResponse, Range, RenameParams, SymbolInformation, TextDocumentIdentifier, |
19 | TextEdit, WorkspaceEdit, | ||
15 | }; | 20 | }; |
16 | use ra_ide::{ | 21 | use ra_ide::{ |
17 | AssistId, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind, | 22 | AssistId, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind, |
@@ -29,6 +34,7 @@ use crate::{ | |||
29 | to_call_hierarchy_item, to_location, Conv, ConvWith, FoldConvCtx, MapConvWith, TryConvWith, | 34 | to_call_hierarchy_item, to_location, Conv, ConvWith, FoldConvCtx, MapConvWith, TryConvWith, |
30 | TryConvWithToVec, | 35 | TryConvWithToVec, |
31 | }, | 36 | }, |
37 | diagnostics::DiagnosticTask, | ||
32 | req::{self, Decoration, InlayHint, InlayHintsParams, InlayKind}, | 38 | req::{self, Decoration, InlayHint, InlayHintsParams, InlayKind}, |
33 | world::WorldSnapshot, | 39 | world::WorldSnapshot, |
34 | LspError, Result, | 40 | LspError, Result, |
@@ -582,21 +588,19 @@ pub fn handle_formatting( | |||
582 | let file_line_index = world.analysis().file_line_index(file_id)?; | 588 | let file_line_index = world.analysis().file_line_index(file_id)?; |
583 | let end_position = TextUnit::of_str(&file).conv_with(&file_line_index); | 589 | let end_position = TextUnit::of_str(&file).conv_with(&file_line_index); |
584 | 590 | ||
585 | use std::process; | ||
586 | let mut rustfmt = process::Command::new("rustfmt"); | 591 | let mut rustfmt = process::Command::new("rustfmt"); |
587 | if let Some(&crate_id) = crate_ids.first() { | 592 | if let Some(&crate_id) = crate_ids.first() { |
588 | // Assume all crates are in the same edition | 593 | // Assume all crates are in the same edition |
589 | let edition = world.analysis().crate_edition(crate_id)?; | 594 | let edition = world.analysis().crate_edition(crate_id)?; |
590 | rustfmt.args(&["--edition", &edition.to_string()]); | 595 | rustfmt.args(&["--edition", &edition.to_string()]); |
591 | } | 596 | } |
592 | rustfmt.stdin(process::Stdio::piped()).stdout(process::Stdio::piped()); | ||
593 | 597 | ||
594 | if let Ok(path) = params.text_document.uri.to_file_path() { | 598 | if let Ok(path) = params.text_document.uri.to_file_path() { |
595 | if let Some(parent) = path.parent() { | 599 | if let Some(parent) = path.parent() { |
596 | rustfmt.current_dir(parent); | 600 | rustfmt.current_dir(parent); |
597 | } | 601 | } |
598 | } | 602 | } |
599 | let mut rustfmt = rustfmt.spawn()?; | 603 | let mut rustfmt = rustfmt.stdin(Stdio::piped()).stdout(Stdio::piped()).spawn()?; |
600 | 604 | ||
601 | rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?; | 605 | rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?; |
602 | 606 | ||
@@ -674,59 +678,61 @@ pub fn handle_code_action( | |||
674 | res.push(action.into()); | 678 | res.push(action.into()); |
675 | } | 679 | } |
676 | 680 | ||
677 | for fix in world.check_watcher.read().fixes_for(¶ms.text_document.uri).into_iter().flatten() | 681 | for fix in world.check_fixes.get(&file_id).into_iter().flatten() { |
678 | { | 682 | let fix_range = fix.range.conv_with(&line_index); |
679 | let fix_range = fix.location.range.conv_with(&line_index); | ||
680 | if fix_range.intersection(&range).is_none() { | 683 | if fix_range.intersection(&range).is_none() { |
681 | continue; | 684 | continue; |
682 | } | 685 | } |
686 | res.push(fix.action.clone()); | ||
687 | } | ||
683 | 688 | ||
684 | let edit = { | 689 | let mut groups = FxHashMap::default(); |
685 | let edits = vec![TextEdit::new(fix.location.range, fix.replacement.clone())]; | 690 | for assist in world.analysis().assists(FileRange { file_id, range })?.into_iter() { |
686 | let mut edit_map = std::collections::HashMap::new(); | 691 | let arg = to_value(assist.source_change.try_conv_with(&world)?)?; |
687 | edit_map.insert(fix.location.uri.clone(), edits); | 692 | |
688 | WorkspaceEdit::new(edit_map) | 693 | let (command, title, arg) = match assist.group_label { |
694 | None => ("rust-analyzer.applySourceChange", assist.label.clone(), arg), | ||
695 | |||
696 | // Group all assists with the same `group_label` into a single CodeAction. | ||
697 | Some(group_label) => { | ||
698 | match groups.entry(group_label.clone()) { | ||
699 | Entry::Occupied(entry) => { | ||
700 | let idx: usize = *entry.get(); | ||
701 | match &mut res[idx] { | ||
702 | CodeActionOrCommand::CodeAction(CodeAction { | ||
703 | command: Some(Command { arguments: Some(arguments), .. }), | ||
704 | .. | ||
705 | }) => match arguments.as_mut_slice() { | ||
706 | [serde_json::Value::Array(arguments)] => arguments.push(arg), | ||
707 | _ => panic!("invalid group"), | ||
708 | }, | ||
709 | _ => panic!("invalid group"), | ||
710 | } | ||
711 | continue; | ||
712 | } | ||
713 | Entry::Vacant(entry) => { | ||
714 | entry.insert(res.len()); | ||
715 | } | ||
716 | } | ||
717 | ("rust-analyzer.selectAndApplySourceChange", group_label, to_value(vec![arg])?) | ||
718 | } | ||
689 | }; | 719 | }; |
690 | 720 | ||
691 | let action = CodeAction { | 721 | let command = Command { |
692 | title: fix.title.clone(), | 722 | title: assist.label.clone(), |
693 | kind: Some("quickfix".to_string()), | 723 | command: command.to_string(), |
694 | diagnostics: Some(fix.diagnostics.clone()), | 724 | arguments: Some(vec![arg]), |
695 | edit: Some(edit), | ||
696 | command: None, | ||
697 | is_preferred: None, | ||
698 | }; | 725 | }; |
699 | res.push(action.into()); | ||
700 | } | ||
701 | 726 | ||
702 | for assist in world.analysis().assists(FileRange { file_id, range })?.into_iter() { | 727 | let kind = match assist.id { |
703 | let title = assist.label.clone(); | 728 | AssistId("introduce_variable") => Some("refactor.extract.variable".to_string()), |
704 | 729 | AssistId("add_custom_impl") => Some("refactor.rewrite.add_custom_impl".to_string()), | |
705 | let command = match assist.change_data { | 730 | _ => None, |
706 | Either::Left(change) => Command { | ||
707 | title, | ||
708 | command: "rust-analyzer.applySourceChange".to_string(), | ||
709 | arguments: Some(vec![to_value(change.try_conv_with(&world)?)?]), | ||
710 | }, | ||
711 | Either::Right(changes) => Command { | ||
712 | title, | ||
713 | command: "rust-analyzer.selectAndApplySourceChange".to_string(), | ||
714 | arguments: Some(vec![to_value( | ||
715 | changes | ||
716 | .into_iter() | ||
717 | .map(|change| change.try_conv_with(&world)) | ||
718 | .collect::<Result<Vec<_>>>()?, | ||
719 | )?]), | ||
720 | }, | ||
721 | }; | 731 | }; |
722 | 732 | ||
723 | let action = CodeAction { | 733 | let action = CodeAction { |
724 | title: command.title.clone(), | 734 | title, |
725 | kind: match assist.id { | 735 | kind, |
726 | AssistId("introduce_variable") => Some("refactor.extract.variable".to_string()), | ||
727 | AssistId("add_custom_impl") => Some("refactor.rewrite.add_custom_impl".to_string()), | ||
728 | _ => None, | ||
729 | }, | ||
730 | diagnostics: None, | 736 | diagnostics: None, |
731 | edit: None, | 737 | edit: None, |
732 | command: Some(command), | 738 | command: Some(command), |
@@ -874,14 +880,10 @@ pub fn handle_document_highlight( | |||
874 | )) | 880 | )) |
875 | } | 881 | } |
876 | 882 | ||
877 | pub fn publish_diagnostics( | 883 | pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result<DiagnosticTask> { |
878 | world: &WorldSnapshot, | ||
879 | file_id: FileId, | ||
880 | ) -> Result<req::PublishDiagnosticsParams> { | ||
881 | let _p = profile("publish_diagnostics"); | 884 | let _p = profile("publish_diagnostics"); |
882 | let uri = world.file_id_to_uri(file_id)?; | ||
883 | let line_index = world.analysis().file_line_index(file_id)?; | 885 | let line_index = world.analysis().file_line_index(file_id)?; |
884 | let mut diagnostics: Vec<Diagnostic> = world | 886 | let diagnostics: Vec<Diagnostic> = world |
885 | .analysis() | 887 | .analysis() |
886 | .diagnostics(file_id)? | 888 | .diagnostics(file_id)? |
887 | .into_iter() | 889 | .into_iter() |
@@ -895,10 +897,7 @@ pub fn publish_diagnostics( | |||
895 | tags: None, | 897 | tags: None, |
896 | }) | 898 | }) |
897 | .collect(); | 899 | .collect(); |
898 | if let Some(check_diags) = world.check_watcher.read().diagnostics_for(&uri) { | 900 | Ok(DiagnosticTask::SetNative(file_id, diagnostics)) |
899 | diagnostics.extend(check_diags.iter().cloned()); | ||
900 | } | ||
901 | Ok(req::PublishDiagnosticsParams { uri, diagnostics, version: None }) | ||
902 | } | 901 | } |
903 | 902 | ||
904 | pub fn publish_decorations( | 903 | pub fn publish_decorations( |
diff --git a/crates/ra_lsp_server/src/world.rs b/crates/ra_lsp_server/src/world.rs index e7a0acfc7..1ee02b47c 100644 --- a/crates/ra_lsp_server/src/world.rs +++ b/crates/ra_lsp_server/src/world.rs | |||
@@ -12,9 +12,7 @@ use crossbeam_channel::{unbounded, Receiver}; | |||
12 | use lsp_server::ErrorCode; | 12 | use lsp_server::ErrorCode; |
13 | use lsp_types::Url; | 13 | use lsp_types::Url; |
14 | use parking_lot::RwLock; | 14 | use parking_lot::RwLock; |
15 | use ra_cargo_watch::{ | 15 | use ra_cargo_watch::{url_from_path_with_drive_lowercasing, CheckOptions, CheckWatcher}; |
16 | url_from_path_with_drive_lowercasing, CheckOptions, CheckState, CheckWatcher, | ||
17 | }; | ||
18 | use ra_ide::{ | 16 | use ra_ide::{ |
19 | Analysis, AnalysisChange, AnalysisHost, CrateGraph, FeatureFlags, FileId, LibraryData, | 17 | Analysis, AnalysisChange, AnalysisHost, CrateGraph, FeatureFlags, FileId, LibraryData, |
20 | SourceRootId, | 18 | SourceRootId, |
@@ -25,6 +23,7 @@ use ra_vfs_glob::{Glob, RustPackageFilterBuilder}; | |||
25 | use relative_path::RelativePathBuf; | 23 | use relative_path::RelativePathBuf; |
26 | 24 | ||
27 | use crate::{ | 25 | use crate::{ |
26 | diagnostics::{CheckFixes, DiagnosticCollection}, | ||
28 | main_loop::pending_requests::{CompletedRequest, LatestRequests}, | 27 | main_loop::pending_requests::{CompletedRequest, LatestRequests}, |
29 | LspError, Result, | 28 | LspError, Result, |
30 | }; | 29 | }; |
@@ -55,6 +54,7 @@ pub struct WorldState { | |||
55 | pub task_receiver: Receiver<VfsTask>, | 54 | pub task_receiver: Receiver<VfsTask>, |
56 | pub latest_requests: Arc<RwLock<LatestRequests>>, | 55 | pub latest_requests: Arc<RwLock<LatestRequests>>, |
57 | pub check_watcher: CheckWatcher, | 56 | pub check_watcher: CheckWatcher, |
57 | pub diagnostics: DiagnosticCollection, | ||
58 | } | 58 | } |
59 | 59 | ||
60 | /// An immutable snapshot of the world's state at a point in time. | 60 | /// An immutable snapshot of the world's state at a point in time. |
@@ -63,7 +63,7 @@ pub struct WorldSnapshot { | |||
63 | pub workspaces: Arc<Vec<ProjectWorkspace>>, | 63 | pub workspaces: Arc<Vec<ProjectWorkspace>>, |
64 | pub analysis: Analysis, | 64 | pub analysis: Analysis, |
65 | pub latest_requests: Arc<RwLock<LatestRequests>>, | 65 | pub latest_requests: Arc<RwLock<LatestRequests>>, |
66 | pub check_watcher: Arc<RwLock<CheckState>>, | 66 | pub check_fixes: CheckFixes, |
67 | vfs: Arc<RwLock<Vfs>>, | 67 | vfs: Arc<RwLock<Vfs>>, |
68 | } | 68 | } |
69 | 69 | ||
@@ -159,6 +159,7 @@ impl WorldState { | |||
159 | task_receiver, | 159 | task_receiver, |
160 | latest_requests: Default::default(), | 160 | latest_requests: Default::default(), |
161 | check_watcher, | 161 | check_watcher, |
162 | diagnostics: Default::default(), | ||
162 | } | 163 | } |
163 | } | 164 | } |
164 | 165 | ||
@@ -220,7 +221,7 @@ impl WorldState { | |||
220 | analysis: self.analysis_host.analysis(), | 221 | analysis: self.analysis_host.analysis(), |
221 | vfs: Arc::clone(&self.vfs), | 222 | vfs: Arc::clone(&self.vfs), |
222 | latest_requests: Arc::clone(&self.latest_requests), | 223 | latest_requests: Arc::clone(&self.latest_requests), |
223 | check_watcher: self.check_watcher.state.clone(), | 224 | check_fixes: Arc::clone(&self.diagnostics.check_fixes), |
224 | } | 225 | } |
225 | } | 226 | } |
226 | 227 | ||