Task::spawn now takes an optional task name as an argument.

Piotr Osiewicz created

If it is not set, we fall back to opening a modal. This allows user to spawn tasks via keybind.

Change summary

crates/tasks_ui/src/lib.rs   | 60 +++++++++++++++++++++++++++++++------
crates/tasks_ui/src/modal.rs | 21 +++++++++---
2 files changed, 65 insertions(+), 16 deletions(-)

Detailed changes

crates/tasks_ui/src/lib.rs 🔗

@@ -3,7 +3,7 @@ use std::{collections::HashMap, path::PathBuf};
 use editor::Editor;
 use gpui::{AppContext, ViewContext, WindowContext};
 use language::Point;
-use modal::TasksModal;
+use modal::{Spawn, TasksModal};
 use project::{Location, WorktreeId};
 use task::{Task, TaskContext};
 use util::ResultExt;
@@ -15,15 +15,7 @@ pub fn init(cx: &mut AppContext) {
     cx.observe_new_views(
         |workspace: &mut Workspace, _: &mut ViewContext<Workspace>| {
             workspace
-                .register_action(|workspace, _: &modal::Spawn, cx| {
-                    let inventory = workspace.project().read(cx).task_inventory().clone();
-                    let workspace_handle = workspace.weak_handle();
-                    let cwd = task_cwd(workspace, cx).log_err().flatten();
-                    let task_context = task_context(workspace, cwd, cx);
-                    workspace.toggle_modal(cx, |cx| {
-                        TasksModal::new(inventory, task_context, workspace_handle, cx)
-                    })
-                })
+                .register_action(spawn_task_or_modal)
                 .register_action(move |workspace, action: &modal::Rerun, cx| {
                     if let Some((task, old_context)) =
                         workspace.project().update(cx, |project, cx| {
@@ -47,6 +39,54 @@ pub fn init(cx: &mut AppContext) {
     .detach();
 }
 
+fn spawn_task_or_modal(workspace: &mut Workspace, action: &Spawn, cx: &mut ViewContext<Workspace>) {
+    let inventory = workspace.project().read(cx).task_inventory().clone();
+    let workspace_handle = workspace.weak_handle();
+    let cwd = task_cwd(workspace, cx).log_err().flatten();
+    let task_context = task_context(workspace, cwd, cx);
+    if let Some(name) = action.task_name.clone() {
+        // Do not actually show the modal.
+        spawn_task_with_name(name.clone(), cx);
+    } else {
+        workspace.toggle_modal(cx, |cx| {
+            TasksModal::new(inventory, task_context, workspace_handle, cx)
+        })
+    }
+}
+
+fn spawn_task_with_name(name: String, cx: &mut ViewContext<Workspace>) {
+    cx.spawn(|workspace, mut cx| async move {
+        let did_spawn = workspace
+            .update(&mut cx, |this, cx| {
+                let active_item = this
+                    .active_item(cx)
+                    .and_then(|item| item.project_path(cx))
+                    .map(|path| path.worktree_id);
+                let tasks = this.project().update(cx, |project, cx| {
+                    project.task_inventory().update(cx, |inventory, cx| {
+                        inventory.list_tasks(None, active_item, false, cx)
+                    })
+                });
+                let (_, target_task) = tasks.into_iter().find(|(_, task)| task.name() == name)?;
+                let cwd = task_cwd(this, cx).log_err().flatten();
+                let task_context = task_context(this, cwd, cx);
+                schedule_task(this, target_task.as_ref(), task_context, cx);
+                Some(())
+            })
+            .ok()
+            .flatten()
+            .is_some();
+        if !did_spawn {
+            workspace
+                .update(&mut cx, |workspace, cx| {
+                    spawn_task_or_modal(workspace, &Spawn::default(), cx);
+                })
+                .ok();
+        }
+    })
+    .detach();
+}
+
 fn task_context(
     workspace: &Workspace,
     cwd: Option<PathBuf>,

crates/tasks_ui/src/modal.rs 🔗

@@ -2,9 +2,9 @@ use std::{path::PathBuf, sync::Arc};
 
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::{
-    actions, impl_actions, rems, AppContext, DismissEvent, EventEmitter, FocusableView,
-    InteractiveElement, Model, ParentElement, Render, SharedString, Styled, Subscription, View,
-    ViewContext, VisualContext, WeakView,
+    impl_actions, rems, AppContext, DismissEvent, EventEmitter, FocusableView, InteractiveElement,
+    Model, ParentElement, Render, SharedString, Styled, Subscription, View, ViewContext,
+    VisualContext, WeakView,
 };
 use picker::{
     highlighted_match_with_paths::{HighlightedMatchWithPaths, HighlightedText},
@@ -18,7 +18,16 @@ use workspace::{ModalView, Workspace};
 
 use crate::schedule_task;
 use serde::Deserialize;
-actions!(task, [Spawn]);
+
+/// Spawn a task with name or open tasks modal
+#[derive(PartialEq, Clone, Deserialize, Default)]
+pub struct Spawn {
+    #[serde(default)]
+    /// Name of the task to spawn.
+    /// If it is not set, a modal with a list of available tasks is opened instead.
+    /// Defaults to None.
+    pub task_name: Option<String>,
+}
 
 /// Rerun last task
 #[derive(PartialEq, Clone, Deserialize, Default)]
@@ -31,7 +40,7 @@ pub struct Rerun {
     pub reevaluate_context: bool,
 }
 
-impl_actions!(task, [Rerun]);
+impl_actions!(task, [Rerun, Spawn]);
 
 /// A modal used to spawn new tasks.
 pub(crate) struct TasksModalDelegate {
@@ -426,7 +435,7 @@ mod tests {
         workspace: &View<Workspace>,
         cx: &mut VisualTestContext,
     ) -> View<Picker<TasksModalDelegate>> {
-        cx.dispatch_action(crate::modal::Spawn);
+        cx.dispatch_action(crate::modal::Spawn::default());
         workspace.update(cx, |workspace, cx| {
             workspace
                 .active_modal::<TasksModal>(cx)