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