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