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