logiguard fork v3: full patch set on verified 8c74db0 tree

Includes prior-session patches (carry forward so the app compiles):
  - crates/gpui/build.rs: cross-compile manifest fix
  - crates/gpui/src/platform.rs: PlatformWindow::activate_with_token trait method
  - crates/gpui/src/window.rs: Window::activate_with_token public API
  - crates/gpui_linux/src/linux/wayland/window.rs: WaylandWindow::activate_with_token + activate() keyboard-serial fix

Plus the focus-serial fix:
  - serial.rs: SerialKind::KeyboardEnter
  - client.rs: store wl_keyboard.enter serial; latest_serial_of()

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Mohamad Khani
2026-07-14 01:52:12 +03:30
commit b9819977a5
3984 changed files with 1487015 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
[package]
name = "dap_adapters"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
license = "GPL-3.0-or-later"
[features]
test-support = [
"dap/test-support",
"gpui/test-support",
"task/test-support",
"util/test-support",
]
[lints]
workspace = true
[lib]
path = "src/dap_adapters.rs"
doctest = false
[dependencies]
anyhow.workspace = true
async-trait.workspace = true
collections.workspace = true
dap.workspace = true
dotenvy.workspace = true
futures.workspace = true
fs.workspace = true
gpui.workspace = true
json_dotpath.workspace = true
language.workspace = true
log.workspace = true
paths.workspace = true
serde.workspace = true
serde_json.workspace = true
smol.workspace = true
task.workspace = true
util.workspace = true
[dev-dependencies]
dap = { workspace = true, features = ["test-support"] }
fs = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, features = ["test-support"] }
http_client.workspace = true
node_runtime.workspace = true
settings = { workspace = true, features = ["test-support"] }
task = { workspace = true, features = ["test-support"] }
util = { workspace = true, features = ["test-support"] }

View File

@@ -0,0 +1 @@
../../LICENSE-GPL

View File

@@ -0,0 +1,429 @@
use std::{env::consts, path::PathBuf, sync::OnceLock};
use anyhow::{Context as _, Result};
use async_trait::async_trait;
use collections::HashMap;
use dap::adapters::{DebugTaskDefinition, latest_github_release};
use futures::StreamExt;
use gpui::AsyncApp;
use serde_json::Value;
use task::{DebugRequest, DebugScenario, ZedDebugConfig};
use util::fs::remove_matching;
use crate::*;
#[derive(Default)]
pub(crate) struct CodeLldbDebugAdapter {
path_to_codelldb: OnceLock<String>,
}
impl CodeLldbDebugAdapter {
const ADAPTER_NAME: &'static str = "CodeLLDB";
async fn request_args(
&self,
delegate: &Arc<dyn DapDelegate>,
mut configuration: Value,
label: &str,
) -> Result<dap::StartDebuggingRequestArguments> {
let obj = configuration
.as_object_mut()
.context("CodeLLDB is not a valid json object")?;
// CodeLLDB uses `name` for a terminal label.
obj.entry("name")
.or_insert(Value::String(String::from(label)));
obj.entry("cwd")
.or_insert(delegate.worktree_root_path().to_string_lossy().into());
let request = self.request_kind(&configuration).await?;
Ok(dap::StartDebuggingRequestArguments {
request,
configuration,
})
}
async fn fetch_latest_adapter_version(
&self,
delegate: &Arc<dyn DapDelegate>,
) -> Result<AdapterVersion> {
let release =
latest_github_release("vadimcn/codelldb", true, false, delegate.http_client()).await?;
let arch = match std::env::consts::ARCH {
"aarch64" => "arm64",
"x86_64" => "x64",
unsupported => {
anyhow::bail!("unsupported architecture {unsupported}");
}
};
let platform = match std::env::consts::OS {
"macos" => "darwin",
"linux" => "linux",
"windows" => "win32",
unsupported => {
anyhow::bail!("unsupported operating system {unsupported}");
}
};
let asset_name = format!("codelldb-{platform}-{arch}.vsix");
let ret = AdapterVersion {
tag_name: release.tag_name,
url: release
.assets
.iter()
.find(|asset| asset.name == asset_name)
.with_context(|| format!("no asset found matching {asset_name:?}"))?
.browser_download_url
.clone(),
};
Ok(ret)
}
}
#[async_trait(?Send)]
impl DebugAdapter for CodeLldbDebugAdapter {
fn name(&self) -> DebugAdapterName {
DebugAdapterName(Self::ADAPTER_NAME.into())
}
async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
let mut configuration = json!({
"request": match zed_scenario.request {
DebugRequest::Launch(_) => "launch",
DebugRequest::Attach(_) => "attach",
},
});
let map = configuration.as_object_mut().unwrap();
// CodeLLDB uses `name` for a terminal label.
map.insert(
"name".into(),
Value::String(String::from(zed_scenario.label.as_ref())),
);
match &zed_scenario.request {
DebugRequest::Attach(attach) => {
map.insert("pid".into(), attach.process_id.into());
}
DebugRequest::Launch(launch) => {
map.insert("program".into(), launch.program.clone().into());
if !launch.args.is_empty() {
map.insert("args".into(), launch.args.clone().into());
}
if !launch.env.is_empty() {
map.insert("env".into(), launch.env_json());
}
if let Some(stop_on_entry) = zed_scenario.stop_on_entry {
map.insert("stopOnEntry".into(), stop_on_entry.into());
}
if let Some(cwd) = launch.cwd.as_ref() {
map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
}
}
}
Ok(DebugScenario {
adapter: zed_scenario.adapter,
label: zed_scenario.label,
config: configuration,
build: None,
tcp_connection: None,
})
}
fn dap_schema(&self) -> serde_json::Value {
json!({
"properties": {
"request": {
"type": "string",
"enum": ["attach", "launch"],
"description": "Debug adapter request type"
},
"program": {
"type": "string",
"description": "Path to the program to debug or attach to"
},
"args": {
"type": ["array", "string"],
"description": "Program arguments"
},
"cwd": {
"type": "string",
"description": "Program working directory"
},
"env": {
"type": "object",
"description": "Additional environment variables",
"patternProperties": {
".*": {
"type": "string"
}
}
},
"envFile": {
"type": "string",
"description": "File to read the environment variables from"
},
"stdio": {
"type": ["null", "string", "array", "object"],
"description": "Destination for stdio streams: null = send to debugger console or a terminal, \"<path>\" = attach to a file/tty/fifo"
},
"terminal": {
"type": "string",
"enum": ["integrated", "console"],
"description": "Terminal type to use",
"default": "integrated"
},
"console": {
"type": "string",
"enum": ["integratedTerminal", "internalConsole"],
"description": "Terminal type to use (compatibility alias of 'terminal')"
},
"stopOnEntry": {
"type": "boolean",
"description": "Automatically stop debuggee after launch",
"default": false
},
"initCommands": {
"type": "array",
"description": "Initialization commands executed upon debugger startup",
"items": {
"type": "string"
}
},
"targetCreateCommands": {
"type": "array",
"description": "Commands that create the debug target",
"items": {
"type": "string"
}
},
"preRunCommands": {
"type": "array",
"description": "Commands executed just before the program is launched",
"items": {
"type": "string"
}
},
"processCreateCommands": {
"type": "array",
"description": "Commands that create the debuggee process",
"items": {
"type": "string"
}
},
"postRunCommands": {
"type": "array",
"description": "Commands executed just after the program has been launched",
"items": {
"type": "string"
}
},
"preTerminateCommands": {
"type": "array",
"description": "Commands executed just before the debuggee is terminated or disconnected from",
"items": {
"type": "string"
}
},
"exitCommands": {
"type": "array",
"description": "Commands executed at the end of debugging session",
"items": {
"type": "string"
}
},
"expressions": {
"type": "string",
"enum": ["simple", "python", "native"],
"description": "The default evaluator type used for expressions"
},
"sourceMap": {
"type": "object",
"description": "Source path remapping between the build machine and the local machine",
"patternProperties": {
".*": {
"type": ["string", "null"]
}
}
},
"relativePathBase": {
"type": "string",
"description": "Base directory used for resolution of relative source paths. Defaults to the workspace folder"
},
"sourceLanguages": {
"type": "array",
"description": "A list of source languages to enable language-specific features for",
"items": {
"type": "string"
}
},
"reverseDebugging": {
"type": "boolean",
"description": "Enable reverse debugging",
"default": false
},
"breakpointMode": {
"type": "string",
"enum": ["path", "file"],
"description": "Specifies how source breakpoints should be set"
},
"pid": {
"type": ["integer", "string"],
"description": "Process id to attach to"
},
"waitFor": {
"type": "boolean",
"description": "Wait for the process to launch (MacOS only)",
"default": false
}
},
"required": ["request"],
"allOf": [
{
"if": {
"properties": {
"request": {
"enum": ["launch"]
}
}
},
"then": {
"oneOf": [
{
"required": ["program"]
},
{
"required": ["targetCreateCommands"]
}
]
}
},
{
"if": {
"properties": {
"request": {
"enum": ["attach"]
}
}
},
"then": {
"oneOf": [
{
"required": ["pid"]
},
{
"required": ["program"]
}
]
}
}
]
})
}
async fn get_binary(
&self,
delegate: &Arc<dyn DapDelegate>,
config: &DebugTaskDefinition,
user_installed_path: Option<PathBuf>,
user_args: Option<Vec<String>>,
user_env: Option<HashMap<String, String>>,
_: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
let mut command = user_installed_path
.map(|p| p.to_string_lossy().into_owned())
.or(self.path_to_codelldb.get().cloned());
if command.is_none() {
delegate.output_to_console(format!("Checking latest version of {}...", self.name()));
let adapter_path = paths::debug_adapters_dir().join(&Self::ADAPTER_NAME);
let version_path = match self.fetch_latest_adapter_version(delegate).await {
Ok(version) => {
adapters::download_adapter_from_github(
self.name(),
version.clone(),
adapters::DownloadedFileType::Vsix,
delegate.as_ref(),
)
.await?;
let version_path =
adapter_path.join(format!("{}_{}", Self::ADAPTER_NAME, version.tag_name));
remove_matching(&adapter_path, |entry| entry != version_path).await;
version_path
}
Err(e) => {
delegate.output_to_console("Unable to fetch latest version".to_string());
log::error!("Error fetching latest version of {}: {}", self.name(), e);
delegate.output_to_console(format!(
"Searching for adapters in: {}",
adapter_path.display()
));
let mut paths = delegate
.fs()
.read_dir(&adapter_path)
.await
.context("No cached adapter directory")?;
paths
.next()
.await
.context("No cached adapter found")?
.context("No cached adapter found")?
}
};
let adapter_dir = version_path.join("extension").join("adapter");
let path = adapter_dir
.join(format!("codelldb{}", consts::EXE_SUFFIX))
.to_string_lossy()
.into_owned();
self.path_to_codelldb.set(path.clone()).ok();
command = Some(path);
};
let mut json_config = config.config.clone();
// Auto-detect Rust projects and add sourceLanguages if not present.
// This enables panic breakpoints to work correctly with CodeLLDB.
if let Some(config_obj) = json_config.as_object_mut() {
if !config_obj.contains_key("sourceLanguages") {
// Check if this looks like a Rust binary (Cargo build output)
if let Some(program) = config_obj.get("program").and_then(|p| p.as_str()) {
let path_str = program.replace('\\', "/");
if path_str.contains("/target/debug/") || path_str.contains("/target/release/")
{
config_obj.insert("sourceLanguages".to_owned(), json!(["rust"]));
}
}
}
}
Ok(DebugAdapterBinary {
command: Some(command.unwrap()),
cwd: Some(delegate.worktree_root_path().to_path_buf()),
arguments: user_args.unwrap_or_else(|| {
if let Some(config) = json_config.as_object_mut()
&& let Some(source_languages) = config.get("sourceLanguages").filter(|value| {
value
.as_array()
.is_some_and(|array| array.iter().all(Value::is_string))
})
{
let ret = vec![
"--settings".into(),
json!({"sourceLanguages": source_languages}).to_string(),
];
config.remove("sourceLanguages");
ret
} else {
vec![]
}
}),
request_args: self
.request_args(delegate, json_config, &config.label)
.await?,
envs: user_env.unwrap_or_default(),
connection: None,
})
}
}

View File

@@ -0,0 +1,104 @@
mod codelldb;
mod gdb;
mod go;
mod javascript;
mod python;
#[cfg(test)]
use std::path::PathBuf;
use std::sync::Arc;
use anyhow::Result;
use async_trait::async_trait;
use codelldb::CodeLldbDebugAdapter;
use dap::{
DapRegistry,
adapters::{
self, AdapterVersion, DapDelegate, DebugAdapter, DebugAdapterBinary, DebugAdapterName,
},
configure_tcp_connection,
};
use gdb::GdbDebugAdapter;
use go::GoDebugAdapter;
use gpui::{App, BorrowAppContext};
use javascript::JsDebugAdapter;
use python::PythonDebugAdapter;
use serde_json::json;
use task::{DebugScenario, ZedDebugConfig};
pub fn init(cx: &mut App) {
cx.update_default_global(|registry: &mut DapRegistry, _cx| {
registry.add_adapter(Arc::from(CodeLldbDebugAdapter::default()));
registry.add_adapter(Arc::from(PythonDebugAdapter::default()));
registry.add_adapter(Arc::from(JsDebugAdapter::default()));
registry.add_adapter(Arc::from(GoDebugAdapter::default()));
registry.add_adapter(Arc::from(GdbDebugAdapter));
#[cfg(any(test, feature = "test-support"))]
{
registry.add_adapter(Arc::from(dap::FakeAdapter {}));
}
})
}
#[cfg(test)]
mod test_mocks {
use super::*;
pub(crate) struct MockDelegate {
worktree_root: PathBuf,
}
impl MockDelegate {
pub(crate) fn new() -> Arc<dyn adapters::DapDelegate> {
Arc::new(Self {
worktree_root: PathBuf::from("/tmp/test"),
})
}
}
#[async_trait::async_trait]
impl adapters::DapDelegate for MockDelegate {
fn worktree_id(&self) -> settings::WorktreeId {
settings::WorktreeId::from_usize(0)
}
fn worktree_root_path(&self) -> &std::path::Path {
&self.worktree_root
}
fn http_client(&self) -> Arc<dyn http_client::HttpClient> {
unimplemented!("Not needed for tests")
}
fn node_runtime(&self) -> node_runtime::NodeRuntime {
unimplemented!("Not needed for tests")
}
fn toolchain_store(&self) -> Arc<dyn language::LanguageToolchainStore> {
unimplemented!("Not needed for tests")
}
fn fs(&self) -> Arc<dyn fs::Fs> {
unimplemented!("Not needed for tests")
}
fn output_to_console(&self, _msg: String) {}
async fn which(&self, _command: &std::ffi::OsStr) -> Option<PathBuf> {
None
}
async fn read_text_file(&self, _path: &util::rel_path::RelPath) -> Result<String> {
Ok(String::new())
}
async fn shell_env(&self) -> collections::HashMap<String, String> {
collections::HashMap::default()
}
fn is_headless(&self) -> bool {
false
}
}
}

View File

@@ -0,0 +1,265 @@
use anyhow::{Context as _, Result, bail};
use async_trait::async_trait;
use collections::HashMap;
use dap::{StartDebuggingRequestArguments, adapters::DebugTaskDefinition};
use gpui::AsyncApp;
use std::ffi::OsStr;
use task::{DebugScenario, ZedDebugConfig};
use crate::*;
#[derive(Default)]
pub(crate) struct GdbDebugAdapter;
impl GdbDebugAdapter {
const ADAPTER_NAME: &'static str = "GDB";
}
/// Ensures that "-i=dap" is present in the GDB argument list.
fn ensure_dap_interface(mut gdb_args: Vec<String>) -> Vec<String> {
if !gdb_args.iter().any(|arg| arg.trim() == "-i=dap") {
gdb_args.insert(0, "-i=dap".to_string());
}
gdb_args
}
#[async_trait(?Send)]
impl DebugAdapter for GdbDebugAdapter {
fn name(&self) -> DebugAdapterName {
DebugAdapterName(Self::ADAPTER_NAME.into())
}
async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
let mut obj = serde_json::Map::default();
match &zed_scenario.request {
dap::DebugRequest::Attach(attach) => {
obj.insert("request".into(), "attach".into());
obj.insert("pid".into(), attach.process_id.into());
}
dap::DebugRequest::Launch(launch) => {
obj.insert("request".into(), "launch".into());
obj.insert("program".into(), launch.program.clone().into());
if !launch.args.is_empty() {
obj.insert("args".into(), launch.args.clone().into());
}
if !launch.env.is_empty() {
obj.insert("env".into(), launch.env_json());
}
if let Some(stop_on_entry) = zed_scenario.stop_on_entry {
obj.insert(
"stopAtBeginningOfMainSubprogram".into(),
stop_on_entry.into(),
);
}
if let Some(cwd) = launch.cwd.as_ref() {
obj.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
}
}
}
Ok(DebugScenario {
adapter: zed_scenario.adapter,
label: zed_scenario.label,
build: None,
config: serde_json::Value::Object(obj),
tcp_connection: None,
})
}
fn dap_schema(&self) -> serde_json::Value {
json!({
"oneOf": [
{
"allOf": [
{
"type": "object",
"required": ["request"],
"properties": {
"request": {
"type": "string",
"enum": ["launch"],
"description": "Request to launch a new process"
}
}
},
{
"type": "object",
"properties": {
"program": {
"type": "string",
"description": "The program to debug. This corresponds to the GDB 'file' command."
},
"args": {
"type": "array",
"items": {
"type": "string"
},
"description": "Command line arguments passed to the program. These strings are provided as command-line arguments to the inferior.",
"default": []
},
"cwd": {
"type": "string",
"description": "Working directory for the debugged program. GDB will change its working directory to this directory."
},
"gdb_path": {
"type": "string",
"description": "Alternative path to the GDB executable, if the one in standard path is not desirable"
},
"gdb_args": {
"type": "array",
"items": {
"type":"string"
},
"description": "additional arguments given to GDB at startup, not the program debugged",
"default": []
},
"env": {
"type": "object",
"description": "Environment variables for the debugged program. Each key is the name of an environment variable; each value is the value of that variable."
},
"stopAtBeginningOfMainSubprogram": {
"type": "boolean",
"description": "When true, GDB will set a temporary breakpoint at the program's main procedure, like the 'start' command.",
"default": false
},
"stopOnEntry": {
"type": "boolean",
"description": "When true, GDB will set a temporary breakpoint at the program's first instruction, like the 'starti' command.",
"default": false
}
},
"required": ["program"]
}
]
},
{
"allOf": [
{
"type": "object",
"required": ["request"],
"properties": {
"request": {
"type": "string",
"enum": ["attach"],
"description": "Request to attach to an existing process"
}
}
},
{
"type": "object",
"properties": {
"pid": {
"type": "number",
"description": "The process ID to which GDB should attach."
},
"program": {
"type": "string",
"description": "The program to debug (optional). This corresponds to the GDB 'file' command. In many cases, GDB can determine which program is running automatically."
},
"target": {
"type": "string",
"description": "The target to which GDB should connect. This is passed to the 'target remote' command."
}
},
"required": ["pid"]
}
]
}
]
})
}
async fn get_binary(
&self,
delegate: &Arc<dyn DapDelegate>,
config: &DebugTaskDefinition,
user_installed_path: Option<std::path::PathBuf>,
user_args: Option<Vec<String>>,
user_env: Option<HashMap<String, String>>,
_: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
// Try to get gdb_path from config
let gdb_path_from_config = config
.config
.get("gdb_path")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let gdb_path = if let Some(path) = gdb_path_from_config {
path
} else {
// Original logic: use user_installed_path or search in system path
let user_setting_path = user_installed_path
.filter(|p| p.exists())
.and_then(|p| p.to_str().map(|s| s.to_string()));
let gdb_path_result = delegate
.which(OsStr::new("gdb"))
.await
.and_then(|p| p.to_str().map(|s| s.to_string()))
.context("Could not find gdb in path");
if gdb_path_result.is_err() && user_setting_path.is_none() {
bail!("Could not find gdb path or it's not installed");
}
user_setting_path.unwrap_or_else(|| gdb_path_result.unwrap())
};
// Arguments: use gdb_args from config if present, else user_args, else default
let gdb_args = {
let args = config
.config
.get("gdb_args")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str().map(|s| s.to_string()))
.collect::<Vec<_>>()
})
.or(user_args.clone())
.unwrap_or_else(|| vec!["-i=dap".into()]);
ensure_dap_interface(args)
};
let mut configuration = config.config.clone();
if let Some(configuration) = configuration.as_object_mut() {
configuration
.entry("cwd")
.or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into());
}
let mut base_env = delegate.shell_env().await;
base_env.extend(user_env.unwrap_or_default());
let config_env: HashMap<String, String> = config
.config
.get("env")
.and_then(|v| v.as_object())
.map(|obj| {
obj.iter()
.filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
.collect::<HashMap<String, String>>()
})
.unwrap_or_else(HashMap::default);
base_env.extend(config_env);
Ok(DebugAdapterBinary {
command: Some(gdb_path),
arguments: gdb_args,
envs: base_env,
cwd: Some(delegate.worktree_root_path().to_path_buf()),
connection: None,
request_args: StartDebuggingRequestArguments {
request: self.request_kind(&config.config).await?,
configuration,
},
})
}
}

View File

@@ -0,0 +1,627 @@
use anyhow::{Context as _, bail};
use collections::HashMap;
use dap::{
StartDebuggingRequestArguments,
adapters::{
DebugTaskDefinition, DownloadedFileType, TcpArguments, download_adapter_from_github,
latest_github_release,
},
};
use fs::Fs;
use futures::StreamExt;
use gpui::{AsyncApp, SharedString};
use language::LanguageName;
use log::warn;
use serde_json::{Map, Value};
use task::TcpArgumentsTemplate;
use util;
use std::{
env::consts,
ffi::OsStr,
path::{Path, PathBuf},
str::FromStr,
sync::OnceLock,
};
use crate::*;
#[derive(Default, Debug)]
pub(crate) struct GoDebugAdapter {
shim_path: OnceLock<PathBuf>,
}
impl GoDebugAdapter {
const ADAPTER_NAME: &'static str = "Delve";
async fn fetch_latest_adapter_version(
delegate: &Arc<dyn DapDelegate>,
) -> Result<AdapterVersion> {
let release = latest_github_release(
"zed-industries/delve-shim-dap",
true,
false,
delegate.http_client(),
)
.await?;
let os = match consts::OS {
"macos" => "apple-darwin",
"linux" => "unknown-linux-gnu",
"windows" => "pc-windows-msvc",
other => bail!("Running on unsupported os: {other}"),
};
let suffix = if consts::OS == "windows" {
".zip"
} else {
".tar.gz"
};
let asset_name = format!("delve-shim-dap-{}-{os}{suffix}", consts::ARCH);
let asset = release
.assets
.iter()
.find(|asset| asset.name == asset_name)
.with_context(|| format!("no asset found matching `{asset_name:?}`"))?;
Ok(AdapterVersion {
tag_name: release.tag_name,
url: asset.browser_download_url.clone(),
})
}
async fn install_shim(&self, delegate: &Arc<dyn DapDelegate>) -> anyhow::Result<PathBuf> {
if let Some(path) = self.shim_path.get().cloned() {
return Ok(path);
}
let adapter_dir = paths::debug_adapters_dir().join("delve-shim-dap");
match Self::fetch_latest_adapter_version(delegate).await {
Ok(asset) => {
let ty = if consts::OS == "windows" {
DownloadedFileType::Zip
} else {
DownloadedFileType::GzipTar
};
download_adapter_from_github(
"delve-shim-dap".into(),
asset.clone(),
ty,
delegate.as_ref(),
)
.await?;
let path = adapter_dir
.join(format!("delve-shim-dap_{}", asset.tag_name))
.join(format!("delve-shim-dap{}", consts::EXE_SUFFIX));
self.shim_path.set(path.clone()).ok();
Ok(path)
}
Err(error) => {
let binary_name = format!("delve-shim-dap{}", consts::EXE_SUFFIX);
let mut cached = None;
if let Ok(mut entries) = delegate.fs().read_dir(&adapter_dir).await {
while let Some(entry) = entries.next().await {
if let Ok(version_dir) = entry {
let candidate = version_dir.join(&binary_name);
if delegate
.fs()
.metadata(&candidate)
.await
.is_ok_and(|m| m.is_some())
{
cached = Some(candidate);
break;
}
}
}
}
if let Some(path) = cached {
warn!("Failed to fetch latest delve-shim-dap, using cached version: {error:#}");
self.shim_path.set(path.clone()).ok();
Ok(path)
} else {
Err(error)
}
}
}
}
}
#[async_trait(?Send)]
impl DebugAdapter for GoDebugAdapter {
fn name(&self) -> DebugAdapterName {
DebugAdapterName(Self::ADAPTER_NAME.into())
}
fn adapter_language_name(&self) -> Option<LanguageName> {
Some(SharedString::new_static("Go").into())
}
fn dap_schema(&self) -> serde_json::Value {
// Create common properties shared between launch and attach
let common_properties = json!({
"debugAdapter": {
"enum": ["legacy", "dlv-dap"],
"description": "Select which debug adapter to use with this configuration.",
"default": "dlv-dap"
},
"stopOnEntry": {
"type": "boolean",
"description": "Automatically stop program after launch or attach.",
"default": false
},
"showLog": {
"type": "boolean",
"description": "Show log output from the delve debugger. Maps to dlv's `--log` flag.",
"default": false
},
"cwd": {
"type": "string",
"description": "Workspace relative or absolute path to the working directory of the program being debugged.",
"default": "${ZED_WORKTREE_ROOT}"
},
"dlvFlags": {
"type": "array",
"description": "Extra flags for `dlv`. See `dlv help` for the full list of supported flags.",
"items": {
"type": "string"
},
"default": []
},
"port": {
"type": "number",
"description": "Debug server port. For remote configurations, this is where to connect.",
"default": 2345
},
"host": {
"type": "string",
"description": "Debug server host. For remote configurations, this is where to connect.",
"default": "127.0.0.1"
},
"substitutePath": {
"type": "array",
"items": {
"type": "object",
"properties": {
"from": {
"type": "string",
"description": "The absolute local path to be replaced."
},
"to": {
"type": "string",
"description": "The absolute remote path to replace with."
}
}
},
"description": "Mappings from local to remote paths for debugging.",
"default": []
},
"trace": {
"type": "string",
"enum": ["verbose", "trace", "log", "info", "warn", "error"],
"default": "error",
"description": "Debug logging level."
},
"backend": {
"type": "string",
"enum": ["default", "native", "lldb", "rr"],
"description": "Backend used by delve. Maps to `dlv`'s `--backend` flag."
},
"logOutput": {
"type": "string",
"enum": ["debugger", "gdbwire", "lldbout", "debuglineerr", "rpc", "dap"],
"description": "Components that should produce debug output.",
"default": "debugger"
},
"logDest": {
"type": "string",
"description": "Log destination for delve."
},
"stackTraceDepth": {
"type": "number",
"description": "Maximum depth of stack traces.",
"default": 50
},
"showGlobalVariables": {
"type": "boolean",
"default": false,
"description": "Show global package variables in variables pane."
},
"showRegisters": {
"type": "boolean",
"default": false,
"description": "Show register variables in variables pane."
},
"hideSystemGoroutines": {
"type": "boolean",
"default": false,
"description": "Hide system goroutines from call stack view."
},
"console": {
"default": "internalConsole",
"description": "Where to launch the debugger.",
"enum": ["internalConsole", "integratedTerminal"]
},
"asRoot": {
"default": false,
"description": "Debug with elevated permissions (on Unix).",
"type": "boolean"
}
});
// Create launch-specific properties
let launch_properties = json!({
"program": {
"type": "string",
"description": "Path to the program folder or file to debug.",
"default": "${ZED_WORKTREE_ROOT}"
},
"args": {
"type": ["array", "string"],
"description": "Command line arguments for the program.",
"items": {
"type": "string"
},
"default": []
},
"env": {
"type": "object",
"description": "Environment variables for the debugged program.",
"default": {}
},
"envFile": {
"type": ["string", "array"],
"items": {
"type": "string"
},
"description": "Path(s) to files with environment variables.",
"default": ""
},
"buildFlags": {
"type": ["string", "array"],
"items": {
"type": "string"
},
"description": "Flags for the Go compiler.",
"default": []
},
"output": {
"type": "string",
"description": "Output path for the binary.",
"default": "debug"
},
"mode": {
"enum": [ "debug", "test", "exec", "replay", "core"],
"description": "Debug mode for launch configuration.",
},
"traceDirPath": {
"type": "string",
"description": "Directory for record trace (for 'replay' mode).",
"default": ""
},
"coreFilePath": {
"type": "string",
"description": "Path to core dump file (for 'core' mode).",
"default": ""
}
});
// Create attach-specific properties
let attach_properties = json!({
"processId": {
"anyOf": [
{
"enum": ["${command:pickProcess}", "${command:pickGoProcess}"],
"description": "Use process picker to select a process."
},
{
"type": "string",
"description": "Process name to attach to."
},
{
"type": "number",
"description": "Process ID to attach to."
}
],
"default": 0
},
"mode": {
"enum": ["local", "remote"],
"description": "Local or remote debugging.",
"default": "local"
},
"remotePath": {
"type": "string",
"description": "Path to source on remote machine.",
"markdownDeprecationMessage": "Use `substitutePath` instead.",
"default": ""
}
});
// Create the final schema
json!({
"oneOf": [
{
"allOf": [
{
"type": "object",
"required": ["request"],
"properties": {
"request": {
"type": "string",
"enum": ["launch"],
"description": "Request to launch a new process"
}
}
},
{
"type": "object",
"properties": common_properties
},
{
"type": "object",
"required": ["program", "mode"],
"properties": launch_properties
}
]
},
{
"allOf": [
{
"type": "object",
"required": ["request"],
"properties": {
"request": {
"type": "string",
"enum": ["attach"],
"description": "Request to attach to an existing process"
}
}
},
{
"type": "object",
"properties": common_properties
},
{
"type": "object",
"required": ["mode"],
"properties": attach_properties
}
]
}
]
})
}
async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
let mut args = match &zed_scenario.request {
dap::DebugRequest::Attach(attach_config) => {
json!({
"request": "attach",
"mode": "local",
"processId": attach_config.process_id,
})
}
dap::DebugRequest::Launch(launch_config) => {
let mode = if launch_config.program != "." {
"exec"
} else {
"debug"
};
json!({
"request": "launch",
"mode": mode,
"program": launch_config.program,
"cwd": launch_config.cwd,
"args": launch_config.args,
"env": launch_config.env_json()
})
}
};
let map = args.as_object_mut().unwrap();
if let Some(stop_on_entry) = zed_scenario.stop_on_entry {
map.insert("stopOnEntry".into(), stop_on_entry.into());
}
Ok(DebugScenario {
adapter: zed_scenario.adapter,
label: zed_scenario.label,
build: None,
config: args,
tcp_connection: None,
})
}
async fn get_binary(
&self,
delegate: &Arc<dyn DapDelegate>,
task_definition: &DebugTaskDefinition,
user_installed_path: Option<PathBuf>,
user_args: Option<Vec<String>>,
user_env: Option<HashMap<String, String>>,
_cx: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
let adapter_path = paths::debug_adapters_dir().join(&Self::ADAPTER_NAME);
let dlv_binary = format!("dlv{}", consts::EXE_SUFFIX);
let dlv_path = adapter_path.join(&dlv_binary);
let delve_path = if let Some(path) = user_installed_path {
path.to_string_lossy().into_owned()
} else if let Some(path) = delegate.which(OsStr::new("dlv")).await {
path.to_string_lossy().into_owned()
} else if delegate.fs().is_file(&dlv_path).await {
dlv_path.to_string_lossy().into_owned()
} else {
let go = delegate
.which(OsStr::new("go"))
.await
.context("Go not found in path. Please install Go first, then Dlv will be installed automatically.")?;
let adapter_path = paths::debug_adapters_dir().join(&Self::ADAPTER_NAME);
let install_output = util::command::new_command(&go)
.env("GO111MODULE", "on")
.env("GOBIN", &adapter_path)
.args(&["install", "github.com/go-delve/delve/cmd/dlv@latest"])
.output()
.await?;
if !install_output.status.success() {
bail!(
"failed to install dlv via `go install`. stdout: {:?}, stderr: {:?}\n Please try installing it manually using 'go install github.com/go-delve/delve/cmd/dlv@latest'",
String::from_utf8_lossy(&install_output.stdout),
String::from_utf8_lossy(&install_output.stderr)
);
}
adapter_path
.join(&dlv_binary)
.to_string_lossy()
.into_owned()
};
let cwd = Some(
task_definition
.config
.get("cwd")
.and_then(|s| s.as_str())
.map(PathBuf::from)
.unwrap_or_else(|| delegate.worktree_root_path().to_path_buf()),
);
let arguments;
let command;
let connection;
let mut configuration = task_definition.config.clone();
let mut envs = user_env.unwrap_or_default();
if let Some(configuration) = configuration.as_object_mut() {
configuration
.entry("cwd")
.or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into());
handle_envs(
configuration,
&mut envs,
cwd.as_deref(),
delegate.fs().clone(),
)
.await;
}
if let Some(connection_options) = &task_definition.tcp_connection {
command = None;
arguments = vec![];
let (host, port, timeout) =
crate::configure_tcp_connection(connection_options.clone()).await?;
connection = Some(TcpArguments {
host,
port,
timeout,
});
} else {
let minidelve_path = self.install_shim(delegate).await?;
let (host, port, _) =
crate::configure_tcp_connection(TcpArgumentsTemplate::default()).await?;
command = Some(minidelve_path.to_string_lossy().into_owned());
connection = None;
arguments = if let Some(mut args) = user_args {
args.insert(0, delve_path);
args
} else if cfg!(windows) {
vec![
delve_path,
"dap".into(),
"--listen".into(),
format!("{}:{}", host, port),
"--headless".into(),
]
} else {
vec![
delve_path,
"dap".into(),
"--listen".into(),
format!("{}:{}", host, port),
]
};
}
Ok(DebugAdapterBinary {
command,
arguments,
cwd,
envs,
connection,
request_args: StartDebuggingRequestArguments {
configuration,
request: self.request_kind(&task_definition.config).await?,
},
})
}
}
// delve doesn't do anything with the envFile setting, so we intercept it
async fn handle_envs(
config: &mut Map<String, Value>,
envs: &mut HashMap<String, String>,
cwd: Option<&Path>,
fs: Arc<dyn Fs>,
) -> Option<()> {
let env_files = match config.get("envFile")? {
Value::Array(arr) => arr.iter().map(|v| v.as_str()).collect::<Vec<_>>(),
Value::String(s) => vec![Some(s.as_str())],
_ => return None,
};
let rebase_path = |path: PathBuf| {
if path.is_absolute() {
Some(path)
} else {
cwd.map(|p| p.join(path))
}
};
let mut env_vars = HashMap::default();
for path in env_files {
let Some(path) = path
.and_then(|s| PathBuf::from_str(s).ok())
.and_then(rebase_path)
else {
continue;
};
if let Ok(file) = fs.open_sync(&path).await {
let file_envs: HashMap<String, String> = dotenvy::from_read_iter(file)
.filter_map(Result::ok)
.collect();
envs.extend(file_envs.iter().map(|(k, v)| (k.clone(), v.clone())));
env_vars.extend(file_envs);
} else {
warn!("While starting Go debug session: failed to read env file {path:?}");
};
}
let mut env_obj: serde_json::Map<String, Value> = serde_json::Map::new();
for (k, v) in env_vars {
env_obj.insert(k, Value::String(v));
}
if let Some(existing_env) = config.get("env").and_then(|v| v.as_object()) {
for (k, v) in existing_env {
env_obj.insert(k.clone(), v.clone());
}
}
if !env_obj.is_empty() {
config.insert("env".to_string(), Value::Object(env_obj));
}
// remove envFile now that it's been handled
config.remove("envFile");
Some(())
}

View File

@@ -0,0 +1,569 @@
use adapters::latest_github_release;
use anyhow::Context as _;
use collections::HashMap;
use dap::{StartDebuggingRequestArguments, adapters::DebugTaskDefinition};
use gpui::AsyncApp;
use serde_json::Value;
use std::{path::PathBuf, sync::OnceLock};
use task::DebugRequest;
use util::{ResultExt, maybe, shell::ShellKind};
use crate::*;
#[derive(Debug, Default)]
pub(crate) struct JsDebugAdapter {
checked: OnceLock<()>,
}
impl JsDebugAdapter {
const ADAPTER_NAME: &'static str = "JavaScript";
const ADAPTER_NPM_NAME: &'static str = "vscode-js-debug";
const ADAPTER_PATH: &'static str = "js-debug/src/dapDebugServer.js";
async fn fetch_latest_adapter_version(
&self,
delegate: &Arc<dyn DapDelegate>,
) -> Result<AdapterVersion> {
let release = latest_github_release(
&format!("microsoft/{}", Self::ADAPTER_NPM_NAME),
true,
false,
delegate.http_client(),
)
.await?;
let asset_name = format!("js-debug-dap-{}.tar.gz", release.tag_name);
Ok(AdapterVersion {
tag_name: release.tag_name,
url: release
.assets
.iter()
.find(|asset| asset.name == asset_name)
.with_context(|| format!("no asset found matching {asset_name:?}"))?
.browser_download_url
.clone(),
})
}
async fn get_installed_binary(
&self,
delegate: &Arc<dyn DapDelegate>,
task_definition: &DebugTaskDefinition,
user_installed_path: Option<PathBuf>,
user_args: Option<Vec<String>>,
user_env: Option<HashMap<String, String>>,
_: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
let tcp_connection = task_definition.tcp_connection.clone().unwrap_or_default();
let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
let mut envs = user_env.unwrap_or_default();
let mut configuration = task_definition.config.clone();
if let Some(configuration) = configuration.as_object_mut() {
maybe!({
configuration
.get("type")
.filter(|value| value == &"node-terminal")?;
let command = configuration.get("command")?.as_str()?.to_owned();
let mut args = ShellKind::Posix.split(&command)?.into_iter();
let program = args.next()?;
configuration.insert("runtimeExecutable".to_owned(), program.into());
configuration.insert(
"runtimeArgs".to_owned(),
args.map(Value::from).collect::<Vec<_>>().into(),
);
configuration.insert("console".to_owned(), "externalTerminal".into());
Some(())
});
configuration.entry("type").and_modify(normalize_task_type);
if let Some(program) = configuration
.get("program")
.cloned()
.and_then(|value| value.as_str().map(str::to_owned))
{
match program.as_str() {
"npm" | "pnpm" | "yarn" | "bun"
if !configuration.contains_key("runtimeExecutable")
&& !configuration.contains_key("runtimeArgs") =>
{
configuration.remove("program");
configuration.insert("runtimeExecutable".to_owned(), program.into());
if let Some(args) = configuration.remove("args") {
configuration.insert("runtimeArgs".to_owned(), args);
}
}
_ => {}
}
}
if let Some(env) = configuration.get("env").cloned()
&& let Ok(env) = serde_json::from_value::<HashMap<String, String>>(env)
{
envs.extend(env);
}
configuration
.entry("cwd")
.or_insert(delegate.worktree_root_path().to_string_lossy().into());
configuration
.entry("console")
.or_insert("externalTerminal".into());
configuration.entry("sourceMaps").or_insert(true.into());
configuration
.entry("pauseForSourceMap")
.or_insert(true.into());
configuration
.entry("sourceMapRenames")
.or_insert(true.into());
// Set up remote browser debugging
if delegate.is_headless() {
configuration
.entry("browserLaunchLocation")
.or_insert("ui".into());
}
}
let adapter_path = if let Some(user_installed_path) = user_installed_path {
user_installed_path
} else {
let adapter_path = paths::debug_adapters_dir().join(self.name().as_ref());
let file_name_prefix = format!("{}_", self.name());
util::fs::find_file_name_in_dir(adapter_path.as_path(), |file_name| {
file_name.starts_with(&file_name_prefix)
})
.await
.context("Couldn't find JavaScript dap directory")?
.join(Self::ADAPTER_PATH)
};
let arguments = if let Some(mut args) = user_args {
args.insert(0, adapter_path.to_string_lossy().into_owned());
args
} else {
vec![
adapter_path.to_string_lossy().into_owned(),
port.to_string(),
host.to_string(),
]
};
Ok(DebugAdapterBinary {
command: Some(
delegate
.node_runtime()
.binary_path()
.await?
.to_string_lossy()
.into_owned(),
),
arguments,
cwd: Some(delegate.worktree_root_path().to_path_buf()),
envs,
connection: Some(adapters::TcpArguments {
host,
port,
timeout,
}),
request_args: StartDebuggingRequestArguments {
configuration,
request: self.request_kind(&task_definition.config).await?,
},
})
}
}
#[async_trait(?Send)]
impl DebugAdapter for JsDebugAdapter {
fn name(&self) -> DebugAdapterName {
DebugAdapterName(Self::ADAPTER_NAME.into())
}
async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
let mut args = json!({
"type": "pwa-node",
"request": match zed_scenario.request {
DebugRequest::Launch(_) => "launch",
DebugRequest::Attach(_) => "attach",
},
});
let map = args.as_object_mut().unwrap();
match &zed_scenario.request {
DebugRequest::Attach(attach) => {
map.insert("processId".into(), attach.process_id.into());
}
DebugRequest::Launch(launch) => {
if launch.program.starts_with("http://") {
map.insert("url".into(), launch.program.clone().into());
} else {
map.insert("program".into(), launch.program.clone().into());
}
if !launch.args.is_empty() {
map.insert("args".into(), launch.args.clone().into());
}
if !launch.env.is_empty() {
map.insert("env".into(), launch.env_json());
}
if let Some(stop_on_entry) = zed_scenario.stop_on_entry {
map.insert("stopOnEntry".into(), stop_on_entry.into());
}
if let Some(cwd) = launch.cwd.as_ref() {
map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
}
}
};
Ok(DebugScenario {
adapter: zed_scenario.adapter,
label: zed_scenario.label,
build: None,
config: args,
tcp_connection: None,
})
}
fn dap_schema(&self) -> serde_json::Value {
json!({
"oneOf": [
{
"allOf": [
{
"type": "object",
"required": ["request"],
"properties": {
"request": {
"type": "string",
"enum": ["launch"],
"description": "Request to launch a new process"
}
}
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["pwa-node", "node", "chrome", "pwa-chrome", "msedge", "pwa-msedge", "node-terminal"],
"description": "The type of debug session",
"default": "pwa-node"
},
"program": {
"type": "string",
"description": "Path to the program or file to debug"
},
"cwd": {
"type": "string",
"description": "Absolute path to the working directory of the program being debugged"
},
"args": {
"type": ["array", "string"],
"description": "Command line arguments passed to the program",
"items": {
"type": "string"
},
"default": []
},
"env": {
"type": "object",
"description": "Environment variables passed to the program",
"default": {}
},
"envFile": {
"type": ["string", "array"],
"description": "Path to a file containing environment variable definitions",
"items": {
"type": "string"
}
},
"stopOnEntry": {
"type": "boolean",
"description": "Automatically stop program after launch",
"default": false
},
"attachSimplePort": {
"type": "number",
"description": "If set, attaches to the process via the given port. This is generally no longer necessary for Node.js programs and loses the ability to debug child processes, but can be useful in more esoteric scenarios such as with Deno and Docker launches. If set to 0, a random port will be chosen and --inspect-brk added to the launch arguments automatically."
},
"runtimeExecutable": {
"type": ["string", "null"],
"description": "Runtime to use, an absolute path or the name of a runtime available on PATH",
"default": "node"
},
"runtimeArgs": {
"type": ["array", "null"],
"description": "Arguments passed to the runtime executable",
"items": {
"type": "string"
},
"default": []
},
"outFiles": {
"type": "array",
"description": "Glob patterns for locating generated JavaScript files",
"items": {
"type": "string"
},
"default": ["${ZED_WORKTREE_ROOT}/**/*.js", "!**/node_modules/**"]
},
"sourceMaps": {
"type": "boolean",
"description": "Use JavaScript source maps if they exist",
"default": true
},
"pauseForSourceMap": {
"type": "boolean",
"description": "Wait for source maps to load before setting breakpoints.",
"default": true
},
"sourceMapRenames": {
"type": "boolean",
"description": "Whether to use the \"names\" mapping in sourcemaps.",
"default": true
},
"sourceMapPathOverrides": {
"type": "object",
"description": "Rewrites the locations of source files from what the sourcemap says to their locations on disk",
"default": {}
},
"restart": {
"type": ["boolean", "object"],
"description": "Restart session after Node.js has terminated",
"default": false
},
"trace": {
"type": ["boolean", "object"],
"description": "Enables logging of the Debug Adapter",
"default": false
},
"console": {
"type": "string",
"enum": ["internalConsole", "integratedTerminal"],
"description": "Where to launch the debug target",
"default": "internalConsole"
},
// Browser-specific
"url": {
"type": ["string", "null"],
"description": "Will navigate to this URL and attach to it (browser debugging)"
},
"webRoot": {
"type": "string",
"description": "Workspace absolute path to the webserver root",
"default": "${ZED_WORKTREE_ROOT}"
},
"userDataDir": {
"type": ["string", "boolean"],
"description": "Path to a custom Chrome user profile (browser debugging)",
"default": true
},
"skipFiles": {
"type": "array",
"description": "An array of glob patterns for files to skip when debugging",
"items": {
"type": "string"
},
"default": ["<node_internals>/**"]
},
"timeout": {
"type": "number",
"description": "Retry for this number of milliseconds to connect to the debug adapter",
"default": 10000
},
"resolveSourceMapLocations": {
"type": ["array", "null"],
"description": "A list of minimatch patterns for source map resolution",
"items": {
"type": "string"
}
}
},
}
]
},
{
"allOf": [
{
"type": "object",
"required": ["request"],
"properties": {
"request": {
"type": "string",
"enum": ["attach"],
"description": "Request to attach to an existing process"
}
}
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["pwa-node", "node", "chrome", "pwa-chrome", "edge", "pwa-edge"],
"description": "The type of debug session",
"default": "pwa-node"
},
"processId": {
"type": ["string", "number"],
"description": "ID of process to attach to (Node.js debugging)"
},
"port": {
"type": "number",
"description": "Debug port to attach to",
"default": 9229
},
"address": {
"type": "string",
"description": "TCP/IP address of the process to be debugged",
"default": "localhost"
},
"restart": {
"type": ["boolean", "object"],
"description": "Restart session after Node.js has terminated",
"default": false
},
"sourceMaps": {
"type": "boolean",
"description": "Use JavaScript source maps if they exist",
"default": true
},
"sourceMapPathOverrides": {
"type": "object",
"description": "Rewrites the locations of source files from what the sourcemap says to their locations on disk",
"default": {}
},
"outFiles": {
"type": "array",
"description": "Glob patterns for locating generated JavaScript files",
"items": {
"type": "string"
},
"default": ["${ZED_WORKTREE_ROOT}/**/*.js", "!**/node_modules/**"]
},
"url": {
"type": "string",
"description": "Will search for a page with this URL and attach to it (browser debugging)"
},
"webRoot": {
"type": "string",
"description": "Workspace absolute path to the webserver root",
"default": "${ZED_WORKTREE_ROOT}"
},
"skipFiles": {
"type": "array",
"description": "An array of glob patterns for files to skip when debugging",
"items": {
"type": "string"
},
"default": ["<node_internals>/**"]
},
"timeout": {
"type": "number",
"description": "Retry for this number of milliseconds to connect to the debug adapter",
"default": 10000
},
"resolveSourceMapLocations": {
"type": ["array", "null"],
"description": "A list of minimatch patterns for source map resolution",
"items": {
"type": "string"
}
},
"remoteRoot": {
"type": ["string", "null"],
"description": "Path to the remote directory containing the program"
},
"localRoot": {
"type": ["string", "null"],
"description": "Path to the local directory containing the program"
}
},
"oneOf": [
{ "required": ["processId"] },
{ "required": ["port"] }
]
}
]
}
]
})
}
async fn get_binary(
&self,
delegate: &Arc<dyn DapDelegate>,
config: &DebugTaskDefinition,
user_installed_path: Option<PathBuf>,
user_args: Option<Vec<String>>,
user_env: Option<HashMap<String, String>>,
cx: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
if self.checked.set(()).is_ok() {
delegate.output_to_console(format!("Checking latest version of {}...", self.name()));
if let Some(version) = self.fetch_latest_adapter_version(delegate).await.log_err() {
adapters::download_adapter_from_github(
self.name(),
version,
adapters::DownloadedFileType::GzipTar,
delegate.as_ref(),
)
.await?;
} else {
delegate.output_to_console(format!("{} debug adapter is up to date", self.name()));
}
}
self.get_installed_binary(
delegate,
config,
user_installed_path,
user_args,
user_env,
cx,
)
.await
}
fn label_for_child_session(&self, args: &StartDebuggingRequestArguments) -> Option<String> {
let label = args
.configuration
.get("name")?
.as_str()
.filter(|name| !name.is_empty())?;
Some(label.to_owned())
}
fn compact_child_session(&self) -> bool {
true
}
fn prefer_thread_name(&self) -> bool {
true
}
}
fn normalize_task_type(task_type: &mut Value) {
let Some(task_type_str) = task_type.as_str() else {
return;
};
let new_name = match task_type_str {
"node" | "pwa-node" | "node-terminal" => "pwa-node",
"chrome" | "pwa-chrome" => "pwa-chrome",
"edge" | "msedge" | "pwa-edge" | "pwa-msedge" => "pwa-msedge",
_ => task_type_str,
}
.to_owned();
*task_type = Value::String(new_name);
}

File diff suppressed because it is too large Load Diff