diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index 621e7e100ad9c6da8352d9ecad4061a1ebf7ae51..9a923c3fb3a11e0ecd01706c19c0087de6475a09 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -2553,6 +2553,27 @@ async fn test_lsp_pull_diagnostics( cx_a.update(editor::init); cx_b.update(editor::init); + let expected_push_diagnostic_main_message = "pushed main diagnostic"; + let expected_push_diagnostic_lib_message = "pushed lib diagnostic"; + let expected_pull_diagnostic_main_message = "pulled main diagnostic"; + let expected_pull_diagnostic_lib_message = "pulled lib diagnostic"; + let expected_workspace_pull_diagnostics_main_message = "pulled workspace main diagnostic"; + let expected_workspace_pull_diagnostics_lib_message = "pulled workspace lib diagnostic"; + + let diagnostics_pulls_result_ids = Arc::new(Mutex::new(BTreeSet::>::new())); + let workspace_diagnostics_pulls_result_ids = Arc::new(Mutex::new(BTreeSet::::new())); + let diagnostics_pulls_made = Arc::new(AtomicUsize::new(0)); + let closure_diagnostics_pulls_made = diagnostics_pulls_made.clone(); + let closure_diagnostics_pulls_result_ids = diagnostics_pulls_result_ids.clone(); + let workspace_diagnostics_pulls_made = Arc::new(AtomicUsize::new(0)); + let closure_workspace_diagnostics_pulls_made = workspace_diagnostics_pulls_made.clone(); + let closure_workspace_diagnostics_pulls_result_ids = + workspace_diagnostics_pulls_result_ids.clone(); + let (workspace_diagnostic_cancel_tx, closure_workspace_diagnostic_cancel_rx) = + smol::channel::bounded::<()>(1); + let (closure_workspace_diagnostic_received_tx, workspace_diagnostic_received_rx) = + smol::channel::bounded::<()>(1); + let capabilities = lsp::ServerCapabilities { diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options( lsp::DiagnosticOptions { @@ -2567,13 +2588,195 @@ async fn test_lsp_pull_diagnostics( ..lsp::ServerCapabilities::default() }; client_a.language_registry().add(rust_lang()); + + let pull_diagnostics_handle = Arc::new(parking_lot::Mutex::new(None)); + let workspace_diagnostics_pulls_handle = Arc::new(parking_lot::Mutex::new(None)); + + let closure_pull_diagnostics_handle = pull_diagnostics_handle.clone(); + let closure_workspace_diagnostics_pulls_handle = workspace_diagnostics_pulls_handle.clone(); let mut fake_language_servers = client_a.language_registry().register_fake_lsp( "Rust", FakeLspAdapter { capabilities: capabilities.clone(), + initializer: Some(Box::new(move |fake_language_server| { + let expected_workspace_diagnostic_token = lsp::ProgressToken::String(format!( + "workspace/diagnostic-{}-1", + fake_language_server.server.server_id() + )); + let closure_workspace_diagnostics_pulls_result_ids = closure_workspace_diagnostics_pulls_result_ids.clone(); + let diagnostics_pulls_made = closure_diagnostics_pulls_made.clone(); + let diagnostics_pulls_result_ids = closure_diagnostics_pulls_result_ids.clone(); + let closure_pull_diagnostics_handle = closure_pull_diagnostics_handle.clone(); + let closure_workspace_diagnostics_pulls_handle = closure_workspace_diagnostics_pulls_handle.clone(); + let closure_workspace_diagnostic_cancel_rx = closure_workspace_diagnostic_cancel_rx.clone(); + let closure_workspace_diagnostic_received_tx = closure_workspace_diagnostic_received_tx.clone(); + let pull_diagnostics_handle = fake_language_server + .set_request_handler::( + move |params, _| { + let requests_made = diagnostics_pulls_made.clone(); + let diagnostics_pulls_result_ids = + diagnostics_pulls_result_ids.clone(); + async move { + let message = if lsp::Uri::from_file_path(path!("/a/main.rs")) + .unwrap() + == params.text_document.uri + { + expected_pull_diagnostic_main_message.to_string() + } else if lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap() + == params.text_document.uri + { + expected_pull_diagnostic_lib_message.to_string() + } else { + panic!("Unexpected document: {}", params.text_document.uri) + }; + { + diagnostics_pulls_result_ids + .lock() + .await + .insert(params.previous_result_id); + } + let new_requests_count = + requests_made.fetch_add(1, atomic::Ordering::Release) + 1; + Ok(lsp::DocumentDiagnosticReportResult::Report( + lsp::DocumentDiagnosticReport::Full( + lsp::RelatedFullDocumentDiagnosticReport { + related_documents: None, + full_document_diagnostic_report: + lsp::FullDocumentDiagnosticReport { + result_id: Some(format!( + "pull-{new_requests_count}" + )), + items: vec![lsp::Diagnostic { + range: lsp::Range { + start: lsp::Position { + line: 0, + character: 0, + }, + end: lsp::Position { + line: 0, + character: 2, + }, + }, + severity: Some( + lsp::DiagnosticSeverity::ERROR, + ), + message, + ..lsp::Diagnostic::default() + }], + }, + }, + ), + )) + } + }, + ); + let _ = closure_pull_diagnostics_handle.lock().insert(pull_diagnostics_handle); + + let closure_workspace_diagnostics_pulls_made = closure_workspace_diagnostics_pulls_made.clone(); + let workspace_diagnostics_pulls_handle = fake_language_server.set_request_handler::( + move |params, _| { + let workspace_requests_made = closure_workspace_diagnostics_pulls_made.clone(); + let workspace_diagnostics_pulls_result_ids = + closure_workspace_diagnostics_pulls_result_ids.clone(); + let workspace_diagnostic_cancel_rx = closure_workspace_diagnostic_cancel_rx.clone(); + let workspace_diagnostic_received_tx = closure_workspace_diagnostic_received_tx.clone(); + let expected_workspace_diagnostic_token = expected_workspace_diagnostic_token.clone(); + async move { + let workspace_request_count = + workspace_requests_made.fetch_add(1, atomic::Ordering::Release) + 1; + { + workspace_diagnostics_pulls_result_ids + .lock() + .await + .extend(params.previous_result_ids.into_iter().map(|id| id.value)); + } + if should_stream_workspace_diagnostic && !workspace_diagnostic_cancel_rx.is_closed() + { + assert_eq!( + params.partial_result_params.partial_result_token, + Some(expected_workspace_diagnostic_token) + ); + workspace_diagnostic_received_tx.send(()).await.unwrap(); + workspace_diagnostic_cancel_rx.recv().await.unwrap(); + workspace_diagnostic_cancel_rx.close(); + // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#partialResults + // > The final response has to be empty in terms of result values. + return Ok(lsp::WorkspaceDiagnosticReportResult::Report( + lsp::WorkspaceDiagnosticReport { items: Vec::new() }, + )); + } + Ok(lsp::WorkspaceDiagnosticReportResult::Report( + lsp::WorkspaceDiagnosticReport { + items: vec![ + lsp::WorkspaceDocumentDiagnosticReport::Full( + lsp::WorkspaceFullDocumentDiagnosticReport { + uri: lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(), + version: None, + full_document_diagnostic_report: + lsp::FullDocumentDiagnosticReport { + result_id: Some(format!( + "workspace_{workspace_request_count}" + )), + items: vec![lsp::Diagnostic { + range: lsp::Range { + start: lsp::Position { + line: 0, + character: 1, + }, + end: lsp::Position { + line: 0, + character: 3, + }, + }, + severity: Some(lsp::DiagnosticSeverity::WARNING), + message: + expected_workspace_pull_diagnostics_main_message + .to_string(), + ..lsp::Diagnostic::default() + }], + }, + }, + ), + lsp::WorkspaceDocumentDiagnosticReport::Full( + lsp::WorkspaceFullDocumentDiagnosticReport { + uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(), + version: None, + full_document_diagnostic_report: + lsp::FullDocumentDiagnosticReport { + result_id: Some(format!( + "workspace_{workspace_request_count}" + )), + items: vec![lsp::Diagnostic { + range: lsp::Range { + start: lsp::Position { + line: 0, + character: 1, + }, + end: lsp::Position { + line: 0, + character: 3, + }, + }, + severity: Some(lsp::DiagnosticSeverity::WARNING), + message: + expected_workspace_pull_diagnostics_lib_message + .to_string(), + ..lsp::Diagnostic::default() + }], + }, + }, + ), + ], + }, + )) + } + }); + let _ = closure_workspace_diagnostics_pulls_handle.lock().insert(workspace_diagnostics_pulls_handle); + })), ..FakeLspAdapter::default() }, ); + client_b.language_registry().add(rust_lang()); client_b.language_registry().register_fake_lsp_adapter( "Rust", @@ -2631,183 +2834,15 @@ async fn test_lsp_pull_diagnostics( .unwrap(); let fake_language_server = fake_language_servers.next().await.unwrap(); - cx_a.run_until_parked(); - cx_b.run_until_parked(); - let expected_push_diagnostic_main_message = "pushed main diagnostic"; - let expected_push_diagnostic_lib_message = "pushed lib diagnostic"; - let expected_pull_diagnostic_main_message = "pulled main diagnostic"; - let expected_pull_diagnostic_lib_message = "pulled lib diagnostic"; - let expected_workspace_pull_diagnostics_main_message = "pulled workspace main diagnostic"; - let expected_workspace_pull_diagnostics_lib_message = "pulled workspace lib diagnostic"; - - let diagnostics_pulls_result_ids = Arc::new(Mutex::new(BTreeSet::>::new())); - let workspace_diagnostics_pulls_result_ids = Arc::new(Mutex::new(BTreeSet::::new())); - let diagnostics_pulls_made = Arc::new(AtomicUsize::new(0)); - let closure_diagnostics_pulls_made = diagnostics_pulls_made.clone(); - let closure_diagnostics_pulls_result_ids = diagnostics_pulls_result_ids.clone(); - let mut pull_diagnostics_handle = fake_language_server - .set_request_handler::(move |params, _| { - let requests_made = closure_diagnostics_pulls_made.clone(); - let diagnostics_pulls_result_ids = closure_diagnostics_pulls_result_ids.clone(); - async move { - let message = if lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap() - == params.text_document.uri - { - expected_pull_diagnostic_main_message.to_string() - } else if lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap() - == params.text_document.uri - { - expected_pull_diagnostic_lib_message.to_string() - } else { - panic!("Unexpected document: {}", params.text_document.uri) - }; - { - diagnostics_pulls_result_ids - .lock() - .await - .insert(params.previous_result_id); - } - let new_requests_count = requests_made.fetch_add(1, atomic::Ordering::Release) + 1; - Ok(lsp::DocumentDiagnosticReportResult::Report( - lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport { - related_documents: None, - full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport { - result_id: Some(format!("pull-{new_requests_count}")), - items: vec![lsp::Diagnostic { - range: lsp::Range { - start: lsp::Position { - line: 0, - character: 0, - }, - end: lsp::Position { - line: 0, - character: 2, - }, - }, - severity: Some(lsp::DiagnosticSeverity::ERROR), - message, - ..lsp::Diagnostic::default() - }], - }, - }), - )) - } - }); - - let workspace_diagnostics_pulls_made = Arc::new(AtomicUsize::new(0)); - let closure_workspace_diagnostics_pulls_made = workspace_diagnostics_pulls_made.clone(); - let closure_workspace_diagnostics_pulls_result_ids = - workspace_diagnostics_pulls_result_ids.clone(); - let (workspace_diagnostic_cancel_tx, closure_workspace_diagnostic_cancel_rx) = - smol::channel::bounded::<()>(1); - let (closure_workspace_diagnostic_received_tx, workspace_diagnostic_received_rx) = - smol::channel::bounded::<()>(1); let expected_workspace_diagnostic_token = lsp::ProgressToken::String(format!( "workspace/diagnostic-{}-1", fake_language_server.server.server_id() )); - let closure_expected_workspace_diagnostic_token = expected_workspace_diagnostic_token.clone(); - let mut workspace_diagnostics_pulls_handle = fake_language_server - .set_request_handler::( - move |params, _| { - let workspace_requests_made = closure_workspace_diagnostics_pulls_made.clone(); - let workspace_diagnostics_pulls_result_ids = - closure_workspace_diagnostics_pulls_result_ids.clone(); - let workspace_diagnostic_cancel_rx = closure_workspace_diagnostic_cancel_rx.clone(); - let workspace_diagnostic_received_tx = closure_workspace_diagnostic_received_tx.clone(); - let expected_workspace_diagnostic_token = - closure_expected_workspace_diagnostic_token.clone(); - async move { - let workspace_request_count = - workspace_requests_made.fetch_add(1, atomic::Ordering::Release) + 1; - { - workspace_diagnostics_pulls_result_ids - .lock() - .await - .extend(params.previous_result_ids.into_iter().map(|id| id.value)); - } - if should_stream_workspace_diagnostic && !workspace_diagnostic_cancel_rx.is_closed() - { - assert_eq!( - params.partial_result_params.partial_result_token, - Some(expected_workspace_diagnostic_token) - ); - workspace_diagnostic_received_tx.send(()).await.unwrap(); - workspace_diagnostic_cancel_rx.recv().await.unwrap(); - workspace_diagnostic_cancel_rx.close(); - // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#partialResults - // > The final response has to be empty in terms of result values. - return Ok(lsp::WorkspaceDiagnosticReportResult::Report( - lsp::WorkspaceDiagnosticReport { items: Vec::new() }, - )); - } - Ok(lsp::WorkspaceDiagnosticReportResult::Report( - lsp::WorkspaceDiagnosticReport { - items: vec![ - lsp::WorkspaceDocumentDiagnosticReport::Full( - lsp::WorkspaceFullDocumentDiagnosticReport { - uri: lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(), - version: None, - full_document_diagnostic_report: - lsp::FullDocumentDiagnosticReport { - result_id: Some(format!( - "workspace_{workspace_request_count}" - )), - items: vec![lsp::Diagnostic { - range: lsp::Range { - start: lsp::Position { - line: 0, - character: 1, - }, - end: lsp::Position { - line: 0, - character: 3, - }, - }, - severity: Some(lsp::DiagnosticSeverity::WARNING), - message: - expected_workspace_pull_diagnostics_main_message - .to_string(), - ..lsp::Diagnostic::default() - }], - }, - }, - ), - lsp::WorkspaceDocumentDiagnosticReport::Full( - lsp::WorkspaceFullDocumentDiagnosticReport { - uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(), - version: None, - full_document_diagnostic_report: - lsp::FullDocumentDiagnosticReport { - result_id: Some(format!( - "workspace_{workspace_request_count}" - )), - items: vec![lsp::Diagnostic { - range: lsp::Range { - start: lsp::Position { - line: 0, - character: 1, - }, - end: lsp::Position { - line: 0, - character: 3, - }, - }, - severity: Some(lsp::DiagnosticSeverity::WARNING), - message: - expected_workspace_pull_diagnostics_lib_message - .to_string(), - ..lsp::Diagnostic::default() - }], - }, - }, - ), - ], - }, - )) - } - }, - ); + cx_a.run_until_parked(); + cx_b.run_until_parked(); + let mut pull_diagnostics_handle = pull_diagnostics_handle.lock().take().unwrap(); + let mut workspace_diagnostics_pulls_handle = + workspace_diagnostics_pulls_handle.lock().take().unwrap(); if should_stream_workspace_diagnostic { workspace_diagnostic_received_rx.recv().await.unwrap(); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 5b0d4b68a5f1d87c55648943f4798e679d59aacc..6229af77ebb27c4d163a26105517905c5665fe04 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1190,6 +1190,7 @@ pub struct Editor { inline_value_cache: InlineValueCache, selection_drag_state: SelectionDragState, colors: Option, + post_scroll_update: Task<()>, refresh_colors_task: Task<()>, folding_newlines: Task<()>, pub lookup_key: Option>, @@ -1786,7 +1787,7 @@ impl Editor { fn new_internal( mode: EditorMode, - buffer: Entity, + multi_buffer: Entity, project: Option>, display_map: Option>, window: &mut Window, @@ -1844,7 +1845,7 @@ impl Editor { let display_map = display_map.unwrap_or_else(|| { cx.new(|cx| { DisplayMap::new( - buffer.clone(), + multi_buffer.clone(), style.font(), font_size, None, @@ -1857,7 +1858,7 @@ impl Editor { }) }); - let selections = SelectionsCollection::new(display_map.clone(), buffer.clone()); + let selections = SelectionsCollection::new(display_map.clone(), multi_buffer.clone()); let blink_manager = cx.new(|cx| { let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx); @@ -1909,8 +1910,19 @@ impl Editor { } } project::Event::LanguageServerBufferRegistered { buffer_id, .. } => { - if editor.buffer().read(cx).buffer(*buffer_id).is_some() { - editor.update_lsp_data(false, Some(*buffer_id), window, cx); + let buffer_id = *buffer_id; + if editor.buffer().read(cx).buffer(buffer_id).is_some() { + let registered = editor.register_buffer(buffer_id, cx); + if registered { + editor.update_lsp_data(Some(buffer_id), window, cx); + editor.refresh_inlay_hints( + InlayHintRefreshReason::RefreshRequested, + cx, + ); + refresh_linked_ranges(editor, window, cx); + editor.refresh_code_actions(window, cx); + editor.refresh_document_highlights(cx); + } } } @@ -2029,7 +2041,7 @@ impl Editor { })); } - let buffer_snapshot = buffer.read(cx).snapshot(cx); + let buffer_snapshot = multi_buffer.read(cx).snapshot(cx); let inlay_hint_settings = inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx); @@ -2066,8 +2078,8 @@ impl Editor { update_uncommitted_diff_for_buffer( cx.entity(), &project, - buffer.read(cx).all_buffers(), - buffer.clone(), + multi_buffer.read(cx).all_buffers(), + multi_buffer.clone(), cx, ) .shared(), @@ -2079,7 +2091,7 @@ impl Editor { focus_handle, show_cursor_when_unfocused: false, last_focused_descendant: None, - buffer: buffer.clone(), + buffer: multi_buffer.clone(), display_map: display_map.clone(), placeholder_display_map: None, selections, @@ -2221,8 +2233,8 @@ impl Editor { _subscriptions: (!is_minimap) .then(|| { vec![ - cx.observe(&buffer, Self::on_buffer_changed), - cx.subscribe_in(&buffer, window, Self::on_buffer_event), + cx.observe(&multi_buffer, Self::on_buffer_changed), + cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event), cx.observe_in(&display_map, window, Self::on_display_map_changed), cx.observe(&blink_manager, |_, _, cx| cx.notify()), cx.observe_global_in::(window, Self::settings_changed), @@ -2248,6 +2260,7 @@ impl Editor { colors: None, refresh_colors_task: Task::ready(()), next_color_inlay_id: 0, + post_scroll_update: Task::ready(()), linked_edit_ranges: Default::default(), in_project_search: false, previous_search_ranges: None, @@ -2370,7 +2383,7 @@ impl Editor { editor.selection_history.mode = SelectionHistoryMode::Normal; editor.scroll_manager.show_scrollbars(window, cx); - jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx); + jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx); if full_mode { let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars(); @@ -2382,24 +2395,13 @@ impl Editor { editor.go_to_active_debug_line(window, cx); - if let Some(buffer) = buffer.read(cx).as_singleton() - && let Some(project) = editor.project() - { - let handle = project.update(cx, |project, cx| { - project.register_buffer_with_language_servers(&buffer, cx) - }); - editor - .registered_buffers - .insert(buffer.read(cx).remote_id(), handle); + if let Some(buffer) = multi_buffer.read(cx).as_singleton() { + editor.register_buffer(buffer.read(cx).remote_id(), cx); } editor.minimap = editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx); editor.colors = Some(LspColorData::new(cx)); - editor.update_lsp_data(false, None, window, cx); - } - - if editor.mode.is_full() { editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx); } @@ -2899,20 +2901,6 @@ impl Editor { self.collapse_matches = collapse_matches; } - fn register_buffers_with_language_servers(&mut self, cx: &mut Context) { - let buffers = self.buffer.read(cx).all_buffers(); - let Some(project) = self.project.as_ref() else { - return; - }; - project.update(cx, |project, cx| { - for buffer in buffers { - self.registered_buffers - .entry(buffer.read(cx).remote_id()) - .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx)); - } - }) - } - pub fn range_for_match(&self, range: &Range) -> Range { if self.collapse_matches { return range.start..range.start; @@ -3112,19 +3100,8 @@ impl Editor { } if local { - if let Some(buffer_id) = new_cursor_position.buffer_id - && !self.registered_buffers.contains_key(&buffer_id) - && let Some(project) = self.project.as_ref() - { - project.update(cx, |project, cx| { - let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else { - return; - }; - self.registered_buffers.insert( - buffer_id, - project.register_buffer_with_language_servers(&buffer, cx), - ); - }) + if let Some(buffer_id) = new_cursor_position.buffer_id { + self.register_buffer(buffer_id, cx); } let mut context_menu = self.context_menu.borrow_mut(); @@ -3174,11 +3151,12 @@ impl Editor { } self.refresh_code_actions(window, cx); self.refresh_document_highlights(cx); + refresh_linked_ranges(self, window, cx); + self.refresh_selected_text_highlights(false, window, cx); refresh_matching_bracket_highlights(self, cx); self.update_visible_edit_prediction(window, cx); self.edit_prediction_requires_modifier_in_indent_conflict = true; - linked_editing_ranges::refresh_linked_ranges(self, window, cx); self.inline_blame_popover.take(); if self.git_blame_inline_enabled { self.start_inline_blame_timer(window, cx); @@ -4444,7 +4422,7 @@ impl Editor { } } this.trigger_completion_on_input(&text, trigger_in_words, window, cx); - linked_editing_ranges::refresh_linked_ranges(this, window, cx); + refresh_linked_ranges(this, window, cx); this.refresh_edit_prediction(true, false, window, cx); jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx); }); @@ -5305,12 +5283,18 @@ impl Editor { } }; + let mut visible_excerpts = self.visible_excerpts(required_languages.as_ref(), cx); + visible_excerpts.retain(|_, (buffer, _, _)| { + self.registered_buffers + .contains_key(&buffer.read(cx).remote_id()) + }); + if let Some(InlaySplice { to_remove, to_insert, }) = self.inlay_hint_cache.spawn_hint_refresh( reason_description, - self.visible_excerpts(required_languages.as_ref(), cx), + visible_excerpts, invalidate_cache, ignore_debounce, cx, @@ -10107,7 +10091,7 @@ impl Editor { }) } this.refresh_edit_prediction(true, false, window, cx); - linked_editing_ranges::refresh_linked_ranges(this, window, cx); + refresh_linked_ranges(this, window, cx); }); } @@ -16974,40 +16958,31 @@ impl Editor { editor }) }); - editor.update(cx, |editor, cx| { - match multibuffer_selection_mode { - MultibufferSelectionMode::First => { - if let Some(first_range) = ranges.first() { - editor.change_selections( - SelectionEffects::no_scroll(), - window, - cx, - |selections| { - selections.clear_disjoint(); - selections - .select_anchor_ranges(std::iter::once(first_range.clone())); - }, - ); - } - editor.highlight_background::( - &ranges, - |theme| theme.colors().editor_highlighted_line_background, - cx, - ); - } - MultibufferSelectionMode::All => { + editor.update(cx, |editor, cx| match multibuffer_selection_mode { + MultibufferSelectionMode::First => { + if let Some(first_range) = ranges.first() { editor.change_selections( SelectionEffects::no_scroll(), window, cx, |selections| { selections.clear_disjoint(); - selections.select_anchor_ranges(ranges); + selections.select_anchor_ranges(std::iter::once(first_range.clone())); }, ); } + editor.highlight_background::( + &ranges, + |theme| theme.colors().editor_highlighted_line_background, + cx, + ); + } + MultibufferSelectionMode::All => { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { + selections.clear_disjoint(); + selections.select_anchor_ranges(ranges); + }); } - editor.register_buffers_with_language_servers(cx); }); let item = Box::new(editor); @@ -17854,7 +17829,7 @@ impl Editor { window: &Window, cx: &mut Context, ) -> Option<()> { - if !self.mode().is_full() { + if self.ignore_lsp_data() { return None; } let pull_diagnostics_settings = ProjectSettings::get_global(cx) @@ -17866,8 +17841,14 @@ impl Editor { let project = self.project()?.downgrade(); let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms); let mut buffers = self.buffer.read(cx).all_buffers(); - if let Some(buffer_id) = buffer_id { - buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id); + buffers.retain(|buffer| { + let buffer_id_to_retain = buffer.read(cx).remote_id(); + buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain) + && self.registered_buffers.contains_key(&buffer_id_to_retain) + }); + if buffers.is_empty() { + self.pull_diagnostics_task = Task::ready(()); + return None; } self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| { @@ -20864,10 +20845,7 @@ impl Editor { cx: &mut Context, ) { match event { - multi_buffer::Event::Edited { - singleton_buffer_edited, - edited_buffer, - } => { + multi_buffer::Event::Edited { edited_buffer } => { self.scrollbar_marker_state.dirty = true; self.active_indent_guides_state.dirty = true; self.refresh_active_diagnostics(cx); @@ -20878,31 +20856,16 @@ impl Editor { if self.has_active_edit_prediction() { self.update_visible_edit_prediction(window, cx); } - if let Some(project) = self.project.as_ref() - && let Some(edited_buffer) = edited_buffer - { - project.update(cx, |project, cx| { - self.registered_buffers - .entry(edited_buffer.read(cx).remote_id()) - .or_insert_with(|| { - project.register_buffer_with_language_servers(edited_buffer, cx) - }); - }); - } - cx.emit(EditorEvent::BufferEdited); - cx.emit(SearchEvent::MatchesInvalidated); - if let Some(buffer) = edited_buffer { - self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx); - } - - if *singleton_buffer_edited { - if let Some(buffer) = edited_buffer - && buffer.read(cx).file().is_none() - { + if let Some(edited_buffer) = edited_buffer { + if edited_buffer.read(cx).file().is_none() { cx.emit(EditorEvent::TitleChanged); } - if let Some(project) = &self.project { + + let buffer_id = edited_buffer.read(cx).remote_id(); + if let Some(project) = self.project.clone() { + self.register_buffer(buffer_id, cx); + self.update_lsp_data(Some(buffer_id), window, cx); #[allow(clippy::mutable_key_type)] let languages_affected = multibuffer.update(cx, |multibuffer, cx| { multibuffer @@ -20929,6 +20892,9 @@ impl Editor { } } + cx.emit(EditorEvent::BufferEdited); + cx.emit(SearchEvent::MatchesInvalidated); + let Some(project) = &self.project else { return }; let (telemetry, is_via_ssh) = { let project = project.read(cx); @@ -20936,7 +20902,6 @@ impl Editor { let is_via_ssh = project.is_via_remote_server(); (telemetry, is_via_ssh) }; - refresh_linked_ranges(self, window, cx); telemetry.log_edit_event("editor", is_via_ssh); } multi_buffer::Event::ExcerptsAdded { @@ -20958,24 +20923,22 @@ impl Editor { ) .detach(); } - if self.active_diagnostics != ActiveDiagnostic::All { - self.update_lsp_data(false, Some(buffer_id), window, cx); - } + self.update_lsp_data(Some(buffer_id), window, cx); + self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx); cx.emit(EditorEvent::ExcerptsAdded { buffer: buffer.clone(), predecessor: *predecessor, excerpts: excerpts.clone(), }); - self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx); } multi_buffer::Event::ExcerptsRemoved { ids, removed_buffer_ids, } => { self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx); - let buffer = self.buffer.read(cx); - self.registered_buffers - .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some()); + for buffer_id in removed_buffer_ids { + self.registered_buffers.remove(buffer_id); + } jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx); cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone(), @@ -21007,7 +20970,7 @@ impl Editor { self.tasks_update_task = Some(self.refresh_runnables(window, cx)); } multi_buffer::Event::LanguageChanged(buffer_id) => { - linked_editing_ranges::refresh_linked_ranges(self, window, cx); + self.registered_buffers.remove(&buffer_id); jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx); cx.emit(EditorEvent::Reparsed(*buffer_id)); cx.notify(); @@ -21148,7 +21111,7 @@ impl Editor { if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() { self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx); } - self.refresh_colors(false, None, window, cx); + self.refresh_colors_for_visible_range(None, window, cx); } cx.notify(); @@ -22060,13 +22023,48 @@ impl Editor { fn update_lsp_data( &mut self, - ignore_cache: bool, for_buffer: Option, window: &mut Window, cx: &mut Context<'_, Self>, ) { self.pull_diagnostics(for_buffer, window, cx); - self.refresh_colors(ignore_cache, for_buffer, window, cx); + self.refresh_colors_for_visible_range(for_buffer, window, cx); + } + + fn register_visible_buffers(&mut self, cx: &mut Context) { + // Singletons are registered on editor creation. + if self.ignore_lsp_data() || self.buffer().read(cx).is_singleton() { + return; + } + for (_, (visible_buffer, _, _)) in self.visible_excerpts(None, cx) { + self.register_buffer(visible_buffer.read(cx).remote_id(), cx); + } + } + + fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context) -> bool { + if !self.registered_buffers.contains_key(&buffer_id) + && let Some(project) = self.project.as_ref() + { + if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) { + project.update(cx, |project, cx| { + self.registered_buffers.insert( + buffer_id, + project.register_buffer_with_language_servers(&buffer, cx), + ); + }); + return true; + } else { + self.registered_buffers.remove(&buffer_id); + } + } + + false + } + + fn ignore_lsp_data(&self) -> bool { + // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view, + // skip any LSP updates for it. + self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full() } } diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index fe2ab972ef6920a8530b493d09f7d0e279e147a5..34d737452e905449c259fb41fe03a96e34159b05 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -2394,6 +2394,8 @@ pub mod tests { editor.scroll_screen(&ScrollAmount::Page(1.0), window, cx); }) .unwrap(); + // Wait for the first hints request to fire off + cx.executor().advance_clock(Duration::from_millis(100)); cx.executor().run_until_parked(); editor .update(cx, |editor, window, cx| { @@ -2752,6 +2754,23 @@ pub mod tests { }) .unwrap(); cx.executor().run_until_parked(); + editor + .update(cx, |editor, _window, cx| { + let expected_hints = vec![ + "main hint #0".to_string(), + "main hint #1".to_string(), + "main hint #2".to_string(), + "main hint #3".to_string(), + "main hint #4".to_string(), + "main hint #5".to_string(), + ]; + assert_eq!(expected_hints, sorted_cached_hint_labels(editor), + "New hints are not shown right after scrolling, we need to wait for the buffer to be registered"); + assert_eq!(expected_hints, visible_hint_labels(editor, cx)); + }) + .unwrap(); + cx.executor().advance_clock(Duration::from_millis(100)); + cx.executor().run_until_parked(); editor .update(cx, |editor, _window, cx| { let expected_hints = vec![ @@ -2766,7 +2785,7 @@ pub mod tests { "other hint #2".to_string(), ]; assert_eq!(expected_hints, sorted_cached_hint_labels(editor), - "With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits"); + "After scrolling to the new buffer and waiting for it to be registered, new hints should appear"); assert_eq!(expected_hints, visible_hint_labels(editor, cx)); }) .unwrap(); @@ -2853,15 +2872,18 @@ pub mod tests { }) .unwrap(); cx.executor().run_until_parked(); + // Wait again to trigger the inlay hints fetch on scroll + cx.executor().advance_clock(Duration::from_millis(100)); + cx.executor().run_until_parked(); editor .update(cx, |editor, _window, cx| { let expected_hints = vec![ - "main hint #0".to_string(), - "main hint #1".to_string(), - "main hint #2".to_string(), - "main hint #3".to_string(), - "main hint #4".to_string(), - "main hint #5".to_string(), + "main hint(edited) #0".to_string(), + "main hint(edited) #1".to_string(), + "main hint(edited) #2".to_string(), + "main hint(edited) #3".to_string(), + "main hint(edited) #4".to_string(), + "main hint(edited) #5".to_string(), "other hint(edited) #0".to_string(), "other hint(edited) #1".to_string(), ]; diff --git a/crates/editor/src/linked_editing_ranges.rs b/crates/editor/src/linked_editing_ranges.rs index 4f1313797f97b1d482effced60b6843541c9e3a7..a26e24e1cbca4f06e94cde4898e7f9d2a80da8e1 100644 --- a/crates/editor/src/linked_editing_ranges.rs +++ b/crates/editor/src/linked_editing_ranges.rs @@ -48,7 +48,7 @@ pub(super) fn refresh_linked_ranges( window: &mut Window, cx: &mut Context, ) -> Option<()> { - if editor.pending_rename.is_some() { + if editor.ignore_lsp_data() || editor.pending_rename.is_some() { return None; } let project = editor.project()?.downgrade(); diff --git a/crates/editor/src/lsp_colors.rs b/crates/editor/src/lsp_colors.rs index f13ffd69206d3d3d89ba8dd29ff6f73fd93e0bec..e7fdb6aaa1f661b717945b568f23062a2fb41f12 100644 --- a/crates/editor/src/lsp_colors.rs +++ b/crates/editor/src/lsp_colors.rs @@ -2,11 +2,11 @@ use std::{cmp, ops::Range}; use collections::HashMap; use futures::future::join_all; -use gpui::{Hsla, Rgba}; +use gpui::{Hsla, Rgba, Task}; use itertools::Itertools; use language::point_from_lsp; use multi_buffer::Anchor; -use project::{DocumentColor, lsp_store::LspFetchStrategy}; +use project::DocumentColor; use settings::Settings as _; use text::{Bias, BufferId, OffsetRangeExt as _}; use ui::{App, Context, Window}; @@ -143,14 +143,13 @@ impl LspColorData { } impl Editor { - pub(super) fn refresh_colors( + pub(super) fn refresh_colors_for_visible_range( &mut self, - ignore_cache: bool, buffer_id: Option, _: &Window, cx: &mut Context, ) { - if !self.mode().is_full() { + if self.ignore_lsp_data() { return; } let Some(project) = self.project.clone() else { @@ -169,7 +168,9 @@ impl Editor { .into_values() .map(|(buffer, ..)| buffer) .filter(|editor_buffer| { - buffer_id.is_none_or(|buffer_id| buffer_id == editor_buffer.read(cx).remote_id()) + let editor_buffer_id = editor_buffer.read(cx).remote_id(); + buffer_id.is_none_or(|buffer_id| buffer_id == editor_buffer_id) + && self.registered_buffers.contains_key(&editor_buffer_id) }) .unique_by(|buffer| buffer.read(cx).remote_id()) .collect::>(); @@ -179,21 +180,20 @@ impl Editor { .into_iter() .filter_map(|buffer| { let buffer_id = buffer.read(cx).remote_id(); - let fetch_strategy = if ignore_cache { - LspFetchStrategy::IgnoreCache - } else { - LspFetchStrategy::UseCache { - known_cache_version: self.colors.as_ref().and_then(|colors| { - Some(colors.buffer_colors.get(&buffer_id)?.cache_version_used) - }), - } - }; - let colors_task = lsp_store.document_colors(fetch_strategy, buffer, cx)?; + let known_cache_version = self.colors.as_ref().and_then(|colors| { + Some(colors.buffer_colors.get(&buffer_id)?.cache_version_used) + }); + let colors_task = lsp_store.document_colors(known_cache_version, buffer, cx)?; Some(async move { (buffer_id, colors_task.await) }) }) .collect::>() }); + if all_colors_task.is_empty() { + self.refresh_colors_task = Task::ready(()); + return; + } + self.refresh_colors_task = cx.spawn(async move |editor, cx| { cx.background_executor() .timer(FETCH_COLORS_DEBOUNCE_TIMEOUT) diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index dae668a4b4f9cd718d034f45259d3706e515fafb..001be45ab814e1627dc34abbba342272d3e15750 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -494,15 +494,15 @@ impl Editor { let opened_first_time = self.scroll_manager.visible_line_count.is_none(); self.scroll_manager.visible_line_count = Some(lines); if opened_first_time { - cx.spawn_in(window, async move |editor, cx| { + self.post_scroll_update = cx.spawn_in(window, async move |editor, cx| { editor .update_in(cx, |editor, window, cx| { + editor.register_visible_buffers(cx); editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx); - editor.refresh_colors(false, None, window, cx); + editor.update_lsp_data(None, window, cx); }) - .ok() - }) - .detach() + .ok(); + }); } } @@ -613,8 +613,19 @@ impl Editor { cx, ); - self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx); - self.refresh_colors(false, None, window, cx); + self.post_scroll_update = cx.spawn_in(window, async move |editor, cx| { + cx.background_executor() + .timer(Duration::from_millis(50)) + .await; + editor + .update_in(cx, |editor, window, cx| { + editor.register_visible_buffers(cx); + editor.refresh_colors_for_visible_range(None, window, cx); + editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx); + }) + .ok(); + }); + editor_was_scrolled } diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index be01c4b6a1f9f67703f99bcd0b9b331574a6b360..0347757660c584efbfe3c786964d1cad51d4a325 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -99,7 +99,6 @@ pub enum Event { }, DiffHunksToggled, Edited { - singleton_buffer_edited: bool, edited_buffer: Option>, }, TransactionUndone { @@ -2033,7 +2032,6 @@ impl MultiBuffer { DiffChangeKind::BufferEdited, ); cx.emit(Event::Edited { - singleton_buffer_edited: false, edited_buffer: None, }); cx.emit(Event::ExcerptsAdded { @@ -2074,7 +2072,6 @@ impl MultiBuffer { DiffChangeKind::BufferEdited, ); cx.emit(Event::Edited { - singleton_buffer_edited: false, edited_buffer: None, }); cx.emit(Event::ExcerptsRemoved { @@ -2361,7 +2358,6 @@ impl MultiBuffer { self.sync_diff_transforms(&mut snapshot, edits, DiffChangeKind::BufferEdited); self.buffer_changed_since_sync.replace(true); cx.emit(Event::Edited { - singleton_buffer_edited: false, edited_buffer: None, }); cx.emit(Event::ExcerptsRemoved { @@ -2429,7 +2425,6 @@ impl MultiBuffer { use language::BufferEvent; cx.emit(match event { BufferEvent::Edited => Event::Edited { - singleton_buffer_edited: true, edited_buffer: Some(buffer), }, BufferEvent::DirtyChanged => Event::DirtyChanged, @@ -2528,7 +2523,6 @@ impl MultiBuffer { }, ); cx.emit(Event::Edited { - singleton_buffer_edited: false, edited_buffer: None, }); } @@ -2808,7 +2802,6 @@ impl MultiBuffer { ); cx.emit(Event::DiffHunksToggled); cx.emit(Event::Edited { - singleton_buffer_edited: false, edited_buffer: None, }); } @@ -2892,7 +2885,6 @@ impl MultiBuffer { self.sync_diff_transforms(&mut snapshot, edits, DiffChangeKind::BufferEdited); cx.emit(Event::Edited { - singleton_buffer_edited: false, edited_buffer: None, }); cx.emit(Event::ExcerptsExpanded { ids: vec![id] }); @@ -2997,7 +2989,6 @@ impl MultiBuffer { self.sync_diff_transforms(&mut snapshot, edits, DiffChangeKind::BufferEdited); cx.emit(Event::Edited { - singleton_buffer_edited: false, edited_buffer: None, }); cx.emit(Event::ExcerptsExpanded { ids }); diff --git a/crates/multi_buffer/src/multi_buffer_tests.rs b/crates/multi_buffer/src/multi_buffer_tests.rs index bad99d5412cd009ecaeabe9c6ad7686f07d30862..2f4bfdfda8d023cf08e31ec6ffc9300446f8a400 100644 --- a/crates/multi_buffer/src/multi_buffer_tests.rs +++ b/crates/multi_buffer/src/multi_buffer_tests.rs @@ -157,15 +157,12 @@ fn test_excerpt_boundaries_and_clipping(cx: &mut App) { events.read().as_slice(), &[ Event::Edited { - singleton_buffer_edited: false, edited_buffer: None, }, Event::Edited { - singleton_buffer_edited: false, edited_buffer: None, }, Event::Edited { - singleton_buffer_edited: false, edited_buffer: None, } ] diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 68db3ac2b436e77281b4c61296b78ff5e4cf52d1..34ea47dbc3a4b6f1215bc1c0342f208cabfb76f0 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -3495,12 +3495,6 @@ struct CodeLensData { update: Option<(Global, CodeLensTask)>, } -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum LspFetchStrategy { - IgnoreCache, - UseCache { known_cache_version: Option }, -} - #[derive(Debug)] pub enum LspStoreEvent { LanguageServerAdded(LanguageServerId, LanguageServerName, Option), @@ -6498,47 +6492,35 @@ impl LspStore { pub fn document_colors( &mut self, - fetch_strategy: LspFetchStrategy, + known_cache_version: Option, buffer: Entity, cx: &mut Context, ) -> Option { let version_queried_for = buffer.read(cx).version(); let buffer_id = buffer.read(cx).remote_id(); - match fetch_strategy { - LspFetchStrategy::IgnoreCache => {} - LspFetchStrategy::UseCache { - known_cache_version, - } => { - if let Some(cached_data) = self.lsp_document_colors.get(&buffer_id) - && !version_queried_for.changed_since(&cached_data.colors_for_version) - { - let has_different_servers = self.as_local().is_some_and(|local| { - local - .buffers_opened_in_servers - .get(&buffer_id) - .cloned() - .unwrap_or_default() - != cached_data.colors.keys().copied().collect() - }); - if !has_different_servers { - if Some(cached_data.cache_version) == known_cache_version { - return None; - } else { - return Some( - Task::ready(Ok(DocumentColors { - colors: cached_data - .colors - .values() - .flatten() - .cloned() - .collect(), - cache_version: Some(cached_data.cache_version), - })) - .shared(), - ); - } - } + if let Some(cached_data) = self.lsp_document_colors.get(&buffer_id) + && !version_queried_for.changed_since(&cached_data.colors_for_version) + { + let has_different_servers = self.as_local().is_some_and(|local| { + local + .buffers_opened_in_servers + .get(&buffer_id) + .cloned() + .unwrap_or_default() + != cached_data.colors.keys().copied().collect() + }); + if !has_different_servers { + if Some(cached_data.cache_version) == known_cache_version { + return None; + } else { + return Some( + Task::ready(Ok(DocumentColors { + colors: cached_data.colors.values().flatten().cloned().collect(), + cache_version: Some(cached_data.cache_version), + })) + .shared(), + ); } } } @@ -6564,13 +6546,12 @@ impl LspStore { .map_err(Arc::new); let fetched_colors = match fetched_colors { Ok(fetched_colors) => { - if fetch_strategy != LspFetchStrategy::IgnoreCache - && Some(true) - == buffer - .update(cx, |buffer, _| { - buffer.version() != query_version_queried_for - }) - .ok() + if Some(true) + == buffer + .update(cx, |buffer, _| { + buffer.version() != query_version_queried_for + }) + .ok() { return Ok(DocumentColors::default()); } diff --git a/crates/project/src/manifest_tree/server_tree.rs b/crates/project/src/manifest_tree/server_tree.rs index 0d80b845dc63ce00ed7221162b323b8549ad4075..b6828fdb281d51600096bbcad411f94a91c69e54 100644 --- a/crates/project/src/manifest_tree/server_tree.rs +++ b/crates/project/src/manifest_tree/server_tree.rs @@ -47,7 +47,7 @@ pub struct LanguageServerTree { /// A node in language server tree represents either: /// - A language server that has already been initialized/updated for a given project /// - A soon-to-be-initialized language server. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct LanguageServerTreeNode(Weak); /// Describes a request to launch a language server.