//! 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;mod diagnostic_set;mod file_content;mod language_registry; pub mod language_settings;mod manifest;pub mod modeline;mod outline;pub mod proto;mod syntax_map;mod task_context;mod text_diff;mod toolchain; #[cfg(test)]pub mod buffer_tests; pub use crate::language_settings::{AutoIndentMode, EditPredictionsMode, IndentGuideSettings};use anyhow::{Context as _, Result};use async_trait::async_trait;use collections::{HashMap, HashSet};use futures::Future;use futures::future::LocalBoxFuture;use futures::lock::OwnedMutexGuard;use gpui::{App, AsyncApp, Entity};use http_client::HttpClient; pub use language_core::highlight_map::{HighlightId, HighlightMap}; use futures::future::FutureExt as _;pub use language_core::{ BlockCommentConfig, BracketPair, BracketPairConfig, BracketPairContent, BracketsConfig, BracketsPatternConfig, CodeLabel, CodeLabelBuilder, DebugVariablesConfig, DebuggerTextObject, DecreaseIndentConfig, Grammar, GrammarId, HighlightsConfig, IndentConfig, InjectionConfig, InjectionPatternConfig, JsxTagAutoCloseConfig, LanguageConfig, LanguageConfigOverride, LanguageId, LanguageMatcher, OrderedListConfig, OutlineConfig, Override, OverrideConfig, OverrideEntry, PromptResponseContext, RedactionConfig, RunnableCapture, RunnableConfig, SoftWrap, Symbol, TaskListConfig, TextObject, TextObjectConfig, ToLspPosition, WrapCharactersConfig, auto_indent_using_last_non_empty_line_default, deserialize_regex, deserialize_regex_vec, regex_json_schema, regex_vec_json_schema, serialize_regex,};pub use language_registry::{ LanguageName, LanguageServerStatusUpdate, LoadedLanguage, ServerHealth,};use lsp::{ CodeActionKind, InitializeParams, LanguageServerBinary, LanguageServerBinaryOptions, Uri,};pub use manifest::{ManifestDelegate, ManifestName, ManifestProvider, ManifestQuery};pub use modeline::{ModelineSettings, parse_modeline};use parking_lot::Mutex;use regex::Regex;use semver::Version;use serde_json::Value;use settings::WorktreeId;use std::{ ffi::OsStr, fmt::Debug, hash::Hash, mem, ops::{DerefMut, Range}, path::{Path, PathBuf}, str, sync::{Arc, LazyLock},};use syntax_map::{QueryCursorHandle, SyntaxSnapshot};use task::RunnableTag;pub use task_context::{ContextLocation, ContextProvider, RunnableRange};pub use text_diff::{ DiffOptions, apply_diff_patch, apply_reversed_diff_patch, char_diff, line_diff, text_diff, text_diff_with_options, unified_diff, unified_diff_with_context, unified_diff_with_offsets, word_diff_ranges,};use theme::SyntaxTheme;pub use toolchain::{ LanguageToolchainStore, LocalLanguageToolchainStore, Toolchain, ToolchainList, ToolchainLister, ToolchainMetadata, ToolchainScope,};use tree_sitter::{self, QueryCursor, WasmStore, wasmtime};use util::rel_path::RelPath; pub use buffer::Operation;pub use buffer::*;pub use diagnostic::{Diagnostic, DiagnosticSourceKind};pub use diagnostic_set::{DiagnosticEntry, DiagnosticEntryRef, DiagnosticGroup};pub use file_content::{ByteContent, FILE_ANALYSIS_BYTES, analyze_byte_content};pub use language_registry::{ AvailableLanguage, BinaryStatus, LanguageNotFound, LanguageQueries, LanguageRegistry, QUERY_FILENAME_PREFIXES,};pub use lsp::{LanguageServerId, LanguageServerName};pub use outline::*;pub use syntax_map::{ OwnedSyntaxLayer, SyntaxLayer, SyntaxMapMatches, ToTreeSitterPoint, TreeSitterOptions,};pub use text::{AnchorRangeExt, LineEnding};pub use tree_sitter::{Node, Parser, Tree, TreeCursor}; pub(crate) fn to_settings_soft_wrap(value: language_core::SoftWrap) -> settings::SoftWrap { match value { language_core::SoftWrap::None => settings::SoftWrap::None, language_core::SoftWrap::PreferLine => settings::SoftWrap::PreferLine, language_core::SoftWrap::EditorWidth => settings::SoftWrap::EditorWidth, language_core::SoftWrap::Bounded => settings::SoftWrap::Bounded, }} static QUERY_CURSORS: Mutex<Vec<QueryCursor>> = Mutex::new(vec![]);static PARSERS: Mutex<Vec<Parser>> = Mutex::new(vec![]); #[ztracing::instrument(skip_all)]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 }); // Tree-sitter auto-resets the parser at the end of a successful parse, // but the cancellation paths (progress callback returning `Break`, // cancelled balancing) leave outstanding state on the parser. The next // call to `parse_with_options` would then *resume* that cancelled parse // instead of starting fresh. parser.reset(); 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 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), matcher: LanguageMatcher { path_suffixes: vec!["txt".to_owned()], first_line_pattern: None, modeline_aliases: vec!["text".to_owned(), "txt".to_owned()], }, brackets: BracketPairConfig { pairs: vec![ BracketPair { start: "(".to_string(), end: ")".to_string(), close: true, surround: true, newline: false, }, BracketPair { start: "[".to_string(), end: "]".to_string(), close: true, surround: true, newline: false, }, BracketPair { start: "{".to_string(), end: "}".to_string(), close: true, surround: true, newline: false, }, BracketPair { start: "\"".to_string(), end: "\"".to_string(), close: true, surround: true, newline: false, }, BracketPair { start: "'".to_string(), end: "'".to_string(), close: true, surround: true, newline: false, }, ], disabled_scopes_by_bracket_ix: Default::default(), }, ..Default::default() }, None, ))}); /// Commands that the client (editor) handles locally rather than forwarding/// to the language server. Servers embed these in code lens and code action/// responses when they want the editor to perform a well-known UI action.#[derive(Debug, Clone)]pub enum ClientCommand { /// Open a location list (references panel / peek view). ShowLocations, /// Schedule a task from an LSP command's arguments. ScheduleTask(task::TaskTemplate),} #[derive(Debug, Clone, PartialEq, Eq, Hash)]pub struct Location { pub buffer: Entity<Buffer>, pub range: Range<Anchor>,} type ServerBinaryCache = futures::lock::Mutex<Option<(bool, LanguageServerBinary)>>;type DownloadableLanguageServerBinary = LocalBoxFuture<'static, Result<LanguageServerBinary>>;pub type LanguageServerBinaryLocations = LocalBoxFuture< 'static, ( Result<LanguageServerBinary>, Option<DownloadableLanguageServerBinary>, ),>;/// 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<LanguageName, String>, pub adapter: Arc<dyn LspAdapter>, cached_binary: Arc<ServerBinaryCache>,} 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) .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(), }) } pub fn name(&self) -> LanguageServerName { self.adapter.name() } pub async fn get_language_server_command( self: Arc<Self>, delegate: Arc<dyn LspAdapterDelegate>, toolchains: Option<Toolchain>, binary_options: LanguageServerBinaryOptions, cx: &mut AsyncApp, ) -> LanguageServerBinaryLocations { let cached_binary = self.cached_binary.clone().lock_owned().await; self.adapter.clone().get_language_server_command( delegate, toolchains, binary_options, cached_binary, cx.clone(), ) } pub fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> { self.adapter.code_action_kinds() } pub fn process_diagnostics( &self, params: &mut lsp::PublishDiagnosticsParams, server_id: LanguageServerId, ) { self.adapter.process_diagnostics(params, server_id) } pub fn retain_old_diagnostic(&self, previous_diagnostic: &Diagnostic) -> bool { self.adapter.retain_old_diagnostic(previous_diagnostic) } pub fn underline_diagnostic(&self, diagnostic: &lsp::Diagnostic) -> bool { self.adapter.underline_diagnostic(diagnostic) } pub fn diagnostic_message_to_markdown(&self, message: &str) -> Option<String> { self.adapter.diagnostic_message_to_markdown(message) } 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: &[Symbol], 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) .cloned() .unwrap_or_else(|| language_name.lsp_id()) } pub async fn initialization_options_schema( &self, delegate: &Arc<dyn LspAdapterDelegate>, cx: &mut AsyncApp, ) -> Option<serde_json::Value> { self.adapter .clone() .initialization_options_schema( delegate, self.cached_binary.clone().lock_owned().await, cx, ) .await } pub async fn settings_schema( &self, delegate: &Arc<dyn LspAdapterDelegate>, cx: &mut AsyncApp, ) -> Option<serde_json::Value> { self.adapter .clone() .settings_schema(delegate, self.cached_binary.clone().lock_owned().await, cx) .await } pub fn process_prompt_response(&self, context: &PromptResponseContext, cx: &mut AsyncApp) { self.adapter.process_prompt_response(context, cx) }} /// [`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 App); fn http_client(&self) -> Arc<dyn HttpClient>; fn worktree_id(&self) -> WorktreeId; fn worktree_root_path(&self) -> &Path; fn resolve_relative_path(&self, path: PathBuf) -> PathBuf; fn update_status(&self, language: LanguageServerName, status: BinaryStatus); fn registered_lsp_adapters(&self) -> Vec<Arc<dyn LspAdapter>>; 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, Version)>>; async fn which(&self, command: &OsStr) -> Option<PathBuf>; async fn shell_env(&self) -> HashMap<String, String>; async fn read_text_file(&self, path: &RelPath) -> Result<String>; async fn try_exec(&self, binary: LanguageServerBinary) -> Result<()>;} #[async_trait(?Send)]pub trait LspAdapter: 'static + Send + Sync + DynLspInstaller { fn name(&self) -> LanguageServerName; fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams, _: LanguageServerId) {} /// When processing new `lsp::PublishDiagnosticsParams` diagnostics, whether to retain previous one(s) or not. fn retain_old_diagnostic(&self, _previous_diagnostic: &Diagnostic) -> bool { false } /// Whether to underline a given diagnostic or not, when rendering in the editor. /// /// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnosticTag /// states that /// > Clients are allowed to render diagnostics with this tag faded out instead of having an error squiggle. /// for the unnecessary diagnostics, so do not underline them. fn underline_diagnostic(&self, _diagnostic: &lsp::Diagnostic) -> bool { true } /// Post-processes completions provided by the language server. async fn process_completions(&self, _: &mut [lsp::CompletionItem]) {} fn diagnostic_message_to_markdown(&self, _message: &str) -> Option<String> { None } 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: &[Symbol], language: &Arc<Language>, ) -> Result<Vec<Option<CodeLabel>>> { let mut labels = Vec::new(); for (ix, symbol) in symbols.iter().enumerate() { let label = self.label_for_symbol(symbol, 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, _symbol: &Symbol, _language: &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>, _cx: &mut AsyncApp, ) -> Result<Option<Value>> { Ok(None) }Lines 1–500 of 1994. 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; mod diagnostic_set; mod file_content; mod language_registry; pub mod language_settings; mod manifest; pub mod modeline; mod outline; pub mod proto; mod syntax_map; mod task_context; mod text_diff; mod toolchain; #[cfg(test)] pub mod buffer_tests; pub use crate::language_settings::{AutoIndentMode, EditPredictionsMode, IndentGuideSettings}; use anyhow::{Context as _, Result}; use async_trait::async_trait; use collections::{HashMap, HashSet}; use futures::Future; use futures::future::LocalBoxFuture; use futures::lock::OwnedMutexGuard; use gpui::{App, AsyncApp, Entity}; use http_client::HttpClient; pub use language_core::highlight_map::{HighlightId, HighlightMap}; use futures::future::FutureExt as _; pub use language_core::{ BlockCommentConfig, BracketPair, BracketPairConfig, BracketPairContent, BracketsConfig, BracketsPatternConfig, CodeLabel, CodeLabelBuilder, DebugVariablesConfig, DebuggerTextObject, DecreaseIndentConfig, Grammar, GrammarId, HighlightsConfig, IndentConfig, InjectionConfig, InjectionPatternConfig, JsxTagAutoCloseConfig, LanguageConfig, LanguageConfigOverride, LanguageId, LanguageMatcher, OrderedListConfig, OutlineConfig, Override, OverrideConfig, OverrideEntry, PromptResponseContext, RedactionConfig, RunnableCapture, RunnableConfig, SoftWrap, Symbol, TaskListConfig, TextObject, TextObjectConfig, ToLspPosition, WrapCharactersConfig, auto_indent_using_last_non_empty_line_default, deserialize_regex, deserialize_regex_vec, regex_json_schema, regex_vec_json_schema, serialize_regex, }; pub use language_registry::{ LanguageName, LanguageServerStatusUpdate, LoadedLanguage, ServerHealth, }; use lsp::{ CodeActionKind, InitializeParams, LanguageServerBinary, LanguageServerBinaryOptions, Uri, }; pub use manifest::{ManifestDelegate, ManifestName, ManifestProvider, ManifestQuery}; pub use modeline::{ModelineSettings, parse_modeline}; use parking_lot::Mutex; use regex::Regex; use semver::Version; use serde_json::Value; use settings::WorktreeId; use std::{ ffi::OsStr, fmt::Debug, hash::Hash, mem, ops::{DerefMut, Range}, path::{Path, PathBuf}, str, sync::{Arc, LazyLock}, }; use syntax_map::{QueryCursorHandle, SyntaxSnapshot}; use task::RunnableTag; pub use task_context::{ContextLocation, ContextProvider, RunnableRange}; pub use text_diff::{ DiffOptions, apply_diff_patch, apply_reversed_diff_patch, char_diff, line_diff, text_diff, text_diff_with_options, unified_diff, unified_diff_with_context, unified_diff_with_offsets, word_diff_ranges, }; use theme::SyntaxTheme; pub use toolchain::{ LanguageToolchainStore, LocalLanguageToolchainStore, Toolchain, ToolchainList, ToolchainLister, ToolchainMetadata, ToolchainScope, }; use tree_sitter::{self, QueryCursor, WasmStore, wasmtime}; use util::rel_path::RelPath; pub use buffer::Operation; pub use buffer::*; pub use diagnostic::{Diagnostic, DiagnosticSourceKind}; pub use diagnostic_set::{DiagnosticEntry, DiagnosticEntryRef, DiagnosticGroup}; pub use file_content::{ByteContent, FILE_ANALYSIS_BYTES, analyze_byte_content}; pub use language_registry::{ AvailableLanguage, BinaryStatus, LanguageNotFound, LanguageQueries, LanguageRegistry, QUERY_FILENAME_PREFIXES, }; pub use lsp::{LanguageServerId, LanguageServerName}; pub use outline::*; pub use syntax_map::{ OwnedSyntaxLayer, SyntaxLayer, SyntaxMapMatches, ToTreeSitterPoint, TreeSitterOptions, }; pub use text::{AnchorRangeExt, LineEnding}; pub use tree_sitter::{Node, Parser, Tree, TreeCursor}; pub(crate) fn to_settings_soft_wrap(value: language_core::SoftWrap) -> settings::SoftWrap { match value { language_core::SoftWrap::None => settings::SoftWrap::None, language_core::SoftWrap::PreferLine => settings::SoftWrap::PreferLine, language_core::SoftWrap::EditorWidth => settings::SoftWrap::EditorWidth, language_core::SoftWrap::Bounded => settings::SoftWrap::Bounded, } } static QUERY_CURSORS: Mutex<Vec<QueryCursor>> = Mutex::new(vec![]); static PARSERS: Mutex<Vec<Parser>> = Mutex::new(vec![]); #[ztracing::instrument(skip_all)] 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 }); // Tree-sitter auto-resets the parser at the end of a successful parse, // but the cancellation paths (progress callback returning `Break`, // cancelled balancing) leave outstanding state on the parser. The next // call to `parse_with_options` would then *resume* that cancelled parse // instead of starting fresh. parser.reset(); 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 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), matcher: LanguageMatcher { path_suffixes: vec!["txt".to_owned()], first_line_pattern: None, modeline_aliases: vec!["text".to_owned(), "txt".to_owned()], }, brackets: BracketPairConfig { pairs: vec![ BracketPair { start: "(".to_string(), end: ")".to_string(), close: true, surround: true, newline: false, }, BracketPair { start: "[".to_string(), end: "]".to_string(), close: true, surround: true, newline: false, }, BracketPair { start: "{".to_string(), end: "}".to_string(), close: true, surround: true, newline: false, }, BracketPair { start: "\"".to_string(), end: "\"".to_string(), close: true, surround: true, newline: false, }, BracketPair { start: "'".to_string(), end: "'".to_string(), close: true, surround: true, newline: false, }, ], disabled_scopes_by_bracket_ix: Default::default(), }, ..Default::default() }, None, )) }); /// Commands that the client (editor) handles locally rather than forwarding /// to the language server. Servers embed these in code lens and code action /// responses when they want the editor to perform a well-known UI action. #[derive(Debug, Clone)] pub enum ClientCommand { /// Open a location list (references panel / peek view). ShowLocations, /// Schedule a task from an LSP command's arguments. ScheduleTask(task::TaskTemplate), } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Location { pub buffer: Entity<Buffer>, pub range: Range<Anchor>, } type ServerBinaryCache = futures::lock::Mutex<Option<(bool, LanguageServerBinary)>>; type DownloadableLanguageServerBinary = LocalBoxFuture<'static, Result<LanguageServerBinary>>; pub type LanguageServerBinaryLocations = LocalBoxFuture< 'static, ( Result<LanguageServerBinary>, Option<DownloadableLanguageServerBinary>, ), >; /// 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<LanguageName, String>, pub adapter: Arc<dyn LspAdapter>, cached_binary: Arc<ServerBinaryCache>, } 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) .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(), }) } pub fn name(&self) -> LanguageServerName { self.adapter.name() } pub async fn get_language_server_command( self: Arc<Self>, delegate: Arc<dyn LspAdapterDelegate>, toolchains: Option<Toolchain>, binary_options: LanguageServerBinaryOptions, cx: &mut AsyncApp, ) -> LanguageServerBinaryLocations { let cached_binary = self.cached_binary.clone().lock_owned().await; self.adapter.clone().get_language_server_command( delegate, toolchains, binary_options, cached_binary, cx.clone(), ) } pub fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> { self.adapter.code_action_kinds() } pub fn process_diagnostics( &self, params: &mut lsp::PublishDiagnosticsParams, server_id: LanguageServerId, ) { self.adapter.process_diagnostics(params, server_id) } pub fn retain_old_diagnostic(&self, previous_diagnostic: &Diagnostic) -> bool { self.adapter.retain_old_diagnostic(previous_diagnostic) } pub fn underline_diagnostic(&self, diagnostic: &lsp::Diagnostic) -> bool { self.adapter.underline_diagnostic(diagnostic) } pub fn diagnostic_message_to_markdown(&self, message: &str) -> Option<String> { self.adapter.diagnostic_message_to_markdown(message) } 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: &[Symbol], 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) .cloned() .unwrap_or_else(|| language_name.lsp_id()) } pub async fn initialization_options_schema( &self, delegate: &Arc<dyn LspAdapterDelegate>, cx: &mut AsyncApp, ) -> Option<serde_json::Value> { self.adapter .clone() .initialization_options_schema( delegate, self.cached_binary.clone().lock_owned().await, cx, ) .await } pub async fn settings_schema( &self, delegate: &Arc<dyn LspAdapterDelegate>, cx: &mut AsyncApp, ) -> Option<serde_json::Value> { self.adapter .clone() .settings_schema(delegate, self.cached_binary.clone().lock_owned().await, cx) .await } pub fn process_prompt_response(&self, context: &PromptResponseContext, cx: &mut AsyncApp) { self.adapter.process_prompt_response(context, cx) } } /// [`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 App); fn http_client(&self) -> Arc<dyn HttpClient>; fn worktree_id(&self) -> WorktreeId; fn worktree_root_path(&self) -> &Path; fn resolve_relative_path(&self, path: PathBuf) -> PathBuf; fn update_status(&self, language: LanguageServerName, status: BinaryStatus); fn registered_lsp_adapters(&self) -> Vec<Arc<dyn LspAdapter>>; 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, Version)>>; async fn which(&self, command: &OsStr) -> Option<PathBuf>; async fn shell_env(&self) -> HashMap<String, String>; async fn read_text_file(&self, path: &RelPath) -> Result<String>; async fn try_exec(&self, binary: LanguageServerBinary) -> Result<()>; } #[async_trait(?Send)] pub trait LspAdapter: 'static + Send + Sync + DynLspInstaller { fn name(&self) -> LanguageServerName; fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams, _: LanguageServerId) {} /// When processing new `lsp::PublishDiagnosticsParams` diagnostics, whether to retain previous one(s) or not. fn retain_old_diagnostic(&self, _previous_diagnostic: &Diagnostic) -> bool { false } /// Whether to underline a given diagnostic or not, when rendering in the editor. /// /// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnosticTag /// states that /// > Clients are allowed to render diagnostics with this tag faded out instead of having an error squiggle. /// for the unnecessary diagnostics, so do not underline them. fn underline_diagnostic(&self, _diagnostic: &lsp::Diagnostic) -> bool { true } /// Post-processes completions provided by the language server. async fn process_completions(&self, _: &mut [lsp::CompletionItem]) {} fn diagnostic_message_to_markdown(&self, _message: &str) -> Option<String> { None } 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: &[Symbol], language: &Arc<Language>, ) -> Result<Vec<Option<CodeLabel>>> { let mut labels = Vec::new(); for (ix, symbol) in symbols.iter().enumerate() { let label = self.label_for_symbol(symbol, 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, _symbol: &Symbol, _language: &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>, _cx: &mut AsyncApp, ) -> Result<Option<Value>> { Ok(None) }