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