use std::any::TypeId; use debugger_panel::DebugPanel;use editor::{Editor, MultiBufferOffsetUtf16};use gpui::{Action, App, DispatchPhase, EntityInputHandler, actions};use new_process_modal::{NewProcessModal, NewProcessMode};use project::debugger::{self, breakpoint_store::SourceBreakpoint, session::ThreadStatus};use schemars::JsonSchema;use serde::Deserialize;use session::DebugSession; use tasks_ui::{Spawn, TaskOverrides};use ui::{FluentBuilder, InteractiveElement};use util::maybe;use workspace::{ShutdownDebugAdapters, Workspace};use zed_actions::debug_panel::{Toggle, ToggleFocus}; pub mod attach_modal;pub mod debugger_panel;mod dropdown_menus;mod new_process_modal;mod persistence;pub(crate) mod session; #[cfg(any(test, feature = "test-support"))]pub mod tests; // Let's see the diff-test in action.actions!( debugger, [ /// Starts a new debugging session. Start, /// Continues execution until the next breakpoint. Continue, /// Detaches the debugger from the running process. Detach, /// Pauses the currently running program. Pause, /// Restarts the current debugging session. Restart, /// Reruns the current debugging session with the same configuration. RerunSession, /// Steps into the next function call. StepInto, /// Steps over the current line. StepOver, /// Steps out of the current function. StepOut, /// Steps back to the previous statement. StepBack, /// Stops the debugging session. Stop, /// Toggles whether to ignore all breakpoints. ToggleIgnoreBreakpoints, /// Clears all breakpoints in the project. ClearAllBreakpoints, /// Focuses on the debugger console panel. FocusConsole, /// Focuses on the variables panel. FocusVariables, /// Focuses on the breakpoint list panel. FocusBreakpointList, /// Focuses on the call stack frames panel. FocusFrames, /// Focuses on the loaded modules panel. FocusModules, /// Focuses on the loaded sources panel. FocusLoadedSources, /// Focuses on the terminal panel. FocusTerminal, /// Toggles the thread picker dropdown. ToggleThreadPicker, /// Toggles the session picker dropdown. ToggleSessionPicker, /// Reruns the last debugging session. #[action(deprecated_aliases = ["debugger::RerunLastSession"])] Rerun, /// Toggles expansion of the selected item in the debugger UI. ToggleExpandItem, /// Toggle the user frame filter in the stack frame list /// When toggled on, only frames from the user's code are shown /// When toggled off, all frames are shown ToggleUserFrames, ]); /// Set a data breakpoint on the selected variable or memory region.#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)]#[action(namespace = debugger)]#[serde(deny_unknown_fields)]pub struct ToggleDataBreakpoint { /// The type of data breakpoint /// Read & Write /// Read /// Write #[serde(default)] pub access_type: Option<dap::DataBreakpointAccessType>,} actions!( dev, [ /// Copies debug adapter launch arguments to clipboard. CopyDebugAdapterArguments ]); pub fn init(cx: &mut App) { workspace::FollowableViewRegistry::register::<DebugSession>(cx); cx.observe_new(|workspace: &mut Workspace, _, _| { workspace .register_action(spawn_task_or_modal) .register_action(|workspace, _: &ToggleFocus, window, cx| { workspace.toggle_panel_focus::<DebugPanel>(window, cx); }) .register_action(|workspace, _: &Toggle, window, cx| { if !workspace.toggle_panel_focus::<DebugPanel>(window, cx) { workspace.close_panel::<DebugPanel>(window, cx); } }) .register_action(|workspace: &mut Workspace, _: &Start, window, cx| { NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx); }) .register_action(|workspace: &mut Workspace, _: &Rerun, window, cx| { let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else { return; }; debug_panel.update(cx, |debug_panel, cx| { debug_panel.rerun_last_session(workspace, window, cx); }) }) .register_action( |workspace: &mut Workspace, _: &ShutdownDebugAdapters, _window, cx| { workspace.project().update(cx, |project, cx| { project.dap_store().update(cx, |store, cx| { store.shutdown_sessions(cx).detach(); }) }) }, ) .register_action_renderer(|div, workspace, _, cx| { let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else { return div; }; let Some(active_item) = debug_panel .read(cx) .active_session() .map(|session| session.read(cx).running_state().clone()) else { return div; }; let running_state = active_item.read(cx); if running_state.session().read(cx).is_terminated() { return div; } let caps = running_state.capabilities(cx); let supports_step_back = caps.supports_step_back.unwrap_or_default(); let supports_detach = running_state.session().read(cx).is_attached(); let status = running_state.thread_status(cx); let active_item = active_item.downgrade(); div.when(status == Some(ThreadStatus::Running), |div| { let active_item = active_item.clone(); div.on_action(move |_: &Pause, _, cx| { active_item .update(cx, |item, cx| item.pause_thread(cx)) .ok(); }) }) .when(status == Some(ThreadStatus::Stopped), |div| { div.on_action({ let active_item = active_item.clone(); move |_: &StepInto, _, cx| { active_item.update(cx, |item, cx| item.step_in(cx)).ok(); } }) .on_action({ let active_item = active_item.clone(); move |_: &StepOver, _, cx| { active_item.update(cx, |item, cx| item.step_over(cx)).ok(); } }) .on_action({ let active_item = active_item.clone(); move |_: &StepOut, _, cx| { active_item.update(cx, |item, cx| item.step_out(cx)).ok(); } }) .when(supports_step_back, |div| { let active_item = active_item.clone(); div.on_action(move |_: &StepBack, _, cx| { active_item.update(cx, |item, cx| item.step_back(cx)).ok(); }) }) .on_action({ let active_item = active_item.clone(); move |_: &Continue, _, cx| { active_item .update(cx, |item, cx| item.continue_thread(cx)) .ok(); } }) }) .when(supports_detach, |div| { let active_item = active_item.clone(); div.on_action(move |_: &Detach, _, cx| { active_item .update(cx, |item, cx| item.detach_client(cx)) .ok(); }) }) .on_action({ let active_item = active_item.clone(); move |_: &Restart, _, cx| { active_item .update(cx, |item, cx| item.restart_session(cx)) .ok(); } }) .on_action({ let active_item = active_item.clone(); move |_: &RerunSession, window, cx| { active_item .update(cx, |item, cx| item.rerun_session(window, cx)) .ok(); } }) .on_action({ let active_item = active_item.clone(); move |_: &Stop, _, cx| { active_item.update(cx, |item, cx| item.stop_thread(cx)).ok(); } }) .on_action({ let active_item = active_item.clone(); move |_: &ToggleIgnoreBreakpoints, _, cx| { active_item .update(cx, |item, cx| item.toggle_ignore_breakpoints(cx)) .ok(); } }) .on_action(move |_: &ToggleUserFrames, _, cx| { if let Some((thread_status, stack_frame_list)) = active_item .read_with(cx, |item, cx| { (item.thread_status(cx), item.stack_frame_list().clone()) }) .ok() { stack_frame_list.update(cx, |stack_frame_list, cx| { stack_frame_list.toggle_frame_filter(thread_status, cx); }) } }) }); }) .detach(); cx.observe_new({ move |editor: &mut Editor, _, _| { editor .register_action_renderer(move |editor, window, cx| { let Some(workspace) = editor.workspace() else { return; }; let Some(debug_panel) = workspace.read(cx).panel::<DebugPanel>(cx) else { return; }; let Some(active_session) = debug_panel.update(cx, |panel, _| panel.active_session()) else { return; }; let session = active_session .read(cx) .running_state .read(cx) .session() .read(cx); if session.is_terminated() { return; } let editor = cx.entity().downgrade(); window.on_action_when( session.any_stopped_thread(), TypeId::of::<editor::actions::RunToCursor>(), { let editor = editor.clone(); let active_session = active_session.clone(); move |_, phase, _, cx| { if phase != DispatchPhase::Bubble { return; } maybe!({ let (buffer, position, _) = editor .update(cx, |editor, cx| { let cursor_point: language::Point = editor .selections .newest(&editor.display_snapshot(cx)) .head(); editor .buffer() .read(cx) .point_to_buffer_point(cursor_point, cx) }) .ok()??; let path = debugger::breakpoint_store::BreakpointStore::abs_path_from_buffer( &buffer, cx, )?; let source_breakpoint = SourceBreakpoint { row: position.row, path, message: None, condition: None, hit_condition: None, state: debugger::breakpoint_store::BreakpointState::Enabled, }; active_session.update(cx, |session, cx| { session.running_state().update(cx, |state, cx| { if let Some(thread_id) = state.selected_thread_id() { state.session().update(cx, |session, cx| { session.run_to_position( source_breakpoint, thread_id, cx, ); }) } }); }); Some(()) }); } }, ); window.on_action( TypeId::of::<editor::actions::EvaluateSelectedText>(), move |_, _, window, cx| { let status = maybe!({ let text = editor .update(cx, |editor, cx| { let range = editor .selections .newest::<MultiBufferOffsetUtf16>( &editor.display_snapshot(cx), ) .range(); editor.text_for_range( range.start.0.0..range.end.0.0, &mut None, window, cx, ) }) .ok()??; active_session.update(cx, |session, cx| { session.running_state().update(cx, |state, cx| { let stack_id = state.selected_stack_frame_id(cx); state.session().update(cx, |session, cx| { session .evaluate( text, Some(dap::EvaluateArgumentsContext::Repl), stack_id, None, cx, ) .detach(); }); }); }); Some(()) }); if status.is_some() { cx.stop_propagation(); } }, ); }) .detach(); } }) .detach();} fn spawn_task_or_modal( workspace: &mut Workspace, action: &Spawn, window: &mut ui::Window, cx: &mut ui::Context<Workspace>,) { match action { Spawn::ByName { task_name, reveal_target, } => { let overrides = reveal_target.map(|reveal_target| TaskOverrides { reveal_target: Some(reveal_target), }); let name = task_name.clone(); tasks_ui::spawn_tasks_filtered( move |(_, task)| task.label.eq(&name), overrides, window, cx, ) .detach_and_log_err(cx) } Spawn::ByTag { task_tag, reveal_target, } => { let overrides = reveal_target.map(|reveal_target| TaskOverrides { reveal_target: Some(reveal_target), }); let tag = task_tag.clone(); tasks_ui::spawn_tasks_filtered( move |(_, task)| task.tags.contains(&tag), overrides, window, cx, ) .detach_and_log_err(cx) } Spawn::ViaModal { reveal_target } => { NewProcessModal::show(workspace, window, NewProcessMode::Task, *reveal_target, cx); } }}
use std::any::TypeId;
use debugger_panel::DebugPanel;
use editor::{Editor, MultiBufferOffsetUtf16};
use gpui::{Action, App, DispatchPhase, EntityInputHandler, actions};
use new_process_modal::{NewProcessModal, NewProcessMode};
use project::debugger::{self, breakpoint_store::SourceBreakpoint, session::ThreadStatus};
use schemars::JsonSchema;
use serde::Deserialize;
use session::DebugSession;
use tasks_ui::{Spawn, TaskOverrides};
use ui::{FluentBuilder, InteractiveElement};
use util::maybe;
use workspace::{ShutdownDebugAdapters, Workspace};
use zed_actions::debug_panel::{Toggle, ToggleFocus};
pub mod attach_modal;
pub mod debugger_panel;
mod dropdown_menus;
mod new_process_modal;
mod persistence;
pub(crate) mod session;
#[cfg(any(test, feature = "test-support"))]
pub mod tests;
// Let's see the diff-test in action.
actions!(
debugger,
[
/// Starts a new debugging session.
Start,
/// Continues execution until the next breakpoint.
Continue,
/// Detaches the debugger from the running process.
Detach,
/// Pauses the currently running program.
Pause,
/// Restarts the current debugging session.
Restart,
/// Reruns the current debugging session with the same configuration.
RerunSession,
/// Steps into the next function call.
StepInto,
/// Steps over the current line.
StepOver,
/// Steps out of the current function.
StepOut,
/// Steps back to the previous statement.
StepBack,
/// Stops the debugging session.
Stop,
/// Toggles whether to ignore all breakpoints.
ToggleIgnoreBreakpoints,
/// Clears all breakpoints in the project.
ClearAllBreakpoints,
/// Focuses on the debugger console panel.
FocusConsole,
/// Focuses on the variables panel.
FocusVariables,
/// Focuses on the breakpoint list panel.
FocusBreakpointList,
/// Focuses on the call stack frames panel.
FocusFrames,
/// Focuses on the loaded modules panel.
FocusModules,
/// Focuses on the loaded sources panel.
FocusLoadedSources,
/// Focuses on the terminal panel.
FocusTerminal,
/// Toggles the thread picker dropdown.
ToggleThreadPicker,
/// Toggles the session picker dropdown.
ToggleSessionPicker,
/// Reruns the last debugging session.
#[action(deprecated_aliases = ["debugger::RerunLastSession"])]
Rerun,
/// Toggles expansion of the selected item in the debugger UI.
ToggleExpandItem,
/// Toggle the user frame filter in the stack frame list
/// When toggled on, only frames from the user's code are shown
/// When toggled off, all frames are shown
ToggleUserFrames,
]
);
/// Set a data breakpoint on the selected variable or memory region.
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)]
#[action(namespace = debugger)]
#[serde(deny_unknown_fields)]
pub struct ToggleDataBreakpoint {
/// The type of data breakpoint
/// Read & Write
/// Read
/// Write
#[serde(default)]
pub access_type: Option<dap::DataBreakpointAccessType>,
}
actions!(
dev,
[
/// Copies debug adapter launch arguments to clipboard.
CopyDebugAdapterArguments
]
);
pub fn init(cx: &mut App) {
workspace::FollowableViewRegistry::register::<DebugSession>(cx);
cx.observe_new(|workspace: &mut Workspace, _, _| {
workspace
.register_action(spawn_task_or_modal)
.register_action(|workspace, _: &ToggleFocus, window, cx| {
workspace.toggle_panel_focus::<DebugPanel>(window, cx);
})
.register_action(|workspace, _: &Toggle, window, cx| {
if !workspace.toggle_panel_focus::<DebugPanel>(window, cx) {
workspace.close_panel::<DebugPanel>(window, cx);
}
})
.register_action(|workspace: &mut Workspace, _: &Start, window, cx| {
NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx);
})
.register_action(|workspace: &mut Workspace, _: &Rerun, window, cx| {
let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
return;
};
debug_panel.update(cx, |debug_panel, cx| {
debug_panel.rerun_last_session(workspace, window, cx);
})
})
.register_action(
|workspace: &mut Workspace, _: &ShutdownDebugAdapters, _window, cx| {
workspace.project().update(cx, |project, cx| {
project.dap_store().update(cx, |store, cx| {
store.shutdown_sessions(cx).detach();
})
})
},
)
.register_action_renderer(|div, workspace, _, cx| {
let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
return div;
};
let Some(active_item) = debug_panel
.read(cx)
.active_session()
.map(|session| session.read(cx).running_state().clone())
else {
return div;
};
let running_state = active_item.read(cx);
if running_state.session().read(cx).is_terminated() {
return div;
}
let caps = running_state.capabilities(cx);
let supports_step_back = caps.supports_step_back.unwrap_or_default();
let supports_detach = running_state.session().read(cx).is_attached();
let status = running_state.thread_status(cx);
let active_item = active_item.downgrade();
div.when(status == Some(ThreadStatus::Running), |div| {
let active_item = active_item.clone();
div.on_action(move |_: &Pause, _, cx| {
active_item
.update(cx, |item, cx| item.pause_thread(cx))
.ok();
})
})
.when(status == Some(ThreadStatus::Stopped), |div| {
div.on_action({
let active_item = active_item.clone();
move |_: &StepInto, _, cx| {
active_item.update(cx, |item, cx| item.step_in(cx)).ok();
}
})
.on_action({
let active_item = active_item.clone();
move |_: &StepOver, _, cx| {
active_item.update(cx, |item, cx| item.step_over(cx)).ok();
}
})
.on_action({
let active_item = active_item.clone();
move |_: &StepOut, _, cx| {
active_item.update(cx, |item, cx| item.step_out(cx)).ok();
}
})
.when(supports_step_back, |div| {
let active_item = active_item.clone();
div.on_action(move |_: &StepBack, _, cx| {
active_item.update(cx, |item, cx| item.step_back(cx)).ok();
})
})
.on_action({
let active_item = active_item.clone();
move |_: &Continue, _, cx| {
active_item
.update(cx, |item, cx| item.continue_thread(cx))
.ok();
}
})
})
.when(supports_detach, |div| {
let active_item = active_item.clone();
div.on_action(move |_: &Detach, _, cx| {
active_item
.update(cx, |item, cx| item.detach_client(cx))
.ok();
})
})
.on_action({
let active_item = active_item.clone();
move |_: &Restart, _, cx| {
active_item
.update(cx, |item, cx| item.restart_session(cx))
.ok();
}
})
.on_action({
let active_item = active_item.clone();
move |_: &RerunSession, window, cx| {
active_item
.update(cx, |item, cx| item.rerun_session(window, cx))
.ok();
}
})
.on_action({
let active_item = active_item.clone();
move |_: &Stop, _, cx| {
active_item.update(cx, |item, cx| item.stop_thread(cx)).ok();
}
})
.on_action({
let active_item = active_item.clone();
move |_: &ToggleIgnoreBreakpoints, _, cx| {
active_item
.update(cx, |item, cx| item.toggle_ignore_breakpoints(cx))
.ok();
}
})
.on_action(move |_: &ToggleUserFrames, _, cx| {
if let Some((thread_status, stack_frame_list)) = active_item
.read_with(cx, |item, cx| {
(item.thread_status(cx), item.stack_frame_list().clone())
})
.ok()
{
stack_frame_list.update(cx, |stack_frame_list, cx| {
stack_frame_list.toggle_frame_filter(thread_status, cx);
})
}
})
});
})
.detach();
cx.observe_new({
move |editor: &mut Editor, _, _| {
editor
.register_action_renderer(move |editor, window, cx| {
let Some(workspace) = editor.workspace() else {
return;
};
let Some(debug_panel) = workspace.read(cx).panel::<DebugPanel>(cx) else {
return;
};
let Some(active_session) =
debug_panel.update(cx, |panel, _| panel.active_session())
else {
return;
};
let session = active_session
.read(cx)
.running_state
.read(cx)
.session()
.read(cx);
if session.is_terminated() {
return;
}
let editor = cx.entity().downgrade();
window.on_action_when(
session.any_stopped_thread(),
TypeId::of::<editor::actions::RunToCursor>(),
{
let editor = editor.clone();
let active_session = active_session.clone();
move |_, phase, _, cx| {
if phase != DispatchPhase::Bubble {
return;
}
maybe!({
let (buffer, position, _) = editor
.update(cx, |editor, cx| {
let cursor_point: language::Point = editor
.selections
.newest(&editor.display_snapshot(cx))
.head();
editor
.buffer()
.read(cx)
.point_to_buffer_point(cursor_point, cx)
})
.ok()??;
let path =
debugger::breakpoint_store::BreakpointStore::abs_path_from_buffer(
&buffer, cx,
)?;
let source_breakpoint = SourceBreakpoint {
row: position.row,
path,
message: None,
condition: None,
hit_condition: None,
state: debugger::breakpoint_store::BreakpointState::Enabled,
};
active_session.update(cx, |session, cx| {
session.running_state().update(cx, |state, cx| {
if let Some(thread_id) = state.selected_thread_id() {
state.session().update(cx, |session, cx| {
session.run_to_position(
source_breakpoint,
thread_id,
cx,
);
})
}
});
});
Some(())
});
}
},
);
window.on_action(
TypeId::of::<editor::actions::EvaluateSelectedText>(),
move |_, _, window, cx| {
let status = maybe!({
let text = editor
.update(cx, |editor, cx| {
let range = editor
.selections
.newest::<MultiBufferOffsetUtf16>(
&editor.display_snapshot(cx),
)
.range();
editor.text_for_range(
range.start.0.0..range.end.0.0,
&mut None,
window,
cx,
)
})
.ok()??;
active_session.update(cx, |session, cx| {
session.running_state().update(cx, |state, cx| {
let stack_id = state.selected_stack_frame_id(cx);
state.session().update(cx, |session, cx| {
session
.evaluate(
text,
Some(dap::EvaluateArgumentsContext::Repl),
stack_id,
None,
cx,
)
.detach();
});
});
});
Some(())
});
if status.is_some() {
cx.stop_propagation();
}
},
);
})
.detach();
}
})
.detach();
}
fn spawn_task_or_modal(
workspace: &mut Workspace,
action: &Spawn,
window: &mut ui::Window,
cx: &mut ui::Context<Workspace>,
) {
match action {
Spawn::ByName {
task_name,
reveal_target,
} => {
let overrides = reveal_target.map(|reveal_target| TaskOverrides {
reveal_target: Some(reveal_target),
});
let name = task_name.clone();
tasks_ui::spawn_tasks_filtered(
move |(_, task)| task.label.eq(&name),
overrides,
window,
cx,
)
.detach_and_log_err(cx)
}
Spawn::ByTag {
task_tag,
reveal_target,
} => {
let overrides = reveal_target.map(|reveal_target| TaskOverrides {
reveal_target: Some(reveal_target),
});
let tag = task_tag.clone();
tasks_ui::spawn_tasks_filtered(
move |(_, task)| task.tags.contains(&tag),
overrides,
window,
cx,
)
.detach_and_log_err(cx)
}
Spawn::ViaModal { reveal_target } => {
NewProcessModal::show(workspace, window, NewProcessMode::Task, *reveal_target, cx);
}
}
}