Add support for detecting tests in source files, and implement it for Rust (#11195)
Continuing work from #10873 Release Notes: - N/A --------- Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
parent
14c7782ce6
commit
5a71d8c7f1
29 changed files with 1148 additions and 606 deletions
|
@ -13,7 +13,7 @@ use crate::{
|
|||
SyntaxLayer, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches,
|
||||
SyntaxSnapshot, ToTreeSitterPoint,
|
||||
},
|
||||
LanguageScope, Outline,
|
||||
LanguageScope, Outline, RunnableTag,
|
||||
};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
pub use clock::ReplicaId;
|
||||
|
@ -501,6 +501,13 @@ pub enum CharKind {
|
|||
Word,
|
||||
}
|
||||
|
||||
/// A runnable is a set of data about a region that could be resolved into a task
|
||||
pub struct Runnable {
|
||||
pub tags: SmallVec<[RunnableTag; 1]>,
|
||||
pub language: Arc<Language>,
|
||||
pub buffer: BufferId,
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
/// Create a new buffer with the given base text.
|
||||
pub fn local<T: Into<String>>(base_text: T, cx: &mut ModelContext<Self>) -> Self {
|
||||
|
@ -2978,6 +2985,53 @@ impl BufferSnapshot {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn runnable_ranges(
|
||||
&self,
|
||||
range: Range<Anchor>,
|
||||
) -> impl Iterator<Item = (Range<usize>, Runnable)> + '_ {
|
||||
let offset_range = range.start.to_offset(self)..range.end.to_offset(self);
|
||||
|
||||
let mut syntax_matches = self.syntax.matches(offset_range, self, |grammar| {
|
||||
grammar.runnable_config.as_ref().map(|config| &config.query)
|
||||
});
|
||||
|
||||
let test_configs = syntax_matches
|
||||
.grammars()
|
||||
.iter()
|
||||
.map(|grammar| grammar.runnable_config.as_ref())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
iter::from_fn(move || {
|
||||
let test_range = syntax_matches
|
||||
.peek()
|
||||
.and_then(|mat| {
|
||||
test_configs[mat.grammar_index].and_then(|test_configs| {
|
||||
let tags = SmallVec::from_iter(mat.captures.iter().filter_map(|capture| {
|
||||
test_configs.runnable_tags.get(&capture.index).cloned()
|
||||
}));
|
||||
|
||||
if tags.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((
|
||||
mat.captures
|
||||
.iter()
|
||||
.find(|capture| capture.index == test_configs.run_capture_ix)?,
|
||||
Runnable {
|
||||
tags,
|
||||
language: mat.language,
|
||||
buffer: self.remote_id(),
|
||||
},
|
||||
))
|
||||
})
|
||||
})
|
||||
.map(|(mat, test_tags)| (mat.node.byte_range(), test_tags));
|
||||
syntax_matches.advance();
|
||||
test_range
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns selections for remote peers intersecting the given range.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn remote_selections_in_range(
|
||||
|
|
|
@ -56,6 +56,7 @@ use std::{
|
|||
},
|
||||
};
|
||||
use syntax_map::{QueryCursorHandle, SyntaxSnapshot};
|
||||
use task::RunnableTag;
|
||||
pub use task_context::{BasicContextProvider, ContextProvider, ContextProviderWithTasks};
|
||||
use theme::SyntaxTheme;
|
||||
use tree_sitter::{self, wasmtime, Query, QueryCursor, WasmStore};
|
||||
|
@ -836,6 +837,7 @@ pub struct Grammar {
|
|||
pub(crate) highlights_query: Option<Query>,
|
||||
pub(crate) brackets_config: Option<BracketConfig>,
|
||||
pub(crate) redactions_config: Option<RedactionConfig>,
|
||||
pub(crate) runnable_config: Option<RunnableConfig>,
|
||||
pub(crate) indents_config: Option<IndentConfig>,
|
||||
pub outline_config: Option<OutlineConfig>,
|
||||
pub embedding_config: Option<EmbeddingConfig>,
|
||||
|
@ -882,6 +884,14 @@ struct RedactionConfig {
|
|||
pub redaction_capture_ix: u32,
|
||||
}
|
||||
|
||||
struct RunnableConfig {
|
||||
pub query: Query,
|
||||
/// A mapping from captures indices to known test tags
|
||||
pub runnable_tags: HashMap<u32, RunnableTag>,
|
||||
/// index of the capture that corresponds to @run
|
||||
pub run_capture_ix: u32,
|
||||
}
|
||||
|
||||
struct OverrideConfig {
|
||||
query: Query,
|
||||
values: HashMap<u32, (String, LanguageConfigOverride)>,
|
||||
|
@ -923,6 +933,7 @@ impl Language {
|
|||
injection_config: None,
|
||||
override_config: None,
|
||||
redactions_config: None,
|
||||
runnable_config: None,
|
||||
error_query: Query::new(&ts_language, "(ERROR) @error").unwrap(),
|
||||
ts_language,
|
||||
highlight_map: Default::default(),
|
||||
|
@ -978,6 +989,11 @@ impl Language {
|
|||
.with_redaction_query(query.as_ref())
|
||||
.context("Error loading redaction query")?;
|
||||
}
|
||||
if let Some(query) = queries.runnables {
|
||||
self = self
|
||||
.with_runnable_query(query.as_ref())
|
||||
.context("Error loading tests query")?;
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
|
@ -989,6 +1005,33 @@ impl Language {
|
|||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn with_runnable_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 run_capture_index = None;
|
||||
let mut runnable_tags = HashMap::default();
|
||||
for (ix, name) in query.capture_names().iter().enumerate() {
|
||||
if *name == "run" {
|
||||
run_capture_index = Some(ix as u32);
|
||||
} else if !name.starts_with('_') {
|
||||
runnable_tags.insert(ix as u32, RunnableTag(name.to_string().into()));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(run_capture_ix) = run_capture_index {
|
||||
grammar.runnable_config = Some(RunnableConfig {
|
||||
query,
|
||||
run_capture_ix,
|
||||
runnable_tags,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn with_outline_query(mut self, source: &str) -> Result<Self> {
|
||||
let grammar = self
|
||||
.grammar_mut()
|
||||
|
|
|
@ -124,6 +124,7 @@ pub const QUERY_FILENAME_PREFIXES: &[(
|
|||
("injections", |q| &mut q.injections),
|
||||
("overrides", |q| &mut q.overrides),
|
||||
("redactions", |q| &mut q.redactions),
|
||||
("runnables", |q| &mut q.runnables),
|
||||
];
|
||||
|
||||
/// Tree-sitter language queries for a given language.
|
||||
|
@ -137,6 +138,7 @@ pub struct LanguageQueries {
|
|||
pub injections: Option<Cow<'static, str>>,
|
||||
pub overrides: Option<Cow<'static, str>>,
|
||||
pub redactions: Option<Cow<'static, str>>,
|
||||
pub runnables: Option<Cow<'static, str>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
|
|
|
@ -56,6 +56,7 @@ pub struct SyntaxMapCapture<'a> {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct SyntaxMapMatch<'a> {
|
||||
pub language: Arc<Language>,
|
||||
pub depth: usize,
|
||||
pub pattern_index: usize,
|
||||
pub captures: &'a [QueryCapture<'a>],
|
||||
|
@ -71,6 +72,7 @@ struct SyntaxMapCapturesLayer<'a> {
|
|||
}
|
||||
|
||||
struct SyntaxMapMatchesLayer<'a> {
|
||||
language: Arc<Language>,
|
||||
depth: usize,
|
||||
next_pattern_index: usize,
|
||||
next_captures: Vec<QueryCapture<'a>>,
|
||||
|
@ -1016,6 +1018,7 @@ impl<'a> SyntaxMapMatches<'a> {
|
|||
result.grammars.len() - 1
|
||||
});
|
||||
let mut layer = SyntaxMapMatchesLayer {
|
||||
language: layer.language.clone(),
|
||||
depth: layer.depth,
|
||||
grammar_index,
|
||||
matches,
|
||||
|
@ -1048,10 +1051,13 @@ impl<'a> SyntaxMapMatches<'a> {
|
|||
|
||||
pub fn peek(&self) -> Option<SyntaxMapMatch> {
|
||||
let layer = self.layers.first()?;
|
||||
|
||||
if !layer.has_next {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(SyntaxMapMatch {
|
||||
language: layer.language.clone(),
|
||||
depth: layer.depth,
|
||||
grammar_index: layer.grammar_index,
|
||||
pattern_index: layer.next_pattern_index,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue