task_context.rs

  1use std::{ops::Range, path::Path};
  2
  3use crate::{Location, Runnable};
  4
  5use anyhow::Result;
  6use collections::HashMap;
  7use gpui::AppContext;
  8use task::{TaskTemplates, TaskVariables, VariableName};
  9use text::{BufferId, Point, ToPoint};
 10
 11pub struct RunnableRange {
 12    pub buffer_id: BufferId,
 13    pub run_range: Range<usize>,
 14    pub runnable: Runnable,
 15    pub extra_captures: HashMap<String, String>,
 16}
 17/// Language Contexts are used by Zed tasks to extract information about the source file where the tasks are supposed to be scheduled from.
 18/// Multiple context providers may be used together: by default, Zed provides a base [`BasicContextProvider`] context that fills all non-custom [`VariableName`] variants.
 19///
 20/// The context will be used to fill data for the tasks, and filter out the ones that do not have the variables required.
 21pub trait ContextProvider: Send + Sync {
 22    /// Builds a specific context to be placed on top of the basic one (replacing all conflicting entries) and to be used for task resolving later.
 23    fn build_context(
 24        &self,
 25        _worktree_abs_path: Option<&Path>,
 26        _location: &Location,
 27        _cx: &mut AppContext,
 28    ) -> Result<TaskVariables> {
 29        Ok(TaskVariables::default())
 30    }
 31
 32    /// Provides all tasks, associated with the current language.
 33    fn associated_tasks(&self) -> Option<TaskTemplates> {
 34        None
 35    }
 36
 37    // Determines whether the [`BasicContextProvider`] variables should be filled too (if `false`), or omitted (if `true`).
 38    fn is_basic(&self) -> bool {
 39        false
 40    }
 41}
 42
 43/// A context provided that tries to provide values for all non-custom [`VariableName`] variants for a currently opened file.
 44/// Applied as a base for every custom [`ContextProvider`] unless explicitly oped out.
 45pub struct BasicContextProvider;
 46
 47impl ContextProvider for BasicContextProvider {
 48    fn is_basic(&self) -> bool {
 49        true
 50    }
 51
 52    fn build_context(
 53        &self,
 54        worktree_abs_path: Option<&Path>,
 55        location: &Location,
 56        cx: &mut AppContext,
 57    ) -> Result<TaskVariables> {
 58        let buffer = location.buffer.read(cx);
 59        let buffer_snapshot = buffer.snapshot();
 60        let symbols = buffer_snapshot.symbols_containing(location.range.start, None);
 61        let symbol = symbols.unwrap_or_default().last().map(|symbol| {
 62            let range = symbol
 63                .name_ranges
 64                .last()
 65                .cloned()
 66                .unwrap_or(0..symbol.text.len());
 67            symbol.text[range].to_string()
 68        });
 69
 70        let current_file = buffer
 71            .file()
 72            .and_then(|file| file.as_local())
 73            .map(|file| file.abs_path(cx).to_string_lossy().to_string());
 74        let Point { row, column } = location.range.start.to_point(&buffer_snapshot);
 75        let row = row + 1;
 76        let column = column + 1;
 77        let selected_text = buffer
 78            .chars_for_range(location.range.clone())
 79            .collect::<String>();
 80
 81        let mut task_variables = TaskVariables::from_iter([
 82            (VariableName::Row, row.to_string()),
 83            (VariableName::Column, column.to_string()),
 84        ]);
 85
 86        if let Some(symbol) = symbol {
 87            task_variables.insert(VariableName::Symbol, symbol);
 88        }
 89        if !selected_text.trim().is_empty() {
 90            task_variables.insert(VariableName::SelectedText, selected_text);
 91        }
 92        if let Some(path) = current_file {
 93            task_variables.insert(VariableName::File, path);
 94        }
 95        if let Some(worktree_path) = worktree_abs_path {
 96            task_variables.insert(
 97                VariableName::WorktreeRoot,
 98                worktree_path.to_string_lossy().to_string(),
 99            );
100        }
101
102        Ok(task_variables)
103    }
104}
105
106/// A ContextProvider that doesn't provide any task variables on it's own, though it has some associated tasks.
107pub struct ContextProviderWithTasks {
108    templates: TaskTemplates,
109}
110
111impl ContextProviderWithTasks {
112    pub fn new(definitions: TaskTemplates) -> Self {
113        Self {
114            templates: definitions,
115        }
116    }
117}
118
119impl ContextProvider for ContextProviderWithTasks {
120    fn associated_tasks(&self) -> Option<TaskTemplates> {
121        Some(self.templates.clone())
122    }
123
124    fn build_context(
125        &self,
126        worktree_abs_path: Option<&Path>,
127        location: &Location,
128        cx: &mut AppContext,
129    ) -> Result<TaskVariables> {
130        BasicContextProvider.build_context(worktree_abs_path, location, cx)
131    }
132}