diff --git a/examples/transient_prompt.rs b/examples/transient_prompt.rs new file mode 100644 index 00000000..7d270a7c --- /dev/null +++ b/examples/transient_prompt.rs @@ -0,0 +1,132 @@ +// Create a reedline object with a transient prompt. +// cargo run --example transient_prompt +// +// Prompts for previous lines will be replaced with a shorter prompt + +use nu_ansi_term::{Color, Style}; +#[cfg(any(feature = "sqlite", feature = "sqlite-dynlib"))] +use reedline::SqliteBackedHistory; +use reedline::{ + default_emacs_keybindings, ColumnarMenu, DefaultCompleter, DefaultHinter, DefaultPrompt, Emacs, + ExampleHighlighter, KeyCode, KeyModifiers, Keybindings, Prompt, PromptEditMode, + PromptHistorySearch, PromptHistorySearchStatus, Reedline, ReedlineEvent, ReedlineMenu, Signal, + ValidationResult, Validator, +}; +use std::{borrow::Cow, io}; + +// For custom prompt, implement the Prompt trait +// +// This example replaces the prompt for old lines with "!" as an example of a +// transient prompt. +pub struct TransientPrompt; + +pub static TRANSIENT_PROMPT: &str = "! "; +pub static TRANSIENT_MULTILINE_INDICATOR: &str = ": "; + +impl Prompt for TransientPrompt { + fn render_prompt_left(&self) -> Cow { + Cow::Owned(String::new()) + } + + fn render_prompt_right(&self) -> Cow { + Cow::Owned(String::new()) + } + + fn render_prompt_indicator(&self, _prompt_mode: PromptEditMode) -> Cow { + Cow::Borrowed(TRANSIENT_PROMPT) + } + + fn render_prompt_multiline_indicator(&self) -> Cow { + Cow::Borrowed(TRANSIENT_MULTILINE_INDICATOR) + } + + fn render_prompt_history_search_indicator( + &self, + history_search: PromptHistorySearch, + ) -> Cow { + let prefix = match history_search.status { + PromptHistorySearchStatus::Passing => "", + PromptHistorySearchStatus::Failing => "failing ", + }; + + Cow::Owned(format!( + "({}reverse-search: {}) ", + prefix, history_search.term + )) + } +} + +// To test multiline input. Treats as multiline if input ends with a '\' +struct CustomValidator; + +impl Validator for CustomValidator { + fn validate(&self, line: &str) -> ValidationResult { + if line.ends_with("\\") { + ValidationResult::Incomplete + } else { + ValidationResult::Complete + } + } +} + +// This is copied from the completions example +fn add_menu_keybindings(keybindings: &mut Keybindings) { + keybindings.add_binding( + KeyModifiers::NONE, + KeyCode::Tab, + ReedlineEvent::UntilFound(vec![ + ReedlineEvent::Menu("completion_menu".to_string()), + ReedlineEvent::MenuNext, + ]), + ); +} + +fn main() -> io::Result<()> { + println!("Transient prompt demo:\nAbort with Ctrl-C or Ctrl-D"); + let commands = vec![ + "test".into(), + "hello world".into(), + "hello world reedline".into(), + "this is the reedline crate".into(), + ]; + let completer = Box::new(DefaultCompleter::new_with_wordlen(commands.clone(), 2)); + // Use the interactive menu to select options from the completer + let completion_menu = Box::new(ColumnarMenu::default().with_name("completion_menu")); + + let mut keybindings = default_emacs_keybindings(); + add_menu_keybindings(&mut keybindings); + + let edit_mode = Box::new(Emacs::new(keybindings)); + + let mut line_editor = Reedline::create() + .with_hinter(Box::new( + DefaultHinter::default().with_style(Style::new().fg(Color::LightGray)), + )) + .with_completer(completer) + .with_menu(ReedlineMenu::EngineCompleter(completion_menu)) + .with_edit_mode(edit_mode) + .with_highlighter(Box::new(ExampleHighlighter::new(commands))) + .with_validator(Box::new(CustomValidator {})) + .with_ansi_colors(true) + .with_history_exclusion_prefix(Some(String::from(" "))) + .with_transient_prompt(Box::new(TransientPrompt {})); + #[cfg(any(feature = "sqlite", feature = "sqlite-dynlib"))] + { + line_editor = line_editor.with_history(Box::new(SqliteBackedHistory::in_memory().unwrap())); + } + + let prompt = DefaultPrompt::default(); + + loop { + let sig = line_editor.read_line(&prompt)?; + match sig { + Signal::Success(buffer) => { + println!("We processed: {buffer}"); + } + Signal::CtrlD | Signal::CtrlC => { + println!("\nAborted!"); + break Ok(()); + } + } + } +} diff --git a/src/engine.rs b/src/engine.rs index ac69df9a..94d4d0c9 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -110,6 +110,8 @@ pub struct Reedline { // Stdout painter: Painter, + transient_prompt: Option>, + // Edit Mode: Vi, Emacs edit_mode: Box, @@ -197,6 +199,7 @@ impl Reedline { history_cursor_on_excluded: false, input_mode: InputMode::Regular, painter, + transient_prompt: None, edit_mode, completer, quick_completions: false, @@ -434,6 +437,13 @@ impl Reedline { self } + /// Set a different prompt to be used after submitting each line + #[must_use] + pub fn with_transient_prompt(mut self, transient_prompt: Box) -> Self { + self.transient_prompt = Some(transient_prompt); + self + } + /// A builder which configures the edit mode for your instance of the Reedline engine #[must_use] pub fn with_edit_mode(mut self, edit_mode: Box) -> Self { @@ -1700,7 +1710,12 @@ impl Reedline { let buffer = self.editor.get_buffer().to_string(); self.hide_hints = true; // Additional repaint to show the content without hints etc. - self.repaint(prompt)?; + if let Some(transient_prompt) = self.transient_prompt.take() { + self.repaint(transient_prompt.as_ref())?; + self.transient_prompt = Some(transient_prompt); + } else { + self.repaint(prompt)?; + } if !buffer.is_empty() { let mut entry = HistoryItem::from_command_line(&buffer); entry.session_id = self.get_history_session_id(); diff --git a/src/prompt/base.rs b/src/prompt/base.rs index 0ba570c7..01d91658 100644 --- a/src/prompt/base.rs +++ b/src/prompt/base.rs @@ -84,9 +84,9 @@ impl Display for PromptEditMode { /// Implementors have to provide [`str`]-based content which will be /// displayed before the `LineBuffer` is drawn. pub trait Prompt: Send { - /// Provide content off the right full prompt + /// Provide content of the left full prompt fn render_prompt_left(&self) -> Cow; - /// Provide content off the left full prompt + /// Provide content of the right full prompt fn render_prompt_right(&self) -> Cow; /// Render the prompt indicator (Last part of the prompt that changes based on the editor mode) fn render_prompt_indicator(&self, prompt_mode: PromptEditMode) -> Cow;