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