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};
147use worktree_store::{WorktreeStore, WorktreeStoreEvent};
148
149pub use fs::*;
150pub use language::Location;
151#[cfg(any(test, feature = "test-support"))]
152pub use prettier::FORMAT_SUFFIX as TEST_PRETTIER_FORMAT_SUFFIX;
153#[cfg(any(test, feature = "test-support"))]
154pub use prettier::RANGE_FORMAT_SUFFIX as TEST_PRETTIER_RANGE_FORMAT_SUFFIX;
155pub use task_inventory::{
156 BasicContextProvider, ContextProviderWithTasks, DebugScenarioContext, Inventory, TaskContexts,
157 TaskSourceKind,
158};
159
160pub use buffer_store::ProjectTransaction;
161pub use lsp_store::{
162 DiagnosticSummary, InvalidationStrategy, LanguageServerLogType, LanguageServerProgress,
163 LanguageServerPromptRequest, LanguageServerStatus, LanguageServerToQuery, LspStore,
164 LspStoreEvent, ProgressToken, SERVER_PROGRESS_THROTTLE_TIMEOUT,
165};
166pub use toolchain_store::{ToolchainStore, Toolchains};
167const MAX_PROJECT_SEARCH_HISTORY_SIZE: usize = 500;
168
169#[derive(Clone, Copy, Debug)]
170pub struct LocalProjectFlags {
171 pub init_worktree_trust: bool,
172 pub watch_global_configs: bool,
173}
174
175impl Default for LocalProjectFlags {
176 fn default() -> Self {
177 Self {
178 init_worktree_trust: true,
179 watch_global_configs: true,
180 }
181 }
182}
183
184pub trait ProjectItem: 'static {
185 fn try_open(
186 project: &Entity<Project>,
187 path: &ProjectPath,
188 cx: &mut App,
189 ) -> Option<Task<Result<Entity<Self>>>>
190 where
191 Self: Sized;
192 fn entry_id(&self, cx: &App) -> Option<ProjectEntryId>;
193 fn project_path(&self, cx: &App) -> Option<ProjectPath>;
194 fn is_dirty(&self) -> bool;
195}
196
197#[derive(Clone)]
198pub enum OpenedBufferEvent {
199 Disconnected,
200 Ok(BufferId),
201 Err(BufferId, Arc<anyhow::Error>),
202}
203
204/// Semantics-aware entity that is relevant to one or more [`Worktree`] with the files.
205/// `Project` is responsible for tasks, LSP and collab queries, synchronizing worktree states accordingly.
206/// Maps [`Worktree`] entries with its own logic using [`ProjectEntryId`] and [`ProjectPath`] structs.
207///
208/// Can be either local (for the project opened on the same host) or remote.(for collab projects, browsed by multiple remote users).
209pub struct Project {
210 active_entry: Option<ProjectEntryId>,
211 buffer_ordered_messages_tx: mpsc::UnboundedSender<BufferOrderedMessage>,
212 languages: Arc<LanguageRegistry>,
213 dap_store: Entity<DapStore>,
214 agent_server_store: Entity<AgentServerStore>,
215
216 breakpoint_store: Entity<BreakpointStore>,
217 collab_client: Arc<client::Client>,
218 join_project_response_message_id: u32,
219 task_store: Entity<TaskStore>,
220 user_store: Entity<UserStore>,
221 fs: Arc<dyn Fs>,
222 remote_client: Option<Entity<RemoteClient>>,
223 // todo lw explain the client_state x remote_client matrix, its super confusing
224 client_state: ProjectClientState,
225 git_store: Entity<GitStore>,
226 collaborators: HashMap<proto::PeerId, Collaborator>,
227 client_subscriptions: Vec<client::Subscription>,
228 worktree_store: Entity<WorktreeStore>,
229 buffer_store: Entity<BufferStore>,
230 context_server_store: Entity<ContextServerStore>,
231 image_store: Entity<ImageStore>,
232 lsp_store: Entity<LspStore>,
233 _subscriptions: Vec<gpui::Subscription>,
234 buffers_needing_diff: HashSet<WeakEntity<Buffer>>,
235 git_diff_debouncer: DebouncedDelay<Self>,
236 remotely_created_models: Arc<Mutex<RemotelyCreatedModels>>,
237 terminals: Terminals,
238 node: Option<NodeRuntime>,
239 search_history: SearchHistory,
240 search_included_history: SearchHistory,
241 search_excluded_history: SearchHistory,
242 snippets: Entity<SnippetProvider>,
243 environment: Entity<ProjectEnvironment>,
244 settings_observer: Entity<SettingsObserver>,
245 toolchain_store: Option<Entity<ToolchainStore>>,
246 agent_location: Option<AgentLocation>,
247 downloading_files: Arc<Mutex<HashMap<(WorktreeId, String), DownloadingFile>>>,
248}
249
250struct DownloadingFile {
251 destination_path: PathBuf,
252 chunks: Vec<u8>,
253 total_size: u64,
254 file_id: Option<u64>, // Set when we receive the State message
255}
256
257#[derive(Clone, Debug, PartialEq, Eq)]
258pub struct AgentLocation {
259 pub buffer: WeakEntity<Buffer>,
260 pub position: Anchor,
261}
262
263#[derive(Default)]
264struct RemotelyCreatedModels {
265 worktrees: Vec<Entity<Worktree>>,
266 buffers: Vec<Entity<Buffer>>,
267 retain_count: usize,
268}
269
270struct RemotelyCreatedModelGuard {
271 remote_models: std::sync::Weak<Mutex<RemotelyCreatedModels>>,
272}
273
274impl Drop for RemotelyCreatedModelGuard {
275 fn drop(&mut self) {
276 if let Some(remote_models) = self.remote_models.upgrade() {
277 let mut remote_models = remote_models.lock();
278 assert!(
279 remote_models.retain_count > 0,
280 "RemotelyCreatedModelGuard dropped too many times"
281 );
282 remote_models.retain_count -= 1;
283 if remote_models.retain_count == 0 {
284 remote_models.buffers.clear();
285 remote_models.worktrees.clear();
286 }
287 }
288 }
289}
290/// Message ordered with respect to buffer operations
291#[derive(Debug)]
292enum BufferOrderedMessage {
293 Operation {
294 buffer_id: BufferId,
295 operation: proto::Operation,
296 },
297 LanguageServerUpdate {
298 language_server_id: LanguageServerId,
299 message: proto::update_language_server::Variant,
300 name: Option<LanguageServerName>,
301 },
302 Resync,
303}
304
305#[derive(Debug)]
306enum ProjectClientState {
307 /// Single-player mode.
308 Local,
309 /// Multi-player mode but still a local project.
310 Shared { remote_id: u64 },
311 /// Multi-player mode but working on a remote project.
312 Collab {
313 sharing_has_stopped: bool,
314 capability: Capability,
315 remote_id: u64,
316 replica_id: ReplicaId,
317 },
318}
319
320/// A link to display in a toast notification, useful to point to documentation.
321#[derive(PartialEq, Debug, Clone)]
322pub struct ToastLink {
323 pub label: &'static str,
324 pub url: &'static str,
325}
326
327#[derive(Clone, Debug, PartialEq)]
328pub enum Event {
329 LanguageServerAdded(LanguageServerId, LanguageServerName, Option<WorktreeId>),
330 LanguageServerRemoved(LanguageServerId),
331 LanguageServerLog(LanguageServerId, LanguageServerLogType, String),
332 // [`lsp::notification::DidOpenTextDocument`] was sent to this server using the buffer data.
333 // Zed's buffer-related data is updated accordingly.
334 LanguageServerBufferRegistered {
335 server_id: LanguageServerId,
336 buffer_id: BufferId,
337 buffer_abs_path: PathBuf,
338 name: Option<LanguageServerName>,
339 },
340 ToggleLspLogs {
341 server_id: LanguageServerId,
342 enabled: bool,
343 toggled_log_kind: LogKind,
344 },
345 Toast {
346 notification_id: SharedString,
347 message: String,
348 /// Optional link to display as a button in the toast.
349 link: Option<ToastLink>,
350 },
351 HideToast {
352 notification_id: SharedString,
353 },
354 LanguageServerPrompt(LanguageServerPromptRequest),
355 LanguageNotFound(Entity<Buffer>),
356 ActiveEntryChanged(Option<ProjectEntryId>),
357 ActivateProjectPanel,
358 WorktreeAdded(WorktreeId),
359 WorktreeOrderChanged,
360 WorktreeRemoved(WorktreeId),
361 WorktreeUpdatedEntries(WorktreeId, UpdatedEntriesSet),
362 WorktreeUpdatedRootRepoCommonDir(WorktreeId),
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 WorktreeStoreEvent::WorktreeUpdatedRootRepoCommonDir(worktree_id) => {
3685 cx.emit(Event::WorktreeUpdatedRootRepoCommonDir(*worktree_id));
3686 }
3687 }
3688 }
3689
3690 fn on_worktree_added(&mut self, worktree: &Entity<Worktree>, _: &mut Context<Self>) {
3691 let mut remotely_created_models = self.remotely_created_models.lock();
3692 if remotely_created_models.retain_count > 0 {
3693 remotely_created_models.worktrees.push(worktree.clone())
3694 }
3695 }
3696
3697 fn on_worktree_released(&mut self, id_to_remove: WorktreeId, cx: &mut Context<Self>) {
3698 if let Some(remote) = &self.remote_client {
3699 remote
3700 .read(cx)
3701 .proto_client()
3702 .send(proto::RemoveWorktree {
3703 worktree_id: id_to_remove.to_proto(),
3704 })
3705 .log_err();
3706 }
3707 }
3708
3709 fn on_buffer_event(
3710 &mut self,
3711 buffer: Entity<Buffer>,
3712 event: &BufferEvent,
3713 cx: &mut Context<Self>,
3714 ) -> Option<()> {
3715 if matches!(event, BufferEvent::Edited { .. } | BufferEvent::Reloaded) {
3716 self.request_buffer_diff_recalculation(&buffer, cx);
3717 }
3718
3719 if matches!(event, BufferEvent::Edited { .. }) {
3720 cx.emit(Event::BufferEdited);
3721 }
3722
3723 let buffer_id = buffer.read(cx).remote_id();
3724 match event {
3725 BufferEvent::ReloadNeeded => {
3726 if !self.is_via_collab() {
3727 self.reload_buffers([buffer.clone()].into_iter().collect(), true, cx)
3728 .detach_and_log_err(cx);
3729 }
3730 }
3731 BufferEvent::Operation {
3732 operation,
3733 is_local: true,
3734 } => {
3735 let operation = language::proto::serialize_operation(operation);
3736
3737 if let Some(remote) = &self.remote_client {
3738 remote
3739 .read(cx)
3740 .proto_client()
3741 .send(proto::UpdateBuffer {
3742 project_id: 0,
3743 buffer_id: buffer_id.to_proto(),
3744 operations: vec![operation.clone()],
3745 })
3746 .ok();
3747 }
3748
3749 self.enqueue_buffer_ordered_message(BufferOrderedMessage::Operation {
3750 buffer_id,
3751 operation,
3752 })
3753 .ok();
3754 }
3755
3756 _ => {}
3757 }
3758
3759 None
3760 }
3761
3762 fn on_image_event(
3763 &mut self,
3764 image: Entity<ImageItem>,
3765 event: &ImageItemEvent,
3766 cx: &mut Context<Self>,
3767 ) -> Option<()> {
3768 // TODO: handle image events from remote
3769 if let ImageItemEvent::ReloadNeeded = event
3770 && !self.is_via_collab()
3771 {
3772 self.reload_images([image].into_iter().collect(), cx)
3773 .detach_and_log_err(cx);
3774 }
3775
3776 None
3777 }
3778
3779 fn request_buffer_diff_recalculation(
3780 &mut self,
3781 buffer: &Entity<Buffer>,
3782 cx: &mut Context<Self>,
3783 ) {
3784 self.buffers_needing_diff.insert(buffer.downgrade());
3785 let first_insertion = self.buffers_needing_diff.len() == 1;
3786 let settings = ProjectSettings::get_global(cx);
3787 let delay = settings.git.gutter_debounce;
3788
3789 if delay == 0 {
3790 if first_insertion {
3791 let this = cx.weak_entity();
3792 cx.defer(move |cx| {
3793 if let Some(this) = this.upgrade() {
3794 this.update(cx, |this, cx| {
3795 this.recalculate_buffer_diffs(cx).detach();
3796 });
3797 }
3798 });
3799 }
3800 return;
3801 }
3802
3803 const MIN_DELAY: u64 = 50;
3804 let delay = delay.max(MIN_DELAY);
3805 let duration = Duration::from_millis(delay);
3806
3807 self.git_diff_debouncer
3808 .fire_new(duration, cx, move |this, cx| {
3809 this.recalculate_buffer_diffs(cx)
3810 });
3811 }
3812
3813 fn recalculate_buffer_diffs(&mut self, cx: &mut Context<Self>) -> Task<()> {
3814 cx.spawn(async move |this, cx| {
3815 loop {
3816 let task = this
3817 .update(cx, |this, cx| {
3818 let buffers = this
3819 .buffers_needing_diff
3820 .drain()
3821 .filter_map(|buffer| buffer.upgrade())
3822 .collect::<Vec<_>>();
3823 if buffers.is_empty() {
3824 None
3825 } else {
3826 Some(this.git_store.update(cx, |git_store, cx| {
3827 git_store.recalculate_buffer_diffs(buffers, cx)
3828 }))
3829 }
3830 })
3831 .ok()
3832 .flatten();
3833
3834 if let Some(task) = task {
3835 task.await;
3836 } else {
3837 break;
3838 }
3839 }
3840 })
3841 }
3842
3843 pub fn set_language_for_buffer(
3844 &mut self,
3845 buffer: &Entity<Buffer>,
3846 new_language: Arc<Language>,
3847 cx: &mut Context<Self>,
3848 ) {
3849 self.lsp_store.update(cx, |lsp_store, cx| {
3850 lsp_store.set_language_for_buffer(buffer, new_language, cx)
3851 })
3852 }
3853
3854 pub fn restart_language_servers_for_buffers(
3855 &mut self,
3856 buffers: Vec<Entity<Buffer>>,
3857 only_restart_servers: HashSet<LanguageServerSelector>,
3858 cx: &mut Context<Self>,
3859 ) {
3860 self.lsp_store.update(cx, |lsp_store, cx| {
3861 lsp_store.restart_language_servers_for_buffers(buffers, only_restart_servers, cx)
3862 })
3863 }
3864
3865 pub fn stop_language_servers_for_buffers(
3866 &mut self,
3867 buffers: Vec<Entity<Buffer>>,
3868 also_restart_servers: HashSet<LanguageServerSelector>,
3869 cx: &mut Context<Self>,
3870 ) {
3871 self.lsp_store
3872 .update(cx, |lsp_store, cx| {
3873 lsp_store.stop_language_servers_for_buffers(buffers, also_restart_servers, cx)
3874 })
3875 .detach_and_log_err(cx);
3876 }
3877
3878 pub fn cancel_language_server_work_for_buffers(
3879 &mut self,
3880 buffers: impl IntoIterator<Item = Entity<Buffer>>,
3881 cx: &mut Context<Self>,
3882 ) {
3883 self.lsp_store.update(cx, |lsp_store, cx| {
3884 lsp_store.cancel_language_server_work_for_buffers(buffers, cx)
3885 })
3886 }
3887
3888 pub fn cancel_language_server_work(
3889 &mut self,
3890 server_id: LanguageServerId,
3891 token_to_cancel: Option<ProgressToken>,
3892 cx: &mut Context<Self>,
3893 ) {
3894 self.lsp_store.update(cx, |lsp_store, cx| {
3895 lsp_store.cancel_language_server_work(server_id, token_to_cancel, cx)
3896 })
3897 }
3898
3899 fn enqueue_buffer_ordered_message(&mut self, message: BufferOrderedMessage) -> Result<()> {
3900 self.buffer_ordered_messages_tx
3901 .unbounded_send(message)
3902 .map_err(|e| anyhow!(e))
3903 }
3904
3905 pub fn available_toolchains(
3906 &self,
3907 path: ProjectPath,
3908 language_name: LanguageName,
3909 cx: &App,
3910 ) -> Task<Option<Toolchains>> {
3911 if let Some(toolchain_store) = self.toolchain_store.as_ref().map(Entity::downgrade) {
3912 cx.spawn(async move |cx| {
3913 toolchain_store
3914 .update(cx, |this, cx| this.list_toolchains(path, language_name, cx))
3915 .ok()?
3916 .await
3917 })
3918 } else {
3919 Task::ready(None)
3920 }
3921 }
3922
3923 pub async fn toolchain_metadata(
3924 languages: Arc<LanguageRegistry>,
3925 language_name: LanguageName,
3926 ) -> Option<ToolchainMetadata> {
3927 languages
3928 .language_for_name(language_name.as_ref())
3929 .await
3930 .ok()?
3931 .toolchain_lister()
3932 .map(|lister| lister.meta())
3933 }
3934
3935 pub fn add_toolchain(
3936 &self,
3937 toolchain: Toolchain,
3938 scope: ToolchainScope,
3939 cx: &mut Context<Self>,
3940 ) {
3941 maybe!({
3942 self.toolchain_store.as_ref()?.update(cx, |this, cx| {
3943 this.add_toolchain(toolchain, scope, cx);
3944 });
3945 Some(())
3946 });
3947 }
3948
3949 pub fn remove_toolchain(
3950 &self,
3951 toolchain: Toolchain,
3952 scope: ToolchainScope,
3953 cx: &mut Context<Self>,
3954 ) {
3955 maybe!({
3956 self.toolchain_store.as_ref()?.update(cx, |this, cx| {
3957 this.remove_toolchain(toolchain, scope, cx);
3958 });
3959 Some(())
3960 });
3961 }
3962
3963 pub fn user_toolchains(
3964 &self,
3965 cx: &App,
3966 ) -> Option<BTreeMap<ToolchainScope, IndexSet<Toolchain>>> {
3967 Some(self.toolchain_store.as_ref()?.read(cx).user_toolchains())
3968 }
3969
3970 pub fn resolve_toolchain(
3971 &self,
3972 path: PathBuf,
3973 language_name: LanguageName,
3974 cx: &App,
3975 ) -> Task<Result<Toolchain>> {
3976 if let Some(toolchain_store) = self.toolchain_store.as_ref().map(Entity::downgrade) {
3977 cx.spawn(async move |cx| {
3978 toolchain_store
3979 .update(cx, |this, cx| {
3980 this.resolve_toolchain(path, language_name, cx)
3981 })?
3982 .await
3983 })
3984 } else {
3985 Task::ready(Err(anyhow!("This project does not support toolchains")))
3986 }
3987 }
3988
3989 pub fn toolchain_store(&self) -> Option<Entity<ToolchainStore>> {
3990 self.toolchain_store.clone()
3991 }
3992 pub fn activate_toolchain(
3993 &self,
3994 path: ProjectPath,
3995 toolchain: Toolchain,
3996 cx: &mut App,
3997 ) -> Task<Option<()>> {
3998 let Some(toolchain_store) = self.toolchain_store.clone() else {
3999 return Task::ready(None);
4000 };
4001 toolchain_store.update(cx, |this, cx| this.activate_toolchain(path, toolchain, cx))
4002 }
4003 pub fn active_toolchain(
4004 &self,
4005 path: ProjectPath,
4006 language_name: LanguageName,
4007 cx: &App,
4008 ) -> Task<Option<Toolchain>> {
4009 let Some(toolchain_store) = self.toolchain_store.clone() else {
4010 return Task::ready(None);
4011 };
4012 toolchain_store
4013 .read(cx)
4014 .active_toolchain(path, language_name, cx)
4015 }
4016 pub fn language_server_statuses<'a>(
4017 &'a self,
4018 cx: &'a App,
4019 ) -> impl DoubleEndedIterator<Item = (LanguageServerId, &'a LanguageServerStatus)> {
4020 self.lsp_store.read(cx).language_server_statuses()
4021 }
4022
4023 pub fn last_formatting_failure<'a>(&self, cx: &'a App) -> Option<&'a str> {
4024 self.lsp_store.read(cx).last_formatting_failure()
4025 }
4026
4027 pub fn reset_last_formatting_failure(&self, cx: &mut App) {
4028 self.lsp_store
4029 .update(cx, |store, _| store.reset_last_formatting_failure());
4030 }
4031
4032 pub fn reload_buffers(
4033 &self,
4034 buffers: HashSet<Entity<Buffer>>,
4035 push_to_history: bool,
4036 cx: &mut Context<Self>,
4037 ) -> Task<Result<ProjectTransaction>> {
4038 self.buffer_store.update(cx, |buffer_store, cx| {
4039 buffer_store.reload_buffers(buffers, push_to_history, cx)
4040 })
4041 }
4042
4043 pub fn reload_images(
4044 &self,
4045 images: HashSet<Entity<ImageItem>>,
4046 cx: &mut Context<Self>,
4047 ) -> Task<Result<()>> {
4048 self.image_store
4049 .update(cx, |image_store, cx| image_store.reload_images(images, cx))
4050 }
4051
4052 pub fn format(
4053 &mut self,
4054 buffers: HashSet<Entity<Buffer>>,
4055 target: LspFormatTarget,
4056 push_to_history: bool,
4057 trigger: lsp_store::FormatTrigger,
4058 cx: &mut Context<Project>,
4059 ) -> Task<anyhow::Result<ProjectTransaction>> {
4060 self.lsp_store.update(cx, |lsp_store, cx| {
4061 lsp_store.format(buffers, target, push_to_history, trigger, cx)
4062 })
4063 }
4064
4065 pub fn definitions<T: ToPointUtf16>(
4066 &mut self,
4067 buffer: &Entity<Buffer>,
4068 position: T,
4069 cx: &mut Context<Self>,
4070 ) -> Task<Result<Option<Vec<LocationLink>>>> {
4071 let position = position.to_point_utf16(buffer.read(cx));
4072 let guard = self.retain_remotely_created_models(cx);
4073 let task = self.lsp_store.update(cx, |lsp_store, cx| {
4074 lsp_store.definitions(buffer, position, cx)
4075 });
4076 cx.background_spawn(async move {
4077 let result = task.await;
4078 drop(guard);
4079 result
4080 })
4081 }
4082
4083 pub fn declarations<T: ToPointUtf16>(
4084 &mut self,
4085 buffer: &Entity<Buffer>,
4086 position: T,
4087 cx: &mut Context<Self>,
4088 ) -> Task<Result<Option<Vec<LocationLink>>>> {
4089 let position = position.to_point_utf16(buffer.read(cx));
4090 let guard = self.retain_remotely_created_models(cx);
4091 let task = self.lsp_store.update(cx, |lsp_store, cx| {
4092 lsp_store.declarations(buffer, position, cx)
4093 });
4094 cx.background_spawn(async move {
4095 let result = task.await;
4096 drop(guard);
4097 result
4098 })
4099 }
4100
4101 pub fn type_definitions<T: ToPointUtf16>(
4102 &mut self,
4103 buffer: &Entity<Buffer>,
4104 position: T,
4105 cx: &mut Context<Self>,
4106 ) -> Task<Result<Option<Vec<LocationLink>>>> {
4107 let position = position.to_point_utf16(buffer.read(cx));
4108 let guard = self.retain_remotely_created_models(cx);
4109 let task = self.lsp_store.update(cx, |lsp_store, cx| {
4110 lsp_store.type_definitions(buffer, position, cx)
4111 });
4112 cx.background_spawn(async move {
4113 let result = task.await;
4114 drop(guard);
4115 result
4116 })
4117 }
4118
4119 pub fn implementations<T: ToPointUtf16>(
4120 &mut self,
4121 buffer: &Entity<Buffer>,
4122 position: T,
4123 cx: &mut Context<Self>,
4124 ) -> Task<Result<Option<Vec<LocationLink>>>> {
4125 let position = position.to_point_utf16(buffer.read(cx));
4126 let guard = self.retain_remotely_created_models(cx);
4127 let task = self.lsp_store.update(cx, |lsp_store, cx| {
4128 lsp_store.implementations(buffer, position, cx)
4129 });
4130 cx.background_spawn(async move {
4131 let result = task.await;
4132 drop(guard);
4133 result
4134 })
4135 }
4136
4137 pub fn references<T: ToPointUtf16>(
4138 &mut self,
4139 buffer: &Entity<Buffer>,
4140 position: T,
4141 cx: &mut Context<Self>,
4142 ) -> Task<Result<Option<Vec<Location>>>> {
4143 let position = position.to_point_utf16(buffer.read(cx));
4144 let guard = self.retain_remotely_created_models(cx);
4145 let task = self.lsp_store.update(cx, |lsp_store, cx| {
4146 lsp_store.references(buffer, position, cx)
4147 });
4148 cx.background_spawn(async move {
4149 let result = task.await;
4150 drop(guard);
4151 result
4152 })
4153 }
4154
4155 pub fn document_highlights<T: ToPointUtf16>(
4156 &mut self,
4157 buffer: &Entity<Buffer>,
4158 position: T,
4159 cx: &mut Context<Self>,
4160 ) -> Task<Result<Vec<DocumentHighlight>>> {
4161 let position = position.to_point_utf16(buffer.read(cx));
4162 self.request_lsp(
4163 buffer.clone(),
4164 LanguageServerToQuery::FirstCapable,
4165 GetDocumentHighlights { position },
4166 cx,
4167 )
4168 }
4169
4170 pub fn document_symbols(
4171 &mut self,
4172 buffer: &Entity<Buffer>,
4173 cx: &mut Context<Self>,
4174 ) -> Task<Result<Vec<DocumentSymbol>>> {
4175 self.request_lsp(
4176 buffer.clone(),
4177 LanguageServerToQuery::FirstCapable,
4178 GetDocumentSymbols,
4179 cx,
4180 )
4181 }
4182
4183 pub fn symbols(&self, query: &str, cx: &mut Context<Self>) -> Task<Result<Vec<Symbol>>> {
4184 self.lsp_store
4185 .update(cx, |lsp_store, cx| lsp_store.symbols(query, cx))
4186 }
4187
4188 pub fn open_buffer_for_symbol(
4189 &mut self,
4190 symbol: &Symbol,
4191 cx: &mut Context<Self>,
4192 ) -> Task<Result<Entity<Buffer>>> {
4193 self.lsp_store.update(cx, |lsp_store, cx| {
4194 lsp_store.open_buffer_for_symbol(symbol, cx)
4195 })
4196 }
4197
4198 pub fn open_server_settings(&mut self, cx: &mut Context<Self>) -> Task<Result<Entity<Buffer>>> {
4199 let guard = self.retain_remotely_created_models(cx);
4200 let Some(remote) = self.remote_client.as_ref() else {
4201 return Task::ready(Err(anyhow!("not an ssh project")));
4202 };
4203
4204 let proto_client = remote.read(cx).proto_client();
4205
4206 cx.spawn(async move |project, cx| {
4207 let buffer = proto_client
4208 .request(proto::OpenServerSettings {
4209 project_id: REMOTE_SERVER_PROJECT_ID,
4210 })
4211 .await?;
4212
4213 let buffer = project
4214 .update(cx, |project, cx| {
4215 project.buffer_store.update(cx, |buffer_store, cx| {
4216 anyhow::Ok(
4217 buffer_store
4218 .wait_for_remote_buffer(BufferId::new(buffer.buffer_id)?, cx),
4219 )
4220 })
4221 })??
4222 .await;
4223
4224 drop(guard);
4225 buffer
4226 })
4227 }
4228
4229 pub fn open_local_buffer_via_lsp(
4230 &mut self,
4231 abs_path: lsp::Uri,
4232 language_server_id: LanguageServerId,
4233 cx: &mut Context<Self>,
4234 ) -> Task<Result<Entity<Buffer>>> {
4235 self.lsp_store.update(cx, |lsp_store, cx| {
4236 lsp_store.open_local_buffer_via_lsp(abs_path, language_server_id, cx)
4237 })
4238 }
4239
4240 pub fn hover<T: ToPointUtf16>(
4241 &self,
4242 buffer: &Entity<Buffer>,
4243 position: T,
4244 cx: &mut Context<Self>,
4245 ) -> Task<Option<Vec<Hover>>> {
4246 let position = position.to_point_utf16(buffer.read(cx));
4247 self.lsp_store
4248 .update(cx, |lsp_store, cx| lsp_store.hover(buffer, position, cx))
4249 }
4250
4251 pub fn linked_edits(
4252 &self,
4253 buffer: &Entity<Buffer>,
4254 position: Anchor,
4255 cx: &mut Context<Self>,
4256 ) -> Task<Result<Vec<Range<Anchor>>>> {
4257 self.lsp_store.update(cx, |lsp_store, cx| {
4258 lsp_store.linked_edits(buffer, position, cx)
4259 })
4260 }
4261
4262 pub fn completions<T: ToOffset + ToPointUtf16>(
4263 &self,
4264 buffer: &Entity<Buffer>,
4265 position: T,
4266 context: CompletionContext,
4267 cx: &mut Context<Self>,
4268 ) -> Task<Result<Vec<CompletionResponse>>> {
4269 let position = position.to_point_utf16(buffer.read(cx));
4270 self.lsp_store.update(cx, |lsp_store, cx| {
4271 lsp_store.completions(buffer, position, context, cx)
4272 })
4273 }
4274
4275 pub fn code_actions<T: Clone + ToOffset>(
4276 &mut self,
4277 buffer_handle: &Entity<Buffer>,
4278 range: Range<T>,
4279 kinds: Option<Vec<CodeActionKind>>,
4280 cx: &mut Context<Self>,
4281 ) -> Task<Result<Option<Vec<CodeAction>>>> {
4282 let buffer = buffer_handle.read(cx);
4283 let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
4284 self.lsp_store.update(cx, |lsp_store, cx| {
4285 lsp_store.code_actions(buffer_handle, range, kinds, cx)
4286 })
4287 }
4288
4289 pub fn code_lens_actions<T: Clone + ToOffset>(
4290 &mut self,
4291 buffer: &Entity<Buffer>,
4292 range: Range<T>,
4293 cx: &mut Context<Self>,
4294 ) -> Task<Result<Option<Vec<CodeAction>>>> {
4295 let snapshot = buffer.read(cx).snapshot();
4296 let range = range.to_point(&snapshot);
4297 let range_start = snapshot.anchor_before(range.start);
4298 let range_end = if range.start == range.end {
4299 range_start
4300 } else {
4301 snapshot.anchor_after(range.end)
4302 };
4303 let range = range_start..range_end;
4304 let code_lens_actions = self
4305 .lsp_store
4306 .update(cx, |lsp_store, cx| lsp_store.code_lens_actions(buffer, cx));
4307
4308 cx.background_spawn(async move {
4309 let mut code_lens_actions = code_lens_actions
4310 .await
4311 .map_err(|e| anyhow!("code lens fetch failed: {e:#}"))?;
4312 if let Some(code_lens_actions) = &mut code_lens_actions {
4313 code_lens_actions.retain(|code_lens_action| {
4314 range
4315 .start
4316 .cmp(&code_lens_action.range.start, &snapshot)
4317 .is_ge()
4318 && range
4319 .end
4320 .cmp(&code_lens_action.range.end, &snapshot)
4321 .is_le()
4322 });
4323 }
4324 Ok(code_lens_actions)
4325 })
4326 }
4327
4328 pub fn apply_code_action(
4329 &self,
4330 buffer_handle: Entity<Buffer>,
4331 action: CodeAction,
4332 push_to_history: bool,
4333 cx: &mut Context<Self>,
4334 ) -> Task<Result<ProjectTransaction>> {
4335 self.lsp_store.update(cx, |lsp_store, cx| {
4336 lsp_store.apply_code_action(buffer_handle, action, push_to_history, cx)
4337 })
4338 }
4339
4340 pub fn apply_code_action_kind(
4341 &self,
4342 buffers: HashSet<Entity<Buffer>>,
4343 kind: CodeActionKind,
4344 push_to_history: bool,
4345 cx: &mut Context<Self>,
4346 ) -> Task<Result<ProjectTransaction>> {
4347 self.lsp_store.update(cx, |lsp_store, cx| {
4348 lsp_store.apply_code_action_kind(buffers, kind, push_to_history, cx)
4349 })
4350 }
4351
4352 pub fn prepare_rename<T: ToPointUtf16>(
4353 &mut self,
4354 buffer: Entity<Buffer>,
4355 position: T,
4356 cx: &mut Context<Self>,
4357 ) -> Task<Result<PrepareRenameResponse>> {
4358 let position = position.to_point_utf16(buffer.read(cx));
4359 self.request_lsp(
4360 buffer,
4361 LanguageServerToQuery::FirstCapable,
4362 PrepareRename { position },
4363 cx,
4364 )
4365 }
4366
4367 pub fn perform_rename<T: ToPointUtf16>(
4368 &mut self,
4369 buffer: Entity<Buffer>,
4370 position: T,
4371 new_name: String,
4372 cx: &mut Context<Self>,
4373 ) -> Task<Result<ProjectTransaction>> {
4374 let push_to_history = true;
4375 let position = position.to_point_utf16(buffer.read(cx));
4376 self.request_lsp(
4377 buffer,
4378 LanguageServerToQuery::FirstCapable,
4379 PerformRename {
4380 position,
4381 new_name,
4382 push_to_history,
4383 },
4384 cx,
4385 )
4386 }
4387
4388 pub fn on_type_format<T: ToPointUtf16>(
4389 &mut self,
4390 buffer: Entity<Buffer>,
4391 position: T,
4392 trigger: String,
4393 push_to_history: bool,
4394 cx: &mut Context<Self>,
4395 ) -> Task<Result<Option<Transaction>>> {
4396 self.lsp_store.update(cx, |lsp_store, cx| {
4397 lsp_store.on_type_format(buffer, position, trigger, push_to_history, cx)
4398 })
4399 }
4400
4401 pub fn inline_values(
4402 &mut self,
4403 session: Entity<Session>,
4404 active_stack_frame: ActiveStackFrame,
4405 buffer_handle: Entity<Buffer>,
4406 range: Range<text::Anchor>,
4407 cx: &mut Context<Self>,
4408 ) -> Task<anyhow::Result<Vec<InlayHint>>> {
4409 let snapshot = buffer_handle.read(cx).snapshot();
4410
4411 let captures =
4412 snapshot.debug_variables_query(Anchor::min_for_buffer(snapshot.remote_id())..range.end);
4413
4414 let row = snapshot
4415 .summary_for_anchor::<text::PointUtf16>(&range.end)
4416 .row as usize;
4417
4418 let inline_value_locations = provide_inline_values(captures, &snapshot, row);
4419
4420 let stack_frame_id = active_stack_frame.stack_frame_id;
4421 cx.spawn(async move |this, cx| {
4422 this.update(cx, |project, cx| {
4423 project.dap_store().update(cx, |dap_store, cx| {
4424 dap_store.resolve_inline_value_locations(
4425 session,
4426 stack_frame_id,
4427 buffer_handle,
4428 inline_value_locations,
4429 cx,
4430 )
4431 })
4432 })?
4433 .await
4434 })
4435 }
4436
4437 fn search_impl(&mut self, query: SearchQuery, cx: &mut Context<Self>) -> SearchResultsHandle {
4438 let client: Option<(AnyProtoClient, _)> = if let Some(ssh_client) = &self.remote_client {
4439 Some((ssh_client.read(cx).proto_client(), 0))
4440 } else if let Some(remote_id) = self.remote_id() {
4441 self.is_local()
4442 .not()
4443 .then(|| (self.collab_client.clone().into(), remote_id))
4444 } else {
4445 None
4446 };
4447 let searcher = if query.is_opened_only() {
4448 project_search::Search::open_buffers_only(
4449 self.buffer_store.clone(),
4450 self.worktree_store.clone(),
4451 project_search::Search::MAX_SEARCH_RESULT_FILES + 1,
4452 )
4453 } else {
4454 match client {
4455 Some((client, remote_id)) => project_search::Search::remote(
4456 self.buffer_store.clone(),
4457 self.worktree_store.clone(),
4458 project_search::Search::MAX_SEARCH_RESULT_FILES + 1,
4459 (client, remote_id, self.remotely_created_models.clone()),
4460 ),
4461 None => project_search::Search::local(
4462 self.fs.clone(),
4463 self.buffer_store.clone(),
4464 self.worktree_store.clone(),
4465 project_search::Search::MAX_SEARCH_RESULT_FILES + 1,
4466 cx,
4467 ),
4468 }
4469 };
4470 searcher.into_handle(query, cx)
4471 }
4472
4473 pub fn search(
4474 &mut self,
4475 query: SearchQuery,
4476 cx: &mut Context<Self>,
4477 ) -> SearchResults<SearchResult> {
4478 self.search_impl(query, cx).results(cx)
4479 }
4480
4481 pub fn request_lsp<R: LspCommand>(
4482 &mut self,
4483 buffer_handle: Entity<Buffer>,
4484 server: LanguageServerToQuery,
4485 request: R,
4486 cx: &mut Context<Self>,
4487 ) -> Task<Result<R::Response>>
4488 where
4489 <R::LspRequest as lsp::request::Request>::Result: Send,
4490 <R::LspRequest as lsp::request::Request>::Params: Send,
4491 {
4492 let guard = self.retain_remotely_created_models(cx);
4493 let task = self.lsp_store.update(cx, |lsp_store, cx| {
4494 lsp_store.request_lsp(buffer_handle, server, request, cx)
4495 });
4496 cx.background_spawn(async move {
4497 let result = task.await;
4498 drop(guard);
4499 result
4500 })
4501 }
4502
4503 /// Move a worktree to a new position in the worktree order.
4504 ///
4505 /// The worktree will moved to the opposite side of the destination worktree.
4506 ///
4507 /// # Example
4508 ///
4509 /// Given the worktree order `[11, 22, 33]` and a call to move worktree `22` to `33`,
4510 /// worktree_order will be updated to produce the indexes `[11, 33, 22]`.
4511 ///
4512 /// Given the worktree order `[11, 22, 33]` and a call to move worktree `22` to `11`,
4513 /// worktree_order will be updated to produce the indexes `[22, 11, 33]`.
4514 ///
4515 /// # Errors
4516 ///
4517 /// An error will be returned if the worktree or destination worktree are not found.
4518 pub fn move_worktree(
4519 &mut self,
4520 source: WorktreeId,
4521 destination: WorktreeId,
4522 cx: &mut Context<Self>,
4523 ) -> Result<()> {
4524 self.worktree_store.update(cx, |worktree_store, cx| {
4525 worktree_store.move_worktree(source, destination, cx)
4526 })
4527 }
4528
4529 /// 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.
4530 pub fn try_windows_path_to_wsl(
4531 &self,
4532 abs_path: &Path,
4533 cx: &App,
4534 ) -> impl Future<Output = Result<PathBuf>> + use<> {
4535 let fut = if cfg!(windows)
4536 && let (
4537 ProjectClientState::Local | ProjectClientState::Shared { .. },
4538 Some(remote_client),
4539 ) = (&self.client_state, &self.remote_client)
4540 && let RemoteConnectionOptions::Wsl(wsl) = remote_client.read(cx).connection_options()
4541 {
4542 Either::Left(wsl.abs_windows_path_to_wsl_path(abs_path))
4543 } else {
4544 Either::Right(abs_path.to_owned())
4545 };
4546 async move {
4547 match fut {
4548 Either::Left(fut) => fut.await.map(Into::into),
4549 Either::Right(path) => Ok(path),
4550 }
4551 }
4552 }
4553
4554 pub fn find_or_create_worktree(
4555 &mut self,
4556 abs_path: impl AsRef<Path>,
4557 visible: bool,
4558 cx: &mut Context<Self>,
4559 ) -> Task<Result<(Entity<Worktree>, Arc<RelPath>)>> {
4560 self.worktree_store.update(cx, |worktree_store, cx| {
4561 worktree_store.find_or_create_worktree(abs_path, visible, cx)
4562 })
4563 }
4564
4565 pub fn find_worktree(
4566 &self,
4567 abs_path: &Path,
4568 cx: &App,
4569 ) -> Option<(Entity<Worktree>, Arc<RelPath>)> {
4570 self.worktree_store.read(cx).find_worktree(abs_path, cx)
4571 }
4572
4573 pub fn is_shared(&self) -> bool {
4574 match &self.client_state {
4575 ProjectClientState::Shared { .. } => true,
4576 ProjectClientState::Local => false,
4577 ProjectClientState::Collab { .. } => true,
4578 }
4579 }
4580
4581 /// Returns the resolved version of `path`, that was found in `buffer`, if it exists.
4582 pub fn resolve_path_in_buffer(
4583 &self,
4584 path: &str,
4585 buffer: &Entity<Buffer>,
4586 cx: &mut Context<Self>,
4587 ) -> Task<Option<ResolvedPath>> {
4588 if util::paths::is_absolute(path, self.path_style(cx)) || path.starts_with("~") {
4589 self.resolve_abs_path(path, cx)
4590 } else {
4591 self.resolve_path_in_worktrees(path, buffer, cx)
4592 }
4593 }
4594
4595 pub fn resolve_abs_file_path(
4596 &self,
4597 path: &str,
4598 cx: &mut Context<Self>,
4599 ) -> Task<Option<ResolvedPath>> {
4600 let resolve_task = self.resolve_abs_path(path, cx);
4601 cx.background_spawn(async move {
4602 let resolved_path = resolve_task.await;
4603 resolved_path.filter(|path| path.is_file())
4604 })
4605 }
4606
4607 pub fn resolve_abs_path(&self, path: &str, cx: &App) -> Task<Option<ResolvedPath>> {
4608 if self.is_local() {
4609 let expanded = PathBuf::from(shellexpand::tilde(&path).into_owned());
4610 let fs = self.fs.clone();
4611 cx.background_spawn(async move {
4612 let metadata = fs.metadata(&expanded).await.ok().flatten();
4613
4614 metadata.map(|metadata| ResolvedPath::AbsPath {
4615 path: expanded.to_string_lossy().into_owned(),
4616 is_dir: metadata.is_dir,
4617 })
4618 })
4619 } else if let Some(ssh_client) = self.remote_client.as_ref() {
4620 let request = ssh_client
4621 .read(cx)
4622 .proto_client()
4623 .request(proto::GetPathMetadata {
4624 project_id: REMOTE_SERVER_PROJECT_ID,
4625 path: path.into(),
4626 });
4627 cx.background_spawn(async move {
4628 let response = request.await.log_err()?;
4629 if response.exists {
4630 Some(ResolvedPath::AbsPath {
4631 path: response.path,
4632 is_dir: response.is_dir,
4633 })
4634 } else {
4635 None
4636 }
4637 })
4638 } else {
4639 Task::ready(None)
4640 }
4641 }
4642
4643 fn resolve_path_in_worktrees(
4644 &self,
4645 path: &str,
4646 buffer: &Entity<Buffer>,
4647 cx: &mut Context<Self>,
4648 ) -> Task<Option<ResolvedPath>> {
4649 let mut candidates = vec![];
4650 let path_style = self.path_style(cx);
4651 if let Ok(path) = RelPath::new(path.as_ref(), path_style) {
4652 candidates.push(path.into_arc());
4653 }
4654
4655 if let Some(file) = buffer.read(cx).file()
4656 && let Some(dir) = file.path().parent()
4657 {
4658 if let Some(joined) = path_style.join(&*dir.display(path_style), path)
4659 && let Some(joined) = RelPath::new(joined.as_ref(), path_style).ok()
4660 {
4661 candidates.push(joined.into_arc());
4662 }
4663 }
4664
4665 let buffer_worktree_id = buffer.read(cx).file().map(|file| file.worktree_id(cx));
4666 let worktrees_with_ids: Vec<_> = self
4667 .worktrees(cx)
4668 .map(|worktree| {
4669 let id = worktree.read(cx).id();
4670 (worktree, id)
4671 })
4672 .collect();
4673
4674 cx.spawn(async move |_, cx| {
4675 if let Some(buffer_worktree_id) = buffer_worktree_id
4676 && let Some((worktree, _)) = worktrees_with_ids
4677 .iter()
4678 .find(|(_, id)| *id == buffer_worktree_id)
4679 {
4680 for candidate in candidates.iter() {
4681 if let Some(path) = Self::resolve_path_in_worktree(worktree, candidate, cx) {
4682 return Some(path);
4683 }
4684 }
4685 }
4686 for (worktree, id) in worktrees_with_ids {
4687 if Some(id) == buffer_worktree_id {
4688 continue;
4689 }
4690 for candidate in candidates.iter() {
4691 if let Some(path) = Self::resolve_path_in_worktree(&worktree, candidate, cx) {
4692 return Some(path);
4693 }
4694 }
4695 }
4696 None
4697 })
4698 }
4699
4700 fn resolve_path_in_worktree(
4701 worktree: &Entity<Worktree>,
4702 path: &RelPath,
4703 cx: &mut AsyncApp,
4704 ) -> Option<ResolvedPath> {
4705 worktree.read_with(cx, |worktree, _| {
4706 worktree.entry_for_path(path).map(|entry| {
4707 let project_path = ProjectPath {
4708 worktree_id: worktree.id(),
4709 path: entry.path.clone(),
4710 };
4711 ResolvedPath::ProjectPath {
4712 project_path,
4713 is_dir: entry.is_dir(),
4714 }
4715 })
4716 })
4717 }
4718
4719 pub fn list_directory(
4720 &self,
4721 query: String,
4722 cx: &mut Context<Self>,
4723 ) -> Task<Result<Vec<DirectoryItem>>> {
4724 if self.is_local() {
4725 DirectoryLister::Local(cx.entity(), self.fs.clone()).list_directory(query, cx)
4726 } else if let Some(session) = self.remote_client.as_ref() {
4727 let request = proto::ListRemoteDirectory {
4728 dev_server_id: REMOTE_SERVER_PROJECT_ID,
4729 path: query,
4730 config: Some(proto::ListRemoteDirectoryConfig { is_dir: true }),
4731 };
4732
4733 let response = session.read(cx).proto_client().request(request);
4734 cx.background_spawn(async move {
4735 let proto::ListRemoteDirectoryResponse {
4736 entries,
4737 entry_info,
4738 } = response.await?;
4739 Ok(entries
4740 .into_iter()
4741 .zip(entry_info)
4742 .map(|(entry, info)| DirectoryItem {
4743 path: PathBuf::from(entry),
4744 is_dir: info.is_dir,
4745 })
4746 .collect())
4747 })
4748 } else {
4749 Task::ready(Err(anyhow!("cannot list directory in remote project")))
4750 }
4751 }
4752
4753 pub fn create_worktree(
4754 &mut self,
4755 abs_path: impl AsRef<Path>,
4756 visible: bool,
4757 cx: &mut Context<Self>,
4758 ) -> Task<Result<Entity<Worktree>>> {
4759 self.worktree_store.update(cx, |worktree_store, cx| {
4760 worktree_store.create_worktree(abs_path, visible, cx)
4761 })
4762 }
4763
4764 pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut Context<Self>) {
4765 self.worktree_store.update(cx, |worktree_store, cx| {
4766 worktree_store.remove_worktree(id_to_remove, cx);
4767 });
4768 }
4769
4770 pub fn remove_worktree_for_main_worktree_path(
4771 &mut self,
4772 path: impl AsRef<Path>,
4773 cx: &mut Context<Self>,
4774 ) {
4775 let path = path.as_ref();
4776 self.worktree_store.update(cx, |worktree_store, cx| {
4777 if let Some(worktree) = worktree_store.worktree_for_main_worktree_path(path, cx) {
4778 worktree_store.remove_worktree(worktree.read(cx).id(), cx);
4779 }
4780 });
4781 }
4782
4783 fn add_worktree(&mut self, worktree: &Entity<Worktree>, cx: &mut Context<Self>) {
4784 self.worktree_store.update(cx, |worktree_store, cx| {
4785 worktree_store.add(worktree, cx);
4786 });
4787 }
4788
4789 pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut Context<Self>) {
4790 let new_active_entry = entry.and_then(|project_path| {
4791 let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
4792 let entry = worktree.read(cx).entry_for_path(&project_path.path)?;
4793 Some(entry.id)
4794 });
4795 if new_active_entry != self.active_entry {
4796 self.active_entry = new_active_entry;
4797 self.lsp_store.update(cx, |lsp_store, _| {
4798 lsp_store.set_active_entry(new_active_entry);
4799 });
4800 cx.emit(Event::ActiveEntryChanged(new_active_entry));
4801 }
4802 }
4803
4804 pub fn language_servers_running_disk_based_diagnostics<'a>(
4805 &'a self,
4806 cx: &'a App,
4807 ) -> impl Iterator<Item = LanguageServerId> + 'a {
4808 self.lsp_store
4809 .read(cx)
4810 .language_servers_running_disk_based_diagnostics()
4811 }
4812
4813 pub fn diagnostic_summary(&self, include_ignored: bool, cx: &App) -> DiagnosticSummary {
4814 self.lsp_store
4815 .read(cx)
4816 .diagnostic_summary(include_ignored, cx)
4817 }
4818
4819 /// Returns a summary of the diagnostics for the provided project path only.
4820 pub fn diagnostic_summary_for_path(&self, path: &ProjectPath, cx: &App) -> DiagnosticSummary {
4821 self.lsp_store
4822 .read(cx)
4823 .diagnostic_summary_for_path(path, cx)
4824 }
4825
4826 pub fn diagnostic_summaries<'a>(
4827 &'a self,
4828 include_ignored: bool,
4829 cx: &'a App,
4830 ) -> impl Iterator<Item = (ProjectPath, LanguageServerId, DiagnosticSummary)> + 'a {
4831 self.lsp_store
4832 .read(cx)
4833 .diagnostic_summaries(include_ignored, cx)
4834 }
4835
4836 pub fn active_entry(&self) -> Option<ProjectEntryId> {
4837 self.active_entry
4838 }
4839
4840 pub fn entry_for_path<'a>(&'a self, path: &ProjectPath, cx: &'a App) -> Option<&'a Entry> {
4841 self.worktree_store.read(cx).entry_for_path(path, cx)
4842 }
4843
4844 pub fn path_for_entry(&self, entry_id: ProjectEntryId, cx: &App) -> Option<ProjectPath> {
4845 let worktree = self.worktree_for_entry(entry_id, cx)?;
4846 let worktree = worktree.read(cx);
4847 let worktree_id = worktree.id();
4848 let path = worktree.entry_for_id(entry_id)?.path.clone();
4849 Some(ProjectPath { worktree_id, path })
4850 }
4851
4852 pub fn absolute_path(&self, project_path: &ProjectPath, cx: &App) -> Option<PathBuf> {
4853 Some(
4854 self.worktree_for_id(project_path.worktree_id, cx)?
4855 .read(cx)
4856 .absolutize(&project_path.path),
4857 )
4858 }
4859
4860 /// Attempts to find a `ProjectPath` corresponding to the given path. If the path
4861 /// is a *full path*, meaning it starts with the root name of a worktree, we'll locate
4862 /// it in that worktree. Otherwise, we'll attempt to find it as a relative path in
4863 /// the first visible worktree that has an entry for that relative path.
4864 ///
4865 /// We use this to resolve edit steps, when there's a chance an LLM may omit the workree
4866 /// root name from paths.
4867 ///
4868 /// # Arguments
4869 ///
4870 /// * `path` - An absolute path, or a full path that starts with a worktree root name, or a
4871 /// relative path within a visible worktree.
4872 /// * `cx` - A reference to the `AppContext`.
4873 ///
4874 /// # Returns
4875 ///
4876 /// Returns `Some(ProjectPath)` if a matching worktree is found, otherwise `None`.
4877 pub fn find_project_path(&self, path: impl AsRef<Path>, cx: &App) -> Option<ProjectPath> {
4878 let path_style = self.path_style(cx);
4879 let path = path.as_ref();
4880 let worktree_store = self.worktree_store.read(cx);
4881
4882 if is_absolute(&path.to_string_lossy(), path_style) {
4883 for worktree in worktree_store.visible_worktrees(cx) {
4884 let worktree_abs_path = worktree.read(cx).abs_path();
4885
4886 if let Ok(relative_path) = path.strip_prefix(worktree_abs_path)
4887 && let Ok(path) = RelPath::new(relative_path, path_style)
4888 {
4889 return Some(ProjectPath {
4890 worktree_id: worktree.read(cx).id(),
4891 path: path.into_arc(),
4892 });
4893 }
4894 }
4895 } else {
4896 for worktree in worktree_store.visible_worktrees(cx) {
4897 let worktree = worktree.read(cx);
4898 if let Ok(rel_path) = RelPath::new(path, path_style) {
4899 if let Some(entry) = worktree.entry_for_path(&rel_path) {
4900 return Some(ProjectPath {
4901 worktree_id: worktree.id(),
4902 path: entry.path.clone(),
4903 });
4904 }
4905 }
4906 }
4907
4908 for worktree in worktree_store.visible_worktrees(cx) {
4909 let worktree_root_name = worktree.read(cx).root_name();
4910 if let Ok(relative_path) = path.strip_prefix(worktree_root_name.as_std_path())
4911 && let Ok(path) = RelPath::new(relative_path, path_style)
4912 {
4913 return Some(ProjectPath {
4914 worktree_id: worktree.read(cx).id(),
4915 path: path.into_arc(),
4916 });
4917 }
4918 }
4919 }
4920
4921 None
4922 }
4923
4924 /// If there's only one visible worktree, returns the given worktree-relative path with no prefix.
4925 ///
4926 /// Otherwise, returns the full path for the project path (obtained by prefixing the worktree-relative path with the name of the worktree).
4927 pub fn short_full_path_for_project_path(
4928 &self,
4929 project_path: &ProjectPath,
4930 cx: &App,
4931 ) -> Option<String> {
4932 let path_style = self.path_style(cx);
4933 if self.visible_worktrees(cx).take(2).count() < 2 {
4934 return Some(project_path.path.display(path_style).to_string());
4935 }
4936 self.worktree_for_id(project_path.worktree_id, cx)
4937 .map(|worktree| {
4938 let worktree_name = worktree.read(cx).root_name();
4939 worktree_name
4940 .join(&project_path.path)
4941 .display(path_style)
4942 .to_string()
4943 })
4944 }
4945
4946 pub fn project_path_for_absolute_path(&self, abs_path: &Path, cx: &App) -> Option<ProjectPath> {
4947 self.worktree_store
4948 .read(cx)
4949 .project_path_for_absolute_path(abs_path, cx)
4950 }
4951
4952 pub fn get_workspace_root(&self, project_path: &ProjectPath, cx: &App) -> Option<PathBuf> {
4953 Some(
4954 self.worktree_for_id(project_path.worktree_id, cx)?
4955 .read(cx)
4956 .abs_path()
4957 .to_path_buf(),
4958 )
4959 }
4960
4961 pub fn blame_buffer(
4962 &self,
4963 buffer: &Entity<Buffer>,
4964 version: Option<clock::Global>,
4965 cx: &mut App,
4966 ) -> Task<Result<Option<Blame>>> {
4967 self.git_store.update(cx, |git_store, cx| {
4968 git_store.blame_buffer(buffer, version, cx)
4969 })
4970 }
4971
4972 pub fn get_permalink_to_line(
4973 &self,
4974 buffer: &Entity<Buffer>,
4975 selection: Range<u32>,
4976 cx: &mut App,
4977 ) -> Task<Result<url::Url>> {
4978 self.git_store.update(cx, |git_store, cx| {
4979 git_store.get_permalink_to_line(buffer, selection, cx)
4980 })
4981 }
4982
4983 // RPC message handlers
4984
4985 async fn handle_unshare_project(
4986 this: Entity<Self>,
4987 _: TypedEnvelope<proto::UnshareProject>,
4988 mut cx: AsyncApp,
4989 ) -> Result<()> {
4990 this.update(&mut cx, |this, cx| {
4991 if this.is_local() || this.is_via_remote_server() {
4992 this.unshare(cx)?;
4993 } else {
4994 this.disconnected_from_host(cx);
4995 }
4996 Ok(())
4997 })
4998 }
4999
5000 async fn handle_add_collaborator(
5001 this: Entity<Self>,
5002 mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
5003 mut cx: AsyncApp,
5004 ) -> Result<()> {
5005 let collaborator = envelope
5006 .payload
5007 .collaborator
5008 .take()
5009 .context("empty collaborator")?;
5010
5011 let collaborator = Collaborator::from_proto(collaborator)?;
5012 this.update(&mut cx, |this, cx| {
5013 this.buffer_store.update(cx, |buffer_store, _| {
5014 buffer_store.forget_shared_buffers_for(&collaborator.peer_id);
5015 });
5016 this.breakpoint_store.read(cx).broadcast();
5017 cx.emit(Event::CollaboratorJoined(collaborator.peer_id));
5018 this.collaborators
5019 .insert(collaborator.peer_id, collaborator);
5020 });
5021
5022 Ok(())
5023 }
5024
5025 async fn handle_update_project_collaborator(
5026 this: Entity<Self>,
5027 envelope: TypedEnvelope<proto::UpdateProjectCollaborator>,
5028 mut cx: AsyncApp,
5029 ) -> Result<()> {
5030 let old_peer_id = envelope
5031 .payload
5032 .old_peer_id
5033 .context("missing old peer id")?;
5034 let new_peer_id = envelope
5035 .payload
5036 .new_peer_id
5037 .context("missing new peer id")?;
5038 this.update(&mut cx, |this, cx| {
5039 let collaborator = this
5040 .collaborators
5041 .remove(&old_peer_id)
5042 .context("received UpdateProjectCollaborator for unknown peer")?;
5043 let is_host = collaborator.is_host;
5044 this.collaborators.insert(new_peer_id, collaborator);
5045
5046 log::info!("peer {} became {}", old_peer_id, new_peer_id,);
5047 this.buffer_store.update(cx, |buffer_store, _| {
5048 buffer_store.update_peer_id(&old_peer_id, new_peer_id)
5049 });
5050
5051 if is_host {
5052 this.buffer_store
5053 .update(cx, |buffer_store, _| buffer_store.discard_incomplete());
5054 this.enqueue_buffer_ordered_message(BufferOrderedMessage::Resync)
5055 .unwrap();
5056 cx.emit(Event::HostReshared);
5057 }
5058
5059 cx.emit(Event::CollaboratorUpdated {
5060 old_peer_id,
5061 new_peer_id,
5062 });
5063 Ok(())
5064 })
5065 }
5066
5067 async fn handle_remove_collaborator(
5068 this: Entity<Self>,
5069 envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
5070 mut cx: AsyncApp,
5071 ) -> Result<()> {
5072 this.update(&mut cx, |this, cx| {
5073 let peer_id = envelope.payload.peer_id.context("invalid peer id")?;
5074 let replica_id = this
5075 .collaborators
5076 .remove(&peer_id)
5077 .with_context(|| format!("unknown peer {peer_id:?}"))?
5078 .replica_id;
5079 this.buffer_store.update(cx, |buffer_store, cx| {
5080 buffer_store.forget_shared_buffers_for(&peer_id);
5081 for buffer in buffer_store.buffers() {
5082 buffer.update(cx, |buffer, cx| buffer.remove_peer(replica_id, cx));
5083 }
5084 });
5085 this.git_store.update(cx, |git_store, _| {
5086 git_store.forget_shared_diffs_for(&peer_id);
5087 });
5088
5089 cx.emit(Event::CollaboratorLeft(peer_id));
5090 Ok(())
5091 })
5092 }
5093
5094 async fn handle_update_project(
5095 this: Entity<Self>,
5096 envelope: TypedEnvelope<proto::UpdateProject>,
5097 mut cx: AsyncApp,
5098 ) -> Result<()> {
5099 this.update(&mut cx, |this, cx| {
5100 // Don't handle messages that were sent before the response to us joining the project
5101 if envelope.message_id > this.join_project_response_message_id {
5102 cx.update_global::<SettingsStore, _>(|store, cx| {
5103 for worktree_metadata in &envelope.payload.worktrees {
5104 store
5105 .clear_local_settings(WorktreeId::from_proto(worktree_metadata.id), cx)
5106 .log_err();
5107 }
5108 });
5109
5110 this.set_worktrees_from_proto(envelope.payload.worktrees, cx)?;
5111 }
5112 Ok(())
5113 })
5114 }
5115
5116 async fn handle_toast(
5117 this: Entity<Self>,
5118 envelope: TypedEnvelope<proto::Toast>,
5119 mut cx: AsyncApp,
5120 ) -> Result<()> {
5121 this.update(&mut cx, |_, cx| {
5122 cx.emit(Event::Toast {
5123 notification_id: envelope.payload.notification_id.into(),
5124 message: envelope.payload.message,
5125 link: None,
5126 });
5127 Ok(())
5128 })
5129 }
5130
5131 async fn handle_language_server_prompt_request(
5132 this: Entity<Self>,
5133 envelope: TypedEnvelope<proto::LanguageServerPromptRequest>,
5134 mut cx: AsyncApp,
5135 ) -> Result<proto::LanguageServerPromptResponse> {
5136 let (tx, rx) = smol::channel::bounded(1);
5137 let actions: Vec<_> = envelope
5138 .payload
5139 .actions
5140 .into_iter()
5141 .map(|action| MessageActionItem {
5142 title: action,
5143 properties: Default::default(),
5144 })
5145 .collect();
5146 this.update(&mut cx, |_, cx| {
5147 cx.emit(Event::LanguageServerPrompt(
5148 LanguageServerPromptRequest::new(
5149 proto_to_prompt(envelope.payload.level.context("Invalid prompt level")?),
5150 envelope.payload.message,
5151 actions.clone(),
5152 envelope.payload.lsp_name,
5153 tx,
5154 ),
5155 ));
5156
5157 anyhow::Ok(())
5158 })?;
5159
5160 // We drop `this` to avoid holding a reference in this future for too
5161 // long.
5162 // If we keep the reference, we might not drop the `Project` early
5163 // enough when closing a window and it will only get releases on the
5164 // next `flush_effects()` call.
5165 drop(this);
5166
5167 let mut rx = pin!(rx);
5168 let answer = rx.next().await;
5169
5170 Ok(LanguageServerPromptResponse {
5171 action_response: answer.and_then(|answer| {
5172 actions
5173 .iter()
5174 .position(|action| *action == answer)
5175 .map(|index| index as u64)
5176 }),
5177 })
5178 }
5179
5180 async fn handle_hide_toast(
5181 this: Entity<Self>,
5182 envelope: TypedEnvelope<proto::HideToast>,
5183 mut cx: AsyncApp,
5184 ) -> Result<()> {
5185 this.update(&mut cx, |_, cx| {
5186 cx.emit(Event::HideToast {
5187 notification_id: envelope.payload.notification_id.into(),
5188 });
5189 Ok(())
5190 })
5191 }
5192
5193 // Collab sends UpdateWorktree protos as messages
5194 async fn handle_update_worktree(
5195 this: Entity<Self>,
5196 envelope: TypedEnvelope<proto::UpdateWorktree>,
5197 mut cx: AsyncApp,
5198 ) -> Result<()> {
5199 this.update(&mut cx, |project, cx| {
5200 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
5201 if let Some(worktree) = project.worktree_for_id(worktree_id, cx) {
5202 worktree.update(cx, |worktree, _| {
5203 let worktree = worktree.as_remote_mut().unwrap();
5204 worktree.update_from_remote(envelope.payload);
5205 });
5206 }
5207 Ok(())
5208 })
5209 }
5210
5211 async fn handle_update_buffer_from_remote_server(
5212 this: Entity<Self>,
5213 envelope: TypedEnvelope<proto::UpdateBuffer>,
5214 cx: AsyncApp,
5215 ) -> Result<proto::Ack> {
5216 let buffer_store = this.read_with(&cx, |this, cx| {
5217 if let Some(remote_id) = this.remote_id() {
5218 let mut payload = envelope.payload.clone();
5219 payload.project_id = remote_id;
5220 cx.background_spawn(this.collab_client.request(payload))
5221 .detach_and_log_err(cx);
5222 }
5223 this.buffer_store.clone()
5224 });
5225 BufferStore::handle_update_buffer(buffer_store, envelope, cx).await
5226 }
5227
5228 async fn handle_trust_worktrees(
5229 this: Entity<Self>,
5230 envelope: TypedEnvelope<proto::TrustWorktrees>,
5231 mut cx: AsyncApp,
5232 ) -> Result<proto::Ack> {
5233 if this.read_with(&cx, |project, _| project.is_via_collab()) {
5234 return Ok(proto::Ack {});
5235 }
5236
5237 let trusted_worktrees = cx
5238 .update(|cx| TrustedWorktrees::try_get_global(cx))
5239 .context("missing trusted worktrees")?;
5240 trusted_worktrees.update(&mut cx, |trusted_worktrees, cx| {
5241 trusted_worktrees.trust(
5242 &this.read(cx).worktree_store(),
5243 envelope
5244 .payload
5245 .trusted_paths
5246 .into_iter()
5247 .filter_map(|proto_path| PathTrust::from_proto(proto_path))
5248 .collect(),
5249 cx,
5250 );
5251 });
5252 Ok(proto::Ack {})
5253 }
5254
5255 async fn handle_restrict_worktrees(
5256 this: Entity<Self>,
5257 envelope: TypedEnvelope<proto::RestrictWorktrees>,
5258 mut cx: AsyncApp,
5259 ) -> Result<proto::Ack> {
5260 if this.read_with(&cx, |project, _| project.is_via_collab()) {
5261 return Ok(proto::Ack {});
5262 }
5263
5264 let trusted_worktrees = cx
5265 .update(|cx| TrustedWorktrees::try_get_global(cx))
5266 .context("missing trusted worktrees")?;
5267 trusted_worktrees.update(&mut cx, |trusted_worktrees, cx| {
5268 let worktree_store = this.read(cx).worktree_store().downgrade();
5269 let restricted_paths = envelope
5270 .payload
5271 .worktree_ids
5272 .into_iter()
5273 .map(WorktreeId::from_proto)
5274 .map(PathTrust::Worktree)
5275 .collect::<HashSet<_>>();
5276 trusted_worktrees.restrict(worktree_store, restricted_paths, cx);
5277 });
5278 Ok(proto::Ack {})
5279 }
5280
5281 // Goes from host to client.
5282 async fn handle_find_search_candidates_chunk(
5283 this: Entity<Self>,
5284 envelope: TypedEnvelope<proto::FindSearchCandidatesChunk>,
5285 mut cx: AsyncApp,
5286 ) -> Result<proto::Ack> {
5287 let buffer_store = this.read_with(&mut cx, |this, _| this.buffer_store.clone());
5288 BufferStore::handle_find_search_candidates_chunk(buffer_store, envelope, cx).await
5289 }
5290
5291 // Goes from client to host.
5292 async fn handle_find_search_candidates_cancel(
5293 this: Entity<Self>,
5294 envelope: TypedEnvelope<proto::FindSearchCandidatesCancelled>,
5295 mut cx: AsyncApp,
5296 ) -> Result<()> {
5297 let buffer_store = this.read_with(&mut cx, |this, _| this.buffer_store.clone());
5298 BufferStore::handle_find_search_candidates_cancel(buffer_store, envelope, cx).await
5299 }
5300
5301 async fn handle_update_buffer(
5302 this: Entity<Self>,
5303 envelope: TypedEnvelope<proto::UpdateBuffer>,
5304 cx: AsyncApp,
5305 ) -> Result<proto::Ack> {
5306 let buffer_store = this.read_with(&cx, |this, cx| {
5307 if let Some(ssh) = &this.remote_client {
5308 let mut payload = envelope.payload.clone();
5309 payload.project_id = REMOTE_SERVER_PROJECT_ID;
5310 cx.background_spawn(ssh.read(cx).proto_client().request(payload))
5311 .detach_and_log_err(cx);
5312 }
5313 this.buffer_store.clone()
5314 });
5315 BufferStore::handle_update_buffer(buffer_store, envelope, cx).await
5316 }
5317
5318 fn retain_remotely_created_models(
5319 &mut self,
5320 cx: &mut Context<Self>,
5321 ) -> RemotelyCreatedModelGuard {
5322 Self::retain_remotely_created_models_impl(
5323 &self.remotely_created_models,
5324 &self.buffer_store,
5325 &self.worktree_store,
5326 cx,
5327 )
5328 }
5329
5330 fn retain_remotely_created_models_impl(
5331 models: &Arc<Mutex<RemotelyCreatedModels>>,
5332 buffer_store: &Entity<BufferStore>,
5333 worktree_store: &Entity<WorktreeStore>,
5334 cx: &mut App,
5335 ) -> RemotelyCreatedModelGuard {
5336 {
5337 let mut remotely_create_models = models.lock();
5338 if remotely_create_models.retain_count == 0 {
5339 remotely_create_models.buffers = buffer_store.read(cx).buffers().collect();
5340 remotely_create_models.worktrees = worktree_store.read(cx).worktrees().collect();
5341 }
5342 remotely_create_models.retain_count += 1;
5343 }
5344 RemotelyCreatedModelGuard {
5345 remote_models: Arc::downgrade(&models),
5346 }
5347 }
5348
5349 async fn handle_create_buffer_for_peer(
5350 this: Entity<Self>,
5351 envelope: TypedEnvelope<proto::CreateBufferForPeer>,
5352 mut cx: AsyncApp,
5353 ) -> Result<()> {
5354 this.update(&mut cx, |this, cx| {
5355 this.buffer_store.update(cx, |buffer_store, cx| {
5356 buffer_store.handle_create_buffer_for_peer(
5357 envelope,
5358 this.replica_id(),
5359 this.capability(),
5360 cx,
5361 )
5362 })
5363 })
5364 }
5365
5366 async fn handle_toggle_lsp_logs(
5367 project: Entity<Self>,
5368 envelope: TypedEnvelope<proto::ToggleLspLogs>,
5369 mut cx: AsyncApp,
5370 ) -> Result<()> {
5371 let toggled_log_kind =
5372 match proto::toggle_lsp_logs::LogType::from_i32(envelope.payload.log_type)
5373 .context("invalid log type")?
5374 {
5375 proto::toggle_lsp_logs::LogType::Log => LogKind::Logs,
5376 proto::toggle_lsp_logs::LogType::Trace => LogKind::Trace,
5377 proto::toggle_lsp_logs::LogType::Rpc => LogKind::Rpc,
5378 };
5379 project.update(&mut cx, |_, cx| {
5380 cx.emit(Event::ToggleLspLogs {
5381 server_id: LanguageServerId::from_proto(envelope.payload.server_id),
5382 enabled: envelope.payload.enabled,
5383 toggled_log_kind,
5384 })
5385 });
5386 Ok(())
5387 }
5388
5389 async fn handle_synchronize_buffers(
5390 this: Entity<Self>,
5391 envelope: TypedEnvelope<proto::SynchronizeBuffers>,
5392 mut cx: AsyncApp,
5393 ) -> Result<proto::SynchronizeBuffersResponse> {
5394 let response = this.update(&mut cx, |this, cx| {
5395 let client = this.collab_client.clone();
5396 this.buffer_store.update(cx, |this, cx| {
5397 this.handle_synchronize_buffers(envelope, cx, client)
5398 })
5399 })?;
5400
5401 Ok(response)
5402 }
5403
5404 // Goes from client to host.
5405 async fn handle_search_candidate_buffers(
5406 this: Entity<Self>,
5407 envelope: TypedEnvelope<proto::FindSearchCandidates>,
5408 mut cx: AsyncApp,
5409 ) -> Result<proto::Ack> {
5410 let peer_id = envelope.original_sender_id.unwrap_or(envelope.sender_id);
5411 let message = envelope.payload;
5412 let project_id = message.project_id;
5413 let path_style = this.read_with(&cx, |this, cx| this.path_style(cx));
5414 let query =
5415 SearchQuery::from_proto(message.query.context("missing query field")?, path_style)?;
5416
5417 let handle = message.handle;
5418 let buffer_store = this.read_with(&cx, |this, _| this.buffer_store().clone());
5419 let client = this.read_with(&cx, |this, _| this.client());
5420 let task = cx.spawn(async move |cx| {
5421 let results = this.update(cx, |this, cx| {
5422 this.search_impl(query, cx).matching_buffers(cx)
5423 });
5424 let (batcher, batches) = project_search::AdaptiveBatcher::new(cx.background_executor());
5425 let mut new_matches = Box::pin(results.rx);
5426
5427 let sender_task = cx.background_executor().spawn({
5428 let client = client.clone();
5429 async move {
5430 let mut batches = std::pin::pin!(batches);
5431 while let Some(buffer_ids) = batches.next().await {
5432 client
5433 .request(proto::FindSearchCandidatesChunk {
5434 handle,
5435 peer_id: Some(peer_id),
5436 project_id,
5437 variant: Some(
5438 proto::find_search_candidates_chunk::Variant::Matches(
5439 proto::FindSearchCandidatesMatches { buffer_ids },
5440 ),
5441 ),
5442 })
5443 .await?;
5444 }
5445 anyhow::Ok(())
5446 }
5447 });
5448
5449 while let Some(buffer) = new_matches.next().await {
5450 let buffer_id = this.update(cx, |this, cx| {
5451 this.create_buffer_for_peer(&buffer, peer_id, cx).to_proto()
5452 });
5453 batcher.push(buffer_id).await;
5454 }
5455 batcher.flush().await;
5456
5457 sender_task.await?;
5458
5459 let _ = client
5460 .request(proto::FindSearchCandidatesChunk {
5461 handle,
5462 peer_id: Some(peer_id),
5463 project_id,
5464 variant: Some(proto::find_search_candidates_chunk::Variant::Done(
5465 proto::FindSearchCandidatesDone {},
5466 )),
5467 })
5468 .await?;
5469 anyhow::Ok(())
5470 });
5471 buffer_store.update(&mut cx, |this, _| {
5472 this.register_ongoing_project_search((peer_id, handle), task);
5473 });
5474
5475 Ok(proto::Ack {})
5476 }
5477
5478 async fn handle_open_buffer_by_id(
5479 this: Entity<Self>,
5480 envelope: TypedEnvelope<proto::OpenBufferById>,
5481 mut cx: AsyncApp,
5482 ) -> Result<proto::OpenBufferResponse> {
5483 let peer_id = envelope.original_sender_id()?;
5484 let buffer_id = BufferId::new(envelope.payload.id)?;
5485 let buffer = this
5486 .update(&mut cx, |this, cx| this.open_buffer_by_id(buffer_id, cx))
5487 .await?;
5488 Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
5489 }
5490
5491 async fn handle_open_buffer_by_path(
5492 this: Entity<Self>,
5493 envelope: TypedEnvelope<proto::OpenBufferByPath>,
5494 mut cx: AsyncApp,
5495 ) -> Result<proto::OpenBufferResponse> {
5496 let peer_id = envelope.original_sender_id()?;
5497 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
5498 let path = RelPath::from_proto(&envelope.payload.path)?;
5499 let open_buffer = this
5500 .update(&mut cx, |this, cx| {
5501 this.open_buffer(ProjectPath { worktree_id, path }, cx)
5502 })
5503 .await?;
5504 Project::respond_to_open_buffer_request(this, open_buffer, peer_id, &mut cx)
5505 }
5506
5507 async fn handle_open_new_buffer(
5508 this: Entity<Self>,
5509 envelope: TypedEnvelope<proto::OpenNewBuffer>,
5510 mut cx: AsyncApp,
5511 ) -> Result<proto::OpenBufferResponse> {
5512 let buffer = this
5513 .update(&mut cx, |this, cx| this.create_buffer(None, true, cx))
5514 .await?;
5515 let peer_id = envelope.original_sender_id()?;
5516
5517 Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
5518 }
5519
5520 fn respond_to_open_buffer_request(
5521 this: Entity<Self>,
5522 buffer: Entity<Buffer>,
5523 peer_id: proto::PeerId,
5524 cx: &mut AsyncApp,
5525 ) -> Result<proto::OpenBufferResponse> {
5526 this.update(cx, |this, cx| {
5527 let is_private = buffer
5528 .read(cx)
5529 .file()
5530 .map(|f| f.is_private())
5531 .unwrap_or_default();
5532 anyhow::ensure!(!is_private, ErrorCode::UnsharedItem);
5533 Ok(proto::OpenBufferResponse {
5534 buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx).into(),
5535 })
5536 })
5537 }
5538
5539 fn create_buffer_for_peer(
5540 &mut self,
5541 buffer: &Entity<Buffer>,
5542 peer_id: proto::PeerId,
5543 cx: &mut App,
5544 ) -> BufferId {
5545 self.buffer_store
5546 .update(cx, |buffer_store, cx| {
5547 buffer_store.create_buffer_for_peer(buffer, peer_id, cx)
5548 })
5549 .detach_and_log_err(cx);
5550 buffer.read(cx).remote_id()
5551 }
5552
5553 async fn handle_create_image_for_peer(
5554 this: Entity<Self>,
5555 envelope: TypedEnvelope<proto::CreateImageForPeer>,
5556 mut cx: AsyncApp,
5557 ) -> Result<()> {
5558 this.update(&mut cx, |this, cx| {
5559 this.image_store.update(cx, |image_store, cx| {
5560 image_store.handle_create_image_for_peer(envelope, cx)
5561 })
5562 })
5563 }
5564
5565 async fn handle_create_file_for_peer(
5566 this: Entity<Self>,
5567 envelope: TypedEnvelope<proto::CreateFileForPeer>,
5568 mut cx: AsyncApp,
5569 ) -> Result<()> {
5570 use proto::create_file_for_peer::Variant;
5571 log::debug!("handle_create_file_for_peer: received message");
5572
5573 let downloading_files: Arc<Mutex<HashMap<(WorktreeId, String), DownloadingFile>>> =
5574 this.update(&mut cx, |this, _| this.downloading_files.clone());
5575
5576 match &envelope.payload.variant {
5577 Some(Variant::State(state)) => {
5578 log::debug!(
5579 "handle_create_file_for_peer: got State: id={}, content_size={}",
5580 state.id,
5581 state.content_size
5582 );
5583
5584 // Extract worktree_id and path from the File field
5585 if let Some(ref file) = state.file {
5586 let worktree_id = WorktreeId::from_proto(file.worktree_id);
5587 let path = file.path.clone();
5588 let key = (worktree_id, path);
5589 log::debug!("handle_create_file_for_peer: looking up key={:?}", key);
5590
5591 let empty_file_destination: Option<PathBuf> = {
5592 let mut files = downloading_files.lock();
5593 log::trace!(
5594 "handle_create_file_for_peer: current downloading_files keys: {:?}",
5595 files.keys().collect::<Vec<_>>()
5596 );
5597
5598 if let Some(file_entry) = files.get_mut(&key) {
5599 file_entry.total_size = state.content_size;
5600 file_entry.file_id = Some(state.id);
5601 log::debug!(
5602 "handle_create_file_for_peer: updated file entry: total_size={}, file_id={}",
5603 state.content_size,
5604 state.id
5605 );
5606 } else {
5607 log::warn!(
5608 "handle_create_file_for_peer: key={:?} not found in downloading_files",
5609 key
5610 );
5611 }
5612
5613 if state.content_size == 0 {
5614 // No chunks will arrive for an empty file; write it now.
5615 files.remove(&key).map(|entry| entry.destination_path)
5616 } else {
5617 None
5618 }
5619 };
5620
5621 if let Some(destination) = empty_file_destination {
5622 log::debug!(
5623 "handle_create_file_for_peer: writing empty file to {:?}",
5624 destination
5625 );
5626 match smol::fs::write(&destination, &[] as &[u8]).await {
5627 Ok(_) => log::info!(
5628 "handle_create_file_for_peer: successfully wrote file to {:?}",
5629 destination
5630 ),
5631 Err(e) => log::error!(
5632 "handle_create_file_for_peer: failed to write empty file: {:?}",
5633 e
5634 ),
5635 }
5636 }
5637 } else {
5638 log::warn!("handle_create_file_for_peer: State has no file field");
5639 }
5640 }
5641 Some(Variant::Chunk(chunk)) => {
5642 log::debug!(
5643 "handle_create_file_for_peer: got Chunk: file_id={}, data_len={}",
5644 chunk.file_id,
5645 chunk.data.len()
5646 );
5647
5648 // Extract data while holding the lock, then release it before await
5649 let (key_to_remove, write_info): (
5650 Option<(WorktreeId, String)>,
5651 Option<(PathBuf, Vec<u8>)>,
5652 ) = {
5653 let mut files = downloading_files.lock();
5654 let mut found_key: Option<(WorktreeId, String)> = None;
5655 let mut write_data: Option<(PathBuf, Vec<u8>)> = None;
5656
5657 for (key, file_entry) in files.iter_mut() {
5658 if file_entry.file_id == Some(chunk.file_id) {
5659 file_entry.chunks.extend_from_slice(&chunk.data);
5660 log::debug!(
5661 "handle_create_file_for_peer: accumulated {} bytes, total_size={}",
5662 file_entry.chunks.len(),
5663 file_entry.total_size
5664 );
5665
5666 if file_entry.chunks.len() as u64 >= file_entry.total_size
5667 && file_entry.total_size > 0
5668 {
5669 let destination = file_entry.destination_path.clone();
5670 let content = std::mem::take(&mut file_entry.chunks);
5671 found_key = Some(key.clone());
5672 write_data = Some((destination, content));
5673 }
5674 break;
5675 }
5676 }
5677 (found_key, write_data)
5678 }; // MutexGuard is dropped here
5679
5680 // Perform the async write outside the lock
5681 if let Some((destination, content)) = write_info {
5682 log::debug!(
5683 "handle_create_file_for_peer: writing {} bytes to {:?}",
5684 content.len(),
5685 destination
5686 );
5687 match smol::fs::write(&destination, &content).await {
5688 Ok(_) => log::info!(
5689 "handle_create_file_for_peer: successfully wrote file to {:?}",
5690 destination
5691 ),
5692 Err(e) => log::error!(
5693 "handle_create_file_for_peer: failed to write file: {:?}",
5694 e
5695 ),
5696 }
5697 }
5698
5699 // Remove the completed entry
5700 if let Some(key) = key_to_remove {
5701 downloading_files.lock().remove(&key);
5702 log::debug!("handle_create_file_for_peer: removed completed download entry");
5703 }
5704 }
5705 None => {
5706 log::warn!("handle_create_file_for_peer: got None variant");
5707 }
5708 }
5709
5710 Ok(())
5711 }
5712
5713 fn synchronize_remote_buffers(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
5714 let project_id = match self.client_state {
5715 ProjectClientState::Collab {
5716 sharing_has_stopped,
5717 remote_id,
5718 ..
5719 } => {
5720 if sharing_has_stopped {
5721 return Task::ready(Err(anyhow!(
5722 "can't synchronize remote buffers on a readonly project"
5723 )));
5724 } else {
5725 remote_id
5726 }
5727 }
5728 ProjectClientState::Shared { .. } | ProjectClientState::Local => {
5729 return Task::ready(Err(anyhow!(
5730 "can't synchronize remote buffers on a local project"
5731 )));
5732 }
5733 };
5734
5735 let client = self.collab_client.clone();
5736 cx.spawn(async move |this, cx| {
5737 let (buffers, incomplete_buffer_ids) = this.update(cx, |this, cx| {
5738 this.buffer_store.read(cx).buffer_version_info(cx)
5739 })?;
5740 let response = client
5741 .request(proto::SynchronizeBuffers {
5742 project_id,
5743 buffers,
5744 })
5745 .await?;
5746
5747 let send_updates_for_buffers = this.update(cx, |this, cx| {
5748 response
5749 .buffers
5750 .into_iter()
5751 .map(|buffer| {
5752 let client = client.clone();
5753 let buffer_id = match BufferId::new(buffer.id) {
5754 Ok(id) => id,
5755 Err(e) => {
5756 return Task::ready(Err(e));
5757 }
5758 };
5759 let remote_version = language::proto::deserialize_version(&buffer.version);
5760 if let Some(buffer) = this.buffer_for_id(buffer_id, cx) {
5761 let operations =
5762 buffer.read(cx).serialize_ops(Some(remote_version), cx);
5763 cx.background_spawn(async move {
5764 let operations = operations.await;
5765 for chunk in split_operations(operations) {
5766 client
5767 .request(proto::UpdateBuffer {
5768 project_id,
5769 buffer_id: buffer_id.into(),
5770 operations: chunk,
5771 })
5772 .await?;
5773 }
5774 anyhow::Ok(())
5775 })
5776 } else {
5777 Task::ready(Ok(()))
5778 }
5779 })
5780 .collect::<Vec<_>>()
5781 })?;
5782
5783 // Any incomplete buffers have open requests waiting. Request that the host sends
5784 // creates these buffers for us again to unblock any waiting futures.
5785 for id in incomplete_buffer_ids {
5786 cx.background_spawn(client.request(proto::OpenBufferById {
5787 project_id,
5788 id: id.into(),
5789 }))
5790 .detach();
5791 }
5792
5793 futures::future::join_all(send_updates_for_buffers)
5794 .await
5795 .into_iter()
5796 .collect()
5797 })
5798 }
5799
5800 pub fn worktree_metadata_protos(&self, cx: &App) -> Vec<proto::WorktreeMetadata> {
5801 self.worktree_store.read(cx).worktree_metadata_protos(cx)
5802 }
5803
5804 /// Iterator of all open buffers that have unsaved changes
5805 pub fn dirty_buffers<'a>(&'a self, cx: &'a App) -> impl Iterator<Item = ProjectPath> + 'a {
5806 self.buffer_store.read(cx).buffers().filter_map(|buf| {
5807 let buf = buf.read(cx);
5808 if buf.is_dirty() {
5809 buf.project_path(cx)
5810 } else {
5811 None
5812 }
5813 })
5814 }
5815
5816 fn set_worktrees_from_proto(
5817 &mut self,
5818 worktrees: Vec<proto::WorktreeMetadata>,
5819 cx: &mut Context<Project>,
5820 ) -> Result<()> {
5821 self.worktree_store.update(cx, |worktree_store, cx| {
5822 worktree_store.set_worktrees_from_proto(worktrees, self.replica_id(), cx)
5823 })
5824 }
5825
5826 fn set_collaborators_from_proto(
5827 &mut self,
5828 messages: Vec<proto::Collaborator>,
5829 cx: &mut Context<Self>,
5830 ) -> Result<()> {
5831 let mut collaborators = HashMap::default();
5832 for message in messages {
5833 let collaborator = Collaborator::from_proto(message)?;
5834 collaborators.insert(collaborator.peer_id, collaborator);
5835 }
5836 for old_peer_id in self.collaborators.keys() {
5837 if !collaborators.contains_key(old_peer_id) {
5838 cx.emit(Event::CollaboratorLeft(*old_peer_id));
5839 }
5840 }
5841 self.collaborators = collaborators;
5842 Ok(())
5843 }
5844
5845 pub fn supplementary_language_servers<'a>(
5846 &'a self,
5847 cx: &'a App,
5848 ) -> impl 'a + Iterator<Item = (LanguageServerId, LanguageServerName)> {
5849 self.lsp_store.read(cx).supplementary_language_servers()
5850 }
5851
5852 pub fn any_language_server_supports_inlay_hints(&self, buffer: &Buffer, cx: &mut App) -> bool {
5853 let Some(language) = buffer.language().cloned() else {
5854 return false;
5855 };
5856 self.lsp_store.update(cx, |lsp_store, _| {
5857 let relevant_language_servers = lsp_store
5858 .languages
5859 .lsp_adapters(&language.name())
5860 .into_iter()
5861 .map(|lsp_adapter| lsp_adapter.name())
5862 .collect::<HashSet<_>>();
5863 lsp_store
5864 .language_server_statuses()
5865 .filter_map(|(server_id, server_status)| {
5866 relevant_language_servers
5867 .contains(&server_status.name)
5868 .then_some(server_id)
5869 })
5870 .filter_map(|server_id| lsp_store.lsp_server_capabilities.get(&server_id))
5871 .any(InlayHints::check_capabilities)
5872 })
5873 }
5874
5875 pub fn any_language_server_supports_semantic_tokens(
5876 &self,
5877 buffer: &Buffer,
5878 cx: &mut App,
5879 ) -> bool {
5880 let Some(language) = buffer.language().cloned() else {
5881 return false;
5882 };
5883 let lsp_store = self.lsp_store.read(cx);
5884 let relevant_language_servers = lsp_store
5885 .languages
5886 .lsp_adapters(&language.name())
5887 .into_iter()
5888 .map(|lsp_adapter| lsp_adapter.name())
5889 .collect::<HashSet<_>>();
5890 lsp_store
5891 .language_server_statuses()
5892 .filter_map(|(server_id, server_status)| {
5893 relevant_language_servers
5894 .contains(&server_status.name)
5895 .then_some(server_id)
5896 })
5897 .filter_map(|server_id| lsp_store.lsp_server_capabilities.get(&server_id))
5898 .any(|capabilities| capabilities.semantic_tokens_provider.is_some())
5899 }
5900
5901 pub fn language_server_id_for_name(
5902 &self,
5903 buffer: &Buffer,
5904 name: &LanguageServerName,
5905 cx: &App,
5906 ) -> Option<LanguageServerId> {
5907 let language = buffer.language()?;
5908 let relevant_language_servers = self
5909 .languages
5910 .lsp_adapters(&language.name())
5911 .into_iter()
5912 .map(|lsp_adapter| lsp_adapter.name())
5913 .collect::<HashSet<_>>();
5914 if !relevant_language_servers.contains(name) {
5915 return None;
5916 }
5917 self.language_server_statuses(cx)
5918 .filter(|(_, server_status)| relevant_language_servers.contains(&server_status.name))
5919 .find_map(|(server_id, server_status)| {
5920 if &server_status.name == name {
5921 Some(server_id)
5922 } else {
5923 None
5924 }
5925 })
5926 }
5927
5928 #[cfg(feature = "test-support")]
5929 pub fn has_language_servers_for(&self, buffer: &Buffer, cx: &mut App) -> bool {
5930 self.lsp_store.update(cx, |this, cx| {
5931 this.running_language_servers_for_local_buffer(buffer, cx)
5932 .next()
5933 .is_some()
5934 })
5935 }
5936
5937 pub fn git_init(
5938 &self,
5939 path: Arc<Path>,
5940 fallback_branch_name: String,
5941 cx: &App,
5942 ) -> Task<Result<()>> {
5943 self.git_store
5944 .read(cx)
5945 .git_init(path, fallback_branch_name, cx)
5946 }
5947
5948 pub fn buffer_store(&self) -> &Entity<BufferStore> {
5949 &self.buffer_store
5950 }
5951
5952 pub fn git_store(&self) -> &Entity<GitStore> {
5953 &self.git_store
5954 }
5955
5956 pub fn agent_server_store(&self) -> &Entity<AgentServerStore> {
5957 &self.agent_server_store
5958 }
5959
5960 #[cfg(feature = "test-support")]
5961 pub fn git_scans_complete(&self, cx: &Context<Self>) -> Task<()> {
5962 use futures::future::join_all;
5963 cx.spawn(async move |this, cx| {
5964 let scans_complete = this
5965 .read_with(cx, |this, cx| {
5966 this.worktrees(cx)
5967 .filter_map(|worktree| Some(worktree.read(cx).as_local()?.scan_complete()))
5968 .collect::<Vec<_>>()
5969 })
5970 .unwrap();
5971 join_all(scans_complete).await;
5972 let barriers = this
5973 .update(cx, |this, cx| {
5974 let repos = this.repositories(cx).values().cloned().collect::<Vec<_>>();
5975 repos
5976 .into_iter()
5977 .map(|repo| repo.update(cx, |repo, _| repo.barrier()))
5978 .collect::<Vec<_>>()
5979 })
5980 .unwrap();
5981 join_all(barriers).await;
5982 })
5983 }
5984
5985 pub fn active_repository(&self, cx: &App) -> Option<Entity<Repository>> {
5986 self.git_store.read(cx).active_repository()
5987 }
5988
5989 pub fn repositories<'a>(&self, cx: &'a App) -> &'a HashMap<RepositoryId, Entity<Repository>> {
5990 self.git_store.read(cx).repositories()
5991 }
5992
5993 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
5994 self.git_store.read(cx).status_for_buffer_id(buffer_id, cx)
5995 }
5996
5997 pub fn set_agent_location(
5998 &mut self,
5999 new_location: Option<AgentLocation>,
6000 cx: &mut Context<Self>,
6001 ) {
6002 if let Some(old_location) = self.agent_location.as_ref() {
6003 old_location
6004 .buffer
6005 .update(cx, |buffer, cx| buffer.remove_agent_selections(cx))
6006 .ok();
6007 }
6008
6009 if let Some(location) = new_location.as_ref() {
6010 location
6011 .buffer
6012 .update(cx, |buffer, cx| {
6013 buffer.set_agent_selections(
6014 Arc::from([language::Selection {
6015 id: 0,
6016 start: location.position,
6017 end: location.position,
6018 reversed: false,
6019 goal: language::SelectionGoal::None,
6020 }]),
6021 false,
6022 CursorShape::Hollow,
6023 cx,
6024 )
6025 })
6026 .ok();
6027 }
6028
6029 self.agent_location = new_location;
6030 cx.emit(Event::AgentLocationChanged);
6031 }
6032
6033 pub fn agent_location(&self) -> Option<AgentLocation> {
6034 self.agent_location.clone()
6035 }
6036
6037 pub fn path_style(&self, cx: &App) -> PathStyle {
6038 self.worktree_store.read(cx).path_style()
6039 }
6040
6041 pub fn contains_local_settings_file(
6042 &self,
6043 worktree_id: WorktreeId,
6044 rel_path: &RelPath,
6045 cx: &App,
6046 ) -> bool {
6047 self.worktree_for_id(worktree_id, cx)
6048 .map_or(false, |worktree| {
6049 worktree.read(cx).entry_for_path(rel_path).is_some()
6050 })
6051 }
6052}
6053
6054/// Identifies a project group by a set of paths the workspaces in this group
6055/// have.
6056///
6057/// Paths are mapped to their main worktree path first so we can group
6058/// workspaces by main repos.
6059#[derive(PartialEq, Eq, Hash, Clone, Debug)]
6060pub struct ProjectGroupKey {
6061 paths: PathList,
6062 host: Option<RemoteConnectionOptions>,
6063}
6064
6065impl ProjectGroupKey {
6066 /// Creates a new `ProjectGroupKey` with the given path list.
6067 ///
6068 /// The path list should point to the git main worktree paths for a project.
6069 pub fn new(host: Option<RemoteConnectionOptions>, paths: PathList) -> Self {
6070 Self { paths, host }
6071 }
6072
6073 pub fn display_name(&self) -> SharedString {
6074 let mut names = Vec::with_capacity(self.paths.paths().len());
6075 for abs_path in self.paths.paths() {
6076 if let Some(name) = abs_path.file_name() {
6077 names.push(name.to_string_lossy().to_string());
6078 }
6079 }
6080 if names.is_empty() {
6081 // TODO: Can we do something better in this case?
6082 "Empty Workspace".into()
6083 } else {
6084 names.join(", ").into()
6085 }
6086 }
6087
6088 pub fn path_list(&self) -> &PathList {
6089 &self.paths
6090 }
6091
6092 pub fn host(&self) -> Option<RemoteConnectionOptions> {
6093 self.host.clone()
6094 }
6095}
6096
6097pub struct PathMatchCandidateSet {
6098 pub snapshot: Snapshot,
6099 pub include_ignored: bool,
6100 pub include_root_name: bool,
6101 pub candidates: Candidates,
6102}
6103
6104pub enum Candidates {
6105 /// Only consider directories.
6106 Directories,
6107 /// Only consider files.
6108 Files,
6109 /// Consider directories and files.
6110 Entries,
6111}
6112
6113impl<'a> fuzzy::PathMatchCandidateSet<'a> for PathMatchCandidateSet {
6114 type Candidates = PathMatchCandidateSetIter<'a>;
6115
6116 fn id(&self) -> usize {
6117 self.snapshot.id().to_usize()
6118 }
6119
6120 fn len(&self) -> usize {
6121 match self.candidates {
6122 Candidates::Files => {
6123 if self.include_ignored {
6124 self.snapshot.file_count()
6125 } else {
6126 self.snapshot.visible_file_count()
6127 }
6128 }
6129
6130 Candidates::Directories => {
6131 if self.include_ignored {
6132 self.snapshot.dir_count()
6133 } else {
6134 self.snapshot.visible_dir_count()
6135 }
6136 }
6137
6138 Candidates::Entries => {
6139 if self.include_ignored {
6140 self.snapshot.entry_count()
6141 } else {
6142 self.snapshot.visible_entry_count()
6143 }
6144 }
6145 }
6146 }
6147
6148 fn prefix(&self) -> Arc<RelPath> {
6149 if self.snapshot.root_entry().is_some_and(|e| e.is_file()) || self.include_root_name {
6150 self.snapshot.root_name().into()
6151 } else {
6152 RelPath::empty().into()
6153 }
6154 }
6155
6156 fn root_is_file(&self) -> bool {
6157 self.snapshot.root_entry().is_some_and(|f| f.is_file())
6158 }
6159
6160 fn path_style(&self) -> PathStyle {
6161 self.snapshot.path_style()
6162 }
6163
6164 fn candidates(&'a self, start: usize) -> Self::Candidates {
6165 PathMatchCandidateSetIter {
6166 traversal: match self.candidates {
6167 Candidates::Directories => self.snapshot.directories(self.include_ignored, start),
6168 Candidates::Files => self.snapshot.files(self.include_ignored, start),
6169 Candidates::Entries => self.snapshot.entries(self.include_ignored, start),
6170 },
6171 }
6172 }
6173}
6174
6175pub struct PathMatchCandidateSetIter<'a> {
6176 traversal: Traversal<'a>,
6177}
6178
6179impl<'a> Iterator for PathMatchCandidateSetIter<'a> {
6180 type Item = fuzzy::PathMatchCandidate<'a>;
6181
6182 fn next(&mut self) -> Option<Self::Item> {
6183 self.traversal
6184 .next()
6185 .map(|entry| fuzzy::PathMatchCandidate {
6186 is_dir: entry.kind.is_dir(),
6187 path: &entry.path,
6188 char_bag: entry.char_bag,
6189 })
6190 }
6191}
6192
6193impl<'a> fuzzy_nucleo::PathMatchCandidateSet<'a> for PathMatchCandidateSet {
6194 type Candidates = PathMatchCandidateSetNucleoIter<'a>;
6195 fn id(&self) -> usize {
6196 self.snapshot.id().to_usize()
6197 }
6198 fn len(&self) -> usize {
6199 match self.candidates {
6200 Candidates::Files => {
6201 if self.include_ignored {
6202 self.snapshot.file_count()
6203 } else {
6204 self.snapshot.visible_file_count()
6205 }
6206 }
6207 Candidates::Directories => {
6208 if self.include_ignored {
6209 self.snapshot.dir_count()
6210 } else {
6211 self.snapshot.visible_dir_count()
6212 }
6213 }
6214 Candidates::Entries => {
6215 if self.include_ignored {
6216 self.snapshot.entry_count()
6217 } else {
6218 self.snapshot.visible_entry_count()
6219 }
6220 }
6221 }
6222 }
6223 fn prefix(&self) -> Arc<RelPath> {
6224 if self.snapshot.root_entry().is_some_and(|e| e.is_file()) || self.include_root_name {
6225 self.snapshot.root_name().into()
6226 } else {
6227 RelPath::empty().into()
6228 }
6229 }
6230 fn root_is_file(&self) -> bool {
6231 self.snapshot.root_entry().is_some_and(|f| f.is_file())
6232 }
6233 fn path_style(&self) -> PathStyle {
6234 self.snapshot.path_style()
6235 }
6236 fn candidates(&'a self, start: usize) -> Self::Candidates {
6237 PathMatchCandidateSetNucleoIter {
6238 traversal: match self.candidates {
6239 Candidates::Directories => self.snapshot.directories(self.include_ignored, start),
6240 Candidates::Files => self.snapshot.files(self.include_ignored, start),
6241 Candidates::Entries => self.snapshot.entries(self.include_ignored, start),
6242 },
6243 }
6244 }
6245}
6246
6247pub struct PathMatchCandidateSetNucleoIter<'a> {
6248 traversal: Traversal<'a>,
6249}
6250
6251impl<'a> Iterator for PathMatchCandidateSetNucleoIter<'a> {
6252 type Item = fuzzy_nucleo::PathMatchCandidate<'a>;
6253 fn next(&mut self) -> Option<Self::Item> {
6254 self.traversal
6255 .next()
6256 .map(|entry| fuzzy_nucleo::PathMatchCandidate {
6257 is_dir: entry.kind.is_dir(),
6258 path: &entry.path,
6259 })
6260 }
6261}
6262
6263impl EventEmitter<Event> for Project {}
6264
6265impl<'a> From<&'a ProjectPath> for SettingsLocation<'a> {
6266 fn from(val: &'a ProjectPath) -> Self {
6267 SettingsLocation {
6268 worktree_id: val.worktree_id,
6269 path: val.path.as_ref(),
6270 }
6271 }
6272}
6273
6274impl<P: Into<Arc<RelPath>>> From<(WorktreeId, P)> for ProjectPath {
6275 fn from((worktree_id, path): (WorktreeId, P)) -> Self {
6276 Self {
6277 worktree_id,
6278 path: path.into(),
6279 }
6280 }
6281}
6282
6283/// ResolvedPath is a path that has been resolved to either a ProjectPath
6284/// or an AbsPath and that *exists*.
6285#[derive(Debug, Clone)]
6286pub enum ResolvedPath {
6287 ProjectPath {
6288 project_path: ProjectPath,
6289 is_dir: bool,
6290 },
6291 AbsPath {
6292 path: String,
6293 is_dir: bool,
6294 },
6295}
6296
6297impl ResolvedPath {
6298 pub fn abs_path(&self) -> Option<&str> {
6299 match self {
6300 Self::AbsPath { path, .. } => Some(path),
6301 _ => None,
6302 }
6303 }
6304
6305 pub fn into_abs_path(self) -> Option<String> {
6306 match self {
6307 Self::AbsPath { path, .. } => Some(path),
6308 _ => None,
6309 }
6310 }
6311
6312 pub fn project_path(&self) -> Option<&ProjectPath> {
6313 match self {
6314 Self::ProjectPath { project_path, .. } => Some(project_path),
6315 _ => None,
6316 }
6317 }
6318
6319 pub fn is_file(&self) -> bool {
6320 !self.is_dir()
6321 }
6322
6323 pub fn is_dir(&self) -> bool {
6324 match self {
6325 Self::ProjectPath { is_dir, .. } => *is_dir,
6326 Self::AbsPath { is_dir, .. } => *is_dir,
6327 }
6328 }
6329}
6330
6331impl ProjectItem for Buffer {
6332 fn try_open(
6333 project: &Entity<Project>,
6334 path: &ProjectPath,
6335 cx: &mut App,
6336 ) -> Option<Task<Result<Entity<Self>>>> {
6337 Some(project.update(cx, |project, cx| project.open_buffer(path.clone(), cx)))
6338 }
6339
6340 fn entry_id(&self, _cx: &App) -> Option<ProjectEntryId> {
6341 File::from_dyn(self.file()).and_then(|file| file.project_entry_id())
6342 }
6343
6344 fn project_path(&self, cx: &App) -> Option<ProjectPath> {
6345 let file = self.file()?;
6346
6347 (!matches!(file.disk_state(), DiskState::Historic { .. })).then(|| ProjectPath {
6348 worktree_id: file.worktree_id(cx),
6349 path: file.path().clone(),
6350 })
6351 }
6352
6353 fn is_dirty(&self) -> bool {
6354 self.is_dirty()
6355 }
6356}
6357
6358impl Completion {
6359 pub fn kind(&self) -> Option<CompletionItemKind> {
6360 self.source
6361 // `lsp::CompletionListItemDefaults` has no `kind` field
6362 .lsp_completion(false)
6363 .and_then(|lsp_completion| lsp_completion.kind)
6364 }
6365
6366 pub fn label(&self) -> Option<String> {
6367 self.source
6368 .lsp_completion(false)
6369 .map(|lsp_completion| lsp_completion.label.clone())
6370 }
6371
6372 /// A key that can be used to sort completions when displaying
6373 /// them to the user.
6374 pub fn sort_key(&self) -> (usize, &str) {
6375 const DEFAULT_KIND_KEY: usize = 4;
6376 let kind_key = self
6377 .kind()
6378 .and_then(|lsp_completion_kind| match lsp_completion_kind {
6379 lsp::CompletionItemKind::KEYWORD => Some(0),
6380 lsp::CompletionItemKind::VARIABLE => Some(1),
6381 lsp::CompletionItemKind::CONSTANT => Some(2),
6382 lsp::CompletionItemKind::PROPERTY => Some(3),
6383 _ => None,
6384 })
6385 .unwrap_or(DEFAULT_KIND_KEY);
6386 (kind_key, self.label.filter_text())
6387 }
6388
6389 /// Whether this completion is a snippet.
6390 pub fn is_snippet_kind(&self) -> bool {
6391 matches!(
6392 &self.source,
6393 CompletionSource::Lsp { lsp_completion, .. }
6394 if lsp_completion.kind == Some(CompletionItemKind::SNIPPET)
6395 )
6396 }
6397
6398 /// Whether this completion is a snippet or snippet-style LSP completion.
6399 pub fn is_snippet(&self) -> bool {
6400 self.source
6401 // `lsp::CompletionListItemDefaults` has `insert_text_format` field
6402 .lsp_completion(true)
6403 .is_some_and(|lsp_completion| {
6404 lsp_completion.insert_text_format == Some(lsp::InsertTextFormat::SNIPPET)
6405 })
6406 }
6407
6408 /// Returns the corresponding color for this completion.
6409 ///
6410 /// Will return `None` if this completion's kind is not [`CompletionItemKind::COLOR`].
6411 pub fn color(&self) -> Option<Hsla> {
6412 // `lsp::CompletionListItemDefaults` has no `kind` field
6413 let lsp_completion = self.source.lsp_completion(false)?;
6414 if lsp_completion.kind? == CompletionItemKind::COLOR {
6415 return color_extractor::extract_color(&lsp_completion);
6416 }
6417 None
6418 }
6419}
6420
6421fn proto_to_prompt(level: proto::language_server_prompt_request::Level) -> gpui::PromptLevel {
6422 match level {
6423 proto::language_server_prompt_request::Level::Info(_) => gpui::PromptLevel::Info,
6424 proto::language_server_prompt_request::Level::Warning(_) => gpui::PromptLevel::Warning,
6425 proto::language_server_prompt_request::Level::Critical(_) => gpui::PromptLevel::Critical,
6426 }
6427}
6428
6429fn provide_inline_values(
6430 captures: impl Iterator<Item = (Range<usize>, language::DebuggerTextObject)>,
6431 snapshot: &language::BufferSnapshot,
6432 max_row: usize,
6433) -> Vec<InlineValueLocation> {
6434 let mut variables = Vec::new();
6435 let mut variable_position = HashSet::default();
6436 let mut scopes = Vec::new();
6437
6438 let active_debug_line_offset = snapshot.point_to_offset(Point::new(max_row as u32, 0));
6439
6440 for (capture_range, capture_kind) in captures {
6441 match capture_kind {
6442 language::DebuggerTextObject::Variable => {
6443 let variable_name = snapshot
6444 .text_for_range(capture_range.clone())
6445 .collect::<String>();
6446 let point = snapshot.offset_to_point(capture_range.end);
6447
6448 while scopes
6449 .last()
6450 .is_some_and(|scope: &Range<_>| !scope.contains(&capture_range.start))
6451 {
6452 scopes.pop();
6453 }
6454
6455 if point.row as usize > max_row {
6456 break;
6457 }
6458
6459 let scope = if scopes
6460 .last()
6461 .is_none_or(|scope| !scope.contains(&active_debug_line_offset))
6462 {
6463 VariableScope::Global
6464 } else {
6465 VariableScope::Local
6466 };
6467
6468 if variable_position.insert(capture_range.end) {
6469 variables.push(InlineValueLocation {
6470 variable_name,
6471 scope,
6472 lookup: VariableLookupKind::Variable,
6473 row: point.row as usize,
6474 column: point.column as usize,
6475 });
6476 }
6477 }
6478 language::DebuggerTextObject::Scope => {
6479 while scopes.last().map_or_else(
6480 || false,
6481 |scope: &Range<usize>| {
6482 !(scope.contains(&capture_range.start)
6483 && scope.contains(&capture_range.end))
6484 },
6485 ) {
6486 scopes.pop();
6487 }
6488 scopes.push(capture_range);
6489 }
6490 }
6491 }
6492
6493 variables
6494}