Skip to content

Commit

Permalink
Hack in realtime verification support
Browse files Browse the repository at this point in the history
Please use this in production :-)
  • Loading branch information
kbjakex authored Oct 23, 2022
1 parent 187d408 commit 2d5ddf6
Show file tree
Hide file tree
Showing 10 changed files with 177 additions and 13 deletions.
6 changes: 6 additions & 0 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,5 +233,11 @@ pub fn execute<H: Helper>(
// Ignore the character typed.
}
}

if let Some(validator) = s.helper {
if validator.validate_while_typing() {
s.validate()?;
}
}
Ok(Proceed)
}
19 changes: 14 additions & 5 deletions src/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
}
if self.highlight_char() {
let prompt_size = self.prompt_size;
self.refresh(self.prompt, prompt_size, true, Info::NoHint)?;
self.refresh(self.prompt, prompt_size, true, Info::NoHint, false)?;
} else {
self.out.move_cursor(self.layout.cursor, cursor)?;
self.layout.prompt_size = self.prompt_size;
Expand Down Expand Up @@ -158,6 +158,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
prompt_size: Position,
default_prompt: bool,
info: Info<'_>,
submit: bool,
) -> Result<()> {
let info = match info {
Info::NoHint => None,
Expand All @@ -183,6 +184,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
&self.layout,
&new_layout,
highlighter,
submit,
)?;
self.layout = new_layout;

Expand Down Expand Up @@ -258,25 +260,32 @@ impl<'out, 'prompt, H: Helper> Invoke for State<'out, 'prompt, H> {
}

impl<'out, 'prompt, H: Helper> Refresher for State<'out, 'prompt, H> {
fn submit_line(&mut self) -> Result<()> {
let prompt_size = self.prompt_size;
self.hint();
self.highlight_char();
self.refresh(self.prompt, prompt_size, true, Info::Hint, true)
}

fn refresh_line(&mut self) -> Result<()> {
let prompt_size = self.prompt_size;
self.hint();
self.highlight_char();
self.refresh(self.prompt, prompt_size, true, Info::Hint)
self.refresh(self.prompt, prompt_size, true, Info::Hint, false)
}

fn refresh_line_with_msg(&mut self, msg: Option<&str>) -> Result<()> {
let prompt_size = self.prompt_size;
self.hint = None;
self.highlight_char();
self.refresh(self.prompt, prompt_size, true, Info::Msg(msg))
self.refresh(self.prompt, prompt_size, true, Info::Msg(msg), false)
}

fn refresh_prompt_and_line(&mut self, prompt: &str) -> Result<()> {
let prompt_size = self.out.calculate_position(prompt, Position::default());
self.hint();
self.highlight_char();
self.refresh(prompt, prompt_size, false, Info::Hint)
self.refresh(prompt, prompt_size, false, Info::Hint, false)
}

fn doing_insert(&mut self) {
Expand Down Expand Up @@ -366,7 +375,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
let bits = ch.encode_utf8(&mut self.byte_buffer);
self.out.write_and_flush(bits)
} else {
self.refresh(self.prompt, prompt_size, true, Info::Hint)
self.refresh(self.prompt, prompt_size, true, Info::Hint, false)
}
} else {
self.refresh_line()
Expand Down
98 changes: 98 additions & 0 deletions src/examples/example.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use std::borrow::Cow::{self, Borrowed, Owned};

use rustyline::completion::FilenameCompleter;
use rustyline::error::ReadlineError;
use rustyline::highlight::{Highlighter, MatchingBracketHighlighter};
use rustyline::hint::HistoryHinter;
use rustyline::validate::MatchingBracketValidator;
use rustyline::{Cmd, CompletionType, Config, EditMode, Editor, KeyEvent};
use rustyline_derive::{Completer, Helper, Hinter, Validator};

#[derive(Helper, Completer, Hinter, Validator)]
struct MyHelper {
#[rustyline(Completer)]
completer: FilenameCompleter,
highlighter: MatchingBracketHighlighter,
#[rustyline(Validator)]
validator: MatchingBracketValidator,
#[rustyline(Hinter)]
hinter: HistoryHinter,
colored_prompt: String,
}

impl Highlighter for MyHelper {
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
prompt: &'p str,
default: bool,
) -> Cow<'b, str> {
if default {
Borrowed(&self.colored_prompt)
} else {
Borrowed(prompt)
}
}

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

fn highlight<'l>(&self, line: &'l str, pos: usize, submit: bool) -> Cow<'l, str> {
self.highlighter.highlight(line, pos, submit)
}

