1994 lines · 68.7 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;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.