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