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