debugger: Start on tabless design (#27837)
 Release Notes: - N/A --------- Co-authored-by: Anthony Eid <hello@anthonyeid.me> Co-authored-by: Anthony <anthony@zed.dev>
This commit is contained in:
parent
9986a21970
commit
ece4a1cd7c
33 changed files with 1287 additions and 1092 deletions
|
@ -1,14 +1,14 @@
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"label": "Debug Zed with LLDB",
|
"label": "Debug Zed with LLDB",
|
||||||
"adapter": "lldb",
|
"adapter": "LLDB",
|
||||||
"program": "$ZED_WORKTREE_ROOT/target/debug/zed",
|
"program": "$ZED_WORKTREE_ROOT/target/debug/zed",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"cwd": "$ZED_WORKTREE_ROOT"
|
"cwd": "$ZED_WORKTREE_ROOT"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Debug Zed with GDB",
|
"label": "Debug Zed with GDB",
|
||||||
"adapter": "gdb",
|
"adapter": "GDB",
|
||||||
"program": "$ZED_WORKTREE_ROOT/target/debug/zed",
|
"program": "$ZED_WORKTREE_ROOT/target/debug/zed",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"cwd": "$ZED_WORKTREE_ROOT",
|
"cwd": "$ZED_WORKTREE_ROOT",
|
||||||
|
|
1
assets/icons/arrow_down_right.svg
Normal file
1
assets/icons/arrow_down_right.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-down-right-icon lucide-arrow-down-right"><path d="m7 7 10 10"/><path d="M17 7v10H7"/></svg>
|
After Width: | Height: | Size: 300 B |
|
@ -1,3 +1 @@
|
||||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-up-right-icon lucide-arrow-up-right"><path d="M7 7h10v10"/><path d="M7 17 17 7"/></svg>
|
||||||
<path d="M6 2H6.5C6.5 1.86739 6.44732 1.74021 6.35355 1.64645C6.25979 1.55268 6.13261 1.5 6 1.5V2ZM2 1.5C1.72386 1.5 1.5 1.72386 1.5 2C1.5 2.27614 1.72386 2.5 2 2.5L2 1.5ZM5.5 6C5.5 6.27614 5.72386 6.5 6 6.5C6.27614 6.5 6.5 6.27614 6.5 6H5.5ZM1.64645 5.64645C1.45118 5.84171 1.45118 6.15829 1.64645 6.35355C1.84171 6.54882 2.15829 6.54882 2.35355 6.35355L1.64645 5.64645ZM6 1.5H2L2 2.5H6V1.5ZM5.5 2V6H6.5V2H5.5ZM5.64645 1.64645L1.64645 5.64645L2.35355 6.35355L6.35355 2.35355L5.64645 1.64645Z" fill="white"/>
|
|
||||||
</svg>
|
|
||||||
|
|
Before Width: | Height: | Size: 608 B After Width: | Height: | Size: 296 B |
1
assets/icons/bug_off.svg
Normal file
1
assets/icons/bug_off.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bug-off-icon lucide-bug-off"><path d="M15 7.13V6a3 3 0 0 0-5.14-2.1L8 2"/><path d="M14.12 3.88 16 2"/><path d="M22 13h-4v-2a4 4 0 0 0-4-4h-1.3"/><path d="M20.97 5c0 2.1-1.6 3.8-3.5 4"/><path d="m2 2 20 20"/><path d="M7.7 7.7A4 4 0 0 0 6 11v3a6 6 0 0 0 11.13 3.13"/><path d="M12 20v-8"/><path d="M6 13H2"/><path d="M3 21c0-2.1 1.7-3.9 3.8-4"/></svg>
|
After Width: | Height: | Size: 551 B |
1
assets/icons/circle_off.svg
Normal file
1
assets/icons/circle_off.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-off-icon lucide-circle-off"><path d="m2 2 20 20"/><path d="M8.35 2.69A10 10 0 0 1 21.3 15.65"/><path d="M19.08 19.08A10 10 0 1 1 4.92 4.92"/></svg>
|
After Width: | Height: | Size: 357 B |
1
assets/icons/power.svg
Normal file
1
assets/icons/power.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-power-icon lucide-power"><path d="M12 2v10"/><path d="M18.4 6.6a9 9 0 1 1-12.77.04"/></svg>
|
After Width: | Height: | Size: 294 B |
|
@ -78,10 +78,10 @@ impl DebugAdapter for LldbDebugAdapter {
|
||||||
match &config.request {
|
match &config.request {
|
||||||
DebugRequestType::Attach(attach) => {
|
DebugRequestType::Attach(attach) => {
|
||||||
map.insert("pid".into(), attach.process_id.into());
|
map.insert("pid".into(), attach.process_id.into());
|
||||||
map.insert("stopOnEntry".into(), config.stop_on_entry.into());
|
|
||||||
}
|
}
|
||||||
DebugRequestType::Launch(launch) => {
|
DebugRequestType::Launch(launch) => {
|
||||||
map.insert("program".into(), launch.program.clone().into());
|
map.insert("program".into(), launch.program.clone().into());
|
||||||
|
map.insert("stopOnEntry".into(), config.stop_on_entry.into());
|
||||||
map.insert("args".into(), launch.args.clone().into());
|
map.insert("args".into(), launch.args.clone().into());
|
||||||
map.insert(
|
map.insert(
|
||||||
"cwd".into(),
|
"cwd".into(),
|
||||||
|
|
|
@ -126,24 +126,31 @@ impl DebugAdapter for PythonDebugAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn request_args(&self, config: &DebugTaskDefinition) -> Value {
|
fn request_args(&self, config: &DebugTaskDefinition) -> Value {
|
||||||
|
let mut args = json!({
|
||||||
|
"request": match config.request {
|
||||||
|
DebugRequestType::Launch(_) => "launch",
|
||||||
|
DebugRequestType::Attach(_) => "attach",
|
||||||
|
},
|
||||||
|
"subProcess": true,
|
||||||
|
"redirectOutput": true,
|
||||||
|
});
|
||||||
|
let map = args.as_object_mut().unwrap();
|
||||||
match &config.request {
|
match &config.request {
|
||||||
DebugRequestType::Launch(launch_config) => {
|
DebugRequestType::Attach(attach) => {
|
||||||
json!({
|
map.insert("processId".into(), attach.process_id.into());
|
||||||
"program": launch_config.program,
|
|
||||||
"args": launch_config.args,
|
|
||||||
"subProcess": true,
|
|
||||||
"cwd": launch_config.cwd,
|
|
||||||
"redirectOutput": true,
|
|
||||||
"StopOnEntry": config.stop_on_entry,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
dap::DebugRequestType::Attach(attach_config) => {
|
DebugRequestType::Launch(launch) => {
|
||||||
json!({
|
map.insert("program".into(), launch.program.clone().into());
|
||||||
"subProcess": true,
|
map.insert("args".into(), launch.args.clone().into());
|
||||||
"redirectOutput": true,
|
|
||||||
"processId": attach_config.process_id
|
if let Some(stop_on_entry) = config.stop_on_entry {
|
||||||
})
|
map.insert("stopOnEntry".into(), stop_on_entry.into());
|
||||||
|
}
|
||||||
|
if let Some(cwd) = launch.cwd.as_ref() {
|
||||||
|
map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
args
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,7 @@ impl AttachModal {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
project: Entity<project::Project>,
|
project: Entity<project::Project>,
|
||||||
debug_config: task::DebugTaskDefinition,
|
debug_config: task::DebugTaskDefinition,
|
||||||
|
modal: bool,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -74,13 +75,14 @@ impl AttachModal {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
processes.sort_by_key(|k| k.name.clone());
|
processes.sort_by_key(|k| k.name.clone());
|
||||||
Self::with_processes(project, debug_config, processes, window, cx)
|
Self::with_processes(project, debug_config, processes, modal, window, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn with_processes(
|
pub(super) fn with_processes(
|
||||||
project: Entity<project::Project>,
|
project: Entity<project::Project>,
|
||||||
debug_config: task::DebugTaskDefinition,
|
debug_config: task::DebugTaskDefinition,
|
||||||
processes: Vec<Candidate>,
|
processes: Vec<Candidate>,
|
||||||
|
modal: bool,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -103,6 +105,7 @@ impl AttachModal {
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
.modal(modal)
|
||||||
});
|
});
|
||||||
Self {
|
Self {
|
||||||
_subscription: cx.subscribe(&picker, |_, _, _, cx| {
|
_subscription: cx.subscribe(&picker, |_, _, _, cx| {
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
use crate::session::DebugSession;
|
use crate::{
|
||||||
|
ClearAllBreakpoints, Continue, CreateDebuggingSession, Disconnect, Pause, Restart, StepBack,
|
||||||
|
StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints,
|
||||||
|
};
|
||||||
|
use crate::{new_session_modal::NewSessionModal, session::DebugSession};
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use command_palette_hooks::CommandPaletteFilter;
|
use command_palette_hooks::CommandPaletteFilter;
|
||||||
|
@ -13,7 +17,10 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use project::{
|
use project::{
|
||||||
Project,
|
Project,
|
||||||
debugger::dap_store::{self, DapStore},
|
debugger::{
|
||||||
|
dap_store::{self, DapStore},
|
||||||
|
session::ThreadStatus,
|
||||||
|
},
|
||||||
terminals::TerminalKind,
|
terminals::TerminalKind,
|
||||||
};
|
};
|
||||||
use rpc::proto::{self};
|
use rpc::proto::{self};
|
||||||
|
@ -21,11 +28,9 @@ use settings::Settings;
|
||||||
use std::{any::TypeId, path::PathBuf};
|
use std::{any::TypeId, path::PathBuf};
|
||||||
use task::DebugTaskDefinition;
|
use task::DebugTaskDefinition;
|
||||||
use terminal_view::terminal_panel::TerminalPanel;
|
use terminal_view::terminal_panel::TerminalPanel;
|
||||||
use ui::prelude::*;
|
use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*};
|
||||||
use util::ResultExt;
|
|
||||||
use workspace::{
|
use workspace::{
|
||||||
ClearAllBreakpoints, Continue, Disconnect, Pane, Pause, Restart, StepBack, StepInto, StepOut,
|
Pane, Workspace,
|
||||||
StepOver, Stop, ToggleIgnoreBreakpoints, Workspace,
|
|
||||||
dock::{DockPosition, Panel, PanelEvent},
|
dock::{DockPosition, Panel, PanelEvent},
|
||||||
pane,
|
pane,
|
||||||
};
|
};
|
||||||
|
@ -51,10 +56,11 @@ actions!(debug_panel, [ToggleFocus]);
|
||||||
pub struct DebugPanel {
|
pub struct DebugPanel {
|
||||||
size: Pixels,
|
size: Pixels,
|
||||||
pane: Entity<Pane>,
|
pane: Entity<Pane>,
|
||||||
|
/// This represents the last debug definition that was created in the new session modal
|
||||||
|
pub(crate) past_debug_definition: Option<DebugTaskDefinition>,
|
||||||
project: WeakEntity<Project>,
|
project: WeakEntity<Project>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
pub(crate) last_inert_config: Option<DebugTaskDefinition>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DebugPanel {
|
impl DebugPanel {
|
||||||
|
@ -66,8 +72,6 @@ impl DebugPanel {
|
||||||
cx.new(|cx| {
|
cx.new(|cx| {
|
||||||
let project = workspace.project().clone();
|
let project = workspace.project().clone();
|
||||||
let dap_store = project.read(cx).dap_store();
|
let dap_store = project.read(cx).dap_store();
|
||||||
let weak_workspace = workspace.weak_handle();
|
|
||||||
let debug_panel = cx.weak_entity();
|
|
||||||
let pane = cx.new(|cx| {
|
let pane = cx.new(|cx| {
|
||||||
let mut pane = Pane::new(
|
let mut pane = Pane::new(
|
||||||
workspace.weak_handle(),
|
workspace.weak_handle(),
|
||||||
|
@ -81,71 +85,9 @@ impl DebugPanel {
|
||||||
pane.set_can_split(None);
|
pane.set_can_split(None);
|
||||||
pane.set_can_navigate(true, cx);
|
pane.set_can_navigate(true, cx);
|
||||||
pane.display_nav_history_buttons(None);
|
pane.display_nav_history_buttons(None);
|
||||||
pane.set_should_display_tab_bar(|_window, _cx| true);
|
pane.set_should_display_tab_bar(|_window, _cx| false);
|
||||||
pane.set_close_pane_if_empty(true, cx);
|
pane.set_close_pane_if_empty(true, cx);
|
||||||
pane.set_render_tab_bar_buttons(cx, {
|
|
||||||
let project = project.clone();
|
|
||||||
let weak_workspace = weak_workspace.clone();
|
|
||||||
let debug_panel = debug_panel.clone();
|
|
||||||
move |_, _, cx| {
|
|
||||||
let project = project.clone();
|
|
||||||
let weak_workspace = weak_workspace.clone();
|
|
||||||
(
|
|
||||||
None,
|
|
||||||
Some(
|
|
||||||
h_flex()
|
|
||||||
.child(
|
|
||||||
IconButton::new("new-debug-session", IconName::Plus)
|
|
||||||
.icon_size(IconSize::Small)
|
|
||||||
.on_click({
|
|
||||||
let debug_panel = debug_panel.clone();
|
|
||||||
|
|
||||||
cx.listener(move |pane, _, window, cx| {
|
|
||||||
let config = debug_panel
|
|
||||||
.read_with(cx, |this: &DebugPanel, _| {
|
|
||||||
this.last_inert_config.clone()
|
|
||||||
})
|
|
||||||
.log_err()
|
|
||||||
.flatten();
|
|
||||||
|
|
||||||
pane.add_item(
|
|
||||||
Box::new(DebugSession::inert(
|
|
||||||
project.clone(),
|
|
||||||
weak_workspace.clone(),
|
|
||||||
debug_panel.clone(),
|
|
||||||
config,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
None,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.into_any_element(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
pane.add_item(
|
|
||||||
Box::new(DebugSession::inert(
|
|
||||||
project.clone(),
|
|
||||||
weak_workspace.clone(),
|
|
||||||
debug_panel.clone(),
|
|
||||||
None,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
None,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
pane
|
pane
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -159,7 +101,7 @@ impl DebugPanel {
|
||||||
pane,
|
pane,
|
||||||
size: px(300.),
|
size: px(300.),
|
||||||
_subscriptions,
|
_subscriptions,
|
||||||
last_inert_config: None,
|
past_debug_definition: None,
|
||||||
project: project.downgrade(),
|
project: project.downgrade(),
|
||||||
workspace: workspace.weak_handle(),
|
workspace: workspace.weak_handle(),
|
||||||
};
|
};
|
||||||
|
@ -295,7 +237,7 @@ impl DebugPanel {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
dap_store::DapStoreEvent::DebugClientStarted(session_id) => {
|
dap_store::DapStoreEvent::DebugSessionInitialized(session_id) => {
|
||||||
let Some(session) = dap_store.read(cx).session_by_id(session_id) else {
|
let Some(session) = dap_store.read(cx).session_by_id(session_id) else {
|
||||||
return log::error!(
|
return log::error!(
|
||||||
"Couldn't get session with id: {session_id:?} from DebugClientStarted event"
|
"Couldn't get session with id: {session_id:?} from DebugClientStarted event"
|
||||||
|
@ -470,6 +412,274 @@ impl DebugPanel {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn top_controls_strip(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
|
||||||
|
let active_session = self
|
||||||
|
.pane
|
||||||
|
.read(cx)
|
||||||
|
.active_item()
|
||||||
|
.and_then(|item| item.downcast::<DebugSession>());
|
||||||
|
Some(
|
||||||
|
h_flex()
|
||||||
|
.border_b_1()
|
||||||
|
.border_color(cx.theme().colors().border)
|
||||||
|
.p_1()
|
||||||
|
.justify_between()
|
||||||
|
.w_full()
|
||||||
|
.child(
|
||||||
|
h_flex().gap_2().w_full().when_some(
|
||||||
|
active_session
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|session| session.read(cx).mode().as_running()),
|
||||||
|
|this, running_session| {
|
||||||
|
let thread_status = running_session
|
||||||
|
.read(cx)
|
||||||
|
.thread_status(cx)
|
||||||
|
.unwrap_or(project::debugger::session::ThreadStatus::Exited);
|
||||||
|
let capabilities = running_session.read(cx).capabilities(cx);
|
||||||
|
this.map(|this| {
|
||||||
|
if thread_status == ThreadStatus::Running {
|
||||||
|
this.child(
|
||||||
|
IconButton::new("debug-pause", IconName::DebugPause)
|
||||||
|
.icon_size(IconSize::XSmall)
|
||||||
|
.shape(ui::IconButtonShape::Square)
|
||||||
|
.on_click(window.listener_for(
|
||||||
|
&running_session,
|
||||||
|
|this, _, _window, cx| {
|
||||||
|
this.pause_thread(cx);
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.tooltip(move |window, cx| {
|
||||||
|
Tooltip::text("Pause program")(window, cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
this.child(
|
||||||
|
IconButton::new("debug-continue", IconName::DebugContinue)
|
||||||
|
.icon_size(IconSize::XSmall)
|
||||||
|
.shape(ui::IconButtonShape::Square)
|
||||||
|
.on_click(window.listener_for(
|
||||||
|
&running_session,
|
||||||
|
|this, _, _window, cx| this.continue_thread(cx),
|
||||||
|
))
|
||||||
|
.disabled(thread_status != ThreadStatus::Stopped)
|
||||||
|
.tooltip(move |window, cx| {
|
||||||
|
Tooltip::text("Continue program")(window, cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
IconButton::new("debug-step-over", IconName::ArrowRight)
|
||||||
|
.icon_size(IconSize::XSmall)
|
||||||
|
.shape(ui::IconButtonShape::Square)
|
||||||
|
.on_click(window.listener_for(
|
||||||
|
&running_session,
|
||||||
|
|this, _, _window, cx| {
|
||||||
|
this.step_over(cx);
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.disabled(thread_status != ThreadStatus::Stopped)
|
||||||
|
.tooltip(move |window, cx| {
|
||||||
|
Tooltip::text("Step over")(window, cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
IconButton::new("debug-step-out", IconName::ArrowUpRight)
|
||||||
|
.icon_size(IconSize::XSmall)
|
||||||
|
.shape(ui::IconButtonShape::Square)
|
||||||
|
.on_click(window.listener_for(
|
||||||
|
&running_session,
|
||||||
|
|this, _, _window, cx| {
|
||||||
|
this.step_out(cx);
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.disabled(thread_status != ThreadStatus::Stopped)
|
||||||
|
.tooltip(move |window, cx| {
|
||||||
|
Tooltip::text("Step out")(window, cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
IconButton::new("debug-step-into", IconName::ArrowDownRight)
|
||||||
|
.icon_size(IconSize::XSmall)
|
||||||
|
.shape(ui::IconButtonShape::Square)
|
||||||
|
.on_click(window.listener_for(
|
||||||
|
&running_session,
|
||||||
|
|this, _, _window, cx| {
|
||||||
|
this.step_in(cx);
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.disabled(thread_status != ThreadStatus::Stopped)
|
||||||
|
.tooltip(move |window, cx| {
|
||||||
|
Tooltip::text("Step in")(window, cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(Divider::vertical())
|
||||||
|
.child(
|
||||||
|
IconButton::new(
|
||||||
|
"debug-enable-breakpoint",
|
||||||
|
IconName::DebugDisabledBreakpoint,
|
||||||
|
)
|
||||||
|
.icon_size(IconSize::XSmall)
|
||||||
|
.shape(ui::IconButtonShape::Square)
|
||||||
|
.disabled(thread_status != ThreadStatus::Stopped),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
IconButton::new("debug-disable-breakpoint", IconName::CircleOff)
|
||||||
|
.icon_size(IconSize::XSmall)
|
||||||
|
.shape(ui::IconButtonShape::Square)
|
||||||
|
.disabled(thread_status != ThreadStatus::Stopped),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
IconButton::new("debug-disable-all-breakpoints", IconName::BugOff)
|
||||||
|
.icon_size(IconSize::XSmall)
|
||||||
|
.shape(ui::IconButtonShape::Square)
|
||||||
|
.disabled(
|
||||||
|
thread_status == ThreadStatus::Exited
|
||||||
|
|| thread_status == ThreadStatus::Ended,
|
||||||
|
)
|
||||||
|
.on_click(window.listener_for(
|
||||||
|
&running_session,
|
||||||
|
|this, _, _window, cx| {
|
||||||
|
this.toggle_ignore_breakpoints(cx);
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.tooltip(move |window, cx| {
|
||||||
|
Tooltip::text("Disable all breakpoints")(window, cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(Divider::vertical())
|
||||||
|
.child(
|
||||||
|
IconButton::new("debug-restart", IconName::DebugRestart)
|
||||||
|
.icon_size(IconSize::XSmall)
|
||||||
|
.on_click(window.listener_for(
|
||||||
|
&running_session,
|
||||||
|
|this, _, _window, cx| {
|
||||||
|
this.restart_session(cx);
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.disabled(
|
||||||
|
!capabilities.supports_restart_request.unwrap_or_default(),
|
||||||
|
)
|
||||||
|
.tooltip(move |window, cx| {
|
||||||
|
Tooltip::text("Restart")(window, cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
IconButton::new("debug-stop", IconName::Power)
|
||||||
|
.icon_size(IconSize::XSmall)
|
||||||
|
.on_click(window.listener_for(
|
||||||
|
&running_session,
|
||||||
|
|this, _, _window, cx| {
|
||||||
|
this.stop_thread(cx);
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.disabled(
|
||||||
|
thread_status != ThreadStatus::Stopped
|
||||||
|
&& thread_status != ThreadStatus::Running,
|
||||||
|
)
|
||||||
|
.tooltip({
|
||||||
|
let label = if capabilities
|
||||||
|
.supports_terminate_threads_request
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
"Terminate Thread"
|
||||||
|
} else {
|
||||||
|
"Terminate all Threads"
|
||||||
|
};
|
||||||
|
move |window, cx| Tooltip::text(label)(window, cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_2()
|
||||||
|
.when_some(
|
||||||
|
active_session
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|session| session.read(cx).mode().as_running())
|
||||||
|
.cloned(),
|
||||||
|
|this, session| {
|
||||||
|
this.child(
|
||||||
|
session.update(cx, |this, cx| this.thread_dropdown(window, cx)),
|
||||||
|
)
|
||||||
|
.child(Divider::vertical())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.when_some(active_session.as_ref(), |this, session| {
|
||||||
|
let pane = self.pane.downgrade();
|
||||||
|
let label = session.read(cx).label(cx);
|
||||||
|
this.child(DropdownMenu::new(
|
||||||
|
"debugger-session-list",
|
||||||
|
label,
|
||||||
|
ContextMenu::build(window, cx, move |mut this, _, cx| {
|
||||||
|
let sessions = pane
|
||||||
|
.read_with(cx, |pane, _| {
|
||||||
|
pane.items().map(|item| item.boxed_clone()).collect()
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
.unwrap_or_else(Vec::new);
|
||||||
|
for (index, item) in sessions.into_iter().enumerate() {
|
||||||
|
if let Some(session) = item.downcast::<DebugSession>() {
|
||||||
|
let pane = pane.clone();
|
||||||
|
this = this.entry(
|
||||||
|
session.read(cx).label(cx),
|
||||||
|
None,
|
||||||
|
move |window, cx| {
|
||||||
|
pane.update(cx, |pane, cx| {
|
||||||
|
pane.activate_item(
|
||||||
|
index, true, true, window, cx,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
.child(Divider::vertical())
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
IconButton::new("debug-new-session", IconName::Plus)
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
.on_click({
|
||||||
|
let workspace = self.workspace.clone();
|
||||||
|
let weak_panel = cx.weak_entity();
|
||||||
|
let past_debug_definition = self.past_debug_definition.clone();
|
||||||
|
move |_, window, cx| {
|
||||||
|
let weak_panel = weak_panel.clone();
|
||||||
|
let past_debug_definition = past_debug_definition.clone();
|
||||||
|
|
||||||
|
let _ = workspace.update(cx, |this, cx| {
|
||||||
|
let workspace = cx.weak_entity();
|
||||||
|
this.toggle_modal(window, cx, |window, cx| {
|
||||||
|
NewSessionModal::new(
|
||||||
|
past_debug_definition,
|
||||||
|
weak_panel,
|
||||||
|
workspace,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.tooltip(|window, cx| {
|
||||||
|
Tooltip::for_action(
|
||||||
|
"New Debug Session",
|
||||||
|
&CreateDebuggingSession,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<PanelEvent> for DebugPanel {}
|
impl EventEmitter<PanelEvent> for DebugPanel {}
|
||||||
|
@ -507,7 +717,7 @@ impl Panel for DebugPanel {
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size(&self, _window: &Window, _cx: &App) -> Pixels {
|
fn size(&self, _window: &Window, _: &App) -> Pixels {
|
||||||
self.size
|
self.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -538,42 +748,49 @@ impl Panel for DebugPanel {
|
||||||
fn activation_priority(&self) -> u32 {
|
fn activation_priority(&self) -> u32 {
|
||||||
9
|
9
|
||||||
}
|
}
|
||||||
fn set_active(&mut self, active: bool, window: &mut Window, cx: &mut Context<Self>) {
|
fn set_active(&mut self, _: bool, _: &mut Window, _: &mut Context<Self>) {}
|
||||||
if active && self.pane.read(cx).items_len() == 0 {
|
|
||||||
let Some(project) = self.project.clone().upgrade() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let config = self.last_inert_config.clone();
|
|
||||||
let panel = cx.weak_entity();
|
|
||||||
// todo: We need to revisit it when we start adding stopped items to pane (as that'll cause us to add two items).
|
|
||||||
self.pane.update(cx, |this, cx| {
|
|
||||||
this.add_item(
|
|
||||||
Box::new(DebugSession::inert(
|
|
||||||
project,
|
|
||||||
self.workspace.clone(),
|
|
||||||
panel,
|
|
||||||
config,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
None,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for DebugPanel {
|
impl Render for DebugPanel {
|
||||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let has_sessions = self.pane.read(cx).items_len() > 0;
|
||||||
v_flex()
|
v_flex()
|
||||||
.key_context("DebugPanel")
|
|
||||||
.track_focus(&self.focus_handle(cx))
|
|
||||||
.size_full()
|
.size_full()
|
||||||
.child(self.pane.clone())
|
.key_context("DebugPanel")
|
||||||
|
.child(h_flex().children(self.top_controls_strip(window, cx)))
|
||||||
|
.track_focus(&self.focus_handle(cx))
|
||||||
|
.map(|this| {
|
||||||
|
if has_sessions {
|
||||||
|
this.child(self.pane.clone())
|
||||||
|
} else {
|
||||||
|
this.child(
|
||||||
|
v_flex()
|
||||||
|
.h_full()
|
||||||
|
.gap_1()
|
||||||
|
.items_center()
|
||||||
|
.justify_center()
|
||||||
|
.child(
|
||||||
|
h_flex().child(
|
||||||
|
Label::new("No Debugging Sessions")
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_flex().flex_shrink().child(
|
||||||
|
Button::new("spawn-new-session-empty-state", "New Session")
|
||||||
|
.size(ButtonSize::Large)
|
||||||
|
.on_click(|_, window, cx| {
|
||||||
|
window.dispatch_action(
|
||||||
|
CreateDebuggingSession.boxed_clone(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
.into_any()
|
.into_any()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,38 @@
|
||||||
use dap::debugger_settings::DebuggerSettings;
|
use dap::debugger_settings::DebuggerSettings;
|
||||||
use debugger_panel::{DebugPanel, ToggleFocus};
|
use debugger_panel::{DebugPanel, ToggleFocus};
|
||||||
use feature_flags::{Debugger, FeatureFlagViewExt};
|
use feature_flags::{Debugger, FeatureFlagViewExt};
|
||||||
use gpui::App;
|
use gpui::{App, actions};
|
||||||
|
use new_session_modal::NewSessionModal;
|
||||||
use session::DebugSession;
|
use session::DebugSession;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use workspace::{
|
use workspace::{ShutdownDebugAdapters, Workspace};
|
||||||
Pause, Restart, ShutdownDebugAdapters, StepBack, StepInto, StepOver, Stop,
|
|
||||||
ToggleIgnoreBreakpoints, Workspace,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod attach_modal;
|
pub mod attach_modal;
|
||||||
pub mod debugger_panel;
|
pub mod debugger_panel;
|
||||||
pub mod session;
|
mod new_session_modal;
|
||||||
|
pub(crate) mod session;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
pub mod tests;
|
||||||
|
|
||||||
|
actions!(
|
||||||
|
debugger,
|
||||||
|
[
|
||||||
|
Start,
|
||||||
|
Continue,
|
||||||
|
Disconnect,
|
||||||
|
Pause,
|
||||||
|
Restart,
|
||||||
|
StepInto,
|
||||||
|
StepOver,
|
||||||
|
StepOut,
|
||||||
|
StepBack,
|
||||||
|
Stop,
|
||||||
|
ToggleIgnoreBreakpoints,
|
||||||
|
ClearAllBreakpoints,
|
||||||
|
CreateDebuggingSession,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
DebuggerSettings::register(cx);
|
DebuggerSettings::register(cx);
|
||||||
|
@ -115,6 +133,23 @@ pub fn init(cx: &mut App) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
)
|
||||||
|
.register_action(
|
||||||
|
|workspace: &mut Workspace, _: &CreateDebuggingSession, window, cx| {
|
||||||
|
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||||
|
let weak_panel = debug_panel.downgrade();
|
||||||
|
let weak_workspace = cx.weak_entity();
|
||||||
|
|
||||||
|
workspace.toggle_modal(window, cx, |window, cx| {
|
||||||
|
NewSessionModal::new(
|
||||||
|
debug_panel.read(cx).past_debug_definition.clone(),
|
||||||
|
weak_panel,
|
||||||
|
weak_workspace,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
},
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
633
crates/debugger_ui/src/new_session_modal.rs
Normal file
633
crates/debugger_ui/src/new_session_modal.rs
Normal file
|
@ -0,0 +1,633 @@
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
ops::Not,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::{Result, anyhow};
|
||||||
|
use dap::DebugRequestType;
|
||||||
|
use editor::{Editor, EditorElement, EditorStyle};
|
||||||
|
use gpui::{
|
||||||
|
App, AppContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Render, TextStyle,
|
||||||
|
WeakEntity,
|
||||||
|
};
|
||||||
|
use settings::Settings;
|
||||||
|
use task::{DebugTaskDefinition, LaunchConfig};
|
||||||
|
use theme::ThemeSettings;
|
||||||
|
use ui::{
|
||||||
|
ActiveTheme, Button, ButtonCommon, ButtonSize, CheckboxWithLabel, Clickable, Color, Context,
|
||||||
|
ContextMenu, Disableable, DropdownMenu, FluentBuilder, InteractiveElement, IntoElement, Label,
|
||||||
|
LabelCommon as _, ParentElement, RenderOnce, SharedString, Styled, StyledExt, ToggleButton,
|
||||||
|
ToggleState, Toggleable, Window, div, h_flex, relative, rems, v_flex,
|
||||||
|
};
|
||||||
|
use util::ResultExt;
|
||||||
|
use workspace::{ModalView, Workspace};
|
||||||
|
|
||||||
|
use crate::{attach_modal::AttachModal, debugger_panel::DebugPanel};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(super) struct NewSessionModal {
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
debug_panel: WeakEntity<DebugPanel>,
|
||||||
|
mode: NewSessionMode,
|
||||||
|
stop_on_entry: ToggleState,
|
||||||
|
debugger: Option<SharedString>,
|
||||||
|
last_selected_profile_name: Option<SharedString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn suggested_label(request: &DebugRequestType, debugger: &str) -> String {
|
||||||
|
match request {
|
||||||
|
DebugRequestType::Launch(config) => {
|
||||||
|
let last_path_component = Path::new(&config.program)
|
||||||
|
.file_name()
|
||||||
|
.map(|name| name.to_string_lossy())
|
||||||
|
.unwrap_or_else(|| Cow::Borrowed(&config.program));
|
||||||
|
|
||||||
|
format!("{} ({debugger})", last_path_component)
|
||||||
|
}
|
||||||
|
DebugRequestType::Attach(config) => format!(
|
||||||
|
"pid: {} ({debugger})",
|
||||||
|
config.process_id.unwrap_or(u32::MAX)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NewSessionModal {
|
||||||
|
pub(super) fn new(
|
||||||
|
past_debug_definition: Option<DebugTaskDefinition>,
|
||||||
|
debug_panel: WeakEntity<DebugPanel>,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Self {
|
||||||
|
let debugger = past_debug_definition
|
||||||
|
.as_ref()
|
||||||
|
.map(|def| def.adapter.clone().into());
|
||||||
|
|
||||||
|
let stop_on_entry = past_debug_definition
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|def| def.stop_on_entry);
|
||||||
|
|
||||||
|
let launch_config = match past_debug_definition.map(|def| def.request) {
|
||||||
|
Some(DebugRequestType::Launch(launch_config)) => Some(launch_config),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
workspace: workspace.clone(),
|
||||||
|
debugger,
|
||||||
|
debug_panel,
|
||||||
|
mode: NewSessionMode::launch(launch_config, window, cx),
|
||||||
|
stop_on_entry: stop_on_entry
|
||||||
|
.map(Into::into)
|
||||||
|
.unwrap_or(ToggleState::Unselected),
|
||||||
|
last_selected_profile_name: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_config(&self, cx: &App) -> Option<DebugTaskDefinition> {
|
||||||
|
let request = self.mode.debug_task(cx);
|
||||||
|
|
||||||
|
Some(DebugTaskDefinition {
|
||||||
|
adapter: self.debugger.clone()?.to_string(),
|
||||||
|
label: suggested_label(&request, self.debugger.as_deref()?),
|
||||||
|
request,
|
||||||
|
initialize_args: None,
|
||||||
|
tcp_connection: None,
|
||||||
|
locator: None,
|
||||||
|
stop_on_entry: match self.stop_on_entry {
|
||||||
|
ToggleState::Selected => Some(true),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn start_new_session(&self, cx: &mut Context<Self>) -> Result<()> {
|
||||||
|
let workspace = self.workspace.clone();
|
||||||
|
let config = self
|
||||||
|
.debug_config(cx)
|
||||||
|
.ok_or_else(|| anyhow!("Failed to create a debug config"))?;
|
||||||
|
|
||||||
|
let _ = self.debug_panel.update(cx, |panel, _| {
|
||||||
|
panel.past_debug_definition = Some(config.clone());
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.spawn(async move |this, cx| {
|
||||||
|
let project = workspace.update(cx, |workspace, _| workspace.project().clone())?;
|
||||||
|
let task =
|
||||||
|
project.update(cx, |this, cx| this.start_debug_session(config.into(), cx))?;
|
||||||
|
let spawn_result = task.await;
|
||||||
|
if spawn_result.is_ok() {
|
||||||
|
this.update(cx, |_, cx| {
|
||||||
|
cx.emit(DismissEvent);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
spawn_result?;
|
||||||
|
anyhow::Result::<_, anyhow::Error>::Ok(())
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_attach_picker(
|
||||||
|
attach: &Entity<AttachMode>,
|
||||||
|
selected_debugger: &str,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut App,
|
||||||
|
) {
|
||||||
|
attach.update(cx, |this, cx| {
|
||||||
|
if selected_debugger != this.debug_definition.adapter {
|
||||||
|
this.debug_definition.adapter = selected_debugger.into();
|
||||||
|
if let Some(project) = this
|
||||||
|
.workspace
|
||||||
|
.read_with(cx, |workspace, _| workspace.project().clone())
|
||||||
|
.ok()
|
||||||
|
{
|
||||||
|
this.attach_picker = Some(cx.new(|cx| {
|
||||||
|
let modal = AttachModal::new(
|
||||||
|
project,
|
||||||
|
this.debug_definition.clone(),
|
||||||
|
false,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
|
window.focus(&modal.focus_handle(cx));
|
||||||
|
|
||||||
|
modal
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn adapter_drop_down_menu(
|
||||||
|
&self,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> ui::DropdownMenu {
|
||||||
|
let workspace = self.workspace.clone();
|
||||||
|
let weak = cx.weak_entity();
|
||||||
|
let debugger = self.debugger.clone();
|
||||||
|
DropdownMenu::new(
|
||||||
|
"dap-adapter-picker",
|
||||||
|
debugger
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or_else(|| &SELECT_DEBUGGER_LABEL)
|
||||||
|
.clone(),
|
||||||
|
ContextMenu::build(window, cx, move |mut menu, _, cx| {
|
||||||
|
let setter_for_name = |name: SharedString| {
|
||||||
|
let weak = weak.clone();
|
||||||
|
move |window: &mut Window, cx: &mut App| {
|
||||||
|
weak.update(cx, |this, cx| {
|
||||||
|
this.debugger = Some(name.clone());
|
||||||
|
cx.notify();
|
||||||
|
if let NewSessionMode::Attach(attach) = &this.mode {
|
||||||
|
Self::update_attach_picker(&attach, &name, window, cx);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let available_adapters = workspace
|
||||||
|
.update(cx, |this, cx| {
|
||||||
|
this.project()
|
||||||
|
.read(cx)
|
||||||
|
.debug_adapters()
|
||||||
|
.enumerate_adapters()
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
for adapter in available_adapters {
|
||||||
|
menu = menu.entry(adapter.0.clone(), None, setter_for_name(adapter.0.clone()));
|
||||||
|
}
|
||||||
|
menu
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_config_drop_down_menu(
|
||||||
|
&self,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> ui::DropdownMenu {
|
||||||
|
let workspace = self.workspace.clone();
|
||||||
|
let weak = cx.weak_entity();
|
||||||
|
let last_profile = self.last_selected_profile_name.clone();
|
||||||
|
DropdownMenu::new(
|
||||||
|
"debug-config-menu",
|
||||||
|
last_profile.unwrap_or_else(|| SELECT_SCENARIO_LABEL.clone()),
|
||||||
|
ContextMenu::build(window, cx, move |mut menu, _, cx| {
|
||||||
|
let setter_for_name = |task: DebugTaskDefinition| {
|
||||||
|
let weak = weak.clone();
|
||||||
|
let workspace = workspace.clone();
|
||||||
|
move |window: &mut Window, cx: &mut App| {
|
||||||
|
weak.update(cx, |this, cx| {
|
||||||
|
this.last_selected_profile_name = Some(SharedString::from(&task.label));
|
||||||
|
this.debugger = Some(task.adapter.clone().into());
|
||||||
|
|
||||||
|
match &task.request {
|
||||||
|
DebugRequestType::Launch(launch_config) => {
|
||||||
|
this.mode = NewSessionMode::launch(
|
||||||
|
Some(launch_config.clone()),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
DebugRequestType::Attach(_) => {
|
||||||
|
this.mode = NewSessionMode::attach(
|
||||||
|
this.debugger.clone(),
|
||||||
|
workspace.clone(),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
if let Some((debugger, attach)) =
|
||||||
|
this.debugger.as_ref().zip(this.mode.as_attach())
|
||||||
|
{
|
||||||
|
Self::update_attach_picker(&attach, &debugger, window, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let available_adapters: Vec<DebugTaskDefinition> = workspace
|
||||||
|
.update(cx, |this, cx| {
|
||||||
|
this.project()
|
||||||
|
.read(cx)
|
||||||
|
.task_store()
|
||||||
|
.read(cx)
|
||||||
|
.task_inventory()
|
||||||
|
.iter()
|
||||||
|
.flat_map(|task_inventory| task_inventory.read(cx).list_debug_tasks())
|
||||||
|
.cloned()
|
||||||
|
.filter_map(|task| task.try_into().ok())
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
for debug_definition in available_adapters {
|
||||||
|
menu = menu.entry(
|
||||||
|
debug_definition.label.clone(),
|
||||||
|
None,
|
||||||
|
setter_for_name(debug_definition),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
menu
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct LaunchMode {
|
||||||
|
program: Entity<Editor>,
|
||||||
|
cwd: Entity<Editor>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LaunchMode {
|
||||||
|
fn new(
|
||||||
|
past_launch_config: Option<LaunchConfig>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Entity<Self> {
|
||||||
|
let (past_program, past_cwd) = past_launch_config
|
||||||
|
.map(|config| (Some(config.program), config.cwd))
|
||||||
|
.unwrap_or_else(|| (None, None));
|
||||||
|
|
||||||
|
let program = cx.new(|cx| Editor::single_line(window, cx));
|
||||||
|
program.update(cx, |this, cx| {
|
||||||
|
this.set_placeholder_text("Program path", cx);
|
||||||
|
|
||||||
|
if let Some(past_program) = past_program {
|
||||||
|
this.set_text(past_program, window, cx);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
let cwd = cx.new(|cx| Editor::single_line(window, cx));
|
||||||
|
cwd.update(cx, |this, cx| {
|
||||||
|
this.set_placeholder_text("Working Directory", cx);
|
||||||
|
if let Some(past_cwd) = past_cwd {
|
||||||
|
this.set_text(past_cwd.to_string_lossy(), window, cx);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
cx.new(|_| Self { program, cwd })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_task(&self, cx: &App) -> task::LaunchConfig {
|
||||||
|
let path = self.cwd.read(cx).text(cx);
|
||||||
|
task::LaunchConfig {
|
||||||
|
program: self.program.read(cx).text(cx),
|
||||||
|
cwd: path.is_empty().not().then(|| PathBuf::from(path)),
|
||||||
|
args: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct AttachMode {
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
debug_definition: DebugTaskDefinition,
|
||||||
|
attach_picker: Option<Entity<AttachModal>>,
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AttachMode {
|
||||||
|
fn new(
|
||||||
|
debugger: Option<SharedString>,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Entity<Self> {
|
||||||
|
let debug_definition = DebugTaskDefinition {
|
||||||
|
label: "Attach New Session Setup".into(),
|
||||||
|
request: dap::DebugRequestType::Attach(task::AttachConfig { process_id: None }),
|
||||||
|
tcp_connection: None,
|
||||||
|
adapter: debugger.clone().unwrap_or_default().into(),
|
||||||
|
locator: None,
|
||||||
|
initialize_args: None,
|
||||||
|
stop_on_entry: Some(false),
|
||||||
|
};
|
||||||
|
|
||||||
|
let attach_picker = if let Some(project) = debugger.and(
|
||||||
|
workspace
|
||||||
|
.read_with(cx, |workspace, _| workspace.project().clone())
|
||||||
|
.ok(),
|
||||||
|
) {
|
||||||
|
Some(cx.new(|cx| {
|
||||||
|
let modal = AttachModal::new(project, debug_definition.clone(), false, window, cx);
|
||||||
|
window.focus(&modal.focus_handle(cx));
|
||||||
|
|
||||||
|
modal
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
cx.new(|cx| Self {
|
||||||
|
workspace,
|
||||||
|
debug_definition,
|
||||||
|
attach_picker,
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn debug_task(&self) -> task::AttachConfig {
|
||||||
|
task::AttachConfig { process_id: None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static SELECT_DEBUGGER_LABEL: SharedString = SharedString::new_static("Select Debugger");
|
||||||
|
static SELECT_SCENARIO_LABEL: SharedString = SharedString::new_static("Select Profile");
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum NewSessionMode {
|
||||||
|
Launch(Entity<LaunchMode>),
|
||||||
|
Attach(Entity<AttachMode>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NewSessionMode {
|
||||||
|
fn debug_task(&self, cx: &App) -> DebugRequestType {
|
||||||
|
match self {
|
||||||
|
NewSessionMode::Launch(entity) => entity.read(cx).debug_task(cx).into(),
|
||||||
|
NewSessionMode::Attach(entity) => entity.read(cx).debug_task().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn as_attach(&self) -> Option<&Entity<AttachMode>> {
|
||||||
|
if let NewSessionMode::Attach(entity) = self {
|
||||||
|
Some(entity)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Focusable for NewSessionMode {
|
||||||
|
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||||
|
match &self {
|
||||||
|
NewSessionMode::Launch(entity) => entity.read(cx).program.focus_handle(cx),
|
||||||
|
NewSessionMode::Attach(entity) => entity.read(cx).focus_handle.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for LaunchMode {
|
||||||
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
v_flex()
|
||||||
|
.p_2()
|
||||||
|
.w_full()
|
||||||
|
.gap_3()
|
||||||
|
.track_focus(&self.program.focus_handle(cx))
|
||||||
|
.child(
|
||||||
|
div().child(
|
||||||
|
Label::new("Program")
|
||||||
|
.size(ui::LabelSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(render_editor(&self.program, window, cx))
|
||||||
|
.child(
|
||||||
|
div().child(
|
||||||
|
Label::new("Working Directory")
|
||||||
|
.size(ui::LabelSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(render_editor(&self.cwd, window, cx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for AttachMode {
|
||||||
|
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
|
||||||
|
v_flex().w_full().children(self.attach_picker.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for NewSessionMode {
|
||||||
|
fn render(self, window: &mut Window, cx: &mut App) -> impl ui::IntoElement {
|
||||||
|
match self {
|
||||||
|
NewSessionMode::Launch(entity) => entity.update(cx, |this, cx| {
|
||||||
|
this.clone().render(window, cx).into_any_element()
|
||||||
|
}),
|
||||||
|
NewSessionMode::Attach(entity) => entity.update(cx, |this, cx| {
|
||||||
|
this.clone().render(window, cx).into_any_element()
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NewSessionMode {
|
||||||
|
fn attach(
|
||||||
|
debugger: Option<SharedString>,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Self {
|
||||||
|
Self::Attach(AttachMode::new(debugger, workspace, window, cx))
|
||||||
|
}
|
||||||
|
fn launch(past_launch_config: Option<LaunchConfig>, window: &mut Window, cx: &mut App) -> Self {
|
||||||
|
Self::Launch(LaunchMode::new(past_launch_config, window, cx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn render_editor(editor: &Entity<Editor>, window: &mut Window, cx: &App) -> impl IntoElement {
|
||||||
|
let settings = ThemeSettings::get_global(cx);
|
||||||
|
let theme = cx.theme();
|
||||||
|
|
||||||
|
let text_style = TextStyle {
|
||||||
|
color: cx.theme().colors().text,
|
||||||
|
font_family: settings.buffer_font.family.clone(),
|
||||||
|
font_features: settings.buffer_font.features.clone(),
|
||||||
|
font_size: settings.buffer_font_size(cx).into(),
|
||||||
|
font_weight: settings.buffer_font.weight,
|
||||||
|
line_height: relative(settings.buffer_line_height.value()),
|
||||||
|
background_color: Some(theme.colors().editor_background),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let element = EditorElement::new(
|
||||||
|
editor,
|
||||||
|
EditorStyle {
|
||||||
|
background: theme.colors().editor_background,
|
||||||
|
local_player: theme.players().local(),
|
||||||
|
text: text_style,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
div()
|
||||||
|
.rounded_md()
|
||||||
|
.p_1()
|
||||||
|
.border_1()
|
||||||
|
.border_color(theme.colors().border_variant)
|
||||||
|
.when(
|
||||||
|
editor.focus_handle(cx).contains_focused(window, cx),
|
||||||
|
|this| this.border_color(theme.colors().border_focused),
|
||||||
|
)
|
||||||
|
.child(element)
|
||||||
|
.bg(theme.colors().editor_background)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for NewSessionModal {
|
||||||
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
window: &mut ui::Window,
|
||||||
|
cx: &mut ui::Context<Self>,
|
||||||
|
) -> impl ui::IntoElement {
|
||||||
|
v_flex()
|
||||||
|
.size_full()
|
||||||
|
.w(rems(34.))
|
||||||
|
.elevation_3(cx)
|
||||||
|
.bg(cx.theme().colors().elevated_surface_background)
|
||||||
|
.on_action(cx.listener(|_, _: &menu::Cancel, _, cx| {
|
||||||
|
cx.emit(DismissEvent);
|
||||||
|
}))
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.w_full()
|
||||||
|
.justify_around()
|
||||||
|
.p_2()
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.justify_start()
|
||||||
|
.w_full()
|
||||||
|
.child(
|
||||||
|
ToggleButton::new(
|
||||||
|
"debugger-session-ui-launch-button",
|
||||||
|
"New Session",
|
||||||
|
)
|
||||||
|
.size(ButtonSize::Default)
|
||||||
|
.style(ui::ButtonStyle::Subtle)
|
||||||
|
.toggle_state(matches!(self.mode, NewSessionMode::Launch(_)))
|
||||||
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
|
this.mode = NewSessionMode::launch(None, window, cx);
|
||||||
|
this.mode.focus_handle(cx).focus(window);
|
||||||
|
cx.notify();
|
||||||
|
}))
|
||||||
|
.first(),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
ToggleButton::new(
|
||||||
|
"debugger-session-ui-attach-button",
|
||||||
|
"Attach to Process",
|
||||||
|
)
|
||||||
|
.size(ButtonSize::Default)
|
||||||
|
.toggle_state(matches!(self.mode, NewSessionMode::Attach(_)))
|
||||||
|
.style(ui::ButtonStyle::Subtle)
|
||||||
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
|
this.mode = NewSessionMode::attach(
|
||||||
|
this.debugger.clone(),
|
||||||
|
this.workspace.clone(),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
if let Some((debugger, attach)) =
|
||||||
|
this.debugger.as_ref().zip(this.mode.as_attach())
|
||||||
|
{
|
||||||
|
Self::update_attach_picker(&attach, &debugger, window, cx);
|
||||||
|
}
|
||||||
|
this.mode.focus_handle(cx).focus(window);
|
||||||
|
cx.notify();
|
||||||
|
}))
|
||||||
|
.last(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.justify_between()
|
||||||
|
.child(self.adapter_drop_down_menu(window, cx))
|
||||||
|
.border_color(cx.theme().colors().border_variant)
|
||||||
|
.border_b_1(),
|
||||||
|
)
|
||||||
|
.child(v_flex().child(self.mode.clone().render(window, cx)))
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.justify_between()
|
||||||
|
.gap_2()
|
||||||
|
.p_2()
|
||||||
|
.border_color(cx.theme().colors().border_variant)
|
||||||
|
.border_t_1()
|
||||||
|
.w_full()
|
||||||
|
.child(self.debug_config_drop_down_menu(window, cx))
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.justify_end()
|
||||||
|
.when(matches!(self.mode, NewSessionMode::Launch(_)), |this| {
|
||||||
|
let weak = cx.weak_entity();
|
||||||
|
this.child(
|
||||||
|
CheckboxWithLabel::new(
|
||||||
|
"debugger-stop-on-entry",
|
||||||
|
Label::new("Stop on Entry").size(ui::LabelSize::Small),
|
||||||
|
self.stop_on_entry,
|
||||||
|
move |state, _, cx| {
|
||||||
|
weak.update(cx, |this, _| {
|
||||||
|
this.stop_on_entry = *state;
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.checkbox_position(ui::IconPosition::End),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
Button::new("debugger-spawn", "Start")
|
||||||
|
.on_click(cx.listener(|this, _, _, cx| {
|
||||||
|
this.start_new_session(cx).log_err();
|
||||||
|
}))
|
||||||
|
.disabled(self.debugger.is_none()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<DismissEvent> for NewSessionModal {}
|
||||||
|
impl Focusable for NewSessionModal {
|
||||||
|
fn focus_handle(&self, cx: &ui::App) -> gpui::FocusHandle {
|
||||||
|
self.mode.focus_handle(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModalView for NewSessionModal {}
|
|
@ -1,26 +1,13 @@
|
||||||
mod failed;
|
|
||||||
mod inert;
|
|
||||||
pub mod running;
|
pub mod running;
|
||||||
mod starting;
|
|
||||||
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use dap::client::SessionId;
|
use dap::client::SessionId;
|
||||||
use failed::FailedState;
|
use gpui::{App, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity};
|
||||||
use gpui::{
|
|
||||||
Animation, AnimationExt, AnyElement, App, Entity, EventEmitter, FocusHandle, Focusable,
|
|
||||||
Subscription, Task, Transformation, WeakEntity, percentage,
|
|
||||||
};
|
|
||||||
use inert::{InertEvent, InertState};
|
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use project::debugger::{dap_store::DapStore, session::Session};
|
use project::debugger::{dap_store::DapStore, session::Session};
|
||||||
use project::worktree_store::WorktreeStore;
|
use project::worktree_store::WorktreeStore;
|
||||||
use rpc::proto::{self, PeerId};
|
use rpc::proto::{self, PeerId};
|
||||||
use running::RunningState;
|
use running::RunningState;
|
||||||
use starting::{StartingEvent, StartingState};
|
use ui::prelude::*;
|
||||||
use task::DebugTaskDefinition;
|
|
||||||
use ui::{Indicator, prelude::*};
|
|
||||||
use util::ResultExt;
|
|
||||||
use workspace::{
|
use workspace::{
|
||||||
FollowableItem, ViewId, Workspace,
|
FollowableItem, ViewId, Workspace,
|
||||||
item::{self, Item},
|
item::{self, Item},
|
||||||
|
@ -29,9 +16,6 @@ use workspace::{
|
||||||
use crate::debugger_panel::DebugPanel;
|
use crate::debugger_panel::DebugPanel;
|
||||||
|
|
||||||
pub(crate) enum DebugSessionState {
|
pub(crate) enum DebugSessionState {
|
||||||
Inert(Entity<InertState>),
|
|
||||||
Starting(Entity<StartingState>),
|
|
||||||
Failed(Entity<FailedState>),
|
|
||||||
Running(Entity<running::RunningState>),
|
Running(Entity<running::RunningState>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +23,6 @@ impl DebugSessionState {
|
||||||
pub(crate) fn as_running(&self) -> Option<&Entity<running::RunningState>> {
|
pub(crate) fn as_running(&self) -> Option<&Entity<running::RunningState>> {
|
||||||
match &self {
|
match &self {
|
||||||
DebugSessionState::Running(entity) => Some(entity),
|
DebugSessionState::Running(entity) => Some(entity),
|
||||||
_ => None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,9 +31,9 @@ pub struct DebugSession {
|
||||||
remote_id: Option<workspace::ViewId>,
|
remote_id: Option<workspace::ViewId>,
|
||||||
mode: DebugSessionState,
|
mode: DebugSessionState,
|
||||||
dap_store: WeakEntity<DapStore>,
|
dap_store: WeakEntity<DapStore>,
|
||||||
debug_panel: WeakEntity<DebugPanel>,
|
_debug_panel: WeakEntity<DebugPanel>,
|
||||||
worktree_store: WeakEntity<WorktreeStore>,
|
_worktree_store: WeakEntity<WorktreeStore>,
|
||||||
workspace: WeakEntity<Workspace>,
|
_workspace: WeakEntity<Workspace>,
|
||||||
_subscriptions: [Subscription; 1],
|
_subscriptions: [Subscription; 1],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,46 +52,11 @@ pub enum ThreadItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DebugSession {
|
impl DebugSession {
|
||||||
pub(super) fn inert(
|
|
||||||
project: Entity<Project>,
|
|
||||||
workspace: WeakEntity<Workspace>,
|
|
||||||
debug_panel: WeakEntity<DebugPanel>,
|
|
||||||
config: Option<DebugTaskDefinition>,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut App,
|
|
||||||
) -> Entity<Self> {
|
|
||||||
let default_cwd = project
|
|
||||||
.read(cx)
|
|
||||||
.worktrees(cx)
|
|
||||||
.next()
|
|
||||||
.and_then(|tree| tree.read(cx).abs_path().to_str().map(|str| str.to_string()))
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let inert =
|
|
||||||
cx.new(|cx| InertState::new(workspace.clone(), &default_cwd, config, window, cx));
|
|
||||||
|
|
||||||
let project = project.read(cx);
|
|
||||||
let dap_store = project.dap_store().downgrade();
|
|
||||||
let worktree_store = project.worktree_store().downgrade();
|
|
||||||
cx.new(|cx| {
|
|
||||||
let _subscriptions = [cx.subscribe_in(&inert, window, Self::on_inert_event)];
|
|
||||||
Self {
|
|
||||||
remote_id: None,
|
|
||||||
mode: DebugSessionState::Inert(inert),
|
|
||||||
dap_store,
|
|
||||||
worktree_store,
|
|
||||||
debug_panel,
|
|
||||||
workspace,
|
|
||||||
_subscriptions,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn running(
|
pub(crate) fn running(
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
session: Entity<Session>,
|
session: Entity<Session>,
|
||||||
debug_panel: WeakEntity<DebugPanel>,
|
_debug_panel: WeakEntity<DebugPanel>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Entity<Self> {
|
) -> Entity<Self> {
|
||||||
|
@ -121,26 +69,20 @@ impl DebugSession {
|
||||||
remote_id: None,
|
remote_id: None,
|
||||||
mode: DebugSessionState::Running(mode),
|
mode: DebugSessionState::Running(mode),
|
||||||
dap_store: project.read(cx).dap_store().downgrade(),
|
dap_store: project.read(cx).dap_store().downgrade(),
|
||||||
debug_panel,
|
_debug_panel,
|
||||||
worktree_store: project.read(cx).worktree_store().downgrade(),
|
_worktree_store: project.read(cx).worktree_store().downgrade(),
|
||||||
workspace,
|
_workspace: workspace,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn session_id(&self, cx: &App) -> Option<SessionId> {
|
pub(crate) fn session_id(&self, cx: &App) -> Option<SessionId> {
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
DebugSessionState::Inert(_) => None,
|
|
||||||
DebugSessionState::Starting(entity) => Some(entity.read(cx).session_id),
|
|
||||||
DebugSessionState::Failed(_) => None,
|
|
||||||
DebugSessionState::Running(entity) => Some(entity.read(cx).session_id()),
|
DebugSessionState::Running(entity) => Some(entity.read(cx).session_id()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn shutdown(&mut self, cx: &mut Context<Self>) {
|
pub(crate) fn shutdown(&mut self, cx: &mut Context<Self>) {
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
DebugSessionState::Inert(_) => {}
|
|
||||||
DebugSessionState::Starting(_entity) => {} // todo(debugger): we need to shutdown the starting process in this case (or recreate it on a breakpoint being hit)
|
|
||||||
DebugSessionState::Failed(_) => {}
|
|
||||||
DebugSessionState::Running(state) => state.update(cx, |state, cx| state.shutdown(cx)),
|
DebugSessionState::Running(state) => state.update(cx, |state, cx| state.shutdown(cx)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,63 +91,29 @@ impl DebugSession {
|
||||||
&self.mode
|
&self.mode
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_inert_event(
|
pub(crate) fn label(&self, cx: &App) -> String {
|
||||||
&mut self,
|
let session_id = match &self.mode {
|
||||||
_: &Entity<InertState>,
|
DebugSessionState::Running(running_state) => running_state.read(cx).session_id(),
|
||||||
event: &InertEvent,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
let dap_store = self.dap_store.clone();
|
|
||||||
let InertEvent::Spawned { config } = event;
|
|
||||||
let config = config.clone();
|
|
||||||
|
|
||||||
self.debug_panel
|
|
||||||
.update(cx, |this, _| this.last_inert_config = Some(config.clone()))
|
|
||||||
.log_err();
|
|
||||||
|
|
||||||
let worktree = self
|
|
||||||
.worktree_store
|
|
||||||
.update(cx, |this, _| this.worktrees().next())
|
|
||||||
.ok()
|
|
||||||
.flatten()
|
|
||||||
.expect("worktree-less project");
|
|
||||||
let Ok((new_session_id, task)) = dap_store.update(cx, |store, cx| {
|
|
||||||
store.new_session(config.into(), &worktree, None, cx)
|
|
||||||
}) else {
|
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
let starting = cx.new(|cx| StartingState::new(new_session_id, task, cx));
|
let Ok(Some(session)) = self
|
||||||
|
.dap_store
|
||||||
self._subscriptions = [cx.subscribe_in(&starting, window, Self::on_starting_event)];
|
.read_with(cx, |store, _| store.session_by_id(session_id))
|
||||||
self.mode = DebugSessionState::Starting(starting);
|
else {
|
||||||
}
|
return "".to_owned();
|
||||||
|
|
||||||
fn on_starting_event(
|
|
||||||
&mut self,
|
|
||||||
_: &Entity<StartingState>,
|
|
||||||
event: &StartingEvent,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
if let StartingEvent::Finished(session) = event {
|
|
||||||
let mode =
|
|
||||||
cx.new(|cx| RunningState::new(session.clone(), self.workspace.clone(), window, cx));
|
|
||||||
self.mode = DebugSessionState::Running(mode);
|
|
||||||
} else if let StartingEvent::Failed = event {
|
|
||||||
self.mode = DebugSessionState::Failed(cx.new(FailedState::new));
|
|
||||||
};
|
};
|
||||||
cx.notify();
|
session
|
||||||
|
.read(cx)
|
||||||
|
.as_local()
|
||||||
|
.expect("Remote Debug Sessions are not implemented yet")
|
||||||
|
.label()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<DebugPanelItemEvent> for DebugSession {}
|
impl EventEmitter<DebugPanelItemEvent> for DebugSession {}
|
||||||
|
|
||||||
impl Focusable for DebugSession {
|
impl Focusable for DebugSession {
|
||||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
DebugSessionState::Inert(inert_state) => inert_state.focus_handle(cx),
|
|
||||||
DebugSessionState::Starting(starting_state) => starting_state.focus_handle(cx),
|
|
||||||
DebugSessionState::Failed(failed_state) => failed_state.focus_handle(cx),
|
|
||||||
DebugSessionState::Running(running_state) => running_state.focus_handle(cx),
|
DebugSessionState::Running(running_state) => running_state.focus_handle(cx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -213,61 +121,6 @@ impl Focusable for DebugSession {
|
||||||
|
|
||||||
impl Item for DebugSession {
|
impl Item for DebugSession {
|
||||||
type Event = DebugPanelItemEvent;
|
type Event = DebugPanelItemEvent;
|
||||||
fn tab_content(&self, _: item::TabContentParams, _: &Window, cx: &App) -> AnyElement {
|
|
||||||
let (icon, label, color) = match &self.mode {
|
|
||||||
DebugSessionState::Inert(_) => (None, "New Session", Color::Default),
|
|
||||||
DebugSessionState::Starting(_) => (None, "Starting", Color::Default),
|
|
||||||
DebugSessionState::Failed(_) => (
|
|
||||||
Some(Indicator::dot().color(Color::Error)),
|
|
||||||
"Failed",
|
|
||||||
Color::Error,
|
|
||||||
),
|
|
||||||
DebugSessionState::Running(state) => {
|
|
||||||
if state.read(cx).session().read(cx).is_terminated() {
|
|
||||||
(
|
|
||||||
Some(Indicator::dot().color(Color::Error)),
|
|
||||||
"Terminated",
|
|
||||||
Color::Error,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
match state.read(cx).thread_status(cx).unwrap_or_default() {
|
|
||||||
project::debugger::session::ThreadStatus::Stopped => (
|
|
||||||
Some(Indicator::dot().color(Color::Conflict)),
|
|
||||||
state
|
|
||||||
.read_with(cx, |state, cx| state.thread_status(cx))
|
|
||||||
.map(|status| status.label())
|
|
||||||
.unwrap_or("Stopped"),
|
|
||||||
Color::Conflict,
|
|
||||||
),
|
|
||||||
_ => (
|
|
||||||
Some(Indicator::dot().color(Color::Success)),
|
|
||||||
state
|
|
||||||
.read_with(cx, |state, cx| state.thread_status(cx))
|
|
||||||
.map(|status| status.label())
|
|
||||||
.unwrap_or("Running"),
|
|
||||||
Color::Success,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let is_starting = matches!(self.mode, DebugSessionState::Starting(_));
|
|
||||||
|
|
||||||
h_flex()
|
|
||||||
.gap_2()
|
|
||||||
.children(is_starting.then(|| {
|
|
||||||
Icon::new(IconName::ArrowCircle).with_animation(
|
|
||||||
"starting-debug-session",
|
|
||||||
Animation::new(Duration::from_secs(2)).repeat(),
|
|
||||||
|this, delta| this.transform(Transformation::rotate(percentage(delta))),
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
.when_some(icon, |this, indicator| this.child(indicator))
|
|
||||||
.justify_between()
|
|
||||||
.child(Label::new(label).color(color))
|
|
||||||
.into_any_element()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FollowableItem for DebugSession {
|
impl FollowableItem for DebugSession {
|
||||||
|
@ -339,15 +192,6 @@ impl FollowableItem for DebugSession {
|
||||||
impl Render for DebugSession {
|
impl Render for DebugSession {
|
||||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
DebugSessionState::Inert(inert_state) => {
|
|
||||||
inert_state.update(cx, |this, cx| this.render(window, cx).into_any_element())
|
|
||||||
}
|
|
||||||
DebugSessionState::Starting(starting_state) => {
|
|
||||||
starting_state.update(cx, |this, cx| this.render(window, cx).into_any_element())
|
|
||||||
}
|
|
||||||
DebugSessionState::Failed(failed_state) => {
|
|
||||||
failed_state.update(cx, |this, cx| this.render(window, cx).into_any_element())
|
|
||||||
}
|
|
||||||
DebugSessionState::Running(running_state) => {
|
DebugSessionState::Running(running_state) => {
|
||||||
running_state.update(cx, |this, cx| this.render(window, cx).into_any_element())
|
running_state.update(cx, |this, cx| this.render(window, cx).into_any_element())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
use gpui::{FocusHandle, Focusable};
|
|
||||||
use ui::{
|
|
||||||
Color, Context, IntoElement, Label, LabelCommon, ParentElement, Render, Styled, Window, h_flex,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub(crate) struct FailedState {
|
|
||||||
focus_handle: FocusHandle,
|
|
||||||
}
|
|
||||||
impl FailedState {
|
|
||||||
pub(super) fn new(cx: &mut Context<Self>) -> Self {
|
|
||||||
Self {
|
|
||||||
focus_handle: cx.focus_handle(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Focusable for FailedState {
|
|
||||||
fn focus_handle(&self, _: &ui::App) -> FocusHandle {
|
|
||||||
self.focus_handle.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Render for FailedState {
|
|
||||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
|
||||||
h_flex()
|
|
||||||
.size_full()
|
|
||||||
.items_center()
|
|
||||||
.justify_center()
|
|
||||||
.child(Label::new("Failed to spawn debugging session").color(Color::Error))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,337 +0,0 @@
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use dap::DebugRequestType;
|
|
||||||
use editor::{Editor, EditorElement, EditorStyle};
|
|
||||||
use gpui::{App, AppContext, Entity, EventEmitter, FocusHandle, Focusable, TextStyle, WeakEntity};
|
|
||||||
use settings::Settings as _;
|
|
||||||
use task::{DebugTaskDefinition, LaunchConfig, TCPHost};
|
|
||||||
use theme::ThemeSettings;
|
|
||||||
use ui::{
|
|
||||||
ActiveTheme as _, ButtonCommon, ButtonLike, Clickable, Context, ContextMenu, Disableable,
|
|
||||||
DropdownMenu, FluentBuilder, Icon, IconName, IconSize, InteractiveElement, IntoElement, Label,
|
|
||||||
LabelCommon, LabelSize, ParentElement, PopoverMenu, PopoverMenuHandle, Render, SharedString,
|
|
||||||
SplitButton, Styled, Window, div, h_flex, relative, v_flex,
|
|
||||||
};
|
|
||||||
use workspace::Workspace;
|
|
||||||
|
|
||||||
use crate::attach_modal::AttachModal;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
|
|
||||||
enum SpawnMode {
|
|
||||||
#[default]
|
|
||||||
Launch,
|
|
||||||
Attach,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SpawnMode {
|
|
||||||
fn label(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
SpawnMode::Launch => "Launch",
|
|
||||||
SpawnMode::Attach => "Attach",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<DebugRequestType> for SpawnMode {
|
|
||||||
fn from(request: DebugRequestType) -> Self {
|
|
||||||
match request {
|
|
||||||
DebugRequestType::Launch(_) => SpawnMode::Launch,
|
|
||||||
DebugRequestType::Attach(_) => SpawnMode::Attach,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct InertState {
|
|
||||||
focus_handle: FocusHandle,
|
|
||||||
selected_debugger: Option<SharedString>,
|
|
||||||
program_editor: Entity<Editor>,
|
|
||||||
cwd_editor: Entity<Editor>,
|
|
||||||
workspace: WeakEntity<Workspace>,
|
|
||||||
spawn_mode: SpawnMode,
|
|
||||||
popover_handle: PopoverMenuHandle<ContextMenu>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InertState {
|
|
||||||
pub(super) fn new(
|
|
||||||
workspace: WeakEntity<Workspace>,
|
|
||||||
default_cwd: &str,
|
|
||||||
debug_config: Option<DebugTaskDefinition>,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Self {
|
|
||||||
let selected_debugger = debug_config
|
|
||||||
.as_ref()
|
|
||||||
.map(|config| SharedString::from(config.adapter.clone()));
|
|
||||||
|
|
||||||
let spawn_mode = debug_config
|
|
||||||
.as_ref()
|
|
||||||
.map(|config| config.request.clone().into())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let program = debug_config
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|config| match &config.request {
|
|
||||||
DebugRequestType::Attach(_) => None,
|
|
||||||
DebugRequestType::Launch(launch_config) => Some(launch_config.program.clone()),
|
|
||||||
});
|
|
||||||
|
|
||||||
let program_editor = cx.new(|cx| {
|
|
||||||
let mut editor = Editor::single_line(window, cx);
|
|
||||||
if let Some(program) = program {
|
|
||||||
editor.insert(&program, window, cx);
|
|
||||||
} else {
|
|
||||||
editor.set_placeholder_text("Program path", cx);
|
|
||||||
}
|
|
||||||
editor
|
|
||||||
});
|
|
||||||
|
|
||||||
let cwd = debug_config
|
|
||||||
.and_then(|config| match &config.request {
|
|
||||||
DebugRequestType::Attach(_) => None,
|
|
||||||
DebugRequestType::Launch(launch_config) => launch_config.cwd.clone(),
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| PathBuf::from(default_cwd));
|
|
||||||
|
|
||||||
let cwd_editor = cx.new(|cx| {
|
|
||||||
let mut editor = Editor::single_line(window, cx);
|
|
||||||
editor.insert(cwd.to_str().unwrap_or_else(|| default_cwd), window, cx);
|
|
||||||
editor.set_placeholder_text("Working directory", cx);
|
|
||||||
editor
|
|
||||||
});
|
|
||||||
|
|
||||||
Self {
|
|
||||||
workspace,
|
|
||||||
cwd_editor,
|
|
||||||
program_editor,
|
|
||||||
selected_debugger,
|
|
||||||
spawn_mode,
|
|
||||||
focus_handle: cx.focus_handle(),
|
|
||||||
popover_handle: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Focusable for InertState {
|
|
||||||
fn focus_handle(&self, _cx: &App) -> FocusHandle {
|
|
||||||
self.focus_handle.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) enum InertEvent {
|
|
||||||
Spawned { config: DebugTaskDefinition },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventEmitter<InertEvent> for InertState {}
|
|
||||||
|
|
||||||
static SELECT_DEBUGGER_LABEL: SharedString = SharedString::new_static("Select Debugger");
|
|
||||||
|
|
||||||
impl Render for InertState {
|
|
||||||
fn render(
|
|
||||||
&mut self,
|
|
||||||
window: &mut ui::Window,
|
|
||||||
cx: &mut ui::Context<'_, Self>,
|
|
||||||
) -> impl ui::IntoElement {
|
|
||||||
let weak = cx.weak_entity();
|
|
||||||
let workspace = self.workspace.clone();
|
|
||||||
let disable_buttons = self.selected_debugger.is_none();
|
|
||||||
let spawn_button = ButtonLike::new_rounded_left("spawn-debug-session")
|
|
||||||
.child(Label::new(self.spawn_mode.label()).size(LabelSize::Small))
|
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
|
||||||
if this.spawn_mode == SpawnMode::Launch {
|
|
||||||
let program = this.program_editor.read(cx).text(cx);
|
|
||||||
let cwd = PathBuf::from(this.cwd_editor.read(cx).text(cx));
|
|
||||||
let kind = this
|
|
||||||
.selected_debugger
|
|
||||||
.as_deref()
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
unimplemented!(
|
|
||||||
"Automatic selection of a debugger based on users project"
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
cx.emit(InertEvent::Spawned {
|
|
||||||
config: DebugTaskDefinition {
|
|
||||||
label: "hard coded".into(),
|
|
||||||
adapter: kind,
|
|
||||||
request: DebugRequestType::Launch(LaunchConfig {
|
|
||||||
program,
|
|
||||||
cwd: Some(cwd),
|
|
||||||
args: Default::default(),
|
|
||||||
}),
|
|
||||||
tcp_connection: Some(TCPHost::default()),
|
|
||||||
initialize_args: None,
|
|
||||||
locator: None,
|
|
||||||
stop_on_entry: None,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.attach(window, cx)
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.disabled(disable_buttons);
|
|
||||||
|
|
||||||
v_flex()
|
|
||||||
.track_focus(&self.focus_handle)
|
|
||||||
.size_full()
|
|
||||||
.gap_1()
|
|
||||||
.p_2()
|
|
||||||
.child(
|
|
||||||
v_flex()
|
|
||||||
.gap_1()
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.w_full()
|
|
||||||
.gap_2()
|
|
||||||
.child(Self::render_editor(&self.program_editor, cx))
|
|
||||||
.child(
|
|
||||||
h_flex().child(DropdownMenu::new(
|
|
||||||
"dap-adapter-picker",
|
|
||||||
self.selected_debugger
|
|
||||||
.as_ref()
|
|
||||||
.unwrap_or_else(|| &SELECT_DEBUGGER_LABEL)
|
|
||||||
.clone(),
|
|
||||||
ContextMenu::build(window, cx, move |mut this, _, cx| {
|
|
||||||
let setter_for_name = |name: SharedString| {
|
|
||||||
let weak = weak.clone();
|
|
||||||
move |_: &mut Window, cx: &mut App| {
|
|
||||||
let name = name.clone();
|
|
||||||
weak.update(cx, move |this, cx| {
|
|
||||||
this.selected_debugger = Some(name.clone());
|
|
||||||
cx.notify();
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let available_adapters = workspace
|
|
||||||
.update(cx, |this, cx| {
|
|
||||||
this.project()
|
|
||||||
.read(cx)
|
|
||||||
.debug_adapters()
|
|
||||||
.enumerate_adapters()
|
|
||||||
})
|
|
||||||
.ok()
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
for adapter in available_adapters {
|
|
||||||
this = this.entry(
|
|
||||||
adapter.0.clone(),
|
|
||||||
None,
|
|
||||||
setter_for_name(adapter.0.clone()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this
|
|
||||||
}),
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.gap_2()
|
|
||||||
.child(Self::render_editor(&self.cwd_editor, cx))
|
|
||||||
.map(|this| {
|
|
||||||
let entity = cx.weak_entity();
|
|
||||||
this.child(SplitButton {
|
|
||||||
left: spawn_button,
|
|
||||||
right: PopoverMenu::new("debugger-select-spawn-mode")
|
|
||||||
.trigger(
|
|
||||||
ButtonLike::new_rounded_right(
|
|
||||||
"debugger-spawn-button-mode",
|
|
||||||
)
|
|
||||||
.layer(ui::ElevationIndex::ModalSurface)
|
|
||||||
.size(ui::ButtonSize::None)
|
|
||||||
.child(
|
|
||||||
div().px_1().child(
|
|
||||||
Icon::new(IconName::ChevronDownSmall)
|
|
||||||
.size(IconSize::XSmall),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.menu(move |window, cx| {
|
|
||||||
Some(ContextMenu::build(window, cx, {
|
|
||||||
let entity = entity.clone();
|
|
||||||
move |this, _, _| {
|
|
||||||
this.entry("Launch", None, {
|
|
||||||
let entity = entity.clone();
|
|
||||||
move |_, cx| {
|
|
||||||
let _ =
|
|
||||||
entity.update(cx, |this, cx| {
|
|
||||||
this.spawn_mode =
|
|
||||||
SpawnMode::Launch;
|
|
||||||
cx.notify();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.entry("Attach", None, {
|
|
||||||
let entity = entity.clone();
|
|
||||||
move |_, cx| {
|
|
||||||
let _ =
|
|
||||||
entity.update(cx, |this, cx| {
|
|
||||||
this.spawn_mode =
|
|
||||||
SpawnMode::Attach;
|
|
||||||
cx.notify();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
.with_handle(self.popover_handle.clone())
|
|
||||||
.into_any_element(),
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InertState {
|
|
||||||
fn render_editor(editor: &Entity<Editor>, cx: &Context<Self>) -> impl IntoElement {
|
|
||||||
let settings = ThemeSettings::get_global(cx);
|
|
||||||
let text_style = TextStyle {
|
|
||||||
color: cx.theme().colors().text,
|
|
||||||
font_family: settings.buffer_font.family.clone(),
|
|
||||||
font_features: settings.buffer_font.features.clone(),
|
|
||||||
font_size: settings.buffer_font_size(cx).into(),
|
|
||||||
font_weight: settings.buffer_font.weight,
|
|
||||||
line_height: relative(settings.buffer_line_height.value()),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
EditorElement::new(
|
|
||||||
editor,
|
|
||||||
EditorStyle {
|
|
||||||
background: cx.theme().colors().editor_background,
|
|
||||||
local_player: cx.theme().players().local(),
|
|
||||||
text: text_style,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn attach(&self, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
let kind = self
|
|
||||||
.selected_debugger
|
|
||||||
.as_deref()
|
|
||||||
.map(|s| s.to_string())
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
unimplemented!("Automatic selection of a debugger based on users project")
|
|
||||||
});
|
|
||||||
|
|
||||||
let config = DebugTaskDefinition {
|
|
||||||
label: "hard coded attach".into(),
|
|
||||||
adapter: kind,
|
|
||||||
request: DebugRequestType::Attach(task::AttachConfig { process_id: None }),
|
|
||||||
initialize_args: None,
|
|
||||||
locator: None,
|
|
||||||
tcp_connection: Some(TCPHost::default()),
|
|
||||||
stop_on_entry: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = self.workspace.update(cx, |workspace, cx| {
|
|
||||||
let project = workspace.project().clone();
|
|
||||||
workspace.toggle_modal(window, cx, |window, cx| {
|
|
||||||
AttachModal::new(project, config, window, cx)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,10 +15,9 @@ use rpc::proto::ViewId;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use stack_frame_list::StackFrameList;
|
use stack_frame_list::StackFrameList;
|
||||||
use ui::{
|
use ui::{
|
||||||
ActiveTheme, AnyElement, App, Button, ButtonCommon, Clickable, Context, ContextMenu,
|
ActiveTheme, AnyElement, App, Button, Context, ContextMenu, DropdownMenu, FluentBuilder,
|
||||||
Disableable, Divider, DropdownMenu, FluentBuilder, IconButton, IconName, IconSize, Indicator,
|
Indicator, InteractiveElement, IntoElement, ParentElement, Render, SharedString,
|
||||||
InteractiveElement, IntoElement, Label, ParentElement, Render, SharedString,
|
StatefulInteractiveElement, Styled, Window, div, h_flex, v_flex,
|
||||||
StatefulInteractiveElement, Styled, Tooltip, Window, div, h_flex, v_flex,
|
|
||||||
};
|
};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use variable_list::VariableList;
|
use variable_list::VariableList;
|
||||||
|
@ -42,7 +41,7 @@ pub struct RunningState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for RunningState {
|
impl Render for RunningState {
|
||||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let threads = self.session.update(cx, |this, cx| this.threads(cx));
|
let threads = self.session.update(cx, |this, cx| this.threads(cx));
|
||||||
self.select_current_thread(&threads, cx);
|
self.select_current_thread(&threads, cx);
|
||||||
|
|
||||||
|
@ -51,255 +50,27 @@ impl Render for RunningState {
|
||||||
.map(|thread_id| self.session.read(cx).thread_status(thread_id))
|
.map(|thread_id| self.session.read(cx).thread_status(thread_id))
|
||||||
.unwrap_or(ThreadStatus::Exited);
|
.unwrap_or(ThreadStatus::Exited);
|
||||||
|
|
||||||
let selected_thread_name = threads
|
|
||||||
.iter()
|
|
||||||
.find(|(thread, _)| self.thread_id.map(|id| id.0) == Some(thread.id))
|
|
||||||
.map(|(thread, _)| thread.name.clone())
|
|
||||||
.unwrap_or("Threads".to_owned());
|
|
||||||
|
|
||||||
self.variable_list.update(cx, |this, cx| {
|
self.variable_list.update(cx, |this, cx| {
|
||||||
this.disabled(thread_status != ThreadStatus::Stopped, cx);
|
this.disabled(thread_status != ThreadStatus::Stopped, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
let active_thread_item = &self.active_thread_item;
|
let active_thread_item = &self.active_thread_item;
|
||||||
|
|
||||||
let has_no_threads = threads.is_empty();
|
|
||||||
let capabilities = self.capabilities(cx);
|
let capabilities = self.capabilities(cx);
|
||||||
let state = cx.entity();
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.key_context("DebugPanelItem")
|
.key_context("DebugPanelItem")
|
||||||
.track_focus(&self.focus_handle(cx))
|
.track_focus(&self.focus_handle(cx))
|
||||||
.size_full()
|
.size_full()
|
||||||
.items_start()
|
.items_start()
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex().size_full().items_start().child(
|
||||||
.size_full()
|
h_flex()
|
||||||
.items_start()
|
.size_full()
|
||||||
.child(
|
.items_start()
|
||||||
h_flex()
|
.p_1()
|
||||||
.w_full()
|
.gap_4()
|
||||||
.border_b_1()
|
.child(self.stack_frame_list.clone()),
|
||||||
.border_color(cx.theme().colors().border_variant)
|
),
|
||||||
.justify_between()
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.px_1()
|
|
||||||
.py_0p5()
|
|
||||||
.w_full()
|
|
||||||
.gap_1()
|
|
||||||
.map(|this| {
|
|
||||||
if thread_status == ThreadStatus::Running {
|
|
||||||
this.child(
|
|
||||||
IconButton::new(
|
|
||||||
"debug-pause",
|
|
||||||
IconName::DebugPause,
|
|
||||||
)
|
|
||||||
.icon_size(IconSize::XSmall)
|
|
||||||
.on_click(cx.listener(|this, _, _window, cx| {
|
|
||||||
this.pause_thread(cx);
|
|
||||||
}))
|
|
||||||
.tooltip(move |window, cx| {
|
|
||||||
Tooltip::text("Pause program")(window, cx)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
this.child(
|
|
||||||
IconButton::new(
|
|
||||||
"debug-continue",
|
|
||||||
IconName::DebugContinue,
|
|
||||||
)
|
|
||||||
.icon_size(IconSize::XSmall)
|
|
||||||
.on_click(cx.listener(|this, _, _window, cx| {
|
|
||||||
this.continue_thread(cx)
|
|
||||||
}))
|
|
||||||
.disabled(thread_status != ThreadStatus::Stopped)
|
|
||||||
.tooltip(move |window, cx| {
|
|
||||||
Tooltip::text("Continue program")(window, cx)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.child(
|
|
||||||
IconButton::new("debug-restart", IconName::DebugRestart)
|
|
||||||
.icon_size(IconSize::XSmall)
|
|
||||||
.on_click(cx.listener(|this, _, _window, cx| {
|
|
||||||
this.restart_session(cx);
|
|
||||||
}))
|
|
||||||
.disabled(
|
|
||||||
!capabilities
|
|
||||||
.supports_restart_request
|
|
||||||
.unwrap_or_default(),
|
|
||||||
)
|
|
||||||
.tooltip(move |window, cx| {
|
|
||||||
Tooltip::text("Restart")(window, cx)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
IconButton::new("debug-stop", IconName::DebugStop)
|
|
||||||
.icon_size(IconSize::XSmall)
|
|
||||||
.on_click(cx.listener(|this, _, _window, cx| {
|
|
||||||
this.stop_thread(cx);
|
|
||||||
}))
|
|
||||||
.disabled(
|
|
||||||
thread_status != ThreadStatus::Stopped
|
|
||||||
&& thread_status != ThreadStatus::Running,
|
|
||||||
)
|
|
||||||
.tooltip({
|
|
||||||
let label = if capabilities
|
|
||||||
.supports_terminate_threads_request
|
|
||||||
.unwrap_or_default()
|
|
||||||
{
|
|
||||||
"Terminate Thread"
|
|
||||||
} else {
|
|
||||||
"Terminate all Threads"
|
|
||||||
};
|
|
||||||
move |window, cx| Tooltip::text(label)(window, cx)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
IconButton::new(
|
|
||||||
"debug-disconnect",
|
|
||||||
IconName::DebugDisconnect,
|
|
||||||
)
|
|
||||||
.icon_size(IconSize::XSmall)
|
|
||||||
.on_click(cx.listener(|this, _, _window, cx| {
|
|
||||||
this.disconnect_client(cx);
|
|
||||||
}))
|
|
||||||
.disabled(
|
|
||||||
thread_status == ThreadStatus::Exited
|
|
||||||
|| thread_status == ThreadStatus::Ended,
|
|
||||||
)
|
|
||||||
.tooltip(Tooltip::text("Disconnect")),
|
|
||||||
)
|
|
||||||
.child(Divider::vertical())
|
|
||||||
.when(
|
|
||||||
capabilities.supports_step_back.unwrap_or(false),
|
|
||||||
|this| {
|
|
||||||
this.child(
|
|
||||||
IconButton::new(
|
|
||||||
"debug-step-back",
|
|
||||||
IconName::DebugStepBack,
|
|
||||||
)
|
|
||||||
.icon_size(IconSize::XSmall)
|
|
||||||
.on_click(cx.listener(|this, _, _window, cx| {
|
|
||||||
this.step_back(cx);
|
|
||||||
}))
|
|
||||||
.disabled(thread_status != ThreadStatus::Stopped)
|
|
||||||
.tooltip(move |window, cx| {
|
|
||||||
Tooltip::text("Step back")(window, cx)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
IconButton::new("debug-step-over", IconName::DebugStepOver)
|
|
||||||
.icon_size(IconSize::XSmall)
|
|
||||||
.on_click(cx.listener(|this, _, _window, cx| {
|
|
||||||
this.step_over(cx);
|
|
||||||
}))
|
|
||||||
.disabled(thread_status != ThreadStatus::Stopped)
|
|
||||||
.tooltip(move |window, cx| {
|
|
||||||
Tooltip::text("Step over")(window, cx)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
IconButton::new("debug-step-in", IconName::DebugStepInto)
|
|
||||||
.icon_size(IconSize::XSmall)
|
|
||||||
.on_click(cx.listener(|this, _, _window, cx| {
|
|
||||||
this.step_in(cx);
|
|
||||||
}))
|
|
||||||
.disabled(thread_status != ThreadStatus::Stopped)
|
|
||||||
.tooltip(move |window, cx| {
|
|
||||||
Tooltip::text("Step in")(window, cx)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
IconButton::new("debug-step-out", IconName::DebugStepOut)
|
|
||||||
.icon_size(IconSize::XSmall)
|
|
||||||
.on_click(cx.listener(|this, _, _window, cx| {
|
|
||||||
this.step_out(cx);
|
|
||||||
}))
|
|
||||||
.disabled(thread_status != ThreadStatus::Stopped)
|
|
||||||
.tooltip(move |window, cx| {
|
|
||||||
Tooltip::text("Step out")(window, cx)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(Divider::vertical())
|
|
||||||
.child(
|
|
||||||
IconButton::new(
|
|
||||||
"debug-ignore-breakpoints",
|
|
||||||
if self.session.read(cx).breakpoints_enabled() {
|
|
||||||
IconName::DebugBreakpoint
|
|
||||||
} else {
|
|
||||||
IconName::DebugIgnoreBreakpoints
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.icon_size(IconSize::XSmall)
|
|
||||||
.on_click(cx.listener(|this, _, _window, cx| {
|
|
||||||
this.toggle_ignore_breakpoints(cx);
|
|
||||||
}))
|
|
||||||
.disabled(
|
|
||||||
thread_status == ThreadStatus::Exited
|
|
||||||
|| thread_status == ThreadStatus::Ended,
|
|
||||||
)
|
|
||||||
.tooltip(
|
|
||||||
move |window, cx| {
|
|
||||||
Tooltip::text("Ignore breakpoints")(window, cx)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.px_1()
|
|
||||||
.py_0p5()
|
|
||||||
.gap_2()
|
|
||||||
.w_3_4()
|
|
||||||
.justify_end()
|
|
||||||
.child(Label::new("Thread:"))
|
|
||||||
.child(
|
|
||||||
DropdownMenu::new(
|
|
||||||
("thread-list", self.session_id.0),
|
|
||||||
selected_thread_name,
|
|
||||||
ContextMenu::build(
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
move |mut this, _, _| {
|
|
||||||
for (thread, _) in threads {
|
|
||||||
let state = state.clone();
|
|
||||||
let thread_id = thread.id;
|
|
||||||
this = this.entry(
|
|
||||||
thread.name,
|
|
||||||
None,
|
|
||||||
move |_, cx| {
|
|
||||||
state.update(cx, |state, cx| {
|
|
||||||
state.select_thread(
|
|
||||||
ThreadId(thread_id),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.disabled(
|
|
||||||
has_no_threads
|
|
||||||
|| thread_status != ThreadStatus::Stopped,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.size_full()
|
|
||||||
.items_start()
|
|
||||||
.p_1()
|
|
||||||
.gap_4()
|
|
||||||
.child(self.stack_frame_list.clone()),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
|
@ -450,37 +221,32 @@ impl RunningState {
|
||||||
self.session_id
|
self.session_id
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(test)]
|
||||||
pub fn set_thread_item(&mut self, thread_item: ThreadItem, cx: &mut Context<Self>) {
|
pub fn set_thread_item(&mut self, thread_item: ThreadItem, cx: &mut Context<Self>) {
|
||||||
self.active_thread_item = thread_item;
|
self.active_thread_item = thread_item;
|
||||||
cx.notify()
|
cx.notify()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(test)]
|
||||||
pub fn stack_frame_list(&self) -> &Entity<StackFrameList> {
|
pub fn stack_frame_list(&self) -> &Entity<StackFrameList> {
|
||||||
&self.stack_frame_list
|
&self.stack_frame_list
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(test)]
|
||||||
pub fn console(&self) -> &Entity<Console> {
|
pub fn console(&self) -> &Entity<Console> {
|
||||||
&self.console
|
&self.console
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(test)]
|
||||||
pub fn module_list(&self) -> &Entity<ModuleList> {
|
pub(crate) fn module_list(&self) -> &Entity<ModuleList> {
|
||||||
&self.module_list
|
&self.module_list
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(test)]
|
||||||
pub fn variable_list(&self) -> &Entity<VariableList> {
|
pub(crate) fn variable_list(&self) -> &Entity<VariableList> {
|
||||||
&self.variable_list
|
&self.variable_list
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
pub fn are_breakpoints_ignored(&self, cx: &App) -> bool {
|
|
||||||
self.session.read(cx).ignore_breakpoints()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn capabilities(&self, cx: &App) -> Capabilities {
|
pub fn capabilities(&self, cx: &App) -> Capabilities {
|
||||||
self.session().read(cx).capabilities().clone()
|
self.session().read(cx).capabilities().clone()
|
||||||
}
|
}
|
||||||
|
@ -504,8 +270,8 @@ impl RunningState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(test)]
|
||||||
pub fn selected_thread_id(&self) -> Option<ThreadId> {
|
pub(crate) fn selected_thread_id(&self) -> Option<ThreadId> {
|
||||||
self.thread_id
|
self.thread_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -583,7 +349,7 @@ impl RunningState {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn step_in(&mut self, cx: &mut Context<Self>) {
|
pub(crate) fn step_in(&mut self, cx: &mut Context<Self>) {
|
||||||
let Some(thread_id) = self.thread_id else {
|
let Some(thread_id) = self.thread_id else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -595,7 +361,7 @@ impl RunningState {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn step_out(&mut self, cx: &mut Context<Self>) {
|
pub(crate) fn step_out(&mut self, cx: &mut Context<Self>) {
|
||||||
let Some(thread_id) = self.thread_id else {
|
let Some(thread_id) = self.thread_id else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -607,7 +373,7 @@ impl RunningState {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn step_back(&mut self, cx: &mut Context<Self>) {
|
pub(crate) fn step_back(&mut self, cx: &mut Context<Self>) {
|
||||||
let Some(thread_id) = self.thread_id else {
|
let Some(thread_id) = self.thread_id else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -675,6 +441,10 @@ impl RunningState {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
unused,
|
||||||
|
reason = "Support for disconnecting a client is not wired through yet"
|
||||||
|
)]
|
||||||
pub fn disconnect_client(&self, cx: &mut Context<Self>) {
|
pub fn disconnect_client(&self, cx: &mut Context<Self>) {
|
||||||
self.session().update(cx, |state, cx| {
|
self.session().update(cx, |state, cx| {
|
||||||
state.disconnect_client(cx);
|
state.disconnect_client(cx);
|
||||||
|
@ -686,6 +456,36 @@ impl RunningState {
|
||||||
session.toggle_ignore_breakpoints(cx).detach();
|
session.toggle_ignore_breakpoints(cx).detach();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn thread_dropdown(
|
||||||
|
&self,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<'_, RunningState>,
|
||||||
|
) -> DropdownMenu {
|
||||||
|
let state = cx.entity();
|
||||||
|
let threads = self.session.update(cx, |this, cx| this.threads(cx));
|
||||||
|
let selected_thread_name = threads
|
||||||
|
.iter()
|
||||||
|
.find(|(thread, _)| self.thread_id.map(|id| id.0) == Some(thread.id))
|
||||||
|
.map(|(thread, _)| thread.name.clone())
|
||||||
|
.unwrap_or("Threads".to_owned());
|
||||||
|
DropdownMenu::new(
|
||||||
|
("thread-list", self.session_id.0),
|
||||||
|
selected_thread_name,
|
||||||
|
ContextMenu::build(window, cx, move |mut this, _, _| {
|
||||||
|
for (thread, _) in threads {
|
||||||
|
let state = state.clone();
|
||||||
|
let thread_id = thread.id;
|
||||||
|
this = this.entry(thread.name, None, move |_, cx| {
|
||||||
|
state.update(cx, |state, cx| {
|
||||||
|
state.select_thread(ThreadId(thread_id), cx);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<DebugPanelItemEvent> for RunningState {}
|
impl EventEmitter<DebugPanelItemEvent> for RunningState {}
|
||||||
|
|
|
@ -85,16 +85,11 @@ impl Console {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(test)]
|
||||||
pub fn editor(&self) -> &Entity<Editor> {
|
pub(crate) fn editor(&self) -> &Entity<Editor> {
|
||||||
&self.console
|
&self.console
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
pub fn query_bar(&self) -> &Entity<Editor> {
|
|
||||||
&self.query_bar
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_local(&self, cx: &Context<Self>) -> bool {
|
fn is_local(&self, cx: &Context<Self>) -> bool {
|
||||||
self.session.read(cx).is_local()
|
self.session.read(cx).is_local()
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,11 +147,9 @@ impl ModuleList {
|
||||||
)
|
)
|
||||||
.into_any()
|
.into_any()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(test)]
|
||||||
impl ModuleList {
|
pub(crate) fn modules(&self, cx: &mut Context<Self>) -> Vec<dap::Module> {
|
||||||
pub fn modules(&self, cx: &mut Context<Self>) -> Vec<dap::Module> {
|
|
||||||
self.session
|
self.session
|
||||||
.update(cx, |session, cx| session.modules(cx).to_vec())
|
.update(cx, |session, cx| session.modules(cx).to_vec())
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,13 +87,13 @@ impl StackFrameList {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(test)]
|
||||||
pub fn entries(&self) -> &Vec<StackFrameEntry> {
|
pub(crate) fn entries(&self) -> &Vec<StackFrameEntry> {
|
||||||
&self.entries
|
&self.entries
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(test)]
|
||||||
pub fn flatten_entries(&self) -> Vec<dap::StackFrame> {
|
pub(crate) fn flatten_entries(&self) -> Vec<dap::StackFrame> {
|
||||||
self.entries
|
self.entries
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|frame| match frame {
|
.flat_map(|frame| match frame {
|
||||||
|
@ -115,8 +115,8 @@ impl StackFrameList {
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(test)]
|
||||||
pub fn dap_stack_frames(&self, cx: &mut App) -> Vec<dap::StackFrame> {
|
pub(crate) fn dap_stack_frames(&self, cx: &mut App) -> Vec<dap::StackFrame> {
|
||||||
self.stack_frames(cx)
|
self.stack_frames(cx)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|stack_frame| stack_frame.dap.clone())
|
.map(|stack_frame| stack_frame.dap.clone())
|
||||||
|
|
|
@ -540,8 +540,8 @@ impl VariableList {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(test)]
|
||||||
pub fn assert_visual_entries(&self, expected: Vec<&str>) {
|
pub(crate) fn assert_visual_entries(&self, expected: Vec<&str>) {
|
||||||
const INDENT: &'static str = " ";
|
const INDENT: &'static str = " ";
|
||||||
|
|
||||||
let entries = &self.entries;
|
let entries = &self.entries;
|
||||||
|
@ -569,8 +569,8 @@ impl VariableList {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(test)]
|
||||||
pub fn scopes(&self) -> Vec<dap::Scope> {
|
pub(crate) fn scopes(&self) -> Vec<dap::Scope> {
|
||||||
self.entries
|
self.entries
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|entry| match &entry.dap_kind {
|
.filter_map(|entry| match &entry.dap_kind {
|
||||||
|
@ -582,8 +582,8 @@ impl VariableList {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(test)]
|
||||||
pub fn variables_per_scope(&self) -> Vec<(dap::Scope, Vec<dap::Variable>)> {
|
pub(crate) fn variables_per_scope(&self) -> Vec<(dap::Scope, Vec<dap::Variable>)> {
|
||||||
let mut scopes: Vec<(dap::Scope, Vec<_>)> = Vec::new();
|
let mut scopes: Vec<(dap::Scope, Vec<_>)> = Vec::new();
|
||||||
let mut idx = 0;
|
let mut idx = 0;
|
||||||
|
|
||||||
|
@ -604,8 +604,8 @@ impl VariableList {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(test)]
|
||||||
pub fn variables(&self) -> Vec<dap::Variable> {
|
pub(crate) fn variables(&self) -> Vec<dap::Variable> {
|
||||||
self.entries
|
self.entries
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|entry| match &entry.dap_kind {
|
.filter_map(|entry| match &entry.dap_kind {
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
use dap::client::SessionId;
|
|
||||||
use gpui::{
|
|
||||||
Animation, AnimationExt, Entity, EventEmitter, FocusHandle, Focusable, Task, Transformation,
|
|
||||||
percentage,
|
|
||||||
};
|
|
||||||
use project::debugger::session::Session;
|
|
||||||
use ui::{Color, Context, Icon, IconName, IntoElement, ParentElement, Render, Styled, v_flex};
|
|
||||||
|
|
||||||
pub(crate) struct StartingState {
|
|
||||||
focus_handle: FocusHandle,
|
|
||||||
pub(super) session_id: SessionId,
|
|
||||||
_notify_parent: Task<()>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) enum StartingEvent {
|
|
||||||
Failed,
|
|
||||||
Finished(Entity<Session>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventEmitter<StartingEvent> for StartingState {}
|
|
||||||
|
|
||||||
impl StartingState {
|
|
||||||
pub(crate) fn new(
|
|
||||||
session_id: SessionId,
|
|
||||||
task: Task<Result<Entity<Session>>>,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Self {
|
|
||||||
let _notify_parent = cx.spawn(async move |this, cx| {
|
|
||||||
let entity = task.await;
|
|
||||||
|
|
||||||
this.update(cx, |_, cx| {
|
|
||||||
if let Ok(entity) = entity {
|
|
||||||
cx.emit(StartingEvent::Finished(entity))
|
|
||||||
} else {
|
|
||||||
cx.emit(StartingEvent::Failed)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
});
|
|
||||||
Self {
|
|
||||||
session_id,
|
|
||||||
focus_handle: cx.focus_handle(),
|
|
||||||
_notify_parent,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Focusable for StartingState {
|
|
||||||
fn focus_handle(&self, _: &ui::App) -> FocusHandle {
|
|
||||||
self.focus_handle.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for StartingState {
|
|
||||||
fn render(
|
|
||||||
&mut self,
|
|
||||||
_window: &mut ui::Window,
|
|
||||||
_cx: &mut ui::Context<'_, Self>,
|
|
||||||
) -> impl ui::IntoElement {
|
|
||||||
v_flex()
|
|
||||||
.size_full()
|
|
||||||
.gap_1()
|
|
||||||
.items_center()
|
|
||||||
.child("Starting a debug adapter")
|
|
||||||
.child(
|
|
||||||
Icon::new(IconName::ArrowCircle)
|
|
||||||
.color(Color::Info)
|
|
||||||
.with_animation(
|
|
||||||
"arrow-circle",
|
|
||||||
Animation::new(Duration::from_secs(2)).repeat(),
|
|
||||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
|
||||||
)
|
|
||||||
.into_any_element(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -109,6 +109,7 @@ async fn test_show_attach_modal_and_select_process(
|
||||||
command: vec![],
|
command: vec![],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
true,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
|
|
@ -99,8 +99,8 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test
|
||||||
|
|
||||||
debug_panel.update(cx, |this, cx| {
|
debug_panel.update(cx, |this, cx| {
|
||||||
assert!(this.active_session(cx).is_some());
|
assert!(this.active_session(cx).is_some());
|
||||||
// we have one active session and one inert item
|
// we have one active session
|
||||||
assert_eq!(2, this.pane().unwrap().read(cx).items_len());
|
assert_eq!(1, this.pane().unwrap().read(cx).items_len());
|
||||||
assert!(running_state.read(cx).selected_thread_id().is_none());
|
assert!(running_state.read(cx).selected_thread_id().is_none());
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -135,9 +135,9 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test
|
||||||
.clone()
|
.clone()
|
||||||
});
|
});
|
||||||
|
|
||||||
// we have one active session and one inert item
|
// we have one active session
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
2,
|
1,
|
||||||
debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len())
|
debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len())
|
||||||
);
|
);
|
||||||
assert_eq!(client.id(), running_state.read(cx).session_id());
|
assert_eq!(client.id(), running_state.read(cx).session_id());
|
||||||
|
@ -175,7 +175,7 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test
|
||||||
|
|
||||||
debug_panel.update(cx, |this, cx| {
|
debug_panel.update(cx, |this, cx| {
|
||||||
assert!(this.active_session(cx).is_some());
|
assert!(this.active_session(cx).is_some());
|
||||||
assert_eq!(2, this.pane().unwrap().read(cx).items_len());
|
assert_eq!(1, this.pane().unwrap().read(cx).items_len());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ThreadId(1),
|
ThreadId(1),
|
||||||
running_state.read(cx).selected_thread_id().unwrap()
|
running_state.read(cx).selected_thread_id().unwrap()
|
||||||
|
@ -245,8 +245,8 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
|
||||||
|
|
||||||
debug_panel.update(cx, |this, cx| {
|
debug_panel.update(cx, |this, cx| {
|
||||||
assert!(this.active_session(cx).is_some());
|
assert!(this.active_session(cx).is_some());
|
||||||
// we have one active session and one inert item
|
// we have one active session
|
||||||
assert_eq!(2, this.pane().unwrap().read(cx).items_len());
|
assert_eq!(1, this.pane().unwrap().read(cx).items_len());
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -281,9 +281,9 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
|
||||||
.clone()
|
.clone()
|
||||||
});
|
});
|
||||||
|
|
||||||
// we have one active session and one inert item
|
// we have one active session
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
2,
|
1,
|
||||||
debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len())
|
debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len())
|
||||||
);
|
);
|
||||||
assert_eq!(client.id(), active_session.read(cx).session_id(cx).unwrap());
|
assert_eq!(client.id(), active_session.read(cx).session_id(cx).unwrap());
|
||||||
|
@ -323,9 +323,9 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
|
||||||
.clone()
|
.clone()
|
||||||
});
|
});
|
||||||
|
|
||||||
// we have one active session and one inert item
|
// we have one active session
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
2,
|
1,
|
||||||
debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len())
|
debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len())
|
||||||
);
|
);
|
||||||
assert_eq!(client.id(), active_session.read(cx).session_id(cx).unwrap());
|
assert_eq!(client.id(), active_session.read(cx).session_id(cx).unwrap());
|
||||||
|
@ -362,7 +362,7 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
|
||||||
|
|
||||||
debug_panel.update(cx, |this, cx| {
|
debug_panel.update(cx, |this, cx| {
|
||||||
assert!(this.active_session(cx).is_some());
|
assert!(this.active_session(cx).is_some());
|
||||||
assert_eq!(2, this.pane().unwrap().read(cx).items_len());
|
assert_eq!(1, this.pane().unwrap().read(cx).items_len());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ThreadId(1),
|
ThreadId(1),
|
||||||
running_state.read(cx).selected_thread_id().unwrap()
|
running_state.read(cx).selected_thread_id().unwrap()
|
||||||
|
@ -1447,7 +1447,7 @@ async fn test_unsetting_breakpoints_on_clear_breakpoint_action(
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
cx.dispatch_action(workspace::ClearAllBreakpoints);
|
cx.dispatch_action(crate::ClearAllBreakpoints);
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
|
|
||||||
let shutdown_session = project.update(cx, |project, cx| {
|
let shutdown_session = project.update(cx, |project, cx| {
|
||||||
|
|
|
@ -23,6 +23,7 @@ pub enum IconName {
|
||||||
ArrowCircle,
|
ArrowCircle,
|
||||||
ArrowDown,
|
ArrowDown,
|
||||||
ArrowDownFromLine,
|
ArrowDownFromLine,
|
||||||
|
ArrowDownRight,
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
ArrowRightLeft,
|
ArrowRightLeft,
|
||||||
|
@ -44,6 +45,7 @@ pub enum IconName {
|
||||||
BookCopy,
|
BookCopy,
|
||||||
BookPlus,
|
BookPlus,
|
||||||
Brain,
|
Brain,
|
||||||
|
BugOff,
|
||||||
CaseSensitive,
|
CaseSensitive,
|
||||||
Check,
|
Check,
|
||||||
CheckDouble,
|
CheckDouble,
|
||||||
|
@ -55,6 +57,7 @@ pub enum IconName {
|
||||||
ChevronUp,
|
ChevronUp,
|
||||||
ChevronUpDown,
|
ChevronUpDown,
|
||||||
Circle,
|
Circle,
|
||||||
|
CircleOff,
|
||||||
Clipboard,
|
Clipboard,
|
||||||
Close,
|
Close,
|
||||||
Code,
|
Code,
|
||||||
|
@ -166,6 +169,7 @@ pub enum IconName {
|
||||||
Play,
|
Play,
|
||||||
Plus,
|
Plus,
|
||||||
PocketKnife,
|
PocketKnife,
|
||||||
|
Power,
|
||||||
Public,
|
Public,
|
||||||
PullRequest,
|
PullRequest,
|
||||||
Quote,
|
Quote,
|
||||||
|
|
|
@ -48,6 +48,7 @@ use worktree::Worktree;
|
||||||
|
|
||||||
pub enum DapStoreEvent {
|
pub enum DapStoreEvent {
|
||||||
DebugClientStarted(SessionId),
|
DebugClientStarted(SessionId),
|
||||||
|
DebugSessionInitialized(SessionId),
|
||||||
DebugClientShutdown(SessionId),
|
DebugClientShutdown(SessionId),
|
||||||
DebugClientEvent {
|
DebugClientEvent {
|
||||||
session_id: SessionId,
|
session_id: SessionId,
|
||||||
|
@ -862,6 +863,10 @@ fn create_new_session(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.update(cx, |_, cx| {
|
||||||
|
cx.emit(DapStoreEvent::DebugSessionInitialized(session_id));
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(session)
|
Ok(session)
|
||||||
});
|
});
|
||||||
task
|
task
|
||||||
|
|
|
@ -537,7 +537,11 @@ impl LocalMode {
|
||||||
Ok((adapter, binary))
|
Ok((adapter, binary))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn initialize_sequence(
|
pub fn label(&self) -> String {
|
||||||
|
self.config.label.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initialize_sequence(
|
||||||
&self,
|
&self,
|
||||||
capabilities: &Capabilities,
|
capabilities: &Capabilities,
|
||||||
initialized_rx: oneshot::Receiver<()>,
|
initialized_rx: oneshot::Receiver<()>,
|
||||||
|
|
|
@ -125,6 +125,22 @@ impl Inventory {
|
||||||
cx.new(|_| Self::default())
|
cx.new(|_| Self::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn list_debug_tasks(&self) -> Vec<&TaskTemplate> {
|
||||||
|
self.templates_from_settings
|
||||||
|
.worktree
|
||||||
|
.values()
|
||||||
|
.flat_map(|tasks| {
|
||||||
|
tasks.iter().filter_map(|(kind, tasks)| {
|
||||||
|
if matches!(kind.1, TaskKind::Debug) {
|
||||||
|
Some(tasks)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
/// Pulls its task sources relevant to the worktree and the language given,
|
/// Pulls its task sources relevant to the worktree and the language given,
|
||||||
/// returns all task templates with their source kinds, worktree tasks first, language tasks second
|
/// returns all task templates with their source kinds, worktree tasks first, language tasks second
|
||||||
/// and global tasks last. No specific order inside source kinds groups.
|
/// and global tasks last. No specific order inside source kinds groups.
|
||||||
|
|
|
@ -41,7 +41,6 @@ impl TCPHost {
|
||||||
#[derive(Default, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
|
#[derive(Default, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
|
||||||
pub struct AttachConfig {
|
pub struct AttachConfig {
|
||||||
/// The processId to attach to, if left empty we will show a process picker
|
/// The processId to attach to, if left empty we will show a process picker
|
||||||
#[serde(default)]
|
|
||||||
pub process_id: Option<u32>,
|
pub process_id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +51,8 @@ pub struct LaunchConfig {
|
||||||
pub program: String,
|
pub program: String,
|
||||||
/// The current working directory of your project
|
/// The current working directory of your project
|
||||||
pub cwd: Option<PathBuf>,
|
pub cwd: Option<PathBuf>,
|
||||||
/// Args to pass to a debuggee
|
/// Arguments to pass to a debuggee
|
||||||
|
#[serde(default)]
|
||||||
pub args: Vec<String>,
|
pub args: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +66,17 @@ pub enum DebugRequestType {
|
||||||
Attach(AttachConfig),
|
Attach(AttachConfig),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<LaunchConfig> for DebugRequestType {
|
||||||
|
fn from(launch_config: LaunchConfig) -> Self {
|
||||||
|
DebugRequestType::Launch(launch_config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AttachConfig> for DebugRequestType {
|
||||||
|
fn from(attach_config: AttachConfig) -> Self {
|
||||||
|
DebugRequestType::Attach(attach_config)
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Represents a request for starting the debugger.
|
/// Represents a request for starting the debugger.
|
||||||
/// Contrary to `DebugRequestType`, `DebugRequestDisposition` is not Serializable.
|
/// Contrary to `DebugRequestType`, `DebugRequestDisposition` is not Serializable.
|
||||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||||
|
@ -144,6 +155,37 @@ impl TryFrom<DebugAdapterConfig> for DebugTaskDefinition {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<TaskTemplate> for DebugTaskDefinition {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(value: TaskTemplate) -> Result<Self, Self::Error> {
|
||||||
|
let TaskType::Debug(debug_args) = value.task_type else {
|
||||||
|
return Err(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let request = match debug_args.request {
|
||||||
|
crate::DebugArgsRequest::Launch => DebugRequestType::Launch(LaunchConfig {
|
||||||
|
program: value.command,
|
||||||
|
cwd: value.cwd.map(PathBuf::from),
|
||||||
|
args: value.args,
|
||||||
|
}),
|
||||||
|
crate::DebugArgsRequest::Attach(attach_config) => {
|
||||||
|
DebugRequestType::Attach(attach_config)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(DebugTaskDefinition {
|
||||||
|
adapter: debug_args.adapter,
|
||||||
|
request,
|
||||||
|
label: value.label,
|
||||||
|
initialize_args: debug_args.initialize_args,
|
||||||
|
tcp_connection: debug_args.tcp_connection,
|
||||||
|
locator: debug_args.locator,
|
||||||
|
stop_on_entry: debug_args.stop_on_entry,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl DebugTaskDefinition {
|
impl DebugTaskDefinition {
|
||||||
/// Translate from debug definition to a task template
|
/// Translate from debug definition to a task template
|
||||||
pub fn to_zed_format(self) -> anyhow::Result<TaskTemplate> {
|
pub fn to_zed_format(self) -> anyhow::Result<TaskTemplate> {
|
||||||
|
@ -249,3 +291,21 @@ impl TryFrom<DebugTaskFile> for TaskTemplates {
|
||||||
Ok(Self(templates))
|
Ok(Self(templates))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{DebugRequestType, LaunchConfig};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_deserialize_non_attach_task() {
|
||||||
|
let deserialized: DebugRequestType =
|
||||||
|
serde_json::from_str(r#"{"program": "cafebabe"}"#).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
deserialized,
|
||||||
|
DebugRequestType::Launch(LaunchConfig {
|
||||||
|
program: "cafebabe".to_owned(),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -338,6 +338,7 @@ impl PickerDelegate for TasksModalDelegate {
|
||||||
debugger_ui::attach_modal::AttachModal::new(
|
debugger_ui::attach_modal::AttachModal::new(
|
||||||
project,
|
project,
|
||||||
config.clone(),
|
config.clone(),
|
||||||
|
true,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
use debugger_ui::Start;
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use feature_flags::{Debugger, FeatureFlagViewExt};
|
use feature_flags::{Debugger, FeatureFlagViewExt};
|
||||||
use gpui::{App, AppContext as _, Context, Entity, Task, Window};
|
use gpui::{App, AppContext as _, Context, Entity, Task, Window};
|
||||||
|
@ -8,7 +9,7 @@ use modal::{TaskOverrides, TasksModal};
|
||||||
use project::{Location, TaskContexts, Worktree};
|
use project::{Location, TaskContexts, Worktree};
|
||||||
use task::{RevealTarget, TaskContext, TaskId, TaskModal, TaskVariables, VariableName};
|
use task::{RevealTarget, TaskContext, TaskId, TaskModal, TaskVariables, VariableName};
|
||||||
use workspace::tasks::schedule_task;
|
use workspace::tasks::schedule_task;
|
||||||
use workspace::{Start, Workspace, tasks::schedule_resolved_task};
|
use workspace::{Workspace, tasks::schedule_resolved_task};
|
||||||
|
|
||||||
mod modal;
|
mod modal;
|
||||||
|
|
||||||
|
|
|
@ -71,6 +71,18 @@ impl SelectableButton for ToggleButton {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FixedWidth for ToggleButton {
|
||||||
|
fn width(mut self, width: DefiniteLength) -> Self {
|
||||||
|
self.base.width = Some(width);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn full_width(mut self) -> Self {
|
||||||
|
self.base.width = Some(relative(1.));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Disableable for ToggleButton {
|
impl Disableable for ToggleButton {
|
||||||
fn disabled(mut self, disabled: bool) -> Self {
|
fn disabled(mut self, disabled: bool) -> Self {
|
||||||
self.base = self.base.disabled(disabled);
|
self.base = self.base.disabled(disabled);
|
||||||
|
|
|
@ -253,6 +253,7 @@ pub struct CheckboxWithLabel {
|
||||||
on_click: Arc<dyn Fn(&ToggleState, &mut Window, &mut App) + 'static>,
|
on_click: Arc<dyn Fn(&ToggleState, &mut Window, &mut App) + 'static>,
|
||||||
filled: bool,
|
filled: bool,
|
||||||
style: ToggleStyle,
|
style: ToggleStyle,
|
||||||
|
checkbox_position: IconPosition,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove `CheckboxWithLabel` now that `label` is a method of `Checkbox`.
|
// TODO: Remove `CheckboxWithLabel` now that `label` is a method of `Checkbox`.
|
||||||
|
@ -271,6 +272,7 @@ impl CheckboxWithLabel {
|
||||||
on_click: Arc::new(on_click),
|
on_click: Arc::new(on_click),
|
||||||
filled: false,
|
filled: false,
|
||||||
style: ToggleStyle::default(),
|
style: ToggleStyle::default(),
|
||||||
|
checkbox_position: IconPosition::Start,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,31 +293,51 @@ impl CheckboxWithLabel {
|
||||||
self.filled = true;
|
self.filled = true;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn checkbox_position(mut self, position: IconPosition) -> Self {
|
||||||
|
self.checkbox_position = position;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderOnce for CheckboxWithLabel {
|
impl RenderOnce for CheckboxWithLabel {
|
||||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap(DynamicSpacing::Base08.rems(cx))
|
.gap(DynamicSpacing::Base08.rems(cx))
|
||||||
.child(
|
.when(self.checkbox_position == IconPosition::Start, |this| {
|
||||||
Checkbox::new(self.id.clone(), self.checked)
|
this.child(
|
||||||
.style(self.style)
|
Checkbox::new(self.id.clone(), self.checked)
|
||||||
.when(self.filled, Checkbox::fill)
|
.style(self.style.clone())
|
||||||
.on_click({
|
.when(self.filled, Checkbox::fill)
|
||||||
let on_click = self.on_click.clone();
|
.on_click({
|
||||||
move |checked, window, cx| {
|
let on_click = self.on_click.clone();
|
||||||
(on_click)(checked, window, cx);
|
move |checked, window, cx| {
|
||||||
}
|
(on_click)(checked, window, cx);
|
||||||
}),
|
}
|
||||||
)
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.id(SharedString::from(format!("{}-label", self.id)))
|
.id(SharedString::from(format!("{}-label", self.id)))
|
||||||
.on_click(move |_event, window, cx| {
|
.on_click({
|
||||||
(self.on_click)(&self.checked.inverse(), window, cx);
|
let on_click = self.on_click.clone();
|
||||||
|
move |_event, window, cx| {
|
||||||
|
(on_click)(&self.checked.inverse(), window, cx);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.child(self.label),
|
.child(self.label),
|
||||||
)
|
)
|
||||||
|
.when(self.checkbox_position == IconPosition::End, |this| {
|
||||||
|
this.child(
|
||||||
|
Checkbox::new(self.id.clone(), self.checked)
|
||||||
|
.style(self.style)
|
||||||
|
.when(self.filled, Checkbox::fill)
|
||||||
|
.on_click(move |checked, window, cx| {
|
||||||
|
(self.on_click)(checked, window, cx);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -129,24 +129,6 @@ static ZED_WINDOW_POSITION: LazyLock<Option<Point<Pixels>>> = LazyLock::new(|| {
|
||||||
|
|
||||||
actions!(assistant, [ShowConfiguration]);
|
actions!(assistant, [ShowConfiguration]);
|
||||||
|
|
||||||
actions!(
|
|
||||||
debugger,
|
|
||||||
[
|
|
||||||
Start,
|
|
||||||
Continue,
|
|
||||||
Disconnect,
|
|
||||||
Pause,
|
|
||||||
Restart,
|
|
||||||
StepInto,
|
|
||||||
StepOver,
|
|
||||||
StepOut,
|
|
||||||
StepBack,
|
|
||||||
Stop,
|
|
||||||
ToggleIgnoreBreakpoints,
|
|
||||||
ClearAllBreakpoints
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
workspace,
|
workspace,
|
||||||
[
|
[
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue