Cargo.lock 🔗
@@ -4234,6 +4234,7 @@ dependencies = [
"futures 0.3.31",
"fuzzy",
"gpui",
+ "itertools 0.14.0",
"language",
"log",
"menu",
Cole Miller and Anthony Eid created
- [x] pass LSP tasks into list_debug_scenarios
- [x] load LSP tasks only once for both modals
- [x] align ordering
- [x] improve appearance of LSP debug task icons
- [ ] reconsider how `add_current_language_tasks` works
- [ ] add a test
Release Notes:
- Debugger Beta: Added debuggable LSP tasks to the "Debug" tab of the
new process modal.
---------
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Cargo.lock | 1
crates/debugger_ui/Cargo.toml | 1
crates/debugger_ui/src/new_process_modal.rs | 117 +++++++++++++++++++---
crates/project/src/task_inventory.rs | 13 ++
crates/tasks_ui/src/modal.rs | 25 ++++
5 files changed, 135 insertions(+), 22 deletions(-)
@@ -4234,6 +4234,7 @@ dependencies = [
"futures 0.3.31",
"fuzzy",
"gpui",
+ "itertools 0.14.0",
"language",
"log",
"menu",
@@ -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
@@ -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<Picker<DebugDelegate>>,
attach_mode: Entity<AttachMode>,
- launch_mode: Entity<LaunchMode>,
+ launch_mode: Entity<ConfigureMode>,
task_mode: TaskMode,
debugger: Option<DebugAdapterName>,
// save_scenario_state: Option<SaveScenarioState>,
@@ -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::<Editor>())
+ .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::<Vec<_>>();
+
+ 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<Editor>,
cwd: Entity<Editor>,
stop_on_entry: ToggleState,
// save_to_debug_json: ToggleState,
}
-impl LaunchMode {
+impl ConfigureMode {
pub(super) fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
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<TaskContexts>,
languages: Arc<LanguageRegistry>,
- _window: &mut Window,
+ lsp_tasks: Vec<(TaskSourceKind, task::ResolvedTask)>,
+ current_resolved_tasks: Vec<(TaskSourceKind, task::ResolvedTask)>,
+ add_current_language_tasks: bool,
cx: &mut Context<Picker<Self>>,
) {
- 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}")))
@@ -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<DebugScenario>, 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()
@@ -162,15 +162,33 @@ impl TasksModal {
}
}
- pub fn task_contexts_loaded(
+ pub fn tasks_loaded(
&mut self,
task_contexts: Arc<TaskContexts>,
+ 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<Self>,
) {
+ 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