Detailed changes
@@ -7,7 +7,6 @@ use crate::{
};
use crate::{new_session_modal::NewSessionModal, session::DebugSession};
use anyhow::{Result, anyhow};
-use collections::HashMap;
use command_palette_hooks::CommandPaletteFilter;
use dap::DebugRequest;
use dap::{
@@ -15,7 +14,6 @@ use dap::{
client::SessionId, debugger_settings::DebuggerSettings,
};
use dap::{StartDebuggingRequestArguments, adapters::DebugTaskDefinition};
-use futures::{SinkExt as _, channel::mpsc};
use gpui::{
Action, App, AsyncWindowContext, Context, DismissEvent, Entity, EntityId, EventEmitter,
FocusHandle, Focusable, MouseButton, MouseDownEvent, Point, Subscription, Task, WeakEntity,
@@ -24,21 +22,11 @@ use gpui::{
use language::Buffer;
use project::debugger::session::{Session, SessionStateEvent};
-use project::{
- Project,
- debugger::{
- dap_store::{self, DapStore},
- session::ThreadStatus,
- },
- terminals::TerminalKind,
-};
+use project::{Project, debugger::session::ThreadStatus};
use rpc::proto::{self};
use settings::Settings;
use std::any::TypeId;
-use std::path::Path;
-use std::sync::Arc;
-use task::{DebugScenario, HideStrategy, RevealStrategy, RevealTarget, TaskContext, TaskId};
-use terminal_view::TerminalView;
+use task::{DebugScenario, TaskContext};
use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*};
use workspace::SplitDirection;
use workspace::{
@@ -74,27 +62,21 @@ pub struct DebugPanel {
workspace: WeakEntity<Workspace>,
focus_handle: FocusHandle,
context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
- _subscriptions: Vec<Subscription>,
}
impl DebugPanel {
pub fn new(
workspace: &Workspace,
- window: &mut Window,
+ _window: &mut Window,
cx: &mut Context<Workspace>,
) -> Entity<Self> {
cx.new(|cx| {
let project = workspace.project().clone();
- let dap_store = project.read(cx).dap_store();
-
- let _subscriptions =
- vec![cx.subscribe_in(&dap_store, window, Self::handle_dap_store_event)];
let debug_panel = Self {
size: px(300.),
sessions: vec![],
active_session: None,
- _subscriptions,
past_debug_definition: None,
focus_handle: cx.focus_handle(),
project,
@@ -288,7 +270,7 @@ impl DebugPanel {
cx.subscribe_in(
&session,
window,
- move |_, session, event: &SessionStateEvent, window, cx| match event {
+ move |this, session, event: &SessionStateEvent, window, cx| match event {
SessionStateEvent::Restart => {
let mut curr_session = session.clone();
while let Some(parent_session) = curr_session
@@ -310,6 +292,9 @@ impl DebugPanel {
})
.detach_and_log_err(cx);
}
+ SessionStateEvent::SpawnChildSession { request } => {
+ this.handle_start_debugging_request(request, session.clone(), window, cx);
+ }
_ => {}
},
)
@@ -357,7 +342,7 @@ impl DebugPanel {
Ok(())
}
- pub fn start_child_session(
+ pub fn handle_start_debugging_request(
&mut self,
request: &StartDebuggingRequestArguments,
parent_session: Entity<Session>,
@@ -419,47 +404,6 @@ impl DebugPanel {
self.active_session.clone()
}
- fn handle_dap_store_event(
- &mut self,
- _dap_store: &Entity<DapStore>,
- event: &dap_store::DapStoreEvent,
- window: &mut Window,
- cx: &mut Context<Self>,
- ) {
- match event {
- dap_store::DapStoreEvent::RunInTerminal {
- session_id,
- title,
- cwd,
- command,
- args,
- envs,
- sender,
- ..
- } => {
- self.handle_run_in_terminal_request(
- *session_id,
- title.clone(),
- cwd.clone(),
- command.clone(),
- args.clone(),
- envs.clone(),
- sender.clone(),
- window,
- cx,
- )
- .detach_and_log_err(cx);
- }
- dap_store::DapStoreEvent::SpawnChildSession {
- request,
- parent_session,
- } => {
- self.start_child_session(request, parent_session.clone(), window, cx);
- }
- _ => {}
- }
- }
-
pub fn resolve_scenario(
&self,
scenario: DebugScenario,
@@ -529,101 +473,6 @@ impl DebugPanel {
})
}
- fn handle_run_in_terminal_request(
- &self,
- session_id: SessionId,
- title: Option<String>,
- cwd: Option<Arc<Path>>,
- command: Option<String>,
- args: Vec<String>,
- envs: HashMap<String, String>,
- mut sender: mpsc::Sender<Result<u32>>,
- window: &mut Window,
- cx: &mut Context<Self>,
- ) -> Task<Result<()>> {
- let Some(session) = self
- .sessions
- .iter()
- .find(|s| s.read(cx).session_id(cx) == session_id)
- else {
- return Task::ready(Err(anyhow!("no session {:?} found", session_id)));
- };
- let running = session.read(cx).running_state();
- let cwd = cwd.map(|p| p.to_path_buf());
- let shell = self
- .project
- .read(cx)
- .terminal_settings(&cwd, cx)
- .shell
- .clone();
- let kind = if let Some(command) = command {
- let title = title.clone().unwrap_or(command.clone());
- TerminalKind::Task(task::SpawnInTerminal {
- id: TaskId("debug".to_string()),
- full_label: title.clone(),
- label: title.clone(),
- command: command.clone(),
- args,
- command_label: title.clone(),
- cwd,
- env: envs,
- 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,
- })
- } else {
- TerminalKind::Shell(cwd.map(|c| c.to_path_buf()))
- };
-
- let workspace = self.workspace.clone();
- let project = self.project.downgrade();
-
- let terminal_task = self.project.update(cx, |project, cx| {
- project.create_terminal(kind, window.window_handle(), cx)
- });
- let terminal_task = cx.spawn_in(window, async move |_, cx| {
- let terminal = terminal_task.await?;
-
- let terminal_view = cx.new_window_entity(|window, cx| {
- TerminalView::new(terminal.clone(), workspace, None, project, window, cx)
- })?;
-
- running.update_in(cx, |running, window, cx| {
- running.ensure_pane_item(DebuggerPaneItem::Terminal, window, cx);
- running.debug_terminal.update(cx, |debug_terminal, cx| {
- debug_terminal.terminal = Some(terminal_view);
- cx.notify();
- });
- })?;
-
- anyhow::Ok(terminal.read_with(cx, |terminal, _| terminal.pty_info.pid())?)
- });
-
- cx.background_spawn(async move {
- match terminal_task.await {
- Ok(pid_task) => match pid_task {
- Some(pid) => sender.send(Ok(pid.as_u32())).await?,
- None => {
- sender
- .send(Err(anyhow!(
- "Terminal was spawned but PID was not available"
- )))
- .await?
- }
- },
- Err(error) => sender.send(Err(anyhow!(error))).await?,
- };
-
- Ok(())
- })
- }
-
fn close_session(&mut self, entity_id: EntityId, window: &mut Window, cx: &mut Context<Self>) {
let Some(session) = self
.sessions
@@ -104,12 +104,6 @@ impl DebugSession {
&self.mode
}
- pub(crate) fn running_state(&self) -> Entity<RunningState> {
- match &self.mode {
- DebugSessionState::Running(running_state) => running_state.clone(),
- }
- }
-
pub(crate) fn label(&self, cx: &App) -> SharedString {
if let Some(label) = self.label.get() {
return label.clone();
@@ -131,6 +125,13 @@ impl DebugSession {
.to_owned()
}
+ #[allow(unused)]
+ pub(crate) fn running_state(&self) -> Entity<RunningState> {
+ match &self.mode {
+ DebugSessionState::Running(running_state) => running_state.clone(),
+ }
+ }
+
pub(crate) fn label_element(&self, cx: &App) -> AnyElement {
let label = self.label(cx);
@@ -5,15 +5,20 @@ pub(crate) mod module_list;
pub mod stack_frame_list;
pub mod variable_list;
-use std::{any::Any, ops::ControlFlow, sync::Arc, time::Duration};
+use std::{any::Any, ops::ControlFlow, path::PathBuf, sync::Arc, time::Duration};
use crate::persistence::{self, DebuggerPaneItem, SerializedPaneLayout};
use super::DebugPanelItemEvent;
+use anyhow::{Result, anyhow};
use breakpoint_list::BreakpointList;
use collections::{HashMap, IndexMap};
use console::Console;
-use dap::{Capabilities, Thread, client::SessionId, debugger_settings::DebuggerSettings};
+use dap::{
+ Capabilities, RunInTerminalRequestArguments, Thread, client::SessionId,
+ debugger_settings::DebuggerSettings,
+};
+use futures::{SinkExt, channel::mpsc};
use gpui::{
Action as _, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
NoAction, Pixels, Point, Subscription, Task, WeakEntity,
@@ -23,8 +28,10 @@ use module_list::ModuleList;
use project::{
Project,
debugger::session::{Session, SessionEvent, ThreadId, ThreadStatus},
+ terminals::TerminalKind,
};
use rpc::proto::ViewId;
+use serde_json::Value;
use settings::Settings;
use stack_frame_list::StackFrameList;
use terminal_view::TerminalView;
@@ -32,7 +39,7 @@ use ui::{
ActiveTheme, AnyElement, App, ButtonCommon as _, Clickable as _, Context, ContextMenu,
DropdownMenu, FluentBuilder, IconButton, IconName, IconSize, InteractiveElement, IntoElement,
Label, LabelCommon as _, ParentElement, Render, SharedString, StatefulInteractiveElement,
- Styled, Tab, Tooltip, VisibleOnHover, Window, div, h_flex, v_flex,
+ Styled, Tab, Tooltip, VisibleOnHover, VisualContext, Window, div, h_flex, v_flex,
};
use util::ResultExt;
use variable_list::VariableList;
@@ -559,6 +566,9 @@ impl RunningState {
this.remove_pane_item(DebuggerPaneItem::LoadedSources, window, cx);
}
}
+ SessionEvent::RunInTerminal { request, sender } => this
+ .handle_run_in_terminal(request, sender.clone(), window, cx)
+ .detach_and_log_err(cx),
_ => {}
}
@@ -657,6 +667,111 @@ impl RunningState {
self.panes.pane_at_pixel_position(position).is_some()
}
+ fn handle_run_in_terminal(
+ &self,
+ request: &RunInTerminalRequestArguments,
+ mut sender: mpsc::Sender<Result<u32>>,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) -> Task<Result<()>> {
+ let running = cx.entity();
+ let Ok(project) = self
+ .workspace
+ .update(cx, |workspace, _| workspace.project().clone())
+ else {
+ return Task::ready(Err(anyhow!("no workspace")));
+ };
+ let session = self.session.read(cx);
+
+ let cwd = Some(&request.cwd)
+ .filter(|cwd| cwd.len() > 0)
+ .map(PathBuf::from)
+ .or_else(|| session.binary().cwd.clone());
+
+ let mut args = request.args.clone();
+
+ // Handle special case for NodeJS debug adapter
+ // If only the Node binary path is provided, we set the command to None
+ // This prevents the NodeJS REPL from appearing, which is not the desired behavior
+ // The expected usage is for users to provide their own Node command, e.g., `node test.js`
+ // This allows the NodeJS debug client to attach correctly
+ let command = if args.len() > 1 {
+ Some(args.remove(0))
+ } else {
+ None
+ };
+
+ let mut envs: HashMap<String, String> = Default::default();
+ if let Some(Value::Object(env)) = &request.env {
+ for (key, value) in env {
+ let value_str = match (key.as_str(), value) {
+ (_, Value::String(value)) => value,
+ _ => continue,
+ };
+
+ envs.insert(key.clone(), value_str.clone());
+ }
+ }
+
+ let shell = project.read(cx).terminal_settings(&cwd, cx).shell.clone();
+ let kind = if let Some(command) = command {
+ let title = request.title.clone().unwrap_or(command.clone());
+ TerminalKind::Task(task::SpawnInTerminal {
+ id: task::TaskId("debug".to_string()),
+ full_label: title.clone(),
+ label: title.clone(),
+ command: command.clone(),
+ args,
+ command_label: title.clone(),
+ cwd,
+ env: envs,
+ use_new_terminal: true,
+ allow_concurrent_runs: true,
+ reveal: task::RevealStrategy::NoFocus,
+ reveal_target: task::RevealTarget::Dock,
+ hide: task::HideStrategy::Never,
+ shell,
+ show_summary: false,
+ show_command: false,
+ show_rerun: false,
+ })
+ } else {
+ TerminalKind::Shell(cwd.map(|c| c.to_path_buf()))
+ };
+
+ let workspace = self.workspace.clone();
+ let weak_project = project.downgrade();
+
+ let terminal_task = project.update(cx, |project, cx| {
+ project.create_terminal(kind, window.window_handle(), cx)
+ });
+ let terminal_task = cx.spawn_in(window, async move |_, cx| {
+ let terminal = terminal_task.await?;
+
+ let terminal_view = cx.new_window_entity(|window, cx| {
+ TerminalView::new(terminal.clone(), workspace, None, weak_project, window, cx)
+ })?;
+
+ running.update_in(cx, |running, window, cx| {
+ running.ensure_pane_item(DebuggerPaneItem::Terminal, window, cx);
+ running.debug_terminal.update(cx, |debug_terminal, cx| {
+ debug_terminal.terminal = Some(terminal_view);
+ cx.notify();
+ });
+ })?;
+
+ terminal.read_with(cx, |terminal, _| {
+ terminal
+ .pty_info
+ .pid()
+ .map(|pid| pid.as_u32())
+ .ok_or_else(|| anyhow!("Terminal was spawned but PID was not available"))
+ })?
+ });
+
+ cx.background_spawn(async move { anyhow::Ok(sender.send(terminal_task.await).await?) })
+ }
+
fn create_sub_view(
&self,
item_kind: DebuggerPaneItem,
@@ -523,8 +523,8 @@ async fn test_handle_error_run_in_terminal_reverse_request(
.fake_reverse_request::<RunInTerminal>(RunInTerminalRequestArguments {
kind: None,
title: None,
- cwd: path!("/non-existing/path").into(), // invalid/non-existing path will cause the terminal spawn to fail
- args: vec![],
+ cwd: "".into(),
+ args: vec!["oops".into(), "oops".into()],
env: None,
args_can_be_interpreted_by_shell: None,
})
@@ -15,20 +15,16 @@ use async_trait::async_trait;
use collections::HashMap;
use dap::{
Capabilities, CompletionItem, CompletionsArguments, DapRegistry, DebugRequest,
- EvaluateArguments, EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments,
- Source, StackFrameId, StartDebuggingRequestArguments,
+ EvaluateArguments, EvaluateArgumentsContext, EvaluateResponse, Source, StackFrameId,
adapters::{
DapStatus, DebugAdapterBinary, DebugAdapterName, DebugTaskDefinition, TcpArguments,
},
client::SessionId,
messages::Message,
- requests::{Completions, Evaluate, Request as _, RunInTerminal, StartDebugging},
+ requests::{Completions, Evaluate},
};
use fs::Fs;
-use futures::{
- channel::mpsc,
- future::{Shared, join_all},
-};
+use futures::future::{Shared, join_all};
use gpui::{App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task};
use http_client::HttpClient;
use language::{
@@ -43,9 +39,8 @@ use rpc::{
AnyProtoClient, TypedEnvelope,
proto::{self},
};
-use serde_json::Value;
use settings::{Settings, WorktreeId};
-use smol::{lock::Mutex, stream::StreamExt};
+use smol::lock::Mutex;
use std::{
borrow::Borrow,
collections::{BTreeMap, HashSet},
@@ -67,19 +62,6 @@ pub enum DapStoreEvent {
session_id: SessionId,
message: Message,
},
- RunInTerminal {
- session_id: SessionId,
- title: Option<String>,
- cwd: Option<Arc<Path>>,
- command: Option<String>,
- args: Vec<String>,
- envs: HashMap<String, String>,
- sender: mpsc::Sender<Result<u32>>,
- },
- SpawnChildSession {
- request: StartDebuggingRequestArguments,
- parent_session: Entity<Session>,
- },
Notification(String),
RemoteHasInitialized,
}
@@ -113,8 +95,6 @@ pub struct DapStore {
worktree_store: Entity<WorktreeStore>,
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 {}
@@ -184,35 +164,10 @@ impl DapStore {
mode: DapStoreMode,
breakpoint_store: Entity<BreakpointStore>,
worktree_store: Entity<WorktreeStore>,
- cx: &mut Context<Self>,
+ _cx: &mut Context<Self>,
) -> Self {
- let (start_debugging_tx, mut message_rx) =
- futures::channel::mpsc::unbounded::<(SessionId, Message)>();
- let task = cx.spawn(async move |this, cx| {
- while let Some((session_id, message)) = message_rx.next().await {
- match message {
- Message::Request(request) => {
- let _ = this
- .update(cx, |this, cx| {
- if request.command == StartDebugging::COMMAND {
- this.handle_start_debugging_request(session_id, request, cx)
- .detach_and_log_err(cx);
- } else if request.command == RunInTerminal::COMMAND {
- this.handle_run_in_terminal_request(session_id, request, cx)
- .detach_and_log_err(cx);
- }
- })
- .log_err();
- }
- _ => {}
- }
- }
- });
-
Self {
mode,
- _start_debugging_task: task,
- start_debugging_tx,
next_session_id: 0,
downstream_client: None,
breakpoint_store,
@@ -450,14 +405,11 @@ impl DapStore {
});
}
- let start_debugging_tx = self.start_debugging_tx.clone();
-
let session = Session::new(
self.breakpoint_store.clone(),
session_id,
parent_session,
template.clone(),
- start_debugging_tx,
cx,
);
@@ -469,7 +421,7 @@ impl DapStore {
SessionStateEvent::Shutdown => {
this.shutdown_session(session_id, cx).detach_and_log_err(cx);
}
- SessionStateEvent::Restart => {}
+ SessionStateEvent::Restart | SessionStateEvent::SpawnChildSession { .. } => {}
SessionStateEvent::Running => {
cx.emit(DapStoreEvent::DebugClientStarted(session_id));
}
@@ -583,196 +535,6 @@ impl DapStore {
)
}
- fn handle_start_debugging_request(
- &mut self,
- session_id: SessionId,
- request: dap::messages::Request,
- cx: &mut Context<Self>,
- ) -> Task<Result<()>> {
- let Some(parent_session) = self.session_by_id(session_id) else {
- return Task::ready(Err(anyhow!("Session not found")));
- };
- let request_seq = request.seq;
-
- let launch_request: Option<Result<StartDebuggingRequestArguments, _>> = request
- .arguments
- .as_ref()
- .map(|value| serde_json::from_value(value.clone()));
-
- let mut success = true;
- if let Some(Ok(request)) = launch_request {
- cx.emit(DapStoreEvent::SpawnChildSession {
- request,
- parent_session: parent_session.clone(),
- });
- } else {
- log::error!(
- "Failed to parse launch request arguments: {:?}",
- request.arguments
- );
- success = false;
- }
-
- cx.spawn(async move |_, cx| {
- parent_session
- .update(cx, |session, cx| {
- session.respond_to_client(
- request_seq,
- success,
- StartDebugging::COMMAND.to_string(),
- None,
- cx,
- )
- })?
- .await
- })
- }
-
- fn handle_run_in_terminal_request(
- &mut self,
- session_id: SessionId,
- request: dap::messages::Request,
- cx: &mut Context<Self>,
- ) -> Task<Result<()>> {
- let Some(session) = self.session_by_id(session_id) else {
- return Task::ready(Err(anyhow!("Session not found")));
- };
-
- let request_args = serde_json::from_value::<RunInTerminalRequestArguments>(
- request.arguments.unwrap_or_default(),
- )
- .expect("To parse StartDebuggingRequestArguments");
-
- let seq = request.seq;
-
- let cwd = Path::new(&request_args.cwd);
-
- match cwd.try_exists() {
- Ok(false) | Err(_) if !request_args.cwd.is_empty() => {
- return session.update(cx, |session, cx| {
- session.respond_to_client(
- seq,
- false,
- RunInTerminal::COMMAND.to_string(),
- serde_json::to_value(dap::ErrorResponse {
- error: Some(dap::Message {
- id: seq,
- format: format!("Received invalid/unknown cwd: {cwd:?}"),
- variables: None,
- send_telemetry: None,
- show_user: None,
- url: None,
- url_label: None,
- }),
- })
- .ok(),
- cx,
- )
- });
- }
- _ => (),
- }
- let mut args = request_args.args.clone();
-
- // Handle special case for NodeJS debug adapter
- // If only the Node binary path is provided, we set the command to None
- // This prevents the NodeJS REPL from appearing, which is not the desired behavior
- // The expected usage is for users to provide their own Node command, e.g., `node test.js`
- // This allows the NodeJS debug client to attach correctly
- let command = if args.len() > 1 {
- Some(args.remove(0))
- } else {
- None
- };
-
- let mut envs: HashMap<String, String> = Default::default();
- if let Some(Value::Object(env)) = request_args.env {
- for (key, value) in env {
- let value_str = match (key.as_str(), value) {
- (_, Value::String(value)) => value,
- _ => continue,
- };
-
- envs.insert(key, value_str);
- }
- }
-
- let (tx, mut rx) = mpsc::channel::<Result<u32>>(1);
- let cwd = Some(cwd)
- .filter(|cwd| cwd.as_os_str().len() > 0)
- .map(Arc::from)
- .or_else(|| {
- self.session_by_id(session_id)
- .and_then(|session| session.read(cx).binary().cwd.as_deref().map(Arc::from))
- });
- cx.emit(DapStoreEvent::RunInTerminal {
- session_id,
- title: request_args.title,
- cwd,
- command,
- args,
- envs,
- sender: tx,
- });
- cx.notify();
-
- let session = session.downgrade();
- cx.spawn(async move |_, cx| {
- let (success, body) = match rx.next().await {
- Some(Ok(pid)) => (
- true,
- serde_json::to_value(dap::RunInTerminalResponse {
- process_id: None,
- shell_process_id: Some(pid as u64),
- })
- .ok(),
- ),
- Some(Err(error)) => (
- false,
- serde_json::to_value(dap::ErrorResponse {
- error: Some(dap::Message {
- id: seq,
- format: error.to_string(),
- variables: None,
- send_telemetry: None,
- show_user: None,
- url: None,
- url_label: None,
- }),
- })
- .ok(),
- ),
- None => (
- false,
- serde_json::to_value(dap::ErrorResponse {
- error: Some(dap::Message {
- id: seq,
- format: "failed to receive response from spawn terminal".to_string(),
- variables: None,
- send_telemetry: None,
- show_user: None,
- url: None,
- url_label: None,
- }),
- })
- .ok(),
- ),
- };
-
- session
- .update(cx, |session, cx| {
- session.respond_to_client(
- seq,
- success,
- RunInTerminal::COMMAND.to_string(),
- body,
- cx,
- )
- })?
- .await
- })
- }
-
pub fn evaluate(
&self,
session_id: &SessionId,
@@ -14,14 +14,18 @@ use anyhow::{Context as _, Result, anyhow};
use collections::{HashMap, HashSet, IndexMap, IndexSet};
use dap::adapters::{DebugAdapterBinary, DebugTaskDefinition};
use dap::messages::Response;
+use dap::requests::{Request, RunInTerminal, StartDebugging};
use dap::{
Capabilities, ContinueArguments, EvaluateArgumentsContext, Module, Source, StackFrameId,
SteppingGranularity, StoppedEvent, VariableReference,
client::{DebugAdapterClient, SessionId},
messages::{Events, Message},
};
-use dap::{ExceptionBreakpointsFilter, ExceptionFilterOptions, OutputEventCategory};
-use futures::channel::oneshot;
+use dap::{
+ ExceptionBreakpointsFilter, ExceptionFilterOptions, OutputEventCategory,
+ RunInTerminalRequestArguments, StartDebuggingRequestArguments,
+};
+use futures::channel::{mpsc, oneshot};
use futures::{FutureExt, future::Shared};
use gpui::{
App, AppContext, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, SharedString,
@@ -522,7 +526,6 @@ pub struct Session {
is_session_terminated: bool,
requests: HashMap<TypeId, HashMap<RequestSlot, Shared<Task<Option<()>>>>>,
exception_breakpoints: BTreeMap<String, (ExceptionBreakpointsFilter, IsEnabled)>,
- start_debugging_requests_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
background_tasks: Vec<Task<()>>,
}
@@ -608,13 +611,20 @@ pub enum SessionEvent {
Threads,
InvalidateInlineValue,
CapabilitiesLoaded,
+ RunInTerminal {
+ request: RunInTerminalRequestArguments,
+ sender: mpsc::Sender<Result<u32>>,
+ },
}
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SessionStateEvent {
Running,
Shutdown,
Restart,
+ SpawnChildSession {
+ request: StartDebuggingRequestArguments,
+ },
}
impl EventEmitter<SessionEvent> for Session {}
@@ -629,7 +639,6 @@ impl Session {
session_id: SessionId,
parent_session: Option<Entity<Session>>,
template: DebugTaskDefinition,
- start_debugging_requests_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
cx: &mut App,
) -> Entity<Self> {
cx.new::<Self>(|cx| {
@@ -678,7 +687,6 @@ impl Session {
is_session_terminated: false,
exception_breakpoints: Default::default(),
definition: template,
- start_debugging_requests_tx,
};
this
@@ -702,7 +710,6 @@ impl Session {
) -> Task<Result<()>> {
let (message_tx, mut message_rx) = futures::channel::mpsc::unbounded();
let (initialized_tx, initialized_rx) = futures::channel::oneshot::channel();
- let session_id = self.session_id();
let background_tasks = vec![cx.spawn(async move |this: WeakEntity<Session>, cx| {
let mut initialized_tx = Some(initialized_tx);
@@ -719,10 +726,15 @@ impl Session {
break;
};
}
- } else {
- let Ok(Ok(_)) = this.update(cx, |this, _| {
- this.start_debugging_requests_tx
- .unbounded_send((session_id, message))
+ } else if let Message::Request(request) = message {
+ let Ok(_) = this.update(cx, |this, cx| {
+ if request.command == StartDebugging::COMMAND {
+ this.handle_start_debugging_request(request, cx)
+ .detach_and_log_err(cx);
+ } else if request.command == RunInTerminal::COMMAND {
+ this.handle_run_in_terminal_request(request, cx)
+ .detach_and_log_err(cx);
+ }
}) else {
break;
};
@@ -830,6 +842,109 @@ impl Session {
}
}
+ fn handle_start_debugging_request(
+ &mut self,
+ request: dap::messages::Request,
+ cx: &mut Context<Self>,
+ ) -> Task<Result<()>> {
+ let request_seq = request.seq;
+
+ let launch_request: Option<Result<StartDebuggingRequestArguments, _>> = request
+ .arguments
+ .as_ref()
+ .map(|value| serde_json::from_value(value.clone()));
+
+ let mut success = true;
+ if let Some(Ok(request)) = launch_request {
+ cx.emit(SessionStateEvent::SpawnChildSession { request });
+ } else {
+ log::error!(
+ "Failed to parse launch request arguments: {:?}",
+ request.arguments
+ );
+ success = false;
+ }
+
+ cx.spawn(async move |this, cx| {
+ this.update(cx, |this, cx| {
+ this.respond_to_client(
+ request_seq,
+ success,
+ StartDebugging::COMMAND.to_string(),
+ None,
+ cx,
+ )
+ })?
+ .await
+ })
+ }
+
+ fn handle_run_in_terminal_request(
+ &mut self,
+ request: dap::messages::Request,
+ cx: &mut Context<Self>,
+ ) -> Task<Result<()>> {
+ let request_args = serde_json::from_value::<RunInTerminalRequestArguments>(
+ request.arguments.unwrap_or_default(),
+ )
+ .expect("To parse StartDebuggingRequestArguments");
+
+ let seq = request.seq;
+
+ let (tx, mut rx) = mpsc::channel::<Result<u32>>(1);
+ cx.emit(SessionEvent::RunInTerminal {
+ request: request_args,
+ sender: tx,
+ });
+ cx.notify();
+
+ cx.spawn(async move |session, cx| {
+ let result = util::maybe!(async move {
+ rx.next().await.ok_or_else(|| {
+ anyhow!("failed to receive response from spawn terminal".to_string())
+ })?
+ })
+ .await;
+ let (success, body) = match result {
+ Ok(pid) => (
+ true,
+ serde_json::to_value(dap::RunInTerminalResponse {
+ process_id: None,
+ shell_process_id: Some(pid as u64),
+ })
+ .ok(),
+ ),
+ Err(error) => (
+ false,
+ serde_json::to_value(dap::ErrorResponse {
+ error: Some(dap::Message {
+ id: seq,
+ format: error.to_string(),
+ variables: None,
+ send_telemetry: None,
+ show_user: None,
+ url: None,
+ url_label: None,
+ }),
+ })
+ .ok(),
+ ),
+ };
+
+ session
+ .update(cx, |session, cx| {
+ session.respond_to_client(
+ seq,
+ success,
+ RunInTerminal::COMMAND.to_string(),
+ body,
+ cx,
+ )
+ })?
+ .await
+ })
+ }
+
pub(super) fn request_initialize(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
let adapter_id = String::from(self.definition.adapter.clone());
let request = Initialize { adapter_id };