fn highlight_char(&self, line: &str, pos: usize) -> bool {
self.highlighter.highlight_char(line, pos)
}
}

// To debug rustyline:
// RUST_LOG=rustyline=debug cargo run --example example 2> debug.log
fn main() -> rustyline::Result<()> {
env_logger::init();
let config = Config::builder()
.history_ignore_space(true)
.completion_type(CompletionType::List)
.edit_mode(EditMode::Emacs)
.build();
let h = MyHelper {
completer: FilenameCompleter::new(),
highlighter: MatchingBracketHighlighter::new(),
hinter: HistoryHinter {},
colored_prompt: "".to_owned(),
validator: MatchingBracketValidator::new(),
};
let mut rl = Editor::with_config(config)?;
rl.set_helper(Some(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() {
println!("No previous history.");
}
let mut count = 1;
loop {
let p = format!("{}> ", count);
rl.helper_mut().expect("No helper").colored_prompt = format!("\x1b[1;32m{}\x1b[0m", p);
let readline = rl.readline(&p);
match readline {
Ok(line) => {
rl.add_history_entry(line.as_str());
println!("Line: {}", line);
}
Err(ReadlineError::Interrupted) => {
println!("Interrupted");
break;
}
Err(ReadlineError::Eof) => {
println!("Encountered Eof");
break;
}
Err(err) => {
println!("Error: {:?}", err);
break;
}
}
count += 1;
}
rl.append_history("history.txt")
}
43 changes: 43 additions & 0 deletions src/examples/read_password.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use std::borrow::Cow::{self, Borrowed, Owned};

use rustyline::config::Configurer;
use rustyline::highlight::Highlighter;
use rustyline::{ColorMode, Editor, Result};
use rustyline_derive::{Completer, Helper, Hinter, Validator};

#[derive(Completer, Helper, Hinter, Validator)]
struct MaskingHighlighter {
masking: bool,
}

impl Highlighter for MaskingHighlighter {
fn highlight<'l>(&self, line: &'l str, _pos: usize, _submit: bool) -> Cow<'l, str> {
use unicode_width::UnicodeWidthStr;
if self.masking {
Owned("*".repeat(line.width()))
} else {
Borrowed(line)
}
}

fn highlight_char(&self, _line: &str, _pos: usize) -> 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 username = rl.readline("Username:")?;
println!("Username: {}", username);

rl.helper_mut().expect("No helper").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 passwd = rl.readline("Password:")?;
println!("Secret: {}", passwd);
Ok(())
}
10 changes: 5 additions & 5 deletions src/highlight.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ pub trait Highlighter {
///
/// For example, you can implement
/// [blink-matching-paren](https://www.gnu.org/software/bash/manual/html_node/Readline-Init-File-Syntax.html).
fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> {
let _ = pos;
fn highlight<'l>(&self, line: &'l str, pos: usize, submit: bool) -> Cow<'l, str> {
let _ = (pos, submit);
Borrowed(line)
}
/// Takes the `prompt` and
Expand Down Expand Up @@ -62,8 +62,8 @@ pub trait Highlighter {
impl Highlighter for () {}

impl<'r, H: ?Sized + Highlighter> Highlighter for &'r H {
fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> {
(**self).highlight(line, pos)
fn highlight<'l>(&self, line: &'l str, pos: usize, submit: bool) -> Cow<'l, str> {
(**self).highlight(line, pos, submit)
}

fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
Expand Down Expand Up @@ -113,7 +113,7 @@ impl MatchingBracketHighlighter {
}

impl Highlighter for MatchingBracketHighlighter {
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
fn highlight<'l>(&self, line: &'l str, _pos: usize, _submit: bool) -> Cow<'l, str> {
if line.len() <= 1 {
return Borrowed(line);
}
Expand Down
1 change: 1 addition & 0 deletions src/keymap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ impl Invoke for &str {
}

pub trait Refresher {
fn submit_line(&mut self) -> Result<()>;
/// Rewrite the currently edited line accordingly to the buffer content,
/// cursor position, and number of columns of the terminal.
fn refresh_line(&mut self) -> Result<()>;
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,7 @@ impl<H: Helper> Editor<H> {
let (original_mode, term_key_map) = self.term.enable_raw_mode()?;
let guard = Guard(&original_mode);
let user_input = self.readline_edit(prompt, initial, &original_mode, term_key_map);

if self.config.auto_add_history() {
if let Ok(ref line) = user_input {
self.add_history_entry(line.as_str());
Expand Down Expand Up @@ -770,6 +771,8 @@ impl<H: Helper> Editor<H> {
// next thing application prints goes after the input
s.edit_move_buffer_end()?;

s.submit_line()?;

if cfg!(windows) {
let _ = original_mode; // silent warning
}
Expand Down
4 changes: 3 additions & 1 deletion src/tty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ pub trait Renderer {
old_layout: &Layout,
new_layout: &Layout,
highlighter: Option<&dyn Highlighter>,
submit: bool,
) -> Result<()>;

/// Compute layout for rendering prompt + line + some info (either hint,
Expand Down Expand Up @@ -131,8 +132,9 @@ impl<'a, R: Renderer + ?Sized> Renderer for &'a mut R {
old_layout: &Layout,
new_layout: &Layout,
highlighter: Option<&dyn Highlighter>,
submit: bool,
) -> Result<()> {
(**self).refresh_line(prompt, line, hint, old_layout, new_layout, highlighter)
(**self).refresh_line(prompt, line, hint, old_layout, new_layout, highlighter, submit)
}

fn calculate_position(&self, s: &str, orig: Position) -> Position {
Expand Down
1 change: 1 addition & 0 deletions src/tty/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ impl Renderer for Sink {
_old_layout: &Layout,
_new_layout: &Layout,
_highlighter: Option<&dyn Highlighter>,
_submit: bool,
) -> Result<()> {
Ok(())
}
Expand Down
5 changes: 3 additions & 2 deletions src/tty/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,7 @@ impl Renderer for PosixRenderer {
old_layout: &Layout,
new_layout: &Layout,
highlighter: Option<&dyn Highlighter>,
submit: bool,
) -> Result<()> {
use std::fmt::Write;
self.buffer.clear();
Expand All @@ -969,7 +970,7 @@ impl Renderer for PosixRenderer {
.push_str(&highlighter.highlight_prompt(prompt, default_prompt));
// display the input line
self.buffer
.push_str(&highlighter.highlight(line, line.pos()));
.push_str(&highlighter.highlight(line, line.pos(), submit));
} else {
// display the prompt
self.buffer.push_str(prompt);
Expand Down Expand Up @@ -1542,7 +1543,7 @@ mod test {
let new_layout = out.compute_layout(prompt_size, default_prompt, &line, None);
assert_eq!(Position { col: 1, row: 1 }, new_layout.cursor);
assert_eq!(new_layout.cursor, new_layout.end);
out.refresh_line(prompt, &line, None, &old_layout, &new_layout, None)
out.refresh_line(prompt, &line, None, &old_layout, &new_layout, None, true)
.unwrap();
#[rustfmt::skip]
assert_eq!(
Expand Down

0 comments on commit 2d5ddf6

Please sign in to comment.