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}