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:
co-authored by
Anthony Eid
Anthony
parent
9986a21970
commit
ece4a1cd7c
@@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user