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
|
@ -1,11 +1,11 @@
|
|||
use std::{collections::HashMap, path::PathBuf};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use editor::Editor;
|
||||
use gpui::{AppContext, ViewContext, WindowContext};
|
||||
use language::Point;
|
||||
use modal::{Spawn, TasksModal};
|
||||
use project::{Location, WorktreeId};
|
||||
use task::{Task, TaskContext};
|
||||
use task::{Task, TaskContext, TaskVariables};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
|
@ -156,40 +156,37 @@ fn task_context(
|
|||
|
||||
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_COLUMN".into(), column.to_string()),
|
||||
("ZED_SELECTED_TEXT".into(), selected_text),
|
||||
]);
|
||||
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 {
|
||||
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(symbol) = language_context.symbol {
|
||||
env.insert("ZED_SYMBOL".into(), symbol);
|
||||
}
|
||||
if let Some(symbol) = language_context.package {
|
||||
env.insert("ZED_PACKAGE".into(), symbol);
|
||||
}
|
||||
task_variables.0.extend(language_context.0);
|
||||
}
|
||||
|
||||
Some(TaskContext {
|
||||
cwd: cwd.clone(),
|
||||
env,
|
||||
task_variables,
|
||||
})
|
||||
})
|
||||
})()
|
||||
.unwrap_or_else(|| TaskContext {
|
||||
cwd,
|
||||
env: Default::default(),
|
||||
task_variables: Default::default(),
|
||||
})
|
||||
} else {
|
||||
TaskContext {
|
||||
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)]
|
||||
mod tests {
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
|
||||
use editor::Editor;
|
||||
use gpui::{Entity, TestAppContext};
|
||||
use language::{DefaultContextProvider, Language, LanguageConfig};
|
||||
use language::{Language, LanguageConfig, SymbolContextProvider};
|
||||
use project::{FakeFs, Project, TaskSourceKind};
|
||||
use serde_json::json;
|
||||
use task::{oneshot_source::OneshotSource, TaskContext};
|
||||
use task::{oneshot_source::OneshotSource, TaskContext, TaskVariables};
|
||||
use ui::VisualContext;
|
||||
use workspace::{AppState, Workspace};
|
||||
|
||||
|
@ -302,7 +299,7 @@ mod tests {
|
|||
name: (_) @name) @item"#,
|
||||
)
|
||||
.unwrap()
|
||||
.with_context_provider(Some(Arc::new(DefaultContextProvider))),
|
||||
.with_context_provider(Some(Arc::new(SymbolContextProvider))),
|
||||
);
|
||||
|
||||
let typescript_language = Arc::new(
|
||||
|
@ -320,7 +317,7 @@ mod tests {
|
|||
")" @context)) @item"#,
|
||||
)
|
||||
.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;
|
||||
project.update(cx, |project, cx| {
|
||||
|
@ -362,7 +359,7 @@ mod tests {
|
|||
task_context(this, task_cwd(this, cx).unwrap(), cx),
|
||||
TaskContext {
|
||||
cwd: Some("/dir".into()),
|
||||
env: HashMap::from_iter([
|
||||
task_variables: TaskVariables::from_iter([
|
||||
("ZED_FILE".into(), "/dir/rust/b.rs".into()),
|
||||
("ZED_WORKTREE_ROOT".into(), "/dir".into()),
|
||||
("ZED_ROW".into(), "1".into()),
|
||||
|
@ -379,7 +376,7 @@ mod tests {
|
|||
task_context(this, task_cwd(this, cx).unwrap(), cx),
|
||||
TaskContext {
|
||||
cwd: Some("/dir".into()),
|
||||
env: HashMap::from_iter([
|
||||
task_variables: TaskVariables::from_iter([
|
||||
("ZED_FILE".into(), "/dir/rust/b.rs".into()),
|
||||
("ZED_WORKTREE_ROOT".into(), "/dir".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),
|
||||
TaskContext {
|
||||
cwd: Some("/dir".into()),
|
||||
env: HashMap::from_iter([
|
||||
task_variables: TaskVariables::from_iter([
|
||||
("ZED_FILE".into(), "/dir/a.ts".into()),
|
||||
("ZED_WORKTREE_ROOT".into(), "/dir".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.
|
||||
pub(crate) struct TasksModalDelegate {
|
||||
inventory: Model<Inventory>,
|
||||
candidates: Vec<(TaskSourceKind, Arc<dyn Task>)>,
|
||||
candidates: Option<Vec<(TaskSourceKind, Arc<dyn Task>)>>,
|
||||
matches: Vec<StringMatch>,
|
||||
selected_index: usize,
|
||||
workspace: WeakView<Workspace>,
|
||||
|
@ -62,7 +62,7 @@ impl TasksModalDelegate {
|
|||
Self {
|
||||
inventory,
|
||||
workspace,
|
||||
candidates: Vec::new(),
|
||||
candidates: None,
|
||||
matches: Vec::new(),
|
||||
selected_index: 0,
|
||||
prompt: String::default(),
|
||||
|
@ -84,10 +84,10 @@ impl TasksModalDelegate {
|
|||
}
|
||||
|
||||
fn active_item_path(
|
||||
&mut self,
|
||||
workspace: &WeakView<Workspace>,
|
||||
cx: &mut ViewContext<'_, Picker<Self>>,
|
||||
) -> Option<(PathBuf, ProjectPath)> {
|
||||
let workspace = self.workspace.upgrade()?.read(cx);
|
||||
let workspace = workspace.upgrade()?.read(cx);
|
||||
let project = workspace.project().read(cx);
|
||||
let active_item = workspace.active_item(cx)?;
|
||||
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 {
|
||||
let Some(candidates) = picker
|
||||
.update(&mut cx, |picker, cx| {
|
||||
let (path, worktree) = match picker.delegate.active_item_path(cx) {
|
||||
Some((abs_path, project_path)) => {
|
||||
(Some(abs_path), Some(project_path.worktree_id))
|
||||
}
|
||||
None => (None, None),
|
||||
};
|
||||
picker.delegate.candidates =
|
||||
let candidates = picker.delegate.candidates.get_or_insert_with(|| {
|
||||
let (path, worktree) =
|
||||
match Self::active_item_path(&picker.delegate.workspace, cx) {
|
||||
Some((abs_path, project_path)) => {
|
||||
(Some(abs_path), Some(project_path.worktree_id))
|
||||
}
|
||||
None => (None, None),
|
||||
};
|
||||
picker.delegate.inventory.update(cx, |inventory, cx| {
|
||||
inventory.list_tasks(path.as_deref(), worktree, true, cx)
|
||||
});
|
||||
picker
|
||||
.delegate
|
||||
.candidates
|
||||
})
|
||||
});
|
||||
|
||||
candidates
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, (_, candidate))| StringMatchCandidate {
|
||||
|
@ -244,10 +245,14 @@ impl PickerDelegate for TasksModalDelegate {
|
|||
None
|
||||
}
|
||||
} else {
|
||||
self.matches.get(current_match_index).map(|current_match| {
|
||||
let ix = current_match.candidate_id;
|
||||
self.candidates[ix].1.clone()
|
||||
})
|
||||
self.matches
|
||||
.get(current_match_index)
|
||||
.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 {
|
||||
|
@ -272,10 +277,12 @@ impl PickerDelegate for TasksModalDelegate {
|
|||
selected: bool,
|
||||
cx: &mut ViewContext<picker::Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let candidates = self.candidates.as_ref()?;
|
||||
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 {
|
||||
TaskSourceKind::UserInput => "user input".to_string(),
|
||||
TaskSourceKind::Buffer => "language extension".to_string(),
|
||||
TaskSourceKind::Worktree { abs_path, .. } | TaskSourceKind::AbsPath(abs_path) => {
|
||||
abs_path.compact().to_string_lossy().to_string()
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue