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