diff --git a/CHANGELOG.md b/CHANGELOG.md index baee33b..a05fe5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ main - Introduce eval feature - Improve scrolling performance on large files and contexts - Fix position of closing braces in context view +- Show historic inline values +- Do not show "undefined" variables 0.1.1 ----- diff --git a/src/app.rs b/src/app.rs index d440834..48aac83 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,5 +1,6 @@ use crate::analyzer::Analyser; use crate::analyzer::Analysis; +use crate::analyzer::VariableRef; use crate::config::Config; use crate::dbgp::client::ContextGetResponse; use crate::dbgp::client::ContinuationResponse; @@ -54,10 +55,33 @@ pub struct StackFrame { pub source: SourceContext, pub context: Option, } +#[derive(Clone,Debug)] +pub struct Variable { + pub var_ref: VariableRef, + pub value: Property, +} +#[derive(Default)] +pub struct DocumentVariables { + doclinemap: HashMap> +} +impl DocumentVariables { + pub fn put(&mut self, context: &SourceContext, variables: Vec) { + let entry = self.doclinemap.entry(format!("{}:{}", context.filename, context.line_no)); + entry.insert_entry(variables); + } + + pub fn get(&self, source_file: &String, line_no: u32) -> Vec { + match self.doclinemap.get(&format!("{}:{}", source_file, line_no)) { + Some(v) => v.to_vec(), + None => vec![], + } + + } +} impl StackFrame { pub(crate) fn get_property(&self, name: &str) -> Option<&Property> { match &self.context { - Some(c) => c.properties.iter().find(|&property| property.name == name), + Some(c) => c.properties.get(name), None => None, } } @@ -221,6 +245,7 @@ pub struct App { pub workspace: Workspace, pub history: History, + pub document_variables: DocumentVariables, pub view_current: SelectedView, pub focus_view: bool, @@ -252,6 +277,7 @@ impl App { sender: sender.clone(), quit: false, history: History::default(), + document_variables: DocumentVariables::default(), client: Arc::clone(&client), workspace: Workspace::new(Arc::clone(&client)), @@ -704,11 +730,30 @@ impl App { } }; - entry.push(StackFrame { + let analysis = self.analyzed_files.get(&filename.clone()); + + let stack = StackFrame { level: (level as u16), source, context, - }); + }; + + { + // populate inline variables with values + let mut vars = vec![]; + if let Some(analysis) = analysis { + for (_, var) in analysis.row(line_no as usize - 1) { + let property = stack.get_property(var.name.as_str()); + if let Some(property) = property { + vars.push(Variable{ var_ref: var, value: property.clone() }); + } + } + + self.document_variables.put(&stack.source, vars); + } + } + + entry.push(stack); } // *xdebug* only evalutes expressions on the current stack frame diff --git a/src/dbgp/client.rs b/src/dbgp/client.rs index 0d43ae7..6b9b532 100644 --- a/src/dbgp/client.rs +++ b/src/dbgp/client.rs @@ -42,8 +42,45 @@ pub struct DbgpError { #[derive(Debug, Clone, PartialEq)] pub struct ContextGetResponse { + pub properties: Properties, +} +impl ContextGetResponse { +} +#[derive(Debug, Clone, PartialEq, Default)] +pub struct Properties { pub properties: Vec, } +impl Properties { + pub fn is_empty(&self) -> bool { + self.properties.is_empty() + } + pub fn none() -> Self { + Self{properties:vec![]} + } + pub fn defined_properties(&self) -> Vec<&Property> { + let mut props = vec![]; + for property in &self.properties { + if property.property_type == PropertyType::Undefined { + continue; + } + props.push(property); + } + props + } + + pub(crate) fn get(&self, name: &str) -> Option<&Property> { + for property in self.defined_properties() { + if property.name == name { + return Some(property) + } + } + None + } + + pub fn from_properties(vec: Vec) -> Properties { + Self{properties:vec} + } +} #[derive(PartialEq, Clone, Debug, Default)] pub enum PropertyType { @@ -108,7 +145,7 @@ pub struct Property { pub property_type: PropertyType, pub facet: Option, pub size: Option, - pub children: Vec, + pub children: Properties, pub key: Option, pub address: Option, pub encoding: Option, @@ -146,7 +183,7 @@ pub struct ContinuationResponse { pub struct EvalResponse { pub success: bool, pub error: Option, - pub properties: Vec, + pub properties: Properties, } #[derive(Debug, Clone)] @@ -411,7 +448,7 @@ fn parse_source(element: &Element) -> Result { fn parse_context_get(element: &mut Element) -> Result { - Ok(ContextGetResponse { properties: parse_properties(element)?}) + Ok(ContextGetResponse { properties: Properties::from_properties(parse_properties(element)?)}) } fn parse_eval(element: &mut Element) -> Result { @@ -433,7 +470,7 @@ fn parse_eval(element: &mut Element) -> Result { None }; - Ok(EvalResponse { success: true, properties: parse_properties(element)?, error }) + Ok(EvalResponse { success: true, properties: Properties::from_properties(parse_properties(element)?), error}) } fn parse_properties(element: &mut Element) -> Result, anyhow::Error> { @@ -481,7 +518,7 @@ fn parse_properties(element: &mut Element) -> Result, anyhow::Erro key: child.attributes.get("key").map(|name| name.to_string()), address: child.attributes.get("address").map(|name| name.to_string()), encoding: encoding.clone(), - children: parse_properties(&mut child)?, + children: Properties::from_properties(parse_properties(&mut child)?), value: decode_element(Some(&child)), }; properties.push(p); @@ -669,7 +706,7 @@ function call_function(string $hello) { let expected = EvalResponse { success: true, error: None, - properties: vec![ + properties: Properties::from_properties(vec![ Property { name: "".to_string(), fullname: "".to_string(), @@ -679,13 +716,13 @@ function call_function(string $hello) { property_type: PropertyType::Int, facet: None, size: None, - children: vec![], + children: Properties::none(), key: None, address: None, encoding: None, value: Some(2.to_string()), }, - ], + ]), }; assert_eq!(expected, response) } @@ -715,7 +752,7 @@ function call_function(string $hello) { message: "error evaluating code: Undefined constant \"asda\"".to_string(), code: "206".to_string() }), - properties: vec![], + properties: Properties::none(), }; assert_eq!(expected, response) } @@ -750,7 +787,7 @@ function call_function(string $hello) { match r.command { CommandResponse::ContextGet(response) => { let expected = ContextGetResponse { - properties: vec![ + properties: Properties::from_properties(vec![ Property { name: "$bar".to_string(), fullname: "$bar".to_string(), @@ -760,7 +797,7 @@ function call_function(string $hello) { property_type: PropertyType::String, facet: None, size: Some(3), - children: vec![], + children: Properties::none(), key: None, address: None, encoding: Some("base64".to_string()), @@ -775,7 +812,7 @@ function call_function(string $hello) { property_type: PropertyType::Float, facet: None, size: None, - children: vec![], + children: Properties::none(), key: None, address: None, encoding: None, @@ -790,7 +827,7 @@ function call_function(string $hello) { property_type: PropertyType::Int, facet: None, size: None, - children: vec![], + children: Properties::none(), key: None, address: None, encoding: None, @@ -805,7 +842,7 @@ function call_function(string $hello) { property_type: PropertyType::Bool, facet: None, size: None, - children: vec![], + children: Properties::none(), key: None, address: None, encoding: None, @@ -820,7 +857,7 @@ function call_function(string $hello) { property_type: PropertyType::Object, facet: None, size: None, - children: vec![ + children: Properties::from_properties(vec![ Property { name: "true".to_string(), fullname: "$this->true".to_string(), @@ -830,7 +867,7 @@ function call_function(string $hello) { property_type: PropertyType::Bool, facet: Some("public".to_string()), size: None, - children: vec![], + children: Properties::none(), key: None, address: None, encoding: None, @@ -845,7 +882,7 @@ function call_function(string $hello) { property_type: PropertyType::String, facet: Some("public".to_string()), size: Some(3), - children: vec![], + children: Properties::none(), key: None, address: None, encoding: Some("base64".to_string()), @@ -860,13 +897,13 @@ function call_function(string $hello) { property_type: PropertyType::Resource, facet: Some("private".to_string()), size: None, - children: vec![], + children: Properties::none(), key: None, address: None, encoding: None, value: Some("resource id='18' type='stream'".to_string()), }, - ], + ]), key: None, address: None, encoding: None, @@ -881,14 +918,14 @@ function call_function(string $hello) { property_type: PropertyType::Array, facet: None, size: None, - children: vec![], + children: Properties::none(), key: None, address: None, encoding: None, value: None, }, - ], - }; + ]), + }; assert_eq!(expected, response) } _ => panic!("Could not parse context_get"), diff --git a/src/theme.rs b/src/theme.rs index 65c1051..2a708f9 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -56,6 +56,7 @@ impl Theme { source_line_no: Style::default().fg(Solarized::Yellow.to_color()), source_line_highlight: Style::default().bg(Solarized::Base02.to_color()).fg(Solarized::Base3.to_color()), source_annotation: Style::default().fg(Solarized::Magenta.to_color()), + source_annotation_historic: Style::default().fg(Solarized::Base01.to_color()), stack_line: Style::default().fg(Solarized::Base1.to_color()), widget_active: Style::default().fg(Solarized::Base02.to_color()).bg(Solarized::Green.to_color()), @@ -87,7 +88,8 @@ impl Theme { source_line: Style::default().fg(Color::White), source_line_no: Style::default().fg(Color::Yellow), source_line_highlight: Style::default().bg(Color::Blue), - source_annotation: Style::default().fg(Color::DarkGray), + source_annotation: Style::default().fg(Color::Cyan), + source_annotation_historic: Style::default().fg(Color::DarkGray), stack_line: Style::default().fg(Color::White), @@ -124,6 +126,7 @@ pub struct Scheme { pub source_line_no: Style, pub source_line_highlight: Style, pub source_annotation: Style, + pub source_annotation_historic: Style, pub stack_line: Style, diff --git a/src/view/context.rs b/src/view/context.rs index 01fdf6d..926cade 100644 --- a/src/view/context.rs +++ b/src/view/context.rs @@ -81,7 +81,7 @@ impl View for ContextComponent { let truncate_from = app.session_view.context_scroll.0 as u32; draw_properties( &app.theme(), - &context.properties, + context.properties.defined_properties(), &mut lines, 0, &mut filter_path, diff --git a/src/view/eval.rs b/src/view/eval.rs index eea398c..2b6ac7d 100644 --- a/src/view/eval.rs +++ b/src/view/eval.rs @@ -48,7 +48,7 @@ impl View for EvalComponent { let mut lines: Vec = Vec::new(); draw_properties( &app.theme(), - &eval_entry.response.properties, + eval_entry.response.properties.defined_properties(), &mut lines, 0, &mut Vec::new(), @@ -120,7 +120,7 @@ impl View for EvalDialog { pub fn draw_properties( theme: &Scheme, - properties: &Vec, + properties: Vec<&Property>, lines: &mut Vec, level: usize, filter_path: &mut Vec<&str>, @@ -160,7 +160,7 @@ pub fn draw_properties( lines.push(Line::from(spans)); if !property.children.is_empty() { - draw_properties(theme, &property.children, lines, level + 1, filter_path); + draw_properties(theme, property.children.defined_properties(), lines, level + 1, filter_path); lines.push(Line::from(vec![Span::raw(delimiters.1)]).style(theme.syntax_brace)); } } @@ -185,7 +185,7 @@ pub fn render_value<'a>(theme: &Scheme, property: &Property) -> Span<'a> { #[cfg(test)] mod test { use super::*; - use crate::theme::Theme; + use crate::{dbgp::client::Properties, theme::Theme}; use anyhow::Result; use pretty_assertions::assert_eq; @@ -194,7 +194,7 @@ mod test { let mut lines = vec![]; draw_properties( &Theme::SolarizedDark.scheme(), - &Vec::new(), + Vec::new(), &mut lines, 0, &mut Vec::new(), @@ -209,12 +209,12 @@ mod test { let mut prop1 = Property::default(); let mut prop2 = Property::default(); prop2.name = "bar".to_string(); - prop1.children = vec![prop2]; + prop1.children = Properties::from_properties(vec![prop2]); prop1.name = "foo".to_string(); draw_properties( &Theme::SolarizedDark.scheme(), - &vec![prop1], + vec![&prop1], &mut lines, 0, &mut Vec::new(), @@ -237,7 +237,7 @@ mod test { let prop3 = Property::default(); prop2.name = "bar".to_string(); - prop1.children = vec![prop2]; + prop1.children = Properties::from_properties(vec![prop2]); prop1.name = "foo".to_string(); // segments are reversed @@ -245,7 +245,7 @@ mod test { draw_properties( &Theme::SolarizedDark.scheme(), - &vec![prop1, prop3], + vec![&prop1, &prop3], &mut lines, 0, filter, diff --git a/src/view/properties.rs b/src/view/properties.rs index 9c8461f..daa66cc 100644 --- a/src/view/properties.rs +++ b/src/view/properties.rs @@ -7,7 +7,7 @@ use ratatui::text::Span; pub fn draw_properties( theme: &Scheme, - properties: &Vec, + properties: Vec<&Property>, lines: &mut Vec, level: usize, filter_path: &mut Vec<&str>, @@ -53,7 +53,7 @@ pub fn draw_properties( *line_no += 1; if !property.children.is_empty() { - draw_properties(theme, &property.children, lines, level + 1, filter_path, truncate_until, line_no); + draw_properties(theme, property.children.defined_properties(), lines, level + 1, filter_path, truncate_until, line_no); if *line_no >= *truncate_until { lines.push(Line::from(vec![ Span::raw(format!("{}{}", " ".repeat(level), delimiters.1)) @@ -82,7 +82,7 @@ pub fn render_value<'a>(theme: &Scheme, property: &Property) -> Span<'a> { #[cfg(test)] mod test { - use crate::theme::Theme; + use crate::{dbgp::client::Properties, theme::Theme}; use anyhow::Result; use super::*; @@ -93,7 +93,7 @@ mod test { let mut lines = vec![]; draw_properties( &Theme::SolarizedDark.scheme(), - &Vec::new(), + vec![], &mut lines, 0, &mut Vec::new(), @@ -110,15 +110,15 @@ mod test { let mut prop1 = Property::default(); let mut prop2 = Property::default(); prop2.name = "bar".to_string(); - prop1.children = vec![ + prop1.children = Properties::from_properties(vec![ prop2 - ]; + ]); prop1.name = "foo".to_string(); draw_properties( &Theme::SolarizedDark.scheme(), - &vec![ - prop1 + vec![ + &prop1 ], &mut lines, 0, @@ -144,9 +144,9 @@ mod test { let prop3 = Property::default(); prop2.name = "bar".to_string(); - prop1.children = vec![ + prop1.children = Properties::from_properties(vec![ prop2 - ]; + ]); prop1.name = "foo".to_string(); // segments are reversed @@ -157,9 +157,9 @@ mod test { draw_properties( &Theme::SolarizedDark.scheme(), - &vec![ - prop1, - prop3 + vec![ + &prop1, + &prop3 ], &mut lines, 0, diff --git a/src/view/source.rs b/src/view/source.rs index 3db6473..5c1593c 100644 --- a/src/view/source.rs +++ b/src/view/source.rs @@ -47,11 +47,6 @@ impl View for SourceComponent { Some(stack) => stack }; - let analysis = app - .analyzed_files - .get(&stack.source.filename.to_string()); - - // trunacte the hidden lines let truncate_until = app.session_view.source_scroll.0 as u32 + 1; @@ -71,30 +66,28 @@ impl View for SourceComponent { }, ])); - // record annotations to add at the end of the line - let mut labels = vec![Span::raw("// ")]; - - if is_current_line { - if let Some(analysis) = analysis { - for (_, var) in analysis.row(line_offset) { - let property = stack.get_property(var.name.as_str()); - if property.is_none() { - continue; - } - match render_label(property.unwrap()) { - Some(label) => labels.push(Span::raw(label)), - None => continue, - }; - labels.push(Span::raw(",")); - } - if labels.len() > 1 { - labels.pop(); - annotations.push(( - line_offset, - line.len() + 8, - Line::from(labels).style(app.theme().source_annotation), - )); - } + { + // record annotations to add at the end of the line + let mut labels = vec![Span::raw("// ")]; + for var in app.document_variables.get(&stack.source.filename, line_no as u32).iter() { + match render_label(&var.value) { + Some(label) => labels.push(Span::raw(label)), + None => continue, + }; + labels.push(Span::raw(",")); + } + if labels.len() > 1 { + labels.pop(); + annotations.push(( + line_offset, + line.len() + 8, + Line::from(labels).style( + match is_current_line { + true => app.theme().source_annotation, + false => app.theme().source_annotation_historic, + } + ), + )); } } } @@ -136,7 +129,7 @@ fn render_label(property: &Property) -> Option { PropertyType::Object | PropertyType::Array | PropertyType::Hash => { format!("{}{{{}}}", property.type_name(), { let mut labels: Vec = Vec::new(); - for child in &property.children { + for child in &property.children.defined_properties() { let label = render_label(child); if label.is_none() { continue; @@ -165,6 +158,8 @@ fn render_label(property: &Property) -> Option { #[cfg(test)] mod test { + use crate::dbgp::client::Properties; + use super::*; use pretty_assertions::assert_eq; @@ -214,7 +209,7 @@ mod test { property_type, facet: None, size: None, - children: Vec::new(), + children: Properties::none(), key: None, address: None, encoding: None, @@ -232,7 +227,7 @@ mod test { property_type: PropertyType::Resource, facet: Some("private".to_string()), size: None, - children: vec![], + children: Properties::none(), key: None, address: None, encoding: None, @@ -250,7 +245,7 @@ mod test { property_type: PropertyType::Object, facet: None, size: None, - children: vec![ + children: Properties::from_properties(vec![ Property { name: "true".to_string(), fullname: "true".to_string(), @@ -260,7 +255,7 @@ mod test { property_type: PropertyType::Bool, facet: Some("public".to_string()), size: None, - children: vec![], + children: Properties::none(), key: None, address: None, encoding: None, @@ -275,13 +270,13 @@ mod test { property_type: PropertyType::String, facet: Some("public".to_string()), size: Some(3), - children: vec![], + children: Properties::none(), key: None, address: None, encoding: Some("base64".to_string()), value: Some("foo".to_string()), }, - ], + ]), key: None, address: None, encoding: None,