diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index 6ddf563f06..b2bc0bc53f 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -1,3 +1,4 @@ +use crate::persistence::DebuggerPaneItem; use crate::{ ClearAllBreakpoints, Continue, CreateDebuggingSession, Disconnect, Pause, Restart, StepBack, StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints, persistence, @@ -32,8 +33,10 @@ use settings::Settings; use std::any::TypeId; use std::path::Path; use std::sync::Arc; -use task::{DebugTaskDefinition, DebugTaskTemplate}; -use terminal_view::terminal_panel::TerminalPanel; +use task::{ + DebugTaskDefinition, DebugTaskTemplate, HideStrategy, RevealStrategy, RevealTarget, TaskId, +}; +use terminal_view::TerminalView; use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*}; use workspace::{ Workspace, @@ -293,23 +296,21 @@ impl DebugPanel { (session.clone(), dap_store.boot_session(session, cx)) })?; + Self::register_session(this.clone(), session.clone(), cx).await?; - match task.await { - Err(e) => { - this.update(cx, |this, cx| { - this.workspace - .update(cx, |workspace, cx| { - workspace.show_error(&e, cx); - }) - .ok(); - }) - .ok(); + if let Err(e) = task.await { + this.update(cx, |this, cx| { + this.workspace + .update(cx, |workspace, cx| { + workspace.show_error(&e, cx); + }) + .ok(); + }) + .ok(); - session - .update(cx, |session, cx| session.shutdown(cx))? - .await; - } - Ok(_) => Self::register_session(this, session, cx).await?, + session + .update(cx, |session, cx| session.shutdown(cx))? + .await; } anyhow::Ok(()) @@ -467,6 +468,7 @@ impl DebugPanel { ) { match event { dap_store::DapStoreEvent::RunInTerminal { + session_id, title, cwd, command, @@ -476,6 +478,7 @@ impl DebugPanel { .. } => { self.handle_run_in_terminal_request( + *session_id, title.clone(), cwd.clone(), command.clone(), @@ -499,6 +502,7 @@ impl DebugPanel { fn handle_run_in_terminal_request( &self, + session_id: SessionId, title: Option, cwd: Option>, command: Option, @@ -506,56 +510,83 @@ impl DebugPanel { envs: HashMap, mut sender: mpsc::Sender>, window: &mut Window, - cx: &mut App, + cx: &mut Context, ) -> Task> { - let terminal_task = self.workspace.update(cx, |workspace, cx| { - let terminal_panel = workspace.panel::(cx).ok_or_else(|| { - anyhow!("RunInTerminal DAP request failed because TerminalPanel wasn't found") - }); - - let terminal_panel = match terminal_panel { - Ok(panel) => panel, - Err(err) => return Task::ready(Err(err)), - }; - - terminal_panel.update(cx, |terminal_panel, cx| { - let terminal_task = terminal_panel.add_terminal( - TerminalKind::Debug { - command, - args, - envs, - cwd, - title, - }, - task::RevealStrategy::Never, - window, - cx, - ); - - cx.spawn(async move |_, cx| { - let pid_task = async move { - let terminal = terminal_task.await?; - - terminal.read_with(cx, |terminal, _| terminal.pty_info.pid()) - }; - - pid_task.await - }) + 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 { - Ok(pid_task) => match pid_task.await { - Ok(Some(pid)) => sender.send(Ok(pid.as_u32())).await?, - Ok(None) => { + 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?, }, Err(error) => sender.send(Err(anyhow!(error))).await?, }; diff --git a/crates/debugger_ui/src/persistence.rs b/crates/debugger_ui/src/persistence.rs index 283efd0d76..0e2ea28fab 100644 --- a/crates/debugger_ui/src/persistence.rs +++ b/crates/debugger_ui/src/persistence.rs @@ -9,7 +9,7 @@ use util::ResultExt; use workspace::{Member, Pane, PaneAxis, Workspace}; use crate::session::running::{ - self, RunningState, SubView, breakpoint_list::BreakpointList, console::Console, + self, DebugTerminal, RunningState, SubView, breakpoint_list::BreakpointList, console::Console, loaded_source_list::LoadedSourceList, module_list::ModuleList, stack_frame_list::StackFrameList, variable_list::VariableList, }; @@ -22,6 +22,7 @@ pub(crate) enum DebuggerPaneItem { Frames, Modules, LoadedSources, + Terminal, } impl DebuggerPaneItem { @@ -33,6 +34,7 @@ impl DebuggerPaneItem { DebuggerPaneItem::Frames, DebuggerPaneItem::Modules, DebuggerPaneItem::LoadedSources, + DebuggerPaneItem::Terminal, ]; VARIANTS } @@ -55,6 +57,7 @@ impl DebuggerPaneItem { DebuggerPaneItem::Frames => SharedString::new_static("Frames"), DebuggerPaneItem::Modules => SharedString::new_static("Modules"), DebuggerPaneItem::LoadedSources => SharedString::new_static("Sources"), + DebuggerPaneItem::Terminal => SharedString::new_static("Terminal"), } } } @@ -169,6 +172,7 @@ pub(crate) fn deserialize_pane_layout( console: &Entity, breakpoint_list: &Entity, loaded_sources: &Entity, + terminal: &Entity, subscriptions: &mut HashMap, window: &mut Window, cx: &mut Context, @@ -191,6 +195,7 @@ pub(crate) fn deserialize_pane_layout( console, breakpoint_list, loaded_sources, + terminal, subscriptions, window, cx, @@ -273,6 +278,13 @@ pub(crate) fn deserialize_pane_layout( })), cx, )), + DebuggerPaneItem::Terminal => Box::new(SubView::new( + pane.focus_handle(cx), + terminal.clone().into(), + DebuggerPaneItem::Terminal, + None, + cx, + )), }) .collect(); diff --git a/crates/debugger_ui/src/session.rs b/crates/debugger_ui/src/session.rs index 7862379413..ee70559ad7 100644 --- a/crates/debugger_ui/src/session.rs +++ b/crates/debugger_ui/src/session.rs @@ -104,6 +104,12 @@ impl DebugSession { &self.mode } + pub(crate) fn running_state(&self) -> Entity { + match &self.mode { + DebugSessionState::Running(running_state) => running_state.clone(), + } + } + pub(crate) fn label(&self, cx: &App) -> String { if let Some(label) = self.label.get() { return label.to_owned(); diff --git a/crates/debugger_ui/src/session/running.rs b/crates/debugger_ui/src/session/running.rs index 85c2c85f89..305d5a8e66 100644 --- a/crates/debugger_ui/src/session/running.rs +++ b/crates/debugger_ui/src/session/running.rs @@ -27,6 +27,7 @@ use project::{ use rpc::proto::ViewId; use settings::Settings; use stack_frame_list::StackFrameList; +use terminal_view::TerminalView; use ui::{ ActiveTheme, AnyElement, App, Context, ContextMenu, DropdownMenu, FluentBuilder, InteractiveElement, IntoElement, Label, LabelCommon as _, ParentElement, Render, SharedString, @@ -35,7 +36,7 @@ use ui::{ use util::ResultExt; use variable_list::VariableList; use workspace::{ - ActivePaneDecorator, DraggedTab, Item, Member, Pane, PaneGroup, Workspace, + ActivePaneDecorator, DraggedTab, Item, ItemHandle, Member, Pane, PaneGroup, Workspace, item::TabContentParams, move_item, pane::Event, }; @@ -50,6 +51,7 @@ pub struct RunningState { _subscriptions: Vec, stack_frame_list: Entity, loaded_sources_list: Entity, + pub debug_terminal: Entity, module_list: Entity, _console: Entity, breakpoint_list: Entity, @@ -364,6 +366,40 @@ pub(crate) fn new_debugger_pane( ret } + +pub struct DebugTerminal { + pub terminal: Option>, + focus_handle: FocusHandle, +} + +impl DebugTerminal { + fn empty(cx: &mut Context) -> Self { + Self { + terminal: None, + focus_handle: cx.focus_handle(), + } + } +} + +impl gpui::Render for DebugTerminal { + fn render(&mut self, _window: &mut Window, _: &mut Context) -> impl IntoElement { + if let Some(terminal) = self.terminal.clone() { + terminal.into_any_element() + } else { + div().track_focus(&self.focus_handle).into_any_element() + } + } +} +impl Focusable for DebugTerminal { + fn focus_handle(&self, cx: &App) -> FocusHandle { + if let Some(terminal) = self.terminal.as_ref() { + return terminal.focus_handle(cx); + } else { + self.focus_handle.clone() + } + } +} + impl RunningState { pub fn new( session: Entity, @@ -380,6 +416,8 @@ impl RunningState { StackFrameList::new(workspace.clone(), session.clone(), weak_state, window, cx) }); + let debug_terminal = cx.new(DebugTerminal::empty); + let variable_list = cx.new(|cx| VariableList::new(session.clone(), stack_frame_list.clone(), window, cx)); @@ -452,6 +490,7 @@ impl RunningState { &console, &breakpoint_list, &loaded_source_list, + &debug_terminal, &mut pane_close_subscriptions, window, cx, @@ -494,6 +533,7 @@ impl RunningState { breakpoint_list, loaded_sources_list: loaded_source_list, pane_close_subscriptions, + debug_terminal, _schedule_serialize: None, } } @@ -525,6 +565,90 @@ impl RunningState { self.panes.pane_at_pixel_position(position).is_some() } + fn create_sub_view( + &self, + item_kind: DebuggerPaneItem, + pane: &Entity, + cx: &mut Context, + ) -> Box { + match item_kind { + DebuggerPaneItem::Console => { + let weak_console = self._console.clone().downgrade(); + + Box::new(SubView::new( + pane.focus_handle(cx), + self._console.clone().into(), + item_kind, + Some(Box::new(move |cx| { + weak_console + .read_with(cx, |console, cx| console.show_indicator(cx)) + .unwrap_or_default() + })), + cx, + )) + } + DebuggerPaneItem::Variables => Box::new(SubView::new( + self.variable_list.focus_handle(cx), + self.variable_list.clone().into(), + item_kind, + None, + cx, + )), + DebuggerPaneItem::BreakpointList => Box::new(SubView::new( + self.breakpoint_list.focus_handle(cx), + self.breakpoint_list.clone().into(), + item_kind, + None, + cx, + )), + DebuggerPaneItem::Frames => Box::new(SubView::new( + self.stack_frame_list.focus_handle(cx), + self.stack_frame_list.clone().into(), + item_kind, + None, + cx, + )), + DebuggerPaneItem::Modules => Box::new(SubView::new( + self.module_list.focus_handle(cx), + self.module_list.clone().into(), + item_kind, + None, + cx, + )), + DebuggerPaneItem::LoadedSources => Box::new(SubView::new( + self.loaded_sources_list.focus_handle(cx), + self.loaded_sources_list.clone().into(), + item_kind, + None, + cx, + )), + DebuggerPaneItem::Terminal => Box::new(SubView::new( + self.debug_terminal.focus_handle(cx), + self.debug_terminal.clone().into(), + item_kind, + None, + cx, + )), + } + } + + pub(crate) fn ensure_pane_item( + &mut self, + item_kind: DebuggerPaneItem, + window: &mut Window, + cx: &mut Context, + ) { + if self.pane_items_status(cx).get(&item_kind) == Some(&true) { + return; + }; + let pane = self.panes.last_pane(); + let sub_view = self.create_sub_view(item_kind, &pane, cx); + + pane.update(cx, |pane, cx| { + pane.add_item_inner(sub_view, false, false, false, None, window, cx); + }) + } + pub(crate) fn add_pane_item( &mut self, item_kind: DebuggerPaneItem, @@ -538,58 +662,7 @@ impl RunningState { ); if let Some(pane) = self.panes.pane_at_pixel_position(position) { - let sub_view = match item_kind { - DebuggerPaneItem::Console => { - let weak_console = self._console.clone().downgrade(); - - Box::new(SubView::new( - pane.focus_handle(cx), - self._console.clone().into(), - item_kind, - Some(Box::new(move |cx| { - weak_console - .read_with(cx, |console, cx| console.show_indicator(cx)) - .unwrap_or_default() - })), - cx, - )) - } - DebuggerPaneItem::Variables => Box::new(SubView::new( - self.variable_list.focus_handle(cx), - self.variable_list.clone().into(), - item_kind, - None, - cx, - )), - DebuggerPaneItem::BreakpointList => Box::new(SubView::new( - self.breakpoint_list.focus_handle(cx), - self.breakpoint_list.clone().into(), - item_kind, - None, - cx, - )), - DebuggerPaneItem::Frames => Box::new(SubView::new( - self.stack_frame_list.focus_handle(cx), - self.stack_frame_list.clone().into(), - item_kind, - None, - cx, - )), - DebuggerPaneItem::Modules => Box::new(SubView::new( - self.module_list.focus_handle(cx), - self.module_list.clone().into(), - item_kind, - None, - cx, - )), - DebuggerPaneItem::LoadedSources => Box::new(SubView::new( - self.loaded_sources_list.focus_handle(cx), - self.loaded_sources_list.clone().into(), - item_kind, - None, - cx, - )), - }; + let sub_view = self.create_sub_view(item_kind, pane, cx); pane.update(cx, |pane, cx| { pane.add_item(sub_view, false, false, None, window, cx); diff --git a/crates/debugger_ui/src/tests/debugger_panel.rs b/crates/debugger_ui/src/tests/debugger_panel.rs index 60760dcb89..7c84e7fa43 100644 --- a/crates/debugger_ui/src/tests/debugger_panel.rs +++ b/crates/debugger_ui/src/tests/debugger_panel.rs @@ -1,4 +1,4 @@ -use crate::{tests::start_debug_session, *}; +use crate::{persistence::DebuggerPaneItem, tests::start_debug_session, *}; use dap::{ ErrorResponse, Message, RunInTerminalRequestArguments, SourceBreakpoint, StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest, @@ -25,7 +25,7 @@ use std::{ atomic::{AtomicBool, Ordering}, }, }; -use terminal_view::{TerminalView, terminal_panel::TerminalPanel}; +use terminal_view::terminal_panel::TerminalPanel; use tests::{active_debug_session_panel, init_test, init_test_workspace}; use util::path; use workspace::{Item, dock::Panel}; @@ -385,22 +385,17 @@ async fn test_handle_successful_run_in_terminal_reverse_request( workspace .update(cx, |workspace, _window, cx| { - let terminal_panel = workspace.panel::(cx).unwrap(); - - let panel = terminal_panel.read(cx).pane().unwrap().read(cx); - - assert_eq!(1, panel.items_len()); - assert!( - panel - .active_item() - .unwrap() - .downcast::() - .unwrap() + let debug_panel = workspace.panel::(cx).unwrap(); + let session = debug_panel.read(cx).active_session().unwrap(); + let running = session.read(cx).running_state(); + assert_eq!( + running .read(cx) - .terminal() - .read(cx) - .debug_terminal() + .pane_items_status(cx) + .get(&DebuggerPaneItem::Terminal), + Some(&true) ); + assert!(running.read(cx).debug_terminal.read(cx).terminal.is_some()); }) .unwrap(); diff --git a/crates/project/src/terminals.rs b/crates/project/src/terminals.rs index 8ce6a7f8da..81467a1a7b 100644 --- a/crates/project/src/terminals.rs +++ b/crates/project/src/terminals.rs @@ -31,14 +31,6 @@ pub enum TerminalKind { Shell(Option), /// Run a task. Task(SpawnInTerminal), - /// Run a debug terminal. - Debug { - command: Option, - args: Vec, - envs: HashMap, - cwd: Option>, - title: Option, - }, } /// SshCommand describes how to connect to a remote server @@ -105,7 +97,6 @@ impl Project { self.active_project_directory(cx) } } - TerminalKind::Debug { cwd, .. } => cwd.clone(), }; let mut settings_location = None; @@ -209,7 +200,6 @@ impl Project { this.active_project_directory(cx) } } - TerminalKind::Debug { cwd, .. } => cwd.clone(), }; let ssh_details = this.ssh_details(cx); @@ -243,7 +233,6 @@ impl Project { }; let mut python_venv_activate_command = None; - let debug_terminal = matches!(kind, TerminalKind::Debug { .. }); let (spawn_task, shell) = match kind { TerminalKind::Shell(_) => { @@ -339,27 +328,6 @@ impl Project { } } } - TerminalKind::Debug { - command, - args, - envs, - title, - .. - } => { - env.extend(envs); - - let shell = if let Some(program) = command { - Shell::WithArguments { - program, - args, - title_override: Some(title.unwrap_or("Debug Terminal".into()).into()), - } - } else { - settings.shell.clone() - }; - - (None, shell) - } }; TerminalBuilder::new( local_path.map(|path| path.to_path_buf()), @@ -373,7 +341,6 @@ impl Project { ssh_details.is_some(), window, completion_tx, - debug_terminal, cx, ) .map(|builder| { diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 8b28097127..a2a184f6a3 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -352,7 +352,6 @@ impl TerminalBuilder { is_ssh_terminal: bool, window: AnyWindowHandle, completion_tx: Sender>, - debug_terminal: bool, cx: &App, ) -> Result { // If the parent environment doesn't have a locale set @@ -502,7 +501,6 @@ impl TerminalBuilder { word_regex: RegexSearch::new(WORD_REGEX).unwrap(), python_file_line_regex: RegexSearch::new(PYTHON_FILE_LINE_REGEX).unwrap(), vi_mode_enabled: false, - debug_terminal, is_ssh_terminal, python_venv_directory, }; @@ -660,7 +658,6 @@ pub struct Terminal { python_file_line_regex: RegexSearch, task: Option, vi_mode_enabled: bool, - debug_terminal: bool, is_ssh_terminal: bool, } @@ -1855,10 +1852,6 @@ impl Terminal { self.task.as_ref() } - pub fn debug_terminal(&self) -> bool { - self.debug_terminal - } - pub fn wait_for_completed_task(&self, cx: &App) -> Task> { if let Some(task) = self.task() { if task.status == TaskStatus::Running { diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 12b5f4290e..3bfa46858c 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -1420,9 +1420,6 @@ impl Item for TerminalView { } } }, - None if self.terminal.read(cx).debug_terminal() => { - (IconName::Debug, Color::Muted, None) - } None => (IconName::Terminal, Color::Muted, None), }; @@ -1583,7 +1580,7 @@ impl SerializableItem for TerminalView { cx: &mut Context, ) -> Option>> { let terminal = self.terminal().read(cx); - if terminal.task().is_some() || terminal.debug_terminal() { + if terminal.task().is_some() { return None; } diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 1d8ce261cf..929087baca 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -142,6 +142,10 @@ impl PaneGroup { self.root.first_pane() } + pub fn last_pane(&self) -> Entity { + self.root.last_pane() + } + pub fn find_pane_in_direction( &mut self, active_pane: &Entity, @@ -360,6 +364,13 @@ impl Member { } } + fn last_pane(&self) -> Entity { + match self { + Member::Axis(axis) => axis.members.last().unwrap().last_pane(), + Member::Pane(pane) => pane.clone(), + } + } + pub fn render( &self, basis: usize,