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