2366 lines · 80.6 KB · Rust
RawDownload
//! 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 manifest;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::EditPredictionsMode;use crate::language_settings::SoftWrap;use anyhow::{Context as _, Result};use async_trait::async_trait;use collections::{HashMap, HashSet, IndexSet};use fs::Fs;use futures::Future;use gpui::{App, AsyncApp, Entity, SharedString, Task};pub use highlight_map::HighlightMap;use http_client::HttpClient;pub use language_registry::{LanguageName, LoadedLanguage};use lsp::{CodeActionKind, InitializeParams, LanguageServerBinary, LanguageServerBinaryOptions};pub use manifest::{ManifestDelegate, ManifestName, ManifestProvider, ManifestQuery};use parking_lot::Mutex;use regex::Regex;use schemars::{    JsonSchema,    r#gen::SchemaGenerator,    schema::{InstanceType, Schema, SchemaObject},};use serde::{Deserialize, Deserializer, Serialize, Serializer, de};use serde_json::Value;use settings::WorktreeId;use smol::future::FutureExt as _;use std::{    any::Any,    ffi::OsStr,    fmt::Debug,    hash::Hash,    mem,    ops::{DerefMut, Range},    path::{Path, PathBuf},    pin::Pin,    str,    sync::{        Arc, LazyLock,        atomic::{AtomicU64, AtomicUsize, Ordering::SeqCst},    },};use std::{num::NonZeroU32, sync::OnceLock};use syntax_map::{QueryCursorHandle, SyntaxSnapshot};use task::RunnableTag;pub use task_context::{ContextLocation, ContextProvider, RunnableRange};pub use text_diff::{    DiffOptions, apply_diff_patch, line_diff, text_diff, text_diff_with_options, unified_diff,};use theme::SyntaxTheme;pub use toolchain::{LanguageToolchainStore, Toolchain, ToolchainList, ToolchainLister};use tree_sitter::{self, Query, QueryCursor, WasmStore, wasmtime};use util::serde::default_true; pub use buffer::Operation;pub use buffer::*;pub use diagnostic_set::{DiagnosticEntry, DiagnosticGroup};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, ToTreeSitterPoint, TreeSitterOptions};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 App) {    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),            matcher: LanguageMatcher {                path_suffixes: vec!["txt".to_owned()],                first_line_pattern: None,            },            ..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;} #[derive(Debug, Clone, PartialEq, Eq, Hash)]pub struct Location {    pub buffer: Entity<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>>,    manifest_name: OnceLock<Option<ManifestName>>,    attach_kind: OnceLock<Attach>,} 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),            attach_kind: Default::default(),            manifest_name: Default::default(),        })    }     pub fn name(&self) -> LanguageServerName {        self.adapter.name().clone()    }     pub async fn get_language_server_command(        self: Arc<Self>,        delegate: Arc<dyn LspAdapterDelegate>,        toolchains: Arc<dyn LanguageToolchainStore>,        binary_options: LanguageServerBinaryOptions,        cx: &mut AsyncApp,    ) -> Result<LanguageServerBinary> {        let cached_binary = self.cached_binary.lock().await;        self.adapter            .clone()            .get_language_server_command(delegate, toolchains, 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,        server_id: LanguageServerId,        existing_diagnostics: Option<&'_ Buffer>,    ) {        self.adapter            .process_diagnostics(params, server_id, existing_diagnostics)    }     pub fn retain_old_diagnostic(&self, previous_diagnostic: &Diagnostic, cx: &App) -> bool {        self.adapter.retain_old_diagnostic(previous_diagnostic, cx)    }     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: &[(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.as_ref())            .cloned()            .unwrap_or_else(|| language_name.lsp_id())    }    pub fn manifest_name(&self) -> Option<ManifestName> {        self.manifest_name            .get_or_init(|| self.adapter.manifest_name())            .clone()    }    pub fn attach_kind(&self) -> Attach {        *self.attach_kind.get_or_init(|| self.adapter.attach_kind())    }} #[derive(Clone, Copy, Debug, PartialEq)]pub enum Attach {    /// Create a single language server instance per subproject root.    InstancePerRoot,    /// Use one shared language server instance for all subprojects within a project.    Shared,} impl Attach {    pub fn root_path(        &self,        root_subproject_path: (WorktreeId, Arc<Path>),    ) -> (WorktreeId, Arc<Path>) {        match self {            Attach::InstancePerRoot => root_subproject_path,            Attach::Shared => (root_subproject_path.0, Arc::from(Path::new(""))),        }    }} /// [`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 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, 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>,        toolchains: Arc<dyn LanguageToolchainStore>,        binary_options: LanguageServerBinaryOptions,        mut cached_binary: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,        cx: &'a mut AsyncApp,    ) -> 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(), toolchains, cx).await {                    log::info!(                        "found user-installed language server for {}. path: {:?}, arguments: {:?}",                        self.name().0,                        binary.path,                        binary.arguments                    );                    return Ok(binary);                }            }             anyhow::ensure!(binary_options.allow_binary_download, "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(),                        BinaryStatus::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,        _: Arc<dyn LanguageToolchainStore>,        _: &AsyncApp,    ) -> 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 AsyncApp,    ) -> Option<Task<Result<()>>> {        None    }     async fn check_if_version_installed(        &self,        _version: &(dyn 'static + Send + Any),        _container_dir: &PathBuf,        _delegate: &dyn LspAdapterDelegate,    ) -> Option<LanguageServerBinary> {        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,        _: LanguageServerId,        _: Option<&'_ Buffer>,    ) {    }     /// When processing new `lsp::PublishDiagnosticsParams` diagnostics, whether to retain previous one(s) or not.    fn retain_old_diagnostic(&self, _previous_diagnostic: &Diagnostic, _cx: &App) -> 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;

Lines 1–500 of 2366.