Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-core/src/syntax.rs')
-rw-r--r--helix-core/src/syntax.rs163
1 files changed, 162 insertions, 1 deletions
diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs
index ca57b72e..1845686a 100644
--- a/helix-core/src/syntax.rs
+++ b/helix-core/src/syntax.rs
@@ -13,6 +13,7 @@ use std::{
use anyhow::{Context, Result};
use arc_swap::{ArcSwap, Guard};
use config::{Configuration, FileType, LanguageConfiguration, LanguageServerConfiguration};
+use foldhash::HashSet;
use helix_loader::grammar::get_language;
use helix_stdx::rope::RopeSliceExt as _;
use once_cell::sync::OnceCell;
@@ -22,7 +23,7 @@ use tree_house::{
query_iter::QueryIter,
tree_sitter::{
query::{InvalidPredicateError, UserPredicate},
- Grammar, InactiveQueryCursor, InputEdit, Node, Query, RopeInput, Tree,
+ Capture, Grammar, InactiveQueryCursor, InputEdit, Node, Pattern, Query, RopeInput, Tree,
},
Error, InjectionLanguageMarker, LanguageConfig as SyntaxConfig, Layer,
};
@@ -42,6 +43,7 @@ pub struct LanguageData {
indent_query: OnceCell<Option<IndentQuery>>,
textobject_query: OnceCell<Option<TextObjectQuery>>,
tag_query: OnceCell<Option<TagQuery>>,
+ rainbow_query: OnceCell<Option<RainbowQuery>>,
}
impl LanguageData {
@@ -52,6 +54,7 @@ impl LanguageData {
indent_query: OnceCell::new(),
textobject_query: OnceCell::new(),
tag_query: OnceCell::new(),
+ rainbow_query: OnceCell::new(),
}
}
@@ -198,6 +201,36 @@ impl LanguageData {
.as_ref()
}
+ /// Compiles the rainbows.scm query for a language.
+ /// This function should only be used by this module or the xtask crate.
+ pub fn compile_rainbow_query(
+ grammar: Grammar,
+ config: &LanguageConfiguration,
+ ) -> Result<Option<RainbowQuery>> {
+ let name = &config.language_id;
+ let text = read_query(name, "rainbows.scm");
+ if text.is_empty() {
+ return Ok(None);
+ }
+ let rainbow_query = RainbowQuery::new(grammar, &text)
+ .with_context(|| format!("Failed to compile rainbows.scm query for '{name}'"))?;
+ Ok(Some(rainbow_query))
+ }
+
+ fn rainbow_query(&self, loader: &Loader) -> Option<&RainbowQuery> {
+ self.rainbow_query
+ .get_or_init(|| {
+ let grammar = self.syntax_config(loader)?.grammar;
+ Self::compile_rainbow_query(grammar, &self.config)
+ .map_err(|err| {
+ log::error!("{err}");
+ })
+ .ok()
+ .flatten()
+ })
+ .as_ref()
+ }
+
fn reconfigure(&self, scopes: &[String]) {
if let Some(Some(config)) = self.syntax.get() {
reconfigure_highlights(config, scopes);
@@ -387,6 +420,10 @@ impl Loader {
self.language(lang).tag_query(self)
}
+ fn rainbow_query(&self, lang: Language) -> Option<&RainbowQuery> {
+ self.language(lang).rainbow_query(self)
+ }
+
pub fn language_server_configs(&self) -> &HashMap<String, LanguageServerConfiguration> {
&self.language_server_configs
}
@@ -572,6 +609,79 @@ impl Syntax {
range,
)
}
+
+ pub fn rainbow_highlights(
+ &self,
+ source: RopeSlice,
+ rainbow_length: usize,
+ loader: &Loader,
+ range: impl RangeBounds<u32>,
+ ) -> OverlayHighlights {
+ struct RainbowScope<'tree> {
+ end: u32,
+ node: Option<Node<'tree>>,
+ highlight: Highlight,
+ }
+
+ let mut scope_stack = Vec::<RainbowScope>::new();
+ let mut highlights = Vec::new();
+ let mut query_iter = self.query_iter::<_, (), _>(
+ source,
+ |lang| loader.rainbow_query(lang).map(|q| &q.query),
+ range,
+ );
+
+ while let Some(event) = query_iter.next() {
+ let QueryIterEvent::Match(mat) = event else {
+ continue;
+ };
+
+ let rainbow_query = loader
+ .rainbow_query(query_iter.current_language())
+ .expect("language must have a rainbow query to emit matches");
+
+ let byte_range = mat.node.byte_range();
+ // Pop any scopes that end before this capture begins.
+ while scope_stack
+ .last()
+ .is_some_and(|scope| byte_range.start >= scope.end)
+ {
+ scope_stack.pop();
+ }
+
+ let capture = Some(mat.capture);
+ if capture == rainbow_query.scope_capture {
+ scope_stack.push(RainbowScope {
+ end: byte_range.end,
+ node: if rainbow_query
+ .include_children_patterns
+ .contains(&mat.pattern)
+ {
+ None
+ } else {
+ Some(mat.node.clone())
+ },
+ highlight: Highlight::new((scope_stack.len() % rainbow_length) as u32),
+ });
+ } else if capture == rainbow_query.bracket_capture {
+ if let Some(scope) = scope_stack.last() {
+ if !scope
+ .node
+ .as_ref()
+ .is_some_and(|node| mat.node.parent().as_ref() != Some(node))
+ {
+ let start = source
+ .byte_to_char(source.floor_char_boundary(byte_range.start as usize));
+ let end =
+ source.byte_to_char(source.ceil_char_boundary(byte_range.end as usize));
+ highlights.push((scope.highlight, start..end));
+ }
+ }
+ }
+ }
+
+ OverlayHighlights::Heterogenous { highlights }
+ }
}
pub type Highlighter<'a> = highlighter::Highlighter<'a, 'a, Loader>;
@@ -1019,6 +1129,57 @@ fn pretty_print_tree_impl<W: fmt::Write>(
Ok(())
}
+/// Finds the child of `node` which contains the given byte range.
+
+pub fn child_for_byte_range<'a>(node: &Node<'a>, range: ops::Range<u32>) -> Option<Node<'a>> {
+ for child in node.children() {
+ let child_range = child.byte_range();
+
+ if range.start >= child_range.start && range.end <= child_range.end {
+ return Some(child);
+ }
+ }
+
+ None
+}
+
+#[derive(Debug)]
+pub struct RainbowQuery {
+ query: Query,
+ include_children_patterns: HashSet<Pattern>,
+ scope_capture: Option<Capture>,
+ bracket_capture: Option<Capture>,
+}
+
+impl RainbowQuery {
+ fn new(grammar: Grammar, source: &str) -> Result<Self, tree_sitter::query::ParseError> {
+ let mut include_children_patterns = HashSet::default();
+
+ let query = Query::new(grammar, source, |pattern, predicate| match predicate {
+ UserPredicate::SetProperty {
+ key: "rainbow.include-children",
+ val,
+ } => {
+ if val.is_some() {
+ return Err(
+ "property 'rainbow.include-children' does not take an argument".into(),
+ );
+ }
+ include_children_patterns.insert(pattern);
+ Ok(())
+ }
+ _ => Err(InvalidPredicateError::unknown(predicate)),
+ })?;
+
+ Ok(Self {
+ include_children_patterns,
+ scope_capture: query.get_capture("rainbow.scope"),
+ bracket_capture: query.get_capture("rainbow.bracket"),
+ query,
+ })
+ }
+}
+
#[cfg(test)]
mod test {
use once_cell::sync::Lazy;