diff --git a/Cargo.lock b/Cargo.lock index 70a05cf4aa..0c832b83aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4348,6 +4348,7 @@ dependencies = [ "terminal_view", "theme", "tree-sitter", + "tree-sitter-go", "tree-sitter-json", "ui", "unindent", diff --git a/crates/dap/src/inline_value.rs b/crates/dap/src/inline_value.rs index 881797e20f..47d7833085 100644 --- a/crates/dap/src/inline_value.rs +++ b/crates/dap/src/inline_value.rs @@ -1,5 +1,3 @@ -use std::collections::{HashMap, HashSet}; - #[derive(Debug, Clone, PartialEq, Eq)] pub enum VariableLookupKind { Variable, @@ -20,641 +18,3 @@ pub struct InlineValueLocation { pub row: usize, pub column: usize, } - -/// A trait for providing inline values for debugging purposes. -/// -/// Implementors of this trait are responsible for analyzing a given node in the -/// source code and extracting variable information, including their names, -/// scopes, and positions. This information is used to display inline values -/// during debugging sessions. Implementors must also handle variable scoping -/// themselves by traversing the syntax tree upwards to determine whether a -/// variable is local or global. -pub trait InlineValueProvider: 'static + Send + Sync { - /// Provides a list of inline value locations based on the given node and source code. - /// - /// # Parameters - /// - `node`: The root node of the active debug line. Implementors should traverse - /// upwards from this node to gather variable information and determine their scope. - /// - `source`: The source code as a string slice, used to extract variable names. - /// - `max_row`: The maximum row to consider when collecting variables. Variables - /// declared beyond this row should be ignored. - /// - /// # Returns - /// A vector of `InlineValueLocation` instances, each representing a variable's - /// name, scope, and the position of the inline value should be shown. - fn provide( - &self, - node: language::Node, - source: &str, - max_row: usize, - ) -> Vec; -} - -pub struct RustInlineValueProvider; - -impl InlineValueProvider for RustInlineValueProvider { - fn provide( - &self, - mut node: language::Node, - source: &str, - max_row: usize, - ) -> Vec { - let mut variables = Vec::new(); - let mut variable_names = HashSet::new(); - let mut scope = VariableScope::Local; - - loop { - let mut variable_names_in_scope = HashMap::new(); - for child in node.named_children(&mut node.walk()) { - if child.start_position().row >= max_row { - break; - } - - if scope == VariableScope::Local && child.kind() == "let_declaration" { - if let Some(identifier) = child.child_by_field_name("pattern") { - let variable_name = source[identifier.byte_range()].to_string(); - - if variable_names.contains(&variable_name) { - continue; - } - - if let Some(index) = variable_names_in_scope.get(&variable_name) { - variables.remove(*index); - } - - variable_names_in_scope.insert(variable_name.clone(), variables.len()); - variables.push(InlineValueLocation { - variable_name, - scope: VariableScope::Local, - lookup: VariableLookupKind::Variable, - row: identifier.end_position().row, - column: identifier.end_position().column, - }); - } - } else if child.kind() == "static_item" { - if let Some(name) = child.child_by_field_name("name") { - let variable_name = source[name.byte_range()].to_string(); - variables.push(InlineValueLocation { - variable_name, - scope: scope.clone(), - lookup: VariableLookupKind::Expression, - row: name.end_position().row, - column: name.end_position().column, - }); - } - } - } - - variable_names.extend(variable_names_in_scope.keys().cloned()); - - if matches!(node.kind(), "function_item" | "closure_expression") { - scope = VariableScope::Global; - } - - if let Some(parent) = node.parent() { - node = parent; - } else { - break; - } - } - - variables - } -} - -pub struct PythonInlineValueProvider; - -impl InlineValueProvider for PythonInlineValueProvider { - fn provide( - &self, - mut node: language::Node, - source: &str, - max_row: usize, - ) -> Vec { - let mut variables = Vec::new(); - let mut variable_names = HashSet::new(); - let mut scope = VariableScope::Local; - - loop { - let mut variable_names_in_scope = HashMap::new(); - for child in node.named_children(&mut node.walk()) { - if child.start_position().row >= max_row { - break; - } - - if scope == VariableScope::Local { - match child.kind() { - "expression_statement" => { - if let Some(expr) = child.child(0) { - if expr.kind() == "assignment" { - if let Some(param) = expr.child(0) { - let param_identifier = if param.kind() == "identifier" { - Some(param) - } else if param.kind() == "typed_parameter" { - param.child(0) - } else { - None - }; - - if let Some(identifier) = param_identifier { - if identifier.kind() == "identifier" { - let variable_name = - source[identifier.byte_range()].to_string(); - - if variable_names.contains(&variable_name) { - continue; - } - - if let Some(index) = - variable_names_in_scope.get(&variable_name) - { - variables.remove(*index); - } - - variable_names_in_scope - .insert(variable_name.clone(), variables.len()); - variables.push(InlineValueLocation { - variable_name, - scope: VariableScope::Local, - lookup: VariableLookupKind::Variable, - row: identifier.end_position().row, - column: identifier.end_position().column, - }); - } - } - } - } - } - } - "function_definition" => { - if let Some(params) = child.child_by_field_name("parameters") { - for param in params.named_children(&mut params.walk()) { - let param_identifier = if param.kind() == "identifier" { - Some(param) - } else if param.kind() == "typed_parameter" { - param.child(0) - } else { - None - }; - - if let Some(identifier) = param_identifier { - if identifier.kind() == "identifier" { - let variable_name = - source[identifier.byte_range()].to_string(); - - if variable_names.contains(&variable_name) { - continue; - } - - if let Some(index) = - variable_names_in_scope.get(&variable_name) - { - variables.remove(*index); - } - - variable_names_in_scope - .insert(variable_name.clone(), variables.len()); - variables.push(InlineValueLocation { - variable_name, - scope: VariableScope::Local, - lookup: VariableLookupKind::Variable, - row: identifier.end_position().row, - column: identifier.end_position().column, - }); - } - } - } - } - } - "for_statement" => { - if let Some(target) = child.child_by_field_name("left") { - if target.kind() == "identifier" { - let variable_name = source[target.byte_range()].to_string(); - - if variable_names.contains(&variable_name) { - continue; - } - - if let Some(index) = variable_names_in_scope.get(&variable_name) - { - variables.remove(*index); - } - - variable_names_in_scope - .insert(variable_name.clone(), variables.len()); - variables.push(InlineValueLocation { - variable_name, - scope: VariableScope::Local, - lookup: VariableLookupKind::Variable, - row: target.end_position().row, - column: target.end_position().column, - }); - } - } - } - _ => {} - } - } - } - - variable_names.extend(variable_names_in_scope.keys().cloned()); - - if matches!(node.kind(), "function_definition" | "module") - && node.range().end_point.row < max_row - { - scope = VariableScope::Global; - } - - if let Some(parent) = node.parent() { - node = parent; - } else { - break; - } - } - - variables - } -} - -pub struct GoInlineValueProvider; - -impl InlineValueProvider for GoInlineValueProvider { - fn provide( - &self, - mut node: language::Node, - source: &str, - max_row: usize, - ) -> Vec { - let mut variables = Vec::new(); - let mut variable_names = HashSet::new(); - let mut scope = VariableScope::Local; - - loop { - let mut variable_names_in_scope = HashMap::new(); - for child in node.named_children(&mut node.walk()) { - if child.start_position().row >= max_row { - break; - } - - if scope == VariableScope::Local { - match child.kind() { - "var_declaration" => { - for var_spec in child.named_children(&mut child.walk()) { - if var_spec.kind() == "var_spec" { - if let Some(name_node) = var_spec.child_by_field_name("name") { - let variable_name = - source[name_node.byte_range()].to_string(); - - if variable_names.contains(&variable_name) { - continue; - } - - if let Some(index) = - variable_names_in_scope.get(&variable_name) - { - variables.remove(*index); - } - - variable_names_in_scope - .insert(variable_name.clone(), variables.len()); - variables.push(InlineValueLocation { - variable_name, - scope: VariableScope::Local, - lookup: VariableLookupKind::Variable, - row: name_node.end_position().row, - column: name_node.end_position().column, - }); - } - } - } - } - "short_var_declaration" => { - if let Some(left_side) = child.child_by_field_name("left") { - for identifier in left_side.named_children(&mut left_side.walk()) { - if identifier.kind() == "identifier" { - let variable_name = - source[identifier.byte_range()].to_string(); - - if variable_names.contains(&variable_name) { - continue; - } - - if let Some(index) = - variable_names_in_scope.get(&variable_name) - { - variables.remove(*index); - } - - variable_names_in_scope - .insert(variable_name.clone(), variables.len()); - variables.push(InlineValueLocation { - variable_name, - scope: VariableScope::Local, - lookup: VariableLookupKind::Variable, - row: identifier.end_position().row, - column: identifier.end_position().column, - }); - } - } - } - } - "assignment_statement" => { - if let Some(left_side) = child.child_by_field_name("left") { - for identifier in left_side.named_children(&mut left_side.walk()) { - if identifier.kind() == "identifier" { - let variable_name = - source[identifier.byte_range()].to_string(); - - if variable_names.contains(&variable_name) { - continue; - } - - if let Some(index) = - variable_names_in_scope.get(&variable_name) - { - variables.remove(*index); - } - - variable_names_in_scope - .insert(variable_name.clone(), variables.len()); - variables.push(InlineValueLocation { - variable_name, - scope: VariableScope::Local, - lookup: VariableLookupKind::Variable, - row: identifier.end_position().row, - column: identifier.end_position().column, - }); - } - } - } - } - "function_declaration" | "method_declaration" => { - if let Some(params) = child.child_by_field_name("parameters") { - for param in params.named_children(&mut params.walk()) { - if param.kind() == "parameter_declaration" { - if let Some(name_node) = param.child_by_field_name("name") { - let variable_name = - source[name_node.byte_range()].to_string(); - - if variable_names.contains(&variable_name) { - continue; - } - - if let Some(index) = - variable_names_in_scope.get(&variable_name) - { - variables.remove(*index); - } - - variable_names_in_scope - .insert(variable_name.clone(), variables.len()); - variables.push(InlineValueLocation { - variable_name, - scope: VariableScope::Local, - lookup: VariableLookupKind::Variable, - row: name_node.end_position().row, - column: name_node.end_position().column, - }); - } - } - } - } - } - "for_statement" => { - if let Some(clause) = child.named_child(0) { - if clause.kind() == "for_clause" { - if let Some(init) = clause.named_child(0) { - if init.kind() == "short_var_declaration" { - if let Some(left_side) = - init.child_by_field_name("left") - { - if left_side.kind() == "expression_list" { - for identifier in left_side - .named_children(&mut left_side.walk()) - { - if identifier.kind() == "identifier" { - let variable_name = source - [identifier.byte_range()] - .to_string(); - - if variable_names - .contains(&variable_name) - { - continue; - } - - if let Some(index) = - variable_names_in_scope - .get(&variable_name) - { - variables.remove(*index); - } - - variable_names_in_scope.insert( - variable_name.clone(), - variables.len(), - ); - variables.push(InlineValueLocation { - variable_name, - scope: VariableScope::Local, - lookup: - VariableLookupKind::Variable, - row: identifier.end_position().row, - column: identifier - .end_position() - .column, - }); - } - } - } - } - } - } - } else if clause.kind() == "range_clause" { - if let Some(left) = clause.child_by_field_name("left") { - if left.kind() == "expression_list" { - for identifier in left.named_children(&mut left.walk()) - { - if identifier.kind() == "identifier" { - let variable_name = - source[identifier.byte_range()].to_string(); - - if variable_name == "_" { - continue; - } - - if variable_names.contains(&variable_name) { - continue; - } - - if let Some(index) = - variable_names_in_scope.get(&variable_name) - { - variables.remove(*index); - } - variable_names_in_scope.insert( - variable_name.clone(), - variables.len(), - ); - variables.push(InlineValueLocation { - variable_name, - scope: VariableScope::Local, - lookup: VariableLookupKind::Variable, - row: identifier.end_position().row, - column: identifier.end_position().column, - }); - } - } - } - } - } - } - } - _ => {} - } - } else if child.kind() == "var_declaration" { - for var_spec in child.named_children(&mut child.walk()) { - if var_spec.kind() == "var_spec" { - if let Some(name_node) = var_spec.child_by_field_name("name") { - let variable_name = source[name_node.byte_range()].to_string(); - variables.push(InlineValueLocation { - variable_name, - scope: VariableScope::Global, - lookup: VariableLookupKind::Expression, - row: name_node.end_position().row, - column: name_node.end_position().column, - }); - } - } - } - } - } - - variable_names.extend(variable_names_in_scope.keys().cloned()); - - if matches!(node.kind(), "function_declaration" | "method_declaration") { - scope = VariableScope::Global; - } - - if let Some(parent) = node.parent() { - node = parent; - } else { - break; - } - } - - variables - } -} -#[cfg(test)] -mod tests { - use super::*; - use tree_sitter::Parser; - - #[test] - fn test_go_inline_value_provider() { - let provider = GoInlineValueProvider; - let source = r#" -package main - -func main() { - items := []int{1, 2, 3, 4, 5} - for i, v := range items { - println(i, v) - } - for j := 0; j < 10; j++ { - println(j) - } -} -"#; - - let mut parser = Parser::new(); - if parser - .set_language(&tree_sitter_go::LANGUAGE.into()) - .is_err() - { - return; - } - let Some(tree) = parser.parse(source, None) else { - return; - }; - let root_node = tree.root_node(); - - let mut main_body = None; - for child in root_node.named_children(&mut root_node.walk()) { - if child.kind() == "function_declaration" { - if let Some(name) = child.child_by_field_name("name") { - if &source[name.byte_range()] == "main" { - if let Some(body) = child.child_by_field_name("body") { - main_body = Some(body); - break; - } - } - } - } - } - - let Some(main_body) = main_body else { - return; - }; - - let variables = provider.provide(main_body, source, 100); - assert!(variables.len() >= 2); - - let variable_names: Vec<&str> = - variables.iter().map(|v| v.variable_name.as_str()).collect(); - assert!(variable_names.contains(&"items")); - assert!(variable_names.contains(&"j")); - } - - #[test] - fn test_go_inline_value_provider_counter_pattern() { - let provider = GoInlineValueProvider; - let source = r#" -package main - -func main() { - N := 10 - for i := range N { - println(i) - } -} -"#; - - let mut parser = Parser::new(); - if parser - .set_language(&tree_sitter_go::LANGUAGE.into()) - .is_err() - { - return; - } - let Some(tree) = parser.parse(source, None) else { - return; - }; - let root_node = tree.root_node(); - - let mut main_body = None; - for child in root_node.named_children(&mut root_node.walk()) { - if child.kind() == "function_declaration" { - if let Some(name) = child.child_by_field_name("name") { - if &source[name.byte_range()] == "main" { - if let Some(body) = child.child_by_field_name("body") { - main_body = Some(body); - break; - } - } - } - } - } - - let Some(main_body) = main_body else { - return; - }; - let variables = provider.provide(main_body, source, 100); - - let variable_names: Vec<&str> = - variables.iter().map(|v| v.variable_name.as_str()).collect(); - assert!(variable_names.contains(&"N")); - assert!(variable_names.contains(&"i")); - } -} diff --git a/crates/dap/src/registry.rs b/crates/dap/src/registry.rs index 2786de227e..9435b16b92 100644 --- a/crates/dap/src/registry.rs +++ b/crates/dap/src/registry.rs @@ -8,10 +8,7 @@ use task::{ AdapterSchema, AdapterSchemas, DebugRequest, DebugScenario, SpawnInTerminal, TaskTemplate, }; -use crate::{ - adapters::{DebugAdapter, DebugAdapterName}, - inline_value::InlineValueProvider, -}; +use crate::adapters::{DebugAdapter, DebugAdapterName}; use std::{collections::BTreeMap, sync::Arc}; /// Given a user build configuration, locator creates a fill-in debug target ([DebugScenario]) on behalf of the user. @@ -33,7 +30,6 @@ pub trait DapLocator: Send + Sync { struct DapRegistryState { adapters: BTreeMap>, locators: FxHashMap>, - inline_value_providers: FxHashMap>, } #[derive(Clone, Default)] @@ -82,22 +78,6 @@ impl DapRegistry { schemas } - pub fn add_inline_value_provider( - &self, - language: String, - provider: Arc, - ) { - let _previous_value = self - .0 - .write() - .inline_value_providers - .insert(language, provider); - debug_assert!( - _previous_value.is_none(), - "Attempted to insert a new inline value provider when one is already registered" - ); - } - pub fn locators(&self) -> FxHashMap> { self.0.read().locators.clone() } @@ -106,10 +86,6 @@ impl DapRegistry { self.0.read().adapters.get(name).cloned() } - pub fn inline_value_provider(&self, language: &str) -> Option> { - self.0.read().inline_value_providers.get(language).cloned() - } - pub fn enumerate_adapters(&self) -> Vec { self.0.read().adapters.keys().cloned().collect() } diff --git a/crates/dap_adapters/src/dap_adapters.rs b/crates/dap_adapters/src/dap_adapters.rs index 414d0a91a3..79c56fdf25 100644 --- a/crates/dap_adapters/src/dap_adapters.rs +++ b/crates/dap_adapters/src/dap_adapters.rs @@ -18,7 +18,6 @@ use dap::{ GithubRepo, }, configure_tcp_connection, - inline_value::{GoInlineValueProvider, PythonInlineValueProvider, RustInlineValueProvider}, }; use gdb::GdbDebugAdapter; use go::GoDebugAdapter; @@ -44,10 +43,5 @@ pub fn init(cx: &mut App) { { registry.add_adapter(Arc::from(dap::FakeAdapter {})); } - - registry.add_inline_value_provider("Rust".to_string(), Arc::from(RustInlineValueProvider)); - registry - .add_inline_value_provider("Python".to_string(), Arc::from(PythonInlineValueProvider)); - registry.add_inline_value_provider("Go".to_string(), Arc::from(GoInlineValueProvider)); }) } diff --git a/crates/debugger_ui/Cargo.toml b/crates/debugger_ui/Cargo.toml index e259b8a4b3..91f9acad3c 100644 --- a/crates/debugger_ui/Cargo.toml +++ b/crates/debugger_ui/Cargo.toml @@ -81,3 +81,4 @@ unindent.workspace = true util = { workspace = true, features = ["test-support"] } workspace = { workspace = true, features = ["test-support"] } zlog.workspace = true +tree-sitter-go.workspace = true diff --git a/crates/debugger_ui/src/tests/inline_values.rs b/crates/debugger_ui/src/tests/inline_values.rs index 6fed57ecac..45cab2a306 100644 --- a/crates/debugger_ui/src/tests/inline_values.rs +++ b/crates/debugger_ui/src/tests/inline_values.rs @@ -246,10 +246,10 @@ fn main() { editor.update_in(cx, |editor, window, cx| { pretty_assertions::assert_eq!( r#" - static mut GLOBAL: 1: usize = 1; + static mut GLOBAL: usize = 1; fn main() { - let x = 10; + let x: 10 = 10; let value = 42; let y = 4; let tester = { @@ -303,11 +303,11 @@ fn main() { editor.update_in(cx, |editor, window, cx| { pretty_assertions::assert_eq!( r#" - static mut GLOBAL: 1: usize = 1; + static mut GLOBAL: usize = 1; fn main() { let x: 10 = 10; - let value = 42; + let value: 42 = 42; let y = 4; let tester = { let y = 10; @@ -360,12 +360,12 @@ fn main() { editor.update_in(cx, |editor, window, cx| { pretty_assertions::assert_eq!( r#" - static mut GLOBAL: 1: usize = 1; + static mut GLOBAL: usize = 1; fn main() { let x: 10 = 10; let value: 42 = 42; - let y = 4; + let y: 4 = 4; let tester = { let y = 10; let y = 5; @@ -417,7 +417,7 @@ fn main() { editor.update_in(cx, |editor, window, cx| { pretty_assertions::assert_eq!( r#" - static mut GLOBAL: 1: usize = 1; + static mut GLOBAL: usize = 1; fn main() { let x: 10 = 10; @@ -474,14 +474,14 @@ fn main() { editor.update_in(cx, |editor, window, cx| { pretty_assertions::assert_eq!( r#" - static mut GLOBAL: 1: usize = 1; + static mut GLOBAL: usize = 1; fn main() { let x: 10 = 10; let value: 42 = 42; let y: 4 = 4; let tester = { - let y = 10; + let y: 4 = 10; let y = 5; let b = 3; vec![y, 20, 30] @@ -581,15 +581,15 @@ fn main() { editor.update_in(cx, |editor, window, cx| { pretty_assertions::assert_eq!( r#" - static mut GLOBAL: 1: usize = 1; + static mut GLOBAL: usize = 1; fn main() { let x: 10 = 10; let value: 42 = 42; - let y = 4; + let y: 10 = 4; let tester = { let y: 10 = 10; - let y = 5; + let y: 10 = 5; let b = 3; vec![y, 20, 30] }; @@ -688,14 +688,14 @@ fn main() { editor.update_in(cx, |editor, window, cx| { pretty_assertions::assert_eq!( r#" - static mut GLOBAL: 1: usize = 1; + static mut GLOBAL: usize = 1; fn main() { let x: 10 = 10; let value: 42 = 42; - let y = 4; + let y: 5 = 4; let tester = { - let y = 10; + let y: 5 = 10; let y: 5 = 5; let b = 3; vec![y, 20, 30] @@ -807,17 +807,17 @@ fn main() { editor.update_in(cx, |editor, window, cx| { pretty_assertions::assert_eq!( r#" - static mut GLOBAL: 1: usize = 1; + static mut GLOBAL: usize = 1; fn main() { let x: 10 = 10; let value: 42 = 42; - let y = 4; + let y: 5 = 4; let tester = { - let y = 10; + let y: 5 = 10; let y: 5 = 5; let b: 3 = 3; - vec![y, 20, 30] + vec![y: 5, 20, 30] }; let caller = || { @@ -926,7 +926,7 @@ fn main() { editor.update_in(cx, |editor, window, cx| { pretty_assertions::assert_eq!( r#" - static mut GLOBAL: 1: usize = 1; + static mut GLOBAL: usize = 1; fn main() { let x: 10 = 10; @@ -1058,7 +1058,7 @@ fn main() { editor.update_in(cx, |editor, window, cx| { pretty_assertions::assert_eq!( r#" - static mut GLOBAL: 1: usize = 1; + static mut GLOBAL: usize = 1; fn main() { let x: 10 = 10; @@ -1115,21 +1115,21 @@ fn main() { editor.update_in(cx, |editor, window, cx| { pretty_assertions::assert_eq!( r#" - static mut GLOBAL: 1: usize = 1; + static mut GLOBAL: usize = 1; fn main() { - let x = 10; - let value = 42; - let y = 4; - let tester = { + let x: 10 = 10; + let value: 42 = 42; + let y: 4 = 4; + let tester: size=3 = { let y = 10; let y = 5; let b = 3; vec![y, 20, 30] }; - let caller = || { - let x = 3; + let caller: = || { + let x: 10 = 3; println!("x={}", x); }; @@ -1193,10 +1193,10 @@ fn main() { editor.update_in(cx, |editor, window, cx| { pretty_assertions::assert_eq!( r#" - static mut GLOBAL: 1: usize = 1; + static mut GLOBAL: usize = 1; fn main() { - let x = 10; + let x: 3 = 10; let value = 42; let y = 4; let tester = { @@ -1208,7 +1208,7 @@ fn main() { let caller = || { let x: 3 = 3; - println!("x={}", x); + println!("x={}", x: 3); }; caller(); @@ -1338,7 +1338,7 @@ fn main() { editor.update_in(cx, |editor, window, cx| { pretty_assertions::assert_eq!( r#" - static mut GLOBAL: 2: usize = 1; + static mut GLOBAL: usize = 1; fn main() { let x: 10 = 10; @@ -1362,7 +1362,7 @@ fn main() { GLOBAL = 2; } - let result = value * 2 * x; + let result = value: 42 * 2 * x: 10; println!("Simple test executed: value={}, result={}", value, result); assert!(true); } @@ -1483,7 +1483,7 @@ fn main() { editor.update_in(cx, |editor, window, cx| { pretty_assertions::assert_eq!( r#" - static mut GLOBAL: 2: usize = 1; + static mut GLOBAL: usize = 1; fn main() { let x: 10 = 10; @@ -1507,8 +1507,8 @@ fn main() { GLOBAL = 2; } - let result: 840 = value * 2 * x; - println!("Simple test executed: value={}, result={}", value, result); + let result: 840 = value: 42 * 2 * x: 10; + println!("Simple test executed: value={}, result={}", value: 42, result: 840); assert!(true); } "# @@ -1519,6 +1519,7 @@ fn main() { } fn rust_lang() -> Language { + let debug_variables_query = include_str!("../../../languages/src/rust/debugger.scm"); Language::new( LanguageConfig { name: "Rust".into(), @@ -1530,6 +1531,8 @@ fn rust_lang() -> Language { }, Some(tree_sitter_rust::LANGUAGE.into()), ) + .with_debug_variables_query(debug_variables_query) + .unwrap() } #[gpui::test] @@ -1818,8 +1821,8 @@ def process_data(untyped_param, typed_param: int, another_typed: str): def process_data(untyped_param: test_value, typed_param: 42: int, another_typed: world: str): # Local variables x: 10 = 10 - result: 84 = typed_param * 2 - text: Hello, world = "Hello, " + another_typed + result: 84 = typed_param: 42 * 2 + text: Hello, world = "Hello, " + another_typed: world # For loop with range sum_value: 10 = 0 @@ -1837,6 +1840,7 @@ def process_data(untyped_param, typed_param: int, another_typed: str): } fn python_lang() -> Language { + let debug_variables_query = include_str!("../../../languages/src/python/debugger.scm"); Language::new( LanguageConfig { name: "Python".into(), @@ -1848,4 +1852,392 @@ fn python_lang() -> Language { }, Some(tree_sitter_python::LANGUAGE.into()), ) + .with_debug_variables_query(debug_variables_query) + .unwrap() +} + +fn go_lang() -> Language { + let debug_variables_query = include_str!("../../../languages/src/go/debugger.scm"); + Language::new( + LanguageConfig { + name: "Go".into(), + matcher: LanguageMatcher { + path_suffixes: vec!["go".to_string()], + ..Default::default() + }, + ..Default::default() + }, + Some(tree_sitter_go::LANGUAGE.into()), + ) + .with_debug_variables_query(debug_variables_query) + .unwrap() +} + +/// Test utility function for inline values testing +/// +/// # Arguments +/// * `variables` - List of tuples containing (variable_name, variable_value) +/// * `before` - Source code before inline values are applied +/// * `after` - Expected source code after inline values are applied +/// * `language` - Language configuration to use for parsing +/// * `executor` - Background executor for async operations +/// * `cx` - Test app context +async fn test_inline_values_util( + local_variables: &[(&str, &str)], + global_variables: &[(&str, &str)], + before: &str, + after: &str, + active_debug_line: Option, + language: Language, + executor: BackgroundExecutor, + cx: &mut TestAppContext, +) { + init_test(cx); + + let lines_count = before.lines().count(); + let stop_line = + active_debug_line.unwrap_or_else(|| if lines_count > 6 { 6 } else { lines_count - 1 }); + + let fs = FakeFs::new(executor.clone()); + fs.insert_tree(path!("/project"), json!({ "main.rs": before.to_string() })) + .await; + + let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await; + let workspace = init_test_workspace(&project, cx).await; + workspace + .update(cx, |workspace, window, cx| { + workspace.focus_panel::(window, cx); + }) + .unwrap(); + let cx = &mut VisualTestContext::from_window(*workspace, cx); + + let session = start_debug_session(&workspace, cx, |_| {}).unwrap(); + let client = session.update(cx, |session, _| session.adapter_client().unwrap()); + + client.on_request::(|_, _| { + Ok(dap::ThreadsResponse { + threads: vec![dap::Thread { + id: 1, + name: "main".into(), + }], + }) + }); + + client.on_request::(move |_, _| { + Ok(dap::StackTraceResponse { + stack_frames: vec![dap::StackFrame { + id: 1, + name: "main".into(), + source: Some(dap::Source { + name: Some("main.rs".into()), + path: Some(path!("/project/main.rs").into()), + source_reference: None, + presentation_hint: None, + origin: None, + sources: None, + adapter_data: None, + checksums: None, + }), + line: stop_line as u64, + column: 1, + end_line: None, + end_column: None, + can_restart: None, + instruction_pointer_reference: None, + module_id: None, + presentation_hint: None, + }], + total_frames: None, + }) + }); + + let local_vars: Vec = local_variables + .iter() + .map(|(name, value)| Variable { + name: (*name).into(), + value: (*value).into(), + type_: None, + presentation_hint: None, + evaluate_name: None, + variables_reference: 0, + named_variables: None, + indexed_variables: None, + memory_reference: None, + declaration_location_reference: None, + value_location_reference: None, + }) + .collect(); + + let global_vars: Vec = global_variables + .iter() + .map(|(name, value)| Variable { + name: (*name).into(), + value: (*value).into(), + type_: None, + presentation_hint: None, + evaluate_name: None, + variables_reference: 0, + named_variables: None, + indexed_variables: None, + memory_reference: None, + declaration_location_reference: None, + value_location_reference: None, + }) + .collect(); + + client.on_request::({ + let local_vars = Arc::new(local_vars.clone()); + let global_vars = Arc::new(global_vars.clone()); + move |_, args| { + let variables = match args.variables_reference { + 2 => (*local_vars).clone(), + 3 => (*global_vars).clone(), + _ => vec![], + }; + Ok(dap::VariablesResponse { variables }) + } + }); + + client.on_request::(move |_, _| { + Ok(dap::ScopesResponse { + scopes: vec![ + Scope { + name: "Local".into(), + presentation_hint: None, + variables_reference: 2, + named_variables: None, + indexed_variables: None, + expensive: false, + source: None, + line: None, + column: None, + end_line: None, + end_column: None, + }, + Scope { + name: "Global".into(), + presentation_hint: None, + variables_reference: 3, + named_variables: None, + indexed_variables: None, + expensive: false, + source: None, + line: None, + column: None, + end_line: None, + end_column: None, + }, + ], + }) + }); + + if !global_variables.is_empty() { + let global_evaluate_map: std::collections::HashMap = global_variables + .iter() + .map(|(name, value)| (name.to_string(), value.to_string())) + .collect(); + + client.on_request::(move |_, args| { + let value = global_evaluate_map + .get(&args.expression) + .unwrap_or(&"undefined".to_string()) + .clone(); + + Ok(dap::EvaluateResponse { + result: value, + type_: None, + presentation_hint: None, + variables_reference: 0, + named_variables: None, + indexed_variables: None, + memory_reference: None, + value_location_reference: None, + }) + }); + } + + client + .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent { + reason: dap::StoppedEventReason::Pause, + description: None, + thread_id: Some(1), + preserve_focus_hint: None, + text: None, + all_threads_stopped: None, + hit_breakpoint_ids: None, + })) + .await; + + cx.run_until_parked(); + + let project_path = Path::new(path!("/project")); + let worktree = project + .update(cx, |project, cx| project.find_worktree(project_path, cx)) + .expect("This worktree should exist in project") + .0; + + let worktree_id = workspace + .update(cx, |_, _, cx| worktree.read(cx).id()) + .unwrap(); + + let buffer = project + .update(cx, |project, cx| { + project.open_buffer((worktree_id, "main.rs"), cx) + }) + .await + .unwrap(); + + buffer.update(cx, |buffer, cx| { + buffer.set_language(Some(Arc::new(language)), cx); + }); + + let (editor, cx) = cx.add_window_view(|window, cx| { + Editor::new( + EditorMode::full(), + MultiBuffer::build_from_buffer(buffer, cx), + Some(project), + window, + cx, + ) + }); + + active_debug_session_panel(workspace, cx).update_in(cx, |_, window, cx| { + cx.focus_self(window); + }); + cx.run_until_parked(); + + editor.update(cx, |editor, cx| editor.refresh_inline_values(cx)); + + cx.run_until_parked(); + + editor.update_in(cx, |editor, window, cx| { + pretty_assertions::assert_eq!(after, editor.snapshot(window, cx).text()); + }); +} + +#[gpui::test] +async fn test_inline_values_example(executor: BackgroundExecutor, cx: &mut TestAppContext) { + let variables = [("x", "10"), ("y", "20"), ("result", "30")]; + + let before = r#" +fn main() { + let x = 10; + let y = 20; + let result = x + y; + println!("Result: {}", result); +} +"# + .unindent(); + + let after = r#" +fn main() { + let x: 10 = 10; + let y: 20 = 20; + let result: 30 = x: 10 + y: 20; + println!("Result: {}", result: 30); +} +"# + .unindent(); + + test_inline_values_util( + &variables, + &[], + &before, + &after, + None, + rust_lang(), + executor, + cx, + ) + .await; +} + +#[gpui::test] +async fn test_inline_values_with_globals(executor: BackgroundExecutor, cx: &mut TestAppContext) { + let variables = [("x", "5"), ("y", "10")]; + + let before = r#" +static mut GLOBAL_COUNTER: usize = 42; + +fn main() { + let x = 5; + let y = 10; + unsafe { + GLOBAL_COUNTER += 1; + } + println!("x={}, y={}, global={}", x, y, unsafe { GLOBAL_COUNTER }); +} +"# + .unindent(); + + let after = r#" +static mut GLOBAL_COUNTER: 42: usize = 42; + +fn main() { + let x: 5 = 5; + let y: 10 = 10; + unsafe { + GLOBAL_COUNTER += 1; + } + println!("x={}, y={}, global={}", x, y, unsafe { GLOBAL_COUNTER }); +} +"# + .unindent(); + + test_inline_values_util( + &variables, + &[("GLOBAL_COUNTER", "42")], + &before, + &after, + None, + rust_lang(), + executor, + cx, + ) + .await; +} + +#[gpui::test] +async fn test_go_inline_values(executor: BackgroundExecutor, cx: &mut TestAppContext) { + let variables = [("x", "42"), ("y", "hello")]; + + let before = r#" +package main + +var globalCounter int = 100 + +func main() { + x := 42 + y := "hello" + z := x + 10 + println(x, y, z) +} +"# + .unindent(); + + let after = r#" +package main + +var globalCounter: 100 int = 100 + +func main() { + x: 42 := 42 + y := "hello" + z := x + 10 + println(x, y, z) +} +"# + .unindent(); + + test_inline_values_util( + &variables, + &[("globalCounter", "100")], + &before, + &after, + None, + go_lang(), + executor, + cx, + ) + .await; } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 568e9062c8..6e9a9be0fe 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -19167,7 +19167,7 @@ impl Editor { let current_execution_position = self .highlighted_rows .get(&TypeId::of::()) - .and_then(|lines| lines.last().map(|line| line.range.start)); + .and_then(|lines| lines.last().map(|line| line.range.end)); self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| { let inline_values = editor @@ -21553,7 +21553,6 @@ impl SemanticsProvider for Entity { fn inline_values( &self, buffer_handle: Entity, - range: Range, cx: &mut App, ) -> Option>>> { diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 278976d3cd..b0e06c3d65 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -20,6 +20,7 @@ test-support = [ "text/test-support", "tree-sitter-rust", "tree-sitter-python", + "tree-sitter-rust", "tree-sitter-typescript", "settings/test-support", "util/test-support", diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 523efa49dc..90a899f79d 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1,12 +1,6 @@ -pub use crate::{ - Grammar, Language, LanguageRegistry, - diagnostic_set::DiagnosticSet, - highlight_map::{HighlightId, HighlightMap}, - proto, -}; use crate::{ - LanguageScope, Outline, OutlineConfig, RunnableCapture, RunnableTag, TextObject, - TreeSitterOptions, + DebuggerTextObject, LanguageScope, Outline, OutlineConfig, RunnableCapture, RunnableTag, + TextObject, TreeSitterOptions, diagnostic_set::{DiagnosticEntry, DiagnosticGroup}, language_settings::{LanguageSettings, language_settings}, outline::OutlineItem, @@ -17,6 +11,12 @@ use crate::{ task_context::RunnableRange, text_diff::text_diff, }; +pub use crate::{ + Grammar, Language, LanguageRegistry, + diagnostic_set::DiagnosticSet, + highlight_map::{HighlightId, HighlightMap}, + proto, +}; use anyhow::{Context as _, Result}; pub use clock::ReplicaId; use clock::{AGENT_REPLICA_ID, Lamport}; @@ -3848,6 +3848,74 @@ impl BufferSnapshot { .filter(|pair| !pair.newline_only) } + pub fn debug_variables_query( + &self, + range: Range, + ) -> impl Iterator, DebuggerTextObject)> + '_ { + let range = range.start.to_offset(self).saturating_sub(1) + ..self.len().min(range.end.to_offset(self) + 1); + + let mut matches = self.syntax.matches_with_options( + range.clone(), + &self.text, + TreeSitterOptions::default(), + |grammar| grammar.debug_variables_config.as_ref().map(|c| &c.query), + ); + + let configs = matches + .grammars() + .iter() + .map(|grammar| grammar.debug_variables_config.as_ref()) + .collect::>(); + + let mut captures = Vec::<(Range, DebuggerTextObject)>::new(); + + iter::from_fn(move || { + loop { + while let Some(capture) = captures.pop() { + if capture.0.overlaps(&range) { + return Some(capture); + } + } + + let mat = matches.peek()?; + + let Some(config) = configs[mat.grammar_index].as_ref() else { + matches.advance(); + continue; + }; + + for capture in mat.captures { + let Some(ix) = config + .objects_by_capture_ix + .binary_search_by_key(&capture.index, |e| e.0) + .ok() + else { + continue; + }; + let text_object = config.objects_by_capture_ix[ix].1; + let byte_range = capture.node.byte_range(); + + let mut found = false; + for (range, existing) in captures.iter_mut() { + if existing == &text_object { + range.start = range.start.min(byte_range.start); + range.end = range.end.max(byte_range.end); + found = true; + break; + } + } + + if !found { + captures.push((byte_range, text_object)); + } + } + + matches.advance(); + } + }) + } + pub fn text_object_ranges( &self, range: Range, diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 8b8c411366..f564b54ed5 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -1082,6 +1082,7 @@ pub struct Grammar { pub embedding_config: Option, pub(crate) injection_config: Option, pub(crate) override_config: Option, + pub(crate) debug_variables_config: Option, pub(crate) highlight_map: Mutex, } @@ -1104,6 +1105,22 @@ pub struct OutlineConfig { pub annotation_capture_ix: Option, } +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum DebuggerTextObject { + Variable, + Scope, +} + +impl DebuggerTextObject { + pub fn from_capture_name(name: &str) -> Option { + match name { + "debug-variable" => Some(DebuggerTextObject::Variable), + "debug-scope" => Some(DebuggerTextObject::Scope), + _ => None, + } + } +} + #[derive(Debug, Clone, Copy, PartialEq)] pub enum TextObject { InsideFunction, @@ -1206,6 +1223,11 @@ struct BracketsPatternConfig { newline_only: bool, } +pub struct DebugVariablesConfig { + pub query: Query, + pub objects_by_capture_ix: Vec<(u32, DebuggerTextObject)>, +} + impl Language { pub fn new(config: LanguageConfig, ts_language: Option) -> Self { Self::new_with_id(LanguageId::new(), config, ts_language) @@ -1237,6 +1259,7 @@ impl Language { redactions_config: None, runnable_config: None, error_query: Query::new(&ts_language, "(ERROR) @error").ok(), + debug_variables_config: None, ts_language, highlight_map: Default::default(), }) @@ -1307,6 +1330,11 @@ impl Language { .with_text_object_query(query.as_ref()) .context("Error loading textobject query")?; } + if let Some(query) = queries.debugger { + self = self + .with_debug_variables_query(query.as_ref()) + .context("Error loading debug variables query")?; + } Ok(self) } @@ -1425,6 +1453,24 @@ impl Language { Ok(self) } + pub fn with_debug_variables_query(mut self, source: &str) -> Result { + let grammar = self.grammar_mut().context("cannot mutate grammar")?; + let query = Query::new(&grammar.ts_language, source)?; + + let mut objects_by_capture_ix = Vec::new(); + for (ix, name) in query.capture_names().iter().enumerate() { + if let Some(text_object) = DebuggerTextObject::from_capture_name(name) { + objects_by_capture_ix.push((ix as u32, text_object)); + } + } + + grammar.debug_variables_config = Some(DebugVariablesConfig { + query, + objects_by_capture_ix, + }); + Ok(self) + } + pub fn with_brackets_query(mut self, source: &str) -> Result { let grammar = self.grammar_mut().context("cannot mutate grammar")?; let query = Query::new(&grammar.ts_language, source)?; @@ -1930,6 +1976,10 @@ impl Grammar { .capture_index_for_name(name)?; Some(self.highlight_map.lock().get(capture_id)) } + + pub fn debug_variables_config(&self) -> Option<&DebugVariablesConfig> { + self.debug_variables_config.as_ref() + } } impl CodeLabel { diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index 4d0837d8e3..c157cd9e73 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -226,7 +226,7 @@ pub const QUERY_FILENAME_PREFIXES: &[( ("overrides", |q| &mut q.overrides), ("redactions", |q| &mut q.redactions), ("runnables", |q| &mut q.runnables), - ("debug_variables", |q| &mut q.debug_variables), + ("debugger", |q| &mut q.debugger), ("textobjects", |q| &mut q.text_objects), ]; @@ -243,7 +243,7 @@ pub struct LanguageQueries { pub redactions: Option>, pub runnables: Option>, pub text_objects: Option>, - pub debug_variables: Option>, + pub debugger: Option>, } #[derive(Clone, Default)] diff --git a/crates/languages/src/go/debugger.scm b/crates/languages/src/go/debugger.scm new file mode 100644 index 0000000000..f22b91f938 --- /dev/null +++ b/crates/languages/src/go/debugger.scm @@ -0,0 +1,26 @@ +(parameter_declaration (identifier) @debug-variable) + +(short_var_declaration (expression_list (identifier) @debug-variable)) + +(var_declaration (var_spec (identifier) @debug-variable)) + +(const_declaration (const_spec (identifier) @debug-variable)) + +(assignment_statement (expression_list (identifier) @debug-variable)) + +(binary_expression (identifier) @debug-variable + (#not-match? @debug-variable "^[A-Z]")) + +(call_expression (argument_list (identifier) @debug-variable + (#not-match? @debug-variable "^[A-Z]"))) + +(return_statement (expression_list (identifier) @debug-variable + (#not-match? @debug-variable "^[A-Z]"))) + +(range_clause (expression_list (identifier) @debug-variable)) + +(parenthesized_expression (identifier) @debug-variable + (#not-match? @debug-variable "^[A-Z]")) + +(block) @debug-scope +(function_declaration) @debug-scope diff --git a/crates/languages/src/python/debugger.scm b/crates/languages/src/python/debugger.scm new file mode 100644 index 0000000000..807d6e865d --- /dev/null +++ b/crates/languages/src/python/debugger.scm @@ -0,0 +1,43 @@ +(identifier) @debug-variable +(#eq? @debug-variable "self") + +(assignment left: (identifier) @debug-variable) +(assignment left: (pattern_list (identifier) @debug-variable)) +(assignment left: (tuple_pattern (identifier) @debug-variable)) + +(augmented_assignment left: (identifier) @debug-variable) + +(for_statement left: (identifier) @debug-variable) +(for_statement left: (pattern_list (identifier) @debug-variable)) +(for_statement left: (tuple_pattern (identifier) @debug-variable)) + +(for_in_clause left: (identifier) @debug-variable) +(for_in_clause left: (pattern_list (identifier) @debug-variable)) +(for_in_clause left: (tuple_pattern (identifier) @debug-variable)) + +(as_pattern (identifier) @debug-variable) + +(binary_operator left: (identifier) @debug-variable (#not-match? @debug-variable "^[A-Z]")) +(binary_operator right: (identifier) @debug-variable (#not-match? @debug-variable "^[A-Z]")) +(comparison_operator (identifier) @debug-variable (#not-match? @debug-variable "^[A-Z]")) + +(list (identifier) @debug-variable (#not-match? @debug-variable "^[A-Z]")) +(tuple (identifier) @debug-variable (#not-match? @debug-variable "^[A-Z]")) +(set (identifier) @debug-variable (#not-match? @debug-variable "^[A-Z]")) + +(subscript value: (identifier) @debug-variable (#not-match? @debug-variable "^[A-Z]")) + +(attribute object: (identifier) @debug-variable (#not-match? @debug-variable "^[A-Z]")) + +(return_statement (identifier) @debug-variable (#not-match? @debug-variable "^[A-Z]")) + +(parenthesized_expression (identifier) @debug-variable (#not-match? @debug-variable "^[A-Z]")) + +(argument_list (identifier) @debug-variable (#not-match? @debug-variable "^[A-Z]")) + +(if_statement condition: (identifier) @debug-variable (#not-match? @debug-variable "^[A-Z]")) + +(while_statement condition: (identifier) @debug-variable (#not-match? @debug-variable "^[A-Z]")) + +(block) @debug-scope +(module) @debug-scope diff --git a/crates/languages/src/rust/debugger.scm b/crates/languages/src/rust/debugger.scm new file mode 100644 index 0000000000..5347413f69 --- /dev/null +++ b/crates/languages/src/rust/debugger.scm @@ -0,0 +1,50 @@ +(metavariable) @debug-variable + +(parameter (identifier) @debug-variable) + +(self) @debug-variable + +(static_item (identifier) @debug-variable) +(const_item (identifier) @debug-variable) + +(let_declaration pattern: (identifier) @debug-variable) + +(let_condition (identifier) @debug-variable) + +(match_arm (identifier) @debug-variable) + +(for_expression (identifier) @debug-variable) + +(closure_parameters (identifier) @debug-variable) + +(assignment_expression (identifier) @debug-variable) + +(field_expression (identifier) @debug-variable) + +(binary_expression (identifier) @debug-variable + (#not-match? @debug-variable "^[A-Z]")) + +(reference_expression (identifier) @debug-variable + (#not-match? @debug-variable "^[A-Z]")) + +(array_expression (identifier) @debug-variable) +(tuple_expression (identifier) @debug-variable) +(return_expression (identifier) @debug-variable) +(await_expression (identifier) @debug-variable) +(try_expression (identifier) @debug-variable) +(index_expression (identifier) @debug-variable) +(range_expression (identifier) @debug-variable) +(unary_expression (identifier) @debug-variable) + +(if_expression (identifier) @debug-variable) +(while_expression (identifier) @debug-variable) + +(parenthesized_expression (identifier) @debug-variable) + +(arguments (identifier) @debug-variable + (#not-match? @debug-variable "^[A-Z]")) + +(macro_invocation (token_tree (identifier) @debug-variable + (#not-match? @debug-variable "^[A-Z]"))) + +(block) @debug-scope diff --git a/crates/project/src/debugger/dap_store.rs b/crates/project/src/debugger/dap_store.rs index 28cfbe4e4d..be4964bbee 100644 --- a/crates/project/src/debugger/dap_store.rs +++ b/crates/project/src/debugger/dap_store.rs @@ -588,7 +588,14 @@ impl DapStore { cx: &mut Context, ) -> Task>> { let snapshot = buffer_handle.read(cx).snapshot(); - let all_variables = session.read(cx).variables_by_stack_frame_id(stack_frame_id); + let local_variables = + session + .read(cx) + .variables_by_stack_frame_id(stack_frame_id, false, true); + let global_variables = + session + .read(cx) + .variables_by_stack_frame_id(stack_frame_id, true, false); fn format_value(mut value: String) -> String { const LIMIT: usize = 100; @@ -617,10 +624,20 @@ impl DapStore { match inline_value_location.lookup { VariableLookupKind::Variable => { - let Some(variable) = all_variables - .iter() - .find(|variable| variable.name == inline_value_location.variable_name) - else { + let variable_search = + if inline_value_location.scope + == dap::inline_value::VariableScope::Local + { + local_variables.iter().chain(global_variables.iter()).find( + |variable| variable.name == inline_value_location.variable_name, + ) + } else { + global_variables.iter().find(|variable| { + variable.name == inline_value_location.variable_name + }) + }; + + let Some(variable) = variable_search else { continue; }; diff --git a/crates/project/src/debugger/session.rs b/crates/project/src/debugger/session.rs index 917506e523..300c598bfb 100644 --- a/crates/project/src/debugger/session.rs +++ b/crates/project/src/debugger/session.rs @@ -2171,7 +2171,12 @@ impl Session { .unwrap_or_default() } - pub fn variables_by_stack_frame_id(&self, stack_frame_id: StackFrameId) -> Vec { + pub fn variables_by_stack_frame_id( + &self, + stack_frame_id: StackFrameId, + globals: bool, + locals: bool, + ) -> Vec { let Some(stack_frame) = self.stack_frames.get(&stack_frame_id) else { return Vec::new(); }; @@ -2179,6 +2184,10 @@ impl Session { stack_frame .scopes .iter() + .filter(|scope| { + (scope.name.to_lowercase().contains("local") && locals) + || (scope.name.to_lowercase().contains("global") && globals) + }) .filter_map(|scope| self.variables.get(&scope.variables_reference)) .flatten() .cloned() diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 2fc7fbbe76..e8b3814850 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -31,6 +31,8 @@ use git_store::{Repository, RepositoryId}; pub mod search_history; mod yarn; +use dap::inline_value::{InlineValueLocation, VariableLookupKind, VariableScope}; + use crate::git_store::GitStore; pub use git_store::{ ConflictRegion, ConflictSet, ConflictSetSnapshot, ConflictSetUpdate, @@ -45,7 +47,7 @@ use client::{ }; use clock::ReplicaId; -use dap::{DapRegistry, client::DebugAdapterClient}; +use dap::client::DebugAdapterClient; use collections::{BTreeSet, HashMap, HashSet}; use debounced_delay::DebouncedDelay; @@ -111,7 +113,7 @@ use std::{ use task_store::TaskStore; use terminals::Terminals; -use text::{Anchor, BufferId}; +use text::{Anchor, BufferId, Point}; use toolchain_store::EmptyToolchainStore; use util::{ ResultExt as _, @@ -3667,35 +3669,15 @@ impl Project { range: Range, cx: &mut Context, ) -> Task>> { - let language_name = buffer_handle - .read(cx) - .language() - .map(|language| language.name().to_string()); - - let Some(inline_value_provider) = language_name - .and_then(|language| DapRegistry::global(cx).inline_value_provider(&language)) - else { - return Task::ready(Err(anyhow::anyhow!("Inline value provider not found"))); - }; - let snapshot = buffer_handle.read(cx).snapshot(); - let Some(root_node) = snapshot.syntax_root_ancestor(range.end) else { - return Task::ready(Ok(vec![])); - }; + let captures = snapshot.debug_variables_query(Anchor::MIN..range.end); let row = snapshot .summary_for_anchor::(&range.end) .row as usize; - let inline_value_locations = inline_value_provider.provide( - root_node, - snapshot - .text_for_range(Anchor::MIN..range.end) - .collect::() - .as_str(), - row, - ); + let inline_value_locations = provide_inline_values(captures, &snapshot, row); let stack_frame_id = active_stack_frame.stack_frame_id; cx.spawn(async move |this, cx| { @@ -5377,3 +5359,69 @@ fn proto_to_prompt(level: proto::language_server_prompt_request::Level) -> gpui: proto::language_server_prompt_request::Level::Critical(_) => gpui::PromptLevel::Critical, } } + +fn provide_inline_values( + captures: impl Iterator, language::DebuggerTextObject)>, + snapshot: &language::BufferSnapshot, + max_row: usize, +) -> Vec { + let mut variables = Vec::new(); + let mut variable_position = HashSet::default(); + let mut scopes = Vec::new(); + + let active_debug_line_offset = snapshot.point_to_offset(Point::new(max_row as u32, 0)); + + for (capture_range, capture_kind) in captures { + match capture_kind { + language::DebuggerTextObject::Variable => { + let variable_name = snapshot + .text_for_range(capture_range.clone()) + .collect::(); + let point = snapshot.offset_to_point(capture_range.end); + + while scopes.last().map_or(false, |scope: &Range<_>| { + !scope.contains(&capture_range.start) + }) { + scopes.pop(); + } + + if point.row as usize > max_row { + break; + } + + let scope = if scopes + .last() + .map_or(true, |scope| !scope.contains(&active_debug_line_offset)) + { + VariableScope::Global + } else { + VariableScope::Local + }; + + if variable_position.insert(capture_range.end) { + variables.push(InlineValueLocation { + variable_name, + scope, + lookup: VariableLookupKind::Variable, + row: point.row as usize, + column: point.column as usize, + }); + } + } + language::DebuggerTextObject::Scope => { + while scopes.last().map_or_else( + || false, + |scope: &Range| { + !(scope.contains(&capture_range.start) + && scope.contains(&capture_range.end)) + }, + ) { + scopes.pop(); + } + scopes.push(capture_range); + } + } + } + + variables +}