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