Skip to content

Commit

Permalink
Add support for a different prompt after submission (#627)
Browse files Browse the repository at this point in the history
adds a separate `transient_prompt` to be displayed after submitting.
  • Loading branch information
ysthakur authored Sep 12, 2023
1 parent b2b8f3f commit 33828e5
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 3 deletions.
132 changes: 132 additions & 0 deletions examples/transient_prompt.rs
Original file line number Diff line number Diff line change
@@ -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<str> {
Cow::Owned(String::new())
}

fn render_prompt_right(&self) -> Cow<str> {
Cow::Owned(String::new())
}

fn render_prompt_indicator(&self, _prompt_mode: PromptEditMode) -> Cow<str> {
Cow::Borrowed(TRANSIENT_PROMPT)
}

fn render_prompt_multiline_indicator(&self) -> Cow<str> {
Cow::Borrowed(TRANSIENT_MULTILINE_INDICATOR)
}

fn render_prompt_history_search_indicator(
&self,
history_search: PromptHistorySearch,
) -> Cow<str> {
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(());
}
}
}
}
17 changes: 16 additions & 1 deletion src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ pub struct Reedline {
// Stdout
painter: Painter,

transient_prompt: Option<Box<dyn Prompt>>,

// Edit Mode: Vi, Emacs
edit_mode: Box<dyn EditMode>,

Expand Down Expand Up @@ -205,6 +207,7 @@ impl Reedline {
history_cursor_on_excluded: false,
input_mode: InputMode::Regular,
painter,
transient_prompt: None,
edit_mode,
completer,
quick_completions: false,
Expand Down Expand Up @@ -462,6 +465,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<dyn Prompt>) -> 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<dyn EditMode>) -> Self {
Expand Down Expand Up @@ -1757,7 +1767,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();
Expand Down
4 changes: 2 additions & 2 deletions src/prompt/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<str>;
/// Provide content off the left full prompt
/// Provide content of the right full prompt
fn render_prompt_right(&self) -> Cow<str>;
/// 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<str>;
Expand Down

0 comments on commit 33828e5

Please sign in to comment.