diff --git a/examples/demo.rs b/examples/demo.rs index 3f49b1e..7a82a0a 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -12,6 +12,8 @@ use { }, }; +use crossterm::cursor::CursorShape; +use reedline::CursorConfig; #[cfg(not(any(feature = "sqlite", feature = "sqlite-dynlib")))] use reedline::FileBackedHistory; @@ -58,11 +60,18 @@ fn main() -> Result<()> { let completer = Box::new(DefaultCompleter::new_with_wordlen(commands.clone(), 2)); + let cursor_config = CursorConfig { + vi_insert: Some(CursorShape::Line), + vi_normal: Some(CursorShape::Block), + emacs: None, + }; + let mut line_editor = Reedline::create() .with_history(history) .with_completer(completer) .with_quick_completions(true) .with_partial_completions(true) + .with_cursor_config(cursor_config) .with_highlighter(Box::new(ExampleHighlighter::new(commands))) .with_hinter(Box::new( DefaultHinter::default().with_style(Style::new().fg(Color::DarkGray)), diff --git a/src/edit_mode/cursors.rs b/src/edit_mode/cursors.rs new file mode 100644 index 0000000..b975d5c --- /dev/null +++ b/src/edit_mode/cursors.rs @@ -0,0 +1,13 @@ +use crossterm::cursor::CursorShape; + +/// Maps cursor shapes to each edit mode (emacs, vi normal & vi insert). +/// If any of the fields is `None`, the cursor won't get changed by Reedline for that mode. +#[derive(Default)] +pub struct CursorConfig { + /// The cursor to be used when in vi insert mode + pub vi_insert: Option, + /// The cursor to be used when in vi normal mode + pub vi_normal: Option, + /// The cursor to be used when in emacs mode + pub emacs: Option, +} diff --git a/src/edit_mode/mod.rs b/src/edit_mode/mod.rs index 80ca920..38e1456 100644 --- a/src/edit_mode/mod.rs +++ b/src/edit_mode/mod.rs @@ -1,9 +1,11 @@ mod base; +mod cursors; mod emacs; mod keybindings; mod vi; pub use base::EditMode; +pub use cursors::CursorConfig; pub use emacs::{default_emacs_keybindings, Emacs}; pub use keybindings::Keybindings; pub use vi::{default_vi_insert_keybindings, default_vi_normal_keybindings, Vi}; diff --git a/src/engine.rs b/src/engine.rs index 7e66ff4..c7af3d4 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,3 +1,4 @@ +use crate::CursorConfig; #[cfg(feature = "bashisms")] use crate::{ history::SearchFilter, @@ -128,6 +129,9 @@ pub struct Reedline { // Text editor used to open the line buffer for editing buffer_editor: Option, + // Use different cursors depending on the current edit mode + cursor_shapes: Option, + #[cfg(feature = "external_printer")] external_printer: Option>, } @@ -179,6 +183,7 @@ impl Reedline { use_ansi_coloring: true, menus: Vec::new(), buffer_editor: None, + cursor_shapes: None, #[cfg(feature = "external_printer")] external_printer: None, } @@ -377,6 +382,14 @@ impl Reedline { self } + /// A builder that enables reedline changing the cursor shape based on the current edit mode. + /// The current implementation sets the cursor shape when drawing the prompt. + /// Do not use this if the cursor shape is set elsewhere, e.g. in the terminal settings or by ansi escape sequences. + pub fn with_cursor_config(mut self, cursor_shapes: CursorConfig) -> Self { + self.cursor_shapes = Some(cursor_shapes); + self + } + /// Returns the corresponding expected prompt style for the given edit mode pub fn prompt_edit_mode(&self) -> PromptEditMode { self.edit_mode.edit_mode() @@ -1393,6 +1406,7 @@ impl Reedline { self.prompt_edit_mode(), None, self.use_ansi_coloring, + &self.cursor_shapes, )?; } @@ -1460,6 +1474,7 @@ impl Reedline { self.prompt_edit_mode(), menu, self.use_ansi_coloring, + &self.cursor_shapes, ) } diff --git a/src/lib.rs b/src/lib.rs index 5ad8860..8d0a74c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -260,7 +260,7 @@ pub use prompt::{ mod edit_mode; pub use edit_mode::{ default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings, - EditMode, Emacs, Keybindings, Vi, + CursorConfig, EditMode, Emacs, Keybindings, Vi, }; mod highlighter; diff --git a/src/painting/painter.rs b/src/painting/painter.rs index bace91d..0458bdc 100644 --- a/src/painting/painter.rs +++ b/src/painting/painter.rs @@ -1,4 +1,4 @@ -use crate::PromptEditMode; +use crate::{CursorConfig, PromptEditMode, PromptViMode}; use { super::utils::{coerce_crlf, line_width}, @@ -137,6 +137,7 @@ impl Painter { prompt_mode: PromptEditMode, menu: Option<&ReedlineMenu>, use_ansi_coloring: bool, + cursor_config: &Option, ) -> Result<()> { self.stdout.queue(cursor::Hide)?; @@ -175,13 +176,20 @@ impl Painter { // can print without overwriting the things written during the painting self.last_required_lines = required_lines; - self.stdout - .queue(RestorePosition)? - .queue(cursor::SetCursorShape(match prompt_mode { - PromptEditMode::Vi(crate::PromptViMode::Insert) => cursor::CursorShape::Line, - _ => cursor::CursorShape::Block, - }))? - .queue(cursor::Show)?; + self.stdout.queue(RestorePosition)?; + + if let Some(shapes) = cursor_config { + let shape = match &prompt_mode { + PromptEditMode::Emacs => shapes.emacs, + PromptEditMode::Vi(PromptViMode::Insert) => shapes.vi_insert, + PromptEditMode::Vi(PromptViMode::Normal) => shapes.vi_normal, + _ => None, + }; + if let Some(shape) = shape { + self.stdout.queue(cursor::SetCursorShape(shape))?; + } + } + self.stdout.queue(cursor::Show)?; self.stdout.flush() }