From afaf3ab0fefe718468cbd7b4ac88ae8ee75ee948 Mon Sep 17 00:00:00 2001 From: mars Date: Wed, 12 Apr 2023 17:39:46 -0400 Subject: [PATCH 1/6] Add line command --- src/buffer.rs | 8 +++++++- src/main.rs | 18 ++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index 73975d5..2b8b67c 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -33,7 +33,7 @@ use crate::{Cursor, Direction}; #[derive(Clone, Debug)] pub struct Buffer { styles: Arc>, - pub text: Rope, + text: Rope, syntax_set: Arc, parser: ParseState, style_dirty: bool, @@ -41,6 +41,12 @@ pub struct Buffer { styled: Vec)>>, } +impl AsRef for Buffer { + fn as_ref(&self) -> &Rope { + &self.text + } +} + impl Buffer { pub fn from_str(styles: Arc>, text: &str) -> Self { let syntax_set = SyntaxSet::load_defaults_nonewlines(); diff --git a/src/main.rs b/src/main.rs index 5b64678..4bc85a4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -325,7 +325,7 @@ impl State { } fn write_buffer(&mut self, file: OsString) -> Result<()> { - let out = self.buffer.text.bytes().collect::>(); + let out = self.buffer.as_ref().bytes().collect::>(); let mut handle = OpenOptions::new().write(true).open(file)?; handle.write_all(out.as_slice())?; @@ -364,9 +364,16 @@ impl State { self.write_command(command, args)?; self.quit = true; } - command => { - return Err(format!("{}: Unrecognized command.", command)); - } + command => match command.parse::() { + Err(_) => return Err(format!("{}: Unrecognized command.", command)), + Ok(line) if line >= self.buffer.as_ref().len_lines() => { + return Err(format!("Line {} is out-of-bounds", line)) + } + Ok(line) => { + self.cursor.line = line; + self.scroll_to_cursor(); + } + }, } Ok(()) @@ -374,7 +381,10 @@ impl State { fn move_cursor(&mut self, direction: Direction) { self.buffer.move_cursor(&mut self.cursor, direction, true); + self.scroll_to_cursor(); + } + fn scroll_to_cursor(&mut self) { if self.cursor.column < self.scroll.column + 3 { self.scroll.column = self.cursor.column.saturating_sub(3); } else if self.cursor.column + 6 >= self.scroll.column + self.size.0 { From 742ed978190e6271304d0a352cd31c3606b932ab Mon Sep 17 00:00:00 2001 From: mars Date: Wed, 12 Apr 2023 17:50:14 -0400 Subject: [PATCH 2/6] Defer stdout flush until end of draw --- src/buffer.rs | 10 +++++----- src/main.rs | 26 ++++++++++++++------------ src/theme.rs | 18 ++++++++---------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index 2b8b67c..80bf44b 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -21,7 +21,7 @@ use std::io::Write; use std::ops::Range; use std::sync::Arc; -use crossterm::{cursor, ExecutableCommand}; +use crossterm::{cursor, ExecutableCommand, QueueableCommand}; use parking_lot::Mutex; use ropey::Rope; use syntect::easy::ScopeRangeIterator; @@ -74,7 +74,7 @@ impl Buffer { cols: u16, rows: u16, scroll: Cursor, - out: &mut (impl ExecutableCommand + Write), + out: &mut impl QueueableCommand, ) -> crossterm::Result { self.parse(); @@ -84,7 +84,7 @@ impl Buffer { let gutter_width = linenr_width + 1; let text_width = cols as usize - gutter_width as usize; - out.execute(cursor::MoveTo(0, 0))?; + out.queue(cursor::MoveTo(0, 0))?; for (row, line) in (0..rows).zip(self.text.lines_at(scroll.line)) { // only the last line is empty and should be skipped @@ -117,7 +117,7 @@ impl Buffer { } } - out.execute(cursor::MoveToNextLine(1))?; + out.queue(cursor::MoveToNextLine(1))?; } Ok(gutter_width) @@ -157,7 +157,7 @@ impl Buffer { // parse the line into a sequence of stack operations let stack_ops = parser.parse_line(&line_buf, &self.syntax_set).unwrap(); - // execute the operations on the stack + // queue the operations on the stack let mut line = Vec::new(); let range_iter = ScopeRangeIterator::new(&stack_ops, &line_buf); for (range, op) in range_iter { diff --git a/src/main.rs b/src/main.rs index 4bc85a4..84e4936 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,7 +31,7 @@ use crossbeam_channel::Sender; use crossterm::{ cursor, event::{Event, KeyCode, KeyEvent}, - terminal, ExecutableCommand, Result, + terminal, Result, QueueableCommand, }; use parking_lot::Mutex; use yacexits::{exit, EX_DATAERR, EX_UNAVAILABLE}; @@ -135,8 +135,8 @@ impl State { pub fn draw(&mut self, out: &mut impl Write) -> Result<()> { // begin update let (cols, rows) = terminal::size()?; - out.execute(terminal::BeginSynchronizedUpdate)?; - out.execute(terminal::Clear(terminal::ClearType::All))?; + out.queue(terminal::BeginSynchronizedUpdate)?; + out.queue(terminal::Clear(terminal::ClearType::All))?; let mut styles = self.styles.lock(); // draw status line @@ -147,14 +147,14 @@ impl State { Mode::Command(CommandState { buf, cursor }) => { let col = *cursor as u16 + 1; let row = rows - 1; - out.execute(cursor::MoveTo(0, row))?; + out.queue(cursor::MoveTo(0, row))?; write!(out, ":{}", buf)?; set_cursor_pos = Some((col, row)); show_status_bar = true; } Mode::Normal(NormalState { error: Some(error) }) => { let error_style = styles.get_scope("error"); - out.execute(cursor::MoveTo(0, rows - 1))?; + out.queue(cursor::MoveTo(0, rows - 1))?; error_style.print_styled(out, error)?; show_status_bar = true; } @@ -178,11 +178,13 @@ impl State { (col, row) }); - out.execute(cursor::MoveTo(cursor_pos.0, cursor_pos.1))?; - out.execute(self.mode.cursor_style())?; + out.queue(cursor::MoveTo(cursor_pos.0, cursor_pos.1))?; + out.queue(self.mode.cursor_style())?; // finish update - out.execute(terminal::EndSynchronizedUpdate)?; + out.queue(terminal::EndSynchronizedUpdate)?; + out.flush()?; + Ok(()) } @@ -246,7 +248,7 @@ impl State { } KeyCode::Enter => { // TODO add to command history - let result = self.execute_command(&state.buf); + let result = self.queue_command(&state.buf); let error = result.err(); self.mode = Mode::Normal(NormalState { error }); return; @@ -347,7 +349,7 @@ impl State { self.write_buffer(handle).map_err(|err| format!("{}", err)) } - fn execute_command(&mut self, command: &str) -> std::result::Result<(), String> { + fn queue_command(&mut self, command: &str) -> std::result::Result<(), String> { let command_parts = command.split(' ').collect::>(); let command = match command_parts.get(0) { @@ -457,9 +459,9 @@ fn main() -> Result<()> { let state = State::from_str(file_name, &text)?; let mut stdout = stdout(); terminal::enable_raw_mode()?; - stdout.execute(terminal::EnterAlternateScreen)?; + stdout.queue(terminal::EnterAlternateScreen)?; let result = screen_main(&mut stdout, state); - stdout.execute(terminal::LeaveAlternateScreen)?; + stdout.queue(terminal::LeaveAlternateScreen)?; terminal::disable_raw_mode()?; result } diff --git a/src/theme.rs b/src/theme.rs index 3723a10..b9eadb4 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -17,11 +17,10 @@ * along with this program. If not, see https://www.gnu.org/licenses/. */ -use std::io::Write; use std::{collections::HashMap, fmt::Display}; -use crossterm::{style::Color, ExecutableCommand}; -use once_cell::sync::Lazy; +use crossterm::style::Color; +use crossterm::QueueableCommand; use toml::{map::Map, Value}; #[derive(Debug, Default)] @@ -110,22 +109,21 @@ pub struct Style { impl Style { pub fn print_styled( &self, - out: &mut (impl ExecutableCommand + Write), + out: &mut impl QueueableCommand, content: impl Display, ) -> crossterm::Result<()> { - use crossterm::style::{ResetColor, SetBackgroundColor, SetForegroundColor}; + use crossterm::style::{Print, ResetColor, SetBackgroundColor, SetForegroundColor}; if let Some(fg) = self.fg { - out.execute(SetForegroundColor(fg))?; + out.queue(SetForegroundColor(fg))?; } if let Some(bg) = self.bg { - out.execute(SetBackgroundColor(bg))?; + out.queue(SetBackgroundColor(bg))?; } - write!(out, "{}", content)?; - - out.execute(ResetColor)?; + out.queue(Print(content))?; + out.queue(ResetColor)?; Ok(()) } From 83872583e086255fa84c093a0954ee0db8dfbc3a Mon Sep 17 00:00:00 2001 From: mars Date: Wed, 12 Apr 2023 17:54:22 -0400 Subject: [PATCH 3/6] Buffer line styling --- src/buffer.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index 80bf44b..92f767a 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -74,7 +74,7 @@ impl Buffer { cols: u16, rows: u16, scroll: Cursor, - out: &mut impl QueueableCommand, + out: &mut impl Write, ) -> crossterm::Result { self.parse(); @@ -86,6 +86,7 @@ impl Buffer { out.queue(cursor::MoveTo(0, 0))?; + let mut line_buf = Vec::::with_capacity(text_width); for (row, line) in (0..rows).zip(self.text.lines_at(scroll.line)) { // only the last line is empty and should be skipped if line.len_chars() == 0 { @@ -98,6 +99,7 @@ impl Buffer { let lhs = scroll.column; let width = line.len_chars(); + line_buf.clear(); if lhs < width { let window = text_width.min(width - lhs); let rhs = lhs + window; @@ -107,7 +109,7 @@ impl Buffer { if range.start < range.end { // this range is in view so display let content = line.slice(range.clone()); - style.print_styled(out, content)?; + style.print_styled(&mut line_buf, content)?; } if range.end >= rhs { @@ -117,6 +119,7 @@ impl Buffer { } } + out.write_all(&line_buf)?; out.queue(cursor::MoveToNextLine(1))?; } From 4f6f17d066e08dc9775ba4aa4c537df87f207801 Mon Sep 17 00:00:00 2001 From: mars Date: Wed, 12 Apr 2023 18:03:25 -0400 Subject: [PATCH 4/6] Oops! accidentally set dirty flag every draw --- src/buffer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/buffer.rs b/src/buffer.rs index 92f767a..4e94f65 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -183,7 +183,7 @@ impl Buffer { self.styled.push(line); } - self.style_dirty = true; + self.style_dirty = false; self.style_generation = styles.generation(); } From b351dbbcbb199cf1b6135130e679a83198b23a29 Mon Sep 17 00:00:00 2001 From: mars Date: Wed, 12 Apr 2023 18:24:06 -0400 Subject: [PATCH 5/6] Faster syntax highlighting with a style stack --- src/buffer.rs | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index 4e94f65..95926ab 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -25,7 +25,7 @@ use crossterm::{cursor, ExecutableCommand, QueueableCommand}; use parking_lot::Mutex; use ropey::Rope; use syntect::easy::ScopeRangeIterator; -use syntect::parsing::{ParseState, Scope, ScopeStack, SyntaxSet}; +use syntect::parsing::{BasicScopeStackOp, ParseState, Scope, ScopeStack, SyntaxSet}; use crate::theme::{Style, StyleStore}; use crate::{Cursor, Direction}; @@ -150,6 +150,8 @@ impl Buffer { let mut parser = self.parser.clone(); let mut line_buf = String::new(); let mut stack = ScopeStack::new(); + let mut style_stack = Vec::