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