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