feat: add literal buffer search and match navigation

This commit is contained in:
Valdemar Lorenzen
2026-10-06 12:02:14 +02:00
parent 714f1ff4de
commit 3ad6ca1fe2
7 changed files with 362 additions and 19 deletions
+73 -3
View File
@@ -228,6 +228,20 @@ fn draw(frame: &mut Frame, editor: &Editor, top: &mut usize, left: &mut usize) {
Paragraph::new(lines),
Rect::new(area.x, area.y, area.width, area.height - 2),
);
if let Some(found) = editor.search_match()
&& found.start.row >= *top
&& found.start.row < top.saturating_add(height)
{
let line = &editor.lines()[found.start.row];
let start = display_column(line, found.start.column).max(*left);
let end = display_column(line, found.end_column).min(left.saturating_add(width));
let y = area.y + (found.start.row - *top) as u16;
let style = Style::default().fg(Color::Black).bg(Color::Yellow);
let buffer = frame.buffer_mut();
for column in start..end {
buffer[(area.x + (column - *left) as u16, y)].set_style(style);
}
}
let path = editor
.path()
.map(|p| p.to_string_lossy().into_owned())
@@ -249,9 +263,14 @@ fn draw(frame: &mut Frame, editor: &Editor, top: &mut usize, left: &mut usize) {
Rect::new(area.x, area.y + area.height - 2, area.width, 1),
);
let bottom = area.y + area.height - 1;
if editor.mode() == Mode::Command {
let command = format!(":{}", editor.command_line());
let command_width = UnicodeWidthStr::width(command.as_str());
if matches!(editor.mode(), Mode::Command | Mode::Search) {
let prefix = if editor.mode() == Mode::Search {
'/'
} else {
':'
};
let command = format!("{prefix}{}", editor.command_line());
let command_width = display_column(&command, usize::MAX);
let offset = command_width.saturating_sub(width - 1);
frame.render_widget(
Paragraph::new(visible_line(&command, offset, width)),
@@ -282,6 +301,57 @@ mod tests {
assert_eq!(visible_line(text, 5, 2), " e\u{301}");
}
#[test]
fn active_search_highlight_uses_display_cells_and_clips_with_horizontal_scroll() {
let mut editor = Editor::open(None, vec![]).unwrap();
editor
.apply(vec![crate::editor::Effect::Replace(
"a\t界e\u{301}zz".into(),
)])
.unwrap();
editor.handle(Input::Char('/'));
for ch in "\t界e\u{301}".chars() {
editor.handle(Input::Char(ch));
}
editor.handle(Input::Enter);
editor.handle(Input::End);
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(5, 5)).unwrap();
let (mut top, mut left) = (0, 0);
terminal
.draw(|frame| draw(frame, &editor, &mut top, &mut left))
.unwrap();
let buffer = terminal.backend().buffer();
assert_eq!(left, 4);
assert_eq!(buffer[(0, 0)].symbol(), "界");
assert_eq!(buffer[(2, 0)].symbol(), "e\u{301}");
for x in [0, 2] {
assert_eq!(buffer[(x, 0)].bg, Color::Yellow);
}
for x in 3..5 {
assert_ne!(buffer[(x, 0)].bg, Color::Yellow);
}
editor.handle(Input::Char('/'));
for ch in "界界".chars() {
editor.handle(Input::Char(ch));
}
terminal
.draw(|frame| draw(frame, &editor, &mut top, &mut left))
.unwrap();
let buffer = terminal.backend().buffer();
assert_eq!(buffer[(0, 4)].symbol(), "界");
assert_eq!(buffer[(2, 4)].symbol(), "界");
editor.handle(Input::Backspace);
editor.handle(Input::Backspace);
editor.handle(Input::Char('\t'));
terminal
.draw(|frame| draw(frame, &editor, &mut top, &mut left))
.unwrap();
assert_eq!(
terminal.get_cursor_position().unwrap(),
ratatui::layout::Position::new(4, 4)
);
}
#[cfg(unix)]
#[test]
fn terminal_session_scopes_the_callers_panic_hook() {