Skip to main content

reovim_module_treesitter_bash/
lib.rs

1#![cfg_attr(coverage_nightly, allow(unused_features))]
2#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
3//! Bash syntax highlighting module for reovim.
4//!
5//! Provides Bash language support for syntax highlighting
6//! using tree-sitter-bash and the `reovim-driver-syntax-treesitter` driver.
7//!
8//! # Architecture
9//!
10//! ```text
11//! reovim-driver-syntax           (trait definitions + SyntaxFactoryStore)
12//!         ^
13//!         |
14//! reovim-driver-syntax-treesitter  (generic tree-sitter driver)
15//!         ^
16//!         |
17//! reovim-module-treesitter-bash    (THIS CRATE - Module + Bash grammar)
18//! ```
19//!
20//! # Example
21//!
22//! ```
23//! use reovim_module_treesitter_bash::BashSyntaxFactory;
24//! use reovim_driver_syntax::SyntaxDriverFactory;
25//!
26//! let factory = BashSyntaxFactory::new();
27//! let mut driver = factory.create("bash").expect("Bash is supported");
28//!
29//! driver.parse("#!/bin/bash\necho \"hello\"");
30//! let highlights = driver.highlights(0..100);
31//!
32//! assert!(!highlights.is_empty());
33//! ```
34
35use std::sync::Arc;
36
37use {
38    reovim_driver_syntax::{
39        CommentTokens, LanguageInfo, LanguageInfoStore, SyntaxDriver, SyntaxDriverFactory,
40        SyntaxFactoryStore,
41    },
42    reovim_driver_syntax_treesitter::{Language, Query, TreeSitterDriver},
43    reovim_kernel::api::v1::{Module, ModuleContext, ModuleError, ModuleId, ProbeResult, Version},
44};
45
46/// Bash highlights query (embedded from queries/highlights.scm)
47const BASH_HIGHLIGHTS_QUERY: &str = include_str!("queries/highlights.scm");
48
49/// Bash folds query (embedded from queries/folds.scm)
50const BASH_FOLDS_QUERY: &str = include_str!("queries/folds.scm");
51
52/// Bash context query (embedded from queries/context.scm)
53const BASH_CONTEXT_QUERY: &str = include_str!("queries/context.scm");
54
55/// Factory for creating Bash syntax drivers.
56///
57/// This factory creates `TreeSitterDriver` instances configured for
58/// Bash syntax highlighting and fold detection using the tree-sitter-bash grammar.
59#[allow(clippy::struct_field_names)]
60pub struct BashSyntaxFactory {
61    /// Pre-compiled highlights query
62    highlight_query: Arc<Query>,
63    /// Pre-compiled folds query
64    folds_query: Arc<Query>,
65    /// Pre-compiled context query
66    context_query: Arc<Query>,
67}
68
69impl BashSyntaxFactory {
70    /// Create a new Bash syntax factory.
71    ///
72    /// Pre-compiles the highlights and folds queries for efficiency.
73    ///
74    /// # Panics
75    ///
76    /// Panics if the embedded queries fail to compile.
77    /// This should never happen with correctly bundled queries.
78    #[must_use]
79    pub fn new() -> Self {
80        let language: Language = tree_sitter_bash::LANGUAGE.into();
81
82        let highlight_query = Query::new(&language, BASH_HIGHLIGHTS_QUERY)
83            .expect("Failed to compile Bash highlights query");
84
85        let folds_query =
86            Query::new(&language, BASH_FOLDS_QUERY).expect("Failed to compile Bash folds query");
87
88        let context_query = Query::new(&language, BASH_CONTEXT_QUERY)
89            .expect("Failed to compile Bash context query");
90
91        Self {
92            highlight_query: Arc::new(highlight_query),
93            folds_query: Arc::new(folds_query),
94            context_query: Arc::new(context_query),
95        }
96    }
97
98    /// Get the shared folds query.
99    #[must_use]
100    pub const fn folds_query(&self) -> &Arc<Query> {
101        &self.folds_query
102    }
103}
104
105impl Default for BashSyntaxFactory {
106    fn default() -> Self {
107        Self::new()
108    }
109}
110
111impl SyntaxDriverFactory for BashSyntaxFactory {
112    fn create(&self, language_id: &str) -> Option<Box<dyn SyntaxDriver>> {
113        if language_id != "bash" {
114            return None;
115        }
116
117        let language: Language = tree_sitter_bash::LANGUAGE.into();
118
119        TreeSitterDriver::builder("bash", &language, self.highlight_query.clone())
120            .folds_query(self.folds_query.clone())
121            .context_query(self.context_query.clone())
122            .build()
123            .map(|d| Box::new(d) as Box<dyn SyntaxDriver>)
124    }
125
126    fn supported_languages(&self) -> Vec<&str> {
127        vec!["bash"]
128    }
129
130    fn supports(&self, language_id: &str) -> bool {
131        language_id == "bash"
132    }
133}
134
135// ============================================================================
136// Module Implementation (Self-Registration Pattern)
137// ============================================================================
138
139/// Treesitter Bash syntax module.
140///
141/// Follows the self-registration pattern:
142/// - Implements `Module` trait
143/// - Registers `BashSyntaxFactory` into `SyntaxFactoryStore` during `init()`
144pub struct TreesitterBashModule;
145
146impl TreesitterBashModule {
147    /// Create a new Treesitter Bash module.
148    #[must_use]
149    pub const fn new() -> Self {
150        Self
151    }
152}
153
154impl Default for TreesitterBashModule {
155    fn default() -> Self {
156        Self::new()
157    }
158}
159
160impl Module for TreesitterBashModule {
161    fn id(&self) -> ModuleId {
162        ModuleId::new("treesitter-bash")
163    }
164
165    fn name(&self) -> &'static str {
166        "Treesitter Bash"
167    }
168
169    fn version(&self) -> Version {
170        Version::new(0, 10, 0)
171    }
172
173    #[cfg_attr(coverage_nightly, coverage(off))]
174    fn init(&mut self, ctx: &ModuleContext) -> ProbeResult {
175        let factory = Arc::new(BashSyntaxFactory::new());
176
177        let syntax_store = ctx.services.get_or_create::<SyntaxFactoryStore>();
178        syntax_store.add(factory);
179
180        let lang_store = ctx.services.get_or_create::<LanguageInfoStore>();
181        lang_store.add(
182            LanguageInfo::new("bash", "Bash")
183                .with_extensions(["sh", "bash", "zsh"])
184                .with_comments(CommentTokens::line_only("#")),
185        );
186
187        tracing::info!("TreesitterBashModule: registered Bash syntax factory");
188        ProbeResult::Success
189    }
190
191    fn exit(&mut self) -> Result<(), ModuleError> {
192        tracing::info!("TreesitterBashModule: exiting");
193        Ok(())
194    }
195}
196
197#[cfg(test)]
198#[path = "lib_tests.rs"]
199mod tests;