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}
278
279pub struct GoInlineValueProvider;
280
281impl InlineValueProvider for GoInlineValueProvider {
282    fn provide(
283        &self,
284        mut node: language::Node,
285        source: &str,
286        max_row: usize,
287    ) -> Vec<InlineValueLocation> {
288        let mut variables = Vec::new();
289        let mut variable_names = HashSet::new();
290        let mut scope = VariableScope::Local;
291
292        loop {
293            let mut variable_names_in_scope = HashMap::new();
294            for child in node.named_children(&mut node.walk()) {
295                if child.start_position().row >= max_row {
296                    break;
297                }
298
299                if scope == VariableScope::Local {
300                    match child.kind() {
301                        "var_declaration" => {
302                            for var_spec in child.named_children(&mut child.walk()) {
303                                if var_spec.kind() == "var_spec" {
304                                    if let Some(name_node) = var_spec.child_by_field_name("name") {
305                                        let variable_name =
306                                            source[name_node.byte_range()].to_string();
307
308                                        if variable_names.contains(&variable_name) {
309                                            continue;
310                                        }
311
312                                        if let Some(index) =
313                                            variable_names_in_scope.get(&variable_name)
314                                        {
315                                            variables.remove(*index);
316                                        }
317
318                                        variable_names_in_scope
319                                            .insert(variable_name.clone(), variables.len());
320                                        variables.push(InlineValueLocation {
321                                            variable_name,
322                                            scope: VariableScope::Local,
323                                            lookup: VariableLookupKind::Variable,
324                                            row: name_node.end_position().row,
325                                            column: name_node.end_position().column,
326                                        });
327                                    }
328                                }
329                            }
330                        }
331                        "short_var_declaration" => {
332                            if let Some(left_side) = child.child_by_field_name("left") {
333                                for identifier in left_side.named_children(&mut left_side.walk()) {
334                                    if identifier.kind() == "identifier" {
335                                        let variable_name =
336                                            source[identifier.byte_range()].to_string();
337
338                                        if variable_names.contains(&variable_name) {
339                                            continue;
340                                        }
341
342                                        if let Some(index) =
343                                            variable_names_in_scope.get(&variable_name)
344                                        {
345                                            variables.remove(*index);
346                                        }
347
348                                        variable_names_in_scope
349                                            .insert(variable_name.clone(), variables.len());
350                                        variables.push(InlineValueLocation {
351                                            variable_name,
352                                            scope: VariableScope::Local,
353                                            lookup: VariableLookupKind::Variable,
354                                            row: identifier.end_position().row,
355                                            column: identifier.end_position().column,
356                                        });
357                                    }
358                                }
359                            }
360                        }
361                        "assignment_statement" => {
362                            if let Some(left_side) = child.child_by_field_name("left") {
363                                for identifier in left_side.named_children(&mut left_side.walk()) {
364                                    if identifier.kind() == "identifier" {
365                                        let variable_name =
366                                            source[identifier.byte_range()].to_string();
367
368                                        if variable_names.contains(&variable_name) {
369                                            continue;
370                                        }
371
372                                        if let Some(index) =
373                                            variable_names_in_scope.get(&variable_name)
374                                        {
375                                            variables.remove(*index);
376                                        }
377
378                                        variable_names_in_scope
379                                            .insert(variable_name.clone(), variables.len());
380                                        variables.push(InlineValueLocation {
381                                            variable_name,
382                                            scope: VariableScope::Local,
383                                            lookup: VariableLookupKind::Variable,
384                                            row: identifier.end_position().row,
385                                            column: identifier.end_position().column,
386                                        });
387                                    }
388                                }
389                            }
390                        }
391                        "function_declaration" | "method_declaration" => {
392                            if let Some(params) = child.child_by_field_name("parameters") {
393                                for param in params.named_children(&mut params.walk()) {
394                                    if param.kind() == "parameter_declaration" {
395                                        if let Some(name_node) = param.child_by_field_name("name") {
396                                            let variable_name =
397                                                source[name_node.byte_range()].to_string();
398
399                                            if variable_names.contains(&variable_name) {
400                                                continue;
401                                            }
402
403                                            if let Some(index) =
404                                                variable_names_in_scope.get(&variable_name)
405                                            {
406                                                variables.remove(*index);
407                                            }
408
409                                            variable_names_in_scope
410                                                .insert(variable_name.clone(), variables.len());
411                                            variables.push(InlineValueLocation {
412                                                variable_name,
413                                                scope: VariableScope::Local,
414                                                lookup: VariableLookupKind::Variable,
415                                                row: name_node.end_position().row,
416                                                column: name_node.end_position().column,
417                                            });
418                                        }
419                                    }
420                                }
421                            }
422                        }
423                        "for_statement" => {
424                            if let Some(clause) = child.named_child(0) {
425                                if clause.kind() == "for_clause" {
426                                    if let Some(init) = clause.named_child(0) {
427                                        if init.kind() == "short_var_declaration" {
428                                            if let Some(left_side) =
429                                                init.child_by_field_name("left")
430                                            {
431                                                if left_side.kind() == "expression_list" {
432                                                    for identifier in left_side
433                                                        .named_children(&mut left_side.walk())
434                                                    {
435                                                        if identifier.kind() == "identifier" {
436                                                            let variable_name = source
437                                                                [identifier.byte_range()]
438                                                            .to_string();
439
440                                                            if variable_names
441                                                                .contains(&variable_name)
442                                                            {
443                                                                continue;
444                                                            }
445
446                                                            if let Some(index) =
447                                                                variable_names_in_scope
448                                                                    .get(&variable_name)
449                                                            {
450                                                                variables.remove(*index);
451                                                            }
452
453                                                            variable_names_in_scope.insert(
454                                                                variable_name.clone(),
455                                                                variables.len(),
456                                                            );
457                                                            variables.push(InlineValueLocation {
458                                                                variable_name,
459                                                                scope: VariableScope::Local,
460                                                                lookup:
461                                                                    VariableLookupKind::Variable,
462                                                                row: identifier.end_position().row,
463                                                                column: identifier
464                                                                    .end_position()
465                                                                    .column,
466                                                            });
467                                                        }
468                                                    }
469                                                }
470                                            }
471                                        }
472                                    }
473                                } else if clause.kind() == "range_clause" {
474                                    if let Some(left) = clause.child_by_field_name("left") {
475                                        if left.kind() == "expression_list" {
476                                            for identifier in left.named_children(&mut left.walk())
477                                            {
478                                                if identifier.kind() == "identifier" {
479                                                    let variable_name =
480                                                        source[identifier.byte_range()].to_string();
481
482                                                    if variable_name == "_" {
483                                                        continue;
484                                                    }
485
486                                                    if variable_names.contains(&variable_name) {
487                                                        continue;
488                                                    }
489
490                                                    if let Some(index) =
491                                                        variable_names_in_scope.get(&variable_name)
492                                                    {
493                                                        variables.remove(*index);
494                                                    }
495                                                    variable_names_in_scope.insert(
496                                                        variable_name.clone(),
497                                                        variables.len(),
498                                                    );
499                                                    variables.push(InlineValueLocation {
500                                                        variable_name,
501                                                        scope: VariableScope::Local,
502                                                        lookup: VariableLookupKind::Variable,
503                                                        row: identifier.end_position().row,
504                                                        column: identifier.end_position().column,
505                                                    });
506                                                }
507                                            }
508                                        }
509                                    }
510                                }
511                            }
512                        }
513                        _ => {}
514                    }
515                } else if child.kind() == "var_declaration" {
516                    for var_spec in child.named_children(&mut child.walk()) {
517                        if var_spec.kind() == "var_spec" {
518                            if let Some(name_node) = var_spec.child_by_field_name("name") {
519                                let variable_name = source[name_node.byte_range()].to_string();
520                                variables.push(InlineValueLocation {
521                                    variable_name,
522                                    scope: VariableScope::Global,
523                                    lookup: VariableLookupKind::Expression,
524                                    row: name_node.end_position().row,
525                                    column: name_node.end_position().column,
526                                });
527                            }
528                        }
529                    }
530                }
531            }
532
533            variable_names.extend(variable_names_in_scope.keys().cloned());
534
535            if matches!(node.kind(), "function_declaration" | "method_declaration") {
536                scope = VariableScope::Global;
537            }
538
539            if let Some(parent) = node.parent() {
540                node = parent;
541            } else {
542                break;
543            }
544        }
545
546        variables
547    }
548}
549#[cfg(test)]
550mod tests {
551    use super::*;
552    use tree_sitter::Parser;
553
554    #[test]
555    fn test_go_inline_value_provider() {
556        let provider = GoInlineValueProvider;
557        let source = r#"
558package main
559
560func main() {
561    items := []int{1, 2, 3, 4, 5}
562    for i, v := range items {
563        println(i, v)
564    }
565    for j := 0; j < 10; j++ {
566        println(j)
567    }
568}
569"#;
570
571        let mut parser = Parser::new();
572        if parser
573            .set_language(&tree_sitter_go::LANGUAGE.into())
574            .is_err()
575        {
576            return;
577        }
578        let Some(tree) = parser.parse(source, None) else {
579            return;
580        };
581        let root_node = tree.root_node();
582
583        let mut main_body = None;
584        for child in root_node.named_children(&mut root_node.walk()) {
585            if child.kind() == "function_declaration" {
586                if let Some(name) = child.child_by_field_name("name") {
587                    if &source[name.byte_range()] == "main" {
588                        if let Some(body) = child.child_by_field_name("body") {
589                            main_body = Some(body);
590                            break;
591                        }
592                    }
593                }
594            }
595        }
596
597        let Some(main_body) = main_body else {
598            return;
599        };
600
601        let variables = provider.provide(main_body, source, 100);
602        assert!(variables.len() >= 2);
603
604        let variable_names: Vec<&str> =
605            variables.iter().map(|v| v.variable_name.as_str()).collect();
606        assert!(variable_names.contains(&"items"));
607        assert!(variable_names.contains(&"j"));
608    }
609
610    #[test]
611    fn test_go_inline_value_provider_counter_pattern() {
612        let provider = GoInlineValueProvider;
613        let source = r#"
614package main
615
616func main() {
617    N := 10
618    for i := range N {
619        println(i)
620    }
621}
622"#;
623
624        let mut parser = Parser::new();
625        if parser
626            .set_language(&tree_sitter_go::LANGUAGE.into())
627            .is_err()
628        {
629            return;
630        }
631        let Some(tree) = parser.parse(source, None) else {
632            return;
633        };
634        let root_node = tree.root_node();
635
636        let mut main_body = None;
637        for child in root_node.named_children(&mut root_node.walk()) {
638            if child.kind() == "function_declaration" {
639                if let Some(name) = child.child_by_field_name("name") {
640                    if &source[name.byte_range()] == "main" {
641                        if let Some(body) = child.child_by_field_name("body") {
642                            main_body = Some(body);
643                            break;
644                        }
645                    }
646                }
647            }
648        }
649
650        let Some(main_body) = main_body else {
651            return;
652        };
653        let variables = provider.provide(main_body, source, 100);
654
655        let variable_names: Vec<&str> =
656            variables.iter().map(|v| v.variable_name.as_str()).collect();
657        assert!(variable_names.contains(&"N"));
658        assert!(variable_names.contains(&"i"));
659    }
660}