language.rs

  1mod buffer;
  2mod diagnostic_set;
  3mod highlight_map;
  4mod outline;
  5pub mod proto;
  6#[cfg(test)]
  7mod tests;
  8
  9use anyhow::{anyhow, Result};
 10pub use buffer::Operation;
 11pub use buffer::*;
 12use collections::HashSet;
 13pub use diagnostic_set::DiagnosticEntry;
 14use gpui::AppContext;
 15use highlight_map::HighlightMap;
 16use lazy_static::lazy_static;
 17pub use outline::{Outline, OutlineItem};
 18use parking_lot::Mutex;
 19use serde::Deserialize;
 20use std::{ops::Range, path::Path, str, sync::Arc};
 21use theme::SyntaxTheme;
 22use tree_sitter::{self, Query};
 23pub use tree_sitter::{Parser, Tree};
 24
 25lazy_static! {
 26    pub static ref PLAIN_TEXT: Arc<Language> = Arc::new(Language::new(
 27        LanguageConfig {
 28            name: "Plain Text".to_string(),
 29            path_suffixes: Default::default(),
 30            brackets: Default::default(),
 31            line_comment: None,
 32            language_server: None,
 33        },
 34        None,
 35    ));
 36}
 37
 38pub trait ToPointUtf16 {
 39    fn to_point_utf16(self) -> PointUtf16;
 40}
 41
 42#[derive(Default, Deserialize)]
 43pub struct LanguageConfig {
 44    pub name: String,
 45    pub path_suffixes: Vec<String>,
 46    pub brackets: Vec<BracketPair>,
 47    pub line_comment: Option<String>,
 48    pub language_server: Option<LanguageServerConfig>,
 49}
 50
 51#[derive(Default, Deserialize)]
 52pub struct LanguageServerConfig {
 53    pub binary: String,
 54    pub disk_based_diagnostic_sources: HashSet<String>,
 55    pub disk_based_diagnostics_progress_token: Option<String>,
 56    #[cfg(any(test, feature = "test-support"))]
 57    #[serde(skip)]
 58    pub fake_server: Option<(Arc<lsp::LanguageServer>, Arc<std::sync::atomic::AtomicBool>)>,
 59}
 60
 61#[derive(Clone, Debug, Deserialize)]
 62pub struct BracketPair {
 63    pub start: String,
 64    pub end: String,
 65    pub close: bool,
 66    pub newline: bool,
 67}
 68
 69pub struct Language {
 70    pub(crate) config: LanguageConfig,
 71    pub(crate) grammar: Option<Arc<Grammar>>,
 72}
 73
 74pub struct Grammar {
 75    pub(crate) ts_language: tree_sitter::Language,
 76    pub(crate) highlights_query: Query,
 77    pub(crate) brackets_query: Query,
 78    pub(crate) indents_query: Query,
 79    pub(crate) outline_query: Query,
 80    pub(crate) highlight_map: Mutex<HighlightMap>,
 81}
 82
 83#[derive(Default)]
 84pub struct LanguageRegistry {
 85    languages: Vec<Arc<Language>>,
 86}
 87
 88impl LanguageRegistry {
 89    pub fn new() -> Self {
 90        Self::default()
 91    }
 92
 93    pub fn add(&mut self, language: Arc<Language>) {
 94        self.languages.push(language);
 95    }
 96
 97    pub fn set_theme(&self, theme: &SyntaxTheme) {
 98        for language in &self.languages {
 99            language.set_theme(theme);
100        }
101    }
102
103    pub fn get_language(&self, name: &str) -> Option<&Arc<Language>> {
104        self.languages
105            .iter()
106            .find(|language| language.name() == name)
107    }
108
109    pub fn select_language(&self, path: impl AsRef<Path>) -> Option<&Arc<Language>> {
110        let path = path.as_ref();
111        let filename = path.file_name().and_then(|name| name.to_str());
112        let extension = path.extension().and_then(|name| name.to_str());
113        let path_suffixes = [extension, filename];
114        self.languages.iter().find(|language| {
115            language
116                .config
117                .path_suffixes
118                .iter()
119                .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())))
120        })
121    }
122}
123
124impl Language {
125    pub fn new(config: LanguageConfig, ts_language: Option<tree_sitter::Language>) -> Self {
126        Self {
127            config,
128            grammar: ts_language.map(|ts_language| {
129                Arc::new(Grammar {
130                    brackets_query: Query::new(ts_language, "").unwrap(),
131                    highlights_query: Query::new(ts_language, "").unwrap(),
132                    indents_query: Query::new(ts_language, "").unwrap(),
133                    outline_query: Query::new(ts_language, "").unwrap(),
134                    ts_language,
135                    highlight_map: Default::default(),
136                })
137            }),
138        }
139    }
140
141    pub fn with_highlights_query(mut self, source: &str) -> Result<Self> {
142        let grammar = self
143            .grammar
144            .as_mut()
145            .and_then(Arc::get_mut)
146            .ok_or_else(|| anyhow!("grammar does not exist or is already being used"))?;
147        grammar.highlights_query = Query::new(grammar.ts_language, source)?;
148        Ok(self)
149    }
150
151    pub fn with_brackets_query(mut self, source: &str) -> Result<Self> {
152        let grammar = self
153            .grammar
154            .as_mut()
155            .and_then(Arc::get_mut)
156            .ok_or_else(|| anyhow!("grammar does not exist or is already being used"))?;
157        grammar.brackets_query = Query::new(grammar.ts_language, source)?;
158        Ok(self)
159    }
160
161    pub fn with_indents_query(mut self, source: &str) -> Result<Self> {
162        let grammar = self
163            .grammar
164            .as_mut()
165            .and_then(Arc::get_mut)
166            .ok_or_else(|| anyhow!("grammar does not exist or is already being used"))?;
167        grammar.indents_query = Query::new(grammar.ts_language, source)?;
168        Ok(self)
169    }
170
171    pub fn with_outline_query(mut self, source: &str) -> Result<Self> {
172        let grammar = self
173            .grammar
174            .as_mut()
175            .and_then(Arc::get_mut)
176            .ok_or_else(|| anyhow!("grammar does not exist or is already being used"))?;
177        grammar.outline_query = Query::new(grammar.ts_language, source)?;
178        Ok(self)
179    }
180
181    pub fn name(&self) -> &str {
182        self.config.name.as_str()
183    }
184
185    pub fn line_comment_prefix(&self) -> Option<&str> {
186        self.config.line_comment.as_deref()
187    }
188
189    pub fn start_server(
190        &self,
191        root_path: &Path,
192        cx: &AppContext,
193    ) -> Result<Option<Arc<lsp::LanguageServer>>> {
194        if let Some(config) = &self.config.language_server {
195            #[cfg(any(test, feature = "test-support"))]
196            if let Some((server, started)) = &config.fake_server {
197                started.store(true, std::sync::atomic::Ordering::SeqCst);
198                return Ok(Some(server.clone()));
199            }
200
201            const ZED_BUNDLE: Option<&'static str> = option_env!("ZED_BUNDLE");
202            let binary_path = if ZED_BUNDLE.map_or(Ok(false), |b| b.parse())? {
203                cx.platform()
204                    .path_for_resource(Some(&config.binary), None)?
205            } else {
206                Path::new(&config.binary).to_path_buf()
207            };
208            lsp::LanguageServer::new(&binary_path, root_path, cx.background().clone()).map(Some)
209        } else {
210            Ok(None)
211        }
212    }
213
214    pub fn disk_based_diagnostic_sources(&self) -> Option<&HashSet<String>> {
215        self.config
216            .language_server
217            .as_ref()
218            .map(|config| &config.disk_based_diagnostic_sources)
219    }
220
221    pub fn disk_based_diagnostics_progress_token(&self) -> Option<&String> {
222        self.config
223            .language_server
224            .as_ref()
225            .and_then(|config| config.disk_based_diagnostics_progress_token.as_ref())
226    }
227
228    pub fn brackets(&self) -> &[BracketPair] {
229        &self.config.brackets
230    }
231
232    pub fn set_theme(&self, theme: &SyntaxTheme) {
233        if let Some(grammar) = self.grammar.as_ref() {
234            *grammar.highlight_map.lock() =
235                HighlightMap::new(grammar.highlights_query.capture_names(), theme);
236        }
237    }
238}
239
240impl Grammar {
241    pub fn highlight_map(&self) -> HighlightMap {
242        self.highlight_map.lock().clone()
243    }
244}
245
246#[cfg(any(test, feature = "test-support"))]
247impl LanguageServerConfig {
248    pub async fn fake(
249        executor: Arc<gpui::executor::Background>,
250    ) -> (Self, lsp::FakeLanguageServer) {
251        let (server, fake) = lsp::LanguageServer::fake(executor).await;
252        fake.started
253            .store(false, std::sync::atomic::Ordering::SeqCst);
254        let started = fake.started.clone();
255        (
256            Self {
257                fake_server: Some((server, started)),
258                disk_based_diagnostics_progress_token: Some("fakeServer/check".to_string()),
259                ..Default::default()
260            },
261            fake,
262        )
263    }
264}
265
266impl ToPointUtf16 for lsp::Position {
267    fn to_point_utf16(self) -> PointUtf16 {
268        PointUtf16::new(self.line, self.character)
269    }
270}
271
272pub fn range_from_lsp(range: lsp::Range) -> Range<PointUtf16> {
273    let start = PointUtf16::new(range.start.line, range.start.character);
274    let end = PointUtf16::new(range.end.line, range.end.character);
275    start..end
276}