work on improving parameter expansion logic
This commit is contained in:
@@ -26,6 +26,7 @@ pub struct Highlighter {
|
||||
style_stack: Vec<StyleSet>,
|
||||
last_was_reset: bool,
|
||||
in_selection: bool,
|
||||
only_hl_visual: bool
|
||||
}
|
||||
|
||||
impl Highlighter {
|
||||
@@ -38,8 +39,12 @@ impl Highlighter {
|
||||
style_stack: Vec::new(),
|
||||
last_was_reset: true, // start as true so we don't emit a leading reset
|
||||
in_selection: false,
|
||||
only_hl_visual: false
|
||||
}
|
||||
}
|
||||
pub fn only_visual(&mut self, only_visual: bool) {
|
||||
self.only_hl_visual = only_visual;
|
||||
}
|
||||
|
||||
/// Loads raw input text and annotates it with syntax markers
|
||||
///
|
||||
@@ -61,6 +66,26 @@ impl Highlighter {
|
||||
out
|
||||
}
|
||||
|
||||
pub fn expand_control_chars(&mut self) {
|
||||
let mut expanded = String::new();
|
||||
let mut chars = self.input.chars().peekable();
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
'\n' | '\t' | '\r' => expanded.push(ch),
|
||||
c if c as u32 <= 0x1F => {
|
||||
let display = (c as u8 + b'@') as char;
|
||||
expanded.push_str("\x1b[7m^");
|
||||
expanded.push(display);
|
||||
expanded.push_str("\x1b[0m");
|
||||
}
|
||||
_ => expanded.push(ch),
|
||||
}
|
||||
}
|
||||
|
||||
self.input = expanded;
|
||||
}
|
||||
|
||||
/// Processes the annotated input and generates ANSI-styled output
|
||||
///
|
||||
/// Walks through the input character by character, interpreting markers and
|
||||
@@ -79,6 +104,9 @@ impl Highlighter {
|
||||
self.reapply_style();
|
||||
self.in_selection = false;
|
||||
}
|
||||
_ if self.only_hl_visual => {
|
||||
self.output.push(ch);
|
||||
}
|
||||
markers::STRING_DQ_END
|
||||
| markers::STRING_SQ_END
|
||||
| markers::VAR_SUB_END
|
||||
|
||||
@@ -2885,6 +2885,8 @@ impl LineBuf {
|
||||
Verb::Insert(string) => {
|
||||
self.push_str(&string);
|
||||
let graphemes = string.graphemes(true).count();
|
||||
log::debug!("Inserted string: {string:?}, graphemes: {graphemes}");
|
||||
log::debug!("buffer after insert: {:?}", self.buffer);
|
||||
self.cursor.add(graphemes);
|
||||
}
|
||||
Verb::Indent => {
|
||||
|
||||
@@ -140,6 +140,7 @@ pub struct Prompt {
|
||||
ps1_raw: String,
|
||||
psr_expanded: Option<String>,
|
||||
psr_raw: Option<String>,
|
||||
dirty: bool,
|
||||
}
|
||||
|
||||
impl Prompt {
|
||||
@@ -170,32 +171,54 @@ impl Prompt {
|
||||
ps1_raw,
|
||||
psr_expanded,
|
||||
psr_raw,
|
||||
dirty: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_ps1(&self) -> &str {
|
||||
pub fn get_ps1(&mut self) -> &str {
|
||||
if self.dirty {
|
||||
self.refresh_now();
|
||||
}
|
||||
&self.ps1_expanded
|
||||
}
|
||||
pub fn set_ps1(&mut self, ps1_raw: String) -> ShResult<()> {
|
||||
self.ps1_expanded = expand_prompt(&ps1_raw)?;
|
||||
self.ps1_raw = ps1_raw;
|
||||
self.dirty = true;
|
||||
Ok(())
|
||||
}
|
||||
pub fn set_psr(&mut self, psr_raw: String) -> ShResult<()> {
|
||||
self.psr_expanded = Some(expand_prompt(&psr_raw)?);
|
||||
self.psr_raw = Some(psr_raw);
|
||||
self.dirty = true;
|
||||
Ok(())
|
||||
}
|
||||
pub fn get_psr(&self) -> Option<&str> {
|
||||
pub fn get_psr(&mut self) -> Option<&str> {
|
||||
if self.dirty {
|
||||
self.refresh_now();
|
||||
}
|
||||
self.psr_expanded.as_deref()
|
||||
}
|
||||
|
||||
pub fn refresh(&mut self) -> ShResult<()> {
|
||||
self.ps1_expanded = expand_prompt(&self.ps1_raw)?;
|
||||
if let Some(psr_raw) = &self.psr_raw {
|
||||
self.psr_expanded = Some(expand_prompt(psr_raw)?);
|
||||
/// Mark the prompt as needing re-expansion on next access.
|
||||
pub fn invalidate(&mut self) {
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
fn refresh_now(&mut self) {
|
||||
let saved_status = state::get_status();
|
||||
if let Ok(expanded) = expand_prompt(&self.ps1_raw) {
|
||||
self.ps1_expanded = expanded;
|
||||
}
|
||||
Ok(())
|
||||
if let Some(psr_raw) = &self.psr_raw {
|
||||
if let Ok(expanded) = expand_prompt(psr_raw) {
|
||||
self.psr_expanded = Some(expanded);
|
||||
}
|
||||
}
|
||||
state::set_status(saved_status);
|
||||
self.dirty = false;
|
||||
}
|
||||
|
||||
pub fn refresh(&mut self) {
|
||||
self.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,6 +230,7 @@ impl Default for Prompt {
|
||||
ps1_raw: Self::DEFAULT_PS1.to_string(),
|
||||
psr_expanded: None,
|
||||
psr_raw: None,
|
||||
dirty: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -253,7 +277,7 @@ impl ShedVi {
|
||||
needs_redraw: true,
|
||||
};
|
||||
write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str(new.mode.report_mode().to_string()), VarFlags::NONE))?;
|
||||
new.prompt.refresh()?;
|
||||
new.prompt.refresh();
|
||||
new.writer.flush_write("\n")?; // ensure we start on a new line, in case the previous command didn't end with a newline
|
||||
new.print_line(false)?;
|
||||
Ok(new)
|
||||
@@ -296,7 +320,7 @@ impl ShedVi {
|
||||
pub fn reset(&mut self, full_redraw: bool) -> ShResult<()> {
|
||||
// Clear old display before resetting state — old_layout must survive
|
||||
// so print_line can call clear_rows with the full multi-line layout
|
||||
self.prompt = Prompt::new();
|
||||
self.prompt.refresh();
|
||||
self.editor = Default::default();
|
||||
self.swap_mode(&mut (Box::new(ViInsert::new()) as Box<dyn ViMode>));
|
||||
self.needs_redraw = true;
|
||||
@@ -598,14 +622,6 @@ impl ShedVi {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn update_layout(&mut self) {
|
||||
let text = self.line_text();
|
||||
let new = self.get_layout(&text);
|
||||
if let Some(old) = self.old_layout.as_mut() {
|
||||
*old = new;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_layout(&mut self, line: &str) -> Layout {
|
||||
let to_cursor = self.editor.slice_to_cursor().unwrap_or_default();
|
||||
let (cols, _) = get_win_size(*TTY_FILENO);
|
||||
@@ -654,8 +670,7 @@ impl ShedVi {
|
||||
|| (self.mode.pending_seq().unwrap(/* always Some on normal mode */).is_empty()
|
||||
&& matches!(event, KeyEvent(KeyCode::Char('l'), ModKeys::NONE)))
|
||||
}
|
||||
ModeReport::Ex => false,
|
||||
_ => unimplemented!(),
|
||||
ModeReport::Ex | ModeReport::Verbatim | ModeReport::Unknown => false,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
@@ -677,21 +692,19 @@ impl ShedVi {
|
||||
pub fn line_text(&mut self) -> String {
|
||||
let line = self.editor.to_string();
|
||||
let hint = self.editor.get_hint_text();
|
||||
if crate::state::read_shopts(|s| s.prompt.highlight) {
|
||||
self
|
||||
.highlighter
|
||||
.load_input(&line, self.editor.cursor_byte_pos());
|
||||
self.highlighter.highlight();
|
||||
let highlighted = self.highlighter.take();
|
||||
format!("{highlighted}{hint}")
|
||||
} else {
|
||||
format!("{line}{hint}")
|
||||
}
|
||||
let do_hl = state::read_shopts(|s| s.prompt.highlight);
|
||||
self.highlighter.only_visual(!do_hl);
|
||||
self.highlighter.load_input(&line, self.editor.cursor_byte_pos());
|
||||
self.highlighter.expand_control_chars();
|
||||
self.highlighter.highlight();
|
||||
let highlighted = self.highlighter.take();
|
||||
format!("{highlighted}{hint}")
|
||||
}
|
||||
|
||||
pub fn print_line(&mut self, final_draw: bool) -> ShResult<()> {
|
||||
let line = self.line_text();
|
||||
let mut new_layout = self.get_layout(&line);
|
||||
|
||||
let pending_seq = self.mode.pending_seq();
|
||||
let mut prompt_string_right = self.prompt.psr_expanded.clone();
|
||||
|
||||
@@ -710,7 +723,7 @@ impl ShedVi {
|
||||
.get_ps1()
|
||||
.lines()
|
||||
.next()
|
||||
.map(|l| Layout::calc_pos(self.writer.t_cols, l, Pos { col: 0, row: 0 }, 0))
|
||||
.map(|l| Layout::calc_pos(self.writer.t_cols, l, Pos { col: 0, row: 0 }, 0, false))
|
||||
.map(|p| p.col)
|
||||
.unwrap_or_default() as usize;
|
||||
let one_line = new_layout.end.row == 0;
|
||||
@@ -769,7 +782,7 @@ impl ShedVi {
|
||||
// Record where the PSR ends so clear_rows can account for wrapping
|
||||
// if the terminal shrinks.
|
||||
let psr_start = Pos { row: new_layout.end.row, col: to_col };
|
||||
new_layout.psr_end = Some(Layout::calc_pos(self.writer.t_cols, &psr, psr_start, 0));
|
||||
new_layout.psr_end = Some(Layout::calc_pos(self.writer.t_cols, &psr, psr_start, 0, false));
|
||||
}
|
||||
|
||||
if let ModeReport::Ex = self.mode.report_mode() {
|
||||
@@ -781,6 +794,7 @@ impl ShedVi {
|
||||
write!(buf, "{}", &self.mode.cursor_style()).unwrap();
|
||||
|
||||
self.writer.flush_write(&buf)?;
|
||||
|
||||
// Tell the completer the width of the prompt line above its \n so it can
|
||||
// account for wrapping when clearing after a resize.
|
||||
let preceding_width = if new_layout.psr_end.is_some() {
|
||||
@@ -808,7 +822,7 @@ impl ShedVi {
|
||||
std::mem::swap(&mut self.mode, mode);
|
||||
self.editor.set_cursor_clamp(self.mode.clamp_cursor());
|
||||
write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str(self.mode.report_mode().to_string()), VarFlags::NONE)).ok();
|
||||
self.prompt.refresh().ok();
|
||||
self.prompt.refresh();
|
||||
|
||||
let post_mode_change = read_logic(|l| l.get_autocmds(AutoCmdKind::PostModeChange));
|
||||
post_mode_change.exec();
|
||||
@@ -820,7 +834,7 @@ impl ShedVi {
|
||||
if cmd.is_mode_transition() {
|
||||
let count = cmd.verb_count();
|
||||
|
||||
let mut mode: Box<dyn ViMode> = if let ModeReport::Ex = self.mode.report_mode() && cmd.flags.contains(CmdFlags::EXIT_CUR_MODE) {
|
||||
let mut mode: Box<dyn ViMode> = if matches!(self.mode.report_mode(), ModeReport::Ex | ModeReport::Verbatim) && cmd.flags.contains(CmdFlags::EXIT_CUR_MODE) {
|
||||
if let Some(saved) = self.saved_mode.take() {
|
||||
saved
|
||||
} else {
|
||||
@@ -874,7 +888,7 @@ impl ShedVi {
|
||||
if matches!(self.mode.report_mode(), ModeReport::Ex | ModeReport::Verbatim) {
|
||||
self.saved_mode = Some(mode);
|
||||
write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str(self.mode.report_mode().to_string()), VarFlags::NONE))?;
|
||||
self.prompt.refresh()?;
|
||||
self.prompt.refresh();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -899,7 +913,7 @@ impl ShedVi {
|
||||
}
|
||||
|
||||
write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str(self.mode.report_mode().to_string()), VarFlags::NONE))?;
|
||||
self.prompt.refresh()?;
|
||||
self.prompt.refresh();
|
||||
|
||||
|
||||
return Ok(());
|
||||
@@ -1055,7 +1069,6 @@ pub fn annotate_input(input: &str) -> String {
|
||||
.filter(|tk| !matches!(tk.class, TkRule::SOI | TkRule::EOI | TkRule::Null))
|
||||
.collect();
|
||||
|
||||
log::debug!("Annotating input with tokens: {tokens:#?}");
|
||||
|
||||
for tk in tokens.into_iter().rev() {
|
||||
let insertions = annotate_token(tk);
|
||||
|
||||
@@ -736,9 +736,9 @@ impl Layout {
|
||||
}
|
||||
}
|
||||
pub fn from_parts(term_width: u16, prompt: &str, to_cursor: &str, to_end: &str) -> Self {
|
||||
let prompt_end = Self::calc_pos(term_width, prompt, Pos { col: 0, row: 0 }, 0);
|
||||
let cursor = Self::calc_pos(term_width, to_cursor, prompt_end, prompt_end.col);
|
||||
let end = Self::calc_pos(term_width, to_end, prompt_end, prompt_end.col);
|
||||
let prompt_end = Self::calc_pos(term_width, prompt, Pos { col: 0, row: 0 }, 0, false);
|
||||
let cursor = Self::calc_pos(term_width, to_cursor, prompt_end, prompt_end.col, true);
|
||||
let end = Self::calc_pos(term_width, to_end, prompt_end, prompt_end.col, false);
|
||||
Layout {
|
||||
prompt_end,
|
||||
cursor,
|
||||
@@ -748,7 +748,15 @@ impl Layout {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calc_pos(term_width: u16, s: &str, orig: Pos, left_margin: u16) -> Pos {
|
||||
fn is_ctl_char(gr: &str) -> bool {
|
||||
gr.len() > 0 &&
|
||||
gr.as_bytes()[0] <= 0x1F &&
|
||||
gr != "\n" &&
|
||||
gr != "\t" &&
|
||||
gr != "\r"
|
||||
}
|
||||
|
||||
pub fn calc_pos(term_width: u16, s: &str, orig: Pos, left_margin: u16, raw_calc: bool) -> Pos {
|
||||
const TAB_STOP: u16 = 8;
|
||||
let mut pos = orig;
|
||||
let mut esc_seq = 0;
|
||||
@@ -759,6 +767,8 @@ impl Layout {
|
||||
}
|
||||
let c_width = if c == "\t" {
|
||||
TAB_STOP - (pos.col % TAB_STOP)
|
||||
} else if raw_calc && Self::is_ctl_char(c) {
|
||||
2
|
||||
} else {
|
||||
width(c, &mut esc_seq)
|
||||
};
|
||||
@@ -1002,7 +1012,7 @@ impl LineWriter for TermWriter {
|
||||
self.buffer.push_str(prompt);
|
||||
let multiline = line.contains('\n');
|
||||
if multiline {
|
||||
let prompt_end = Layout::calc_pos(self.t_cols, prompt, Pos { col: 0, row: 0 }, 0);
|
||||
let prompt_end = Layout::calc_pos(self.t_cols, prompt, Pos { col: 0, row: 0 }, 0, false);
|
||||
let display_line = enumerate_lines(line, prompt_end.col as usize);
|
||||
self.buffer.push_str(&display_line);
|
||||
} else {
|
||||
|
||||
@@ -24,6 +24,7 @@ impl ViMode for ViVerbatim {
|
||||
fn handle_key(&mut self, key: E) -> Option<ViCmd> {
|
||||
match key {
|
||||
E(K::Verbatim(seq),_mods) => {
|
||||
log::debug!("Received verbatim key sequence: {:?}", seq);
|
||||
let cmd = ViCmd { register: RegisterName::default(),
|
||||
verb: Some(VerbCmd(1,Verb::Insert(seq.to_string()))),
|
||||
motion: None,
|
||||
@@ -61,6 +62,6 @@ impl ViMode for ViVerbatim {
|
||||
Some(To::End)
|
||||
}
|
||||
fn report_mode(&self) -> ModeReport {
|
||||
ModeReport::Insert
|
||||
ModeReport::Verbatim
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user