tasks: Expose captured variables to ContextProvider (#12134)

This PR changes the interface of ContextProvider, allowing it to inspect
*all* variables set so far during the process of building
`TaskVariables`. This makes it possible to capture e.g. an identifier in
tree-sitter query, process it and then export it as a task variable.

Notably, the list of variables includes captures prefixed with leading
underscore; they are removed after all calls to `build_context`, but it
makes it possible to capture something and then conditionally preserve
it (and perhaps modify it).

Release Notes:

- N/A
This commit is contained in:
Piotr Osiewicz 2024-05-22 19:45:43 +02:00 committed by GitHub
parent ba9449692e
commit 58796a8480
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 212 additions and 201 deletions

View file

@ -406,6 +406,7 @@ struct RunnableTasks {
templates: Vec<(TaskSourceKind, TaskTemplate)>,
// We need the column at which the task context evaluation should take place.
column: u32,
// Values of all named captures, including those starting with '_'
extra_variables: HashMap<String, String>,
}
@ -3973,7 +3974,7 @@ impl Editor {
this.completion_tasks.clear();
this.discard_inline_completion(false, cx);
let task_context = tasks.as_ref().zip(this.workspace.clone()).and_then(
let tasks = tasks.as_ref().zip(this.workspace.clone()).and_then(
|(tasks, (workspace, _))| {
let position = Point::new(buffer_row, tasks.1.column);
let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
@ -3981,43 +3982,45 @@ impl Editor {
buffer: buffer.clone(),
range: range_start..range_start,
};
// Fill in the environmental variables from the tree-sitter captures
let mut captured_task_variables = TaskVariables::default();
for (capture_name, value) in tasks.1.extra_variables.clone() {
captured_task_variables.insert(
task::VariableName::Custom(capture_name.into()),
value.clone(),
);
}
workspace
.update(cx, |workspace, cx| {
tasks::task_context_for_location(workspace, location, cx)
tasks::task_context_for_location(
captured_task_variables,
workspace,
location,
cx,
)
})
.ok()
.flatten()
.map(|task_context| {
Arc::new(ResolvedTasks {
templates: tasks
.1
.templates
.iter()
.filter_map(|(kind, template)| {
template
.resolve_task(&kind.to_id_base(), &task_context)
.map(|task| (kind.clone(), task))
})
.collect(),
position: snapshot.buffer_snapshot.anchor_before(
Point::new(multibuffer_point.row, tasks.1.column),
),
})
})
},
);
let tasks = tasks.zip(task_context).map(|(tasks, mut task_context)| {
// Fill in the environmental variables from the tree-sitter captures
let mut additional_task_variables = TaskVariables::default();
for (capture_name, value) in tasks.1.extra_variables.clone() {
additional_task_variables.insert(
task::VariableName::Custom(capture_name.into()),
value.clone(),
);
}
task_context
.task_variables
.extend(additional_task_variables);
Arc::new(ResolvedTasks {
templates: tasks
.1
.templates
.iter()
.filter_map(|(kind, template)| {
template
.resolve_task(&kind.to_id_base(), &task_context)
.map(|task| (kind.clone(), task))
})
.collect(),
position: snapshot
.buffer_snapshot
.anchor_before(Point::new(multibuffer_point.row, tasks.1.column)),
})
});
let spawn_straight_away = tasks
.as_ref()
.map_or(false, |tasks| tasks.templates.len() == 1)

View file

@ -1,17 +1,16 @@
use crate::Editor;
use std::{path::Path, sync::Arc};
use anyhow::Context;
use gpui::WindowContext;
use language::{BasicContextProvider, ContextProvider};
use project::{Location, WorktreeId};
use gpui::{Model, WindowContext};
use language::ContextProvider;
use project::{BasicContextProvider, Location, Project};
use task::{TaskContext, TaskVariables, VariableName};
use text::Point;
use util::ResultExt;
use workspace::Workspace;
pub(crate) fn task_context_for_location(
captured_variables: TaskVariables,
workspace: &Workspace,
location: Location,
cx: &mut WindowContext<'_>,
@ -20,31 +19,16 @@ pub(crate) fn task_context_for_location(
.log_err()
.flatten();
let buffer = location.buffer.clone();
let language_context_provider = buffer
.read(cx)
.language()
.and_then(|language| language.context_provider())
.unwrap_or_else(|| Arc::new(BasicContextProvider));
let worktree_abs_path = buffer
.read(cx)
.file()
.map(|file| WorktreeId::from_usize(file.worktree_id()))
.and_then(|worktree_id| {
workspace
.project()
.read(cx)
.worktree_for_id(worktree_id, cx)
.map(|worktree| worktree.read(cx).abs_path())
});
let task_variables = combine_task_variables(
worktree_abs_path.as_deref(),
let mut task_variables = combine_task_variables(
captured_variables,
location,
language_context_provider.as_ref(),
workspace.project().clone(),
cx,
)
.log_err()?;
// Remove all custom entries starting with _, as they're not intended for use by the end user.
task_variables.sweep();
Some(TaskContext {
cwd,
task_variables,
@ -84,21 +68,21 @@ fn task_context_with_editor(
buffer,
range: start..end,
};
task_context_for_location(workspace, location.clone(), cx).map(|mut task_context| {
let captured_variables = {
let mut variables = TaskVariables::default();
for range in location
.buffer
.read(cx)
.snapshot()
.runnable_ranges(location.range)
.runnable_ranges(location.range.clone())
{
for (capture_name, value) in range.extra_captures {
task_context
.task_variables
.insert(VariableName::Custom(capture_name.into()), value);
variables.insert(VariableName::Custom(capture_name.into()), value);
}
}
task_context
})
variables
};
task_context_for_location(captured_variables, workspace, location.clone(), cx)
}
pub fn task_context(workspace: &Workspace, cx: &mut WindowContext<'_>) -> TaskContext {
@ -114,24 +98,26 @@ pub fn task_context(workspace: &Workspace, cx: &mut WindowContext<'_>) -> TaskCo
}
fn combine_task_variables(
worktree_abs_path: Option<&Path>,
mut captured_variables: TaskVariables,
location: Location,
context_provider: &dyn ContextProvider,
project: Model<Project>,
cx: &mut WindowContext<'_>,
) -> anyhow::Result<TaskVariables> {
if context_provider.is_basic() {
context_provider
.build_context(worktree_abs_path, &location, cx)
.context("building basic provider context")
} else {
let mut basic_context = BasicContextProvider
.build_context(worktree_abs_path, &location, cx)
.context("building basic default context")?;
basic_context.extend(
context_provider
.build_context(worktree_abs_path, &location, cx)
let language_context_provider = location
.buffer
.read(cx)
.language()
.and_then(|language| language.context_provider());
let baseline = BasicContextProvider::new(project)
.build_context(&captured_variables, &location, cx)
.context("building basic default context")?;
captured_variables.extend(baseline);
if let Some(provider) = language_context_provider {
captured_variables.extend(
provider
.build_context(&captured_variables, &location, cx)
.context("building provider context ")?,
);
Ok(basic_context)
}
Ok(captured_variables)
}