mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-24 06:12:25 +03:00
tasks: Add experimental support for user-defined task variables (#13699)
Context: @bennetbo spotted a regression in handling of `cargo run` task in zed repo following a merge of #13658. We've started invoking `cargo run` from the folder of an active file whereas previously we did it from the workspace root. We brainstormed few solutions that involved adding a separate task that gets invoked at a workspace level, but I realized that a cleaner solution may be to finally add user-configured task variables. This way, we can choose which crate to run by default at a workspace level. This has been originally brought up in the context of javascript tasks in https://github.com/zed-industries/zed/pull/12118#issuecomment-2129232114 Note that this is intended for internal use only for the time being. /cc @RemcoSmitsDev we should be unblocked on having runner-dependant tasks now. Release notes: - N/A
This commit is contained in:
parent
065ab93ca7
commit
bac6e2fee7
@ -19,6 +19,13 @@
|
||||
"JavaScript": {
|
||||
"tab_size": 2,
|
||||
"formatter": "prettier"
|
||||
},
|
||||
"Rust": {
|
||||
"tasks": {
|
||||
"variables": {
|
||||
"RUST_DEFAULT_PACKAGE_RUN": "zed"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"formatter": "auto",
|
||||
|
@ -128,7 +128,14 @@
|
||||
// The default number of lines to expand excerpts in the multibuffer by.
|
||||
"expand_excerpt_lines": 3,
|
||||
// Globs to match against file paths to determine if a file is private.
|
||||
"private_files": ["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/secrets.yml"],
|
||||
"private_files": [
|
||||
"**/.env*",
|
||||
"**/*.pem",
|
||||
"**/*.key",
|
||||
"**/*.cert",
|
||||
"**/*.crt",
|
||||
"**/secrets.yml"
|
||||
],
|
||||
// Whether to use additional LSP queries to format (and amend) the code after
|
||||
// every "trigger" symbol input, defined by LSP server capabilities.
|
||||
"use_on_type_format": true,
|
||||
@ -666,6 +673,10 @@
|
||||
// "max_scroll_history_lines": 10000,
|
||||
},
|
||||
"code_actions_on_format": {},
|
||||
/// Settings related to running tasks.
|
||||
"tasks": {
|
||||
"variables": {}
|
||||
},
|
||||
// An object whose keys are language names, and whose values
|
||||
// are arrays of filenames or extensions of files that should
|
||||
// use those languages.
|
||||
|
@ -8469,13 +8469,14 @@ impl Editor {
|
||||
runnable: &mut Runnable,
|
||||
cx: &WindowContext<'_>,
|
||||
) -> Vec<(TaskSourceKind, TaskTemplate)> {
|
||||
let (inventory, worktree_id) = project.read_with(cx, |project, cx| {
|
||||
let worktree_id = project
|
||||
let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
|
||||
let (worktree_id, file) = project
|
||||
.buffer_for_id(runnable.buffer)
|
||||
.and_then(|buffer| buffer.read(cx).file())
|
||||
.map(|file| WorktreeId::from_usize(file.worktree_id()));
|
||||
.map(|file| (WorktreeId::from_usize(file.worktree_id()), file.clone()))
|
||||
.unzip();
|
||||
|
||||
(project.task_inventory().clone(), worktree_id)
|
||||
(project.task_inventory().clone(), worktree_id, file)
|
||||
});
|
||||
|
||||
let inventory = inventory.read(cx);
|
||||
@ -8485,7 +8486,12 @@ impl Editor {
|
||||
.flat_map(|tag| {
|
||||
let tag = tag.0.clone();
|
||||
inventory
|
||||
.list_tasks(Some(runnable.language.clone()), worktree_id)
|
||||
.list_tasks(
|
||||
file.clone(),
|
||||
Some(runnable.language.clone()),
|
||||
worktree_id,
|
||||
cx,
|
||||
)
|
||||
.into_iter()
|
||||
.filter(move |(_, template)| {
|
||||
template.tags.iter().any(|source_tag| source_tag == &tag)
|
||||
|
@ -120,6 +120,8 @@ pub struct LanguageSettings {
|
||||
pub code_actions_on_format: HashMap<String, bool>,
|
||||
/// Whether to perform linked edits
|
||||
pub linked_edits: bool,
|
||||
/// Task configuration for this language.
|
||||
pub tasks: LanguageTaskConfig,
|
||||
}
|
||||
|
||||
impl LanguageSettings {
|
||||
@ -340,6 +342,10 @@ pub struct LanguageSettingsContent {
|
||||
///
|
||||
/// Default: true
|
||||
pub linked_edits: Option<bool>,
|
||||
/// Task configuration for this language.
|
||||
///
|
||||
/// Default: {}
|
||||
pub tasks: Option<LanguageTaskConfig>,
|
||||
}
|
||||
|
||||
/// The contents of the inline completion settings.
|
||||
@ -546,6 +552,13 @@ fn scroll_debounce_ms() -> u64 {
|
||||
50
|
||||
}
|
||||
|
||||
/// The task settings for a particular language.
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct LanguageTaskConfig {
|
||||
/// Extra task variables to set for a particular language.
|
||||
pub variables: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl InlayHintSettings {
|
||||
/// Returns the kinds of inlay hints that are enabled based on the settings.
|
||||
pub fn enabled_inlay_hint_kinds(&self) -> HashSet<Option<InlayHintKind>> {
|
||||
@ -823,6 +836,7 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent
|
||||
src.code_actions_on_format.clone(),
|
||||
);
|
||||
merge(&mut settings.linked_edits, src.linked_edits);
|
||||
merge(&mut settings.tasks, src.tasks.clone());
|
||||
|
||||
merge(
|
||||
&mut settings.preferred_line_length,
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::ops::Range;
|
||||
use std::{ops::Range, sync::Arc};
|
||||
|
||||
use crate::{Location, Runnable};
|
||||
|
||||
@ -31,7 +31,11 @@ pub trait ContextProvider: Send + Sync {
|
||||
}
|
||||
|
||||
/// Provides all tasks, associated with the current language.
|
||||
fn associated_tasks(&self) -> Option<TaskTemplates> {
|
||||
fn associated_tasks(
|
||||
&self,
|
||||
_: Option<Arc<dyn crate::File>>,
|
||||
_cx: &AppContext,
|
||||
) -> Option<TaskTemplates> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::StreamExt;
|
||||
use gpui::{AsyncAppContext, Task};
|
||||
use gpui::{AppContext, AsyncAppContext, Task};
|
||||
use http::github::latest_github_release;
|
||||
pub use language::*;
|
||||
use lazy_static::lazy_static;
|
||||
@ -501,7 +501,11 @@ impl ContextProvider for GoContextProvider {
|
||||
))
|
||||
}
|
||||
|
||||
fn associated_tasks(&self) -> Option<TaskTemplates> {
|
||||
fn associated_tasks(
|
||||
&self,
|
||||
_: Option<Arc<dyn language::File>>,
|
||||
_: &AppContext,
|
||||
) -> Option<TaskTemplates> {
|
||||
Some(TaskTemplates(vec![
|
||||
TaskTemplate {
|
||||
label: format!(
|
||||
|
@ -1,5 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use gpui::AppContext;
|
||||
use language::{ContextProvider, LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use node_runtime::NodeRuntime;
|
||||
@ -220,7 +221,11 @@ impl ContextProvider for PythonContextProvider {
|
||||
Ok(task::TaskVariables::from_iter([unittest_target]))
|
||||
}
|
||||
|
||||
fn associated_tasks(&self) -> Option<TaskTemplates> {
|
||||
fn associated_tasks(
|
||||
&self,
|
||||
_: Option<Arc<dyn language::File>>,
|
||||
_: &AppContext,
|
||||
) -> Option<TaskTemplates> {
|
||||
Some(TaskTemplates(vec![
|
||||
TaskTemplate {
|
||||
label: "execute selection".to_owned(),
|
||||
|
@ -2,9 +2,10 @@ use anyhow::{anyhow, bail, Context, Result};
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_trait::async_trait;
|
||||
use futures::{io::BufReader, StreamExt};
|
||||
use gpui::AsyncAppContext;
|
||||
use gpui::{AppContext, AsyncAppContext};
|
||||
use http::github::{latest_github_release, GitHubLspBinaryVersion};
|
||||
pub use language::*;
|
||||
use language_settings::all_language_settings;
|
||||
use lazy_static::lazy_static;
|
||||
use lsp::LanguageServerBinary;
|
||||
use project::project_settings::{BinarySettings, ProjectSettings};
|
||||
@ -407,7 +408,22 @@ impl ContextProvider for RustContextProvider {
|
||||
Ok(TaskVariables::default())
|
||||
}
|
||||
|
||||
fn associated_tasks(&self) -> Option<TaskTemplates> {
|
||||
fn associated_tasks(
|
||||
&self,
|
||||
file: Option<Arc<dyn language::File>>,
|
||||
cx: &AppContext,
|
||||
) -> Option<TaskTemplates> {
|
||||
const DEFAULT_RUN_NAME_STR: &'static str = "RUST_DEFAULT_PACKAGE_RUN";
|
||||
let package_to_run = all_language_settings(file.as_ref(), cx)
|
||||
.language(Some("Rust"))
|
||||
.tasks
|
||||
.variables
|
||||
.get(DEFAULT_RUN_NAME_STR);
|
||||
let run_task_args = if let Some(package_to_run) = package_to_run {
|
||||
vec!["run".into(), "-p".into(), package_to_run.clone()]
|
||||
} else {
|
||||
vec!["run".into()]
|
||||
};
|
||||
Some(TaskTemplates(vec![
|
||||
TaskTemplate {
|
||||
label: format!(
|
||||
@ -501,7 +517,7 @@ impl ContextProvider for RustContextProvider {
|
||||
TaskTemplate {
|
||||
label: "cargo run".into(),
|
||||
command: "cargo".into(),
|
||||
args: vec!["run".into()],
|
||||
args: run_task_args,
|
||||
cwd: Some("$ZED_DIRNAME".to_owned()),
|
||||
..TaskTemplate::default()
|
||||
},
|
||||
|
@ -10861,12 +10861,19 @@ impl Project {
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Vec<(TaskSourceKind, TaskTemplate)>>> {
|
||||
if self.is_local() {
|
||||
let language = location
|
||||
.and_then(|location| location.buffer.read(cx).language_at(location.range.start));
|
||||
let (file, language) = location
|
||||
.map(|location| {
|
||||
let buffer = location.buffer.read(cx);
|
||||
(
|
||||
buffer.file().cloned(),
|
||||
buffer.language_at(location.range.start),
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
Task::ready(Ok(self
|
||||
.task_inventory()
|
||||
.read(cx)
|
||||
.list_tasks(language, worktree)))
|
||||
.list_tasks(file, language, worktree, cx)))
|
||||
} else if let Some(project_id) = self
|
||||
.remote_id()
|
||||
.filter(|_| self.ssh_connection_string(cx).is_some())
|
||||
|
@ -15,7 +15,7 @@ use futures::{
|
||||
};
|
||||
use gpui::{AppContext, Context, Model, ModelContext, Task};
|
||||
use itertools::Itertools;
|
||||
use language::{ContextProvider, Language, Location};
|
||||
use language::{ContextProvider, File, Language, Location};
|
||||
use task::{
|
||||
static_source::StaticSource, ResolvedTask, TaskContext, TaskId, TaskTemplate, TaskTemplates,
|
||||
TaskVariables, VariableName,
|
||||
@ -155,14 +155,16 @@ impl Inventory {
|
||||
/// returns all task templates with their source kinds, in no specific order.
|
||||
pub fn list_tasks(
|
||||
&self,
|
||||
file: Option<Arc<dyn File>>,
|
||||
language: Option<Arc<Language>>,
|
||||
worktree: Option<WorktreeId>,
|
||||
cx: &AppContext,
|
||||
) -> Vec<(TaskSourceKind, TaskTemplate)> {
|
||||
let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language {
|
||||
name: language.name(),
|
||||
});
|
||||
let language_tasks = language
|
||||
.and_then(|language| language.context_provider()?.associated_tasks())
|
||||
.and_then(|language| language.context_provider()?.associated_tasks(file, cx))
|
||||
.into_iter()
|
||||
.flat_map(|tasks| tasks.0.into_iter())
|
||||
.flat_map(|task| Some((task_source_kind.as_ref()?, task)));
|
||||
@ -207,8 +209,11 @@ impl Inventory {
|
||||
let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language {
|
||||
name: language.name(),
|
||||
});
|
||||
let file = location
|
||||
.as_ref()
|
||||
.and_then(|location| location.buffer.read(cx).file().cloned());
|
||||
let language_tasks = language
|
||||
.and_then(|language| language.context_provider()?.associated_tasks())
|
||||
.and_then(|language| language.context_provider()?.associated_tasks(file, cx))
|
||||
.into_iter()
|
||||
.flat_map(|tasks| tasks.0.into_iter())
|
||||
.flat_map(|task| Some((task_source_kind.as_ref()?, task)));
|
||||
@ -471,9 +476,9 @@ mod test_inventory {
|
||||
worktree: Option<WorktreeId>,
|
||||
cx: &mut TestAppContext,
|
||||
) -> Vec<String> {
|
||||
inventory.update(cx, |inventory, _| {
|
||||
inventory.update(cx, |inventory, cx| {
|
||||
inventory
|
||||
.list_tasks(None, worktree)
|
||||
.list_tasks(None, None, worktree, cx)
|
||||
.into_iter()
|
||||
.map(|(_, task)| task.label)
|
||||
.sorted()
|
||||
@ -486,9 +491,9 @@ mod test_inventory {
|
||||
task_name: &str,
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
inventory.update(cx, |inventory, _| {
|
||||
inventory.update(cx, |inventory, cx| {
|
||||
let (task_source_kind, task) = inventory
|
||||
.list_tasks(None, None)
|
||||
.list_tasks(None, None, None, cx)
|
||||
.into_iter()
|
||||
.find(|(_, task)| task.label == task_name)
|
||||
.unwrap_or_else(|| panic!("Failed to find task with name {task_name}"));
|
||||
@ -639,7 +644,11 @@ impl ContextProviderWithTasks {
|
||||
}
|
||||
|
||||
impl ContextProvider for ContextProviderWithTasks {
|
||||
fn associated_tasks(&self) -> Option<TaskTemplates> {
|
||||
fn associated_tasks(
|
||||
&self,
|
||||
_: Option<Arc<dyn language::File>>,
|
||||
_: &AppContext,
|
||||
) -> Option<TaskTemplates> {
|
||||
Some(self.templates.clone())
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user