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