editor: Highlight all matching occurrences of text in selection (#24835)
Closes #12635 - [x] Get it working - [x] Disable for multi cursor - [x] Disable for vim visual line selection - [x] Add setting to disable it - [x] Add scrollbar marker - [x] Handle delete state capturing selection Preview: https://github.com/user-attachments/assets/a76cde64-4f6c-4575-91cc-3a03a954e7a9 Release Notes: - Added support to highlight all matching occurrences of text within the selection in editor. --------- Co-authored-by: Agus Zubiaga <agus@zed.dev> Co-authored-by: Danilo <danilo@zed.dev>
This commit is contained in:
parent
1e1b637b50
commit
3e9722685b
5 changed files with 144 additions and 2 deletions
|
@ -154,6 +154,10 @@
|
||||||
// 4. Highlight the full line (default):
|
// 4. Highlight the full line (default):
|
||||||
// "all"
|
// "all"
|
||||||
"current_line_highlight": "all",
|
"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
|
// The debounce delay before querying highlights from the language
|
||||||
// server based on the current cursor location.
|
// server based on the current cursor location.
|
||||||
"lsp_highlight_debounce": 75,
|
"lsp_highlight_debounce": 75,
|
||||||
|
@ -259,6 +263,8 @@
|
||||||
"git_diff": true,
|
"git_diff": true,
|
||||||
// Whether to show buffer search results in the scrollbar.
|
// Whether to show buffer search results in the scrollbar.
|
||||||
"search_results": true,
|
"search_results": true,
|
||||||
|
// Whether to show selected text occurrences in the scrollbar.
|
||||||
|
"selected_text": true,
|
||||||
// Whether to show selected symbol occurrences in the scrollbar.
|
// Whether to show selected symbol occurrences in the scrollbar.
|
||||||
"selected_symbol": true,
|
"selected_symbol": true,
|
||||||
// Which diagnostic indicators to show in the scrollbar:
|
// Which diagnostic indicators to show in the scrollbar:
|
||||||
|
|
|
@ -283,6 +283,7 @@ impl InlayId {
|
||||||
enum DocumentHighlightRead {}
|
enum DocumentHighlightRead {}
|
||||||
enum DocumentHighlightWrite {}
|
enum DocumentHighlightWrite {}
|
||||||
enum InputComposition {}
|
enum InputComposition {}
|
||||||
|
enum SelectedTextHighlight {}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub enum Navigated {
|
pub enum Navigated {
|
||||||
|
@ -681,6 +682,7 @@ pub struct Editor {
|
||||||
next_completion_id: CompletionId,
|
next_completion_id: CompletionId,
|
||||||
available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
|
available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
|
||||||
code_actions_task: Option<Task<Result<()>>>,
|
code_actions_task: Option<Task<Result<()>>>,
|
||||||
|
selection_highlight_task: Option<Task<()>>,
|
||||||
document_highlights_task: Option<Task<()>>,
|
document_highlights_task: Option<Task<()>>,
|
||||||
linked_editing_range_task: Option<Task<Option<()>>>,
|
linked_editing_range_task: Option<Task<Option<()>>>,
|
||||||
linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
|
linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
|
||||||
|
@ -1384,6 +1386,7 @@ impl Editor {
|
||||||
code_action_providers,
|
code_action_providers,
|
||||||
available_code_actions: Default::default(),
|
available_code_actions: Default::default(),
|
||||||
code_actions_task: Default::default(),
|
code_actions_task: Default::default(),
|
||||||
|
selection_highlight_task: Default::default(),
|
||||||
document_highlights_task: Default::default(),
|
document_highlights_task: Default::default(),
|
||||||
linked_editing_range_task: Default::default(),
|
linked_editing_range_task: Default::default(),
|
||||||
pending_rename: Default::default(),
|
pending_rename: Default::default(),
|
||||||
|
@ -2165,6 +2168,7 @@ impl Editor {
|
||||||
}
|
}
|
||||||
self.refresh_code_actions(window, cx);
|
self.refresh_code_actions(window, cx);
|
||||||
self.refresh_document_highlights(cx);
|
self.refresh_document_highlights(cx);
|
||||||
|
self.refresh_selected_text_highlights(window, cx);
|
||||||
refresh_matching_bracket_highlights(self, window, cx);
|
refresh_matching_bracket_highlights(self, window, cx);
|
||||||
self.update_visible_inline_completion(window, cx);
|
self.update_visible_inline_completion(window, cx);
|
||||||
self.edit_prediction_requires_modifier_in_leading_space = true;
|
self.edit_prediction_requires_modifier_in_leading_space = true;
|
||||||
|
@ -4722,6 +4726,93 @@ impl Editor {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn refresh_selected_text_highlights(
|
||||||
|
&mut self,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Editor>,
|
||||||
|
) {
|
||||||
|
self.selection_highlight_task.take();
|
||||||
|
if !EditorSettings::get_global(cx).selection_highlight {
|
||||||
|
self.clear_background_highlights::<SelectedTextHighlight>(cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.selections.count() != 1 || self.selections.line_mode {
|
||||||
|
self.clear_background_highlights::<SelectedTextHighlight>(cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let selection = self.selections.newest::<Point>(cx);
|
||||||
|
if selection.is_empty() || selection.start.row != selection.end.row {
|
||||||
|
self.clear_background_highlights::<SelectedTextHighlight>(cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let debounce = EditorSettings::get_global(cx).selection_highlight_debounce;
|
||||||
|
self.selection_highlight_task = Some(cx.spawn_in(window, |editor, mut cx| async move {
|
||||||
|
cx.background_executor()
|
||||||
|
.timer(Duration::from_millis(debounce))
|
||||||
|
.await;
|
||||||
|
let Some(matches_task) = editor
|
||||||
|
.read_with(&mut cx, |editor, cx| {
|
||||||
|
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||||
|
cx.background_executor().spawn(async move {
|
||||||
|
let mut ranges = Vec::new();
|
||||||
|
let buffer_ranges =
|
||||||
|
vec![buffer.anchor_before(0)..buffer.anchor_after(buffer.len())];
|
||||||
|
let query = buffer.text_for_range(selection.range()).collect::<String>();
|
||||||
|
for range in buffer_ranges {
|
||||||
|
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()
|
||||||
|
.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);
|
||||||
|
Anchor::range_in_buffer(
|
||||||
|
excerpt_id,
|
||||||
|
search_buffer.remote_id(),
|
||||||
|
start..end,
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ranges
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.log_err()
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let matches = matches_task.await;
|
||||||
|
editor
|
||||||
|
.update_in(&mut cx, |editor, _, cx| {
|
||||||
|
editor.clear_background_highlights::<SelectedTextHighlight>(cx);
|
||||||
|
if !matches.is_empty() {
|
||||||
|
editor.highlight_background::<SelectedTextHighlight>(
|
||||||
|
&matches,
|
||||||
|
|theme| theme.editor_document_highlight_bracket_background,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn refresh_inline_completion(
|
pub fn refresh_inline_completion(
|
||||||
&mut self,
|
&mut self,
|
||||||
debounce: bool,
|
debounce: bool,
|
||||||
|
|
|
@ -9,6 +9,8 @@ pub struct EditorSettings {
|
||||||
pub cursor_blink: bool,
|
pub cursor_blink: bool,
|
||||||
pub cursor_shape: Option<CursorShape>,
|
pub cursor_shape: Option<CursorShape>,
|
||||||
pub current_line_highlight: CurrentLineHighlight,
|
pub current_line_highlight: CurrentLineHighlight,
|
||||||
|
pub selection_highlight: bool,
|
||||||
|
pub selection_highlight_debounce: u64,
|
||||||
pub lsp_highlight_debounce: u64,
|
pub lsp_highlight_debounce: u64,
|
||||||
pub hover_popover_enabled: bool,
|
pub hover_popover_enabled: bool,
|
||||||
pub hover_popover_delay: u64,
|
pub hover_popover_delay: u64,
|
||||||
|
@ -102,6 +104,7 @@ pub struct Toolbar {
|
||||||
pub struct Scrollbar {
|
pub struct Scrollbar {
|
||||||
pub show: ShowScrollbar,
|
pub show: ShowScrollbar,
|
||||||
pub git_diff: bool,
|
pub git_diff: bool,
|
||||||
|
pub selected_text: bool,
|
||||||
pub selected_symbol: bool,
|
pub selected_symbol: bool,
|
||||||
pub search_results: bool,
|
pub search_results: bool,
|
||||||
pub diagnostics: ScrollbarDiagnostics,
|
pub diagnostics: ScrollbarDiagnostics,
|
||||||
|
@ -271,6 +274,14 @@ pub struct EditorSettingsContent {
|
||||||
///
|
///
|
||||||
/// Default: all
|
/// Default: all
|
||||||
pub current_line_highlight: Option<CurrentLineHighlight>,
|
pub current_line_highlight: Option<CurrentLineHighlight>,
|
||||||
|
/// Whether to highlight all occurrences of the selected text in an editor.
|
||||||
|
///
|
||||||
|
/// Default: true
|
||||||
|
pub selection_highlight: Option<bool>,
|
||||||
|
/// The debounce delay before querying highlights based on the selected text.
|
||||||
|
///
|
||||||
|
/// Default: 75
|
||||||
|
pub selection_highlight_debounce: Option<u64>,
|
||||||
/// The debounce delay before querying highlights from the language
|
/// The debounce delay before querying highlights from the language
|
||||||
/// server based on the current cursor location.
|
/// server based on the current cursor location.
|
||||||
///
|
///
|
||||||
|
@ -404,6 +415,10 @@ pub struct ScrollbarContent {
|
||||||
///
|
///
|
||||||
/// Default: true
|
/// Default: true
|
||||||
pub search_results: Option<bool>,
|
pub search_results: Option<bool>,
|
||||||
|
/// Whether to show selected text occurrences in the scrollbar.
|
||||||
|
///
|
||||||
|
/// Default: true
|
||||||
|
pub selected_text: Option<bool>,
|
||||||
/// Whether to show selected symbol occurrences in the scrollbar.
|
/// Whether to show selected symbol occurrences in the scrollbar.
|
||||||
///
|
///
|
||||||
/// Default: true
|
/// Default: true
|
||||||
|
|
|
@ -20,8 +20,8 @@ use crate::{
|
||||||
EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GoToHunk,
|
EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GoToHunk,
|
||||||
GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor, InlineCompletion,
|
GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor, InlineCompletion,
|
||||||
JumpData, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, RevertSelectedHunks, RowExt,
|
JumpData, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, RevertSelectedHunks, RowExt,
|
||||||
RowRangeExt, SelectPhase, Selection, SoftWrap, StickyHeaderExcerpt, ToPoint, ToggleFold,
|
RowRangeExt, SelectPhase, SelectedTextHighlight, Selection, SoftWrap, StickyHeaderExcerpt,
|
||||||
ToggleStagedSelectedDiffHunks, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT,
|
ToPoint, ToggleFold, ToggleStagedSelectedDiffHunks, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT,
|
||||||
GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
|
GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
|
||||||
};
|
};
|
||||||
use buffer_diff::{DiffHunkSecondaryStatus, DiffHunkStatus};
|
use buffer_diff::{DiffHunkSecondaryStatus, DiffHunkStatus};
|
||||||
|
@ -1296,6 +1296,9 @@ impl EditorElement {
|
||||||
// Buffer Search Results
|
// Buffer Search Results
|
||||||
(is_singleton && scrollbar_settings.search_results && editor.has_background_highlights::<BufferSearchHighlights>())
|
(is_singleton && scrollbar_settings.search_results && editor.has_background_highlights::<BufferSearchHighlights>())
|
||||||
||
|
||
|
||||||
|
// Selected Text Occurrences
|
||||||
|
(is_singleton && scrollbar_settings.selected_text && editor.has_background_highlights::<SelectedTextHighlight>())
|
||||||
|
||
|
||||||
// Selected Symbol Occurrences
|
// Selected Symbol Occurrences
|
||||||
(is_singleton && scrollbar_settings.selected_symbol && (editor.has_background_highlights::<DocumentHighlightRead>() || editor.has_background_highlights::<DocumentHighlightWrite>()))
|
(is_singleton && scrollbar_settings.selected_symbol && (editor.has_background_highlights::<DocumentHighlightRead>() || editor.has_background_highlights::<DocumentHighlightWrite>()))
|
||||||
||
|
||
|
||||||
|
@ -5439,11 +5442,14 @@ impl EditorElement {
|
||||||
{
|
{
|
||||||
let is_search_highlights = *background_highlight_id
|
let is_search_highlights = *background_highlight_id
|
||||||
== TypeId::of::<BufferSearchHighlights>();
|
== TypeId::of::<BufferSearchHighlights>();
|
||||||
|
let is_text_highlights = *background_highlight_id
|
||||||
|
== TypeId::of::<SelectedTextHighlight>();
|
||||||
let is_symbol_occurrences = *background_highlight_id
|
let is_symbol_occurrences = *background_highlight_id
|
||||||
== TypeId::of::<DocumentHighlightRead>()
|
== TypeId::of::<DocumentHighlightRead>()
|
||||||
|| *background_highlight_id
|
|| *background_highlight_id
|
||||||
== TypeId::of::<DocumentHighlightWrite>();
|
== TypeId::of::<DocumentHighlightWrite>();
|
||||||
if (is_search_highlights && scrollbar_settings.search_results)
|
if (is_search_highlights && scrollbar_settings.search_results)
|
||||||
|
|| (is_text_highlights && scrollbar_settings.selected_text)
|
||||||
|| (is_symbol_occurrences && scrollbar_settings.selected_symbol)
|
|| (is_symbol_occurrences && scrollbar_settings.selected_symbol)
|
||||||
{
|
{
|
||||||
let mut color = theme.status().info;
|
let mut color = theme.status().info;
|
||||||
|
|
|
@ -472,6 +472,19 @@ List of `string` values
|
||||||
"current_line_highlight": "all"
|
"current_line_highlight": "all"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Selection Highlight
|
||||||
|
|
||||||
|
- Description: Whether to highlight all occurrences of the selected text in an editor.
|
||||||
|
- 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: `75`
|
||||||
|
|
||||||
## LSP Highlight Debounce
|
## LSP Highlight Debounce
|
||||||
|
|
||||||
- Description: The debounce delay before querying highlights from the language server based on the current cursor location.
|
- Description: The debounce delay before querying highlights from the language server based on the current cursor location.
|
||||||
|
@ -532,6 +545,7 @@ List of `string` values
|
||||||
"cursors": true,
|
"cursors": true,
|
||||||
"git_diff": true,
|
"git_diff": true,
|
||||||
"search_results": true,
|
"search_results": true,
|
||||||
|
"selected_text": true,
|
||||||
"selected_symbol": true,
|
"selected_symbol": true,
|
||||||
"diagnostics": "all",
|
"diagnostics": "all",
|
||||||
"axes": {
|
"axes": {
|
||||||
|
@ -611,6 +625,16 @@ List of `string` values
|
||||||
|
|
||||||
`boolean` values
|
`boolean` values
|
||||||
|
|
||||||
|
### Selected Text Indicators
|
||||||
|
|
||||||
|
- Description: Whether to show selected text occurrences in the scrollbar.
|
||||||
|
- Setting: `selected_text`
|
||||||
|
- Default: `true`
|
||||||
|
|
||||||
|
**Options**
|
||||||
|
|
||||||
|
`boolean` values
|
||||||
|
|
||||||
### Selected Symbols Indicators
|
### Selected Symbols Indicators
|
||||||
|
|
||||||
- Description: Whether to show selected symbol occurrences in the scrollbar.
|
- Description: Whether to show selected symbol occurrences in the scrollbar.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue