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