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