From eeb267c46340d5d47f41cc2440f0b281f9ae9261 Mon Sep 17 00:00:00 2001 From: Jonas Maier Date: Fri, 22 May 2026 21:26:41 +0200 Subject: basic syntax highlighting --- src/lib.rs | 208 ++++++++++++++----------------------------------------------- 1 file changed, 47 insertions(+), 161 deletions(-) (limited to 'src/lib.rs') diff --git a/src/lib.rs b/src/lib.rs index 3931f5d..2a9a1ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,7 @@ pub mod date; pub mod defer; pub mod export_fun; pub mod history; -pub mod linebuf; +pub mod line; pub mod panic; pub mod parse; pub mod raw; @@ -33,15 +33,15 @@ pub mod reload; pub mod run; pub mod rw; pub mod serialization; +pub mod syntax_highlighting; pub mod wait; -use linebuf::LineBuf; use raw::*; use crate::completion::{PathCache, completion}; use crate::ctrlc::CtrlC; -use crate::cursor::{Direction, move_cursor}; use crate::history::HistoryEntry; +use crate::line::Line; use crate::parse::{Block, ExpString, Parse, PostExpansion}; macro_rules! print { @@ -93,7 +93,7 @@ impl PushAll for BString { pub struct Session { raw: Option, - line: LineBuf, + line: Line, history: Vec, prev_path: BString, builtins: HashMap, @@ -115,13 +115,15 @@ pub struct Session { /// n before end of history.len() /// 0 == not checking history history_visit: usize, + + highlighter: syntax_highlighting::Highlighter, } impl Session { pub fn new_noninteractive() -> Self { Self { raw: None, - line: LineBuf::new(), + line: Line::new(), history: Vec::new(), prev_path: b".".into(), builtins: HashMap::new(), @@ -136,6 +138,7 @@ impl Session { debug_keystrokes: false, loud: false, history_visit: 0, + highlighter: syntax_highlighting::Highlighter::new(), } } } @@ -212,17 +215,8 @@ impl Session { expanded } - fn clear_prompt(&mut self) { - cursor::move_cursor(Direction::Right, self.line.distance_from_right_end()); - for _ in 0..self.line.len() { - print!("\x08 \x08"); - } - io::stdout().lock().flush().unwrap(); - self.line.clear(); - } - fn prompt_clear(&mut self) { - self.clear_prompt(); + self.line.clear_prompt().unwrap(); self.history_visit = 0; } @@ -234,7 +228,7 @@ impl Session { } fn display_historic_entry(&mut self) { - self.clear_prompt(); + self.line.clear_prompt().unwrap(); let new = if self.history_visit == 0 { Vec::new() } else { @@ -244,7 +238,7 @@ impl Session { }; io::stdout().write_all(&new).unwrap(); io::stdout().flush().unwrap(); - self.line.set_content(new); + self.line.set_content(new).unwrap(); } fn history_up(&mut self) { @@ -261,150 +255,26 @@ impl Session { } } - fn type_bytes(&mut self, bs: &[u8]) { - for b in bs.iter() { - self.line.add(*b); - } - io::stdout().lock().write_all(&bs).unwrap(); - self.line.display_post(b""); - } - - fn type_byte(&mut self, b: u8) { - self.type_bytes(&[b]); - } - - fn del_left(&mut self) { - if self.line.del_left().is_some() { - cursor::move_cursor(Direction::Left, 1); - self.line.display_post(b" "); - } - } - - fn del_right(&mut self) { - self.line.del_right(); - self.line.display_post(b" "); - } - fn del_left_or_previous(&mut self) { - if self.line.is_empty() && !self.line.is_dirty() && !self.history.is_empty() { + if self.line.is_empty() && !self.line.had_contents() && !self.history.is_empty() { // take previous command for editing let cmd = self.history[self.history.len() - 1].cmd.clone(); - self.type_bytes(&cmd); + self.line.type_bytes(&cmd).unwrap(); } else { - self.del_left(); + self.line.del_left().unwrap(); } } fn prompt_pipe_previous(&mut self) { - if self.line.is_empty() && let Some(prev) = self.history.last() { + if self.line.is_empty() + && let Some(prev) = self.history.last() + { let mut cmd = prev.cmd.clone(); cmd.push_all(b" | "); - self.type_bytes(&cmd); + self.line.type_bytes(&cmd).unwrap(); } else { - self.type_byte(b'|'); - } - } - - fn move_to_begin(&mut self) { - cursor::move_cursor(Direction::Left, self.line.all_left()); - io::stdout().flush().unwrap(); - } - - fn move_to_end(&mut self) { - cursor::move_cursor(Direction::Right, self.line.all_right()); - io::stdout().flush().unwrap(); - } - - fn del_left_word(&mut self) { - let mut del = 0; - while let Some(x) = self.line.get_left() - && x == b' ' - { - self.line.del_left(); - del += 1; - } - while let Some(x) = self.line.get_left() - && x != b' ' - { - self.line.del_left(); - del += 1; - } - cursor::move_cursor(Direction::Left, del); - self.line.display_post(&vec![b' '; del]); - } - - fn del_right_word(&mut self) { - let mut del = 0; - while let Some(x) = self.line.get_right() - && x == b' ' - { - self.line.del_right(); - del += 1; - } - while let Some(x) = self.line.get_right() - && x != b' ' - { - self.line.del_right(); - del += 1; - } - self.line.display_post(&vec![b' '; del]); - } - - fn cursor_left(&mut self) { - if self.line.left() { - move_cursor(Direction::Left, 1); - io::stdout().lock().flush().unwrap(); - } - } - - fn cursor_right(&mut self) { - if self.line.right() { - move_cursor(Direction::Right, 1); - io::stdout().lock().flush().unwrap(); - } - } - - // move to next - fn cursor_left_word(&mut self) { - let mut i = 0; - - // find word - while let Some(b' ') = self.line.get_left() { - self.line.left(); - i += 1; - } - - // skip it - while let Some(x) = self.line.get_left() - && !x.is_ascii_whitespace() - { - self.line.left(); - i += 1; - } - - cursor::move_cursor(Direction::Left, i); - io::stdout().flush().unwrap(); - } - - fn cursor_right_word(&mut self) { - let mut i = 0; - - // find word - while let Some(b' ') = self.line.get_right() { - self.line.right(); - i += 1; + self.line.type_byte(b'|').unwrap(); } - - // skip it - while let Some(x) = self.line.get_right() - && !x.is_ascii_whitespace() - { - self.line.right(); - i += 1; - } - - cursor::move_cursor(Direction::Right, i); - io::stdout().flush().unwrap(); } fn complete(session: Arc>) { @@ -414,7 +284,7 @@ impl Session { let mut se = session.lock().unwrap(); - se.type_bytes(&comp.shared_prefix); + se.line.type_bytes(&comp.shared_prefix).unwrap(); if comp.suggestions.len() > 1 { print!("\r\n"); @@ -435,7 +305,7 @@ impl Session { let parsed = match parse::do_parse(&line) { Ok(p) => p, Err(_) => { - se.line.add(b'\n'); + se.line.type_byte(b'\n').unwrap(); print!("\r\n> "); return; } @@ -467,6 +337,27 @@ impl Session { r.disable(); } } + + /// do cleanup actions after a bunch of mutations. + fn cohere(&mut self) -> io::Result<()> { + if self.line.is_dirty() { + self.line.mark_clean(); + + // overwrite line in terminal with syntax-highlighted line. + if self.highlighter.enabled { + let buf = self.line.into_bytes(); + let mut parser = parse::Cursor::new(&buf, parse::ParseMode::Completion); + let _res = parse::Ast::parse(&mut parser); + let mut stdout = io::stdout().lock(); + cursor::fmove_cursor(cursor::Direction::Left, self.line.left_len(), &mut stdout)?; + self.highlighter + .pretty_print(&buf, parser.highlights, &mut stdout)?; + cursor::fmove_cursor(cursor::Direction::Left, self.line.right_len(), &mut stdout)?; + } + } + + Ok(()) + } } fn exec_rc_file(se: Arc>) { @@ -492,21 +383,14 @@ pub fn event_loop() { let se = Session { raw: Some(raw), - line: LineBuf::new(), - history: Vec::new(), + line: Line::new(), builtins: run::builtin_map(), prev_path: vec![b'.'], history_visit: 0, socket_running: None, - vars: run::Vars::default(), - funs: HashMap::new(), - aliases: run::Aliases::new(), path_cache: Default::default(), ctrlc: Default::default(), - debug_keystrokes: false, - loud: false, - ti_keybinds: HashMap::new(), - ascii_keybinds: HashMap::new(), + ..Session::new_noninteractive() }; let session = Arc::new(Mutex::new(se)); @@ -540,7 +424,7 @@ pub fn event_loop() { } match key { - ansi::KbInput::Key([x]) => se.type_byte(x), + ansi::KbInput::Key([x]) => se.line.type_byte(x).unwrap(), ansi::KbInput::Escape(escape) => { for terminfo_key in escape.keys.iter() { if let Some(cmd) = se.ti_keybinds.get(terminfo_key.as_bytes()) { @@ -554,6 +438,8 @@ pub fn event_loop() { } ansi::KbInput::InvalidEscape(_) => continue, } + + se.cohere().unwrap(); } session.lock().unwrap().raw_disable(); -- cgit v1.2.3