Skip to content

Commit

Permalink
new Helper to reduce parser overhead and support continuation prompt
Browse files Browse the repository at this point in the history
  • Loading branch information
zao111222333 committed Sep 25, 2024
1 parent 7465673 commit f53ba9e
Show file tree
Hide file tree
Showing 24 changed files with 434 additions and 273 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ with-fuzzy = ["skim"]
case_insensitive_history_search = ["regex"]
# For continuation prompt, indentation, scrolling
split-highlight = []
continuation-prompt = []

[[example]]
name = "custom_key_bindings"
Expand All @@ -97,6 +98,9 @@ required-features = ["derive"]
[[example]]
name = "sqlite_history"
required-features = ["with-sqlite-history"]
[[example]]
name = "continuation_prompt"
required-features = ["custom-bindings", "derive", "continuation-prompt", "split-highlight"]

[package.metadata.docs.rs]
features = [
Expand Down
76 changes: 76 additions & 0 deletions examples/continuation_prompt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use rustyline::highlight::Highlighter;
use rustyline::validate::{ValidationContext, ValidationResult, Validator};
use rustyline::{Cmd, Editor, EventHandler, Helper, KeyCode, KeyEvent, Modifiers, Result};
use rustyline::{Completer, Hinter};

#[derive(Completer, Hinter)]
struct InputValidator {
bracket_level: i32,
/// re-render only when input just changed
/// not render after cursor moving
need_render: bool,
}

impl Helper for InputValidator {
fn update_after_edit(&mut self, line: &str, _pos: usize, _forced_refresh: bool) {
self.bracket_level = line.chars().fold(0, |level, c| {
if c == '(' {
level + 1
} else if c == ')' {
level - 1
} else {
level
}
});
self.need_render = true;
}
}

impl Validator for InputValidator {
fn validate(&mut self, _ctx: &mut ValidationContext) -> Result<ValidationResult> {
if self.bracket_level > 0 {
Ok(ValidationResult::Incomplete(2))
} else if self.bracket_level < 0 {
Ok(ValidationResult::Invalid(Some(format!(
" - excess {} close bracket",
-self.bracket_level
))))
} else {
Ok(ValidationResult::Valid(None))
}
}
}

impl Highlighter for InputValidator {
fn highlight_char(&mut self, _line: &str, _pos: usize, _forced: bool) -> bool {
self.need_render
}
fn highlight_line<'l>(
&mut self,
line: &'l str,
_pos: usize,
) -> impl Iterator<Item = impl 'l + rustyline::highlight::StyledBlock> {
use core::iter::once;
let mut lines = line.split('\n');
self.need_render = false;
once(((), lines.next().unwrap()))
.chain(lines.flat_map(|line| once(((), "\n.. ")).chain(once(((), line)))))
}
}

fn main() -> Result<()> {
let h = InputValidator {
bracket_level: 0,
need_render: true,
};
let mut rl = Editor::new(h)?;
rl.bind_sequence(
KeyEvent(KeyCode::Char('s'), Modifiers::CTRL),
EventHandler::Simple(Cmd::Newline),
);

let input = rl.readline(">> ")?;
println!("Input: {input}");

Ok(())
}
7 changes: 3 additions & 4 deletions examples/custom_key_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ struct MyHelper(#[rustyline(Hinter)] HistoryHinter);

impl Highlighter for MyHelper {
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
&'s mut self,
prompt: &'p str,
default: bool,
) -> Cow<'b, str> {
Expand All @@ -25,7 +25,7 @@ impl Highlighter for MyHelper {
}
}

fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
fn highlight_hint<'h>(&mut self, hint: &'h str) -> Cow<'h, str> {
Owned(format!("\x1b[1m{hint}\x1b[m"))
}
}
Expand Down Expand Up @@ -85,8 +85,7 @@ impl ConditionalEventHandler for TabEventHandler {
}

