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