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