debugger: Start on tabless design (#27837)

![image](https://github.com/user-attachments/assets/1cd54b70-5457-4c64-95bd-45a7055ea165)

Release Notes:

- N/A

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Anthony <anthony@zed.dev>
This commit is contained in:
Piotr Osiewicz
2025-04-03 16:11:14 +00:00
committed by GitHub
co-authored by Anthony Eid Anthony
parent 9986a21970
commit ece4a1cd7c
33 changed files with 1287 additions and 1092 deletions
-30
View File
@@ -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))
}
}
-337
View File
@@ -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)
});
});
}
}
+58 -258
View File
@@ -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(),
)
}
}