From 4d986a0f5420d744d357612f449cf7cb0b8a5887 Mon Sep 17 00:00:00 2001 From: junzhuo Date: Tue, 13 Aug 2024 02:28:27 +0800 Subject: [PATCH 1/3] Prototype implementation of continuation prompt --- Cargo.toml | 3 ++ examples/continuation_prompt.rs | 35 +++++++++++++++++++++ src/edit.rs | 54 +++++++++++++++++++++++++++++++-- src/highlight.rs | 4 +++ src/tty/unix.rs | 8 +++-- src/tty/windows.rs | 8 ++++- 6 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 examples/continuation_prompt.rs diff --git a/Cargo.toml b/Cargo.toml index 92b4cd953..8557dcdaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,6 +83,9 @@ required-features = ["custom-bindings", "derive"] name = "input_multiline" required-features = ["custom-bindings", "derive"] [[example]] +name = "continuation_prompt" +required-features = ["custom-bindings", "derive"] +[[example]] name = "input_validation" required-features = ["derive"] [[example]] diff --git a/examples/continuation_prompt.rs b/examples/continuation_prompt.rs new file mode 100644 index 000000000..25346d21f --- /dev/null +++ b/examples/continuation_prompt.rs @@ -0,0 +1,35 @@ +use rustyline::validate::MatchingBracketValidator; +use rustyline::{Cmd, Editor, EventHandler, KeyCode, KeyEvent, Modifiers, Result, highlight::Highlighter}; +use rustyline::{Completer, Helper, Hinter, Validator}; +use std::borrow::Cow::{self, Borrowed}; +#[derive(Completer, Helper, Hinter, Validator)] +struct InputValidator { + #[rustyline(Validator)] + brackets: MatchingBracketValidator, +} + +impl Highlighter for InputValidator { + fn continuation_prompt<'p, 'b>( + &self, + prompt: &'p str, + default: bool, + ) -> Option> { + Some(Borrowed(".... ")) + } +} + +fn main() -> Result<()> { + let h = InputValidator { + brackets: MatchingBracketValidator::new(), + }; + let mut rl = Editor::new()?; + rl.set_helper(Some(h)); + rl.bind_sequence( + KeyEvent(KeyCode::Char('s'), Modifiers::CTRL), + EventHandler::Simple(Cmd::Newline), + ); + let input = rl.readline(">>>> ")?; + println!("Input:\n{input}"); + + Ok(()) +} diff --git a/src/edit.rs b/src/edit.rs index c231e4a42..402e0e6b7 100644 --- a/src/edit.rs +++ b/src/edit.rs @@ -37,6 +37,18 @@ pub struct State<'out, 'prompt, H: Helper> { pub hint: Option>, // last hint displayed pub highlight_char: bool, // `true` if a char has been highlighted pub forced_refresh: bool, // `true` if line is redraw without hint or highlight_char + continuation_prompt_state: Option, +} + +pub struct ContinuationPromptState { + size: Position, + moved: bool, +} + +impl ContinuationPromptState { + fn new(size: Position) -> Self { + Self { size, moved: false } + } } enum Info<'m> { @@ -53,6 +65,13 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { ctx: Context<'out>, ) -> Self { let prompt_size = out.calculate_position(prompt, Position::default()); + let continuation_prompt_state = helper + .and_then(|h|h.continuation_prompt(prompt, true)) + .and_then(|s| + Some( + ContinuationPromptState::new(out.calculate_position(&s, Position::default())) + ) + ); Self { out, prompt, @@ -67,6 +86,8 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { hint: None, highlight_char: false, forced_refresh: false, + // FIXME + continuation_prompt_state, } } @@ -122,6 +143,33 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { ); } + fn continuation_prompt_move_cursor(&mut self) -> Result<()> { + if let Some(state) = self.continuation_prompt_state.as_mut() { + let need_continuation = self.line[..self.line.pos()].contains('\n'); + if !state.moved && need_continuation { + self.out.move_cursor( + self.layout.cursor, + Position { + col: self.layout.cursor.col+state.size.col, + row: self.layout.cursor.row+state.size.row, + } + )?; + state.moved = true; + } + if state.moved && !need_continuation { + self.out.move_cursor( + self.layout.cursor, + Position { + col: self.layout.cursor.col-state.size.col, + row: self.layout.cursor.row-state.size.row, + } + )?; + state.moved = true; + } + }; + Ok(()) + } + pub fn move_cursor(&mut self) -> Result<()> { // calculate the desired position of the cursor let cursor = self @@ -140,7 +188,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { debug_assert!(self.layout.prompt_size <= self.layout.cursor); debug_assert!(self.layout.cursor <= self.layout.end); } - Ok(()) + self.continuation_prompt_move_cursor() } pub fn move_cursor_to_end(&mut self) -> Result<()> { @@ -189,8 +237,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { highlighter, )?; self.layout = new_layout; - - Ok(()) + self.continuation_prompt_move_cursor() } pub fn hint(&mut self) { @@ -766,6 +813,7 @@ pub fn init_state<'out, H: Helper>( hint: Some(Box::new("hint".to_owned())), highlight_char: false, forced_refresh: false, + continuation_prompt_state: None, } } diff --git a/src/highlight.rs b/src/highlight.rs index d5c0e8967..6a831d01e 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -11,6 +11,10 @@ use std::cell::Cell; /// Currently, the highlighted version *must* have the same display width as /// the original input. pub trait Highlighter { + #[allow(unused_variables)] + fn continuation_prompt<'p,'b>(&self, prompt: &'p str, default: bool) -> Option> { + None + } /// Takes the currently edited `line` with the cursor `pos`ition and /// returns the highlighted version (with ANSI color). /// diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 08d754810..a46caf443 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -996,14 +996,18 @@ impl Renderer for PosixRenderer { let end_pos = new_layout.end; self.clear_old_rows(old_layout); - if let Some(highlighter) = highlighter { // display the prompt self.buffer .push_str(&highlighter.highlight_prompt(prompt, default_prompt)); + // add continuation prompt to line if `highlighter.continuation_prompt` is not None + let line_str = match highlighter.continuation_prompt(prompt, default_prompt){ + Some(continuation_prompt) => &line.as_str().replace('\n', &format!("\n{}", &continuation_prompt)), + None => line.as_str(), + }; // display the input line self.buffer - .push_str(&highlighter.highlight(line, line.pos())); + .push_str(&highlighter.highlight(line_str, line.pos())); } else { // display the prompt self.buffer.push_str(prompt); diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 7574403f0..12910c262 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -448,8 +448,14 @@ impl Renderer for ConsoleRenderer { // TODO handle ansi escape code (SetConsoleTextAttribute) // append the prompt col = self.wrap_at_eol(&highlighter.highlight_prompt(prompt, default_prompt), col); + // TODO need test for windows user + // add continuation prompt to line if `highlighter.continuation_prompt` is not None + let line_str = match highlighter.continuation_prompt(prompt, default_prompt){ + Some(continuation_prompt) => &line.as_str().replace('\n', &format!("\n{}", &continuation_prompt)), + None => line.as_str(), + }; // append the input line - col = self.wrap_at_eol(&highlighter.highlight(line, line.pos()), col); + col = self.wrap_at_eol(&highlighter.highlight(line_str, line.pos()), col); } else if self.colors_enabled { // append the prompt col = self.wrap_at_eol(prompt, col); From c10e73481b483053e63b6f7b2c7b3e2c4d97620c Mon Sep 17 00:00:00 2001 From: junzhuo Date: Tue, 13 Aug 2024 02:35:51 +0800 Subject: [PATCH 2/3] Prototype implementation of continuation prompt --- examples/continuation_prompt.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/continuation_prompt.rs b/examples/continuation_prompt.rs index 25346d21f..2660141d3 100644 --- a/examples/continuation_prompt.rs +++ b/examples/continuation_prompt.rs @@ -28,8 +28,10 @@ fn main() -> Result<()> { KeyEvent(KeyCode::Char('s'), Modifiers::CTRL), EventHandler::Simple(Cmd::Newline), ); - let input = rl.readline(">>>> ")?; - println!("Input:\n{input}"); + loop { + let input = rl.readline(">>>> ")?; + println!("Input:\n{input}"); + } Ok(()) } From 3f55e44245827c5ffaa42ef2d91686a39148cbcf Mon Sep 17 00:00:00 2001 From: junzhuo Date: Tue, 13 Aug 2024 03:00:16 +0800 Subject: [PATCH 3/3] Prototype implementation of continuation prompt --- src/highlight.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/highlight.rs b/src/highlight.rs index 6a831d01e..17965c2e0 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -11,10 +11,6 @@ use std::cell::Cell; /// Currently, the highlighted version *must* have the same display width as /// the original input. pub trait Highlighter { - #[allow(unused_variables)] - fn continuation_prompt<'p,'b>(&self, prompt: &'p str, default: bool) -> Option> { - None - } /// Takes the currently edited `line` with the cursor `pos`ition and /// returns the highlighted version (with ANSI color). /// @@ -34,6 +30,13 @@ pub trait Highlighter { let _ = default; Borrowed(prompt) } + /// Takes the `prompt` and + /// returns the continuation prompt. + /// See more at [PR #793](https://github.com/kkawakam/rustyline/pull/793) + #[allow(unused_variables)] + fn continuation_prompt<'p,'b>(&self, prompt: &'p str, default: bool) -> Option> { + None + } /// Takes the `hint` and /// returns the highlighted version (with ANSI color). fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {