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