Highlighter now handles highlighting visual mode selections instead of LineBuf
This commit is contained in:
@@ -15,6 +15,8 @@ impl<T: Display> Styled for T {}
|
|||||||
pub enum Style {
|
pub enum Style {
|
||||||
// Undoes all styles
|
// Undoes all styles
|
||||||
Reset,
|
Reset,
|
||||||
|
ResetFg,
|
||||||
|
ResetBg,
|
||||||
// Foreground Colors
|
// Foreground Colors
|
||||||
Black,
|
Black,
|
||||||
Red,
|
Red,
|
||||||
@@ -66,6 +68,8 @@ impl Display for Style {
|
|||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Style::Reset => write!(f, "\x1b[0m"),
|
Style::Reset => write!(f, "\x1b[0m"),
|
||||||
|
Style::ResetFg => write!(f, "\x1b[39m"),
|
||||||
|
Style::ResetBg => write!(f, "\x1b[49m"),
|
||||||
|
|
||||||
// Foreground colors
|
// Foreground colors
|
||||||
Style::Black => write!(f, "\x1b[30m"),
|
Style::Black => write!(f, "\x1b[30m"),
|
||||||
@@ -127,6 +131,14 @@ impl StyleSet {
|
|||||||
Self { styles: vec![] }
|
Self { styles: vec![] }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn styles(&self) -> &[Style] {
|
||||||
|
&self.styles
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn styles_mut(&mut self) -> &mut Vec<Style> {
|
||||||
|
&mut self.styles
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_style(mut self, style: Style) -> Self {
|
pub fn add_style(mut self, style: Style) -> Self {
|
||||||
if !self.styles.contains(&style) {
|
if !self.styles.contains(&style) {
|
||||||
self.styles.push(style);
|
self.styles.push(style);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use std::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
libsh::term::{Style, StyleSet, Styled},
|
libsh::term::{Style, StyleSet, Styled},
|
||||||
prompt::readline::{annotate_input, markers},
|
prompt::readline::{annotate_input, markers::{self, is_marker}},
|
||||||
state::{read_logic, read_shopts},
|
state::{read_logic, read_shopts},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -23,6 +23,7 @@ pub struct Highlighter {
|
|||||||
linebuf_cursor_pos: usize,
|
linebuf_cursor_pos: usize,
|
||||||
style_stack: Vec<StyleSet>,
|
style_stack: Vec<StyleSet>,
|
||||||
last_was_reset: bool,
|
last_was_reset: bool,
|
||||||
|
in_selection: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Highlighter {
|
impl Highlighter {
|
||||||
@@ -34,6 +35,7 @@ impl Highlighter {
|
|||||||
linebuf_cursor_pos: 0,
|
linebuf_cursor_pos: 0,
|
||||||
style_stack: Vec::new(),
|
style_stack: Vec::new(),
|
||||||
last_was_reset: true, // start as true so we don't emit a leading reset
|
last_was_reset: true, // start as true so we don't emit a leading reset
|
||||||
|
in_selection: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,10 +45,21 @@ impl Highlighter {
|
|||||||
/// indicating token types and sub-token constructs (strings, variables, etc.)
|
/// indicating token types and sub-token constructs (strings, variables, etc.)
|
||||||
pub fn load_input(&mut self, input: &str, linebuf_cursor_pos: usize) {
|
pub fn load_input(&mut self, input: &str, linebuf_cursor_pos: usize) {
|
||||||
let input = annotate_input(input);
|
let input = annotate_input(input);
|
||||||
|
log::debug!("Annotated input: {:?}", input);
|
||||||
self.input = input;
|
self.input = input;
|
||||||
self.linebuf_cursor_pos = linebuf_cursor_pos;
|
self.linebuf_cursor_pos = linebuf_cursor_pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn strip_markers(str: &str) -> String {
|
||||||
|
let mut out = String::new();
|
||||||
|
for ch in str.chars() {
|
||||||
|
if !is_marker(ch) {
|
||||||
|
out.push(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
/// Processes the annotated input and generates ANSI-styled output
|
/// Processes the annotated input and generates ANSI-styled output
|
||||||
///
|
///
|
||||||
/// Walks through the input character by character, interpreting markers and
|
/// Walks through the input character by character, interpreting markers and
|
||||||
@@ -57,6 +70,14 @@ impl Highlighter {
|
|||||||
let mut input_chars = input.chars().peekable();
|
let mut input_chars = input.chars().peekable();
|
||||||
while let Some(ch) = input_chars.next() {
|
while let Some(ch) = input_chars.next() {
|
||||||
match ch {
|
match ch {
|
||||||
|
markers::VISUAL_MODE_START => {
|
||||||
|
self.emit_style(Style::BgWhite | Style::Black);
|
||||||
|
self.in_selection = true;
|
||||||
|
}
|
||||||
|
markers::VISUAL_MODE_END => {
|
||||||
|
self.reapply_style();
|
||||||
|
self.in_selection = false;
|
||||||
|
}
|
||||||
markers::STRING_DQ_END
|
markers::STRING_DQ_END
|
||||||
| markers::STRING_SQ_END
|
| markers::STRING_SQ_END
|
||||||
| markers::VAR_SUB_END
|
| markers::VAR_SUB_END
|
||||||
@@ -78,6 +99,7 @@ impl Highlighter {
|
|||||||
|
|
||||||
markers::REDIRECT | markers::OPERATOR => self.push_style(Style::Magenta | Style::Bold),
|
markers::REDIRECT | markers::OPERATOR => self.push_style(Style::Magenta | Style::Bold),
|
||||||
|
|
||||||
|
|
||||||
markers::ASSIGNMENT => {
|
markers::ASSIGNMENT => {
|
||||||
let mut var_name = String::new();
|
let mut var_name = String::new();
|
||||||
|
|
||||||
@@ -116,7 +138,7 @@ impl Highlighter {
|
|||||||
arg.push(ch);
|
arg.push(ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
let style = if Self::is_filename(&arg) {
|
let style = if Self::is_filename(&Self::strip_markers(&arg)) {
|
||||||
Style::White | Style::Underline
|
Style::White | Style::Underline
|
||||||
} else {
|
} else {
|
||||||
Style::White.into()
|
Style::White.into()
|
||||||
@@ -136,7 +158,7 @@ impl Highlighter {
|
|||||||
}
|
}
|
||||||
cmd_name.push(ch);
|
cmd_name.push(ch);
|
||||||
}
|
}
|
||||||
let style = if Self::is_valid(&cmd_name) {
|
let style = if Self::is_valid(&Self::strip_markers(&cmd_name)) {
|
||||||
Style::Green.into()
|
Style::Green.into()
|
||||||
} else {
|
} else {
|
||||||
Style::Red | Style::Bold
|
Style::Red | Style::Bold
|
||||||
@@ -346,7 +368,11 @@ impl Highlighter {
|
|||||||
///
|
///
|
||||||
/// Unconditionally appends the ANSI escape sequence for the given style
|
/// Unconditionally appends the ANSI escape sequence for the given style
|
||||||
/// and marks that we're no longer in a reset state.
|
/// and marks that we're no longer in a reset state.
|
||||||
fn emit_style(&mut self, style: &StyleSet) {
|
fn emit_style(&mut self, style: StyleSet) {
|
||||||
|
let mut style = style;
|
||||||
|
if !style.styles().contains(&Style::BgWhite) {
|
||||||
|
style = style.add_style(Style::BgBlack);
|
||||||
|
}
|
||||||
self.output.push_str(&style.to_string());
|
self.output.push_str(&style.to_string());
|
||||||
self.last_was_reset = false;
|
self.last_was_reset = false;
|
||||||
}
|
}
|
||||||
@@ -358,7 +384,9 @@ impl Highlighter {
|
|||||||
pub fn push_style(&mut self, style: impl Into<StyleSet>) {
|
pub fn push_style(&mut self, style: impl Into<StyleSet>) {
|
||||||
let set: StyleSet = style.into();
|
let set: StyleSet = style.into();
|
||||||
self.style_stack.push(set.clone());
|
self.style_stack.push(set.clone());
|
||||||
self.emit_style(&set);
|
if !self.in_selection {
|
||||||
|
self.emit_style(set.clone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pops a style from the stack and restores the previous style
|
/// Pops a style from the stack and restores the previous style
|
||||||
@@ -371,7 +399,7 @@ impl Highlighter {
|
|||||||
pub fn pop_style(&mut self) {
|
pub fn pop_style(&mut self) {
|
||||||
self.style_stack.pop();
|
self.style_stack.pop();
|
||||||
if let Some(style) = self.style_stack.last().cloned() {
|
if let Some(style) = self.style_stack.last().cloned() {
|
||||||
self.emit_style(&style);
|
self.emit_style(style);
|
||||||
} else {
|
} else {
|
||||||
self.emit_reset();
|
self.emit_reset();
|
||||||
}
|
}
|
||||||
@@ -383,8 +411,18 @@ impl Highlighter {
|
|||||||
/// the default terminal color between independent commands.
|
/// the default terminal color between independent commands.
|
||||||
pub fn clear_styles(&mut self) {
|
pub fn clear_styles(&mut self) {
|
||||||
self.style_stack.clear();
|
self.style_stack.clear();
|
||||||
|
if !self.in_selection {
|
||||||
self.emit_reset();
|
self.emit_reset();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reapply_style(&mut self) {
|
||||||
|
if let Some(style) = self.style_stack.last().cloned() {
|
||||||
|
self.emit_style(style);
|
||||||
|
} else {
|
||||||
|
self.emit_reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Simple marker-to-ANSI replacement (unused in favor of stack-based
|
/// Simple marker-to-ANSI replacement (unused in favor of stack-based
|
||||||
/// highlighting)
|
/// highlighting)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use crate::{
|
|||||||
error::ShResult,
|
error::ShResult,
|
||||||
term::{Style, Styled},
|
term::{Style, Styled},
|
||||||
},
|
},
|
||||||
prelude::*, prompt::readline::register::write_register,
|
prelude::*, prompt::readline::{markers, register::write_register},
|
||||||
};
|
};
|
||||||
|
|
||||||
const PUNCTUATION: [&str; 3] = ["?", "!", "."];
|
const PUNCTUATION: [&str; 3] = ["?", "!", "."];
|
||||||
@@ -2242,7 +2242,7 @@ impl LineBuf {
|
|||||||
|
|
||||||
if has_consumed_hint {
|
if has_consumed_hint {
|
||||||
let buf_end = if self.cursor.exclusive {
|
let buf_end = if self.cursor.exclusive {
|
||||||
self.cursor.ret_add(1)
|
self.cursor.ret_add_inclusive(1)
|
||||||
} else {
|
} else {
|
||||||
self.cursor.get()
|
self.cursor.get()
|
||||||
};
|
};
|
||||||
@@ -2873,17 +2873,22 @@ impl Display for LineBuf {
|
|||||||
let start_byte = self.read_idx_byte_pos(start);
|
let start_byte = self.read_idx_byte_pos(start);
|
||||||
let end_byte = self.read_idx_byte_pos(end);
|
let end_byte = self.read_idx_byte_pos(end);
|
||||||
|
|
||||||
|
if start_byte >= full_buf.len() || end_byte >= full_buf.len() {
|
||||||
|
log::warn!("Selection range '{:?}' is out of bounds for buffer of length {}, clearing selection", (start, end), full_buf.len());
|
||||||
|
return write!(f, "{}", full_buf);
|
||||||
|
}
|
||||||
|
|
||||||
match mode.anchor() {
|
match mode.anchor() {
|
||||||
SelectAnchor::Start => {
|
SelectAnchor::Start => {
|
||||||
let mut inclusive = start_byte..=end_byte;
|
let mut inclusive = start_byte..=end_byte;
|
||||||
if *inclusive.end() == full_buf.len() {
|
if *inclusive.end() == full_buf.len() {
|
||||||
inclusive = start_byte..=end_byte.saturating_sub(1);
|
inclusive = start_byte..=end_byte.saturating_sub(1);
|
||||||
}
|
}
|
||||||
let selected = full_buf[inclusive.clone()].styled(Style::BgWhite | Style::Black);
|
let selected = format!("{}{}{}", markers::VISUAL_MODE_START, &full_buf[inclusive.clone()], markers::VISUAL_MODE_END);
|
||||||
full_buf.replace_range(inclusive, &selected);
|
full_buf.replace_range(inclusive, &selected);
|
||||||
}
|
}
|
||||||
SelectAnchor::End => {
|
SelectAnchor::End => {
|
||||||
let selected = full_buf[start..end].styled(Style::BgWhite | Style::Black);
|
let selected = format!("{}{}{}", markers::VISUAL_MODE_START, &full_buf[start..end], markers::VISUAL_MODE_END);
|
||||||
full_buf.replace_range(start_byte..end_byte, &selected);
|
full_buf.replace_range(start_byte..end_byte, &selected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,10 @@ pub mod markers {
|
|||||||
pub const ESCAPE: Marker = '\u{fddd}';
|
pub const ESCAPE: Marker = '\u{fddd}';
|
||||||
pub const GLOB: Marker = '\u{fdde}';
|
pub const GLOB: Marker = '\u{fdde}';
|
||||||
|
|
||||||
|
// other
|
||||||
|
pub const VISUAL_MODE_START: Marker = '\u{fdea}';
|
||||||
|
pub const VISUAL_MODE_END: Marker = '\u{fdeb}';
|
||||||
|
|
||||||
pub const RESET: Marker = '\u{fde2}';
|
pub const RESET: Marker = '\u{fde2}';
|
||||||
|
|
||||||
pub const NULL: Marker = '\u{fdef}';
|
pub const NULL: Marker = '\u{fdef}';
|
||||||
@@ -77,8 +81,10 @@ pub mod markers {
|
|||||||
];
|
];
|
||||||
pub const SUB_TOKEN: [Marker; 6] = [VAR_SUB, CMD_SUB, PROC_SUB, STRING_DQ, STRING_SQ, GLOB];
|
pub const SUB_TOKEN: [Marker; 6] = [VAR_SUB, CMD_SUB, PROC_SUB, STRING_DQ, STRING_SQ, GLOB];
|
||||||
|
|
||||||
|
pub const MISC: [Marker; 3] = [ESCAPE, VISUAL_MODE_START, VISUAL_MODE_END];
|
||||||
|
|
||||||
pub fn is_marker(c: Marker) -> bool {
|
pub fn is_marker(c: Marker) -> bool {
|
||||||
TOKEN_LEVEL.contains(&c) || SUB_TOKEN.contains(&c) || END_MARKERS.contains(&c)
|
TOKEN_LEVEL.contains(&c) || SUB_TOKEN.contains(&c) || END_MARKERS.contains(&c) || MISC.contains(&c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
type Marker = char;
|
type Marker = char;
|
||||||
@@ -263,7 +269,8 @@ impl FernVi {
|
|||||||
if self.editor.buffer.is_empty() {
|
if self.editor.buffer.is_empty() {
|
||||||
return Ok(ReadlineEvent::Eof);
|
return Ok(ReadlineEvent::Eof);
|
||||||
} else {
|
} else {
|
||||||
self.editor.buffer.clear();
|
self.editor = LineBuf::new();
|
||||||
|
self.mode = Box::new(ViInsert::new());
|
||||||
self.needs_redraw = true;
|
self.needs_redraw = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -369,6 +376,7 @@ impl FernVi {
|
|||||||
self.highlighter.load_input(&line,self.editor.cursor_byte_pos());
|
self.highlighter.load_input(&line,self.editor.cursor_byte_pos());
|
||||||
self.highlighter.highlight();
|
self.highlighter.highlight();
|
||||||
let highlighted = self.highlighter.take();
|
let highlighted = self.highlighter.take();
|
||||||
|
log::info!("Highlighting line. highlighted: {:?}, hint: {:?}", highlighted, hint);
|
||||||
format!("{highlighted}{hint}")
|
format!("{highlighted}{hint}")
|
||||||
} else {
|
} else {
|
||||||
format!("{line}{hint}")
|
format!("{line}{hint}")
|
||||||
@@ -691,7 +699,9 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
|||||||
std::cmp::Ordering::Equal => {
|
std::cmp::Ordering::Equal => {
|
||||||
let priority = |m: Marker| -> u8 {
|
let priority = |m: Marker| -> u8 {
|
||||||
match m {
|
match m {
|
||||||
markers::RESET => 0,
|
markers::VISUAL_MODE_END
|
||||||
|
| markers::VISUAL_MODE_START
|
||||||
|
| markers::RESET => 0,
|
||||||
markers::VAR_SUB
|
markers::VAR_SUB
|
||||||
| markers::VAR_SUB_END
|
| markers::VAR_SUB_END
|
||||||
| markers::CMD_SUB
|
| markers::CMD_SUB
|
||||||
@@ -720,7 +730,9 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
|||||||
std::cmp::Ordering::Equal => {
|
std::cmp::Ordering::Equal => {
|
||||||
let priority = |m: Marker| -> u8 {
|
let priority = |m: Marker| -> u8 {
|
||||||
match m {
|
match m {
|
||||||
markers::RESET => 0,
|
markers::VISUAL_MODE_END
|
||||||
|
| markers::VISUAL_MODE_START
|
||||||
|
| markers::RESET => 0,
|
||||||
markers::VAR_SUB
|
markers::VAR_SUB
|
||||||
| markers::VAR_SUB_END
|
| markers::VAR_SUB_END
|
||||||
| markers::CMD_SUB
|
| markers::CMD_SUB
|
||||||
@@ -732,7 +744,8 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
|||||||
| markers::STRING_SQ
|
| markers::STRING_SQ
|
||||||
| markers::STRING_SQ_END
|
| markers::STRING_SQ_END
|
||||||
| markers::SUBSH_END => 2,
|
| markers::SUBSH_END => 2,
|
||||||
markers::ARG => 3, // Lowest priority - processed first, overridden by sub-tokens
|
|
||||||
|
| markers::ARG => 3, // Lowest priority - processed first, overridden by sub-tokens
|
||||||
_ => 1,
|
_ => 1,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user