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:
63
crates/dap/Cargo.toml
Normal file
63
crates/dap/Cargo.toml
Normal file
@@ -0,0 +1,63 @@
|
||||
[package]
|
||||
name = "dap"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/dap.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
test-support = [
|
||||
"gpui/test-support",
|
||||
"util/test-support",
|
||||
"task/test-support",
|
||||
"async-pipe",
|
||||
"settings/test-support",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-compression.workspace = true
|
||||
async-pipe = { workspace = true, optional = true }
|
||||
async-tar.workspace = true
|
||||
async-trait.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
dap-types.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
node_runtime.workspace = true
|
||||
parking_lot.workspace = true
|
||||
paths.workspace = true
|
||||
proto.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
smol.workspace = true
|
||||
task.workspace = true
|
||||
telemetry.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
libc.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
async-pipe.workspace = true
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
settings = { workspace = true, features = ["test-support"] }
|
||||
task = { workspace = true, features = ["test-support"] }
|
||||
|
||||
util = { workspace = true, features = ["test-support"] }
|
||||
zlog.workspace = true
|
||||
1
crates/dap/LICENSE-GPL
Symbolic link
1
crates/dap/LICENSE-GPL
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE-GPL
|
||||
9
crates/dap/docs/breakpoints.md
Normal file
9
crates/dap/docs/breakpoints.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Overview
|
||||
|
||||
The active `Project` is responsible for maintain opened and closed breakpoints
|
||||
as well as serializing breakpoints to save. At a high level project serializes
|
||||
the positions of breakpoints that don't belong to any active buffers and handles
|
||||
converting breakpoints from serializing to active whenever a buffer is opened/closed.
|
||||
|
||||
`Project` also handles sending all relevant breakpoint information to debug adapter's
|
||||
during debugging or when starting a debugger.
|
||||
483
crates/dap/src/adapters.rs
Normal file
483
crates/dap/src/adapters.rs
Normal file
@@ -0,0 +1,483 @@
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use async_trait::async_trait;
|
||||
use collections::HashMap;
|
||||
pub use dap_types::{StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest};
|
||||
use fs::Fs;
|
||||
use futures::io::BufReader;
|
||||
use gpui::{AsyncApp, SharedString};
|
||||
pub use http_client::{HttpClient, github::latest_github_release};
|
||||
use language::{LanguageName, LanguageToolchainStore};
|
||||
use node_runtime::NodeRuntime;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::WorktreeId;
|
||||
use smol::fs::File;
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
ffi::OsStr,
|
||||
fmt::Debug,
|
||||
net::IpAddr,
|
||||
ops::Deref,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use task::{DebugScenario, TcpArgumentsTemplate, ZedDebugConfig};
|
||||
use util::{archive::extract_zip, rel_path::RelPath};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum DapStatus {
|
||||
None,
|
||||
CheckingForUpdate,
|
||||
Downloading,
|
||||
Failed { error: String },
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait DapDelegate: Send + Sync + 'static {
|
||||
fn worktree_id(&self) -> WorktreeId;
|
||||
fn worktree_root_path(&self) -> &Path;
|
||||
fn http_client(&self) -> Arc<dyn HttpClient>;
|
||||
fn node_runtime(&self) -> NodeRuntime;
|
||||
fn toolchain_store(&self) -> Arc<dyn LanguageToolchainStore>;
|
||||
fn fs(&self) -> Arc<dyn Fs>;
|
||||
fn output_to_console(&self, msg: String);
|
||||
async fn which(&self, command: &OsStr) -> Option<PathBuf>;
|
||||
async fn read_text_file(&self, path: &RelPath) -> Result<String>;
|
||||
async fn shell_env(&self) -> collections::HashMap<String, String>;
|
||||
fn is_headless(&self) -> bool;
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize, JsonSchema,
|
||||
)]
|
||||
#[serde(transparent)]
|
||||
pub struct DebugAdapterName(pub SharedString);
|
||||
|
||||
impl Deref for DebugAdapterName {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for DebugAdapterName {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<str> for DebugAdapterName {
|
||||
fn borrow(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<SharedString> for DebugAdapterName {
|
||||
fn borrow(&self) -> &SharedString {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DebugAdapterName {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DebugAdapterName> for SharedString {
|
||||
fn from(name: DebugAdapterName) -> Self {
|
||||
name.0
|
||||
}
|
||||
}
|
||||
impl From<SharedString> for DebugAdapterName {
|
||||
fn from(name: SharedString) -> Self {
|
||||
DebugAdapterName(name)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for DebugAdapterName {
|
||||
fn from(str: &'a str) -> DebugAdapterName {
|
||||
DebugAdapterName(str.to_string().into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
pub struct TcpArguments {
|
||||
pub host: IpAddr,
|
||||
pub port: u16,
|
||||
pub timeout: Option<u64>,
|
||||
}
|
||||
|
||||
impl TcpArguments {
|
||||
pub fn from_proto(proto: proto::TcpHost) -> anyhow::Result<Self> {
|
||||
let host = TcpArgumentsTemplate::from_proto(proto)?;
|
||||
Ok(TcpArguments {
|
||||
host: host.host.context("missing host")?,
|
||||
port: host.port.context("missing port")?,
|
||||
timeout: host.timeout,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_proto(&self) -> proto::TcpHost {
|
||||
TcpArgumentsTemplate {
|
||||
host: Some(self.host),
|
||||
port: Some(self.port),
|
||||
timeout: self.timeout,
|
||||
}
|
||||
.to_proto()
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a debuggable binary/process (what process is going to be debugged and with what arguments).
|
||||
///
|
||||
/// We start off with a [DebugScenario], a user-facing type that additionally defines how a debug target is built; once
|
||||
/// an optional build step is completed, we turn it's result into a DebugTaskDefinition by running a locator (or using a user-provided task) and resolving task variables.
|
||||
/// Finally, a [DebugTaskDefinition] has to be turned into a concrete debugger invocation ([DebugAdapterBinary]).
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(
|
||||
any(feature = "test-support", test),
|
||||
derive(serde::Deserialize, serde::Serialize)
|
||||
)]
|
||||
pub struct DebugTaskDefinition {
|
||||
/// The name of this debug task
|
||||
pub label: SharedString,
|
||||
/// The debug adapter to use
|
||||
pub adapter: DebugAdapterName,
|
||||
/// The configuration to send to the debug adapter
|
||||
pub config: serde_json::Value,
|
||||
/// Optional TCP connection information
|
||||
///
|
||||
/// If provided, this will be used to connect to the debug adapter instead of
|
||||
/// spawning a new debug adapter process. This is useful for connecting to a debug adapter
|
||||
/// that is already running or is started by another process.
|
||||
pub tcp_connection: Option<TcpArgumentsTemplate>,
|
||||
}
|
||||
|
||||
impl DebugTaskDefinition {
|
||||
pub fn to_scenario(&self) -> DebugScenario {
|
||||
DebugScenario {
|
||||
label: self.label.clone(),
|
||||
adapter: self.adapter.clone().into(),
|
||||
build: None,
|
||||
tcp_connection: self.tcp_connection.clone(),
|
||||
config: self.config.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_proto(&self) -> proto::DebugTaskDefinition {
|
||||
proto::DebugTaskDefinition {
|
||||
label: self.label.clone().into(),
|
||||
config: self.config.to_string(),
|
||||
tcp_connection: self.tcp_connection.clone().map(|v| v.to_proto()),
|
||||
adapter: self.adapter.clone().0.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_proto(proto: proto::DebugTaskDefinition) -> Result<Self> {
|
||||
Ok(Self {
|
||||
label: proto.label.into(),
|
||||
config: serde_json::from_str(&proto.config)?,
|
||||
tcp_connection: proto
|
||||
.tcp_connection
|
||||
.map(TcpArgumentsTemplate::from_proto)
|
||||
.transpose()?,
|
||||
adapter: DebugAdapterName(proto.adapter.into()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Created from a [DebugTaskDefinition], this struct describes how to spawn the debugger to create a previously-configured debug session.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
pub struct DebugAdapterBinary {
|
||||
pub command: Option<String>,
|
||||
pub arguments: Vec<String>,
|
||||
pub envs: HashMap<String, String>,
|
||||
pub cwd: Option<PathBuf>,
|
||||
pub connection: Option<TcpArguments>,
|
||||
pub request_args: StartDebuggingRequestArguments,
|
||||
}
|
||||
|
||||
impl DebugAdapterBinary {
|
||||
pub fn from_proto(binary: proto::DebugAdapterBinary) -> anyhow::Result<Self> {
|
||||
let request = match binary.launch_type() {
|
||||
proto::debug_adapter_binary::LaunchType::Launch => {
|
||||
StartDebuggingRequestArgumentsRequest::Launch
|
||||
}
|
||||
proto::debug_adapter_binary::LaunchType::Attach => {
|
||||
StartDebuggingRequestArgumentsRequest::Attach
|
||||
}
|
||||
};
|
||||
|
||||
Ok(DebugAdapterBinary {
|
||||
command: binary.command,
|
||||
arguments: binary.arguments,
|
||||
envs: binary.envs.into_iter().collect(),
|
||||
connection: binary
|
||||
.connection
|
||||
.map(TcpArguments::from_proto)
|
||||
.transpose()?,
|
||||
request_args: StartDebuggingRequestArguments {
|
||||
configuration: serde_json::from_str(&binary.configuration)?,
|
||||
request,
|
||||
},
|
||||
cwd: binary.cwd.map(|cwd| cwd.into()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_proto(&self) -> proto::DebugAdapterBinary {
|
||||
proto::DebugAdapterBinary {
|
||||
command: self.command.clone(),
|
||||
arguments: self.arguments.clone(),
|
||||
envs: self
|
||||
.envs
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect(),
|
||||
cwd: self
|
||||
.cwd
|
||||
.as_ref()
|
||||
.map(|cwd| cwd.to_string_lossy().into_owned()),
|
||||
connection: self.connection.as_ref().map(|c| c.to_proto()),
|
||||
launch_type: match self.request_args.request {
|
||||
StartDebuggingRequestArgumentsRequest::Launch => {
|
||||
proto::debug_adapter_binary::LaunchType::Launch.into()
|
||||
}
|
||||
StartDebuggingRequestArgumentsRequest::Attach => {
|
||||
proto::debug_adapter_binary::LaunchType::Attach.into()
|
||||
}
|
||||
},
|
||||
configuration: self.request_args.configuration.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AdapterVersion {
|
||||
pub tag_name: String,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
pub enum DownloadedFileType {
|
||||
Vsix,
|
||||
GzipTar,
|
||||
Zip,
|
||||
}
|
||||
|
||||
pub struct GithubRepo {
|
||||
pub repo_name: String,
|
||||
pub repo_owner: String,
|
||||
}
|
||||
|
||||
pub async fn download_adapter_from_github(
|
||||
adapter_name: DebugAdapterName,
|
||||
github_version: AdapterVersion,
|
||||
file_type: DownloadedFileType,
|
||||
delegate: &dyn DapDelegate,
|
||||
) -> Result<PathBuf> {
|
||||
let adapter_path = paths::debug_adapters_dir().join(&adapter_name.as_ref());
|
||||
let version_path = adapter_path.join(format!("{}_{}", adapter_name, github_version.tag_name));
|
||||
let fs = delegate.fs();
|
||||
|
||||
if version_path.exists() {
|
||||
return Ok(version_path);
|
||||
}
|
||||
|
||||
if !adapter_path.exists() {
|
||||
fs.create_dir(adapter_path.as_path())
|
||||
.await
|
||||
.context("Failed creating adapter path")?;
|
||||
}
|
||||
|
||||
log::debug!(
|
||||
"Downloading adapter {} from {}",
|
||||
adapter_name,
|
||||
&github_version.url,
|
||||
);
|
||||
delegate.output_to_console(format!("Downloading from {}...", github_version.url));
|
||||
|
||||
let mut response = delegate
|
||||
.http_client()
|
||||
.get(&github_version.url, Default::default(), true)
|
||||
.await
|
||||
.context("Error downloading release")?;
|
||||
anyhow::ensure!(
|
||||
response.status().is_success(),
|
||||
"download failed with status {}",
|
||||
response.status()
|
||||
);
|
||||
|
||||
delegate.output_to_console("Download complete".to_owned());
|
||||
match file_type {
|
||||
DownloadedFileType::GzipTar => {
|
||||
let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
|
||||
let archive = Archive::new(decompressed_bytes);
|
||||
archive.unpack(&version_path).await?;
|
||||
}
|
||||
DownloadedFileType::Zip | DownloadedFileType::Vsix => {
|
||||
let zip_path = version_path.with_extension("zip");
|
||||
let mut file = File::create(&zip_path).await?;
|
||||
futures::io::copy(response.body_mut(), &mut file).await?;
|
||||
let file = File::open(&zip_path).await?;
|
||||
extract_zip(&version_path, file)
|
||||
.await
|
||||
// we cannot check the status as some adapter include files with names that trigger `Illegal byte sequence`
|
||||
.inspect_err(|e| log::warn!("ZIP extraction error: {}. Ignoring...", e))
|
||||
.ok();
|
||||
|
||||
util::fs::remove_matching(&adapter_path, |entry| {
|
||||
entry
|
||||
.file_name()
|
||||
.is_some_and(|file| file.to_string_lossy().ends_with(".zip"))
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
// remove older versions
|
||||
util::fs::remove_matching(&adapter_path, |entry| {
|
||||
entry.to_string_lossy() != version_path.to_string_lossy()
|
||||
})
|
||||
.await;
|
||||
|
||||
Ok(version_path)
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
pub trait DebugAdapter: 'static + Send + Sync {
|
||||
fn name(&self) -> DebugAdapterName;
|
||||
|
||||
async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario>;
|
||||
|
||||
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>;
|
||||
|
||||
/// Returns the language name of an adapter if it only supports one language
|
||||
fn adapter_language_name(&self) -> Option<LanguageName> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Extracts the kind (attach/launch) of debug configuration from the given JSON config.
|
||||
/// This method should only return error when the kind cannot be determined for a given configuration;
|
||||
/// in particular, it *should not* validate whether the request as a whole is valid, because that's best left to the debug adapter itself to decide.
|
||||
async fn request_kind(
|
||||
&self,
|
||||
config: &serde_json::Value,
|
||||
) -> Result<StartDebuggingRequestArgumentsRequest> {
|
||||
match config.get("request") {
|
||||
Some(val) if val == "launch" => Ok(StartDebuggingRequestArgumentsRequest::Launch),
|
||||
Some(val) if val == "attach" => Ok(StartDebuggingRequestArgumentsRequest::Attach),
|
||||
_ => Err(anyhow!(
|
||||
"missing or invalid `request` field in config. Expected 'launch' or 'attach'"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn dap_schema(&self) -> serde_json::Value;
|
||||
|
||||
fn label_for_child_session(&self, _args: &StartDebuggingRequestArguments) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn compact_child_session(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn prefer_thread_name(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub struct FakeAdapter {}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl FakeAdapter {
|
||||
pub const ADAPTER_NAME: &'static str = "fake-adapter";
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[async_trait(?Send)]
|
||||
impl DebugAdapter for FakeAdapter {
|
||||
fn name(&self) -> DebugAdapterName {
|
||||
DebugAdapterName(Self::ADAPTER_NAME.into())
|
||||
}
|
||||
|
||||
fn dap_schema(&self) -> serde_json::Value {
|
||||
serde_json::Value::Null
|
||||
}
|
||||
|
||||
async fn request_kind(
|
||||
&self,
|
||||
config: &serde_json::Value,
|
||||
) -> Result<StartDebuggingRequestArgumentsRequest> {
|
||||
let request = config.as_object().unwrap()["request"].as_str().unwrap();
|
||||
|
||||
let request = match request {
|
||||
"launch" => dap_types::StartDebuggingRequestArgumentsRequest::Launch,
|
||||
"attach" => dap_types::StartDebuggingRequestArgumentsRequest::Attach,
|
||||
_ => unreachable!("Wrong fake adapter input for request field"),
|
||||
};
|
||||
|
||||
Ok(request)
|
||||
}
|
||||
|
||||
fn adapter_language_name(&self) -> Option<LanguageName> {
|
||||
None
|
||||
}
|
||||
|
||||
async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
|
||||
let config = serde_json::to_value(zed_scenario.request).unwrap();
|
||||
|
||||
Ok(DebugScenario {
|
||||
adapter: zed_scenario.adapter,
|
||||
label: zed_scenario.label,
|
||||
build: None,
|
||||
config,
|
||||
tcp_connection: None,
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_binary(
|
||||
&self,
|
||||
_: &Arc<dyn DapDelegate>,
|
||||
task_definition: &DebugTaskDefinition,
|
||||
_: Option<PathBuf>,
|
||||
_: Option<Vec<String>>,
|
||||
_: Option<HashMap<String, String>>,
|
||||
_: &mut AsyncApp,
|
||||
) -> Result<DebugAdapterBinary> {
|
||||
let connection = task_definition
|
||||
.tcp_connection
|
||||
.as_ref()
|
||||
.map(|connection| TcpArguments {
|
||||
host: connection.host(),
|
||||
port: connection.port.unwrap_or(17),
|
||||
timeout: connection.timeout,
|
||||
});
|
||||
Ok(DebugAdapterBinary {
|
||||
command: Some("command".into()),
|
||||
arguments: vec![],
|
||||
connection,
|
||||
envs: HashMap::default(),
|
||||
cwd: None,
|
||||
request_args: StartDebuggingRequestArguments {
|
||||
request: self.request_kind(&task_definition.config).await?,
|
||||
configuration: task_definition.config.clone(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
465
crates/dap/src/client.rs
Normal file
465
crates/dap/src/client.rs
Normal file
@@ -0,0 +1,465 @@
|
||||
use crate::{
|
||||
adapters::DebugAdapterBinary,
|
||||
transport::{IoKind, LogKind, TransportDelegate},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use dap_types::{
|
||||
messages::{Message, Response},
|
||||
requests::Request,
|
||||
};
|
||||
use futures::channel::oneshot;
|
||||
use gpui::AsyncApp;
|
||||
use std::{
|
||||
hash::Hash,
|
||||
sync::atomic::{AtomicU64, Ordering},
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct SessionId(pub u32);
|
||||
|
||||
impl SessionId {
|
||||
pub fn from_proto(client_id: u64) -> Self {
|
||||
Self(client_id as u32)
|
||||
}
|
||||
|
||||
pub fn to_proto(self) -> u64 {
|
||||
self.0 as u64
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a connection to the debug adapter process, either via stdout/stdin or a socket.
|
||||
pub struct DebugAdapterClient {
|
||||
id: SessionId,
|
||||
sequence_count: AtomicU64,
|
||||
binary: DebugAdapterBinary,
|
||||
transport_delegate: TransportDelegate,
|
||||
}
|
||||
|
||||
pub type DapMessageHandler = Box<dyn FnMut(Message) + 'static + Send + Sync>;
|
||||
|
||||
impl DebugAdapterClient {
|
||||
pub async fn start(
|
||||
id: SessionId,
|
||||
binary: DebugAdapterBinary,
|
||||
message_handler: DapMessageHandler,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Self> {
|
||||
let transport_delegate = TransportDelegate::start(&binary, cx).await?;
|
||||
let this = Self {
|
||||
id,
|
||||
binary,
|
||||
transport_delegate,
|
||||
sequence_count: AtomicU64::new(1),
|
||||
};
|
||||
this.connect(message_handler, cx).await?;
|
||||
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
pub fn should_reconnect_for_ssh(&self) -> bool {
|
||||
self.transport_delegate.tcp_arguments().is_some()
|
||||
&& (self.binary.command.as_deref() == Some("ssh")
|
||||
|| (cfg!(feature = "test-support")
|
||||
&& self.binary.command.as_deref() == Some("mock")))
|
||||
}
|
||||
|
||||
pub async fn connect(
|
||||
&self,
|
||||
message_handler: DapMessageHandler,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<()> {
|
||||
self.transport_delegate.connect(message_handler, cx).await
|
||||
}
|
||||
|
||||
pub async fn create_child_connection(
|
||||
&self,
|
||||
session_id: SessionId,
|
||||
binary: DebugAdapterBinary,
|
||||
message_handler: DapMessageHandler,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Self> {
|
||||
let binary = if let Some(connection) = self.transport_delegate.tcp_arguments() {
|
||||
DebugAdapterBinary {
|
||||
command: None,
|
||||
arguments: Default::default(),
|
||||
envs: Default::default(),
|
||||
cwd: Default::default(),
|
||||
connection: Some(connection),
|
||||
request_args: binary.request_args,
|
||||
}
|
||||
} else {
|
||||
self.binary.clone()
|
||||
};
|
||||
|
||||
Self::start(session_id, binary, message_handler, cx).await
|
||||
}
|
||||
|
||||
/// Send a request to an adapter and get a response back
|
||||
/// Note: This function will block until a response is sent back from the adapter
|
||||
pub async fn request<R: Request>(&self, arguments: R::Arguments) -> Result<R::Response> {
|
||||
let serialized_arguments = serde_json::to_value(arguments)?;
|
||||
|
||||
let (callback_tx, callback_rx) = oneshot::channel::<Result<Response>>();
|
||||
|
||||
let sequence_id = self.next_sequence_id();
|
||||
|
||||
let request = crate::messages::Request {
|
||||
seq: sequence_id,
|
||||
command: R::COMMAND.to_string(),
|
||||
arguments: Some(serialized_arguments),
|
||||
};
|
||||
self.transport_delegate
|
||||
.pending_requests
|
||||
.lock()
|
||||
.insert(sequence_id, callback_tx)?;
|
||||
|
||||
log::debug!(
|
||||
"Client {} send `{}` request with sequence_id: {}",
|
||||
self.id.0,
|
||||
R::COMMAND,
|
||||
sequence_id
|
||||
);
|
||||
log::debug!(" request: {request:?}");
|
||||
|
||||
self.send_message(Message::Request(request)).await?;
|
||||
|
||||
let command = R::COMMAND.to_string();
|
||||
|
||||
let response = callback_rx.await??;
|
||||
log::debug!(
|
||||
"Client {} received response for: `{}` sequence_id: {}",
|
||||
self.id.0,
|
||||
command,
|
||||
sequence_id
|
||||
);
|
||||
log::debug!(" response: {response:?}");
|
||||
|
||||
match response.success {
|
||||
true => {
|
||||
if let Some(json) = response.body {
|
||||
Ok(serde_json::from_value(json)?)
|
||||
// Note: dap types configure themselves to return `None` when an empty object is received,
|
||||
// which then fails here...
|
||||
} else if let Ok(result) =
|
||||
serde_json::from_value(serde_json::Value::Object(Default::default()))
|
||||
{
|
||||
Ok(result)
|
||||
} else {
|
||||
Ok(serde_json::from_value(Default::default())?)
|
||||
}
|
||||
}
|
||||
false => anyhow::bail!("Request failed: {}", response.message.unwrap_or_default()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_message(&self, message: Message) -> Result<()> {
|
||||
self.transport_delegate.send_message(message).await
|
||||
}
|
||||
|
||||
pub fn id(&self) -> SessionId {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn binary(&self) -> &DebugAdapterBinary {
|
||||
&self.binary
|
||||
}
|
||||
|
||||
/// Get the next sequence id to be used in a request
|
||||
pub fn next_sequence_id(&self) -> u64 {
|
||||
self.sequence_count.fetch_add(1, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn kill(&self) {
|
||||
log::debug!("Killing DAP process");
|
||||
self.transport_delegate.transport.lock().kill();
|
||||
self.transport_delegate.pending_requests.lock().shutdown();
|
||||
}
|
||||
|
||||
pub fn has_adapter_logs(&self) -> bool {
|
||||
self.transport_delegate.has_adapter_logs()
|
||||
}
|
||||
|
||||
pub fn add_log_handler<F>(&self, f: F, kind: LogKind)
|
||||
where
|
||||
F: 'static + Send + FnMut(IoKind, Option<&str>, &str),
|
||||
{
|
||||
self.transport_delegate.add_log_handler(f, kind);
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn on_request<R: dap_types::requests::Request, F>(&self, mut handler: F)
|
||||
where
|
||||
F: 'static
|
||||
+ Send
|
||||
+ FnMut(u64, R::Arguments) -> Result<R::Response, dap_types::ErrorResponse>,
|
||||
{
|
||||
use crate::transport::RequestHandling;
|
||||
|
||||
self.transport_delegate
|
||||
.transport
|
||||
.lock()
|
||||
.as_fake()
|
||||
.on_request::<R, _>(move |seq, request| {
|
||||
RequestHandling::Respond(handler(seq, request))
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn on_request_ext<R: dap_types::requests::Request, F>(&self, handler: F)
|
||||
where
|
||||
F: 'static
|
||||
+ Send
|
||||
+ FnMut(
|
||||
u64,
|
||||
R::Arguments,
|
||||
) -> crate::transport::RequestHandling<
|
||||
Result<R::Response, dap_types::ErrorResponse>,
|
||||
>,
|
||||
{
|
||||
self.transport_delegate
|
||||
.transport
|
||||
.lock()
|
||||
.as_fake()
|
||||
.on_request::<R, F>(handler);
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub async fn fake_reverse_request<R: dap_types::requests::Request>(&self, args: R::Arguments) {
|
||||
self.send_message(Message::Request(dap_types::messages::Request {
|
||||
seq: self.sequence_count.load(Ordering::Relaxed),
|
||||
command: R::COMMAND.into(),
|
||||
arguments: serde_json::to_value(args).ok(),
|
||||
}))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub async fn on_response<R: dap_types::requests::Request, F>(&self, handler: F)
|
||||
where
|
||||
F: 'static + Send + Fn(Response),
|
||||
{
|
||||
self.transport_delegate
|
||||
.transport
|
||||
.lock()
|
||||
.as_fake()
|
||||
.on_response::<R, F>(handler);
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub async fn fake_event(&self, event: dap_types::messages::Events) {
|
||||
self.send_message(Message::Event(Box::new(event)))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::client::DebugAdapterClient;
|
||||
use dap_types::{
|
||||
Capabilities, InitializeRequestArguments, InitializeRequestArgumentsPathFormat,
|
||||
RunInTerminalRequestArguments, StartDebuggingRequestArguments,
|
||||
messages::Events,
|
||||
requests::{Initialize, Request, RunInTerminal},
|
||||
};
|
||||
use gpui::TestAppContext;
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
|
||||
pub fn init_test(cx: &mut gpui::TestAppContext) {
|
||||
zlog::init_test();
|
||||
|
||||
cx.update(|cx| {
|
||||
let settings = SettingsStore::test(cx);
|
||||
cx.set_global(settings);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
pub async fn test_initialize_client(cx: &mut TestAppContext) {
|
||||
#![expect(clippy::result_large_err)]
|
||||
init_test(cx);
|
||||
|
||||
let client = DebugAdapterClient::start(
|
||||
crate::client::SessionId(1),
|
||||
DebugAdapterBinary {
|
||||
command: Some("command".into()),
|
||||
arguments: Default::default(),
|
||||
envs: Default::default(),
|
||||
connection: None,
|
||||
cwd: None,
|
||||
request_args: StartDebuggingRequestArguments {
|
||||
configuration: serde_json::Value::Null,
|
||||
request: dap_types::StartDebuggingRequestArgumentsRequest::Launch,
|
||||
},
|
||||
},
|
||||
Box::new(|_| {}),
|
||||
&mut cx.to_async(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
client.on_request::<Initialize, _>(move |_, _| {
|
||||
Ok(dap_types::Capabilities {
|
||||
supports_configuration_done_request: Some(true),
|
||||
..Default::default()
|
||||
})
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
let response = client
|
||||
.request::<Initialize>(InitializeRequestArguments {
|
||||
client_id: Some("zed".to_owned()),
|
||||
client_name: Some("Zed".to_owned()),
|
||||
adapter_id: "fake-adapter".to_owned(),
|
||||
locale: Some("en-US".to_owned()),
|
||||
path_format: Some(InitializeRequestArgumentsPathFormat::Path),
|
||||
supports_variable_type: Some(true),
|
||||
supports_variable_paging: Some(false),
|
||||
supports_run_in_terminal_request: Some(true),
|
||||
supports_memory_references: Some(true),
|
||||
supports_progress_reporting: Some(false),
|
||||
supports_invalidated_event: Some(false),
|
||||
lines_start_at1: Some(true),
|
||||
columns_start_at1: Some(true),
|
||||
supports_memory_event: Some(false),
|
||||
supports_args_can_be_interpreted_by_shell: Some(false),
|
||||
supports_start_debugging_request: Some(true),
|
||||
supports_ansistyling: Some(false),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
assert_eq!(
|
||||
dap_types::Capabilities {
|
||||
supports_configuration_done_request: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
response
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
pub async fn test_calls_event_handler(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let called_event_handler = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let client = DebugAdapterClient::start(
|
||||
crate::client::SessionId(1),
|
||||
DebugAdapterBinary {
|
||||
command: Some("command".into()),
|
||||
arguments: Default::default(),
|
||||
envs: Default::default(),
|
||||
connection: None,
|
||||
cwd: None,
|
||||
request_args: StartDebuggingRequestArguments {
|
||||
configuration: serde_json::Value::Null,
|
||||
request: dap_types::StartDebuggingRequestArgumentsRequest::Launch,
|
||||
},
|
||||
},
|
||||
Box::new({
|
||||
let called_event_handler = called_event_handler.clone();
|
||||
move |event| {
|
||||
called_event_handler.store(true, Ordering::SeqCst);
|
||||
|
||||
assert_eq!(
|
||||
Message::Event(Box::new(Events::Initialized(
|
||||
Some(Capabilities::default())
|
||||
))),
|
||||
event
|
||||
);
|
||||
}
|
||||
}),
|
||||
&mut cx.to_async(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
client
|
||||
.fake_event(Events::Initialized(Some(Capabilities::default())))
|
||||
.await;
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
assert!(
|
||||
called_event_handler.load(std::sync::atomic::Ordering::SeqCst),
|
||||
"Event handler was not called"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
pub async fn test_calls_event_handler_for_reverse_request(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let called_event_handler = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let client = DebugAdapterClient::start(
|
||||
crate::client::SessionId(1),
|
||||
DebugAdapterBinary {
|
||||
command: Some("command".into()),
|
||||
arguments: Default::default(),
|
||||
envs: Default::default(),
|
||||
connection: None,
|
||||
cwd: None,
|
||||
request_args: dap_types::StartDebuggingRequestArguments {
|
||||
configuration: serde_json::Value::Null,
|
||||
request: dap_types::StartDebuggingRequestArgumentsRequest::Launch,
|
||||
},
|
||||
},
|
||||
Box::new({
|
||||
let called_event_handler = called_event_handler.clone();
|
||||
move |event| {
|
||||
called_event_handler.store(true, Ordering::SeqCst);
|
||||
|
||||
assert_eq!(
|
||||
Message::Request(dap_types::messages::Request {
|
||||
seq: 1,
|
||||
command: RunInTerminal::COMMAND.into(),
|
||||
arguments: Some(json!({
|
||||
"cwd": "/project/path/src",
|
||||
"args": ["node", "test.js"],
|
||||
}))
|
||||
}),
|
||||
event
|
||||
);
|
||||
}
|
||||
}),
|
||||
&mut cx.to_async(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
client
|
||||
.fake_reverse_request::<RunInTerminal>(RunInTerminalRequestArguments {
|
||||
kind: None,
|
||||
title: None,
|
||||
cwd: "/project/path/src".into(),
|
||||
args: vec!["node".into(), "test.js".into()],
|
||||
env: None,
|
||||
args_can_be_interpreted_by_shell: None,
|
||||
})
|
||||
.await;
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
assert!(
|
||||
called_event_handler.load(std::sync::atomic::Ordering::SeqCst),
|
||||
"Event handler was not called"
|
||||
);
|
||||
}
|
||||
}
|
||||
76
crates/dap/src/dap.rs
Normal file
76
crates/dap/src/dap.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
pub mod adapters;
|
||||
pub mod client;
|
||||
pub mod debugger_settings;
|
||||
pub mod inline_value;
|
||||
pub mod proto_conversions;
|
||||
mod registry;
|
||||
pub mod transport;
|
||||
|
||||
use std::net::IpAddr;
|
||||
|
||||
pub use dap_types::*;
|
||||
use debugger_settings::DebuggerSettings;
|
||||
use gpui::App;
|
||||
pub use registry::{DapLocator, DapRegistry};
|
||||
use serde::Serialize;
|
||||
use settings::Settings;
|
||||
pub use task::DebugRequest;
|
||||
|
||||
pub type ScopeId = u64;
|
||||
pub type VariableReference = u64;
|
||||
pub type StackFrameId = u64;
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub use adapters::FakeAdapter;
|
||||
use task::{DebugScenario, TcpArgumentsTemplate};
|
||||
|
||||
pub async fn configure_tcp_connection(
|
||||
tcp_connection: TcpArgumentsTemplate,
|
||||
) -> anyhow::Result<(IpAddr, u16, Option<u64>)> {
|
||||
let host = tcp_connection.host();
|
||||
let timeout = tcp_connection.timeout;
|
||||
|
||||
let port = if let Some(port) = tcp_connection.port {
|
||||
port
|
||||
} else {
|
||||
transport::TcpTransport::port(&tcp_connection).await?
|
||||
};
|
||||
|
||||
Ok((host, port, timeout))
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TelemetrySpawnLocation {
|
||||
Gutter,
|
||||
ScenarioList,
|
||||
Custom,
|
||||
}
|
||||
|
||||
pub fn send_telemetry(scenario: &DebugScenario, location: TelemetrySpawnLocation, cx: &App) {
|
||||
let Some(adapter) = cx.global::<DapRegistry>().adapter(&scenario.adapter) else {
|
||||
return;
|
||||
};
|
||||
let dock = DebuggerSettings::get_global(cx).dock;
|
||||
let config = scenario.config.clone();
|
||||
let with_build_task = scenario.build.is_some();
|
||||
let adapter_name = scenario.adapter.clone();
|
||||
cx.spawn(async move |_| {
|
||||
let kind = adapter
|
||||
.request_kind(&config)
|
||||
.await
|
||||
.ok()
|
||||
.map(serde_json::to_value)
|
||||
.and_then(Result::ok);
|
||||
|
||||
telemetry::event!(
|
||||
"Debugger Session Started",
|
||||
spawn_location = location,
|
||||
with_build_task = with_build_task,
|
||||
kind = kind,
|
||||
adapter = adapter_name,
|
||||
dock_position = dock,
|
||||
);
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
61
crates/dap/src/debugger_settings.rs
Normal file
61
crates/dap/src/debugger_settings.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use dap_types::SteppingGranularity;
|
||||
use settings::{RegisterSetting, Settings, SettingsContent};
|
||||
|
||||
#[derive(Debug, RegisterSetting)]
|
||||
pub struct DebuggerSettings {
|
||||
/// Determines the stepping granularity.
|
||||
///
|
||||
/// Default: line
|
||||
pub stepping_granularity: SteppingGranularity,
|
||||
/// Whether the breakpoints should be reused across Zed sessions.
|
||||
///
|
||||
/// Default: true
|
||||
pub save_breakpoints: bool,
|
||||
/// Whether to show the debug button in the status bar.
|
||||
///
|
||||
/// Default: true
|
||||
pub button: bool,
|
||||
/// Time in milliseconds until timeout error when connecting to a TCP debug adapter
|
||||
///
|
||||
/// Default: 2000ms
|
||||
pub timeout: u64,
|
||||
/// Whether to log messages between active debug adapters and Zed
|
||||
///
|
||||
/// Default: true
|
||||
pub log_dap_communications: bool,
|
||||
/// Whether to format dap messages in when adding them to debug adapter logger
|
||||
///
|
||||
/// Default: true
|
||||
pub format_dap_log_messages: bool,
|
||||
/// The dock position of the debug panel
|
||||
///
|
||||
/// Default: Bottom
|
||||
pub dock: settings::DockPosition,
|
||||
}
|
||||
|
||||
impl Settings for DebuggerSettings {
|
||||
fn from_settings(content: &SettingsContent) -> Self {
|
||||
let content = content.debugger.clone().unwrap();
|
||||
Self {
|
||||
stepping_granularity: dap_granularity_from_settings(
|
||||
content.stepping_granularity.unwrap(),
|
||||
),
|
||||
save_breakpoints: content.save_breakpoints.unwrap(),
|
||||
button: content.button.unwrap(),
|
||||
timeout: content.timeout.unwrap(),
|
||||
log_dap_communications: content.log_dap_communications.unwrap(),
|
||||
format_dap_log_messages: content.format_dap_log_messages.unwrap(),
|
||||
dock: content.dock.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dap_granularity_from_settings(
|
||||
granularity: settings::SteppingGranularity,
|
||||
) -> dap_types::SteppingGranularity {
|
||||
match granularity {
|
||||
settings::SteppingGranularity::Instruction => dap_types::SteppingGranularity::Instruction,
|
||||
settings::SteppingGranularity::Line => dap_types::SteppingGranularity::Line,
|
||||
settings::SteppingGranularity::Statement => dap_types::SteppingGranularity::Statement,
|
||||
}
|
||||
}
|
||||
20
crates/dap/src/inline_value.rs
Normal file
20
crates/dap/src/inline_value.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum VariableLookupKind {
|
||||
Variable,
|
||||
Expression,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum VariableScope {
|
||||
Local,
|
||||
Global,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InlineValueLocation {
|
||||
pub variable_name: String,
|
||||
pub scope: VariableScope,
|
||||
pub lookup: VariableLookupKind,
|
||||
pub row: usize,
|
||||
pub column: usize,
|
||||
}
|
||||
578
crates/dap/src/proto_conversions.rs
Normal file
578
crates/dap/src/proto_conversions.rs
Normal file
@@ -0,0 +1,578 @@
|
||||
use anyhow::{Context as _, Result};
|
||||
use client::proto::{
|
||||
self, DapChecksum, DapChecksumAlgorithm, DapEvaluateContext, DapModule, DapScope,
|
||||
DapScopePresentationHint, DapSource, DapSourcePresentationHint, DapStackFrame, DapVariable,
|
||||
};
|
||||
use dap_types::{OutputEventCategory, OutputEventGroup, ScopePresentationHint, Source};
|
||||
|
||||
pub trait ProtoConversion {
|
||||
type ProtoType;
|
||||
type Output;
|
||||
|
||||
fn to_proto(self) -> Self::ProtoType;
|
||||
fn from_proto(payload: Self::ProtoType) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<T> ProtoConversion for Vec<T>
|
||||
where
|
||||
T: ProtoConversion<Output = T>,
|
||||
{
|
||||
type ProtoType = Vec<T::ProtoType>;
|
||||
type Output = Self;
|
||||
|
||||
fn to_proto(self) -> Self::ProtoType {
|
||||
self.into_iter().map(|item| item.to_proto()).collect()
|
||||
}
|
||||
|
||||
fn from_proto(payload: Self::ProtoType) -> Self {
|
||||
payload
|
||||
.into_iter()
|
||||
.map(|item| T::from_proto(item))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtoConversion for dap_types::Scope {
|
||||
type ProtoType = DapScope;
|
||||
type Output = Self;
|
||||
|
||||
fn to_proto(self) -> Self::ProtoType {
|
||||
Self::ProtoType {
|
||||
name: self.name,
|
||||
presentation_hint: self.presentation_hint.map(|hint| hint.to_proto().into()),
|
||||
variables_reference: self.variables_reference,
|
||||
named_variables: self.named_variables,
|
||||
indexed_variables: self.indexed_variables,
|
||||
expensive: self.expensive,
|
||||
source: self.source.map(Source::to_proto),
|
||||
line: self.line,
|
||||
end_line: self.end_line,
|
||||
column: self.column,
|
||||
end_column: self.end_column,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_proto(payload: Self::ProtoType) -> Self {
|
||||
let presentation_hint = payload
|
||||
.presentation_hint
|
||||
.and_then(DapScopePresentationHint::from_i32);
|
||||
Self {
|
||||
name: payload.name,
|
||||
presentation_hint: presentation_hint.map(ScopePresentationHint::from_proto),
|
||||
variables_reference: payload.variables_reference,
|
||||
named_variables: payload.named_variables,
|
||||
indexed_variables: payload.indexed_variables,
|
||||
expensive: payload.expensive,
|
||||
source: payload.source.map(dap_types::Source::from_proto),
|
||||
line: payload.line,
|
||||
end_line: payload.end_line,
|
||||
column: payload.column,
|
||||
end_column: payload.end_column,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtoConversion for dap_types::Variable {
|
||||
type ProtoType = DapVariable;
|
||||
type Output = Self;
|
||||
|
||||
fn to_proto(self) -> Self::ProtoType {
|
||||
Self::ProtoType {
|
||||
name: self.name,
|
||||
value: self.value,
|
||||
r#type: self.type_,
|
||||
evaluate_name: self.evaluate_name,
|
||||
variables_reference: self.variables_reference,
|
||||
named_variables: self.named_variables,
|
||||
indexed_variables: self.indexed_variables,
|
||||
memory_reference: self.memory_reference,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_proto(payload: Self::ProtoType) -> Self {
|
||||
Self {
|
||||
name: payload.name,
|
||||
value: payload.value,
|
||||
type_: payload.r#type,
|
||||
evaluate_name: payload.evaluate_name,
|
||||
presentation_hint: None, // TODO Debugger Collab Add this
|
||||
variables_reference: payload.variables_reference,
|
||||
named_variables: payload.named_variables,
|
||||
indexed_variables: payload.indexed_variables,
|
||||
memory_reference: payload.memory_reference,
|
||||
declaration_location_reference: None, // TODO
|
||||
value_location_reference: None, // TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtoConversion for dap_types::ScopePresentationHint {
|
||||
type ProtoType = DapScopePresentationHint;
|
||||
type Output = Self;
|
||||
|
||||
fn to_proto(self) -> Self::ProtoType {
|
||||
match self {
|
||||
Self::Locals => Self::ProtoType::Locals,
|
||||
Self::Arguments => Self::ProtoType::Arguments,
|
||||
Self::Registers => Self::ProtoType::Registers,
|
||||
Self::ReturnValue => Self::ProtoType::ReturnValue,
|
||||
Self::Unknown => Self::ProtoType::ScopeUnknown,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_proto(payload: Self::ProtoType) -> Self {
|
||||
match payload {
|
||||
Self::ProtoType::Locals => Self::Locals,
|
||||
Self::ProtoType::Arguments => Self::Arguments,
|
||||
Self::ProtoType::Registers => Self::Registers,
|
||||
Self::ProtoType::ReturnValue => Self::ReturnValue,
|
||||
Self::ProtoType::ScopeUnknown => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtoConversion for dap_types::SourcePresentationHint {
|
||||
type ProtoType = DapSourcePresentationHint;
|
||||
type Output = Self;
|
||||
|
||||
fn to_proto(self) -> Self::ProtoType {
|
||||
match self {
|
||||
Self::Normal => Self::ProtoType::SourceNormal,
|
||||
Self::Emphasize => Self::ProtoType::Emphasize,
|
||||
Self::Deemphasize => Self::ProtoType::Deemphasize,
|
||||
Self::Unknown => Self::ProtoType::SourceUnknown,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_proto(payload: Self::ProtoType) -> Self {
|
||||
match payload {
|
||||
Self::ProtoType::SourceNormal => Self::Normal,
|
||||
Self::ProtoType::Emphasize => Self::Emphasize,
|
||||
Self::ProtoType::Deemphasize => Self::Deemphasize,
|
||||
Self::ProtoType::SourceUnknown => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtoConversion for dap_types::Checksum {
|
||||
type ProtoType = DapChecksum;
|
||||
type Output = Self;
|
||||
|
||||
fn to_proto(self) -> Self::ProtoType {
|
||||
DapChecksum {
|
||||
algorithm: self.algorithm.to_proto().into(),
|
||||
checksum: self.checksum,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_proto(payload: Self::ProtoType) -> Self {
|
||||
Self {
|
||||
algorithm: dap_types::ChecksumAlgorithm::from_proto(payload.algorithm()),
|
||||
checksum: payload.checksum,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtoConversion for dap_types::ChecksumAlgorithm {
|
||||
type ProtoType = DapChecksumAlgorithm;
|
||||
type Output = Self;
|
||||
|
||||
fn to_proto(self) -> Self::ProtoType {
|
||||
match self {
|
||||
Self::Md5 => DapChecksumAlgorithm::Md5,
|
||||
Self::Sha1 => DapChecksumAlgorithm::Sha1,
|
||||
Self::Sha256 => DapChecksumAlgorithm::Sha256,
|
||||
Self::Timestamp => DapChecksumAlgorithm::Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_proto(payload: Self::ProtoType) -> Self {
|
||||
match payload {
|
||||
Self::ProtoType::Md5 => Self::Md5,
|
||||
Self::ProtoType::Sha1 => Self::Sha1,
|
||||
Self::ProtoType::Sha256 => Self::Sha256,
|
||||
Self::ProtoType::Timestamp => Self::Timestamp,
|
||||
Self::ProtoType::ChecksumAlgorithmUnspecified => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtoConversion for dap_types::Source {
|
||||
type ProtoType = DapSource;
|
||||
type Output = Self;
|
||||
|
||||
fn to_proto(self) -> Self::ProtoType {
|
||||
Self::ProtoType {
|
||||
name: self.name,
|
||||
path: self.path,
|
||||
source_reference: self.source_reference,
|
||||
presentation_hint: self.presentation_hint.map(|hint| hint.to_proto().into()),
|
||||
origin: self.origin,
|
||||
sources: self.sources.map(|src| src.to_proto()).unwrap_or_default(),
|
||||
adapter_data: Default::default(), // TODO Debugger Collab
|
||||
checksums: self.checksums.map(|c| c.to_proto()).unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_proto(payload: Self::ProtoType) -> Self {
|
||||
Self {
|
||||
name: payload.name,
|
||||
path: payload.path,
|
||||
source_reference: payload.source_reference,
|
||||
presentation_hint: payload
|
||||
.presentation_hint
|
||||
.and_then(DapSourcePresentationHint::from_i32)
|
||||
.map(dap_types::SourcePresentationHint::from_proto),
|
||||
origin: payload.origin,
|
||||
sources: Some(Vec::<dap_types::Source>::from_proto(payload.sources)),
|
||||
checksums: Some(Vec::<dap_types::Checksum>::from_proto(payload.checksums)),
|
||||
adapter_data: None, // TODO Debugger Collab
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtoConversion for dap_types::StackFrame {
|
||||
type ProtoType = DapStackFrame;
|
||||
type Output = Self;
|
||||
|
||||
fn to_proto(self) -> Self::ProtoType {
|
||||
Self::ProtoType {
|
||||
id: self.id,
|
||||
name: self.name.clone(),
|
||||
source: self.source.map(|src| src.to_proto()),
|
||||
line: self.line,
|
||||
column: self.column,
|
||||
end_line: self.end_line,
|
||||
end_column: self.end_column,
|
||||
can_restart: self.can_restart,
|
||||
instruction_pointer_reference: self.instruction_pointer_reference,
|
||||
module_id: None, // TODO Debugger Collab
|
||||
presentation_hint: None, // TODO Debugger Collab
|
||||
}
|
||||
}
|
||||
|
||||
fn from_proto(payload: Self::ProtoType) -> Self {
|
||||
Self {
|
||||
id: payload.id,
|
||||
name: payload.name,
|
||||
source: payload.source.map(dap_types::Source::from_proto),
|
||||
line: payload.line,
|
||||
column: payload.column,
|
||||
end_line: payload.end_line,
|
||||
end_column: payload.end_column,
|
||||
can_restart: payload.can_restart,
|
||||
instruction_pointer_reference: payload.instruction_pointer_reference,
|
||||
module_id: None, // TODO Debugger Collab
|
||||
presentation_hint: None, // TODO Debugger Collab
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtoConversion for dap_types::ModuleId {
|
||||
type ProtoType = proto::dap_module_id::Id;
|
||||
type Output = Self;
|
||||
|
||||
fn to_proto(self) -> Self::ProtoType {
|
||||
match self {
|
||||
Self::Number(num) => Self::ProtoType::Number(num),
|
||||
Self::String(string) => Self::ProtoType::String(string),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_proto(payload: Self::ProtoType) -> Self::Output {
|
||||
match payload {
|
||||
Self::ProtoType::Number(num) => Self::Number(num),
|
||||
Self::ProtoType::String(string) => Self::String(string),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtoConversion for dap_types::Module {
|
||||
type ProtoType = DapModule;
|
||||
type Output = Result<Self>;
|
||||
|
||||
fn to_proto(self) -> Self::ProtoType {
|
||||
DapModule {
|
||||
id: Some(proto::DapModuleId {
|
||||
id: Some(self.id.to_proto()),
|
||||
}),
|
||||
name: self.name,
|
||||
path: self.path,
|
||||
is_optimized: self.is_optimized,
|
||||
is_user_code: self.is_user_code,
|
||||
version: self.version,
|
||||
symbol_status: self.symbol_status,
|
||||
symbol_file_path: self.symbol_file_path,
|
||||
date_time_stamp: self.date_time_stamp,
|
||||
address_range: self.address_range,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_proto(payload: Self::ProtoType) -> Result<Self> {
|
||||
let id = match payload
|
||||
.id
|
||||
.context("All DapModule proto messages must have an id")?
|
||||
.id
|
||||
.context("All DapModuleID proto messages must have an id")?
|
||||
{
|
||||
proto::dap_module_id::Id::String(string) => dap_types::ModuleId::String(string),
|
||||
proto::dap_module_id::Id::Number(num) => dap_types::ModuleId::Number(num),
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
id,
|
||||
name: payload.name,
|
||||
path: payload.path,
|
||||
is_optimized: payload.is_optimized,
|
||||
is_user_code: payload.is_user_code,
|
||||
version: payload.version,
|
||||
symbol_status: payload.symbol_status,
|
||||
symbol_file_path: payload.symbol_file_path,
|
||||
date_time_stamp: payload.date_time_stamp,
|
||||
address_range: payload.address_range,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtoConversion for dap_types::SteppingGranularity {
|
||||
type ProtoType = proto::SteppingGranularity;
|
||||
type Output = Self;
|
||||
|
||||
fn to_proto(self) -> Self::ProtoType {
|
||||
match self {
|
||||
Self::Statement => Self::ProtoType::Statement,
|
||||
Self::Line => Self::ProtoType::Line,
|
||||
Self::Instruction => Self::ProtoType::Instruction,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_proto(payload: Self::ProtoType) -> Self {
|
||||
match payload {
|
||||
Self::ProtoType::Line => Self::Line,
|
||||
Self::ProtoType::Instruction => Self::Instruction,
|
||||
Self::ProtoType::Statement => Self::Statement,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtoConversion for dap_types::OutputEventCategory {
|
||||
type ProtoType = proto::DapOutputCategory;
|
||||
type Output = Self;
|
||||
|
||||
fn to_proto(self) -> Self::ProtoType {
|
||||
match self {
|
||||
Self::Console => Self::ProtoType::ConsoleOutput,
|
||||
Self::Important => Self::ProtoType::Important,
|
||||
Self::Stdout => Self::ProtoType::Stdout,
|
||||
Self::Stderr => Self::ProtoType::Stderr,
|
||||
_ => Self::ProtoType::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_proto(payload: Self::ProtoType) -> Self {
|
||||
match payload {
|
||||
Self::ProtoType::ConsoleOutput => Self::Console,
|
||||
Self::ProtoType::Important => Self::Important,
|
||||
Self::ProtoType::Stdout => Self::Stdout,
|
||||
Self::ProtoType::Stderr => Self::Stderr,
|
||||
Self::ProtoType::Unknown => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtoConversion for dap_types::OutputEvent {
|
||||
type ProtoType = proto::DapOutputEvent;
|
||||
type Output = Self;
|
||||
|
||||
fn to_proto(self) -> Self::ProtoType {
|
||||
Self::ProtoType {
|
||||
category: self.category.map(|category| category.to_proto().into()),
|
||||
output: self.output.clone(),
|
||||
variables_reference: self.variables_reference,
|
||||
source: self.source.map(|source| source.to_proto()),
|
||||
line: self.line.map(|line| line as u32),
|
||||
column: self.column.map(|column| column as u32),
|
||||
group: self.group.map(|group| group.to_proto().into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_proto(payload: Self::ProtoType) -> Self {
|
||||
Self {
|
||||
category: payload
|
||||
.category
|
||||
.and_then(proto::DapOutputCategory::from_i32)
|
||||
.map(OutputEventCategory::from_proto),
|
||||
output: payload.output,
|
||||
variables_reference: payload.variables_reference,
|
||||
source: payload.source.map(Source::from_proto),
|
||||
line: payload.line.map(|line| line as u64),
|
||||
column: payload.column.map(|column| column as u64),
|
||||
group: payload
|
||||
.group
|
||||
.and_then(proto::DapOutputEventGroup::from_i32)
|
||||
.map(OutputEventGroup::from_proto),
|
||||
data: None,
|
||||
location_reference: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtoConversion for dap_types::OutputEventGroup {
|
||||
type ProtoType = proto::DapOutputEventGroup;
|
||||
type Output = Self;
|
||||
|
||||
fn to_proto(self) -> Self::ProtoType {
|
||||
match self {
|
||||
Self::Start => Self::ProtoType::Start,
|
||||
Self::StartCollapsed => Self::ProtoType::StartCollapsed,
|
||||
Self::End => Self::ProtoType::End,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_proto(payload: Self::ProtoType) -> Self {
|
||||
match payload {
|
||||
Self::ProtoType::Start => Self::Start,
|
||||
Self::ProtoType::StartCollapsed => Self::StartCollapsed,
|
||||
Self::ProtoType::End => Self::End,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtoConversion for dap_types::CompletionItem {
|
||||
type ProtoType = proto::DapCompletionItem;
|
||||
type Output = Self;
|
||||
|
||||
fn to_proto(self) -> Self::ProtoType {
|
||||
Self::ProtoType {
|
||||
label: self.label.clone(),
|
||||
text: self.text.clone(),
|
||||
detail: self.detail.clone(),
|
||||
typ: self
|
||||
.type_
|
||||
.map(ProtoConversion::to_proto)
|
||||
.map(|typ| typ.into()),
|
||||
start: self.start,
|
||||
length: self.length,
|
||||
selection_start: self.selection_start,
|
||||
selection_length: self.selection_length,
|
||||
sort_text: self.sort_text,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_proto(payload: Self::ProtoType) -> Self {
|
||||
let typ = payload.typ(); // todo(debugger): This might be a potential issue/bug because it defaults to a type when it's None
|
||||
|
||||
Self {
|
||||
label: payload.label,
|
||||
detail: payload.detail,
|
||||
sort_text: payload.sort_text,
|
||||
text: payload.text.clone(),
|
||||
type_: Some(dap_types::CompletionItemType::from_proto(typ)),
|
||||
start: payload.start,
|
||||
length: payload.length,
|
||||
selection_start: payload.selection_start,
|
||||
selection_length: payload.selection_length,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtoConversion for dap_types::EvaluateArgumentsContext {
|
||||
type ProtoType = DapEvaluateContext;
|
||||
type Output = Self;
|
||||
|
||||
fn to_proto(self) -> Self::ProtoType {
|
||||
match self {
|
||||
Self::Variables => Self::ProtoType::EvaluateVariables,
|
||||
Self::Watch => Self::ProtoType::Watch,
|
||||
Self::Hover => Self::ProtoType::Hover,
|
||||
Self::Repl => Self::ProtoType::Repl,
|
||||
Self::Clipboard => Self::ProtoType::Clipboard,
|
||||
Self::Unknown => Self::ProtoType::EvaluateUnknown,
|
||||
_ => Self::ProtoType::EvaluateUnknown,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_proto(payload: Self::ProtoType) -> Self {
|
||||
match payload {
|
||||
Self::ProtoType::EvaluateVariables => Self::Variables,
|
||||
Self::ProtoType::Watch => Self::Watch,
|
||||
Self::ProtoType::Hover => Self::Hover,
|
||||
Self::ProtoType::Repl => Self::Repl,
|
||||
Self::ProtoType::Clipboard => Self::Clipboard,
|
||||
Self::ProtoType::EvaluateUnknown => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtoConversion for dap_types::CompletionItemType {
|
||||
type ProtoType = proto::DapCompletionItemType;
|
||||
type Output = Self;
|
||||
|
||||
fn to_proto(self) -> Self::ProtoType {
|
||||
match self {
|
||||
Self::Class => Self::ProtoType::Class,
|
||||
Self::Color => Self::ProtoType::Color,
|
||||
Self::Constructor => Self::ProtoType::Constructor,
|
||||
Self::Customcolor => Self::ProtoType::Customcolor,
|
||||
Self::Enum => Self::ProtoType::Enum,
|
||||
Self::Field => Self::ProtoType::Field,
|
||||
Self::File => Self::ProtoType::CompletionItemFile,
|
||||
Self::Function => Self::ProtoType::Function,
|
||||
Self::Interface => Self::ProtoType::Interface,
|
||||
Self::Keyword => Self::ProtoType::Keyword,
|
||||
Self::Method => Self::ProtoType::Method,
|
||||
Self::Module => Self::ProtoType::Module,
|
||||
Self::Property => Self::ProtoType::Property,
|
||||
Self::Reference => Self::ProtoType::Reference,
|
||||
Self::Snippet => Self::ProtoType::Snippet,
|
||||
Self::Text => Self::ProtoType::Text,
|
||||
Self::Unit => Self::ProtoType::Unit,
|
||||
Self::Value => Self::ProtoType::Value,
|
||||
Self::Variable => Self::ProtoType::Variable,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_proto(payload: Self::ProtoType) -> Self {
|
||||
match payload {
|
||||
Self::ProtoType::Class => Self::Class,
|
||||
Self::ProtoType::Color => Self::Color,
|
||||
Self::ProtoType::CompletionItemFile => Self::File,
|
||||
Self::ProtoType::Constructor => Self::Constructor,
|
||||
Self::ProtoType::Customcolor => Self::Customcolor,
|
||||
Self::ProtoType::Enum => Self::Enum,
|
||||
Self::ProtoType::Field => Self::Field,
|
||||
Self::ProtoType::Function => Self::Function,
|
||||
Self::ProtoType::Interface => Self::Interface,
|
||||
Self::ProtoType::Keyword => Self::Keyword,
|
||||
Self::ProtoType::Method => Self::Method,
|
||||
Self::ProtoType::Module => Self::Module,
|
||||
Self::ProtoType::Property => Self::Property,
|
||||
Self::ProtoType::Reference => Self::Reference,
|
||||
Self::ProtoType::Snippet => Self::Snippet,
|
||||
Self::ProtoType::Text => Self::Text,
|
||||
Self::ProtoType::Unit => Self::Unit,
|
||||
Self::ProtoType::Value => Self::Value,
|
||||
Self::ProtoType::Variable => Self::Variable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtoConversion for dap_types::Thread {
|
||||
type ProtoType = proto::DapThread;
|
||||
type Output = Self;
|
||||
|
||||
fn to_proto(self) -> Self::ProtoType {
|
||||
proto::DapThread {
|
||||
id: self.id,
|
||||
name: self.name,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_proto(payload: Self::ProtoType) -> Self {
|
||||
Self {
|
||||
id: payload.id,
|
||||
name: payload.name,
|
||||
}
|
||||
}
|
||||
}
|
||||
97
crates/dap/src/registry.rs
Normal file
97
crates/dap/src/registry.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use collections::FxHashMap;
|
||||
use gpui::{App, BackgroundExecutor, Global, SharedString};
|
||||
use language::LanguageName;
|
||||
use parking_lot::RwLock;
|
||||
use task::{
|
||||
AdapterSchema, AdapterSchemas, DebugRequest, DebugScenario, SpawnInTerminal, TaskTemplate,
|
||||
};
|
||||
|
||||
use crate::adapters::{DebugAdapter, DebugAdapterName};
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
|
||||
/// Given a user build configuration, locator creates a fill-in debug target ([DebugScenario]) on behalf of the user.
|
||||
#[async_trait]
|
||||
pub trait DapLocator: Send + Sync {
|
||||
fn name(&self) -> SharedString;
|
||||
/// Determines whether this locator can generate debug target for given task.
|
||||
async fn create_scenario(
|
||||
&self,
|
||||
build_config: &TaskTemplate,
|
||||
resolved_label: &str,
|
||||
adapter: &DebugAdapterName,
|
||||
) -> Option<DebugScenario>;
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
build_config: SpawnInTerminal,
|
||||
executor: BackgroundExecutor,
|
||||
) -> Result<DebugRequest>;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct DapRegistryState {
|
||||
adapters: BTreeMap<DebugAdapterName, Arc<dyn DebugAdapter>>,
|
||||
locators: FxHashMap<SharedString, Arc<dyn DapLocator>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
/// Stores available debug adapters.
|
||||
pub struct DapRegistry(Arc<RwLock<DapRegistryState>>);
|
||||
impl Global for DapRegistry {}
|
||||
|
||||
impl DapRegistry {
|
||||
pub fn global(cx: &mut App) -> &mut Self {
|
||||
cx.default_global::<Self>()
|
||||
}
|
||||
|
||||
pub fn add_adapter(&self, adapter: Arc<dyn DebugAdapter>) {
|
||||
let name = adapter.name();
|
||||
let _previous_value = self.0.write().adapters.insert(name, adapter);
|
||||
}
|
||||
|
||||
pub fn add_locator(&self, locator: Arc<dyn DapLocator>) {
|
||||
self.0.write().locators.insert(locator.name(), locator);
|
||||
}
|
||||
|
||||
pub fn remove_adapter(&self, name: &str) {
|
||||
self.0.write().adapters.remove(name);
|
||||
}
|
||||
|
||||
pub fn remove_locator(&self, locator: &str) {
|
||||
self.0.write().locators.remove(locator);
|
||||
}
|
||||
|
||||
pub fn adapter_language(&self, adapter_name: &str) -> Option<LanguageName> {
|
||||
self.adapter(adapter_name)
|
||||
.and_then(|adapter| adapter.adapter_language_name())
|
||||
}
|
||||
|
||||
pub fn adapters_schema(&self) -> task::AdapterSchemas {
|
||||
let mut schemas = vec![];
|
||||
|
||||
let adapters = &self.0.read().adapters;
|
||||
|
||||
for (name, adapter) in adapters.into_iter() {
|
||||
schemas.push(AdapterSchema {
|
||||
adapter: name.clone().into(),
|
||||
schema: adapter.dap_schema(),
|
||||
});
|
||||
}
|
||||
|
||||
AdapterSchemas(schemas)
|
||||
}
|
||||
|
||||
pub fn locators(&self) -> FxHashMap<SharedString, Arc<dyn DapLocator>> {
|
||||
self.0.read().locators.clone()
|
||||
}
|
||||
|
||||
pub fn adapter(&self, name: &str) -> Option<Arc<dyn DebugAdapter>> {
|
||||
self.0.read().adapters.get(name).cloned()
|
||||
}
|
||||
|
||||
pub fn enumerate_adapters<B: FromIterator<DebugAdapterName>>(&self) -> B {
|
||||
self.0.read().adapters.keys().cloned().collect()
|
||||
}
|
||||
}
|
||||
1020
crates/dap/src/transport.rs
Normal file
1020
crates/dap/src/transport.rs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user