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