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