language.rs

  1mod buffer;
  2mod diagnostic_set;
  3mod highlight_map;
  4mod outline;
  5pub mod proto;
  6#[cfg(test)]
  7mod tests;
  8
  9use anyhow::{anyhow, Context, Result};
 10use client::http::{self, HttpClient};
 11use collections::HashSet;
 12use futures::{
 13    future::{BoxFuture, Shared},
 14    FutureExt, TryFutureExt,
 15};
 16use gpui::{MutableAppContext, Task};
 17use highlight_map::HighlightMap;
 18use lazy_static::lazy_static;
 19use parking_lot::{Mutex, RwLock};
 20use serde::Deserialize;
 21use serde_json::Value;
 22use std::{
 23    cell::RefCell,
 24    ops::Range,
 25    path::{Path, PathBuf},
 26    str,
 27    sync::Arc,
 28};
 29use theme::SyntaxTheme;
 30use tree_sitter::{self, Query};
 31use util::ResultExt;
 32
 33#[cfg(any(test, feature = "test-support"))]
 34use futures::channel::mpsc;
 35
 36pub use buffer::Operation;
 37pub use buffer::*;
 38pub use diagnostic_set::DiagnosticEntry;
 39pub use outline::{Outline, OutlineItem};
 40pub use tree_sitter::{Parser, Tree};
 41
 42thread_local! {
 43    static PARSER: RefCell<Parser>  = RefCell::new(Parser::new());
 44}
 45
 46lazy_static! {
 47    pub static ref PLAIN_TEXT: Arc<Language> = Arc::new(Language::new(
 48        LanguageConfig {
 49            name: "Plain Text".into(),
 50            path_suffixes: Default::default(),
 51            brackets: Default::default(),
 52            autoclose_before: Default::default(),
 53            line_comment: None,
 54            language_server: None,
 55        },
 56        None,
 57    ));
 58}
 59
 60pub trait ToLspPosition {
 61    fn to_lsp_position(self) -> lsp::Position;
 62}
 63
 64pub struct LspBinaryVersion {
 65    pub name: String,
 66    pub url: Option<http::Url>,
 67}
 68
 69pub trait LspAdapter: 'static + Send + Sync {
 70    fn name(&self) -> &'static str;
 71    fn fetch_latest_server_version(
 72        &self,
 73        http: Arc<dyn HttpClient>,
 74    ) -> BoxFuture<'static, Result<LspBinaryVersion>>;
 75    fn fetch_server_binary(
 76        &self,
 77        version: LspBinaryVersion,
 78        http: Arc<dyn HttpClient>,
 79        container_dir: PathBuf,
 80    ) -> BoxFuture<'static, Result<PathBuf>>;
 81    fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option<PathBuf>>;
 82    fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams);
 83
 84    fn label_for_completion(&self, _: &lsp::CompletionItem, _: &Language) -> Option<CodeLabel> {
 85        None
 86    }
 87
 88    fn label_for_symbol(&self, _: &str, _: lsp::SymbolKind, _: &Language) -> Option<CodeLabel> {
 89        None
 90    }
 91
 92    fn server_args(&self) -> &[&str] {
 93        &[]
 94    }
 95
 96    fn initialization_options(&self) -> Option<Value> {
 97        None
 98    }
 99}
