WIP syntax highlighting

This commit is contained in:
mars 2023-04-12 12:52:46 -04:00
parent 51622bf6fd
commit 3d03c90c7b
2 changed files with 110 additions and 13 deletions

View File

@ -18,23 +18,45 @@
*/
use std::io::Write;
use std::ops::Range;
use std::sync::Arc;
use crossterm::{cursor, ExecutableCommand};
use parking_lot::Mutex;
use ropey::Rope;
use syntect::easy::ScopeRangeIterator;
use syntect::parsing::{ParseState, Scope, ScopeStack, SyntaxSet};
use crate::theme::StyleStore;
use crate::theme::{Style, StyleStore};
use crate::{Cursor, Direction};
#[derive(Clone, Debug)]
pub struct Buffer {
styles: Arc<Mutex<StyleStore>>,
text: Rope,
syntax_set: Arc<SyntaxSet>,
parser: ParseState,
styled: Vec<Vec<(Style, Range<usize>)>>,
}
impl Buffer {
pub fn from_str(text: &str) -> Self {
Self {
pub fn from_str(styles: Arc<Mutex<StyleStore>>, text: &str) -> Self {
let syntax_set = SyntaxSet::load_defaults_nonewlines();
// TODO load non-Rust syntax highlighting
let syntax_ref = syntax_set.find_syntax_by_extension("rs").unwrap();
let parser = ParseState::new(syntax_ref);
let mut buf = Self {
styles,
text: Rope::from_str(text),
}
syntax_set: Arc::new(SyntaxSet::load_defaults_newlines()),
parser,
styled: Vec::new(),
};
buf.parse();
buf
}
pub fn draw(
@ -63,11 +85,24 @@ impl Buffer {
linenr_style.print_styled(out, &linenr)?;
let lhs = scroll.column;
let width = line.len_chars().saturating_sub(1); // lop off whitespace
let width = line.len_chars();
if lhs < width {
let window = text_width.min(width - lhs);
let rhs = lhs + window;
write!(out, "{}", line.slice(lhs..rhs))?;
let styled = &self.styled[row];
for (style, range) in styled.iter() {
let range = range.start.max(lhs)..range.end.min(rhs);
if range.start < range.end {
// this range is in view so display
let content = line.slice(range.clone());
style.print_styled(out, content)?;
}
if range.end >= rhs {
// past the end of the window so we're done
break;
}
}
}
out.execute(cursor::MoveToNextLine(1))?;
@ -79,11 +114,55 @@ impl Buffer {
pub fn remove(&mut self, cursor: Cursor) {
let index = self.cursor_to_char(cursor);
self.text.remove(index..=index);
self.parse();
}
pub fn insert_char(&mut self, cursor: Cursor, c: char) {
let index = self.cursor_to_char(cursor);
self.text.insert_char(index, c);
self.parse();
}
/// Parses the whole file from scratch.
fn parse(&mut self) {
use std::fmt::Write;
let mut styles = self.styles.lock();
self.styled.clear();
let mut parser = self.parser.clone();
let mut line_buf = String::new();
let mut stack = ScopeStack::new();
for line in self.text.lines() {
// display line to a pre-allocated buffer for fast parsing
line_buf.clear();
write!(line_buf, "{}", line).unwrap();
let end = line_buf.trim_end().len();
line_buf.truncate(end);
// 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
let mut line = Vec::new();
let range_iter = ScopeRangeIterator::new(&stack_ops, &line_buf);
for (range, op) in range_iter {
stack.apply(&op).unwrap();
if range.start == range.end {
continue;
}
let mut style = Style::default();
for scope in stack.scopes.clone() {
// TODO docs say that build_string "shouldn't be done frequently"
let scope = scope.build_string();
style.apply(&styles.get_scope(&scope));
}
line.push((style, range));
}
self.styled.push(line);
}
}
pub fn clamped_cursor(&self, cursor: Cursor) -> Cursor {
@ -136,3 +215,16 @@ impl Buffer {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn style_scopes() {
let text = include_str!("buffer.rs");
let mut buffer = Buffer::from_str(Default::default(), text);
buffer.parse();
println!("{:#?}", buffer.styled);
}
}

View File

@ -22,6 +22,7 @@ use std::{
fs::File,
io::{stdout, Read, Stdout, Write},
os::fd::FromRawFd,
sync::Arc,
};
use crossterm::{
@ -29,6 +30,7 @@ use crossterm::{
event::{read, Event, KeyCode, KeyEvent},
terminal, ExecutableCommand, Result,
};
use parking_lot::Mutex;
use yacexits::{exit, EX_DATAERR, EX_UNAVAILABLE};
mod buffer;
@ -94,7 +96,7 @@ pub enum Direction {
}
struct State {
pub style_store: StyleStore,
pub styles: Arc<Mutex<StyleStore>>,
pub buffer: Buffer,
pub cursor: Cursor,
pub scroll: Cursor,
@ -105,11 +107,13 @@ struct State {
impl State {
pub fn from_str(text: &str) -> Result<Self> {
let styles = Arc::new(Mutex::new(StyleStore::default()));
let buffer = Buffer::from_str(styles.clone(), text);
let (cols, rows) = terminal::size()?;
Ok(Self {
style_store: StyleStore::default(),
buffer: Buffer::from_str(text),
styles,
buffer,
cursor: Cursor::default(),
scroll: Cursor::default(),
size: (cols as usize, rows as usize),
@ -123,6 +127,7 @@ impl State {
let (cols, rows) = terminal::size()?;
out.execute(terminal::BeginSynchronizedUpdate)?;
out.execute(terminal::Clear(terminal::ClearType::All))?;
let mut styles = self.styles.lock();
// draw status line
let mut set_cursor_pos = None;
@ -138,7 +143,7 @@ impl State {
show_status_bar = true;
}
Mode::Normal(NormalState { error: Some(error) }) => {
let error_style = self.style_store.get_scope("error");
let error_style = styles.get_scope("error");
out.execute(cursor::MoveTo(0, rows - 1))?;
error_style.print_styled(out, error)?;
show_status_bar = true;
@ -148,9 +153,9 @@ impl State {
// draw buffer
let buffer_rows = if show_status_bar { rows - 1 } else { rows };
let lr_width =
self.buffer
.draw(&mut self.style_store, cols, buffer_rows, self.scroll, out)?;
let lr_width = self
.buffer
.draw(&mut styles, cols, buffer_rows, self.scroll, out)?;
// draw cursor
let cursor_pos = set_cursor_pos.unwrap_or_else(|| {