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

Support for line continuation #819

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ fn main() -> Result<()> {
println!("No previous history.");
}
loop {
let readline = rl.readline(">> ");
let readline = rl.readline(">> ", "");
match readline {
Ok(line) => {
rl.add_history_entry(line.as_str());
Expand Down
2 changes: 1 addition & 1 deletion examples/custom_key_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ fn main() -> Result<()> {
);

loop {
let line = rl.readline("> ")?;
let line = rl.readline("> ", "")?;
rl.add_history_entry(line.as_str())?;
println!("Line: {line}");
}
Expand Down
2 changes: 1 addition & 1 deletion examples/diy_hints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ fn main() -> Result<()> {
rl.set_helper(Some(h));

loop {
let input = rl.readline("> ")?;
let input = rl.readline("> ", "")?;
println!("input: {input}");
}
}
2 changes: 1 addition & 1 deletion examples/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ fn main() -> rustyline::Result<()> {
loop {
let p = format!("{count}> ");
rl.helper_mut().expect("No helper").colored_prompt = format!("\x1b[1;32m{p}\x1b[0m");
let readline = rl.readline(&p);
let readline = rl.readline(&p, "");
match readline {
Ok(line) => {
rl.add_history_entry(line.as_str())?;
Expand Down
2 changes: 1 addition & 1 deletion examples/external_print.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ fn main() -> Result<()> {
});

loop {
let line = rl.readline("> ")?;
let line = rl.readline("> ", "")?;
rl.add_history_entry(line.as_str())?;
println!("Line: {line}");
}
Expand Down
2 changes: 1 addition & 1 deletion examples/input_multiline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ fn main() -> Result<()> {
EventHandler::Simple(Cmd::Newline),
);

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

Ok(())
Expand Down
2 changes: 1 addition & 1 deletion examples/input_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ fn main() -> Result<()> {
let mut rl = Editor::new()?;
rl.set_helper(Some(h));

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

loop {
let line = rl.readline("> ")?;
let line = rl.readline("> ", "")?;
println!("Num: {line}");
}
}
4 changes: 2 additions & 2 deletions examples/read_password.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ fn main() -> Result<()> {
let mut rl = Editor::new()?;
rl.set_helper(Some(h));

let username = rl.readline("Username:")?;
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 mut guard = rl.set_cursor_visibility(false)?;
let passwd = rl.readline("Password:")?;
let passwd = rl.readline("Password:", "")?;
guard.take();
println!("Secret: {passwd}");
Ok(())
Expand Down
2 changes: 1 addition & 1 deletion examples/sqlite_history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ fn main() -> Result<()> {
};
let mut rl: Editor<(), _> = Editor::with_history(config, history)?;
loop {
let line = rl.readline("> ")?;
let line = rl.readline("> ", "")?;
println!("{line}");
}
}
39 changes: 27 additions & 12 deletions src/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ pub struct State<'out, 'prompt, H: Helper> {
pub out: &'out mut <Terminal as Term>::Writer,
prompt: &'prompt str, // Prompt to display (rl_prompt)
prompt_size: Position, // Prompt Unicode/visible width and height
pub line: LineBuffer, // Edited line buffer
continuation: &'prompt str,
pub line: LineBuffer, // Edited line buffer
pub layout: Layout,
saved_line_for_history: LineBuffer, // Current edited line before history browsing
byte_buffer: [u8; 4],
Expand All @@ -48,14 +49,16 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
pub fn new(
out: &'out mut <Terminal as Term>::Writer,
prompt: &'prompt str,
continuation: &'prompt str,
helper: Option<&'out H>,
ctx: Context<'out>,
) -> Self {
let prompt_size = out.calculate_position(prompt, Position::default());
let prompt_size = out.calculate_position(prompt, Position::default(), continuation);
Self {
out,
prompt,
prompt_size,
continuation,
line: LineBuffer::with_capacity(MAX_LINE).can_growth(true),
layout: Layout::default(),
saved_line_for_history: LineBuffer::with_capacity(MAX_LINE).can_growth(true),
Expand Down Expand Up @@ -93,9 +96,11 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
if new_cols != old_cols
&& (self.layout.end.row > 0 || self.layout.end.col >= new_cols)
{
self.prompt_size = self
.out
.calculate_position(self.prompt, Position::default());
self.prompt_size = self.out.calculate_position(
self.prompt,
Position::default(),
self.continuation,
);
self.refresh_line()?;
}
continue;
Expand All @@ -122,9 +127,11 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {

pub fn move_cursor(&mut self, kind: CmdKind) -> Result<()> {
// calculate the desired position of the cursor
let cursor = self
.out
.calculate_position(&self.line[..self.line.pos()], self.prompt_size);
let cursor = self.out.calculate_position(
&self.line[..self.line.pos()],
self.prompt_size,
self.continuation,
);
if self.layout.cursor == cursor {
return Ok(());
}
Expand Down Expand Up @@ -172,9 +179,13 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
None
};

let new_layout = self
.out
.compute_layout(prompt_size, default_prompt, &self.line, info);
let new_layout = self.out.compute_layout(
prompt_size,
default_prompt,
&self.line,
info,
self.continuation,
);

debug!(target: "rustyline", "old layout: {:?}", self.layout);
debug!(target: "rustyline", "new layout: {:?}", new_layout);
Expand All @@ -185,6 +196,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
&self.layout,
&new_layout,
highlighter,
self.continuation,
)?;
self.layout = new_layout;

Expand Down Expand Up @@ -275,7 +287,9 @@ impl<H: Helper> Refresher for State<'_, '_, H> {
}

fn refresh_prompt_and_line(&mut self, prompt: &str) -> Result<()> {
let prompt_size = self.out.calculate_position(prompt, Position::default());
let prompt_size =
self.out
.calculate_position(prompt, Position::default(), self.continuation);
self.hint();
self.highlight_char(CmdKind::Other);
self.refresh(prompt, prompt_size, false, Info::Hint)
Expand Down Expand Up @@ -753,6 +767,7 @@ pub fn init_state<'out, H: Helper>(
out,
prompt: "",
prompt_size: Position::default(),
continuation: "",
line: LineBuffer::init(line, pos),
layout: Layout::default(),
saved_line_for_history: LineBuffer::with_capacity(100),
Expand Down
40 changes: 29 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//!
//! ```
//! let mut rl = rustyline::DefaultEditor::new()?;
//! let readline = rl.readline(">> ");
//! let readline = rl.readline(">> ", "");
//! match readline {
//! Ok(line) => println!("Line: {:?}", line),
//! Err(_) => println!("No input"),
Expand Down Expand Up @@ -634,8 +634,8 @@ impl<H: Helper, I: History> Editor<H, I> {
/// terminal.
/// Otherwise (e.g., if `stdin` is a pipe or the terminal is not supported),
/// it uses file-style interaction.
pub fn readline(&mut self, prompt: &str) -> Result<String> {
self.readline_with(prompt, None)
pub fn readline(&mut self, prompt: &str, continuation: &str) -> Result<String> {
self.readline_with(prompt, continuation, None)
}

/// This function behaves in the exact same manner as `readline`, except
Expand All @@ -645,11 +645,21 @@ impl<H: Helper, I: History> Editor<H, I> {
/// The string on the left of the tuple is what will appear to the left of
/// the cursor and the string on the right is what will appear to the
/// right of the cursor.
pub fn readline_with_initial(&mut self, prompt: &str, initial: (&str, &str)) -> Result<String> {
self.readline_with(prompt, Some(initial))
pub fn readline_with_initial(
&mut self,
prompt: &str,
continuation: &str,
initial: (&str, &str),
) -> Result<String> {
self.readline_with(prompt, continuation, Some(initial))
}

fn readline_with(&mut self, prompt: &str, initial: Option<(&str, &str)>) -> Result<String> {
fn readline_with(
&mut self,
prompt: &str,
continuation: &str,
initial: Option<(&str, &str)>,
) -> Result<String> {
if self.term.is_unsupported() {
debug!(target: "rustyline", "unsupported terminal");
// Write prompt and flush it to stdout
Expand All @@ -661,7 +671,8 @@ impl<H: Helper, I: History> Editor<H, I> {
} else if self.term.is_input_tty() {
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);
let user_input =
self.readline_edit(prompt, continuation, 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 All @@ -683,6 +694,7 @@ impl<H: Helper, I: History> Editor<H, I> {
fn readline_edit(
&mut self,
prompt: &str,
continuation: &str,
initial: Option<(&str, &str)>,
original_mode: &tty::Mode,
term_key_map: tty::KeyMap,
Expand All @@ -691,7 +703,7 @@ impl<H: Helper, I: History> Editor<H, I> {

self.kill_ring.reset(); // TODO recreate a new kill ring vs reset
let ctx = Context::new(&self.history);
let mut s = State::new(&mut stdout, prompt, self.helper.as_ref(), ctx);
let mut s = State::new(&mut stdout, prompt, continuation, self.helper.as_ref(), ctx);

let mut input_state = InputState::new(&self.config, &self.custom_bindings);

Expand Down Expand Up @@ -872,7 +884,7 @@ impl<H: Helper, I: History> Editor<H, I> {
/// Iterator ends at [EOF](ReadlineError::Eof).
/// ```
/// let mut rl = rustyline::DefaultEditor::new()?;
/// for readline in rl.iter("> ") {
/// for readline in rl.iter("> ", "") {
/// match readline {
/// Ok(line) => {
/// println!("Line: {}", line);
Expand All @@ -885,10 +897,15 @@ impl<H: Helper, I: History> Editor<H, I> {
/// }
/// # Ok::<(), rustyline::error::ReadlineError>(())
/// ```
pub fn iter<'a>(&'a mut self, prompt: &'a str) -> impl Iterator<Item = Result<String>> + 'a {
pub fn iter<'a>(
&'a mut self,
prompt: &'a str,
continuation: &'a str,
) -> impl Iterator<Item = Result<String>> + 'a {
Iter {
editor: self,
prompt,
continuation,
}
}

Expand Down Expand Up @@ -965,13 +982,14 @@ impl<H: Helper, I: History> fmt::Debug for Editor<H, I> {
struct Iter<'a, H: Helper, I: History> {
editor: &'a mut Editor<H, I>,
prompt: &'a str,
continuation: &'a str,
}

impl<H: Helper, I: History> Iterator for Iter<'_, H, I> {
type Item = Result<String>;

fn next(&mut self) -> Option<Result<String>> {
let readline = self.editor.readline(self.prompt);
let readline = self.editor.readline(self.prompt, self.continuation);
match readline {
Ok(l) => Some(Ok(l)),
Err(ReadlineError::Eof) => None,
Expand Down
8 changes: 4 additions & 4 deletions src/test/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ fn newline_key() {
fn eof_key() {
for mode in &[EditMode::Emacs, EditMode::Vi] {
let mut editor = init_editor(*mode, &[E::ctrl('D')]);
let err = editor.readline(">>");
let err = editor.readline(">>", "");
assert_matches!(err, Err(ReadlineError::Eof));
}
assert_line(
Expand All @@ -176,16 +176,16 @@ fn eof_key() {
fn interrupt_key() {
for mode in &[EditMode::Emacs, EditMode::Vi] {
let mut editor = init_editor(*mode, &[E::ctrl('C')]);
let err = editor.readline(">>");
let err = editor.readline(">>", "");
assert_matches!(err, Err(ReadlineError::Interrupted));

let mut editor = init_editor(*mode, &[E::ctrl('C')]);
let err = editor.readline_with_initial(">>", ("Hi", ""));
let err = editor.readline_with_initial(">>", "", ("Hi", ""));
assert_matches!(err, Err(ReadlineError::Interrupted));
if *mode == EditMode::Vi {
// vi command mode
let mut editor = init_editor(*mode, &[E::ESC, E::ctrl('C')]);
let err = editor.readline_with_initial(">>", ("Hi", ""));
let err = editor.readline_with_initial(">>", "", ("Hi", ""));
assert_matches!(err, Err(ReadlineError::Interrupted));
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ fn complete_symbol() {
// `expected_line`: line after enter key
fn assert_line(mode: EditMode, keys: &[KeyEvent], expected_line: &str) {
let mut editor = init_editor(mode, keys);
let actual_line = editor.readline(">>").unwrap();
let actual_line = editor.readline(">>", "").unwrap();
assert_eq!(expected_line, actual_line);
}

Expand All @@ -118,7 +118,7 @@ fn assert_line_with_initial(
expected_line: &str,
) {
let mut editor = init_editor(mode, keys);
let actual_line = editor.readline_with_initial(">>", initial).unwrap();
let actual_line = editor.readline_with_initial(">>", "", initial).unwrap();
assert_eq!(expected_line, actual_line);
}

Expand All @@ -127,7 +127,7 @@ fn assert_line_with_initial(
// `expected`: line status before enter key: strings before and after cursor
fn assert_cursor(mode: EditMode, initial: (&str, &str), keys: &[KeyEvent], expected: (&str, &str)) {
let mut editor = init_editor(mode, keys);
let actual_line = editor.readline_with_initial("", initial).unwrap();
let actual_line = editor.readline_with_initial("", "", initial).unwrap();
assert_eq!(expected.0.to_owned() + expected.1, actual_line);
assert_eq!(expected.0.len(), editor.term.cursor);
}
Expand All @@ -146,7 +146,7 @@ fn assert_history(
for entry in entries {
editor.history.add(entry).unwrap();
}
let actual_line = editor.readline(prompt).unwrap();
let actual_line = editor.readline(prompt, "").unwrap();
assert_eq!(expected.0.to_owned() + expected.1, actual_line);
if prompt.is_empty() {
assert_eq!(expected.0.len(), editor.term.cursor);
Expand Down
Loading