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

Hardcoded continuation prompt implementation with continuation_prompt feature #3

Draft
wants to merge 13 commits into
base: highlight
Choose a base branch
from
Draft
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
14 changes: 7 additions & 7 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ jobs:
run: cargo check --workspace --no-default-features
env:
RUSTFLAGS: "-D warnings"
- name: Check split-highlight feature
run: |
cargo check --workspace --all-targets --features 'split-highlight'
cargo check --workspace --all-targets --features 'split-highlight ansi-str'
cargo check --workspace --all-targets --features 'split-highlight anstyle'
cargo check --workspace --all-targets --features 'split-highlight ansi-str derive'
cargo check --workspace --all-targets --features 'split-highlight anstyle derive'
# - name: Check split-highlight feature
# run: |
# cargo check --workspace --all-targets --features 'split-highlight'
# cargo check --workspace --all-targets --features 'split-highlight ansi-str'
# cargo check --workspace --all-targets --features 'split-highlight anstyle'
# cargo check --workspace --all-targets --features 'split-highlight ansi-str derive'
# cargo check --workspace --all-targets --features 'split-highlight anstyle derive'

direct-minimal-versions:
name: Test min versions
Expand Down
11 changes: 7 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ members = ["rustyline-derive"]
# For convenience / compatibilty, you can highlight the whole input buffer, ansi-str helps to split
ansi-str = { version = "0.8.0", optional = true }
# ansi_str::Style is immutable so we use anstyle::Style instead
anstyle = { version = "1.0.8", optional = true }
anstyle = "1.0.8"
bitflags = "2.6"
cfg-if = "1.0"
# For file completion
Expand Down Expand Up @@ -71,7 +71,7 @@ with-sqlite-history = ["rusqlite"]
with-fuzzy = ["skim"]
case_insensitive_history_search = ["regex"]
# For continuation prompt, indentation, scrolling
split-highlight = []
# split-highlight = []

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

[package.metadata.docs.rs]
features = [
Expand All @@ -105,8 +108,8 @@ features = [
"with-dirs",
"with-file-history",
"with-fuzzy",
"split-highlight",
"anstyle",
# "split-highlight",
# "anstyle",
]
all-features = false
no-default-features = true
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ use rustyline::{DefaultEditor, Result};

fn main() -> Result<()> {
// `()` can be used when no completer is required
let mut rl = DefaultEditor::new()?;
// Use `new(())` for default Helper.
let mut rl = DefaultEditor::new(())?;
#[cfg(feature = "with-file-history")]
if rl.load_history("history.txt").is_err() {
println!("No previous history.");
Expand Down
80 changes: 80 additions & 0 deletions examples/continuation_prompt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use rustyline::highlight::{DisplayOnce, Highlighter, StyledBlocks};
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;
}
fn continuation_prompt_width<'b, 's: 'b, 'p: 'b>(&'s self, prompt: &'p str) -> usize {
3
}
}

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<'b, 's: 'b, 'l: 'b>(
&'s mut self,
line: &'l str,
_pos: usize,
) -> impl 'b + DisplayOnce {
use core::iter::once;
let mut lines = line.split('\n');
self.need_render = false;
let iter = once(((), lines.next().unwrap()))
.chain(lines.flat_map(|line| once(((), "\n.. ")).chain(once(((), line)))));
StyledBlocks::new(iter)
}
}

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<'b, 's: 'b, 'h: 'b>(&'s mut self, hint: &'h str) -> Cow<'b, 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
27 changes: 10 additions & 17 deletions examples/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::borrow::Cow::{self, Borrowed, Owned};

use rustyline::completion::FilenameCompleter;
use rustyline::error::ReadlineError;
use rustyline::highlight::{Highlighter, MatchingBracketHighlighter};
use rustyline::highlight::{DisplayOnce, Highlighter, MatchingBracketHighlighter};
use rustyline::hint::HistoryHinter;
use rustyline::validate::MatchingBracketValidator;
use rustyline::{Cmd, CompletionType, Config, EditMode, Editor, KeyEvent};
Expand All @@ -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,19 @@ impl Highlighter for MyHelper {
}
}

fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
fn highlight_hint<'b, 's: 'b, 'h: 'b>(&'s mut self, hint: &'h str) -> Cow<'b, 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> {
self.highlighter.highlight(line, pos)
}

#[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))]
fn highlight_line<'l>(
&self,
fn highlight<'b, 's: 'b, 'l: 'b>(
&'s mut self,
line: &'l str,
pos: usize,
) -> impl Iterator<Item = impl 'l + rustyline::highlight::StyledBlock> {
self.highlighter.highlight_line(line, pos)
) -> impl 'b + DisplayOnce {
self.highlighter.highlight(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 +66,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 +75,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
5 changes: 2 additions & 3 deletions examples/input_multiline.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use rustyline::highlight::MatchingBracketHighlighter;
use rustyline::highlight::{DisplayOnce, MatchingBracketHighlighter};
use rustyline::validate::MatchingBracketValidator;
use rustyline::{Cmd, Editor, EventHandler, KeyCode, KeyEvent, Modifiers, Result};
use rustyline::{Completer, Helper, Highlighter, Hinter, Validator};
Expand All @@ -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
32 changes: 8 additions & 24 deletions examples/read_password.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ 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<'b, 's: 'b, 'l: 'b>(
&'s mut self,
line: &'l str,
pos: usize,
) -> std::borrow::Cow<'b, str> {
use unicode_width::UnicodeWidthStr;
if self.masking {
std::borrow::Cow::Owned(" ".repeat(line.width()))
Expand All @@ -19,39 +22,20 @@ impl Highlighter for MaskingHighlighter {
}
}

#[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))]
fn highlight_line<'l>(
&self,
line: &'l str,
_pos: usize,
) -> impl Iterator<Item = impl 'l + rustyline::highlight::StyledBlock> {
use unicode_width::UnicodeWidthStr;
if self.masking {
vec![(
rustyline::highlight::AnsiStyle::default(),
" ".repeat(line.width()),
)]
.into_iter()
} else {
vec![(rustyline::highlight::AnsiStyle::default(), line.to_owned())].into_iter()
}
}

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
Loading