Skip to content

Commit

Permalink
Hardcoded continuation prompt implementation with `continuation_promp…
Browse files Browse the repository at this point in the history
…t` feature
  • Loading branch information
zao111222333 committed Sep 16, 2024
1 parent 7465673 commit e42bda3
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 14 deletions.
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ with-fuzzy = ["skim"]
case_insensitive_history_search = ["regex"]
# For continuation prompt, indentation, scrolling
split-highlight = []

continuation-prompt = ["split-highlight"]
[[example]]
name = "custom_key_bindings"
required-features = ["custom-bindings", "derive"]
Expand All @@ -86,6 +86,9 @@ required-features = ["custom-bindings", "derive"]
name = "input_multiline"
required-features = ["custom-bindings", "derive"]
[[example]]
name = "continuation_prompt"
required-features = ["custom-bindings", "derive", "continuation-prompt", "anstyle"]
[[example]]
name = "input_validation"
required-features = ["derive"]
[[example]]
Expand Down
43 changes: 43 additions & 0 deletions examples/continuation_prompt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use rustyline::highlight::{AnsiStyle, StyledBlock};
use rustyline::{Cmd, Editor, EventHandler, KeyCode, KeyEvent, Modifiers, Result};
use rustyline::{Completer, Helper, Hinter, Validator};
use std::borrow::Cow;
#[derive(Completer, Helper, Hinter, Validator)]
struct InputHelper;

impl rustyline::highlight::Highlighter for InputHelper {
fn continuation_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
prompt: &'p str,
default: bool,
) -> Cow<'b, str> {
let _ = default;
if let Some(pos) = prompt.rfind('\n') {
Cow::Owned(prompt[pos + 1..].replace('>', "."))
} else {
Cow::Owned(prompt.replace('>', "."))
}
}
fn highlight_line<'l>(
&self,
line: &'l str,
_pos: usize,
) -> impl Iterator<Item = impl Iterator<Item = impl 'l + StyledBlock>> {
line.split('\n')
.map(|l| core::iter::once((AnsiStyle::default(), l)))
}
}
fn main() -> Result<()> {
let h = InputHelper {};
let mut rl = Editor::new()?;
rl.set_helper(Some(h));
rl.bind_sequence(
KeyEvent(KeyCode::Char('s'), Modifiers::CTRL),
EventHandler::Simple(Cmd::Newline),
);

let input = rl.readline("continuation-prompt-example\n> ")?;
println!("Input: {input}");

Ok(())
}
115 changes: 109 additions & 6 deletions src/highlight.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,20 +132,72 @@ pub trait Highlighter {
Borrowed(line)
}

/// Takes the `prompt` and
/// returns the highlighted continuation-prompt (with ANSI color).
///
/// The continuation-prompt should be a single line, and
/// the same length as the last line of prompt
#[cfg(feature = "continuation-prompt")]
fn continuation_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
prompt: &'p str,
default: bool,
) -> Cow<'b, str> {
let _ = default;
if let Some(pos) = prompt.rfind('\n') {
Borrowed(&prompt[pos + 1..])
} else {
Borrowed(prompt)
}
}

/// Takes the currently edited `line` with the cursor `pos`ition and
/// returns the styled blocks.
#[cfg(all(
feature = "split-highlight",
feature = "continuation-prompt",
not(feature = "ansi-str")
))]
#[cfg_attr(
docsrs,
doc(cfg(all(
feature = "split-highlight",
feature = "continuation-prompt",
not(feature = "ansi-str")
)))
)]
fn highlight_line<'l>(
&self,
line: &'l str,
pos: usize,
) -> impl Iterator<Item = impl Iterator<Item = impl 'l + StyledBlock>> {
let _ = (line, pos);
line.split('\n')
.map(|l| core::iter::once((AnsiStyle::default(), l)))
}

/// Takes the currently edited `line` with the cursor `pos`ition and
/// returns the styled blocks.
#[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))]
#[cfg(all(
feature = "split-highlight",
not(feature = "continuation-prompt"),
not(feature = "ansi-str")
))]
#[cfg_attr(
docsrs,
doc(cfg(all(feature = "split-highlight", not(feature = "ansi-str"))))
doc(cfg(all(
feature = "split-highlight",
not(feature = "continuation-prompt"),
not(feature = "ansi-str")
)))
)]
fn highlight_line<'l>(
&self,
line: &'l str,
pos: usize,
) -> impl Iterator<Item = impl 'l + StyledBlock> {
let _ = (line, pos);
vec![(AnsiStyle::default(), line)].into_iter()
core::iter::once((AnsiStyle::default(), line))
}

/// Takes the `prompt` and
Expand Down Expand Up @@ -196,7 +248,11 @@ impl<'r, H: Highlighter> Highlighter for &'r H {
(**self).highlight(line, pos)
}

#[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))]
#[cfg(all(
feature = "split-highlight",
not(feature = "continuation-prompt"),
not(feature = "ansi-str")
))]
fn highlight_line<'l>(
&self,
line: &'l str,
Expand All @@ -205,6 +261,22 @@ impl<'r, H: Highlighter> Highlighter for &'r H {
(**self).highlight_line(line, pos)
}

/// Takes the currently edited `line` with the cursor `pos`ition and
/// returns the styled blocks.
#[cfg(all(
feature = "split-highlight",
feature = "continuation-prompt",
feature = "continuation-prompt",
not(feature = "ansi-str")
))]
fn highlight_line<'l>(
&self,
line: &'l str,
pos: usize,
) -> impl Iterator<Item = impl Iterator<Item = impl 'l + StyledBlock>> {
(**self).highlight_line(line, pos)
}

fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
prompt: &'p str,
Expand Down Expand Up @@ -276,7 +348,11 @@ impl Highlighter for MatchingBracketHighlighter {
Borrowed(line)
}

#[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))]
#[cfg(all(
feature = "split-highlight",
not(feature = "continuation-prompt"),
not(feature = "ansi-str")
))]
fn highlight_line<'l>(
&self,
line: &'l str,
Expand All @@ -298,6 +374,34 @@ impl Highlighter for MatchingBracketHighlighter {
vec![(AnsiStyle::default(), line)].into_iter()
}

#[cfg(all(
feature = "split-highlight",
feature = "continuation-prompt",
feature = "continuation-prompt",
not(feature = "ansi-str")
))]
fn highlight_line<'l>(
&self,
line: &'l str,
_pos: usize,
) -> impl Iterator<Item = impl Iterator<Item = impl 'l + StyledBlock>> {
if line.len() <= 1 {
return vec![vec![(AnsiStyle::default(), line)].into_iter()].into_iter();
}
if let Some((bracket, pos)) = self.bracket.get() {
if let Some((_, idx)) = find_matching_bracket(line, pos, bracket) {
return vec![vec![
(AnsiStyle::default(), &line[0..idx]),
(self.style, &line[idx..=idx]),
(AnsiStyle::default(), &line[idx + 1..]),
]
.into_iter()]
.into_iter();
}
}
vec![vec![(AnsiStyle::default(), line)].into_iter()].into_iter()
}

fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool {
if forced {
self.bracket.set(None);
Expand Down Expand Up @@ -442,7 +546,6 @@ mod tests {
assert_eq!(matching_bracket(b'('), b')');
assert_eq!(matching_bracket(b')'), b'(');
}

#[test]
pub fn is_open_bracket() {
use super::is_close_bracket;
Expand Down
35 changes: 31 additions & 4 deletions src/tty/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1003,12 +1003,31 @@ impl Renderer for PosixRenderer {
.push_str(&highlighter.highlight_prompt(prompt, default_prompt));
// display the input line
cfg_if::cfg_if! {
if #[cfg(not(feature = "split-highlight"))] {
self.buffer
.push_str(&highlighter.highlight(line, line.pos()));
} else if #[cfg(feature = "ansi-str")] {
if #[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] {
self.buffer
.push_str(&highlighter.highlight(line, line.pos()));
} else if #[cfg(feature = "continuation-prompt")] {
use crate::highlight::{Style, StyledBlock};
let mut iter = highlighter.highlight_line(line, line.pos());
if let Some(fisrt_line_blocks) = iter.next() {
for sb in fisrt_line_blocks {
let style = sb.style();
write!(self.buffer, "{}", style.start())?;
self.buffer.push_str(sb.text());
write!(self.buffer, "{}", style.end())?;
}
let continuation_prompt = highlighter.continuation_prompt(prompt, default_prompt);
while let Some(line_blocks) = iter.next() {
self.buffer.push('\n');
self.buffer.push_str(&continuation_prompt);
for sb in line_blocks {
let style = sb.style();
write!(self.buffer, "{}", style.start())?;
self.buffer.push_str(sb.text());
write!(self.buffer, "{}", style.end())?;
}
}
}
} else {
use crate::highlight::{Style, StyledBlock};
for sb in highlighter.highlight_line(line, line.pos()) {
Expand Down Expand Up @@ -1088,6 +1107,14 @@ impl Renderer for PosixRenderer {
pos.col = 0;
pos.row += 1;
}
cfg_if::cfg_if! {
if #[cfg(feature = "continuation-prompt")] {
// add continuation prompt offset
if pos.row > orig.row {
pos.col += orig.col;
}
}
}
pos
}

Expand Down
35 changes: 32 additions & 3 deletions src/tty/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,10 +450,31 @@ impl Renderer for ConsoleRenderer {
col = self.wrap_at_eol(&highlighter.highlight_prompt(prompt, default_prompt), col);
// append the input line
cfg_if::cfg_if! {
if #[cfg(not(feature = "split-highlight"))] {
col = self.wrap_at_eol(&highlighter.highlight(line, line.pos()), col);
} else if #[cfg(feature = "ansi-str")] {
if #[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] {
col = self.wrap_at_eol(&highlighter.highlight(line, line.pos()), col);
} else if #[cfg(feature = "continuation-prompt")] {
use std::fmt::Write;
use crate::highlight::{Style, StyledBlock};
let mut iter = highlighter.highlight_line(line, line.pos());
if let Some(fisrt_line_blocks) = iter.next() {
for sb in fisrt_line_blocks {
let style = sb.style();
write!(self.buffer, "{}", style.start())?;
col = self.wrap_at_eol(sb.text(), col);
write!(self.buffer, "{}", style.end())?;
}
let continuation_prompt = highlighter.continuation_prompt(prompt, default_prompt);
while let Some(line_blocks) = iter.next() {
col = self.wrap_at_eol("\n", col);
col = self.wrap_at_eol(&continuation_prompt, col);
for sb in line_blocks {
let style = sb.style();
write!(self.buffer, "{}", style.start())?;
col = self.wrap_at_eol(sb.text(), col);
write!(self.buffer, "{}", style.end())?;
}
}
}
} else {
use std::fmt::Write;
use crate::highlight::{Style, StyledBlock};
Expand Down Expand Up @@ -528,6 +549,14 @@ impl Renderer for ConsoleRenderer {
pos.col = 0;
pos.row += 1;
}
cfg_if::cfg_if! {
if #[cfg(feature = "continuation-prompt")] {
// add continuation prompt offset
if pos.row > orig.row {
pos.col += orig.col;
}
}
}
pos
}

Expand Down

0 comments on commit e42bda3

Please sign in to comment.