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