diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index f16afdfe973c8..f845079d882e5 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1170,7 +1170,14 @@ fn split_selection_on_newline(cx: &mut Context) { doc.set_selection(view.id, selection); } -fn search_impl(doc: &mut Document, view: &mut View, contents: &str, regex: &Regex, extend: bool) { +fn search_impl( + doc: &mut Document, + view: &mut View, + contents: &str, + regex: &Regex, + extend: bool, + scrolloff: usize, +) { let text = doc.text().slice(..); let selection = doc.selection(view.id); @@ -1205,7 +1212,11 @@ fn search_impl(doc: &mut Document, view: &mut View, contents: &str, regex: &Rege }; doc.set_selection(view.id, selection); - align_view(doc, view, Align::Center); + if view.is_cursor_in_view(doc, 0) { + view.ensure_cursor_in_view(doc, scrolloff); + } else { + align_view(doc, view, Align::Center) + } }; } @@ -1221,6 +1232,8 @@ fn search_completions(cx: &mut Context, reg: Option) -> Vec { // TODO: use one function for search vs extend fn search(cx: &mut Context) { let reg = cx.register.unwrap_or('/'); + let scrolloff = cx.editor.config.scrolloff; + let (_, doc) = current!(cx.editor); // TODO: could probably share with select_on_matches? @@ -1245,7 +1258,7 @@ fn search(cx: &mut Context) { if event != PromptEvent::Update { return; } - search_impl(doc, view, &contents, ®ex, false); + search_impl(doc, view, &contents, ®ex, false, scrolloff); }, ); @@ -1253,6 +1266,7 @@ fn search(cx: &mut Context) { } fn search_next_impl(cx: &mut Context, extend: bool) { + let scrolloff = cx.editor.config.scrolloff; let (view, doc) = current!(cx.editor); let registers = &cx.editor.registers; if let Some(query) = registers.read('/') { @@ -1267,7 +1281,7 @@ fn search_next_impl(cx: &mut Context, extend: bool) { .case_insensitive(case_insensitive) .build() { - search_impl(doc, view, &contents, ®ex, extend); + search_impl(doc, view, &contents, ®ex, extend, scrolloff); } else { // get around warning `mutable_borrow_reservation_conflict` // which will be a hard error in the future diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index 11f301550a050..f23802616ac4d 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -121,6 +121,35 @@ impl View { } } + pub fn is_cursor_in_view(&mut self, doc: &Document, scrolloff: usize) -> bool { + let cursor = doc + .selection(self.id) + .primary() + .cursor(doc.text().slice(..)); + + let Position { col, row: line } = + visual_coords_at_pos(doc.text().slice(..), cursor, doc.tab_width()); + + let inner_area = self.inner_area(); + let last_line = (self.offset.row + inner_area.height as usize).saturating_sub(1); + + // - 1 so we have at least one gap in the middle. + // a height of 6 with padding of 3 on each side will keep shifting the view back and forth + // as we type + let scrolloff = scrolloff.min(inner_area.height.saturating_sub(1) as usize / 2); + + let last_col = self.offset.col + inner_area.width.saturating_sub(1) as usize; + + if (line > last_line.saturating_sub(scrolloff)) || (line < self.offset.row + scrolloff) { + return false; + } + + if (col > last_col.saturating_sub(scrolloff)) || (col < self.offset.col + scrolloff) { + return false; + } + true + } + /// Calculates the last visible line on screen #[inline] pub fn last_line(&self, doc: &Document) -> usize {