diff --git a/src/edit.rs b/src/edit.rs index f78064bda7..758ceb04ab 100644 --- a/src/edit.rs +++ b/src/edit.rs @@ -14,15 +14,16 @@ use crate::keymap::{Anchor, At, CharSearch, Cmd, Movement, RepeatCount, Word}; use crate::keymap::{InputState, Invoke, Refresher}; use crate::layout::{Layout, Position}; use crate::line_buffer::{LineBuffer, WordAction, MAX_LINE}; +use crate::prompt::Prompt; use crate::tty::{Renderer, Term, Terminal}; use crate::undo::Changeset; use crate::validate::{ValidationContext, ValidationResult}; /// 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 ::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, @@ -41,13 +42,13 @@ enum Info<'m> { Msg(Option<&'m str>), } -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 ::Writer, - prompt: &'prompt str, + prompt: &'prompt P, helper: Option<&'out H>, ctx: Context<'out>, - ) -> State<'out, 'prompt, H> { + ) -> State<'out, 'prompt, H, P> { let prompt_size = out.calculate_position(prompt, Position::default()); State { out, @@ -135,7 +136,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { fn refresh( &mut self, - prompt: &str, + prompt: &P, prompt_size: Position, default_prompt: bool, info: Info<'_>, @@ -231,13 +232,13 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { } } -impl<'out, 'prompt, H: Helper> Invoke for State<'out, 'prompt, H> { +impl<'out, 'prompt, H: Helper, P: Prompt + ?Sized> Invoke for State<'out, 'prompt, H, P> { fn input(&self) -> &str { self.line.as_str() } } -impl<'out, 'prompt, H: Helper> Refresher for State<'out, 'prompt, H> { +impl<'out, 'prompt, H: Helper, P: Prompt + ?Sized> Refresher for State<'out, 'prompt, H, P> { fn refresh_line(&mut self) -> Result<()> { let prompt_size = self.prompt_size; self.hint(); @@ -280,10 +281,10 @@ impl<'out, 'prompt, H: Helper> Refresher for State<'out, 'prompt, H> { } } -impl<'out, 'prompt, H: Helper> fmt::Debug for State<'out, 'prompt, H> { +impl<'out, 'prompt, H: Helper, P: Prompt + ?Sized> fmt::Debug for State<'out, 'prompt, H, P> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("State") - .field("prompt", &self.prompt) + .field("prompt", &self.prompt.get_prompt(&())) .field("prompt_size", &self.prompt_size) .field("buf", &self.line) .field("cols", &self.out.get_columns()) @@ -293,7 +294,7 @@ impl<'out, 'prompt, H: Helper> fmt::Debug for State<'out, 'prompt, H> { } } -impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { +impl<'out, 'prompt, H: Helper, P: Prompt + ?Sized> State<'out, 'prompt, H, P> { pub fn clear_screen(&mut self) -> Result<()> { self.out.clear_screen()?; self.layout.cursor = Position::default(); @@ -661,7 +662,7 @@ pub fn init_state<'out, H: Helper>( pos: usize, helper: Option<&'out H>, history: &'out crate::history::History, -) -> State<'out, 'static, H> { +) -> State<'out, 'static, H, str> { State { out, prompt: "", diff --git a/src/lib.rs b/src/lib.rs index 5740236dd7..e04e038da9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,7 @@ mod keys; mod kill_ring; mod layout; pub mod line_buffer; +pub mod prompt; mod tty; mod undo; pub mod validate; @@ -58,15 +59,16 @@ use crate::keymap::{InputState, Refresher}; pub use crate::keys::KeyPress; use crate::kill_ring::{KillRing, Mode}; use crate::line_buffer::WordAction; +pub use crate::prompt::Prompt; use crate::validate::Validator; /// The error type for I/O and Linux Syscalls (Errno) pub type Result = result::Result; /// Completes the line/word -fn complete_line( +fn complete_line( rdr: &mut ::Reader, - s: &mut State<'_, '_, H>, + s: &mut State<'_, '_, H, P>, input_state: &mut InputState, config: &Config, ) -> Result> { @@ -231,7 +233,7 @@ fn complete_line( } /// Completes the current hint -fn complete_hint_line(s: &mut State<'_, '_, H>) -> Result<()> { +fn complete_hint_line(s: &mut State<'_, '_, H, P>) -> Result<()> { let hint = match s.hint.as_ref() { Some(hint) => hint, None => return Ok(()), @@ -244,9 +246,9 @@ fn complete_hint_line(s: &mut State<'_, '_, H>) -> Result<()> { Ok(()) } -fn page_completions( +fn page_completions( rdr: &mut ::Reader, - s: &mut State<'_, '_, H>, + s: &mut State<'_, '_, H, P>, input_state: &mut InputState, candidates: &[C], ) -> Result> { @@ -324,9 +326,9 @@ fn page_completions( } /// Incremental search -fn reverse_incremental_search( +fn reverse_incremental_search( rdr: &mut ::Reader, - s: &mut State<'_, '_, H>, + s: &mut State<'_, '_, H, P>, input_state: &mut InputState, history: &History, ) -> Result> { @@ -412,8 +414,8 @@ fn reverse_incremental_search( /// 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( - prompt: &str, +fn readline_edit( + prompt: &P, initial: Option<(&str, &str)>, editor: &mut Editor, original_mode: &tty::Mode, @@ -687,8 +689,8 @@ impl Drop for Guard<'_> { /// Readline method that will enable RAW mode, call the `readline_edit()` /// method and disable raw mode -fn readline_raw( - prompt: &str, +fn readline_raw( + prompt: &P, initial: Option<(&str, &str)>, editor: &mut Editor, ) -> Result { @@ -798,7 +800,7 @@ impl Editor { /// 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 { + pub fn readline(&mut self, prompt: &P) -> Result { self.readline_with(prompt, None) } @@ -813,12 +815,16 @@ impl Editor { self.readline_with(prompt, Some(initial)) } - fn readline_with(&mut self, prompt: &str, initial: Option<(&str, &str)>) -> Result { + fn readline_with( + &mut self, + prompt: &P, + initial: Option<(&str, &str)>, + ) -> Result { 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.get_prompt(&()).as_bytes())?; stdout.flush()?; readline_direct() diff --git a/src/prompt.rs b/src/prompt.rs new file mode 100644 index 0000000000..c020a87920 --- /dev/null +++ b/src/prompt.rs @@ -0,0 +1,46 @@ +//! Prompt and line continuations +use std::borrow::Cow::{self, Borrowed}; + +/// Prompt and line continuations +pub trait Prompt { + /// Returns text to be shown as the prompt at the first line. + /// Or text for the next lines of the input when + /// `Prompt::has_continuation()`. + fn get_prompt<'p>(&'p self, ctx: &dyn PromptContext) -> Cow<'p, str>; + /// Returns `true` when line continuations should be displayed. `false` by + /// default. + fn has_continuation(&self) -> bool { + false + } +} + +/* +We can cache Prompt::get_prompt result(s) if: + * if `input_mode` is kept untouched + * or (if `Prompt::has_continuation`), if `wrap_count`/`line_number` are kept untouched + +TODO +* Layout impacts + - compute_layout + - State.prompt_size + - ... +* Highlight impacts + - highlight_prompt + */ + +impl PromptContext for () {} + +pub trait PromptContext { + // Current line number. Computed/meaningful only when + // `Prompt::has_continuation()`. fn line_number(&self) -> usize; + // Soft wrap count. Computed/meaningful only when `Prompt::has_continuation()`. + //fn wrap_count(&self) -> usize; + // Vi input mode. `None` with default emacs editing mode. + //fn input_mode(&self) -> Option; +} + +impl Prompt for str { + fn get_prompt<'p>(&'p self, _: &dyn PromptContext) -> Cow<'p, str> { + Borrowed(self) + } +}