Show tasks in debugger: start (#30584)

Conrad Irwin , Cole , and Anthony created

- **Show relevant tasks in debugger: start**
- **Add history too**

Closes #ISSUE

Release Notes:

- N/A

---------

Co-authored-by: Cole <cole@zed.dev>
Co-authored-by: Anthony <anthony@zed.dev>

Change summary

Cargo.lock                                    |   1 
crates/debugger_ui/Cargo.toml                 |   1 
crates/debugger_ui/src/debugger_panel.rs      |  12 
crates/debugger_ui/src/new_session_modal.rs   | 272 +++++++++++---------
crates/editor/src/code_context_menus.rs       |   5 
crates/editor/src/editor.rs                   |   4 
crates/project/src/debugger/locators/cargo.rs |   5 
crates/project/src/task_inventory.rs          | 133 +++++++--
8 files changed, 273 insertions(+), 160 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -4167,6 +4167,7 @@ dependencies = [
  "editor",
  "env_logger 0.11.8",
  "feature_flags",
+ "file_icons",
  "futures 0.3.31",
  "fuzzy",
  "gpui",

crates/debugger_ui/Cargo.toml 🔗

@@ -36,6 +36,7 @@ dap_adapters = { workspace = true, optional = true }
 db.workspace = true
 editor.workspace = true
 feature_flags.workspace = true
+file_icons.workspace = true
 futures.workspace = true
 fuzzy.workspace = true
 gpui.workspace = true

crates/debugger_ui/src/debugger_panel.rs 🔗

@@ -218,6 +218,18 @@ impl DebugPanel {
                 cx,
             )
         });
