Extensions registering tasks (#9572)

This PR also introduces built-in tasks for Rust and Elixir. Note that
this is not a precedent for future PRs to include tasks for more
languages; we simply want to find the rough edges with tasks & language
integrations before proceeding to task contexts provided by extensions.

As is, we'll load tasks for all loaded languages, so in order to get
Elixir tasks, you have to open an Elixir buffer first. I think it sort
of makes sense (though it's not ideal), as in the future where
extensions do provide their own tasks.json, we'd like to limit the # of
tasks surfaced to the user to make them as relevant to the project at
hand as possible.

Release Notes:

- Added built-in tasks for Rust and Elixir files.
This commit is contained in:
Piotr Osiewicz 2024-03-22 16:18:33 +01:00 committed by GitHub
parent cb4f868815
commit 4dc61f7ccd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 416 additions and 169 deletions

View file

@ -18,6 +18,7 @@ use std::{
Arc,
},
};
use task::static_source::{Definition, TaskDefinitions};
use util::{
async_maybe,
fs::remove_matching,
@ -535,3 +536,45 @@ fn label_for_symbol_elixir(
filter_range: 0..name.len(),
})
}
pub(super) fn elixir_task_context() -> ContextProviderWithTasks {
// Taken from https://gist.github.com/josevalim/2e4f60a14ccd52728e3256571259d493#gistcomment-4995881
ContextProviderWithTasks::new(TaskDefinitions(vec![
Definition {
label: "Elixir: test suite".to_owned(),
command: "mix".to_owned(),
args: vec!["test".to_owned()],
..Default::default()
},
Definition {
label: "Elixir: failed tests suite".to_owned(),
command: "mix".to_owned(),
args: vec!["test".to_owned(), "--failed".to_owned()],
..Default::default()
},
Definition {
label: "Elixir: test file".to_owned(),
command: "mix".to_owned(),
args: vec!["test".to_owned(), "$ZED_FILE".to_owned()],
..Default::default()
},
Definition {
label: "Elixir: test at current line".to_owned(),
command: "mix".to_owned(),
args: vec!["test".to_owned(), "$ZED_FILE:$ZED_ROW".to_owned()],
..Default::default()
},
Definition {
label: "Elixir: break line".to_owned(),
command: "iex".to_owned(),
args: vec![
"-S".to_owned(),
"mix".to_owned(),
"test".to_owned(),
"-b".to_owned(),
"$ZED_FILE:$ZED_ROW".to_owned(),
],
..Default::default()
},
]))
}

View file

@ -52,7 +52,7 @@ impl JsonLspAdapter {
},
cx,
);
let tasks_schema = task::static_source::DefinitionProvider::generate_json_schema();
let tasks_schema = task::static_source::TaskDefinitions::generate_json_schema();
serde_json::json!({
"json": {
"format": {

View file

@ -7,7 +7,7 @@ use settings::Settings;
use std::{str, sync::Arc};
use util::asset_str;
use crate::rust::RustContextProvider;
use crate::{elixir::elixir_task_context, rust::RustContextProvider};
use self::{deno::DenoSettings, elixir::ElixirSettings};
@ -130,8 +130,13 @@ pub fn init(
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
Some(Arc::new(language::DefaultContextProvider)),
move || Ok((config.clone(), load_queries($name))),
move || {
Ok((
config.clone(),
load_queries($name),
Some(Arc::new(language::SymbolContextProvider)),
))
},
);
};
($name:literal, $adapters:expr) => {
@ -145,8 +150,13 @@ pub fn init(
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
Some(Arc::new(language::DefaultContextProvider)),
move || Ok((config.clone(), load_queries($name))),
move || {
Ok((
config.clone(),
load_queries($name),
Some(Arc::new(language::SymbolContextProvider)),
))
},
);
};
($name:literal, $adapters:expr, $context_provider:expr) => {
@ -160,8 +170,13 @@ pub fn init(
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
Some(Arc::new($context_provider)),
move || Ok((config.clone(), load_queries($name))),
move || {
Ok((
config.clone(),
load_queries($name),
Some(Arc::new($context_provider)),
))
},
);
};
}
@ -199,11 +214,16 @@ pub fn init(
vec![
Arc::new(elixir::ElixirLspAdapter),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
]
],
elixir_task_context()
);
}
elixir::ElixirLspSetting::NextLs => {
language!("elixir", vec![Arc::new(elixir::NextLspAdapter)]);
language!(
"elixir",
vec![Arc::new(elixir::NextLspAdapter)],
elixir_task_context()
);
}
elixir::ElixirLspSetting::Local { path, arguments } => {
language!(
@ -211,7 +231,8 @@ pub fn init(
vec![Arc::new(elixir::LocalLspAdapter {
path: path.clone(),
arguments: arguments.clone(),
})]
})],
elixir_task_context()
);
}
}

