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