From 1a520990ccf5946a8ac790115b078e5f7072c2a5 Mon Sep 17 00:00:00 2001 From: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> Date: Wed, 7 May 2025 14:39:35 +0200 Subject: [PATCH] debugger: Add inline value tests (#29815) ## Context This PR improves the accuracy of our inline values for Rust/Python. It does this by only adding inline value hints to the last valid use of a variable and checking whether variables are valid within a given scope or not. We also added tests for Rust/Python inline values and inline values refreshing when stepping in a debug session. ### Future tasks 1. Handle functions that have inner functions defined within them. 2. Add inline values to variables that were used in inner scopes but not defined in them. 3. Move the inline value provider trait and impls to the language trait (or somewhere else). 4. Use Semantic tokens as the first inline value provider and fall back to tree sitter 5. add let some variable statement, for loops, and function inline value hints to Rust. 6. Make writing tests more streamlined. 6.1 We should be able to write a test by only passing in variables, language, source file, expected result, and stop position to a function. 7. Write a test that has coverage for selecting different stack frames. co-authored-by: Remco Smits \ Release Notes: - N/A --------- Co-authored-by: Remco Smits --- Cargo.lock | 4 +- crates/dap/Cargo.toml | 1 - crates/dap/src/adapters.rs | 8 - crates/dap/src/dap.rs | 1 + crates/dap/src/inline_value.rs | 277 +++ crates/dap/src/registry.rs | 26 +- crates/dap_adapters/Cargo.toml | 1 - crates/dap_adapters/src/codelldb.rs | 23 +- crates/dap_adapters/src/dap_adapters.rs | 5 + crates/dap_adapters/src/python.rs | 35 +- crates/debugger_ui/Cargo.toml | 3 + crates/debugger_ui/src/tests.rs | 3 + crates/debugger_ui/src/tests/inline_values.rs | 1859 +++++++++++++++++ crates/editor/src/editor.rs | 12 +- crates/language/Cargo.toml | 5 +- crates/language/src/buffer.rs | 127 +- crates/language/src/language.rs | 38 - .../languages/src/python/debug_variables.scm | 5 - crates/languages/src/rust/debug_variables.scm | 3 - crates/project/src/debugger/dap_store.rs | 69 +- crates/project/src/project.rs | 51 +- 21 files changed, 2274 insertions(+), 282 deletions(-) create mode 100644 crates/dap/src/inline_value.rs create mode 100644 crates/debugger_ui/src/tests/inline_values.rs delete mode 100644 crates/languages/src/python/debug_variables.scm delete mode 100644 crates/languages/src/rust/debug_variables.scm diff --git a/Cargo.lock b/Cargo.lock index 4b6f74756cc95d3e61d9810dad246ff3aec4d828..140e81cfd59fe59023cf32ae8dab1fa541e66bf5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4068,7 +4068,6 @@ dependencies = [ "http_client", "language", "log", - "lsp-types", "node_runtime", "parking_lot", "paths", @@ -4104,7 +4103,6 @@ dependencies = [ "futures 0.3.31", "gpui", "language", - "lsp-types", "paths", "serde", "serde_json", @@ -4249,6 +4247,7 @@ dependencies = [ "collections", "command_palette_hooks", "dap", + "dap_adapters", "db", "debugger_tools", "editor", @@ -7797,6 +7796,7 @@ dependencies = [ "tree-sitter-html", "tree-sitter-json", "tree-sitter-md", + "tree-sitter-python", "tree-sitter-ruby", "tree-sitter-rust", "tree-sitter-typescript", diff --git a/crates/dap/Cargo.toml b/crates/dap/Cargo.toml index c5e7fec0c126f478685df98999808b5c7feb8196..531276e7088da42578e788e5b8eed48aecea07e4 100644 --- a/crates/dap/Cargo.toml +++ b/crates/dap/Cargo.toml @@ -36,7 +36,6 @@ gpui.workspace = true http_client.workspace = true language.workspace = true log.workspace = true -lsp-types.workspace = true node_runtime.workspace = true parking_lot.workspace = true paths.workspace = true diff --git a/crates/dap/src/adapters.rs b/crates/dap/src/adapters.rs index 3cd8bb9fb79407d0b1d71eb4dcfc15b5d157eff5..485e5f1b4c1dd96b4247a325bc081111971d5ee0 100644 --- a/crates/dap/src/adapters.rs +++ b/crates/dap/src/adapters.rs @@ -402,10 +402,6 @@ pub async fn fetch_latest_adapter_version_from_github( }) } -pub trait InlineValueProvider { - fn provide(&self, variables: Vec<(String, lsp_types::Range)>) -> Vec; -} - #[async_trait(?Send)] pub trait DebugAdapter: 'static + Send + Sync { fn name(&self) -> DebugAdapterName; @@ -417,10 +413,6 @@ pub trait DebugAdapter: 'static + Send + Sync { user_installed_path: Option, cx: &mut AsyncApp, ) -> Result; - - fn inline_value_provider(&self) -> Option> { - None - } } #[cfg(any(test, feature = "test-support"))] diff --git a/crates/dap/src/dap.rs b/crates/dap/src/dap.rs index 8e06396d6b55b0dc2e1637e861d4e7cfab84b9ba..df8d915812bcbec845f2d6d878b08c28db24e730 100644 --- a/crates/dap/src/dap.rs +++ b/crates/dap/src/dap.rs @@ -1,6 +1,7 @@ pub mod adapters; pub mod client; pub mod debugger_settings; +pub mod inline_value; pub mod proto_conversions; mod registry; pub mod transport; diff --git a/crates/dap/src/inline_value.rs b/crates/dap/src/inline_value.rs new file mode 100644 index 0000000000000000000000000000000000000000..7204d985aa7ad72ebc23f7d180ed7a9bae8ed014 --- /dev/null +++ b/crates/dap/src/inline_value.rs @@ -0,0 +1,277 @@ +use std::collections::{HashMap, HashSet}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum VariableLookupKind { + Variable, + Expression, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum VariableScope { + Local, + Global, +} + +#[derive(Debug, Clone)] +pub struct InlineValueLocation { + pub variable_name: String, + pub scope: VariableScope, + pub lookup: VariableLookupKind, + 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 { + /// 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 + } +} diff --git a/crates/dap/src/registry.rs b/crates/dap/src/registry.rs index c631452b8c1be3d8ff6bd7190dd7b958cb3024f9..a03be4860d0a7bd668c74fecd79a09b8f8555869 100644 --- a/crates/dap/src/registry.rs +++ b/crates/dap/src/registry.rs @@ -5,7 +5,10 @@ use gpui::{App, Global, SharedString}; use parking_lot::RwLock; use task::{DebugRequest, DebugScenario, SpawnInTerminal, TaskTemplate}; -use crate::adapters::{DebugAdapter, DebugAdapterName}; +use crate::{ + adapters::{DebugAdapter, DebugAdapterName}, + inline_value::InlineValueProvider, +}; use std::{collections::BTreeMap, sync::Arc}; /// Given a user build configuration, locator creates a fill-in debug target ([DebugRequest]) on behalf of the user. @@ -22,6 +25,7 @@ pub trait DapLocator: Send + Sync { struct DapRegistryState { adapters: BTreeMap>, locators: FxHashMap>, + inline_value_providers: FxHashMap>, } #[derive(Clone, Default)] @@ -58,6 +62,22 @@ impl DapRegistry { ); } + 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() } @@ -66,6 +86,10 @@ 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/Cargo.toml b/crates/dap_adapters/Cargo.toml index 794db55feb39baf88cfa811bdeeb8738587cea69..77dbe40088065e8ab299be86281582872c32de41 100644 --- a/crates/dap_adapters/Cargo.toml +++ b/crates/dap_adapters/Cargo.toml @@ -27,7 +27,6 @@ dap.workspace = true futures.workspace = true gpui.workspace = true language.workspace = true -lsp-types.workspace = true paths.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/crates/dap_adapters/src/codelldb.rs b/crates/dap_adapters/src/codelldb.rs index 8acfec1b5e1592535755a37cec6d67b6b0776f19..e0c585e4663e058bcc12c90757a49ba6ec38af7c 100644 --- a/crates/dap_adapters/src/codelldb.rs +++ b/crates/dap_adapters/src/codelldb.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, path::PathBuf, sync::OnceLock}; use anyhow::Result; use async_trait::async_trait; -use dap::adapters::{DebugTaskDefinition, InlineValueProvider, latest_github_release}; +use dap::adapters::{DebugTaskDefinition, latest_github_release}; use futures::StreamExt; use gpui::AsyncApp; use task::DebugRequest; @@ -159,25 +159,4 @@ impl DebugAdapter for CodeLldbDebugAdapter { connection: None, }) } - - fn inline_value_provider(&self) -> Option> { - Some(Box::new(CodeLldbInlineValueProvider)) - } -} - -struct CodeLldbInlineValueProvider; - -impl InlineValueProvider for CodeLldbInlineValueProvider { - fn provide(&self, variables: Vec<(String, lsp_types::Range)>) -> Vec { - variables - .into_iter() - .map(|(variable, range)| { - lsp_types::InlineValue::VariableLookup(lsp_types::InlineValueVariableLookup { - range, - variable_name: Some(variable), - case_sensitive_lookup: true, - }) - }) - .collect() - } } diff --git a/crates/dap_adapters/src/dap_adapters.rs b/crates/dap_adapters/src/dap_adapters.rs index 93af93c6b9daed51654ad4ea2ea86d03851044a2..4c1ed025adc39fced0adafad1bd6fbc381cea16b 100644 --- a/crates/dap_adapters/src/dap_adapters.rs +++ b/crates/dap_adapters/src/dap_adapters.rs @@ -16,6 +16,7 @@ use dap::{ self, AdapterVersion, DapDelegate, DebugAdapter, DebugAdapterBinary, DebugAdapterName, GithubRepo, }, + inline_value::{PythonInlineValueProvider, RustInlineValueProvider}, }; use gdb::GdbDebugAdapter; use go::GoDebugAdapter; @@ -34,6 +35,10 @@ pub fn init(cx: &mut App) { registry.add_adapter(Arc::from(JsDebugAdapter::default())); registry.add_adapter(Arc::from(GoDebugAdapter)); registry.add_adapter(Arc::from(GdbDebugAdapter)); + + registry.add_inline_value_provider("Rust".to_string(), Arc::from(RustInlineValueProvider)); + registry + .add_inline_value_provider("Python".to_string(), Arc::from(PythonInlineValueProvider)); }) } diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index f1eb24c6e88e1a4aef3c32ca9c37eff0878884b6..5c0d805ba43b59c03907dfdea2003cf0ff255c17 100644 --- a/crates/dap_adapters/src/python.rs +++ b/crates/dap_adapters/src/python.rs @@ -1,8 +1,5 @@ use crate::*; -use dap::{ - DebugRequest, StartDebuggingRequestArguments, adapters::DebugTaskDefinition, - adapters::InlineValueProvider, -}; +use dap::{DebugRequest, StartDebuggingRequestArguments, adapters::DebugTaskDefinition}; use gpui::AsyncApp; use std::{collections::HashMap, ffi::OsStr, path::PathBuf, sync::OnceLock}; use util::ResultExt; @@ -182,34 +179,4 @@ impl DebugAdapter for PythonDebugAdapter { self.get_installed_binary(delegate, &config, user_installed_path, cx) .await } - - fn inline_value_provider(&self) -> Option> { - Some(Box::new(PythonInlineValueProvider)) - } -} - -struct PythonInlineValueProvider; - -impl InlineValueProvider for PythonInlineValueProvider { - fn provide(&self, variables: Vec<(String, lsp_types::Range)>) -> Vec { - variables - .into_iter() - .map(|(variable, range)| { - if variable.contains(".") || variable.contains("[") { - lsp_types::InlineValue::EvaluatableExpression( - lsp_types::InlineValueEvaluatableExpression { - range, - expression: Some(variable), - }, - ) - } else { - lsp_types::InlineValue::VariableLookup(lsp_types::InlineValueVariableLookup { - range, - variable_name: Some(variable), - case_sensitive_lookup: true, - }) - } - }) - .collect() - } } diff --git a/crates/debugger_ui/Cargo.toml b/crates/debugger_ui/Cargo.toml index a195b108016fab18c12269237e5433c4cd0fbc05..295d18e4b3b8c217bad19b4d676897f47a18f905 100644 --- a/crates/debugger_ui/Cargo.toml +++ b/crates/debugger_ui/Cargo.toml @@ -15,6 +15,7 @@ doctest = false [features] test-support = [ "dap/test-support", + "dap_adapters/test-support", "editor/test-support", "gpui/test-support", "project/test-support", @@ -31,6 +32,7 @@ client.workspace = true collections.workspace = true command_palette_hooks.workspace = true dap.workspace = true +dap_adapters = { workspace = true, optional = true } db.workspace = true editor.workspace = true feature_flags.workspace = true @@ -63,6 +65,7 @@ unindent = { workspace = true, optional = true } [dev-dependencies] dap = { workspace = true, features = ["test-support"] } +dap_adapters = { workspace = true, features = ["test-support"] } debugger_tools = { workspace = true, features = ["test-support"] } editor = { workspace = true, features = ["test-support"] } env_logger.workspace = true diff --git a/crates/debugger_ui/src/tests.rs b/crates/debugger_ui/src/tests.rs index c99ccc12ea0db75b2659bdf9a9ce7055d8fa8f8f..e8e9beef354df771680ce829e53be38b3d618666 100644 --- a/crates/debugger_ui/src/tests.rs +++ b/crates/debugger_ui/src/tests.rs @@ -21,6 +21,8 @@ mod dap_logger; #[cfg(test)] mod debugger_panel; #[cfg(test)] +mod inline_values; +#[cfg(test)] mod module_list; #[cfg(test)] mod persistence; @@ -45,6 +47,7 @@ pub fn init_test(cx: &mut gpui::TestAppContext) { Project::init_settings(cx); editor::init(cx); crate::init(cx); + dap_adapters::init(cx); }); } diff --git a/crates/debugger_ui/src/tests/inline_values.rs b/crates/debugger_ui/src/tests/inline_values.rs new file mode 100644 index 0000000000000000000000000000000000000000..76b94360807e4c20fdc44074646a9e9ae1a59883 --- /dev/null +++ b/crates/debugger_ui/src/tests/inline_values.rs @@ -0,0 +1,1859 @@ +use std::{path::Path, sync::Arc}; + +use dap::{Scope, StackFrame, Variable, requests::Variables}; +use editor::{Editor, EditorMode, MultiBuffer, actions::ToggleInlineValues}; +use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext}; +use language::{Language, LanguageConfig, LanguageMatcher, tree_sitter_python, tree_sitter_rust}; +use project::{FakeFs, Project}; +use serde_json::json; +use unindent::Unindent as _; +use util::path; + +use crate::{ + debugger_panel::DebugPanel, + tests::{active_debug_session_panel, init_test, init_test_workspace, start_debug_session}, +}; + +#[gpui::test] +async fn test_rust_inline_values(executor: BackgroundExecutor, cx: &mut TestAppContext) { + init_test(cx); + + fn stack_frame_for_line(line: u64) -> dap::StackFrame { + StackFrame { + id: 1, + name: "Stack Frame 1".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, + column: 1, + end_line: None, + end_column: None, + can_restart: None, + instruction_pointer_reference: None, + module_id: None, + presentation_hint: None, + } + } + + let fs = FakeFs::new(executor.clone()); + let source_code = r#" +static mut GLOBAL: usize = 1; + +fn main() { + let x = 10; + let value = 42; + let y = 4; + let tester = { + let y = 10; + let y = 5; + let b = 3; + vec![y, 20, 30] + }; + + let caller = || { + let x = 3; + println!("x={}", x); + }; + + caller(); + + unsafe { + GLOBAL = 2; + } + + let result = value * 2 * x; + println!("Simple test executed: value={}, result={}", value, result); + assert!(true); +} +"# + .unindent(); + fs.insert_tree(path!("/project"), json!({ "main.rs": source_code })) + .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::(move |_, _| { + Ok(dap::ThreadsResponse { + threads: vec![dap::Thread { + id: 1, + name: "Thread 1".into(), + }], + }) + }); + + client.on_request::(move |_, _| { + Ok(dap::StackTraceResponse { + stack_frames: vec![stack_frame_for_line(4)], + total_frames: None, + }) + }); + + client.on_request::(move |_, args| { + assert_eq!("GLOBAL", args.expression); + Ok(dap::EvaluateResponse { + result: "1".into(), + type_: None, + presentation_hint: None, + variables_reference: 0, + named_variables: None, + indexed_variables: None, + memory_reference: None, + value_location_reference: None, + }) + }); + + let local_variables = vec![ + Variable { + name: "x".into(), + value: "10".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, + }, + Variable { + name: "y".into(), + value: "4".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, + }, + Variable { + name: "value".into(), + value: "42".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, + }, + ]; + + client.on_request::({ + let local_variables = Arc::new(local_variables.clone()); + move |_, _| { + Ok(dap::VariablesResponse { + variables: (*local_variables).clone(), + }) + } + }); + + client.on_request::(move |_, _| { + Ok(dap::ScopesResponse { + scopes: vec![Scope { + name: "Locale".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, + }], + }) + }); + + 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(rust_lang())), 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_in(cx, |editor, window, cx| { + if !editor.inline_values_enabled() { + editor.toggle_inline_values(&ToggleInlineValues, window, cx); + } + }); + + cx.run_until_parked(); + + editor.update_in(cx, |editor, window, cx| { + pretty_assertions::assert_eq!( + r#" + static mut GLOBAL: 1: usize = 1; + + fn main() { + let x = 10; + let value = 42; + let y = 4; + let tester = { + let y = 10; + let y = 5; + let b = 3; + vec![y, 20, 30] + }; + + let caller = || { + let x = 3; + println!("x={}", x); + }; + + caller(); + + unsafe { + GLOBAL = 2; + } + + let result = value * 2 * x; + println!("Simple test executed: value={}, result={}", value, result); + assert!(true); + } + "# + .unindent(), + editor.snapshot(window, cx).text() + ); + }); + + client.on_request::(move |_, _| { + Ok(dap::StackTraceResponse { + stack_frames: vec![stack_frame_for_line(5)], + total_frames: 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(); + + editor.update_in(cx, |editor, window, cx| { + pretty_assertions::assert_eq!( + r#" + static mut GLOBAL: 1: usize = 1; + + fn main() { + let x: 10 = 10; + let value = 42; + let y = 4; + let tester = { + let y = 10; + let y = 5; + let b = 3; + vec![y, 20, 30] + }; + + let caller = || { + let x = 3; + println!("x={}", x); + }; + + caller(); + + unsafe { + GLOBAL = 2; + } + + let result = value * 2 * x; + println!("Simple test executed: value={}, result={}", value, result); + assert!(true); + } + "# + .unindent(), + editor.snapshot(window, cx).text() + ); + }); + + client.on_request::(move |_, _| { + Ok(dap::StackTraceResponse { + stack_frames: vec![stack_frame_for_line(6)], + total_frames: 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(); + + editor.update_in(cx, |editor, window, cx| { + pretty_assertions::assert_eq!( + r#" + static mut GLOBAL: 1: usize = 1; + + fn main() { + let x: 10 = 10; + let value: 42 = 42; + let y = 4; + let tester = { + let y = 10; + let y = 5; + let b = 3; + vec![y, 20, 30] + }; + + let caller = || { + let x = 3; + println!("x={}", x); + }; + + caller(); + + unsafe { + GLOBAL = 2; + } + + let result = value * 2 * x; + println!("Simple test executed: value={}, result={}", value, result); + assert!(true); + } + "# + .unindent(), + editor.snapshot(window, cx).text() + ); + }); + + client.on_request::(move |_, _| { + Ok(dap::StackTraceResponse { + stack_frames: vec![stack_frame_for_line(7)], + total_frames: 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(); + + editor.update_in(cx, |editor, window, cx| { + pretty_assertions::assert_eq!( + r#" + static mut GLOBAL: 1: usize = 1; + + fn main() { + let x: 10 = 10; + let value: 42 = 42; + let y: 4 = 4; + let tester = { + let y = 10; + let y = 5; + let b = 3; + vec![y, 20, 30] + }; + + let caller = || { + let x = 3; + println!("x={}", x); + }; + + caller(); + + unsafe { + GLOBAL = 2; + } + + let result = value * 2 * x; + println!("Simple test executed: value={}, result={}", value, result); + assert!(true); + } + "# + .unindent(), + editor.snapshot(window, cx).text() + ); + }); + + client.on_request::(move |_, _| { + Ok(dap::StackTraceResponse { + stack_frames: vec![stack_frame_for_line(8)], + total_frames: 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(); + + editor.update_in(cx, |editor, window, cx| { + pretty_assertions::assert_eq!( + r#" + static mut GLOBAL: 1: usize = 1; + + fn main() { + let x: 10 = 10; + let value: 42 = 42; + let y: 4 = 4; + let tester = { + let y = 10; + let y = 5; + let b = 3; + vec![y, 20, 30] + }; + + let caller = || { + let x = 3; + println!("x={}", x); + }; + + caller(); + + unsafe { + GLOBAL = 2; + } + + let result = value * 2 * x; + println!("Simple test executed: value={}, result={}", value, result); + assert!(true); + } + "# + .unindent(), + editor.snapshot(window, cx).text() + ); + }); + + let local_variables = vec![ + Variable { + name: "x".into(), + value: "10".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, + }, + Variable { + name: "y".into(), + value: "10".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, + }, + Variable { + name: "value".into(), + value: "42".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, + }, + ]; + + client.on_request::({ + let local_variables = Arc::new(local_variables.clone()); + move |_, _| { + Ok(dap::VariablesResponse { + variables: (*local_variables).clone(), + }) + } + }); + client.on_request::(move |_, _| { + Ok(dap::StackTraceResponse { + stack_frames: vec![stack_frame_for_line(9)], + total_frames: 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(); + + editor.update_in(cx, |editor, window, cx| { + pretty_assertions::assert_eq!( + r#" + static mut GLOBAL: 1: usize = 1; + + fn main() { + let x: 10 = 10; + let value: 42 = 42; + let y = 4; + let tester = { + let y: 10 = 10; + let y = 5; + let b = 3; + vec![y, 20, 30] + }; + + let caller = || { + let x = 3; + println!("x={}", x); + }; + + caller(); + + unsafe { + GLOBAL = 2; + } + + let result = value * 2 * x; + println!("Simple test executed: value={}, result={}", value, result); + assert!(true); + } + "# + .unindent(), + editor.snapshot(window, cx).text() + ); + }); + + let local_variables = vec![ + Variable { + name: "x".into(), + value: "10".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, + }, + Variable { + name: "y".into(), + value: "5".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, + }, + Variable { + name: "value".into(), + value: "42".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, + }, + ]; + + client.on_request::({ + let local_variables = Arc::new(local_variables.clone()); + move |_, _| { + Ok(dap::VariablesResponse { + variables: (*local_variables).clone(), + }) + } + }); + client.on_request::(move |_, _| { + Ok(dap::StackTraceResponse { + stack_frames: vec![stack_frame_for_line(10)], + total_frames: 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(); + + editor.update_in(cx, |editor, window, cx| { + pretty_assertions::assert_eq!( + r#" + static mut GLOBAL: 1: usize = 1; + + fn main() { + let x: 10 = 10; + let value: 42 = 42; + let y = 4; + let tester = { + let y = 10; + let y: 5 = 5; + let b = 3; + vec![y, 20, 30] + }; + + let caller = || { + let x = 3; + println!("x={}", x); + }; + + caller(); + + unsafe { + GLOBAL = 2; + } + + let result = value * 2 * x; + println!("Simple test executed: value={}, result={}", value, result); + assert!(true); + } + "# + .unindent(), + editor.snapshot(window, cx).text() + ); + }); + + let local_variables = vec![ + Variable { + name: "x".into(), + value: "10".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, + }, + Variable { + name: "y".into(), + value: "5".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, + }, + Variable { + name: "value".into(), + value: "42".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, + }, + Variable { + name: "b".into(), + value: "3".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, + }, + ]; + client.on_request::({ + let local_variables = Arc::new(local_variables.clone()); + move |_, _| { + Ok(dap::VariablesResponse { + variables: (*local_variables).clone(), + }) + } + }); + client.on_request::(move |_, _| { + Ok(dap::StackTraceResponse { + stack_frames: vec![stack_frame_for_line(11)], + total_frames: 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(); + + editor.update_in(cx, |editor, window, cx| { + pretty_assertions::assert_eq!( + r#" + static mut GLOBAL: 1: usize = 1; + + fn main() { + let x: 10 = 10; + let value: 42 = 42; + let y = 4; + let tester = { + let y = 10; + let y: 5 = 5; + let b: 3 = 3; + vec![y, 20, 30] + }; + + let caller = || { + let x = 3; + println!("x={}", x); + }; + + caller(); + + unsafe { + GLOBAL = 2; + } + + let result = value * 2 * x; + println!("Simple test executed: value={}, result={}", value, result); + assert!(true); + } + "# + .unindent(), + editor.snapshot(window, cx).text() + ); + }); + + let local_variables = vec![ + Variable { + name: "x".into(), + value: "10".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, + }, + Variable { + name: "y".into(), + value: "4".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, + }, + Variable { + name: "value".into(), + value: "42".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, + }, + Variable { + name: "tester".into(), + value: "size=3".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, + }, + ]; + client.on_request::({ + let local_variables = Arc::new(local_variables.clone()); + move |_, _| { + Ok(dap::VariablesResponse { + variables: (*local_variables).clone(), + }) + } + }); + client.on_request::(move |_, _| { + Ok(dap::StackTraceResponse { + stack_frames: vec![stack_frame_for_line(14)], + total_frames: 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(); + + editor.update_in(cx, |editor, window, cx| { + pretty_assertions::assert_eq!( + r#" + static mut GLOBAL: 1: usize = 1; + + fn main() { + 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; + println!("x={}", x); + }; + + caller(); + + unsafe { + GLOBAL = 2; + } + + let result = value * 2 * x; + println!("Simple test executed: value={}, result={}", value, result); + assert!(true); + } + "# + .unindent(), + editor.snapshot(window, cx).text() + ); + }); + + let local_variables = vec![ + Variable { + name: "x".into(), + value: "10".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, + }, + Variable { + name: "y".into(), + value: "4".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, + }, + Variable { + name: "value".into(), + value: "42".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, + }, + Variable { + name: "tester".into(), + value: "size=3".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, + }, + Variable { + name: "caller".into(), + 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, + }, + ]; + client.on_request::({ + let local_variables = Arc::new(local_variables.clone()); + move |_, _| { + Ok(dap::VariablesResponse { + variables: (*local_variables).clone(), + }) + } + }); + client.on_request::(move |_, _| { + Ok(dap::StackTraceResponse { + stack_frames: vec![stack_frame_for_line(19)], + total_frames: 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(); + + editor.update_in(cx, |editor, window, cx| { + pretty_assertions::assert_eq!( + r#" + static mut GLOBAL: 1: usize = 1; + + fn main() { + 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; + println!("x={}", x); + }; + + caller(); + + unsafe { + GLOBAL = 2; + } + + let result = value * 2 * x; + println!("Simple test executed: value={}, result={}", value, result); + assert!(true); + } + "# + .unindent(), + editor.snapshot(window, cx).text() + ); + }); + + client.on_request::(move |_, _| { + Ok(dap::StackTraceResponse { + stack_frames: vec![stack_frame_for_line(15)], + total_frames: 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(); + + editor.update_in(cx, |editor, window, cx| { + pretty_assertions::assert_eq!( + r#" + static mut GLOBAL: 1: usize = 1; + + fn main() { + let x = 10; + let value = 42; + let y = 4; + let tester = { + let y = 10; + let y = 5; + let b = 3; + vec![y, 20, 30] + }; + + let caller = || { + let x = 3; + println!("x={}", x); + }; + + caller(); + + unsafe { + GLOBAL = 2; + } + + let result = value * 2 * x; + println!("Simple test executed: value={}, result={}", value, result); + assert!(true); + } + "# + .unindent(), + editor.snapshot(window, cx).text() + ); + }); + + let local_variables = vec![Variable { + name: "x".into(), + value: "3".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, + }]; + client.on_request::({ + let local_variables = Arc::new(local_variables.clone()); + move |_, _| { + Ok(dap::VariablesResponse { + variables: (*local_variables).clone(), + }) + } + }); + client.on_request::(move |_, _| { + Ok(dap::StackTraceResponse { + stack_frames: vec![stack_frame_for_line(16)], + total_frames: 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(); + + editor.update_in(cx, |editor, window, cx| { + pretty_assertions::assert_eq!( + r#" + static mut GLOBAL: 1: usize = 1; + + fn main() { + let x = 10; + let value = 42; + let y = 4; + let tester = { + let y = 10; + let y = 5; + let b = 3; + vec![y, 20, 30] + }; + + let caller = || { + let x: 3 = 3; + println!("x={}", x); + }; + + caller(); + + unsafe { + GLOBAL = 2; + } + + let result = value * 2 * x; + println!("Simple test executed: value={}, result={}", value, result); + assert!(true); + } + "# + .unindent(), + editor.snapshot(window, cx).text() + ); + }); + + let local_variables = vec![ + Variable { + name: "x".into(), + value: "10".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, + }, + Variable { + name: "y".into(), + value: "4".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, + }, + Variable { + name: "value".into(), + value: "42".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, + }, + Variable { + name: "tester".into(), + value: "size=3".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, + }, + Variable { + name: "caller".into(), + 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, + }, + ]; + client.on_request::({ + let local_variables = Arc::new(local_variables.clone()); + move |_, _| { + Ok(dap::VariablesResponse { + variables: (*local_variables).clone(), + }) + } + }); + client.on_request::(move |_, args| { + assert_eq!("GLOBAL", args.expression); + Ok(dap::EvaluateResponse { + result: "2".into(), + type_: None, + presentation_hint: None, + variables_reference: 0, + named_variables: None, + indexed_variables: None, + memory_reference: None, + value_location_reference: None, + }) + }); + client.on_request::(move |_, _| { + Ok(dap::StackTraceResponse { + stack_frames: vec![stack_frame_for_line(25)], + total_frames: 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(); + + editor.update_in(cx, |editor, window, cx| { + pretty_assertions::assert_eq!( + r#" + static mut GLOBAL: 2: usize = 1; + + fn main() { + 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; + println!("x={}", x); + }; + + caller(); + + unsafe { + GLOBAL = 2; + } + + let result = value * 2 * x; + println!("Simple test executed: value={}, result={}", value, result); + assert!(true); + } + "# + .unindent(), + editor.snapshot(window, cx).text() + ); + }); + + let local_variables = vec![ + Variable { + name: "x".into(), + value: "10".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, + }, + Variable { + name: "y".into(), + value: "4".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, + }, + Variable { + name: "value".into(), + value: "42".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, + }, + Variable { + name: "tester".into(), + value: "size=3".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, + }, + Variable { + name: "caller".into(), + 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, + }, + Variable { + name: "result".into(), + value: "840".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, + }, + ]; + client.on_request::({ + let local_variables = Arc::new(local_variables.clone()); + move |_, _| { + Ok(dap::VariablesResponse { + variables: (*local_variables).clone(), + }) + } + }); + client.on_request::(move |_, _| { + Ok(dap::StackTraceResponse { + stack_frames: vec![stack_frame_for_line(26)], + total_frames: 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(); + + editor.update_in(cx, |editor, window, cx| { + pretty_assertions::assert_eq!( + r#" + static mut GLOBAL: 2: usize = 1; + + fn main() { + 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; + println!("x={}", x); + }; + + caller(); + + unsafe { + GLOBAL = 2; + } + + let result: 840 = value * 2 * x; + println!("Simple test executed: value={}, result={}", value, result); + assert!(true); + } + "# + .unindent(), + editor.snapshot(window, cx).text() + ); + }); +} + +fn rust_lang() -> Language { + Language::new( + LanguageConfig { + name: "Rust".into(), + matcher: LanguageMatcher { + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + ..Default::default() + }, + Some(tree_sitter_rust::LANGUAGE.into()), + ) +} + +#[gpui::test] +async fn test_python_inline_values(executor: BackgroundExecutor, cx: &mut TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(executor.clone()); + let source_code = r#" +def process_data(untyped_param, typed_param: int, another_typed: str): + # Local variables + x = 10 + result = typed_param * 2 + text = "Hello, " + another_typed + + # For loop with range + sum_value = 0 + for i in range(5): + sum_value += i + + # Final result + final_result = x + result + sum_value + return final_result +"# + .unindent(); + fs.insert_tree(path!("/project"), json!({ "main.py": source_code })) + .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()); + + 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.py"), cx) + }) + .await + .unwrap(); + + buffer.update(cx, |buffer, cx| { + buffer.set_language(Some(Arc::new(python_lang())), cx); + }); + + let (editor, cx) = cx.add_window_view(|window, cx| { + Editor::new( + EditorMode::full(), + MultiBuffer::build_from_buffer(buffer, cx), + Some(project), + window, + cx, + ) + }); + + editor.update_in(cx, |editor, window, cx| { + if !editor.inline_values_enabled() { + editor.toggle_inline_values(&ToggleInlineValues, window, cx); + } + }); + + client.on_request::(move |_, _| { + Ok(dap::ThreadsResponse { + threads: vec![dap::Thread { + id: 1, + name: "Thread 1".into(), + }], + }) + }); + + client.on_request::(move |_, args| { + assert_eq!(args.thread_id, 1); + Ok(dap::StackTraceResponse { + stack_frames: vec![StackFrame { + id: 1, + name: "Stack Frame 1".into(), + source: Some(dap::Source { + name: Some("main.py".into()), + path: Some(path!("/project/main.py").into()), + source_reference: None, + presentation_hint: None, + origin: None, + sources: None, + adapter_data: None, + checksums: None, + }), + line: 12, + column: 1, + end_line: None, + end_column: None, + can_restart: None, + instruction_pointer_reference: None, + module_id: None, + presentation_hint: None, + }], + total_frames: None, + }) + }); + + client.on_request::(move |_, _| { + Ok(dap::ScopesResponse { + scopes: vec![ + Scope { + name: "Local".into(), + presentation_hint: None, + variables_reference: 1, + 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: 2, + named_variables: None, + indexed_variables: None, + expensive: false, + source: None, + line: None, + column: None, + end_line: None, + end_column: None, + }, + ], + }) + }); + + client.on_request::(move |_, args| match args.variables_reference { + 1 => Ok(dap::VariablesResponse { + variables: vec![ + Variable { + name: "untyped_param".into(), + value: "test_value".into(), + type_: Some("str".into()), + 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, + }, + Variable { + name: "typed_param".into(), + value: "42".into(), + type_: Some("int".into()), + 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, + }, + Variable { + name: "another_typed".into(), + value: "world".into(), + type_: Some("str".into()), + 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, + }, + Variable { + name: "x".into(), + value: "10".into(), + type_: Some("int".into()), + 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, + }, + Variable { + name: "result".into(), + value: "84".into(), + type_: Some("int".into()), + 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, + }, + Variable { + name: "text".into(), + value: "Hello, world".into(), + type_: Some("str".into()), + 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, + }, + Variable { + name: "sum_value".into(), + value: "10".into(), + type_: Some("int".into()), + 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, + }, + Variable { + name: "i".into(), + value: "4".into(), + type_: Some("int".into()), + 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, + }, + Variable { + name: "final_result".into(), + value: "104".into(), + type_: Some("int".into()), + 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, + }, + ], + }), + _ => Ok(dap::VariablesResponse { variables: vec![] }), + }); + + 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(); + + editor.update_in(cx, |editor, window, cx| { + pretty_assertions::assert_eq!( + r#" + 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 + + # For loop with range + sum_value: 10 = 0 + for i: 4 in range(5): + sum_value += i + + # Final result + final_result = x + result + sum_value + return final_result + "# + .unindent(), + editor.snapshot(window, cx).text() + ); + }); +} + +fn python_lang() -> Language { + Language::new( + LanguageConfig { + name: "Python".into(), + matcher: LanguageMatcher { + path_suffixes: vec!["py".to_string()], + ..Default::default() + }, + ..Default::default() + }, + Some(tree_sitter_python::LANGUAGE.into()), + ) +} diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 1c3318b08964c0364a948698703058c4243042f2..16b259629e0736cbf781450dd0659810b9063755 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4333,6 +4333,16 @@ impl Editor { self.inline_value_cache.enabled } + #[cfg(any(test, feature = "test-support"))] + pub fn inline_value_inlays(&self, cx: &App) -> Vec { + self.display_map + .read(cx) + .current_inlays() + .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_))) + .cloned() + .collect() + } + fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context) { if self.semantics_provider.is_none() || !self.mode.is_full() { return; @@ -17665,7 +17675,7 @@ impl Editor { } } - fn refresh_inline_values(&mut self, cx: &mut Context) { + pub fn refresh_inline_values(&mut self, cx: &mut Context) { let Some(project) = self.project.clone() else { return; }; diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index cc5a3a2972d7085c626df10a8ea13c2fb8a20ae0..407573513d50e0e9ae0df53ef9319187d30151ae 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -19,6 +19,7 @@ test-support = [ "lsp/test-support", "text/test-support", "tree-sitter-rust", + "tree-sitter-python", "tree-sitter-typescript", "settings/test-support", "util/test-support", @@ -58,6 +59,7 @@ sum_tree.workspace = true task.workspace = true text.workspace = true theme.workspace = true +tree-sitter-python = { workspace = true, optional = true } tree-sitter-rust = { workspace = true, optional = true } tree-sitter-typescript = { workspace = true, optional = true } tree-sitter.workspace = true @@ -76,15 +78,16 @@ pretty_assertions.workspace = true rand.workspace = true settings = { workspace = true, features = ["test-support"] } text = { workspace = true, features = ["test-support"] } +http_client = { workspace = true, features = ["test-support"] } tree-sitter-elixir.workspace = true tree-sitter-embedded-template.workspace = true tree-sitter-heex.workspace = true tree-sitter-html.workspace = true tree-sitter-json.workspace = true tree-sitter-md.workspace = true +tree-sitter-python.workspace = true tree-sitter-ruby.workspace = true tree-sitter-rust.workspace = true tree-sitter-typescript.workspace = true unindent.workspace = true util = { workspace = true, features = ["test-support"] } -http_client = { workspace = true, features = ["test-support"] } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index b40d889d17337a7473da414018540c83313e9558..700157496ed2e0d45ae438805b65c74f30093b72 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1,6 +1,12 @@ +pub use crate::{ + Grammar, Language, LanguageRegistry, + diagnostic_set::DiagnosticSet, + highlight_map::{HighlightId, HighlightMap}, + proto, +}; use crate::{ - DebugVariableCapture, LanguageScope, Outline, OutlineConfig, RunnableCapture, RunnableTag, - TextObject, TreeSitterOptions, + LanguageScope, Outline, OutlineConfig, RunnableCapture, RunnableTag, TextObject, + TreeSitterOptions, diagnostic_set::{DiagnosticEntry, DiagnosticGroup}, language_settings::{LanguageSettings, language_settings}, outline::OutlineItem, @@ -11,12 +17,6 @@ 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, anyhow}; use async_watch as watch; pub use clock::ReplicaId; @@ -69,16 +69,10 @@ use util::RandomCharIter; use util::{RangeExt, debug_panic, maybe}; #[cfg(any(test, feature = "test-support"))] -pub use {tree_sitter_rust, tree_sitter_typescript}; +pub use {tree_sitter_python, tree_sitter_rust, tree_sitter_typescript}; pub use lsp::DiagnosticSeverity; -#[derive(Debug)] -pub struct DebugVariableRanges { - pub buffer_id: BufferId, - pub range: Range, -} - /// A label for the background task spawned by the buffer to compute /// a diff against the contents of its file. pub static BUFFER_DIFF_TASK: LazyLock = LazyLock::new(TaskLabel::new); @@ -3377,6 +3371,36 @@ impl BufferSnapshot { result } + /// Returns the root syntax node within the given row + pub fn syntax_root_ancestor(&self, position: Anchor) -> Option { + let start_offset = position.to_offset(self); + + let row = self.summary_for_anchor::(&position).row as usize; + + let layer = self + .syntax + .layers_for_range(start_offset..start_offset, &self.text, true) + .next()?; + + let mut cursor = layer.node().walk(); + + // Descend to the first leaf that touches the start of the range. + while cursor.goto_first_child_for_byte(start_offset).is_some() { + if cursor.node().end_byte() == start_offset { + cursor.goto_next_sibling(); + } + } + + // Ascend to the root node within the same row. + while cursor.goto_parent() { + if cursor.node().start_position().row != row { + break; + } + } + + return Some(cursor.node()); + } + /// Returns the outline for the buffer. /// /// This method allows passing an optional [`SyntaxTheme`] to @@ -3938,79 +3962,6 @@ impl BufferSnapshot { }) } - pub fn debug_variable_ranges( - &self, - offset_range: Range, - ) -> impl Iterator + '_ { - let mut syntax_matches = self.syntax.matches(offset_range, self, |grammar| { - grammar - .debug_variables_config - .as_ref() - .map(|config| &config.query) - }); - - let configs = syntax_matches - .grammars() - .iter() - .map(|grammar| grammar.debug_variables_config.as_ref()) - .collect::>(); - - iter::from_fn(move || { - loop { - let mat = syntax_matches.peek()?; - - let variable_ranges = configs[mat.grammar_index].and_then(|config| { - let full_range = mat.captures.iter().fold( - Range { - start: usize::MAX, - end: 0, - }, - |mut acc, next| { - let byte_range = next.node.byte_range(); - if acc.start > byte_range.start { - acc.start = byte_range.start; - } - if acc.end < byte_range.end { - acc.end = byte_range.end; - } - acc - }, - ); - if full_range.start > full_range.end { - // We did not find a full spanning range of this match. - return None; - } - - let captures = mat.captures.iter().filter_map(|capture| { - Some(( - capture, - config.captures.get(capture.index as usize).cloned()?, - )) - }); - - let mut variable_range = None; - for (query, capture) in captures { - if let DebugVariableCapture::Variable = capture { - let _ = variable_range.insert(query.node.byte_range()); - } - } - - Some(DebugVariableRanges { - buffer_id: self.remote_id(), - range: variable_range?, - }) - }); - - syntax_matches.advance(); - if variable_ranges.is_some() { - // It's fine for us to short-circuit on .peek()? returning None. We don't want to return None from this iter if we - // had a capture that did not contain a run marker, hence we'll just loop around for the next capture. - return variable_ranges; - } - } - }) - } - pub fn runnable_ranges( &self, offset_range: Range, diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index b7252b13cf98c7bc2838abc61605ae98bac7e457..93abc6f49dab0f023a92dbcd5517e03f9935cdfd 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -1044,7 +1044,6 @@ pub struct Grammar { pub(crate) brackets_config: Option, pub(crate) redactions_config: Option, pub(crate) runnable_config: Option, - pub(crate) debug_variables_config: Option, pub(crate) indents_config: Option, pub outline_config: Option, pub text_object_config: Option, @@ -1145,18 +1144,6 @@ struct RunnableConfig { pub extra_captures: Vec, } -#[derive(Clone, Debug, PartialEq)] -enum DebugVariableCapture { - Named(SharedString), - Variable, -} - -#[derive(Debug)] -struct DebugVariablesConfig { - pub query: Query, - pub captures: Vec, -} - struct OverrideConfig { query: Query, values: HashMap, @@ -1217,7 +1204,6 @@ impl Language { override_config: None, redactions_config: None, runnable_config: None, - debug_variables_config: None, error_query: Query::new(&ts_language, "(ERROR) @error").ok(), ts_language, highlight_map: Default::default(), @@ -1289,11 +1275,6 @@ impl Language { .with_text_object_query(query.as_ref()) .context("Error loading textobject query")?; } - if let Some(query) = queries.debug_variables { - self = self - .with_debug_variables_query(query.as_ref()) - .context("Error loading debug variable query")?; - } Ok(self) } @@ -1389,25 +1370,6 @@ impl Language { Ok(self) } - pub fn with_debug_variables_query(mut self, source: &str) -> Result { - let grammar = self - .grammar_mut() - .ok_or_else(|| anyhow!("cannot mutate grammar"))?; - let query = Query::new(&grammar.ts_language, source)?; - - let mut captures = Vec::new(); - for name in query.capture_names() { - captures.push(if *name == "debug_variable" { - DebugVariableCapture::Variable - } else { - DebugVariableCapture::Named(name.to_string().into()) - }); - } - grammar.debug_variables_config = Some(DebugVariablesConfig { query, captures }); - - Ok(self) - } - pub fn with_embedding_query(mut self, source: &str) -> Result { let grammar = self .grammar_mut() diff --git a/crates/languages/src/python/debug_variables.scm b/crates/languages/src/python/debug_variables.scm deleted file mode 100644 index a434e07bf0a8eaa71b2689354976023335c530b2..0000000000000000000000000000000000000000 --- a/crates/languages/src/python/debug_variables.scm +++ /dev/null @@ -1,5 +0,0 @@ -(assignment - left: (identifier) @debug_variable) - -(function_definition - parameters: (parameters (identifier) @debug_variable)) diff --git a/crates/languages/src/rust/debug_variables.scm b/crates/languages/src/rust/debug_variables.scm deleted file mode 100644 index 9ef51035efba9d9b8e4958ae75e63b415229f4c1..0000000000000000000000000000000000000000 --- a/crates/languages/src/rust/debug_variables.scm +++ /dev/null @@ -1,3 +0,0 @@ -(let_declaration pattern: (identifier) @debug_variable) - -(parameter (identifier) @debug_variable) diff --git a/crates/project/src/debugger/dap_store.rs b/crates/project/src/debugger/dap_store.rs index 965e0c6e7e14d25afc01c0ecfc10aa3cd441d302..cc9c185e35ff908aacb136d5af6455643cbb4dfa 100644 --- a/crates/project/src/debugger/dap_store.rs +++ b/crates/project/src/debugger/dap_store.rs @@ -18,6 +18,7 @@ use dap::{ EvaluateArguments, EvaluateArgumentsContext, EvaluateResponse, Source, StackFrameId, adapters::{DebugAdapterBinary, DebugAdapterName, DebugTaskDefinition, TcpArguments}, client::SessionId, + inline_value::VariableLookupKind, messages::Message, requests::{Completions, Evaluate}, }; @@ -29,7 +30,7 @@ use futures::{ }; use gpui::{App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task}; use http_client::HttpClient; -use language::{Buffer, LanguageToolchainStore, language_settings::InlayHintKind, range_from_lsp}; +use language::{Buffer, LanguageToolchainStore, language_settings::InlayHintKind}; use node_runtime::NodeRuntime; use remote::SshRemoteClient; @@ -564,56 +565,37 @@ impl DapStore { }) } - pub fn resolve_inline_values( + pub fn resolve_inline_value_locations( &self, session: Entity, stack_frame_id: StackFrameId, buffer_handle: Entity, - inline_values: Vec, + inline_value_locations: Vec, 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); cx.spawn(async move |_, cx| { - let mut inlay_hints = Vec::with_capacity(inline_values.len()); - for inline_value in inline_values.iter() { - match inline_value { - lsp::InlineValue::Text(text) => { - inlay_hints.push(InlayHint { - position: snapshot.anchor_after(range_from_lsp(text.range).end), - label: InlayHintLabel::String(format!(": {}", text.text)), - kind: Some(InlayHintKind::Type), - padding_left: false, - padding_right: false, - tooltip: None, - resolve_state: ResolveState::Resolved, - }); - } - lsp::InlineValue::VariableLookup(variable_lookup) => { - let range = range_from_lsp(variable_lookup.range); - - let mut variable_name = variable_lookup - .variable_name - .clone() - .unwrap_or_else(|| snapshot.text_for_range(range.clone()).collect()); - - if !variable_lookup.case_sensitive_lookup { - variable_name = variable_name.to_ascii_lowercase(); - } - - let Some(variable) = all_variables.iter().find(|variable| { - if variable_lookup.case_sensitive_lookup { - variable.name == variable_name - } else { - variable.name.to_ascii_lowercase() == variable_name - } - }) else { + let mut inlay_hints = Vec::with_capacity(inline_value_locations.len()); + for inline_value_location in inline_value_locations.iter() { + let point = snapshot.point_to_point_utf16(language::Point::new( + inline_value_location.row as u32, + inline_value_location.column as u32, + )); + let position = snapshot.anchor_after(point); + + match inline_value_location.lookup { + VariableLookupKind::Variable => { + let Some(variable) = all_variables + .iter() + .find(|variable| variable.name == inline_value_location.variable_name) + else { continue; }; inlay_hints.push(InlayHint { - position: snapshot.anchor_after(range.end), + position, label: InlayHintLabel::String(format!(": {}", variable.value)), kind: Some(InlayHintKind::Type), padding_left: false, @@ -622,17 +604,10 @@ impl DapStore { resolve_state: ResolveState::Resolved, }); } - lsp::InlineValue::EvaluatableExpression(expression) => { - let range = range_from_lsp(expression.range); - - let expression = expression - .expression - .clone() - .unwrap_or_else(|| snapshot.text_for_range(range.clone()).collect()); - + VariableLookupKind::Expression => { let Ok(eval_task) = session.update(cx, |session, _| { session.mode.request_dap(EvaluateCommand { - expression, + expression: inline_value_location.variable_name.clone(), frame_id: Some(stack_frame_id), source: None, context: Some(EvaluateArgumentsContext::Variables), @@ -643,7 +618,7 @@ impl DapStore { if let Some(response) = eval_task.await.log_err() { inlay_hints.push(InlayHint { - position: snapshot.anchor_after(range.end), + position, label: InlayHintLabel::String(format!(": {}", response.result)), kind: Some(InlayHintKind::Type), padding_left: false, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 38bfb084c28b1d4f983dfd1c8a5a153f7b56babd..9a5be5e09a91cf44e4ae0ec864ef683aad1b12b3 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3562,52 +3562,43 @@ impl Project { range: Range, cx: &mut Context, ) -> Task>> { - let snapshot = buffer_handle.read(cx).snapshot(); + let language_name = buffer_handle + .read(cx) + .language() + .map(|language| language.name().to_string()); - let adapter = session.read(cx).adapter(); - let Some(inline_value_provider) = DapRegistry::global(cx) - .adapter(&adapter) - .and_then(|adapter| adapter.inline_value_provider()) + 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 mut text_objects = - snapshot.text_object_ranges(range.end..range.end, Default::default()); - let text_object_range = text_objects - .find(|(_, obj)| matches!(obj, language::TextObject::AroundFunction)) - .map(|(range, _)| snapshot.anchor_before(range.start)) - .unwrap_or(range.start); + let snapshot = buffer_handle.read(cx).snapshot(); - let variable_ranges = snapshot - .debug_variable_ranges( - text_object_range.to_offset(&snapshot)..range.end.to_offset(&snapshot), - ) - .filter_map(|range| { - let lsp_range = language::range_to_lsp( - range.range.start.to_point_utf16(&snapshot) - ..range.range.end.to_point_utf16(&snapshot), - ) - .ok()?; + let root_node = snapshot.syntax_root_ancestor(range.end).unwrap(); - Some(( - snapshot.text_for_range(range.range).collect::(), - lsp_range, - )) - }) - .collect::>(); + let row = snapshot + .summary_for_anchor::(&range.end) + .row as usize; - let inline_values = inline_value_provider.provide(variable_ranges); + let inline_value_locations = inline_value_provider.provide( + root_node, + snapshot + .text_for_range(Anchor::MIN..range.end) + .collect::() + .as_str(), + row, + ); let stack_frame_id = active_stack_frame.stack_frame_id; cx.spawn(async move |this, cx| { this.update(cx, |project, cx| { project.dap_store().update(cx, |dap_store, cx| { - dap_store.resolve_inline_values( + dap_store.resolve_inline_value_locations( session, stack_frame_id, buffer_handle, - inline_values, + inline_value_locations, cx, ) })