debugger_panel.rs

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