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