From 01a198b8ad42680c9333039c75317d1787120c78 Mon Sep 17 00:00:00 2001 From: Jonas Maier <> Date: Sun, 24 May 2026 18:02:04 +0200 Subject: better multiline handling --- src/ansi/mod.rs | 54 ++++++++++++++++++++++++++++++---------------- src/cursor.rs | 20 ++++++++++++++++- src/lib.rs | 28 ++++++------------------ src/line/mod.rs | 35 +++++++++++++++++++++++++++++- src/syntax_highlighting.rs | 6 +++++- 5 files changed, 99 insertions(+), 44 deletions(-) (limited to 'src') diff --git a/src/ansi/mod.rs b/src/ansi/mod.rs index 96fb87a..4265b45 100644 --- a/src/ansi/mod.rs +++ b/src/ansi/mod.rs @@ -67,39 +67,55 @@ impl TerminalInput { self.buf.pop_front() } - fn read_all_input(&mut self) { - while { - let buf_len = self.buf.len(); - self.read_internal(); - self.buf.len() != buf_len - } {} - } - pub fn query_cursor_position(&mut self) -> Option { - self.read_all_input(); - // query to request cursor position let mut stdout = std::io::stdout().lock(); stdout.write_all(b"\x1b[6n").ok()?; stdout.flush().ok()?; drop(stdout); - loop { + self.read_internal(); + + while self + .buf + .back() + .map(|b| !b.as_bytes().ends_with(b"R")) + .unwrap_or(true) + { let buf_len = self.buf.len(); self.read_internal(); // no new input, give up if self.buf.len() <= buf_len { - return None; + break; } + } - // buf.len() > buf_len >= 0, unwrap is safe. - let input = self.buf.back().unwrap(); - if let Some(pos) = try_parse_cursor_position_message(input.as_bytes()) { - self.buf.pop_back(); - return Some(pos); - } + if self.buf.is_empty() { + return None; + } + + if !self.buf.back().unwrap().as_bytes().ends_with(b"R") { + return None; } + + let i = self + .buf + .iter() + .enumerate() + .rev() + .find(|(_, x)| x.as_bytes().starts_with(b"\x1b")) + .map(|x| x.0)?; + + let input = self.buf.iter().skip(i).fold(BString::new(), |mut acc, x| { + acc.push_all(x.as_bytes()); + acc + }); + + let pos = try_parse_cursor_position_message(&input)?; + self.buf.truncate(i); + + Some(pos) } } @@ -193,7 +209,7 @@ pub struct Escape<'a> { use terminfo_lean::parse::Terminfo; -use crate::cursor::CursorPos; +use crate::{BString, PushAll, cursor::CursorPos}; static TERMINFO: RwLock>> = RwLock::new(None); static ESCAPE_TRIE: RwLock> = RwLock::new(None); diff --git a/src/cursor.rs b/src/cursor.rs index 1ec23d5..959b15f 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -1,4 +1,4 @@ -use std::io::Write; +use std::io::{self, Write}; #[derive(Debug, Clone, Copy)] pub enum Direction { @@ -41,3 +41,21 @@ pub struct CursorPos { pub row: usize, pub col: usize, } + +impl Default for CursorPos { + fn default() -> Self { + Self { row: 1, col: 1 } + } +} + +impl CursorPos { + pub fn f_go_to(&self, stdout: &mut dyn Write) -> std::io::Result<()> { + write!(stdout, "\x1b[{};{}H", self.row, self.col) + } + + pub fn go_to(&self) { + let mut stdout = io::stdout().lock(); + self.f_go_to(&mut stdout).unwrap(); + stdout.flush().unwrap(); + } +} diff --git a/src/lib.rs b/src/lib.rs index 849ddea..93d9cb8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -197,8 +197,11 @@ impl Session { let prompt = this.lock().unwrap().prompt.prompt().to_vec(); io::stdout().write_all(&prompt).unwrap(); let mut this = this.lock().unwrap(); - this.line.display_pre(); - this.line.display_post(b""); + if let Some(ti) = this.terminal_input.as_mut() + && let Some(pos) = ti.query_cursor_position() + { + this.line.set_begin_of_line(pos); + } this.line.mark_dirty(); this.cohere().unwrap(); } @@ -212,8 +215,6 @@ impl Session { .cmd .clone() }; - io::stdout().write_all(&new).unwrap(); - io::stdout().flush().unwrap(); self.line.set_content(new).unwrap(); } @@ -299,7 +300,6 @@ impl Session { Ok(p) => p, Err(_) => { se.line.type_byte(b'\n').unwrap(); - print!("\r\n> "); return; } }; @@ -340,23 +340,7 @@ impl Session { /// 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)?; - stdout.flush()?; - } - } - + self.line.highlight_syntax(&mut self.highlighter)?; Ok(()) } } diff --git a/src/line/mod.rs b/src/line/mod.rs index 75a4508..c6c2f34 100644 --- a/src/line/mod.rs +++ b/src/line/mod.rs @@ -2,12 +2,16 @@ mod buf; use std::io::{self, Write}; -use crate::cursor::{self, Direction}; +use crate::{ + cursor::{self, CursorPos, Direction}, + syntax_highlighting::Highlighter, +}; pub use buf::LineBuf; pub struct Line { buf: LineBuf, dirty: bool, + line_start: CursorPos, } impl std::ops::Deref for Line { @@ -23,9 +27,38 @@ impl Line { Self { buf: LineBuf::new(), dirty: false, + line_start: CursorPos::default(), } } + pub fn set_begin_of_line(&mut self, pos: CursorPos) { + self.line_start = pos; + } + + pub fn highlight_syntax(&mut self, h: &mut Highlighter) -> std::io::Result<()> { + if !self.is_dirty() { + return Ok(()); + } + + self.mark_clean(); + + if !h.enabled { + return Ok(()); + } + + use crate::parse::{self, Parse}; + + let buf = self.into_bytes(); + let mut parser = parse::Cursor::new(&buf, parse::ParseMode::Completion); + let _ = parse::Ast::parse(&mut parser); + let mut stdout = io::stdout().lock(); + self.line_start.f_go_to(&mut stdout)?; + h.pretty_print(&buf, parser.highlights, &mut stdout)?; + stdout.flush()?; + + Ok(()) + } + pub fn mark_clean(&mut self) { self.dirty = false; } diff --git a/src/syntax_highlighting.rs b/src/syntax_highlighting.rs index 1a6e626..043509a 100644 --- a/src/syntax_highlighting.rs +++ b/src/syntax_highlighting.rs @@ -124,7 +124,11 @@ impl Highlighter { } } - stdout.write_all(&[x])?; + if x == b'\n' { + stdout.write_all(b"\r\n")?; + } else { + stdout.write_all(&[x])?; + } } stdout.write_all(self.color(HighlightKind::None))?; -- cgit v1.2.3