Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
CHANGELOG
=========

main
----

- Stack traversal - select stack and inspect stack frames in current mode.
- Fixed light theme.

0.0.4
-----

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

Expand All @@ -30,14 +31,14 @@ Prefix with number to repeat:
- `N` step over
- `p` previous (switches to history mode if in current mode)
- `o` step out
- `j` scroll down
- `J` scroll down 10
- `k` scroll up
- `K` scroll up 10
- `h` scroll left
- `H` scroll left 10
- `l` scroll right
- `L` scroll right 10
- `j` down
- `J` down 10
- `k` up
- `K` up 10
- `h` left
- `H` left 10
- `l` right
- `L` right 10
- `+` increase context depth
- `-` decrease context depth
- `tab` switch pane
Expand Down
193 changes: 140 additions & 53 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use crate::dbgp::client::ContinuationResponse;
use crate::dbgp::client::ContinuationStatus;
use crate::dbgp::client::DbgpClient;
use crate::dbgp::client::Property;
use crate::dbgp::client::StackGetResponse;
use crate::event::input::AppEvent;
use crate::notification::Notification;
use crate::theme::Scheme;
Expand All @@ -30,7 +29,6 @@ use ratatui::widgets::Block;
use ratatui::widgets::Padding;
use ratatui::widgets::Paragraph;
use ratatui::Terminal;
use tokio::sync::Notify;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::io;
Expand All @@ -40,21 +38,65 @@ use tokio::net::TcpListener;
use tokio::sync::mpsc::Receiver;
use tokio::sync::mpsc::Sender;
use tokio::sync::Mutex;
use tokio::sync::Notify;
use tokio::task;
use tui_input::Input;

type AnalyzedFiles = HashMap<String,Analysis>;
type AnalyzedFiles = HashMap<String, Analysis>;

