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