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
 42pub trait ToLspPosition {
 43    fn to_lsp_position(self) -> lsp::Position;
 44}
 45
 46pub trait DiagnosticProcessor: 'static + Send + Sync {
 47    fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams);
 48}
 49
 50#[derive(Default, Deserialize)]
 51pub struct LanguageConfig {
 52    pub name: String,
 53    pub path_suffixes: Vec<String>,
 54    pub brackets: Vec<BracketPair>,
 55    pub line_comment: Option<String>,
 56    pub language_server: Option<LanguageServerConfig>,
 57}
 58
 59#[derive(Default, Deserialize)]
 60pub struct LanguageServerConfig {
 61    pub binary: String,
 62    pub disk_based_diagnostic_sources: HashSet<String>,
 63    pub disk_based_diagnostics_progress_token: Option<String>,
 64    #[cfg(any(test, feature = "test-support"))]
 65    #[serde(skip)]
 66    pub fake_server: Option<(Arc<lsp::LanguageServer>, Arc<std::sync::atomic::AtomicBool>)>,
 67}
 68
 69#[derive(Clone, Debug, Deserialize)]
 70pub struct BracketPair {
 71    pub start: String,
 72    pub end: String,
 73    pub close: bool,
 74    pub newline: bool,
 75}
 76
 77pub struct Language {
 78    pub(crate) config: LanguageConfig,
 79    pub(crate) grammar: Option<Arc<Grammar>>,
 80    pub(crate) diagnostic_processor: Option<Box<dyn DiagnosticProcessor>>,
 81}
 82
 83pub struct Grammar {
 84    pub(crate) ts_language: tree_sitter::Language,
 85    pub(crate) highlights_query: Query,
 86    pub(crate) brackets_query: Query,
 87    pub(crate) indents_query: Query,
 88    pub(crate) outline_query: Query,
 89    pub(crate) highlight_map: Mutex<HighlightMap>,
 90}
 91
 92#[derive(Default)]
 93pub struct LanguageRegistry {
 94    languages: Vec<Arc<Language>>,
 95}
 96
 97impl LanguageRegistry {
 98    pub fn new() -> Self {
 99        Self::default()
100    }
101
102    pub fn add(&mut self, language: Arc<Language>) {
103        self.languages.push(language);
104    }
105
106    pub fn set_theme(&self, theme: &SyntaxTheme) {
107        for language in &self.languages {
108            language.set_theme(theme);
109        }
110    }
111
112    pub fn get_language(&self, name: &str) -> Option<&Arc<Language>> {
113        self.languages
114            .iter()
115            .find(|language| language.name() == name)
116    }
117
118    pub fn select_language(&self, path: impl AsRef<Path>) -> Option<&Arc<Language>> {
119        let path = path.as_ref();
120        let filename = path.file_name().and_then(|name| name.to_str());
121        let extension = path.extension().and_then(|name| name.to_str());
122        let path_suffixes = [extension, filename];
123        self.languages.iter().find(|language| {
124            language
125                .config
126                .path_suffixes
127                .iter()
128                .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())))
129        })
130    }
131}
132
133impl Language {
134    pub fn new(config: LanguageConfig, ts_language: Option<tree_sitter::Language>) -> Self {
135        Self {
136            config,
137            grammar: ts_language.map(|ts_language| {
138                Arc::new(Grammar {
139                    brackets_query: Query::new(ts_language, "").unwrap(),
140                    highlights_query: Query::new(ts_language, "").unwrap(),
141                    indents_query: Query::new(ts_language, "").unwrap(),
142                    outline_query: Query::new(ts_language, "").unwrap(),
143                    ts_language,
144                    highlight_map: Default::default(),
145                })
146            }),
147            diagnostic_processor: None,
148        }
149    }
150
151    pub fn with_highlights_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.highlights_query = Query::new(grammar.ts_language, source)?;
158        Ok(self)
159    }
160
161    pub fn with_brackets_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.brackets_query = Query::new(grammar.ts_language, source)?;
168        Ok(self)
169    }
170
171    pub fn with_indents_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.indents_query = Query::new(grammar.ts_language, source)?;
178        Ok(self)
179    }
180
181    pub fn with_outline_query(mut self, source: &str) -> Result<Self> {
182        let grammar = self
183            .grammar
184            .as_mut()
185            .and_then(Arc::get_mut)
186            .ok_or_else(|| anyhow!("grammar does not exist or is already being used"))?;
187        grammar.outline_query = Query::new(grammar.ts_language, source)?;
188        Ok(self)
189    }
190
191    pub fn with_diagnostics_processor(mut self, processor: impl DiagnosticProcessor) -> Self {
192        self.diagnostic_processor = Some(Box::new(processor));
193        self
194    }
195
196    pub fn name(&self) -> &str {
197        self.config.name.as_str()
198    }
199
200    pub fn line_comment_prefix(&self) -> Option<&str> {
201        self.config.line_comment.as_deref()
202    }
203
204    pub fn start_server(
205        &self,
206        root_path: &Path,
207        cx: &AppContext,
208    ) -> Result<Option<Arc<lsp::LanguageServer>>> {
209        if let Some(config) = &self.config.language_server {
210            #[cfg(any(test, feature = "test-support"))]
211            if let Some((server, started)) = &config.fake_server {
212                started.store(true, std::sync::atomic::Ordering::SeqCst);
213                return Ok(Some(server.clone()));
214            }
215
216            const ZED_BUNDLE: Option<&'static str> = option_env!("ZED_BUNDLE");
217            let binary_path = if ZED_BUNDLE.map_or(Ok(false), |b| b.parse())? {
218                cx.platform()
219                    .path_for_resource(Some(&config.binary), None)?
220            } else {
221                Path::new(&config.binary).to_path_buf()
222            };
223            lsp::LanguageServer::new(&binary_path, root_path, cx.background().clone()).map(Some)
224        } else {
225            Ok(None)
226        }
227    }
228
229    pub fn disk_based_diagnostic_sources(&self) -> Option<&HashSet<String>> {
230        self.config
231            .language_server
232            .as_ref()
233            .map(|config| &config.disk_based_diagnostic_sources)
234    }
235
236    pub fn disk_based_diagnostics_progress_token(&self) -> Option<&String> {
237        self.config
238            .language_server
239            .as_ref()
240            .and_then(|config| config.disk_based_diagnostics_progress_token.as_ref())
241    }
242
243    pub fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams) {
244        if let Some(processor) = self.diagnostic_processor.as_ref() {
245            processor.process_diagnostics(diagnostics);
246        }
247    }
248
249    pub fn brackets(&self) -> &[BracketPair] {
250        &self.config.brackets
251    }
252
253    pub fn set_theme(&self, theme: &SyntaxTheme) {
254        if let Some(grammar) = self.grammar.as_ref() {
255            *grammar.highlight_map.lock() =
256                HighlightMap::new(grammar.highlights_query.capture_names(), theme);
257        }
258    }
259}
260
261impl Grammar {
262    pub fn highlight_map(&self) -> HighlightMap {
263        self.highlight_map.lock().clone()
264    }
265}
266
267#[cfg(any(test, feature = "test-support"))]
268impl LanguageServerConfig {
269    pub async fn fake(
270        executor: Arc<gpui::executor::Background>,
271    ) -> (Self, lsp::FakeLanguageServer) {
272        let (server, fake) = lsp::LanguageServer::fake(executor).await;
273        fake.started
274            .store(false, std::sync::atomic::Ordering::SeqCst);
275        let started = fake.started.clone();
276        (
277            Self {
278                fake_server: Some((server, started)),
279                disk_based_diagnostics_progress_token: Some("fakeServer/check".to_string()),
280                ..Default::default()
281            },
282            fake,
283        )
284    }
285}
286
287impl ToPointUtf16 for lsp::Position {
288    fn to_point_utf16(self) -> PointUtf16 {
289        PointUtf16::new(self.line, self.character)
290    }
291}
292
293impl ToLspPosition for PointUtf16 {
294    fn to_lsp_position(self) -> lsp::Position {
295        lsp::Position::new(self.row, self.column)
296    }
297}
298
299pub fn range_from_lsp(range: lsp::Range) -> Range<PointUtf16> {
300    let start = PointUtf16::new(range.start.line, range.start.character);
301    let end = PointUtf16::new(range.end.line, range.end.character);
302    start..end
303}