From fc1fc264ec02b326560291132e2e782b081f3c62 Mon Sep 17 00:00:00 2001 From: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> Date: Tue, 24 Jun 2025 14:24:43 -0400 Subject: [PATCH] debugger: Generate inline values based on debugger.scm file (#33081) ## Context To support inline values a language will have to implement their own provider trait that walks through tree sitter nodes. This is overly complicated, hard to accurately implement for each language, and lacks proper extension support. This PR switches to a singular inline provider that uses a language's `debugger.scm` query field to capture variables and scopes. The inline provider is able to use this information to generate inlays that take scope into account and work with any language that defines a debugger query file. ### Todos - [x] Implement a utility test function to easily test inline values - [x] Generate inline values based on captures - [x] Reimplement Python, Rust, and Go support - [x] Take scope into account when iterating through variable captures - [x] Add tests for Go inline values - [x] Remove old inline provider code and trait implementations Release Notes: - debugger: Generate inline values based on a language debugger.scm file --- Cargo.lock | 1 + crates/dap/src/inline_value.rs | 640 ------------------ crates/dap/src/registry.rs | 26 +- crates/dap_adapters/src/dap_adapters.rs | 6 - crates/debugger_ui/Cargo.toml | 1 + crates/debugger_ui/src/tests/inline_values.rs | 468 +++++++++++-- crates/editor/src/editor.rs | 3 +- crates/language/Cargo.toml | 1 + crates/language/src/buffer.rs | 84 ++- crates/language/src/language.rs | 50 ++ crates/language/src/language_registry.rs | 4 +- crates/languages/src/go/debugger.scm | 26 + crates/languages/src/python/debugger.scm | 43 ++ crates/languages/src/rust/debugger.scm | 50 ++ crates/project/src/debugger/dap_store.rs | 27 +- crates/project/src/debugger/session.rs | 11 +- crates/project/src/project.rs | 96 ++- 17 files changed, 786 insertions(+), 751 deletions(-) create mode 100644 crates/languages/src/go/debugger.scm create mode 100644 crates/languages/src/python/debugger.scm create mode 100644 crates/languages/src/rust/debugger.scm diff --git a/Cargo.lock b/Cargo.lock index 70a05cf4aa2a47de3973dbf64d4b8c0430d06a2c..0c832b83aa59834ee6fac4e8b936826de1465256 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 881797e20fb5e400ebbbfa6c88c9b5691f8928a9..47d783308518d4317ff8c7f100253bef431a1962 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 2786de227e95ffa9d0b253f1309224d6f21ed877..9435b16b924e43406d5ed99c864df78c179f27b1 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 414d0a91a3de0d5a75ea4dc981d277c84962246f..79c56fdf25583e6cbe3a182b3abf464ac449eb27 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 e259b8a4b38fef1d702ff5268c29f64fe45498c8..91f9acad3c73334980036880143df9c7b410b3b6 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 6fed57ecacc9ad6062a27f3fa33a95bd52cc1a10..45cab2a3063a8741d01efb54059667026a646879 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 568e9062c86bb5b2b584bfeaa4f430c88d251a76..6e9a9be0fe3267e5b13bfdb1b9d880848fa968bf 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 278976d3cdfaf304b6d28bd3c88e9a81cbfdb69f..b0e06c3d65a7bc05df0cb41104a1139353372539 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 523efa49dc71694084529a13d45b67fdd7c09afd..90a899f79d42f33f91044f025bc22383c2f3881d 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 8b8c411366f02793bdeb86bf5154fc77aa6d338b..f564b54ed52e028a8a19f13616bc42364ff4d4a4 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 4d0837d8e30fc1fd9be961e2cc05487d276e3792..c157cd9e73a0bb2f208672d391e98e2445317e5c 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 0000000000000000000000000000000000000000..f22b91f938e1159fa9bfec99f5000976766faf06 --- /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 0000000000000000000000000000000000000000..807d6e865d2f60637f60b397ccc1a61fe3360fa1 --- /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 0000000000000000000000000000000000000000..5347413f698083287b9bedd25f4732d24fbbf76e --- /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 28cfbe4e4d69ae67d99192cf0b99cfbca3f7ee31..be4964bbee2688c0025900c552eec3fbbc9af492 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 917506e523e7c7d64b58812baef78ec69e516ce8..300c598bfb9e1daa198baecda0ce5ef5c08aa3e7 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 2fc7fbbe7600889cb0c4d3c25a8453095aa878d4..e8b38148502fe161e0abb5b35dc5dd93ee331373 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 +}