1pub mod buffer_store;
2mod color_extractor;
3pub mod connection_manager;
4pub mod debounced_delay;
5pub mod git;
6pub mod image_store;
7pub mod lsp_command;
8pub mod lsp_ext_command;
9pub mod lsp_store;
10pub mod prettier_store;
11pub mod project_settings;
12mod project_tree;
13pub mod search;
14mod task_inventory;
15pub mod task_store;
16pub mod terminals;
17pub mod toolchain_store;
18pub mod worktree_store;
19
20#[cfg(test)]
21mod project_tests;
22
23mod direnv;
24mod environment;
25use buffer_diff::BufferDiff;
26pub use environment::EnvironmentErrorMessage;
27use git::Repository;
28pub mod search_history;
29mod yarn;
30
31use crate::git::GitStore;
32use anyhow::{anyhow, Context as _, Result};
33use buffer_store::{BufferStore, BufferStoreEvent};
34use client::{
35 proto, Client, Collaborator, PendingEntitySubscription, ProjectId, TypedEnvelope, UserStore,
36};
37use clock::ReplicaId;
38use collections::{BTreeSet, HashMap, HashSet};
39use debounced_delay::DebouncedDelay;
40pub use environment::ProjectEnvironment;
41use futures::{
42 channel::mpsc::{self, UnboundedReceiver},
43 future::try_join_all,
44 StreamExt,
45};
46pub use image_store::{ImageItem, ImageStore};
47use image_store::{ImageItemEvent, ImageStoreEvent};
48
49use ::git::{
50 blame::Blame,
51 repository::{Branch, GitRepository, RepoPath},
52 status::FileStatus,
53};
54use gpui::{
55 AnyEntity, App, AppContext as _, AsyncApp, BorrowAppContext, Context, Entity, EventEmitter,
56 Hsla, SharedString, Task, WeakEntity, Window,
57};
58use itertools::Itertools;
59use language::{
60 language_settings::InlayHintKind, proto::split_operations, Buffer, BufferEvent, Capability,
61 CodeLabel, CompletionDocumentation, File as _, Language, LanguageName, LanguageRegistry,
62 PointUtf16, ToOffset, ToPointUtf16, Toolchain, ToolchainList, Transaction, Unclipped,
63};
64use lsp::{
65 CodeActionKind, CompletionContext, CompletionItemKind, DocumentHighlightKind, LanguageServerId,
66 LanguageServerName, MessageActionItem,
67};
68use lsp_command::*;
69use lsp_store::{LspFormatTarget, OpenLspBufferHandle};
70use node_runtime::NodeRuntime;
71use parking_lot::Mutex;
72pub use prettier_store::PrettierStore;
73use project_settings::{ProjectSettings, SettingsObserver, SettingsObserverEvent};
74use remote::{SshConnectionOptions, SshRemoteClient};
75use rpc::{
76 proto::{FromProto, LanguageServerPromptResponse, ToProto, SSH_PROJECT_ID},
77 AnyProtoClient, ErrorCode,
78};
79use search::{SearchInputKind, SearchQuery, SearchResult};
80use search_history::SearchHistory;
81use settings::{InvalidSettingsError, Settings, SettingsLocation, SettingsStore};
82use smol::channel::Receiver;
83use snippet::Snippet;
84use snippet_provider::SnippetProvider;
85use std::{
86 borrow::Cow,
87 ops::Range,
88 path::{Component, Path, PathBuf},
89 pin::pin,
90 str,
91 sync::Arc,
92 time::Duration,
93};
94use task_store::TaskStore;
95use terminals::Terminals;
96use text::{Anchor, BufferId};
97use toolchain_store::EmptyToolchainStore;
98use util::{
99 paths::{compare_paths, SanitizedPath},
100 ResultExt as _,
101};
102use worktree::{CreatedEntry, Snapshot, Traversal};
103use worktree_store::{WorktreeStore, WorktreeStoreEvent};
104
105pub use fs::*;
106pub use language::Location;
107#[cfg(any(test, feature = "test-support"))]
108pub use prettier::FORMAT_SUFFIX as TEST_PRETTIER_FORMAT_SUFFIX;
109pub use task_inventory::{
110 BasicContextProvider, ContextProviderWithTasks, Inventory, TaskSourceKind,
111};
112pub use worktree::{
113 Entry, EntryKind, File, LocalWorktree, PathChange, ProjectEntryId, UpdatedEntriesSet,
114 UpdatedGitRepositoriesSet, Worktree, WorktreeId, WorktreeSettings, FS_WATCH_LATENCY,
115};
116
117pub use buffer_store::ProjectTransaction;
118pub use lsp_store::{
119 DiagnosticSummary, LanguageServerLogType, LanguageServerProgress, LanguageServerPromptRequest,
120 LanguageServerStatus, LanguageServerToQuery, LspStore, LspStoreEvent,
121 SERVER_PROGRESS_THROTTLE_TIMEOUT,
122};
123pub use toolchain_store::ToolchainStore;
124const MAX_PROJECT_SEARCH_HISTORY_SIZE: usize = 500;
125const MAX_SEARCH_RESULT_FILES: usize = 5_000;
126const MAX_SEARCH_RESULT_RANGES: usize = 10_000;
127
128pub trait ProjectItem {
129 fn try_open(
130 project: &Entity<Project>,
131 path: &ProjectPath,
132 cx: &mut App,
133 ) -> Option<Task<Result<Entity<Self>>>>
134 where
135 Self: Sized;
136 fn entry_id(&self, cx: &App) -> Option<ProjectEntryId>;
137 fn project_path(&self, cx: &App) -> Option<ProjectPath>;
138 fn is_dirty(&self) -> bool;
139}
140
141#[derive(Clone)]
142pub enum OpenedBufferEvent {
143 Disconnected,
144 Ok(BufferId),
145 Err(BufferId, Arc<anyhow::Error>),
146}
147
148/// Semantics-aware entity that is relevant to one or more [`Worktree`] with the files.
149/// `Project` is responsible for tasks, LSP and collab queries, synchronizing worktree states accordingly.
150/// Maps [`Worktree`] entries with its own logic using [`ProjectEntryId`] and [`ProjectPath`] structs.
151///
152/// Can be either local (for the project opened on the same host) or remote.(for collab projects, browsed by multiple remote users).
153pub struct Project {
154 active_entry: Option<ProjectEntryId>,
155 buffer_ordered_messages_tx: mpsc::UnboundedSender<BufferOrderedMessage>,
156 languages: Arc<LanguageRegistry>,
157 client: Arc<client::Client>,
158 join_project_response_message_id: u32,
159 task_store: Entity<TaskStore>,
160 user_store: Entity<UserStore>,
161 fs: Arc<dyn Fs>,
162 ssh_client: Option<Entity<SshRemoteClient>>,
163 client_state: ProjectClientState,
164 git_store: Entity<GitStore>,
165 collaborators: HashMap<proto::PeerId, Collaborator>,
166 client_subscriptions: Vec<client::Subscription>,
167 worktree_store: Entity<WorktreeStore>,
168 buffer_store: Entity<BufferStore>,
169 image_store: Entity<ImageStore>,
170 lsp_store: Entity<LspStore>,
171 _subscriptions: Vec<gpui::Subscription>,
172 buffers_needing_diff: HashSet<WeakEntity<Buffer>>,
173 git_diff_debouncer: DebouncedDelay<Self>,
174 remotely_created_models: Arc<Mutex<RemotelyCreatedModels>>,
175 terminals: Terminals,
176 node: Option<NodeRuntime>,
177 search_history: SearchHistory,
178 search_included_history: SearchHistory,
179 search_excluded_history: SearchHistory,
180 snippets: Entity<SnippetProvider>,
181 environment: Entity<ProjectEnvironment>,
182 settings_observer: Entity<SettingsObserver>,
183 toolchain_store: Option<Entity<ToolchainStore>>,
184}
185
186#[derive(Default)]
187struct RemotelyCreatedModels {
188 worktrees: Vec<Entity<Worktree>>,
189 buffers: Vec<Entity<Buffer>>,
190 retain_count: usize,
191}
192
193struct RemotelyCreatedModelGuard {
194 remote_models: std::sync::Weak<Mutex<RemotelyCreatedModels>>,
195}
196
197impl Drop for RemotelyCreatedModelGuard {
198 fn drop(&mut self) {
199 if let Some(remote_models) = self.remote_models.upgrade() {
200 let mut remote_models = remote_models.lock();
201 assert!(
202 remote_models.retain_count > 0,
203 "RemotelyCreatedModelGuard dropped too many times"
204 );
205 remote_models.retain_count -= 1;
206 if remote_models.retain_count == 0 {
207 remote_models.buffers.clear();
208 remote_models.worktrees.clear();
209 }
210 }
211 }
212}
213/// Message ordered with respect to buffer operations
214#[derive(Debug)]
215enum BufferOrderedMessage {
216 Operation {
217 buffer_id: BufferId,
218 operation: proto::Operation,
219 },
220 LanguageServerUpdate {
221 language_server_id: LanguageServerId,
222 message: proto::update_language_server::Variant,
223 },
224 Resync,
225}
226
227#[derive(Debug)]
228enum ProjectClientState {
229 /// Single-player mode.
230 Local,
231 /// Multi-player mode but still a local project.
232 Shared { remote_id: u64 },
233 /// Multi-player mode but working on a remote project.
234 Remote {
235 sharing_has_stopped: bool,
236 capability: Capability,
237 remote_id: u64,
238 replica_id: ReplicaId,
239 },
240}
241
242#[derive(Clone, Debug, PartialEq)]
243pub enum Event {
244 LanguageServerAdded(LanguageServerId, LanguageServerName, Option<WorktreeId>),
245 LanguageServerRemoved(LanguageServerId),
246 LanguageServerLog(LanguageServerId, LanguageServerLogType, String),
247 Toast {
248 notification_id: SharedString,
249 message: String,
250 },
251 HideToast {
252 notification_id: SharedString,
253 },
254 LanguageServerPrompt(LanguageServerPromptRequest),
255 LanguageNotFound(Entity<Buffer>),
256 ActiveEntryChanged(Option<ProjectEntryId>),
257 ActivateProjectPanel,
258 WorktreeAdded(WorktreeId),
259 WorktreeOrderChanged,
260 WorktreeRemoved(WorktreeId),
261 WorktreeUpdatedEntries(WorktreeId, UpdatedEntriesSet),
262 WorktreeUpdatedGitRepositories(WorktreeId),
263 DiskBasedDiagnosticsStarted {
264 language_server_id: LanguageServerId,
265 },
266 DiskBasedDiagnosticsFinished {
267 language_server_id: LanguageServerId,
268 },
269 DiagnosticsUpdated {
270 path: ProjectPath,
271 language_server_id: LanguageServerId,
272 },
273 RemoteIdChanged(Option<u64>),
274 DisconnectedFromHost,
275 DisconnectedFromSshRemote,
276 Closed,
277 DeletedEntry(WorktreeId, ProjectEntryId),
278 CollaboratorUpdated {
279 old_peer_id: proto::PeerId,
280 new_peer_id: proto::PeerId,
281 },
282 CollaboratorJoined(proto::PeerId),
283 CollaboratorLeft(proto::PeerId),
284 HostReshared,
285 Reshared,
286 Rejoined,
287 RefreshInlayHints,
288 RevealInProjectPanel(ProjectEntryId),
289 SnippetEdit(BufferId, Vec<(lsp::Range, Snippet)>),
290 ExpandedAllForEntry(WorktreeId, ProjectEntryId),
291}
292
293#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
294pub struct ProjectPath {
295 pub worktree_id: WorktreeId,
296 pub path: Arc<Path>,
297}
298
299impl ProjectPath {
300 pub fn from_proto(p: proto::ProjectPath) -> Self {
301 Self {
302 worktree_id: WorktreeId::from_proto(p.worktree_id),
303 path: Arc::<Path>::from_proto(p.path),
304 }
305 }
306
307 pub fn to_proto(&self) -> proto::ProjectPath {
308 proto::ProjectPath {
309 worktree_id: self.worktree_id.to_proto(),
310 path: self.path.as_ref().to_proto(),
311 }
312 }
313
314 pub fn root_path(worktree_id: WorktreeId) -> Self {
315 Self {
316 worktree_id,
317 path: Path::new("").into(),
318 }
319 }
320}
321
322#[derive(Debug, Default)]
323pub enum PrepareRenameResponse {
324 Success(Range<Anchor>),
325 OnlyUnpreparedRenameSupported,
326 #[default]
327 InvalidPosition,
328}
329
330#[derive(Debug, Clone, PartialEq, Eq)]
331pub struct InlayHint {
332 pub position: language::Anchor,
333 pub label: InlayHintLabel,
334 pub kind: Option<InlayHintKind>,
335 pub padding_left: bool,
336 pub padding_right: bool,
337 pub tooltip: Option<InlayHintTooltip>,
338 pub resolve_state: ResolveState,
339}
340
341/// The user's intent behind a given completion confirmation
342#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)]
343pub enum CompletionIntent {
344 /// The user intends to 'commit' this result, if possible
345 /// completion confirmations should run side effects
346 Complete,
347 /// The user intends to continue 'composing' this completion
348 /// completion confirmations should not run side effects and
349 /// let the user continue composing their action
350 Compose,
351}
352
353impl CompletionIntent {
354 pub fn is_complete(&self) -> bool {
355 self == &Self::Complete
356 }
357
358 pub fn is_compose(&self) -> bool {
359 self == &Self::Compose
360 }
361}
362
363/// A completion provided by a language server
364#[derive(Clone)]
365pub struct Completion {
366 /// The range of the buffer that will be replaced.
367 pub old_range: Range<Anchor>,
368 /// The new text that will be inserted.
369 pub new_text: String,
370 /// A label for this completion that is shown in the menu.
371 pub label: CodeLabel,
372 /// The id of the language server that produced this completion.
373 pub server_id: LanguageServerId,
374 /// The documentation for this completion.
375 pub documentation: Option<CompletionDocumentation>,
376 /// The raw completion provided by the language server.
377 pub lsp_completion: lsp::CompletionItem,
378 /// Whether this completion has been resolved, to ensure it happens once per completion.
379 pub resolved: bool,
380 /// An optional callback to invoke when this completion is confirmed.
381 /// Returns, whether new completions should be retriggered after the current one.
382 /// If `true` is returned, the editor will show a new completion menu after this completion is confirmed.
383 /// if no confirmation is provided or `false` is returned, the completion will be committed.
384 pub confirm: Option<Arc<dyn Send + Sync + Fn(CompletionIntent, &mut Window, &mut App) -> bool>>,
385}
386
387impl std::fmt::Debug for Completion {
388 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
389 f.debug_struct("Completion")
390 .field("old_range", &self.old_range)
391 .field("new_text", &self.new_text)
392 .field("label", &self.label)
393 .field("server_id", &self.server_id)
394 .field("documentation", &self.documentation)
395 .field("lsp_completion", &self.lsp_completion)
396 .finish()
397 }
398}
399
400/// A completion provided by a language server
401#[derive(Clone, Debug)]
402pub(crate) struct CoreCompletion {
403 old_range: Range<Anchor>,
404 new_text: String,
405 server_id: LanguageServerId,
406 lsp_completion: lsp::CompletionItem,
407 resolved: bool,
408}
409
410/// A code action provided by a language server.
411#[derive(Clone, Debug)]
412pub struct CodeAction {
413 /// The id of the language server that produced this code action.
414 pub server_id: LanguageServerId,
415 /// The range of the buffer where this code action is applicable.
416 pub range: Range<Anchor>,
417 /// The raw code action provided by the language server.
418 pub lsp_action: lsp::CodeAction,
419}
420
421#[derive(Debug, Clone, PartialEq, Eq)]
422pub enum ResolveState {
423 Resolved,
424 CanResolve(LanguageServerId, Option<lsp::LSPAny>),
425 Resolving,
426}
427
428impl InlayHint {
429 pub fn text(&self) -> String {
430 match &self.label {
431 InlayHintLabel::String(s) => s.to_owned(),
432 InlayHintLabel::LabelParts(parts) => parts.iter().map(|part| &part.value).join(""),
433 }
434 }
435}
436
437#[derive(Debug, Clone, PartialEq, Eq)]
438pub enum InlayHintLabel {
439 String(String),
440 LabelParts(Vec<InlayHintLabelPart>),
441}
442
443#[derive(Debug, Clone, PartialEq, Eq)]
444pub struct InlayHintLabelPart {
445 pub value: String,
446 pub tooltip: Option<InlayHintLabelPartTooltip>,
447 pub location: Option<(LanguageServerId, lsp::Location)>,
448}
449
450#[derive(Debug, Clone, PartialEq, Eq)]
451pub enum InlayHintTooltip {
452 String(String),
453 MarkupContent(MarkupContent),
454}
455
456#[derive(Debug, Clone, PartialEq, Eq)]
457pub enum InlayHintLabelPartTooltip {
458 String(String),
459 MarkupContent(MarkupContent),
460}
461
462#[derive(Debug, Clone, PartialEq, Eq)]
463pub struct MarkupContent {
464 pub kind: HoverBlockKind,
465 pub value: String,
466}
467
468#[derive(Debug, Clone)]
469pub struct LocationLink {
470 pub origin: Option<Location>,
471 pub target: Location,
472}
473
474#[derive(Debug)]
475pub struct DocumentHighlight {
476 pub range: Range<language::Anchor>,
477 pub kind: DocumentHighlightKind,
478}
479
480#[derive(Clone, Debug)]
481pub struct Symbol {
482 pub language_server_name: LanguageServerName,
483 pub source_worktree_id: WorktreeId,
484 pub source_language_server_id: LanguageServerId,
485 pub path: ProjectPath,
486 pub label: CodeLabel,
487 pub name: String,
488 pub kind: lsp::SymbolKind,
489 pub range: Range<Unclipped<PointUtf16>>,
490 pub signature: [u8; 32],
491}
492
493#[derive(Clone, Debug, PartialEq)]
494pub struct HoverBlock {
495 pub text: String,
496 pub kind: HoverBlockKind,
497}
498
499#[derive(Clone, Debug, PartialEq, Eq)]
500pub enum HoverBlockKind {
501 PlainText,
502 Markdown,
503 Code { language: String },
504}
505
506#[derive(Debug, Clone)]
507pub struct Hover {
508 pub contents: Vec<HoverBlock>,
509 pub range: Option<Range<language::Anchor>>,
510 pub language: Option<Arc<Language>>,
511}
512
513impl Hover {
514 pub fn is_empty(&self) -> bool {
515 self.contents.iter().all(|block| block.text.is_empty())
516 }
517}
518
519enum EntitySubscription {
520 Project(PendingEntitySubscription<Project>),
521 BufferStore(PendingEntitySubscription<BufferStore>),
522 WorktreeStore(PendingEntitySubscription<WorktreeStore>),
523 LspStore(PendingEntitySubscription<LspStore>),
524 SettingsObserver(PendingEntitySubscription<SettingsObserver>),
525}
526
527#[derive(Clone)]
528pub enum DirectoryLister {
529 Project(Entity<Project>),
530 Local(Arc<dyn Fs>),
531}
532
533impl DirectoryLister {
534 pub fn is_local(&self, cx: &App) -> bool {
535 match self {
536 DirectoryLister::Local(_) => true,
537 DirectoryLister::Project(project) => project.read(cx).is_local(),
538 }
539 }
540
541 pub fn resolve_tilde<'a>(&self, path: &'a String, cx: &App) -> Cow<'a, str> {
542 if self.is_local(cx) {
543 shellexpand::tilde(path)
544 } else {
545 Cow::from(path)
546 }
547 }
548
549 pub fn default_query(&self, cx: &mut App) -> String {
550 if let DirectoryLister::Project(project) = self {
551 if let Some(worktree) = project.read(cx).visible_worktrees(cx).next() {
552 return worktree.read(cx).abs_path().to_string_lossy().to_string();
553 }
554 };
555 "~/".to_string()
556 }
557
558 pub fn list_directory(&self, path: String, cx: &mut App) -> Task<Result<Vec<PathBuf>>> {
559 match self {
560 DirectoryLister::Project(project) => {
561 project.update(cx, |project, cx| project.list_directory(path, cx))
562 }
563 DirectoryLister::Local(fs) => {
564 let fs = fs.clone();
565 cx.background_spawn(async move {
566 let mut results = vec![];
567 let expanded = shellexpand::tilde(&path);
568 let query = Path::new(expanded.as_ref());
569 let mut response = fs.read_dir(query).await?;
570 while let Some(path) = response.next().await {
571 if let Some(file_name) = path?.file_name() {
572 results.push(PathBuf::from(file_name.to_os_string()));
573 }
574 }
575 Ok(results)
576 })
577 }
578 }
579 }
580}
581
582#[cfg(any(test, feature = "test-support"))]
583pub const DEFAULT_COMPLETION_CONTEXT: CompletionContext = CompletionContext {
584 trigger_kind: lsp::CompletionTriggerKind::INVOKED,
585 trigger_character: None,
586};
587
588impl Project {
589 pub fn init_settings(cx: &mut App) {
590 WorktreeSettings::register(cx);
591 ProjectSettings::register(cx);
592 }
593
594 pub fn init(client: &Arc<Client>, cx: &mut App) {
595 connection_manager::init(client.clone(), cx);
596 Self::init_settings(cx);
597
598 let client: AnyProtoClient = client.clone().into();
599 client.add_entity_message_handler(Self::handle_add_collaborator);
600 client.add_entity_message_handler(Self::handle_update_project_collaborator);
601 client.add_entity_message_handler(Self::handle_remove_collaborator);
602 client.add_entity_message_handler(Self::handle_update_project);
603 client.add_entity_message_handler(Self::handle_unshare_project);
604 client.add_entity_request_handler(Self::handle_update_buffer);
605 client.add_entity_message_handler(Self::handle_update_worktree);
606 client.add_entity_request_handler(Self::handle_synchronize_buffers);
607
608 client.add_entity_request_handler(Self::handle_search_candidate_buffers);
609 client.add_entity_request_handler(Self::handle_open_buffer_by_id);
610 client.add_entity_request_handler(Self::handle_open_buffer_by_path);
611 client.add_entity_request_handler(Self::handle_open_new_buffer);
612 client.add_entity_message_handler(Self::handle_create_buffer_for_peer);
613
614 WorktreeStore::init(&client);
615 BufferStore::init(&client);
616 LspStore::init(&client);
617 GitStore::init(&client);
618 SettingsObserver::init(&client);
619 TaskStore::init(Some(&client));
620 ToolchainStore::init(&client);
621 }
622
623 pub fn local(
624 client: Arc<Client>,
625 node: NodeRuntime,
626 user_store: Entity<UserStore>,
627 languages: Arc<LanguageRegistry>,
628 fs: Arc<dyn Fs>,
629 env: Option<HashMap<String, String>>,
630 cx: &mut App,
631 ) -> Entity<Self> {
632 cx.new(|cx: &mut Context<Self>| {
633 let (tx, rx) = mpsc::unbounded();
634 cx.spawn(move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
635 .detach();
636 let snippets = SnippetProvider::new(fs.clone(), BTreeSet::from_iter([]), cx);
637 let worktree_store = cx.new(|_| WorktreeStore::local(false, fs.clone()));
638 cx.subscribe(&worktree_store, Self::on_worktree_store_event)
639 .detach();
640
641 let buffer_store = cx.new(|cx| BufferStore::local(worktree_store.clone(), cx));
642 cx.subscribe(&buffer_store, Self::on_buffer_store_event)
643 .detach();
644
645 let image_store = cx.new(|cx| ImageStore::local(worktree_store.clone(), cx));
646 cx.subscribe(&image_store, Self::on_image_store_event)
647 .detach();
648
649 let prettier_store = cx.new(|cx| {
650 PrettierStore::new(
651 node.clone(),
652 fs.clone(),
653 languages.clone(),
654 worktree_store.clone(),
655 cx,
656 )
657 });
658
659 let environment = ProjectEnvironment::new(&worktree_store, env, cx);
660 let toolchain_store = cx.new(|cx| {
661 ToolchainStore::local(
662 languages.clone(),
663 worktree_store.clone(),
664 environment.clone(),
665 cx,
666 )
667 });
668 let task_store = cx.new(|cx| {
669 TaskStore::local(
670 fs.clone(),
671 buffer_store.downgrade(),
672 worktree_store.clone(),
673 toolchain_store.read(cx).as_language_toolchain_store(),
674 environment.clone(),
675 cx,
676 )
677 });
678
679 let settings_observer = cx.new(|cx| {
680 SettingsObserver::new_local(
681 fs.clone(),
682 worktree_store.clone(),
683 task_store.clone(),
684 cx,
685 )
686 });
687 cx.subscribe(&settings_observer, Self::on_settings_observer_event)
688 .detach();
689
690 let lsp_store = cx.new(|cx| {
691 LspStore::new_local(
692 buffer_store.clone(),
693 worktree_store.clone(),
694 prettier_store.clone(),
695 toolchain_store.clone(),
696 environment.clone(),
697 languages.clone(),
698 client.http_client(),
699 fs.clone(),
700 cx,
701 )
702 });
703
704 let git_store =
705 cx.new(|cx| GitStore::new(&worktree_store, buffer_store.clone(), None, None, cx));
706
707 cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
708
709 Self {
710 buffer_ordered_messages_tx: tx,
711 collaborators: Default::default(),
712 worktree_store,
713 buffer_store,
714 image_store,
715 lsp_store,
716 join_project_response_message_id: 0,
717 client_state: ProjectClientState::Local,
718 git_store,
719 client_subscriptions: Vec::new(),
720 _subscriptions: vec![cx.on_release(Self::release)],
721 active_entry: None,
722 snippets,
723 languages,
724 client,
725 task_store,
726 user_store,
727 settings_observer,
728 fs,
729 ssh_client: None,
730 buffers_needing_diff: Default::default(),
731 git_diff_debouncer: DebouncedDelay::new(),
732 terminals: Terminals {
733 local_handles: Vec::new(),
734 },
735 node: Some(node),
736 search_history: Self::new_search_history(),
737 environment,
738 remotely_created_models: Default::default(),
739
740 search_included_history: Self::new_search_history(),
741 search_excluded_history: Self::new_search_history(),
742
743 toolchain_store: Some(toolchain_store),
744 }
745 })
746 }
747
748 pub fn ssh(
749 ssh: Entity<SshRemoteClient>,
750 client: Arc<Client>,
751 node: NodeRuntime,
752 user_store: Entity<UserStore>,
753 languages: Arc<LanguageRegistry>,
754 fs: Arc<dyn Fs>,
755 cx: &mut App,
756 ) -> Entity<Self> {
757 cx.new(|cx: &mut Context<Self>| {
758 let (tx, rx) = mpsc::unbounded();
759 cx.spawn(move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
760 .detach();
761 let global_snippets_dir = paths::config_dir().join("snippets");
762 let snippets =
763 SnippetProvider::new(fs.clone(), BTreeSet::from_iter([global_snippets_dir]), cx);
764
765 let ssh_proto = ssh.read(cx).proto_client();
766 let worktree_store =
767 cx.new(|_| WorktreeStore::remote(false, ssh_proto.clone(), SSH_PROJECT_ID));
768 cx.subscribe(&worktree_store, Self::on_worktree_store_event)
769 .detach();
770
771 let buffer_store = cx.new(|cx| {
772 BufferStore::remote(
773 worktree_store.clone(),
774 ssh.read(cx).proto_client(),
775 SSH_PROJECT_ID,
776 cx,
777 )
778 });
779 let image_store = cx.new(|cx| {
780 ImageStore::remote(
781 worktree_store.clone(),
782 ssh.read(cx).proto_client(),
783 SSH_PROJECT_ID,
784 cx,
785 )
786 });
787 cx.subscribe(&buffer_store, Self::on_buffer_store_event)
788 .detach();
789 let toolchain_store = cx
790 .new(|cx| ToolchainStore::remote(SSH_PROJECT_ID, ssh.read(cx).proto_client(), cx));
791 let task_store = cx.new(|cx| {
792 TaskStore::remote(
793 fs.clone(),
794 buffer_store.downgrade(),
795 worktree_store.clone(),
796 toolchain_store.read(cx).as_language_toolchain_store(),
797 ssh.read(cx).proto_client(),
798 SSH_PROJECT_ID,
799 cx,
800 )
801 });
802
803 let settings_observer = cx.new(|cx| {
804 SettingsObserver::new_remote(worktree_store.clone(), task_store.clone(), cx)
805 });
806 cx.subscribe(&settings_observer, Self::on_settings_observer_event)
807 .detach();
808
809 let environment = ProjectEnvironment::new(&worktree_store, None, cx);
810
811 let lsp_store = cx.new(|cx| {
812 LspStore::new_remote(
813 buffer_store.clone(),
814 worktree_store.clone(),
815 Some(toolchain_store.clone()),
816 languages.clone(),
817 ssh_proto.clone(),
818 SSH_PROJECT_ID,
819 fs.clone(),
820 cx,
821 )
822 });
823 cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
824
825 let git_store = cx.new(|cx| {
826 GitStore::new(
827 &worktree_store,
828 buffer_store.clone(),
829 Some(ssh_proto.clone()),
830 Some(ProjectId(SSH_PROJECT_ID)),
831 cx,
832 )
833 });
834
835 cx.subscribe(&ssh, Self::on_ssh_event).detach();
836 cx.observe(&ssh, |_, _, cx| cx.notify()).detach();
837
838 let this = Self {
839 buffer_ordered_messages_tx: tx,
840 collaborators: Default::default(),
841 worktree_store,
842 buffer_store,
843 image_store,
844 lsp_store,
845 join_project_response_message_id: 0,
846 client_state: ProjectClientState::Local,
847 git_store,
848 client_subscriptions: Vec::new(),
849 _subscriptions: vec![
850 cx.on_release(Self::release),
851 cx.on_app_quit(|this, cx| {
852 let shutdown = this.ssh_client.take().and_then(|client| {
853 client
854 .read(cx)
855 .shutdown_processes(Some(proto::ShutdownRemoteServer {}))
856 });
857
858 cx.background_executor().spawn(async move {
859 if let Some(shutdown) = shutdown {
860 shutdown.await;
861 }
862 })
863 }),
864 ],
865 active_entry: None,
866 snippets,
867 languages,
868 client,
869 task_store,
870 user_store,
871 settings_observer,
872 fs,
873 ssh_client: Some(ssh.clone()),
874 buffers_needing_diff: Default::default(),
875 git_diff_debouncer: DebouncedDelay::new(),
876 terminals: Terminals {
877 local_handles: Vec::new(),
878 },
879 node: Some(node),
880 search_history: Self::new_search_history(),
881 environment,
882 remotely_created_models: Default::default(),
883
884 search_included_history: Self::new_search_history(),
885 search_excluded_history: Self::new_search_history(),
886
887 toolchain_store: Some(toolchain_store),
888 };
889
890 // ssh -> local machine handlers
891 let ssh = ssh.read(cx);
892 ssh.subscribe_to_entity(SSH_PROJECT_ID, &cx.entity());
893 ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.buffer_store);
894 ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.worktree_store);
895 ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.lsp_store);
896 ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.settings_observer);
897 ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.git_store);
898
899 ssh_proto.add_entity_message_handler(Self::handle_create_buffer_for_peer);
900 ssh_proto.add_entity_message_handler(Self::handle_update_worktree);
901 ssh_proto.add_entity_message_handler(Self::handle_update_project);
902 ssh_proto.add_entity_message_handler(Self::handle_toast);
903 ssh_proto.add_entity_request_handler(Self::handle_language_server_prompt_request);
904 ssh_proto.add_entity_message_handler(Self::handle_hide_toast);
905 ssh_proto.add_entity_request_handler(Self::handle_update_buffer_from_ssh);
906 BufferStore::init(&ssh_proto);
907 LspStore::init(&ssh_proto);
908 SettingsObserver::init(&ssh_proto);
909 TaskStore::init(Some(&ssh_proto));
910 ToolchainStore::init(&ssh_proto);
911 GitStore::init(&ssh_proto);
912
913 this
914 })
915 }
916
917 pub async fn remote(
918 remote_id: u64,
919 client: Arc<Client>,
920 user_store: Entity<UserStore>,
921 languages: Arc<LanguageRegistry>,
922 fs: Arc<dyn Fs>,
923 cx: AsyncApp,
924 ) -> Result<Entity<Self>> {
925 let project =
926 Self::in_room(remote_id, client, user_store, languages, fs, cx.clone()).await?;
927 cx.update(|cx| {
928 connection_manager::Manager::global(cx).update(cx, |manager, cx| {
929 manager.maintain_project_connection(&project, cx)
930 })
931 })?;
932 Ok(project)
933 }
934
935 pub async fn in_room(
936 remote_id: u64,
937 client: Arc<Client>,
938 user_store: Entity<UserStore>,
939 languages: Arc<LanguageRegistry>,
940 fs: Arc<dyn Fs>,
941 cx: AsyncApp,
942 ) -> Result<Entity<Self>> {
943 client.authenticate_and_connect(true, &cx).await?;
944
945 let subscriptions = [
946 EntitySubscription::Project(client.subscribe_to_entity::<Self>(remote_id)?),
947 EntitySubscription::BufferStore(client.subscribe_to_entity::<BufferStore>(remote_id)?),
948 EntitySubscription::WorktreeStore(
949 client.subscribe_to_entity::<WorktreeStore>(remote_id)?,
950 ),
951 EntitySubscription::LspStore(client.subscribe_to_entity::<LspStore>(remote_id)?),
952 EntitySubscription::SettingsObserver(
953 client.subscribe_to_entity::<SettingsObserver>(remote_id)?,
954 ),
955 ];
956 let response = client
957 .request_envelope(proto::JoinProject {
958 project_id: remote_id,
959 })
960 .await?;
961 Self::from_join_project_response(
962 response,
963 subscriptions,
964 client,
965 false,
966 user_store,
967 languages,
968 fs,
969 cx,
970 )
971 .await
972 }
973
974 async fn from_join_project_response(
975 response: TypedEnvelope<proto::JoinProjectResponse>,
976 subscriptions: [EntitySubscription; 5],
977 client: Arc<Client>,
978 run_tasks: bool,
979 user_store: Entity<UserStore>,
980 languages: Arc<LanguageRegistry>,
981 fs: Arc<dyn Fs>,
982 mut cx: AsyncApp,
983 ) -> Result<Entity<Self>> {
984 let remote_id = response.payload.project_id;
985 let role = response.payload.role();
986
987 let worktree_store = cx.new(|_| {
988 WorktreeStore::remote(true, client.clone().into(), response.payload.project_id)
989 })?;
990 let buffer_store = cx.new(|cx| {
991 BufferStore::remote(worktree_store.clone(), client.clone().into(), remote_id, cx)
992 })?;
993 let image_store = cx.new(|cx| {
994 ImageStore::remote(worktree_store.clone(), client.clone().into(), remote_id, cx)
995 })?;
996
997 let lsp_store = cx.new(|cx| {
998 let mut lsp_store = LspStore::new_remote(
999 buffer_store.clone(),
1000 worktree_store.clone(),
1001 None,
1002 languages.clone(),
1003 client.clone().into(),
1004 remote_id,
1005 fs.clone(),
1006 cx,
1007 );
1008 lsp_store.set_language_server_statuses_from_proto(response.payload.language_servers);
1009 lsp_store
1010 })?;
1011
1012 let task_store = cx.new(|cx| {
1013 if run_tasks {
1014 TaskStore::remote(
1015 fs.clone(),
1016 buffer_store.downgrade(),
1017 worktree_store.clone(),
1018 Arc::new(EmptyToolchainStore),
1019 client.clone().into(),
1020 remote_id,
1021 cx,
1022 )
1023 } else {
1024 TaskStore::Noop
1025 }
1026 })?;
1027
1028 let settings_observer = cx.new(|cx| {
1029 SettingsObserver::new_remote(worktree_store.clone(), task_store.clone(), cx)
1030 })?;
1031
1032 let git_store = cx.new(|cx| {
1033 GitStore::new(
1034 &worktree_store,
1035 buffer_store.clone(),
1036 Some(client.clone().into()),
1037 Some(ProjectId(remote_id)),
1038 cx,
1039 )
1040 })?;
1041
1042 let this = cx.new(|cx| {
1043 let replica_id = response.payload.replica_id as ReplicaId;
1044
1045 let snippets = SnippetProvider::new(fs.clone(), BTreeSet::from_iter([]), cx);
1046
1047 let mut worktrees = Vec::new();
1048 for worktree in response.payload.worktrees {
1049 let worktree =
1050 Worktree::remote(remote_id, replica_id, worktree, client.clone().into(), cx);
1051 worktrees.push(worktree);
1052 }
1053
1054 let (tx, rx) = mpsc::unbounded();
1055 cx.spawn(move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
1056 .detach();
1057
1058 cx.subscribe(&worktree_store, Self::on_worktree_store_event)
1059 .detach();
1060
1061 cx.subscribe(&buffer_store, Self::on_buffer_store_event)
1062 .detach();
1063 cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
1064 cx.subscribe(&settings_observer, Self::on_settings_observer_event)
1065 .detach();
1066
1067 let mut this = Self {
1068 buffer_ordered_messages_tx: tx,
1069 buffer_store: buffer_store.clone(),
1070 image_store,
1071 worktree_store: worktree_store.clone(),
1072 lsp_store: lsp_store.clone(),
1073 active_entry: None,
1074 collaborators: Default::default(),
1075 join_project_response_message_id: response.message_id,
1076 languages,
1077 user_store: user_store.clone(),
1078 task_store,
1079 snippets,
1080 fs,
1081 ssh_client: None,
1082 settings_observer: settings_observer.clone(),
1083 client_subscriptions: Default::default(),
1084 _subscriptions: vec![cx.on_release(Self::release)],
1085 client: client.clone(),
1086 client_state: ProjectClientState::Remote {
1087 sharing_has_stopped: false,
1088 capability: Capability::ReadWrite,
1089 remote_id,
1090 replica_id,
1091 },
1092 git_store,
1093 buffers_needing_diff: Default::default(),
1094 git_diff_debouncer: DebouncedDelay::new(),
1095 terminals: Terminals {
1096 local_handles: Vec::new(),
1097 },
1098 node: None,
1099 search_history: Self::new_search_history(),
1100 search_included_history: Self::new_search_history(),
1101 search_excluded_history: Self::new_search_history(),
1102 environment: ProjectEnvironment::new(&worktree_store, None, cx),
1103 remotely_created_models: Arc::new(Mutex::new(RemotelyCreatedModels::default())),
1104 toolchain_store: None,
1105 };
1106 this.set_role(role, cx);
1107 for worktree in worktrees {
1108 this.add_worktree(&worktree, cx);
1109 }
1110 this
1111 })?;
1112
1113 let subscriptions = subscriptions
1114 .into_iter()
1115 .map(|s| match s {
1116 EntitySubscription::BufferStore(subscription) => {
1117 subscription.set_entity(&buffer_store, &mut cx)
1118 }
1119 EntitySubscription::WorktreeStore(subscription) => {
1120 subscription.set_entity(&worktree_store, &mut cx)
1121 }
1122 EntitySubscription::SettingsObserver(subscription) => {
1123 subscription.set_entity(&settings_observer, &mut cx)
1124 }
1125 EntitySubscription::Project(subscription) => {
1126 subscription.set_entity(&this, &mut cx)
1127 }
1128 EntitySubscription::LspStore(subscription) => {
1129 subscription.set_entity(&lsp_store, &mut cx)
1130 }
1131 })
1132 .collect::<Vec<_>>();
1133
1134 let user_ids = response
1135 .payload
1136 .collaborators
1137 .iter()
1138 .map(|peer| peer.user_id)
1139 .collect();
1140 user_store
1141 .update(&mut cx, |user_store, cx| user_store.get_users(user_ids, cx))?
1142 .await?;
1143
1144 this.update(&mut cx, |this, cx| {
1145 this.set_collaborators_from_proto(response.payload.collaborators, cx)?;
1146 this.client_subscriptions.extend(subscriptions);
1147 anyhow::Ok(())
1148 })??;
1149
1150 Ok(this)
1151 }
1152
1153 fn new_search_history() -> SearchHistory {
1154 SearchHistory::new(
1155 Some(MAX_PROJECT_SEARCH_HISTORY_SIZE),
1156 search_history::QueryInsertionBehavior::AlwaysInsert,
1157 )
1158 }
1159
1160 fn release(&mut self, cx: &mut App) {
1161 if let Some(client) = self.ssh_client.take() {
1162 let shutdown = client
1163 .read(cx)
1164 .shutdown_processes(Some(proto::ShutdownRemoteServer {}));
1165
1166 cx.background_spawn(async move {
1167 if let Some(shutdown) = shutdown {
1168 shutdown.await;
1169 }
1170 })
1171 .detach()
1172 }
1173
1174 match &self.client_state {
1175 ProjectClientState::Local => {}
1176 ProjectClientState::Shared { .. } => {
1177 let _ = self.unshare_internal(cx);
1178 }
1179 ProjectClientState::Remote { remote_id, .. } => {
1180 let _ = self.client.send(proto::LeaveProject {
1181 project_id: *remote_id,
1182 });
1183 self.disconnected_from_host_internal(cx);
1184 }
1185 }
1186 }
1187
1188 #[cfg(any(test, feature = "test-support"))]
1189 pub async fn example(
1190 root_paths: impl IntoIterator<Item = &Path>,
1191 cx: &mut AsyncApp,
1192 ) -> Entity<Project> {
1193 use clock::FakeSystemClock;
1194
1195 let fs = Arc::new(RealFs::default());
1196 let languages = LanguageRegistry::test(cx.background_executor().clone());
1197 let clock = Arc::new(FakeSystemClock::new());
1198 let http_client = http_client::FakeHttpClient::with_404_response();
1199 let client = cx
1200 .update(|cx| client::Client::new(clock, http_client.clone(), cx))
1201 .unwrap();
1202 let user_store = cx.new(|cx| UserStore::new(client.clone(), cx)).unwrap();
1203 let project = cx
1204 .update(|cx| {
1205 Project::local(
1206 client,
1207 node_runtime::NodeRuntime::unavailable(),
1208 user_store,
1209 Arc::new(languages),
1210 fs,
1211 None,
1212 cx,
1213 )
1214 })
1215 .unwrap();
1216 for path in root_paths {
1217 let (tree, _) = project
1218 .update(cx, |project, cx| {
1219 project.find_or_create_worktree(path, true, cx)
1220 })
1221 .unwrap()
1222 .await
1223 .unwrap();
1224 tree.update(cx, |tree, _| tree.as_local().unwrap().scan_complete())
1225 .unwrap()
1226 .await;
1227 }
1228 project
1229 }
1230
1231 #[cfg(any(test, feature = "test-support"))]
1232 pub async fn test(
1233 fs: Arc<dyn Fs>,
1234 root_paths: impl IntoIterator<Item = &Path>,
1235 cx: &mut gpui::TestAppContext,
1236 ) -> Entity<Project> {
1237 use clock::FakeSystemClock;
1238
1239 let languages = LanguageRegistry::test(cx.executor());
1240 let clock = Arc::new(FakeSystemClock::new());
1241 let http_client = http_client::FakeHttpClient::with_404_response();
1242 let client = cx.update(|cx| client::Client::new(clock, http_client.clone(), cx));
1243 let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
1244 let project = cx.update(|cx| {
1245 Project::local(
1246 client,
1247 node_runtime::NodeRuntime::unavailable(),
1248 user_store,
1249 Arc::new(languages),
1250 fs,
1251 None,
1252 cx,
1253 )
1254 });
1255 for path in root_paths {
1256 let (tree, _) = project
1257 .update(cx, |project, cx| {
1258 project.find_or_create_worktree(path, true, cx)
1259 })
1260 .await
1261 .unwrap();
1262
1263 tree.update(cx, |tree, _| tree.as_local().unwrap().scan_complete())
1264 .await;
1265 }
1266 project
1267 }
1268
1269 pub fn lsp_store(&self) -> Entity<LspStore> {
1270 self.lsp_store.clone()
1271 }
1272
1273 pub fn worktree_store(&self) -> Entity<WorktreeStore> {
1274 self.worktree_store.clone()
1275 }
1276
1277 pub fn buffer_for_id(&self, remote_id: BufferId, cx: &App) -> Option<Entity<Buffer>> {
1278 self.buffer_store.read(cx).get(remote_id)
1279 }
1280
1281 pub fn languages(&self) -> &Arc<LanguageRegistry> {
1282 &self.languages
1283 }
1284
1285 pub fn client(&self) -> Arc<Client> {
1286 self.client.clone()
1287 }
1288
1289 pub fn ssh_client(&self) -> Option<Entity<SshRemoteClient>> {
1290 self.ssh_client.clone()
1291 }
1292
1293 pub fn user_store(&self) -> Entity<UserStore> {
1294 self.user_store.clone()
1295 }
1296
1297 pub fn node_runtime(&self) -> Option<&NodeRuntime> {
1298 self.node.as_ref()
1299 }
1300
1301 pub fn opened_buffers(&self, cx: &App) -> Vec<Entity<Buffer>> {
1302 self.buffer_store.read(cx).buffers().collect()
1303 }
1304
1305 pub fn environment(&self) -> &Entity<ProjectEnvironment> {
1306 &self.environment
1307 }
1308
1309 pub fn cli_environment(&self, cx: &App) -> Option<HashMap<String, String>> {
1310 self.environment.read(cx).get_cli_environment()
1311 }
1312
1313 pub fn shell_environment_errors<'a>(
1314 &'a self,
1315 cx: &'a App,
1316 ) -> impl Iterator<Item = (&'a WorktreeId, &'a EnvironmentErrorMessage)> {
1317 self.environment.read(cx).environment_errors()
1318 }
1319
1320 pub fn remove_environment_error(&mut self, cx: &mut Context<Self>, worktree_id: WorktreeId) {
1321 self.environment.update(cx, |environment, _| {
1322 environment.remove_environment_error(worktree_id);
1323 });
1324 }
1325
1326 #[cfg(any(test, feature = "test-support"))]
1327 pub fn has_open_buffer(&self, path: impl Into<ProjectPath>, cx: &App) -> bool {
1328 self.buffer_store
1329 .read(cx)
1330 .get_by_path(&path.into(), cx)
1331 .is_some()
1332 }
1333
1334 pub fn fs(&self) -> &Arc<dyn Fs> {
1335 &self.fs
1336 }
1337
1338 pub fn remote_id(&self) -> Option<u64> {
1339 match self.client_state {
1340 ProjectClientState::Local => None,
1341 ProjectClientState::Shared { remote_id, .. }
1342 | ProjectClientState::Remote { remote_id, .. } => Some(remote_id),
1343 }
1344 }
1345
1346 pub fn supports_terminal(&self, _cx: &App) -> bool {
1347 if self.is_local() {
1348 return true;
1349 }
1350 if self.is_via_ssh() {
1351 return true;
1352 }
1353
1354 return false;
1355 }
1356
1357 pub fn ssh_connection_string(&self, cx: &App) -> Option<SharedString> {
1358 if let Some(ssh_state) = &self.ssh_client {
1359 return Some(ssh_state.read(cx).connection_string().into());
1360 }
1361
1362 return None;
1363 }
1364
1365 pub fn ssh_connection_state(&self, cx: &App) -> Option<remote::ConnectionState> {
1366 self.ssh_client
1367 .as_ref()
1368 .map(|ssh| ssh.read(cx).connection_state())
1369 }
1370
1371 pub fn ssh_connection_options(&self, cx: &App) -> Option<SshConnectionOptions> {
1372 self.ssh_client
1373 .as_ref()
1374 .map(|ssh| ssh.read(cx).connection_options())
1375 }
1376
1377 pub fn replica_id(&self) -> ReplicaId {
1378 match self.client_state {
1379 ProjectClientState::Remote { replica_id, .. } => replica_id,
1380 _ => {
1381 if self.ssh_client.is_some() {
1382 1
1383 } else {
1384 0
1385 }
1386 }
1387 }
1388 }
1389
1390 pub fn task_store(&self) -> &Entity<TaskStore> {
1391 &self.task_store
1392 }
1393
1394 pub fn snippets(&self) -> &Entity<SnippetProvider> {
1395 &self.snippets
1396 }
1397
1398 pub fn search_history(&self, kind: SearchInputKind) -> &SearchHistory {
1399 match kind {
1400 SearchInputKind::Query => &self.search_history,
1401 SearchInputKind::Include => &self.search_included_history,
1402 SearchInputKind::Exclude => &self.search_excluded_history,
1403 }
1404 }
1405
1406 pub fn search_history_mut(&mut self, kind: SearchInputKind) -> &mut SearchHistory {
1407 match kind {
1408 SearchInputKind::Query => &mut self.search_history,
1409 SearchInputKind::Include => &mut self.search_included_history,
1410 SearchInputKind::Exclude => &mut self.search_excluded_history,
1411 }
1412 }
1413
1414 pub fn collaborators(&self) -> &HashMap<proto::PeerId, Collaborator> {
1415 &self.collaborators
1416 }
1417
1418 pub fn host(&self) -> Option<&Collaborator> {
1419 self.collaborators.values().find(|c| c.is_host)
1420 }
1421
1422 pub fn set_worktrees_reordered(&mut self, worktrees_reordered: bool, cx: &mut App) {
1423 self.worktree_store.update(cx, |store, _| {
1424 store.set_worktrees_reordered(worktrees_reordered);
1425 });
1426 }
1427
1428 /// Collect all worktrees, including ones that don't appear in the project panel
1429 pub fn worktrees<'a>(
1430 &self,
1431 cx: &'a App,
1432 ) -> impl 'a + DoubleEndedIterator<Item = Entity<Worktree>> {
1433 self.worktree_store.read(cx).worktrees()
1434 }
1435
1436 /// Collect all user-visible worktrees, the ones that appear in the project panel.
1437 pub fn visible_worktrees<'a>(
1438 &'a self,
1439 cx: &'a App,
1440 ) -> impl 'a + DoubleEndedIterator<Item = Entity<Worktree>> {
1441 self.worktree_store.read(cx).visible_worktrees(cx)
1442 }
1443
1444 pub fn worktree_root_names<'a>(&'a self, cx: &'a App) -> impl Iterator<Item = &'a str> {
1445 self.visible_worktrees(cx)
1446 .map(|tree| tree.read(cx).root_name())
1447 }
1448
1449 pub fn worktree_for_id(&self, id: WorktreeId, cx: &App) -> Option<Entity<Worktree>> {
1450 self.worktree_store.read(cx).worktree_for_id(id, cx)
1451 }
1452
1453 pub fn worktree_for_entry(
1454 &self,
1455 entry_id: ProjectEntryId,
1456 cx: &App,
1457 ) -> Option<Entity<Worktree>> {
1458 self.worktree_store
1459 .read(cx)
1460 .worktree_for_entry(entry_id, cx)
1461 }
1462
1463 pub fn worktree_id_for_entry(&self, entry_id: ProjectEntryId, cx: &App) -> Option<WorktreeId> {
1464 self.worktree_for_entry(entry_id, cx)
1465 .map(|worktree| worktree.read(cx).id())
1466 }
1467
1468 /// Checks if the entry is the root of a worktree.
1469 pub fn entry_is_worktree_root(&self, entry_id: ProjectEntryId, cx: &App) -> bool {
1470 self.worktree_for_entry(entry_id, cx)
1471 .map(|worktree| {
1472 worktree
1473 .read(cx)
1474 .root_entry()
1475 .is_some_and(|e| e.id == entry_id)
1476 })
1477 .unwrap_or(false)
1478 }
1479
1480 pub fn project_path_git_status(
1481 &self,
1482 project_path: &ProjectPath,
1483 cx: &App,
1484 ) -> Option<FileStatus> {
1485 self.worktree_for_id(project_path.worktree_id, cx)
1486 .and_then(|worktree| worktree.read(cx).status_for_file(&project_path.path))
1487 }
1488
1489 pub fn visibility_for_paths(
1490 &self,
1491 paths: &[PathBuf],
1492 metadatas: &[Metadata],
1493 exclude_sub_dirs: bool,
1494 cx: &App,
1495 ) -> Option<bool> {
1496 paths
1497 .iter()
1498 .zip(metadatas)
1499 .map(|(path, metadata)| self.visibility_for_path(path, metadata, exclude_sub_dirs, cx))
1500 .max()
1501 .flatten()
1502 }
1503
1504 pub fn visibility_for_path(
1505 &self,
1506 path: &Path,
1507 metadata: &Metadata,
1508 exclude_sub_dirs: bool,
1509 cx: &App,
1510 ) -> Option<bool> {
1511 let sanitized_path = SanitizedPath::from(path);
1512 let path = sanitized_path.as_path();
1513 self.worktrees(cx)
1514 .filter_map(|worktree| {
1515 let worktree = worktree.read(cx);
1516 let abs_path = worktree.as_local()?.abs_path();
1517 let contains = path == abs_path
1518 || (path.starts_with(abs_path) && (!exclude_sub_dirs || !metadata.is_dir));
1519 contains.then(|| worktree.is_visible())
1520 })
1521 .max()
1522 }
1523
1524 pub fn create_entry(
1525 &mut self,
1526 project_path: impl Into<ProjectPath>,
1527 is_directory: bool,
1528 cx: &mut Context<Self>,
1529 ) -> Task<Result<CreatedEntry>> {
1530 let project_path = project_path.into();
1531 let Some(worktree) = self.worktree_for_id(project_path.worktree_id, cx) else {
1532 return Task::ready(Err(anyhow!(format!(
1533 "No worktree for path {project_path:?}"
1534 ))));
1535 };
1536 worktree.update(cx, |worktree, cx| {
1537 worktree.create_entry(project_path.path, is_directory, cx)
1538 })
1539 }
1540
1541 pub fn copy_entry(
1542 &mut self,
1543 entry_id: ProjectEntryId,
1544 relative_worktree_source_path: Option<PathBuf>,
1545 new_path: impl Into<Arc<Path>>,
1546 cx: &mut Context<Self>,
1547 ) -> Task<Result<Option<Entry>>> {
1548 let Some(worktree) = self.worktree_for_entry(entry_id, cx) else {
1549 return Task::ready(Ok(None));
1550 };
1551 worktree.update(cx, |worktree, cx| {
1552 worktree.copy_entry(entry_id, relative_worktree_source_path, new_path, cx)
1553 })
1554 }
1555
1556 /// Renames the project entry with given `entry_id`.
1557 ///
1558 /// `new_path` is a relative path to worktree root.
1559 /// If root entry is renamed then its new root name is used instead.
1560 pub fn rename_entry(
1561 &mut self,
1562 entry_id: ProjectEntryId,
1563 new_path: impl Into<Arc<Path>>,
1564 cx: &mut Context<Self>,
1565 ) -> Task<Result<CreatedEntry>> {
1566 let worktree_store = self.worktree_store.read(cx);
1567 let new_path = new_path.into();
1568 let Some((worktree, old_path, is_dir)) = worktree_store
1569 .worktree_and_entry_for_id(entry_id, cx)
1570 .map(|(worktree, entry)| (worktree, entry.path.clone(), entry.is_dir()))
1571 else {
1572 return Task::ready(Err(anyhow!(format!("No worktree for entry {entry_id:?}"))));
1573 };
1574
1575 let worktree_id = worktree.read(cx).id();
1576 let is_root_entry = self.entry_is_worktree_root(entry_id, cx);
1577
1578 let lsp_store = self.lsp_store().downgrade();
1579 cx.spawn(|_, mut cx| async move {
1580 let (old_abs_path, new_abs_path) = {
1581 let root_path = worktree.update(&mut cx, |this, _| this.abs_path())?;
1582 let new_abs_path = if is_root_entry {
1583 root_path.parent().unwrap().join(&new_path)
1584 } else {
1585 root_path.join(&new_path)
1586 };
1587 (root_path.join(&old_path), new_abs_path)
1588 };
1589 LspStore::will_rename_entry(
1590 lsp_store.clone(),
1591 worktree_id,
1592 &old_abs_path,
1593 &new_abs_path,
1594 is_dir,
1595 cx.clone(),
1596 )
1597 .await;
1598
1599 let entry = worktree
1600 .update(&mut cx, |worktree, cx| {
1601 worktree.rename_entry(entry_id, new_path.clone(), cx)
1602 })?
1603 .await?;
1604
1605 lsp_store
1606 .update(&mut cx, |this, _| {
1607 this.did_rename_entry(worktree_id, &old_abs_path, &new_abs_path, is_dir);
1608 })
1609 .ok();
1610 Ok(entry)
1611 })
1612 }
1613
1614 pub fn delete_file(
1615 &mut self,
1616 path: ProjectPath,
1617 trash: bool,
1618 cx: &mut Context<Self>,
1619 ) -> Option<Task<Result<()>>> {
1620 let entry = self.entry_for_path(&path, cx)?;
1621 self.delete_entry(entry.id, trash, cx)
1622 }
1623
1624 pub fn delete_entry(
1625 &mut self,
1626 entry_id: ProjectEntryId,
1627 trash: bool,
1628 cx: &mut Context<Self>,
1629 ) -> Option<Task<Result<()>>> {
1630 let worktree = self.worktree_for_entry(entry_id, cx)?;
1631 cx.emit(Event::DeletedEntry(worktree.read(cx).id(), entry_id));
1632 worktree.update(cx, |worktree, cx| {
1633 worktree.delete_entry(entry_id, trash, cx)
1634 })
1635 }
1636
1637 pub fn expand_entry(
1638 &mut self,
1639 worktree_id: WorktreeId,
1640 entry_id: ProjectEntryId,
1641 cx: &mut Context<Self>,
1642 ) -> Option<Task<Result<()>>> {
1643 let worktree = self.worktree_for_id(worktree_id, cx)?;
1644 worktree.update(cx, |worktree, cx| worktree.expand_entry(entry_id, cx))
1645 }
1646
1647 pub fn expand_all_for_entry(
1648 &mut self,
1649 worktree_id: WorktreeId,
1650 entry_id: ProjectEntryId,
1651 cx: &mut Context<Self>,
1652 ) -> Option<Task<Result<()>>> {
1653 let worktree = self.worktree_for_id(worktree_id, cx)?;
1654 let task = worktree.update(cx, |worktree, cx| {
1655 worktree.expand_all_for_entry(entry_id, cx)
1656 });
1657 Some(cx.spawn(|this, mut cx| async move {
1658 task.ok_or_else(|| anyhow!("no task"))?.await?;
1659 this.update(&mut cx, |_, cx| {
1660 cx.emit(Event::ExpandedAllForEntry(worktree_id, entry_id));
1661 })?;
1662 Ok(())
1663 }))
1664 }
1665
1666 pub fn shared(&mut self, project_id: u64, cx: &mut Context<Self>) -> Result<()> {
1667 if !matches!(self.client_state, ProjectClientState::Local) {
1668 return Err(anyhow!("project was already shared"));
1669 }
1670
1671 self.client_subscriptions.extend([
1672 self.client
1673 .subscribe_to_entity(project_id)?
1674 .set_entity(&cx.entity(), &mut cx.to_async()),
1675 self.client
1676 .subscribe_to_entity(project_id)?
1677 .set_entity(&self.worktree_store, &mut cx.to_async()),
1678 self.client
1679 .subscribe_to_entity(project_id)?
1680 .set_entity(&self.buffer_store, &mut cx.to_async()),
1681 self.client
1682 .subscribe_to_entity(project_id)?
1683 .set_entity(&self.lsp_store, &mut cx.to_async()),
1684 self.client
1685 .subscribe_to_entity(project_id)?
1686 .set_entity(&self.settings_observer, &mut cx.to_async()),
1687 self.client
1688 .subscribe_to_entity(project_id)?
1689 .set_entity(&self.git_store, &mut cx.to_async()),
1690 ]);
1691
1692 self.buffer_store.update(cx, |buffer_store, cx| {
1693 buffer_store.shared(project_id, self.client.clone().into(), cx)
1694 });
1695 self.worktree_store.update(cx, |worktree_store, cx| {
1696 worktree_store.shared(project_id, self.client.clone().into(), cx);
1697 });
1698 self.lsp_store.update(cx, |lsp_store, cx| {
1699 lsp_store.shared(project_id, self.client.clone().into(), cx)
1700 });
1701 self.task_store.update(cx, |task_store, cx| {
1702 task_store.shared(project_id, self.client.clone().into(), cx);
1703 });
1704 self.settings_observer.update(cx, |settings_observer, cx| {
1705 settings_observer.shared(project_id, self.client.clone().into(), cx)
1706 });
1707
1708 self.client_state = ProjectClientState::Shared {
1709 remote_id: project_id,
1710 };
1711
1712 cx.emit(Event::RemoteIdChanged(Some(project_id)));
1713 cx.notify();
1714 Ok(())
1715 }
1716
1717 pub fn reshared(
1718 &mut self,
1719 message: proto::ResharedProject,
1720 cx: &mut Context<Self>,
1721 ) -> Result<()> {
1722 self.buffer_store
1723 .update(cx, |buffer_store, _| buffer_store.forget_shared_buffers());
1724 self.set_collaborators_from_proto(message.collaborators, cx)?;
1725
1726 self.worktree_store.update(cx, |worktree_store, cx| {
1727 worktree_store.send_project_updates(cx);
1728 });
1729 cx.notify();
1730 cx.emit(Event::Reshared);
1731 Ok(())
1732 }
1733
1734 pub fn rejoined(
1735 &mut self,
1736 message: proto::RejoinedProject,
1737 message_id: u32,
1738 cx: &mut Context<Self>,
1739 ) -> Result<()> {
1740 cx.update_global::<SettingsStore, _>(|store, cx| {
1741 self.worktree_store.update(cx, |worktree_store, cx| {
1742 for worktree in worktree_store.worktrees() {
1743 store
1744 .clear_local_settings(worktree.read(cx).id(), cx)
1745 .log_err();
1746 }
1747 });
1748 });
1749
1750 self.join_project_response_message_id = message_id;
1751 self.set_worktrees_from_proto(message.worktrees, cx)?;
1752 self.set_collaborators_from_proto(message.collaborators, cx)?;
1753 self.lsp_store.update(cx, |lsp_store, _| {
1754 lsp_store.set_language_server_statuses_from_proto(message.language_servers)
1755 });
1756 self.enqueue_buffer_ordered_message(BufferOrderedMessage::Resync)
1757 .unwrap();
1758 cx.emit(Event::Rejoined);
1759 cx.notify();
1760 Ok(())
1761 }
1762
1763 pub fn unshare(&mut self, cx: &mut Context<Self>) -> Result<()> {
1764 self.unshare_internal(cx)?;
1765 cx.notify();
1766 Ok(())
1767 }
1768
1769 fn unshare_internal(&mut self, cx: &mut App) -> Result<()> {
1770 if self.is_via_collab() {
1771 return Err(anyhow!("attempted to unshare a remote project"));
1772 }
1773
1774 if let ProjectClientState::Shared { remote_id, .. } = self.client_state {
1775 self.client_state = ProjectClientState::Local;
1776 self.collaborators.clear();
1777 self.client_subscriptions.clear();
1778 self.worktree_store.update(cx, |store, cx| {
1779 store.unshared(cx);
1780 });
1781 self.buffer_store.update(cx, |buffer_store, cx| {
1782 buffer_store.forget_shared_buffers();
1783 buffer_store.unshared(cx)
1784 });
1785 self.task_store.update(cx, |task_store, cx| {
1786 task_store.unshared(cx);
1787 });
1788 self.settings_observer.update(cx, |settings_observer, cx| {
1789 settings_observer.unshared(cx);
1790 });
1791
1792 self.client
1793 .send(proto::UnshareProject {
1794 project_id: remote_id,
1795 })
1796 .ok();
1797 Ok(())
1798 } else {
1799 Err(anyhow!("attempted to unshare an unshared project"))
1800 }
1801 }
1802
1803 pub fn disconnected_from_host(&mut self, cx: &mut Context<Self>) {
1804 if self.is_disconnected(cx) {
1805 return;
1806 }
1807 self.disconnected_from_host_internal(cx);
1808 cx.emit(Event::DisconnectedFromHost);
1809 cx.notify();
1810 }
1811
1812 pub fn set_role(&mut self, role: proto::ChannelRole, cx: &mut Context<Self>) {
1813 let new_capability =
1814 if role == proto::ChannelRole::Member || role == proto::ChannelRole::Admin {
1815 Capability::ReadWrite
1816 } else {
1817 Capability::ReadOnly
1818 };
1819 if let ProjectClientState::Remote { capability, .. } = &mut self.client_state {
1820 if *capability == new_capability {
1821 return;
1822 }
1823
1824 *capability = new_capability;
1825 for buffer in self.opened_buffers(cx) {
1826 buffer.update(cx, |buffer, cx| buffer.set_capability(new_capability, cx));
1827 }
1828 }
1829 }
1830
1831 fn disconnected_from_host_internal(&mut self, cx: &mut App) {
1832 if let ProjectClientState::Remote {
1833 sharing_has_stopped,
1834 ..
1835 } = &mut self.client_state
1836 {
1837 *sharing_has_stopped = true;
1838 self.collaborators.clear();
1839 self.worktree_store.update(cx, |store, cx| {
1840 store.disconnected_from_host(cx);
1841 });
1842 self.buffer_store.update(cx, |buffer_store, cx| {
1843 buffer_store.disconnected_from_host(cx)
1844 });
1845 self.lsp_store
1846 .update(cx, |lsp_store, _cx| lsp_store.disconnected_from_host());
1847 }
1848 }
1849
1850 pub fn close(&mut self, cx: &mut Context<Self>) {
1851 cx.emit(Event::Closed);
1852 }
1853
1854 pub fn is_disconnected(&self, cx: &App) -> bool {
1855 match &self.client_state {
1856 ProjectClientState::Remote {
1857 sharing_has_stopped,
1858 ..
1859 } => *sharing_has_stopped,
1860 ProjectClientState::Local if self.is_via_ssh() => self.ssh_is_disconnected(cx),
1861 _ => false,
1862 }
1863 }
1864
1865 fn ssh_is_disconnected(&self, cx: &App) -> bool {
1866 self.ssh_client
1867 .as_ref()
1868 .map(|ssh| ssh.read(cx).is_disconnected())
1869 .unwrap_or(false)
1870 }
1871
1872 pub fn capability(&self) -> Capability {
1873 match &self.client_state {
1874 ProjectClientState::Remote { capability, .. } => *capability,
1875 ProjectClientState::Shared { .. } | ProjectClientState::Local => Capability::ReadWrite,
1876 }
1877 }
1878
1879 pub fn is_read_only(&self, cx: &App) -> bool {
1880 self.is_disconnected(cx) || self.capability() == Capability::ReadOnly
1881 }
1882
1883 pub fn is_local(&self) -> bool {
1884 match &self.client_state {
1885 ProjectClientState::Local | ProjectClientState::Shared { .. } => {
1886 self.ssh_client.is_none()
1887 }
1888 ProjectClientState::Remote { .. } => false,
1889 }
1890 }
1891
1892 pub fn is_via_ssh(&self) -> bool {
1893 match &self.client_state {
1894 ProjectClientState::Local | ProjectClientState::Shared { .. } => {
1895 self.ssh_client.is_some()
1896 }
1897 ProjectClientState::Remote { .. } => false,
1898 }
1899 }
1900
1901 pub fn is_via_collab(&self) -> bool {
1902 match &self.client_state {
1903 ProjectClientState::Local | ProjectClientState::Shared { .. } => false,
1904 ProjectClientState::Remote { .. } => true,
1905 }
1906 }
1907
1908 pub fn create_buffer(&mut self, cx: &mut Context<Self>) -> Task<Result<Entity<Buffer>>> {
1909 self.buffer_store
1910 .update(cx, |buffer_store, cx| buffer_store.create_buffer(cx))
1911 }
1912
1913 pub fn create_local_buffer(
1914 &mut self,
1915 text: &str,
1916 language: Option<Arc<Language>>,
1917 cx: &mut Context<Self>,
1918 ) -> Entity<Buffer> {
1919 if self.is_via_collab() || self.is_via_ssh() {
1920 panic!("called create_local_buffer on a remote project")
1921 }
1922 self.buffer_store.update(cx, |buffer_store, cx| {
1923 buffer_store.create_local_buffer(text, language, cx)
1924 })
1925 }
1926
1927 pub fn open_path(
1928 &mut self,
1929 path: ProjectPath,
1930 cx: &mut Context<Self>,
1931 ) -> Task<Result<(Option<ProjectEntryId>, AnyEntity)>> {
1932 let task = self.open_buffer(path.clone(), cx);
1933 cx.spawn(move |_, cx| async move {
1934 let buffer = task.await?;
1935 let project_entry_id = buffer.read_with(&cx, |buffer, cx| {
1936 File::from_dyn(buffer.file()).and_then(|file| file.project_entry_id(cx))
1937 })?;
1938
1939 let buffer: &AnyEntity = &buffer;
1940 Ok((project_entry_id, buffer.clone()))
1941 })
1942 }
1943
1944 pub fn open_local_buffer(
1945 &mut self,
1946 abs_path: impl AsRef<Path>,
1947 cx: &mut Context<Self>,
1948 ) -> Task<Result<Entity<Buffer>>> {
1949 if let Some((worktree, relative_path)) = self.find_worktree(abs_path.as_ref(), cx) {
1950 self.open_buffer((worktree.read(cx).id(), relative_path), cx)
1951 } else {
1952 Task::ready(Err(anyhow!("no such path")))
1953 }
1954 }
1955
1956 #[cfg(any(test, feature = "test-support"))]
1957 pub fn open_local_buffer_with_lsp(
1958 &mut self,
1959 abs_path: impl AsRef<Path>,
1960 cx: &mut Context<Self>,
1961 ) -> Task<Result<(Entity<Buffer>, lsp_store::OpenLspBufferHandle)>> {
1962 if let Some((worktree, relative_path)) = self.find_worktree(abs_path.as_ref(), cx) {
1963 self.open_buffer_with_lsp((worktree.read(cx).id(), relative_path), cx)
1964 } else {
1965 Task::ready(Err(anyhow!("no such path")))
1966 }
1967 }
1968
1969 pub fn open_buffer(
1970 &mut self,
1971 path: impl Into<ProjectPath>,
1972 cx: &mut App,
1973 ) -> Task<Result<Entity<Buffer>>> {
1974 if self.is_disconnected(cx) {
1975 return Task::ready(Err(anyhow!(ErrorCode::Disconnected)));
1976 }
1977
1978 self.buffer_store.update(cx, |buffer_store, cx| {
1979 buffer_store.open_buffer(path.into(), cx)
1980 })
1981 }
1982
1983 #[cfg(any(test, feature = "test-support"))]
1984 pub fn open_buffer_with_lsp(
1985 &mut self,
1986 path: impl Into<ProjectPath>,
1987 cx: &mut Context<Self>,
1988 ) -> Task<Result<(Entity<Buffer>, lsp_store::OpenLspBufferHandle)>> {
1989 let buffer = self.open_buffer(path, cx);
1990 cx.spawn(|this, mut cx| async move {
1991 let buffer = buffer.await?;
1992 let handle = this.update(&mut cx, |project, cx| {
1993 project.register_buffer_with_language_servers(&buffer, cx)
1994 })?;
1995 Ok((buffer, handle))
1996 })
1997 }
1998
1999 pub fn register_buffer_with_language_servers(
2000 &self,
2001 buffer: &Entity<Buffer>,
2002 cx: &mut App,
2003 ) -> OpenLspBufferHandle {
2004 self.lsp_store.update(cx, |lsp_store, cx| {
2005 lsp_store.register_buffer_with_language_servers(&buffer, false, cx)
2006 })
2007 }
2008
2009 pub fn open_unstaged_diff(
2010 &mut self,
2011 buffer: Entity<Buffer>,
2012 cx: &mut Context<Self>,
2013 ) -> Task<Result<Entity<BufferDiff>>> {
2014 if self.is_disconnected(cx) {
2015 return Task::ready(Err(anyhow!(ErrorCode::Disconnected)));
2016 }
2017
2018 self.buffer_store.update(cx, |buffer_store, cx| {
2019 buffer_store.open_unstaged_diff(buffer, cx)
2020 })
2021 }
2022
2023 pub fn open_uncommitted_diff(
2024 &mut self,
2025 buffer: Entity<Buffer>,
2026 cx: &mut Context<Self>,
2027 ) -> Task<Result<Entity<BufferDiff>>> {
2028 if self.is_disconnected(cx) {
2029 return Task::ready(Err(anyhow!(ErrorCode::Disconnected)));
2030 }
2031
2032 self.buffer_store.update(cx, |buffer_store, cx| {
2033 buffer_store.open_uncommitted_diff(buffer, cx)
2034 })
2035 }
2036
2037 pub fn open_buffer_by_id(
2038 &mut self,
2039 id: BufferId,
2040 cx: &mut Context<Self>,
2041 ) -> Task<Result<Entity<Buffer>>> {
2042 if let Some(buffer) = self.buffer_for_id(id, cx) {
2043 Task::ready(Ok(buffer))
2044 } else if self.is_local() || self.is_via_ssh() {
2045 Task::ready(Err(anyhow!("buffer {} does not exist", id)))
2046 } else if let Some(project_id) = self.remote_id() {
2047 let request = self.client.request(proto::OpenBufferById {
2048 project_id,
2049 id: id.into(),
2050 });
2051 cx.spawn(move |project, mut cx| async move {
2052 let buffer_id = BufferId::new(request.await?.buffer_id)?;
2053 project
2054 .update(&mut cx, |project, cx| {
2055 project.buffer_store.update(cx, |buffer_store, cx| {
2056 buffer_store.wait_for_remote_buffer(buffer_id, cx)
2057 })
2058 })?
2059 .await
2060 })
2061 } else {
2062 Task::ready(Err(anyhow!("cannot open buffer while disconnected")))
2063 }
2064 }
2065
2066 pub fn save_buffers(
2067 &self,
2068 buffers: HashSet<Entity<Buffer>>,
2069 cx: &mut Context<Self>,
2070 ) -> Task<Result<()>> {
2071 cx.spawn(move |this, mut cx| async move {
2072 let save_tasks = buffers.into_iter().filter_map(|buffer| {
2073 this.update(&mut cx, |this, cx| this.save_buffer(buffer, cx))
2074 .ok()
2075 });
2076 try_join_all(save_tasks).await?;
2077 Ok(())
2078 })
2079 }
2080
2081 pub fn save_buffer(&self, buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Task<Result<()>> {
2082 self.buffer_store
2083 .update(cx, |buffer_store, cx| buffer_store.save_buffer(buffer, cx))
2084 }
2085
2086 pub fn save_buffer_as(
2087 &mut self,
2088 buffer: Entity<Buffer>,
2089 path: ProjectPath,
2090 cx: &mut Context<Self>,
2091 ) -> Task<Result<()>> {
2092 self.buffer_store.update(cx, |buffer_store, cx| {
2093 buffer_store.save_buffer_as(buffer.clone(), path, cx)
2094 })
2095 }
2096
2097 pub fn get_open_buffer(&self, path: &ProjectPath, cx: &App) -> Option<Entity<Buffer>> {
2098 self.buffer_store.read(cx).get_by_path(path, cx)
2099 }
2100
2101 fn register_buffer(&mut self, buffer: &Entity<Buffer>, cx: &mut Context<Self>) -> Result<()> {
2102 {
2103 let mut remotely_created_models = self.remotely_created_models.lock();
2104 if remotely_created_models.retain_count > 0 {
2105 remotely_created_models.buffers.push(buffer.clone())
2106 }
2107 }
2108
2109 self.request_buffer_diff_recalculation(buffer, cx);
2110
2111 cx.subscribe(buffer, |this, buffer, event, cx| {
2112 this.on_buffer_event(buffer, event, cx);
2113 })
2114 .detach();
2115
2116 Ok(())
2117 }
2118
2119 pub fn open_image(
2120 &mut self,
2121 path: impl Into<ProjectPath>,
2122 cx: &mut Context<Self>,
2123 ) -> Task<Result<Entity<ImageItem>>> {
2124 if self.is_disconnected(cx) {
2125 return Task::ready(Err(anyhow!(ErrorCode::Disconnected)));
2126 }
2127
2128 let open_image_task = self.image_store.update(cx, |image_store, cx| {
2129 image_store.open_image(path.into(), cx)
2130 });
2131
2132 let weak_project = cx.entity().downgrade();
2133 cx.spawn(move |_, mut cx| async move {
2134 let image_item = open_image_task.await?;
2135 let project = weak_project
2136 .upgrade()
2137 .ok_or_else(|| anyhow!("Project dropped"))?;
2138
2139 let metadata =
2140 ImageItem::load_image_metadata(image_item.clone(), project, &mut cx).await?;
2141 image_item.update(&mut cx, |image_item, cx| {
2142 image_item.image_metadata = Some(metadata);
2143 cx.emit(ImageItemEvent::MetadataUpdated);
2144 })?;
2145
2146 Ok(image_item)
2147 })
2148 }
2149
2150 async fn send_buffer_ordered_messages(
2151 this: WeakEntity<Self>,
2152 rx: UnboundedReceiver<BufferOrderedMessage>,
2153 mut cx: AsyncApp,
2154 ) -> Result<()> {
2155 const MAX_BATCH_SIZE: usize = 128;
2156
2157 let mut operations_by_buffer_id = HashMap::default();
2158 async fn flush_operations(
2159 this: &WeakEntity<Project>,
2160 operations_by_buffer_id: &mut HashMap<BufferId, Vec<proto::Operation>>,
2161 needs_resync_with_host: &mut bool,
2162 is_local: bool,
2163 cx: &mut AsyncApp,
2164 ) -> Result<()> {
2165 for (buffer_id, operations) in operations_by_buffer_id.drain() {
2166 let request = this.update(cx, |this, _| {
2167 let project_id = this.remote_id()?;
2168 Some(this.client.request(proto::UpdateBuffer {
2169 buffer_id: buffer_id.into(),
2170 project_id,
2171 operations,
2172 }))
2173 })?;
2174 if let Some(request) = request {
2175 if request.await.is_err() && !is_local {
2176 *needs_resync_with_host = true;
2177 break;
2178 }
2179 }
2180 }
2181 Ok(())
2182 }
2183
2184 let mut needs_resync_with_host = false;
2185 let mut changes = rx.ready_chunks(MAX_BATCH_SIZE);
2186
2187 while let Some(changes) = changes.next().await {
2188 let is_local = this.update(&mut cx, |this, _| this.is_local())?;
2189
2190 for change in changes {
2191 match change {
2192 BufferOrderedMessage::Operation {
2193 buffer_id,
2194 operation,
2195 } => {
2196 if needs_resync_with_host {
2197 continue;
2198 }
2199
2200 operations_by_buffer_id
2201 .entry(buffer_id)
2202 .or_insert(Vec::new())
2203 .push(operation);
2204 }
2205
2206 BufferOrderedMessage::Resync => {
2207 operations_by_buffer_id.clear();
2208 if this
2209 .update(&mut cx, |this, cx| this.synchronize_remote_buffers(cx))?
2210 .await
2211 .is_ok()
2212 {
2213 needs_resync_with_host = false;
2214 }
2215 }
2216
2217 BufferOrderedMessage::LanguageServerUpdate {
2218 language_server_id,
2219 message,
2220 } => {
2221 flush_operations(
2222 &this,
2223 &mut operations_by_buffer_id,
2224 &mut needs_resync_with_host,
2225 is_local,
2226 &mut cx,
2227 )
2228 .await?;
2229
2230 this.update(&mut cx, |this, _| {
2231 if let Some(project_id) = this.remote_id() {
2232 this.client
2233 .send(proto::UpdateLanguageServer {
2234 project_id,
2235 language_server_id: language_server_id.0 as u64,
2236 variant: Some(message),
2237 })
2238 .log_err();
2239 }
2240 })?;
2241 }
2242 }
2243 }
2244
2245 flush_operations(
2246 &this,
2247 &mut operations_by_buffer_id,
2248 &mut needs_resync_with_host,
2249 is_local,
2250 &mut cx,
2251 )
2252 .await?;
2253 }
2254
2255 Ok(())
2256 }
2257
2258 fn on_buffer_store_event(
2259 &mut self,
2260 _: Entity<BufferStore>,
2261 event: &BufferStoreEvent,
2262 cx: &mut Context<Self>,
2263 ) {
2264 match event {
2265 BufferStoreEvent::BufferAdded(buffer) => {
2266 self.register_buffer(buffer, cx).log_err();
2267 }
2268 BufferStoreEvent::BufferChangedFilePath { .. } => {}
2269 BufferStoreEvent::BufferDropped(buffer_id) => {
2270 if let Some(ref ssh_client) = self.ssh_client {
2271 ssh_client
2272 .read(cx)
2273 .proto_client()
2274 .send(proto::CloseBuffer {
2275 project_id: 0,
2276 buffer_id: buffer_id.to_proto(),
2277 })
2278 .log_err();
2279 }
2280 }
2281 }
2282 }
2283
2284 fn on_image_store_event(
2285 &mut self,
2286 _: Entity<ImageStore>,
2287 event: &ImageStoreEvent,
2288 cx: &mut Context<Self>,
2289 ) {
2290 match event {
2291 ImageStoreEvent::ImageAdded(image) => {
2292 cx.subscribe(image, |this, image, event, cx| {
2293 this.on_image_event(image, event, cx);
2294 })
2295 .detach();
2296 }
2297 }
2298 }
2299
2300 fn on_lsp_store_event(
2301 &mut self,
2302 _: Entity<LspStore>,
2303 event: &LspStoreEvent,
2304 cx: &mut Context<Self>,
2305 ) {
2306 match event {
2307 LspStoreEvent::DiagnosticsUpdated {
2308 language_server_id,
2309 path,
2310 } => cx.emit(Event::DiagnosticsUpdated {
2311 path: path.clone(),
2312 language_server_id: *language_server_id,
2313 }),
2314 LspStoreEvent::LanguageServerAdded(language_server_id, name, worktree_id) => cx.emit(
2315 Event::LanguageServerAdded(*language_server_id, name.clone(), *worktree_id),
2316 ),
2317 LspStoreEvent::LanguageServerRemoved(language_server_id) => {
2318 cx.emit(Event::LanguageServerRemoved(*language_server_id))
2319 }
2320 LspStoreEvent::LanguageServerLog(server_id, log_type, string) => cx.emit(
2321 Event::LanguageServerLog(*server_id, log_type.clone(), string.clone()),
2322 ),
2323 LspStoreEvent::LanguageDetected {
2324 buffer,
2325 new_language,
2326 } => {
2327 let Some(_) = new_language else {
2328 cx.emit(Event::LanguageNotFound(buffer.clone()));
2329 return;
2330 };
2331 }
2332 LspStoreEvent::RefreshInlayHints => cx.emit(Event::RefreshInlayHints),
2333 LspStoreEvent::LanguageServerPrompt(prompt) => {
2334 cx.emit(Event::LanguageServerPrompt(prompt.clone()))
2335 }
2336 LspStoreEvent::DiskBasedDiagnosticsStarted { language_server_id } => {
2337 cx.emit(Event::DiskBasedDiagnosticsStarted {
2338 language_server_id: *language_server_id,
2339 });
2340 }
2341 LspStoreEvent::DiskBasedDiagnosticsFinished { language_server_id } => {
2342 cx.emit(Event::DiskBasedDiagnosticsFinished {
2343 language_server_id: *language_server_id,
2344 });
2345 }
2346 LspStoreEvent::LanguageServerUpdate {
2347 language_server_id,
2348 message,
2349 } => {
2350 if self.is_local() {
2351 self.enqueue_buffer_ordered_message(
2352 BufferOrderedMessage::LanguageServerUpdate {
2353 language_server_id: *language_server_id,
2354 message: message.clone(),
2355 },
2356 )
2357 .ok();
2358 }
2359 }
2360 LspStoreEvent::Notification(message) => cx.emit(Event::Toast {
2361 notification_id: "lsp".into(),
2362 message: message.clone(),
2363 }),
2364 LspStoreEvent::SnippetEdit {
2365 buffer_id,
2366 edits,
2367 most_recent_edit,
2368 } => {
2369 if most_recent_edit.replica_id == self.replica_id() {
2370 cx.emit(Event::SnippetEdit(*buffer_id, edits.clone()))
2371 }
2372 }
2373 }
2374 }
2375
2376 fn on_ssh_event(
2377 &mut self,
2378 _: Entity<SshRemoteClient>,
2379 event: &remote::SshRemoteEvent,
2380 cx: &mut Context<Self>,
2381 ) {
2382 match event {
2383 remote::SshRemoteEvent::Disconnected => {
2384 // if self.is_via_ssh() {
2385 // self.collaborators.clear();
2386 self.worktree_store.update(cx, |store, cx| {
2387 store.disconnected_from_host(cx);
2388 });
2389 self.buffer_store.update(cx, |buffer_store, cx| {
2390 buffer_store.disconnected_from_host(cx)
2391 });
2392 self.lsp_store.update(cx, |lsp_store, _cx| {
2393 lsp_store.disconnected_from_ssh_remote()
2394 });
2395 cx.emit(Event::DisconnectedFromSshRemote);
2396 }
2397 }
2398 }
2399
2400 fn on_settings_observer_event(
2401 &mut self,
2402 _: Entity<SettingsObserver>,
2403 event: &SettingsObserverEvent,
2404 cx: &mut Context<Self>,
2405 ) {
2406 match event {
2407 SettingsObserverEvent::LocalSettingsUpdated(result) => match result {
2408 Err(InvalidSettingsError::LocalSettings { message, path }) => {
2409 let message =
2410 format!("Failed to set local settings in {:?}:\n{}", path, message);
2411 cx.emit(Event::Toast {
2412 notification_id: "local-settings".into(),
2413 message,
2414 });
2415 }
2416 Ok(_) => cx.emit(Event::HideToast {
2417 notification_id: "local-settings".into(),
2418 }),
2419 Err(_) => {}
2420 },
2421 }
2422 }
2423
2424 fn on_worktree_store_event(
2425 &mut self,
2426 _: Entity<WorktreeStore>,
2427 event: &WorktreeStoreEvent,
2428 cx: &mut Context<Self>,
2429 ) {
2430 match event {
2431 WorktreeStoreEvent::WorktreeAdded(worktree) => {
2432 self.on_worktree_added(worktree, cx);
2433 cx.emit(Event::WorktreeAdded(worktree.read(cx).id()));
2434 }
2435 WorktreeStoreEvent::WorktreeRemoved(_, id) => {
2436 cx.emit(Event::WorktreeRemoved(*id));
2437 }
2438 WorktreeStoreEvent::WorktreeReleased(_, id) => {
2439 self.on_worktree_released(*id, cx);
2440 }
2441 WorktreeStoreEvent::WorktreeOrderChanged => cx.emit(Event::WorktreeOrderChanged),
2442 WorktreeStoreEvent::WorktreeUpdateSent(_) => {}
2443 WorktreeStoreEvent::WorktreeUpdatedEntries(worktree_id, changes) => {
2444 self.client()
2445 .telemetry()
2446 .report_discovered_project_events(*worktree_id, changes);
2447 cx.emit(Event::WorktreeUpdatedEntries(*worktree_id, changes.clone()))
2448 }
2449 WorktreeStoreEvent::WorktreeUpdatedGitRepositories(worktree_id) => {
2450 cx.emit(Event::WorktreeUpdatedGitRepositories(*worktree_id))
2451 }
2452 WorktreeStoreEvent::WorktreeDeletedEntry(worktree_id, id) => {
2453 cx.emit(Event::DeletedEntry(*worktree_id, *id))
2454 }
2455 }
2456 }
2457
2458 fn on_worktree_added(&mut self, worktree: &Entity<Worktree>, cx: &mut Context<Self>) {
2459 {
2460 let mut remotely_created_models = self.remotely_created_models.lock();
2461 if remotely_created_models.retain_count > 0 {
2462 remotely_created_models.worktrees.push(worktree.clone())
2463 }
2464 }
2465 cx.observe(worktree, |_, _, cx| cx.notify()).detach();
2466 cx.notify();
2467 }
2468
2469 fn on_worktree_released(&mut self, id_to_remove: WorktreeId, cx: &mut Context<Self>) {
2470 if let Some(ssh) = &self.ssh_client {
2471 ssh.read(cx)
2472 .proto_client()
2473 .send(proto::RemoveWorktree {
2474 worktree_id: id_to_remove.to_proto(),
2475 })
2476 .log_err();
2477 }
2478
2479 cx.notify();
2480 }
2481
2482 fn on_buffer_event(
2483 &mut self,
2484 buffer: Entity<Buffer>,
2485 event: &BufferEvent,
2486 cx: &mut Context<Self>,
2487 ) -> Option<()> {
2488 if matches!(event, BufferEvent::Edited { .. } | BufferEvent::Reloaded) {
2489 self.request_buffer_diff_recalculation(&buffer, cx);
2490 }
2491
2492 let buffer_id = buffer.read(cx).remote_id();
2493 match event {
2494 BufferEvent::ReloadNeeded => {
2495 if !self.is_via_collab() {
2496 self.reload_buffers([buffer.clone()].into_iter().collect(), true, cx)
2497 .detach_and_log_err(cx);
2498 }
2499 }
2500 BufferEvent::Operation {
2501 operation,
2502 is_local: true,
2503 } => {
2504 let operation = language::proto::serialize_operation(operation);
2505
2506 if let Some(ssh) = &self.ssh_client {
2507 ssh.read(cx)
2508 .proto_client()
2509 .send(proto::UpdateBuffer {
2510 project_id: 0,
2511 buffer_id: buffer_id.to_proto(),
2512 operations: vec![operation.clone()],
2513 })
2514 .ok();
2515 }
2516
2517 self.enqueue_buffer_ordered_message(BufferOrderedMessage::Operation {
2518 buffer_id,
2519 operation,
2520 })
2521 .ok();
2522 }
2523
2524 _ => {}
2525 }
2526
2527 None
2528 }
2529
2530 fn on_image_event(
2531 &mut self,
2532 image: Entity<ImageItem>,
2533 event: &ImageItemEvent,
2534 cx: &mut Context<Self>,
2535 ) -> Option<()> {
2536 match event {
2537 ImageItemEvent::ReloadNeeded => {
2538 if !self.is_via_collab() {
2539 self.reload_images([image.clone()].into_iter().collect(), cx)
2540 .detach_and_log_err(cx);
2541 }
2542 }
2543 _ => {}
2544 }
2545
2546 None
2547 }
2548
2549 fn request_buffer_diff_recalculation(
2550 &mut self,
2551 buffer: &Entity<Buffer>,
2552 cx: &mut Context<Self>,
2553 ) {
2554 self.buffers_needing_diff.insert(buffer.downgrade());
2555 let first_insertion = self.buffers_needing_diff.len() == 1;
2556
2557 let settings = ProjectSettings::get_global(cx);
2558 let delay = if let Some(delay) = settings.git.gutter_debounce {
2559 delay
2560 } else {
2561 if first_insertion {
2562 let this = cx.weak_entity();
2563 cx.defer(move |cx| {
2564 if let Some(this) = this.upgrade() {
2565 this.update(cx, |this, cx| {
2566 this.recalculate_buffer_diffs(cx).detach();
2567 });
2568 }
2569 });
2570 }
2571 return;
2572 };
2573
2574 const MIN_DELAY: u64 = 50;
2575 let delay = delay.max(MIN_DELAY);
2576 let duration = Duration::from_millis(delay);
2577
2578 self.git_diff_debouncer
2579 .fire_new(duration, cx, move |this, cx| {
2580 this.recalculate_buffer_diffs(cx)
2581 });
2582 }
2583
2584 fn recalculate_buffer_diffs(&mut self, cx: &mut Context<Self>) -> Task<()> {
2585 cx.spawn(move |this, mut cx| async move {
2586 loop {
2587 let task = this
2588 .update(&mut cx, |this, cx| {
2589 let buffers = this
2590 .buffers_needing_diff
2591 .drain()
2592 .filter_map(|buffer| buffer.upgrade())
2593 .collect::<Vec<_>>();
2594 if buffers.is_empty() {
2595 None
2596 } else {
2597 Some(this.buffer_store.update(cx, |buffer_store, cx| {
2598 buffer_store.recalculate_buffer_diffs(buffers, cx)
2599 }))
2600 }
2601 })
2602 .ok()
2603 .flatten();
2604
2605 if let Some(task) = task {
2606 task.await;
2607 } else {
2608 break;
2609 }
2610 }
2611 })
2612 }
2613
2614 pub fn set_language_for_buffer(
2615 &mut self,
2616 buffer: &Entity<Buffer>,
2617 new_language: Arc<Language>,
2618 cx: &mut Context<Self>,
2619 ) {
2620 self.lsp_store.update(cx, |lsp_store, cx| {
2621 lsp_store.set_language_for_buffer(buffer, new_language, cx)
2622 })
2623 }
2624
2625 pub fn restart_language_servers_for_buffers(
2626 &mut self,
2627 buffers: Vec<Entity<Buffer>>,
2628 cx: &mut Context<Self>,
2629 ) {
2630 self.lsp_store.update(cx, |lsp_store, cx| {
2631 lsp_store.restart_language_servers_for_buffers(buffers, cx)
2632 })
2633 }
2634
2635 pub fn cancel_language_server_work_for_buffers(
2636 &mut self,
2637 buffers: impl IntoIterator<Item = Entity<Buffer>>,
2638 cx: &mut Context<Self>,
2639 ) {
2640 self.lsp_store.update(cx, |lsp_store, cx| {
2641 lsp_store.cancel_language_server_work_for_buffers(buffers, cx)
2642 })
2643 }
2644
2645 pub fn cancel_language_server_work(
2646 &mut self,
2647 server_id: LanguageServerId,
2648 token_to_cancel: Option<String>,
2649 cx: &mut Context<Self>,
2650 ) {
2651 self.lsp_store.update(cx, |lsp_store, cx| {
2652 lsp_store.cancel_language_server_work(server_id, token_to_cancel, cx)
2653 })
2654 }
2655
2656 fn enqueue_buffer_ordered_message(&mut self, message: BufferOrderedMessage) -> Result<()> {
2657 self.buffer_ordered_messages_tx
2658 .unbounded_send(message)
2659 .map_err(|e| anyhow!(e))
2660 }
2661
2662 pub fn available_toolchains(
2663 &self,
2664 worktree_id: WorktreeId,
2665 language_name: LanguageName,
2666 cx: &App,
2667 ) -> Task<Option<ToolchainList>> {
2668 if let Some(toolchain_store) = self.toolchain_store.clone() {
2669 cx.spawn(|cx| async move {
2670 cx.update(|cx| {
2671 toolchain_store
2672 .read(cx)
2673 .list_toolchains(worktree_id, language_name, cx)
2674 })
2675 .ok()?
2676 .await
2677 })
2678 } else {
2679 Task::ready(None)
2680 }
2681 }
2682
2683 pub async fn toolchain_term(
2684 languages: Arc<LanguageRegistry>,
2685 language_name: LanguageName,
2686 ) -> Option<SharedString> {
2687 languages
2688 .language_for_name(language_name.as_ref())
2689 .await
2690 .ok()?
2691 .toolchain_lister()
2692 .map(|lister| lister.term())
2693 }
2694
2695 pub fn activate_toolchain(
2696 &self,
2697 worktree_id: WorktreeId,
2698 toolchain: Toolchain,
2699 cx: &mut App,
2700 ) -> Task<Option<()>> {
2701 let Some(toolchain_store) = self.toolchain_store.clone() else {
2702 return Task::ready(None);
2703 };
2704 toolchain_store.update(cx, |this, cx| {
2705 this.activate_toolchain(worktree_id, toolchain, cx)
2706 })
2707 }
2708 pub fn active_toolchain(
2709 &self,
2710 worktree_id: WorktreeId,
2711 language_name: LanguageName,
2712 cx: &App,
2713 ) -> Task<Option<Toolchain>> {
2714 let Some(toolchain_store) = self.toolchain_store.clone() else {
2715 return Task::ready(None);
2716 };
2717 toolchain_store
2718 .read(cx)
2719 .active_toolchain(worktree_id, language_name, cx)
2720 }
2721 pub fn language_server_statuses<'a>(
2722 &'a self,
2723 cx: &'a App,
2724 ) -> impl DoubleEndedIterator<Item = (LanguageServerId, &'a LanguageServerStatus)> {
2725 self.lsp_store.read(cx).language_server_statuses()
2726 }
2727
2728 pub fn last_formatting_failure<'a>(&self, cx: &'a App) -> Option<&'a str> {
2729 self.lsp_store.read(cx).last_formatting_failure()
2730 }
2731
2732 pub fn reset_last_formatting_failure(&self, cx: &mut App) {
2733 self.lsp_store
2734 .update(cx, |store, _| store.reset_last_formatting_failure());
2735 }
2736
2737 pub fn reload_buffers(
2738 &self,
2739 buffers: HashSet<Entity<Buffer>>,
2740 push_to_history: bool,
2741 cx: &mut Context<Self>,
2742 ) -> Task<Result<ProjectTransaction>> {
2743 self.buffer_store.update(cx, |buffer_store, cx| {
2744 buffer_store.reload_buffers(buffers, push_to_history, cx)
2745 })
2746 }
2747
2748 pub fn reload_images(
2749 &self,
2750 images: HashSet<Entity<ImageItem>>,
2751 cx: &mut Context<Self>,
2752 ) -> Task<Result<()>> {
2753 self.image_store
2754 .update(cx, |image_store, cx| image_store.reload_images(images, cx))
2755 }
2756
2757 pub fn format(
2758 &mut self,
2759 buffers: HashSet<Entity<Buffer>>,
2760 target: LspFormatTarget,
2761 push_to_history: bool,
2762 trigger: lsp_store::FormatTrigger,
2763 cx: &mut Context<Project>,
2764 ) -> Task<anyhow::Result<ProjectTransaction>> {
2765 self.lsp_store.update(cx, |lsp_store, cx| {
2766 lsp_store.format(buffers, target, push_to_history, trigger, cx)
2767 })
2768 }
2769
2770 #[inline(never)]
2771 fn definition_impl(
2772 &mut self,
2773 buffer: &Entity<Buffer>,
2774 position: PointUtf16,
2775 cx: &mut Context<Self>,
2776 ) -> Task<Result<Vec<LocationLink>>> {
2777 self.request_lsp(
2778 buffer.clone(),
2779 LanguageServerToQuery::Primary,
2780 GetDefinition { position },
2781 cx,
2782 )
2783 }
2784 pub fn definition<T: ToPointUtf16>(
2785 &mut self,
2786 buffer: &Entity<Buffer>,
2787 position: T,
2788 cx: &mut Context<Self>,
2789 ) -> Task<Result<Vec<LocationLink>>> {
2790 let position = position.to_point_utf16(buffer.read(cx));
2791 self.definition_impl(buffer, position, cx)
2792 }
2793
2794 fn declaration_impl(
2795 &mut self,
2796 buffer: &Entity<Buffer>,
2797 position: PointUtf16,
2798 cx: &mut Context<Self>,
2799 ) -> Task<Result<Vec<LocationLink>>> {
2800 self.request_lsp(
2801 buffer.clone(),
2802 LanguageServerToQuery::Primary,
2803 GetDeclaration { position },
2804 cx,
2805 )
2806 }
2807
2808 pub fn declaration<T: ToPointUtf16>(
2809 &mut self,
2810 buffer: &Entity<Buffer>,
2811 position: T,
2812 cx: &mut Context<Self>,
2813 ) -> Task<Result<Vec<LocationLink>>> {
2814 let position = position.to_point_utf16(buffer.read(cx));
2815 self.declaration_impl(buffer, position, cx)
2816 }
2817
2818 fn type_definition_impl(
2819 &mut self,
2820 buffer: &Entity<Buffer>,
2821 position: PointUtf16,
2822 cx: &mut Context<Self>,
2823 ) -> Task<Result<Vec<LocationLink>>> {
2824 self.request_lsp(
2825 buffer.clone(),
2826 LanguageServerToQuery::Primary,
2827 GetTypeDefinition { position },
2828 cx,
2829 )
2830 }
2831
2832 pub fn type_definition<T: ToPointUtf16>(
2833 &mut self,
2834 buffer: &Entity<Buffer>,
2835 position: T,
2836 cx: &mut Context<Self>,
2837 ) -> Task<Result<Vec<LocationLink>>> {
2838 let position = position.to_point_utf16(buffer.read(cx));
2839 self.type_definition_impl(buffer, position, cx)
2840 }
2841
2842 pub fn implementation<T: ToPointUtf16>(
2843 &mut self,
2844 buffer: &Entity<Buffer>,
2845 position: T,
2846 cx: &mut Context<Self>,
2847 ) -> Task<Result<Vec<LocationLink>>> {
2848 let position = position.to_point_utf16(buffer.read(cx));
2849 self.request_lsp(
2850 buffer.clone(),
2851 LanguageServerToQuery::Primary,
2852 GetImplementation { position },
2853 cx,
2854 )
2855 }
2856
2857 pub fn references<T: ToPointUtf16>(
2858 &mut self,
2859 buffer: &Entity<Buffer>,
2860 position: T,
2861 cx: &mut Context<Self>,
2862 ) -> Task<Result<Vec<Location>>> {
2863 let position = position.to_point_utf16(buffer.read(cx));
2864 self.request_lsp(
2865 buffer.clone(),
2866 LanguageServerToQuery::Primary,
2867 GetReferences { position },
2868 cx,
2869 )
2870 }
2871
2872 fn document_highlights_impl(
2873 &mut self,
2874 buffer: &Entity<Buffer>,
2875 position: PointUtf16,
2876 cx: &mut Context<Self>,
2877 ) -> Task<Result<Vec<DocumentHighlight>>> {
2878 self.request_lsp(
2879 buffer.clone(),
2880 LanguageServerToQuery::Primary,
2881 GetDocumentHighlights { position },
2882 cx,
2883 )
2884 }
2885
2886 pub fn document_highlights<T: ToPointUtf16>(
2887 &mut self,
2888 buffer: &Entity<Buffer>,
2889 position: T,
2890 cx: &mut Context<Self>,
2891 ) -> Task<Result<Vec<DocumentHighlight>>> {
2892 let position = position.to_point_utf16(buffer.read(cx));
2893 self.document_highlights_impl(buffer, position, cx)
2894 }
2895
2896 pub fn symbols(&self, query: &str, cx: &mut Context<Self>) -> Task<Result<Vec<Symbol>>> {
2897 self.lsp_store
2898 .update(cx, |lsp_store, cx| lsp_store.symbols(query, cx))
2899 }
2900
2901 pub fn open_buffer_for_symbol(
2902 &mut self,
2903 symbol: &Symbol,
2904 cx: &mut Context<Self>,
2905 ) -> Task<Result<Entity<Buffer>>> {
2906 self.lsp_store.update(cx, |lsp_store, cx| {
2907 lsp_store.open_buffer_for_symbol(symbol, cx)
2908 })
2909 }
2910
2911 pub fn open_server_settings(&mut self, cx: &mut Context<Self>) -> Task<Result<Entity<Buffer>>> {
2912 let guard = self.retain_remotely_created_models(cx);
2913 let Some(ssh_client) = self.ssh_client.as_ref() else {
2914 return Task::ready(Err(anyhow!("not an ssh project")));
2915 };
2916
2917 let proto_client = ssh_client.read(cx).proto_client();
2918
2919 cx.spawn(|project, mut cx| async move {
2920 let buffer = proto_client
2921 .request(proto::OpenServerSettings {
2922 project_id: SSH_PROJECT_ID,
2923 })
2924 .await?;
2925
2926 let buffer = project
2927 .update(&mut cx, |project, cx| {
2928 project.buffer_store.update(cx, |buffer_store, cx| {
2929 anyhow::Ok(
2930 buffer_store
2931 .wait_for_remote_buffer(BufferId::new(buffer.buffer_id)?, cx),
2932 )
2933 })
2934 })??
2935 .await;
2936
2937 drop(guard);
2938 buffer
2939 })
2940 }
2941
2942 pub fn open_local_buffer_via_lsp(
2943 &mut self,
2944 abs_path: lsp::Url,
2945 language_server_id: LanguageServerId,
2946 language_server_name: LanguageServerName,
2947 cx: &mut Context<Self>,
2948 ) -> Task<Result<Entity<Buffer>>> {
2949 self.lsp_store.update(cx, |lsp_store, cx| {
2950 lsp_store.open_local_buffer_via_lsp(
2951 abs_path,
2952 language_server_id,
2953 language_server_name,
2954 cx,
2955 )
2956 })
2957 }
2958
2959 pub fn signature_help<T: ToPointUtf16>(
2960 &self,
2961 buffer: &Entity<Buffer>,
2962 position: T,
2963 cx: &mut Context<Self>,
2964 ) -> Task<Vec<SignatureHelp>> {
2965 self.lsp_store.update(cx, |lsp_store, cx| {
2966 lsp_store.signature_help(buffer, position, cx)
2967 })
2968 }
2969
2970 pub fn hover<T: ToPointUtf16>(
2971 &self,
2972 buffer: &Entity<Buffer>,
2973 position: T,
2974 cx: &mut Context<Self>,
2975 ) -> Task<Vec<Hover>> {
2976 let position = position.to_point_utf16(buffer.read(cx));
2977 self.lsp_store
2978 .update(cx, |lsp_store, cx| lsp_store.hover(buffer, position, cx))
2979 }
2980
2981 pub fn linked_edit(
2982 &self,
2983 buffer: &Entity<Buffer>,
2984 position: Anchor,
2985 cx: &mut Context<Self>,
2986 ) -> Task<Result<Vec<Range<Anchor>>>> {
2987 self.lsp_store.update(cx, |lsp_store, cx| {
2988 lsp_store.linked_edit(buffer, position, cx)
2989 })
2990 }
2991
2992 pub fn completions<T: ToOffset + ToPointUtf16>(
2993 &self,
2994 buffer: &Entity<Buffer>,
2995 position: T,
2996 context: CompletionContext,
2997 cx: &mut Context<Self>,
2998 ) -> Task<Result<Vec<Completion>>> {
2999 let position = position.to_point_utf16(buffer.read(cx));
3000 self.lsp_store.update(cx, |lsp_store, cx| {
3001 lsp_store.completions(buffer, position, context, cx)
3002 })
3003 }
3004
3005 pub fn code_actions<T: Clone + ToOffset>(
3006 &mut self,
3007 buffer_handle: &Entity<Buffer>,
3008 range: Range<T>,
3009 kinds: Option<Vec<CodeActionKind>>,
3010 cx: &mut Context<Self>,
3011 ) -> Task<Result<Vec<CodeAction>>> {
3012 let buffer = buffer_handle.read(cx);
3013 let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
3014 self.lsp_store.update(cx, |lsp_store, cx| {
3015 lsp_store.code_actions(buffer_handle, range, kinds, cx)
3016 })
3017 }
3018
3019 pub fn apply_code_action(
3020 &self,
3021 buffer_handle: Entity<Buffer>,
3022 action: CodeAction,
3023 push_to_history: bool,
3024 cx: &mut Context<Self>,
3025 ) -> Task<Result<ProjectTransaction>> {
3026 self.lsp_store.update(cx, |lsp_store, cx| {
3027 lsp_store.apply_code_action(buffer_handle, action, push_to_history, cx)
3028 })
3029 }
3030
3031 fn prepare_rename_impl(
3032 &mut self,
3033 buffer: Entity<Buffer>,
3034 position: PointUtf16,
3035 cx: &mut Context<Self>,
3036 ) -> Task<Result<PrepareRenameResponse>> {
3037 self.request_lsp(
3038 buffer,
3039 LanguageServerToQuery::Primary,
3040 PrepareRename { position },
3041 cx,
3042 )
3043 }
3044 pub fn prepare_rename<T: ToPointUtf16>(
3045 &mut self,
3046 buffer: Entity<Buffer>,
3047 position: T,
3048 cx: &mut Context<Self>,
3049 ) -> Task<Result<PrepareRenameResponse>> {
3050 let position = position.to_point_utf16(buffer.read(cx));
3051 self.prepare_rename_impl(buffer, position, cx)
3052 }
3053
3054 pub fn perform_rename<T: ToPointUtf16>(
3055 &mut self,
3056 buffer: Entity<Buffer>,
3057 position: T,
3058 new_name: String,
3059 cx: &mut Context<Self>,
3060 ) -> Task<Result<ProjectTransaction>> {
3061 let push_to_history = true;
3062 let position = position.to_point_utf16(buffer.read(cx));
3063 self.request_lsp(
3064 buffer,
3065 LanguageServerToQuery::Primary,
3066 PerformRename {
3067 position,
3068 new_name,
3069 push_to_history,
3070 },
3071 cx,
3072 )
3073 }
3074
3075 pub fn on_type_format<T: ToPointUtf16>(
3076 &mut self,
3077 buffer: Entity<Buffer>,
3078 position: T,
3079 trigger: String,
3080 push_to_history: bool,
3081 cx: &mut Context<Self>,
3082 ) -> Task<Result<Option<Transaction>>> {
3083 self.lsp_store.update(cx, |lsp_store, cx| {
3084 lsp_store.on_type_format(buffer, position, trigger, push_to_history, cx)
3085 })
3086 }
3087
3088 pub fn inlay_hints<T: ToOffset>(
3089 &mut self,
3090 buffer_handle: Entity<Buffer>,
3091 range: Range<T>,
3092 cx: &mut Context<Self>,
3093 ) -> Task<anyhow::Result<Vec<InlayHint>>> {
3094 let buffer = buffer_handle.read(cx);
3095 let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
3096 self.lsp_store.update(cx, |lsp_store, cx| {
3097 lsp_store.inlay_hints(buffer_handle, range, cx)
3098 })
3099 }
3100
3101 pub fn resolve_inlay_hint(
3102 &self,
3103 hint: InlayHint,
3104 buffer_handle: Entity<Buffer>,
3105 server_id: LanguageServerId,
3106 cx: &mut Context<Self>,
3107 ) -> Task<anyhow::Result<InlayHint>> {
3108 self.lsp_store.update(cx, |lsp_store, cx| {
3109 lsp_store.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
3110 })
3111 }
3112
3113 pub fn search(&mut self, query: SearchQuery, cx: &mut Context<Self>) -> Receiver<SearchResult> {
3114 let (result_tx, result_rx) = smol::channel::unbounded();
3115
3116 let matching_buffers_rx = if query.is_opened_only() {
3117 self.sort_search_candidates(&query, cx)
3118 } else {
3119 self.find_search_candidate_buffers(&query, MAX_SEARCH_RESULT_FILES + 1, cx)
3120 };
3121
3122 cx.spawn(|_, cx| async move {
3123 let mut range_count = 0;
3124 let mut buffer_count = 0;
3125 let mut limit_reached = false;
3126 let query = Arc::new(query);
3127 let mut chunks = matching_buffers_rx.ready_chunks(64);
3128
3129 // Now that we know what paths match the query, we will load at most
3130 // 64 buffers at a time to avoid overwhelming the main thread. For each
3131 // opened buffer, we will spawn a background task that retrieves all the
3132 // ranges in the buffer matched by the query.
3133 let mut chunks = pin!(chunks);
3134 'outer: while let Some(matching_buffer_chunk) = chunks.next().await {
3135 let mut chunk_results = Vec::new();
3136 for buffer in matching_buffer_chunk {
3137 let buffer = buffer.clone();
3138 let query = query.clone();
3139 let snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot())?;
3140 chunk_results.push(cx.background_spawn(async move {
3141 let ranges = query
3142 .search(&snapshot, None)
3143 .await
3144 .iter()
3145 .map(|range| {
3146 snapshot.anchor_before(range.start)
3147 ..snapshot.anchor_after(range.end)
3148 })
3149 .collect::<Vec<_>>();
3150 anyhow::Ok((buffer, ranges))
3151 }));
3152 }
3153
3154 let chunk_results = futures::future::join_all(chunk_results).await;
3155 for result in chunk_results {
3156 if let Some((buffer, ranges)) = result.log_err() {
3157 range_count += ranges.len();
3158 buffer_count += 1;
3159 result_tx
3160 .send(SearchResult::Buffer { buffer, ranges })
3161 .await?;
3162 if buffer_count > MAX_SEARCH_RESULT_FILES
3163 || range_count > MAX_SEARCH_RESULT_RANGES
3164 {
3165 limit_reached = true;
3166 break 'outer;
3167 }
3168 }
3169 }
3170 }
3171
3172 if limit_reached {
3173 result_tx.send(SearchResult::LimitReached).await?;
3174 }
3175
3176 anyhow::Ok(())
3177 })
3178 .detach();
3179
3180 result_rx
3181 }
3182
3183 fn find_search_candidate_buffers(
3184 &mut self,
3185 query: &SearchQuery,
3186 limit: usize,
3187 cx: &mut Context<Project>,
3188 ) -> Receiver<Entity<Buffer>> {
3189 if self.is_local() {
3190 let fs = self.fs.clone();
3191 self.buffer_store.update(cx, |buffer_store, cx| {
3192 buffer_store.find_search_candidates(query, limit, fs, cx)
3193 })
3194 } else {
3195 self.find_search_candidates_remote(query, limit, cx)
3196 }
3197 }
3198
3199 fn sort_search_candidates(
3200 &mut self,
3201 search_query: &SearchQuery,
3202 cx: &mut Context<Project>,
3203 ) -> Receiver<Entity<Buffer>> {
3204 let worktree_store = self.worktree_store.read(cx);
3205 let mut buffers = search_query
3206 .buffers()
3207 .into_iter()
3208 .flatten()
3209 .filter(|buffer| {
3210 let b = buffer.read(cx);
3211 if let Some(file) = b.file() {
3212 if !search_query.file_matches(file.path()) {
3213 return false;
3214 }
3215 if let Some(entry) = b
3216 .entry_id(cx)
3217 .and_then(|entry_id| worktree_store.entry_for_id(entry_id, cx))
3218 {
3219 if entry.is_ignored && !search_query.include_ignored() {
3220 return false;
3221 }
3222 }
3223 }
3224 true
3225 })
3226 .collect::<Vec<_>>();
3227 let (tx, rx) = smol::channel::unbounded();
3228 buffers.sort_by(|a, b| match (a.read(cx).file(), b.read(cx).file()) {
3229 (None, None) => a.read(cx).remote_id().cmp(&b.read(cx).remote_id()),
3230 (None, Some(_)) => std::cmp::Ordering::Less,
3231 (Some(_), None) => std::cmp::Ordering::Greater,
3232 (Some(a), Some(b)) => compare_paths((a.path(), true), (b.path(), true)),
3233 });
3234 for buffer in buffers {
3235 tx.send_blocking(buffer.clone()).unwrap()
3236 }
3237
3238 rx
3239 }
3240
3241 fn find_search_candidates_remote(
3242 &mut self,
3243 query: &SearchQuery,
3244 limit: usize,
3245 cx: &mut Context<Project>,
3246 ) -> Receiver<Entity<Buffer>> {
3247 let (tx, rx) = smol::channel::unbounded();
3248
3249 let (client, remote_id): (AnyProtoClient, _) = if let Some(ssh_client) = &self.ssh_client {
3250 (ssh_client.read(cx).proto_client(), 0)
3251 } else if let Some(remote_id) = self.remote_id() {
3252 (self.client.clone().into(), remote_id)
3253 } else {
3254 return rx;
3255 };
3256
3257 let request = client.request(proto::FindSearchCandidates {
3258 project_id: remote_id,
3259 query: Some(query.to_proto()),
3260 limit: limit as _,
3261 });
3262 let guard = self.retain_remotely_created_models(cx);
3263
3264 cx.spawn(move |project, mut cx| async move {
3265 let response = request.await?;
3266 for buffer_id in response.buffer_ids {
3267 let buffer_id = BufferId::new(buffer_id)?;
3268 let buffer = project
3269 .update(&mut cx, |project, cx| {
3270 project.buffer_store.update(cx, |buffer_store, cx| {
3271 buffer_store.wait_for_remote_buffer(buffer_id, cx)
3272 })
3273 })?
3274 .await?;
3275 let _ = tx.send(buffer).await;
3276 }
3277
3278 drop(guard);
3279 anyhow::Ok(())
3280 })
3281 .detach_and_log_err(cx);
3282 rx
3283 }
3284
3285 pub fn request_lsp<R: LspCommand>(
3286 &mut self,
3287 buffer_handle: Entity<Buffer>,
3288 server: LanguageServerToQuery,
3289 request: R,
3290 cx: &mut Context<Self>,
3291 ) -> Task<Result<R::Response>>
3292 where
3293 <R::LspRequest as lsp::request::Request>::Result: Send,
3294 <R::LspRequest as lsp::request::Request>::Params: Send,
3295 {
3296 let guard = self.retain_remotely_created_models(cx);
3297 let task = self.lsp_store.update(cx, |lsp_store, cx| {
3298 lsp_store.request_lsp(buffer_handle, server, request, cx)
3299 });
3300 cx.spawn(|_, _| async move {
3301 let result = task.await;
3302 drop(guard);
3303 result
3304 })
3305 }
3306
3307 /// Move a worktree to a new position in the worktree order.
3308 ///
3309 /// The worktree will moved to the opposite side of the destination worktree.
3310 ///
3311 /// # Example
3312 ///
3313 /// Given the worktree order `[11, 22, 33]` and a call to move worktree `22` to `33`,
3314 /// worktree_order will be updated to produce the indexes `[11, 33, 22]`.
3315 ///
3316 /// Given the worktree order `[11, 22, 33]` and a call to move worktree `22` to `11`,
3317 /// worktree_order will be updated to produce the indexes `[22, 11, 33]`.
3318 ///
3319 /// # Errors
3320 ///
3321 /// An error will be returned if the worktree or destination worktree are not found.
3322 pub fn move_worktree(
3323 &mut self,
3324 source: WorktreeId,
3325 destination: WorktreeId,
3326 cx: &mut Context<'_, Self>,
3327 ) -> Result<()> {
3328 self.worktree_store.update(cx, |worktree_store, cx| {
3329 worktree_store.move_worktree(source, destination, cx)
3330 })
3331 }
3332
3333 pub fn find_or_create_worktree(
3334 &mut self,
3335 abs_path: impl AsRef<Path>,
3336 visible: bool,
3337 cx: &mut Context<Self>,
3338 ) -> Task<Result<(Entity<Worktree>, PathBuf)>> {
3339 self.worktree_store.update(cx, |worktree_store, cx| {
3340 worktree_store.find_or_create_worktree(abs_path, visible, cx)
3341 })
3342 }
3343
3344 pub fn find_worktree(&self, abs_path: &Path, cx: &App) -> Option<(Entity<Worktree>, PathBuf)> {
3345 self.worktree_store.read_with(cx, |worktree_store, cx| {
3346 worktree_store.find_worktree(abs_path, cx)
3347 })
3348 }
3349
3350 pub fn is_shared(&self) -> bool {
3351 match &self.client_state {
3352 ProjectClientState::Shared { .. } => true,
3353 ProjectClientState::Local => false,
3354 ProjectClientState::Remote { .. } => true,
3355 }
3356 }
3357
3358 /// Returns the resolved version of `path`, that was found in `buffer`, if it exists.
3359 pub fn resolve_path_in_buffer(
3360 &self,
3361 path: &str,
3362 buffer: &Entity<Buffer>,
3363 cx: &mut Context<Self>,
3364 ) -> Task<Option<ResolvedPath>> {
3365 let path_buf = PathBuf::from(path);
3366 if path_buf.is_absolute() || path.starts_with("~") {
3367 self.resolve_abs_path(path, cx)
3368 } else {
3369 self.resolve_path_in_worktrees(path_buf, buffer, cx)
3370 }
3371 }
3372
3373 pub fn resolve_abs_file_path(
3374 &self,
3375 path: &str,
3376 cx: &mut Context<Self>,
3377 ) -> Task<Option<ResolvedPath>> {
3378 let resolve_task = self.resolve_abs_path(path, cx);
3379 cx.background_spawn(async move {
3380 let resolved_path = resolve_task.await;
3381 resolved_path.filter(|path| path.is_file())
3382 })
3383 }
3384
3385 pub fn resolve_abs_path(
3386 &self,
3387 path: &str,
3388 cx: &mut Context<Self>,
3389 ) -> Task<Option<ResolvedPath>> {
3390 if self.is_local() {
3391 let expanded = PathBuf::from(shellexpand::tilde(&path).into_owned());
3392 let fs = self.fs.clone();
3393 cx.background_spawn(async move {
3394 let path = expanded.as_path();
3395 let metadata = fs.metadata(path).await.ok().flatten();
3396
3397 metadata.map(|metadata| ResolvedPath::AbsPath {
3398 path: expanded,
3399 is_dir: metadata.is_dir,
3400 })
3401 })
3402 } else if let Some(ssh_client) = self.ssh_client.as_ref() {
3403 let request_path = Path::new(path);
3404 let request = ssh_client
3405 .read(cx)
3406 .proto_client()
3407 .request(proto::GetPathMetadata {
3408 project_id: SSH_PROJECT_ID,
3409 path: request_path.to_proto(),
3410 });
3411 cx.background_spawn(async move {
3412 let response = request.await.log_err()?;
3413 if response.exists {
3414 Some(ResolvedPath::AbsPath {
3415 path: PathBuf::from_proto(response.path),
3416 is_dir: response.is_dir,
3417 })
3418 } else {
3419 None
3420 }
3421 })
3422 } else {
3423 return Task::ready(None);
3424 }
3425 }
3426
3427 fn resolve_path_in_worktrees(
3428 &self,
3429 path: PathBuf,
3430 buffer: &Entity<Buffer>,
3431 cx: &mut Context<Self>,
3432 ) -> Task<Option<ResolvedPath>> {
3433 let mut candidates = vec![path.clone()];
3434
3435 if let Some(file) = buffer.read(cx).file() {
3436 if let Some(dir) = file.path().parent() {
3437 let joined = dir.to_path_buf().join(path);
3438 candidates.push(joined);
3439 }
3440 }
3441
3442 let worktrees = self.worktrees(cx).collect::<Vec<_>>();
3443 cx.spawn(|_, mut cx| async move {
3444 for worktree in worktrees {
3445 for candidate in candidates.iter() {
3446 let path = worktree
3447 .update(&mut cx, |worktree, _| {
3448 let root_entry_path = &worktree.root_entry()?.path;
3449
3450 let resolved = resolve_path(root_entry_path, candidate);
3451
3452 let stripped =
3453 resolved.strip_prefix(root_entry_path).unwrap_or(&resolved);
3454
3455 worktree.entry_for_path(stripped).map(|entry| {
3456 let project_path = ProjectPath {
3457 worktree_id: worktree.id(),
3458 path: entry.path.clone(),
3459 };
3460 ResolvedPath::ProjectPath {
3461 project_path,
3462 is_dir: entry.is_dir(),
3463 }
3464 })
3465 })
3466 .ok()?;
3467
3468 if path.is_some() {
3469 return path;
3470 }
3471 }
3472 }
3473 None
3474 })
3475 }
3476
3477 pub fn list_directory(
3478 &self,
3479 query: String,
3480 cx: &mut Context<Self>,
3481 ) -> Task<Result<Vec<PathBuf>>> {
3482 if self.is_local() {
3483 DirectoryLister::Local(self.fs.clone()).list_directory(query, cx)
3484 } else if let Some(session) = self.ssh_client.as_ref() {
3485 let path_buf = PathBuf::from(query);
3486 let request = proto::ListRemoteDirectory {
3487 dev_server_id: SSH_PROJECT_ID,
3488 path: path_buf.to_proto(),
3489 };
3490
3491 let response = session.read(cx).proto_client().request(request);
3492 cx.background_spawn(async move {
3493 let response = response.await?;
3494 Ok(response.entries.into_iter().map(PathBuf::from).collect())
3495 })
3496 } else {
3497 Task::ready(Err(anyhow!("cannot list directory in remote project")))
3498 }
3499 }
3500
3501 pub fn create_worktree(
3502 &mut self,
3503 abs_path: impl AsRef<Path>,
3504 visible: bool,
3505 cx: &mut Context<Self>,
3506 ) -> Task<Result<Entity<Worktree>>> {
3507 self.worktree_store.update(cx, |worktree_store, cx| {
3508 worktree_store.create_worktree(abs_path, visible, cx)
3509 })
3510 }
3511
3512 pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut Context<Self>) {
3513 self.worktree_store.update(cx, |worktree_store, cx| {
3514 worktree_store.remove_worktree(id_to_remove, cx);
3515 });
3516 }
3517
3518 fn add_worktree(&mut self, worktree: &Entity<Worktree>, cx: &mut Context<Self>) {
3519 self.worktree_store.update(cx, |worktree_store, cx| {
3520 worktree_store.add(worktree, cx);
3521 });
3522 }
3523
3524 pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut Context<Self>) {
3525 let new_active_entry = entry.and_then(|project_path| {
3526 let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
3527 let entry = worktree.read(cx).entry_for_path(project_path.path)?;
3528 Some(entry.id)
3529 });
3530 if new_active_entry != self.active_entry {
3531 self.active_entry = new_active_entry;
3532 self.lsp_store.update(cx, |lsp_store, _| {
3533 lsp_store.set_active_entry(new_active_entry);
3534 });
3535 cx.emit(Event::ActiveEntryChanged(new_active_entry));
3536 }
3537 }
3538
3539 pub fn language_servers_running_disk_based_diagnostics<'a>(
3540 &'a self,
3541 cx: &'a App,
3542 ) -> impl Iterator<Item = LanguageServerId> + 'a {
3543 self.lsp_store
3544 .read(cx)
3545 .language_servers_running_disk_based_diagnostics()
3546 }
3547
3548 pub fn diagnostic_summary(&self, include_ignored: bool, cx: &App) -> DiagnosticSummary {
3549 self.lsp_store
3550 .read(cx)
3551 .diagnostic_summary(include_ignored, cx)
3552 }
3553
3554 pub fn diagnostic_summaries<'a>(
3555 &'a self,
3556 include_ignored: bool,
3557 cx: &'a App,
3558 ) -> impl Iterator<Item = (ProjectPath, LanguageServerId, DiagnosticSummary)> + 'a {
3559 self.lsp_store
3560 .read(cx)
3561 .diagnostic_summaries(include_ignored, cx)
3562 }
3563
3564 pub fn active_entry(&self) -> Option<ProjectEntryId> {
3565 self.active_entry
3566 }
3567
3568 pub fn entry_for_path(&self, path: &ProjectPath, cx: &App) -> Option<Entry> {
3569 self.worktree_store.read(cx).entry_for_path(path, cx)
3570 }
3571
3572 pub fn path_for_entry(&self, entry_id: ProjectEntryId, cx: &App) -> Option<ProjectPath> {
3573 let worktree = self.worktree_for_entry(entry_id, cx)?;
3574 let worktree = worktree.read(cx);
3575 let worktree_id = worktree.id();
3576 let path = worktree.entry_for_id(entry_id)?.path.clone();
3577 Some(ProjectPath { worktree_id, path })
3578 }
3579
3580 pub fn absolute_path(&self, project_path: &ProjectPath, cx: &App) -> Option<PathBuf> {
3581 self.worktree_for_id(project_path.worktree_id, cx)?
3582 .read(cx)
3583 .absolutize(&project_path.path)
3584 .ok()
3585 }
3586
3587 /// Attempts to find a `ProjectPath` corresponding to the given path. If the path
3588 /// is a *full path*, meaning it starts with the root name of a worktree, we'll locate
3589 /// it in that worktree. Otherwise, we'll attempt to find it as a relative path in
3590 /// the first visible worktree that has an entry for that relative path.
3591 ///
3592 /// We use this to resolve edit steps, when there's a chance an LLM may omit the workree
3593 /// root name from paths.
3594 ///
3595 /// # Arguments
3596 ///
3597 /// * `path` - A full path that starts with a worktree root name, or alternatively a
3598 /// relative path within a visible worktree.
3599 /// * `cx` - A reference to the `AppContext`.
3600 ///
3601 /// # Returns
3602 ///
3603 /// Returns `Some(ProjectPath)` if a matching worktree is found, otherwise `None`.
3604 pub fn find_project_path(&self, path: &Path, cx: &App) -> Option<ProjectPath> {
3605 let worktree_store = self.worktree_store.read(cx);
3606
3607 for worktree in worktree_store.visible_worktrees(cx) {
3608 let worktree_root_name = worktree.read(cx).root_name();
3609 if let Ok(relative_path) = path.strip_prefix(worktree_root_name) {
3610 return Some(ProjectPath {
3611 worktree_id: worktree.read(cx).id(),
3612 path: relative_path.into(),
3613 });
3614 }
3615 }
3616
3617 for worktree in worktree_store.visible_worktrees(cx) {
3618 let worktree = worktree.read(cx);
3619 if let Some(entry) = worktree.entry_for_path(path) {
3620 return Some(ProjectPath {
3621 worktree_id: worktree.id(),
3622 path: entry.path.clone(),
3623 });
3624 }
3625 }
3626
3627 None
3628 }
3629
3630 pub fn get_workspace_root(&self, project_path: &ProjectPath, cx: &App) -> Option<PathBuf> {
3631 Some(
3632 self.worktree_for_id(project_path.worktree_id, cx)?
3633 .read(cx)
3634 .abs_path()
3635 .to_path_buf(),
3636 )
3637 }
3638
3639 pub fn get_first_worktree_root_repo(&self, cx: &App) -> Option<Arc<dyn GitRepository>> {
3640 let worktree = self.visible_worktrees(cx).next()?.read(cx).as_local()?;
3641 let root_entry = worktree.root_git_entry()?;
3642 worktree.get_local_repo(&root_entry)?.repo().clone().into()
3643 }
3644
3645 pub fn branches(&self, project_path: ProjectPath, cx: &App) -> Task<Result<Vec<Branch>>> {
3646 self.worktree_store().read(cx).branches(project_path, cx)
3647 }
3648
3649 pub fn update_or_create_branch(
3650 &self,
3651 repository: ProjectPath,
3652 new_branch: String,
3653 cx: &App,
3654 ) -> Task<Result<()>> {
3655 self.worktree_store()
3656 .read(cx)
3657 .update_or_create_branch(repository, new_branch, cx)
3658 }
3659
3660 pub fn blame_buffer(
3661 &self,
3662 buffer: &Entity<Buffer>,
3663 version: Option<clock::Global>,
3664 cx: &App,
3665 ) -> Task<Result<Option<Blame>>> {
3666 self.buffer_store.read(cx).blame_buffer(buffer, version, cx)
3667 }
3668
3669 pub fn get_permalink_to_line(
3670 &self,
3671 buffer: &Entity<Buffer>,
3672 selection: Range<u32>,
3673 cx: &App,
3674 ) -> Task<Result<url::Url>> {
3675 self.buffer_store
3676 .read(cx)
3677 .get_permalink_to_line(buffer, selection, cx)
3678 }
3679
3680 // RPC message handlers
3681
3682 async fn handle_unshare_project(
3683 this: Entity<Self>,
3684 _: TypedEnvelope<proto::UnshareProject>,
3685 mut cx: AsyncApp,
3686 ) -> Result<()> {
3687 this.update(&mut cx, |this, cx| {
3688 if this.is_local() || this.is_via_ssh() {
3689 this.unshare(cx)?;
3690 } else {
3691 this.disconnected_from_host(cx);
3692 }
3693 Ok(())
3694 })?
3695 }
3696
3697 async fn handle_add_collaborator(
3698 this: Entity<Self>,
3699 mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
3700 mut cx: AsyncApp,
3701 ) -> Result<()> {
3702 let collaborator = envelope
3703 .payload
3704 .collaborator
3705 .take()
3706 .ok_or_else(|| anyhow!("empty collaborator"))?;
3707
3708 let collaborator = Collaborator::from_proto(collaborator)?;
3709 this.update(&mut cx, |this, cx| {
3710 this.buffer_store.update(cx, |buffer_store, _| {
3711 buffer_store.forget_shared_buffers_for(&collaborator.peer_id);
3712 });
3713 cx.emit(Event::CollaboratorJoined(collaborator.peer_id));
3714 this.collaborators
3715 .insert(collaborator.peer_id, collaborator);
3716 cx.notify();
3717 })?;
3718
3719 Ok(())
3720 }
3721
3722 async fn handle_update_project_collaborator(
3723 this: Entity<Self>,
3724 envelope: TypedEnvelope<proto::UpdateProjectCollaborator>,
3725 mut cx: AsyncApp,
3726 ) -> Result<()> {
3727 let old_peer_id = envelope
3728 .payload
3729 .old_peer_id
3730 .ok_or_else(|| anyhow!("missing old peer id"))?;
3731 let new_peer_id = envelope
3732 .payload
3733 .new_peer_id
3734 .ok_or_else(|| anyhow!("missing new peer id"))?;
3735 this.update(&mut cx, |this, cx| {
3736 let collaborator = this
3737 .collaborators
3738 .remove(&old_peer_id)
3739 .ok_or_else(|| anyhow!("received UpdateProjectCollaborator for unknown peer"))?;
3740 let is_host = collaborator.is_host;
3741 this.collaborators.insert(new_peer_id, collaborator);
3742
3743 log::info!("peer {} became {}", old_peer_id, new_peer_id,);
3744 this.buffer_store.update(cx, |buffer_store, _| {
3745 buffer_store.update_peer_id(&old_peer_id, new_peer_id)
3746 });
3747
3748 if is_host {
3749 this.buffer_store
3750 .update(cx, |buffer_store, _| buffer_store.discard_incomplete());
3751 this.enqueue_buffer_ordered_message(BufferOrderedMessage::Resync)
3752 .unwrap();
3753 cx.emit(Event::HostReshared);
3754 }
3755
3756 cx.emit(Event::CollaboratorUpdated {
3757 old_peer_id,
3758 new_peer_id,
3759 });
3760 cx.notify();
3761 Ok(())
3762 })?
3763 }
3764
3765 async fn handle_remove_collaborator(
3766 this: Entity<Self>,
3767 envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
3768 mut cx: AsyncApp,
3769 ) -> Result<()> {
3770 this.update(&mut cx, |this, cx| {
3771 let peer_id = envelope
3772 .payload
3773 .peer_id
3774 .ok_or_else(|| anyhow!("invalid peer id"))?;
3775 let replica_id = this
3776 .collaborators
3777 .remove(&peer_id)
3778 .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?
3779 .replica_id;
3780 this.buffer_store.update(cx, |buffer_store, cx| {
3781 buffer_store.forget_shared_buffers_for(&peer_id);
3782 for buffer in buffer_store.buffers() {
3783 buffer.update(cx, |buffer, cx| buffer.remove_peer(replica_id, cx));
3784 }
3785 });
3786
3787 cx.emit(Event::CollaboratorLeft(peer_id));
3788 cx.notify();
3789 Ok(())
3790 })?
3791 }
3792
3793 async fn handle_update_project(
3794 this: Entity<Self>,
3795 envelope: TypedEnvelope<proto::UpdateProject>,
3796 mut cx: AsyncApp,
3797 ) -> Result<()> {
3798 this.update(&mut cx, |this, cx| {
3799 // Don't handle messages that were sent before the response to us joining the project
3800 if envelope.message_id > this.join_project_response_message_id {
3801 this.set_worktrees_from_proto(envelope.payload.worktrees, cx)?;
3802 }
3803 Ok(())
3804 })?
3805 }
3806
3807 async fn handle_toast(
3808 this: Entity<Self>,
3809 envelope: TypedEnvelope<proto::Toast>,
3810 mut cx: AsyncApp,
3811 ) -> Result<()> {
3812 this.update(&mut cx, |_, cx| {
3813 cx.emit(Event::Toast {
3814 notification_id: envelope.payload.notification_id.into(),
3815 message: envelope.payload.message,
3816 });
3817 Ok(())
3818 })?
3819 }
3820
3821 async fn handle_language_server_prompt_request(
3822 this: Entity<Self>,
3823 envelope: TypedEnvelope<proto::LanguageServerPromptRequest>,
3824 mut cx: AsyncApp,
3825 ) -> Result<proto::LanguageServerPromptResponse> {
3826 let (tx, mut rx) = smol::channel::bounded(1);
3827 let actions: Vec<_> = envelope
3828 .payload
3829 .actions
3830 .into_iter()
3831 .map(|action| MessageActionItem {
3832 title: action,
3833 properties: Default::default(),
3834 })
3835 .collect();
3836 this.update(&mut cx, |_, cx| {
3837 cx.emit(Event::LanguageServerPrompt(LanguageServerPromptRequest {
3838 level: proto_to_prompt(envelope.payload.level.context("Invalid prompt level")?),
3839 message: envelope.payload.message,
3840 actions: actions.clone(),
3841 lsp_name: envelope.payload.lsp_name,
3842 response_channel: tx,
3843 }));
3844
3845 anyhow::Ok(())
3846 })??;
3847
3848 // We drop `this` to avoid holding a reference in this future for too
3849 // long.
3850 // If we keep the reference, we might not drop the `Project` early
3851 // enough when closing a window and it will only get releases on the
3852 // next `flush_effects()` call.
3853 drop(this);
3854
3855 let mut rx = pin!(rx);
3856 let answer = rx.next().await;
3857
3858 Ok(LanguageServerPromptResponse {
3859 action_response: answer.and_then(|answer| {
3860 actions
3861 .iter()
3862 .position(|action| *action == answer)
3863 .map(|index| index as u64)
3864 }),
3865 })
3866 }
3867
3868 async fn handle_hide_toast(
3869 this: Entity<Self>,
3870 envelope: TypedEnvelope<proto::HideToast>,
3871 mut cx: AsyncApp,
3872 ) -> Result<()> {
3873 this.update(&mut cx, |_, cx| {
3874 cx.emit(Event::HideToast {
3875 notification_id: envelope.payload.notification_id.into(),
3876 });
3877 Ok(())
3878 })?
3879 }
3880
3881 // Collab sends UpdateWorktree protos as messages
3882 async fn handle_update_worktree(
3883 this: Entity<Self>,
3884 envelope: TypedEnvelope<proto::UpdateWorktree>,
3885 mut cx: AsyncApp,
3886 ) -> Result<()> {
3887 this.update(&mut cx, |this, cx| {
3888 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
3889 if let Some(worktree) = this.worktree_for_id(worktree_id, cx) {
3890 worktree.update(cx, |worktree, _| {
3891 let worktree = worktree.as_remote_mut().unwrap();
3892 worktree.update_from_remote(envelope.payload);
3893 });
3894 }
3895 Ok(())
3896 })?
3897 }
3898
3899 async fn handle_update_buffer_from_ssh(
3900 this: Entity<Self>,
3901 envelope: TypedEnvelope<proto::UpdateBuffer>,
3902 cx: AsyncApp,
3903 ) -> Result<proto::Ack> {
3904 let buffer_store = this.read_with(&cx, |this, cx| {
3905 if let Some(remote_id) = this.remote_id() {
3906 let mut payload = envelope.payload.clone();
3907 payload.project_id = remote_id;
3908 cx.background_spawn(this.client.request(payload))
3909 .detach_and_log_err(cx);
3910 }
3911 this.buffer_store.clone()
3912 })?;
3913 BufferStore::handle_update_buffer(buffer_store, envelope, cx).await
3914 }
3915
3916 async fn handle_update_buffer(
3917 this: Entity<Self>,
3918 envelope: TypedEnvelope<proto::UpdateBuffer>,
3919 cx: AsyncApp,
3920 ) -> Result<proto::Ack> {
3921 let buffer_store = this.read_with(&cx, |this, cx| {
3922 if let Some(ssh) = &this.ssh_client {
3923 let mut payload = envelope.payload.clone();
3924 payload.project_id = SSH_PROJECT_ID;
3925 cx.background_spawn(ssh.read(cx).proto_client().request(payload))
3926 .detach_and_log_err(cx);
3927 }
3928 this.buffer_store.clone()
3929 })?;
3930 BufferStore::handle_update_buffer(buffer_store, envelope, cx).await
3931 }
3932
3933 fn retain_remotely_created_models(
3934 &mut self,
3935 cx: &mut Context<Self>,
3936 ) -> RemotelyCreatedModelGuard {
3937 {
3938 let mut remotely_create_models = self.remotely_created_models.lock();
3939 if remotely_create_models.retain_count == 0 {
3940 remotely_create_models.buffers = self.buffer_store.read(cx).buffers().collect();
3941 remotely_create_models.worktrees =
3942 self.worktree_store.read(cx).worktrees().collect();
3943 }
3944 remotely_create_models.retain_count += 1;
3945 }
3946 RemotelyCreatedModelGuard {
3947 remote_models: Arc::downgrade(&self.remotely_created_models),
3948 }
3949 }
3950
3951 async fn handle_create_buffer_for_peer(
3952 this: Entity<Self>,
3953 envelope: TypedEnvelope<proto::CreateBufferForPeer>,
3954 mut cx: AsyncApp,
3955 ) -> Result<()> {
3956 this.update(&mut cx, |this, cx| {
3957 this.buffer_store.update(cx, |buffer_store, cx| {
3958 buffer_store.handle_create_buffer_for_peer(
3959 envelope,
3960 this.replica_id(),
3961 this.capability(),
3962 cx,
3963 )
3964 })
3965 })?
3966 }
3967
3968 async fn handle_synchronize_buffers(
3969 this: Entity<Self>,
3970 envelope: TypedEnvelope<proto::SynchronizeBuffers>,
3971 mut cx: AsyncApp,
3972 ) -> Result<proto::SynchronizeBuffersResponse> {
3973 let response = this.update(&mut cx, |this, cx| {
3974 let client = this.client.clone();
3975 this.buffer_store.update(cx, |this, cx| {
3976 this.handle_synchronize_buffers(envelope, cx, client)
3977 })
3978 })??;
3979
3980 Ok(response)
3981 }
3982
3983 async fn handle_search_candidate_buffers(
3984 this: Entity<Self>,
3985 envelope: TypedEnvelope<proto::FindSearchCandidates>,
3986 mut cx: AsyncApp,
3987 ) -> Result<proto::FindSearchCandidatesResponse> {
3988 let peer_id = envelope.original_sender_id()?;
3989 let message = envelope.payload;
3990 let query = SearchQuery::from_proto(
3991 message
3992 .query
3993 .ok_or_else(|| anyhow!("missing query field"))?,
3994 )?;
3995 let results = this.update(&mut cx, |this, cx| {
3996 this.find_search_candidate_buffers(&query, message.limit as _, cx)
3997 })?;
3998
3999 let mut response = proto::FindSearchCandidatesResponse {
4000 buffer_ids: Vec::new(),
4001 };
4002
4003 while let Ok(buffer) = results.recv().await {
4004 this.update(&mut cx, |this, cx| {
4005 let buffer_id = this.create_buffer_for_peer(&buffer, peer_id, cx);
4006 response.buffer_ids.push(buffer_id.to_proto());
4007 })?;
4008 }
4009
4010 Ok(response)
4011 }
4012
4013 async fn handle_open_buffer_by_id(
4014 this: Entity<Self>,
4015 envelope: TypedEnvelope<proto::OpenBufferById>,
4016 mut cx: AsyncApp,
4017 ) -> Result<proto::OpenBufferResponse> {
4018 let peer_id = envelope.original_sender_id()?;
4019 let buffer_id = BufferId::new(envelope.payload.id)?;
4020 let buffer = this
4021 .update(&mut cx, |this, cx| this.open_buffer_by_id(buffer_id, cx))?
4022 .await?;
4023 Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
4024 }
4025
4026 async fn handle_open_buffer_by_path(
4027 this: Entity<Self>,
4028 envelope: TypedEnvelope<proto::OpenBufferByPath>,
4029 mut cx: AsyncApp,
4030 ) -> Result<proto::OpenBufferResponse> {
4031 let peer_id = envelope.original_sender_id()?;
4032 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
4033 let open_buffer = this.update(&mut cx, |this, cx| {
4034 this.open_buffer(
4035 ProjectPath {
4036 worktree_id,
4037 path: Arc::<Path>::from_proto(envelope.payload.path),
4038 },
4039 cx,
4040 )
4041 })?;
4042
4043 let buffer = open_buffer.await?;
4044 Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
4045 }
4046
4047 async fn handle_open_new_buffer(
4048 this: Entity<Self>,
4049 envelope: TypedEnvelope<proto::OpenNewBuffer>,
4050 mut cx: AsyncApp,
4051 ) -> Result<proto::OpenBufferResponse> {
4052 let buffer = this
4053 .update(&mut cx, |this, cx| this.create_buffer(cx))?
4054 .await?;
4055 let peer_id = envelope.original_sender_id()?;
4056
4057 Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
4058 }
4059
4060 fn respond_to_open_buffer_request(
4061 this: Entity<Self>,
4062 buffer: Entity<Buffer>,
4063 peer_id: proto::PeerId,
4064 cx: &mut AsyncApp,
4065 ) -> Result<proto::OpenBufferResponse> {
4066 this.update(cx, |this, cx| {
4067 let is_private = buffer
4068 .read(cx)
4069 .file()
4070 .map(|f| f.is_private())
4071 .unwrap_or_default();
4072 if is_private {
4073 Err(anyhow!(ErrorCode::UnsharedItem))
4074 } else {
4075 Ok(proto::OpenBufferResponse {
4076 buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx).into(),
4077 })
4078 }
4079 })?
4080 }
4081
4082 fn create_buffer_for_peer(
4083 &mut self,
4084 buffer: &Entity<Buffer>,
4085 peer_id: proto::PeerId,
4086 cx: &mut App,
4087 ) -> BufferId {
4088 self.buffer_store
4089 .update(cx, |buffer_store, cx| {
4090 buffer_store.create_buffer_for_peer(buffer, peer_id, cx)
4091 })
4092 .detach_and_log_err(cx);
4093 buffer.read(cx).remote_id()
4094 }
4095
4096 fn synchronize_remote_buffers(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
4097 let project_id = match self.client_state {
4098 ProjectClientState::Remote {
4099 sharing_has_stopped,
4100 remote_id,
4101 ..
4102 } => {
4103 if sharing_has_stopped {
4104 return Task::ready(Err(anyhow!(
4105 "can't synchronize remote buffers on a readonly project"
4106 )));
4107 } else {
4108 remote_id
4109 }
4110 }
4111 ProjectClientState::Shared { .. } | ProjectClientState::Local => {
4112 return Task::ready(Err(anyhow!(
4113 "can't synchronize remote buffers on a local project"
4114 )))
4115 }
4116 };
4117
4118 let client = self.client.clone();
4119 cx.spawn(move |this, mut cx| async move {
4120 let (buffers, incomplete_buffer_ids) = this.update(&mut cx, |this, cx| {
4121 this.buffer_store.read(cx).buffer_version_info(cx)
4122 })?;
4123 let response = client
4124 .request(proto::SynchronizeBuffers {
4125 project_id,
4126 buffers,
4127 })
4128 .await?;
4129
4130 let send_updates_for_buffers = this.update(&mut cx, |this, cx| {
4131 response
4132 .buffers
4133 .into_iter()
4134 .map(|buffer| {
4135 let client = client.clone();
4136 let buffer_id = match BufferId::new(buffer.id) {
4137 Ok(id) => id,
4138 Err(e) => {
4139 return Task::ready(Err(e));
4140 }
4141 };
4142 let remote_version = language::proto::deserialize_version(&buffer.version);
4143 if let Some(buffer) = this.buffer_for_id(buffer_id, cx) {
4144 let operations =
4145 buffer.read(cx).serialize_ops(Some(remote_version), cx);
4146 cx.background_spawn(async move {
4147 let operations = operations.await;
4148 for chunk in split_operations(operations) {
4149 client
4150 .request(proto::UpdateBuffer {
4151 project_id,
4152 buffer_id: buffer_id.into(),
4153 operations: chunk,
4154 })
4155 .await?;
4156 }
4157 anyhow::Ok(())
4158 })
4159 } else {
4160 Task::ready(Ok(()))
4161 }
4162 })
4163 .collect::<Vec<_>>()
4164 })?;
4165
4166 // Any incomplete buffers have open requests waiting. Request that the host sends
4167 // creates these buffers for us again to unblock any waiting futures.
4168 for id in incomplete_buffer_ids {
4169 cx.background_spawn(client.request(proto::OpenBufferById {
4170 project_id,
4171 id: id.into(),
4172 }))
4173 .detach();
4174 }
4175
4176 futures::future::join_all(send_updates_for_buffers)
4177 .await
4178 .into_iter()
4179 .collect()
4180 })
4181 }
4182
4183 pub fn worktree_metadata_protos(&self, cx: &App) -> Vec<proto::WorktreeMetadata> {
4184 self.worktree_store.read(cx).worktree_metadata_protos(cx)
4185 }
4186
4187 /// Iterator of all open buffers that have unsaved changes
4188 pub fn dirty_buffers<'a>(&'a self, cx: &'a App) -> impl Iterator<Item = ProjectPath> + 'a {
4189 self.buffer_store.read(cx).buffers().filter_map(|buf| {
4190 let buf = buf.read(cx);
4191 if buf.is_dirty() {
4192 buf.project_path(cx)
4193 } else {
4194 None
4195 }
4196 })
4197 }
4198
4199 fn set_worktrees_from_proto(
4200 &mut self,
4201 worktrees: Vec<proto::WorktreeMetadata>,
4202 cx: &mut Context<Project>,
4203 ) -> Result<()> {
4204 cx.notify();
4205 self.worktree_store.update(cx, |worktree_store, cx| {
4206 worktree_store.set_worktrees_from_proto(worktrees, self.replica_id(), cx)
4207 })
4208 }
4209
4210 fn set_collaborators_from_proto(
4211 &mut self,
4212 messages: Vec<proto::Collaborator>,
4213 cx: &mut Context<Self>,
4214 ) -> Result<()> {
4215 let mut collaborators = HashMap::default();
4216 for message in messages {
4217 let collaborator = Collaborator::from_proto(message)?;
4218 collaborators.insert(collaborator.peer_id, collaborator);
4219 }
4220 for old_peer_id in self.collaborators.keys() {
4221 if !collaborators.contains_key(old_peer_id) {
4222 cx.emit(Event::CollaboratorLeft(*old_peer_id));
4223 }
4224 }
4225 self.collaborators = collaborators;
4226 Ok(())
4227 }
4228
4229 pub fn supplementary_language_servers<'a>(
4230 &'a self,
4231 cx: &'a App,
4232 ) -> impl 'a + Iterator<Item = (LanguageServerId, LanguageServerName)> {
4233 self.lsp_store.read(cx).supplementary_language_servers()
4234 }
4235
4236 pub fn any_language_server_supports_inlay_hints(&self, buffer: &Buffer, cx: &mut App) -> bool {
4237 self.lsp_store.update(cx, |this, cx| {
4238 this.language_servers_for_local_buffer(buffer, cx)
4239 .any(
4240 |(_, server)| match server.capabilities().inlay_hint_provider {
4241 Some(lsp::OneOf::Left(enabled)) => enabled,
4242 Some(lsp::OneOf::Right(_)) => true,
4243 None => false,
4244 },
4245 )
4246 })
4247 }
4248
4249 pub fn language_server_id_for_name(
4250 &self,
4251 buffer: &Buffer,
4252 name: &str,
4253 cx: &mut App,
4254 ) -> Option<LanguageServerId> {
4255 self.lsp_store.update(cx, |this, cx| {
4256 this.language_servers_for_local_buffer(buffer, cx)
4257 .find_map(|(adapter, server)| {
4258 if adapter.name.0 == name {
4259 Some(server.server_id())
4260 } else {
4261 None
4262 }
4263 })
4264 })
4265 }
4266
4267 pub fn has_language_servers_for(&self, buffer: &Buffer, cx: &mut App) -> bool {
4268 self.lsp_store.update(cx, |this, cx| {
4269 this.language_servers_for_local_buffer(buffer, cx)
4270 .next()
4271 .is_some()
4272 })
4273 }
4274
4275 pub fn buffer_store(&self) -> &Entity<BufferStore> {
4276 &self.buffer_store
4277 }
4278
4279 pub fn git_store(&self) -> &Entity<GitStore> {
4280 &self.git_store
4281 }
4282
4283 pub fn active_repository(&self, cx: &App) -> Option<Entity<Repository>> {
4284 self.git_store.read(cx).active_repository()
4285 }
4286
4287 pub fn all_repositories(&self, cx: &App) -> Vec<Entity<Repository>> {
4288 self.git_store.read(cx).all_repositories()
4289 }
4290
4291 pub fn repository_and_path_for_buffer_id(
4292 &self,
4293 buffer_id: BufferId,
4294 cx: &App,
4295 ) -> Option<(Entity<Repository>, RepoPath)> {
4296 let path = self
4297 .buffer_for_id(buffer_id, cx)?
4298 .read(cx)
4299 .project_path(cx)?;
4300 self.git_store
4301 .read(cx)
4302 .all_repositories()
4303 .into_iter()
4304 .find_map(|repo| {
4305 Some((
4306 repo.clone(),
4307 repo.read(cx).repository_entry.relativize(&path.path).ok()?,
4308 ))
4309 })
4310 }
4311}
4312
4313fn deserialize_code_actions(code_actions: &HashMap<String, bool>) -> Vec<lsp::CodeActionKind> {
4314 code_actions
4315 .iter()
4316 .flat_map(|(kind, enabled)| {
4317 if *enabled {
4318 Some(kind.clone().into())
4319 } else {
4320 None
4321 }
4322 })
4323 .collect()
4324}
4325
4326pub struct PathMatchCandidateSet {
4327 pub snapshot: Snapshot,
4328 pub include_ignored: bool,
4329 pub include_root_name: bool,
4330 pub candidates: Candidates,
4331}
4332
4333pub enum Candidates {
4334 /// Only consider directories.
4335 Directories,
4336 /// Only consider files.
4337 Files,
4338 /// Consider directories and files.
4339 Entries,
4340}
4341
4342impl<'a> fuzzy::PathMatchCandidateSet<'a> for PathMatchCandidateSet {
4343 type Candidates = PathMatchCandidateSetIter<'a>;
4344
4345 fn id(&self) -> usize {
4346 self.snapshot.id().to_usize()
4347 }
4348
4349 fn len(&self) -> usize {
4350 match self.candidates {
4351 Candidates::Files => {
4352 if self.include_ignored {
4353 self.snapshot.file_count()
4354 } else {
4355 self.snapshot.visible_file_count()
4356 }
4357 }
4358
4359 Candidates::Directories => {
4360 if self.include_ignored {
4361 self.snapshot.dir_count()
4362 } else {
4363 self.snapshot.visible_dir_count()
4364 }
4365 }
4366
4367 Candidates::Entries => {
4368 if self.include_ignored {
4369 self.snapshot.entry_count()
4370 } else {
4371 self.snapshot.visible_entry_count()
4372 }
4373 }
4374 }
4375 }
4376
4377 fn prefix(&self) -> Arc<str> {
4378 if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
4379 self.snapshot.root_name().into()
4380 } else if self.include_root_name {
4381 format!("{}{}", self.snapshot.root_name(), std::path::MAIN_SEPARATOR).into()
4382 } else {
4383 Arc::default()
4384 }
4385 }
4386
4387 fn candidates(&'a self, start: usize) -> Self::Candidates {
4388 PathMatchCandidateSetIter {
4389 traversal: match self.candidates {
4390 Candidates::Directories => self.snapshot.directories(self.include_ignored, start),
4391 Candidates::Files => self.snapshot.files(self.include_ignored, start),
4392 Candidates::Entries => self.snapshot.entries(self.include_ignored, start),
4393 },
4394 }
4395 }
4396}
4397
4398pub struct PathMatchCandidateSetIter<'a> {
4399 traversal: Traversal<'a>,
4400}
4401
4402impl<'a> Iterator for PathMatchCandidateSetIter<'a> {
4403 type Item = fuzzy::PathMatchCandidate<'a>;
4404
4405 fn next(&mut self) -> Option<Self::Item> {
4406 self.traversal
4407 .next()
4408 .map(|entry| fuzzy::PathMatchCandidate {
4409 is_dir: entry.kind.is_dir(),
4410 path: &entry.path,
4411 char_bag: entry.char_bag,
4412 })
4413 }
4414}
4415
4416impl EventEmitter<Event> for Project {}
4417
4418impl<'a> From<&'a ProjectPath> for SettingsLocation<'a> {
4419 fn from(val: &'a ProjectPath) -> Self {
4420 SettingsLocation {
4421 worktree_id: val.worktree_id,
4422 path: val.path.as_ref(),
4423 }
4424 }
4425}
4426
4427impl<P: AsRef<Path>> From<(WorktreeId, P)> for ProjectPath {
4428 fn from((worktree_id, path): (WorktreeId, P)) -> Self {
4429 Self {
4430 worktree_id,
4431 path: path.as_ref().into(),
4432 }
4433 }
4434}
4435
4436pub fn relativize_path(base: &Path, path: &Path) -> PathBuf {
4437 let mut path_components = path.components();
4438 let mut base_components = base.components();
4439 let mut components: Vec<Component> = Vec::new();
4440 loop {
4441 match (path_components.next(), base_components.next()) {
4442 (None, None) => break,
4443 (Some(a), None) => {
4444 components.push(a);
4445 components.extend(path_components.by_ref());
4446 break;
4447 }
4448 (None, _) => components.push(Component::ParentDir),
4449 (Some(a), Some(b)) if components.is_empty() && a == b => (),
4450 (Some(a), Some(Component::CurDir)) => components.push(a),
4451 (Some(a), Some(_)) => {
4452 components.push(Component::ParentDir);
4453 for _ in base_components {
4454 components.push(Component::ParentDir);
4455 }
4456 components.push(a);
4457 components.extend(path_components.by_ref());
4458 break;
4459 }
4460 }
4461 }
4462 components.iter().map(|c| c.as_os_str()).collect()
4463}
4464
4465fn resolve_path(base: &Path, path: &Path) -> PathBuf {
4466 let mut result = base.to_path_buf();
4467 for component in path.components() {
4468 match component {
4469 Component::ParentDir => {
4470 result.pop();
4471 }
4472 Component::CurDir => (),
4473 _ => result.push(component),
4474 }
4475 }
4476 result
4477}
4478
4479/// ResolvedPath is a path that has been resolved to either a ProjectPath
4480/// or an AbsPath and that *exists*.
4481#[derive(Debug, Clone)]
4482pub enum ResolvedPath {
4483 ProjectPath {
4484 project_path: ProjectPath,
4485 is_dir: bool,
4486 },
4487 AbsPath {
4488 path: PathBuf,
4489 is_dir: bool,
4490 },
4491}
4492
4493impl ResolvedPath {
4494 pub fn abs_path(&self) -> Option<&Path> {
4495 match self {
4496 Self::AbsPath { path, .. } => Some(path.as_path()),
4497 _ => None,
4498 }
4499 }
4500
4501 pub fn project_path(&self) -> Option<&ProjectPath> {
4502 match self {
4503 Self::ProjectPath { project_path, .. } => Some(&project_path),
4504 _ => None,
4505 }
4506 }
4507
4508 pub fn is_file(&self) -> bool {
4509 !self.is_dir()
4510 }
4511
4512 pub fn is_dir(&self) -> bool {
4513 match self {
4514 Self::ProjectPath { is_dir, .. } => *is_dir,
4515 Self::AbsPath { is_dir, .. } => *is_dir,
4516 }
4517 }
4518}
4519
4520impl ProjectItem for Buffer {
4521 fn try_open(
4522 project: &Entity<Project>,
4523 path: &ProjectPath,
4524 cx: &mut App,
4525 ) -> Option<Task<Result<Entity<Self>>>> {
4526 Some(project.update(cx, |project, cx| project.open_buffer(path.clone(), cx)))
4527 }
4528
4529 fn entry_id(&self, cx: &App) -> Option<ProjectEntryId> {
4530 File::from_dyn(self.file()).and_then(|file| file.project_entry_id(cx))
4531 }
4532
4533 fn project_path(&self, cx: &App) -> Option<ProjectPath> {
4534 File::from_dyn(self.file()).map(|file| ProjectPath {
4535 worktree_id: file.worktree_id(cx),
4536 path: file.path().clone(),
4537 })
4538 }
4539
4540 fn is_dirty(&self) -> bool {
4541 self.is_dirty()
4542 }
4543}
4544
4545impl Completion {
4546 /// A key that can be used to sort completions when displaying
4547 /// them to the user.
4548 pub fn sort_key(&self) -> (usize, &str) {
4549 let kind_key = match self.lsp_completion.kind {
4550 Some(lsp::CompletionItemKind::KEYWORD) => 0,
4551 Some(lsp::CompletionItemKind::VARIABLE) => 1,
4552 _ => 2,
4553 };
4554 (kind_key, &self.label.text[self.label.filter_range.clone()])
4555 }
4556
4557 /// Whether this completion is a snippet.
4558 pub fn is_snippet(&self) -> bool {
4559 self.lsp_completion.insert_text_format == Some(lsp::InsertTextFormat::SNIPPET)
4560 }
4561
4562 /// Returns the corresponding color for this completion.
4563 ///
4564 /// Will return `None` if this completion's kind is not [`CompletionItemKind::COLOR`].
4565 pub fn color(&self) -> Option<Hsla> {
4566 match self.lsp_completion.kind {
4567 Some(CompletionItemKind::COLOR) => color_extractor::extract_color(&self.lsp_completion),
4568 _ => None,
4569 }
4570 }
4571}
4572
4573pub fn sort_worktree_entries(entries: &mut [impl AsRef<Entry>]) {
4574 entries.sort_by(|entry_a, entry_b| {
4575 let entry_a = entry_a.as_ref();
4576 let entry_b = entry_b.as_ref();
4577 compare_paths(
4578 (&entry_a.path, entry_a.is_file()),
4579 (&entry_b.path, entry_b.is_file()),
4580 )
4581 });
4582}
4583
4584fn proto_to_prompt(level: proto::language_server_prompt_request::Level) -> gpui::PromptLevel {
4585 match level {
4586 proto::language_server_prompt_request::Level::Info(_) => gpui::PromptLevel::Info,
4587 proto::language_server_prompt_request::Level::Warning(_) => gpui::PromptLevel::Warning,
4588 proto::language_server_prompt_request::Level::Critical(_) => gpui::PromptLevel::Critical,
4589 }
4590}