Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T02:54:02.955Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

project.rs

6887 lines · 242.4 KB · rust
1pub mod agent_registry_store;
2pub mod agent_server_store;
3pub mod bookmark_store;
4pub mod buffer_store;
5pub mod color_extractor;
6pub mod connection_manager;
7pub mod context_server_store;
8pub mod debounced_delay;
9pub mod debugger;
10pub mod git_store;
11pub mod harness_maintenance;
12pub mod image_store;
13pub mod lsp_command;
14pub mod lsp_store;
15pub mod manifest_tree;
16pub mod prettier_store;
17pub mod project_search;
18pub mod project_settings;
19pub mod search;
20pub mod task_inventory;
21pub mod task_store;
22pub mod telemetry_snapshot;
23pub mod terminals;
24pub mod toolchain_store;
25pub mod trusted_worktrees;
26pub mod worktree_store;
27
28mod environment;
29use buffer_diff::BufferDiff;
30use context_server_store::ContextServerStore;
31pub use environment::ProjectEnvironmentEvent;
32use git::repository::get_git_committer;
33use git_store::{Repository, RepositoryId};
34pub mod search_history;
35pub mod yarn;
36
37use dap::inline_value::{InlineValueLocation, VariableLookupKind, VariableScope};
38use itertools::{Either, Itertools};
39
40use crate::{
41    bookmark_store::BookmarkStore,
42    git_store::GitStore,
43    lsp_store::{SymbolLocation, log_store::LogKind},
44    project_search::SearchResultsHandle,
45    trusted_worktrees::{PathTrust, RemoteHostLocation, TrustedWorktrees},
46    worktree_store::WorktreeIdCounter,
47};
48pub use agent_registry_store::{AgentRegistryStore, RegistryAgent};
49pub use agent_server_store::{AgentId, AgentServerStore, AgentServersUpdated, ExternalAgentSource};
50pub use git_store::{
51    ConflictRegion, ConflictSet, ConflictSetSnapshot, ConflictSetUpdate,
52    git_traversal::{ChildEntriesGitIter, GitEntry, GitEntryRef, GitTraversal},
53    linked_worktree_short_name, repo_identity_path, worktrees_directory_for_repo,
54};
55pub use manifest_tree::ManifestTree;
56pub use project_search::{Search, SearchResults};
57pub use worktree_store::WorktreePaths;
58
59use anyhow::{Context as _, Result, anyhow};
60use buffer_store::{BufferStore, BufferStoreEvent};
61use client::{
62    Client, Collaborator, PendingEntitySubscription, ProjectId, TypedEnvelope, UserStore, proto,
63};
64use clock::ReplicaId;
65
66use dap::client::DebugAdapterClient;
67
68use collections::{BTreeSet, HashMap, HashSet, IndexSet};
69use debounced_delay::DebouncedDelay;
70pub use debugger::breakpoint_store::BreakpointWithPosition;
71use debugger::{
72    breakpoint_store::{ActiveStackFrame, BreakpointStore},
73    dap_store::{DapStore, DapStoreEvent},
74    session::Session,
75};
76
77pub use environment::ProjectEnvironment;
78
79use futures::{
80    StreamExt,
81    channel::mpsc::{self, UnboundedReceiver},
82    future::try_join_all,
83};
84pub use image_store::{ImageItem, ImageStore};
85use image_store::{ImageItemEvent, ImageStoreEvent};
86
87use ::git::{blame::Blame, status::FileStatus};
88use gpui::{
89    App, AppContext, AsyncApp, BorrowAppContext, Context, Entity, EventEmitter, Hsla, SharedString,
90    Task, TaskExt, WeakEntity, Window,
91};
92use language::{
93    Buffer, BufferEditSource, BufferEvent, Capability, CodeLabel, CursorShape, DiskState, Language,
94    LanguageName, LanguageRegistry, PointUtf16, ToOffset, ToPointUtf16, Toolchain,
95    ToolchainMetadata, ToolchainScope, Transaction, Unclipped, language_settings::InlayHintKind,
96    proto::split_operations,
97};
98use lsp::{
99    CodeActionKind, CompletionContext, CompletionItemKind, DocumentHighlightKind, InsertTextMode,
100    LanguageServerBinary, LanguageServerId, LanguageServerName, LanguageServerSelector,
101    MessageActionItem,
102};
103pub use lsp_command::EditPredictionDefinition;
104use lsp_command::*;
105use lsp_store::{CompletionDocumentation, LspFormatTarget, OpenLspBufferHandle};
106pub use manifest_tree::ManifestProvidersStore;
107use node_runtime::NodeRuntime;
108use parking_lot::Mutex;
109pub use prettier_store::PrettierStore;
110use project_settings::{ProjectSettings, SettingsObserver, SettingsObserverEvent};
111#[cfg(target_os = "windows")]
112use remote::wsl_path_to_windows_path;
113use remote::{RemoteClient, RemoteConnectionOptions, same_remote_connection_identity};
114use rpc::{
115    AnyProtoClient, ErrorCode,
116    proto::{LanguageServerPromptResponse, REMOTE_SERVER_PROJECT_ID},
117};
118use search::{SearchInputKind, SearchQuery, SearchResult};
119use search_history::SearchHistory;
120use settings::{InvalidSettingsError, RegisterSetting, Settings, SettingsLocation, SettingsStore};
121use snippet::Snippet;
122pub use snippet_provider;
123use snippet_provider::SnippetProvider;
124use std::{
125    borrow::Cow,
126    collections::BTreeMap,
127    ffi::OsString,
128    future::Future,
129    ops::{Not as _, Range},
130    path::{Path, PathBuf},
131    pin::pin,
132    str::{self, FromStr},
133    sync::Arc,
134    time::Duration,
135};
136
137use task_store::TaskStore;
138use terminals::Terminals;
139use text::{Anchor, BufferId, Point, Rope};
140use toolchain_store::EmptyToolchainStore;
141use util::{
142    ResultExt as _, maybe,
143    path_list::PathList,
144    paths::{PathStyle, SanitizedPath, is_absolute},
145    rel_path::RelPath,
146};
147use worktree::{CreatedEntry, Snapshot, Traversal};
148pub use worktree::{
149    Entry, EntryKind, FS_WATCH_LATENCY, File, LocalWorktree, PathChange, ProjectEntryId,
150    UpdatedEntriesSet, UpdatedGitRepositoriesSet, Worktree, WorktreeId, WorktreeSettings,
151    discover_root_repo_common_dir,
152};
153use worktree_store::{WorktreeStore, WorktreeStoreEvent};
154
155pub use fs::*;
156pub use language::Location;
157#[cfg(any(test, feature = "test-support"))]
158pub use prettier::FORMAT_SUFFIX as TEST_PRETTIER_FORMAT_SUFFIX;
159#[cfg(any(test, feature = "test-support"))]
160pub use prettier::RANGE_FORMAT_SUFFIX as TEST_PRETTIER_RANGE_FORMAT_SUFFIX;
161pub use task_inventory::{
162    BasicContextProvider, ContextProviderWithTasks, DebugScenarioContext, GIT_COMMAND_TASK_TAG,
163    Inventory, TaskContexts, TaskSourceKind,
164};
165
166pub use buffer_store::ProjectTransaction;
167pub use lsp_store::{
168    DiagnosticSummary, InvalidationStrategy, LanguageServerLogType, LanguageServerProgress,
169    LanguageServerPromptRequest, LanguageServerStatus, LanguageServerToQuery, LspStore,
170    LspStoreEvent, ProgressToken, SERVER_PROGRESS_THROTTLE_TIMEOUT,
171};
172pub use toolchain_store::{ToolchainStore, Toolchains};
173const MAX_PROJECT_SEARCH_HISTORY_SIZE: usize = 500;
174
175#[derive(Clone, Copy, Debug)]
176pub struct LocalProjectFlags {
177    pub init_worktree_trust: bool,
178    pub watch_global_configs: bool,
179}
180
181impl Default for LocalProjectFlags {
182    fn default() -> Self {
183        Self {
184            init_worktree_trust: true,
185            watch_global_configs: true,
186        }
187    }
188}
189
190pub trait ProjectItem: 'static {
191    fn try_open(
192        project: &Entity<Project>,
193        path: &ProjectPath,
194        cx: &mut App,
195    ) -> Option<Task<Result<Entity<Self>>>>
196    where
197        Self: Sized;
198    fn entry_id(&self, cx: &App) -> Option<ProjectEntryId>;
199    fn project_path(&self, cx: &App) -> Option<ProjectPath>;
200    fn is_dirty(&self) -> bool;
201}
202
203#[derive(Clone)]
204pub enum OpenedBufferEvent {
205    Disconnected,
206    Ok(BufferId),
207    Err(BufferId, Arc<anyhow::Error>),
208}
209
210/// Semantics-aware entity that is relevant to one or more [`Worktree`] with the files.
211/// `Project` is responsible for tasks, LSP and collab queries, synchronizing worktree states accordingly.
212/// Maps [`Worktree`] entries with its own logic using [`ProjectEntryId`] and [`ProjectPath`] structs.
213///
214/// Can be either local (for the project opened on the same host) or remote.(for collab projects, browsed by multiple remote users).
215pub struct Project {
216    active_entry: Option<ProjectEntryId>,
217    buffer_ordered_messages_tx: mpsc::UnboundedSender<BufferOrderedMessage>,
218    languages: Arc<LanguageRegistry>,
219    dap_store: Entity<DapStore>,
220    agent_server_store: Entity<AgentServerStore>,
221
222    bookmark_store: Entity<BookmarkStore>,
223    breakpoint_store: Entity<BreakpointStore>,
224    collab_client: Arc<client::Client>,
225    join_project_response_message_id: u32,
226    task_store: Entity<TaskStore>,
227    user_store: Entity<UserStore>,
228    fs: Arc<dyn Fs>,
229    remote_client: Option<Entity<RemoteClient>>,
230    // todo lw explain the client_state x remote_client matrix, its super confusing
231    client_state: ProjectClientState,
232    git_store: Entity<GitStore>,
233    collaborators: HashMap<proto::PeerId, Collaborator>,
234    client_subscriptions: Vec<client::Subscription>,
235    worktree_store: Entity<WorktreeStore>,
236    buffer_store: Entity<BufferStore>,
237    context_server_store: Entity<ContextServerStore>,
238    image_store: Entity<ImageStore>,
239    lsp_store: Entity<LspStore>,
240    _subscriptions: Vec<gpui::Subscription>,
241    buffers_needing_diff: HashSet<WeakEntity<Buffer>>,
242    git_diff_debouncer: DebouncedDelay<Self>,
243    remotely_created_models: Arc<Mutex<RemotelyCreatedModels>>,
244    terminals: Terminals,
245    node: Option<NodeRuntime>,
246    search_history: SearchHistory,
247    search_included_history: SearchHistory,
248    search_excluded_history: SearchHistory,
249    snippets: Entity<SnippetProvider>,
250    environment: Entity<ProjectEnvironment>,
251    settings_observer: Entity<SettingsObserver>,
252    toolchain_store: Option<Entity<ToolchainStore>>,
253    agent_location: Option<AgentLocation>,
254    downloading_files: Arc<Mutex<HashMap<(WorktreeId, String), DownloadingFile>>>,
255    last_worktree_paths: WorktreePaths,
256}
257
258struct DownloadingFile {
259    destination_path: PathBuf,
260    chunks: Vec<u8>,
261    total_size: u64,
262    file_id: Option<u64>, // Set when we receive the State message
263}
264
265#[derive(Clone, Debug, PartialEq, Eq)]
266pub struct AgentLocation {
267    pub buffer: WeakEntity<Buffer>,
268    pub position: Anchor,
269}
270
271#[derive(Default)]
272struct RemotelyCreatedModels {
273    worktrees: Vec<Entity<Worktree>>,
274    buffers: Vec<Entity<Buffer>>,
275    retain_count: usize,
276}
277
278struct RemotelyCreatedModelGuard {
279    remote_models: std::sync::Weak<Mutex<RemotelyCreatedModels>>,
280}
281
282impl Drop for RemotelyCreatedModelGuard {
283    fn drop(&mut self) {
284        if let Some(remote_models) = self.remote_models.upgrade() {
285            let mut remote_models = remote_models.lock();
286            assert!(
287                remote_models.retain_count > 0,
288                "RemotelyCreatedModelGuard dropped too many times"
289            );
290            remote_models.retain_count -= 1;
291            if remote_models.retain_count == 0 {
292                remote_models.buffers.clear();
293                remote_models.worktrees.clear();
294            }
295        }
296    }
297}
298/// Message ordered with respect to buffer operations
299#[derive(Debug)]
300enum BufferOrderedMessage {
301    Operation {
302        buffer_id: BufferId,
303        operation: proto::Operation,
304    },
305    LanguageServerUpdate {
306        language_server_id: LanguageServerId,
307        message: proto::update_language_server::Variant,
308        name: Option<LanguageServerName>,
309    },
310    Resync,
311}
312
313#[derive(Debug)]
314enum ProjectClientState {
315    /// Single-player mode.
316    Local,
317    /// Multi-player mode but still a local project.
318    Shared { remote_id: u64 },
319    /// Multi-player mode but working on a remote project.
320    Collab {
321        sharing_has_stopped: bool,
322        capability: Capability,
323        remote_id: u64,
324        replica_id: ReplicaId,
325    },
326}
327
328/// A link to display in a toast notification, useful to point to documentation.
329#[derive(PartialEq, Debug, Clone)]
330pub struct ToastLink {
331    pub label: &'static str,
332    pub url: &'static str,
333}
334
335#[derive(Clone, Debug, PartialEq)]
336pub enum Event {
337    LanguageServerAdded(LanguageServerId, LanguageServerName, Option<WorktreeId>),
338    LanguageServerRemoved(LanguageServerId),
339    LanguageServerLog(LanguageServerId, LanguageServerLogType, String),
340    // [`lsp::notification::DidOpenTextDocument`] was sent to this server using the buffer data.
341    // Zed's buffer-related data is updated accordingly.
342    LanguageServerBufferRegistered {
343        server_id: LanguageServerId,
344        buffer_id: BufferId,
345        buffer_abs_path: PathBuf,
346        name: Option<LanguageServerName>,
347    },
348    ToggleLspLogs {
349        server_id: LanguageServerId,
350        enabled: bool,
351        toggled_log_kind: LogKind,
352    },
353    Toast {
354        notification_id: SharedString,
355        message: String,
356        /// Optional link to display as a button in the toast.
357        link: Option<ToastLink>,
358    },
359    HideToast {
360        notification_id: SharedString,
361    },
362    LanguageServerPrompt(LanguageServerPromptRequest),
363    LanguageNotFound(Entity<Buffer>),
364    ActiveEntryChanged(Option<ProjectEntryId>),
365    ActivateProjectPanel,
366    WorktreeAdded(WorktreeId),
367    WorktreeOrderChanged,
368    WorktreeRemoved(WorktreeId),
369    WorktreeUpdatedEntries(WorktreeId, UpdatedEntriesSet),
370    WorktreeUpdatedRootRepoCommonDir(WorktreeId),
371    WorktreePathsChanged {
372        old_worktree_paths: WorktreePaths,
373    },
374    DiskBasedDiagnosticsStarted {
375        language_server_id: LanguageServerId,
376    },
377    DiskBasedDiagnosticsFinished {
378        language_server_id: LanguageServerId,
379    },
380    DiagnosticsUpdated {
381        paths: Vec<ProjectPath>,
382        language_server_id: LanguageServerId,
383    },
384    RemoteIdChanged(Option<u64>),
385    DisconnectedFromHost,
386    DisconnectedFromRemote {
387        server_not_running: bool,
388    },
389    Closed,
390    DeletedEntry(WorktreeId, ProjectEntryId),
391    CollaboratorUpdated {
392        old_peer_id: proto::PeerId,
393        new_peer_id: proto::PeerId,
394    },
395    CollaboratorJoined(proto::PeerId),
396    CollaboratorLeft(proto::PeerId),
397    HostReshared,
398    Reshared,
399    Rejoined,
400    RefreshInlayHints {
401        server_id: LanguageServerId,
402    },
403    RefreshSemanticTokens {
404        server_id: LanguageServerId,
405    },
406    RefreshCodeLens {
407        server_id: Option<LanguageServerId>,
408    },
409    RefreshDocumentColors {
410        server_id: Option<LanguageServerId>,
411    },
412    RefreshDocumentLinks {
413        server_id: Option<LanguageServerId>,
414    },
415    RefreshFoldingRanges {
416        server_id: Option<LanguageServerId>,
417    },
418    RefreshDocumentSymbols {
419        server_id: Option<LanguageServerId>,
420    },
421    RevealInProjectPanel(ProjectEntryId),
422    SnippetEdit(BufferId, Vec<(lsp::Range, Snippet)>),
423    ExpandedAllForEntry(WorktreeId, ProjectEntryId),
424    EntryRenamed(ProjectTransaction, ProjectPath, PathBuf),
425    WorkspaceEditApplied(ProjectTransaction),
426    AgentLocationChanged,
427    BufferEdited {
428        source: BufferEditSource,
429    },
430}
431
432pub struct AgentLocationChanged;
433
434pub enum DebugAdapterClientState {
435    Starting(Task<Option<Arc<DebugAdapterClient>>>),
436    Running(Arc<DebugAdapterClient>),
437}
438
439#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
440pub struct ProjectPath {
441    pub worktree_id: WorktreeId,
442    pub path: Arc<RelPath>,
443}
444
445impl ProjectPath {
446    pub fn from_file(value: &dyn language::File, cx: &App) -> Self {
447        ProjectPath {
448            worktree_id: value.worktree_id(cx),
449            path: value.path().clone(),
450        }
451    }
452
453    pub fn from_proto(p: proto::ProjectPath) -> Option<Self> {
454        Some(Self {
455            worktree_id: WorktreeId::from_proto(p.worktree_id),
456            path: RelPath::from_unix_str(&p.path).log_err()?.into(),
457        })
458    }
459
460    pub fn to_proto(&self) -> proto::ProjectPath {
461        proto::ProjectPath {
462            worktree_id: self.worktree_id.to_proto(),
463            path: self.path.as_ref().as_unix_str().to_owned(),
464        }
465    }
466
467    pub fn root_path(worktree_id: WorktreeId) -> Self {
468        Self {
469            worktree_id,
470            path: RelPath::empty_arc(),
471        }
472    }
473
474    pub fn starts_with(&self, other: &ProjectPath) -> bool {
475        self.worktree_id == other.worktree_id && self.path.starts_with(&other.path)
476    }
477}
478
479#[derive(Debug, Default)]
480pub enum PrepareRenameResponse {
481    Success(Range<Anchor>),
482    OnlyUnpreparedRenameSupported,
483    #[default]
484    InvalidPosition,
485}
486
487#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
488pub enum InlayId {
489    EditPrediction(usize),
490    DebuggerValue(usize),
491    // LSP
492    Hint(usize),
493    Color(usize),
494    ReplResult(usize),
495}
496
497impl InlayId {
498    pub fn id(&self) -> usize {
499        match self {
500            Self::EditPrediction(id) => *id,
501            Self::DebuggerValue(id) => *id,
502            Self::Hint(id) => *id,
503            Self::Color(id) => *id,
504            Self::ReplResult(id) => *id,
505        }
506    }
507}
508
509#[derive(Debug, Clone, PartialEq, Eq)]
510pub struct InlayHint {
511    pub position: language::Anchor,
512    pub label: InlayHintLabel,
513    pub kind: Option<InlayHintKind>,
514    pub padding_left: bool,
515    pub padding_right: bool,
516    pub tooltip: Option<InlayHintTooltip>,
517    pub resolve_state: ResolveState,
518}
519
520/// The user's intent behind a given completion confirmation.
521#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)]
522pub enum CompletionIntent {
523    /// The user intends to 'commit' this result, if possible.
524    /// Completion confirmations should run side effects.
525    ///
526    /// For LSP completions, will respect the setting `completions.lsp_insert_mode`.
527    Complete,
528    /// Similar to [Self::Complete], but behaves like `lsp_insert_mode` is set to `insert`.
529    CompleteWithInsert,
530    /// Similar to [Self::Complete], but behaves like `lsp_insert_mode` is set to `replace`.
531    CompleteWithReplace,
532    /// The user intends to continue 'composing' this completion.
533    /// Completion confirmations should not run side effects and
534    /// let the user continue composing their action.
535    Compose,
536}
537
538impl CompletionIntent {
539    pub fn is_complete(&self) -> bool {
540        self == &Self::Complete
541    }
542
543    pub fn is_compose(&self) -> bool {
544        self == &Self::Compose
545    }
546}
547
548/// Describes a visual group for a completion item in the menu.
549/// When the group changes between consecutive completions, the menu inserts a divider.
550/// If a label is provided, a non-selectable header row is also rendered.
551#[derive(Clone, Debug, PartialEq, Eq)]
552pub struct CompletionGroup {
553    /// Identity of this group, used to detect transitions between consecutive items.
554    pub key: SharedString,
555    /// When set, a non-selectable header with this text is rendered below the divider.
556    pub label: Option<SharedString>,
557}
558
559/// Similar to `CoreCompletion`, but with extra metadata attached.
560#[derive(Clone)]
561pub struct Completion {
562    /// The range of text that will be replaced by this completion.
563    pub replace_range: Range<Anchor>,
564    /// The new text that will be inserted.
565    pub new_text: String,
566    /// A label for this completion that is shown in the menu.
567    pub label: CodeLabel,
568    /// The documentation for this completion.
569    pub documentation: Option<CompletionDocumentation>,
570    /// Completion data source which it was constructed from.
571    pub source: CompletionSource,
572    /// A path to an icon for this completion that is shown in the menu.
573    pub icon_path: Option<SharedString>,
574    /// An optional color to tint this completion's icon with in the menu.
575    /// When `None`, the menu's default muted color is used.
576    pub icon_color: Option<Hsla>,
577    /// Text starting here and ending at the cursor will be used as the query for filtering this completion.
578    ///
579    /// If None, the start of the surrounding word is used.
580    pub match_start: Option<text::Anchor>,
581    /// Key used for de-duplicating snippets. If None, always considered unique.
582    pub snippet_deduplication_key: Option<(usize, usize)>,
583    /// Whether to adjust indentation (the default) or not.
584    pub insert_text_mode: Option<InsertTextMode>,
585    /// An optional callback to invoke when this completion is confirmed.
586    /// Returns whether new completions should be retriggered after the current one.
587    /// If `true` is returned, the editor will show a new completion menu after this completion is confirmed.
588    /// if no confirmation is provided or `false` is returned, the completion will be committed.
589    pub confirm: Option<Arc<dyn Send + Sync + Fn(CompletionIntent, &mut Window, &mut App) -> bool>>,
590    /// An optional group for this completion. When the group changes between consecutive
591    /// items, the completion menu inserts a divider. If the group also carries a label,
592    /// a non-selectable header row is rendered below the divider.
593    pub group: Option<CompletionGroup>,
594}
595
596#[derive(Debug, Clone)]
597pub enum CompletionSource {
598    Lsp {
599        /// The alternate `insert` range, if provided by the LSP server.
600        insert_range: Option<Range<Anchor>>,
601        /// The id of the language server that produced this completion.
602        server_id: LanguageServerId,
603        /// The raw completion provided by the language server.
604        lsp_completion: Box<lsp::CompletionItem>,
605        /// A set of defaults for this completion item.
606        lsp_defaults: Option<Arc<lsp::CompletionListItemDefaults>>,
607        /// Whether this completion has been resolved, to ensure it happens once per completion.
608        resolved: bool,
609    },
610    Dap {
611        /// The sort text for this completion.
612        sort_text: String,
613    },
614    Custom,
615    BufferWord {
616        word_range: Range<Anchor>,
617        resolved: bool,
618    },
619}
620
621impl CompletionSource {
622    pub fn server_id(&self) -> Option<LanguageServerId> {
623        if let CompletionSource::Lsp { server_id, .. } = self {
624            Some(*server_id)
625        } else {
626            None
627        }
628    }
629
630    pub fn lsp_completion(&self, apply_defaults: bool) -> Option<Cow<'_, lsp::CompletionItem>> {
631        if let Self::Lsp {
632            lsp_completion,
633            lsp_defaults,
634            ..
635        } = self
636        {
637            if apply_defaults && let Some(lsp_defaults) = lsp_defaults {
638                let mut completion_with_defaults = *lsp_completion.clone();
639                let default_commit_characters = lsp_defaults.commit_characters.as_ref();
640                let default_edit_range = lsp_defaults.edit_range.as_ref();
641                let default_insert_text_format = lsp_defaults.insert_text_format.as_ref();
642                let default_insert_text_mode = lsp_defaults.insert_text_mode.as_ref();
643
644                if default_commit_characters.is_some()
645                    || default_edit_range.is_some()
646                    || default_insert_text_format.is_some()
647                    || default_insert_text_mode.is_some()
648                {
649                    if completion_with_defaults.commit_characters.is_none()
650                        && default_commit_characters.is_some()
651                    {
652                        completion_with_defaults.commit_characters =
653                            default_commit_characters.cloned()
654                    }
655                    if completion_with_defaults.text_edit.is_none() {
656                        match default_edit_range {
657                            Some(lsp::CompletionListItemDefaultsEditRange::Range(range)) => {
658                                completion_with_defaults.text_edit =
659                                    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
660                                        range: *range,
661                                        new_text: completion_with_defaults.label.clone(),
662                                    }))
663                            }
664                            Some(lsp::CompletionListItemDefaultsEditRange::InsertAndReplace {
665                                insert,
666                                replace,
667                            }) => {
668                                completion_with_defaults.text_edit =
669                                    Some(lsp::CompletionTextEdit::InsertAndReplace(
670                                        lsp::InsertReplaceEdit {
671                                            new_text: completion_with_defaults.label.clone(),
672                                            insert: *insert,
673                                            replace: *replace,
674                                        },
675                                    ))
676                            }
677                            None => {}
678                        }
679                    }
680                    if completion_with_defaults.insert_text_format.is_none()
681                        && default_insert_text_format.is_some()
682                    {
683                        completion_with_defaults.insert_text_format =
684                            default_insert_text_format.cloned()
685                    }
686                    if completion_with_defaults.insert_text_mode.is_none()
687                        && default_insert_text_mode.is_some()
688                    {
689                        completion_with_defaults.insert_text_mode =
690                            default_insert_text_mode.cloned()
691                    }
692                }
693                return Some(Cow::Owned(completion_with_defaults));
694            }
695            Some(Cow::Borrowed(lsp_completion))
696        } else {
697            None
698        }
699    }
700}
701
702impl std::fmt::Debug for Completion {
703    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
704        f.debug_struct("Completion")
705            .field("replace_range", &self.replace_range)
706            .field("new_text", &self.new_text)
707            .field("label", &self.label)
708            .field("documentation", &self.documentation)
709            .field("source", &self.source)
710            .finish()
711    }
712}
713
714/// Response from a source of completions.
715pub struct CompletionResponse {
716    pub completions: Vec<Completion>,
717    pub display_options: CompletionDisplayOptions,
718    /// When false, indicates that the list is complete and does not need to be re-queried if it
719    /// can be filtered instead.
720    pub is_incomplete: bool,
721}
722
723#[derive(Default)]
724pub struct CompletionDisplayOptions {
725    pub dynamic_width: bool,
726}
727
728impl CompletionDisplayOptions {
729    pub fn merge(&mut self, other: &CompletionDisplayOptions) {
730        self.dynamic_width = self.dynamic_width && other.dynamic_width;
731    }
732}
733
734/// Response from language server completion request.
735#[derive(Clone, Debug, Default)]
736pub(crate) struct CoreCompletionResponse {
737    pub completions: Vec<CoreCompletion>,
738    /// When false, indicates that the list is complete and does not need to be re-queried if it
739    /// can be filtered instead.
740    pub is_incomplete: bool,
741}
742
743/// A generic completion that can come from different sources.
744#[derive(Clone, Debug)]
745pub(crate) struct CoreCompletion {
746    replace_range: Range<Anchor>,
747    new_text: String,
748    source: CompletionSource,
749}
750
751/// A code action provided by a language server.
752#[derive(Clone, Debug, PartialEq)]
753pub struct CodeAction {
754    /// The id of the language server that produced this code action.
755    pub server_id: LanguageServerId,
756    /// The range of the buffer where this code action is applicable.
757    pub range: Range<Anchor>,
758    /// The raw code action provided by the language server.
759    /// Can be either an action or a command.
760    pub lsp_action: LspAction,
761    /// Whether the action needs to be resolved using the language server.
762    pub resolved: bool,
763}
764
765/// An action sent back by a language server.
766#[derive(Clone, Debug, PartialEq)]
767pub enum LspAction {
768    /// An action with the full data, may have a command or may not.
769    /// May require resolving.
770    Action(Box<lsp::CodeAction>),
771    /// A command data to run as an action.
772    Command(lsp::Command),
773    /// A code lens data to run as an action.
774    CodeLens(lsp::CodeLens),
775}
776
777impl LspAction {
778    pub fn title(&self) -> &str {
779        match self {
780            Self::Action(action) => &action.title,
781            Self::Command(command) => &command.title,
782            Self::CodeLens(lens) => lens
783                .command
784                .as_ref()
785                .map(|command| command.title.as_str())
786                .unwrap_or("Unknown command"),
787        }
788    }
789
790    pub fn action_kind(&self) -> Option<lsp::CodeActionKind> {
791        match self {
792            Self::Action(action) => action.kind.clone(),
793            Self::Command(_) => Some(lsp::CodeActionKind::new("command")),
794            Self::CodeLens(_) => Some(lsp::CodeActionKind::new("code lens")),
795        }
796    }
797
798    pub fn edit(&self) -> Option<&lsp::WorkspaceEdit> {
799        match self {
800            Self::Action(action) => action.edit.as_ref(),
801            Self::Command(_) => None,
802            Self::CodeLens(_) => None,
803        }
804    }
805
806    pub fn command(&self) -> Option<&lsp::Command> {
807        match self {
808            Self::Action(action) => action.command.as_ref(),
809            Self::Command(command) => Some(command),
810            Self::CodeLens(lens) => lens.command.as_ref(),
811        }
812    }
813}
814
815#[derive(Debug, Clone, PartialEq, Eq)]
816pub enum ResolveState {
817    Resolved,
818    CanResolve(LanguageServerId, Option<lsp::LSPAny>),
819    Resolving,
820}
821impl InlayHint {
822    pub fn text(&self) -> Rope {
823        match &self.label {
824            InlayHintLabel::String(s) => Rope::from(s),
825            InlayHintLabel::LabelParts(parts) => parts.iter().map(|part| &*part.value).collect(),
826        }
827    }
828}
829
830#[derive(Debug, Clone, PartialEq, Eq)]
831pub enum InlayHintLabel {
832    String(String),
833    LabelParts(Vec<InlayHintLabelPart>),
834}
835
836#[derive(Debug, Clone, PartialEq, Eq)]
837pub struct InlayHintLabelPart {
838    pub value: String,
839    pub tooltip: Option<InlayHintLabelPartTooltip>,
840    pub location: Option<(LanguageServerId, lsp::Location)>,
841}
842
843#[derive(Debug, Clone, PartialEq, Eq)]
844pub enum InlayHintTooltip {
845    String(String),
846    MarkupContent(MarkupContent),
847}
848
849#[derive(Debug, Clone, PartialEq, Eq)]
850pub enum InlayHintLabelPartTooltip {
851    String(String),
852    MarkupContent(MarkupContent),
853}
854
855#[derive(Debug, Clone, PartialEq, Eq)]
856pub struct MarkupContent {
857    pub kind: HoverBlockKind,
858    pub value: String,
859}
860
861#[derive(Debug, Clone, PartialEq)]
862pub struct LocationLink {
863    pub origin: Option<Location>,
864    pub target: Location,
865}
866
867#[derive(Debug)]
868pub struct DocumentHighlight {
869    pub range: Range<language::Anchor>,
870    pub kind: DocumentHighlightKind,
871}
872
873#[derive(Clone, Debug)]
874pub struct Symbol {
875    pub language_server_name: LanguageServerName,
876    pub source_worktree_id: WorktreeId,
877    pub source_language_server_id: LanguageServerId,
878    pub path: SymbolLocation,
879    pub label: CodeLabel,
880    pub name: String,
881    pub kind: language::SymbolKind,
882    pub range: Range<Unclipped<PointUtf16>>,
883    pub container_name: Option<String>,
884}
885
886#[derive(Clone, Debug)]
887pub struct DocumentSymbol {
888    pub name: String,
889    pub kind: language::SymbolKind,
890    pub range: Range<Unclipped<PointUtf16>>,
891    pub selection_range: Range<Unclipped<PointUtf16>>,
892    pub children: Vec<DocumentSymbol>,
893}
894
895#[derive(Clone, Debug, PartialEq)]
896pub struct HoverBlock {
897    pub text: String,
898    pub kind: HoverBlockKind,
899}
900
901#[derive(Clone, Debug, PartialEq, Eq)]
902pub enum HoverBlockKind {
903    PlainText,
904    Markdown,
905    Code { language: String },
906}
907
908#[derive(Debug, Clone)]
909pub struct Hover {
910    pub contents: Vec<HoverBlock>,
911    pub range: Option<Range<language::Anchor>>,
912    pub language: Option<Arc<Language>>,
913}
914
915impl Hover {
916    pub fn is_empty(&self) -> bool {
917        self.contents.iter().all(|block| block.text.is_empty())
918    }
919}
920
921enum EntitySubscription {
922    Project(PendingEntitySubscription<Project>),
923    BufferStore(PendingEntitySubscription<BufferStore>),
924    GitStore(PendingEntitySubscription<GitStore>),
925    WorktreeStore(PendingEntitySubscription<WorktreeStore>),
926    LspStore(PendingEntitySubscription<LspStore>),
927    SettingsObserver(PendingEntitySubscription<SettingsObserver>),
928    DapStore(PendingEntitySubscription<DapStore>),
929    BreakpointStore(PendingEntitySubscription<BreakpointStore>),
930}
931
932#[derive(Debug, Clone)]
933pub struct DirectoryItem {
934    pub path: PathBuf,
935    pub is_dir: bool,
936}
937
938#[derive(Clone, Debug, PartialEq)]
939pub struct DocumentColor {
940    pub lsp_range: lsp::Range,
941    pub color: lsp::Color,
942    pub resolved: bool,
943    pub color_presentations: Vec<ColorPresentation>,
944}
945
946impl Eq for DocumentColor {}
947
948impl std::hash::Hash for DocumentColor {
949    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
950        self.lsp_range.hash(state);
951        self.color.red.to_bits().hash(state);
952        self.color.green.to_bits().hash(state);
953        self.color.blue.to_bits().hash(state);
954        self.color.alpha.to_bits().hash(state);
955        self.resolved.hash(state);
956        self.color_presentations.hash(state);
957    }
958}
959
960#[derive(Clone, Debug, PartialEq, Eq)]
961pub struct ColorPresentation {
962    pub label: SharedString,
963    pub text_edit: Option<lsp::TextEdit>,
964    pub additional_text_edits: Vec<lsp::TextEdit>,
965}
966
967impl std::hash::Hash for ColorPresentation {
968    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
969        self.label.hash(state);
970        if let Some(ref edit) = self.text_edit {
971            edit.range.hash(state);
972            edit.new_text.hash(state);
973        }
974        self.additional_text_edits.len().hash(state);
975        for edit in &self.additional_text_edits {
976            edit.range.hash(state);
977            edit.new_text.hash(state);
978        }
979    }
980}
981
982#[derive(Clone)]
983pub enum DirectoryLister {
984    Project(Entity<Project>),
985    Local(Entity<Project>, Arc<dyn Fs>),
986}
987
988impl std::fmt::Debug for DirectoryLister {
989    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
990        match self {
991            DirectoryLister::Project(project) => {
992                write!(f, "DirectoryLister::Project({project:?})")
993            }
994            DirectoryLister::Local(project, _) => {
995                write!(f, "DirectoryLister::Local({project:?})")
996            }
997        }
998    }
999}
1000
1001impl DirectoryLister {
1002    pub fn is_local(&self, cx: &App) -> bool {
1003        match self {
1004            DirectoryLister::Local(..) => true,
1005            DirectoryLister::Project(project) => project.read(cx).is_local(),
1006        }
1007    }
1008
1009    pub fn resolve_tilde<'a>(&self, path: &'a String, cx: &App) -> Cow<'a, str> {
1010        if self.is_local(cx) {
1011            shellexpand::tilde(path)
1012        } else {
1013            Cow::from(path)
1014        }
1015    }
1016
1017    pub fn default_query(&self, cx: &mut App) -> String {
1018        let project = match self {
1019            DirectoryLister::Project(project) => project,
1020            DirectoryLister::Local(project, _) => project,
1021        }
1022        .read(cx);
1023        let path_style = project.path_style(cx);
1024        project
1025            .visible_worktrees(cx)
1026            .next()
1027            .map(|worktree| worktree.read(cx).abs_path().to_string_lossy().into_owned())
1028            .or_else(|| std::env::home_dir().map(|dir| dir.to_string_lossy().into_owned()))
1029            .map(|mut s| {
1030                s.push_str(path_style.primary_separator());
1031                s
1032            })
1033            .unwrap_or_else(|| {
1034                if path_style.is_windows() {
1035                    "C:\\"
1036                } else {
1037                    "~/"
1038                }
1039                .to_string()
1040            })
1041    }
1042
1043    pub fn list_directory(&self, path: String, cx: &mut App) -> Task<Result<Vec<DirectoryItem>>> {
1044        match self {
1045            DirectoryLister::Project(project) => {
1046                project.update(cx, |project, cx| project.list_directory(path, cx))
1047            }
1048            DirectoryLister::Local(_, fs) => {
1049                let fs = fs.clone();
1050                cx.background_spawn(async move {
1051                    let mut results = vec![];
1052                    let expanded = shellexpand::tilde(&path);
1053                    let query = Path::new(expanded.as_ref());
1054                    let mut response = fs.read_dir(query).await?;
1055                    while let Some(path) = response.next().await {
1056                        let path = path?;
1057                        if let Some(file_name) = path.file_name() {
1058                            results.push(DirectoryItem {
1059                                path: PathBuf::from(file_name.to_os_string()),
1060                                is_dir: fs.is_dir(&path).await,
1061                            });
1062                        }
1063                    }
1064                    Ok(results)
1065                })
1066            }
1067        }
1068    }
1069
1070    pub fn path_style(&self, cx: &App) -> PathStyle {
1071        match self {
1072            Self::Local(project, ..) | Self::Project(project, ..) => {
1073                project.read(cx).path_style(cx)
1074            }
1075        }
1076    }
1077}
1078
1079pub const CURRENT_PROJECT_FEATURES: &[&str] = &["new-style-anchors"];
1080
1081#[cfg(feature = "test-support")]
1082pub const DEFAULT_COMPLETION_CONTEXT: CompletionContext = CompletionContext {
1083    trigger_kind: lsp::CompletionTriggerKind::INVOKED,
1084    trigger_character: None,
1085};
1086
1087/// An LSP diagnostics associated with a certain language server.
1088#[derive(Clone, Debug, Default)]
1089pub enum LspPullDiagnostics {
1090    #[default]
1091    Default,
1092    Response {
1093        /// The id of the language server that produced diagnostics.
1094        server_id: LanguageServerId,
1095        /// URI of the resource,
1096        uri: lsp::Uri,
1097        /// The ID provided by the dynamic registration that produced diagnostics.
1098        registration_id: Option<SharedString>,
1099        /// The diagnostics produced by this language server.
1100        diagnostics: PulledDiagnostics,
1101    },
1102}
1103
1104#[derive(Clone, Debug)]
1105pub enum PulledDiagnostics {
1106    Unchanged {
1107        /// An ID the current pulled batch for this file.
1108        /// If given, can be used to query workspace diagnostics partially.
1109        result_id: SharedString,
1110    },
1111    Changed {
1112        result_id: Option<SharedString>,
1113        diagnostics: Vec<lsp::Diagnostic>,
1114    },
1115}
1116
1117/// Whether to disable all AI features in Zed.
1118///
1119/// Default: false
1120#[derive(Copy, Clone, Debug, RegisterSetting)]
1121pub struct DisableAiSettings {
1122    pub disable_ai: bool,
1123}
1124
1125impl settings::Settings for DisableAiSettings {
1126    fn from_settings(content: &settings::SettingsContent) -> Self {
1127        Self {
1128            disable_ai: content.project.disable_ai.unwrap().0,
1129        }
1130    }
1131}
1132
1133impl DisableAiSettings {
1134    /// Returns whether AI is disabled for the given file.
1135    ///
1136    /// This checks the project-level settings for the file's worktree,
1137    /// allowing `disable_ai` to be configured per-project in `.zed/settings.json`.
1138    pub fn is_ai_disabled_for_buffer(buffer: Option<&Entity<Buffer>>, cx: &App) -> bool {
1139        Self::is_ai_disabled_for_file(buffer.and_then(|buffer| buffer.read(cx).file()), cx)
1140    }
1141
1142    pub fn is_ai_disabled_for_file(file: Option<&Arc<dyn language::File>>, cx: &App) -> bool {
1143        let location = file.map(|f| settings::SettingsLocation {
1144            worktree_id: f.worktree_id(cx),
1145            path: f.path().as_ref(),
1146        });
1147        Self::get(location, cx).disable_ai
1148    }
1149}
1150
1151impl Project {
1152    pub fn init(client: &Arc<Client>, cx: &mut App) {
1153        connection_manager::init(client.clone(), cx);
1154
1155        let client: AnyProtoClient = client.clone().into();
1156        client.add_entity_message_handler(Self::handle_add_collaborator);
1157        client.add_entity_message_handler(Self::handle_update_project_collaborator);
1158        client.add_entity_message_handler(Self::handle_remove_collaborator);
1159        client.add_entity_message_handler(Self::handle_update_project);
1160        client.add_entity_message_handler(Self::handle_unshare_project);
1161        client.add_entity_request_handler(Self::handle_update_buffer);
1162        client.add_entity_message_handler(Self::handle_update_worktree);
1163        client.add_entity_request_handler(Self::handle_synchronize_buffers);
1164
1165        client.add_entity_request_handler(Self::handle_search_candidate_buffers);
1166        client.add_entity_request_handler(Self::handle_open_buffer_by_id);
1167        client.add_entity_request_handler(Self::handle_open_buffer_by_path);
1168        client.add_entity_request_handler(Self::handle_open_new_buffer);
1169        client.add_entity_message_handler(Self::handle_create_buffer_for_peer);
1170        client.add_entity_message_handler(Self::handle_toggle_lsp_logs);
1171        client.add_entity_message_handler(Self::handle_create_image_for_peer);
1172        client.add_entity_request_handler(Self::handle_find_search_candidates_chunk);
1173        client.add_entity_message_handler(Self::handle_find_search_candidates_cancel);
1174        client.add_entity_message_handler(Self::handle_create_file_for_peer);
1175
1176        WorktreeStore::init(&client);
1177        BufferStore::init(&client);
1178        LspStore::init(&client);
1179        GitStore::init(&client);
1180        SettingsObserver::init(&client);
1181        TaskStore::init(Some(&client));
1182        ToolchainStore::init(&client);
1183        DapStore::init(&client, cx);
1184        BreakpointStore::init(&client);
1185        context_server_store::init(cx);
1186    }
1187
1188    pub fn local(
1189        client: Arc<Client>,
1190        node: NodeRuntime,
1191        user_store: Entity<UserStore>,
1192        languages: Arc<LanguageRegistry>,
1193        fs: Arc<dyn Fs>,
1194        env: Option<HashMap<String, String>>,
1195        flags: LocalProjectFlags,
1196        cx: &mut App,
1197    ) -> Entity<Self> {
1198        cx.new(|cx: &mut Context<Self>| {
1199            let (tx, rx) = mpsc::unbounded();
1200            cx.spawn(async move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx).await)
1201                .detach();
1202            let snippets = SnippetProvider::new(fs.clone(), BTreeSet::from_iter([]), cx);
1203            let worktree_store =
1204                cx.new(|cx| WorktreeStore::local(false, fs.clone(), WorktreeIdCounter::get(cx)));
1205            if flags.init_worktree_trust {
1206                trusted_worktrees::track_worktree_trust(
1207                    worktree_store.clone(),
1208                    None,
1209                    None,
1210                    None,
1211                    cx,
1212                );
1213            }
1214            cx.subscribe(&worktree_store, Self::on_worktree_store_event)
1215                .detach();
1216
1217            let weak_self = cx.weak_entity();
1218            let context_server_store = cx.new(|cx| {
1219                ContextServerStore::local(
1220                    worktree_store.clone(),
1221                    Some(weak_self.clone()),
1222                    false,
1223                    cx,
1224                )
1225            });
1226
1227            let environment = cx.new(|cx| {
1228                ProjectEnvironment::new(env, worktree_store.downgrade(), None, false, cx)
1229            });
1230            let manifest_tree = ManifestTree::new(worktree_store.clone(), cx);
1231            let toolchain_store = cx.new(|cx| {
1232                ToolchainStore::local(
1233                    languages.clone(),
1234                    worktree_store.clone(),
1235                    environment.clone(),
1236                    manifest_tree.clone(),
1237                    cx,
1238                )
1239            });
1240
1241            let buffer_store = cx.new(|cx| BufferStore::local(worktree_store.clone(), cx));
1242            cx.subscribe(&buffer_store, Self::on_buffer_store_event)
1243                .detach();
1244
1245            let bookmark_store =
1246                cx.new(|_| BookmarkStore::new(worktree_store.clone(), buffer_store.clone()));
1247
1248            let breakpoint_store =
1249                cx.new(|_| BreakpointStore::local(worktree_store.clone(), buffer_store.clone()));
1250
1251            let dap_store = cx.new(|cx| {
1252                DapStore::new_local(
1253                    client.http_client(),
1254                    node.clone(),
1255                    fs.clone(),
1256                    environment.clone(),
1257                    toolchain_store.read(cx).as_language_toolchain_store(),
1258                    worktree_store.clone(),
1259                    breakpoint_store.clone(),
1260                    false,
1261                    cx,
1262                )
1263            });
1264            cx.subscribe(&dap_store, Self::on_dap_store_event).detach();
1265
1266            let image_store = cx.new(|cx| ImageStore::local(worktree_store.clone(), cx));
1267            cx.subscribe(&image_store, Self::on_image_store_event)
1268                .detach();
1269
1270            let prettier_store = cx.new(|cx| {
1271                PrettierStore::new(
1272                    node.clone(),
1273                    fs.clone(),
1274                    languages.clone(),
1275                    worktree_store.clone(),
1276                    cx,
1277                )
1278            });
1279
1280            let git_store = cx.new(|cx| {
1281                GitStore::local(
1282                    &worktree_store,
1283                    buffer_store.clone(),
1284                    environment.clone(),
1285                    fs.clone(),
1286                    cx,
1287                )
1288            });
1289
1290            let task_store = cx.new(|cx| {
1291                TaskStore::local(
1292                    buffer_store.downgrade(),
1293                    worktree_store.clone(),
1294                    toolchain_store.read(cx).as_language_toolchain_store(),
1295                    environment.clone(),
1296                    git_store.clone(),
1297                    cx,
1298                )
1299            });
1300
1301            let settings_observer = cx.new(|cx| {
1302                SettingsObserver::new_local(
1303                    fs.clone(),
1304                    worktree_store.clone(),
1305                    task_store.clone(),
1306                    flags.watch_global_configs,
1307                    cx,
1308                )
1309            });
1310            cx.subscribe(&settings_observer, Self::on_settings_observer_event)
1311                .detach();
1312
1313            let lsp_store = cx.new(|cx| {
1314                LspStore::new_local(
1315                    buffer_store.clone(),
1316                    worktree_store.clone(),
1317                    prettier_store.clone(),
1318                    toolchain_store
1319                        .read(cx)
1320                        .as_local_store()
1321                        .expect("Toolchain store to be local")
1322                        .clone(),
1323                    environment.clone(),
1324                    manifest_tree,
1325                    languages.clone(),
1326                    client.http_client(),
1327                    fs.clone(),
1328                    cx,
1329                )
1330            });
1331
1332            let agent_server_store = cx.new(|cx| {
1333                AgentServerStore::local(
1334                    node.clone(),
1335                    fs.clone(),
1336                    environment.clone(),
1337                    client.http_client(),
1338                    cx,
1339                )
1340            });
1341
1342            cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
1343
1344            Self {
1345                buffer_ordered_messages_tx: tx,
1346                collaborators: Default::default(),
1347                worktree_store,
1348                buffer_store,
1349                image_store,
1350                lsp_store,
1351                context_server_store,
1352                join_project_response_message_id: 0,
1353                client_state: ProjectClientState::Local,
1354                git_store,
1355                client_subscriptions: Vec::new(),
1356                _subscriptions: vec![cx.on_release(Self::release)],
1357                active_entry: None,
1358                snippets,
1359                languages,
1360                collab_client: client,
1361                task_store,
1362                user_store,
1363                settings_observer,
1364                fs,
1365                remote_client: None,
1366                bookmark_store,
1367                breakpoint_store,
1368                dap_store,
1369                agent_server_store,
1370
1371                buffers_needing_diff: Default::default(),
1372                git_diff_debouncer: DebouncedDelay::new(),
1373                terminals: Terminals {
1374                    local_handles: Vec::new(),
1375                },
1376                node: Some(node),
1377                search_history: Self::new_search_history(),
1378                environment,
1379                remotely_created_models: Default::default(),
1380
1381                search_included_history: Self::new_search_history(),
1382                search_excluded_history: Self::new_search_history(),
1383
1384                toolchain_store: Some(toolchain_store),
1385
1386                agent_location: None,
1387                downloading_files: Default::default(),
1388                last_worktree_paths: WorktreePaths::default(),
1389            }
1390        })
1391    }
1392
1393    pub fn remote(
1394        remote: Entity<RemoteClient>,
1395        client: Arc<Client>,
1396        node: NodeRuntime,
1397        user_store: Entity<UserStore>,
1398        languages: Arc<LanguageRegistry>,
1399        fs: Arc<dyn Fs>,
1400        init_worktree_trust: bool,
1401        cx: &mut App,
1402    ) -> Entity<Self> {
1403        cx.new(|cx: &mut Context<Self>| {
1404            let (tx, rx) = mpsc::unbounded();
1405            cx.spawn(async move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx).await)
1406                .detach();
1407            let snippets = SnippetProvider::new(fs.clone(), BTreeSet::from_iter([]), cx);
1408
1409            let (remote_proto, path_style, connection_options) =
1410                remote.read_with(cx, |remote, _| {
1411                    (
1412                        remote.proto_client(),
1413                        remote.path_style(),
1414                        remote.connection_options(),
1415                    )
1416                });
1417            let worktree_store = cx.new(|cx| {
1418                WorktreeStore::remote(
1419                    false,
1420                    remote_proto.clone(),
1421                    REMOTE_SERVER_PROJECT_ID,
1422                    path_style,
1423                    WorktreeIdCounter::get(cx),
1424                )
1425            });
1426
1427            cx.subscribe(&worktree_store, Self::on_worktree_store_event)
1428                .detach();
1429            if init_worktree_trust {
1430                trusted_worktrees::track_worktree_trust(
1431                    worktree_store.clone(),
1432                    Some(RemoteHostLocation::from(connection_options)),
1433                    None,
1434                    Some((remote_proto.clone(), ProjectId(REMOTE_SERVER_PROJECT_ID))),
1435                    cx,
1436                );
1437            }
1438
1439            let weak_self = cx.weak_entity();
1440
1441            let buffer_store = cx.new(|cx| {
1442                BufferStore::remote(
1443                    worktree_store.clone(),
1444                    remote.read(cx).proto_client(),
1445                    REMOTE_SERVER_PROJECT_ID,
1446                    cx,
1447                )
1448            });
1449            let image_store = cx.new(|cx| {
1450                ImageStore::remote(
1451                    worktree_store.clone(),
1452                    remote.read(cx).proto_client(),
1453                    REMOTE_SERVER_PROJECT_ID,
1454                    cx,
1455                )
1456            });
1457            cx.subscribe(&buffer_store, Self::on_buffer_store_event)
1458                .detach();
1459            let toolchain_store = cx.new(|cx| {
1460                ToolchainStore::remote(
1461                    REMOTE_SERVER_PROJECT_ID,
1462                    worktree_store.clone(),
1463                    remote.read(cx).proto_client(),
1464                    cx,
1465                )
1466            });
1467
1468            let context_server_store = cx.new(|cx| {
1469                ContextServerStore::remote(
1470                    rpc::proto::REMOTE_SERVER_PROJECT_ID,
1471                    remote.clone(),
1472                    worktree_store.clone(),
1473                    Some(weak_self.clone()),
1474                    cx,
1475                )
1476            });
1477
1478            let environment = cx.new(|cx| {
1479                ProjectEnvironment::new(
1480                    None,
1481                    worktree_store.downgrade(),
1482                    Some(remote.downgrade()),
1483                    false,
1484                    cx,
1485                )
1486            });
1487
1488            let lsp_store = cx.new(|cx| {
1489                LspStore::new_remote(
1490                    buffer_store.clone(),
1491                    worktree_store.clone(),
1492                    languages.clone(),
1493                    remote_proto.clone(),
1494                    REMOTE_SERVER_PROJECT_ID,
1495                    cx,
1496                )
1497            });
1498            cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
1499
1500            let bookmark_store =
1501                cx.new(|_| BookmarkStore::new(worktree_store.clone(), buffer_store.clone()));
1502
1503            let breakpoint_store = cx.new(|_| {
1504                BreakpointStore::remote(
1505                    REMOTE_SERVER_PROJECT_ID,
1506                    remote_proto.clone(),
1507                    buffer_store.clone(),
1508                    worktree_store.clone(),
1509                )
1510            });
1511
1512            let dap_store = cx.new(|cx| {
1513                DapStore::new_remote(
1514                    REMOTE_SERVER_PROJECT_ID,
1515                    remote.clone(),
1516                    breakpoint_store.clone(),
1517                    worktree_store.clone(),
1518                    node.clone(),
1519                    client.http_client(),
1520                    fs.clone(),
1521                    cx,
1522                )
1523            });
1524
1525            let git_store = cx.new(|cx| {
1526                GitStore::remote(
1527                    &worktree_store,
1528                    buffer_store.clone(),
1529                    remote_proto.clone(),
1530                    REMOTE_SERVER_PROJECT_ID,
1531                    cx,
1532                )
1533            });
1534
1535            let task_store = cx.new(|cx| {
1536                TaskStore::remote(
1537                    buffer_store.downgrade(),
1538                    worktree_store.clone(),
1539                    toolchain_store.read(cx).as_language_toolchain_store(),
1540                    remote.read(cx).proto_client(),
1541                    REMOTE_SERVER_PROJECT_ID,
1542                    git_store.clone(),
1543                    cx,
1544                )
1545            });
1546
1547            let settings_observer = cx.new(|cx| {
1548                SettingsObserver::new_remote(
1549                    fs.clone(),
1550                    worktree_store.clone(),
1551                    task_store.clone(),
1552                    Some(remote_proto.clone()),
1553                    false,
1554                    cx,
1555                )
1556            });
1557            cx.subscribe(&settings_observer, Self::on_settings_observer_event)
1558                .detach();
1559
1560            let agent_server_store = cx.new(|_| {
1561                AgentServerStore::remote(
1562                    REMOTE_SERVER_PROJECT_ID,
1563                    remote.clone(),
1564                    worktree_store.clone(),
1565                )
1566            });
1567
1568            cx.subscribe(&remote, Self::on_remote_client_event).detach();
1569
1570            let this = Self {
1571                buffer_ordered_messages_tx: tx,
1572                collaborators: Default::default(),
1573                worktree_store,
1574                buffer_store,
1575                image_store,
1576                lsp_store,
1577                context_server_store,
1578                bookmark_store,
1579                breakpoint_store,
1580                dap_store,
1581                join_project_response_message_id: 0,
1582                client_state: ProjectClientState::Local,
1583                git_store,
1584                agent_server_store,
1585                client_subscriptions: Vec::new(),
1586                _subscriptions: vec![
1587                    cx.on_release(Self::release),
1588                    cx.on_app_quit(|this, cx| {
1589                        let shutdown = this.remote_client.take().and_then(|client| {
1590                            client.update(cx, |client, cx| {
1591                                client.shutdown_processes(
1592                                    Some(proto::ShutdownRemoteServer {}),
1593                                    cx.background_executor().clone(),
1594                                )
1595                            })
1596                        });
1597
1598                        cx.background_executor().spawn(async move {
1599                            if let Some(shutdown) = shutdown {
1600                                shutdown.await;
1601                            }
1602                        })
1603                    }),
1604                ],
1605                active_entry: None,
1606                snippets,
1607                languages,
1608                collab_client: client,
1609                task_store,
1610                user_store,
1611                settings_observer,
1612                fs,
1613                remote_client: Some(remote.clone()),
1614                buffers_needing_diff: Default::default(),
1615                git_diff_debouncer: DebouncedDelay::new(),
1616                terminals: Terminals {
1617                    local_handles: Vec::new(),
1618                },
1619                node: Some(node),
1620                search_history: Self::new_search_history(),
1621                environment,
1622                remotely_created_models: Default::default(),
1623
1624                search_included_history: Self::new_search_history(),
1625                search_excluded_history: Self::new_search_history(),
1626
1627                toolchain_store: Some(toolchain_store),
1628                agent_location: None,
1629                downloading_files: Default::default(),
1630                last_worktree_paths: WorktreePaths::default(),
1631            };
1632
1633            // remote server -> local machine handlers
1634            remote_proto.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &cx.entity());
1635            remote_proto.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &this.buffer_store);
1636            remote_proto.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &this.worktree_store);
1637            remote_proto.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &this.lsp_store);
1638            remote_proto.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &this.dap_store);
1639            remote_proto.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &this.breakpoint_store);
1640            remote_proto.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &this.settings_observer);
1641            remote_proto.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &this.git_store);
1642            remote_proto.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &this.agent_server_store);
1643
1644            remote_proto.add_entity_message_handler(Self::handle_create_buffer_for_peer);
1645            remote_proto.add_entity_message_handler(Self::handle_create_image_for_peer);
1646            remote_proto.add_entity_message_handler(Self::handle_create_file_for_peer);
1647            remote_proto.add_entity_message_handler(Self::handle_update_worktree);
1648            remote_proto.add_entity_message_handler(Self::handle_update_project);
1649            remote_proto.add_entity_message_handler(Self::handle_toast);
1650            remote_proto.add_entity_message_handler(Self::handle_telemetry_event);
1651            remote_proto.add_entity_request_handler(Self::handle_language_server_prompt_request);
1652            remote_proto.add_entity_message_handler(Self::handle_hide_toast);
1653            remote_proto.add_entity_request_handler(Self::handle_update_buffer_from_remote_server);
1654            remote_proto.add_entity_request_handler(Self::handle_trust_worktrees);
1655            remote_proto.add_entity_request_handler(Self::handle_restrict_worktrees);
1656            remote_proto.add_entity_request_handler(Self::handle_find_search_candidates_chunk);
1657
1658            remote_proto.add_entity_message_handler(Self::handle_find_search_candidates_cancel);
1659            BufferStore::init(&remote_proto);
1660            WorktreeStore::init_remote(&remote_proto);
1661            LspStore::init(&remote_proto);
1662            SettingsObserver::init(&remote_proto);
1663            TaskStore::init(Some(&remote_proto));
1664            ToolchainStore::init(&remote_proto);
1665            DapStore::init(&remote_proto, cx);
1666            BreakpointStore::init(&remote_proto);
1667            GitStore::init(&remote_proto);
1668            AgentServerStore::init_remote(&remote_proto);
1669
1670            this
1671        })
1672    }
1673
1674    pub async fn in_room(
1675        remote_id: u64,
1676        client: Arc<Client>,
1677        user_store: Entity<UserStore>,
1678        languages: Arc<LanguageRegistry>,
1679        fs: Arc<dyn Fs>,
1680        cx: AsyncApp,
1681    ) -> Result<Entity<Self>> {
1682        client.connect(true, &cx).await.into_response()?;
1683
1684        let subscriptions = [
1685            EntitySubscription::Project(client.subscribe_to_entity::<Self>(remote_id)?),
1686            EntitySubscription::BufferStore(client.subscribe_to_entity::<BufferStore>(remote_id)?),
1687            EntitySubscription::GitStore(client.subscribe_to_entity::<GitStore>(remote_id)?),
1688            EntitySubscription::WorktreeStore(
1689                client.subscribe_to_entity::<WorktreeStore>(remote_id)?,
1690            ),
1691            EntitySubscription::LspStore(client.subscribe_to_entity::<LspStore>(remote_id)?),
1692            EntitySubscription::SettingsObserver(
1693                client.subscribe_to_entity::<SettingsObserver>(remote_id)?,
1694            ),
1695            EntitySubscription::DapStore(client.subscribe_to_entity::<DapStore>(remote_id)?),
1696            EntitySubscription::BreakpointStore(
1697                client.subscribe_to_entity::<BreakpointStore>(remote_id)?,
1698            ),
1699        ];
1700        let committer = get_git_committer(&cx).await;
1701        let response = client
1702            .request_envelope(proto::JoinProject {
1703                project_id: remote_id,
1704                committer_email: committer.email,
1705                committer_name: committer.name,
1706                features: CURRENT_PROJECT_FEATURES
1707                    .iter()
1708                    .map(|s| s.to_string())
1709                    .collect(),
1710            })
1711            .await?;
1712        Self::from_join_project_response(
1713            response,
1714            subscriptions,
1715            client,
1716            false,
1717            user_store,
1718            languages,
1719            fs,
1720            cx,
1721        )
1722        .await
1723    }
1724
1725    async fn from_join_project_response(
1726        response: TypedEnvelope<proto::JoinProjectResponse>,
1727        subscriptions: [EntitySubscription; 8],
1728        client: Arc<Client>,
1729        run_tasks: bool,
1730        user_store: Entity<UserStore>,
1731        languages: Arc<LanguageRegistry>,
1732        fs: Arc<dyn Fs>,
1733        mut cx: AsyncApp,
1734    ) -> Result<Entity<Self>> {
1735        let remote_id = response.payload.project_id;
1736        let role = response.payload.role();
1737
1738        let path_style = if response.payload.windows_paths {
1739            PathStyle::Windows
1740        } else {
1741            PathStyle::Unix
1742        };
1743
1744        let worktree_store = cx.new(|cx| {
1745            WorktreeStore::remote(
1746                true,
1747                client.clone().into(),
1748                response.payload.project_id,
1749                path_style,
1750                WorktreeIdCounter::get(cx),
1751            )
1752        });
1753        let buffer_store = cx.new(|cx| {
1754            BufferStore::remote(worktree_store.clone(), client.clone().into(), remote_id, cx)
1755        });
1756        let image_store = cx.new(|cx| {
1757            ImageStore::remote(worktree_store.clone(), client.clone().into(), remote_id, cx)
1758        });
1759
1760        let environment =
1761            cx.new(|cx| ProjectEnvironment::new(None, worktree_store.downgrade(), None, true, cx));
1762
1763        let bookmark_store =
1764            cx.new(|_| BookmarkStore::new(worktree_store.clone(), buffer_store.clone()));
1765
1766        let breakpoint_store = cx.new(|_| {
1767            BreakpointStore::remote(
1768                remote_id,
1769                client.clone().into(),
1770                buffer_store.clone(),
1771                worktree_store.clone(),
1772            )
1773        });
1774        let dap_store = cx.new(|cx| {
1775            DapStore::new_collab(
1776                remote_id,
1777                client.clone().into(),
1778                breakpoint_store.clone(),
1779                worktree_store.clone(),
1780                fs.clone(),
1781                cx,
1782            )
1783        });
1784
1785        let lsp_store = cx.new(|cx| {
1786            LspStore::new_remote(
1787                buffer_store.clone(),
1788                worktree_store.clone(),
1789                languages.clone(),
1790                client.clone().into(),
1791                remote_id,
1792                cx,
1793            )
1794        });
1795
1796        let git_store = cx.new(|cx| {
1797            GitStore::remote(
1798                // In this remote case we pass None for the environment
1799                &worktree_store,
1800                buffer_store.clone(),
1801                client.clone().into(),
1802                remote_id,
1803                cx,
1804            )
1805        });
1806
1807        let task_store = cx.new(|cx| {
1808            if run_tasks {
1809                TaskStore::remote(
1810                    buffer_store.downgrade(),
1811                    worktree_store.clone(),
1812                    Arc::new(EmptyToolchainStore),
1813                    client.clone().into(),
1814                    remote_id,
1815                    git_store.clone(),
1816                    cx,
1817                )
1818            } else {
1819                TaskStore::Noop
1820            }
1821        });
1822
1823        let settings_observer = cx.new(|cx| {
1824            SettingsObserver::new_remote(
1825                fs.clone(),
1826                worktree_store.clone(),
1827                task_store.clone(),
1828                None,
1829                true,
1830                cx,
1831            )
1832        });
1833
1834        let agent_server_store = cx.new(|_cx| AgentServerStore::collab());
1835        let replica_id = ReplicaId::new(response.payload.replica_id as u16);
1836
1837        let project = cx.new(|cx| {
1838            let snippets = SnippetProvider::new(fs.clone(), BTreeSet::from_iter([]), cx);
1839
1840            let weak_self = cx.weak_entity();
1841            let context_server_store = cx.new(|cx| {
1842                ContextServerStore::local(worktree_store.clone(), Some(weak_self), false, cx)
1843            });
1844
1845            let mut worktrees = Vec::new();
1846            for worktree in response.payload.worktrees {
1847                let worktree = Worktree::remote(
1848                    remote_id,
1849                    replica_id,
1850                    worktree,
1851                    client.clone().into(),
1852                    path_style,
1853                    cx,
1854                );
1855                worktrees.push(worktree);
1856            }
1857
1858            let (tx, rx) = mpsc::unbounded();
1859            cx.spawn(async move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx).await)
1860                .detach();
1861
1862            cx.subscribe(&worktree_store, Self::on_worktree_store_event)
1863                .detach();
1864
1865            cx.subscribe(&buffer_store, Self::on_buffer_store_event)
1866                .detach();
1867            cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
1868            cx.subscribe(&settings_observer, Self::on_settings_observer_event)
1869                .detach();
1870
1871            cx.subscribe(&dap_store, Self::on_dap_store_event).detach();
1872
1873            let mut project = Self {
1874                buffer_ordered_messages_tx: tx,
1875                buffer_store: buffer_store.clone(),
1876                image_store,
1877                worktree_store: worktree_store.clone(),
1878                lsp_store: lsp_store.clone(),
1879                context_server_store,
1880                active_entry: None,
1881                collaborators: Default::default(),
1882                join_project_response_message_id: response.message_id,
1883                languages,
1884                user_store: user_store.clone(),
1885                task_store,
1886                snippets,
1887                fs,
1888                remote_client: None,
1889                settings_observer: settings_observer.clone(),
1890                client_subscriptions: Default::default(),
1891                _subscriptions: vec![cx.on_release(Self::release)],
1892                collab_client: client.clone(),
1893                client_state: ProjectClientState::Collab {
1894                    sharing_has_stopped: false,
1895                    capability: Capability::ReadWrite,
1896                    remote_id,
1897                    replica_id,
1898                },
1899                bookmark_store: bookmark_store.clone(),
1900                breakpoint_store: breakpoint_store.clone(),
1901                dap_store: dap_store.clone(),
1902                git_store: git_store.clone(),
1903                agent_server_store,
1904                buffers_needing_diff: Default::default(),
1905                git_diff_debouncer: DebouncedDelay::new(),
1906                terminals: Terminals {
1907                    local_handles: Vec::new(),
1908                },
1909                node: None,
1910                search_history: Self::new_search_history(),
1911                search_included_history: Self::new_search_history(),
1912                search_excluded_history: Self::new_search_history(),
1913                environment,
1914                remotely_created_models: Arc::new(Mutex::new(RemotelyCreatedModels::default())),
1915                toolchain_store: None,
1916                agent_location: None,
1917                downloading_files: Default::default(),
1918                last_worktree_paths: WorktreePaths::default(),
1919            };
1920            project.set_role(role, cx);
1921            for worktree in worktrees {
1922                project.add_worktree(&worktree, cx);
1923            }
1924            project
1925        });
1926
1927        let weak_project = project.downgrade();
1928        lsp_store.update(&mut cx, |lsp_store, cx| {
1929            lsp_store.set_language_server_statuses_from_proto(
1930                weak_project,
1931                response.payload.language_servers,
1932                response.payload.language_server_capabilities,
1933                cx,
1934            );
1935        });
1936
1937        let subscriptions = subscriptions
1938            .into_iter()
1939            .map(|s| match s {
1940                EntitySubscription::BufferStore(subscription) => {
1941                    subscription.set_entity(&buffer_store, &cx)
1942                }
1943                EntitySubscription::WorktreeStore(subscription) => {
1944                    subscription.set_entity(&worktree_store, &cx)
1945                }
1946                EntitySubscription::GitStore(subscription) => {
1947                    subscription.set_entity(&git_store, &cx)
1948                }
1949                EntitySubscription::SettingsObserver(subscription) => {
1950                    subscription.set_entity(&settings_observer, &cx)
1951                }
1952                EntitySubscription::Project(subscription) => subscription.set_entity(&project, &cx),
1953                EntitySubscription::LspStore(subscription) => {
1954                    subscription.set_entity(&lsp_store, &cx)
1955                }
1956                EntitySubscription::DapStore(subscription) => {
1957                    subscription.set_entity(&dap_store, &cx)
1958                }
1959                EntitySubscription::BreakpointStore(subscription) => {
1960                    subscription.set_entity(&breakpoint_store, &cx)
1961                }
1962            })
1963            .collect::<Vec<_>>();
1964
1965        let user_ids = response
1966            .payload
1967            .collaborators
1968            .iter()
1969            .map(|peer| peer.user_id)
1970            .collect();
1971        user_store
1972            .update(&mut cx, |user_store, cx| user_store.get_users(user_ids, cx))
1973            .await?;
1974
1975        project.update(&mut cx, |this, cx| {
1976            this.set_collaborators_from_proto(response.payload.collaborators, cx)?;
1977            this.client_subscriptions.extend(subscriptions);
1978            anyhow::Ok(())
1979        })?;
1980
1981        Ok(project)
1982    }
1983
1984    fn new_search_history() -> SearchHistory {
1985        SearchHistory::new(
1986            Some(MAX_PROJECT_SEARCH_HISTORY_SIZE),
1987            search_history::QueryInsertionBehavior::AlwaysInsert,
1988        )
1989    }
1990
1991    fn release(&mut self, cx: &mut App) {
1992        if let Some(client) = self.remote_client.take() {
1993            let shutdown = client.update(cx, |client, cx| {
1994                client.shutdown_processes(
1995                    Some(proto::ShutdownRemoteServer {}),
1996                    cx.background_executor().clone(),
1997                )
1998            });
1999
2000            cx.background_spawn(async move {
2001                if let Some(shutdown) = shutdown {
2002                    shutdown.await;
2003                }
2004            })
2005            .detach()
2006        }
2007
2008        match &self.client_state {
2009            ProjectClientState::Local => {}
2010            ProjectClientState::Shared { .. } => {
2011                let _ = self.unshare_internal(cx);
2012            }
2013            ProjectClientState::Collab { remote_id, .. } => {
2014                let _ = self.collab_client.send(proto::LeaveProject {
2015                    project_id: *remote_id,
2016                });
2017                self.disconnected_from_host_internal(cx);
2018            }
2019        }
2020    }
2021
2022    #[cfg(feature = "test-support")]
2023    pub fn client_subscriptions(&self) -> &Vec<client::Subscription> {
2024        &self.client_subscriptions
2025    }
2026
2027    #[cfg(feature = "test-support")]
2028    pub async fn example(
2029        root_paths: impl IntoIterator<Item = &Path>,
2030        cx: &mut AsyncApp,
2031    ) -> Entity<Project> {
2032        use clock::FakeSystemClock;
2033
2034        let fs = Arc::new(RealFs::new(None, cx.background_executor().clone()));
2035        let languages = LanguageRegistry::test(cx.background_executor().clone());
2036        let clock = Arc::new(FakeSystemClock::new());
2037        let http_client = http_client::FakeHttpClient::with_404_response();
2038        let client = cx.update(|cx| client::Client::new(clock, http_client.clone(), cx));
2039        let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
2040        let project = cx.update(|cx| {
2041            Project::local(
2042                client,
2043                node_runtime::NodeRuntime::unavailable(),
2044                user_store,
2045                Arc::new(languages),
2046                fs,
2047                None,
2048                LocalProjectFlags {
2049                    init_worktree_trust: false,
2050                    ..Default::default()
2051                },
2052                cx,
2053            )
2054        });
2055        for path in root_paths {
2056            let (tree, _): (Entity<Worktree>, _) = project
2057                .update(cx, |project, cx| {
2058                    project.find_or_create_worktree(path, true, cx)
2059                })
2060                .await
2061                .unwrap();
2062            tree.read_with(cx, |tree, _| tree.as_local().unwrap().scan_complete())
2063                .await;
2064        }
2065        project
2066    }
2067
2068    #[cfg(feature = "test-support")]
2069    pub async fn test(
2070        fs: Arc<dyn Fs>,
2071        root_paths: impl IntoIterator<Item = &Path>,
2072        cx: &mut gpui::TestAppContext,
2073    ) -> Entity<Project> {
2074        Self::test_project(fs, root_paths, false, cx).await
2075    }
2076
2077    #[cfg(feature = "test-support")]
2078    pub async fn test_with_worktree_trust(
2079        fs: Arc<dyn Fs>,
2080        root_paths: impl IntoIterator<Item = &Path>,
2081        cx: &mut gpui::TestAppContext,
2082    ) -> Entity<Project> {
2083        Self::test_project(fs, root_paths, true, cx).await
2084    }
2085
2086    #[cfg(feature = "test-support")]
2087    async fn test_project(
2088        fs: Arc<dyn Fs>,
2089        root_paths: impl IntoIterator<Item = &Path>,
2090        init_worktree_trust: bool,
2091        cx: &mut gpui::TestAppContext,
2092    ) -> Entity<Project> {
2093        use clock::FakeSystemClock;
2094
2095        let languages = LanguageRegistry::test(cx.executor());
2096        let clock = Arc::new(FakeSystemClock::new());
2097        let http_client = http_client::FakeHttpClient::with_404_response();
2098        let client = cx.update(|cx| client::Client::new(clock, http_client.clone(), cx));
2099        let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
2100        let project = cx.update(|cx| {
2101            Project::local(
2102                client,
2103                node_runtime::NodeRuntime::unavailable(),
2104                user_store,
2105                Arc::new(languages),
2106                fs,
2107                None,
2108                LocalProjectFlags {
2109                    init_worktree_trust,
2110                    ..Default::default()
2111                },
2112                cx,
2113            )
2114        });
2115        for path in root_paths {
2116            let (tree, _) = project
2117                .update(cx, |project, cx| {
2118                    project.find_or_create_worktree(path, true, cx)
2119                })
2120                .await
2121                .unwrap();
2122
2123            tree.read_with(cx, |tree, _| tree.as_local().unwrap().scan_complete())
2124                .await;
2125        }
2126        project
2127    }
2128
2129    /// Transitions a local test project into the `Collab` client state so that
2130    /// `is_via_collab()` returns `true`. Use only in tests.
2131    #[cfg(any(test, feature = "test-support"))]
2132    pub fn mark_as_collab_for_testing(&mut self) {
2133        self.client_state = ProjectClientState::Collab {
2134            sharing_has_stopped: false,
2135            capability: Capability::ReadWrite,
2136            remote_id: 0,
2137            replica_id: clock::ReplicaId::new(1),
2138        };
2139    }
2140
2141    #[cfg(any(test, feature = "test-support"))]
2142    pub fn add_test_remote_worktree(
2143        &mut self,
2144        abs_path: &str,
2145        cx: &mut Context<Self>,
2146    ) -> Entity<Worktree> {
2147        use rpc::NoopProtoClient;
2148        use util::paths::PathStyle;
2149
2150        let root_name = std::path::Path::new(abs_path)
2151            .file_name()
2152            .map(|n| n.to_string_lossy().to_string())
2153            .unwrap_or_default();
2154
2155        let client = AnyProtoClient::new(NoopProtoClient::new());
2156        let worktree = Worktree::remote(
2157            0,
2158            ReplicaId::new(1),
2159            proto::WorktreeMetadata {
2160                id: 100 + self.visible_worktrees(cx).count() as u64,
2161                root_name,
2162                visible: true,
2163                abs_path: abs_path.to_string(),
2164                root_repo_common_dir: None,
2165                root_repo_is_linked_worktree: false,
2166            },
2167            client,
2168            PathStyle::Unix,
2169            cx,
2170        );
2171        self.worktree_store
2172            .update(cx, |store, cx| store.add(&worktree, cx));
2173        worktree
2174    }
2175
2176    #[inline]
2177    pub fn dap_store(&self) -> Entity<DapStore> {
2178        self.dap_store.clone()
2179    }
2180
2181    #[inline]
2182    pub fn bookmark_store(&self) -> Entity<BookmarkStore> {
2183        self.bookmark_store.clone()
2184    }
2185
2186    #[inline]
2187    pub fn breakpoint_store(&self) -> Entity<BreakpointStore> {
2188        self.breakpoint_store.clone()
2189    }
2190
2191    pub fn active_debug_session(&self, cx: &App) -> Option<(Entity<Session>, ActiveStackFrame)> {
2192        let active_position = self.breakpoint_store.read(cx).active_position()?;
2193        let session = self
2194            .dap_store
2195            .read(cx)
2196            .session_by_id(active_position.session_id)?;
2197        Some((session, active_position.clone()))
2198    }
2199
2200    #[inline]
2201    pub fn lsp_store(&self) -> Entity<LspStore> {
2202        self.lsp_store.clone()
2203    }
2204
2205    #[inline]
2206    pub fn worktree_store(&self) -> Entity<WorktreeStore> {
2207        self.worktree_store.clone()
2208    }
2209
2210    /// Returns a future that resolves when all visible worktrees have completed
2211    /// their initial scan.
2212    pub fn wait_for_initial_scan(&self, cx: &App) -> impl Future<Output = ()> + use<> {
2213        self.worktree_store.read(cx).wait_for_initial_scan()
2214    }
2215
2216    #[inline]
2217    pub fn context_server_store(&self) -> Entity<ContextServerStore> {
2218        self.context_server_store.clone()
2219    }
2220
2221    #[inline]
2222    pub fn buffer_for_id(&self, remote_id: BufferId, cx: &App) -> Option<Entity<Buffer>> {
2223        self.buffer_store.read(cx).get(remote_id)
2224    }
2225
2226    #[inline]
2227    pub fn languages(&self) -> &Arc<LanguageRegistry> {
2228        &self.languages
2229    }
2230
2231    #[inline]
2232    pub fn client(&self) -> Arc<Client> {
2233        self.collab_client.clone()
2234    }
2235
2236    #[inline]
2237    pub fn remote_client(&self) -> Option<Entity<RemoteClient>> {
2238        self.remote_client.clone()
2239    }
2240
2241    #[inline]
2242    pub fn user_store(&self) -> Entity<UserStore> {
2243        self.user_store.clone()
2244    }
2245
2246    #[inline]
2247    pub fn node_runtime(&self) -> Option<&NodeRuntime> {
2248        self.node.as_ref()
2249    }
2250
2251    #[inline]
2252    pub fn opened_buffers(&self, cx: &App) -> Vec<Entity<Buffer>> {
2253        self.buffer_store.read(cx).buffers().collect()
2254    }
2255
2256    #[inline]
2257    pub fn environment(&self) -> &Entity<ProjectEnvironment> {
2258        &self.environment
2259    }
2260
2261    #[inline]
2262    pub fn cli_environment(&self, cx: &App) -> Option<HashMap<String, String>> {
2263        self.environment.read(cx).get_cli_environment()
2264    }
2265
2266    #[inline]
2267    pub fn peek_environment_error<'a>(&'a self, cx: &'a App) -> Option<&'a String> {
2268        self.environment.read(cx).peek_environment_error()
2269    }
2270
2271    #[inline]
2272    pub fn pop_environment_error(&mut self, cx: &mut Context<Self>) {
2273        self.environment.update(cx, |environment, _| {
2274            environment.pop_environment_error();
2275        });
2276    }
2277
2278    #[cfg(feature = "test-support")]
2279    #[inline]
2280    pub fn has_open_buffer(&self, path: impl Into<ProjectPath>, cx: &App) -> bool {
2281        self.buffer_store
2282            .read(cx)
2283            .get_by_path(&path.into())
2284            .is_some()
2285    }
2286
2287    #[inline]
2288    pub fn fs(&self) -> &Arc<dyn Fs> {
2289        &self.fs
2290    }
2291
2292    #[inline]
2293    pub fn remote_id(&self) -> Option<u64> {
2294        match self.client_state {
2295            ProjectClientState::Local => None,
2296            ProjectClientState::Shared { remote_id, .. }
2297            | ProjectClientState::Collab { remote_id, .. } => Some(remote_id),
2298        }
2299    }
2300
2301    #[inline]
2302    pub fn supports_terminal(&self, _cx: &App) -> bool {
2303        self.is_local() || self.is_via_remote_server()
2304    }
2305
2306    #[inline]
2307    pub fn remote_connection_state(&self, cx: &App) -> Option<remote::ConnectionState> {
2308        self.remote_client
2309            .as_ref()
2310            .map(|remote| remote.read(cx).connection_state())
2311    }
2312
2313    #[inline]
2314    pub fn remote_connection_options(&self, cx: &App) -> Option<RemoteConnectionOptions> {
2315        self.remote_client
2316            .as_ref()
2317            .map(|remote| remote.read(cx).connection_options())
2318    }
2319
2320    /// Reveals the given path in the system file manager.
2321    ///
2322    /// On Windows with a WSL remote connection, this converts the POSIX path
2323    /// to a Windows UNC path before revealing.
2324    pub fn reveal_path(&self, path: &Path, cx: &mut Context<Self>) {
2325        #[cfg(target_os = "windows")]
2326        if let Some(RemoteConnectionOptions::Wsl(wsl_options)) = self.remote_connection_options(cx)
2327        {
2328            let path = path.to_path_buf();
2329            cx.spawn(async move |_, cx| {
2330                wsl_path_to_windows_path(&wsl_options, &path)
2331                    .await
2332                    .map(|windows_path| cx.update(|cx| cx.reveal_path(&windows_path)))
2333            })
2334            .detach_and_log_err(cx);
2335            return;
2336        }
2337
2338        cx.reveal_path(path);
2339    }
2340
2341    #[inline]
2342    pub fn replica_id(&self) -> ReplicaId {
2343        match self.client_state {
2344            ProjectClientState::Collab { replica_id, .. } => replica_id,
2345            _ => {
2346                if self.remote_client.is_some() {
2347                    ReplicaId::REMOTE_SERVER
2348                } else {
2349                    ReplicaId::LOCAL
2350                }
2351            }
2352        }
2353    }
2354
2355    #[inline]
2356    pub fn task_store(&self) -> &Entity<TaskStore> {
2357        &self.task_store
2358    }
2359
2360    #[inline]
2361    pub fn snippets(&self) -> &Entity<SnippetProvider> {
2362        &self.snippets
2363    }
2364
2365    #[inline]
2366    pub fn search_history(&self, kind: SearchInputKind) -> &SearchHistory {
2367        match kind {
2368            SearchInputKind::Query => &self.search_history,
2369            SearchInputKind::Include => &self.search_included_history,
2370            SearchInputKind::Exclude => &self.search_excluded_history,
2371        }
2372    }
2373
2374    #[inline]
2375    pub fn search_history_mut(&mut self, kind: SearchInputKind) -> &mut SearchHistory {
2376        match kind {
2377            SearchInputKind::Query => &mut self.search_history,
2378            SearchInputKind::Include => &mut self.search_included_history,
2379            SearchInputKind::Exclude => &mut self.search_excluded_history,
2380        }
2381    }
2382
2383    #[inline]
2384    pub fn collaborators(&self) -> &HashMap<proto::PeerId, Collaborator> {
2385        &self.collaborators
2386    }
2387
2388    #[inline]
2389    pub fn host(&self) -> Option<&Collaborator> {
2390        self.collaborators.values().find(|c| c.is_host)
2391    }
2392
2393    /// Collect all worktrees, including ones that don't appear in the project panel
2394    #[inline]
2395    pub fn worktrees<'a>(
2396        &self,
2397        cx: &'a App,
2398    ) -> impl 'a + DoubleEndedIterator<Item = Entity<Worktree>> {
2399        self.worktree_store.read(cx).worktrees()
2400    }
2401
2402    /// Collect all user-visible worktrees, the ones that appear in the project panel.
2403    #[inline]
2404    pub fn visible_worktrees<'a>(
2405        &'a self,
2406        cx: &'a App,
2407    ) -> impl 'a + DoubleEndedIterator<Item = Entity<Worktree>> {
2408        self.worktree_store.read(cx).visible_worktrees(cx)
2409    }
2410
2411    pub(crate) fn default_visible_worktree_paths(
2412        worktree_store: &WorktreeStore,
2413        cx: &App,
2414    ) -> Vec<PathBuf> {
2415        worktree_store
2416            .visible_worktrees(cx)
2417            .sorted_by(|left, right| {
2418                left.read(cx)
2419                    .is_single_file()
2420                    .cmp(&right.read(cx).is_single_file())
2421            })
2422            .filter_map(|worktree| {
2423                let worktree = worktree.read(cx);
2424                let path = worktree.abs_path();
2425                if worktree.is_single_file() {
2426                    Some(path.parent()?.to_path_buf())
2427                } else {
2428                    Some(path.to_path_buf())
2429                }
2430            })
2431            .collect()
2432    }
2433
2434    pub fn default_path_list(&self, cx: &App) -> PathList {
2435        let worktree_roots =
2436            Self::default_visible_worktree_paths(&self.worktree_store.read(cx), cx);
2437
2438        if worktree_roots.is_empty() {
2439            PathList::new(&[paths::home_dir().as_path()])
2440        } else {
2441            PathList::new(&worktree_roots)
2442        }
2443    }
2444
2445    #[inline]
2446    pub fn worktree_for_root_name(&self, root_name: &str, cx: &App) -> Option<Entity<Worktree>> {
2447        self.visible_worktrees(cx)
2448            .find(|tree| tree.read(cx).root_name() == root_name)
2449    }
2450
2451    fn emit_group_key_changed_if_needed(&mut self, cx: &mut Context<Self>) {
2452        let new_worktree_paths = self.worktree_paths(cx);
2453        if new_worktree_paths != self.last_worktree_paths {
2454            let old_worktree_paths =
2455                std::mem::replace(&mut self.last_worktree_paths, new_worktree_paths);
2456            cx.emit(Event::WorktreePathsChanged { old_worktree_paths });
2457        }
2458    }
2459
2460    #[inline]
2461    pub fn worktree_root_names<'a>(&'a self, cx: &'a App) -> impl Iterator<Item = &'a str> {
2462        self.visible_worktrees(cx)
2463            .map(|tree| tree.read(cx).root_name().as_unix_str())
2464    }
2465
2466    #[inline]
2467    pub fn worktree_for_id(&self, id: WorktreeId, cx: &App) -> Option<Entity<Worktree>> {
2468        self.worktree_store.read(cx).worktree_for_id(id, cx)
2469    }
2470
2471    pub fn worktree_for_entry(
2472        &self,
2473        entry_id: ProjectEntryId,
2474        cx: &App,
2475    ) -> Option<Entity<Worktree>> {
2476        self.worktree_store
2477            .read(cx)
2478            .worktree_for_entry(entry_id, cx)
2479    }
2480
2481    #[inline]
2482    pub fn worktree_id_for_entry(&self, entry_id: ProjectEntryId, cx: &App) -> Option<WorktreeId> {
2483        self.worktree_for_entry(entry_id, cx)
2484            .map(|worktree| worktree.read(cx).id())
2485    }
2486
2487    /// Checks if the entry is the root of a worktree.
2488    #[inline]
2489    pub fn entry_is_worktree_root(&self, entry_id: ProjectEntryId, cx: &App) -> bool {
2490        self.worktree_for_entry(entry_id, cx)
2491            .map(|worktree| {
2492                worktree
2493                    .read(cx)
2494                    .root_entry()
2495                    .is_some_and(|e| e.id == entry_id)
2496            })
2497            .unwrap_or(false)
2498    }
2499
2500    #[inline]
2501    pub fn project_path_git_status(
2502        &self,
2503        project_path: &ProjectPath,
2504        cx: &App,
2505    ) -> Option<FileStatus> {
2506        self.git_store
2507            .read(cx)
2508            .project_path_git_status(project_path, cx)
2509    }
2510
2511    #[inline]
2512    pub fn visibility_for_paths(
2513        &self,
2514        paths: &[PathBuf],
2515        exclude_sub_dirs: bool,
2516        cx: &App,
2517    ) -> Option<bool> {
2518        paths
2519            .iter()
2520            .map(|path| self.visibility_for_path(path, exclude_sub_dirs, cx))
2521            .max()
2522            .flatten()
2523    }
2524
2525    pub fn visibility_for_path(
2526        &self,
2527        path: &Path,
2528        exclude_sub_dirs: bool,
2529        cx: &App,
2530    ) -> Option<bool> {
2531        let path = SanitizedPath::new(path).as_path();
2532        let path_style = self.path_style(cx);
2533        self.worktrees(cx)
2534            .filter_map(|worktree| {
2535                let worktree = worktree.read(cx);
2536                let abs_path = worktree.abs_path();
2537                let relative_path = path_style.strip_prefix(path, abs_path.as_ref())?;
2538                // Don't exclude the worktree root itself, only actual subdirectories
2539                let is_subpath = !relative_path.is_empty();
2540                // Gitignored subtrees aren't scanned, so their contents don't
2541                // meaningfully belong to this project (e.g. nested checkouts
2542                // in an ignored directory). Treat such paths as not contained
2543                // so opening them behaves like opening an unrelated path.
2544                if is_subpath && worktree.is_path_ignored(&relative_path) {
2545                    return None;
2546                }
2547                let is_dir = worktree
2548                    .entry_for_path(&relative_path)
2549                    .is_some_and(|e| e.is_dir());
2550                let contains = !exclude_sub_dirs || !is_dir || !is_subpath;
2551                contains.then(|| worktree.is_visible())
2552            })
2553            .max()
2554    }
2555
2556    pub fn visibility_for_subpaths(&self, paths: &[PathBuf], cx: &App) -> Option<bool> {
2557        paths
2558            .iter()
2559            .map(|path| self.visibility_for_subpath(path, cx))
2560            .max()
2561            .flatten()
2562    }
2563
2564    fn visibility_for_subpath(&self, path: &Path, cx: &App) -> Option<bool> {
2565        let path = SanitizedPath::new(path).as_path();
2566        let path_style = self.path_style(cx);
2567        self.worktrees(cx)
2568            .filter_map(|worktree| {
2569                let worktree = worktree.read(cx);
2570                let abs_path = worktree.abs_path();
2571                let relative_path = path_style.strip_prefix(path, abs_path.as_ref())?;
2572                let is_subpath =
2573                    !relative_path.is_empty() && !worktree.is_path_ignored(&relative_path);
2574                is_subpath.then(|| worktree.is_visible())
2575            })
2576            .max()
2577    }
2578
2579    pub fn create_entry(
2580        &mut self,
2581        project_path: impl Into<ProjectPath>,
2582        is_directory: bool,
2583        cx: &mut Context<Self>,
2584    ) -> Task<Result<CreatedEntry>> {
2585        let project_path = project_path.into();
2586        let Some(worktree) = self.worktree_for_id(project_path.worktree_id, cx) else {
2587            return Task::ready(Err(anyhow!(format!(
2588                "No worktree for path {project_path:?}"
2589            ))));
2590        };
2591        worktree.update(cx, |worktree, cx| {
2592            worktree.create_entry(project_path.path, is_directory, None, cx)
2593        })
2594    }
2595
2596    #[inline]
2597    pub fn copy_entry(
2598        &mut self,
2599        entry_id: ProjectEntryId,
2600        new_project_path: ProjectPath,
2601        cx: &mut Context<Self>,
2602    ) -> Task<Result<Option<Entry>>> {
2603        self.worktree_store.update(cx, |worktree_store, cx| {
2604            worktree_store.copy_entry(entry_id, new_project_path, cx)
2605        })
2606    }
2607
2608    /// Renames the project entry with given `entry_id`.
2609    ///
2610    /// `new_path` is a relative path to worktree root.
2611    /// If root entry is renamed then its new root name is used instead.
2612    pub fn rename_entry(
2613        &mut self,
2614        entry_id: ProjectEntryId,
2615        new_path: ProjectPath,
2616        cx: &mut Context<Self>,
2617    ) -> Task<Result<CreatedEntry>> {
2618        let worktree_store = self.worktree_store.clone();
2619        let Some((worktree, old_path, is_dir)) = worktree_store
2620            .read(cx)
2621            .worktree_and_entry_for_id(entry_id, cx)
2622            .map(|(worktree, entry)| (worktree, entry.path.clone(), entry.is_dir()))
2623        else {
2624            return Task::ready(Err(anyhow!(format!("No worktree for entry {entry_id:?}"))));
2625        };
2626
2627        let worktree_id = worktree.read(cx).id();
2628        let is_root_entry = self.entry_is_worktree_root(entry_id, cx);
2629
2630        let lsp_store = self.lsp_store().downgrade();
2631        cx.spawn(async move |project, cx| {
2632            let (old_abs_path, new_abs_path) = {
2633                let root_path = worktree.read_with(cx, |this, _| this.abs_path());
2634                let new_abs_path = if is_root_entry {
2635                    root_path
2636                        .parent()
2637                        .unwrap()
2638                        .join(new_path.path.as_std_path())
2639                } else {
2640                    root_path.join(&new_path.path.as_std_path())
2641                };
2642                (root_path.join(old_path.as_std_path()), new_abs_path)
2643            };
2644            let transaction = LspStore::will_rename_entry(
2645                lsp_store.clone(),
2646                worktree_id,
2647                &old_abs_path,
2648                &new_abs_path,
2649                is_dir,
2650                cx.clone(),
2651            )
2652            .await;
2653
2654            let entry = worktree_store
2655                .update(cx, |worktree_store, cx| {
2656                    worktree_store.rename_entry(entry_id, new_path.clone(), cx)
2657                })
2658                .await?;
2659
2660            project
2661                .update(cx, |_, cx| {
2662                    cx.emit(Event::EntryRenamed(
2663                        transaction,
2664                        new_path.clone(),
2665                        new_abs_path.clone(),
2666                    ));
2667                })
2668                .ok();
2669
2670            lsp_store
2671                .read_with(cx, |this, _| {
2672                    this.did_rename_entry(worktree_id, &old_abs_path, &new_abs_path, is_dir);
2673                })
2674                .ok();
2675            Ok(entry)
2676        })
2677    }
2678
2679    #[inline]
2680    pub fn trash_file(
2681        &mut self,
2682        path: ProjectPath,
2683        cx: &mut Context<Self>,
2684    ) -> Option<Task<Result<TrashId>>> {
2685        let entry = self.entry_for_path(&path, cx)?;
2686        self.trash_entry(entry.id, cx)
2687    }
2688
2689    #[inline]
2690    pub fn delete_file(
2691        &mut self,
2692        path: ProjectPath,
2693        cx: &mut Context<Self>,
2694    ) -> Option<Task<Result<()>>> {
2695        let entry = self.entry_for_path(&path, cx)?;
2696        self.delete_entry(entry.id, cx)
2697    }
2698
2699    #[inline]
2700    pub fn trash_entry(
2701        &mut self,
2702        entry_id: ProjectEntryId,
2703        cx: &mut Context<Self>,
2704    ) -> Option<Task<Result<TrashId>>> {
2705        let worktree = self.worktree_for_entry(entry_id, cx)?;
2706        cx.emit(Event::DeletedEntry(worktree.read(cx).id(), entry_id));
2707        worktree.update(cx, |worktree, cx| worktree.trash_entry(entry_id, cx))
2708    }
2709
2710    #[inline]
2711    pub fn delete_entry(
2712        &mut self,
2713        entry_id: ProjectEntryId,
2714        cx: &mut Context<Self>,
2715    ) -> Option<Task<Result<()>>> {
2716        let worktree = self.worktree_for_entry(entry_id, cx)?;
2717        cx.emit(Event::DeletedEntry(worktree.read(cx).id(), entry_id));
2718        worktree.update(cx, |worktree, cx| worktree.delete_entry(entry_id, cx))
2719    }
2720
2721    #[inline]
2722    pub fn restore_entry(
2723        &self,
2724        worktree_id: WorktreeId,
2725        trash_id: TrashId,
2726        cx: &mut Context<'_, Self>,
2727    ) -> Task<Result<ProjectPath>> {
2728        let Some(worktree) = self.worktree_for_id(worktree_id, cx) else {
2729            return Task::ready(Err(anyhow!("No worktree for id {worktree_id:?}")));
2730        };
2731
2732        cx.spawn(async move |_, cx| {
2733            let entry = worktree
2734                .update(cx, |worktree, cx| worktree.restore_entry(trash_id, cx))
2735                .await?;
2736
2737            Ok(ProjectPath {
2738                worktree_id: worktree_id,
2739                path: entry.path,
2740            })
2741        })
2742    }
2743
2744    #[inline]
2745    pub fn expand_entry(
2746        &mut self,
2747        worktree_id: WorktreeId,
2748        entry_id: ProjectEntryId,
2749        cx: &mut Context<Self>,
2750    ) -> Option<Task<Result<()>>> {
2751        let worktree = self.worktree_for_id(worktree_id, cx)?;
2752        worktree.update(cx, |worktree, cx| worktree.expand_entry(entry_id, cx))
2753    }
2754
2755    pub fn expand_all_for_entry(
2756        &mut self,
2757        worktree_id: WorktreeId,
2758        entry_id: ProjectEntryId,
2759        cx: &mut Context<Self>,
2760    ) -> Option<Task<Result<()>>> {
2761        let worktree = self.worktree_for_id(worktree_id, cx)?;
2762        let task = worktree.update(cx, |worktree, cx| {
2763            worktree.expand_all_for_entry(entry_id, cx)
2764        });
2765        Some(cx.spawn(async move |this, cx| {
2766            task.context("no task")?.await?;
2767            this.update(cx, |_, cx| {
2768                cx.emit(Event::ExpandedAllForEntry(worktree_id, entry_id));
2769            })?;
2770            Ok(())
2771        }))
2772    }
2773
2774    pub fn shared(&mut self, project_id: u64, cx: &mut Context<Self>) -> Result<()> {
2775        anyhow::ensure!(
2776            matches!(self.client_state, ProjectClientState::Local),
2777            "project was already shared"
2778        );
2779
2780        self.client_subscriptions.extend([
2781            self.collab_client
2782                .subscribe_to_entity(project_id)?
2783                .set_entity(&cx.entity(), &cx.to_async()),
2784            self.collab_client
2785                .subscribe_to_entity(project_id)?
2786                .set_entity(&self.worktree_store, &cx.to_async()),
2787            self.collab_client
2788                .subscribe_to_entity(project_id)?
2789                .set_entity(&self.buffer_store, &cx.to_async()),
2790            self.collab_client
2791                .subscribe_to_entity(project_id)?
2792                .set_entity(&self.lsp_store, &cx.to_async()),
2793            self.collab_client
2794                .subscribe_to_entity(project_id)?
2795                .set_entity(&self.settings_observer, &cx.to_async()),
2796            self.collab_client
2797                .subscribe_to_entity(project_id)?
2798                .set_entity(&self.dap_store, &cx.to_async()),
2799            self.collab_client
2800                .subscribe_to_entity(project_id)?
2801                .set_entity(&self.breakpoint_store, &cx.to_async()),
2802            self.collab_client
2803                .subscribe_to_entity(project_id)?
2804                .set_entity(&self.git_store, &cx.to_async()),
2805        ]);
2806
2807        self.buffer_store.update(cx, |buffer_store, cx| {
2808            buffer_store.shared(project_id, self.collab_client.clone().into(), cx)
2809        });
2810        self.worktree_store.update(cx, |worktree_store, cx| {
2811            worktree_store.shared(project_id, self.collab_client.clone().into(), cx);
2812        });
2813        self.lsp_store.update(cx, |lsp_store, cx| {
2814            lsp_store.shared(project_id, self.collab_client.clone().into(), cx)
2815        });
2816        self.breakpoint_store.update(cx, |breakpoint_store, _| {
2817            breakpoint_store.shared(project_id, self.collab_client.clone().into())
2818        });
2819        self.dap_store.update(cx, |dap_store, cx| {
2820            dap_store.shared(project_id, self.collab_client.clone().into(), cx);
2821        });
2822        self.task_store.update(cx, |task_store, cx| {
2823            task_store.shared(project_id, self.collab_client.clone().into(), cx);
2824        });
2825        self.settings_observer.update(cx, |settings_observer, cx| {
2826            settings_observer.shared(project_id, self.collab_client.clone().into(), cx)
2827        });
2828        self.git_store.update(cx, |git_store, cx| {
2829            git_store.shared(project_id, self.collab_client.clone().into(), cx)
2830        });
2831
2832        self.client_state = ProjectClientState::Shared {
2833            remote_id: project_id,
2834        };
2835
2836        cx.emit(Event::RemoteIdChanged(Some(project_id)));
2837        Ok(())
2838    }
2839
2840    pub fn reshared(
2841        &mut self,
2842        message: proto::ResharedProject,
2843        cx: &mut Context<Self>,
2844    ) -> Result<()> {
2845        self.buffer_store
2846            .update(cx, |buffer_store, _| buffer_store.forget_shared_buffers());
2847        self.set_collaborators_from_proto(message.collaborators, cx)?;
2848
2849        self.worktree_store.update(cx, |worktree_store, cx| {
2850            worktree_store.send_project_updates(cx);
2851        });
2852        if let Some(remote_id) = self.remote_id() {
2853            self.git_store.update(cx, |git_store, cx| {
2854                git_store.shared(remote_id, self.collab_client.clone().into(), cx)
2855            });
2856        }
2857        cx.emit(Event::Reshared);
2858        Ok(())
2859    }
2860
2861    pub fn rejoined(
2862        &mut self,
2863        message: proto::RejoinedProject,
2864        message_id: u32,
2865        cx: &mut Context<Self>,
2866    ) -> Result<()> {
2867        cx.update_global::<SettingsStore, _>(|store, cx| {
2868            for worktree_metadata in &message.worktrees {
2869                store
2870                    .clear_local_settings(WorktreeId::from_proto(worktree_metadata.id), cx)
2871                    .log_err();
2872            }
2873        });
2874
2875        self.join_project_response_message_id = message_id;
2876        self.set_worktrees_from_proto(message.worktrees, cx)?;
2877        self.set_collaborators_from_proto(message.collaborators, cx)?;
2878
2879        let project = cx.weak_entity();
2880        self.lsp_store.update(cx, |lsp_store, cx| {
2881            lsp_store.set_language_server_statuses_from_proto(
2882                project,
2883                message.language_servers,
2884                message.language_server_capabilities,
2885                cx,
2886            )
2887        });
2888        self.enqueue_buffer_ordered_message(BufferOrderedMessage::Resync)
2889            .unwrap();
2890        cx.emit(Event::Rejoined);
2891        Ok(())
2892    }
2893
2894    #[inline]
2895    pub fn unshare(&mut self, cx: &mut Context<Self>) -> Result<()> {
2896        self.unshare_internal(cx)?;
2897        cx.emit(Event::RemoteIdChanged(None));
2898        Ok(())
2899    }
2900
2901    fn unshare_internal(&mut self, cx: &mut App) -> Result<()> {
2902        anyhow::ensure!(
2903            !self.is_via_collab(),
2904            "attempted to unshare a remote project"
2905        );
2906
2907        if let ProjectClientState::Shared { remote_id, .. } = self.client_state {
2908            self.client_state = ProjectClientState::Local;
2909            self.collaborators.clear();
2910            self.client_subscriptions.clear();
2911            self.worktree_store.update(cx, |store, cx| {
2912                store.unshared(cx);
2913            });
2914            self.buffer_store.update(cx, |buffer_store, cx| {
2915                buffer_store.forget_shared_buffers();
2916                buffer_store.unshared(cx)
2917            });
2918            self.task_store.update(cx, |task_store, cx| {
2919                task_store.unshared(cx);
2920            });
2921            self.breakpoint_store.update(cx, |breakpoint_store, cx| {
2922                breakpoint_store.unshared(cx);
2923            });
2924            self.dap_store.update(cx, |dap_store, cx| {
2925                dap_store.unshared(cx);
2926            });
2927            self.settings_observer.update(cx, |settings_observer, cx| {
2928                settings_observer.unshared(cx);
2929            });
2930            self.git_store.update(cx, |git_store, cx| {
2931                git_store.unshared(cx);
2932            });
2933
2934            self.collab_client
2935                .send(proto::UnshareProject {
2936                    project_id: remote_id,
2937                })
2938                .ok();
2939            Ok(())
2940        } else {
2941            anyhow::bail!("attempted to unshare an unshared project");
2942        }
2943    }
2944
2945    pub fn disconnected_from_host(&mut self, cx: &mut Context<Self>) {
2946        if self.is_disconnected(cx) {
2947            return;
2948        }
2949        self.disconnected_from_host_internal(cx);
2950        cx.emit(Event::DisconnectedFromHost);
2951    }
2952
2953    pub fn set_role(&mut self, role: proto::ChannelRole, cx: &mut Context<Self>) {
2954        let new_capability =
2955            if role == proto::ChannelRole::Member || role == proto::ChannelRole::Admin {
2956                Capability::ReadWrite
2957            } else {
2958                Capability::ReadOnly
2959            };
2960        if let ProjectClientState::Collab { capability, .. } = &mut self.client_state {
2961            if *capability == new_capability {
2962                return;
2963            }
2964
2965            *capability = new_capability;
2966            for buffer in self.opened_buffers(cx) {
2967                buffer.update(cx, |buffer, cx| buffer.set_capability(new_capability, cx));
2968            }
2969        }
2970    }
2971
2972    fn disconnected_from_host_internal(&mut self, cx: &mut App) {
2973        if let ProjectClientState::Collab {
2974            sharing_has_stopped,
2975            ..
2976        } = &mut self.client_state
2977        {
2978            *sharing_has_stopped = true;
2979            self.client_subscriptions.clear();
2980            self.collaborators.clear();
2981            self.worktree_store.update(cx, |store, cx| {
2982                store.disconnected_from_host(cx);
2983            });
2984            self.buffer_store.update(cx, |buffer_store, cx| {
2985                buffer_store.disconnected_from_host(cx)
2986            });
2987            self.lsp_store
2988                .update(cx, |lsp_store, _cx| lsp_store.disconnected_from_host());
2989        }
2990    }
2991
2992    #[inline]
2993    pub fn close(&mut self, cx: &mut Context<Self>) {
2994        cx.emit(Event::Closed);
2995    }
2996
2997    #[inline]
2998    pub fn is_disconnected(&self, cx: &App) -> bool {
2999        match &self.client_state {
3000            ProjectClientState::Collab {
3001                sharing_has_stopped,
3002                ..
3003            } => *sharing_has_stopped,
3004            ProjectClientState::Local if self.is_via_remote_server() => {
3005                self.remote_client_is_disconnected(cx)
3006            }
3007            _ => false,
3008        }
3009    }
3010
3011    #[inline]
3012    fn remote_client_is_disconnected(&self, cx: &App) -> bool {
3013        self.remote_client
3014            .as_ref()
3015            .map(|remote| remote.read(cx).is_disconnected())
3016            .unwrap_or(false)
3017    }
3018
3019    #[inline]
3020    pub fn capability(&self) -> Capability {
3021        match &self.client_state {
3022            ProjectClientState::Collab { capability, .. } => *capability,
3023            ProjectClientState::Shared { .. } | ProjectClientState::Local => Capability::ReadWrite,
3024        }
3025    }
3026
3027    #[inline]
3028    pub fn is_read_only(&self, cx: &App) -> bool {
3029        self.is_disconnected(cx) || !self.capability().editable()
3030    }
3031
3032    #[inline]
3033    pub fn is_local(&self) -> bool {
3034        match &self.client_state {
3035            ProjectClientState::Local | ProjectClientState::Shared { .. } => {
3036                self.remote_client.is_none()
3037            }
3038            ProjectClientState::Collab { .. } => false,
3039        }
3040    }
3041
3042    /// Whether this project is a remote server (not counting collab).
3043    #[inline]
3044    pub fn is_via_remote_server(&self) -> bool {
3045        match &self.client_state {
3046            ProjectClientState::Local | ProjectClientState::Shared { .. } => {
3047                self.remote_client.is_some()
3048            }
3049            ProjectClientState::Collab { .. } => false,
3050        }
3051    }
3052
3053    /// Whether this project is from collab (not counting remote servers).
3054    #[inline]
3055    pub fn is_via_collab(&self) -> bool {
3056        match &self.client_state {
3057            ProjectClientState::Local | ProjectClientState::Shared { .. } => false,
3058            ProjectClientState::Collab { .. } => true,
3059        }
3060    }
3061
3062    /// `!self.is_local()`
3063    #[inline]
3064    pub fn is_remote(&self) -> bool {
3065        debug_assert_eq!(
3066            !self.is_local(),
3067            self.is_via_collab() || self.is_via_remote_server()
3068        );
3069        !self.is_local()
3070    }
3071
3072    #[inline]
3073    pub fn is_via_wsl_with_host_interop(&self, cx: &App) -> bool {
3074        match &self.client_state {
3075            ProjectClientState::Local | ProjectClientState::Shared { .. } => {
3076                matches!(
3077                    &self.remote_client, Some(remote_client)
3078                    if remote_client.read(cx).has_wsl_interop()
3079                )
3080            }
3081            _ => false,
3082        }
3083    }
3084
3085    pub fn disable_worktree_scanner(&mut self, cx: &mut Context<Self>) {
3086        self.worktree_store.update(cx, |worktree_store, _cx| {
3087            worktree_store.disable_scanner();
3088        });
3089    }
3090
3091    #[inline]
3092    pub fn create_buffer(
3093        &mut self,
3094        language: Option<Arc<Language>>,
3095        project_searchable: bool,
3096        cx: &mut Context<Self>,
3097    ) -> Task<Result<Entity<Buffer>>> {
3098        self.buffer_store.update(cx, |buffer_store, cx| {
3099            buffer_store.create_buffer(language, project_searchable, cx)
3100        })
3101    }
3102
3103    #[inline]
3104    pub fn create_local_buffer(
3105        &mut self,
3106        text: &str,
3107        language: Option<Arc<Language>>,
3108        project_searchable: bool,
3109        cx: &mut Context<Self>,
3110    ) -> Entity<Buffer> {
3111        if self.is_remote() {
3112            panic!("called create_local_buffer on a remote project")
3113        }
3114        self.buffer_store.update(cx, |buffer_store, cx| {
3115            buffer_store.create_local_buffer(text, language, project_searchable, cx)
3116        })
3117    }
3118
3119    pub fn open_path(
3120        &mut self,
3121        path: ProjectPath,
3122        cx: &mut Context<Self>,
3123    ) -> Task<Result<(Option<ProjectEntryId>, Entity<Buffer>)>> {
3124        let task = self.open_buffer(path, cx);
3125        cx.spawn(async move |_project, cx| {
3126            let buffer = task.await?;
3127            let project_entry_id = buffer.read_with(cx, |buffer, _cx| {
3128                File::from_dyn(buffer.file()).and_then(|file| file.project_entry_id())
3129            });
3130
3131            Ok((project_entry_id, buffer))
3132        })
3133    }
3134
3135    pub fn open_local_buffer(
3136        &mut self,
3137        abs_path: impl AsRef<Path>,
3138        cx: &mut Context<Self>,
3139    ) -> Task<Result<Entity<Buffer>>> {
3140        let worktree_task = self.find_or_create_worktree(abs_path.as_ref(), false, cx);
3141        cx.spawn(async move |this, cx| {
3142            let (worktree, relative_path) = worktree_task.await?;
3143            this.update(cx, |this, cx| {
3144                this.open_buffer((worktree.read(cx).id(), relative_path), cx)
3145            })?
3146            .await
3147        })
3148    }
3149
3150    #[cfg(feature = "test-support")]
3151    pub fn open_local_buffer_with_lsp(
3152        &mut self,
3153        abs_path: impl AsRef<Path>,
3154        cx: &mut Context<Self>,
3155    ) -> Task<Result<(Entity<Buffer>, lsp_store::OpenLspBufferHandle)>> {
3156        if let Some((worktree, relative_path)) = self.find_worktree(abs_path.as_ref(), cx) {
3157            self.open_buffer_with_lsp((worktree.read(cx).id(), relative_path), cx)
3158        } else {
3159            Task::ready(Err(anyhow!("no such path")))
3160        }
3161    }
3162
3163    pub fn download_file(
3164        &mut self,
3165        worktree_id: WorktreeId,
3166        path: Arc<RelPath>,
3167        destination_path: PathBuf,
3168        cx: &mut Context<Self>,
3169    ) -> Task<Result<()>> {
3170        log::debug!(
3171            "download_file called: worktree_id={:?}, path={:?}, destination={:?}",
3172            worktree_id,
3173            path,
3174            destination_path
3175        );
3176
3177        let Some(remote_client) = &self.remote_client else {
3178            log::error!("download_file: not a remote project");
3179            return Task::ready(Err(anyhow!("not a remote project")));
3180        };
3181
3182        let proto_client = remote_client.read(cx).proto_client();
3183        // For SSH remote projects, use REMOTE_SERVER_PROJECT_ID instead of remote_id()
3184        // because SSH projects have client_state: Local but still need to communicate with remote server
3185        let project_id = self.remote_id().unwrap_or(REMOTE_SERVER_PROJECT_ID);
3186        let downloading_files = self.downloading_files.clone();
3187        let path_str = path.as_unix_str().to_owned();
3188
3189        static NEXT_FILE_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
3190        let file_id = NEXT_FILE_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
3191
3192        // Register BEFORE sending request to avoid race condition
3193        let key = (worktree_id, path_str.clone());
3194        log::debug!(
3195            "download_file: pre-registering download with key={:?}, file_id={}",
3196            key,
3197            file_id
3198        );
3199        downloading_files.lock().insert(
3200            key,
3201            DownloadingFile {
3202                destination_path: destination_path,
3203                chunks: Vec::new(),
3204                total_size: 0,
3205                file_id: Some(file_id),
3206            },
3207        );
3208        log::debug!(
3209            "download_file: sending DownloadFileByPath request, path_str={}",
3210            path_str
3211        );
3212
3213        cx.spawn(async move |_this, _cx| {
3214            log::debug!("download_file: sending request with file_id={}...", file_id);
3215            let response = proto_client
3216                .request(proto::DownloadFileByPath {
3217                    project_id,
3218                    worktree_id: worktree_id.to_proto(),
3219                    path: path_str.clone(),
3220                    file_id,
3221                })
3222                .await?;
3223
3224            log::debug!("download_file: got response, file_id={}", response.file_id);
3225            // The file_id is set from the State message, we just confirm the request succeeded
3226            Ok(())
3227        })
3228    }
3229
3230    #[ztracing::instrument(skip_all)]
3231    pub fn open_buffer(
3232        &mut self,
3233        path: impl Into<ProjectPath>,
3234        cx: &mut App,
3235    ) -> Task<Result<Entity<Buffer>>> {
3236        if self.is_disconnected(cx) {
3237            return Task::ready(Err(anyhow!(ErrorCode::Disconnected)));
3238        }
3239
3240        self.buffer_store.update(cx, |buffer_store, cx| {
3241            buffer_store.open_buffer(path.into(), cx)
3242        })
3243    }
3244
3245    #[cfg(feature = "test-support")]
3246    pub fn open_buffer_with_lsp(
3247        &mut self,
3248        path: impl Into<ProjectPath>,
3249        cx: &mut Context<Self>,
3250    ) -> Task<Result<(Entity<Buffer>, lsp_store::OpenLspBufferHandle)>> {
3251        let buffer = self.open_buffer(path, cx);
3252        cx.spawn(async move |this, cx| {
3253            let buffer = buffer.await?;
3254            let handle = this.update(cx, |project, cx| {
3255                project.register_buffer_with_language_servers(&buffer, cx)
3256            })?;
3257            Ok((buffer, handle))
3258        })
3259    }
3260
3261    pub fn register_buffer_with_language_servers(
3262        &self,
3263        buffer: &Entity<Buffer>,
3264        cx: &mut App,
3265    ) -> OpenLspBufferHandle {
3266        self.lsp_store.update(cx, |lsp_store, cx| {
3267            lsp_store.register_buffer_with_language_servers(buffer, HashSet::default(), false, cx)
3268        })
3269    }
3270
3271    pub fn open_unstaged_diff(
3272        &mut self,
3273        buffer: Entity<Buffer>,
3274        cx: &mut Context<Self>,
3275    ) -> Task<Result<Entity<BufferDiff>>> {
3276        if self.is_disconnected(cx) {
3277            return Task::ready(Err(anyhow!(ErrorCode::Disconnected)));
3278        }
3279        self.git_store
3280            .update(cx, |git_store, cx| git_store.open_unstaged_diff(buffer, cx))
3281    }
3282
3283    /// Opens the staged (HEAD-vs-index) diff for the given buffer, along with
3284    /// the index text buffer that is the diff's main buffer.
3285    #[ztracing::instrument(skip_all)]
3286    pub fn open_staged_diff(
3287        &mut self,
3288        buffer: Entity<Buffer>,
3289        cx: &mut Context<Self>,
3290    ) -> Task<Result<(Entity<BufferDiff>, Entity<Buffer>)>> {
3291        if self.is_disconnected(cx) {
3292            return Task::ready(Err(anyhow!(ErrorCode::Disconnected)));
3293        }
3294        self.git_store
3295            .update(cx, |git_store, cx| git_store.open_staged_diff(buffer, cx))
3296    }
3297
3298    #[ztracing::instrument(skip_all)]
3299    pub fn open_uncommitted_diff(
3300        &mut self,
3301        buffer: Entity<Buffer>,
3302        cx: &mut Context<Self>,
3303    ) -> Task<Result<Entity<BufferDiff>>> {
3304        if self.is_disconnected(cx) {
3305            return Task::ready(Err(anyhow!(ErrorCode::Disconnected)));
3306        }
3307        self.git_store.update(cx, |git_store, cx| {
3308            git_store.open_uncommitted_diff(buffer, cx)
3309        })
3310    }
3311
3312    /// Stages the worktree changes covered by `worktree_ranges` (in the worktree
3313    /// buffer's coordinates), acting on the given unstaged diff. Used by both the
3314    /// unstaged-changes view and the uncommitted (gutter) controls.
3315    pub fn stage_hunks(
3316        &mut self,
3317        buffer: Entity<Buffer>,
3318        unstaged_diff: Entity<BufferDiff>,
3319        worktree_ranges: Vec<Range<Anchor>>,
3320        cx: &mut Context<Self>,
3321    ) -> Result<()> {
3322        if self.is_disconnected(cx) {
3323            return Err(anyhow!(ErrorCode::Disconnected));
3324        }
3325        self.git_store.update(cx, |git_store, cx| {
3326            git_store.stage_hunks(buffer, unstaged_diff, worktree_ranges, cx)
3327        })
3328    }
3329
3330    /// Unstages the worktree changes covered by `worktree_ranges` (in the worktree
3331    /// buffer's coordinates), acting on the given uncommitted diff. Used by the
3332    /// uncommitted (gutter) controls.
3333    pub fn unstage_uncommitted_hunks(
3334        &mut self,
3335        buffer: Entity<Buffer>,
3336        uncommitted_diff: Entity<BufferDiff>,
3337        worktree_ranges: Vec<Range<Anchor>>,
3338        cx: &mut Context<Self>,
3339    ) -> Result<()> {
3340        if self.is_disconnected(cx) {
3341            return Err(anyhow!(ErrorCode::Disconnected));
3342        }
3343        self.git_store.update(cx, |git_store, cx| {
3344            git_store.unstage_uncommitted_hunks(buffer, uncommitted_diff, worktree_ranges, cx)
3345        })
3346    }
3347
3348    /// Unstages the staged changes covered by `index_ranges` (in the index
3349    /// buffer's coordinates), acting on the given staged diff. Used by the
3350    /// staged-changes view.
3351    pub fn unstage_staged_hunks(
3352        &mut self,
3353        staged_diff: Entity<BufferDiff>,
3354        index_ranges: Vec<Range<Anchor>>,
3355        cx: &mut Context<Self>,
3356    ) -> Result<()> {
3357        if self.is_disconnected(cx) {
3358            return Err(anyhow!(ErrorCode::Disconnected));
3359        }
3360        self.git_store.update(cx, |git_store, cx| {
3361            git_store.unstage_staged_hunks(staged_diff, index_ranges, cx)
3362        })
3363    }
3364
3365    pub fn open_buffer_by_id(
3366        &mut self,
3367        id: BufferId,
3368        cx: &mut Context<Self>,
3369    ) -> Task<Result<Entity<Buffer>>> {
3370        if let Some(buffer) = self.buffer_for_id(id, cx) {
3371            Task::ready(Ok(buffer))
3372        } else if self.is_local() || self.is_via_remote_server() {
3373            Task::ready(Err(anyhow!("buffer {id} does not exist")))
3374        } else if let Some(project_id) = self.remote_id() {
3375            let request = self.collab_client.request(proto::OpenBufferById {
3376                project_id,
3377                id: id.into(),
3378            });
3379            cx.spawn(async move |project, cx| {
3380                let buffer_id = BufferId::new(request.await?.buffer_id)?;
3381                project
3382                    .update(cx, |project, cx| {
3383                        project.buffer_store.update(cx, |buffer_store, cx| {
3384                            buffer_store.wait_for_remote_buffer(buffer_id, cx)
3385                        })
3386                    })?
3387                    .await
3388            })
3389        } else {
3390            Task::ready(Err(anyhow!("cannot open buffer while disconnected")))
3391        }
3392    }
3393
3394    pub fn save_buffers(
3395        &self,
3396        buffers: HashSet<Entity<Buffer>>,
3397        cx: &mut Context<Self>,
3398    ) -> Task<Result<()>> {
3399        cx.spawn(async move |this, cx| {
3400            let save_tasks = buffers.into_iter().filter_map(|buffer| {
3401                this.update(cx, |this, cx| this.save_buffer(buffer, cx))
3402                    .ok()
3403            });
3404            try_join_all(save_tasks).await?;
3405            Ok(())
3406        })
3407    }
3408
3409    pub fn save_buffer(&self, buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Task<Result<()>> {
3410        self.buffer_store
3411            .update(cx, |buffer_store, cx| buffer_store.save_buffer(buffer, cx))
3412    }
3413
3414    pub fn save_buffer_as(
3415        &mut self,
3416        buffer: Entity<Buffer>,
3417        path: ProjectPath,
3418        cx: &mut Context<Self>,
3419    ) -> Task<Result<()>> {
3420        self.buffer_store.update(cx, |buffer_store, cx| {
3421            buffer_store.save_buffer_as(buffer.clone(), path, cx)
3422        })
3423    }
3424
3425    pub fn get_open_buffer(&self, path: &ProjectPath, cx: &App) -> Option<Entity<Buffer>> {
3426        self.buffer_store.read(cx).get_by_path(path)
3427    }
3428
3429    fn register_buffer(&mut self, buffer: &Entity<Buffer>, cx: &mut Context<Self>) -> Result<()> {
3430        {
3431            let mut remotely_created_models = self.remotely_created_models.lock();
3432            if remotely_created_models.retain_count > 0 {
3433                remotely_created_models.buffers.push(buffer.clone())
3434            }
3435        }
3436
3437        self.request_buffer_diff_recalculation(buffer, cx);
3438
3439        cx.subscribe(buffer, |this, buffer, event, cx| {
3440            this.on_buffer_event(buffer, event, cx);
3441        })
3442        .detach();
3443
3444        Ok(())
3445    }
3446
3447    pub fn open_image(
3448        &mut self,
3449        path: impl Into<ProjectPath>,
3450        cx: &mut Context<Self>,
3451    ) -> Task<Result<Entity<ImageItem>>> {
3452        if self.is_disconnected(cx) {
3453            return Task::ready(Err(anyhow!(ErrorCode::Disconnected)));
3454        }
3455
3456        let open_image_task = self.image_store.update(cx, |image_store, cx| {
3457            image_store.open_image(path.into(), cx)
3458        });
3459
3460        let weak_project = cx.entity().downgrade();
3461        cx.spawn(async move |_, cx| {
3462            let image_item = open_image_task.await?;
3463
3464            // Check if metadata already exists (e.g., for remote images)
3465            let needs_metadata =
3466                cx.read_entity(&image_item, |item, _| item.image_metadata.is_none());
3467
3468            if needs_metadata {
3469                let project = weak_project.upgrade().context("Project dropped")?;
3470                let metadata =
3471                    ImageItem::load_image_metadata(image_item.clone(), project, cx).await?;
3472                image_item.update(cx, |image_item, cx| {
3473                    image_item.image_metadata = Some(metadata);
3474                    cx.emit(ImageItemEvent::MetadataUpdated);
3475                });
3476            }
3477
3478            Ok(image_item)
3479        })
3480    }
3481
3482    async fn send_buffer_ordered_messages(
3483        project: WeakEntity<Self>,
3484        rx: UnboundedReceiver<BufferOrderedMessage>,
3485        cx: &mut AsyncApp,
3486    ) -> Result<()> {
3487        const MAX_BATCH_SIZE: usize = 128;
3488
3489        let mut operations_by_buffer_id = HashMap::default();
3490        async fn flush_operations(
3491            this: &WeakEntity<Project>,
3492            operations_by_buffer_id: &mut HashMap<BufferId, Vec<proto::Operation>>,
3493            needs_resync_with_host: &mut bool,
3494            is_local: bool,
3495            cx: &mut AsyncApp,
3496        ) -> Result<()> {
3497            for (buffer_id, operations) in operations_by_buffer_id.drain() {
3498                let request = this.read_with(cx, |this, _| {
3499                    let project_id = this.remote_id()?;
3500                    Some(this.collab_client.request(proto::UpdateBuffer {
3501                        buffer_id: buffer_id.into(),
3502                        project_id,
3503                        operations,
3504                    }))
3505                })?;
3506                if let Some(request) = request
3507                    && request.await.is_err()
3508                    && !is_local
3509                {
3510                    *needs_resync_with_host = true;
3511                    break;
3512                }
3513            }
3514            Ok(())
3515        }
3516
3517        let mut needs_resync_with_host = false;
3518        let mut changes = rx.ready_chunks(MAX_BATCH_SIZE);
3519
3520        while let Some(changes) = changes.next().await {
3521            let is_local = project.read_with(cx, |this, _| this.is_local())?;
3522
3523            for change in changes {
3524                match change {
3525                    BufferOrderedMessage::Operation {
3526                        buffer_id,
3527                        operation,
3528                    } => {
3529                        if needs_resync_with_host {
3530                            continue;
3531                        }
3532
3533                        operations_by_buffer_id
3534                            .entry(buffer_id)
3535                            .or_insert(Vec::new())
3536                            .push(operation);
3537                    }
3538
3539                    BufferOrderedMessage::Resync => {
3540                        operations_by_buffer_id.clear();
3541                        if project
3542                            .update(cx, |this, cx| this.synchronize_remote_buffers(cx))?
3543                            .await
3544                            .is_ok()
3545                        {
3546                            needs_resync_with_host = false;
3547                        }
3548                    }
3549
3550                    BufferOrderedMessage::LanguageServerUpdate {
3551                        language_server_id,
3552                        message,
3553                        name,
3554                    } => {
3555                        flush_operations(
3556                            &project,
3557                            &mut operations_by_buffer_id,
3558                            &mut needs_resync_with_host,
3559                            is_local,
3560                            cx,
3561                        )
3562                        .await?;
3563
3564                        project.read_with(cx, |project, _| {
3565                            if let Some(project_id) = project.remote_id() {
3566                                project
3567                                    .collab_client
3568                                    .send(proto::UpdateLanguageServer {
3569                                        project_id,
3570                                        server_name: name.map(|name| String::from(name.0)),
3571                                        language_server_id: language_server_id.to_proto(),
3572                                        variant: Some(message),
3573                                    })
3574                                    .log_err();
3575                            }
3576                        })?;
3577                    }
3578                }
3579            }
3580
3581            flush_operations(
3582                &project,
3583                &mut operations_by_buffer_id,
3584                &mut needs_resync_with_host,
3585                is_local,
3586                cx,
3587            )
3588            .await?;
3589        }
3590
3591        Ok(())
3592    }
3593
3594    fn on_buffer_store_event(
3595        &mut self,
3596        _: Entity<BufferStore>,
3597        event: &BufferStoreEvent,
3598        cx: &mut Context<Self>,
3599    ) {
3600        match event {
3601            BufferStoreEvent::BufferAdded(buffer) => {
3602                self.register_buffer(buffer, cx).log_err();
3603            }
3604            BufferStoreEvent::BufferDropped(buffer_id) => {
3605                if let Some(ref remote_client) = self.remote_client {
3606                    remote_client
3607                        .read(cx)
3608                        .proto_client()
3609                        .send(proto::CloseBuffer {
3610                            project_id: 0,
3611                            buffer_id: buffer_id.to_proto(),
3612                        })
3613                        .log_err();
3614                }
3615            }
3616            _ => {}
3617        }
3618    }
3619
3620    fn on_image_store_event(
3621        &mut self,
3622        _: Entity<ImageStore>,
3623        event: &ImageStoreEvent,
3624        cx: &mut Context<Self>,
3625    ) {
3626        match event {
3627            ImageStoreEvent::ImageAdded(image) => {
3628                cx.subscribe(image, |this, image, event, cx| {
3629                    this.on_image_event(image, event, cx);
3630                })
3631                .detach();
3632            }
3633        }
3634    }
3635
3636    fn on_dap_store_event(
3637        &mut self,
3638        _: Entity<DapStore>,
3639        event: &DapStoreEvent,
3640        cx: &mut Context<Self>,
3641    ) {
3642        if let DapStoreEvent::Notification(message) = event {
3643            cx.emit(Event::Toast {
3644                notification_id: "dap".into(),
3645                message: message.clone(),
3646                link: None,
3647            });
3648        }
3649    }
3650
3651    fn on_lsp_store_event(
3652        &mut self,
3653        _: Entity<LspStore>,
3654        event: &LspStoreEvent,
3655        cx: &mut Context<Self>,
3656    ) {
3657        match event {
3658            LspStoreEvent::DiagnosticsUpdated { server_id, paths } => {
3659                cx.emit(Event::DiagnosticsUpdated {
3660                    paths: paths.clone(),
3661                    language_server_id: *server_id,
3662                })
3663            }
3664            LspStoreEvent::LanguageServerAdded(server_id, name, worktree_id) => cx.emit(
3665                Event::LanguageServerAdded(*server_id, name.clone(), *worktree_id),
3666            ),
3667            LspStoreEvent::LanguageServerRemoved(server_id) => {
3668                cx.emit(Event::LanguageServerRemoved(*server_id))
3669            }
3670            LspStoreEvent::LanguageServerLog(server_id, log_type, string) => cx.emit(
3671                Event::LanguageServerLog(*server_id, log_type.clone(), string.clone()),
3672            ),
3673            LspStoreEvent::LanguageDetected {
3674                buffer,
3675                new_language,
3676            } => {
3677                let Some(_) = new_language else {
3678                    cx.emit(Event::LanguageNotFound(buffer.clone()));
3679                    return;
3680                };
3681            }
3682            LspStoreEvent::RefreshInlayHints { server_id } => cx.emit(Event::RefreshInlayHints {
3683                server_id: *server_id,
3684            }),
3685            LspStoreEvent::RefreshSemanticTokens { server_id } => {
3686                cx.emit(Event::RefreshSemanticTokens {
3687                    server_id: *server_id,
3688                })
3689            }
3690            LspStoreEvent::RefreshCodeLens { server_id } => cx.emit(Event::RefreshCodeLens {
3691                server_id: *server_id,
3692            }),
3693            LspStoreEvent::RefreshDocumentColors { server_id } => {
3694                cx.emit(Event::RefreshDocumentColors {
3695                    server_id: *server_id,
3696                })
3697            }
3698            LspStoreEvent::RefreshDocumentLinks { server_id } => {
3699                cx.emit(Event::RefreshDocumentLinks {
3700                    server_id: *server_id,
3701                })
3702            }
3703            LspStoreEvent::RefreshFoldingRanges { server_id } => {
3704                cx.emit(Event::RefreshFoldingRanges {
3705                    server_id: *server_id,
3706                })
3707            }
3708            LspStoreEvent::RefreshDocumentSymbols { server_id } => {
3709                cx.emit(Event::RefreshDocumentSymbols {
3710                    server_id: *server_id,
3711                })
3712            }
3713            LspStoreEvent::LanguageServerPrompt(prompt) => {
3714                cx.emit(Event::LanguageServerPrompt(prompt.clone()))
3715            }
3716            LspStoreEvent::DiskBasedDiagnosticsStarted { language_server_id } => {
3717                cx.emit(Event::DiskBasedDiagnosticsStarted {
3718                    language_server_id: *language_server_id,
3719                });
3720            }
3721            LspStoreEvent::DiskBasedDiagnosticsFinished { language_server_id } => {
3722                cx.emit(Event::DiskBasedDiagnosticsFinished {
3723                    language_server_id: *language_server_id,
3724                });
3725            }
3726            LspStoreEvent::LanguageServerUpdate {
3727                language_server_id,
3728                name,
3729                message,
3730            } => {
3731                if self.is_local() {
3732                    self.enqueue_buffer_ordered_message(
3733                        BufferOrderedMessage::LanguageServerUpdate {
3734                            language_server_id: *language_server_id,
3735                            message: message.clone(),
3736                            name: name.clone(),
3737                        },
3738                    )
3739                    .ok();
3740                }
3741
3742                match message {
3743                    proto::update_language_server::Variant::MetadataUpdated(update) => {
3744                        self.lsp_store.update(cx, |lsp_store, _| {
3745                            if let Some(capabilities) = update
3746                                .capabilities
3747                                .as_ref()
3748                                .and_then(|capabilities| serde_json::from_str(capabilities).ok())
3749                            {
3750                                lsp_store
3751                                    .lsp_server_capabilities
3752                                    .insert(*language_server_id, capabilities);
3753                            }
3754
3755                            if let Some(language_server_status) = lsp_store
3756                                .language_server_statuses
3757                                .get_mut(language_server_id)
3758                            {
3759                                if let Some(binary) = &update.binary {
3760                                    language_server_status.binary = Some(LanguageServerBinary {
3761                                        path: PathBuf::from(&binary.path),
3762                                        arguments: binary
3763                                            .arguments
3764                                            .iter()
3765                                            .map(OsString::from)
3766                                            .collect(),
3767                                        env: None,
3768                                    });
3769                                }
3770
3771                                language_server_status.configuration = update
3772                                    .configuration
3773                                    .as_ref()
3774                                    .and_then(|config_str| serde_json::from_str(config_str).ok());
3775
3776                                language_server_status.workspace_folders = update
3777                                    .workspace_folders
3778                                    .iter()
3779                                    .filter_map(|uri_str| lsp::Uri::from_str(uri_str).ok())
3780                                    .collect();
3781                            }
3782                        });
3783                    }
3784                    proto::update_language_server::Variant::RegisteredForBuffer(update) => {
3785                        if let Some(buffer_id) = BufferId::new(update.buffer_id).ok() {
3786                            cx.emit(Event::LanguageServerBufferRegistered {
3787                                buffer_id,
3788                                server_id: *language_server_id,
3789                                buffer_abs_path: PathBuf::from(&update.buffer_abs_path),
3790                                name: name.clone(),
3791                            });
3792                        }
3793                    }
3794                    _ => (),
3795                }
3796            }
3797            LspStoreEvent::Notification(message) => cx.emit(Event::Toast {
3798                notification_id: "lsp".into(),
3799                message: message.clone(),
3800                link: None,
3801            }),
3802            LspStoreEvent::SnippetEdit {
3803                buffer_id,
3804                edits,
3805                most_recent_edit,
3806            } => {
3807                if most_recent_edit.replica_id == self.replica_id() {
3808                    cx.emit(Event::SnippetEdit(*buffer_id, edits.clone()))
3809                }
3810            }
3811            LspStoreEvent::WorkspaceEditApplied(transaction) => {
3812                cx.emit(Event::WorkspaceEditApplied(transaction.clone()))
3813            }
3814        }
3815    }
3816
3817    fn on_remote_client_event(
3818        &mut self,
3819        _: Entity<RemoteClient>,
3820        event: &remote::RemoteClientEvent,
3821        cx: &mut Context<Self>,
3822    ) {
3823        match event {
3824            &remote::RemoteClientEvent::Disconnected { server_not_running } => {
3825                self.worktree_store.update(cx, |store, cx| {
3826                    store.disconnected_from_host(cx);
3827                });
3828                self.buffer_store.update(cx, |buffer_store, cx| {
3829                    buffer_store.disconnected_from_host(cx)
3830                });
3831                self.lsp_store.update(cx, |lsp_store, _cx| {
3832                    lsp_store.disconnected_from_ssh_remote()
3833                });
3834                cx.emit(Event::DisconnectedFromRemote { server_not_running });
3835            }
3836        }
3837    }
3838
3839    fn on_settings_observer_event(
3840        &mut self,
3841        _: Entity<SettingsObserver>,
3842        event: &SettingsObserverEvent,
3843        cx: &mut Context<Self>,
3844    ) {
3845        match event {
3846            SettingsObserverEvent::LocalSettingsUpdated(result) => match result {
3847                Err(InvalidSettingsError::LocalSettings { message, path }) => {
3848                    let message = format!("Failed to set local settings in {path:?}:\n{message}");
3849                    cx.emit(Event::Toast {
3850                        notification_id: format!("local-settings-{path:?}").into(),
3851                        link: None,
3852                        message,
3853                    });
3854                }
3855                Ok(path) => cx.emit(Event::HideToast {
3856                    notification_id: format!("local-settings-{path:?}").into(),
3857                }),
3858                Err(_) => {}
3859            },
3860            SettingsObserverEvent::LocalTasksUpdated(result) => match result {
3861                Err(InvalidSettingsError::Tasks { message, path }) => {
3862                    let message = format!("Failed to set local tasks in {path:?}:\n{message}");
3863                    cx.emit(Event::Toast {
3864                        notification_id: format!("local-tasks-{path:?}").into(),
3865                        link: Some(ToastLink {
3866                            label: "Open Tasks Documentation",
3867                            url: "https://zed.dev/docs/tasks",
3868                        }),
3869                        message,
3870                    });
3871                }
3872                Ok(path) => cx.emit(Event::HideToast {
3873                    notification_id: format!("local-tasks-{path:?}").into(),
3874                }),
3875                Err(_) => {}
3876            },
3877            SettingsObserverEvent::LocalDebugScenariosUpdated(result) => match result {
3878                Err(InvalidSettingsError::Debug { message, path }) => {
3879                    let message =
3880                        format!("Failed to set local debug scenarios in {path:?}:\n{message}");
3881                    cx.emit(Event::Toast {
3882                        notification_id: format!("local-debug-scenarios-{path:?}").into(),
3883                        link: None,
3884                        message,
3885                    });
3886                }
3887                Ok(path) => cx.emit(Event::HideToast {
3888                    notification_id: format!("local-debug-scenarios-{path:?}").into(),
3889                }),
3890                Err(_) => {}
3891            },
3892        }
3893    }
3894
3895    fn on_worktree_store_event(
3896        &mut self,
3897        _: Entity<WorktreeStore>,
3898        event: &WorktreeStoreEvent,
3899        cx: &mut Context<Self>,
3900    ) {
3901        match event {
3902            WorktreeStoreEvent::WorktreeAdded(worktree) => {
3903                self.on_worktree_added(worktree, cx);
3904                cx.emit(Event::WorktreeAdded(worktree.read(cx).id()));
3905                self.emit_group_key_changed_if_needed(cx);
3906            }
3907            WorktreeStoreEvent::WorktreeRemoved(_, id) => {
3908                cx.emit(Event::WorktreeRemoved(*id));
3909                self.emit_group_key_changed_if_needed(cx);
3910            }
3911            WorktreeStoreEvent::WorktreeReleased(_, id) => {
3912                self.on_worktree_released(*id, cx);
3913            }
3914            WorktreeStoreEvent::WorktreeOrderChanged => cx.emit(Event::WorktreeOrderChanged),
3915            WorktreeStoreEvent::WorktreeUpdateSent(_) => {}
3916            WorktreeStoreEvent::WorktreeUpdatedEntries(worktree_id, changes) => {
3917                self.client()
3918                    .telemetry()
3919                    .report_discovered_project_type_events(*worktree_id, changes);
3920                cx.emit(Event::WorktreeUpdatedEntries(*worktree_id, changes.clone()))
3921            }
3922            WorktreeStoreEvent::WorktreeDeletedEntry(worktree_id, id) => {
3923                cx.emit(Event::DeletedEntry(*worktree_id, *id))
3924            }
3925            // Listen to the GitStore instead.
3926            WorktreeStoreEvent::WorktreeUpdatedGitRepositories(_, _) => {}
3927            WorktreeStoreEvent::WorktreeUpdatedRootRepoCommonDir(worktree_id) => {
3928                cx.emit(Event::WorktreeUpdatedRootRepoCommonDir(*worktree_id));
3929                self.emit_group_key_changed_if_needed(cx);
3930            }
3931        }
3932    }
3933
3934    fn on_worktree_added(&mut self, worktree: &Entity<Worktree>, _: &mut Context<Self>) {
3935        let mut remotely_created_models = self.remotely_created_models.lock();
3936        if remotely_created_models.retain_count > 0 {
3937            remotely_created_models.worktrees.push(worktree.clone())
3938        }
3939    }
3940
3941    fn on_worktree_released(&mut self, id_to_remove: WorktreeId, cx: &mut Context<Self>) {
3942        if let Some(remote) = &self.remote_client {
3943            remote
3944                .read(cx)
3945                .proto_client()
3946                .send(proto::RemoveWorktree {
3947                    worktree_id: id_to_remove.to_proto(),
3948                })
3949                .log_err();
3950        }
3951    }
3952
3953    fn on_buffer_event(
3954        &mut self,
3955        buffer: Entity<Buffer>,
3956        event: &BufferEvent,
3957        cx: &mut Context<Self>,
3958    ) -> Option<()> {
3959        if matches!(event, BufferEvent::Edited { .. } | BufferEvent::Reloaded) {
3960            self.request_buffer_diff_recalculation(&buffer, cx);
3961        }
3962
3963        if let BufferEvent::Edited { source } = event {
3964            cx.emit(Event::BufferEdited { source: *source });
3965        }
3966
3967        let buffer_id = buffer.read(cx).remote_id();
3968        match event {
3969            BufferEvent::ReloadNeeded => {
3970                if !self.is_via_collab() {
3971                    self.reload_buffers([buffer.clone()].into_iter().collect(), true, cx)
3972                        .detach_and_log_err(cx);
3973                }
3974            }
3975            BufferEvent::Operation {
3976                operation,
3977                is_local: true,
3978            } => {
3979                let operation = language::proto::serialize_operation(operation);
3980
3981                if let Some(remote) = &self.remote_client {
3982                    remote
3983                        .read(cx)
3984                        .proto_client()
3985                        .send(proto::UpdateBuffer {
3986                            project_id: 0,
3987                            buffer_id: buffer_id.to_proto(),
3988                            operations: vec![operation.clone()],
3989                        })
3990                        .ok();
3991                }
3992
3993                self.enqueue_buffer_ordered_message(BufferOrderedMessage::Operation {
3994                    buffer_id,
3995                    operation,
3996                })
3997                .ok();
3998            }
3999
4000            _ => {}
4001        }
4002
4003        None
4004    }
4005
4006    fn on_image_event(
4007        &mut self,
4008        image: Entity<ImageItem>,
4009        event: &ImageItemEvent,
4010        cx: &mut Context<Self>,
4011    ) -> Option<()> {
4012        // TODO: handle image events from remote
4013        if let ImageItemEvent::ReloadNeeded = event
4014            && !self.is_via_collab()
4015        {
4016            self.reload_images([image].into_iter().collect(), cx)
4017                .detach_and_log_err(cx);
4018        }
4019
4020        None
4021    }
4022
4023    fn request_buffer_diff_recalculation(
4024        &mut self,
4025        buffer: &Entity<Buffer>,
4026        cx: &mut Context<Self>,
4027    ) {
4028        self.buffers_needing_diff.insert(buffer.downgrade());
4029        let first_insertion = self.buffers_needing_diff.len() == 1;
4030        let settings = ProjectSettings::get_global(cx);
4031        let delay = settings.git.gutter_debounce;
4032
4033        if delay == 0 {
4034            if first_insertion {
4035                let this = cx.weak_entity();
4036                cx.defer(move |cx| {
4037                    if let Some(this) = this.upgrade() {
4038                        this.update(cx, |this, cx| {
4039                            this.recalculate_buffer_diffs(cx).detach();
4040                        });
4041                    }
4042                });
4043            }
4044            return;
4045        }
4046
4047        const MIN_DELAY: u64 = 50;
4048        let delay = delay.max(MIN_DELAY);
4049        let duration = Duration::from_millis(delay);
4050
4051        self.git_diff_debouncer
4052            .fire_new(duration, cx, move |this, cx| {
4053                this.recalculate_buffer_diffs(cx)
4054            });
4055    }
4056
4057    fn recalculate_buffer_diffs(&mut self, cx: &mut Context<Self>) -> Task<()> {
4058        cx.spawn(async move |this, cx| {
4059            loop {
4060                let task = this
4061                    .update(cx, |this, cx| {
4062                        let buffers = this
4063                            .buffers_needing_diff
4064                            .drain()
4065                            .filter_map(|buffer| buffer.upgrade())
4066                            .collect::<Vec<_>>();
4067                        if buffers.is_empty() {
4068                            None
4069                        } else {
4070                            Some(this.git_store.update(cx, |git_store, cx| {
4071                                git_store.recalculate_buffer_diffs(buffers, cx)
4072                            }))
4073                        }
4074                    })
4075                    .ok()
4076                    .flatten();
4077
4078                if let Some(task) = task {
4079                    task.await;
4080                } else {
4081                    break;
4082                }
4083            }
4084        })
4085    }
4086
4087    pub fn set_language_for_buffer(
4088        &mut self,
4089        buffer: &Entity<Buffer>,
4090        new_language: Arc<Language>,
4091        cx: &mut Context<Self>,
4092    ) {
4093        self.lsp_store.update(cx, |lsp_store, cx| {
4094            lsp_store.set_language_for_buffer(buffer, new_language, cx)
4095        })
4096    }
4097
4098    pub fn restart_language_servers_for_buffers(
4099        &mut self,
4100        buffers: Vec<Entity<Buffer>>,
4101        only_restart_servers: HashSet<LanguageServerSelector>,
4102        clear_stopped: bool,
4103        cx: &mut Context<Self>,
4104    ) {
4105        self.lsp_store.update(cx, |lsp_store, cx| {
4106            lsp_store.restart_language_servers_for_buffers(
4107                buffers,
4108                only_restart_servers,
4109                clear_stopped,
4110                cx,
4111            )
4112        })
4113    }
4114
4115    pub fn stop_language_servers_for_buffers(
4116        &mut self,
4117        buffers: Vec<Entity<Buffer>>,
4118        also_restart_servers: HashSet<LanguageServerSelector>,
4119        cx: &mut Context<Self>,
4120    ) {
4121        self.lsp_store
4122            .update(cx, |lsp_store, cx| {
4123                lsp_store.stop_language_servers_for_buffers(buffers, also_restart_servers, cx)
4124            })
4125            .detach_and_log_err(cx);
4126    }
4127
4128    pub fn cancel_language_server_work_for_buffers(
4129        &mut self,
4130        buffers: impl IntoIterator<Item = Entity<Buffer>>,
4131        cx: &mut Context<Self>,
4132    ) {
4133        self.lsp_store.update(cx, |lsp_store, cx| {
4134            lsp_store.cancel_language_server_work_for_buffers(buffers, cx)
4135        })
4136    }
4137
4138    pub fn cancel_language_server_work(
4139        &mut self,
4140        server_id: LanguageServerId,
4141        token_to_cancel: Option<ProgressToken>,
4142        cx: &mut Context<Self>,
4143    ) {
4144        self.lsp_store.update(cx, |lsp_store, cx| {
4145            lsp_store.cancel_language_server_work(server_id, token_to_cancel, cx)
4146        })
4147    }
4148
4149    fn enqueue_buffer_ordered_message(&mut self, message: BufferOrderedMessage) -> Result<()> {
4150        self.buffer_ordered_messages_tx
4151            .unbounded_send(message)
4152            .map_err(|e| anyhow!(e))
4153    }
4154
4155    pub fn available_toolchains(
4156        &self,
4157        path: ProjectPath,
4158        language_name: LanguageName,
4159        cx: &App,
4160    ) -> Task<Option<Toolchains>> {
4161        if let Some(toolchain_store) = self.toolchain_store.as_ref().map(Entity::downgrade) {
4162            cx.spawn(async move |cx| {
4163                toolchain_store
4164                    .update(cx, |this, cx| this.list_toolchains(path, language_name, cx))
4165                    .ok()?
4166                    .await
4167            })
4168        } else {
4169            Task::ready(None)
4170        }
4171    }
4172
4173    pub async fn toolchain_metadata(
4174        languages: Arc<LanguageRegistry>,
4175        language_name: LanguageName,
4176    ) -> Option<ToolchainMetadata> {
4177        languages
4178            .language_for_name(language_name.as_ref())
4179            .await
4180            .ok()?
4181            .toolchain_lister()
4182            .map(|lister| lister.meta())
4183    }
4184
4185    pub fn add_toolchain(
4186        &self,
4187        toolchain: Toolchain,
4188        scope: ToolchainScope,
4189        cx: &mut Context<Self>,
4190    ) {
4191        maybe!({
4192            self.toolchain_store.as_ref()?.update(cx, |this, cx| {
4193                this.add_toolchain(toolchain, scope, cx);
4194            });
4195            Some(())
4196        });
4197    }
4198
4199    pub fn remove_toolchain(
4200        &self,
4201        toolchain: Toolchain,
4202        scope: ToolchainScope,
4203        cx: &mut Context<Self>,
4204    ) {
4205        maybe!({
4206            self.toolchain_store.as_ref()?.update(cx, |this, cx| {
4207                this.remove_toolchain(toolchain, scope, cx);
4208            });
4209            Some(())
4210        });
4211    }
4212
4213    pub fn user_toolchains(
4214        &self,
4215        cx: &App,
4216    ) -> Option<BTreeMap<ToolchainScope, IndexSet<Toolchain>>> {
4217        Some(self.toolchain_store.as_ref()?.read(cx).user_toolchains())
4218    }
4219
4220    pub fn resolve_toolchain(
4221        &self,
4222        path: PathBuf,
4223        language_name: LanguageName,
4224        cx: &App,
4225    ) -> Task<Result<Toolchain>> {
4226        if let Some(toolchain_store) = self.toolchain_store.as_ref().map(Entity::downgrade) {
4227            cx.spawn(async move |cx| {
4228                toolchain_store
4229                    .update(cx, |this, cx| {
4230                        this.resolve_toolchain(path, language_name, cx)
4231                    })?
4232                    .await
4233            })
4234        } else {
4235            Task::ready(Err(anyhow!("This project does not support toolchains")))
4236        }
4237    }
4238
4239    pub fn toolchain_store(&self) -> Option<Entity<ToolchainStore>> {
4240        self.toolchain_store.clone()
4241    }
4242    pub fn activate_toolchain(
4243        &self,
4244        path: ProjectPath,
4245        toolchain: Toolchain,
4246        cx: &mut App,
4247    ) -> Task<Option<()>> {
4248        let Some(toolchain_store) = self.toolchain_store.clone() else {
4249            return Task::ready(None);
4250        };
4251        toolchain_store.update(cx, |this, cx| this.activate_toolchain(path, toolchain, cx))
4252    }
4253    pub fn active_toolchain(
4254        &self,
4255        path: ProjectPath,
4256        language_name: LanguageName,
4257        cx: &App,
4258    ) -> Task<Option<Toolchain>> {
4259        let Some(toolchain_store) = self.toolchain_store.clone() else {
4260            return Task::ready(None);
4261        };
4262        toolchain_store
4263            .read(cx)
4264            .active_toolchain(path, language_name, cx)
4265    }
4266    pub fn language_server_statuses<'a>(
4267        &'a self,
4268        cx: &'a App,
4269    ) -> impl DoubleEndedIterator<Item = (LanguageServerId, &'a LanguageServerStatus)> {
4270        self.lsp_store.read(cx).language_server_statuses()
4271    }
4272
4273    pub fn last_formatting_failure<'a>(&self, cx: &'a App) -> Option<&'a str> {
4274        self.lsp_store.read(cx).last_formatting_failure()
4275    }
4276
4277    pub fn reset_last_formatting_failure(&self, cx: &mut App) {
4278        self.lsp_store
4279            .update(cx, |store, _| store.reset_last_formatting_failure());
4280    }
4281
4282    pub fn reload_buffers(
4283        &self,
4284        buffers: HashSet<Entity<Buffer>>,
4285        push_to_history: bool,
4286        cx: &mut Context<Self>,
4287    ) -> Task<Result<ProjectTransaction>> {
4288        self.buffer_store.update(cx, |buffer_store, cx| {
4289            buffer_store.reload_buffers(buffers, push_to_history, cx)
4290        })
4291    }
4292
4293    pub fn reload_images(
4294        &self,
4295        images: HashSet<Entity<ImageItem>>,
4296        cx: &mut Context<Self>,
4297    ) -> Task<Result<()>> {
4298        self.image_store
4299            .update(cx, |image_store, cx| image_store.reload_images(images, cx))
4300    }
4301
4302    pub fn format(
4303        &mut self,
4304        buffers: HashSet<Entity<Buffer>>,
4305        target: LspFormatTarget,
4306        push_to_history: bool,
4307        trigger: lsp_store::FormatTrigger,
4308        cx: &mut Context<Project>,
4309    ) -> Task<anyhow::Result<ProjectTransaction>> {
4310        self.lsp_store.update(cx, |lsp_store, cx| {
4311            lsp_store.format(buffers, target, push_to_history, trigger, cx)
4312        })
4313    }
4314
4315    pub fn supports_range_formatting(&self, buffer: &Entity<Buffer>, cx: &App) -> bool {
4316        self.lsp_store
4317            .read(cx)
4318            .supports_range_formatting(buffer, cx)
4319    }
4320
4321    pub fn definitions<T: ToPointUtf16>(
4322        &mut self,
4323        buffer: &Entity<Buffer>,
4324        position: T,
4325        cx: &mut Context<Self>,
4326    ) -> Task<Result<Option<Vec<LocationLink>>>> {
4327        let position = position.to_point_utf16(buffer.read(cx));
4328        let guard = self.retain_remotely_created_models(cx);
4329        let task = self.lsp_store.update(cx, |lsp_store, cx| {
4330            lsp_store.definitions(buffer, position, cx)
4331        });
4332        cx.background_spawn(async move {
4333            let result = task.await;
4334            drop(guard);
4335            result
4336        })
4337    }
4338
4339    pub fn edit_prediction_definitions<T: ToPointUtf16>(
4340        &mut self,
4341        buffer: &Entity<Buffer>,
4342        position: T,
4343        include_type_definitions: bool,
4344        cx: &mut Context<Self>,
4345    ) -> Task<Result<Vec<EditPredictionDefinition>>> {
4346        let position = position.to_point_utf16(buffer.read(cx));
4347        self.lsp_store.update(cx, |lsp_store, cx| {
4348            lsp_store.edit_prediction_definitions(buffer, position, include_type_definitions, cx)
4349        })
4350    }
4351
4352    pub fn declarations<T: ToPointUtf16>(
4353        &mut self,
4354        buffer: &Entity<Buffer>,
4355        position: T,
4356        cx: &mut Context<Self>,
4357    ) -> Task<Result<Option<Vec<LocationLink>>>> {
4358        let position = position.to_point_utf16(buffer.read(cx));
4359        let guard = self.retain_remotely_created_models(cx);
4360        let task = self.lsp_store.update(cx, |lsp_store, cx| {
4361            lsp_store.declarations(buffer, position, cx)
4362        });
4363        cx.background_spawn(async move {
4364            let result = task.await;
4365            drop(guard);
4366            result
4367        })
4368    }
4369
4370    pub fn type_definitions<T: ToPointUtf16>(
4371        &mut self,
4372        buffer: &Entity<Buffer>,
4373        position: T,
4374        cx: &mut Context<Self>,
4375    ) -> Task<Result<Option<Vec<LocationLink>>>> {
4376        let position = position.to_point_utf16(buffer.read(cx));
4377        let guard = self.retain_remotely_created_models(cx);
4378        let task = self.lsp_store.update(cx, |lsp_store, cx| {
4379            lsp_store.type_definitions(buffer, position, cx)
4380        });
4381        cx.background_spawn(async move {
4382            let result = task.await;
4383            drop(guard);
4384            result
4385        })
4386    }
4387
4388    pub fn implementations<T: ToPointUtf16>(
4389        &mut self,
4390        buffer: &Entity<Buffer>,
4391        position: T,
4392        cx: &mut Context<Self>,
4393    ) -> Task<Result<Option<Vec<LocationLink>>>> {
4394        let position = position.to_point_utf16(buffer.read(cx));
4395        let guard = self.retain_remotely_created_models(cx);
4396        let task = self.lsp_store.update(cx, |lsp_store, cx| {
4397            lsp_store.implementations(buffer, position, cx)
4398        });
4399        cx.background_spawn(async move {
4400            let result = task.await;
4401            drop(guard);
4402            result
4403        })
4404    }
4405
4406    pub fn references<T: ToPointUtf16>(
4407        &mut self,
4408        buffer: &Entity<Buffer>,
4409        position: T,
4410        cx: &mut Context<Self>,
4411    ) -> Task<Result<Option<Vec<Location>>>> {
4412        let position = position.to_point_utf16(buffer.read(cx));
4413        let guard = self.retain_remotely_created_models(cx);
4414        let task = self.lsp_store.update(cx, |lsp_store, cx| {
4415            lsp_store.references(buffer, position, cx)
4416        });
4417        cx.background_spawn(async move {
4418            let result = task.await;
4419            drop(guard);
4420            result
4421        })
4422    }
4423
4424    pub fn document_highlights<T: ToPointUtf16>(
4425        &mut self,
4426        buffer: &Entity<Buffer>,
4427        position: T,
4428        cx: &mut Context<Self>,
4429    ) -> Task<Result<Vec<DocumentHighlight>>> {
4430        let position = position.to_point_utf16(buffer.read(cx));
4431        self.request_lsp(
4432            buffer.clone(),
4433            LanguageServerToQuery::FirstCapable,
4434            GetDocumentHighlights { position },
4435            cx,
4436        )
4437    }
4438
4439    pub fn document_symbols(
4440        &mut self,
4441        buffer: &Entity<Buffer>,
4442        cx: &mut Context<Self>,
4443    ) -> Task<Result<Vec<DocumentSymbol>>> {
4444        self.request_lsp(
4445            buffer.clone(),
4446            LanguageServerToQuery::FirstCapable,
4447            GetDocumentSymbols,
4448            cx,
4449        )
4450    }
4451
4452    pub fn symbols(&self, query: &str, cx: &mut Context<Self>) -> Task<Result<Vec<Symbol>>> {
4453        self.lsp_store
4454            .update(cx, |lsp_store, cx| lsp_store.symbols(query, cx))
4455    }
4456
4457    pub fn open_buffer_for_symbol(
4458        &mut self,
4459        symbol: &Symbol,
4460        cx: &mut Context<Self>,
4461    ) -> Task<Result<Entity<Buffer>>> {
4462        self.lsp_store.update(cx, |lsp_store, cx| {
4463            lsp_store.open_buffer_for_symbol(symbol, cx)
4464        })
4465    }
4466
4467    pub fn open_server_settings(&mut self, cx: &mut Context<Self>) -> Task<Result<Entity<Buffer>>> {
4468        let guard = self.retain_remotely_created_models(cx);
4469        let Some(remote) = self.remote_client.as_ref() else {
4470            return Task::ready(Err(anyhow!("not an ssh project")));
4471        };
4472
4473        let proto_client = remote.read(cx).proto_client();
4474
4475        cx.spawn(async move |project, cx| {
4476            let buffer = proto_client
4477                .request(proto::OpenServerSettings {
4478                    project_id: REMOTE_SERVER_PROJECT_ID,
4479                })
4480                .await?;
4481
4482            let buffer = project
4483                .update(cx, |project, cx| {
4484                    project.buffer_store.update(cx, |buffer_store, cx| {
4485                        anyhow::Ok(
4486                            buffer_store
4487                                .wait_for_remote_buffer(BufferId::new(buffer.buffer_id)?, cx),
4488                        )
4489                    })
4490                })??
4491                .await;
4492
4493            drop(guard);
4494            buffer
4495        })
4496    }
4497
4498    pub fn open_local_buffer_via_lsp(
4499        &mut self,
4500        abs_path: lsp::Uri,
4501        language_server_id: LanguageServerId,
4502        cx: &mut Context<Self>,
4503    ) -> Task<Result<Entity<Buffer>>> {
4504        self.lsp_store.update(cx, |lsp_store, cx| {
4505            lsp_store.open_local_buffer_via_lsp(abs_path, language_server_id, cx)
4506        })
4507    }
4508
4509    pub fn hover<T: ToPointUtf16>(
4510        &self,
4511        buffer: &Entity<Buffer>,
4512        position: T,
4513        cx: &mut Context<Self>,
4514    ) -> Task<Option<Vec<Hover>>> {
4515        let position = position.to_point_utf16(buffer.read(cx));
4516        self.lsp_store
4517            .update(cx, |lsp_store, cx| lsp_store.hover(buffer, position, cx))
4518    }
4519
4520    pub fn linked_edits(
4521        &self,
4522        buffer: &Entity<Buffer>,
4523        position: Anchor,
4524        cx: &mut Context<Self>,
4525    ) -> Task<Result<Vec<Range<Anchor>>>> {
4526        self.lsp_store.update(cx, |lsp_store, cx| {
4527            lsp_store.linked_edits(buffer, position, cx)
4528        })
4529    }
4530
4531    pub fn completions<T: ToOffset + ToPointUtf16>(
4532        &self,
4533        buffer: &Entity<Buffer>,
4534        position: T,
4535        context: CompletionContext,
4536        cx: &mut Context<Self>,
4537    ) -> Task<Result<Vec<CompletionResponse>>> {
4538        let position = position.to_point_utf16(buffer.read(cx));
4539        self.lsp_store.update(cx, |lsp_store, cx| {
4540            lsp_store.completions(buffer, position, context, cx)
4541        })
4542    }
4543
4544    pub fn code_actions<T: Clone + ToOffset>(
4545        &mut self,
4546        buffer_handle: &Entity<Buffer>,
4547        range: Range<T>,
4548        kinds: Option<Vec<CodeActionKind>>,
4549        cx: &mut Context<Self>,
4550    ) -> Task<Result<Option<Vec<CodeAction>>>> {
4551        let buffer = buffer_handle.read(cx);
4552        let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
4553        self.lsp_store.update(cx, |lsp_store, cx| {
4554            lsp_store.code_actions(buffer_handle, range, kinds, cx)
4555        })
4556    }
4557
4558    pub fn apply_code_action(
4559        &self,
4560        buffer_handle: Entity<Buffer>,
4561        action: CodeAction,
4562        push_to_history: bool,
4563        cx: &mut Context<Self>,
4564    ) -> Task<Result<ProjectTransaction>> {
4565        self.lsp_store.update(cx, |lsp_store, cx| {
4566            lsp_store.apply_code_action(buffer_handle, action, push_to_history, cx)
4567        })
4568    }
4569
4570    pub fn apply_code_action_kind(
4571        &self,
4572        buffers: HashSet<Entity<Buffer>>,
4573        kind: CodeActionKind,
4574        push_to_history: bool,
4575        cx: &mut Context<Self>,
4576    ) -> Task<Result<ProjectTransaction>> {
4577        self.lsp_store.update(cx, |lsp_store, cx| {
4578            lsp_store.apply_code_action_kind(buffers, kind, push_to_history, cx)
4579        })
4580    }
4581
4582    pub fn prepare_rename<T: ToPointUtf16>(
4583        &mut self,
4584        buffer: Entity<Buffer>,
4585        position: T,
4586        cx: &mut Context<Self>,
4587    ) -> Task<Result<PrepareRenameResponse>> {
4588        let position = position.to_point_utf16(buffer.read(cx));
4589        self.request_lsp(
4590            buffer,
4591            LanguageServerToQuery::FirstCapable,
4592            PrepareRename { position },
4593            cx,
4594        )
4595    }
4596
4597    pub fn perform_rename<T: ToPointUtf16>(
4598        &mut self,
4599        buffer: Entity<Buffer>,
4600        position: T,
4601        new_name: String,
4602        cx: &mut Context<Self>,
4603    ) -> Task<Result<ProjectTransaction>> {
4604        let push_to_history = true;
4605        let position = position.to_point_utf16(buffer.read(cx));
4606        self.request_lsp(
4607            buffer,
4608            LanguageServerToQuery::FirstCapable,
4609            PerformRename {
4610                position,
4611                new_name,
4612                push_to_history,
4613            },
4614            cx,
4615        )
4616    }
4617
4618    pub fn on_type_format<T: ToPointUtf16>(
4619        &mut self,
4620        buffer: Entity<Buffer>,
4621        position: T,
4622        trigger: String,
4623        push_to_history: bool,
4624        cx: &mut Context<Self>,
4625    ) -> Task<Result<Option<Transaction>>> {
4626        self.lsp_store.update(cx, |lsp_store, cx| {
4627            lsp_store.on_type_format(buffer, position, trigger, push_to_history, cx)
4628        })
4629    }
4630
4631    pub fn inline_values(
4632        &mut self,
4633        session: Entity<Session>,
4634        active_stack_frame: ActiveStackFrame,
4635        buffer_handle: Entity<Buffer>,
4636        range: Range<text::Anchor>,
4637        cx: &mut Context<Self>,
4638    ) -> Task<anyhow::Result<Vec<InlayHint>>> {
4639        let snapshot = buffer_handle.read(cx).snapshot();
4640
4641        let captures =
4642            snapshot.debug_variables_query(Anchor::min_for_buffer(snapshot.remote_id())..range.end);
4643
4644        let row = snapshot
4645            .summary_for_anchor::<text::PointUtf16>(&range.end)
4646            .row as usize;
4647
4648        let inline_value_locations = provide_inline_values(captures, &snapshot, row);
4649
4650        let stack_frame_id = active_stack_frame.stack_frame_id;
4651        cx.spawn(async move |this, cx| {
4652            this.update(cx, |project, cx| {
4653                project.dap_store().update(cx, |dap_store, cx| {
4654                    dap_store.resolve_inline_value_locations(
4655                        session,
4656                        stack_frame_id,
4657                        buffer_handle,
4658                        inline_value_locations,
4659                        cx,
4660                    )
4661                })
4662            })?
4663            .await
4664        })
4665    }
4666
4667    fn search_impl(&mut self, query: SearchQuery, cx: &mut Context<Self>) -> SearchResultsHandle {
4668        let client: Option<(AnyProtoClient, _)> = if let Some(ssh_client) = &self.remote_client {
4669            Some((ssh_client.read(cx).proto_client(), 0))
4670        } else if let Some(remote_id) = self.remote_id() {
4671            self.is_local()
4672                .not()
4673                .then(|| (self.collab_client.clone().into(), remote_id))
4674        } else {
4675            None
4676        };
4677        let searcher = if query.is_opened_only() {
4678            project_search::Search::open_buffers_only(
4679                self.buffer_store.clone(),
4680                self.worktree_store.clone(),
4681                project_search::Search::MAX_SEARCH_RESULT_FILES + 1,
4682            )
4683        } else {
4684            match client {
4685                Some((client, remote_id)) => project_search::Search::remote(
4686                    self.buffer_store.clone(),
4687                    self.worktree_store.clone(),
4688                    project_search::Search::MAX_SEARCH_RESULT_FILES + 1,
4689                    (client, remote_id, self.remotely_created_models.clone()),
4690                ),
4691                None => project_search::Search::local(
4692                    self.fs.clone(),
4693                    self.buffer_store.clone(),
4694                    self.worktree_store.clone(),
4695                    project_search::Search::MAX_SEARCH_RESULT_FILES + 1,
4696                    cx,
4697                ),
4698            }
4699        };
4700        searcher.into_handle(query, cx)
4701    }
4702
4703    pub fn search(
4704        &mut self,
4705        query: SearchQuery,
4706        cx: &mut Context<Self>,
4707    ) -> SearchResults<SearchResult> {
4708        self.search_impl(query, cx).results(cx)
4709    }
4710
4711    pub fn request_lsp<R: LspCommand>(
4712        &mut self,
4713        buffer_handle: Entity<Buffer>,
4714        server: LanguageServerToQuery,
4715        request: R,
4716        cx: &mut Context<Self>,
4717    ) -> Task<Result<R::Response>>
4718    where
4719        <R::LspRequest as lsp::request::Request>::Result: Send,
4720        <R::LspRequest as lsp::request::Request>::Params: Send,
4721    {
4722        let guard = self.retain_remotely_created_models(cx);
4723        let task = self.lsp_store.update(cx, |lsp_store, cx| {
4724            lsp_store.request_lsp(buffer_handle, server, request, cx)
4725        });
4726        cx.background_spawn(async move {
4727            let result = task.await;
4728            drop(guard);
4729            result
4730        })
4731    }
4732
4733    /// Move a worktree to a new position in the worktree order.
4734    ///
4735    /// The worktree will moved to the opposite side of the destination worktree.
4736    ///
4737    /// # Example
4738    ///
4739    /// Given the worktree order `[11, 22, 33]` and a call to move worktree `22` to `33`,
4740    /// worktree_order will be updated to produce the indexes `[11, 33, 22]`.
4741    ///
4742    /// Given the worktree order `[11, 22, 33]` and a call to move worktree `22` to `11`,
4743    /// worktree_order will be updated to produce the indexes `[22, 11, 33]`.
4744    ///
4745    /// # Errors
4746    ///
4747    /// An error will be returned if the worktree or destination worktree are not found.
4748    pub fn move_worktree(
4749        &mut self,
4750        source: WorktreeId,
4751        destination: WorktreeId,
4752        cx: &mut Context<Self>,
4753    ) -> Result<()> {
4754        self.worktree_store.update(cx, |worktree_store, cx| {
4755            worktree_store.move_worktree(source, destination, cx)
4756        })
4757    }
4758
4759    /// Attempts to convert the input path to a WSL path if this is a wsl remote project and the input path is a host windows path.
4760    pub fn try_windows_path_to_wsl(
4761        &self,
4762        abs_path: &Path,
4763        cx: &App,
4764    ) -> impl Future<Output = Result<PathBuf>> + use<> {
4765        let fut = if cfg!(windows)
4766            && let (
4767                ProjectClientState::Local | ProjectClientState::Shared { .. },
4768                Some(remote_client),
4769            ) = (&self.client_state, &self.remote_client)
4770            && let RemoteConnectionOptions::Wsl(wsl) = remote_client.read(cx).connection_options()
4771        {
4772            Either::Left(wsl.abs_windows_path_to_wsl_path(abs_path))
4773        } else {
4774            Either::Right(abs_path.to_owned())
4775        };
4776        async move {
4777            match fut {
4778                Either::Left(fut) => fut.await.map(Into::into),
4779                Either::Right(path) => Ok(path),
4780            }
4781        }
4782    }
4783
4784    pub fn find_or_create_worktree(
4785        &mut self,
4786        abs_path: impl AsRef<Path>,
4787        visible: bool,
4788        cx: &mut Context<Self>,
4789    ) -> Task<Result<(Entity<Worktree>, Arc<RelPath>)>> {
4790        self.worktree_store.update(cx, |worktree_store, cx| {
4791            worktree_store.find_or_create_worktree(abs_path, visible, cx)
4792        })
4793    }
4794
4795    pub fn find_worktree(
4796        &self,
4797        abs_path: &Path,
4798        cx: &App,
4799    ) -> Option<(Entity<Worktree>, Arc<RelPath>)> {
4800        self.worktree_store.read(cx).find_worktree(abs_path, cx)
4801    }
4802
4803    pub fn is_shared(&self) -> bool {
4804        match &self.client_state {
4805            ProjectClientState::Shared { .. } => true,
4806            ProjectClientState::Local => false,
4807            ProjectClientState::Collab { .. } => true,
4808        }
4809    }
4810
4811    /// Returns the resolved version of `path`, that was found in `buffer`, if it exists.
4812    pub fn resolve_path_in_buffer(
4813        &self,
4814        path: &str,
4815        buffer: &Entity<Buffer>,
4816        cx: &mut Context<Self>,
4817    ) -> Task<Option<ResolvedPath>> {
4818        if util::paths::is_absolute(path, self.path_style(cx)) || path.starts_with("~") {
4819            self.resolve_abs_path(path, cx)
4820        } else {
4821            self.resolve_path_in_worktrees(path, buffer, cx)
4822        }
4823    }
4824
4825    pub fn resolve_abs_file_path(
4826        &self,
4827        path: &str,
4828        cx: &mut Context<Self>,
4829    ) -> Task<Option<ResolvedPath>> {
4830        let resolve_task = self.resolve_abs_path(path, cx);
4831        cx.background_spawn(async move {
4832            let resolved_path = resolve_task.await;
4833            resolved_path.filter(|path| path.is_file())
4834        })
4835    }
4836
4837    pub fn resolve_abs_path(&self, path: &str, cx: &App) -> Task<Option<ResolvedPath>> {
4838        if self.is_local() {
4839            let expanded = PathBuf::from(shellexpand::tilde(&path).into_owned());
4840            let fs = self.fs.clone();
4841            cx.background_spawn(async move {
4842                let metadata = fs.metadata(&expanded).await.ok().flatten();
4843
4844                metadata.map(|metadata| ResolvedPath::AbsPath {
4845                    path: expanded.to_string_lossy().into_owned(),
4846                    is_dir: metadata.is_dir,
4847                })
4848            })
4849        } else if let Some(ssh_client) = self.remote_client.as_ref() {
4850            let request = ssh_client
4851                .read(cx)
4852                .proto_client()
4853                .request(proto::GetPathMetadata {
4854                    project_id: REMOTE_SERVER_PROJECT_ID,
4855                    path: path.into(),
4856                });
4857            cx.background_spawn(async move {
4858                let response = request.await.log_err()?;
4859                if response.exists {
4860                    Some(ResolvedPath::AbsPath {
4861                        path: response.path,
4862                        is_dir: response.is_dir,
4863                    })
4864                } else {
4865                    None
4866                }
4867            })
4868        } else {
4869            Task::ready(None)
4870        }
4871    }
4872
4873    fn resolve_path_in_worktrees(
4874        &self,
4875        path: &str,
4876        buffer: &Entity<Buffer>,
4877        cx: &mut Context<Self>,
4878    ) -> Task<Option<ResolvedPath>> {
4879        let mut candidates = vec![];
4880        let path_style = self.path_style(cx);
4881        if let Ok(path) = RelPath::new(path.as_ref(), path_style) {
4882            candidates.push(path.into_arc());
4883        }
4884
4885        if let Some(file) = buffer.read(cx).file()
4886            && let Some(dir) = file.path().parent()
4887        {
4888            if let Some(joined) = path_style.join(&*dir.display(path_style), path)
4889                && let Some(joined) = RelPath::new(joined.as_ref(), path_style).ok()
4890            {
4891                candidates.push(joined.into_arc());
4892            }
4893        }
4894
4895        let buffer_worktree_id = buffer.read(cx).file().map(|file| file.worktree_id(cx));
4896        let worktrees_with_ids: Vec<_> = self
4897            .worktrees(cx)
4898            .map(|worktree| {
4899                let id = worktree.read(cx).id();
4900                (worktree, id)
4901            })
4902            .collect();
4903
4904        cx.spawn(async move |_, cx| {
4905            if let Some(buffer_worktree_id) = buffer_worktree_id
4906                && let Some((worktree, _)) = worktrees_with_ids
4907                    .iter()
4908                    .find(|(_, id)| *id == buffer_worktree_id)
4909            {
4910                for candidate in candidates.iter() {
4911                    if let Some(path) = Self::resolve_path_in_worktree(worktree, candidate, cx) {
4912                        return Some(path);
4913                    }
4914                }
4915            }
4916            for (worktree, id) in worktrees_with_ids {
4917                if Some(id) == buffer_worktree_id {
4918                    continue;
4919                }
4920                for candidate in candidates.iter() {
4921                    if let Some(path) = Self::resolve_path_in_worktree(&worktree, candidate, cx) {
4922                        return Some(path);
4923                    }
4924                }
4925            }
4926            None
4927        })
4928    }
4929
4930    fn resolve_path_in_worktree(
4931        worktree: &Entity<Worktree>,
4932        path: &RelPath,
4933        cx: &mut AsyncApp,
4934    ) -> Option<ResolvedPath> {
4935        worktree.read_with(cx, |worktree, _| {
4936            worktree.entry_for_path(path).map(|entry| {
4937                let project_path = ProjectPath {
4938                    worktree_id: worktree.id(),
4939                    path: entry.path.clone(),
4940                };
4941                ResolvedPath::ProjectPath {
4942                    project_path,
4943                    is_dir: entry.is_dir(),
4944                }
4945            })
4946        })
4947    }
4948
4949    pub fn list_directory(
4950        &self,
4951        query: String,
4952        cx: &mut Context<Self>,
4953    ) -> Task<Result<Vec<DirectoryItem>>> {
4954        if self.is_local() {
4955            DirectoryLister::Local(cx.entity(), self.fs.clone()).list_directory(query, cx)
4956        } else if let Some(session) = self.remote_client.as_ref() {
4957            let request = proto::ListRemoteDirectory {
4958                dev_server_id: REMOTE_SERVER_PROJECT_ID,
4959                path: query,
4960                config: Some(proto::ListRemoteDirectoryConfig { is_dir: true }),
4961            };
4962
4963            let response = session.read(cx).proto_client().request(request);
4964            cx.background_spawn(async move {
4965                let proto::ListRemoteDirectoryResponse {
4966                    entries,
4967                    entry_info,
4968                } = response.await?;
4969                Ok(entries
4970                    .into_iter()
4971                    .zip(entry_info)
4972                    .map(|(entry, info)| DirectoryItem {
4973                        path: PathBuf::from(entry),
4974                        is_dir: info.is_dir,
4975                    })
4976                    .collect())
4977            })
4978        } else {
4979            Task::ready(Err(anyhow!("cannot list directory in remote project")))
4980        }
4981    }
4982
4983    pub fn create_worktree(
4984        &mut self,
4985        abs_path: impl AsRef<Path>,
4986        visible: bool,
4987        cx: &mut Context<Self>,
4988    ) -> Task<Result<Entity<Worktree>>> {
4989        self.worktree_store.update(cx, |worktree_store, cx| {
4990            worktree_store.create_worktree(abs_path, visible, cx)
4991        })
4992    }
4993
4994    /// Returns a task that resolves when the given worktree's `Entity` is
4995    /// fully dropped (all strong references released), not merely when
4996    /// `remove_worktree` is called. `remove_worktree` drops the store's
4997    /// reference and emits `WorktreeRemoved`, but other code may still
4998    /// hold a strong handle — the worktree isn't safe to delete from
4999    /// disk until every handle is gone.
5000    ///
5001    /// We use `observe_release` on the specific entity rather than
5002    /// listening for `WorktreeReleased` events because it's simpler at
5003    /// the call site (one awaitable task, no subscription / channel /
5004    /// ID filtering).
5005    pub fn wait_for_worktree_release(
5006        &mut self,
5007        worktree_id: WorktreeId,
5008        cx: &mut Context<Self>,
5009    ) -> Task<Result<()>> {
5010        let Some(worktree) = self.worktree_for_id(worktree_id, cx) else {
5011            return Task::ready(Ok(()));
5012        };
5013
5014        let (released_tx, released_rx) = futures::channel::oneshot::channel();
5015        let released_tx = std::sync::Arc::new(Mutex::new(Some(released_tx)));
5016        let release_subscription =
5017            cx.observe_release(&worktree, move |_project, _released_worktree, _cx| {
5018                if let Some(released_tx) = released_tx.lock().take() {
5019                    let _ = released_tx.send(());
5020                }
5021            });
5022
5023        cx.spawn(async move |_project, _cx| {
5024            let _release_subscription = release_subscription;
5025            released_rx
5026                .await
5027                .map_err(|_| anyhow!("worktree release observer dropped before release"))?;
5028            Ok(())
5029        })
5030    }
5031
5032    pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut Context<Self>) {
5033        self.worktree_store.update(cx, |worktree_store, cx| {
5034            worktree_store.remove_worktree(id_to_remove, cx);
5035        });
5036    }
5037
5038    pub fn remove_worktree_for_main_worktree_path(
5039        &mut self,
5040        path: impl AsRef<Path>,
5041        cx: &mut Context<Self>,
5042    ) {
5043        let path = path.as_ref();
5044        self.worktree_store.update(cx, |worktree_store, cx| {
5045            if let Some(worktree) = worktree_store.worktree_for_main_worktree_path(path, cx) {
5046                worktree_store.remove_worktree(worktree.read(cx).id(), cx);
5047            }
5048        });
5049    }
5050
5051    fn add_worktree(&mut self, worktree: &Entity<Worktree>, cx: &mut Context<Self>) {
5052        self.worktree_store.update(cx, |worktree_store, cx| {
5053            worktree_store.add(worktree, cx);
5054        });
5055    }
5056
5057    pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut Context<Self>) {
5058        let new_active_entry = entry.and_then(|project_path| {
5059            let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
5060            let entry = worktree.read(cx).entry_for_path(&project_path.path)?;
5061            Some(entry.id)
5062        });
5063        if new_active_entry != self.active_entry {
5064            self.active_entry = new_active_entry;
5065            self.lsp_store.update(cx, |lsp_store, _| {
5066                lsp_store.set_active_entry(new_active_entry);
5067            });
5068            cx.emit(Event::ActiveEntryChanged(new_active_entry));
5069        }
5070    }
5071
5072    pub fn language_servers_running_disk_based_diagnostics<'a>(
5073        &'a self,
5074        cx: &'a App,
5075    ) -> impl Iterator<Item = LanguageServerId> + 'a {
5076        self.lsp_store
5077            .read(cx)
5078            .language_servers_running_disk_based_diagnostics()
5079    }
5080
5081    pub fn diagnostic_summary(&self, include_ignored: bool, cx: &App) -> DiagnosticSummary {
5082        self.lsp_store
5083            .read(cx)
5084            .diagnostic_summary(include_ignored, cx)
5085    }
5086
5087    /// Returns a summary of the diagnostics for the provided project path only.
5088    pub fn diagnostic_summary_for_path(&self, path: &ProjectPath, cx: &App) -> DiagnosticSummary {
5089        self.lsp_store
5090            .read(cx)
5091            .diagnostic_summary_for_path(path, cx)
5092    }
5093
5094    pub fn diagnostic_summaries<'a>(
5095        &'a self,
5096        include_ignored: bool,
5097        cx: &'a App,
5098    ) -> impl Iterator<Item = (ProjectPath, LanguageServerId, DiagnosticSummary)> + 'a {
5099        self.lsp_store
5100            .read(cx)
5101            .diagnostic_summaries(include_ignored, cx)
5102    }
5103
5104    pub fn active_entry(&self) -> Option<ProjectEntryId> {
5105        self.active_entry
5106    }
5107
5108    pub fn entry_for_path<'a>(&'a self, path: &ProjectPath, cx: &'a App) -> Option<&'a Entry> {
5109        self.worktree_store.read(cx).entry_for_path(path, cx)
5110    }
5111
5112    pub fn path_for_entry(&self, entry_id: ProjectEntryId, cx: &App) -> Option<ProjectPath> {
5113        let worktree = self.worktree_for_entry(entry_id, cx)?;
5114        let worktree = worktree.read(cx);
5115        let worktree_id = worktree.id();
5116        let path = worktree.entry_for_id(entry_id)?.path.clone();
5117        Some(ProjectPath { worktree_id, path })
5118    }
5119
5120    pub fn absolute_path(&self, project_path: &ProjectPath, cx: &App) -> Option<PathBuf> {
5121        Some(
5122            self.worktree_for_id(project_path.worktree_id, cx)?
5123                .read(cx)
5124                .absolutize(&project_path.path),
5125        )
5126    }
5127
5128    /// Attempts to find a `ProjectPath` corresponding to the given path. If the path
5129    /// is a *full path*, meaning it starts with the root name of a worktree, we'll locate
5130    /// it in that worktree. Otherwise, we'll attempt to find it as a relative path in
5131    /// the first visible worktree that has an entry for that relative path.
5132    ///
5133    /// We use this to resolve edit steps, when there's a chance an LLM may omit the workree
5134    /// root name from paths.
5135    ///
5136    /// # Arguments
5137    ///
5138    /// * `path` - An absolute path, or a full path that starts with a worktree root name, or a
5139    ///   relative path within a visible worktree.
5140    /// * `cx` - A reference to the `AppContext`.
5141    ///
5142    /// # Returns
5143    ///
5144    /// Returns `Some(ProjectPath)` if a matching worktree is found, otherwise `None`.
5145    pub fn find_project_path(&self, path: impl AsRef<Path>, cx: &App) -> Option<ProjectPath> {
5146        let path_style = self.path_style(cx);
5147        let path = path.as_ref();
5148        let worktree_store = self.worktree_store.read(cx);
5149
5150        if is_absolute(&path.to_string_lossy(), path_style) {
5151            for worktree in worktree_store.visible_worktrees(cx) {
5152                let worktree_abs_path = worktree.read(cx).abs_path();
5153
5154                if let Ok(relative_path) = path.strip_prefix(worktree_abs_path)
5155                    && let Ok(path) = RelPath::new(relative_path, path_style)
5156                {
5157                    return Some(ProjectPath {
5158                        worktree_id: worktree.read(cx).id(),
5159                        path: path.into_arc(),
5160                    });
5161                }
5162            }
5163        } else {
5164            // First pass: for each worktree, try two interpretations of the path and
5165            // return whichever finds an existing entry first:
5166            //   (a) Strip the worktree root name as a prefix.
5167            //   (b) Treat the path as a literal worktree-relative path.
5168            for worktree in worktree_store.visible_worktrees(cx) {
5169                let worktree = worktree.read(cx);
5170                if let Ok(relative_path) = path.strip_prefix(worktree.root_name().as_std_path())
5171                    && let Ok(rel_path) = RelPath::new(relative_path, path_style)
5172                    && let Some(entry) = worktree.entry_for_path(&rel_path)
5173                {
5174                    return Some(ProjectPath {
5175                        worktree_id: worktree.id(),
5176                        path: entry.path.clone(),
5177                    });
5178                }
5179                if let Ok(rel_path) = RelPath::new(path, path_style)
5180                    && let Some(entry) = worktree.entry_for_path(&rel_path)
5181                {
5182                    return Some(ProjectPath {
5183                        worktree_id: worktree.id(),
5184                        path: entry.path.clone(),
5185                    });
5186                }
5187            }
5188
5189            // Second pass: strip the worktree root name prefix without requiring the
5190            // entry to exist, to allow resolving paths that don't exist yet.
5191            for worktree in worktree_store.visible_worktrees(cx) {
5192                let worktree_root_name = worktree.read(cx).root_name();
5193                if let Ok(relative_path) = path.strip_prefix(worktree_root_name.as_std_path())
5194                    && let Ok(path) = RelPath::new(relative_path, path_style)
5195                {
5196                    return Some(ProjectPath {
5197                        worktree_id: worktree.read(cx).id(),
5198                        path: path.into_arc(),
5199                    });
5200                }
5201            }
5202        }
5203
5204        None
5205    }
5206
5207    /// If there's only one visible worktree, returns the given worktree-relative path with no prefix.
5208    ///
5209    /// Otherwise, returns the full path for the project path (obtained by prefixing the worktree-relative path with the name of the worktree).
5210    pub fn short_full_path_for_project_path(
5211        &self,
5212        project_path: &ProjectPath,
5213        cx: &App,
5214    ) -> Option<String> {
5215        let path_style = self.path_style(cx);
5216        if self.visible_worktrees(cx).take(2).count() < 2 {
5217            return Some(project_path.path.display(path_style).to_string());
5218        }
5219        self.worktree_for_id(project_path.worktree_id, cx)
5220            .map(|worktree| {
5221                let worktree_name = worktree.read(cx).root_name();
5222                worktree_name
5223                    .join(&project_path.path)
5224                    .display(path_style)
5225                    .to_string()
5226            })
5227    }
5228
5229    pub fn project_path_for_absolute_path(&self, abs_path: &Path, cx: &App) -> Option<ProjectPath> {
5230        self.worktree_store
5231            .read(cx)
5232            .project_path_for_absolute_path(abs_path, cx)
5233    }
5234
5235    pub fn get_workspace_root(&self, project_path: &ProjectPath, cx: &App) -> Option<PathBuf> {
5236        Some(
5237            self.worktree_for_id(project_path.worktree_id, cx)?
5238                .read(cx)
5239                .abs_path()
5240                .to_path_buf(),
5241        )
5242    }
5243
5244    pub fn blame_buffer(
5245        &self,
5246        buffer: &Entity<Buffer>,
5247        version: Option<clock::Global>,
5248        cx: &mut App,
5249    ) -> Task<Result<Option<Blame>>> {
5250        self.git_store.update(cx, |git_store, cx| {
5251            git_store.blame_buffer(buffer, version, cx)
5252        })
5253    }
5254
5255    pub fn get_permalink_to_line(
5256        &self,
5257        buffer: &Entity<Buffer>,
5258        selection: Range<u32>,
5259        cx: &mut App,
5260    ) -> Task<Result<url::Url>> {
5261        self.git_store.update(cx, |git_store, cx| {
5262            git_store.get_permalink_to_line(buffer, selection, cx)
5263        })
5264    }
5265
5266    // RPC message handlers
5267
5268    async fn handle_unshare_project(
5269        this: Entity<Self>,
5270        _: TypedEnvelope<proto::UnshareProject>,
5271        mut cx: AsyncApp,
5272    ) -> Result<()> {
5273        this.update(&mut cx, |this, cx| {
5274            if this.is_local() || this.is_via_remote_server() {
5275                this.unshare(cx)?;
5276            } else {
5277                this.disconnected_from_host(cx);
5278            }
5279            Ok(())
5280        })
5281    }
5282
5283    async fn handle_add_collaborator(
5284        this: Entity<Self>,
5285        mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
5286        mut cx: AsyncApp,
5287    ) -> Result<()> {
5288        let collaborator = envelope
5289            .payload
5290            .collaborator
5291            .take()
5292            .context("empty collaborator")?;
5293
5294        let collaborator = Collaborator::from_proto(collaborator)?;
5295        this.update(&mut cx, |this, cx| {
5296            this.buffer_store.update(cx, |buffer_store, _| {
5297                buffer_store.forget_shared_buffers_for(&collaborator.peer_id);
5298            });
5299            this.breakpoint_store.read(cx).broadcast();
5300            cx.emit(Event::CollaboratorJoined(collaborator.peer_id));
5301            this.collaborators
5302                .insert(collaborator.peer_id, collaborator);
5303        });
5304
5305        Ok(())
5306    }
5307
5308    async fn handle_update_project_collaborator(
5309        this: Entity<Self>,
5310        envelope: TypedEnvelope<proto::UpdateProjectCollaborator>,
5311        mut cx: AsyncApp,
5312    ) -> Result<()> {
5313        let old_peer_id = envelope
5314            .payload
5315            .old_peer_id
5316            .context("missing old peer id")?;
5317        let new_peer_id = envelope
5318            .payload
5319            .new_peer_id
5320            .context("missing new peer id")?;
5321        this.update(&mut cx, |this, cx| {
5322            let collaborator = this
5323                .collaborators
5324                .remove(&old_peer_id)
5325                .context("received UpdateProjectCollaborator for unknown peer")?;
5326            let is_host = collaborator.is_host;
5327            this.collaborators.insert(new_peer_id, collaborator);
5328
5329            log::info!("peer {} became {}", old_peer_id, new_peer_id,);
5330            this.buffer_store.update(cx, |buffer_store, _| {
5331                buffer_store.update_peer_id(&old_peer_id, new_peer_id)
5332            });
5333
5334            if is_host {
5335                this.buffer_store
5336                    .update(cx, |buffer_store, _| buffer_store.discard_incomplete());
5337                this.enqueue_buffer_ordered_message(BufferOrderedMessage::Resync)
5338                    .unwrap();
5339                cx.emit(Event::HostReshared);
5340            }
5341
5342            cx.emit(Event::CollaboratorUpdated {
5343                old_peer_id,
5344                new_peer_id,
5345            });
5346            Ok(())
5347        })
5348    }
5349
5350    async fn handle_remove_collaborator(
5351        this: Entity<Self>,
5352        envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
5353        mut cx: AsyncApp,
5354    ) -> Result<()> {
5355        this.update(&mut cx, |this, cx| {
5356            let peer_id = envelope.payload.peer_id.context("invalid peer id")?;
5357            let replica_id = this
5358                .collaborators
5359                .remove(&peer_id)
5360                .with_context(|| format!("unknown peer {peer_id:?}"))?
5361                .replica_id;
5362            this.buffer_store.update(cx, |buffer_store, cx| {
5363                buffer_store.forget_shared_buffers_for(&peer_id);
5364                for buffer in buffer_store.buffers() {
5365                    buffer.update(cx, |buffer, cx| buffer.remove_peer(replica_id, cx));
5366                }
5367            });
5368            this.git_store.update(cx, |git_store, _| {
5369                git_store.forget_shared_diffs_for(&peer_id);
5370            });
5371
5372            cx.emit(Event::CollaboratorLeft(peer_id));
5373            Ok(())
5374        })
5375    }
5376
5377    async fn handle_update_project(
5378        this: Entity<Self>,
5379        envelope: TypedEnvelope<proto::UpdateProject>,
5380        mut cx: AsyncApp,
5381    ) -> Result<()> {
5382        this.update(&mut cx, |this, cx| {
5383            // Don't handle messages that were sent before the response to us joining the project
5384            if envelope.message_id > this.join_project_response_message_id {
5385                cx.update_global::<SettingsStore, _>(|store, cx| {
5386                    for worktree_metadata in &envelope.payload.worktrees {
5387                        store
5388                            .clear_local_settings(WorktreeId::from_proto(worktree_metadata.id), cx)
5389                            .log_err();
5390                    }
5391                });
5392
5393                this.set_worktrees_from_proto(envelope.payload.worktrees, cx)?;
5394            }
5395            Ok(())
5396        })
5397    }
5398
5399    async fn handle_toast(
5400        this: Entity<Self>,
5401        envelope: TypedEnvelope<proto::Toast>,
5402        mut cx: AsyncApp,
5403    ) -> Result<()> {
5404        this.update(&mut cx, |_, cx| {
5405            cx.emit(Event::Toast {
5406                notification_id: envelope.payload.notification_id.into(),
5407                message: envelope.payload.message,
5408                link: None,
5409            });
5410            Ok(())
5411        })
5412    }
5413
5414    async fn handle_telemetry_event(
5415        this: Entity<Self>,
5416        envelope: TypedEnvelope<proto::TelemetryEvent>,
5417        mut cx: AsyncApp,
5418    ) -> Result<()> {
5419        let payload = envelope.payload;
5420        this.update(&mut cx, |this, cx| {
5421            // The remote connection type, OS, version, and architecture are all
5422            // already known from connection setup, so they don't need to be sent
5423            // with each event.
5424            let Some((connection_type, platform, os_version)) =
5425                this.remote_client.as_ref().map(|client| {
5426                    let client = client.read(cx);
5427                    (
5428                        client.connection_type(),
5429                        client.remote_platform(),
5430                        client.remote_os_version(),
5431                    )
5432                })
5433            else {
5434                return;
5435            };
5436            this.client()
5437                .telemetry()
5438                .report_remote_event(
5439                    &payload.event_json,
5440                    connection_type,
5441                    platform.os.display_name().to_string(),
5442                    os_version,
5443                    platform.arch.as_str().to_string(),
5444                )
5445                .log_err();
5446        });
5447        Ok(())
5448    }
5449
5450    async fn handle_language_server_prompt_request(
5451        this: Entity<Self>,
5452        envelope: TypedEnvelope<proto::LanguageServerPromptRequest>,
5453        mut cx: AsyncApp,
5454    ) -> Result<proto::LanguageServerPromptResponse> {
5455        let (tx, rx) = async_channel::bounded(1);
5456        let actions: Vec<_> = envelope
5457            .payload
5458            .actions
5459            .into_iter()
5460            .map(|action| MessageActionItem {
5461                title: action,
5462                properties: Default::default(),
5463            })
5464            .collect();
5465        this.update(&mut cx, |_, cx| {
5466            cx.emit(Event::LanguageServerPrompt(
5467                LanguageServerPromptRequest::new(
5468                    proto_to_prompt(envelope.payload.level.context("Invalid prompt level")?),
5469                    envelope.payload.message,
5470                    actions.clone(),
5471                    envelope.payload.lsp_name,
5472                    tx,
5473                ),
5474            ));
5475
5476            anyhow::Ok(())
5477        })?;
5478
5479        // We drop `this` to avoid holding a reference in this future for too
5480        // long.
5481        // If we keep the reference, we might not drop the `Project` early
5482        // enough when closing a window and it will only get releases on the
5483        // next `flush_effects()` call.
5484        drop(this);
5485
5486        let mut rx = pin!(rx);
5487        let answer = rx.next().await;
5488
5489        Ok(LanguageServerPromptResponse {
5490            action_response: answer.and_then(|answer| {
5491                actions
5492                    .iter()
5493                    .position(|action| *action == answer)
5494                    .map(|index| index as u64)
5495            }),
5496        })
5497    }
5498
5499    async fn handle_hide_toast(
5500        this: Entity<Self>,
5501        envelope: TypedEnvelope<proto::HideToast>,
5502        mut cx: AsyncApp,
5503    ) -> Result<()> {
5504        this.update(&mut cx, |_, cx| {
5505            cx.emit(Event::HideToast {
5506                notification_id: envelope.payload.notification_id.into(),
5507            });
5508            Ok(())
5509        })
5510    }
5511
5512    // Collab sends UpdateWorktree protos as messages
5513    async fn handle_update_worktree(
5514        this: Entity<Self>,
5515        envelope: TypedEnvelope<proto::UpdateWorktree>,
5516        mut cx: AsyncApp,
5517    ) -> Result<()> {
5518        this.update(&mut cx, |project, cx| {
5519            let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
5520            if let Some(worktree) = project.worktree_for_id(worktree_id, cx) {
5521                worktree.update(cx, |worktree, _| {
5522                    let worktree = worktree.as_remote_mut().unwrap();
5523                    worktree.update_from_remote(envelope.payload);
5524                });
5525            }
5526            Ok(())
5527        })
5528    }
5529
5530    async fn handle_update_buffer_from_remote_server(
5531        this: Entity<Self>,
5532        envelope: TypedEnvelope<proto::UpdateBuffer>,
5533        cx: AsyncApp,
5534    ) -> Result<proto::Ack> {
5535        let buffer_store = this.read_with(&cx, |this, cx| {
5536            if let Some(remote_id) = this.remote_id() {
5537                let mut payload = envelope.payload.clone();
5538                payload.project_id = remote_id;
5539                cx.background_spawn(this.collab_client.request(payload))
5540                    .detach_and_log_err(cx);
5541            }
5542            this.buffer_store.clone()
5543        });
5544        BufferStore::handle_update_buffer(buffer_store, envelope, cx).await
5545    }
5546
5547    async fn handle_trust_worktrees(
5548        this: Entity<Self>,
5549        envelope: TypedEnvelope<proto::TrustWorktrees>,
5550        mut cx: AsyncApp,
5551    ) -> Result<proto::Ack> {
5552        if this.read_with(&cx, |project, _| project.is_via_collab()) {
5553            return Ok(proto::Ack {});
5554        }
5555
5556        let trusted_worktrees = cx
5557            .update(|cx| TrustedWorktrees::try_get_global(cx))
5558            .context("missing trusted worktrees")?;
5559        trusted_worktrees.update(&mut cx, |trusted_worktrees, cx| {
5560            trusted_worktrees.trust(
5561                &this.read(cx).worktree_store(),
5562                envelope
5563                    .payload
5564                    .trusted_paths
5565                    .into_iter()
5566                    .filter_map(|proto_path| PathTrust::from_proto(proto_path))
5567                    .collect(),
5568                cx,
5569            );
5570        });
5571        Ok(proto::Ack {})
5572    }
5573
5574    async fn handle_restrict_worktrees(
5575        this: Entity<Self>,
5576        envelope: TypedEnvelope<proto::RestrictWorktrees>,
5577        mut cx: AsyncApp,
5578    ) -> Result<proto::Ack> {
5579        if this.read_with(&cx, |project, _| project.is_via_collab()) {
5580            return Ok(proto::Ack {});
5581        }
5582
5583        // A remote server can push a restriction regardless of what this client
5584        // decided locally. Omega never restricts (OMEGA-DELTA-0001), and a
5585        // remote peer running upstream Zed must not be able to reintroduce
5586        // Restricted Mode on the operator's machine.
5587        let auto_trusts_everything =
5588            cx.update(|cx| ProjectSettings::get_global(cx).session.trust_all_worktrees);
5589        if auto_trusts_everything {
5590            return Ok(proto::Ack {});
5591        }
5592
5593        let trusted_worktrees = cx
5594            .update(|cx| TrustedWorktrees::try_get_global(cx))
5595            .context("missing trusted worktrees")?;
5596        trusted_worktrees.update(&mut cx, |trusted_worktrees, cx| {
5597            let worktree_store = this.read(cx).worktree_store().downgrade();
5598            let restricted_paths = envelope
5599                .payload
5600                .worktree_ids
5601                .into_iter()
5602                .map(WorktreeId::from_proto)
5603                .map(PathTrust::Worktree)
5604                .collect::<HashSet<_>>();
5605            trusted_worktrees.restrict(worktree_store, restricted_paths, cx);
5606        });
5607        Ok(proto::Ack {})
5608    }
5609
5610    // Goes from host to client.
5611    async fn handle_find_search_candidates_chunk(
5612        this: Entity<Self>,
5613        envelope: TypedEnvelope<proto::FindSearchCandidatesChunk>,
5614        mut cx: AsyncApp,
5615    ) -> Result<proto::Ack> {
5616        let buffer_store = this.read_with(&mut cx, |this, _| this.buffer_store.clone());
5617        BufferStore::handle_find_search_candidates_chunk(buffer_store, envelope, cx).await
5618    }
5619
5620    // Goes from client to host.
5621    async fn handle_find_search_candidates_cancel(
5622        this: Entity<Self>,
5623        envelope: TypedEnvelope<proto::FindSearchCandidatesCancelled>,
5624        mut cx: AsyncApp,
5625    ) -> Result<()> {
5626        let buffer_store = this.read_with(&mut cx, |this, _| this.buffer_store.clone());
5627        BufferStore::handle_find_search_candidates_cancel(buffer_store, envelope, cx).await
5628    }
5629
5630    async fn handle_update_buffer(
5631        this: Entity<Self>,
5632        envelope: TypedEnvelope<proto::UpdateBuffer>,
5633        cx: AsyncApp,
5634    ) -> Result<proto::Ack> {
5635        let buffer_store = this.read_with(&cx, |this, cx| {
5636            if let Some(ssh) = &this.remote_client {
5637                let mut payload = envelope.payload.clone();
5638                payload.project_id = REMOTE_SERVER_PROJECT_ID;
5639                cx.background_spawn(ssh.read(cx).proto_client().request(payload))
5640                    .detach_and_log_err(cx);
5641            }
5642            this.buffer_store.clone()
5643        });
5644        BufferStore::handle_update_buffer(buffer_store, envelope, cx).await
5645    }
5646
5647    fn retain_remotely_created_models(
5648        &mut self,
5649        cx: &mut Context<Self>,
5650    ) -> RemotelyCreatedModelGuard {
5651        Self::retain_remotely_created_models_impl(
5652            &self.remotely_created_models,
5653            &self.buffer_store,
5654            &self.worktree_store,
5655            cx,
5656        )
5657    }
5658
5659    fn retain_remotely_created_models_impl(
5660        models: &Arc<Mutex<RemotelyCreatedModels>>,
5661        buffer_store: &Entity<BufferStore>,
5662        worktree_store: &Entity<WorktreeStore>,
5663        cx: &mut App,
5664    ) -> RemotelyCreatedModelGuard {
5665        {
5666            let mut remotely_create_models = models.lock();
5667            if remotely_create_models.retain_count == 0 {
5668                remotely_create_models.buffers = buffer_store.read(cx).buffers().collect();
5669                remotely_create_models.worktrees = worktree_store.read(cx).worktrees().collect();
5670            }
5671            remotely_create_models.retain_count += 1;
5672        }
5673        RemotelyCreatedModelGuard {
5674            remote_models: Arc::downgrade(&models),
5675        }
5676    }
5677
5678    async fn handle_create_buffer_for_peer(
5679        this: Entity<Self>,
5680        envelope: TypedEnvelope<proto::CreateBufferForPeer>,
5681        mut cx: AsyncApp,
5682    ) -> Result<()> {
5683        this.update(&mut cx, |this, cx| {
5684            this.buffer_store.update(cx, |buffer_store, cx| {
5685                buffer_store.handle_create_buffer_for_peer(
5686                    envelope,
5687                    this.replica_id(),
5688                    this.capability(),
5689                    cx,
5690                )
5691            })
5692        })
5693    }
5694
5695    async fn handle_toggle_lsp_logs(
5696        project: Entity<Self>,
5697        envelope: TypedEnvelope<proto::ToggleLspLogs>,
5698        mut cx: AsyncApp,
5699    ) -> Result<()> {
5700        let toggled_log_kind =
5701            match proto::toggle_lsp_logs::LogType::from_i32(envelope.payload.log_type)
5702                .context("invalid log type")?
5703            {
5704                proto::toggle_lsp_logs::LogType::Log => LogKind::Logs,
5705                proto::toggle_lsp_logs::LogType::Trace => LogKind::Trace,
5706                proto::toggle_lsp_logs::LogType::Rpc => LogKind::Rpc,
5707            };
5708        project.update(&mut cx, |_, cx| {
5709            cx.emit(Event::ToggleLspLogs {
5710                server_id: LanguageServerId::from_proto(envelope.payload.server_id),
5711                enabled: envelope.payload.enabled,
5712                toggled_log_kind,
5713            })
5714        });
5715        Ok(())
5716    }
5717
5718    async fn handle_synchronize_buffers(
5719        this: Entity<Self>,
5720        envelope: TypedEnvelope<proto::SynchronizeBuffers>,
5721        mut cx: AsyncApp,
5722    ) -> Result<proto::SynchronizeBuffersResponse> {
5723        let response = this.update(&mut cx, |this, cx| {
5724            let client = this.collab_client.clone();
5725            this.buffer_store.update(cx, |this, cx| {
5726                this.handle_synchronize_buffers(envelope, cx, client)
5727            })
5728        })?;
5729
5730        Ok(response)
5731    }
5732
5733    // Goes from client to host.
5734    async fn handle_search_candidate_buffers(
5735        this: Entity<Self>,
5736        envelope: TypedEnvelope<proto::FindSearchCandidates>,
5737        mut cx: AsyncApp,
5738    ) -> Result<proto::Ack> {
5739        let peer_id = envelope.original_sender_id.unwrap_or(envelope.sender_id);
5740        let message = envelope.payload;
5741        let project_id = message.project_id;
5742        let path_style = this.read_with(&cx, |this, cx| this.path_style(cx));
5743        let query =
5744            SearchQuery::from_proto(message.query.context("missing query field")?, path_style)?;
5745
5746        let handle = message.handle;
5747        let buffer_store = this.read_with(&cx, |this, _| this.buffer_store().clone());
5748        let client = this.read_with(&cx, |this, _| this.client());
5749        let task = cx.spawn(async move |cx| {
5750            let results = this.update(cx, |this, cx| {
5751                this.search_impl(query, cx).matching_buffers(cx)
5752            });
5753            let (batcher, batches) = project_search::AdaptiveBatcher::new(cx.background_executor());
5754            let mut new_matches = Box::pin(results.rx);
5755
5756            let sender_task = cx.background_executor().spawn({
5757                let client = client.clone();
5758                async move {
5759                    let mut batches = std::pin::pin!(batches);
5760                    while let Some(buffer_ids) = batches.next().await {
5761                        client
5762                            .request(proto::FindSearchCandidatesChunk {
5763                                handle,
5764                                peer_id: Some(peer_id),
5765                                project_id,
5766                                variant: Some(
5767                                    proto::find_search_candidates_chunk::Variant::Matches(
5768                                        proto::FindSearchCandidatesMatches { buffer_ids },
5769                                    ),
5770                                ),
5771                            })
5772                            .await?;
5773                    }
5774                    anyhow::Ok(())
5775                }
5776            });
5777
5778            while let Some((buffer, _)) = new_matches.next().await {
5779                let buffer_id = this.update(cx, |this, cx| {
5780                    this.create_buffer_for_peer(&buffer, peer_id, cx).to_proto()
5781                });
5782                batcher.push(buffer_id).await;
5783            }
5784            batcher.flush().await;
5785
5786            sender_task.await?;
5787
5788            let _ = client
5789                .request(proto::FindSearchCandidatesChunk {
5790                    handle,
5791                    peer_id: Some(peer_id),
5792                    project_id,
5793                    variant: Some(proto::find_search_candidates_chunk::Variant::Done(
5794                        proto::FindSearchCandidatesDone {},
5795                    )),
5796                })
5797                .await?;
5798            anyhow::Ok(())
5799        });
5800        buffer_store.update(&mut cx, |this, _| {
5801            this.register_ongoing_project_search((peer_id, handle), task);
5802        });
5803
5804        Ok(proto::Ack {})
5805    }
5806
5807    async fn handle_open_buffer_by_id(
5808        this: Entity<Self>,
5809        envelope: TypedEnvelope<proto::OpenBufferById>,
5810        mut cx: AsyncApp,
5811    ) -> Result<proto::OpenBufferResponse> {
5812        let peer_id = envelope.original_sender_id()?;
5813        let buffer_id = BufferId::new(envelope.payload.id)?;
5814        let buffer = this
5815            .update(&mut cx, |this, cx| this.open_buffer_by_id(buffer_id, cx))
5816            .await?;
5817        Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
5818    }
5819
5820    async fn handle_open_buffer_by_path(
5821        this: Entity<Self>,
5822        envelope: TypedEnvelope<proto::OpenBufferByPath>,
5823        mut cx: AsyncApp,
5824    ) -> Result<proto::OpenBufferResponse> {
5825        let peer_id = envelope.original_sender_id()?;
5826        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
5827        let path = RelPath::from_unix_str(&envelope.payload.path)?.into();
5828        let open_buffer = this
5829            .update(&mut cx, |this, cx| {
5830                this.open_buffer(ProjectPath { worktree_id, path }, cx)
5831            })
5832            .await?;
5833        Project::respond_to_open_buffer_request(this, open_buffer, peer_id, &mut cx)
5834    }
5835
5836    async fn handle_open_new_buffer(
5837        this: Entity<Self>,
5838        envelope: TypedEnvelope<proto::OpenNewBuffer>,
5839        mut cx: AsyncApp,
5840    ) -> Result<proto::OpenBufferResponse> {
5841        let buffer = this
5842            .update(&mut cx, |this, cx| this.create_buffer(None, true, cx))
5843            .await?;
5844        let peer_id = envelope.original_sender_id()?;
5845
5846        Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
5847    }
5848
5849    fn respond_to_open_buffer_request(
5850        this: Entity<Self>,
5851        buffer: Entity<Buffer>,
5852        peer_id: proto::PeerId,
5853        cx: &mut AsyncApp,
5854    ) -> Result<proto::OpenBufferResponse> {
5855        this.update(cx, |this, cx| {
5856            let is_private = buffer
5857                .read(cx)
5858                .file()
5859                .map(|f| f.is_private())
5860                .unwrap_or_default();
5861            anyhow::ensure!(!is_private, ErrorCode::UnsharedItem);
5862            Ok(proto::OpenBufferResponse {
5863                buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx).into(),
5864            })
5865        })
5866    }
5867
5868    fn create_buffer_for_peer(
5869        &mut self,
5870        buffer: &Entity<Buffer>,
5871        peer_id: proto::PeerId,
5872        cx: &mut App,
5873    ) -> BufferId {
5874        self.buffer_store
5875            .update(cx, |buffer_store, cx| {
5876                buffer_store.create_buffer_for_peer(buffer, peer_id, cx)
5877            })
5878            .detach_and_log_err(cx);
5879        buffer.read(cx).remote_id()
5880    }
5881
5882    async fn handle_create_image_for_peer(
5883        this: Entity<Self>,
5884        envelope: TypedEnvelope<proto::CreateImageForPeer>,
5885        mut cx: AsyncApp,
5886    ) -> Result<()> {
5887        this.update(&mut cx, |this, cx| {
5888            this.image_store.update(cx, |image_store, cx| {
5889                image_store.handle_create_image_for_peer(envelope, cx)
5890            })
5891        })
5892    }
5893
5894    async fn handle_create_file_for_peer(
5895        this: Entity<Self>,
5896        envelope: TypedEnvelope<proto::CreateFileForPeer>,
5897        mut cx: AsyncApp,
5898    ) -> Result<()> {
5899        use proto::create_file_for_peer::Variant;
5900        log::debug!("handle_create_file_for_peer: received message");
5901
5902        let downloading_files: Arc<Mutex<HashMap<(WorktreeId, String), DownloadingFile>>> =
5903            this.update(&mut cx, |this, _| this.downloading_files.clone());
5904
5905        match &envelope.payload.variant {
5906            Some(Variant::State(state)) => {
5907                log::debug!(
5908                    "handle_create_file_for_peer: got State: id={}, content_size={}",
5909                    state.id,
5910                    state.content_size
5911                );
5912
5913                // Extract worktree_id and path from the File field
5914                if let Some(ref file) = state.file {
5915                    let worktree_id = WorktreeId::from_proto(file.worktree_id);
5916                    let path = file.path.clone();
5917                    let key = (worktree_id, path);
5918                    log::debug!("handle_create_file_for_peer: looking up key={:?}", key);
5919
5920                    let empty_file_destination: Option<PathBuf> = {
5921                        let mut files = downloading_files.lock();
5922                        log::trace!(
5923                            "handle_create_file_for_peer: current downloading_files keys: {:?}",
5924                            files.keys().collect::<Vec<_>>()
5925                        );
5926
5927                        if let Some(file_entry) = files.get_mut(&key) {
5928                            file_entry.total_size = state.content_size;
5929                            file_entry.file_id = Some(state.id);
5930                            log::debug!(
5931                                "handle_create_file_for_peer: updated file entry: total_size={}, file_id={}",
5932                                state.content_size,
5933                                state.id
5934                            );
5935                        } else {
5936                            log::warn!(
5937                                "handle_create_file_for_peer: key={:?} not found in downloading_files",
5938                                key
5939                            );
5940                        }
5941
5942                        if state.content_size == 0 {
5943                            // No chunks will arrive for an empty file; write it now.
5944                            files.remove(&key).map(|entry| entry.destination_path)
5945                        } else {
5946                            None
5947                        }
5948                    };
5949
5950                    if let Some(destination) = empty_file_destination {
5951                        log::debug!(
5952                            "handle_create_file_for_peer: writing empty file to {:?}",
5953                            destination
5954                        );
5955                        match smol::fs::write(&destination, &[] as &[u8]).await {
5956                            Ok(_) => log::info!(
5957                                "handle_create_file_for_peer: successfully wrote file to {:?}",
5958                                destination
5959                            ),
5960                            Err(e) => log::error!(
5961                                "handle_create_file_for_peer: failed to write empty file: {:?}",
5962                                e
5963                            ),
5964                        }
5965                    }
5966                } else {
5967                    log::warn!("handle_create_file_for_peer: State has no file field");
5968                }
5969            }
5970            Some(Variant::Chunk(chunk)) => {
5971                log::debug!(
5972                    "handle_create_file_for_peer: got Chunk: file_id={}, data_len={}",
5973                    chunk.file_id,
5974                    chunk.data.len()
5975                );
5976
5977                // Extract data while holding the lock, then release it before await
5978                let (key_to_remove, write_info): (
5979                    Option<(WorktreeId, String)>,
5980                    Option<(PathBuf, Vec<u8>)>,
5981                ) = {
5982                    let mut files = downloading_files.lock();
5983                    let mut found_key: Option<(WorktreeId, String)> = None;
5984                    let mut write_data: Option<(PathBuf, Vec<u8>)> = None;
5985
5986                    for (key, file_entry) in files.iter_mut() {
5987                        if file_entry.file_id == Some(chunk.file_id) {
5988                            file_entry.chunks.extend_from_slice(&chunk.data);
5989                            log::debug!(
5990                                "handle_create_file_for_peer: accumulated {} bytes, total_size={}",
5991                                file_entry.chunks.len(),
5992                                file_entry.total_size
5993                            );
5994
5995                            if file_entry.chunks.len() as u64 >= file_entry.total_size
5996                                && file_entry.total_size > 0
5997                            {
5998                                let destination = file_entry.destination_path.clone();
5999                                let content = std::mem::take(&mut file_entry.chunks);
6000                                found_key = Some(key.clone());
6001                                write_data = Some((destination, content));
6002                            }
6003                            break;
6004                        }
6005                    }
6006                    (found_key, write_data)
6007                }; // MutexGuard is dropped here
6008
6009                // Perform the async write outside the lock
6010                if let Some((destination, content)) = write_info {
6011                    log::debug!(
6012                        "handle_create_file_for_peer: writing {} bytes to {:?}",
6013                        content.len(),
6014                        destination
6015                    );
6016                    match smol::fs::write(&destination, &content).await {
6017                        Ok(_) => log::info!(
6018                            "handle_create_file_for_peer: successfully wrote file to {:?}",
6019                            destination
6020                        ),
6021                        Err(e) => log::error!(
6022                            "handle_create_file_for_peer: failed to write file: {:?}",
6023                            e
6024                        ),
6025                    }
6026                }
6027
6028                // Remove the completed entry
6029                if let Some(key) = key_to_remove {
6030                    downloading_files.lock().remove(&key);
6031                    log::debug!("handle_create_file_for_peer: removed completed download entry");
6032                }
6033            }
6034            None => {
6035                log::warn!("handle_create_file_for_peer: got None variant");
6036            }
6037        }
6038
6039        Ok(())
6040    }
6041
6042    fn synchronize_remote_buffers(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
6043        let project_id = match self.client_state {
6044            ProjectClientState::Collab {
6045                sharing_has_stopped,
6046                remote_id,
6047                ..
6048            } => {
6049                if sharing_has_stopped {
6050                    return Task::ready(Err(anyhow!(
6051                        "can't synchronize remote buffers on a readonly project"
6052                    )));
6053                } else {
6054                    remote_id
6055                }
6056            }
6057            ProjectClientState::Shared { .. } | ProjectClientState::Local => {
6058                return Task::ready(Err(anyhow!(
6059                    "can't synchronize remote buffers on a local project"
6060                )));
6061            }
6062        };
6063
6064        let client = self.collab_client.clone();
6065        cx.spawn(async move |this, cx| {
6066            let (buffers, incomplete_buffer_ids) = this.update(cx, |this, cx| {
6067                this.buffer_store.read(cx).buffer_version_info(cx)
6068            })?;
6069            let response = client
6070                .request(proto::SynchronizeBuffers {
6071                    project_id,
6072                    buffers,
6073                })
6074                .await?;
6075
6076            let send_updates_for_buffers = this.update(cx, |this, cx| {
6077                response
6078                    .buffers
6079                    .into_iter()
6080                    .map(|buffer| {
6081                        let client = client.clone();
6082                        let buffer_id = match BufferId::new(buffer.id) {
6083                            Ok(id) => id,
6084                            Err(e) => {
6085                                return Task::ready(Err(e));
6086                            }
6087                        };
6088                        let remote_version = language::proto::deserialize_version(&buffer.version);
6089                        if let Some(buffer) = this.buffer_for_id(buffer_id, cx) {
6090                            let operations =
6091                                buffer.read(cx).serialize_ops(Some(remote_version), cx);
6092                            cx.background_spawn(async move {
6093                                let operations = operations.await;
6094                                for chunk in split_operations(operations) {
6095                                    client
6096                                        .request(proto::UpdateBuffer {
6097                                            project_id,
6098                                            buffer_id: buffer_id.into(),
6099                                            operations: chunk,
6100                                        })
6101                                        .await?;
6102                                }
6103                                anyhow::Ok(())
6104                            })
6105                        } else {
6106                            Task::ready(Ok(()))
6107                        }
6108                    })
6109                    .collect::<Vec<_>>()
6110            })?;
6111
6112            // Any incomplete buffers have open requests waiting. Request that the host sends
6113            // creates these buffers for us again to unblock any waiting futures.
6114            for id in incomplete_buffer_ids {
6115                cx.background_spawn(client.request(proto::OpenBufferById {
6116                    project_id,
6117                    id: id.into(),
6118                }))
6119                .detach();
6120            }
6121
6122            futures::future::join_all(send_updates_for_buffers)
6123                .await
6124                .into_iter()
6125                .collect()
6126        })
6127    }
6128
6129    pub fn worktree_metadata_protos(&self, cx: &App) -> Vec<proto::WorktreeMetadata> {
6130        self.worktree_store.read(cx).worktree_metadata_protos(cx)
6131    }
6132
6133    /// Iterator of all open buffers that have unsaved changes
6134    pub fn dirty_buffers<'a>(&'a self, cx: &'a App) -> impl Iterator<Item = ProjectPath> + 'a {
6135        self.buffer_store.read(cx).buffers().filter_map(|buf| {
6136            let buf = buf.read(cx);
6137            if buf.is_dirty() {
6138                buf.project_path(cx)
6139            } else {
6140                None
6141            }
6142        })
6143    }
6144
6145    fn set_worktrees_from_proto(
6146        &mut self,
6147        worktrees: Vec<proto::WorktreeMetadata>,
6148        cx: &mut Context<Project>,
6149    ) -> Result<()> {
6150        self.worktree_store.update(cx, |worktree_store, cx| {
6151            worktree_store.set_worktrees_from_proto(worktrees, self.replica_id(), cx)
6152        })
6153    }
6154
6155    fn set_collaborators_from_proto(
6156        &mut self,
6157        messages: Vec<proto::Collaborator>,
6158        cx: &mut Context<Self>,
6159    ) -> Result<()> {
6160        let mut collaborators = HashMap::default();
6161        for message in messages {
6162            let collaborator = Collaborator::from_proto(message)?;
6163            collaborators.insert(collaborator.peer_id, collaborator);
6164        }
6165        for old_peer_id in self.collaborators.keys() {
6166            if !collaborators.contains_key(old_peer_id) {
6167                cx.emit(Event::CollaboratorLeft(*old_peer_id));
6168            }
6169        }
6170        self.collaborators = collaborators;
6171        Ok(())
6172    }
6173
6174    pub fn supplementary_language_servers<'a>(
6175        &'a self,
6176        cx: &'a App,
6177    ) -> impl 'a + Iterator<Item = (LanguageServerId, LanguageServerName)> {
6178        self.lsp_store.read(cx).supplementary_language_servers()
6179    }
6180
6181    pub fn any_language_server_supports_inlay_hints(&self, buffer: &Buffer, cx: &mut App) -> bool {
6182        let Some(language) = buffer.language().cloned() else {
6183            return false;
6184        };
6185        self.lsp_store.update(cx, |lsp_store, _| {
6186            let relevant_language_servers = lsp_store
6187                .languages
6188                .lsp_adapters(&language.name())
6189                .into_iter()
6190                .map(|lsp_adapter| lsp_adapter.name())
6191                .collect::<HashSet<_>>();
6192            lsp_store
6193                .language_server_statuses()
6194                .filter_map(|(server_id, server_status)| {
6195                    relevant_language_servers
6196                        .contains(&server_status.name)
6197                        .then_some(server_id)
6198                })
6199                .filter_map(|server_id| lsp_store.lsp_server_capabilities.get(&server_id))
6200                .any(InlayHints::check_capabilities)
6201        })
6202    }
6203
6204    pub fn any_language_server_supports_semantic_tokens(
6205        &self,
6206        buffer: &Buffer,
6207        cx: &mut App,
6208    ) -> bool {
6209        let Some(language) = buffer.language().cloned() else {
6210            return false;
6211        };
6212        let lsp_store = self.lsp_store.read(cx);
6213        let relevant_language_servers = lsp_store
6214            .languages
6215            .lsp_adapters(&language.name())
6216            .into_iter()
6217            .map(|lsp_adapter| lsp_adapter.name())
6218            .collect::<HashSet<_>>();
6219        lsp_store
6220            .language_server_statuses()
6221            .filter_map(|(server_id, server_status)| {
6222                relevant_language_servers
6223                    .contains(&server_status.name)
6224                    .then_some(server_id)
6225            })
6226            .filter_map(|server_id| lsp_store.lsp_server_capabilities.get(&server_id))
6227            .any(|capabilities| capabilities.semantic_tokens_provider.is_some())
6228    }
6229
6230    pub fn language_server_id_for_name(
6231        &self,
6232        buffer: &Buffer,
6233        name: &LanguageServerName,
6234        cx: &App,
6235    ) -> Option<LanguageServerId> {
6236        let language = buffer.language()?;
6237        let relevant_language_servers = self
6238            .languages
6239            .lsp_adapters(&language.name())
6240            .into_iter()
6241            .map(|lsp_adapter| lsp_adapter.name())
6242            .collect::<HashSet<_>>();
6243        if !relevant_language_servers.contains(name) {
6244            return None;
6245        }
6246        self.language_server_statuses(cx)
6247            .filter(|(_, server_status)| relevant_language_servers.contains(&server_status.name))
6248            .find_map(|(server_id, server_status)| {
6249                if &server_status.name == name {
6250                    Some(server_id)
6251                } else {
6252                    None
6253                }
6254            })
6255    }
6256
6257    #[cfg(feature = "test-support")]
6258    pub fn has_language_servers_for(&self, buffer: &Buffer, cx: &mut App) -> bool {
6259        self.lsp_store.update(cx, |this, cx| {
6260            this.running_language_servers_for_local_buffer(buffer, cx)
6261                .next()
6262                .is_some()
6263        })
6264    }
6265
6266    pub fn git_init(
6267        &self,
6268        path: Arc<Path>,
6269        fallback_branch_name: String,
6270        cx: &App,
6271    ) -> Task<Result<()>> {
6272        self.git_store
6273            .read(cx)
6274            .git_init(path, fallback_branch_name, cx)
6275    }
6276
6277    pub fn git_config(&self, path: Arc<Path>, args: Vec<String>, cx: &App) -> Task<Result<String>> {
6278        self.git_store.read(cx).git_config(path, args, cx)
6279    }
6280
6281    pub fn buffer_store(&self) -> &Entity<BufferStore> {
6282        &self.buffer_store
6283    }
6284
6285    pub fn git_store(&self) -> &Entity<GitStore> {
6286        &self.git_store
6287    }
6288
6289    pub fn agent_server_store(&self) -> &Entity<AgentServerStore> {
6290        &self.agent_server_store
6291    }
6292
6293    #[cfg(feature = "test-support")]
6294    pub fn git_scans_complete(&self, cx: &Context<Self>) -> Task<()> {
6295        use futures::future::join_all;
6296        cx.spawn(async move |this, cx| {
6297            let scans_complete = this
6298                .read_with(cx, |this, cx| {
6299                    this.worktrees(cx)
6300                        .filter_map(|worktree| Some(worktree.read(cx).as_local()?.scan_complete()))
6301                        .collect::<Vec<_>>()
6302                })
6303                .unwrap();
6304            join_all(scans_complete).await;
6305            let barriers = this
6306                .update(cx, |this, cx| {
6307                    let repos = this.repositories(cx).values().cloned().collect::<Vec<_>>();
6308                    repos
6309                        .into_iter()
6310                        .map(|repo| repo.update(cx, |repo, _| repo.barrier()))
6311                        .collect::<Vec<_>>()
6312                })
6313                .unwrap();
6314            join_all(barriers).await;
6315        })
6316    }
6317
6318    pub fn active_repository(&self, cx: &App) -> Option<Entity<Repository>> {
6319        self.git_store.read(cx).active_repository()
6320    }
6321
6322    pub fn repositories<'a>(&self, cx: &'a App) -> &'a HashMap<RepositoryId, Entity<Repository>> {
6323        self.git_store.read(cx).repositories()
6324    }
6325
6326    pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
6327        self.git_store.read(cx).status_for_buffer_id(buffer_id, cx)
6328    }
6329
6330    pub fn set_agent_location(
6331        &mut self,
6332        new_location: Option<AgentLocation>,
6333        cx: &mut Context<Self>,
6334    ) {
6335        if let Some(old_location) = self.agent_location.as_ref() {
6336            old_location
6337                .buffer
6338                .update(cx, |buffer, cx| buffer.remove_agent_selections(cx))
6339                .ok();
6340        }
6341
6342        if let Some(location) = new_location.as_ref() {
6343            location
6344                .buffer
6345                .update(cx, |buffer, cx| {
6346                    buffer.set_agent_selections(
6347                        Arc::from([language::Selection {
6348                            id: 0,
6349                            start: location.position,
6350                            end: location.position,
6351                            reversed: false,
6352                            goal: language::SelectionGoal::None,
6353                        }]),
6354                        false,
6355                        CursorShape::Hollow,
6356                        cx,
6357                    )
6358                })
6359                .ok();
6360        }
6361
6362        self.agent_location = new_location;
6363        cx.emit(Event::AgentLocationChanged);
6364    }
6365
6366    pub fn agent_location(&self) -> Option<AgentLocation> {
6367        self.agent_location.clone()
6368    }
6369
6370    pub fn path_style(&self, cx: &App) -> PathStyle {
6371        self.worktree_store.read(cx).path_style()
6372    }
6373
6374    pub fn contains_local_settings_file(
6375        &self,
6376        worktree_id: WorktreeId,
6377        rel_path: &RelPath,
6378        cx: &App,
6379    ) -> bool {
6380        self.worktree_for_id(worktree_id, cx)
6381            .map_or(false, |worktree| {
6382                worktree.read(cx).entry_for_path(rel_path).is_some()
6383            })
6384    }
6385
6386    pub fn worktree_paths(&self, cx: &App) -> WorktreePaths {
6387        self.worktree_store.read(cx).paths(cx)
6388    }
6389
6390    pub fn project_group_key(&self, cx: &App) -> ProjectGroupKey {
6391        ProjectGroupKey::from_project(self, cx)
6392    }
6393}
6394
6395/// Identifies a project group by a set of paths the workspaces in this group
6396/// have.
6397///
6398/// Paths are mapped to their main worktree path first so we can group
6399/// workspaces by main repos.
6400#[derive(PartialEq, Eq, Hash, Clone, Debug, Default)]
6401pub struct ProjectGroupKey {
6402    /// The paths of the main worktrees for this project group.
6403    paths: PathList,
6404    host: Option<RemoteConnectionOptions>,
6405}
6406
6407impl ProjectGroupKey {
6408    /// Creates a new `ProjectGroupKey` with the given path list.
6409    ///
6410    /// The path list should point to the git main worktree paths for a project.
6411    pub fn new(host: Option<RemoteConnectionOptions>, paths: PathList) -> Self {
6412        Self { paths, host }
6413    }
6414
6415    pub fn from_project(project: &Project, cx: &App) -> Self {
6416        let paths = project.worktree_paths(cx);
6417        let host = project.remote_connection_options(cx);
6418        Self {
6419            paths: paths.main_worktree_path_list().clone(),
6420            host,
6421        }
6422    }
6423
6424    pub fn from_worktree_paths(
6425        paths: &WorktreePaths,
6426        host: Option<RemoteConnectionOptions>,
6427    ) -> Self {
6428        Self {
6429            paths: paths.main_worktree_path_list().clone(),
6430            host,
6431        }
6432    }
6433
6434    pub fn path_list(&self) -> &PathList {
6435        &self.paths
6436    }
6437
6438    pub fn display_name(
6439        &self,
6440        path_detail_map: &std::collections::HashMap<PathBuf, usize>,
6441    ) -> SharedString {
6442        let mut names = Vec::with_capacity(self.paths.paths().len());
6443        for abs_path in self.paths.ordered_paths() {
6444            let detail = path_detail_map.get(abs_path).copied().unwrap_or(0);
6445            // Strip a `.git` extension for display (bare clones like `foo.git`
6446            // should display as `foo`, matching the titlebar).
6447            let display_path = if abs_path.extension() == Some(std::ffi::OsStr::new("git")) {
6448                std::borrow::Cow::Owned(abs_path.with_extension(""))
6449            } else {
6450                std::borrow::Cow::Borrowed(abs_path.as_path())
6451            };
6452            let suffix = path_suffix(&display_path, detail);
6453            if !suffix.is_empty() {
6454                names.push(suffix);
6455            }
6456        }
6457        if names.is_empty() {
6458            "Empty Workspace".into()
6459        } else {
6460            names.join(", ").into()
6461        }
6462    }
6463
6464    pub fn host(&self) -> Option<RemoteConnectionOptions> {
6465        self.host.clone()
6466    }
6467
6468    pub fn matches(&self, other: &ProjectGroupKey) -> bool {
6469        self.paths == other.paths
6470            && same_remote_connection_identity(self.host.as_ref(), other.host.as_ref())
6471    }
6472}
6473
6474pub fn path_suffix(path: &Path, detail: usize) -> String {
6475    let mut components: Vec<_> = path
6476        .components()
6477        .rev()
6478        .filter_map(|component| match component {
6479            std::path::Component::Normal(s) => Some(s.to_string_lossy()),
6480            _ => None,
6481        })
6482        .take(detail + 1)
6483        .collect();
6484    components.reverse();
6485    components.join("/")
6486}
6487
6488pub struct PathMatchCandidateSet {
6489    pub snapshot: Snapshot,
6490    pub include_ignored: bool,
6491    pub include_root_name: bool,
6492    pub candidates: Candidates,
6493}
6494
6495pub enum Candidates {
6496    /// Only consider directories.
6497    Directories,
6498    /// Only consider files.
6499    Files,
6500    /// Consider directories and files.
6501    Entries,
6502}
6503
6504impl<'a> fuzzy::PathMatchCandidateSet<'a> for PathMatchCandidateSet {
6505    type Candidates = PathMatchCandidateSetIter<'a>;
6506
6507    fn id(&self) -> usize {
6508        self.snapshot.id().to_usize()
6509    }
6510
6511    fn len(&self) -> usize {
6512        match self.candidates {
6513            Candidates::Files => {
6514                if self.include_ignored {
6515                    self.snapshot.file_count()
6516                } else {
6517                    self.snapshot.visible_file_count()
6518                }
6519            }
6520
6521            Candidates::Directories => {
6522                if self.include_ignored {
6523                    self.snapshot.dir_count()
6524                } else {
6525                    self.snapshot.visible_dir_count()
6526                }
6527            }
6528
6529            Candidates::Entries => {
6530                if self.include_ignored {
6531                    self.snapshot.entry_count()
6532                } else {
6533                    self.snapshot.visible_entry_count()
6534                }
6535            }
6536        }
6537    }
6538
6539    fn prefix(&self) -> Arc<RelPath> {
6540        if self.snapshot.root_entry().is_some_and(|e| e.is_file()) || self.include_root_name {
6541            self.snapshot.root_name().into()
6542        } else {
6543            RelPath::empty_arc()
6544        }
6545    }
6546
6547    fn root_is_file(&self) -> bool {
6548        self.snapshot.root_entry().is_some_and(|f| f.is_file())
6549    }
6550
6551    fn path_style(&self) -> PathStyle {
6552        self.snapshot.path_style()
6553    }
6554
6555    fn candidates(&'a self, start: usize) -> Self::Candidates {
6556        PathMatchCandidateSetIter {
6557            traversal: match self.candidates {
6558                Candidates::Directories => self.snapshot.directories(self.include_ignored, start),
6559                Candidates::Files => self.snapshot.files(self.include_ignored, start),
6560                Candidates::Entries => self.snapshot.entries(self.include_ignored, start),
6561            },
6562        }
6563    }
6564}
6565
6566pub struct PathMatchCandidateSetIter<'a> {
6567    traversal: Traversal<'a>,
6568}
6569
6570impl<'a> Iterator for PathMatchCandidateSetIter<'a> {
6571    type Item = fuzzy::PathMatchCandidate<'a>;
6572
6573    fn next(&mut self) -> Option<Self::Item> {
6574        self.traversal
6575            .next()
6576            .map(|entry| fuzzy::PathMatchCandidate {
6577                is_dir: entry.kind.is_dir(),
6578                path: &entry.path,
6579                char_bag: entry.char_bag,
6580            })
6581    }
6582}
6583
6584impl<'a> fuzzy_nucleo::PathMatchCandidateSet<'a> for PathMatchCandidateSet {
6585    type Candidates = PathMatchCandidateSetNucleoIter<'a>;
6586    fn id(&self) -> usize {
6587        self.snapshot.id().to_usize()
6588    }
6589    fn len(&self) -> usize {
6590        match self.candidates {
6591            Candidates::Files => {
6592                if self.include_ignored {
6593                    self.snapshot.file_count()
6594                } else {
6595                    self.snapshot.visible_file_count()
6596                }
6597            }
6598            Candidates::Directories => {
6599                if self.include_ignored {
6600                    self.snapshot.dir_count()
6601                } else {
6602                    self.snapshot.visible_dir_count()
6603                }
6604            }
6605            Candidates::Entries => {
6606                if self.include_ignored {
6607                    self.snapshot.entry_count()
6608                } else {
6609                    self.snapshot.visible_entry_count()
6610                }
6611            }
6612        }
6613    }
6614    fn prefix(&self) -> Arc<RelPath> {
6615        if self.snapshot.root_entry().is_some_and(|e| e.is_file()) || self.include_root_name {
6616            self.snapshot.root_name().into()
6617        } else {
6618            RelPath::empty_arc()
6619        }
6620    }
6621    fn root_is_file(&self) -> bool {
6622        self.snapshot.root_entry().is_some_and(|f| f.is_file())
6623    }
6624    fn path_style(&self) -> PathStyle {
6625        self.snapshot.path_style()
6626    }
6627    fn candidates(&'a self, start: usize) -> Self::Candidates {
6628        PathMatchCandidateSetNucleoIter {
6629            traversal: match self.candidates {
6630                Candidates::Directories => self.snapshot.directories(self.include_ignored, start),
6631                Candidates::Files => self.snapshot.files(self.include_ignored, start),
6632                Candidates::Entries => self.snapshot.entries(self.include_ignored, start),
6633            },
6634        }
6635    }
6636}
6637
6638pub struct PathMatchCandidateSetNucleoIter<'a> {
6639    traversal: Traversal<'a>,
6640}
6641
6642impl<'a> Iterator for PathMatchCandidateSetNucleoIter<'a> {
6643    type Item = fuzzy_nucleo::PathMatchCandidate<'a>;
6644    fn next(&mut self) -> Option<Self::Item> {
6645        self.traversal
6646            .next()
6647            .map(|entry| fuzzy_nucleo::PathMatchCandidate {
6648                is_dir: entry.kind.is_dir(),
6649                path: &entry.path,
6650                char_bag: entry.char_bag,
6651            })
6652    }
6653}
6654
6655impl EventEmitter<Event> for Project {}
6656
6657impl<'a> From<&'a ProjectPath> for SettingsLocation<'a> {
6658    fn from(val: &'a ProjectPath) -> Self {
6659        SettingsLocation {
6660            worktree_id: val.worktree_id,
6661            path: val.path.as_ref(),
6662        }
6663    }
6664}
6665
6666impl<P: Into<Arc<RelPath>>> From<(WorktreeId, P)> for ProjectPath {
6667    fn from((worktree_id, path): (WorktreeId, P)) -> Self {
6668        Self {
6669            worktree_id,
6670            path: path.into(),
6671        }
6672    }
6673}
6674
6675/// ResolvedPath is a path that has been resolved to either a ProjectPath
6676/// or an AbsPath and that *exists*.
6677#[derive(Debug, Clone)]
6678pub enum ResolvedPath {
6679    ProjectPath {
6680        project_path: ProjectPath,
6681        is_dir: bool,
6682    },
6683    AbsPath {
6684        path: String,
6685        is_dir: bool,
6686    },
6687}
6688
6689impl ResolvedPath {
6690    pub fn abs_path(&self) -> Option<&str> {
6691        match self {
6692            Self::AbsPath { path, .. } => Some(path),
6693            _ => None,
6694        }
6695    }
6696
6697    pub fn into_abs_path(self) -> Option<String> {
6698        match self {
6699            Self::AbsPath { path, .. } => Some(path),
6700            _ => None,
6701        }
6702    }
6703
6704    pub fn project_path(&self) -> Option<&ProjectPath> {
6705        match self {
6706            Self::ProjectPath { project_path, .. } => Some(project_path),
6707            _ => None,
6708        }
6709    }
6710
6711    pub fn is_file(&self) -> bool {
6712        !self.is_dir()
6713    }
6714
6715    pub fn is_dir(&self) -> bool {
6716        match self {
6717            Self::ProjectPath { is_dir, .. } => *is_dir,
6718            Self::AbsPath { is_dir, .. } => *is_dir,
6719        }
6720    }
6721}
6722
6723impl ProjectItem for Buffer {
6724    fn try_open(
6725        project: &Entity<Project>,
6726        path: &ProjectPath,
6727        cx: &mut App,
6728    ) -> Option<Task<Result<Entity<Self>>>> {
6729        Some(project.update(cx, |project, cx| project.open_buffer(path.clone(), cx)))
6730    }
6731
6732    fn entry_id(&self, _cx: &App) -> Option<ProjectEntryId> {
6733        File::from_dyn(self.file()).and_then(|file| file.project_entry_id())
6734    }
6735
6736    fn project_path(&self, cx: &App) -> Option<ProjectPath> {
6737        let file = self.file()?;
6738
6739        (!matches!(file.disk_state(), DiskState::Historic { .. })).then(|| ProjectPath {
6740            worktree_id: file.worktree_id(cx),
6741            path: file.path().clone(),
6742        })
6743    }
6744
6745    fn is_dirty(&self) -> bool {
6746        self.is_dirty()
6747    }
6748}
6749
6750impl Completion {
6751    pub fn kind(&self) -> Option<CompletionItemKind> {
6752        self.source
6753            // `lsp::CompletionListItemDefaults` has no `kind` field
6754            .lsp_completion(false)
6755            .and_then(|lsp_completion| lsp_completion.kind)
6756    }
6757
6758    pub fn label(&self) -> Option<String> {
6759        self.source
6760            .lsp_completion(false)
6761            .map(|lsp_completion| lsp_completion.label.clone())
6762    }
6763
6764    /// A key that can be used to sort completions when displaying
6765    /// them to the user.
6766    pub fn sort_key(&self) -> (usize, &str) {
6767        const DEFAULT_KIND_KEY: usize = 4;
6768        let kind_key = self
6769            .kind()
6770            .and_then(|lsp_completion_kind| match lsp_completion_kind {
6771                lsp::CompletionItemKind::KEYWORD => Some(0),
6772                lsp::CompletionItemKind::VARIABLE => Some(1),
6773                lsp::CompletionItemKind::CONSTANT => Some(2),
6774                lsp::CompletionItemKind::PROPERTY => Some(3),
6775                _ => None,
6776            })
6777            .unwrap_or(DEFAULT_KIND_KEY);
6778        (kind_key, self.label.filter_text())
6779    }
6780
6781    /// Whether this completion is a snippet.
6782    pub fn is_snippet_kind(&self) -> bool {
6783        matches!(
6784            &self.source,
6785            CompletionSource::Lsp { lsp_completion, .. }
6786            if lsp_completion.kind == Some(CompletionItemKind::SNIPPET)
6787        )
6788    }
6789
6790    /// Whether this completion is a snippet or snippet-style LSP completion.
6791    pub fn is_snippet(&self) -> bool {
6792        self.source
6793            // `lsp::CompletionListItemDefaults` has `insert_text_format` field
6794            .lsp_completion(true)
6795            .is_some_and(|lsp_completion| {
6796                lsp_completion.insert_text_format == Some(lsp::InsertTextFormat::SNIPPET)
6797            })
6798    }
6799
6800    /// Returns the corresponding color for this completion.
6801    ///
6802    /// Will return `None` if this completion's kind is not [`CompletionItemKind::COLOR`].
6803    pub fn color(&self) -> Option<Hsla> {
6804        // `lsp::CompletionListItemDefaults` has no `kind` field
6805        let lsp_completion = self.source.lsp_completion(false)?;
6806        if lsp_completion.kind? == CompletionItemKind::COLOR {
6807            return color_extractor::extract_color(&lsp_completion);
6808        }
6809        None
6810    }
6811}
6812
6813fn proto_to_prompt(level: proto::language_server_prompt_request::Level) -> gpui::PromptLevel {
6814    match level {
6815        proto::language_server_prompt_request::Level::Info(_) => gpui::PromptLevel::Info,
6816        proto::language_server_prompt_request::Level::Warning(_) => gpui::PromptLevel::Warning,
6817        proto::language_server_prompt_request::Level::Critical(_) => gpui::PromptLevel::Critical,
6818    }
6819}
6820
6821fn provide_inline_values(
6822    captures: impl Iterator<Item = (Range<usize>, language::DebuggerTextObject)>,
6823    snapshot: &language::BufferSnapshot,
6824    max_row: usize,
6825) -> Vec<InlineValueLocation> {
6826    let mut variables = Vec::new();
6827    let mut variable_position = HashSet::default();
6828    let mut scopes = Vec::new();
6829
6830    let active_debug_line_offset = snapshot.point_to_offset(Point::new(max_row as u32, 0));
6831
6832    for (capture_range, capture_kind) in captures {
6833        match capture_kind {
6834            language::DebuggerTextObject::Variable => {
6835                let variable_name = snapshot
6836                    .text_for_range(capture_range.clone())
6837                    .collect::<String>();
6838                let point = snapshot.offset_to_point(capture_range.end);
6839
6840                while scopes
6841                    .last()
6842                    .is_some_and(|scope: &Range<_>| !scope.contains(&capture_range.start))
6843                {
6844                    scopes.pop();
6845                }
6846
6847                if point.row as usize > max_row {
6848                    break;
6849                }
6850
6851                let scope = if scopes
6852                    .last()
6853                    .is_none_or(|scope| !scope.contains(&active_debug_line_offset))
6854                {
6855                    VariableScope::Global
6856                } else {
6857                    VariableScope::Local
6858                };
6859
6860                if variable_position.insert(capture_range.end) {
6861                    variables.push(InlineValueLocation {
6862                        variable_name,
6863                        scope,
6864                        lookup: VariableLookupKind::Variable,
6865                        row: point.row as usize,
6866                        column: point.column as usize,
6867                    });
6868                }
6869            }
6870            language::DebuggerTextObject::Scope => {
6871                while scopes.last().map_or_else(
6872                    || false,
6873                    |scope: &Range<usize>| {
6874                        !(scope.contains(&capture_range.start)
6875                            && scope.contains(&capture_range.end))
6876                    },
6877                ) {
6878                    scopes.pop();
6879                }
6880                scopes.push(capture_range);
6881            }
6882        }
6883    }
6884
6885    variables
6886}
6887
Served at tenant.openagents/omega Member data and write actions are omitted.