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