Skip to repository content6887 lines · 242.4 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T02:54:02.955Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
project.rs
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