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}