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