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