Skip to content

Commit

Permalink
Add visual selection effect
Browse files Browse the repository at this point in the history
  • Loading branch information
Tastaturtaste committed Dec 28, 2023
1 parent b72d2e6 commit 5e1cabd
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 7 deletions.
12 changes: 12 additions & 0 deletions src/core_editor/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,18 @@ impl Editor {
fn reset_selection(&mut self) {
self.selection_anchor = None;
}

/// If a selection is active returns the selected range, otherwise None.
/// The range is guaranteed to be ascending.
pub fn get_selection(&self) -> Option<(usize, usize)> {
self.selection_anchor.map(|selection_anchor| {
if self.insertion_point() > selection_anchor {
(selection_anchor, self.insertion_point())
} else {
(self.insertion_point(), selection_anchor)
}
})
}
}

#[cfg(test)]
Expand Down
31 changes: 24 additions & 7 deletions src/engine.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::path::PathBuf;

use itertools::Itertools;
use nu_ansi_term::{Color, Style};

use crate::{enums::ReedlineRawEvent, CursorConfig};
#[cfg(feature = "bashisms")]
Expand Down Expand Up @@ -127,6 +128,9 @@ pub struct Reedline {
// Highlight the edit buffer
highlighter: Box<dyn Highlighter>,

// Style used for visual selection
visual_selection_style: Style,

// Showcase hints based on various strategies (history, language-completion, spellcheck, etc)
hinter: Option<Box<dyn Hinter>>,
hide_hints: bool,
Expand Down Expand Up @@ -183,6 +187,7 @@ impl Reedline {
let history = Box::<FileBackedHistory>::default();
let painter = Painter::new(std::io::BufWriter::new(std::io::stderr()));
let buffer_highlighter = Box::<ExampleHighlighter>::default();
let visual_selection_style = Style::new().on(Color::LightGray);
let completer = Box::<DefaultCompleter>::default();
let hinter = None;
let validator = None;
Expand All @@ -209,6 +214,7 @@ impl Reedline {
quick_completions: false,
partial_completions: false,
highlighter: buffer_highlighter,
visual_selection_style,
hinter,
hide_hints: false,
validator,
Expand Down Expand Up @@ -371,6 +377,13 @@ impl Reedline {
self
}

/// A builder that configures the style used for visual selection
#[must_use]
pub fn with_visual_selection_style(mut self, style: Style) -> Self {
self.visual_selection_style = style;
self
}

/// A builder which configures the history for your instance of the Reedline engine
/// # Example
/// ```rust,no_run
Expand Down Expand Up @@ -1638,14 +1651,18 @@ impl Reedline {
let cursor_position_in_buffer = self.editor.insertion_point();
let buffer_to_paint = self.editor.get_buffer();

let (before_cursor, after_cursor) = self
let mut styled_text = self
.highlighter
.highlight(buffer_to_paint, cursor_position_in_buffer)
.render_around_insertion_point(
cursor_position_in_buffer,
prompt,
self.use_ansi_coloring,
);
.highlight(buffer_to_paint, cursor_position_in_buffer);
if let Some((from, to)) = self.editor.get_selection() {
styled_text.style_range(from, to, self.visual_selection_style);
}

let (before_cursor, after_cursor) = styled_text.render_around_insertion_point(
cursor_position_in_buffer,
prompt,
self.use_ansi_coloring,
);

let hint: String = if self.hints_active() {
self.hinter.as_mut().map_or_else(String::new, |hinter| {
Expand Down
147 changes: 147 additions & 0 deletions src/painting/styled_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::Prompt;
use super::utils::strip_ansi;

/// A representation of a buffer with styling, used for doing syntax highlighting
#[derive(Clone)]
pub struct StyledText {
/// The component, styled parts of the text
pub buffer: Vec<(Style, String)>,
Expand All @@ -27,6 +28,67 @@ impl StyledText {
self.buffer.push(styled_string);
}

/// Style range with the provided style
pub fn style_range(&mut self, from: usize, to: usize, new_style: Style) {
let (from, to) = if from > to { (to, from) } else { (from, to) };
let mut current_idx = 0;
let mut pair_idx = 0;
while pair_idx < self.buffer.len() {
let pair = &mut self.buffer[pair_idx];
let end_idx = current_idx + pair.1.len();
enum Position {
Before,
In,
After,
}
let start_position = if current_idx < from {
Position::Before
} else if current_idx >= to {
Position::After
} else {
Position::In
};
let end_position = if end_idx < from {
Position::Before
} else if end_idx > to {
Position::After
} else {
Position::In
};
match (start_position, end_position) {
(Position::Before, Position::After) => {
let mut in_range = pair.1.split_off(from - current_idx);
let after_range = in_range.split_off(to - current_idx - from);
let in_range = (new_style, in_range);
let after_range = (pair.0, after_range);
self.buffer.insert(pair_idx + 1, in_range);
self.buffer.insert(pair_idx + 2, after_range);
break;
}
(Position::Before, Position::In) => {
let in_range = pair.1.split_off(from - current_idx);
pair_idx += 1; // Additional increment for the split pair, since the new insertion is already correctly styled and can be skipped next iteration
self.buffer.insert(pair_idx, (new_style, in_range));
}
(Position::In, Position::After) => {
let after_range = pair.1.split_off(to - current_idx);
let old_style = pair.0;
pair.0 = new_style;
if !after_range.is_empty() {
self.buffer.insert(pair_idx + 1, (old_style, after_range));
}
break;
}
(Position::In, Position::In) => pair.0 = new_style,

(Position::After, _) => break,
_ => (),
}
current_idx = end_idx;
pair_idx += 1;
}
}

/// Render the styled string. We use the insertion point to render around so that
/// we can properly write out the styled string to the screen and find the correct
/// place to put the cursor. This assumes a logic that prints the first part of the
Expand Down Expand Up @@ -109,3 +171,88 @@ fn render_as_string(
}
rendered
}

#[cfg(test)]
mod test {
use nu_ansi_term::{Color, Style};

use crate::StyledText;

fn get_styled_text_template() -> (super::StyledText, Style, Style) {
let before_style = Style::new().on(Color::Black);
let after_style = Style::new().on(Color::Red);
(
super::StyledText {
buffer: vec![
(before_style, "aaa".into()),
(before_style, "bbb".into()),
(before_style, "ccc".into()),
],
},
before_style,
after_style,
)
}
#[test]
fn style_range_partial_update_one_part() {
let (styled_text_template, before_style, after_style) = get_styled_text_template();
let mut styled_text = styled_text_template.clone();
styled_text.style_range(0, 1, after_style);
assert_eq!(styled_text.buffer[0], (after_style, "a".into()));
assert_eq!(styled_text.buffer[1], (before_style, "aa".into()));
assert_eq!(styled_text.buffer[2], (before_style, "bbb".into()));
assert_eq!(styled_text.buffer[3], (before_style, "ccc".into()));
}
#[test]
fn style_range_complete_update_one_part() {
let (styled_text_template, before_style, after_style) = get_styled_text_template();
let mut styled_text = styled_text_template.clone();
styled_text.style_range(0, 3, after_style);
assert_eq!(styled_text.buffer[0], (after_style, "aaa".into()));
assert_eq!(styled_text.buffer[1], (before_style, "bbb".into()));
assert_eq!(styled_text.buffer[2], (before_style, "ccc".into()));
assert_eq!(styled_text.buffer.len(), 3);
}
#[test]
fn style_range_update_over_boundary() {
let (styled_text_template, before_style, after_style) = get_styled_text_template();
let mut styled_text = styled_text_template;
styled_text.style_range(0, 5, after_style);
assert_eq!(styled_text.buffer[0], (after_style, "aaa".into()));
assert_eq!(styled_text.buffer[1], (after_style, "bb".into()));
assert_eq!(styled_text.buffer[2], (before_style, "b".into()));
assert_eq!(styled_text.buffer[3], (before_style, "ccc".into()));
}
#[test]
fn style_range_update_over_part() {
let (styled_text_template, before_style, after_style) = get_styled_text_template();
let mut styled_text = styled_text_template;
styled_text.style_range(1, 7, after_style);
assert_eq!(styled_text.buffer[0], (before_style, "a".into()));
assert_eq!(styled_text.buffer[1], (after_style, "aa".into()));
assert_eq!(styled_text.buffer[2], (after_style, "bbb".into()));
assert_eq!(styled_text.buffer[3], (after_style, "c".into()));
assert_eq!(styled_text.buffer[4], (before_style, "cc".into()));
}
#[test]
fn style_range_last_letter() {
let (_, before_style, after_style) = get_styled_text_template();
let mut styled_text = StyledText {
buffer: vec![(before_style, "asdf".into())],
};
styled_text.style_range(3, 4, after_style);
assert_eq!(styled_text.buffer[0], (before_style, "asd".into()));
assert_eq!(styled_text.buffer[1], (after_style, "f".into()));
}
#[test]
fn style_range_from_second_to_last() {
let (_, before_style, after_style) = get_styled_text_template();
let mut styled_text = StyledText {
buffer: vec![(before_style, "asdf".into())],
};
styled_text.style_range(2, 3, after_style);
assert_eq!(styled_text.buffer[0], (before_style, "as".into()));
assert_eq!(styled_text.buffer[1], (after_style, "d".into()));
assert_eq!(styled_text.buffer[2], (before_style, "f".into()));
}
}

0 comments on commit 5e1cabd

Please sign in to comment.