+        if let Some(inventory) = self
+            .project
+            .read(cx)
+            .task_store()
+            .read(cx)
+            .task_inventory()
+            .cloned()
+        {
+            inventory.update(cx, |inventory, _| {
+                inventory.scenario_scheduled(scenario.clone());
+            })
+        }
         let task = cx.spawn_in(window, {
             let session = session.clone();
             async move |this, cx| {

crates/debugger_ui/src/new_session_modal.rs 🔗

@@ -3,7 +3,6 @@ use std::{
     borrow::Cow,
     ops::Not,
     path::{Path, PathBuf},
-    sync::Arc,
     time::Duration,
     usize,
 };
@@ -50,7 +49,6 @@ pub(super) struct NewSessionModal {
     attach_mode: Entity<AttachMode>,
     custom_mode: Entity<CustomMode>,
     debugger: Option<DebugAdapterName>,
-    task_contexts: Arc<TaskContexts>,
     save_scenario_state: Option<SaveScenarioState>,
     _subscriptions: [Subscription; 2],
 }
@@ -85,14 +83,6 @@ impl NewSessionModal {
         let task_store = workspace.project().read(cx).task_store().clone();
 
         cx.spawn_in(window, async move |workspace, cx| {
-            let task_contexts = Arc::from(
-                workspace
-                    .update_in(cx, |workspace, window, cx| {
-                        tasks_ui::task_contexts(workspace, window, cx)
-                    })?
-                    .await,
-            );
-
             workspace.update_in(cx, |workspace, window, cx| {
                 let workspace_handle = workspace.weak_handle();
                 workspace.toggle_modal(window, cx, |window, cx| {
@@ -100,12 +90,7 @@ impl NewSessionModal {
 
                     let launch_picker = cx.new(|cx| {
                         Picker::uniform_list(
-                            DebugScenarioDelegate::new(
-                                debug_panel.downgrade(),
-                                workspace_handle.clone(),
-                                task_store,
-                                task_contexts.clone(),
-                            ),
+                            DebugScenarioDelegate::new(debug_panel.downgrade(), task_store),
                             window,
                             cx,
                         )
@@ -124,11 +109,38 @@ impl NewSessionModal {
                         ),
                     ];
 
-                    let active_cwd = task_contexts
-                        .active_context()
-                        .and_then(|context| context.cwd.clone());
+                    let custom_mode = CustomMode::new(None, window, cx);
+
+                    cx.spawn_in(window, {
+                        let workspace_handle = workspace_handle.clone();
+                        async move |this, cx| {
+                            let task_contexts = workspace_handle
+                                .update_in(cx, |workspace, window, cx| {
+                                    tasks_ui::task_contexts(workspace, window, cx)
+                                })?
+                                .await;
+
+                            this.update_in(cx, |this, window, cx| {
+                                if let Some(active_cwd) = task_contexts
+                                    .active_context()
+                                    .and_then(|context| context.cwd.clone())
+                                {
+                                    this.custom_mode.update(cx, |custom, cx| {
+                                        custom.load(active_cwd, window, cx);
+                                    });
+                                }
 
-                    let custom_mode = CustomMode::new(None, active_cwd, window, cx);
+                                this.launch_picker.update(cx, |picker, cx| {
+                                    picker
+                                        .delegate
+                                        .task_contexts_loaded(task_contexts, window, cx);
+                                    picker.refresh(window, cx);
+                                    cx.notify();
+                                });
+                            })
+                        }
+                    })
+                    .detach();
 
                     Self {
                         launch_picker,
@@ -138,7 +150,6 @@ impl NewSessionModal {
                         mode: NewSessionMode::Launch,
                         debug_panel: debug_panel.downgrade(),
                         workspace: workspace_handle,
-                        task_contexts,
                         save_scenario_state: None,
                         _subscriptions,
                     }
@@ -205,8 +216,6 @@ impl NewSessionModal {
 
     fn start_new_session(&self, window: &mut Window, cx: &mut Context<Self>) {
         let Some(debugger) = self.debugger.as_ref() else {
-            // todo(debugger): show in UI.
-            log::error!("No debugger selected");
             return;
         };
 
@@ -223,10 +232,12 @@ impl NewSessionModal {
         };
 
         let debug_panel = self.debug_panel.clone();
-        let task_contexts = self.task_contexts.clone();
+        let Some(task_contexts) = self.task_contexts(cx) else {
+            return;
+        };
+        let task_context = task_contexts.active_context().cloned().unwrap_or_default();
+        let worktree_id = task_contexts.worktree();
         cx.spawn_in(window, async move |this, cx| {
-            let task_context = task_contexts.active_context().cloned().unwrap_or_default();
-            let worktree_id = task_contexts.worktree();
             debug_panel.update_in(cx, |debug_panel, window, cx| {
                 debug_panel.start_session(config, task_context, None, worktree_id, window, cx)
             })?;
@@ -260,6 +271,11 @@ impl NewSessionModal {
             cx.notify();
         })
     }
+
+    fn task_contexts<'a>(&self, cx: &'a mut Context<Self>) -> Option<&'a TaskContexts> {
+        self.launch_picker.read(cx).delegate.task_contexts.as_ref()
+    }
+
     fn adapter_drop_down_menu(
         &mut self,
         window: &mut Window,
@@ -267,15 +283,14 @@ impl NewSessionModal {
     ) -> ui::DropdownMenu {
         let workspace = self.workspace.clone();
         let weak = cx.weak_entity();
-        let active_buffer_language = self
-            .task_contexts
-            .active_item_context
-            .as_ref()
-            .and_then(|item| {
-                item.1
-                    .as_ref()
-                    .and_then(|location| location.buffer.read(cx).language())
-            })
+        let active_buffer = self.task_contexts(cx).and_then(|tc| {
+            tc.active_item_context
+                .as_ref()
+                .and_then(|aic| aic.1.as_ref().map(|l| l.buffer.clone()))
+        });
+
+        let active_buffer_language = active_buffer
+            .and_then(|buffer| buffer.read(cx).language())
             .cloned();
 
         let mut available_adapters = workspace
@@ -515,7 +530,10 @@ impl Render for NewSessionModal {
                                             .debugger
                                             .as_ref()
                                             .and_then(|debugger| this.debug_scenario(&debugger, cx))
-                                            .zip(this.task_contexts.worktree())
+                                            .zip(
+                                                this.task_contexts(cx)
+                                                    .and_then(|tcx| tcx.worktree()),
+                                            )
                                             .and_then(|(scenario, worktree_id)| {
                                                 this.debug_panel
                                                     .update(cx, |panel, cx| {
@@ -715,13 +733,12 @@ pub(super) struct CustomMode {
 impl CustomMode {
     pub(super) fn new(
         past_launch_config: Option<LaunchRequest>,
-        active_cwd: Option<PathBuf>,
         window: &mut Window,
         cx: &mut App,
     ) -> Entity<Self> {
         let (past_program, past_cwd) = past_launch_config
             .map(|config| (Some(config.program), config.cwd))
-            .unwrap_or_else(|| (None, active_cwd));
+            .unwrap_or_else(|| (None, None));
 
         let program = cx.new(|cx| Editor::single_line(window, cx));
         program.update(cx, |this, cx| {
@@ -745,6 +762,14 @@ impl CustomMode {
         })
     }
 
+    fn load(&mut self, cwd: PathBuf, window: &mut Window, cx: &mut App) {
+        self.cwd.update(cx, |editor, cx| {
+            if editor.is_empty(cx) {
+                editor.set_text(cwd.to_string_lossy(), window, cx);
+            }
+        });
+    }
+
     pub(super) fn debug_request(&self, cx: &App) -> task::LaunchRequest {
         let path = self.cwd.read(cx).text(cx);
         if cfg!(windows) {
@@ -894,32 +919,63 @@ impl AttachMode {
 
 pub(super) struct DebugScenarioDelegate {
     task_store: Entity<TaskStore>,
-    candidates: Option<Vec<(TaskSourceKind, DebugScenario)>>,
+    candidates: Vec<(Option<TaskSourceKind>, DebugScenario)>,
     selected_index: usize,
     matches: Vec<StringMatch>,
     prompt: String,
     debug_panel: WeakEntity<DebugPanel>,
-    workspace: WeakEntity<Workspace>,
-    task_contexts: Arc<TaskContexts>,
+    task_contexts: Option<TaskContexts>,
+    divider_index: Option<usize>,
+    last_used_candidate_index: Option<usize>,
 }
 
 impl DebugScenarioDelegate {
-    pub(super) fn new(
-        debug_panel: WeakEntity<DebugPanel>,
-        workspace: WeakEntity<Workspace>,
-        task_store: Entity<TaskStore>,
-        task_contexts: Arc<TaskContexts>,
-    ) -> Self {
+    pub(super) fn new(debug_panel: WeakEntity<DebugPanel>, task_store: Entity<TaskStore>) -> Self {
         Self {
             task_store,
-            candidates: None,
+            candidates: Vec::default(),
             selected_index: 0,
             matches: Vec::new(),
             prompt: String::new(),
             debug_panel,
-            workspace,
-            task_contexts,
+            task_contexts: None,
+            divider_index: None,
+            last_used_candidate_index: None,
+        }
+    }
+
+    pub fn task_contexts_loaded(
+        &mut self,
+        task_contexts: TaskContexts,
+        _window: &mut Window,
+        cx: &mut Context<Picker<Self>>,
+    ) {
+        self.task_contexts = Some(task_contexts);
+
+        let (recent, scenarios) = self
+            .task_store
+            .update(cx, |task_store, cx| {
+                task_store.task_inventory().map(|inventory| {
+                    inventory.update(cx, |inventory, cx| {
+                        inventory.list_debug_scenarios(self.task_contexts.as_ref().unwrap(), cx)
+                    })
+                })
+            })
+            .unwrap_or_default();
+
+        if !recent.is_empty() {
+            self.last_used_candidate_index = Some(recent.len() - 1);
         }
+
+        self.candidates = recent
+            .into_iter()
+            .map(|scenario| (None, scenario))
+            .chain(
+                scenarios
+                    .into_iter()
+                    .map(|(kind, scenario)| (Some(kind), scenario)),
+            )
+            .collect();
     }
 }
 
@@ -954,53 +1010,15 @@ impl PickerDelegate for DebugScenarioDelegate {
         cx: &mut Context<picker::Picker<Self>>,
     ) -> gpui::Task<()> {
         let candidates = self.candidates.clone();
-        let workspace = self.workspace.clone();
-        let task_store = self.task_store.clone();
 
         cx.spawn_in(window, async move |picker, cx| {
-            let candidates: Vec<_> = match &candidates {
-                Some(candidates) => candidates
-                    .into_iter()
-                    .enumerate()
-                    .map(|(index, (_, candidate))| {
-                        StringMatchCandidate::new(index, candidate.label.as_ref())
-                    })
-                    .collect(),
-                None => {
-                    let worktree_ids: Vec<_> = workspace
-                        .update(cx, |this, cx| {
-                            this.visible_worktrees(cx)
-                                .map(|tree| tree.read(cx).id())
-                                .collect()
-                        })
-                        .ok()
-                        .unwrap_or_default();
-
-                    let scenarios: Vec<_> = task_store
-                        .update(cx, |task_store, cx| {
-                            task_store.task_inventory().map(|item| {
-                                item.read(cx).list_debug_scenarios(worktree_ids.into_iter())
-                            })
-                        })
-                        .ok()
-                        .flatten()
-                        .unwrap_or_default();
-
-                    picker
-                        .update(cx, |picker, _| {
-                            picker.delegate.candidates = Some(scenarios.clone());
-                        })
-                        .ok();
-
-                    scenarios
-                        .into_iter()
-                        .enumerate()
-                        .map(|(index, (_, candidate))| {
-                            StringMatchCandidate::new(index, candidate.label.as_ref())
-                        })
-                        .collect()
-                }
-            };
+            let candidates: Vec<_> = candidates
+                .into_iter()
+                .enumerate()
+                .map(|(index, (_, candidate))| {
+                    StringMatchCandidate::new(index, candidate.label.as_ref())
+                })
+                .collect();
 
             let matches = fuzzy::match_strings(
                 &candidates,
@@ -1019,6 +1037,13 @@ impl PickerDelegate for DebugScenarioDelegate {
                     delegate.matches = matches;
                     delegate.prompt = query;
 
+                    delegate.divider_index = delegate.last_used_candidate_index.and_then(|index| {
+                        let index = delegate
+                            .matches
+                            .partition_point(|matching_task| matching_task.candidate_id <= index);
+                        Some(index).and_then(|index| (index != 0).then(|| index - 1))
+                    });
+
                     if delegate.matches.is_empty() {
                         delegate.selected_index = 0;
                     } else {
@@ -1030,34 +1055,34 @@ impl PickerDelegate for DebugScenarioDelegate {
         })
     }
 
+    fn separators_after_indices(&self) -> Vec<usize> {
+        if let Some(i) = self.divider_index {
+            vec![i]
+        } else {
+            Vec::new()
+        }
+    }
+
     fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<picker::Picker<Self>>) {
         let debug_scenario = self
             .matches
             .get(self.selected_index())
-            .and_then(|match_candidate| {
-                self.candidates
-                    .as_ref()
-                    .map(|candidates| candidates[match_candidate.candidate_id].clone())
-            });
+            .and_then(|match_candidate| self.candidates.get(match_candidate.candidate_id).cloned());
 
-        let Some((task_source_kind, debug_scenario)) = debug_scenario else {
+        let Some((_, debug_scenario)) = debug_scenario else {
             return;
         };
 
-        let (task_context, worktree_id) = if let TaskSourceKind::Worktree {
-            id: worktree_id,
-            directory_in_worktree: _,
-            id_base: _,
-        } = task_source_kind
-        {
-            self.task_contexts
-                .task_context_for_worktree_id(worktree_id)
-                .cloned()
-                .map(|context| (context, Some(worktree_id)))
-        } else {
-            None
-        }
-        .unwrap_or_default();
+        let (task_context, worktree_id) = self
+            .task_contexts
+            .as_ref()
+            .and_then(|task_contexts| {
+                Some((
+                    task_contexts.active_context().cloned()?,
+                    task_contexts.worktree(),
+                ))
+            })
+            .unwrap_or_default();
 
         self.debug_panel
             .update(cx, |panel, cx| {
@@ -1087,10 +1112,19 @@ impl PickerDelegate for DebugScenarioDelegate {
             char_count: hit.string.chars().count(),
             color: Color::Default,
         };
-
-        let icon = Icon::new(IconName::FileTree)
-            .color(Color::Muted)
-            .size(ui::IconSize::Small);
+        let task_kind = &self.candidates[hit.candidate_id].0;
+
+        let icon = match task_kind {
+            Some(TaskSourceKind::Lsp(..)) => Some(Icon::new(IconName::Bolt)),
+            Some(TaskSourceKind::UserInput) => Some(Icon::new(IconName::Terminal)),
+            Some(TaskSourceKind::AbsPath { .. }) => Some(Icon::new(IconName::Settings)),
+            Some(TaskSourceKind::Worktree { .. }) => Some(Icon::new(IconName::FileTree)),
+            Some(TaskSourceKind::Language { name }) => file_icons::FileIcons::get(cx)
+                .get_icon_for_type(&name.to_lowercase(), cx)
+                .map(Icon::from_path),
+            None => Some(Icon::new(IconName::HistoryRerun)),
+        }
+        .map(|icon| icon.color(Color::Muted).size(ui::IconSize::Small));
 
         Some(
             ListItem::new(SharedString::from(format!("debug-scenario-selection-{ix}")))

crates/editor/src/code_context_menus.rs 🔗

@@ -1103,6 +1103,7 @@ impl CodeActionsMenu {
                                     this.child(
                                         h_flex()
                                             .overflow_hidden()
+                                            .child("debug: ")
                                             .child(scenario.label.clone())
                                             .when(selected, |this| {
                                                 this.text_color(colors.text_accent)
@@ -1138,7 +1139,9 @@ impl CodeActionsMenu {
                     CodeActionsItem::CodeAction { action, .. } => {
                         action.lsp_action.title().chars().count()
                     }
-                    CodeActionsItem::DebugScenario(scenario) => scenario.label.chars().count(),
+                    CodeActionsItem::DebugScenario(scenario) => {
+                        format!("debug: {}", scenario.label).chars().count()
+                    }
                 })
                 .map(|(ix, _)| ix),
         )

crates/editor/src/editor.rs 🔗

@@ -5331,9 +5331,9 @@ impl Editor {
                                                     .map(SharedString::from)
                                             })?;
 
-                                    dap_store.update(cx, |this, cx| {
+                                    dap_store.update(cx, |dap_store, cx| {
                                         for (_, task) in &resolved_tasks.templates {
-                                            if let Some(scenario) = this
+                                            if let Some(scenario) = dap_store
                                                 .debug_scenario_for_build_task(
                                                     task.original_task().clone(),
                                                     debug_adapter.clone().into(),

crates/project/src/debugger/locators/cargo.rs 🔗

@@ -52,7 +52,7 @@ impl DapLocator for CargoLocator {
         }
         let mut task_template = build_config.clone();
         let cargo_action = task_template.args.first_mut()?;
-        if cargo_action == "check" {
+        if cargo_action == "check" || cargo_action == "clean" {
             return None;
         }
 
@@ -75,10 +75,9 @@ impl DapLocator for CargoLocator {
             }
             _ => {}
         }
-        let label = format!("Debug `{resolved_label}`");
         Some(DebugScenario {
             adapter: adapter.0,
-            label: SharedString::from(label),
+            label: resolved_label.to_string().into(),
             build: Some(BuildTaskDefinition::Template {
                 task_template,
                 locator_name: Some(self.name()),

crates/project/src/task_inventory.rs 🔗

@@ -10,6 +10,7 @@ use std::{
 
 use anyhow::Result;
 use collections::{HashMap, HashSet, VecDeque};
+use dap::DapRegistry;
 use gpui::{App, AppContext as _, Entity, SharedString, Task};
 use itertools::Itertools;
 use language::{
@@ -33,6 +34,7 @@ use crate::{task_store::TaskSettingsLocation, worktree_store::WorktreeStore};
 #[derive(Debug, Default)]
 pub struct Inventory {
     last_scheduled_tasks: VecDeque<(TaskSourceKind, ResolvedTask)>,
+    last_scheduled_scenarios: VecDeque<DebugScenario>,
     templates_from_settings: InventoryFor<TaskTemplate>,
     scenarios_from_settings: InventoryFor<DebugScenario>,
 }
@@ -63,30 +65,28 @@ struct InventoryFor<T> {
 impl<T: InventoryContents> InventoryFor<T> {
     fn worktree_scenarios(
         &self,
-        worktree: Option<WorktreeId>,
+        worktree: WorktreeId,
     ) -> impl '_ + Iterator<Item = (TaskSourceKind, T)> {
-        worktree.into_iter().flat_map(|worktree| {
-            self.worktree
-                .get(&worktree)
-                .into_iter()
-                .flatten()
-                .flat_map(|(directory, templates)| {
-                    templates.iter().map(move |template| (directory, template))
-                })
-                .map(move |(directory, template)| {
-                    (
-                        TaskSourceKind::Worktree {
-                            id: worktree,
-                            directory_in_worktree: directory.to_path_buf(),
-                            id_base: Cow::Owned(format!(
-                                "local worktree {} from directory {directory:?}",
-                                T::LABEL
-                            )),
-                        },
-                        template.clone(),
-                    )
-                })
-        })
+        self.worktree
+            .get(&worktree)
+            .into_iter()
+            .flatten()
+            .flat_map(|(directory, templates)| {
+                templates.iter().map(move |template| (directory, template))
+            })
+            .map(move |(directory, template)| {
+                (
+                    TaskSourceKind::Worktree {
+                        id: worktree,
+                        directory_in_worktree: directory.to_path_buf(),
+                        id_base: Cow::Owned(format!(
+                            "local worktree {} from directory {directory:?}",
+                            T::LABEL
+                        )),
+                    },
+                    template.clone(),
+                )
+            })
     }
 
     fn global_scenarios(&self) -> impl '_ + Iterator<Item = (TaskSourceKind, T)> {
@@ -168,6 +168,13 @@ impl TaskContexts {
             .and_then(|(_, location, _)| location.as_ref())
     }
 
+    pub fn file(&self, cx: &App) -> Option<Arc<dyn File>> {
+        self.active_item_context
+            .as_ref()
+            .and_then(|(_, location, _)| location.as_ref())
+            .and_then(|location| location.buffer.read(cx).file().cloned())
+    }
+
     pub fn worktree(&self) -> Option<WorktreeId> {
         self.active_item_context
             .as_ref()
@@ -214,16 +221,69 @@ impl Inventory {
         cx.new(|_| Self::default())
     }
 
+    pub fn scenario_scheduled(&mut self, scenario: DebugScenario) {
+        self.last_scheduled_scenarios
+            .retain(|s| s.label != scenario.label);
+        self.last_scheduled_scenarios.push_back(scenario);
+        if self.last_scheduled_scenarios.len() > 5_000 {
+            self.last_scheduled_scenarios.pop_front();
+        }
+    }
+
     pub fn list_debug_scenarios(
         &self,
-        worktrees: impl Iterator<Item = WorktreeId>,
-    ) -> Vec<(TaskSourceKind, DebugScenario)> {
-        let global_scenarios = self.global_debug_scenarios_from_settings();
+        task_contexts: &TaskContexts,
+        cx: &mut App,
+    ) -> (Vec<DebugScenario>, Vec<(TaskSourceKind, DebugScenario)>) {
+        let mut scenarios = Vec::new();
 
-        worktrees
-            .flat_map(|tree_id| self.worktree_scenarios_from_settings(Some(tree_id)))
-            .chain(global_scenarios)
-            .collect()
+        if let Some(worktree_id) = task_contexts
+            .active_worktree_context
+            .iter()
+            .chain(task_contexts.other_worktree_contexts.iter())
+            .map(|context| context.0)
+            .next()
+        {
+            scenarios.extend(self.worktree_scenarios_from_settings(worktree_id));
+        }
+        scenarios.extend(self.global_debug_scenarios_from_settings());
+
+        let (_, new) = self.used_and_current_resolved_tasks(task_contexts, cx);
+        if let Some(location) = task_contexts.location() {
+            let file = location.buffer.read(cx).file();
+            let language = location.buffer.read(cx).language();
+            let language_name = language.as_ref().map(|l| l.name());
+            let adapter = language_settings(language_name, file, cx)
+                .debuggers
+                .first()
+                .map(SharedString::from)
+                .or_else(|| {
+                    language.and_then(|l| l.config().debuggers.first().map(SharedString::from))
+                });
+            if let Some(adapter) = adapter {
+                for (kind, task) in new {
+                    if let Some(scenario) =
+                        DapRegistry::global(cx)
+                            .locators()
+                            .values()
+                            .find_map(|locator| {
+                                locator.create_scenario(
+                                    &task.original_task().clone(),
+                                    &task.display_label(),
+                                    adapter.clone().into(),
+                                )
+                            })
+                    {
+                        scenarios.push((kind, scenario));
+                    }
+                }
+            }
+        }
+
+        (
+            self.last_scheduled_scenarios.iter().cloned().collect(),
+            scenarios,
+        )
     }
 
     pub fn task_template_by_label(
@@ -262,7 +322,9 @@ impl Inventory {
         cx: &App,
     ) -> Vec<(TaskSourceKind, TaskTemplate)> {
         let global_tasks = self.global_templates_from_settings();
-        let worktree_tasks = self.worktree_templates_from_settings(worktree);
+        let worktree_tasks = worktree
+            .into_iter()
+            .flat_map(|worktree| self.worktree_templates_from_settings(worktree));
         let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language {
             name: language.name().into(),
         });
@@ -354,8 +416,9 @@ impl Inventory {
             .into_iter()
             .flat_map(|tasks| tasks.0.into_iter())
             .flat_map(|task| Some((task_source_kind.clone()?, task)));
-        let worktree_tasks = self
-            .worktree_templates_from_settings(worktree)
+        let worktree_tasks = worktree
+            .into_iter()
+            .flat_map(|worktree| self.worktree_templates_from_settings(worktree))
             .chain(language_tasks)
             .chain(global_tasks);
 
@@ -471,14 +534,14 @@ impl Inventory {
 
     fn worktree_scenarios_from_settings(
         &self,
-        worktree: Option<WorktreeId>,
+        worktree: WorktreeId,
     ) -> impl '_ + Iterator<Item = (TaskSourceKind, DebugScenario)> {
         self.scenarios_from_settings.worktree_scenarios(worktree)
     }
 
     fn worktree_templates_from_settings(
         &self,
-        worktree: Option<WorktreeId>,
+        worktree: WorktreeId,
     ) -> impl '_ + Iterator<Item = (TaskSourceKind, TaskTemplate)> {
         self.templates_from_settings.worktree_scenarios(worktree)
     }