1use super::{
2 breakpoint_store::BreakpointStore,
3 dap_command::EvaluateCommand,
4 locators,
5 session::{self, Session, SessionStateEvent},
6};
7use crate::{
8 InlayHint, InlayHintLabel, ProjectEnvironment, ResolveState,
9 project_settings::ProjectSettings,
10 terminals::{SshCommand, wrap_for_ssh},
11 worktree_store::WorktreeStore,
12};
13use anyhow::{Context as _, Result, anyhow};
14use async_trait::async_trait;
15use collections::HashMap;
16use dap::{
17 Capabilities, CompletionItem, CompletionsArguments, DapRegistry, DebugRequest,
18 EvaluateArguments, EvaluateArgumentsContext, EvaluateResponse, Source, StackFrameId,
19 adapters::{
20 DapDelegate, DebugAdapterBinary, DebugAdapterName, DebugTaskDefinition, TcpArguments,
21 },
22 client::SessionId,
23 inline_value::VariableLookupKind,
24 messages::Message,
25 requests::{Completions, Evaluate},
26};
27use fs::Fs;
28use futures::{
29 StreamExt,
30 channel::mpsc::{self, UnboundedSender},
31 future::{Shared, join_all},
32};
33use gpui::{App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task};
34use http_client::HttpClient;
35use language::{Buffer, LanguageToolchainStore, language_settings::InlayHintKind};
36use node_runtime::NodeRuntime;
37
38use remote::SshRemoteClient;
39use rpc::{
40 AnyProtoClient, TypedEnvelope,
41 proto::{self},
42};
43use settings::{Settings, WorktreeId};
44use std::{
45 borrow::Borrow,
46 collections::BTreeMap,
47 ffi::OsStr,
48 net::Ipv4Addr,
49 path::{Path, PathBuf},
50 sync::{Arc, Once},
51};
52use task::{DebugScenario, SpawnInTerminal, TaskTemplate};
53use util::ResultExt as _;
54use worktree::Worktree;
55
56#[derive(Debug)]
57pub enum DapStoreEvent {
58 DebugClientStarted(SessionId),
59 DebugSessionInitialized(SessionId),
60 DebugClientShutdown(SessionId),
61 DebugClientEvent {
62 session_id: SessionId,
63 message: Message,
64 },
65 Notification(String),
66 RemoteHasInitialized,
67}
68
69enum DapStoreMode {
70 Local(LocalDapStore),
71 Ssh(SshDapStore),
72 Collab,
73}
74
75pub struct LocalDapStore {
76 fs: Arc<dyn Fs>,
77 node_runtime: NodeRuntime,
78 http_client: Arc<dyn HttpClient>,
79 environment: Entity<ProjectEnvironment>,
80 toolchain_store: Arc<dyn LanguageToolchainStore>,
81}
82
83pub struct SshDapStore {
84 ssh_client: Entity<SshRemoteClient>,
85 upstream_client: AnyProtoClient,
86 upstream_project_id: u64,
87}
88
89pub struct DapStore {
90 mode: DapStoreMode,
91 downstream_client: Option<(AnyProtoClient, u64)>,
92 breakpoint_store: Entity<BreakpointStore>,
93 worktree_store: Entity<WorktreeStore>,
94 sessions: BTreeMap<SessionId, Entity<Session>>,
95 next_session_id: u32,
96}
97
98impl EventEmitter<DapStoreEvent> for DapStore {}
99
100impl DapStore {
101 pub fn init(client: &AnyProtoClient, cx: &mut App) {
102 static ADD_LOCATORS: Once = Once::new();
103 ADD_LOCATORS.call_once(|| {
104 let registry = DapRegistry::global(cx);
105 registry.add_locator(Arc::new(locators::cargo::CargoLocator {}));
106 registry.add_locator(Arc::new(locators::python::PythonLocator));
107 registry.add_locator(Arc::new(locators::go::GoLocator {}));
108 });
109 client.add_entity_request_handler(Self::handle_run_debug_locator);
110 client.add_entity_request_handler(Self::handle_get_debug_adapter_binary);
111 client.add_entity_message_handler(Self::handle_log_to_debug_console);
112 }
113
114 #[expect(clippy::too_many_arguments)]
115 pub fn new_local(
116 http_client: Arc<dyn HttpClient>,
117 node_runtime: NodeRuntime,
118 fs: Arc<dyn Fs>,
119 environment: Entity<ProjectEnvironment>,
120 toolchain_store: Arc<dyn LanguageToolchainStore>,
121 worktree_store: Entity<WorktreeStore>,
122 breakpoint_store: Entity<BreakpointStore>,
123 cx: &mut Context<Self>,
124 ) -> Self {
125 let mode = DapStoreMode::Local(LocalDapStore {
126 fs,
127 environment,
128 http_client,
129 node_runtime,
130 toolchain_store,
131 });
132
133 Self::new(mode, breakpoint_store, worktree_store, cx)
134 }
135
136 pub fn new_ssh(
137 project_id: u64,
138 ssh_client: Entity<SshRemoteClient>,
139 breakpoint_store: Entity<BreakpointStore>,
140 worktree_store: Entity<WorktreeStore>,
141 cx: &mut Context<Self>,
142 ) -> Self {
143 let mode = DapStoreMode::Ssh(SshDapStore {
144 upstream_client: ssh_client.read(cx).proto_client(),
145 ssh_client,
146 upstream_project_id: project_id,
147 });
148
149 Self::new(mode, breakpoint_store, worktree_store, cx)
150 }
151
152 pub fn new_collab(
153 _project_id: u64,
154 _upstream_client: AnyProtoClient,
155 breakpoint_store: Entity<BreakpointStore>,
156 worktree_store: Entity<WorktreeStore>,
157 cx: &mut Context<Self>,
158 ) -> Self {
159 Self::new(DapStoreMode::Collab, breakpoint_store, worktree_store, cx)
160 }
161
162 fn new(
163 mode: DapStoreMode,
164 breakpoint_store: Entity<BreakpointStore>,
165 worktree_store: Entity<WorktreeStore>,
166 _cx: &mut Context<Self>,
167 ) -> Self {
168 Self {
169 mode,
170 next_session_id: 0,
171 downstream_client: None,
172 breakpoint_store,
173 worktree_store,
174 sessions: Default::default(),
175 }
176 }
177
178 pub fn get_debug_adapter_binary(
179 &mut self,
180 definition: DebugTaskDefinition,
181 session_id: SessionId,
182 console: UnboundedSender<String>,
183 cx: &mut Context<Self>,
184 ) -> Task<Result<DebugAdapterBinary>> {
185 match &self.mode {
186 DapStoreMode::Local(_) => {
187 let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next()
188 else {
189 return Task::ready(Err(anyhow!("Failed to find a worktree")));
190 };
191 let Some(adapter) = DapRegistry::global(cx).adapter(&definition.adapter) else {
192 return Task::ready(Err(anyhow!("Failed to find a debug adapter")));
193 };
194
195 let user_installed_path = ProjectSettings::get_global(cx)
196 .dap
197 .get(&adapter.name())
198 .and_then(|s| s.binary.as_ref().map(PathBuf::from));
199
200 let delegate = self.delegate(&worktree, console, cx);
201 let cwd: Arc<Path> = worktree.read(cx).abs_path().as_ref().into();
202
203 cx.spawn(async move |this, cx| {
204 let mut binary = adapter
205 .get_binary(&delegate, &definition, user_installed_path, cx)
206 .await?;
207
208 let env = this
209 .update(cx, |this, cx| {
210 this.as_local()
211 .unwrap()
212 .environment
213 .update(cx, |environment, cx| {
214 environment.get_directory_environment(cwd, cx)
215 })
216 })?
217 .await;
218
219 if let Some(mut env) = env {
220 env.extend(std::mem::take(&mut binary.envs));
221 binary.envs = env;
222 }
223
224 Ok(binary)
225 })
226 }
227 DapStoreMode::Ssh(ssh) => {
228 let request = ssh.upstream_client.request(proto::GetDebugAdapterBinary {
229 session_id: session_id.to_proto(),
230 project_id: ssh.upstream_project_id,
231 definition: Some(definition.to_proto()),
232 });
233 let ssh_client = ssh.ssh_client.clone();
234
235 cx.spawn(async move |_, cx| {
236 let response = request.await?;
237 let binary = DebugAdapterBinary::from_proto(response)?;
238 let mut ssh_command = ssh_client.read_with(cx, |ssh, _| {
239 anyhow::Ok(SshCommand {
240 arguments: ssh.ssh_args().context("SSH arguments not found")?,
241 })
242 })??;
243
244 let mut connection = None;
245 if let Some(c) = binary.connection {
246 let local_bind_addr = Ipv4Addr::LOCALHOST;
247 let port =
248 dap::transport::TcpTransport::unused_port(local_bind_addr).await?;
249
250 ssh_command.add_port_forwarding(port, c.host.to_string(), c.port);
251 connection = Some(TcpArguments {
252 port,
253 host: local_bind_addr,
254 timeout: c.timeout,
255 })
256 }
257
258 let (program, args) = wrap_for_ssh(
259 &ssh_command,
260 Some((&binary.command, &binary.arguments)),
261 binary.cwd.as_deref(),
262 binary.envs,
263 None,
264 );
265
266 Ok(DebugAdapterBinary {
267 command: program,
268 arguments: args,
269 envs: HashMap::default(),
270 cwd: None,
271 connection,
272 request_args: binary.request_args,
273 })
274 })
275 }
276 DapStoreMode::Collab => {
277 Task::ready(Err(anyhow!("Debugging is not yet supported via collab")))
278 }
279 }
280 }
281
282 pub fn debug_scenario_for_build_task(
283 &self,
284 build: TaskTemplate,
285 adapter: DebugAdapterName,
286 label: SharedString,
287 cx: &mut App,
288 ) -> Option<DebugScenario> {
289 DapRegistry::global(cx)
290 .locators()
291 .values()
292 .find_map(|locator| locator.create_scenario(&build, &label, adapter.clone()))
293 }
294
295 pub fn run_debug_locator(
296 &mut self,
297 locator_name: &str,
298 build_command: SpawnInTerminal,
299 cx: &mut Context<Self>,
300 ) -> Task<Result<DebugRequest>> {
301 match &self.mode {
302 DapStoreMode::Local(_) => {
303 // Pre-resolve args with existing environment.
304 let locators = DapRegistry::global(cx).locators();
305 let locator = locators.get(locator_name);
306
307 if let Some(locator) = locator.cloned() {
308 cx.background_spawn(async move {
309 let result = locator
310 .run(build_command.clone())
311 .await
312 .log_with_level(log::Level::Error);
313 if let Some(result) = result {
314 return Ok(result);
315 }
316
317 anyhow::bail!(
318 "None of the locators for task `{}` completed successfully",
319 build_command.label
320 )
321 })
322 } else {
323 Task::ready(Err(anyhow!(
324 "Couldn't find any locator for task `{}`. Specify the `attach` or `launch` arguments in your debug scenario definition",
325 build_command.label
326 )))
327 }
328 }
329 DapStoreMode::Ssh(ssh) => {
330 let request = ssh.upstream_client.request(proto::RunDebugLocators {
331 project_id: ssh.upstream_project_id,
332 build_command: Some(build_command.to_proto()),
333 locator: locator_name.to_owned(),
334 });
335 cx.background_spawn(async move {
336 let response = request.await?;
337 DebugRequest::from_proto(response)
338 })
339 }
340 DapStoreMode::Collab => {
341 Task::ready(Err(anyhow!("Debugging is not yet supported via collab")))
342 }
343 }
344 }
345
346 fn as_local(&self) -> Option<&LocalDapStore> {
347 match &self.mode {
348 DapStoreMode::Local(local_dap_store) => Some(local_dap_store),
349 _ => None,
350 }
351 }
352
353 pub fn new_session(
354 &mut self,
355 label: SharedString,
356 adapter: DebugAdapterName,
357 parent_session: Option<Entity<Session>>,
358 cx: &mut Context<Self>,
359 ) -> Entity<Session> {
360 let session_id = SessionId(util::post_inc(&mut self.next_session_id));
361
362 if let Some(session) = &parent_session {
363 session.update(cx, |session, _| {
364 session.add_child_session_id(session_id);
365 });
366 }
367
368 let session = Session::new(
369 self.breakpoint_store.clone(),
370 session_id,
371 parent_session,
372 label,
373 adapter,
374 cx,
375 );
376
377 self.sessions.insert(session_id, session.clone());
378 cx.notify();
379
380 cx.subscribe(&session, {
381 move |this: &mut DapStore, _, event: &SessionStateEvent, cx| match event {
382 SessionStateEvent::Shutdown => {
383 this.shutdown_session(session_id, cx).detach_and_log_err(cx);
384 }
385 SessionStateEvent::Restart | SessionStateEvent::SpawnChildSession { .. } => {}
386 SessionStateEvent::Running => {
387 cx.emit(DapStoreEvent::DebugClientStarted(session_id));
388 }
389 }
390 })
391 .detach();
392
393 session
394 }
395
396 pub fn boot_session(
397 &self,
398 session: Entity<Session>,
399 definition: DebugTaskDefinition,
400 cx: &mut Context<Self>,
401 ) -> Task<Result<()>> {
402 let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next() else {
403 return Task::ready(Err(anyhow!("Failed to find a worktree")));
404 };
405
406 let dap_store = cx.weak_entity();
407 let console = session.update(cx, |session, cx| session.console_output(cx));
408 let session_id = session.read(cx).session_id();
409
410 cx.spawn({
411 let session = session.clone();
412 async move |this, cx| {
413 let binary = this
414 .update(cx, |this, cx| {
415 this.get_debug_adapter_binary(definition.clone(), session_id, console, cx)
416 })?
417 .await?;
418 session
419 .update(cx, |session, cx| {
420 session.boot(binary, worktree, dap_store, cx)
421 })?
422 .await
423 }
424 })
425 }
426
427 pub fn session_by_id(
428 &self,
429 session_id: impl Borrow<SessionId>,
430 ) -> Option<Entity<session::Session>> {
431 let session_id = session_id.borrow();
432 let client = self.sessions.get(session_id).cloned();
433
434 client
435 }
436 pub fn sessions(&self) -> impl Iterator<Item = &Entity<Session>> {
437 self.sessions.values()
438 }
439
440 pub fn capabilities_by_id(
441 &self,
442 session_id: impl Borrow<SessionId>,
443 cx: &App,
444 ) -> Option<Capabilities> {
445 let session_id = session_id.borrow();
446 self.sessions
447 .get(session_id)
448 .map(|client| client.read(cx).capabilities.clone())
449 }
450
451 pub fn breakpoint_store(&self) -> &Entity<BreakpointStore> {
452 &self.breakpoint_store
453 }
454
455 pub fn worktree_store(&self) -> &Entity<WorktreeStore> {
456 &self.worktree_store
457 }
458
459 #[allow(dead_code)]
460 async fn handle_ignore_breakpoint_state(
461 this: Entity<Self>,
462 envelope: TypedEnvelope<proto::IgnoreBreakpointState>,
463 mut cx: AsyncApp,
464 ) -> Result<()> {
465 let session_id = SessionId::from_proto(envelope.payload.session_id);
466
467 this.update(&mut cx, |this, cx| {
468 if let Some(session) = this.session_by_id(&session_id) {
469 session.update(cx, |session, cx| {
470 session.set_ignore_breakpoints(envelope.payload.ignore, cx)
471 })
472 } else {
473 Task::ready(HashMap::default())
474 }
475 })?
476 .await;
477
478 Ok(())
479 }
480
481 fn delegate(
482 &self,
483 worktree: &Entity<Worktree>,
484 console: UnboundedSender<String>,
485 cx: &mut App,
486 ) -> Arc<dyn DapDelegate> {
487 let Some(local_store) = self.as_local() else {
488 unimplemented!("Starting session on remote side");
489 };
490
491 Arc::new(DapAdapterDelegate::new(
492 local_store.fs.clone(),
493 worktree.read(cx).snapshot(),
494 console,
495 local_store.node_runtime.clone(),
496 local_store.http_client.clone(),
497 local_store.toolchain_store.clone(),
498 local_store.environment.update(cx, |env, cx| {
499 env.get_worktree_environment(worktree.clone(), cx)
500 }),
501 ))
502 }
503
504 pub fn evaluate(
505 &self,
506 session_id: &SessionId,
507 stack_frame_id: u64,
508 expression: String,
509 context: EvaluateArgumentsContext,
510 source: Option<Source>,
511 cx: &mut Context<Self>,
512 ) -> Task<Result<EvaluateResponse>> {
513 let Some(client) = self
514 .session_by_id(session_id)
515 .and_then(|client| client.read(cx).adapter_client())
516 else {
517 return Task::ready(Err(anyhow!("Could not find client: {:?}", session_id)));
518 };
519
520 cx.background_executor().spawn(async move {
521 client
522 .request::<Evaluate>(EvaluateArguments {
523 expression: expression.clone(),
524 frame_id: Some(stack_frame_id),
525 context: Some(context),
526 format: None,
527 line: None,
528 column: None,
529 source,
530 })
531 .await
532 })
533 }
534
535 pub fn completions(
536 &self,
537 session_id: &SessionId,
538 stack_frame_id: u64,
539 text: String,
540 completion_column: u64,
541 cx: &mut Context<Self>,
542 ) -> Task<Result<Vec<CompletionItem>>> {
543 let Some(client) = self
544 .session_by_id(session_id)
545 .and_then(|client| client.read(cx).adapter_client())
546 else {
547 return Task::ready(Err(anyhow!("Could not find client: {:?}", session_id)));
548 };
549
550 cx.background_executor().spawn(async move {
551 Ok(client
552 .request::<Completions>(CompletionsArguments {
553 frame_id: Some(stack_frame_id),
554 line: None,
555 text,
556 column: completion_column,
557 })
558 .await?
559 .targets)
560 })
561 }
562
563 pub fn resolve_inline_value_locations(
564 &self,
565 session: Entity<Session>,
566 stack_frame_id: StackFrameId,
567 buffer_handle: Entity<Buffer>,
568 inline_value_locations: Vec<dap::inline_value::InlineValueLocation>,
569 cx: &mut Context<Self>,
570 ) -> Task<Result<Vec<InlayHint>>> {
571 let snapshot = buffer_handle.read(cx).snapshot();
572 let all_variables = session.read(cx).variables_by_stack_frame_id(stack_frame_id);
573
574 fn format_value(mut value: String) -> String {
575 const LIMIT: usize = 100;
576
577 if value.len() > LIMIT {
578 value.truncate(LIMIT);
579 value.push_str("...");
580 }
581
582 format!(": {}", value)
583 }
584
585 cx.spawn(async move |_, cx| {
586 let mut inlay_hints = Vec::with_capacity(inline_value_locations.len());
587 for inline_value_location in inline_value_locations.iter() {
588 let point = snapshot.point_to_point_utf16(language::Point::new(
589 inline_value_location.row as u32,
590 inline_value_location.column as u32,
591 ));
592 let position = snapshot.anchor_after(point);
593
594 match inline_value_location.lookup {
595 VariableLookupKind::Variable => {
596 let Some(variable) = all_variables
597 .iter()
598 .find(|variable| variable.name == inline_value_location.variable_name)
599 else {
600 continue;
601 };
602
603 inlay_hints.push(InlayHint {
604 position,
605 label: InlayHintLabel::String(format_value(variable.value.clone())),
606 kind: Some(InlayHintKind::Type),
607 padding_left: false,
608 padding_right: false,
609 tooltip: None,
610 resolve_state: ResolveState::Resolved,
611 });
612 }
613 VariableLookupKind::Expression => {
614 let Ok(eval_task) = session.read_with(cx, |session, _| {
615 session.mode.request_dap(EvaluateCommand {
616 expression: inline_value_location.variable_name.clone(),
617 frame_id: Some(stack_frame_id),
618 source: None,
619 context: Some(EvaluateArgumentsContext::Variables),
620 })
621 }) else {
622 continue;
623 };
624
625 if let Some(response) = eval_task.await.log_err() {
626 inlay_hints.push(InlayHint {
627 position,
628 label: InlayHintLabel::String(format_value(response.result)),
629 kind: Some(InlayHintKind::Type),
630 padding_left: false,
631 padding_right: false,
632 tooltip: None,
633 resolve_state: ResolveState::Resolved,
634 });
635 };
636 }
637 };
638 }
639
640 Ok(inlay_hints)
641 })
642 }
643
644 pub fn shutdown_sessions(&mut self, cx: &mut Context<Self>) -> Task<()> {
645 let mut tasks = vec![];
646 for session_id in self.sessions.keys().cloned().collect::<Vec<_>>() {
647 tasks.push(self.shutdown_session(session_id, cx));
648 }
649
650 cx.background_executor().spawn(async move {
651 futures::future::join_all(tasks).await;
652 })
653 }
654
655 pub fn shutdown_session(
656 &mut self,
657 session_id: SessionId,
658 cx: &mut Context<Self>,
659 ) -> Task<Result<()>> {
660 let Some(session) = self.sessions.remove(&session_id) else {
661 return Task::ready(Err(anyhow!("Could not find session: {:?}", session_id)));
662 };
663
664 let shutdown_children = session
665 .read(cx)
666 .child_session_ids()
667 .iter()
668 .map(|session_id| self.shutdown_session(*session_id, cx))
669 .collect::<Vec<_>>();
670
671 let shutdown_parent_task = if let Some(parent_session) = session
672 .read(cx)
673 .parent_id(cx)
674 .and_then(|session_id| self.session_by_id(session_id))
675 {
676 let shutdown_id = parent_session.update(cx, |parent_session, _| {
677 parent_session.remove_child_session_id(session_id);
678
679 if parent_session.child_session_ids().len() == 0 {
680 Some(parent_session.session_id())
681 } else {
682 None
683 }
684 });
685
686 shutdown_id.map(|session_id| self.shutdown_session(session_id, cx))
687 } else {
688 None
689 };
690
691 let shutdown_task = session.update(cx, |this, cx| this.shutdown(cx));
692
693 cx.background_spawn(async move {
694 if shutdown_children.len() > 0 {
695 let _ = join_all(shutdown_children).await;
696 }
697
698 shutdown_task.await;
699
700 if let Some(parent_task) = shutdown_parent_task {
701 parent_task.await?;
702 }
703
704 Ok(())
705 })
706 }
707
708 pub fn shared(
709 &mut self,
710 project_id: u64,
711 downstream_client: AnyProtoClient,
712 _: &mut Context<Self>,
713 ) {
714 self.downstream_client = Some((downstream_client.clone(), project_id));
715 }
716
717 pub fn unshared(&mut self, cx: &mut Context<Self>) {
718 self.downstream_client.take();
719
720 cx.notify();
721 }
722
723 async fn handle_run_debug_locator(
724 this: Entity<Self>,
725 envelope: TypedEnvelope<proto::RunDebugLocators>,
726 mut cx: AsyncApp,
727 ) -> Result<proto::DebugRequest> {
728 let task = envelope
729 .payload
730 .build_command
731 .context("missing definition")?;
732 let build_task = SpawnInTerminal::from_proto(task);
733 let locator = envelope.payload.locator;
734 let request = this
735 .update(&mut cx, |this, cx| {
736 this.run_debug_locator(&locator, build_task, cx)
737 })?
738 .await?;
739
740 Ok(request.to_proto())
741 }
742
743 async fn handle_get_debug_adapter_binary(
744 this: Entity<Self>,
745 envelope: TypedEnvelope<proto::GetDebugAdapterBinary>,
746 mut cx: AsyncApp,
747 ) -> Result<proto::DebugAdapterBinary> {
748 let definition = DebugTaskDefinition::from_proto(
749 envelope.payload.definition.context("missing definition")?,
750 )?;
751 let (tx, mut rx) = mpsc::unbounded();
752 let session_id = envelope.payload.session_id;
753 cx.spawn({
754 let this = this.clone();
755 async move |cx| {
756 while let Some(message) = rx.next().await {
757 this.read_with(cx, |this, _| {
758 if let Some((downstream, project_id)) = this.downstream_client.clone() {
759 downstream
760 .send(proto::LogToDebugConsole {
761 project_id,
762 session_id,
763 message,
764 })
765 .ok();
766 }
767 })
768 .ok();
769 }
770 }
771 })
772 .detach();
773
774 let binary = this
775 .update(&mut cx, |this, cx| {
776 this.get_debug_adapter_binary(definition, SessionId::from_proto(session_id), tx, cx)
777 })?
778 .await?;
779 Ok(binary.to_proto())
780 }
781
782 async fn handle_log_to_debug_console(
783 this: Entity<Self>,
784 envelope: TypedEnvelope<proto::LogToDebugConsole>,
785 mut cx: AsyncApp,
786 ) -> Result<()> {
787 let session_id = SessionId::from_proto(envelope.payload.session_id);
788 this.update(&mut cx, |this, cx| {
789 let Some(session) = this.sessions.get(&session_id) else {
790 return;
791 };
792 session.update(cx, |session, cx| {
793 session
794 .console_output(cx)
795 .unbounded_send(envelope.payload.message)
796 .ok();
797 })
798 })
799 }
800}
801
802#[derive(Clone)]
803pub struct DapAdapterDelegate {
804 fs: Arc<dyn Fs>,
805 console: mpsc::UnboundedSender<String>,
806 worktree: worktree::Snapshot,
807 node_runtime: NodeRuntime,
808 http_client: Arc<dyn HttpClient>,
809 toolchain_store: Arc<dyn LanguageToolchainStore>,
810 load_shell_env_task: Shared<Task<Option<HashMap<String, String>>>>,
811}
812
813impl DapAdapterDelegate {
814 pub fn new(
815 fs: Arc<dyn Fs>,
816 worktree: worktree::Snapshot,
817 status: mpsc::UnboundedSender<String>,
818 node_runtime: NodeRuntime,
819 http_client: Arc<dyn HttpClient>,
820 toolchain_store: Arc<dyn LanguageToolchainStore>,
821 load_shell_env_task: Shared<Task<Option<HashMap<String, String>>>>,
822 ) -> Self {
823 Self {
824 fs,
825 console: status,
826 worktree,
827 http_client,
828 node_runtime,
829 toolchain_store,
830 load_shell_env_task,
831 }
832 }
833}
834
835#[async_trait]
836impl dap::adapters::DapDelegate for DapAdapterDelegate {
837 fn worktree_id(&self) -> WorktreeId {
838 self.worktree.id()
839 }
840
841 fn worktree_root_path(&self) -> &Path {
842 &self.worktree.abs_path()
843 }
844 fn http_client(&self) -> Arc<dyn HttpClient> {
845 self.http_client.clone()
846 }
847
848 fn node_runtime(&self) -> NodeRuntime {
849 self.node_runtime.clone()
850 }
851
852 fn fs(&self) -> Arc<dyn Fs> {
853 self.fs.clone()
854 }
855
856 fn output_to_console(&self, msg: String) {
857 self.console.unbounded_send(msg).ok();
858 }
859
860 async fn which(&self, command: &OsStr) -> Option<PathBuf> {
861 which::which(command).ok()
862 }
863
864 async fn shell_env(&self) -> HashMap<String, String> {
865 let task = self.load_shell_env_task.clone();
866 task.await.unwrap_or_default()
867 }
868
869 fn toolchain_store(&self) -> Arc<dyn LanguageToolchainStore> {
870 self.toolchain_store.clone()
871 }
872 async fn read_text_file(&self, path: PathBuf) -> Result<String> {
873 let entry = self
874 .worktree
875 .entry_for_path(&path)
876 .with_context(|| format!("no worktree entry for path {path:?}"))?;
877 let abs_path = self
878 .worktree
879 .absolutize(&entry.path)
880 .with_context(|| format!("cannot absolutize path {path:?}"))?;
881
882 self.fs.load(&abs_path).await
883 }
884}