Compare commits

...

2 Commits

Author SHA1 Message Date
mars 814d4b116b More efficient scope style lookups 2023-04-12 18:38:12 -04:00
mars b351dbbcbb Faster syntax highlighting with a style stack 2023-04-12 18:24:06 -04:00
2 changed files with 60 additions and 27 deletions

View File

@ -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,11 @@ impl Buffer {
let mut parser = self.parser.clone();
let mut line_buf = String::new();
let mut stack = ScopeStack::new();
let mut styled_buf = Vec::new();
let mut style_stack = Vec::<Style>::new();
style_stack.push(Default::default());
for line in self.text.lines() {
// display line to a pre-allocated buffer for fast parsing
line_buf.clear();
@ -161,26 +166,32 @@ impl Buffer {
let stack_ops = parser.parse_line(&line_buf, &self.syntax_set).unwrap();
// queue the operations on the stack
let mut line = Vec::new();
let range_iter = ScopeRangeIterator::new(&stack_ops, &line_buf);
styled_buf.clear();
for (range, op) in range_iter {
stack.apply(&op).unwrap();
stack
.apply_with_hook(&op, |op, _scopes| match op {
BasicScopeStackOp::Push(scope) => {
let style = styles.get_syntect_scope(&scope);
let mut top_style = style_stack.last().unwrap().clone();
top_style.apply(style);
style_stack.push(top_style);
}
BasicScopeStackOp::Pop => {
style_stack.pop();
}
})
.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));
let style = style_stack.last().cloned().unwrap_or_default();
styled_buf.push((style, range));
}
self.styled.push(line);
self.styled.push(styled_buf.clone());
}
self.style_dirty = false;

View File

@ -21,6 +21,7 @@ use std::{collections::HashMap, fmt::Display};
use crossterm::style::Color;
use crossterm::QueueableCommand;
use syntect::parsing::Scope;
use toml::{map::Map, Value};
#[derive(Debug, Default)]
@ -34,6 +35,9 @@ pub struct StyleStore {
/// Maps named scopes to indices.
scopes: HashMap<String, usize>,
/// Maps syntect scopes to indices.
syntect_scopes: HashMap<Scope, usize>,
/// The style data store.
styles: Vec<Style>,
}
@ -45,41 +49,59 @@ impl StyleStore {
theme,
generation: 0,
scopes: HashMap::new(),
syntect_scopes: HashMap::new(),
styles: Vec::new(),
}
}
/// Adds a scope to this store and returns its index. Reuses indices for existing scopes.
pub fn add_scope(&mut self, scope: &str) -> usize {
let style = self.theme.get_scope_style(scope);
if let Some(style_idx) = self.scopes.get(scope).copied() {
let old_style = self
.styles
.get_mut(style_idx)
.expect("StyleStore index is out-of-bounds");
let _ = std::mem::replace(old_style, style);
style_idx
if let Some(style_idx) = self.scopes.get(scope) {
*style_idx
} else {
let style_idx = self.styles.len();
let style = self.theme.get_scope_style(scope);
self.styles.push(style);
self.scopes.insert(scope.to_string(), style_idx);
if let Ok(scope) = Scope::new(scope) {
self.syntect_scopes.insert(scope, style_idx);
}
style_idx
}
}
/// Gets the style for a scope by name.
pub fn get_scope(&mut self, scope: &str) -> Style {
pub fn get_scope(&mut self, scope: &str) -> &Style {
let idx = self.add_scope(scope);
self.get_style(idx)
}
/// Adds a Syntect [Scope] and returns its index. Resues indices for existing scopes.
pub fn add_syntect_scope(&mut self, scope: &Scope) -> usize {
if let Some(style_idx) = self.syntect_scopes.get(scope) {
*style_idx
} else {
let style_idx = self.styles.len();
let scope_str = scope.build_string();
let style = self.theme.get_scope_style(&scope_str);
self.styles.push(style);
self.scopes.insert(scope_str, style_idx);
self.syntect_scopes.insert(*scope, style_idx);
style_idx
}
}
/// Gets the style for a Syntect [Scope].
pub fn get_syntect_scope(&mut self, scope: &Scope) -> &Style {
let idx = self.add_syntect_scope(scope);
self.get_style(idx)
}
/// Gets the style for a scope index. Panics if out-of-bounds.
pub fn get_style(&self, index: usize) -> Style {
self.styles
.get(index)
.expect("StyleStore is out-of-bounds")
.to_owned()
pub fn get_style(&self, index: usize) -> &Style {
self.styles.get(index).expect("StyleStore is out-of-bounds")
}
/// The current generation of the store. Increments every update.