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",
|
||||
"adapter": "lldb",
|
||||
"adapter": "LLDB",
|
||||
"program": "$ZED_WORKTREE_ROOT/target/debug/zed",
|
||||
"request": "launch",
|
||||
"cwd": "$ZED_WORKTREE_ROOT"
|
||||
},
|
||||
{
|
||||
"label": "Debug Zed with GDB",
|
||||
"adapter": "gdb",
|
||||
"adapter": "GDB",
|
||||
"program": "$ZED_WORKTREE_ROOT/target/debug/zed",
|
||||
"request": "launch",
|
||||
"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">
|
||||
<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>
|
||||
<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>
|
||||
|
|
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 {
|
||||
DebugRequestType::Attach(attach) => {
|
||||
map.insert("pid".into(), attach.process_id.into());
|
||||
map.insert("stopOnEntry".into(), config.stop_on_entry.into());
|
||||
}
|
||||
DebugRequestType::Launch(launch) => {
|
||||
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(
|
||||
"cwd".into(),
|
||||
|
|
|
@ -126,24 +126,31 @@ impl DebugAdapter for PythonDebugAdapter {
|
|||
}
|
||||
|
||||
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 {
|
||||
DebugRequestType::Launch(launch_config) => {
|
||||
json!({
|
||||
"program": launch_config.program,
|
||||
"args": launch_config.args,
|
||||
"subProcess": true,
|
||||
"cwd": launch_config.cwd,
|
||||
"redirectOutput": true,
|
||||
"StopOnEntry": config.stop_on_entry,
|
||||
})
|
||||
DebugRequestType::Attach(attach) => {
|
||||
map.insert("processId".into(), attach.process_id.into());
|
||||
}
|
||||
dap::DebugRequestType::Attach(attach_config) => {
|
||||
json!({
|
||||
"subProcess": true,
|
||||
"redirectOutput": true,
|
||||
"processId": attach_config.process_id
|
||||
})
|
||||
DebugRequestType::Launch(launch) => {
|
||||
map.insert("program".into(), launch.program.clone().into());
|
||||
map.insert("args".into(), launch.args.clone().into());
|
||||
|
||||
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(
|
||||
project: Entity<project::Project>,
|
||||
debug_config: task::DebugTaskDefinition,
|
||||
modal: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
|
@ -74,13 +75,14 @@ impl AttachModal {
|
|||
})
|
||||
.collect();
|
||||
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(
|
||||
project: Entity<project::Project>,
|
||||
debug_config: task::DebugTaskDefinition,
|
||||
processes: Vec<Candidate>,
|
||||
modal: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
|
@ -103,6 +105,7 @@ impl AttachModal {
|
|||
window,
|
||||
cx,
|
||||
)
|
||||
.modal(modal)
|
||||
});
|
||||
Self {
|
||||
_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 collections::HashMap;
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
|
@ -13,7 +17,10 @@ use gpui::{
|
|||
};
|
||||
use project::{
|
||||
Project,
|
||||
debugger::dap_store::{self, DapStore},
|
||||
debugger::{
|
||||
dap_store::{self, DapStore},
|
||||
session::ThreadStatus,
|
||||
},
|
||||
terminals::TerminalKind,
|
||||
};
|
||||
use rpc::proto::{self};
|
||||
|
@ -21,11 +28,9 @@ use settings::Settings;
|
|||
use std::{any::TypeId, path::PathBuf};
|
||||
use task::DebugTaskDefinition;
|
||||
use terminal_view::terminal_panel::TerminalPanel;
|
||||
use ui::prelude::*;
|
||||
use util::ResultExt;
|
||||
use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*};
|
||||
use workspace::{
|
||||
ClearAllBreakpoints, Continue, Disconnect, Pane, Pause, Restart, StepBack, StepInto, StepOut,
|
||||
StepOver, Stop, ToggleIgnoreBreakpoints, Workspace,
|
||||
Pane, Workspace,
|
||||
dock::{DockPosition, Panel, PanelEvent},
|
||||
pane,
|
||||
};
|
||||
|
@ -51,10 +56,11 @@ actions!(debug_panel, [ToggleFocus]);
|
|||
pub struct DebugPanel {
|
||||
size: Pixels,
|
||||
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>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
pub(crate) last_inert_config: Option<DebugTaskDefinition>,
|
||||
}
|
||||
|
||||
impl DebugPanel {
|
||||
|
@ -66,8 +72,6 @@ impl DebugPanel {
|
|||
cx.new(|cx| {
|
||||
let project = workspace.project().clone();
|
||||
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 mut pane = Pane::new(
|
||||
workspace.weak_handle(),
|
||||
|
@ -81,71 +85,9 @@ impl DebugPanel {
|
|||
pane.set_can_split(None);
|
||||
pane.set_can_navigate(true, cx);
|
||||
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_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
|
||||
});
|
||||
|
||||
|
@ -159,7 +101,7 @@ impl DebugPanel {
|
|||
pane,
|
||||
size: px(300.),
|
||||
_subscriptions,
|
||||
last_inert_config: None,
|
||||
past_debug_definition: None,
|
||||
project: project.downgrade(),
|
||||
workspace: workspace.weak_handle(),
|
||||
};
|
||||
|
@ -295,7 +237,7 @@ impl DebugPanel {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
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 {
|
||||
return log::error!(
|
||||
"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 {}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -538,42 +748,49 @@ impl Panel for DebugPanel {
|
|||
fn activation_priority(&self) -> u32 {
|
||||
9
|
||||
}
|
||||
fn set_active(&mut self, active: bool, window: &mut Window, cx: &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,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
fn set_active(&mut self, _: bool, _: &mut Window, _: &mut Context<Self>) {}
|
||||
}
|
||||
|
||||
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()
|
||||
.key_context("DebugPanel")
|
||||
.track_focus(&self.focus_handle(cx))
|
||||
.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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,38 @@
|
|||
use dap::debugger_settings::DebuggerSettings;
|
||||
use debugger_panel::{DebugPanel, ToggleFocus};
|
||||
use feature_flags::{Debugger, FeatureFlagViewExt};
|
||||
use gpui::App;
|
||||
use gpui::{App, actions};
|
||||
use new_session_modal::NewSessionModal;
|
||||
use session::DebugSession;
|
||||
use settings::Settings;
|
||||
use workspace::{
|
||||
Pause, Restart, ShutdownDebugAdapters, StepBack, StepInto, StepOver, Stop,
|
||||
ToggleIgnoreBreakpoints, Workspace,
|
||||
};
|
||||
use workspace::{ShutdownDebugAdapters, Workspace};
|
||||
|
||||
pub mod attach_modal;
|
||||
pub mod debugger_panel;
|
||||
pub mod session;
|
||||
mod new_session_modal;
|
||||
pub(crate) mod session;
|
||||
|
||||
#[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) {
|
||||
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;
|
||||
mod starting;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use dap::client::SessionId;
|
||||
use failed::FailedState;
|
||||
use gpui::{
|
||||
Animation, AnimationExt, AnyElement, App, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
Subscription, Task, Transformation, WeakEntity, percentage,
|
||||
};
|
||||
use inert::{InertEvent, InertState};
|
||||
use gpui::{App, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity};
|
||||
use project::Project;
|
||||
use project::debugger::{dap_store::DapStore, session::Session};
|
||||
use project::worktree_store::WorktreeStore;
|
||||
use rpc::proto::{self, PeerId};
|
||||
use running::RunningState;
|
||||
use starting::{StartingEvent, StartingState};
|
||||
use task::DebugTaskDefinition;
|
||||
use ui::{Indicator, prelude::*};
|
||||
use util::ResultExt;
|
||||
use ui::prelude::*;
|
||||
use workspace::{
|
||||
FollowableItem, ViewId, Workspace,
|
||||
item::{self, Item},
|
||||
|
@ -29,9 +16,6 @@ use workspace::{
|
|||
use crate::debugger_panel::DebugPanel;
|
||||
|
||||
pub(crate) enum DebugSessionState {
|
||||
Inert(Entity<InertState>),
|
||||
Starting(Entity<StartingState>),
|
||||
Failed(Entity<FailedState>),
|
||||
Running(Entity<running::RunningState>),
|
||||
}
|
||||
|
||||
|
@ -39,7 +23,6 @@ impl DebugSessionState {
|
|||
pub(crate) fn as_running(&self) -> Option<&Entity<running::RunningState>> {
|
||||
match &self {
|
||||
DebugSessionState::Running(entity) => Some(entity),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,9 +31,9 @@ pub struct DebugSession {
|
|||
remote_id: Option<workspace::ViewId>,
|
||||
mode: DebugSessionState,
|
||||
dap_store: WeakEntity<DapStore>,
|
||||
debug_panel: WeakEntity<DebugPanel>,
|
||||
worktree_store: WeakEntity<WorktreeStore>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
_debug_panel: WeakEntity<DebugPanel>,
|
||||
_worktree_store: WeakEntity<WorktreeStore>,
|
||||
_workspace: WeakEntity<Workspace>,
|
||||
_subscriptions: [Subscription; 1],
|
||||
}
|
||||
|
||||
|
@ -69,46 +52,11 @@ pub enum ThreadItem {
|
|||
}
|
||||
|
||||
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(
|
||||
project: Entity<Project>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
session: Entity<Session>,
|
||||
debug_panel: WeakEntity<DebugPanel>,
|
||||
_debug_panel: WeakEntity<DebugPanel>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<Self> {
|
||||
|
@ -121,26 +69,20 @@ impl DebugSession {
|
|||
remote_id: None,
|
||||
mode: DebugSessionState::Running(mode),
|
||||
dap_store: project.read(cx).dap_store().downgrade(),
|
||||
debug_panel,
|
||||
worktree_store: project.read(cx).worktree_store().downgrade(),
|
||||
workspace,
|
||||
_debug_panel,
|
||||
_worktree_store: project.read(cx).worktree_store().downgrade(),
|
||||
_workspace: workspace,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn session_id(&self, cx: &App) -> Option<SessionId> {
|
||||
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()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn shutdown(&mut self, cx: &mut Context<Self>) {
|
||||
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)),
|
||||
}
|
||||
}
|
||||
|
@ -149,63 +91,29 @@ impl DebugSession {
|
|||
&self.mode
|
||||
}
|
||||
|
||||
fn on_inert_event(
|
||||
&mut self,
|
||||
_: &Entity<InertState>,
|
||||
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;
|
||||
pub(crate) fn label(&self, cx: &App) -> String {
|
||||
let session_id = match &self.mode {
|
||||
DebugSessionState::Running(running_state) => running_state.read(cx).session_id(),
|
||||
};
|
||||
let starting = cx.new(|cx| StartingState::new(new_session_id, task, cx));
|
||||
|
||||
self._subscriptions = [cx.subscribe_in(&starting, window, Self::on_starting_event)];
|
||||
self.mode = DebugSessionState::Starting(starting);
|
||||
}
|
||||
|
||||
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));
|
||||
let Ok(Some(session)) = self
|
||||
.dap_store
|
||||
.read_with(cx, |store, _| store.session_by_id(session_id))
|
||||
else {
|
||||
return "".to_owned();
|
||||
};
|
||||
cx.notify();
|
||||
session
|
||||
.read(cx)
|
||||
.as_local()
|
||||
.expect("Remote Debug Sessions are not implemented yet")
|
||||
.label()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<DebugPanelItemEvent> for DebugSession {}
|
||||
|
||||
impl Focusable for DebugSession {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
@ -213,61 +121,6 @@ impl Focusable for DebugSession {
|
|||
|
||||
impl Item for DebugSession {
|
||||
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 {
|
||||
|
@ -339,15 +192,6 @@ impl FollowableItem for DebugSession {
|
|||
impl Render for DebugSession {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
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) => {
|
||||
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 stack_frame_list::StackFrameList;
|
||||
use ui::{
|
||||
ActiveTheme, AnyElement, App, Button, ButtonCommon, Clickable, Context, ContextMenu,
|
||||
Disableable, Divider, DropdownMenu, FluentBuilder, IconButton, IconName, IconSize, Indicator,
|
||||
InteractiveElement, IntoElement, Label, ParentElement, Render, SharedString,
|
||||
StatefulInteractiveElement, Styled, Tooltip, Window, div, h_flex, v_flex,
|
||||
ActiveTheme, AnyElement, App, Button, Context, ContextMenu, DropdownMenu, FluentBuilder,
|
||||
Indicator, InteractiveElement, IntoElement, ParentElement, Render, SharedString,
|
||||
StatefulInteractiveElement, Styled, Window, div, h_flex, v_flex,
|
||||
};
|
||||
use util::ResultExt;
|
||||
use variable_list::VariableList;
|
||||
|
@ -42,7 +41,7 @@ pub struct 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));
|
||||
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))
|
||||
.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| {
|
||||
this.disabled(thread_status != ThreadStatus::Stopped, cx);
|
||||
});
|
||||
|
||||
let active_thread_item = &self.active_thread_item;
|
||||
|
||||
let has_no_threads = threads.is_empty();
|
||||
let capabilities = self.capabilities(cx);
|
||||
let state = cx.entity();
|
||||
h_flex()
|
||||
.key_context("DebugPanelItem")
|
||||
.track_focus(&self.focus_handle(cx))
|
||||
.size_full()
|
||||
.items_start()
|
||||
.child(
|
||||
v_flex()
|
||||
.size_full()
|
||||
.items_start()
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.border_b_1()
|
||||
.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()),
|
||||
),
|
||||
v_flex().size_full().items_start().child(
|
||||
h_flex()
|
||||
.size_full()
|
||||
.items_start()
|
||||
.p_1()
|
||||
.gap_4()
|
||||
.child(self.stack_frame_list.clone()),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
|
@ -450,37 +221,32 @@ impl RunningState {
|
|||
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>) {
|
||||
self.active_thread_item = thread_item;
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[cfg(test)]
|
||||
pub fn stack_frame_list(&self) -> &Entity<StackFrameList> {
|
||||
&self.stack_frame_list
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[cfg(test)]
|
||||
pub fn console(&self) -> &Entity<Console> {
|
||||
&self.console
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn module_list(&self) -> &Entity<ModuleList> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn module_list(&self) -> &Entity<ModuleList> {
|
||||
&self.module_list
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn variable_list(&self) -> &Entity<VariableList> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn variable_list(&self) -> &Entity<VariableList> {
|
||||
&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 {
|
||||
self.session().read(cx).capabilities().clone()
|
||||
}
|
||||
|
@ -504,8 +270,8 @@ impl RunningState {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn selected_thread_id(&self) -> Option<ThreadId> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn selected_thread_id(&self) -> Option<ThreadId> {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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>) {
|
||||
self.session().update(cx, |state, cx| {
|
||||
state.disconnect_client(cx);
|
||||
|
@ -686,6 +456,36 @@ impl RunningState {
|
|||
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 {}
|
||||
|
|
|
@ -85,16 +85,11 @@ impl Console {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn editor(&self) -> &Entity<Editor> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn editor(&self) -> &Entity<Editor> {
|
||||
&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 {
|
||||
self.session.read(cx).is_local()
|
||||
}
|
||||
|
|
|
@ -147,11 +147,9 @@ impl ModuleList {
|
|||
)
|
||||
.into_any()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl ModuleList {
|
||||
pub fn modules(&self, cx: &mut Context<Self>) -> Vec<dap::Module> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn modules(&self, cx: &mut Context<Self>) -> Vec<dap::Module> {
|
||||
self.session
|
||||
.update(cx, |session, cx| session.modules(cx).to_vec())
|
||||
}
|
||||
|
|
|
@ -87,13 +87,13 @@ impl StackFrameList {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn entries(&self) -> &Vec<StackFrameEntry> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn entries(&self) -> &Vec<StackFrameEntry> {
|
||||
&self.entries
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn flatten_entries(&self) -> Vec<dap::StackFrame> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn flatten_entries(&self) -> Vec<dap::StackFrame> {
|
||||
self.entries
|
||||
.iter()
|
||||
.flat_map(|frame| match frame {
|
||||
|
@ -115,8 +115,8 @@ impl StackFrameList {
|
|||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn dap_stack_frames(&self, cx: &mut App) -> Vec<dap::StackFrame> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn dap_stack_frames(&self, cx: &mut App) -> Vec<dap::StackFrame> {
|
||||
self.stack_frames(cx)
|
||||
.into_iter()
|
||||
.map(|stack_frame| stack_frame.dap.clone())
|
||||
|
|
|
@ -540,8 +540,8 @@ impl VariableList {
|
|||
}
|
||||
|
||||
#[track_caller]
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn assert_visual_entries(&self, expected: Vec<&str>) {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn assert_visual_entries(&self, expected: Vec<&str>) {
|
||||
const INDENT: &'static str = " ";
|
||||
|
||||
let entries = &self.entries;
|
||||
|
@ -569,8 +569,8 @@ impl VariableList {
|
|||
}
|
||||
|
||||
#[track_caller]
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn scopes(&self) -> Vec<dap::Scope> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn scopes(&self) -> Vec<dap::Scope> {
|
||||
self.entries
|
||||
.iter()
|
||||
.filter_map(|entry| match &entry.dap_kind {
|
||||
|
@ -582,8 +582,8 @@ impl VariableList {
|
|||
}
|
||||
|
||||
#[track_caller]
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn variables_per_scope(&self) -> Vec<(dap::Scope, Vec<dap::Variable>)> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn variables_per_scope(&self) -> Vec<(dap::Scope, Vec<dap::Variable>)> {
|
||||
let mut scopes: Vec<(dap::Scope, Vec<_>)> = Vec::new();
|
||||
let mut idx = 0;
|
||||
|
||||
|
@ -604,8 +604,8 @@ impl VariableList {
|
|||
}
|
||||
|
||||
#[track_caller]
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn variables(&self) -> Vec<dap::Variable> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn variables(&self) -> Vec<dap::Variable> {
|
||||
self.entries
|
||||
.iter()
|
||||
.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![],
|
||||
},
|
||||
],
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
|
|
|
@ -99,8 +99,8 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test
|
|||
|
||||
debug_panel.update(cx, |this, cx| {
|
||||
assert!(this.active_session(cx).is_some());
|
||||
// we have one active session and one inert item
|
||||
assert_eq!(2, this.pane().unwrap().read(cx).items_len());
|
||||
// we have one active session
|
||||
assert_eq!(1, this.pane().unwrap().read(cx).items_len());
|
||||
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()
|
||||
});
|
||||
|
||||
// we have one active session and one inert item
|
||||
// we have one active session
|
||||
assert_eq!(
|
||||
2,
|
||||
1,
|
||||
debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len())
|
||||
);
|
||||
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| {
|
||||
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!(
|
||||
ThreadId(1),
|
||||
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| {
|
||||
assert!(this.active_session(cx).is_some());
|
||||
// we have one active session and one inert item
|
||||
assert_eq!(2, this.pane().unwrap().read(cx).items_len());
|
||||
// we have one active session
|
||||
assert_eq!(1, this.pane().unwrap().read(cx).items_len());
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
|
@ -281,9 +281,9 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
|
|||
.clone()
|
||||
});
|
||||
|
||||
// we have one active session and one inert item
|
||||
// we have one active session
|
||||
assert_eq!(
|
||||
2,
|
||||
1,
|
||||
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());
|
||||
|
@ -323,9 +323,9 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
|
|||
.clone()
|
||||
});
|
||||
|
||||
// we have one active session and one inert item
|
||||
// we have one active session
|
||||
assert_eq!(
|
||||
2,
|
||||
1,
|
||||
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());
|
||||
|
@ -362,7 +362,7 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
|
|||
|
||||
debug_panel.update(cx, |this, cx| {
|
||||
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!(
|
||||
ThreadId(1),
|
||||
running_state.read(cx).selected_thread_id().unwrap()
|
||||
|
@ -1447,7 +1447,7 @@ async fn test_unsetting_breakpoints_on_clear_breakpoint_action(
|
|||
})
|
||||
.await;
|
||||
|
||||
cx.dispatch_action(workspace::ClearAllBreakpoints);
|
||||
cx.dispatch_action(crate::ClearAllBreakpoints);
|
||||
cx.run_until_parked();
|
||||
|
||||
let shutdown_session = project.update(cx, |project, cx| {
|
||||
|
|
|
@ -23,6 +23,7 @@ pub enum IconName {
|
|||
ArrowCircle,
|
||||
ArrowDown,
|
||||
ArrowDownFromLine,
|
||||
ArrowDownRight,
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
ArrowRightLeft,
|
||||
|
@ -44,6 +45,7 @@ pub enum IconName {
|
|||
BookCopy,
|
||||
BookPlus,
|
||||
Brain,
|
||||
BugOff,
|
||||
CaseSensitive,
|
||||
Check,
|
||||
CheckDouble,
|
||||
|
@ -55,6 +57,7 @@ pub enum IconName {
|
|||
ChevronUp,
|
||||
ChevronUpDown,
|
||||
Circle,
|
||||
CircleOff,
|
||||
Clipboard,
|
||||
Close,
|
||||
Code,
|
||||
|
@ -166,6 +169,7 @@ pub enum IconName {
|
|||
Play,
|
||||
Plus,
|
||||
PocketKnife,
|
||||
Power,
|
||||
Public,
|
||||
PullRequest,
|
||||
Quote,
|
||||
|
|
|
@ -48,6 +48,7 @@ use worktree::Worktree;
|
|||
|
||||
pub enum DapStoreEvent {
|
||||
DebugClientStarted(SessionId),
|
||||
DebugSessionInitialized(SessionId),
|
||||
DebugClientShutdown(SessionId),
|
||||
DebugClientEvent {
|
||||
session_id: SessionId,
|
||||
|
@ -862,6 +863,10 @@ fn create_new_session(
|
|||
}
|
||||
}
|
||||
|
||||
this.update(cx, |_, cx| {
|
||||
cx.emit(DapStoreEvent::DebugSessionInitialized(session_id));
|
||||
})?;
|
||||
|
||||
Ok(session)
|
||||
});
|
||||
task
|
||||
|
|
|
@ -537,7 +537,11 @@ impl LocalMode {
|
|||
Ok((adapter, binary))
|
||||
}
|
||||
|
||||
pub fn initialize_sequence(
|
||||
pub fn label(&self) -> String {
|
||||
self.config.label.clone()
|
||||
}
|
||||
|
||||
fn initialize_sequence(
|
||||
&self,
|
||||
capabilities: &Capabilities,
|
||||
initialized_rx: oneshot::Receiver<()>,
|
||||
|
|
|
@ -125,6 +125,22 @@ impl Inventory {
|
|||
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,
|
||||
/// 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.
|
||||
|
|
|
@ -41,7 +41,6 @@ impl TCPHost {
|
|||
#[derive(Default, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
|
||||
pub struct AttachConfig {
|
||||
/// The processId to attach to, if left empty we will show a process picker
|
||||
#[serde(default)]
|
||||
pub process_id: Option<u32>,
|
||||
}
|
||||
|
||||
|
@ -52,7 +51,8 @@ pub struct LaunchConfig {
|
|||
pub program: String,
|
||||
/// The current working directory of your project
|
||||
pub cwd: Option<PathBuf>,
|
||||
/// Args to pass to a debuggee
|
||||
/// Arguments to pass to a debuggee
|
||||
#[serde(default)]
|
||||
pub args: Vec<String>,
|
||||
}
|
||||
|
||||
|
@ -66,6 +66,17 @@ pub enum DebugRequestType {
|
|||
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.
|
||||
/// Contrary to `DebugRequestType`, `DebugRequestDisposition` is not Serializable.
|
||||
#[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 {
|
||||
/// Translate from debug definition to a task template
|
||||
pub fn to_zed_format(self) -> anyhow::Result<TaskTemplate> {
|
||||
|
@ -249,3 +291,21 @@ impl TryFrom<DebugTaskFile> for TaskTemplates {
|
|||
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(
|
||||
project,
|
||||
config.clone(),
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
|
||||
use debugger_ui::Start;
|
||||
use editor::Editor;
|
||||
use feature_flags::{Debugger, FeatureFlagViewExt};
|
||||
use gpui::{App, AppContext as _, Context, Entity, Task, Window};
|
||||
|
@ -8,7 +9,7 @@ use modal::{TaskOverrides, TasksModal};
|
|||
use project::{Location, TaskContexts, Worktree};
|
||||
use task::{RevealTarget, TaskContext, TaskId, TaskModal, TaskVariables, VariableName};
|
||||
use workspace::tasks::schedule_task;
|
||||
use workspace::{Start, Workspace, tasks::schedule_resolved_task};
|
||||
use workspace::{Workspace, tasks::schedule_resolved_task};
|
||||
|
||||
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 {
|
||||
fn disabled(mut self, disabled: bool) -> Self {
|
||||
self.base = self.base.disabled(disabled);
|
||||
|
|
|
@ -253,6 +253,7 @@ pub struct CheckboxWithLabel {
|
|||
on_click: Arc<dyn Fn(&ToggleState, &mut Window, &mut App) + 'static>,
|
||||
filled: bool,
|
||||
style: ToggleStyle,
|
||||
checkbox_position: IconPosition,
|
||||
}
|
||||
|
||||
// TODO: Remove `CheckboxWithLabel` now that `label` is a method of `Checkbox`.
|
||||
|
@ -271,6 +272,7 @@ impl CheckboxWithLabel {
|
|||
on_click: Arc::new(on_click),
|
||||
filled: false,
|
||||
style: ToggleStyle::default(),
|
||||
checkbox_position: IconPosition::Start,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -291,31 +293,51 @@ impl CheckboxWithLabel {
|
|||
self.filled = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn checkbox_position(mut self, position: IconPosition) -> Self {
|
||||
self.checkbox_position = position;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for CheckboxWithLabel {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
h_flex()
|
||||
.gap(DynamicSpacing::Base08.rems(cx))
|
||||
.child(
|
||||
Checkbox::new(self.id.clone(), self.checked)
|
||||
.style(self.style)
|
||||
.when(self.filled, Checkbox::fill)
|
||||
.on_click({
|
||||
let on_click = self.on_click.clone();
|
||||
move |checked, window, cx| {
|
||||
(on_click)(checked, window, cx);
|
||||
}
|
||||
}),
|
||||
)
|
||||
.when(self.checkbox_position == IconPosition::Start, |this| {
|
||||
this.child(
|
||||
Checkbox::new(self.id.clone(), self.checked)
|
||||
.style(self.style.clone())
|
||||
.when(self.filled, Checkbox::fill)
|
||||
.on_click({
|
||||
let on_click = self.on_click.clone();
|
||||
move |checked, window, cx| {
|
||||
(on_click)(checked, window, cx);
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
div()
|
||||
.id(SharedString::from(format!("{}-label", self.id)))
|
||||
.on_click(move |_event, window, cx| {
|
||||
(self.on_click)(&self.checked.inverse(), window, cx);
|
||||
.on_click({
|
||||
let on_click = self.on_click.clone();
|
||||
move |_event, window, cx| {
|
||||
(on_click)(&self.checked.inverse(), window, cx);
|
||||
}
|
||||
})
|
||||
.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!(
|
||||
debugger,
|
||||
[
|
||||
Start,
|
||||
Continue,
|
||||
Disconnect,
|
||||
Pause,
|
||||
Restart,
|
||||
StepInto,
|
||||
StepOver,
|
||||
StepOut,
|
||||
StepBack,
|
||||
Stop,
|
||||
ToggleIgnoreBreakpoints,
|
||||
ClearAllBreakpoints
|
||||
]
|
||||
);
|
||||
|
||||
actions!(
|
||||
workspace,
|
||||
[
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue