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