#[derive(Clone, Debug)]
pub struct HistoryEntry {
pub struct StackFrame {
pub level: u16,
pub source: SourceContext,
pub stack: StackGetResponse,
pub context: ContextGetResponse,
pub context: Option<ContextGetResponse>,
}
impl HistoryEntry {
// todo: this is inefficient!
impl StackFrame {
pub(crate) fn get_property(&self, name: &str) -> Option<&Property> {
self.context.properties.iter().find(|&property| property.name == name)
match &self.context {
Some(c) => c.properties.iter().find(|&property| property.name == name),
None => None,
}
}
}

#[derive(Clone, Debug)]
pub struct HistoryEntry {
pub stacks: Vec<StackFrame>,
}

impl HistoryEntry {
fn push(&mut self, frame: StackFrame) {
self.stacks.push(frame);
}
fn new() -> Self {
let stacks = Vec::new();
HistoryEntry { stacks }
}

fn initial(filename: String, source: String) -> HistoryEntry {
HistoryEntry {
stacks: vec![StackFrame {
level: 0,
source: SourceContext {
source,
filename,
line_no: 0,
},
context: None,
}],
}
}

pub fn source(&self, level: u16) -> SourceContext {
let entry = self.stacks.get(level as usize);
match entry {
Some(e) => e.source.clone(),
None => SourceContext::default(),
}
}

pub(crate) fn stack(&self, stack_depth: u16) -> Option<&StackFrame> {
self.stacks.get(stack_depth as usize)
}
}

Expand Down Expand Up @@ -99,22 +141,14 @@ impl History {
self.entries.get(self.offset)
}

pub(crate) fn current_mut(&mut self) -> Option<&mut HistoryEntry> {
self.entries.get_mut(self.offset)
}

fn push(&mut self, entry: HistoryEntry) {
self.entries.push(entry);
self.offset = self.entries.len() - 1;
}

fn push_source(&mut self, filename: String, source: String) {
self.push(HistoryEntry {
source: SourceContext {
source,
filename,
line_no: 1,
},
context: ContextGetResponse { properties: vec![] },
stack: StackGetResponse { entries: vec![] },
});
}
}

#[derive(Clone, Debug)]
Expand All @@ -123,6 +157,15 @@ pub struct SourceContext {
pub filename: String,
pub line_no: u32,
}
impl SourceContext {
fn default() -> SourceContext {
SourceContext {
source: "".to_string(),
filename: "".to_string(),
line_no: 0,
}
}
}

#[derive(Debug, Clone)]
pub enum CurrentView {
Expand Down Expand Up @@ -157,6 +200,8 @@ pub struct App {
pub theme: Theme,

pub analyzed_files: AnalyzedFiles,

pub stack_max_context_fetch: u16,
}

impl App {
Expand All @@ -174,6 +219,7 @@ impl App {
client: Arc::new(Mutex::new(client)),
counter: 0,
context_depth: 4,
stack_max_context_fetch: 1,

theme: Theme::SolarizedDark,
server_status: None,
Expand Down Expand Up @@ -304,8 +350,8 @@ impl App {
let mut client = self.client.lock().await;
let response = client.deref_mut().connect(s).await?;
for (feature, value) in [
("max_depth",self.context_depth.to_string().as_str()),
("extended_properties","1"),
("max_depth", self.context_depth.to_string().as_str()),
("extended_properties", "1"),
] {
info!("setting feature {} to {:?}", feature, value);
client.feature_set(feature, value).await?;
Expand All @@ -317,7 +363,8 @@ impl App {
let source = client.source(response.fileuri.clone()).await.unwrap();

self.history = History::default();
self.history.push_source(response.fileuri.clone(), source);
self.history
.push(HistoryEntry::initial(response.fileuri.clone(), source));
}
AppEvent::Snapshot() => {
self.snapshot().await?;
Expand All @@ -338,19 +385,33 @@ impl App {
AppEvent::ContextDepth(inc) => {
let depth = self.context_depth as i8;
self.context_depth = depth.wrapping_add(inc).max(0) as u8;
self.client.lock().await.feature_set(
"max_depth",
self.context_depth.to_string().as_str()
).await?;
},
self.client
.lock()
.await
.feature_set("max_depth", self.context_depth.to_string().as_str())
.await?;
}
AppEvent::ScrollSource(amount) => {
self.session_view.source_scroll = apply_scroll(self.session_view.source_scroll, amount, self.take_motion() as i16);
self.session_view.source_scroll = apply_scroll(
self.session_view.source_scroll,
amount,
self.take_motion() as i16,
);
}
AppEvent::ScrollContext(amount) => {
self.session_view.context_scroll = apply_scroll(self.session_view.context_scroll, amount, self.take_motion() as i16);
self.session_view.context_scroll = apply_scroll(
self.session_view.context_scroll,
amount,
self.take_motion() as i16,
);
}
AppEvent::ScrollStack(amount) => {
self.session_view.stack_scroll = apply_scroll(self.session_view.stack_scroll, amount, self.take_motion() as i16);
self.session_view.stack_scroll = apply_scroll(
self.session_view.stack_scroll,
amount,
self.take_motion() as i16,
);
self.populate_stack_context().await?;
}
AppEvent::ToggleFullscreen => {
self.session_view.full_screen = !self.session_view.full_screen;
Expand All @@ -369,17 +430,19 @@ impl App {
.await?;
}
AppEvent::PushInputPlurality(char) => self.input_plurality.push(char),
AppEvent::Input(key_event) => {
match key_event.code {
KeyCode::Char('t') => {
self.theme = self.theme.next();
self.notification = Notification::info(format!("Switched to theme: {:?}", self.theme));
},
KeyCode::Char('?') => {
self.sender.send(AppEvent::ChangeView(CurrentView::Help)).await.unwrap();
},
_ => self.send_event_to_current_view(event).await
AppEvent::Input(key_event) => match key_event.code {
KeyCode::Char('t') => {
self.theme = self.theme.next();
self.notification =
Notification::info(format!("Switched to theme: {:?}", self.theme));
}
KeyCode::Char('?') => {
self.sender
.send(AppEvent::ChangeView(CurrentView::Help))
.await
.unwrap();
}
_ => self.send_event_to_current_view(event).await,
},
_ => self.send_event_to_current_view(event).await,
};
Expand All @@ -400,7 +463,6 @@ impl App {
tokio::spawn(async move {
let mut last_response: Option<ContinuationResponse> = None;
for i in 0..count {

// we need to wait for the snapshot to complete before running a further
// continuation.
snapshot_notify.notified().await;
Expand Down Expand Up @@ -477,41 +539,66 @@ impl App {
pub async fn snapshot(&mut self) -> Result<()> {
let mut client = self.client.lock().await;
let stack = client.deref_mut().get_stack().await?;
if let Some(top) = stack.top_or_none() {
let filename = &top.filename;
let line_no = top.line;
let mut entry = HistoryEntry::new();
for (level, frame) in stack.entries.iter().enumerate() {
let filename = &frame.filename;
let line_no = frame.line;
let context = match (level as u16) < self.stack_max_context_fetch {
true => Some(client.deref_mut().context_get(level as u16).await.unwrap()),
false => None,
};
let source_code = client
.deref_mut()
.source(filename.to_string())
.await
.unwrap();

let source = SourceContext {
source: source_code,
filename: filename.to_string(),
line_no,
};
let context = client.deref_mut().context_get().await.unwrap();
match self.analyzed_files.entry(source.filename.clone()) {

match self.analyzed_files.entry(filename.clone()) {
Entry::Occupied(_) => (),
Entry::Vacant(vacant_entry) => {
let mut analyser = Analyser::new();
vacant_entry.insert(analyser.analyze(source.source.as_str()).unwrap());
}
};
let entry = HistoryEntry {

entry.push(StackFrame {
level: (level as u16),
source,
stack,
context,
};
self.history.push(entry);
self.session_view.reset();
});
}
self.history.push(entry);
self.session_view.reset();
Ok(())
}

pub(crate) fn theme(&self) -> Scheme {
self.theme.scheme()
}

async fn populate_stack_context(&mut self) -> Result<()> {
if !self.history.is_current() {
return Ok(());
}
let level = self.session_view.stack_scroll.0 as usize;
if let Some(c) = self.history.current_mut() {
let stack = c.stacks.get_mut(level);
if let Some(s) = stack {
if s.context.is_none() {
let mut client = self.client.lock().await;
let context = client.deref_mut().context_get(level as u16).await?;
s.context = Some(context);
}
};
};
Ok(())
}
}

fn apply_scroll(scroll: (u16, u16), amount: (i16, i16), motion: i16) -> (u16, u16) {
Expand Down
Loading