inline_value.rs

  1use std::collections::{HashMap, HashSet};
  2
  3#[derive(Debug, Clone, PartialEq, Eq)]
  4pub enum VariableLookupKind {
  5    Variable,
  6    Expression,
  7}
  8
  9#[derive(Debug, Clone, PartialEq, Eq)]
 10pub enum VariableScope {
 11    Local,
 12    Global,
 13}
 14
 15#[derive(Debug, Clone)]
 16pub struct InlineValueLocation {
 17    pub variable_name: String,
 18    pub scope: VariableScope,
 19    pub lookup: VariableLookupKind,
 20    pub row: usize,
 21    pub column: usize,
 22}
 23
 24/// A trait for providing inline values for debugging purposes.
 25///
 26/// Implementors of this trait are responsible for analyzing a given node in the
 27/// source code and extracting variable information, including their names,
 28/// scopes, and positions. This information is used to display inline values
 29/// during debugging sessions. Implementors must also handle variable scoping
 30/// themselves by traversing the syntax tree upwards to determine whether a
 31/// variable is local or global.
 32pub trait InlineValueProvider: 'static + Send + Sync {
 33    /// Provides a list of inline value locations based on the given node and source code.
 34    ///
 35    /// # Parameters
 36    /// - `node`: The root node of the active debug line. Implementors should traverse
 37    ///   upwards from this node to gather variable information and determine their scope.
 38    /// - `source`: The source code as a string slice, used to extract variable names.
 39    /// - `max_row`: The maximum row to consider when collecting variables. Variables
 40    ///   declared beyond this row should be ignored.
 41    ///
 42    /// # Returns
 43    /// A vector of `InlineValueLocation` instances, each representing a variable's
 44    /// name, scope, and the position of the inline value should be shown.
 45    fn provide(
 46        &self,
 47        node: language::Node,
 48        source: &str,
 49        max_row: usize,
 50    ) -> Vec<InlineValueLocation>;
 51}
 52
 53pub struct RustInlineValueProvider;
 54
 55impl InlineValueProvider for RustInlineValueProvider {
 56    fn provide(
 57        &self,
 58        mut node: language::Node,
 59        source: &str,
 60        max_row: usize,
 61    ) -> Vec<InlineValueLocation> {
 62        let mut variables = Vec::new();
 63        let mut variable_names = HashSet::new();
 64        let mut scope = VariableScope::Local;
 65
 66        loop {
 67            let mut variable_names_in_scope = HashMap::new();
 68            for child in node.named_children(&mut node.walk()) {
 69                if child.start_position().row >= max_row {
 70                    break;
 71                }
 72
 73                if scope == VariableScope::Local && child.kind() == "let_declaration" {
 74                    if let Some(identifier) = child.child_by_field_name("pattern") {
 75                        let variable_name = source[identifier.byte_range()].to_string();
 76
 77                        if variable_names.contains(&variable_name) {
 78                            continue;
 79                        }
 80
 81                        if let Some(index) = variable_names_in_scope.get(&variable_name) {
 82                            variables.remove(*index);
 83                        }
 84
 85                        variable_names_in_scope.insert(variable_name.clone(), variables.len());
 86                        variables.push(InlineValueLocation {
 87                            variable_name,
 88                            scope: VariableScope::Local,
 89                            lookup: VariableLookupKind::Variable,
 90                            row: identifier.end_position().row,
 91                            column: identifier.end_position().column,
 92                        });
 93                    }
 94                } else if child.kind() == "static_item" {
 95                    if let Some(name) = child.child_by_field_name("name") {
 96                        let variable_name = source[name.byte_range()].to_string();
 97                        variables.push(InlineValueLocation {
 98                            variable_name,
 99                            scope: scope.clone(),
100                            lookup: VariableLookupKind::Expression,
101                            row: name.end_position().row,
102                            column: name.end_position().column,
103                        });
104                    }
105                }
106            }
107
108            variable_names.extend(variable_names_in_scope.keys().cloned());
109
110            if matches!(node.kind(), "function_item" | "closure_expression") {
111                scope = VariableScope::Global;
112            }
113
114            if let Some(parent) = node.parent() {
115                node = parent;
116            } else {
117                break;
118            }
119        }
120
121        variables
122    }
123}
124
125pub struct PythonInlineValueProvider;
126
127impl InlineValueProvider for PythonInlineValueProvider {
128    fn provide(
129        &self,
130        mut node: language::Node,
131        source: &str,
132        max_row: usize,
133    ) -> Vec<InlineValueLocation> {
134        let mut variables = Vec::new();
135        let mut variable_names = HashSet::new();
136        let mut scope = VariableScope::Local;
137
138        loop {
139            let mut variable_names_in_scope = HashMap::new();
140            for child in node.named_children(&mut node.walk()) {
141                if child.start_position().row >= max_row {
142                    break;
143                }
144
145                if scope == VariableScope::Local {
146                    match child.kind() {
147                        "expression_statement" => {
148                            if let Some(expr) = child.child(0) {
149                                if expr.kind() == "assignment" {
150                                    if let Some(param) = expr.child(0) {
151                                        let param_identifier = if param.kind() == "identifier" {
152                                            Some(param)
153                                        } else if param.kind() == "typed_parameter" {
154                                            param.child(0)
155                                        } else {
156                                            None
157                                        };
158
159                                        if let Some(identifier) = param_identifier {
160                                            if identifier.kind() == "identifier" {
161                                                let variable_name =
162                                                    source[identifier.byte_range()].to_string();
163
164                                                if variable_names.contains(&variable_name) {
165                                                    continue;
166                                                }
167
168                                                if let Some(index) =
169                                                    variable_names_in_scope.get(&variable_name)
170                                                {
171                                                    variables.remove(*index);
172                                                }
173
174                                                variable_names_in_scope
175                                                    .insert(variable_name.clone(), variables.len());
176                                                variables.push(InlineValueLocation {
177                                                    variable_name,
178                                                    scope: VariableScope::Local,
179                                                    lookup: VariableLookupKind::Variable,
180                                                    row: identifier.end_position().row,
181                                                    column: identifier.end_position().column,
182                                                });
183                                            }
184                                        }
185                                    }
186                                }
187                            }
188                        }
189                        "function_definition" => {
190                            if let Some(params) = child.child_by_field_name("parameters") {
191                                for param in params.named_children(&mut params.walk()) {
192                                    let param_identifier = if param.kind() == "identifier" {
193                                        Some(param)
194                                    } else if param.kind() == "typed_parameter" {
195                                        param.child(0)
196                                    } else {
197                                        None
198                                    };
199
200                                    if let Some(identifier) = param_identifier {
201                                        if identifier.kind() == "identifier" {
202                                            let variable_name =
203                                                source[identifier.byte_range()].to_string();
204
205                                            if variable_names.contains(&variable_name) {
206                                                continue;
207                                            }
208
209                                            if let Some(index) =
210                                                variable_names_in_scope.get(&variable_name)
211                                            {
212                                                variables.remove(*index);
213                                            }
214
215                                            variable_names_in_scope
216                                                .insert(variable_name.clone(), variables.len());
217                                            variables.push(InlineValueLocation {
218                                                variable_name,
219                                                scope: VariableScope::Local,
220                                                lookup: VariableLookupKind::Variable,
221                                                row: identifier.end_position().row,
222                                                column: identifier.end_position().column,
223                                            });
224                                        }
225                                    }
226                                }
227                            }
228                        }
229                        "for_statement" => {
230                            if let Some(target) = child.child_by_field_name("left") {
231                                if target.kind() == "identifier" {
232                                    let variable_name = source[target.byte_range()].to_string();
233
234                                    if variable_names.contains(&variable_name) {
235                                        continue;
236                                    }
237
238                                    if let Some(index) = variable_names_in_scope.get(&variable_name)
239                                    {
240                                        variables.remove(*index);
241                                    }
242
243                                    variable_names_in_scope
244                                        .insert(variable_name.clone(), variables.len());
245                                    variables.push(InlineValueLocation {
246                                        variable_name,
247                                        scope: VariableScope::Local,
248                                        lookup: VariableLookupKind::Variable,
249                                        row: target.end_position().row,
250                                        column: target.end_position().column,
251                                    });
252                                }
253                            }
254                        }
255                        _ => {}
256                    }
257                }
258            }
259
260            variable_names.extend(variable_names_in_scope.keys().cloned());
261
262            if matches!(node.kind(), "function_definition" | "module")
263                && node.range().end_point.row < max_row
264            {
265                scope = VariableScope::Global;
266            }
267
268            if let Some(parent) = node.parent() {
269                node = parent;
270            } else {
271                break;
272            }
273        }
274
275        variables
276    }
277}