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