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 | 67 +- crates/project/src/project.rs | 51 +- 21 files changed, 2273 insertions(+), 281 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 4b6f74756c..140e81cfd5 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 c5e7fec0c1..531276e708 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 3cd8bb9fb7..485e5f1b4c 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 8e06396d6b..df8d915812 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 0000000000..7204d985aa --- /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 c631452b8c..a03be4860d 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 794db55feb..77dbe40088 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 8acfec1b5e..e0c585e466 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 93af93c6b9..4c1ed025ad 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 f1eb24c6e8..5c0d805ba4 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 a195b10801..295d18e4b3 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 c99ccc12ea..e8e9beef35 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 0000000000..76b9436080 --- /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 1c3318b089..16b259629e 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 cc5a3a2972..407573513d 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 b40d889d17..700157496e 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 b7252b13cf..93abc6f49d 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 a434e07bf0..0000000000 --- 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 9ef51035ef..0000000000 --- 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 965e0c6e7e..cc9c185e35 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 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); - 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 { + 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 38bfb084c2..9a5be5e09a 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, ) })