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