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:
parent
d13cd007a2
commit
9d35f0389d
57 changed files with 1146 additions and 884 deletions
|
@ -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))]
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue