debugger: Use DAP schema to configure daps (#30833)
This PR allows DAPs to define their own schema so users can see completion items when editing their debug.json files. Users facing this aren’t the biggest chance, but behind the scenes, this affected a lot of code because we manually translated common fields from Zed's config format to be adapter-specific. Now we store the raw JSON from a user's configuration file and just send that. I'm ignoring the Protobuf CICD error because the DebugTaskDefinition message is not yet user facing and we need to deprecate some fields in it. Release Notes: - debugger beta: Show completion items when editing debug.json - debugger beta: Breaking change, debug.json schema now relays on what DAP you have selected instead of always having the same based values. --------- Co-authored-by: Remco Smits <djsmits12@gmail.com> Co-authored-by: Cole Miller <m@cole-miller.net> Co-authored-by: Cole Miller <cole@zed.dev>
This commit is contained in:
co-authored by
Remco Smits
Cole Miller
Cole Miller
parent
0d7f4842f3
commit
1c9b818342
@@ -1,15 +1,15 @@
|
||||
use dap::DebugRequest;
|
||||
use dap::adapters::DebugTaskDefinition;
|
||||
use dap::{DapRegistry, DebugRequest};
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{DismissEvent, Entity, EventEmitter, Focusable, Render};
|
||||
use gpui::{AppContext, DismissEvent, Entity, EventEmitter, Focusable, Render};
|
||||
use gpui::{Subscription, WeakEntity};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use task::ZedDebugConfig;
|
||||
use util::debug_panic;
|
||||
|
||||
use std::sync::Arc;
|
||||
use sysinfo::System;
|
||||
use ui::{Context, Tooltip, prelude::*};
|
||||
use ui::{ListItem, ListItemSpacing};
|
||||
use util::debug_panic;
|
||||
use workspace::{ModalView, Workspace};
|
||||
|
||||
use crate::debugger_panel::DebugPanel;
|
||||
@@ -25,7 +25,7 @@ pub(crate) struct AttachModalDelegate {
|
||||
selected_index: usize,
|
||||
matches: Vec<StringMatch>,
|
||||
placeholder_text: Arc<str>,
|
||||
pub(crate) definition: DebugTaskDefinition,
|
||||
pub(crate) definition: ZedDebugConfig,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
candidates: Arc<[Candidate]>,
|
||||
}
|
||||
@@ -33,7 +33,7 @@ pub(crate) struct AttachModalDelegate {
|
||||
impl AttachModalDelegate {
|
||||
fn new(
|
||||
workspace: WeakEntity<Workspace>,
|
||||
definition: DebugTaskDefinition,
|
||||
definition: ZedDebugConfig,
|
||||
candidates: Arc<[Candidate]>,
|
||||
) -> Self {
|
||||
Self {
|
||||
@@ -54,7 +54,7 @@ pub struct AttachModal {
|
||||
|
||||
impl AttachModal {
|
||||
pub fn new(
|
||||
definition: DebugTaskDefinition,
|
||||
definition: ZedDebugConfig,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
modal: bool,
|
||||
window: &mut Window,
|
||||
@@ -83,7 +83,7 @@ impl AttachModal {
|
||||
|
||||
pub(super) fn with_processes(
|
||||
workspace: WeakEntity<Workspace>,
|
||||
definition: DebugTaskDefinition,
|
||||
definition: ZedDebugConfig,
|
||||
processes: Arc<[Candidate]>,
|
||||
modal: bool,
|
||||
window: &mut Window,
|
||||
@@ -228,7 +228,13 @@ impl PickerDelegate for AttachModalDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
let scenario = self.definition.to_scenario();
|
||||
let Some(scenario) = cx.read_global::<DapRegistry, _>(|registry, _| {
|
||||
registry
|
||||
.adapter(&self.definition.adapter)
|
||||
.and_then(|adapter| adapter.config_from_zed_format(self.definition.clone()).ok())
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let panel = self
|
||||
.workspace
|
||||
|
||||
@@ -82,7 +82,7 @@ impl DebugPanel {
|
||||
let thread_picker_menu_handle = PopoverMenuHandle::default();
|
||||
let session_picker_menu_handle = PopoverMenuHandle::default();
|
||||
|
||||
let debug_panel = Self {
|
||||
Self {
|
||||
size: px(300.),
|
||||
sessions: vec![],
|
||||
active_session: None,
|
||||
@@ -93,9 +93,7 @@ impl DebugPanel {
|
||||
fs: workspace.app_state().fs.clone(),
|
||||
thread_picker_menu_handle,
|
||||
session_picker_menu_handle,
|
||||
};
|
||||
|
||||
debug_panel
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -301,6 +299,7 @@ impl DebugPanel {
|
||||
|
||||
cx.spawn(async move |_, cx| {
|
||||
if let Err(error) = task.await {
|
||||
log::error!("{error}");
|
||||
session
|
||||
.update(cx, |session, cx| {
|
||||
session
|
||||
|
||||
@@ -9,10 +9,7 @@ use std::{
|
||||
usize,
|
||||
};
|
||||
|
||||
use dap::{
|
||||
DapRegistry, DebugRequest,
|
||||
adapters::{DebugAdapterName, DebugTaskDefinition},
|
||||
};
|
||||
use dap::{DapRegistry, DebugRequest, adapters::DebugAdapterName};
|
||||
use editor::{Editor, EditorElement, EditorStyle};
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
@@ -22,7 +19,7 @@ use gpui::{
|
||||
use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch};
|
||||
use project::{ProjectPath, TaskContexts, TaskSourceKind, task_store::TaskStore};
|
||||
use settings::Settings;
|
||||
use task::{DebugScenario, LaunchRequest};
|
||||
use task::{DebugScenario, LaunchRequest, ZedDebugConfig};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
ActiveTheme, Button, ButtonCommon, ButtonSize, CheckboxWithLabel, Clickable, Color, Context,
|
||||
@@ -210,15 +207,16 @@ impl NewSessionModal {
|
||||
None
|
||||
};
|
||||
|
||||
Some(DebugScenario {
|
||||
let session_scenario = ZedDebugConfig {
|
||||
adapter: debugger.to_owned().into(),
|
||||
label,
|
||||
request: Some(request),
|
||||
initialize_args: None,
|
||||
tcp_connection: None,
|
||||
request: request,
|
||||
stop_on_entry,
|
||||
build: None,
|
||||
})
|
||||
};
|
||||
|
||||
cx.global::<DapRegistry>()
|
||||
.adapter(&session_scenario.adapter)
|
||||
.and_then(|adapter| adapter.config_from_zed_format(session_scenario).ok())
|
||||
}
|
||||
|
||||
fn start_new_session(&self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
@@ -264,12 +262,12 @@ impl NewSessionModal {
|
||||
cx: &mut App,
|
||||
) {
|
||||
attach.update(cx, |this, cx| {
|
||||
if adapter != &this.definition.adapter {
|
||||
this.definition.adapter = adapter.clone();
|
||||
if adapter.0 != this.definition.adapter {
|
||||
this.definition.adapter = adapter.0.clone();
|
||||
|
||||
this.attach_picker.update(cx, |this, cx| {
|
||||
this.picker.update(cx, |this, cx| {
|
||||
this.delegate.definition.adapter = adapter.clone();
|
||||
this.delegate.definition.adapter = adapter.0.clone();
|
||||
this.focus(window, cx);
|
||||
})
|
||||
});
|
||||
@@ -862,7 +860,7 @@ impl CustomMode {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct AttachMode {
|
||||
pub(super) definition: DebugTaskDefinition,
|
||||
pub(super) definition: ZedDebugConfig,
|
||||
pub(super) attach_picker: Entity<AttachModal>,
|
||||
}
|
||||
|
||||
@@ -873,12 +871,10 @@ impl AttachMode {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<NewSessionModal>,
|
||||
) -> Entity<Self> {
|
||||
let definition = DebugTaskDefinition {
|
||||
adapter: debugger.unwrap_or(DebugAdapterName("".into())),
|
||||
let definition = ZedDebugConfig {
|
||||
adapter: debugger.unwrap_or(DebugAdapterName("".into())).0,
|
||||
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| {
|
||||
@@ -938,27 +934,14 @@ impl DebugScenarioDelegate {
|
||||
});
|
||||
|
||||
let language = language.or_else(|| {
|
||||
scenario
|
||||
.request
|
||||
.as_ref()
|
||||
.and_then(|request| match request {
|
||||
DebugRequest::Launch(launch) => launch
|
||||
.program
|
||||
.rsplit_once(".")
|
||||
.and_then(|split| languages.language_name_for_extension(split.1))
|
||||
.map(|name| TaskSourceKind::Language { name: name.into() }),
|
||||
_ => None,
|
||||
})
|
||||
.or_else(|| {
|
||||
scenario.label.split_whitespace().find_map(|word| {
|
||||
language_names
|
||||
.iter()
|
||||
.find(|name| name.eq_ignore_ascii_case(word))
|
||||
.map(|name| TaskSourceKind::Language {
|
||||
name: name.to_owned().into(),
|
||||
})
|
||||
scenario.label.split_whitespace().find_map(|word| {
|
||||
language_names
|
||||
.iter()
|
||||
.find(|name| name.eq_ignore_ascii_case(word))
|
||||
.map(|name| TaskSourceKind::Language {
|
||||
name: name.to_owned().into(),
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
(language, scenario)
|
||||
@@ -1092,7 +1075,7 @@ impl PickerDelegate for DebugScenarioDelegate {
|
||||
.get(self.selected_index())
|
||||
.and_then(|match_candidate| self.candidates.get(match_candidate.candidate_id).cloned());
|
||||
|
||||
let Some((_, mut debug_scenario)) = debug_scenario else {
|
||||
let Some((_, debug_scenario)) = debug_scenario else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -1107,19 +1090,6 @@ impl PickerDelegate for DebugScenarioDelegate {
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
if let Some(launch_config) =
|
||||
debug_scenario
|
||||
.request
|
||||
.as_mut()
|
||||
.and_then(|request| match request {
|
||||
DebugRequest::Launch(launch) => Some(launch),
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
let (program, _) = resolve_paths(launch_config.program.clone(), String::new());
|
||||
launch_config.program = program;
|
||||
}
|
||||
|
||||
self.debug_panel
|
||||
.update(cx, |panel, cx| {
|
||||
panel.start_session(debug_scenario, task_context, None, worktree_id, window, cx);
|
||||
|
||||
@@ -15,7 +15,7 @@ use breakpoint_list::BreakpointList;
|
||||
use collections::{HashMap, IndexMap};
|
||||
use console::Console;
|
||||
use dap::{
|
||||
Capabilities, RunInTerminalRequestArguments, Thread,
|
||||
Capabilities, DapRegistry, RunInTerminalRequestArguments, Thread,
|
||||
adapters::{DebugAdapterName, DebugTaskDefinition},
|
||||
client::SessionId,
|
||||
debugger_settings::DebuggerSettings,
|
||||
@@ -38,8 +38,8 @@ use serde_json::Value;
|
||||
use settings::Settings;
|
||||
use stack_frame_list::StackFrameList;
|
||||
use task::{
|
||||
BuildTaskDefinition, DebugScenario, LaunchRequest, ShellBuilder, SpawnInTerminal, TaskContext,
|
||||
substitute_variables_in_map, substitute_variables_in_str,
|
||||
BuildTaskDefinition, DebugScenario, ShellBuilder, SpawnInTerminal, TaskContext, ZedDebugConfig,
|
||||
substitute_variables_in_str,
|
||||
};
|
||||
use terminal_view::TerminalView;
|
||||
use ui::{
|
||||
@@ -519,6 +519,30 @@ impl Focusable for DebugTerminal {
|
||||
}
|
||||
|
||||
impl RunningState {
|
||||
// todo(debugger) move this to util and make it so you pass a closure to it that converts a string
|
||||
pub(crate) fn substitute_variables_in_config(
|
||||
config: &mut serde_json::Value,
|
||||
context: &TaskContext,
|
||||
) {
|
||||
match config {
|
||||
serde_json::Value::Object(obj) => {
|
||||
obj.values_mut()
|
||||
.for_each(|value| Self::substitute_variables_in_config(value, context));
|
||||
}
|
||||
serde_json::Value::Array(array) => {
|
||||
array
|
||||
.iter_mut()
|
||||
.for_each(|value| Self::substitute_variables_in_config(value, context));
|
||||
}
|
||||
serde_json::Value::String(s) => {
|
||||
if let Some(substituted) = substitute_variables_in_str(&s, context) {
|
||||
*s = substituted;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new(
|
||||
session: Entity<Session>,
|
||||
project: Entity<Project>,
|
||||
@@ -704,6 +728,7 @@ impl RunningState {
|
||||
};
|
||||
let project = workspace.read(cx).project().clone();
|
||||
let dap_store = project.read(cx).dap_store().downgrade();
|
||||
let dap_registry = cx.global::<DapRegistry>().clone();
|
||||
let task_store = project.read(cx).task_store().downgrade();
|
||||
let weak_project = project.downgrade();
|
||||
let weak_workspace = workspace.downgrade();
|
||||
@@ -713,11 +738,18 @@ impl RunningState {
|
||||
adapter,
|
||||
label,
|
||||
build,
|
||||
request,
|
||||
initialize_args,
|
||||
mut config,
|
||||
tcp_connection,
|
||||
stop_on_entry,
|
||||
} = scenario;
|
||||
Self::substitute_variables_in_config(&mut config, &task_context);
|
||||
|
||||
let request_type = dap_registry
|
||||
.adapter(&adapter)
|
||||
.ok_or_else(|| anyhow!("{}: is not a valid adapter name", &adapter))
|
||||
.and_then(|adapter| adapter.validate_config(&config));
|
||||
|
||||
let config_is_valid = request_type.is_ok();
|
||||
|
||||
let build_output = if let Some(build) = build {
|
||||
let (task, locator_name) = match build {
|
||||
BuildTaskDefinition::Template {
|
||||
@@ -746,9 +778,9 @@ impl RunningState {
|
||||
};
|
||||
|
||||
let locator_name = if let Some(locator_name) = locator_name {
|
||||
debug_assert!(request.is_none());
|
||||
debug_assert!(!config_is_valid);
|
||||
Some(locator_name)
|
||||
} else if request.is_none() {
|
||||
} else if !config_is_valid {
|
||||
dap_store
|
||||
.update(cx, |this, cx| {
|
||||
this.debug_scenario_for_build_task(
|
||||
@@ -825,63 +857,43 @@ impl RunningState {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let request = if let Some(request) = request {
|
||||
request
|
||||
|
||||
if config_is_valid {
|
||||
// Ok(DebugTaskDefinition {
|
||||
// label,
|
||||
// adapter: DebugAdapterName(adapter),
|
||||
// config,
|
||||
// tcp_connection,
|
||||
// })
|
||||
} else if let Some((task, locator_name)) = build_output {
|
||||
let locator_name =
|
||||
locator_name.context("Could not find a valid locator for a build task")?;
|
||||
dap_store
|
||||
let request = dap_store
|
||||
.update(cx, |this, cx| {
|
||||
this.run_debug_locator(&locator_name, task, cx)
|
||||
})?
|
||||
.await?
|
||||
.await?;
|
||||
|
||||
let zed_config = ZedDebugConfig {
|
||||
label: label.clone(),
|
||||
adapter: adapter.clone(),
|
||||
request,
|
||||
stop_on_entry: None,
|
||||
};
|
||||
|
||||
let scenario = dap_registry
|
||||
.adapter(&adapter)
|
||||
.ok_or_else(|| anyhow!("{}: is not a valid adapter name", &adapter))
|
||||
.map(|adapter| adapter.config_from_zed_format(zed_config))??;
|
||||
config = scenario.config;
|
||||
} else {
|
||||
anyhow::bail!("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)
|
||||
.context("substituting variables in cwd")?;
|
||||
Some(PathBuf::from(substituted_cwd))
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let env = substitute_variables_in_map(
|
||||
&launch_request.env.into_iter().collect(),
|
||||
&task_context,
|
||||
)
|
||||
.context("substituting variables in env")?
|
||||
.into_iter()
|
||||
.collect();
|
||||
let new_launch_request = LaunchRequest {
|
||||
program: substitute_variables_in_str(
|
||||
&launch_request.program,
|
||||
&task_context,
|
||||
)
|
||||
.context("substituting variables in program")?,
|
||||
args: launch_request
|
||||
.args
|
||||
.into_iter()
|
||||
.map(|arg| substitute_variables_in_str(&arg, &task_context))
|
||||
.collect::<Option<Vec<_>>>()
|
||||
.context("substituting variables in args")?,
|
||||
cwd,
|
||||
env,
|
||||
};
|
||||
|
||||
dap::DebugRequest::Launch(new_launch_request)
|
||||
}
|
||||
request @ dap::DebugRequest::Attach(_) => request, // todo(debugger): We should check that process_id is valid and if not show the modal
|
||||
};
|
||||
Ok(DebugTaskDefinition {
|
||||
label,
|
||||
adapter: DebugAdapterName(adapter),
|
||||
request,
|
||||
initialize_args,
|
||||
stop_on_entry,
|
||||
config,
|
||||
tcp_connection,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use dap::adapters::DebugTaskDefinition;
|
||||
use dap::{DebugRequest, client::DebugAdapterClient};
|
||||
use dap::client::DebugAdapterClient;
|
||||
use gpui::{Entity, TestAppContext, WindowHandle};
|
||||
use project::{Project, debugger::session::Session};
|
||||
use settings::SettingsStore;
|
||||
@@ -136,16 +136,18 @@ pub fn start_debug_session<T: Fn(&Arc<DebugAdapterClient>) + 'static>(
|
||||
cx: &mut gpui::TestAppContext,
|
||||
configure: T,
|
||||
) -> Result<Entity<Session>> {
|
||||
use serde_json::json;
|
||||
|
||||
start_debug_session_with(
|
||||
workspace,
|
||||
cx,
|
||||
DebugTaskDefinition {
|
||||
adapter: "fake-adapter".into(),
|
||||
request: DebugRequest::Launch(Default::default()),
|
||||
label: "test".into(),
|
||||
initialize_args: None,
|
||||
config: json!({
|
||||
"request": "launch"
|
||||
}),
|
||||
tcp_connection: None,
|
||||
stop_on_entry: None,
|
||||
},
|
||||
configure,
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
||||
use menu::Confirm;
|
||||
use project::{FakeFs, Project};
|
||||
use serde_json::json;
|
||||
use task::{AttachRequest, TcpArgumentsTemplate};
|
||||
use task::AttachRequest;
|
||||
use tests::{init_test, init_test_workspace};
|
||||
use util::path;
|
||||
|
||||
@@ -32,13 +32,12 @@ async fn test_direct_attach_to_process(executor: BackgroundExecutor, cx: &mut Te
|
||||
cx,
|
||||
DebugTaskDefinition {
|
||||
adapter: "fake-adapter".into(),
|
||||
request: dap::DebugRequest::Attach(AttachRequest {
|
||||
process_id: Some(10),
|
||||
}),
|
||||
label: "label".into(),
|
||||
initialize_args: None,
|
||||
config: json!({
|
||||
"request": "attach",
|
||||
"process_id": 10,
|
||||
}),
|
||||
tcp_connection: None,
|
||||
stop_on_entry: None,
|
||||
},
|
||||
|client| {
|
||||
client.on_request::<dap::requests::Attach, _>(move |_, args| {
|
||||
@@ -107,13 +106,10 @@ async fn test_show_attach_modal_and_select_process(
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
AttachModal::with_processes(
|
||||
workspace_handle,
|
||||
DebugTaskDefinition {
|
||||
task::ZedDebugConfig {
|
||||
adapter: FakeAdapter::ADAPTER_NAME.into(),
|
||||
|
||||
request: dap::DebugRequest::Attach(AttachRequest::default()),
|
||||
label: "attach example".into(),
|
||||
initialize_args: None,
|
||||
tcp_connection: Some(TcpArgumentsTemplate::default()),
|
||||
stop_on_entry: None,
|
||||
},
|
||||
vec![
|
||||
|
||||
@@ -24,14 +24,12 @@ use project::{
|
||||
};
|
||||
use serde_json::json;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
path::Path,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
},
|
||||
};
|
||||
use task::LaunchRequest;
|
||||
use terminal_view::terminal_panel::TerminalPanel;
|
||||
use tests::{active_debug_session_panel, init_test, init_test_workspace};
|
||||
use util::path;
|
||||
@@ -1388,16 +1386,15 @@ async fn test_we_send_arguments_from_user_config(
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let debug_definition = DebugTaskDefinition {
|
||||
adapter: "fake-adapter".into(),
|
||||
request: dap::DebugRequest::Launch(LaunchRequest {
|
||||
program: "main.rs".to_owned(),
|
||||
args: vec!["arg1".to_owned(), "arg2".to_owned()],
|
||||
cwd: Some(path!("/Random_path").into()),
|
||||
env: HashMap::from_iter(vec![("KEY".to_owned(), "VALUE".to_owned())]),
|
||||
config: json!({
|
||||
"request": "launch",
|
||||
"program": "main.rs".to_owned(),
|
||||
"args": vec!["arg1".to_owned(), "arg2".to_owned()],
|
||||
"cwd": path!("/Random_path"),
|
||||
"env": json!({ "KEY": "VALUE" }),
|
||||
}),
|
||||
label: "test".into(),
|
||||
initialize_args: None,
|
||||
tcp_connection: None,
|
||||
stop_on_entry: None,
|
||||
};
|
||||
|
||||
let launch_handler_called = Arc::new(AtomicBool::new(false));
|
||||
|
||||
Reference in New Issue
Block a user