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:
parent
cb4f868815
commit
4dc61f7ccd
19 changed files with 416 additions and 169 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -3486,7 +3486,9 @@ dependencies = [
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serde_json_lenient",
|
||||||
"settings",
|
"settings",
|
||||||
|
"task",
|
||||||
"theme",
|
"theme",
|
||||||
"toml 0.8.10",
|
"toml 0.8.10",
|
||||||
"url",
|
"url",
|
||||||
|
@ -5176,6 +5178,7 @@ dependencies = [
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"smol",
|
"smol",
|
||||||
"sum_tree",
|
"sum_tree",
|
||||||
|
"task",
|
||||||
"text",
|
"text",
|
||||||
"theme",
|
"theme",
|
||||||
"tree-sitter",
|
"tree-sitter",
|
||||||
|
|
|
@ -44,6 +44,8 @@ wasmtime.workspace = true
|
||||||
wasmtime-wasi.workspace = true
|
wasmtime-wasi.workspace = true
|
||||||
wasmparser.workspace = true
|
wasmparser.workspace = true
|
||||||
wit-component.workspace = true
|
wit-component.workspace = true
|
||||||
|
task.workspace = true
|
||||||
|
serde_json_lenient.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
ctor.workspace = true
|
ctor.workspace = true
|
||||||
|
|
|
@ -23,7 +23,8 @@ use futures::{
|
||||||
};
|
};
|
||||||
use gpui::{actions, AppContext, Context, EventEmitter, Global, Model, ModelContext, Task};
|
use gpui::{actions, AppContext, Context, EventEmitter, Global, Model, ModelContext, Task};
|
||||||
use language::{
|
use language::{
|
||||||
LanguageConfig, LanguageMatcher, LanguageQueries, LanguageRegistry, QUERY_FILENAME_PREFIXES,
|
ContextProviderWithTasks, LanguageConfig, LanguageMatcher, LanguageQueries, LanguageRegistry,
|
||||||
|
QUERY_FILENAME_PREFIXES,
|
||||||
};
|
};
|
||||||
use node_runtime::NodeRuntime;
|
use node_runtime::NodeRuntime;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -835,12 +836,18 @@ impl ExtensionStore {
|
||||||
language_name.clone(),
|
language_name.clone(),
|
||||||
language.grammar.clone(),
|
language.grammar.clone(),
|
||||||
language.matcher.clone(),
|
language.matcher.clone(),
|
||||||
None,
|
|
||||||
move || {
|
move || {
|
||||||
let config = std::fs::read_to_string(language_path.join("config.toml"))?;
|
let config = std::fs::read_to_string(language_path.join("config.toml"))?;
|
||||||
let config: LanguageConfig = ::toml::from_str(&config)?;
|
let config: LanguageConfig = ::toml::from_str(&config)?;
|
||||||
let queries = load_plugin_queries(&language_path);
|
let queries = load_plugin_queries(&language_path);
|
||||||
Ok((config, queries))
|
let tasks = std::fs::read_to_string(language_path.join("tasks.json"))
|
||||||
|
.ok()
|
||||||
|
.and_then(|contents| {
|
||||||
|
let definitions = serde_json_lenient::from_str(&contents).log_err()?;
|
||||||
|
Some(Arc::new(ContextProviderWithTasks::new(definitions)) as Arc<_>)
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok((config, queries, tasks))
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,6 +58,7 @@ pulldown-cmark.workspace = true
|
||||||
tree-sitter.workspace = true
|
tree-sitter.workspace = true
|
||||||
unicase = "2.6"
|
unicase = "2.6"
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
|
task.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
collections = { workspace = true, features = ["test-support"] }
|
collections = { workspace = true, features = ["test-support"] }
|
||||||
|
|
|
@ -14,6 +14,7 @@ pub mod language_settings;
|
||||||
mod outline;
|
mod outline;
|
||||||
pub mod proto;
|
pub mod proto;
|
||||||
mod syntax_map;
|
mod syntax_map;
|
||||||
|
mod task_context;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod buffer_tests;
|
mod buffer_tests;
|
||||||
|
@ -54,6 +55,9 @@ use std::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use syntax_map::SyntaxSnapshot;
|
use syntax_map::SyntaxSnapshot;
|
||||||
|
pub use task_context::{
|
||||||
|
ContextProvider, ContextProviderWithTasks, LanguageSource, SymbolContextProvider,
|
||||||
|
};
|
||||||
use theme::SyntaxTheme;
|
use theme::SyntaxTheme;
|
||||||
use tree_sitter::{self, wasmtime, Query, WasmStore};
|
use tree_sitter::{self, wasmtime, Query, WasmStore};
|
||||||
use util::http::HttpClient;
|
use util::http::HttpClient;
|
||||||
|
@ -120,46 +124,6 @@ pub struct Location {
|
||||||
pub range: Range<Anchor>,
|
pub range: Range<Anchor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LanguageContext {
|
|
||||||
pub package: Option<String>,
|
|
||||||
pub symbol: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait LanguageContextProvider: Send + Sync {
|
|
||||||
fn build_context(&self, location: Location, cx: &mut AppContext) -> Result<LanguageContext>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A context provider that fills out LanguageContext without inspecting the contents.
|
|
||||||
pub struct DefaultContextProvider;
|
|
||||||
|
|
||||||
impl LanguageContextProvider for DefaultContextProvider {
|
|
||||||
fn build_context(
|
|
||||||
&self,
|
|
||||||
location: Location,
|
|
||||||
cx: &mut AppContext,
|
|
||||||
) -> gpui::Result<LanguageContext> {
|
|
||||||
let symbols = location
|
|
||||||
.buffer
|
|
||||||
.read(cx)
|
|
||||||
.snapshot()
|
|
||||||
.symbols_containing(location.range.start, None);
|
|
||||||
let symbol = symbols.and_then(|symbols| {
|
|
||||||
symbols.last().map(|symbol| {
|
|
||||||
let range = symbol
|
|
||||||
.name_ranges
|
|
||||||
.last()
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or(0..symbol.text.len());
|
|
||||||
symbol.text[range].to_string()
|
|
||||||
})
|
|
||||||
});
|
|
||||||
Ok(LanguageContext {
|
|
||||||
package: None,
|
|
||||||
symbol,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a Language Server, with certain cached sync properties.
|
/// Represents a Language Server, with certain cached sync properties.
|
||||||
/// Uses [`LspAdapter`] under the hood, but calls all 'static' methods
|
/// Uses [`LspAdapter`] under the hood, but calls all 'static' methods
|
||||||
/// once at startup, and caches the results.
|
/// once at startup, and caches the results.
|
||||||
|
@ -777,7 +741,7 @@ pub struct Language {
|
||||||
pub(crate) id: LanguageId,
|
pub(crate) id: LanguageId,
|
||||||
pub(crate) config: LanguageConfig,
|
pub(crate) config: LanguageConfig,
|
||||||
pub(crate) grammar: Option<Arc<Grammar>>,
|
pub(crate) grammar: Option<Arc<Grammar>>,
|
||||||
pub(crate) context_provider: Option<Arc<dyn LanguageContextProvider>>,
|
pub(crate) context_provider: Option<Arc<dyn ContextProvider>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||||
|
@ -892,10 +856,7 @@ impl Language {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_context_provider(
|
pub fn with_context_provider(mut self, provider: Option<Arc<dyn ContextProvider>>) -> Self {
|
||||||
mut self,
|
|
||||||
provider: Option<Arc<dyn LanguageContextProvider>>,
|
|
||||||
) -> Self {
|
|
||||||
self.context_provider = provider;
|
self.context_provider = provider;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -1220,7 +1181,7 @@ impl Language {
|
||||||
self.config.name.clone()
|
self.config.name.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn context_provider(&self) -> Option<Arc<dyn LanguageContextProvider>> {
|
pub fn context_provider(&self) -> Option<Arc<dyn ContextProvider>> {
|
||||||
self.context_provider.clone()
|
self.context_provider.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
language_settings::all_language_settings, CachedLspAdapter, File, Language, LanguageConfig,
|
language_settings::all_language_settings, task_context::ContextProvider, CachedLspAdapter,
|
||||||
LanguageContextProvider, LanguageId, LanguageMatcher, LanguageServerName, LspAdapter,
|
File, Language, LanguageConfig, LanguageId, LanguageMatcher, LanguageServerName, LspAdapter,
|
||||||
LspAdapterDelegate, PARSER, PLAIN_TEXT,
|
LspAdapterDelegate, PARSER, PLAIN_TEXT,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
|
@ -73,9 +73,17 @@ struct AvailableLanguage {
|
||||||
name: Arc<str>,
|
name: Arc<str>,
|
||||||
grammar: Option<Arc<str>>,
|
grammar: Option<Arc<str>>,
|
||||||
matcher: LanguageMatcher,
|
matcher: LanguageMatcher,
|
||||||
load: Arc<dyn Fn() -> Result<(LanguageConfig, LanguageQueries)> + 'static + Send + Sync>,
|
load: Arc<
|
||||||
|
dyn Fn() -> Result<(
|
||||||
|
LanguageConfig,
|
||||||
|
LanguageQueries,
|
||||||
|
Option<Arc<dyn ContextProvider>>,
|
||||||
|
)>
|
||||||
|
+ 'static
|
||||||
|
+ Send
|
||||||
|
+ Sync,
|
||||||
|
>,
|
||||||
loaded: bool,
|
loaded: bool,
|
||||||
context_provider: Option<Arc<dyn LanguageContextProvider>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AvailableGrammar {
|
enum AvailableGrammar {
|
||||||
|
@ -195,8 +203,7 @@ impl LanguageRegistry {
|
||||||
config.name.clone(),
|
config.name.clone(),
|
||||||
config.grammar.clone(),
|
config.grammar.clone(),
|
||||||
config.matcher.clone(),
|
config.matcher.clone(),
|
||||||
None,
|
move || Ok((config.clone(), Default::default(), None)),
|
||||||
move || Ok((config.clone(), Default::default())),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,8 +252,14 @@ impl LanguageRegistry {
|
||||||
name: Arc<str>,
|
name: Arc<str>,
|
||||||
grammar_name: Option<Arc<str>>,
|
grammar_name: Option<Arc<str>>,
|
||||||
matcher: LanguageMatcher,
|
matcher: LanguageMatcher,
|
||||||
context_provider: Option<Arc<dyn LanguageContextProvider>>,
|
load: impl Fn() -> Result<(
|
||||||
load: impl Fn() -> Result<(LanguageConfig, LanguageQueries)> + 'static + Send + Sync,
|
LanguageConfig,
|
||||||
|
LanguageQueries,
|
||||||
|
Option<Arc<dyn ContextProvider>>,
|
||||||
|
)>
|
||||||
|
+ 'static
|
||||||
|
+ Send
|
||||||
|
+ Sync,
|
||||||
) {
|
) {
|
||||||
let load = Arc::new(load);
|
let load = Arc::new(load);
|
||||||
let state = &mut *self.state.write();
|
let state = &mut *self.state.write();
|
||||||
|
@ -266,8 +279,6 @@ impl LanguageRegistry {
|
||||||
grammar: grammar_name,
|
grammar: grammar_name,
|
||||||
matcher,
|
matcher,
|
||||||
load,
|
load,
|
||||||
|
|
||||||
context_provider,
|
|
||||||
loaded: false,
|
loaded: false,
|
||||||
});
|
});
|
||||||
state.version += 1;
|
state.version += 1;
|
||||||
|
@ -333,7 +344,6 @@ impl LanguageRegistry {
|
||||||
matcher: language.config.matcher.clone(),
|
matcher: language.config.matcher.clone(),
|
||||||
load: Arc::new(|| Err(anyhow!("already loaded"))),
|
load: Arc::new(|| Err(anyhow!("already loaded"))),
|
||||||
loaded: true,
|
loaded: true,
|
||||||
context_provider: language.context_provider.clone(),
|
|
||||||
});
|
});
|
||||||
state.add(language);
|
state.add(language);
|
||||||
}
|
}
|
||||||
|
@ -507,9 +517,8 @@ impl LanguageRegistry {
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
let id = language.id;
|
let id = language.id;
|
||||||
let name = language.name.clone();
|
let name = language.name.clone();
|
||||||
let provider = language.context_provider.clone();
|
|
||||||
let language = async {
|
let language = async {
|
||||||
let (config, queries) = (language.load)()?;
|
let (config, queries, provider) = (language.load)()?;
|
||||||
|
|
||||||
if let Some(grammar) = config.grammar.clone() {
|
if let Some(grammar) = config.grammar.clone() {
|
||||||
let grammar = Some(this.get_or_load_grammar(grammar).await?);
|
let grammar = Some(this.get_or_load_grammar(grammar).await?);
|
||||||
|
|
109
crates/language/src/task_context.rs
Normal file
109
crates/language/src/task_context.rs
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
use crate::{LanguageRegistry, Location};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use gpui::{AppContext, Context, Model};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use task::{static_source::tasks_for, static_source::TaskDefinitions, TaskSource, TaskVariables};
|
||||||
|
|
||||||
|
/// Language Contexts are used by Zed tasks to extract information about source file.
|
||||||
|
pub trait ContextProvider: Send + Sync {
|
||||||
|
fn build_context(&self, _: Location, _: &mut AppContext) -> Result<TaskVariables> {
|
||||||
|
Ok(TaskVariables::default())
|
||||||
|
}
|
||||||
|
fn associated_tasks(&self) -> Option<TaskDefinitions> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A context provider that finds out what symbol is currently focused in the buffer.
|
||||||
|
pub struct SymbolContextProvider;
|
||||||
|
|
||||||
|
impl ContextProvider for SymbolContextProvider {
|
||||||
|
fn build_context(
|
||||||
|
&self,
|
||||||
|
location: Location,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> gpui::Result<TaskVariables> {
|
||||||
|
let symbols = location
|
||||||
|
.buffer
|
||||||
|
.read(cx)
|
||||||
|
.snapshot()
|
||||||
|
.symbols_containing(location.range.start, None);
|
||||||
|
let symbol = symbols.and_then(|symbols| {
|
||||||
|
symbols.last().map(|symbol| {
|
||||||
|
let range = symbol
|
||||||
|
.name_ranges
|
||||||
|
.last()
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or(0..symbol.text.len());
|
||||||
|
symbol.text[range].to_string()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
Ok(TaskVariables::from_iter(
|
||||||
|
symbol.map(|symbol| ("ZED_SYMBOL".to_string(), symbol)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A ContextProvider that doesn't provide any task variables on it's own, though it has some associated tasks.
|
||||||
|
pub struct ContextProviderWithTasks {
|
||||||
|
definitions: TaskDefinitions,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContextProviderWithTasks {
|
||||||
|
pub fn new(definitions: TaskDefinitions) -> Self {
|
||||||
|
Self { definitions }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContextProvider for ContextProviderWithTasks {
|
||||||
|
fn associated_tasks(&self) -> Option<TaskDefinitions> {
|
||||||
|
Some(self.definitions.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_context(&self, location: Location, cx: &mut AppContext) -> Result<TaskVariables> {
|
||||||
|
SymbolContextProvider.build_context(location, cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A source that pulls in the tasks from language registry.
|
||||||
|
pub struct LanguageSource {
|
||||||
|
languages: Arc<LanguageRegistry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LanguageSource {
|
||||||
|
pub fn new(
|
||||||
|
languages: Arc<LanguageRegistry>,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Model<Box<dyn TaskSource>> {
|
||||||
|
cx.new_model(|_| Box::new(Self { languages }) as Box<_>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TaskSource for LanguageSource {
|
||||||
|
fn as_any(&mut self) -> &mut dyn std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tasks_for_path(
|
||||||
|
&mut self,
|
||||||
|
_: Option<&std::path::Path>,
|
||||||
|
_: &mut gpui::ModelContext<Box<dyn TaskSource>>,
|
||||||
|
) -> Vec<Arc<dyn task::Task>> {
|
||||||
|
self.languages
|
||||||
|
.to_vec()
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|language| {
|
||||||
|
language
|
||||||
|
.context_provider()?
|
||||||
|
.associated_tasks()
|
||||||
|
.map(|tasks| (tasks, language))
|
||||||
|
})
|
||||||
|
.flat_map(|(tasks, language)| {
|
||||||
|
let language_name = language.name();
|
||||||
|
let id_base = format!("buffer_source_{language_name}");
|
||||||
|
tasks_for(tasks, &id_base)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ use std::{
|
||||||
Arc,
|
Arc,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use task::static_source::{Definition, TaskDefinitions};
|
||||||
use util::{
|
use util::{
|
||||||
async_maybe,
|
async_maybe,
|
||||||
fs::remove_matching,
|
fs::remove_matching,
|
||||||
|
@ -535,3 +536,45 @@ fn label_for_symbol_elixir(
|
||||||
filter_range: 0..name.len(),
|
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()
|
||||||
|
},
|
||||||
|
]))
|
||||||
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ impl JsonLspAdapter {
|
||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
let tasks_schema = task::static_source::DefinitionProvider::generate_json_schema();
|
let tasks_schema = task::static_source::TaskDefinitions::generate_json_schema();
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"json": {
|
"json": {
|
||||||
"format": {
|
"format": {
|
||||||
|
|
|
@ -7,7 +7,7 @@ use settings::Settings;
|
||||||
use std::{str, sync::Arc};
|
use std::{str, sync::Arc};
|
||||||
use util::asset_str;
|
use util::asset_str;
|
||||||
|
|
||||||
use crate::rust::RustContextProvider;
|
use crate::{elixir::elixir_task_context, rust::RustContextProvider};
|
||||||
|
|
||||||
use self::{deno::DenoSettings, elixir::ElixirSettings};
|
use self::{deno::DenoSettings, elixir::ElixirSettings};
|
||||||
|
|
||||||
|
@ -130,8 +130,13 @@ pub fn init(
|
||||||
config.name.clone(),
|
config.name.clone(),
|
||||||
config.grammar.clone(),
|
config.grammar.clone(),
|
||||||
config.matcher.clone(),
|
config.matcher.clone(),
|
||||||
Some(Arc::new(language::DefaultContextProvider)),
|
move || {
|
||||||
move || Ok((config.clone(), load_queries($name))),
|
Ok((
|
||||||
|
config.clone(),
|
||||||
|
load_queries($name),
|
||||||
|
Some(Arc::new(language::SymbolContextProvider)),
|
||||||
|
))
|
||||||
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
($name:literal, $adapters:expr) => {
|
($name:literal, $adapters:expr) => {
|
||||||
|
@ -145,8 +150,13 @@ pub fn init(
|
||||||
config.name.clone(),
|
config.name.clone(),
|
||||||
config.grammar.clone(),
|
config.grammar.clone(),
|
||||||
config.matcher.clone(),
|
config.matcher.clone(),
|
||||||
Some(Arc::new(language::DefaultContextProvider)),
|
move || {
|
||||||
move || Ok((config.clone(), load_queries($name))),
|
Ok((
|
||||||
|
config.clone(),
|
||||||
|
load_queries($name),
|
||||||
|
Some(Arc::new(language::SymbolContextProvider)),
|
||||||
|
))
|
||||||
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
($name:literal, $adapters:expr, $context_provider:expr) => {
|
($name:literal, $adapters:expr, $context_provider:expr) => {
|
||||||
|
@ -160,8 +170,13 @@ pub fn init(
|
||||||
config.name.clone(),
|
config.name.clone(),
|
||||||
config.grammar.clone(),
|
config.grammar.clone(),
|
||||||
config.matcher.clone(),
|
config.matcher.clone(),
|
||||||
Some(Arc::new($context_provider)),
|
move || {
|
||||||
move || Ok((config.clone(), load_queries($name))),
|
Ok((
|
||||||
|
config.clone(),
|
||||||
|
load_queries($name),
|
||||||
|
Some(Arc::new($context_provider)),
|
||||||
|
))
|
||||||
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -199,11 +214,16 @@ pub fn init(
|
||||||
vec![
|
vec![
|
||||||
Arc::new(elixir::ElixirLspAdapter),
|
Arc::new(elixir::ElixirLspAdapter),
|
||||||
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||||
]
|
],
|
||||||
|
elixir_task_context()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
elixir::ElixirLspSetting::NextLs => {
|
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 } => {
|
elixir::ElixirLspSetting::Local { path, arguments } => {
|
||||||
language!(
|
language!(
|
||||||
|
@ -211,7 +231,8 @@ pub fn init(
|
||||||
vec![Arc::new(elixir::LocalLspAdapter {
|
vec![Arc::new(elixir::LocalLspAdapter {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
arguments: arguments.clone(),
|
arguments: arguments.clone(),
|
||||||
})]
|
})],
|
||||||
|
elixir_task_context()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,10 @@ use regex::Regex;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use smol::fs::{self, File};
|
use smol::fs::{self, File};
|
||||||
use std::{any::Any, borrow::Cow, env::consts, path::PathBuf, sync::Arc};
|
use std::{any::Any, borrow::Cow, env::consts, path::PathBuf, sync::Arc};
|
||||||
|
use task::{
|
||||||
|
static_source::{Definition, TaskDefinitions},
|
||||||
|
TaskVariables,
|
||||||
|
};
|
||||||
use util::{
|
use util::{
|
||||||
async_maybe,
|
async_maybe,
|
||||||
fs::remove_matching,
|
fs::remove_matching,
|
||||||
|
@ -319,44 +323,77 @@ impl LspAdapter for RustLspAdapter {
|
||||||
|
|
||||||
pub(crate) struct RustContextProvider;
|
pub(crate) struct RustContextProvider;
|
||||||
|
|
||||||
impl LanguageContextProvider for RustContextProvider {
|
impl ContextProvider for RustContextProvider {
|
||||||
fn build_context(
|
fn build_context(
|
||||||
&self,
|
&self,
|
||||||
location: Location,
|
location: Location,
|
||||||
cx: &mut gpui::AppContext,
|
cx: &mut gpui::AppContext,
|
||||||
) -> Result<LanguageContext> {
|
) -> Result<TaskVariables> {
|
||||||
let mut context = DefaultContextProvider.build_context(location.clone(), cx)?;
|
let mut context = SymbolContextProvider.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();
|
|
||||||
|
|
||||||
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)
|
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> {
|
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||||
|
|
|
@ -35,13 +35,15 @@ pub enum TaskSourceKind {
|
||||||
AbsPath(PathBuf),
|
AbsPath(PathBuf),
|
||||||
/// Worktree-specific task definitions, e.g. dynamic tasks from open worktree file, or tasks from the worktree's .zed/task.json
|
/// Worktree-specific task definitions, e.g. dynamic tasks from open worktree file, or tasks from the worktree's .zed/task.json
|
||||||
Worktree { id: WorktreeId, abs_path: PathBuf },
|
Worktree { id: WorktreeId, abs_path: PathBuf },
|
||||||
|
/// Buffer-specific task definitions, originating in e.g. language extension.
|
||||||
|
Buffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TaskSourceKind {
|
impl TaskSourceKind {
|
||||||
fn abs_path(&self) -> Option<&Path> {
|
fn abs_path(&self) -> Option<&Path> {
|
||||||
match self {
|
match self {
|
||||||
Self::AbsPath(abs_path) | Self::Worktree { abs_path, .. } => Some(abs_path),
|
Self::AbsPath(abs_path) | Self::Worktree { abs_path, .. } => Some(abs_path),
|
||||||
Self::UserInput => None,
|
Self::UserInput | Self::Buffer => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,13 +41,26 @@ pub struct SpawnInTerminal {
|
||||||
pub reveal: RevealStrategy,
|
pub reveal: RevealStrategy,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VariableName = String;
|
||||||
|
type VariableValue = String;
|
||||||
|
|
||||||
|
/// Container for predefined environment variables that describe state of Zed at the time the task was spawned.
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||||
|
pub struct TaskVariables(pub HashMap<VariableName, VariableValue>);
|
||||||
|
|
||||||
|
impl FromIterator<(String, String)> for TaskVariables {
|
||||||
|
fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {
|
||||||
|
Self(HashMap::from_iter(iter))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Keeps track of the file associated with a task and context of tasks execution (i.e. current file or current function)
|
/// 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)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||||
pub struct TaskContext {
|
pub struct TaskContext {
|
||||||
/// A path to a directory in which the task should be executed.
|
/// A path to a directory in which the task should be executed.
|
||||||
pub cwd: Option<PathBuf>,
|
pub cwd: Option<PathBuf>,
|
||||||
/// Additional environment variables associated with a given task.
|
/// Additional environment variables associated with a given task.
|
||||||
pub env: HashMap<String, String>,
|
pub task_variables: TaskVariables,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a short lived recipe of a task, whose main purpose
|
/// Represents a short lived recipe of a task, whose main purpose
|
||||||
|
|
|
@ -40,14 +40,17 @@ impl Task for OneshotTask {
|
||||||
if self.id().0.is_empty() {
|
if self.id().0.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let TaskContext { cwd, env } = cx;
|
let TaskContext {
|
||||||
|
cwd,
|
||||||
|
task_variables,
|
||||||
|
} = cx;
|
||||||
Some(SpawnInTerminal {
|
Some(SpawnInTerminal {
|
||||||
id: self.id().clone(),
|
id: self.id().clone(),
|
||||||
label: self.name().to_owned(),
|
label: self.name().to_owned(),
|
||||||
command: self.id().0.clone(),
|
command: self.id().0.clone(),
|
||||||
args: vec![],
|
args: vec![],
|
||||||
cwd,
|
cwd,
|
||||||
env,
|
env: task_variables.0,
|
||||||
use_new_terminal: Default::default(),
|
use_new_terminal: Default::default(),
|
||||||
allow_concurrent_runs: Default::default(),
|
allow_concurrent_runs: Default::default(),
|
||||||
reveal: RevealStrategy::default(),
|
reveal: RevealStrategy::default(),
|
||||||
|
|
|
@ -19,17 +19,46 @@ struct StaticTask {
|
||||||
definition: Definition,
|
definition: Definition,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl StaticTask {
|
||||||
|
fn new(definition: Definition, (id_base, index_in_file): (&str, usize)) -> Arc<Self> {
|
||||||
|
Arc::new(Self {
|
||||||
|
id: TaskId(format!(
|
||||||
|
"static_{id_base}_{index_in_file}_{}",
|
||||||
|
definition.label
|
||||||
|
)),
|
||||||
|
definition,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TODO: doc
|
||||||
|
pub fn tasks_for(tasks: TaskDefinitions, id_base: &str) -> Vec<Arc<dyn Task>> {
|
||||||
|
tasks
|
||||||
|
.0
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, task)| StaticTask::new(task, (id_base, index)) as Arc<_>)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
impl Task for StaticTask {
|
impl Task for StaticTask {
|
||||||
fn exec(&self, cx: TaskContext) -> Option<SpawnInTerminal> {
|
fn exec(&self, cx: TaskContext) -> Option<SpawnInTerminal> {
|
||||||
let TaskContext { cwd, env } = cx;
|
let TaskContext {
|
||||||
|
cwd,
|
||||||
|
task_variables,
|
||||||
|
} = cx;
|
||||||
let cwd = self
|
let cwd = self
|
||||||
.definition
|
.definition
|
||||||
.cwd
|
.cwd
|
||||||
.clone()
|
.clone()
|
||||||
.and_then(|path| subst::substitute(&path, &env).map(Into::into).ok())
|
.and_then(|path| {
|
||||||
|
subst::substitute(&path, &task_variables.0)
|
||||||
|
.map(Into::into)
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
.or(cwd);
|
.or(cwd);
|
||||||
let mut definition_env = self.definition.env.clone();
|
let mut definition_env = self.definition.env.clone();
|
||||||
definition_env.extend(env);
|
definition_env.extend(task_variables.0);
|
||||||
Some(SpawnInTerminal {
|
Some(SpawnInTerminal {
|
||||||
id: self.id.clone(),
|
id: self.id.clone(),
|
||||||
cwd,
|
cwd,
|
||||||
|
@ -58,15 +87,15 @@ impl Task for StaticTask {
|
||||||
|
|
||||||
/// The source of tasks defined in a tasks config file.
|
/// The source of tasks defined in a tasks config file.
|
||||||
pub struct StaticSource {
|
pub struct StaticSource {
|
||||||
tasks: Vec<StaticTask>,
|
tasks: Vec<Arc<StaticTask>>,
|
||||||
_definitions: Model<TrackedFile<DefinitionProvider>>,
|
_definitions: Model<TrackedFile<TaskDefinitions>>,
|
||||||
_subscription: Subscription,
|
_subscription: Subscription,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Static task definition from the tasks config file.
|
/// Static task definition from the tasks config file.
|
||||||
#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub(crate) struct Definition {
|
pub struct Definition {
|
||||||
/// Human readable name of the task to display in the UI.
|
/// Human readable name of the task to display in the UI.
|
||||||
pub label: String,
|
pub label: String,
|
||||||
/// Executable command to spawn.
|
/// Executable command to spawn.
|
||||||
|
@ -106,9 +135,9 @@ pub enum RevealStrategy {
|
||||||
|
|
||||||
/// A group of Tasks defined in a JSON file.
|
/// A group of Tasks defined in a JSON file.
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct DefinitionProvider(pub(crate) Vec<Definition>);
|
pub struct TaskDefinitions(pub Vec<Definition>);
|
||||||
|
|
||||||
impl DefinitionProvider {
|
impl TaskDefinitions {
|
||||||
/// Generates JSON schema of Tasks JSON definition format.
|
/// Generates JSON schema of Tasks JSON definition format.
|
||||||
pub fn generate_json_schema() -> serde_json_lenient::Value {
|
pub fn generate_json_schema() -> serde_json_lenient::Value {
|
||||||
let schema = SchemaSettings::draft07()
|
let schema = SchemaSettings::draft07()
|
||||||
|
@ -206,7 +235,7 @@ impl StaticSource {
|
||||||
/// Initializes the static source, reacting on tasks config changes.
|
/// Initializes the static source, reacting on tasks config changes.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
id_base: impl Into<Cow<'static, str>>,
|
id_base: impl Into<Cow<'static, str>>,
|
||||||
definitions: Model<TrackedFile<DefinitionProvider>>,
|
definitions: Model<TrackedFile<TaskDefinitions>>,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> Model<Box<dyn TaskSource>> {
|
) -> Model<Box<dyn TaskSource>> {
|
||||||
cx.new_model(|cx| {
|
cx.new_model(|cx| {
|
||||||
|
@ -222,10 +251,7 @@ impl StaticSource {
|
||||||
.clone()
|
.clone()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, definition)| StaticTask {
|
.map(|(i, definition)| StaticTask::new(definition, (&id_base, i)))
|
||||||
id: TaskId(format!("static_{id_base}_{i}_{}", definition.label)),
|
|
||||||
definition,
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
@ -247,9 +273,8 @@ impl TaskSource for StaticSource {
|
||||||
_: &mut ModelContext<Box<dyn TaskSource>>,
|
_: &mut ModelContext<Box<dyn TaskSource>>,
|
||||||
) -> Vec<Arc<dyn Task>> {
|
) -> Vec<Arc<dyn Task>> {
|
||||||
self.tasks
|
self.tasks
|
||||||
.clone()
|
.iter()
|
||||||
.into_iter()
|
.map(|task| task.clone() as Arc<dyn Task>)
|
||||||
.map(|task| Arc::new(task) as Arc<dyn Task>)
|
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ use collections::HashMap;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
use crate::static_source::{Definition, DefinitionProvider};
|
use crate::static_source::{Definition, TaskDefinitions};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
@ -124,7 +124,7 @@ pub struct VsCodeTaskFile {
|
||||||
tasks: Vec<VsCodeTaskDefinition>,
|
tasks: Vec<VsCodeTaskDefinition>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<VsCodeTaskFile> for DefinitionProvider {
|
impl TryFrom<VsCodeTaskFile> for TaskDefinitions {
|
||||||
type Error = anyhow::Error;
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
fn try_from(value: VsCodeTaskFile) -> Result<Self, Self::Error> {
|
fn try_from(value: VsCodeTaskFile) -> Result<Self, Self::Error> {
|
||||||
|
@ -148,7 +148,7 @@ mod tests {
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
static_source::{Definition, DefinitionProvider},
|
static_source::{Definition, TaskDefinitions},
|
||||||
vscode_format::{Command, VsCodeTaskDefinition},
|
vscode_format::{Command, VsCodeTaskDefinition},
|
||||||
VsCodeTaskFile,
|
VsCodeTaskFile,
|
||||||
};
|
};
|
||||||
|
@ -279,7 +279,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
let tasks: DefinitionProvider = vscode_definitions.try_into().unwrap();
|
let tasks: TaskDefinitions = vscode_definitions.try_into().unwrap();
|
||||||
assert_eq!(tasks.0, expected);
|
assert_eq!(tasks.0, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,7 +380,7 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
let tasks: DefinitionProvider = vscode_definitions.try_into().unwrap();
|
let tasks: TaskDefinitions = vscode_definitions.try_into().unwrap();
|
||||||
assert_eq!(tasks.0, expected);
|
assert_eq!(tasks.0, expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use std::{collections::HashMap, path::PathBuf};
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use gpui::{AppContext, ViewContext, WindowContext};
|
use gpui::{AppContext, ViewContext, WindowContext};
|
||||||
use language::Point;
|
use language::Point;
|
||||||
use modal::{Spawn, TasksModal};
|
use modal::{Spawn, TasksModal};
|
||||||
use project::{Location, WorktreeId};
|
use project::{Location, WorktreeId};
|
||||||
use task::{Task, TaskContext};
|
use task::{Task, TaskContext, TaskVariables};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
@ -156,40 +156,37 @@ fn task_context(
|
||||||
|
|
||||||
let selected_text = buffer.read(cx).chars_for_range(selection_range).collect();
|
let selected_text = buffer.read(cx).chars_for_range(selection_range).collect();
|
||||||
|
|
||||||
let mut env = HashMap::from_iter([
|
let mut task_variables = TaskVariables::from_iter([
|
||||||
("ZED_ROW".into(), row.to_string()),
|
("ZED_ROW".into(), row.to_string()),
|
||||||
("ZED_COLUMN".into(), column.to_string()),
|
("ZED_COLUMN".into(), column.to_string()),
|
||||||
("ZED_SELECTED_TEXT".into(), selected_text),
|
("ZED_SELECTED_TEXT".into(), selected_text),
|
||||||
]);
|
]);
|
||||||
if let Some(path) = current_file {
|
if let Some(path) = current_file {
|
||||||
env.insert("ZED_FILE".into(), path);
|
task_variables.0.insert("ZED_FILE".into(), path);
|
||||||
}
|
}
|
||||||
if let Some(worktree_path) = worktree_path {
|
if let Some(worktree_path) = worktree_path {
|
||||||
env.insert("ZED_WORKTREE_ROOT".into(), worktree_path);
|
task_variables
|
||||||
|
.0
|
||||||
|
.insert("ZED_WORKTREE_ROOT".into(), worktree_path);
|
||||||
}
|
}
|
||||||
if let Some(language_context) = context {
|
if let Some(language_context) = context {
|
||||||
if let Some(symbol) = language_context.symbol {
|
task_variables.0.extend(language_context.0);
|
||||||
env.insert("ZED_SYMBOL".into(), symbol);
|
|
||||||
}
|
|
||||||
if let Some(symbol) = language_context.package {
|
|
||||||
env.insert("ZED_PACKAGE".into(), symbol);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(TaskContext {
|
Some(TaskContext {
|
||||||
cwd: cwd.clone(),
|
cwd: cwd.clone(),
|
||||||
env,
|
task_variables,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})()
|
})()
|
||||||
.unwrap_or_else(|| TaskContext {
|
.unwrap_or_else(|| TaskContext {
|
||||||
cwd,
|
cwd,
|
||||||
env: Default::default(),
|
task_variables: Default::default(),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
TaskContext {
|
TaskContext {
|
||||||
cwd,
|
cwd,
|
||||||
env: Default::default(),
|
task_variables: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -248,14 +245,14 @@ fn task_cwd(workspace: &Workspace, cx: &mut WindowContext) -> anyhow::Result<Opt
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::sync::Arc;
|
||||||
|
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use gpui::{Entity, TestAppContext};
|
use gpui::{Entity, TestAppContext};
|
||||||
use language::{DefaultContextProvider, Language, LanguageConfig};
|
use language::{Language, LanguageConfig, SymbolContextProvider};
|
||||||
use project::{FakeFs, Project, TaskSourceKind};
|
use project::{FakeFs, Project, TaskSourceKind};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use task::{oneshot_source::OneshotSource, TaskContext};
|
use task::{oneshot_source::OneshotSource, TaskContext, TaskVariables};
|
||||||
use ui::VisualContext;
|
use ui::VisualContext;
|
||||||
use workspace::{AppState, Workspace};
|
use workspace::{AppState, Workspace};
|
||||||
|
|
||||||
|
@ -302,7 +299,7 @@ mod tests {
|
||||||
name: (_) @name) @item"#,
|
name: (_) @name) @item"#,
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.with_context_provider(Some(Arc::new(DefaultContextProvider))),
|
.with_context_provider(Some(Arc::new(SymbolContextProvider))),
|
||||||
);
|
);
|
||||||
|
|
||||||
let typescript_language = Arc::new(
|
let typescript_language = Arc::new(
|
||||||
|
@ -320,7 +317,7 @@ mod tests {
|
||||||
")" @context)) @item"#,
|
")" @context)) @item"#,
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.with_context_provider(Some(Arc::new(DefaultContextProvider))),
|
.with_context_provider(Some(Arc::new(SymbolContextProvider))),
|
||||||
);
|
);
|
||||||
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||||
project.update(cx, |project, cx| {
|
project.update(cx, |project, cx| {
|
||||||
|
@ -362,7 +359,7 @@ mod tests {
|
||||||
task_context(this, task_cwd(this, cx).unwrap(), cx),
|
task_context(this, task_cwd(this, cx).unwrap(), cx),
|
||||||
TaskContext {
|
TaskContext {
|
||||||
cwd: Some("/dir".into()),
|
cwd: Some("/dir".into()),
|
||||||
env: HashMap::from_iter([
|
task_variables: TaskVariables::from_iter([
|
||||||
("ZED_FILE".into(), "/dir/rust/b.rs".into()),
|
("ZED_FILE".into(), "/dir/rust/b.rs".into()),
|
||||||
("ZED_WORKTREE_ROOT".into(), "/dir".into()),
|
("ZED_WORKTREE_ROOT".into(), "/dir".into()),
|
||||||
("ZED_ROW".into(), "1".into()),
|
("ZED_ROW".into(), "1".into()),
|
||||||
|
@ -379,7 +376,7 @@ mod tests {
|
||||||
task_context(this, task_cwd(this, cx).unwrap(), cx),
|
task_context(this, task_cwd(this, cx).unwrap(), cx),
|
||||||
TaskContext {
|
TaskContext {
|
||||||
cwd: Some("/dir".into()),
|
cwd: Some("/dir".into()),
|
||||||
env: HashMap::from_iter([
|
task_variables: TaskVariables::from_iter([
|
||||||
("ZED_FILE".into(), "/dir/rust/b.rs".into()),
|
("ZED_FILE".into(), "/dir/rust/b.rs".into()),
|
||||||
("ZED_WORKTREE_ROOT".into(), "/dir".into()),
|
("ZED_WORKTREE_ROOT".into(), "/dir".into()),
|
||||||
("ZED_SYMBOL".into(), "this_is_a_rust_file".into()),
|
("ZED_SYMBOL".into(), "this_is_a_rust_file".into()),
|
||||||
|
@ -396,7 +393,7 @@ mod tests {
|
||||||
task_context(this, task_cwd(this, cx).unwrap(), cx),
|
task_context(this, task_cwd(this, cx).unwrap(), cx),
|
||||||
TaskContext {
|
TaskContext {
|
||||||
cwd: Some("/dir".into()),
|
cwd: Some("/dir".into()),
|
||||||
env: HashMap::from_iter([
|
task_variables: TaskVariables::from_iter([
|
||||||
("ZED_FILE".into(), "/dir/a.ts".into()),
|
("ZED_FILE".into(), "/dir/a.ts".into()),
|
||||||
("ZED_WORKTREE_ROOT".into(), "/dir".into()),
|
("ZED_WORKTREE_ROOT".into(), "/dir".into()),
|
||||||
("ZED_SYMBOL".into(), "this_is_a_test".into()),
|
("ZED_SYMBOL".into(), "this_is_a_test".into()),
|
||||||
|
|
|
@ -45,7 +45,7 @@ impl_actions!(task, [Rerun, Spawn]);
|
||||||
/// A modal used to spawn new tasks.
|
/// A modal used to spawn new tasks.
|
||||||
pub(crate) struct TasksModalDelegate {
|
pub(crate) struct TasksModalDelegate {
|
||||||
inventory: Model<Inventory>,
|
inventory: Model<Inventory>,
|
||||||
candidates: Vec<(TaskSourceKind, Arc<dyn Task>)>,
|
candidates: Option<Vec<(TaskSourceKind, Arc<dyn Task>)>>,
|
||||||
matches: Vec<StringMatch>,
|
matches: Vec<StringMatch>,
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
|
@ -62,7 +62,7 @@ impl TasksModalDelegate {
|
||||||
Self {
|
Self {
|
||||||
inventory,
|
inventory,
|
||||||
workspace,
|
workspace,
|
||||||
candidates: Vec::new(),
|
candidates: None,
|
||||||
matches: Vec::new(),
|
matches: Vec::new(),
|
||||||
selected_index: 0,
|
selected_index: 0,
|
||||||
prompt: String::default(),
|
prompt: String::default(),
|
||||||
|
@ -84,10 +84,10 @@ impl TasksModalDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn active_item_path(
|
fn active_item_path(
|
||||||
&mut self,
|
workspace: &WeakView<Workspace>,
|
||||||
cx: &mut ViewContext<'_, Picker<Self>>,
|
cx: &mut ViewContext<'_, Picker<Self>>,
|
||||||
) -> Option<(PathBuf, ProjectPath)> {
|
) -> Option<(PathBuf, ProjectPath)> {
|
||||||
let workspace = self.workspace.upgrade()?.read(cx);
|
let workspace = workspace.upgrade()?.read(cx);
|
||||||
let project = workspace.project().read(cx);
|
let project = workspace.project().read(cx);
|
||||||
let active_item = workspace.active_item(cx)?;
|
let active_item = workspace.active_item(cx)?;
|
||||||
active_item.project_path(cx).and_then(|project_path| {
|
active_item.project_path(cx).and_then(|project_path| {
|
||||||
|
@ -183,19 +183,20 @@ impl PickerDelegate for TasksModalDelegate {
|
||||||
cx.spawn(move |picker, mut cx| async move {
|
cx.spawn(move |picker, mut cx| async move {
|
||||||
let Some(candidates) = picker
|
let Some(candidates) = picker
|
||||||
.update(&mut cx, |picker, cx| {
|
.update(&mut cx, |picker, cx| {
|
||||||
let (path, worktree) = match picker.delegate.active_item_path(cx) {
|
let candidates = picker.delegate.candidates.get_or_insert_with(|| {
|
||||||
Some((abs_path, project_path)) => {
|
let (path, worktree) =
|
||||||
(Some(abs_path), Some(project_path.worktree_id))
|
match Self::active_item_path(&picker.delegate.workspace, cx) {
|
||||||
}
|
Some((abs_path, project_path)) => {
|
||||||
None => (None, None),
|
(Some(abs_path), Some(project_path.worktree_id))
|
||||||
};
|
}
|
||||||
picker.delegate.candidates =
|
None => (None, None),
|
||||||
|
};
|
||||||
picker.delegate.inventory.update(cx, |inventory, cx| {
|
picker.delegate.inventory.update(cx, |inventory, cx| {
|
||||||
inventory.list_tasks(path.as_deref(), worktree, true, cx)
|
inventory.list_tasks(path.as_deref(), worktree, true, cx)
|
||||||
});
|
})
|
||||||
picker
|
});
|
||||||
.delegate
|
|
||||||
.candidates
|
candidates
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(index, (_, candidate))| StringMatchCandidate {
|
.map(|(index, (_, candidate))| StringMatchCandidate {
|
||||||
|
@ -244,10 +245,14 @@ impl PickerDelegate for TasksModalDelegate {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.matches.get(current_match_index).map(|current_match| {
|
self.matches
|
||||||
let ix = current_match.candidate_id;
|
.get(current_match_index)
|
||||||
self.candidates[ix].1.clone()
|
.and_then(|current_match| {
|
||||||
})
|
let ix = current_match.candidate_id;
|
||||||
|
self.candidates
|
||||||
|
.as_ref()
|
||||||
|
.map(|candidates| candidates[ix].1.clone())
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(task) = task else {
|
let Some(task) = task else {
|
||||||
|
@ -272,10 +277,12 @@ impl PickerDelegate for TasksModalDelegate {
|
||||||
selected: bool,
|
selected: bool,
|
||||||
cx: &mut ViewContext<picker::Picker<Self>>,
|
cx: &mut ViewContext<picker::Picker<Self>>,
|
||||||
) -> Option<Self::ListItem> {
|
) -> Option<Self::ListItem> {
|
||||||
|
let candidates = self.candidates.as_ref()?;
|
||||||
let hit = &self.matches[ix];
|
let hit = &self.matches[ix];
|
||||||
let (source_kind, _) = &self.candidates[hit.candidate_id];
|
let (source_kind, _) = &candidates[hit.candidate_id];
|
||||||
let details = match source_kind {
|
let details = match source_kind {
|
||||||
TaskSourceKind::UserInput => "user input".to_string(),
|
TaskSourceKind::UserInput => "user input".to_string(),
|
||||||
|
TaskSourceKind::Buffer => "language extension".to_string(),
|
||||||
TaskSourceKind::Worktree { abs_path, .. } | TaskSourceKind::AbsPath(abs_path) => {
|
TaskSourceKind::Worktree { abs_path, .. } | TaskSourceKind::AbsPath(abs_path) => {
|
||||||
abs_path.compact().to_string_lossy().to_string()
|
abs_path.compact().to_string_lossy().to_string()
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ pub use open_listener::*;
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
use assets::Assets;
|
use assets::Assets;
|
||||||
use futures::{channel::mpsc, select_biased, StreamExt};
|
use futures::{channel::mpsc, select_biased, StreamExt};
|
||||||
|
use language::LanguageSource;
|
||||||
use project::TaskSourceKind;
|
use project::TaskSourceKind;
|
||||||
use project_panel::ProjectPanel;
|
use project_panel::ProjectPanel;
|
||||||
use quick_action_bar::QuickActionBar;
|
use quick_action_bar::QuickActionBar;
|
||||||
|
@ -33,6 +34,7 @@ use task::{
|
||||||
oneshot_source::OneshotSource,
|
oneshot_source::OneshotSource,
|
||||||
static_source::{StaticSource, TrackedFile},
|
static_source::{StaticSource, TrackedFile},
|
||||||
};
|
};
|
||||||
|
|
||||||
use terminal_view::terminal_panel::{self, TerminalPanel};
|
use terminal_view::terminal_panel::{self, TerminalPanel};
|
||||||
use util::{
|
use util::{
|
||||||
asset_str,
|
asset_str,
|
||||||
|
@ -177,6 +179,11 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
inventory.add_source(
|
||||||
|
TaskSourceKind::Buffer,
|
||||||
|
|cx| LanguageSource::new(app_state.languages.clone(), cx),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue