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
22 changes: 6 additions & 16 deletions examples/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::borrow::Cow::{self, Borrowed, Owned};
use rustyline::completion::{Completer, FilenameCompleter, Pair};
use rustyline::config::OutputStreamType;
use rustyline::error::ReadlineError;
use rustyline::highlight::{Highlighter, MatchingBracketHighlighter, PromptInfo};
use rustyline::highlight::{Highlighter, MatchingBracketHighlighter};
use rustyline::hint::{Hinter, HistoryHinter};
use rustyline::validate::{self, MatchingBracketValidator, Validator};
use rustyline::{Cmd, CompletionType, Config, Context, EditMode, Editor, KeyPress};
Expand All @@ -16,7 +16,6 @@ struct MyHelper {
validator: MatchingBracketValidator,
hinter: HistoryHinter,
colored_prompt: String,
continuation_prompt: String,
}

impl Completer for MyHelper {
Expand All @@ -42,23 +41,15 @@ impl Highlighter for MyHelper {
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
prompt: &'p str,
info: PromptInfo<'_>,
default: bool,
) -> Cow<'b, str> {
if info.default() {
if info.line_no() > 0 {
Borrowed(&self.continuation_prompt)
} else {
Borrowed(&self.colored_prompt)
}
if default {
Borrowed(&self.colored_prompt)
} else {
Borrowed(prompt)
}
}

fn has_continuation_prompt(&self) -> bool {
true
}

fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
Owned("\x1b[1m".to_owned() + hint + "\x1b[m")
}
Expand Down Expand Up @@ -99,8 +90,7 @@ fn main() -> rustyline::Result<()> {
completer: FilenameCompleter::new(),
highlighter: MatchingBracketHighlighter::new(),
hinter: HistoryHinter {},
colored_prompt: " 0> ".to_owned(),
continuation_prompt: "\x1b[1;32m...> \x1b[0m".to_owned(),
colored_prompt: "".to_owned(),
validator: MatchingBracketValidator::new(),
};
let mut rl = Editor::with_config(config);
Expand All @@ -112,7 +102,7 @@ fn main() -> rustyline::Result<()> {
}
let mut count = 1;
loop {
let p = format!("{:>3}> ", count);
let p = format!("{}> ", count);
rl.helper_mut().expect("No helper").colored_prompt = format!("\x1b[1;32m{}\x1b[0m", p);
let readline = rl.readline(&p);
match readline {
Expand Down
9 changes: 5 additions & 4 deletions src/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
// calculate the desired position of the cursor
let cursor = self
.out
.calculate_position(&self.line[..self.line.pos()], Position::default());
.calculate_position(&self.line[..self.line.pos()], self.prompt_size);
if self.layout.cursor == cursor {
return Ok(());
}
Expand All @@ -123,6 +123,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
self.out.move_cursor(self.layout.cursor, cursor)?;
self.layout.prompt_size = self.prompt_size;
self.layout.cursor = cursor;
debug_assert!(self.layout.prompt_size <= self.layout.cursor);
debug_assert!(self.layout.cursor <= self.layout.end);
}
Ok(())
Expand Down Expand Up @@ -152,9 +153,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {

// calculate the desired position of the cursor
let pos = self.line.pos();
let cursor = self
.out
.calculate_position(&self.line[..pos], Position::default());
let cursor = self.out.calculate_position(&self.line[..pos], prompt_size);
// calculate the position of the end of the input line
let mut end = if pos == self.line.len() {
cursor
Expand All @@ -171,6 +170,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
cursor,
end,
};
debug_assert!(new_layout.prompt_size <= new_layout.cursor);
debug_assert!(new_layout.cursor <= new_layout.end);

debug!(target: "rustyline", "old layout: {:?}", self.layout);
Expand Down Expand Up @@ -336,6 +336,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
// Avoid a full update of the line in the trivial case.
self.layout.cursor.col += width;
self.layout.end.col += width;
debug_assert!(self.layout.prompt_size <= self.layout.cursor);
debug_assert!(self.layout.cursor <= self.layout.end);
let bits = ch.encode_utf8(&mut self.byte_buffer);
let bits = bits.as_bytes();
Expand Down
104 changes: 4 additions & 100 deletions src/highlight.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,6 @@ use memchr::memchr;
use std::borrow::Cow::{self, Borrowed, Owned};
use std::cell::Cell;

pub struct PromptInfo<'a> {
pub(crate) default: bool,
pub(crate) offset: usize,
pub(crate) cursor: Option<usize>,
pub(crate) input: &'a str,
pub(crate) line: &'a str,
pub(crate) line_no: usize,
}

/// Syntax highlighter with [ANSI color](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters).
/// Rustyline will try to handle escape sequence for ANSI color on windows
/// when not supported natively (windows <10).
Expand All @@ -35,18 +26,11 @@ pub trait Highlighter {
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
prompt: &'p str,
info: PromptInfo<'_>,
default: bool,
) -> Cow<'b, str> {
let _ = info;
let _ = default;
Borrowed(prompt)
}

/// Returns `true` if prompt is rectangular rather than being present only
/// on the first line of input
fn has_continuation_prompt(&self) -> bool {
false
}

/// Takes the `hint` and
/// returns the highlighted version (with ANSI color).
fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
Expand Down Expand Up @@ -85,9 +69,9 @@ impl<'r, H: ?Sized + Highlighter> Highlighter for &'r H {
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
prompt: &'p str,
info: PromptInfo<'_>,
default: bool,
) -> Cow<'b, str> {
(**self).highlight_prompt(prompt, info)
(**self).highlight_prompt(prompt, default)
}

fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
Expand Down Expand Up @@ -246,86 +230,6 @@ fn is_close_bracket(bracket: u8) -> bool {
memchr(bracket, CLOSES).is_some()
}

pub(crate) fn split_highlight(src: &str, offset: usize) -> (Cow<'_, str>, Cow<'_, str>) {
let mut style_buffer = String::with_capacity(32);
let mut iter = src.char_indices();
let mut non_escape_idx = 0;
while let Some((idx, c)) = iter.next() {
if c == '\x1b' {
match iter.next() {
Some((_, '[')) => {}
_ => continue, // unknown escape, skip
}
while let Some((end_idx, c)) = iter.next() {
match c {
'm' => {
let slice = &src[idx..end_idx + 1];
if slice == "\x1b[0m" {
style_buffer.clear();
} else {
style_buffer.push_str(slice);
}
break;
}
';' | '0'..='9' => continue,
_ => break, // unknown escape, skip
}
}
continue;
}
if non_escape_idx >= offset {
if style_buffer.is_empty() {
return (src[..idx].into(), src[idx..].into());
} else {
let mut left = String::with_capacity(idx + 4);
left.push_str(&src[..idx]);
left.push_str("\x1b[0m");
let mut right = String::with_capacity(src.len() - idx + style_buffer.len());
right.push_str(&style_buffer);
right.push_str(&src[idx..]);
return (left.into(), right.into());
}
}
non_escape_idx += c.len_utf8();
}
(src.into(), "".into())
}

impl PromptInfo<'_> {
/// Returns true if this is the default prompt
pub fn default(&self) -> bool {
self.default
}

/// Returns the byte offset where prompt is shown in the initial text
///
/// This is a position right after the newline of the previous line
pub fn line_offset(&self) -> usize {
self.offset
}

/// Returns the byte position of the cursor relative to `line_offset` if
/// the cursor is in the current line
pub fn cursor(&self) -> Option<usize> {
self.cursor
}

/// Returns the zero-based line number of the current prompt line
pub fn line_no(&self) -> usize {
self.line_no
}

/// Returns the line contents shown after the prompt
pub fn line(&self) -> &str {
self.line
}

/// Returns the whole input (equal to `line` if input is the single line)
pub fn input(&self) -> &str {
self.input
}
}

#[cfg(test)]
mod tests {
#[test]
Expand Down
4 changes: 2 additions & 2 deletions src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ pub struct Layout {
/// Prompt Unicode/visible width and height
pub prompt_size: Position,
pub default_prompt: bool,
/// Cursor position (relative to the end of the prompt)
/// Cursor position (relative to the start of the prompt)
pub cursor: Position,
/// Number of rows used so far (from end of prompt to end of input)
/// Number of rows used so far (from start of prompt to end of input)
pub end: Position,
}
22 changes: 0 additions & 22 deletions src/test/highlight.rs

This file was deleted.

1 change: 0 additions & 1 deletion src/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ use crate::{Context, Editor, Helper, Result};

mod common;
mod emacs;
mod highlight;
mod history;
mod vi_cmd;
mod vi_insert;
Expand Down
96 changes: 0 additions & 96 deletions src/tty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,102 +156,6 @@ pub trait Term {
fn create_writer(&self) -> Self::Writer;
}

#[cfg(not(any(test, target_arch = "wasm32")))]
fn add_prompt_and_highlight(
buffer: &mut String,
highlighter: Option<&dyn Highlighter>,
line: &LineBuffer,
prompt: &str,
default_prompt: bool,
layout: &Layout,
cursor: &mut Position,
) {
use crate::highlight::{split_highlight, PromptInfo};

if let Some(highlighter) = highlighter {
if highlighter.has_continuation_prompt() {
if &line[..] == "" {
// line.lines() is an empty iterator for empty line so
// we need to treat it as a special case
let prompt = highlighter.highlight_prompt(
prompt,
PromptInfo {
default: default_prompt,
offset: 0,
cursor: Some(0),
input: "",
line: "",
line_no: 0,
},
);
buffer.push_str(&prompt);
} else {
let highlighted = highlighter.highlight(line, line.pos());
let lines = line.split('\n');
let mut highlighted_left = highlighted.to_string();
let mut offset = 0;
for (line_no, orig) in lines.enumerate() {
let (hl, tail) = split_highlight(&highlighted_left, orig.len() + 1);
let prompt = highlighter.highlight_prompt(
prompt,
PromptInfo {
default: default_prompt,
offset,
cursor: if line.pos() > offset && line.pos() < orig.len() {
Some(line.pos() - offset)
} else {
None
},
input: line,
line: orig,
line_no,
},
);
buffer.push_str(&prompt);
buffer.push_str(&hl);
highlighted_left = tail.to_string();
offset += orig.len() + 1;
}
}
cursor.col += layout.prompt_size.col;
} else {
// display the prompt
buffer.push_str(&highlighter.highlight_prompt(
prompt,
PromptInfo {
default: default_prompt,
offset: 0,
cursor: Some(line.pos()),
input: line,
line,
line_no: 0,
},
));
// display the input line
buffer.push_str(&highlighter.highlight(line, line.pos()));
// we have to generate our own newline on line wrap
if layout.end.col == 0 && layout.end.row > 0 && !buffer.ends_with('\n') {
buffer.push_str("\n");
}
if cursor.row == 0 {
cursor.col += layout.prompt_size.col;
}
}
} else {
// display the prompt
buffer.push_str(prompt);
// display the input line
buffer.push_str(line);
// we have to generate our own newline on line wrap
if layout.end.col == 0 && layout.end.row > 0 && !buffer.ends_with('\n') {
buffer.push_str("\n");
}
if cursor.row == 0 {
cursor.col += layout.prompt_size.col;
}
}
}

cfg_if::cfg_if! {
if #[cfg(any(test, target_arch = "wasm32"))] {
mod test;
Expand Down
Loading