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.rs76
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs37
-rw-r--r--crates/ra_lsp_server/src/world.rs11
5 files changed, 140 insertions, 70 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 508fe08c0..12961ba37 100644
--- a/crates/ra_lsp_server/src/main_loop.rs
+++ b/crates/ra_lsp_server/src/main_loop.rs
@@ -17,16 +17,17 @@ use std::{
17use crossbeam_channel::{select, unbounded, RecvError, Sender}; 17use crossbeam_channel::{select, unbounded, RecvError, Sender};
18use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; 18use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
19use lsp_types::{ClientCapabilities, NumberOrString}; 19use lsp_types::{ClientCapabilities, NumberOrString};
20use ra_cargo_watch::{CheckOptions, CheckTask}; 20use ra_cargo_watch::{url_from_path_with_drive_lowercasing, CheckOptions, CheckTask};
21use ra_ide::{Canceled, FeatureFlags, FileId, LibraryData, SourceRootId}; 21use ra_ide::{Canceled, FeatureFlags, FileId, LibraryData, SourceRootId};
22use ra_prof::profile; 22use ra_prof::profile;
23use ra_vfs::{VfsTask, Watch}; 23use ra_vfs::{VfsFile, VfsTask, Watch};
24use relative_path::RelativePathBuf; 24use relative_path::RelativePathBuf;
25use rustc_hash::FxHashSet; 25use rustc_hash::FxHashSet;
26use serde::{de::DeserializeOwned, Serialize}; 26use serde::{de::DeserializeOwned, Serialize};
27use threadpool::ThreadPool; 27use threadpool::ThreadPool;
28 28
29use crate::{ 29use crate::{
30 diagnostics::DiagnosticTask,
30 main_loop::{ 31 main_loop::{
31 pending_requests::{PendingRequest, PendingRequests}, 32 pending_requests::{PendingRequest, PendingRequests},
32 subscriptions::Subscriptions, 33 subscriptions::Subscriptions,
@@ -254,6 +255,7 @@ pub fn main_loop(
254enum Task { 255enum Task {
255 Respond(Response), 256 Respond(Response),
256 Notify(Notification), 257 Notify(Notification),
258 Diagnostic(DiagnosticTask),
257} 259}
258 260
259enum Event { 261enum Event {
@@ -359,7 +361,7 @@ fn loop_turn(
359 world_state.maybe_collect_garbage(); 361 world_state.maybe_collect_garbage();
360 loop_state.in_flight_libraries -= 1; 362 loop_state.in_flight_libraries -= 1;
361 } 363 }
362 Event::CheckWatcher(task) => on_check_task(pool, task, world_state, task_sender)?, 364 Event::CheckWatcher(task) => on_check_task(task, world_state, task_sender)?,
363 Event::Msg(msg) => match msg { 365 Event::Msg(msg) => match msg {
364 Message::Request(req) => on_request( 366 Message::Request(req) => on_request(
365 world_state, 367 world_state,
@@ -464,6 +466,7 @@ fn on_task(
464 Task::Notify(n) => { 466 Task::Notify(n) => {
465 msg_sender.send(n.into()).unwrap(); 467 msg_sender.send(n.into()).unwrap();
466 } 468 }
469 Task::Diagnostic(task) => on_diagnostic_task(task, msg_sender, state),
467 } 470 }
468} 471}
469 472
@@ -621,23 +624,26 @@ fn on_notification(
621} 624}
622 625
623fn on_check_task( 626fn on_check_task(
624 pool: &ThreadPool,
625 task: CheckTask, 627 task: CheckTask,
626 world_state: &mut WorldState, 628 world_state: &mut WorldState,
627 task_sender: &Sender<Task>, 629 task_sender: &Sender<Task>,
628) -> Result<()> { 630) -> Result<()> {
629 let urls = match task { 631 match task {
630 CheckTask::ClearDiagnostics => { 632 CheckTask::ClearDiagnostics => {
631 let state = Arc::get_mut(&mut world_state.check_watcher.state) 633 task_sender.send(Task::Diagnostic(DiagnosticTask::ClearCheck))?;
632 .expect("couldn't get check watcher state as mutable");
633 state.clear()
634 } 634 }
635 635
636 CheckTask::AddDiagnostic(url, diagnostic) => { 636 CheckTask::AddDiagnostic { url, diagnostic, fixes } => {
637 let state = Arc::get_mut(&mut world_state.check_watcher.state) 637 let path = url.to_file_path().map_err(|()| format!("invalid uri: {}", url))?;
638 .expect("couldn't get check watcher state as mutable"); 638 let file_id = world_state
639 state.add_diagnostic_with_fixes(url.clone(), diagnostic); 639 .vfs
640 vec![url] 640 .read()
641 .path2file(&path)
642 .map(|it| FileId(it.0))
643 .ok_or_else(|| format!("unknown file: {}", path.to_string_lossy()))?;
644
645 task_sender
646 .send(Task::Diagnostic(DiagnosticTask::AddCheck(file_id, diagnostic, fixes)))?;
641 } 647 }
642 648
643 CheckTask::Status(progress) => { 649 CheckTask::Status(progress) => {
@@ -647,31 +653,30 @@ fn on_check_task(
647 }; 653 };
648 let not = notification_new::<req::Progress>(params); 654 let not = notification_new::<req::Progress>(params);
649 task_sender.send(Task::Notify(not)).unwrap(); 655 task_sender.send(Task::Notify(not)).unwrap();
650 Vec::new()
651 } 656 }
652 }; 657 };
653 658
654 let subscriptions = urls 659 Ok(())
655 .into_iter() 660}
656 .map(|url| {
657 let path = url.to_file_path().map_err(|()| format!("invalid uri: {}", url))?;
658 Ok(world_state.vfs.read().path2file(&path).map(|it| FileId(it.0)))
659 })
660 .filter_map(|res| res.transpose())
661 .collect::<Result<Vec<_>>>()?;
662 661
663 // We manually send a diagnostic update when the watcher asks 662fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender<Message>, state: &mut WorldState) {
664 // us to, to avoid the issue of having to change the file to 663 let subscriptions = state.diagnostics.handle_task(task);
665 // receive updated diagnostics.
666 update_file_notifications_on_threadpool(
667 pool,
668 world_state.snapshot(),
669 false,
670 task_sender.clone(),
671 subscriptions,
672 );
673 664
674 Ok(()) 665 for file_id in subscriptions {
666 let path = state.vfs.read().file2path(VfsFile(file_id.0));
667 let uri = match url_from_path_with_drive_lowercasing(&path) {
668 Ok(uri) => uri,
669 Err(err) => {
670 log::error!("Couldn't convert path to url ({}): {:?}", err, path.to_string_lossy());
671 continue;
672 }
673 };
674
675 let diagnostics = state.diagnostics.diagnostics_for(file_id).cloned().collect();
676 let params = req::PublishDiagnosticsParams { uri, diagnostics, version: None };
677 let not = notification_new::<req::PublishDiagnostics>(params);
678 msg_sender.send(not.into()).unwrap();
679 }
675} 680}
676 681
677struct PoolDispatcher<'a> { 682struct PoolDispatcher<'a> {
@@ -819,9 +824,8 @@ fn update_file_notifications_on_threadpool(
819 log::error!("failed to compute diagnostics: {:?}", e); 824 log::error!("failed to compute diagnostics: {:?}", e);
820 } 825 }
821 } 826 }
822 Ok(params) => { 827 Ok(task) => {
823 let not = notification_new::<req::PublishDiagnostics>(params); 828 task_sender.send(Task::Diagnostic(task)).unwrap();
824 task_sender.send(Task::Notify(not)).unwrap();
825 } 829 }
826 } 830 }
827 } 831 }
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index 9aa1e7eea..839bca342 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -33,6 +33,7 @@ use crate::{
33 to_call_hierarchy_item, to_location, Conv, ConvWith, FoldConvCtx, MapConvWith, TryConvWith, 33 to_call_hierarchy_item, to_location, Conv, ConvWith, FoldConvCtx, MapConvWith, TryConvWith,
34 TryConvWithToVec, 34 TryConvWithToVec,
35 }, 35 },
36 diagnostics::DiagnosticTask,
36 req::{self, Decoration, InlayHint, InlayHintsParams, InlayKind}, 37 req::{self, Decoration, InlayHint, InlayHintsParams, InlayKind},
37 world::WorldSnapshot, 38 world::WorldSnapshot,
38 LspError, Result, 39 LspError, Result,
@@ -656,6 +657,7 @@ pub fn handle_code_action(
656 .filter(|(diag_range, _fix)| diag_range.intersection(&range).is_some()) 657 .filter(|(diag_range, _fix)| diag_range.intersection(&range).is_some())
657 .map(|(_range, fix)| fix); 658 .map(|(_range, fix)| fix);
658 659
660 // TODO: When done, we won't need this, only the one pulling from world.diagnostics
659 for source_edit in fixes_from_diagnostics { 661 for source_edit in fixes_from_diagnostics {
660 let title = source_edit.label.clone(); 662 let title = source_edit.label.clone();
661 let edit = source_edit.try_conv_with(&world)?; 663 let edit = source_edit.try_conv_with(&world)?;
@@ -676,28 +678,12 @@ pub fn handle_code_action(
676 res.push(action.into()); 678 res.push(action.into());
677 } 679 }
678 680
679 for fix in world.check_watcher.fixes_for(&params.text_document.uri).into_iter().flatten() { 681 for fix in world.check_fixes.get(&file_id).into_iter().flatten() {
680 let fix_range = fix.location.range.conv_with(&line_index); 682 let fix_range = fix.range.conv_with(&line_index);
681 if fix_range.intersection(&range).is_none() { 683 if fix_range.intersection(&range).is_none() {
682 continue; 684 continue;
683 } 685 }
684 686 res.push(fix.action.clone());
685 let edit = {
686 let edits = vec![TextEdit::new(fix.location.range, fix.replacement.clone())];
687 let mut edit_map = std::collections::HashMap::new();
688 edit_map.insert(fix.location.uri.clone(), edits);
689 WorkspaceEdit::new(edit_map)
690 };
691
692 let action = CodeAction {
693 title: fix.title.clone(),
694 kind: Some("quickfix".to_string()),
695 diagnostics: Some(fix.diagnostics.clone()),
696 edit: Some(edit),
697 command: None,
698 is_preferred: None,
699 };
700 res.push(action.into());
701 } 687 }
702 688
703 for assist in world.analysis().assists(FileRange { file_id, range })?.into_iter() { 689 for assist in world.analysis().assists(FileRange { file_id, range })?.into_iter() {
@@ -875,14 +861,10 @@ pub fn handle_document_highlight(
875 )) 861 ))
876} 862}
877 863
878pub fn publish_diagnostics( 864pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result<DiagnosticTask> {
879 world: &WorldSnapshot,
880 file_id: FileId,
881) -> Result<req::PublishDiagnosticsParams> {
882 let _p = profile("publish_diagnostics"); 865 let _p = profile("publish_diagnostics");
883 let uri = world.file_id_to_uri(file_id)?;
884 let line_index = world.analysis().file_line_index(file_id)?; 866 let line_index = world.analysis().file_line_index(file_id)?;
885 let mut diagnostics: Vec<Diagnostic> = world 867 let diagnostics: Vec<Diagnostic> = world
886 .analysis() 868 .analysis()
887 .diagnostics(file_id)? 869 .diagnostics(file_id)?
888 .into_iter() 870 .into_iter()
@@ -896,10 +878,7 @@ pub fn publish_diagnostics(
896 tags: None, 878 tags: None,
897 }) 879 })
898 .collect(); 880 .collect();
899 if let Some(check_diags) = world.check_watcher.diagnostics_for(&uri) { 881 Ok(DiagnosticTask::SetNative(file_id, diagnostics))
900 diagnostics.extend(check_diags.iter().cloned());
901 }
902 Ok(req::PublishDiagnosticsParams { uri, diagnostics, version: None })
903} 882}
904 883
905pub fn publish_decorations( 884pub fn publish_decorations(
diff --git a/crates/ra_lsp_server/src/world.rs b/crates/ra_lsp_server/src/world.rs
index 3059ef9ec..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: 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