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