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