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().cloned().unwrap();
 473            original_binary.request_args = StartDebuggingRequestArguments {
 474                request: StartDebuggingRequestArgumentsRequest::Launch,
 475                configuration: fake_config.clone(),
 476            };
 477
 478            assert_eq!(
 479                current_sessions[1]
 480                    .read(cx)
 481                    .session(cx)
 482                    .read(cx)
 483                    .binary()
 484                    .unwrap(),
 485                &original_binary
 486            );
 487        })
 488        .unwrap();
 489
 490    assert_eq!(&fake_config, launched_with.lock().as_ref().unwrap());
 491}
 492
 493// // covers that we always send a response back, if something when wrong,
 494// // while spawning the terminal
 495#[gpui::test]
 496async fn test_handle_error_run_in_terminal_reverse_request(
 497    executor: BackgroundExecutor,
 498    cx: &mut TestAppContext,
 499) {
 500    init_test(cx);
 501
 502    let send_response = Arc::new(AtomicBool::new(false));
 503
 504    let fs = FakeFs::new(executor.clone());
 505
 506    fs.insert_tree(
 507        path!("/project"),
 508        json!({
 509            "main.rs": "First line\nSecond line\nThird line\nFourth line",
 510        }),
 511    )
 512    .await;
 513
 514    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
 515    let workspace = init_test_workspace(&project, cx).await;
 516    let cx = &mut VisualTestContext::from_window(*workspace, cx);
 517
 518    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
 519    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 520
 521    client
 522        .on_response::<RunInTerminal, _>({
 523            let send_response = send_response.clone();
 524            move |response| {
 525                send_response.store(true, Ordering::SeqCst);
 526
 527                assert!(!response.success);
 528                assert!(response.body.is_some());
 529            }
 530        })
 531        .await;
 532
 533    client
 534        .fake_reverse_request::<RunInTerminal>(RunInTerminalRequestArguments {
 535            kind: None,
 536            title: None,
 537            cwd: "".into(),
 538            args: vec!["oops".into(), "oops".into()],
 539            env: None,
 540            args_can_be_interpreted_by_shell: None,
 541        })
 542        .await;
 543
 544    cx.run_until_parked();
 545
 546    assert!(
 547        send_response.load(std::sync::atomic::Ordering::SeqCst),
 548        "Expected to receive response from reverse request"
 549    );
 550
 551    workspace
 552        .update(cx, |workspace, _window, cx| {
 553            let terminal_panel = workspace.panel::<TerminalPanel>(cx).unwrap();
 554
 555            assert_eq!(
 556                0,
 557                terminal_panel.read(cx).pane().unwrap().read(cx).items_len()
 558            );
 559        })
 560        .unwrap();
 561}
 562
 563#[gpui::test]
 564async fn test_handle_start_debugging_reverse_request(
 565    executor: BackgroundExecutor,
 566    cx: &mut TestAppContext,
 567) {
 568    init_test(cx);
 569
 570    let send_response = Arc::new(AtomicBool::new(false));
 571
 572    let fs = FakeFs::new(executor.clone());
 573
 574    fs.insert_tree(
 575        path!("/project"),
 576        json!({
 577            "main.rs": "First line\nSecond line\nThird line\nFourth line",
 578        }),
 579    )
 580    .await;
 581
 582    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
 583    let workspace = init_test_workspace(&project, cx).await;
 584    let cx = &mut VisualTestContext::from_window(*workspace, cx);
 585
 586    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
 587    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 588
 589    client.on_request::<dap::requests::Threads, _>(move |_, _| {
 590        Ok(dap::ThreadsResponse {
 591            threads: vec![dap::Thread {
 592                id: 1,
 593                name: "Thread 1".into(),
 594            }],
 595        })
 596    });
 597
 598    client
 599        .on_response::<StartDebugging, _>({
 600            let send_response = send_response.clone();
 601            move |response| {
 602                send_response.store(true, Ordering::SeqCst);
 603
 604                assert!(response.success);
 605                assert!(response.body.is_some());
 606            }
 607        })
 608        .await;
 609    // Set up handlers for sessions spawned with reverse request too.
 610    let _reverse_request_subscription =
 611        project::debugger::test::intercept_debug_sessions(cx, |_| {});
 612    client
 613        .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
 614            configuration: json!({}),
 615            request: StartDebuggingRequestArgumentsRequest::Launch,
 616        })
 617        .await;
 618
 619    cx.run_until_parked();
 620
 621    let child_session = project.update(cx, |project, cx| {
 622        project
 623            .dap_store()
 624            .read(cx)
 625            .session_by_id(SessionId(1))
 626            .unwrap()
 627    });
 628    let child_client = child_session.update(cx, |session, _| session.adapter_client().unwrap());
 629
 630    child_client.on_request::<dap::requests::Threads, _>(move |_, _| {
 631        Ok(dap::ThreadsResponse {
 632            threads: vec![dap::Thread {
 633                id: 1,
 634                name: "Thread 1".into(),
 635            }],
 636        })
 637    });
 638
 639    child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
 640
 641    child_client
 642        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
 643            reason: dap::StoppedEventReason::Pause,
 644            description: None,
 645            thread_id: Some(2),
 646            preserve_focus_hint: None,
 647            text: None,
 648            all_threads_stopped: None,
 649            hit_breakpoint_ids: None,
 650        }))
 651        .await;
 652
 653    cx.run_until_parked();
 654
 655    assert!(
 656        send_response.load(std::sync::atomic::Ordering::SeqCst),
 657        "Expected to receive response from reverse request"
 658    );
 659}
 660
 661#[gpui::test]
 662async fn test_shutdown_children_when_parent_session_shutdown(
 663    executor: BackgroundExecutor,
 664    cx: &mut TestAppContext,
 665) {
 666    init_test(cx);
 667
 668    let fs = FakeFs::new(executor.clone());
 669
 670    fs.insert_tree(
 671        path!("/project"),
 672        json!({
 673            "main.rs": "First line\nSecond line\nThird line\nFourth line",
 674        }),
 675    )
 676    .await;
 677
 678    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
 679    let dap_store = project.update(cx, |project, _| project.dap_store());
 680    let workspace = init_test_workspace(&project, cx).await;
 681    let cx = &mut VisualTestContext::from_window(*workspace, cx);
 682
 683    let parent_session = start_debug_session(&workspace, cx, |_| {}).unwrap();
 684    let client = parent_session.update(cx, |session, _| session.adapter_client().unwrap());
 685
 686    client.on_request::<dap::requests::Threads, _>(move |_, _| {
 687        Ok(dap::ThreadsResponse {
 688            threads: vec![dap::Thread {
 689                id: 1,
 690                name: "Thread 1".into(),
 691            }],
 692        })
 693    });
 694
 695    client.on_response::<StartDebugging, _>(move |_| {}).await;
 696    // Set up handlers for sessions spawned with reverse request too.
 697    let _reverse_request_subscription =
 698        project::debugger::test::intercept_debug_sessions(cx, |_| {});
 699    // start first child session
 700    client
 701        .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
 702            configuration: json!({}),
 703            request: StartDebuggingRequestArgumentsRequest::Launch,
 704        })
 705        .await;
 706
 707    cx.run_until_parked();
 708
 709    // start second child session
 710    client
 711        .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
 712            configuration: json!({}),
 713            request: StartDebuggingRequestArgumentsRequest::Launch,
 714        })
 715        .await;
 716
 717    cx.run_until_parked();
 718
 719    // configure first child session
 720    let first_child_session = dap_store.read_with(cx, |dap_store, _| {
 721        dap_store.session_by_id(SessionId(1)).unwrap()
 722    });
 723    let first_child_client =
 724        first_child_session.update(cx, |session, _| session.adapter_client().unwrap());
 725
 726    first_child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
 727
 728    // configure second child session
 729    let second_child_session = dap_store.read_with(cx, |dap_store, _| {
 730        dap_store.session_by_id(SessionId(2)).unwrap()
 731    });
 732    let second_child_client =
 733        second_child_session.update(cx, |session, _| session.adapter_client().unwrap());
 734
 735    second_child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
 736
 737    cx.run_until_parked();
 738
 739    // shutdown parent session
 740    dap_store
 741        .update(cx, |dap_store, cx| {
 742            dap_store.shutdown_session(parent_session.read(cx).session_id(), cx)
 743        })
 744        .await
 745        .unwrap();
 746
 747    // assert parent session and all children sessions are shutdown
 748    dap_store.update(cx, |dap_store, cx| {
 749        assert!(
 750            dap_store
 751                .session_by_id(parent_session.read(cx).session_id())
 752                .is_none()
 753        );
 754        assert!(
 755            dap_store
 756                .session_by_id(first_child_session.read(cx).session_id())
 757                .is_none()
 758        );
 759        assert!(
 760            dap_store
 761                .session_by_id(second_child_session.read(cx).session_id())
 762                .is_none()
 763        );
 764    });
 765}
 766
 767#[gpui::test]
 768async fn test_shutdown_parent_session_if_all_children_are_shutdown(
 769    executor: BackgroundExecutor,
 770    cx: &mut TestAppContext,
 771) {
 772    init_test(cx);
 773
 774    let fs = FakeFs::new(executor.clone());
 775
 776    fs.insert_tree(
 777        path!("/project"),
 778        json!({
 779            "main.rs": "First line\nSecond line\nThird line\nFourth line",
 780        }),
 781    )
 782    .await;
 783
 784    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
 785    let dap_store = project.update(cx, |project, _| project.dap_store());
 786    let workspace = init_test_workspace(&project, cx).await;
 787    let cx = &mut VisualTestContext::from_window(*workspace, cx);
 788
 789    let parent_session = start_debug_session(&workspace, cx, |_| {}).unwrap();
 790    let client = parent_session.update(cx, |session, _| session.adapter_client().unwrap());
 791
 792    client.on_response::<StartDebugging, _>(move |_| {}).await;
 793    // Set up handlers for sessions spawned with reverse request too.
 794    let _reverse_request_subscription =
 795        project::debugger::test::intercept_debug_sessions(cx, |_| {});
 796    // start first child session
 797    client
 798        .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
 799            configuration: json!({}),
 800            request: StartDebuggingRequestArgumentsRequest::Launch,
 801        })
 802        .await;
 803
 804    cx.run_until_parked();
 805
 806    // start second child session
 807    client
 808        .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
 809            configuration: json!({}),
 810            request: StartDebuggingRequestArgumentsRequest::Launch,
 811        })
 812        .await;
 813
 814    cx.run_until_parked();
 815
 816    // configure first child session
 817    let first_child_session = dap_store.read_with(cx, |dap_store, _| {
 818        dap_store.session_by_id(SessionId(1)).unwrap()
 819    });
 820    let first_child_client =
 821        first_child_session.update(cx, |session, _| session.adapter_client().unwrap());
 822
 823    first_child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
 824
 825    // configure second child session
 826    let second_child_session = dap_store.read_with(cx, |dap_store, _| {
 827        dap_store.session_by_id(SessionId(2)).unwrap()
 828    });
 829    let second_child_client =
 830        second_child_session.update(cx, |session, _| session.adapter_client().unwrap());
 831
 832    second_child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
 833
 834    cx.run_until_parked();
 835
 836    // shutdown first child session
 837    dap_store
 838        .update(cx, |dap_store, cx| {
 839            dap_store.shutdown_session(first_child_session.read(cx).session_id(), cx)
 840        })
 841        .await
 842        .unwrap();
 843
 844    // assert parent session and second child session still exist
 845    dap_store.update(cx, |dap_store, cx| {
 846        assert!(
 847            dap_store
 848                .session_by_id(parent_session.read(cx).session_id())
 849                .is_some()
 850        );
 851        assert!(
 852            dap_store
 853                .session_by_id(first_child_session.read(cx).session_id())
 854                .is_none()
 855        );
 856        assert!(
 857            dap_store
 858                .session_by_id(second_child_session.read(cx).session_id())
 859                .is_some()
 860        );
 861    });
 862
 863    // shutdown first child session
 864    dap_store
 865        .update(cx, |dap_store, cx| {
 866            dap_store.shutdown_session(second_child_session.read(cx).session_id(), cx)
 867        })
 868        .await
 869        .unwrap();
 870
 871    // assert parent session got shutdown by second child session
 872    // because it was the last child
 873    dap_store.update(cx, |dap_store, cx| {
 874        assert!(
 875            dap_store
 876                .session_by_id(parent_session.read(cx).session_id())
 877                .is_none()
 878        );
 879        assert!(
 880            dap_store
 881                .session_by_id(second_child_session.read(cx).session_id())
 882                .is_none()
 883        );
 884    });
 885}
 886
 887#[gpui::test]
 888async fn test_debug_panel_item_thread_status_reset_on_failure(
 889    executor: BackgroundExecutor,
 890    cx: &mut TestAppContext,
 891) {
 892    init_test(cx);
 893
 894    let fs = FakeFs::new(executor.clone());
 895
 896    fs.insert_tree(
 897        path!("/project"),
 898        json!({
 899            "main.rs": "First line\nSecond line\nThird line\nFourth line",
 900        }),
 901    )
 902    .await;
 903
 904    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
 905    let workspace = init_test_workspace(&project, cx).await;
 906    let cx = &mut VisualTestContext::from_window(*workspace, cx);
 907
 908    let session = start_debug_session(&workspace, cx, |client| {
 909        client.on_request::<dap::requests::Initialize, _>(move |_, _| {
 910            Ok(dap::Capabilities {
 911                supports_step_back: Some(true),
 912                ..Default::default()
 913            })
 914        });
 915    })
 916    .unwrap();
 917
 918    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 919    const THREAD_ID_NUM: u64 = 1;
 920
 921    client.on_request::<dap::requests::Threads, _>(move |_, _| {
 922        Ok(dap::ThreadsResponse {
 923            threads: vec![dap::Thread {
 924                id: THREAD_ID_NUM,
 925                name: "Thread 1".into(),
 926            }],
 927        })
 928    });
 929
 930    client.on_request::<Launch, _>(move |_, _| Ok(()));
 931
 932    client.on_request::<StackTrace, _>(move |_, _| {
 933        Ok(dap::StackTraceResponse {
 934            stack_frames: Vec::default(),
 935            total_frames: None,
 936        })
 937    });
 938
 939    client.on_request::<Next, _>(move |_, _| {
 940        Err(ErrorResponse {
 941            error: Some(dap::Message {
 942                id: 1,
 943                format: "error".into(),
 944                variables: None,
 945                send_telemetry: None,
 946                show_user: None,
 947                url: None,
 948                url_label: None,
 949            }),
 950        })
 951    });
 952
 953    client.on_request::<StepOut, _>(move |_, _| {
 954        Err(ErrorResponse {
 955            error: Some(dap::Message {
 956                id: 1,
 957                format: "error".into(),
 958                variables: None,
 959                send_telemetry: None,
 960                show_user: None,
 961                url: None,
 962                url_label: None,
 963            }),
 964        })
 965    });
 966
 967    client.on_request::<StepIn, _>(move |_, _| {
 968        Err(ErrorResponse {
 969            error: Some(dap::Message {
 970                id: 1,
 971                format: "error".into(),
 972                variables: None,
 973                send_telemetry: None,
 974                show_user: None,
 975                url: None,
 976                url_label: None,
 977            }),
 978        })
 979    });
 980
 981    client.on_request::<StepBack, _>(move |_, _| {
 982        Err(ErrorResponse {
 983            error: Some(dap::Message {
 984                id: 1,
 985                format: "error".into(),
 986                variables: None,
 987                send_telemetry: None,
 988                show_user: None,
 989                url: None,
 990                url_label: None,
 991            }),
 992        })
 993    });
 994
 995    client.on_request::<Continue, _>(move |_, _| {
 996        Err(ErrorResponse {
 997            error: Some(dap::Message {
 998                id: 1,
 999                format: "error".into(),
1000                variables: None,
1001                send_telemetry: None,
1002                show_user: None,
1003                url: None,
1004                url_label: None,
1005            }),
1006        })
1007    });
1008
1009    client
1010        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
1011            reason: dap::StoppedEventReason::Pause,
1012            description: None,
1013            thread_id: Some(1),
1014            preserve_focus_hint: None,
1015            text: None,
1016            all_threads_stopped: None,
1017            hit_breakpoint_ids: None,
1018        }))
1019        .await;
1020
1021    cx.run_until_parked();
1022
1023    let running_state = active_debug_session_panel(workspace, cx)
1024        .read_with(cx, |item, _| item.running_state().clone());
1025
1026    cx.run_until_parked();
1027    let thread_id = ThreadId(1);
1028
1029    for operation in &[
1030        "step_over",
1031        "continue_thread",
1032        "step_back",
1033        "step_in",
1034        "step_out",
1035    ] {
1036        running_state.update(cx, |running_state, cx| match *operation {
1037            "step_over" => running_state.step_over(cx),
1038            "continue_thread" => running_state.continue_thread(cx),
1039            "step_back" => running_state.step_back(cx),
1040            "step_in" => running_state.step_in(cx),
1041            "step_out" => running_state.step_out(cx),
1042            _ => unreachable!(),
1043        });
1044
1045        // Check that we step the thread status to the correct intermediate state
1046        running_state.update(cx, |running_state, cx| {
1047            assert_eq!(
1048                running_state
1049                    .thread_status(cx)
1050                    .expect("There should be an active thread selected"),
1051                match *operation {
1052                    "continue_thread" => ThreadStatus::Running,
1053                    _ => ThreadStatus::Stepping,
1054                },
1055                "Thread status was not set to correct intermediate state after {} request",
1056                operation
1057            );
1058        });
1059
1060        cx.run_until_parked();
1061
1062        running_state.update(cx, |running_state, cx| {
1063            assert_eq!(
1064                running_state
1065                    .thread_status(cx)
1066                    .expect("There should be an active thread selected"),
1067                ThreadStatus::Stopped,
1068                "Thread status not reset to Stopped after failed {}",
1069                operation
1070            );
1071
1072            // update state to running, so we can test it actually changes the status back to stopped
1073            running_state
1074                .session()
1075                .update(cx, |session, cx| session.continue_thread(thread_id, cx));
1076        });
1077    }
1078}
1079
1080#[gpui::test]
1081async fn test_send_breakpoints_when_editor_has_been_saved(
1082    executor: BackgroundExecutor,
1083    cx: &mut TestAppContext,
1084) {
1085    init_test(cx);
1086
1087    let fs = FakeFs::new(executor.clone());
1088
1089    fs.insert_tree(
1090        path!("/project"),
1091        json!({
1092            "main.rs": "First line\nSecond line\nThird line\nFourth line",
1093        }),
1094    )
1095    .await;
1096
1097    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1098    let workspace = init_test_workspace(&project, cx).await;
1099    let cx = &mut VisualTestContext::from_window(*workspace, cx);
1100    let project_path = Path::new(path!("/project"));
1101    let worktree = project
1102        .update(cx, |project, cx| project.find_worktree(project_path, cx))
1103        .expect("This worktree should exist in project")
1104        .0;
1105
1106    let worktree_id = workspace
1107        .update(cx, |_, _, cx| worktree.read(cx).id())
1108        .unwrap();
1109
1110    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
1111    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
1112
1113    let buffer = project
1114        .update(cx, |project, cx| {
1115            project.open_buffer((worktree_id, "main.rs"), cx)
1116        })
1117        .await
1118        .unwrap();
1119
1120    let (editor, cx) = cx.add_window_view(|window, cx| {
1121        Editor::new(
1122            EditorMode::full(),
1123            MultiBuffer::build_from_buffer(buffer, cx),
1124            Some(project.clone()),
1125            window,
1126            cx,
1127        )
1128    });
1129
1130    client.on_request::<Launch, _>(move |_, _| Ok(()));
1131
1132    client.on_request::<StackTrace, _>(move |_, _| {
1133        Ok(dap::StackTraceResponse {
1134            stack_frames: Vec::default(),
1135            total_frames: None,
1136        })
1137    });
1138
1139    client
1140        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
1141            reason: dap::StoppedEventReason::Pause,
1142            description: None,
1143            thread_id: Some(1),
1144            preserve_focus_hint: None,
1145            text: None,
1146            all_threads_stopped: None,
1147            hit_breakpoint_ids: None,
1148        }))
1149        .await;
1150
1151    let called_set_breakpoints = Arc::new(AtomicBool::new(false));
1152    client.on_request::<SetBreakpoints, _>({
1153        let called_set_breakpoints = called_set_breakpoints.clone();
1154        move |_, args| {
1155            assert_eq!(path!("/project/main.rs"), args.source.path.unwrap());
1156            assert_eq!(
1157                vec![SourceBreakpoint {
1158                    line: 2,
1159                    column: None,
1160                    condition: None,
1161                    hit_condition: None,
1162                    log_message: None,
1163                    mode: None
1164                }],
1165                args.breakpoints.unwrap()
1166            );
1167            assert!(!args.source_modified.unwrap());
1168
1169            called_set_breakpoints.store(true, Ordering::SeqCst);
1170
1171            Ok(dap::SetBreakpointsResponse {
1172                breakpoints: Vec::default(),
1173            })
1174        }
1175    });
1176
1177    editor.update_in(cx, |editor, window, cx| {
1178        editor.move_down(&actions::MoveDown, window, cx);
1179        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
1180    });
1181
1182    cx.run_until_parked();
1183
1184    assert!(
1185        called_set_breakpoints.load(std::sync::atomic::Ordering::SeqCst),
1186        "SetBreakpoint request must be called"
1187    );
1188
1189    let called_set_breakpoints = Arc::new(AtomicBool::new(false));
1190    client.on_request::<SetBreakpoints, _>({
1191        let called_set_breakpoints = called_set_breakpoints.clone();
1192        move |_, args| {
1193            assert_eq!(path!("/project/main.rs"), args.source.path.unwrap());
1194            assert_eq!(
1195                vec![SourceBreakpoint {
1196                    line: 3,
1197                    column: None,
1198                    condition: None,
1199                    hit_condition: None,
1200                    log_message: None,
1201                    mode: None
1202                }],
1203                args.breakpoints.unwrap()
1204            );
1205            assert!(args.source_modified.unwrap());
1206
1207            called_set_breakpoints.store(true, Ordering::SeqCst);
1208
1209            Ok(dap::SetBreakpointsResponse {
1210                breakpoints: Vec::default(),
1211            })
1212        }
1213    });
1214
1215    editor.update_in(cx, |editor, window, cx| {
1216        editor.move_up(&actions::MoveUp, window, cx);
1217        editor.insert("new text\n", window, cx);
1218    });
1219
1220    editor
1221        .update_in(cx, |editor, window, cx| {
1222            editor.save(
1223                SaveOptions {
1224                    format: true,
1225                    autosave: false,
1226                },
1227                project.clone(),
1228                window,
1229                cx,
1230            )
1231        })
1232        .await
1233        .unwrap();
1234
1235    cx.run_until_parked();
1236
1237    assert!(
1238        called_set_breakpoints.load(std::sync::atomic::Ordering::SeqCst),
1239        "SetBreakpoint request must be called after editor is saved"
1240    );
1241}
1242
1243#[gpui::test]
1244async fn test_unsetting_breakpoints_on_clear_breakpoint_action(
1245    executor: BackgroundExecutor,
1246    cx: &mut TestAppContext,
1247) {
1248    init_test(cx);
1249
1250    let fs = FakeFs::new(executor.clone());
1251
1252    fs.insert_tree(
1253        path!("/project"),
1254        json!({
1255            "main.rs": "First line\nSecond line\nThird line\nFourth line",
1256            "second.rs": "First line\nSecond line\nThird line\nFourth line",
1257            "no_breakpoints.rs": "Used to ensure that we don't unset breakpoint in files with no breakpoints"
1258        }),
1259    )
1260    .await;
1261
1262    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1263    let workspace = init_test_workspace(&project, cx).await;
1264    let cx = &mut VisualTestContext::from_window(*workspace, cx);
1265    let project_path = Path::new(path!("/project"));
1266    let worktree = project
1267        .update(cx, |project, cx| project.find_worktree(project_path, cx))
1268        .expect("This worktree should exist in project")
1269        .0;
1270
1271    let worktree_id = workspace
1272        .update(cx, |_, _, cx| worktree.read(cx).id())
1273        .unwrap();
1274
1275    let first = project
1276        .update(cx, |project, cx| {
1277            project.open_buffer((worktree_id, "main.rs"), cx)
1278        })
1279        .await
1280        .unwrap();
1281
1282    let second = project
1283        .update(cx, |project, cx| {
1284            project.open_buffer((worktree_id, "second.rs"), cx)
1285        })
1286        .await
1287        .unwrap();
1288
1289    let (first_editor, cx) = cx.add_window_view(|window, cx| {
1290        Editor::new(
1291            EditorMode::full(),
1292            MultiBuffer::build_from_buffer(first, cx),
1293            Some(project.clone()),
1294            window,
1295            cx,
1296        )
1297    });
1298
1299    let (second_editor, cx) = cx.add_window_view(|window, cx| {
1300        Editor::new(
1301            EditorMode::full(),
1302            MultiBuffer::build_from_buffer(second, cx),
1303            Some(project.clone()),
1304            window,
1305            cx,
1306        )
1307    });
1308
1309    first_editor.update_in(cx, |editor, window, cx| {
1310        editor.move_down(&actions::MoveDown, window, cx);
1311        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
1312        editor.move_down(&actions::MoveDown, window, cx);
1313        editor.move_down(&actions::MoveDown, window, cx);
1314        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
1315    });
1316
1317    second_editor.update_in(cx, |editor, window, cx| {
1318        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
1319        editor.move_down(&actions::MoveDown, window, cx);
1320        editor.move_down(&actions::MoveDown, window, cx);
1321        editor.move_down(&actions::MoveDown, window, cx);
1322        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
1323    });
1324
1325    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
1326    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
1327
1328    let called_set_breakpoints = Arc::new(AtomicBool::new(false));
1329
1330    client.on_request::<SetBreakpoints, _>({
1331        let called_set_breakpoints = called_set_breakpoints.clone();
1332        move |_, args| {
1333            assert!(
1334                args.breakpoints.is_none_or(|bps| bps.is_empty()),
1335                "Send empty breakpoint sets to clear them from DAP servers"
1336            );
1337
1338            match args
1339                .source
1340                .path
1341                .expect("We should always send a breakpoint's path")
1342                .as_str()
1343            {
1344                path!("/project/main.rs") | path!("/project/second.rs") => {}
1345                _ => {
1346                    panic!("Unset breakpoints for path that doesn't have any")
1347                }
1348            }
1349
1350            called_set_breakpoints.store(true, Ordering::SeqCst);
1351
1352            Ok(dap::SetBreakpointsResponse {
1353                breakpoints: Vec::default(),
1354            })
1355        }
1356    });
1357
1358    cx.dispatch_action(crate::ClearAllBreakpoints);
1359    cx.run_until_parked();
1360}
1361
1362#[gpui::test]
1363async fn test_debug_session_is_shutdown_when_attach_and_launch_request_fails(
1364    executor: BackgroundExecutor,
1365    cx: &mut TestAppContext,
1366) {
1367    init_test(cx);
1368
1369    let fs = FakeFs::new(executor.clone());
1370
1371    fs.insert_tree(
1372        path!("/project"),
1373        json!({
1374            "main.rs": "First line\nSecond line\nThird line\nFourth line",
1375        }),
1376    )
1377    .await;
1378
1379    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1380    let workspace = init_test_workspace(&project, cx).await;
1381    let cx = &mut VisualTestContext::from_window(*workspace, cx);
1382
1383    start_debug_session(&workspace, cx, |client| {
1384        client.on_request::<dap::requests::Initialize, _>(|_, _| {
1385            Err(ErrorResponse {
1386                error: Some(Message {
1387                    format: "failed to launch".to_string(),
1388                    id: 1,
1389                    variables: None,
1390                    send_telemetry: None,
1391                    show_user: None,
1392                    url: None,
1393                    url_label: None,
1394                }),
1395            })
1396        });
1397    })
1398    .ok();
1399
1400    cx.run_until_parked();
1401
1402    project.update(cx, |project, cx| {
1403        assert!(
1404            project.dap_store().read(cx).sessions().count() == 0,
1405            "Session wouldn't exist if it was shutdown"
1406        );
1407    });
1408}
1409
1410#[gpui::test]
1411async fn test_we_send_arguments_from_user_config(
1412    executor: BackgroundExecutor,
1413    cx: &mut TestAppContext,
1414) {
1415    init_test(cx);
1416
1417    let fs = FakeFs::new(executor.clone());
1418
1419    fs.insert_tree(
1420        path!("/project"),
1421        json!({
1422            "main.rs": "First line\nSecond line\nThird line\nFourth line",
1423        }),
1424    )
1425    .await;
1426
1427    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1428    let workspace = init_test_workspace(&project, cx).await;
1429    let cx = &mut VisualTestContext::from_window(*workspace, cx);
1430    let debug_definition = DebugTaskDefinition {
1431        adapter: "fake-adapter".into(),
1432        config: json!({
1433            "request": "launch",
1434            "program": "main.rs".to_owned(),
1435            "args": vec!["arg1".to_owned(), "arg2".to_owned()],
1436            "cwd": path!("/Random_path"),
1437            "env": json!({ "KEY": "VALUE" }),
1438        }),
1439        label: "test".into(),
1440        tcp_connection: None,
1441    };
1442
1443    let launch_handler_called = Arc::new(AtomicBool::new(false));
1444
1445    start_debug_session_with(&workspace, cx, debug_definition.clone(), {
1446        let debug_definition = debug_definition.clone();
1447        let launch_handler_called = launch_handler_called.clone();
1448
1449        move |client| {
1450            let debug_definition = debug_definition.clone();
1451            let launch_handler_called = launch_handler_called.clone();
1452
1453            client.on_request::<dap::requests::Launch, _>(move |_, args| {
1454                launch_handler_called.store(true, Ordering::SeqCst);
1455
1456                assert_eq!(args.raw, debug_definition.config);
1457
1458                Ok(())
1459            });
1460        }
1461    })
1462    .ok();
1463
1464    cx.run_until_parked();
1465
1466    assert!(
1467        launch_handler_called.load(Ordering::SeqCst),
1468        "Launch request handler was not called"
1469    );
1470}
1471
1472#[gpui::test]
1473async fn test_active_debug_line_setting(executor: BackgroundExecutor, cx: &mut TestAppContext) {
1474    init_test(cx);
1475
1476    let fs = FakeFs::new(executor.clone());
1477
1478    fs.insert_tree(
1479        path!("/project"),
1480        json!({
1481            "main.rs": "First line\nSecond line\nThird line\nFourth line",
1482            "second.rs": "First line\nSecond line\nThird line\nFourth line",
1483        }),
1484    )
1485    .await;
1486
1487    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1488    let workspace = init_test_workspace(&project, cx).await;
1489    let cx = &mut VisualTestContext::from_window(*workspace, cx);
1490    let project_path = Path::new(path!("/project"));
1491    let worktree = project
1492        .update(cx, |project, cx| project.find_worktree(project_path, cx))
1493        .expect("This worktree should exist in project")
1494        .0;
1495
1496    let worktree_id = workspace
1497        .update(cx, |_, _, cx| worktree.read(cx).id())
1498        .unwrap();
1499
1500    let main_buffer = project
1501        .update(cx, |project, cx| {
1502            project.open_buffer((worktree_id, "main.rs"), cx)
1503        })
1504        .await
1505        .unwrap();
1506
1507    let second_buffer = project
1508        .update(cx, |project, cx| {
1509            project.open_buffer((worktree_id, "second.rs"), cx)
1510        })
1511        .await
1512        .unwrap();
1513
1514    let (main_editor, cx) = cx.add_window_view(|window, cx| {
1515        Editor::new(
1516            EditorMode::full(),
1517            MultiBuffer::build_from_buffer(main_buffer, cx),
1518            Some(project.clone()),
1519            window,
1520            cx,
1521        )
1522    });
1523
1524    let (second_editor, cx) = cx.add_window_view(|window, cx| {
1525        Editor::new(
1526            EditorMode::full(),
1527            MultiBuffer::build_from_buffer(second_buffer, cx),
1528            Some(project.clone()),
1529            window,
1530            cx,
1531        )
1532    });
1533
1534    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
1535    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
1536
1537    client.on_request::<dap::requests::Threads, _>(move |_, _| {
1538        Ok(dap::ThreadsResponse {
1539            threads: vec![dap::Thread {
1540                id: 1,
1541                name: "Thread 1".into(),
1542            }],
1543        })
1544    });
1545
1546    client.on_request::<dap::requests::Scopes, _>(move |_, _| {
1547        Ok(dap::ScopesResponse {
1548            scopes: Vec::default(),
1549        })
1550    });
1551
1552    client.on_request::<StackTrace, _>(move |_, args| {
1553        assert_eq!(args.thread_id, 1);
1554
1555        Ok(dap::StackTraceResponse {
1556            stack_frames: vec![dap::StackFrame {
1557                id: 1,
1558                name: "frame 1".into(),
1559                source: Some(dap::Source {
1560                    name: Some("main.rs".into()),
1561                    path: Some(path!("/project/main.rs").into()),
1562                    source_reference: None,
1563                    presentation_hint: None,
1564                    origin: None,
1565                    sources: None,
1566                    adapter_data: None,
1567                    checksums: None,
1568                }),
1569                line: 2,
1570                column: 0,
1571                end_line: None,
1572                end_column: None,
1573                can_restart: None,
1574                instruction_pointer_reference: None,
1575                module_id: None,
1576                presentation_hint: None,
1577            }],
1578            total_frames: None,
1579        })
1580    });
1581
1582    client
1583        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
1584            reason: dap::StoppedEventReason::Breakpoint,
1585            description: None,
1586            thread_id: Some(1),
1587            preserve_focus_hint: None,
1588            text: None,
1589            all_threads_stopped: None,
1590            hit_breakpoint_ids: None,
1591        }))
1592        .await;
1593
1594    cx.run_until_parked();
1595
1596    main_editor.update_in(cx, |editor, window, cx| {
1597        let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1598
1599        assert_eq!(
1600            active_debug_lines.len(),
1601            1,
1602            "There should be only one active debug line"
1603        );
1604
1605        let point = editor
1606            .snapshot(window, cx)
1607            .buffer_snapshot
1608            .summary_for_anchor::<language::Point>(&active_debug_lines.first().unwrap().0.start);
1609
1610        assert_eq!(point.row, 1);
1611    });
1612
1613    second_editor.update(cx, |editor, _| {
1614        let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1615
1616        assert!(
1617            active_debug_lines.is_empty(),
1618            "There shouldn't be any active debug lines"
1619        );
1620    });
1621
1622    let handled_second_stacktrace = Arc::new(AtomicBool::new(false));
1623    client.on_request::<StackTrace, _>({
1624        let handled_second_stacktrace = handled_second_stacktrace.clone();
1625        move |_, args| {
1626            handled_second_stacktrace.store(true, Ordering::SeqCst);
1627            assert_eq!(args.thread_id, 1);
1628
1629            Ok(dap::StackTraceResponse {
1630                stack_frames: vec![dap::StackFrame {
1631                    id: 2,
1632                    name: "frame 2".into(),
1633                    source: Some(dap::Source {
1634                        name: Some("second.rs".into()),
1635                        path: Some(path!("/project/second.rs").into()),
1636                        source_reference: None,
1637                        presentation_hint: None,
1638                        origin: None,
1639                        sources: None,
1640                        adapter_data: None,
1641                        checksums: None,
1642                    }),
1643                    line: 3,
1644                    column: 0,
1645                    end_line: None,
1646                    end_column: None,
1647                    can_restart: None,
1648                    instruction_pointer_reference: None,
1649                    module_id: None,
1650                    presentation_hint: None,
1651                }],
1652                total_frames: None,
1653            })
1654        }
1655    });
1656
1657    client
1658        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
1659            reason: dap::StoppedEventReason::Breakpoint,
1660            description: None,
1661            thread_id: Some(1),
1662            preserve_focus_hint: None,
1663            text: None,
1664            all_threads_stopped: None,
1665            hit_breakpoint_ids: None,
1666        }))
1667        .await;
1668
1669    cx.run_until_parked();
1670
1671    second_editor.update_in(cx, |editor, window, cx| {
1672        let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1673
1674        assert_eq!(
1675            active_debug_lines.len(),
1676            1,
1677            "There should be only one active debug line"
1678        );
1679
1680        let point = editor
1681            .snapshot(window, cx)
1682            .buffer_snapshot
1683            .summary_for_anchor::<language::Point>(&active_debug_lines.first().unwrap().0.start);
1684
1685        assert_eq!(point.row, 2);
1686    });
1687
1688    main_editor.update(cx, |editor, _| {
1689        let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1690
1691        assert!(
1692            active_debug_lines.is_empty(),
1693            "There shouldn't be any active debug lines"
1694        );
1695    });
1696
1697    assert!(
1698        handled_second_stacktrace.load(Ordering::SeqCst),
1699        "Second stacktrace request handler was not called"
1700    );
1701
1702    client
1703        .fake_event(dap::messages::Events::Continued(dap::ContinuedEvent {
1704            thread_id: 0,
1705            all_threads_continued: Some(true),
1706        }))
1707        .await;
1708
1709    cx.run_until_parked();
1710
1711    second_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    main_editor.update(cx, |editor, _| {
1721        let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1722
1723        assert!(
1724            active_debug_lines.is_empty(),
1725            "There shouldn't be any active debug lines"
1726        );
1727    });
1728
1729    // Clean up
1730    let shutdown_session = project.update(cx, |project, cx| {
1731        project.dap_store().update(cx, |dap_store, cx| {
1732            dap_store.shutdown_session(session.read(cx).session_id(), cx)
1733        })
1734    });
1735
1736    shutdown_session.await.unwrap();
1737
1738    main_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
1747    second_editor.update(cx, |editor, _| {
1748        let active_debug_lines: Vec<_> = editor.highlighted_rows::<ActiveDebugLine>().collect();
1749
1750        assert!(
1751            active_debug_lines.is_empty(),
1752            "There shouldn't be any active debug lines after session shutdown"
1753        );
1754    });
1755}