debugger_panel.rs

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