fix: render theme-aware software cursors and mode banners

This commit is contained in:
2026-10-26 15:52:30 +01:00
parent c6901eaaa6
commit 496c93774f
7 changed files with 407 additions and 212 deletions
+217 -102
View File
@@ -323,7 +323,11 @@ fn historical_buffer_markers_retain_real_final_newlines_and_scope() {
for (x, y, symbol) in [(3, 0, "↵"), (0, 1, "·"), (1, 1, "·"), (7, 1, "↵")] {
let cell = &buffer[(area.x + x, area.y + y)];
assert_eq!(cell.symbol(), symbol);
assert!(cell.modifier.contains(Modifier::DIM));
assert_eq!(
cell.modifier.contains(Modifier::DIM),
views.panels.cursor != Some(Position::new(area.x + x, area.y + y)),
"only the primary cursor removes marker dim styling",
);
}
assert_eq!(buffer[(area.x + 6, area.y + 1)].symbol(), " ");
assert_eq!(buffer[(area.x + 6, area.y + 1)].bg, Color::Red);
@@ -847,6 +851,7 @@ fn whitespace_characters_preserve_source_geometry_and_overlay_styles() {
session.handle(Input::End);
session.handle(Input::Char('v'));
session.handle(Input::Left);
session.handle(Input::Left);
terminal
.draw(|frame| views.draw(frame, session.editor_mut()))
.unwrap();
@@ -905,6 +910,7 @@ fn trailing_whitespace_is_opt_in_and_preserves_wrapping_and_selection() {
session.handle(Input::End);
session.handle(Input::Char('v'));
session.handle(Input::Left);
session.handle(Input::Left);
terminal
.draw(|frame| views.draw(frame, session.editor_mut()))
.unwrap();
@@ -1290,7 +1296,7 @@ fn resolver_renders_aligned_native_draft_alternative_and_separate_history() {
);
assert_eq!(buffer[(x, content.y + 1)].symbol(), "v");
assert_eq!(
buffer[(x, content.y + 1)].bg,
buffer[(x + 1, content.y + 1)].bg,
views.panels.diff_colors.row[1]
);
}
@@ -1464,7 +1470,7 @@ fn resolver_renders_aligned_native_draft_alternative_and_separate_history() {
let searched_caret = render(&mut terminal, &mut views, &mut session);
assert!(screen_text(&terminal).contains("context 30"));
assert_eq!(
terminal.backend().buffer()[(searched_caret.x, searched_caret.y)].bg,
terminal.backend().buffer()[(searched_caret.x + 1, searched_caret.y)].bg,
Color::Yellow,
"source {:?} ({:?}), terminal {searched_caret:?}, caret {:?}, viewport {:?}, focus {:?}",
session.editor().cursor(),
@@ -5383,9 +5389,10 @@ fn floating_fields_use_border_titles_and_inner_horizontal_padding() {
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(80, 12)).unwrap();
let workspace = Rect::new(3, 2, 74, 8);
let mut panel = Rect::default();
let mut cursor = Position::default();
terminal
.draw(|frame| {
panel = draw_floating_input(frame, &editor, workspace).unwrap();
(panel, cursor) = draw_floating_input(frame, &editor, workspace).unwrap();
})
.unwrap();
assert_eq!(panel.x, workspace.x);
@@ -5397,10 +5404,7 @@ fn floating_fields_use_border_titles_and_inner_horizontal_padding() {
assert_eq!(buffer[(panel.x + 1, panel.y + 1)].symbol(), " ");
assert_eq!(buffer[(panel.x + 2, panel.y + 1)].symbol(), "n");
assert_eq!(buffer[(panel.right() - 2, panel.y + 1)].symbol(), " ");
assert_eq!(
terminal.get_cursor_position().unwrap(),
Position::new(panel.x + 8, panel.y + 1)
);
assert_eq!(cursor, Position::new(panel.x + 8, panel.y + 1));
assert_eq!(
pane_screen(
&terminal,
@@ -8558,7 +8562,7 @@ fn native_visual_selection_overrides_selected_review_range_background() {
let mut views = BufferViewports::default();
let source = render_native_diff(&mut terminal, &mut views, &mut editor);
assert_eq!(
terminal.backend().buffer()[(source.x, source.y)].bg,
terminal.backend().buffer()[(source.x + 1, source.y)].bg,
Color::Magenta
);
editor.handle(Input::Char('v'));
@@ -9064,10 +9068,6 @@ fn wrapped_diff_aligns_continuations_and_preserves_native_selection_and_caret()
terminal.backend().buffer()[(caret.x, caret.y)].symbol(),
"D"
);
assert_eq!(
terminal.backend().buffer()[(caret.x, caret.y)].fg,
Color::DarkGray
);
editor.handle(Input::Char('v'));
editor.handle(Input::Home);
render_native_diff(&mut terminal, &mut views, &mut editor);
@@ -9123,10 +9123,6 @@ fn readonly_diff_panes_render_native_caret_visual_search_and_paired_focus() {
terminal.backend().buffer()[(caret.x, caret.y)].symbol(),
"f"
);
assert_eq!(
terminal.backend().buffer()[(caret.x, caret.y)].fg,
Color::Magenta
);
editor.handle(Input::Char('v'));
editor.handle(Input::Right);
render_native_diff(&mut terminal, &mut views, &mut editor);
@@ -9140,7 +9136,7 @@ fn readonly_diff_panes_render_native_caret_visual_search_and_paired_focus() {
editor.handle(Input::Char('V'));
render_native_diff(&mut terminal, &mut views, &mut editor);
assert_eq!(
terminal.backend().buffer()[(caret.x, caret.y + 1)].bg,
terminal.backend().buffer()[(caret.x + 1, caret.y + 1)].bg,
Color::Blue
);
assert_eq!(
@@ -9157,11 +9153,11 @@ fn readonly_diff_panes_render_native_caret_visual_search_and_paired_focus() {
let searched = render_native_diff(&mut terminal, &mut views, &mut editor);
assert!(before.contains(searched));
assert_eq!(
terminal.backend().buffer()[(searched.x, searched.y)].bg,
terminal.backend().buffer()[(searched.x + 1, searched.y)].bg,
Color::Yellow
);
assert_eq!(
terminal.backend().buffer()[(searched.x, searched.y)].fg,
terminal.backend().buffer()[(searched.x + 1, searched.y)].fg,
Color::Black
);
let before_row = editor
@@ -9926,7 +9922,7 @@ fn snippet_highlight_tracks_argument_graphemes_without_filling_the_row() {
.unwrap();
let buffer = terminal.backend().buffer();
// A wide glyph's leading cell owns its terminal style.
for x in (0..28).filter(|x| *x != 8) {
for x in (0..28).filter(|x| *x != 8 && *x != caret.x) {
assert_eq!(
buffer[(x, 0)].bg == Color::Blue,
selected.contains(&x),
@@ -9935,6 +9931,11 @@ fn snippet_highlight_tracks_argument_graphemes_without_filling_the_row() {
screen_text(&terminal),
);
}
assert_eq!(
buffer[caret].bg,
Color::Green,
"cursor overrides the snippet selection"
);
assert_eq!(terminal.get_cursor_position().unwrap(), caret);
}
editor.handle(Input::Escape);
@@ -10504,7 +10505,7 @@ end
"T"
);
assert_eq!(
terminal.backend().buffer()[(cursor.x, cursor.y)].fg,
terminal.backend().buffer()[(cursor.x - 1, cursor.y)].fg,
Color::DarkGray
);
assert_eq!(fs::read_to_string(target).unwrap(), target_text);
@@ -10522,7 +10523,7 @@ fn syntax_tokens_refresh_after_native_edits_and_undo_without_changing_text_on_dr
terminal
.draw(|frame| viewports.draw(frame, &mut editor))
.unwrap();
assert_eq!(terminal.backend().buffer()[(2, 0)].fg, Color::Yellow);
assert_eq!(terminal.backend().buffer()[(3, 0)].fg, Color::Yellow);
let revision = editor.text_revision();
editor.handle(Input::Right);
terminal
@@ -10571,7 +10572,7 @@ fn syntax_tokens_refresh_after_native_edits_and_undo_without_changing_text_on_dr
("42", Color::Yellow),
("\"ok\"", Color::Green),
] {
let x = 2 + source.find(token).unwrap() as u16;
let x = 2 + (source.find(token).unwrap() + token.len() - 1) as u16;
assert_eq!(buffer[(x, 0)].fg, color, "{token}");
assert_eq!(buffer[(x, 0)].bg, Color::Reset);
}
@@ -10611,7 +10612,7 @@ fn syntax_continuations_above_viewport_refresh_when_the_opening_line_changes() {
"v"
);
assert_eq!(
terminal.backend().buffer()[(caret.x, caret.y)].fg,
terminal.backend().buffer()[(caret.x + 1, caret.y)].fg,
color,
"{name}"
);
@@ -10626,7 +10627,7 @@ fn syntax_continuations_above_viewport_refresh_when_the_opening_line_changes() {
"v"
);
assert_eq!(
terminal.backend().buffer()[(caret.x, caret.y)].fg,
terminal.backend().buffer()[(caret.x + 1, caret.y)].fg,
Color::Reset,
"{name}"
);
@@ -10635,7 +10636,7 @@ fn syntax_continuations_above_viewport_refresh_when_the_opening_line_changes() {
.draw(|frame| viewports.draw(frame, &mut editor))
.unwrap();
assert_eq!(
terminal.backend().buffer()[(caret.x, caret.y)].fg,
terminal.backend().buffer()[(caret.x + 1, caret.y)].fg,
color,
"{name}"
);
@@ -10895,7 +10896,7 @@ fn search_visual_selection_and_secondary_carets_override_semantic_foreground() {
.unwrap();
let found = editor.search_match().unwrap();
let y = found.start.row as u16;
for x in 4..8 {
for x in 5..8 {
assert_eq!(terminal.backend().buffer()[(x, y)].fg, Color::Black);
assert_eq!(terminal.backend().buffer()[(x, y)].bg, Color::Yellow);
assert!(
@@ -10956,7 +10957,7 @@ fn syntax_redetects_saved_paths_without_text_changes_and_plain_text_keeps_defaul
terminal
.draw(|frame| viewports.draw(frame, &mut editor))
.unwrap();
assert_eq!(terminal.backend().buffer()[(2, 0)].fg, Color::Magenta);
assert_eq!(terminal.backend().buffer()[(3, 0)].fg, Color::Magenta);
let revision = editor.text_revision();
for name in ["source.vampunknown", "source.txt", "again.rs"] {
editor.execute(&format!("w {}", directory.0.join(name).display()));
@@ -10965,13 +10966,13 @@ fn syntax_redetects_saved_paths_without_text_changes_and_plain_text_keeps_defaul
.draw(|frame| viewports.draw(frame, &mut editor))
.unwrap();
if name.ends_with(".rs") {
assert_eq!(terminal.backend().buffer()[(2, 0)].fg, Color::Magenta);
assert_eq!(terminal.backend().buffer()[(3, 0)].fg, Color::Magenta);
assert_eq!(
viewports.syntax[&editor.buffer_id()].language(),
Some("Rust")
);
} else {
assert_eq!(terminal.backend().buffer()[(2, 0)].fg, Color::Reset);
assert_eq!(terminal.backend().buffer()[(3, 0)].fg, Color::Reset);
assert!(viewports.syntax[&editor.buffer_id()].line(0).is_empty());
assert!(viewports.syntax[&editor.buffer_id()].language().is_none());
}
@@ -10990,7 +10991,7 @@ fn syntax_caches_stay_independent_across_buffer_edits_and_retire_closed_ids() {
terminal
.draw(|frame| viewports.draw(frame, &mut editor))
.unwrap();
assert_eq!(terminal.backend().buffer()[(2, 0)].fg, Color::Yellow);
assert_eq!(terminal.backend().buffer()[(3, 0)].fg, Color::Yellow);
editor.execute(&format!(
"edit {}",
directory.file("b.py", "# note").display()
@@ -10999,19 +11000,19 @@ fn syntax_caches_stay_independent_across_buffer_edits_and_retire_closed_ids() {
terminal
.draw(|frame| viewports.draw(frame, &mut editor))
.unwrap();
assert_eq!(terminal.backend().buffer()[(2, 0)].fg, Color::DarkGray);
assert_eq!(terminal.backend().buffer()[(3, 0)].fg, Color::DarkGray);
editor
.apply(vec![crate::editor::Effect::Replace("\"ok\"".into())])
.unwrap();
terminal
.draw(|frame| viewports.draw(frame, &mut editor))
.unwrap();
assert_eq!(terminal.backend().buffer()[(2, 0)].fg, Color::Green);
assert_eq!(terminal.backend().buffer()[(3, 0)].fg, Color::Green);
editor.execute(&format!("buffer {a_id}"));
terminal
.draw(|frame| viewports.draw(frame, &mut editor))
.unwrap();
assert_eq!(terminal.backend().buffer()[(2, 0)].fg, Color::Yellow);
assert_eq!(terminal.backend().buffer()[(3, 0)].fg, Color::Yellow);
assert_eq!(viewports.syntax[&a_id].language(), Some("Rust"));
assert_eq!(viewports.syntax[&b_id].language(), Some("Python"));
editor.execute(&format!("buffer {b_id}"));
@@ -11019,7 +11020,7 @@ fn syntax_caches_stay_independent_across_buffer_edits_and_retire_closed_ids() {
terminal
.draw(|frame| viewports.draw(frame, &mut editor))
.unwrap();
assert_eq!(terminal.backend().buffer()[(2, 0)].fg, Color::Green);
assert_eq!(terminal.backend().buffer()[(3, 0)].fg, Color::Green);
assert!(!viewports.syntax.contains_key(&a_id));
assert!(!viewports.offsets.contains_key(&a_id));
assert_eq!(viewports.syntax.len(), 1);
@@ -15217,7 +15218,7 @@ fn secondary_carets_render_on_empty_lines_and_at_line_end() {
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(48, 5)).unwrap();
let (mut top, mut left) = (0, 0);
let mut syntax = SyntaxCache::new();
for (enabled, expected) in [(true, Color::Rgb(135, 215, 135)), (false, Color::Magenta)] {
for (enabled, expected) in [(true, Color::Green), (false, Color::Magenta)] {
editor.cursor_config.mode_colors = enabled;
terminal
.draw(|frame| {
@@ -15597,84 +15598,118 @@ fn active_search_highlight_uses_display_cells_and_clips_with_horizontal_scroll()
}
#[test]
fn cursor_appearance_tracks_editing_and_prompt_modes_without_replacing_text() {
use crate::cursor::CursorShape;
fn software_cursor_tracks_mode_palette_and_preserves_document_text() {
let mut editor = Editor::open(None, vec![]).unwrap();
editor
.apply(vec![crate::editor::Effect::Replace("hello\n".into())])
.unwrap();
editor.cursor_config.insert = CursorShape::Bar;
editor.cursor_config.visual = CursorShape::Underline;
let mut terminal = Terminal::with_options(
CrosstermBackend::new(Vec::new()),
ratatui::TerminalOptions {
viewport: ratatui::Viewport::Fixed(Rect::new(0, 0, 60, 12)),
},
)
let mut terminal = Terminal::new(SoftwareCursorBackend(ratatui::backend::TestBackend::new(
60, 12,
)))
.unwrap();
let mut views = BufferViewports::default();
for (inputs, mode, shape, color) in [
(vec![], Mode::Normal, "\x1b[2 q", "#5fd7ff"),
(vec![Input::Char('i')], Mode::Insert, "\x1b[6 q", "#87d787"),
for (inputs, mode, expected) in [
(vec![], Mode::Normal, Color::Cyan),
(vec![Input::Char('i')], Mode::Insert, Color::Green),
(
vec![Input::Escape, Input::Char('v')],
Mode::Visual,
"\x1b[4 q",
"#d787ff",
Color::Magenta,
),
(
vec![Input::Escape, Input::Char(':')],
Mode::Command,
"\x1b[2 q",
"#ffd75f",
Color::Yellow,
),
(
vec![Input::Escape, Input::Char('/')],
Mode::Search,
"\x1b[2 q",
"#ffaf5f",
Color::Blue,
),
(vec![Input::Escape], Mode::Normal, "\x1b[2 q", "#5fd7ff"),
(vec![Input::Escape], Mode::Normal, Color::Cyan),
] {
for input in inputs {
editor.handle(input);
}
assert_eq!(editor.mode(), mode);
let appearance = CursorAppearance::for_editor(&editor);
let mut output = Vec::new();
draw_frame(
&mut terminal,
&mut output,
false,
Some(appearance),
|frame| views.draw(frame, &mut editor),
)
.unwrap();
let output = String::from_utf8(output).unwrap();
assert!(output.contains(shape), "{mode}: {output:?}");
assert!(
output.contains(&format!("\x1b]12;{color}\x1b\\")),
"{mode}: {output:?}"
terminal
.draw(|frame| views.draw(frame, &mut editor))
.unwrap();
let position = if matches!(mode, Mode::Command | Mode::Search) {
let panel = views.panels.input.unwrap();
Position::new(panel.x + 2, panel.y + 1)
} else {
views.panels.caret.unwrap().1
};
assert_eq!(
terminal.backend().0.buffer()[position].bg,
expected,
"{mode}"
);
assert!(
!terminal.backend().0.cursor_visible(),
"hardware cursor must stay hidden"
);
assert!(output.find(shape).unwrap() < output.find("\x1b[?25h").unwrap());
assert_eq!(editor.lines(), &["hello", ""]);
}
editor.cursor_config.mode_colors = false;
editor.handle(Input::Char('i'));
let mut output = Vec::new();
draw_frame(
&mut terminal,
&mut output,
false,
Some(CursorAppearance::for_editor(&editor)),
|frame| views.draw(frame, &mut editor),
)
}
#[test]
fn software_cursor_shapes_preserve_wide_text_and_restore_cells_after_motion() {
use crate::cursor::CursorShape;
let mut editor = Editor::open(None, vec![]).unwrap();
editor
.apply(vec![crate::editor::Effect::Replace("界e\u{301}\n".into())])
.unwrap();
let mut terminal = Terminal::new(SoftwareCursorBackend(ratatui::backend::TestBackend::new(
40, 8,
)))
.unwrap();
let output = String::from_utf8(output).unwrap();
assert!(output.contains("\x1b[6 q"));
assert!(output.contains("\x1b]112\x1b\\"));
assert!(!output.contains("\x1b]12;"));
let mut views = BufferViewports::default();
terminal
.draw(|frame| views.draw(frame, &mut editor))
.unwrap();
let position = views.panels.cursor.unwrap();
let buffer = terminal.backend().0.buffer();
assert_eq!(buffer[position].symbol(), "界");
assert_eq!(buffer[position].bg, Color::Cyan);
editor.cursor_config.normal = CursorShape::Bar;
terminal
.draw(|frame| views.draw(frame, &mut editor))
.unwrap();
assert_eq!(terminal.backend().0.buffer()[position].symbol(), "▏");
assert_eq!(terminal.backend().0.buffer()[position].fg, Color::Cyan);
editor.handle(Input::Right);
terminal
.draw(|frame| views.draw(frame, &mut editor))
.unwrap();
assert_eq!(terminal.backend().0.buffer()[position].symbol(), "界");
let next = views.panels.cursor.unwrap();
assert_eq!(next.x, position.x + 2);
assert_eq!(terminal.backend().0.buffer()[next].symbol(), "▏");
editor.cursor_config.normal = CursorShape::Underline;
terminal
.draw(|frame| views.draw(frame, &mut editor))
.unwrap();
let buffer = terminal.backend().0.buffer();
assert_eq!(buffer[next].symbol(), "e\u{301}");
assert!(buffer[next].modifier.contains(Modifier::UNDERLINED));
assert_eq!(buffer[next].underline_color, Color::Cyan);
assert_eq!(buffer[next].fg, Color::Cyan);
editor.cursor_config.normal = CursorShape::Block;
editor.cursor_config.mode_colors = false;
terminal
.draw(|frame| views.draw(frame, &mut editor))
.unwrap();
let buffer = terminal.backend().0.buffer();
assert_eq!(buffer[next].symbol(), "e\u{301}");
assert!(buffer[next].modifier.contains(Modifier::REVERSED));
assert_eq!(buffer[next].bg, Color::Reset);
assert_eq!(editor.lines(), &["界e\u{301}", ""]);
assert!(!terminal.backend().0.cursor_visible());
}
#[test]
@@ -15708,14 +15743,14 @@ fn terminal_can_present_previous_frame_while_next_frame_is_computed() {
synchronized: Rc::clone(&synchronized),
};
let mut terminal = Terminal::with_options(
CrosstermBackend::new(Vec::new()),
SoftwareCursorBackend(CrosstermBackend::new(Vec::new())),
ratatui::TerminalOptions {
viewport: ratatui::Viewport::Fixed(Rect::new(0, 0, 20, 5)),
},
)
.unwrap();
for text in ["first frame", "second frame", "third frame"] {
draw_frame(&mut terminal, &mut output, false, None, |frame| {
draw_frame(&mut terminal, &mut output, false, |frame| {
assert!(
!synchronized.get(),
"terminal presentation suspended during frame computation"
@@ -15737,7 +15772,7 @@ fn scrolling_never_paints_with_an_exposed_cursor_even_without_synchronized_updat
.unwrap();
let mut output = Vec::new();
let mut terminal = Terminal::with_options(
CrosstermBackend::new(Vec::new()),
SoftwareCursorBackend(CrosstermBackend::new(Vec::new())),
ratatui::TerminalOptions {
viewport: ratatui::Viewport::Fixed(Rect::new(0, 0, 20, 5)),
},
@@ -15746,7 +15781,7 @@ fn scrolling_never_paints_with_an_exposed_cursor_even_without_synchronized_updat
let mut viewports = BufferViewports::default();
for key in std::iter::repeat_n('j', 20).chain(std::iter::repeat_n('k', 20)) {
editor.handle(Input::Char(key));
draw_frame(&mut terminal, &mut output, false, None, |frame| {
draw_frame(&mut terminal, &mut output, false, |frame| {
viewports.draw(frame, &mut editor)
})
.unwrap();
@@ -15788,7 +15823,10 @@ fn scrolling_never_paints_with_an_exposed_cursor_even_without_synchronized_updat
x += 1;
}
}
assert!(visible);
assert!(
!visible,
"hardware cursor must stay hidden behind the software cursor"
);
assert!(!synchronized);
for (row, expected) in [(0, "row 0"), (1, "row 1"), (2, "row 2")] {
let line = std::str::from_utf8(&screen[row]).unwrap().trim_end();
@@ -15821,12 +15859,12 @@ fn terminal_session_scopes_the_callers_panic_hook() {
"error" => Err(io::Error::from(io::ErrorKind::BrokenPipe)),
"panic" => {
let mut terminal = Terminal::with_options(
CrosstermBackend::new(Vec::new()),
SoftwareCursorBackend(CrosstermBackend::new(Vec::new())),
ratatui::TerminalOptions {
viewport: ratatui::Viewport::Fixed(Rect::new(0, 0, 20, 5)),
},
)?;
draw_frame(&mut terminal, &mut io::stdout(), false, None, |_| {
draw_frame(&mut terminal, &mut io::stdout(), false, |_| {
panic::panic_any(73_u64)
})
}
@@ -15953,13 +15991,6 @@ fn terminal_session_scopes_the_callers_panic_hook() {
"{mode}: {before:?}"
);
}
for sequence in ["\x1b[0 q", "\x1b]112\x1b\\"] {
assert_eq!(
before.matches(sequence).count(),
entered,
"{mode}: {before:?}"
);
}
for protocol in [1000, 1002, 1003, 1015, 1006] {
assert_eq!(
before.matches(&format!("\x1b[?{protocol}h")).count(),
@@ -16046,6 +16077,8 @@ end
}
}
// Document-layer checks exclude the final primary-cursor overlay. Complete-frame
// cursor and banner checks render through BufferViewports instead.
#[cfg(test)]
fn draw(
frame: &mut Frame,
@@ -16257,3 +16290,85 @@ fn shared_shell_status_is_a_separate_segment_left_of_the_battery() {
assert!(!top.contains("sle..."));
assert!(top.trim_end().ends_with("bat 27%"), "{top}");
}
#[test]
fn both_banners_follow_mode_with_neutral_gaps_even_when_cursor_colors_are_disabled() {
let mut editor = Editor::open(None, vec![]).unwrap();
editor.execute("tab.new");
editor.cursor_config.mode_colors = false;
configure_chrome(&mut editor, |top, bottom| {
top.left = vec!["tabs".into()];
top.center = vec!["path".into()];
top.right = vec!["badge".into()];
for name in ["tabs", "badge", "path"] {
top.segments.insert(
name.into(),
SegmentConfig {
title: match name {
"tabs" => "{index}".into(),
"path" => "N".into(),
_ => String::new(),
},
padding: usize::from(name != "path"),
margin: [1; 2],
leading_edge: if name == "path" { "" } else { "<" }.into(),
trailing_edge: if name == "path" { "" } else { ">" }.into(),
first_leading_edge: None,
},
);
}
*bottom = top.clone();
});
editor.set_label("badge".into(), "B".into()).unwrap();
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(60, 6)).unwrap();
let mut syntax = SyntaxCache::new();
for (input, mode, accent, foreground) in [
(None, Mode::Normal, Color::Cyan, Color::Black),
(Some('i'), Mode::Insert, Color::Green, Color::Black),
(Some('v'), Mode::Visual, Color::Magenta, Color::Black),
(Some(':'), Mode::Command, Color::Yellow, Color::Black),
(Some('/'), Mode::Search, Color::Blue, Color::White),
(None, Mode::Normal, Color::Cyan, Color::Black),
] {
editor.handle(Input::Escape);
if let Some(ch) = input {
editor.handle(Input::Char(ch));
}
assert_eq!(editor.mode(), mode);
terminal
.draw(|frame| {
let area = frame.area();
assert_eq!(
draw_bars(frame, &editor, area, &mut syntax),
Rect::new(0, 1, 60, 4)
);
})
.unwrap();
let buffer = terminal.backend().buffer();
for y in [0, 5] {
for (x, symbol) in [(9, "2"), (56, "B")] {
assert_eq!(buffer[(x, y)].symbol(), symbol, "{mode:?}");
}
for x in [8, 9, 10, 55, 56, 57] {
assert_eq!(buffer[(x, y)].bg, accent, "{mode:?} at {x},{y}");
assert_eq!(buffer[(x, y)].fg, foreground, "{mode:?} at {x},{y}");
}
for (x, symbol) in [(7, "<"), (11, ">"), (54, "<"), (58, ">")] {
assert_eq!(buffer[(x, y)].symbol(), symbol);
assert_eq!(buffer[(x, y)].fg, accent, "{mode:?} at {x},{y}");
assert_eq!(buffer[(x, y)].bg, Color::Reset);
}
assert_eq!(buffer[(2, y)].symbol(), "1");
assert_eq!(buffer[(2, y)].fg, Color::Gray);
assert_eq!(buffer[(2, y)].bg, Color::Reset);
assert_eq!(buffer[(29, y)].symbol(), "N");
assert_eq!(buffer[(29, y)].fg, Color::Reset);
assert_eq!(buffer[(29, y)].bg, Color::Reset);
for x in [0, 4, 5, 6, 12, 13, 28, 30, 52, 53, 59] {
assert_eq!(buffer[(x, y)].symbol(), " ");
assert_eq!(buffer[(x, y)].fg, Color::Reset, "{mode:?} at {x},{y}");
assert_eq!(buffer[(x, y)].bg, Color::Reset, "{mode:?} at {x},{y}");
}
}
}
}