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 git_store::GitStore,
39 lsp_store::{SymbolLocation, log_store::LogKind},
40};
41pub use agent_server_store::{AgentServerStore, AgentServersUpdated, ExternalAgentServerName};
42pub use git_store::{
43 ConflictRegion, ConflictSet, ConflictSetSnapshot, ConflictSetUpdate,
44 git_traversal::{ChildEntriesGitIter, GitEntry, GitEntryRef, GitTraversal},
45};
46pub use manifest_tree::ManifestTree;
47
48use anyhow::{Context as _, Result, anyhow};
49use buffer_store::{BufferStore, BufferStoreEvent};
50use client::{Client, Collaborator, PendingEntitySubscription, TypedEnvelope, UserStore, proto};
51use clock::ReplicaId;
52
53use dap::client::DebugAdapterClient;
54
55use collections::{BTreeSet, HashMap, HashSet, IndexSet};
56use debounced_delay::DebouncedDelay;
57pub use debugger::breakpoint_store::BreakpointWithPosition;
58use debugger::{
59 breakpoint_store::{ActiveStackFrame, BreakpointStore},
60 dap_store::{DapStore, DapStoreEvent},
61 session::Session,
62};
63pub use environment::ProjectEnvironment;
64#[cfg(test)]
65use futures::future::join_all;
66use futures::{
67 StreamExt,
68 channel::mpsc::{self, UnboundedReceiver},
69 future::try_join_all,
70};
71pub use image_store::{ImageItem, ImageStore};
72use image_store::{ImageItemEvent, ImageStoreEvent};
73
74use ::git::{blame::Blame, status::FileStatus};
75use gpui::{
76 App, AppContext, AsyncApp, BorrowAppContext, Context, Entity, EventEmitter, Hsla, SharedString,
77 Task, WeakEntity, Window,
78};
79use language::{
80 Buffer, BufferEvent, Capability, CodeLabel, CursorShape, Language, LanguageName,
81 LanguageRegistry, PointUtf16, ToOffset, ToPointUtf16, Toolchain, ToolchainMetadata,
82 ToolchainScope, Transaction, Unclipped, language_settings::InlayHintKind,
83 proto::split_operations,
84};
85use lsp::{
86 CodeActionKind, CompletionContext, CompletionItemKind, DocumentHighlightKind, InsertTextMode,
87 LanguageServerId, LanguageServerName, LanguageServerSelector, MessageActionItem,
88};
89use lsp_command::*;
90use lsp_store::{CompletionDocumentation, LspFormatTarget, OpenLspBufferHandle};
91pub use manifest_tree::ManifestProvidersStore;
92use node_runtime::NodeRuntime;
93use parking_lot::Mutex;
94pub use prettier_store::PrettierStore;
95use project_settings::{ProjectSettings, SettingsObserver, SettingsObserverEvent};
96use remote::{RemoteClient, RemoteConnectionOptions};
97use rpc::{
98 AnyProtoClient, ErrorCode,
99 proto::{LanguageServerPromptResponse, REMOTE_SERVER_PROJECT_ID},
100};
101use search::{SearchInputKind, SearchQuery, SearchResult};
102use search_history::SearchHistory;
103use settings::{InvalidSettingsError, RegisterSetting, Settings, SettingsLocation, SettingsStore};
104use smol::channel::Receiver;
105use snippet::Snippet;
106pub use snippet_provider;
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 /// Text starting here and ending at the cursor will be used as the query for filtering this completion.
481 ///
482 /// If None, the start of the surrounding word is used.
483 pub match_start: Option<text::Anchor>,
484 /// Key used for de-duplicating snippets. If None, always considered unique.
485 pub snippet_deduplication_key: Option<(usize, usize)>,
486 /// Whether to adjust indentation (the default) or not.
487 pub insert_text_mode: Option<InsertTextMode>,
488 /// An optional callback to invoke when this completion is confirmed.
489 /// Returns, whether new completions should be retriggered after the current one.
490 /// If `true` is returned, the editor will show a new completion menu after this completion is confirmed.
491 /// if no confirmation is provided or `false` is returned, the completion will be committed.
492 pub confirm: Option<Arc<dyn Send + Sync + Fn(CompletionIntent, &mut Window, &mut App) -> bool>>,
493}
494
495#[derive(Debug, Clone)]
496pub enum CompletionSource {
497 Lsp {
498 /// The alternate `insert` range, if provided by the LSP server.
499 insert_range: Option<Range<Anchor>>,
500 /// The id of the language server that produced this completion.
501 server_id: LanguageServerId,
502 /// The raw completion provided by the language server.
503 lsp_completion: Box<lsp::CompletionItem>,
504 /// A set of defaults for this completion item.
505 lsp_defaults: Option<Arc<lsp::CompletionListItemDefaults>>,
506 /// Whether this completion has been resolved, to ensure it happens once per completion.
507 resolved: bool,
508 },
509 Dap {
510 /// The sort text for this completion.
511 sort_text: String,
512 },
513 Custom,
514 BufferWord {
515 word_range: Range<Anchor>,
516 resolved: bool,
517 },
518}
519
520impl CompletionSource {
521 pub fn server_id(&self) -> Option<LanguageServerId> {
522 if let CompletionSource::Lsp { server_id, .. } = self {
523 Some(*server_id)
524 } else {
525 None
526 }
527 }
528
529 pub fn lsp_completion(&self, apply_defaults: bool) -> Option<Cow<'_, lsp::CompletionItem>> {
530 if let Self::Lsp {
531 lsp_completion,
532 lsp_defaults,
533 ..
534 } = self
535 {
536 if apply_defaults && let Some(lsp_defaults) = lsp_defaults {
537 let mut completion_with_defaults = *lsp_completion.clone();
538 let default_commit_characters = lsp_defaults.commit_characters.as_ref();
539 let default_edit_range = lsp_defaults.edit_range.as_ref();
540 let default_insert_text_format = lsp_defaults.insert_text_format.as_ref();
541 let default_insert_text_mode = lsp_defaults.insert_text_mode.as_ref();
542
543 if default_commit_characters.is_some()
544 || default_edit_range.is_some()
545 || default_insert_text_format.is_some()
546 || default_insert_text_mode.is_some()
547 {
548 if completion_with_defaults.commit_characters.is_none()
549 && default_commit_characters.is_some()
550 {
551 completion_with_defaults.commit_characters =
552 default_commit_characters.cloned()
553 }
554 if completion_with_defaults.text_edit.is_none() {
555 match default_edit_range {
556 Some(lsp::CompletionListItemDefaultsEditRange::Range(range)) => {
557 completion_with_defaults.text_edit =
558 Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
559 range: *range,
560 new_text: completion_with_defaults.label.clone(),
561 }))
562 }
563 Some(lsp::CompletionListItemDefaultsEditRange::InsertAndReplace {
564 insert,
565 replace,
566 }) => {
567 completion_with_defaults.text_edit =
568 Some(lsp::CompletionTextEdit::InsertAndReplace(
569 lsp::InsertReplaceEdit {
570 new_text: completion_with_defaults.label.clone(),
571 insert: *insert,
572 replace: *replace,
573 },
574 ))
575 }
576 None => {}
577 }
578 }
579 if completion_with_defaults.insert_text_format.is_none()
580 && default_insert_text_format.is_some()
581 {
582 completion_with_defaults.insert_text_format =
583 default_insert_text_format.cloned()
584 }
585 if completion_with_defaults.insert_text_mode.is_none()
586 && default_insert_text_mode.is_some()
587 {
588 completion_with_defaults.insert_text_mode =
589 default_insert_text_mode.cloned()
590 }
591 }
592 return Some(Cow::Owned(completion_with_defaults));
593 }
594 Some(Cow::Borrowed(lsp_completion))
595 } else {
596 None
597 }
598 }
599}
600
601impl std::fmt::Debug for Completion {
602 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
603 f.debug_struct("Completion")
604 .field("replace_range", &self.replace_range)
605 .field("new_text", &self.new_text)
606 .field("label", &self.label)
607 .field("documentation", &self.documentation)
608 .field("source", &self.source)
609 .finish()
610 }
611}
612
613/// Response from a source of completions.
614pub struct CompletionResponse {
615 pub completions: Vec<Completion>,
616 pub display_options: CompletionDisplayOptions,
617 /// When false, indicates that the list is complete and so does not need to be re-queried if it
618 /// can be filtered instead.
619 pub is_incomplete: bool,
620}
621
622#[derive(Default)]
623pub struct CompletionDisplayOptions {
624 pub dynamic_width: bool,
625}
626
627impl CompletionDisplayOptions {
628 pub fn merge(&mut self, other: &CompletionDisplayOptions) {
629 self.dynamic_width = self.dynamic_width && other.dynamic_width;
630 }
631}
632
633/// Response from language server completion request.
634#[derive(Clone, Debug, Default)]
635pub(crate) struct CoreCompletionResponse {
636 pub completions: Vec<CoreCompletion>,
637 /// When false, indicates that the list is complete and so does not need to be re-queried if it
638 /// can be filtered instead.
639 pub is_incomplete: bool,
640}
641
642/// A generic completion that can come from different sources.
643#[derive(Clone, Debug)]
644pub(crate) struct CoreCompletion {
645 replace_range: Range<Anchor>,
646 new_text: String,
647 source: CompletionSource,
648}
649
650/// A code action provided by a language server.
651#[derive(Clone, Debug, PartialEq)]
652pub struct CodeAction {
653 /// The id of the language server that produced this code action.
654 pub server_id: LanguageServerId,
655 /// The range of the buffer where this code action is applicable.
656 pub range: Range<Anchor>,
657 /// The raw code action provided by the language server.
658 /// Can be either an action or a command.
659 pub lsp_action: LspAction,
660 /// Whether the action needs to be resolved using the language server.
661 pub resolved: bool,
662}
663
664/// An action sent back by a language server.
665#[derive(Clone, Debug, PartialEq)]
666pub enum LspAction {
667 /// An action with the full data, may have a command or may not.
668 /// May require resolving.
669 Action(Box<lsp::CodeAction>),
670 /// A command data to run as an action.
671 Command(lsp::Command),
672 /// A code lens data to run as an action.
673 CodeLens(lsp::CodeLens),
674}
675
676impl LspAction {
677 pub fn title(&self) -> &str {
678 match self {
679 Self::Action(action) => &action.title,
680 Self::Command(command) => &command.title,
681 Self::CodeLens(lens) => lens
682 .command
683 .as_ref()
684 .map(|command| command.title.as_str())
685 .unwrap_or("Unknown command"),
686 }
687 }
688
689 fn action_kind(&self) -> Option<lsp::CodeActionKind> {
690 match self {
691 Self::Action(action) => action.kind.clone(),
692 Self::Command(_) => Some(lsp::CodeActionKind::new("command")),
693 Self::CodeLens(_) => Some(lsp::CodeActionKind::new("code lens")),
694 }
695 }
696
697 fn edit(&self) -> Option<&lsp::WorkspaceEdit> {
698 match self {
699 Self::Action(action) => action.edit.as_ref(),
700 Self::Command(_) => None,
701 Self::CodeLens(_) => None,
702 }
703 }
704
705 fn command(&self) -> Option<&lsp::Command> {
706 match self {
707 Self::Action(action) => action.command.as_ref(),
708 Self::Command(command) => Some(command),
709 Self::CodeLens(lens) => lens.command.as_ref(),
710 }
711 }
712}
713
714#[derive(Debug, Clone, PartialEq, Eq)]
715pub enum ResolveState {
716 Resolved,
717 CanResolve(LanguageServerId, Option<lsp::LSPAny>),
718 Resolving,
719}
720impl InlayHint {
721 pub fn text(&self) -> Rope {
722 match &self.label {
723 InlayHintLabel::String(s) => Rope::from(s),
724 InlayHintLabel::LabelParts(parts) => parts.iter().map(|part| &*part.value).collect(),
725 }
726 }
727}
728
729#[derive(Debug, Clone, PartialEq, Eq)]
730pub enum InlayHintLabel {
731 String(String),
732 LabelParts(Vec<InlayHintLabelPart>),
733}
734
735#[derive(Debug, Clone, PartialEq, Eq)]
736pub struct InlayHintLabelPart {
737 pub value: String,
738 pub tooltip: Option<InlayHintLabelPartTooltip>,
739 pub location: Option<(LanguageServerId, lsp::Location)>,
740}
741
742#[derive(Debug, Clone, PartialEq, Eq)]
743pub enum InlayHintTooltip {
744 String(String),
745 MarkupContent(MarkupContent),
746}
747
748#[derive(Debug, Clone, PartialEq, Eq)]
749pub enum InlayHintLabelPartTooltip {
750 String(String),
751 MarkupContent(MarkupContent),
752}
753
754#[derive(Debug, Clone, PartialEq, Eq)]
755pub struct MarkupContent {
756 pub kind: HoverBlockKind,
757 pub value: String,
758}
759
760#[derive(Debug, Clone, PartialEq)]
761pub struct LocationLink {
762 pub origin: Option<Location>,
763 pub target: Location,
764}
765
766#[derive(Debug)]
767pub struct DocumentHighlight {
768 pub range: Range<language::Anchor>,
769 pub kind: DocumentHighlightKind,
770}
771
772#[derive(Clone, Debug)]
773pub struct Symbol {
774 pub language_server_name: LanguageServerName,
775 pub source_worktree_id: WorktreeId,
776 pub source_language_server_id: LanguageServerId,
777 pub path: SymbolLocation,
778 pub label: CodeLabel,
779 pub name: String,
780 pub kind: lsp::SymbolKind,
781 pub range: Range<Unclipped<PointUtf16>>,
782}
783
784#[derive(Clone, Debug)]
785pub struct DocumentSymbol {
786 pub name: String,
787 pub kind: lsp::SymbolKind,
788 pub range: Range<Unclipped<PointUtf16>>,
789 pub selection_range: Range<Unclipped<PointUtf16>>,
790 pub children: Vec<DocumentSymbol>,
791}
792
793#[derive(Clone, Debug, PartialEq)]
794pub struct HoverBlock {
795 pub text: String,
796 pub kind: HoverBlockKind,
797}
798
799#[derive(Clone, Debug, PartialEq, Eq)]
800pub enum HoverBlockKind {
801 PlainText,
802 Markdown,
803 Code { language: String },
804}
805
806#[derive(Debug, Clone)]
807pub struct Hover {
808 pub contents: Vec<HoverBlock>,
809 pub range: Option<Range<language::Anchor>>,
810 pub language: Option<Arc<Language>>,
811}
812
813impl Hover {
814 pub fn is_empty(&self) -> bool {
815 self.contents.iter().all(|block| block.text.is_empty())
816 }
817}
818
819enum EntitySubscription {
820 Project(PendingEntitySubscription<Project>),
821 BufferStore(PendingEntitySubscription<BufferStore>),
822 GitStore(PendingEntitySubscription<GitStore>),
823 WorktreeStore(PendingEntitySubscription<WorktreeStore>),
824 LspStore(PendingEntitySubscription<LspStore>),
825 SettingsObserver(PendingEntitySubscription<SettingsObserver>),
826 DapStore(PendingEntitySubscription<DapStore>),
827}
828
829#[derive(Debug, Clone)]
830pub struct DirectoryItem {
831 pub path: PathBuf,
832 pub is_dir: bool,
833}
834
835#[derive(Clone, Debug, PartialEq)]
836pub struct DocumentColor {
837 pub lsp_range: lsp::Range,
838 pub color: lsp::Color,
839 pub resolved: bool,
840 pub color_presentations: Vec<ColorPresentation>,
841}
842
843impl Eq for DocumentColor {}
844
845impl std::hash::Hash for DocumentColor {
846 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
847 self.lsp_range.hash(state);
848 self.color.red.to_bits().hash(state);
849 self.color.green.to_bits().hash(state);
850 self.color.blue.to_bits().hash(state);
851 self.color.alpha.to_bits().hash(state);
852 self.resolved.hash(state);
853 self.color_presentations.hash(state);
854 }
855}
856
857#[derive(Clone, Debug, PartialEq, Eq)]
858pub struct ColorPresentation {
859 pub label: SharedString,
860 pub text_edit: Option<lsp::TextEdit>,
861 pub additional_text_edits: Vec<lsp::TextEdit>,
862}
863
864impl std::hash::Hash for ColorPresentation {
865 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
866 self.label.hash(state);
867 if let Some(ref edit) = self.text_edit {
868 edit.range.hash(state);
869 edit.new_text.hash(state);
870 }
871 self.additional_text_edits.len().hash(state);
872 for edit in &self.additional_text_edits {
873 edit.range.hash(state);
874 edit.new_text.hash(state);
875 }
876 }
877}
878
879#[derive(Clone)]
880pub enum DirectoryLister {
881 Project(Entity<Project>),
882 Local(Entity<Project>, Arc<dyn Fs>),
883}
884
885impl std::fmt::Debug for DirectoryLister {
886 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
887 match self {
888 DirectoryLister::Project(project) => {
889 write!(f, "DirectoryLister::Project({project:?})")
890 }
891 DirectoryLister::Local(project, _) => {
892 write!(f, "DirectoryLister::Local({project:?})")
893 }
894 }
895 }
896}
897
898impl DirectoryLister {
899 pub fn is_local(&self, cx: &App) -> bool {
900 match self {
901 DirectoryLister::Local(..) => true,
902 DirectoryLister::Project(project) => project.read(cx).is_local(),
903 }
904 }
905
906 pub fn resolve_tilde<'a>(&self, path: &'a String, cx: &App) -> Cow<'a, str> {
907 if self.is_local(cx) {
908 shellexpand::tilde(path)
909 } else {
910 Cow::from(path)
911 }
912 }
913
914 pub fn default_query(&self, cx: &mut App) -> String {
915 let project = match self {
916 DirectoryLister::Project(project) => project,
917 DirectoryLister::Local(project, _) => project,
918 }
919 .read(cx);
920 let path_style = project.path_style(cx);
921 project
922 .visible_worktrees(cx)
923 .next()
924 .map(|worktree| worktree.read(cx).abs_path().to_string_lossy().into_owned())
925 .or_else(|| std::env::home_dir().map(|dir| dir.to_string_lossy().into_owned()))
926 .map(|mut s| {
927 s.push_str(path_style.separator());
928 s
929 })
930 .unwrap_or_else(|| {
931 if path_style.is_windows() {
932 "C:\\"
933 } else {
934 "~/"
935 }
936 .to_string()
937 })
938 }
939
940 pub fn list_directory(&self, path: String, cx: &mut App) -> Task<Result<Vec<DirectoryItem>>> {
941 match self {
942 DirectoryLister::Project(project) => {
943 project.update(cx, |project, cx| project.list_directory(path, cx))
944 }
945 DirectoryLister::Local(_, fs) => {
946 let fs = fs.clone();
947 cx.background_spawn(async move {
948 let mut results = vec![];
949 let expanded = shellexpand::tilde(&path);
950 let query = Path::new(expanded.as_ref());
951 let mut response = fs.read_dir(query).await?;
952 while let Some(path) = response.next().await {
953 let path = path?;
954 if let Some(file_name) = path.file_name() {
955 results.push(DirectoryItem {
956 path: PathBuf::from(file_name.to_os_string()),
957 is_dir: fs.is_dir(&path).await,
958 });
959 }
960 }
961 Ok(results)
962 })
963 }
964 }
965 }
966}
967
968#[cfg(any(test, feature = "test-support"))]
969pub const DEFAULT_COMPLETION_CONTEXT: CompletionContext = CompletionContext {
970 trigger_kind: lsp::CompletionTriggerKind::INVOKED,
971 trigger_character: None,
972};
973
974/// An LSP diagnostics associated with a certain language server.
975#[derive(Clone, Debug, Default)]
976pub enum LspPullDiagnostics {
977 #[default]
978 Default,
979 Response {
980 /// The id of the language server that produced diagnostics.
981 server_id: LanguageServerId,
982 /// URI of the resource,
983 uri: lsp::Uri,
984 /// The diagnostics produced by this language server.
985 diagnostics: PulledDiagnostics,
986 },
987}
988
989#[derive(Clone, Debug)]
990pub enum PulledDiagnostics {
991 Unchanged {
992 /// An ID the current pulled batch for this file.
993 /// If given, can be used to query workspace diagnostics partially.
994 result_id: String,
995 },
996 Changed {
997 result_id: Option<String>,
998 diagnostics: Vec<lsp::Diagnostic>,
999 },
1000}
1001
1002/// Whether to disable all AI features in Zed.
1003///
1004/// Default: false
1005#[derive(Copy, Clone, Debug, RegisterSetting)]
1006pub struct DisableAiSettings {
1007 pub disable_ai: bool,
1008}
1009
1010impl settings::Settings for DisableAiSettings {
1011 fn from_settings(content: &settings::SettingsContent) -> Self {
1012 Self {
1013 disable_ai: content.disable_ai.unwrap().0,
1014 }
1015 }
1016}
1017
1018impl Project {
1019 pub fn init(client: &Arc<Client>, cx: &mut App) {
1020 connection_manager::init(client.clone(), cx);
1021
1022 let client: AnyProtoClient = client.clone().into();
1023 client.add_entity_message_handler(Self::handle_add_collaborator);
1024 client.add_entity_message_handler(Self::handle_update_project_collaborator);
1025 client.add_entity_message_handler(Self::handle_remove_collaborator);
1026 client.add_entity_message_handler(Self::handle_update_project);
1027 client.add_entity_message_handler(Self::handle_unshare_project);
1028 client.add_entity_request_handler(Self::handle_update_buffer);
1029 client.add_entity_message_handler(Self::handle_update_worktree);
1030 client.add_entity_request_handler(Self::handle_synchronize_buffers);
1031
1032 client.add_entity_request_handler(Self::handle_search_candidate_buffers);
1033 client.add_entity_request_handler(Self::handle_open_buffer_by_id);
1034 client.add_entity_request_handler(Self::handle_open_buffer_by_path);
1035 client.add_entity_request_handler(Self::handle_open_new_buffer);
1036 client.add_entity_message_handler(Self::handle_create_buffer_for_peer);
1037 client.add_entity_message_handler(Self::handle_toggle_lsp_logs);
1038 client.add_entity_message_handler(Self::handle_create_image_for_peer);
1039
1040 WorktreeStore::init(&client);
1041 BufferStore::init(&client);
1042 LspStore::init(&client);
1043 GitStore::init(&client);
1044 SettingsObserver::init(&client);
1045 TaskStore::init(Some(&client));
1046 ToolchainStore::init(&client);
1047 DapStore::init(&client, cx);
1048 BreakpointStore::init(&client);
1049 context_server_store::init(cx);
1050 }
1051
1052 pub fn local(
1053 client: Arc<Client>,
1054 node: NodeRuntime,
1055 user_store: Entity<UserStore>,
1056 languages: Arc<LanguageRegistry>,
1057 fs: Arc<dyn Fs>,
1058 env: Option<HashMap<String, String>>,
1059 cx: &mut App,
1060 ) -> Entity<Self> {
1061 cx.new(|cx: &mut Context<Self>| {
1062 let (tx, rx) = mpsc::unbounded();
1063 cx.spawn(async move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx).await)
1064 .detach();
1065 let snippets = SnippetProvider::new(fs.clone(), BTreeSet::from_iter([]), cx);
1066 let worktree_store = cx.new(|_| WorktreeStore::local(false, fs.clone()));
1067 cx.subscribe(&worktree_store, Self::on_worktree_store_event)
1068 .detach();
1069
1070 let weak_self = cx.weak_entity();
1071 let context_server_store =
1072 cx.new(|cx| ContextServerStore::new(worktree_store.clone(), weak_self.clone(), cx));
1073
1074 let environment = cx.new(|cx| {
1075 ProjectEnvironment::new(env, worktree_store.downgrade(), None, false, cx)
1076 });
1077 let manifest_tree = ManifestTree::new(worktree_store.clone(), cx);
1078 let toolchain_store = cx.new(|cx| {
1079 ToolchainStore::local(
1080 languages.clone(),
1081 worktree_store.clone(),
1082 environment.clone(),
1083 manifest_tree.clone(),
1084 fs.clone(),
1085 cx,
1086 )
1087 });
1088
1089 let buffer_store = cx.new(|cx| BufferStore::local(worktree_store.clone(), cx));
1090 cx.subscribe(&buffer_store, Self::on_buffer_store_event)
1091 .detach();
1092
1093 let breakpoint_store =
1094 cx.new(|_| BreakpointStore::local(worktree_store.clone(), buffer_store.clone()));
1095
1096 let dap_store = cx.new(|cx| {
1097 DapStore::new_local(
1098 client.http_client(),
1099 node.clone(),
1100 fs.clone(),
1101 environment.clone(),
1102 toolchain_store.read(cx).as_language_toolchain_store(),
1103 worktree_store.clone(),
1104 breakpoint_store.clone(),
1105 false,
1106 cx,
1107 )
1108 });
1109 cx.subscribe(&dap_store, Self::on_dap_store_event).detach();
1110
1111 let image_store = cx.new(|cx| ImageStore::local(worktree_store.clone(), cx));
1112 cx.subscribe(&image_store, Self::on_image_store_event)
1113 .detach();
1114
1115 let prettier_store = cx.new(|cx| {
1116 PrettierStore::new(
1117 node.clone(),
1118 fs.clone(),
1119 languages.clone(),
1120 worktree_store.clone(),
1121 cx,
1122 )
1123 });
1124
1125 let task_store = cx.new(|cx| {
1126 TaskStore::local(
1127 buffer_store.downgrade(),
1128 worktree_store.clone(),
1129 toolchain_store.read(cx).as_language_toolchain_store(),
1130 environment.clone(),
1131 cx,
1132 )
1133 });
1134
1135 let settings_observer = cx.new(|cx| {
1136 SettingsObserver::new_local(
1137 fs.clone(),
1138 worktree_store.clone(),
1139 task_store.clone(),
1140 cx,
1141 )
1142 });
1143 cx.subscribe(&settings_observer, Self::on_settings_observer_event)
1144 .detach();
1145
1146 let lsp_store = cx.new(|cx| {
1147 LspStore::new_local(
1148 buffer_store.clone(),
1149 worktree_store.clone(),
1150 prettier_store.clone(),
1151 toolchain_store
1152 .read(cx)
1153 .as_local_store()
1154 .expect("Toolchain store to be local")
1155 .clone(),
1156 environment.clone(),
1157 manifest_tree,
1158 languages.clone(),
1159 client.http_client(),
1160 fs.clone(),
1161 cx,
1162 )
1163 });
1164
1165 let git_store = cx.new(|cx| {
1166 GitStore::local(
1167 &worktree_store,
1168 buffer_store.clone(),
1169 environment.clone(),
1170 fs.clone(),
1171 cx,
1172 )
1173 });
1174
1175 let agent_server_store = cx.new(|cx| {
1176 AgentServerStore::local(
1177 node.clone(),
1178 fs.clone(),
1179 environment.clone(),
1180 client.http_client(),
1181 cx,
1182 )
1183 });
1184
1185 cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
1186
1187 Self {
1188 buffer_ordered_messages_tx: tx,
1189 collaborators: Default::default(),
1190 worktree_store,
1191 buffer_store,
1192 image_store,
1193 lsp_store,
1194 context_server_store,
1195 join_project_response_message_id: 0,
1196 client_state: ProjectClientState::Local,
1197 git_store,
1198 client_subscriptions: Vec::new(),
1199 _subscriptions: vec![cx.on_release(Self::release)],
1200 active_entry: None,
1201 snippets,
1202 languages,
1203 collab_client: client,
1204 task_store,
1205 user_store,
1206 settings_observer,
1207 fs,
1208 remote_client: None,
1209 breakpoint_store,
1210 dap_store,
1211 agent_server_store,
1212
1213 buffers_needing_diff: Default::default(),
1214 git_diff_debouncer: DebouncedDelay::new(),
1215 terminals: Terminals {
1216 local_handles: Vec::new(),
1217 },
1218 node: Some(node),
1219 search_history: Self::new_search_history(),
1220 environment,
1221 remotely_created_models: Default::default(),
1222
1223 search_included_history: Self::new_search_history(),
1224 search_excluded_history: Self::new_search_history(),
1225
1226 toolchain_store: Some(toolchain_store),
1227
1228 agent_location: None,
1229 }
1230 })
1231 }
1232
1233 pub fn remote(
1234 remote: Entity<RemoteClient>,
1235 client: Arc<Client>,
1236 node: NodeRuntime,
1237 user_store: Entity<UserStore>,
1238 languages: Arc<LanguageRegistry>,
1239 fs: Arc<dyn Fs>,
1240 cx: &mut App,
1241 ) -> Entity<Self> {
1242 cx.new(|cx: &mut Context<Self>| {
1243 let (tx, rx) = mpsc::unbounded();
1244 cx.spawn(async move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx).await)
1245 .detach();
1246 let global_snippets_dir = paths::snippets_dir().to_owned();
1247 let snippets =
1248 SnippetProvider::new(fs.clone(), BTreeSet::from_iter([global_snippets_dir]), cx);
1249
1250 let (remote_proto, path_style) =
1251 remote.read_with(cx, |remote, _| (remote.proto_client(), remote.path_style()));
1252 let worktree_store = cx.new(|_| {
1253 WorktreeStore::remote(
1254 false,
1255 remote_proto.clone(),
1256 REMOTE_SERVER_PROJECT_ID,
1257 path_style,
1258 )
1259 });
1260 cx.subscribe(&worktree_store, Self::on_worktree_store_event)
1261 .detach();
1262
1263 let weak_self = cx.weak_entity();
1264 let context_server_store =
1265 cx.new(|cx| ContextServerStore::new(worktree_store.clone(), weak_self.clone(), cx));
1266
1267 let buffer_store = cx.new(|cx| {
1268 BufferStore::remote(
1269 worktree_store.clone(),
1270 remote.read(cx).proto_client(),
1271 REMOTE_SERVER_PROJECT_ID,
1272 cx,
1273 )
1274 });
1275 let image_store = cx.new(|cx| {
1276 ImageStore::remote(
1277 worktree_store.clone(),
1278 remote.read(cx).proto_client(),
1279 REMOTE_SERVER_PROJECT_ID,
1280 cx,
1281 )
1282 });
1283 cx.subscribe(&buffer_store, Self::on_buffer_store_event)
1284 .detach();
1285 let toolchain_store = cx.new(|cx| {
1286 ToolchainStore::remote(REMOTE_SERVER_PROJECT_ID, remote.read(cx).proto_client(), cx)
1287 });
1288 let task_store = cx.new(|cx| {
1289 TaskStore::remote(
1290 buffer_store.downgrade(),
1291 worktree_store.clone(),
1292 toolchain_store.read(cx).as_language_toolchain_store(),
1293 remote.read(cx).proto_client(),
1294 REMOTE_SERVER_PROJECT_ID,
1295 cx,
1296 )
1297 });
1298
1299 let settings_observer = cx.new(|cx| {
1300 SettingsObserver::new_remote(
1301 fs.clone(),
1302 worktree_store.clone(),
1303 task_store.clone(),
1304 Some(remote_proto.clone()),
1305 cx,
1306 )
1307 });
1308 cx.subscribe(&settings_observer, Self::on_settings_observer_event)
1309 .detach();
1310
1311 let environment = cx.new(|cx| {
1312 ProjectEnvironment::new(
1313 None,
1314 worktree_store.downgrade(),
1315 Some(remote.downgrade()),
1316 false,
1317 cx,
1318 )
1319 });
1320
1321 let lsp_store = cx.new(|cx| {
1322 LspStore::new_remote(
1323 buffer_store.clone(),
1324 worktree_store.clone(),
1325 languages.clone(),
1326 remote_proto.clone(),
1327 REMOTE_SERVER_PROJECT_ID,
1328 cx,
1329 )
1330 });
1331 cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
1332
1333 let breakpoint_store =
1334 cx.new(|_| BreakpointStore::remote(REMOTE_SERVER_PROJECT_ID, remote_proto.clone()));
1335
1336 let dap_store = cx.new(|cx| {
1337 DapStore::new_remote(
1338 REMOTE_SERVER_PROJECT_ID,
1339 remote.clone(),
1340 breakpoint_store.clone(),
1341 worktree_store.clone(),
1342 node.clone(),
1343 client.http_client(),
1344 fs.clone(),
1345 cx,
1346 )
1347 });
1348
1349 let git_store = cx.new(|cx| {
1350 GitStore::remote(
1351 &worktree_store,
1352 buffer_store.clone(),
1353 remote_proto.clone(),
1354 REMOTE_SERVER_PROJECT_ID,
1355 cx,
1356 )
1357 });
1358
1359 let agent_server_store =
1360 cx.new(|_| AgentServerStore::remote(REMOTE_SERVER_PROJECT_ID, remote.clone()));
1361
1362 cx.subscribe(&remote, Self::on_remote_client_event).detach();
1363
1364 let this = Self {
1365 buffer_ordered_messages_tx: tx,
1366 collaborators: Default::default(),
1367 worktree_store,
1368 buffer_store,
1369 image_store,
1370 lsp_store,
1371 context_server_store,
1372 breakpoint_store,
1373 dap_store,
1374 join_project_response_message_id: 0,
1375 client_state: ProjectClientState::Local,
1376 git_store,
1377 agent_server_store,
1378 client_subscriptions: Vec::new(),
1379 _subscriptions: vec![
1380 cx.on_release(Self::release),
1381 cx.on_app_quit(|this, cx| {
1382 let shutdown = this.remote_client.take().and_then(|client| {
1383 client.update(cx, |client, cx| {
1384 client.shutdown_processes(
1385 Some(proto::ShutdownRemoteServer {}),
1386 cx.background_executor().clone(),
1387 )
1388 })
1389 });
1390
1391 cx.background_executor().spawn(async move {
1392 if let Some(shutdown) = shutdown {
1393 shutdown.await;
1394 }
1395 })
1396 }),
1397 ],
1398 active_entry: None,
1399 snippets,
1400 languages,
1401 collab_client: client,
1402 task_store,
1403 user_store,
1404 settings_observer,
1405 fs,
1406 remote_client: Some(remote.clone()),
1407 buffers_needing_diff: Default::default(),
1408 git_diff_debouncer: DebouncedDelay::new(),
1409 terminals: Terminals {
1410 local_handles: Vec::new(),
1411 },
1412 node: Some(node),
1413 search_history: Self::new_search_history(),
1414 environment,
1415 remotely_created_models: Default::default(),
1416
1417 search_included_history: Self::new_search_history(),
1418 search_excluded_history: Self::new_search_history(),
1419
1420 toolchain_store: Some(toolchain_store),
1421 agent_location: None,
1422 };
1423
1424 // remote server -> local machine handlers
1425 remote_proto.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &cx.entity());
1426 remote_proto.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &this.buffer_store);
1427 remote_proto.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &this.worktree_store);
1428 remote_proto.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &this.lsp_store);
1429 remote_proto.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &this.dap_store);
1430 remote_proto.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &this.settings_observer);
1431 remote_proto.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &this.git_store);
1432 remote_proto.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &this.agent_server_store);
1433
1434 remote_proto.add_entity_message_handler(Self::handle_create_buffer_for_peer);
1435 remote_proto.add_entity_message_handler(Self::handle_create_image_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
2857 // Check if metadata already exists (e.g., for remote images)
2858 let needs_metadata =
2859 cx.read_entity(&image_item, |item, _| item.image_metadata.is_none())?;
2860
2861 if needs_metadata {
2862 let project = weak_project.upgrade().context("Project dropped")?;
2863 let metadata =
2864 ImageItem::load_image_metadata(image_item.clone(), project, cx).await?;
2865 image_item.update(cx, |image_item, cx| {
2866 image_item.image_metadata = Some(metadata);
2867 cx.emit(ImageItemEvent::MetadataUpdated);
2868 })?;
2869 }
2870
2871 Ok(image_item)
2872 })
2873 }
2874
2875 async fn send_buffer_ordered_messages(
2876 project: WeakEntity<Self>,
2877 rx: UnboundedReceiver<BufferOrderedMessage>,
2878 cx: &mut AsyncApp,
2879 ) -> Result<()> {
2880 const MAX_BATCH_SIZE: usize = 128;
2881
2882 let mut operations_by_buffer_id = HashMap::default();
2883 async fn flush_operations(
2884 this: &WeakEntity<Project>,
2885 operations_by_buffer_id: &mut HashMap<BufferId, Vec<proto::Operation>>,
2886 needs_resync_with_host: &mut bool,
2887 is_local: bool,
2888 cx: &mut AsyncApp,
2889 ) -> Result<()> {
2890 for (buffer_id, operations) in operations_by_buffer_id.drain() {
2891 let request = this.read_with(cx, |this, _| {
2892 let project_id = this.remote_id()?;
2893 Some(this.collab_client.request(proto::UpdateBuffer {
2894 buffer_id: buffer_id.into(),
2895 project_id,
2896 operations,
2897 }))
2898 })?;
2899 if let Some(request) = request
2900 && request.await.is_err()
2901 && !is_local
2902 {
2903 *needs_resync_with_host = true;
2904 break;
2905 }
2906 }
2907 Ok(())
2908 }
2909
2910 let mut needs_resync_with_host = false;
2911 let mut changes = rx.ready_chunks(MAX_BATCH_SIZE);
2912
2913 while let Some(changes) = changes.next().await {
2914 let is_local = project.read_with(cx, |this, _| this.is_local())?;
2915
2916 for change in changes {
2917 match change {
2918 BufferOrderedMessage::Operation {
2919 buffer_id,
2920 operation,
2921 } => {
2922 if needs_resync_with_host {
2923 continue;
2924 }
2925
2926 operations_by_buffer_id
2927 .entry(buffer_id)
2928 .or_insert(Vec::new())
2929 .push(operation);
2930 }
2931
2932 BufferOrderedMessage::Resync => {
2933 operations_by_buffer_id.clear();
2934 if project
2935 .update(cx, |this, cx| this.synchronize_remote_buffers(cx))?
2936 .await
2937 .is_ok()
2938 {
2939 needs_resync_with_host = false;
2940 }
2941 }
2942
2943 BufferOrderedMessage::LanguageServerUpdate {
2944 language_server_id,
2945 message,
2946 name,
2947 } => {
2948 flush_operations(
2949 &project,
2950 &mut operations_by_buffer_id,
2951 &mut needs_resync_with_host,
2952 is_local,
2953 cx,
2954 )
2955 .await?;
2956
2957 project.read_with(cx, |project, _| {
2958 if let Some(project_id) = project.remote_id() {
2959 project
2960 .collab_client
2961 .send(proto::UpdateLanguageServer {
2962 project_id,
2963 server_name: name.map(|name| String::from(name.0)),
2964 language_server_id: language_server_id.to_proto(),
2965 variant: Some(message),
2966 })
2967 .log_err();
2968 }
2969 })?;
2970 }
2971 }
2972 }
2973
2974 flush_operations(
2975 &project,
2976 &mut operations_by_buffer_id,
2977 &mut needs_resync_with_host,
2978 is_local,
2979 cx,
2980 )
2981 .await?;
2982 }
2983
2984 Ok(())
2985 }
2986
2987 fn on_buffer_store_event(
2988 &mut self,
2989 _: Entity<BufferStore>,
2990 event: &BufferStoreEvent,
2991 cx: &mut Context<Self>,
2992 ) {
2993 match event {
2994 BufferStoreEvent::BufferAdded(buffer) => {
2995 self.register_buffer(buffer, cx).log_err();
2996 }
2997 BufferStoreEvent::BufferDropped(buffer_id) => {
2998 if let Some(ref remote_client) = self.remote_client {
2999 remote_client
3000 .read(cx)
3001 .proto_client()
3002 .send(proto::CloseBuffer {
3003 project_id: 0,
3004 buffer_id: buffer_id.to_proto(),
3005 })
3006 .log_err();
3007 }
3008 }
3009 _ => {}
3010 }
3011 }
3012
3013 fn on_image_store_event(
3014 &mut self,
3015 _: Entity<ImageStore>,
3016 event: &ImageStoreEvent,
3017 cx: &mut Context<Self>,
3018 ) {
3019 match event {
3020 ImageStoreEvent::ImageAdded(image) => {
3021 cx.subscribe(image, |this, image, event, cx| {
3022 this.on_image_event(image, event, cx);
3023 })
3024 .detach();
3025 }
3026 }
3027 }
3028
3029 fn on_dap_store_event(
3030 &mut self,
3031 _: Entity<DapStore>,
3032 event: &DapStoreEvent,
3033 cx: &mut Context<Self>,
3034 ) {
3035 if let DapStoreEvent::Notification(message) = event {
3036 cx.emit(Event::Toast {
3037 notification_id: "dap".into(),
3038 message: message.clone(),
3039 });
3040 }
3041 }
3042
3043 fn on_lsp_store_event(
3044 &mut self,
3045 _: Entity<LspStore>,
3046 event: &LspStoreEvent,
3047 cx: &mut Context<Self>,
3048 ) {
3049 match event {
3050 LspStoreEvent::DiagnosticsUpdated { server_id, paths } => {
3051 cx.emit(Event::DiagnosticsUpdated {
3052 paths: paths.clone(),
3053 language_server_id: *server_id,
3054 })
3055 }
3056 LspStoreEvent::LanguageServerAdded(server_id, name, worktree_id) => cx.emit(
3057 Event::LanguageServerAdded(*server_id, name.clone(), *worktree_id),
3058 ),
3059 LspStoreEvent::LanguageServerRemoved(server_id) => {
3060 cx.emit(Event::LanguageServerRemoved(*server_id))
3061 }
3062 LspStoreEvent::LanguageServerLog(server_id, log_type, string) => cx.emit(
3063 Event::LanguageServerLog(*server_id, log_type.clone(), string.clone()),
3064 ),
3065 LspStoreEvent::LanguageDetected {
3066 buffer,
3067 new_language,
3068 } => {
3069 let Some(_) = new_language else {
3070 cx.emit(Event::LanguageNotFound(buffer.clone()));
3071 return;
3072 };
3073 }
3074 LspStoreEvent::RefreshInlayHints {
3075 server_id,
3076 request_id,
3077 } => cx.emit(Event::RefreshInlayHints {
3078 server_id: *server_id,
3079 request_id: *request_id,
3080 }),
3081 LspStoreEvent::RefreshCodeLens => cx.emit(Event::RefreshCodeLens),
3082 LspStoreEvent::LanguageServerPrompt(prompt) => {
3083 cx.emit(Event::LanguageServerPrompt(prompt.clone()))
3084 }
3085 LspStoreEvent::DiskBasedDiagnosticsStarted { language_server_id } => {
3086 cx.emit(Event::DiskBasedDiagnosticsStarted {
3087 language_server_id: *language_server_id,
3088 });
3089 }
3090 LspStoreEvent::DiskBasedDiagnosticsFinished { language_server_id } => {
3091 cx.emit(Event::DiskBasedDiagnosticsFinished {
3092 language_server_id: *language_server_id,
3093 });
3094 }
3095 LspStoreEvent::LanguageServerUpdate {
3096 language_server_id,
3097 name,
3098 message,
3099 } => {
3100 if self.is_local() {
3101 self.enqueue_buffer_ordered_message(
3102 BufferOrderedMessage::LanguageServerUpdate {
3103 language_server_id: *language_server_id,
3104 message: message.clone(),
3105 name: name.clone(),
3106 },
3107 )
3108 .ok();
3109 }
3110
3111 match message {
3112 proto::update_language_server::Variant::MetadataUpdated(update) => {
3113 if let Some(capabilities) = update
3114 .capabilities
3115 .as_ref()
3116 .and_then(|capabilities| serde_json::from_str(capabilities).ok())
3117 {
3118 self.lsp_store.update(cx, |lsp_store, _| {
3119 lsp_store
3120 .lsp_server_capabilities
3121 .insert(*language_server_id, capabilities);
3122 });
3123 }
3124 }
3125 proto::update_language_server::Variant::RegisteredForBuffer(update) => {
3126 if let Some(buffer_id) = BufferId::new(update.buffer_id).ok() {
3127 cx.emit(Event::LanguageServerBufferRegistered {
3128 buffer_id,
3129 server_id: *language_server_id,
3130 buffer_abs_path: PathBuf::from(&update.buffer_abs_path),
3131 name: name.clone(),
3132 });
3133 }
3134 }
3135 _ => (),
3136 }
3137 }
3138 LspStoreEvent::Notification(message) => cx.emit(Event::Toast {
3139 notification_id: "lsp".into(),
3140 message: message.clone(),
3141 }),
3142 LspStoreEvent::SnippetEdit {
3143 buffer_id,
3144 edits,
3145 most_recent_edit,
3146 } => {
3147 if most_recent_edit.replica_id == self.replica_id() {
3148 cx.emit(Event::SnippetEdit(*buffer_id, edits.clone()))
3149 }
3150 }
3151 }
3152 }
3153
3154 fn on_remote_client_event(
3155 &mut self,
3156 _: Entity<RemoteClient>,
3157 event: &remote::RemoteClientEvent,
3158 cx: &mut Context<Self>,
3159 ) {
3160 match event {
3161 remote::RemoteClientEvent::Disconnected => {
3162 self.worktree_store.update(cx, |store, cx| {
3163 store.disconnected_from_host(cx);
3164 });
3165 self.buffer_store.update(cx, |buffer_store, cx| {
3166 buffer_store.disconnected_from_host(cx)
3167 });
3168 self.lsp_store.update(cx, |lsp_store, _cx| {
3169 lsp_store.disconnected_from_ssh_remote()
3170 });
3171 cx.emit(Event::DisconnectedFromSshRemote);
3172 }
3173 }
3174 }
3175
3176 fn on_settings_observer_event(
3177 &mut self,
3178 _: Entity<SettingsObserver>,
3179 event: &SettingsObserverEvent,
3180 cx: &mut Context<Self>,
3181 ) {
3182 match event {
3183 SettingsObserverEvent::LocalSettingsUpdated(result) => match result {
3184 Err(InvalidSettingsError::LocalSettings { message, path }) => {
3185 let message = format!("Failed to set local settings in {path:?}:\n{message}");
3186 cx.emit(Event::Toast {
3187 notification_id: format!("local-settings-{path:?}").into(),
3188 message,
3189 });
3190 }
3191 Ok(path) => cx.emit(Event::HideToast {
3192 notification_id: format!("local-settings-{path:?}").into(),
3193 }),
3194 Err(_) => {}
3195 },
3196 SettingsObserverEvent::LocalTasksUpdated(result) => match result {
3197 Err(InvalidSettingsError::Tasks { message, path }) => {
3198 let message = format!("Failed to set local tasks in {path:?}:\n{message}");
3199 cx.emit(Event::Toast {
3200 notification_id: format!("local-tasks-{path:?}").into(),
3201 message,
3202 });
3203 }
3204 Ok(path) => cx.emit(Event::HideToast {
3205 notification_id: format!("local-tasks-{path:?}").into(),
3206 }),
3207 Err(_) => {}
3208 },
3209 SettingsObserverEvent::LocalDebugScenariosUpdated(result) => match result {
3210 Err(InvalidSettingsError::Debug { message, path }) => {
3211 let message =
3212 format!("Failed to set local debug scenarios in {path:?}:\n{message}");
3213 cx.emit(Event::Toast {
3214 notification_id: format!("local-debug-scenarios-{path:?}").into(),
3215 message,
3216 });
3217 }
3218 Ok(path) => cx.emit(Event::HideToast {
3219 notification_id: format!("local-debug-scenarios-{path:?}").into(),
3220 }),
3221 Err(_) => {}
3222 },
3223 }
3224 }
3225
3226 fn on_worktree_store_event(
3227 &mut self,
3228 _: Entity<WorktreeStore>,
3229 event: &WorktreeStoreEvent,
3230 cx: &mut Context<Self>,
3231 ) {
3232 match event {
3233 WorktreeStoreEvent::WorktreeAdded(worktree) => {
3234 self.on_worktree_added(worktree, cx);
3235 cx.emit(Event::WorktreeAdded(worktree.read(cx).id()));
3236 }
3237 WorktreeStoreEvent::WorktreeRemoved(_, id) => {
3238 cx.emit(Event::WorktreeRemoved(*id));
3239 }
3240 WorktreeStoreEvent::WorktreeReleased(_, id) => {
3241 self.on_worktree_released(*id, cx);
3242 }
3243 WorktreeStoreEvent::WorktreeOrderChanged => cx.emit(Event::WorktreeOrderChanged),
3244 WorktreeStoreEvent::WorktreeUpdateSent(_) => {}
3245 WorktreeStoreEvent::WorktreeUpdatedEntries(worktree_id, changes) => {
3246 self.client()
3247 .telemetry()
3248 .report_discovered_project_type_events(*worktree_id, changes);
3249 cx.emit(Event::WorktreeUpdatedEntries(*worktree_id, changes.clone()))
3250 }
3251 WorktreeStoreEvent::WorktreeDeletedEntry(worktree_id, id) => {
3252 cx.emit(Event::DeletedEntry(*worktree_id, *id))
3253 }
3254 // Listen to the GitStore instead.
3255 WorktreeStoreEvent::WorktreeUpdatedGitRepositories(_, _) => {}
3256 }
3257 }
3258
3259 fn on_worktree_added(&mut self, worktree: &Entity<Worktree>, _: &mut Context<Self>) {
3260 let mut remotely_created_models = self.remotely_created_models.lock();
3261 if remotely_created_models.retain_count > 0 {
3262 remotely_created_models.worktrees.push(worktree.clone())
3263 }
3264 }
3265
3266 fn on_worktree_released(&mut self, id_to_remove: WorktreeId, cx: &mut Context<Self>) {
3267 if let Some(remote) = &self.remote_client {
3268 remote
3269 .read(cx)
3270 .proto_client()
3271 .send(proto::RemoveWorktree {
3272 worktree_id: id_to_remove.to_proto(),
3273 })
3274 .log_err();
3275 }
3276 }
3277
3278 fn on_buffer_event(
3279 &mut self,
3280 buffer: Entity<Buffer>,
3281 event: &BufferEvent,
3282 cx: &mut Context<Self>,
3283 ) -> Option<()> {
3284 if matches!(event, BufferEvent::Edited | BufferEvent::Reloaded) {
3285 self.request_buffer_diff_recalculation(&buffer, cx);
3286 }
3287
3288 let buffer_id = buffer.read(cx).remote_id();
3289 match event {
3290 BufferEvent::ReloadNeeded => {
3291 if !self.is_via_collab() {
3292 self.reload_buffers([buffer.clone()].into_iter().collect(), true, cx)
3293 .detach_and_log_err(cx);
3294 }
3295 }
3296 BufferEvent::Operation {
3297 operation,
3298 is_local: true,
3299 } => {
3300 let operation = language::proto::serialize_operation(operation);
3301
3302 if let Some(remote) = &self.remote_client {
3303 remote
3304 .read(cx)
3305 .proto_client()
3306 .send(proto::UpdateBuffer {
3307 project_id: 0,
3308 buffer_id: buffer_id.to_proto(),
3309 operations: vec![operation.clone()],
3310 })
3311 .ok();
3312 }
3313
3314 self.enqueue_buffer_ordered_message(BufferOrderedMessage::Operation {
3315 buffer_id,
3316 operation,
3317 })
3318 .ok();
3319 }
3320
3321 _ => {}
3322 }
3323
3324 None
3325 }
3326
3327 fn on_image_event(
3328 &mut self,
3329 image: Entity<ImageItem>,
3330 event: &ImageItemEvent,
3331 cx: &mut Context<Self>,
3332 ) -> Option<()> {
3333 // TODO: handle image events from remote
3334 if let ImageItemEvent::ReloadNeeded = event
3335 && !self.is_via_collab()
3336 {
3337 self.reload_images([image].into_iter().collect(), cx)
3338 .detach_and_log_err(cx);
3339 }
3340
3341 None
3342 }
3343
3344 fn request_buffer_diff_recalculation(
3345 &mut self,
3346 buffer: &Entity<Buffer>,
3347 cx: &mut Context<Self>,
3348 ) {
3349 self.buffers_needing_diff.insert(buffer.downgrade());
3350 let first_insertion = self.buffers_needing_diff.len() == 1;
3351 let settings = ProjectSettings::get_global(cx);
3352 let delay = settings.git.gutter_debounce;
3353
3354 if delay == 0 {
3355 if first_insertion {
3356 let this = cx.weak_entity();
3357 cx.defer(move |cx| {
3358 if let Some(this) = this.upgrade() {
3359 this.update(cx, |this, cx| {
3360 this.recalculate_buffer_diffs(cx).detach();
3361 });
3362 }
3363 });
3364 }
3365 return;
3366 }
3367
3368 const MIN_DELAY: u64 = 50;
3369 let delay = delay.max(MIN_DELAY);
3370 let duration = Duration::from_millis(delay);
3371
3372 self.git_diff_debouncer
3373 .fire_new(duration, cx, move |this, cx| {
3374 this.recalculate_buffer_diffs(cx)
3375 });
3376 }
3377
3378 fn recalculate_buffer_diffs(&mut self, cx: &mut Context<Self>) -> Task<()> {
3379 cx.spawn(async move |this, cx| {
3380 loop {
3381 let task = this
3382 .update(cx, |this, cx| {
3383 let buffers = this
3384 .buffers_needing_diff
3385 .drain()
3386 .filter_map(|buffer| buffer.upgrade())
3387 .collect::<Vec<_>>();
3388 if buffers.is_empty() {
3389 None
3390 } else {
3391 Some(this.git_store.update(cx, |git_store, cx| {
3392 git_store.recalculate_buffer_diffs(buffers, cx)
3393 }))
3394 }
3395 })
3396 .ok()
3397 .flatten();
3398
3399 if let Some(task) = task {
3400 task.await;
3401 } else {
3402 break;
3403 }
3404 }
3405 })
3406 }
3407
3408 pub fn set_language_for_buffer(
3409 &mut self,
3410 buffer: &Entity<Buffer>,
3411 new_language: Arc<Language>,
3412 cx: &mut Context<Self>,
3413 ) {
3414 self.lsp_store.update(cx, |lsp_store, cx| {
3415 lsp_store.set_language_for_buffer(buffer, new_language, cx)
3416 })
3417 }
3418
3419 pub fn restart_language_servers_for_buffers(
3420 &mut self,
3421 buffers: Vec<Entity<Buffer>>,
3422 only_restart_servers: HashSet<LanguageServerSelector>,
3423 cx: &mut Context<Self>,
3424 ) {
3425 self.lsp_store.update(cx, |lsp_store, cx| {
3426 lsp_store.restart_language_servers_for_buffers(buffers, only_restart_servers, cx)
3427 })
3428 }
3429
3430 pub fn stop_language_servers_for_buffers(
3431 &mut self,
3432 buffers: Vec<Entity<Buffer>>,
3433 also_restart_servers: HashSet<LanguageServerSelector>,
3434 cx: &mut Context<Self>,
3435 ) {
3436 self.lsp_store
3437 .update(cx, |lsp_store, cx| {
3438 lsp_store.stop_language_servers_for_buffers(buffers, also_restart_servers, cx)
3439 })
3440 .detach_and_log_err(cx);
3441 }
3442
3443 pub fn cancel_language_server_work_for_buffers(
3444 &mut self,
3445 buffers: impl IntoIterator<Item = Entity<Buffer>>,
3446 cx: &mut Context<Self>,
3447 ) {
3448 self.lsp_store.update(cx, |lsp_store, cx| {
3449 lsp_store.cancel_language_server_work_for_buffers(buffers, cx)
3450 })
3451 }
3452
3453 pub fn cancel_language_server_work(
3454 &mut self,
3455 server_id: LanguageServerId,
3456 token_to_cancel: Option<ProgressToken>,
3457 cx: &mut Context<Self>,
3458 ) {
3459 self.lsp_store.update(cx, |lsp_store, cx| {
3460 lsp_store.cancel_language_server_work(server_id, token_to_cancel, cx)
3461 })
3462 }
3463
3464 fn enqueue_buffer_ordered_message(&mut self, message: BufferOrderedMessage) -> Result<()> {
3465 self.buffer_ordered_messages_tx
3466 .unbounded_send(message)
3467 .map_err(|e| anyhow!(e))
3468 }
3469
3470 pub fn available_toolchains(
3471 &self,
3472 path: ProjectPath,
3473 language_name: LanguageName,
3474 cx: &App,
3475 ) -> Task<Option<Toolchains>> {
3476 if let Some(toolchain_store) = self.toolchain_store.as_ref().map(Entity::downgrade) {
3477 cx.spawn(async move |cx| {
3478 toolchain_store
3479 .update(cx, |this, cx| this.list_toolchains(path, language_name, cx))
3480 .ok()?
3481 .await
3482 })
3483 } else {
3484 Task::ready(None)
3485 }
3486 }
3487
3488 pub async fn toolchain_metadata(
3489 languages: Arc<LanguageRegistry>,
3490 language_name: LanguageName,
3491 ) -> Option<ToolchainMetadata> {
3492 languages
3493 .language_for_name(language_name.as_ref())
3494 .await
3495 .ok()?
3496 .toolchain_lister()
3497 .map(|lister| lister.meta())
3498 }
3499
3500 pub fn add_toolchain(
3501 &self,
3502 toolchain: Toolchain,
3503 scope: ToolchainScope,
3504 cx: &mut Context<Self>,
3505 ) {
3506 maybe!({
3507 self.toolchain_store.as_ref()?.update(cx, |this, cx| {
3508 this.add_toolchain(toolchain, scope, cx);
3509 });
3510 Some(())
3511 });
3512 }
3513
3514 pub fn remove_toolchain(
3515 &self,
3516 toolchain: Toolchain,
3517 scope: ToolchainScope,
3518 cx: &mut Context<Self>,
3519 ) {
3520 maybe!({
3521 self.toolchain_store.as_ref()?.update(cx, |this, cx| {
3522 this.remove_toolchain(toolchain, scope, cx);
3523 });
3524 Some(())
3525 });
3526 }
3527
3528 pub fn user_toolchains(
3529 &self,
3530 cx: &App,
3531 ) -> Option<BTreeMap<ToolchainScope, IndexSet<Toolchain>>> {
3532 Some(self.toolchain_store.as_ref()?.read(cx).user_toolchains())
3533 }
3534
3535 pub fn resolve_toolchain(
3536 &self,
3537 path: PathBuf,
3538 language_name: LanguageName,
3539 cx: &App,
3540 ) -> Task<Result<Toolchain>> {
3541 if let Some(toolchain_store) = self.toolchain_store.as_ref().map(Entity::downgrade) {
3542 cx.spawn(async move |cx| {
3543 toolchain_store
3544 .update(cx, |this, cx| {
3545 this.resolve_toolchain(path, language_name, cx)
3546 })?
3547 .await
3548 })
3549 } else {
3550 Task::ready(Err(anyhow!("This project does not support toolchains")))
3551 }
3552 }
3553
3554 pub fn toolchain_store(&self) -> Option<Entity<ToolchainStore>> {
3555 self.toolchain_store.clone()
3556 }
3557 pub fn activate_toolchain(
3558 &self,
3559 path: ProjectPath,
3560 toolchain: Toolchain,
3561 cx: &mut App,
3562 ) -> Task<Option<()>> {
3563 let Some(toolchain_store) = self.toolchain_store.clone() else {
3564 return Task::ready(None);
3565 };
3566 toolchain_store.update(cx, |this, cx| this.activate_toolchain(path, toolchain, cx))
3567 }
3568 pub fn active_toolchain(
3569 &self,
3570 path: ProjectPath,
3571 language_name: LanguageName,
3572 cx: &App,
3573 ) -> Task<Option<Toolchain>> {
3574 let Some(toolchain_store) = self.toolchain_store.clone() else {
3575 return Task::ready(None);
3576 };
3577 toolchain_store
3578 .read(cx)
3579 .active_toolchain(path, language_name, cx)
3580 }
3581 pub fn language_server_statuses<'a>(
3582 &'a self,
3583 cx: &'a App,
3584 ) -> impl DoubleEndedIterator<Item = (LanguageServerId, &'a LanguageServerStatus)> {
3585 self.lsp_store.read(cx).language_server_statuses()
3586 }
3587
3588 pub fn last_formatting_failure<'a>(&self, cx: &'a App) -> Option<&'a str> {
3589 self.lsp_store.read(cx).last_formatting_failure()
3590 }
3591
3592 pub fn reset_last_formatting_failure(&self, cx: &mut App) {
3593 self.lsp_store
3594 .update(cx, |store, _| store.reset_last_formatting_failure());
3595 }
3596
3597 pub fn reload_buffers(
3598 &self,
3599 buffers: HashSet<Entity<Buffer>>,
3600 push_to_history: bool,
3601 cx: &mut Context<Self>,
3602 ) -> Task<Result<ProjectTransaction>> {
3603 self.buffer_store.update(cx, |buffer_store, cx| {
3604 buffer_store.reload_buffers(buffers, push_to_history, cx)
3605 })
3606 }
3607
3608 pub fn reload_images(
3609 &self,
3610 images: HashSet<Entity<ImageItem>>,
3611 cx: &mut Context<Self>,
3612 ) -> Task<Result<()>> {
3613 self.image_store
3614 .update(cx, |image_store, cx| image_store.reload_images(images, cx))
3615 }
3616
3617 pub fn format(
3618 &mut self,
3619 buffers: HashSet<Entity<Buffer>>,
3620 target: LspFormatTarget,
3621 push_to_history: bool,
3622 trigger: lsp_store::FormatTrigger,
3623 cx: &mut Context<Project>,
3624 ) -> Task<anyhow::Result<ProjectTransaction>> {
3625 self.lsp_store.update(cx, |lsp_store, cx| {
3626 lsp_store.format(buffers, target, push_to_history, trigger, cx)
3627 })
3628 }
3629
3630 pub fn definitions<T: ToPointUtf16>(
3631 &mut self,
3632 buffer: &Entity<Buffer>,
3633 position: T,
3634 cx: &mut Context<Self>,
3635 ) -> Task<Result<Option<Vec<LocationLink>>>> {
3636 let position = position.to_point_utf16(buffer.read(cx));
3637 let guard = self.retain_remotely_created_models(cx);
3638 let task = self.lsp_store.update(cx, |lsp_store, cx| {
3639 lsp_store.definitions(buffer, position, cx)
3640 });
3641 cx.background_spawn(async move {
3642 let result = task.await;
3643 drop(guard);
3644 result
3645 })
3646 }
3647
3648 pub fn declarations<T: ToPointUtf16>(
3649 &mut self,
3650 buffer: &Entity<Buffer>,
3651 position: T,
3652 cx: &mut Context<Self>,
3653 ) -> Task<Result<Option<Vec<LocationLink>>>> {
3654 let position = position.to_point_utf16(buffer.read(cx));
3655 let guard = self.retain_remotely_created_models(cx);
3656 let task = self.lsp_store.update(cx, |lsp_store, cx| {
3657 lsp_store.declarations(buffer, position, cx)
3658 });
3659 cx.background_spawn(async move {
3660 let result = task.await;
3661 drop(guard);
3662 result
3663 })
3664 }
3665
3666 pub fn type_definitions<T: ToPointUtf16>(
3667 &mut self,
3668 buffer: &Entity<Buffer>,
3669 position: T,
3670 cx: &mut Context<Self>,
3671 ) -> Task<Result<Option<Vec<LocationLink>>>> {
3672 let position = position.to_point_utf16(buffer.read(cx));
3673 let guard = self.retain_remotely_created_models(cx);
3674 let task = self.lsp_store.update(cx, |lsp_store, cx| {
3675 lsp_store.type_definitions(buffer, position, cx)
3676 });
3677 cx.background_spawn(async move {
3678 let result = task.await;
3679 drop(guard);
3680 result
3681 })
3682 }
3683
3684 pub fn implementations<T: ToPointUtf16>(
3685 &mut self,
3686 buffer: &Entity<Buffer>,
3687 position: T,
3688 cx: &mut Context<Self>,
3689 ) -> Task<Result<Option<Vec<LocationLink>>>> {
3690 let position = position.to_point_utf16(buffer.read(cx));
3691 let guard = self.retain_remotely_created_models(cx);
3692 let task = self.lsp_store.update(cx, |lsp_store, cx| {
3693 lsp_store.implementations(buffer, position, cx)
3694 });
3695 cx.background_spawn(async move {
3696 let result = task.await;
3697 drop(guard);
3698 result
3699 })
3700 }
3701
3702 pub fn references<T: ToPointUtf16>(
3703 &mut self,
3704 buffer: &Entity<Buffer>,
3705 position: T,
3706 cx: &mut Context<Self>,
3707 ) -> Task<Result<Option<Vec<Location>>>> {
3708 let position = position.to_point_utf16(buffer.read(cx));
3709 let guard = self.retain_remotely_created_models(cx);
3710 let task = self.lsp_store.update(cx, |lsp_store, cx| {
3711 lsp_store.references(buffer, position, cx)
3712 });
3713 cx.background_spawn(async move {
3714 let result = task.await;
3715 drop(guard);
3716 result
3717 })
3718 }
3719
3720 pub fn document_highlights<T: ToPointUtf16>(
3721 &mut self,
3722 buffer: &Entity<Buffer>,
3723 position: T,
3724 cx: &mut Context<Self>,
3725 ) -> Task<Result<Vec<DocumentHighlight>>> {
3726 let position = position.to_point_utf16(buffer.read(cx));
3727 self.request_lsp(
3728 buffer.clone(),
3729 LanguageServerToQuery::FirstCapable,
3730 GetDocumentHighlights { position },
3731 cx,
3732 )
3733 }
3734
3735 pub fn document_symbols(
3736 &mut self,
3737 buffer: &Entity<Buffer>,
3738 cx: &mut Context<Self>,
3739 ) -> Task<Result<Vec<DocumentSymbol>>> {
3740 self.request_lsp(
3741 buffer.clone(),
3742 LanguageServerToQuery::FirstCapable,
3743 GetDocumentSymbols,
3744 cx,
3745 )
3746 }
3747
3748 pub fn symbols(&self, query: &str, cx: &mut Context<Self>) -> Task<Result<Vec<Symbol>>> {
3749 self.lsp_store
3750 .update(cx, |lsp_store, cx| lsp_store.symbols(query, cx))
3751 }
3752
3753 pub fn open_buffer_for_symbol(
3754 &mut self,
3755 symbol: &Symbol,
3756 cx: &mut Context<Self>,
3757 ) -> Task<Result<Entity<Buffer>>> {
3758 self.lsp_store.update(cx, |lsp_store, cx| {
3759 lsp_store.open_buffer_for_symbol(symbol, cx)
3760 })
3761 }
3762
3763 pub fn open_server_settings(&mut self, cx: &mut Context<Self>) -> Task<Result<Entity<Buffer>>> {
3764 let guard = self.retain_remotely_created_models(cx);
3765 let Some(remote) = self.remote_client.as_ref() else {
3766 return Task::ready(Err(anyhow!("not an ssh project")));
3767 };
3768
3769 let proto_client = remote.read(cx).proto_client();
3770
3771 cx.spawn(async move |project, cx| {
3772 let buffer = proto_client
3773 .request(proto::OpenServerSettings {
3774 project_id: REMOTE_SERVER_PROJECT_ID,
3775 })
3776 .await?;
3777
3778 let buffer = project
3779 .update(cx, |project, cx| {
3780 project.buffer_store.update(cx, |buffer_store, cx| {
3781 anyhow::Ok(
3782 buffer_store
3783 .wait_for_remote_buffer(BufferId::new(buffer.buffer_id)?, cx),
3784 )
3785 })
3786 })??
3787 .await;
3788
3789 drop(guard);
3790 buffer
3791 })
3792 }
3793
3794 pub fn open_local_buffer_via_lsp(
3795 &mut self,
3796 abs_path: lsp::Uri,
3797 language_server_id: LanguageServerId,
3798 cx: &mut Context<Self>,
3799 ) -> Task<Result<Entity<Buffer>>> {
3800 self.lsp_store.update(cx, |lsp_store, cx| {
3801 lsp_store.open_local_buffer_via_lsp(abs_path, language_server_id, cx)
3802 })
3803 }
3804
3805 pub fn hover<T: ToPointUtf16>(
3806 &self,
3807 buffer: &Entity<Buffer>,
3808 position: T,
3809 cx: &mut Context<Self>,
3810 ) -> Task<Option<Vec<Hover>>> {
3811 let position = position.to_point_utf16(buffer.read(cx));
3812 self.lsp_store
3813 .update(cx, |lsp_store, cx| lsp_store.hover(buffer, position, cx))
3814 }
3815
3816 pub fn linked_edits(
3817 &self,
3818 buffer: &Entity<Buffer>,
3819 position: Anchor,
3820 cx: &mut Context<Self>,
3821 ) -> Task<Result<Vec<Range<Anchor>>>> {
3822 self.lsp_store.update(cx, |lsp_store, cx| {
3823 lsp_store.linked_edits(buffer, position, cx)
3824 })
3825 }
3826
3827 pub fn completions<T: ToOffset + ToPointUtf16>(
3828 &self,
3829 buffer: &Entity<Buffer>,
3830 position: T,
3831 context: CompletionContext,
3832 cx: &mut Context<Self>,
3833 ) -> Task<Result<Vec<CompletionResponse>>> {
3834 let position = position.to_point_utf16(buffer.read(cx));
3835 self.lsp_store.update(cx, |lsp_store, cx| {
3836 lsp_store.completions(buffer, position, context, cx)
3837 })
3838 }
3839
3840 pub fn code_actions<T: Clone + ToOffset>(
3841 &mut self,
3842 buffer_handle: &Entity<Buffer>,
3843 range: Range<T>,
3844 kinds: Option<Vec<CodeActionKind>>,
3845 cx: &mut Context<Self>,
3846 ) -> Task<Result<Option<Vec<CodeAction>>>> {
3847 let buffer = buffer_handle.read(cx);
3848 let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
3849 self.lsp_store.update(cx, |lsp_store, cx| {
3850 lsp_store.code_actions(buffer_handle, range, kinds, cx)
3851 })
3852 }
3853
3854 pub fn code_lens_actions<T: Clone + ToOffset>(
3855 &mut self,
3856 buffer: &Entity<Buffer>,
3857 range: Range<T>,
3858 cx: &mut Context<Self>,
3859 ) -> Task<Result<Option<Vec<CodeAction>>>> {
3860 let snapshot = buffer.read(cx).snapshot();
3861 let range = range.to_point(&snapshot);
3862 let range_start = snapshot.anchor_before(range.start);
3863 let range_end = if range.start == range.end {
3864 range_start
3865 } else {
3866 snapshot.anchor_after(range.end)
3867 };
3868 let range = range_start..range_end;
3869 let code_lens_actions = self
3870 .lsp_store
3871 .update(cx, |lsp_store, cx| lsp_store.code_lens_actions(buffer, cx));
3872
3873 cx.background_spawn(async move {
3874 let mut code_lens_actions = code_lens_actions
3875 .await
3876 .map_err(|e| anyhow!("code lens fetch failed: {e:#}"))?;
3877 if let Some(code_lens_actions) = &mut code_lens_actions {
3878 code_lens_actions.retain(|code_lens_action| {
3879 range
3880 .start
3881 .cmp(&code_lens_action.range.start, &snapshot)
3882 .is_ge()
3883 && range
3884 .end
3885 .cmp(&code_lens_action.range.end, &snapshot)
3886 .is_le()
3887 });
3888 }
3889 Ok(code_lens_actions)
3890 })
3891 }
3892
3893 pub fn apply_code_action(
3894 &self,
3895 buffer_handle: Entity<Buffer>,
3896 action: CodeAction,
3897 push_to_history: bool,
3898 cx: &mut Context<Self>,
3899 ) -> Task<Result<ProjectTransaction>> {
3900 self.lsp_store.update(cx, |lsp_store, cx| {
3901 lsp_store.apply_code_action(buffer_handle, action, push_to_history, cx)
3902 })
3903 }
3904
3905 pub fn apply_code_action_kind(
3906 &self,
3907 buffers: HashSet<Entity<Buffer>>,
3908 kind: CodeActionKind,
3909 push_to_history: bool,
3910 cx: &mut Context<Self>,
3911 ) -> Task<Result<ProjectTransaction>> {
3912 self.lsp_store.update(cx, |lsp_store, cx| {
3913 lsp_store.apply_code_action_kind(buffers, kind, push_to_history, cx)
3914 })
3915 }
3916
3917 pub fn prepare_rename<T: ToPointUtf16>(
3918 &mut self,
3919 buffer: Entity<Buffer>,
3920 position: T,
3921 cx: &mut Context<Self>,
3922 ) -> Task<Result<PrepareRenameResponse>> {
3923 let position = position.to_point_utf16(buffer.read(cx));
3924 self.request_lsp(
3925 buffer,
3926 LanguageServerToQuery::FirstCapable,
3927 PrepareRename { position },
3928 cx,
3929 )
3930 }
3931
3932 pub fn perform_rename<T: ToPointUtf16>(
3933 &mut self,
3934 buffer: Entity<Buffer>,
3935 position: T,
3936 new_name: String,
3937 cx: &mut Context<Self>,
3938 ) -> Task<Result<ProjectTransaction>> {
3939 let push_to_history = true;
3940 let position = position.to_point_utf16(buffer.read(cx));
3941 self.request_lsp(
3942 buffer,
3943 LanguageServerToQuery::FirstCapable,
3944 PerformRename {
3945 position,
3946 new_name,
3947 push_to_history,
3948 },
3949 cx,
3950 )
3951 }
3952
3953 pub fn on_type_format<T: ToPointUtf16>(
3954 &mut self,
3955 buffer: Entity<Buffer>,
3956 position: T,
3957 trigger: String,
3958 push_to_history: bool,
3959 cx: &mut Context<Self>,
3960 ) -> Task<Result<Option<Transaction>>> {
3961 self.lsp_store.update(cx, |lsp_store, cx| {
3962 lsp_store.on_type_format(buffer, position, trigger, push_to_history, cx)
3963 })
3964 }
3965
3966 pub fn inline_values(
3967 &mut self,
3968 session: Entity<Session>,
3969 active_stack_frame: ActiveStackFrame,
3970 buffer_handle: Entity<Buffer>,
3971 range: Range<text::Anchor>,
3972 cx: &mut Context<Self>,
3973 ) -> Task<anyhow::Result<Vec<InlayHint>>> {
3974 let snapshot = buffer_handle.read(cx).snapshot();
3975
3976 let captures = snapshot.debug_variables_query(Anchor::MIN..range.end);
3977
3978 let row = snapshot
3979 .summary_for_anchor::<text::PointUtf16>(&range.end)
3980 .row as usize;
3981
3982 let inline_value_locations = provide_inline_values(captures, &snapshot, row);
3983
3984 let stack_frame_id = active_stack_frame.stack_frame_id;
3985 cx.spawn(async move |this, cx| {
3986 this.update(cx, |project, cx| {
3987 project.dap_store().update(cx, |dap_store, cx| {
3988 dap_store.resolve_inline_value_locations(
3989 session,
3990 stack_frame_id,
3991 buffer_handle,
3992 inline_value_locations,
3993 cx,
3994 )
3995 })
3996 })?
3997 .await
3998 })
3999 }
4000
4001 pub fn search(&mut self, query: SearchQuery, cx: &mut Context<Self>) -> Receiver<SearchResult> {
4002 let (result_tx, result_rx) = smol::channel::unbounded();
4003
4004 let matching_buffers_rx = if query.is_opened_only() {
4005 self.sort_search_candidates(&query, cx)
4006 } else {
4007 self.find_search_candidate_buffers(&query, MAX_SEARCH_RESULT_FILES + 1, cx)
4008 };
4009
4010 cx.spawn(async move |_, cx| {
4011 let mut range_count = 0;
4012 let mut buffer_count = 0;
4013 let mut limit_reached = false;
4014 let query = Arc::new(query);
4015 let chunks = matching_buffers_rx.ready_chunks(64);
4016
4017 // Now that we know what paths match the query, we will load at most
4018 // 64 buffers at a time to avoid overwhelming the main thread. For each
4019 // opened buffer, we will spawn a background task that retrieves all the
4020 // ranges in the buffer matched by the query.
4021 let mut chunks = pin!(chunks);
4022 'outer: while let Some(matching_buffer_chunk) = chunks.next().await {
4023 let mut chunk_results = Vec::with_capacity(matching_buffer_chunk.len());
4024 for buffer in matching_buffer_chunk {
4025 let query = query.clone();
4026 let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot())?;
4027 chunk_results.push(cx.background_spawn(async move {
4028 let ranges = query
4029 .search(&snapshot, None)
4030 .await
4031 .iter()
4032 .map(|range| {
4033 snapshot.anchor_before(range.start)
4034 ..snapshot.anchor_after(range.end)
4035 })
4036 .collect::<Vec<_>>();
4037 anyhow::Ok((buffer, ranges))
4038 }));
4039 }
4040
4041 let chunk_results = futures::future::join_all(chunk_results).await;
4042 for result in chunk_results {
4043 if let Some((buffer, ranges)) = result.log_err() {
4044 range_count += ranges.len();
4045 buffer_count += 1;
4046 result_tx
4047 .send(SearchResult::Buffer { buffer, ranges })
4048 .await?;
4049 if buffer_count > MAX_SEARCH_RESULT_FILES
4050 || range_count > MAX_SEARCH_RESULT_RANGES
4051 {
4052 limit_reached = true;
4053 break 'outer;
4054 }
4055 }
4056 }
4057 }
4058
4059 if limit_reached {
4060 result_tx.send(SearchResult::LimitReached).await?;
4061 }
4062
4063 anyhow::Ok(())
4064 })
4065 .detach();
4066
4067 result_rx
4068 }
4069
4070 pub fn find_search_candidate_buffers(
4071 &mut self,
4072 query: &SearchQuery,
4073 limit: usize,
4074 cx: &mut Context<Project>,
4075 ) -> Receiver<Entity<Buffer>> {
4076 if self.is_local() {
4077 let fs = self.fs.clone();
4078 self.buffer_store.update(cx, |buffer_store, cx| {
4079 buffer_store.find_search_candidates(query, limit, fs, cx)
4080 })
4081 } else {
4082 self.find_search_candidates_remote(query, limit, cx)
4083 }
4084 }
4085
4086 fn sort_search_candidates(
4087 &mut self,
4088 search_query: &SearchQuery,
4089 cx: &mut Context<Project>,
4090 ) -> Receiver<Entity<Buffer>> {
4091 let worktree_store = self.worktree_store.read(cx);
4092 let mut buffers = search_query
4093 .buffers()
4094 .into_iter()
4095 .flatten()
4096 .filter(|buffer| {
4097 let b = buffer.read(cx);
4098 if let Some(file) = b.file() {
4099 if !search_query.match_path(file.path().as_std_path()) {
4100 return false;
4101 }
4102 if let Some(entry) = b
4103 .entry_id(cx)
4104 .and_then(|entry_id| worktree_store.entry_for_id(entry_id, cx))
4105 && entry.is_ignored
4106 && !search_query.include_ignored()
4107 {
4108 return false;
4109 }
4110 }
4111 true
4112 })
4113 .collect::<Vec<_>>();
4114 let (tx, rx) = smol::channel::unbounded();
4115 buffers.sort_by(|a, b| match (a.read(cx).file(), b.read(cx).file()) {
4116 (None, None) => a.read(cx).remote_id().cmp(&b.read(cx).remote_id()),
4117 (None, Some(_)) => std::cmp::Ordering::Less,
4118 (Some(_), None) => std::cmp::Ordering::Greater,
4119 (Some(a), Some(b)) => compare_paths(
4120 (a.path().as_std_path(), true),
4121 (b.path().as_std_path(), true),
4122 ),
4123 });
4124 for buffer in buffers {
4125 tx.send_blocking(buffer.clone()).unwrap()
4126 }
4127
4128 rx
4129 }
4130
4131 fn find_search_candidates_remote(
4132 &mut self,
4133 query: &SearchQuery,
4134 limit: usize,
4135 cx: &mut Context<Project>,
4136 ) -> Receiver<Entity<Buffer>> {
4137 let (tx, rx) = smol::channel::unbounded();
4138
4139 let (client, remote_id): (AnyProtoClient, _) = if let Some(ssh_client) = &self.remote_client
4140 {
4141 (ssh_client.read(cx).proto_client(), 0)
4142 } else if let Some(remote_id) = self.remote_id() {
4143 (self.collab_client.clone().into(), remote_id)
4144 } else {
4145 return rx;
4146 };
4147
4148 let request = client.request(proto::FindSearchCandidates {
4149 project_id: remote_id,
4150 query: Some(query.to_proto()),
4151 limit: limit as _,
4152 });
4153 let guard = self.retain_remotely_created_models(cx);
4154
4155 cx.spawn(async move |project, cx| {
4156 let response = request.await?;
4157 for buffer_id in response.buffer_ids {
4158 let buffer_id = BufferId::new(buffer_id)?;
4159 let buffer = project
4160 .update(cx, |project, cx| {
4161 project.buffer_store.update(cx, |buffer_store, cx| {
4162 buffer_store.wait_for_remote_buffer(buffer_id, cx)
4163 })
4164 })?
4165 .await?;
4166 let _ = tx.send(buffer).await;
4167 }
4168
4169 drop(guard);
4170 anyhow::Ok(())
4171 })
4172 .detach_and_log_err(cx);
4173 rx
4174 }
4175
4176 pub fn request_lsp<R: LspCommand>(
4177 &mut self,
4178 buffer_handle: Entity<Buffer>,
4179 server: LanguageServerToQuery,
4180 request: R,
4181 cx: &mut Context<Self>,
4182 ) -> Task<Result<R::Response>>
4183 where
4184 <R::LspRequest as lsp::request::Request>::Result: Send,
4185 <R::LspRequest as lsp::request::Request>::Params: Send,
4186 {
4187 let guard = self.retain_remotely_created_models(cx);
4188 let task = self.lsp_store.update(cx, |lsp_store, cx| {
4189 lsp_store.request_lsp(buffer_handle, server, request, cx)
4190 });
4191 cx.background_spawn(async move {
4192 let result = task.await;
4193 drop(guard);
4194 result
4195 })
4196 }
4197
4198 /// Move a worktree to a new position in the worktree order.
4199 ///
4200 /// The worktree will moved to the opposite side of the destination worktree.
4201 ///
4202 /// # Example
4203 ///
4204 /// Given the worktree order `[11, 22, 33]` and a call to move worktree `22` to `33`,
4205 /// worktree_order will be updated to produce the indexes `[11, 33, 22]`.
4206 ///
4207 /// Given the worktree order `[11, 22, 33]` and a call to move worktree `22` to `11`,
4208 /// worktree_order will be updated to produce the indexes `[22, 11, 33]`.
4209 ///
4210 /// # Errors
4211 ///
4212 /// An error will be returned if the worktree or destination worktree are not found.
4213 pub fn move_worktree(
4214 &mut self,
4215 source: WorktreeId,
4216 destination: WorktreeId,
4217 cx: &mut Context<Self>,
4218 ) -> Result<()> {
4219 self.worktree_store.update(cx, |worktree_store, cx| {
4220 worktree_store.move_worktree(source, destination, cx)
4221 })
4222 }
4223
4224 pub fn find_or_create_worktree(
4225 &mut self,
4226 abs_path: impl AsRef<Path>,
4227 visible: bool,
4228 cx: &mut Context<Self>,
4229 ) -> Task<Result<(Entity<Worktree>, Arc<RelPath>)>> {
4230 self.worktree_store.update(cx, |worktree_store, cx| {
4231 worktree_store.find_or_create_worktree(abs_path, visible, cx)
4232 })
4233 }
4234
4235 pub fn find_worktree(
4236 &self,
4237 abs_path: &Path,
4238 cx: &App,
4239 ) -> Option<(Entity<Worktree>, Arc<RelPath>)> {
4240 self.worktree_store.read(cx).find_worktree(abs_path, cx)
4241 }
4242
4243 pub fn is_shared(&self) -> bool {
4244 match &self.client_state {
4245 ProjectClientState::Shared { .. } => true,
4246 ProjectClientState::Local => false,
4247 ProjectClientState::Remote { .. } => true,
4248 }
4249 }
4250
4251 /// Returns the resolved version of `path`, that was found in `buffer`, if it exists.
4252 pub fn resolve_path_in_buffer(
4253 &self,
4254 path: &str,
4255 buffer: &Entity<Buffer>,
4256 cx: &mut Context<Self>,
4257 ) -> Task<Option<ResolvedPath>> {
4258 if util::paths::is_absolute(path, self.path_style(cx)) || path.starts_with("~") {
4259 self.resolve_abs_path(path, cx)
4260 } else {
4261 self.resolve_path_in_worktrees(path, buffer, cx)
4262 }
4263 }
4264
4265 pub fn resolve_abs_file_path(
4266 &self,
4267 path: &str,
4268 cx: &mut Context<Self>,
4269 ) -> Task<Option<ResolvedPath>> {
4270 let resolve_task = self.resolve_abs_path(path, cx);
4271 cx.background_spawn(async move {
4272 let resolved_path = resolve_task.await;
4273 resolved_path.filter(|path| path.is_file())
4274 })
4275 }
4276
4277 pub fn resolve_abs_path(&self, path: &str, cx: &App) -> Task<Option<ResolvedPath>> {
4278 if self.is_local() {
4279 let expanded = PathBuf::from(shellexpand::tilde(&path).into_owned());
4280 let fs = self.fs.clone();
4281 cx.background_spawn(async move {
4282 let metadata = fs.metadata(&expanded).await.ok().flatten();
4283
4284 metadata.map(|metadata| ResolvedPath::AbsPath {
4285 path: expanded.to_string_lossy().into_owned(),
4286 is_dir: metadata.is_dir,
4287 })
4288 })
4289 } else if let Some(ssh_client) = self.remote_client.as_ref() {
4290 let request = ssh_client
4291 .read(cx)
4292 .proto_client()
4293 .request(proto::GetPathMetadata {
4294 project_id: REMOTE_SERVER_PROJECT_ID,
4295 path: path.into(),
4296 });
4297 cx.background_spawn(async move {
4298 let response = request.await.log_err()?;
4299 if response.exists {
4300 Some(ResolvedPath::AbsPath {
4301 path: response.path,
4302 is_dir: response.is_dir,
4303 })
4304 } else {
4305 None
4306 }
4307 })
4308 } else {
4309 Task::ready(None)
4310 }
4311 }
4312
4313 fn resolve_path_in_worktrees(
4314 &self,
4315 path: &str,
4316 buffer: &Entity<Buffer>,
4317 cx: &mut Context<Self>,
4318 ) -> Task<Option<ResolvedPath>> {
4319 let mut candidates = vec![];
4320 let path_style = self.path_style(cx);
4321 if let Ok(path) = RelPath::new(path.as_ref(), path_style) {
4322 candidates.push(path.into_arc());
4323 }
4324
4325 if let Some(file) = buffer.read(cx).file()
4326 && let Some(dir) = file.path().parent()
4327 {
4328 if let Some(joined) = path_style.join(&*dir.display(path_style), path)
4329 && let Some(joined) = RelPath::new(joined.as_ref(), path_style).ok()
4330 {
4331 candidates.push(joined.into_arc());
4332 }
4333 }
4334
4335 let buffer_worktree_id = buffer.read(cx).file().map(|file| file.worktree_id(cx));
4336 let worktrees_with_ids: Vec<_> = self
4337 .worktrees(cx)
4338 .map(|worktree| {
4339 let id = worktree.read(cx).id();
4340 (worktree, id)
4341 })
4342 .collect();
4343
4344 cx.spawn(async move |_, cx| {
4345 if let Some(buffer_worktree_id) = buffer_worktree_id
4346 && let Some((worktree, _)) = worktrees_with_ids
4347 .iter()
4348 .find(|(_, id)| *id == buffer_worktree_id)
4349 {
4350 for candidate in candidates.iter() {
4351 if let Some(path) = Self::resolve_path_in_worktree(worktree, candidate, cx) {
4352 return Some(path);
4353 }
4354 }
4355 }
4356 for (worktree, id) in worktrees_with_ids {
4357 if Some(id) == buffer_worktree_id {
4358 continue;
4359 }
4360 for candidate in candidates.iter() {
4361 if let Some(path) = Self::resolve_path_in_worktree(&worktree, candidate, cx) {
4362 return Some(path);
4363 }
4364 }
4365 }
4366 None
4367 })
4368 }
4369
4370 fn resolve_path_in_worktree(
4371 worktree: &Entity<Worktree>,
4372 path: &RelPath,
4373 cx: &mut AsyncApp,
4374 ) -> Option<ResolvedPath> {
4375 worktree
4376 .read_with(cx, |worktree, _| {
4377 worktree.entry_for_path(path).map(|entry| {
4378 let project_path = ProjectPath {
4379 worktree_id: worktree.id(),
4380 path: entry.path.clone(),
4381 };
4382 ResolvedPath::ProjectPath {
4383 project_path,
4384 is_dir: entry.is_dir(),
4385 }
4386 })
4387 })
4388 .ok()?
4389 }
4390
4391 pub fn list_directory(
4392 &self,
4393 query: String,
4394 cx: &mut Context<Self>,
4395 ) -> Task<Result<Vec<DirectoryItem>>> {
4396 if self.is_local() {
4397 DirectoryLister::Local(cx.entity(), self.fs.clone()).list_directory(query, cx)
4398 } else if let Some(session) = self.remote_client.as_ref() {
4399 let request = proto::ListRemoteDirectory {
4400 dev_server_id: REMOTE_SERVER_PROJECT_ID,
4401 path: query,
4402 config: Some(proto::ListRemoteDirectoryConfig { is_dir: true }),
4403 };
4404
4405 let response = session.read(cx).proto_client().request(request);
4406 cx.background_spawn(async move {
4407 let proto::ListRemoteDirectoryResponse {
4408 entries,
4409 entry_info,
4410 } = response.await?;
4411 Ok(entries
4412 .into_iter()
4413 .zip(entry_info)
4414 .map(|(entry, info)| DirectoryItem {
4415 path: PathBuf::from(entry),
4416 is_dir: info.is_dir,
4417 })
4418 .collect())
4419 })
4420 } else {
4421 Task::ready(Err(anyhow!("cannot list directory in remote project")))
4422 }
4423 }
4424
4425 pub fn create_worktree(
4426 &mut self,
4427 abs_path: impl AsRef<Path>,
4428 visible: bool,
4429 cx: &mut Context<Self>,
4430 ) -> Task<Result<Entity<Worktree>>> {
4431 self.worktree_store.update(cx, |worktree_store, cx| {
4432 worktree_store.create_worktree(abs_path, visible, cx)
4433 })
4434 }
4435
4436 pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut Context<Self>) {
4437 self.worktree_store.update(cx, |worktree_store, cx| {
4438 worktree_store.remove_worktree(id_to_remove, cx);
4439 });
4440 }
4441
4442 fn add_worktree(&mut self, worktree: &Entity<Worktree>, cx: &mut Context<Self>) {
4443 self.worktree_store.update(cx, |worktree_store, cx| {
4444 worktree_store.add(worktree, cx);
4445 });
4446 }
4447
4448 pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut Context<Self>) {
4449 let new_active_entry = entry.and_then(|project_path| {
4450 let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
4451 let entry = worktree.read(cx).entry_for_path(&project_path.path)?;
4452 Some(entry.id)
4453 });
4454 if new_active_entry != self.active_entry {
4455 self.active_entry = new_active_entry;
4456 self.lsp_store.update(cx, |lsp_store, _| {
4457 lsp_store.set_active_entry(new_active_entry);
4458 });
4459 cx.emit(Event::ActiveEntryChanged(new_active_entry));
4460 }
4461 }
4462
4463 pub fn language_servers_running_disk_based_diagnostics<'a>(
4464 &'a self,
4465 cx: &'a App,
4466 ) -> impl Iterator<Item = LanguageServerId> + 'a {
4467 self.lsp_store
4468 .read(cx)
4469 .language_servers_running_disk_based_diagnostics()
4470 }
4471
4472 pub fn diagnostic_summary(&self, include_ignored: bool, cx: &App) -> DiagnosticSummary {
4473 self.lsp_store
4474 .read(cx)
4475 .diagnostic_summary(include_ignored, cx)
4476 }
4477
4478 /// Returns a summary of the diagnostics for the provided project path only.
4479 pub fn diagnostic_summary_for_path(&self, path: &ProjectPath, cx: &App) -> DiagnosticSummary {
4480 self.lsp_store
4481 .read(cx)
4482 .diagnostic_summary_for_path(path, cx)
4483 }
4484
4485 pub fn diagnostic_summaries<'a>(
4486 &'a self,
4487 include_ignored: bool,
4488 cx: &'a App,
4489 ) -> impl Iterator<Item = (ProjectPath, LanguageServerId, DiagnosticSummary)> + 'a {
4490 self.lsp_store
4491 .read(cx)
4492 .diagnostic_summaries(include_ignored, cx)
4493 }
4494
4495 pub fn active_entry(&self) -> Option<ProjectEntryId> {
4496 self.active_entry
4497 }
4498
4499 pub fn entry_for_path<'a>(&'a self, path: &ProjectPath, cx: &'a App) -> Option<&'a Entry> {
4500 self.worktree_store.read(cx).entry_for_path(path, cx)
4501 }
4502
4503 pub fn path_for_entry(&self, entry_id: ProjectEntryId, cx: &App) -> Option<ProjectPath> {
4504 let worktree = self.worktree_for_entry(entry_id, cx)?;
4505 let worktree = worktree.read(cx);
4506 let worktree_id = worktree.id();
4507 let path = worktree.entry_for_id(entry_id)?.path.clone();
4508 Some(ProjectPath { worktree_id, path })
4509 }
4510
4511 pub fn absolute_path(&self, project_path: &ProjectPath, cx: &App) -> Option<PathBuf> {
4512 Some(
4513 self.worktree_for_id(project_path.worktree_id, cx)?
4514 .read(cx)
4515 .absolutize(&project_path.path),
4516 )
4517 }
4518
4519 /// Attempts to find a `ProjectPath` corresponding to the given path. If the path
4520 /// is a *full path*, meaning it starts with the root name of a worktree, we'll locate
4521 /// it in that worktree. Otherwise, we'll attempt to find it as a relative path in
4522 /// the first visible worktree that has an entry for that relative path.
4523 ///
4524 /// We use this to resolve edit steps, when there's a chance an LLM may omit the workree
4525 /// root name from paths.
4526 ///
4527 /// # Arguments
4528 ///
4529 /// * `path` - An absolute path, or a full path that starts with a worktree root name, or a
4530 /// relative path within a visible worktree.
4531 /// * `cx` - A reference to the `AppContext`.
4532 ///
4533 /// # Returns
4534 ///
4535 /// Returns `Some(ProjectPath)` if a matching worktree is found, otherwise `None`.
4536 pub fn find_project_path(&self, path: impl AsRef<Path>, cx: &App) -> Option<ProjectPath> {
4537 let path_style = self.path_style(cx);
4538 let path = path.as_ref();
4539 let worktree_store = self.worktree_store.read(cx);
4540
4541 if is_absolute(&path.to_string_lossy(), path_style) {
4542 for worktree in worktree_store.visible_worktrees(cx) {
4543 let worktree_abs_path = worktree.read(cx).abs_path();
4544
4545 if let Ok(relative_path) = path.strip_prefix(worktree_abs_path)
4546 && let Ok(path) = RelPath::new(relative_path, path_style)
4547 {
4548 return Some(ProjectPath {
4549 worktree_id: worktree.read(cx).id(),
4550 path: path.into_arc(),
4551 });
4552 }
4553 }
4554 } else {
4555 for worktree in worktree_store.visible_worktrees(cx) {
4556 let worktree_root_name = worktree.read(cx).root_name();
4557 if let Ok(relative_path) = path.strip_prefix(worktree_root_name.as_std_path())
4558 && let Ok(path) = RelPath::new(relative_path, path_style)
4559 {
4560 return Some(ProjectPath {
4561 worktree_id: worktree.read(cx).id(),
4562 path: path.into_arc(),
4563 });
4564 }
4565 }
4566
4567 for worktree in worktree_store.visible_worktrees(cx) {
4568 let worktree = worktree.read(cx);
4569 if let Ok(path) = RelPath::new(path, path_style)
4570 && let Some(entry) = worktree.entry_for_path(&path)
4571 {
4572 return Some(ProjectPath {
4573 worktree_id: worktree.id(),
4574 path: entry.path.clone(),
4575 });
4576 }
4577 }
4578 }
4579
4580 None
4581 }
4582
4583 /// If there's only one visible worktree, returns the given worktree-relative path with no prefix.
4584 ///
4585 /// Otherwise, returns the full path for the project path (obtained by prefixing the worktree-relative path with the name of the worktree).
4586 pub fn short_full_path_for_project_path(
4587 &self,
4588 project_path: &ProjectPath,
4589 cx: &App,
4590 ) -> Option<String> {
4591 let path_style = self.path_style(cx);
4592 if self.visible_worktrees(cx).take(2).count() < 2 {
4593 return Some(project_path.path.display(path_style).to_string());
4594 }
4595 self.worktree_for_id(project_path.worktree_id, cx)
4596 .map(|worktree| {
4597 let worktree_name = worktree.read(cx).root_name();
4598 worktree_name
4599 .join(&project_path.path)
4600 .display(path_style)
4601 .to_string()
4602 })
4603 }
4604
4605 pub fn project_path_for_absolute_path(&self, abs_path: &Path, cx: &App) -> Option<ProjectPath> {
4606 self.find_worktree(abs_path, cx)
4607 .map(|(worktree, relative_path)| ProjectPath {
4608 worktree_id: worktree.read(cx).id(),
4609 path: relative_path,
4610 })
4611 }
4612
4613 pub fn get_workspace_root(&self, project_path: &ProjectPath, cx: &App) -> Option<PathBuf> {
4614 Some(
4615 self.worktree_for_id(project_path.worktree_id, cx)?
4616 .read(cx)
4617 .abs_path()
4618 .to_path_buf(),
4619 )
4620 }
4621
4622 pub fn blame_buffer(
4623 &self,
4624 buffer: &Entity<Buffer>,
4625 version: Option<clock::Global>,
4626 cx: &mut App,
4627 ) -> Task<Result<Option<Blame>>> {
4628 self.git_store.update(cx, |git_store, cx| {
4629 git_store.blame_buffer(buffer, version, cx)
4630 })
4631 }
4632
4633 pub fn get_permalink_to_line(
4634 &self,
4635 buffer: &Entity<Buffer>,
4636 selection: Range<u32>,
4637 cx: &mut App,
4638 ) -> Task<Result<url::Url>> {
4639 self.git_store.update(cx, |git_store, cx| {
4640 git_store.get_permalink_to_line(buffer, selection, cx)
4641 })
4642 }
4643
4644 // RPC message handlers
4645
4646 async fn handle_unshare_project(
4647 this: Entity<Self>,
4648 _: TypedEnvelope<proto::UnshareProject>,
4649 mut cx: AsyncApp,
4650 ) -> Result<()> {
4651 this.update(&mut cx, |this, cx| {
4652 if this.is_local() || this.is_via_remote_server() {
4653 this.unshare(cx)?;
4654 } else {
4655 this.disconnected_from_host(cx);
4656 }
4657 Ok(())
4658 })?
4659 }
4660
4661 async fn handle_add_collaborator(
4662 this: Entity<Self>,
4663 mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
4664 mut cx: AsyncApp,
4665 ) -> Result<()> {
4666 let collaborator = envelope
4667 .payload
4668 .collaborator
4669 .take()
4670 .context("empty collaborator")?;
4671
4672 let collaborator = Collaborator::from_proto(collaborator)?;
4673 this.update(&mut cx, |this, cx| {
4674 this.buffer_store.update(cx, |buffer_store, _| {
4675 buffer_store.forget_shared_buffers_for(&collaborator.peer_id);
4676 });
4677 this.breakpoint_store.read(cx).broadcast();
4678 cx.emit(Event::CollaboratorJoined(collaborator.peer_id));
4679 this.collaborators
4680 .insert(collaborator.peer_id, collaborator);
4681 })?;
4682
4683 Ok(())
4684 }
4685
4686 async fn handle_update_project_collaborator(
4687 this: Entity<Self>,
4688 envelope: TypedEnvelope<proto::UpdateProjectCollaborator>,
4689 mut cx: AsyncApp,
4690 ) -> Result<()> {
4691 let old_peer_id = envelope
4692 .payload
4693 .old_peer_id
4694 .context("missing old peer id")?;
4695 let new_peer_id = envelope
4696 .payload
4697 .new_peer_id
4698 .context("missing new peer id")?;
4699 this.update(&mut cx, |this, cx| {
4700 let collaborator = this
4701 .collaborators
4702 .remove(&old_peer_id)
4703 .context("received UpdateProjectCollaborator for unknown peer")?;
4704 let is_host = collaborator.is_host;
4705 this.collaborators.insert(new_peer_id, collaborator);
4706
4707 log::info!("peer {} became {}", old_peer_id, new_peer_id,);
4708 this.buffer_store.update(cx, |buffer_store, _| {
4709 buffer_store.update_peer_id(&old_peer_id, new_peer_id)
4710 });
4711
4712 if is_host {
4713 this.buffer_store
4714 .update(cx, |buffer_store, _| buffer_store.discard_incomplete());
4715 this.enqueue_buffer_ordered_message(BufferOrderedMessage::Resync)
4716 .unwrap();
4717 cx.emit(Event::HostReshared);
4718 }
4719
4720 cx.emit(Event::CollaboratorUpdated {
4721 old_peer_id,
4722 new_peer_id,
4723 });
4724 Ok(())
4725 })?
4726 }
4727
4728 async fn handle_remove_collaborator(
4729 this: Entity<Self>,
4730 envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
4731 mut cx: AsyncApp,
4732 ) -> Result<()> {
4733 this.update(&mut cx, |this, cx| {
4734 let peer_id = envelope.payload.peer_id.context("invalid peer id")?;
4735 let replica_id = this
4736 .collaborators
4737 .remove(&peer_id)
4738 .with_context(|| format!("unknown peer {peer_id:?}"))?
4739 .replica_id;
4740 this.buffer_store.update(cx, |buffer_store, cx| {
4741 buffer_store.forget_shared_buffers_for(&peer_id);
4742 for buffer in buffer_store.buffers() {
4743 buffer.update(cx, |buffer, cx| buffer.remove_peer(replica_id, cx));
4744 }
4745 });
4746 this.git_store.update(cx, |git_store, _| {
4747 git_store.forget_shared_diffs_for(&peer_id);
4748 });
4749
4750 cx.emit(Event::CollaboratorLeft(peer_id));
4751 Ok(())
4752 })?
4753 }
4754
4755 async fn handle_update_project(
4756 this: Entity<Self>,
4757 envelope: TypedEnvelope<proto::UpdateProject>,
4758 mut cx: AsyncApp,
4759 ) -> Result<()> {
4760 this.update(&mut cx, |this, cx| {
4761 // Don't handle messages that were sent before the response to us joining the project
4762 if envelope.message_id > this.join_project_response_message_id {
4763 this.set_worktrees_from_proto(envelope.payload.worktrees, cx)?;
4764 }
4765 Ok(())
4766 })?
4767 }
4768
4769 async fn handle_toast(
4770 this: Entity<Self>,
4771 envelope: TypedEnvelope<proto::Toast>,
4772 mut cx: AsyncApp,
4773 ) -> Result<()> {
4774 this.update(&mut cx, |_, cx| {
4775 cx.emit(Event::Toast {
4776 notification_id: envelope.payload.notification_id.into(),
4777 message: envelope.payload.message,
4778 });
4779 Ok(())
4780 })?
4781 }
4782
4783 async fn handle_language_server_prompt_request(
4784 this: Entity<Self>,
4785 envelope: TypedEnvelope<proto::LanguageServerPromptRequest>,
4786 mut cx: AsyncApp,
4787 ) -> Result<proto::LanguageServerPromptResponse> {
4788 let (tx, rx) = smol::channel::bounded(1);
4789 let actions: Vec<_> = envelope
4790 .payload
4791 .actions
4792 .into_iter()
4793 .map(|action| MessageActionItem {
4794 title: action,
4795 properties: Default::default(),
4796 })
4797 .collect();
4798 this.update(&mut cx, |_, cx| {
4799 cx.emit(Event::LanguageServerPrompt(LanguageServerPromptRequest {
4800 level: proto_to_prompt(envelope.payload.level.context("Invalid prompt level")?),
4801 message: envelope.payload.message,
4802 actions: actions.clone(),
4803 lsp_name: envelope.payload.lsp_name,
4804 response_channel: tx,
4805 }));
4806
4807 anyhow::Ok(())
4808 })??;
4809
4810 // We drop `this` to avoid holding a reference in this future for too
4811 // long.
4812 // If we keep the reference, we might not drop the `Project` early
4813 // enough when closing a window and it will only get releases on the
4814 // next `flush_effects()` call.
4815 drop(this);
4816
4817 let mut rx = pin!(rx);
4818 let answer = rx.next().await;
4819
4820 Ok(LanguageServerPromptResponse {
4821 action_response: answer.and_then(|answer| {
4822 actions
4823 .iter()
4824 .position(|action| *action == answer)
4825 .map(|index| index as u64)
4826 }),
4827 })
4828 }
4829
4830 async fn handle_hide_toast(
4831 this: Entity<Self>,
4832 envelope: TypedEnvelope<proto::HideToast>,
4833 mut cx: AsyncApp,
4834 ) -> Result<()> {
4835 this.update(&mut cx, |_, cx| {
4836 cx.emit(Event::HideToast {
4837 notification_id: envelope.payload.notification_id.into(),
4838 });
4839 Ok(())
4840 })?
4841 }
4842
4843 // Collab sends UpdateWorktree protos as messages
4844 async fn handle_update_worktree(
4845 this: Entity<Self>,
4846 envelope: TypedEnvelope<proto::UpdateWorktree>,
4847 mut cx: AsyncApp,
4848 ) -> Result<()> {
4849 this.update(&mut cx, |this, cx| {
4850 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
4851 if let Some(worktree) = this.worktree_for_id(worktree_id, cx) {
4852 worktree.update(cx, |worktree, _| {
4853 let worktree = worktree.as_remote_mut().unwrap();
4854 worktree.update_from_remote(envelope.payload);
4855 });
4856 }
4857 Ok(())
4858 })?
4859 }
4860
4861 async fn handle_update_buffer_from_remote_server(
4862 this: Entity<Self>,
4863 envelope: TypedEnvelope<proto::UpdateBuffer>,
4864 cx: AsyncApp,
4865 ) -> Result<proto::Ack> {
4866 let buffer_store = this.read_with(&cx, |this, cx| {
4867 if let Some(remote_id) = this.remote_id() {
4868 let mut payload = envelope.payload.clone();
4869 payload.project_id = remote_id;
4870 cx.background_spawn(this.collab_client.request(payload))
4871 .detach_and_log_err(cx);
4872 }
4873 this.buffer_store.clone()
4874 })?;
4875 BufferStore::handle_update_buffer(buffer_store, envelope, cx).await
4876 }
4877
4878 async fn handle_update_buffer(
4879 this: Entity<Self>,
4880 envelope: TypedEnvelope<proto::UpdateBuffer>,
4881 cx: AsyncApp,
4882 ) -> Result<proto::Ack> {
4883 let buffer_store = this.read_with(&cx, |this, cx| {
4884 if let Some(ssh) = &this.remote_client {
4885 let mut payload = envelope.payload.clone();
4886 payload.project_id = REMOTE_SERVER_PROJECT_ID;
4887 cx.background_spawn(ssh.read(cx).proto_client().request(payload))
4888 .detach_and_log_err(cx);
4889 }
4890 this.buffer_store.clone()
4891 })?;
4892 BufferStore::handle_update_buffer(buffer_store, envelope, cx).await
4893 }
4894
4895 fn retain_remotely_created_models(
4896 &mut self,
4897 cx: &mut Context<Self>,
4898 ) -> RemotelyCreatedModelGuard {
4899 {
4900 let mut remotely_create_models = self.remotely_created_models.lock();
4901 if remotely_create_models.retain_count == 0 {
4902 remotely_create_models.buffers = self.buffer_store.read(cx).buffers().collect();
4903 remotely_create_models.worktrees =
4904 self.worktree_store.read(cx).worktrees().collect();
4905 }
4906 remotely_create_models.retain_count += 1;
4907 }
4908 RemotelyCreatedModelGuard {
4909 remote_models: Arc::downgrade(&self.remotely_created_models),
4910 }
4911 }
4912
4913 async fn handle_create_buffer_for_peer(
4914 this: Entity<Self>,
4915 envelope: TypedEnvelope<proto::CreateBufferForPeer>,
4916 mut cx: AsyncApp,
4917 ) -> Result<()> {
4918 this.update(&mut cx, |this, cx| {
4919 this.buffer_store.update(cx, |buffer_store, cx| {
4920 buffer_store.handle_create_buffer_for_peer(
4921 envelope,
4922 this.replica_id(),
4923 this.capability(),
4924 cx,
4925 )
4926 })
4927 })?
4928 }
4929
4930 async fn handle_toggle_lsp_logs(
4931 project: Entity<Self>,
4932 envelope: TypedEnvelope<proto::ToggleLspLogs>,
4933 mut cx: AsyncApp,
4934 ) -> Result<()> {
4935 let toggled_log_kind =
4936 match proto::toggle_lsp_logs::LogType::from_i32(envelope.payload.log_type)
4937 .context("invalid log type")?
4938 {
4939 proto::toggle_lsp_logs::LogType::Log => LogKind::Logs,
4940 proto::toggle_lsp_logs::LogType::Trace => LogKind::Trace,
4941 proto::toggle_lsp_logs::LogType::Rpc => LogKind::Rpc,
4942 };
4943 project.update(&mut cx, |_, cx| {
4944 cx.emit(Event::ToggleLspLogs {
4945 server_id: LanguageServerId::from_proto(envelope.payload.server_id),
4946 enabled: envelope.payload.enabled,
4947 toggled_log_kind,
4948 })
4949 })?;
4950 Ok(())
4951 }
4952
4953 async fn handle_synchronize_buffers(
4954 this: Entity<Self>,
4955 envelope: TypedEnvelope<proto::SynchronizeBuffers>,
4956 mut cx: AsyncApp,
4957 ) -> Result<proto::SynchronizeBuffersResponse> {
4958 let response = this.update(&mut cx, |this, cx| {
4959 let client = this.collab_client.clone();
4960 this.buffer_store.update(cx, |this, cx| {
4961 this.handle_synchronize_buffers(envelope, cx, client)
4962 })
4963 })??;
4964
4965 Ok(response)
4966 }
4967
4968 async fn handle_search_candidate_buffers(
4969 this: Entity<Self>,
4970 envelope: TypedEnvelope<proto::FindSearchCandidates>,
4971 mut cx: AsyncApp,
4972 ) -> Result<proto::FindSearchCandidatesResponse> {
4973 let peer_id = envelope.original_sender_id()?;
4974 let message = envelope.payload;
4975 let path_style = this.read_with(&cx, |this, cx| this.path_style(cx))?;
4976 let query =
4977 SearchQuery::from_proto(message.query.context("missing query field")?, path_style)?;
4978 let results = this.update(&mut cx, |this, cx| {
4979 this.find_search_candidate_buffers(&query, message.limit as _, cx)
4980 })?;
4981
4982 let mut response = proto::FindSearchCandidatesResponse {
4983 buffer_ids: Vec::new(),
4984 };
4985
4986 while let Ok(buffer) = results.recv().await {
4987 this.update(&mut cx, |this, cx| {
4988 let buffer_id = this.create_buffer_for_peer(&buffer, peer_id, cx);
4989 response.buffer_ids.push(buffer_id.to_proto());
4990 })?;
4991 }
4992
4993 Ok(response)
4994 }
4995
4996 async fn handle_open_buffer_by_id(
4997 this: Entity<Self>,
4998 envelope: TypedEnvelope<proto::OpenBufferById>,
4999 mut cx: AsyncApp,
5000 ) -> Result<proto::OpenBufferResponse> {
5001 let peer_id = envelope.original_sender_id()?;
5002 let buffer_id = BufferId::new(envelope.payload.id)?;
5003 let buffer = this
5004 .update(&mut cx, |this, cx| this.open_buffer_by_id(buffer_id, cx))?
5005 .await?;
5006 Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
5007 }
5008
5009 async fn handle_open_buffer_by_path(
5010 this: Entity<Self>,
5011 envelope: TypedEnvelope<proto::OpenBufferByPath>,
5012 mut cx: AsyncApp,
5013 ) -> Result<proto::OpenBufferResponse> {
5014 let peer_id = envelope.original_sender_id()?;
5015 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
5016 let path = RelPath::from_proto(&envelope.payload.path)?;
5017 let open_buffer = this
5018 .update(&mut cx, |this, cx| {
5019 this.open_buffer(ProjectPath { worktree_id, path }, cx)
5020 })?
5021 .await?;
5022 Project::respond_to_open_buffer_request(this, open_buffer, peer_id, &mut cx)
5023 }
5024
5025 async fn handle_open_new_buffer(
5026 this: Entity<Self>,
5027 envelope: TypedEnvelope<proto::OpenNewBuffer>,
5028 mut cx: AsyncApp,
5029 ) -> Result<proto::OpenBufferResponse> {
5030 let buffer = this
5031 .update(&mut cx, |this, cx| this.create_buffer(true, cx))?
5032 .await?;
5033 let peer_id = envelope.original_sender_id()?;
5034
5035 Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
5036 }
5037
5038 fn respond_to_open_buffer_request(
5039 this: Entity<Self>,
5040 buffer: Entity<Buffer>,
5041 peer_id: proto::PeerId,
5042 cx: &mut AsyncApp,
5043 ) -> Result<proto::OpenBufferResponse> {
5044 this.update(cx, |this, cx| {
5045 let is_private = buffer
5046 .read(cx)
5047 .file()
5048 .map(|f| f.is_private())
5049 .unwrap_or_default();
5050 anyhow::ensure!(!is_private, ErrorCode::UnsharedItem);
5051 Ok(proto::OpenBufferResponse {
5052 buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx).into(),
5053 })
5054 })?
5055 }
5056
5057 fn create_buffer_for_peer(
5058 &mut self,
5059 buffer: &Entity<Buffer>,
5060 peer_id: proto::PeerId,
5061 cx: &mut App,
5062 ) -> BufferId {
5063 self.buffer_store
5064 .update(cx, |buffer_store, cx| {
5065 buffer_store.create_buffer_for_peer(buffer, peer_id, cx)
5066 })
5067 .detach_and_log_err(cx);
5068 buffer.read(cx).remote_id()
5069 }
5070
5071 async fn handle_create_image_for_peer(
5072 this: Entity<Self>,
5073 envelope: TypedEnvelope<proto::CreateImageForPeer>,
5074 mut cx: AsyncApp,
5075 ) -> Result<()> {
5076 this.update(&mut cx, |this, cx| {
5077 this.image_store.update(cx, |image_store, cx| {
5078 image_store.handle_create_image_for_peer(envelope, cx)
5079 })
5080 })?
5081 .log_err();
5082 Ok(())
5083 }
5084
5085 fn synchronize_remote_buffers(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
5086 let project_id = match self.client_state {
5087 ProjectClientState::Remote {
5088 sharing_has_stopped,
5089 remote_id,
5090 ..
5091 } => {
5092 if sharing_has_stopped {
5093 return Task::ready(Err(anyhow!(
5094 "can't synchronize remote buffers on a readonly project"
5095 )));
5096 } else {
5097 remote_id
5098 }
5099 }
5100 ProjectClientState::Shared { .. } | ProjectClientState::Local => {
5101 return Task::ready(Err(anyhow!(
5102 "can't synchronize remote buffers on a local project"
5103 )));
5104 }
5105 };
5106
5107 let client = self.collab_client.clone();
5108 cx.spawn(async move |this, cx| {
5109 let (buffers, incomplete_buffer_ids) = this.update(cx, |this, cx| {
5110 this.buffer_store.read(cx).buffer_version_info(cx)
5111 })?;
5112 let response = client
5113 .request(proto::SynchronizeBuffers {
5114 project_id,
5115 buffers,
5116 })
5117 .await?;
5118
5119 let send_updates_for_buffers = this.update(cx, |this, cx| {
5120 response
5121 .buffers
5122 .into_iter()
5123 .map(|buffer| {
5124 let client = client.clone();
5125 let buffer_id = match BufferId::new(buffer.id) {
5126 Ok(id) => id,
5127 Err(e) => {
5128 return Task::ready(Err(e));
5129 }
5130 };
5131 let remote_version = language::proto::deserialize_version(&buffer.version);
5132 if let Some(buffer) = this.buffer_for_id(buffer_id, cx) {
5133 let operations =
5134 buffer.read(cx).serialize_ops(Some(remote_version), cx);
5135 cx.background_spawn(async move {
5136 let operations = operations.await;
5137 for chunk in split_operations(operations) {
5138 client
5139 .request(proto::UpdateBuffer {
5140 project_id,
5141 buffer_id: buffer_id.into(),
5142 operations: chunk,
5143 })
5144 .await?;
5145 }
5146 anyhow::Ok(())
5147 })
5148 } else {
5149 Task::ready(Ok(()))
5150 }
5151 })
5152 .collect::<Vec<_>>()
5153 })?;
5154
5155 // Any incomplete buffers have open requests waiting. Request that the host sends
5156 // creates these buffers for us again to unblock any waiting futures.
5157 for id in incomplete_buffer_ids {
5158 cx.background_spawn(client.request(proto::OpenBufferById {
5159 project_id,
5160 id: id.into(),
5161 }))
5162 .detach();
5163 }
5164
5165 futures::future::join_all(send_updates_for_buffers)
5166 .await
5167 .into_iter()
5168 .collect()
5169 })
5170 }
5171
5172 pub fn worktree_metadata_protos(&self, cx: &App) -> Vec<proto::WorktreeMetadata> {
5173 self.worktree_store.read(cx).worktree_metadata_protos(cx)
5174 }
5175
5176 /// Iterator of all open buffers that have unsaved changes
5177 pub fn dirty_buffers<'a>(&'a self, cx: &'a App) -> impl Iterator<Item = ProjectPath> + 'a {
5178 self.buffer_store.read(cx).buffers().filter_map(|buf| {
5179 let buf = buf.read(cx);
5180 if buf.is_dirty() {
5181 buf.project_path(cx)
5182 } else {
5183 None
5184 }
5185 })
5186 }
5187
5188 fn set_worktrees_from_proto(
5189 &mut self,
5190 worktrees: Vec<proto::WorktreeMetadata>,
5191 cx: &mut Context<Project>,
5192 ) -> Result<()> {
5193 self.worktree_store.update(cx, |worktree_store, cx| {
5194 worktree_store.set_worktrees_from_proto(worktrees, self.replica_id(), cx)
5195 })
5196 }
5197
5198 fn set_collaborators_from_proto(
5199 &mut self,
5200 messages: Vec<proto::Collaborator>,
5201 cx: &mut Context<Self>,
5202 ) -> Result<()> {
5203 let mut collaborators = HashMap::default();
5204 for message in messages {
5205 let collaborator = Collaborator::from_proto(message)?;
5206 collaborators.insert(collaborator.peer_id, collaborator);
5207 }
5208 for old_peer_id in self.collaborators.keys() {
5209 if !collaborators.contains_key(old_peer_id) {
5210 cx.emit(Event::CollaboratorLeft(*old_peer_id));
5211 }
5212 }
5213 self.collaborators = collaborators;
5214 Ok(())
5215 }
5216
5217 pub fn supplementary_language_servers<'a>(
5218 &'a self,
5219 cx: &'a App,
5220 ) -> impl 'a + Iterator<Item = (LanguageServerId, LanguageServerName)> {
5221 self.lsp_store.read(cx).supplementary_language_servers()
5222 }
5223
5224 pub fn any_language_server_supports_inlay_hints(&self, buffer: &Buffer, cx: &mut App) -> bool {
5225 let Some(language) = buffer.language().cloned() else {
5226 return false;
5227 };
5228 self.lsp_store.update(cx, |lsp_store, _| {
5229 let relevant_language_servers = lsp_store
5230 .languages
5231 .lsp_adapters(&language.name())
5232 .into_iter()
5233 .map(|lsp_adapter| lsp_adapter.name())
5234 .collect::<HashSet<_>>();
5235 lsp_store
5236 .language_server_statuses()
5237 .filter_map(|(server_id, server_status)| {
5238 relevant_language_servers
5239 .contains(&server_status.name)
5240 .then_some(server_id)
5241 })
5242 .filter_map(|server_id| lsp_store.lsp_server_capabilities.get(&server_id))
5243 .any(InlayHints::check_capabilities)
5244 })
5245 }
5246
5247 pub fn language_server_id_for_name(
5248 &self,
5249 buffer: &Buffer,
5250 name: &LanguageServerName,
5251 cx: &App,
5252 ) -> Option<LanguageServerId> {
5253 let language = buffer.language()?;
5254 let relevant_language_servers = self
5255 .languages
5256 .lsp_adapters(&language.name())
5257 .into_iter()
5258 .map(|lsp_adapter| lsp_adapter.name())
5259 .collect::<HashSet<_>>();
5260 if !relevant_language_servers.contains(name) {
5261 return None;
5262 }
5263 self.language_server_statuses(cx)
5264 .filter(|(_, server_status)| relevant_language_servers.contains(&server_status.name))
5265 .find_map(|(server_id, server_status)| {
5266 if &server_status.name == name {
5267 Some(server_id)
5268 } else {
5269 None
5270 }
5271 })
5272 }
5273
5274 #[cfg(any(test, feature = "test-support"))]
5275 pub fn has_language_servers_for(&self, buffer: &Buffer, cx: &mut App) -> bool {
5276 self.lsp_store.update(cx, |this, cx| {
5277 this.language_servers_for_local_buffer(buffer, cx)
5278 .next()
5279 .is_some()
5280 })
5281 }
5282
5283 pub fn git_init(
5284 &self,
5285 path: Arc<Path>,
5286 fallback_branch_name: String,
5287 cx: &App,
5288 ) -> Task<Result<()>> {
5289 self.git_store
5290 .read(cx)
5291 .git_init(path, fallback_branch_name, cx)
5292 }
5293
5294 pub fn buffer_store(&self) -> &Entity<BufferStore> {
5295 &self.buffer_store
5296 }
5297
5298 pub fn git_store(&self) -> &Entity<GitStore> {
5299 &self.git_store
5300 }
5301
5302 pub fn agent_server_store(&self) -> &Entity<AgentServerStore> {
5303 &self.agent_server_store
5304 }
5305
5306 #[cfg(test)]
5307 fn git_scans_complete(&self, cx: &Context<Self>) -> Task<()> {
5308 cx.spawn(async move |this, cx| {
5309 let scans_complete = this
5310 .read_with(cx, |this, cx| {
5311 this.worktrees(cx)
5312 .filter_map(|worktree| Some(worktree.read(cx).as_local()?.scan_complete()))
5313 .collect::<Vec<_>>()
5314 })
5315 .unwrap();
5316 join_all(scans_complete).await;
5317 let barriers = this
5318 .update(cx, |this, cx| {
5319 let repos = this.repositories(cx).values().cloned().collect::<Vec<_>>();
5320 repos
5321 .into_iter()
5322 .map(|repo| repo.update(cx, |repo, _| repo.barrier()))
5323 .collect::<Vec<_>>()
5324 })
5325 .unwrap();
5326 join_all(barriers).await;
5327 })
5328 }
5329
5330 pub fn active_repository(&self, cx: &App) -> Option<Entity<Repository>> {
5331 self.git_store.read(cx).active_repository()
5332 }
5333
5334 pub fn repositories<'a>(&self, cx: &'a App) -> &'a HashMap<RepositoryId, Entity<Repository>> {
5335 self.git_store.read(cx).repositories()
5336 }
5337
5338 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
5339 self.git_store.read(cx).status_for_buffer_id(buffer_id, cx)
5340 }
5341
5342 pub fn set_agent_location(
5343 &mut self,
5344 new_location: Option<AgentLocation>,
5345 cx: &mut Context<Self>,
5346 ) {
5347 if let Some(old_location) = self.agent_location.as_ref() {
5348 old_location
5349 .buffer
5350 .update(cx, |buffer, cx| buffer.remove_agent_selections(cx))
5351 .ok();
5352 }
5353
5354 if let Some(location) = new_location.as_ref() {
5355 location
5356 .buffer
5357 .update(cx, |buffer, cx| {
5358 buffer.set_agent_selections(
5359 Arc::from([language::Selection {
5360 id: 0,
5361 start: location.position,
5362 end: location.position,
5363 reversed: false,
5364 goal: language::SelectionGoal::None,
5365 }]),
5366 false,
5367 CursorShape::Hollow,
5368 cx,
5369 )
5370 })
5371 .ok();
5372 }
5373
5374 self.agent_location = new_location;
5375 cx.emit(Event::AgentLocationChanged);
5376 }
5377
5378 pub fn agent_location(&self) -> Option<AgentLocation> {
5379 self.agent_location.clone()
5380 }
5381
5382 pub fn path_style(&self, cx: &App) -> PathStyle {
5383 self.worktree_store.read(cx).path_style()
5384 }
5385
5386 pub fn contains_local_settings_file(
5387 &self,
5388 worktree_id: WorktreeId,
5389 rel_path: &RelPath,
5390 cx: &App,
5391 ) -> bool {
5392 self.worktree_for_id(worktree_id, cx)
5393 .map_or(false, |worktree| {
5394 worktree.read(cx).entry_for_path(rel_path).is_some()
5395 })
5396 }
5397
5398 pub fn update_local_settings_file(
5399 &self,
5400 worktree_id: WorktreeId,
5401 rel_path: Arc<RelPath>,
5402 cx: &mut App,
5403 update: impl 'static + Send + FnOnce(&mut settings::SettingsContent, &App),
5404 ) {
5405 let Some(worktree) = self.worktree_for_id(worktree_id, cx) else {
5406 // todo(settings_ui) error?
5407 return;
5408 };
5409 cx.spawn(async move |cx| {
5410 let file = worktree
5411 .update(cx, |worktree, cx| worktree.load_file(&rel_path, cx))?
5412 .await
5413 .context("Failed to load settings file")?;
5414
5415 let new_text = cx.read_global::<SettingsStore, _>(|store, cx| {
5416 store.new_text_for_update(file.text, move |settings| update(settings, cx))
5417 })?;
5418 worktree
5419 .update(cx, |worktree, cx| {
5420 let line_ending = text::LineEnding::detect(&new_text);
5421 worktree.write_file(rel_path.clone(), new_text.into(), line_ending, cx)
5422 })?
5423 .await
5424 .context("Failed to write settings file")?;
5425
5426 anyhow::Ok(())
5427 })
5428 .detach_and_log_err(cx);
5429 }
5430}
5431
5432pub struct PathMatchCandidateSet {
5433 pub snapshot: Snapshot,
5434 pub include_ignored: bool,
5435 pub include_root_name: bool,
5436 pub candidates: Candidates,
5437}
5438
5439pub enum Candidates {
5440 /// Only consider directories.
5441 Directories,
5442 /// Only consider files.
5443 Files,
5444 /// Consider directories and files.
5445 Entries,
5446}
5447
5448impl<'a> fuzzy::PathMatchCandidateSet<'a> for PathMatchCandidateSet {
5449 type Candidates = PathMatchCandidateSetIter<'a>;
5450
5451 fn id(&self) -> usize {
5452 self.snapshot.id().to_usize()
5453 }
5454
5455 fn len(&self) -> usize {
5456 match self.candidates {
5457 Candidates::Files => {
5458 if self.include_ignored {
5459 self.snapshot.file_count()
5460 } else {
5461 self.snapshot.visible_file_count()
5462 }
5463 }
5464
5465 Candidates::Directories => {
5466 if self.include_ignored {
5467 self.snapshot.dir_count()
5468 } else {
5469 self.snapshot.visible_dir_count()
5470 }
5471 }
5472
5473 Candidates::Entries => {
5474 if self.include_ignored {
5475 self.snapshot.entry_count()
5476 } else {
5477 self.snapshot.visible_entry_count()
5478 }
5479 }
5480 }
5481 }
5482
5483 fn prefix(&self) -> Arc<RelPath> {
5484 if self.snapshot.root_entry().is_some_and(|e| e.is_file()) || self.include_root_name {
5485 self.snapshot.root_name().into()
5486 } else {
5487 RelPath::empty().into()
5488 }
5489 }
5490
5491 fn root_is_file(&self) -> bool {
5492 self.snapshot.root_entry().is_some_and(|f| f.is_file())
5493 }
5494
5495 fn path_style(&self) -> PathStyle {
5496 self.snapshot.path_style()
5497 }
5498
5499 fn candidates(&'a self, start: usize) -> Self::Candidates {
5500 PathMatchCandidateSetIter {
5501 traversal: match self.candidates {
5502 Candidates::Directories => self.snapshot.directories(self.include_ignored, start),
5503 Candidates::Files => self.snapshot.files(self.include_ignored, start),
5504 Candidates::Entries => self.snapshot.entries(self.include_ignored, start),
5505 },
5506 }
5507 }
5508}
5509
5510pub struct PathMatchCandidateSetIter<'a> {
5511 traversal: Traversal<'a>,
5512}
5513
5514impl<'a> Iterator for PathMatchCandidateSetIter<'a> {
5515 type Item = fuzzy::PathMatchCandidate<'a>;
5516
5517 fn next(&mut self) -> Option<Self::Item> {
5518 self.traversal
5519 .next()
5520 .map(|entry| fuzzy::PathMatchCandidate {
5521 is_dir: entry.kind.is_dir(),
5522 path: &entry.path,
5523 char_bag: entry.char_bag,
5524 })
5525 }
5526}
5527
5528impl EventEmitter<Event> for Project {}
5529
5530impl<'a> From<&'a ProjectPath> for SettingsLocation<'a> {
5531 fn from(val: &'a ProjectPath) -> Self {
5532 SettingsLocation {
5533 worktree_id: val.worktree_id,
5534 path: val.path.as_ref(),
5535 }
5536 }
5537}
5538
5539impl<P: Into<Arc<RelPath>>> From<(WorktreeId, P)> for ProjectPath {
5540 fn from((worktree_id, path): (WorktreeId, P)) -> Self {
5541 Self {
5542 worktree_id,
5543 path: path.into(),
5544 }
5545 }
5546}
5547
5548/// ResolvedPath is a path that has been resolved to either a ProjectPath
5549/// or an AbsPath and that *exists*.
5550#[derive(Debug, Clone)]
5551pub enum ResolvedPath {
5552 ProjectPath {
5553 project_path: ProjectPath,
5554 is_dir: bool,
5555 },
5556 AbsPath {
5557 path: String,
5558 is_dir: bool,
5559 },
5560}
5561
5562impl ResolvedPath {
5563 pub fn abs_path(&self) -> Option<&str> {
5564 match self {
5565 Self::AbsPath { path, .. } => Some(path),
5566 _ => None,
5567 }
5568 }
5569
5570 pub fn into_abs_path(self) -> Option<String> {
5571 match self {
5572 Self::AbsPath { path, .. } => Some(path),
5573 _ => None,
5574 }
5575 }
5576
5577 pub fn project_path(&self) -> Option<&ProjectPath> {
5578 match self {
5579 Self::ProjectPath { project_path, .. } => Some(project_path),
5580 _ => None,
5581 }
5582 }
5583
5584 pub fn is_file(&self) -> bool {
5585 !self.is_dir()
5586 }
5587
5588 pub fn is_dir(&self) -> bool {
5589 match self {
5590 Self::ProjectPath { is_dir, .. } => *is_dir,
5591 Self::AbsPath { is_dir, .. } => *is_dir,
5592 }
5593 }
5594}
5595
5596impl ProjectItem for Buffer {
5597 fn try_open(
5598 project: &Entity<Project>,
5599 path: &ProjectPath,
5600 cx: &mut App,
5601 ) -> Option<Task<Result<Entity<Self>>>> {
5602 Some(project.update(cx, |project, cx| project.open_buffer(path.clone(), cx)))
5603 }
5604
5605 fn entry_id(&self, _cx: &App) -> Option<ProjectEntryId> {
5606 File::from_dyn(self.file()).and_then(|file| file.project_entry_id())
5607 }
5608
5609 fn project_path(&self, cx: &App) -> Option<ProjectPath> {
5610 self.file().map(|file| ProjectPath {
5611 worktree_id: file.worktree_id(cx),
5612 path: file.path().clone(),
5613 })
5614 }
5615
5616 fn is_dirty(&self) -> bool {
5617 self.is_dirty()
5618 }
5619}
5620
5621impl Completion {
5622 pub fn kind(&self) -> Option<CompletionItemKind> {
5623 self.source
5624 // `lsp::CompletionListItemDefaults` has no `kind` field
5625 .lsp_completion(false)
5626 .and_then(|lsp_completion| lsp_completion.kind)
5627 }
5628
5629 pub fn label(&self) -> Option<String> {
5630 self.source
5631 .lsp_completion(false)
5632 .map(|lsp_completion| lsp_completion.label.clone())
5633 }
5634
5635 /// A key that can be used to sort completions when displaying
5636 /// them to the user.
5637 pub fn sort_key(&self) -> (usize, &str) {
5638 const DEFAULT_KIND_KEY: usize = 4;
5639 let kind_key = self
5640 .kind()
5641 .and_then(|lsp_completion_kind| match lsp_completion_kind {
5642 lsp::CompletionItemKind::KEYWORD => Some(0),
5643 lsp::CompletionItemKind::VARIABLE => Some(1),
5644 lsp::CompletionItemKind::CONSTANT => Some(2),
5645 lsp::CompletionItemKind::PROPERTY => Some(3),
5646 _ => None,
5647 })
5648 .unwrap_or(DEFAULT_KIND_KEY);
5649 (kind_key, self.label.filter_text())
5650 }
5651
5652 /// Whether this completion is a snippet.
5653 pub fn is_snippet_kind(&self) -> bool {
5654 matches!(
5655 &self.source,
5656 CompletionSource::Lsp { lsp_completion, .. }
5657 if lsp_completion.kind == Some(CompletionItemKind::SNIPPET)
5658 )
5659 }
5660
5661 /// Whether this completion is a snippet or snippet-style LSP completion.
5662 pub fn is_snippet(&self) -> bool {
5663 self.source
5664 // `lsp::CompletionListItemDefaults` has `insert_text_format` field
5665 .lsp_completion(true)
5666 .is_some_and(|lsp_completion| {
5667 lsp_completion.insert_text_format == Some(lsp::InsertTextFormat::SNIPPET)
5668 })
5669 }
5670
5671 /// Returns the corresponding color for this completion.
5672 ///
5673 /// Will return `None` if this completion's kind is not [`CompletionItemKind::COLOR`].
5674 pub fn color(&self) -> Option<Hsla> {
5675 // `lsp::CompletionListItemDefaults` has no `kind` field
5676 let lsp_completion = self.source.lsp_completion(false)?;
5677 if lsp_completion.kind? == CompletionItemKind::COLOR {
5678 return color_extractor::extract_color(&lsp_completion);
5679 }
5680 None
5681 }
5682}
5683
5684fn proto_to_prompt(level: proto::language_server_prompt_request::Level) -> gpui::PromptLevel {
5685 match level {
5686 proto::language_server_prompt_request::Level::Info(_) => gpui::PromptLevel::Info,
5687 proto::language_server_prompt_request::Level::Warning(_) => gpui::PromptLevel::Warning,
5688 proto::language_server_prompt_request::Level::Critical(_) => gpui::PromptLevel::Critical,
5689 }
5690}
5691
5692fn provide_inline_values(
5693 captures: impl Iterator<Item = (Range<usize>, language::DebuggerTextObject)>,
5694 snapshot: &language::BufferSnapshot,
5695 max_row: usize,
5696) -> Vec<InlineValueLocation> {
5697 let mut variables = Vec::new();
5698 let mut variable_position = HashSet::default();
5699 let mut scopes = Vec::new();
5700
5701 let active_debug_line_offset = snapshot.point_to_offset(Point::new(max_row as u32, 0));
5702
5703 for (capture_range, capture_kind) in captures {
5704 match capture_kind {
5705 language::DebuggerTextObject::Variable => {
5706 let variable_name = snapshot
5707 .text_for_range(capture_range.clone())
5708 .collect::<String>();
5709 let point = snapshot.offset_to_point(capture_range.end);
5710
5711 while scopes
5712 .last()
5713 .is_some_and(|scope: &Range<_>| !scope.contains(&capture_range.start))
5714 {
5715 scopes.pop();
5716 }
5717
5718 if point.row as usize > max_row {
5719 break;
5720 }
5721
5722 let scope = if scopes
5723 .last()
5724 .is_none_or(|scope| !scope.contains(&active_debug_line_offset))
5725 {
5726 VariableScope::Global
5727 } else {
5728 VariableScope::Local
5729 };
5730
5731 if variable_position.insert(capture_range.end) {
5732 variables.push(InlineValueLocation {
5733 variable_name,
5734 scope,
5735 lookup: VariableLookupKind::Variable,
5736 row: point.row as usize,
5737 column: point.column as usize,
5738 });
5739 }
5740 }
5741 language::DebuggerTextObject::Scope => {
5742 while scopes.last().map_or_else(
5743 || false,
5744 |scope: &Range<usize>| {
5745 !(scope.contains(&capture_range.start)
5746 && scope.contains(&capture_range.end))
5747 },
5748 ) {
5749 scopes.pop();
5750 }
5751 scopes.push(capture_range);
5752 }
5753 }
5754 }
5755
5756 variables
5757}
5758
5759#[cfg(test)]
5760mod disable_ai_settings_tests {
5761 use super::*;
5762 use gpui::TestAppContext;
5763 use settings::Settings;
5764
5765 #[gpui::test]
5766 async fn test_disable_ai_settings_security(cx: &mut TestAppContext) {
5767 cx.update(|cx| {
5768 settings::init(cx);
5769
5770 // Test 1: Default is false (AI enabled)
5771 assert!(
5772 !DisableAiSettings::get_global(cx).disable_ai,
5773 "Default should allow AI"
5774 );
5775 });
5776
5777 let disable_true = serde_json::json!({
5778 "disable_ai": true
5779 })
5780 .to_string();
5781 let disable_false = serde_json::json!({
5782 "disable_ai": false
5783 })
5784 .to_string();
5785
5786 cx.update_global::<SettingsStore, _>(|store, cx| {
5787 store.set_user_settings(&disable_false, cx).unwrap();
5788 store.set_global_settings(&disable_true, cx).unwrap();
5789 });
5790 cx.update(|cx| {
5791 assert!(
5792 DisableAiSettings::get_global(cx).disable_ai,
5793 "Local false cannot override global true"
5794 );
5795 });
5796
5797 cx.update_global::<SettingsStore, _>(|store, cx| {
5798 store.set_global_settings(&disable_false, cx).unwrap();
5799 store.set_user_settings(&disable_true, cx).unwrap();
5800 });
5801
5802 cx.update(|cx| {
5803 assert!(
5804 DisableAiSettings::get_global(cx).disable_ai,
5805 "Local false cannot override global true"
5806 );
5807 });
5808 }
5809}