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