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