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

User-specified continuation prompt #793

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
Expand Down
37 changes: 37 additions & 0 deletions examples/continuation_prompt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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<Cow<'b, str>> {
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),
);
loop {
let input = rl.readline(">>>> ")?;
println!("Input:\n{input}");
}

Ok(())
}
54 changes: 51 additions & 3 deletions src/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ pub struct State<'out, 'prompt, H: Helper> {
pub hint: Option<Box<dyn Hint>>, // 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<ContinuationPromptState>,
}

pub struct ContinuationPromptState {
size: Position,
moved: bool,
}

impl ContinuationPromptState {
fn new(size: Position) -> Self {
Self { size, moved: false }
}
}

enum Info<'m> {
Expand All @@ -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,
Expand All @@ -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,
}
}

Expand Down Expand Up @@ -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
Expand All @@ -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<()> {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
}
}

Expand Down
7 changes: 7 additions & 0 deletions src/highlight.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,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<Cow<'b, str>> {
None
}
/// Takes the `hint` and
/// returns the highlighted version (with ANSI color).
fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
Expand Down
8 changes: 6 additions & 2 deletions src/tty/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
8 changes: 7 additions & 1 deletion src/tty/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down