Files
Ely-GPUI-Components/examples/gallery/pages/debug/session.rs
T
ZacharyZhang-NY 74d6bd1771 T20: Debug
- DebugToolbar, BreakpointList, CallStack with folded library frames, ThreadList
- VariablesPanel and WatchPanel, colored by kind, changes washed
- HexViewer at a base address with the byte under the pointer lit on both sides
- Disassembly with source lines, registers and numbers colored, gutter breakpoints
- Flamegraph that zooms by frame, TimelineProfiler that zooms and pans by wheel
- NetworkInspector with a waterfall, a filter and a request's headers and body
- icons: redo-dot, arrow-down-to-dot, arrow-up-from-dot
- gallery: the Debug page
2026-09-26 02:38:50 -04:00

303 lines
11 KiB
Rust

use ely_gpui_component::{
debug::{
Breakpoint, BreakpointList, CallStack, DebugCommand, DebugState, DebugToolbar, StackFrame,
Thread, ThreadList, ThreadState, ValueKind, Variable, VariablesPanel, Watch, WatchPanel,
},
forms::{InputEvent, TextInput},
typography::Caption,
};
use gpui::{App, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
use crate::ui::{keep, row, section, set};
fn frames() -> Vec<StackFrame> {
let frame = |function: &str, path: &str, line, library| StackFrame {
function: function.to_string().into(),
path: path.to_string().into(),
line,
library,
};
vec![
frame("palette::Palette::lookup", "src/palette.rs", 48, false),
frame("palette::tints::{{closure}}", "src/main.rs", 65, false),
frame(
"core::iter::adapters::map::map_fold",
"core/src/iter/adapters/map.rs",
88,
true,
),
frame(
"core::iter::traits::iterator::Iterator::fold",
"core/src/iter/traits/iterator.rs",
2587,
true,
),
frame(
"alloc::vec::Vec::extend_trusted",
"alloc/src/vec/mod.rs",
3301,
true,
),
frame("palette::main", "src/main.rs", 65, false),
frame("std::rt::lang_start", "std/src/rt.rs", 206, true),
]
}
fn scopes() -> Vec<(SharedString, Vec<Variable>)> {
let value = |name: &str, value: &str, kind, type_name: Option<&str>, children| Variable {
name: name.to_string().into(),
value: value.to_string().into(),
kind,
type_name: type_name.map(|name| name.to_string().into()),
children,
};
vec![
(
"Locals".into(),
vec![
value(
"self",
"&Palette { colors: {…}, fallback: Color }",
ValueKind::Object,
Some("&Palette"),
vec![
value(
"colors",
"{\"accent\": Color, \"fg\": Color}",
ValueKind::Object,
Some("BTreeMap<String, Color>"),
vec![],
),
value(
"fallback",
"Color { red: 0.1, green: 0.1, blue: 0.1 }",
ValueKind::Object,
Some("Color"),
vec![
value("red", "0.1", ValueKind::Number, Some("f32"), vec![]),
value("green", "0.1", ValueKind::Number, Some("f32"), vec![]),
value("blue", "0.1", ValueKind::Number, Some("f32"), vec![]),
],
),
],
),
value("name", "\"accent\"", ValueKind::Text, Some("&str"), vec![]),
value("lift", "0.75", ValueKind::Number, Some("f32"), vec![]),
value("clamped", "false", ValueKind::Bool, Some("bool"), vec![]),
],
),
(
"Statics".into(),
vec![value(
"WHITE",
"Color { red: 1.0, green: 1.0, blue: 1.0 }",
ValueKind::Object,
Some("Color"),
vec![],
)],
),
]
}
pub fn session(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let state = keep("debug-state", || DebugState::Paused, window, cx);
let said = keep("debug-said", || None::<SharedString>, window, cx);
let (now_state, told) = (*state.read(cx), said.read(cx).clone());
let (command, tell) = (state.clone(), said.clone());
section(
"DebugToolbar",
"The floating row that drives a session: continue or pause, the three steps while paused, restart and stop, each with its key.",
cx,
)
.child(row().child(DebugToolbar::new("debug-toolbar", now_state).on_command(move |which, _, cx| {
let next = match which {
DebugCommand::Pause => DebugState::Paused,
DebugCommand::Continue | DebugCommand::Restart => DebugState::Running,
_ => *command.read(cx),
};
set(&command, next, cx);
set(&tell, Some(format!("{which:?}").into()), cx);
})))
.children(told.map(Caption::new))
}
pub fn breakpoints_and_stack(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let points = keep(
"debug-breakpoints",
|| {
let point = |path: &str, line, enabled, condition: Option<&str>, hits| Breakpoint {
path: path.to_string().into(),
line,
enabled,
condition: condition.map(|text| text.to_string().into()),
hits,
};
vec![
point("src/palette.rs", 48, true, None, 3),
point("src/main.rs", 65, true, Some("step > 2"), 1),
point("src/theme/tokens.rs", 112, false, None, 0),
]
},
window,
cx,
);
let current = keep("debug-frame", || 0usize, window, cx);
let libraries = keep("debug-libraries", || false, window, cx);
let thread = keep("debug-thread", || 1u64, window, cx);
let (now_points, now_current, now_libraries, now_thread) = (
points.read(cx).clone(),
*current.read(cx),
*libraries.read(cx),
*thread.read(cx),
);
let (toggle, remove, pick, show, choose) = (
points.clone(),
points.clone(),
current.clone(),
libraries.clone(),
thread.clone(),
);
let threads = [
Thread {
id: 1,
name: "main".into(),
state: ThreadState::Stopped,
},
Thread {
id: 2,
name: "renderer".into(),
state: ThreadState::Paused,
},
Thread {
id: 3,
name: "io-worker".into(),
state: ThreadState::Paused,
},
];
section(
"BreakpointList / BreakpointGutter / CallStack / ThreadList",
"Every breakpoint with its condition and hits; the stack where the program waits, library frames folded until pressed; and the threads, one picked. The gutter's breakpoints are the editor's (CodeEditor).",
cx,
)
.child(
div()
.flex()
.gap_8()
.child(
div().w(px(400.)).child(
BreakpointList::new("debug-breakpoint-list", now_points)
.on_toggle(move |ix, on, _, cx| {
let mut next = toggle.read(cx).clone();
next[ix].enabled = on;
set(&toggle, next, cx)
})
.on_remove(move |ix, _, cx| {
let mut next = remove.read(cx).clone();
next.remove(ix);
set(&remove, next, cx)
}),
),
)
.child(
div().w(px(400.)).flex().flex_col().gap_4().child(
CallStack::new("debug-stack", frames())
.current(now_current)
.libraries(now_libraries)
.on_pick(move |ix, _, cx| set(&pick, ix, cx))
.on_libraries(move |on, _, cx| set(&show, on, cx)),
)
.child(ThreadList::new("debug-threads", threads).selected(now_thread).on_pick(move |id, _, cx| set(&choose, id, cx))),
),
)
}
pub fn values(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let open = keep(
"debug-open",
|| vec![SharedString::from("Locals"), "Locals/self".into()],
window,
cx,
);
let watches = keep(
"debug-watches",
|| {
vec![
Watch {
expression: "lift * 2.0".into(),
value: Ok(("1.5".into(), ValueKind::Number)),
},
Watch {
expression: "self.colors.len()".into(),
value: Ok(("2".into(), ValueKind::Number)),
},
Watch {
expression: "missing".into(),
value: Err("cannot find value `missing` in this scope".into()),
},
]
},
window,
cx,
);
let field = window.use_keyed_state("debug-watch-field", cx, |window, cx| {
TextInput::new(window, cx).placeholder("Add an expression")
});
window.use_keyed_state("debug-watch-sub", cx, {
let (field, watches) = (field.clone(), watches.clone());
move |_, cx| {
cx.subscribe(&field, move |_, input, event: &InputEvent, cx| {
if *event != InputEvent::Submit {
return;
}
let text = input.read(cx).text().trim().to_string();
if text.is_empty() {
return;
}
let mut next = watches.read(cx).clone();
next.push(Watch {
expression: text.into(),
value: Ok(("0.75".into(), ValueKind::Number)),
});
set(&watches, next, cx);
input.update(cx, |input, cx| input.set_text("", cx));
})
}
});
let (now_open, now_watches) = (open.read(cx).clone(), watches.read(cx).clone());
let (toggle, remove) = (open.clone(), watches.clone());
section(
"VariablesPanel / WatchPanel / ConsoleREPL",
"Variables by scope as a tree, colored by kind, with the value that changed at the last step washed; expressions watched at every step, errors in red. The REPL beside a paused program is the editor's DebugConsole.",
cx,
)
.child(
div()
.flex()
.gap_8()
.child(
div().w(px(460.)).child(
VariablesPanel::new("debug-variables", scopes())
.open(now_open)
.changed([SharedString::from("Locals/lift")])
.on_toggle(move |key, on, _, cx| {
let mut next = toggle.read(cx).clone();
next.retain(|kept| kept != key);
if on {
next.push(key.clone());
}
set(&toggle, next, cx)
}),
),
)
.child(
div().w(px(340.)).child(
WatchPanel::new("debug-watch", now_watches, &field).on_remove(move |ix, _, cx| {
let mut next = remove.read(cx).clone();
next.remove(ix);
set(&remove, next, cx)
}),
),
),
)
}