1 //! The main loop of `rust-analyzer` responsible for dispatching LSP
2 //! requests/replies and notifications back to the client.
6 pub(crate) mod pending_requests;
18 time::{Duration, Instant},
21 use crossbeam_channel::{never, select, unbounded, RecvError, Sender};
22 use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
23 use lsp_types::{DidChangeTextDocumentParams, NumberOrString, TextDocumentContentChangeEvent};
24 use ra_flycheck::CheckTask;
25 use ra_ide::{Canceled, FileId, LineIndex};
27 use ra_project_model::{PackageRoot, ProjectWorkspace};
29 use rustc_hash::FxHashSet;
30 use serde::{de::DeserializeOwned, Serialize};
31 use threadpool::ThreadPool;
34 config::{Config, FilesWatcher, LinkedProject},
35 diagnostics::DiagnosticTask,
37 global_state::{file_id_to_url, GlobalState, GlobalStateSnapshot},
40 pending_requests::{PendingRequest, PendingRequests},
41 subscriptions::Subscriptions,
45 pub use lsp_utils::show_message;
46 use lsp_utils::{is_canceled, notification_cast, notification_is, notification_new, request_new};
48 IsDone, ProgressStatus, U32Progress, U32ProgressReport, U32ProgressSource, U32ProgressStatus,
51 const FLYCHECK_PROGRESS_TOKEN: &str = "rustAnalyzer/flycheck";
52 const ROOTS_SCANNED_PROGRESS_TOKEN: &str = "rustAnalyzer/rootsScanned";
61 pub const UNKNOWN_FILE: i32 = -32900;
63 pub fn new(code: i32, message: String) -> LspError {
64 LspError { code, message }
68 impl fmt::Display for LspError {
69 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
70 write!(f, "Language Server request failed with {}. ({})", self.code, self.message)
74 impl Error for LspError {}
76 pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
77 log::info!("initial config: {:#?}", config);
79 // Windows scheduler implements priority boosts: if thread waits for an
80 // event (like a condvar), and event fires, priority of the thread is
81 // temporary bumped. This optimization backfires in our case: each time the
82 // `main_loop` schedules a task to run on a threadpool, the worker threads
83 // gets a higher priority, and (on a machine with fewer cores) displaces the
84 // main loop! We work-around this by marking the main loop as a
85 // higher-priority thread.
87 // https://docs.microsoft.com/en-us/windows/win32/procthread/scheduling-priorities
88 // https://docs.microsoft.com/en-us/windows/win32/procthread/priority-boosts
89 // https://github.com/rust-analyzer/rust-analyzer/issues/2835
92 use winapi::um::processthreadsapi::*;
93 let thread = GetCurrentThread();
94 let thread_priority_above_normal = 1;
95 SetThreadPriority(thread, thread_priority_above_normal);
98 let mut loop_state = LoopState::default();
99 let mut global_state = {
101 if config.linked_projects.is_empty() && config.notifications.cargo_toml_not_found {
103 lsp_types::MessageType::Error,
104 "rust-analyzer failed to discover workspace".to_string(),
112 .filter_map(|project| match project {
113 LinkedProject::ProjectManifest(manifest) => {
114 ra_project_model::ProjectWorkspace::load(
120 log::error!("failed to load workspace: {:#}", err);
122 lsp_types::MessageType::Error,
123 format!("rust-analyzer failed to load workspace: {:#}", err),
129 LinkedProject::InlineJsonProject(it) => {
130 Some(ra_project_model::ProjectWorkspace::Json {
132 project_location: config.root_path.clone(),
143 .map(|glob| crate::vfs_glob::Glob::new(glob))
144 .collect::<std::result::Result<Vec<_>, _>>()?;
146 if let FilesWatcher::Client = config.files.watcher {
147 let registration_options = lsp_types::DidChangeWatchedFilesRegistrationOptions {
150 .flat_map(ProjectWorkspace::to_roots)
151 .filter(PackageRoot::is_member)
152 .map(|root| format!("{}/**/*.rs", root.path().display()))
153 .map(|glob_pattern| lsp_types::FileSystemWatcher { glob_pattern, kind: None })
156 let registration = lsp_types::Registration {
157 id: "file-watcher".to_string(),
158 method: "workspace/didChangeWatchedFiles".to_string(),
159 register_options: Some(serde_json::to_value(registration_options).unwrap()),
161 let params = lsp_types::RegistrationParams { registrations: vec![registration] };
162 let request = request_new::<lsp_types::request::RegisterCapability>(
163 loop_state.next_request_id(),
166 connection.sender.send(request.into()).unwrap();
169 GlobalState::new(workspaces, config.lru_capacity, &globs, config)
172 loop_state.roots_total = u32::try_from(global_state.vfs.read().n_roots())
173 .expect("Wow, your project is so huge, that it cannot fit into u32...");
175 loop_state.roots_scanned = 0;
176 let mut roots_scanned_progress_receiver = {
177 let (recv, mut progress_src) =
178 U32ProgressSource::real_if(global_state.config.client_caps.work_done_progress);
179 loop_state.roots_progress = Some(progress_src.begin(0, loop_state.roots_total));
183 let pool = ThreadPool::default();
184 let (task_sender, task_receiver) = unbounded::<Task>();
186 log::info!("server initialized, serving requests");
188 let task_sender = task_sender;
190 log::trace!("selecting");
191 let event = select! {
192 recv(&connection.receiver) -> msg => match msg {
193 Ok(msg) => Event::Msg(msg),
194 Err(RecvError) => return Err("client exited without shutdown".into()),
196 recv(task_receiver) -> task => Event::Task(task.unwrap()),
197 recv(global_state.task_receiver) -> task => match task {
198 Ok(task) => Event::Vfs(task),
199 Err(RecvError) => return Err("vfs died".into()),
201 recv(global_state.flycheck.as_ref().map_or(&never(), |it| &it.task_recv)) -> task => match task {
202 Ok(task) => Event::CheckWatcher(task),
203 Err(RecvError) => return Err("check watcher died".into()),
205 recv(global_state.flycheck_progress_receiver) -> status => match status {
206 Ok(status) => Event::ProgressReport(ProgressReport::Flycheck(status)),
207 Err(RecvError) => return Err("check watcher died".into()),
209 recv(roots_scanned_progress_receiver) -> status => match status {
210 Ok(status) => Event::ProgressReport(ProgressReport::RootsScanned(status)),
212 // Roots analysis has finished, we no longer need this receiver
213 roots_scanned_progress_receiver = never();
218 if let Event::Msg(Message::Request(req)) = &event {
219 if connection.handle_shutdown(&req)? {
223 loop_turn(&pool, &task_sender, &connection, &mut global_state, &mut loop_state, event)?;
226 global_state.analysis_host.request_cancellation();
227 log::info!("waiting for tasks to finish...");
228 task_receiver.into_iter().for_each(|task| {
229 on_task(task, &connection.sender, &mut loop_state.pending_requests, &mut global_state)
231 log::info!("...tasks have finished");
232 log::info!("joining threadpool...");
235 log::info!("...threadpool has finished");
237 let vfs = Arc::try_unwrap(global_state.vfs).expect("all snapshots should be dead");
246 Notify(Notification),
247 SendRequest(Request),
248 Diagnostic(DiagnosticTask),
255 CheckWatcher(CheckTask),
256 ProgressReport(ProgressReport),
260 enum ProgressReport {
261 Flycheck(ProgressStatus<(), String>),
262 RootsScanned(U32ProgressStatus),
265 impl fmt::Debug for Event {
266 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
267 let debug_verbose_not = |not: &Notification, f: &mut fmt::Formatter| {
268 f.debug_struct("Notification").field("method", ¬.method).finish()
272 Event::Msg(Message::Notification(not)) => {
273 if notification_is::<lsp_types::notification::DidOpenTextDocument>(not)
274 || notification_is::<lsp_types::notification::DidChangeTextDocument>(not)
276 return debug_verbose_not(not, f);
279 Event::Task(Task::Notify(not)) => {
280 if notification_is::<lsp_types::notification::PublishDiagnostics>(not) {
281 return debug_verbose_not(not, f);
284 Event::Task(Task::Respond(resp)) => {
286 .debug_struct("Response")
287 .field("id", &resp.id)
288 .field("error", &resp.error)
294 Event::Msg(it) => fmt::Debug::fmt(it, f),
295 Event::Task(it) => fmt::Debug::fmt(it, f),
296 Event::Vfs(it) => fmt::Debug::fmt(it, f),
297 Event::CheckWatcher(it) => fmt::Debug::fmt(it, f),
298 Event::ProgressReport(it) => fmt::Debug::fmt(it, f),
305 next_request_id: u64,
306 pending_responses: FxHashSet<RequestId>,
307 pending_requests: PendingRequests,
308 subscriptions: Subscriptions,
309 workspace_loaded: bool,
310 roots_progress: Option<U32Progress>,
313 configuration_request_id: Option<RequestId>,
317 fn next_request_id(&mut self) -> RequestId {
318 self.next_request_id += 1;
319 let res: RequestId = self.next_request_id.into();
320 let inserted = self.pending_responses.insert(res.clone());
328 task_sender: &Sender<Task>,
329 connection: &Connection,
330 global_state: &mut GlobalState,
331 loop_state: &mut LoopState,
334 let loop_start = Instant::now();
336 // NOTE: don't count blocking select! call as a loop-turn time
337 let _p = profile("main_loop_inner/loop-turn");
338 log::info!("loop turn = {:?}", event);
339 let queue_count = pool.queued_count();
341 log::info!("queued count = {}", queue_count);
345 Event::Task(task) => {
346 on_task(task, &connection.sender, &mut loop_state.pending_requests, global_state);
347 global_state.maybe_collect_garbage();
349 Event::Vfs(task) => {
350 global_state.vfs.write().handle_task(task);
352 Event::CheckWatcher(task) => on_check_task(task, global_state, task_sender)?,
353 Event::ProgressReport(report) => {
354 on_progress_report(report, task_sender, loop_state, global_state)
356 Event::Msg(msg) => match msg {
357 Message::Request(req) => on_request(
359 &mut loop_state.pending_requests,
366 Message::Notification(not) => {
367 on_notification(&connection.sender, global_state, loop_state, not)?;
369 Message::Response(resp) => {
370 let removed = loop_state.pending_responses.remove(&resp.id);
372 log::error!("unexpected response: {:?}", resp)
375 if Some(&resp.id) == loop_state.configuration_request_id.as_ref() {
376 loop_state.configuration_request_id = None;
377 log::debug!("config update response: '{:?}", resp);
378 let Response { error, result, .. } = resp;
380 match (error, result) {
382 log::error!("failed to fetch the server settings: {:?}", err)
384 (None, Some(configs)) => {
385 if let Some(new_config) = configs.get(0) {
386 let mut config = global_state.config.clone();
387 config.update(&new_config);
388 global_state.update_configuration(config);
392 log::error!("received empty server settings response from the client")
400 let mut state_changed = global_state.process_changes(&mut loop_state.roots_scanned);
403 !loop_state.workspace_loaded && global_state.config.client_caps.work_done_progress;
405 if !loop_state.workspace_loaded && loop_state.roots_scanned == loop_state.roots_total {
406 state_changed = true;
407 loop_state.workspace_loaded = true;
408 if let Some(flycheck) = &global_state.flycheck {
414 if let Some(progress) = &mut loop_state.roots_progress {
415 if loop_state.workspace_loaded
416 || progress.report(loop_state.roots_scanned) == IsDone(true)
418 loop_state.roots_progress = None;
423 if state_changed && loop_state.workspace_loaded {
424 update_file_notifications_on_threadpool(
426 global_state.snapshot(),
428 loop_state.subscriptions.subscriptions(),
431 let subs = loop_state.subscriptions.subscriptions();
432 let snap = global_state.snapshot();
433 move || snap.analysis().prime_caches(subs).unwrap_or_else(|_: Canceled| ())
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() {
442 lsp_types::MessageType::Error,
443 format!("overly long loop turn: {:?}", loop_duration),
452 fn on_progress_report(
453 report: ProgressReport,
454 task_sender: &Sender<Task>,
455 loop_state: &mut LoopState,
456 global_state: &GlobalState,
459 || lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { message: None });
460 let mut create_progress = |token: &'static str| {
461 let create_progress_req = request_new::<lsp_types::request::WorkDoneProgressCreate>(
462 loop_state.next_request_id(),
463 lsp_types::WorkDoneProgressCreateParams {
464 token: lsp_types::ProgressToken::String(token.to_string()),
467 task_sender.send(Task::SendRequest(create_progress_req.into())).unwrap();
470 let (token, progress) = match report {
471 ProgressReport::Flycheck(status) => {
472 let command = global_state
476 .expect("There should be config, since flycheck is active");
478 let progress = match status {
479 ProgressStatus::Begin(()) => {
480 create_progress(FLYCHECK_PROGRESS_TOKEN);
481 lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
482 title: "".to_string(),
483 cancellable: Some(false),
484 message: Some(command.to_string()),
488 ProgressStatus::Progress(target) => {
489 lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport {
490 cancellable: Some(false),
491 message: Some(format!("{} [{}]", command, target)),
495 ProgressStatus::End => end_report(),
497 (FLYCHECK_PROGRESS_TOKEN, progress)
499 ProgressReport::RootsScanned(status) => {
500 fn to_message(report: &U32ProgressReport) -> String {
501 report.to_message("analyzing the workspace", "packages")
503 let progress = match status {
504 ProgressStatus::Begin(report) => {
505 create_progress(ROOTS_SCANNED_PROGRESS_TOKEN);
506 lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
507 title: "rust-analyzer".to_string(),
508 cancellable: Some(false),
509 message: Some(to_message(&report)),
510 percentage: Some(report.percentage()),
513 ProgressStatus::Progress(report) => {
514 lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport {
515 cancellable: Some(false),
516 message: Some(to_message(&report)),
517 percentage: Some(report.percentage()),
520 ProgressStatus::End => end_report(),
522 (ROOTS_SCANNED_PROGRESS_TOKEN, progress)
525 let params = lsp_types::ProgressParams {
526 token: lsp_types::ProgressToken::String(token.to_string()),
527 value: lsp_types::ProgressParamsValue::WorkDone(progress),
529 let not = notification_new::<lsp_types::notification::Progress>(params);
530 task_sender.send(Task::Notify(not.into())).unwrap()
535 msg_sender: &Sender<Message>,
536 pending_requests: &mut PendingRequests,
537 state: &mut GlobalState,
540 Task::Respond(response) => {
541 if let Some(completed) = pending_requests.finish(&response.id) {
542 log::info!("handled req#{} in {:?}", completed.id, completed.duration);
543 state.complete_request(completed);
544 msg_sender.send(response.into()).unwrap();
547 Task::Notify(not) => msg_sender.send(not.into()).unwrap(),
548 Task::SendRequest(req) => msg_sender.send(req.into()).unwrap(),
549 Task::Diagnostic(task) => on_diagnostic_task(task, msg_sender, state),
554 global_state: &mut GlobalState,
555 pending_requests: &mut PendingRequests,
557 task_sender: &Sender<Task>,
558 msg_sender: &Sender<Message>,
559 request_received: Instant,
562 let mut pool_dispatcher = PoolDispatcher {
572 .on_sync::<lsp_ext::CollectGarbage>(|s, ()| Ok(s.collect_garbage()))?
573 .on_sync::<lsp_ext::JoinLines>(|s, p| handlers::handle_join_lines(s.snapshot(), p))?
574 .on_sync::<lsp_ext::OnEnter>(|s, p| handlers::handle_on_enter(s.snapshot(), p))?
575 .on_sync::<lsp_types::request::SelectionRangeRequest>(|s, p| {
576 handlers::handle_selection_range(s.snapshot(), p)
578 .on_sync::<lsp_ext::MatchingBrace>(|s, p| handlers::handle_matching_brace(s.snapshot(), p))?
579 .on::<lsp_ext::AnalyzerStatus>(handlers::handle_analyzer_status)?
580 .on::<lsp_ext::SyntaxTree>(handlers::handle_syntax_tree)?
581 .on::<lsp_ext::ExpandMacro>(handlers::handle_expand_macro)?
582 .on::<lsp_ext::ParentModule>(handlers::handle_parent_module)?
583 .on::<lsp_ext::Runnables>(handlers::handle_runnables)?
584 .on::<lsp_ext::InlayHints>(handlers::handle_inlay_hints)?
585 .on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action)?
586 .on::<lsp_ext::ResolveCodeActionRequest>(handlers::handle_resolve_code_action)?
587 .on::<lsp_ext::HoverRequest>(handlers::handle_hover)?
588 .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)?
589 .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)?
590 .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)?
591 .on::<lsp_types::request::GotoDefinition>(handlers::handle_goto_definition)?
592 .on::<lsp_types::request::GotoImplementation>(handlers::handle_goto_implementation)?
593 .on::<lsp_types::request::GotoTypeDefinition>(handlers::handle_goto_type_definition)?
594 .on::<lsp_types::request::Completion>(handlers::handle_completion)?
595 .on::<lsp_types::request::CodeLensRequest>(handlers::handle_code_lens)?
596 .on::<lsp_types::request::CodeLensResolve>(handlers::handle_code_lens_resolve)?
597 .on::<lsp_types::request::FoldingRangeRequest>(handlers::handle_folding_range)?
598 .on::<lsp_types::request::SignatureHelpRequest>(handlers::handle_signature_help)?
599 .on::<lsp_types::request::PrepareRenameRequest>(handlers::handle_prepare_rename)?
600 .on::<lsp_types::request::Rename>(handlers::handle_rename)?
601 .on::<lsp_types::request::References>(handlers::handle_references)?
602 .on::<lsp_types::request::Formatting>(handlers::handle_formatting)?
603 .on::<lsp_types::request::DocumentHighlightRequest>(handlers::handle_document_highlight)?
604 .on::<lsp_types::request::CallHierarchyPrepare>(handlers::handle_call_hierarchy_prepare)?
605 .on::<lsp_types::request::CallHierarchyIncomingCalls>(
606 handlers::handle_call_hierarchy_incoming,
608 .on::<lsp_types::request::CallHierarchyOutgoingCalls>(
609 handlers::handle_call_hierarchy_outgoing,
611 .on::<lsp_types::request::SemanticTokensRequest>(handlers::handle_semantic_tokens)?
612 .on::<lsp_types::request::SemanticTokensRangeRequest>(
613 handlers::handle_semantic_tokens_range,
615 .on::<lsp_ext::Ssr>(handlers::handle_ssr)?
621 msg_sender: &Sender<Message>,
622 state: &mut GlobalState,
623 loop_state: &mut LoopState,
626 let not = match notification_cast::<lsp_types::notification::Cancel>(not) {
628 let id: RequestId = match params.id {
629 NumberOrString::Number(id) => id.into(),
630 NumberOrString::String(id) => id.into(),
632 if loop_state.pending_requests.cancel(&id) {
633 let response = Response::new_err(
635 ErrorCode::RequestCanceled as i32,
636 "canceled by client".to_string(),
638 msg_sender.send(response.into()).unwrap()
644 let not = match notification_cast::<lsp_types::notification::DidOpenTextDocument>(not) {
646 let uri = params.text_document.uri;
647 let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
648 if let Some(file_id) =
649 state.vfs.write().add_file_overlay(&path, params.text_document.text)
651 loop_state.subscriptions.add_sub(FileId(file_id.0));
657 let not = match notification_cast::<lsp_types::notification::DidChangeTextDocument>(not) {
659 let DidChangeTextDocumentParams { text_document, content_changes } = params;
660 let world = state.snapshot();
661 let file_id = from_proto::file_id(&world, &text_document.uri)?;
662 let line_index = world.analysis().file_line_index(file_id)?;
663 let uri = text_document.uri;
664 let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
665 state.vfs.write().change_file_overlay(&path, |old_text| {
666 apply_document_changes(old_text, Cow::Borrowed(&line_index), content_changes);
672 let not = match notification_cast::<lsp_types::notification::DidSaveTextDocument>(not) {
674 if let Some(flycheck) = &state.flycheck {
681 let not = match notification_cast::<lsp_types::notification::DidCloseTextDocument>(not) {
683 let uri = params.text_document.uri;
684 let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
685 if let Some(file_id) = state.vfs.write().remove_file_overlay(path.as_path()) {
686 loop_state.subscriptions.remove_sub(FileId(file_id.0));
689 lsp_types::PublishDiagnosticsParams { uri, diagnostics: Vec::new(), version: None };
690 let not = notification_new::<lsp_types::notification::PublishDiagnostics>(params);
691 msg_sender.send(not.into()).unwrap();
696 let not = match notification_cast::<lsp_types::notification::DidChangeConfiguration>(not) {
698 // As stated in https://github.com/microsoft/language-server-protocol/issues/676,
699 // this notification's parameters should be ignored and the actual config queried separately.
700 let request_id = loop_state.next_request_id();
701 let request = request_new::<lsp_types::request::WorkspaceConfiguration>(
703 lsp_types::ConfigurationParams {
704 items: vec![lsp_types::ConfigurationItem {
706 section: Some("rust-analyzer".to_string()),
710 msg_sender.send(request.into())?;
711 loop_state.configuration_request_id = Some(request_id);
717 let not = match notification_cast::<lsp_types::notification::DidChangeWatchedFiles>(not) {
719 let mut vfs = state.vfs.write();
720 for change in params.changes {
721 let uri = change.uri;
722 let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
723 vfs.notify_changed(path)
729 if not.method.starts_with("$/") {
732 log::error!("unhandled notification: {:?}", not);
736 fn apply_document_changes(
737 old_text: &mut String,
738 mut line_index: Cow<'_, LineIndex>,
739 content_changes: Vec<TextDocumentContentChangeEvent>,
741 // The changes we got must be applied sequentially, but can cross lines so we
742 // have to keep our line index updated.
743 // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we
744 // remember the last valid line in the index and only rebuild it if needed.
745 // The VFS will normalize the end of lines to `\n`.
748 UpToLineExclusive(u64),
752 fn covers(&self, line: u64) -> bool {
754 IndexValid::UpToLineExclusive(to) => to > line,
760 let mut index_valid = IndexValid::All;
761 for change in content_changes {
764 if !index_valid.covers(range.end.line) {
765 line_index = Cow::Owned(LineIndex::new(&old_text));
767 index_valid = IndexValid::UpToLineExclusive(range.start.line);
768 let range = from_proto::text_range(&line_index, range);
769 old_text.replace_range(Range::<usize>::from(range), &change.text);
772 *old_text = change.text;
773 index_valid = IndexValid::UpToLineExclusive(0);
781 global_state: &mut GlobalState,
782 task_sender: &Sender<Task>,
785 CheckTask::ClearDiagnostics => {
786 task_sender.send(Task::Diagnostic(DiagnosticTask::ClearCheck))?;
789 CheckTask::AddDiagnostic { workspace_root, diagnostic } => {
790 let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp(
791 &global_state.config.diagnostics,
795 for diag in diagnostics {
800 .map_err(|()| format!("invalid uri: {}", diag.location.uri))?;
801 let file_id = match global_state.vfs.read().path2file(&path) {
802 Some(file) => FileId(file.0),
805 "File with cargo diagnostic not found in VFS: {}",
812 task_sender.send(Task::Diagnostic(DiagnosticTask::AddCheck(
815 diag.fixes.into_iter().map(|it| it.into()).collect(),
824 fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender<Message>, state: &mut GlobalState) {
825 let subscriptions = state.diagnostics.handle_task(task);
827 for file_id in subscriptions {
828 let url = file_id_to_url(&state.vfs.read(), file_id);
829 let diagnostics = state.diagnostics.diagnostics_for(file_id).cloned().collect();
830 let params = lsp_types::PublishDiagnosticsParams { uri: url, diagnostics, version: None };
831 let not = notification_new::<lsp_types::notification::PublishDiagnostics>(params);
832 msg_sender.send(not.into()).unwrap();
836 struct PoolDispatcher<'a> {
837 req: Option<Request>,
838 pool: &'a ThreadPool,
839 global_state: &'a mut GlobalState,
840 pending_requests: &'a mut PendingRequests,
841 msg_sender: &'a Sender<Message>,
842 task_sender: &'a Sender<Task>,
843 request_received: Instant,
846 impl<'a> PoolDispatcher<'a> {
847 /// Dispatches the request onto the current thread
850 f: fn(&mut GlobalState, R::Params) -> Result<R::Result>,
851 ) -> Result<&mut Self>
853 R: lsp_types::request::Request + 'static,
854 R::Params: DeserializeOwned + panic::UnwindSafe + 'static,
855 R::Result: Serialize + 'static,
857 let (id, params) = match self.parse::<R>() {
863 let world = panic::AssertUnwindSafe(&mut *self.global_state);
864 let task = panic::catch_unwind(move || {
865 let result = f(world.0, params);
866 result_to_task::<R>(id, result)
868 .map_err(|_| format!("sync task {:?} panicked", R::METHOD))?;
869 on_task(task, self.msg_sender, self.pending_requests, self.global_state);
873 /// Dispatches the request onto thread pool
876 f: fn(GlobalStateSnapshot, R::Params) -> Result<R::Result>,
877 ) -> Result<&mut Self>
879 R: lsp_types::request::Request + 'static,
880 R::Params: DeserializeOwned + Send + 'static,
881 R::Result: Serialize + 'static,
883 let (id, params) = match self.parse::<R>() {
891 let world = self.global_state.snapshot();
892 let sender = self.task_sender.clone();
894 let result = f(world, params);
895 let task = result_to_task::<R>(id, result);
896 sender.send(task).unwrap();
903 fn parse<R>(&mut self) -> Option<(RequestId, R::Params)>
905 R: lsp_types::request::Request + 'static,
906 R::Params: DeserializeOwned + 'static,
908 let req = self.req.take()?;
909 let (id, params) = match req.extract::<R::Params>(R::METHOD) {
912 self.req = Some(req);
916 self.pending_requests.start(PendingRequest {
918 method: R::METHOD.to_string(),
919 received: self.request_received,
924 fn finish(&mut self) {
925 match self.req.take() {
928 log::error!("unknown request: {:?}", req);
929 let resp = Response::new_err(
931 ErrorCode::MethodNotFound as i32,
932 "unknown request".to_string(),
934 self.msg_sender.send(resp.into()).unwrap();
940 fn result_to_task<R>(id: RequestId, result: Result<R::Result>) -> Task
942 R: lsp_types::request::Request + 'static,
943 R::Params: DeserializeOwned + 'static,
944 R::Result: Serialize + 'static,
946 let response = match result {
947 Ok(resp) => Response::new_ok(id, &resp),
948 Err(e) => match e.downcast::<LspError>() {
950 if lsp_error.code == LspError::UNKNOWN_FILE {
951 // Work-around for https://github.com/rust-analyzer/rust-analyzer/issues/1521
952 Response::new_ok(id, ())
954 Response::new_err(id, lsp_error.code, lsp_error.message)
958 if is_canceled(&*e) {
961 ErrorCode::ContentModified as i32,
962 "content modified".to_string(),
965 Response::new_err(id, ErrorCode::InternalError as i32, e.to_string())
970 Task::Respond(response)
973 fn update_file_notifications_on_threadpool(
975 world: GlobalStateSnapshot,
976 task_sender: Sender<Task>,
977 subscriptions: Vec<FileId>,
979 log::trace!("updating notifications for {:?}", subscriptions);
980 if world.config.publish_diagnostics {
981 pool.execute(move || {
982 for file_id in subscriptions {
983 match handlers::publish_diagnostics(&world, file_id) {
985 if !is_canceled(&*e) {
986 log::error!("failed to compute diagnostics: {:?}", e);
990 task_sender.send(Task::Diagnostic(task)).unwrap();
1000 use std::borrow::Cow;
1002 use lsp_types::{Position, Range, TextDocumentContentChangeEvent};
1003 use ra_ide::LineIndex;
1006 fn apply_document_changes() {
1007 fn run(text: &mut String, changes: Vec<TextDocumentContentChangeEvent>) {
1008 let line_index = Cow::Owned(LineIndex::new(&text));
1009 super::apply_document_changes(text, line_index, changes);
1013 [$($sl:expr, $sc:expr; $el:expr, $ec:expr => $text:expr),+] => {
1014 vec![$(TextDocumentContentChangeEvent {
1016 start: Position { line: $sl, character: $sc },
1017 end: Position { line: $el, character: $ec },
1020 text: String::from($text),
1025 let mut text = String::new();
1026 run(&mut text, vec![]);
1027 assert_eq!(text, "");
1030 vec![TextDocumentContentChangeEvent {
1033 text: String::from("the"),
1036 assert_eq!(text, "the");
1037 run(&mut text, c![0, 3; 0, 3 => " quick"]);
1038 assert_eq!(text, "the quick");
1039 run(&mut text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]);
1040 assert_eq!(text, "quick foxes");
1041 run(&mut text, c![0, 11; 0, 11 => "\ndream"]);
1042 assert_eq!(text, "quick foxes\ndream");
1043 run(&mut text, c![1, 0; 1, 0 => "have "]);
1044 assert_eq!(text, "quick foxes\nhave dream");
1045 run(&mut text, c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"]);
1046 assert_eq!(text, "the quick foxes\nhave quiet dreams\n");
1047 run(&mut text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]);
1048 assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n");
1051 c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"],
1053 assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n");
1054 run(&mut text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]);
1055 assert_eq!(text, "the quick \nthey have quiet dreams\n");
1057 text = String::from("❤️");
1058 run(&mut text, c![0, 0; 0, 0 => "a"]);
1059 assert_eq!(text, "a❤️");
1061 text = String::from("a\nb");
1062 run(&mut text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]);
1063 assert_eq!(text, "adcb");
1065 text = String::from("a\nb");
1066 run(&mut text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]);
1067 assert_eq!(text, "ațc\ncb");