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