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