Make reedline handling cursor shapes more configurable (#515)

Adds a struct to configure the cursor shape

Co-authored-by: sholderbach <sholderbach@users.noreply.github.com>
resolve https://github.com/nushell/reedline/issues/514
This commit is contained in:
Carl Schierig 2022-12-21 12:15:46 +01:00 committed by GitHub
parent de8fc988df
commit 475495d785
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 56 additions and 9 deletions

View File

@ -12,6 +12,8 @@ use {
}, },
}; };
use crossterm::cursor::CursorShape;
use reedline::CursorConfig;
#[cfg(not(any(feature = "sqlite", feature = "sqlite-dynlib")))] #[cfg(not(any(feature = "sqlite", feature = "sqlite-dynlib")))]
use reedline::FileBackedHistory; use reedline::FileBackedHistory;
@ -58,11 +60,18 @@ fn main() -> Result<()> {
let completer = Box::new(DefaultCompleter::new_with_wordlen(commands.clone(), 2)); 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() let mut line_editor = Reedline::create()
.with_history(history) .with_history(history)
.with_completer(completer) .with_completer(completer)
.with_quick_completions(true) .with_quick_completions(true)
.with_partial_completions(true) .with_partial_completions(true)
.with_cursor_config(cursor_config)
.with_highlighter(Box::new(ExampleHighlighter::new(commands))) .with_highlighter(Box::new(ExampleHighlighter::new(commands)))
.with_hinter(Box::new( .with_hinter(Box::new(
DefaultHinter::default().with_style(Style::new().fg(Color::DarkGray)), DefaultHinter::default().with_style(Style::new().fg(Color::DarkGray)),

13
src/edit_mode/cursors.rs Normal file
View File

@ -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<CursorShape>,
/// The cursor to be used when in vi normal mode
pub vi_normal: Option<CursorShape>,
/// The cursor to be used when in emacs mode
pub emacs: Option<CursorShape>,
}

View File

@ -1,9 +1,11 @@
mod base; mod base;
mod cursors;
mod emacs; mod emacs;
mod keybindings; mod keybindings;
mod vi; mod vi;
pub use base::EditMode; pub use base::EditMode;
pub use cursors::CursorConfig;
pub use emacs::{default_emacs_keybindings, Emacs}; pub use emacs::{default_emacs_keybindings, Emacs};
pub use keybindings::Keybindings; pub use keybindings::Keybindings;
pub use vi::{default_vi_insert_keybindings, default_vi_normal_keybindings, Vi}; pub use vi::{default_vi_insert_keybindings, default_vi_normal_keybindings, Vi};

View File

@ -1,3 +1,4 @@
use crate::CursorConfig;
#[cfg(feature = "bashisms")] #[cfg(feature = "bashisms")]
use crate::{ use crate::{
history::SearchFilter, history::SearchFilter,
@ -128,6 +129,9 @@ pub struct Reedline {
// Text editor used to open the line buffer for editing // Text editor used to open the line buffer for editing
buffer_editor: Option<BufferEditor>, buffer_editor: Option<BufferEditor>,
// Use different cursors depending on the current edit mode
cursor_shapes: Option<CursorConfig>,
#[cfg(feature = "external_printer")] #[cfg(feature = "external_printer")]
external_printer: Option<ExternalPrinter<String>>, external_printer: Option<ExternalPrinter<String>>,
} }
@ -179,6 +183,7 @@ impl Reedline {
use_ansi_coloring: true, use_ansi_coloring: true,
menus: Vec::new(), menus: Vec::new(),
buffer_editor: None, buffer_editor: None,
cursor_shapes: None,
#[cfg(feature = "external_printer")] #[cfg(feature = "external_printer")]
external_printer: None, external_printer: None,
} }
@ -377,6 +382,14 @@ impl Reedline {
self 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 /// Returns the corresponding expected prompt style for the given edit mode
pub fn prompt_edit_mode(&self) -> PromptEditMode { pub fn prompt_edit_mode(&self) -> PromptEditMode {
self.edit_mode.edit_mode() self.edit_mode.edit_mode()
@ -1393,6 +1406,7 @@ impl Reedline {
self.prompt_edit_mode(), self.prompt_edit_mode(),
None, None,
self.use_ansi_coloring, self.use_ansi_coloring,
&self.cursor_shapes,
)?; )?;
} }
@ -1460,6 +1474,7 @@ impl Reedline {
self.prompt_edit_mode(), self.prompt_edit_mode(),
menu, menu,
self.use_ansi_coloring, self.use_ansi_coloring,
&self.cursor_shapes,
) )
} }

View File

@ -260,7 +260,7 @@ pub use prompt::{
mod edit_mode; mod edit_mode;
pub use edit_mode::{ pub use edit_mode::{
default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings, default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings,
EditMode, Emacs, Keybindings, Vi, CursorConfig, EditMode, Emacs, Keybindings, Vi,
}; };
mod highlighter; mod highlighter;

View File

@ -1,4 +1,4 @@
use crate::PromptEditMode; use crate::{CursorConfig, PromptEditMode, PromptViMode};
use { use {
super::utils::{coerce_crlf, line_width}, super::utils::{coerce_crlf, line_width},
@ -137,6 +137,7 @@ impl Painter {
prompt_mode: PromptEditMode, prompt_mode: PromptEditMode,
menu: Option<&ReedlineMenu>, menu: Option<&ReedlineMenu>,
use_ansi_coloring: bool, use_ansi_coloring: bool,
cursor_config: &Option<CursorConfig>,
) -> Result<()> { ) -> Result<()> {
self.stdout.queue(cursor::Hide)?; self.stdout.queue(cursor::Hide)?;
@ -175,13 +176,20 @@ impl Painter {
// can print without overwriting the things written during the painting // can print without overwriting the things written during the painting
self.last_required_lines = required_lines; self.last_required_lines = required_lines;
self.stdout self.stdout.queue(RestorePosition)?;
.queue(RestorePosition)?
.queue(cursor::SetCursorShape(match prompt_mode { if let Some(shapes) = cursor_config {
PromptEditMode::Vi(crate::PromptViMode::Insert) => cursor::CursorShape::Line, let shape = match &prompt_mode {
_ => cursor::CursorShape::Block, PromptEditMode::Emacs => shapes.emacs,
}))? PromptEditMode::Vi(PromptViMode::Insert) => shapes.vi_insert,
.queue(cursor::Show)?; 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() self.stdout.flush()
} }