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