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