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 {
|
||||
// Undoes all styles
|
||||
Reset,
|
||||
ResetFg,
|
||||
ResetBg,
|
||||
// Foreground Colors
|
||||
Black,
|
||||
Red,
|
||||
@@ -66,6 +68,8 @@ impl Display for Style {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Style::Reset => write!(f, "\x1b[0m"),
|
||||
Style::ResetFg => write!(f, "\x1b[39m"),
|
||||
Style::ResetBg => write!(f, "\x1b[49m"),
|
||||
|
||||
// Foreground colors
|
||||
Style::Black => write!(f, "\x1b[30m"),
|
||||
@@ -127,6 +131,14 @@ impl StyleSet {
|
||||
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 {
|
||||
if !self.styles.contains(&style) {
|
||||
self.styles.push(style);
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::{
|
||||
|
||||
use crate::{
|
||||
libsh::term::{Style, StyleSet, Styled},
|
||||
prompt::readline::{annotate_input, markers},
|
||||
prompt::readline::{annotate_input, markers::{self, is_marker}},
|
||||
state::{read_logic, read_shopts},
|
||||
};
|
||||
|
||||
@@ -23,6 +23,7 @@ pub struct Highlighter {
|
||||
linebuf_cursor_pos: usize,
|
||||
style_stack: Vec<StyleSet>,
|
||||
last_was_reset: bool,
|
||||
in_selection: bool
|
||||
}
|
||||
|
||||
impl Highlighter {
|
||||
@@ -34,6 +35,7 @@ impl Highlighter {
|
||||
linebuf_cursor_pos: 0,
|
||||
style_stack: Vec::new(),
|
||||
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.)
|
||||
pub fn load_input(&mut self, input: &str, linebuf_cursor_pos: usize) {
|
||||
let input = annotate_input(input);
|
||||
log::debug!("Annotated input: {:?}", input);
|
||||
self.input = input;
|
||||
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
|
||||
///
|
||||
/// Walks through the input character by character, interpreting markers and
|
||||
@@ -57,6 +70,14 @@ impl Highlighter {
|
||||
let mut input_chars = input.chars().peekable();
|
||||
while let Some(ch) = input_chars.next() {
|
||||
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_SQ_END
|
||||
| markers::VAR_SUB_END
|
||||
@@ -78,6 +99,7 @@ impl Highlighter {
|
||||
|
||||
markers::REDIRECT | markers::OPERATOR => self.push_style(Style::Magenta | Style::Bold),
|
||||
|
||||
|
||||
markers::ASSIGNMENT => {
|
||||
let mut var_name = String::new();
|
||||
|
||||
@@ -116,7 +138,7 @@ impl Highlighter {
|
||||
arg.push(ch);
|
||||
}
|
||||
|
||||
let style = if Self::is_filename(&arg) {
|
||||
let style = if Self::is_filename(&Self::strip_markers(&arg)) {
|
||||
Style::White | Style::Underline
|
||||
} else {
|
||||
Style::White.into()
|
||||
@@ -136,7 +158,7 @@ impl Highlighter {
|
||||
}
|
||||
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()
|
||||
} else {
|
||||
Style::Red | Style::Bold
|
||||
@@ -346,7 +368,11 @@ impl Highlighter {
|
||||
///
|
||||
/// Unconditionally appends the ANSI escape sequence for the given style
|
||||
/// 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.last_was_reset = false;
|
||||
}
|
||||
@@ -358,7 +384,9 @@ impl Highlighter {
|
||||
pub fn push_style(&mut self, style: impl Into<StyleSet>) {
|
||||
let set: StyleSet = style.into();
|
||||
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
|
||||
@@ -371,7 +399,7 @@ impl Highlighter {
|
||||
pub fn pop_style(&mut self) {
|
||||
self.style_stack.pop();
|
||||
if let Some(style) = self.style_stack.last().cloned() {
|
||||
self.emit_style(&style);
|
||||
self.emit_style(style);
|
||||
} else {
|
||||
self.emit_reset();
|
||||
}
|
||||
@@ -383,8 +411,18 @@ impl Highlighter {
|
||||
/// the default terminal color between independent commands.
|
||||
pub fn clear_styles(&mut self) {
|
||||
self.style_stack.clear();
|
||||
if !self.in_selection {
|
||||
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
|
||||
/// highlighting)
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::{
|
||||
error::ShResult,
|
||||
term::{Style, Styled},
|
||||
},
|
||||
prelude::*, prompt::readline::register::write_register,
|
||||
prelude::*, prompt::readline::{markers, register::write_register},
|
||||
};
|
||||
|
||||
const PUNCTUATION: [&str; 3] = ["?", "!", "."];
|
||||
@@ -2242,7 +2242,7 @@ impl LineBuf {
|
||||
|
||||
if has_consumed_hint {
|
||||
let buf_end = if self.cursor.exclusive {
|
||||
self.cursor.ret_add(1)
|
||||
self.cursor.ret_add_inclusive(1)
|
||||
} else {
|
||||
self.cursor.get()
|
||||
};
|
||||
@@ -2873,17 +2873,22 @@ impl Display for LineBuf {
|
||||
let start_byte = self.read_idx_byte_pos(start);
|
||||
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() {
|
||||
SelectAnchor::Start => {
|
||||
let mut inclusive = start_byte..=end_byte;
|
||||
if *inclusive.end() == full_buf.len() {
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,10 @@ pub mod markers {
|
||||
pub const ESCAPE: Marker = '\u{fddd}';
|
||||
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 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 MISC: [Marker; 3] = [ESCAPE, VISUAL_MODE_START, VISUAL_MODE_END];
|
||||
|
||||
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;
|
||||
@@ -263,7 +269,8 @@ impl FernVi {
|
||||
if self.editor.buffer.is_empty() {
|
||||
return Ok(ReadlineEvent::Eof);
|
||||
} else {
|
||||
self.editor.buffer.clear();
|
||||
self.editor = LineBuf::new();
|
||||
self.mode = Box::new(ViInsert::new());
|
||||
self.needs_redraw = true;
|
||||
continue;
|
||||
}
|
||||
@@ -369,6 +376,7 @@ impl FernVi {
|
||||
self.highlighter.load_input(&line,self.editor.cursor_byte_pos());
|
||||
self.highlighter.highlight();
|
||||
let highlighted = self.highlighter.take();
|
||||
log::info!("Highlighting line. highlighted: {:?}, hint: {:?}", highlighted, hint);
|
||||
format!("{highlighted}{hint}")
|
||||
} else {
|
||||
format!("{line}{hint}")
|
||||
@@ -691,7 +699,9 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
||||
std::cmp::Ordering::Equal => {
|
||||
let priority = |m: Marker| -> u8 {
|
||||
match m {
|
||||
markers::RESET => 0,
|
||||
markers::VISUAL_MODE_END
|
||||
| markers::VISUAL_MODE_START
|
||||
| markers::RESET => 0,
|
||||
markers::VAR_SUB
|
||||
| markers::VAR_SUB_END
|
||||
| markers::CMD_SUB
|
||||
@@ -720,7 +730,9 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
||||
std::cmp::Ordering::Equal => {
|
||||
let priority = |m: Marker| -> u8 {
|
||||
match m {
|
||||
markers::RESET => 0,
|
||||
markers::VISUAL_MODE_END
|
||||
| markers::VISUAL_MODE_START
|
||||
| markers::RESET => 0,
|
||||
markers::VAR_SUB
|
||||
| markers::VAR_SUB_END
|
||||
| markers::CMD_SUB
|
||||
@@ -732,7 +744,8 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
||||
| markers::STRING_SQ
|
||||
| markers::STRING_SQ_END
|
||||
| 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,
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user