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