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