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