debugger_panel.rs

   1use crate::{
   2    persistence::DebuggerPaneItem,
   3    tests::{start_debug_session, start_debug_session_with},
   4    *,
   5};
   6use dap::{
   7    ErrorResponse, Message, RunInTerminalRequestArguments, SourceBreakpoint,
   8    StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest,
   9    adapters::DebugTaskDefinition,
  10    client::SessionId,
  11    requests::{
  12        Continue, Disconnect, Launch, Next, RunInTerminal, SetBreakpoints, StackTrace,
  13        StartDebugging, StepBack, StepIn, StepOut, Threads,
  14    },
  15};
  16use editor::{
  17    ActiveDebugLine, Editor, EditorMode, MultiBuffer,
  18    actions::{self},
  19};
  20use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
  21use project::{
  22    FakeFs, Project,
  23    debugger::session::{ThreadId, ThreadStatus},
  24};
  25use serde_json::json;
  26use std::{
  27    path::Path,
  28    sync::{
  29        Arc,
  30        atomic::{AtomicBool, Ordering},
  31    },
  32};
  33use terminal_view::terminal_panel::TerminalPanel;
  34use tests::{active_debug_session_panel, init_test, init_test_workspace};
  35use util::path;
  36use workspace::{Item, dock::Panel};
  37
  38#[gpui::test]
  39async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut TestAppContext) {
  40    init_test(cx);
  41
  42    let fs = FakeFs::new(executor.clone());
  43
  44    fs.insert_tree(
  45        path!("/project"),
  46        json!({
  47            "main.rs": "First line\nSecond line\nThird line\nFourth line",
  48        }),
  49    )
  50    .await;
  51
  52    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
  53    let workspace = init_test_workspace(&project, cx).await;
  54    let cx = &mut VisualTestContext::from_window(*workspace, cx);
  55
  56    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
  57    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
  58
  59    client.on_request::<Threads, _>(move |_, _| {
  60        Ok(dap::ThreadsResponse {
  61            threads: vec![dap::Thread {
  62                id: 1,
  63                name: "Thread 1".into(),
  64            }],
  65        })
  66    });
  67
  68    client.on_request::<StackTrace, _>(move |_, _| {
  69        Ok(dap::StackTraceResponse {
  70            stack_frames: Vec::default(),
  71            total_frames: None,
  72        })
  73    });
  74
  75    cx.run_until_parked();
  76
  77    // assert we have a debug panel item before the session has stopped
  78    workspace
  79        .update(cx, |workspace, _window, cx| {
  80            let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
  81            let active_session =
  82                debug_panel.update(cx, |debug_panel, _| debug_panel.active_session().unwrap());
  83
  84            let running_state = active_session.update(cx, |active_session, _| {
  85                active_session.running_state().clone()
  86            });
  87
  88            debug_panel.update(cx, |this, cx| {
  89                assert!(this.active_session().is_some());
  90                assert!(running_state.read(cx).selected_thread_id().is_none());
  91            });
  92        })
  93        .unwrap();
  94
  95    client
  96        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
  97            reason: dap::StoppedEventReason::Pause,
  98            description: None,
  99            thread_id: Some(1),
 100            preserve_focus_hint: None,
 101            text: None,
 102            all_threads_stopped: None,
 103            hit_breakpoint_ids: None,
 104        }))
 105        .await;
 106
 107    cx.run_until_parked();
 108
 109    workspace
 110        .update(cx, |workspace, _window, cx| {
 111            let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
 112            let active_session = debug_panel
 113                .update(cx, |this, _| this.active_session())
 114                .unwrap();
 115
 116            let running_state = active_session.update(cx, |active_session, _| {
 117                active_session.running_state().clone()
 118            });
 119
 120            assert_eq!(client.id(), running_state.read(cx).session_id());
 121            assert_eq!(
 122                ThreadId(1),
 123                running_state.read(cx).selected_thread_id().unwrap()
 124            );
 125        })
 126        .unwrap();
 127
 128    let shutdown_session = project.update(cx, |project, cx| {
 129        project.dap_store().update(cx, |dap_store, cx| {
 130            dap_store.shutdown_session(session.read(cx).session_id(), cx)
 131        })
 132    });
 133
 134    shutdown_session.await.unwrap();
 135
 136    // assert we still have a debug panel item after the client shutdown
 137    workspace
 138        .update(cx, |workspace, _window, cx| {
 139            let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
 140
 141            let active_session = debug_panel
 142                .update(cx, |this, _| this.active_session())
 143                .unwrap();
 144
 145            let running_state = active_session.update(cx, |active_session, _| {
 146                active_session.running_state().clone()
 147            });
 148
 149            debug_panel.update(cx, |this, cx| {
 150                assert!(this.active_session().is_some());
 151                assert_eq!(
 152                    ThreadId(1),
 153                    running_state.read(cx).selected_thread_id().unwrap()
 154                );
 155            });
 156        })
 157        .unwrap();
 158}
 159
 160#[gpui::test]
 161async fn test_we_can_only_have_one_panel_per_debug_session(
 162    executor: BackgroundExecutor,
 163    cx: &mut TestAppContext,
 164) {
 165    init_test(cx);
 166
 167    let fs = FakeFs::new(executor.clone());
 168
 169    fs.insert_tree(
 170        path!("/project"),
 171        json!({
 172            "main.rs": "First line\nSecond line\nThird line\nFourth line",
 173        }),
 174    )
 175    .await;
 176
 177    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
 178    let workspace = init_test_workspace(&project, cx).await;
 179    let cx = &mut VisualTestContext::from_window(*workspace, cx);
 180
 181    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
 182    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 183
 184    client.on_request::<Threads, _>(move |_, _| {
 185        Ok(dap::ThreadsResponse {
 186            threads: vec![dap::Thread {
 187                id: 1,
 188                name: "Thread 1".into(),
 189            }],
 190        })
 191    });
 192
 193    client.on_request::<StackTrace, _>(move |_, _| {
 194        Ok(dap::StackTraceResponse {
 195            stack_frames: Vec::default(),
 196            total_frames: None,
 197        })
 198    });
 199
 200    cx.run_until_parked();
 201
 202    // assert we have a debug panel item before the session has stopped
 203    workspace
 204        .update(cx, |workspace, _window, cx| {
 205            let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
 206
 207            debug_panel.update(cx, |this, _| {
 208                assert!(this.active_session().is_some());
 209            });
 210        })
 211        .unwrap();
 212
 213    client
 214        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
 215            reason: dap::StoppedEventReason::Pause,
 216            description: None,
 217            thread_id: Some(1),
 218            preserve_focus_hint: None,
 219            text: None,
 220            all_threads_stopped: None,
 221            hit_breakpoint_ids: None,
 222        }))
 223        .await;
 224
 225    cx.run_until_parked();
 226
 227    // assert we added a debug panel item
 228    workspace
 229        .update(cx, |workspace, _window, cx| {
 230            let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
 231            let active_session = debug_panel
 232                .update(cx, |this, _| this.active_session())
 233                .unwrap();
 234
 235            let running_state = active_session.update(cx, |active_session, _| {
 236                active_session.running_state().clone()
 237            });
 238
 239            assert_eq!(client.id(), active_session.read(cx).session_id(cx));
 240            assert_eq!(
 241                ThreadId(1),
 242                running_state.read(cx).selected_thread_id().unwrap()
 243            );
 244        })
 245        .unwrap();
 246
 247    client
 248        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
 249            reason: dap::StoppedEventReason::Pause,
 250            description: None,
 251            thread_id: Some(2),
 252            preserve_focus_hint: None,
 253            text: None,
 254            all_threads_stopped: None,
 255            hit_breakpoint_ids: None,
 256        }))
 257        .await;
 258
 259    cx.run_until_parked();
 260
 261    workspace
 262        .update(cx, |workspace, _window, cx| {
 263            let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
 264            let active_session = debug_panel
 265                .update(cx, |this, _| this.active_session())
 266                .unwrap();
 267
 268            let running_state = active_session.update(cx, |active_session, _| {
 269                active_session.running_state().clone()
 270            });
 271
 272            assert_eq!(client.id(), active_session.read(cx).session_id(cx));
 273            assert_eq!(
 274                ThreadId(1),
 275                running_state.read(cx).selected_thread_id().unwrap()
 276            );
 277        })
 278        .unwrap();
 279
 280    let shutdown_session = project.update(cx, |project, cx| {
 281        project.dap_store().update(cx, |dap_store, cx| {
 282            dap_store.shutdown_session(session.read(cx).session_id(), cx)
 283        })
 284    });
 285
 286    shutdown_session.await.unwrap();
 287
 288    // assert we still have a debug panel item after the client shutdown
 289    workspace
 290        .update(cx, |workspace, _window, cx| {
 291            let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
 292            let active_session = debug_panel
 293                .update(cx, |this, _| this.active_session())
 294                .unwrap();
 295
 296            let running_state = active_session.update(cx, |active_session, _| {
 297                active_session.running_state().clone()
 298            });
 299
 300            debug_panel.update(cx, |this, cx| {
 301                assert!(this.active_session().is_some());
 302                assert_eq!(
 303                    ThreadId(1),
 304                    running_state.read(cx).selected_thread_id().unwrap()
 305                );
 306            });
 307        })
 308        .unwrap();
 309}
 310
 311#[gpui::test]
 312async fn test_handle_successful_run_in_terminal_reverse_request(
 313    executor: BackgroundExecutor,
 314    cx: &mut TestAppContext,
 315) {
 316    init_test(cx);
 317
 318    let send_response = Arc::new(AtomicBool::new(false));
 319
 320    let fs = FakeFs::new(executor.clone());
 321
 322    fs.insert_tree(
 323        path!("/project"),
 324        json!({
 325            "main.rs": "First line\nSecond line\nThird line\nFourth line",
 326        }),
 327    )
 328    .await;
 329
 330    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
 331    let workspace = init_test_workspace(&project, cx).await;
 332    let cx = &mut VisualTestContext::from_window(*workspace, cx);
 333
 334    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
 335    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 336
 337    client
 338        .on_response::<RunInTerminal, _>({
 339            let send_response = send_response.clone();
 340            move |response| {
 341                send_response.store(true, Ordering::SeqCst);
 342
 343                assert!(response.success);
 344                assert!(response.body.is_some());
 345            }
 346        })
 347        .await;
 348
 349    client
 350        .fake_reverse_request::<RunInTerminal>(RunInTerminalRequestArguments {
 351            kind: None,
 352            title: None,
 353            cwd: std::env::temp_dir().to_string_lossy().to_string(),
 354            args: vec![],
 355            env: None,
 356            args_can_be_interpreted_by_shell: None,
 357        })
 358        .await;
 359
 360    cx.run_until_parked();
 361
 362    assert!(
 363        send_response.load(std::sync::atomic::Ordering::SeqCst),
 364        "Expected to receive response from reverse request"
 365    );
 366
 367    workspace
 368        .update(cx, |workspace, _window, cx| {
 369            let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
 370            let session = debug_panel.read(cx).active_session().unwrap();
 371            let running = session.read(cx).running_state();
 372            assert_eq!(
 373                running
 374                    .read(cx)
 375                    .pane_items_status(cx)
 376                    .get(&DebuggerPaneItem::Terminal),
 377                Some(&true)
 378            );
 379            assert!(running.read(cx).debug_terminal.read(cx).terminal.is_some());
 380        })
 381        .unwrap();
 382}
 383
 384#[gpui::test]
 385async fn test_handle_start_debugging_request(
 386    executor: BackgroundExecutor,
 387    cx: &mut TestAppContext,
 388) {
 389    init_test(cx);
 390
 391    let fs = FakeFs::new(executor.clone());
 392
 393    fs.insert_tree(
 394        path!("/project"),
 395        json!({
 396            "main.rs": "First line\nSecond line\nThird line\nFourth line",
 397        }),
 398    )
 399    .await;
 400
 401    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
 402    let workspace = init_test_workspace(&project, cx).await;
 403    let cx = &mut VisualTestContext::from_window(*workspace, cx);
 404
 405    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
 406    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 407
 408    let fake_config = json!({"one": "two"});
 409    let launched_with = Arc::new(parking_lot::Mutex::new(None));
 410
 411    let _subscription = project::debugger::test::intercept_debug_sessions(cx, {
 412        let launched_with = launched_with.clone();
 413        move |client| {
 414            let launched_with = launched_with.clone();
 415            client.on_request::<dap::requests::Launch, _>(move |_, args| {
 416                launched_with.lock().replace(args.raw);
 417                Ok(())
 418            });
 419            client.on_request::<dap::requests::Attach, _>(move |_, _| {
 420                assert!(false, "should not get attach request");
 421                Ok(())
 422            });
 423        }
 424    });
 425
 426    let sessions = workspace
 427        .update(cx, |workspace, _window, cx| {
 428            let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
 429            debug_panel.read(cx).sessions()
 430        })
 431        .unwrap();
 432    assert_eq!(sessions.len(), 1);
 433    client
 434        .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
 435            request: StartDebuggingRequestArgumentsRequest::Launch,
 436            configuration: fake_config.clone(),
 437        })
 438        .await;
 439
 440    cx.run_until_parked();
 441
 442    workspace
 443        .update(cx, |workspace, _window, cx| {
 444            let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
 445
 446            // Active session changes on spawn, as the parent has never stopped.
 447            let active_session = debug_panel
 448                .read(cx)
 449                .active_session()
 450                .unwrap()
 451                .read(cx)
 452                .session(cx);
 453            let current_sessions = debug_panel.read(cx).sessions();
 454            assert_eq!(active_session, current_sessions[1].read(cx).session(cx));
 455            assert_eq!(
 456                active_session.read(cx).parent_session(),
 457                Some(&current_sessions[0].read(cx).session(cx))
 458            );
 459
 460            assert_eq!(current_sessions.len(), 2);
 461            assert_eq!(current_sessions[0], sessions[0]);
 462
 463            let parent_session = current_sessions[1]
 464                .read(cx)
 465                .session(cx)
 466                .read(cx)
 467                .parent_session()
 468                .unwrap();
 469            assert_eq!(parent_session, &sessions[0].read(cx).session(cx));
 470
 471            // We should preserve the original binary (params to spawn process etc.) except for launch params
 472            // (as they come from reverse spawn request).
 473            let mut original_binary = parent_session.read(cx).binary().cloned().unwrap();
 474            original_binary.request_args = StartDebuggingRequestArguments {
 475                request: StartDebuggingRequestArgumentsRequest::Launch,
 476                configuration: fake_config.clone(),
 477            };
 478
 479            assert_eq!(
 480                current_sessions[1]
 481                    .read(cx)
 482                    .session(cx)
 483                    .read(cx)
 484                    .binary()
 485                    .unwrap(),
 486                &original_binary
 487            );
 488        })
 489        .unwrap();
 490
 491    assert_eq!(&fake_config, launched_with.lock().as_ref().unwrap());
 492}
 493
 494// // covers that we always send a response back, if something when wrong,
 495// // while spawning the terminal
 496#[gpui::test]
 497async fn test_handle_error_run_in_terminal_reverse_request(
 498    executor: BackgroundExecutor,
 499    cx: &mut TestAppContext,
 500) {
 501    init_test(cx);
 502
 503    let send_response = Arc::new(AtomicBool::new(false));
 504
 505    let fs = FakeFs::new(executor.clone());
 506
 507    fs.insert_tree(
 508        path!("/project"),
 509        json!({
 510            "main.rs": "First line\nSecond line\nThird line\nFourth line",
 511        }),
 512    )
 513    .await;
 514
 515    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
 516    let workspace = init_test_workspace(&project, cx).await;
 517    let cx = &mut VisualTestContext::from_window(*workspace, cx);
 518
 519    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
 520    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 521
 522    client
 523        .on_response::<RunInTerminal, _>({
 524            let send_response = send_response.clone();
 525            move |response| {
 526                send_response.store(true, Ordering::SeqCst);
 527
 528                assert!(!response.success);
 529                assert!(response.body.is_some());
 530            }
 531        })
 532        .await;
 533
 534    client
 535        .fake_reverse_request::<RunInTerminal>(RunInTerminalRequestArguments {
 536            kind: None,
 537            title: None,
 538            cwd: "".into(),
 539            args: vec!["oops".into(), "oops".into()],
 540            env: None,
 541            args_can_be_interpreted_by_shell: None,
 542        })
 543        .await;
 544
 545    cx.run_until_parked();
 546
 547    assert!(
 548        send_response.load(std::sync::atomic::Ordering::SeqCst),
 549        "Expected to receive response from reverse request"
 550    );
 551
 552    workspace
 553        .update(cx, |workspace, _window, cx| {
 554            let terminal_panel = workspace.panel::<TerminalPanel>(cx).unwrap();
 555
 556            assert_eq!(
 557                0,
 558                terminal_panel.read(cx).pane().unwrap().read(cx).items_len()
 559            );
 560        })
 561        .unwrap();
 562}
 563
 564#[gpui::test]
 565async fn test_handle_start_debugging_reverse_request(
 566    executor: BackgroundExecutor,
 567    cx: &mut TestAppContext,
 568) {
 569    init_test(cx);
 570
 571    let send_response = Arc::new(AtomicBool::new(false));
 572
 573    let fs = FakeFs::new(executor.clone());
 574
 575    fs.insert_tree(
 576        path!("/project"),
 577        json!({
 578            "main.rs": "First line\nSecond line\nThird line\nFourth line",
 579        }),
 580    )
 581    .await;
 582
 583    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
 584    let workspace = init_test_workspace(&project, cx).await;
 585    let cx = &mut VisualTestContext::from_window(*workspace, cx);
 586
 587    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
 588    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 589
 590    client.on_request::<dap::requests::Threads, _>(move |_, _| {
 591        Ok(dap::ThreadsResponse {
 592            threads: vec![dap::Thread {
 593                id: 1,
 594                name: "Thread 1".into(),
 595            }],
 596        })
 597    });
 598
 599    client
 600        .on_response::<StartDebugging, _>({
 601            let send_response = send_response.clone();
 602            move |response| {
 603                send_response.store(true, Ordering::SeqCst);
 604
 605                assert!(response.success);
 606                assert!(response.body.is_some());
 607            }
 608        })
 609        .await;
 610    // Set up handlers for sessions spawned with reverse request too.
 611    let _reverse_request_subscription =
 612        project::debugger::test::intercept_debug_sessions(cx, |_| {});
 613    client
 614        .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
 615            configuration: json!({}),
 616            request: StartDebuggingRequestArgumentsRequest::Launch,
 617        })
 618        .await;
 619
 620    cx.run_until_parked();
 621
 622    let child_session = project.update(cx, |project, cx| {
 623        project
 624            .dap_store()
 625            .read(cx)
 626            .session_by_id(SessionId(1))
 627            .unwrap()
 628    });
 629    let child_client = child_session.update(cx, |session, _| session.adapter_client().unwrap());
 630
 631    child_client.on_request::<dap::requests::Threads, _>(move |_, _| {
 632        Ok(dap::ThreadsResponse {
 633            threads: vec![dap::Thread {
 634                id: 1,
 635                name: "Thread 1".into(),
 636            }],
 637        })
 638    });
 639
 640    child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
 641
 642    child_client
 643        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
 644            reason: dap::StoppedEventReason::Pause,
 645            description: None,
 646            thread_id: Some(2),
 647            preserve_focus_hint: None,
 648            text: None,
 649            all_threads_stopped: None,
 650            hit_breakpoint_ids: None,
 651        }))
 652        .await;
 653
 654    cx.run_until_parked();
 655
 656    assert!(
 657        send_response.load(std::sync::atomic::Ordering::SeqCst),
 658        "Expected to receive response from reverse request"
 659    );
 660}
 661
 662#[gpui::test]
 663async fn test_shutdown_children_when_parent_session_shutdown(
 664    executor: BackgroundExecutor,
 665    cx: &mut TestAppContext,
 666) {
 667    init_test(cx);
 668
 669    let fs = FakeFs::new(executor.clone());
 670
 671    fs.insert_tree(
 672        path!("/project"),
 673        json!({
 674            "main.rs": "First line\nSecond line\nThird line\nFourth line",
 675        }),
 676    )
 677    .await;
 678
 679    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
 680    let dap_store = project.update(cx, |project, _| project.dap_store());
 681    let workspace = init_test_workspace(&project, cx).await;
 682    let cx = &mut VisualTestContext::from_window(*workspace, cx);
 683
 684    let parent_session = start_debug_session(&workspace, cx, |_| {}).unwrap();
 685    let client = parent_session.update(cx, |session, _| session.adapter_client().unwrap());
 686
 687    client.on_request::<dap::requests::Threads, _>(move |_, _| {
 688        Ok(dap::ThreadsResponse {
 689            threads: vec![dap::Thread {
 690                id: 1,
 691                name: "Thread 1".into(),
 692            }],
 693        })
 694    });
 695
 696    client.on_response::<StartDebugging, _>(move |_| {}).await;
 697    // Set up handlers for sessions spawned with reverse request too.
 698    let _reverse_request_subscription =
 699        project::debugger::test::intercept_debug_sessions(cx, |_| {});
 700    // start first child session
 701    client
 702        .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
 703            configuration: json!({}),
 704            request: StartDebuggingRequestArgumentsRequest::Launch,
 705        })
 706        .await;
 707
 708    cx.run_until_parked();
 709
 710    // start second child session
 711    client
 712        .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
 713            configuration: json!({}),
 714            request: StartDebuggingRequestArgumentsRequest::Launch,
 715        })
 716        .await;
 717
 718    cx.run_until_parked();
 719
 720    // configure first child session
 721    let first_child_session = dap_store.read_with(cx, |dap_store, _| {
 722        dap_store.session_by_id(SessionId(1)).unwrap()
 723    });
 724    let first_child_client =
 725        first_child_session.update(cx, |session, _| session.adapter_client().unwrap());
 726
 727    first_child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
 728
 729    // configure second child session
 730    let second_child_session = dap_store.read_with(cx, |dap_store, _| {
 731        dap_store.session_by_id(SessionId(2)).unwrap()
 732    });
 733    let second_child_client =
 734        second_child_session.update(cx, |session, _| session.adapter_client().unwrap());
 735
 736    second_child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
 737
 738    cx.run_until_parked();
 739
 740    // shutdown parent session
 741    dap_store
 742        .update(cx, |dap_store, cx| {
 743            dap_store.shutdown_session(parent_session.read(cx).session_id(), cx)
 744        })
 745        .await
 746        .unwrap();
 747
 748    // assert parent session and all children sessions are shutdown
 749    dap_store.update(cx, |dap_store, cx| {
 750        assert!(
 751            dap_store
 752                .session_by_id(parent_session.read(cx).session_id())
 753                .is_none()
 754        );
 755        assert!(
 756            dap_store
 757                .session_by_id(first_child_session.read(cx).session_id())
 758                .is_none()
 759        );
 760        assert!(
 761            dap_store
 762                .session_by_id(second_child_session.read(cx).session_id())
 763                .is_none()
 764        );
 765    });
 766}
 767
 768#[gpui::test]
 769async fn test_shutdown_parent_session_if_all_children_are_shutdown(
 770    executor: BackgroundExecutor,
 771    cx: &mut TestAppContext,
 772) {
 773    init_test(cx);
 774
 775    let fs = FakeFs::new(executor.clone());
 776
 777    fs.insert_tree(
 778        path!("/project"),
 779        json!({
 780            "main.rs": "First line\nSecond line\nThird line\nFourth line",
 781        }),
 782    )
 783    .await;
 784
 785    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
 786    let dap_store = project.update(cx, |project, _| project.dap_store());
 787    let workspace = init_test_workspace(&project, cx).await;
 788    let cx = &mut VisualTestContext::from_window(*workspace, cx);
 789
 790    let parent_session = start_debug_session(&workspace, cx, |_| {}).unwrap();
 791    let client = parent_session.update(cx, |session, _| session.adapter_client().unwrap());
 792
 793    client.on_response::<StartDebugging, _>(move |_| {}).await;
 794    // Set up handlers for sessions spawned with reverse request too.
 795    let _reverse_request_subscription =
 796        project::debugger::test::intercept_debug_sessions(cx, |_| {});
 797    // start first child session
 798    client
 799        .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
 800            configuration: json!({}),
 801            request: StartDebuggingRequestArgumentsRequest::Launch,
 802        })
 803        .await;
 804
 805    cx.run_until_parked();
 806
 807    // start second child session
 808    client
 809        .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
 810            configuration: json!({}),
 811            request: StartDebuggingRequestArgumentsRequest::Launch,
 812        })
 813        .await;
 814
 815    cx.run_until_parked();
 816
 817    // configure first child session
 818    let first_child_session = dap_store.read_with(cx, |dap_store, _| {
 819        dap_store.session_by_id(SessionId(1)).unwrap()
 820    });
 821    let first_child_client =
 822        first_child_session.update(cx, |session, _| session.adapter_client().unwrap());
 823
 824    first_child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
 825
 826    // configure second child session
 827    let second_child_session = dap_store.read_with(cx, |dap_store, _| {
 828        dap_store.session_by_id(SessionId(2)).unwrap()
 829    });
 830    let second_child_client =
 831        second_child_session.update(cx, |session, _| session.adapter_client().unwrap());
 832
 833    second_child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
 834
 835    cx.run_until_parked();
 836
 837    // shutdown first child session
 838    dap_store
 839        .update(cx, |dap_store, cx| {
 840            dap_store.shutdown_session(first_child_session.read(cx).session_id(), cx)
 841        })
 842        .await
 843        .unwrap();
 844
 845    // assert parent session and second child session still exist
 846    dap_store.update(cx, |dap_store, cx| {
 847        assert!(
 848            dap_store
 849                .session_by_id(parent_session.read(cx).session_id())
 850                .is_some()
 851        );
 852        assert!(
 853            dap_store
 854                .session_by_id(first_child_session.read(cx).session_id())
 855                .is_none()
 856        );
 857        assert!(
 858            dap_store
 859                .session_by_id(second_child_session.read(cx).session_id())
 860                .is_some()
 861        );
 862    });
 863
 864    // shutdown first child session
 865    dap_store
 866        .update(cx, |dap_store, cx| {
 867            dap_store.shutdown_session(second_child_session.read(cx).session_id(), cx)
 868        })
 869        .await
 870        .unwrap();
 871
 872    // assert parent session got shutdown by second child session
 873    // because it was the last child
 874    dap_store.update(cx, |dap_store, cx| {
 875        assert!(
 876            dap_store
 877                .session_by_id(parent_session.read(cx).session_id())
 878                .is_none()
 879        );
 880        assert!(
 881            dap_store
 882                .session_by_id(second_child_session.read(cx).session_id())
 883                .is_none()
 884        );
 885    });
 886}
 887
 888#[gpui::test]
 889async fn test_debug_panel_item_thread_status_reset_on_failure(
 890    executor: BackgroundExecutor,
 891    cx: &mut TestAppContext,
 892) {
 893    init_test(cx);
 894
 895    let fs = FakeFs::new(executor.clone());
 896
 897    fs.insert_tree(
 898        path!("/project"),
 899        json!({
 900            "main.rs": "First line\nSecond line\nThird line\nFourth line",
 901        }),
 902    )
 903    .await;
 904
 905    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
 906    let workspace = init_test_workspace(&project, cx).await;
 907    let cx = &mut VisualTestContext::from_window(*workspace, cx);
 908
 909    let session = start_debug_session(&workspace, cx, |client| {
 910        client.on_request::<dap::requests::Initialize, _>(move |_, _| {
 911            Ok(dap::Capabilities {
 912                supports_step_back: Some(true),
 913                ..Default::default()
 914            })
 915        });
 916    })
 917    .unwrap();
 918
 919    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 920    const THREAD_ID_NUM: u64 = 1;
 921
 922    client.on_request::<dap::requests::Threads, _>(move |_, _| {
 923        Ok(dap::ThreadsResponse {
 924            threads: vec![dap::Thread {
 925                id: THREAD_ID_NUM,
 926                name: "Thread 1".into(),
 927            }],
 928        })
 929    });
 930
 931    client.on_request::<Launch, _>(move |_, _| Ok(()));
 932
 933    client.on_request::<StackTrace, _>(move |_, _| {
 934        Ok(dap::StackTraceResponse {
 935            stack_frames: Vec::default(),
 936            total_frames: None,
 937        })
 938    });
 939
 940    client.on_request::<Next, _>(move |_, _| {
 941        Err(ErrorResponse {
 942            error: Some(dap::Message {
 943                id: 1,
 944                format: "error".into(),
 945                variables: None,
 946                send_telemetry: None,
 947                show_user: None,
 948                url: None,
 949                url_label: None,
 950            }),
 951        })
 952    });
 953
 954    client.on_request::<StepOut, _>(move |_, _| {
 955        Err(ErrorResponse {
 956            error: Some(dap::Message {
 957                id: 1,
 958                format: "error".into(),
 959                variables: None,
 960                send_telemetry: None,
 961                show_user: None,
 962                url: None,
 963                url_label: None,
 964            }),
 965        })
 966    });
 967
 968    client.on_request::<StepIn, _>(move |_, _| {
 969        Err(ErrorResponse {
 970            error: Some(dap::Message {
 971                id: 1,
 972                format: "error".into(),
 973                variables: None,
 974                send_telemetry: None,
 975                show_user: None,
 976                url: None,
 977                url_label: None,
 978            }),
 979        })
 980    });
 981
 982    client.on_request::<StepBack, _>(move |_, _| {
 983        Err(ErrorResponse {
 984            error: Some(dap::Message {
 985                id: 1,
 986                format: "error".into(),
 987                variables: None,
 988                send_telemetry: None,
 989                show_user: None,
 990                url: None,
 991                url_label: None,
 992            }),
 993        })
 994    });
 995
 996    client.on_request::<Continue, _>(move |_, _| {
 997        Err(ErrorResponse {
 998            error: Some(dap::Message {
 999                id: 1,
1000                format: "error".into(),
1001                variables: None,
1002                send_telemetry: None,
1003                show_user: None,
1004                url: None,
1005                url_label: None,
1006            }),
1007        })
1008    });
1009
1010    client
1011        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
1012            reason: dap::StoppedEventReason::Pause,
1013            description: None,
1014            thread_id: Some(1),
1015            preserve_focus_hint: None,
1016            text: None,
1017            all_threads_stopped: None,
1018            hit_breakpoint_ids: None,
1019        }))
1020        .await;
1021
1022    cx.run_until_parked();
1023
1024    let running_state = active_debug_session_panel(workspace, cx)
1025        .read_with(cx, |item, _| item.running_state().clone());
1026
1027    cx.run_until_parked();
1028    let thread_id = ThreadId(1);
1029
1030    for operation in &[
1031        "step_over",
1032        "continue_thread",
1033        "step_back",
1034        "step_in",
1035        "step_out",
1036    ] {
1037        running_state.update(cx, |running_state, cx| match *operation {
1038            "step_over" => running_state.step_over(cx),
1039            "continue_thread" => running_state.continue_thread(cx),
1040            "step_back" => running_state.step_back(cx),
1041            "step_in" => running_state.step_in(cx),
1042            "step_out" => running_state.step_out(cx),
1043            _ => unreachable!(),
1044        });
1045
1046        // Check that we step the thread status to the correct intermediate state
1047        running_state.update(cx, |running_state, cx| {
1048            assert_eq!(
1049                running_state
1050                    .thread_status(cx)
1051                    .expect("There should be an active thread selected"),
1052                match *operation {
1053                    "continue_thread" => ThreadStatus::Running,
1054                    _ => ThreadStatus::Stepping,
1055                },
1056                "Thread status was not set to correct intermediate state after {} request",
1057                operation
1058            );
1059        });
1060
1061        cx.run_until_parked();
1062
1063        running_state.update(cx, |running_state, cx| {
1064            assert_eq!(
1065                running_state
1066                    .thread_status(cx)
1067                    .expect("There should be an active thread selected"),
1068                ThreadStatus::Stopped,
1069                "Thread status not reset to Stopped after failed {}",
1070                operation
1071            );
1072
1073            // update state to running, so we can test it actually changes the status back to stopped
1074            running_state
1075                .session()
1076                .update(cx, |session, cx| session.continue_thread(thread_id, cx));
1077        });
1078    }
1079}
1080
1081#[gpui::test]
1082async fn test_send_breakpoints_when_editor_has_been_saved(
1083    executor: BackgroundExecutor,
1084    cx: &mut TestAppContext,
1085) {
1086    init_test(cx);
1087
1088    let fs = FakeFs::new(executor.clone());
1089
1090    fs.insert_tree(
1091        path!("/project"),
1092        json!({
1093            "main.rs": "First line\nSecond line\nThird line\nFourth line",
1094        }),
1095    )
1096    .await;
1097
1098    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1099    let workspace = init_test_workspace(&project, cx).await;
1100    let cx = &mut VisualTestContext::from_window(*workspace, cx);
1101    let project_path = Path::new(path!("/project"));
1102    let worktree = project
1103        .update(cx, |project, cx| project.find_worktree(project_path, cx))
1104        .expect("This worktree should exist in project")
1105        .0;
1106
1107    let worktree_id = workspace
1108        .update(cx, |_, _, cx| worktree.read(cx).id())
1109        .unwrap();
1110
1111    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
1112    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
1113
1114    let buffer = project
1115        .update(cx, |project, cx| {
1116            project.open_buffer((worktree_id, "main.rs"), cx)
1117        })
1118        .await
1119        .unwrap();
1120
1121    let (editor, cx) = cx.add_window_view(|window, cx| {
1122        Editor::new(
1123            EditorMode::full(),
1124            MultiBuffer::build_from_buffer(buffer, cx),
1125            Some(project.clone()),
1126            window,
1127            cx,
1128        )
1129    });
1130
1131    client.on_request::<Launch, _>(move |_, _| Ok(()));
1132
1133    client.on_request::<StackTrace, _>(move |_, _| {
1134        Ok(dap::StackTraceResponse {
1135            stack_frames: Vec::default(),
1136            total_frames: None,
1137        })
1138    });
1139
1140    client
1141        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
1142            reason: dap::StoppedEventReason::Pause,
1143            description: None,
1144            thread_id: Some(1),
1145            preserve_focus_hint: None,
1146            text: None,
1147            all_threads_stopped: None,
1148            hit_breakpoint_ids: None,
1149        }))
1150        .await;
1151
1152    let called_set_breakpoints = Arc::new(AtomicBool::new(false));
1153    client.on_request::<SetBreakpoints, _>({
1154        let called_set_breakpoints = called_set_breakpoints.clone();
1155        move |_, args| {
1156            assert_eq!(path!("/project/main.rs"), args.source.path.unwrap());
1157            assert_eq!(
1158                vec![SourceBreakpoint {
1159                    line: 2,
1160                    column: None,
1161                    condition: None,
1162                    hit_condition: None,
1163                    log_message: None,
1164                    mode: None
1165                }],
1166                args.breakpoints.unwrap()
1167            );
1168            assert!(!args.source_modified.unwrap());
1169
1170            called_set_breakpoints.store(true, Ordering::SeqCst);
1171
1172            Ok(dap::SetBreakpointsResponse {
1173                breakpoints: Vec::default(),
1174            })
1175        }
1176    });
1177
1178    editor.update_in(cx, |editor, window, cx| {
1179        editor.move_down(&actions::MoveDown, window, cx);
1180        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
1181    });
1182
1183    cx.run_until_parked();
1184
1185    assert!(
1186        called_set_breakpoints.load(std::sync::atomic::Ordering::SeqCst),
1187        "SetBreakpoint request must be called"
1188    );
1189
1190    let called_set_breakpoints = Arc::new(AtomicBool::new(false));
1191    client.on_request::<SetBreakpoints, _>({
1192        let called_set_breakpoints = called_set_breakpoints.clone();
1193        move |_, args| {
1194            assert_eq!(path!("/project/main.rs"), args.source.path.unwrap());
1195            assert_eq!(
1196                vec![SourceBreakpoint {
1197                    line: 3,
1198                    column: None,
1199                    condition: None,
1200                    hit_condition: None,
1201                    log_message: None,
1202                    mode: None
1203                }],
1204                args.breakpoints.unwrap()
1205            );
1206            assert!(args.source_modified.unwrap());
1207
1208            called_set_breakpoints.store(true, Ordering::SeqCst);
1209
1210            Ok(dap::SetBreakpointsResponse {
1211                breakpoints: Vec::default(),
1212            })
1213        }
1214    });
1215
1216    editor.update_in(cx, |editor, window, cx| {
1217        editor.move_up(&actions::MoveUp, window, cx);
1218        editor.insert("new text\n", window, cx);
1219    });
1220
1221    editor
1222        .update_in(cx, |editor, window, cx| {
1223            editor.save(true, project.clone(), window, cx)
1224        })
1225        .await
1226        .unwrap();
1227
1228    cx.run_until_parked();
1229
1230    assert!(
1231        called_set_breakpoints.load(std::sync::atomic::Ordering::SeqCst),
1232        "SetBreakpoint request must be called after editor is saved"
1233    );
1234}
1235
1236#[gpui::test]
1237async fn test_unsetting_breakpoints_on_clear_breakpoint_action(
1238    executor: BackgroundExecutor,
1239    cx: &mut TestAppContext,
1240) {
1241    init_test(cx);
1242
1243    let fs = FakeFs::new(executor.clone());
1244
1245    fs.insert_tree(
1246        path!("/project"),
1247        json!({
1248            "main.rs": "First line\nSecond line\nThird line\nFourth line",
1249            "second.rs": "First line\nSecond line\nThird line\nFourth line",
1250            "no_breakpoints.rs": "Used to ensure that we don't unset breakpoint in files with no breakpoints"
1251        }),
1252    )
1253    .await;
1254
1255    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1256    let workspace = init_test_workspace(&project, cx).await;
1257    let cx = &mut VisualTestContext::from_window(*workspace, cx);
1258    let project_path = Path::new(path!("/project"));
1259    let worktree = project
1260        .update(cx, |project, cx| project.find_worktree(project_path, cx))
1261        .expect("This worktree should exist in project")
1262        .0;
1263
1264    let worktree_id = workspace
1265        .update(cx, |_, _, cx| worktree.read(cx).id())
1266        .unwrap();
1267
1268    let first = project
1269        .update(cx, |project, cx| {
1270            project.open_buffer((worktree_id, "main.rs"), cx)
1271        })
1272        .await
1273        .unwrap();
1274
1275    let second = project
1276        .update(cx, |project, cx| {
1277            project.open_buffer((worktree_id, "second.rs"), cx)
1278        })
1279        .await
1280        .unwrap();
1281
1282    let (first_editor, cx) = cx.add_window_view(|window, cx| {
1283        Editor::new(
1284            EditorMode::full(),
1285            MultiBuffer::build_from_buffer(first, cx),
1286            Some(project.clone()),
1287            window,
1288            cx,
1289        )
1290    });
1291
1292    let (second_editor, cx) = cx.add_window_view(|window, cx| {
1293        Editor::new(
1294            EditorMode::full(),
1295            MultiBuffer::build_from_buffer(second, cx),
1296            Some(project.clone()),
1297            window,
1298            cx,
1299        )
1300    });
1301
1302    first_editor.update_in(cx, |editor, window, cx| {
1303        editor.move_down(&actions::MoveDown, window, cx);
1304        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
1305        editor.move_down(&actions::MoveDown, window, cx);
1306        editor.move_down(&actions::MoveDown, window, cx);
1307        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
1308    });
1309
1310    second_editor.update_in(cx, |editor, window, cx| {
1311        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
1312        editor.move_down(&actions::MoveDown, window, cx);
1313        editor.move_down(&actions::MoveDown, window, cx);
1314        editor.move_down(&actions::MoveDown, window, cx);
1315        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
1316    });
1317
1318    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
1319    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
1320
1321    let called_set_breakpoints = Arc::new(AtomicBool::new(false));
1322
1323    client.on_request::<SetBreakpoints, _>({
1324        let called_set_breakpoints = called_set_breakpoints.clone();
1325        move |_, args| {
1326            assert!(
1327                args.breakpoints.is_none_or(|bps| bps.is_empty()),
1328                "Send empty breakpoint sets to clear them from DAP servers"
1329            );
1330
1331            match args
1332                .source
1333                .path
1334                .expect("We should always send a breakpoint's path")
1335                .as_str()
1336            {
1337                path!("/project/main.rs") | path!("/project/second.rs") => {}
1338                _ => {
1339                    panic!("Unset breakpoints for path that doesn't have any")
1340                }
1341            }
1342
1343            called_set_breakpoints.store(true, Ordering::SeqCst);
1344
1345            Ok(dap::SetBreakpointsResponse {
1346                breakpoints: Vec::default(),
1347            })
1348        }
1349    });
1350
1351    cx.dispatch_action(crate::ClearAllBreakpoints);
1352    cx.run_until_parked();
1353}
1354
1355#[gpui::test]
1356async fn test_debug_session_is_shutdown_when_attach_and_launch_request_fails(
1357    executor: BackgroundExecutor,
1358    cx: &mut TestAppContext,
1359) {
1360    init_test(cx);
1361
1362    let fs = FakeFs::new(executor.clone());
1363
1364    fs.insert_tree(
1365        path!("/project"),
1366        json!({
1367            "main.rs": "First line\nSecond line\nThird line\nFourth line",
1368        }),
1369    )
1370    .await;
1371
1372    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1373    let workspace = init_test_workspace(&project, cx).await;
1374    let cx = &mut VisualTestContext::from_window(*workspace, cx);
1375
1376    start_debug_session(&workspace, cx, |client| {
1377        client.on_request::<dap::requests::Initialize, _>(|_, _| {
1378            Err(ErrorResponse {
1379                error: Some(Message {
1380                    format: "failed to launch".to_string(),
1381                    id: 1,
1382                    variables: None,
1383                    send_telemetry: None,
1384                    show_user: None,
1385                    url: None,
1386                    url_label: None,
1387                }),
1388            })
1389        });
1390    })
1391    .ok();
1392
1393    cx.run_until_parked();
1394
1395    project.update(cx, |project, cx| {
1396        assert!(
1397            project.dap_store().read(cx).sessions().count() == 0,
1398            "Session wouldn't exist if it was shutdown"
1399        );
1400    });
1401}
1402
1403#[gpui::test]
1404async fn test_we_send_arguments_from_user_config(
1405    executor: BackgroundExecutor,
1406    cx: &mut TestAppContext,
1407) {
1408    init_test(cx);
1409
1410    let fs = FakeFs::new(executor.clone());
1411
1412    fs.insert_tree(
1413        path!("/project"),
1414        json!({
1415            "main.rs": "First line\nSecond line\nThird line\nFourth line",
1416        }),
1417    )
1418    .await;
1419
1420    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1421    let workspace = init_test_workspace(&project, cx).await;
1422    let cx = &mut VisualTestContext::from_window(*workspace, cx);
1423    let debug_definition = DebugTaskDefinition {
1424        adapter: "fake-adapter".into(),
1425        config: json!({
1426            "request": "launch",
1427            "program": "main.rs".to_owned(),
1428            "args": vec!["arg1".to_owned(), "arg2".to_owned()],
1429            "cwd": path!("/Random_path"),
1430            "env": json!({ "KEY": "VALUE" }),
1431        }),
1432        label: "test".into(),
1433        tcp_connection: None,
1434    };
1435
1436    let launch_handler_called = Arc::new(AtomicBool::new(false));
1437
1438    start_debug_session_with(&workspace, cx, debug_definition.clone(), {
1439        let debug_definition = debug_definition.clone();
1440        let launch_handler_called = launch_handler_called.clone();
1441
1442        move |client| {
1443            let debug_definition = debug_definition.clone();
1444            let launch_handler_called = launch_handler_called.clone();
1445
1446            client.on_request::<dap::requests::Launch, _>(move |_, args| {
1447                launch_handler_called.store(true, Ordering::SeqCst);
1448
1449                assert_eq!(args.raw, debug_definition.config);
1450
1451                Ok(())
1452            });
1453        }
1454    })
1455    .ok();
1456
1457    cx.run_until_parked();
1458
1459    assert!(
1460        launch_handler_called.load(Ordering::SeqCst),
1461        "Launch request handler was not called"
1462    );
1463}
1464
1465#[gpui::test]
1466async fn test_active_debug_line_setting(executor: BackgroundExecutor, cx: &mut TestAppContext) {
1467    init_test(cx);
1468
1469    let fs = FakeFs::new(executor.clone());
1470
1471    fs.insert_tree(
1472        path!("/project"),
1473        json!({
1474            "main.rs": "First line\nSecond line\nThird line\nFourth line",
1475            "second.rs": "First line\nSecond line\nThird line\nFourth line",
1476        }),
1477    )
1478    .await;
1479
1480    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1481    let workspace = init_test_workspace(&project, cx).await;
1482    let cx = &mut VisualTestContext::from_window(*workspace, cx);
1483    let project_path = Path::new(path!("/project"));
1484    let worktree = project
1485        .update(cx, |project, cx| project.find_worktree(project_path, cx))
1486        .expect("This worktree should exist in project")
1487        .0;
1488
1489    let worktree_id = workspace
1490        .update(cx, |_, _, cx| worktree.read(cx).id())
1491        .unwrap();
1492
1493    let main_buffer = project
1494        .update(cx, |project, cx| {
1495            project.open_buffer((worktree_id, "main.rs"), cx)
1496        })
1497        .await
1498        .unwrap();
1499
1500    let second_buffer = project
1501        .update(cx, |project, cx| {
1502            project.open_buffer((worktree_id, "second.rs"), cx)
1503        })
1504        .await
1505        .unwrap();
1506
1507    let (main_editor, cx) = cx.add_window_view(|window, cx| {
1508        Editor::new(
1509            EditorMode::full(),
1510            MultiBuffer::build_from_buffer(main_buffer, cx),
1511            Some(project.clone()),
1512            window,
1513            cx,
1514        )
1515    });
1516
1517    let (second_editor, cx) = cx.add_window_view(|window, cx| {
1518        Editor::new(
1519            EditorMode::full(),
1520            MultiBuffer::build_from_buffer(second_buffer, cx),
1521            Some(project.clone()),
1522            window,
1523            cx,
1524        )
1525    });
1526
1527    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
1528    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
1529
1530    client.on_request::<dap::requests::Threads, _>(move |_, _| {
1531        Ok(dap::ThreadsResponse {
1532            threads: vec![dap::Thread {
1533                id: 1,
1534                name: "Thread 1".into(),
1535            }],
1536        })
1537    });
1538
1539    client.on_request::<dap::requests::Scopes, _>(move |_, _| {
1540        Ok(dap::ScopesResponse {
1541            scopes: Vec::default(),
1542        })
1543    });
1544
1545    client.on_request::<StackTrace, _>(move |_, args| {
1546        assert_eq!(args.thread_id, 1);
1547
1548        Ok(dap::StackTraceResponse {
1549            stack_frames: vec![dap::StackFrame {
1550                id: 1,
1551                name: "frame 1".into(),
1552                source: Some(dap::Source {
1553                    name: Some("main.rs".into()),
1554                    path: Some(path!("/project/main.rs").into()),
1555                    source_reference: None,
1556                    presentation_hint: None,
1557                    origin: None,
1558                    sources: None,
1559                    adapter_data: None,
1560                    checksums: None,
1561                }),
1562                line: 2,
1563                column: 0,
1564                end_line: None,
1565                end_column: None,
1566                can_restart: None,
1567                instruction_pointer_reference: None,
1568                module_id: None,
1569                presentation_hint: None,
1570            }],
1571            total_frames: None,
1572        })
1573    });
1574
1575    client
1576        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
1577            reason: dap::StoppedEventReason::Breakpoint,
1578            description: None,
1579            thread_id: Some(1),
1580            preserve_focus_hint: None,
1581            text: None,
1582            all_threads_stopped: None,
1583            hit_breakpoint_ids: None,
1584        }))
1585        .await;
1586
1587    cx.run_until_parked();
1588
1589    main_editor.update_in(cx, |editor, window, cx| {
1590        let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1591
1592        assert_eq!(
1593            active_debug_lines.len(),
1594            1,
1595            "There should be only one active debug line"
1596        );
1597
1598        let point = editor
1599            .snapshot(window, cx)
1600            .buffer_snapshot
1601            .summary_for_anchor::<language::Point>(&active_debug_lines.first().unwrap().0.start);
1602
1603        assert_eq!(point.row, 1);
1604    });
1605
1606    second_editor.update(cx, |editor, _| {
1607        let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1608
1609        assert!(
1610            active_debug_lines.is_empty(),
1611            "There shouldn't be any active debug lines"
1612        );
1613    });
1614
1615    let handled_second_stacktrace = Arc::new(AtomicBool::new(false));
1616    client.on_request::<StackTrace, _>({
1617        let handled_second_stacktrace = handled_second_stacktrace.clone();
1618        move |_, args| {
1619            handled_second_stacktrace.store(true, Ordering::SeqCst);
1620            assert_eq!(args.thread_id, 1);
1621
1622            Ok(dap::StackTraceResponse {
1623                stack_frames: vec![dap::StackFrame {
1624                    id: 2,
1625                    name: "frame 2".into(),
1626                    source: Some(dap::Source {
1627                        name: Some("second.rs".into()),
1628                        path: Some(path!("/project/second.rs").into()),
1629                        source_reference: None,
1630                        presentation_hint: None,
1631                        origin: None,
1632                        sources: None,
1633                        adapter_data: None,
1634                        checksums: None,
1635                    }),
1636                    line: 3,
1637                    column: 0,
1638                    end_line: None,
1639                    end_column: None,
1640                    can_restart: None,
1641                    instruction_pointer_reference: None,
1642                    module_id: None,
1643                    presentation_hint: None,
1644                }],
1645                total_frames: None,
1646            })
1647        }
1648    });
1649
1650    client
1651        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
1652            reason: dap::StoppedEventReason::Breakpoint,
1653            description: None,
1654            thread_id: Some(1),
1655            preserve_focus_hint: None,
1656            text: None,
1657            all_threads_stopped: None,
1658            hit_breakpoint_ids: None,
1659        }))
1660        .await;
1661
1662    cx.run_until_parked();
1663
1664    second_editor.update_in(cx, |editor, window, cx| {
1665        let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1666
1667        assert_eq!(
1668            active_debug_lines.len(),
1669            1,
1670            "There should be only one active debug line"
1671        );
1672
1673        let point = editor
1674            .snapshot(window, cx)
1675            .buffer_snapshot
1676            .summary_for_anchor::<language::Point>(&active_debug_lines.first().unwrap().0.start);
1677
1678        assert_eq!(point.row, 2);
1679    });
1680
1681    main_editor.update(cx, |editor, _| {
1682        let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1683
1684        assert!(
1685            active_debug_lines.is_empty(),
1686            "There shouldn't be any active debug lines"
1687        );
1688    });
1689
1690    assert!(
1691        handled_second_stacktrace.load(Ordering::SeqCst),
1692        "Second stacktrace request handler was not called"
1693    );
1694
1695    client
1696        .fake_event(dap::messages::Events::Continued(dap::ContinuedEvent {
1697            thread_id: 0,
1698            all_threads_continued: Some(true),
1699        }))
1700        .await;
1701
1702    cx.run_until_parked();
1703
1704    second_editor.update(cx, |editor, _| {
1705        let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1706
1707        assert!(
1708            active_debug_lines.is_empty(),
1709            "There shouldn't be any active debug lines"
1710        );
1711    });
1712
1713    main_editor.update(cx, |editor, _| {
1714        let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1715
1716        assert!(
1717            active_debug_lines.is_empty(),
1718            "There shouldn't be any active debug lines"
1719        );
1720    });
1721
1722    // Clean up
1723    let shutdown_session = project.update(cx, |project, cx| {
1724        project.dap_store().update(cx, |dap_store, cx| {
1725            dap_store.shutdown_session(session.read(cx).session_id(), cx)
1726        })
1727    });
1728
1729    shutdown_session.await.unwrap();
1730
1731    main_editor.update(cx, |editor, _| {
1732        let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1733
1734        assert!(
1735            active_debug_lines.is_empty(),
1736            "There shouldn't be any active debug lines after session shutdown"
1737        );
1738    });
1739
1740    second_editor.update(cx, |editor, _| {
1741        let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1742
1743        assert!(
1744            active_debug_lines.is_empty(),
1745            "There shouldn't be any active debug lines after session shutdown"
1746        );
1747    });
1748}