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