diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 6bb8e01ab0..18fb97d052 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -693,7 +693,7 @@ } }, { - "context": "GitPanel || ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView", + "context": "GitPanel || ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || DebugPanel", "bindings": { // window related commands (ctrl-w X) "ctrl-w": null, diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index b6fd102ad1..5736d7ee69 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -1,7 +1,9 @@ use crate::persistence::DebuggerPaneItem; use crate::{ - ClearAllBreakpoints, Continue, CreateDebuggingSession, Disconnect, Pause, Restart, StepBack, - StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints, persistence, + ClearAllBreakpoints, Continue, CreateDebuggingSession, Disconnect, FocusBreakpointList, + FocusConsole, FocusFrames, FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables, + Pause, Restart, StepBack, StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints, + persistence, }; use crate::{new_session_modal::NewSessionModal, session::DebugSession}; use anyhow::{Result, anyhow}; @@ -38,6 +40,7 @@ use task::{ }; use terminal_view::TerminalView; use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*}; +use workspace::SplitDirection; use workspace::{ Pane, Workspace, dock::{DockPosition, Panel, PanelEvent}, @@ -790,6 +793,7 @@ impl DebugPanel { fn top_controls_strip(&self, window: &mut Window, cx: &mut Context) -> Option
{ let active_session = self.active_session.clone(); + let focus_handle = self.focus_handle.clone(); Some( h_flex() @@ -821,8 +825,17 @@ impl DebugPanel { this.pause_thread(cx); }, )) - .tooltip(move |window, cx| { - Tooltip::text("Pause program")(window, cx) + .tooltip({ + let focus_handle = focus_handle.clone(); + move |window, cx| { + Tooltip::for_action_in( + "Pause program", + &Pause, + &focus_handle, + window, + cx, + ) + } }), ) } else { @@ -835,8 +848,17 @@ impl DebugPanel { |this, _, _window, cx| this.continue_thread(cx), )) .disabled(thread_status != ThreadStatus::Stopped) - .tooltip(move |window, cx| { - Tooltip::text("Continue program")(window, cx) + .tooltip({ + let focus_handle = focus_handle.clone(); + move |window, cx| { + Tooltip::for_action_in( + "Continue program", + &Continue, + &focus_handle, + window, + cx, + ) + } }), ) } @@ -852,8 +874,17 @@ impl DebugPanel { }, )) .disabled(thread_status != ThreadStatus::Stopped) - .tooltip(move |window, cx| { - Tooltip::text("Step over")(window, cx) + .tooltip({ + let focus_handle = focus_handle.clone(); + move |window, cx| { + Tooltip::for_action_in( + "Step over", + &StepOver, + &focus_handle, + window, + cx, + ) + } }), ) .child( @@ -867,8 +898,17 @@ impl DebugPanel { }, )) .disabled(thread_status != ThreadStatus::Stopped) - .tooltip(move |window, cx| { - Tooltip::text("Step out")(window, cx) + .tooltip({ + let focus_handle = focus_handle.clone(); + move |window, cx| { + Tooltip::for_action_in( + "Step out", + &StepOut, + &focus_handle, + window, + cx, + ) + } }), ) .child( @@ -882,8 +922,17 @@ impl DebugPanel { }, )) .disabled(thread_status != ThreadStatus::Stopped) - .tooltip(move |window, cx| { - Tooltip::text("Step in")(window, cx) + .tooltip({ + let focus_handle = focus_handle.clone(); + move |window, cx| { + Tooltip::for_action_in( + "Step in", + &StepInto, + &focus_handle, + window, + cx, + ) + } }), ) .child(Divider::vertical()) @@ -916,8 +965,17 @@ impl DebugPanel { this.toggle_ignore_breakpoints(cx); }, )) - .tooltip(move |window, cx| { - Tooltip::text("Disable all breakpoints")(window, cx) + .tooltip({ + let focus_handle = focus_handle.clone(); + move |window, cx| { + Tooltip::for_action_in( + "Disable all breakpoints", + &ToggleIgnoreBreakpoints, + &focus_handle, + window, + cx, + ) + } }), ) .child(Divider::vertical()) @@ -930,8 +988,17 @@ impl DebugPanel { this.restart_session(cx); }, )) - .tooltip(move |window, cx| { - Tooltip::text("Restart")(window, cx) + .tooltip({ + let focus_handle = focus_handle.clone(); + move |window, cx| { + Tooltip::for_action_in( + "Restart", + &Restart, + &focus_handle, + window, + cx, + ) + } }), ) .child( @@ -948,15 +1015,24 @@ impl DebugPanel { && thread_status != ThreadStatus::Running, ) .tooltip({ + let focus_handle = focus_handle.clone(); let label = if capabilities .supports_terminate_threads_request .unwrap_or_default() { "Terminate Thread" } else { - "Terminate all Threads" + "Terminate All Threads" }; - move |window, cx| Tooltip::text(label)(window, cx) + move |window, cx| { + Tooltip::for_action_in( + label, + &Stop, + &focus_handle, + window, + cx, + ) + } }), ) }, @@ -1006,19 +1082,57 @@ impl DebugPanel { }); } }) - .tooltip(|window, cx| { - Tooltip::for_action( - "New Debug Session", - &CreateDebuggingSession, - window, - cx, - ) + .tooltip({ + let focus_handle = focus_handle.clone(); + move |window, cx| { + Tooltip::for_action_in( + "New Debug Session", + &CreateDebuggingSession, + &focus_handle, + window, + cx, + ) + } }), ), ), ) } + fn activate_pane_in_direction( + &mut self, + direction: SplitDirection, + window: &mut Window, + cx: &mut Context, + ) { + if let Some(session) = self.active_session() { + session.update(cx, |session, cx| { + if let Some(running) = session.mode().as_running() { + running.update(cx, |running, cx| { + running.activate_pane_in_direction(direction, window, cx); + }) + } + }) + } + } + + fn activate_item( + &mut self, + item: DebuggerPaneItem, + window: &mut Window, + cx: &mut Context, + ) { + if let Some(session) = self.active_session() { + session.update(cx, |session, cx| { + if let Some(running) = session.mode().as_running() { + running.update(cx, |running, cx| { + running.activate_item(item, window, cx); + }) + } + }) + } + } + fn activate_session( &mut self, session_item: Entity, @@ -1111,6 +1225,7 @@ impl Panel for DebugPanel { impl Render for DebugPanel { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let has_sessions = self.sessions.len() > 0; + let this = cx.weak_entity(); debug_assert_eq!(has_sessions, self.active_session.is_some()); if self @@ -1128,6 +1243,105 @@ impl Render for DebugPanel { .key_context("DebugPanel") .child(h_flex().children(self.top_controls_strip(window, cx))) .track_focus(&self.focus_handle(cx)) + .on_action({ + let this = this.clone(); + move |_: &workspace::ActivatePaneLeft, window, cx| { + this.update(cx, |this, cx| { + this.activate_pane_in_direction(SplitDirection::Left, window, cx); + }) + .ok(); + } + }) + .on_action({ + let this = this.clone(); + move |_: &workspace::ActivatePaneRight, window, cx| { + this.update(cx, |this, cx| { + this.activate_pane_in_direction(SplitDirection::Right, window, cx); + }) + .ok(); + } + }) + .on_action({ + let this = this.clone(); + move |_: &workspace::ActivatePaneUp, window, cx| { + this.update(cx, |this, cx| { + this.activate_pane_in_direction(SplitDirection::Up, window, cx); + }) + .ok(); + } + }) + .on_action({ + let this = this.clone(); + move |_: &workspace::ActivatePaneDown, window, cx| { + this.update(cx, |this, cx| { + this.activate_pane_in_direction(SplitDirection::Down, window, cx); + }) + .ok(); + } + }) + .on_action({ + let this = this.clone(); + move |_: &FocusConsole, window, cx| { + this.update(cx, |this, cx| { + this.activate_item(DebuggerPaneItem::Console, window, cx); + }) + .ok(); + } + }) + .on_action({ + let this = this.clone(); + move |_: &FocusVariables, window, cx| { + this.update(cx, |this, cx| { + this.activate_item(DebuggerPaneItem::Variables, window, cx); + }) + .ok(); + } + }) + .on_action({ + let this = this.clone(); + move |_: &FocusBreakpointList, window, cx| { + this.update(cx, |this, cx| { + this.activate_item(DebuggerPaneItem::BreakpointList, window, cx); + }) + .ok(); + } + }) + .on_action({ + let this = this.clone(); + move |_: &FocusFrames, window, cx| { + this.update(cx, |this, cx| { + this.activate_item(DebuggerPaneItem::Frames, window, cx); + }) + .ok(); + } + }) + .on_action({ + let this = this.clone(); + move |_: &FocusModules, window, cx| { + this.update(cx, |this, cx| { + this.activate_item(DebuggerPaneItem::Modules, window, cx); + }) + .ok(); + } + }) + .on_action({ + let this = this.clone(); + move |_: &FocusLoadedSources, window, cx| { + this.update(cx, |this, cx| { + this.activate_item(DebuggerPaneItem::LoadedSources, window, cx); + }) + .ok(); + } + }) + .on_action({ + let this = this.clone(); + move |_: &FocusTerminal, window, cx| { + this.update(cx, |this, cx| { + this.activate_item(DebuggerPaneItem::Terminal, window, cx); + }) + .ok(); + } + }) .when(self.active_session.is_some(), |this| { this.on_mouse_down( MouseButton::Right, diff --git a/crates/debugger_ui/src/debugger_ui.rs b/crates/debugger_ui/src/debugger_ui.rs index bb90399837..c95c33be9d 100644 --- a/crates/debugger_ui/src/debugger_ui.rs +++ b/crates/debugger_ui/src/debugger_ui.rs @@ -35,6 +35,13 @@ actions!( ToggleIgnoreBreakpoints, ClearAllBreakpoints, CreateDebuggingSession, + FocusConsole, + FocusVariables, + FocusBreakpointList, + FocusFrames, + FocusModules, + FocusLoadedSources, + FocusTerminal, ] ); diff --git a/crates/debugger_ui/src/session/running.rs b/crates/debugger_ui/src/session/running.rs index f2b35fc4ea..218a20fab6 100644 --- a/crates/debugger_ui/src/session/running.rs +++ b/crates/debugger_ui/src/session/running.rs @@ -37,8 +37,8 @@ use ui::{ use util::ResultExt; use variable_list::VariableList; use workspace::{ - ActivePaneDecorator, DraggedTab, Item, ItemHandle, Member, Pane, PaneGroup, Workspace, - item::TabContentParams, move_item, pane::Event, + ActivePaneDecorator, DraggedTab, Item, ItemHandle, Member, Pane, PaneGroup, SplitDirection, + Workspace, item::TabContentParams, move_item, pane::Event, }; pub struct RunningState { @@ -57,6 +57,7 @@ pub struct RunningState { _console: Entity, breakpoint_list: Entity, panes: PaneGroup, + active_pane: Option>, pane_close_subscriptions: HashMap, _schedule_serialize: Option>, } @@ -167,8 +168,14 @@ impl Item for SubView { } impl Render for SubView { - fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { - v_flex().size_full().child(self.inner.clone()) + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + v_flex() + .size_full() + .when(self.pane_focus_handle.contains_focused(window, cx), |el| { + // TODO better way of showing focus? + el.border_1().border_color(gpui::red()) + }) + .child(self.inner.clone()) } } @@ -576,6 +583,7 @@ impl RunningState { stack_frame_list, session_id, panes, + active_pane: None, module_list, _console: console, breakpoint_list, @@ -616,7 +624,7 @@ impl RunningState { fn create_sub_view( &self, item_kind: DebuggerPaneItem, - pane: &Entity, + _pane: &Entity, cx: &mut Context, ) -> Box { match item_kind { @@ -624,7 +632,7 @@ impl RunningState { let weak_console = self._console.clone().downgrade(); Box::new(SubView::new( - pane.focus_handle(cx), + self._console.focus_handle(cx), self._console.clone().into(), item_kind, Some(Box::new(move |cx| { @@ -784,6 +792,9 @@ impl RunningState { debug_assert!(_did_find_pane); cx.notify(); } + Event::Focus => { + this.active_pane = Some(source_pane.clone()); + } Event::ZoomIn => { source_pane.update(cx, |pane, cx| { pane.set_zoomed(true, cx); @@ -800,6 +811,27 @@ impl RunningState { } } + pub(crate) fn activate_pane_in_direction( + &mut self, + direction: SplitDirection, + window: &mut Window, + cx: &mut Context, + ) { + if let Some(pane) = self + .active_pane + .as_ref() + .and_then(|pane| self.panes.find_pane_in_direction(pane, direction, cx)) + { + window.focus(&pane.focus_handle(cx)); + } else { + self.workspace + .update(cx, |workspace, cx| { + workspace.activate_pane_in_direction(direction, window, cx) + }) + .ok(); + } + } + pub(crate) fn go_to_selected_stack_frame(&self, window: &Window, cx: &mut Context) { if self.thread_id.is_some() { self.stack_frame_list @@ -838,8 +870,7 @@ impl RunningState { &self.module_list } - #[cfg(test)] - pub(crate) fn activate_modules_list(&self, window: &mut Window, cx: &mut App) { + pub(crate) fn activate_item(&self, item: DebuggerPaneItem, window: &mut Window, cx: &mut App) { let (variable_list_position, pane) = self .panes .panes() @@ -847,7 +878,7 @@ impl RunningState { .find_map(|pane| { pane.read(cx) .items_of_type::() - .position(|view| view.read(cx).view_kind().to_shared_string() == *"Modules") + .position(|view| view.read(cx).view_kind() == item) .map(|view| (view, pane)) }) .unwrap(); @@ -855,6 +886,7 @@ impl RunningState { this.activate_item(variable_list_position, true, true, window, cx); }) } + #[cfg(test)] pub(crate) fn variable_list(&self) -> &Entity { &self.variable_list diff --git a/crates/debugger_ui/src/session/running/breakpoint_list.rs b/crates/debugger_ui/src/session/running/breakpoint_list.rs index d9972284fe..a8917e84e5 100644 --- a/crates/debugger_ui/src/session/running/breakpoint_list.rs +++ b/crates/debugger_ui/src/session/running/breakpoint_list.rs @@ -45,6 +45,7 @@ impl Focusable for BreakpointList { self.focus_handle.clone() } } + impl BreakpointList { pub(super) fn new( session: Entity, @@ -213,6 +214,7 @@ impl Render for BreakpointList { } v_flex() .id("breakpoint-list") + .track_focus(&self.focus_handle) .on_hover(cx.listener(|this, hovered, window, cx| { if *hovered { this.show_scrollbar = true; diff --git a/crates/debugger_ui/src/session/running/console.rs b/crates/debugger_ui/src/session/running/console.rs index 86be2269e4..2fb43fcb9a 100644 --- a/crates/debugger_ui/src/session/running/console.rs +++ b/crates/debugger_ui/src/session/running/console.rs @@ -7,7 +7,9 @@ use collections::HashMap; use dap::OutputEvent; use editor::{CompletionProvider, Editor, EditorElement, EditorStyle, ExcerptId}; use fuzzy::StringMatchCandidate; -use gpui::{Context, Entity, Render, Subscription, Task, TextStyle, WeakEntity}; +use gpui::{ + Context, Entity, FocusHandle, Focusable, Render, Subscription, Task, TextStyle, WeakEntity, +}; use language::{Buffer, CodeLabel, ToOffset}; use menu::Confirm; use project::{ @@ -28,6 +30,7 @@ pub struct Console { stack_frame_list: Entity, last_token: OutputToken, update_output_task: Task<()>, + focus_handle: FocusHandle, } impl Console { @@ -56,6 +59,7 @@ impl Console { editor.set_show_edit_predictions(Some(false), window, cx); editor }); + let focus_handle = cx.focus_handle(); let this = cx.weak_entity(); let query_bar = cx.new(|cx| { @@ -82,6 +86,7 @@ impl Console { stack_frame_list, update_output_task: Task::ready(()), last_token: OutputToken(0), + focus_handle, } } @@ -230,6 +235,7 @@ impl Render for Console { }); v_flex() + .track_focus(&self.focus_handle) .key_context("DebugConsole") .on_action(cx.listener(Self::evaluate)) .size_full() @@ -242,6 +248,12 @@ impl Render for Console { } } +impl Focusable for Console { + fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle { + self.focus_handle.clone() + } +} + struct ConsoleQueryBarCompletionProvider(WeakEntity); impl CompletionProvider for ConsoleQueryBarCompletionProvider { diff --git a/crates/debugger_ui/src/session/running/stack_frame_list.rs b/crates/debugger_ui/src/session/running/stack_frame_list.rs index b60262f2d7..113c205a1a 100644 --- a/crates/debugger_ui/src/session/running/stack_frame_list.rs +++ b/crates/debugger_ui/src/session/running/stack_frame_list.rs @@ -132,13 +132,6 @@ impl StackFrameList { .collect() } - pub fn _get_main_stack_frame_id(&self, cx: &mut Context) -> u64 { - self.stack_frames(cx) - .first() - .map(|stack_frame| stack_frame.dap.id) - .unwrap_or(0) - } - pub fn selected_stack_frame_id(&self) -> Option { self.selected_stack_frame_id } @@ -557,6 +550,7 @@ impl StackFrameList { impl Render for StackFrameList { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { div() + .track_focus(&self.focus_handle) .size_full() .p_1() .child(list(self.list.clone()).size_full()) diff --git a/crates/debugger_ui/src/session/running/variable_list.rs b/crates/debugger_ui/src/session/running/variable_list.rs index 75a041e1b2..5cd0dcd28b 100644 --- a/crates/debugger_ui/src/session/running/variable_list.rs +++ b/crates/debugger_ui/src/session/running/variable_list.rs @@ -929,12 +929,12 @@ impl Render for VariableList { self.build_entries(cx); v_flex() + .track_focus(&self.focus_handle) .key_context("VariableList") .id("variable-list") .group("variable-list") .overflow_y_scroll() .size_full() - .track_focus(&self.focus_handle(cx)) .on_action(cx.listener(Self::select_first)) .on_action(cx.listener(Self::select_last)) .on_action(cx.listener(Self::select_prev)) diff --git a/crates/debugger_ui/src/tests/module_list.rs b/crates/debugger_ui/src/tests/module_list.rs index b84abe4373..5a9c7a5f5c 100644 --- a/crates/debugger_ui/src/tests/module_list.rs +++ b/crates/debugger_ui/src/tests/module_list.rs @@ -112,7 +112,7 @@ async fn test_module_list(executor: BackgroundExecutor, cx: &mut TestAppContext) }); running_state.update_in(cx, |this, window, cx| { - this.activate_modules_list(window, cx); + this.activate_item(crate::persistence::DebuggerPaneItem::Modules, window, cx); cx.refresh_windows(); }); diff --git a/crates/project/src/debugger/dap_store.rs b/crates/project/src/debugger/dap_store.rs index c2b47ce5ac..860892516f 100644 --- a/crates/project/src/debugger/dap_store.rs +++ b/crates/project/src/debugger/dap_store.rs @@ -55,6 +55,7 @@ use task::{DebugTaskDefinition, DebugTaskTemplate}; use util::ResultExt as _; use worktree::Worktree; +#[derive(Debug)] pub enum DapStoreEvent { DebugClientStarted(SessionId), DebugSessionInitialized(SessionId),