Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Let prompts choose to repaint on enter #627

Merged
merged 24 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1225cb9
Let prompts choose to repaint on enter
ysthakur Aug 23, 2023
ce664c3
Add transient prompt to custom_prompt example
ysthakur Aug 23, 2023
43ed497
Add last line before rendering transient prompt
ysthakur Aug 23, 2023
41b8be6
Remove debug println
ysthakur Aug 23, 2023
43463bd
Format with cargo fmt
ysthakur Aug 23, 2023
1d7fc16
Add some comments
ysthakur Aug 23, 2023
2fe9f35
Make separate transient prompt example, restore custom prompt example
ysthakur Aug 23, 2023
1181949
Use sqlite-backed history in transient_prompt example
ysthakur Aug 23, 2023
fd2be40
Add bells and whistles to transient prompt example
ysthakur Aug 23, 2023
856216a
Add custom validator to transient prompt example
ysthakur Aug 23, 2023
69dd574
Format with cargo fmt
ysthakur Aug 23, 2023
2c3c74a
Use \ for multiline input instead of making it the default
ysthakur Aug 27, 2023
657c983
Use ansi colors, exclude ' '-prefixed lines from history
ysthakur Aug 27, 2023
0cfe40a
Add post_submit versions of render methods to Prompt
ysthakur Aug 27, 2023
0070e27
Add a get_transient_prompt method to Prompt
ysthakur Aug 27, 2023
792efff
Run cargo fmt
ysthakur Aug 27, 2023
dc5270f
Add transient_prompt field to Reedline itself
ysthakur Aug 28, 2023
9bb3efa
Minor stylistic change
ysthakur Aug 28, 2023
d70d216
Make transient_prompt an Arc<Mutex> so it's Send
ysthakur Aug 29, 2023
22609d0
Get rid of unwrap()
ysthakur Aug 29, 2023
46090a9
Use a Cell<Option<Box>>>
ysthakur Aug 31, 2023
c567bce
Don't use Cell
ysthakur Aug 31, 2023
a086b4c
Use Option.take instead of mem::replace
ysthakur Sep 12, 2023
35f44ae
Merge pull request #1 from ysthakur/no-arc-mutex
ysthakur Sep 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -110,6 +110,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 @@ -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,
Expand Down Expand Up @@ -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<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 @@ -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();
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
Loading