Skip to content

Commit 147e738

Browse files
authored
Stack Frame Inspection (#36)
Inspect stack frames. Refacored to store the source code and context against a StackFrame. The source code is fetched pre-emptively for each stack frame and the context is fetched up until a configured point (currently hard-coded to 1). When in current mode the context will be fetched where it does not exist fo the current frame. Fetched contexts will be stored and be accessible in the history.
1 parent 7e83021 commit 147e738

File tree

9 files changed

+214
-90
lines changed

9 files changed

+214
-90
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
CHANGELOG
22
=========
33

4+
main
5+
----
6+
7+
- Stack traversal - select stack and inspect stack frames in current mode.
8+
- Fixed light theme.
9+
410
0.0.4
511
-----
612

README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Interactive [Xdebug](https://xdebug.org) step-debugging client your terminal.
88
- **Travel forwards**: step over, into and out.
99
- **Travel backwards**: it's not quite time travel, but you can revisit
1010
previous steps in _history mode_.
11+
- **Jump the stack**: jump up and down the stack.
1112
- **Vim-like motions**: Typing `100n` will repeat "step into" 100 times.
1213
- **Inline values**: Show variable values inline with the source code.
1314

@@ -30,14 +31,14 @@ Prefix with number to repeat:
3031
- `N` step over
3132
- `p` previous (switches to history mode if in current mode)
3233
- `o` step out
33-
- `j` scroll down
34-
- `J` scroll down 10
35-
- `k` scroll up
36-
- `K` scroll up 10
37-
- `h` scroll left
38-
- `H` scroll left 10
39-
- `l` scroll right
40-
- `L` scroll right 10
34+
- `j` down
35+
- `J` down 10
36+
- `k` up
37+
- `K` up 10
38+
- `h` left
39+
- `H` left 10
40+
- `l` right
41+
- `L` right 10
4142
- `+` increase context depth
4243
- `-` decrease context depth
4344
- `tab` switch pane

src/app.rs

Lines changed: 140 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use crate::dbgp::client::ContinuationResponse;
66
use crate::dbgp::client::ContinuationStatus;
77
use crate::dbgp::client::DbgpClient;
88
use crate::dbgp::client::Property;
9-
use crate::dbgp::client::StackGetResponse;
109
use crate::event::input::AppEvent;
1110
use crate::notification::Notification;
1211
use crate::theme::Scheme;
@@ -30,7 +29,6 @@ use ratatui::widgets::Block;
3029
use ratatui::widgets::Padding;
3130
use ratatui::widgets::Paragraph;
3231
use ratatui::Terminal;
33-
use tokio::sync::Notify;
3432
use std::collections::hash_map::Entry;
3533
use std::collections::HashMap;
3634
use std::io;
@@ -40,21 +38,65 @@ use tokio::net::TcpListener;
4038
use tokio::sync::mpsc::Receiver;
4139
use tokio::sync::mpsc::Sender;
4240
use tokio::sync::Mutex;
41+
use tokio::sync::Notify;
4342
use tokio::task;
4443
use tui_input::Input;
4544

46-
type AnalyzedFiles = HashMap<String,Analysis>;
45+
type AnalyzedFiles = HashMap<String, Analysis>;
4746

4847
#[derive(Clone, Debug)]
49-
pub struct HistoryEntry {
48+
pub struct StackFrame {
49+
pub level: u16,
5050
pub source: SourceContext,
51-
pub stack: StackGetResponse,
52-
pub context: ContextGetResponse,
51+
pub context: Option<ContextGetResponse>,
5352
}
54-
impl HistoryEntry {
55-
// todo: this is inefficient!
53+
impl StackFrame {
5654
pub(crate) fn get_property(&self, name: &str) -> Option<&Property> {
57-
self.context.properties.iter().find(|&property| property.name == name)
55+
match &self.context {
56+
Some(c) => c.properties.iter().find(|&property| property.name == name),
57+
None => None,
58+
}
59+
}
60+
}
61+
62+
#[derive(Clone, Debug)]
63+
pub struct HistoryEntry {
64+
pub stacks: Vec<StackFrame>,
65+
}
66+
67+
impl HistoryEntry {
68+
fn push(&mut self, frame: StackFrame) {
69+
self.stacks.push(frame);
70+
}
71+
fn new() -> Self {
72+
let stacks = Vec::new();
73+
HistoryEntry { stacks }
74+
}
75+
76+
fn initial(filename: String, source: String) -> HistoryEntry {
77+
HistoryEntry {
78+
stacks: vec![StackFrame {
79+
level: 0,
80+
source: SourceContext {
81+
source,
82+
filename,
83+
line_no: 0,
84+
},
85+
context: None,
86+
}],
87+
}
88+
}
89+
90+
pub fn source(&self, level: u16) -> SourceContext {
91+
let entry = self.stacks.get(level as usize);
92+
match entry {
93+
Some(e) => e.source.clone(),
94+
None => SourceContext::default(),
95+
}
96+
}
97+
98+
pub(crate) fn stack(&self, stack_depth: u16) -> Option<&StackFrame> {
99+
self.stacks.get(stack_depth as usize)
58100
}
59101
}
60102

@@ -99,22 +141,14 @@ impl History {
99141
self.entries.get(self.offset)
100142
}
101143

144+
pub(crate) fn current_mut(&mut self) -> Option<&mut HistoryEntry> {
145+
self.entries.get_mut(self.offset)
146+
}
147+
102148
fn push(&mut self, entry: HistoryEntry) {
103149
self.entries.push(entry);
104150
self.offset = self.entries.len() - 1;
105151
}
106-
107-
fn push_source(&mut self, filename: String, source: String) {
108-
self.push(HistoryEntry {
109-
source: SourceContext {
110-
source,
111-
filename,
112-
line_no: 1,
113-
},
114-
context: ContextGetResponse { properties: vec![] },
115-
stack: StackGetResponse { entries: vec![] },
116-
});
117-
}
118152
}
119153

120154
#[derive(Clone, Debug)]
@@ -123,6 +157,15 @@ pub struct SourceContext {
123157
pub filename: String,
124158
pub line_no: u32,
125159
}
160+
impl SourceContext {
161+
fn default() -> SourceContext {
162+
SourceContext {
163+
source: "".to_string(),
164+
filename: "".to_string(),
165+
line_no: 0,
166+
}
167+
}
168+
}
126169

127170
#[derive(Debug, Clone)]
128171
pub enum CurrentView {
@@ -157,6 +200,8 @@ pub struct App {
157200
pub theme: Theme,
158201

159202
pub analyzed_files: AnalyzedFiles,
203+
204+
pub stack_max_context_fetch: u16,
160205
}
161206

162207
impl App {
@@ -174,6 +219,7 @@ impl App {
174219
client: Arc::new(Mutex::new(client)),
175220
counter: 0,
176221
context_depth: 4,
222+
stack_max_context_fetch: 1,
177223

178224
theme: Theme::SolarizedDark,
179225
server_status: None,
@@ -304,8 +350,8 @@ impl App {
304350
let mut client = self.client.lock().await;
305351
let response = client.deref_mut().connect(s).await?;
306352
for (feature, value) in [
307-
("max_depth",self.context_depth.to_string().as_str()),
308-
("extended_properties","1"),
353+
("max_depth", self.context_depth.to_string().as_str()),
354+
("extended_properties", "1"),
309355
] {
310356
info!("setting feature {} to {:?}", feature, value);
311357
client.feature_set(feature, value).await?;
@@ -317,7 +363,8 @@ impl App {
317363
let source = client.source(response.fileuri.clone()).await.unwrap();
318364

319365
self.history = History::default();
320-
self.history.push_source(response.fileuri.clone(), source);
366+
self.history
367+
.push(HistoryEntry::initial(response.fileuri.clone(), source));
321368
}
322369
AppEvent::Snapshot() => {
323370
self.snapshot().await?;
@@ -338,19 +385,33 @@ impl App {
338385
AppEvent::ContextDepth(inc) => {
339386
let depth = self.context_depth as i8;
340387
self.context_depth = depth.wrapping_add(inc).max(0) as u8;
341-
self.client.lock().await.feature_set(
342-
"max_depth",
343-
self.context_depth.to_string().as_str()
344-
).await?;
345-
},
388+
self.client
389+
.lock()
390+
.await
391+
.feature_set("max_depth", self.context_depth.to_string().as_str())
392+
.await?;
393+
}
346394
AppEvent::ScrollSource(amount) => {
347-
self.session_view.source_scroll = apply_scroll(self.session_view.source_scroll, amount, self.take_motion() as i16);
395+
self.session_view.source_scroll = apply_scroll(
396+
self.session_view.source_scroll,
397+
amount,
398+
self.take_motion() as i16,
399+
);
348400
}
349401
AppEvent::ScrollContext(amount) => {
350-
self.session_view.context_scroll = apply_scroll(self.session_view.context_scroll, amount, self.take_motion() as i16);
402+
self.session_view.context_scroll = apply_scroll(
403+
self.session_view.context_scroll,
404+
amount,
405+
self.take_motion() as i16,
406+
);
351407
}
352408
AppEvent::ScrollStack(amount) => {
353-
self.session_view.stack_scroll = apply_scroll(self.session_view.stack_scroll, amount, self.take_motion() as i16);
409+
self.session_view.stack_scroll = apply_scroll(
410+
self.session_view.stack_scroll,
411+
amount,
412+
self.take_motion() as i16,
413+
);
414+
self.populate_stack_context().await?;
354415
}
355416
AppEvent::ToggleFullscreen => {
356417
self.session_view.full_screen = !self.session_view.full_screen;
@@ -369,17 +430,19 @@ impl App {
369430
.await?;
370431
}
371432
AppEvent::PushInputPlurality(char) => self.input_plurality.push(char),
372-
AppEvent::Input(key_event) => {
373-
match key_event.code {
374-
KeyCode::Char('t') => {
375-
self.theme = self.theme.next();
376-
self.notification = Notification::info(format!("Switched to theme: {:?}", self.theme));
377-
},
378-
KeyCode::Char('?') => {
379-
self.sender.send(AppEvent::ChangeView(CurrentView::Help)).await.unwrap();
380-
},
381-
_ => self.send_event_to_current_view(event).await
433+
AppEvent::Input(key_event) => match key_event.code {
434+
KeyCode::Char('t') => {
435+
self.theme = self.theme.next();
436+
self.notification =
437+
Notification::info(format!("Switched to theme: {:?}", self.theme));
438+
}
439+
KeyCode::Char('?') => {
440+
self.sender
441+
.send(AppEvent::ChangeView(CurrentView::Help))
442+
.await
443+
.unwrap();
382444
}
445+
_ => self.send_event_to_current_view(event).await,
383446
},
384447
_ => self.send_event_to_current_view(event).await,
385448
};
@@ -400,7 +463,6 @@ impl App {
400463
tokio::spawn(async move {
401464
let mut last_response: Option<ContinuationResponse> = None;
402465
for i in 0..count {
403-
404466
// we need to wait for the snapshot to complete before running a further
405467
// continuation.
406468
snapshot_notify.notified().await;
@@ -477,41 +539,66 @@ impl App {
477539
pub async fn snapshot(&mut self) -> Result<()> {
478540
let mut client = self.client.lock().await;
479541
let stack = client.deref_mut().get_stack().await?;
480-
if let Some(top) = stack.top_or_none() {
481-
let filename = &top.filename;
482-
let line_no = top.line;
542+
let mut entry = HistoryEntry::new();
543+
for (level, frame) in stack.entries.iter().enumerate() {
544+
let filename = &frame.filename;
545+
let line_no = frame.line;
546+
let context = match (level as u16) < self.stack_max_context_fetch {
547+
true => Some(client.deref_mut().context_get(level as u16).await.unwrap()),
548+
false => None,
549+
};
483550
let source_code = client
484551
.deref_mut()
485552
.source(filename.to_string())
486553
.await
487554
.unwrap();
555+
488556
let source = SourceContext {
489557
source: source_code,
490558
filename: filename.to_string(),
491559
line_no,
492560
};
493-
let context = client.deref_mut().context_get().await.unwrap();
494-
match self.analyzed_files.entry(source.filename.clone()) {
561+
562+
match self.analyzed_files.entry(filename.clone()) {
495563
Entry::Occupied(_) => (),
496564
Entry::Vacant(vacant_entry) => {
497565
let mut analyser = Analyser::new();
498566
vacant_entry.insert(analyser.analyze(source.source.as_str()).unwrap());
499567
}
500568
};
501-
let entry = HistoryEntry {
569+
570+
entry.push(StackFrame {
571+
level: (level as u16),
502572
source,
503-
stack,
504573
context,
505-
};
506-
self.history.push(entry);
507-
self.session_view.reset();
574+
});
508575
}
576+
self.history.push(entry);
577+
self.session_view.reset();
509578
Ok(())
510579
}
511580

512581
pub(crate) fn theme(&self) -> Scheme {
513582
self.theme.scheme()
514583
}
584+
585+
async fn populate_stack_context(&mut self) -> Result<()> {
586+
if !self.history.is_current() {
587+
return Ok(());
588+
}
589+
let level = self.session_view.stack_scroll.0 as usize;
590+
if let Some(c) = self.history.current_mut() {
591+
let stack = c.stacks.get_mut(level);
592+
if let Some(s) = stack {
593+
if s.context.is_none() {
594+
let mut client = self.client.lock().await;
595+
let context = client.deref_mut().context_get(level as u16).await?;
596+
s.context = Some(context);
597+
}
598+
};
599+
};
600+
Ok(())
601+
}
515602
}
516603

517604
fn apply_scroll(scroll: (u16, u16), amount: (i16, i16), motion: i16) -> (u16, u16) {

0 commit comments

Comments
 (0)