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:
Piotr Osiewicz 2024-05-05 16:32:48 +02:00 committed by GitHub
parent 14c7782ce6
commit 5a71d8c7f1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 1148 additions and 606 deletions

View file

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

View file

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

View file

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

View file

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