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