View file

@ -11,6 +11,10 @@ use regex::Regex;
use settings::Settings;
use smol::fs::{self, File};
use std::{any::Any, borrow::Cow, env::consts, path::PathBuf, sync::Arc};
use task::{
static_source::{Definition, TaskDefinitions},
TaskVariables,
};
use util::{
async_maybe,
fs::remove_matching,
@ -319,44 +323,77 @@ impl LspAdapter for RustLspAdapter {
pub(crate) struct RustContextProvider;
impl LanguageContextProvider for RustContextProvider {
impl ContextProvider for RustContextProvider {
fn build_context(
&self,
location: Location,
cx: &mut gpui::AppContext,
) -> Result<LanguageContext> {
let mut context = DefaultContextProvider.build_context(location.clone(), cx)?;
if context.package.is_none() {
if let Some(path) = location.buffer.read(cx).file().and_then(|file| {
let local_file = file.as_local()?.abs_path(cx);
local_file.parent().map(PathBuf::from)
}) {
// src/
// main.rs
// lib.rs
// foo/
// bar/
// baz.rs <|>
// /bin/
// bin_1.rs
//
let Some(pkgid) = std::process::Command::new("cargo")
.current_dir(path)
.arg("pkgid")
.output()
.log_err()
else {
return Ok(context);
};
let package_name = String::from_utf8(pkgid.stdout)
.map(|name| name.trim().to_owned())
.ok();
) -> Result<TaskVariables> {
let mut context = SymbolContextProvider.build_context(location.clone(), cx)?;
context.package = package_name;
if let Some(path) = location.buffer.read(cx).file().and_then(|file| {
let local_file = file.as_local()?.abs_path(cx);
local_file.parent().map(PathBuf::from)
}) {
let Some(pkgid) = std::process::Command::new("cargo")
.current_dir(path)
.arg("pkgid")
.output()
.log_err()
else {
return Ok(context);
};
let package_name = String::from_utf8(pkgid.stdout)
.map(|name| name.trim().to_owned())
.ok();
if let Some(package_name) = package_name {
context.0.insert("ZED_PACKAGE".to_owned(), package_name);
}
}
Ok(context)
}
fn associated_tasks(&self) -> Option<TaskDefinitions> {
Some(TaskDefinitions(vec![
Definition {
label: "Rust: Test current crate".to_owned(),
command: "cargo".into(),
args: vec!["test".into(), "-p".into(), "$ZED_PACKAGE".into()],
..Default::default()
},
Definition {
label: "Rust: Test current function".to_owned(),
command: "cargo".into(),
args: vec![
"test".into(),
"-p".into(),
"$ZED_PACKAGE".into(),
"--".into(),
"$ZED_SYMBOL".into(),
],
..Default::default()
},
Definition {
label: "Rust: cargo run".into(),
command: "cargo".into(),
args: vec!["run".into()],
..Default::default()
},
Definition {
label: "Rust: cargo check current crate".into(),
command: "cargo".into(),
args: vec!["check".into(), "-p".into(), "$ZED_PACKAGE".into()],
..Default::default()
},
Definition {
label: "Rust: cargo check workspace".into(),
command: "cargo".into(),
args: vec!["check".into(), "--workspace".into()],
..Default::default()
},
]))
}
}
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {