Move the cursor on search in Terminal if ViMode is active (#33305)

Aleksei Gusev created

Currently, the terminal search function doesn't work well with ViMode.
It matches the search terms, scrolls the active match in the view, but
it doesn't move the cursor to the match, which makes it useless for
navigating the scrollback in vimode.

With this improvement, if a user activates ViMode before the search Zed
moves the cursor to the active search terms. So, when the search dialog
is dismissed the cursor is places on the latest active search term and
it's possible to navigate the scrollback via ViMode using this place as
the starting point.


https://github.com/user-attachments/assets/63325405-ed93-4bf8-a00f-28ded5511f31

Release Notes:

- Improved the search function in the terminal when ViMode is activated

Change summary

crates/terminal/src/terminal.rs           | 20 +++++++++++++++++---
crates/terminal_view/src/terminal_view.rs |  2 +-
2 files changed, 18 insertions(+), 4 deletions(-)

Detailed changes

crates/terminal/src/terminal.rs 🔗

@@ -167,6 +167,7 @@ enum InternalEvent {
     // Vi mode events
     ToggleViMode,
     ViMotion(ViMotion),
+    MoveViCursorToAlacPoint(AlacPoint),
 }
 
 ///A translation struct for Alacritty to communicate with us from their event loop
@@ -972,6 +973,10 @@ impl Terminal {
                 term.scroll_to_point(*point);
                 self.refresh_hovered_word(window);
             }
+            InternalEvent::MoveViCursorToAlacPoint(point) => {
+                term.vi_goto_point(*point);
+                self.refresh_hovered_word(window);
+            }
             InternalEvent::ToggleViMode => {
                 self.vi_mode_enabled = !self.vi_mode_enabled;
                 term.toggle_vi_mode();
@@ -1100,12 +1105,21 @@ impl Terminal {
     pub fn activate_match(&mut self, index: usize) {
         if let Some(search_match) = self.matches.get(index).cloned() {
             self.set_selection(Some((make_selection(&search_match), *search_match.end())));
-
-            self.events
-                .push_back(InternalEvent::ScrollToAlacPoint(*search_match.start()));
+            if self.vi_mode_enabled {
+                self.events
+                    .push_back(InternalEvent::MoveViCursorToAlacPoint(*search_match.end()));
+            } else {
+                self.events
+                    .push_back(InternalEvent::ScrollToAlacPoint(*search_match.start()));
+            }
         }
     }
 
+    pub fn clear_matches(&mut self) {
+        self.matches.clear();
+        self.set_selection(None);
+    }
+
     pub fn select_matches(&mut self, matches: &[RangeInclusive<AlacPoint>]) {
         let matches_to_select = self
             .matches

crates/terminal_view/src/terminal_view.rs 🔗

@@ -1869,7 +1869,7 @@ impl SearchableItem for TerminalView {
 
     /// Clear stored matches
     fn clear_matches(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
-        self.terminal().update(cx, |term, _| term.matches.clear())
+        self.terminal().update(cx, |term, _| term.clear_matches())
     }
 
     /// Store matches returned from find_matches somewhere for rendering