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
|
@ -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