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
|
@ -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<usize>,
|
||||
}
|
||||
|
||||
/// 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<TaskLabel> = 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<tree_sitter::Node> {
|
||||
let start_offset = position.to_offset(self);
|
||||
|
||||
let row = self.summary_for_anchor::<text::PointUtf16>(&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<usize>,
|
||||
) -> impl Iterator<Item = DebugVariableRanges> + '_ {
|
||||
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::<Vec<_>>();
|
||||
|
||||
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<usize>,
|
||||
|
|
|
@ -1044,7 +1044,6 @@ pub struct Grammar {
|
|||
pub(crate) brackets_config: Option<BracketsConfig>,
|
||||
pub(crate) redactions_config: Option<RedactionConfig>,
|
||||
pub(crate) runnable_config: Option<RunnableConfig>,
|
||||
pub(crate) debug_variables_config: Option<DebugVariablesConfig>,
|
||||
pub(crate) indents_config: Option<IndentConfig>,
|
||||
pub outline_config: Option<OutlineConfig>,
|
||||
pub text_object_config: Option<TextObjectConfig>,
|
||||
|
@ -1145,18 +1144,6 @@ struct RunnableConfig {
|
|||
pub extra_captures: Vec<RunnableCapture>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
enum DebugVariableCapture {
|
||||
Named(SharedString),
|
||||
Variable,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DebugVariablesConfig {
|
||||
pub query: Query,
|
||||
pub captures: Vec<DebugVariableCapture>,
|
||||
}
|
||||
|
||||
struct OverrideConfig {
|
||||
query: Query,
|
||||
values: HashMap<u32, OverrideEntry>,
|
||||
|
@ -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<Self> {
|
||||
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<Self> {
|
||||
let grammar = self
|
||||
.grammar_mut()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue