feat(pane-mode): add 's' key to create stacked panes
Implement Ctrl+P then 's' keyboard shortcut to stack a new pane on top of the active pane. This completes the pane mode action set alongside existing split and close actions. - Add keyS and actionStack constants to keyboard bridge - Map 's'/'S' keys to stack action - Implement handlePaneStack() to create stacked panes via StackedPaneManager - Clone pane state to new stacked pane for script execution
- commit
- 2e0dabfe3a3d0c10a4443cd73c78bc13ddad2946
- author
- bamen · · 2025-10-14 18:10 UTC
- parent
- b171bac
- signature
- unsigned
2 files · +31 -0
M internal/app/browser/workspace_pane_mode.go +26 -0
| @@ -91,6 +91,8 @@ func (wm *WorkspaceManager) HandlePaneAction(action string) { | |||
| 91 | 91 | wm.handlePaneClose(activePane) | |
| 92 | 92 | case "split-right", "split-left", "split-up", "split-down": | |
| 93 | 93 | wm.handlePaneSplit(activePane, action) | |
| 94 | + | case "stack": | |
| 95 | + | wm.handlePaneStack(activePane) | |
| 94 | 96 | default: | |
| 95 | 97 | log.Printf("[pane-mode] Unknown action: %s", action) | |
| 96 | 98 | } | |
| @@ -156,6 +158,30 @@ func (wm *WorkspaceManager) handlePaneSplit(node *paneNode, action string) { | |||
| 156 | 158 | log.Printf("[pane-mode] Split successful: direction=%s", direction) | |
| 157 | 159 | } | |
| 158 | 160 | ||
| 161 | + | // handlePaneStack creates a stacked pane | |
| 162 | + | func (wm *WorkspaceManager) handlePaneStack(node *paneNode) { | |
| 163 | + | if node == nil { | |
| 164 | + | return | |
| 165 | + | } | |
| 166 | + | ||
| 167 | + | log.Printf("[pane-mode] Stacking pane %p", node) | |
| 168 | + | ||
| 169 | + | // Notify JavaScript first so it can show toast | |
| 170 | + | wm.dispatchPaneModeEvent("action", "stack") | |
| 171 | + | ||
| 172 | + | // Perform the stack operation | |
| 173 | + | newNode, err := wm.stackedPaneManager.StackPane(node) | |
| 174 | + | if err != nil { | |
| 175 | + | log.Printf("[pane-mode] Failed to stack pane: %v", err) | |
| 176 | + | return | |
| 177 | + | } | |
| 178 | + | ||
| 179 | + | // Load initial URL in the new pane so scripts execute | |
| 180 | + | wm.clonePaneState(node, newNode) | |
| 181 | + | ||
| 182 | + | log.Printf("[pane-mode] Stack successful") | |
| 183 | + | } | |
| 184 | + | ||
| 159 | 185 | // dispatchPaneModeEvent sends a pane mode event to JavaScript for UI updates | |
| 160 | 186 | func (wm *WorkspaceManager) dispatchPaneModeEvent(event string, detail string) { | |
| 161 | 187 | if wm.paneModeActivePane == nil || wm.paneModeActivePane.pane == nil || wm.paneModeActivePane.pane.webView == nil { | |
| @@ -91,6 +91,8 @@ func (wm *WorkspaceManager) HandlePaneAction(action string) { | |||||
| 91 | wm.handlePaneClose(activePane) | 91 | wm.handlePaneClose(activePane) | ||
| 92 | case "split-right", "split-left", "split-up", "split-down": | 92 | case "split-right", "split-left", "split-up", "split-down": | ||
| 93 | wm.handlePaneSplit(activePane, action) | 93 | wm.handlePaneSplit(activePane, action) | ||
| 94 | + | case "stack": | |||
| 95 | + | wm.handlePaneStack(activePane) | |||
| 94 | default: | 96 | default: | ||
| 95 | log.Printf("[pane-mode] Unknown action: %s", action) | 97 | log.Printf("[pane-mode] Unknown action: %s", action) | ||
| 96 | } | 98 | } | ||
| @@ -156,6 +158,30 @@ func (wm *WorkspaceManager) handlePaneSplit(node *paneNode, action string) { | |||||
| 156 | log.Printf("[pane-mode] Split successful: direction=%s", direction) | 158 | log.Printf("[pane-mode] Split successful: direction=%s", direction) | ||
| 157 | } | 159 | } | ||
| 158 | 160 | ||||
| 161 | + | // handlePaneStack creates a stacked pane | |||
| 162 | + | func (wm *WorkspaceManager) handlePaneStack(node *paneNode) { | |||
| 163 | + | if node == nil { | |||
| 164 | + | return | |||
| 165 | + | } | |||
| 166 | + | ||||
| 167 | + | log.Printf("[pane-mode] Stacking pane %p", node) | |||
| 168 | + | ||||
| 169 | + | // Notify JavaScript first so it can show toast | |||
| 170 | + | wm.dispatchPaneModeEvent("action", "stack") | |||
| 171 | + | ||||
| 172 | + | // Perform the stack operation | |||
| 173 | + | newNode, err := wm.stackedPaneManager.StackPane(node) | |||
| 174 | + | if err != nil { | |||
| 175 | + | log.Printf("[pane-mode] Failed to stack pane: %v", err) | |||
| 176 | + | return | |||
| 177 | + | } | |||
| 178 | + | ||||
| 179 | + | // Load initial URL in the new pane so scripts execute | |||
| 180 | + | wm.clonePaneState(node, newNode) | |||
| 181 | + | ||||
| 182 | + | log.Printf("[pane-mode] Stack successful") | |||
| 183 | + | } | |||
| 184 | + | ||||
| 159 | // dispatchPaneModeEvent sends a pane mode event to JavaScript for UI updates | 185 | // dispatchPaneModeEvent sends a pane mode event to JavaScript for UI updates | ||
| 160 | func (wm *WorkspaceManager) dispatchPaneModeEvent(event string, detail string) { | 186 | func (wm *WorkspaceManager) dispatchPaneModeEvent(event string, detail string) { | ||
| 161 | if wm.paneModeActivePane == nil || wm.paneModeActivePane.pane == nil || wm.paneModeActivePane.pane.webView == nil { | 187 | if wm.paneModeActivePane == nil || wm.paneModeActivePane.pane == nil || wm.paneModeActivePane.pane.webView == nil { | ||
M pkg/webkit/keyboard_bridge.go +5 -0
| @@ -39,6 +39,7 @@ const ( | |||
| 39 | 39 | keyC = "c" | |
| 40 | 40 | keyW = "w" | |
| 41 | 41 | keyX = "x" | |
| 42 | + | keyS = "s" | |
| 42 | 43 | keyEscape = "escape" | |
| 43 | 44 | keyArrowLeft = "arrowleft" | |
| 44 | 45 | keyArrowRight = "arrowright" | |
| @@ -58,6 +59,7 @@ const ( | |||
| 58 | 59 | actionSplitRight = "split-right" | |
| 59 | 60 | actionSplitDown = "split-down" | |
| 60 | 61 | actionSplitUp = "split-up" | |
| 62 | + | actionStack = "stack" | |
| 61 | 63 | actionExit = "exit" | |
| 62 | 64 | actionEnter = "enter" | |
| 63 | 65 | ) | |
| @@ -74,6 +76,7 @@ const ( | |||
| 74 | 76 | var shortcutActions = map[string]string{ | |
| 75 | 77 | // Pane mode actions (no modifier) | |
| 76 | 78 | keyX: actionClose, | |
| 79 | + | keyS: actionStack, | |
| 77 | 80 | keyL: actionSplitLeft, | |
| 78 | 81 | keyR: actionSplitRight, | |
| 79 | 82 | keyD: actionSplitDown, | |
| @@ -155,6 +158,8 @@ func (w *WebView) AttachKeyboardBridge() { | |||
| 155 | 158 | keyName = keyW | |
| 156 | 159 | case 'x', 'X': | |
| 157 | 160 | keyName = keyX | |
| 161 | + | case 's', 'S': | |
| 162 | + | keyName = keyS | |
| 158 | 163 | case gdkKeyLeft: | |
| 159 | 164 | keyName = keyArrowLeft | |
| 160 | 165 | case gdkKeyRight: | |
| @@ -39,6 +39,7 @@ const ( | |||||
| 39 | keyC = "c" | 39 | keyC = "c" | ||
| 40 | keyW = "w" | 40 | keyW = "w" | ||
| 41 | keyX = "x" | 41 | keyX = "x" | ||
| 42 | + | keyS = "s" | |||
| 42 | keyEscape = "escape" | 43 | keyEscape = "escape" | ||
| 43 | keyArrowLeft = "arrowleft" | 44 | keyArrowLeft = "arrowleft" | ||
| 44 | keyArrowRight = "arrowright" | 45 | keyArrowRight = "arrowright" | ||
| @@ -58,6 +59,7 @@ const ( | |||||
| 58 | actionSplitRight = "split-right" | 59 | actionSplitRight = "split-right" | ||
| 59 | actionSplitDown = "split-down" | 60 | actionSplitDown = "split-down" | ||
| 60 | actionSplitUp = "split-up" | 61 | actionSplitUp = "split-up" | ||
| 62 | + | actionStack = "stack" | |||
| 61 | actionExit = "exit" | 63 | actionExit = "exit" | ||
| 62 | actionEnter = "enter" | 64 | actionEnter = "enter" | ||
| 63 | ) | 65 | ) | ||
| @@ -74,6 +76,7 @@ const ( | |||||
| 74 | var shortcutActions = map[string]string{ | 76 | var shortcutActions = map[string]string{ | ||
| 75 | // Pane mode actions (no modifier) | 77 | // Pane mode actions (no modifier) | ||
| 76 | keyX: actionClose, | 78 | keyX: actionClose, | ||
| 79 | + | keyS: actionStack, | |||
| 77 | keyL: actionSplitLeft, | 80 | keyL: actionSplitLeft, | ||
| 78 | keyR: actionSplitRight, | 81 | keyR: actionSplitRight, | ||
| 79 | keyD: actionSplitDown, | 82 | keyD: actionSplitDown, | ||
| @@ -155,6 +158,8 @@ func (w *WebView) AttachKeyboardBridge() { | |||||
| 155 | keyName = keyW | 158 | keyName = keyW | ||
| 156 | case 'x', 'X': | 159 | case 'x', 'X': | ||
| 157 | keyName = keyX | 160 | keyName = keyX | ||
| 161 | + | case 's', 'S': | |||
| 162 | + | keyName = keyS | |||
| 158 | case gdkKeyLeft: | 163 | case gdkKeyLeft: | ||
| 159 | keyName = keyArrowLeft | 164 | keyName = keyArrowLeft | ||
| 160 | case gdkKeyRight: | 165 | case gdkKeyRight: | ||