diff --git a/Cargo.lock b/Cargo.lock index 07f629a653..b2ba359623 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4234,6 +4234,7 @@ dependencies = [ "futures 0.3.31", "fuzzy", "gpui", + "itertools 0.14.0", "language", "log", "menu", diff --git a/crates/debugger_ui/Cargo.toml b/crates/debugger_ui/Cargo.toml index 01f0ad7289..6fb582a58a 100644 --- a/crates/debugger_ui/Cargo.toml +++ b/crates/debugger_ui/Cargo.toml @@ -39,6 +39,7 @@ file_icons.workspace = true futures.workspace = true fuzzy.workspace = true gpui.workspace = true +itertools.workspace = true language.workspace = true log.workspace = true menu.workspace = true diff --git a/crates/debugger_ui/src/new_process_modal.rs b/crates/debugger_ui/src/new_process_modal.rs index 1c6166cfac..b515bebd03 100644 --- a/crates/debugger_ui/src/new_process_modal.rs +++ b/crates/debugger_ui/src/new_process_modal.rs @@ -19,6 +19,7 @@ use gpui::{ InteractiveText, KeyContext, PromptButton, PromptLevel, Render, StyledText, Subscription, TextStyle, UnderlineStyle, WeakEntity, }; +use itertools::Itertools as _; use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch}; use project::{ProjectPath, TaskContexts, TaskSourceKind, task_store::TaskStore}; use settings::{Settings, initial_local_debug_tasks_content}; @@ -49,7 +50,7 @@ pub(super) struct NewProcessModal { mode: NewProcessMode, debug_picker: Entity>, attach_mode: Entity, - launch_mode: Entity, + launch_mode: Entity, task_mode: TaskMode, debugger: Option, // save_scenario_state: Option, @@ -97,13 +98,13 @@ impl NewProcessModal { workspace.toggle_modal(window, cx, |window, cx| { let attach_mode = AttachMode::new(None, workspace_handle.clone(), window, cx); - let launch_picker = cx.new(|cx| { + let debug_picker = cx.new(|cx| { let delegate = DebugDelegate::new(debug_panel.downgrade(), task_store.clone()); Picker::uniform_list(delegate, window, cx).modal(false) }); - let configure_mode = LaunchMode::new(window, cx); + let configure_mode = ConfigureMode::new(window, cx); let task_overrides = Some(TaskOverrides { reveal_target }); @@ -122,7 +123,7 @@ impl NewProcessModal { }; let _subscriptions = [ - cx.subscribe(&launch_picker, |_, _, _, cx| { + cx.subscribe(&debug_picker, |_, _, _, cx| { cx.emit(DismissEvent); }), cx.subscribe( @@ -137,19 +138,76 @@ impl NewProcessModal { ]; cx.spawn_in(window, { - let launch_picker = launch_picker.downgrade(); + let debug_picker = debug_picker.downgrade(); let configure_mode = configure_mode.downgrade(); let task_modal = task_mode.task_modal.downgrade(); + let workspace = workspace_handle.clone(); async move |this, cx| { let task_contexts = task_contexts.await; let task_contexts = Arc::new(task_contexts); - launch_picker + let lsp_task_sources = task_contexts.lsp_task_sources.clone(); + let task_position = task_contexts.latest_selection; + // Get LSP tasks and filter out based on language vs lsp preference + let (lsp_tasks, prefer_lsp) = + workspace.update(cx, |workspace, cx| { + let lsp_tasks = editor::lsp_tasks( + workspace.project().clone(), + &lsp_task_sources, + task_position, + cx, + ); + let prefer_lsp = workspace + .active_item(cx) + .and_then(|item| item.downcast::()) + .map(|editor| { + editor + .read(cx) + .buffer() + .read(cx) + .language_settings(cx) + .tasks + .prefer_lsp + }) + .unwrap_or(false); + (lsp_tasks, prefer_lsp) + })?; + + let lsp_tasks = lsp_tasks.await; + let add_current_language_tasks = !prefer_lsp || lsp_tasks.is_empty(); + + let lsp_tasks = lsp_tasks + .into_iter() + .flat_map(|(kind, tasks_with_locations)| { + tasks_with_locations + .into_iter() + .sorted_by_key(|(location, task)| { + (location.is_none(), task.resolved_label.clone()) + }) + .map(move |(_, task)| (kind.clone(), task)) + }) + .collect::>(); + + let Some(task_inventory) = task_store + .update(cx, |task_store, _| task_store.task_inventory().cloned())? + else { + return Ok(()); + }; + + let (used_tasks, current_resolved_tasks) = + task_inventory.update(cx, |task_inventory, cx| { + task_inventory + .used_and_current_resolved_tasks(&task_contexts, cx) + })?; + + debug_picker .update_in(cx, |picker, window, cx| { - picker.delegate.task_contexts_loaded( + picker.delegate.tasks_loaded( task_contexts.clone(), languages, - window, + lsp_tasks.clone(), + current_resolved_tasks.clone(), + add_current_language_tasks, cx, ); picker.refresh(window, cx); @@ -170,7 +228,15 @@ impl NewProcessModal { task_modal .update_in(cx, |task_modal, window, cx| { - task_modal.task_contexts_loaded(task_contexts, window, cx); + task_modal.tasks_loaded( + task_contexts, + lsp_tasks, + used_tasks, + current_resolved_tasks, + add_current_language_tasks, + window, + cx, + ); }) .ok(); @@ -178,12 +244,14 @@ impl NewProcessModal { cx.notify(); }) .ok(); + + anyhow::Ok(()) } }) .detach(); Self { - debug_picker: launch_picker, + debug_picker, attach_mode, launch_mode: configure_mode, task_mode, @@ -820,14 +888,14 @@ impl RenderOnce for AttachMode { } #[derive(Clone)] -pub(super) struct LaunchMode { +pub(super) struct ConfigureMode { program: Entity, cwd: Entity, stop_on_entry: ToggleState, // save_to_debug_json: ToggleState, } -impl LaunchMode { +impl ConfigureMode { pub(super) fn new(window: &mut Window, cx: &mut App) -> Entity { let program = cx.new(|cx| Editor::single_line(window, cx)); program.update(cx, |this, cx| { @@ -1067,21 +1135,29 @@ impl DebugDelegate { (language, scenario) } - pub fn task_contexts_loaded( + pub fn tasks_loaded( &mut self, task_contexts: Arc, languages: Arc, - _window: &mut Window, + lsp_tasks: Vec<(TaskSourceKind, task::ResolvedTask)>, + current_resolved_tasks: Vec<(TaskSourceKind, task::ResolvedTask)>, + add_current_language_tasks: bool, cx: &mut Context>, ) { - self.task_contexts = Some(task_contexts); + self.task_contexts = Some(task_contexts.clone()); 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) + inventory.list_debug_scenarios( + &task_contexts, + lsp_tasks, + current_resolved_tasks, + add_current_language_tasks, + cx, + ) }) }) }) @@ -1257,12 +1333,17 @@ impl PickerDelegate for DebugDelegate { .map(|icon| icon.color(Color::Muted).size(IconSize::Small)); let indicator = if matches!(task_kind, Some(TaskSourceKind::Lsp { .. })) { Some(Indicator::icon( - Icon::new(IconName::BoltFilled).color(Color::Muted), + Icon::new(IconName::BoltFilled) + .color(Color::Muted) + .size(IconSize::Small), )) } else { None }; - let icon = icon.map(|icon| IconWithIndicator::new(icon, indicator)); + let icon = icon.map(|icon| { + IconWithIndicator::new(icon, indicator) + .indicator_border_color(Some(cx.theme().colors().border_transparent)) + }); Some( ListItem::new(SharedString::from(format!("debug-scenario-selection-{ix}"))) diff --git a/crates/project/src/task_inventory.rs b/crates/project/src/task_inventory.rs index 0e4ca55c99..b6997fae71 100644 --- a/crates/project/src/task_inventory.rs +++ b/crates/project/src/task_inventory.rs @@ -243,6 +243,9 @@ impl Inventory { pub fn list_debug_scenarios( &self, task_contexts: &TaskContexts, + lsp_tasks: Vec<(TaskSourceKind, task::ResolvedTask)>, + current_resolved_tasks: Vec<(TaskSourceKind, task::ResolvedTask)>, + add_current_language_tasks: bool, cx: &mut App, ) -> (Vec, Vec<(TaskSourceKind, DebugScenario)>) { let mut scenarios = Vec::new(); @@ -258,7 +261,6 @@ impl Inventory { } 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(); @@ -271,7 +273,14 @@ impl Inventory { language.and_then(|l| l.config().debuggers.first().map(SharedString::from)) }); if let Some(adapter) = adapter { - for (kind, task) in new { + for (kind, task) in + lsp_tasks + .into_iter() + .chain(current_resolved_tasks.into_iter().filter(|(kind, _)| { + add_current_language_tasks + || !matches!(kind, TaskSourceKind::Language { .. }) + })) + { if let Some(scenario) = DapRegistry::global(cx) .locators() diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index 0a003c324f..dce29c1d2a 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -162,15 +162,33 @@ impl TasksModal { } } - pub fn task_contexts_loaded( + pub fn tasks_loaded( &mut self, task_contexts: Arc, + lsp_tasks: Vec<(TaskSourceKind, task::ResolvedTask)>, + used_tasks: Vec<(TaskSourceKind, task::ResolvedTask)>, + current_resolved_tasks: Vec<(TaskSourceKind, task::ResolvedTask)>, + add_current_language_tasks: bool, window: &mut Window, cx: &mut Context, ) { + let last_used_candidate_index = if used_tasks.is_empty() { + None + } else { + Some(used_tasks.len() - 1) + }; + let mut new_candidates = used_tasks; + new_candidates.extend(lsp_tasks); + // todo(debugger): We're always adding lsp tasks here even if prefer_lsp is false + // We should move the filter to new_candidates instead of on current + // and add a test for this + new_candidates.extend(current_resolved_tasks.into_iter().filter(|(task_kind, _)| { + add_current_language_tasks || !matches!(task_kind, TaskSourceKind::Language { .. }) + })); self.picker.update(cx, |picker, cx| { picker.delegate.task_contexts = task_contexts; - picker.delegate.candidates = None; + picker.delegate.last_used_candidate_index = last_used_candidate_index; + picker.delegate.candidates = Some(new_candidates); picker.refresh(window, cx); cx.notify(); }) @@ -296,6 +314,9 @@ impl PickerDelegate for TasksModalDelegate { .map(move |(_, task)| (kind.clone(), task)) }, )); + // todo(debugger): We're always adding lsp tasks here even if prefer_lsp is false + // We should move the filter to new_candidates instead of on current + // and add a test for this new_candidates.extend(current.into_iter().filter( |(task_kind, _)| { add_current_language_tasks