15058 lines · 596.1 KB · Rust
RawDownload
//! LSP store provides unified access to the language server protocol.//! The consumers of LSP store can interact with language servers without knowing exactly which language server they're interacting with.//!//! # Local/Remote LSP Stores//! This module is split up into three distinct parts://! - [`LocalLspStore`], which is ran on the host machine (either project host or SSH host), that manages the lifecycle of language servers.//! - [`RemoteLspStore`], which is ran on the remote machine (project guests) which is mostly about passing through the requests via RPC.//!   The remote stores don't really care about which language server they're running against - they don't usually get to decide which language server is going to responsible for handling their request.//! - [`LspStore`], which unifies the two under one consistent interface for interacting with language servers.//!//! Most of the interesting work happens at the local layer, as bulk of the complexity is with managing the lifecycle of language servers. The actual implementation of the LSP protocol is handled by [`lsp`] crate.pub mod clangd_ext;pub mod code_lens;mod document_colors;mod document_links;mod document_symbols;mod dynamic_registration;mod folding_ranges;mod inlay_hints;pub mod json_language_server_ext;pub mod log_store;pub mod lsp_ext_command;pub mod rust_analyzer_ext;mod semantic_tokens;pub mod vue_language_server_ext; use self::code_lens::CodeLensData;use self::document_colors::DocumentColorData;use self::document_links::DocumentLinksData;use self::document_symbols::DocumentSymbolsData;use self::dynamic_registration::{DynamicRegistrations, RegistrationSource};use self::inlay_hints::BufferInlayHints;use crate::{    CodeAction, Completion, CompletionDisplayOptions, CompletionResponse, CompletionSource,    CoreCompletion, Hover, InlayHint, InlayId, LocationLink, LspAction, LspPullDiagnostics,    ManifestProvidersStore, Project, ProjectItem, ProjectPath, ProjectTransaction,    PulledDiagnostics, ResolveState, Symbol,    buffer_store::{BufferStore, BufferStoreEvent},    environment::ProjectEnvironment,    lsp_command::{self, *},    lsp_store::{        self,        folding_ranges::FoldingRangeData,        log_store::{GlobalLogStore, LanguageServerKind},        semantic_tokens::{SemanticTokenConfig, SemanticTokensData},    },    manifest_tree::{        LanguageServerTree, LanguageServerTreeNode, LaunchDisposition, ManifestQueryDelegate,        ManifestTree,    },    prettier_store::{self, PrettierStore, PrettierStoreEvent},    project_settings::{BinarySettings, LspSettings, ProjectSettings},    toolchain_store::{LocalToolchainStore, ToolchainStoreEvent},    trusted_worktrees::{PathTrust, TrustedWorktrees, TrustedWorktreesEvent},    worktree_store::{WorktreeStore, WorktreeStoreEvent},    yarn::YarnPathStore,};use anyhow::{Context as _, Result, anyhow};use async_trait::async_trait;use client::{TypedEnvelope, proto};use clock::Global;use collections::{BTreeMap, BTreeSet, HashMap, HashSet, btree_map};use futures::{    AsyncWriteExt, Future, FutureExt, StreamExt,    channel::oneshot,    future::{Either, Shared, join_all, select},    select, select_biased,    stream::FuturesUnordered,};use globset::{Glob, GlobBuilder, GlobMatcher, GlobSet, GlobSetBuilder};use gpui::{    App, AppContext, AsyncApp, Context, Entity, EventEmitter, PromptLevel, SharedString,    Subscription, Task, TaskExt, WeakEntity,};use http_client::HttpClient;use itertools::Itertools as _;use language::{    Bias, BinaryStatus, Buffer, BufferRow, BufferSnapshot, CachedLspAdapter, Capability, CodeLabel,    CodeLabelExt, Diagnostic, DiagnosticEntry, DiagnosticSet, DiagnosticSourceKind, Diff,    File as _, Language, LanguageAwareStyling, LanguageName, LanguageRegistry, LocalFile,    LspAdapter, LspAdapterDelegate, LspInstaller, ManifestDelegate, ManifestName, ModelineSettings,    OffsetUtf16, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToOffsetUtf16, ToPointUtf16,    Toolchain, Transaction, Unclipped,    language_settings::{        AllLanguageSettings, FormatOnSave, Formatter, LanguageSettings, LineEndingSetting,        all_language_settings,    },    lsp_to_symbol_kind, modeline, point_to_lsp,    proto::{        deserialize_anchor, deserialize_anchor_range, deserialize_version, serialize_anchor,        serialize_anchor_range, serialize_version,    },    range_from_lsp, range_to_lsp,    row_chunk::RowChunk,};use lsp::{    AdapterServerCapabilities, CodeActionKind, CompletionContext, DEFAULT_LSP_REQUEST_TIMEOUT,    DiagnosticServerCapabilities, DiagnosticSeverity, DiagnosticTag,    DidChangeWatchedFilesRegistrationOptions, Edit, FileOperationFilter, FileOperationPatternKind,    FileOperationRegistrationOptions, FileRename, FileSystemWatcher, LanguageServer,    LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerId, LanguageServerName,    LanguageServerSelector, LspRequestFuture, MessageActionItem, MessageType, OneOf,    RenameFilesParams, TextEdit, Uri, WillRenameFiles, WorkDoneProgressCancelParams,    notification::DidRenameFiles,};use node_runtime::read_package_installed_version;use parking_lot::Mutex;use postage::{mpsc, sink::Sink, stream::Stream, watch};use rand::prelude::*;use rpc::{    AnyProtoClient, ErrorCode, ErrorExt as _,    proto::{LspRequestId, LspRequestMessage as _},};use semver::Version;use serde::Serialize;use serde_json::Value;use settings::{Settings, SettingsLocation, SettingsStore};use sha2::{Digest, Sha256};use snippet::Snippet;use std::{    any::TypeId,    borrow::Cow,    cell::RefCell,    cmp::{Ordering, Reverse},    collections::{VecDeque, hash_map},    convert::TryInto,    ffi::OsStr,    iter, mem,    ops::{ControlFlow, Range},    path::{self, Path, PathBuf},    pin::pin,    rc::Rc,    sync::{        Arc,        atomic::{self, AtomicUsize},    },    time::{Duration, Instant},    vec,};use sum_tree::Dimensions;use text::{Anchor, BufferId, LineEnding, OffsetRangeExt, ToPoint as _}; use util::{    ConnectionResult, ResultExt as _, debug_panic, defer, maybe, merge_json_value_into,    paths::{PathStyle, SanitizedPath, UrlExt},    post_inc,    redact::redact_command,    rel_path::RelPath,}; pub use document_colors::DocumentColors;pub use document_links::{    BufferDocumentLinks, DocumentLinkId, DocumentLinkResolveTask, LspDocumentLink,    ResolvedDocumentLink,};pub use folding_ranges::LspFoldingRange;pub use fs::*;pub use language::Location;pub use lsp_store::inlay_hints::{CacheInlayHints, CacheInlayHintsTask, InvalidationStrategy};#[cfg(any(test, feature = "test-support"))]pub use prettier::FORMAT_SUFFIX as TEST_PRETTIER_FORMAT_SUFFIX;#[cfg(any(test, feature = "test-support"))]pub use prettier::RANGE_FORMAT_SUFFIX as TEST_PRETTIER_RANGE_FORMAT_SUFFIX;pub use semantic_tokens::{    BufferSemanticToken, BufferSemanticTokens, SemanticTokenStylizer, TokenType,}; pub use worktree::{    Entry, EntryKind, FS_WATCH_LATENCY, File, LocalWorktree, PathChange, ProjectEntryId,    UpdatedEntriesSet, UpdatedGitRepositoriesSet, Worktree, WorktreeId, WorktreeSettings,}; const SERVER_LAUNCHING_BEFORE_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);pub const SERVER_PROGRESS_THROTTLE_TIMEOUT: Duration = Duration::from_millis(100);const WORKSPACE_DIAGNOSTICS_TOKEN_START: &str = "id:";const SERVER_DOWNLOAD_TIMEOUT: Duration = Duration::from_secs(10);static NEXT_PROMPT_REQUEST_ID: AtomicUsize = AtomicUsize::new(0); /// Refresh messages carry a monotonic id for backwards compatibility only: older peers/// use it to order refreshes, while current ones re-mark pending refreshes on arrival./// The envelope's own message id cannot be used, as old peers compare the payload field.fn next_wire_refresh_request_id() -> u64 {    static NEXT_WIRE_REFRESH_REQUEST_ID: AtomicUsize = AtomicUsize::new(1);    NEXT_WIRE_REFRESH_REQUEST_ID.fetch_add(1, atomic::Ordering::Relaxed) as u64} #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]pub enum ProgressToken {    Number(i32),    String(SharedString),} impl std::fmt::Display for ProgressToken {    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {        match self {            Self::Number(number) => write!(f, "{number}"),            Self::String(string) => write!(f, "{string}"),        }    }} impl ProgressToken {    fn from_lsp(value: lsp::NumberOrString) -> Self {        match value {            lsp::NumberOrString::Number(number) => Self::Number(number),            lsp::NumberOrString::String(string) => Self::String(SharedString::new(string)),        }    }     fn to_lsp(&self) -> lsp::NumberOrString {        match self {            Self::Number(number) => lsp::NumberOrString::Number(*number),            Self::String(string) => lsp::NumberOrString::String(string.to_string()),        }    }     fn from_proto(value: proto::ProgressToken) -> Option<Self> {        Some(match value.value? {            proto::progress_token::Value::Number(number) => Self::Number(number),            proto::progress_token::Value::String(string) => Self::String(SharedString::new(string)),        })    }     fn to_proto(&self) -> proto::ProgressToken {        proto::ProgressToken {            value: Some(match self {                Self::Number(number) => proto::progress_token::Value::Number(*number),                Self::String(string) => proto::progress_token::Value::String(string.to_string()),            }),        }    }} #[derive(Debug, Clone, Copy, PartialEq, Eq)]pub enum FormatTrigger {    Save,    Manual,} pub enum LspFormatTarget {    Buffers,    Ranges(BTreeMap<BufferId, Vec<Range<Anchor>>>),} #[derive(Debug, Clone, PartialEq, Eq, Hash)]pub struct OpenLspBufferHandle(Entity<OpenLspBuffer>); struct OpenLspBuffer(Entity<Buffer>); impl FormatTrigger {    fn from_proto(value: i32) -> FormatTrigger {        match value {            0 => FormatTrigger::Save,            1 => FormatTrigger::Manual,            _ => FormatTrigger::Save,        }    }} #[derive(Clone)]struct UnifiedLanguageServer {    id: LanguageServerId,    project_roots: HashSet<Arc<RelPath>>,} /// Settings that affect language server identity.////// Dynamic settings (`LspSettings::settings`) are excluded because they can be/// updated via `workspace/didChangeConfiguration` without restarting the server.#[derive(Clone, Debug, Hash, PartialEq, Eq)]struct LanguageServerSeedSettings {    binary: Option<BinarySettings>,    initialization_options: Option<serde_json::Value>,} #[derive(Clone, Debug, Hash, PartialEq, Eq)]struct LanguageServerSeed {    worktree_id: WorktreeId,    name: LanguageServerName,    toolchain: Option<Toolchain>,    settings: LanguageServerSeedSettings,} #[derive(Debug)]pub struct DocumentDiagnosticsUpdate<'a, D> {    pub diagnostics: D,    pub result_id: Option<SharedString>,    pub registration_id: Option<SharedString>,    pub server_id: LanguageServerId,    pub disk_based_sources: Cow<'a, [String]>,} pub struct DocumentDiagnostics {    diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,    document_abs_path: PathBuf,    version: Option<i32>,} pub struct LocalLspStore {    weak: WeakEntity<LspStore>,    pub worktree_store: Entity<WorktreeStore>,    toolchain_store: Entity<LocalToolchainStore>,    http_client: Arc<dyn HttpClient>,    environment: Entity<ProjectEnvironment>,    fs: Arc<dyn Fs>,    languages: Arc<LanguageRegistry>,    language_server_ids: HashMap<LanguageServerSeed, UnifiedLanguageServer>,    yarn: Entity<YarnPathStore>,    pub language_servers: HashMap<LanguageServerId, LanguageServerState>,    buffers_being_formatted: HashSet<BufferId>,    last_workspace_edits_by_language_server: HashMap<LanguageServerId, ProjectTransaction>,    language_server_watched_paths: HashMap<LanguageServerId, LanguageServerWatchedPaths>,    watched_manifest_filenames: HashSet<ManifestName>,    language_server_paths_watched_for_rename:        HashMap<LanguageServerId, RenamePathsWatchedForServer>,    language_server_dynamic_registrations: HashMap<LanguageServerId, DynamicRegistrations>,    supplementary_language_servers:        HashMap<LanguageServerId, (LanguageServerName, Arc<LanguageServer>)>,    prettier_store: Entity<PrettierStore>,    next_diagnostic_group_id: usize,    diagnostics: HashMap<        WorktreeId,        HashMap<            Arc<RelPath>,            Vec<(                LanguageServerId,                Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,            )>,        >,    >,    buffer_snapshots: HashMap<BufferId, HashMap<LanguageServerId, Vec<LspBufferSnapshot>>>, // buffer_id -> server_id -> vec of snapshots    _subscription: gpui::Subscription,    lsp_tree: LanguageServerTree,    registered_buffers: HashMap<BufferId, usize>,    buffers_opened_in_servers: HashMap<BufferId, HashSet<LanguageServerId>>,    buffer_pull_diagnostics_result_ids: HashMap<        LanguageServerId,        HashMap<Option<SharedString>, HashMap<PathBuf, Option<SharedString>>>,    >,    workspace_pull_diagnostics_result_ids: HashMap<        LanguageServerId,        HashMap<Option<SharedString>, HashMap<PathBuf, Option<SharedString>>>,    >,    restricted_worktrees_tasks: HashMap<WorktreeId, (Subscription, watch::Receiver<bool>)>,    all_language_servers_stopped: bool,    stopped_language_servers: HashSet<LanguageServerName>,     buffers_to_refresh_hash_set: HashSet<BufferId>,    buffers_to_refresh_queue: VecDeque<BufferId>,    _background_diagnostics_worker: Shared<Task<()>>,} impl LocalLspStore {    /// Returns the running language server for the given ID. Note if the language server is starting, it will not be returned.    pub fn running_language_server_for_id(        &self,        id: LanguageServerId,    ) -> Option<&Arc<LanguageServer>> {        let language_server_state = self.language_servers.get(&id)?;         match language_server_state {            LanguageServerState::Running { server, .. } => Some(server),            LanguageServerState::Starting { .. } => None,        }    }     fn get_or_insert_language_server(        &mut self,        worktree_handle: &Entity<Worktree>,        delegate: Arc<LocalLspAdapterDelegate>,        disposition: &Arc<LaunchDisposition>,        language_name: &LanguageName,        cx: &mut App,    ) -> LanguageServerId {        let key = LanguageServerSeed {            worktree_id: worktree_handle.read(cx).id(),            name: disposition.server_name.clone(),            settings: LanguageServerSeedSettings {                binary: disposition.settings.binary.clone(),                initialization_options: disposition.settings.initialization_options.clone(),            },            toolchain: disposition.toolchain.clone(),        };        if let Some(state) = self.language_server_ids.get_mut(&key) {            state.project_roots.insert(disposition.path.path.clone());            state.id        } else {            let adapter = self                .languages                .lsp_adapters(language_name)                .into_iter()                .find(|adapter| adapter.name() == disposition.server_name)                .expect("To find LSP adapter");            let new_language_server_id = self.start_language_server(                worktree_handle,                delegate,                adapter,                disposition.settings.clone(),                key.clone(),                language_name.clone(),                cx,            );            if let Some(state) = self.language_server_ids.get_mut(&key) {                state.project_roots.insert(disposition.path.path.clone());            } else {                debug_assert!(                    false,                    "Expected `start_language_server` to ensure that `key` exists in a map"                );            }            new_language_server_id        }    }     fn start_language_server(        &mut self,        worktree_handle: &Entity<Worktree>,        delegate: Arc<LocalLspAdapterDelegate>,        adapter: Arc<CachedLspAdapter>,        settings: Arc<LspSettings>,        key: LanguageServerSeed,        language_name: LanguageName,        cx: &mut App,    ) -> LanguageServerId {        let worktree = worktree_handle.read(cx);         let worktree_id = worktree.id();        let worktree_abs_path = worktree.abs_path();        let toolchain = key.toolchain.clone();        let override_options = settings.initialization_options.clone();         let stderr_capture = Arc::new(Mutex::new(Some(String::new())));         let server_id = self.languages.next_language_server_id();        log::trace!(            "attempting to start language server {:?}, path: {worktree_abs_path:?}, id: {server_id}",            adapter.name.0        );         let wait_until_worktree_trust =            TrustedWorktrees::try_get_global(cx).and_then(|trusted_worktrees| {                let can_trust = trusted_worktrees.update(cx, |trusted_worktrees, cx| {                    trusted_worktrees.can_trust(&self.worktree_store, worktree_id, cx)                });                if can_trust {                    self.restricted_worktrees_tasks.remove(&worktree_id);                    None                } else {                    match self.restricted_worktrees_tasks.entry(worktree_id) {                        hash_map::Entry::Occupied(o) => Some(o.get().1.clone()),                        hash_map::Entry::Vacant(v) => {                            let (mut tx, rx) = watch::channel::<bool>();                            let lsp_store = self.weak.clone();                            let subscription = cx.subscribe(&trusted_worktrees, move |_, e, cx| {                                if let TrustedWorktreesEvent::Trusted(_, trusted_paths) = e {                                    if trusted_paths.contains(&PathTrust::Worktree(worktree_id)) {                                        tx.blocking_send(true).ok();                                        lsp_store                                            .update(cx, |lsp_store, _| {                                                if let Some(local_lsp_store) =                                                    lsp_store.as_local_mut()                                                {                                                    local_lsp_store                                                        .restricted_worktrees_tasks                                                        .remove(&worktree_id);                                                }                                            })                                            .ok();                                    }                                }                            });                            v.insert((subscription, rx.clone()));                            Some(rx)                        }                    }                }            });        let update_binary_status = wait_until_worktree_trust.is_none();         let binary = self.get_language_server_binary(            worktree_abs_path.clone(),            adapter.clone(),            settings,            toolchain.clone(),            delegate.clone(),            true,            wait_until_worktree_trust,            cx,        );        let pending_workspace_folders = Arc::<Mutex<BTreeSet<Uri>>>::default();         let pending_server = cx.spawn({            let adapter = adapter.clone();            let server_name = adapter.name.clone();            let stderr_capture = stderr_capture.clone();            #[cfg(any(test, feature = "test-support"))]            let lsp_store = self.weak.clone();            let pending_workspace_folders = pending_workspace_folders.clone();            async move |cx| {                let binary = binary.await?;

Lines 1–500 of 15058.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t