//! 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; #[cfg(test)]mod buffer_tests;pub mod markdown; 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 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::{ any::Any, cell::RefCell, ffi::OsString, fmt::Debug, hash::Hash, mem, ops::Range, path::{Path, PathBuf}, pin::Pin, str, sync::{ atomic::{AtomicU64, AtomicUsize, Ordering::SeqCst}, Arc, },};use syntax_map::SyntaxSnapshot;use theme::SyntaxTheme;use tree_sitter::{self, wasmtime, Query, WasmStore};use util::http::HttpClient; pub use buffer::Operation;pub use buffer::*;pub use diagnostic_set::DiagnosticEntry;pub use language_registry::{ 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::{Parser, Tree}; /// 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);} thread_local! { static PARSER: RefCell<Parser> = { let mut parser = Parser::new(); parser.set_wasm_store(WasmStore::new(WASM_ENGINE.clone()).unwrap()).unwrap(); RefCell::new(parser) };} 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(), ..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>,} pub struct LanguageContext { pub package: Option<String>, pub symbol: Option<String>,} pub trait LanguageContextProvider: Send + Sync { fn build_context(&self, location: Location, cx: &mut AppContext) -> Result<LanguageContext>;} /// A context provider that fills out LanguageContext without inspecting the contents.pub struct DefaultContextProvider; impl LanguageContextProvider for DefaultContextProvider { fn build_context( &self, location: Location, cx: &mut AppContext, ) -> gpui::Result<LanguageContext> { let symbols = location .buffer .read(cx) .snapshot() .symbols_containing(location.range.start, None); let symbol = symbols.and_then(|symbols| { symbols.last().map(|symbol| { let range = symbol .name_ranges .last() .cloned() .unwrap_or(0..symbol.text.len()); symbol.text[range].to_string() }) }); Ok(LanguageContext { package: None, symbol, }) }} /// 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>, pub language_ids: HashMap<String, String>, pub adapter: Arc<dyn LspAdapter>, pub reinstall_attempt_count: AtomicU64, cached_binary: futures::lock::Mutex<Option<LanguageServerBinary>>,} impl CachedLspAdapter { pub fn new(adapter: Arc<dyn LspAdapter>) -> 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, 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 workspace_configuration(&self, workspace_root: &Path, cx: &mut AppContext) -> Value { self.adapter.workspace_configuration(workspace_root, cx) } pub fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) { self.adapter.process_diagnostics(params) } pub async fn process_completion(&self, completion_item: &mut lsp::CompletionItem) { self.adapter.process_completion(completion_item).await } pub async fn label_for_completion( &self, completion_item: &lsp::CompletionItem, language: &Arc<Language>, ) -> Option<CodeLabel> { self.adapter .label_for_completion(completion_item, language) .await } pub async fn label_for_symbol( &self, name: &str, kind: lsp::SymbolKind, language: &Arc<Language>, ) -> Option<CodeLabel> { self.adapter.label_for_symbol(name, kind, language).await } pub fn prettier_plugins(&self) -> &[&'static str] { self.adapter.prettier_plugins() } #[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 update_status(&self, language: LanguageServerName, status: LanguageServerBinaryStatus); async fn which_command(&self, command: OsString) -> Option<(PathBuf, HashMap<String, String>)>; async fn read_text_file(&self, path: PathBuf) -> Result<String>;} #[async_trait]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()).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")?; } if let Some(task) = self.will_fetch_server(&delegate, cx) { task.await?; } let name = self.name(); log::info!("fetching latest version of language server {:?}", name.0); delegate.update_status( name.clone(), LanguageServerBinaryStatus::CheckingForUpdate, ); let version_info = self.fetch_latest_server_version(delegate.as_ref()).await?; log::info!("downloading language server {:?}", name.0); delegate.update_status(self.name(), LanguageServerBinaryStatus::Downloading); let mut binary = self .fetch_server_binary(version_info, container_dir.to_path_buf(), delegate.as_ref()) .await; delegate.update_status(name.clone(), LanguageServerBinaryStatus::Downloaded); 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 { delegate.update_status(name.clone(), LanguageServerBinaryStatus::Cached); log::info!( "failed to fetch newest version of language server {:?}. falling back to using {:?}", name.clone(), prev_downloaded_binary.path.display() ); binary = Ok(prev_downloaded_binary); } else { delegate.update_status( name.clone(), 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, ) -> 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, 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) {} /// A callback called for each [`lsp::CompletionItem`] obtained from LSP server. /// Some LspAdapter implementations might want to modify the obtained item to /// change how it's displayed. async fn process_completion(&self, _: &mut lsp::CompletionItem) {} async fn label_for_completion( &self, _: &lsp::CompletionItem, _: &Arc<Language>, ) -> Option<CodeLabel> { None } 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`] fn initialization_options(&self) -> Option<Value> { None } fn workspace_configuration(&self, _workspace_root: &Path, _cx: &mut AppContext) -> Value { 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 } fn language_ids(&self) -> HashMap<String, String> { Default::default() } fn prettier_plugins(&self) -> &[&'static str] { &[] } #[cfg(any(test, feature = "test-support"))] fn as_fake(&self) -> Option<&FakeLspAdapter> { None }} Lines 1–500 of 1670. 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; #[cfg(test)] mod buffer_tests; pub mod markdown; 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 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::{ any::Any, cell::RefCell, ffi::OsString, fmt::Debug, hash::Hash, mem, ops::Range, path::{Path, PathBuf}, pin::Pin, str, sync::{ atomic::{AtomicU64, AtomicUsize, Ordering::SeqCst}, Arc, }, }; use syntax_map::SyntaxSnapshot; use theme::SyntaxTheme; use tree_sitter::{self, wasmtime, Query, WasmStore}; use util::http::HttpClient; pub use buffer::Operation; pub use buffer::*; pub use diagnostic_set::DiagnosticEntry; pub use language_registry::{ 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::{Parser, Tree}; /// 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); } thread_local! { static PARSER: RefCell<Parser> = { let mut parser = Parser::new(); parser.set_wasm_store(WasmStore::new(WASM_ENGINE.clone()).unwrap()).unwrap(); RefCell::new(parser) }; } 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(), ..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>, } pub struct LanguageContext { pub package: Option<String>, pub symbol: Option<String>, } pub trait LanguageContextProvider: Send + Sync { fn build_context(&self, location: Location, cx: &mut AppContext) -> Result<LanguageContext>; } /// A context provider that fills out LanguageContext without inspecting the contents. pub struct DefaultContextProvider; impl LanguageContextProvider for DefaultContextProvider { fn build_context( &self, location: Location, cx: &mut AppContext, ) -> gpui::Result<LanguageContext> { let symbols = location .buffer .read(cx) .snapshot() .symbols_containing(location.range.start, None); let symbol = symbols.and_then(|symbols| { symbols.last().map(|symbol| { let range = symbol .name_ranges .last() .cloned() .unwrap_or(0..symbol.text.len()); symbol.text[range].to_string() }) }); Ok(LanguageContext { package: None, symbol, }) } } /// 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>, pub language_ids: HashMap<String, String>, pub adapter: Arc<dyn LspAdapter>, pub reinstall_attempt_count: AtomicU64, cached_binary: futures::lock::Mutex<Option<LanguageServerBinary>>, } impl CachedLspAdapter { pub fn new(adapter: Arc<dyn LspAdapter>) -> 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, 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 workspace_configuration(&self, workspace_root: &Path, cx: &mut AppContext) -> Value { self.adapter.workspace_configuration(workspace_root, cx) } pub fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) { self.adapter.process_diagnostics(params) } pub async fn process_completion(&self, completion_item: &mut lsp::CompletionItem) { self.adapter.process_completion(completion_item).await } pub async fn label_for_completion( &self, completion_item: &lsp::CompletionItem, language: &Arc<Language>, ) -> Option<CodeLabel> { self.adapter .label_for_completion(completion_item, language) .await } pub async fn label_for_symbol( &self, name: &str, kind: lsp::SymbolKind, language: &Arc<Language>, ) -> Option<CodeLabel> { self.adapter.label_for_symbol(name, kind, language).await } pub fn prettier_plugins(&self) -> &[&'static str] { self.adapter.prettier_plugins() } #[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 update_status(&self, language: LanguageServerName, status: LanguageServerBinaryStatus); async fn which_command(&self, command: OsString) -> Option<(PathBuf, HashMap<String, String>)>; async fn read_text_file(&self, path: PathBuf) -> Result<String>; } #[async_trait] 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()).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")?; } if let Some(task) = self.will_fetch_server(&delegate, cx) { task.await?; } let name = self.name(); log::info!("fetching latest version of language server {:?}", name.0); delegate.update_status( name.clone(), LanguageServerBinaryStatus::CheckingForUpdate, ); let version_info = self.fetch_latest_server_version(delegate.as_ref()).await?; log::info!("downloading language server {:?}", name.0); delegate.update_status(self.name(), LanguageServerBinaryStatus::Downloading); let mut binary = self .fetch_server_binary(version_info, container_dir.to_path_buf(), delegate.as_ref()) .await; delegate.update_status(name.clone(), LanguageServerBinaryStatus::Downloaded); 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 { delegate.update_status(name.clone(), LanguageServerBinaryStatus::Cached); log::info!( "failed to fetch newest version of language server {:?}. falling back to using {:?}", name.clone(), prev_downloaded_binary.path.display() ); binary = Ok(prev_downloaded_binary); } else { delegate.update_status( name.clone(), 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, ) -> 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, 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) {} /// A callback called for each [`lsp::CompletionItem`] obtained from LSP server. /// Some LspAdapter implementations might want to modify the obtained item to /// change how it's displayed. async fn process_completion(&self, _: &mut lsp::CompletionItem) {} async fn label_for_completion( &self, _: &lsp::CompletionItem, _: &Arc<Language>, ) -> Option<CodeLabel> { None } 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`] fn initialization_options(&self) -> Option<Value> { None } fn workspace_configuration(&self, _workspace_root: &Path, _cx: &mut AppContext) -> Value { 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 } fn language_ids(&self) -> HashMap<String, String> { Default::default() } fn prettier_plugins(&self) -> &[&'static str] { &[] } #[cfg(any(test, feature = "test-support"))] fn as_fake(&self) -> Option<&FakeLspAdapter> { None } }