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