debugger: More tidy up for SSH (#28993)

Split `locator` out of DebugTaskDefinition to make it clearer when
location needs to happen.

Release Notes:

- N/A

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Anthony <anthony@zed.dev>
Co-authored-by: Cole Miller <m@cole-miller.net>
This commit is contained in:
Conrad Irwin 2025-04-21 10:00:03 -06:00 committed by GitHub
parent d13cd007a2
commit 9d35f0389d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
57 changed files with 1146 additions and 884 deletions

5
Cargo.lock generated
View file

@ -4013,6 +4013,7 @@ dependencies = [
"node_runtime",
"parking_lot",
"paths",
"proto",
"schemars",
"serde",
"serde_json",
@ -4897,7 +4898,6 @@ dependencies = [
"client",
"collections",
"context_server",
"dap",
"dirs 5.0.1",
"env_logger 0.11.8",
"extension",
@ -11743,6 +11743,7 @@ dependencies = [
"client",
"clock",
"dap",
"dap_adapters",
"env_logger 0.11.8",
"extension",
"extension_host",
@ -14214,11 +14215,11 @@ version = "0.1.0"
dependencies = [
"anyhow",
"collections",
"dap-types",
"futures 0.3.31",
"gpui",
"hex",
"parking_lot",
"proto",
"schemars",
"serde",
"serde_json",

View file

@ -1,7 +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 _;
@ -86,7 +86,6 @@ 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,
@ -254,7 +253,6 @@ 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,
@ -460,7 +458,6 @@ 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,

View file

@ -14,7 +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::{StreamExt as _, channel::oneshot};
use git::GitHostingProviderRegistry;
@ -275,14 +275,12 @@ 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(),
@ -798,7 +796,6 @@ 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,

View file

@ -39,6 +39,7 @@ log.workspace = true
node_runtime.workspace = true
parking_lot.workspace = true
paths.workspace = true
proto.workspace = true
schemars.workspace = true
serde.workspace = true
serde_json.workspace = true

View file

@ -3,7 +3,8 @@ use anyhow::{Context as _, Result, anyhow};
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use async_trait::async_trait;
use dap_types::StartDebuggingRequestArguments;
use collections::HashMap;
use dap_types::{StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest};
use futures::io::BufReader;
use gpui::{AsyncApp, SharedString};
pub use http_client::{HttpClient, github::latest_github_release};
@ -13,16 +14,10 @@ use serde::{Deserialize, Serialize};
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::PathBuf,
sync::Arc,
borrow::Borrow, collections::HashSet, ffi::OsStr, fmt::Debug, net::Ipv4Addr, ops::Deref,
path::PathBuf, sync::Arc,
};
use task::DebugTaskDefinition;
use task::{DebugTaskDefinition, TcpArgumentsTemplate};
use util::ResultExt;
#[derive(Clone, Debug, PartialEq, Eq)]
@ -93,17 +88,91 @@ pub struct TcpArguments {
pub port: u16,
pub timeout: Option<u64>,
}
impl TcpArguments {
pub fn from_proto(proto: proto::TcpHost) -> anyhow::Result<Self> {
let host = TcpArgumentsTemplate::from_proto(proto)?;
Ok(TcpArguments {
host: host.host.ok_or_else(|| anyhow!("missing host"))?,
port: host.port.ok_or_else(|| anyhow!("missing port"))?,
timeout: host.timeout,
})
}
pub fn to_proto(&self) -> proto::TcpHost {
TcpArgumentsTemplate {
host: Some(self.host),
port: Some(self.port),
timeout: self.timeout,
}
.to_proto()
}
}
#[derive(Debug, Clone)]
pub struct DebugAdapterBinary {
pub adapter_name: DebugAdapterName,
pub command: String,
pub arguments: Option<Vec<OsString>>,
pub envs: Option<HashMap<String, String>>,
pub arguments: Vec<String>,
pub envs: HashMap<String, String>,
pub cwd: Option<PathBuf>,
pub connection: Option<TcpArguments>,
pub request_args: StartDebuggingRequestArguments,
}
impl DebugAdapterBinary {
pub fn from_proto(binary: proto::DebugAdapterBinary) -> anyhow::Result<Self> {
let request = match binary.launch_type() {
proto::debug_adapter_binary::LaunchType::Launch => {
StartDebuggingRequestArgumentsRequest::Launch
}
proto::debug_adapter_binary::LaunchType::Attach => {
StartDebuggingRequestArgumentsRequest::Attach
}
};
Ok(DebugAdapterBinary {
command: binary.command,
arguments: binary.arguments,
envs: binary.envs.into_iter().collect(),
connection: binary
.connection
.map(TcpArguments::from_proto)
.transpose()?,
request_args: StartDebuggingRequestArguments {
configuration: serde_json::from_str(&binary.configuration)?,
request,
},
cwd: binary.cwd.map(|cwd| cwd.into()),
})
}
pub fn to_proto(&self) -> proto::DebugAdapterBinary {
proto::DebugAdapterBinary {
command: self.command.clone(),
arguments: self.arguments.clone(),
envs: self
.envs
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect(),
cwd: self
.cwd
.as_ref()
.map(|cwd| cwd.to_string_lossy().to_string()),
connection: self.connection.as_ref().map(|c| c.to_proto()),
launch_type: match self.request_args.request {
StartDebuggingRequestArgumentsRequest::Launch => {
proto::debug_adapter_binary::LaunchType::Launch.into()
}
StartDebuggingRequestArgumentsRequest::Attach => {
proto::debug_adapter_binary::LaunchType::Attach.into()
}
},
configuration: self.request_args.configuration.to_string(),
}
}
}
#[derive(Debug)]
pub struct AdapterVersion {
pub tag_name: String,
@ -318,22 +387,22 @@ impl FakeAdapter {
fn request_args(&self, config: &DebugTaskDefinition) -> StartDebuggingRequestArguments {
use serde_json::json;
use task::DebugRequestType;
use task::DebugRequest;
let value = json!({
"request": match config.request {
DebugRequestType::Launch(_) => "launch",
DebugRequestType::Attach(_) => "attach",
DebugRequest::Launch(_) => "launch",
DebugRequest::Attach(_) => "attach",
},
"process_id": if let DebugRequestType::Attach(attach_config) = &config.request {
"process_id": if let DebugRequest::Attach(attach_config) = &config.request {
attach_config.process_id
} else {
None
},
});
let request = match config.request {
DebugRequestType::Launch(_) => dap_types::StartDebuggingRequestArgumentsRequest::Launch,
DebugRequestType::Attach(_) => dap_types::StartDebuggingRequestArgumentsRequest::Attach,
DebugRequest::Launch(_) => dap_types::StartDebuggingRequestArgumentsRequest::Launch,
DebugRequest::Attach(_) => dap_types::StartDebuggingRequestArgumentsRequest::Attach,
};
StartDebuggingRequestArguments {
configuration: value,
@ -357,11 +426,10 @@ impl DebugAdapter for FakeAdapter {
_: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
Ok(DebugAdapterBinary {
adapter_name: Self::ADAPTER_NAME.into(),
command: "command".into(),
arguments: None,
arguments: vec![],
connection: None,
envs: None,
envs: HashMap::default(),
cwd: None,
request_args: self.request_args(config),
})

View file

@ -1,5 +1,5 @@
use crate::{
adapters::{DebugAdapterBinary, DebugAdapterName},
adapters::DebugAdapterBinary,
transport::{IoKind, LogKind, TransportDelegate},
};
use anyhow::{Result, anyhow};
@ -88,7 +88,6 @@ impl DebugAdapterClient {
) -> Result<Self> {
let binary = match self.transport_delegate.transport() {
crate::transport::Transport::Tcp(tcp_transport) => DebugAdapterBinary {
adapter_name: binary.adapter_name,
command: binary.command,
arguments: binary.arguments,
envs: binary.envs,
@ -219,9 +218,6 @@ impl DebugAdapterClient {
self.id
}
pub fn name(&self) -> DebugAdapterName {
self.binary.adapter_name.clone()
}
pub fn binary(&self) -> &DebugAdapterBinary {
&self.binary
}
@ -322,7 +318,6 @@ mod tests {
let client = DebugAdapterClient::start(
crate::client::SessionId(1),
DebugAdapterBinary {
adapter_name: "adapter".into(),
command: "command".into(),
arguments: Default::default(),
envs: Default::default(),
@ -393,7 +388,6 @@ mod tests {
let client = DebugAdapterClient::start(
crate::client::SessionId(1),
DebugAdapterBinary {
adapter_name: "adapter".into(),
command: "command".into(),
arguments: Default::default(),
envs: Default::default(),
@ -447,7 +441,6 @@ mod tests {
let client = DebugAdapterClient::start(
crate::client::SessionId(1),
DebugAdapterBinary {
adapter_name: "test-adapter".into(),
command: "command".into(),
arguments: Default::default(),
envs: Default::default(),

View file

@ -7,7 +7,7 @@ pub mod transport;
pub use dap_types::*;
pub use registry::DapRegistry;
pub use task::DebugRequestType;
pub use task::DebugRequest;
pub type ScopeId = u64;
pub type VariableReference = u64;

View file

@ -1,3 +1,4 @@
use gpui::{App, Global};
use parking_lot::RwLock;
use crate::adapters::{DebugAdapter, DebugAdapterName};
@ -11,8 +12,20 @@ struct DapRegistryState {
#[derive(Clone, Default)]
/// Stores available debug adapters.
pub struct DapRegistry(Arc<RwLock<DapRegistryState>>);
impl Global for DapRegistry {}
impl DapRegistry {
pub fn global(cx: &mut App) -> &mut Self {
let ret = cx.default_global::<Self>();
#[cfg(any(test, feature = "test-support"))]
if ret.adapter(crate::FakeAdapter::ADAPTER_NAME).is_none() {
ret.add_adapter(Arc::new(crate::FakeAdapter::new()));
}
ret
}
pub fn add_adapter(&self, adapter: Arc<dyn DebugAdapter>) {
let name = adapter.name();
let _previous_value = self.0.write().adapters.insert(name, adapter);
@ -21,19 +34,12 @@ impl DapRegistry {
"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
}
}

View file

@ -21,7 +21,7 @@ use std::{
sync::Arc,
time::Duration,
};
use task::TCPHost;
use task::TcpArgumentsTemplate;
use util::ResultExt as _;
use crate::{adapters::DebugAdapterBinary, debugger_settings::DebuggerSettings};
@ -74,16 +74,14 @@ pub enum Transport {
}
impl Transport {
#[cfg(any(test, feature = "test-support"))]
async fn start(_: &DebugAdapterBinary, cx: AsyncApp) -> Result<(TransportPipe, Self)> {
#[cfg(any(test, feature = "test-support"))]
return FakeTransport::start(cx)
.await
.map(|(transports, fake)| (transports, Self::Fake(fake)));
}
#[cfg(not(any(test, feature = "test-support")))]
async fn start(binary: &DebugAdapterBinary, cx: AsyncApp) -> Result<(TransportPipe, Self)> {
#[cfg(any(test, feature = "test-support"))]
if cfg!(any(test, feature = "test-support")) {
return FakeTransport::start(cx)
.await
.map(|(transports, fake)| (transports, Self::Fake(fake)));
}
if binary.connection.is_some() {
TcpTransport::start(binary, cx)
.await
@ -520,18 +518,21 @@ pub struct TcpTransport {
impl TcpTransport {
/// Get an open port to use with the tcp client when not supplied by debug config
pub async fn port(host: &TCPHost) -> Result<u16> {
pub async fn port(host: &TcpArgumentsTemplate) -> Result<u16> {
if let Some(port) = host.port {
Ok(port)
} else {
Ok(TcpListener::bind(SocketAddrV4::new(host.host(), 0))
.await?
.local_addr()?
.port())
Self::unused_port(host.host()).await
}
}
#[allow(dead_code, reason = "This is used in non test builds of Zed")]
pub async fn unused_port(host: Ipv4Addr) -> Result<u16> {
Ok(TcpListener::bind(SocketAddrV4::new(host, 0))
.await?
.local_addr()?
.port())
}
async fn start(binary: &DebugAdapterBinary, cx: AsyncApp) -> Result<(TransportPipe, Self)> {
let Some(connection_args) = binary.connection.as_ref() else {
return Err(anyhow!("No connection arguments provided"));
@ -546,13 +547,8 @@ impl TcpTransport {
command.current_dir(cwd);
}
if let Some(args) = &binary.arguments {
command.args(args);
}
if let Some(envs) = &binary.envs {
command.envs(envs);
}
command.args(&binary.arguments);
command.envs(&binary.envs);
command
.stdin(Stdio::null())
@ -635,13 +631,8 @@ impl StdioTransport {
command.current_dir(cwd);
}
if let Some(args) = &binary.arguments {
command.args(args);
}
if let Some(envs) = &binary.envs {
command.envs(envs);
}
command.args(&binary.arguments);
command.envs(&binary.envs);
command
.stdin(Stdio::piped())

View file

@ -1,10 +1,10 @@
use std::{path::PathBuf, sync::OnceLock};
use std::{collections::HashMap, path::PathBuf, sync::OnceLock};
use anyhow::{Result, bail};
use async_trait::async_trait;
use dap::adapters::latest_github_release;
use gpui::AsyncApp;
use task::{DebugRequestType, DebugTaskDefinition};
use task::{DebugRequest, DebugTaskDefinition};
use crate::*;
@ -19,8 +19,8 @@ impl CodeLldbDebugAdapter {
fn request_args(&self, config: &DebugTaskDefinition) -> dap::StartDebuggingRequestArguments {
let mut configuration = json!({
"request": match config.request {
DebugRequestType::Launch(_) => "launch",
DebugRequestType::Attach(_) => "attach",
DebugRequest::Launch(_) => "launch",
DebugRequest::Attach(_) => "attach",
},
});
let map = configuration.as_object_mut().unwrap();
@ -28,10 +28,10 @@ impl CodeLldbDebugAdapter {
map.insert("name".into(), Value::String(config.label.clone()));
let request = config.request.to_dap();
match &config.request {
DebugRequestType::Attach(attach) => {
DebugRequest::Attach(attach) => {
map.insert("pid".into(), attach.process_id.into());
}
DebugRequestType::Launch(launch) => {
DebugRequest::Launch(launch) => {
map.insert("program".into(), launch.program.clone().into());
if !launch.args.is_empty() {
@ -140,16 +140,13 @@ impl DebugAdapter for CodeLldbDebugAdapter {
.ok_or_else(|| anyhow!("Adapter path is expected to be valid UTF-8"))?;
Ok(DebugAdapterBinary {
command,
cwd: Some(adapter_dir),
arguments: Some(vec![
cwd: None,
arguments: vec![
"--settings".into(),
json!({"sourceLanguages": ["cpp", "rust"]})
.to_string()
.into(),
]),
json!({"sourceLanguages": ["cpp", "rust"]}).to_string(),
],
request_args: self.request_args(config),
adapter_name: "test".into(),
envs: None,
envs: HashMap::default(),
connection: None,
})
}

View file

@ -11,7 +11,7 @@ use anyhow::{Result, anyhow};
use async_trait::async_trait;
use codelldb::CodeLldbDebugAdapter;
use dap::{
DapRegistry, DebugRequestType,
DapRegistry, DebugRequest,
adapters::{
self, AdapterVersion, DapDelegate, DebugAdapter, DebugAdapterBinary, DebugAdapterName,
GithubRepo,
@ -19,23 +19,26 @@ use dap::{
};
use gdb::GdbDebugAdapter;
use go::GoDebugAdapter;
use gpui::{App, BorrowAppContext};
use javascript::JsDebugAdapter;
use php::PhpDebugAdapter;
use python::PythonDebugAdapter;
use serde_json::{Value, json};
use task::TCPHost;
use task::TcpArgumentsTemplate;
pub fn init(registry: Arc<DapRegistry>) {
registry.add_adapter(Arc::from(CodeLldbDebugAdapter::default()));
registry.add_adapter(Arc::from(PythonDebugAdapter));
registry.add_adapter(Arc::from(PhpDebugAdapter));
registry.add_adapter(Arc::from(JsDebugAdapter));
registry.add_adapter(Arc::from(GoDebugAdapter));
registry.add_adapter(Arc::from(GdbDebugAdapter));
pub fn init(cx: &mut App) {
cx.update_default_global(|registry: &mut DapRegistry, _cx| {
registry.add_adapter(Arc::from(CodeLldbDebugAdapter::default()));
registry.add_adapter(Arc::from(PythonDebugAdapter));
registry.add_adapter(Arc::from(PhpDebugAdapter));
registry.add_adapter(Arc::from(JsDebugAdapter));
registry.add_adapter(Arc::from(GoDebugAdapter));
registry.add_adapter(Arc::from(GdbDebugAdapter));
})
}
pub(crate) async fn configure_tcp_connection(
tcp_connection: TCPHost,
tcp_connection: TcpArgumentsTemplate,
) -> Result<(Ipv4Addr, u16, Option<u64>)> {
let host = tcp_connection.host();
let timeout = tcp_connection.timeout;
@ -53,7 +56,7 @@ trait ToDap {
fn to_dap(&self) -> dap::StartDebuggingRequestArgumentsRequest;
}
impl ToDap for DebugRequestType {
impl ToDap for DebugRequest {
fn to_dap(&self) -> dap::StartDebuggingRequestArgumentsRequest {
match self {
Self::Launch(_) => dap::StartDebuggingRequestArgumentsRequest::Launch,

View file

@ -1,10 +1,10 @@
use std::ffi::OsStr;
use std::{collections::HashMap, ffi::OsStr};
use anyhow::{Result, bail};
use async_trait::async_trait;
use dap::StartDebuggingRequestArguments;
use gpui::AsyncApp;
use task::{DebugRequestType, DebugTaskDefinition};
use task::{DebugRequest, DebugTaskDefinition};
use crate::*;
@ -17,18 +17,18 @@ impl GdbDebugAdapter {
fn request_args(&self, config: &DebugTaskDefinition) -> StartDebuggingRequestArguments {
let mut args = json!({
"request": match config.request {
DebugRequestType::Launch(_) => "launch",
DebugRequestType::Attach(_) => "attach",
DebugRequest::Launch(_) => "launch",
DebugRequest::Attach(_) => "attach",
},
});
let map = args.as_object_mut().unwrap();
match &config.request {
DebugRequestType::Attach(attach) => {
DebugRequest::Attach(attach) => {
map.insert("pid".into(), attach.process_id.into());
}
DebugRequestType::Launch(launch) => {
DebugRequest::Launch(launch) => {
map.insert("program".into(), launch.program.clone().into());
if !launch.args.is_empty() {
@ -82,10 +82,9 @@ impl DebugAdapter for GdbDebugAdapter {
let gdb_path = user_setting_path.unwrap_or(gdb_path?);
Ok(DebugAdapterBinary {
adapter_name: Self::ADAPTER_NAME.into(),
command: gdb_path,
arguments: Some(vec!["-i=dap".into()]),
envs: None,
arguments: vec!["-i=dap".into()],
envs: HashMap::default(),
cwd: None,
connection: None,
request_args: self.request_args(config),

View file

@ -1,6 +1,6 @@
use dap::StartDebuggingRequestArguments;
use gpui::AsyncApp;
use std::{ffi::OsStr, path::PathBuf};
use std::{collections::HashMap, ffi::OsStr, path::PathBuf};
use task::DebugTaskDefinition;
use crate::*;
@ -12,12 +12,12 @@ impl GoDebugAdapter {
const ADAPTER_NAME: &'static str = "Delve";
fn request_args(&self, config: &DebugTaskDefinition) -> StartDebuggingRequestArguments {
let mut args = match &config.request {
dap::DebugRequestType::Attach(attach_config) => {
dap::DebugRequest::Attach(attach_config) => {
json!({
"processId": attach_config.process_id,
})
}
dap::DebugRequestType::Launch(launch_config) => json!({
dap::DebugRequest::Launch(launch_config) => json!({
"program": launch_config.program,
"cwd": launch_config.cwd,
"args": launch_config.args
@ -92,15 +92,14 @@ impl DebugAdapter for GoDebugAdapter {
let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
Ok(DebugAdapterBinary {
adapter_name: self.name(),
command: delve_path,
arguments: Some(vec![
arguments: vec![
"dap".into(),
"--listen".into(),
format!("{}:{}", host, port).into(),
]),
format!("{}:{}", host, port),
],
cwd: None,
envs: None,
envs: HashMap::default(),
connection: Some(adapters::TcpArguments {
host,
port,

View file

@ -1,8 +1,8 @@
use adapters::latest_github_release;
use dap::StartDebuggingRequestArguments;
use gpui::AsyncApp;
use std::path::PathBuf;
use task::{DebugRequestType, DebugTaskDefinition};
use std::{collections::HashMap, path::PathBuf};
use task::{DebugRequest, DebugTaskDefinition};
use crate::*;
@ -18,16 +18,16 @@ impl JsDebugAdapter {
let mut args = json!({
"type": "pwa-node",
"request": match config.request {
DebugRequestType::Launch(_) => "launch",
DebugRequestType::Attach(_) => "attach",
DebugRequest::Launch(_) => "launch",
DebugRequest::Attach(_) => "attach",
},
});
let map = args.as_object_mut().unwrap();
match &config.request {
DebugRequestType::Attach(attach) => {
DebugRequest::Attach(attach) => {
map.insert("processId".into(), attach.process_id.into());
}
DebugRequestType::Launch(launch) => {
DebugRequest::Launch(launch) => {
map.insert("program".into(), launch.program.clone().into());
if !launch.args.is_empty() {
@ -106,20 +106,22 @@ impl DebugAdapter for JsDebugAdapter {
let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
Ok(DebugAdapterBinary {
adapter_name: self.name(),
command: delegate
.node_runtime()
.binary_path()
.await?
.to_string_lossy()
.into_owned(),
arguments: Some(vec![
adapter_path.join(Self::ADAPTER_PATH).into(),
port.to_string().into(),
host.to_string().into(),
]),
arguments: vec![
adapter_path
.join(Self::ADAPTER_PATH)
.to_string_lossy()
.to_string(),
port.to_string(),
host.to_string(),
],
cwd: None,
envs: None,
envs: HashMap::default(),
connection: Some(adapters::TcpArguments {
host,
port,

View file

@ -1,7 +1,7 @@
use adapters::latest_github_release;
use dap::adapters::TcpArguments;
use gpui::AsyncApp;
use std::path::PathBuf;
use std::{collections::HashMap, path::PathBuf};
use task::DebugTaskDefinition;
use crate::*;
@ -19,20 +19,18 @@ impl PhpDebugAdapter {
config: &DebugTaskDefinition,
) -> Result<dap::StartDebuggingRequestArguments> {
match &config.request {
dap::DebugRequestType::Attach(_) => {
dap::DebugRequest::Attach(_) => {
anyhow::bail!("php adapter does not support attaching")
}
dap::DebugRequestType::Launch(launch_config) => {
Ok(dap::StartDebuggingRequestArguments {
configuration: json!({
"program": launch_config.program,
"cwd": launch_config.cwd,
"args": launch_config.args,
"stopOnEntry": config.stop_on_entry.unwrap_or_default(),
}),
request: config.request.to_dap(),
})
}
dap::DebugRequest::Launch(launch_config) => Ok(dap::StartDebuggingRequestArguments {
configuration: json!({
"program": launch_config.program,
"cwd": launch_config.cwd,
"args": launch_config.args,
"stopOnEntry": config.stop_on_entry.unwrap_or_default(),
}),
request: config.request.to_dap(),
}),
}
}
}
@ -94,24 +92,26 @@ impl DebugAdapter for PhpDebugAdapter {
let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
Ok(DebugAdapterBinary {
adapter_name: self.name(),
command: delegate
.node_runtime()
.binary_path()
.await?
.to_string_lossy()
.into_owned(),
arguments: Some(vec![
adapter_path.join(Self::ADAPTER_PATH).into(),
format!("--server={}", port).into(),
]),
arguments: vec![
adapter_path
.join(Self::ADAPTER_PATH)
.to_string_lossy()
.to_string(),
format!("--server={}", port),
],
connection: Some(TcpArguments {
port,
host,
timeout,
}),
cwd: None,
envs: None,
envs: HashMap::default(),
request_args: self.request_args(config)?,
})
}

View file

@ -1,7 +1,7 @@
use crate::*;
use dap::{DebugRequestType, StartDebuggingRequestArguments};
use dap::{DebugRequest, StartDebuggingRequestArguments};
use gpui::AsyncApp;
use std::{ffi::OsStr, path::PathBuf};
use std::{collections::HashMap, ffi::OsStr, path::PathBuf};
use task::DebugTaskDefinition;
#[derive(Default)]
@ -16,18 +16,18 @@ impl PythonDebugAdapter {
fn request_args(&self, config: &DebugTaskDefinition) -> StartDebuggingRequestArguments {
let mut args = json!({
"request": match config.request {
DebugRequestType::Launch(_) => "launch",
DebugRequestType::Attach(_) => "attach",
DebugRequest::Launch(_) => "launch",
DebugRequest::Attach(_) => "attach",
},
"subProcess": true,
"redirectOutput": true,
});
let map = args.as_object_mut().unwrap();
match &config.request {
DebugRequestType::Attach(attach) => {
DebugRequest::Attach(attach) => {
map.insert("processId".into(), attach.process_id.into());
}
DebugRequestType::Launch(launch) => {
DebugRequest::Launch(launch) => {
map.insert("program".into(), launch.program.clone().into());
map.insert("args".into(), launch.args.clone().into());
@ -141,20 +141,22 @@ impl DebugAdapter for PythonDebugAdapter {
};
Ok(DebugAdapterBinary {
adapter_name: self.name(),
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={}", port).into(),
format!("--host={}", host).into(),
]),
arguments: vec![
debugpy_dir
.join(Self::ADAPTER_PATH)
.to_string_lossy()
.to_string(),
format!("--port={}", port),
format!("--host={}", host),
],
connection: Some(adapters::TcpArguments {
host,
port,
timeout,
}),
cwd: None,
envs: None,
envs: HashMap::default(),
request_args: self.request_args(config),
})
}

View file

@ -566,11 +566,13 @@ impl DapLogView {
.dap_store()
.read(cx)
.sessions()
.filter_map(|client| {
let client = client.read(cx).adapter_client()?;
.filter_map(|session| {
let session = session.read(cx);
session.adapter_name();
let client = session.adapter_client()?;
Some(DapMenuItem {
client_id: client.id(),
client_name: client.name().0.as_ref().into(),
client_name: session.adapter_name().to_string(),
has_adapter_logs: client.has_adapter_logs(),
selected_entry: self.current_view.map_or(LogKind::Adapter, |(_, kind)| kind),
})

View file

@ -1,4 +1,4 @@
use dap::DebugRequestType;
use dap::DebugRequest;
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::Subscription;
use gpui::{DismissEvent, Entity, EventEmitter, Focusable, Render};
@ -216,10 +216,10 @@ impl PickerDelegate for AttachModalDelegate {
};
match &mut self.debug_config.request {
DebugRequestType::Attach(config) => {
DebugRequest::Attach(config) => {
config.process_id = Some(candidate.pid);
}
DebugRequestType::Launch(_) => {
DebugRequest::Launch(_) => {
debug_panic!("Debugger attach modal used on launch debug config");
return;
}

View file

@ -5,7 +5,7 @@ use std::{
};
use anyhow::{Result, anyhow};
use dap::DebugRequestType;
use dap::{DapRegistry, DebugRequest};
use editor::{Editor, EditorElement, EditorStyle};
use gpui::{
App, AppContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Render, TextStyle,
@ -13,7 +13,7 @@ use gpui::{
};
use project::Project;
use settings::Settings;
use task::{DebugTaskDefinition, LaunchConfig};
use task::{DebugTaskDefinition, DebugTaskTemplate, LaunchRequest};
use theme::ThemeSettings;
use ui::{
ActiveTheme, Button, ButtonCommon, ButtonSize, CheckboxWithLabel, Clickable, Color, Context,
@ -37,9 +37,9 @@ pub(super) struct NewSessionModal {
last_selected_profile_name: Option<SharedString>,
}
fn suggested_label(request: &DebugRequestType, debugger: &str) -> String {
fn suggested_label(request: &DebugRequest, debugger: &str) -> String {
match request {
DebugRequestType::Launch(config) => {
DebugRequest::Launch(config) => {
let last_path_component = Path::new(&config.program)
.file_name()
.map(|name| name.to_string_lossy())
@ -47,7 +47,7 @@ fn suggested_label(request: &DebugRequestType, debugger: &str) -> String {
format!("{} ({debugger})", last_path_component)
}
DebugRequestType::Attach(config) => format!(
DebugRequest::Attach(config) => format!(
"pid: {} ({debugger})",
config.process_id.unwrap_or(u32::MAX)
),
@ -71,7 +71,7 @@ impl NewSessionModal {
.and_then(|def| def.stop_on_entry);
let launch_config = match past_debug_definition.map(|def| def.request) {
Some(DebugRequestType::Launch(launch_config)) => Some(launch_config),
Some(DebugRequest::Launch(launch_config)) => Some(launch_config),
_ => None,
};
@ -96,7 +96,6 @@ impl NewSessionModal {
request,
initialize_args: self.initialize_args.clone(),
tcp_connection: None,
locator: None,
stop_on_entry: match self.stop_on_entry {
ToggleState::Selected => Some(true),
_ => None,
@ -131,20 +130,16 @@ impl NewSessionModal {
let project = workspace.update(cx, |workspace, _| workspace.project().clone())?;
let task = project.update(cx, |this, cx| {
if let Some(debug_config) =
config
.clone()
.to_zed_format()
.ok()
.and_then(|task_template| {
task_template
.resolve_task("debug_task", &task_context)
.and_then(|resolved_task| {
resolved_task.resolved_debug_adapter_config()
})
})
let template = DebugTaskTemplate {
locator: None,
definition: config.clone(),
};
if let Some(debug_config) = template
.to_zed_format()
.resolve_task("debug_task", &task_context)
.and_then(|resolved_task| resolved_task.resolved_debug_adapter_config())
{
this.start_debug_session(debug_config, cx)
this.start_debug_session(debug_config.definition, cx)
} else {
this.start_debug_session(config, cx)
}
@ -214,12 +209,7 @@ impl NewSessionModal {
};
let available_adapters = workspace
.update(cx, |this, cx| {
this.project()
.read(cx)
.debug_adapters()
.enumerate_adapters()
})
.update(cx, |_, cx| DapRegistry::global(cx).enumerate_adapters())
.ok()
.unwrap_or_default();
@ -251,14 +241,14 @@ impl NewSessionModal {
this.debugger = Some(task.adapter.clone().into());
this.initialize_args = task.initialize_args.clone();
match &task.request {
DebugRequestType::Launch(launch_config) => {
DebugRequest::Launch(launch_config) => {
this.mode = NewSessionMode::launch(
Some(launch_config.clone()),
window,
cx,
);
}
DebugRequestType::Attach(_) => {
DebugRequest::Attach(_) => {
let Ok(project) = this
.workspace
.read_with(cx, |this, _| this.project().clone())
@ -285,7 +275,7 @@ impl NewSessionModal {
}
};
let available_adapters: Vec<DebugTaskDefinition> = workspace
let available_adapters: Vec<DebugTaskTemplate> = workspace
.update(cx, |this, cx| {
this.project()
.read(cx)
@ -303,9 +293,9 @@ impl NewSessionModal {
for debug_definition in available_adapters {
menu = menu.entry(
debug_definition.label.clone(),
debug_definition.definition.label.clone(),
None,
setter_for_name(debug_definition),
setter_for_name(debug_definition.definition),
);
}
menu
@ -322,7 +312,7 @@ struct LaunchMode {
impl LaunchMode {
fn new(
past_launch_config: Option<LaunchConfig>,
past_launch_config: Option<LaunchRequest>,
window: &mut Window,
cx: &mut App,
) -> Entity<Self> {
@ -348,9 +338,9 @@ impl LaunchMode {
cx.new(|_| Self { program, cwd })
}
fn debug_task(&self, cx: &App) -> task::LaunchConfig {
fn debug_task(&self, cx: &App) -> task::LaunchRequest {
let path = self.cwd.read(cx).text(cx);
task::LaunchConfig {
task::LaunchRequest {
program: self.program.read(cx).text(cx),
cwd: path.is_empty().not().then(|| PathBuf::from(path)),
args: Default::default(),
@ -373,10 +363,9 @@ impl AttachMode {
) -> Entity<Self> {
let debug_definition = DebugTaskDefinition {
label: "Attach New Session Setup".into(),
request: dap::DebugRequestType::Attach(task::AttachConfig { process_id: None }),
request: dap::DebugRequest::Attach(task::AttachRequest { process_id: None }),
tcp_connection: None,
adapter: debugger.clone().unwrap_or_default().into(),
locator: None,
initialize_args: None,
stop_on_entry: Some(false),
};
@ -391,8 +380,8 @@ impl AttachMode {
attach_picker,
})
}
fn debug_task(&self) -> task::AttachConfig {
task::AttachConfig { process_id: None }
fn debug_task(&self) -> task::AttachRequest {
task::AttachRequest { process_id: None }
}
}
@ -406,7 +395,7 @@ enum NewSessionMode {
}
impl NewSessionMode {
fn debug_task(&self, cx: &App) -> DebugRequestType {
fn debug_task(&self, cx: &App) -> DebugRequest {
match self {
NewSessionMode::Launch(entity) => entity.read(cx).debug_task(cx).into(),
NewSessionMode::Attach(entity) => entity.read(cx).debug_task().into(),
@ -488,7 +477,7 @@ impl NewSessionMode {
Self::Attach(AttachMode::new(debugger, project, window, cx))
}
fn launch(
past_launch_config: Option<LaunchConfig>,
past_launch_config: Option<LaunchRequest>,
window: &mut Window,
cx: &mut Context<NewSessionModal>,
) -> Self {

View file

@ -5,7 +5,7 @@ use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
use menu::Confirm;
use project::{FakeFs, Project};
use serde_json::json;
use task::{AttachConfig, DebugTaskDefinition, TCPHost};
use task::{AttachRequest, DebugTaskDefinition, TcpArgumentsTemplate};
use tests::{init_test, init_test_workspace};
#[gpui::test]
@ -31,13 +31,12 @@ async fn test_direct_attach_to_process(executor: BackgroundExecutor, cx: &mut Te
cx,
DebugTaskDefinition {
adapter: "fake-adapter".to_string(),
request: dap::DebugRequestType::Attach(AttachConfig {
request: dap::DebugRequest::Attach(AttachRequest {
process_id: Some(10),
}),
label: "label".to_string(),
initialize_args: None,
tcp_connection: None,
locator: None,
stop_on_entry: None,
},
|client| {
@ -105,11 +104,10 @@ async fn test_show_attach_modal_and_select_process(
project.clone(),
DebugTaskDefinition {
adapter: FakeAdapter::ADAPTER_NAME.into(),
request: dap::DebugRequestType::Attach(AttachConfig::default()),
request: dap::DebugRequest::Attach(AttachRequest::default()),
label: "attach example".into(),
initialize_args: None,
tcp_connection: Some(TCPHost::default()),
locator: None,
tcp_connection: Some(TcpArgumentsTemplate::default()),
stop_on_entry: None,
},
vec![

View file

@ -5111,44 +5111,21 @@ impl Editor {
CodeActionsItem::Task(task_source_kind, resolved_task) => {
match resolved_task.task_type() {
task::TaskType::Script => workspace.update(cx, |workspace, cx| {
workspace::tasks::schedule_resolved_task(
workspace,
workspace.schedule_resolved_task(
task_source_kind,
resolved_task,
false,
window,
cx,
);
Some(Task::ready(Ok(())))
}),
task::TaskType::Debug(debug_args) => {
if debug_args.locator.is_some() {
workspace.update(cx, |workspace, cx| {
workspace::tasks::schedule_resolved_task(
workspace,
task_source_kind,
resolved_task,
false,
cx,
);
});
return Some(Task::ready(Ok(())));
}
if let Some(project) = self.project.as_ref() {
project
.update(cx, |project, cx| {
project.start_debug_session(
resolved_task.resolved_debug_adapter_config().unwrap(),
cx,
)
})
.detach_and_log_err(cx);
Some(Task::ready(Ok(())))
} else {
Some(Task::ready(Ok(())))
}
task::TaskType::Debug(_) => {
workspace.update(cx, |workspace, cx| {
workspace.schedule_debug_task(resolved_task, window, cx);
});
Some(Task::ready(Ok(())))
}
}
}
@ -6845,12 +6822,12 @@ impl Editor {
resolved.reveal = reveal_strategy;
workspace
.update(cx, |workspace, cx| {
workspace::tasks::schedule_resolved_task(
workspace,
.update_in(cx, |workspace, window, cx| {
workspace.schedule_resolved_task(
task_source_kind,
resolved_task,
false,
window,
cx,
);
})

View file

@ -15,7 +15,6 @@ clap.workspace = true
client.workspace = true
collections.workspace = true
context_server.workspace = true
dap.workspace = true
dirs = "5.0"
env_logger.workspace = true
extension.workspace = true

View file

@ -3,7 +3,6 @@ use agent::{ThreadEvent, ThreadStore};
use anyhow::{Context as _, Result, anyhow};
use assistant_tool::ToolWorkingSet;
use client::proto::LspWorkProgress;
use dap::DapRegistry;
use futures::channel::mpsc;
use futures::{FutureExt, StreamExt as _, select_biased};
use gpui::{App, AppContext as _, AsyncApp, Entity, Task};
@ -243,7 +242,6 @@ impl Example {
app_state.node_runtime.clone(),
app_state.user_store.clone(),
app_state.languages.clone(),
Arc::new(DapRegistry::default()),
app_state.fs.clone(),
None,
cx,

View file

@ -14,7 +14,7 @@
pub mod breakpoint_store;
pub mod dap_command;
pub mod dap_store;
mod locator_store;
pub mod locators;
pub mod session;
#[cfg(any(feature = "test-support", test))]

View file

@ -1,16 +1,18 @@
use super::{
breakpoint_store::BreakpointStore,
locator_store::LocatorStore,
locators::DapLocator,
session::{self, Session, SessionStateEvent},
};
use crate::{ProjectEnvironment, debugger};
use crate::{
ProjectEnvironment, debugger, project_settings::ProjectSettings, worktree_store::WorktreeStore,
};
use anyhow::{Result, anyhow};
use async_trait::async_trait;
use collections::HashMap;
use dap::{
Capabilities, CompletionItem, CompletionsArguments, ErrorResponse, EvaluateArguments,
EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments, Source,
StartDebuggingRequestArguments,
Capabilities, CompletionItem, CompletionsArguments, DapRegistry, ErrorResponse,
EvaluateArguments, EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments,
Source, StartDebuggingRequestArguments,
adapters::{DapStatus, DebugAdapterBinary, DebugAdapterName},
client::SessionId,
messages::Message,
@ -22,8 +24,7 @@ use futures::{
future::{Shared, join_all},
};
use gpui::{
App, AppContext, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, SharedString,
Task, WeakEntity,
App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task, WeakEntity,
};
use http_client::HttpClient;
use language::{BinaryStatus, LanguageRegistry, LanguageToolchainStore};
@ -35,17 +36,16 @@ use rpc::{
proto::{self},
};
use serde_json::Value;
use settings::WorktreeId;
use settings::{Settings, WorktreeId};
use smol::{lock::Mutex, stream::StreamExt};
use std::{
borrow::Borrow,
collections::{BTreeMap, HashSet},
ffi::OsStr,
path::{Path, PathBuf},
sync::{Arc, atomic::Ordering::SeqCst},
sync::Arc,
};
use std::{collections::VecDeque, sync::atomic::AtomicU32};
use task::DebugTaskDefinition;
use task::{DebugTaskDefinition, DebugTaskTemplate};
use util::ResultExt as _;
use worktree::Worktree;
@ -71,45 +71,26 @@ pub enum DapStoreEvent {
}
#[allow(clippy::large_enum_variant)]
pub enum DapStoreMode {
Local(LocalDapStore), // ssh host and collab host
Remote(RemoteDapStore), // collab guest
enum DapStoreMode {
Local(LocalDapStore),
Ssh(SshDapStore),
Collab,
}
pub struct LocalDapStore {
fs: Arc<dyn Fs>,
node_runtime: NodeRuntime,
next_session_id: AtomicU32,
http_client: Arc<dyn HttpClient>,
environment: Entity<ProjectEnvironment>,
language_registry: Arc<LanguageRegistry>,
worktree_store: Entity<WorktreeStore>,
toolchain_store: Arc<dyn LanguageToolchainStore>,
locator_store: Arc<LocatorStore>,
start_debugging_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
_start_debugging_task: Task<()>,
locators: HashMap<String, Arc<dyn DapLocator>>,
}
impl LocalDapStore {
fn next_session_id(&self) -> SessionId {
SessionId(self.next_session_id.fetch_add(1, SeqCst))
}
pub(crate) fn locate_binary(
&self,
mut definition: DebugTaskDefinition,
executor: BackgroundExecutor,
) -> Task<DebugTaskDefinition> {
let locator_store = self.locator_store.clone();
executor.spawn(async move {
let _ = locator_store.resolve_debug_config(&mut definition).await;
definition
})
}
}
pub struct RemoteDapStore {
pub struct SshDapStore {
upstream_client: AnyProtoClient,
upstream_project_id: u64,
event_queue: Option<VecDeque<DapStoreEvent>>,
}
pub struct DapStore {
@ -117,25 +98,17 @@ pub struct DapStore {
downstream_client: Option<(AnyProtoClient, u64)>,
breakpoint_store: Entity<BreakpointStore>,
sessions: BTreeMap<SessionId, Entity<Session>>,
next_session_id: u32,
start_debugging_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
_start_debugging_task: Task<()>,
}
impl EventEmitter<DapStoreEvent> for DapStore {}
impl DapStore {
pub fn init(_client: &AnyProtoClient) {
// todo(debugger): Reenable these after we finish handle_dap_command refactor
// client.add_entity_request_handler(Self::handle_dap_command::<NextCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<StepInCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<StepOutCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<StepBackCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<ContinueCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<PauseCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<DisconnectCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<TerminateThreadsCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<TerminateCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<RestartCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<VariablesCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<RestartStackFrameCommand>);
pub fn init(client: &AnyProtoClient) {
client.add_entity_request_handler(Self::handle_run_debug_locator);
client.add_entity_request_handler(Self::handle_get_debug_adapter_binary);
}
#[expect(clippy::too_many_arguments)]
@ -146,15 +119,62 @@ impl DapStore {
language_registry: Arc<LanguageRegistry>,
environment: Entity<ProjectEnvironment>,
toolchain_store: Arc<dyn LanguageToolchainStore>,
worktree_store: Entity<WorktreeStore>,
breakpoint_store: Entity<BreakpointStore>,
cx: &mut Context<Self>,
) -> Self {
cx.on_app_quit(Self::shutdown_sessions).detach();
let locators = HashMap::from_iter([(
"cargo".to_string(),
Arc::new(super::locators::cargo::CargoLocator {}) as _,
)]);
let mode = DapStoreMode::Local(LocalDapStore {
fs,
environment,
http_client,
node_runtime,
toolchain_store,
worktree_store,
language_registry,
locators,
});
Self::new(mode, breakpoint_store, cx)
}
pub fn new_ssh(
project_id: u64,
upstream_client: AnyProtoClient,
breakpoint_store: Entity<BreakpointStore>,
cx: &mut Context<Self>,
) -> Self {
let mode = DapStoreMode::Ssh(SshDapStore {
upstream_client,
upstream_project_id: project_id,
});
Self::new(mode, breakpoint_store, cx)
}
pub fn new_collab(
_project_id: u64,
_upstream_client: AnyProtoClient,
breakpoint_store: Entity<BreakpointStore>,
cx: &mut Context<Self>,
) -> Self {
Self::new(DapStoreMode::Collab, breakpoint_store, cx)
}
fn new(
mode: DapStoreMode,
breakpoint_store: Entity<BreakpointStore>,
cx: &mut Context<Self>,
) -> Self {
let (start_debugging_tx, mut message_rx) =
futures::channel::mpsc::unbounded::<(SessionId, Message)>();
let _start_debugging_task = cx.spawn(async move |this, cx| {
let task = cx.spawn(async move |this, cx| {
while let Some((session_id, message)) = message_rx.next().await {
match message {
Message::Request(request) => {
@ -174,94 +194,135 @@ impl DapStore {
}
}
});
Self {
mode: DapStoreMode::Local(LocalDapStore {
fs,
environment,
http_client,
node_runtime,
toolchain_store,
language_registry,
start_debugging_tx,
_start_debugging_task,
locator_store: Arc::from(LocatorStore::new()),
next_session_id: Default::default(),
}),
mode,
_start_debugging_task: task,
start_debugging_tx,
next_session_id: 0,
downstream_client: None,
breakpoint_store,
sessions: Default::default(),
}
}
pub fn new_remote(
project_id: u64,
upstream_client: AnyProtoClient,
breakpoint_store: Entity<BreakpointStore>,
) -> Self {
Self {
mode: DapStoreMode::Remote(RemoteDapStore {
upstream_client,
upstream_project_id: project_id,
event_queue: Some(VecDeque::default()),
}),
downstream_client: None,
breakpoint_store,
sessions: Default::default(),
}
}
pub fn as_remote(&self) -> Option<&RemoteDapStore> {
pub fn get_debug_adapter_binary(
&mut self,
definition: DebugTaskDefinition,
cx: &mut Context<Self>,
) -> Task<Result<DebugAdapterBinary>> {
match &self.mode {
DapStoreMode::Remote(remote_dap_store) => Some(remote_dap_store),
_ => None,
DapStoreMode::Local(local) => {
let Some(worktree) = local.worktree_store.read(cx).visible_worktrees(cx).next()
else {
return Task::ready(Err(anyhow!("Failed to find a worktree")));
};
let Some(adapter) = DapRegistry::global(cx).adapter(&definition.adapter) else {
return Task::ready(Err(anyhow!("Failed to find a debug adapter")));
};
let user_installed_path = ProjectSettings::get_global(cx)
.dap
.get(&adapter.name())
.and_then(|s| s.binary.as_ref().map(PathBuf::from));
let delegate = self.delegate(&worktree, cx);
let cwd: Arc<Path> = definition
.cwd()
.unwrap_or(worktree.read(cx).abs_path().as_ref())
.into();
cx.spawn(async move |this, cx| {
let mut binary = adapter
.get_binary(&delegate, &definition, user_installed_path, cx)
.await?;
let env = this
.update(cx, |this, cx| {
this.as_local()
.unwrap()
.environment
.update(cx, |environment, cx| {
environment.get_directory_environment(cwd, cx)
})
})?
.await;
if let Some(mut env) = env {
env.extend(std::mem::take(&mut binary.envs));
binary.envs = env;
}
Ok(binary)
})
}
DapStoreMode::Ssh(ssh) => {
let request = ssh.upstream_client.request(proto::GetDebugAdapterBinary {
project_id: ssh.upstream_project_id,
task: Some(definition.to_proto()),
});
cx.background_spawn(async move {
let response = request.await?;
DebugAdapterBinary::from_proto(response)
})
}
DapStoreMode::Collab => {
Task::ready(Err(anyhow!("Debugging is not yet supported via collab")))
}
}
}
pub fn remote_event_queue(&mut self) -> Option<VecDeque<DapStoreEvent>> {
if let DapStoreMode::Remote(remote) = &mut self.mode {
remote.event_queue.take()
} else {
None
pub fn run_debug_locator(
&mut self,
template: DebugTaskTemplate,
cx: &mut Context<Self>,
) -> Task<Result<DebugTaskDefinition>> {
let Some(locator_name) = template.locator else {
return Task::ready(Ok(template.definition));
};
match &self.mode {
DapStoreMode::Local(local) => {
if let Some(locator) = local.locators.get(&locator_name).cloned() {
cx.background_spawn(
async move { locator.run_locator(template.definition).await },
)
} else {
Task::ready(Err(anyhow!("Couldn't find locator {}", locator_name)))
}
}
DapStoreMode::Ssh(ssh) => {
let request = ssh.upstream_client.request(proto::RunDebugLocator {
project_id: ssh.upstream_project_id,
locator: locator_name,
task: Some(template.definition.to_proto()),
});
cx.background_spawn(async move {
let response = request.await?;
DebugTaskDefinition::from_proto(response)
})
}
DapStoreMode::Collab => {
Task::ready(Err(anyhow!("Debugging is not yet supported via collab")))
}
}
}
pub fn as_local(&self) -> Option<&LocalDapStore> {
fn as_local(&self) -> Option<&LocalDapStore> {
match &self.mode {
DapStoreMode::Local(local_dap_store) => Some(local_dap_store),
_ => None,
}
}
pub fn as_local_mut(&mut self) -> Option<&mut LocalDapStore> {
match &mut self.mode {
DapStoreMode::Local(local_dap_store) => Some(local_dap_store),
_ => None,
}
}
pub fn upstream_client(&self) -> Option<(AnyProtoClient, u64)> {
match &self.mode {
DapStoreMode::Remote(RemoteDapStore {
upstream_client,
upstream_project_id,
..
}) => Some((upstream_client.clone(), *upstream_project_id)),
DapStoreMode::Local(_) => None,
}
}
pub fn downstream_client(&self) -> Option<&(AnyProtoClient, u64)> {
self.downstream_client.as_ref()
}
pub fn add_remote_client(
&mut self,
session_id: SessionId,
ignore: Option<bool>,
cx: &mut Context<Self>,
) {
if let DapStoreMode::Remote(remote) = &self.mode {
if let DapStoreMode::Ssh(remote) = &self.mode {
self.sessions.insert(
session_id,
cx.new(|_| {
@ -328,7 +389,7 @@ impl DapStore {
Ok(())
}
pub fn delegate(&self, worktree: &Entity<Worktree>, cx: &mut App) -> DapAdapterDelegate {
fn delegate(&self, worktree: &Entity<Worktree>, cx: &mut App) -> DapAdapterDelegate {
let Some(local_store) = self.as_local() else {
unimplemented!("Starting session on remote side");
};
@ -354,11 +415,7 @@ impl DapStore {
parent_session: Option<Entity<Session>>,
cx: &mut Context<Self>,
) -> (SessionId, Task<Result<Entity<Session>>>) {
let Some(local_store) = self.as_local() else {
unimplemented!("Starting session on remote side");
};
let session_id = local_store.next_session_id();
let session_id = SessionId(util::post_inc(&mut self.next_session_id));
if let Some(session) = &parent_session {
session.update(cx, |session, _| {
@ -368,7 +425,7 @@ impl DapStore {
let (initialized_tx, initialized_rx) = oneshot::channel();
let start_debugging_tx = local_store.start_debugging_tx.clone();
let start_debugging_tx = self.start_debugging_tx.clone();
let task = cx.spawn(async move |this, cx| {
let start_client_task = this.update(cx, |this, cx| {
@ -682,10 +739,6 @@ impl DapStore {
session_id: SessionId,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
let Some(_) = self.as_local_mut() else {
return Task::ready(Err(anyhow!("Cannot shutdown session on remote side")));
};
let Some(session) = self.sessions.remove(&session_id) else {
return Task::ready(Err(anyhow!("Could not find session: {:?}", session_id)));
};
@ -748,6 +801,45 @@ impl DapStore {
cx.notify();
}
async fn handle_run_debug_locator(
this: Entity<Self>,
envelope: TypedEnvelope<proto::RunDebugLocator>,
mut cx: AsyncApp,
) -> Result<proto::DebugTaskDefinition> {
let template = DebugTaskTemplate {
locator: Some(envelope.payload.locator),
definition: DebugTaskDefinition::from_proto(
envelope
.payload
.task
.ok_or_else(|| anyhow!("missing definition"))?,
)?,
};
let definition = this
.update(&mut cx, |this, cx| this.run_debug_locator(template, cx))?
.await?;
Ok(definition.to_proto())
}
async fn handle_get_debug_adapter_binary(
this: Entity<Self>,
envelope: TypedEnvelope<proto::GetDebugAdapterBinary>,
mut cx: AsyncApp,
) -> Result<proto::DebugAdapterBinary> {
let definition = DebugTaskDefinition::from_proto(
envelope
.payload
.task
.ok_or_else(|| anyhow!("missing definition"))?,
)?;
let binary = this
.update(&mut cx, |this, cx| {
this.get_debug_adapter_binary(definition, cx)
})?
.await?;
Ok(binary.to_proto())
}
}
fn create_new_session(

View file

@ -3,10 +3,10 @@ use cargo::CargoLocator;
use collections::HashMap;
use gpui::SharedString;
use locators::DapLocator;
use task::DebugTaskDefinition;
use task::{DebugTaskDefinition, DebugTaskTemplate};
mod cargo;
mod locators;
pub mod locators;
pub(super) struct LocatorStore {
locators: HashMap<SharedString, Box<dyn DapLocator>>,
@ -14,24 +14,19 @@ pub(super) struct LocatorStore {
impl LocatorStore {
pub(super) fn new() -> Self {
let locators = HashMap::from_iter([(
SharedString::new("cargo"),
Box::new(CargoLocator {}) as Box<dyn DapLocator>,
)]);
Self { locators }
}
pub(super) async fn resolve_debug_config(
&self,
debug_config: &mut DebugTaskDefinition,
) -> Result<()> {
let Some(locator_name) = &debug_config.locator else {
log::debug!("Attempted to resolve debug config without a locator field");
return Ok(());
template: DebugTaskTemplate,
) -> Result<DebugTaskDefinition> {
let Some(locator_name) = &template.locator else {
return Ok(template.definition);
};
if let Some(locator) = self.locators.get(locator_name as &str) {
locator.run_locator(debug_config).await
locator.run_locator(template.definition).await
} else {
Err(anyhow!("Couldn't find locator {}", locator_name))
}

View file

@ -2,7 +2,9 @@ use anyhow::Result;
use async_trait::async_trait;
use task::DebugTaskDefinition;
pub(crate) mod cargo;
#[async_trait]
pub(super) trait DapLocator: Send + Sync {
async fn run_locator(&self, debug_config: &mut DebugTaskDefinition) -> Result<()>;
async fn run_locator(&self, debug_config: DebugTaskDefinition) -> Result<DebugTaskDefinition>;
}

View file

@ -8,7 +8,7 @@ use smol::{
};
use task::DebugTaskDefinition;
pub(super) struct CargoLocator;
pub(crate) struct CargoLocator;
async fn find_best_executable(executables: &[String], test_name: &str) -> Option<String> {
if executables.len() == 1 {
@ -37,9 +37,12 @@ async fn find_best_executable(executables: &[String], test_name: &str) -> Option
}
#[async_trait]
impl DapLocator for CargoLocator {
async fn run_locator(&self, debug_config: &mut DebugTaskDefinition) -> Result<()> {
async fn run_locator(
&self,
mut debug_config: DebugTaskDefinition,
) -> Result<DebugTaskDefinition> {
let Some(launch_config) = (match &mut debug_config.request {
task::DebugRequestType::Launch(launch_config) => Some(launch_config),
task::DebugRequest::Launch(launch_config) => Some(launch_config),
_ => None,
}) else {
return Err(anyhow!("Couldn't get launch config in locator"));
@ -119,6 +122,6 @@ impl DapLocator for CargoLocator {
if let Some(test_name) = test_name {
launch_config.args.push(test_name);
}
Ok(())
Ok(debug_config)
}
}

View file

@ -396,7 +396,7 @@ impl LocalMode {
}
fn request_initialization(&self, cx: &App) -> Task<Result<Capabilities>> {
let adapter_id = self.binary.adapter_name.to_string();
let adapter_id = self.definition.adapter.clone();
self.request(Initialize { adapter_id }, cx.background_executor().clone())
}

View file

@ -1,7 +1,7 @@
use std::{path::Path, sync::Arc};
use anyhow::Result;
use dap::{DebugRequestType, client::DebugAdapterClient};
use dap::{DebugRequest, client::DebugAdapterClient};
use gpui::{App, AppContext, Entity, Subscription, Task};
use task::DebugTaskDefinition;
@ -53,11 +53,10 @@ pub fn start_debug_session<T: Fn(&Arc<DebugAdapterClient>) + 'static>(
cx,
DebugTaskDefinition {
adapter: "fake-adapter".to_string(),
request: DebugRequestType::Launch(Default::default()),
request: DebugRequest::Launch(Default::default()),
label: "test".to_string(),
initialize_args: None,
tcp_connection: None,
locator: None,
stop_on_entry: None,
},
configure,

View file

@ -39,7 +39,10 @@ use client::{
};
use clock::ReplicaId;
use dap::{DapRegistry, client::DebugAdapterClient};
use dap::{
adapters::{DebugAdapterBinary, TcpArguments},
client::DebugAdapterClient,
};
use collections::{BTreeSet, HashMap, HashSet};
use debounced_delay::DebouncedDelay;
@ -94,6 +97,7 @@ use snippet::Snippet;
use snippet_provider::SnippetProvider;
use std::{
borrow::Cow,
net::Ipv4Addr,
ops::Range,
path::{Component, Path, PathBuf},
pin::pin,
@ -103,7 +107,7 @@ use std::{
};
use task_store::TaskStore;
use terminals::Terminals;
use terminals::{SshCommand, Terminals, wrap_for_ssh};
use text::{Anchor, BufferId};
use toolchain_store::EmptyToolchainStore;
use util::{
@ -165,7 +169,6 @@ 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>,
@ -834,7 +837,6 @@ 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,
@ -873,6 +875,7 @@ impl Project {
languages.clone(),
environment.clone(),
toolchain_store.read(cx).as_language_toolchain_store(),
worktree_store.clone(),
breakpoint_store.clone(),
cx,
)
@ -955,7 +958,6 @@ impl Project {
active_entry: None,
snippets,
languages,
debug_adapters,
client,
task_store,
user_store,
@ -1065,13 +1067,14 @@ impl Project {
cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
let breakpoint_store =
cx.new(|_| BreakpointStore::remote(SSH_PROJECT_ID, client.clone().into()));
cx.new(|_| BreakpointStore::remote(SSH_PROJECT_ID, ssh_proto.clone()));
let dap_store = cx.new(|_| {
DapStore::new_remote(
let dap_store = cx.new(|cx| {
DapStore::new_ssh(
SSH_PROJECT_ID,
client.clone().into(),
ssh_proto.clone(),
breakpoint_store.clone(),
cx,
)
});
@ -1113,7 +1116,6 @@ impl Project {
active_entry: None,
snippets,
languages,
debug_adapters: Arc::new(DapRegistry::default()),
client,
task_store,
user_store,
@ -1251,8 +1253,13 @@ 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())
let dap_store = cx.new(|cx| {
DapStore::new_collab(
remote_id,
client.clone().into(),
breakpoint_store.clone(),
cx,
)
})?;
let lsp_store = cx.new(|cx| {
@ -1337,7 +1344,6 @@ 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,
@ -1459,49 +1465,68 @@ impl Project {
pub fn start_debug_session(
&mut self,
config: DebugTaskDefinition,
definition: DebugTaskDefinition,
cx: &mut Context<Self>,
) -> Task<Result<Entity<Session>>> {
let Some(worktree) = self.worktrees(cx).find(|tree| tree.read(cx).is_visible()) else {
return Task::ready(Err(anyhow!("Failed to find a worktree")));
};
let Some(adapter) = self.debug_adapters.adapter(&config.adapter) else {
return Task::ready(Err(anyhow!("Failed to find a debug adapter")));
};
let user_installed_path = ProjectSettings::get_global(cx)
.dap
.get(&adapter.name())
.and_then(|s| s.binary.as_ref().map(PathBuf::from));
let ssh_client = self.ssh_client().clone();
let result = cx.spawn(async move |this, cx| {
let delegate = this.update(cx, |project, cx| {
project
.dap_store
.update(cx, |dap_store, cx| dap_store.delegate(&worktree, cx))
})?;
let task = this.update(cx, |project, cx| {
project.dap_store.read(cx).as_local().and_then(|local| {
config.locator.is_some().then(|| {
local.locate_binary(config.clone(), cx.background_executor().clone())
let mut binary = this
.update(cx, |this, cx| {
this.dap_store.update(cx, |dap_store, cx| {
dap_store.get_debug_adapter_binary(definition.clone(), cx)
})
})
})?;
let config = if let Some(task) = task {
task.await
} else {
config
};
let binary = adapter
.get_binary(&delegate, &config, user_installed_path, cx)
})?
.await?;
if let Some(ssh_client) = ssh_client {
let mut ssh_command = ssh_client.update(cx, |ssh, _| {
anyhow::Ok(SshCommand {
arguments: ssh
.ssh_args()
.ok_or_else(|| anyhow!("SSH arguments not found"))?,
})
})??;
let mut connection = None;
if let Some(c) = binary.connection {
let local_bind_addr = Ipv4Addr::new(127, 0, 0, 1);
let port = dap::transport::TcpTransport::unused_port(local_bind_addr).await?;
ssh_command.add_port_forwarding(port, c.host.to_string(), c.port);
connection = Some(TcpArguments {
port: c.port,
host: local_bind_addr,
timeout: c.timeout,
})
}
let (program, args) = wrap_for_ssh(
&ssh_command,
Some((&binary.command, &binary.arguments)),
binary.cwd.as_deref(),
binary.envs,
None,
);
binary = DebugAdapterBinary {
command: program,
arguments: args,
envs: HashMap::default(),
cwd: None,
connection,
request_args: binary.request_args,
}
};
let ret = this
.update(cx, |project, cx| {
project.dap_store.update(cx, |dap_store, cx| {
dap_store.new_session(binary, config, worktree.downgrade(), None, cx)
dap_store.new_session(binary, definition, worktree.downgrade(), None, cx)
})
})?
.1
@ -1520,7 +1545,6 @@ 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
@ -1534,7 +1558,6 @@ impl Project {
node_runtime::NodeRuntime::unavailable(),
user_store,
Arc::new(languages),
debug_adapters,
fs,
None,
cx,
@ -1565,7 +1588,6 @@ 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));
@ -1576,7 +1598,6 @@ impl Project {
node_runtime::NodeRuntime::unavailable(),
user_store,
Arc::new(languages),
Arc::new(debug_adapters),
fs,
None,
cx,
@ -1620,10 +1641,6 @@ impl Project {
&self.languages
}
pub fn debug_adapters(&self) -> &Arc<DapRegistry> {
&self.debug_adapters
}
pub fn client(&self) -> Arc<Client> {
self.client.clone()
}

View file

@ -19,7 +19,7 @@ use language::{
use lsp::{LanguageServerId, LanguageServerName};
use settings::{InvalidSettingsError, TaskKind, parse_json_with_comments};
use task::{
DebugTaskDefinition, ResolvedTask, TaskContext, TaskId, TaskTemplate, TaskTemplates,
DebugTaskTemplate, ResolvedTask, TaskContext, TaskId, TaskTemplate, TaskTemplates,
TaskVariables, VariableName,
};
use text::{BufferId, Point, ToPoint};
@ -435,9 +435,9 @@ impl Inventory {
.into_iter()
.filter_map(|raw_template| match &task_kind {
TaskKind::Script => serde_json::from_value::<TaskTemplate>(raw_template).log_err(),
TaskKind::Debug => serde_json::from_value::<DebugTaskDefinition>(raw_template)
TaskKind::Debug => serde_json::from_value::<DebugTaskTemplate>(raw_template)
.log_err()
.and_then(|content| content.to_zed_format().log_err()),
.map(|content| content.to_zed_format()),
});
let parsed_templates = &mut self.templates_from_settings;

View file

@ -9,20 +9,16 @@ use smol::channel::bounded;
use std::{
borrow::Cow,
env::{self},
iter,
path::{Path, PathBuf},
sync::Arc,
};
use task::{Shell, ShellBuilder, SpawnInTerminal};
use task::{DEFAULT_REMOTE_SHELL, Shell, ShellBuilder, SpawnInTerminal};
use terminal::{
TaskState, TaskStatus, Terminal, TerminalBuilder,
terminal_settings::{self, TerminalSettings, VenvSettings},
};
use util::ResultExt;
// #[cfg(target_os = "macos")]
// use std::os::unix::ffi::OsStrExt;
pub struct Terminals {
pub(crate) local_handles: Vec<WeakEntity<terminal::Terminal>>,
}
@ -48,7 +44,15 @@ pub enum TerminalKind {
/// SshCommand describes how to connect to a remote server
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SshCommand {
arguments: Vec<String>,
pub arguments: Vec<String>,
}
impl SshCommand {
pub fn add_port_forwarding(&mut self, local_port: u16, host: String, remote_port: u16) {
self.arguments.push("-L".to_string());
self.arguments
.push(format!("{}:{}:{}", local_port, host, remote_port));
}
}
impl Project {
@ -551,7 +555,7 @@ impl Project {
}
}
fn wrap_for_ssh(
pub fn wrap_for_ssh(
ssh_command: &SshCommand,
command: Option<(&String, &Vec<String>)>,
path: Option<&Path>,
@ -559,9 +563,14 @@ fn wrap_for_ssh(
venv_directory: Option<&Path>,
) -> (String, Vec<String>) {
let to_run = if let Some((command, args)) = command {
let command = Cow::Borrowed(command.as_str());
// DEFAULT_REMOTE_SHELL is '"${SHELL:-sh}"' so must not be escaped
let command: Option<Cow<str>> = if command == DEFAULT_REMOTE_SHELL {
Some(command.into())
} else {
shlex::try_quote(command).ok()
};
let args = args.iter().filter_map(|arg| shlex::try_quote(arg).ok());
iter::once(command).chain(args).join(" ")
command.into_iter().chain(args).join(" ")
} else {
"exec ${SHELL:-sh} -l".to_string()
};

View file

@ -508,7 +508,6 @@ enum DapStackPresentationHint {
Subtle = 2;
StackUnknown = 3;
}
message DapModule {
DapModuleId id = 1;
string name = 2;
@ -522,9 +521,62 @@ message DapModule {
optional string address_range = 10;
}
message DebugTaskDefinition {
string adapter = 1;
string label = 2;
oneof request {
DebugLaunchRequest debug_launch_request = 3;
DebugAttachRequest debug_attach_request = 4;
}
optional string initialize_args = 5;
optional TcpHost tcp_connection = 6;
optional bool stop_on_entry = 7;
}
message TcpHost {
optional uint32 port = 1;
optional string host = 2;
optional uint64 timeout = 3;
}
message DebugLaunchRequest {
string program = 1;
optional string cwd = 2;
repeated string args = 3;
}
message DebugAttachRequest {
uint32 process_id = 1;
}
message DapModuleId {
oneof id {
uint32 number = 1;
string string = 2;
}
}
message GetDebugAdapterBinary {
uint64 project_id = 1;
DebugTaskDefinition task = 2;
}
message DebugAdapterBinary {
string command = 1;
repeated string arguments = 2;
map<string, string> envs = 3;
optional string cwd = 4;
optional TcpHost connection = 5;
string configuration = 7;
LaunchType launch_type = 8;
enum LaunchType {
Attach = 0;
Launch = 1;
}
}
message RunDebugLocator {
uint64 project_id = 1;
string locator = 2;
DebugTaskDefinition task = 3;
}

View file

@ -380,7 +380,12 @@ message Envelope {
StopLanguageServers stop_language_servers = 336;
LspExtRunnables lsp_ext_runnables = 337;
LspExtRunnablesResponse lsp_ext_runnables_response = 338; // current max
LspExtRunnablesResponse lsp_ext_runnables_response = 338;
GetDebugAdapterBinary get_debug_adapter_binary = 339;
DebugAdapterBinary debug_adapter_binary = 340;
RunDebugLocator run_debug_locator = 341;
DebugTaskDefinition debug_task_definition = 342; // current max
}
reserved 87 to 88;

View file

@ -302,6 +302,10 @@ messages!(
(GitDiff, Background),
(GitDiffResponse, Background),
(GitInit, Background),
(GetDebugAdapterBinary, Background),
(DebugAdapterBinary, Background),
(RunDebugLocator, Background),
(DebugTaskDefinition, Background),
);
request_messages!(
@ -460,6 +464,8 @@ request_messages!(
(GitDiff, GitDiffResponse),
(GitInit, Ack),
(ToggleBreakpoint, Ack),
(GetDebugAdapterBinary, DebugAdapterBinary),
(RunDebugLocator, DebugTaskDefinition),
);
entity_messages!(
@ -579,6 +585,8 @@ entity_messages!(
GitInit,
BreakpointsForFile,
ToggleBreakpoint,
RunDebugLocator,
GetDebugAdapterBinary,
);
entity_messages!(

View file

@ -570,7 +570,6 @@ 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,

View file

@ -29,7 +29,7 @@ backtrace = "0.3"
chrono.workspace = true
clap.workspace = true
client.workspace = true
dap.workspace = true
dap_adapters.workspace = true
env_logger.workspace = true
extension.workspace = true
extension_host.workspace = true

View file

@ -1,6 +1,6 @@
use ::proto::{FromProto, ToProto};
use anyhow::{Result, anyhow};
use dap::DapRegistry;
use extension::ExtensionHostProxy;
use extension_host::headless_host::HeadlessExtensionStore;
use fs::Fs;
@ -41,6 +41,7 @@ pub struct HeadlessProject {
pub buffer_store: Entity<BufferStore>,
pub lsp_store: Entity<LspStore>,
pub task_store: Entity<TaskStore>,
pub dap_store: Entity<DapStore>,
pub settings_observer: Entity<SettingsObserver>,
pub next_entry_id: Arc<AtomicUsize>,
pub languages: Arc<LanguageRegistry>,
@ -54,7 +55,6 @@ 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>,
}
@ -72,7 +72,6 @@ impl HeadlessProject {
http_client,
node_runtime,
languages,
debug_adapters: _debug_adapters,
extension_host_proxy: proxy,
}: HeadlessAppState,
cx: &mut Context<Self>,
@ -114,6 +113,7 @@ impl HeadlessProject {
languages.clone(),
environment.clone(),
toolchain_store.read(cx).as_language_toolchain_store(),
worktree_store.clone(),
breakpoint_store.clone(),
cx,
)
@ -258,6 +258,7 @@ impl HeadlessProject {
buffer_store,
lsp_store,
task_store,
dap_store,
next_entry_id: Default::default(),
languages,
extensions,

View file

@ -4,7 +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};
@ -1566,7 +1566,6 @@ 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| {
@ -1579,7 +1578,6 @@ pub async fn init_test(
http_client,
node_runtime,
languages,
debug_adapters,
extension_host_proxy: proxy,
},
cx,

View file

@ -3,7 +3,7 @@ use crate::headless_project::HeadlessAppState;
use anyhow::{Context as _, Result, anyhow};
use chrono::Utc;
use client::{ProxySettings, telemetry};
use dap::DapRegistry;
use extension::ExtensionHostProxy;
use fs::{Fs, RealFs};
use futures::channel::mpsc;
@ -441,6 +441,7 @@ pub fn execute_run(
GitHostingProviderRegistry::set_global(git_hosting_provider_registry, cx);
git_hosting_providers::init(cx);
dap_adapters::init(cx);
extension::init(cx);
let extension_host_proxy = ExtensionHostProxy::global(cx);
@ -472,7 +473,6 @@ 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 {
@ -481,7 +481,6 @@ pub fn execute_run(
http_client,
node_runtime,
languages,
debug_adapters,
extension_host_proxy,
},
cx,

View file

@ -17,11 +17,11 @@ workspace = true
[dependencies]
anyhow.workspace = true
collections.workspace = true
dap-types.workspace = true
futures.workspace = true
gpui.workspace = true
hex.workspace = true
parking_lot.workspace = true
proto.workspace = true
schemars.workspace = true
serde.workspace = true
serde_json.workspace = true

View file

@ -1,21 +1,14 @@
use dap_types::StartDebuggingRequestArguments;
use anyhow::Result;
use schemars::{JsonSchema, r#gen::SchemaSettings};
use serde::{Deserialize, Serialize};
use std::net::Ipv4Addr;
use std::path::PathBuf;
use util::ResultExt;
use std::{net::Ipv4Addr, path::Path};
use crate::{TaskTemplate, TaskTemplates, TaskType, task_template::DebugArgs};
impl Default for DebugConnectionType {
fn default() -> Self {
DebugConnectionType::TCP(TCPHost::default())
}
}
use crate::{TaskTemplate, TaskType, task_template::DebugArgs};
/// Represents the host information of the debug adapter
#[derive(Default, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
pub struct TCPHost {
pub struct TcpArgumentsTemplate {
/// The port that the debug adapter is listening on
///
/// Default: We will try to find an open port
@ -30,23 +23,39 @@ pub struct TCPHost {
pub timeout: Option<u64>,
}
impl TCPHost {
impl TcpArgumentsTemplate {
/// Get the host or fallback to the default host
pub fn host(&self) -> Ipv4Addr {
self.host.unwrap_or_else(|| Ipv4Addr::new(127, 0, 0, 1))
}
pub fn from_proto(proto: proto::TcpHost) -> Result<Self> {
Ok(Self {
port: proto.port.map(|p| p.try_into()).transpose()?,
host: proto.host.map(|h| h.parse()).transpose()?,
timeout: proto.timeout,
})
}
pub fn to_proto(&self) -> proto::TcpHost {
proto::TcpHost {
port: self.port.map(|p| p.into()),
host: self.host.map(|h| h.to_string()),
timeout: self.timeout,
}
}
}
/// Represents the attach request information of the debug adapter
#[derive(Default, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
pub struct AttachConfig {
pub struct AttachRequest {
/// The processId to attach to, if left empty we will show a process picker
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 {
pub struct LaunchRequest {
/// The program that you trying to debug
pub program: String,
/// The current working directory of your project
@ -59,47 +68,26 @@ pub struct LaunchConfig {
/// Represents the type that will determine which request to call on the debug adapter
#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
#[serde(rename_all = "lowercase", untagged)]
pub enum DebugRequestType {
pub enum DebugRequest {
/// Call the `launch` request on the debug adapter
Launch(LaunchConfig),
Launch(LaunchRequest),
/// Call the `attach` request on the debug adapter
Attach(AttachConfig),
Attach(AttachRequest),
}
impl From<LaunchConfig> for DebugRequestType {
fn from(launch_config: LaunchConfig) -> Self {
DebugRequestType::Launch(launch_config)
impl From<LaunchRequest> for DebugRequest {
fn from(launch_config: LaunchRequest) -> Self {
DebugRequest::Launch(launch_config)
}
}
impl From<AttachConfig> for DebugRequestType {
fn from(attach_config: AttachConfig) -> Self {
DebugRequestType::Attach(attach_config)
}
}
/// 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 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::UserConfigured(DebugRequestType::Launch(launch_config)) => {
launch_config.cwd.clone()
}
_ => None,
}
impl From<AttachRequest> for DebugRequest {
fn from(attach_config: AttachRequest) -> Self {
DebugRequest::Attach(attach_config)
}
}
impl TryFrom<TaskTemplate> for DebugTaskDefinition {
impl TryFrom<TaskTemplate> for DebugTaskTemplate {
type Error = ();
fn try_from(value: TaskTemplate) -> Result<Self, Self::Error> {
@ -108,40 +96,40 @@ impl TryFrom<TaskTemplate> for DebugTaskDefinition {
};
let request = match debug_args.request {
crate::DebugArgsRequest::Launch => DebugRequestType::Launch(LaunchConfig {
crate::DebugArgsRequest::Launch => DebugRequest::Launch(LaunchRequest {
program: value.command,
cwd: value.cwd.map(PathBuf::from),
args: value.args,
}),
crate::DebugArgsRequest::Attach(attach_config) => {
DebugRequestType::Attach(attach_config)
}
crate::DebugArgsRequest::Attach(attach_config) => DebugRequest::Attach(attach_config),
};
Ok(DebugTaskDefinition {
adapter: debug_args.adapter,
request,
label: value.label,
initialize_args: debug_args.initialize_args,
tcp_connection: debug_args.tcp_connection,
Ok(DebugTaskTemplate {
locator: debug_args.locator,
stop_on_entry: debug_args.stop_on_entry,
definition: DebugTaskDefinition {
adapter: debug_args.adapter,
request,
label: value.label,
initialize_args: debug_args.initialize_args,
tcp_connection: debug_args.tcp_connection,
stop_on_entry: debug_args.stop_on_entry,
},
})
}
}
impl DebugTaskDefinition {
impl DebugTaskTemplate {
/// Translate from debug definition to a task template
pub fn to_zed_format(self) -> anyhow::Result<TaskTemplate> {
let (command, cwd, request) = match self.request {
DebugRequestType::Launch(launch_config) => (
pub fn to_zed_format(self) -> TaskTemplate {
let (command, cwd, request) = match self.definition.request {
DebugRequest::Launch(launch_config) => (
launch_config.program,
launch_config
.cwd
.map(|cwd| cwd.to_string_lossy().to_string()),
crate::task_template::DebugArgsRequest::Launch,
),
DebugRequestType::Attach(attach_config) => (
DebugRequest::Attach(attach_config) => (
"".to_owned(),
None,
crate::task_template::DebugArgsRequest::Attach(attach_config),
@ -149,34 +137,33 @@ impl DebugTaskDefinition {
};
let task_type = TaskType::Debug(DebugArgs {
adapter: self.adapter,
adapter: self.definition.adapter,
request,
initialize_args: self.initialize_args,
initialize_args: self.definition.initialize_args,
locator: self.locator,
tcp_connection: self.tcp_connection,
stop_on_entry: self.stop_on_entry,
tcp_connection: self.definition.tcp_connection,
stop_on_entry: self.definition.stop_on_entry,
});
let label = self.label.clone();
let label = self.definition.label.clone();
Ok(TaskTemplate {
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")]
pub enum DebugConnectionType {
/// Connect to the debug adapter via TCP
TCP(TCPHost),
/// Connect to the debug adapter via STDIO
STDIO,
#[serde(rename_all = "snake_case")]
pub struct DebugTaskTemplate {
pub locator: Option<String>,
#[serde(flatten)]
pub definition: DebugTaskDefinition,
}
/// This struct represent a user created debug task
@ -187,7 +174,7 @@ pub struct DebugTaskDefinition {
pub adapter: String,
/// The type of request that should be called on the debug adapter
#[serde(flatten)]
pub request: DebugRequestType,
pub request: DebugRequest,
/// Name of the debug task
pub label: String,
/// Additional initialization arguments to be sent on DAP initialization
@ -197,18 +184,83 @@ pub struct DebugTaskDefinition {
/// 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>,
/// Locator to use
/// -- cargo
pub locator: Option<String>,
pub tcp_connection: Option<TcpArgumentsTemplate>,
/// Whether to tell the debug adapter to stop on entry
pub stop_on_entry: Option<bool>,
}
impl DebugTaskDefinition {
pub fn cwd(&self) -> Option<&Path> {
if let DebugRequest::Launch(config) = &self.request {
config.cwd.as_deref()
} else {
None
}
}
pub fn to_proto(&self) -> proto::DebugTaskDefinition {
proto::DebugTaskDefinition {
adapter: self.adapter.clone(),
request: Some(match &self.request {
DebugRequest::Launch(config) => {
proto::debug_task_definition::Request::DebugLaunchRequest(
proto::DebugLaunchRequest {
program: config.program.clone(),
cwd: config.cwd.as_ref().map(|c| c.to_string_lossy().to_string()),
args: config.args.clone(),
},
)
}
DebugRequest::Attach(attach_request) => {
proto::debug_task_definition::Request::DebugAttachRequest(
proto::DebugAttachRequest {
process_id: attach_request.process_id.unwrap_or_default(),
},
)
}
}),
label: self.label.clone(),
initialize_args: self.initialize_args.as_ref().map(|v| v.to_string()),
tcp_connection: self.tcp_connection.as_ref().map(|t| t.to_proto()),
stop_on_entry: self.stop_on_entry,
}
}
pub fn from_proto(proto: proto::DebugTaskDefinition) -> Result<Self> {
let request = proto
.request
.ok_or_else(|| anyhow::anyhow!("request is required"))?;
Ok(Self {
label: proto.label,
initialize_args: proto.initialize_args.map(|v| v.into()),
tcp_connection: proto
.tcp_connection
.map(TcpArgumentsTemplate::from_proto)
.transpose()?,
stop_on_entry: proto.stop_on_entry,
adapter: proto.adapter.clone(),
request: match request {
proto::debug_task_definition::Request::DebugAttachRequest(config) => {
DebugRequest::Attach(AttachRequest {
process_id: Some(config.process_id),
})
}
proto::debug_task_definition::Request::DebugLaunchRequest(config) => {
DebugRequest::Launch(LaunchRequest {
program: config.program,
cwd: config.cwd.map(|cwd| cwd.into()),
args: config.args,
})
}
},
})
}
}
/// A group of Debug Tasks defined in a JSON file.
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(transparent)]
pub struct DebugTaskFile(pub Vec<DebugTaskDefinition>);
pub struct DebugTaskFile(pub Vec<DebugTaskTemplate>);
impl DebugTaskFile {
/// Generates JSON schema of Tasks JSON template format.
@ -222,31 +274,17 @@ impl DebugTaskFile {
}
}
impl TryFrom<DebugTaskFile> for TaskTemplates {
type Error = anyhow::Error;
fn try_from(value: DebugTaskFile) -> Result<Self, Self::Error> {
let templates = value
.0
.into_iter()
.filter_map(|debug_definition| debug_definition.to_zed_format().log_err())
.collect();
Ok(Self(templates))
}
}
#[cfg(test)]
mod tests {
use crate::{DebugRequestType, LaunchConfig};
use crate::{DebugRequest, LaunchRequest};
#[test]
fn test_can_deserialize_non_attach_task() {
let deserialized: DebugRequestType =
let deserialized: DebugRequest =
serde_json::from_str(r#"{"program": "cafebabe"}"#).unwrap();
assert_eq!(
deserialized,
DebugRequestType::Launch(LaunchConfig {
DebugRequest::Launch(LaunchRequest {
program: "cafebabe".to_owned(),
..Default::default()
})

View file

@ -1,5 +1,4 @@
//! Baseline interface of Tasks in Zed: all tasks in Zed are intended to use those for implementing their own logic.
#![deny(missing_docs)]
mod debug_format;
mod serde_helpers;
@ -16,8 +15,8 @@ use std::path::PathBuf;
use std::str::FromStr;
pub use debug_format::{
AttachConfig, DebugConnectionType, DebugRequestDisposition, DebugRequestType,
DebugTaskDefinition, DebugTaskFile, LaunchConfig, TCPHost,
AttachRequest, DebugRequest, DebugTaskDefinition, DebugTaskFile, DebugTaskTemplate,
LaunchRequest, TcpArgumentsTemplate,
};
pub use task_template::{
DebugArgs, DebugArgsRequest, HideStrategy, RevealStrategy, TaskModal, TaskTemplate,
@ -104,7 +103,7 @@ impl ResolvedTask {
}
/// Get the configuration for the debug adapter that should be used for this task.
pub fn resolved_debug_adapter_config(&self) -> Option<DebugTaskDefinition> {
pub fn resolved_debug_adapter_config(&self) -> Option<DebugTaskTemplate> {
match self.original_task.task_type.clone() {
TaskType::Debug(debug_args) if self.resolved.is_some() => {
let resolved = self
@ -127,25 +126,27 @@ impl ResolvedTask {
})
.collect();
Some(DebugTaskDefinition {
label: resolved.label.clone(),
adapter: debug_args.adapter.clone(),
request: match debug_args.request {
crate::task_template::DebugArgsRequest::Launch => {
DebugRequestType::Launch(LaunchConfig {
program: resolved.command.clone(),
cwd: resolved.cwd.clone(),
args,
})
}
crate::task_template::DebugArgsRequest::Attach(attach_config) => {
DebugRequestType::Attach(attach_config)
}
},
initialize_args: debug_args.initialize_args,
tcp_connection: debug_args.tcp_connection,
Some(DebugTaskTemplate {
locator: debug_args.locator.clone(),
stop_on_entry: debug_args.stop_on_entry,
definition: DebugTaskDefinition {
label: resolved.label.clone(),
adapter: debug_args.adapter.clone(),
request: match debug_args.request {
crate::task_template::DebugArgsRequest::Launch => {
DebugRequest::Launch(LaunchRequest {
program: resolved.command.clone(),
cwd: resolved.cwd.clone(),
args,
})
}
crate::task_template::DebugArgsRequest::Attach(attach_config) => {
DebugRequest::Attach(attach_config)
}
},
initialize_args: debug_args.initialize_args,
tcp_connection: debug_args.tcp_connection,
stop_on_entry: debug_args.stop_on_entry,
},
})
}
_ => None,
@ -366,6 +367,8 @@ pub struct ShellBuilder {
args: Vec<String>,
}
pub static DEFAULT_REMOTE_SHELL: &str = "\"${SHELL:-sh}\"";
impl ShellBuilder {
/// Create a new ShellBuilder as configured.
pub fn new(is_local: bool, shell: &Shell) -> Self {
@ -374,7 +377,7 @@ impl ShellBuilder {
if is_local {
(Self::system_shell(), Vec::new())
} else {
("\"${SHELL:-sh}\"".to_string(), Vec::new())
(DEFAULT_REMOTE_SHELL.to_string(), Vec::new())
}
}
Shell::Program(shell) => (shell.clone(), Vec::new()),

View file

@ -7,8 +7,9 @@ use std::path::PathBuf;
use util::serde::default_true;
use util::{ResultExt, truncate_and_remove_front};
use crate::debug_format::TcpArgumentsTemplate;
use crate::{
AttachConfig, ResolvedTask, RevealTarget, Shell, SpawnInTerminal, TCPHost, TaskContext, TaskId,
AttachRequest, ResolvedTask, RevealTarget, Shell, SpawnInTerminal, TaskContext, TaskId,
VariableName, ZED_VARIABLE_NAME_PREFIX,
serde_helpers::{non_empty_string_vec, non_empty_string_vec_json_schema},
};
@ -83,7 +84,7 @@ pub enum DebugArgsRequest {
/// launch (program, cwd) are stored in TaskTemplate as (command, cwd)
Launch,
/// Attach
Attach(AttachConfig),
Attach(AttachRequest),
}
#[derive(Deserialize, Eq, PartialEq, Clone, Debug)]
@ -94,7 +95,7 @@ pub struct DebugArgs {
/// Adapter choice
pub adapter: String,
/// TCP connection to make with debug adapter
pub tcp_connection: Option<TCPHost>,
pub tcp_connection: Option<TcpArgumentsTemplate>,
/// Args to send to debug adapter
pub initialize_args: Option<serde_json::value::Value>,
/// the locator to use

View file

@ -11,7 +11,7 @@ use itertools::Itertools;
use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch};
use project::{TaskSourceKind, task_store::TaskStore};
use task::{
DebugRequestType, DebugTaskDefinition, ResolvedTask, RevealTarget, TaskContext, TaskModal,
DebugRequest, DebugTaskDefinition, ResolvedTask, RevealTarget, TaskContext, TaskModal,
TaskTemplate, TaskType,
};
use ui::{
@ -21,7 +21,7 @@ use ui::{
};
use util::{ResultExt, truncate_and_trailoff};
use workspace::{ModalView, Workspace, tasks::schedule_resolved_task};
use workspace::{ModalView, Workspace};
pub use zed_actions::{Rerun, Spawn};
/// A modal used to spawn new tasks.
@ -334,7 +334,7 @@ impl PickerDelegate for TasksModalDelegate {
fn confirm(
&mut self,
omit_history_entry: bool,
_: &mut Window,
window: &mut Window,
cx: &mut Context<picker::Picker<Self>>,
) {
let current_match_index = self.selected_index();
@ -360,17 +360,14 @@ impl PickerDelegate for TasksModalDelegate {
}
match task.task_type() {
TaskType::Debug(config) if config.locator.is_none() => {
let Some(config): Option<DebugTaskDefinition> =
task.resolved_debug_adapter_config()
else {
TaskType::Debug(_) => {
let Some(config) = task.resolved_debug_adapter_config() else {
return;
};
let config = config.definition;
match &config.request {
DebugRequestType::Attach(attach_config)
if attach_config.process_id.is_none() =>
{
DebugRequest::Attach(attach_config) if attach_config.process_id.is_none() => {
cx.emit(ShowAttachModal {
debug_config: config.clone(),
});
@ -379,24 +376,20 @@ impl PickerDelegate for TasksModalDelegate {
_ => {
self.workspace
.update(cx, |workspace, cx| {
workspace.project().update(cx, |project, cx| {
project
.start_debug_session(config, cx)
.detach_and_log_err(cx);
});
workspace.schedule_debug_task(task, window, cx);
})
.ok();
}
}
}
_ => {
TaskType::Script => {
self.workspace
.update(cx, |workspace, cx| {
schedule_resolved_task(
workspace,
workspace.schedule_resolved_task(
task_source_kind,
task,
omit_history_entry,
window,
cx,
);
})
@ -566,7 +559,7 @@ impl PickerDelegate for TasksModalDelegate {
fn confirm_input(
&mut self,
omit_history_entry: bool,
_: &mut Window,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) {
let Some((task_source_kind, mut task)) = self.spawn_oneshot() else {
@ -584,36 +577,17 @@ impl PickerDelegate for TasksModalDelegate {
self.workspace
.update(cx, |workspace, cx| {
match task.task_type() {
TaskType::Script => schedule_resolved_task(
workspace,
TaskType::Script => workspace.schedule_resolved_task(
task_source_kind,
task,
omit_history_entry,
window,
cx,
),
// todo(debugger): Should create a schedule_resolved_debug_task function
// This would allow users to access to debug history and other issues
TaskType::Debug(debug_args) => {
let Some(debug_config) = task.resolved_debug_adapter_config() else {
// todo(debugger) log an error, this should never happen
return;
};
if debug_args.locator.is_some() {
schedule_resolved_task(
workspace,
task_source_kind,
task,
omit_history_entry,
cx,
);
} else {
workspace.project().update(cx, |project, cx| {
project
.start_debug_session(debug_config, cx)
.detach_and_log_err(cx);
});
}
TaskType::Debug(_) => {
workspace.schedule_debug_task(task, window, cx);
}
};
})

View file

@ -8,8 +8,7 @@ use project::{Location, TaskContexts, TaskSourceKind, Worktree};
use task::{
RevealTarget, TaskContext, TaskId, TaskModal, TaskTemplate, TaskVariables, VariableName,
};
use workspace::tasks::schedule_task;
use workspace::{Workspace, tasks::schedule_resolved_task};
use workspace::Workspace;
mod modal;
@ -50,15 +49,15 @@ pub fn init(cx: &mut App) {
let task_contexts = task_contexts.await;
let default_context = TaskContext::default();
workspace
.update_in(cx, |workspace, _, cx| {
schedule_task(
workspace,
.update_in(cx, |workspace, window, cx| {
workspace.schedule_task(
task_source_kind,
&original_task,
task_contexts
.active_context()
.unwrap_or(&default_context),
false,
window,
cx,
)
})
@ -75,11 +74,11 @@ pub fn init(cx: &mut App) {
}
}
schedule_resolved_task(
workspace,
workspace.schedule_resolved_task(
task_source_kind,
last_scheduled_task,
false,
window,
cx,
);
}
@ -217,7 +216,7 @@ where
})?;
let did_spawn = workspace
.update(cx, |workspace, cx| {
.update_in(cx, |workspace, window, cx| {
let default_context = TaskContext::default();
let active_context = task_contexts.active_context().unwrap_or(&default_context);
@ -228,12 +227,12 @@ where
target_task.reveal_target = target_override;
}
}
schedule_task(
workspace,
workspace.schedule_task(
task_source_kind.clone(),
target_task,
active_context,
false,
window,
cx,
);
true

View file

@ -46,13 +46,14 @@ use smol::channel::{Receiver, Sender};
use task::{HideStrategy, Shell, TaskId};
use terminal_settings::{AlternateScroll, CursorShape, TerminalSettings};
use theme::{ActiveTheme, Theme};
use util::{paths::home_dir, truncate_and_trailoff};
use util::{ResultExt, paths::home_dir, truncate_and_trailoff};
use std::{
cmp::{self, min},
fmt::Display,
ops::{Deref, Index, RangeInclusive},
path::PathBuf,
process::ExitStatus,
sync::{Arc, LazyLock},
time::Duration,
};
@ -109,7 +110,6 @@ pub enum Event {
SelectionsChanged,
NewNavigationTarget(Option<MaybeNavigationTarget>),
Open(MaybeNavigationTarget),
TaskLocatorReady { task_id: TaskId, success: bool },
}
#[derive(Clone, Debug)]
@ -351,7 +351,7 @@ impl TerminalBuilder {
max_scroll_history_lines: Option<usize>,
is_ssh_terminal: bool,
window: AnyWindowHandle,
completion_tx: Sender<()>,
completion_tx: Sender<Option<ExitStatus>>,
debug_terminal: bool,
cx: &App,
) -> Result<TerminalBuilder> {
@ -639,7 +639,7 @@ pub enum SelectionPhase {
pub struct Terminal {
pty_tx: Notifier,
completion_tx: Sender<()>,
completion_tx: Sender<Option<ExitStatus>>,
term: Arc<FairMutex<Term<ZedListener>>>,
term_config: Config,
events: VecDeque<InternalEvent>,
@ -670,7 +670,7 @@ pub struct TaskState {
pub label: String,
pub command_label: String,
pub status: TaskStatus,
pub completion_rx: Receiver<()>,
pub completion_rx: Receiver<Option<ExitStatus>>,
pub hide: HideStrategy,
pub show_summary: bool,
pub show_command: bool,
@ -1859,20 +1859,30 @@ impl Terminal {
self.debug_terminal
}
pub fn wait_for_completed_task(&self, cx: &App) -> Task<()> {
pub fn wait_for_completed_task(&self, cx: &App) -> Task<Option<ExitStatus>> {
if let Some(task) = self.task() {
if task.status == TaskStatus::Running {
let completion_receiver = task.completion_rx.clone();
return cx.spawn(async move |_| {
let _ = completion_receiver.recv().await;
});
return cx
.spawn(async move |_| completion_receiver.recv().await.log_err().flatten());
}
}
Task::ready(())
Task::ready(None)
}
fn register_task_finished(&mut self, error_code: Option<i32>, cx: &mut Context<Terminal>) {
self.completion_tx.try_send(()).ok();
let e: Option<ExitStatus> = error_code.map(|code| {
#[cfg(unix)]
{
return std::os::unix::process::ExitStatusExt::from_raw(code);
}
#[cfg(windows)]
{
return std::os::windows::process::ExitStatusExt::from_raw(code as u32);
}
});
self.completion_tx.try_send(e).ok();
let task = match &mut self.task {
Some(task) => task,
None => {
@ -1911,11 +1921,6 @@ impl Terminal {
unsafe { append_text_to_term(&mut self.term.lock(), &lines_to_show) };
}
cx.emit(Event::TaskLocatorReady {
task_id: task.id.clone(),
success: finished_successfully,
});
match task.hide {
HideStrategy::Never => {}
HideStrategy::Always => {

View file

@ -1,4 +1,4 @@
use std::{cmp, ops::ControlFlow, path::PathBuf, sync::Arc, time::Duration};
use std::{cmp, ops::ControlFlow, path::PathBuf, process::ExitStatus, sync::Arc, time::Duration};
use crate::{
TerminalView, default_working_directory,
@ -9,7 +9,7 @@ use crate::{
use breadcrumbs::Breadcrumbs;
use collections::HashMap;
use db::kvp::KEY_VALUE_STORE;
use futures::future::join_all;
use futures::{channel::oneshot, future::join_all};
use gpui::{
Action, AnyView, App, AsyncApp, AsyncWindowContext, Context, Corner, Entity, EventEmitter,
ExternalPaths, FocusHandle, Focusable, IntoElement, ParentElement, Pixels, Render, Styled,
@ -279,17 +279,9 @@ impl TerminalPanel {
};
if let Some(workspace) = workspace.upgrade() {
terminal_panel
.update_in(&mut cx, |_, window, cx| {
cx.subscribe_in(&workspace, window, |terminal_panel, _, e, window, cx| {
if let workspace::Event::SpawnTask {
action: spawn_in_terminal,
} = e
{
terminal_panel.spawn_task(spawn_in_terminal, window, cx);
};
})
.detach();
workspace
.update(&mut cx, |workspace, _| {
workspace.set_terminal_provider(TerminalProvider(terminal_panel.clone()))
})
.ok();
}
@ -486,12 +478,17 @@ impl TerminalPanel {
.detach_and_log_err(cx);
}
fn spawn_task(&mut self, task: &SpawnInTerminal, window: &mut Window, cx: &mut Context<Self>) {
fn spawn_task(
&mut self,
task: &SpawnInTerminal,
window: &mut Window,
cx: &mut Context<Self>,
) -> Task<Result<Entity<Terminal>>> {
let Ok(is_local) = self
.workspace
.update(cx, |workspace, cx| workspace.project().read(cx).is_local())
else {
return;
return Task::ready(Err(anyhow!("Project is not local")));
};
let builder = ShellBuilder::new(is_local, &task.shell);
@ -506,58 +503,53 @@ impl TerminalPanel {
};
if task.allow_concurrent_runs && task.use_new_terminal {
self.spawn_in_new_terminal(task, window, cx)
.detach_and_log_err(cx);
return;
return self.spawn_in_new_terminal(task, window, cx);
}
let mut terminals_for_task = self.terminals_for_task(&task.full_label, cx);
let Some(existing) = terminals_for_task.pop() else {
self.spawn_in_new_terminal(task, window, cx)
.detach_and_log_err(cx);
return;
return self.spawn_in_new_terminal(task, window, cx);
};
let (existing_item_index, task_pane, existing_terminal) = existing;
if task.allow_concurrent_runs {
self.replace_terminal(
return self.replace_terminal(
task,
task_pane,
existing_item_index,
existing_terminal,
window,
cx,
)
.detach();
return;
);
}
let (tx, rx) = oneshot::channel();
self.deferred_tasks.insert(
task.id.clone(),
cx.spawn_in(window, async move |terminal_panel, cx| {
wait_for_terminals_tasks(terminals_for_task, cx).await;
let task = terminal_panel.update_in(cx, |terminal_panel, window, cx| {
if task.use_new_terminal {
terminal_panel
.spawn_in_new_terminal(task, window, cx)
.detach_and_log_err(cx);
None
terminal_panel.spawn_in_new_terminal(task, window, cx)
} else {
Some(terminal_panel.replace_terminal(
terminal_panel.replace_terminal(
task,
task_pane,
existing_item_index,
existing_terminal,
window,
cx,
))
)
}
});
if let Ok(Some(task)) = task {
task.await;
if let Ok(task) = task {
tx.send(task.await).ok();
}
}),
);
cx.spawn(async move |_, _| rx.await?)
}
pub fn spawn_in_new_terminal(
@ -810,60 +802,47 @@ impl TerminalPanel {
terminal_to_replace: Entity<TerminalView>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Task<Option<()>> {
) -> Task<Result<Entity<Terminal>>> {
let reveal = spawn_task.reveal;
let reveal_target = spawn_task.reveal_target;
let window_handle = window.window_handle();
let task_workspace = self.workspace.clone();
cx.spawn_in(window, async move |terminal_panel, cx| {
let project = terminal_panel
.update(cx, |this, cx| {
this.workspace
.update(cx, |workspace, _| workspace.project().clone())
.ok()
})
.ok()
.flatten()?;
let project = terminal_panel.update(cx, |this, cx| {
this.workspace
.update(cx, |workspace, _| workspace.project().clone())
})??;
let new_terminal = project
.update(cx, |project, cx| {
project.create_terminal(TerminalKind::Task(spawn_task), window_handle, cx)
})
.ok()?
.await
.log_err()?;
terminal_to_replace
.update_in(cx, |terminal_to_replace, window, cx| {
terminal_to_replace.set_terminal(new_terminal, window, cx);
})
.ok()?;
})?
.await?;
terminal_to_replace.update_in(cx, |terminal_to_replace, window, cx| {
terminal_to_replace.set_terminal(new_terminal.clone(), window, cx);
})?;
match reveal {
RevealStrategy::Always => match reveal_target {
RevealTarget::Center => {
task_workspace
.update_in(cx, |workspace, window, cx| {
workspace
.active_item(cx)
.context("retrieving active terminal item in the workspace")
.log_err()?
.item_focus_handle(cx)
.focus(window);
Some(())
})
.ok()??;
task_workspace.update_in(cx, |workspace, window, cx| {
workspace
.active_item(cx)
.context("retrieving active terminal item in the workspace")?
.item_focus_handle(cx)
.focus(window);
anyhow::Ok(())
})??;
}
RevealTarget::Dock => {
terminal_panel
.update_in(cx, |terminal_panel, window, cx| {
terminal_panel.activate_terminal_view(
&task_pane,
terminal_item_index,
true,
window,
cx,
)
})
.ok()?;
terminal_panel.update_in(cx, |terminal_panel, window, cx| {
terminal_panel.activate_terminal_view(
&task_pane,
terminal_item_index,
true,
window,
cx,
)
})?;
cx.spawn(async move |cx| {
task_workspace
@ -877,24 +856,20 @@ impl TerminalPanel {
},
RevealStrategy::NoFocus => match reveal_target {
RevealTarget::Center => {
task_workspace
.update_in(cx, |workspace, window, cx| {
workspace.active_pane().focus_handle(cx).focus(window);
})
.ok()?;
task_workspace.update_in(cx, |workspace, window, cx| {
workspace.active_pane().focus_handle(cx).focus(window);
})?;
}
RevealTarget::Dock => {
terminal_panel
.update_in(cx, |terminal_panel, window, cx| {
terminal_panel.activate_terminal_view(
&task_pane,
terminal_item_index,
false,
window,
cx,
)
})
.ok()?;
terminal_panel.update_in(cx, |terminal_panel, window, cx| {
terminal_panel.activate_terminal_view(
&task_pane,
terminal_item_index,
false,
window,
cx,
)
})?;
cx.spawn(async move |cx| {
task_workspace
@ -909,7 +884,7 @@ impl TerminalPanel {
RevealStrategy::Never => {}
}
Some(())
Ok(new_terminal)
})
}
@ -1158,7 +1133,7 @@ async fn wait_for_terminals_tasks(
})
.ok()
});
let _: Vec<()> = join_all(pending_tasks).await;
let _: Vec<_> = join_all(pending_tasks).await;
}
fn add_paths_to_terminal(
@ -1475,6 +1450,34 @@ impl Panel for TerminalPanel {
}
}
struct TerminalProvider(Entity<TerminalPanel>);
impl workspace::TerminalProvider for TerminalProvider {
fn spawn(
&self,
task: SpawnInTerminal,
window: &mut Window,
cx: &mut App,
) -> Task<Result<ExitStatus>> {
let this = self.0.clone();
window.spawn(cx, async move |cx| {
let terminal = this
.update_in(cx, |terminal_panel, window, cx| {
terminal_panel.spawn_task(&task, window, cx)
})?
.await?;
let Some(exit_code) = terminal
.read_with(cx, |terminal, cx| terminal.wait_for_completed_task(cx))?
.await
else {
return Err(anyhow!("Task cancelled"));
};
Ok(exit_code)
})
}
}
struct InlineAssistTabBarButton {
focus_handle: FocusHandle,
}

View file

@ -982,15 +982,6 @@ fn subscribe_for_terminal_events(
window.invalidate_character_coordinates();
cx.emit(SearchEvent::ActiveMatchChanged)
}
Event::TaskLocatorReady { task_id, success } => {
if *success {
workspace
.update(cx, |workspace, cx| {
workspace.debug_task_ready(task_id, cx);
})
.log_err();
}
}
},
);
vec![terminal_subscription, terminal_events_subscription]

View file

@ -1446,27 +1446,29 @@ impl ShellExec {
let project = workspace.project().read(cx);
let cwd = project.first_project_directory(cx);
let shell = project.terminal_settings(&cwd, cx).shell.clone();
cx.emit(workspace::Event::SpawnTask {
action: Box::new(SpawnInTerminal {
id: TaskId("vim".to_string()),
full_label: command.clone(),
label: command.clone(),
command: command.clone(),
args: Vec::new(),
command_label: command.clone(),
cwd,
env: HashMap::default(),
use_new_terminal: true,
allow_concurrent_runs: true,
reveal: RevealStrategy::NoFocus,
reveal_target: RevealTarget::Dock,
hide: HideStrategy::Never,
shell,
show_summary: false,
show_command: false,
show_rerun: false,
}),
});
let spawn_in_terminal = SpawnInTerminal {
id: TaskId("vim".to_string()),
full_label: command.clone(),
label: command.clone(),
command: command.clone(),
args: Vec::new(),
command_label: command.clone(),
cwd,
env: HashMap::default(),
use_new_terminal: true,
allow_concurrent_runs: true,
reveal: RevealStrategy::NoFocus,
reveal_target: RevealTarget::Dock,
hide: HideStrategy::Never,
shell,
show_summary: false,
show_command: false,
show_rerun: false,
};
workspace
.spawn_in_terminal(spawn_in_terminal, window, cx)
.detach_and_log_err(cx);
});
return;
};

View file

@ -35,7 +35,6 @@ 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

View file

@ -1,75 +1,132 @@
use gpui::Context;
use std::process::ExitStatus;
use anyhow::{Result, anyhow};
use gpui::{Context, Task};
use project::TaskSourceKind;
use remote::ConnectionState;
use task::{ResolvedTask, TaskContext, TaskTemplate};
use task::{ResolvedTask, SpawnInTerminal, TaskContext, TaskTemplate};
use ui::Window;
use crate::Workspace;
pub fn schedule_task(
workspace: &mut Workspace,
task_source_kind: TaskSourceKind,
task_to_resolve: &TaskTemplate,
task_cx: &TaskContext,
omit_history: bool,
cx: &mut Context<Workspace>,
) {
match workspace.project.read(cx).ssh_connection_state(cx) {
None | Some(ConnectionState::Connected) => {}
Some(
ConnectionState::Connecting
| ConnectionState::Disconnected
| ConnectionState::HeartbeatMissed
| ConnectionState::Reconnecting,
) => {
log::warn!("Cannot schedule tasks when disconnected from a remote host");
impl Workspace {
pub fn schedule_task(
self: &mut Workspace,
task_source_kind: TaskSourceKind,
task_to_resolve: &TaskTemplate,
task_cx: &TaskContext,
omit_history: bool,
window: &mut Window,
cx: &mut Context<Self>,
) {
match self.project.read(cx).ssh_connection_state(cx) {
None | Some(ConnectionState::Connected) => {}
Some(
ConnectionState::Connecting
| ConnectionState::Disconnected
| ConnectionState::HeartbeatMissed
| ConnectionState::Reconnecting,
) => {
log::warn!("Cannot schedule tasks when disconnected from a remote host");
return;
}
}
if let Some(spawn_in_terminal) =
task_to_resolve.resolve_task(&task_source_kind.to_id_base(), task_cx)
{
self.schedule_resolved_task(
task_source_kind,
spawn_in_terminal,
omit_history,
window,
cx,
);
}
}
pub fn schedule_resolved_task(
self: &mut Workspace,
task_source_kind: TaskSourceKind,
mut resolved_task: ResolvedTask,
omit_history: bool,
window: &mut Window,
cx: &mut Context<Workspace>,
) {
if let Some(spawn_in_terminal) = resolved_task.resolved.take() {
if !omit_history {
resolved_task.resolved = Some(spawn_in_terminal.clone());
self.project().update(cx, |project, cx| {
if let Some(task_inventory) =
project.task_store().read(cx).task_inventory().cloned()
{
task_inventory.update(cx, |inventory, _| {
inventory.task_scheduled(task_source_kind, resolved_task);
})
}
});
}
if let Some(terminal_provider) = self.terminal_provider.as_ref() {
terminal_provider
.spawn(spawn_in_terminal, window, cx)
.detach_and_log_err(cx);
}
}
}
pub fn schedule_debug_task(
&mut self,
task: ResolvedTask,
window: &mut Window,
cx: &mut Context<Workspace>,
) {
let Some(debug_config) = task.resolved_debug_adapter_config() else {
log::error!("Debug task has no debug adapter config");
return;
}
}
};
if let Some(spawn_in_terminal) =
task_to_resolve.resolve_task(&task_source_kind.to_id_base(), task_cx)
{
schedule_resolved_task(
workspace,
task_source_kind,
spawn_in_terminal,
omit_history,
cx,
);
}
}
let project = self.project().clone();
cx.spawn_in(window, async move |workspace, cx| {
let config = if debug_config.locator.is_some() {
let task = workspace.update_in(cx, |workspace, window, cx| {
workspace.spawn_in_terminal(task.resolved.unwrap(), window, cx)
})?;
pub fn schedule_resolved_task(
workspace: &mut Workspace,
task_source_kind: TaskSourceKind,
mut resolved_task: ResolvedTask,
omit_history: bool,
cx: &mut Context<Workspace>,
) {
let debug_config = resolved_task.resolved_debug_adapter_config();
if let Some(spawn_in_terminal) = resolved_task.resolved.take() {
if let Some(debug_config) = debug_config {
workspace
.debug_task_queue
.insert(resolved_task.id.clone(), debug_config);
}
if !omit_history {
resolved_task.resolved = Some(spawn_in_terminal.clone());
workspace.project().update(cx, |project, cx| {
if let Some(task_inventory) =
project.task_store().read(cx).task_inventory().cloned()
{
task_inventory.update(cx, |inventory, _| {
inventory.task_scheduled(task_source_kind, resolved_task);
})
let exit_code = task.await?;
if !exit_code.success() {
return anyhow::Ok(());
}
});
}
let ret = project
.update(cx, |project, cx| {
project.dap_store().update(cx, |dap_store, cx| {
dap_store.run_debug_locator(debug_config, cx)
})
})?
.await?;
ret
} else {
debug_config.definition
};
cx.emit(crate::Event::SpawnTask {
action: Box::new(spawn_in_terminal),
});
project
.update(cx, |project, cx| project.start_debug_session(config, cx))?
.await?;
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
pub fn spawn_in_terminal(
self: &mut Workspace,
spawn_in_terminal: SpawnInTerminal,
window: &mut Window,
cx: &mut Context<Workspace>,
) -> Task<Result<ExitStatus>> {
if let Some(terminal_provider) = self.terminal_provider.as_ref() {
terminal_provider.spawn(spawn_in_terminal, window, cx)
} else {
Task::ready(Err(anyhow!("No terminal provider")))
}
}
}

View file

@ -15,7 +15,6 @@ mod toast_layer;
mod toolbar;
mod workspace_settings;
use dap::DapRegistry;
pub use toast_layer::{RunAction, ToastAction, ToastLayer, ToastView};
use anyhow::{Context as _, Result, anyhow};
@ -92,11 +91,12 @@ use std::{
env,
hash::{Hash, Hasher},
path::{Path, PathBuf},
process::ExitStatus,
rc::Rc,
sync::{Arc, LazyLock, Weak, atomic::AtomicUsize},
time::Duration,
};
use task::{DebugTaskDefinition, SpawnInTerminal, TaskId};
use task::SpawnInTerminal;
use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
pub use ui;
@ -130,6 +130,15 @@ static ZED_WINDOW_POSITION: LazyLock<Option<Point<Pixels>>> = LazyLock::new(|| {
.and_then(parse_pixel_position_env_var)
});
pub trait TerminalProvider {
fn spawn(
&self,
task: SpawnInTerminal,
window: &mut Window,
cx: &mut App,
) -> Task<Result<ExitStatus>>;
}
actions!(
workspace,
[
@ -626,7 +635,6 @@ 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>,
@ -678,7 +686,6 @@ 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);
@ -694,7 +701,6 @@ impl AppState {
client,
fs,
languages,
debug_adapters,
user_store,
workspace_store,
node_runtime: NodeRuntime::unavailable(),
@ -772,9 +778,6 @@ pub enum Event {
},
ContactRequestedJoin(u64),
WorkspaceCreated(WeakEntity<Workspace>),
SpawnTask {
action: Box<SpawnInTerminal>,
},
OpenBundledFile {
text: Cow<'static, str>,
title: &'static str,
@ -856,11 +859,11 @@ pub struct Workspace {
bounds_save_task_queued: Option<Task<()>>,
on_prompt_for_new_path: Option<PromptForNewPath>,
on_prompt_for_open_path: Option<PromptForOpenPath>,
terminal_provider: Option<Box<dyn TerminalProvider>>,
serializable_items_tx: UnboundedSender<Box<dyn SerializableItemHandle>>,
serialized_ssh_project: Option<SerializedSshProject>,
_items_serializer: Task<Result<()>>,
session_id: Option<String>,
debug_task_queue: HashMap<task::TaskId, DebugTaskDefinition>,
}
impl EventEmitter<Event> for Workspace {}
@ -1182,11 +1185,11 @@ impl Workspace {
bounds_save_task_queued: None,
on_prompt_for_new_path: None,
on_prompt_for_open_path: None,
terminal_provider: None,
serializable_items_tx,
_items_serializer,
session_id: Some(session_id),
serialized_ssh_project: None,
debug_task_queue: Default::default(),
}
}
@ -1207,7 +1210,6 @@ 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,
@ -1699,6 +1701,10 @@ impl Workspace {
self.on_prompt_for_open_path = Some(prompt)
}
pub fn set_terminal_provider(&mut self, provider: impl TerminalProvider + 'static) {
self.terminal_provider = Some(Box::new(provider));
}
pub fn serialized_ssh_project(&self) -> Option<SerializedSshProject> {
self.serialized_ssh_project.clone()
}
@ -5082,7 +5088,6 @@ 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,
@ -5238,16 +5243,6 @@ impl Workspace {
.update(cx, |_, window, _| window.activate_window())
.ok();
}
pub fn debug_task_ready(&mut self, task_id: &TaskId, cx: &mut App) {
if let Some(debug_config) = self.debug_task_queue.remove(task_id) {
self.project.update(cx, |project, cx| {
project
.start_debug_session(debug_config, cx)
.detach_and_log_err(cx);
})
}
}
}
fn leader_border_for_pane(

View file

@ -42,7 +42,6 @@ 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

View file

@ -10,7 +10,6 @@ use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
use client::{Client, ProxySettings, UserStore, parse_zed_link};
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;
@ -449,7 +448,6 @@ 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(),
@ -461,7 +459,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());
dap_adapters::init(cx);
auto_update_ui::init(cx);
reliability::init(
client.http_client(),

View file

@ -14,7 +14,7 @@ use log;
static ENV_FILTER: OnceLock<env_config::EnvFilter> = OnceLock::new();
static SCOPE_MAP: RwLock<Option<ScopeMap>> = RwLock::new(None);
const LEVEL_ENABLED_MAX_DEFAULT: log::LevelFilter = log::LevelFilter::Info;
pub const LEVEL_ENABLED_MAX_DEFAULT: log::LevelFilter = log::LevelFilter::Info;
/// The maximum log level of verbosity that is enabled by default.
/// All messages more verbose than this level will be discarded
/// by default unless specially configured.
@ -34,7 +34,7 @@ static mut LEVEL_ENABLED_MAX_STATIC: log::LevelFilter = LEVEL_ENABLED_MAX_DEFAUL
/// `trace` logs will be discarded.
/// Therefore, it should always be `>= LEVEL_ENABLED_MAX_STATIC`
// PERF: this doesn't need to be an atomic, we don't actually care about race conditions here
static LEVEL_ENABLED_MAX_CONFIG: AtomicU8 = AtomicU8::new(LEVEL_ENABLED_MAX_DEFAULT as u8);
pub static LEVEL_ENABLED_MAX_CONFIG: AtomicU8 = AtomicU8::new(LEVEL_ENABLED_MAX_DEFAULT as u8);
pub fn init_env_filter(filter: env_config::EnvFilter) {
if let Some(level_max) = filter.level_global {