aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_lsp_server/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_lsp_server/src')
-rw-r--r--crates/ra_lsp_server/src/diagnostics.rs85
-rw-r--r--crates/ra_lsp_server/src/lib.rs1
-rw-r--r--crates/ra_lsp_server/src/main_loop.rs139
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs121
-rw-r--r--crates/ra_lsp_server/src/world.rs11
5 files changed, 242 insertions, 115 deletions
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.
2use lsp_types::{CodeActionOrCommand, Diagnostic, Range};
3use ra_ide::FileId;
4use std::{collections::HashMap, sync::Arc};
5
6pub type CheckFixes = Arc<HashMap<FileId, Vec<Fix>>>;
7
8#[derive(Debug, Default, Clone)]
9pub 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)]
16pub struct Fix {
17 pub range: Range,
18 pub action: CodeActionOrCommand,
19}
20
21#[derive(Debug)]
22pub enum DiagnosticTask {
23 ClearCheck,
24 AddCheck(FileId, Diagnostic, Vec<CodeActionOrCommand>),
25 SetNative(FileId, Vec<Diagnostic>),
26}
27
28impl 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
80fn 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;
29pub mod req; 29pub mod req;
30mod config; 30mod config;
31mod world; 31mod world;
32mod diagnostics;
32 33
33pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; 34pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
34pub use crate::{ 35pub 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;
5mod subscriptions; 5mod subscriptions;
6pub(crate) mod pending_requests; 6pub(crate) mod pending_requests;
7 7
8use std::{error::Error, fmt, panic, path::PathBuf, sync::Arc, time::Instant}; 8use std::{
9 env,
10 error::Error,
11 fmt, panic,
12 path::PathBuf,
13 sync::Arc,
14 time::{Duration, Instant},
15};
9 16
10use crossbeam_channel::{select, unbounded, RecvError, Sender}; 17use crossbeam_channel::{select, unbounded, RecvError, Sender};
11use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; 18use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
12use lsp_types::{ClientCapabilities, NumberOrString, Url}; 19use lsp_types::{ClientCapabilities, NumberOrString};
13use ra_cargo_watch::{CheckOptions, CheckTask}; 20use ra_cargo_watch::{url_from_path_with_drive_lowercasing, CheckOptions, CheckTask};
14use ra_ide::{Canceled, FeatureFlags, FileId, LibraryData, SourceRootId}; 21use ra_ide::{Canceled, FeatureFlags, FileId, LibraryData, SourceRootId};
15use ra_prof::profile; 22use ra_prof::profile;
16use ra_vfs::{VfsTask, Watch}; 23use ra_vfs::{VfsFile, VfsTask, Watch};
17use relative_path::RelativePathBuf; 24use relative_path::RelativePathBuf;
18use rustc_hash::FxHashSet; 25use rustc_hash::FxHashSet;
19use serde::{de::DeserializeOwned, Serialize}; 26use serde::{de::DeserializeOwned, Serialize};
20use threadpool::ThreadPool; 27use threadpool::ThreadPool;
21 28
22use crate::{ 29use 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
32const THREADPOOL_SIZE: usize = 8;
33const MAX_IN_FLIGHT_LIBS: usize = THREADPOOL_SIZE - 3;
34
35#[derive(Debug)] 40#[derive(Debug)]
36pub struct LspError { 41pub 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(
231enum Task { 255enum Task {
232 Respond(Response), 256 Respond(Response),
233 Notify(Notification), 257 Notify(Notification),
258 Diagnostic(DiagnosticTask),
234} 259}
235 260
236enum Event { 261enum 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
587fn on_check_task( 626fn 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
627fn publish_diagnostics_for_url( 663fn 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
641struct PoolDispatcher<'a> { 683struct 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
4use std::{fmt::Write as _, io::Write as _}; 4use std::{
5 collections::hash_map::Entry,
6 fmt::Write as _,
7 io::Write as _,
8 process::{self, Stdio},
9};
5 10
6use either::Either;
7use lsp_server::ErrorCode; 11use lsp_server::ErrorCode;
8use lsp_types::{ 12use 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};
16use ra_ide::{ 21use 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(&params.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
877pub fn publish_diagnostics( 883pub 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
904pub fn publish_decorations( 903pub 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};
12use lsp_server::ErrorCode; 12use lsp_server::ErrorCode;
13use lsp_types::Url; 13use lsp_types::Url;
14use parking_lot::RwLock; 14use parking_lot::RwLock;
15use ra_cargo_watch::{ 15use ra_cargo_watch::{url_from_path_with_drive_lowercasing, CheckOptions, CheckWatcher};
16 url_from_path_with_drive_lowercasing, CheckOptions, CheckState, CheckWatcher,
17};
18use ra_ide::{ 16use 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};
25use relative_path::RelativePathBuf; 23use relative_path::RelativePathBuf;
26 24
27use crate::{ 25use 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