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:
parent
7bc3f74cab
commit
1a520990cc
21 changed files with 2273 additions and 281 deletions
|
@ -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<Session>,
|
||||
stack_frame_id: StackFrameId,
|
||||
buffer_handle: Entity<Buffer>,
|
||||
inline_values: Vec<lsp::InlineValue>,
|
||||
inline_value_locations: Vec<dap::inline_value::InlineValueLocation>,
|
||||
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);
|
||||
|
||||
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,
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue