diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 6e26d2f69..376d143d0 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -2,11 +2,12 @@ pub use helix_syntax::Lang; pub use helix_syntax::{get_language, get_language_name}; +use std::cell::RefCell; use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::sync::Arc; -use once_cell::sync::OnceCell; +use once_cell::sync::{Lazy, OnceCell}; // largely based on tree-sitter/cli/src/loader.rs pub struct LanguageConfiguration { @@ -65,8 +66,6 @@ pub fn scope(&self) -> &str { } } -use once_cell::sync::Lazy; - pub static LOADER: Lazy = Lazy::new(Loader::init); pub struct Loader { @@ -145,13 +144,20 @@ pub fn language_config_for_scope(&self, scope: &str) -> Option, +} + +// could also just use a pool, or a single instance? +thread_local! { + pub static PARSER: RefCell = RefCell::new(TSParser { + parser: Parser::new(), + cursors: Vec::new(), + }) +} pub struct Syntax { - // grammar: Grammar, - parser: Parser, - cursors: Vec, - config: Arc, pub(crate) root_layer: LanguageLayer, @@ -163,10 +169,6 @@ pub fn new( /*language: Lang,*/ source: &Rope, config: Arc, ) -> Self { - // fetch grammar for parser based on language string - // let grammar = get_language(&language); - let parser = Parser::new(); - let root_layer = LanguageLayer { tree: None }; // track markers of injections @@ -174,25 +176,25 @@ pub fn new( let mut syntax = Self { // grammar, - parser, - cursors: Vec::new(), config, root_layer, }; // update root layer - syntax.root_layer.parse( - &mut syntax.parser, - &syntax.config, - source, - 0, - vec![Range { - start_byte: 0, - end_byte: usize::MAX, - start_point: Point::new(0, 0), - end_point: Point::new(usize::MAX, usize::MAX), - }], - ); + PARSER.with(|ts_parser| { + syntax.root_layer.parse( + &mut ts_parser.borrow_mut(), + &syntax.config, + source, + 0, + vec![Range { + start_byte: 0, + end_byte: usize::MAX, + start_point: Point::new(0, 0), + end_point: Point::new(usize::MAX, usize::MAX), + }], + ); + }); syntax } @@ -202,13 +204,15 @@ pub fn update( source: &Rope, changeset: &ChangeSet, ) -> Result<(), Error> { - self.root_layer.update( - &mut self.parser, - &self.config, - old_source, - source, - changeset, - ) + PARSER.with(|ts_parser| { + self.root_layer.update( + &mut ts_parser.borrow_mut(), + &self.config, + old_source, + source, + changeset, + ) + }) // TODO: deal with injections and update them too } @@ -229,7 +233,8 @@ fn tree(&self) -> &Tree { /// Iterate over the highlighted regions for a given slice of source code. pub fn highlight_iter<'a>( - &'a mut self, + &self, + ts_parser: &'a mut TSParser, source: &'a [u8], range: Option>, cancellation_flag: Option<&'a AtomicUsize>, @@ -283,7 +288,7 @@ pub fn highlight_iter<'a>( byte_offset: range.map(|r| r.start).unwrap_or(0), // TODO: simplify injection_callback, cancellation_flag, - highlighter: self, + highlighter: ts_parser, iter_count: 0, layers: vec![layer], next_event: None, @@ -336,19 +341,21 @@ fn tree(&self) -> &Tree { fn parse( &mut self, - parser: &mut Parser, + ts_parser: &mut TSParser, config: &HighlightConfiguration, source: &Rope, mut depth: usize, mut ranges: Vec, ) -> Result<(), Error> { - if parser.set_included_ranges(&ranges).is_ok() { - parser + if ts_parser.parser.set_included_ranges(&ranges).is_ok() { + ts_parser + .parser .set_language(config.language) .map_err(|_| Error::InvalidLanguage)?; // unsafe { syntax.parser.set_cancellation_flag(cancellation_flag) }; - let tree = parser + let tree = ts_parser + .parser .parse_with( &mut |byte, _| { if byte <= source.len_bytes() { @@ -517,7 +524,7 @@ fn traverse(point: Point, text: &Tendril) -> Point { fn update( &mut self, - parser: &mut Parser, + ts_parser: &mut TSParser, config: &HighlightConfiguration, old_source: &Rope, source: &Rope, @@ -535,7 +542,7 @@ fn update( } self.parse( - parser, + ts_parser, config, source, 0, @@ -652,7 +659,7 @@ struct HighlightIter<'a, F> { source: &'a [u8], byte_offset: usize, - highlighter: &'a mut Syntax, + highlighter: &'a mut TSParser, injection_callback: F, cancellation_flag: Option<&'a AtomicUsize>, layers: Vec>, @@ -839,7 +846,7 @@ impl<'a> HighlightIterLayer<'a> { /// added to the returned vector. fn new Option<&'a HighlightConfiguration> + 'a>( source: &'a [u8], - highlighter: &mut Syntax, + highlighter: &mut TSParser, cancellation_flag: Option<&'a AtomicUsize>, injection_callback: &mut F, mut config: &'a HighlightConfiguration, diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index ae48950f1..a9aa6fec3 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -3,7 +3,11 @@ use crate::keymap::{self, Keymaps}; use crate::ui::text_color; -use helix_core::{indent::TAB_WIDTH, syntax::HighlightEvent, Position, Range, State}; +use helix_core::{ + indent::TAB_WIDTH, + syntax::{self, HighlightEvent}, + Position, Range, State, +}; use helix_view::{document::Mode, Document, Editor, Theme, View}; use std::borrow::Cow; @@ -34,7 +38,7 @@ pub fn new() -> Self { } pub fn render_view( &self, - view: &mut View, + view: &View, viewport: Rect, surface: &mut Surface, theme: &Theme, @@ -61,10 +65,9 @@ pub fn render_view( self.render_statusline(&view.doc, area, surface, theme, is_focused); } - // TODO: ideally not &mut View but highlights require it because of cursor cache pub fn render_buffer( &self, - view: &mut View, + view: &View, viewport: Rect, surface: &mut Surface, theme: &Theme, @@ -79,7 +82,7 @@ pub fn render_buffer( let range = { // calculate viewport byte ranges let start = text.line_to_byte(view.first_line); - let end = text.line_to_byte(last_line + 1); // TODO: double check + let end = text.line_to_byte(last_line + 1); start..end }; @@ -87,12 +90,20 @@ pub fn render_buffer( // TODO: range doesn't actually restrict source, just highlight range // TODO: cache highlight results // TODO: only recalculate when state.doc is actually modified - let highlights: Vec<_> = match view.doc.syntax.as_mut() { + let highlights: Vec<_> = match &view.doc.syntax { Some(syntax) => { - syntax - .highlight_iter(source_code.as_bytes(), Some(range), None, |_| None) - .unwrap() - .collect() // TODO: we collect here to avoid double borrow, fix later + syntax::PARSER.with(|ts_parser| { + syntax + .highlight_iter( + &mut ts_parser.borrow_mut(), + source_code.as_bytes(), + Some(range), + None, + |_| None, + ) + .unwrap() + .collect() // TODO: we collect here to avoid holding the lock, fix later + }) } None => vec![Ok(HighlightEvent::Source { start: range.start, @@ -102,7 +113,6 @@ pub fn render_buffer( let mut spans = Vec::new(); let mut visual_x = 0; let mut line = 0u16; - let text = view.doc.text(); 'outer: for event in highlights { match event.unwrap() { diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index 7cfeb811b..0fdf085f3 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -12,9 +12,6 @@ use helix_core::Position; use helix_view::Editor; -// TODO: factor out a popup component that we can reuse for displaying docs on autocomplete, -// diagnostics popups, etc. - pub struct Menu { options: Vec,