Skip to content

Commit 0634377

Browse files
authored
Evaluate expressions (#50)
* Introducing Eval (i to edit) * Parse eval response * Eval evals and prints
1 parent 53eb160 commit 0634377

File tree

9 files changed

+581
-177
lines changed

9 files changed

+581
-177
lines changed

src/app.rs

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::event::input::AppEvent;
1010
use crate::notification::Notification;
1111
use crate::theme::Scheme;
1212
use crate::theme::Theme;
13+
use crate::view::eval::draw_properties;
1314
use crate::view::help::HelpView;
1415
use crate::view::layout::LayoutView;
1516
use crate::view::listen::ListenView;
@@ -27,6 +28,7 @@ use ratatui::layout::Rect;
2728
use ratatui::prelude::CrosstermBackend;
2829
use ratatui::style::Color;
2930
use ratatui::style::Style;
31+
use ratatui::text::Line;
3032
use ratatui::widgets::Block;
3133
use ratatui::widgets::Padding;
3234
use ratatui::widgets::Paragraph;
@@ -216,7 +218,7 @@ pub struct App {
216218
pub counter: u16,
217219

218220
pub snapshot_notify: Arc<Notify>,
219-
pub context_depth: u8,
221+
pub context_depth: u16,
220222
pub theme: Theme,
221223

222224
pub analyzed_files: AnalyzedFiles,
@@ -288,7 +290,7 @@ impl App {
288290
Ok(_) => (),
289291
Err(e) => error!(
290292
"Could not send connection event: {}",
291-
e.to_string()
293+
e
292294
),
293295
}
294296
}
@@ -434,8 +436,8 @@ impl App {
434436
self.exec_continuation(AppEvent::Run).await;
435437
}
436438
AppEvent::ContextDepth(inc) => {
437-
let depth = self.context_depth as i8;
438-
self.context_depth = depth.wrapping_add(inc).max(0) as u8;
439+
let depth = self.context_depth;
440+
self.context_depth = depth.wrapping_add(inc as u16).max(0);
439441
self.client
440442
.lock()
441443
.await
@@ -490,6 +492,28 @@ impl App {
490492
.await?;
491493
}
492494
AppEvent::PushInputPlurality(char) => self.input_plurality.push(char),
495+
AppEvent::EvalStart => {
496+
self.session_view.eval_state.active = true;
497+
self.focus_view = true;
498+
},
499+
AppEvent::EvalCancel => {
500+
self.session_view.eval_state.active = false;
501+
self.focus_view = false;
502+
},
503+
AppEvent::EvalExecute => {
504+
self.session_view.eval_state.active = false;
505+
self.focus_view = false;
506+
let response = self.client
507+
.lock()
508+
.await
509+
.eval(
510+
self.session_view.eval_state.input.to_string(),
511+
self.session_view.stack_depth()
512+
)
513+
.await?;
514+
515+
self.session_view.eval_state.properties = response.properties;
516+
},
493517
AppEvent::Input(key_event) => {
494518
if self.focus_view {
495519
// event shandled exclusively by view (e.g. input needs focus)
@@ -654,7 +678,7 @@ impl App {
654678
if !self.history.is_current() {
655679
return Ok(());
656680
}
657-
let level = self.session_view.stack_scroll.0 as usize;
681+
let level = self.session_view.stack_level();
658682
if let Some(c) = self.history.current_mut() {
659683
let stack = c.stacks.get_mut(level);
660684
if let Some(s) = stack {

src/dbgp/client.rs

Lines changed: 129 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ pub enum CommandResponse {
3131
StackGet(StackGetResponse),
3232
Source(String),
3333
ContextGet(ContextGetResponse),
34+
Eval(EvalResponse),
35+
}
36+
37+
#[derive(Debug, Clone, PartialEq)]
38+
pub struct DbgpError {
39+
pub message: String,
40+
pub code: String,
3441
}
3542

3643
#[derive(Debug, Clone, PartialEq)]
@@ -135,6 +142,13 @@ pub struct ContinuationResponse {
135142
pub reason: String,
136143
}
137144

145+
#[derive(Debug, Clone, PartialEq)]
146+
pub struct EvalResponse {
147+
pub success: bool,
148+
pub error: Option<DbgpError>,
149+
pub properties: Vec<Property>,
150+
}
151+
138152
#[derive(Debug, Clone)]
139153
pub struct StackGetResponse {
140154
pub entries: Vec<StackEntry>,
@@ -244,6 +258,17 @@ impl DbgpClient {
244258
}
245259
}
246260

261+
pub(crate) async fn eval(&mut self, expression: String, depth: u16) -> Result<EvalResponse> {
262+
let base64 = general_purpose::STANDARD.encode(expression.as_bytes());
263+
match self.command("eval", &mut ["-d", format!("{}", depth).as_str(), "--", &base64]).await? {
264+
Message::Response(r) => match r.command {
265+
CommandResponse::Eval(s) => Ok(s),
266+
_ => anyhow::bail!("Unexpected response"),
267+
},
268+
_ => anyhow::bail!("Unexpected response"),
269+
}
270+
}
271+
247272
pub(crate) async fn step_into(&mut self) -> Result<ContinuationResponse> {
248273
match self.command("step_into", &mut []).await? {
249274
Message::Response(r) => match r.command {
@@ -368,6 +393,7 @@ fn parse_xml(xml: &str) -> Result<Message, anyhow::Error> {
368393
"stack_get" => CommandResponse::StackGet(parse_stack_get(&root)),
369394
"source" => CommandResponse::Source(parse_source(&root)?),
370395
"context_get" => CommandResponse::ContextGet(parse_context_get(&mut root)?),
396+
"eval" => CommandResponse::Eval(parse_eval(&mut root)?),
371397
_ => CommandResponse::Unknown,
372398
},
373399
})),
@@ -377,13 +403,40 @@ fn parse_xml(xml: &str) -> Result<Message, anyhow::Error> {
377403
fn parse_source(element: &Element) -> Result<String, anyhow::Error> {
378404
match element.children.first() {
379405
Some(XMLNode::CData(e)) => {
380-
Ok(String::from_utf8(general_purpose::STANDARD.decode(e).unwrap()).unwrap())
406+
Ok(String::from_utf8(general_purpose::STANDARD.decode(e)?)?)
381407
}
382408
_ => anyhow::bail!("Expected CDATA"),
383409
}
384410
}
385411

412+
386413
fn parse_context_get(element: &mut Element) -> Result<ContextGetResponse, anyhow::Error> {
414+
Ok(ContextGetResponse { properties: parse_properties(element)?})
415+
}
416+
417+
fn parse_eval(element: &mut Element) -> Result<EvalResponse, anyhow::Error> {
418+
419+
let error = if let Some(mut error_el) = element.take_child("error") {
420+
let code = error_el.attributes.get("code").map_or("".to_string(), |v|v.to_string());
421+
let message = match error_el.take_child("message") {
422+
Some(m) => match m.children.first() {
423+
Some(XMLNode::CData(e)) => {
424+
e.to_string()
425+
},
426+
_ => String::new(),
427+
},
428+
_ => "".to_string()
429+
};
430+
431+
Some(DbgpError{message, code: code.to_string()})
432+
} else {
433+
None
434+
};
435+
436+
Ok(EvalResponse { success: true, properties: parse_properties(element)?, error })
437+
}
438+
439+
fn parse_properties(element: &mut Element) -> Result<Vec<Property>, anyhow::Error> {
387440
let mut properties: Vec<Property> = vec![];
388441
while let Some(mut child) = element.take_child("property") {
389442
let encoding = child.attributes.get("encoding").map(|s| s.to_string());
@@ -428,12 +481,12 @@ fn parse_context_get(element: &mut Element) -> Result<ContextGetResponse, anyhow
428481
key: child.attributes.get("key").map(|name| name.to_string()),
429482
address: child.attributes.get("address").map(|name| name.to_string()),
430483
encoding: encoding.clone(),
431-
children: parse_context_get(&mut child).unwrap().properties,
484+
children: parse_properties(&mut child)?,
432485
value: decode_element(Some(&child)),
433486
};
434487
properties.push(p);
435488
}
436-
Ok(ContextGetResponse { properties })
489+
Ok(properties)
437490
}
438491

439492
fn decode_element(element: Option<&Element>) -> Option<String> {
@@ -601,6 +654,79 @@ function call_function(string $hello) {
601654
Ok(())
602655
}
603656

657+
#[test]
658+
fn test_parse_eval() -> Result<(), anyhow::Error> {
659+
let result = parse_xml(
660+
r#"
661+
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="https://xdebug.org/dbgp/xdebug" command="eval" transaction_id="28"><property type="int"><![CDATA[2]]></property></response>
662+
"#,
663+
)?;
664+
665+
match result {
666+
Message::Response(r) => {
667+
match r.command {
668+
CommandResponse::Eval(response) => {
669+
let expected = EvalResponse {
670+
success: true,
671+
error: None,
672+
properties: vec![
673+
Property {
674+
name: "".to_string(),
675+
fullname: "".to_string(),
676+
classname: None,
677+
page: None,
678+
pagesize: None,
679+
property_type: PropertyType::Int,
680+
facet: None,
681+
size: None,
682+
children: vec![],
683+
key: None,
684+
address: None,
685+
encoding: None,
686+
value: Some(2.to_string()),
687+
},
688+
],
689+
};
690+
assert_eq!(expected, response)
691+
}
692+
_ => panic!("Could not parse context_get"),
693+
};
694+
}
695+
_ => panic!("Did not parse"),
696+
};
697+
Ok(())
698+
}
699+
700+
#[test]
701+
fn test_parse_eval_error() -> Result<(), anyhow::Error> {
702+
let result = parse_xml(
703+
r#"
704+
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="https://xdebug.org/dbgp/xdebug" command="eval" transaction_id="6" status="break" reason="ok"><error code="206"><message><![CDATA[error evaluating code: Undefined constant "asda"]]></message></error></response>
705+
"#,
706+
)?;
707+
708+
match result {
709+
Message::Response(r) => {
710+
match r.command {
711+
CommandResponse::Eval(response) => {
712+
let expected = EvalResponse {
713+
success: true,
714+
error: Some(DbgpError {
715+
message: "error evaluating code: Undefined constant \"asda\"".to_string(),
716+
code: "206".to_string()
717+
}),
718+
properties: vec![],
719+
};
720+
assert_eq!(expected, response)
721+
}
722+
_ => panic!("Could not parse context_get"),
723+
};
724+
}
725+
_ => panic!("Did not parse"),
726+
};
727+
Ok(())
728+
}
729+
604730
#[test]
605731
fn test_parse_context_get() -> Result<(), anyhow::Error> {
606732
let result = parse_xml(

src/event/input.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ pub enum AppEvent {
5050
ContextFilterOpen,
5151
ContextSearchClose,
5252
Listen,
53+
EvalCancel,
54+
EvalExecute,
55+
EvalStart,
5356
}
5457

5558
pub type EventSender = Sender<AppEvent>;

src/view/common.rs

Whitespace-only changes.

0 commit comments

Comments
 (0)