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>
This commit is contained in:
parent
c12e6376b8
commit
ff215b4f11
12 changed files with 695 additions and 622 deletions
|
@ -88,7 +88,7 @@ impl<'a> From<&'a str> for DebugAdapterName {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct TcpArguments {
|
pub struct TcpArguments {
|
||||||
pub host: Ipv4Addr,
|
pub host: Ipv4Addr,
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
|
@ -127,7 +127,7 @@ impl TcpArguments {
|
||||||
)]
|
)]
|
||||||
pub struct DebugTaskDefinition {
|
pub struct DebugTaskDefinition {
|
||||||
pub label: SharedString,
|
pub label: SharedString,
|
||||||
pub adapter: SharedString,
|
pub adapter: DebugAdapterName,
|
||||||
pub request: DebugRequest,
|
pub request: DebugRequest,
|
||||||
/// Additional initialization arguments to be sent on DAP initialization
|
/// Additional initialization arguments to be sent on DAP initialization
|
||||||
pub initialize_args: Option<serde_json::Value>,
|
pub initialize_args: Option<serde_json::Value>,
|
||||||
|
@ -153,7 +153,7 @@ impl DebugTaskDefinition {
|
||||||
pub fn to_scenario(&self) -> DebugScenario {
|
pub fn to_scenario(&self) -> DebugScenario {
|
||||||
DebugScenario {
|
DebugScenario {
|
||||||
label: self.label.clone(),
|
label: self.label.clone(),
|
||||||
adapter: self.adapter.clone(),
|
adapter: self.adapter.clone().into(),
|
||||||
build: None,
|
build: None,
|
||||||
request: Some(self.request.clone()),
|
request: Some(self.request.clone()),
|
||||||
stop_on_entry: self.stop_on_entry,
|
stop_on_entry: self.stop_on_entry,
|
||||||
|
@ -207,7 +207,7 @@ impl DebugTaskDefinition {
|
||||||
.map(TcpArgumentsTemplate::from_proto)
|
.map(TcpArgumentsTemplate::from_proto)
|
||||||
.transpose()?,
|
.transpose()?,
|
||||||
stop_on_entry: proto.stop_on_entry,
|
stop_on_entry: proto.stop_on_entry,
|
||||||
adapter: proto.adapter.into(),
|
adapter: DebugAdapterName(proto.adapter.into()),
|
||||||
request: match request {
|
request: match request {
|
||||||
proto::debug_task_definition::Request::DebugAttachRequest(config) => {
|
proto::debug_task_definition::Request::DebugAttachRequest(config) => {
|
||||||
DebugRequest::Attach(AttachRequest {
|
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.
|
/// 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 struct DebugAdapterBinary {
|
||||||
pub command: String,
|
pub command: String,
|
||||||
pub arguments: Vec<String>,
|
pub arguments: Vec<String>,
|
||||||
|
|
|
@ -568,11 +568,11 @@ impl DapLogView {
|
||||||
.sessions()
|
.sessions()
|
||||||
.filter_map(|session| {
|
.filter_map(|session| {
|
||||||
let session = session.read(cx);
|
let session = session.read(cx);
|
||||||
session.adapter_name();
|
session.adapter();
|
||||||
let client = session.adapter_client()?;
|
let client = session.adapter_client()?;
|
||||||
Some(DapMenuItem {
|
Some(DapMenuItem {
|
||||||
client_id: client.id(),
|
client_id: client.id(),
|
||||||
client_name: session.adapter_name().to_string(),
|
client_name: session.adapter().to_string(),
|
||||||
has_adapter_logs: client.has_adapter_logs(),
|
has_adapter_logs: client.has_adapter_logs(),
|
||||||
selected_entry: self.current_view.map_or(LogKind::Adapter, |(_, kind)| kind),
|
selected_entry: self.current_view.map_or(LogKind::Adapter, |(_, kind)| kind),
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,10 +6,9 @@ use crate::{
|
||||||
persistence,
|
persistence,
|
||||||
};
|
};
|
||||||
use crate::{new_session_modal::NewSessionModal, session::DebugSession};
|
use crate::{new_session_modal::NewSessionModal, session::DebugSession};
|
||||||
use anyhow::{Context as _, Result, anyhow};
|
use anyhow::Result;
|
||||||
use collections::{HashMap, HashSet};
|
|
||||||
use command_palette_hooks::CommandPaletteFilter;
|
use command_palette_hooks::CommandPaletteFilter;
|
||||||
use dap::DebugRequest;
|
use dap::adapters::DebugAdapterName;
|
||||||
use dap::{
|
use dap::{
|
||||||
ContinuedEvent, LoadedSourceEvent, ModuleEvent, OutputEvent, StoppedEvent, ThreadEvent,
|
ContinuedEvent, LoadedSourceEvent, ModuleEvent, OutputEvent, StoppedEvent, ThreadEvent,
|
||||||
client::SessionId, debugger_settings::DebuggerSettings,
|
client::SessionId, debugger_settings::DebuggerSettings,
|
||||||
|
@ -27,7 +26,6 @@ use project::{Project, debugger::session::ThreadStatus};
|
||||||
use rpc::proto::{self};
|
use rpc::proto::{self};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
use std::path::PathBuf;
|
|
||||||
use task::{DebugScenario, TaskContext};
|
use task::{DebugScenario, TaskContext};
|
||||||
use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*};
|
use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*};
|
||||||
use workspace::SplitDirection;
|
use workspace::SplitDirection;
|
||||||
|
@ -200,40 +198,6 @@ impl DebugPanel {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_from_definition(
|
|
||||||
&mut self,
|
|
||||||
definition: DebugTaskDefinition,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Task<Result<()>> {
|
|
||||||
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(
|
pub fn start_session(
|
||||||
&mut self,
|
&mut self,
|
||||||
scenario: DebugScenario,
|
scenario: DebugScenario,
|
||||||
|
@ -242,16 +206,56 @@ impl DebugPanel {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
let dap_store = self.project.read(cx).dap_store();
|
||||||
let definition = this
|
let workspace = self.workspace.clone();
|
||||||
.update_in(cx, |this, window, cx| {
|
let session = dap_store.update(cx, |dap_store, cx| {
|
||||||
this.resolve_scenario(scenario, task_context, active_buffer, window, cx)
|
dap_store.new_session(
|
||||||
})?
|
scenario.label.clone(),
|
||||||
.await?;
|
DebugAdapterName(scenario.adapter.clone()),
|
||||||
this.update_in(cx, |this, window, cx| {
|
None,
|
||||||
this.start_from_definition(definition, window, cx)
|
cx,
|
||||||
})?
|
)
|
||||||
.await
|
});
|
||||||
|
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);
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
|
@ -260,33 +264,15 @@ impl DebugPanel {
|
||||||
this: WeakEntity<Self>,
|
this: WeakEntity<Self>,
|
||||||
session: Entity<Session>,
|
session: Entity<Session>,
|
||||||
cx: &mut AsyncWindowContext,
|
cx: &mut AsyncWindowContext,
|
||||||
) -> Result<()> {
|
) -> Result<Entity<DebugSession>> {
|
||||||
let adapter_name = session.update(cx, |session, _| session.adapter_name())?;
|
let adapter_name = session.update(cx, |session, _| session.adapter())?;
|
||||||
this.update_in(cx, |_, window, cx| {
|
this.update_in(cx, |_, window, cx| {
|
||||||
cx.subscribe_in(
|
cx.subscribe_in(
|
||||||
&session,
|
&session,
|
||||||
window,
|
window,
|
||||||
move |this, session, event: &SessionStateEvent, window, cx| match event {
|
move |this, session, event: &SessionStateEvent, window, cx| match event {
|
||||||
SessionStateEvent::Restart => {
|
SessionStateEvent::Restart => {
|
||||||
let mut curr_session = session.clone();
|
this.handle_restart_request(session.clone(), window, cx);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
SessionStateEvent::SpawnChildSession { request } => {
|
SessionStateEvent::SpawnChildSession { request } => {
|
||||||
this.handle_start_debugging_request(request, session.clone(), window, cx);
|
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 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| {
|
this.sessions.retain(|session| {
|
||||||
session
|
session
|
||||||
.read(cx)
|
.read(cx)
|
||||||
|
@ -311,7 +297,7 @@ impl DebugPanel {
|
||||||
.is_terminated()
|
.is_terminated()
|
||||||
});
|
});
|
||||||
|
|
||||||
let session_item = DebugSession::running(
|
let debug_session = DebugSession::running(
|
||||||
this.project.clone(),
|
this.project.clone(),
|
||||||
this.workspace.clone(),
|
this.workspace.clone(),
|
||||||
session,
|
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
|
// 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
|
// This is used to filter the command menu correctly
|
||||||
cx.observe(
|
cx.observe(
|
||||||
&session_item.read(cx).running_state().clone(),
|
&debug_session.read(cx).running_state().clone(),
|
||||||
|_, _, cx| cx.notify(),
|
|_, _, cx| cx.notify(),
|
||||||
)
|
)
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
this.sessions.push(session_item.clone());
|
this.sessions.push(debug_session.clone());
|
||||||
this.activate_session(session_item, window, cx);
|
this.activate_session(debug_session.clone(), window, cx);
|
||||||
this.workspace.clone()
|
|
||||||
|
(debug_session, this.workspace.clone())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
workspace.update_in(cx, |workspace, window, cx| {
|
workspace.update_in(cx, |workspace, window, cx| {
|
||||||
workspace.focus_panel::<Self>(window, cx);
|
workspace.focus_panel::<Self>(window, cx);
|
||||||
})?;
|
})?;
|
||||||
Ok(())
|
|
||||||
|
Ok(debug_session)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_restart_request(
|
||||||
|
&mut self,
|
||||||
|
mut curr_session: Entity<Session>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
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(
|
pub fn handle_start_debugging_request(
|
||||||
|
@ -353,40 +381,23 @@ impl DebugPanel {
|
||||||
};
|
};
|
||||||
|
|
||||||
let dap_store_handle = self.project.read(cx).dap_store().clone();
|
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();
|
let mut binary = parent_session.read(cx).binary().clone();
|
||||||
binary.request_args = request.clone();
|
binary.request_args = request.clone();
|
||||||
|
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let (session, task) = dap_store_handle.update(cx, |dap_store, cx| {
|
let (session, task) = dap_store_handle.update(cx, |dap_store, cx| {
|
||||||
let session =
|
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| {
|
let task = session.update(cx, |session, cx| {
|
||||||
session.boot(binary, worktree, dap_store_handle.downgrade(), cx)
|
session.boot(binary, worktree, dap_store_handle.downgrade(), cx)
|
||||||
});
|
});
|
||||||
(session, task)
|
(session, task)
|
||||||
})?;
|
})?;
|
||||||
|
Self::register_session(this, session, cx).await?;
|
||||||
match task.await {
|
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(())
|
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
|
@ -394,127 +405,6 @@ impl DebugPanel {
|
||||||
pub fn active_session(&self) -> Option<Entity<DebugSession>> {
|
pub fn active_session(&self) -> Option<Entity<DebugSession>> {
|
||||||
self.active_session.clone()
|
self.active_session.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve_scenario(
|
|
||||||
&self,
|
|
||||||
scenario: DebugScenario,
|
|
||||||
task_context: TaskContext,
|
|
||||||
buffer: Option<Entity<Buffer>>,
|
|
||||||
window: &Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Task<Result<DebugTaskDefinition>> {
|
|
||||||
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::<HashMap<_, _>>();
|
|
||||||
|
|
||||||
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<Self>) {
|
fn close_session(&mut self, entity_id: EntityId, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let Some(session) = self
|
let Some(session) = self
|
||||||
.sessions
|
.sessions
|
||||||
|
|
|
@ -4,7 +4,10 @@ use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use dap::{DapRegistry, DebugRequest, adapters::DebugTaskDefinition};
|
use dap::{
|
||||||
|
DapRegistry, DebugRequest,
|
||||||
|
adapters::{DebugAdapterName, DebugTaskDefinition},
|
||||||
|
};
|
||||||
use editor::{Editor, EditorElement, EditorStyle};
|
use editor::{Editor, EditorElement, EditorStyle};
|
||||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
@ -13,9 +16,9 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch};
|
use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch};
|
||||||
use project::{TaskSourceKind, task_store::TaskStore};
|
use project::{TaskSourceKind, task_store::TaskStore};
|
||||||
use session_modes::{AttachMode, DebugScenarioDelegate, LaunchMode};
|
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use task::{DebugScenario, LaunchRequest};
|
use task::{DebugScenario, LaunchRequest};
|
||||||
|
use tasks_ui::task_contexts;
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::{
|
use ui::{
|
||||||
ActiveTheme, Button, ButtonCommon, ButtonSize, CheckboxWithLabel, Clickable, Color, Context,
|
ActiveTheme, Button, ButtonCommon, ButtonSize, CheckboxWithLabel, Clickable, Color, Context,
|
||||||
|
@ -36,7 +39,7 @@ pub(super) struct NewSessionModal {
|
||||||
mode: NewSessionMode,
|
mode: NewSessionMode,
|
||||||
stop_on_entry: ToggleState,
|
stop_on_entry: ToggleState,
|
||||||
initialize_args: Option<serde_json::Value>,
|
initialize_args: Option<serde_json::Value>,
|
||||||
debugger: Option<SharedString>,
|
debugger: Option<DebugAdapterName>,
|
||||||
last_selected_profile_name: Option<SharedString>,
|
last_selected_profile_name: Option<SharedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,16 +146,19 @@ impl NewSessionModal {
|
||||||
|
|
||||||
let debug_panel = self.debug_panel.clone();
|
let debug_panel = self.debug_panel.clone();
|
||||||
let workspace = self.workspace.clone();
|
let workspace = self.workspace.clone();
|
||||||
|
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let task_contexts = workspace
|
let task_contexts = workspace
|
||||||
.update_in(cx, |workspace, window, cx| {
|
.update_in(cx, |this, window, cx| task_contexts(this, window, cx))?
|
||||||
tasks_ui::task_contexts(workspace, window, cx)
|
|
||||||
})?
|
|
||||||
.await;
|
.await;
|
||||||
|
let task_context = task_contexts
|
||||||
let task_context = task_contexts.active_context().cloned().unwrap_or_default();
|
.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.update_in(cx, |debug_panel, window, cx| {
|
||||||
debug_panel.start_session(config, task_context, None, window, cx)
|
debug_panel.start_session(config, task_context, None, window, cx)
|
||||||
})?;
|
})?;
|
||||||
|
@ -167,18 +173,17 @@ impl NewSessionModal {
|
||||||
|
|
||||||
fn update_attach_picker(
|
fn update_attach_picker(
|
||||||
attach: &Entity<AttachMode>,
|
attach: &Entity<AttachMode>,
|
||||||
selected_debugger: &str,
|
adapter: &DebugAdapterName,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) {
|
) {
|
||||||
attach.update(cx, |this, cx| {
|
attach.update(cx, |this, cx| {
|
||||||
if selected_debugger != this.definition.adapter.as_ref() {
|
if adapter != &this.definition.adapter {
|
||||||
let adapter: SharedString = selected_debugger.to_owned().into();
|
|
||||||
this.definition.adapter = adapter.clone();
|
this.definition.adapter = adapter.clone();
|
||||||
|
|
||||||
this.attach_picker.update(cx, |this, cx| {
|
this.attach_picker.update(cx, |this, cx| {
|
||||||
this.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);
|
this.focus(window, cx);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
@ -194,15 +199,16 @@ impl NewSessionModal {
|
||||||
) -> ui::DropdownMenu {
|
) -> ui::DropdownMenu {
|
||||||
let workspace = self.workspace.clone();
|
let workspace = self.workspace.clone();
|
||||||
let weak = cx.weak_entity();
|
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(
|
DropdownMenu::new(
|
||||||
"dap-adapter-picker",
|
"dap-adapter-picker",
|
||||||
debugger
|
label,
|
||||||
.as_ref()
|
|
||||||
.unwrap_or_else(|| &SELECT_DEBUGGER_LABEL)
|
|
||||||
.clone(),
|
|
||||||
ContextMenu::build(window, cx, move |mut menu, _, cx| {
|
ContextMenu::build(window, cx, move |mut menu, _, cx| {
|
||||||
let setter_for_name = |name: SharedString| {
|
let setter_for_name = |name: DebugAdapterName| {
|
||||||
let weak = weak.clone();
|
let weak = weak.clone();
|
||||||
move |window: &mut Window, cx: &mut App| {
|
move |window: &mut Window, cx: &mut App| {
|
||||||
weak.update(cx, |this, cx| {
|
weak.update(cx, |this, cx| {
|
||||||
|
@ -222,7 +228,7 @@ impl NewSessionModal {
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
for adapter in available_adapters {
|
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
|
menu
|
||||||
}),
|
}),
|
||||||
|
@ -251,7 +257,7 @@ impl NewSessionModal {
|
||||||
move |window: &mut Window, cx: &mut App| {
|
move |window: &mut Window, cx: &mut App| {
|
||||||
weak.update(cx, |this, cx| {
|
weak.update(cx, |this, cx| {
|
||||||
this.last_selected_profile_name = Some(SharedString::from(&task.label));
|
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();
|
this.initialize_args = task.initialize_args.clone();
|
||||||
match &task.request {
|
match &task.request {
|
||||||
Some(DebugRequest::Launch(launch_config)) => {
|
Some(DebugRequest::Launch(launch_config)) => {
|
||||||
|
@ -374,7 +380,7 @@ impl NewSessionMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn attach(
|
fn attach(
|
||||||
debugger: Option<SharedString>,
|
debugger: Option<DebugAdapterName>,
|
||||||
workspace: Entity<Workspace>,
|
workspace: Entity<Workspace>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<NewSessionModal>,
|
cx: &mut Context<NewSessionModal>,
|
||||||
|
@ -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 {
|
impl RenderOnce for NewSessionMode {
|
||||||
fn render(self, window: &mut Window, cx: &mut App) -> impl ui::IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl ui::IntoElement {
|
||||||
match self {
|
match self {
|
||||||
|
@ -684,318 +655,342 @@ impl Focusable for NewSessionModal {
|
||||||
|
|
||||||
impl ModalView for NewSessionModal {}
|
impl ModalView for NewSessionModal {}
|
||||||
|
|
||||||
// This module makes sure that the modes setup the correct subscriptions whenever they're created
|
impl RenderOnce for LaunchMode {
|
||||||
mod session_modes {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
use std::rc::Rc;
|
v_flex()
|
||||||
|
.p_2()
|
||||||
use super::*;
|
.w_full()
|
||||||
|
.gap_3()
|
||||||
#[derive(Clone)]
|
.track_focus(&self.program.focus_handle(cx))
|
||||||
#[non_exhaustive]
|
.child(
|
||||||
pub(super) struct LaunchMode {
|
div().child(
|
||||||
pub(super) program: Entity<Editor>,
|
Label::new("Program")
|
||||||
pub(super) cwd: Entity<Editor>,
|
.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 {
|
impl RenderOnce for AttachMode {
|
||||||
pub(super) fn new(
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
past_launch_config: Option<LaunchRequest>,
|
v_flex()
|
||||||
window: &mut Window,
|
.w_full()
|
||||||
cx: &mut App,
|
.track_focus(&self.attach_picker.focus_handle(cx))
|
||||||
) -> Entity<Self> {
|
.child(self.attach_picker.clone())
|
||||||
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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
use std::rc::Rc;
|
||||||
pub(super) struct AttachMode {
|
|
||||||
pub(super) definition: DebugTaskDefinition,
|
|
||||||
pub(super) attach_picker: Entity<AttachModal>,
|
|
||||||
_subscription: Rc<Subscription>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AttachMode {
|
#[derive(Clone)]
|
||||||
pub(super) fn new(
|
pub(super) struct LaunchMode {
|
||||||
debugger: Option<SharedString>,
|
program: Entity<Editor>,
|
||||||
workspace: Entity<Workspace>,
|
cwd: Entity<Editor>,
|
||||||
window: &mut Window,
|
}
|
||||||
cx: &mut Context<NewSessionModal>,
|
|
||||||
) -> Entity<Self> {
|
impl LaunchMode {
|
||||||
let definition = DebugTaskDefinition {
|
pub(super) fn new(
|
||||||
adapter: debugger.clone().unwrap_or_default(),
|
past_launch_config: Option<LaunchRequest>,
|
||||||
label: "Attach New Session Setup".into(),
|
window: &mut Window,
|
||||||
request: dap::DebugRequest::Attach(task::AttachRequest { process_id: None }),
|
cx: &mut App,
|
||||||
initialize_args: None,
|
) -> Entity<Self> {
|
||||||
tcp_connection: None,
|
let (past_program, past_cwd) = past_launch_config
|
||||||
stop_on_entry: Some(false),
|
.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);
|
let cwd = cx.new(|cx| Editor::single_line(window, cx));
|
||||||
window.focus(&modal.focus_handle(cx));
|
cwd.update(cx, |this, cx| {
|
||||||
|
this.set_placeholder_text("Working Directory", cx);
|
||||||
modal
|
if let Some(past_cwd) = past_cwd {
|
||||||
});
|
this.set_text(past_cwd.to_string_lossy(), window, cx);
|
||||||
|
};
|
||||||
let subscription = cx.subscribe(&attach_picker, |_, _, _, cx| {
|
});
|
||||||
cx.emit(DismissEvent);
|
cx.new(|_| Self { program, cwd })
|
||||||
});
|
|
||||||
|
|
||||||
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 {
|
pub(super) fn debug_task(&self, cx: &App) -> task::LaunchRequest {
|
||||||
task_store: Entity<TaskStore>,
|
let path = self.cwd.read(cx).text(cx);
|
||||||
candidates: Option<Vec<(TaskSourceKind, DebugScenario)>>,
|
task::LaunchRequest {
|
||||||
selected_index: usize,
|
program: self.program.read(cx).text(cx),
|
||||||
matches: Vec<StringMatch>,
|
cwd: path.is_empty().not().then(|| PathBuf::from(path)),
|
||||||
prompt: String,
|
args: Default::default(),
|
||||||
|
env: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(super) struct AttachMode {
|
||||||
|
pub(super) definition: DebugTaskDefinition,
|
||||||
|
pub(super) attach_picker: Entity<AttachModal>,
|
||||||
|
_subscription: Rc<Subscription>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AttachMode {
|
||||||
|
pub(super) fn new(
|
||||||
|
debugger: Option<DebugAdapterName>,
|
||||||
|
workspace: Entity<Workspace>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<NewSessionModal>,
|
||||||
|
) -> Entity<Self> {
|
||||||
|
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<TaskStore>,
|
||||||
|
candidates: Option<Vec<(TaskSourceKind, DebugScenario)>>,
|
||||||
|
selected_index: usize,
|
||||||
|
matches: Vec<StringMatch>,
|
||||||
|
prompt: String,
|
||||||
|
debug_panel: WeakEntity<DebugPanel>,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugScenarioDelegate {
|
||||||
|
pub(super) fn new(
|
||||||
debug_panel: WeakEntity<DebugPanel>,
|
debug_panel: WeakEntity<DebugPanel>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
}
|
task_store: Entity<TaskStore>,
|
||||||
|
) -> Self {
|
||||||
impl DebugScenarioDelegate {
|
Self {
|
||||||
pub(super) fn new(
|
task_store,
|
||||||
debug_panel: WeakEntity<DebugPanel>,
|
candidates: None,
|
||||||
workspace: WeakEntity<Workspace>,
|
selected_index: 0,
|
||||||
task_store: Entity<TaskStore>,
|
matches: Vec::new(),
|
||||||
) -> Self {
|
prompt: String::new(),
|
||||||
Self {
|
debug_panel,
|
||||||
task_store,
|
workspace,
|
||||||
candidates: None,
|
|
||||||
selected_index: 0,
|
|
||||||
matches: Vec::new(),
|
|
||||||
prompt: String::new(),
|
|
||||||
debug_panel,
|
|
||||||
workspace,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PickerDelegate for DebugScenarioDelegate {
|
impl PickerDelegate for DebugScenarioDelegate {
|
||||||
type ListItem = ui::ListItem;
|
type ListItem = ui::ListItem;
|
||||||
|
|
||||||
fn match_count(&self) -> usize {
|
fn match_count(&self) -> usize {
|
||||||
self.matches.len()
|
self.matches.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selected_index(&self) -> usize {
|
fn selected_index(&self) -> usize {
|
||||||
self.selected_index
|
self.selected_index
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_selected_index(
|
fn set_selected_index(
|
||||||
&mut self,
|
&mut self,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
_window: &mut Window,
|
_window: &mut Window,
|
||||||
_cx: &mut Context<picker::Picker<Self>>,
|
_cx: &mut Context<picker::Picker<Self>>,
|
||||||
) {
|
) {
|
||||||
self.selected_index = ix;
|
self.selected_index = ix;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> std::sync::Arc<str> {
|
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> std::sync::Arc<str> {
|
||||||
"".into()
|
"".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_matches(
|
fn update_matches(
|
||||||
&mut self,
|
&mut self,
|
||||||
query: String,
|
query: String,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<picker::Picker<Self>>,
|
cx: &mut Context<picker::Picker<Self>>,
|
||||||
) -> gpui::Task<()> {
|
) -> gpui::Task<()> {
|
||||||
let candidates: Vec<_> = match &self.candidates {
|
let candidates: Vec<_> = match &self.candidates {
|
||||||
Some(candidates) => 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()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(index, (_, candidate))| {
|
.map(|(index, (_, candidate))| {
|
||||||
StringMatchCandidate::new(index, candidate.label.as_ref())
|
StringMatchCandidate::new(index, candidate.label.as_ref())
|
||||||
})
|
})
|
||||||
.collect(),
|
.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
|
cx.spawn_in(window, async move |picker, cx| {
|
||||||
.task_store
|
let matches = fuzzy::match_strings(
|
||||||
.read(cx)
|
&candidates,
|
||||||
.task_inventory()
|
&query,
|
||||||
.map(|item| item.read(cx).list_debug_scenarios(worktree_ids.into_iter()))
|
true,
|
||||||
.unwrap_or_default();
|
1000,
|
||||||
|
&Default::default(),
|
||||||
self.candidates = Some(scenarios.clone());
|
cx.background_executor().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<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())
|
|
||||||
});
|
|
||||||
|
|
||||||
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<picker::Picker<Self>>) {
|
|
||||||
cx.emit(DismissEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_match(
|
|
||||||
&self,
|
|
||||||
ix: usize,
|
|
||||||
selected: bool,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<picker::Picker<Self>>,
|
|
||||||
) -> Option<Self::ListItem> {
|
|
||||||
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>(icon)
|
|
||||||
.spacing(ListItemSpacing::Sparse)
|
|
||||||
.toggle_state(selected)
|
|
||||||
.child(highlighted_location.render(window, cx)),
|
|
||||||
)
|
)
|
||||||
}
|
.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<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())
|
||||||
|
});
|
||||||
|
|
||||||
|
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<picker::Picker<Self>>) {
|
||||||
|
cx.emit(DismissEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_match(
|
||||||
|
&self,
|
||||||
|
ix: usize,
|
||||||
|
selected: bool,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<picker::Picker<Self>>,
|
||||||
|
) -> Option<Self::ListItem> {
|
||||||
|
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>(icon)
|
||||||
|
.spacing(ListItemSpacing::Sparse)
|
||||||
|
.toggle_state(selected)
|
||||||
|
.child(highlighted_location.render(window, cx)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use dap::Capabilities;
|
use dap::{Capabilities, adapters::DebugAdapterName};
|
||||||
use db::kvp::KEY_VALUE_STORE;
|
use db::kvp::KEY_VALUE_STORE;
|
||||||
use gpui::{Axis, Context, Entity, EntityId, Focusable, Subscription, WeakEntity, Window};
|
use gpui::{Axis, Context, Entity, EntityId, Focusable, Subscription, WeakEntity, Window};
|
||||||
use project::Project;
|
use project::Project;
|
||||||
|
@ -90,7 +90,7 @@ pub(crate) struct SerializedPane {
|
||||||
const DEBUGGER_PANEL_PREFIX: &str = "debugger_panel_";
|
const DEBUGGER_PANEL_PREFIX: &str = "debugger_panel_";
|
||||||
|
|
||||||
pub(crate) async fn serialize_pane_layout(
|
pub(crate) async fn serialize_pane_layout(
|
||||||
adapter_name: SharedString,
|
adapter_name: DebugAdapterName,
|
||||||
pane_group: SerializedPaneLayout,
|
pane_group: SerializedPaneLayout,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
if let Ok(serialized_pane_group) = serde_json::to_string(&pane_group) {
|
if let Ok(serialized_pane_group) = serde_json::to_string(&pane_group) {
|
||||||
|
|
|
@ -15,7 +15,9 @@ use breakpoint_list::BreakpointList;
|
||||||
use collections::{HashMap, IndexMap};
|
use collections::{HashMap, IndexMap};
|
||||||
use console::Console;
|
use console::Console;
|
||||||
use dap::{
|
use dap::{
|
||||||
Capabilities, RunInTerminalRequestArguments, Thread, client::SessionId,
|
Capabilities, RunInTerminalRequestArguments, Thread,
|
||||||
|
adapters::{DebugAdapterName, DebugTaskDefinition},
|
||||||
|
client::SessionId,
|
||||||
debugger_settings::DebuggerSettings,
|
debugger_settings::DebuggerSettings,
|
||||||
};
|
};
|
||||||
use futures::{SinkExt, channel::mpsc};
|
use futures::{SinkExt, channel::mpsc};
|
||||||
|
@ -23,6 +25,7 @@ use gpui::{
|
||||||
Action as _, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
|
Action as _, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
|
||||||
NoAction, Pixels, Point, Subscription, Task, WeakEntity,
|
NoAction, Pixels, Point, Subscription, Task, WeakEntity,
|
||||||
};
|
};
|
||||||
|
use language::Buffer;
|
||||||
use loaded_source_list::LoadedSourceList;
|
use loaded_source_list::LoadedSourceList;
|
||||||
use module_list::ModuleList;
|
use module_list::ModuleList;
|
||||||
use project::{
|
use project::{
|
||||||
|
@ -34,6 +37,10 @@ use rpc::proto::ViewId;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use stack_frame_list::StackFrameList;
|
use stack_frame_list::StackFrameList;
|
||||||
|
use task::{
|
||||||
|
DebugScenario, LaunchRequest, TaskContext, substitute_variables_in_map,
|
||||||
|
substitute_variables_in_str,
|
||||||
|
};
|
||||||
use terminal_view::TerminalView;
|
use terminal_view::TerminalView;
|
||||||
use ui::{
|
use ui::{
|
||||||
ActiveTheme, AnyElement, App, ButtonCommon as _, Clickable as _, Context, ContextMenu,
|
ActiveTheme, AnyElement, App, ButtonCommon as _, Clickable as _, Context, ContextMenu,
|
||||||
|
@ -667,6 +674,143 @@ impl RunningState {
|
||||||
self.panes.pane_at_pixel_position(position).is_some()
|
self.panes.pane_at_pixel_position(position).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn resolve_scenario(
|
||||||
|
&self,
|
||||||
|
scenario: DebugScenario,
|
||||||
|
task_context: TaskContext,
|
||||||
|
buffer: Option<Entity<Buffer>>,
|
||||||
|
window: &Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Task<Result<DebugTaskDefinition>> {
|
||||||
|
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::<Option<Vec<_>>>()
|
||||||
|
.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(
|
fn handle_run_in_terminal(
|
||||||
&self,
|
&self,
|
||||||
request: &RunInTerminalRequestArguments,
|
request: &RunInTerminalRequestArguments,
|
||||||
|
@ -914,7 +1058,7 @@ impl RunningState {
|
||||||
|
|
||||||
let Some((adapter_name, pane_group)) = this
|
let Some((adapter_name, pane_group)) = this
|
||||||
.update(cx, |this, cx| {
|
.update(cx, |this, cx| {
|
||||||
let adapter_name = this.session.read(cx).adapter_name();
|
let adapter_name = this.session.read(cx).adapter();
|
||||||
(
|
(
|
||||||
adapter_name,
|
adapter_name,
|
||||||
persistence::build_serialized_pane_layout(&this.panes.root, cx),
|
persistence::build_serialized_pane_layout(&this.panes.root, cx),
|
||||||
|
|
|
@ -444,11 +444,13 @@ async fn test_handle_start_debugging_request(
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.session(cx);
|
.session(cx);
|
||||||
let parent_session = active_session.read(cx).parent_session().unwrap();
|
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!(
|
assert_eq!(active_session.read(cx).binary(), &original_binary);
|
||||||
active_session.read(cx).definition(),
|
|
||||||
parent_session.read(cx).definition()
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ use std::{
|
||||||
sync::{Arc, Once},
|
sync::{Arc, Once},
|
||||||
};
|
};
|
||||||
use task::{DebugScenario, SpawnInTerminal};
|
use task::{DebugScenario, SpawnInTerminal};
|
||||||
use util::ResultExt as _;
|
use util::{ResultExt as _, merge_json_value_into};
|
||||||
use worktree::Worktree;
|
use worktree::Worktree;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -393,7 +393,8 @@ impl DapStore {
|
||||||
|
|
||||||
pub fn new_session(
|
pub fn new_session(
|
||||||
&mut self,
|
&mut self,
|
||||||
template: DebugTaskDefinition,
|
label: SharedString,
|
||||||
|
adapter: DebugAdapterName,
|
||||||
parent_session: Option<Entity<Session>>,
|
parent_session: Option<Entity<Session>>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Entity<Session> {
|
) -> Entity<Session> {
|
||||||
|
@ -409,7 +410,8 @@ impl DapStore {
|
||||||
self.breakpoint_store.clone(),
|
self.breakpoint_store.clone(),
|
||||||
session_id,
|
session_id,
|
||||||
parent_session,
|
parent_session,
|
||||||
template.clone(),
|
label,
|
||||||
|
adapter,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -435,6 +437,7 @@ impl DapStore {
|
||||||
pub fn boot_session(
|
pub fn boot_session(
|
||||||
&self,
|
&self,
|
||||||
session: Entity<Session>,
|
session: Entity<Session>,
|
||||||
|
definition: DebugTaskDefinition,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next() else {
|
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 dap_store = cx.weak_entity();
|
||||||
let definition = session.read(cx).definition();
|
|
||||||
|
|
||||||
cx.spawn({
|
cx.spawn({
|
||||||
let session = session.clone();
|
let session = session.clone();
|
||||||
async move |this, cx| {
|
async move |this, cx| {
|
||||||
let binary = this
|
let mut binary = this
|
||||||
.update(cx, |this, cx| {
|
.update(cx, |this, cx| {
|
||||||
this.get_debug_adapter_binary(definition.clone(), cx)
|
this.get_debug_adapter_binary(definition.clone(), cx)
|
||||||
})?
|
})?
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
if let Some(args) = definition.initialize_args {
|
||||||
|
merge_json_value_into(args, &mut binary.request_args.configuration);
|
||||||
|
}
|
||||||
|
|
||||||
session
|
session
|
||||||
.update(cx, |session, cx| {
|
.update(cx, |session, cx| {
|
||||||
session.boot(binary, worktree, dap_store, cx)
|
session.boot(binary, worktree, dap_store, cx)
|
||||||
|
|
|
@ -12,7 +12,7 @@ use super::dap_command::{
|
||||||
use super::dap_store::DapStore;
|
use super::dap_store::DapStore;
|
||||||
use anyhow::{Context as _, Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use collections::{HashMap, HashSet, IndexMap, IndexSet};
|
use collections::{HashMap, HashSet, IndexMap, IndexSet};
|
||||||
use dap::adapters::{DebugAdapterBinary, DebugTaskDefinition};
|
use dap::adapters::{DebugAdapterBinary, DebugAdapterName};
|
||||||
use dap::messages::Response;
|
use dap::messages::Response;
|
||||||
use dap::requests::{Request, RunInTerminal, StartDebugging};
|
use dap::requests::{Request, RunInTerminal, StartDebugging};
|
||||||
use dap::{
|
use dap::{
|
||||||
|
@ -32,7 +32,7 @@ use gpui::{
|
||||||
Task, WeakEntity,
|
Task, WeakEntity,
|
||||||
};
|
};
|
||||||
|
|
||||||
use serde_json::{Value, json};
|
use serde_json::Value;
|
||||||
use smol::stream::StreamExt;
|
use smol::stream::StreamExt;
|
||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
@ -45,7 +45,7 @@ use std::{
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use text::{PointUtf16, ToPointUtf16};
|
use text::{PointUtf16, ToPointUtf16};
|
||||||
use util::{ResultExt, merge_json_value_into};
|
use util::ResultExt;
|
||||||
use worktree::Worktree;
|
use worktree::Worktree;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Ord, Eq)]
|
#[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Ord, Eq)]
|
||||||
|
@ -307,18 +307,11 @@ impl LocalMode {
|
||||||
fn initialize_sequence(
|
fn initialize_sequence(
|
||||||
&self,
|
&self,
|
||||||
capabilities: &Capabilities,
|
capabilities: &Capabilities,
|
||||||
definition: &DebugTaskDefinition,
|
|
||||||
initialized_rx: oneshot::Receiver<()>,
|
initialized_rx: oneshot::Receiver<()>,
|
||||||
dap_store: WeakEntity<DapStore>,
|
dap_store: WeakEntity<DapStore>,
|
||||||
breakpoint_store: Entity<BreakpointStore>,
|
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
let mut raw = self.binary.request_args.clone();
|
let raw = self.binary.request_args.clone();
|
||||||
|
|
||||||
merge_json_value_into(
|
|
||||||
definition.initialize_args.clone().unwrap_or(json!({})),
|
|
||||||
&mut raw.configuration,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Of relevance: https://github.com/microsoft/vscode/issues/4902#issuecomment-368583522
|
// Of relevance: https://github.com/microsoft/vscode/issues/4902#issuecomment-368583522
|
||||||
let launch = match raw.request {
|
let launch = match raw.request {
|
||||||
|
@ -349,6 +342,8 @@ impl LocalMode {
|
||||||
let worktree = self.worktree().clone();
|
let worktree = self.worktree().clone();
|
||||||
let configuration_sequence = cx.spawn({
|
let configuration_sequence = cx.spawn({
|
||||||
async move |cx| {
|
async move |cx| {
|
||||||
|
let breakpoint_store =
|
||||||
|
dap_store.update(cx, |dap_store, _| dap_store.breakpoint_store().clone())?;
|
||||||
initialized_rx.await?;
|
initialized_rx.await?;
|
||||||
let errors_by_path = cx
|
let errors_by_path = cx
|
||||||
.update(|cx| this.send_source_breakpoints(false, &breakpoint_store, 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.
|
/// Represents a current state of a single debug adapter and provides ways to mutate it.
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
pub mode: Mode,
|
pub mode: Mode,
|
||||||
definition: DebugTaskDefinition,
|
|
||||||
pub(super) capabilities: Capabilities,
|
|
||||||
id: SessionId,
|
id: SessionId,
|
||||||
|
label: SharedString,
|
||||||
|
adapter: DebugAdapterName,
|
||||||
|
pub(super) capabilities: Capabilities,
|
||||||
child_session_ids: HashSet<SessionId>,
|
child_session_ids: HashSet<SessionId>,
|
||||||
parent_session: Option<Entity<Session>>,
|
parent_session: Option<Entity<Session>>,
|
||||||
modules: Vec<dap::Module>,
|
modules: Vec<dap::Module>,
|
||||||
|
@ -636,7 +632,8 @@ impl Session {
|
||||||
breakpoint_store: Entity<BreakpointStore>,
|
breakpoint_store: Entity<BreakpointStore>,
|
||||||
session_id: SessionId,
|
session_id: SessionId,
|
||||||
parent_session: Option<Entity<Session>>,
|
parent_session: Option<Entity<Session>>,
|
||||||
template: DebugTaskDefinition,
|
label: SharedString,
|
||||||
|
adapter: DebugAdapterName,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Entity<Self> {
|
) -> Entity<Self> {
|
||||||
cx.new::<Self>(|cx| {
|
cx.new::<Self>(|cx| {
|
||||||
|
@ -685,7 +682,8 @@ impl Session {
|
||||||
ignore_breakpoints: false,
|
ignore_breakpoints: false,
|
||||||
breakpoint_store,
|
breakpoint_store,
|
||||||
exception_breakpoints: Default::default(),
|
exception_breakpoints: Default::default(),
|
||||||
definition: template,
|
label,
|
||||||
|
adapter,
|
||||||
};
|
};
|
||||||
|
|
||||||
this
|
this
|
||||||
|
@ -805,16 +803,12 @@ impl Session {
|
||||||
&local_mode.binary
|
&local_mode.binary
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn adapter_name(&self) -> SharedString {
|
pub fn adapter(&self) -> DebugAdapterName {
|
||||||
self.definition.adapter.clone()
|
self.adapter.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn label(&self) -> SharedString {
|
pub fn label(&self) -> SharedString {
|
||||||
self.definition.label.clone()
|
self.label.clone()
|
||||||
}
|
|
||||||
|
|
||||||
pub fn definition(&self) -> DebugTaskDefinition {
|
|
||||||
self.definition.clone()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_terminated(&self) -> bool {
|
pub fn is_terminated(&self) -> bool {
|
||||||
|
@ -943,7 +937,7 @@ impl Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn request_initialize(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
|
pub(super) fn request_initialize(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
|
||||||
let adapter_id = String::from(self.definition.adapter.clone());
|
let adapter_id = self.adapter().to_string();
|
||||||
let request = Initialize { adapter_id };
|
let request = Initialize { adapter_id };
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
Mode::Running(local_mode) => {
|
Mode::Running(local_mode) => {
|
||||||
|
@ -983,14 +977,9 @@ impl Session {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
Mode::Running(local_mode) => local_mode.initialize_sequence(
|
Mode::Running(local_mode) => {
|
||||||
&self.capabilities,
|
local_mode.initialize_sequence(&self.capabilities, initialize_rx, dap_store, cx)
|
||||||
&self.definition,
|
}
|
||||||
initialize_rx,
|
|
||||||
dap_store,
|
|
||||||
self.breakpoint_store.clone(),
|
|
||||||
cx,
|
|
||||||
),
|
|
||||||
Mode::Building => Task::ready(Err(anyhow!("cannot initialize, still building"))),
|
Mode::Building => Task::ready(Err(anyhow!("cannot initialize, still building"))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ use image_store::{ImageItemEvent, ImageStoreEvent};
|
||||||
use ::git::{blame::Blame, status::FileStatus};
|
use ::git::{blame::Blame, status::FileStatus};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyEntity, App, AppContext, AsyncApp, BorrowAppContext, Context, Entity, EventEmitter, Hsla,
|
AnyEntity, App, AppContext, AsyncApp, BorrowAppContext, Context, Entity, EventEmitter, Hsla,
|
||||||
SharedString, Task, WeakEntity, Window, prelude::FluentBuilder,
|
SharedString, Task, WeakEntity, Window,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language::{
|
use language::{
|
||||||
|
@ -3565,10 +3565,9 @@ impl Project {
|
||||||
) -> Task<anyhow::Result<Vec<InlayHint>>> {
|
) -> Task<anyhow::Result<Vec<InlayHint>>> {
|
||||||
let snapshot = buffer_handle.read(cx).snapshot();
|
let snapshot = buffer_handle.read(cx).snapshot();
|
||||||
|
|
||||||
let Some(inline_value_provider) = session
|
let adapter = session.read(cx).adapter();
|
||||||
.read(cx)
|
let Some(inline_value_provider) = DapRegistry::global(cx)
|
||||||
.adapter_name()
|
.adapter(&adapter)
|
||||||
.map(|adapter_name| DapRegistry::global(cx).adapter(&adapter_name))
|
|
||||||
.and_then(|adapter| adapter.inline_value_provider())
|
.and_then(|adapter| adapter.inline_value_provider())
|
||||||
else {
|
else {
|
||||||
return Task::ready(Err(anyhow::anyhow!("Inline value provider not found")));
|
return Task::ready(Err(anyhow::anyhow!("Inline value provider not found")));
|
||||||
|
|
|
@ -20,7 +20,8 @@ pub use debug_format::{
|
||||||
};
|
};
|
||||||
pub use task_template::{
|
pub use task_template::{
|
||||||
DebugArgsRequest, HideStrategy, RevealStrategy, TaskTemplate, TaskTemplates,
|
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_debug_format::VsCodeDebugTaskFile;
|
||||||
pub use vscode_format::VsCodeTaskFile;
|
pub use vscode_format::VsCodeTaskFile;
|
||||||
|
|
|
@ -293,6 +293,28 @@ fn to_hex_hash(object: impl Serialize) -> anyhow::Result<String> {
|
||||||
Ok(hex::encode(hasher.finalize()))
|
Ok(hex::encode(hasher.finalize()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn substitute_variables_in_str(template_str: &str, context: &TaskContext) -> Option<String> {
|
||||||
|
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::<HashMap<_, _>>();
|
||||||
|
substitute_all_template_variables_in_str(
|
||||||
|
template_str,
|
||||||
|
&task_variables,
|
||||||
|
&variable_names,
|
||||||
|
&mut substituted_variables,
|
||||||
|
)
|
||||||
|
}
|
||||||
pub fn substitute_all_template_variables_in_str<A: AsRef<str>>(
|
pub fn substitute_all_template_variables_in_str<A: AsRef<str>>(
|
||||||
template_str: &str,
|
template_str: &str,
|
||||||
task_variables: &HashMap<String, A>,
|
task_variables: &HashMap<String, A>,
|
||||||
|
@ -349,6 +371,31 @@ fn substitute_all_template_variables_in_vec(
|
||||||
Some(expanded)
|
Some(expanded)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn substitute_variables_in_map(
|
||||||
|
keys_and_values: &HashMap<String, String>,
|
||||||
|
context: &TaskContext,
|
||||||
|
) -> Option<HashMap<String, String>> {
|
||||||
|
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::<HashMap<_, _>>();
|
||||||
|
substitute_all_template_variables_in_map(
|
||||||
|
keys_and_values,
|
||||||
|
&task_variables,
|
||||||
|
&variable_names,
|
||||||
|
&mut substituted_variables,
|
||||||
|
)
|
||||||
|
}
|
||||||
fn substitute_all_template_variables_in_map(
|
fn substitute_all_template_variables_in_map(
|
||||||
keys_and_values: &HashMap<String, String>,
|
keys_and_values: &HashMap<String, String>,
|
||||||
task_variables: &HashMap<String, &str>,
|
task_variables: &HashMap<String, &str>,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue