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