feat: configure cursor appearance by editing mode
This commit is contained in:
@@ -653,6 +653,69 @@ fn git_gutter_config_modes_default_and_rejection() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cursor_config_partial_shapes_and_invalid_options() {
|
||||
use crate::cursor::CursorShape::{Bar, Block, Underline};
|
||||
|
||||
let fixture = Fixture::new();
|
||||
for (options, mode_colors, shapes) in [
|
||||
(
|
||||
r#"{ insert: :bar, "visual" => "underline" }"#,
|
||||
true,
|
||||
[Block, Bar, Block, Block, Underline],
|
||||
),
|
||||
(
|
||||
r#"{ mode_colors: false, normal: "bar", insert: :underline, command: :bar, search: "underline", visual: :block }"#,
|
||||
false,
|
||||
[Bar, Underline, Bar, Underline, Block],
|
||||
),
|
||||
(
|
||||
r#"{ "mode_colors" => true, normal: :underline, insert: "block", command: "underline", search: :bar, visual: "bar" }"#,
|
||||
true,
|
||||
[Underline, Block, Underline, Bar, Bar],
|
||||
),
|
||||
] {
|
||||
let path = fixture.file(
|
||||
"config.rb",
|
||||
&format!("Vamp.configure {{ |config| config.cursor = {options} }}"),
|
||||
);
|
||||
let cursor = load_config(&path).unwrap().cursor;
|
||||
assert_eq!(cursor.mode_colors, mode_colors);
|
||||
assert_eq!(
|
||||
[
|
||||
cursor.normal,
|
||||
cursor.insert,
|
||||
cursor.command,
|
||||
cursor.search,
|
||||
cursor.visual
|
||||
],
|
||||
shapes
|
||||
);
|
||||
}
|
||||
for options in [
|
||||
"nil",
|
||||
"false",
|
||||
"[]",
|
||||
"{ mode_colors: nil }",
|
||||
"{ mode_colors: 1 }",
|
||||
r#"{ mode_colors: "true" }"#,
|
||||
"{ insert: nil }",
|
||||
"{ insert: false }",
|
||||
"{ insert: 1 }",
|
||||
"{ insert: :beam }",
|
||||
r#"{ normal: "" }"#,
|
||||
"{ unknown: :block }",
|
||||
"{ 1 => :block }",
|
||||
r#"{ insert: :bar, "insert" => :block }"#,
|
||||
] {
|
||||
let path = fixture.file(
|
||||
"config.rb",
|
||||
&format!("Vamp.configure {{ |config| config.cursor = {options} }}"),
|
||||
);
|
||||
assert!(load_config(&path).is_err(), "{options}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn git_blame_boolean_config_typed_toggle_palette_and_wire_validation() {
|
||||
let fixture = Fixture::new();
|
||||
|
||||
+116
-22
@@ -10908,6 +10908,7 @@ fn search_visual_selection_and_secondary_carets_override_semantic_foreground() {
|
||||
editor.handle(Input::Char('e'));
|
||||
editor.handle(Input::Ctrl('a'));
|
||||
assert_eq!(editor.selection_count(), 3);
|
||||
editor.cursor_config.mode_colors = false;
|
||||
terminal
|
||||
.draw(|frame| viewports.draw(frame, &mut editor))
|
||||
.unwrap();
|
||||
@@ -14348,6 +14349,7 @@ fn status_identifies_active_buffer_and_restored_visual_selection_count() {
|
||||
let b = directory.file("b", "other");
|
||||
let mut editor = Editor::open(Some(a), vec![]).unwrap();
|
||||
editor.set_gutter_style(crate::gutter::GutterStyle::None);
|
||||
editor.cursor_config.mode_colors = false;
|
||||
let a_id = editor.buffer_id();
|
||||
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(240, 6)).unwrap();
|
||||
let mut viewports = BufferViewports::default();
|
||||
@@ -15160,6 +15162,7 @@ fn tabs_and_wide_graphemes_scroll_in_terminal_cells() {
|
||||
fn selection_set_renders_all_ranges_and_counts_offscreen_targets() {
|
||||
let mut editor = Editor::open(None, vec![]).unwrap();
|
||||
editor.set_gutter_style(crate::gutter::GutterStyle::None);
|
||||
editor.cursor_config.mode_colors = false;
|
||||
editor
|
||||
.apply(vec![crate::editor::Effect::Replace(
|
||||
"界\n界\n界\n界\n界".into(),
|
||||
@@ -15214,25 +15217,28 @@ 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();
|
||||
terminal
|
||||
.draw(|frame| {
|
||||
draw(
|
||||
frame,
|
||||
&editor,
|
||||
&mut top,
|
||||
&mut left,
|
||||
&mut syntax,
|
||||
&mut DocumentViewport::default(),
|
||||
&mut WheelPanels::default(),
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
let buffer = terminal.backend().buffer();
|
||||
for caret in editor.carets() {
|
||||
let x = 2 + display_column(&editor.lines()[caret.row], caret.column) as u16;
|
||||
assert_eq!(buffer[(x, caret.row as u16)].symbol(), " ");
|
||||
if caret != editor.cursor() {
|
||||
assert_eq!(buffer[(x, caret.row as u16)].bg, Color::Magenta);
|
||||
for (enabled, expected) in [(true, Color::Rgb(135, 215, 135)), (false, Color::Magenta)] {
|
||||
editor.cursor_config.mode_colors = enabled;
|
||||
terminal
|
||||
.draw(|frame| {
|
||||
draw(
|
||||
frame,
|
||||
&editor,
|
||||
&mut top,
|
||||
&mut left,
|
||||
&mut syntax,
|
||||
&mut DocumentViewport::default(),
|
||||
&mut WheelPanels::default(),
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
let buffer = terminal.backend().buffer();
|
||||
for caret in editor.carets() {
|
||||
let x = 2 + display_column(&editor.lines()[caret.row], caret.column) as u16;
|
||||
assert_eq!(buffer[(x, caret.row as u16)].symbol(), " ");
|
||||
if caret != editor.cursor() {
|
||||
assert_eq!(buffer[(x, caret.row as u16)].bg, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(
|
||||
@@ -15590,6 +15596,87 @@ fn active_search_highlight_uses_display_cells_and_clips_with_horizontal_scroll()
|
||||
assert!(content.contains(terminal.get_cursor_position().unwrap()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cursor_appearance_tracks_editing_and_prompt_modes_without_replacing_text() {
|
||||
use crate::cursor::CursorShape;
|
||||
|
||||
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)),
|
||||
},
|
||||
)
|
||||
.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"),
|
||||
(
|
||||
vec![Input::Escape, Input::Char('v')],
|
||||
Mode::Visual,
|
||||
"\x1b[4 q",
|
||||
"#d787ff",
|
||||
),
|
||||
(
|
||||
vec![Input::Escape, Input::Char(':')],
|
||||
Mode::Command,
|
||||
"\x1b[2 q",
|
||||
"#ffd75f",
|
||||
),
|
||||
(
|
||||
vec![Input::Escape, Input::Char('/')],
|
||||
Mode::Search,
|
||||
"\x1b[2 q",
|
||||
"#ffaf5f",
|
||||
),
|
||||
(vec![Input::Escape], Mode::Normal, "\x1b[2 q", "#5fd7ff"),
|
||||
] {
|
||||
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:?}"
|
||||
);
|
||||
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),
|
||||
)
|
||||
.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;"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terminal_can_present_previous_frame_while_next_frame_is_computed() {
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
@@ -15628,7 +15715,7 @@ fn terminal_can_present_previous_frame_while_next_frame_is_computed() {
|
||||
)
|
||||
.unwrap();
|
||||
for text in ["first frame", "second frame", "third frame"] {
|
||||
draw_frame(&mut terminal, &mut output, false, |frame| {
|
||||
draw_frame(&mut terminal, &mut output, false, None, |frame| {
|
||||
assert!(
|
||||
!synchronized.get(),
|
||||
"terminal presentation suspended during frame computation"
|
||||
@@ -15659,7 +15746,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, |frame| {
|
||||
draw_frame(&mut terminal, &mut output, false, None, |frame| {
|
||||
viewports.draw(frame, &mut editor)
|
||||
})
|
||||
.unwrap();
|
||||
@@ -15739,7 +15826,7 @@ fn terminal_session_scopes_the_callers_panic_hook() {
|
||||
viewport: ratatui::Viewport::Fixed(Rect::new(0, 0, 20, 5)),
|
||||
},
|
||||
)?;
|
||||
draw_frame(&mut terminal, &mut io::stdout(), false, |_| {
|
||||
draw_frame(&mut terminal, &mut io::stdout(), false, None, |_| {
|
||||
panic::panic_any(73_u64)
|
||||
})
|
||||
}
|
||||
@@ -15866,6 +15953,13 @@ 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(),
|
||||
|
||||
Reference in New Issue
Block a user