diff --git a/assets/settings/default.json b/assets/settings/default.json index f31feb7356..069a9b910f 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -181,8 +181,6 @@ "current_line_highlight": "all", // Whether to highlight all occurrences of the selected text in an editor. "selection_highlight": true, - // The debounce delay before querying highlights based on the selected text. - "selection_highlight_debounce": 50, // The debounce delay before querying highlights from the language // server based on the current cursor location. "lsp_highlight_debounce": 75, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 4c0bca39ed..6210b03222 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -215,6 +215,7 @@ const MAX_SELECTION_HISTORY_LEN: usize = 1024; pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000); #[doc(hidden)] pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250); +const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100); pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5); pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5); @@ -811,7 +812,8 @@ pub struct Editor { next_completion_id: CompletionId, available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>, code_actions_task: Option>>, - selection_highlight_task: Option>, + quick_selection_highlight_task: Option<(Range, Task<()>)>, + debounced_selection_highlight_task: Option<(Range, Task<()>)>, document_highlights_task: Option>, linked_editing_range_task: Option>>, linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges, @@ -1590,7 +1592,8 @@ impl Editor { code_action_providers, available_code_actions: Default::default(), code_actions_task: Default::default(), - selection_highlight_task: Default::default(), + quick_selection_highlight_task: Default::default(), + debounced_selection_highlight_task: Default::default(), document_highlights_task: Default::default(), linked_editing_range_task: Default::default(), pending_rename: Default::default(), @@ -5475,111 +5478,168 @@ impl Editor { None } - pub fn refresh_selected_text_highlights( + fn prepare_highlight_query_from_selection( &mut self, - window: &mut Window, cx: &mut Context, - ) { + ) -> Option<(String, Range)> { if matches!(self.mode, EditorMode::SingleLine { .. }) { - return; + return None; } - self.selection_highlight_task.take(); if !EditorSettings::get_global(cx).selection_highlight { - self.clear_background_highlights::(cx); - return; + return None; } if self.selections.count() != 1 || self.selections.line_mode { - self.clear_background_highlights::(cx); - return; + return None; } let selection = self.selections.newest::(cx); if selection.is_empty() || selection.start.row != selection.end.row { - self.clear_background_highlights::(cx); - return; + return None; } - let debounce = EditorSettings::get_global(cx).selection_highlight_debounce; - self.selection_highlight_task = Some(cx.spawn_in(window, async move |editor, cx| { - cx.background_executor() - .timer(Duration::from_millis(debounce)) - .await; - let Some(Some(matches_task)) = editor - .update_in(cx, |editor, _, cx| { - if editor.selections.count() != 1 || editor.selections.line_mode { - editor.clear_background_highlights::(cx); - return None; - } - let selection = editor.selections.newest::(cx); - if selection.is_empty() || selection.start.row != selection.end.row { - editor.clear_background_highlights::(cx); - return None; - } - let buffer = editor.buffer().read(cx).snapshot(cx); - let query = buffer.text_for_range(selection.range()).collect::(); - if query.trim().is_empty() { - editor.clear_background_highlights::(cx); - return None; - } - Some(cx.background_spawn(async move { - let mut ranges = Vec::new(); - let selection_anchors = selection.range().to_anchors(&buffer); - for range in [buffer.anchor_before(0)..buffer.anchor_after(buffer.len())] { - for (search_buffer, search_range, excerpt_id) in - buffer.range_to_buffer_ranges(range) - { - ranges.extend( - project::search::SearchQuery::text( - query.clone(), - false, - false, - false, - Default::default(), - Default::default(), - None, - ) - .unwrap() - .search(search_buffer, Some(search_range.clone())) - .await - .into_iter() - .filter_map( - |match_range| { - let start = search_buffer.anchor_after( - search_range.start + match_range.start, - ); - let end = search_buffer.anchor_before( - search_range.start + match_range.end, - ); - let range = Anchor::range_in_buffer( - excerpt_id, - search_buffer.remote_id(), - start..end, - ); - (range != selection_anchors).then_some(range) - }, - ), - ); - } - } - ranges - })) - }) - .log_err() - else { - return; - }; - let matches = matches_task.await; + let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx); + let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot); + let query = multi_buffer_snapshot + .text_for_range(selection_anchor_range.clone()) + .collect::(); + if query.trim().is_empty() { + return None; + } + Some((query, selection_anchor_range)) + } + + fn update_selection_occurrence_highlights( + &mut self, + query_text: String, + query_range: Range, + multi_buffer_range_to_query: Range, + use_debounce: bool, + window: &mut Window, + cx: &mut Context, + ) -> Task<()> { + let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx); + cx.spawn_in(window, async move |editor, cx| { + if use_debounce { + cx.background_executor() + .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT) + .await; + } + let match_task = cx.background_spawn(async move { + let buffer_ranges = multi_buffer_snapshot + .range_to_buffer_ranges(multi_buffer_range_to_query) + .into_iter() + .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty()); + let mut match_ranges = Vec::new(); + for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges { + match_ranges.extend( + project::search::SearchQuery::text( + query_text.clone(), + false, + false, + false, + Default::default(), + Default::default(), + None, + ) + .unwrap() + .search(&buffer_snapshot, Some(search_range.clone())) + .await + .into_iter() + .filter_map(|match_range| { + let match_start = buffer_snapshot + .anchor_after(search_range.start + match_range.start); + let match_end = + buffer_snapshot.anchor_before(search_range.start + match_range.end); + let match_anchor_range = Anchor::range_in_buffer( + excerpt_id, + buffer_snapshot.remote_id(), + match_start..match_end, + ); + (match_anchor_range != query_range).then_some(match_anchor_range) + }), + ); + } + match_ranges + }); + let match_ranges = match_task.await; editor .update_in(cx, |editor, _, cx| { editor.clear_background_highlights::(cx); - if !matches.is_empty() { + if !match_ranges.is_empty() { editor.highlight_background::( - &matches, + &match_ranges, |theme| theme.editor_document_highlight_bracket_background, cx, ) } }) .log_err(); - })); + }) + } + + fn refresh_selected_text_highlights(&mut self, window: &mut Window, cx: &mut Context) { + let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx) + else { + self.clear_background_highlights::(cx); + self.quick_selection_highlight_task.take(); + self.debounced_selection_highlight_task.take(); + return; + }; + let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx); + if self + .quick_selection_highlight_task + .as_ref() + .map_or(true, |(prev_anchor_range, _)| { + prev_anchor_range != &query_range + }) + { + let multi_buffer_visible_start = self + .scroll_manager + .anchor() + .anchor + .to_point(&multi_buffer_snapshot); + let multi_buffer_visible_end = multi_buffer_snapshot.clip_point( + multi_buffer_visible_start + + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0), + Bias::Left, + ); + let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end; + self.quick_selection_highlight_task = Some(( + query_range.clone(), + self.update_selection_occurrence_highlights( + query_text.clone(), + query_range.clone(), + multi_buffer_visible_range, + false, + window, + cx, + ), + )); + } + if self + .debounced_selection_highlight_task + .as_ref() + .map_or(true, |(prev_anchor_range, _)| { + prev_anchor_range != &query_range + }) + { + let multi_buffer_start = multi_buffer_snapshot + .anchor_before(0) + .to_point(&multi_buffer_snapshot); + let multi_buffer_end = multi_buffer_snapshot + .anchor_after(multi_buffer_snapshot.len()) + .to_point(&multi_buffer_snapshot); + let multi_buffer_full_range = multi_buffer_start..multi_buffer_end; + self.debounced_selection_highlight_task = Some(( + query_range.clone(), + self.update_selection_occurrence_highlights( + query_text, + query_range, + multi_buffer_full_range, + true, + window, + cx, + ), + )); + } } pub fn refresh_inline_completion( diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index 5622c0c398..25604462c3 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -10,7 +10,6 @@ pub struct EditorSettings { pub cursor_shape: Option, pub current_line_highlight: CurrentLineHighlight, pub selection_highlight: bool, - pub selection_highlight_debounce: u64, pub lsp_highlight_debounce: u64, pub hover_popover_enabled: bool, pub hover_popover_delay: u64, @@ -263,10 +262,6 @@ pub struct EditorSettingsContent { /// /// Default: true pub selection_highlight: Option, - /// The debounce delay before querying highlights based on the selected text. - /// - /// Default: 75 - pub selection_highlight_debounce: Option, /// The debounce delay before querying highlights from the language /// server based on the current cursor location. /// diff --git a/docs/src/configuring-zed.md b/docs/src/configuring-zed.md index eeb7c3ffe4..516ea99159 100644 --- a/docs/src/configuring-zed.md +++ b/docs/src/configuring-zed.md @@ -536,13 +536,6 @@ List of `string` values - Setting: `selection_highlight` - Default: `true` -## Selection Highlight Debounce - -- Description: The debounce delay before querying highlights based on the selected text. - -- Setting: `selection_highlight_debounce` -- Default: `50` - ## LSP Highlight Debounce - Description: The debounce delay before querying highlights from the language server based on the current cursor location.