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