//! 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)]pub 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, SharedString, Task};pub use highlight_map::HighlightMap;use http_client::HttpClient;pub use language_registry::LanguageName;use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions};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 settings::WorktreeId;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, LazyLock, },};use syntax_map::{QueryCursorHandle, SyntaxSnapshot};use task::RunnableTag;pub use task_context::{ContextProvider, RunnableRange};use theme::SyntaxTheme;use tree_sitter::{self, wasmtime, Query, QueryCursor, WasmStore};use util::serde::default_true; pub use buffer::Operation;pub use buffer::*;pub use diagnostic_set::DiagnosticEntry;pub use language_registry::{ AvailableLanguage, LanguageNotFound, LanguageQueries, LanguageRegistry, LanguageServerBinaryStatus, QUERY_FILENAME_PREFIXES,};pub use lsp::LanguageServerId;pub use outline::*;pub use syntax_map::{OwnedSyntaxLayer, SyntaxLayer};pub use text::{AnchorRangeExt, 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).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())} static NEXT_LANGUAGE_ID: LazyLock<AtomicUsize> = LazyLock::new(Default::default);static NEXT_GRAMMAR_ID: LazyLock<AtomicUsize> = LazyLock::new(Default::default);static WASM_ENGINE: LazyLock<wasmtime::Engine> = LazyLock::new(|| { wasmtime::Engine::new(&wasmtime::Config::new()).expect("Failed to create Wasmtime engine")}); /// A shared grammar for plain text, exposed for reuse by downstream crates.pub static PLAIN_TEXT: LazyLock<Arc<Language>> = LazyLock::new(|| { Arc::new(Language::new( LanguageConfig { name: "Plain Text".into(), soft_wrap: Some(SoftWrap::EditorWidth), ..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 SharedString); impl std::fmt::Display for LanguageServerName { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(&self.0, f) }} impl AsRef<str> for LanguageServerName { fn as_ref(&self) -> &str { self.0.as_ref() }} impl AsRef<OsStr> for LanguageServerName { fn as_ref(&self) -> &OsStr { self.0.as_ref().as_ref() }} impl JsonSchema for LanguageServerName { fn schema_name() -> String { "LanguageServerName".into() } fn json_schema(_: &mut SchemaGenerator) -> Schema { SchemaObject { instance_type: Some(InstanceType::String.into()), ..Default::default() } .into() }}impl LanguageServerName { pub const fn new_static(s: &'static str) -> Self { Self(SharedString::new_static(s)) } pub fn from_proto(s: String) -> Self { Self(s.into()) }} impl<'a> From<&'a str> for LanguageServerName { fn from(str: &'a str) -> LanguageServerName { LanguageServerName(str.to_string().into()) }} #[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, cached_binary: futures::lock::Mutex<Option<LanguageServerBinary>>,} impl Debug for CachedLspAdapter { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("CachedLspAdapter") .field("name", &self.name) .field( "disk_based_diagnostic_sources", &self.disk_based_diagnostic_sources, ) .field( "disk_based_diagnostics_progress_token", &self.disk_based_diagnostics_progress_token, ) .field("language_ids", &self.language_ids) .field("reinstall_attempt_count", &self.reinstall_attempt_count) .finish_non_exhaustive() }} 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 fn name(&self) -> LanguageServerName { self.adapter.name().clone() } pub async fn get_language_server_command( self: Arc<Self>, delegate: Arc<dyn LspAdapterDelegate>, binary_options: LanguageServerBinaryOptions, cx: &mut AsyncAppContext, ) -> Result<LanguageServerBinary> { let cached_binary = self.cached_binary.lock().await; self.adapter .clone() .get_language_server_command(delegate, binary_options, cached_binary, cx) .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_name: &LanguageName) -> String { self.language_ids .get(language_name.0.as_ref()) .cloned() .unwrap_or_else(|| language_name.lsp_id()) }} /// [`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) -> WorktreeId; fn worktree_root_path(&self) -> &Path; fn update_status(&self, language: LanguageServerName, status: LanguageServerBinaryStatus); async fn language_server_download_dir(&self, name: &LanguageServerName) -> Option<Arc<Path>>; async fn npm_package_installed_version( &self, package_name: &str, ) -> Result<Option<(PathBuf, String)>>; 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 fn try_exec(&self, binary: LanguageServerBinary) -> Result<()>;} #[async_trait(?Send)]pub trait LspAdapter: 'static + Send + Sync { fn name(&self) -> LanguageServerName; fn get_language_server_command<'a>( self: Arc<Self>, delegate: Arc<dyn LspAdapterDelegate>, binary_options: LanguageServerBinaryOptions, 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 binary_options.allow_path_lookup { if let Some(binary) = self.check_if_user_installed(delegate.as_ref(), cx).await { log::info!( "found user-installed language server for {}. path: {:?}, arguments: {:?}", self.name().0, binary.path, binary.arguments ); return Ok(binary); } } if !binary_options.allow_binary_download { return Err(anyhow!("downloading language servers disabled")); } if let Some(cached_binary) = cached_binary.as_ref() { return Ok(cached_binary.clone()); } let Some(container_dir) = delegate.language_server_download_dir(&self.name()).await else { anyhow::bail!("no language server download dir defined") }; 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 {:?}. error: {:?}, falling back to using {:?}", self.name(), error, 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 } 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>; 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.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.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) }Lines 1–500 of 1847. 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)] pub 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, SharedString, Task}; pub use highlight_map::HighlightMap; use http_client::HttpClient; pub use language_registry::LanguageName; use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions}; 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 settings::WorktreeId; 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, LazyLock, }, }; use syntax_map::{QueryCursorHandle, SyntaxSnapshot}; use task::RunnableTag; pub use task_context::{ContextProvider, RunnableRange}; use theme::SyntaxTheme; use tree_sitter::{self, wasmtime, Query, QueryCursor, WasmStore}; use util::serde::default_true; pub use buffer::Operation; pub use buffer::*; pub use diagnostic_set::DiagnosticEntry; pub use language_registry::{ AvailableLanguage, LanguageNotFound, LanguageQueries, LanguageRegistry, LanguageServerBinaryStatus, QUERY_FILENAME_PREFIXES, }; pub use lsp::LanguageServerId; pub use outline::*; pub use syntax_map::{OwnedSyntaxLayer, SyntaxLayer}; pub use text::{AnchorRangeExt, 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).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()) } static NEXT_LANGUAGE_ID: LazyLock<AtomicUsize> = LazyLock::new(Default::default); static NEXT_GRAMMAR_ID: LazyLock<AtomicUsize> = LazyLock::new(Default::default); static WASM_ENGINE: LazyLock<wasmtime::Engine> = LazyLock::new(|| { wasmtime::Engine::new(&wasmtime::Config::new()).expect("Failed to create Wasmtime engine") }); /// A shared grammar for plain text, exposed for reuse by downstream crates. pub static PLAIN_TEXT: LazyLock<Arc<Language>> = LazyLock::new(|| { Arc::new(Language::new( LanguageConfig { name: "Plain Text".into(), soft_wrap: Some(SoftWrap::EditorWidth), ..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 SharedString); impl std::fmt::Display for LanguageServerName { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(&self.0, f) } } impl AsRef<str> for LanguageServerName { fn as_ref(&self) -> &str { self.0.as_ref() } } impl AsRef<OsStr> for LanguageServerName { fn as_ref(&self) -> &OsStr { self.0.as_ref().as_ref() } } impl JsonSchema for LanguageServerName { fn schema_name() -> String { "LanguageServerName".into() } fn json_schema(_: &mut SchemaGenerator) -> Schema { SchemaObject { instance_type: Some(InstanceType::String.into()), ..Default::default() } .into() } } impl LanguageServerName { pub const fn new_static(s: &'static str) -> Self { Self(SharedString::new_static(s)) } pub fn from_proto(s: String) -> Self { Self(s.into()) } } impl<'a> From<&'a str> for LanguageServerName { fn from(str: &'a str) -> LanguageServerName { LanguageServerName(str.to_string().into()) } } #[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, cached_binary: futures::lock::Mutex<Option<LanguageServerBinary>>, } impl Debug for CachedLspAdapter { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("CachedLspAdapter") .field("name", &self.name) .field( "disk_based_diagnostic_sources", &self.disk_based_diagnostic_sources, ) .field( "disk_based_diagnostics_progress_token", &self.disk_based_diagnostics_progress_token, ) .field("language_ids", &self.language_ids) .field("reinstall_attempt_count", &self.reinstall_attempt_count) .finish_non_exhaustive() } } 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 fn name(&self) -> LanguageServerName { self.adapter.name().clone() } pub async fn get_language_server_command( self: Arc<Self>, delegate: Arc<dyn LspAdapterDelegate>, binary_options: LanguageServerBinaryOptions, cx: &mut AsyncAppContext, ) -> Result<LanguageServerBinary> { let cached_binary = self.cached_binary.lock().await; self.adapter .clone() .get_language_server_command(delegate, binary_options, cached_binary, cx) .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_name: &LanguageName) -> String { self.language_ids .get(language_name.0.as_ref()) .cloned() .unwrap_or_else(|| language_name.lsp_id()) } } /// [`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) -> WorktreeId; fn worktree_root_path(&self) -> &Path; fn update_status(&self, language: LanguageServerName, status: LanguageServerBinaryStatus); async fn language_server_download_dir(&self, name: &LanguageServerName) -> Option<Arc<Path>>; async fn npm_package_installed_version( &self, package_name: &str, ) -> Result<Option<(PathBuf, String)>>; 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 fn try_exec(&self, binary: LanguageServerBinary) -> Result<()>; } #[async_trait(?Send)] pub trait LspAdapter: 'static + Send + Sync { fn name(&self) -> LanguageServerName; fn get_language_server_command<'a>( self: Arc<Self>, delegate: Arc<dyn LspAdapterDelegate>, binary_options: LanguageServerBinaryOptions, 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 binary_options.allow_path_lookup { if let Some(binary) = self.check_if_user_installed(delegate.as_ref(), cx).await { log::info!( "found user-installed language server for {}. path: {:?}, arguments: {:?}", self.name().0, binary.path, binary.arguments ); return Ok(binary); } } if !binary_options.allow_binary_download { return Err(anyhow!("downloading language servers disabled")); } if let Some(cached_binary) = cached_binary.as_ref() { return Ok(cached_binary.clone()); } let Some(container_dir) = delegate.language_server_download_dir(&self.name()).await else { anyhow::bail!("no language server download dir defined") }; 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 {:?}. error: {:?}, falling back to using {:?}", self.name(), error, 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 } 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>; 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.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.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) }