100
101#[derive(Clone, Debug, PartialEq, Eq)]
102pub struct CodeLabel {
103    pub text: String,
104    pub runs: Vec<(Range<usize>, HighlightId)>,
105    pub filter_range: Range<usize>,
106}
107
108#[derive(Deserialize)]
109pub struct LanguageConfig {
110    pub name: Arc<str>,
111    pub path_suffixes: Vec<String>,
112    pub brackets: Vec<BracketPair>,
113    #[serde(default)]
114    pub autoclose_before: String,
115    pub line_comment: Option<String>,
116    pub language_server: Option<LanguageServerConfig>,
117}
118
119impl Default for LanguageConfig {
120    fn default() -> Self {
121        Self {
122            name: "".into(),
123            path_suffixes: Default::default(),
124            brackets: Default::default(),
125            autoclose_before: Default::default(),
126            line_comment: Default::default(),
127            language_server: Default::default(),
128        }
129    }
130}
131
132#[derive(Default, Deserialize)]
133pub struct LanguageServerConfig {
134    pub disk_based_diagnostic_sources: HashSet<String>,
135    pub disk_based_diagnostics_progress_token: Option<String>,
136    #[cfg(any(test, feature = "test-support"))]
137    #[serde(skip)]
138    fake_config: Option<FakeLanguageServerConfig>,
139}
140
141#[cfg(any(test, feature = "test-support"))]
142struct FakeLanguageServerConfig {
143    servers_tx: mpsc::UnboundedSender<lsp::FakeLanguageServer>,
144    capabilities: lsp::ServerCapabilities,
145    initializer: Option<Box<dyn 'static + Send + Sync + Fn(&mut lsp::FakeLanguageServer)>>,
146}
147
148#[derive(Clone, Debug, Deserialize)]
149pub struct BracketPair {
150    pub start: String,
151    pub end: String,
152    pub close: bool,
153    pub newline: bool,
154}
155
156pub struct Language {
157    pub(crate) config: LanguageConfig,
158    pub(crate) grammar: Option<Arc<Grammar>>,
159    pub(crate) adapter: Option<Arc<dyn LspAdapter>>,
160    lsp_binary_path: Mutex<Option<Shared<BoxFuture<'static, Result<PathBuf, Arc<anyhow::Error>>>>>>,
161}
162
163pub struct Grammar {
164    pub(crate) ts_language: tree_sitter::Language,
165    pub(crate) highlights_query: Query,
166    pub(crate) brackets_query: Query,
167    pub(crate) indents_query: Query,
168    pub(crate) outline_query: Query,
169    pub(crate) highlight_map: Mutex<HighlightMap>,
170}
171
172#[derive(Clone)]
173pub enum LanguageServerBinaryStatus {
174    CheckingForUpdate,
175    Downloading,
176    Downloaded,
177    Cached,
178    Failed,
179}
180
181pub struct LanguageRegistry {
182    languages: RwLock<Vec<Arc<Language>>>,
183    language_server_download_dir: Option<Arc<Path>>,
184    lsp_binary_statuses_tx: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
185    lsp_binary_statuses_rx: async_broadcast::Receiver<(Arc<Language>, LanguageServerBinaryStatus)>,
186    login_shell_env_loaded: Shared<Task<()>>,
187}
188
189impl LanguageRegistry {
190    pub fn new(login_shell_env_loaded: Task<()>) -> Self {
191        let (lsp_binary_statuses_tx, lsp_binary_statuses_rx) = async_broadcast::broadcast(16);
192        Self {
193            language_server_download_dir: None,
194            languages: Default::default(),
195            lsp_binary_statuses_tx,
196            lsp_binary_statuses_rx,
197            login_shell_env_loaded: login_shell_env_loaded.shared(),
198        }
199    }
200
201    #[cfg(any(test, feature = "test-support"))]
202    pub fn test() -> Self {
203        Self::new(Task::ready(()))
204    }
205
206    pub fn add(&self, language: Arc<Language>) {
207        self.languages.write().push(language.clone());
208    }
209
210    pub fn set_theme(&self, theme: &SyntaxTheme) {
211        for language in self.languages.read().iter() {
212            language.set_theme(theme);
213        }
214    }
215
216    pub fn set_language_server_download_dir(&mut self, path: impl Into<Arc<Path>>) {
217        self.language_server_download_dir = Some(path.into());
218    }
219
220    pub fn get_language(&self, name: &str) -> Option<Arc<Language>> {
221        self.languages
222            .read()
223            .iter()
224            .find(|language| language.name().as_ref() == name)
225            .cloned()
226    }
227
228    pub fn select_language(&self, path: impl AsRef<Path>) -> Option<Arc<Language>> {
229        let path = path.as_ref();
230        let filename = path.file_name().and_then(|name| name.to_str());
231        let extension = path.extension().and_then(|name| name.to_str());
232        let path_suffixes = [extension, filename];
233        self.languages
234            .read()
235            .iter()
236            .find(|language| {
237                language
238                    .config
239                    .path_suffixes
240                    .iter()
241                    .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())))
242            })
243            .cloned()
244    }
245
246    pub fn start_language_server(
247        &self,
248        language: Arc<Language>,
249        root_path: Arc<Path>,
250        http_client: Arc<dyn HttpClient>,
251        cx: &mut MutableAppContext,
252    ) -> Option<Task<Result<lsp::LanguageServer>>> {
253        #[cfg(any(test, feature = "test-support"))]
254        if language
255            .config
256            .language_server
257            .as_ref()
258            .and_then(|config| config.fake_config.as_ref())
259            .is_some()
260        {
261            let language = language.clone();
262            return Some(cx.spawn(|mut cx| async move {
263                let fake_config = language
264                    .config
265                    .language_server
266                    .as_ref()
267                    .unwrap()
268                    .fake_config
269                    .as_ref()
270                    .unwrap();
271                let (server, mut fake_server) = cx.update(|cx| {
272                    lsp::LanguageServer::fake_with_capabilities(
273                        fake_config.capabilities.clone(),
274                        cx,
275                    )
276                });
277                if let Some(initializer) = &fake_config.initializer {
278                    initializer(&mut fake_server);
279                }
280
281                let servers_tx = fake_config.servers_tx.clone();
282                cx.background()
283                    .spawn(async move {
284                        fake_server
285                            .receive_notification::<lsp::notification::Initialized>()
286                            .await;
287                        servers_tx.unbounded_send(fake_server).ok();
288                    })
289                    .detach();
290                Ok(server)
291            }));
292        }
293
294        let download_dir = self
295            .language_server_download_dir
296            .clone()
297            .ok_or_else(|| anyhow!("language server download directory has not been assigned"))
298            .log_err()?;
299
300        let adapter = language.adapter.clone()?;
301        let background = cx.background().clone();
302        let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone();
303        let login_shell_env_loaded = self.login_shell_env_loaded.clone();
304        Some(cx.background().spawn(async move {
305            login_shell_env_loaded.await;
306            let server_binary_path = language
307                .lsp_binary_path
308                .lock()
309                .get_or_insert_with(|| {
310                    get_server_binary_path(
311                        adapter.clone(),
312                        language.clone(),
313                        http_client,
314                        download_dir,
315                        lsp_binary_statuses,
316                    )
317                    .map_err(Arc::new)
318                    .boxed()
319                    .shared()
320                })
321                .clone()
322                .map_err(|e| anyhow!(e));
323
324            let server_binary_path = server_binary_path.await?;
325            let server_args = adapter.server_args();
326            let server = lsp::LanguageServer::new(
327                &server_binary_path,
328                server_args,
329                &root_path,
330                adapter.initialization_options(),
331                background,
332            )?;
333            Ok(server)
334        }))
335    }
336
337    pub fn language_server_binary_statuses(
338        &self,
339    ) -> async_broadcast::Receiver<(Arc<Language>, LanguageServerBinaryStatus)> {
340        self.lsp_binary_statuses_rx.clone()
341    }
342}
343
344async fn get_server_binary_path(
345    adapter: Arc<dyn LspAdapter>,
346    language: Arc<Language>,
347    http_client: Arc<dyn HttpClient>,
348    download_dir: Arc<Path>,
349    statuses: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
350) -> Result<PathBuf> {
351    let container_dir = download_dir.join(adapter.name());
352    if !container_dir.exists() {
353        smol::fs::create_dir_all(&container_dir)
354            .await
355            .context("failed to create container directory")?;
356    }
357
358    let path = fetch_latest_server_binary_path(
359        adapter.clone(),
360        language.clone(),
361        http_client,
362        &container_dir,
363        statuses.clone(),
364    )
365    .await;
366    if path.is_err() {
367        if let Some(cached_path) = adapter.cached_server_binary(container_dir).await {
368            statuses
369                .broadcast((language.clone(), LanguageServerBinaryStatus::Cached))
370                .await?;
371            return Ok(cached_path);
372        } else {
373            statuses
374                .broadcast((language.clone(), LanguageServerBinaryStatus::Failed))
375                .await?;
376        }
377    }
378    path
379}
380
381async fn fetch_latest_server_binary_path(
382    adapter: Arc<dyn LspAdapter>,
383    language: Arc<Language>,
384    http_client: Arc<dyn HttpClient>,
385    container_dir: &Path,
386    lsp_binary_statuses_tx: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
387) -> Result<PathBuf> {
388    lsp_binary_statuses_tx
389        .broadcast((
390            language.clone(),
391            LanguageServerBinaryStatus::CheckingForUpdate,
392        ))
393        .await?;
394    let version_info = adapter
395        .fetch_latest_server_version(http_client.clone())
396        .await?;
397    lsp_binary_statuses_tx
398        .broadcast((language.clone(), LanguageServerBinaryStatus::Downloading))
399        .await?;
400    let path = adapter
401        .fetch_server_binary(version_info, http_client, container_dir.to_path_buf())
402        .await?;
403    lsp_binary_statuses_tx
404        .broadcast((language.clone(), LanguageServerBinaryStatus::Downloaded))
405        .await?;
406    Ok(path)
407}
408
409impl Language {
410    pub fn new(config: LanguageConfig, ts_language: Option<tree_sitter::Language>) -> Self {
411        Self {
412            config,
413            grammar: ts_language.map(|ts_language| {
414                Arc::new(Grammar {
415                    brackets_query: Query::new(ts_language, "").unwrap(),
416                    highlights_query: Query::new(ts_language, "").unwrap(),
417                    indents_query: Query::new(ts_language, "").unwrap(),
418                    outline_query: Query::new(ts_language, "").unwrap(),
419                    ts_language,
420                    highlight_map: Default::default(),
421                })
422            }),
423            adapter: None,
424            lsp_binary_path: Default::default(),
425        }
426    }
427
428    pub fn with_highlights_query(mut self, source: &str) -> Result<Self> {
429        let grammar = self
430            .grammar
431            .as_mut()
432            .and_then(Arc::get_mut)
433            .ok_or_else(|| anyhow!("grammar does not exist or is already being used"))?;
434        grammar.highlights_query = Query::new(grammar.ts_language, source)?;
435        Ok(self)
436    }
437
438    pub fn with_brackets_query(mut self, source: &str) -> Result<Self> {
439        let grammar = self
440            .grammar
441            .as_mut()
442            .and_then(Arc::get_mut)
443            .ok_or_else(|| anyhow!("grammar does not exist or is already being used"))?;
444        grammar.brackets_query = Query::new(grammar.ts_language, source)?;
445        Ok(self)
446    }
447
448    pub fn with_indents_query(mut self, source: &str) -> Result<Self> {
449        let grammar = self
450            .grammar
451            .as_mut()
452            .and_then(Arc::get_mut)
453            .ok_or_else(|| anyhow!("grammar does not exist or is already being used"))?;
454        grammar.indents_query = Query::new(grammar.ts_language, source)?;
455        Ok(self)
456    }
457
458    pub fn with_outline_query(mut self, source: &str) -> Result<Self> {
459        let grammar = self
460            .grammar
461            .as_mut()
462            .and_then(Arc::get_mut)
463            .ok_or_else(|| anyhow!("grammar does not exist or is already being used"))?;
464        grammar.outline_query = Query::new(grammar.ts_language, source)?;
465        Ok(self)
466    }
467
468    pub fn with_lsp_adapter(mut self, lsp_adapter: impl LspAdapter) -> Self {
469        self.adapter = Some(Arc::new(lsp_adapter));
470        self
471    }
472
473    pub fn name(&self) -> Arc<str> {
474        self.config.name.clone()
475    }
476
477    pub fn line_comment_prefix(&self) -> Option<&str> {
478        self.config.line_comment.as_deref()
479    }
480
481    pub fn disk_based_diagnostic_sources(&self) -> Option<&HashSet<String>> {
482        self.config
483            .language_server
484            .as_ref()
485            .map(|config| &config.disk_based_diagnostic_sources)
486    }
487
488    pub fn disk_based_diagnostics_progress_token(&self) -> Option<&String> {
489        self.config
490            .language_server
491            .as_ref()
492            .and_then(|config| config.disk_based_diagnostics_progress_token.as_ref())
493    }
494
495    pub fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams) {
496        if let Some(processor) = self.adapter.as_ref() {
497            processor.process_diagnostics(diagnostics);
498        }
499    }
500
501    pub fn label_for_completion(&self, completion: &lsp::CompletionItem) -> Option<CodeLabel> {
502        self.adapter
503            .as_ref()?
504            .label_for_completion(completion, self)
505    }
506
507    pub fn label_for_symbol(&self, name: &str, kind: lsp::SymbolKind) -> Option<CodeLabel> {
508        self.adapter.as_ref()?.label_for_symbol(name, kind, self)
509    }
510
511    pub fn highlight_text<'a>(
512        &'a self,
513        text: &'a Rope,
514        range: Range<usize>,
515    ) -> Vec<(Range<usize>, HighlightId)> {
516        let mut result = Vec::new();
517        if let Some(grammar) = &self.grammar {
518            let tree = grammar.parse_text(text, None);
519            let mut offset = 0;
520            for chunk in BufferChunks::new(text, range, Some(&tree), self.grammar.as_ref(), vec![])
521            {
522                let end_offset = offset + chunk.text.len();
523                if let Some(highlight_id) = chunk.syntax_highlight_id {
524                    result.push((offset..end_offset, highlight_id));
525                }
526                offset = end_offset;
527            }
528        }
529        result
530    }
531
532    pub fn brackets(&self) -> &[BracketPair] {
533        &self.config.brackets
534    }
535
536    pub fn should_autoclose_before(&self, c: char) -> bool {
537        c.is_whitespace() || self.config.autoclose_before.contains(c)
538    }
539
540    pub fn set_theme(&self, theme: &SyntaxTheme) {
541        if let Some(grammar) = self.grammar.as_ref() {
542            *grammar.highlight_map.lock() =
543                HighlightMap::new(grammar.highlights_query.capture_names(), theme);
544        }
545    }
546
547    pub fn grammar(&self) -> Option<&Arc<Grammar>> {
548        self.grammar.as_ref()
549    }
550}
551
552impl Grammar {
553    fn parse_text(&self, text: &Rope, old_tree: Option<Tree>) -> Tree {
554        PARSER.with(|parser| {
555            let mut parser = parser.borrow_mut();
556            parser
557                .set_language(self.ts_language)
558                .expect("incompatible grammar");
559            let mut chunks = text.chunks_in_range(0..text.len());
560            parser
561                .parse_with(
562                    &mut move |offset, _| {
563                        chunks.seek(offset);
564                        chunks.next().unwrap_or("").as_bytes()
565                    },
566                    old_tree.as_ref(),
567                )
568                .unwrap()
569        })
570    }
571
572    pub fn highlight_map(&self) -> HighlightMap {
573        self.highlight_map.lock().clone()
574    }
575
576    pub fn highlight_id_for_name(&self, name: &str) -> Option<HighlightId> {
577        let capture_id = self.highlights_query.capture_index_for_name(name)?;
578        Some(self.highlight_map.lock().get(capture_id))
579    }
580}
581
582impl CodeLabel {
583    pub fn plain(text: String, filter_text: Option<&str>) -> Self {
584        let mut result = Self {
585            runs: Vec::new(),
586            filter_range: 0..text.len(),
587            text,
588        };
589        if let Some(filter_text) = filter_text {
590            if let Some(ix) = result.text.find(filter_text) {
591                result.filter_range = ix..ix + filter_text.len();
592            }
593        }
594        result
595    }
596}
597
598#[cfg(any(test, feature = "test-support"))]
599impl LanguageServerConfig {
600    pub fn fake() -> (Self, mpsc::UnboundedReceiver<lsp::FakeLanguageServer>) {
601        let (servers_tx, servers_rx) = mpsc::unbounded();
602        (
603            Self {
604                fake_config: Some(FakeLanguageServerConfig {
605                    servers_tx,
606                    capabilities: lsp::LanguageServer::full_capabilities(),
607                    initializer: None,
608                }),
609                disk_based_diagnostics_progress_token: Some("fakeServer/check".to_string()),
610                ..Default::default()
611            },
612            servers_rx,
613        )
614    }
615
616    pub fn set_fake_capabilities(&mut self, capabilities: lsp::ServerCapabilities) {
617        self.fake_config.as_mut().unwrap().capabilities = capabilities;
618    }
619
620    pub fn set_fake_initializer(
621        &mut self,
622        initializer: impl 'static + Send + Sync + Fn(&mut lsp::FakeLanguageServer),
623    ) {
624        self.fake_config.as_mut().unwrap().initializer = Some(Box::new(initializer));
625    }
626}
627
628impl ToLspPosition for PointUtf16 {
629    fn to_lsp_position(self) -> lsp::Position {
630        lsp::Position::new(self.row, self.column)
631    }
632}
633
634pub fn point_from_lsp(point: lsp::Position) -> PointUtf16 {
635    PointUtf16::new(point.line, point.character)
636}
637
638pub fn range_from_lsp(range: lsp::Range) -> Range<PointUtf16> {
639    let start = PointUtf16::new(range.start.line, range.start.character);
640    let end = PointUtf16::new(range.end.line, range.end.character);
641    start..end
642}