Skip to content
Merged
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
16 changes: 2 additions & 14 deletions examples/custom_key_bindings.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::borrow::Cow::{self, Borrowed, Owned};
use std::borrow::Cow::{self, Owned};

use rustyline::highlight::Highlighter;
use rustyline::hint::HistoryHinter;
Expand All @@ -13,18 +13,6 @@ use rustyline::{Completer, Helper, Hinter, Validator};
struct MyHelper(#[rustyline(Hinter)] HistoryHinter);

impl Highlighter for MyHelper {
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
prompt: &'p str,
default: bool,
) -> Cow<'b, str> {
if default {
Owned(format!("\x1b[1;32m{prompt}\x1b[m"))
} else {
Borrowed(prompt)
}
}

fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
Owned(format!("\x1b[1m{hint}\x1b[m"))
}
Expand Down Expand Up @@ -101,7 +89,7 @@ fn main() -> Result<()> {
);

loop {
let line = rl.readline("> ")?;
let line = rl.readline(&("> ", "\x1b[1;32m> \x1b[m"))?;
rl.add_history_entry(line.as_str())?;
println!("Line: {line}");
}
Expand Down
20 changes: 3 additions & 17 deletions examples/example.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::borrow::Cow::{self, Borrowed, Owned};
use std::borrow::Cow::{self, Owned};

