diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 14b9d01b21..68fe4b997e 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -294,6 +294,7 @@ "replace_newest": false } ], + "cmd-shift-d": "editor::SelectAllMatches", "ctrl-cmd-d": [ "editor::SelectPrevious", { @@ -453,7 +454,6 @@ "context": "Editor", "bindings": { "ctrl-shift-k": "editor::DeleteLine", - "cmd-shift-d": "editor::DuplicateLine", "cmd-shift-l": "editor::SplitSelectionIntoLines", "ctrl-j": "editor::JoinLines", "ctrl-cmd-up": "editor::MoveLineUp", diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8c30859841..29782eb1f3 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -129,6 +129,12 @@ pub struct SelectPrevious { pub replace_newest: bool, } +#[derive(Clone, Deserialize, PartialEq, Default)] +pub struct SelectAllMatches { + #[serde(default)] + pub replace_newest: bool, +} + #[derive(Clone, Deserialize, PartialEq)] pub struct SelectToBeginningOfLine { #[serde(default)] @@ -325,6 +331,7 @@ impl_actions!( [ SelectNext, SelectPrevious, + SelectAllMatches, SelectToBeginningOfLine, SelectToEndOfLine, ToggleCodeActions, @@ -427,6 +434,7 @@ pub fn init(cx: &mut AppContext) { cx.add_action(Editor::select_to_beginning); cx.add_action(Editor::select_to_end); cx.add_action(Editor::select_all); + cx.add_action(Editor::select_all_matches); cx.add_action(Editor::select_line); cx.add_action(Editor::split_selection_into_lines); cx.add_action(Editor::add_selection_above); @@ -724,13 +732,22 @@ struct AddSelectionsState { stack: Vec, } -#[derive(Clone, Debug)] +#[derive(Clone)] struct SelectNextState { query: AhoCorasick, wordwise: bool, done: bool, } +impl std::fmt::Debug for SelectNextState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct(std::any::type_name::()) + .field("wordwise", &self.wordwise) + .field("done", &self.done) + .finish() + } +} + #[derive(Debug)] struct AutocloseRegion { selection_id: usize, @@ -5936,9 +5953,29 @@ impl Editor { } } - pub fn select_next(&mut self, action: &SelectNext, cx: &mut ViewContext) -> Result<()> { - self.push_to_selection_history(); - let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + pub fn select_next_match_internal( + &mut self, + display_map: &DisplaySnapshot, + replace_newest: bool, + autoscroll: Option, + cx: &mut ViewContext, + ) -> Result<()> { + fn select_next_match_ranges( + this: &mut Editor, + range: Range, + replace_newest: bool, + auto_scroll: Option, + cx: &mut ViewContext, + ) { + this.unfold_ranges([range.clone()], false, true, cx); + this.change_selections(auto_scroll, cx, |s| { + if replace_newest { + s.delete(s.newest_anchor().id); + } + s.insert_range(range.clone()); + }); + } + let buffer = &display_map.buffer_snapshot; let mut selections = self.selections.all::(cx); if let Some(mut select_next_state) = self.select_next_state.take() { @@ -5959,6 +5996,7 @@ impl Editor { .stream_find_iter(bytes_before_first_selection) .map(|result| (0, result)), ); + for (start_offset, query_match) in query_matches { let query_match = query_match.unwrap(); // can only fail due to I/O let offset_range = @@ -5970,19 +6008,25 @@ impl Editor { || (!movement::is_inside_word(&display_map, display_range.start) && !movement::is_inside_word(&display_map, display_range.end)) { - next_selected_range = Some(offset_range); - break; + if selections + .iter() + .find(|selection| selection.equals(&offset_range)) + .is_none() + { + next_selected_range = Some(offset_range); + break; + } } } if let Some(next_selected_range) = next_selected_range { - self.unfold_ranges([next_selected_range.clone()], false, true, cx); - self.change_selections(Some(Autoscroll::newest()), cx, |s| { - if action.replace_newest { - s.delete(s.newest_anchor().id); - } - s.insert_range(next_selected_range); - }); + select_next_match_ranges( + self, + next_selected_range, + replace_newest, + autoscroll, + cx, + ); } else { select_next_state.done = true; } @@ -6009,10 +6053,13 @@ impl Editor { wordwise: true, done: false, }; - self.unfold_ranges([selection.start..selection.end], false, true, cx); - self.change_selections(Some(Autoscroll::newest()), cx, |s| { - s.select(selections); - }); + select_next_match_ranges( + self, + selection.start..selection.end, + replace_newest, + autoscroll, + cx, + ); self.select_next_state = Some(select_state); } else { let query = buffer @@ -6023,12 +6070,48 @@ impl Editor { wordwise: false, done: false, }); - self.select_next(action, cx)?; + self.select_next_match_internal(display_map, replace_newest, autoscroll, cx)?; } } Ok(()) } + pub fn select_all_matches( + &mut self, + action: &SelectAllMatches, + cx: &mut ViewContext, + ) -> Result<()> { + self.push_to_selection_history(); + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + + loop { + self.select_next_match_internal(&display_map, action.replace_newest, None, cx)?; + + if self + .select_next_state + .as_ref() + .map(|selection_state| selection_state.done) + .unwrap_or(true) + { + break; + } + } + + Ok(()) + } + + pub fn select_next(&mut self, action: &SelectNext, cx: &mut ViewContext) -> Result<()> { + self.push_to_selection_history(); + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + self.select_next_match_internal( + &display_map, + action.replace_newest, + Some(Autoscroll::newest()), + cx, + )?; + Ok(()) + } + pub fn select_previous( &mut self, action: &SelectPrevious, diff --git a/crates/text/src/selection.rs b/crates/text/src/selection.rs index d4f55a043b..205c27239d 100644 --- a/crates/text/src/selection.rs +++ b/crates/text/src/selection.rs @@ -100,6 +100,10 @@ impl Selection { reversed: false, } } + + pub fn equals(&self, offset_range: &Range) -> bool { + self.start == offset_range.start && self.end == offset_range.end + } } impl Selection {