From ff215b4f11e808469325d77f9f02517968c80765 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 5 May 2025 21:08:14 +0100 Subject: [PATCH] debugger: Run build in terminal (#29645) Currently contains the pre-work of making sessions creatable without a definition, but still need to change the spawn in terminal to use the running session Release Notes: - N/A --------- Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> --- crates/dap/src/adapters.rs | 10 +- crates/debugger_tools/src/dap_log.rs | 4 +- crates/debugger_ui/src/debugger_panel.rs | 328 +++------ crates/debugger_ui/src/new_session_modal.rs | 687 +++++++++--------- crates/debugger_ui/src/persistence.rs | 4 +- crates/debugger_ui/src/session/running.rs | 148 +++- .../debugger_ui/src/tests/debugger_panel.rs | 10 +- crates/project/src/debugger/dap_store.rs | 16 +- crates/project/src/debugger/session.rs | 51 +- crates/project/src/project.rs | 9 +- crates/task/src/lib.rs | 3 +- crates/task/src/task_template.rs | 47 ++ 12 files changed, 695 insertions(+), 622 deletions(-) diff --git a/crates/dap/src/adapters.rs b/crates/dap/src/adapters.rs index 343dd1eb9b..073b852778 100644 --- a/crates/dap/src/adapters.rs +++ b/crates/dap/src/adapters.rs @@ -88,7 +88,7 @@ impl<'a> From<&'a str> for DebugAdapterName { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct TcpArguments { pub host: Ipv4Addr, pub port: u16, @@ -127,7 +127,7 @@ impl TcpArguments { )] pub struct DebugTaskDefinition { pub label: SharedString, - pub adapter: SharedString, + pub adapter: DebugAdapterName, pub request: DebugRequest, /// Additional initialization arguments to be sent on DAP initialization pub initialize_args: Option, @@ -153,7 +153,7 @@ impl DebugTaskDefinition { pub fn to_scenario(&self) -> DebugScenario { DebugScenario { label: self.label.clone(), - adapter: self.adapter.clone(), + adapter: self.adapter.clone().into(), build: None, request: Some(self.request.clone()), stop_on_entry: self.stop_on_entry, @@ -207,7 +207,7 @@ impl DebugTaskDefinition { .map(TcpArgumentsTemplate::from_proto) .transpose()?, stop_on_entry: proto.stop_on_entry, - adapter: proto.adapter.into(), + adapter: DebugAdapterName(proto.adapter.into()), request: match request { proto::debug_task_definition::Request::DebugAttachRequest(config) => { DebugRequest::Attach(AttachRequest { @@ -229,7 +229,7 @@ impl DebugTaskDefinition { } /// Created from a [DebugTaskDefinition], this struct describes how to spawn the debugger to create a previously-configured debug session. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct DebugAdapterBinary { pub command: String, pub arguments: Vec, diff --git a/crates/debugger_tools/src/dap_log.rs b/crates/debugger_tools/src/dap_log.rs index eca26d2b73..a850480253 100644 --- a/crates/debugger_tools/src/dap_log.rs +++ b/crates/debugger_tools/src/dap_log.rs @@ -568,11 +568,11 @@ impl DapLogView { .sessions() .filter_map(|session| { let session = session.read(cx); - session.adapter_name(); + session.adapter(); let client = session.adapter_client()?; Some(DapMenuItem { client_id: client.id(), - client_name: session.adapter_name().to_string(), + client_name: session.adapter().to_string(), has_adapter_logs: client.has_adapter_logs(), selected_entry: self.current_view.map_or(LogKind::Adapter, |(_, kind)| kind), }) diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index 6459c76a01..3790dd126f 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -6,10 +6,9 @@ use crate::{ persistence, }; use crate::{new_session_modal::NewSessionModal, session::DebugSession}; -use anyhow::{Context as _, Result, anyhow}; -use collections::{HashMap, HashSet}; +use anyhow::Result; use command_palette_hooks::CommandPaletteFilter; -use dap::DebugRequest; +use dap::adapters::DebugAdapterName; use dap::{ ContinuedEvent, LoadedSourceEvent, ModuleEvent, OutputEvent, StoppedEvent, ThreadEvent, client::SessionId, debugger_settings::DebuggerSettings, @@ -27,7 +26,6 @@ use project::{Project, debugger::session::ThreadStatus}; use rpc::proto::{self}; use settings::Settings; use std::any::TypeId; -use std::path::PathBuf; use task::{DebugScenario, TaskContext}; use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*}; use workspace::SplitDirection; @@ -200,40 +198,6 @@ impl DebugPanel { }) } - fn start_from_definition( - &mut self, - definition: DebugTaskDefinition, - window: &mut Window, - cx: &mut Context, - ) -> Task> { - cx.spawn_in(window, async move |this, cx| { - let dap_store = this.update(cx, |this, cx| this.project.read(cx).dap_store())?; - let (session, task) = dap_store.update(cx, |dap_store, cx| { - let session = dap_store.new_session(definition, None, cx); - - (session.clone(), dap_store.boot_session(session, cx)) - })?; - Self::register_session(this.clone(), session.clone(), cx).await?; - - if let Err(e) = task.await { - this.update(cx, |this, cx| { - this.workspace - .update(cx, |workspace, cx| { - workspace.show_error(&e, cx); - }) - .ok(); - }) - .ok(); - - session - .update(cx, |session, cx| session.shutdown(cx))? - .await; - } - - anyhow::Ok(()) - }) - } - pub fn start_session( &mut self, scenario: DebugScenario, @@ -242,16 +206,56 @@ impl DebugPanel { window: &mut Window, cx: &mut Context, ) { - cx.spawn_in(window, async move |this, cx| { - let definition = this - .update_in(cx, |this, window, cx| { - this.resolve_scenario(scenario, task_context, active_buffer, window, cx) - })? - .await?; - this.update_in(cx, |this, window, cx| { - this.start_from_definition(definition, window, cx) - })? - .await + let dap_store = self.project.read(cx).dap_store(); + let workspace = self.workspace.clone(); + let session = dap_store.update(cx, |dap_store, cx| { + dap_store.new_session( + scenario.label.clone(), + DebugAdapterName(scenario.adapter.clone()), + None, + cx, + ) + }); + let task = cx.spawn_in(window, { + let session = session.clone(); + async move |this, cx| { + let debug_session = + Self::register_session(this.clone(), session.clone(), cx).await?; + let definition = debug_session + .update_in(cx, |debug_session, window, cx| { + debug_session.running_state().update(cx, |running, cx| { + running.resolve_scenario( + scenario, + task_context, + active_buffer, + window, + cx, + ) + }) + })? + .await?; + + dap_store + .update(cx, |dap_store, cx| { + dap_store.boot_session(session.clone(), definition, cx) + })? + .await + } + }); + + cx.spawn(async move |_, cx| { + if let Err(error) = task.await { + log::error!("{:?}", error); + workspace + .update(cx, |workspace, cx| { + workspace.show_error(&error, cx); + }) + .ok(); + session + .update(cx, |session, cx| session.shutdown(cx))? + .await; + } + anyhow::Ok(()) }) .detach_and_log_err(cx); } @@ -260,33 +264,15 @@ impl DebugPanel { this: WeakEntity, session: Entity, cx: &mut AsyncWindowContext, - ) -> Result<()> { - let adapter_name = session.update(cx, |session, _| session.adapter_name())?; + ) -> Result> { + let adapter_name = session.update(cx, |session, _| session.adapter())?; this.update_in(cx, |_, window, cx| { cx.subscribe_in( &session, window, move |this, session, event: &SessionStateEvent, window, cx| match event { SessionStateEvent::Restart => { - let mut curr_session = session.clone(); - while let Some(parent_session) = curr_session - .read_with(cx, |session, _| session.parent_session().cloned()) - { - curr_session = parent_session; - } - - let definition = curr_session.update(cx, |session, _| session.definition()); - let task = curr_session.update(cx, |session, cx| session.shutdown(cx)); - - cx.spawn_in(window, async move |this, cx| { - task.await; - - this.update_in(cx, |this, window, cx| { - this.start_from_definition(definition, window, cx) - })? - .await - }) - .detach_and_log_err(cx); + this.handle_restart_request(session.clone(), window, cx); } SessionStateEvent::SpawnChildSession { request } => { this.handle_start_debugging_request(request, session.clone(), window, cx); @@ -300,7 +286,7 @@ impl DebugPanel { let serialized_layout = persistence::get_serialized_pane_layout(adapter_name).await; - let workspace = this.update_in(cx, |this, window, cx| { + let (debug_session, workspace) = this.update_in(cx, |this, window, cx| { this.sessions.retain(|session| { session .read(cx) @@ -311,7 +297,7 @@ impl DebugPanel { .is_terminated() }); - let session_item = DebugSession::running( + let debug_session = DebugSession::running( this.project.clone(), this.workspace.clone(), session, @@ -324,20 +310,62 @@ impl DebugPanel { // We might want to make this an event subscription and only notify when a new thread is selected // This is used to filter the command menu correctly cx.observe( - &session_item.read(cx).running_state().clone(), + &debug_session.read(cx).running_state().clone(), |_, _, cx| cx.notify(), ) .detach(); - this.sessions.push(session_item.clone()); - this.activate_session(session_item, window, cx); - this.workspace.clone() + this.sessions.push(debug_session.clone()); + this.activate_session(debug_session.clone(), window, cx); + + (debug_session, this.workspace.clone()) })?; workspace.update_in(cx, |workspace, window, cx| { workspace.focus_panel::(window, cx); })?; - Ok(()) + + Ok(debug_session) + } + + fn handle_restart_request( + &mut self, + mut curr_session: Entity, + window: &mut Window, + cx: &mut Context, + ) { + while let Some(parent_session) = + curr_session.read_with(cx, |session, _| session.parent_session().cloned()) + { + curr_session = parent_session; + } + + let Some(worktree) = curr_session.read(cx).worktree() else { + log::error!("Attempted to start a child session from non local debug session"); + return; + }; + + let dap_store_handle = self.project.read(cx).dap_store().clone(); + let label = curr_session.read(cx).label().clone(); + let adapter = curr_session.read(cx).adapter().clone(); + let binary = curr_session.read(cx).binary().clone(); + let task = curr_session.update(cx, |session, cx| session.shutdown(cx)); + + cx.spawn_in(window, async move |this, cx| { + task.await; + + let (session, task) = dap_store_handle.update(cx, |dap_store, cx| { + let session = dap_store.new_session(label, adapter, None, cx); + + let task = session.update(cx, |session, cx| { + session.boot(binary, worktree, dap_store_handle.downgrade(), cx) + }); + (session, task) + })?; + Self::register_session(this, session, cx).await?; + task.await + }) + .detach_and_log_err(cx); } pub fn handle_start_debugging_request( @@ -353,40 +381,23 @@ impl DebugPanel { }; let dap_store_handle = self.project.read(cx).dap_store().clone(); - let definition = parent_session.read(cx).definition().clone(); + let label = parent_session.read(cx).label().clone(); + let adapter = parent_session.read(cx).adapter().clone(); let mut binary = parent_session.read(cx).binary().clone(); binary.request_args = request.clone(); cx.spawn_in(window, async move |this, cx| { let (session, task) = dap_store_handle.update(cx, |dap_store, cx| { let session = - dap_store.new_session(definition.clone(), Some(parent_session.clone()), cx); + dap_store.new_session(label, adapter, Some(parent_session.clone()), cx); let task = session.update(cx, |session, cx| { session.boot(binary, worktree, dap_store_handle.downgrade(), cx) }); (session, task) })?; - - match task.await { - Err(e) => { - this.update(cx, |this, cx| { - this.workspace - .update(cx, |workspace, cx| { - workspace.show_error(&e, cx); - }) - .ok(); - }) - .ok(); - - session - .update(cx, |session, cx| session.shutdown(cx))? - .await; - } - Ok(_) => Self::register_session(this, session, cx).await?, - } - - anyhow::Ok(()) + Self::register_session(this, session, cx).await?; + task.await }) .detach_and_log_err(cx); } @@ -394,127 +405,6 @@ impl DebugPanel { pub fn active_session(&self) -> Option> { self.active_session.clone() } - - pub fn resolve_scenario( - &self, - scenario: DebugScenario, - task_context: TaskContext, - buffer: Option>, - window: &Window, - cx: &mut Context, - ) -> Task> { - let project = self.project.read(cx); - let dap_store = project.dap_store().downgrade(); - let task_store = project.task_store().downgrade(); - let workspace = self.workspace.clone(); - cx.spawn_in(window, async move |_, cx| { - let DebugScenario { - adapter, - label, - build, - request, - initialize_args, - tcp_connection, - stop_on_entry, - } = scenario; - let request = if let Some(mut request) = request { - if let DebugRequest::Launch(launch_config) = &mut request { - let mut variable_names = HashMap::default(); - let mut substituted_variables = HashSet::default(); - let task_variables = task_context - .task_variables - .iter() - .map(|(key, value)| { - let key_string = key.to_string(); - if !variable_names.contains_key(&key_string) { - variable_names.insert(key_string.clone(), key.clone()); - } - (key_string, value.as_str()) - }) - .collect::>(); - - let cwd = launch_config - .cwd - .as_ref() - .and_then(|cwd| cwd.to_str()) - .and_then(|cwd| { - task::substitute_all_template_variables_in_str( - cwd, - &task_variables, - &variable_names, - &mut substituted_variables, - ) - }); - - if let Some(cwd) = cwd { - launch_config.cwd = Some(PathBuf::from(cwd)) - } - - if let Some(program) = task::substitute_all_template_variables_in_str( - &launch_config.program, - &task_variables, - &variable_names, - &mut substituted_variables, - ) { - launch_config.program = program; - } - - for arg in launch_config.args.iter_mut() { - if let Some(substituted_arg) = - task::substitute_all_template_variables_in_str( - &arg, - &task_variables, - &variable_names, - &mut substituted_variables, - ) - { - *arg = substituted_arg; - } - } - } - - request - } else if let Some(build) = build { - let Some(task) = task_store.update(cx, |this, cx| { - this.task_inventory().and_then(|inventory| { - inventory - .read(cx) - .task_template_by_label(buffer, &build, cx) - }) - })? - else { - anyhow::bail!("Couldn't find task template for {:?}", build) - }; - let Some(task) = task.resolve_task("debug-build-task", &task_context) else { - anyhow::bail!("Could not resolve task variables within a debug scenario"); - }; - - let run_build = workspace.update_in(cx, |workspace, window, cx| { - workspace.spawn_in_terminal(task.resolved.clone(), window, cx) - })?; - - let exit_status = run_build.await.transpose()?.context("task cancelled")?; - if !exit_status.success() { - anyhow::bail!("Build failed"); - } - - dap_store - .update(cx, |this, cx| this.run_debug_locator(task.resolved, cx))? - .await? - } else { - return Err(anyhow!("No request or build provided")); - }; - Ok(DebugTaskDefinition { - label, - adapter, - request, - initialize_args, - stop_on_entry, - tcp_connection, - }) - }) - } - fn close_session(&mut self, entity_id: EntityId, window: &mut Window, cx: &mut Context) { let Some(session) = self .sessions diff --git a/crates/debugger_ui/src/new_session_modal.rs b/crates/debugger_ui/src/new_session_modal.rs index 1769a919fd..06398336bc 100644 --- a/crates/debugger_ui/src/new_session_modal.rs +++ b/crates/debugger_ui/src/new_session_modal.rs @@ -4,7 +4,10 @@ use std::{ path::{Path, PathBuf}, }; -use dap::{DapRegistry, DebugRequest, adapters::DebugTaskDefinition}; +use dap::{ + DapRegistry, DebugRequest, + adapters::{DebugAdapterName, DebugTaskDefinition}, +}; use editor::{Editor, EditorElement, EditorStyle}; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ @@ -13,9 +16,9 @@ use gpui::{ }; use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch}; use project::{TaskSourceKind, task_store::TaskStore}; -use session_modes::{AttachMode, DebugScenarioDelegate, LaunchMode}; use settings::Settings; use task::{DebugScenario, LaunchRequest}; +use tasks_ui::task_contexts; use theme::ThemeSettings; use ui::{ ActiveTheme, Button, ButtonCommon, ButtonSize, CheckboxWithLabel, Clickable, Color, Context, @@ -36,7 +39,7 @@ pub(super) struct NewSessionModal { mode: NewSessionMode, stop_on_entry: ToggleState, initialize_args: Option, - debugger: Option, + debugger: Option, last_selected_profile_name: Option, } @@ -143,16 +146,19 @@ impl NewSessionModal { let debug_panel = self.debug_panel.clone(); let workspace = self.workspace.clone(); - cx.spawn_in(window, async move |this, cx| { let task_contexts = workspace - .update_in(cx, |workspace, window, cx| { - tasks_ui::task_contexts(workspace, window, cx) - })? + .update_in(cx, |this, window, cx| task_contexts(this, window, cx))? .await; - - let task_context = task_contexts.active_context().cloned().unwrap_or_default(); - + let task_context = task_contexts + .active_item_context + .map(|(_, _, context)| context) + .or_else(|| { + task_contexts + .active_worktree_context + .map(|(_, context)| context) + }) + .unwrap_or_default(); debug_panel.update_in(cx, |debug_panel, window, cx| { debug_panel.start_session(config, task_context, None, window, cx) })?; @@ -167,18 +173,17 @@ impl NewSessionModal { fn update_attach_picker( attach: &Entity, - selected_debugger: &str, + adapter: &DebugAdapterName, window: &mut Window, cx: &mut App, ) { attach.update(cx, |this, cx| { - if selected_debugger != this.definition.adapter.as_ref() { - let adapter: SharedString = selected_debugger.to_owned().into(); + if adapter != &this.definition.adapter { this.definition.adapter = adapter.clone(); this.attach_picker.update(cx, |this, cx| { this.picker.update(cx, |this, cx| { - this.delegate.definition.adapter = adapter; + this.delegate.definition.adapter = adapter.clone(); this.focus(window, cx); }) }); @@ -194,15 +199,16 @@ impl NewSessionModal { ) -> ui::DropdownMenu { let workspace = self.workspace.clone(); let weak = cx.weak_entity(); - let debugger = self.debugger.clone(); + let label = self + .debugger + .as_ref() + .map(|d| d.0.clone()) + .unwrap_or_else(|| SELECT_DEBUGGER_LABEL.clone()); DropdownMenu::new( "dap-adapter-picker", - debugger - .as_ref() - .unwrap_or_else(|| &SELECT_DEBUGGER_LABEL) - .clone(), + label, ContextMenu::build(window, cx, move |mut menu, _, cx| { - let setter_for_name = |name: SharedString| { + let setter_for_name = |name: DebugAdapterName| { let weak = weak.clone(); move |window: &mut Window, cx: &mut App| { weak.update(cx, |this, cx| { @@ -222,7 +228,7 @@ impl NewSessionModal { .unwrap_or_default(); for adapter in available_adapters { - menu = menu.entry(adapter.0.clone(), None, setter_for_name(adapter.0.clone())); + menu = menu.entry(adapter.0.clone(), None, setter_for_name(adapter.clone())); } menu }), @@ -251,7 +257,7 @@ impl NewSessionModal { move |window: &mut Window, cx: &mut App| { weak.update(cx, |this, cx| { this.last_selected_profile_name = Some(SharedString::from(&task.label)); - this.debugger = Some(task.adapter.clone()); + this.debugger = Some(DebugAdapterName(task.adapter.clone())); this.initialize_args = task.initialize_args.clone(); match &task.request { Some(DebugRequest::Launch(launch_config)) => { @@ -374,7 +380,7 @@ impl NewSessionMode { } fn attach( - debugger: Option, + debugger: Option, workspace: Entity, window: &mut Window, cx: &mut Context, @@ -431,41 +437,6 @@ impl Focusable for NewSessionMode { } } -impl RenderOnce for LaunchMode { - fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { - v_flex() - .p_2() - .w_full() - .gap_3() - .track_focus(&self.program.focus_handle(cx)) - .child( - div().child( - Label::new("Program") - .size(ui::LabelSize::Small) - .color(Color::Muted), - ), - ) - .child(render_editor(&self.program, window, cx)) - .child( - div().child( - Label::new("Working Directory") - .size(ui::LabelSize::Small) - .color(Color::Muted), - ), - ) - .child(render_editor(&self.cwd, window, cx)) - } -} - -impl RenderOnce for AttachMode { - fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { - v_flex() - .w_full() - .track_focus(&self.attach_picker.focus_handle(cx)) - .child(self.attach_picker.clone()) - } -} - impl RenderOnce for NewSessionMode { fn render(self, window: &mut Window, cx: &mut App) -> impl ui::IntoElement { match self { @@ -684,318 +655,342 @@ impl Focusable for NewSessionModal { impl ModalView for NewSessionModal {} -// This module makes sure that the modes setup the correct subscriptions whenever they're created -mod session_modes { - use std::rc::Rc; - - use super::*; - - #[derive(Clone)] - #[non_exhaustive] - pub(super) struct LaunchMode { - pub(super) program: Entity, - pub(super) cwd: Entity, +impl RenderOnce for LaunchMode { + fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { + v_flex() + .p_2() + .w_full() + .gap_3() + .track_focus(&self.program.focus_handle(cx)) + .child( + div().child( + Label::new("Program") + .size(ui::LabelSize::Small) + .color(Color::Muted), + ), + ) + .child(render_editor(&self.program, window, cx)) + .child( + div().child( + Label::new("Working Directory") + .size(ui::LabelSize::Small) + .color(Color::Muted), + ), + ) + .child(render_editor(&self.cwd, window, cx)) } +} - impl LaunchMode { - pub(super) fn new( - past_launch_config: Option, - window: &mut Window, - cx: &mut App, - ) -> Entity { - let (past_program, past_cwd) = past_launch_config - .map(|config| (Some(config.program), config.cwd)) - .unwrap_or_else(|| (None, None)); - - let program = cx.new(|cx| Editor::single_line(window, cx)); - program.update(cx, |this, cx| { - this.set_placeholder_text("Program path", cx); - - if let Some(past_program) = past_program { - this.set_text(past_program, window, cx); - }; - }); - let cwd = cx.new(|cx| Editor::single_line(window, cx)); - cwd.update(cx, |this, cx| { - this.set_placeholder_text("Working Directory", cx); - if let Some(past_cwd) = past_cwd { - this.set_text(past_cwd.to_string_lossy(), window, cx); - }; - }); - cx.new(|_| Self { program, cwd }) - } - - pub(super) fn debug_task(&self, cx: &App) -> task::LaunchRequest { - let path = self.cwd.read(cx).text(cx); - task::LaunchRequest { - program: self.program.read(cx).text(cx), - cwd: path.is_empty().not().then(|| PathBuf::from(path)), - args: Default::default(), - env: Default::default(), - } - } +impl RenderOnce for AttachMode { + fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { + v_flex() + .w_full() + .track_focus(&self.attach_picker.focus_handle(cx)) + .child(self.attach_picker.clone()) } +} - #[derive(Clone)] - pub(super) struct AttachMode { - pub(super) definition: DebugTaskDefinition, - pub(super) attach_picker: Entity, - _subscription: Rc, - } +use std::rc::Rc; - impl AttachMode { - pub(super) fn new( - debugger: Option, - workspace: Entity, - window: &mut Window, - cx: &mut Context, - ) -> Entity { - let definition = DebugTaskDefinition { - adapter: debugger.clone().unwrap_or_default(), - label: "Attach New Session Setup".into(), - request: dap::DebugRequest::Attach(task::AttachRequest { process_id: None }), - initialize_args: None, - tcp_connection: None, - stop_on_entry: Some(false), +#[derive(Clone)] +pub(super) struct LaunchMode { + program: Entity, + cwd: Entity, +} + +impl LaunchMode { + pub(super) fn new( + past_launch_config: Option, + window: &mut Window, + cx: &mut App, + ) -> Entity { + let (past_program, past_cwd) = past_launch_config + .map(|config| (Some(config.program), config.cwd)) + .unwrap_or_else(|| (None, None)); + + let program = cx.new(|cx| Editor::single_line(window, cx)); + program.update(cx, |this, cx| { + this.set_placeholder_text("Program path", cx); + + if let Some(past_program) = past_program { + this.set_text(past_program, window, cx); }; - let attach_picker = cx.new(|cx| { - let modal = AttachModal::new(definition.clone(), workspace, false, window, cx); - window.focus(&modal.focus_handle(cx)); - - modal - }); - - let subscription = cx.subscribe(&attach_picker, |_, _, _, cx| { - cx.emit(DismissEvent); - }); - - cx.new(|_| Self { - definition, - attach_picker, - _subscription: Rc::new(subscription), - }) - } - pub(super) fn debug_task(&self) -> task::AttachRequest { - task::AttachRequest { process_id: None } - } + }); + let cwd = cx.new(|cx| Editor::single_line(window, cx)); + cwd.update(cx, |this, cx| { + this.set_placeholder_text("Working Directory", cx); + if let Some(past_cwd) = past_cwd { + this.set_text(past_cwd.to_string_lossy(), window, cx); + }; + }); + cx.new(|_| Self { program, cwd }) } - pub(super) struct DebugScenarioDelegate { - task_store: Entity, - candidates: Option>, - selected_index: usize, - matches: Vec, - prompt: String, + pub(super) fn debug_task(&self, cx: &App) -> task::LaunchRequest { + let path = self.cwd.read(cx).text(cx); + task::LaunchRequest { + program: self.program.read(cx).text(cx), + cwd: path.is_empty().not().then(|| PathBuf::from(path)), + args: Default::default(), + env: Default::default(), + } + } +} + +#[derive(Clone)] +pub(super) struct AttachMode { + pub(super) definition: DebugTaskDefinition, + pub(super) attach_picker: Entity, + _subscription: Rc, +} + +impl AttachMode { + pub(super) fn new( + debugger: Option, + workspace: Entity, + window: &mut Window, + cx: &mut Context, + ) -> Entity { + let definition = DebugTaskDefinition { + adapter: debugger.unwrap_or(DebugAdapterName("".into())), + label: "Attach New Session Setup".into(), + request: dap::DebugRequest::Attach(task::AttachRequest { process_id: None }), + initialize_args: None, + tcp_connection: None, + stop_on_entry: Some(false), + }; + let attach_picker = cx.new(|cx| { + let modal = AttachModal::new(definition.clone(), workspace, false, window, cx); + window.focus(&modal.focus_handle(cx)); + + modal + }); + + let subscription = cx.subscribe(&attach_picker, |_, _, _, cx| { + cx.emit(DismissEvent); + }); + + cx.new(|_| Self { + definition, + attach_picker, + _subscription: Rc::new(subscription), + }) + } + pub(super) fn debug_task(&self) -> task::AttachRequest { + task::AttachRequest { process_id: None } + } +} + +pub(super) struct DebugScenarioDelegate { + task_store: Entity, + candidates: Option>, + selected_index: usize, + matches: Vec, + prompt: String, + debug_panel: WeakEntity, + workspace: WeakEntity, +} + +impl DebugScenarioDelegate { + pub(super) fn new( debug_panel: WeakEntity, workspace: WeakEntity, - } - - impl DebugScenarioDelegate { - pub(super) fn new( - debug_panel: WeakEntity, - workspace: WeakEntity, - task_store: Entity, - ) -> Self { - Self { - task_store, - candidates: None, - selected_index: 0, - matches: Vec::new(), - prompt: String::new(), - debug_panel, - workspace, - } + task_store: Entity, + ) -> Self { + Self { + task_store, + candidates: None, + selected_index: 0, + matches: Vec::new(), + prompt: String::new(), + debug_panel, + workspace, } } +} - impl PickerDelegate for DebugScenarioDelegate { - type ListItem = ui::ListItem; +impl PickerDelegate for DebugScenarioDelegate { + type ListItem = ui::ListItem; - fn match_count(&self) -> usize { - self.matches.len() - } + fn match_count(&self) -> usize { + self.matches.len() + } - fn selected_index(&self) -> usize { - self.selected_index - } + fn selected_index(&self) -> usize { + self.selected_index + } - fn set_selected_index( - &mut self, - ix: usize, - _window: &mut Window, - _cx: &mut Context>, - ) { - self.selected_index = ix; - } + fn set_selected_index( + &mut self, + ix: usize, + _window: &mut Window, + _cx: &mut Context>, + ) { + self.selected_index = ix; + } - fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> std::sync::Arc { - "".into() - } + fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> std::sync::Arc { + "".into() + } - fn update_matches( - &mut self, - query: String, - window: &mut Window, - cx: &mut Context>, - ) -> gpui::Task<()> { - let candidates: Vec<_> = match &self.candidates { - Some(candidates) => candidates + fn update_matches( + &mut self, + query: String, + window: &mut Window, + cx: &mut Context>, + ) -> gpui::Task<()> { + let candidates: Vec<_> = match &self.candidates { + Some(candidates) => candidates + .into_iter() + .enumerate() + .map(|(index, (_, candidate))| { + StringMatchCandidate::new(index, candidate.label.as_ref()) + }) + .collect(), + None => { + let worktree_ids: Vec<_> = self + .workspace + .update(cx, |this, cx| { + this.visible_worktrees(cx) + .map(|tree| tree.read(cx).id()) + .collect() + }) + .ok() + .unwrap_or_default(); + + let scenarios: Vec<_> = self + .task_store + .read(cx) + .task_inventory() + .map(|item| item.read(cx).list_debug_scenarios(worktree_ids.into_iter())) + .unwrap_or_default(); + + self.candidates = Some(scenarios.clone()); + + scenarios .into_iter() .enumerate() .map(|(index, (_, candidate))| { StringMatchCandidate::new(index, candidate.label.as_ref()) }) - .collect(), - None => { - let worktree_ids: Vec<_> = self - .workspace - .update(cx, |this, cx| { - this.visible_worktrees(cx) - .map(|tree| tree.read(cx).id()) - .collect() - }) - .ok() - .unwrap_or_default(); + .collect() + } + }; - let scenarios: Vec<_> = self - .task_store - .read(cx) - .task_inventory() - .map(|item| item.read(cx).list_debug_scenarios(worktree_ids.into_iter())) - .unwrap_or_default(); - - self.candidates = Some(scenarios.clone()); - - scenarios - .into_iter() - .enumerate() - .map(|(index, (_, candidate))| { - StringMatchCandidate::new(index, candidate.label.as_ref()) - }) - .collect() - } - }; - - cx.spawn_in(window, async move |picker, cx| { - let matches = fuzzy::match_strings( - &candidates, - &query, - true, - 1000, - &Default::default(), - cx.background_executor().clone(), - ) - .await; - - picker - .update(cx, |picker, _| { - let delegate = &mut picker.delegate; - - delegate.matches = matches; - delegate.prompt = query; - - if delegate.matches.is_empty() { - delegate.selected_index = 0; - } else { - delegate.selected_index = - delegate.selected_index.min(delegate.matches.len() - 1); - } - }) - .log_err(); - }) - } - - fn confirm( - &mut self, - _: bool, - window: &mut Window, - cx: &mut Context>, - ) { - 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()) - }); - - let Some((task_source_kind, debug_scenario)) = debug_scenario else { - return; - }; - - let task_context = if let TaskSourceKind::Worktree { - id: worktree_id, - directory_in_worktree: _, - id_base: _, - } = task_source_kind - { - let workspace = self.workspace.clone(); - - cx.spawn_in(window, async move |_, cx| { - workspace - .update_in(cx, |workspace, window, cx| { - tasks_ui::task_contexts(workspace, window, cx) - }) - .ok()? - .await - .task_context_for_worktree_id(worktree_id) - .cloned() - }) - } else { - gpui::Task::ready(None) - }; - - cx.spawn_in(window, async move |this, cx| { - let task_context = task_context.await.unwrap_or_default(); - - this.update_in(cx, |this, window, cx| { - this.delegate - .debug_panel - .update(cx, |panel, cx| { - panel.start_session(debug_scenario, task_context, None, window, cx); - }) - .ok(); - - cx.emit(DismissEvent); - }) - .ok(); - }) - .detach(); - } - - fn dismissed(&mut self, _: &mut Window, cx: &mut Context>) { - cx.emit(DismissEvent); - } - - fn render_match( - &self, - ix: usize, - selected: bool, - window: &mut Window, - cx: &mut Context>, - ) -> Option { - let hit = &self.matches[ix]; - - let highlighted_location = HighlightedMatch { - text: hit.string.clone(), - highlight_positions: hit.positions.clone(), - char_count: hit.string.chars().count(), - color: Color::Default, - }; - - let icon = Icon::new(IconName::FileTree) - .color(Color::Muted) - .size(ui::IconSize::Small); - - Some( - ListItem::new(SharedString::from(format!("debug-scenario-selection-{ix}"))) - .inset(true) - .start_slot::(icon) - .spacing(ListItemSpacing::Sparse) - .toggle_state(selected) - .child(highlighted_location.render(window, cx)), + cx.spawn_in(window, async move |picker, cx| { + let matches = fuzzy::match_strings( + &candidates, + &query, + true, + 1000, + &Default::default(), + cx.background_executor().clone(), ) - } + .await; + + picker + .update(cx, |picker, _| { + let delegate = &mut picker.delegate; + + delegate.matches = matches; + delegate.prompt = query; + + if delegate.matches.is_empty() { + delegate.selected_index = 0; + } else { + delegate.selected_index = + delegate.selected_index.min(delegate.matches.len() - 1); + } + }) + .log_err(); + }) + } + + fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context>) { + 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()) + }); + + let Some((task_source_kind, debug_scenario)) = debug_scenario else { + return; + }; + + let task_context = if let TaskSourceKind::Worktree { + id: worktree_id, + directory_in_worktree: _, + id_base: _, + } = task_source_kind + { + let workspace = self.workspace.clone(); + + cx.spawn_in(window, async move |_, cx| { + workspace + .update_in(cx, |workspace, window, cx| { + tasks_ui::task_contexts(workspace, window, cx) + }) + .ok()? + .await + .task_context_for_worktree_id(worktree_id) + .cloned() + }) + } else { + gpui::Task::ready(None) + }; + + cx.spawn_in(window, async move |this, cx| { + let task_context = task_context.await.unwrap_or_default(); + + this.update_in(cx, |this, window, cx| { + this.delegate + .debug_panel + .update(cx, |panel, cx| { + panel.start_session(debug_scenario, task_context, None, window, cx); + }) + .ok(); + + cx.emit(DismissEvent); + }) + .ok(); + }) + .detach(); + } + + fn dismissed(&mut self, _: &mut Window, cx: &mut Context>) { + cx.emit(DismissEvent); + } + + fn render_match( + &self, + ix: usize, + selected: bool, + window: &mut Window, + cx: &mut Context>, + ) -> Option { + let hit = &self.matches[ix]; + + let highlighted_location = HighlightedMatch { + text: hit.string.clone(), + highlight_positions: hit.positions.clone(), + char_count: hit.string.chars().count(), + color: Color::Default, + }; + + let icon = Icon::new(IconName::FileTree) + .color(Color::Muted) + .size(ui::IconSize::Small); + + Some( + ListItem::new(SharedString::from(format!("debug-scenario-selection-{ix}"))) + .inset(true) + .start_slot::(icon) + .spacing(ListItemSpacing::Sparse) + .toggle_state(selected) + .child(highlighted_location.render(window, cx)), + ) } } diff --git a/crates/debugger_ui/src/persistence.rs b/crates/debugger_ui/src/persistence.rs index 0e2ea28fab..ce3a2c0811 100644 --- a/crates/debugger_ui/src/persistence.rs +++ b/crates/debugger_ui/src/persistence.rs @@ -1,5 +1,5 @@ use collections::HashMap; -use dap::Capabilities; +use dap::{Capabilities, adapters::DebugAdapterName}; use db::kvp::KEY_VALUE_STORE; use gpui::{Axis, Context, Entity, EntityId, Focusable, Subscription, WeakEntity, Window}; use project::Project; @@ -90,7 +90,7 @@ pub(crate) struct SerializedPane { const DEBUGGER_PANEL_PREFIX: &str = "debugger_panel_"; pub(crate) async fn serialize_pane_layout( - adapter_name: SharedString, + adapter_name: DebugAdapterName, pane_group: SerializedPaneLayout, ) -> anyhow::Result<()> { if let Ok(serialized_pane_group) = serde_json::to_string(&pane_group) { diff --git a/crates/debugger_ui/src/session/running.rs b/crates/debugger_ui/src/session/running.rs index 5654609a92..3736a673ab 100644 --- a/crates/debugger_ui/src/session/running.rs +++ b/crates/debugger_ui/src/session/running.rs @@ -15,7 +15,9 @@ use breakpoint_list::BreakpointList; use collections::{HashMap, IndexMap}; use console::Console; use dap::{ - Capabilities, RunInTerminalRequestArguments, Thread, client::SessionId, + Capabilities, RunInTerminalRequestArguments, Thread, + adapters::{DebugAdapterName, DebugTaskDefinition}, + client::SessionId, debugger_settings::DebuggerSettings, }; use futures::{SinkExt, channel::mpsc}; @@ -23,6 +25,7 @@ use gpui::{ Action as _, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, Focusable, NoAction, Pixels, Point, Subscription, Task, WeakEntity, }; +use language::Buffer; use loaded_source_list::LoadedSourceList; use module_list::ModuleList; use project::{ @@ -34,6 +37,10 @@ use rpc::proto::ViewId; use serde_json::Value; use settings::Settings; use stack_frame_list::StackFrameList; +use task::{ + DebugScenario, LaunchRequest, TaskContext, substitute_variables_in_map, + substitute_variables_in_str, +}; use terminal_view::TerminalView; use ui::{ ActiveTheme, AnyElement, App, ButtonCommon as _, Clickable as _, Context, ContextMenu, @@ -667,6 +674,143 @@ impl RunningState { self.panes.pane_at_pixel_position(position).is_some() } + pub(crate) fn resolve_scenario( + &self, + scenario: DebugScenario, + task_context: TaskContext, + buffer: Option>, + window: &Window, + cx: &mut Context, + ) -> Task> { + let Some(workspace) = self.workspace.upgrade() else { + return Task::ready(Err(anyhow!("no workspace"))); + }; + let project = workspace.read(cx).project().clone(); + let dap_store = project.read(cx).dap_store().downgrade(); + let task_store = project.read(cx).task_store().downgrade(); + let weak_project = project.downgrade(); + let weak_workspace = workspace.downgrade(); + cx.spawn_in(window, async move |this, cx| { + let DebugScenario { + adapter, + label, + build, + request, + initialize_args, + tcp_connection, + stop_on_entry, + } = scenario; + let request = if let Some(request) = request { + request + } else if let Some(build) = build { + let Some(task) = task_store.update(cx, |this, cx| { + this.task_inventory().and_then(|inventory| { + inventory + .read(cx) + .task_template_by_label(buffer, &build, cx) + }) + })? + else { + anyhow::bail!("Couldn't find task template for {:?}", build) + }; + let Some(task) = task.resolve_task("debug-build-task", &task_context) else { + anyhow::bail!("Could not resolve task variables within a debug scenario"); + }; + + let terminal = project + .update_in(cx, |project, window, cx| { + project.create_terminal( + TerminalKind::Task(task.resolved.clone()), + window.window_handle(), + cx, + ) + })? + .await?; + + let terminal_view = cx.new_window_entity(|window, cx| { + TerminalView::new( + terminal.clone(), + weak_workspace, + None, + weak_project, + false, + window, + cx, + ) + })?; + + this.update_in(cx, |this, window, cx| { + this.ensure_pane_item(DebuggerPaneItem::Terminal, window, cx); + this.debug_terminal.update(cx, |debug_terminal, cx| { + debug_terminal.terminal = Some(terminal_view); + cx.notify(); + }); + })?; + + let exit_status = terminal + .read_with(cx, |terminal, cx| terminal.wait_for_completed_task(cx))? + .await + .ok_or_else(|| anyhow!("Failed to wait for completed task"))?; + + if !exit_status.success() { + anyhow::bail!("Build failed"); + } + + dap_store + .update(cx, |this, cx| this.run_debug_locator(task.resolved, cx))? + .await? + } else { + return Err(anyhow!("No request or build provided")); + }; + let request = match request { + dap::DebugRequest::Launch(launch_request) => { + let cwd = match launch_request.cwd.as_deref().and_then(|path| path.to_str()) { + Some(cwd) => { + let substituted_cwd = substitute_variables_in_str(&cwd, &task_context) + .ok_or_else(|| anyhow!("Failed to substitute variables in cwd"))?; + Some(PathBuf::from(substituted_cwd)) + } + None => None, + }; + + let env = substitute_variables_in_map( + &launch_request.env.into_iter().collect(), + &task_context, + ) + .ok_or_else(|| anyhow!("Failed to substitute variables in env"))? + .into_iter() + .collect(); + let new_launch_request = LaunchRequest { + program: substitute_variables_in_str( + &launch_request.program, + &task_context, + ) + .ok_or_else(|| anyhow!("Failed to substitute variables in program"))?, + args: launch_request + .args + .into_iter() + .map(|arg| substitute_variables_in_str(&arg, &task_context)) + .collect::>>() + .ok_or_else(|| anyhow!("Failed to substitute variables in args"))?, + cwd, + env, + }; + + dap::DebugRequest::Launch(new_launch_request) + } + request @ dap::DebugRequest::Attach(_) => request, + }; + Ok(DebugTaskDefinition { + label, + adapter: DebugAdapterName(adapter), + request, + initialize_args, + stop_on_entry, + tcp_connection, + }) + }) + } + fn handle_run_in_terminal( &self, request: &RunInTerminalRequestArguments, @@ -914,7 +1058,7 @@ impl RunningState { let Some((adapter_name, pane_group)) = this .update(cx, |this, cx| { - let adapter_name = this.session.read(cx).adapter_name(); + let adapter_name = this.session.read(cx).adapter(); ( adapter_name, persistence::build_serialized_pane_layout(&this.panes.root, cx), diff --git a/crates/debugger_ui/src/tests/debugger_panel.rs b/crates/debugger_ui/src/tests/debugger_panel.rs index b44a65cd40..55ab5955cd 100644 --- a/crates/debugger_ui/src/tests/debugger_panel.rs +++ b/crates/debugger_ui/src/tests/debugger_panel.rs @@ -444,11 +444,13 @@ async fn test_handle_start_debugging_request( .read(cx) .session(cx); let parent_session = active_session.read(cx).parent_session().unwrap(); + let mut original_binary = parent_session.read(cx).binary().clone(); + original_binary.request_args = StartDebuggingRequestArguments { + request: StartDebuggingRequestArgumentsRequest::Launch, + configuration: fake_config.clone(), + }; - assert_eq!( - active_session.read(cx).definition(), - parent_session.read(cx).definition() - ); + assert_eq!(active_session.read(cx).binary(), &original_binary); }) .unwrap(); diff --git a/crates/project/src/debugger/dap_store.rs b/crates/project/src/debugger/dap_store.rs index d5a0670545..11cc0c92fb 100644 --- a/crates/project/src/debugger/dap_store.rs +++ b/crates/project/src/debugger/dap_store.rs @@ -50,7 +50,7 @@ use std::{ sync::{Arc, Once}, }; use task::{DebugScenario, SpawnInTerminal}; -use util::ResultExt as _; +use util::{ResultExt as _, merge_json_value_into}; use worktree::Worktree; #[derive(Debug)] @@ -393,7 +393,8 @@ impl DapStore { pub fn new_session( &mut self, - template: DebugTaskDefinition, + label: SharedString, + adapter: DebugAdapterName, parent_session: Option>, cx: &mut Context, ) -> Entity { @@ -409,7 +410,8 @@ impl DapStore { self.breakpoint_store.clone(), session_id, parent_session, - template.clone(), + label, + adapter, cx, ); @@ -435,6 +437,7 @@ impl DapStore { pub fn boot_session( &self, session: Entity, + definition: DebugTaskDefinition, cx: &mut Context, ) -> Task> { let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next() else { @@ -442,17 +445,20 @@ impl DapStore { }; let dap_store = cx.weak_entity(); - let definition = session.read(cx).definition(); cx.spawn({ let session = session.clone(); async move |this, cx| { - let binary = this + let mut binary = this .update(cx, |this, cx| { this.get_debug_adapter_binary(definition.clone(), cx) })? .await?; + if let Some(args) = definition.initialize_args { + merge_json_value_into(args, &mut binary.request_args.configuration); + } + session .update(cx, |session, cx| { session.boot(binary, worktree, dap_store, cx) diff --git a/crates/project/src/debugger/session.rs b/crates/project/src/debugger/session.rs index 9a0cb5e6a4..3054f73903 100644 --- a/crates/project/src/debugger/session.rs +++ b/crates/project/src/debugger/session.rs @@ -12,7 +12,7 @@ use super::dap_command::{ use super::dap_store::DapStore; use anyhow::{Context as _, Result, anyhow}; use collections::{HashMap, HashSet, IndexMap, IndexSet}; -use dap::adapters::{DebugAdapterBinary, DebugTaskDefinition}; +use dap::adapters::{DebugAdapterBinary, DebugAdapterName}; use dap::messages::Response; use dap::requests::{Request, RunInTerminal, StartDebugging}; use dap::{ @@ -32,7 +32,7 @@ use gpui::{ Task, WeakEntity, }; -use serde_json::{Value, json}; +use serde_json::Value; use smol::stream::StreamExt; use std::any::TypeId; use std::collections::BTreeMap; @@ -45,7 +45,7 @@ use std::{ sync::Arc, }; use text::{PointUtf16, ToPointUtf16}; -use util::{ResultExt, merge_json_value_into}; +use util::ResultExt; use worktree::Worktree; #[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Ord, Eq)] @@ -307,18 +307,11 @@ impl LocalMode { fn initialize_sequence( &self, capabilities: &Capabilities, - definition: &DebugTaskDefinition, initialized_rx: oneshot::Receiver<()>, dap_store: WeakEntity, - breakpoint_store: Entity, cx: &App, ) -> Task> { - let mut raw = self.binary.request_args.clone(); - - merge_json_value_into( - definition.initialize_args.clone().unwrap_or(json!({})), - &mut raw.configuration, - ); + let raw = self.binary.request_args.clone(); // Of relevance: https://github.com/microsoft/vscode/issues/4902#issuecomment-368583522 let launch = match raw.request { @@ -349,6 +342,8 @@ impl LocalMode { let worktree = self.worktree().clone(); let configuration_sequence = cx.spawn({ async move |cx| { + let breakpoint_store = + dap_store.update(cx, |dap_store, _| dap_store.breakpoint_store().clone())?; initialized_rx.await?; let errors_by_path = cx .update(|cx| this.send_source_breakpoints(false, &breakpoint_store, cx))? @@ -505,9 +500,10 @@ pub struct OutputToken(pub usize); /// Represents a current state of a single debug adapter and provides ways to mutate it. pub struct Session { pub mode: Mode, - definition: DebugTaskDefinition, - pub(super) capabilities: Capabilities, id: SessionId, + label: SharedString, + adapter: DebugAdapterName, + pub(super) capabilities: Capabilities, child_session_ids: HashSet, parent_session: Option>, modules: Vec, @@ -636,7 +632,8 @@ impl Session { breakpoint_store: Entity, session_id: SessionId, parent_session: Option>, - template: DebugTaskDefinition, + label: SharedString, + adapter: DebugAdapterName, cx: &mut App, ) -> Entity { cx.new::(|cx| { @@ -685,7 +682,8 @@ impl Session { ignore_breakpoints: false, breakpoint_store, exception_breakpoints: Default::default(), - definition: template, + label, + adapter, }; this @@ -805,16 +803,12 @@ impl Session { &local_mode.binary } - pub fn adapter_name(&self) -> SharedString { - self.definition.adapter.clone() + pub fn adapter(&self) -> DebugAdapterName { + self.adapter.clone() } pub fn label(&self) -> SharedString { - self.definition.label.clone() - } - - pub fn definition(&self) -> DebugTaskDefinition { - self.definition.clone() + self.label.clone() } pub fn is_terminated(&self) -> bool { @@ -943,7 +937,7 @@ impl Session { } pub(super) fn request_initialize(&mut self, cx: &mut Context) -> Task> { - let adapter_id = String::from(self.definition.adapter.clone()); + let adapter_id = self.adapter().to_string(); let request = Initialize { adapter_id }; match &self.mode { Mode::Running(local_mode) => { @@ -983,14 +977,9 @@ impl Session { cx: &mut Context, ) -> Task> { match &self.mode { - Mode::Running(local_mode) => local_mode.initialize_sequence( - &self.capabilities, - &self.definition, - initialize_rx, - dap_store, - self.breakpoint_store.clone(), - cx, - ), + Mode::Running(local_mode) => { + local_mode.initialize_sequence(&self.capabilities, initialize_rx, dap_store, cx) + } Mode::Building => Task::ready(Err(anyhow!("cannot initialize, still building"))), } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index ba388186a4..7c693cca5c 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -66,7 +66,7 @@ use image_store::{ImageItemEvent, ImageStoreEvent}; use ::git::{blame::Blame, status::FileStatus}; use gpui::{ AnyEntity, App, AppContext, AsyncApp, BorrowAppContext, Context, Entity, EventEmitter, Hsla, - SharedString, Task, WeakEntity, Window, prelude::FluentBuilder, + SharedString, Task, WeakEntity, Window, }; use itertools::Itertools; use language::{ @@ -3565,10 +3565,9 @@ impl Project { ) -> Task>> { let snapshot = buffer_handle.read(cx).snapshot(); - let Some(inline_value_provider) = session - .read(cx) - .adapter_name() - .map(|adapter_name| DapRegistry::global(cx).adapter(&adapter_name)) + let adapter = session.read(cx).adapter(); + let Some(inline_value_provider) = DapRegistry::global(cx) + .adapter(&adapter) .and_then(|adapter| adapter.inline_value_provider()) else { return Task::ready(Err(anyhow::anyhow!("Inline value provider not found"))); diff --git a/crates/task/src/lib.rs b/crates/task/src/lib.rs index c400f0e0f0..1e7b93a161 100644 --- a/crates/task/src/lib.rs +++ b/crates/task/src/lib.rs @@ -20,7 +20,8 @@ pub use debug_format::{ }; pub use task_template::{ DebugArgsRequest, HideStrategy, RevealStrategy, TaskTemplate, TaskTemplates, - substitute_all_template_variables_in_str, + substitute_all_template_variables_in_str, substitute_variables_in_map, + substitute_variables_in_str, }; pub use vscode_debug_format::VsCodeDebugTaskFile; pub use vscode_format::VsCodeTaskFile; diff --git a/crates/task/src/task_template.rs b/crates/task/src/task_template.rs index b1958037fb..a39d6395b6 100644 --- a/crates/task/src/task_template.rs +++ b/crates/task/src/task_template.rs @@ -293,6 +293,28 @@ fn to_hex_hash(object: impl Serialize) -> anyhow::Result { Ok(hex::encode(hasher.finalize())) } +pub fn substitute_variables_in_str(template_str: &str, context: &TaskContext) -> Option { + let mut variable_names = HashMap::default(); + let mut substituted_variables = HashSet::default(); + let task_variables = context + .task_variables + .0 + .iter() + .map(|(key, value)| { + let key_string = key.to_string(); + if !variable_names.contains_key(&key_string) { + variable_names.insert(key_string.clone(), key.clone()); + } + (key_string, value.as_str()) + }) + .collect::>(); + substitute_all_template_variables_in_str( + template_str, + &task_variables, + &variable_names, + &mut substituted_variables, + ) +} pub fn substitute_all_template_variables_in_str>( template_str: &str, task_variables: &HashMap, @@ -349,6 +371,31 @@ fn substitute_all_template_variables_in_vec( Some(expanded) } +pub fn substitute_variables_in_map( + keys_and_values: &HashMap, + context: &TaskContext, +) -> Option> { + let mut variable_names = HashMap::default(); + let mut substituted_variables = HashSet::default(); + let task_variables = context + .task_variables + .0 + .iter() + .map(|(key, value)| { + let key_string = key.to_string(); + if !variable_names.contains_key(&key_string) { + variable_names.insert(key_string.clone(), key.clone()); + } + (key_string, value.as_str()) + }) + .collect::>(); + substitute_all_template_variables_in_map( + keys_and_values, + &task_variables, + &variable_names, + &mut substituted_variables, + ) +} fn substitute_all_template_variables_in_map( keys_and_values: &HashMap, task_variables: &HashMap,