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