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