Stop routing session events via the DAP store (#29588)
This cleans up a bunch of indirection and will make it easier to show the session building state in the debugger terminal Closes #ISSUE Release Notes: - N/A
This commit is contained in:
parent
fde1cc78a1
commit
15a83b5a10
6 changed files with 267 additions and 425 deletions
|
@ -7,7 +7,6 @@ use crate::{
|
|||
};
|
||||
use crate::{new_session_modal::NewSessionModal, session::DebugSession};
|
||||
use anyhow::{Result, anyhow};
|
||||
use collections::HashMap;
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
use dap::DebugRequest;
|
||||
use dap::{
|
||||
|
@ -15,7 +14,6 @@ use dap::{
|
|||
client::SessionId, debugger_settings::DebuggerSettings,
|
||||
};
|
||||
use dap::{StartDebuggingRequestArguments, adapters::DebugTaskDefinition};
|
||||
use futures::{SinkExt as _, channel::mpsc};
|
||||
use gpui::{
|
||||
Action, App, AsyncWindowContext, Context, DismissEvent, Entity, EntityId, EventEmitter,
|
||||
FocusHandle, Focusable, MouseButton, MouseDownEvent, Point, Subscription, Task, WeakEntity,
|
||||
|
@ -24,21 +22,11 @@ use gpui::{
|
|||
|
||||
use language::Buffer;
|
||||
use project::debugger::session::{Session, SessionStateEvent};
|
||||
use project::{
|
||||
Project,
|
||||
debugger::{
|
||||
dap_store::{self, DapStore},
|
||||
session::ThreadStatus,
|
||||
},
|
||||
terminals::TerminalKind,
|
||||
};
|
||||
use project::{Project, debugger::session::ThreadStatus};
|
||||
use rpc::proto::{self};
|
||||
use settings::Settings;
|
||||
use std::any::TypeId;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use task::{DebugScenario, HideStrategy, RevealStrategy, RevealTarget, TaskContext, TaskId};
|
||||
use terminal_view::TerminalView;
|
||||
use task::{DebugScenario, TaskContext};
|
||||
use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*};
|
||||
use workspace::SplitDirection;
|
||||
use workspace::{
|
||||
|
@ -74,27 +62,21 @@ pub struct DebugPanel {
|
|||
workspace: WeakEntity<Workspace>,
|
||||
focus_handle: FocusHandle,
|
||||
context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
impl DebugPanel {
|
||||
pub fn new(
|
||||
workspace: &Workspace,
|
||||
window: &mut Window,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Entity<Self> {
|
||||
cx.new(|cx| {
|
||||
let project = workspace.project().clone();
|
||||
let dap_store = project.read(cx).dap_store();
|
||||
|
||||
let _subscriptions =
|
||||
vec![cx.subscribe_in(&dap_store, window, Self::handle_dap_store_event)];
|
||||
|
||||
let debug_panel = Self {
|
||||
size: px(300.),
|
||||
sessions: vec![],
|
||||
active_session: None,
|
||||
_subscriptions,
|
||||
past_debug_definition: None,
|
||||
focus_handle: cx.focus_handle(),
|
||||
project,
|
||||
|
@ -288,7 +270,7 @@ impl DebugPanel {
|
|||
cx.subscribe_in(
|
||||
&session,
|
||||
window,
|
||||
move |_, session, event: &SessionStateEvent, window, cx| match event {
|
||||
move |this, session, event: &SessionStateEvent, window, cx| match event {
|
||||
SessionStateEvent::Restart => {
|
||||
let mut curr_session = session.clone();
|
||||
while let Some(parent_session) = curr_session
|
||||
|
@ -310,6 +292,9 @@ impl DebugPanel {
|
|||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
SessionStateEvent::SpawnChildSession { request } => {
|
||||
this.handle_start_debugging_request(request, session.clone(), window, cx);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
)
|
||||
|
@ -357,7 +342,7 @@ impl DebugPanel {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start_child_session(
|
||||
pub fn handle_start_debugging_request(
|
||||
&mut self,
|
||||
request: &StartDebuggingRequestArguments,
|
||||
parent_session: Entity<Session>,
|
||||
|
@ -419,47 +404,6 @@ impl DebugPanel {
|
|||
self.active_session.clone()
|
||||
}
|
||||
|
||||
fn handle_dap_store_event(
|
||||
&mut self,
|
||||
_dap_store: &Entity<DapStore>,
|
||||
event: &dap_store::DapStoreEvent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match event {
|
||||
dap_store::DapStoreEvent::RunInTerminal {
|
||||
session_id,
|
||||
title,
|
||||
cwd,
|
||||
command,
|
||||
args,
|
||||
envs,
|
||||
sender,
|
||||
..
|
||||
} => {
|
||||
self.handle_run_in_terminal_request(
|
||||
*session_id,
|
||||
title.clone(),
|
||||
cwd.clone(),
|
||||
command.clone(),
|
||||
args.clone(),
|
||||
envs.clone(),
|
||||
sender.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
dap_store::DapStoreEvent::SpawnChildSession {
|
||||
request,
|
||||
parent_session,
|
||||
} => {
|
||||
self.start_child_session(request, parent_session.clone(), window, cx);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_scenario(
|
||||
&self,
|
||||
scenario: DebugScenario,
|
||||
|
@ -529,101 +473,6 @@ impl DebugPanel {
|
|||
})
|
||||
}
|
||||
|
||||
fn handle_run_in_terminal_request(
|
||||
&self,
|
||||
session_id: SessionId,
|
||||
title: Option<String>,
|
||||
cwd: Option<Arc<Path>>,
|
||||
command: Option<String>,
|
||||
args: Vec<String>,
|
||||
envs: HashMap<String, String>,
|
||||
mut sender: mpsc::Sender<Result<u32>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let Some(session) = self
|
||||
.sessions
|
||||
.iter()
|
||||
.find(|s| s.read(cx).session_id(cx) == session_id)
|
||||
else {
|
||||
return Task::ready(Err(anyhow!("no session {:?} found", session_id)));
|
||||
};
|
||||
let running = session.read(cx).running_state();
|
||||
let cwd = cwd.map(|p| p.to_path_buf());
|
||||
let shell = self
|
||||
.project
|
||||
.read(cx)
|
||||
.terminal_settings(&cwd, cx)
|
||||
.shell
|
||||
.clone();
|
||||
let kind = if let Some(command) = command {
|
||||
let title = title.clone().unwrap_or(command.clone());
|
||||
TerminalKind::Task(task::SpawnInTerminal {
|
||||
id: TaskId("debug".to_string()),
|
||||
full_label: title.clone(),
|
||||
label: title.clone(),
|
||||
command: command.clone(),
|
||||
args,
|
||||
command_label: title.clone(),
|
||||
cwd,
|
||||
env: envs,
|
||||
use_new_terminal: true,
|
||||
allow_concurrent_runs: true,
|
||||
reveal: RevealStrategy::NoFocus,
|
||||
reveal_target: RevealTarget::Dock,
|
||||
hide: HideStrategy::Never,
|
||||
shell,
|
||||
show_summary: false,
|
||||
show_command: false,
|
||||
show_rerun: false,
|
||||
})
|
||||
} else {
|
||||
TerminalKind::Shell(cwd.map(|c| c.to_path_buf()))
|
||||
};
|
||||
|
||||
let workspace = self.workspace.clone();
|
||||
let project = self.project.downgrade();
|
||||
|
||||
let terminal_task = self.project.update(cx, |project, cx| {
|
||||
project.create_terminal(kind, window.window_handle(), cx)
|
||||
});
|
||||
let terminal_task = cx.spawn_in(window, async move |_, cx| {
|
||||
let terminal = terminal_task.await?;
|
||||
|
||||
let terminal_view = cx.new_window_entity(|window, cx| {
|
||||
TerminalView::new(terminal.clone(), workspace, None, project, window, cx)
|
||||
})?;
|
||||
|
||||
running.update_in(cx, |running, window, cx| {
|
||||
running.ensure_pane_item(DebuggerPaneItem::Terminal, window, cx);
|
||||
running.debug_terminal.update(cx, |debug_terminal, cx| {
|
||||
debug_terminal.terminal = Some(terminal_view);
|
||||
cx.notify();
|
||||
});
|
||||
})?;
|
||||
|
||||
anyhow::Ok(terminal.read_with(cx, |terminal, _| terminal.pty_info.pid())?)
|
||||
});
|
||||
|
||||
cx.background_spawn(async move {
|
||||
match terminal_task.await {
|
||||
Ok(pid_task) => match pid_task {
|
||||
Some(pid) => sender.send(Ok(pid.as_u32())).await?,
|
||||
None => {
|
||||
sender
|
||||
.send(Err(anyhow!(
|
||||
"Terminal was spawned but PID was not available"
|
||||
)))
|
||||
.await?
|
||||
}
|
||||
},
|
||||
Err(error) => sender.send(Err(anyhow!(error))).await?,
|
||||
};
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn close_session(&mut self, entity_id: EntityId, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let Some(session) = self
|
||||
.sessions
|
||||
|
|
|
@ -104,12 +104,6 @@ impl DebugSession {
|
|||
&self.mode
|
||||
}
|
||||
|
||||
pub(crate) fn running_state(&self) -> Entity<RunningState> {
|
||||
match &self.mode {
|
||||
DebugSessionState::Running(running_state) => running_state.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn label(&self, cx: &App) -> SharedString {
|
||||
if let Some(label) = self.label.get() {
|
||||
return label.clone();
|
||||
|
@ -131,6 +125,13 @@ impl DebugSession {
|
|||
.to_owned()
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn running_state(&self) -> Entity<RunningState> {
|
||||
match &self.mode {
|
||||
DebugSessionState::Running(running_state) => running_state.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn label_element(&self, cx: &App) -> AnyElement {
|
||||
let label = self.label(cx);
|
||||
|
||||
|
|
|
@ -5,15 +5,20 @@ pub(crate) mod module_list;
|
|||
pub mod stack_frame_list;
|
||||
pub mod variable_list;
|
||||
|
||||
use std::{any::Any, ops::ControlFlow, sync::Arc, time::Duration};
|
||||
use std::{any::Any, ops::ControlFlow, path::PathBuf, sync::Arc, time::Duration};
|
||||
|
||||
use crate::persistence::{self, DebuggerPaneItem, SerializedPaneLayout};
|
||||
|
||||
use super::DebugPanelItemEvent;
|
||||
use anyhow::{Result, anyhow};
|
||||
use breakpoint_list::BreakpointList;
|
||||
use collections::{HashMap, IndexMap};
|
||||
use console::Console;
|
||||
use dap::{Capabilities, Thread, client::SessionId, debugger_settings::DebuggerSettings};
|
||||
use dap::{
|
||||
Capabilities, RunInTerminalRequestArguments, Thread, client::SessionId,
|
||||
debugger_settings::DebuggerSettings,
|
||||
};
|
||||
use futures::{SinkExt, channel::mpsc};
|
||||
use gpui::{
|
||||
Action as _, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
|
||||
NoAction, Pixels, Point, Subscription, Task, WeakEntity,
|
||||
|
@ -23,8 +28,10 @@ use module_list::ModuleList;
|
|||
use project::{
|
||||
Project,
|
||||
debugger::session::{Session, SessionEvent, ThreadId, ThreadStatus},
|
||||
terminals::TerminalKind,
|
||||
};
|
||||
use rpc::proto::ViewId;
|
||||
use serde_json::Value;
|
||||
use settings::Settings;
|
||||
use stack_frame_list::StackFrameList;
|
||||
use terminal_view::TerminalView;
|
||||
|
@ -32,7 +39,7 @@ use ui::{
|
|||
ActiveTheme, AnyElement, App, ButtonCommon as _, Clickable as _, Context, ContextMenu,
|
||||
DropdownMenu, FluentBuilder, IconButton, IconName, IconSize, InteractiveElement, IntoElement,
|
||||
Label, LabelCommon as _, ParentElement, Render, SharedString, StatefulInteractiveElement,
|
||||
Styled, Tab, Tooltip, VisibleOnHover, Window, div, h_flex, v_flex,
|
||||
Styled, Tab, Tooltip, VisibleOnHover, VisualContext, Window, div, h_flex, v_flex,
|
||||
};
|
||||
use util::ResultExt;
|
||||
use variable_list::VariableList;
|
||||
|
@ -559,6 +566,9 @@ impl RunningState {
|
|||
this.remove_pane_item(DebuggerPaneItem::LoadedSources, window, cx);
|
||||
}
|
||||
}
|
||||
SessionEvent::RunInTerminal { request, sender } => this
|
||||
.handle_run_in_terminal(request, sender.clone(), window, cx)
|
||||
.detach_and_log_err(cx),
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
@ -657,6 +667,111 @@ impl RunningState {
|
|||
self.panes.pane_at_pixel_position(position).is_some()
|
||||
}
|
||||
|
||||
fn handle_run_in_terminal(
|
||||
&self,
|
||||
request: &RunInTerminalRequestArguments,
|
||||
mut sender: mpsc::Sender<Result<u32>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let running = cx.entity();
|
||||
let Ok(project) = self
|
||||
.workspace
|
||||
.update(cx, |workspace, _| workspace.project().clone())
|
||||
else {
|
||||
return Task::ready(Err(anyhow!("no workspace")));
|
||||
};
|
||||
let session = self.session.read(cx);
|
||||
|
||||
let cwd = Some(&request.cwd)
|
||||
.filter(|cwd| cwd.len() > 0)
|
||||
.map(PathBuf::from)
|
||||
.or_else(|| session.binary().cwd.clone());
|
||||
|
||||
let mut args = request.args.clone();
|
||||
|
||||
// Handle special case for NodeJS debug adapter
|
||||
// If only the Node binary path is provided, we set the command to None
|
||||
// This prevents the NodeJS REPL from appearing, which is not the desired behavior
|
||||
// The expected usage is for users to provide their own Node command, e.g., `node test.js`
|
||||
// This allows the NodeJS debug client to attach correctly
|
||||
let command = if args.len() > 1 {
|
||||
Some(args.remove(0))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut envs: HashMap<String, String> = Default::default();
|
||||
if let Some(Value::Object(env)) = &request.env {
|
||||
for (key, value) in env {
|
||||
let value_str = match (key.as_str(), value) {
|
||||
(_, Value::String(value)) => value,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
envs.insert(key.clone(), value_str.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let shell = project.read(cx).terminal_settings(&cwd, cx).shell.clone();
|
||||
let kind = if let Some(command) = command {
|
||||
let title = request.title.clone().unwrap_or(command.clone());
|
||||
TerminalKind::Task(task::SpawnInTerminal {
|
||||
id: task::TaskId("debug".to_string()),
|
||||
full_label: title.clone(),
|
||||
label: title.clone(),
|
||||
command: command.clone(),
|
||||
args,
|
||||
command_label: title.clone(),
|
||||
cwd,
|
||||
env: envs,
|
||||
use_new_terminal: true,
|
||||
allow_concurrent_runs: true,
|
||||
reveal: task::RevealStrategy::NoFocus,
|
||||
reveal_target: task::RevealTarget::Dock,
|
||||
hide: task::HideStrategy::Never,
|
||||
shell,
|
||||
show_summary: false,
|
||||
show_command: false,
|
||||
show_rerun: false,
|
||||
})
|
||||
} else {
|
||||
TerminalKind::Shell(cwd.map(|c| c.to_path_buf()))
|
||||
};
|
||||
|
||||
let workspace = self.workspace.clone();
|
||||
let weak_project = project.downgrade();
|
||||
|
||||
let terminal_task = project.update(cx, |project, cx| {
|
||||
project.create_terminal(kind, window.window_handle(), cx)
|
||||
});
|
||||
let terminal_task = cx.spawn_in(window, async move |_, cx| {
|
||||
let terminal = terminal_task.await?;
|
||||
|
||||
let terminal_view = cx.new_window_entity(|window, cx| {
|
||||
TerminalView::new(terminal.clone(), workspace, None, weak_project, window, cx)
|
||||
})?;
|
||||
|
||||
running.update_in(cx, |running, window, cx| {
|
||||
running.ensure_pane_item(DebuggerPaneItem::Terminal, window, cx);
|
||||
running.debug_terminal.update(cx, |debug_terminal, cx| {
|
||||
debug_terminal.terminal = Some(terminal_view);
|
||||
cx.notify();
|
||||
});
|
||||
})?;
|
||||
|
||||
terminal.read_with(cx, |terminal, _| {
|
||||
terminal
|
||||
.pty_info
|
||||
.pid()
|
||||
.map(|pid| pid.as_u32())
|
||||
.ok_or_else(|| anyhow!("Terminal was spawned but PID was not available"))
|
||||
})?
|
||||
});
|
||||
|
||||
cx.background_spawn(async move { anyhow::Ok(sender.send(terminal_task.await).await?) })
|
||||
}
|
||||
|
||||
fn create_sub_view(
|
||||
&self,
|
||||
item_kind: DebuggerPaneItem,
|
||||
|
|
|
@ -523,8 +523,8 @@ async fn test_handle_error_run_in_terminal_reverse_request(
|
|||
.fake_reverse_request::<RunInTerminal>(RunInTerminalRequestArguments {
|
||||
kind: None,
|
||||
title: None,
|
||||
cwd: path!("/non-existing/path").into(), // invalid/non-existing path will cause the terminal spawn to fail
|
||||
args: vec![],
|
||||
cwd: "".into(),
|
||||
args: vec!["oops".into(), "oops".into()],
|
||||
env: None,
|
||||
args_can_be_interpreted_by_shell: None,
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue