debugger: Remove fake adapter and un-gate GDB (#27557)
This is a clean-up PR in anticipation of introduction of Debugger Registry. I wanna get rid of DebugAdapterKind (or rather, it being an enum). Release Notes: - N/A --------- Co-authored-by: Anthony Eid <hello@anthonyeid.me> Co-authored-by: Anthony <anthony@zed.dev>
This commit is contained in:
parent
56eb650f09
commit
4839195003
53 changed files with 1315 additions and 924 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -581,6 +581,7 @@ dependencies = [
|
|||
"client",
|
||||
"collections",
|
||||
"context_server",
|
||||
"dap",
|
||||
"env_logger 0.11.7",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
|
@ -3875,6 +3876,7 @@ dependencies = [
|
|||
"node_runtime",
|
||||
"parking_lot",
|
||||
"paths",
|
||||
"regex",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -3908,7 +3910,6 @@ dependencies = [
|
|||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sysinfo",
|
||||
"task",
|
||||
"util",
|
||||
]
|
||||
|
@ -4642,6 +4643,7 @@ dependencies = [
|
|||
"client",
|
||||
"clock",
|
||||
"collections",
|
||||
"dap",
|
||||
"env_logger 0.11.7",
|
||||
"feature_flags",
|
||||
"fs",
|
||||
|
@ -11349,6 +11351,7 @@ dependencies = [
|
|||
"clap",
|
||||
"client",
|
||||
"clock",
|
||||
"dap",
|
||||
"env_logger 0.11.7",
|
||||
"extension",
|
||||
"extension_host",
|
||||
|
@ -16908,6 +16911,7 @@ dependencies = [
|
|||
"clock",
|
||||
"collections",
|
||||
"component",
|
||||
"dap",
|
||||
"db",
|
||||
"derive_more",
|
||||
"env_logger 0.11.7",
|
||||
|
@ -17284,6 +17288,8 @@ dependencies = [
|
|||
"command_palette_hooks",
|
||||
"component_preview",
|
||||
"copilot",
|
||||
"dap",
|
||||
"dap_adapters",
|
||||
"db",
|
||||
"debugger_tools",
|
||||
"debugger_ui",
|
||||
|
|
|
@ -21,6 +21,7 @@ clap.workspace = true
|
|||
client.workspace = true
|
||||
collections.workspace = true
|
||||
context_server.workspace = true
|
||||
dap.workspace = true
|
||||
env_logger.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
|
|
|
@ -3,6 +3,7 @@ use assistant2::{RequestKind, Thread, ThreadEvent, ThreadStore};
|
|||
use assistant_tool::ToolWorkingSet;
|
||||
use client::{Client, UserStore};
|
||||
use collections::HashMap;
|
||||
use dap::DapRegistry;
|
||||
use futures::StreamExt;
|
||||
use gpui::{prelude::*, App, AsyncApp, Entity, SemanticVersion, Subscription, Task};
|
||||
use language::LanguageRegistry;
|
||||
|
@ -50,6 +51,7 @@ impl HeadlessAssistant {
|
|||
app_state.node_runtime.clone(),
|
||||
app_state.user_store.clone(),
|
||||
app_state.languages.clone(),
|
||||
Arc::new(DapRegistry::default()),
|
||||
app_state.fs.clone(),
|
||||
env,
|
||||
cx,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::tests::TestServer;
|
||||
use call::ActiveCall;
|
||||
use collections::{HashMap, HashSet};
|
||||
use dap::DapRegistry;
|
||||
use extension::ExtensionHostProxy;
|
||||
use fs::{FakeFs, Fs as _, RemoveOptions};
|
||||
use futures::StreamExt as _;
|
||||
|
@ -85,6 +86,7 @@ async fn test_sharing_an_ssh_remote_project(
|
|||
http_client: remote_http_client,
|
||||
node_runtime: node,
|
||||
languages,
|
||||
debug_adapters: Arc::new(DapRegistry::fake()),
|
||||
extension_host_proxy: Arc::new(ExtensionHostProxy::new()),
|
||||
},
|
||||
cx,
|
||||
|
@ -252,6 +254,7 @@ async fn test_ssh_collaboration_git_branches(
|
|||
http_client: remote_http_client,
|
||||
node_runtime: node,
|
||||
languages,
|
||||
debug_adapters: Arc::new(DapRegistry::fake()),
|
||||
extension_host_proxy: Arc::new(ExtensionHostProxy::new()),
|
||||
},
|
||||
cx,
|
||||
|
@ -451,6 +454,7 @@ async fn test_ssh_collaboration_formatting_with_prettier(
|
|||
http_client: remote_http_client,
|
||||
node_runtime: NodeRuntime::unavailable(),
|
||||
languages,
|
||||
debug_adapters: Arc::new(DapRegistry::fake()),
|
||||
extension_host_proxy: Arc::new(ExtensionHostProxy::new()),
|
||||
},
|
||||
cx,
|
||||
|
|
|
@ -14,6 +14,7 @@ use client::{
|
|||
use clock::FakeSystemClock;
|
||||
use collab_ui::channel_view::ChannelView;
|
||||
use collections::{HashMap, HashSet};
|
||||
use dap::DapRegistry;
|
||||
use fs::FakeFs;
|
||||
use futures::{channel::oneshot, StreamExt as _};
|
||||
use git::GitHostingProviderRegistry;
|
||||
|
@ -277,12 +278,14 @@ impl TestServer {
|
|||
let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
|
||||
let workspace_store = cx.new(|cx| WorkspaceStore::new(client.clone(), cx));
|
||||
let language_registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
||||
let debug_adapters = Arc::new(DapRegistry::default());
|
||||
let session = cx.new(|cx| AppSession::new(Session::test(), cx));
|
||||
let app_state = Arc::new(workspace::AppState {
|
||||
client: client.clone(),
|
||||
user_store: user_store.clone(),
|
||||
workspace_store,
|
||||
languages: language_registry,
|
||||
debug_adapters,
|
||||
fs: fs.clone(),
|
||||
build_window_options: |_, _| Default::default(),
|
||||
node_runtime: NodeRuntime::unavailable(),
|
||||
|
@ -795,6 +798,7 @@ impl TestClient {
|
|||
self.app_state.node_runtime.clone(),
|
||||
self.app_state.user_store.clone(),
|
||||
self.app_state.languages.clone(),
|
||||
self.app_state.debug_adapters.clone(),
|
||||
self.app_state.fs.clone(),
|
||||
None,
|
||||
cx,
|
||||
|
|
|
@ -8,6 +8,10 @@ license = "GPL-3.0-or-later"
|
|||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/dap.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
test-support = [
|
||||
"gpui/test-support",
|
||||
|
@ -35,6 +39,7 @@ log.workspace = true
|
|||
node_runtime.workspace = true
|
||||
parking_lot.workspace = true
|
||||
paths.workspace = true
|
||||
regex.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
|
|
@ -13,15 +13,16 @@ use serde_json::Value;
|
|||
use settings::WorktreeId;
|
||||
use smol::{self, fs::File, lock::Mutex};
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
collections::{HashMap, HashSet},
|
||||
ffi::{OsStr, OsString},
|
||||
fmt::Debug,
|
||||
net::Ipv4Addr,
|
||||
ops::Deref,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
path::PathBuf,
|
||||
sync::{Arc, LazyLock},
|
||||
};
|
||||
use task::DebugAdapterConfig;
|
||||
use task::{DebugAdapterConfig, DebugTaskDefinition};
|
||||
use util::ResultExt;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
|
@ -46,7 +47,7 @@ pub trait DapDelegate {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
|
||||
pub struct DebugAdapterName(pub Arc<str>);
|
||||
pub struct DebugAdapterName(pub SharedString);
|
||||
|
||||
impl Deref for DebugAdapterName {
|
||||
type Target = str;
|
||||
|
@ -62,9 +63,9 @@ impl AsRef<str> for DebugAdapterName {
|
|||
}
|
||||
}
|
||||
|
||||
impl AsRef<Path> for DebugAdapterName {
|
||||
fn as_ref(&self) -> &Path {
|
||||
Path::new(&*self.0)
|
||||
impl Borrow<str> for DebugAdapterName {
|
||||
fn borrow(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,7 +77,7 @@ impl std::fmt::Display for DebugAdapterName {
|
|||
|
||||
impl From<DebugAdapterName> for SharedString {
|
||||
fn from(name: DebugAdapterName) -> Self {
|
||||
SharedString::from(name.0)
|
||||
name.0
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,7 +124,7 @@ pub async fn download_adapter_from_github(
|
|||
file_type: DownloadedFileType,
|
||||
delegate: &dyn DapDelegate,
|
||||
) -> Result<PathBuf> {
|
||||
let adapter_path = paths::debug_adapters_dir().join(&adapter_name);
|
||||
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();
|
||||
|
||||
|
@ -288,15 +289,21 @@ pub trait DebugAdapter: 'static + Send + Sync {
|
|||
) -> Result<DebugAdapterBinary>;
|
||||
|
||||
/// Should return base configuration to make the debug adapter work
|
||||
fn request_args(&self, config: &DebugAdapterConfig) -> Value;
|
||||
fn request_args(&self, config: &DebugTaskDefinition) -> Value;
|
||||
|
||||
fn attach_processes_filter(&self) -> regex::Regex {
|
||||
EMPTY_REGEX.clone()
|
||||
}
|
||||
}
|
||||
|
||||
static EMPTY_REGEX: LazyLock<regex::Regex> =
|
||||
LazyLock::new(|| regex::Regex::new("").expect("Regex compilation to succeed"));
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub struct FakeAdapter {}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl FakeAdapter {
|
||||
const ADAPTER_NAME: &'static str = "fake-adapter";
|
||||
pub const ADAPTER_NAME: &'static str = "fake-adapter";
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
|
@ -351,13 +358,13 @@ impl DebugAdapter for FakeAdapter {
|
|||
unimplemented!("get installed binary");
|
||||
}
|
||||
|
||||
fn request_args(&self, config: &DebugAdapterConfig) -> Value {
|
||||
fn request_args(&self, config: &DebugTaskDefinition) -> Value {
|
||||
use serde_json::json;
|
||||
use task::DebugRequestType;
|
||||
|
||||
json!({
|
||||
"request": match config.request {
|
||||
DebugRequestType::Launch => "launch",
|
||||
DebugRequestType::Launch(_) => "launch",
|
||||
DebugRequestType::Attach(_) => "attach",
|
||||
},
|
||||
"process_id": if let DebugRequestType::Attach(attach_config) = &config.request {
|
||||
|
@ -367,4 +374,10 @@ impl DebugAdapter for FakeAdapter {
|
|||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn attach_processes_filter(&self) -> regex::Regex {
|
||||
static REGEX: LazyLock<regex::Regex> =
|
||||
LazyLock::new(|| regex::Regex::new("^fake-binary").unwrap());
|
||||
REGEX.clone()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,7 +71,6 @@ impl DebugAdapterClient {
|
|||
let client_id = this.id;
|
||||
|
||||
// start handling events/reverse requests
|
||||
|
||||
cx.background_spawn(Self::handle_receive_messages(
|
||||
client_id,
|
||||
server_rx,
|
||||
|
@ -119,7 +118,6 @@ impl DebugAdapterClient {
|
|||
Ok(message) => message,
|
||||
Err(e) => break Err(e.into()),
|
||||
};
|
||||
|
||||
match message {
|
||||
Message::Event(ev) => {
|
||||
log::debug!("Client {} received event `{}`", client_id.0, &ev);
|
||||
|
@ -164,7 +162,6 @@ impl DebugAdapterClient {
|
|||
command: R::COMMAND.to_string(),
|
||||
arguments: Some(serialized_arguments),
|
||||
};
|
||||
|
||||
self.transport_delegate
|
||||
.add_pending_request(sequence_id, callback_tx)
|
||||
.await;
|
||||
|
@ -434,7 +431,7 @@ mod tests {
|
|||
|
||||
let client = DebugAdapterClient::start(
|
||||
crate::client::SessionId(1),
|
||||
DebugAdapterName(Arc::from("test-adapter")),
|
||||
DebugAdapterName("test-adapter".into()),
|
||||
DebugAdapterBinary {
|
||||
command: "command".into(),
|
||||
arguments: Default::default(),
|
||||
|
|
17
crates/dap/src/dap.rs
Normal file
17
crates/dap/src/dap.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
pub mod adapters;
|
||||
pub mod client;
|
||||
pub mod debugger_settings;
|
||||
pub mod proto_conversions;
|
||||
mod registry;
|
||||
pub mod transport;
|
||||
|
||||
pub use dap_types::*;
|
||||
pub use registry::DapRegistry;
|
||||
pub use task::{DebugAdapterConfig, DebugRequestType};
|
||||
|
||||
pub type ScopeId = u64;
|
||||
pub type VariableReference = u64;
|
||||
pub type StackFrameId = u64;
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub use adapters::FakeAdapter;
|
|
@ -1,38 +0,0 @@
|
|||
pub mod adapters;
|
||||
pub mod client;
|
||||
pub mod debugger_settings;
|
||||
pub mod proto_conversions;
|
||||
pub mod transport;
|
||||
|
||||
pub use dap_types::*;
|
||||
pub use task::{DebugAdapterConfig, DebugAdapterKind, DebugRequestType};
|
||||
|
||||
pub type ScopeId = u64;
|
||||
pub type VariableReference = u64;
|
||||
pub type StackFrameId = u64;
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub use adapters::FakeAdapter;
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn test_config(
|
||||
request: DebugRequestType,
|
||||
fail: Option<bool>,
|
||||
caps: Option<Capabilities>,
|
||||
) -> DebugAdapterConfig {
|
||||
DebugAdapterConfig {
|
||||
label: "test config".into(),
|
||||
kind: DebugAdapterKind::Fake((
|
||||
fail.unwrap_or_default(),
|
||||
caps.unwrap_or(Capabilities {
|
||||
supports_step_back: Some(false),
|
||||
..Default::default()
|
||||
}),
|
||||
)),
|
||||
request,
|
||||
program: None,
|
||||
supports_attach: false,
|
||||
cwd: None,
|
||||
initialize_args: None,
|
||||
}
|
||||
}
|
39
crates/dap/src/registry.rs
Normal file
39
crates/dap/src/registry.rs
Normal file
|
@ -0,0 +1,39 @@
|
|||
use parking_lot::RwLock;
|
||||
|
||||
use crate::adapters::{DebugAdapter, DebugAdapterName};
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
|
||||
#[derive(Default)]
|
||||
struct DapRegistryState {
|
||||
adapters: BTreeMap<DebugAdapterName, Arc<dyn DebugAdapter>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
/// Stores available debug adapters.
|
||||
pub struct DapRegistry(Arc<RwLock<DapRegistryState>>);
|
||||
|
||||
impl DapRegistry {
|
||||
pub fn add_adapter(&self, adapter: Arc<dyn DebugAdapter>) {
|
||||
let name = adapter.name();
|
||||
let _previous_value = self.0.write().adapters.insert(name, adapter);
|
||||
debug_assert!(
|
||||
_previous_value.is_none(),
|
||||
"Attempted to insert a new debug adapter when one is already registered"
|
||||
);
|
||||
}
|
||||
pub fn adapter(&self, name: &str) -> Option<Arc<dyn DebugAdapter>> {
|
||||
self.0.read().adapters.get(name).cloned()
|
||||
}
|
||||
pub fn enumerate_adapters(&self) -> Vec<DebugAdapterName> {
|
||||
self.0.read().adapters.keys().cloned().collect()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn fake() -> Self {
|
||||
use crate::FakeAdapter;
|
||||
|
||||
let register = Self::default();
|
||||
register.add_adapter(Arc::new(FakeAdapter::new()));
|
||||
register
|
||||
}
|
||||
}
|
|
@ -261,8 +261,6 @@ impl TransportDelegate {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
smol::future::yield_now().await;
|
||||
};
|
||||
|
||||
log::debug!("Handle adapter log dropped");
|
||||
|
@ -319,8 +317,6 @@ impl TransportDelegate {
|
|||
}
|
||||
Err(error) => break Err(error.into()),
|
||||
}
|
||||
|
||||
smol::future::yield_now().await;
|
||||
};
|
||||
|
||||
log::debug!("Handle adapter input dropped");
|
||||
|
@ -360,8 +356,6 @@ impl TransportDelegate {
|
|||
}
|
||||
Err(e) => break Err(e),
|
||||
}
|
||||
|
||||
smol::future::yield_now().await;
|
||||
};
|
||||
|
||||
drop(client_tx);
|
||||
|
@ -393,8 +387,6 @@ impl TransportDelegate {
|
|||
}
|
||||
Err(error) => break Err(error.into()),
|
||||
}
|
||||
|
||||
smol::future::yield_now().await;
|
||||
};
|
||||
|
||||
log::debug!("Handle adapter error dropped");
|
||||
|
|
|
@ -30,7 +30,6 @@ paths.workspace = true
|
|||
regex.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
sysinfo.workspace = true
|
||||
task.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
use dap::transport::TcpTransport;
|
||||
use gpui::AsyncApp;
|
||||
use serde_json::Value;
|
||||
use std::{collections::HashMap, ffi::OsString, path::PathBuf};
|
||||
use sysinfo::{Pid, Process};
|
||||
use task::DebugAdapterConfig;
|
||||
|
||||
use crate::*;
|
||||
|
||||
pub(crate) struct CustomDebugAdapter {
|
||||
custom_args: CustomArgs,
|
||||
}
|
||||
|
||||
impl CustomDebugAdapter {
|
||||
const ADAPTER_NAME: &'static str = "custom_dap";
|
||||
|
||||
pub(crate) async fn new(custom_args: CustomArgs) -> Result<Self> {
|
||||
Ok(CustomDebugAdapter { custom_args })
|
||||
}
|
||||
|
||||
pub fn attach_processes(processes: &HashMap<Pid, Process>) -> Vec<(&Pid, &Process)> {
|
||||
processes.iter().collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl DebugAdapter for CustomDebugAdapter {
|
||||
fn name(&self) -> DebugAdapterName {
|
||||
DebugAdapterName(Self::ADAPTER_NAME.into())
|
||||
}
|
||||
|
||||
async fn get_binary(
|
||||
&self,
|
||||
_: &dyn DapDelegate,
|
||||
config: &DebugAdapterConfig,
|
||||
_: Option<PathBuf>,
|
||||
_: &mut AsyncApp,
|
||||
) -> Result<DebugAdapterBinary> {
|
||||
let connection = if let DebugConnectionType::TCP(connection) = &self.custom_args.connection
|
||||
{
|
||||
Some(adapters::TcpArguments {
|
||||
host: connection.host(),
|
||||
port: TcpTransport::port(&connection).await?,
|
||||
timeout: connection.timeout,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let ret = DebugAdapterBinary {
|
||||
command: self.custom_args.command.clone(),
|
||||
arguments: self
|
||||
.custom_args
|
||||
.args
|
||||
.clone()
|
||||
.map(|args| args.iter().map(OsString::from).collect()),
|
||||
cwd: config.cwd.clone(),
|
||||
envs: self.custom_args.envs.clone(),
|
||||
connection,
|
||||
};
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
async fn fetch_latest_adapter_version(&self, _: &dyn DapDelegate) -> Result<AdapterVersion> {
|
||||
bail!("Custom debug adapters don't have latest versions")
|
||||
}
|
||||
|
||||
async fn install_binary(&self, _: AdapterVersion, _: &dyn DapDelegate) -> Result<()> {
|
||||
bail!("Custom debug adapters cannot be installed")
|
||||
}
|
||||
|
||||
async fn get_installed_binary(
|
||||
&self,
|
||||
_: &dyn DapDelegate,
|
||||
_: &DebugAdapterConfig,
|
||||
_: Option<PathBuf>,
|
||||
_: &mut AsyncApp,
|
||||
) -> Result<DebugAdapterBinary> {
|
||||
bail!("Custom debug adapters cannot be installed")
|
||||
}
|
||||
|
||||
fn request_args(&self, config: &DebugAdapterConfig) -> Value {
|
||||
json!({"program": config.program})
|
||||
}
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
mod custom;
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
mod gdb;
|
||||
mod go;
|
||||
mod javascript;
|
||||
|
@ -7,16 +5,17 @@ mod lldb;
|
|||
mod php;
|
||||
mod python;
|
||||
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use std::{net::Ipv4Addr, sync::Arc};
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use custom::CustomDebugAdapter;
|
||||
use dap::adapters::{
|
||||
self, AdapterVersion, DapDelegate, DebugAdapter, DebugAdapterBinary, DebugAdapterName,
|
||||
GithubRepo,
|
||||
use dap::{
|
||||
adapters::{
|
||||
self, AdapterVersion, DapDelegate, DebugAdapter, DebugAdapterBinary, DebugAdapterName,
|
||||
GithubRepo,
|
||||
},
|
||||
DapRegistry,
|
||||
};
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
use gdb::GdbDebugAdapter;
|
||||
use go::GoDebugAdapter;
|
||||
use javascript::JsDebugAdapter;
|
||||
|
@ -24,44 +23,28 @@ use lldb::LldbDebugAdapter;
|
|||
use php::PhpDebugAdapter;
|
||||
use python::PythonDebugAdapter;
|
||||
use serde_json::{json, Value};
|
||||
use sysinfo::{Pid, Process};
|
||||
use task::{CustomArgs, DebugAdapterConfig, DebugAdapterKind, DebugConnectionType, TCPHost};
|
||||
use task::{DebugAdapterConfig, TCPHost};
|
||||
|
||||
pub async fn build_adapter(kind: &DebugAdapterKind) -> Result<Arc<dyn DebugAdapter>> {
|
||||
match kind {
|
||||
DebugAdapterKind::Custom(start_args) => {
|
||||
Ok(Arc::new(CustomDebugAdapter::new(start_args.clone()).await?))
|
||||
}
|
||||
DebugAdapterKind::Python(host) => Ok(Arc::new(PythonDebugAdapter::new(host).await?)),
|
||||
DebugAdapterKind::Php(host) => Ok(Arc::new(PhpDebugAdapter::new(host.clone()).await?)),
|
||||
DebugAdapterKind::Javascript(host) => {
|
||||
Ok(Arc::new(JsDebugAdapter::new(host.clone()).await?))
|
||||
}
|
||||
DebugAdapterKind::Lldb => Ok(Arc::new(LldbDebugAdapter::new())),
|
||||
DebugAdapterKind::Go(host) => Ok(Arc::new(GoDebugAdapter::new(host).await?)),
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
DebugAdapterKind::Gdb => Ok(Arc::new(GdbDebugAdapter::new())),
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
DebugAdapterKind::Fake(_) => Ok(Arc::new(dap::adapters::FakeAdapter::new())),
|
||||
#[cfg(not(any(test, feature = "test-support")))]
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => unreachable!("Fake variant only exists with test-support feature"),
|
||||
}
|
||||
pub fn init(registry: Arc<DapRegistry>) {
|
||||
registry.add_adapter(Arc::from(PythonDebugAdapter));
|
||||
registry.add_adapter(Arc::from(PhpDebugAdapter));
|
||||
registry.add_adapter(Arc::from(JsDebugAdapter::default()));
|
||||
registry.add_adapter(Arc::from(LldbDebugAdapter));
|
||||
registry.add_adapter(Arc::from(GoDebugAdapter));
|
||||
registry.add_adapter(Arc::from(GdbDebugAdapter));
|
||||
}
|
||||
|
||||
pub fn attach_processes<'a>(
|
||||
kind: &DebugAdapterKind,
|
||||
processes: &'a HashMap<Pid, Process>,
|
||||
) -> Vec<(&'a Pid, &'a Process)> {
|
||||
match kind {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
DebugAdapterKind::Fake(_) => processes
|
||||
.iter()
|
||||
.filter(|(pid, _)| pid.as_u32() == std::process::id())
|
||||
.collect::<Vec<_>>(),
|
||||
DebugAdapterKind::Custom(_) => CustomDebugAdapter::attach_processes(processes),
|
||||
DebugAdapterKind::Javascript(_) => JsDebugAdapter::attach_processes(processes),
|
||||
DebugAdapterKind::Lldb => LldbDebugAdapter::attach_processes(processes),
|
||||
_ => processes.iter().collect::<Vec<_>>(),
|
||||
}
|
||||
pub(crate) async fn configure_tcp_connection(
|
||||
tcp_connection: TCPHost,
|
||||
) -> Result<(Ipv4Addr, u16, Option<u64>)> {
|
||||
let host = tcp_connection.host();
|
||||
let timeout = tcp_connection.timeout;
|
||||
|
||||
let port = if let Some(port) = tcp_connection.port {
|
||||
port
|
||||
} else {
|
||||
dap::transport::TcpTransport::port(&tcp_connection).await?
|
||||
};
|
||||
|
||||
Ok((host, port, timeout))
|
||||
}
|
||||
|
|
|
@ -1,20 +1,17 @@
|
|||
use std::ffi::OsStr;
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{bail, Result};
|
||||
use async_trait::async_trait;
|
||||
use gpui::AsyncApp;
|
||||
use task::DebugAdapterConfig;
|
||||
use task::{DebugAdapterConfig, DebugTaskDefinition};
|
||||
|
||||
use crate::*;
|
||||
|
||||
pub(crate) struct GdbDebugAdapter {}
|
||||
#[derive(Default)]
|
||||
pub(crate) struct GdbDebugAdapter;
|
||||
|
||||
impl GdbDebugAdapter {
|
||||
const ADAPTER_NAME: &'static str = "gdb";
|
||||
|
||||
pub(crate) fn new() -> Self {
|
||||
GdbDebugAdapter {}
|
||||
}
|
||||
const ADAPTER_NAME: &'static str = "GDB";
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
|
@ -26,7 +23,7 @@ impl DebugAdapter for GdbDebugAdapter {
|
|||
async fn get_binary(
|
||||
&self,
|
||||
delegate: &dyn DapDelegate,
|
||||
config: &DebugAdapterConfig,
|
||||
_: &DebugAdapterConfig,
|
||||
user_installed_path: Option<std::path::PathBuf>,
|
||||
_: &mut AsyncApp,
|
||||
) -> Result<DebugAdapterBinary> {
|
||||
|
@ -34,7 +31,6 @@ impl DebugAdapter for GdbDebugAdapter {
|
|||
.filter(|p| p.exists())
|
||||
.and_then(|p| p.to_str().map(|s| s.to_string()));
|
||||
|
||||
/* GDB implements DAP natively so just need to */
|
||||
let gdb_path = delegate
|
||||
.which(OsStr::new("gdb"))
|
||||
.and_then(|p| p.to_str().map(|s| s.to_string()))
|
||||
|
@ -50,7 +46,7 @@ impl DebugAdapter for GdbDebugAdapter {
|
|||
command: gdb_path,
|
||||
arguments: Some(vec!["-i=dap".into()]),
|
||||
envs: None,
|
||||
cwd: config.cwd.clone(),
|
||||
cwd: None,
|
||||
connection: None,
|
||||
})
|
||||
}
|
||||
|
@ -77,7 +73,14 @@ impl DebugAdapter for GdbDebugAdapter {
|
|||
unimplemented!("GDB cannot be installed by Zed (yet)")
|
||||
}
|
||||
|
||||
fn request_args(&self, config: &DebugAdapterConfig) -> Value {
|
||||
json!({"program": config.program, "cwd": config.cwd})
|
||||
fn request_args(&self, config: &DebugTaskDefinition) -> Value {
|
||||
match &config.request {
|
||||
dap::DebugRequestType::Attach(attach_config) => {
|
||||
json!({"pid": attach_config.process_id})
|
||||
}
|
||||
dap::DebugRequestType::Launch(launch_config) => {
|
||||
json!({"program": launch_config.program, "cwd": launch_config.cwd})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,15 @@
|
|||
use dap::transport::TcpTransport;
|
||||
use anyhow::bail;
|
||||
use gpui::AsyncApp;
|
||||
use std::{ffi::OsStr, net::Ipv4Addr, path::PathBuf};
|
||||
use std::{ffi::OsStr, path::PathBuf};
|
||||
use task::DebugTaskDefinition;
|
||||
|
||||
use crate::*;
|
||||
|
||||
pub(crate) struct GoDebugAdapter {
|
||||
port: u16,
|
||||
host: Ipv4Addr,
|
||||
timeout: Option<u64>,
|
||||
}
|
||||
#[derive(Default, Debug)]
|
||||
pub(crate) struct GoDebugAdapter;
|
||||
|
||||
impl GoDebugAdapter {
|
||||
const ADAPTER_NAME: &'static str = "delve";
|
||||
|
||||
pub(crate) async fn new(host: &TCPHost) -> Result<Self> {
|
||||
Ok(GoDebugAdapter {
|
||||
port: TcpTransport::port(host).await?,
|
||||
host: host.host(),
|
||||
timeout: host.timeout,
|
||||
})
|
||||
}
|
||||
const ADAPTER_NAME: &'static str = "Delve";
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
|
@ -73,28 +63,39 @@ impl DebugAdapter for GoDebugAdapter {
|
|||
.and_then(|p| p.to_str().map(|p| p.to_string()))
|
||||
.ok_or(anyhow!("Dlv not found in path"))?;
|
||||
|
||||
let Some(tcp_connection) = config.tcp_connection.clone() else {
|
||||
bail!("Go Debug Adapter expects tcp connection arguments to be provided");
|
||||
};
|
||||
let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
|
||||
|
||||
Ok(DebugAdapterBinary {
|
||||
command: delve_path,
|
||||
arguments: Some(vec![
|
||||
"dap".into(),
|
||||
"--listen".into(),
|
||||
format!("{}:{}", self.host, self.port).into(),
|
||||
format!("{}:{}", host, port).into(),
|
||||
]),
|
||||
cwd: config.cwd.clone(),
|
||||
cwd: None,
|
||||
envs: None,
|
||||
connection: Some(adapters::TcpArguments {
|
||||
host: self.host,
|
||||
port: self.port,
|
||||
timeout: self.timeout,
|
||||
host,
|
||||
port,
|
||||
timeout,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
fn request_args(&self, config: &DebugAdapterConfig) -> Value {
|
||||
json!({
|
||||
"program": config.program,
|
||||
"cwd": config.cwd,
|
||||
"subProcess": true,
|
||||
})
|
||||
fn request_args(&self, config: &DebugTaskDefinition) -> Value {
|
||||
match &config.request {
|
||||
dap::DebugRequestType::Attach(attach_config) => {
|
||||
json!({
|
||||
"processId": attach_config.process_id
|
||||
})
|
||||
}
|
||||
dap::DebugRequestType::Launch(launch_config) => json!({
|
||||
"program": launch_config.program,
|
||||
"cwd": launch_config.cwd,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,39 +1,28 @@
|
|||
use adapters::latest_github_release;
|
||||
use dap::transport::TcpTransport;
|
||||
use gpui::AsyncApp;
|
||||
use regex::Regex;
|
||||
use std::{collections::HashMap, net::Ipv4Addr, path::PathBuf};
|
||||
use sysinfo::{Pid, Process};
|
||||
use task::DebugRequestType;
|
||||
use std::path::PathBuf;
|
||||
use task::{DebugRequestType, DebugTaskDefinition};
|
||||
|
||||
use crate::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct JsDebugAdapter {
|
||||
port: u16,
|
||||
host: Ipv4Addr,
|
||||
timeout: Option<u64>,
|
||||
attach_processes: Regex,
|
||||
}
|
||||
|
||||
impl Default for JsDebugAdapter {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
attach_processes: Regex::new(r"(?i)^(?:node|bun|iojs)(?:$|\b)")
|
||||
.expect("Regex compilation to succeed"),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl JsDebugAdapter {
|
||||
const ADAPTER_NAME: &'static str = "vscode-js-debug";
|
||||
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";
|
||||
|
||||
pub(crate) async fn new(host: TCPHost) -> Result<Self> {
|
||||
Ok(JsDebugAdapter {
|
||||
host: host.host(),
|
||||
timeout: host.timeout,
|
||||
port: TcpTransport::port(&host).await?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn attach_processes(processes: &HashMap<Pid, Process>) -> Vec<(&Pid, &Process)> {
|
||||
let regex = Regex::new(r"(?i)^(?:node|bun|iojs)(?:$|\b)").unwrap();
|
||||
|
||||
processes
|
||||
.iter()
|
||||
.filter(|(_, process)| regex.is_match(&process.name().to_string_lossy()))
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
|
@ -47,7 +36,7 @@ impl DebugAdapter for JsDebugAdapter {
|
|||
delegate: &dyn DapDelegate,
|
||||
) -> Result<AdapterVersion> {
|
||||
let release = latest_github_release(
|
||||
&format!("{}/{}", "microsoft", Self::ADAPTER_NAME),
|
||||
&format!("{}/{}", "microsoft", Self::ADAPTER_NPM_NAME),
|
||||
true,
|
||||
false,
|
||||
delegate.http_client(),
|
||||
|
@ -78,7 +67,7 @@ impl DebugAdapter for JsDebugAdapter {
|
|||
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());
|
||||
let adapter_path = paths::debug_adapters_dir().join(self.name().as_ref());
|
||||
|
||||
let file_name_prefix = format!("{}_", self.name());
|
||||
|
||||
|
@ -89,6 +78,13 @@ impl DebugAdapter for JsDebugAdapter {
|
|||
.ok_or_else(|| anyhow!("Couldn't find JavaScript dap directory"))?
|
||||
};
|
||||
|
||||
let Some(tcp_connection) = config.tcp_connection.clone() else {
|
||||
anyhow::bail!(
|
||||
"Javascript Debug Adapter expects tcp connection arguments to be provided"
|
||||
);
|
||||
};
|
||||
let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
|
||||
|
||||
Ok(DebugAdapterBinary {
|
||||
command: delegate
|
||||
.node_runtime()
|
||||
|
@ -98,15 +94,15 @@ impl DebugAdapter for JsDebugAdapter {
|
|||
.into_owned(),
|
||||
arguments: Some(vec![
|
||||
adapter_path.join(Self::ADAPTER_PATH).into(),
|
||||
self.port.to_string().into(),
|
||||
self.host.to_string().into(),
|
||||
port.to_string().into(),
|
||||
host.to_string().into(),
|
||||
]),
|
||||
cwd: config.cwd.clone(),
|
||||
cwd: None,
|
||||
envs: None,
|
||||
connection: Some(adapters::TcpArguments {
|
||||
host: self.host,
|
||||
port: self.port,
|
||||
timeout: self.timeout,
|
||||
host,
|
||||
port,
|
||||
timeout,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
@ -127,22 +123,35 @@ impl DebugAdapter for JsDebugAdapter {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
fn request_args(&self, config: &DebugAdapterConfig) -> Value {
|
||||
let pid = if let DebugRequestType::Attach(attach_config) = &config.request {
|
||||
attach_config.process_id
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
json!({
|
||||
"program": config.program,
|
||||
fn request_args(&self, config: &DebugTaskDefinition) -> Value {
|
||||
let mut args = json!({
|
||||
"type": "pwa-node",
|
||||
"request": match config.request {
|
||||
DebugRequestType::Launch => "launch",
|
||||
DebugRequestType::Launch(_) => "launch",
|
||||
DebugRequestType::Attach(_) => "attach",
|
||||
},
|
||||
"processId": pid,
|
||||
"cwd": config.cwd,
|
||||
})
|
||||
});
|
||||
let map = args.as_object_mut().unwrap();
|
||||
match &config.request {
|
||||
DebugRequestType::Attach(attach) => {
|
||||
map.insert("processId".into(), attach.process_id.into());
|
||||
}
|
||||
DebugRequestType::Launch(launch) => {
|
||||
map.insert("program".into(), launch.program.clone().into());
|
||||
map.insert(
|
||||
"cwd".into(),
|
||||
launch
|
||||
.cwd
|
||||
.as_ref()
|
||||
.map(|s| s.to_string_lossy().into_owned())
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
args
|
||||
}
|
||||
|
||||
fn attach_processes_filter(&self) -> Regex {
|
||||
self.attach_processes.clone()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,17 @@
|
|||
use std::{collections::HashMap, ffi::OsStr, path::PathBuf};
|
||||
use std::{ffi::OsStr, path::PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use gpui::AsyncApp;
|
||||
use sysinfo::{Pid, Process};
|
||||
use task::{DebugAdapterConfig, DebugRequestType};
|
||||
use task::{DebugAdapterConfig, DebugRequestType, DebugTaskDefinition};
|
||||
|
||||
use crate::*;
|
||||
|
||||
pub(crate) struct LldbDebugAdapter {}
|
||||
#[derive(Default)]
|
||||
pub(crate) struct LldbDebugAdapter;
|
||||
|
||||
impl LldbDebugAdapter {
|
||||
const ADAPTER_NAME: &'static str = "lldb";
|
||||
|
||||
pub(crate) fn new() -> Self {
|
||||
LldbDebugAdapter {}
|
||||
}
|
||||
|
||||
pub fn attach_processes(processes: &HashMap<Pid, Process>) -> Vec<(&Pid, &Process)> {
|
||||
processes.iter().collect::<Vec<_>>()
|
||||
}
|
||||
const ADAPTER_NAME: &'static str = "LLDB";
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
|
@ -31,7 +23,7 @@ impl DebugAdapter for LldbDebugAdapter {
|
|||
async fn get_binary(
|
||||
&self,
|
||||
delegate: &dyn DapDelegate,
|
||||
config: &DebugAdapterConfig,
|
||||
_: &DebugAdapterConfig,
|
||||
user_installed_path: Option<PathBuf>,
|
||||
_: &mut AsyncApp,
|
||||
) -> Result<DebugAdapterBinary> {
|
||||
|
@ -48,7 +40,7 @@ impl DebugAdapter for LldbDebugAdapter {
|
|||
command: lldb_dap_path,
|
||||
arguments: None,
|
||||
envs: None,
|
||||
cwd: config.cwd.clone(),
|
||||
cwd: None,
|
||||
connection: None,
|
||||
})
|
||||
}
|
||||
|
@ -75,21 +67,30 @@ impl DebugAdapter for LldbDebugAdapter {
|
|||
unimplemented!("LLDB debug adapter cannot be installed by Zed (yet)")
|
||||
}
|
||||
|
||||
fn request_args(&self, config: &DebugAdapterConfig) -> Value {
|
||||
let pid = if let DebugRequestType::Attach(attach_config) = &config.request {
|
||||
attach_config.process_id
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
json!({
|
||||
"program": config.program,
|
||||
fn request_args(&self, config: &DebugTaskDefinition) -> Value {
|
||||
let mut args = json!({
|
||||
"request": match config.request {
|
||||
DebugRequestType::Launch => "launch",
|
||||
DebugRequestType::Launch(_) => "launch",
|
||||
DebugRequestType::Attach(_) => "attach",
|
||||
},
|
||||
"pid": pid,
|
||||
"cwd": config.cwd,
|
||||
})
|
||||
});
|
||||
let map = args.as_object_mut().unwrap();
|
||||
match &config.request {
|
||||
DebugRequestType::Attach(attach) => {
|
||||
map.insert("pid".into(), attach.process_id.into());
|
||||
}
|
||||
DebugRequestType::Launch(launch) => {
|
||||
map.insert("program".into(), launch.program.clone().into());
|
||||
map.insert(
|
||||
"cwd".into(),
|
||||
launch
|
||||
.cwd
|
||||
.as_ref()
|
||||
.map(|s| s.to_string_lossy().into_owned())
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
args
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +1,19 @@
|
|||
use adapters::latest_github_release;
|
||||
use dap::{adapters::TcpArguments, transport::TcpTransport};
|
||||
use anyhow::bail;
|
||||
use dap::adapters::TcpArguments;
|
||||
use gpui::AsyncApp;
|
||||
use std::{net::Ipv4Addr, path::PathBuf};
|
||||
use std::path::PathBuf;
|
||||
use task::DebugTaskDefinition;
|
||||
|
||||
use crate::*;
|
||||
|
||||
pub(crate) struct PhpDebugAdapter {
|
||||
port: u16,
|
||||
host: Ipv4Addr,
|
||||
timeout: Option<u64>,
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub(crate) struct PhpDebugAdapter;
|
||||
|
||||
impl PhpDebugAdapter {
|
||||
const ADAPTER_NAME: &'static str = "vscode-php-debug";
|
||||
const ADAPTER_NAME: &'static str = "PHP";
|
||||
const ADAPTER_PACKAGE_NAME: &'static str = "vscode-php-debug";
|
||||
const ADAPTER_PATH: &'static str = "extension/out/phpDebug.js";
|
||||
|
||||
pub(crate) async fn new(host: TCPHost) -> Result<Self> {
|
||||
Ok(PhpDebugAdapter {
|
||||
port: TcpTransport::port(&host).await?,
|
||||
host: host.host(),
|
||||
timeout: host.timeout,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
|
@ -35,7 +27,7 @@ impl DebugAdapter for PhpDebugAdapter {
|
|||
delegate: &dyn DapDelegate,
|
||||
) -> Result<AdapterVersion> {
|
||||
let release = latest_github_release(
|
||||
&format!("{}/{}", "xdebug", Self::ADAPTER_NAME),
|
||||
&format!("{}/{}", "xdebug", Self::ADAPTER_PACKAGE_NAME),
|
||||
true,
|
||||
false,
|
||||
delegate.http_client(),
|
||||
|
@ -66,7 +58,7 @@ impl DebugAdapter for PhpDebugAdapter {
|
|||
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());
|
||||
let adapter_path = paths::debug_adapters_dir().join(self.name().as_ref());
|
||||
|
||||
let file_name_prefix = format!("{}_", self.name());
|
||||
|
||||
|
@ -77,6 +69,11 @@ impl DebugAdapter for PhpDebugAdapter {
|
|||
.ok_or_else(|| anyhow!("Couldn't find PHP dap directory"))?
|
||||
};
|
||||
|
||||
let Some(tcp_connection) = config.tcp_connection.clone() else {
|
||||
bail!("PHP Debug Adapter expects tcp connection arguments to be provided");
|
||||
};
|
||||
let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
|
||||
|
||||
Ok(DebugAdapterBinary {
|
||||
command: delegate
|
||||
.node_runtime()
|
||||
|
@ -86,14 +83,14 @@ impl DebugAdapter for PhpDebugAdapter {
|
|||
.into_owned(),
|
||||
arguments: Some(vec![
|
||||
adapter_path.join(Self::ADAPTER_PATH).into(),
|
||||
format!("--server={}", self.port).into(),
|
||||
format!("--server={}", port).into(),
|
||||
]),
|
||||
connection: Some(TcpArguments {
|
||||
port: self.port,
|
||||
host: self.host,
|
||||
timeout: self.timeout,
|
||||
port,
|
||||
host,
|
||||
timeout,
|
||||
}),
|
||||
cwd: config.cwd.clone(),
|
||||
cwd: None,
|
||||
envs: None,
|
||||
})
|
||||
}
|
||||
|
@ -114,10 +111,18 @@ impl DebugAdapter for PhpDebugAdapter {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn request_args(&self, config: &DebugAdapterConfig) -> Value {
|
||||
json!({
|
||||
"program": config.program,
|
||||
"cwd": config.cwd,
|
||||
})
|
||||
fn request_args(&self, config: &DebugTaskDefinition) -> Value {
|
||||
match &config.request {
|
||||
dap::DebugRequestType::Attach(_) => {
|
||||
// php adapter does not support attaching
|
||||
json!({})
|
||||
}
|
||||
dap::DebugRequestType::Launch(launch_config) => {
|
||||
json!({
|
||||
"program": launch_config.program,
|
||||
"cwd": launch_config.cwd,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,18 @@
|
|||
use crate::*;
|
||||
use dap::transport::TcpTransport;
|
||||
use anyhow::bail;
|
||||
use dap::DebugRequestType;
|
||||
use gpui::AsyncApp;
|
||||
use std::{ffi::OsStr, net::Ipv4Addr, path::PathBuf};
|
||||
use std::{ffi::OsStr, path::PathBuf};
|
||||
use task::DebugTaskDefinition;
|
||||
|
||||
pub(crate) struct PythonDebugAdapter {
|
||||
port: u16,
|
||||
host: Ipv4Addr,
|
||||
timeout: Option<u64>,
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub(crate) struct PythonDebugAdapter;
|
||||
|
||||
impl PythonDebugAdapter {
|
||||
const ADAPTER_NAME: &'static str = "debugpy";
|
||||
const ADAPTER_NAME: &'static str = "Debugpy";
|
||||
const ADAPTER_PACKAGE_NAME: &'static str = "debugpy";
|
||||
const ADAPTER_PATH: &'static str = "src/debugpy/adapter";
|
||||
const LANGUAGE_NAME: &'static str = "Python";
|
||||
|
||||
pub(crate) async fn new(host: &TCPHost) -> Result<Self> {
|
||||
Ok(PythonDebugAdapter {
|
||||
port: TcpTransport::port(host).await?,
|
||||
host: host.host(),
|
||||
timeout: host.timeout,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
|
@ -34,7 +26,7 @@ impl DebugAdapter for PythonDebugAdapter {
|
|||
delegate: &dyn DapDelegate,
|
||||
) -> Result<AdapterVersion> {
|
||||
let github_repo = GithubRepo {
|
||||
repo_name: Self::ADAPTER_NAME.into(),
|
||||
repo_name: Self::ADAPTER_PACKAGE_NAME.into(),
|
||||
repo_owner: "microsoft".into(),
|
||||
};
|
||||
|
||||
|
@ -78,12 +70,16 @@ impl DebugAdapter for PythonDebugAdapter {
|
|||
cx: &mut AsyncApp,
|
||||
) -> Result<DebugAdapterBinary> {
|
||||
const BINARY_NAMES: [&str; 3] = ["python3", "python", "py"];
|
||||
let Some(tcp_connection) = config.tcp_connection.clone() else {
|
||||
bail!("Python Debug Adapter expects tcp connection arguments to be provided");
|
||||
};
|
||||
let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
|
||||
|
||||
let debugpy_dir = if let Some(user_installed_path) = user_installed_path {
|
||||
user_installed_path
|
||||
} else {
|
||||
let adapter_path = paths::debug_adapters_dir().join(self.name());
|
||||
let file_name_prefix = format!("{}_", self.name());
|
||||
let adapter_path = paths::debug_adapters_dir().join(self.name().as_ref());
|
||||
let file_name_prefix = format!("{}_", Self::ADAPTER_PACKAGE_NAME);
|
||||
|
||||
util::fs::find_file_name_in_dir(adapter_path.as_path(), |file_name| {
|
||||
file_name.starts_with(&file_name_prefix)
|
||||
|
@ -118,25 +114,36 @@ impl DebugAdapter for PythonDebugAdapter {
|
|||
command: python_path.ok_or(anyhow!("failed to find binary path for python"))?,
|
||||
arguments: Some(vec![
|
||||
debugpy_dir.join(Self::ADAPTER_PATH).into(),
|
||||
format!("--port={}", self.port).into(),
|
||||
format!("--host={}", self.host).into(),
|
||||
format!("--port={}", port).into(),
|
||||
format!("--host={}", host).into(),
|
||||
]),
|
||||
connection: Some(adapters::TcpArguments {
|
||||
host: self.host,
|
||||
port: self.port,
|
||||
timeout: self.timeout,
|
||||
host,
|
||||
port,
|
||||
timeout,
|
||||
}),
|
||||
cwd: config.cwd.clone(),
|
||||
cwd: None,
|
||||
envs: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn request_args(&self, config: &DebugAdapterConfig) -> Value {
|
||||
json!({
|
||||
"program": config.program,
|
||||
"subProcess": true,
|
||||
"cwd": config.cwd,
|
||||
"redirectOutput": true,
|
||||
})
|
||||
fn request_args(&self, config: &DebugTaskDefinition) -> Value {
|
||||
match &config.request {
|
||||
DebugRequestType::Launch(launch_config) => {
|
||||
json!({
|
||||
"program": launch_config.program,
|
||||
"subProcess": true,
|
||||
"cwd": launch_config.cwd,
|
||||
"redirectOutput": true,
|
||||
})
|
||||
}
|
||||
dap::DebugRequestType::Attach(attach_config) => {
|
||||
json!({
|
||||
"subProcess": true,
|
||||
"redirectOutput": true,
|
||||
"processId": attach_config.process_id
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,10 @@ license = "GPL-3.0-or-later"
|
|||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/debugger_ui.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
test-support = [
|
||||
"dap/test-support",
|
||||
|
|
|
@ -3,8 +3,8 @@ use fuzzy::{StringMatch, StringMatchCandidate};
|
|||
use gpui::Subscription;
|
||||
use gpui::{DismissEvent, Entity, EventEmitter, Focusable, Render};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::debugger::attach_processes;
|
||||
|
||||
use std::cell::LazyCell;
|
||||
use std::sync::Arc;
|
||||
use sysinfo::System;
|
||||
use ui::{prelude::*, Context, Tooltip};
|
||||
|
@ -13,10 +13,10 @@ use util::debug_panic;
|
|||
use workspace::ModalView;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Candidate {
|
||||
pid: u32,
|
||||
name: String,
|
||||
command: Vec<String>,
|
||||
pub(super) struct Candidate {
|
||||
pub(super) pid: u32,
|
||||
pub(super) name: SharedString,
|
||||
pub(super) command: Vec<String>,
|
||||
}
|
||||
|
||||
pub(crate) struct AttachModalDelegate {
|
||||
|
@ -24,16 +24,20 @@ pub(crate) struct AttachModalDelegate {
|
|||
matches: Vec<StringMatch>,
|
||||
placeholder_text: Arc<str>,
|
||||
project: Entity<project::Project>,
|
||||
debug_config: task::DebugAdapterConfig,
|
||||
candidates: Option<Vec<Candidate>>,
|
||||
debug_config: task::DebugTaskDefinition,
|
||||
candidates: Arc<[Candidate]>,
|
||||
}
|
||||
|
||||
impl AttachModalDelegate {
|
||||
pub fn new(project: Entity<project::Project>, debug_config: task::DebugAdapterConfig) -> Self {
|
||||
fn new(
|
||||
project: Entity<project::Project>,
|
||||
debug_config: task::DebugTaskDefinition,
|
||||
candidates: Arc<[Candidate]>,
|
||||
) -> Self {
|
||||
Self {
|
||||
project,
|
||||
debug_config,
|
||||
candidates: None,
|
||||
candidates,
|
||||
selected_index: 0,
|
||||
matches: Vec::default(),
|
||||
placeholder_text: Arc::from("Select the process you want to attach the debugger to"),
|
||||
|
@ -49,12 +53,56 @@ pub struct AttachModal {
|
|||
impl AttachModal {
|
||||
pub fn new(
|
||||
project: Entity<project::Project>,
|
||||
debug_config: task::DebugAdapterConfig,
|
||||
debug_config: task::DebugTaskDefinition,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let mut processes: Vec<_> = System::new_all()
|
||||
.processes()
|
||||
.values()
|
||||
.map(|process| {
|
||||
let name = process.name().to_string_lossy().into_owned();
|
||||
Candidate {
|
||||
name: name.into(),
|
||||
pid: process.pid().as_u32(),
|
||||
command: process
|
||||
.cmd()
|
||||
.iter()
|
||||
.map(|s| s.to_string_lossy().to_string())
|
||||
.collect::<Vec<_>>(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
processes.sort_by_key(|k| k.name.clone());
|
||||
Self::with_processes(project, debug_config, processes, window, cx)
|
||||
}
|
||||
|
||||
pub(super) fn with_processes(
|
||||
project: Entity<project::Project>,
|
||||
debug_config: task::DebugTaskDefinition,
|
||||
processes: Vec<Candidate>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let adapter = project
|
||||
.read(cx)
|
||||
.debug_adapters()
|
||||
.adapter(&debug_config.adapter);
|
||||
let filter = LazyCell::new(|| adapter.map(|adapter| adapter.attach_processes_filter()));
|
||||
let processes = processes
|
||||
.into_iter()
|
||||
.filter(|process| {
|
||||
filter
|
||||
.as_ref()
|
||||
.map_or(false, |filter| filter.is_match(&process.name))
|
||||
})
|
||||
.collect();
|
||||
let picker = cx.new(|cx| {
|
||||
Picker::uniform_list(AttachModalDelegate::new(project, debug_config), window, cx)
|
||||
Picker::uniform_list(
|
||||
AttachModalDelegate::new(project, debug_config, processes),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
Self {
|
||||
_subscription: cx.subscribe(&picker, |_, _, _, cx| {
|
||||
|
@ -116,32 +164,7 @@ impl PickerDelegate for AttachModalDelegate {
|
|||
) -> gpui::Task<()> {
|
||||
cx.spawn(async move |this, cx| {
|
||||
let Some(processes) = this
|
||||
.update(cx, |this, _| {
|
||||
if let Some(processes) = this.delegate.candidates.clone() {
|
||||
processes
|
||||
} else {
|
||||
let system = System::new_all();
|
||||
|
||||
let processes =
|
||||
attach_processes(&this.delegate.debug_config.kind, &system.processes());
|
||||
let candidates = processes
|
||||
.into_iter()
|
||||
.map(|(pid, process)| Candidate {
|
||||
pid: pid.as_u32(),
|
||||
name: process.name().to_string_lossy().into_owned(),
|
||||
command: process
|
||||
.cmd()
|
||||
.iter()
|
||||
.map(|s| s.to_string_lossy().to_string())
|
||||
.collect::<Vec<_>>(),
|
||||
})
|
||||
.collect::<Vec<Candidate>>();
|
||||
|
||||
let _ = this.delegate.candidates.insert(candidates.clone());
|
||||
|
||||
candidates
|
||||
}
|
||||
})
|
||||
.update(cx, |this, _| this.delegate.candidates.clone())
|
||||
.ok()
|
||||
else {
|
||||
return;
|
||||
|
@ -176,7 +199,6 @@ impl PickerDelegate for AttachModalDelegate {
|
|||
let delegate = &mut this.delegate;
|
||||
|
||||
delegate.matches = matches;
|
||||
delegate.candidates = Some(processes);
|
||||
|
||||
if delegate.matches.is_empty() {
|
||||
delegate.selected_index = 0;
|
||||
|
@ -195,7 +217,7 @@ impl PickerDelegate for AttachModalDelegate {
|
|||
.get(self.selected_index())
|
||||
.and_then(|current_match| {
|
||||
let ix = current_match.candidate_id;
|
||||
self.candidates.as_ref().map(|candidates| &candidates[ix])
|
||||
self.candidates.get(ix)
|
||||
});
|
||||
|
||||
let Some(candidate) = candidate else {
|
||||
|
@ -206,7 +228,7 @@ impl PickerDelegate for AttachModalDelegate {
|
|||
DebugRequestType::Attach(config) => {
|
||||
config.process_id = Some(candidate.pid);
|
||||
}
|
||||
DebugRequestType::Launch => {
|
||||
DebugRequestType::Launch(_) => {
|
||||
debug_panic!("Debugger attach modal used on launch debug config");
|
||||
return;
|
||||
}
|
||||
|
@ -214,7 +236,13 @@ impl PickerDelegate for AttachModalDelegate {
|
|||
|
||||
let config = self.debug_config.clone();
|
||||
self.project
|
||||
.update(cx, |project, cx| project.start_debug_session(config, cx))
|
||||
.update(cx, |project, cx| {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
let ret = project.fake_debug_session(config.request, None, false, cx);
|
||||
#[cfg(not(any(test, feature = "test-support")))]
|
||||
let ret = project.start_debug_session(config.into(), cx);
|
||||
ret
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
cx.emit(DismissEvent);
|
||||
|
@ -222,7 +250,6 @@ impl PickerDelegate for AttachModalDelegate {
|
|||
|
||||
fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||
self.selected_index = 0;
|
||||
self.candidates.take();
|
||||
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
|
@ -234,9 +261,8 @@ impl PickerDelegate for AttachModalDelegate {
|
|||
_window: &mut Window,
|
||||
_: &mut Context<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let candidates = self.candidates.as_ref()?;
|
||||
let hit = &self.matches[ix];
|
||||
let candidate = &candidates.get(hit.candidate_id)?;
|
||||
let candidate = self.candidates.get(hit.candidate_id)?;
|
||||
|
||||
Some(
|
||||
ListItem::new(SharedString::from(format!("process-entry-{ix}")))
|
||||
|
@ -279,9 +305,8 @@ impl PickerDelegate for AttachModalDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub(crate) fn process_names(modal: &AttachModal, cx: &mut Context<AttachModal>) -> Vec<String> {
|
||||
pub(crate) fn _process_names(modal: &AttachModal, cx: &mut Context<AttachModal>) -> Vec<String> {
|
||||
modal.picker.update(cx, |picker, _| {
|
||||
picker
|
||||
.delegate
|
||||
|
|
|
@ -3,8 +3,8 @@ use anyhow::{anyhow, Result};
|
|||
use collections::HashMap;
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
use dap::{
|
||||
client::SessionId, debugger_settings::DebuggerSettings, ContinuedEvent, DebugAdapterConfig,
|
||||
LoadedSourceEvent, ModuleEvent, OutputEvent, StoppedEvent, ThreadEvent,
|
||||
client::SessionId, debugger_settings::DebuggerSettings, ContinuedEvent, LoadedSourceEvent,
|
||||
ModuleEvent, OutputEvent, StoppedEvent, ThreadEvent,
|
||||
};
|
||||
use futures::{channel::mpsc, SinkExt as _};
|
||||
use gpui::{
|
||||
|
@ -19,6 +19,7 @@ use project::{
|
|||
use rpc::proto::{self};
|
||||
use settings::Settings;
|
||||
use std::{any::TypeId, path::PathBuf};
|
||||
use task::DebugTaskDefinition;
|
||||
use terminal_view::terminal_panel::TerminalPanel;
|
||||
use ui::prelude::*;
|
||||
use util::ResultExt;
|
||||
|
@ -52,7 +53,7 @@ pub struct DebugPanel {
|
|||
project: WeakEntity<Project>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
pub(crate) last_inert_config: Option<DebugAdapterConfig>,
|
||||
pub(crate) last_inert_config: Option<DebugTaskDefinition>,
|
||||
}
|
||||
|
||||
impl DebugPanel {
|
||||
|
|
|
@ -6,7 +6,6 @@ mod starting;
|
|||
use std::time::Duration;
|
||||
|
||||
use dap::client::SessionId;
|
||||
use dap::DebugAdapterConfig;
|
||||
use failed::FailedState;
|
||||
use gpui::{
|
||||
percentage, Animation, AnimationExt, AnyElement, App, Entity, EventEmitter, FocusHandle,
|
||||
|
@ -19,6 +18,7 @@ use project::Project;
|
|||
use rpc::proto::{self, PeerId};
|
||||
use running::RunningState;
|
||||
use starting::{StartingEvent, StartingState};
|
||||
use task::DebugTaskDefinition;
|
||||
use ui::{prelude::*, Indicator};
|
||||
use util::ResultExt;
|
||||
use workspace::{
|
||||
|
@ -73,7 +73,7 @@ impl DebugSession {
|
|||
project: Entity<Project>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
debug_panel: WeakEntity<DebugPanel>,
|
||||
config: Option<DebugAdapterConfig>,
|
||||
config: Option<DebugTaskDefinition>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<Self> {
|
||||
|
@ -171,7 +171,7 @@ impl DebugSession {
|
|||
.flatten()
|
||||
.expect("worktree-less project");
|
||||
let Ok((new_session_id, task)) = dap_store.update(cx, |store, cx| {
|
||||
store.new_session(config, &worktree, None, cx)
|
||||
store.new_session(config.into(), &worktree, None, cx)
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use dap::{DebugAdapterConfig, DebugAdapterKind, DebugRequestType};
|
||||
use dap::DebugRequestType;
|
||||
use editor::{Editor, EditorElement, EditorStyle};
|
||||
use gpui::{App, AppContext, Entity, EventEmitter, FocusHandle, Focusable, TextStyle, WeakEntity};
|
||||
use settings::Settings as _;
|
||||
use task::TCPHost;
|
||||
use task::{DebugTaskDefinition, LaunchConfig, TCPHost};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
div, h_flex, relative, v_flex, ActiveTheme as _, ButtonCommon, ButtonLike, Clickable, Context,
|
||||
|
@ -35,7 +35,7 @@ impl SpawnMode {
|
|||
impl From<DebugRequestType> for SpawnMode {
|
||||
fn from(request: DebugRequestType) -> Self {
|
||||
match request {
|
||||
DebugRequestType::Launch => SpawnMode::Launch,
|
||||
DebugRequestType::Launch(_) => SpawnMode::Launch,
|
||||
DebugRequestType::Attach(_) => SpawnMode::Attach,
|
||||
}
|
||||
}
|
||||
|
@ -55,18 +55,13 @@ impl InertState {
|
|||
pub(super) fn new(
|
||||
workspace: WeakEntity<Workspace>,
|
||||
default_cwd: &str,
|
||||
debug_config: Option<DebugAdapterConfig>,
|
||||
debug_config: Option<DebugTaskDefinition>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let selected_debugger = debug_config.as_ref().and_then(|config| match config.kind {
|
||||
DebugAdapterKind::Lldb => Some("LLDB".into()),
|
||||
DebugAdapterKind::Go(_) => Some("Delve".into()),
|
||||
DebugAdapterKind::Php(_) => Some("PHP".into()),
|
||||
DebugAdapterKind::Javascript(_) => Some("JavaScript".into()),
|
||||
DebugAdapterKind::Python(_) => Some("Debugpy".into()),
|
||||
_ => None,
|
||||
});
|
||||
let selected_debugger = debug_config
|
||||
.as_ref()
|
||||
.map(|config| SharedString::from(config.adapter.clone()));
|
||||
|
||||
let spawn_mode = debug_config
|
||||
.as_ref()
|
||||
|
@ -75,7 +70,10 @@ impl InertState {
|
|||
|
||||
let program = debug_config
|
||||
.as_ref()
|
||||
.and_then(|config| config.program.to_owned());
|
||||
.and_then(|config| match &config.request {
|
||||
DebugRequestType::Attach(_) => None,
|
||||
DebugRequestType::Launch(launch_config) => Some(launch_config.program.clone()),
|
||||
});
|
||||
|
||||
let program_editor = cx.new(|cx| {
|
||||
let mut editor = Editor::single_line(window, cx);
|
||||
|
@ -88,7 +86,10 @@ impl InertState {
|
|||
});
|
||||
|
||||
let cwd = debug_config
|
||||
.and_then(|config| config.cwd.map(|cwd| cwd.to_owned()))
|
||||
.and_then(|config| match &config.request {
|
||||
DebugRequestType::Attach(_) => None,
|
||||
DebugRequestType::Launch(launch_config) => launch_config.cwd.clone(),
|
||||
})
|
||||
.unwrap_or_else(|| PathBuf::from(default_cwd));
|
||||
|
||||
let cwd_editor = cx.new(|cx| {
|
||||
|
@ -116,7 +117,7 @@ impl Focusable for InertState {
|
|||
}
|
||||
|
||||
pub(crate) enum InertEvent {
|
||||
Spawned { config: DebugAdapterConfig },
|
||||
Spawned { config: DebugTaskDefinition },
|
||||
}
|
||||
|
||||
impl EventEmitter<InertEvent> for InertState {}
|
||||
|
@ -130,6 +131,7 @@ impl Render for InertState {
|
|||
cx: &mut ui::Context<'_, Self>,
|
||||
) -> impl ui::IntoElement {
|
||||
let weak = cx.weak_entity();
|
||||
let workspace = self.workspace.clone();
|
||||
let disable_buttons = self.selected_debugger.is_none();
|
||||
let spawn_button = ButtonLike::new_rounded_left("spawn-debug-session")
|
||||
.child(Label::new(self.spawn_mode.label()).size(LabelSize::Small))
|
||||
|
@ -137,21 +139,26 @@ impl Render for InertState {
|
|||
if this.spawn_mode == SpawnMode::Launch {
|
||||
let program = this.program_editor.read(cx).text(cx);
|
||||
let cwd = PathBuf::from(this.cwd_editor.read(cx).text(cx));
|
||||
let kind =
|
||||
kind_for_label(this.selected_debugger.as_deref().unwrap_or_else(|| {
|
||||
let kind = this
|
||||
.selected_debugger
|
||||
.as_deref()
|
||||
.unwrap_or_else(|| {
|
||||
unimplemented!(
|
||||
"Automatic selection of a debugger based on users project"
|
||||
)
|
||||
}));
|
||||
})
|
||||
.to_string();
|
||||
|
||||
cx.emit(InertEvent::Spawned {
|
||||
config: DebugAdapterConfig {
|
||||
config: DebugTaskDefinition {
|
||||
label: "hard coded".into(),
|
||||
kind,
|
||||
request: DebugRequestType::Launch,
|
||||
program: Some(program),
|
||||
cwd: Some(cwd),
|
||||
adapter: kind,
|
||||
request: DebugRequestType::Launch(LaunchConfig {
|
||||
program,
|
||||
cwd: Some(cwd),
|
||||
}),
|
||||
tcp_connection: Some(TCPHost::default()),
|
||||
initialize_args: None,
|
||||
supports_attach: false,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
|
@ -159,6 +166,7 @@ impl Render for InertState {
|
|||
}
|
||||
}))
|
||||
.disabled(disable_buttons);
|
||||
|
||||
v_flex()
|
||||
.track_focus(&self.focus_handle)
|
||||
.size_full()
|
||||
|
@ -179,28 +187,36 @@ impl Render for InertState {
|
|||
.as_ref()
|
||||
.unwrap_or_else(|| &SELECT_DEBUGGER_LABEL)
|
||||
.clone(),
|
||||
ContextMenu::build(window, cx, move |this, _, _| {
|
||||
let setter_for_name = |name: &'static str| {
|
||||
ContextMenu::build(window, cx, move |mut this, _, cx| {
|
||||
let setter_for_name = |name: SharedString| {
|
||||
let weak = weak.clone();
|
||||
move |_: &mut Window, cx: &mut App| {
|
||||
let name = name;
|
||||
(&weak)
|
||||
.update(cx, move |this, _| {
|
||||
this.selected_debugger = Some(name.into());
|
||||
})
|
||||
.ok();
|
||||
let name = name.clone();
|
||||
weak.update(cx, move |this, cx| {
|
||||
this.selected_debugger = Some(name.clone());
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
};
|
||||
this.entry("GDB", None, setter_for_name("GDB"))
|
||||
.entry("Delve", None, setter_for_name("Delve"))
|
||||
.entry("LLDB", None, setter_for_name("LLDB"))
|
||||
.entry("PHP", None, setter_for_name("PHP"))
|
||||
.entry(
|
||||
"JavaScript",
|
||||
let available_adapters = workspace
|
||||
.update(cx, |this, cx| {
|
||||
this.project()
|
||||
.read(cx)
|
||||
.debug_adapters()
|
||||
.enumerate_adapters()
|
||||
})
|
||||
.ok()
|
||||
.unwrap_or_default();
|
||||
|
||||
for adapter in available_adapters {
|
||||
this = this.entry(
|
||||
adapter.0.clone(),
|
||||
None,
|
||||
setter_for_name("JavaScript"),
|
||||
)
|
||||
.entry("Debugpy", None, setter_for_name("Debugpy"))
|
||||
setter_for_name(adapter.0.clone()),
|
||||
);
|
||||
}
|
||||
this
|
||||
}),
|
||||
)),
|
||||
),
|
||||
|
@ -265,18 +281,6 @@ impl Render for InertState {
|
|||
}
|
||||
}
|
||||
|
||||
fn kind_for_label(label: &str) -> DebugAdapterKind {
|
||||
match label {
|
||||
"LLDB" => DebugAdapterKind::Lldb,
|
||||
"Debugpy" => DebugAdapterKind::Python(TCPHost::default()),
|
||||
"JavaScript" => DebugAdapterKind::Javascript(TCPHost::default()),
|
||||
"PHP" => DebugAdapterKind::Php(TCPHost::default()),
|
||||
"Delve" => DebugAdapterKind::Go(TCPHost::default()),
|
||||
_ => {
|
||||
unimplemented!()
|
||||
} // Maybe we should set a toast notification here
|
||||
}
|
||||
}
|
||||
impl InertState {
|
||||
fn render_editor(editor: &Entity<Editor>, cx: &Context<Self>) -> impl IntoElement {
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
|
@ -302,19 +306,20 @@ impl InertState {
|
|||
}
|
||||
|
||||
fn attach(&self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let cwd = PathBuf::from(self.cwd_editor.read(cx).text(cx));
|
||||
let kind = kind_for_label(self.selected_debugger.as_deref().unwrap_or_else(|| {
|
||||
unimplemented!("Automatic selection of a debugger based on users project")
|
||||
}));
|
||||
let kind = self
|
||||
.selected_debugger
|
||||
.as_deref()
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_else(|| {
|
||||
unimplemented!("Automatic selection of a debugger based on users project")
|
||||
});
|
||||
|
||||
let config = DebugAdapterConfig {
|
||||
let config = DebugTaskDefinition {
|
||||
label: "hard coded attach".into(),
|
||||
kind,
|
||||
adapter: kind,
|
||||
request: DebugRequestType::Attach(task::AttachConfig { process_id: None }),
|
||||
program: None,
|
||||
cwd: Some(cwd),
|
||||
initialize_args: None,
|
||||
supports_attach: true,
|
||||
tcp_connection: Some(TCPHost::default()),
|
||||
};
|
||||
|
||||
let _ = self.workspace.update(cx, |workspace, cx| {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use crate::*;
|
||||
use crate::{attach_modal::Candidate, *};
|
||||
use attach_modal::AttachModal;
|
||||
use dap::client::SessionId;
|
||||
use dap::{client::SessionId, FakeAdapter};
|
||||
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
||||
use menu::Confirm;
|
||||
use project::{FakeFs, Project};
|
||||
use serde_json::json;
|
||||
use task::AttachConfig;
|
||||
use task::{AttachConfig, DebugTaskDefinition, TCPHost};
|
||||
use tests::{init_test, init_test_workspace};
|
||||
|
||||
#[gpui::test]
|
||||
|
@ -27,14 +27,12 @@ async fn test_direct_attach_to_process(executor: BackgroundExecutor, cx: &mut Te
|
|||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let task = project.update(cx, |project, cx| {
|
||||
project.start_debug_session(
|
||||
dap::test_config(
|
||||
dap::DebugRequestType::Attach(AttachConfig {
|
||||
process_id: Some(10),
|
||||
}),
|
||||
None,
|
||||
None,
|
||||
),
|
||||
project.fake_debug_session(
|
||||
dap::DebugRequestType::Attach(AttachConfig {
|
||||
process_id: Some(10),
|
||||
}),
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
@ -83,13 +81,32 @@ async fn test_show_attach_modal_and_select_process(
|
|||
let attach_modal = workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
AttachModal::new(
|
||||
AttachModal::with_processes(
|
||||
project.clone(),
|
||||
dap::test_config(
|
||||
dap::DebugRequestType::Attach(AttachConfig { process_id: None }),
|
||||
None,
|
||||
None,
|
||||
),
|
||||
DebugTaskDefinition {
|
||||
adapter: FakeAdapter::ADAPTER_NAME.into(),
|
||||
request: dap::DebugRequestType::Attach(AttachConfig::default()),
|
||||
label: "attach example".into(),
|
||||
initialize_args: None,
|
||||
tcp_connection: Some(TCPHost::default()),
|
||||
},
|
||||
vec![
|
||||
Candidate {
|
||||
pid: 0,
|
||||
name: "fake-binary-1".into(),
|
||||
command: vec![],
|
||||
},
|
||||
Candidate {
|
||||
pid: 3,
|
||||
name: "non-fake-binary-1".into(),
|
||||
command: vec![],
|
||||
},
|
||||
Candidate {
|
||||
pid: 1,
|
||||
name: "fake-binary-2".into(),
|
||||
command: vec![],
|
||||
},
|
||||
],
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
|
@ -105,10 +122,10 @@ async fn test_show_attach_modal_and_select_process(
|
|||
workspace
|
||||
.update(cx, |_, _, cx| {
|
||||
let names =
|
||||
attach_modal.update(cx, |modal, cx| attach_modal::process_names(&modal, cx));
|
||||
attach_modal.update(cx, |modal, cx| attach_modal::_process_names(&modal, cx));
|
||||
|
||||
// we filtered out all processes that are not the current process(zed itself)
|
||||
assert_eq!(1, names.len());
|
||||
// we filtered out all processes that are not starting with `fake-binary`
|
||||
assert_eq!(2, names.len());
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ use dap::requests::StackTrace;
|
|||
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
||||
use project::{FakeFs, Project};
|
||||
use serde_json::json;
|
||||
use task::LaunchConfig;
|
||||
use tests::{init_test, init_test_workspace};
|
||||
|
||||
#[gpui::test]
|
||||
|
@ -29,8 +30,10 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp
|
|||
.unwrap();
|
||||
|
||||
let task = project.update(cx, |project, cx| {
|
||||
project.start_debug_session(
|
||||
dap::test_config(dap::DebugRequestType::Launch, None, None),
|
||||
project.fake_debug_session(
|
||||
dap::DebugRequestType::Launch(LaunchConfig::default()),
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
|
|
@ -5,8 +5,8 @@ use dap::{
|
|||
Continue, Disconnect, Launch, Next, RunInTerminal, SetBreakpoints, StackTrace,
|
||||
StartDebugging, StepBack, StepIn, StepOut, Threads,
|
||||
},
|
||||
DebugRequestType, ErrorResponse, RunInTerminalRequestArguments, SourceBreakpoint,
|
||||
StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest,
|
||||
ErrorResponse, RunInTerminalRequestArguments, SourceBreakpoint, StartDebuggingRequestArguments,
|
||||
StartDebuggingRequestArgumentsRequest,
|
||||
};
|
||||
use editor::{
|
||||
actions::{self},
|
||||
|
@ -25,6 +25,7 @@ use std::{
|
|||
Arc,
|
||||
},
|
||||
};
|
||||
use task::LaunchConfig;
|
||||
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
|
||||
use tests::{active_debug_session_panel, init_test, init_test_workspace};
|
||||
use util::path;
|
||||
|
@ -49,7 +50,12 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test
|
|||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let task = project.update(cx, |project, cx| {
|
||||
project.start_debug_session(dap::test_config(DebugRequestType::Launch, None, None), cx)
|
||||
project.fake_debug_session(
|
||||
dap::DebugRequestType::Launch(LaunchConfig::default()),
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let session = task.await.unwrap();
|
||||
|
@ -201,7 +207,12 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
|
|||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let task = project.update(cx, |project, cx| {
|
||||
project.start_debug_session(dap::test_config(DebugRequestType::Launch, None, None), cx)
|
||||
project.fake_debug_session(
|
||||
dap::DebugRequestType::Launch(LaunchConfig::default()),
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let session = task.await.unwrap();
|
||||
|
@ -385,7 +396,12 @@ async fn test_handle_successful_run_in_terminal_reverse_request(
|
|||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let task = project.update(cx, |project, cx| {
|
||||
project.start_debug_session(dap::test_config(DebugRequestType::Launch, None, None), cx)
|
||||
project.fake_debug_session(
|
||||
dap::DebugRequestType::Launch(LaunchConfig::default()),
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let session = task.await.unwrap();
|
||||
|
@ -475,7 +491,12 @@ async fn test_handle_error_run_in_terminal_reverse_request(
|
|||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let task = project.update(cx, |project, cx| {
|
||||
project.start_debug_session(dap::test_config(DebugRequestType::Launch, None, None), cx)
|
||||
project.fake_debug_session(
|
||||
dap::DebugRequestType::Launch(LaunchConfig::default()),
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let session = task.await.unwrap();
|
||||
|
@ -555,7 +576,12 @@ async fn test_handle_start_debugging_reverse_request(
|
|||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let task = project.update(cx, |project, cx| {
|
||||
project.start_debug_session(dap::test_config(DebugRequestType::Launch, None, None), cx)
|
||||
project.fake_debug_session(
|
||||
dap::DebugRequestType::Launch(LaunchConfig::default()),
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let session = task.await.unwrap();
|
||||
|
@ -668,7 +694,12 @@ async fn test_shutdown_children_when_parent_session_shutdown(
|
|||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let task = project.update(cx, |project, cx| {
|
||||
project.start_debug_session(dap::test_config(DebugRequestType::Launch, None, None), cx)
|
||||
project.fake_debug_session(
|
||||
dap::DebugRequestType::Launch(LaunchConfig::default()),
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let parent_session = task.await.unwrap();
|
||||
|
@ -776,7 +807,12 @@ async fn test_shutdown_parent_session_if_all_children_are_shutdown(
|
|||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let task = project.update(cx, |project, cx| {
|
||||
project.start_debug_session(dap::test_config(DebugRequestType::Launch, None, None), cx)
|
||||
project.fake_debug_session(
|
||||
dap::DebugRequestType::Launch(LaunchConfig::default()),
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let parent_session = task.await.unwrap();
|
||||
|
@ -891,15 +927,13 @@ async fn test_debug_panel_item_thread_status_reset_on_failure(
|
|||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let task = project.update(cx, |project, cx| {
|
||||
project.start_debug_session(
|
||||
dap::test_config(
|
||||
DebugRequestType::Launch,
|
||||
None,
|
||||
Some(dap::Capabilities {
|
||||
supports_step_back: Some(true),
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
project.fake_debug_session(
|
||||
dap::DebugRequestType::Launch(LaunchConfig::default()),
|
||||
Some(dap::Capabilities {
|
||||
supports_step_back: Some(true),
|
||||
..Default::default()
|
||||
}),
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
@ -1122,7 +1156,12 @@ async fn test_send_breakpoints_when_editor_has_been_saved(
|
|||
.unwrap();
|
||||
|
||||
let task = project.update(cx, |project, cx| {
|
||||
project.start_debug_session(dap::test_config(DebugRequestType::Launch, None, None), cx)
|
||||
project.fake_debug_session(
|
||||
dap::DebugRequestType::Launch(LaunchConfig::default()),
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let session = task.await.unwrap();
|
||||
|
@ -1347,7 +1386,12 @@ async fn test_unsetting_breakpoints_on_clear_breakpoint_action(
|
|||
});
|
||||
|
||||
let task = project.update(cx, |project, cx| {
|
||||
project.start_debug_session(dap::test_config(DebugRequestType::Launch, None, None), cx)
|
||||
project.fake_debug_session(
|
||||
dap::DebugRequestType::Launch(LaunchConfig::default()),
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let session = task.await.unwrap();
|
||||
|
@ -1419,8 +1463,10 @@ async fn test_debug_session_is_shutdown_when_attach_and_launch_request_fails(
|
|||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let task = project.update(cx, |project, cx| {
|
||||
project.start_debug_session(
|
||||
dap::test_config(DebugRequestType::Launch, Some(true), None),
|
||||
project.fake_debug_session(
|
||||
dap::DebugRequestType::Launch(LaunchConfig::default()),
|
||||
None,
|
||||
true,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
|||
};
|
||||
use dap::{
|
||||
requests::{Modules, StackTrace, Threads},
|
||||
DebugRequestType, StoppedEvent,
|
||||
StoppedEvent,
|
||||
};
|
||||
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
||||
use project::{FakeFs, Project};
|
||||
|
@ -13,6 +13,7 @@ use std::sync::{
|
|||
atomic::{AtomicBool, AtomicI32, Ordering},
|
||||
Arc,
|
||||
};
|
||||
use task::LaunchConfig;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_module_list(executor: BackgroundExecutor, cx: &mut TestAppContext) {
|
||||
|
@ -30,15 +31,13 @@ async fn test_module_list(executor: BackgroundExecutor, cx: &mut TestAppContext)
|
|||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let task = project.update(cx, |project, cx| {
|
||||
project.start_debug_session(
|
||||
dap::test_config(
|
||||
DebugRequestType::Launch,
|
||||
None,
|
||||
Some(dap::Capabilities {
|
||||
supports_modules_request: Some(true),
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
project.fake_debug_session(
|
||||
dap::DebugRequestType::Launch(LaunchConfig::default()),
|
||||
Some(dap::Capabilities {
|
||||
supports_modules_request: Some(true),
|
||||
..Default::default()
|
||||
}),
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
|
|
@ -12,6 +12,7 @@ use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
|||
use project::{FakeFs, Project};
|
||||
use serde_json::json;
|
||||
use std::sync::Arc;
|
||||
use task::LaunchConfig;
|
||||
use unindent::Unindent as _;
|
||||
use util::path;
|
||||
|
||||
|
@ -52,8 +53,10 @@ async fn test_fetch_initial_stack_frames_and_go_to_stack_frame(
|
|||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let task = project.update(cx, |project, cx| {
|
||||
project.start_debug_session(
|
||||
dap::test_config(dap::DebugRequestType::Launch, None, None),
|
||||
project.fake_debug_session(
|
||||
dap::DebugRequestType::Launch(LaunchConfig::default()),
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
@ -240,8 +243,10 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC
|
|||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let task = project.update(cx, |project, cx| {
|
||||
project.start_debug_session(
|
||||
dap::test_config(dap::DebugRequestType::Launch, None, None),
|
||||
project.fake_debug_session(
|
||||
dap::DebugRequestType::Launch(LaunchConfig::default()),
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
@ -513,8 +518,10 @@ async fn test_collapsed_entries(executor: BackgroundExecutor, cx: &mut TestAppCo
|
|||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let task = project.update(cx, |project, cx| {
|
||||
project.start_debug_session(
|
||||
dap::test_config(dap::DebugRequestType::Launch, None, None),
|
||||
project.fake_debug_session(
|
||||
dap::DebugRequestType::Launch(LaunchConfig::default()),
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
|
|
@ -17,6 +17,7 @@ use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
|||
use menu::{SelectFirst, SelectNext, SelectPrevious};
|
||||
use project::{FakeFs, Project};
|
||||
use serde_json::json;
|
||||
use task::LaunchConfig;
|
||||
use unindent::Unindent as _;
|
||||
use util::path;
|
||||
|
||||
|
@ -56,8 +57,10 @@ async fn test_basic_fetch_initial_scope_and_variables(
|
|||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let task = project.update(cx, |project, cx| {
|
||||
project.start_debug_session(
|
||||
dap::test_config(dap::DebugRequestType::Launch, None, None),
|
||||
project.fake_debug_session(
|
||||
dap::DebugRequestType::Launch(LaunchConfig::default()),
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
@ -283,8 +286,10 @@ async fn test_fetch_variables_for_multiple_scopes(
|
|||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let task = project.update(cx, |project, cx| {
|
||||
project.start_debug_session(
|
||||
dap::test_config(dap::DebugRequestType::Launch, None, None),
|
||||
project.fake_debug_session(
|
||||
dap::DebugRequestType::Launch(LaunchConfig::default()),
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
@ -562,8 +567,10 @@ async fn test_keyboard_navigation(executor: BackgroundExecutor, cx: &mut TestApp
|
|||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let task = project.update(cx, |project, cx| {
|
||||
project.start_debug_session(
|
||||
dap::test_config(dap::DebugRequestType::Launch, None, None),
|
||||
project.fake_debug_session(
|
||||
dap::DebugRequestType::Launch(LaunchConfig::default()),
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
@ -1362,8 +1369,10 @@ async fn test_variable_list_only_sends_requests_when_rendering(
|
|||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let task = project.update(cx, |project, cx| {
|
||||
project.start_debug_session(
|
||||
dap::test_config(dap::DebugRequestType::Launch, None, None),
|
||||
project.fake_debug_session(
|
||||
dap::DebugRequestType::Launch(LaunchConfig::default()),
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
@ -1639,8 +1648,10 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame(
|
|||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let task = project.update(cx, |project, cx| {
|
||||
project.start_debug_session(
|
||||
dap::test_config(dap::DebugRequestType::Launch, None, None),
|
||||
project.fake_debug_session(
|
||||
dap::DebugRequestType::Launch(LaunchConfig::default()),
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
|
|
@ -19,6 +19,7 @@ clap.workspace = true
|
|||
client.workspace = true
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
dap.workspace = true
|
||||
env_logger.workspace = true
|
||||
feature_flags.workspace = true
|
||||
fs.workspace = true
|
||||
|
|
|
@ -4,6 +4,7 @@ use clap::Parser;
|
|||
use client::{Client, UserStore};
|
||||
use clock::RealSystemClock;
|
||||
use collections::BTreeMap;
|
||||
use dap::DapRegistry;
|
||||
use feature_flags::FeatureFlagAppExt as _;
|
||||
use gpui::{AppContext as _, AsyncApp, BackgroundExecutor, Entity};
|
||||
use http_client::{HttpClient, Method};
|
||||
|
@ -302,6 +303,7 @@ async fn run_evaluation(
|
|||
));
|
||||
|
||||
let language_registry = Arc::new(LanguageRegistry::new(executor.clone()));
|
||||
let debug_adapters = Arc::new(DapRegistry::default());
|
||||
cx.update(|cx| languages::init(language_registry.clone(), node_runtime.clone(), cx))
|
||||
.unwrap();
|
||||
|
||||
|
@ -346,6 +348,7 @@ async fn run_evaluation(
|
|||
node_runtime.clone(),
|
||||
user_store.clone(),
|
||||
language_registry.clone(),
|
||||
debug_adapters.clone(),
|
||||
fs.clone(),
|
||||
None,
|
||||
cx,
|
||||
|
|
|
@ -37,7 +37,6 @@ client.workspace = true
|
|||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
dap.workspace = true
|
||||
dap_adapters.workspace = true
|
||||
extension.workspace = true
|
||||
fancy-regex.workspace = true
|
||||
fs.workspace = true
|
||||
|
|
|
@ -15,5 +15,3 @@ pub mod breakpoint_store;
|
|||
pub mod dap_command;
|
||||
pub mod dap_store;
|
||||
pub mod session;
|
||||
|
||||
pub use dap_adapters::attach_processes;
|
||||
|
|
|
@ -20,10 +20,9 @@ use dap::{
|
|||
Completions, Evaluate, Request as _, RunInTerminal, SetExpression, SetVariable,
|
||||
StartDebugging,
|
||||
},
|
||||
Capabilities, CompletionItem, CompletionsArguments, ErrorResponse, EvaluateArguments,
|
||||
EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments,
|
||||
Capabilities, CompletionItem, CompletionsArguments, DapRegistry, ErrorResponse,
|
||||
EvaluateArguments, EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments,
|
||||
SetExpressionArguments, SetVariableArguments, Source, StartDebuggingRequestArguments,
|
||||
StartDebuggingRequestArgumentsRequest,
|
||||
};
|
||||
use fs::Fs;
|
||||
use futures::{
|
||||
|
@ -51,7 +50,7 @@ use std::{
|
|||
sync::{atomic::Ordering::SeqCst, Arc},
|
||||
};
|
||||
use std::{collections::VecDeque, sync::atomic::AtomicU32};
|
||||
use task::{AttachConfig, DebugAdapterConfig, DebugRequestType};
|
||||
use task::{DebugAdapterConfig, DebugRequestDisposition};
|
||||
use util::ResultExt as _;
|
||||
use worktree::Worktree;
|
||||
|
||||
|
@ -89,6 +88,7 @@ pub struct LocalDapStore {
|
|||
worktree_store: Entity<WorktreeStore>,
|
||||
environment: Entity<ProjectEnvironment>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
debug_adapters: Arc<DapRegistry>,
|
||||
toolchain_store: Arc<dyn LanguageToolchainStore>,
|
||||
start_debugging_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
|
||||
_start_debugging_task: Task<()>,
|
||||
|
@ -138,6 +138,7 @@ impl DapStore {
|
|||
node_runtime: NodeRuntime,
|
||||
fs: Arc<dyn Fs>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
debug_adapters: Arc<DapRegistry>,
|
||||
environment: Entity<ProjectEnvironment>,
|
||||
toolchain_store: Arc<dyn LanguageToolchainStore>,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
|
@ -178,6 +179,7 @@ impl DapStore {
|
|||
worktree_store,
|
||||
toolchain_store,
|
||||
language_registry,
|
||||
debug_adapters,
|
||||
start_debugging_tx,
|
||||
_start_debugging_task,
|
||||
next_session_id: Default::default(),
|
||||
|
@ -364,52 +366,63 @@ impl DapStore {
|
|||
config,
|
||||
local_store.start_debugging_tx.clone(),
|
||||
initialized_tx,
|
||||
local_store.debug_adapters.clone(),
|
||||
cx,
|
||||
);
|
||||
|
||||
let task = cx.spawn(async move |this, cx| {
|
||||
let session = match start_client_task.await {
|
||||
Ok(session) => session,
|
||||
Err(error) => {
|
||||
this.update(cx, |_, cx| {
|
||||
cx.emit(DapStoreEvent::Notification(error.to_string()));
|
||||
})
|
||||
.log_err();
|
||||
let task = create_new_session(session_id, initialized_rx, start_client_task, cx);
|
||||
(session_id, task)
|
||||
}
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn new_fake_session(
|
||||
&mut self,
|
||||
config: DebugAdapterConfig,
|
||||
worktree: &Entity<Worktree>,
|
||||
parent_session: Option<Entity<Session>>,
|
||||
caps: Capabilities,
|
||||
fails: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> (SessionId, Task<Result<Entity<Session>>>) {
|
||||
let Some(local_store) = self.as_local() else {
|
||||
unimplemented!("Starting session on remote side");
|
||||
};
|
||||
|
||||
return Err(error);
|
||||
}
|
||||
};
|
||||
let delegate = DapAdapterDelegate::new(
|
||||
local_store.fs.clone(),
|
||||
worktree.read(cx).id(),
|
||||
local_store.node_runtime.clone(),
|
||||
local_store.http_client.clone(),
|
||||
local_store.language_registry.clone(),
|
||||
local_store.toolchain_store.clone(),
|
||||
local_store.environment.update(cx, |env, cx| {
|
||||
let worktree = worktree.read(cx);
|
||||
env.get_environment(Some(worktree.id()), Some(worktree.abs_path()), cx)
|
||||
}),
|
||||
);
|
||||
let session_id = local_store.next_session_id();
|
||||
|
||||
// we have to insert the session early, so we can handle reverse requests
|
||||
// that need the session to be available
|
||||
this.update(cx, |store, cx| {
|
||||
store.sessions.insert(session_id, session.clone());
|
||||
cx.emit(DapStoreEvent::DebugClientStarted(session_id));
|
||||
cx.notify();
|
||||
})?;
|
||||
if let Some(session) = &parent_session {
|
||||
session.update(cx, |session, _| {
|
||||
session.add_child_session_id(session_id);
|
||||
});
|
||||
}
|
||||
|
||||
match session
|
||||
.update(cx, |session, cx| {
|
||||
session.initialize_sequence(initialized_rx, cx)
|
||||
})?
|
||||
.await
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
this.update(cx, |this, cx| {
|
||||
cx.emit(DapStoreEvent::Notification(error.to_string()));
|
||||
let (initialized_tx, initialized_rx) = oneshot::channel();
|
||||
|
||||
this.shutdown_session(session_id, cx)
|
||||
})?
|
||||
.await
|
||||
.log_err();
|
||||
let start_client_task = Session::fake(
|
||||
self.breakpoint_store.clone(),
|
||||
session_id,
|
||||
parent_session,
|
||||
delegate,
|
||||
config,
|
||||
local_store.start_debugging_tx.clone(),
|
||||
initialized_tx,
|
||||
caps,
|
||||
fails,
|
||||
cx,
|
||||
);
|
||||
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(session)
|
||||
});
|
||||
let task = create_new_session(session_id, initialized_rx, start_client_task, cx);
|
||||
(session_id, task)
|
||||
}
|
||||
|
||||
|
@ -431,7 +444,6 @@ impl DapStore {
|
|||
request.arguments.unwrap_or_default(),
|
||||
)
|
||||
.expect("To parse StartDebuggingRequestArguments");
|
||||
|
||||
let worktree = local_store
|
||||
.worktree_store
|
||||
.update(cx, |this, _| this.worktrees().next())
|
||||
|
@ -441,25 +453,30 @@ impl DapStore {
|
|||
unreachable!("there must be a config for local sessions");
|
||||
};
|
||||
|
||||
let (_, new_session_task) = self.new_session(
|
||||
DebugAdapterConfig {
|
||||
label: config.label,
|
||||
kind: config.kind,
|
||||
request: match &args.request {
|
||||
StartDebuggingRequestArgumentsRequest::Launch => DebugRequestType::Launch,
|
||||
StartDebuggingRequestArgumentsRequest::Attach => {
|
||||
DebugRequestType::Attach(AttachConfig::default())
|
||||
}
|
||||
},
|
||||
program: config.program,
|
||||
cwd: config.cwd,
|
||||
initialize_args: Some(args.configuration),
|
||||
supports_attach: config.supports_attach,
|
||||
},
|
||||
&worktree,
|
||||
Some(parent_session.clone()),
|
||||
cx,
|
||||
);
|
||||
let debug_config = DebugAdapterConfig {
|
||||
label: config.label,
|
||||
adapter: config.adapter,
|
||||
request: DebugRequestDisposition::ReverseRequest(args),
|
||||
initialize_args: config.initialize_args.clone(),
|
||||
tcp_connection: config.tcp_connection.clone(),
|
||||
};
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
let new_session_task = {
|
||||
let caps = parent_session.read(cx).capabilities.clone();
|
||||
self.new_fake_session(
|
||||
debug_config,
|
||||
&worktree,
|
||||
Some(parent_session.clone()),
|
||||
caps,
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
.1
|
||||
};
|
||||
#[cfg(not(any(test, feature = "test-support")))]
|
||||
let new_session_task = self
|
||||
.new_session(debug_config, &worktree, Some(parent_session.clone()), cx)
|
||||
.1;
|
||||
|
||||
let request_seq = request.seq;
|
||||
cx.spawn(async move |_, cx| {
|
||||
|
@ -830,6 +847,58 @@ impl DapStore {
|
|||
}
|
||||
}
|
||||
|
||||
fn create_new_session(
|
||||
session_id: SessionId,
|
||||
initialized_rx: oneshot::Receiver<()>,
|
||||
start_client_task: Task<Result<Entity<Session>, anyhow::Error>>,
|
||||
cx: &mut Context<'_, DapStore>,
|
||||
) -> Task<Result<Entity<Session>>> {
|
||||
let task = cx.spawn(async move |this, cx| {
|
||||
let session = match start_client_task.await {
|
||||
Ok(session) => session,
|
||||
Err(error) => {
|
||||
this.update(cx, |_, cx| {
|
||||
cx.emit(DapStoreEvent::Notification(error.to_string()));
|
||||
})
|
||||
.log_err();
|
||||
|
||||
return Err(error);
|
||||
}
|
||||
};
|
||||
|
||||
// we have to insert the session early, so we can handle reverse requests
|
||||
// that need the session to be available
|
||||
this.update(cx, |store, cx| {
|
||||
store.sessions.insert(session_id, session.clone());
|
||||
cx.emit(DapStoreEvent::DebugClientStarted(session_id));
|
||||
cx.notify();
|
||||
})?;
|
||||
|
||||
match session
|
||||
.update(cx, |session, cx| {
|
||||
session.initialize_sequence(initialized_rx, cx)
|
||||
})?
|
||||
.await
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
this.update(cx, |this, cx| {
|
||||
cx.emit(DapStoreEvent::Notification(error.to_string()));
|
||||
|
||||
this.shutdown_session(session_id, cx)
|
||||
})?
|
||||
.await
|
||||
.log_err();
|
||||
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(session)
|
||||
});
|
||||
task
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DapAdapterDelegate {
|
||||
fs: Arc<dyn Fs>,
|
||||
|
|
|
@ -14,7 +14,6 @@ use anyhow::{anyhow, Result};
|
|||
use collections::{HashMap, HashSet, IndexMap, IndexSet};
|
||||
use dap::adapters::{DebugAdapter, DebugAdapterBinary};
|
||||
use dap::messages::Response;
|
||||
use dap::OutputEventCategory;
|
||||
use dap::{
|
||||
adapters::{DapDelegate, DapStatus},
|
||||
client::{DebugAdapterClient, SessionId},
|
||||
|
@ -22,7 +21,7 @@ use dap::{
|
|||
Capabilities, ContinueArguments, EvaluateArgumentsContext, Module, Source, StackFrameId,
|
||||
SteppingGranularity, StoppedEvent, VariableReference,
|
||||
};
|
||||
use dap_adapters::build_adapter;
|
||||
use dap::{DapRegistry, DebugRequestType, OutputEventCategory};
|
||||
use futures::channel::oneshot;
|
||||
use futures::{future::Shared, FutureExt};
|
||||
use gpui::{
|
||||
|
@ -42,7 +41,7 @@ use std::{
|
|||
path::Path,
|
||||
sync::Arc,
|
||||
};
|
||||
use task::DebugAdapterConfig;
|
||||
use task::{DebugAdapterConfig, DebugTaskDefinition};
|
||||
use text::{PointUtf16, ToPointUtf16};
|
||||
use util::{merge_json_value_into, ResultExt};
|
||||
|
||||
|
@ -183,6 +182,7 @@ fn client_source(abs_path: &Path) -> dap::Source {
|
|||
|
||||
impl LocalMode {
|
||||
fn new(
|
||||
debug_adapters: Arc<DapRegistry>,
|
||||
session_id: SessionId,
|
||||
parent_session: Option<Entity<Session>>,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
|
@ -190,9 +190,168 @@ impl LocalMode {
|
|||
delegate: DapAdapterDelegate,
|
||||
messages_tx: futures::channel::mpsc::UnboundedSender<Message>,
|
||||
cx: AsyncApp,
|
||||
) -> Task<Result<(Self, Capabilities)>> {
|
||||
Self::new_inner(
|
||||
debug_adapters,
|
||||
session_id,
|
||||
parent_session,
|
||||
breakpoint_store,
|
||||
config,
|
||||
delegate,
|
||||
messages_tx,
|
||||
async |_, _| {},
|
||||
cx,
|
||||
)
|
||||
}
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fn new_fake(
|
||||
session_id: SessionId,
|
||||
parent_session: Option<Entity<Session>>,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
config: DebugAdapterConfig,
|
||||
delegate: DapAdapterDelegate,
|
||||
messages_tx: futures::channel::mpsc::UnboundedSender<Message>,
|
||||
caps: Capabilities,
|
||||
fail: bool,
|
||||
cx: AsyncApp,
|
||||
) -> Task<Result<(Self, Capabilities)>> {
|
||||
use task::DebugRequestDisposition;
|
||||
|
||||
let request = match config.request.clone() {
|
||||
DebugRequestDisposition::UserConfigured(request) => request,
|
||||
DebugRequestDisposition::ReverseRequest(reverse_request_args) => {
|
||||
match reverse_request_args.request {
|
||||
dap::StartDebuggingRequestArgumentsRequest::Launch => {
|
||||
DebugRequestType::Launch(task::LaunchConfig {
|
||||
program: "".to_owned(),
|
||||
cwd: None,
|
||||
})
|
||||
}
|
||||
dap::StartDebuggingRequestArgumentsRequest::Attach => {
|
||||
DebugRequestType::Attach(task::AttachConfig {
|
||||
process_id: Some(0),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let callback = async move |session: &mut LocalMode, cx: AsyncApp| {
|
||||
session
|
||||
.client
|
||||
.on_request::<dap::requests::Initialize, _>(move |_, _| Ok(caps.clone()))
|
||||
.await;
|
||||
|
||||
let paths = cx
|
||||
.update(|cx| session.breakpoint_store.read(cx).breakpoint_paths())
|
||||
.expect("Breakpoint store should exist in all tests that start debuggers");
|
||||
|
||||
session
|
||||
.client
|
||||
.on_request::<dap::requests::SetBreakpoints, _>(move |_, args| {
|
||||
let p = Arc::from(Path::new(&args.source.path.unwrap()));
|
||||
if !paths.contains(&p) {
|
||||
panic!("Sent breakpoints for path without any")
|
||||
}
|
||||
|
||||
Ok(dap::SetBreakpointsResponse {
|
||||
breakpoints: Vec::default(),
|
||||
})
|
||||
})
|
||||
.await;
|
||||
|
||||
match request {
|
||||
dap::DebugRequestType::Launch(_) => {
|
||||
if fail {
|
||||
session
|
||||
.client
|
||||
.on_request::<dap::requests::Launch, _>(move |_, _| {
|
||||
Err(dap::ErrorResponse {
|
||||
error: Some(dap::Message {
|
||||
id: 1,
|
||||
format: "error".into(),
|
||||
variables: None,
|
||||
send_telemetry: None,
|
||||
show_user: None,
|
||||
url: None,
|
||||
url_label: None,
|
||||
}),
|
||||
})
|
||||
})
|
||||
.await;
|
||||
} else {
|
||||
session
|
||||
.client
|
||||
.on_request::<dap::requests::Launch, _>(move |_, _| Ok(()))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
dap::DebugRequestType::Attach(attach_config) => {
|
||||
if fail {
|
||||
session
|
||||
.client
|
||||
.on_request::<dap::requests::Attach, _>(move |_, _| {
|
||||
Err(dap::ErrorResponse {
|
||||
error: Some(dap::Message {
|
||||
id: 1,
|
||||
format: "error".into(),
|
||||
variables: None,
|
||||
send_telemetry: None,
|
||||
show_user: None,
|
||||
url: None,
|
||||
url_label: None,
|
||||
}),
|
||||
})
|
||||
})
|
||||
.await;
|
||||
} else {
|
||||
session
|
||||
.client
|
||||
.on_request::<dap::requests::Attach, _>(move |_, args| {
|
||||
assert_eq!(
|
||||
json!({"request": "attach", "process_id": attach_config.process_id.unwrap()}),
|
||||
args.raw
|
||||
);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
session
|
||||
.client
|
||||
.on_request::<dap::requests::Disconnect, _>(move |_, _| Ok(()))
|
||||
.await;
|
||||
session.client.fake_event(Events::Initialized(None)).await;
|
||||
};
|
||||
Self::new_inner(
|
||||
DapRegistry::fake().into(),
|
||||
session_id,
|
||||
parent_session,
|
||||
breakpoint_store,
|
||||
config,
|
||||
delegate,
|
||||
messages_tx,
|
||||
callback,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
fn new_inner(
|
||||
registry: Arc<DapRegistry>,
|
||||
session_id: SessionId,
|
||||
parent_session: Option<Entity<Session>>,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
config: DebugAdapterConfig,
|
||||
delegate: DapAdapterDelegate,
|
||||
messages_tx: futures::channel::mpsc::UnboundedSender<Message>,
|
||||
on_initialized: impl AsyncFnOnce(&mut LocalMode, AsyncApp) + 'static,
|
||||
cx: AsyncApp,
|
||||
) -> Task<Result<(Self, Capabilities)>> {
|
||||
cx.spawn(async move |cx| {
|
||||
let (adapter, binary) = Self::get_adapter_binary(&config, &delegate, cx).await?;
|
||||
let (adapter, binary) =
|
||||
Self::get_adapter_binary(®istry, &config, &delegate, cx).await?;
|
||||
|
||||
let message_handler = Box::new(move |message| {
|
||||
messages_tx.unbounded_send(message).ok();
|
||||
|
@ -219,99 +378,14 @@ impl LocalMode {
|
|||
);
|
||||
|
||||
let adapter_id = adapter.name().to_string().to_owned();
|
||||
let session = Self {
|
||||
let mut session = Self {
|
||||
client,
|
||||
adapter,
|
||||
breakpoint_store,
|
||||
config: config.clone(),
|
||||
};
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
{
|
||||
let dap::DebugAdapterKind::Fake((fail, caps)) = session.config.kind.clone() else {
|
||||
panic!("Only fake debug adapter configs should be used in tests");
|
||||
};
|
||||
|
||||
session
|
||||
.client
|
||||
.on_request::<dap::requests::Initialize, _>(move |_, _| Ok(caps.clone()))
|
||||
.await;
|
||||
|
||||
let paths = cx.update(|cx| session.breakpoint_store.read(cx).breakpoint_paths()).expect("Breakpoint store should exist in all tests that start debuggers");
|
||||
|
||||
session.client.on_request::<dap::requests::SetBreakpoints, _>(move |_, args| {
|
||||
let p = Arc::from(Path::new(&args.source.path.unwrap()));
|
||||
if !paths.contains(&p) {
|
||||
panic!("Sent breakpoints for path without any")
|
||||
}
|
||||
|
||||
Ok(dap::SetBreakpointsResponse {
|
||||
breakpoints: Vec::default(),
|
||||
})
|
||||
}).await;
|
||||
|
||||
match config.request.clone() {
|
||||
dap::DebugRequestType::Launch if fail => {
|
||||
session
|
||||
.client
|
||||
.on_request::<dap::requests::Launch, _>(move |_, _| {
|
||||
Err(dap::ErrorResponse {
|
||||
error: Some(dap::Message {
|
||||
id: 1,
|
||||
format: "error".into(),
|
||||
variables: None,
|
||||
send_telemetry: None,
|
||||
show_user: None,
|
||||
url: None,
|
||||
url_label: None,
|
||||
}),
|
||||
})
|
||||
})
|
||||
.await;
|
||||
}
|
||||
dap::DebugRequestType::Launch => {
|
||||
session
|
||||
.client
|
||||
.on_request::<dap::requests::Launch, _>(move |_, _| Ok(()))
|
||||
.await;
|
||||
}
|
||||
dap::DebugRequestType::Attach(_) if fail => {
|
||||
session
|
||||
.client
|
||||
.on_request::<dap::requests::Attach, _>(move |_, _| {
|
||||
Err(dap::ErrorResponse {
|
||||
error: Some(dap::Message {
|
||||
id: 1,
|
||||
format: "error".into(),
|
||||
variables: None,
|
||||
send_telemetry: None,
|
||||
show_user: None,
|
||||
url: None,
|
||||
url_label: None,
|
||||
}),
|
||||
})
|
||||
})
|
||||
.await;
|
||||
}
|
||||
dap::DebugRequestType::Attach(attach_config) => {
|
||||
session
|
||||
.client
|
||||
.on_request::<dap::requests::Attach, _>(move |_, args| {
|
||||
assert_eq!(
|
||||
json!({"request": "attach", "process_id": attach_config.process_id.unwrap()}),
|
||||
args.raw
|
||||
);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
session.client.on_request::<dap::requests::Disconnect, _>(move |_, _| Ok(())).await;
|
||||
session.client.fake_event(Events::Initialized(None)).await;
|
||||
}
|
||||
|
||||
on_initialized(&mut session, cx.clone()).await;
|
||||
let capabilities = session
|
||||
.request(Initialize { adapter_id }, cx.background_executor().clone())
|
||||
.await?;
|
||||
|
@ -420,11 +494,14 @@ impl LocalMode {
|
|||
}
|
||||
|
||||
async fn get_adapter_binary(
|
||||
registry: &Arc<DapRegistry>,
|
||||
config: &DebugAdapterConfig,
|
||||
delegate: &DapAdapterDelegate,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<(Arc<dyn DebugAdapter>, DebugAdapterBinary)> {
|
||||
let adapter = build_adapter(&config.kind).await?;
|
||||
let adapter = registry
|
||||
.adapter(&config.adapter)
|
||||
.ok_or_else(|| anyhow!("Debug adapter with name `{}` was not found", config.adapter))?;
|
||||
|
||||
let binary = cx.update(|cx| {
|
||||
ProjectSettings::get_global(cx)
|
||||
|
@ -465,20 +542,36 @@ impl LocalMode {
|
|||
initialized_rx: oneshot::Receiver<()>,
|
||||
cx: &App,
|
||||
) -> Task<Result<()>> {
|
||||
let mut raw = self.adapter.request_args(&self.config);
|
||||
let (mut raw, is_launch) = match &self.config.request {
|
||||
task::DebugRequestDisposition::UserConfigured(_) => {
|
||||
let Ok(raw) = DebugTaskDefinition::try_from(self.config.clone()) else {
|
||||
debug_assert!(false, "This part of code should be unreachable in practice");
|
||||
return Task::ready(Err(anyhow!(
|
||||
"Expected debug config conversion to succeed"
|
||||
)));
|
||||
};
|
||||
let is_launch = matches!(raw.request, DebugRequestType::Launch(_));
|
||||
let raw = self.adapter.request_args(&raw);
|
||||
(raw, is_launch)
|
||||
}
|
||||
task::DebugRequestDisposition::ReverseRequest(start_debugging_request_arguments) => (
|
||||
start_debugging_request_arguments.configuration.clone(),
|
||||
matches!(
|
||||
start_debugging_request_arguments.request,
|
||||
dap::StartDebuggingRequestArgumentsRequest::Launch
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
merge_json_value_into(
|
||||
self.config.initialize_args.clone().unwrap_or(json!({})),
|
||||
&mut raw,
|
||||
);
|
||||
|
||||
// Of relevance: https://github.com/microsoft/vscode/issues/4902#issuecomment-368583522
|
||||
let launch = match &self.config.request {
|
||||
dap::DebugRequestType::Launch => {
|
||||
self.request(Launch { raw }, cx.background_executor().clone())
|
||||
}
|
||||
dap::DebugRequestType::Attach(_) => {
|
||||
self.request(Attach { raw }, cx.background_executor().clone())
|
||||
}
|
||||
let launch = if is_launch {
|
||||
self.request(Launch { raw }, cx.background_executor().clone())
|
||||
} else {
|
||||
self.request(Attach { raw }, cx.background_executor().clone())
|
||||
};
|
||||
|
||||
let configuration_done_supported = ConfigurationDone::is_supported(capabilities);
|
||||
|
@ -745,12 +838,14 @@ impl Session {
|
|||
config: DebugAdapterConfig,
|
||||
start_debugging_requests_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
|
||||
initialized_tx: oneshot::Sender<()>,
|
||||
debug_adapters: Arc<DapRegistry>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<Entity<Self>>> {
|
||||
let (message_tx, mut message_rx) = futures::channel::mpsc::unbounded();
|
||||
let (message_tx, message_rx) = futures::channel::mpsc::unbounded();
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let (mode, capabilities) = LocalMode::new(
|
||||
debug_adapters,
|
||||
session_id,
|
||||
parent_session.clone(),
|
||||
breakpoint_store.clone(),
|
||||
|
@ -762,74 +857,62 @@ impl Session {
|
|||
.await?;
|
||||
|
||||
cx.new(|cx| {
|
||||
let _background_tasks = vec![cx.spawn(async move |this: WeakEntity<Self>, cx| {
|
||||
let mut initialized_tx = Some(initialized_tx);
|
||||
while let Some(message) = message_rx.next().await {
|
||||
if let Message::Event(event) = message {
|
||||
if let Events::Initialized(_) = *event {
|
||||
if let Some(tx) = initialized_tx.take() {
|
||||
tx.send(()).ok();
|
||||
}
|
||||
} else {
|
||||
let Ok(_) = this.update(cx, |session, cx| {
|
||||
session.handle_dap_event(event, cx);
|
||||
}) else {
|
||||
break;
|
||||
};
|
||||
}
|
||||
} else {
|
||||
let Ok(_) =
|
||||
start_debugging_requests_tx.unbounded_send((session_id, message))
|
||||
else {
|
||||
break;
|
||||
};
|
||||
}
|
||||
}
|
||||
})];
|
||||
|
||||
cx.subscribe(&breakpoint_store, |this, _, event, cx| match event {
|
||||
BreakpointStoreEvent::BreakpointsUpdated(path, reason) => {
|
||||
if let Some(local) = (!this.ignore_breakpoints)
|
||||
.then(|| this.as_local_mut())
|
||||
.flatten()
|
||||
{
|
||||
local
|
||||
.send_breakpoints_from_path(path.clone(), *reason, cx)
|
||||
.detach();
|
||||
};
|
||||
}
|
||||
BreakpointStoreEvent::BreakpointsCleared(paths) => {
|
||||
if let Some(local) = (!this.ignore_breakpoints)
|
||||
.then(|| this.as_local_mut())
|
||||
.flatten()
|
||||
{
|
||||
local.unset_breakpoints_from_paths(paths, cx).detach();
|
||||
}
|
||||
}
|
||||
BreakpointStoreEvent::ActiveDebugLineChanged => {}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Self {
|
||||
mode: Mode::Local(mode),
|
||||
id: session_id,
|
||||
child_session_ids: HashSet::default(),
|
||||
parent_id: parent_session.map(|session| session.read(cx).id),
|
||||
variables: Default::default(),
|
||||
create_local_session(
|
||||
breakpoint_store,
|
||||
session_id,
|
||||
parent_session,
|
||||
start_debugging_requests_tx,
|
||||
initialized_tx,
|
||||
message_rx,
|
||||
mode,
|
||||
capabilities,
|
||||
thread_states: ThreadStates::default(),
|
||||
output_token: OutputToken(0),
|
||||
ignore_breakpoints: false,
|
||||
output: circular_buffer::CircularBuffer::boxed(),
|
||||
requests: HashMap::default(),
|
||||
modules: Vec::default(),
|
||||
loaded_sources: Vec::default(),
|
||||
threads: IndexMap::default(),
|
||||
stack_frames: IndexMap::default(),
|
||||
locations: Default::default(),
|
||||
_background_tasks,
|
||||
is_session_terminated: false,
|
||||
}
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub(crate) fn fake(
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
session_id: SessionId,
|
||||
parent_session: Option<Entity<Session>>,
|
||||
delegate: DapAdapterDelegate,
|
||||
config: DebugAdapterConfig,
|
||||
start_debugging_requests_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
|
||||
initialized_tx: oneshot::Sender<()>,
|
||||
caps: Capabilities,
|
||||
fails: bool,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<Entity<Session>>> {
|
||||
let (message_tx, message_rx) = futures::channel::mpsc::unbounded();
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let (mode, capabilities) = LocalMode::new_fake(
|
||||
session_id,
|
||||
parent_session.clone(),
|
||||
breakpoint_store.clone(),
|
||||
config.clone(),
|
||||
delegate,
|
||||
message_tx,
|
||||
caps,
|
||||
fails,
|
||||
cx.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
cx.new(|cx| {
|
||||
create_local_session(
|
||||
breakpoint_store,
|
||||
session_id,
|
||||
parent_session,
|
||||
start_debugging_requests_tx,
|
||||
initialized_tx,
|
||||
message_rx,
|
||||
mode,
|
||||
capabilities,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -1838,3 +1921,83 @@ impl Session {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_local_session(
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
session_id: SessionId,
|
||||
parent_session: Option<Entity<Session>>,
|
||||
start_debugging_requests_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
|
||||
initialized_tx: oneshot::Sender<()>,
|
||||
mut message_rx: futures::channel::mpsc::UnboundedReceiver<Message>,
|
||||
mode: LocalMode,
|
||||
capabilities: Capabilities,
|
||||
cx: &mut Context<'_, Session>,
|
||||
) -> Session {
|
||||
let _background_tasks = vec![cx.spawn(async move |this: WeakEntity<Session>, cx| {
|
||||
let mut initialized_tx = Some(initialized_tx);
|
||||
while let Some(message) = message_rx.next().await {
|
||||
if let Message::Event(event) = message {
|
||||
if let Events::Initialized(_) = *event {
|
||||
if let Some(tx) = initialized_tx.take() {
|
||||
tx.send(()).ok();
|
||||
}
|
||||
} else {
|
||||
let Ok(_) = this.update(cx, |session, cx| {
|
||||
session.handle_dap_event(event, cx);
|
||||
}) else {
|
||||
break;
|
||||
};
|
||||
}
|
||||
} else {
|
||||
let Ok(_) = start_debugging_requests_tx.unbounded_send((session_id, message))
|
||||
else {
|
||||
break;
|
||||
};
|
||||
}
|
||||
}
|
||||
})];
|
||||
|
||||
cx.subscribe(&breakpoint_store, |this, _, event, cx| match event {
|
||||
BreakpointStoreEvent::BreakpointsUpdated(path, reason) => {
|
||||
if let Some(local) = (!this.ignore_breakpoints)
|
||||
.then(|| this.as_local_mut())
|
||||
.flatten()
|
||||
{
|
||||
local
|
||||
.send_breakpoints_from_path(path.clone(), *reason, cx)
|
||||
.detach();
|
||||
};
|
||||
}
|
||||
BreakpointStoreEvent::BreakpointsCleared(paths) => {
|
||||
if let Some(local) = (!this.ignore_breakpoints)
|
||||
.then(|| this.as_local_mut())
|
||||
.flatten()
|
||||
{
|
||||
local.unset_breakpoints_from_paths(paths, cx).detach();
|
||||
}
|
||||
}
|
||||
BreakpointStoreEvent::ActiveDebugLineChanged => {}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Session {
|
||||
mode: Mode::Local(mode),
|
||||
id: session_id,
|
||||
child_session_ids: HashSet::default(),
|
||||
parent_id: parent_session.map(|session| session.read(cx).id),
|
||||
variables: Default::default(),
|
||||
capabilities,
|
||||
thread_states: ThreadStates::default(),
|
||||
output_token: OutputToken(0),
|
||||
ignore_breakpoints: false,
|
||||
output: circular_buffer::CircularBuffer::boxed(),
|
||||
requests: HashMap::default(),
|
||||
modules: Vec::default(),
|
||||
loaded_sources: Vec::default(),
|
||||
threads: IndexMap::default(),
|
||||
stack_frames: IndexMap::default(),
|
||||
locations: Default::default(),
|
||||
_background_tasks,
|
||||
is_session_terminated: false,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ use client::{
|
|||
};
|
||||
use clock::ReplicaId;
|
||||
|
||||
use dap::{client::DebugAdapterClient, DebugAdapterConfig};
|
||||
use dap::{client::DebugAdapterClient, DapRegistry, DebugAdapterConfig};
|
||||
|
||||
use collections::{BTreeSet, HashMap, HashSet};
|
||||
use debounced_delay::DebouncedDelay;
|
||||
|
@ -163,6 +163,7 @@ pub struct Project {
|
|||
active_entry: Option<ProjectEntryId>,
|
||||
buffer_ordered_messages_tx: mpsc::UnboundedSender<BufferOrderedMessage>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
debug_adapters: Arc<DapRegistry>,
|
||||
dap_store: Entity<DapStore>,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
client: Arc<client::Client>,
|
||||
|
@ -818,6 +819,7 @@ impl Project {
|
|||
node: NodeRuntime,
|
||||
user_store: Entity<UserStore>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
debug_adapters: Arc<DapRegistry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
env: Option<HashMap<String, String>>,
|
||||
cx: &mut App,
|
||||
|
@ -854,6 +856,7 @@ impl Project {
|
|||
node.clone(),
|
||||
fs.clone(),
|
||||
languages.clone(),
|
||||
debug_adapters.clone(),
|
||||
environment.clone(),
|
||||
toolchain_store.read(cx).as_language_toolchain_store(),
|
||||
breakpoint_store.clone(),
|
||||
|
@ -940,6 +943,7 @@ impl Project {
|
|||
active_entry: None,
|
||||
snippets,
|
||||
languages,
|
||||
debug_adapters,
|
||||
client,
|
||||
task_store,
|
||||
user_store,
|
||||
|
@ -1102,6 +1106,7 @@ impl Project {
|
|||
active_entry: None,
|
||||
snippets,
|
||||
languages,
|
||||
debug_adapters: Arc::new(DapRegistry::default()),
|
||||
client,
|
||||
task_store,
|
||||
user_store,
|
||||
|
@ -1239,7 +1244,6 @@ impl Project {
|
|||
|
||||
let breakpoint_store =
|
||||
cx.new(|_| BreakpointStore::remote(remote_id, client.clone().into()))?;
|
||||
|
||||
let dap_store = cx.new(|_cx| {
|
||||
DapStore::new_remote(remote_id, client.clone().into(), breakpoint_store.clone())
|
||||
})?;
|
||||
|
@ -1326,6 +1330,7 @@ impl Project {
|
|||
collaborators: Default::default(),
|
||||
join_project_response_message_id: response.message_id,
|
||||
languages,
|
||||
debug_adapters: Arc::new(DapRegistry::default()),
|
||||
user_store: user_store.clone(),
|
||||
task_store,
|
||||
snippets,
|
||||
|
@ -1450,13 +1455,7 @@ impl Project {
|
|||
config: DebugAdapterConfig,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<Session>>> {
|
||||
let worktree = maybe!({
|
||||
if let Some(cwd) = &config.cwd {
|
||||
Some(self.find_worktree(cwd.as_path(), cx)?.0)
|
||||
} else {
|
||||
self.worktrees(cx).next()
|
||||
}
|
||||
});
|
||||
let worktree = maybe!({ self.worktrees(cx).next() });
|
||||
|
||||
let Some(worktree) = &worktree else {
|
||||
return Task::ready(Err(anyhow!("Failed to find a worktree")));
|
||||
|
@ -1469,6 +1468,40 @@ impl Project {
|
|||
.1
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn fake_debug_session(
|
||||
&mut self,
|
||||
request: task::DebugRequestType,
|
||||
caps: Option<dap::Capabilities>,
|
||||
fails: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<Session>>> {
|
||||
use dap::{Capabilities, FakeAdapter};
|
||||
use task::DebugRequestDisposition;
|
||||
|
||||
let worktree = maybe!({ self.worktrees(cx).next() });
|
||||
|
||||
let Some(worktree) = &worktree else {
|
||||
return Task::ready(Err(anyhow!("Failed to find a worktree")));
|
||||
};
|
||||
let config = DebugAdapterConfig {
|
||||
label: "test config".into(),
|
||||
adapter: FakeAdapter::ADAPTER_NAME.into(),
|
||||
request: DebugRequestDisposition::UserConfigured(request),
|
||||
initialize_args: None,
|
||||
tcp_connection: None,
|
||||
};
|
||||
let caps = caps.unwrap_or(Capabilities {
|
||||
supports_step_back: Some(false),
|
||||
..Default::default()
|
||||
});
|
||||
self.dap_store
|
||||
.update(cx, |dap_store, cx| {
|
||||
dap_store.new_fake_session(config, worktree, None, caps, fails, cx)
|
||||
})
|
||||
.1
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub async fn example(
|
||||
root_paths: impl IntoIterator<Item = &Path>,
|
||||
|
@ -1478,6 +1511,7 @@ impl Project {
|
|||
|
||||
let fs = Arc::new(RealFs::new(None, cx.background_executor().clone()));
|
||||
let languages = LanguageRegistry::test(cx.background_executor().clone());
|
||||
let debug_adapters = DapRegistry::default().into();
|
||||
let clock = Arc::new(FakeSystemClock::new());
|
||||
let http_client = http_client::FakeHttpClient::with_404_response();
|
||||
let client = cx
|
||||
|
@ -1491,6 +1525,7 @@ impl Project {
|
|||
node_runtime::NodeRuntime::unavailable(),
|
||||
user_store,
|
||||
Arc::new(languages),
|
||||
debug_adapters,
|
||||
fs,
|
||||
None,
|
||||
cx,
|
||||
|
@ -1521,6 +1556,7 @@ impl Project {
|
|||
use clock::FakeSystemClock;
|
||||
|
||||
let languages = LanguageRegistry::test(cx.executor());
|
||||
let debug_adapters = DapRegistry::fake();
|
||||
let clock = Arc::new(FakeSystemClock::new());
|
||||
let http_client = http_client::FakeHttpClient::with_404_response();
|
||||
let client = cx.update(|cx| client::Client::new(clock, http_client.clone(), cx));
|
||||
|
@ -1531,6 +1567,7 @@ impl Project {
|
|||
node_runtime::NodeRuntime::unavailable(),
|
||||
user_store,
|
||||
Arc::new(languages),
|
||||
Arc::new(debug_adapters),
|
||||
fs,
|
||||
None,
|
||||
cx,
|
||||
|
@ -1574,6 +1611,10 @@ impl Project {
|
|||
&self.languages
|
||||
}
|
||||
|
||||
pub fn debug_adapters(&self) -> &Arc<DapRegistry> {
|
||||
&self.debug_adapters
|
||||
}
|
||||
|
||||
pub fn client(&self) -> Arc<Client> {
|
||||
self.client.clone()
|
||||
}
|
||||
|
|
|
@ -557,6 +557,7 @@ pub async fn open_ssh_project(
|
|||
app_state.node_runtime.clone(),
|
||||
app_state.user_store.clone(),
|
||||
app_state.languages.clone(),
|
||||
app_state.debug_adapters.clone(),
|
||||
app_state.fs.clone(),
|
||||
None,
|
||||
cx,
|
||||
|
|
|
@ -28,6 +28,7 @@ backtrace = "0.3"
|
|||
chrono.workspace = true
|
||||
clap.workspace = true
|
||||
client.workspace = true
|
||||
dap.workspace = true
|
||||
env_logger.workspace = true
|
||||
extension.workspace = true
|
||||
extension_host.workspace = true
|
||||
|
@ -69,6 +70,7 @@ libc.workspace = true
|
|||
[dev-dependencies]
|
||||
client = { workspace = true, features = ["test-support"] }
|
||||
clock = { workspace = true, features = ["test-support"] }
|
||||
dap = { workspace = true, features = ["test-support"] }
|
||||
fs = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
http_client = { workspace = true, features = ["test-support"] }
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use ::proto::{FromProto, ToProto};
|
||||
use anyhow::{anyhow, Result};
|
||||
use dap::DapRegistry;
|
||||
use extension::ExtensionHostProxy;
|
||||
use extension_host::headless_host::HeadlessExtensionStore;
|
||||
use fs::Fs;
|
||||
|
@ -52,6 +53,7 @@ pub struct HeadlessAppState {
|
|||
pub http_client: Arc<dyn HttpClient>,
|
||||
pub node_runtime: NodeRuntime,
|
||||
pub languages: Arc<LanguageRegistry>,
|
||||
pub debug_adapters: Arc<DapRegistry>,
|
||||
pub extension_host_proxy: Arc<ExtensionHostProxy>,
|
||||
}
|
||||
|
||||
|
@ -69,6 +71,7 @@ impl HeadlessProject {
|
|||
http_client,
|
||||
node_runtime,
|
||||
languages,
|
||||
debug_adapters,
|
||||
extension_host_proxy: proxy,
|
||||
}: HeadlessAppState,
|
||||
cx: &mut Context<Self>,
|
||||
|
@ -108,6 +111,7 @@ impl HeadlessProject {
|
|||
node_runtime.clone(),
|
||||
fs.clone(),
|
||||
languages.clone(),
|
||||
debug_adapters.clone(),
|
||||
environment.clone(),
|
||||
toolchain_store.read(cx).as_language_toolchain_store(),
|
||||
breakpoint_store.clone(),
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
use crate::headless_project::HeadlessProject;
|
||||
use client::{Client, UserStore};
|
||||
use clock::FakeSystemClock;
|
||||
use dap::DapRegistry;
|
||||
use extension::ExtensionHostProxy;
|
||||
use fs::{FakeFs, Fs};
|
||||
use gpui::{AppContext as _, Entity, SemanticVersion, TestAppContext};
|
||||
|
@ -1445,6 +1446,7 @@ pub async fn init_test(
|
|||
let http_client = Arc::new(BlockedHttpClient);
|
||||
let node_runtime = NodeRuntime::unavailable();
|
||||
let languages = Arc::new(LanguageRegistry::new(cx.executor()));
|
||||
let debug_adapters = DapRegistry::default().into();
|
||||
let proxy = Arc::new(ExtensionHostProxy::new());
|
||||
server_cx.update(HeadlessProject::init);
|
||||
let headless = server_cx.new(|cx| {
|
||||
|
@ -1457,6 +1459,7 @@ pub async fn init_test(
|
|||
http_client,
|
||||
node_runtime,
|
||||
languages,
|
||||
debug_adapters,
|
||||
extension_host_proxy: proxy,
|
||||
},
|
||||
cx,
|
||||
|
|
|
@ -3,6 +3,7 @@ use crate::HeadlessProject;
|
|||
use anyhow::{anyhow, Context as _, Result};
|
||||
use chrono::Utc;
|
||||
use client::{telemetry, ProxySettings};
|
||||
use dap::DapRegistry;
|
||||
use extension::ExtensionHostProxy;
|
||||
use fs::{Fs, RealFs};
|
||||
use futures::channel::mpsc;
|
||||
|
@ -471,6 +472,7 @@ pub fn execute_run(
|
|||
let mut languages = LanguageRegistry::new(cx.background_executor().clone());
|
||||
languages.set_language_server_download_dir(paths::languages_dir().clone());
|
||||
let languages = Arc::new(languages);
|
||||
let debug_adapters = DapRegistry::default().into();
|
||||
|
||||
HeadlessProject::new(
|
||||
HeadlessAppState {
|
||||
|
@ -479,6 +481,7 @@ pub fn execute_run(
|
|||
http_client,
|
||||
node_runtime,
|
||||
languages,
|
||||
debug_adapters,
|
||||
extension_host_proxy,
|
||||
},
|
||||
cx,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use dap_types::StartDebuggingRequestArguments;
|
||||
use schemars::{gen::SchemaSettings, JsonSchema};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::path::PathBuf;
|
||||
use util::ResultExt;
|
||||
|
@ -45,97 +45,121 @@ pub struct AttachConfig {
|
|||
pub process_id: Option<u32>,
|
||||
}
|
||||
|
||||
/// Represents the launch request information of the debug adapter
|
||||
#[derive(Deserialize, Serialize, Default, PartialEq, Eq, JsonSchema, Clone, Debug)]
|
||||
pub struct LaunchConfig {
|
||||
/// The program that you trying to debug
|
||||
pub program: String,
|
||||
/// The current working directory of your project
|
||||
pub cwd: Option<PathBuf>,
|
||||
}
|
||||
|
||||
/// Represents the type that will determine which request to call on the debug adapter
|
||||
#[derive(Default, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
|
||||
#[serde(rename_all = "lowercase", untagged)]
|
||||
pub enum DebugRequestType {
|
||||
/// Call the `launch` request on the debug adapter
|
||||
#[default]
|
||||
Launch,
|
||||
Launch(LaunchConfig),
|
||||
/// Call the `attach` request on the debug adapter
|
||||
Attach(AttachConfig),
|
||||
}
|
||||
|
||||
/// The Debug adapter to use
|
||||
#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
|
||||
#[serde(rename_all = "lowercase", tag = "adapter")]
|
||||
pub enum DebugAdapterKind {
|
||||
/// Manually setup starting a debug adapter
|
||||
/// The argument within is used to start the DAP
|
||||
Custom(CustomArgs),
|
||||
/// Use debugpy
|
||||
Python(TCPHost),
|
||||
/// Use vscode-php-debug
|
||||
Php(TCPHost),
|
||||
/// Use vscode-js-debug
|
||||
Javascript(TCPHost),
|
||||
/// Use delve
|
||||
Go(TCPHost),
|
||||
/// Use lldb
|
||||
Lldb,
|
||||
/// Use GDB's built-in DAP support
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
Gdb,
|
||||
/// Used for integration tests
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[serde(skip)]
|
||||
Fake((bool, dap_types::Capabilities)),
|
||||
/// Represents a request for starting the debugger.
|
||||
/// Contrary to `DebugRequestType`, `DebugRequestDisposition` is not Serializable.
|
||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
pub enum DebugRequestDisposition {
|
||||
/// Debug session configured by the user.
|
||||
UserConfigured(DebugRequestType),
|
||||
/// Debug session configured by the debug adapter
|
||||
ReverseRequest(StartDebuggingRequestArguments),
|
||||
}
|
||||
|
||||
impl DebugAdapterKind {
|
||||
/// Returns the display name for the adapter kind
|
||||
pub fn display_name(&self) -> &str {
|
||||
impl DebugRequestDisposition {
|
||||
/// Get the current working directory from request if it's a launch request and exits
|
||||
pub fn cwd(&self) -> Option<PathBuf> {
|
||||
match self {
|
||||
Self::Custom(_) => "Custom",
|
||||
Self::Python(_) => "Python",
|
||||
Self::Php(_) => "PHP",
|
||||
Self::Javascript(_) => "JavaScript",
|
||||
Self::Lldb => "LLDB",
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
Self::Gdb => "GDB",
|
||||
Self::Go(_) => "Go",
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
Self::Fake(_) => "Fake",
|
||||
Self::UserConfigured(DebugRequestType::Launch(launch_config)) => {
|
||||
launch_config.cwd.clone()
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Custom arguments used to setup a custom debugger
|
||||
#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
|
||||
pub struct CustomArgs {
|
||||
/// The connection that a custom debugger should use
|
||||
#[serde(flatten)]
|
||||
pub connection: DebugConnectionType,
|
||||
/// The cli command used to start the debug adapter e.g. `python3`, `node` or the adapter binary
|
||||
pub command: String,
|
||||
/// The cli arguments used to start the debug adapter
|
||||
pub args: Option<Vec<String>>,
|
||||
/// The cli envs used to start the debug adapter
|
||||
pub envs: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
/// Represents the configuration for the debug adapter
|
||||
#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
pub struct DebugAdapterConfig {
|
||||
/// Name of the debug task
|
||||
pub label: String,
|
||||
/// The type of adapter you want to use
|
||||
#[serde(flatten)]
|
||||
pub kind: DebugAdapterKind,
|
||||
pub adapter: String,
|
||||
/// The type of request that should be called on the debug adapter
|
||||
#[serde(default)]
|
||||
pub request: DebugRequestType,
|
||||
/// The program that you trying to debug
|
||||
pub program: Option<String>,
|
||||
/// The current working directory of your project
|
||||
pub cwd: Option<PathBuf>,
|
||||
pub request: DebugRequestDisposition,
|
||||
/// Additional initialization arguments to be sent on DAP initialization
|
||||
pub initialize_args: Option<serde_json::Value>,
|
||||
/// Whether the debug adapter supports attaching to a running process.
|
||||
pub supports_attach: bool,
|
||||
/// Optional TCP connection information
|
||||
///
|
||||
/// If provided, this will be used to connect to the debug adapter instead of
|
||||
/// spawning a new process. This is useful for connecting to a debug adapter
|
||||
/// that is already running or is started by another process.
|
||||
pub tcp_connection: Option<TCPHost>,
|
||||
}
|
||||
|
||||
impl From<DebugTaskDefinition> for DebugAdapterConfig {
|
||||
fn from(def: DebugTaskDefinition) -> Self {
|
||||
Self {
|
||||
label: def.label,
|
||||
adapter: def.adapter,
|
||||
request: DebugRequestDisposition::UserConfigured(def.request),
|
||||
initialize_args: def.initialize_args,
|
||||
tcp_connection: def.tcp_connection,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<DebugAdapterConfig> for DebugTaskDefinition {
|
||||
type Error = ();
|
||||
fn try_from(def: DebugAdapterConfig) -> Result<Self, Self::Error> {
|
||||
let request = match def.request {
|
||||
DebugRequestDisposition::UserConfigured(debug_request_type) => debug_request_type,
|
||||
DebugRequestDisposition::ReverseRequest(_) => return Err(()),
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
label: def.label,
|
||||
adapter: def.adapter,
|
||||
request,
|
||||
initialize_args: def.initialize_args,
|
||||
tcp_connection: def.tcp_connection,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DebugTaskDefinition {
|
||||
/// Translate from debug definition to a task template
|
||||
pub fn to_zed_format(self) -> anyhow::Result<TaskTemplate> {
|
||||
let command = "".to_string();
|
||||
|
||||
let cwd = if let DebugRequestType::Launch(ref launch) = self.request {
|
||||
launch
|
||||
.cwd
|
||||
.as_ref()
|
||||
.map(|path| path.to_string_lossy().into_owned())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let label = self.label.clone();
|
||||
let task_type = TaskType::Debug(self);
|
||||
|
||||
Ok(TaskTemplate {
|
||||
label,
|
||||
command,
|
||||
args: vec![],
|
||||
task_type,
|
||||
cwd,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
/// Represents the type of the debugger adapter connection
|
||||
#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
|
||||
#[serde(rename_all = "lowercase", tag = "connection")]
|
||||
|
@ -151,48 +175,20 @@ pub enum DebugConnectionType {
|
|||
#[serde(rename_all = "snake_case")]
|
||||
pub struct DebugTaskDefinition {
|
||||
/// The adapter to run
|
||||
#[serde(flatten)]
|
||||
kind: DebugAdapterKind,
|
||||
pub adapter: String,
|
||||
/// The type of request that should be called on the debug adapter
|
||||
#[serde(default)]
|
||||
request: DebugRequestType,
|
||||
#[serde(flatten)]
|
||||
pub request: DebugRequestType,
|
||||
/// Name of the debug task
|
||||
label: String,
|
||||
/// Program to run the debugger on
|
||||
program: Option<String>,
|
||||
/// The current working directory of your project
|
||||
cwd: Option<String>,
|
||||
pub label: String,
|
||||
/// Additional initialization arguments to be sent on DAP initialization
|
||||
initialize_args: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
impl DebugTaskDefinition {
|
||||
/// Translate from debug definition to a task template
|
||||
pub fn to_zed_format(self) -> anyhow::Result<TaskTemplate> {
|
||||
let command = "".to_string();
|
||||
let cwd = self.cwd.clone().map(PathBuf::from).take_if(|p| p.exists());
|
||||
|
||||
let task_type = TaskType::Debug(DebugAdapterConfig {
|
||||
label: self.label.clone(),
|
||||
kind: self.kind,
|
||||
request: self.request,
|
||||
program: self.program,
|
||||
cwd: cwd.clone(),
|
||||
initialize_args: self.initialize_args,
|
||||
supports_attach: true,
|
||||
});
|
||||
|
||||
let args: Vec<String> = Vec::new();
|
||||
|
||||
Ok(TaskTemplate {
|
||||
label: self.label,
|
||||
command,
|
||||
args,
|
||||
task_type,
|
||||
cwd: self.cwd,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
pub initialize_args: Option<serde_json::Value>,
|
||||
/// Optional TCP connection information
|
||||
///
|
||||
/// If provided, this will be used to connect to the debug adapter instead of
|
||||
/// spawning a new process. This is useful for connecting to a debug adapter
|
||||
/// that is already running or is started by another process.
|
||||
pub tcp_connection: Option<TCPHost>,
|
||||
}
|
||||
|
||||
/// A group of Debug Tasks defined in a JSON file.
|
||||
|
|
|
@ -15,8 +15,8 @@ use std::path::PathBuf;
|
|||
use std::str::FromStr;
|
||||
|
||||
pub use debug_format::{
|
||||
AttachConfig, CustomArgs, DebugAdapterConfig, DebugAdapterKind, DebugConnectionType,
|
||||
DebugRequestType, DebugTaskDefinition, DebugTaskFile, TCPHost,
|
||||
AttachConfig, DebugAdapterConfig, DebugConnectionType, DebugRequestDisposition,
|
||||
DebugRequestType, DebugTaskDefinition, DebugTaskFile, LaunchConfig, TCPHost,
|
||||
};
|
||||
pub use task_template::{
|
||||
HideStrategy, RevealStrategy, TaskModal, TaskTemplate, TaskTemplates, TaskType,
|
||||
|
@ -104,14 +104,20 @@ impl ResolvedTask {
|
|||
}
|
||||
|
||||
/// Get the configuration for the debug adapter that should be used for this task.
|
||||
pub fn resolved_debug_adapter_config(&self) -> Option<DebugAdapterConfig> {
|
||||
pub fn resolved_debug_adapter_config(&self) -> Option<DebugTaskDefinition> {
|
||||
match self.original_task.task_type.clone() {
|
||||
TaskType::Script => None,
|
||||
TaskType::Debug(mut adapter_config) => {
|
||||
if let Some(resolved) = &self.resolved {
|
||||
adapter_config.label = resolved.label.clone();
|
||||
adapter_config.program = resolved.program.clone().or(adapter_config.program);
|
||||
adapter_config.cwd = resolved.cwd.clone().or(adapter_config.cwd);
|
||||
if let DebugRequestType::Launch(ref mut launch) = adapter_config.request {
|
||||
if let Some(program) = resolved.program.clone() {
|
||||
launch.program = program;
|
||||
}
|
||||
if let Some(cwd) = resolved.cwd.clone() {
|
||||
launch.cwd = Some(cwd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(adapter_config)
|
||||
|
|
|
@ -9,7 +9,7 @@ use sha2::{Digest, Sha256};
|
|||
use util::{truncate_and_remove_front, ResultExt};
|
||||
|
||||
use crate::{
|
||||
debug_format::DebugAdapterConfig, ResolvedTask, RevealTarget, Shell, SpawnInTerminal,
|
||||
DebugRequestType, DebugTaskDefinition, ResolvedTask, RevealTarget, Shell, SpawnInTerminal,
|
||||
TaskContext, TaskId, VariableName, ZED_VARIABLE_NAME_PREFIX,
|
||||
};
|
||||
|
||||
|
@ -78,18 +78,18 @@ pub struct TaskTemplate {
|
|||
/// Represents the type of task that is being ran
|
||||
#[derive(Default, Deserialize, Serialize, Eq, PartialEq, JsonSchema, Clone, Debug)]
|
||||
#[serde(rename_all = "snake_case", tag = "type")]
|
||||
#[expect(clippy::large_enum_variant)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum TaskType {
|
||||
/// Act like a typically task that runs commands
|
||||
#[default]
|
||||
Script,
|
||||
/// This task starts the debugger for a language
|
||||
Debug(DebugAdapterConfig),
|
||||
Debug(DebugTaskDefinition),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod deserialization_tests {
|
||||
use crate::{DebugAdapterKind, TCPHost};
|
||||
use crate::LaunchConfig;
|
||||
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
@ -105,19 +105,20 @@ mod deserialization_tests {
|
|||
|
||||
#[test]
|
||||
fn deserialize_task_type_debug() {
|
||||
let adapter_config = DebugAdapterConfig {
|
||||
let adapter_config = DebugTaskDefinition {
|
||||
label: "test config".into(),
|
||||
kind: DebugAdapterKind::Python(TCPHost::default()),
|
||||
request: crate::DebugRequestType::Launch,
|
||||
program: Some("main".to_string()),
|
||||
supports_attach: false,
|
||||
cwd: None,
|
||||
adapter: "Debugpy".into(),
|
||||
request: crate::DebugRequestType::Launch(LaunchConfig {
|
||||
program: "main".to_string(),
|
||||
cwd: None,
|
||||
}),
|
||||
initialize_args: None,
|
||||
tcp_connection: None,
|
||||
};
|
||||
let json = json!({
|
||||
"label": "test config",
|
||||
"type": "debug",
|
||||
"adapter": "python",
|
||||
"adapter": "Debugpy",
|
||||
"program": "main",
|
||||
"supports_attach": false,
|
||||
});
|
||||
|
@ -272,9 +273,9 @@ impl TaskTemplate {
|
|||
let program = match &self.task_type {
|
||||
TaskType::Script => None,
|
||||
TaskType::Debug(adapter_config) => {
|
||||
if let Some(program) = &adapter_config.program {
|
||||
if let DebugRequestType::Launch(ref launch) = &adapter_config.request {
|
||||
Some(substitute_all_template_variables_in_str(
|
||||
program,
|
||||
&launch.program,
|
||||
&task_variables,
|
||||
&variable_names,
|
||||
&mut substituted_variables,
|
||||
|
|
|
@ -349,7 +349,7 @@ impl PickerDelegate for TasksModalDelegate {
|
|||
_ => {
|
||||
project.update(cx, |project, cx| {
|
||||
project
|
||||
.start_debug_session(config, cx)
|
||||
.start_debug_session(config.into(), cx)
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
}
|
||||
|
@ -521,7 +521,10 @@ impl PickerDelegate for TasksModalDelegate {
|
|||
// This would allow users to access to debug history and other issues
|
||||
TaskType::Debug(_) => workspace.project().update(cx, |project, cx| {
|
||||
project
|
||||
.start_debug_session(task.resolved_debug_adapter_config().unwrap(), cx)
|
||||
.start_debug_session(
|
||||
task.resolved_debug_adapter_config().unwrap().into(),
|
||||
cx,
|
||||
)
|
||||
.detach_and_log_err(cx);
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -35,6 +35,7 @@ client.workspace = true
|
|||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
component.workspace = true
|
||||
dap.workspace = true
|
||||
db.workspace = true
|
||||
derive_more.workspace = true
|
||||
fs.workspace = true
|
||||
|
@ -68,6 +69,7 @@ zed_actions.workspace = true
|
|||
[dev-dependencies]
|
||||
call = { workspace = true, features = ["test-support"] }
|
||||
client = { workspace = true, features = ["test-support"] }
|
||||
dap = { workspace = true, features = ["test-support"] }
|
||||
db = { workspace = true, features = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
fs = { workspace = true, features = ["test-support"] }
|
||||
|
|
|
@ -14,6 +14,7 @@ mod toast_layer;
|
|||
mod toolbar;
|
||||
mod workspace_settings;
|
||||
|
||||
use dap::DapRegistry;
|
||||
pub use toast_layer::{RunAction, ToastAction, ToastLayer, ToastView};
|
||||
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
|
@ -637,6 +638,7 @@ pub fn register_serializable_item<I: SerializableItem>(cx: &mut App) {
|
|||
|
||||
pub struct AppState {
|
||||
pub languages: Arc<LanguageRegistry>,
|
||||
pub debug_adapters: Arc<DapRegistry>,
|
||||
pub client: Arc<Client>,
|
||||
pub user_store: Entity<UserStore>,
|
||||
pub workspace_store: Entity<WorkspaceStore>,
|
||||
|
@ -688,6 +690,7 @@ impl AppState {
|
|||
|
||||
let fs = fs::FakeFs::new(cx.background_executor().clone());
|
||||
let languages = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||
let debug_adapters = Arc::new(DapRegistry::fake());
|
||||
let clock = Arc::new(clock::FakeSystemClock::new());
|
||||
let http_client = http_client::FakeHttpClient::with_404_response();
|
||||
let client = Client::new(clock, http_client.clone(), cx);
|
||||
|
@ -703,6 +706,7 @@ impl AppState {
|
|||
client,
|
||||
fs,
|
||||
languages,
|
||||
debug_adapters,
|
||||
user_store,
|
||||
workspace_store,
|
||||
node_runtime: NodeRuntime::unavailable(),
|
||||
|
@ -1197,6 +1201,7 @@ impl Workspace {
|
|||
app_state.node_runtime.clone(),
|
||||
app_state.user_store.clone(),
|
||||
app_state.languages.clone(),
|
||||
app_state.debug_adapters.clone(),
|
||||
app_state.fs.clone(),
|
||||
env,
|
||||
cx,
|
||||
|
@ -5025,6 +5030,7 @@ impl Workspace {
|
|||
window.activate_window();
|
||||
let app_state = Arc::new(AppState {
|
||||
languages: project.read(cx).languages().clone(),
|
||||
debug_adapters: project.read(cx).debug_adapters().clone(),
|
||||
workspace_store,
|
||||
client,
|
||||
user_store,
|
||||
|
|
|
@ -41,6 +41,8 @@ command_palette.workspace = true
|
|||
command_palette_hooks.workspace = true
|
||||
component_preview.workspace = true
|
||||
copilot.workspace = true
|
||||
dap.workspace = true
|
||||
dap_adapters.workspace = true
|
||||
debugger_ui.workspace = true
|
||||
debugger_tools.workspace = true
|
||||
db.workspace = true
|
||||
|
@ -149,6 +151,7 @@ ashpd.workspace = true
|
|||
|
||||
[dev-dependencies]
|
||||
call = { workspace = true, features = ["test-support"] }
|
||||
dap = { workspace = true, features = ["test-support"] }
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
image_viewer = { workspace = true, features = ["test-support"] }
|
||||
|
|
|
@ -11,6 +11,7 @@ use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
|
|||
use client::{parse_zed_link, Client, ProxySettings, UserStore};
|
||||
use collab_ui::channel_view::ChannelView;
|
||||
use collections::HashMap;
|
||||
use dap::DapRegistry;
|
||||
use db::kvp::{GLOBAL_KEY_VALUE_STORE, KEY_VALUE_STORE};
|
||||
use editor::Editor;
|
||||
use extension::ExtensionHostProxy;
|
||||
|
@ -422,6 +423,7 @@ fn main() {
|
|||
|
||||
let app_state = Arc::new(AppState {
|
||||
languages: languages.clone(),
|
||||
debug_adapters: DapRegistry::default().into(),
|
||||
client: client.clone(),
|
||||
user_store: user_store.clone(),
|
||||
fs: fs.clone(),
|
||||
|
@ -433,6 +435,7 @@ fn main() {
|
|||
AppState::set_global(Arc::downgrade(&app_state), cx);
|
||||
|
||||
auto_update::init(client.http_client(), cx);
|
||||
dap_adapters::init(app_state.debug_adapters.clone());
|
||||
auto_update_ui::init(cx);
|
||||
reliability::init(
|
||||
client.http_client(),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue