1use super::{
2 breakpoint_store::BreakpointStore,
3 locators::DapLocator,
4 session::{self, Session, SessionStateEvent},
5};
6use crate::{
7 InlayHint, InlayHintLabel, ProjectEnvironment, ResolveState,
8 project_settings::ProjectSettings,
9 terminals::{SshCommand, wrap_for_ssh},
10 worktree_store::WorktreeStore,
11};
12use anyhow::{Result, anyhow};
13use async_trait::async_trait;
14use collections::HashMap;
15use dap::{
16 Capabilities, CompletionItem, CompletionsArguments, DapRegistry, EvaluateArguments,
17 EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments, Source,
18 StackFrameId, StartDebuggingRequestArguments,
19 adapters::{DapStatus, DebugAdapterBinary, DebugAdapterName, TcpArguments},
20 client::SessionId,
21 messages::Message,
22 requests::{Completions, Evaluate, Request as _, RunInTerminal, StartDebugging},
23};
24use fs::Fs;
25use futures::{
26 channel::mpsc,
27 future::{Shared, join_all},
28};
29use gpui::{App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task};
30use http_client::HttpClient;
31use language::{
32 BinaryStatus, Buffer, LanguageRegistry, LanguageToolchainStore,
33 language_settings::InlayHintKind, range_from_lsp,
34};
35use lsp::LanguageServerName;
36use node_runtime::NodeRuntime;
37
38use remote::SshRemoteClient;
39use rpc::{
40 AnyProtoClient, TypedEnvelope,
41 proto::{self},
42};
43use serde_json::Value;
44use settings::{Settings, WorktreeId};
45use smol::{lock::Mutex, stream::StreamExt};
46use std::{
47 borrow::Borrow,
48 collections::{BTreeMap, HashSet},
49 ffi::OsStr,
50 net::Ipv4Addr,
51 path::{Path, PathBuf},
52 sync::Arc,
53};
54use task::{DebugTaskDefinition, DebugTaskTemplate};
55use util::ResultExt as _;
56use worktree::Worktree;
57
58#[derive(Debug)]
59pub enum DapStoreEvent {
60 DebugClientStarted(SessionId),
61 DebugSessionInitialized(SessionId),
62 DebugClientShutdown(SessionId),
63 DebugClientEvent {
64 session_id: SessionId,
65 message: Message,
66 },
67 RunInTerminal {
68 session_id: SessionId,
69 title: Option<String>,
70 cwd: Option<Arc<Path>>,
71 command: Option<String>,
72 args: Vec<String>,
73 envs: HashMap<String, String>,
74 sender: mpsc::Sender<Result<u32>>,
75 },
76 SpawnChildSession {
77 request: StartDebuggingRequestArguments,
78 parent_session: Entity<Session>,
79 },
80 Notification(String),
81 RemoteHasInitialized,
82}
83
84#[allow(clippy::large_enum_variant)]
85enum DapStoreMode {
86 Local(LocalDapStore),
87 Ssh(SshDapStore),
88 Collab,
89}
90
91pub struct LocalDapStore {
92 fs: Arc<dyn Fs>,
93 node_runtime: NodeRuntime,
94 http_client: Arc<dyn HttpClient>,
95 environment: Entity<ProjectEnvironment>,
96 language_registry: Arc<LanguageRegistry>,
97 toolchain_store: Arc<dyn LanguageToolchainStore>,
98 locators: HashMap<String, Arc<dyn DapLocator>>,
99}
100
101pub struct SshDapStore {
102 ssh_client: Entity<SshRemoteClient>,
103 upstream_client: AnyProtoClient,
104 upstream_project_id: u64,
105}
106
107pub struct DapStore {
108 mode: DapStoreMode,
109 downstream_client: Option<(AnyProtoClient, u64)>,
110 breakpoint_store: Entity<BreakpointStore>,
111 worktree_store: Entity<WorktreeStore>,
112 sessions: BTreeMap<SessionId, Entity<Session>>,
113 next_session_id: u32,
114 start_debugging_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
115 _start_debugging_task: Task<()>,
116}
117
118impl EventEmitter<DapStoreEvent> for DapStore {}
119
120impl DapStore {
121 pub fn init(client: &AnyProtoClient) {
122 client.add_entity_request_handler(Self::handle_run_debug_locator);
123 client.add_entity_request_handler(Self::handle_get_debug_adapter_binary);
124 }
125
126 #[expect(clippy::too_many_arguments)]
127 pub fn new_local(
128 http_client: Arc<dyn HttpClient>,
129 node_runtime: NodeRuntime,
130 fs: Arc<dyn Fs>,
131 language_registry: Arc<LanguageRegistry>,
132 environment: Entity<ProjectEnvironment>,
133 toolchain_store: Arc<dyn LanguageToolchainStore>,
134 worktree_store: Entity<WorktreeStore>,
135 breakpoint_store: Entity<BreakpointStore>,
136 cx: &mut Context<Self>,
137 ) -> Self {
138 let locators = HashMap::from_iter([(
139 "cargo".to_string(),
140 Arc::new(super::locators::cargo::CargoLocator {}) as _,
141 )]);
142
143 let mode = DapStoreMode::Local(LocalDapStore {
144 fs,
145 environment,
146 http_client,
147 node_runtime,
148 toolchain_store,
149 language_registry,
150 locators,
151 });
152
153 Self::new(mode, breakpoint_store, worktree_store, cx)
154 }
155
156 pub fn new_ssh(
157 project_id: u64,
158 ssh_client: Entity<SshRemoteClient>,
159 breakpoint_store: Entity<BreakpointStore>,
160 worktree_store: Entity<WorktreeStore>,
161 cx: &mut Context<Self>,
162 ) -> Self {
163 let mode = DapStoreMode::Ssh(SshDapStore {
164 upstream_client: ssh_client.read(cx).proto_client(),
165 ssh_client,
166 upstream_project_id: project_id,
167 });
168
169 Self::new(mode, breakpoint_store, worktree_store, cx)
170 }
171
172 pub fn new_collab(
173 _project_id: u64,
174 _upstream_client: AnyProtoClient,
175 breakpoint_store: Entity<BreakpointStore>,
176 worktree_store: Entity<WorktreeStore>,
177 cx: &mut Context<Self>,
178 ) -> Self {
179 Self::new(DapStoreMode::Collab, breakpoint_store, worktree_store, cx)
180 }
181
182 fn new(
183 mode: DapStoreMode,
184 breakpoint_store: Entity<BreakpointStore>,
185 worktree_store: Entity<WorktreeStore>,
186 cx: &mut Context<Self>,
187 ) -> Self {
188 let (start_debugging_tx, mut message_rx) =
189 futures::channel::mpsc::unbounded::<(SessionId, Message)>();
190 let task = cx.spawn(async move |this, cx| {
191 while let Some((session_id, message)) = message_rx.next().await {
192 match message {
193 Message::Request(request) => {
194 let _ = this
195 .update(cx, |this, cx| {
196 if request.command == StartDebugging::COMMAND {
197 this.handle_start_debugging_request(session_id, request, cx)
198 .detach_and_log_err(cx);
199 } else if request.command == RunInTerminal::COMMAND {
200 this.handle_run_in_terminal_request(session_id, request, cx)
201 .detach_and_log_err(cx);
202 }
203 })
204 .log_err();
205 }
206 _ => {}
207 }
208 }
209 });
210
211 Self {
212 mode,
213 _start_debugging_task: task,
214 start_debugging_tx,
215 next_session_id: 0,
216 downstream_client: None,
217 breakpoint_store,
218 worktree_store,
219 sessions: Default::default(),
220 }
221 }
222
223 pub fn get_debug_adapter_binary(
224 &mut self,
225 definition: DebugTaskDefinition,
226 cx: &mut Context<Self>,
227 ) -> Task<Result<DebugAdapterBinary>> {
228 match &self.mode {
229 DapStoreMode::Local(_) => {
230 let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next()
231 else {
232 return Task::ready(Err(anyhow!("Failed to find a worktree")));
233 };
234 let Some(adapter) = DapRegistry::global(cx).adapter(&definition.adapter) else {
235 return Task::ready(Err(anyhow!("Failed to find a debug adapter")));
236 };
237
238 let user_installed_path = ProjectSettings::get_global(cx)
239 .dap
240 .get(&adapter.name())
241 .and_then(|s| s.binary.as_ref().map(PathBuf::from));
242
243 let delegate = self.delegate(&worktree, cx);
244 let cwd: Arc<Path> = definition
245 .cwd()
246 .unwrap_or(worktree.read(cx).abs_path().as_ref())
247 .into();
248
249 cx.spawn(async move |this, cx| {
250 let mut binary = adapter
251 .get_binary(&delegate, &definition, user_installed_path, cx)
252 .await?;
253
254 let env = this
255 .update(cx, |this, cx| {
256 this.as_local()
257 .unwrap()
258 .environment
259 .update(cx, |environment, cx| {
260 environment.get_directory_environment(cwd, cx)
261 })
262 })?
263 .await;
264
265 if let Some(mut env) = env {
266 env.extend(std::mem::take(&mut binary.envs));
267 binary.envs = env;
268 }
269
270 Ok(binary)
271 })
272 }
273 DapStoreMode::Ssh(ssh) => {
274 let request = ssh.upstream_client.request(proto::GetDebugAdapterBinary {
275 project_id: ssh.upstream_project_id,
276 task: Some(definition.to_proto()),
277 });
278 let ssh_client = ssh.ssh_client.clone();
279
280 cx.spawn(async move |_, cx| {
281 let response = request.await?;
282 let binary = DebugAdapterBinary::from_proto(response)?;
283 let mut ssh_command = ssh_client.update(cx, |ssh, _| {
284 anyhow::Ok(SshCommand {
285 arguments: ssh
286 .ssh_args()
287 .ok_or_else(|| anyhow!("SSH arguments not found"))?,
288 })
289 })??;
290
291 let mut connection = None;
292 if let Some(c) = binary.connection {
293 let local_bind_addr = Ipv4Addr::new(127, 0, 0, 1);
294 let port =
295 dap::transport::TcpTransport::unused_port(local_bind_addr).await?;
296
297 ssh_command.add_port_forwarding(port, c.host.to_string(), c.port);
298 connection = Some(TcpArguments {
299 port: c.port,
300 host: local_bind_addr,
301 timeout: c.timeout,
302 })
303 }
304
305 let (program, args) = wrap_for_ssh(
306 &ssh_command,
307 Some((&binary.command, &binary.arguments)),
308 binary.cwd.as_deref(),
309 binary.envs,
310 None,
311 );
312
313 Ok(DebugAdapterBinary {
314 command: program,
315 arguments: args,
316 envs: HashMap::default(),
317 cwd: None,
318 connection,
319 request_args: binary.request_args,
320 })
321 })
322 }
323 DapStoreMode::Collab => {
324 Task::ready(Err(anyhow!("Debugging is not yet supported via collab")))
325 }
326 }
327 }
328
329 pub fn run_debug_locator(
330 &mut self,
331 template: DebugTaskTemplate,
332 cx: &mut Context<Self>,
333 ) -> Task<Result<DebugTaskDefinition>> {
334 let Some(locator_name) = template.locator else {
335 return Task::ready(Ok(template.definition));
336 };
337
338 match &self.mode {
339 DapStoreMode::Local(local) => {
340 if let Some(locator) = local.locators.get(&locator_name).cloned() {
341 cx.background_spawn(
342 async move { locator.run_locator(template.definition).await },
343 )
344 } else {
345 Task::ready(Err(anyhow!("Couldn't find locator {}", locator_name)))
346 }
347 }
348 DapStoreMode::Ssh(ssh) => {
349 let request = ssh.upstream_client.request(proto::RunDebugLocator {
350 project_id: ssh.upstream_project_id,
351 locator: locator_name,
352 task: Some(template.definition.to_proto()),
353 });
354 cx.background_spawn(async move {
355 let response = request.await?;
356 DebugTaskDefinition::from_proto(response)
357 })
358 }
359 DapStoreMode::Collab => {
360 Task::ready(Err(anyhow!("Debugging is not yet supported via collab")))
361 }
362 }
363 }
364
365 fn as_local(&self) -> Option<&LocalDapStore> {
366 match &self.mode {
367 DapStoreMode::Local(local_dap_store) => Some(local_dap_store),
368 _ => None,
369 }
370 }
371
372 pub fn new_session(
373 &mut self,
374 template: DebugTaskDefinition,
375 parent_session: Option<Entity<Session>>,
376 cx: &mut Context<Self>,
377 ) -> Entity<Session> {
378 let session_id = SessionId(util::post_inc(&mut self.next_session_id));
379
380 if let Some(session) = &parent_session {
381 session.update(cx, |session, _| {
382 session.add_child_session_id(session_id);
383 });
384 }
385
386 let start_debugging_tx = self.start_debugging_tx.clone();
387
388 let session = Session::new(
389 self.breakpoint_store.clone(),
390 session_id,
391 parent_session,
392 template.clone(),
393 start_debugging_tx,
394 cx,
395 );
396
397 self.sessions.insert(session_id, session.clone());
398 cx.notify();
399
400 cx.subscribe(&session, {
401 move |this: &mut DapStore, _, event: &SessionStateEvent, cx| match event {
402 SessionStateEvent::Shutdown => {
403 this.shutdown_session(session_id, cx).detach_and_log_err(cx);
404 }
405 SessionStateEvent::Restart => {}
406 SessionStateEvent::Running => {
407 cx.emit(DapStoreEvent::DebugClientStarted(session_id));
408 }
409 }
410 })
411 .detach();
412
413 session
414 }
415
416 pub fn boot_session(
417 &self,
418 session: Entity<Session>,
419 cx: &mut Context<Self>,
420 ) -> Task<Result<()>> {
421 let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next() else {
422 return Task::ready(Err(anyhow!("Failed to find a worktree")));
423 };
424
425 let dap_store = cx.weak_entity();
426 let breakpoint_store = self.breakpoint_store.clone();
427 let definition = session.read(cx).definition();
428
429 cx.spawn({
430 let session = session.clone();
431 async move |this, cx| {
432 let binary = this
433 .update(cx, |this, cx| {
434 this.get_debug_adapter_binary(definition.clone(), cx)
435 })?
436 .await?;
437
438 session
439 .update(cx, |session, cx| {
440 session.boot(binary, worktree, breakpoint_store, dap_store, cx)
441 })?
442 .await
443 }
444 })
445 }
446
447 pub fn session_by_id(
448 &self,
449 session_id: impl Borrow<SessionId>,
450 ) -> Option<Entity<session::Session>> {
451 let session_id = session_id.borrow();
452 let client = self.sessions.get(session_id).cloned();
453
454 client
455 }
456 pub fn sessions(&self) -> impl Iterator<Item = &Entity<Session>> {
457 self.sessions.values()
458 }
459
460 pub fn capabilities_by_id(
461 &self,
462 session_id: impl Borrow<SessionId>,
463 cx: &App,
464 ) -> Option<Capabilities> {
465 let session_id = session_id.borrow();
466 self.sessions
467 .get(session_id)
468 .map(|client| client.read(cx).capabilities.clone())
469 }
470
471 pub fn breakpoint_store(&self) -> &Entity<BreakpointStore> {
472 &self.breakpoint_store
473 }
474
475 pub fn worktree_store(&self) -> &Entity<WorktreeStore> {
476 &self.worktree_store
477 }
478
479 #[allow(dead_code)]
480 async fn handle_ignore_breakpoint_state(
481 this: Entity<Self>,
482 envelope: TypedEnvelope<proto::IgnoreBreakpointState>,
483 mut cx: AsyncApp,
484 ) -> Result<()> {
485 let session_id = SessionId::from_proto(envelope.payload.session_id);
486
487 this.update(&mut cx, |this, cx| {
488 if let Some(session) = this.session_by_id(&session_id) {
489 session.update(cx, |session, cx| {
490 session.set_ignore_breakpoints(envelope.payload.ignore, cx)
491 })
492 } else {
493 Task::ready(HashMap::default())
494 }
495 })?
496 .await;
497
498 Ok(())
499 }
500
501 fn delegate(&self, worktree: &Entity<Worktree>, cx: &mut App) -> DapAdapterDelegate {
502 let Some(local_store) = self.as_local() else {
503 unimplemented!("Starting session on remote side");
504 };
505
506 DapAdapterDelegate::new(
507 local_store.fs.clone(),
508 worktree.read(cx).id(),
509 local_store.node_runtime.clone(),
510 local_store.http_client.clone(),
511 local_store.language_registry.clone(),
512 local_store.toolchain_store.clone(),
513 local_store.environment.update(cx, |env, cx| {
514 env.get_worktree_environment(worktree.clone(), cx)
515 }),
516 )
517 }
518
519 fn handle_start_debugging_request(
520 &mut self,
521 session_id: SessionId,
522 request: dap::messages::Request,
523 cx: &mut Context<Self>,
524 ) -> Task<Result<()>> {
525 let Some(parent_session) = self.session_by_id(session_id) else {
526 return Task::ready(Err(anyhow!("Session not found")));
527 };
528 let request_seq = request.seq;
529
530 let launch_request: Option<Result<StartDebuggingRequestArguments, _>> = request
531 .arguments
532 .as_ref()
533 .map(|value| serde_json::from_value(value.clone()));
534
535 let mut success = true;
536 if let Some(Ok(request)) = launch_request {
537 cx.emit(DapStoreEvent::SpawnChildSession {
538 request,
539 parent_session: parent_session.clone(),
540 });
541 } else {
542 log::error!(
543 "Failed to parse launch request arguments: {:?}",
544 request.arguments
545 );
546 success = false;
547 }
548
549 cx.spawn(async move |_, cx| {
550 parent_session
551 .update(cx, |session, cx| {
552 session.respond_to_client(
553 request_seq,
554 success,
555 StartDebugging::COMMAND.to_string(),
556 None,
557 cx,
558 )
559 })?
560 .await
561 })
562 }
563
564 fn handle_run_in_terminal_request(
565 &mut self,
566 session_id: SessionId,
567 request: dap::messages::Request,
568 cx: &mut Context<Self>,
569 ) -> Task<Result<()>> {
570 let Some(session) = self.session_by_id(session_id) else {
571 return Task::ready(Err(anyhow!("Session not found")));
572 };
573
574 let request_args = serde_json::from_value::<RunInTerminalRequestArguments>(
575 request.arguments.unwrap_or_default(),
576 )
577 .expect("To parse StartDebuggingRequestArguments");
578
579 let seq = request.seq;
580
581 let cwd = Path::new(&request_args.cwd);
582
583 match cwd.try_exists() {
584 Ok(false) | Err(_) if !request_args.cwd.is_empty() => {
585 return session.update(cx, |session, cx| {
586 session.respond_to_client(
587 seq,
588 false,
589 RunInTerminal::COMMAND.to_string(),
590 serde_json::to_value(dap::ErrorResponse {
591 error: Some(dap::Message {
592 id: seq,
593 format: format!("Received invalid/unknown cwd: {cwd:?}"),
594 variables: None,
595 send_telemetry: None,
596 show_user: None,
597 url: None,
598 url_label: None,
599 }),
600 })
601 .ok(),
602 cx,
603 )
604 });
605 }
606 _ => (),
607 }
608 let mut args = request_args.args.clone();
609
610 // Handle special case for NodeJS debug adapter
611 // If only the Node binary path is provided, we set the command to None
612 // This prevents the NodeJS REPL from appearing, which is not the desired behavior
613 // The expected usage is for users to provide their own Node command, e.g., `node test.js`
614 // This allows the NodeJS debug client to attach correctly
615 let command = if args.len() > 1 {
616 Some(args.remove(0))
617 } else {
618 None
619 };
620
621 let mut envs: HashMap<String, String> = Default::default();
622 if let Some(Value::Object(env)) = request_args.env {
623 for (key, value) in env {
624 let value_str = match (key.as_str(), value) {
625 (_, Value::String(value)) => value,
626 _ => continue,
627 };
628
629 envs.insert(key, value_str);
630 }
631 }
632
633 let (tx, mut rx) = mpsc::channel::<Result<u32>>(1);
634 let cwd = Some(cwd)
635 .filter(|cwd| cwd.as_os_str().len() > 0)
636 .map(Arc::from)
637 .or_else(|| {
638 self.session_by_id(session_id)
639 .and_then(|session| session.read(cx).binary().cwd.as_deref().map(Arc::from))
640 });
641 cx.emit(DapStoreEvent::RunInTerminal {
642 session_id,
643 title: request_args.title,
644 cwd,
645 command,
646 args,
647 envs,
648 sender: tx,
649 });
650 cx.notify();
651
652 let session = session.downgrade();
653 cx.spawn(async move |_, cx| {
654 let (success, body) = match rx.next().await {
655 Some(Ok(pid)) => (
656 true,
657 serde_json::to_value(dap::RunInTerminalResponse {
658 process_id: None,
659 shell_process_id: Some(pid as u64),
660 })
661 .ok(),
662 ),
663 Some(Err(error)) => (
664 false,
665 serde_json::to_value(dap::ErrorResponse {
666 error: Some(dap::Message {
667 id: seq,
668 format: error.to_string(),
669 variables: None,
670 send_telemetry: None,
671 show_user: None,
672 url: None,
673 url_label: None,
674 }),
675 })
676 .ok(),
677 ),
678 None => (
679 false,
680 serde_json::to_value(dap::ErrorResponse {
681 error: Some(dap::Message {
682 id: seq,
683 format: "failed to receive response from spawn terminal".to_string(),
684 variables: None,
685 send_telemetry: None,
686 show_user: None,
687 url: None,
688 url_label: None,
689 }),
690 })
691 .ok(),
692 ),
693 };
694
695 session
696 .update(cx, |session, cx| {
697 session.respond_to_client(
698 seq,
699 success,
700 RunInTerminal::COMMAND.to_string(),
701 body,
702 cx,
703 )
704 })?
705 .await
706 })
707 }
708
709 pub fn evaluate(
710 &self,
711 session_id: &SessionId,
712 stack_frame_id: u64,
713 expression: String,
714 context: EvaluateArgumentsContext,
715 source: Option<Source>,
716 cx: &mut Context<Self>,
717 ) -> Task<Result<EvaluateResponse>> {
718 let Some(client) = self
719 .session_by_id(session_id)
720 .and_then(|client| client.read(cx).adapter_client())
721 else {
722 return Task::ready(Err(anyhow!("Could not find client: {:?}", session_id)));
723 };
724
725 cx.background_executor().spawn(async move {
726 client
727 .request::<Evaluate>(EvaluateArguments {
728 expression: expression.clone(),
729 frame_id: Some(stack_frame_id),
730 context: Some(context),
731 format: None,
732 line: None,
733 column: None,
734 source,
735 })
736 .await
737 })
738 }
739
740 pub fn completions(
741 &self,
742 session_id: &SessionId,
743 stack_frame_id: u64,
744 text: String,
745 completion_column: u64,
746 cx: &mut Context<Self>,
747 ) -> Task<Result<Vec<CompletionItem>>> {
748 let Some(client) = self
749 .session_by_id(session_id)
750 .and_then(|client| client.read(cx).adapter_client())
751 else {
752 return Task::ready(Err(anyhow!("Could not find client: {:?}", session_id)));
753 };
754
755 cx.background_executor().spawn(async move {
756 Ok(client
757 .request::<Completions>(CompletionsArguments {
758 frame_id: Some(stack_frame_id),
759 line: None,
760 text,
761 column: completion_column,
762 })
763 .await?
764 .targets)
765 })
766 }
767
768 pub fn resolve_inline_values(
769 &self,
770 session: Entity<Session>,
771 stack_frame_id: StackFrameId,
772 buffer_handle: Entity<Buffer>,
773 inline_values: Vec<lsp::InlineValue>,
774 cx: &mut Context<Self>,
775 ) -> Task<Result<Vec<InlayHint>>> {
776 let snapshot = buffer_handle.read(cx).snapshot();
777 let all_variables = session.read(cx).variables_by_stack_frame_id(stack_frame_id);
778
779 cx.spawn(async move |_, cx| {
780 let mut inlay_hints = Vec::with_capacity(inline_values.len());
781 for inline_value in inline_values.iter() {
782 match inline_value {
783 lsp::InlineValue::Text(text) => {
784 inlay_hints.push(InlayHint {
785 position: snapshot.anchor_after(range_from_lsp(text.range).end),
786 label: InlayHintLabel::String(format!(": {}", text.text)),
787 kind: Some(InlayHintKind::Type),
788 padding_left: false,
789 padding_right: false,
790 tooltip: None,
791 resolve_state: ResolveState::Resolved,
792 });
793 }
794 lsp::InlineValue::VariableLookup(variable_lookup) => {
795 let range = range_from_lsp(variable_lookup.range);
796
797 let mut variable_name = variable_lookup
798 .variable_name
799 .clone()
800 .unwrap_or_else(|| snapshot.text_for_range(range.clone()).collect());
801
802 if !variable_lookup.case_sensitive_lookup {
803 variable_name = variable_name.to_ascii_lowercase();
804 }
805
806 let Some(variable) = all_variables.iter().find(|variable| {
807 if variable_lookup.case_sensitive_lookup {
808 variable.name == variable_name
809 } else {
810 variable.name.to_ascii_lowercase() == variable_name
811 }
812 }) else {
813 continue;
814 };
815
816 inlay_hints.push(InlayHint {
817 position: snapshot.anchor_after(range.end),
818 label: InlayHintLabel::String(format!(": {}", variable.value)),
819 kind: Some(InlayHintKind::Type),
820 padding_left: false,
821 padding_right: false,
822 tooltip: None,
823 resolve_state: ResolveState::Resolved,
824 });
825 }
826 lsp::InlineValue::EvaluatableExpression(expression) => {
827 let range = range_from_lsp(expression.range);
828
829 let expression = expression
830 .expression
831 .clone()
832 .unwrap_or_else(|| snapshot.text_for_range(range.clone()).collect());
833
834 let Ok(eval_task) = session.update(cx, |session, cx| {
835 session.evaluate(
836 expression,
837 Some(EvaluateArgumentsContext::Variables),
838 Some(stack_frame_id),
839 None,
840 cx,
841 )
842 }) else {
843 continue;
844 };
845
846 if let Some(response) = eval_task.await {
847 inlay_hints.push(InlayHint {
848 position: snapshot.anchor_after(range.end),
849 label: InlayHintLabel::String(format!(": {}", response.result)),
850 kind: Some(InlayHintKind::Type),
851 padding_left: false,
852 padding_right: false,
853 tooltip: None,
854 resolve_state: ResolveState::Resolved,
855 });
856 };
857 }
858 };
859 }
860
861 Ok(inlay_hints)
862 })
863 }
864
865 pub fn shutdown_sessions(&mut self, cx: &mut Context<Self>) -> Task<()> {
866 let mut tasks = vec![];
867 for session_id in self.sessions.keys().cloned().collect::<Vec<_>>() {
868 tasks.push(self.shutdown_session(session_id, cx));
869 }
870
871 cx.background_executor().spawn(async move {
872 futures::future::join_all(tasks).await;
873 })
874 }
875
876 pub fn shutdown_session(
877 &mut self,
878 session_id: SessionId,
879 cx: &mut Context<Self>,
880 ) -> Task<Result<()>> {
881 let Some(session) = self.sessions.remove(&session_id) else {
882 return Task::ready(Err(anyhow!("Could not find session: {:?}", session_id)));
883 };
884
885 let shutdown_children = session
886 .read(cx)
887 .child_session_ids()
888 .iter()
889 .map(|session_id| self.shutdown_session(*session_id, cx))
890 .collect::<Vec<_>>();
891
892 let shutdown_parent_task = if let Some(parent_session) = session
893 .read(cx)
894 .parent_id(cx)
895 .and_then(|session_id| self.session_by_id(session_id))
896 {
897 let shutdown_id = parent_session.update(cx, |parent_session, _| {
898 parent_session.remove_child_session_id(session_id);
899
900 if parent_session.child_session_ids().len() == 0 {
901 Some(parent_session.session_id())
902 } else {
903 None
904 }
905 });
906
907 shutdown_id.map(|session_id| self.shutdown_session(session_id, cx))
908 } else {
909 None
910 };
911
912 let shutdown_task = session.update(cx, |this, cx| this.shutdown(cx));
913
914 cx.background_spawn(async move {
915 if shutdown_children.len() > 0 {
916 let _ = join_all(shutdown_children).await;
917 }
918
919 shutdown_task.await;
920
921 if let Some(parent_task) = shutdown_parent_task {
922 parent_task.await?;
923 }
924
925 Ok(())
926 })
927 }
928
929 pub fn shared(
930 &mut self,
931 project_id: u64,
932 downstream_client: AnyProtoClient,
933 _: &mut Context<Self>,
934 ) {
935 self.downstream_client = Some((downstream_client.clone(), project_id));
936 }
937
938 pub fn unshared(&mut self, cx: &mut Context<Self>) {
939 self.downstream_client.take();
940
941 cx.notify();
942 }
943
944 async fn handle_run_debug_locator(
945 this: Entity<Self>,
946 envelope: TypedEnvelope<proto::RunDebugLocator>,
947 mut cx: AsyncApp,
948 ) -> Result<proto::DebugTaskDefinition> {
949 let template = DebugTaskTemplate {
950 locator: Some(envelope.payload.locator),
951 definition: DebugTaskDefinition::from_proto(
952 envelope
953 .payload
954 .task
955 .ok_or_else(|| anyhow!("missing definition"))?,
956 )?,
957 };
958 let definition = this
959 .update(&mut cx, |this, cx| this.run_debug_locator(template, cx))?
960 .await?;
961 Ok(definition.to_proto())
962 }
963
964 async fn handle_get_debug_adapter_binary(
965 this: Entity<Self>,
966 envelope: TypedEnvelope<proto::GetDebugAdapterBinary>,
967 mut cx: AsyncApp,
968 ) -> Result<proto::DebugAdapterBinary> {
969 let definition = DebugTaskDefinition::from_proto(
970 envelope
971 .payload
972 .task
973 .ok_or_else(|| anyhow!("missing definition"))?,
974 )?;
975 let binary = this
976 .update(&mut cx, |this, cx| {
977 this.get_debug_adapter_binary(definition, cx)
978 })?
979 .await?;
980 Ok(binary.to_proto())
981 }
982}
983
984#[derive(Clone)]
985pub struct DapAdapterDelegate {
986 fs: Arc<dyn Fs>,
987 worktree_id: WorktreeId,
988 node_runtime: NodeRuntime,
989 http_client: Arc<dyn HttpClient>,
990 language_registry: Arc<LanguageRegistry>,
991 toolchain_store: Arc<dyn LanguageToolchainStore>,
992 updated_adapters: Arc<Mutex<HashSet<DebugAdapterName>>>,
993 load_shell_env_task: Shared<Task<Option<HashMap<String, String>>>>,
994}
995
996impl DapAdapterDelegate {
997 pub fn new(
998 fs: Arc<dyn Fs>,
999 worktree_id: WorktreeId,
1000 node_runtime: NodeRuntime,
1001 http_client: Arc<dyn HttpClient>,
1002 language_registry: Arc<LanguageRegistry>,
1003 toolchain_store: Arc<dyn LanguageToolchainStore>,
1004 load_shell_env_task: Shared<Task<Option<HashMap<String, String>>>>,
1005 ) -> Self {
1006 Self {
1007 fs,
1008 worktree_id,
1009 http_client,
1010 node_runtime,
1011 toolchain_store,
1012 language_registry,
1013 load_shell_env_task,
1014 updated_adapters: Default::default(),
1015 }
1016 }
1017}
1018
1019#[async_trait(?Send)]
1020impl dap::adapters::DapDelegate for DapAdapterDelegate {
1021 fn worktree_id(&self) -> WorktreeId {
1022 self.worktree_id
1023 }
1024
1025 fn http_client(&self) -> Arc<dyn HttpClient> {
1026 self.http_client.clone()
1027 }
1028
1029 fn node_runtime(&self) -> NodeRuntime {
1030 self.node_runtime.clone()
1031 }
1032
1033 fn fs(&self) -> Arc<dyn Fs> {
1034 self.fs.clone()
1035 }
1036
1037 fn updated_adapters(&self) -> Arc<Mutex<HashSet<DebugAdapterName>>> {
1038 self.updated_adapters.clone()
1039 }
1040
1041 fn update_status(&self, dap_name: DebugAdapterName, status: dap::adapters::DapStatus) {
1042 let name = SharedString::from(dap_name.to_string());
1043 let status = match status {
1044 DapStatus::None => BinaryStatus::None,
1045 DapStatus::Downloading => BinaryStatus::Downloading,
1046 DapStatus::Failed { error } => BinaryStatus::Failed { error },
1047 DapStatus::CheckingForUpdate => BinaryStatus::CheckingForUpdate,
1048 };
1049
1050 self.language_registry
1051 .update_dap_status(LanguageServerName(name), status);
1052 }
1053
1054 fn which(&self, command: &OsStr) -> Option<PathBuf> {
1055 which::which(command).ok()
1056 }
1057
1058 async fn shell_env(&self) -> HashMap<String, String> {
1059 let task = self.load_shell_env_task.clone();
1060 task.await.unwrap_or_default()
1061 }
1062
1063 fn toolchain_store(&self) -> Arc<dyn LanguageToolchainStore> {
1064 self.toolchain_store.clone()
1065 }
1066}