//! The `language` crate provides a large chunk of Zed's language-related//! features (the other big contributors being project and lsp crates that revolve around LSP features).//! Namely, this crate://! - Provides [`Language`], [`Grammar`] and [`LanguageRegistry`] types that//! use Tree-sitter to provide syntax highlighting to the editor; note though that `language` doesn't perform the highlighting by itself. It only maps ranges in a buffer to colors. Treesitter is also used for buffer outlines (lists of symbols in a buffer)//! - Exposes [`LanguageConfig`] that describes how constructs (like brackets or line comments) should be handled by the editor for a source file of a particular language.//!//! Notably we do *not* assign a single language to a single file; in real world a single file can consist of multiple programming languages - HTML is a good example of that - and `language` crate tends to reflect that status quo in its API.mod buffer;mod diagnostic_set;mod highlight_map;mod language_registry;pub mod language_settings;mod outline;pub mod proto;mod syntax_map;mod task_context; #[cfg(test)]mod buffer_tests;pub mod markdown; use crate::language_settings::SoftWrap;use anyhow::{anyhow, Context, Result};use async_trait::async_trait;use collections::{HashMap, HashSet};use futures::Future;use gpui::{AppContext, AsyncAppContext, Model, Task};pub use highlight_map::HighlightMap;use http::HttpClient;use lazy_static::lazy_static;use lsp::{CodeActionKind, LanguageServerBinary};use parking_lot::Mutex;use regex::Regex;use schemars::{ gen::SchemaGenerator, schema::{InstanceType, Schema, SchemaObject}, JsonSchema,};use serde::{de, Deserialize, Deserializer, Serialize, Serializer};use serde_json::Value;use smol::future::FutureExt as _;use std::num::NonZeroU32;use std::{ any::Any, ffi::OsStr, fmt::Debug, hash::Hash, mem, ops::{DerefMut, Range}, path::{Path, PathBuf}, pin::Pin, str, sync::{ atomic::{AtomicU64, AtomicUsize, Ordering::SeqCst}, Arc, },};use syntax_map::{QueryCursorHandle, SyntaxSnapshot};use task::RunnableTag;pub use task_context::{ BasicContextProvider, ContextProvider, ContextProviderWithTasks, RunnableRange,};use theme::SyntaxTheme;use tree_sitter::{self, wasmtime, Query, QueryCursor, WasmStore}; pub use buffer::Operation;pub use buffer::*;pub use diagnostic_set::DiagnosticEntry;pub use language_registry::{ LanguageNotFound, LanguageQueries, LanguageRegistry, LanguageServerBinaryStatus, PendingLanguageServer, QUERY_FILENAME_PREFIXES,};pub use lsp::LanguageServerId;pub use outline::{Outline, OutlineItem};pub use syntax_map::{OwnedSyntaxLayer, SyntaxLayer};pub use text::LineEnding;pub use tree_sitter::{Node, Parser, Tree, TreeCursor}; /// Initializes the `language` crate.////// This should be called before making use of items from the create.pub fn init(cx: &mut AppContext) { language_settings::init(cx);} static QUERY_CURSORS: Mutex<Vec<QueryCursor>> = Mutex::new(vec![]);static PARSERS: Mutex<Vec<Parser>> = Mutex::new(vec![]); pub fn with_parser<F, R>(func: F) -> Rwhere F: FnOnce(&mut Parser) -> R,{ let mut parser = PARSERS.lock().pop().unwrap_or_else(|| { let mut parser = Parser::new(); parser .set_wasm_store(WasmStore::new(WASM_ENGINE.clone()).unwrap()) .unwrap(); parser }); parser.set_included_ranges(&[]).unwrap(); let result = func(&mut parser); PARSERS.lock().push(parser); result} pub fn with_query_cursor<F, R>(func: F) -> Rwhere F: FnOnce(&mut QueryCursor) -> R,{ let mut cursor = QueryCursorHandle::new(); func(cursor.deref_mut())} lazy_static! { static ref NEXT_LANGUAGE_ID: AtomicUsize = Default::default(); static ref NEXT_GRAMMAR_ID: AtomicUsize = Default::default(); static ref WASM_ENGINE: wasmtime::Engine = { wasmtime::Engine::new(&wasmtime::Config::new()).unwrap() }; /// A shared grammar for plain text, exposed for reuse by downstream crates. pub static ref PLAIN_TEXT: Arc<Language> = Arc::new(Language::new( LanguageConfig { name: "Plain Text".into(), soft_wrap: Some(SoftWrap::PreferredLineLength), ..Default::default() }, None, ));} /// Types that represent a position in a buffer, and can be converted into/// an LSP position, to send to a language server.pub trait ToLspPosition { /// Converts the value into an LSP position. fn to_lsp_position(self) -> lsp::Position;} /// A name of a language server.#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]pub struct LanguageServerName(pub Arc<str>); #[derive(Debug, Clone, PartialEq, Eq, Hash)]pub struct Location { pub buffer: Model<Buffer>, pub range: Range<Anchor>,} /// Represents a Language Server, with certain cached sync properties./// Uses [`LspAdapter`] under the hood, but calls all 'static' methods/// once at startup, and caches the results.pub struct CachedLspAdapter { pub name: LanguageServerName, pub disk_based_diagnostic_sources: Vec<String>, pub disk_based_diagnostics_progress_token: Option<String>, language_ids: HashMap<String, String>, pub adapter: Arc<dyn LspAdapter>, pub reinstall_attempt_count: AtomicU64, /// Indicates whether this language server is the primary language server /// for a given language. Currently, most LSP-backed features only work /// with one language server, so one server needs to be primary. pub is_primary: bool, cached_binary: futures::lock::Mutex<Option<LanguageServerBinary>>,} impl CachedLspAdapter { pub fn new(adapter: Arc<dyn LspAdapter>, is_primary: bool) -> Arc<Self> { let name = adapter.name(); let disk_based_diagnostic_sources = adapter.disk_based_diagnostic_sources(); let disk_based_diagnostics_progress_token = adapter.disk_based_diagnostics_progress_token(); let language_ids = adapter.language_ids(); Arc::new(CachedLspAdapter { name, disk_based_diagnostic_sources, disk_based_diagnostics_progress_token, language_ids, adapter, is_primary, cached_binary: Default::default(), reinstall_attempt_count: AtomicU64::new(0), }) } pub async fn get_language_server_command( self: Arc<Self>, language: Arc<Language>, container_dir: Arc<Path>, delegate: Arc<dyn LspAdapterDelegate>, cx: &mut AsyncAppContext, ) -> Result<LanguageServerBinary> { let cached_binary = self.cached_binary.lock().await; self.adapter .clone() .get_language_server_command(language, container_dir, delegate, cached_binary, cx) .await } pub fn will_start_server( &self, delegate: &Arc<dyn LspAdapterDelegate>, cx: &mut AsyncAppContext, ) -> Option<Task<Result<()>>> { self.adapter.will_start_server(delegate, cx) } pub fn can_be_reinstalled(&self) -> bool { self.adapter.can_be_reinstalled() } pub async fn installation_test_binary( &self, container_dir: PathBuf, ) -> Option<LanguageServerBinary> { self.adapter.installation_test_binary(container_dir).await } pub fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> { self.adapter.code_action_kinds() } pub fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) { self.adapter.process_diagnostics(params) } pub async fn process_completions(&self, completion_items: &mut [lsp::CompletionItem]) { self.adapter.process_completions(completion_items).await } pub async fn labels_for_completions( &self, completion_items: &[lsp::CompletionItem], language: &Arc<Language>, ) -> Result<Vec<Option<CodeLabel>>> { self.adapter .clone() .labels_for_completions(completion_items, language) .await } pub async fn labels_for_symbols( &self, symbols: &[(String, lsp::SymbolKind)], language: &Arc<Language>, ) -> Result<Vec<Option<CodeLabel>>> { self.adapter .clone() .labels_for_symbols(symbols, language) .await } pub fn language_id(&self, language: &Language) -> String { self.language_ids .get(language.name().as_ref()) .cloned() .unwrap_or_else(|| language.lsp_id()) } #[cfg(any(test, feature = "test-support"))] fn as_fake(&self) -> Option<&FakeLspAdapter> { self.adapter.as_fake() }} /// [`LspAdapterDelegate`] allows [`LspAdapter]` implementations to interface with the application// e.g. to display a notification or fetch data from the web.#[async_trait]pub trait LspAdapterDelegate: Send + Sync { fn show_notification(&self, message: &str, cx: &mut AppContext); fn http_client(&self) -> Arc<dyn HttpClient>; fn worktree_id(&self) -> u64; fn worktree_root_path(&self) -> &Path; fn update_status(&self, language: LanguageServerName, status: LanguageServerBinaryStatus); async fn which(&self, command: &OsStr) -> Option<PathBuf>; async fn shell_env(&self) -> HashMap<String, String>; async fn read_text_file(&self, path: PathBuf) -> Result<String>;} #[async_trait(?Send)]pub trait LspAdapter: 'static + Send + Sync { fn name(&self) -> LanguageServerName; fn get_language_server_command<'a>( self: Arc<Self>, language: Arc<Language>, container_dir: Arc<Path>, delegate: Arc<dyn LspAdapterDelegate>, mut cached_binary: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>, cx: &'a mut AsyncAppContext, ) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> { async move { // First we check whether the adapter can give us a user-installed binary. // If so, we do *not* want to cache that, because each worktree might give us a different // binary: // // worktree 1: user-installed at `.bin/gopls` // worktree 2: user-installed at `~/bin/gopls` // worktree 3: no gopls found in PATH -> fallback to Zed installation // // We only want to cache when we fall back to the global one, // because we don't want to download and overwrite our global one // for each worktree we might have open. if let Some(binary) = self.check_if_user_installed(delegate.as_ref(), cx).await { log::info!( "found user-installed language server for {}. path: {:?}, arguments: {:?}", language.name(), binary.path, binary.arguments ); return Ok(binary); } if let Some(cached_binary) = cached_binary.as_ref() { return Ok(cached_binary.clone()); } if !container_dir.exists() { smol::fs::create_dir_all(&container_dir) .await .context("failed to create container directory")?; } let mut binary = try_fetch_server_binary(self.as_ref(), &delegate, container_dir.to_path_buf(), cx).await; if let Err(error) = binary.as_ref() { if let Some(prev_downloaded_binary) = self .cached_server_binary(container_dir.to_path_buf(), delegate.as_ref()) .await { log::info!( "failed to fetch newest version of language server {:?}. falling back to using {:?}", self.name(), prev_downloaded_binary.path ); binary = Ok(prev_downloaded_binary); } else { delegate.update_status( self.name(), LanguageServerBinaryStatus::Failed { error: format!("{error:?}"), }, ); } } if let Ok(binary) = &binary { *cached_binary = Some(binary.clone()); } binary } .boxed_local() } async fn check_if_user_installed( &self, _: &dyn LspAdapterDelegate, _: &AsyncAppContext, ) -> Option<LanguageServerBinary> { None } async fn fetch_latest_server_version( &self, delegate: &dyn LspAdapterDelegate, ) -> Result<Box<dyn 'static + Send + Any>>; fn will_fetch_server( &self, _: &Arc<dyn LspAdapterDelegate>, _: &mut AsyncAppContext, ) -> Option<Task<Result<()>>> { None } fn will_start_server( &self, _: &Arc<dyn LspAdapterDelegate>, _: &mut AsyncAppContext, ) -> Option<Task<Result<()>>> { None } async fn fetch_server_binary( &self, latest_version: Box<dyn 'static + Send + Any>, container_dir: PathBuf, delegate: &dyn LspAdapterDelegate, ) -> Result<LanguageServerBinary>; async fn cached_server_binary( &self, container_dir: PathBuf, delegate: &dyn LspAdapterDelegate, ) -> Option<LanguageServerBinary>; /// Returns `true` if a language server can be reinstalled. /// /// If language server initialization fails, a reinstallation will be attempted unless the value returned from this method is `false`. /// /// Implementations that rely on software already installed on user's system /// should have [`can_be_reinstalled`](Self::can_be_reinstalled) return `false`. fn can_be_reinstalled(&self) -> bool { true } async fn installation_test_binary( &self, container_dir: PathBuf, ) -> Option<LanguageServerBinary>; fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} /// Post-processes completions provided by the language server. async fn process_completions(&self, _: &mut [lsp::CompletionItem]) {} async fn labels_for_completions( self: Arc<Self>, completions: &[lsp::CompletionItem], language: &Arc<Language>, ) -> Result<Vec<Option<CodeLabel>>> { let mut labels = Vec::new(); for (ix, completion) in completions.into_iter().enumerate() { let label = self.label_for_completion(completion, language).await; if let Some(label) = label { labels.resize(ix + 1, None); *labels.last_mut().unwrap() = Some(label); } } Ok(labels) } async fn label_for_completion( &self, _: &lsp::CompletionItem, _: &Arc<Language>, ) -> Option<CodeLabel> { None } async fn labels_for_symbols( self: Arc<Self>, symbols: &[(String, lsp::SymbolKind)], language: &Arc<Language>, ) -> Result<Vec<Option<CodeLabel>>> { let mut labels = Vec::new(); for (ix, (name, kind)) in symbols.into_iter().enumerate() { let label = self.label_for_symbol(name, *kind, language).await; if let Some(label) = label { labels.resize(ix + 1, None); *labels.last_mut().unwrap() = Some(label); } } Ok(labels) } async fn label_for_symbol( &self, _: &str, _: lsp::SymbolKind, _: &Arc<Language>, ) -> Option<CodeLabel> { None } /// Returns initialization options that are going to be sent to a LSP server as a part of [`lsp::InitializeParams`] async fn initialization_options( self: Arc<Self>, _: &Arc<dyn LspAdapterDelegate>, ) -> Result<Option<Value>> { Ok(None) } async fn workspace_configuration( self: Arc<Self>, _: &Arc<dyn LspAdapterDelegate>, _cx: &mut AsyncAppContext, ) -> Result<Value> { Ok(serde_json::json!({})) } /// Returns a list of code actions supported by a given LspAdapter fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> { Some(vec![ CodeActionKind::EMPTY, CodeActionKind::QUICKFIX, CodeActionKind::REFACTOR, CodeActionKind::REFACTOR_EXTRACT, CodeActionKind::SOURCE, ]) } fn disk_based_diagnostic_sources(&self) -> Vec<String> { Default::default() } fn disk_based_diagnostics_progress_token(&self) -> Option<String> { NoneLines 1–500 of 1755. Load more
//! The `language` crate provides a large chunk of Zed's language-related //! features (the other big contributors being project and lsp crates that revolve around LSP features). //! Namely, this crate: //! - Provides [`Language`], [`Grammar`] and [`LanguageRegistry`] types that //! use Tree-sitter to provide syntax highlighting to the editor; note though that `language` doesn't perform the highlighting by itself. It only maps ranges in a buffer to colors. Treesitter is also used for buffer outlines (lists of symbols in a buffer) //! - Exposes [`LanguageConfig`] that describes how constructs (like brackets or line comments) should be handled by the editor for a source file of a particular language. //! //! Notably we do *not* assign a single language to a single file; in real world a single file can consist of multiple programming languages - HTML is a good example of that - and `language` crate tends to reflect that status quo in its API. mod buffer; mod diagnostic_set; mod highlight_map; mod language_registry; pub mod language_settings; mod outline; pub mod proto; mod syntax_map; mod task_context; #[cfg(test)] mod buffer_tests; pub mod markdown; use crate::language_settings::SoftWrap; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use collections::{HashMap, HashSet}; use futures::Future; use gpui::{AppContext, AsyncAppContext, Model, Task}; pub use highlight_map::HighlightMap; use http::HttpClient; use lazy_static::lazy_static; use lsp::{CodeActionKind, LanguageServerBinary}; use parking_lot::Mutex; use regex::Regex; use schemars::{ gen::SchemaGenerator, schema::{InstanceType, Schema, SchemaObject}, JsonSchema, }; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use serde_json::Value; use smol::future::FutureExt as _; use std::num::NonZeroU32; use std::{ any::Any, ffi::OsStr, fmt::Debug, hash::Hash, mem, ops::{DerefMut, Range}, path::{Path, PathBuf}, pin::Pin, str, sync::{ atomic::{AtomicU64, AtomicUsize, Ordering::SeqCst}, Arc, }, }; use syntax_map::{QueryCursorHandle, SyntaxSnapshot}; use task::RunnableTag; pub use task_context::{ BasicContextProvider, ContextProvider, ContextProviderWithTasks, RunnableRange, }; use theme::SyntaxTheme; use tree_sitter::{self, wasmtime, Query, QueryCursor, WasmStore}; pub use buffer::Operation; pub use buffer::*; pub use diagnostic_set::DiagnosticEntry; pub use language_registry::{ LanguageNotFound, LanguageQueries, LanguageRegistry, LanguageServerBinaryStatus, PendingLanguageServer, QUERY_FILENAME_PREFIXES, }; pub use lsp::LanguageServerId; pub use outline::{Outline, OutlineItem}; pub use syntax_map::{OwnedSyntaxLayer, SyntaxLayer}; pub use text::LineEnding; pub use tree_sitter::{Node, Parser, Tree, TreeCursor}; /// Initializes the `language` crate. /// /// This should be called before making use of items from the create. pub fn init(cx: &mut AppContext) { language_settings::init(cx); } static QUERY_CURSORS: Mutex<Vec<QueryCursor>> = Mutex::new(vec![]); static PARSERS: Mutex<Vec<Parser>> = Mutex::new(vec![]); pub fn with_parser<F, R>(func: F) -> R where F: FnOnce(&mut Parser) -> R, { let mut parser = PARSERS.lock().pop().unwrap_or_else(|| { let mut parser = Parser::new(); parser .set_wasm_store(WasmStore::new(WASM_ENGINE.clone()).unwrap()) .unwrap(); parser }); parser.set_included_ranges(&[]).unwrap(); let result = func(&mut parser); PARSERS.lock().push(parser); result } pub fn with_query_cursor<F, R>(func: F) -> R where F: FnOnce(&mut QueryCursor) -> R, { let mut cursor = QueryCursorHandle::new(); func(cursor.deref_mut()) } lazy_static! { static ref NEXT_LANGUAGE_ID: AtomicUsize = Default::default(); static ref NEXT_GRAMMAR_ID: AtomicUsize = Default::default(); static ref WASM_ENGINE: wasmtime::Engine = { wasmtime::Engine::new(&wasmtime::Config::new()).unwrap() }; /// A shared grammar for plain text, exposed for reuse by downstream crates. pub static ref PLAIN_TEXT: Arc<Language> = Arc::new(Language::new( LanguageConfig { name: "Plain Text".into(), soft_wrap: Some(SoftWrap::PreferredLineLength), ..Default::default() }, None, )); } /// Types that represent a position in a buffer, and can be converted into /// an LSP position, to send to a language server. pub trait ToLspPosition { /// Converts the value into an LSP position. fn to_lsp_position(self) -> lsp::Position; } /// A name of a language server. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)] pub struct LanguageServerName(pub Arc<str>); #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Location { pub buffer: Model<Buffer>, pub range: Range<Anchor>, } /// Represents a Language Server, with certain cached sync properties. /// Uses [`LspAdapter`] under the hood, but calls all 'static' methods /// once at startup, and caches the results. pub struct CachedLspAdapter { pub name: LanguageServerName, pub disk_based_diagnostic_sources: Vec<String>, pub disk_based_diagnostics_progress_token: Option<String>, language_ids: HashMap<String, String>, pub adapter: Arc<dyn LspAdapter>, pub reinstall_attempt_count: AtomicU64, /// Indicates whether this language server is the primary language server /// for a given language. Currently, most LSP-backed features only work /// with one language server, so one server needs to be primary. pub is_primary: bool, cached_binary: futures::lock::Mutex<Option<LanguageServerBinary>>, } impl CachedLspAdapter { pub fn new(adapter: Arc<dyn LspAdapter>, is_primary: bool) -> Arc<Self> { let name = adapter.name(); let disk_based_diagnostic_sources = adapter.disk_based_diagnostic_sources(); let disk_based_diagnostics_progress_token = adapter.disk_based_diagnostics_progress_token(); let language_ids = adapter.language_ids(); Arc::new(CachedLspAdapter { name, disk_based_diagnostic_sources, disk_based_diagnostics_progress_token, language_ids, adapter, is_primary, cached_binary: Default::default(), reinstall_attempt_count: AtomicU64::new(0), }) } pub async fn get_language_server_command( self: Arc<Self>, language: Arc<Language>, container_dir: Arc<Path>, delegate: Arc<dyn LspAdapterDelegate>, cx: &mut AsyncAppContext, ) -> Result<LanguageServerBinary> { let cached_binary = self.cached_binary.lock().await; self.adapter .clone() .get_language_server_command(language, container_dir, delegate, cached_binary, cx) .await } pub fn will_start_server( &self, delegate: &Arc<dyn LspAdapterDelegate>, cx: &mut AsyncAppContext, ) -> Option<Task<Result<()>>> { self.adapter.will_start_server(delegate, cx) } pub fn can_be_reinstalled(&self) -> bool { self.adapter.can_be_reinstalled() } pub async fn installation_test_binary( &self, container_dir: PathBuf, ) -> Option<LanguageServerBinary> { self.adapter.installation_test_binary(container_dir).await } pub fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> { self.adapter.code_action_kinds() } pub fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) { self.adapter.process_diagnostics(params) } pub async fn process_completions(&self, completion_items: &mut [lsp::CompletionItem]) { self.adapter.process_completions(completion_items).await } pub async fn labels_for_completions( &self, completion_items: &[lsp::CompletionItem], language: &Arc<Language>, ) -> Result<Vec<Option<CodeLabel>>> { self.adapter .clone() .labels_for_completions(completion_items, language) .await } pub async fn labels_for_symbols( &self, symbols: &[(String, lsp::SymbolKind)], language: &Arc<Language>, ) -> Result<Vec<Option<CodeLabel>>> { self.adapter .clone() .labels_for_symbols(symbols, language) .await } pub fn language_id(&self, language: &Language) -> String { self.language_ids .get(language.name().as_ref()) .cloned() .unwrap_or_else(|| language.lsp_id()) } #[cfg(any(test, feature = "test-support"))] fn as_fake(&self) -> Option<&FakeLspAdapter> { self.adapter.as_fake() } } /// [`LspAdapterDelegate`] allows [`LspAdapter]` implementations to interface with the application // e.g. to display a notification or fetch data from the web. #[async_trait] pub trait LspAdapterDelegate: Send + Sync { fn show_notification(&self, message: &str, cx: &mut AppContext); fn http_client(&self) -> Arc<dyn HttpClient>; fn worktree_id(&self) -> u64; fn worktree_root_path(&self) -> &Path; fn update_status(&self, language: LanguageServerName, status: LanguageServerBinaryStatus); async fn which(&self, command: &OsStr) -> Option<PathBuf>; async fn shell_env(&self) -> HashMap<String, String>; async fn read_text_file(&self, path: PathBuf) -> Result<String>; } #[async_trait(?Send)] pub trait LspAdapter: 'static + Send + Sync { fn name(&self) -> LanguageServerName; fn get_language_server_command<'a>( self: Arc<Self>, language: Arc<Language>, container_dir: Arc<Path>, delegate: Arc<dyn LspAdapterDelegate>, mut cached_binary: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>, cx: &'a mut AsyncAppContext, ) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> { async move { // First we check whether the adapter can give us a user-installed binary. // If so, we do *not* want to cache that, because each worktree might give us a different // binary: // // worktree 1: user-installed at `.bin/gopls` // worktree 2: user-installed at `~/bin/gopls` // worktree 3: no gopls found in PATH -> fallback to Zed installation // // We only want to cache when we fall back to the global one, // because we don't want to download and overwrite our global one // for each worktree we might have open. if let Some(binary) = self.check_if_user_installed(delegate.as_ref(), cx).await { log::info!( "found user-installed language server for {}. path: {:?}, arguments: {:?}", language.name(), binary.path, binary.arguments ); return Ok(binary); } if let Some(cached_binary) = cached_binary.as_ref() { return Ok(cached_binary.clone()); } if !container_dir.exists() { smol::fs::create_dir_all(&container_dir) .await .context("failed to create container directory")?; } let mut binary = try_fetch_server_binary(self.as_ref(), &delegate, container_dir.to_path_buf(), cx).await; if let Err(error) = binary.as_ref() { if let Some(prev_downloaded_binary) = self .cached_server_binary(container_dir.to_path_buf(), delegate.as_ref()) .await { log::info!( "failed to fetch newest version of language server {:?}. falling back to using {:?}", self.name(), prev_downloaded_binary.path ); binary = Ok(prev_downloaded_binary); } else { delegate.update_status( self.name(), LanguageServerBinaryStatus::Failed { error: format!("{error:?}"), }, ); } } if let Ok(binary) = &binary { *cached_binary = Some(binary.clone()); } binary } .boxed_local() } async fn check_if_user_installed( &self, _: &dyn LspAdapterDelegate, _: &AsyncAppContext, ) -> Option<LanguageServerBinary> { None } async fn fetch_latest_server_version( &self, delegate: &dyn LspAdapterDelegate, ) -> Result<Box<dyn 'static + Send + Any>>; fn will_fetch_server( &self, _: &Arc<dyn LspAdapterDelegate>, _: &mut AsyncAppContext, ) -> Option<Task<Result<()>>> { None } fn will_start_server( &self, _: &Arc<dyn LspAdapterDelegate>, _: &mut AsyncAppContext, ) -> Option<Task<Result<()>>> { None } async fn fetch_server_binary( &self, latest_version: Box<dyn 'static + Send + Any>, container_dir: PathBuf, delegate: &dyn LspAdapterDelegate, ) -> Result<LanguageServerBinary>; async fn cached_server_binary( &self, container_dir: PathBuf, delegate: &dyn LspAdapterDelegate, ) -> Option<LanguageServerBinary>; /// Returns `true` if a language server can be reinstalled. /// /// If language server initialization fails, a reinstallation will be attempted unless the value returned from this method is `false`. /// /// Implementations that rely on software already installed on user's system /// should have [`can_be_reinstalled`](Self::can_be_reinstalled) return `false`. fn can_be_reinstalled(&self) -> bool { true } async fn installation_test_binary( &self, container_dir: PathBuf, ) -> Option<LanguageServerBinary>; fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} /// Post-processes completions provided by the language server. async fn process_completions(&self, _: &mut [lsp::CompletionItem]) {} async fn labels_for_completions( self: Arc<Self>, completions: &[lsp::CompletionItem], language: &Arc<Language>, ) -> Result<Vec<Option<CodeLabel>>> { let mut labels = Vec::new(); for (ix, completion) in completions.into_iter().enumerate() { let label = self.label_for_completion(completion, language).await; if let Some(label) = label { labels.resize(ix + 1, None); *labels.last_mut().unwrap() = Some(label); } } Ok(labels) } async fn label_for_completion( &self, _: &lsp::CompletionItem, _: &Arc<Language>, ) -> Option<CodeLabel> { None } async fn labels_for_symbols( self: Arc<Self>, symbols: &[(String, lsp::SymbolKind)], language: &Arc<Language>, ) -> Result<Vec<Option<CodeLabel>>> { let mut labels = Vec::new(); for (ix, (name, kind)) in symbols.into_iter().enumerate() { let label = self.label_for_symbol(name, *kind, language).await; if let Some(label) = label { labels.resize(ix + 1, None); *labels.last_mut().unwrap() = Some(label); } } Ok(labels) } async fn label_for_symbol( &self, _: &str, _: lsp::SymbolKind, _: &Arc<Language>, ) -> Option<CodeLabel> { None } /// Returns initialization options that are going to be sent to a LSP server as a part of [`lsp::InitializeParams`] async fn initialization_options( self: Arc<Self>, _: &Arc<dyn LspAdapterDelegate>, ) -> Result<Option<Value>> { Ok(None) } async fn workspace_configuration( self: Arc<Self>, _: &Arc<dyn LspAdapterDelegate>, _cx: &mut AsyncAppContext, ) -> Result<Value> { Ok(serde_json::json!({})) } /// Returns a list of code actions supported by a given LspAdapter fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> { Some(vec![ CodeActionKind::EMPTY, CodeActionKind::QUICKFIX, CodeActionKind::REFACTOR, CodeActionKind::REFACTOR_EXTRACT, CodeActionKind::SOURCE, ]) } fn disk_based_diagnostic_sources(&self) -> Vec<String> { Default::default() } fn disk_based_diagnostics_progress_token(&self) -> Option<String> { None