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