use rustyline::completion::FilenameCompleter;
use rustyline::error::ReadlineError;
Expand All @@ -17,22 +17,9 @@ struct MyHelper {
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")
}
Expand All @@ -59,7 +46,6 @@ fn main() -> rustyline::Result<()> {
completer: FilenameCompleter::new(),
highlighter: MatchingBracketHighlighter::new(),
hinter: HistoryHinter::new(),
colored_prompt: "".to_owned(),
validator: MatchingBracketValidator::new(),
};
let mut rl = Editor::with_config(config)?;
Expand All @@ -72,8 +58,8 @@ 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");
let readline = rl.readline(&p);
let colored_prompt = format!("\x1b[1;32m{p}\x1b[0m");
let readline = rl.readline(&(p, colored_prompt));
match readline {
Ok(line) => {
rl.add_history_entry(line.as_str())?;
Expand Down
6 changes: 3 additions & 3 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ use crate::keymap::{Anchor, At, Cmd, Movement, Word};
use crate::keymap::{InputState, Refresher};
use crate::kill_ring::{KillRing, Mode};
use crate::line_buffer::WordAction;
use crate::{Helper, Result};
use crate::{Helper, Prompt, Result};

pub enum Status {
Proceed,
Submit,
}

pub fn execute<H: Helper>(
pub fn execute<H: Helper, P: Prompt + ?Sized>(
cmd: Cmd,
s: &mut State<'_, '_, H>,
s: &mut State<'_, '_, H, P>,
input_state: &InputState,
kill_ring: &mut KillRing,
config: &Config,
Expand Down
30 changes: 15 additions & 15 deletions src/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use log::debug;
use std::fmt;
use unicode_segmentation::UnicodeSegmentation;

use super::{Context, Helper, Result};
use super::{Context, Helper, Prompt, Result};
use crate::error::{ReadlineError, Signal};
use crate::highlight::{CmdKind, Highlighter};
use crate::hint::Hint;
Expand All @@ -21,9 +21,9 @@ use RefreshKind::All;

/// Represent the state during line editing.
/// Implement rendering.
pub struct State<'out, 'prompt, H: Helper> {
pub struct State<'out, 'prompt, H: Helper, P: Prompt + ?Sized> {
pub out: &'out mut <Terminal as Term>::Writer,
prompt: &'prompt str, // Prompt to display (rl_prompt)
prompt: &'prompt P, // Prompt to display (rl_prompt)
prompt_size: Position, // Prompt Unicode/visible width and height
pub line: LineBuffer, // Edited line buffer
pub layout: Layout,
Expand Down Expand Up @@ -51,14 +51,14 @@ pub enum RefreshKind {
All,
}

impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
impl<'out, 'prompt, H: Helper, P: Prompt + ?Sized> State<'out, 'prompt, H, P> {
pub fn new(
out: &'out mut <Terminal as Term>::Writer,
prompt: &'prompt str,
prompt: &'prompt P,
helper: Option<&'out H>,
ctx: Context<'out>,
) -> Self {
let prompt_size = out.calculate_position(prompt, Position::default());
let prompt_size = out.calculate_position(prompt.raw(), Position::default());
let gcm = out.grapheme_cluster_mode();
Self {
out,
Expand Down Expand Up @@ -110,7 +110,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
{
self.prompt_size = self
.out
.calculate_position(self.prompt, Position::default());
.calculate_position(self.prompt.raw(), Position::default());
self.refresh_line()?;
}
continue;
Expand Down Expand Up @@ -174,9 +174,9 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
self.refresh(self.prompt, self.prompt_size, true, kind, Info::Hint)
}

fn refresh(
fn refresh<Q: Prompt + ?Sized>(
&mut self,
prompt: &str,
prompt: &Q,
prompt_size: Position,
default_prompt: bool,
kind: RefreshKind,
Expand Down Expand Up @@ -279,13 +279,13 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
}
}

impl<H: Helper> Invoke for State<'_, '_, H> {
impl<H: Helper, P: Prompt + ?Sized> Invoke for State<'_, '_, H, P> {
fn input(&self) -> &str {
self.line.as_str()
}
}

impl<H: Helper> Refresher for State<'_, '_, H> {
impl<H: Helper, P: Prompt + ?Sized> Refresher for State<'_, '_, H, P> {
fn refresh_line(&mut self) -> Result<()> {
self.hint();
self.highlight_char(CmdKind::Other);
Expand Down Expand Up @@ -349,10 +349,10 @@ impl<H: Helper> Refresher for State<'_, '_, H> {
}
}

impl<H: Helper> fmt::Debug for State<'_, '_, H> {
impl<H: Helper, P: Prompt + ?Sized> fmt::Debug for State<'_, '_, H, P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("State")
.field("prompt", &self.prompt)
.field("prompt", &self.prompt.raw())
.field("prompt_size", &self.prompt_size)
.field("buf", &self.line)
.field("cols", &self.out.get_columns())
Expand All @@ -362,7 +362,7 @@ impl<H: Helper> fmt::Debug for State<'_, '_, H> {
}
}

impl<H: Helper> State<'_, '_, H> {
impl<H: Helper, P: Prompt + ?Sized> State<'_, '_, H, P> {
pub fn clear_screen(&mut self) -> Result<()> {
self.out.clear_screen()?;
self.layout.cursor = Position::default();
Expand Down Expand Up @@ -819,7 +819,7 @@ pub fn init_state<'out, H: Helper>(
pos: usize,
helper: Option<&'out H>,
history: &'out crate::history::DefaultHistory,
) -> State<'out, 'static, H> {
) -> State<'out, 'static, H, str> {
State {
out,
prompt: "",
Expand Down
41 changes: 23 additions & 18 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ mod keys;
mod kill_ring;
mod layout;
pub mod line_buffer;
mod prompt;
#[cfg(feature = "with-sqlite-history")]
pub mod sqlite_history;
mod tty;
Expand Down Expand Up @@ -66,6 +67,7 @@ pub use crate::keys::{KeyCode, KeyEvent, Modifiers};
use crate::kill_ring::KillRing;
pub use crate::layout::GraphemeClusterMode;
use crate::layout::Unit;
pub use crate::prompt::Prompt;
pub use crate::tty::ExternalPrinter;
pub use crate::undo::Changeset;
use crate::validate::Validator;
Expand All @@ -74,9 +76,9 @@ use crate::validate::Validator;
pub type Result<T> = result::Result<T, ReadlineError>;

/// Completes the line/word
fn complete_line<H: Helper>(
fn complete_line<H: Helper, P: Prompt + ?Sized>(
rdr: &mut <Terminal as Term>::Reader,
s: &mut State<'_, '_, H>,
s: &mut State<'_, '_, H, P>,
input_state: &mut InputState,
config: &Config,
) -> Result<Option<Cmd>> {
Expand Down Expand Up @@ -266,7 +268,7 @@ fn complete_line<H: Helper>(
}

/// Completes the current hint
fn complete_hint_line<H: Helper>(s: &mut State<'_, '_, H>) -> Result<()> {
fn complete_hint_line<H: Helper, P: Prompt + ?Sized>(s: &mut State<'_, '_, H, P>) -> Result<()> {
let Some(hint) = s.hint.as_ref() else {
return Ok(());
};
Expand All @@ -281,9 +283,9 @@ fn complete_hint_line<H: Helper>(s: &mut State<'_, '_, H>) -> Result<()> {
s.refresh_line()
}

fn page_completions<C: Candidate, H: Helper>(
fn page_completions<C: Candidate, H: Helper, P: Prompt + ?Sized>(
rdr: &mut <Terminal as Term>::Reader,
s: &mut State<'_, '_, H>,
s: &mut State<'_, '_, H, P>,
input_state: &mut InputState,
candidates: &[C],
) -> Result<Option<Cmd>> {
Expand Down Expand Up @@ -361,9 +363,9 @@ fn page_completions<C: Candidate, H: Helper>(
}

/// Incremental search
fn reverse_incremental_search<H: Helper, I: History>(
fn reverse_incremental_search<H: Helper, I: History, P: Prompt + ?Sized>(
rdr: &mut <Terminal as Term>::Reader,
s: &mut State<'_, '_, H>,
s: &mut State<'_, '_, H, P>,
input_state: &mut InputState,
history: &I,
) -> Result<Option<Cmd>> {
Expand Down Expand Up @@ -633,7 +635,7 @@ 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> {
pub fn readline<P: Prompt + ?Sized>(&mut self, prompt: &P) -> Result<String> {
self.readline_with(prompt, None)
}

Expand All @@ -644,21 +646,24 @@ 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> {
pub fn readline_with_initial<P: Prompt + ?Sized>(
&mut self,
prompt: &P,
initial: (&str, &str),
) -> Result<String> {
self.readline_with(prompt, Some(initial))
}

fn readline_with(&mut self, prompt: &str, initial: Option<(&str, &str)>) -> Result<String> {
#[cfg(windows)]
debug_assert!(
memchr::memmem::find(prompt.as_bytes(), b"\x1b[").is_none(),
"prompt should not be styled"
);
fn readline_with<P: Prompt + ?Sized>(
&mut self,
prompt: &P,
initial: Option<(&str, &str)>,
) -> Result<String> {
if self.term.is_unsupported() {
debug!(target: "rustyline", "unsupported terminal");
// Write prompt and flush it to stdout
let mut stdout = io::stdout();
stdout.write_all(prompt.as_bytes())?;
stdout.write_all(prompt.raw().as_bytes())?;
stdout.flush()?;

readline_direct(io::stdin().lock(), io::stderr(), &self.helper)
Expand All @@ -684,9 +689,9 @@ impl<H: Helper, I: History> Editor<H, I> {
/// Handles reading and editing the readline buffer.
/// It will also handle special inputs in an appropriate fashion
/// (e.g., C-c will exit readline)
fn readline_edit(
fn readline_edit<P: Prompt + ?Sized>(
&mut self,
prompt: &str,
prompt: &P,
initial: Option<(&str, &str)>,
original_mode: &tty::Mode,
term_key_map: tty::KeyMap,
Expand Down
37 changes: 37 additions & 0 deletions src/prompt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/// Provide two versions of the prompt:
/// - the `raw` version used when `stdout` is not a tty, or when the terminal is
/// not supported or in `NO_COLOR` mode
/// - the `styled` version
pub trait Prompt {
/// No style, no ANSI escape sequence
fn raw(&self) -> &str;
/// With style(s), ANSI escape sequences
///
/// Currently, the styled version *must* have the same display width as
/// the raw version.
///
/// By default, returns the raw string.
fn styled(&self) -> &str {
self.raw()
}
}

impl Prompt for str {
fn raw(&self) -> &str {
self
}
}
impl Prompt for String {
fn raw(&self) -> &str {
self.as_str()
}
}
impl<Raw: AsRef<str>, Styled: AsRef<str>> Prompt for (Raw, Styled) {
fn raw(&self) -> &str {
self.0.as_ref()
}

fn styled(&self) -> &str {
self.1.as_ref()
}
}
6 changes: 3 additions & 3 deletions src/tty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::highlight::Highlighter;
use crate::keys::KeyEvent;
use crate::layout::{GraphemeClusterMode, Layout, Position, Unit};
use crate::line_buffer::LineBuffer;
use crate::{Cmd, Result};
use crate::{Cmd, Prompt, Result};

/// Terminal state
pub trait RawMode: Sized {
Expand Down Expand Up @@ -49,9 +49,9 @@ pub trait Renderer {
fn move_cursor(&mut self, old: Position, new: Position) -> Result<()>;

/// Display `prompt`, line and cursor in terminal output
fn refresh_line(
fn refresh_line<P: Prompt + ?Sized>(
&mut self,
prompt: &str,
prompt: &P,
line: &LineBuffer,
hint: Option<&str>,
old_layout: Option<&Layout>, // used to clear old rows
Expand Down
Loading
Loading