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 worktree_id: WorktreeId,
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(worktree_id, 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 worktree_id: WorktreeId,
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| {
3109 this.activate_toolchain(worktree_id, toolchain, cx)
3110 })
3111 }
3112 pub fn active_toolchain(
3113 &self,
3114 worktree_id: WorktreeId,
3115 language_name: LanguageName,
3116 cx: &App,
3117 ) -> Task<Option<Toolchain>> {
3118 let Some(toolchain_store) = self.toolchain_store.clone() else {
3119 return Task::ready(None);
3120 };
3121 toolchain_store
3122 .read(cx)
3123 .active_toolchain(worktree_id, language_name, cx)
3124 }
3125 pub fn language_server_statuses<'a>(
3126 &'a self,
3127 cx: &'a App,
3128 ) -> impl DoubleEndedIterator<Item = (LanguageServerId, &'a LanguageServerStatus)> {
3129 self.lsp_store.read(cx).language_server_statuses()
3130 }
3131
3132 pub fn last_formatting_failure<'a>(&self, cx: &'a App) -> Option<&'a str> {
3133 self.lsp_store.read(cx).last_formatting_failure()
3134 }
3135
3136 pub fn reset_last_formatting_failure(&self, cx: &mut App) {
3137 self.lsp_store
3138 .update(cx, |store, _| store.reset_last_formatting_failure());
3139 }
3140
3141 pub fn reload_buffers(
3142 &self,
3143 buffers: HashSet<Entity<Buffer>>,
3144 push_to_history: bool,
3145 cx: &mut Context<Self>,
3146 ) -> Task<Result<ProjectTransaction>> {
3147 self.buffer_store.update(cx, |buffer_store, cx| {
3148 buffer_store.reload_buffers(buffers, push_to_history, cx)
3149 })
3150 }
3151
3152 pub fn reload_images(
3153 &self,
3154 images: HashSet<Entity<ImageItem>>,
3155 cx: &mut Context<Self>,
3156 ) -> Task<Result<()>> {
3157 self.image_store
3158 .update(cx, |image_store, cx| image_store.reload_images(images, cx))
3159 }
3160
3161 pub fn format(
3162 &mut self,
3163 buffers: HashSet<Entity<Buffer>>,
3164 target: LspFormatTarget,
3165 push_to_history: bool,
3166 trigger: lsp_store::FormatTrigger,
3167 cx: &mut Context<Project>,
3168 ) -> Task<anyhow::Result<ProjectTransaction>> {
3169 self.lsp_store.update(cx, |lsp_store, cx| {
3170 lsp_store.format(buffers, target, push_to_history, trigger, cx)
3171 })
3172 }
3173
3174 #[inline(never)]
3175 fn definition_impl(
3176 &mut self,
3177 buffer: &Entity<Buffer>,
3178 position: PointUtf16,
3179 cx: &mut Context<Self>,
3180 ) -> Task<Result<Vec<LocationLink>>> {
3181 self.request_lsp(
3182 buffer.clone(),
3183 LanguageServerToQuery::FirstCapable,
3184 GetDefinition { position },
3185 cx,
3186 )
3187 }
3188 pub fn definition<T: ToPointUtf16>(
3189 &mut self,
3190 buffer: &Entity<Buffer>,
3191 position: T,
3192 cx: &mut Context<Self>,
3193 ) -> Task<Result<Vec<LocationLink>>> {
3194 let position = position.to_point_utf16(buffer.read(cx));
3195 self.definition_impl(buffer, position, cx)
3196 }
3197
3198 fn declaration_impl(
3199 &mut self,
3200 buffer: &Entity<Buffer>,
3201 position: PointUtf16,
3202 cx: &mut Context<Self>,
3203 ) -> Task<Result<Vec<LocationLink>>> {
3204 self.request_lsp(
3205 buffer.clone(),
3206 LanguageServerToQuery::FirstCapable,
3207 GetDeclaration { position },
3208 cx,
3209 )
3210 }
3211
3212 pub fn declaration<T: ToPointUtf16>(
3213 &mut self,
3214 buffer: &Entity<Buffer>,
3215 position: T,
3216 cx: &mut Context<Self>,
3217 ) -> Task<Result<Vec<LocationLink>>> {
3218 let position = position.to_point_utf16(buffer.read(cx));
3219 self.declaration_impl(buffer, position, cx)
3220 }
3221
3222 fn type_definition_impl(
3223 &mut self,
3224 buffer: &Entity<Buffer>,
3225 position: PointUtf16,
3226 cx: &mut Context<Self>,
3227 ) -> Task<Result<Vec<LocationLink>>> {
3228 self.request_lsp(
3229 buffer.clone(),
3230 LanguageServerToQuery::FirstCapable,
3231 GetTypeDefinition { position },
3232 cx,
3233 )
3234 }
3235
3236 pub fn type_definition<T: ToPointUtf16>(
3237 &mut self,
3238 buffer: &Entity<Buffer>,
3239 position: T,
3240 cx: &mut Context<Self>,
3241 ) -> Task<Result<Vec<LocationLink>>> {
3242 let position = position.to_point_utf16(buffer.read(cx));
3243 self.type_definition_impl(buffer, position, cx)
3244 }
3245
3246 pub fn implementation<T: ToPointUtf16>(
3247 &mut self,
3248 buffer: &Entity<Buffer>,
3249 position: T,
3250 cx: &mut Context<Self>,
3251 ) -> Task<Result<Vec<LocationLink>>> {
3252 let position = position.to_point_utf16(buffer.read(cx));
3253 self.request_lsp(
3254 buffer.clone(),
3255 LanguageServerToQuery::FirstCapable,
3256 GetImplementation { position },
3257 cx,
3258 )
3259 }
3260
3261 pub fn references<T: ToPointUtf16>(
3262 &mut self,
3263 buffer: &Entity<Buffer>,
3264 position: T,
3265 cx: &mut Context<Self>,
3266 ) -> Task<Result<Vec<Location>>> {
3267 let position = position.to_point_utf16(buffer.read(cx));
3268 self.request_lsp(
3269 buffer.clone(),
3270 LanguageServerToQuery::FirstCapable,
3271 GetReferences { position },
3272 cx,
3273 )
3274 }
3275
3276 fn document_highlights_impl(
3277 &mut self,
3278 buffer: &Entity<Buffer>,
3279 position: PointUtf16,
3280 cx: &mut Context<Self>,
3281 ) -> Task<Result<Vec<DocumentHighlight>>> {
3282 self.request_lsp(
3283 buffer.clone(),
3284 LanguageServerToQuery::FirstCapable,
3285 GetDocumentHighlights { position },
3286 cx,
3287 )
3288 }
3289
3290 pub fn document_highlights<T: ToPointUtf16>(
3291 &mut self,
3292 buffer: &Entity<Buffer>,
3293 position: T,
3294 cx: &mut Context<Self>,
3295 ) -> Task<Result<Vec<DocumentHighlight>>> {
3296 let position = position.to_point_utf16(buffer.read(cx));
3297 self.document_highlights_impl(buffer, position, cx)
3298 }
3299
3300 pub fn document_symbols(
3301 &mut self,
3302 buffer: &Entity<Buffer>,
3303 cx: &mut Context<Self>,
3304 ) -> Task<Result<Vec<DocumentSymbol>>> {
3305 self.request_lsp(
3306 buffer.clone(),
3307 LanguageServerToQuery::FirstCapable,
3308 GetDocumentSymbols,
3309 cx,
3310 )
3311 }
3312
3313 pub fn symbols(&self, query: &str, cx: &mut Context<Self>) -> Task<Result<Vec<Symbol>>> {
3314 self.lsp_store
3315 .update(cx, |lsp_store, cx| lsp_store.symbols(query, cx))
3316 }
3317
3318 pub fn open_buffer_for_symbol(
3319 &mut self,
3320 symbol: &Symbol,
3321 cx: &mut Context<Self>,
3322 ) -> Task<Result<Entity<Buffer>>> {
3323 self.lsp_store.update(cx, |lsp_store, cx| {
3324 lsp_store.open_buffer_for_symbol(symbol, cx)
3325 })
3326 }
3327
3328 pub fn open_server_settings(&mut self, cx: &mut Context<Self>) -> Task<Result<Entity<Buffer>>> {
3329 let guard = self.retain_remotely_created_models(cx);
3330 let Some(ssh_client) = self.ssh_client.as_ref() else {
3331 return Task::ready(Err(anyhow!("not an ssh project")));
3332 };
3333
3334 let proto_client = ssh_client.read(cx).proto_client();
3335
3336 cx.spawn(async move |project, cx| {
3337 let buffer = proto_client
3338 .request(proto::OpenServerSettings {
3339 project_id: SSH_PROJECT_ID,
3340 })
3341 .await?;
3342
3343 let buffer = project
3344 .update(cx, |project, cx| {
3345 project.buffer_store.update(cx, |buffer_store, cx| {
3346 anyhow::Ok(
3347 buffer_store
3348 .wait_for_remote_buffer(BufferId::new(buffer.buffer_id)?, cx),
3349 )
3350 })
3351 })??
3352 .await;
3353
3354 drop(guard);
3355 buffer
3356 })
3357 }
3358
3359 pub fn open_local_buffer_via_lsp(
3360 &mut self,
3361 abs_path: lsp::Url,
3362 language_server_id: LanguageServerId,
3363 language_server_name: LanguageServerName,
3364 cx: &mut Context<Self>,
3365 ) -> Task<Result<Entity<Buffer>>> {
3366 self.lsp_store.update(cx, |lsp_store, cx| {
3367 lsp_store.open_local_buffer_via_lsp(
3368 abs_path,
3369 language_server_id,
3370 language_server_name,
3371 cx,
3372 )
3373 })
3374 }
3375
3376 pub fn signature_help<T: ToPointUtf16>(
3377 &self,
3378 buffer: &Entity<Buffer>,
3379 position: T,
3380 cx: &mut Context<Self>,
3381 ) -> Task<Vec<SignatureHelp>> {
3382 self.lsp_store.update(cx, |lsp_store, cx| {
3383 lsp_store.signature_help(buffer, position, cx)
3384 })
3385 }
3386
3387 pub fn hover<T: ToPointUtf16>(
3388 &self,
3389 buffer: &Entity<Buffer>,
3390 position: T,
3391 cx: &mut Context<Self>,
3392 ) -> Task<Vec<Hover>> {
3393 let position = position.to_point_utf16(buffer.read(cx));
3394 self.lsp_store
3395 .update(cx, |lsp_store, cx| lsp_store.hover(buffer, position, cx))
3396 }
3397
3398 pub fn linked_edit(
3399 &self,
3400 buffer: &Entity<Buffer>,
3401 position: Anchor,
3402 cx: &mut Context<Self>,
3403 ) -> Task<Result<Vec<Range<Anchor>>>> {
3404 self.lsp_store.update(cx, |lsp_store, cx| {
3405 lsp_store.linked_edit(buffer, position, cx)
3406 })
3407 }
3408
3409 pub fn completions<T: ToOffset + ToPointUtf16>(
3410 &self,
3411 buffer: &Entity<Buffer>,
3412 position: T,
3413 context: CompletionContext,
3414 cx: &mut Context<Self>,
3415 ) -> Task<Result<Option<Vec<Completion>>>> {
3416 let position = position.to_point_utf16(buffer.read(cx));
3417 self.lsp_store.update(cx, |lsp_store, cx| {
3418 lsp_store.completions(buffer, position, context, cx)
3419 })
3420 }
3421
3422 pub fn code_actions<T: Clone + ToOffset>(
3423 &mut self,
3424 buffer_handle: &Entity<Buffer>,
3425 range: Range<T>,
3426 kinds: Option<Vec<CodeActionKind>>,
3427 cx: &mut Context<Self>,
3428 ) -> Task<Result<Vec<CodeAction>>> {
3429 let buffer = buffer_handle.read(cx);
3430 let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
3431 self.lsp_store.update(cx, |lsp_store, cx| {
3432 lsp_store.code_actions(buffer_handle, range, kinds, cx)
3433 })
3434 }
3435
3436 pub fn code_lens<T: Clone + ToOffset>(
3437 &mut self,
3438 buffer_handle: &Entity<Buffer>,
3439 range: Range<T>,
3440 cx: &mut Context<Self>,
3441 ) -> Task<Result<Vec<CodeAction>>> {
3442 let snapshot = buffer_handle.read(cx).snapshot();
3443 let range = snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end);
3444 let code_lens_actions = self
3445 .lsp_store
3446 .update(cx, |lsp_store, cx| lsp_store.code_lens(buffer_handle, cx));
3447
3448 cx.background_spawn(async move {
3449 let mut code_lens_actions = code_lens_actions.await?;
3450 code_lens_actions.retain(|code_lens_action| {
3451 range
3452 .start
3453 .cmp(&code_lens_action.range.start, &snapshot)
3454 .is_ge()
3455 && range
3456 .end
3457 .cmp(&code_lens_action.range.end, &snapshot)
3458 .is_le()
3459 });
3460 Ok(code_lens_actions)
3461 })
3462 }
3463
3464 pub fn apply_code_action(
3465 &self,
3466 buffer_handle: Entity<Buffer>,
3467 action: CodeAction,
3468 push_to_history: bool,
3469 cx: &mut Context<Self>,
3470 ) -> Task<Result<ProjectTransaction>> {
3471 self.lsp_store.update(cx, |lsp_store, cx| {
3472 lsp_store.apply_code_action(buffer_handle, action, push_to_history, cx)
3473 })
3474 }
3475
3476 pub fn apply_code_action_kind(
3477 &self,
3478 buffers: HashSet<Entity<Buffer>>,
3479 kind: CodeActionKind,
3480 push_to_history: bool,
3481 cx: &mut Context<Self>,
3482 ) -> Task<Result<ProjectTransaction>> {
3483 self.lsp_store.update(cx, |lsp_store, cx| {
3484 lsp_store.apply_code_action_kind(buffers, kind, push_to_history, cx)
3485 })
3486 }
3487
3488 fn prepare_rename_impl(
3489 &mut self,
3490 buffer: Entity<Buffer>,
3491 position: PointUtf16,
3492 cx: &mut Context<Self>,
3493 ) -> Task<Result<PrepareRenameResponse>> {
3494 self.request_lsp(
3495 buffer,
3496 LanguageServerToQuery::FirstCapable,
3497 PrepareRename { position },
3498 cx,
3499 )
3500 }
3501 pub fn prepare_rename<T: ToPointUtf16>(
3502 &mut self,
3503 buffer: Entity<Buffer>,
3504 position: T,
3505 cx: &mut Context<Self>,
3506 ) -> Task<Result<PrepareRenameResponse>> {
3507 let position = position.to_point_utf16(buffer.read(cx));
3508 self.prepare_rename_impl(buffer, position, cx)
3509 }
3510
3511 pub fn perform_rename<T: ToPointUtf16>(
3512 &mut self,
3513 buffer: Entity<Buffer>,
3514 position: T,
3515 new_name: String,
3516 cx: &mut Context<Self>,
3517 ) -> Task<Result<ProjectTransaction>> {
3518 let push_to_history = true;
3519 let position = position.to_point_utf16(buffer.read(cx));
3520 self.request_lsp(
3521 buffer,
3522 LanguageServerToQuery::FirstCapable,
3523 PerformRename {
3524 position,
3525 new_name,
3526 push_to_history,
3527 },
3528 cx,
3529 )
3530 }
3531
3532 pub fn on_type_format<T: ToPointUtf16>(
3533 &mut self,
3534 buffer: Entity<Buffer>,
3535 position: T,
3536 trigger: String,
3537 push_to_history: bool,
3538 cx: &mut Context<Self>,
3539 ) -> Task<Result<Option<Transaction>>> {
3540 self.lsp_store.update(cx, |lsp_store, cx| {
3541 lsp_store.on_type_format(buffer, position, trigger, push_to_history, cx)
3542 })
3543 }
3544
3545 pub fn inlay_hints<T: ToOffset>(
3546 &mut self,
3547 buffer_handle: Entity<Buffer>,
3548 range: Range<T>,
3549 cx: &mut Context<Self>,
3550 ) -> Task<anyhow::Result<Vec<InlayHint>>> {
3551 let buffer = buffer_handle.read(cx);
3552 let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
3553 self.lsp_store.update(cx, |lsp_store, cx| {
3554 lsp_store.inlay_hints(buffer_handle, range, cx)
3555 })
3556 }
3557
3558 pub fn resolve_inlay_hint(
3559 &self,
3560 hint: InlayHint,
3561 buffer_handle: Entity<Buffer>,
3562 server_id: LanguageServerId,
3563 cx: &mut Context<Self>,
3564 ) -> Task<anyhow::Result<InlayHint>> {
3565 self.lsp_store.update(cx, |lsp_store, cx| {
3566 lsp_store.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
3567 })
3568 }
3569
3570 pub fn search(&mut self, query: SearchQuery, cx: &mut Context<Self>) -> Receiver<SearchResult> {
3571 let (result_tx, result_rx) = smol::channel::unbounded();
3572
3573 let matching_buffers_rx = if query.is_opened_only() {
3574 self.sort_search_candidates(&query, cx)
3575 } else {
3576 self.find_search_candidate_buffers(&query, MAX_SEARCH_RESULT_FILES + 1, cx)
3577 };
3578
3579 cx.spawn(async move |_, cx| {
3580 let mut range_count = 0;
3581 let mut buffer_count = 0;
3582 let mut limit_reached = false;
3583 let query = Arc::new(query);
3584 let mut chunks = matching_buffers_rx.ready_chunks(64);
3585
3586 // Now that we know what paths match the query, we will load at most
3587 // 64 buffers at a time to avoid overwhelming the main thread. For each
3588 // opened buffer, we will spawn a background task that retrieves all the
3589 // ranges in the buffer matched by the query.
3590 let mut chunks = pin!(chunks);
3591 'outer: while let Some(matching_buffer_chunk) = chunks.next().await {
3592 let mut chunk_results = Vec::new();
3593 for buffer in matching_buffer_chunk {
3594 let buffer = buffer.clone();
3595 let query = query.clone();
3596 let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot())?;
3597 chunk_results.push(cx.background_spawn(async move {
3598 let ranges = query
3599 .search(&snapshot, None)
3600 .await
3601 .iter()
3602 .map(|range| {
3603 snapshot.anchor_before(range.start)
3604 ..snapshot.anchor_after(range.end)
3605 })
3606 .collect::<Vec<_>>();
3607 anyhow::Ok((buffer, ranges))
3608 }));
3609 }
3610
3611 let chunk_results = futures::future::join_all(chunk_results).await;
3612 for result in chunk_results {
3613 if let Some((buffer, ranges)) = result.log_err() {
3614 range_count += ranges.len();
3615 buffer_count += 1;
3616 result_tx
3617 .send(SearchResult::Buffer { buffer, ranges })
3618 .await?;
3619 if buffer_count > MAX_SEARCH_RESULT_FILES
3620 || range_count > MAX_SEARCH_RESULT_RANGES
3621 {
3622 limit_reached = true;
3623 break 'outer;
3624 }
3625 }
3626 }
3627 }
3628
3629 if limit_reached {
3630 result_tx.send(SearchResult::LimitReached).await?;
3631 }
3632
3633 anyhow::Ok(())
3634 })
3635 .detach();
3636
3637 result_rx
3638 }
3639
3640 fn find_search_candidate_buffers(
3641 &mut self,
3642 query: &SearchQuery,
3643 limit: usize,
3644 cx: &mut Context<Project>,
3645 ) -> Receiver<Entity<Buffer>> {
3646 if self.is_local() {
3647 let fs = self.fs.clone();
3648 self.buffer_store.update(cx, |buffer_store, cx| {
3649 buffer_store.find_search_candidates(query, limit, fs, cx)
3650 })
3651 } else {
3652 self.find_search_candidates_remote(query, limit, cx)
3653 }
3654 }
3655
3656 fn sort_search_candidates(
3657 &mut self,
3658 search_query: &SearchQuery,
3659 cx: &mut Context<Project>,
3660 ) -> Receiver<Entity<Buffer>> {
3661 let worktree_store = self.worktree_store.read(cx);
3662 let mut buffers = search_query
3663 .buffers()
3664 .into_iter()
3665 .flatten()
3666 .filter(|buffer| {
3667 let b = buffer.read(cx);
3668 if let Some(file) = b.file() {
3669 if !search_query.file_matches(file.path()) {
3670 return false;
3671 }
3672 if let Some(entry) = b
3673 .entry_id(cx)
3674 .and_then(|entry_id| worktree_store.entry_for_id(entry_id, cx))
3675 {
3676 if entry.is_ignored && !search_query.include_ignored() {
3677 return false;
3678 }
3679 }
3680 }
3681 true
3682 })
3683 .collect::<Vec<_>>();
3684 let (tx, rx) = smol::channel::unbounded();
3685 buffers.sort_by(|a, b| match (a.read(cx).file(), b.read(cx).file()) {
3686 (None, None) => a.read(cx).remote_id().cmp(&b.read(cx).remote_id()),
3687 (None, Some(_)) => std::cmp::Ordering::Less,
3688 (Some(_), None) => std::cmp::Ordering::Greater,
3689 (Some(a), Some(b)) => compare_paths((a.path(), true), (b.path(), true)),
3690 });
3691 for buffer in buffers {
3692 tx.send_blocking(buffer.clone()).unwrap()
3693 }
3694
3695 rx
3696 }
3697
3698 fn find_search_candidates_remote(
3699 &mut self,
3700 query: &SearchQuery,
3701 limit: usize,
3702 cx: &mut Context<Project>,
3703 ) -> Receiver<Entity<Buffer>> {
3704 let (tx, rx) = smol::channel::unbounded();
3705
3706 let (client, remote_id): (AnyProtoClient, _) = if let Some(ssh_client) = &self.ssh_client {
3707 (ssh_client.read(cx).proto_client(), 0)
3708 } else if let Some(remote_id) = self.remote_id() {
3709 (self.client.clone().into(), remote_id)
3710 } else {
3711 return rx;
3712 };
3713
3714 let request = client.request(proto::FindSearchCandidates {
3715 project_id: remote_id,
3716 query: Some(query.to_proto()),
3717 limit: limit as _,
3718 });
3719 let guard = self.retain_remotely_created_models(cx);
3720
3721 cx.spawn(async move |project, cx| {
3722 let response = request.await?;
3723 for buffer_id in response.buffer_ids {
3724 let buffer_id = BufferId::new(buffer_id)?;
3725 let buffer = project
3726 .update(cx, |project, cx| {
3727 project.buffer_store.update(cx, |buffer_store, cx| {
3728 buffer_store.wait_for_remote_buffer(buffer_id, cx)
3729 })
3730 })?
3731 .await?;
3732 let _ = tx.send(buffer).await;
3733 }
3734
3735 drop(guard);
3736 anyhow::Ok(())
3737 })
3738 .detach_and_log_err(cx);
3739 rx
3740 }
3741
3742 pub fn request_lsp<R: LspCommand>(
3743 &mut self,
3744 buffer_handle: Entity<Buffer>,
3745 server: LanguageServerToQuery,
3746 request: R,
3747 cx: &mut Context<Self>,
3748 ) -> Task<Result<R::Response>>
3749 where
3750 <R::LspRequest as lsp::request::Request>::Result: Send,
3751 <R::LspRequest as lsp::request::Request>::Params: Send,
3752 {
3753 let guard = self.retain_remotely_created_models(cx);
3754 let task = self.lsp_store.update(cx, |lsp_store, cx| {
3755 lsp_store.request_lsp(buffer_handle, server, request, cx)
3756 });
3757 cx.spawn(async move |_, _| {
3758 let result = task.await;
3759 drop(guard);
3760 result
3761 })
3762 }
3763
3764 /// Move a worktree to a new position in the worktree order.
3765 ///
3766 /// The worktree will moved to the opposite side of the destination worktree.
3767 ///
3768 /// # Example
3769 ///
3770 /// Given the worktree order `[11, 22, 33]` and a call to move worktree `22` to `33`,
3771 /// worktree_order will be updated to produce the indexes `[11, 33, 22]`.
3772 ///
3773 /// Given the worktree order `[11, 22, 33]` and a call to move worktree `22` to `11`,
3774 /// worktree_order will be updated to produce the indexes `[22, 11, 33]`.
3775 ///
3776 /// # Errors
3777 ///
3778 /// An error will be returned if the worktree or destination worktree are not found.
3779 pub fn move_worktree(
3780 &mut self,
3781 source: WorktreeId,
3782 destination: WorktreeId,
3783 cx: &mut Context<Self>,
3784 ) -> Result<()> {
3785 self.worktree_store.update(cx, |worktree_store, cx| {
3786 worktree_store.move_worktree(source, destination, cx)
3787 })
3788 }
3789
3790 pub fn find_or_create_worktree(
3791 &mut self,
3792 abs_path: impl AsRef<Path>,
3793 visible: bool,
3794 cx: &mut Context<Self>,
3795 ) -> Task<Result<(Entity<Worktree>, PathBuf)>> {
3796 self.worktree_store.update(cx, |worktree_store, cx| {
3797 worktree_store.find_or_create_worktree(abs_path, visible, cx)
3798 })
3799 }
3800
3801 pub fn find_worktree(&self, abs_path: &Path, cx: &App) -> Option<(Entity<Worktree>, PathBuf)> {
3802 self.worktree_store.read_with(cx, |worktree_store, cx| {
3803 worktree_store.find_worktree(abs_path, cx)
3804 })
3805 }
3806
3807 pub fn is_shared(&self) -> bool {
3808 match &self.client_state {
3809 ProjectClientState::Shared { .. } => true,
3810 ProjectClientState::Local => false,
3811 ProjectClientState::Remote { .. } => true,
3812 }
3813 }
3814
3815 /// Returns the resolved version of `path`, that was found in `buffer`, if it exists.
3816 pub fn resolve_path_in_buffer(
3817 &self,
3818 path: &str,
3819 buffer: &Entity<Buffer>,
3820 cx: &mut Context<Self>,
3821 ) -> Task<Option<ResolvedPath>> {
3822 let path_buf = PathBuf::from(path);
3823 if path_buf.is_absolute() || path.starts_with("~") {
3824 self.resolve_abs_path(path, cx)
3825 } else {
3826 self.resolve_path_in_worktrees(path_buf, buffer, cx)
3827 }
3828 }
3829
3830 pub fn resolve_abs_file_path(
3831 &self,
3832 path: &str,
3833 cx: &mut Context<Self>,
3834 ) -> Task<Option<ResolvedPath>> {
3835 let resolve_task = self.resolve_abs_path(path, cx);
3836 cx.background_spawn(async move {
3837 let resolved_path = resolve_task.await;
3838 resolved_path.filter(|path| path.is_file())
3839 })
3840 }
3841
3842 pub fn resolve_abs_path(
3843 &self,
3844 path: &str,
3845 cx: &mut Context<Self>,
3846 ) -> Task<Option<ResolvedPath>> {
3847 if self.is_local() {
3848 let expanded = PathBuf::from(shellexpand::tilde(&path).into_owned());
3849 let fs = self.fs.clone();
3850 cx.background_spawn(async move {
3851 let path = expanded.as_path();
3852 let metadata = fs.metadata(path).await.ok().flatten();
3853
3854 metadata.map(|metadata| ResolvedPath::AbsPath {
3855 path: expanded,
3856 is_dir: metadata.is_dir,
3857 })
3858 })
3859 } else if let Some(ssh_client) = self.ssh_client.as_ref() {
3860 let request_path = Path::new(path);
3861 let request = ssh_client
3862 .read(cx)
3863 .proto_client()
3864 .request(proto::GetPathMetadata {
3865 project_id: SSH_PROJECT_ID,
3866 path: request_path.to_proto(),
3867 });
3868 cx.background_spawn(async move {
3869 let response = request.await.log_err()?;
3870 if response.exists {
3871 Some(ResolvedPath::AbsPath {
3872 path: PathBuf::from_proto(response.path),
3873 is_dir: response.is_dir,
3874 })
3875 } else {
3876 None
3877 }
3878 })
3879 } else {
3880 return Task::ready(None);
3881 }
3882 }
3883
3884 fn resolve_path_in_worktrees(
3885 &self,
3886 path: PathBuf,
3887 buffer: &Entity<Buffer>,
3888 cx: &mut Context<Self>,
3889 ) -> Task<Option<ResolvedPath>> {
3890 let mut candidates = vec![path.clone()];
3891
3892 if let Some(file) = buffer.read(cx).file() {
3893 if let Some(dir) = file.path().parent() {
3894 let joined = dir.to_path_buf().join(path);
3895 candidates.push(joined);
3896 }
3897 }
3898
3899 let buffer_worktree_id = buffer.read(cx).file().map(|file| file.worktree_id(cx));
3900 let worktrees_with_ids: Vec<_> = self
3901 .worktrees(cx)
3902 .map(|worktree| {
3903 let id = worktree.read(cx).id();
3904 (worktree, id)
3905 })
3906 .collect();
3907
3908 cx.spawn(async move |_, mut cx| {
3909 if let Some(buffer_worktree_id) = buffer_worktree_id {
3910 if let Some((worktree, _)) = worktrees_with_ids
3911 .iter()
3912 .find(|(_, id)| *id == buffer_worktree_id)
3913 {
3914 for candidate in candidates.iter() {
3915 if let Some(path) =
3916 Self::resolve_path_in_worktree(&worktree, candidate, &mut cx)
3917 {
3918 return Some(path);
3919 }
3920 }
3921 }
3922 }
3923 for (worktree, id) in worktrees_with_ids {
3924 if Some(id) == buffer_worktree_id {
3925 continue;
3926 }
3927 for candidate in candidates.iter() {
3928 if let Some(path) =
3929 Self::resolve_path_in_worktree(&worktree, candidate, &mut cx)
3930 {
3931 return Some(path);
3932 }
3933 }
3934 }
3935 None
3936 })
3937 }
3938
3939 fn resolve_path_in_worktree(
3940 worktree: &Entity<Worktree>,
3941 path: &PathBuf,
3942 cx: &mut AsyncApp,
3943 ) -> Option<ResolvedPath> {
3944 worktree
3945 .update(cx, |worktree, _| {
3946 let root_entry_path = &worktree.root_entry()?.path;
3947 let resolved = resolve_path(root_entry_path, path);
3948 let stripped = resolved.strip_prefix(root_entry_path).unwrap_or(&resolved);
3949 worktree.entry_for_path(stripped).map(|entry| {
3950 let project_path = ProjectPath {
3951 worktree_id: worktree.id(),
3952 path: entry.path.clone(),
3953 };
3954 ResolvedPath::ProjectPath {
3955 project_path,
3956 is_dir: entry.is_dir(),
3957 }
3958 })
3959 })
3960 .ok()?
3961 }
3962
3963 pub fn list_directory(
3964 &self,
3965 query: String,
3966 cx: &mut Context<Self>,
3967 ) -> Task<Result<Vec<DirectoryItem>>> {
3968 if self.is_local() {
3969 DirectoryLister::Local(self.fs.clone()).list_directory(query, cx)
3970 } else if let Some(session) = self.ssh_client.as_ref() {
3971 let path_buf = PathBuf::from(query);
3972 let request = proto::ListRemoteDirectory {
3973 dev_server_id: SSH_PROJECT_ID,
3974 path: path_buf.to_proto(),
3975 config: Some(proto::ListRemoteDirectoryConfig { is_dir: true }),
3976 };
3977
3978 let response = session.read(cx).proto_client().request(request);
3979 cx.background_spawn(async move {
3980 let proto::ListRemoteDirectoryResponse {
3981 entries,
3982 entry_info,
3983 } = response.await?;
3984 Ok(entries
3985 .into_iter()
3986 .zip(entry_info)
3987 .map(|(entry, info)| DirectoryItem {
3988 path: PathBuf::from(entry),
3989 is_dir: info.is_dir,
3990 })
3991 .collect())
3992 })
3993 } else {
3994 Task::ready(Err(anyhow!("cannot list directory in remote project")))
3995 }
3996 }
3997
3998 pub fn create_worktree(
3999 &mut self,
4000 abs_path: impl AsRef<Path>,
4001 visible: bool,
4002 cx: &mut Context<Self>,
4003 ) -> Task<Result<Entity<Worktree>>> {
4004 self.worktree_store.update(cx, |worktree_store, cx| {
4005 worktree_store.create_worktree(abs_path, visible, cx)
4006 })
4007 }
4008
4009 pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut Context<Self>) {
4010 self.worktree_store.update(cx, |worktree_store, cx| {
4011 worktree_store.remove_worktree(id_to_remove, cx);
4012 });
4013 }
4014
4015 fn add_worktree(&mut self, worktree: &Entity<Worktree>, cx: &mut Context<Self>) {
4016 self.worktree_store.update(cx, |worktree_store, cx| {
4017 worktree_store.add(worktree, cx);
4018 });
4019 }
4020
4021 pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut Context<Self>) {
4022 let new_active_entry = entry.and_then(|project_path| {
4023 let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
4024 let entry = worktree.read(cx).entry_for_path(project_path.path)?;
4025 Some(entry.id)
4026 });
4027 if new_active_entry != self.active_entry {
4028 self.active_entry = new_active_entry;
4029 self.lsp_store.update(cx, |lsp_store, _| {
4030 lsp_store.set_active_entry(new_active_entry);
4031 });
4032 cx.emit(Event::ActiveEntryChanged(new_active_entry));
4033 }
4034 }
4035
4036 pub fn language_servers_running_disk_based_diagnostics<'a>(
4037 &'a self,
4038 cx: &'a App,
4039 ) -> impl Iterator<Item = LanguageServerId> + 'a {
4040 self.lsp_store
4041 .read(cx)
4042 .language_servers_running_disk_based_diagnostics()
4043 }
4044
4045 pub fn diagnostic_summary(&self, include_ignored: bool, cx: &App) -> DiagnosticSummary {
4046 self.lsp_store
4047 .read(cx)
4048 .diagnostic_summary(include_ignored, cx)
4049 }
4050
4051 pub fn diagnostic_summaries<'a>(
4052 &'a self,
4053 include_ignored: bool,
4054 cx: &'a App,
4055 ) -> impl Iterator<Item = (ProjectPath, LanguageServerId, DiagnosticSummary)> + 'a {
4056 self.lsp_store
4057 .read(cx)
4058 .diagnostic_summaries(include_ignored, cx)
4059 }
4060
4061 pub fn active_entry(&self) -> Option<ProjectEntryId> {
4062 self.active_entry
4063 }
4064
4065 pub fn entry_for_path(&self, path: &ProjectPath, cx: &App) -> Option<Entry> {
4066 self.worktree_store.read(cx).entry_for_path(path, cx)
4067 }
4068
4069 pub fn path_for_entry(&self, entry_id: ProjectEntryId, cx: &App) -> Option<ProjectPath> {
4070 let worktree = self.worktree_for_entry(entry_id, cx)?;
4071 let worktree = worktree.read(cx);
4072 let worktree_id = worktree.id();
4073 let path = worktree.entry_for_id(entry_id)?.path.clone();
4074 Some(ProjectPath { worktree_id, path })
4075 }
4076
4077 pub fn absolute_path(&self, project_path: &ProjectPath, cx: &App) -> Option<PathBuf> {
4078 self.worktree_for_id(project_path.worktree_id, cx)?
4079 .read(cx)
4080 .absolutize(&project_path.path)
4081 .ok()
4082 }
4083
4084 /// Attempts to find a `ProjectPath` corresponding to the given path. If the path
4085 /// is a *full path*, meaning it starts with the root name of a worktree, we'll locate
4086 /// it in that worktree. Otherwise, we'll attempt to find it as a relative path in
4087 /// the first visible worktree that has an entry for that relative path.
4088 ///
4089 /// We use this to resolve edit steps, when there's a chance an LLM may omit the workree
4090 /// root name from paths.
4091 ///
4092 /// # Arguments
4093 ///
4094 /// * `path` - A full path that starts with a worktree root name, or alternatively a
4095 /// relative path within a visible worktree.
4096 /// * `cx` - A reference to the `AppContext`.
4097 ///
4098 /// # Returns
4099 ///
4100 /// Returns `Some(ProjectPath)` if a matching worktree is found, otherwise `None`.
4101 pub fn find_project_path(&self, path: impl AsRef<Path>, cx: &App) -> Option<ProjectPath> {
4102 let path = path.as_ref();
4103 let worktree_store = self.worktree_store.read(cx);
4104
4105 for worktree in worktree_store.visible_worktrees(cx) {
4106 let worktree_root_name = worktree.read(cx).root_name();
4107 if let Ok(relative_path) = path.strip_prefix(worktree_root_name) {
4108 return Some(ProjectPath {
4109 worktree_id: worktree.read(cx).id(),
4110 path: relative_path.into(),
4111 });
4112 }
4113 }
4114
4115 for worktree in worktree_store.visible_worktrees(cx) {
4116 let worktree = worktree.read(cx);
4117 if let Some(entry) = worktree.entry_for_path(path) {
4118 return Some(ProjectPath {
4119 worktree_id: worktree.id(),
4120 path: entry.path.clone(),
4121 });
4122 }
4123 }
4124
4125 None
4126 }
4127
4128 pub fn project_path_for_absolute_path(&self, abs_path: &Path, cx: &App) -> Option<ProjectPath> {
4129 self.find_worktree(abs_path, cx)
4130 .map(|(worktree, relative_path)| ProjectPath {
4131 worktree_id: worktree.read(cx).id(),
4132 path: relative_path.into(),
4133 })
4134 }
4135
4136 pub fn get_workspace_root(&self, project_path: &ProjectPath, cx: &App) -> Option<PathBuf> {
4137 Some(
4138 self.worktree_for_id(project_path.worktree_id, cx)?
4139 .read(cx)
4140 .abs_path()
4141 .to_path_buf(),
4142 )
4143 }
4144
4145 pub fn blame_buffer(
4146 &self,
4147 buffer: &Entity<Buffer>,
4148 version: Option<clock::Global>,
4149 cx: &App,
4150 ) -> Task<Result<Option<Blame>>> {
4151 self.git_store.read(cx).blame_buffer(buffer, version, cx)
4152 }
4153
4154 pub fn get_permalink_to_line(
4155 &self,
4156 buffer: &Entity<Buffer>,
4157 selection: Range<u32>,
4158 cx: &App,
4159 ) -> Task<Result<url::Url>> {
4160 self.git_store
4161 .read(cx)
4162 .get_permalink_to_line(buffer, selection, cx)
4163 }
4164
4165 // RPC message handlers
4166
4167 async fn handle_unshare_project(
4168 this: Entity<Self>,
4169 _: TypedEnvelope<proto::UnshareProject>,
4170 mut cx: AsyncApp,
4171 ) -> Result<()> {
4172 this.update(&mut cx, |this, cx| {
4173 if this.is_local() || this.is_via_ssh() {
4174 this.unshare(cx)?;
4175 } else {
4176 this.disconnected_from_host(cx);
4177 }
4178 Ok(())
4179 })?
4180 }
4181
4182 async fn handle_add_collaborator(
4183 this: Entity<Self>,
4184 mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
4185 mut cx: AsyncApp,
4186 ) -> Result<()> {
4187 let collaborator = envelope
4188 .payload
4189 .collaborator
4190 .take()
4191 .ok_or_else(|| anyhow!("empty collaborator"))?;
4192
4193 let collaborator = Collaborator::from_proto(collaborator)?;
4194 this.update(&mut cx, |this, cx| {
4195 this.buffer_store.update(cx, |buffer_store, _| {
4196 buffer_store.forget_shared_buffers_for(&collaborator.peer_id);
4197 });
4198 this.breakpoint_store.read(cx).broadcast();
4199 cx.emit(Event::CollaboratorJoined(collaborator.peer_id));
4200 this.collaborators
4201 .insert(collaborator.peer_id, collaborator);
4202 })?;
4203
4204 Ok(())
4205 }
4206
4207 async fn handle_update_project_collaborator(
4208 this: Entity<Self>,
4209 envelope: TypedEnvelope<proto::UpdateProjectCollaborator>,
4210 mut cx: AsyncApp,
4211 ) -> Result<()> {
4212 let old_peer_id = envelope
4213 .payload
4214 .old_peer_id
4215 .ok_or_else(|| anyhow!("missing old peer id"))?;
4216 let new_peer_id = envelope
4217 .payload
4218 .new_peer_id
4219 .ok_or_else(|| anyhow!("missing new peer id"))?;
4220 this.update(&mut cx, |this, cx| {
4221 let collaborator = this
4222 .collaborators
4223 .remove(&old_peer_id)
4224 .ok_or_else(|| anyhow!("received UpdateProjectCollaborator for unknown peer"))?;
4225 let is_host = collaborator.is_host;
4226 this.collaborators.insert(new_peer_id, collaborator);
4227
4228 log::info!("peer {} became {}", old_peer_id, new_peer_id,);
4229 this.buffer_store.update(cx, |buffer_store, _| {
4230 buffer_store.update_peer_id(&old_peer_id, new_peer_id)
4231 });
4232
4233 if is_host {
4234 this.buffer_store
4235 .update(cx, |buffer_store, _| buffer_store.discard_incomplete());
4236 this.enqueue_buffer_ordered_message(BufferOrderedMessage::Resync)
4237 .unwrap();
4238 cx.emit(Event::HostReshared);
4239 }
4240
4241 cx.emit(Event::CollaboratorUpdated {
4242 old_peer_id,
4243 new_peer_id,
4244 });
4245 Ok(())
4246 })?
4247 }
4248
4249 async fn handle_remove_collaborator(
4250 this: Entity<Self>,
4251 envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
4252 mut cx: AsyncApp,
4253 ) -> Result<()> {
4254 this.update(&mut cx, |this, cx| {
4255 let peer_id = envelope
4256 .payload
4257 .peer_id
4258 .ok_or_else(|| anyhow!("invalid peer id"))?;
4259 let replica_id = this
4260 .collaborators
4261 .remove(&peer_id)
4262 .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?
4263 .replica_id;
4264 this.buffer_store.update(cx, |buffer_store, cx| {
4265 buffer_store.forget_shared_buffers_for(&peer_id);
4266 for buffer in buffer_store.buffers() {
4267 buffer.update(cx, |buffer, cx| buffer.remove_peer(replica_id, cx));
4268 }
4269 });
4270 this.git_store.update(cx, |git_store, _| {
4271 git_store.forget_shared_diffs_for(&peer_id);
4272 });
4273
4274 cx.emit(Event::CollaboratorLeft(peer_id));
4275 Ok(())
4276 })?
4277 }
4278
4279 async fn handle_update_project(
4280 this: Entity<Self>,
4281 envelope: TypedEnvelope<proto::UpdateProject>,
4282 mut cx: AsyncApp,
4283 ) -> Result<()> {
4284 this.update(&mut cx, |this, cx| {
4285 // Don't handle messages that were sent before the response to us joining the project
4286 if envelope.message_id > this.join_project_response_message_id {
4287 this.set_worktrees_from_proto(envelope.payload.worktrees, cx)?;
4288 }
4289 Ok(())
4290 })?
4291 }
4292
4293 async fn handle_toast(
4294 this: Entity<Self>,
4295 envelope: TypedEnvelope<proto::Toast>,
4296 mut cx: AsyncApp,
4297 ) -> Result<()> {
4298 this.update(&mut cx, |_, cx| {
4299 cx.emit(Event::Toast {
4300 notification_id: envelope.payload.notification_id.into(),
4301 message: envelope.payload.message,
4302 });
4303 Ok(())
4304 })?
4305 }
4306
4307 async fn handle_language_server_prompt_request(
4308 this: Entity<Self>,
4309 envelope: TypedEnvelope<proto::LanguageServerPromptRequest>,
4310 mut cx: AsyncApp,
4311 ) -> Result<proto::LanguageServerPromptResponse> {
4312 let (tx, mut rx) = smol::channel::bounded(1);
4313 let actions: Vec<_> = envelope
4314 .payload
4315 .actions
4316 .into_iter()
4317 .map(|action| MessageActionItem {
4318 title: action,
4319 properties: Default::default(),
4320 })
4321 .collect();
4322 this.update(&mut cx, |_, cx| {
4323 cx.emit(Event::LanguageServerPrompt(LanguageServerPromptRequest {
4324 level: proto_to_prompt(envelope.payload.level.context("Invalid prompt level")?),
4325 message: envelope.payload.message,
4326 actions: actions.clone(),
4327 lsp_name: envelope.payload.lsp_name,
4328 response_channel: tx,
4329 }));
4330
4331 anyhow::Ok(())
4332 })??;
4333
4334 // We drop `this` to avoid holding a reference in this future for too
4335 // long.
4336 // If we keep the reference, we might not drop the `Project` early
4337 // enough when closing a window and it will only get releases on the
4338 // next `flush_effects()` call.
4339 drop(this);
4340
4341 let mut rx = pin!(rx);
4342 let answer = rx.next().await;
4343
4344 Ok(LanguageServerPromptResponse {
4345 action_response: answer.and_then(|answer| {
4346 actions
4347 .iter()
4348 .position(|action| *action == answer)
4349 .map(|index| index as u64)
4350 }),
4351 })
4352 }
4353
4354 async fn handle_hide_toast(
4355 this: Entity<Self>,
4356 envelope: TypedEnvelope<proto::HideToast>,
4357 mut cx: AsyncApp,
4358 ) -> Result<()> {
4359 this.update(&mut cx, |_, cx| {
4360 cx.emit(Event::HideToast {
4361 notification_id: envelope.payload.notification_id.into(),
4362 });
4363 Ok(())
4364 })?
4365 }
4366
4367 // Collab sends UpdateWorktree protos as messages
4368 async fn handle_update_worktree(
4369 this: Entity<Self>,
4370 envelope: TypedEnvelope<proto::UpdateWorktree>,
4371 mut cx: AsyncApp,
4372 ) -> Result<()> {
4373 this.update(&mut cx, |this, cx| {
4374 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
4375 if let Some(worktree) = this.worktree_for_id(worktree_id, cx) {
4376 worktree.update(cx, |worktree, _| {
4377 let worktree = worktree.as_remote_mut().unwrap();
4378 worktree.update_from_remote(envelope.payload);
4379 });
4380 }
4381 Ok(())
4382 })?
4383 }
4384
4385 async fn handle_update_buffer_from_ssh(
4386 this: Entity<Self>,
4387 envelope: TypedEnvelope<proto::UpdateBuffer>,
4388 cx: AsyncApp,
4389 ) -> Result<proto::Ack> {
4390 let buffer_store = this.read_with(&cx, |this, cx| {
4391 if let Some(remote_id) = this.remote_id() {
4392 let mut payload = envelope.payload.clone();
4393 payload.project_id = remote_id;
4394 cx.background_spawn(this.client.request(payload))
4395 .detach_and_log_err(cx);
4396 }
4397 this.buffer_store.clone()
4398 })?;
4399 BufferStore::handle_update_buffer(buffer_store, envelope, cx).await
4400 }
4401
4402 async fn handle_update_buffer(
4403 this: Entity<Self>,
4404 envelope: TypedEnvelope<proto::UpdateBuffer>,
4405 cx: AsyncApp,
4406 ) -> Result<proto::Ack> {
4407 let buffer_store = this.read_with(&cx, |this, cx| {
4408 if let Some(ssh) = &this.ssh_client {
4409 let mut payload = envelope.payload.clone();
4410 payload.project_id = SSH_PROJECT_ID;
4411 cx.background_spawn(ssh.read(cx).proto_client().request(payload))
4412 .detach_and_log_err(cx);
4413 }
4414 this.buffer_store.clone()
4415 })?;
4416 BufferStore::handle_update_buffer(buffer_store, envelope, cx).await
4417 }
4418
4419 fn retain_remotely_created_models(
4420 &mut self,
4421 cx: &mut Context<Self>,
4422 ) -> RemotelyCreatedModelGuard {
4423 {
4424 let mut remotely_create_models = self.remotely_created_models.lock();
4425 if remotely_create_models.retain_count == 0 {
4426 remotely_create_models.buffers = self.buffer_store.read(cx).buffers().collect();
4427 remotely_create_models.worktrees =
4428 self.worktree_store.read(cx).worktrees().collect();
4429 }
4430 remotely_create_models.retain_count += 1;
4431 }
4432 RemotelyCreatedModelGuard {
4433 remote_models: Arc::downgrade(&self.remotely_created_models),
4434 }
4435 }
4436
4437 async fn handle_create_buffer_for_peer(
4438 this: Entity<Self>,
4439 envelope: TypedEnvelope<proto::CreateBufferForPeer>,
4440 mut cx: AsyncApp,
4441 ) -> Result<()> {
4442 this.update(&mut cx, |this, cx| {
4443 this.buffer_store.update(cx, |buffer_store, cx| {
4444 buffer_store.handle_create_buffer_for_peer(
4445 envelope,
4446 this.replica_id(),
4447 this.capability(),
4448 cx,
4449 )
4450 })
4451 })?
4452 }
4453
4454 async fn handle_synchronize_buffers(
4455 this: Entity<Self>,
4456 envelope: TypedEnvelope<proto::SynchronizeBuffers>,
4457 mut cx: AsyncApp,
4458 ) -> Result<proto::SynchronizeBuffersResponse> {
4459 let response = this.update(&mut cx, |this, cx| {
4460 let client = this.client.clone();
4461 this.buffer_store.update(cx, |this, cx| {
4462 this.handle_synchronize_buffers(envelope, cx, client)
4463 })
4464 })??;
4465
4466 Ok(response)
4467 }
4468
4469 async fn handle_search_candidate_buffers(
4470 this: Entity<Self>,
4471 envelope: TypedEnvelope<proto::FindSearchCandidates>,
4472 mut cx: AsyncApp,
4473 ) -> Result<proto::FindSearchCandidatesResponse> {
4474 let peer_id = envelope.original_sender_id()?;
4475 let message = envelope.payload;
4476 let query = SearchQuery::from_proto(
4477 message
4478 .query
4479 .ok_or_else(|| anyhow!("missing query field"))?,
4480 )?;
4481 let results = this.update(&mut cx, |this, cx| {
4482 this.find_search_candidate_buffers(&query, message.limit as _, cx)
4483 })?;
4484
4485 let mut response = proto::FindSearchCandidatesResponse {
4486 buffer_ids: Vec::new(),
4487 };
4488
4489 while let Ok(buffer) = results.recv().await {
4490 this.update(&mut cx, |this, cx| {
4491 let buffer_id = this.create_buffer_for_peer(&buffer, peer_id, cx);
4492 response.buffer_ids.push(buffer_id.to_proto());
4493 })?;
4494 }
4495
4496 Ok(response)
4497 }
4498
4499 async fn handle_open_buffer_by_id(
4500 this: Entity<Self>,
4501 envelope: TypedEnvelope<proto::OpenBufferById>,
4502 mut cx: AsyncApp,
4503 ) -> Result<proto::OpenBufferResponse> {
4504 let peer_id = envelope.original_sender_id()?;
4505 let buffer_id = BufferId::new(envelope.payload.id)?;
4506 let buffer = this
4507 .update(&mut cx, |this, cx| this.open_buffer_by_id(buffer_id, cx))?
4508 .await?;
4509 Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
4510 }
4511
4512 async fn handle_open_buffer_by_path(
4513 this: Entity<Self>,
4514 envelope: TypedEnvelope<proto::OpenBufferByPath>,
4515 mut cx: AsyncApp,
4516 ) -> Result<proto::OpenBufferResponse> {
4517 let peer_id = envelope.original_sender_id()?;
4518 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
4519 let open_buffer = this.update(&mut cx, |this, cx| {
4520 this.open_buffer(
4521 ProjectPath {
4522 worktree_id,
4523 path: Arc::<Path>::from_proto(envelope.payload.path),
4524 },
4525 cx,
4526 )
4527 })?;
4528
4529 let buffer = open_buffer.await?;
4530 Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
4531 }
4532
4533 async fn handle_open_new_buffer(
4534 this: Entity<Self>,
4535 envelope: TypedEnvelope<proto::OpenNewBuffer>,
4536 mut cx: AsyncApp,
4537 ) -> Result<proto::OpenBufferResponse> {
4538 let buffer = this
4539 .update(&mut cx, |this, cx| this.create_buffer(cx))?
4540 .await?;
4541 let peer_id = envelope.original_sender_id()?;
4542
4543 Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
4544 }
4545
4546 fn respond_to_open_buffer_request(
4547 this: Entity<Self>,
4548 buffer: Entity<Buffer>,
4549 peer_id: proto::PeerId,
4550 cx: &mut AsyncApp,
4551 ) -> Result<proto::OpenBufferResponse> {
4552 this.update(cx, |this, cx| {
4553 let is_private = buffer
4554 .read(cx)
4555 .file()
4556 .map(|f| f.is_private())
4557 .unwrap_or_default();
4558 if is_private {
4559 Err(anyhow!(ErrorCode::UnsharedItem))
4560 } else {
4561 Ok(proto::OpenBufferResponse {
4562 buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx).into(),
4563 })
4564 }
4565 })?
4566 }
4567
4568 fn create_buffer_for_peer(
4569 &mut self,
4570 buffer: &Entity<Buffer>,
4571 peer_id: proto::PeerId,
4572 cx: &mut App,
4573 ) -> BufferId {
4574 self.buffer_store
4575 .update(cx, |buffer_store, cx| {
4576 buffer_store.create_buffer_for_peer(buffer, peer_id, cx)
4577 })
4578 .detach_and_log_err(cx);
4579 buffer.read(cx).remote_id()
4580 }
4581
4582 fn synchronize_remote_buffers(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
4583 let project_id = match self.client_state {
4584 ProjectClientState::Remote {
4585 sharing_has_stopped,
4586 remote_id,
4587 ..
4588 } => {
4589 if sharing_has_stopped {
4590 return Task::ready(Err(anyhow!(
4591 "can't synchronize remote buffers on a readonly project"
4592 )));
4593 } else {
4594 remote_id
4595 }
4596 }
4597 ProjectClientState::Shared { .. } | ProjectClientState::Local => {
4598 return Task::ready(Err(anyhow!(
4599 "can't synchronize remote buffers on a local project"
4600 )))
4601 }
4602 };
4603
4604 let client = self.client.clone();
4605 cx.spawn(async move |this, cx| {
4606 let (buffers, incomplete_buffer_ids) = this.update(cx, |this, cx| {
4607 this.buffer_store.read(cx).buffer_version_info(cx)
4608 })?;
4609 let response = client
4610 .request(proto::SynchronizeBuffers {
4611 project_id,
4612 buffers,
4613 })
4614 .await?;
4615
4616 let send_updates_for_buffers = this.update(cx, |this, cx| {
4617 response
4618 .buffers
4619 .into_iter()
4620 .map(|buffer| {
4621 let client = client.clone();
4622 let buffer_id = match BufferId::new(buffer.id) {
4623 Ok(id) => id,
4624 Err(e) => {
4625 return Task::ready(Err(e));
4626 }
4627 };
4628 let remote_version = language::proto::deserialize_version(&buffer.version);
4629 if let Some(buffer) = this.buffer_for_id(buffer_id, cx) {
4630 let operations =
4631 buffer.read(cx).serialize_ops(Some(remote_version), cx);
4632 cx.background_spawn(async move {
4633 let operations = operations.await;
4634 for chunk in split_operations(operations) {
4635 client
4636 .request(proto::UpdateBuffer {
4637 project_id,
4638 buffer_id: buffer_id.into(),
4639 operations: chunk,
4640 })
4641 .await?;
4642 }
4643 anyhow::Ok(())
4644 })
4645 } else {
4646 Task::ready(Ok(()))
4647 }
4648 })
4649 .collect::<Vec<_>>()
4650 })?;
4651
4652 // Any incomplete buffers have open requests waiting. Request that the host sends
4653 // creates these buffers for us again to unblock any waiting futures.
4654 for id in incomplete_buffer_ids {
4655 cx.background_spawn(client.request(proto::OpenBufferById {
4656 project_id,
4657 id: id.into(),
4658 }))
4659 .detach();
4660 }
4661
4662 futures::future::join_all(send_updates_for_buffers)
4663 .await
4664 .into_iter()
4665 .collect()
4666 })
4667 }
4668
4669 pub fn worktree_metadata_protos(&self, cx: &App) -> Vec<proto::WorktreeMetadata> {
4670 self.worktree_store.read(cx).worktree_metadata_protos(cx)
4671 }
4672
4673 /// Iterator of all open buffers that have unsaved changes
4674 pub fn dirty_buffers<'a>(&'a self, cx: &'a App) -> impl Iterator<Item = ProjectPath> + 'a {
4675 self.buffer_store.read(cx).buffers().filter_map(|buf| {
4676 let buf = buf.read(cx);
4677 if buf.is_dirty() {
4678 buf.project_path(cx)
4679 } else {
4680 None
4681 }
4682 })
4683 }
4684
4685 fn set_worktrees_from_proto(
4686 &mut self,
4687 worktrees: Vec<proto::WorktreeMetadata>,
4688 cx: &mut Context<Project>,
4689 ) -> Result<()> {
4690 self.worktree_store.update(cx, |worktree_store, cx| {
4691 worktree_store.set_worktrees_from_proto(worktrees, self.replica_id(), cx)
4692 })
4693 }
4694
4695 fn set_collaborators_from_proto(
4696 &mut self,
4697 messages: Vec<proto::Collaborator>,
4698 cx: &mut Context<Self>,
4699 ) -> Result<()> {
4700 let mut collaborators = HashMap::default();
4701 for message in messages {
4702 let collaborator = Collaborator::from_proto(message)?;
4703 collaborators.insert(collaborator.peer_id, collaborator);
4704 }
4705 for old_peer_id in self.collaborators.keys() {
4706 if !collaborators.contains_key(old_peer_id) {
4707 cx.emit(Event::CollaboratorLeft(*old_peer_id));
4708 }
4709 }
4710 self.collaborators = collaborators;
4711 Ok(())
4712 }
4713
4714 pub fn supplementary_language_servers<'a>(
4715 &'a self,
4716 cx: &'a App,
4717 ) -> impl 'a + Iterator<Item = (LanguageServerId, LanguageServerName)> {
4718 self.lsp_store.read(cx).supplementary_language_servers()
4719 }
4720
4721 pub fn any_language_server_supports_inlay_hints(&self, buffer: &Buffer, cx: &mut App) -> bool {
4722 self.lsp_store.update(cx, |this, cx| {
4723 this.language_servers_for_local_buffer(buffer, cx)
4724 .any(
4725 |(_, server)| match server.capabilities().inlay_hint_provider {
4726 Some(lsp::OneOf::Left(enabled)) => enabled,
4727 Some(lsp::OneOf::Right(_)) => true,
4728 None => false,
4729 },
4730 )
4731 })
4732 }
4733
4734 pub fn language_server_id_for_name(
4735 &self,
4736 buffer: &Buffer,
4737 name: &str,
4738 cx: &mut App,
4739 ) -> Option<LanguageServerId> {
4740 self.lsp_store.update(cx, |this, cx| {
4741 this.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 }
4751
4752 pub fn has_language_servers_for(&self, buffer: &Buffer, cx: &mut App) -> bool {
4753 self.lsp_store.update(cx, |this, cx| {
4754 this.language_servers_for_local_buffer(buffer, cx)
4755 .next()
4756 .is_some()
4757 })
4758 }
4759
4760 pub fn git_init(
4761 &self,
4762 path: Arc<Path>,
4763 fallback_branch_name: String,
4764 cx: &App,
4765 ) -> Task<Result<()>> {
4766 self.git_store
4767 .read(cx)
4768 .git_init(path, fallback_branch_name, cx)
4769 }
4770
4771 pub fn buffer_store(&self) -> &Entity<BufferStore> {
4772 &self.buffer_store
4773 }
4774
4775 pub fn git_store(&self) -> &Entity<GitStore> {
4776 &self.git_store
4777 }
4778
4779 pub fn active_repository(&self, cx: &App) -> Option<Entity<Repository>> {
4780 self.git_store.read(cx).active_repository()
4781 }
4782
4783 pub fn repositories<'a>(&self, cx: &'a App) -> &'a HashMap<ProjectEntryId, Entity<Repository>> {
4784 self.git_store.read(cx).repositories()
4785 }
4786
4787 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
4788 self.git_store.read(cx).status_for_buffer_id(buffer_id, cx)
4789 }
4790}
4791
4792pub struct PathMatchCandidateSet {
4793 pub snapshot: Snapshot,
4794 pub include_ignored: bool,
4795 pub include_root_name: bool,
4796 pub candidates: Candidates,
4797}
4798
4799pub enum Candidates {
4800 /// Only consider directories.
4801 Directories,
4802 /// Only consider files.
4803 Files,
4804 /// Consider directories and files.
4805 Entries,
4806}
4807
4808impl<'a> fuzzy::PathMatchCandidateSet<'a> for PathMatchCandidateSet {
4809 type Candidates = PathMatchCandidateSetIter<'a>;
4810
4811 fn id(&self) -> usize {
4812 self.snapshot.id().to_usize()
4813 }
4814
4815 fn len(&self) -> usize {
4816 match self.candidates {
4817 Candidates::Files => {
4818 if self.include_ignored {
4819 self.snapshot.file_count()
4820 } else {
4821 self.snapshot.visible_file_count()
4822 }
4823 }
4824
4825 Candidates::Directories => {
4826 if self.include_ignored {
4827 self.snapshot.dir_count()
4828 } else {
4829 self.snapshot.visible_dir_count()
4830 }
4831 }
4832
4833 Candidates::Entries => {
4834 if self.include_ignored {
4835 self.snapshot.entry_count()
4836 } else {
4837 self.snapshot.visible_entry_count()
4838 }
4839 }
4840 }
4841 }
4842
4843 fn prefix(&self) -> Arc<str> {
4844 if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
4845 self.snapshot.root_name().into()
4846 } else if self.include_root_name {
4847 format!("{}{}", self.snapshot.root_name(), std::path::MAIN_SEPARATOR).into()
4848 } else {
4849 Arc::default()
4850 }
4851 }
4852
4853 fn candidates(&'a self, start: usize) -> Self::Candidates {
4854 PathMatchCandidateSetIter {
4855 traversal: match self.candidates {
4856 Candidates::Directories => self.snapshot.directories(self.include_ignored, start),
4857 Candidates::Files => self.snapshot.files(self.include_ignored, start),
4858 Candidates::Entries => self.snapshot.entries(self.include_ignored, start),
4859 },
4860 }
4861 }
4862}
4863
4864pub struct PathMatchCandidateSetIter<'a> {
4865 traversal: Traversal<'a>,
4866}
4867
4868impl<'a> Iterator for PathMatchCandidateSetIter<'a> {
4869 type Item = fuzzy::PathMatchCandidate<'a>;
4870
4871 fn next(&mut self) -> Option<Self::Item> {
4872 self.traversal
4873 .next()
4874 .map(|entry| fuzzy::PathMatchCandidate {
4875 is_dir: entry.kind.is_dir(),
4876 path: &entry.path,
4877 char_bag: entry.char_bag,
4878 })
4879 }
4880}
4881
4882impl EventEmitter<Event> for Project {}
4883
4884impl<'a> From<&'a ProjectPath> for SettingsLocation<'a> {
4885 fn from(val: &'a ProjectPath) -> Self {
4886 SettingsLocation {
4887 worktree_id: val.worktree_id,
4888 path: val.path.as_ref(),
4889 }
4890 }
4891}
4892
4893impl<P: AsRef<Path>> From<(WorktreeId, P)> for ProjectPath {
4894 fn from((worktree_id, path): (WorktreeId, P)) -> Self {
4895 Self {
4896 worktree_id,
4897 path: path.as_ref().into(),
4898 }
4899 }
4900}
4901
4902pub fn relativize_path(base: &Path, path: &Path) -> PathBuf {
4903 let mut path_components = path.components();
4904 let mut base_components = base.components();
4905 let mut components: Vec<Component> = Vec::new();
4906 loop {
4907 match (path_components.next(), base_components.next()) {
4908 (None, None) => break,
4909 (Some(a), None) => {
4910 components.push(a);
4911 components.extend(path_components.by_ref());
4912 break;
4913 }
4914 (None, _) => components.push(Component::ParentDir),
4915 (Some(a), Some(b)) if components.is_empty() && a == b => (),
4916 (Some(a), Some(Component::CurDir)) => components.push(a),
4917 (Some(a), Some(_)) => {
4918 components.push(Component::ParentDir);
4919 for _ in base_components {
4920 components.push(Component::ParentDir);
4921 }
4922 components.push(a);
4923 components.extend(path_components.by_ref());
4924 break;
4925 }
4926 }
4927 }
4928 components.iter().map(|c| c.as_os_str()).collect()
4929}
4930
4931fn resolve_path(base: &Path, path: &Path) -> PathBuf {
4932 let mut result = base.to_path_buf();
4933 for component in path.components() {
4934 match component {
4935 Component::ParentDir => {
4936 result.pop();
4937 }
4938 Component::CurDir => (),
4939 _ => result.push(component),
4940 }
4941 }
4942 result
4943}
4944
4945/// ResolvedPath is a path that has been resolved to either a ProjectPath
4946/// or an AbsPath and that *exists*.
4947#[derive(Debug, Clone)]
4948pub enum ResolvedPath {
4949 ProjectPath {
4950 project_path: ProjectPath,
4951 is_dir: bool,
4952 },
4953 AbsPath {
4954 path: PathBuf,
4955 is_dir: bool,
4956 },
4957}
4958
4959impl ResolvedPath {
4960 pub fn abs_path(&self) -> Option<&Path> {
4961 match self {
4962 Self::AbsPath { path, .. } => Some(path.as_path()),
4963 _ => None,
4964 }
4965 }
4966
4967 pub fn project_path(&self) -> Option<&ProjectPath> {
4968 match self {
4969 Self::ProjectPath { project_path, .. } => Some(&project_path),
4970 _ => None,
4971 }
4972 }
4973
4974 pub fn is_file(&self) -> bool {
4975 !self.is_dir()
4976 }
4977
4978 pub fn is_dir(&self) -> bool {
4979 match self {
4980 Self::ProjectPath { is_dir, .. } => *is_dir,
4981 Self::AbsPath { is_dir, .. } => *is_dir,
4982 }
4983 }
4984}
4985
4986impl ProjectItem for Buffer {
4987 fn try_open(
4988 project: &Entity<Project>,
4989 path: &ProjectPath,
4990 cx: &mut App,
4991 ) -> Option<Task<Result<Entity<Self>>>> {
4992 Some(project.update(cx, |project, cx| project.open_buffer(path.clone(), cx)))
4993 }
4994
4995 fn entry_id(&self, cx: &App) -> Option<ProjectEntryId> {
4996 File::from_dyn(self.file()).and_then(|file| file.project_entry_id(cx))
4997 }
4998
4999 fn project_path(&self, cx: &App) -> Option<ProjectPath> {
5000 File::from_dyn(self.file()).map(|file| ProjectPath {
5001 worktree_id: file.worktree_id(cx),
5002 path: file.path().clone(),
5003 })
5004 }
5005
5006 fn is_dirty(&self) -> bool {
5007 self.is_dirty()
5008 }
5009}
5010
5011impl Completion {
5012 /// A key that can be used to sort completions when displaying
5013 /// them to the user.
5014 pub fn sort_key(&self) -> (usize, &str) {
5015 const DEFAULT_KIND_KEY: usize = 2;
5016 let kind_key = self
5017 .source
5018 // `lsp::CompletionListItemDefaults` has no `kind` field
5019 .lsp_completion(false)
5020 .and_then(|lsp_completion| lsp_completion.kind)
5021 .and_then(|lsp_completion_kind| match lsp_completion_kind {
5022 lsp::CompletionItemKind::KEYWORD => Some(0),
5023 lsp::CompletionItemKind::VARIABLE => Some(1),
5024 _ => None,
5025 })
5026 .unwrap_or(DEFAULT_KIND_KEY);
5027 (kind_key, &self.label.text[self.label.filter_range.clone()])
5028 }
5029
5030 /// Whether this completion is a snippet.
5031 pub fn is_snippet(&self) -> bool {
5032 self.source
5033 // `lsp::CompletionListItemDefaults` has `insert_text_format` field
5034 .lsp_completion(true)
5035 .map_or(false, |lsp_completion| {
5036 lsp_completion.insert_text_format == Some(lsp::InsertTextFormat::SNIPPET)
5037 })
5038 }
5039
5040 /// Returns the corresponding color for this completion.
5041 ///
5042 /// Will return `None` if this completion's kind is not [`CompletionItemKind::COLOR`].
5043 pub fn color(&self) -> Option<Hsla> {
5044 // `lsp::CompletionListItemDefaults` has no `kind` field
5045 let lsp_completion = self.source.lsp_completion(false)?;
5046 if lsp_completion.kind? == CompletionItemKind::COLOR {
5047 return color_extractor::extract_color(&lsp_completion);
5048 }
5049 None
5050 }
5051}
5052
5053pub fn sort_worktree_entries(entries: &mut [impl AsRef<Entry>]) {
5054 entries.sort_by(|entry_a, entry_b| {
5055 let entry_a = entry_a.as_ref();
5056 let entry_b = entry_b.as_ref();
5057 compare_paths(
5058 (&entry_a.path, entry_a.is_file()),
5059 (&entry_b.path, entry_b.is_file()),
5060 )
5061 });
5062}
5063
5064fn proto_to_prompt(level: proto::language_server_prompt_request::Level) -> gpui::PromptLevel {
5065 match level {
5066 proto::language_server_prompt_request::Level::Info(_) => gpui::PromptLevel::Info,
5067 proto::language_server_prompt_request::Level::Warning(_) => gpui::PromptLevel::Warning,
5068 proto::language_server_prompt_request::Level::Critical(_) => gpui::PromptLevel::Critical,
5069 }
5070}