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