task: Add task contexts (#8675)

This PR supplements tasks with additional environment variables; ideally
we'll be able to write a task like:
`cargo test -p $ZED_CURRENT_PACKAGE -- $ZED_CURRENT_FUNCTION`
- [x] Flesh out multibuffer interactions
- [x] Add ZED_SYMBOL detection based on tree-sitter queries
- [ ] Add release note and demo
- [x] Figure out a solution for rerun dilemma - should `task: rerun`
reevaluate contexts for tasks?

This PR introduced the following variables:
- ZED_COLUMN - current line column
- ZED_ROW - current line row
and the following, which are available for buffers with associated
files:
- ZED_WORKTREE_ROOT - absolute path to the root of the current worktree.
- ZED_FILE - absolute path to the file
- ZED_SYMBOL - currently selected symbol; should match the last symbol
shown in a symbol breadcrumb (e.g. `mod tests > fn test_task_contexts`
should be equal to ZED_SYMBOL of `test_task_contexts`). Note that this
isn't necessarily a test function or a function at all.

Also, you can use them in `cwd` field of definitions (note though that
we're using https://docs.rs/subst/latest/subst/#features for that, so
don't expect a full shell functionality to work); the syntax should
match up with your typical Unix shell.


Release Notes:

- Added task contexts, which are additional environment variables set by
Zed for task execution; task content is dependent on the state of the
editor at the time the task is spawned.

---------

Co-authored-by: Anthony <anthonyeid7@protonmail.com>
This commit is contained in:
Piotr Osiewicz 2024-03-04 21:04:53 +01:00 committed by GitHub
parent b2f18cfe71
commit 2201b9b116
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 623 additions and 190 deletions

View file

@ -13,6 +13,7 @@ gpui.workspace = true
schemars.workspace = true
serde.workspace = true
serde_json_lenient.workspace = true
subst = "0.3.0"
util.workspace = true
[dev-dependencies]

View file

@ -36,6 +36,15 @@ pub struct SpawnInTerminal {
pub allow_concurrent_runs: bool,
}
/// Keeps track of the file associated with a task and context of tasks execution (i.e. current file or current function)
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct TaskContext {
/// A path to a directory in which the task should be executed.
pub cwd: Option<PathBuf>,
/// Additional environment variables associated with a given task.
pub env: HashMap<String, String>,
}
/// Represents a short lived recipe of a task, whose main purpose
/// is to get spawned.
pub trait Task {
@ -44,10 +53,10 @@ pub trait Task {
/// Human readable name of the task to display in the UI.
fn name(&self) -> &str;
/// Task's current working directory. If `None`, current project's root will be used.
fn cwd(&self) -> Option<&Path>;
fn cwd(&self) -> Option<&str>;
/// Sets up everything needed to spawn the task in the given directory (`cwd`).
/// If a task is intended to be spawned in the terminal, it should return the corresponding struct filled with the data necessary.
fn exec(&self, cwd: Option<PathBuf>) -> Option<SpawnInTerminal>;
fn exec(&self, cx: TaskContext) -> Option<SpawnInTerminal>;
}
/// [`Source`] produces tasks that can be scheduled.

View file

@ -2,7 +2,7 @@
use std::sync::Arc;
use crate::{SpawnInTerminal, Task, TaskId, TaskSource};
use crate::{SpawnInTerminal, Task, TaskContext, TaskId, TaskSource};
use gpui::{AppContext, Context, Model};
/// A storage and source of tasks generated out of user command prompt inputs.
@ -30,21 +30,22 @@ impl Task for OneshotTask {
&self.id.0
}
fn cwd(&self) -> Option<&std::path::Path> {
fn cwd(&self) -> Option<&str> {
None
}
fn exec(&self, cwd: Option<std::path::PathBuf>) -> Option<SpawnInTerminal> {
fn exec(&self, cx: TaskContext) -> Option<SpawnInTerminal> {
if self.id().0.is_empty() {
return None;
}
let TaskContext { cwd, env } = cx;
Some(SpawnInTerminal {
id: self.id().clone(),
label: self.name().to_owned(),
command: self.id().0.clone(),
args: vec![],
cwd,
env: Default::default(),
env,
use_new_terminal: Default::default(),
allow_concurrent_runs: Default::default(),
})

View file

@ -1,10 +1,6 @@
//! A source of tasks, based on a static configuration, deserialized from the tasks config file, and related infrastructure for tracking changes to the file.
use std::{
borrow::Cow,
path::{Path, PathBuf},
sync::Arc,
};
use std::{borrow::Cow, path::Path, sync::Arc};
use collections::HashMap;
use futures::StreamExt;
@ -13,7 +9,7 @@ use schemars::{gen::SchemaSettings, JsonSchema};
use serde::{Deserialize, Serialize};
use util::ResultExt;
use crate::{SpawnInTerminal, Task, TaskId, TaskSource};
use crate::{SpawnInTerminal, Task, TaskContext, TaskId, TaskSource};
use futures::channel::mpsc::UnboundedReceiver;
/// A single config file entry with the deserialized task definition.
@ -24,7 +20,16 @@ struct StaticTask {
}
impl Task for StaticTask {
fn exec(&self, cwd: Option<PathBuf>) -> Option<SpawnInTerminal> {
fn exec(&self, cx: TaskContext) -> Option<SpawnInTerminal> {
let TaskContext { cwd, env } = cx;
let cwd = self
.definition
.cwd
.clone()
.and_then(|path| subst::substitute(&path, &env).map(Into::into).ok())
.or(cwd);
let mut definition_env = self.definition.env.clone();
definition_env.extend(env);
Some(SpawnInTerminal {
id: self.id.clone(),
cwd,
@ -33,7 +38,7 @@ impl Task for StaticTask {
label: self.definition.label.clone(),
command: self.definition.command.clone(),
args: self.definition.args.clone(),
env: self.definition.env.clone(),
env: definition_env,
})
}
@ -45,7 +50,7 @@ impl Task for StaticTask {
&self.id
}
fn cwd(&self) -> Option<&Path> {
fn cwd(&self) -> Option<&str> {
self.definition.cwd.as_deref()
}
}
@ -72,7 +77,7 @@ pub(crate) struct Definition {
pub env: HashMap<String, String>,
/// Current working directory to spawn the command into, defaults to current project root.
#[serde(default)]
pub cwd: Option<PathBuf>,
pub cwd: Option<String>,
/// Whether to use a new terminal tab or reuse the existing one to spawn the process.
#[serde(default)]
pub use_new_terminal: bool,