757 lines · 24.2 KB · Rust
RawDownload
use std::{cmp::Reverse, iter, ops::Range, sync::Arc}; use collections::HashMap;use smallvec::SmallVec;use text::BufferId;use tree_sitter::QueryCapture;use util::RangeExt; use crate::{BufferSnapshot, Language, Runnable, RunnableCapture, RunnableConfig, RunnableTag}; pub struct RunnableRange {    pub buffer_id: BufferId,    pub run_range: Range<usize>,    pub full_range: Range<usize>,    pub runnable: Runnable,    pub extra_captures: HashMap<String, String>,} #[derive(Clone, Debug)]pub struct RunnableMatchCapture {    range: Range<usize>,    capture: ResolverCapture,} /// The subset of `RunnableCapture` that's meaningful to resolvers.#[derive(Clone, Debug)]enum ResolverCapture {    Run,    Named(String),} impl RunnableMatchCapture {    pub fn range(&self) -> Range<usize> {        self.range.clone()    }     pub fn is_run(&self) -> bool {        matches!(self.capture, ResolverCapture::Run)    }     pub fn name(&self) -> Option<&str> {        match &self.capture {            ResolverCapture::Named(name) => Some(name.as_str()),            ResolverCapture::Run => None,        }    }} pub struct ResolvedRunnable {    pub run_range: Range<usize>,    pub extra_captures: SmallVec<[(String, String); 2]>,} pub trait RunnableResolver: Send + Sync {    fn resolve(        &self,        local_captures: &[RunnableMatchCapture],        shared_captures: &[RunnableMatchCapture],        buffer: &BufferSnapshot,    ) -> Option<ResolvedRunnable>;} pub(crate) fn runnable_ranges(    buffer: &BufferSnapshot,    offset_range: Range<usize>,) -> impl Iterator<Item = RunnableRange> + '_ {    let mut syntax_matches = buffer.matches(offset_range.clone(), |grammar| {        grammar.runnable_config.as_ref().map(|config| &config.query)    });     let runnable_configs = syntax_matches        .grammars()        .iter()        .map(|grammar| grammar.runnable_config.as_ref())        .collect::<Vec<_>>();     iter::from_fn(move || -> Option<SmallVec<[RunnableRange; 1]>> {        let mat = syntax_matches.peek()?;         let ranges = match runnable_configs[mat.grammar_index] {            Some(runnable_config) => {                let is_grouped = runnable_config.supports_grouped_runnables                    && mat.captures.iter().any(|capture| {                        matches!(                            runnable_config.extra_captures.get(capture.index as usize),                            Some(RunnableCapture::RunItem)                        )                    });                if is_grouped {                    runnable_ranges_from_grouped_matches(                        buffer,                        mat.captures,                        runnable_config,                        mat.pattern_index,                        mat.language.clone(),                        offset_range.clone(),                    )                } else {                    runnable_range_from_captures(                        buffer,                        mat.captures,                        runnable_config,                        mat.pattern_index,                        mat.language,                    )                    .into_iter()                    .collect()                }            }            None => SmallVec::new(),        };         syntax_matches.advance();        Some(ranges)    })    .flatten()} type RunnableMatchCaptures = SmallVec<[RunnableMatchCapture; 4]>; struct RunnableMatchGroup {    range: Range<usize>,    captures: RunnableMatchCaptures,} struct GroupedRunnableMatches {    groups: SmallVec<[RunnableMatchGroup; 1]>,    shared_captures: RunnableMatchCaptures,} fn runnable_tags_from_pattern(    query: &tree_sitter::Query,    pattern_index: usize,) -> SmallVec<[RunnableTag; 1]> {    query        .property_settings(pattern_index)        .iter()        .filter_map(|property| {            (*property.key == *"tag")                .then(|| {                    property                        .value                        .as_ref()                        .map(|value| RunnableTag(value.to_string().into()))                })                .flatten()        })        .collect()} /// `overlaps` rejects empty ranges, so handle a zero-width `offset_range` (cursor) separately.fn range_overlaps_or_contains(range: &Range<usize>, offset_range: &Range<usize>) -> bool {    if offset_range.is_empty() {        range.contains(&offset_range.start)    } else {        range.overlaps(offset_range)    }} fn group_runnable_matches(    captures: &[QueryCapture<'_>],    runnable_config: &RunnableConfig,    offset_range: Range<usize>,) -> GroupedRunnableMatches {    let mut sorted: SmallVec<[&QueryCapture<'_>; 16]> = captures.iter().collect();    sorted.sort_by_key(|capture| {        let range = capture.node.byte_range();        (range.start, Reverse(range.end))    });     let mut groups = SmallVec::new();    let mut shared_captures = SmallVec::new();    let mut current_group: Option<RunnableMatchGroup> = None;    let mut current_in_offset = false;     for capture in sorted {        let range = capture.node.byte_range();        let Some(kind) = runnable_config.extra_captures.get(capture.index as usize) else {            continue;        };         let resolver_capture = match kind {            RunnableCapture::RunItem => {                if let Some(group) = current_group.take()                    && current_in_offset                {                    groups.push(group);                }                current_in_offset = range_overlaps_or_contains(&range, &offset_range);                current_group = Some(RunnableMatchGroup {                    range,                    captures: SmallVec::new(),                });                continue;            }            RunnableCapture::Run => ResolverCapture::Run,            RunnableCapture::Named(name) => ResolverCapture::Named(name.to_string()),        };         let match_capture = RunnableMatchCapture {            range: range.clone(),            capture: resolver_capture,        };         match current_group.as_mut() {            Some(group) if group.range.contains_inclusive(&range) => {                if current_in_offset {                    group.captures.push(match_capture);                }            }            _ => {                if let Some(group) = current_group.take()                    && current_in_offset                {                    groups.push(group);                }                shared_captures.push(match_capture);            }        }    }    if let Some(group) = current_group.take()        && current_in_offset    {        groups.push(group);    }     GroupedRunnableMatches {        groups,        shared_captures,    }} fn runnable_ranges_from_grouped_matches(    buffer: &BufferSnapshot,    captures: &[QueryCapture<'_>],    runnable_config: &RunnableConfig,    pattern_index: usize,    language: Arc<Language>,    offset_range: Range<usize>,) -> SmallVec<[RunnableRange; 1]> {    let GroupedRunnableMatches {        groups,        shared_captures,    } = group_runnable_matches(captures, runnable_config, offset_range);     let shared_extras: SmallVec<[(String, String); 4]> = shared_captures        .iter()        .filter_map(|capture| {            capture.name().map(|name| {                (                    name.to_string(),                    buffer.text_for_range(capture.range()).collect::<String>(),                )            })        })        .collect();     let Some(resolver) = language        .context_provider()        .and_then(|provider| provider.runnable_resolver())    else {        return SmallVec::new();    };    let mut runnable_ranges = SmallVec::with_capacity(groups.len());     let tags = runnable_tags_from_pattern(&runnable_config.query, pattern_index);    let buffer_id = buffer.remote_id();    for group in groups {        let Some(ResolvedRunnable {            run_range,            extra_captures: local_extras,        }) = resolver.resolve(&group.captures, &shared_captures, buffer)        else {            continue;        };         let extra_captures = shared_extras.iter().cloned().chain(local_extras).collect();         runnable_ranges.push(RunnableRange {            run_range,            full_range: group.range,            runnable: Runnable {                tags: tags.clone(),                language: language.clone(),                buffer: buffer_id,            },            extra_captures,            buffer_id,        });    }     runnable_ranges} fn runnable_range_from_captures(    buffer: &BufferSnapshot,    captures: &[QueryCapture<'_>],    runnable_config: &RunnableConfig,    pattern_index: usize,    language: Arc<Language>,) -> Option<RunnableRange> {    let mut run_range = None;    let first_capture = captures.first()?;    let full_range =        captures            .iter()            .skip(1)            .fold(first_capture.node.byte_range(), |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            });     let extra_captures: SmallVec<[_; 1]> =        SmallVec::from_iter(captures.iter().filter_map(|capture| {            runnable_config                .extra_captures                .get(capture.index as usize)                .cloned()                .and_then(|tag_name| match tag_name {                    RunnableCapture::Named(name) => Some((capture.node.byte_range(), name)),                    RunnableCapture::Run => {                        let _ = run_range.insert(capture.node.byte_range());                        None                    }                    RunnableCapture::RunItem => None,                })        }));    let run_range = run_range?;    let tags = runnable_tags_from_pattern(&runnable_config.query, pattern_index);    let extra_captures = extra_captures        .into_iter()        .map(|(range, name)| {            (                name.to_string(),                buffer.text_for_range(range).collect::<String>(),            )        })        .collect();     // A runnable has one range, even when its query pattern contributes multiple tags.    let buffer_id = buffer.remote_id();    Some(RunnableRange {        run_range,        full_range,        runnable: Runnable {            tags,            language,            buffer: buffer_id,        },        extra_captures,        buffer_id,    })} #[cfg(test)]mod tests {    use super::*;    use crate::{        Buffer, ContextProvider, Language, LanguageConfig, LanguageMatcher, LanguageQueries,    };    use gpui::{AppContext as _, TestAppContext};    use indoc::indoc;    use std::{borrow::Cow, sync::Arc};     struct TestContextProvider {        resolver: Arc<dyn RunnableResolver>,    }     impl ContextProvider for TestContextProvider {        fn runnable_resolver(&self) -> Option<Arc<dyn RunnableResolver>> {            Some(self.resolver.clone())        }    }     fn make_language(        runnables_query: &'static str,        resolver: Option<Arc<dyn RunnableResolver>>,    ) -> Arc<Language> {        let language = Language::new(            LanguageConfig {                name: "Rust".into(),                matcher: (LanguageMatcher {                    path_suffixes: vec!["rs".to_string()],                    ..Default::default()                })                .into(),                ..Default::default()            },            Some(tree_sitter_rust::LANGUAGE.into()),        )        .with_queries(LanguageQueries {            runnables: Some(Cow::Borrowed(runnables_query)),            ..Default::default()        })        .expect("parse runnables query");        let context_provider = resolver            .map(|resolver| Arc::new(TestContextProvider { resolver }) as Arc<dyn ContextProvider>);        Arc::new(language.with_context_provider(context_provider))    }     fn collect_runnables(        cx: &mut TestAppContext,        source: &str,        runnables_query: &'static str,        resolver: Option<Arc<dyn RunnableResolver>>,    ) -> Vec<RunnableRange> {        collect_runnables_in(cx, source, runnables_query, resolver, None)    }     fn collect_runnables_in(        cx: &mut TestAppContext,        source: &str,        runnables_query: &'static str,        resolver: Option<Arc<dyn RunnableResolver>>,        offset_range: Option<Range<usize>>,    ) -> Vec<RunnableRange> {        let language = make_language(runnables_query, resolver);        let source_owned = source.to_string();        let buffer = cx            .new(|cx| Buffer::local(source_owned.clone(), cx).with_language(language.clone(), cx));        cx.executor().run_until_parked();        let range = offset_range.unwrap_or(0..source_owned.len());        buffer.update(cx, |buffer, _| {            buffer.snapshot().runnable_ranges(range).collect()        })    }     fn text_at(buffer: &BufferSnapshot, range: Range<usize>) -> String {        buffer.text_for_range(range).collect()    }     /// Picks the first `@run` capture, attaches no extras.    struct FirstRunResolver;     impl RunnableResolver for FirstRunResolver {        fn resolve(            &self,            local_captures: &[RunnableMatchCapture],            _shared_captures: &[RunnableMatchCapture],            _buffer: &BufferSnapshot,        ) -> Option<ResolvedRunnable> {            let run = local_captures.iter().find(|capture| capture.is_run())?;            Some(ResolvedRunnable {                run_range: run.range(),                extra_captures: SmallVec::new(),            })        }    }     /// Picks the first `@run` and surfaces every local named capture as an extra.    struct LocalExtrasResolver;     impl RunnableResolver for LocalExtrasResolver {        fn resolve(            &self,            local_captures: &[RunnableMatchCapture],            _shared_captures: &[RunnableMatchCapture],            buffer: &BufferSnapshot,        ) -> Option<ResolvedRunnable> {            let run = local_captures.iter().find(|capture| capture.is_run())?;            let extras = local_captures                .iter()                .filter_map(|capture| {                    capture                        .name()                        .map(|name| (name.to_string(), text_at(buffer, capture.range())))                })                .collect();            Some(ResolvedRunnable {                run_range: run.range(),                extra_captures: extras,            })        }    }     /// Skips groups whose `@run` text equals `skip_text`; otherwise picks the first `@run`.    struct SkipByTextResolver {        skip_text: &'static str,    }     impl RunnableResolver for SkipByTextResolver {        fn resolve(            &self,            local_captures: &[RunnableMatchCapture],            _shared_captures: &[RunnableMatchCapture],            buffer: &BufferSnapshot,        ) -> Option<ResolvedRunnable> {            let run = local_captures.iter().find(|capture| capture.is_run())?;            if text_at(buffer, run.range()) == self.skip_text {                return None;            }            Some(ResolvedRunnable {                run_range: run.range(),                extra_captures: SmallVec::new(),

Lines 1–500 of 757.