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 \<djsmits12@gmail.com\>

Release Notes:

- N/A

---------

Co-authored-by: Remco Smits <djsmits12@gmail.com>
This commit is contained in:
Anthony Eid 2025-05-07 14:39:35 +02:00 committed by GitHub
parent 7bc3f74cab
commit 1a520990cc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 2273 additions and 281 deletions

View file

@ -3562,52 +3562,43 @@ impl Project {
range: Range<text::Anchor>,
cx: &mut Context<Self>,
) -> Task<anyhow::Result<Vec<InlayHint>>> {
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::<String>(),
lsp_range,
))
})
.collect::<Vec<_>>();
let row = snapshot
.summary_for_anchor::<text::PointUtf16>(&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::<String>()
.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,
)
})