WIP syntax highlighting
This commit is contained in:
parent
51622bf6fd
commit
3d03c90c7b
104
src/buffer.rs
104
src/buffer.rs
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
19
src/main.rs
19
src/main.rs
|
@ -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(|| {
|
||||
|
|
Loading…
Reference in New Issue