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