fn main() -> Result<()> {
let mut rl = Editor::<MyHelper, DefaultHistory>::new()?;
rl.set_helper(Some(MyHelper(HistoryHinter::new())));
let mut rl = Editor::<MyHelper, DefaultHistory>::new(MyHelper(HistoryHinter::new()))?;

let ceh = Box::new(CompleteHintHandler);
rl.bind_sequence(KeyEvent::ctrl('E'), EventHandler::Conditional(ceh.clone()));
Expand Down
5 changes: 2 additions & 3 deletions examples/diy_hints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ impl CommandHint {
impl Hinter for DIYHinter {
type Hint = CommandHint;

fn hint(&self, line: &str, pos: usize, _ctx: &Context<'_>) -> Option<CommandHint> {
fn hint(&mut self, line: &str, pos: usize, _ctx: &Context<'_>) -> Option<CommandHint> {
if line.is_empty() || pos < line.len() {
return None;
}
Expand Down Expand Up @@ -86,8 +86,7 @@ fn main() -> Result<()> {
println!("This is a DIY hint hack of rustyline");
let h = DIYHinter { hints: diy_hints() };

let mut rl: Editor<DIYHinter, DefaultHistory> = Editor::new()?;
rl.set_helper(Some(h));
let mut rl: Editor<DIYHinter, DefaultHistory> = Editor::new(h)?;

loop {
let input = rl.readline("> ")?;
Expand Down
15 changes: 7 additions & 8 deletions examples/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ struct MyHelper {

impl Highlighter for MyHelper {
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
&'s mut self,
prompt: &'p str,
default: bool,
) -> Cow<'b, str> {
Expand All @@ -33,25 +33,25 @@ impl Highlighter for MyHelper {
}
}

fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
fn highlight_hint<'h>(&mut self, hint: &'h str) -> Cow<'h, str> {
Owned("\x1b[1m".to_owned() + hint + "\x1b[m")
}

#[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))]
fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> {
fn highlight<'l>(&mut self, line: &'l str, pos: usize) -> Cow<'l, str> {
self.highlighter.highlight(line, pos)
}

#[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))]
fn highlight_line<'l>(
&self,
&mut self,
line: &'l str,
pos: usize,
) -> impl Iterator<Item = impl 'l + rustyline::highlight::StyledBlock> {
self.highlighter.highlight_line(line, pos)
}

fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool {
fn highlight_char(&mut self, line: &str, pos: usize, forced: bool) -> bool {
self.highlighter.highlight_char(line, pos, forced)
}
}
Expand All @@ -72,8 +72,7 @@ fn main() -> rustyline::Result<()> {
colored_prompt: "".to_owned(),
validator: MatchingBracketValidator::new(),
};
let mut rl = Editor::with_config(config)?;
rl.set_helper(Some(h));
let mut rl = Editor::with_config(config, h)?;
rl.bind_sequence(KeyEvent::alt('n'), Cmd::HistorySearchForward);
rl.bind_sequence(KeyEvent::alt('p'), Cmd::HistorySearchBackward);
if rl.load_history("history.txt").is_err() {
Expand All @@ -82,7 +81,7 @@ fn main() -> rustyline::Result<()> {
let mut count = 1;
loop {
let p = format!("{count}> ");
rl.helper_mut().expect("No helper").colored_prompt = format!("\x1b[1;32m{p}\x1b[0m");
rl.helper_mut().colored_prompt = format!("\x1b[1;32m{p}\x1b[0m");
let readline = rl.readline(&p);
match readline {
Ok(line) => {
Expand Down
2 changes: 1 addition & 1 deletion examples/external_print.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use rand::{thread_rng, Rng};
use rustyline::{DefaultEditor, ExternalPrinter, Result};

fn main() -> Result<()> {
let mut rl = DefaultEditor::new()?;
let mut rl = DefaultEditor::new(())?;
let mut printer = rl.create_external_printer()?;
thread::spawn(move || {
let mut rng = thread_rng();
Expand Down
3 changes: 1 addition & 2 deletions examples/input_multiline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ fn main() -> Result<()> {
brackets: MatchingBracketValidator::new(),
highlighter: MatchingBracketHighlighter::new(),
};
let mut rl = Editor::new()?;
rl.set_helper(Some(h));
let mut rl = Editor::new(h)?;
rl.bind_sequence(
KeyEvent(KeyCode::Char('s'), Modifiers::CTRL),
EventHandler::Simple(Cmd::Newline),
Expand Down
7 changes: 3 additions & 4 deletions examples/input_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ use rustyline::{Editor, Result};
struct InputValidator {}

impl Validator for InputValidator {
fn validate(&self, ctx: &mut ValidationContext) -> Result<ValidationResult> {
fn validate(&mut self, ctx: &mut ValidationContext) -> Result<ValidationResult> {
use ValidationResult::{Incomplete, Invalid, Valid};
let input = ctx.input();
let result = if !input.starts_with("SELECT") {
Invalid(Some(" --< Expect: SELECT stmt".to_owned()))
} else if !input.ends_with(';') {
Incomplete
Incomplete(0)
} else {
Valid(None)
};
Expand All @@ -22,8 +22,7 @@ impl Validator for InputValidator {

fn main() -> Result<()> {
let h = InputValidator {};
let mut rl = Editor::new()?;
rl.set_helper(Some(h));
let mut rl = Editor::new(h)?;

let input = rl.readline("> ")?;
println!("Input: {input}");
Expand Down
2 changes: 1 addition & 1 deletion examples/minimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use rustyline::{DefaultEditor, Result};
/// Minimal REPL
fn main() -> Result<()> {
env_logger::init();
let mut rl = DefaultEditor::new()?;
let mut rl = DefaultEditor::new(())?;
loop {
let line = rl.readline("> ")?; // read
println!("Line: {line}"); // eval / print
Expand Down
2 changes: 1 addition & 1 deletion examples/numeric_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ impl ConditionalEventHandler for FilteringEventHandler {
}

fn main() -> Result<()> {
let mut rl = DefaultEditor::new()?;
let mut rl = DefaultEditor::new(())?;

rl.bind_sequence(
Event::Any,
Expand Down
9 changes: 4 additions & 5 deletions examples/read_password.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ struct MaskingHighlighter {

impl Highlighter for MaskingHighlighter {
#[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))]
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> std::borrow::Cow<'l, str> {
fn highlight<'l>(&mut self, line: &'l str, _pos: usize) -> std::borrow::Cow<'l, str> {
use unicode_width::UnicodeWidthStr;
if self.masking {
std::borrow::Cow::Owned(" ".repeat(line.width()))
Expand All @@ -37,21 +37,20 @@ impl Highlighter for MaskingHighlighter {
}
}

fn highlight_char(&self, _line: &str, _pos: usize, _forced: bool) -> bool {
fn highlight_char(&mut self, _line: &str, _pos: usize, _forced: bool) -> bool {
self.masking
}
}

fn main() -> Result<()> {
println!("This is just a hack. Reading passwords securely requires more than that.");
let h = MaskingHighlighter { masking: false };
let mut rl = Editor::new()?;
rl.set_helper(Some(h));
let mut rl = Editor::new(h)?;

let username = rl.readline("Username:")?;
println!("Username: {username}");

rl.helper_mut().expect("No helper").masking = true;
rl.helper_mut().masking = true;
rl.set_color_mode(ColorMode::Forced); // force masking
rl.set_auto_add_history(false); // make sure password is not added to history
let mut guard = rl.set_cursor_visibility(false)?;
Expand Down
2 changes: 1 addition & 1 deletion examples/sqlite_history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ fn main() -> Result<()> {
// file
rustyline::sqlite_history::SQLiteHistory::open(config, "history.sqlite3")?
};
let mut rl: Editor<(), _> = Editor::with_history(config, history)?;
let mut rl: Editor<(), _> = Editor::with_history(config, history, ())?;
loop {
let line = rl.readline("> ")?;
println!("{line}");
Expand Down
Loading

0 comments on commit f53ba9e

Please sign in to comment.