on master help sign in
More robust syntax detection/grammar loading.
commiteb639eb2e4610ed2b440c8d95217f125005288fd authorBlaž Hrastnik <blaz@mxxn.io> dateTue 22 Sep 2020, 18:23 (6 years ago) parent2c3b10d
11 files changed+203−138
.gitmodules+4 Cargo.lock+16−14 TODO.md+3−2 helix-core/Cargo.toml+1 helix-core/src/lib.rs−1 helix-core/src/state.rs+10−21 helix-core/src/syntax.rs+153−84 helix-syntax/languages/tree-sitter-toml+1 helix-syntax/src/lib.rs+1 helix-term/src/editor.rs+13−14 helix-view/src/view.rs+1−2
.gitmodules+4
@@ -78,3 +78,7 @@
7878 path = helix-syntax/languages/tree-sitter-swift
7979 url = https://github.com/tree-sitter/tree-sitter-swift
8080 shallow = true
81+[submodule "helix-syntax/languages/tree-sitter-toml"]
82+ path = helix-syntax/languages/tree-sitter-toml
83+ url = https://github.com/ikatyang/tree-sitter-toml
84+ shallow = true
Cargo.lock+16−14
@@ -73,15 +73,16 @@ dependencies = [
7373
7474[[package]]
7575name = "async-executor"
76−version = "1.1.1"
76+version = "1.3.0"
7777source = "registry+https://github.com/rust-lang/crates.io-index"
78−checksum = "a831e74aa1937d3bbd3a356f34c23dbc6b6f0abc5160bd5484a9f75d5e76aea8"
78+checksum = "d373d78ded7d0b3fa8039375718cde0aace493f2e34fb60f51cbf567562ca801"
7979dependencies = [
8080 "async-task",
8181 "concurrent-queue",
8282 "fastrand",
8383 "futures-lite",
8484 "once_cell",
85+ "vec-arena",
8586]
8687
8788[[package]]
@@ -135,12 +136,13 @@ dependencies = [
135136
136137[[package]]
137138name = "async-net"
138−version = "1.3.0"
139+version = "1.4.3"
139140source = "registry+https://github.com/rust-lang/crates.io-index"
140−checksum = "a48af5438be856056bdeb6c5d895148a715be5915fccee49d1e5b50851dc9b8b"
141+checksum = "fb04482b77baa38d59d56aee0a7b4266600ab28e2b8be7af03508f6a30ecbdcf"
141142dependencies = [
142143 "async-io",
143144 "blocking",
145+ "fastrand",
144146 "futures-lite",
145147]
146148
@@ -162,9 +164,9 @@ dependencies = [
162164
163165[[package]]
164166name = "async-rwlock"
165−version = "1.1.0"
167+version = "1.2.1"
166168source = "registry+https://github.com/rust-lang/crates.io-index"
167−checksum = "6f8978b5ae008b5177da07a1bf1bfbe428f9bdb970c3fca0e92ed1c1930d7f34"
169+checksum = "806b1cc0828c2b1611ccbdd743fc0cc7af09009e62c95a0501c1e5da7b142a22"
168170dependencies = [
169171 "async-mutex",
170172 "event-listener",
@@ -181,9 +183,9 @@ dependencies = [
181183
182184[[package]]
183185name = "async-task"
184−version = "3.0.0"
186+version = "4.0.1"
185187source = "registry+https://github.com/rust-lang/crates.io-index"
186−checksum = "c17772156ef2829aadc587461c7753af20b7e8db1529bc66855add962a3b35d3"
188+checksum = "6725e96011a83fae25074a8734932e8d67763522839be7473dcfe8a0d6a378b1"
187189
188190[[package]]
189191name = "atomic-waker"
@@ -428,9 +430,9 @@ dependencies = [
428430
429431[[package]]
430432name = "hermit-abi"
431−version = "0.1.15"
433+version = "0.1.16"
432434source = "registry+https://github.com/rust-lang/crates.io-index"
433−checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9"
435+checksum = "4c30f6d0bc6b00693347368a67d41b58f2fb851215ff1da49e90fe2c5c667151"
434436dependencies = [
435437 "libc",
436438]
@@ -615,9 +617,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
615617
616618[[package]]
617619name = "polling"
618−version = "1.0.3"
620+version = "1.1.0"
619621source = "registry+https://github.com/rust-lang/crates.io-index"
620−checksum = "0307b8c7f438902536321f63c28cab0362f6ee89f1c7da47e3642ff956641c8b"
622+checksum = "e0720e0b9ea9d52451cf29d3413ba8a9303f8815d9d9653ef70e03ff73e65566"
621623dependencies = [
622624 "cfg-if",
623625 "libc",
@@ -712,9 +714,9 @@ checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252"
712714
713715[[package]]
714716name = "smol"
715−version = "1.0.1"
717+version = "1.2.1"
716718source = "registry+https://github.com/rust-lang/crates.io-index"
717−checksum = "712d02afa6ac9e7b8c777fd181aff476d009280b54b8c28703d10fa5d7e80d83"
719+checksum = "d41237ba3e3ada55ff3515d37becc8fa90e5e4af2b13a011ec3f932d9f1b2405"
718720dependencies = [
719721 "async-channel",
720722 "async-executor",
TODO.md+3−2
@@ -1,5 +1,6 @@
1−- Implement backspace/delete
2−- Implement marks
1+helper methods: iterate over selection spans in the document.
2+
3+- Implement marks (superset of Selection/Range)
34- Implement style configs, tab settings
45- Visual tab width
56- Refactor tree-sitter-highlight to work like the atom one, recomputing partial tree updates.
helix-core/Cargo.toml+1
@@ -17,3 +17,4 @@ unicode-segmentation = "1.6.0"
1717unicode-width = "0.1.7"
1818# slab = "0.4.2"
1919tree-sitter = "0.16.1"
20+once_cell = "1.4.1"
helix-core/src/lib.rs−1
@@ -1,5 +1,4 @@
11#![allow(unused)]
2−pub mod config;
32pub mod graphemes;
43pub mod macros;
54mod position;
helix-core/src/state.rs+10−21
@@ -1,4 +1,5 @@
11use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes};
2+use crate::syntax::LOADER;
23use crate::{Position, Range, Rope, RopeSlice, Selection, Syntax};
34use anyhow::Error;
45
@@ -48,7 +49,8 @@ impl State {
4849 }
4950 }
5051
51− pub fn load(path: PathBuf) -> Result<Self, Error> {
52+ // TODO: passing scopes here is awkward
53+ pub fn load(path: PathBuf, scopes: &[String]) -> Result<Self, Error> {
5254 use std::{env, fs::File, io::BufReader, path::PathBuf};
5355 let _current_dir = env::current_dir()?;
5456
@@ -57,30 +59,17 @@ impl State {
5759 // TODO: create if not found
5860
5961 let mut state = Self::new(doc);
60− state.path = Some(path);
61−
62− let language = helix_syntax::get_language(&helix_syntax::LANG::Rust);
6362
64− let mut highlight_config = crate::syntax::HighlightConfiguration::new(
65− language,
66− &std::fs::read_to_string(
67− "../helix-syntax/languages/tree-sitter-rust/queries/highlights.scm",
68− )
69− .unwrap(),
70− &std::fs::read_to_string(
71− "../helix-syntax/languages/tree-sitter-rust/queries/injections.scm",
72− )
73− .unwrap(),
74− "", // locals.scm
75− )
76− .unwrap();
63+ if let Some(language_config) = LOADER.language_config_for_file_name(path.as_path()) {
64+ let highlight_config = language_config.highlight_config(scopes).unwrap().unwrap();
65+ // TODO: config.configure(scopes) is now delayed, is that ok?
7766
78− // TODO: config.configure(scopes) is now delayed, is that ok?
67+ let syntax = Syntax::new(&state.doc, highlight_config.clone());
7968
80− // TODO: get_language is called twice
81− let syntax = Syntax::new(helix_syntax::LANG::Rust, &state.doc, highlight_config);
69+ state.syntax = Some(syntax);
70+ };
8271
83− state.syntax = Some(syntax);
72+ state.path = Some(path);
8473
8574 Ok(state)
8675 }
helix-core/src/syntax.rs+153−84
@@ -2,21 +2,161 @@ use crate::{Change, Rope, RopeSlice, Transaction};
22pub use helix_syntax::LANG;
33pub use helix_syntax::{get_language, get_language_name};
44
5+use std::collections::HashMap;
6+use std::path::{Path, PathBuf};
7+use std::sync::Arc;
8+
9+use once_cell::sync::OnceCell;
10+
11+// largely based on tree-sitter/cli/src/loader.rs
12+pub struct LanguageConfiguration {
13+ pub(crate) scope: String, // source.rust
14+ pub(crate) file_types: Vec<String>, // filename ends_with? <Gemfile, rb, etc>
15+
16+ pub(crate) path: PathBuf,
17+
18+ // content_regex
19+ // injection_regex
20+ // first_line_regex
21+ //
22+ // root_path
23+ //
24+ pub(crate) language_id: LANG,
25+ pub(crate) highlight_config: OnceCell<Option<Arc<HighlightConfiguration>>>,
26+ // tags_config OnceCell<> https://github.com/tree-sitter/tree-sitter/pull/583
27+}
28+
29+impl LanguageConfiguration {
30+ pub fn highlight_config(
31+ &self,
32+ scopes: &[String],
33+ ) -> Result<Option<&Arc<HighlightConfiguration>>, anyhow::Error> {
34+ self.highlight_config
35+ .get_or_try_init(|| {
36+ // let name = get_language_name(&self.language_id);
37+
38+ let highlights_query =
39+ std::fs::read_to_string(self.path.join("queries/highlights.scm"))
40+ .unwrap_or(String::new());
41+
42+ let injections_query =
43+ std::fs::read_to_string(self.path.join("queries/injections.scm"))
44+ .unwrap_or(String::new());
45+
46+ let locals_query = "";
47+
48+ if highlights_query.is_empty() {
49+ Ok(None)
50+ } else {
51+ let language = get_language(&self.language_id);
52+ let mut config = HighlightConfiguration::new(
53+ language,
54+ &highlights_query,
55+ &injections_query,
56+ &locals_query,
57+ )
58+ .unwrap(); // TODO: no unwrap
59+ config.configure(&scopes);
60+ Ok(Some(Arc::new(config)))
61+ }
62+ })
63+ .map(Option::as_ref)
64+ }
65+}
66+
67+use once_cell::sync::Lazy;
68+
69+pub(crate) static LOADER: Lazy<Loader> = Lazy::new(|| Loader::init());
70+
71+pub struct Loader {
72+ // highlight_names ?
73+ language_configs: Vec<Arc<LanguageConfiguration>>,
74+ language_config_ids_by_file_type: HashMap<String, usize>, // Vec<usize>
75+}
76+
77+impl Loader {
78+ fn init() -> Loader {
79+ let mut loader = Loader {
80+ language_configs: Vec::new(),
81+ language_config_ids_by_file_type: HashMap::new(),
82+ };
83+
84+ // hardcoded from now, might load from toml
85+ let configs = vec![
86+ LanguageConfiguration {
87+ scope: "source.rust".to_string(),
88+ file_types: vec!["rs".to_string()],
89+ language_id: LANG::Rust,
90+ highlight_config: OnceCell::new(),
91+ //
92+ path: "../helix-syntax/languages/tree-sitter-rust".into(),
93+ },
94+ LanguageConfiguration {
95+ scope: "source.toml".to_string(),
96+ file_types: vec!["toml".to_string()],
97+ language_id: LANG::Toml,
98+ highlight_config: OnceCell::new(),
99+ //
100+ path: "../helix-syntax/languages/tree-sitter-toml".into(),
101+ },
102+ ];
103+
104+ for config in configs {
105+ // get the next id
106+ let language_id = loader.language_configs.len();
107+
108+ for file_type in &config.file_types {
109+ // entry().or_insert(Vec::new).push(language_id);
110+ loader
111+ .language_config_ids_by_file_type
112+ .insert(file_type.clone(), language_id);
113+ }
114+
115+ loader.language_configs.push(Arc::new(config));
116+ }
117+
118+ loader
119+ }
120+
121+ pub fn language_config_for_file_name(&self, path: &Path) -> Option<Arc<LanguageConfiguration>> {
122+ // Find all the language configurations that match this file name
123+ // or a suffix of the file name.
124+ let configuration_id = path
125+ .file_name()
126+ .and_then(|n| n.to_str())
127+ .and_then(|file_name| self.language_config_ids_by_file_type.get(file_name))
128+ .or_else(|| {
129+ path.extension()
130+ .and_then(|extension| extension.to_str())
131+ .and_then(|extension| self.language_config_ids_by_file_type.get(extension))
132+ });
133+
134+ configuration_id.and_then(|&id| self.language_configs.get(id).cloned())
135+
136+ // TODO: content_regex handling conflict resolution
137+ }
138+}
139+
140+//
141+
5142pub struct Syntax {
6− grammar: Language,
143+ // grammar: Grammar,
7144 parser: Parser,
8145 cursors: Vec<QueryCursor>,
9146
10− config: HighlightConfiguration,
147+ config: Arc<HighlightConfiguration>,
11148
12149 root_layer: LanguageLayer,
13150}
14151
15152impl Syntax {
16153 // buffer, grammar, config, grammars, sync_timeout?
17− pub fn new(language: LANG, source: &Rope, config: HighlightConfiguration) -> Self {
154+ pub fn new(
155+ /*language: LANG,*/ source: &Rope,
156+ config: Arc<HighlightConfiguration>,
157+ ) -> Self {
18158 // fetch grammar for parser based on language string
19− let grammar = get_language(&language);
159+ // let grammar = get_language(&language);
20160 let parser = Parser::new();
21161
22162 let root_layer = LanguageLayer::new();
@@ -25,7 +165,7 @@ impl Syntax {
25165 // track scope_descriptor: a Vec of scopes for item in tree
26166
27167 let mut syntax = Self {
28− grammar,
168+ // grammar,
29169 parser,
30170 cursors: Vec::new(),
31171 config,
@@ -48,10 +188,6 @@ impl Syntax {
48188 syntax
49189 }
50190
51− pub fn configure(&mut self, scopes: &[String]) {
52− self.config.configure(scopes)
53− }
54−
55191 pub fn update(&mut self, source: &Rope, changeset: &ChangeSet) -> Result<(), Error> {
56192 self.root_layer
57193 .update(&mut self.parser, &self.config, source, changeset)
@@ -88,9 +224,9 @@ impl Syntax {
88224 let mut cursor = QueryCursor::new(); // reuse a pool
89225 let tree_ref = unsafe { mem::transmute::<_, &'static Tree>(self.tree()) };
90226 let cursor_ref = unsafe { mem::transmute::<_, &'static mut QueryCursor>(&mut cursor) };
91− let query_ref = unsafe { mem::transmute::<_, &'static mut Query>(&mut self.config.query) };
227+ let query_ref = unsafe { mem::transmute::<_, &'static Query>(&self.config.query) };
92228 let config_ref =
93− unsafe { mem::transmute::<_, &'static HighlightConfiguration>(&self.config) };
229+ unsafe { mem::transmute::<_, &'static HighlightConfiguration>(self.config.as_ref()) };
94230
95231 // TODO: if reusing cursors this might need resetting
96232 if let Some(range) = &range {
@@ -432,8 +568,8 @@ impl LanguageLayer {
432568use std::sync::atomic::{AtomicUsize, Ordering};
433569use std::{iter, mem, ops, str, usize};
434570use tree_sitter::{
435− Language, Node, Parser, Point, Query, QueryCaptures, QueryCursor, QueryError, QueryMatch,
436− Range, Tree,
571+ Language as Grammar, Node, Parser, Point, Query, QueryCaptures, QueryCursor, QueryError,
572+ QueryMatch, Range, Tree,
437573};
438574
439575const CANCELLATION_CHECK_INTERVAL: usize = 100;
@@ -462,7 +598,7 @@ pub enum HighlightEvent {
462598///
463599/// This struct is immutable and can be shared between threads.
464600pub struct HighlightConfiguration {
465− pub language: Language,
601+ pub language: Grammar,
466602 pub query: Query,
467603 combined_injections_query: Option<Query>,
468604 locals_pattern_index: usize,
@@ -477,16 +613,6 @@ pub struct HighlightConfiguration {
477613 local_ref_capture_index: Option<u32>,
478614}
479615
480−/// Performs syntax highlighting, recognizing a given list of highlight names.
481−///
482−/// For the best performance `Highlighter` values should be reused between
483−/// syntax highlighting calls. A separate highlighter is needed for each thread that
484−/// is performing highlighting.
485−pub struct Highlighter {
486− parser: Parser,
487− cursors: Vec<QueryCursor>,
488−}
489−
490616#[derive(Debug)]
491617struct LocalDef<'a> {
492618 name: &'a str,
@@ -527,70 +653,13 @@ struct HighlightIterLayer<'a> {
527653 depth: usize,
528654}
529655
530−impl Default for Highlighter {
531− fn default() -> Self {
532− Highlighter {
533− parser: Parser::new(),
534− cursors: Vec::new(),
535− }
536− }
537−}
538−
539−impl Highlighter {
540− pub fn new() -> Self {
541− Self::default()
542− }
543−
544− pub fn parser(&mut self) -> &mut Parser {
545− &mut self.parser
546− }
547−
548− // /// Iterate over the highlighted regions for a given slice of source code.
549− // pub fn highlight<'a>(
550− // &'a mut self,
551− // config: &'a HighlightConfiguration,
552− // source: &'a [u8],
553− // cancellation_flag: Option<&'a AtomicUsize>,
554− // mut injection_callback: impl FnMut(&str) -> Option<&'a HighlightConfiguration> + 'a,
555− // ) -> Result<impl Iterator<Item = Result<HighlightEvent, Error>> + 'a, Error> {
556− // let layers = HighlightIterLayer::new(
557− // source,
558− // self,
559− // cancellation_flag,
560− // &mut injection_callback,
561− // config,
562− // 0,
563− // vec![Range {
564− // start_byte: 0,
565− // end_byte: usize::MAX,
566− // start_point: Point::new(0, 0),
567− // end_point: Point::new(usize::MAX, usize::MAX),
568− // }],
569− // )?;
570− // assert_ne!(layers.len(), 0);
571− // let mut result = HighlightIter {
572− // source,
573− // byte_offset: 0,
574− // injection_callback,
575− // cancellation_flag,
576− // highlighter: self,
577− // iter_count: 0,
578− // layers,
579− // next_event: None,
580− // last_highlight_range: None,
581− // };
582− // result.sort_layers();
583− // Ok(result)
584− // }
585−}
586−
587656impl HighlightConfiguration {
588− /// Creates a `HighlightConfiguration` for a given `Language` and set of highlighting
657+ /// Creates a `HighlightConfiguration` for a given `Grammar` and set of highlighting
589658 /// queries.
590659 ///
591660 /// # Parameters
592661 ///
593− /// * `language` - The Tree-sitter `Language` that should be used for parsing.
662+ /// * `language` - The Tree-sitter `Grammar` that should be used for parsing.
594663 /// * `highlights_query` - A string containing tree patterns for syntax highlighting. This
595664 /// should be non-empty, otherwise no syntax highlights will be added.
596665 /// * `injections_query` - A string containing tree patterns for injecting other languages
@@ -600,7 +669,7 @@ impl HighlightConfiguration {
600669 ///
601670 /// Returns a `HighlightConfiguration` that can then be used with the `highlight` method.
602671 pub fn new(
603− language: Language,
672+ language: Grammar,
604673 highlights_query: &str,
605674 injection_query: &str,
606675 locals_query: &str,
helix-syntax/languages/tree-sitter-tomlnew file+1

Submodule changed.

helix-syntax/src/lib.rs+1
@@ -82,6 +82,7 @@ mk_langs!(
8282 (Rust, tree_sitter_rust),
8383 (Scala, tree_sitter_scala),
8484 (Swift, tree_sitter_swift),
85+ (Toml, tree_sitter_toml),
8586 (Tsx, tree_sitter_tsx),
8687 (Typescript, tree_sitter_typescript)
8788);
helix-term/src/editor.rs+13−14
@@ -1,10 +1,5 @@
11use crate::Args;
2−use helix_core::{
3− state::coords_at_pos,
4− state::Mode,
5− syntax::{HighlightConfiguration, HighlightEvent, Highlighter},
6− State,
7−};
2+use helix_core::{state::coords_at_pos, state::Mode, syntax::HighlightEvent, State};
83use helix_view::{commands, keymap, View};
94
105use std::{
@@ -107,14 +102,18 @@ impl Editor {
107102
108103 // TODO: cache highlight results
109104 // TODO: only recalculate when state.doc is actually modified
110− let highlights: Vec<_> = view
111− .state
112− .syntax
113− .as_mut()
114− .unwrap()
115− .highlight_iter(source_code.as_bytes(), Some(range), None, |_| None)
116− .unwrap()
117− .collect(); // TODO: we collect here to avoid double borrow, fix later
105+ let highlights: Vec<_> = match view.state.syntax.as_mut() {
106+ Some(syntax) => {
107+ syntax
108+ .highlight_iter(source_code.as_bytes(), Some(range), None, |_| None)
109+ .unwrap()
110+ .collect() // TODO: we collect here to avoid double borrow, fix later
111+ }
112+ None => vec![Ok(HighlightEvent::Source {
113+ start: range.start,
114+ end: range.end,
115+ })],
116+ };
118117
119118 let mut spans = Vec::new();
120119
helix-view/src/view.rs+1−2
@@ -14,9 +14,8 @@ pub struct View {
1414
1515impl View {
1616 pub fn open(path: PathBuf, size: (u16, u16)) -> Result<View, Error> {
17− let mut state = State::load(path)?;
1817 let theme = Theme::default();
19− state.syntax.as_mut().unwrap().configure(theme.scopes());
18+ let state = State::load(path, theme.scopes())?;
2019
2120 let view = View {
2221 state,