Merge pull request #376 from bnema/agent/rename-page-mode-vim-mode
feat(input): rename Page Mode to Vim Mode
52 files · +1789 -1568
M README.md +1 -1
| @@ -21,7 +21,7 @@ Dumber uses three layout levels: | |||||
| 21 | - **Workspaces** hold a layout of panes. | 21 | - **Workspaces** hold a layout of panes. | ||
| 22 | - **Panes** display web pages and can be split, stacked, moved, resized, or closed. | 22 | - **Panes** display web pages and can be split, stacked, moved, resized, or closed. | ||
| 23 | 23 | ||||
| 24 | - | Most browser-management actions are exposed through modal keybindings. Enter a mode, run one or more commands, then leave the mode. The default modes cover pane management, tabs, page scrolling, resizing, and session commands. | 24 | + | Most browser-management actions are exposed through modal keybindings. Enter a mode, run one or more commands, then leave the mode. The default modes cover pane management, tabs, Vim-style scrolling, resizing, and session commands. |
| 25 | 25 | ||||
| 26 | The browser chrome stays out of the way by default. Open the omnibox when you want to navigate; otherwise the pane is just the page or web app you are using. This makes Dumber work well as a side pane next to an editor, terminal, or another desktop application. | 26 | The browser chrome stays out of the way by default. Open the omnibox when you want to navigate; otherwise the pane is just the page or web app you are using. This makes Dumber work well as a side pane next to an editor, terminal, or another desktop application. | ||
| 27 | 27 | ||||
M docs/config/index.md +22 -22
| @@ -398,57 +398,57 @@ cancel = ["escape"] | |||||
| 398 | 398 | ||||
| 399 | > **Note:** Actions are inverted to key→action map in memory for O(1) lookup performance during navigation. | 399 | > **Note:** Actions are inverted to key→action map in memory for O(1) lookup performance during navigation. | ||
| 400 | 400 | ||||
| 401 | - | ### Page Mode | 401 | + | ### Vim Mode |
| 402 | 402 | ||||
| 403 | | Key | Type | Default | Description | | 403 | | Key | Type | Default | Description | | ||
| 404 | |-----|------|---------|-------------| | 404 | |-----|------|---------|-------------| | ||
| 405 | - | | `workspace.page_mode.activation_shortcut` | string | `"ctrl+y"` | Page mode activation key | | 405 | + | | `workspace.vim_mode.activation_shortcut` | string | `"ctrl+y"` | Vim mode activation key | |
| 406 | - | | `workspace.page_mode.timeout_ms` | int | `0` | Page mode timeout in milliseconds (`0` disables auto-timeout) | | 406 | + | | `workspace.vim_mode.timeout_ms` | int | `0` | Vim mode timeout in milliseconds (`0` disables auto-timeout) | |
| 407 | - | | `workspace.page_mode.actions` | map | See below | Action→binding mappings | | 407 | + | | `workspace.vim_mode.actions` | map | See below | Action→binding mappings | |
| 408 | 408 | ||||
| 409 | - | **Default page mode actions:** | 409 | + | **Default vim mode actions:** |
| 410 | 410 | ||||
| 411 | ```toml | 411 | ```toml | ||
| 412 | - | [workspace.page_mode.actions.page-scroll-left] | 412 | + | [workspace.vim_mode.actions.vim-scroll-left] |
| 413 | keys = ["h"] | 413 | keys = ["h"] | ||
| 414 | desc = "Scroll page left" | 414 | desc = "Scroll page left" | ||
| 415 | 415 | ||||
| 416 | - | [workspace.page_mode.actions.page-scroll-down] | 416 | + | [workspace.vim_mode.actions.vim-scroll-down] |
| 417 | keys = ["j"] | 417 | keys = ["j"] | ||
| 418 | desc = "Scroll page down" | 418 | desc = "Scroll page down" | ||
| 419 | 419 | ||||
| 420 | - | [workspace.page_mode.actions.page-scroll-up] | 420 | + | [workspace.vim_mode.actions.vim-scroll-up] |
| 421 | keys = ["k"] | 421 | keys = ["k"] | ||
| 422 | desc = "Scroll page up" | 422 | desc = "Scroll page up" | ||
| 423 | 423 | ||||
| 424 | - | [workspace.page_mode.actions.page-scroll-right] | 424 | + | [workspace.vim_mode.actions.vim-scroll-right] |
| 425 | keys = ["l"] | 425 | keys = ["l"] | ||
| 426 | desc = "Scroll page right" | 426 | desc = "Scroll page right" | ||
| 427 | 427 | ||||
| 428 | - | [workspace.page_mode.actions.page-scroll-down-fast] | 428 | + | [workspace.vim_mode.actions.vim-scroll-down-fast] |
| 429 | keys = ["shift+j"] | 429 | keys = ["shift+j"] | ||
| 430 | desc = "Scroll page down fast" | 430 | desc = "Scroll page down fast" | ||
| 431 | 431 | ||||
| 432 | - | [workspace.page_mode.actions.page-scroll-up-fast] | 432 | + | [workspace.vim_mode.actions.vim-scroll-up-fast] |
| 433 | keys = ["shift+k"] | 433 | keys = ["shift+k"] | ||
| 434 | desc = "Scroll page up fast" | 434 | desc = "Scroll page up fast" | ||
| 435 | 435 | ||||
| 436 | - | [workspace.page_mode.actions.confirm] | 436 | + | [workspace.vim_mode.actions.confirm] |
| 437 | keys = ["enter"] | 437 | keys = ["enter"] | ||
| 438 | desc = "Confirm action" | 438 | desc = "Confirm action" | ||
| 439 | 439 | ||||
| 440 | - | [workspace.page_mode.actions.cancel] | 440 | + | [workspace.vim_mode.actions.cancel] |
| 441 | keys = ["escape"] | 441 | keys = ["escape"] | ||
| 442 | desc = "Cancel/exit mode" | 442 | desc = "Cancel/exit mode" | ||
| 443 | ``` | 443 | ``` | ||
| 444 | 444 | ||||
| 445 | Notes: | 445 | Notes: | ||
| 446 | - | - Page Mode scrolls the active webpage inside the active pane only. | 446 | + | - Vim Mode scrolls the active webpage inside the active pane only. |
| 447 | - | - The default `timeout_ms = 0` means Page Mode stays active until you exit it or focus moves into an editable/browser UI context. | 447 | + | - The default `timeout_ms = 0` means Vim Mode stays active until you exit it or focus moves into an editable/browser UI context. |
| 448 | - | - Arrow keys still use the browser engine's native page-navigation path while Page Mode is active. | 448 | + | - Arrow keys still use the browser engine's native page-navigation path while Vim Mode is active. |
| 449 | - | - Other app-level shortcuts stay suspended until Page Mode exits, except for the Page Mode toggle itself. | 449 | + | - Other app-level shortcuts stay suspended until Vim Mode exits, except for the Vim Mode toggle itself. |
| 450 | - | - `workspace.styling.pane_mode_color`, `workspace.styling.transition_duration`, and `workspace.styling.mode_indicator_toaster_enabled` control the pane-local Page Mode visuals. | 450 | + | - `workspace.styling.pane_mode_color`, `workspace.styling.transition_duration`, and `workspace.styling.mode_indicator_toaster_enabled` control the pane-local Vim Mode visuals. |
| 451 | - | - Scroll execution: CEF and WebKit both use the shared `BuildScrollByJS` resolver when the page is ready. Each step starts under the viewport center, walks up through ancestors that can move in the requested direction, and hands scrolling to the document when a nested container reaches its boundary. The application repeater owns held-key cadence; each engine executes one immediate scroll step per tick. Cross-origin frame contents remain best-effort. CEF may use native precision-wheel input only as a pre-frame fallback before the browser/main frame is ready. | 451 | + | - Scroll execution: CEF and WebKit both use the shared `BuildPageScrollByJS` resolver when the page is ready. Each step starts under the viewport center, walks up through ancestors that can move in the requested direction, and hands scrolling to the document when a nested container reaches its boundary. The application repeater owns held-key cadence; each engine executes one immediate scroll step per tick. Cross-origin frame contents remain best-effort. CEF may use native precision-wheel input only as a pre-frame fallback before the browser/main frame is ready. |
| 452 | 452 | ||||
| 453 | ### Resize Mode | 453 | ### Resize Mode | ||
| 454 | 454 | ||||
| @@ -581,11 +581,11 @@ desc = "Open floating pane on GitHub" | |||||
| 581 | | `workspace.styling.border_width` | int | `1` | Active pane border width (px) - overlay | | 581 | | `workspace.styling.border_width` | int | `1` | Active pane border width (px) - overlay | | ||
| 582 | | `workspace.styling.border_color` | string | `"@theme_selected_bg_color"` | Active pane border color | | 582 | | `workspace.styling.border_color` | string | `"@theme_selected_bg_color"` | Active pane border color | | ||
| 583 | | `workspace.styling.mode_border_width` | int | `4` | Modal mode border width (px) - applies to all modes | | 583 | | `workspace.styling.mode_border_width` | int | `4` | Modal mode border width (px) - applies to all modes | | ||
| 584 | - | | `workspace.styling.pane_mode_color` | string | `"#4A90E2"` | Pane mode color (blue); also reused for Page mode pane-local accent, indicator, and pulse | | 584 | + | | `workspace.styling.pane_mode_color` | string | `"#4A90E2"` | Pane mode color (blue); also reused for Vim mode pane-local accent, indicator, and pulse | |
| 585 | | `workspace.styling.tab_mode_color` | string | `"#FFA500"` | Tab mode color (orange) - used for border and toaster | | 585 | | `workspace.styling.tab_mode_color` | string | `"#FFA500"` | Tab mode color (orange) - used for border and toaster | | ||
| 586 | | `workspace.styling.session_mode_color` | string | `"#9B59B6"` | Session mode color (purple) - used for border and toaster | | 586 | | `workspace.styling.session_mode_color` | string | `"#9B59B6"` | Session mode color (purple) - used for border and toaster | | ||
| 587 | | `workspace.styling.resize_mode_color` | string | `"#00D4AA"` | Resize mode color (teal) - used for border and toaster | | 587 | | `workspace.styling.resize_mode_color` | string | `"#00D4AA"` | Resize mode color (teal) - used for border and toaster | | ||
| 588 | - | | `workspace.styling.mode_indicator_toaster_enabled` | bool | `true` | Show bottom-left mode toaster when modal modes are active; Page Mode keeps a persistent `PAGE MODE` indicator until exit or toggle off | | 588 | + | | `workspace.styling.mode_indicator_toaster_enabled` | bool | `true` | Show bottom-left mode toaster when modal modes are active; Vim Mode keeps a persistent `VIM MODE` indicator until exit or toggle off | |
| 589 | | `workspace.styling.transition_duration` | int | `120` | Border and pulse transition duration (ms) | | 589 | | `workspace.styling.transition_duration` | int | `120` | Border and pulse transition duration (ms) | | ||
| 590 | 590 | ||||
| 591 | **Example:** | 591 | **Example:** | ||
| @@ -595,7 +595,7 @@ desc = "Open floating pane on GitHub" | |||||
| 595 | border_width = 1 | 595 | border_width = 1 | ||
| 596 | border_color = "@theme_selected_bg_color" | 596 | border_color = "@theme_selected_bg_color" | ||
| 597 | mode_border_width = 4 | 597 | mode_border_width = 4 | ||
| 598 | - | pane_mode_color = "#4A90E2" # Blue for pane mode and Page mode local accent | 598 | + | pane_mode_color = "#4A90E2" # Blue for pane mode and Vim mode local accent |
| 599 | tab_mode_color = "#FFA500" # Orange for tab mode | 599 | tab_mode_color = "#FFA500" # Orange for tab mode | ||
| 600 | session_mode_color = "#9B59B6" # Purple for session mode | 600 | session_mode_color = "#9B59B6" # Purple for session mode | ||
| 601 | resize_mode_color = "#00D4AA" # Teal for resize mode | 601 | resize_mode_color = "#00D4AA" # Teal for resize mode | ||
M docs/getting-started.md +8 -8
| @@ -16,12 +16,12 @@ Dumber uses modal keybindings inspired by Zellij: | |||||
| 16 | |------|------------|---------| | 16 | |------|------------|---------| | ||
| 17 | | Pane Mode | `Ctrl+P` | Split, close, focus panes | | 17 | | Pane Mode | `Ctrl+P` | Split, close, focus panes | | ||
| 18 | | Tab Mode | `Ctrl+T` | Create, close, switch tabs | | 18 | | Tab Mode | `Ctrl+T` | Create, close, switch tabs | | ||
| 19 | - | | Page Mode | `Ctrl+Y` | Scroll the active webpage with `h/j/k/l` and `Shift+J/K`; arrow keys stay native and other app shortcuts wait until exit | | 19 | + | | Vim Mode | `Ctrl+Y` | Scroll the active webpage with `h/j/k/l` and `Shift+J/K`; arrow keys stay native and other app shortcuts wait until exit | |
| 20 | | Resize Mode | `Ctrl+N` | Resize pane splits | | 20 | | Resize Mode | `Ctrl+N` | Resize pane splits | | ||
| 21 | | Session Mode | `Ctrl+O` | Session management | | 21 | | Session Mode | `Ctrl+O` | Session management | | ||
| 22 | 22 | ||||
| 23 | Press `Escape` or `Enter` to exit any mode. | 23 | Press `Escape` or `Enter` to exit any mode. | ||
| 24 | - | Page Mode stays local to the active pane and automatically leaves the mode when focus moves into the omnibox, find bar, overlays, or a page editable. | 24 | + | Vim Mode stays local to the active pane and automatically leaves the mode when focus moves into the omnibox, find bar, overlays, or a page editable. |
| 25 | 25 | ||||
| 26 | ## Pane Mode Quick Reference | 26 | ## Pane Mode Quick Reference | ||
| 27 | 27 | ||||
| @@ -30,18 +30,18 @@ Page Mode stays local to the active pane and automatically leaves the mode when | |||||
| 30 | 3. `Shift+arrows` to focus adjacent panes | 30 | 3. `Shift+arrows` to focus adjacent panes | ||
| 31 | 4. `X` to close current pane | 31 | 4. `X` to close current pane | ||
| 32 | 32 | ||||
| 33 | - | ## Page Mode Quick Reference | 33 | + | ## Vim Mode Quick Reference |
| 34 | 34 | ||||
| 35 | - | 1. Press `Ctrl+Y` to enter Page Mode | 35 | + | 1. Press `Ctrl+Y` to enter Vim Mode |
| 36 | 2. Use `h`, `j`, `k`, `l` to scroll left, down, up, or right | 36 | 2. Use `h`, `j`, `k`, `l` to scroll left, down, up, or right | ||
| 37 | 3. Use `Shift+J` / `Shift+K` for faster vertical jumps | 37 | 3. Use `Shift+J` / `Shift+K` for faster vertical jumps | ||
| 38 | - | 4. Arrow keys continue to use the browser engine's native page navigation while Page Mode is active | 38 | + | 4. Arrow keys continue to use the browser engine's native page navigation while Vim Mode is active |
| 39 | - | 5. Other app-level shortcuts stay suspended until you leave Page Mode | 39 | + | 5. Other app-level shortcuts stay suspended until you leave Vim Mode |
| 40 | 6. Press `Escape` or `Enter` to leave the mode | 40 | 6. Press `Escape` or `Enter` to leave the mode | ||
| 41 | 7. If a page input or editor is already focused, `Ctrl+Y` is passed through instead of stealing the keypress | 41 | 7. If a page input or editor is already focused, `Ctrl+Y` is passed through instead of stealing the keypress | ||
| 42 | 42 | ||||
| 43 | - | > **Engine behavior**: CEF and WebKit execute Page Mode scroll steps with the | 43 | + | > **Engine behavior**: CEF and WebKit execute Vim Mode scroll steps with the |
| 44 | - | > shared `BuildScrollByJS` resolver (viewport-center start, nested-scroller | 44 | + | > shared `BuildPageScrollByJS` resolver (viewport-center start, nested-scroller |
| 45 | > handoff, document fallback). The application repeater owns held-key cadence; | 45 | > handoff, document fallback). The application repeater owns held-key cadence; | ||
| 46 | > each engine runs one immediate step per tick. Cross-origin frames are | 46 | > each engine runs one immediate step per tick. Cross-origin frames are | ||
| 47 | > best-effort. CEF may use native precision-wheel input only before the browser | 47 | > best-effort. CEF may use native precision-wheel input only before the browser | ||
M docs/reference/configuration.md +3 -3
| @@ -100,9 +100,9 @@ | |||||
| 100 | | `workspace.tab_mode.activation_shortcut` | string | `ctrl+t` | | | 100 | | `workspace.tab_mode.activation_shortcut` | string | `ctrl+t` | | | ||
| 101 | | `workspace.tab_mode.timeout_ms` | int | `3000` | | | 101 | | `workspace.tab_mode.timeout_ms` | int | `3000` | | | ||
| 102 | | `workspace.tab_mode.actions.<action>` | []string | see defaults | tab mode key mappings | | 102 | | `workspace.tab_mode.actions.<action>` | []string | see defaults | tab mode key mappings | | ||
| 103 | - | | `workspace.page_mode.activation_shortcut` | string | `ctrl+y` | | | 103 | + | | `workspace.vim_mode.activation_shortcut` | string | `ctrl+y` | | |
| 104 | - | | `workspace.page_mode.timeout_ms` | int | `0` | >= 0 | | 104 | + | | `workspace.vim_mode.timeout_ms` | int | `0` | >= 0 | |
| 105 | - | | `workspace.page_mode.actions.<action>` | []string | see defaults | page mode key mappings | | 105 | + | | `workspace.vim_mode.actions.<action>` | object | see defaults | vim mode action object with `keys` ([]string) and `desc` (string) | |
| 106 | | `workspace.resize_mode.activation_shortcut` | string | `ctrl+n` | | | 106 | | `workspace.resize_mode.activation_shortcut` | string | `ctrl+n` | | | ||
| 107 | | `workspace.resize_mode.timeout_ms` | int | `3000` | | | 107 | | `workspace.resize_mode.timeout_ms` | int | `3000` | | | ||
| 108 | | `workspace.resize_mode.actions.<action>` | []string | see defaults | resize mode key mappings | | 108 | | `workspace.resize_mode.actions.<action>` | []string | see defaults | resize mode key mappings | | ||
M docs/reference/keybindings.md +12 -12
| @@ -8,12 +8,12 @@ Dumber uses modal keybindings inspired by Zellij. Press a mode activation key, t | |||||
| 8 | |------|-----|---------| | 8 | |------|-----|---------| | ||
| 9 | | Pane Mode | `Ctrl+P` | Split, close, focus panes | | 9 | | Pane Mode | `Ctrl+P` | Split, close, focus panes | | ||
| 10 | | Tab Mode | `Ctrl+T` | Create, close, switch tabs | | 10 | | Tab Mode | `Ctrl+T` | Create, close, switch tabs | | ||
| 11 | - | | Page Mode | `Ctrl+Y` | Scroll the active webpage with `h/j/k/l` and `Shift+J/K`; arrow keys stay native and other app shortcuts wait until exit | | 11 | + | | Vim Mode | `Ctrl+Y` | Scroll the active webpage with `h/j/k/l` and `Shift+J/K`; arrow keys stay native and other app shortcuts wait until exit | |
| 12 | | Resize Mode | `Ctrl+N` | Resize pane splits | | 12 | | Resize Mode | `Ctrl+N` | Resize pane splits | | ||
| 13 | | Session Mode | `Ctrl+O` | Session management | | 13 | | Session Mode | `Ctrl+O` | Session management | | ||
| 14 | 14 | ||||
| 15 | Press `Escape` or `Enter` to exit any mode. | 15 | Press `Escape` or `Enter` to exit any mode. | ||
| 16 | - | Page Mode activation is passed through when the focused webpage element is already editable, so browser-page editors keep their own `Ctrl+Y` behavior. | 16 | + | Vim Mode activation is passed through when the focused webpage element is already editable, so browser-page editors keep their own `Ctrl+Y` behavior. |
| 17 | 17 | ||||
| 18 | Keybinding tables use uppercase letters as visual labels for unshifted letter keys. In config, use lowercase (for example, `["w"]` for Pane Mode eject). Shifted keys are shown with an explicit `Shift+` prefix. | 18 | Keybinding tables use uppercase letters as visual labels for unshifted letter keys. In config, use lowercase (for example, `["w"]` for Pane Mode eject). Shifted keys are shown with an explicit `Shift+` prefix. | ||
| 19 | 19 | ||||
| @@ -53,11 +53,11 @@ Keybinding tables use uppercase letters as visual labels for unshifted letter ke | |||||
| 53 | | Confirm | `Enter` | | 53 | | Confirm | `Enter` | | ||
| 54 | | Cancel | `Escape` | | 54 | | Cancel | `Escape` | | ||
| 55 | 55 | ||||
| 56 | - | ## Page Mode (`Ctrl+Y`) | 56 | + | ## Vim Mode (`Ctrl+Y`) |
| 57 | 57 | ||||
| 58 | - | Page Mode is an explicit page-scrolling mode for the active pane only. It applies a pane-local accent from `workspace.styling.pane_mode_color`, and when `workspace.styling.mode_indicator_toaster_enabled` is true, shows a persistent bottom-left `PAGE MODE` toaster while the mode is active; the toaster hides on exit, when the mode is toggled off, or when the toaster is disabled in config. The mode exits automatically when focus moves into the omnibox, find bar, overlays, or an editable element inside the page. The default `timeout_ms` is `0`, so Page Mode does not auto-time out unless you configure one. Arrow keys continue to flow through the browser engine's native page-navigation path while Page Mode is active, while other app-level shortcuts stay suspended until you leave the mode. | 58 | + | Vim Mode is an explicit Vim-style scrolling mode for the active pane only. It applies a pane-local accent from `workspace.styling.pane_mode_color`, and when `workspace.styling.mode_indicator_toaster_enabled` is true, shows a persistent bottom-left `VIM MODE` toaster while the mode is active; the toaster hides on exit, when the mode is toggled off, or when the toaster is disabled in config. The mode exits automatically when focus moves into the omnibox, find bar, overlays, or an editable element inside the page. The default `timeout_ms` is `0`, so Vim Mode does not auto-time out unless you configure one. Arrow keys continue to flow through the browser engine's native page-navigation path while Vim Mode is active, while other app-level shortcuts stay suspended until you leave the mode. |
| 59 | 59 | ||||
| 60 | - | CEF and WebKit execute Page Mode scroll commands (`h/j/k/l`, `Shift+J/K`) with the shared `BuildScrollByJS` resolver. Each step starts under the viewport center, walks up through ancestors that can move in the requested direction, and hands scrolling to the document when a nested container reaches its boundary. Cross-origin frame contents remain best-effort. | 60 | + | CEF and WebKit execute Vim Mode scroll commands (`h/j/k/l`, `Shift+J/K`) with the shared `BuildPageScrollByJS` resolver. Each step starts under the viewport center, walks up through ancestors that can move in the requested direction, and hands scrolling to the document when a nested container reaches its boundary. Cross-origin frame contents remain best-effort. |
| 61 | 61 | ||||
| 62 | | Action | Keys | | 62 | | Action | Keys | | ||
| 63 | |--------|------| | 63 | |--------|------| | ||
| @@ -137,26 +137,26 @@ All keybindings can be customized in `~/.config/dumber/config.toml`: | |||||
| 137 | split-right = ["arrowright", "r"] | 137 | split-right = ["arrowright", "r"] | ||
| 138 | close-pane = ["x", "q"] | 138 | close-pane = ["x", "q"] | ||
| 139 | 139 | ||||
| 140 | - | [workspace.page_mode] | 140 | + | [workspace.vim_mode] |
| 141 | activation_shortcut = "ctrl+y" | 141 | activation_shortcut = "ctrl+y" | ||
| 142 | timeout_ms = 0 | 142 | timeout_ms = 0 | ||
| 143 | 143 | ||||
| 144 | - | [workspace.page_mode.actions.page-scroll-left] | 144 | + | [workspace.vim_mode.actions.vim-scroll-left] |
| 145 | keys = ["h"] | 145 | keys = ["h"] | ||
| 146 | 146 | ||||
| 147 | - | [workspace.page_mode.actions.page-scroll-down] | 147 | + | [workspace.vim_mode.actions.vim-scroll-down] |
| 148 | keys = ["j"] | 148 | keys = ["j"] | ||
| 149 | 149 | ||||
| 150 | - | [workspace.page_mode.actions.page-scroll-up] | 150 | + | [workspace.vim_mode.actions.vim-scroll-up] |
| 151 | keys = ["k"] | 151 | keys = ["k"] | ||
| 152 | 152 | ||||
| 153 | - | [workspace.page_mode.actions.page-scroll-right] | 153 | + | [workspace.vim_mode.actions.vim-scroll-right] |
| 154 | keys = ["l"] | 154 | keys = ["l"] | ||
| 155 | 155 | ||||
| 156 | - | [workspace.page_mode.actions.page-scroll-down-fast] | 156 | + | [workspace.vim_mode.actions.vim-scroll-down-fast] |
| 157 | keys = ["shift+j"] | 157 | keys = ["shift+j"] | ||
| 158 | 158 | ||||
| 159 | - | [workspace.page_mode.actions.page-scroll-up-fast] | 159 | + | [workspace.vim_mode.actions.vim-scroll-up-fast] |
| 160 | keys = ["shift+k"] | 160 | keys = ["shift+k"] | ||
| 161 | 161 | ||||
| 162 | [workspace.shortcuts.actions.close-pane] | 162 | [workspace.shortcuts.actions.close-pane] | ||
M internal/application/port/webview.go +4 -4
| @@ -395,7 +395,7 @@ type TextInputTargetProvider interface { | |||||
| 395 | TextInputTarget() TextInputTarget | 395 | TextInputTarget() TextInputTarget | ||
| 396 | } | 396 | } | ||
| 397 | 397 | ||||
| 398 | - | // PageScrollCommand identifies a semantic page-scroll request at the | 398 | + | // PageScrollCommand identifies a semantic vim-scroll request at the |
| 399 | // application boundary without depending on usecase package enums. | 399 | // application boundary without depending on usecase package enums. | ||
| 400 | type PageScrollCommand int | 400 | type PageScrollCommand int | ||
| 401 | 401 | ||||
| @@ -408,7 +408,7 @@ const ( | |||||
| 408 | PageScrollCommandDownFast | 408 | PageScrollCommandDownFast | ||
| 409 | ) | 409 | ) | ||
| 410 | 410 | ||||
| 411 | - | // PageScrollRequest carries a semantic page-scroll command identity and its | 411 | + | // PageScrollRequest carries a semantic vim-scroll command identity and its |
| 412 | // fallback pixel deltas. Engines own the execution strategy and may use the | 412 | // fallback pixel deltas. Engines own the execution strategy and may use the | ||
| 413 | // fallback delta when no native mechanism exists for the command. | 413 | // fallback delta when no native mechanism exists for the command. | ||
| 414 | type PageScrollRequest struct { | 414 | type PageScrollRequest struct { | ||
| @@ -422,7 +422,7 @@ type PageScrollRequest struct { | |||||
| 422 | 422 | ||||
| 423 | // PageScrollable is an optional capability for WebViews that support semantic | 423 | // PageScrollable is an optional capability for WebViews that support semantic | ||
| 424 | // page scrolling. Page scrolling is used by keyboard-driven navigation modes | 424 | // page scrolling. Page scrolling is used by keyboard-driven navigation modes | ||
| 425 | - | // such as Page mode. | 425 | + | // such as Vim mode. |
| 426 | // | 426 | // | ||
| 427 | // Engines own the execution strategy. When a native mechanism exists for the | 427 | // Engines own the execution strategy. When a native mechanism exists for the | ||
| 428 | // requested command, the engine may ignore the fallback delta. Otherwise it | 428 | // requested command, the engine may ignore the fallback delta. Otherwise it | ||
| @@ -432,7 +432,7 @@ type PageScrollable interface { | |||||
| 432 | } | 432 | } | ||
| 433 | 433 | ||||
| 434 | // PageScrollCanceler is an optional capability for adapters that queue held | 434 | // PageScrollCanceler is an optional capability for adapters that queue held | ||
| 435 | - | // page-scroll work. Implementations discard unconsumed continuous deltas while | 435 | + | // vim-scroll work. Implementations discard unconsumed continuous deltas while |
| 436 | // preserving any guaranteed tap already accepted. | 436 | // preserving any guaranteed tap already accepted. | ||
| 437 | type PageScrollCanceler interface { | 437 | type PageScrollCanceler interface { | ||
| 438 | CancelPageScroll(ctx context.Context) | 438 | CancelPageScroll(ctx context.Context) | ||
M internal/application/usecase/keybindings.go +1 -0
| @@ -18,6 +18,7 @@ var validModes = map[string]bool{ | |||||
| 18 | "global": true, | 18 | "global": true, | ||
| 19 | "pane": true, | 19 | "pane": true, | ||
| 20 | "tab": true, | 20 | "tab": true, | ||
| 21 | + | "vim": true, | |||
| 21 | "resize": true, | 22 | "resize": true, | ||
| 22 | "session": true, | 23 | "session": true, | ||
| 23 | } | 24 | } | ||
M internal/application/usecase/keybindings_test.go +11 -0
| @@ -72,6 +72,17 @@ func TestGetKeybindingsUseCase_Execute(t *testing.T) { | |||||
| 72 | } | 72 | } | ||
| 73 | 73 | ||||
| 74 | func TestSetKeybindingUseCase_Execute(t *testing.T) { | 74 | func TestSetKeybindingUseCase_Execute(t *testing.T) { | ||
| 75 | + | t.Run("accepts vim mode", func(t *testing.T) { | |||
| 76 | + | mockProvider := mocks.NewMockKeybindingsProvider(t) | |||
| 77 | + | mockSaver := mocks.NewMockKeybindingsSaver(t) | |||
| 78 | + | req := port.SetKeybindingRequest{Mode: "vim", Action: "vim-scroll-down", Keys: []string{"j"}} | |||
| 79 | + | mockProvider.EXPECT().CheckConflicts(mock.Anything, req.Mode, req.Action, req.Keys).Return(nil, nil) | |||
| 80 | + | mockSaver.EXPECT().SetKeybinding(mock.Anything, req).Return(nil) | |||
| 81 | + | ||||
| 82 | + | _, err := usecase.NewSetKeybindingUseCase(mockProvider, mockSaver).Execute(context.Background(), req) | |||
| 83 | + | require.NoError(t, err) | |||
| 84 | + | }) | |||
| 85 | + | ||||
| 75 | t.Run("successfully sets keybinding", func(t *testing.T) { | 86 | t.Run("successfully sets keybinding", func(t *testing.T) { | ||
| 76 | // Arrange | 87 | // Arrange | ||
| 77 | mockProvider := mocks.NewMockKeybindingsProvider(t) | 88 | mockProvider := mocks.NewMockKeybindingsProvider(t) | ||
D internal/application/usecase/page_mode_policy.go +0 -73
| @@ -1,73 +0,0 @@ | |||||
| 1 | - | package usecase | |||
| 2 | - | ||||
| 3 | - | // PageModePolicyTransition describes what the UI should do after a Page mode | |||
| 4 | - | // policy evaluation. | |||
| 5 | - | type PageModePolicyTransition string | |||
| 6 | - | ||||
| 7 | - | const ( | |||
| 8 | - | PageModePolicyTransitionStay PageModePolicyTransition = "stay" | |||
| 9 | - | PageModePolicyTransitionExit PageModePolicyTransition = "exit" | |||
| 10 | - | PageModePolicyTransitionBlockActivation PageModePolicyTransition = "block_activation" | |||
| 11 | - | ) | |||
| 12 | - | ||||
| 13 | - | // PageModePolicyTrigger identifies the focus or activation event being | |||
| 14 | - | // evaluated. | |||
| 15 | - | type PageModePolicyTrigger string | |||
| 16 | - | ||||
| 17 | - | const ( | |||
| 18 | - | PageModePolicyTriggerActivationAttempt PageModePolicyTrigger = "activation_attempt" | |||
| 19 | - | PageModePolicyTriggerOmniboxFocus PageModePolicyTrigger = "omnibox_focus" | |||
| 20 | - | PageModePolicyTriggerFindBarFocus PageModePolicyTrigger = "find_bar_focus" | |||
| 21 | - | PageModePolicyTriggerOverlayFocus PageModePolicyTrigger = "overlay_focus" | |||
| 22 | - | PageModePolicyTriggerPageEditableFocusChanged PageModePolicyTrigger = "page_editable_focus_changed" | |||
| 23 | - | PageModePolicyTriggerContextChanged PageModePolicyTrigger = "context_changed" | |||
| 24 | - | ) | |||
| 25 | - | ||||
| 26 | - | // PageModePolicyInput captures the current Page mode state and the event being | |||
| 27 | - | // evaluated. | |||
| 28 | - | type PageModePolicyInput struct { | |||
| 29 | - | Trigger PageModePolicyTrigger | |||
| 30 | - | PageModeActive bool | |||
| 31 | - | PageEditableFocused bool | |||
| 32 | - | EventInActiveContext bool | |||
| 33 | - | PreserveOnContextChange bool | |||
| 34 | - | } | |||
| 35 | - | ||||
| 36 | - | // PageModePolicyUseCase evaluates whether Page mode should stay active, exit, | |||
| 37 | - | // or be blocked from activating. | |||
| 38 | - | type PageModePolicyUseCase struct{} | |||
| 39 | - | ||||
| 40 | - | // NewPageModePolicyUseCase creates a PageModePolicyUseCase. | |||
| 41 | - | func NewPageModePolicyUseCase() *PageModePolicyUseCase { | |||
| 42 | - | return &PageModePolicyUseCase{} | |||
| 43 | - | } | |||
| 44 | - | ||||
| 45 | - | // Evaluate decides the Page mode transition for the provided event. | |||
| 46 | - | func (*PageModePolicyUseCase) Evaluate(input PageModePolicyInput) PageModePolicyTransition { | |||
| 47 | - | switch input.Trigger { | |||
| 48 | - | case PageModePolicyTriggerActivationAttempt: | |||
| 49 | - | if input.PageEditableFocused { | |||
| 50 | - | return PageModePolicyTransitionBlockActivation | |||
| 51 | - | } | |||
| 52 | - | return PageModePolicyTransitionStay | |||
| 53 | - | case PageModePolicyTriggerOmniboxFocus, | |||
| 54 | - | PageModePolicyTriggerFindBarFocus, | |||
| 55 | - | PageModePolicyTriggerOverlayFocus: | |||
| 56 | - | if input.PageModeActive { | |||
| 57 | - | return PageModePolicyTransitionExit | |||
| 58 | - | } | |||
| 59 | - | return PageModePolicyTransitionStay | |||
| 60 | - | case PageModePolicyTriggerPageEditableFocusChanged: | |||
| 61 | - | if input.PageModeActive && input.PageEditableFocused && input.EventInActiveContext { | |||
| 62 | - | return PageModePolicyTransitionExit | |||
| 63 | - | } | |||
| 64 | - | return PageModePolicyTransitionStay | |||
| 65 | - | case PageModePolicyTriggerContextChanged: | |||
| 66 | - | if input.PageModeActive && !input.PreserveOnContextChange { | |||
| 67 | - | return PageModePolicyTransitionExit | |||
| 68 | - | } | |||
| 69 | - | return PageModePolicyTransitionStay | |||
| 70 | - | default: | |||
| 71 | - | return PageModePolicyTransitionStay | |||
| 72 | - | } | |||
| 73 | - | } | |||
D internal/application/usecase/page_mode_policy_test.go +0 -109
| @@ -1,109 +0,0 @@ | |||||
| 1 | - | package usecase | |||
| 2 | - | ||||
| 3 | - | import ( | |||
| 4 | - | "testing" | |||
| 5 | - | ||||
| 6 | - | "github.com/stretchr/testify/assert" | |||
| 7 | - | ) | |||
| 8 | - | ||||
| 9 | - | func TestPageModePolicyEvaluate(t *testing.T) { | |||
| 10 | - | tests := []struct { | |||
| 11 | - | name string | |||
| 12 | - | input PageModePolicyInput | |||
| 13 | - | want PageModePolicyTransition | |||
| 14 | - | }{ | |||
| 15 | - | { | |||
| 16 | - | name: "activation blocked while page editable focused", | |||
| 17 | - | input: PageModePolicyInput{ | |||
| 18 | - | Trigger: PageModePolicyTriggerActivationAttempt, | |||
| 19 | - | PageEditableFocused: true, | |||
| 20 | - | }, | |||
| 21 | - | want: PageModePolicyTransitionBlockActivation, | |||
| 22 | - | }, | |||
| 23 | - | { | |||
| 24 | - | name: "activation stays allowed when page is not editable", | |||
| 25 | - | input: PageModePolicyInput{ | |||
| 26 | - | Trigger: PageModePolicyTriggerActivationAttempt, | |||
| 27 | - | PageEditableFocused: false, | |||
| 28 | - | }, | |||
| 29 | - | want: PageModePolicyTransitionStay, | |||
| 30 | - | }, | |||
| 31 | - | { | |||
| 32 | - | name: "active page editable focus exits page mode", | |||
| 33 | - | input: PageModePolicyInput{ | |||
| 34 | - | Trigger: PageModePolicyTriggerPageEditableFocusChanged, | |||
| 35 | - | PageModeActive: true, | |||
| 36 | - | PageEditableFocused: true, | |||
| 37 | - | EventInActiveContext: true, | |||
| 38 | - | }, | |||
| 39 | - | want: PageModePolicyTransitionExit, | |||
| 40 | - | }, | |||
| 41 | - | { | |||
| 42 | - | name: "background page editable focus does not exit page mode", | |||
| 43 | - | input: PageModePolicyInput{ | |||
| 44 | - | Trigger: PageModePolicyTriggerPageEditableFocusChanged, | |||
| 45 | - | PageModeActive: true, | |||
| 46 | - | PageEditableFocused: true, | |||
| 47 | - | EventInActiveContext: false, | |||
| 48 | - | }, | |||
| 49 | - | want: PageModePolicyTransitionStay, | |||
| 50 | - | }, | |||
| 51 | - | { | |||
| 52 | - | name: "overlay focus exits page mode", | |||
| 53 | - | input: PageModePolicyInput{ | |||
| 54 | - | Trigger: PageModePolicyTriggerOverlayFocus, | |||
| 55 | - | PageModeActive: true, | |||
| 56 | - | }, | |||
| 57 | - | want: PageModePolicyTransitionExit, | |||
| 58 | - | }, | |||
| 59 | - | { | |||
| 60 | - | name: "omnibox focus exits page mode", | |||
| 61 | - | input: PageModePolicyInput{ | |||
| 62 | - | Trigger: PageModePolicyTriggerOmniboxFocus, | |||
| 63 | - | PageModeActive: true, | |||
| 64 | - | }, | |||
| 65 | - | want: PageModePolicyTransitionExit, | |||
| 66 | - | }, | |||
| 67 | - | { | |||
| 68 | - | name: "find bar focus exits page mode", | |||
| 69 | - | input: PageModePolicyInput{ | |||
| 70 | - | Trigger: PageModePolicyTriggerFindBarFocus, | |||
| 71 | - | PageModeActive: true, | |||
| 72 | - | }, | |||
| 73 | - | want: PageModePolicyTransitionExit, | |||
| 74 | - | }, | |||
| 75 | - | { | |||
| 76 | - | name: "context change preserves page mode when scoped context stays valid", | |||
| 77 | - | input: PageModePolicyInput{ | |||
| 78 | - | Trigger: PageModePolicyTriggerContextChanged, | |||
| 79 | - | PageModeActive: true, | |||
| 80 | - | PreserveOnContextChange: true, | |||
| 81 | - | }, | |||
| 82 | - | want: PageModePolicyTransitionStay, | |||
| 83 | - | }, | |||
| 84 | - | { | |||
| 85 | - | name: "context change exits page mode when scoped context is lost", | |||
| 86 | - | input: PageModePolicyInput{ | |||
| 87 | - | Trigger: PageModePolicyTriggerContextChanged, | |||
| 88 | - | PageModeActive: true, | |||
| 89 | - | PreserveOnContextChange: false, | |||
| 90 | - | }, | |||
| 91 | - | want: PageModePolicyTransitionExit, | |||
| 92 | - | }, | |||
| 93 | - | { | |||
| 94 | - | name: "non page mode focus change stays normal", | |||
| 95 | - | input: PageModePolicyInput{ | |||
| 96 | - | Trigger: PageModePolicyTriggerOverlayFocus, | |||
| 97 | - | PageModeActive: false, | |||
| 98 | - | }, | |||
| 99 | - | want: PageModePolicyTransitionStay, | |||
| 100 | - | }, | |||
| 101 | - | } | |||
| 102 | - | ||||
| 103 | - | uc := NewPageModePolicyUseCase() | |||
| 104 | - | for _, tt := range tests { | |||
| 105 | - | t.Run(tt.name, func(t *testing.T) { | |||
| 106 | - | assert.Equal(t, tt.want, uc.Evaluate(tt.input)) | |||
| 107 | - | }) | |||
| 108 | - | } | |||
| 109 | - | } | |||
A internal/application/usecase/vim_mode_policy.go +73 -0
| @@ -0,0 +1,73 @@ | |||||
| 1 | + | package usecase | |||
| 2 | + | ||||
| 3 | + | // VimModePolicyTransition describes what the UI should do after a Vim mode | |||
| 4 | + | // policy evaluation. | |||
| 5 | + | type VimModePolicyTransition string | |||
| 6 | + | ||||
| 7 | + | const ( | |||
| 8 | + | VimModePolicyTransitionStay VimModePolicyTransition = "stay" | |||
| 9 | + | VimModePolicyTransitionExit VimModePolicyTransition = "exit" | |||
| 10 | + | VimModePolicyTransitionBlockActivation VimModePolicyTransition = "block_activation" | |||
| 11 | + | ) | |||
| 12 | + | ||||
| 13 | + | // VimModePolicyTrigger identifies the focus or activation event being | |||
| 14 | + | // evaluated. | |||
| 15 | + | type VimModePolicyTrigger string | |||
| 16 | + | ||||
| 17 | + | const ( | |||
| 18 | + | VimModePolicyTriggerActivationAttempt VimModePolicyTrigger = "activation_attempt" | |||
| 19 | + | VimModePolicyTriggerOmniboxFocus VimModePolicyTrigger = "omnibox_focus" | |||
| 20 | + | VimModePolicyTriggerFindBarFocus VimModePolicyTrigger = "find_bar_focus" | |||
| 21 | + | VimModePolicyTriggerOverlayFocus VimModePolicyTrigger = "overlay_focus" | |||
| 22 | + | VimModePolicyTriggerPageEditableFocusChanged VimModePolicyTrigger = "page_editable_focus_changed" | |||
| 23 | + | VimModePolicyTriggerContextChanged VimModePolicyTrigger = "context_changed" | |||
| 24 | + | ) | |||
| 25 | + | ||||
| 26 | + | // VimModePolicyInput captures the current Vim mode state and the event being | |||
| 27 | + | // evaluated. | |||
| 28 | + | type VimModePolicyInput struct { | |||
| 29 | + | Trigger VimModePolicyTrigger | |||
| 30 | + | VimModeActive bool | |||
| 31 | + | PageEditableFocused bool | |||
| 32 | + | EventInActiveContext bool | |||
| 33 | + | PreserveOnContextChange bool | |||
| 34 | + | } | |||
| 35 | + | ||||
| 36 | + | // VimModePolicyUseCase evaluates whether Vim mode should stay active, exit, | |||
| 37 | + | // or be blocked from activating. | |||
| 38 | + | type VimModePolicyUseCase struct{} | |||
| 39 | + | ||||
| 40 | + | // NewVimModePolicyUseCase creates a VimModePolicyUseCase. | |||
| 41 | + | func NewVimModePolicyUseCase() *VimModePolicyUseCase { | |||
| 42 | + | return &VimModePolicyUseCase{} | |||
| 43 | + | } | |||
| 44 | + | ||||
| 45 | + | // Evaluate decides the Vim mode transition for the provided event. | |||
| 46 | + | func (*VimModePolicyUseCase) Evaluate(input VimModePolicyInput) VimModePolicyTransition { | |||
| 47 | + | switch input.Trigger { | |||
| 48 | + | case VimModePolicyTriggerActivationAttempt: | |||
| 49 | + | if input.PageEditableFocused { | |||
| 50 | + | return VimModePolicyTransitionBlockActivation | |||
| 51 | + | } | |||
| 52 | + | return VimModePolicyTransitionStay | |||
| 53 | + | case VimModePolicyTriggerOmniboxFocus, | |||
| 54 | + | VimModePolicyTriggerFindBarFocus, | |||
| 55 | + | VimModePolicyTriggerOverlayFocus: | |||
| 56 | + | if input.VimModeActive { | |||
| 57 | + | return VimModePolicyTransitionExit | |||
| 58 | + | } | |||
| 59 | + | return VimModePolicyTransitionStay | |||
| 60 | + | case VimModePolicyTriggerPageEditableFocusChanged: | |||
| 61 | + | if input.VimModeActive && input.PageEditableFocused && input.EventInActiveContext { | |||
| 62 | + | return VimModePolicyTransitionExit | |||
| 63 | + | } | |||
| 64 | + | return VimModePolicyTransitionStay | |||
| 65 | + | case VimModePolicyTriggerContextChanged: | |||
| 66 | + | if input.VimModeActive && !input.PreserveOnContextChange { | |||
| 67 | + | return VimModePolicyTransitionExit | |||
| 68 | + | } | |||
| 69 | + | return VimModePolicyTransitionStay | |||
| 70 | + | default: | |||
| 71 | + | return VimModePolicyTransitionStay | |||
| 72 | + | } | |||
| 73 | + | } | |||
A internal/application/usecase/vim_mode_policy_test.go +109 -0
| @@ -0,0 +1,109 @@ | |||||
| 1 | + | package usecase | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "testing" | |||
| 5 | + | ||||
| 6 | + | "github.com/stretchr/testify/assert" | |||
| 7 | + | ) | |||
| 8 | + | ||||
| 9 | + | func TestVimModePolicyEvaluate(t *testing.T) { | |||
| 10 | + | tests := []struct { | |||
| 11 | + | name string | |||
| 12 | + | input VimModePolicyInput | |||
| 13 | + | want VimModePolicyTransition | |||
| 14 | + | }{ | |||
| 15 | + | { | |||
| 16 | + | name: "activation blocked while page editable focused", | |||
| 17 | + | input: VimModePolicyInput{ | |||
| 18 | + | Trigger: VimModePolicyTriggerActivationAttempt, | |||
| 19 | + | PageEditableFocused: true, | |||
| 20 | + | }, | |||
| 21 | + | want: VimModePolicyTransitionBlockActivation, | |||
| 22 | + | }, | |||
| 23 | + | { | |||
| 24 | + | name: "activation stays allowed when page is not editable", | |||
| 25 | + | input: VimModePolicyInput{ | |||
| 26 | + | Trigger: VimModePolicyTriggerActivationAttempt, | |||
| 27 | + | PageEditableFocused: false, | |||
| 28 | + | }, | |||
| 29 | + | want: VimModePolicyTransitionStay, | |||
| 30 | + | }, | |||
| 31 | + | { | |||
| 32 | + | name: "active page editable focus exits vim mode", | |||
| 33 | + | input: VimModePolicyInput{ | |||
| 34 | + | Trigger: VimModePolicyTriggerPageEditableFocusChanged, | |||
| 35 | + | VimModeActive: true, | |||
| 36 | + | PageEditableFocused: true, | |||
| 37 | + | EventInActiveContext: true, | |||
| 38 | + | }, | |||
| 39 | + | want: VimModePolicyTransitionExit, | |||
| 40 | + | }, | |||
| 41 | + | { | |||
| 42 | + | name: "background page editable focus does not exit vim mode", | |||
| 43 | + | input: VimModePolicyInput{ | |||
| 44 | + | Trigger: VimModePolicyTriggerPageEditableFocusChanged, | |||
| 45 | + | VimModeActive: true, | |||
| 46 | + | PageEditableFocused: true, | |||
| 47 | + | EventInActiveContext: false, | |||
| 48 | + | }, | |||
| 49 | + | want: VimModePolicyTransitionStay, | |||
| 50 | + | }, | |||
| 51 | + | { | |||
| 52 | + | name: "overlay focus exits vim mode", | |||
| 53 | + | input: VimModePolicyInput{ | |||
| 54 | + | Trigger: VimModePolicyTriggerOverlayFocus, | |||
| 55 | + | VimModeActive: true, | |||
| 56 | + | }, | |||
| 57 | + | want: VimModePolicyTransitionExit, | |||
| 58 | + | }, | |||
| 59 | + | { | |||
| 60 | + | name: "omnibox focus exits vim mode", | |||
| 61 | + | input: VimModePolicyInput{ | |||
| 62 | + | Trigger: VimModePolicyTriggerOmniboxFocus, | |||
| 63 | + | VimModeActive: true, | |||
| 64 | + | }, | |||
| 65 | + | want: VimModePolicyTransitionExit, | |||
| 66 | + | }, | |||
| 67 | + | { | |||
| 68 | + | name: "find bar focus exits vim mode", | |||
| 69 | + | input: VimModePolicyInput{ | |||
| 70 | + | Trigger: VimModePolicyTriggerFindBarFocus, | |||
| 71 | + | VimModeActive: true, | |||
| 72 | + | }, | |||
| 73 | + | want: VimModePolicyTransitionExit, | |||
| 74 | + | }, | |||
| 75 | + | { | |||
| 76 | + | name: "context change preserves vim mode when scoped context stays valid", | |||
| 77 | + | input: VimModePolicyInput{ | |||
| 78 | + | Trigger: VimModePolicyTriggerContextChanged, | |||
| 79 | + | VimModeActive: true, | |||
| 80 | + | PreserveOnContextChange: true, | |||
| 81 | + | }, | |||
| 82 | + | want: VimModePolicyTransitionStay, | |||
| 83 | + | }, | |||
| 84 | + | { | |||
| 85 | + | name: "context change exits vim mode when scoped context is lost", | |||
| 86 | + | input: VimModePolicyInput{ | |||
| 87 | + | Trigger: VimModePolicyTriggerContextChanged, | |||
| 88 | + | VimModeActive: true, | |||
| 89 | + | PreserveOnContextChange: false, | |||
| 90 | + | }, | |||
| 91 | + | want: VimModePolicyTransitionExit, | |||
| 92 | + | }, | |||
| 93 | + | { | |||
| 94 | + | name: "non vim mode focus change stays normal", | |||
| 95 | + | input: VimModePolicyInput{ | |||
| 96 | + | Trigger: VimModePolicyTriggerOverlayFocus, | |||
| 97 | + | VimModeActive: false, | |||
| 98 | + | }, | |||
| 99 | + | want: VimModePolicyTransitionStay, | |||
| 100 | + | }, | |||
| 101 | + | } | |||
| 102 | + | ||||
| 103 | + | uc := NewVimModePolicyUseCase() | |||
| 104 | + | for _, tt := range tests { | |||
| 105 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 106 | + | assert.Equal(t, tt.want, uc.Evaluate(tt.input)) | |||
| 107 | + | }) | |||
| 108 | + | } | |||
| 109 | + | } | |||
M internal/bootstrap/runtime_config.go +1 -1
| @@ -140,7 +140,7 @@ func cloneRuntimeSearchShortcuts(in map[string]entity.RuntimeSearchShortcut) map | |||||
| 140 | func cloneWorkspaceConfig(in entity.WorkspaceConfig) entity.WorkspaceConfig { | 140 | func cloneWorkspaceConfig(in entity.WorkspaceConfig) entity.WorkspaceConfig { | ||
| 141 | in.PaneMode.Actions = cloneActionBindings(in.PaneMode.Actions) | 141 | in.PaneMode.Actions = cloneActionBindings(in.PaneMode.Actions) | ||
| 142 | in.TabMode.Actions = cloneActionBindings(in.TabMode.Actions) | 142 | in.TabMode.Actions = cloneActionBindings(in.TabMode.Actions) | ||
| 143 | - | in.PageMode.Actions = cloneActionBindings(in.PageMode.Actions) | 143 | + | in.VimMode.Actions = cloneActionBindings(in.VimMode.Actions) |
| 144 | in.ResizeMode.Actions = cloneActionBindings(in.ResizeMode.Actions) | 144 | in.ResizeMode.Actions = cloneActionBindings(in.ResizeMode.Actions) | ||
| 145 | in.Shortcuts.Actions = cloneActionBindings(in.Shortcuts.Actions) | 145 | in.Shortcuts.Actions = cloneActionBindings(in.Shortcuts.Actions) | ||
| 146 | in.FloatingPane.Profiles = cloneFloatingPaneProfiles(in.FloatingPane.Profiles) | 146 | in.FloatingPane.Profiles = cloneFloatingPaneProfiles(in.FloatingPane.Profiles) | ||
M internal/bootstrap/runtime_config_test.go +8 -8
| @@ -258,8 +258,8 @@ func runtimeConfigWithNestedMutableFields() *config.Config { | |||||
| 258 | cfg.Workspace.TabMode.Actions = map[string]entity.ActionBinding{ | 258 | cfg.Workspace.TabMode.Actions = map[string]entity.ActionBinding{ | ||
| 259 | "next-tab": {Keys: []string{"l"}, Desc: "Next tab"}, | 259 | "next-tab": {Keys: []string{"l"}, Desc: "Next tab"}, | ||
| 260 | } | 260 | } | ||
| 261 | - | cfg.Workspace.PageMode.Actions = map[string]entity.ActionBinding{ | 261 | + | cfg.Workspace.VimMode.Actions = map[string]entity.ActionBinding{ |
| 262 | - | "page-scroll-down": {Keys: []string{"j"}, Desc: "Scroll down"}, | 262 | + | "vim-scroll-down": {Keys: []string{"j"}, Desc: "Scroll down"}, |
| 263 | } | 263 | } | ||
| 264 | cfg.Workspace.ResizeMode.Actions = map[string]entity.ActionBinding{ | 264 | cfg.Workspace.ResizeMode.Actions = map[string]entity.ActionBinding{ | ||
| 265 | "grow-right": {Keys: []string{"right"}, Desc: "Grow right"}, | 265 | "grow-right": {Keys: []string{"right"}, Desc: "Grow right"}, | ||
| @@ -283,8 +283,8 @@ func mutateNestedRuntimeConfigSnapshot(snapshot entity.RuntimeConfigSnapshot) { | |||||
| 283 | mutateActionBinding(snapshot.UI.Workspace.TabMode.Actions, "next-tab", "mutated-tab") | 283 | mutateActionBinding(snapshot.UI.Workspace.TabMode.Actions, "next-tab", "mutated-tab") | ||
| 284 | snapshot.UI.Workspace.TabMode.Actions["added-tab"] = entity.ActionBinding{Keys: []string{"added"}} | 284 | snapshot.UI.Workspace.TabMode.Actions["added-tab"] = entity.ActionBinding{Keys: []string{"added"}} | ||
| 285 | 285 | ||||
| 286 | - | mutateActionBinding(snapshot.UI.Workspace.PageMode.Actions, "page-scroll-down", "mutated-page") | 286 | + | mutateActionBinding(snapshot.UI.Workspace.VimMode.Actions, "vim-scroll-down", "mutated-page") |
| 287 | - | snapshot.UI.Workspace.PageMode.Actions["added-page"] = entity.ActionBinding{Keys: []string{"added"}} | 287 | + | snapshot.UI.Workspace.VimMode.Actions["added-page"] = entity.ActionBinding{Keys: []string{"added"}} |
| 288 | 288 | ||||
| 289 | mutateActionBinding(snapshot.UI.Workspace.ResizeMode.Actions, "grow-right", "mutated-resize") | 289 | mutateActionBinding(snapshot.UI.Workspace.ResizeMode.Actions, "grow-right", "mutated-resize") | ||
| 290 | snapshot.UI.Workspace.ResizeMode.Actions["added-resize"] = entity.ActionBinding{Keys: []string{"added"}} | 290 | snapshot.UI.Workspace.ResizeMode.Actions["added-resize"] = entity.ActionBinding{Keys: []string{"added"}} | ||
| @@ -316,8 +316,8 @@ func assertNestedRuntimeConfigSnapshotUnchanged(t *testing.T, snapshot entity.Ru | |||||
| 316 | assertActionBinding(t, snapshot.UI.Workspace.TabMode.Actions, "next-tab", "l") | 316 | assertActionBinding(t, snapshot.UI.Workspace.TabMode.Actions, "next-tab", "l") | ||
| 317 | assertMapEntryAbsent(t, snapshot.UI.Workspace.TabMode.Actions, "added-tab") | 317 | assertMapEntryAbsent(t, snapshot.UI.Workspace.TabMode.Actions, "added-tab") | ||
| 318 | 318 | ||||
| 319 | - | assertActionBinding(t, snapshot.UI.Workspace.PageMode.Actions, "page-scroll-down", "j") | 319 | + | assertActionBinding(t, snapshot.UI.Workspace.VimMode.Actions, "vim-scroll-down", "j") |
| 320 | - | assertMapEntryAbsent(t, snapshot.UI.Workspace.PageMode.Actions, "added-page") | 320 | + | assertMapEntryAbsent(t, snapshot.UI.Workspace.VimMode.Actions, "added-page") |
| 321 | 321 | ||||
| 322 | assertActionBinding(t, snapshot.UI.Workspace.ResizeMode.Actions, "grow-right", "right") | 322 | assertActionBinding(t, snapshot.UI.Workspace.ResizeMode.Actions, "grow-right", "right") | ||
| 323 | assertMapEntryAbsent(t, snapshot.UI.Workspace.ResizeMode.Actions, "added-resize") | 323 | assertMapEntryAbsent(t, snapshot.UI.Workspace.ResizeMode.Actions, "added-resize") | ||
| @@ -366,8 +366,8 @@ func assertNestedSourceConfigUnchanged(t *testing.T, cfg *config.Config) { | |||||
| 366 | assertActionBinding(t, cfg.Workspace.TabMode.Actions, "next-tab", "l") | 366 | assertActionBinding(t, cfg.Workspace.TabMode.Actions, "next-tab", "l") | ||
| 367 | assertMapEntryAbsent(t, cfg.Workspace.TabMode.Actions, "added-tab") | 367 | assertMapEntryAbsent(t, cfg.Workspace.TabMode.Actions, "added-tab") | ||
| 368 | 368 | ||||
| 369 | - | assertActionBinding(t, cfg.Workspace.PageMode.Actions, "page-scroll-down", "j") | 369 | + | assertActionBinding(t, cfg.Workspace.VimMode.Actions, "vim-scroll-down", "j") |
| 370 | - | assertMapEntryAbsent(t, cfg.Workspace.PageMode.Actions, "added-page") | 370 | + | assertMapEntryAbsent(t, cfg.Workspace.VimMode.Actions, "added-page") |
| 371 | 371 | ||||
| 372 | assertActionBinding(t, cfg.Workspace.ResizeMode.Actions, "grow-right", "right") | 372 | assertActionBinding(t, cfg.Workspace.ResizeMode.Actions, "grow-right", "right") | ||
| 373 | assertMapEntryAbsent(t, cfg.Workspace.ResizeMode.Actions, "added-resize") | 373 | assertMapEntryAbsent(t, cfg.Workspace.ResizeMode.Actions, "added-resize") | ||
M internal/domain/entity/config_types.go +4 -4
| @@ -78,15 +78,15 @@ func (t *TabModeConfig) GetKeyBindings() map[string]string { | |||||
| 78 | return keyBindingsFromActions(t.Actions) | 78 | return keyBindingsFromActions(t.Actions) | ||
| 79 | } | 79 | } | ||
| 80 | 80 | ||||
| 81 | - | // PageModeConfig holds page scroll mode shortcut configuration. | 81 | + | // VimModeConfig holds vim scroll mode shortcut configuration. |
| 82 | - | type PageModeConfig struct { | 82 | + | type VimModeConfig struct { |
| 83 | ActivationShortcut string `mapstructure:"activation_shortcut" yaml:"activation_shortcut" toml:"activation_shortcut" json:"activation_shortcut"` //nolint:lll // struct tags must stay on one line | 83 | ActivationShortcut string `mapstructure:"activation_shortcut" yaml:"activation_shortcut" toml:"activation_shortcut" json:"activation_shortcut"` //nolint:lll // struct tags must stay on one line | ||
| 84 | TimeoutMilliseconds int `mapstructure:"timeout_ms" yaml:"timeout_ms" toml:"timeout_ms" json:"timeout_ms"` | 84 | TimeoutMilliseconds int `mapstructure:"timeout_ms" yaml:"timeout_ms" toml:"timeout_ms" json:"timeout_ms"` | ||
| 85 | Actions map[string]ActionBinding `mapstructure:"actions" yaml:"actions" toml:"actions" json:"actions"` | 85 | Actions map[string]ActionBinding `mapstructure:"actions" yaml:"actions" toml:"actions" json:"actions"` | ||
| 86 | } | 86 | } | ||
| 87 | 87 | ||||
| 88 | // GetKeyBindings returns a map from key string to action name. | 88 | // GetKeyBindings returns a map from key string to action name. | ||
| 89 | - | func (p *PageModeConfig) GetKeyBindings() map[string]string { | 89 | + | func (p *VimModeConfig) GetKeyBindings() map[string]string { |
| 90 | return keyBindingsFromActions(p.Actions) | 90 | return keyBindingsFromActions(p.Actions) | ||
| 91 | } | 91 | } | ||
| 92 | 92 | ||||
| @@ -240,7 +240,7 @@ type WorkspaceConfig struct { | |||||
| 240 | NewPaneURL string `mapstructure:"new_pane_url" yaml:"new_pane_url" toml:"new_pane_url" json:"new_pane_url"` | 240 | NewPaneURL string `mapstructure:"new_pane_url" yaml:"new_pane_url" toml:"new_pane_url" json:"new_pane_url"` | ||
| 241 | PaneMode PaneModeConfig `mapstructure:"pane_mode" yaml:"pane_mode" toml:"pane_mode" json:"pane_mode"` | 241 | PaneMode PaneModeConfig `mapstructure:"pane_mode" yaml:"pane_mode" toml:"pane_mode" json:"pane_mode"` | ||
| 242 | TabMode TabModeConfig `mapstructure:"tab_mode" yaml:"tab_mode" toml:"tab_mode" json:"tab_mode"` | 242 | TabMode TabModeConfig `mapstructure:"tab_mode" yaml:"tab_mode" toml:"tab_mode" json:"tab_mode"` | ||
| 243 | - | PageMode PageModeConfig `mapstructure:"page_mode" yaml:"page_mode" toml:"page_mode" json:"page_mode"` | 243 | + | VimMode VimModeConfig `mapstructure:"vim_mode" yaml:"vim_mode" toml:"vim_mode" json:"vim_mode"` |
| 244 | ResizeMode ResizeModeConfig `mapstructure:"resize_mode" yaml:"resize_mode" toml:"resize_mode" json:"resize_mode"` | 244 | ResizeMode ResizeModeConfig `mapstructure:"resize_mode" yaml:"resize_mode" toml:"resize_mode" json:"resize_mode"` | ||
| 245 | Shortcuts GlobalShortcutsConfig `mapstructure:"shortcuts" yaml:"shortcuts" toml:"shortcuts" json:"shortcuts"` | 245 | Shortcuts GlobalShortcutsConfig `mapstructure:"shortcuts" yaml:"shortcuts" toml:"shortcuts" json:"shortcuts"` | ||
| 246 | FloatingPane FloatingPaneConfig `mapstructure:"floating_pane" yaml:"floating_pane" toml:"floating_pane" json:"floating_pane"` | 246 | FloatingPane FloatingPaneConfig `mapstructure:"floating_pane" yaml:"floating_pane" toml:"floating_pane" json:"floating_pane"` | ||
M internal/infrastructure/cef/webview.go +3 -3
| @@ -2396,7 +2396,7 @@ func (wv *WebView) takePendingCreate() *pendingBrowserCreate { | |||||
| 2396 | } | 2396 | } | ||
| 2397 | 2397 | ||||
| 2398 | // pageScrollWheelEvent builds the CEF mouse wheel event used for keyboard-driven | 2398 | // pageScrollWheelEvent builds the CEF mouse wheel event used for keyboard-driven | ||
| 2399 | - | // Page Mode scrolling. It targets the center of the current OSR view so the | 2399 | + | // Vim Mode scrolling. It targets the center of the current OSR view so the |
| 2400 | // scroll goes through Chromium's compositor/input pipeline instead of a series | 2400 | // scroll goes through Chromium's compositor/input pipeline instead of a series | ||
| 2401 | // of synthetic key taps. | 2401 | // of synthetic key taps. | ||
| 2402 | func (wv *WebView) pageScrollWheelEvent() purecef.MouseEvent { | 2402 | func (wv *WebView) pageScrollWheelEvent() purecef.MouseEvent { | ||
| @@ -2441,7 +2441,7 @@ func (wv *WebView) ScrollPage(ctx context.Context, request port.PageScrollReques | |||||
| 2441 | return nil | 2441 | return nil | ||
| 2442 | } | 2442 | } | ||
| 2443 | 2443 | ||||
| 2444 | - | wv.RunJavaScript(ctx, webutil.BuildScrollByJS(request.FallbackDX, request.FallbackDY)) | 2444 | + | wv.RunJavaScript(ctx, webutil.BuildPageScrollByJS(request.FallbackDX, request.FallbackDY)) |
| 2445 | return nil | 2445 | return nil | ||
| 2446 | } | 2446 | } | ||
| 2447 | 2447 | ||||
| @@ -2510,7 +2510,7 @@ func (wv *WebView) flushPageScroll() { | |||||
| 2510 | } | 2510 | } | ||
| 2511 | q.mu.Unlock() | 2511 | q.mu.Unlock() | ||
| 2512 | if dx, dy := tapDX+heldDX, tapDY+heldDY; (dx != 0 || dy != 0) && !wv.destroyed.Load() { | 2512 | if dx, dy := tapDX+heldDX, tapDY+heldDY; (dx != 0 || dy != 0) && !wv.destroyed.Load() { | ||
| 2513 | - | wv.executeJavaScriptNow(webutil.BuildScrollByJS(dx, dy)) | 2513 | + | wv.executeJavaScriptNow(webutil.BuildPageScrollByJS(dx, dy)) |
| 2514 | } | 2514 | } | ||
| 2515 | q.commitMu.Unlock() | 2515 | q.commitMu.Unlock() | ||
| 2516 | 2516 | ||||
M internal/infrastructure/cef/webview_scroll_test.go +3 -3
| @@ -42,7 +42,7 @@ func TestPageScrollWheelDeltas_UseFallbackDeltasWithCEFSign(t *testing.T) { | |||||
| 42 | } | 42 | } | ||
| 43 | 43 | ||||
| 44 | // scrollRecorderHost wraps purecef.BrowserHost and records mouse wheel events, | 44 | // scrollRecorderHost wraps purecef.BrowserHost and records mouse wheel events, | ||
| 45 | - | // allowing tests to verify that Page Mode uses Chromium's native scroll path. | 45 | + | // allowing tests to verify that Vim Mode uses Chromium's native scroll path. |
| 46 | type scrollRecorderHost struct { | 46 | type scrollRecorderHost struct { | ||
| 47 | purecef.BrowserHost // embedded nil — only SendMouseWheelEvent is called in these tests | 47 | purecef.BrowserHost // embedded nil — only SendMouseWheelEvent is called in these tests | ||
| 48 | 48 | ||||
| @@ -174,7 +174,7 @@ func TestScrollPage_ReadyBrowser_HorizontalDirectionsUseDOMSigns(t *testing.T) { | |||||
| 174 | func TestScrollPage_ReadyBrowser_NestedBoundaryHandsOffToDocument(t *testing.T) { | 174 | func TestScrollPage_ReadyBrowser_NestedBoundaryHandsOffToDocument(t *testing.T) { | ||
| 175 | node, err := exec.LookPath("node") | 175 | node, err := exec.LookPath("node") | ||
| 176 | if err != nil { | 176 | if err != nil { | ||
| 177 | - | t.Skip("node is required to execute the generated page-scroll helper") | 177 | + | t.Skip("node is required to execute the generated vim-scroll helper") |
| 178 | } | 178 | } | ||
| 179 | 179 | ||||
| 180 | host := &scrollRecorderHost{} | 180 | host := &scrollRecorderHost{} | ||
| @@ -211,7 +211,7 @@ process.stdout.write(inner.scrollTop+','+root.scrollTop); | |||||
| 211 | `, pageScrollScript) | 211 | `, pageScrollScript) | ||
| 212 | output, err := exec.Command(node, "-e", harness).CombinedOutput() | 212 | output, err := exec.Command(node, "-e", harness).CombinedOutput() | ||
| 213 | if err != nil { | 213 | if err != nil { | ||
| 214 | - | t.Fatalf("execute generated page-scroll helper: %v\n%s", err, output) | 214 | + | t.Fatalf("execute generated vim-scroll helper: %v\n%s", err, output) |
| 215 | } | 215 | } | ||
| 216 | if got := strings.TrimSpace(string(output)); got != "200,80" { | 216 | if got := strings.TrimSpace(string(output)); got != "200,80" { | ||
| 217 | t.Fatalf("nested/document scroll offsets = %s, want 200,80", got) | 217 | t.Fatalf("nested/document scroll offsets = %s, want 200,80", got) | ||
M internal/infrastructure/config/defaults.go +9 -9
| @@ -304,18 +304,18 @@ func DefaultConfig() *Config { | |||||
| 304 | "cancel": {Keys: []string{"escape"}, Desc: "Cancel/exit mode"}, | 304 | "cancel": {Keys: []string{"escape"}, Desc: "Cancel/exit mode"}, | ||
| 305 | }, | 305 | }, | ||
| 306 | }, | 306 | }, | ||
| 307 | - | PageMode: PageModeConfig{ | 307 | + | VimMode: VimModeConfig{ |
| 308 | ActivationShortcut: defaultPageActivationShortcut, | 308 | ActivationShortcut: defaultPageActivationShortcut, | ||
| 309 | TimeoutMilliseconds: defaultPageTimeoutMilliseconds, | 309 | TimeoutMilliseconds: defaultPageTimeoutMilliseconds, | ||
| 310 | Actions: map[string]ActionBinding{ | 310 | Actions: map[string]ActionBinding{ | ||
| 311 | - | "page-scroll-left": {Keys: []string{"h"}, Desc: "Scroll page left"}, | 311 | + | "vim-scroll-left": {Keys: []string{"h"}, Desc: "Scroll page left"}, |
| 312 | - | "page-scroll-down": {Keys: []string{"j"}, Desc: "Scroll page down"}, | 312 | + | "vim-scroll-down": {Keys: []string{"j"}, Desc: "Scroll page down"}, |
| 313 | - | "page-scroll-up": {Keys: []string{"k"}, Desc: "Scroll page up"}, | 313 | + | "vim-scroll-up": {Keys: []string{"k"}, Desc: "Scroll page up"}, |
| 314 | - | "page-scroll-right": {Keys: []string{"l"}, Desc: "Scroll page right"}, | 314 | + | "vim-scroll-right": {Keys: []string{"l"}, Desc: "Scroll page right"}, |
| 315 | - | "page-scroll-down-fast": {Keys: []string{"shift+j"}, Desc: "Scroll page down fast"}, | 315 | + | "vim-scroll-down-fast": {Keys: []string{"shift+j"}, Desc: "Scroll page down fast"}, |
| 316 | - | "page-scroll-up-fast": {Keys: []string{"shift+k"}, Desc: "Scroll page up fast"}, | 316 | + | "vim-scroll-up-fast": {Keys: []string{"shift+k"}, Desc: "Scroll page up fast"}, |
| 317 | - | "confirm": {Keys: []string{"enter"}, Desc: "Confirm action"}, | 317 | + | "confirm": {Keys: []string{"enter"}, Desc: "Confirm action"}, |
| 318 | - | "cancel": {Keys: []string{"escape"}, Desc: "Cancel/exit mode"}, | 318 | + | "cancel": {Keys: []string{"escape"}, Desc: "Cancel/exit mode"}, |
| 319 | }, | 319 | }, | ||
| 320 | }, | 320 | }, | ||
| 321 | ResizeMode: ResizeModeConfig{ | 321 | ResizeMode: ResizeModeConfig{ | ||
M internal/infrastructure/config/keybindings_gateway.go +15 -15
| @@ -32,7 +32,7 @@ func (g *KeybindingsGateway) GetKeybindings(ctx context.Context) (port.Keybindin | |||||
| 32 | g.buildGlobalGroup(cfg, defaults), | 32 | g.buildGlobalGroup(cfg, defaults), | ||
| 33 | g.buildPaneModeGroup(cfg, defaults), | 33 | g.buildPaneModeGroup(cfg, defaults), | ||
| 34 | g.buildTabModeGroup(cfg, defaults), | 34 | g.buildTabModeGroup(cfg, defaults), | ||
| 35 | - | g.buildPageModeGroup(cfg, defaults), | 35 | + | g.buildVimModeGroup(cfg, defaults), |
| 36 | g.buildResizeModeGroup(cfg, defaults), | 36 | g.buildResizeModeGroup(cfg, defaults), | ||
| 37 | g.buildSessionModeGroup(cfg, defaults), | 37 | g.buildSessionModeGroup(cfg, defaults), | ||
| 38 | } | 38 | } | ||
| @@ -49,7 +49,7 @@ func (g *KeybindingsGateway) GetDefaultKeybindings(ctx context.Context) (port.Ke | |||||
| 49 | g.buildGlobalGroup(defaults, defaults), | 49 | g.buildGlobalGroup(defaults, defaults), | ||
| 50 | g.buildPaneModeGroup(defaults, defaults), | 50 | g.buildPaneModeGroup(defaults, defaults), | ||
| 51 | g.buildTabModeGroup(defaults, defaults), | 51 | g.buildTabModeGroup(defaults, defaults), | ||
| 52 | - | g.buildPageModeGroup(defaults, defaults), | 52 | + | g.buildVimModeGroup(defaults, defaults), |
| 53 | g.buildResizeModeGroup(defaults, defaults), | 53 | g.buildResizeModeGroup(defaults, defaults), | ||
| 54 | g.buildSessionModeGroup(defaults, defaults), | 54 | g.buildSessionModeGroup(defaults, defaults), | ||
| 55 | } | 55 | } | ||
| @@ -110,7 +110,7 @@ func (g *KeybindingsGateway) ResetAllKeybindings(ctx context.Context) error { | |||||
| 110 | 110 | ||||
| 111 | cfg.Workspace.PaneMode.Actions = defaults.Workspace.PaneMode.Actions | 111 | cfg.Workspace.PaneMode.Actions = defaults.Workspace.PaneMode.Actions | ||
| 112 | cfg.Workspace.TabMode.Actions = defaults.Workspace.TabMode.Actions | 112 | cfg.Workspace.TabMode.Actions = defaults.Workspace.TabMode.Actions | ||
| 113 | - | cfg.Workspace.PageMode.Actions = defaults.Workspace.PageMode.Actions | 113 | + | cfg.Workspace.VimMode.Actions = defaults.Workspace.VimMode.Actions |
| 114 | cfg.Workspace.ResizeMode.Actions = defaults.Workspace.ResizeMode.Actions | 114 | cfg.Workspace.ResizeMode.Actions = defaults.Workspace.ResizeMode.Actions | ||
| 115 | cfg.Workspace.Shortcuts = defaults.Workspace.Shortcuts | 115 | cfg.Workspace.Shortcuts = defaults.Workspace.Shortcuts | ||
| 116 | cfg.Session.SessionMode.Actions = defaults.Session.SessionMode.Actions | 116 | cfg.Session.SessionMode.Actions = defaults.Session.SessionMode.Actions | ||
| @@ -147,13 +147,13 @@ func (g *KeybindingsGateway) buildTabModeGroup(cfg, defaults *Config) port.Keybi | |||||
| 147 | } | 147 | } | ||
| 148 | } | 148 | } | ||
| 149 | 149 | ||||
| 150 | - | // buildPageModeGroup builds the page mode group. | 150 | + | // buildVimModeGroup builds the vim mode group. |
| 151 | - | func (g *KeybindingsGateway) buildPageModeGroup(cfg, defaults *Config) port.KeybindingGroup { | 151 | + | func (g *KeybindingsGateway) buildVimModeGroup(cfg, defaults *Config) port.KeybindingGroup { |
| 152 | return port.KeybindingGroup{ | 152 | return port.KeybindingGroup{ | ||
| 153 | - | Mode: "page", | 153 | + | Mode: "vim", |
| 154 | - | DisplayName: "Page Mode", | 154 | + | DisplayName: "Vim Mode", |
| 155 | - | Bindings: g.buildModeBindings(cfg.Workspace.PageMode.Actions, defaults.Workspace.PageMode.Actions), | 155 | + | Bindings: g.buildModeBindings(cfg.Workspace.VimMode.Actions, defaults.Workspace.VimMode.Actions), |
| 156 | - | Activation: cfg.Workspace.PageMode.ActivationShortcut, | 156 | + | Activation: cfg.Workspace.VimMode.ActivationShortcut, |
| 157 | } | 157 | } | ||
| 158 | } | 158 | } | ||
| 159 | 159 | ||||
| @@ -221,10 +221,10 @@ func (*KeybindingsGateway) updateKeybinding(cfg *Config, req port.SetKeybindingR | |||||
| 221 | existing.Keys = req.Keys | 221 | existing.Keys = req.Keys | ||
| 222 | cfg.Workspace.TabMode.Actions[req.Action] = existing | 222 | cfg.Workspace.TabMode.Actions[req.Action] = existing | ||
| 223 | } | 223 | } | ||
| 224 | - | case "page": | 224 | + | case "vim": |
| 225 | - | if existing, ok := cfg.Workspace.PageMode.Actions[req.Action]; ok { | 225 | + | if existing, ok := cfg.Workspace.VimMode.Actions[req.Action]; ok { |
| 226 | existing.Keys = req.Keys | 226 | existing.Keys = req.Keys | ||
| 227 | - | cfg.Workspace.PageMode.Actions[req.Action] = existing | 227 | + | cfg.Workspace.VimMode.Actions[req.Action] = existing |
| 228 | } | 228 | } | ||
| 229 | case "resize": | 229 | case "resize": | ||
| 230 | if existing, ok := cfg.Workspace.ResizeMode.Actions[req.Action]; ok { | 230 | if existing, ok := cfg.Workspace.ResizeMode.Actions[req.Action]; ok { | ||
| @@ -254,8 +254,8 @@ func (*KeybindingsGateway) getDefaultKeys(defaults *Config, mode, action string) | |||||
| 254 | if binding, ok := defaults.Workspace.TabMode.Actions[action]; ok { | 254 | if binding, ok := defaults.Workspace.TabMode.Actions[action]; ok { | ||
| 255 | return binding.Keys | 255 | return binding.Keys | ||
| 256 | } | 256 | } | ||
| 257 | - | case "page": | 257 | + | case "vim": |
| 258 | - | if binding, ok := defaults.Workspace.PageMode.Actions[action]; ok { | 258 | + | if binding, ok := defaults.Workspace.VimMode.Actions[action]; ok { |
| 259 | return binding.Keys | 259 | return binding.Keys | ||
| 260 | } | 260 | } | ||
| 261 | case "resize": | 261 | case "resize": | ||
| @@ -291,7 +291,7 @@ func (*KeybindingsGateway) checkConflicts(cfg *Config, targetMode, targetAction | |||||
| 291 | addBindings(modeGlobal, cfg.Workspace.Shortcuts.Actions) | 291 | addBindings(modeGlobal, cfg.Workspace.Shortcuts.Actions) | ||
| 292 | addBindings("pane", cfg.Workspace.PaneMode.Actions) | 292 | addBindings("pane", cfg.Workspace.PaneMode.Actions) | ||
| 293 | addBindings("tab", cfg.Workspace.TabMode.Actions) | 293 | addBindings("tab", cfg.Workspace.TabMode.Actions) | ||
| 294 | - | addBindings("page", cfg.Workspace.PageMode.Actions) | 294 | + | addBindings("vim", cfg.Workspace.VimMode.Actions) |
| 295 | addBindings("resize", cfg.Workspace.ResizeMode.Actions) | 295 | addBindings("resize", cfg.Workspace.ResizeMode.Actions) | ||
| 296 | addBindings("session", cfg.Session.SessionMode.Actions) | 296 | addBindings("session", cfg.Session.SessionMode.Actions) | ||
| 297 | 297 | ||||
M internal/infrastructure/config/keybindings_gateway_test.go +47 -47
| @@ -37,83 +37,83 @@ func inMemoryManager() *Manager { | |||||
| 37 | } | 37 | } | ||
| 38 | } | 38 | } | ||
| 39 | 39 | ||||
| 40 | - | func TestKeybindingsGateway_PageModeIncludedInGetKeybindings(t *testing.T) { | 40 | + | func TestKeybindingsGateway_VimModeIncludedInGetKeybindings(t *testing.T) { |
| 41 | gw := NewKeybindingsGateway(inMemoryManager()) | 41 | gw := NewKeybindingsGateway(inMemoryManager()) | ||
| 42 | cfg, err := gw.GetKeybindings(context.Background()) | 42 | cfg, err := gw.GetKeybindings(context.Background()) | ||
| 43 | require.NoError(t, err) | 43 | require.NoError(t, err) | ||
| 44 | 44 | ||||
| 45 | var pageGroup *port.KeybindingGroup | 45 | var pageGroup *port.KeybindingGroup | ||
| 46 | for i, g := range cfg.Groups { | 46 | for i, g := range cfg.Groups { | ||
| 47 | - | if g.Mode == "page" { | 47 | + | if g.Mode == "vim" { |
| 48 | pageGroup = &cfg.Groups[i] | 48 | pageGroup = &cfg.Groups[i] | ||
| 49 | break | 49 | break | ||
| 50 | } | 50 | } | ||
| 51 | } | 51 | } | ||
| 52 | - | require.NotNil(t, pageGroup, "page mode group must be present in GetKeybindings output") | 52 | + | require.NotNil(t, pageGroup, "vim mode group must be present in GetKeybindings output") |
| 53 | - | assert.Equal(t, "Page Mode", pageGroup.DisplayName) | 53 | + | assert.Equal(t, "Vim Mode", pageGroup.DisplayName) |
| 54 | assert.Equal(t, "ctrl+y", pageGroup.Activation) | 54 | assert.Equal(t, "ctrl+y", pageGroup.Activation) | ||
| 55 | 55 | ||||
| 56 | // Verify all expected actions are present. | 56 | // Verify all expected actions are present. | ||
| 57 | expectedActions := map[string]struct{}{ | 57 | expectedActions := map[string]struct{}{ | ||
| 58 | - | "page-scroll-left": {}, | 58 | + | "vim-scroll-left": {}, |
| 59 | - | "page-scroll-down": {}, | 59 | + | "vim-scroll-down": {}, |
| 60 | - | "page-scroll-up": {}, | 60 | + | "vim-scroll-up": {}, |
| 61 | - | "page-scroll-right": {}, | 61 | + | "vim-scroll-right": {}, |
| 62 | - | "page-scroll-down-fast": {}, | 62 | + | "vim-scroll-down-fast": {}, |
| 63 | - | "page-scroll-up-fast": {}, | 63 | + | "vim-scroll-up-fast": {}, |
| 64 | - | "confirm": {}, | 64 | + | "confirm": {}, |
| 65 | - | "cancel": {}, | 65 | + | "cancel": {}, |
| 66 | } | 66 | } | ||
| 67 | 67 | ||||
| 68 | for _, entry := range pageGroup.Bindings { | 68 | for _, entry := range pageGroup.Bindings { | ||
| 69 | delete(expectedActions, entry.Action) | 69 | delete(expectedActions, entry.Action) | ||
| 70 | assert.NotEmpty(t, entry.Keys, "action %q has empty keys", entry.Action) | 70 | assert.NotEmpty(t, entry.Keys, "action %q has empty keys", entry.Action) | ||
| 71 | - | assert.False(t, entry.IsCustom, "default page mode config should not have IsCustom=true") | 71 | + | assert.False(t, entry.IsCustom, "default vim mode config should not have IsCustom=true") |
| 72 | } | 72 | } | ||
| 73 | - | assert.Empty(t, expectedActions, "page mode group missing actions: %v", expectedActions) | 73 | + | assert.Empty(t, expectedActions, "vim mode group missing actions: %v", expectedActions) |
| 74 | 74 | ||||
| 75 | // Must be placed after global, pane, tab groups (consistent ordering). | 75 | // Must be placed after global, pane, tab groups (consistent ordering). | ||
| 76 | groups := cfg.Groups | 76 | groups := cfg.Groups | ||
| 77 | foundIdx := -1 | 77 | foundIdx := -1 | ||
| 78 | for i, g := range groups { | 78 | for i, g := range groups { | ||
| 79 | - | if g.Mode == "page" { | 79 | + | if g.Mode == "vim" { |
| 80 | foundIdx = i | 80 | foundIdx = i | ||
| 81 | break | 81 | break | ||
| 82 | } | 82 | } | ||
| 83 | } | 83 | } | ||
| 84 | - | assert.Greater(t, foundIdx, 2, "page mode group should be after pane/tab groups") | 84 | + | assert.Greater(t, foundIdx, 2, "vim mode group should be after pane/tab groups") |
| 85 | } | 85 | } | ||
| 86 | 86 | ||||
| 87 | - | func TestKeybindingsGateway_PageModeIncludedInGetDefaultKeybindings(t *testing.T) { | 87 | + | func TestKeybindingsGateway_VimModeIncludedInGetDefaultKeybindings(t *testing.T) { |
| 88 | gw := NewKeybindingsGateway(inMemoryManager()) | 88 | gw := NewKeybindingsGateway(inMemoryManager()) | ||
| 89 | cfg, err := gw.GetDefaultKeybindings(context.Background()) | 89 | cfg, err := gw.GetDefaultKeybindings(context.Background()) | ||
| 90 | require.NoError(t, err) | 90 | require.NoError(t, err) | ||
| 91 | 91 | ||||
| 92 | var pageGroup *port.KeybindingGroup | 92 | var pageGroup *port.KeybindingGroup | ||
| 93 | for i, g := range cfg.Groups { | 93 | for i, g := range cfg.Groups { | ||
| 94 | - | if g.Mode == "page" { | 94 | + | if g.Mode == "vim" { |
| 95 | pageGroup = &cfg.Groups[i] | 95 | pageGroup = &cfg.Groups[i] | ||
| 96 | break | 96 | break | ||
| 97 | } | 97 | } | ||
| 98 | } | 98 | } | ||
| 99 | - | require.NotNil(t, pageGroup, "page mode group must be present in GetDefaultKeybindings output") | 99 | + | require.NotNil(t, pageGroup, "vim mode group must be present in GetDefaultKeybindings output") |
| 100 | - | assert.Equal(t, "Page Mode", pageGroup.DisplayName) | 100 | + | assert.Equal(t, "Vim Mode", pageGroup.DisplayName) |
| 101 | assert.Equal(t, "ctrl+y", pageGroup.Activation) | 101 | assert.Equal(t, "ctrl+y", pageGroup.Activation) | ||
| 102 | 102 | ||||
| 103 | // Verify default keys match actual keys. | 103 | // Verify default keys match actual keys. | ||
| 104 | for _, entry := range pageGroup.Bindings { | 104 | for _, entry := range pageGroup.Bindings { | ||
| 105 | assert.Equal(t, entry.DefaultKeys, entry.Keys, | 105 | assert.Equal(t, entry.DefaultKeys, entry.Keys, | ||
| 106 | - | "default page mode action %q should have keys == default_keys", entry.Action) | 106 | + | "default vim mode action %q should have keys == default_keys", entry.Action) |
| 107 | } | 107 | } | ||
| 108 | } | 108 | } | ||
| 109 | 109 | ||||
| 110 | - | func TestKeybindingsGateway_PageModeSetKeybindingUpdatesFile(t *testing.T) { | 110 | + | func TestKeybindingsGateway_VimModeSetKeybindingUpdatesFile(t *testing.T) { |
| 111 | mgr := newTestKeybindingsManager(t) | 111 | mgr := newTestKeybindingsManager(t) | ||
| 112 | gw := NewKeybindingsGateway(mgr) | 112 | gw := NewKeybindingsGateway(mgr) | ||
| 113 | 113 | ||||
| 114 | err := gw.SetKeybinding(context.Background(), port.SetKeybindingRequest{ | 114 | err := gw.SetKeybinding(context.Background(), port.SetKeybindingRequest{ | ||
| 115 | - | Mode: "page", | 115 | + | Mode: "vim", |
| 116 | - | Action: "page-scroll-left", | 116 | + | Action: "vim-scroll-left", |
| 117 | Keys: []string{"a"}, | 117 | Keys: []string{"a"}, | ||
| 118 | }) | 118 | }) | ||
| 119 | require.NoError(t, err) | 119 | require.NoError(t, err) | ||
| @@ -127,21 +127,21 @@ func TestKeybindingsGateway_PageModeSetKeybindingUpdatesFile(t *testing.T) { | |||||
| 127 | assert.NotContains(t, content, `keys = ['h']`) | 127 | assert.NotContains(t, content, `keys = ['h']`) | ||
| 128 | } | 128 | } | ||
| 129 | 129 | ||||
| 130 | - | func TestKeybindingsGateway_PageModeResetKeybinding(t *testing.T) { | 130 | + | func TestKeybindingsGateway_VimModeResetKeybinding(t *testing.T) { |
| 131 | mgr := newTestKeybindingsManager(t) | 131 | mgr := newTestKeybindingsManager(t) | ||
| 132 | gw := NewKeybindingsGateway(mgr) | 132 | gw := NewKeybindingsGateway(mgr) | ||
| 133 | 133 | ||||
| 134 | // Set a keybinding. | 134 | // Set a keybinding. | ||
| 135 | require.NoError(t, gw.SetKeybinding(context.Background(), port.SetKeybindingRequest{ | 135 | require.NoError(t, gw.SetKeybinding(context.Background(), port.SetKeybindingRequest{ | ||
| 136 | - | Mode: "page", | 136 | + | Mode: "vim", |
| 137 | - | Action: "page-scroll-left", | 137 | + | Action: "vim-scroll-left", |
| 138 | Keys: []string{"a"}, | 138 | Keys: []string{"a"}, | ||
| 139 | })) | 139 | })) | ||
| 140 | 140 | ||||
| 141 | // Reset it. | 141 | // Reset it. | ||
| 142 | err := gw.ResetKeybinding(context.Background(), port.ResetKeybindingRequest{ | 142 | err := gw.ResetKeybinding(context.Background(), port.ResetKeybindingRequest{ | ||
| 143 | - | Mode: "page", | 143 | + | Mode: "vim", |
| 144 | - | Action: "page-scroll-left", | 144 | + | Action: "vim-scroll-left", |
| 145 | }) | 145 | }) | ||
| 146 | require.NoError(t, err) | 146 | require.NoError(t, err) | ||
| 147 | 147 | ||||
| @@ -154,21 +154,21 @@ func TestKeybindingsGateway_PageModeResetKeybinding(t *testing.T) { | |||||
| 154 | assert.NotContains(t, content, `keys = ['a']`) | 154 | assert.NotContains(t, content, `keys = ['a']`) | ||
| 155 | } | 155 | } | ||
| 156 | 156 | ||||
| 157 | - | func TestKeybindingsGateway_PageModeIsPartOfResetAll(t *testing.T) { | 157 | + | func TestKeybindingsGateway_VimModeIsPartOfResetAll(t *testing.T) { |
| 158 | mgr := newTestKeybindingsManager(t) | 158 | mgr := newTestKeybindingsManager(t) | ||
| 159 | gw := NewKeybindingsGateway(mgr) | 159 | gw := NewKeybindingsGateway(mgr) | ||
| 160 | 160 | ||||
| 161 | - | // Change a page mode binding. | 161 | + | // Change a vim mode binding. |
| 162 | require.NoError(t, gw.SetKeybinding(context.Background(), port.SetKeybindingRequest{ | 162 | require.NoError(t, gw.SetKeybinding(context.Background(), port.SetKeybindingRequest{ | ||
| 163 | - | Mode: "page", | 163 | + | Mode: "vim", |
| 164 | - | Action: "page-scroll-left", | 164 | + | Action: "vim-scroll-left", |
| 165 | Keys: []string{"a"}, | 165 | Keys: []string{"a"}, | ||
| 166 | })) | 166 | })) | ||
| 167 | 167 | ||||
| 168 | // Change another action. | 168 | // Change another action. | ||
| 169 | require.NoError(t, gw.SetKeybinding(context.Background(), port.SetKeybindingRequest{ | 169 | require.NoError(t, gw.SetKeybinding(context.Background(), port.SetKeybindingRequest{ | ||
| 170 | - | Mode: "page", | 170 | + | Mode: "vim", |
| 171 | - | Action: "page-scroll-down", | 171 | + | Action: "vim-scroll-down", |
| 172 | Keys: []string{"b"}, | 172 | Keys: []string{"b"}, | ||
| 173 | })) | 173 | })) | ||
| 174 | 174 | ||||
| @@ -181,36 +181,36 @@ func TestKeybindingsGateway_PageModeIsPartOfResetAll(t *testing.T) { | |||||
| 181 | data, err := os.ReadFile(configFile) | 181 | data, err := os.ReadFile(configFile) | ||
| 182 | require.NoError(t, err) | 182 | require.NoError(t, err) | ||
| 183 | content := string(data) | 183 | content := string(data) | ||
| 184 | - | assert.Contains(t, content, `keys = ['h']`, "page-scroll-left should be restored to default") | 184 | + | assert.Contains(t, content, `keys = ['h']`, "vim-scroll-left should be restored to default") |
| 185 | - | assert.Contains(t, content, `keys = ['j']`, "page-scroll-down should be restored to default") | 185 | + | assert.Contains(t, content, `keys = ['j']`, "vim-scroll-down should be restored to default") |
| 186 | assert.NotContains(t, content, "keys = ['a']") | 186 | assert.NotContains(t, content, "keys = ['a']") | ||
| 187 | assert.NotContains(t, content, "keys = ['b']") | 187 | assert.NotContains(t, content, "keys = ['b']") | ||
| 188 | } | 188 | } | ||
| 189 | 189 | ||||
| 190 | - | func TestKeybindingsGateway_PageModeConflictsWithinSelf(t *testing.T) { | 190 | + | func TestKeybindingsGateway_VimModeConflictsWithinSelf(t *testing.T) { |
| 191 | gw := NewKeybindingsGateway(inMemoryManager()) | 191 | gw := NewKeybindingsGateway(inMemoryManager()) | ||
| 192 | - | conflicts, err := gw.CheckConflicts(context.Background(), "page", "custom", []string{"h"}) | 192 | + | conflicts, err := gw.CheckConflicts(context.Background(), "vim", "custom", []string{"h"}) |
| 193 | require.NoError(t, err) | 193 | require.NoError(t, err) | ||
| 194 | - | require.NotEmpty(t, conflicts, "should detect conflict with page-scroll-left which uses 'h'") | 194 | + | require.NotEmpty(t, conflicts, "should detect conflict with vim-scroll-left which uses 'h'") |
| 195 | 195 | ||||
| 196 | // The conflict should reference the same mode. | 196 | // The conflict should reference the same mode. | ||
| 197 | - | assert.Equal(t, "page-scroll-left", conflicts[0].ConflictingAction) | 197 | + | assert.Equal(t, "vim-scroll-left", conflicts[0].ConflictingAction) |
| 198 | - | assert.Equal(t, "page", conflicts[0].ConflictingMode) | 198 | + | assert.Equal(t, "vim", conflicts[0].ConflictingMode) |
| 199 | } | 199 | } | ||
| 200 | 200 | ||||
| 201 | - | func TestKeybindingsGateway_PageModeConflictsWithOtherModes(t *testing.T) { | 201 | + | func TestKeybindingsGateway_VimModeConflictsWithOtherModes(t *testing.T) { |
| 202 | gw := NewKeybindingsGateway(inMemoryManager()) | 202 | gw := NewKeybindingsGateway(inMemoryManager()) | ||
| 203 | - | // "enter" is used by page mode's confirm - but also by other modes like pane_mode. | 203 | + | // "enter" is used by vim mode's confirm - but also by other modes like pane_mode. |
| 204 | - | conflicts, err := gw.CheckConflicts(context.Background(), "page", "custom", []string{"enter"}) | 204 | + | conflicts, err := gw.CheckConflicts(context.Background(), "vim", "custom", []string{"enter"}) |
| 205 | require.NoError(t, err) | 205 | require.NoError(t, err) | ||
| 206 | 206 | ||||
| 207 | // There should be conflicts since "enter" is used in other modes too. | 207 | // There should be conflicts since "enter" is used in other modes too. | ||
| 208 | assert.NotEmpty(t, conflicts, "should detect cross-mode conflict for 'enter'") | 208 | assert.NotEmpty(t, conflicts, "should detect cross-mode conflict for 'enter'") | ||
| 209 | } | 209 | } | ||
| 210 | 210 | ||||
| 211 | - | func TestKeybindingsGateway_PageModeNoConflictForUnusedKey(t *testing.T) { | 211 | + | func TestKeybindingsGateway_VimModeNoConflictForUnusedKey(t *testing.T) { |
| 212 | gw := NewKeybindingsGateway(inMemoryManager()) | 212 | gw := NewKeybindingsGateway(inMemoryManager()) | ||
| 213 | - | conflicts, err := gw.CheckConflicts(context.Background(), "page", "custom", []string{"ctrl+9"}) | 213 | + | conflicts, err := gw.CheckConflicts(context.Background(), "vim", "custom", []string{"ctrl+9"}) |
| 214 | require.NoError(t, err) | 214 | require.NoError(t, err) | ||
| 215 | assert.Empty(t, conflicts, "no conflict expected for unused key") | 215 | assert.Empty(t, conflicts, "no conflict expected for unused key") | ||
| 216 | } | 216 | } | ||
M internal/infrastructure/config/loader.go +3 -3
| @@ -706,9 +706,9 @@ func (m *Manager) setWorkspaceDefaults(defaults *Config) { | |||||
| 706 | m.viper.SetDefault("workspace.tab_mode.activation_shortcut", defaults.Workspace.TabMode.ActivationShortcut) | 706 | m.viper.SetDefault("workspace.tab_mode.activation_shortcut", defaults.Workspace.TabMode.ActivationShortcut) | ||
| 707 | m.viper.SetDefault("workspace.tab_mode.timeout_ms", defaults.Workspace.TabMode.TimeoutMilliseconds) | 707 | m.viper.SetDefault("workspace.tab_mode.timeout_ms", defaults.Workspace.TabMode.TimeoutMilliseconds) | ||
| 708 | m.viper.SetDefault("workspace.tab_mode.actions", defaults.Workspace.TabMode.Actions) | 708 | m.viper.SetDefault("workspace.tab_mode.actions", defaults.Workspace.TabMode.Actions) | ||
| 709 | - | m.viper.SetDefault("workspace.page_mode.activation_shortcut", defaults.Workspace.PageMode.ActivationShortcut) | 709 | + | m.viper.SetDefault("workspace.vim_mode.activation_shortcut", defaults.Workspace.VimMode.ActivationShortcut) |
| 710 | - | m.viper.SetDefault("workspace.page_mode.timeout_ms", defaults.Workspace.PageMode.TimeoutMilliseconds) | 710 | + | m.viper.SetDefault("workspace.vim_mode.timeout_ms", defaults.Workspace.VimMode.TimeoutMilliseconds) |
| 711 | - | m.viper.SetDefault("workspace.page_mode.actions", defaults.Workspace.PageMode.Actions) | 711 | + | m.viper.SetDefault("workspace.vim_mode.actions", defaults.Workspace.VimMode.Actions) |
| 712 | m.viper.SetDefault("workspace.resize_mode.activation_shortcut", defaults.Workspace.ResizeMode.ActivationShortcut) | 712 | m.viper.SetDefault("workspace.resize_mode.activation_shortcut", defaults.Workspace.ResizeMode.ActivationShortcut) | ||
| 713 | m.viper.SetDefault("workspace.resize_mode.timeout_ms", defaults.Workspace.ResizeMode.TimeoutMilliseconds) | 713 | m.viper.SetDefault("workspace.resize_mode.timeout_ms", defaults.Workspace.ResizeMode.TimeoutMilliseconds) | ||
| 714 | m.viper.SetDefault("workspace.resize_mode.step_percent", defaults.Workspace.ResizeMode.StepPercent) | 714 | m.viper.SetDefault("workspace.resize_mode.step_percent", defaults.Workspace.ResizeMode.StepPercent) | ||
M internal/infrastructure/config/migrate.go +91 -1
| @@ -123,6 +123,7 @@ func (m *Migrator) DetectChanges() ([]port.KeyChange, error) { | |||||
| 123 | m.detectLegacyBrowsingContextRenames(rawUserKeysWithValues), | 123 | m.detectLegacyBrowsingContextRenames(rawUserKeysWithValues), | ||
| 124 | m.detectLegacyFavoritesSidebarRename(userKeysWithValues)..., | 124 | m.detectLegacyFavoritesSidebarRename(userKeysWithValues)..., | ||
| 125 | ) | 125 | ) | ||
| 126 | + | legacyRenames = append(legacyRenames, m.detectLegacyVimModeRenames(rawUserKeysWithValues)...) | |||
| 126 | var changes []port.KeyChange | 127 | var changes []port.KeyChange | ||
| 127 | changes = append(changes, legacyRenames...) | 128 | changes = append(changes, legacyRenames...) | ||
| 128 | 129 | ||||
| @@ -302,7 +303,7 @@ func (m *Migrator) defaultActionMaps() []defaultActionMap { | |||||
| 302 | {key: "workspace.shortcuts.actions", actions: m.defaultConfig.Workspace.Shortcuts.Actions}, | 303 | {key: "workspace.shortcuts.actions", actions: m.defaultConfig.Workspace.Shortcuts.Actions}, | ||
| 303 | {key: "workspace.pane_mode.actions", actions: m.defaultConfig.Workspace.PaneMode.Actions}, | 304 | {key: "workspace.pane_mode.actions", actions: m.defaultConfig.Workspace.PaneMode.Actions}, | ||
| 304 | {key: "workspace.tab_mode.actions", actions: m.defaultConfig.Workspace.TabMode.Actions}, | 305 | {key: "workspace.tab_mode.actions", actions: m.defaultConfig.Workspace.TabMode.Actions}, | ||
| 305 | - | {key: "workspace.page_mode.actions", actions: m.defaultConfig.Workspace.PageMode.Actions}, | 306 | + | {key: "workspace.vim_mode.actions", actions: m.defaultConfig.Workspace.VimMode.Actions}, |
| 306 | {key: "workspace.resize_mode.actions", actions: m.defaultConfig.Workspace.ResizeMode.Actions}, | 307 | {key: "workspace.resize_mode.actions", actions: m.defaultConfig.Workspace.ResizeMode.Actions}, | ||
| 307 | {key: "session.session_mode.actions", actions: m.defaultConfig.Session.SessionMode.Actions}, | 308 | {key: "session.session_mode.actions", actions: m.defaultConfig.Session.SessionMode.Actions}, | ||
| 308 | } | 309 | } | ||
| @@ -420,6 +421,7 @@ func (m *Migrator) Migrate() ([]string, error) { | |||||
| 420 | transformer.TransformLegacyActions(rawConfig) | 421 | transformer.TransformLegacyActions(rawConfig) | ||
| 421 | transformer.TransformLegacyEngineConfig(rawConfig) | 422 | transformer.TransformLegacyEngineConfig(rawConfig) | ||
| 422 | transformer.TransformLegacyPopupsToBrowsingContexts(rawConfig) | 423 | transformer.TransformLegacyPopupsToBrowsingContexts(rawConfig) | ||
| 424 | + | m.migrateLegacyVimMode(rawConfig) | |||
| 423 | 425 | ||||
| 424 | // Build sets of keys to remove and renames to apply | 426 | // Build sets of keys to remove and renames to apply | ||
| 425 | keysToRemove := make(map[string]bool) | 427 | keysToRemove := make(map[string]bool) | ||
| @@ -788,6 +790,94 @@ func (m *Migrator) detectLegacyBrowsingContextRenames(rawUserKeys map[string]any | |||||
| 788 | return changes | 790 | return changes | ||
| 789 | } | 791 | } | ||
| 790 | 792 | ||||
| 793 | + | func (m *Migrator) detectLegacyVimModeRenames(rawUserKeys map[string]any) []port.KeyChange { | |||
| 794 | + | changes := make([]port.KeyChange, 0, 3) | |||
| 795 | + | for oldKey, oldValue := range rawUserKeys { | |||
| 796 | + | newKey, ok := legacyVimModeKey(oldKey) | |||
| 797 | + | if !ok { | |||
| 798 | + | continue | |||
| 799 | + | } | |||
| 800 | + | if _, hasNew := rawUserKeys[newKey]; hasNew { | |||
| 801 | + | continue | |||
| 802 | + | } | |||
| 803 | + | changes = append(changes, port.KeyChange{ | |||
| 804 | + | Type: port.KeyChangeRenamed, | |||
| 805 | + | OldKey: oldKey, | |||
| 806 | + | NewKey: newKey, | |||
| 807 | + | OldValue: m.formatValue(oldValue), | |||
| 808 | + | NewValue: m.formatValue(m.defaultValueForKey(newKey)), | |||
| 809 | + | }) | |||
| 810 | + | } | |||
| 811 | + | return changes | |||
| 812 | + | } | |||
| 813 | + | ||||
| 814 | + | func (*Migrator) migrateLegacyVimMode(rawConfig map[string]any) { | |||
| 815 | + | workspace, ok := rawConfig["workspace"].(map[string]any) | |||
| 816 | + | if !ok { | |||
| 817 | + | return | |||
| 818 | + | } | |||
| 819 | + | legacyMode, ok := workspace["page_mode"].(map[string]any) | |||
| 820 | + | if !ok { | |||
| 821 | + | return | |||
| 822 | + | } | |||
| 823 | + | vimMode, ok := workspace["vim_mode"].(map[string]any) | |||
| 824 | + | if !ok { | |||
| 825 | + | vimMode = make(map[string]any) | |||
| 826 | + | workspace["vim_mode"] = vimMode | |||
| 827 | + | } | |||
| 828 | + | for key, value := range legacyMode { | |||
| 829 | + | if key == "actions" { | |||
| 830 | + | continue | |||
| 831 | + | } | |||
| 832 | + | if _, exists := vimMode[key]; !exists { | |||
| 833 | + | vimMode[key] = value | |||
| 834 | + | } | |||
| 835 | + | } | |||
| 836 | + | legacyActions, _ := legacyMode["actions"].(map[string]any) | |||
| 837 | + | vimActions, _ := vimMode["actions"].(map[string]any) | |||
| 838 | + | if vimActions == nil { | |||
| 839 | + | vimActions = make(map[string]any) | |||
| 840 | + | vimMode["actions"] = vimActions | |||
| 841 | + | } | |||
| 842 | + | for oldAction, value := range legacyActions { | |||
| 843 | + | newKey, ok := legacyVimModeKey("workspace.page_mode.actions." + oldAction) | |||
| 844 | + | if !ok { | |||
| 845 | + | continue | |||
| 846 | + | } | |||
| 847 | + | newAction := strings.TrimPrefix(newKey, "workspace.vim_mode.actions.") | |||
| 848 | + | if _, exists := vimActions[newAction]; !exists { | |||
| 849 | + | vimActions[newAction] = value | |||
| 850 | + | } | |||
| 851 | + | } | |||
| 852 | + | delete(workspace, "page_mode") | |||
| 853 | + | } | |||
| 854 | + | ||||
| 855 | + | // legacyVimModeKey converts one legacy Page Mode config key to its canonical | |||
| 856 | + | // Vim Mode key, including action-name normalization. | |||
| 857 | + | func legacyVimModeKey(oldKey string) (string, bool) { | |||
| 858 | + | const oldPrefix = "workspace.page_mode" | |||
| 859 | + | const newPrefix = "workspace.vim_mode" | |||
| 860 | + | ||||
| 861 | + | if !strings.HasPrefix(oldKey, oldPrefix+".") { | |||
| 862 | + | return "", false | |||
| 863 | + | } | |||
| 864 | + | newKey := newPrefix + strings.TrimPrefix(oldKey, oldPrefix) | |||
| 865 | + | const actionsPrefix = newPrefix + ".actions." | |||
| 866 | + | if strings.HasPrefix(newKey, actionsPrefix) { | |||
| 867 | + | return actionsPrefix + legacyVimActionName(strings.TrimPrefix(newKey, actionsPrefix)), true | |||
| 868 | + | } | |||
| 869 | + | return newKey, true | |||
| 870 | + | } | |||
| 871 | + | ||||
| 872 | + | func legacyVimActionName(action string) string { | |||
| 873 | + | for _, prefix := range []string{"page-scroll-", "page_scroll_"} { | |||
| 874 | + | if strings.HasPrefix(action, prefix) { | |||
| 875 | + | return "vim-scroll-" + strings.ReplaceAll(strings.TrimPrefix(action, prefix), "_", "-") | |||
| 876 | + | } | |||
| 877 | + | } | |||
| 878 | + | return action | |||
| 879 | + | } | |||
| 880 | + | ||||
| 791 | // flattenMapWithValues recursively flattens a nested map to dot-notation keys with values. | 881 | // flattenMapWithValues recursively flattens a nested map to dot-notation keys with values. | ||
| 792 | func (m *Migrator) flattenMapWithValues(data map[string]any, prefix string, result map[string]any) { | 882 | func (m *Migrator) flattenMapWithValues(data map[string]any, prefix string, result map[string]any) { | ||
| 793 | for k, v := range data { | 883 | for k, v := range data { | ||
M internal/infrastructure/config/migrate_test.go +112 -0
| @@ -1175,6 +1175,118 @@ func TestMigrator_Migrate_AppliesLegacyBrowsingContextsRename(t *testing.T) { | |||||
| 1175 | assert.NotContains(t, string(migrated), "[workspace.popups]") | 1175 | assert.NotContains(t, string(migrated), "[workspace.popups]") | ||
| 1176 | } | 1176 | } | ||
| 1177 | 1177 | ||||
| 1178 | + | func TestMigrator_Migrate_RenamesLegacyPageModeToVimMode(t *testing.T) { | |||
| 1179 | + | home := t.TempDir() | |||
| 1180 | + | t.Setenv("HOME", home) | |||
| 1181 | + | t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config")) | |||
| 1182 | + | t.Setenv("XDG_DATA_HOME", filepath.Join(home, ".local", "share")) | |||
| 1183 | + | t.Setenv("XDG_STATE_HOME", filepath.Join(home, ".local", "state")) | |||
| 1184 | + | t.Setenv("XDG_CACHE_HOME", filepath.Join(home, ".cache")) | |||
| 1185 | + | ||||
| 1186 | + | configFile, err := GetConfigFile() | |||
| 1187 | + | require.NoError(t, err) | |||
| 1188 | + | require.NoError(t, os.MkdirAll(filepath.Dir(configFile), 0o755)) | |||
| 1189 | + | require.NoError(t, os.WriteFile(configFile, []byte(` | |||
| 1190 | + | [workspace.page_mode] | |||
| 1191 | + | activation_shortcut = "ctrl+alt+y" | |||
| 1192 | + | timeout_ms = 250 | |||
| 1193 | + | ||||
| 1194 | + | [workspace.page_mode.actions.page-scroll-down] | |||
| 1195 | + | keys = ["n"] | |||
| 1196 | + | desc = "Scroll down" | |||
| 1197 | + | ||||
| 1198 | + | [workspace.page_mode.actions.page_scroll_up] | |||
| 1199 | + | keys = ["u"] | |||
| 1200 | + | desc = "Scroll up" | |||
| 1201 | + | ||||
| 1202 | + | [workspace.page_mode.actions.page_scroll_down_fast] | |||
| 1203 | + | keys = ["d"] | |||
| 1204 | + | desc = "Scroll down fast" | |||
| 1205 | + | `), 0o644)) | |||
| 1206 | + | ||||
| 1207 | + | migrator := NewMigrator() | |||
| 1208 | + | applied, err := migrator.Migrate() | |||
| 1209 | + | require.NoError(t, err) | |||
| 1210 | + | assert.Contains(t, applied, "workspace.page_mode.activation_shortcut -> workspace.vim_mode.activation_shortcut") | |||
| 1211 | + | assert.Contains(t, applied, "workspace.page_mode.timeout_ms -> workspace.vim_mode.timeout_ms") | |||
| 1212 | + | assert.Contains(t, applied, "workspace.page_mode.actions -> workspace.vim_mode.actions") | |||
| 1213 | + | ||||
| 1214 | + | migrated, err := os.ReadFile(configFile) | |||
| 1215 | + | require.NoError(t, err) | |||
| 1216 | + | assert.Contains(t, string(migrated), "[workspace.vim_mode]") | |||
| 1217 | + | assert.Contains(t, string(migrated), "activation_shortcut = 'ctrl+alt+y'") | |||
| 1218 | + | assert.Contains(t, string(migrated), "[workspace.vim_mode.actions.vim-scroll-down]") | |||
| 1219 | + | assert.Contains(t, string(migrated), "[workspace.vim_mode.actions.vim-scroll-up]") | |||
| 1220 | + | assert.Contains(t, string(migrated), "[workspace.vim_mode.actions.vim-scroll-down-fast]") | |||
| 1221 | + | assert.Contains(t, string(migrated), "keys = ['u']") | |||
| 1222 | + | assert.NotContains(t, string(migrated), "page_mode") | |||
| 1223 | + | assert.NotContains(t, string(migrated), "page-scroll-down") | |||
| 1224 | + | } | |||
| 1225 | + | ||||
| 1226 | + | func TestMigrator_DetectChanges_CanonicalizesLegacyVimActionNames(t *testing.T) { | |||
| 1227 | + | home := t.TempDir() | |||
| 1228 | + | t.Setenv("HOME", home) | |||
| 1229 | + | t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config")) | |||
| 1230 | + | t.Setenv("XDG_DATA_HOME", filepath.Join(home, ".local", "share")) | |||
| 1231 | + | t.Setenv("XDG_STATE_HOME", filepath.Join(home, ".local", "state")) | |||
| 1232 | + | t.Setenv("XDG_CACHE_HOME", filepath.Join(home, ".cache")) | |||
| 1233 | + | ||||
| 1234 | + | configFile, err := GetConfigFile() | |||
| 1235 | + | require.NoError(t, err) | |||
| 1236 | + | require.NoError(t, os.MkdirAll(filepath.Dir(configFile), 0o755)) | |||
| 1237 | + | require.NoError(t, os.WriteFile(configFile, []byte(` | |||
| 1238 | + | [workspace.page_mode.actions.page_scroll_down_fast] | |||
| 1239 | + | keys = ["d"] | |||
| 1240 | + | desc = "Scroll down fast" | |||
| 1241 | + | `), 0o644)) | |||
| 1242 | + | ||||
| 1243 | + | changes, err := NewMigrator().DetectChanges() | |||
| 1244 | + | require.NoError(t, err) | |||
| 1245 | + | ||||
| 1246 | + | foundRename := false | |||
| 1247 | + | for _, change := range changes { | |||
| 1248 | + | if change.Type == port.KeyChangeRenamed && | |||
| 1249 | + | change.OldKey == "workspace.page_mode.actions" && | |||
| 1250 | + | change.NewKey == "workspace.vim_mode.actions" { | |||
| 1251 | + | foundRename = true | |||
| 1252 | + | } | |||
| 1253 | + | } | |||
| 1254 | + | assert.True(t, foundRename, "DetectChanges must report the canonical Vim Mode actions key") | |||
| 1255 | + | assert.Equal(t, "vim-scroll-down-fast", legacyVimActionName("page_scroll_down_fast")) | |||
| 1256 | + | } | |||
| 1257 | + | ||||
| 1258 | + | func TestMigrator_Migrate_MergesLegacyPageModeActionsIntoExistingVimMode(t *testing.T) { | |||
| 1259 | + | home := t.TempDir() | |||
| 1260 | + | t.Setenv("HOME", home) | |||
| 1261 | + | t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config")) | |||
| 1262 | + | t.Setenv("XDG_DATA_HOME", filepath.Join(home, ".local", "share")) | |||
| 1263 | + | t.Setenv("XDG_STATE_HOME", filepath.Join(home, ".local", "state")) | |||
| 1264 | + | t.Setenv("XDG_CACHE_HOME", filepath.Join(home, ".cache")) | |||
| 1265 | + | ||||
| 1266 | + | configFile, err := GetConfigFile() | |||
| 1267 | + | require.NoError(t, err) | |||
| 1268 | + | require.NoError(t, os.MkdirAll(filepath.Dir(configFile), 0o755)) | |||
| 1269 | + | require.NoError(t, os.WriteFile(configFile, []byte(` | |||
| 1270 | + | [workspace.page_mode.actions.page-scroll-down] | |||
| 1271 | + | keys = ["n"] | |||
| 1272 | + | ||||
| 1273 | + | [workspace.page_mode.actions.page-scroll-up] | |||
| 1274 | + | keys = ["u"] | |||
| 1275 | + | ||||
| 1276 | + | [workspace.vim_mode.actions.vim-scroll-down] | |||
| 1277 | + | keys = ["j"] | |||
| 1278 | + | `), 0o644)) | |||
| 1279 | + | ||||
| 1280 | + | _, err = NewMigrator().Migrate() | |||
| 1281 | + | require.NoError(t, err) | |||
| 1282 | + | ||||
| 1283 | + | migrated, err := os.ReadFile(configFile) | |||
| 1284 | + | require.NoError(t, err) | |||
| 1285 | + | assert.Contains(t, string(migrated), "[workspace.vim_mode.actions.vim-scroll-down]\n keys = ['j']") | |||
| 1286 | + | assert.Contains(t, string(migrated), "[workspace.vim_mode.actions.vim-scroll-up]\n keys = ['u']") | |||
| 1287 | + | assert.NotContains(t, string(migrated), "page_mode") | |||
| 1288 | + | } | |||
| 1289 | + | ||||
| 1178 | func TestMigrator_DetectChanges_MixedBrowsingContextsDoesNotDoubleReportLegacyRename(t *testing.T) { | 1290 | func TestMigrator_DetectChanges_MixedBrowsingContextsDoesNotDoubleReportLegacyRename(t *testing.T) { | ||
| 1179 | home := t.TempDir() | 1291 | home := t.TempDir() | ||
| 1180 | t.Setenv("HOME", home) | 1292 | t.Setenv("HOME", home) | ||
D internal/infrastructure/config/page_mode_config_test.go +0 -223
| @@ -1,223 +0,0 @@ | |||||
| 1 | - | package config | |||
| 2 | - | ||||
| 3 | - | import ( | |||
| 4 | - | "testing" | |||
| 5 | - | ||||
| 6 | - | "github.com/stretchr/testify/assert" | |||
| 7 | - | "github.com/stretchr/testify/require" | |||
| 8 | - | ) | |||
| 9 | - | ||||
| 10 | - | func TestDefaultConfig_PageModeDefaults(t *testing.T) { | |||
| 11 | - | cfg := DefaultConfig() | |||
| 12 | - | ||||
| 13 | - | // PageMode must exist in workspace config. | |||
| 14 | - | pageMode := cfg.Workspace.PageMode | |||
| 15 | - | assert.Equal(t, "ctrl+y", pageMode.ActivationShortcut) | |||
| 16 | - | assert.Equal(t, 0, pageMode.TimeoutMilliseconds) | |||
| 17 | - | ||||
| 18 | - | // All required actions must exist with exact key bindings. | |||
| 19 | - | require.NotNil(t, pageMode.Actions) | |||
| 20 | - | requireActionBinding(t, pageMode.Actions, "page-scroll-left", []string{"h"}) | |||
| 21 | - | requireActionBinding(t, pageMode.Actions, "page-scroll-down", []string{"j"}) | |||
| 22 | - | requireActionBinding(t, pageMode.Actions, "page-scroll-up", []string{"k"}) | |||
| 23 | - | requireActionBinding(t, pageMode.Actions, "page-scroll-right", []string{"l"}) | |||
| 24 | - | requireActionBinding(t, pageMode.Actions, "page-scroll-down-fast", []string{"shift+j"}) | |||
| 25 | - | requireActionBinding(t, pageMode.Actions, "page-scroll-up-fast", []string{"shift+k"}) | |||
| 26 | - | requireActionBinding(t, pageMode.Actions, "confirm", []string{"enter"}) | |||
| 27 | - | requireActionBinding(t, pageMode.Actions, "cancel", []string{"escape"}) | |||
| 28 | - | } | |||
| 29 | - | ||||
| 30 | - | func TestPageModeConfig_GetKeyBindings(t *testing.T) { | |||
| 31 | - | cfg := DefaultConfig() | |||
| 32 | - | bindings := cfg.Workspace.PageMode.GetKeyBindings() | |||
| 33 | - | ||||
| 34 | - | assert.Equal(t, "page-scroll-left", bindings["h"]) | |||
| 35 | - | assert.Equal(t, "page-scroll-down", bindings["j"]) | |||
| 36 | - | assert.Equal(t, "page-scroll-up", bindings["k"]) | |||
| 37 | - | assert.Equal(t, "page-scroll-right", bindings["l"]) | |||
| 38 | - | assert.Equal(t, "page-scroll-down-fast", bindings["shift+j"]) | |||
| 39 | - | assert.Equal(t, "page-scroll-up-fast", bindings["shift+k"]) | |||
| 40 | - | assert.Equal(t, "confirm", bindings["enter"]) | |||
| 41 | - | assert.Equal(t, "cancel", bindings["escape"]) | |||
| 42 | - | assert.Len(t, bindings, 8) | |||
| 43 | - | } | |||
| 44 | - | ||||
| 45 | - | func TestValidatePageMode_NonNegativeTimeout(t *testing.T) { | |||
| 46 | - | tests := []struct { | |||
| 47 | - | name string | |||
| 48 | - | mutate func(*Config) | |||
| 49 | - | want string | |||
| 50 | - | }{ | |||
| 51 | - | { | |||
| 52 | - | name: "negative timeout fails", | |||
| 53 | - | mutate: func(cfg *Config) { | |||
| 54 | - | cfg.Workspace.PageMode.TimeoutMilliseconds = -1 | |||
| 55 | - | }, | |||
| 56 | - | want: "workspace.page_mode.timeout_ms must be non-negative", | |||
| 57 | - | }, | |||
| 58 | - | { | |||
| 59 | - | name: "zero timeout ok", | |||
| 60 | - | mutate: func(_ *Config) { | |||
| 61 | - | }, | |||
| 62 | - | want: "", | |||
| 63 | - | }, | |||
| 64 | - | { | |||
| 65 | - | name: "positive timeout ok", | |||
| 66 | - | mutate: func(cfg *Config) { | |||
| 67 | - | cfg.Workspace.PageMode.TimeoutMilliseconds = 5000 | |||
| 68 | - | }, | |||
| 69 | - | want: "", | |||
| 70 | - | }, | |||
| 71 | - | } | |||
| 72 | - | ||||
| 73 | - | for _, tt := range tests { | |||
| 74 | - | t.Run(tt.name, func(t *testing.T) { | |||
| 75 | - | cfg := DefaultConfig() | |||
| 76 | - | tt.mutate(cfg) | |||
| 77 | - | err := validateConfig(cfg) | |||
| 78 | - | if tt.want == "" { | |||
| 79 | - | require.NoError(t, err) | |||
| 80 | - | } else { | |||
| 81 | - | require.Error(t, err) | |||
| 82 | - | assert.Contains(t, err.Error(), tt.want) | |||
| 83 | - | } | |||
| 84 | - | }) | |||
| 85 | - | } | |||
| 86 | - | } | |||
| 87 | - | ||||
| 88 | - | func TestValidatePageMode_NilActionsFail(t *testing.T) { | |||
| 89 | - | cfg := DefaultConfig() | |||
| 90 | - | cfg.Workspace.PageMode.Actions = nil | |||
| 91 | - | ||||
| 92 | - | err := validateConfig(cfg) | |||
| 93 | - | require.Error(t, err) | |||
| 94 | - | assert.Contains(t, err.Error(), "workspace.page_mode.actions cannot be empty") | |||
| 95 | - | } | |||
| 96 | - | ||||
| 97 | - | func TestValidatePageMode_DuplicateKeyBindings(t *testing.T) { | |||
| 98 | - | cfg := DefaultConfig() | |||
| 99 | - | // Add a duplicate key binding that conflicts with an existing one. | |||
| 100 | - | action := "custom-action" | |||
| 101 | - | cfg.Workspace.PageMode.Actions[action] = ActionBinding{ | |||
| 102 | - | Keys: []string{"h"}, // "h" is already used by page-scroll-left | |||
| 103 | - | Desc: "Custom action", | |||
| 104 | - | } | |||
| 105 | - | ||||
| 106 | - | err := validateConfig(cfg) | |||
| 107 | - | require.Error(t, err) | |||
| 108 | - | assert.Contains(t, err.Error(), "duplicate key binding 'h' found in page_mode actions") | |||
| 109 | - | assert.Contains(t, err.Error(), "page-scroll-left") | |||
| 110 | - | assert.Contains(t, err.Error(), action) | |||
| 111 | - | } | |||
| 112 | - | ||||
| 113 | - | func TestValidatePageMode_EmptyActionKeysFail(t *testing.T) { | |||
| 114 | - | cfg := DefaultConfig() | |||
| 115 | - | // Override an action with no key bindings. | |||
| 116 | - | cfg.Workspace.PageMode.Actions = map[string]ActionBinding{ | |||
| 117 | - | "page-scroll-left": {Keys: []string{}, Desc: "No keys"}, | |||
| 118 | - | } | |||
| 119 | - | ||||
| 120 | - | err := validateConfig(cfg) | |||
| 121 | - | require.Error(t, err) | |||
| 122 | - | assert.Contains(t, err.Error(), "workspace.page_mode.actions.page-scroll-left must have at least one key binding") | |||
| 123 | - | } | |||
| 124 | - | ||||
| 125 | - | func TestValidateModalModeActions_RegressionTable(t *testing.T) { | |||
| 126 | - | tests := []struct { | |||
| 127 | - | name string | |||
| 128 | - | modePath string | |||
| 129 | - | timeout int | |||
| 130 | - | actions map[string]ActionBinding | |||
| 131 | - | want string | |||
| 132 | - | }{ | |||
| 133 | - | { | |||
| 134 | - | name: "page negative timeout", | |||
| 135 | - | modePath: "page_mode", | |||
| 136 | - | timeout: -1, | |||
| 137 | - | actions: map[string]ActionBinding{"page-scroll-left": {Keys: []string{"h"}}}, | |||
| 138 | - | want: "workspace.page_mode.timeout_ms must be non-negative", | |||
| 139 | - | }, | |||
| 140 | - | { | |||
| 141 | - | name: "pane empty actions", | |||
| 142 | - | modePath: "pane_mode", | |||
| 143 | - | timeout: 0, | |||
| 144 | - | actions: nil, | |||
| 145 | - | want: "workspace.pane_mode.actions cannot be empty", | |||
| 146 | - | }, | |||
| 147 | - | { | |||
| 148 | - | name: "tab empty keys", | |||
| 149 | - | modePath: "tab_mode", | |||
| 150 | - | timeout: 0, | |||
| 151 | - | actions: map[string]ActionBinding{"new-tab": {Keys: []string{}}}, | |||
| 152 | - | want: "workspace.tab_mode.actions.new-tab must have at least one key binding", | |||
| 153 | - | }, | |||
| 154 | - | { | |||
| 155 | - | name: "page duplicate vim binding", | |||
| 156 | - | modePath: "page_mode", | |||
| 157 | - | timeout: 0, | |||
| 158 | - | actions: map[string]ActionBinding{ | |||
| 159 | - | "page-scroll-left": {Keys: []string{"h"}}, | |||
| 160 | - | "page-scroll-right": {Keys: []string{"h"}}, | |||
| 161 | - | }, | |||
| 162 | - | want: "duplicate key binding 'h' found in page_mode actions", | |||
| 163 | - | }, | |||
| 164 | - | { | |||
| 165 | - | name: "page canonical vim set ok", | |||
| 166 | - | modePath: "page_mode", | |||
| 167 | - | timeout: 0, | |||
| 168 | - | actions: map[string]ActionBinding{ | |||
| 169 | - | "page-scroll-left": {Keys: []string{"h"}}, | |||
| 170 | - | "page-scroll-down": {Keys: []string{"j"}}, | |||
| 171 | - | "page-scroll-up": {Keys: []string{"k"}}, | |||
| 172 | - | "page-scroll-right": {Keys: []string{"l"}}, | |||
| 173 | - | "page-scroll-down-fast": {Keys: []string{"shift+j"}}, | |||
| 174 | - | "page-scroll-up-fast": {Keys: []string{"shift+k"}}, | |||
| 175 | - | "confirm": {Keys: []string{"enter"}}, | |||
| 176 | - | "cancel": {Keys: []string{"escape"}}, | |||
| 177 | - | }, | |||
| 178 | - | want: "", | |||
| 179 | - | }, | |||
| 180 | - | } | |||
| 181 | - | ||||
| 182 | - | for _, tt := range tests { | |||
| 183 | - | t.Run(tt.name, func(t *testing.T) { | |||
| 184 | - | errs := validateModalModeActions(tt.modePath, tt.timeout, tt.actions) | |||
| 185 | - | if tt.want == "" { | |||
| 186 | - | assert.Empty(t, errs) | |||
| 187 | - | return | |||
| 188 | - | } | |||
| 189 | - | require.NotEmpty(t, errs) | |||
| 190 | - | assert.Contains(t, errs[0], tt.want) | |||
| 191 | - | }) | |||
| 192 | - | } | |||
| 193 | - | } | |||
| 194 | - | ||||
| 195 | - | func TestSchemaProvider_PageModeKeys(t *testing.T) { | |||
| 196 | - | provider := &SchemaProvider{} | |||
| 197 | - | schema := provider.GetSchema() | |||
| 198 | - | ||||
| 199 | - | var foundActivation, foundTimeout, foundActions bool | |||
| 200 | - | for _, key := range schema { | |||
| 201 | - | switch key.Key { | |||
| 202 | - | case "workspace.page_mode.activation_shortcut": | |||
| 203 | - | foundActivation = true | |||
| 204 | - | assert.Equal(t, "string", key.Type) | |||
| 205 | - | assert.Equal(t, "ctrl+y", key.Default) | |||
| 206 | - | assert.Equal(t, SectionWorkspace, key.Section) | |||
| 207 | - | case "workspace.page_mode.timeout_ms": | |||
| 208 | - | foundTimeout = true | |||
| 209 | - | assert.Equal(t, "int", key.Type) | |||
| 210 | - | assert.Equal(t, "0", key.Default) | |||
| 211 | - | assert.Equal(t, ">=0", key.Range) | |||
| 212 | - | assert.Equal(t, SectionWorkspace, key.Section) | |||
| 213 | - | case "workspace.page_mode.actions.<action>": | |||
| 214 | - | foundActions = true | |||
| 215 | - | assert.Equal(t, "[]string", key.Type) | |||
| 216 | - | assert.Equal(t, SectionWorkspace, key.Section) | |||
| 217 | - | } | |||
| 218 | - | } | |||
| 219 | - | ||||
| 220 | - | assert.True(t, foundActivation, "missing workspace.page_mode.activation_shortcut schema key") | |||
| 221 | - | assert.True(t, foundTimeout, "missing workspace.page_mode.timeout_ms schema key") | |||
| 222 | - | assert.True(t, foundActions, "missing workspace.page_mode.actions.<action> schema key") | |||
| 223 | - | } | |||
M internal/infrastructure/config/schema.go +2 -2
| @@ -24,8 +24,8 @@ type TabModeConfig = entity.TabModeConfig | |||||
| 24 | // ResizeModeConfig defines modal behavior for resizing panes (Zellij-style). | 24 | // ResizeModeConfig defines modal behavior for resizing panes (Zellij-style). | ||
| 25 | type ResizeModeConfig = entity.ResizeModeConfig | 25 | type ResizeModeConfig = entity.ResizeModeConfig | ||
| 26 | 26 | ||||
| 27 | - | // PageModeConfig defines modal behavior for page scrolling. | 27 | + | // VimModeConfig defines modal behavior for Vim-style scrolling. |
| 28 | - | type PageModeConfig = entity.PageModeConfig | 28 | + | type VimModeConfig = entity.VimModeConfig |
| 29 | 29 | ||||
| 30 | // SessionModeConfig defines modal behavior for session management. | 30 | // SessionModeConfig defines modal behavior for session management. | ||
| 31 | type SessionModeConfig = entity.SessionModeConfig | 31 | type SessionModeConfig = entity.SessionModeConfig | ||
M internal/infrastructure/config/schema_provider.go +10 -10
| @@ -472,27 +472,27 @@ func (*SchemaProvider) getWorkspaceKeys(defaults *Config) []entity.ConfigKeyInfo | |||||
| 472 | Description: "Key bindings for tab mode actions", | 472 | Description: "Key bindings for tab mode actions", | ||
| 473 | Section: SectionWorkspace, | 473 | Section: SectionWorkspace, | ||
| 474 | }, | 474 | }, | ||
| 475 | - | // Page mode | 475 | + | // Vim mode |
| 476 | { | 476 | { | ||
| 477 | - | Key: "workspace.page_mode.activation_shortcut", | 477 | + | Key: "workspace.vim_mode.activation_shortcut", |
| 478 | Type: "string", | 478 | Type: "string", | ||
| 479 | - | Default: defaults.Workspace.PageMode.ActivationShortcut, | 479 | + | Default: defaults.Workspace.VimMode.ActivationShortcut, |
| 480 | - | Description: "Shortcut to enter page scroll mode", | 480 | + | Description: "Shortcut to enter vim scroll mode", |
| 481 | Section: SectionWorkspace, | 481 | Section: SectionWorkspace, | ||
| 482 | }, | 482 | }, | ||
| 483 | { | 483 | { | ||
| 484 | - | Key: "workspace.page_mode.timeout_ms", | 484 | + | Key: "workspace.vim_mode.timeout_ms", |
| 485 | Type: "int", | 485 | Type: "int", | ||
| 486 | - | Default: fmt.Sprintf("%d", defaults.Workspace.PageMode.TimeoutMilliseconds), | 486 | + | Default: fmt.Sprintf("%d", defaults.Workspace.VimMode.TimeoutMilliseconds), |
| 487 | - | Description: "Page mode timeout in milliseconds (0 = no timeout)", | 487 | + | Description: "Vim mode timeout in milliseconds (0 = no timeout)", |
| 488 | Range: ">=0", | 488 | Range: ">=0", | ||
| 489 | Section: SectionWorkspace, | 489 | Section: SectionWorkspace, | ||
| 490 | }, | 490 | }, | ||
| 491 | { | 491 | { | ||
| 492 | - | Key: "workspace.page_mode.actions.<action>", | 492 | + | Key: "workspace.vim_mode.actions.<action>", |
| 493 | - | Type: "[]string", | 493 | + | Type: "object", |
| 494 | Default: "(see defaults)", | 494 | Default: "(see defaults)", | ||
| 495 | - | Description: "Key bindings for page mode actions", | 495 | + | Description: "Vim mode action object with keys ([]string) and desc (string)", |
| 496 | Section: SectionWorkspace, | 496 | Section: SectionWorkspace, | ||
| 497 | }, | 497 | }, | ||
| 498 | // Resize mode | 498 | // Resize mode | ||
M internal/infrastructure/config/schema_provider_test.go +1 -1
| @@ -146,7 +146,7 @@ func TestSchemaProviderCoversViperDefaults(t *testing.T) { | |||||
| 146 | "search_shortcuts": "search_shortcuts.<key>", | 146 | "search_shortcuts": "search_shortcuts.<key>", | ||
| 147 | "workspace.pane_mode.actions": "workspace.pane_mode.actions.<action>", | 147 | "workspace.pane_mode.actions": "workspace.pane_mode.actions.<action>", | ||
| 148 | "workspace.tab_mode.actions": "workspace.tab_mode.actions.<action>", | 148 | "workspace.tab_mode.actions": "workspace.tab_mode.actions.<action>", | ||
| 149 | - | "workspace.page_mode.actions": "workspace.page_mode.actions.<action>", | 149 | + | "workspace.vim_mode.actions": "workspace.vim_mode.actions.<action>", |
| 150 | "workspace.resize_mode.actions": "workspace.resize_mode.actions.<action>", | 150 | "workspace.resize_mode.actions": "workspace.resize_mode.actions.<action>", | ||
| 151 | "workspace.floating_pane.profiles": "workspace.floating_pane.profiles.<name>", | 151 | "workspace.floating_pane.profiles": "workspace.floating_pane.profiles.<name>", | ||
| 152 | "session.session_mode.actions": "session.session_mode.actions.<action>", | 152 | "session.session_mode.actions": "session.session_mode.actions.<action>", | ||
M internal/infrastructure/config/validation.go +3 -3
| @@ -29,7 +29,7 @@ func validateConfig(config *Config) error { | |||||
| 29 | validationErrors = append(validationErrors, validatePaneMode(config)...) | 29 | validationErrors = append(validationErrors, validatePaneMode(config)...) | ||
| 30 | validationErrors = append(validationErrors, validateTabBar(config)...) | 30 | validationErrors = append(validationErrors, validateTabBar(config)...) | ||
| 31 | validationErrors = append(validationErrors, validateTabMode(config)...) | 31 | validationErrors = append(validationErrors, validateTabMode(config)...) | ||
| 32 | - | validationErrors = append(validationErrors, validatePageMode(config)...) | 32 | + | validationErrors = append(validationErrors, validateVimMode(config)...) |
| 33 | validationErrors = append(validationErrors, validateFloatingPane(config)...) | 33 | validationErrors = append(validationErrors, validateFloatingPane(config)...) | ||
| 34 | validationErrors = append(validationErrors, validateLogging(config)...) | 34 | validationErrors = append(validationErrors, validateLogging(config)...) | ||
| 35 | validationErrors = append(validationErrors, validateWorkspaceNewPaneURL(config)...) | 35 | validationErrors = append(validationErrors, validateWorkspaceNewPaneURL(config)...) | ||
| @@ -242,8 +242,8 @@ func validateTabMode(config *Config) []string { | |||||
| 242 | return validateModalModeActions("tab_mode", config.Workspace.TabMode.TimeoutMilliseconds, config.Workspace.TabMode.Actions) | 242 | return validateModalModeActions("tab_mode", config.Workspace.TabMode.TimeoutMilliseconds, config.Workspace.TabMode.Actions) | ||
| 243 | } | 243 | } | ||
| 244 | 244 | ||||
| 245 | - | func validatePageMode(config *Config) []string { | 245 | + | func validateVimMode(config *Config) []string { |
| 246 | - | return validateModalModeActions("page_mode", config.Workspace.PageMode.TimeoutMilliseconds, config.Workspace.PageMode.Actions) | 246 | + | return validateModalModeActions("vim_mode", config.Workspace.VimMode.TimeoutMilliseconds, config.Workspace.VimMode.Actions) |
| 247 | } | 247 | } | ||
| 248 | 248 | ||||
| 249 | // validateModalModeActions shared timeout/empty-actions/duplicate-key checks for | 249 | // validateModalModeActions shared timeout/empty-actions/duplicate-key checks for | ||
A internal/infrastructure/config/vim_mode_config_test.go +223 -0
| @@ -0,0 +1,223 @@ | |||||
| 1 | + | package config | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "testing" | |||
| 5 | + | ||||
| 6 | + | "github.com/stretchr/testify/assert" | |||
| 7 | + | "github.com/stretchr/testify/require" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 10 | + | func TestDefaultConfig_VimModeDefaults(t *testing.T) { | |||
| 11 | + | cfg := DefaultConfig() | |||
| 12 | + | ||||
| 13 | + | // VimMode must exist in workspace config. | |||
| 14 | + | vimMode := cfg.Workspace.VimMode | |||
| 15 | + | assert.Equal(t, "ctrl+y", vimMode.ActivationShortcut) | |||
| 16 | + | assert.Equal(t, 0, vimMode.TimeoutMilliseconds) | |||
| 17 | + | ||||
| 18 | + | // All required actions must exist with exact key bindings. | |||
| 19 | + | require.NotNil(t, vimMode.Actions) | |||
| 20 | + | requireActionBinding(t, vimMode.Actions, "vim-scroll-left", []string{"h"}) | |||
| 21 | + | requireActionBinding(t, vimMode.Actions, "vim-scroll-down", []string{"j"}) | |||
| 22 | + | requireActionBinding(t, vimMode.Actions, "vim-scroll-up", []string{"k"}) | |||
| 23 | + | requireActionBinding(t, vimMode.Actions, "vim-scroll-right", []string{"l"}) | |||
| 24 | + | requireActionBinding(t, vimMode.Actions, "vim-scroll-down-fast", []string{"shift+j"}) | |||
| 25 | + | requireActionBinding(t, vimMode.Actions, "vim-scroll-up-fast", []string{"shift+k"}) | |||
| 26 | + | requireActionBinding(t, vimMode.Actions, "confirm", []string{"enter"}) | |||
| 27 | + | requireActionBinding(t, vimMode.Actions, "cancel", []string{"escape"}) | |||
| 28 | + | } | |||
| 29 | + | ||||
| 30 | + | func TestVimModeConfig_GetKeyBindings(t *testing.T) { | |||
| 31 | + | cfg := DefaultConfig() | |||
| 32 | + | bindings := cfg.Workspace.VimMode.GetKeyBindings() | |||
| 33 | + | ||||
| 34 | + | assert.Equal(t, "vim-scroll-left", bindings["h"]) | |||
| 35 | + | assert.Equal(t, "vim-scroll-down", bindings["j"]) | |||
| 36 | + | assert.Equal(t, "vim-scroll-up", bindings["k"]) | |||
| 37 | + | assert.Equal(t, "vim-scroll-right", bindings["l"]) | |||
| 38 | + | assert.Equal(t, "vim-scroll-down-fast", bindings["shift+j"]) | |||
| 39 | + | assert.Equal(t, "vim-scroll-up-fast", bindings["shift+k"]) | |||
| 40 | + | assert.Equal(t, "confirm", bindings["enter"]) | |||
| 41 | + | assert.Equal(t, "cancel", bindings["escape"]) | |||
| 42 | + | assert.Len(t, bindings, 8) | |||
| 43 | + | } | |||
| 44 | + | ||||
| 45 | + | func TestValidateVimMode_NonNegativeTimeout(t *testing.T) { | |||
| 46 | + | tests := []struct { | |||
| 47 | + | name string | |||
| 48 | + | mutate func(*Config) | |||
| 49 | + | want string | |||
| 50 | + | }{ | |||
| 51 | + | { | |||
| 52 | + | name: "negative timeout fails", | |||
| 53 | + | mutate: func(cfg *Config) { | |||
| 54 | + | cfg.Workspace.VimMode.TimeoutMilliseconds = -1 | |||
| 55 | + | }, | |||
| 56 | + | want: "workspace.vim_mode.timeout_ms must be non-negative", | |||
| 57 | + | }, | |||
| 58 | + | { | |||
| 59 | + | name: "zero timeout ok", | |||
| 60 | + | mutate: func(_ *Config) { | |||
| 61 | + | }, | |||
| 62 | + | want: "", | |||
| 63 | + | }, | |||
| 64 | + | { | |||
| 65 | + | name: "positive timeout ok", | |||
| 66 | + | mutate: func(cfg *Config) { | |||
| 67 | + | cfg.Workspace.VimMode.TimeoutMilliseconds = 5000 | |||
| 68 | + | }, | |||
| 69 | + | want: "", | |||
| 70 | + | }, | |||
| 71 | + | } | |||
| 72 | + | ||||
| 73 | + | for _, tt := range tests { | |||
| 74 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 75 | + | cfg := DefaultConfig() | |||
| 76 | + | tt.mutate(cfg) | |||
| 77 | + | err := validateConfig(cfg) | |||
| 78 | + | if tt.want == "" { | |||
| 79 | + | require.NoError(t, err) | |||
| 80 | + | } else { | |||
| 81 | + | require.Error(t, err) | |||
| 82 | + | assert.Contains(t, err.Error(), tt.want) | |||
| 83 | + | } | |||
| 84 | + | }) | |||
| 85 | + | } | |||
| 86 | + | } | |||
| 87 | + | ||||
| 88 | + | func TestValidateVimMode_NilActionsFail(t *testing.T) { | |||
| 89 | + | cfg := DefaultConfig() | |||
| 90 | + | cfg.Workspace.VimMode.Actions = nil | |||
| 91 | + | ||||
| 92 | + | err := validateConfig(cfg) | |||
| 93 | + | require.Error(t, err) | |||
| 94 | + | assert.Contains(t, err.Error(), "workspace.vim_mode.actions cannot be empty") | |||
| 95 | + | } | |||
| 96 | + | ||||
| 97 | + | func TestValidateVimMode_DuplicateKeyBindings(t *testing.T) { | |||
| 98 | + | cfg := DefaultConfig() | |||
| 99 | + | // Add a duplicate key binding that conflicts with an existing one. | |||
| 100 | + | action := "custom-action" | |||
| 101 | + | cfg.Workspace.VimMode.Actions[action] = ActionBinding{ | |||
| 102 | + | Keys: []string{"h"}, // "h" is already used by vim-scroll-left | |||
| 103 | + | Desc: "Custom action", | |||
| 104 | + | } | |||
| 105 | + | ||||
| 106 | + | err := validateConfig(cfg) | |||
| 107 | + | require.Error(t, err) | |||
| 108 | + | assert.Contains(t, err.Error(), "duplicate key binding 'h' found in vim_mode actions") | |||
| 109 | + | assert.Contains(t, err.Error(), "vim-scroll-left") | |||
| 110 | + | assert.Contains(t, err.Error(), action) | |||
| 111 | + | } | |||
| 112 | + | ||||
| 113 | + | func TestValidateVimMode_EmptyActionKeysFail(t *testing.T) { | |||
| 114 | + | cfg := DefaultConfig() | |||
| 115 | + | // Override an action with no key bindings. | |||
| 116 | + | cfg.Workspace.VimMode.Actions = map[string]ActionBinding{ | |||
| 117 | + | "vim-scroll-left": {Keys: []string{}, Desc: "No keys"}, | |||
| 118 | + | } | |||
| 119 | + | ||||
| 120 | + | err := validateConfig(cfg) | |||
| 121 | + | require.Error(t, err) | |||
| 122 | + | assert.Contains(t, err.Error(), "workspace.vim_mode.actions.vim-scroll-left must have at least one key binding") | |||
| 123 | + | } | |||
| 124 | + | ||||
| 125 | + | func TestValidateModalModeActions_RegressionTable(t *testing.T) { | |||
| 126 | + | tests := []struct { | |||
| 127 | + | name string | |||
| 128 | + | modePath string | |||
| 129 | + | timeout int | |||
| 130 | + | actions map[string]ActionBinding | |||
| 131 | + | want string | |||
| 132 | + | }{ | |||
| 133 | + | { | |||
| 134 | + | name: "page negative timeout", | |||
| 135 | + | modePath: "vim_mode", | |||
| 136 | + | timeout: -1, | |||
| 137 | + | actions: map[string]ActionBinding{"vim-scroll-left": {Keys: []string{"h"}}}, | |||
| 138 | + | want: "workspace.vim_mode.timeout_ms must be non-negative", | |||
| 139 | + | }, | |||
| 140 | + | { | |||
| 141 | + | name: "pane empty actions", | |||
| 142 | + | modePath: "pane_mode", | |||
| 143 | + | timeout: 0, | |||
| 144 | + | actions: nil, | |||
| 145 | + | want: "workspace.pane_mode.actions cannot be empty", | |||
| 146 | + | }, | |||
| 147 | + | { | |||
| 148 | + | name: "tab empty keys", | |||
| 149 | + | modePath: "tab_mode", | |||
| 150 | + | timeout: 0, | |||
| 151 | + | actions: map[string]ActionBinding{"new-tab": {Keys: []string{}}}, | |||
| 152 | + | want: "workspace.tab_mode.actions.new-tab must have at least one key binding", | |||
| 153 | + | }, | |||
| 154 | + | { | |||
| 155 | + | name: "page duplicate vim binding", | |||
| 156 | + | modePath: "vim_mode", | |||
| 157 | + | timeout: 0, | |||
| 158 | + | actions: map[string]ActionBinding{ | |||
| 159 | + | "vim-scroll-left": {Keys: []string{"h"}}, | |||
| 160 | + | "vim-scroll-right": {Keys: []string{"h"}}, | |||
| 161 | + | }, | |||
| 162 | + | want: "duplicate key binding 'h' found in vim_mode actions", | |||
| 163 | + | }, | |||
| 164 | + | { | |||
| 165 | + | name: "page canonical vim set ok", | |||
| 166 | + | modePath: "vim_mode", | |||
| 167 | + | timeout: 0, | |||
| 168 | + | actions: map[string]ActionBinding{ | |||
| 169 | + | "vim-scroll-left": {Keys: []string{"h"}}, | |||
| 170 | + | "vim-scroll-down": {Keys: []string{"j"}}, | |||
| 171 | + | "vim-scroll-up": {Keys: []string{"k"}}, | |||
| 172 | + | "vim-scroll-right": {Keys: []string{"l"}}, | |||
| 173 | + | "vim-scroll-down-fast": {Keys: []string{"shift+j"}}, | |||
| 174 | + | "vim-scroll-up-fast": {Keys: []string{"shift+k"}}, | |||
| 175 | + | "confirm": {Keys: []string{"enter"}}, | |||
| 176 | + | "cancel": {Keys: []string{"escape"}}, | |||
| 177 | + | }, | |||
| 178 | + | want: "", | |||
| 179 | + | }, | |||
| 180 | + | } | |||
| 181 | + | ||||
| 182 | + | for _, tt := range tests { | |||
| 183 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 184 | + | errs := validateModalModeActions(tt.modePath, tt.timeout, tt.actions) | |||
| 185 | + | if tt.want == "" { | |||
| 186 | + | assert.Empty(t, errs) | |||
| 187 | + | return | |||
| 188 | + | } | |||
| 189 | + | require.NotEmpty(t, errs) | |||
| 190 | + | assert.Contains(t, errs[0], tt.want) | |||
| 191 | + | }) | |||
| 192 | + | } | |||
| 193 | + | } | |||
| 194 | + | ||||
| 195 | + | func TestSchemaProvider_VimModeKeys(t *testing.T) { | |||
| 196 | + | provider := &SchemaProvider{} | |||
| 197 | + | schema := provider.GetSchema() | |||
| 198 | + | ||||
| 199 | + | var foundActivation, foundTimeout, foundActions bool | |||
| 200 | + | for _, key := range schema { | |||
| 201 | + | switch key.Key { | |||
| 202 | + | case "workspace.vim_mode.activation_shortcut": | |||
| 203 | + | foundActivation = true | |||
| 204 | + | assert.Equal(t, "string", key.Type) | |||
| 205 | + | assert.Equal(t, "ctrl+y", key.Default) | |||
| 206 | + | assert.Equal(t, SectionWorkspace, key.Section) | |||
| 207 | + | case "workspace.vim_mode.timeout_ms": | |||
| 208 | + | foundTimeout = true | |||
| 209 | + | assert.Equal(t, "int", key.Type) | |||
| 210 | + | assert.Equal(t, "0", key.Default) | |||
| 211 | + | assert.Equal(t, ">=0", key.Range) | |||
| 212 | + | assert.Equal(t, SectionWorkspace, key.Section) | |||
| 213 | + | case "workspace.vim_mode.actions.<action>": | |||
| 214 | + | foundActions = true | |||
| 215 | + | assert.Equal(t, "object", key.Type) | |||
| 216 | + | assert.Equal(t, SectionWorkspace, key.Section) | |||
| 217 | + | } | |||
| 218 | + | } | |||
| 219 | + | ||||
| 220 | + | assert.True(t, foundActivation, "missing workspace.vim_mode.activation_shortcut schema key") | |||
| 221 | + | assert.True(t, foundTimeout, "missing workspace.vim_mode.timeout_ms schema key") | |||
| 222 | + | assert.True(t, foundActions, "missing workspace.vim_mode.actions.<action> schema key") | |||
| 223 | + | } | |||
M internal/infrastructure/webkit/messaging.go +2 -2
| @@ -289,9 +289,9 @@ func decodeScriptMessage(valuePtr uintptr, log zerolog.Logger) (Message, bool) { | |||||
| 289 | } | 289 | } | ||
| 290 | 290 | ||||
| 291 | // handleAllowlistedBridgeMessage handles the one narrow page-origin hint that | 291 | // handleAllowlistedBridgeMessage handles the one narrow page-origin hint that | ||
| 292 | - | // is allowed before trusted-page routing: editable focus state for Page mode. | 292 | + | // is allowed before trusted-page routing: editable focus state for Vim mode. |
| 293 | // This is intentionally not a privileged bridge command; the only effect is | 293 | // This is intentionally not a privileged bridge command; the only effect is | ||
| 294 | - | // updating editable-focus state on the sender WebView so Page mode can block or | 294 | + | // updating editable-focus state on the sender WebView so Vim mode can block or |
| 295 | // exit locally. The per-WebView token is a reuse/staleness guard, not a broad | 295 | // exit locally. The per-WebView token is a reuse/staleness guard, not a broad | ||
| 296 | // authorization boundary for page privileges. | 296 | // authorization boundary for page privileges. | ||
| 297 | func (r *MessageRouter) handleAllowlistedBridgeMessage(senderWV *WebView, msg Message) bool { | 297 | func (r *MessageRouter) handleAllowlistedBridgeMessage(senderWV *WebView, msg Message) bool { | ||
M internal/infrastructure/webkit/webview.go +1 -1
| @@ -40,7 +40,7 @@ var _ port.PopupFeatureResolver = (*WebView)(nil) | |||||
| 40 | var _ port.OAuthCallbackCapable = (*WebView)(nil) | 40 | var _ port.OAuthCallbackCapable = (*WebView)(nil) | ||
| 41 | var _ port.PageScrollable = (*WebView)(nil) | 41 | var _ port.PageScrollable = (*WebView)(nil) | ||
| 42 | 42 | ||||
| 43 | - | var buildPageScrollFallbackJS = webutil.BuildScrollByJS | 43 | + | var buildPageScrollFallbackJS = webutil.BuildPageScrollByJS |
| 44 | var runPageScrollFallbackJS = func(ctx context.Context, wv *WebView, script string) { | 44 | var runPageScrollFallbackJS = func(ctx context.Context, wv *WebView, script string) { | ||
| 45 | wv.RunJavaScript(ctx, script) | 45 | wv.RunJavaScript(ctx, script) | ||
| 46 | } | 46 | } | ||
M internal/infrastructure/webutil/pagescroll.go +3 -3
| @@ -2,16 +2,16 @@ package webutil | |||||
| 2 | 2 | ||||
| 3 | import "fmt" | 3 | import "fmt" | ||
| 4 | 4 | ||||
| 5 | - | // BuildScrollByJS returns one immediate JavaScript scroll step for the given | 5 | + | // BuildPageScrollByJS returns one immediate JavaScript scroll step for the given |
| 6 | // CSS-pixel delta. It resolves the initial target under the viewport center, | 6 | // CSS-pixel delta. It resolves the initial target under the viewport center, | ||
| 7 | // walks toward scrollable ancestors that can move in the requested direction, | 7 | // walks toward scrollable ancestors that can move in the requested direction, | ||
| 8 | // and hands off to the document when a nested scroller reaches its boundary. | 8 | // and hands off to the document when a nested scroller reaches its boundary. | ||
| 9 | // Cross-origin frame contents remain best-effort because elementFromPoint can | 9 | // Cross-origin frame contents remain best-effort because elementFromPoint can | ||
| 10 | // only return the accessible frame element from the top-level document. | 10 | // only return the accessible frame element from the top-level document. | ||
| 11 | // | 11 | // | ||
| 12 | - | // The app-level Page Mode repeater owns held-key cadence; this helper performs | 12 | + | // The app-level Vim Mode repeater owns held-key cadence; this helper performs |
| 13 | // no requestAnimationFrame loop of its own. | 13 | // no requestAnimationFrame loop of its own. | ||
| 14 | - | func BuildScrollByJS(dx, dy int) string { | 14 | + | func BuildPageScrollByJS(dx, dy int) string { |
| 15 | return fmt.Sprintf(`(function(){ | 15 | return fmt.Sprintf(`(function(){ | ||
| 16 | var dx=%d,dy=%d,doc=document; | 16 | var dx=%d,dy=%d,doc=document; | ||
| 17 | function hasScrollableOverflow(value){ | 17 | function hasScrollableOverflow(value){ | ||
M internal/infrastructure/webutil/pagescroll_test.go +17 -17
| @@ -5,8 +5,8 @@ import ( | |||||
| 5 | "testing" | 5 | "testing" | ||
| 6 | ) | 6 | ) | ||
| 7 | 7 | ||||
| 8 | - | func TestBuildScrollByJS_TargetsViewportCenterInsteadOfActiveElement(t *testing.T) { | 8 | + | func TestBuildPageScrollByJS_TargetsViewportCenterInsteadOfActiveElement(t *testing.T) { |
| 9 | - | js := BuildScrollByJS(0, 80) | 9 | + | js := BuildPageScrollByJS(0, 80) |
| 10 | 10 | ||||
| 11 | if !strings.Contains(js, "doc.elementFromPoint(window.innerWidth/2,window.innerHeight/2)") { | 11 | if !strings.Contains(js, "doc.elementFromPoint(window.innerWidth/2,window.innerHeight/2)") { | ||
| 12 | t.Fatalf("page scroll must resolve its initial target from the viewport center, got: %s", js) | 12 | t.Fatalf("page scroll must resolve its initial target from the viewport center, got: %s", js) | ||
| @@ -16,7 +16,7 @@ func TestBuildScrollByJS_TargetsViewportCenterInsteadOfActiveElement(t *testing. | |||||
| 16 | } | 16 | } | ||
| 17 | } | 17 | } | ||
| 18 | 18 | ||||
| 19 | - | func TestBuildScrollByJS_ContainsRequiredOperations(t *testing.T) { | 19 | + | func TestBuildPageScrollByJS_ContainsRequiredOperations(t *testing.T) { |
| 20 | tests := []struct { | 20 | tests := []struct { | ||
| 21 | name string | 21 | name string | ||
| 22 | dx int | 22 | dx int | ||
| @@ -50,7 +50,7 @@ func TestBuildScrollByJS_ContainsRequiredOperations(t *testing.T) { | |||||
| 50 | 50 | ||||
| 51 | for _, tt := range tests { | 51 | for _, tt := range tests { | ||
| 52 | t.Run(tt.name, func(t *testing.T) { | 52 | t.Run(tt.name, func(t *testing.T) { | ||
| 53 | - | js := BuildScrollByJS(tt.dx, tt.dy) | 53 | + | js := BuildPageScrollByJS(tt.dx, tt.dy) |
| 54 | for _, check := range required { | 54 | for _, check := range required { | ||
| 55 | for _, snippet := range check.snippets { | 55 | for _, snippet := range check.snippets { | ||
| 56 | if !strings.Contains(js, snippet) { | 56 | if !strings.Contains(js, snippet) { | ||
| @@ -66,8 +66,8 @@ func TestBuildScrollByJS_ContainsRequiredOperations(t *testing.T) { | |||||
| 66 | } | 66 | } | ||
| 67 | } | 67 | } | ||
| 68 | 68 | ||||
| 69 | - | func TestBuildScrollByJS_SpecificDelta(t *testing.T) { | 69 | + | func TestBuildPageScrollByJS_SpecificDelta(t *testing.T) { |
| 70 | - | js := BuildScrollByJS(0, 80) | 70 | + | js := BuildPageScrollByJS(0, 80) |
| 71 | if !strings.Contains(js, "var dx=0,dy=80") { | 71 | if !strings.Contains(js, "var dx=0,dy=80") { | ||
| 72 | t.Errorf("expected dy=80 in JS variables, got: %s", js) | 72 | t.Errorf("expected dy=80 in JS variables, got: %s", js) | ||
| 73 | } | 73 | } | ||
| @@ -75,38 +75,38 @@ func TestBuildScrollByJS_SpecificDelta(t *testing.T) { | |||||
| 75 | t.Errorf("expected immediate fallback scroll step without requestAnimationFrame, got: %s", js) | 75 | t.Errorf("expected immediate fallback scroll step without requestAnimationFrame, got: %s", js) | ||
| 76 | } | 76 | } | ||
| 77 | 77 | ||||
| 78 | - | js = BuildScrollByJS(-80, 0) | 78 | + | js = BuildPageScrollByJS(-80, 0) |
| 79 | if !strings.Contains(js, "var dx=-80,dy=0") { | 79 | if !strings.Contains(js, "var dx=-80,dy=0") { | ||
| 80 | t.Errorf("expected dx=-80 in JS variables, got: %s", js) | 80 | t.Errorf("expected dx=-80 in JS variables, got: %s", js) | ||
| 81 | } | 81 | } | ||
| 82 | } | 82 | } | ||
| 83 | 83 | ||||
| 84 | - | func TestBuildScrollByJS_HorizontalAxisCheck(t *testing.T) { | 84 | + | func TestBuildPageScrollByJS_HorizontalAxisCheck(t *testing.T) { |
| 85 | - | js := BuildScrollByJS(-80, 0) | 85 | + | js := BuildPageScrollByJS(-80, 0) |
| 86 | if !strings.Contains(js, "clientWidth") || !strings.Contains(js, "scrollWidth") { | 86 | if !strings.Contains(js, "clientWidth") || !strings.Contains(js, "scrollWidth") { | ||
| 87 | t.Error("horizontal scroll must check scrollWidth/clientWidth") | 87 | t.Error("horizontal scroll must check scrollWidth/clientWidth") | ||
| 88 | } | 88 | } | ||
| 89 | } | 89 | } | ||
| 90 | 90 | ||||
| 91 | - | func TestBuildScrollByJS_VerticalAxisCheck(t *testing.T) { | 91 | + | func TestBuildPageScrollByJS_VerticalAxisCheck(t *testing.T) { |
| 92 | - | js := BuildScrollByJS(0, 80) | 92 | + | js := BuildPageScrollByJS(0, 80) |
| 93 | if !strings.Contains(js, "clientHeight") || !strings.Contains(js, "scrollHeight") { | 93 | if !strings.Contains(js, "clientHeight") || !strings.Contains(js, "scrollHeight") { | ||
| 94 | t.Error("vertical scroll must check scrollHeight/clientHeight") | 94 | t.Error("vertical scroll must check scrollHeight/clientHeight") | ||
| 95 | } | 95 | } | ||
| 96 | } | 96 | } | ||
| 97 | 97 | ||||
| 98 | - | func TestBuildScrollByJS_NegativeDeltaMagnitude(t *testing.T) { | 98 | + | func TestBuildPageScrollByJS_NegativeDeltaMagnitude(t *testing.T) { |
| 99 | - | js := BuildScrollByJS(0, -320) | 99 | + | js := BuildPageScrollByJS(0, -320) |
| 100 | if !strings.Contains(js, "var dx=0,dy=-320") { | 100 | if !strings.Contains(js, "var dx=0,dy=-320") { | ||
| 101 | t.Errorf("expected dy=-320 in JS variables, got: %s", js) | 101 | t.Errorf("expected dy=-320 in JS variables, got: %s", js) | ||
| 102 | } | 102 | } | ||
| 103 | } | 103 | } | ||
| 104 | 104 | ||||
| 105 | - | func TestBuildScrollByJS_SyntacticallyValid(t *testing.T) { | 105 | + | func TestBuildPageScrollByJS_SyntacticallyValid(t *testing.T) { |
| 106 | tests := []struct{ dx, dy int }{{0, 80}, {0, -320}, {-80, 0}, {80, 0}, {0, 0}} | 106 | tests := []struct{ dx, dy int }{{0, 80}, {0, -320}, {-80, 0}, {80, 0}, {0, 0}} | ||
| 107 | 107 | ||||
| 108 | for _, tt := range tests { | 108 | for _, tt := range tests { | ||
| 109 | - | js := BuildScrollByJS(tt.dx, tt.dy) | 109 | + | js := BuildPageScrollByJS(tt.dx, tt.dy) |
| 110 | 110 | ||||
| 111 | depth := 0 | 111 | depth := 0 | ||
| 112 | for _, ch := range js { | 112 | for _, ch := range js { | ||
| @@ -136,8 +136,8 @@ func TestBuildScrollByJS_SyntacticallyValid(t *testing.T) { | |||||
| 136 | } | 136 | } | ||
| 137 | } | 137 | } | ||
| 138 | 138 | ||||
| 139 | - | func TestBuildScrollByJS_ZeroDeltaSafe(t *testing.T) { | 139 | + | func TestBuildPageScrollByJS_ZeroDeltaSafe(t *testing.T) { |
| 140 | - | js := BuildScrollByJS(0, 0) | 140 | + | js := BuildPageScrollByJS(0, 0) |
| 141 | if !strings.Contains(js, "var dx=0,dy=0") { | 141 | if !strings.Contains(js, "var dx=0,dy=0") { | ||
| 142 | t.Errorf("expected zero deltas in JS variables, got: %s", js) | 142 | t.Errorf("expected zero deltas in JS variables, got: %s", js) | ||
| 143 | } | 143 | } | ||
M internal/ui/app.go +118 -116
| @@ -53,11 +53,11 @@ const ( | |||||
| 53 | floatingSessionIDDefault = "default" | 53 | floatingSessionIDDefault = "default" | ||
| 54 | floatingPaneVisibleClass = "floating-pane-visible" | 54 | floatingPaneVisibleClass = "floating-pane-visible" | ||
| 55 | 55 | ||||
| 56 | - | // pageModePulseInterval is the minimum interval between page mode pulses. | 56 | + | // vimModePulseInterval is the minimum interval between vim mode pulses. |
| 57 | - | // Held Page Mode scrolling now runs on its own smooth repeater, so the pulse | 57 | + | // Held Vim Mode scrolling now runs on its own smooth repeater, so the pulse |
| 58 | // is intentionally throttled well below scroll cadence to avoid GTK CSS churn | 58 | // is intentionally throttled well below scroll cadence to avoid GTK CSS churn | ||
| 59 | // becoming part of the perceived scroll jank. | 59 | // becoming part of the perceived scroll jank. | ||
| 60 | - | pageModePulseInterval = 120 * time.Millisecond | 60 | + | vimModePulseInterval = 120 * time.Millisecond |
| 61 | ) | 61 | ) | ||
| 62 | 62 | ||||
| 63 | func gtkApplicationFlags() gio.ApplicationFlags { | 63 | func gtkApplicationFlags() gio.ApplicationFlags { | ||
| @@ -123,7 +123,7 @@ type App struct { | |||||
| 123 | // Focus tracking stays app-global. | 123 | // Focus tracking stays app-global. | ||
| 124 | focusMgr *focus.Manager | 124 | focusMgr *focus.Manager | ||
| 125 | 125 | ||||
| 126 | - | pageModePolicyUC *usecase.PageModePolicyUseCase | 126 | + | vimModePolicyUC *usecase.VimModePolicyUseCase |
| 127 | pageEditableFocusByPane map[entity.PaneID]bool | 127 | pageEditableFocusByPane map[entity.PaneID]bool | ||
| 128 | 128 | ||||
| 129 | resizeModeBorderTarget layout.Widget | 129 | resizeModeBorderTarget layout.Widget | ||
| @@ -159,11 +159,11 @@ type App struct { | |||||
| 159 | // Accent picker for dead keys support | 159 | // Accent picker for dead keys support | ||
| 160 | accentFocusProvider port.FocusedInputProvider | 160 | accentFocusProvider port.FocusedInputProvider | ||
| 161 | 161 | ||||
| 162 | - | // Page mode pulse debounce - tracks last pulse time to skip rapid | 162 | + | // Vim mode pulse debounce - tracks last pulse time to skip rapid |
| 163 | - | // repeats during held-key scroll actions. Reset on page mode exit | 163 | + | // repeats during held-key scroll actions. Reset on vim mode exit |
| 164 | // via handleModeChange. | 164 | // via handleModeChange. | ||
| 165 | - | pageModePulseLastTime time.Time | 165 | + | vimModePulseLastTime time.Time |
| 166 | - | pageModePulseMu sync.Mutex | 166 | + | vimModePulseMu sync.Mutex |
| 167 | 167 | ||||
| 168 | // Deferred initialization - runs after first load_started to avoid blocking initial navigation | 168 | // Deferred initialization - runs after first load_started to avoid blocking initial navigation | ||
| 169 | deferredInitOnce sync.Once | 169 | deferredInitOnce sync.Once | ||
| @@ -216,7 +216,7 @@ func New(deps *Dependencies) (*App, error) { | |||||
| 216 | windowForTab: make(map[entity.TabID]*browserWindow), | 216 | windowForTab: make(map[entity.TabID]*browserWindow), | ||
| 217 | floatingSessions: make(map[floatingSessionKey]*floatingWorkspaceSession), | 217 | floatingSessions: make(map[floatingSessionKey]*floatingWorkspaceSession), | ||
| 218 | browserWindows: make(map[string]*browserWindow), | 218 | browserWindows: make(map[string]*browserWindow), | ||
| 219 | - | pageModePolicyUC: usecase.NewPageModePolicyUseCase(), | 219 | + | vimModePolicyUC: usecase.NewVimModePolicyUseCase(), |
| 220 | pageEditableFocusByPane: make(map[entity.PaneID]bool), | 220 | pageEditableFocusByPane: make(map[entity.PaneID]bool), | ||
| 221 | dispatchOnMainThread: func(label string, fn func()) syncdispatch.SyncDispatchResult { | 221 | dispatchOnMainThread: func(label string, fn func()) syncdispatch.SyncDispatchResult { | ||
| 222 | if fn != nil { | 222 | if fn != nil { | ||
| @@ -835,7 +835,7 @@ func (a *App) wireSessionManagerShortcut() { | |||||
| 835 | } | 835 | } | ||
| 836 | bw.sessionManager.Toggle(ctx) | 836 | bw.sessionManager.Toggle(ctx) | ||
| 837 | if bw.sessionManager.IsVisible() { | 837 | if bw.sessionManager.IsVisible() { | ||
| 838 | - | a.handlePageModeFocusTrigger(ctx, bw, usecase.PageModePolicyTriggerOverlayFocus) | 838 | + | a.handleVimModeFocusTrigger(ctx, bw, usecase.VimModePolicyTriggerOverlayFocus) |
| 839 | } | 839 | } | ||
| 840 | return nil | 840 | return nil | ||
| 841 | }) | 841 | }) | ||
| @@ -1088,9 +1088,9 @@ func (a *App) initBrowserWindowInput(ctx context.Context, bw *browserWindow) { | |||||
| 1088 | } | 1088 | } | ||
| 1089 | return a.dispatchBrowserWindowAction(actionCtx, bw, action) | 1089 | return a.dispatchBrowserWindowAction(actionCtx, bw, action) | ||
| 1090 | }) | 1090 | }) | ||
| 1091 | - | bw.keyboardHandler.SetOnPageScrollLifecycle(func(scrollCtx context.Context, action input.Action, phase input.PageScrollPhase) error { | 1091 | + | bw.keyboardHandler.SetOnVimScrollLifecycle(func(scrollCtx context.Context, action input.Action, phase input.VimScrollPhase) error { |
| 1092 | a.activateBrowserWindow(bw) | 1092 | a.activateBrowserWindow(bw) | ||
| 1093 | - | return a.kbDispatcher.DispatchPageScrollLifecycle(scrollCtx, action, phase) | 1093 | + | return a.kbDispatcher.DispatchVimScrollLifecycle(scrollCtx, action, phase) |
| 1094 | }) | 1094 | }) | ||
| 1095 | bw.keyboardHandler.SetOnEscape(func(escapeCtx context.Context) bool { | 1095 | bw.keyboardHandler.SetOnEscape(func(escapeCtx context.Context) bool { | ||
| 1096 | a.activateBrowserWindow(bw) | 1096 | a.activateBrowserWindow(bw) | ||
| @@ -1126,8 +1126,8 @@ func (a *App) initBrowserWindowInput(ctx context.Context, bw *browserWindow) { | |||||
| 1126 | 1126 | ||||
| 1127 | return input.RouteHandleShortcuts | 1127 | return input.RouteHandleShortcuts | ||
| 1128 | }) | 1128 | }) | ||
| 1129 | - | bw.keyboardHandler.SetPageModeActivationPassthrough(func() bool { | 1129 | + | bw.keyboardHandler.SetVimModeActivationPassthrough(func() bool { |
| 1130 | - | return a.shouldBypassPageModeActivation(bw) | 1130 | + | return a.shouldBypassVimModeActivation(bw) |
| 1131 | }) | 1131 | }) | ||
| 1132 | bw.keyboardHandler.SetAccentHandler(a) | 1132 | bw.keyboardHandler.SetAccentHandler(a) | ||
| 1133 | bw.keyboardHandler.AttachTo(bw.mainWindow.Window()) | 1133 | bw.keyboardHandler.AttachTo(bw.mainWindow.Window()) | ||
| @@ -1369,7 +1369,7 @@ func (a *App) initOmniboxConfig(ctx context.Context) { | |||||
| 1369 | a.accentFocusProvider.SetFocusedInput(a.deps.NewGTKEntryTarget(entry)) | 1369 | a.accentFocusProvider.SetFocusedInput(a.deps.NewGTKEntryTarget(entry)) | ||
| 1370 | } | 1370 | } | ||
| 1371 | } | 1371 | } | ||
| 1372 | - | a.handlePageModeFocusTrigger(ctx, a.lastFocusedBrowserWindow(), usecase.PageModePolicyTriggerOmniboxFocus) | 1372 | + | a.handleVimModeFocusTrigger(ctx, a.lastFocusedBrowserWindow(), usecase.VimModePolicyTriggerOmniboxFocus) |
| 1373 | }, | 1373 | }, | ||
| 1374 | OnFocusOut: func() { | 1374 | OnFocusOut: func() { | ||
| 1375 | // When omnibox loses focus, set WebView as the focused input | 1375 | // When omnibox loses focus, set WebView as the focused input | ||
| @@ -1408,7 +1408,7 @@ func (a *App) initFindBarConfig(ctx context.Context) { | |||||
| 1408 | a.accentFocusProvider.SetFocusedInput(a.deps.NewGTKEntryTarget(entry)) | 1408 | a.accentFocusProvider.SetFocusedInput(a.deps.NewGTKEntryTarget(entry)) | ||
| 1409 | } | 1409 | } | ||
| 1410 | } | 1410 | } | ||
| 1411 | - | a.handlePageModeFocusTrigger(ctx, a.lastFocusedBrowserWindow(), usecase.PageModePolicyTriggerFindBarFocus) | 1411 | + | a.handleVimModeFocusTrigger(ctx, a.lastFocusedBrowserWindow(), usecase.VimModePolicyTriggerFindBarFocus) |
| 1412 | }, | 1412 | }, | ||
| 1413 | OnFocusOut: func() { | 1413 | OnFocusOut: func() { | ||
| 1414 | // When find bar loses focus, set WebView as the focused input | 1414 | // When find bar loses focus, set WebView as the focused input | ||
| @@ -1433,7 +1433,7 @@ func (a *App) ToggleSessionManager(ctx context.Context) { | |||||
| 1433 | } | 1433 | } | ||
| 1434 | bw.sessionManager.Toggle(ctx) | 1434 | bw.sessionManager.Toggle(ctx) | ||
| 1435 | if bw.sessionManager.IsVisible() { | 1435 | if bw.sessionManager.IsVisible() { | ||
| 1436 | - | a.handlePageModeFocusTrigger(ctx, bw, usecase.PageModePolicyTriggerOverlayFocus) | 1436 | + | a.handleVimModeFocusTrigger(ctx, bw, usecase.VimModePolicyTriggerOverlayFocus) |
| 1437 | } | 1437 | } | ||
| 1438 | } | 1438 | } | ||
| 1439 | 1439 | ||||
| @@ -1533,7 +1533,7 @@ func (a *App) HandleMovePaneToTab(ctx context.Context) error { | |||||
| 1533 | 1533 | ||||
| 1534 | a.attachTabPickerToActivePane() | 1534 | a.attachTabPickerToActivePane() | ||
| 1535 | bw.tabPicker.Show(ctx, items) | 1535 | bw.tabPicker.Show(ctx, items) | ||
| 1536 | - | a.handlePageModeFocusTrigger(ctx, bw, usecase.PageModePolicyTriggerOverlayFocus) | 1536 | + | a.handleVimModeFocusTrigger(ctx, bw, usecase.VimModePolicyTriggerOverlayFocus) |
| 1537 | return nil | 1537 | return nil | ||
| 1538 | } | 1538 | } | ||
| 1539 | 1539 | ||||
| @@ -3108,7 +3108,7 @@ func (a *App) initTabCoordinator(ctx context.Context) { | |||||
| 3108 | // tracks ActiveTabID and PreviousActiveTabID on bw.tabs. No manual bw state needed. | 3108 | // tracks ActiveTabID and PreviousActiveTabID on bw.tabs. No manual bw state needed. | ||
| 3109 | if bw := a.browserWindowForTabTarget(target); bw != nil { | 3109 | if bw := a.browserWindowForTabTarget(target); bw != nil { | ||
| 3110 | a.activateBrowserWindow(bw) | 3110 | a.activateBrowserWindow(bw) | ||
| 3111 | - | a.handlePageModeTabSwitch(ctx, bw) | 3111 | + | a.handleVimModeTabSwitch(ctx, bw) |
| 3112 | } | 3112 | } | ||
| 3113 | a.switchWorkspaceView(ctx, tab.ID) | 3113 | a.switchWorkspaceView(ctx, tab.ID) | ||
| 3114 | }) | 3114 | }) | ||
| @@ -3393,8 +3393,8 @@ func (a *App) withFocusedTabTarget(ctx context.Context, action string, ensure bo | |||||
| 3393 | 3393 | ||||
| 3394 | func (a *App) wireKeyboardActions() { | 3394 | func (a *App) wireKeyboardActions() { | ||
| 3395 | a.kbDispatcher.SetOnQuit(a.Quit) | 3395 | a.kbDispatcher.SetOnQuit(a.Quit) | ||
| 3396 | - | a.kbDispatcher.SetOnPageModePulse(func(ctx context.Context, fast bool) { | 3396 | + | a.kbDispatcher.SetOnVimModePulse(func(ctx context.Context, fast bool) { |
| 3397 | - | a.triggerPageModePulse(ctx, fast) | 3397 | + | a.triggerVimModePulse(ctx, fast) |
| 3398 | }) | 3398 | }) | ||
| 3399 | a.kbDispatcher.SetOnFindOpen(func(ctx context.Context) error { | 3399 | a.kbDispatcher.SetOnFindOpen(func(ctx context.Context) error { | ||
| 3400 | a.ToggleFindBar(ctx) | 3400 | a.ToggleFindBar(ctx) | ||
| @@ -3727,14 +3727,14 @@ func (a *App) updateWindowTitleFromActivePane(tabID entity.TabID) { | |||||
| 3727 | a.updateWindowTitle(title, a.ownerOrLastFocusedBrowserWindow(tabID, "")) | 3727 | a.updateWindowTitle(title, a.ownerOrLastFocusedBrowserWindow(tabID, "")) | ||
| 3728 | } | 3728 | } | ||
| 3729 | 3729 | ||||
| 3730 | - | func (a *App) pageModePolicy() *usecase.PageModePolicyUseCase { | 3730 | + | func (a *App) vimModePolicy() *usecase.VimModePolicyUseCase { |
| 3731 | if a == nil { | 3731 | if a == nil { | ||
| 3732 | - | return usecase.NewPageModePolicyUseCase() | 3732 | + | return usecase.NewVimModePolicyUseCase() |
| 3733 | } | 3733 | } | ||
| 3734 | - | if a.pageModePolicyUC == nil { | 3734 | + | if a.vimModePolicyUC == nil { |
| 3735 | - | a.pageModePolicyUC = usecase.NewPageModePolicyUseCase() | 3735 | + | a.vimModePolicyUC = usecase.NewVimModePolicyUseCase() |
| 3736 | } | 3736 | } | ||
| 3737 | - | return a.pageModePolicyUC | 3737 | + | return a.vimModePolicyUC |
| 3738 | } | 3738 | } | ||
| 3739 | 3739 | ||||
| 3740 | func (a *App) ensurePageEditableFocusMap() { | 3740 | func (a *App) ensurePageEditableFocusMap() { | ||
| @@ -3766,51 +3766,51 @@ func (a *App) clearPageEditableFocusState(paneID entity.PaneID) { | |||||
| 3766 | a.setPageEditableFocused(paneID, false) | 3766 | a.setPageEditableFocused(paneID, false) | ||
| 3767 | } | 3767 | } | ||
| 3768 | 3768 | ||||
| 3769 | - | func (a *App) pageModeActiveForBrowserWindow(bw *browserWindow) bool { | 3769 | + | func (a *App) vimModeActiveForBrowserWindow(bw *browserWindow) bool { |
| 3770 | - | return bw != nil && bw.keyboardHandler != nil && bw.keyboardHandler.Mode() == input.ModePage | 3770 | + | return bw != nil && bw.keyboardHandler != nil && bw.keyboardHandler.Mode() == input.ModeVim |
| 3771 | } | 3771 | } | ||
| 3772 | 3772 | ||||
| 3773 | - | func (a *App) applyPageModePolicyTransition(_ context.Context, bw *browserWindow, transition usecase.PageModePolicyTransition) { | 3773 | + | func (a *App) applyVimModePolicyTransition(_ context.Context, bw *browserWindow, transition usecase.VimModePolicyTransition) { |
| 3774 | - | if transition != usecase.PageModePolicyTransitionExit || bw == nil || bw.keyboardHandler == nil { | 3774 | + | if transition != usecase.VimModePolicyTransitionExit || bw == nil || bw.keyboardHandler == nil { |
| 3775 | return | 3775 | return | ||
| 3776 | } | 3776 | } | ||
| 3777 | - | if bw.keyboardHandler.Mode() != input.ModePage { | 3777 | + | if bw.keyboardHandler.Mode() != input.ModeVim { |
| 3778 | return | 3778 | return | ||
| 3779 | } | 3779 | } | ||
| 3780 | bw.keyboardHandler.ExitMode() | 3780 | bw.keyboardHandler.ExitMode() | ||
| 3781 | } | 3781 | } | ||
| 3782 | 3782 | ||||
| 3783 | - | func (a *App) handlePageModeFocusTrigger(ctx context.Context, bw *browserWindow, trigger usecase.PageModePolicyTrigger) { | 3783 | + | func (a *App) handleVimModeFocusTrigger(ctx context.Context, bw *browserWindow, trigger usecase.VimModePolicyTrigger) { |
| 3784 | if bw == nil { | 3784 | if bw == nil { | ||
| 3785 | return | 3785 | return | ||
| 3786 | } | 3786 | } | ||
| 3787 | - | transition := a.pageModePolicy().Evaluate(usecase.PageModePolicyInput{ | 3787 | + | transition := a.vimModePolicy().Evaluate(usecase.VimModePolicyInput{ |
| 3788 | - | Trigger: trigger, | 3788 | + | Trigger: trigger, |
| 3789 | - | PageModeActive: a.pageModeActiveForBrowserWindow(bw), | 3789 | + | VimModeActive: a.vimModeActiveForBrowserWindow(bw), |
| 3790 | }) | 3790 | }) | ||
| 3791 | - | a.applyPageModePolicyTransition(ctx, bw, transition) | 3791 | + | a.applyVimModePolicyTransition(ctx, bw, transition) |
| 3792 | } | 3792 | } | ||
| 3793 | 3793 | ||||
| 3794 | - | func (a *App) handlePageModeTabSwitch(_ context.Context, bw *browserWindow) { | 3794 | + | func (a *App) handleVimModeTabSwitch(_ context.Context, bw *browserWindow) { |
| 3795 | - | if bw == nil || bw.tabs == nil || !a.pageModeActiveForBrowserWindow(bw) { | 3795 | + | if bw == nil || bw.tabs == nil || !a.vimModeActiveForBrowserWindow(bw) { |
| 3796 | return | 3796 | return | ||
| 3797 | } | 3797 | } | ||
| 3798 | - | transition := a.pageModePolicy().Evaluate(usecase.PageModePolicyInput{ | 3798 | + | transition := a.vimModePolicy().Evaluate(usecase.VimModePolicyInput{ |
| 3799 | - | Trigger: usecase.PageModePolicyTriggerContextChanged, | 3799 | + | Trigger: usecase.VimModePolicyTriggerContextChanged, |
| 3800 | - | PageModeActive: true, | 3800 | + | VimModeActive: true, |
| 3801 | PreserveOnContextChange: false, | 3801 | PreserveOnContextChange: false, | ||
| 3802 | }) | 3802 | }) | ||
| 3803 | - | if transition != usecase.PageModePolicyTransitionExit { | 3803 | + | if transition != usecase.VimModePolicyTransitionExit { |
| 3804 | return | 3804 | return | ||
| 3805 | } | 3805 | } | ||
| 3806 | if prevTabID := bw.tabs.PreviousActiveTabID; prevTabID != "" { | 3806 | if prevTabID := bw.tabs.PreviousActiveTabID; prevTabID != "" { | ||
| 3807 | if prevView := a.workspaceViews[prevTabID]; prevView != nil { | 3807 | if prevView := a.workspaceViews[prevTabID]; prevView != nil { | ||
| 3808 | - | if pv := prevView.GetPaneView(bw.pageModePaneID); pv != nil { | 3808 | + | if pv := prevView.GetPaneView(bw.vimModePaneID); pv != nil { |
| 3809 | - | pv.SetPageMode(false) | 3809 | + | pv.SetVimMode(false) |
| 3810 | } | 3810 | } | ||
| 3811 | } | 3811 | } | ||
| 3812 | } | 3812 | } | ||
| 3813 | - | bw.pageModePaneID = "" | 3813 | + | bw.vimModePaneID = "" |
| 3814 | bw.keyboardHandler.ExitMode() | 3814 | bw.keyboardHandler.ExitMode() | ||
| 3815 | } | 3815 | } | ||
| 3816 | 3816 | ||||
| @@ -3826,25 +3826,25 @@ func (a *App) handlePageEditableFocusChanged(ctx context.Context, paneID entity. | |||||
| 3826 | } | 3826 | } | ||
| 3827 | ws := a.activeWorkspaceForBrowserWindow(bw) | 3827 | ws := a.activeWorkspaceForBrowserWindow(bw) | ||
| 3828 | activeContext := ws != nil && ws.ActivePaneID == paneID && a.lastFocusedBrowserWindow() == bw | 3828 | activeContext := ws != nil && ws.ActivePaneID == paneID && a.lastFocusedBrowserWindow() == bw | ||
| 3829 | - | transition := a.pageModePolicy().Evaluate(usecase.PageModePolicyInput{ | 3829 | + | transition := a.vimModePolicy().Evaluate(usecase.VimModePolicyInput{ |
| 3830 | - | Trigger: usecase.PageModePolicyTriggerPageEditableFocusChanged, | 3830 | + | Trigger: usecase.VimModePolicyTriggerPageEditableFocusChanged, |
| 3831 | - | PageModeActive: a.pageModeActiveForBrowserWindow(bw), | 3831 | + | VimModeActive: a.vimModeActiveForBrowserWindow(bw), |
| 3832 | PageEditableFocused: editable, | 3832 | PageEditableFocused: editable, | ||
| 3833 | EventInActiveContext: activeContext, | 3833 | EventInActiveContext: activeContext, | ||
| 3834 | }) | 3834 | }) | ||
| 3835 | - | a.applyPageModePolicyTransition(ctx, bw, transition) | 3835 | + | a.applyVimModePolicyTransition(ctx, bw, transition) |
| 3836 | } | 3836 | } | ||
| 3837 | 3837 | ||||
| 3838 | - | func (a *App) shouldBypassPageModeActivation(bw *browserWindow) bool { | 3838 | + | func (a *App) shouldBypassVimModeActivation(bw *browserWindow) bool { |
| 3839 | ws := a.activeWorkspaceForBrowserWindow(bw) | 3839 | ws := a.activeWorkspaceForBrowserWindow(bw) | ||
| 3840 | if ws == nil { | 3840 | if ws == nil { | ||
| 3841 | return false | 3841 | return false | ||
| 3842 | } | 3842 | } | ||
| 3843 | - | transition := a.pageModePolicy().Evaluate(usecase.PageModePolicyInput{ | 3843 | + | transition := a.vimModePolicy().Evaluate(usecase.VimModePolicyInput{ |
| 3844 | - | Trigger: usecase.PageModePolicyTriggerActivationAttempt, | 3844 | + | Trigger: usecase.VimModePolicyTriggerActivationAttempt, |
| 3845 | PageEditableFocused: a.pageEditableFocused(ws.ActivePaneID), | 3845 | PageEditableFocused: a.pageEditableFocused(ws.ActivePaneID), | ||
| 3846 | }) | 3846 | }) | ||
| 3847 | - | return transition == usecase.PageModePolicyTransitionBlockActivation | 3847 | + | return transition == usecase.VimModePolicyTransitionBlockActivation |
| 3848 | } | 3848 | } | ||
| 3849 | 3849 | ||||
| 3850 | // handleModeChange is called when the input mode changes for a specific browser window. | 3850 | // handleModeChange is called when the input mode changes for a specific browser window. | ||
| @@ -3860,13 +3860,13 @@ func (a *App) handleModeChange(ctx context.Context, bw *browserWindow, from, to | |||||
| 3860 | a.applyResizeModeBorder(ctx, a.activeWorkspace()) | 3860 | a.applyResizeModeBorder(ctx, a.activeWorkspace()) | ||
| 3861 | } | 3861 | } | ||
| 3862 | 3862 | ||||
| 3863 | - | // Handle pane-local Page Mode visual ownership. | 3863 | + | // Handle pane-local Vim Mode visual ownership. |
| 3864 | - | // Entering Page Mode accents the active pane; leaving removes the accent. | 3864 | + | // Entering Vim Mode accents the active pane; leaving removes the accent. |
| 3865 | - | a.handlePageModeOwnership(ctx, bw, to, from) | 3865 | + | a.handleVimModeOwnership(ctx, bw, to, from) |
| 3866 | 3866 | ||||
| 3867 | // Update global border overlay visibility based on mode. | 3867 | // Update global border overlay visibility based on mode. | ||
| 3868 | // Note: resize mode border is handled per-pane (stack container), not via global overlay. | 3868 | // Note: resize mode border is handled per-pane (stack container), not via global overlay. | ||
| 3869 | - | // Page mode explicitly skips the global border overlay. | 3869 | + | // Vim mode explicitly skips the global border overlay. |
| 3870 | if bw != nil && bw.borderMgr != nil { | 3870 | if bw != nil && bw.borderMgr != nil { | ||
| 3871 | bw.borderMgr.OnModeChange(ctx, from, to) | 3871 | bw.borderMgr.OnModeChange(ctx, from, to) | ||
| 3872 | } | 3872 | } | ||
| @@ -3875,23 +3875,23 @@ func (a *App) handleModeChange(ctx context.Context, bw *browserWindow, from, to | |||||
| 3875 | a.updateModeIndicatorToaster(ctx, bw, to) | 3875 | a.updateModeIndicatorToaster(ctx, bw, to) | ||
| 3876 | } | 3876 | } | ||
| 3877 | 3877 | ||||
| 3878 | - | // transferPageModeOwnershipToPane transfers the pane-local Page Mode accent | 3878 | + | // transferVimModeOwnershipToPane transfers the pane-local Vim Mode accent |
| 3879 | // from the current owning pane to another pane in the same browser window. | 3879 | // from the current owning pane to another pane in the same browser window. | ||
| 3880 | - | // The transfer only happens while that window is in Page Mode. | 3880 | + | // The transfer only happens while that window is in Vim Mode. |
| 3881 | - | func (a *App) transferPageModeOwnershipToPane(ctx context.Context, bw *browserWindow, newPaneID entity.PaneID) { | 3881 | + | func (a *App) transferVimModeOwnershipToPane(ctx context.Context, bw *browserWindow, newPaneID entity.PaneID) { |
| 3882 | if bw == nil { | 3882 | if bw == nil { | ||
| 3883 | return | 3883 | return | ||
| 3884 | } | 3884 | } | ||
| 3885 | 3885 | ||||
| 3886 | - | // Only transfer if page mode is currently active and the owner is changing. | 3886 | + | // Only transfer if vim mode is currently active and the owner is changing. |
| 3887 | - | if bw.pageModePaneID == "" || bw.pageModePaneID == newPaneID { | 3887 | + | if bw.vimModePaneID == "" || bw.vimModePaneID == newPaneID { |
| 3888 | return | 3888 | return | ||
| 3889 | } | 3889 | } | ||
| 3890 | 3890 | ||||
| 3891 | - | // Check if this window is actually in page mode. | 3891 | + | // Check if this window is actually in vim mode. |
| 3892 | - | if bw.keyboardHandler == nil || bw.keyboardHandler.Mode() != input.ModePage { | 3892 | + | if bw.keyboardHandler == nil || bw.keyboardHandler.Mode() != input.ModeVim { |
| 3893 | - | // Page mode not active on this window; just clear stale ownership. | 3893 | + | // Vim mode not active on this window; just clear stale ownership. |
| 3894 | - | bw.pageModePaneID = "" | 3894 | + | bw.vimModePaneID = "" |
| 3895 | return | 3895 | return | ||
| 3896 | } | 3896 | } | ||
| 3897 | 3897 | ||||
| @@ -3900,39 +3900,39 @@ func (a *App) transferPageModeOwnershipToPane(ctx context.Context, bw *browserWi | |||||
| 3900 | return | 3900 | return | ||
| 3901 | } | 3901 | } | ||
| 3902 | 3902 | ||||
| 3903 | - | transition := a.pageModePolicy().Evaluate(usecase.PageModePolicyInput{ | 3903 | + | transition := a.vimModePolicy().Evaluate(usecase.VimModePolicyInput{ |
| 3904 | - | Trigger: usecase.PageModePolicyTriggerContextChanged, | 3904 | + | Trigger: usecase.VimModePolicyTriggerContextChanged, |
| 3905 | - | PageModeActive: true, | 3905 | + | VimModeActive: true, |
| 3906 | PreserveOnContextChange: !a.pageEditableFocused(newPaneID), | 3906 | PreserveOnContextChange: !a.pageEditableFocused(newPaneID), | ||
| 3907 | }) | 3907 | }) | ||
| 3908 | - | if transition == usecase.PageModePolicyTransitionExit { | 3908 | + | if transition == usecase.VimModePolicyTransitionExit { |
| 3909 | - | a.applyPageModePolicyTransition(ctx, bw, transition) | 3909 | + | a.applyVimModePolicyTransition(ctx, bw, transition) |
| 3910 | return | 3910 | return | ||
| 3911 | } | 3911 | } | ||
| 3912 | 3912 | ||||
| 3913 | - | oldPaneID := bw.pageModePaneID | 3913 | + | oldPaneID := bw.vimModePaneID |
| 3914 | 3914 | ||||
| 3915 | // Deactivate the old owning pane. | 3915 | // Deactivate the old owning pane. | ||
| 3916 | if oldPV := wsView.GetPaneView(oldPaneID); oldPV != nil { | 3916 | if oldPV := wsView.GetPaneView(oldPaneID); oldPV != nil { | ||
| 3917 | - | oldPV.SetPageMode(false) | 3917 | + | oldPV.SetVimMode(false) |
| 3918 | } | 3918 | } | ||
| 3919 | 3919 | ||||
| 3920 | // Activate the new pane. | 3920 | // Activate the new pane. | ||
| 3921 | if newPV := wsView.GetPaneView(newPaneID); newPV != nil { | 3921 | if newPV := wsView.GetPaneView(newPaneID); newPV != nil { | ||
| 3922 | - | newPV.SetPageMode(true) | 3922 | + | newPV.SetVimMode(true) |
| 3923 | } | 3923 | } | ||
| 3924 | - | bw.pageModePaneID = newPaneID | 3924 | + | bw.vimModePaneID = newPaneID |
| 3925 | 3925 | ||||
| 3926 | logging.FromContext(ctx).Debug(). | 3926 | logging.FromContext(ctx).Debug(). | ||
| 3927 | Str("window_id", bw.id). | 3927 | Str("window_id", bw.id). | ||
| 3928 | Str("old_pane_id", string(oldPaneID)). | 3928 | Str("old_pane_id", string(oldPaneID)). | ||
| 3929 | Str("new_pane_id", string(newPaneID)). | 3929 | Str("new_pane_id", string(newPaneID)). | ||
| 3930 | - | Msg("page mode ownership transferred") | 3930 | + | Msg("vim mode ownership transferred") |
| 3931 | } | 3931 | } | ||
| 3932 | 3932 | ||||
| 3933 | - | // handlePageModeOwnership manages the pane-local Page Mode accent and pulse | 3933 | + | // handleVimModeOwnership manages the pane-local Vim Mode accent and pulse |
| 3934 | // owner for a specific browser window when input modes change. | 3934 | // owner for a specific browser window when input modes change. | ||
| 3935 | - | func (a *App) handlePageModeOwnership(ctx context.Context, bw *browserWindow, to, from input.Mode) { | 3935 | + | func (a *App) handleVimModeOwnership(ctx context.Context, bw *browserWindow, to, from input.Mode) { |
| 3936 | if bw == nil { | 3936 | if bw == nil { | ||
| 3937 | return | 3937 | return | ||
| 3938 | } | 3938 | } | ||
| @@ -3941,96 +3941,96 @@ func (a *App) handlePageModeOwnership(ctx context.Context, bw *browserWindow, to | |||||
| 3941 | wsView := a.activeWorkspaceViewForBrowserWindow(bw) | 3941 | wsView := a.activeWorkspaceViewForBrowserWindow(bw) | ||
| 3942 | if ws == nil || wsView == nil { | 3942 | if ws == nil || wsView == nil { | ||
| 3943 | // If there's no workspace yet (startup), just track the intent. | 3943 | // If there's no workspace yet (startup), just track the intent. | ||
| 3944 | - | if to != input.ModePage && from == input.ModePage { | 3944 | + | if to != input.ModeVim && from == input.ModeVim { |
| 3945 | - | bw.pageModePaneID = "" | 3945 | + | bw.vimModePaneID = "" |
| 3946 | } | 3946 | } | ||
| 3947 | return | 3947 | return | ||
| 3948 | } | 3948 | } | ||
| 3949 | 3949 | ||||
| 3950 | - | if to == input.ModePage && from != input.ModePage { | 3950 | + | if to == input.ModeVim && from != input.ModeVim { |
| 3951 | - | // Entering page mode: activate on the current active pane of this window. | 3951 | + | // Entering vim mode: activate on the current active pane of this window. |
| 3952 | paneID := ws.ActivePaneID | 3952 | paneID := ws.ActivePaneID | ||
| 3953 | if pv := wsView.GetPaneView(paneID); pv != nil { | 3953 | if pv := wsView.GetPaneView(paneID); pv != nil { | ||
| 3954 | - | pv.SetPageMode(true) | 3954 | + | pv.SetVimMode(true) |
| 3955 | - | bw.pageModePaneID = paneID | 3955 | + | bw.vimModePaneID = paneID |
| 3956 | logging.FromContext(ctx).Debug(). | 3956 | logging.FromContext(ctx).Debug(). | ||
| 3957 | Str("window_id", bw.id). | 3957 | Str("window_id", bw.id). | ||
| 3958 | Str("pane_id", string(paneID)). | 3958 | Str("pane_id", string(paneID)). | ||
| 3959 | - | Msg("page mode activated on pane") | 3959 | + | Msg("vim mode activated on pane") |
| 3960 | } | 3960 | } | ||
| 3961 | - | } else if from == input.ModePage && to != input.ModePage { | 3961 | + | } else if from == input.ModeVim && to != input.ModeVim { |
| 3962 | - | // Leaving Page Mode: deactivate the pane that owns the visual accent. | 3962 | + | // Leaving Vim Mode: deactivate the pane that owns the visual accent. |
| 3963 | - | a.clearPageModeOwnership(ctx, bw) | 3963 | + | a.clearVimModeOwnership(ctx, bw) |
| 3964 | } | 3964 | } | ||
| 3965 | } | 3965 | } | ||
| 3966 | 3966 | ||||
| 3967 | - | // triggerPageModePulse triggers a pane-local page mode pulse on the | 3967 | + | // triggerVimModePulse triggers a pane-local vim mode pulse on the |
| 3968 | // last-focused browser window's owning pane. This is called by the | 3968 | // last-focused browser window's owning pane. This is called by the | ||
| 3969 | // keyboard dispatcher after a page scroll action. The bw used is the | 3969 | // keyboard dispatcher after a page scroll action. The bw used is the | ||
| 3970 | // window that was active when the action was dispatched (via | 3970 | // window that was active when the action was dispatched (via | ||
| 3971 | // dispatchBrowserWindowAction which calls activateBrowserWindow first). | 3971 | // dispatchBrowserWindowAction which calls activateBrowserWindow first). | ||
| 3972 | - | // If no pane is currently in page mode, the pulse is a no-op. | 3972 | + | // If no pane is currently in vim mode, the pulse is a no-op. |
| 3973 | // | 3973 | // | ||
| 3974 | - | // Pulses are debounced at pageModePulseInterval to prevent excessive GTK CSS | 3974 | + | // Pulses are debounced at vimModePulseInterval to prevent excessive GTK CSS |
| 3975 | // class churn during held-key repeats. Smooth scroll cadence is owned by the | 3975 | // class churn during held-key repeats. Smooth scroll cadence is owned by the | ||
| 3976 | - | // Page Mode repeater and backend scroll path; pulse feedback is deliberately | 3976 | + | // Vim Mode repeater and backend scroll path; pulse feedback is deliberately |
| 3977 | // slower so indicator/overlay animation work does not become part of the | 3977 | // slower so indicator/overlay animation work does not become part of the | ||
| 3978 | // scrolling critical path. | 3978 | // scrolling critical path. | ||
| 3979 | - | func (a *App) triggerPageModePulse(_ context.Context, fast bool) { | 3979 | + | func (a *App) triggerVimModePulse(_ context.Context, fast bool) { |
| 3980 | // Debounce: skip if called again too soon during a continuous hold. | 3980 | // Debounce: skip if called again too soon during a continuous hold. | ||
| 3981 | // We still serialize the timestamp update so repeated dispatcher calls do | 3981 | // We still serialize the timestamp update so repeated dispatcher calls do | ||
| 3982 | // not restart CSS animations at scroll cadence. | 3982 | // not restart CSS animations at scroll cadence. | ||
| 3983 | - | a.pageModePulseMu.Lock() | 3983 | + | a.vimModePulseMu.Lock() |
| 3984 | - | since := time.Since(a.pageModePulseLastTime) | 3984 | + | since := time.Since(a.vimModePulseLastTime) |
| 3985 | - | if since < pageModePulseInterval { | 3985 | + | if since < vimModePulseInterval { |
| 3986 | - | a.pageModePulseMu.Unlock() | 3986 | + | a.vimModePulseMu.Unlock() |
| 3987 | return | 3987 | return | ||
| 3988 | } | 3988 | } | ||
| 3989 | - | a.pageModePulseLastTime = time.Now() | 3989 | + | a.vimModePulseLastTime = time.Now() |
| 3990 | - | a.pageModePulseMu.Unlock() | 3990 | + | a.vimModePulseMu.Unlock() |
| 3991 | 3991 | ||||
| 3992 | bw := a.lastFocusedBrowserWindow() | 3992 | bw := a.lastFocusedBrowserWindow() | ||
| 3993 | - | if bw == nil || bw.pageModePaneID == "" { | 3993 | + | if bw == nil || bw.vimModePaneID == "" { |
| 3994 | return | 3994 | return | ||
| 3995 | } | 3995 | } | ||
| 3996 | wsView := a.activeWorkspaceViewForBrowserWindow(bw) | 3996 | wsView := a.activeWorkspaceViewForBrowserWindow(bw) | ||
| 3997 | if wsView == nil { | 3997 | if wsView == nil { | ||
| 3998 | return | 3998 | return | ||
| 3999 | } | 3999 | } | ||
| 4000 | - | if pv := wsView.GetPaneView(bw.pageModePaneID); pv != nil { | 4000 | + | if pv := wsView.GetPaneView(bw.vimModePaneID); pv != nil { |
| 4001 | if fast { | 4001 | if fast { | ||
| 4002 | - | pv.TriggerPageModePulseFast() | 4002 | + | pv.TriggerVimModePulseFast() |
| 4003 | } else { | 4003 | } else { | ||
| 4004 | - | pv.TriggerPageModePulse() | 4004 | + | pv.TriggerVimModePulse() |
| 4005 | } | 4005 | } | ||
| 4006 | } | 4006 | } | ||
| 4007 | } | 4007 | } | ||
| 4008 | 4008 | ||||
| 4009 | - | // clearPageModeOwnership deactivates page mode on the owning pane for a | 4009 | + | // clearVimModeOwnership deactivates vim mode on the owning pane for a |
| 4010 | // specific browser window and resets its tracked pane ID. | 4010 | // specific browser window and resets its tracked pane ID. | ||
| 4011 | // Also resets the pulse debounce timer so the first pulse on re-entry | 4011 | // Also resets the pulse debounce timer so the first pulse on re-entry | ||
| 4012 | // is never skipped. | 4012 | // is never skipped. | ||
| 4013 | - | func (a *App) clearPageModeOwnership(ctx context.Context, bw *browserWindow) { | 4013 | + | func (a *App) clearVimModeOwnership(ctx context.Context, bw *browserWindow) { |
| 4014 | - | if bw == nil || bw.pageModePaneID == "" { | 4014 | + | if bw == nil || bw.vimModePaneID == "" { |
| 4015 | return | 4015 | return | ||
| 4016 | } | 4016 | } | ||
| 4017 | 4017 | ||||
| 4018 | wsView := a.activeWorkspaceViewForBrowserWindow(bw) | 4018 | wsView := a.activeWorkspaceViewForBrowserWindow(bw) | ||
| 4019 | if wsView != nil { | 4019 | if wsView != nil { | ||
| 4020 | - | if pv := wsView.GetPaneView(bw.pageModePaneID); pv != nil { | 4020 | + | if pv := wsView.GetPaneView(bw.vimModePaneID); pv != nil { |
| 4021 | - | pv.SetPageMode(false) | 4021 | + | pv.SetVimMode(false) |
| 4022 | logging.FromContext(ctx).Debug(). | 4022 | logging.FromContext(ctx).Debug(). | ||
| 4023 | Str("window_id", bw.id). | 4023 | Str("window_id", bw.id). | ||
| 4024 | - | Str("pane_id", string(bw.pageModePaneID)). | 4024 | + | Str("pane_id", string(bw.vimModePaneID)). |
| 4025 | - | Msg("page mode deactivated on pane") | 4025 | + | Msg("vim mode deactivated on pane") |
| 4026 | } | 4026 | } | ||
| 4027 | } | 4027 | } | ||
| 4028 | - | bw.pageModePaneID = "" | 4028 | + | bw.vimModePaneID = "" |
| 4029 | 4029 | ||||
| 4030 | // Reset debounce timer so re-entry pulses are never mis-skipped. | 4030 | // Reset debounce timer so re-entry pulses are never mis-skipped. | ||
| 4031 | - | a.pageModePulseMu.Lock() | 4031 | + | a.vimModePulseMu.Lock() |
| 4032 | - | a.pageModePulseLastTime = time.Time{} | 4032 | + | a.vimModePulseLastTime = time.Time{} |
| 4033 | - | a.pageModePulseMu.Unlock() | 4033 | + | a.vimModePulseMu.Unlock() |
| 4034 | } | 4034 | } | ||
| 4035 | 4035 | ||||
| 4036 | // updateModeIndicatorToaster reconciles one window's mode toaster with its | 4036 | // updateModeIndicatorToaster reconciles one window's mode toaster with its | ||
| @@ -4057,8 +4057,10 @@ func (a *App) updateModeIndicatorToaster(ctx context.Context, bw *browserWindow, | |||||
| 4057 | // getModeToastClass returns the CSS class for the given mode's toast styling. | 4057 | // getModeToastClass returns the CSS class for the given mode's toast styling. | ||
| 4058 | func getModeToastClass(mode input.Mode) string { | 4058 | func getModeToastClass(mode input.Mode) string { | ||
| 4059 | switch mode { | 4059 | switch mode { | ||
| 4060 | - | case input.ModePane, input.ModePage: | 4060 | + | case input.ModePane: |
| 4061 | return "toast-pane-mode" | 4061 | return "toast-pane-mode" | ||
| 4062 | + | case input.ModeVim: | |||
| 4063 | + | return "toast-vim-mode" | |||
| 4062 | case input.ModeTab: | 4064 | case input.ModeTab: | ||
| 4063 | return "toast-tab-mode" | 4065 | return "toast-tab-mode" | ||
| 4064 | case input.ModeSession: | 4066 | case input.ModeSession: | ||
| @@ -4169,7 +4171,7 @@ func (a *App) createWorkspaceViewWithoutAttach(ctx context.Context, tab *entity. | |||||
| 4169 | wsView.SetOnActivePaneChanged(func(paneID entity.PaneID) { | 4171 | wsView.SetOnActivePaneChanged(func(paneID entity.PaneID) { | ||
| 4170 | a.contentCoord.SyncWebViewViewport(syncCtx, paneID, "workspace-pane-activated") | 4172 | a.contentCoord.SyncWebViewViewport(syncCtx, paneID, "workspace-pane-activated") | ||
| 4171 | if bw := a.browserWindowForTab(tab.ID); bw != nil { | 4173 | if bw := a.browserWindowForTab(tab.ID); bw != nil { | ||
| 4172 | - | a.transferPageModeOwnershipToPane(ctx, bw, paneID) | 4174 | + | a.transferVimModeOwnershipToPane(ctx, bw, paneID) |
| 4173 | } | 4175 | } | ||
| 4174 | }) | 4176 | }) | ||
| 4175 | } | 4177 | } | ||
R internal/ui/app_page_mode_test.go → internal/ui/app_vim_mode_test.go +309 -309 renamed, 58% similar
| @@ -23,7 +23,7 @@ import ( | |||||
| 23 | // --------------------------------------------------------------------------- | 23 | // --------------------------------------------------------------------------- | ||
| 24 | // Mock-based PaneView and WorkspaceView creation helpers | 24 | // Mock-based PaneView and WorkspaceView creation helpers | ||
| 25 | // | 25 | // | ||
| 26 | - | // These are used by pageModeTestFixture to build a minimal test environment | 26 | + | // These are used by vimModeTestFixture to build a minimal test environment |
| 27 | // without any GTK infrastructure. | 27 | // without any GTK infrastructure. | ||
| 28 | // --------------------------------------------------------------------------- | 28 | // --------------------------------------------------------------------------- | ||
| 29 | 29 | ||||
| @@ -99,7 +99,7 @@ func newLoadingSkeletonMocks( | |||||
| 99 | } | 99 | } | ||
| 100 | 100 | ||||
| 101 | // createTestPaneView builds a mock-backed PaneView. Returns the PaneView and | 101 | // createTestPaneView builds a mock-backed PaneView. Returns the PaneView and | ||
| 102 | - | // its overlay mock so subsequent page-mode indicator / pulse expectations can | 102 | + | // its overlay mock so subsequent vim-mode indicator / pulse expectations can |
| 103 | // be set up by the caller. | 103 | // be set up by the caller. | ||
| 104 | func createTestPaneView( | 104 | func createTestPaneView( | ||
| 105 | t *testing.T, | 105 | t *testing.T, | ||
| @@ -138,20 +138,20 @@ func createTestPaneView( | |||||
| 138 | return pv, overlay | 138 | return pv, overlay | ||
| 139 | } | 139 | } | ||
| 140 | 140 | ||||
| 141 | - | // enterPageMode marks the owning pane with the pane-local visual accent. | 141 | + | // enterVimMode marks the owning pane with the pane-local visual accent. |
| 142 | - | func enterPageMode( | 142 | + | func enterVimMode( |
| 143 | t *testing.T, | 143 | t *testing.T, | ||
| 144 | app *App, | 144 | app *App, | ||
| 145 | overlay *mocks.MockOverlayWidget, | 145 | overlay *mocks.MockOverlayWidget, | ||
| 146 | bw *browserWindow, | 146 | bw *browserWindow, | ||
| 147 | ) { | 147 | ) { | ||
| 148 | t.Helper() | 148 | t.Helper() | ||
| 149 | - | overlay.EXPECT().AddCssClass("page-mode-active").Once() | 149 | + | overlay.EXPECT().AddCssClass("vim-mode-active").Once() |
| 150 | - | app.handlePageModeOwnership(context.Background(), bw, input.ModePage, input.ModeNormal) | 150 | + | app.handleVimModeOwnership(context.Background(), bw, input.ModeVim, input.ModeNormal) |
| 151 | } | 151 | } | ||
| 152 | 152 | ||||
| 153 | - | func expectPageModeAccentHidden(overlay *mocks.MockOverlayWidget) { | 153 | + | func expectVimModeAccentHidden(overlay *mocks.MockOverlayWidget) { |
| 154 | - | overlay.EXPECT().RemoveCssClass("page-mode-active").Once() | 154 | + | overlay.EXPECT().RemoveCssClass("vim-mode-active").Once() |
| 155 | } | 155 | } | ||
| 156 | 156 | ||||
| 157 | // setupWorkspaceViewMocks creates a mock-backed WorkspaceView. | 157 | // setupWorkspaceViewMocks creates a mock-backed WorkspaceView. | ||
| @@ -181,22 +181,22 @@ func setupWorkspaceViewMocks( | |||||
| 181 | return wv | 181 | return wv | ||
| 182 | } | 182 | } | ||
| 183 | 183 | ||||
| 184 | - | // newKeyboardHandlerInPageMode creates a KeyboardHandler whose Mode() returns | 184 | + | // newKeyboardHandlerInVimMode creates a KeyboardHandler whose Mode() returns |
| 185 | - | // ModePage — no GTK widgets required. | 185 | + | // ModeVim — no GTK widgets required. |
| 186 | - | func newKeyboardHandlerInPageMode(t *testing.T) *input.KeyboardHandler { | 186 | + | func newKeyboardHandlerInVimMode(t *testing.T) *input.KeyboardHandler { |
| 187 | t.Helper() | 187 | t.Helper() | ||
| 188 | kh := input.NewKeyboardHandler( | 188 | kh := input.NewKeyboardHandler( | ||
| 189 | context.Background(), | 189 | context.Background(), | ||
| 190 | &entity.WorkspaceConfig{}, | 190 | &entity.WorkspaceConfig{}, | ||
| 191 | &entity.SessionConfig{}, | 191 | &entity.SessionConfig{}, | ||
| 192 | ) | 192 | ) | ||
| 193 | - | kh.EnterPageMode() | 193 | + | kh.EnterVimMode() |
| 194 | return kh | 194 | return kh | ||
| 195 | } | 195 | } | ||
| 196 | 196 | ||||
| 197 | - | func bindPageModeKeyboardHandler(t *testing.T, app *App, bw *browserWindow) *input.KeyboardHandler { | 197 | + | func bindVimModeKeyboardHandler(t *testing.T, app *App, bw *browserWindow) *input.KeyboardHandler { |
| 198 | t.Helper() | 198 | t.Helper() | ||
| 199 | - | kh := newKeyboardHandlerInPageMode(t) | 199 | + | kh := newKeyboardHandlerInVimMode(t) |
| 200 | kh.SetOnModeChange(func(from, to input.Mode) { | 200 | kh.SetOnModeChange(func(from, to input.Mode) { | ||
| 201 | app.handleModeChange(context.Background(), bw, from, to) | 201 | app.handleModeChange(context.Background(), bw, from, to) | ||
| 202 | }) | 202 | }) | ||
| @@ -205,13 +205,13 @@ func bindPageModeKeyboardHandler(t *testing.T, app *App, bw *browserWindow) *inp | |||||
| 205 | } | 205 | } | ||
| 206 | 206 | ||||
| 207 | // --------------------------------------------------------------------------- | 207 | // --------------------------------------------------------------------------- | ||
| 208 | - | // pageModeTestFixture — complete, minimal App + workspace environment | 208 | + | // vimModeTestFixture — complete, minimal App + workspace environment |
| 209 | // | 209 | // | ||
| 210 | // Provides two browser windows, each with a two-pane workspace. The active | 210 | // Provides two browser windows, each with a two-pane workspace. The active | ||
| 211 | // pane on both workspaces starts at "pane-a". | 211 | // pane on both workspaces starts at "pane-a". | ||
| 212 | // --------------------------------------------------------------------------- | 212 | // --------------------------------------------------------------------------- | ||
| 213 | 213 | ||||
| 214 | - | type pageModeTestFixture struct { | 214 | + | type vimModeTestFixture struct { |
| 215 | app *App | 215 | app *App | ||
| 216 | bw1 *browserWindow | 216 | bw1 *browserWindow | ||
| 217 | bw2 *browserWindow | 217 | bw2 *browserWindow | ||
| @@ -227,7 +227,7 @@ type pageModeTestFixture struct { | |||||
| 227 | overlay2B *mocks.MockOverlayWidget | 227 | overlay2B *mocks.MockOverlayWidget | ||
| 228 | } | 228 | } | ||
| 229 | 229 | ||||
| 230 | - | func newPageModeTestFixture(t *testing.T) *pageModeTestFixture { | 230 | + | func newVimModeTestFixture(t *testing.T) *vimModeTestFixture { |
| 231 | t.Helper() | 231 | t.Helper() | ||
| 232 | factory := mocks.NewMockWidgetFactory(t) | 232 | factory := mocks.NewMockWidgetFactory(t) | ||
| 233 | 233 | ||||
| @@ -297,7 +297,7 @@ func newPageModeTestFixture(t *testing.T) *pageModeTestFixture { | |||||
| 297 | lastFocusedWindowID: "win-1", | 297 | lastFocusedWindowID: "win-1", | ||
| 298 | } | 298 | } | ||
| 299 | 299 | ||||
| 300 | - | return &pageModeTestFixture{ | 300 | + | return &vimModeTestFixture{ |
| 301 | app: app, bw1: bw1, bw2: bw2, | 301 | app: app, bw1: bw1, bw2: bw2, | ||
| 302 | pv1A: pv1A, pv1B: pv1B, pv2A: pv2A, pv2B: pv2B, | 302 | pv1A: pv1A, pv1B: pv1B, pv2A: pv2A, pv2B: pv2B, | ||
| 303 | factory: factory, | 303 | factory: factory, | ||
| @@ -306,9 +306,9 @@ func newPageModeTestFixture(t *testing.T) *pageModeTestFixture { | |||||
| 306 | } | 306 | } | ||
| 307 | } | 307 | } | ||
| 308 | 308 | ||||
| 309 | - | func newSingleWindowPageModeFixture(t *testing.T) *pageModeTestFixture { | 309 | + | func newSingleWindowVimModeFixture(t *testing.T) *vimModeTestFixture { |
| 310 | t.Helper() | 310 | t.Helper() | ||
| 311 | - | f := newPageModeTestFixture(t) | 311 | + | f := newVimModeTestFixture(t) |
| 312 | delete(f.app.browserWindows, "win-2") | 312 | delete(f.app.browserWindows, "win-2") | ||
| 313 | delete(f.app.workspaceViews, entity.TabID("tab-2")) | 313 | delete(f.app.workspaceViews, entity.TabID("tab-2")) | ||
| 314 | f.bw2 = nil | 314 | f.bw2 = nil | ||
| @@ -320,127 +320,127 @@ func newSingleWindowPageModeFixture(t *testing.T) *pageModeTestFixture { | |||||
| 320 | } | 320 | } | ||
| 321 | 321 | ||||
| 322 | // ============================================================================ | 322 | // ============================================================================ | ||
| 323 | - | // 1. Entering Page mode marks only the active pane | 323 | + | // 1. Entering Vim mode marks only the active pane |
| 324 | // ============================================================================ | 324 | // ============================================================================ | ||
| 325 | 325 | ||||
| 326 | - | func TestPageMode_Enter_SetsOwnershipOnActivePane(t *testing.T) { | 326 | + | func TestVimMode_Enter_SetsOwnershipOnActivePane(t *testing.T) { |
| 327 | - | f := newPageModeTestFixture(t) | 327 | + | f := newVimModeTestFixture(t) |
| 328 | 328 | ||||
| 329 | - | enterPageMode(t, f.app, f.overlay1A, f.bw1) | 329 | + | enterVimMode(t, f.app, f.overlay1A, f.bw1) |
| 330 | 330 | ||||
| 331 | - | assert.Equal(t, entity.PaneID("pane-a"), f.bw1.pageModePaneID, | 331 | + | assert.Equal(t, entity.PaneID("pane-a"), f.bw1.vimModePaneID, |
| 332 | - | "entering page mode sets ownership on the active pane") | 332 | + | "entering vim mode sets ownership on the active pane") |
| 333 | - | assert.True(t, f.pv1A.IsPageMode(), | 333 | + | assert.True(t, f.pv1A.IsVimMode(), |
| 334 | - | "the active pane view enters page mode") | 334 | + | "the active pane view enters vim mode") |
| 335 | - | assert.False(t, f.pv1B.IsPageMode(), | 335 | + | assert.False(t, f.pv1B.IsVimMode(), |
| 336 | - | "the inactive pane is NOT in page mode") | 336 | + | "the inactive pane is NOT in vim mode") |
| 337 | - | assert.Empty(t, f.bw2.pageModePaneID, | 337 | + | assert.Empty(t, f.bw2.vimModePaneID, |
| 338 | "second window is untouched") | 338 | "second window is untouched") | ||
| 339 | - | assert.False(t, f.pv2A.IsPageMode(), | 339 | + | assert.False(t, f.pv2A.IsVimMode(), |
| 340 | - | "second window active pane is NOT in page mode") | 340 | + | "second window active pane is NOT in vim mode") |
| 341 | } | 341 | } | ||
| 342 | 342 | ||||
| 343 | - | func TestPageMode_Enter_MarksCorrectWindow(t *testing.T) { | 343 | + | func TestVimMode_Enter_MarksCorrectWindow(t *testing.T) { |
| 344 | - | f := newPageModeTestFixture(t) | 344 | + | f := newVimModeTestFixture(t) |
| 345 | 345 | ||||
| 346 | - | enterPageMode(t, f.app, f.overlay2A, f.bw2) | 346 | + | enterVimMode(t, f.app, f.overlay2A, f.bw2) |
| 347 | 347 | ||||
| 348 | - | assert.Equal(t, entity.PaneID("pane-a"), f.bw2.pageModePaneID) | 348 | + | assert.Equal(t, entity.PaneID("pane-a"), f.bw2.vimModePaneID) |
| 349 | - | assert.True(t, f.pv2A.IsPageMode()) | 349 | + | assert.True(t, f.pv2A.IsVimMode()) |
| 350 | - | assert.Empty(t, f.bw1.pageModePaneID) | 350 | + | assert.Empty(t, f.bw1.vimModePaneID) |
| 351 | - | assert.False(t, f.pv1A.IsPageMode()) | 351 | + | assert.False(t, f.pv1A.IsVimMode()) |
| 352 | } | 352 | } | ||
| 353 | 353 | ||||
| 354 | // ============================================================================ | 354 | // ============================================================================ | ||
| 355 | - | // 2. Leaving Page mode clears ownership | 355 | + | // 2. Leaving Vim mode clears ownership |
| 356 | // ============================================================================ | 356 | // ============================================================================ | ||
| 357 | 357 | ||||
| 358 | - | func TestPageMode_Leave_ClearsOwnershipFromOwningPane(t *testing.T) { | 358 | + | func TestVimMode_Leave_ClearsOwnershipFromOwningPane(t *testing.T) { |
| 359 | - | f := newPageModeTestFixture(t) | 359 | + | f := newVimModeTestFixture(t) |
| 360 | 360 | ||||
| 361 | // Enter first | 361 | // Enter first | ||
| 362 | - | enterPageMode(t, f.app, f.overlay1A, f.bw1) | 362 | + | enterVimMode(t, f.app, f.overlay1A, f.bw1) |
| 363 | - | require.Equal(t, entity.PaneID("pane-a"), f.bw1.pageModePaneID) | 363 | + | require.Equal(t, entity.PaneID("pane-a"), f.bw1.vimModePaneID) |
| 364 | 364 | ||||
| 365 | - | // Leave — SetPageMode(false) hides indicator and removes the CSS class | 365 | + | // Leave — SetVimMode(false) hides indicator and removes the CSS class |
| 366 | - | expectPageModeAccentHidden(f.overlay1A) | 366 | + | expectVimModeAccentHidden(f.overlay1A) |
| 367 | - | f.app.handlePageModeOwnership(context.Background(), f.bw1, input.ModeNormal, input.ModePage) | 367 | + | f.app.handleVimModeOwnership(context.Background(), f.bw1, input.ModeNormal, input.ModeVim) |
| 368 | 368 | ||||
| 369 | - | assert.Empty(t, f.bw1.pageModePaneID, | 369 | + | assert.Empty(t, f.bw1.vimModePaneID, |
| 370 | - | "leaving page mode clears ownership field") | 370 | + | "leaving vim mode clears ownership field") |
| 371 | - | assert.False(t, f.pv1A.IsPageMode(), | 371 | + | assert.False(t, f.pv1A.IsVimMode(), |
| 372 | - | "the previously owning pane exits page mode") | 372 | + | "the previously owning pane exits vim mode") |
| 373 | } | 373 | } | ||
| 374 | 374 | ||||
| 375 | // ============================================================================ | 375 | // ============================================================================ | ||
| 376 | // 3. Transfer within the same window | 376 | // 3. Transfer within the same window | ||
| 377 | // ============================================================================ | 377 | // ============================================================================ | ||
| 378 | 378 | ||||
| 379 | - | func TestPageMode_Transfer_MovesOwnershipToNewPane(t *testing.T) { | 379 | + | func TestVimMode_Transfer_MovesOwnershipToNewPane(t *testing.T) { |
| 380 | - | f := newPageModeTestFixture(t) | 380 | + | f := newVimModeTestFixture(t) |
| 381 | 381 | ||||
| 382 | - | // Enter page mode on bw1 pane-a — save label for hide expectation | 382 | + | // Enter vim mode on bw1 pane-a — save label for hide expectation |
| 383 | - | enterPageMode(t, f.app, f.overlay1A, f.bw1) | 383 | + | enterVimMode(t, f.app, f.overlay1A, f.bw1) |
| 384 | - | require.Equal(t, entity.PaneID("pane-a"), f.bw1.pageModePaneID) | 384 | + | require.Equal(t, entity.PaneID("pane-a"), f.bw1.vimModePaneID) |
| 385 | 385 | ||||
| 386 | - | // Give bw1 a keyboard handler in page mode so transfer performs the switch | 386 | + | // Give bw1 a keyboard handler in vim mode so transfer performs the switch |
| 387 | - | f.bw1.keyboardHandler = newKeyboardHandlerInPageMode(t) | 387 | + | f.bw1.keyboardHandler = newKeyboardHandlerInVimMode(t) |
| 388 | 388 | ||||
| 389 | - | // transferPageModeOwnershipToPane calls SetPageMode(false) on the old | 389 | + | // transferVimModeOwnershipToPane calls SetVimMode(false) on the old |
| 390 | - | // pane before SetPageMode(true) on the new pane. Set up both. | 390 | + | // pane before SetVimMode(true) on the new pane. Set up both. |
| 391 | - | expectPageModeAccentHidden(f.overlay1A) | 391 | + | expectVimModeAccentHidden(f.overlay1A) |
| 392 | 392 | ||||
| 393 | // Set up the pane-local accent for pane-b. | 393 | // Set up the pane-local accent for pane-b. | ||
| 394 | - | f.overlay1B.EXPECT().AddCssClass("page-mode-active").Once() | 394 | + | f.overlay1B.EXPECT().AddCssClass("vim-mode-active").Once() |
| 395 | 395 | ||||
| 396 | // Transfer ownership to pane-b | 396 | // Transfer ownership to pane-b | ||
| 397 | - | f.app.transferPageModeOwnershipToPane(context.Background(), f.bw1, entity.PaneID("pane-b")) | 397 | + | f.app.transferVimModeOwnershipToPane(context.Background(), f.bw1, entity.PaneID("pane-b")) |
| 398 | 398 | ||||
| 399 | - | assert.Equal(t, entity.PaneID("pane-b"), f.bw1.pageModePaneID, | 399 | + | assert.Equal(t, entity.PaneID("pane-b"), f.bw1.vimModePaneID, |
| 400 | "ownership transferred to new pane") | 400 | "ownership transferred to new pane") | ||
| 401 | - | assert.False(t, f.pv1A.IsPageMode(), | 401 | + | assert.False(t, f.pv1A.IsVimMode(), |
| 402 | "previous owning pane deactivated") | 402 | "previous owning pane deactivated") | ||
| 403 | - | assert.True(t, f.pv1B.IsPageMode(), | 403 | + | assert.True(t, f.pv1B.IsVimMode(), |
| 404 | - | "new pane is now in page mode") | 404 | + | "new pane is now in vim mode") |
| 405 | } | 405 | } | ||
| 406 | 406 | ||||
| 407 | - | func TestPageMode_Transfer_NoPageModeClearsStale(t *testing.T) { | 407 | + | func TestVimMode_Transfer_NoVimModeClearsStale(t *testing.T) { |
| 408 | - | f := newPageModeTestFixture(t) | 408 | + | f := newVimModeTestFixture(t) |
| 409 | 409 | ||||
| 410 | - | f.bw1.pageModePaneID = entity.PaneID("pane-a") | 410 | + | f.bw1.vimModePaneID = entity.PaneID("pane-a") |
| 411 | f.bw1.keyboardHandler = input.NewKeyboardHandler( | 411 | f.bw1.keyboardHandler = input.NewKeyboardHandler( | ||
| 412 | context.Background(), | 412 | context.Background(), | ||
| 413 | &entity.WorkspaceConfig{}, | 413 | &entity.WorkspaceConfig{}, | ||
| 414 | &entity.SessionConfig{}, | 414 | &entity.SessionConfig{}, | ||
| 415 | ) | 415 | ) | ||
| 416 | - | // No EnterPageMode — handler stays in ModeNormal. | 416 | + | // No EnterVimMode — handler stays in ModeNormal. |
| 417 | 417 | ||||
| 418 | - | f.app.transferPageModeOwnershipToPane(context.Background(), f.bw1, entity.PaneID("pane-b")) | 418 | + | f.app.transferVimModeOwnershipToPane(context.Background(), f.bw1, entity.PaneID("pane-b")) |
| 419 | 419 | ||||
| 420 | - | assert.Empty(t, f.bw1.pageModePaneID, | 420 | + | assert.Empty(t, f.bw1.vimModePaneID, |
| 421 | - | "stale ownership cleared when window is not in page mode") | 421 | + | "stale ownership cleared when window is not in vim mode") |
| 422 | - | assert.False(t, f.pv1A.IsPageMode()) | 422 | + | assert.False(t, f.pv1A.IsVimMode()) |
| 423 | - | assert.False(t, f.pv1B.IsPageMode()) | 423 | + | assert.False(t, f.pv1B.IsVimMode()) |
| 424 | } | 424 | } | ||
| 425 | 425 | ||||
| 426 | - | func TestPageMode_Transfer_NilBwDoesNotCrash(t *testing.T) { | 426 | + | func TestVimMode_Transfer_NilBwDoesNotCrash(t *testing.T) { |
| 427 | app := &App{} | 427 | app := &App{} | ||
| 428 | - | app.transferPageModeOwnershipToPane(context.Background(), nil, entity.PaneID("pane-1")) | 428 | + | app.transferVimModeOwnershipToPane(context.Background(), nil, entity.PaneID("pane-1")) |
| 429 | } | 429 | } | ||
| 430 | 430 | ||||
| 431 | - | func TestPageMode_Transfer_SamePaneIsNoop(t *testing.T) { | 431 | + | func TestVimMode_Transfer_SamePaneIsNoop(t *testing.T) { |
| 432 | app := &App{} | 432 | app := &App{} | ||
| 433 | paneID := entity.PaneID("pane-a") | 433 | paneID := entity.PaneID("pane-a") | ||
| 434 | - | bw := &browserWindow{pageModePaneID: paneID} | 434 | + | bw := &browserWindow{vimModePaneID: paneID} |
| 435 | - | app.transferPageModeOwnershipToPane(context.Background(), bw, paneID) | 435 | + | app.transferVimModeOwnershipToPane(context.Background(), bw, paneID) |
| 436 | - | assert.Equal(t, paneID, bw.pageModePaneID) | 436 | + | assert.Equal(t, paneID, bw.vimModePaneID) |
| 437 | } | 437 | } | ||
| 438 | 438 | ||||
| 439 | - | func TestPageMode_Transfer_EmptyOwnershipIsNoop(t *testing.T) { | 439 | + | func TestVimMode_Transfer_EmptyOwnershipIsNoop(t *testing.T) { |
| 440 | app := &App{} | 440 | app := &App{} | ||
| 441 | bw := &browserWindow{} | 441 | bw := &browserWindow{} | ||
| 442 | - | app.transferPageModeOwnershipToPane(context.Background(), bw, entity.PaneID("pane-a")) | 442 | + | app.transferVimModeOwnershipToPane(context.Background(), bw, entity.PaneID("pane-a")) |
| 443 | - | assert.Empty(t, bw.pageModePaneID) | 443 | + | assert.Empty(t, bw.vimModePaneID) |
| 444 | } | 444 | } | ||
| 445 | 445 | ||||
| 446 | // ============================================================================ | 446 | // ============================================================================ | ||
| @@ -448,52 +448,52 @@ func TestPageMode_Transfer_EmptyOwnershipIsNoop(t *testing.T) { | |||||
| 448 | // ============================================================================ | 448 | // ============================================================================ | ||
| 449 | 449 | ||||
| 450 | func setUpNormalPulse(overlay *mocks.MockOverlayWidget) { | 450 | func setUpNormalPulse(overlay *mocks.MockOverlayWidget) { | ||
| 451 | - | overlay.EXPECT().RemoveCssClass("page-mode-pulse").Once() | 451 | + | overlay.EXPECT().RemoveCssClass("vim-mode-pulse").Once() |
| 452 | - | overlay.EXPECT().RemoveCssClass("page-mode-pulse-fast").Once() | 452 | + | overlay.EXPECT().RemoveCssClass("vim-mode-pulse-fast").Once() |
| 453 | - | overlay.EXPECT().RemoveCssClass("page-mode-pulse-cycle-a").Once() | 453 | + | overlay.EXPECT().RemoveCssClass("vim-mode-pulse-cycle-a").Once() |
| 454 | - | overlay.EXPECT().RemoveCssClass("page-mode-pulse-cycle-b").Once() | 454 | + | overlay.EXPECT().RemoveCssClass("vim-mode-pulse-cycle-b").Once() |
| 455 | - | overlay.EXPECT().AddCssClass("page-mode-pulse").Once() | 455 | + | overlay.EXPECT().AddCssClass("vim-mode-pulse").Once() |
| 456 | - | overlay.EXPECT().AddCssClass("page-mode-pulse-cycle-a").Once() | 456 | + | overlay.EXPECT().AddCssClass("vim-mode-pulse-cycle-a").Once() |
| 457 | } | 457 | } | ||
| 458 | 458 | ||||
| 459 | func setUpFastPulse(overlay *mocks.MockOverlayWidget) { | 459 | func setUpFastPulse(overlay *mocks.MockOverlayWidget) { | ||
| 460 | - | overlay.EXPECT().RemoveCssClass("page-mode-pulse").Once() | 460 | + | overlay.EXPECT().RemoveCssClass("vim-mode-pulse").Once() |
| 461 | - | overlay.EXPECT().RemoveCssClass("page-mode-pulse-fast").Once() | 461 | + | overlay.EXPECT().RemoveCssClass("vim-mode-pulse-fast").Once() |
| 462 | - | overlay.EXPECT().RemoveCssClass("page-mode-pulse-cycle-a").Once() | 462 | + | overlay.EXPECT().RemoveCssClass("vim-mode-pulse-cycle-a").Once() |
| 463 | - | overlay.EXPECT().RemoveCssClass("page-mode-pulse-cycle-b").Once() | 463 | + | overlay.EXPECT().RemoveCssClass("vim-mode-pulse-cycle-b").Once() |
| 464 | - | overlay.EXPECT().AddCssClass("page-mode-pulse-fast").Once() | 464 | + | overlay.EXPECT().AddCssClass("vim-mode-pulse-fast").Once() |
| 465 | - | overlay.EXPECT().AddCssClass("page-mode-pulse-cycle-a").Once() | 465 | + | overlay.EXPECT().AddCssClass("vim-mode-pulse-cycle-a").Once() |
| 466 | } | 466 | } | ||
| 467 | 467 | ||||
| 468 | - | func TestPageMode_Pulse_NormalTriggersOnOwningPane(t *testing.T) { | 468 | + | func TestVimMode_Pulse_NormalTriggersOnOwningPane(t *testing.T) { |
| 469 | - | f := newPageModeTestFixture(t) | 469 | + | f := newVimModeTestFixture(t) |
| 470 | 470 | ||||
| 471 | - | enterPageMode(t, f.app, f.overlay1A, f.bw1) | 471 | + | enterVimMode(t, f.app, f.overlay1A, f.bw1) |
| 472 | - | require.Equal(t, entity.PaneID("pane-a"), f.bw1.pageModePaneID) | 472 | + | require.Equal(t, entity.PaneID("pane-a"), f.bw1.vimModePaneID) |
| 473 | 473 | ||||
| 474 | // Pulse needs the indicator to exist — it was created lazily during | 474 | // Pulse needs the indicator to exist — it was created lazily during | ||
| 475 | - | // enterPageMode, but we also need to set up pulse expectations. | 475 | + | // enterVimMode, but we also need to set up pulse expectations. |
| 476 | - | // (enterPageMode created it, so we just set up pulse mocks.) | 476 | + | // (enterVimMode created it, so we just set up pulse mocks.) |
| 477 | setUpNormalPulse(f.overlay1A) | 477 | setUpNormalPulse(f.overlay1A) | ||
| 478 | 478 | ||||
| 479 | - | f.app.triggerPageModePulse(context.Background(), false) | 479 | + | f.app.triggerVimModePulse(context.Background(), false) |
| 480 | } | 480 | } | ||
| 481 | 481 | ||||
| 482 | - | func TestPageMode_Pulse_FastTriggersOnOwningPane(t *testing.T) { | 482 | + | func TestVimMode_Pulse_FastTriggersOnOwningPane(t *testing.T) { |
| 483 | - | f := newPageModeTestFixture(t) | 483 | + | f := newVimModeTestFixture(t) |
| 484 | 484 | ||||
| 485 | - | enterPageMode(t, f.app, f.overlay1A, f.bw1) | 485 | + | enterVimMode(t, f.app, f.overlay1A, f.bw1) |
| 486 | - | require.Equal(t, entity.PaneID("pane-a"), f.bw1.pageModePaneID) | 486 | + | require.Equal(t, entity.PaneID("pane-a"), f.bw1.vimModePaneID) |
| 487 | 487 | ||||
| 488 | setUpFastPulse(f.overlay1A) | 488 | setUpFastPulse(f.overlay1A) | ||
| 489 | 489 | ||||
| 490 | - | f.app.triggerPageModePulse(context.Background(), true) | 490 | + | f.app.triggerVimModePulse(context.Background(), true) |
| 491 | } | 491 | } | ||
| 492 | 492 | ||||
| 493 | - | func TestPageMode_Pulse_NoOwnerIsNoop(t *testing.T) { | 493 | + | func TestVimMode_Pulse_NoOwnerIsNoop(t *testing.T) { |
| 494 | - | f := newPageModeTestFixture(t) | 494 | + | f := newVimModeTestFixture(t) |
| 495 | - | f.app.triggerPageModePulse(context.Background(), false) | 495 | + | f.app.triggerVimModePulse(context.Background(), false) |
| 496 | - | f.app.triggerPageModePulse(context.Background(), true) | 496 | + | f.app.triggerVimModePulse(context.Background(), true) |
| 497 | // No pane has ownership — no pulse expectations needed. | 497 | // No pane has ownership — no pulse expectations needed. | ||
| 498 | } | 498 | } | ||
| 499 | 499 | ||||
| @@ -501,141 +501,141 @@ func TestPageMode_Pulse_NoOwnerIsNoop(t *testing.T) { | |||||
| 501 | // 5. Multi-window isolation | 501 | // 5. Multi-window isolation | ||
| 502 | // ============================================================================ | 502 | // ============================================================================ | ||
| 503 | 503 | ||||
| 504 | - | func TestPageMode_MultiWindow_TwoWindowsEnterLeave(t *testing.T) { | 504 | + | func TestVimMode_MultiWindow_TwoWindowsEnterLeave(t *testing.T) { |
| 505 | - | f := newPageModeTestFixture(t) | 505 | + | f := newVimModeTestFixture(t) |
| 506 | 506 | ||||
| 507 | - | enterPageMode(t, f.app, f.overlay1A, f.bw1) | 507 | + | enterVimMode(t, f.app, f.overlay1A, f.bw1) |
| 508 | - | enterPageMode(t, f.app, f.overlay2A, f.bw2) | 508 | + | enterVimMode(t, f.app, f.overlay2A, f.bw2) |
| 509 | 509 | ||||
| 510 | - | assert.Equal(t, entity.PaneID("pane-a"), f.bw1.pageModePaneID) | 510 | + | assert.Equal(t, entity.PaneID("pane-a"), f.bw1.vimModePaneID) |
| 511 | - | assert.Equal(t, entity.PaneID("pane-a"), f.bw2.pageModePaneID) | 511 | + | assert.Equal(t, entity.PaneID("pane-a"), f.bw2.vimModePaneID) |
| 512 | 512 | ||||
| 513 | // Leave bw1 only | 513 | // Leave bw1 only | ||
| 514 | - | expectPageModeAccentHidden(f.overlay1A) | 514 | + | expectVimModeAccentHidden(f.overlay1A) |
| 515 | - | f.app.handlePageModeOwnership(context.Background(), f.bw1, input.ModeNormal, input.ModePage) | 515 | + | f.app.handleVimModeOwnership(context.Background(), f.bw1, input.ModeNormal, input.ModeVim) |
| 516 | 516 | ||||
| 517 | - | assert.Empty(t, f.bw1.pageModePaneID) | 517 | + | assert.Empty(t, f.bw1.vimModePaneID) |
| 518 | - | assert.False(t, f.pv1A.IsPageMode()) | 518 | + | assert.False(t, f.pv1A.IsVimMode()) |
| 519 | 519 | ||||
| 520 | // bw2 unchanged | 520 | // bw2 unchanged | ||
| 521 | - | assert.Equal(t, entity.PaneID("pane-a"), f.bw2.pageModePaneID) | 521 | + | assert.Equal(t, entity.PaneID("pane-a"), f.bw2.vimModePaneID) |
| 522 | - | assert.True(t, f.pv2A.IsPageMode()) | 522 | + | assert.True(t, f.pv2A.IsVimMode()) |
| 523 | } | 523 | } | ||
| 524 | 524 | ||||
| 525 | - | func TestPageMode_MultiWindow_TransferIsolated(t *testing.T) { | 525 | + | func TestVimMode_MultiWindow_TransferIsolated(t *testing.T) { |
| 526 | - | f := newPageModeTestFixture(t) | 526 | + | f := newVimModeTestFixture(t) |
| 527 | 527 | ||||
| 528 | - | enterPageMode(t, f.app, f.overlay1A, f.bw1) | 528 | + | enterVimMode(t, f.app, f.overlay1A, f.bw1) |
| 529 | - | enterPageMode(t, f.app, f.overlay2A, f.bw2) | 529 | + | enterVimMode(t, f.app, f.overlay2A, f.bw2) |
| 530 | 530 | ||||
| 531 | - | f.bw1.keyboardHandler = newKeyboardHandlerInPageMode(t) | 531 | + | f.bw1.keyboardHandler = newKeyboardHandlerInVimMode(t) |
| 532 | 532 | ||||
| 533 | - | // transferPageModeOwnershipToPane calls SetPageMode(false) on old pane first | 533 | + | // transferVimModeOwnershipToPane calls SetVimMode(false) on old pane first |
| 534 | - | expectPageModeAccentHidden(f.overlay1A) | 534 | + | expectVimModeAccentHidden(f.overlay1A) |
| 535 | 535 | ||||
| 536 | // Transfer bw1 to pane-b. | 536 | // Transfer bw1 to pane-b. | ||
| 537 | - | f.overlay1B.EXPECT().AddCssClass("page-mode-active").Once() | 537 | + | f.overlay1B.EXPECT().AddCssClass("vim-mode-active").Once() |
| 538 | - | f.app.transferPageModeOwnershipToPane(context.Background(), f.bw1, entity.PaneID("pane-b")) | 538 | + | f.app.transferVimModeOwnershipToPane(context.Background(), f.bw1, entity.PaneID("pane-b")) |
| 539 | 539 | ||||
| 540 | - | assert.Equal(t, entity.PaneID("pane-b"), f.bw1.pageModePaneID) | 540 | + | assert.Equal(t, entity.PaneID("pane-b"), f.bw1.vimModePaneID) |
| 541 | - | assert.True(t, f.pv1B.IsPageMode()) | 541 | + | assert.True(t, f.pv1B.IsVimMode()) |
| 542 | - | assert.False(t, f.pv1A.IsPageMode()) | 542 | + | assert.False(t, f.pv1A.IsVimMode()) |
| 543 | 543 | ||||
| 544 | // bw2 unchanged | 544 | // bw2 unchanged | ||
| 545 | - | assert.Equal(t, entity.PaneID("pane-a"), f.bw2.pageModePaneID) | 545 | + | assert.Equal(t, entity.PaneID("pane-a"), f.bw2.vimModePaneID) |
| 546 | - | assert.True(t, f.pv2A.IsPageMode()) | 546 | + | assert.True(t, f.pv2A.IsVimMode()) |
| 547 | } | 547 | } | ||
| 548 | 548 | ||||
| 549 | - | func TestPageMode_MultiWindow_PulseIsolated(t *testing.T) { | 549 | + | func TestVimMode_MultiWindow_PulseIsolated(t *testing.T) { |
| 550 | - | f := newPageModeTestFixture(t) | 550 | + | f := newVimModeTestFixture(t) |
| 551 | 551 | ||||
| 552 | - | enterPageMode(t, f.app, f.overlay1A, f.bw1) | 552 | + | enterVimMode(t, f.app, f.overlay1A, f.bw1) |
| 553 | - | enterPageMode(t, f.app, f.overlay2A, f.bw2) | 553 | + | enterVimMode(t, f.app, f.overlay2A, f.bw2) |
| 554 | 554 | ||||
| 555 | // Pulse on bw1 (lastFocusedWindowID = "win-1") | 555 | // Pulse on bw1 (lastFocusedWindowID = "win-1") | ||
| 556 | setUpNormalPulse(f.overlay1A) | 556 | setUpNormalPulse(f.overlay1A) | ||
| 557 | - | f.app.triggerPageModePulse(context.Background(), false) | 557 | + | f.app.triggerVimModePulse(context.Background(), false) |
| 558 | 558 | ||||
| 559 | - | // bw2 pane still in page mode | 559 | + | // bw2 pane still in vim mode |
| 560 | - | assert.True(t, f.pv2A.IsPageMode()) | 560 | + | assert.True(t, f.pv2A.IsVimMode()) |
| 561 | } | 561 | } | ||
| 562 | 562 | ||||
| 563 | // ============================================================================ | 563 | // ============================================================================ | ||
| 564 | // Edge cases — nil / no-workspace / startup guards | 564 | // Edge cases — nil / no-workspace / startup guards | ||
| 565 | // ============================================================================ | 565 | // ============================================================================ | ||
| 566 | 566 | ||||
| 567 | - | func TestPageMode_Enter_NoWorkspaceIsNoop(t *testing.T) { | 567 | + | func TestVimMode_Enter_NoWorkspaceIsNoop(t *testing.T) { |
| 568 | app := &App{} | 568 | app := &App{} | ||
| 569 | bw := &browserWindow{id: "win-1"} | 569 | bw := &browserWindow{id: "win-1"} | ||
| 570 | - | app.handlePageModeOwnership(context.Background(), bw, input.ModePage, input.ModeNormal) | 570 | + | app.handleVimModeOwnership(context.Background(), bw, input.ModeVim, input.ModeNormal) |
| 571 | - | assert.Empty(t, bw.pageModePaneID) | 571 | + | assert.Empty(t, bw.vimModePaneID) |
| 572 | } | 572 | } | ||
| 573 | 573 | ||||
| 574 | - | func TestPageMode_Clear_NilBwDoesNotCrash(t *testing.T) { | 574 | + | func TestVimMode_Clear_NilBwDoesNotCrash(t *testing.T) { |
| 575 | - | (&App{}).clearPageModeOwnership(context.Background(), nil) | 575 | + | (&App{}).clearVimModeOwnership(context.Background(), nil) |
| 576 | } | 576 | } | ||
| 577 | 577 | ||||
| 578 | - | func TestPageMode_Clear_NoPaneID(t *testing.T) { | 578 | + | func TestVimMode_Clear_NoPaneID(t *testing.T) { |
| 579 | app := &App{} | 579 | app := &App{} | ||
| 580 | bw := &browserWindow{id: "test-win"} | 580 | bw := &browserWindow{id: "test-win"} | ||
| 581 | - | app.clearPageModeOwnership(context.Background(), bw) | 581 | + | app.clearVimModeOwnership(context.Background(), bw) |
| 582 | - | assert.Empty(t, bw.pageModePaneID) | 582 | + | assert.Empty(t, bw.vimModePaneID) |
| 583 | } | 583 | } | ||
| 584 | 584 | ||||
| 585 | - | func TestPageMode_Clear_StalePaneWithoutWorkspace(t *testing.T) { | 585 | + | func TestVimMode_Clear_StalePaneWithoutWorkspace(t *testing.T) { |
| 586 | app := &App{} | 586 | app := &App{} | ||
| 587 | - | bw := &browserWindow{id: "test-win", pageModePaneID: entity.PaneID("stale-pane")} | 587 | + | bw := &browserWindow{id: "test-win", vimModePaneID: entity.PaneID("stale-pane")} |
| 588 | - | app.clearPageModeOwnership(context.Background(), bw) | 588 | + | app.clearVimModeOwnership(context.Background(), bw) |
| 589 | - | assert.Empty(t, bw.pageModePaneID) | 589 | + | assert.Empty(t, bw.vimModePaneID) |
| 590 | } | 590 | } | ||
| 591 | 591 | ||||
| 592 | - | func TestPageMode_HandleModeChange_NilBwDoesNotCrash(t *testing.T) { | 592 | + | func TestVimMode_HandleModeChange_NilBwDoesNotCrash(t *testing.T) { |
| 593 | - | (&App{}).handleModeChange(context.Background(), nil, input.ModeNormal, input.ModePage) | 593 | + | (&App{}).handleModeChange(context.Background(), nil, input.ModeNormal, input.ModeVim) |
| 594 | } | 594 | } | ||
| 595 | 595 | ||||
| 596 | - | func TestPageMode_EditableFocusOnActivePaneExitsPageMode(t *testing.T) { | 596 | + | func TestVimMode_EditableFocusOnActivePaneExitsVimMode(t *testing.T) { |
| 597 | - | f := newSingleWindowPageModeFixture(t) | 597 | + | f := newSingleWindowVimModeFixture(t) |
| 598 | - | enterPageMode(t, f.app, f.overlay1A, f.bw1) | 598 | + | enterVimMode(t, f.app, f.overlay1A, f.bw1) |
| 599 | - | kh := bindPageModeKeyboardHandler(t, f.app, f.bw1) | 599 | + | kh := bindVimModeKeyboardHandler(t, f.app, f.bw1) |
| 600 | 600 | ||||
| 601 | - | expectPageModeAccentHidden(f.overlay1A) | 601 | + | expectVimModeAccentHidden(f.overlay1A) |
| 602 | f.app.handlePageEditableFocusChanged(context.Background(), entity.PaneID("pane-a"), true) | 602 | f.app.handlePageEditableFocusChanged(context.Background(), entity.PaneID("pane-a"), true) | ||
| 603 | 603 | ||||
| 604 | assert.Equal(t, input.ModeNormal, kh.Mode()) | 604 | assert.Equal(t, input.ModeNormal, kh.Mode()) | ||
| 605 | - | assert.Empty(t, f.bw1.pageModePaneID) | 605 | + | assert.Empty(t, f.bw1.vimModePaneID) |
| 606 | - | assert.False(t, f.pv1A.IsPageMode()) | 606 | + | assert.False(t, f.pv1A.IsVimMode()) |
| 607 | } | 607 | } | ||
| 608 | 608 | ||||
| 609 | - | func TestPageMode_EditableFocusOnInactivePaneDoesNotExitPageMode(t *testing.T) { | 609 | + | func TestVimMode_EditableFocusOnInactivePaneDoesNotExitVimMode(t *testing.T) { |
| 610 | - | f := newSingleWindowPageModeFixture(t) | 610 | + | f := newSingleWindowVimModeFixture(t) |
| 611 | - | enterPageMode(t, f.app, f.overlay1A, f.bw1) | 611 | + | enterVimMode(t, f.app, f.overlay1A, f.bw1) |
| 612 | - | kh := bindPageModeKeyboardHandler(t, f.app, f.bw1) | 612 | + | kh := bindVimModeKeyboardHandler(t, f.app, f.bw1) |
| 613 | 613 | ||||
| 614 | f.app.handlePageEditableFocusChanged(context.Background(), entity.PaneID("pane-b"), true) | 614 | f.app.handlePageEditableFocusChanged(context.Background(), entity.PaneID("pane-b"), true) | ||
| 615 | 615 | ||||
| 616 | - | assert.Equal(t, input.ModePage, kh.Mode()) | 616 | + | assert.Equal(t, input.ModeVim, kh.Mode()) |
| 617 | - | assert.Equal(t, entity.PaneID("pane-a"), f.bw1.pageModePaneID) | 617 | + | assert.Equal(t, entity.PaneID("pane-a"), f.bw1.vimModePaneID) |
| 618 | - | assert.True(t, f.pv1A.IsPageMode()) | 618 | + | assert.True(t, f.pv1A.IsVimMode()) |
| 619 | } | 619 | } | ||
| 620 | 620 | ||||
| 621 | - | func TestPageMode_PaneSwitchToEditablePaneExitsPageMode(t *testing.T) { | 621 | + | func TestVimMode_PaneSwitchToEditablePaneExitsVimMode(t *testing.T) { |
| 622 | - | f := newSingleWindowPageModeFixture(t) | 622 | + | f := newSingleWindowVimModeFixture(t) |
| 623 | - | enterPageMode(t, f.app, f.overlay1A, f.bw1) | 623 | + | enterVimMode(t, f.app, f.overlay1A, f.bw1) |
| 624 | - | kh := bindPageModeKeyboardHandler(t, f.app, f.bw1) | 624 | + | kh := bindVimModeKeyboardHandler(t, f.app, f.bw1) |
| 625 | f.app.pageEditableFocusByPane = map[entity.PaneID]bool{ | 625 | f.app.pageEditableFocusByPane = map[entity.PaneID]bool{ | ||
| 626 | "pane-b": true, | 626 | "pane-b": true, | ||
| 627 | } | 627 | } | ||
| 628 | 628 | ||||
| 629 | - | expectPageModeAccentHidden(f.overlay1A) | 629 | + | expectVimModeAccentHidden(f.overlay1A) |
| 630 | - | f.app.transferPageModeOwnershipToPane(context.Background(), f.bw1, entity.PaneID("pane-b")) | 630 | + | f.app.transferVimModeOwnershipToPane(context.Background(), f.bw1, entity.PaneID("pane-b")) |
| 631 | 631 | ||||
| 632 | assert.Equal(t, input.ModeNormal, kh.Mode()) | 632 | assert.Equal(t, input.ModeNormal, kh.Mode()) | ||
| 633 | - | assert.Empty(t, f.bw1.pageModePaneID) | 633 | + | assert.Empty(t, f.bw1.vimModePaneID) |
| 634 | - | assert.False(t, f.pv1A.IsPageMode()) | 634 | + | assert.False(t, f.pv1A.IsVimMode()) |
| 635 | - | assert.False(t, f.pv1B.IsPageMode()) | 635 | + | assert.False(t, f.pv1B.IsVimMode()) |
| 636 | } | 636 | } | ||
| 637 | 637 | ||||
| 638 | - | func TestPageMode_BackgroundWindowEditableFocusDoesNotExitFocusedWindowPageMode(t *testing.T) { | 638 | + | func TestVimMode_BackgroundWindowEditableFocusDoesNotExitFocusedWindowVimMode(t *testing.T) { |
| 639 | factory := mocks.NewMockWidgetFactory(t) | 639 | factory := mocks.NewMockWidgetFactory(t) | ||
| 640 | pv1, overlay1 := createTestPaneView(t, factory, "pane-a") | 640 | pv1, overlay1 := createTestPaneView(t, factory, "pane-a") | ||
| 641 | pv2, _ := createTestPaneView(t, factory, "pane-x") | 641 | pv2, _ := createTestPaneView(t, factory, "pane-x") | ||
| @@ -661,44 +661,44 @@ func TestPageMode_BackgroundWindowEditableFocusDoesNotExitFocusedWindowPageMode( | |||||
| 661 | lastFocusedWindowID: "win-1", | 661 | lastFocusedWindowID: "win-1", | ||
| 662 | } | 662 | } | ||
| 663 | 663 | ||||
| 664 | - | enterPageMode(t, app, overlay1, bw1) | 664 | + | enterVimMode(t, app, overlay1, bw1) |
| 665 | - | kh := bindPageModeKeyboardHandler(t, app, bw1) | 665 | + | kh := bindVimModeKeyboardHandler(t, app, bw1) |
| 666 | 666 | ||||
| 667 | app.handlePageEditableFocusChanged(context.Background(), entity.PaneID("pane-x"), true) | 667 | app.handlePageEditableFocusChanged(context.Background(), entity.PaneID("pane-x"), true) | ||
| 668 | 668 | ||||
| 669 | - | assert.Equal(t, input.ModePage, kh.Mode()) | 669 | + | assert.Equal(t, input.ModeVim, kh.Mode()) |
| 670 | - | assert.Equal(t, entity.PaneID("pane-a"), bw1.pageModePaneID) | 670 | + | assert.Equal(t, entity.PaneID("pane-a"), bw1.vimModePaneID) |
| 671 | - | assert.True(t, pv1.IsPageMode()) | 671 | + | assert.True(t, pv1.IsVimMode()) |
| 672 | } | 672 | } | ||
| 673 | 673 | ||||
| 674 | - | func TestPageMode_OmniboxFocusExitsPageMode(t *testing.T) { | 674 | + | func TestVimMode_OmniboxFocusExitsVimMode(t *testing.T) { |
| 675 | - | f := newSingleWindowPageModeFixture(t) | 675 | + | f := newSingleWindowVimModeFixture(t) |
| 676 | - | enterPageMode(t, f.app, f.overlay1A, f.bw1) | 676 | + | enterVimMode(t, f.app, f.overlay1A, f.bw1) |
| 677 | - | kh := bindPageModeKeyboardHandler(t, f.app, f.bw1) | 677 | + | kh := bindVimModeKeyboardHandler(t, f.app, f.bw1) |
| 678 | 678 | ||||
| 679 | - | expectPageModeAccentHidden(f.overlay1A) | 679 | + | expectVimModeAccentHidden(f.overlay1A) |
| 680 | - | f.app.handlePageModeFocusTrigger(context.Background(), f.bw1, usecase.PageModePolicyTriggerOmniboxFocus) | 680 | + | f.app.handleVimModeFocusTrigger(context.Background(), f.bw1, usecase.VimModePolicyTriggerOmniboxFocus) |
| 681 | 681 | ||||
| 682 | assert.Equal(t, input.ModeNormal, kh.Mode()) | 682 | assert.Equal(t, input.ModeNormal, kh.Mode()) | ||
| 683 | - | assert.Empty(t, f.bw1.pageModePaneID) | 683 | + | assert.Empty(t, f.bw1.vimModePaneID) |
| 684 | } | 684 | } | ||
| 685 | 685 | ||||
| 686 | - | func TestPageMode_FindBarFocusExitsPageMode(t *testing.T) { | 686 | + | func TestVimMode_FindBarFocusExitsVimMode(t *testing.T) { |
| 687 | - | f := newSingleWindowPageModeFixture(t) | 687 | + | f := newSingleWindowVimModeFixture(t) |
| 688 | - | enterPageMode(t, f.app, f.overlay1A, f.bw1) | 688 | + | enterVimMode(t, f.app, f.overlay1A, f.bw1) |
| 689 | - | kh := bindPageModeKeyboardHandler(t, f.app, f.bw1) | 689 | + | kh := bindVimModeKeyboardHandler(t, f.app, f.bw1) |
| 690 | 690 | ||||
| 691 | - | expectPageModeAccentHidden(f.overlay1A) | 691 | + | expectVimModeAccentHidden(f.overlay1A) |
| 692 | - | f.app.handlePageModeFocusTrigger(context.Background(), f.bw1, usecase.PageModePolicyTriggerFindBarFocus) | 692 | + | f.app.handleVimModeFocusTrigger(context.Background(), f.bw1, usecase.VimModePolicyTriggerFindBarFocus) |
| 693 | 693 | ||||
| 694 | assert.Equal(t, input.ModeNormal, kh.Mode()) | 694 | assert.Equal(t, input.ModeNormal, kh.Mode()) | ||
| 695 | - | assert.Empty(t, f.bw1.pageModePaneID) | 695 | + | assert.Empty(t, f.bw1.vimModePaneID) |
| 696 | } | 696 | } | ||
| 697 | 697 | ||||
| 698 | - | func TestPageMode_TabSwitchExitsAndClearsOldAccent(t *testing.T) { | 698 | + | func TestVimMode_TabSwitchExitsAndClearsOldAccent(t *testing.T) { |
| 699 | - | f := newSingleWindowPageModeFixture(t) | 699 | + | f := newSingleWindowVimModeFixture(t) |
| 700 | - | enterPageMode(t, f.app, f.overlay1A, f.bw1) | 700 | + | enterVimMode(t, f.app, f.overlay1A, f.bw1) |
| 701 | - | kh := bindPageModeKeyboardHandler(t, f.app, f.bw1) | 701 | + | kh := bindVimModeKeyboardHandler(t, f.app, f.bw1) |
| 702 | 702 | ||||
| 703 | tab2 := &entity.Tab{ID: "tab-2b", Workspace: &entity.Workspace{ | 703 | tab2 := &entity.Tab{ID: "tab-2b", Workspace: &entity.Workspace{ | ||
| 704 | ID: "ws-2b", | 704 | ID: "ws-2b", | ||
| @@ -709,57 +709,57 @@ func TestPageMode_TabSwitchExitsAndClearsOldAccent(t *testing.T) { | |||||
| 709 | f.bw1.tabs.SetActive(tab2.ID) | 709 | f.bw1.tabs.SetActive(tab2.ID) | ||
| 710 | f.app.workspaceViews[tab2.ID] = setupWorkspaceViewMocks(t, f.factory) | 710 | f.app.workspaceViews[tab2.ID] = setupWorkspaceViewMocks(t, f.factory) | ||
| 711 | 711 | ||||
| 712 | - | expectPageModeAccentHidden(f.overlay1A) | 712 | + | expectVimModeAccentHidden(f.overlay1A) |
| 713 | - | f.app.handlePageModeTabSwitch(context.Background(), f.bw1) | 713 | + | f.app.handleVimModeTabSwitch(context.Background(), f.bw1) |
| 714 | 714 | ||||
| 715 | assert.Equal(t, input.ModeNormal, kh.Mode()) | 715 | assert.Equal(t, input.ModeNormal, kh.Mode()) | ||
| 716 | - | assert.Empty(t, f.bw1.pageModePaneID) | 716 | + | assert.Empty(t, f.bw1.vimModePaneID) |
| 717 | - | assert.False(t, f.pv1A.IsPageMode()) | 717 | + | assert.False(t, f.pv1A.IsVimMode()) |
| 718 | } | 718 | } | ||
| 719 | 719 | ||||
| 720 | - | func TestPageMode_ActivationBypassWhenActivePageIsEditable(t *testing.T) { | 720 | + | func TestVimMode_ActivationBypassWhenActivePageIsEditable(t *testing.T) { |
| 721 | - | f := newSingleWindowPageModeFixture(t) | 721 | + | f := newSingleWindowVimModeFixture(t) |
| 722 | f.app.pageEditableFocusByPane = map[entity.PaneID]bool{ | 722 | f.app.pageEditableFocusByPane = map[entity.PaneID]bool{ | ||
| 723 | "pane-a": true, | 723 | "pane-a": true, | ||
| 724 | } | 724 | } | ||
| 725 | 725 | ||||
| 726 | - | assert.True(t, f.app.shouldBypassPageModeActivation(f.bw1)) | 726 | + | assert.True(t, f.app.shouldBypassVimModeActivation(f.bw1)) |
| 727 | } | 727 | } | ||
| 728 | 728 | ||||
| 729 | - | func TestPageMode_ActivationBypassClearsWhenEditableFocusLeaves(t *testing.T) { | 729 | + | func TestVimMode_ActivationBypassClearsWhenEditableFocusLeaves(t *testing.T) { |
| 730 | - | f := newSingleWindowPageModeFixture(t) | 730 | + | f := newSingleWindowVimModeFixture(t) |
| 731 | 731 | ||||
| 732 | f.app.handlePageEditableFocusChanged(context.Background(), entity.PaneID("pane-a"), true) | 732 | f.app.handlePageEditableFocusChanged(context.Background(), entity.PaneID("pane-a"), true) | ||
| 733 | - | assert.True(t, f.app.shouldBypassPageModeActivation(f.bw1)) | 733 | + | assert.True(t, f.app.shouldBypassVimModeActivation(f.bw1)) |
| 734 | 734 | ||||
| 735 | f.app.handlePageEditableFocusChanged(context.Background(), entity.PaneID("pane-a"), false) | 735 | f.app.handlePageEditableFocusChanged(context.Background(), entity.PaneID("pane-a"), false) | ||
| 736 | - | assert.False(t, f.app.shouldBypassPageModeActivation(f.bw1)) | 736 | + | assert.False(t, f.app.shouldBypassVimModeActivation(f.bw1)) |
| 737 | } | 737 | } | ||
| 738 | 738 | ||||
| 739 | - | func TestPageMode_ClearEditableFocusStateRemovesStoredBypass(t *testing.T) { | 739 | + | func TestVimMode_ClearEditableFocusStateRemovesStoredBypass(t *testing.T) { |
| 740 | - | f := newSingleWindowPageModeFixture(t) | 740 | + | f := newSingleWindowVimModeFixture(t) |
| 741 | f.app.pageEditableFocusByPane = map[entity.PaneID]bool{"pane-a": true} | 741 | f.app.pageEditableFocusByPane = map[entity.PaneID]bool{"pane-a": true} | ||
| 742 | 742 | ||||
| 743 | f.app.clearPageEditableFocusState(entity.PaneID("pane-a")) | 743 | f.app.clearPageEditableFocusState(entity.PaneID("pane-a")) | ||
| 744 | 744 | ||||
| 745 | - | assert.False(t, f.app.shouldBypassPageModeActivation(f.bw1)) | 745 | + | assert.False(t, f.app.shouldBypassVimModeActivation(f.bw1)) |
| 746 | } | 746 | } | ||
| 747 | 747 | ||||
| 748 | // ============================================================================ | 748 | // ============================================================================ | ||
| 749 | // CSS class constants | 749 | // CSS class constants | ||
| 750 | // ============================================================================ | 750 | // ============================================================================ | ||
| 751 | 751 | ||||
| 752 | - | func TestPageMode_CSSClassConstants(t *testing.T) { | 752 | + | func TestVimMode_CSSClassConstants(t *testing.T) { |
| 753 | - | assert.Equal(t, "page-mode-active", component.PageModeActiveClass) | 753 | + | assert.Equal(t, "vim-mode-active", component.VimModeActiveClass) |
| 754 | - | assert.Equal(t, "page-mode-pulse", component.PageModePulseClass) | 754 | + | assert.Equal(t, "vim-mode-pulse", component.VimModePulseClass) |
| 755 | - | assert.Equal(t, "page-mode-pulse-fast", component.PageModeFastPulseClass) | 755 | + | assert.Equal(t, "vim-mode-pulse-fast", component.VimModeFastPulseClass) |
| 756 | } | 756 | } | ||
| 757 | 757 | ||||
| 758 | // ============================================================================ | 758 | // ============================================================================ | ||
| 759 | // Mode toaster | 759 | // Mode toaster | ||
| 760 | // ============================================================================ | 760 | // ============================================================================ | ||
| 761 | 761 | ||||
| 762 | - | func newModeToasterForPageModeTest(t *testing.T, factory *mocks.MockWidgetFactory) (*component.Toaster, *mocks.MockBoxWidget, *mocks.MockLabelWidget) { | 762 | + | func newModeToasterForVimModeTest(t *testing.T, factory *mocks.MockWidgetFactory) (*component.Toaster, *mocks.MockBoxWidget, *mocks.MockLabelWidget) { |
| 763 | t.Helper() | 763 | t.Helper() | ||
| 764 | 764 | ||||
| 765 | container := mocks.NewMockBoxWidget(t) | 765 | container := mocks.NewMockBoxWidget(t) | ||
| @@ -782,21 +782,21 @@ func newModeToasterForPageModeTest(t *testing.T, factory *mocks.MockWidgetFactor | |||||
| 782 | return component.NewToaster(factory), container, label | 782 | return component.NewToaster(factory), container, label | ||
| 783 | } | 783 | } | ||
| 784 | 784 | ||||
| 785 | - | func expectPageModeToastShown(container *mocks.MockBoxWidget, label *mocks.MockLabelWidget) { | 785 | + | func expectVimModeToastShown(container *mocks.MockBoxWidget, label *mocks.MockLabelWidget) { |
| 786 | container.EXPECT().RemoveCssClass("toast-info").Once() | 786 | container.EXPECT().RemoveCssClass("toast-info").Once() | ||
| 787 | - | container.EXPECT().AddCssClass("toast-pane-mode").Once() | 787 | + | container.EXPECT().AddCssClass("toast-vim-mode").Once() |
| 788 | container.EXPECT().SetHalign(mock.Anything).Once() | 788 | container.EXPECT().SetHalign(mock.Anything).Once() | ||
| 789 | container.EXPECT().SetValign(mock.Anything).Once() | 789 | container.EXPECT().SetValign(mock.Anything).Once() | ||
| 790 | - | label.EXPECT().SetText("PAGE MODE").Once() | 790 | + | label.EXPECT().SetText("VIM MODE").Once() |
| 791 | container.EXPECT().SetVisible(true).Once() | 791 | container.EXPECT().SetVisible(true).Once() | ||
| 792 | } | 792 | } | ||
| 793 | 793 | ||||
| 794 | - | func TestPageMode_ToasterRemainsVisibleUntilModeExit(t *testing.T) { | 794 | + | func TestVimMode_ToasterRemainsVisibleUntilModeExit(t *testing.T) { |
| 795 | factory := mocks.NewMockWidgetFactory(t) | 795 | factory := mocks.NewMockWidgetFactory(t) | ||
| 796 | - | toaster, container, label := newModeToasterForPageModeTest(t, factory) | 796 | + | toaster, container, label := newModeToasterForVimModeTest(t, factory) |
| 797 | - | expectPageModeToastShown(container, label) | 797 | + | expectVimModeToastShown(container, label) |
| 798 | // These optional calls are made only by the old brief auto-dismiss path. | 798 | // These optional calls are made only by the old brief auto-dismiss path. | ||
| 799 | - | container.EXPECT().RemoveCssClass("toast-pane-mode").Maybe() | 799 | + | container.EXPECT().RemoveCssClass("toast-vim-mode").Maybe() |
| 800 | container.EXPECT().SetVisible(false).Maybe() | 800 | container.EXPECT().SetVisible(false).Maybe() | ||
| 801 | 801 | ||||
| 802 | app := &App{runtimeConfig: runtimeConfigStateFromSnapshotForTest(entity.RuntimeConfigSnapshot{ | 802 | app := &App{runtimeConfig: runtimeConfigStateFromSnapshotForTest(entity.RuntimeConfigSnapshot{ | ||
| @@ -808,7 +808,7 @@ func TestPageMode_ToasterRemainsVisibleUntilModeExit(t *testing.T) { | |||||
| 808 | app.browserWindows = map[string]*browserWindow{bw.id: bw} | 808 | app.browserWindows = map[string]*browserWindow{bw.id: bw} | ||
| 809 | app.lastFocusedWindowID = bw.id | 809 | app.lastFocusedWindowID = bw.id | ||
| 810 | 810 | ||||
| 811 | - | app.updateModeIndicatorToaster(context.Background(), bw, input.ModePage) | 811 | + | app.updateModeIndicatorToaster(context.Background(), bw, input.ModeVim) |
| 812 | require.True(t, toaster.IsVisible()) | 812 | require.True(t, toaster.IsVisible()) | ||
| 813 | 813 | ||||
| 814 | deadline := time.Now().Add(time.Duration(component.ToastBriefDurationMs+100) * time.Millisecond) | 814 | deadline := time.Now().Add(time.Duration(component.ToastBriefDurationMs+100) * time.Millisecond) | ||
| @@ -820,15 +820,15 @@ func TestPageMode_ToasterRemainsVisibleUntilModeExit(t *testing.T) { | |||||
| 820 | } | 820 | } | ||
| 821 | } | 821 | } | ||
| 822 | 822 | ||||
| 823 | - | assert.True(t, toaster.IsVisible(), "Page Mode toast must remain visible until mode exit") | 823 | + | assert.True(t, toaster.IsVisible(), "Vim Mode toast must remain visible until mode exit") |
| 824 | } | 824 | } | ||
| 825 | 825 | ||||
| 826 | - | func TestPageMode_ModeExitHidesToaster(t *testing.T) { | 826 | + | func TestVimMode_ModeExitHidesToaster(t *testing.T) { |
| 827 | factory := mocks.NewMockWidgetFactory(t) | 827 | factory := mocks.NewMockWidgetFactory(t) | ||
| 828 | - | toaster, container, label := newModeToasterForPageModeTest(t, factory) | 828 | + | toaster, container, label := newModeToasterForVimModeTest(t, factory) |
| 829 | - | expectPageModeToastShown(container, label) | 829 | + | expectVimModeToastShown(container, label) |
| 830 | container.EXPECT().RemoveCssClass("toast-custom").Once() | 830 | container.EXPECT().RemoveCssClass("toast-custom").Once() | ||
| 831 | - | container.EXPECT().RemoveCssClass("toast-pane-mode").Once() | 831 | + | container.EXPECT().RemoveCssClass("toast-vim-mode").Once() |
| 832 | container.EXPECT().SetVisible(false).Once() | 832 | container.EXPECT().SetVisible(false).Once() | ||
| 833 | 833 | ||||
| 834 | app := &App{runtimeConfig: runtimeConfigStateFromSnapshotForTest(entity.RuntimeConfigSnapshot{ | 834 | app := &App{runtimeConfig: runtimeConfigStateFromSnapshotForTest(entity.RuntimeConfigSnapshot{ | ||
| @@ -838,32 +838,32 @@ func TestPageMode_ModeExitHidesToaster(t *testing.T) { | |||||
| 838 | })} | 838 | })} | ||
| 839 | bw := &browserWindow{id: "win-1", modeToaster: toaster} | 839 | bw := &browserWindow{id: "win-1", modeToaster: toaster} | ||
| 840 | 840 | ||||
| 841 | - | app.updateModeIndicatorToaster(context.Background(), bw, input.ModePage) | 841 | + | app.updateModeIndicatorToaster(context.Background(), bw, input.ModeVim) |
| 842 | require.True(t, toaster.IsVisible()) | 842 | require.True(t, toaster.IsVisible()) | ||
| 843 | app.updateModeIndicatorToaster(context.Background(), bw, input.ModeNormal) | 843 | app.updateModeIndicatorToaster(context.Background(), bw, input.ModeNormal) | ||
| 844 | 844 | ||||
| 845 | - | assert.False(t, toaster.IsVisible(), "leaving Page Mode must hide its persistent toast") | 845 | + | assert.False(t, toaster.IsVisible(), "leaving Vim Mode must hide its persistent toast") |
| 846 | } | 846 | } | ||
| 847 | 847 | ||||
| 848 | - | func TestPageMode_DisabledModeIndicatorPreferenceSuppressesToaster(t *testing.T) { | 848 | + | func TestVimMode_DisabledModeIndicatorPreferenceSuppressesToaster(t *testing.T) { |
| 849 | factory := mocks.NewMockWidgetFactory(t) | 849 | factory := mocks.NewMockWidgetFactory(t) | ||
| 850 | - | toaster, _, _ := newModeToasterForPageModeTest(t, factory) | 850 | + | toaster, _, _ := newModeToasterForVimModeTest(t, factory) |
| 851 | app := &App{runtimeConfig: runtimeConfigStateFromSnapshotForTest(entity.RuntimeConfigSnapshot{})} | 851 | app := &App{runtimeConfig: runtimeConfigStateFromSnapshotForTest(entity.RuntimeConfigSnapshot{})} | ||
| 852 | bw := &browserWindow{id: "win-1", modeToaster: toaster} | 852 | bw := &browserWindow{id: "win-1", modeToaster: toaster} | ||
| 853 | 853 | ||||
| 854 | - | app.updateModeIndicatorToaster(context.Background(), bw, input.ModePage) | 854 | + | app.updateModeIndicatorToaster(context.Background(), bw, input.ModeVim) |
| 855 | 855 | ||||
| 856 | assert.False(t, toaster.IsVisible()) | 856 | assert.False(t, toaster.IsVisible()) | ||
| 857 | } | 857 | } | ||
| 858 | 858 | ||||
| 859 | - | func TestPageMode_MultiWindowToastersExitIndependently(t *testing.T) { | 859 | + | func TestVimMode_MultiWindowToastersExitIndependently(t *testing.T) { |
| 860 | factory := mocks.NewMockWidgetFactory(t) | 860 | factory := mocks.NewMockWidgetFactory(t) | ||
| 861 | - | toaster1, container1, label1 := newModeToasterForPageModeTest(t, factory) | 861 | + | toaster1, container1, label1 := newModeToasterForVimModeTest(t, factory) |
| 862 | - | toaster2, container2, label2 := newModeToasterForPageModeTest(t, factory) | 862 | + | toaster2, container2, label2 := newModeToasterForVimModeTest(t, factory) |
| 863 | - | expectPageModeToastShown(container1, label1) | 863 | + | expectVimModeToastShown(container1, label1) |
| 864 | - | expectPageModeToastShown(container2, label2) | 864 | + | expectVimModeToastShown(container2, label2) |
| 865 | container1.EXPECT().RemoveCssClass("toast-custom").Once() | 865 | container1.EXPECT().RemoveCssClass("toast-custom").Once() | ||
| 866 | - | container1.EXPECT().RemoveCssClass("toast-pane-mode").Once() | 866 | + | container1.EXPECT().RemoveCssClass("toast-vim-mode").Once() |
| 867 | container1.EXPECT().SetVisible(false).Once() | 867 | container1.EXPECT().SetVisible(false).Once() | ||
| 868 | 868 | ||||
| 869 | app := &App{runtimeConfig: runtimeConfigStateFromSnapshotForTest(entity.RuntimeConfigSnapshot{ | 869 | app := &App{runtimeConfig: runtimeConfigStateFromSnapshotForTest(entity.RuntimeConfigSnapshot{ | ||
| @@ -874,20 +874,20 @@ func TestPageMode_MultiWindowToastersExitIndependently(t *testing.T) { | |||||
| 874 | bw1 := &browserWindow{id: "win-1", modeToaster: toaster1} | 874 | bw1 := &browserWindow{id: "win-1", modeToaster: toaster1} | ||
| 875 | bw2 := &browserWindow{id: "win-2", modeToaster: toaster2} | 875 | bw2 := &browserWindow{id: "win-2", modeToaster: toaster2} | ||
| 876 | 876 | ||||
| 877 | - | app.updateModeIndicatorToaster(context.Background(), bw1, input.ModePage) | 877 | + | app.updateModeIndicatorToaster(context.Background(), bw1, input.ModeVim) |
| 878 | - | app.updateModeIndicatorToaster(context.Background(), bw2, input.ModePage) | 878 | + | app.updateModeIndicatorToaster(context.Background(), bw2, input.ModeVim) |
| 879 | app.updateModeIndicatorToaster(context.Background(), bw1, input.ModeNormal) | 879 | app.updateModeIndicatorToaster(context.Background(), bw1, input.ModeNormal) | ||
| 880 | 880 | ||||
| 881 | assert.False(t, toaster1.IsVisible()) | 881 | assert.False(t, toaster1.IsVisible()) | ||
| 882 | - | assert.True(t, toaster2.IsVisible(), "exiting one window must not hide another window's Page Mode toast") | 882 | + | assert.True(t, toaster2.IsVisible(), "exiting one window must not hide another window's Vim Mode toast") |
| 883 | } | 883 | } | ||
| 884 | 884 | ||||
| 885 | - | func TestPageMode_RuntimePreferenceDisableHidesPersistentToaster(t *testing.T) { | 885 | + | func TestVimMode_RuntimePreferenceDisableHidesPersistentToaster(t *testing.T) { |
| 886 | factory := mocks.NewMockWidgetFactory(t) | 886 | factory := mocks.NewMockWidgetFactory(t) | ||
| 887 | - | toaster, container, label := newModeToasterForPageModeTest(t, factory) | 887 | + | toaster, container, label := newModeToasterForVimModeTest(t, factory) |
| 888 | - | expectPageModeToastShown(container, label) | 888 | + | expectVimModeToastShown(container, label) |
| 889 | container.EXPECT().RemoveCssClass("toast-custom").Once() | 889 | container.EXPECT().RemoveCssClass("toast-custom").Once() | ||
| 890 | - | container.EXPECT().RemoveCssClass("toast-pane-mode").Once() | 890 | + | container.EXPECT().RemoveCssClass("toast-vim-mode").Once() |
| 891 | container.EXPECT().SetVisible(false).Once() | 891 | container.EXPECT().SetVisible(false).Once() | ||
| 892 | 892 | ||||
| 893 | enabled := entity.RuntimeConfigSnapshot{ | 893 | enabled := entity.RuntimeConfigSnapshot{ | ||
| @@ -895,26 +895,26 @@ func TestPageMode_RuntimePreferenceDisableHidesPersistentToaster(t *testing.T) { | |||||
| 895 | ModeIndicatorToasterEnabled: true, | 895 | ModeIndicatorToasterEnabled: true, | ||
| 896 | }}}, | 896 | }}}, | ||
| 897 | } | 897 | } | ||
| 898 | - | keyboardHandler := newKeyboardHandlerInPageMode(t) | 898 | + | keyboardHandler := newKeyboardHandlerInVimMode(t) |
| 899 | bw := &browserWindow{id: "win-1", modeToaster: toaster, keyboardHandler: keyboardHandler} | 899 | bw := &browserWindow{id: "win-1", modeToaster: toaster, keyboardHandler: keyboardHandler} | ||
| 900 | app := &App{ | 900 | app := &App{ | ||
| 901 | runtimeConfig: runtimeConfigStateFromSnapshotForTest(enabled), | 901 | runtimeConfig: runtimeConfigStateFromSnapshotForTest(enabled), | ||
| 902 | browserWindows: map[string]*browserWindow{bw.id: bw}, | 902 | browserWindows: map[string]*browserWindow{bw.id: bw}, | ||
| 903 | } | 903 | } | ||
| 904 | 904 | ||||
| 905 | - | app.updateModeIndicatorToaster(context.Background(), bw, input.ModePage) | 905 | + | app.updateModeIndicatorToaster(context.Background(), bw, input.ModeVim) |
| 906 | require.True(t, toaster.IsVisible()) | 906 | require.True(t, toaster.IsVisible()) | ||
| 907 | app.applyRuntimeConfigChange(context.Background(), entity.RuntimeConfigSnapshot{}) | 907 | app.applyRuntimeConfigChange(context.Background(), entity.RuntimeConfigSnapshot{}) | ||
| 908 | 908 | ||||
| 909 | assert.False(t, toaster.IsVisible(), "runtime preference disable must reconcile an existing persistent toast") | 909 | assert.False(t, toaster.IsVisible(), "runtime preference disable must reconcile an existing persistent toast") | ||
| 910 | } | 910 | } | ||
| 911 | 911 | ||||
| 912 | - | func TestPageMode_RuntimePreferenceEnableShowsPersistentToaster(t *testing.T) { | 912 | + | func TestVimMode_RuntimePreferenceEnableShowsPersistentToaster(t *testing.T) { |
| 913 | factory := mocks.NewMockWidgetFactory(t) | 913 | factory := mocks.NewMockWidgetFactory(t) | ||
| 914 | - | toaster, container, label := newModeToasterForPageModeTest(t, factory) | 914 | + | toaster, container, label := newModeToasterForVimModeTest(t, factory) |
| 915 | - | expectPageModeToastShown(container, label) | 915 | + | expectVimModeToastShown(container, label) |
| 916 | 916 | ||||
| 917 | - | keyboardHandler := newKeyboardHandlerInPageMode(t) | 917 | + | keyboardHandler := newKeyboardHandlerInVimMode(t) |
| 918 | bw := &browserWindow{id: "win-1", modeToaster: toaster, keyboardHandler: keyboardHandler} | 918 | bw := &browserWindow{id: "win-1", modeToaster: toaster, keyboardHandler: keyboardHandler} | ||
| 919 | app := &App{ | 919 | app := &App{ | ||
| 920 | runtimeConfig: runtimeConfigStateFromSnapshotForTest(entity.RuntimeConfigSnapshot{}), | 920 | runtimeConfig: runtimeConfigStateFromSnapshotForTest(entity.RuntimeConfigSnapshot{}), | ||
| @@ -928,14 +928,14 @@ func TestPageMode_RuntimePreferenceEnableShowsPersistentToaster(t *testing.T) { | |||||
| 928 | 928 | ||||
| 929 | app.applyRuntimeConfigChange(context.Background(), enabled) | 929 | app.applyRuntimeConfigChange(context.Background(), enabled) | ||
| 930 | 930 | ||||
| 931 | - | assert.True(t, toaster.IsVisible(), "runtime preference enable must reconcile active Page Mode") | 931 | + | assert.True(t, toaster.IsVisible(), "runtime preference enable must reconcile active Vim Mode") |
| 932 | } | 932 | } | ||
| 933 | 933 | ||||
| 934 | // ============================================================================ | 934 | // ============================================================================ | ||
| 935 | - | // BorderManager — Page mode must NOT use the global border overlay | 935 | + | // BorderManager — Vim mode must NOT use the global border overlay |
| 936 | // ============================================================================ | 936 | // ============================================================================ | ||
| 937 | 937 | ||||
| 938 | - | func TestPageMode_GlobalBorderOverlayStaysOff(t *testing.T) { | 938 | + | func TestVimMode_GlobalBorderOverlayStaysOff(t *testing.T) { |
| 939 | mockFactory := mocks.NewMockWidgetFactory(t) | 939 | mockFactory := mocks.NewMockWidgetFactory(t) | ||
| 940 | mockBox := mocks.NewMockBoxWidget(t) | 940 | mockBox := mocks.NewMockBoxWidget(t) | ||
| 941 | 941 | ||||
| @@ -949,113 +949,113 @@ func TestPageMode_GlobalBorderOverlayStaysOff(t *testing.T) { | |||||
| 949 | bm := focus.NewBorderManager(mockFactory) | 949 | bm := focus.NewBorderManager(mockFactory) | ||
| 950 | 950 | ||||
| 951 | mockBox.EXPECT().SetVisible(false).Once() | 951 | mockBox.EXPECT().SetVisible(false).Once() | ||
| 952 | - | bm.OnModeChange(context.Background(), input.ModeNormal, input.ModePage) | 952 | + | bm.OnModeChange(context.Background(), input.ModeNormal, input.ModeVim) |
| 953 | 953 | ||||
| 954 | mockBox.EXPECT().SetVisible(false).Once() | 954 | mockBox.EXPECT().SetVisible(false).Once() | ||
| 955 | - | bm.OnModeChange(context.Background(), input.ModePage, input.ModeNormal) | 955 | + | bm.OnModeChange(context.Background(), input.ModeVim, input.ModeNormal) |
| 956 | } | 956 | } | ||
| 957 | 957 | ||||
| 958 | // ============================================================================ | 958 | // ============================================================================ | ||
| 959 | // Pulse Debounce | 959 | // Pulse Debounce | ||
| 960 | // ============================================================================ | 960 | // ============================================================================ | ||
| 961 | 961 | ||||
| 962 | - | func TestPageMode_Pulse_DebounceSkipsRapidConsecutiveCalls(t *testing.T) { | 962 | + | func TestVimMode_Pulse_DebounceSkipsRapidConsecutiveCalls(t *testing.T) { |
| 963 | - | f := newSingleWindowPageModeFixture(t) | 963 | + | f := newSingleWindowVimModeFixture(t) |
| 964 | - | enterPageMode(t, f.app, f.overlay1A, f.bw1) | 964 | + | enterVimMode(t, f.app, f.overlay1A, f.bw1) |
| 965 | 965 | ||||
| 966 | // Set up expectations for exactly ONE normal pulse. | 966 | // Set up expectations for exactly ONE normal pulse. | ||
| 967 | setUpNormalPulse(f.overlay1A) | 967 | setUpNormalPulse(f.overlay1A) | ||
| 968 | 968 | ||||
| 969 | // First call fires the pulse (debounce timer is zero). | 969 | // First call fires the pulse (debounce timer is zero). | ||
| 970 | - | f.app.triggerPageModePulse(context.Background(), false) | 970 | + | f.app.triggerVimModePulse(context.Background(), false) |
| 971 | 971 | ||||
| 972 | // Second call within the same frame should be debounced — no | 972 | // Second call within the same frame should be debounced — no | ||
| 973 | // mock expectations set up for it, so the test fails if it fires. | 973 | // mock expectations set up for it, so the test fails if it fires. | ||
| 974 | - | f.app.triggerPageModePulse(context.Background(), false) | 974 | + | f.app.triggerVimModePulse(context.Background(), false) |
| 975 | } | 975 | } | ||
| 976 | 976 | ||||
| 977 | - | func TestPageMode_Pulse_DebounceAllowsSpacedCalls(t *testing.T) { | 977 | + | func TestVimMode_Pulse_DebounceAllowsSpacedCalls(t *testing.T) { |
| 978 | // Verify that the debounce timer starts at zero on a fresh App, | 978 | // Verify that the debounce timer starts at zero on a fresh App, | ||
| 979 | - | // so the first pulse after entering page mode always fires. | 979 | + | // so the first pulse after entering vim mode always fires. |
| 980 | // The exit-and-re-enter path is covered by the | 980 | // The exit-and-re-enter path is covered by the | ||
| 981 | - | // TestPageMode_Pulse_DebounceResetOnClearOwnership test below. | 981 | + | // TestVimMode_Pulse_DebounceResetOnClearOwnership test below. |
| 982 | 982 | ||||
| 983 | - | f := newSingleWindowPageModeFixture(t) | 983 | + | f := newSingleWindowVimModeFixture(t) |
| 984 | 984 | ||||
| 985 | // Fresh app: debounce timer is zero. | 985 | // Fresh app: debounce timer is zero. | ||
| 986 | - | assert.True(t, f.app.pageModePulseLastTime.IsZero(), | 986 | + | assert.True(t, f.app.vimModePulseLastTime.IsZero(), |
| 987 | "fresh app should have zero pulse timer") | 987 | "fresh app should have zero pulse timer") | ||
| 988 | 988 | ||||
| 989 | - | enterPageMode(t, f.app, f.overlay1A, f.bw1) | 989 | + | enterVimMode(t, f.app, f.overlay1A, f.bw1) |
| 990 | 990 | ||||
| 991 | // Pulse #1 fires because timer is zero (time.Since(zero) is huge). | 991 | // Pulse #1 fires because timer is zero (time.Since(zero) is huge). | ||
| 992 | setUpNormalPulse(f.overlay1A) | 992 | setUpNormalPulse(f.overlay1A) | ||
| 993 | - | f.app.triggerPageModePulse(context.Background(), false) | 993 | + | f.app.triggerVimModePulse(context.Background(), false) |
| 994 | 994 | ||||
| 995 | // Timer is now set, proving the first pulse registered. | 995 | // Timer is now set, proving the first pulse registered. | ||
| 996 | - | assert.False(t, f.app.pageModePulseLastTime.IsZero(), | 996 | + | assert.False(t, f.app.vimModePulseLastTime.IsZero(), |
| 997 | "pulse timer should be non-zero after first pulse") | 997 | "pulse timer should be non-zero after first pulse") | ||
| 998 | } | 998 | } | ||
| 999 | 999 | ||||
| 1000 | - | func TestPageMode_Pulse_DebounceResetOnClearOwnership(t *testing.T) { | 1000 | + | func TestVimMode_Pulse_DebounceResetOnClearOwnership(t *testing.T) { |
| 1001 | - | // Verify that clearPageModeOwnership resets the debounce timer | 1001 | + | // Verify that clearVimModeOwnership resets the debounce timer |
| 1002 | - | // so a subsequent pulse in a new page mode session is not skipped. | 1002 | + | // so a subsequent pulse in a new vim mode session is not skipped. |
| 1003 | // We check the timer state directly rather than triggering a second | 1003 | // We check the timer state directly rather than triggering a second | ||
| 1004 | // pulse (which would need pulse-cycle alternation handling). | 1004 | // pulse (which would need pulse-cycle alternation handling). | ||
| 1005 | 1005 | ||||
| 1006 | - | f := newSingleWindowPageModeFixture(t) | 1006 | + | f := newSingleWindowVimModeFixture(t) |
| 1007 | - | enterPageMode(t, f.app, f.overlay1A, f.bw1) | 1007 | + | enterVimMode(t, f.app, f.overlay1A, f.bw1) |
| 1008 | 1008 | ||||
| 1009 | // Pulse #1 fires and sets the timer. | 1009 | // Pulse #1 fires and sets the timer. | ||
| 1010 | setUpNormalPulse(f.overlay1A) | 1010 | setUpNormalPulse(f.overlay1A) | ||
| 1011 | - | f.app.triggerPageModePulse(context.Background(), false) | 1011 | + | f.app.triggerVimModePulse(context.Background(), false) |
| 1012 | - | assert.False(t, f.app.pageModePulseLastTime.IsZero(), | 1012 | + | assert.False(t, f.app.vimModePulseLastTime.IsZero(), |
| 1013 | "pulse timer should be non-zero after first pulse") | 1013 | "pulse timer should be non-zero after first pulse") | ||
| 1014 | 1014 | ||||
| 1015 | // Clear ownership — this resets the debounce timer. | 1015 | // Clear ownership — this resets the debounce timer. | ||
| 1016 | - | expectPageModeAccentHidden(f.overlay1A) | 1016 | + | expectVimModeAccentHidden(f.overlay1A) |
| 1017 | - | f.app.clearPageModeOwnership(context.Background(), f.bw1) | 1017 | + | f.app.clearVimModeOwnership(context.Background(), f.bw1) |
| 1018 | - | assert.Empty(t, f.bw1.pageModePaneID) | 1018 | + | assert.Empty(t, f.bw1.vimModePaneID) |
| 1019 | 1019 | ||||
| 1020 | // Debounce timer should now be back to zero. | 1020 | // Debounce timer should now be back to zero. | ||
| 1021 | - | assert.True(t, f.app.pageModePulseLastTime.IsZero(), | 1021 | + | assert.True(t, f.app.vimModePulseLastTime.IsZero(), |
| 1022 | - | "pulse timer should be reset after clearPageModeOwnership") | 1022 | + | "pulse timer should be reset after clearVimModeOwnership") |
| 1023 | } | 1023 | } | ||
| 1024 | 1024 | ||||
| 1025 | - | func TestPageMode_Pulse_DebounceFirstPulseAlwaysFires(t *testing.T) { | 1025 | + | func TestVimMode_Pulse_DebounceFirstPulseAlwaysFires(t *testing.T) { |
| 1026 | - | // Even if triggerPageModePulse is called rapidly on a fresh | 1026 | + | // Even if triggerVimModePulse is called rapidly on a fresh |
| 1027 | - | // App (e.g., first keystroke after page mode entry), the first | 1027 | + | // App (e.g., first keystroke after vim mode entry), the first |
| 1028 | // call must always fire. This is guaranteed by the zero value | 1028 | // call must always fire. This is guaranteed by the zero value | ||
| 1029 | // of time.Time producing a time.Since result much larger than | 1029 | // of time.Time producing a time.Since result much larger than | ||
| 1030 | // the debounce interval. | 1030 | // the debounce interval. | ||
| 1031 | - | f := newSingleWindowPageModeFixture(t) | 1031 | + | f := newSingleWindowVimModeFixture(t) |
| 1032 | - | enterPageMode(t, f.app, f.overlay1A, f.bw1) | 1032 | + | enterVimMode(t, f.app, f.overlay1A, f.bw1) |
| 1033 | 1033 | ||||
| 1034 | // Rapid double pulse — only first should fire regardless of order. | 1034 | // Rapid double pulse — only first should fire regardless of order. | ||
| 1035 | // Set up just one set of expectations. | 1035 | // Set up just one set of expectations. | ||
| 1036 | setUpNormalPulse(f.overlay1A) | 1036 | setUpNormalPulse(f.overlay1A) | ||
| 1037 | - | f.app.triggerPageModePulse(context.Background(), false) | 1037 | + | f.app.triggerVimModePulse(context.Background(), false) |
| 1038 | - | f.app.triggerPageModePulse(context.Background(), false) | 1038 | + | f.app.triggerVimModePulse(context.Background(), false) |
| 1039 | } | 1039 | } | ||
| 1040 | 1040 | ||||
| 1041 | // ============================================================================ | 1041 | // ============================================================================ | ||
| 1042 | - | // UI Scale: Page mode CSS uses relative (em) units | 1042 | + | // UI Scale: Vim mode CSS uses relative (em) units |
| 1043 | // ============================================================================ | 1043 | // ============================================================================ | ||
| 1044 | 1044 | ||||
| 1045 | - | func TestPageMode_CSSUsesEms(t *testing.T) { | 1045 | + | func TestVimMode_CSSUsesEms(t *testing.T) { |
| 1046 | css := theme.GenerateCSS(theme.DefaultDarkPalette()) | 1046 | css := theme.GenerateCSS(theme.DefaultDarkPalette()) | ||
| 1047 | 1047 | ||||
| 1048 | assert.Contains(t, css, "box-shadow: inset 0 0 0 0.125em", | 1048 | assert.Contains(t, css, "box-shadow: inset 0 0 0 0.125em", | ||
| 1049 | - | "page mode overlay box-shadow should use em for scaling") | 1049 | + | "vim mode overlay box-shadow should use em for scaling") |
| 1050 | assert.Contains(t, css, "0.2em", | 1050 | assert.Contains(t, css, "0.2em", | ||
| 1051 | - | "page mode overlay pulse glow radius uses em") | 1051 | + | "vim mode overlay pulse glow radius uses em") |
| 1052 | assert.Contains(t, css, "0.26em", | 1052 | assert.Contains(t, css, "0.26em", | ||
| 1053 | - | "page mode overlay fast-pulse glow radius uses em") | 1053 | + | "vim mode overlay fast-pulse glow radius uses em") |
| 1054 | - | assert.NotContains(t, css, "page-mode-indicator", | 1054 | + | assert.NotContains(t, css, "vim-mode-indicator", |
| 1055 | "removed PAGE label styling must not remain") | 1055 | "removed PAGE label styling must not remain") | ||
| 1056 | } | 1056 | } | ||
| 1057 | 1057 | ||||
| 1058 | - | func TestPageMode_CSSPulseTimingUsesDefaultTransitionDuration(t *testing.T) { | 1058 | + | func TestVimMode_CSSPulseTimingUsesDefaultTransitionDuration(t *testing.T) { |
| 1059 | // The pulse animation durations should be derived from the | 1059 | // The pulse animation durations should be derived from the | ||
| 1060 | // default transition duration (normal = 3x, fast = 6x). | 1060 | // default transition duration (normal = 3x, fast = 6x). | ||
| 1061 | // Default = 120ms → normal = 360ms, fast = 720ms. | 1061 | // Default = 120ms → normal = 360ms, fast = 720ms. | ||
| @@ -1067,7 +1067,7 @@ func TestPageMode_CSSPulseTimingUsesDefaultTransitionDuration(t *testing.T) { | |||||
| 1067 | "fast pulse duration should be 6x transition duration (720ms for 120ms base)") | 1067 | "fast pulse duration should be 6x transition duration (720ms for 120ms base)") | ||
| 1068 | } | 1068 | } | ||
| 1069 | 1069 | ||||
| 1070 | - | func TestPageMode_CSSWithCustomTransitionDuration(t *testing.T) { | 1070 | + | func TestVimMode_CSSWithCustomTransitionDuration(t *testing.T) { |
| 1071 | // Custom transition duration (e.g., 200ms) should produce | 1071 | // Custom transition duration (e.g., 200ms) should produce | ||
| 1072 | // adjusted pulse timings: normal = 600ms, fast = 1200ms. | 1072 | // adjusted pulse timings: normal = 600ms, fast = 1200ms. | ||
| 1073 | css := theme.GenerateCSSFullWithTiming( | 1073 | css := theme.GenerateCSSFullWithTiming( | ||
M internal/ui/browser_window.go +3 -3
| @@ -53,8 +53,8 @@ type browserWindow struct { | |||||
| 53 | sidebarVisible bool | 53 | sidebarVisible bool | ||
| 54 | activeSidebarKind nativeSidebarKind | 54 | activeSidebarKind nativeSidebarKind | ||
| 55 | 55 | ||||
| 56 | - | // pageModePaneID tracks this window's pane-local Page Mode accent/pulse owner. | 56 | + | // vimModePaneID tracks this window's pane-local Vim Mode accent/pulse owner. |
| 57 | - | pageModePaneID entity.PaneID | 57 | + | vimModePaneID entity.PaneID |
| 58 | } | 58 | } | ||
| 59 | 59 | ||||
| 60 | func (bw *browserWindow) detachInputForDestroy() { | 60 | func (bw *browserWindow) detachInputForDestroy() { | ||
| @@ -111,7 +111,7 @@ func (bw *browserWindow) clearShellState() { | |||||
| 111 | bw.favoritesSidebar = nil | 111 | bw.favoritesSidebar = nil | ||
| 112 | bw.historySidebarReloader = nil | 112 | bw.historySidebarReloader = nil | ||
| 113 | bw.activeSidebarKind = nativeSidebarNone | 113 | bw.activeSidebarKind = nativeSidebarNone | ||
| 114 | - | bw.pageModePaneID = "" | 114 | + | bw.vimModePaneID = "" |
| 115 | } | 115 | } | ||
| 116 | 116 | ||||
| 117 | func (bw *browserWindow) initChrome(ctx context.Context, a *App) { | 117 | func (bw *browserWindow) initChrome(ctx context.Context, a *App) { | ||
M internal/ui/component/pane_view.go +35 -35
| @@ -15,26 +15,26 @@ const ( | |||||
| 15 | // CSS class applied to active pane's border overlay | 15 | // CSS class applied to active pane's border overlay | ||
| 16 | activePaneClass = "pane-active" | 16 | activePaneClass = "pane-active" | ||
| 17 | 17 | ||||
| 18 | - | // pageModeActiveClass is added to the pane overlay when Page mode is active. | 18 | + | // vimModeActiveClass is added to the pane overlay when Vim mode is active. |
| 19 | // It produces a subtle local border accent using the pane mode color. | 19 | // It produces a subtle local border accent using the pane mode color. | ||
| 20 | - | pageModeActiveClass = "page-mode-active" | 20 | + | vimModeActiveClass = "vim-mode-active" |
| 21 | 21 | ||||
| 22 | - | // pageModePulseClass triggers a normal scroll pulse on the pane overlay. | 22 | + | // vimModePulseClass triggers a normal scroll pulse on the pane overlay. |
| 23 | // Repeated calls must remove-then-add to restart the CSS animation. | 23 | // Repeated calls must remove-then-add to restart the CSS animation. | ||
| 24 | - | pageModePulseClass = "page-mode-pulse" | 24 | + | vimModePulseClass = "vim-mode-pulse" |
| 25 | 25 | ||||
| 26 | - | // pageModeFastPulseClass triggers a fast/stronger scroll pulse. | 26 | + | // vimModeFastPulseClass triggers a fast/stronger scroll pulse. |
| 27 | - | pageModeFastPulseClass = "page-mode-pulse-fast" | 27 | + | vimModeFastPulseClass = "vim-mode-pulse-fast" |
| 28 | 28 | ||||
| 29 | // Alternate equivalent animations so repeated scrolls reliably restart | 29 | // Alternate equivalent animations so repeated scrolls reliably restart | ||
| 30 | // the GTK CSS animation. | 30 | // the GTK CSS animation. | ||
| 31 | - | pageModePulseCycleClassA = "page-mode-pulse-cycle-a" | 31 | + | vimModePulseCycleClassA = "vim-mode-pulse-cycle-a" |
| 32 | - | pageModePulseCycleClassB = "page-mode-pulse-cycle-b" | 32 | + | vimModePulseCycleClassB = "vim-mode-pulse-cycle-b" |
| 33 | 33 | ||||
| 34 | // Exported aliases for tests and external inspection. | 34 | // Exported aliases for tests and external inspection. | ||
| 35 | - | PageModeActiveClass = pageModeActiveClass | 35 | + | VimModeActiveClass = vimModeActiveClass |
| 36 | - | PageModePulseClass = pageModePulseClass | 36 | + | VimModePulseClass = vimModePulseClass |
| 37 | - | PageModeFastPulseClass = pageModeFastPulseClass | 37 | + | VimModeFastPulseClass = vimModeFastPulseClass |
| 38 | ) | 38 | ) | ||
| 39 | 39 | ||||
| 40 | // PaneView is a container for a single WebView with active state indication. | 40 | // PaneView is a container for a single WebView with active state indication. | ||
| @@ -52,8 +52,8 @@ type PaneView struct { | |||||
| 52 | paneID entity.PaneID | 52 | paneID entity.PaneID | ||
| 53 | isActive bool | 53 | isActive bool | ||
| 54 | 54 | ||||
| 55 | - | // Page Mode pane-local visual state. | 55 | + | // Vim Mode pane-local visual state. |
| 56 | - | pageMode bool | 56 | + | vimMode bool |
| 57 | pulseCycle bool | 57 | pulseCycle bool | ||
| 58 | 58 | ||||
| 59 | onFocusIn func(paneID entity.PaneID) | 59 | onFocusIn func(paneID entity.PaneID) | ||
| @@ -117,7 +117,7 @@ func NewPaneView(ctx context.Context, factory layout.WidgetFactory, paneID entit | |||||
| 117 | loading: loading, | 117 | loading: loading, | ||
| 118 | paneID: paneID, | 118 | paneID: paneID, | ||
| 119 | isActive: false, | 119 | isActive: false, | ||
| 120 | - | pageMode: false, | 120 | + | vimMode: false, |
| 121 | } | 121 | } | ||
| 122 | } | 122 | } | ||
| 123 | 123 | ||||
| @@ -546,39 +546,39 @@ func (pv *PaneView) HideLinkStatus() { | |||||
| 546 | } | 546 | } | ||
| 547 | } | 547 | } | ||
| 548 | 548 | ||||
| 549 | - | // SetPageMode activates or deactivates the pane-local Page Mode accent. | 549 | + | // SetVimMode activates or deactivates the pane-local Vim Mode accent. |
| 550 | - | func (pv *PaneView) SetPageMode(active bool) { | 550 | + | func (pv *PaneView) SetVimMode(active bool) { |
| 551 | pv.mu.Lock() | 551 | pv.mu.Lock() | ||
| 552 | defer pv.mu.Unlock() | 552 | defer pv.mu.Unlock() | ||
| 553 | 553 | ||||
| 554 | - | if pv.pageMode == active { | 554 | + | if pv.vimMode == active { |
| 555 | return | 555 | return | ||
| 556 | } | 556 | } | ||
| 557 | 557 | ||||
| 558 | - | pv.pageMode = active | 558 | + | pv.vimMode = active |
| 559 | 559 | ||||
| 560 | if active { | 560 | if active { | ||
| 561 | - | pv.overlay.AddCssClass(pageModeActiveClass) | 561 | + | pv.overlay.AddCssClass(vimModeActiveClass) |
| 562 | } else { | 562 | } else { | ||
| 563 | - | pv.overlay.RemoveCssClass(pageModeActiveClass) | 563 | + | pv.overlay.RemoveCssClass(vimModeActiveClass) |
| 564 | } | 564 | } | ||
| 565 | } | 565 | } | ||
| 566 | 566 | ||||
| 567 | - | // IsPageMode returns whether Page mode is currently active on this pane. | 567 | + | // IsVimMode returns whether Vim mode is currently active on this pane. |
| 568 | - | func (pv *PaneView) IsPageMode() bool { | 568 | + | func (pv *PaneView) IsVimMode() bool { |
| 569 | pv.mu.RLock() | 569 | pv.mu.RLock() | ||
| 570 | defer pv.mu.RUnlock() | 570 | defer pv.mu.RUnlock() | ||
| 571 | 571 | ||||
| 572 | - | return pv.pageMode | 572 | + | return pv.vimMode |
| 573 | } | 573 | } | ||
| 574 | 574 | ||||
| 575 | - | // TriggerPageModePulse triggers a normal scroll pulse on the pane overlay. | 575 | + | // TriggerVimModePulse triggers a normal scroll pulse on the pane overlay. |
| 576 | - | func (pv *PaneView) TriggerPageModePulse() { | 576 | + | func (pv *PaneView) TriggerVimModePulse() { |
| 577 | pv.triggerOverlayPulse(false) | 577 | pv.triggerOverlayPulse(false) | ||
| 578 | } | 578 | } | ||
| 579 | 579 | ||||
| 580 | - | // TriggerPageModePulseFast triggers a stronger, longer pane-overlay pulse. | 580 | + | // TriggerVimModePulseFast triggers a stronger, longer pane-overlay pulse. |
| 581 | - | func (pv *PaneView) TriggerPageModePulseFast() { | 581 | + | func (pv *PaneView) TriggerVimModePulseFast() { |
| 582 | pv.triggerOverlayPulse(true) | 582 | pv.triggerOverlayPulse(true) | ||
| 583 | } | 583 | } | ||
| 584 | 584 | ||||
| @@ -589,18 +589,18 @@ func (pv *PaneView) triggerOverlayPulse(fast bool) { | |||||
| 589 | pv.mu.Lock() | 589 | pv.mu.Lock() | ||
| 590 | defer pv.mu.Unlock() | 590 | defer pv.mu.Unlock() | ||
| 591 | 591 | ||||
| 592 | - | pv.overlay.RemoveCssClass(pageModePulseClass) | 592 | + | pv.overlay.RemoveCssClass(vimModePulseClass) |
| 593 | - | pv.overlay.RemoveCssClass(pageModeFastPulseClass) | 593 | + | pv.overlay.RemoveCssClass(vimModeFastPulseClass) |
| 594 | - | pv.overlay.RemoveCssClass(pageModePulseCycleClassA) | 594 | + | pv.overlay.RemoveCssClass(vimModePulseCycleClassA) |
| 595 | - | pv.overlay.RemoveCssClass(pageModePulseCycleClassB) | 595 | + | pv.overlay.RemoveCssClass(vimModePulseCycleClassB) |
| 596 | if fast { | 596 | if fast { | ||
| 597 | - | pv.overlay.AddCssClass(pageModeFastPulseClass) | 597 | + | pv.overlay.AddCssClass(vimModeFastPulseClass) |
| 598 | } else { | 598 | } else { | ||
| 599 | - | pv.overlay.AddCssClass(pageModePulseClass) | 599 | + | pv.overlay.AddCssClass(vimModePulseClass) |
| 600 | } | 600 | } | ||
| 601 | - | cycle := pageModePulseCycleClassA | 601 | + | cycle := vimModePulseCycleClassA |
| 602 | if pv.pulseCycle { | 602 | if pv.pulseCycle { | ||
| 603 | - | cycle = pageModePulseCycleClassB | 603 | + | cycle = vimModePulseCycleClassB |
| 604 | } | 604 | } | ||
| 605 | pv.pulseCycle = !pv.pulseCycle | 605 | pv.pulseCycle = !pv.pulseCycle | ||
| 606 | pv.overlay.AddCssClass(cycle) | 606 | pv.overlay.AddCssClass(cycle) | ||
M internal/ui/component/pane_view_test.go +43 -43
| @@ -682,7 +682,7 @@ func setupPaneViewMocksNoWebView( | |||||
| 682 | mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once() | 682 | mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once() | ||
| 683 | } | 683 | } | ||
| 684 | 684 | ||||
| 685 | - | func TestSetPageMode_TrueAddsPaneAccentWithoutCreatingLabel(t *testing.T) { | 685 | + | func TestSetVimMode_TrueAddsPaneAccentWithoutCreatingLabel(t *testing.T) { |
| 686 | mockFactory := mocks.NewMockWidgetFactory(t) | 686 | mockFactory := mocks.NewMockWidgetFactory(t) | ||
| 687 | mockOverlay := mocks.NewMockOverlayWidget(t) | 687 | mockOverlay := mocks.NewMockOverlayWidget(t) | ||
| 688 | mockBorderBox := mocks.NewMockBoxWidget(t) | 688 | mockBorderBox := mocks.NewMockBoxWidget(t) | ||
| @@ -690,13 +690,13 @@ func TestSetPageMode_TrueAddsPaneAccentWithoutCreatingLabel(t *testing.T) { | |||||
| 690 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | 690 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | ||
| 691 | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | 691 | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | ||
| 692 | 692 | ||||
| 693 | - | mockOverlay.EXPECT().AddCssClass("page-mode-active").Once() | 693 | + | mockOverlay.EXPECT().AddCssClass("vim-mode-active").Once() |
| 694 | - | pv.SetPageMode(true) | 694 | + | pv.SetVimMode(true) |
| 695 | 695 | ||||
| 696 | - | require.True(t, pv.IsPageMode()) | 696 | + | require.True(t, pv.IsVimMode()) |
| 697 | } | 697 | } | ||
| 698 | 698 | ||||
| 699 | - | func TestSetPageMode_FalseRemovesPaneAccent(t *testing.T) { | 699 | + | func TestSetVimMode_FalseRemovesPaneAccent(t *testing.T) { |
| 700 | mockFactory := mocks.NewMockWidgetFactory(t) | 700 | mockFactory := mocks.NewMockWidgetFactory(t) | ||
| 701 | mockOverlay := mocks.NewMockOverlayWidget(t) | 701 | mockOverlay := mocks.NewMockOverlayWidget(t) | ||
| 702 | mockBorderBox := mocks.NewMockBoxWidget(t) | 702 | mockBorderBox := mocks.NewMockBoxWidget(t) | ||
| @@ -704,15 +704,15 @@ func TestSetPageMode_FalseRemovesPaneAccent(t *testing.T) { | |||||
| 704 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | 704 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | ||
| 705 | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | 705 | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | ||
| 706 | 706 | ||||
| 707 | - | mockOverlay.EXPECT().AddCssClass("page-mode-active").Once() | 707 | + | mockOverlay.EXPECT().AddCssClass("vim-mode-active").Once() |
| 708 | - | pv.SetPageMode(true) | 708 | + | pv.SetVimMode(true) |
| 709 | - | mockOverlay.EXPECT().RemoveCssClass("page-mode-active").Once() | 709 | + | mockOverlay.EXPECT().RemoveCssClass("vim-mode-active").Once() |
| 710 | - | pv.SetPageMode(false) | 710 | + | pv.SetVimMode(false) |
| 711 | 711 | ||||
| 712 | - | require.False(t, pv.IsPageMode()) | 712 | + | require.False(t, pv.IsVimMode()) |
| 713 | } | 713 | } | ||
| 714 | 714 | ||||
| 715 | - | func TestSetPageMode_NoChangeWhenSameState(t *testing.T) { | 715 | + | func TestSetVimMode_NoChangeWhenSameState(t *testing.T) { |
| 716 | mockFactory := mocks.NewMockWidgetFactory(t) | 716 | mockFactory := mocks.NewMockWidgetFactory(t) | ||
| 717 | mockOverlay := mocks.NewMockOverlayWidget(t) | 717 | mockOverlay := mocks.NewMockOverlayWidget(t) | ||
| 718 | mockBorderBox := mocks.NewMockBoxWidget(t) | 718 | mockBorderBox := mocks.NewMockBoxWidget(t) | ||
| @@ -720,12 +720,12 @@ func TestSetPageMode_NoChangeWhenSameState(t *testing.T) { | |||||
| 720 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | 720 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | ||
| 721 | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | 721 | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | ||
| 722 | 722 | ||||
| 723 | - | pv.SetPageMode(false) | 723 | + | pv.SetVimMode(false) |
| 724 | 724 | ||||
| 725 | - | require.False(t, pv.IsPageMode()) | 725 | + | require.False(t, pv.IsVimMode()) |
| 726 | } | 726 | } | ||
| 727 | 727 | ||||
| 728 | - | func TestSetPageMode_TrueThenTrueIsIdempotent(t *testing.T) { | 728 | + | func TestSetVimMode_TrueThenTrueIsIdempotent(t *testing.T) { |
| 729 | mockFactory := mocks.NewMockWidgetFactory(t) | 729 | mockFactory := mocks.NewMockWidgetFactory(t) | ||
| 730 | mockOverlay := mocks.NewMockOverlayWidget(t) | 730 | mockOverlay := mocks.NewMockOverlayWidget(t) | ||
| 731 | mockBorderBox := mocks.NewMockBoxWidget(t) | 731 | mockBorderBox := mocks.NewMockBoxWidget(t) | ||
| @@ -733,27 +733,27 @@ func TestSetPageMode_TrueThenTrueIsIdempotent(t *testing.T) { | |||||
| 733 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | 733 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | ||
| 734 | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | 734 | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | ||
| 735 | 735 | ||||
| 736 | - | mockOverlay.EXPECT().AddCssClass("page-mode-active").Once() | 736 | + | mockOverlay.EXPECT().AddCssClass("vim-mode-active").Once() |
| 737 | - | pv.SetPageMode(true) | 737 | + | pv.SetVimMode(true) |
| 738 | - | pv.SetPageMode(true) | 738 | + | pv.SetVimMode(true) |
| 739 | 739 | ||||
| 740 | - | require.True(t, pv.IsPageMode()) | 740 | + | require.True(t, pv.IsVimMode()) |
| 741 | } | 741 | } | ||
| 742 | 742 | ||||
| 743 | - | func expectPageModeOverlayPulse(overlay *mocks.MockOverlayWidget, fast bool, cycle string) { | 743 | + | func expectVimModeOverlayPulse(overlay *mocks.MockOverlayWidget, fast bool, cycle string) { |
| 744 | - | overlay.EXPECT().RemoveCssClass("page-mode-pulse").Once() | 744 | + | overlay.EXPECT().RemoveCssClass("vim-mode-pulse").Once() |
| 745 | - | overlay.EXPECT().RemoveCssClass("page-mode-pulse-fast").Once() | 745 | + | overlay.EXPECT().RemoveCssClass("vim-mode-pulse-fast").Once() |
| 746 | - | overlay.EXPECT().RemoveCssClass("page-mode-pulse-cycle-a").Once() | 746 | + | overlay.EXPECT().RemoveCssClass("vim-mode-pulse-cycle-a").Once() |
| 747 | - | overlay.EXPECT().RemoveCssClass("page-mode-pulse-cycle-b").Once() | 747 | + | overlay.EXPECT().RemoveCssClass("vim-mode-pulse-cycle-b").Once() |
| 748 | if fast { | 748 | if fast { | ||
| 749 | - | overlay.EXPECT().AddCssClass("page-mode-pulse-fast").Once() | 749 | + | overlay.EXPECT().AddCssClass("vim-mode-pulse-fast").Once() |
| 750 | } else { | 750 | } else { | ||
| 751 | - | overlay.EXPECT().AddCssClass("page-mode-pulse").Once() | 751 | + | overlay.EXPECT().AddCssClass("vim-mode-pulse").Once() |
| 752 | } | 752 | } | ||
| 753 | overlay.EXPECT().AddCssClass(cycle).Once() | 753 | overlay.EXPECT().AddCssClass(cycle).Once() | ||
| 754 | } | 754 | } | ||
| 755 | 755 | ||||
| 756 | - | func TestTriggerPageModePulse_PulsesOnlyOverlay(t *testing.T) { | 756 | + | func TestTriggerVimModePulse_PulsesOnlyOverlay(t *testing.T) { |
| 757 | mockFactory := mocks.NewMockWidgetFactory(t) | 757 | mockFactory := mocks.NewMockWidgetFactory(t) | ||
| 758 | mockOverlay := mocks.NewMockOverlayWidget(t) | 758 | mockOverlay := mocks.NewMockOverlayWidget(t) | ||
| 759 | mockBorderBox := mocks.NewMockBoxWidget(t) | 759 | mockBorderBox := mocks.NewMockBoxWidget(t) | ||
| @@ -761,11 +761,11 @@ func TestTriggerPageModePulse_PulsesOnlyOverlay(t *testing.T) { | |||||
| 761 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | 761 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | ||
| 762 | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | 762 | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | ||
| 763 | 763 | ||||
| 764 | - | expectPageModeOverlayPulse(mockOverlay, false, "page-mode-pulse-cycle-a") | 764 | + | expectVimModeOverlayPulse(mockOverlay, false, "vim-mode-pulse-cycle-a") |
| 765 | - | pv.TriggerPageModePulse() | 765 | + | pv.TriggerVimModePulse() |
| 766 | } | 766 | } | ||
| 767 | 767 | ||||
| 768 | - | func TestTriggerPageModePulseFast_PulsesOnlyOverlay(t *testing.T) { | 768 | + | func TestTriggerVimModePulseFast_PulsesOnlyOverlay(t *testing.T) { |
| 769 | mockFactory := mocks.NewMockWidgetFactory(t) | 769 | mockFactory := mocks.NewMockWidgetFactory(t) | ||
| 770 | mockOverlay := mocks.NewMockOverlayWidget(t) | 770 | mockOverlay := mocks.NewMockOverlayWidget(t) | ||
| 771 | mockBorderBox := mocks.NewMockBoxWidget(t) | 771 | mockBorderBox := mocks.NewMockBoxWidget(t) | ||
| @@ -773,11 +773,11 @@ func TestTriggerPageModePulseFast_PulsesOnlyOverlay(t *testing.T) { | |||||
| 773 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | 773 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | ||
| 774 | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | 774 | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | ||
| 775 | 775 | ||||
| 776 | - | expectPageModeOverlayPulse(mockOverlay, true, "page-mode-pulse-cycle-a") | 776 | + | expectVimModeOverlayPulse(mockOverlay, true, "vim-mode-pulse-cycle-a") |
| 777 | - | pv.TriggerPageModePulseFast() | 777 | + | pv.TriggerVimModePulseFast() |
| 778 | } | 778 | } | ||
| 779 | 779 | ||||
| 780 | - | func TestTriggerPageModePulse_RepeatedCallsReTriggerOverlayAnimation(t *testing.T) { | 780 | + | func TestTriggerVimModePulse_RepeatedCallsReTriggerOverlayAnimation(t *testing.T) { |
| 781 | mockFactory := mocks.NewMockWidgetFactory(t) | 781 | mockFactory := mocks.NewMockWidgetFactory(t) | ||
| 782 | mockOverlay := mocks.NewMockOverlayWidget(t) | 782 | mockOverlay := mocks.NewMockOverlayWidget(t) | ||
| 783 | mockBorderBox := mocks.NewMockBoxWidget(t) | 783 | mockBorderBox := mocks.NewMockBoxWidget(t) | ||
| @@ -785,15 +785,15 @@ func TestTriggerPageModePulse_RepeatedCallsReTriggerOverlayAnimation(t *testing. | |||||
| 785 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | 785 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | ||
| 786 | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | 786 | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | ||
| 787 | 787 | ||||
| 788 | - | expectPageModeOverlayPulse(mockOverlay, false, "page-mode-pulse-cycle-a") | 788 | + | expectVimModeOverlayPulse(mockOverlay, false, "vim-mode-pulse-cycle-a") |
| 789 | - | pv.TriggerPageModePulse() | 789 | + | pv.TriggerVimModePulse() |
| 790 | - | expectPageModeOverlayPulse(mockOverlay, true, "page-mode-pulse-cycle-b") | 790 | + | expectVimModeOverlayPulse(mockOverlay, true, "vim-mode-pulse-cycle-b") |
| 791 | - | pv.TriggerPageModePulseFast() | 791 | + | pv.TriggerVimModePulseFast() |
| 792 | - | expectPageModeOverlayPulse(mockOverlay, false, "page-mode-pulse-cycle-a") | 792 | + | expectVimModeOverlayPulse(mockOverlay, false, "vim-mode-pulse-cycle-a") |
| 793 | - | pv.TriggerPageModePulse() | 793 | + | pv.TriggerVimModePulse() |
| 794 | } | 794 | } | ||
| 795 | 795 | ||||
| 796 | - | func TestPageMode_CleanupHasNoIndicatorOverlay(t *testing.T) { | 796 | + | func TestVimMode_CleanupHasNoIndicatorOverlay(t *testing.T) { |
| 797 | mockFactory := mocks.NewMockWidgetFactory(t) | 797 | mockFactory := mocks.NewMockWidgetFactory(t) | ||
| 798 | mockOverlay := mocks.NewMockOverlayWidget(t) | 798 | mockOverlay := mocks.NewMockOverlayWidget(t) | ||
| 799 | mockBorderBox := mocks.NewMockBoxWidget(t) | 799 | mockBorderBox := mocks.NewMockBoxWidget(t) | ||
| @@ -801,15 +801,15 @@ func TestPageMode_CleanupHasNoIndicatorOverlay(t *testing.T) { | |||||
| 801 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | 801 | setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView) | ||
| 802 | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | 802 | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | ||
| 803 | 803 | ||||
| 804 | - | mockOverlay.EXPECT().AddCssClass("page-mode-active").Once() | 804 | + | mockOverlay.EXPECT().AddCssClass("vim-mode-active").Once() |
| 805 | - | pv.SetPageMode(true) | 805 | + | pv.SetVimMode(true) |
| 806 | mockOverlay.EXPECT().RemoveOverlay(mock.Anything).Once() | 806 | mockOverlay.EXPECT().RemoveOverlay(mock.Anything).Once() | ||
| 807 | mockOverlay.EXPECT().SetChild(nil).Once() | 807 | mockOverlay.EXPECT().SetChild(nil).Once() | ||
| 808 | 808 | ||||
| 809 | pv.Cleanup() | 809 | pv.Cleanup() | ||
| 810 | } | 810 | } | ||
| 811 | 811 | ||||
| 812 | - | func TestPageMode_NewPaneViewInactiveByDefault(t *testing.T) { | 812 | + | func TestVimMode_NewPaneViewInactiveByDefault(t *testing.T) { |
| 813 | mockFactory := mocks.NewMockWidgetFactory(t) | 813 | mockFactory := mocks.NewMockWidgetFactory(t) | ||
| 814 | mockOverlay := mocks.NewMockOverlayWidget(t) | 814 | mockOverlay := mocks.NewMockOverlayWidget(t) | ||
| 815 | mockBorderBox := mocks.NewMockBoxWidget(t) | 815 | mockBorderBox := mocks.NewMockBoxWidget(t) | ||
| @@ -818,5 +818,5 @@ func TestPageMode_NewPaneViewInactiveByDefault(t *testing.T) { | |||||
| 818 | 818 | ||||
| 819 | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | 819 | pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView) | ||
| 820 | 820 | ||||
| 821 | - | require.False(t, pv.IsPageMode()) | 821 | + | require.False(t, pv.IsVimMode()) |
| 822 | } | 822 | } | ||
M internal/ui/coordinator/navigation.go +1 -1
| @@ -304,7 +304,7 @@ func (c *NavigationCoordinator) ScrollWebView(ctx context.Context, wv port.WebVi | |||||
| 304 | return c.pageScrollUC.Scroll(ctx, wv, cmd) | 304 | return c.pageScrollUC.Scroll(ctx, wv, cmd) | ||
| 305 | } | 305 | } | ||
| 306 | 306 | ||||
| 307 | - | // ScrollWebViewContinuous applies one autonomous held-key page-scroll step. | 307 | + | // ScrollWebViewContinuous applies one autonomous held-key vim-scroll step. |
| 308 | func (c *NavigationCoordinator) ScrollWebViewContinuous(ctx context.Context, wv port.WebView, cmd usecase.PageScrollCommand) error { | 308 | func (c *NavigationCoordinator) ScrollWebViewContinuous(ctx context.Context, wv port.WebView, cmd usecase.PageScrollCommand) error { | ||
| 309 | if err := requireWebView(wv); err != nil { | 309 | if err := requireWebView(wv); err != nil { | ||
| 310 | return err | 310 | return err | ||
M internal/ui/dispatcher/keyboard.go +37 -37
| @@ -52,7 +52,7 @@ type KeyboardDispatcher struct { | |||||
| 52 | onMovePaneToTab func(ctx context.Context, paneID entity.PaneID) error | 52 | onMovePaneToTab func(ctx context.Context, paneID entity.PaneID) error | ||
| 53 | onMovePaneToNext func(ctx context.Context, paneID entity.PaneID) error | 53 | onMovePaneToNext func(ctx context.Context, paneID entity.PaneID) error | ||
| 54 | onEjectPaneToWindow func(ctx context.Context, paneID entity.PaneID) error | 54 | onEjectPaneToWindow func(ctx context.Context, paneID entity.PaneID) error | ||
| 55 | - | onPageModePulse func(ctx context.Context, fast bool) | 55 | + | onVimModePulse func(ctx context.Context, fast bool) |
| 56 | onToggleHistorySidebar func(ctx context.Context) error | 56 | onToggleHistorySidebar func(ctx context.Context) error | ||
| 57 | onToggleFavoritesSidebar func(ctx context.Context) error | 57 | onToggleFavoritesSidebar func(ctx context.Context) error | ||
| 58 | onToggleCurrentFavorite func(ctx context.Context) error | 58 | onToggleCurrentFavorite func(ctx context.Context) error | ||
| @@ -157,19 +157,19 @@ func (d *KeyboardDispatcher) SetOnOpenFloatingTarget(fn func(ctx context.Context | |||||
| 157 | d.onOpenFloating = fn | 157 | d.onOpenFloating = fn | ||
| 158 | } | 158 | } | ||
| 159 | 159 | ||||
| 160 | - | // SetOnPageModePulse registers a callback for pane-local Page mode pulse | 160 | + | // SetOnVimModePulse registers a callback for pane-local Vim mode pulse |
| 161 | // visual feedback triggered after a page scroll action. | 161 | // visual feedback triggered after a page scroll action. | ||
| 162 | // The cb receives a bool indicating whether the pulse should be "fast" | 162 | // The cb receives a bool indicating whether the pulse should be "fast" | ||
| 163 | // (stronger/longer for up-fast/down-fast). | 163 | // (stronger/longer for up-fast/down-fast). | ||
| 164 | // If nil, pulse callbacks are no-ops. | 164 | // If nil, pulse callbacks are no-ops. | ||
| 165 | - | func (d *KeyboardDispatcher) SetOnPageModePulse(cb func(ctx context.Context, fast bool)) { | 165 | + | func (d *KeyboardDispatcher) SetOnVimModePulse(cb func(ctx context.Context, fast bool)) { |
| 166 | - | d.onPageModePulse = cb | 166 | + | d.onVimModePulse = cb |
| 167 | } | 167 | } | ||
| 168 | 168 | ||||
| 169 | // triggerPulse calls the registered pulse callback if set, or no-ops otherwise. | 169 | // triggerPulse calls the registered pulse callback if set, or no-ops otherwise. | ||
| 170 | func (d *KeyboardDispatcher) triggerPulse(ctx context.Context, fast bool) { | 170 | func (d *KeyboardDispatcher) triggerPulse(ctx context.Context, fast bool) { | ||
| 171 | - | if d.onPageModePulse != nil { | 171 | + | if d.onVimModePulse != nil { |
| 172 | - | d.onPageModePulse(ctx, fast) | 172 | + | d.onVimModePulse(ctx, fast) |
| 173 | } | 173 | } | ||
| 174 | } | 174 | } | ||
| 175 | 175 | ||||
| @@ -266,13 +266,13 @@ func (d *KeyboardDispatcher) initActionHandlers() { | |||||
| 266 | input.ActionZoomIn: func(ctx context.Context) error { return d.handleZoom(ctx, "in") }, | 266 | input.ActionZoomIn: func(ctx context.Context) error { return d.handleZoom(ctx, "in") }, | ||
| 267 | input.ActionZoomOut: func(ctx context.Context) error { return d.handleZoom(ctx, "out") }, | 267 | input.ActionZoomOut: func(ctx context.Context) error { return d.handleZoom(ctx, "out") }, | ||
| 268 | input.ActionZoomReset: func(ctx context.Context) error { return d.handleZoom(ctx, "reset") }, | 268 | input.ActionZoomReset: func(ctx context.Context) error { return d.handleZoom(ctx, "reset") }, | ||
| 269 | - | // Page mode scroll actions — single mapping drives tap pulse + command. | 269 | + | // Vim mode scroll actions — single mapping drives tap pulse + command. |
| 270 | - | input.ActionPageScrollLeft: d.pageScrollTapHandler(input.ActionPageScrollLeft), | 270 | + | input.ActionVimScrollLeft: d.vimScrollTapHandler(input.ActionVimScrollLeft), |
| 271 | - | input.ActionPageScrollRight: d.pageScrollTapHandler(input.ActionPageScrollRight), | 271 | + | input.ActionVimScrollRight: d.vimScrollTapHandler(input.ActionVimScrollRight), |
| 272 | - | input.ActionPageScrollUp: d.pageScrollTapHandler(input.ActionPageScrollUp), | 272 | + | input.ActionVimScrollUp: d.vimScrollTapHandler(input.ActionVimScrollUp), |
| 273 | - | input.ActionPageScrollDown: d.pageScrollTapHandler(input.ActionPageScrollDown), | 273 | + | input.ActionVimScrollDown: d.vimScrollTapHandler(input.ActionVimScrollDown), |
| 274 | - | input.ActionPageScrollUpFast: d.pageScrollTapHandler(input.ActionPageScrollUpFast), | 274 | + | input.ActionVimScrollUpFast: d.vimScrollTapHandler(input.ActionVimScrollUpFast), |
| 275 | - | input.ActionPageScrollDownFast: d.pageScrollTapHandler(input.ActionPageScrollDownFast), | 275 | + | input.ActionVimScrollDownFast: d.vimScrollTapHandler(input.ActionVimScrollDownFast), |
| 276 | // UI | 276 | // UI | ||
| 277 | input.ActionOpenOmnibox: d.navCoord.OpenOmnibox, | 277 | input.ActionOpenOmnibox: d.navCoord.OpenOmnibox, | ||
| 278 | input.ActionOpenFind: d.handleFindOpen, | 278 | input.ActionOpenFind: d.handleFindOpen, | ||
| @@ -322,50 +322,50 @@ func (d *KeyboardDispatcher) initActionHandlers() { | |||||
| 322 | } | 322 | } | ||
| 323 | } | 323 | } | ||
| 324 | 324 | ||||
| 325 | - | // DispatchPageScrollLifecycle routes autonomous held-key page scroll events. | 325 | + | // DispatchVimScrollLifecycle routes autonomous held-key page scroll events. |
| 326 | // Stop deliberately avoids the normal action map so it emits neither another | 326 | // Stop deliberately avoids the normal action map so it emits neither another | ||
| 327 | // scroll tap nor visual pulse. | 327 | // scroll tap nor visual pulse. | ||
| 328 | - | func (d *KeyboardDispatcher) DispatchPageScrollLifecycle(ctx context.Context, action input.Action, phase input.PageScrollPhase) error { | 328 | + | func (d *KeyboardDispatcher) DispatchVimScrollLifecycle(ctx context.Context, action input.Action, phase input.VimScrollPhase) error { |
| 329 | - | spec, ok := pageScrollSpec(action) | 329 | + | spec, ok := vimScrollSpec(action) |
| 330 | if !ok { | 330 | if !ok { | ||
| 331 | return nil | 331 | return nil | ||
| 332 | } | 332 | } | ||
| 333 | return d.withActiveWebView(ctx, "page scroll lifecycle", func(wv port.WebView) error { | 333 | return d.withActiveWebView(ctx, "page scroll lifecycle", func(wv port.WebView) error { | ||
| 334 | - | if phase == input.PageScrollStop { | 334 | + | if phase == input.VimScrollStop { |
| 335 | return d.navCoord.StopPageScroll(ctx, wv) | 335 | return d.navCoord.StopPageScroll(ctx, wv) | ||
| 336 | } | 336 | } | ||
| 337 | return d.navCoord.ScrollWebViewContinuous(ctx, wv, spec.cmd) | 337 | return d.navCoord.ScrollWebViewContinuous(ctx, wv, spec.cmd) | ||
| 338 | }) | 338 | }) | ||
| 339 | } | 339 | } | ||
| 340 | 340 | ||||
| 341 | - | type pageScrollActionSpec struct { | 341 | + | type vimScrollActionSpec struct { |
| 342 | cmd usecase.PageScrollCommand | 342 | cmd usecase.PageScrollCommand | ||
| 343 | fast bool | 343 | fast bool | ||
| 344 | } | 344 | } | ||
| 345 | 345 | ||||
| 346 | - | // pageScrollSpec is the single source of truth for Page Mode action→command | 346 | + | // vimScrollSpec is the single source of truth for Vim Mode action→command |
| 347 | // and fast/slow pulse used by tap handlers and held lifecycle routing. | 347 | // and fast/slow pulse used by tap handlers and held lifecycle routing. | ||
| 348 | - | func pageScrollSpec(action input.Action) (pageScrollActionSpec, bool) { | 348 | + | func vimScrollSpec(action input.Action) (vimScrollActionSpec, bool) { |
| 349 | switch action { | 349 | switch action { | ||
| 350 | - | case input.ActionPageScrollLeft: | 350 | + | case input.ActionVimScrollLeft: |
| 351 | - | return pageScrollActionSpec{cmd: usecase.PageScrollLeft, fast: false}, true | 351 | + | return vimScrollActionSpec{cmd: usecase.PageScrollLeft, fast: false}, true |
| 352 | - | case input.ActionPageScrollRight: | 352 | + | case input.ActionVimScrollRight: |
| 353 | - | return pageScrollActionSpec{cmd: usecase.PageScrollRight, fast: false}, true | 353 | + | return vimScrollActionSpec{cmd: usecase.PageScrollRight, fast: false}, true |
| 354 | - | case input.ActionPageScrollUp: | 354 | + | case input.ActionVimScrollUp: |
| 355 | - | return pageScrollActionSpec{cmd: usecase.PageScrollUp, fast: false}, true | 355 | + | return vimScrollActionSpec{cmd: usecase.PageScrollUp, fast: false}, true |
| 356 | - | case input.ActionPageScrollDown: | 356 | + | case input.ActionVimScrollDown: |
| 357 | - | return pageScrollActionSpec{cmd: usecase.PageScrollDown, fast: false}, true | 357 | + | return vimScrollActionSpec{cmd: usecase.PageScrollDown, fast: false}, true |
| 358 | - | case input.ActionPageScrollUpFast: | 358 | + | case input.ActionVimScrollUpFast: |
| 359 | - | return pageScrollActionSpec{cmd: usecase.PageScrollUpFast, fast: true}, true | 359 | + | return vimScrollActionSpec{cmd: usecase.PageScrollUpFast, fast: true}, true |
| 360 | - | case input.ActionPageScrollDownFast: | 360 | + | case input.ActionVimScrollDownFast: |
| 361 | - | return pageScrollActionSpec{cmd: usecase.PageScrollDownFast, fast: true}, true | 361 | + | return vimScrollActionSpec{cmd: usecase.PageScrollDownFast, fast: true}, true |
| 362 | default: | 362 | default: | ||
| 363 | - | return pageScrollActionSpec{}, false | 363 | + | return vimScrollActionSpec{}, false |
| 364 | } | 364 | } | ||
| 365 | } | 365 | } | ||
| 366 | 366 | ||||
| 367 | - | func (d *KeyboardDispatcher) pageScrollTapHandler(action input.Action) func(context.Context) error { | 367 | + | func (d *KeyboardDispatcher) vimScrollTapHandler(action input.Action) func(context.Context) error { |
| 368 | - | spec, ok := pageScrollSpec(action) | 368 | + | spec, ok := vimScrollSpec(action) |
| 369 | if !ok { | 369 | if !ok { | ||
| 370 | return func(context.Context) error { return nil } | 370 | return func(context.Context) error { return nil } | ||
| 371 | } | 371 | } | ||
| @@ -381,8 +381,8 @@ func (d *KeyboardDispatcher) pageScrollTapHandler(action input.Action) func(cont | |||||
| 381 | } | 381 | } | ||
| 382 | } | 382 | } | ||
| 383 | 383 | ||||
| 384 | - | func pageScrollCommand(action input.Action) (usecase.PageScrollCommand, bool) { | 384 | + | func vimScrollCommand(action input.Action) (usecase.PageScrollCommand, bool) { |
| 385 | - | spec, ok := pageScrollSpec(action) | 385 | + | spec, ok := vimScrollSpec(action) |
| 386 | if !ok { | 386 | if !ok { | ||
| 387 | return 0, false | 387 | return 0, false | ||
| 388 | } | 388 | } | ||
M internal/ui/dispatcher/keyboard_test.go +28 -28
| @@ -220,7 +220,7 @@ func (wv *lifecycleScrollableWebView) CancelPageScroll(context.Context) { | |||||
| 220 | wv.cancelCalls++ | 220 | wv.cancelCalls++ | ||
| 221 | } | 221 | } | ||
| 222 | 222 | ||||
| 223 | - | func TestKeyboardDispatcher_PageModeActionsRouteToCorrectScrollCommand(t *testing.T) { | 223 | + | func TestKeyboardDispatcher_VimModeActionsRouteToCorrectScrollCommand(t *testing.T) { |
| 224 | ctx := context.Background() | 224 | ctx := context.Background() | ||
| 225 | 225 | ||||
| 226 | tests := []struct { | 226 | tests := []struct { | ||
| @@ -229,12 +229,12 @@ func TestKeyboardDispatcher_PageModeActionsRouteToCorrectScrollCommand(t *testin | |||||
| 229 | expectedDx int | 229 | expectedDx int | ||
| 230 | expectedDy int | 230 | expectedDy int | ||
| 231 | }{ | 231 | }{ | ||
| 232 | - | {input.ActionPageScrollLeft, usecase.PageScrollLeft, -80, 0}, | 232 | + | {input.ActionVimScrollLeft, usecase.PageScrollLeft, -80, 0}, |
| 233 | - | {input.ActionPageScrollRight, usecase.PageScrollRight, 80, 0}, | 233 | + | {input.ActionVimScrollRight, usecase.PageScrollRight, 80, 0}, |
| 234 | - | {input.ActionPageScrollUp, usecase.PageScrollUp, 0, -80}, | 234 | + | {input.ActionVimScrollUp, usecase.PageScrollUp, 0, -80}, |
| 235 | - | {input.ActionPageScrollDown, usecase.PageScrollDown, 0, 80}, | 235 | + | {input.ActionVimScrollDown, usecase.PageScrollDown, 0, 80}, |
| 236 | - | {input.ActionPageScrollUpFast, usecase.PageScrollUpFast, 0, -320}, | 236 | + | {input.ActionVimScrollUpFast, usecase.PageScrollUpFast, 0, -320}, |
| 237 | - | {input.ActionPageScrollDownFast, usecase.PageScrollDownFast, 0, 320}, | 237 | + | {input.ActionVimScrollDownFast, usecase.PageScrollDownFast, 0, 320}, |
| 238 | } | 238 | } | ||
| 239 | 239 | ||||
| 240 | for _, tc := range tests { | 240 | for _, tc := range tests { | ||
| @@ -268,7 +268,7 @@ func TestKeyboardDispatcher_PageModeActionsRouteToCorrectScrollCommand(t *testin | |||||
| 268 | } | 268 | } | ||
| 269 | } | 269 | } | ||
| 270 | 270 | ||||
| 271 | - | func TestKeyboardDispatcher_PageScrollLifecycleRoutesContinuousAndStop(t *testing.T) { | 271 | + | func TestKeyboardDispatcher_VimScrollLifecycleRoutesContinuousAndStop(t *testing.T) { |
| 272 | ctx := context.Background() | 272 | ctx := context.Background() | ||
| 273 | base := mocks.NewMockWebView(t) | 273 | base := mocks.NewMockWebView(t) | ||
| 274 | scroller := mocks.NewMockPageScrollable(t) | 274 | scroller := mocks.NewMockPageScrollable(t) | ||
| @@ -282,12 +282,12 @@ func TestKeyboardDispatcher_PageScrollLifecycleRoutesContinuousAndStop(t *testin | |||||
| 282 | scroller.EXPECT().ScrollPage(ctx, port.PageScrollRequest{ | 282 | scroller.EXPECT().ScrollPage(ctx, port.PageScrollRequest{ | ||
| 283 | Command: port.PageScrollCommandDown, FallbackDY: 80, Continuous: true, | 283 | Command: port.PageScrollCommandDown, FallbackDY: 80, Continuous: true, | ||
| 284 | }).Return(nil).Once() | 284 | }).Return(nil).Once() | ||
| 285 | - | require.NoError(t, d.DispatchPageScrollLifecycle(ctx, input.ActionPageScrollDown, input.PageScrollContinuous)) | 285 | + | require.NoError(t, d.DispatchVimScrollLifecycle(ctx, input.ActionVimScrollDown, input.VimScrollContinuous)) |
| 286 | - | require.NoError(t, d.DispatchPageScrollLifecycle(ctx, input.ActionPageScrollDown, input.PageScrollStop)) | 286 | + | require.NoError(t, d.DispatchVimScrollLifecycle(ctx, input.ActionVimScrollDown, input.VimScrollStop)) |
| 287 | assert.Equal(t, 1, wv.cancelCalls) | 287 | assert.Equal(t, 1, wv.cancelCalls) | ||
| 288 | } | 288 | } | ||
| 289 | 289 | ||||
| 290 | - | func TestKeyboardDispatcher_PageModeNoopWhenNoActiveWebView(t *testing.T) { | 290 | + | func TestKeyboardDispatcher_VimModeNoopWhenNoActiveWebView(t *testing.T) { |
| 291 | ctx := context.Background() | 291 | ctx := context.Background() | ||
| 292 | 292 | ||||
| 293 | navCoord := &coordinator.NavigationCoordinator{} | 293 | navCoord := &coordinator.NavigationCoordinator{} | ||
| @@ -304,11 +304,11 @@ func TestKeyboardDispatcher_PageModeNoopWhenNoActiveWebView(t *testing.T) { | |||||
| 304 | ) | 304 | ) | ||
| 305 | 305 | ||||
| 306 | // No ActiveWebView set — dispatcher should no-op cleanly | 306 | // No ActiveWebView set — dispatcher should no-op cleanly | ||
| 307 | - | err := d.Dispatch(ctx, input.ActionPageScrollDown) | 307 | + | err := d.Dispatch(ctx, input.ActionVimScrollDown) |
| 308 | require.NoError(t, err) | 308 | require.NoError(t, err) | ||
| 309 | } | 309 | } | ||
| 310 | 310 | ||||
| 311 | - | func TestKeyboardDispatcher_PageModeNoopWhenActiveWebViewReturnsNil(t *testing.T) { | 311 | + | func TestKeyboardDispatcher_VimModeNoopWhenActiveWebViewReturnsNil(t *testing.T) { |
| 312 | ctx := context.Background() | 312 | ctx := context.Background() | ||
| 313 | 313 | ||||
| 314 | navCoord := &coordinator.NavigationCoordinator{} | 314 | navCoord := &coordinator.NavigationCoordinator{} | ||
| @@ -326,37 +326,37 @@ func TestKeyboardDispatcher_PageModeNoopWhenActiveWebViewReturnsNil(t *testing.T | |||||
| 326 | func(context.Context) entity.PaneID { return "" }, | 326 | func(context.Context) entity.PaneID { return "" }, | ||
| 327 | ) | 327 | ) | ||
| 328 | 328 | ||||
| 329 | - | err := d.Dispatch(ctx, input.ActionPageScrollDown) | 329 | + | err := d.Dispatch(ctx, input.ActionVimScrollDown) |
| 330 | require.NoError(t, err) | 330 | require.NoError(t, err) | ||
| 331 | } | 331 | } | ||
| 332 | 332 | ||||
| 333 | - | func TestPageScrollSpec_IsSingleSourceForCommandAndPulse(t *testing.T) { | 333 | + | func TestVimScrollSpec_IsSingleSourceForCommandAndPulse(t *testing.T) { |
| 334 | tests := []struct { | 334 | tests := []struct { | ||
| 335 | action input.Action | 335 | action input.Action | ||
| 336 | cmd usecase.PageScrollCommand | 336 | cmd usecase.PageScrollCommand | ||
| 337 | fast bool | 337 | fast bool | ||
| 338 | }{ | 338 | }{ | ||
| 339 | - | {input.ActionPageScrollLeft, usecase.PageScrollLeft, false}, | 339 | + | {input.ActionVimScrollLeft, usecase.PageScrollLeft, false}, |
| 340 | - | {input.ActionPageScrollRight, usecase.PageScrollRight, false}, | 340 | + | {input.ActionVimScrollRight, usecase.PageScrollRight, false}, |
| 341 | - | {input.ActionPageScrollUp, usecase.PageScrollUp, false}, | 341 | + | {input.ActionVimScrollUp, usecase.PageScrollUp, false}, |
| 342 | - | {input.ActionPageScrollDown, usecase.PageScrollDown, false}, | 342 | + | {input.ActionVimScrollDown, usecase.PageScrollDown, false}, |
| 343 | - | {input.ActionPageScrollUpFast, usecase.PageScrollUpFast, true}, | 343 | + | {input.ActionVimScrollUpFast, usecase.PageScrollUpFast, true}, |
| 344 | - | {input.ActionPageScrollDownFast, usecase.PageScrollDownFast, true}, | 344 | + | {input.ActionVimScrollDownFast, usecase.PageScrollDownFast, true}, |
| 345 | } | 345 | } | ||
| 346 | for _, tc := range tests { | 346 | for _, tc := range tests { | ||
| 347 | - | spec, ok := pageScrollSpec(tc.action) | 347 | + | spec, ok := vimScrollSpec(tc.action) |
| 348 | require.True(t, ok, string(tc.action)) | 348 | require.True(t, ok, string(tc.action)) | ||
| 349 | assert.Equal(t, tc.cmd, spec.cmd) | 349 | assert.Equal(t, tc.cmd, spec.cmd) | ||
| 350 | assert.Equal(t, tc.fast, spec.fast) | 350 | assert.Equal(t, tc.fast, spec.fast) | ||
| 351 | - | cmd, ok := pageScrollCommand(tc.action) | 351 | + | cmd, ok := vimScrollCommand(tc.action) |
| 352 | require.True(t, ok) | 352 | require.True(t, ok) | ||
| 353 | assert.Equal(t, tc.cmd, cmd) | 353 | assert.Equal(t, tc.cmd, cmd) | ||
| 354 | } | 354 | } | ||
| 355 | - | _, ok := pageScrollSpec(input.ActionQuit) | 355 | + | _, ok := vimScrollSpec(input.ActionQuit) |
| 356 | assert.False(t, ok) | 356 | assert.False(t, ok) | ||
| 357 | } | 357 | } | ||
| 358 | 358 | ||||
| 359 | - | func TestKeyboardDispatcher_PageScrollTapUsesSharedSpecPulse(t *testing.T) { | 359 | + | func TestKeyboardDispatcher_VimScrollTapUsesSharedSpecPulse(t *testing.T) { |
| 360 | ctx := context.Background() | 360 | ctx := context.Background() | ||
| 361 | base := mocks.NewMockWebView(t) | 361 | base := mocks.NewMockWebView(t) | ||
| 362 | scroller := mocks.NewMockPageScrollable(t) | 362 | scroller := mocks.NewMockPageScrollable(t) | ||
| @@ -368,7 +368,7 @@ func TestKeyboardDispatcher_PageScrollTapUsesSharedSpecPulse(t *testing.T) { | |||||
| 368 | }, func(context.Context) entity.PaneID { return "" }) | 368 | }, func(context.Context) entity.PaneID { return "" }) | ||
| 369 | 369 | ||||
| 370 | var pulses []bool | 370 | var pulses []bool | ||
| 371 | - | d.SetOnPageModePulse(func(_ context.Context, fast bool) { | 371 | + | d.SetOnVimModePulse(func(_ context.Context, fast bool) { |
| 372 | pulses = append(pulses, fast) | 372 | pulses = append(pulses, fast) | ||
| 373 | }) | 373 | }) | ||
| 374 | 374 | ||||
| @@ -380,7 +380,7 @@ func TestKeyboardDispatcher_PageScrollTapUsesSharedSpecPulse(t *testing.T) { | |||||
| 380 | Command: port.PageScrollCommandDownFast, FallbackDY: 320, | 380 | Command: port.PageScrollCommandDownFast, FallbackDY: 320, | ||
| 381 | }).Return(nil).Once() | 381 | }).Return(nil).Once() | ||
| 382 | 382 | ||||
| 383 | - | require.NoError(t, d.Dispatch(ctx, input.ActionPageScrollDown)) | 383 | + | require.NoError(t, d.Dispatch(ctx, input.ActionVimScrollDown)) |
| 384 | - | require.NoError(t, d.Dispatch(ctx, input.ActionPageScrollDownFast)) | 384 | + | require.NoError(t, d.Dispatch(ctx, input.ActionVimScrollDownFast)) |
| 385 | assert.Equal(t, []bool{false, true}, pulses) | 385 | assert.Equal(t, []bool{false, true}, pulses) | ||
| 386 | } | 386 | } | ||
M internal/ui/focus/borders.go +6 -6
| @@ -61,8 +61,8 @@ func (bm *BorderManager) OnModeChange(ctx context.Context, from, to input.Mode) | |||||
| 61 | bm.borderOverlay.RemoveCssClass(tabModeClass) | 61 | bm.borderOverlay.RemoveCssClass(tabModeClass) | ||
| 62 | case input.ModeSession: | 62 | case input.ModeSession: | ||
| 63 | bm.borderOverlay.RemoveCssClass(sessionModeClass) | 63 | bm.borderOverlay.RemoveCssClass(sessionModeClass) | ||
| 64 | - | case input.ModePage: | 64 | + | case input.ModeVim: |
| 65 | - | // Page mode never activates the global border overlay. | 65 | + | // Vim mode never activates the global border overlay. |
| 66 | // No class to remove. | 66 | // No class to remove. | ||
| 67 | } | 67 | } | ||
| 68 | 68 | ||||
| @@ -77,10 +77,10 @@ func (bm *BorderManager) OnModeChange(ctx context.Context, from, to input.Mode) | |||||
| 77 | case input.ModeSession: | 77 | case input.ModeSession: | ||
| 78 | bm.borderOverlay.AddCssClass(sessionModeClass) | 78 | bm.borderOverlay.AddCssClass(sessionModeClass) | ||
| 79 | bm.borderOverlay.SetVisible(true) | 79 | bm.borderOverlay.SetVisible(true) | ||
| 80 | - | case input.ModePage: | 80 | + | case input.ModeVim: |
| 81 | - | // Page mode must not activate the workspace-wide border overlay. | 81 | + | // Vim mode must not activate the workspace-wide border overlay. |
| 82 | - | // The pane-local page-mode-active CSS class and indicator are | 82 | + | // The pane-local vim-mode-active CSS class and indicator are |
| 83 | - | // managed by PaneView.SetPageMode / app handleModeChange. | 83 | + | // managed by PaneView.SetVimMode / app handleModeChange. |
| 84 | bm.borderOverlay.SetVisible(false) | 84 | bm.borderOverlay.SetVisible(false) | ||
| 85 | case input.ModeNormal: | 85 | case input.ModeNormal: | ||
| 86 | bm.borderOverlay.SetVisible(false) | 86 | bm.borderOverlay.SetVisible(false) | ||
M internal/ui/input/global_shortcuts.go +4 -4
| @@ -114,7 +114,7 @@ func NewGlobalShortcutHandler( | |||||
| 114 | 114 | ||||
| 115 | // Register app-reserved shortcuts (Ctrl+L, Ctrl+F, other mode activations, etc.) | 115 | // Register app-reserved shortcuts (Ctrl+L, Ctrl+F, other mode activations, etc.) | ||
| 116 | // so they work even when WebView has focus. | 116 | // so they work even when WebView has focus. | ||
| 117 | - | // Page mode activation is intentionally excluded here: it must flow through | 117 | + | // Vim mode activation is intentionally excluded here: it must flow through |
| 118 | // the normal KeyboardHandler path so editable page contexts can pass Ctrl+Y | 118 | // the normal KeyboardHandler path so editable page contexts can pass Ctrl+Y | ||
| 119 | // through instead of having a global shortcut controller consume it first. | 119 | // through instead of having a global shortcut controller consume it first. | ||
| 120 | shortcuts := NewShortcutSet(ctx, workspace, session) | 120 | shortcuts := NewShortcutSet(ctx, workspace, session) | ||
| @@ -496,11 +496,11 @@ func appendGlobalShortcutEventFields(evt *zerolog.Event, binding KeyBinding, act | |||||
| 496 | } | 496 | } | ||
| 497 | 497 | ||||
| 498 | func shouldRegisterGTKGlobalShortcut(action Action) bool { | 498 | func shouldRegisterGTKGlobalShortcut(action Action) bool { | ||
| 499 | - | return action != ActionEnterPageMode | 499 | + | return action != ActionEnterVimMode |
| 500 | } | 500 | } | ||
| 501 | 501 | ||||
| 502 | func shouldIgnoreGlobalShortcutInMode(mode Mode, action Action) bool { | 502 | func shouldIgnoreGlobalShortcutInMode(mode Mode, action Action) bool { | ||
| 503 | - | return mode == ModePage && action != ActionEnterPageMode | 503 | + | return mode == ModeVim && action != ActionEnterVimMode |
| 504 | } | 504 | } | ||
| 505 | 505 | ||||
| 506 | func globalShortcutActionMap() map[string]Action { | 506 | func globalShortcutActionMap() map[string]Action { | ||
| @@ -840,7 +840,7 @@ func formatEventType(eventType gdk.EventType) string { | |||||
| 840 | 840 | ||||
| 841 | func isModeAction(action Action) bool { | 841 | func isModeAction(action Action) bool { | ||
| 842 | switch action { | 842 | switch action { | ||
| 843 | - | case ActionEnterTabMode, ActionEnterPaneMode, ActionEnterSessionMode, ActionEnterResizeMode, ActionEnterPageMode, ActionExitMode: | 843 | + | case ActionEnterTabMode, ActionEnterPaneMode, ActionEnterSessionMode, ActionEnterResizeMode, ActionEnterVimMode, ActionExitMode: |
| 844 | return true | 844 | return true | ||
| 845 | default: | 845 | default: | ||
| 846 | return false | 846 | return false | ||
M internal/ui/input/global_shortcuts_test.go +9 -9
| @@ -10,24 +10,24 @@ import ( | |||||
| 10 | "github.com/bnema/puregotk/v4/gtk" | 10 | "github.com/bnema/puregotk/v4/gtk" | ||
| 11 | ) | 11 | ) | ||
| 12 | 12 | ||||
| 13 | - | func TestShouldRegisterGTKGlobalShortcut_ExcludesPageModeActivation(t *testing.T) { | 13 | + | func TestShouldRegisterGTKGlobalShortcut_ExcludesVimModeActivation(t *testing.T) { |
| 14 | - | if shouldRegisterGTKGlobalShortcut(ActionEnterPageMode) { | 14 | + | if shouldRegisterGTKGlobalShortcut(ActionEnterVimMode) { |
| 15 | - | t.Fatal("ActionEnterPageMode should stay off the GTK global shortcut controller") | 15 | + | t.Fatal("ActionEnterVimMode should stay off the GTK global shortcut controller") |
| 16 | } | 16 | } | ||
| 17 | if !shouldRegisterGTKGlobalShortcut(ActionOpenOmnibox) { | 17 | if !shouldRegisterGTKGlobalShortcut(ActionOpenOmnibox) { | ||
| 18 | - | t.Fatal("non-page-mode global shortcuts should remain registerable") | 18 | + | t.Fatal("non-vim-mode global shortcuts should remain registerable") |
| 19 | } | 19 | } | ||
| 20 | } | 20 | } | ||
| 21 | 21 | ||||
| 22 | - | func TestShouldIgnoreGlobalShortcutInMode_PageModeBlocksNonToggleActions(t *testing.T) { | 22 | + | func TestShouldIgnoreGlobalShortcutInMode_VimModeBlocksNonToggleActions(t *testing.T) { |
| 23 | - | if !shouldIgnoreGlobalShortcutInMode(ModePage, ActionOpenOmnibox) { | 23 | + | if !shouldIgnoreGlobalShortcutInMode(ModeVim, ActionOpenOmnibox) { |
| 24 | - | t.Fatal("Page mode should ignore app-global shortcuts like omnibox") | 24 | + | t.Fatal("Vim mode should ignore app-global shortcuts like omnibox") |
| 25 | } | 25 | } | ||
| 26 | if shouldIgnoreGlobalShortcutInMode(ModeNormal, ActionOpenOmnibox) { | 26 | if shouldIgnoreGlobalShortcutInMode(ModeNormal, ActionOpenOmnibox) { | ||
| 27 | t.Fatal("normal mode should not ignore app-global shortcuts") | 27 | t.Fatal("normal mode should not ignore app-global shortcuts") | ||
| 28 | } | 28 | } | ||
| 29 | - | if shouldIgnoreGlobalShortcutInMode(ModePage, ActionEnterPageMode) { | 29 | + | if shouldIgnoreGlobalShortcutInMode(ModeVim, ActionEnterVimMode) { |
| 30 | - | t.Fatal("page mode toggle should not be blocked by the mode guard") | 30 | + | t.Fatal("vim mode toggle should not be blocked by the mode guard") |
| 31 | } | 31 | } | ||
| 32 | } | 32 | } | ||
| 33 | 33 | ||||
M internal/ui/input/keyboard.go +116 -116
| @@ -36,22 +36,22 @@ import ( | |||||
| 36 | // Return an error if the action fails. | 36 | // Return an error if the action fails. | ||
| 37 | type ActionHandler func(ctx context.Context, action Action) error | 37 | type ActionHandler func(ctx context.Context, action Action) error | ||
| 38 | 38 | ||||
| 39 | - | // PageScrollPhase identifies held-scroll lifecycle events. It is internal input | 39 | + | // VimScrollPhase identifies held-scroll lifecycle events. It is internal input |
| 40 | // state, not a configurable action or shortcut. | 40 | // state, not a configurable action or shortcut. | ||
| 41 | - | type PageScrollPhase uint8 | 41 | + | type VimScrollPhase uint8 |
| 42 | 42 | ||||
| 43 | const ( | 43 | const ( | ||
| 44 | - | PageScrollContinuous PageScrollPhase = iota | 44 | + | VimScrollContinuous VimScrollPhase = iota |
| 45 | - | PageScrollStop | 45 | + | VimScrollStop |
| 46 | ) | 46 | ) | ||
| 47 | 47 | ||||
| 48 | - | // PageScrollLifecycleHandler receives autonomous held-scroll ticks and the | 48 | + | // VimScrollLifecycleHandler receives autonomous held-scroll ticks and the |
| 49 | // matching stop event without changing the normal configurable action API. | 49 | // matching stop event without changing the normal configurable action API. | ||
| 50 | - | type PageScrollLifecycleHandler func(ctx context.Context, action Action, phase PageScrollPhase) error | 50 | + | type VimScrollLifecycleHandler func(ctx context.Context, action Action, phase VimScrollPhase) error |
| 51 | 51 | ||||
| 52 | const ( | 52 | const ( | ||
| 53 | - | pageScrollHoldDelay = 250 * time.Millisecond | 53 | + | vimScrollHoldDelay = 250 * time.Millisecond |
| 54 | - | pageScrollCadence = 16 * time.Millisecond | 54 | + | vimScrollCadence = 16 * time.Millisecond |
| 55 | ) | 55 | ) | ||
| 56 | 56 | ||||
| 57 | // AccentHandler handles long-press accent detection. | 57 | // AccentHandler handles long-press accent detection. | ||
| @@ -77,8 +77,8 @@ type KeyboardHandler struct { | |||||
| 77 | session *entity.SessionConfig | 77 | session *entity.SessionConfig | ||
| 78 | 78 | ||||
| 79 | // Action handler callbacks. | 79 | // Action handler callbacks. | ||
| 80 | - | onAction ActionHandler | 80 | + | onAction ActionHandler |
| 81 | - | onPageScrollLifecycle PageScrollLifecycleHandler | 81 | + | onVimScrollLifecycle VimScrollLifecycleHandler |
| 82 | // Optional routing callback that determines how a key should be handled. | 82 | // Optional routing callback that determines how a key should be handled. | ||
| 83 | // Returns RouteHandleShortcuts (default), RoutePassToWidget (let focused | 83 | // Returns RouteHandleShortcuts (default), RoutePassToWidget (let focused | ||
| 84 | // widget handle it), or RouteAccentDetection (long-press accent for GTK entries). | 84 | // widget handle it), or RouteAccentDetection (long-press accent for GTK entries). | ||
| @@ -87,9 +87,9 @@ type KeyboardHandler struct { | |||||
| 87 | accentHandler AccentHandler | 87 | accentHandler AccentHandler | ||
| 88 | // Optional escape hook for app-level overlays | 88 | // Optional escape hook for app-level overlays | ||
| 89 | onEscape func(ctx context.Context) bool | 89 | onEscape func(ctx context.Context) bool | ||
| 90 | - | // Optional hook that blocks Page mode activation and lets the original key | 90 | + | // Optional hook that blocks Vim mode activation and lets the original key |
| 91 | // event pass through to the focused widget/page instead. | 91 | // event pass through to the focused widget/page instead. | ||
| 92 | - | pageModeActivationPassthrough func() bool | 92 | + | vimModeActivationPassthrough func() bool |
| 93 | 93 | ||||
| 94 | // GTK controller (nil until attached) | 94 | // GTK controller (nil until attached) | ||
| 95 | controller *gtk.EventControllerKey | 95 | controller *gtk.EventControllerKey | ||
| @@ -105,15 +105,15 @@ type KeyboardHandler struct { | |||||
| 105 | ctx context.Context | 105 | ctx context.Context | ||
| 106 | mu sync.RWMutex | 106 | mu sync.RWMutex | ||
| 107 | 107 | ||||
| 108 | - | pageScrollRepeatAction Action | 108 | + | vimScrollRepeatAction Action |
| 109 | - | pageScrollRepeatKeyval uint | 109 | + | vimScrollRepeatKeyval uint |
| 110 | - | pageScrollRepeatTimer uint | 110 | + | vimScrollRepeatTimer uint |
| 111 | - | pageScrollRepeatGeneration uint64 | 111 | + | vimScrollRepeatGeneration uint64 |
| 112 | - | pageScrollRepeatPressedAt time.Time | 112 | + | vimScrollRepeatPressedAt time.Time |
| 113 | - | pageScrollRepeatCb glib.SourceFunc | 113 | + | vimScrollRepeatCb glib.SourceFunc |
| 114 | - | pageScrollRepeatAdd func(intervalMS uint, cb *glib.SourceFunc) uint | 114 | + | vimScrollRepeatAdd func(intervalMS uint, cb *glib.SourceFunc) uint |
| 115 | - | pageScrollRepeatRemove func(id uint) bool | 115 | + | vimScrollRepeatRemove func(id uint) bool |
| 116 | - | pageScrollNow func() time.Time | 116 | + | vimScrollNow func() time.Time |
| 117 | } | 117 | } | ||
| 118 | 118 | ||||
| 119 | // NewKeyboardHandler creates a new keyboard handler. | 119 | // NewKeyboardHandler creates a new keyboard handler. | ||
| @@ -129,11 +129,11 @@ func NewKeyboardHandler(ctx context.Context, workspace *entity.WorkspaceConfig, | |||||
| 129 | session: session, | 129 | session: session, | ||
| 130 | activePressedActions: make(map[Action]uint), | 130 | activePressedActions: make(map[Action]uint), | ||
| 131 | ctx: ctx, | 131 | ctx: ctx, | ||
| 132 | - | pageScrollRepeatAdd: func(intervalMS uint, cb *glib.SourceFunc) uint { | 132 | + | vimScrollRepeatAdd: func(intervalMS uint, cb *glib.SourceFunc) uint { |
| 133 | return glib.TimeoutAdd(intervalMS, cb, 0) | 133 | return glib.TimeoutAdd(intervalMS, cb, 0) | ||
| 134 | }, | 134 | }, | ||
| 135 | - | pageScrollRepeatRemove: glib.SourceRemove, | 135 | + | vimScrollRepeatRemove: glib.SourceRemove, |
| 136 | - | pageScrollNow: time.Now, | 136 | + | vimScrollNow: time.Now, |
| 137 | } | 137 | } | ||
| 138 | h.SetOnModeChange(nil) | 138 | h.SetOnModeChange(nil) | ||
| 139 | 139 | ||||
| @@ -147,26 +147,26 @@ func (h *KeyboardHandler) SetOnAction(fn ActionHandler) { | |||||
| 147 | h.onAction = fn | 147 | h.onAction = fn | ||
| 148 | } | 148 | } | ||
| 149 | 149 | ||||
| 150 | - | // SetOnPageScrollLifecycle sets the callback for autonomous held-scroll ticks | 150 | + | // SetOnVimScrollLifecycle sets the callback for autonomous held-scroll ticks |
| 151 | // and stop notification. Tap actions continue through SetOnAction. | 151 | // and stop notification. Tap actions continue through SetOnAction. | ||
| 152 | - | func (h *KeyboardHandler) SetOnPageScrollLifecycle(fn PageScrollLifecycleHandler) { | 152 | + | func (h *KeyboardHandler) SetOnVimScrollLifecycle(fn VimScrollLifecycleHandler) { |
| 153 | h.mu.Lock() | 153 | h.mu.Lock() | ||
| 154 | defer h.mu.Unlock() | 154 | defer h.mu.Unlock() | ||
| 155 | - | h.onPageScrollLifecycle = fn | 155 | + | h.onVimScrollLifecycle = fn |
| 156 | } | 156 | } | ||
| 157 | 157 | ||||
| 158 | - | // SetPageModeActivationPassthrough sets the callback that can block Page mode | 158 | + | // SetVimModeActivationPassthrough sets the callback that can block Vim mode |
| 159 | // activation and let the original key event propagate instead. | 159 | // activation and let the original key event propagate instead. | ||
| 160 | - | func (h *KeyboardHandler) SetPageModeActivationPassthrough(fn func() bool) { | 160 | + | func (h *KeyboardHandler) SetVimModeActivationPassthrough(fn func() bool) { |
| 161 | h.mu.Lock() | 161 | h.mu.Lock() | ||
| 162 | defer h.mu.Unlock() | 162 | defer h.mu.Unlock() | ||
| 163 | - | h.pageModeActivationPassthrough = fn | 163 | + | h.vimModeActivationPassthrough = fn |
| 164 | } | 164 | } | ||
| 165 | 165 | ||||
| 166 | // ReloadShortcuts rebuilds the shortcut set from new config values. | 166 | // ReloadShortcuts rebuilds the shortcut set from new config values. | ||
| 167 | // This enables hot-reloading of keybindings without restarting. | 167 | // This enables hot-reloading of keybindings without restarting. | ||
| 168 | func (h *KeyboardHandler) ReloadShortcuts(ctx context.Context, workspace *entity.WorkspaceConfig, session *entity.SessionConfig) { | 168 | func (h *KeyboardHandler) ReloadShortcuts(ctx context.Context, workspace *entity.WorkspaceConfig, session *entity.SessionConfig) { | ||
| 169 | - | h.stopPageScrollRepeat() | 169 | + | h.stopVimScrollRepeat() |
| 170 | h.mu.Lock() | 170 | h.mu.Lock() | ||
| 171 | defer h.mu.Unlock() | 171 | defer h.mu.Unlock() | ||
| 172 | 172 | ||||
| @@ -189,8 +189,8 @@ func (h *KeyboardHandler) SetOnModeChange(fn func(from, to Mode)) { | |||||
| 189 | } else if from == ModeNormal { | 189 | } else if from == ModeNormal { | ||
| 190 | h.setControllerPhase(gtk.PhaseCaptureValue) | 190 | h.setControllerPhase(gtk.PhaseCaptureValue) | ||
| 191 | } | 191 | } | ||
| 192 | - | if to != ModePage { | 192 | + | if to != ModeVim { |
| 193 | - | h.stopPageScrollRepeat() | 193 | + | h.stopVimScrollRepeat() |
| 194 | } | 194 | } | ||
| 195 | // Forward to app-level callback | 195 | // Forward to app-level callback | ||
| 196 | if fn != nil { | 196 | if fn != nil { | ||
| @@ -315,7 +315,7 @@ func (h *KeyboardHandler) DetachForDestroy() { | |||||
| 315 | } | 315 | } | ||
| 316 | 316 | ||||
| 317 | func (h *KeyboardHandler) detach(removeController bool) { | 317 | func (h *KeyboardHandler) detach(removeController bool) { | ||
| 318 | - | h.stopPageScrollRepeat() | 318 | + | h.stopVimScrollRepeat() |
| 319 | h.mu.Lock() | 319 | h.mu.Lock() | ||
| 320 | if h.controller != nil && h.keyPressedHandlerID != 0 { | 320 | if h.controller != nil && h.keyPressedHandlerID != 0 { | ||
| 321 | h.controller.DisconnectSignal(h.keyPressedHandlerID) | 321 | h.controller.DisconnectSignal(h.keyPressedHandlerID) | ||
| @@ -367,8 +367,8 @@ func (h *KeyboardHandler) handleKeyPress(keyval, keycode uint, state gdk.Modifie | |||||
| 367 | if mode != ModeNormal && modifiers == 0 { | 367 | if mode != ModeNormal && modifiers == 0 { | ||
| 368 | switch keyval { | 368 | switch keyval { | ||
| 369 | case uint(gdk.KEY_Escape), uint(gdk.KEY_Return), uint(gdk.KEY_KP_Enter): | 369 | case uint(gdk.KEY_Escape), uint(gdk.KEY_Return), uint(gdk.KEY_KP_Enter): | ||
| 370 | - | if mode == ModePage { | 370 | + | if mode == ModeVim { |
| 371 | - | h.stopPageScrollRepeat() | 371 | + | h.stopVimScrollRepeat() |
| 372 | } | 372 | } | ||
| 373 | h.modal.ExitMode(h.ctx) | 373 | h.modal.ExitMode(h.ctx) | ||
| 374 | return true | 374 | return true | ||
| @@ -424,17 +424,17 @@ func (h *KeyboardHandler) handleShortcutLookupResult( | |||||
| 424 | modifiers Modifier, | 424 | modifiers Modifier, | ||
| 425 | ) bool { | 425 | ) bool { | ||
| 426 | if !found { | 426 | if !found { | ||
| 427 | - | if shouldPassthroughNativePageModeNavigation(mode, keyval, modifiers) { | 427 | + | if shouldPassthroughNativeVimModeNavigation(mode, keyval, modifiers) { |
| 428 | - | log.Trace().Uint("keyval", keyval).Msg("routing native page navigation key to focused widget in page mode") | 428 | + | log.Trace().Uint("keyval", keyval).Msg("routing native page navigation key to focused widget in vim mode") |
| 429 | return false | 429 | return false | ||
| 430 | } | 430 | } | ||
| 431 | return mode != ModeNormal // Consume unrecognized keys in modal mode | 431 | return mode != ModeNormal // Consume unrecognized keys in modal mode | ||
| 432 | } | 432 | } | ||
| 433 | - | if h.shouldPassthroughPageModeActivation(action, mode) { | 433 | + | if h.shouldPassthroughVimModeActivation(action, mode) { |
| 434 | return false | 434 | return false | ||
| 435 | } | 435 | } | ||
| 436 | - | if mode == ModePage && isPageScrollAction(action) { | 436 | + | if mode == ModeVim && isVimScrollAction(action) { |
| 437 | - | return h.handlePageScrollAction(log, action, mode, keyval) | 437 | + | return h.handleVimScrollAction(log, action, mode, keyval) |
| 438 | } | 438 | } | ||
| 439 | if h.suppressHeldAction(action, keyval) { | 439 | if h.suppressHeldAction(action, keyval) { | ||
| 440 | log.Trace(). | 440 | log.Trace(). | ||
| @@ -445,105 +445,105 @@ func (h *KeyboardHandler) handleShortcutLookupResult( | |||||
| 445 | return h.dispatchAction(action, mode) | 445 | return h.dispatchAction(action, mode) | ||
| 446 | } | 446 | } | ||
| 447 | 447 | ||||
| 448 | - | func (h *KeyboardHandler) handlePageScrollAction(log *zerolog.Logger, action Action, mode Mode, keyval uint) bool { | 448 | + | func (h *KeyboardHandler) handleVimScrollAction(log *zerolog.Logger, action Action, mode Mode, keyval uint) bool { |
| 449 | h.mu.RLock() | 449 | h.mu.RLock() | ||
| 450 | - | sameHeldKey := h.pageScrollRepeatAction == action && | 450 | + | sameHeldKey := h.vimScrollRepeatAction == action && |
| 451 | - | h.pageScrollRepeatKeyval == keyval && h.pageScrollRepeatTimer != 0 | 451 | + | h.vimScrollRepeatKeyval == keyval && h.vimScrollRepeatTimer != 0 |
| 452 | h.mu.RUnlock() | 452 | h.mu.RUnlock() | ||
| 453 | if sameHeldKey { | 453 | if sameHeldKey { | ||
| 454 | - | log.Trace().Str("action", string(action)).Msg("page scroll OS repeat suppressed") | 454 | + | log.Trace().Str("action", string(action)).Msg("vim scroll OS repeat suppressed") |
| 455 | return true | 455 | return true | ||
| 456 | } | 456 | } | ||
| 457 | 457 | ||||
| 458 | - | h.stopPageScrollRepeat() | 458 | + | h.stopVimScrollRepeat() |
| 459 | if !h.dispatchAction(action, mode) { | 459 | if !h.dispatchAction(action, mode) { | ||
| 460 | return false | 460 | return false | ||
| 461 | } | 461 | } | ||
| 462 | - | h.startPageScrollRepeat(action, keyval) | 462 | + | h.startVimScrollRepeat(action, keyval) |
| 463 | return true | 463 | return true | ||
| 464 | } | 464 | } | ||
| 465 | 465 | ||||
| 466 | - | func (h *KeyboardHandler) startPageScrollRepeat(action Action, keyval uint) { | 466 | + | func (h *KeyboardHandler) startVimScrollRepeat(action Action, keyval uint) { |
| 467 | h.mu.Lock() | 467 | h.mu.Lock() | ||
| 468 | - | if h.pageScrollRepeatTimer != 0 || h.pageScrollRepeatAdd == nil { | 468 | + | if h.vimScrollRepeatTimer != 0 || h.vimScrollRepeatAdd == nil { |
| 469 | h.mu.Unlock() | 469 | h.mu.Unlock() | ||
| 470 | return | 470 | return | ||
| 471 | } | 471 | } | ||
| 472 | - | h.pageScrollRepeatGeneration++ | 472 | + | h.vimScrollRepeatGeneration++ |
| 473 | - | generation := h.pageScrollRepeatGeneration | 473 | + | generation := h.vimScrollRepeatGeneration |
| 474 | - | h.pageScrollRepeatAction = action | 474 | + | h.vimScrollRepeatAction = action |
| 475 | - | h.pageScrollRepeatKeyval = keyval | 475 | + | h.vimScrollRepeatKeyval = keyval |
| 476 | - | h.pageScrollRepeatPressedAt = h.pageScrollNow() | 476 | + | h.vimScrollRepeatPressedAt = h.vimScrollNow() |
| 477 | cb := glib.SourceFunc(func(_ uintptr) bool { | 477 | cb := glib.SourceFunc(func(_ uintptr) bool { | ||
| 478 | - | return h.pageScrollRepeatTick(action, keyval, generation) | 478 | + | return h.vimScrollRepeatTick(action, keyval, generation) |
| 479 | }) | 479 | }) | ||
| 480 | - | h.pageScrollRepeatCb = cb | 480 | + | h.vimScrollRepeatCb = cb |
| 481 | - | add := h.pageScrollRepeatAdd | 481 | + | add := h.vimScrollRepeatAdd |
| 482 | - | id := add(uint(pageScrollCadence/time.Millisecond), &h.pageScrollRepeatCb) | 482 | + | id := add(uint(vimScrollCadence/time.Millisecond), &h.vimScrollRepeatCb) |
| 483 | if id == 0 { | 483 | if id == 0 { | ||
| 484 | - | h.clearPageScrollRepeatLocked() | 484 | + | h.clearVimScrollRepeatLocked() |
| 485 | h.mu.Unlock() | 485 | h.mu.Unlock() | ||
| 486 | return | 486 | return | ||
| 487 | } | 487 | } | ||
| 488 | - | h.pageScrollRepeatTimer = id | 488 | + | h.vimScrollRepeatTimer = id |
| 489 | h.mu.Unlock() | 489 | h.mu.Unlock() | ||
| 490 | } | 490 | } | ||
| 491 | 491 | ||||
| 492 | - | func (h *KeyboardHandler) pageScrollRepeatTick(action Action, keyval uint, generation uint64) bool { | 492 | + | func (h *KeyboardHandler) vimScrollRepeatTick(action Action, keyval uint, generation uint64) bool { |
| 493 | h.mu.RLock() | 493 | h.mu.RLock() | ||
| 494 | - | active := h.pageScrollRepeatAction == action && h.pageScrollRepeatKeyval == keyval && | 494 | + | active := h.vimScrollRepeatAction == action && h.vimScrollRepeatKeyval == keyval && |
| 495 | - | h.pageScrollRepeatTimer != 0 && h.pageScrollRepeatGeneration == generation | 495 | + | h.vimScrollRepeatTimer != 0 && h.vimScrollRepeatGeneration == generation |
| 496 | - | pressedAt := h.pageScrollRepeatPressedAt | 496 | + | pressedAt := h.vimScrollRepeatPressedAt |
| 497 | - | now := h.pageScrollNow | 497 | + | now := h.vimScrollNow |
| 498 | h.mu.RUnlock() | 498 | h.mu.RUnlock() | ||
| 499 | - | if !active || h.modal.Mode() != ModePage { | 499 | + | if !active || h.modal.Mode() != ModeVim { |
| 500 | return false | 500 | return false | ||
| 501 | } | 501 | } | ||
| 502 | - | if now().Sub(pressedAt) < pageScrollHoldDelay { | 502 | + | if now().Sub(pressedAt) < vimScrollHoldDelay { |
| 503 | return true | 503 | return true | ||
| 504 | } | 504 | } | ||
| 505 | - | // Continuous dispatch bypasses dispatchAction, so refresh Page Mode timeout here. | 505 | + | // Continuous dispatch bypasses dispatchAction, so refresh Vim Mode timeout here. |
| 506 | h.modal.ResetTimeout(h.ctx) | 506 | h.modal.ResetTimeout(h.ctx) | ||
| 507 | - | h.dispatchPageScrollLifecycle(action, PageScrollContinuous) | 507 | + | h.dispatchVimScrollLifecycle(action, VimScrollContinuous) |
| 508 | return true | 508 | return true | ||
| 509 | } | 509 | } | ||
| 510 | 510 | ||||
| 511 | - | func (h *KeyboardHandler) stopPageScrollRepeat() { | 511 | + | func (h *KeyboardHandler) stopVimScrollRepeat() { |
| 512 | h.mu.Lock() | 512 | h.mu.Lock() | ||
| 513 | - | action := h.pageScrollRepeatAction | 513 | + | action := h.vimScrollRepeatAction |
| 514 | - | timerID := h.pageScrollRepeatTimer | 514 | + | timerID := h.vimScrollRepeatTimer |
| 515 | - | remove := h.pageScrollRepeatRemove | 515 | + | remove := h.vimScrollRepeatRemove |
| 516 | - | h.clearPageScrollRepeatLocked() | 516 | + | h.clearVimScrollRepeatLocked() |
| 517 | h.mu.Unlock() | 517 | h.mu.Unlock() | ||
| 518 | if timerID != 0 && remove != nil { | 518 | if timerID != 0 && remove != nil { | ||
| 519 | remove(timerID) | 519 | remove(timerID) | ||
| 520 | } | 520 | } | ||
| 521 | if action != "" { | 521 | if action != "" { | ||
| 522 | - | h.dispatchPageScrollLifecycle(action, PageScrollStop) | 522 | + | h.dispatchVimScrollLifecycle(action, VimScrollStop) |
| 523 | } | 523 | } | ||
| 524 | } | 524 | } | ||
| 525 | 525 | ||||
| 526 | - | func (h *KeyboardHandler) clearPageScrollRepeatLocked() { | 526 | + | func (h *KeyboardHandler) clearVimScrollRepeatLocked() { |
| 527 | - | h.pageScrollRepeatGeneration++ | 527 | + | h.vimScrollRepeatGeneration++ |
| 528 | - | h.pageScrollRepeatAction = "" | 528 | + | h.vimScrollRepeatAction = "" |
| 529 | - | h.pageScrollRepeatKeyval = 0 | 529 | + | h.vimScrollRepeatKeyval = 0 |
| 530 | - | h.pageScrollRepeatTimer = 0 | 530 | + | h.vimScrollRepeatTimer = 0 |
| 531 | - | h.pageScrollRepeatPressedAt = time.Time{} | 531 | + | h.vimScrollRepeatPressedAt = time.Time{} |
| 532 | - | h.pageScrollRepeatCb = nil | 532 | + | h.vimScrollRepeatCb = nil |
| 533 | } | 533 | } | ||
| 534 | 534 | ||||
| 535 | - | func (h *KeyboardHandler) dispatchPageScrollLifecycle(action Action, phase PageScrollPhase) { | 535 | + | func (h *KeyboardHandler) dispatchVimScrollLifecycle(action Action, phase VimScrollPhase) { |
| 536 | h.mu.RLock() | 536 | h.mu.RLock() | ||
| 537 | - | handler := h.onPageScrollLifecycle | 537 | + | handler := h.onVimScrollLifecycle |
| 538 | h.mu.RUnlock() | 538 | h.mu.RUnlock() | ||
| 539 | if handler == nil { | 539 | if handler == nil { | ||
| 540 | - | if phase == PageScrollContinuous { | 540 | + | if phase == VimScrollContinuous { |
| 541 | - | h.dispatchAction(action, ModePage) | 541 | + | h.dispatchAction(action, ModeVim) |
| 542 | } | 542 | } | ||
| 543 | return | 543 | return | ||
| 544 | } | 544 | } | ||
| 545 | if err := handler(h.ctx, action, phase); err != nil { | 545 | if err := handler(h.ctx, action, phase); err != nil { | ||
| 546 | - | logging.FromContext(h.ctx).Error().Err(err).Str("action", string(action)).Msg("page scroll lifecycle handler error") | 546 | + | logging.FromContext(h.ctx).Error().Err(err).Str("action", string(action)).Msg("vim scroll lifecycle handler error") |
| 547 | } | 547 | } | ||
| 548 | } | 548 | } | ||
| 549 | 549 | ||||
| @@ -575,8 +575,8 @@ func normalizeKeyval(keyval uint) uint { | |||||
| 575 | return keyval | 575 | return keyval | ||
| 576 | } | 576 | } | ||
| 577 | 577 | ||||
| 578 | - | func shouldPassthroughNativePageModeNavigation(mode Mode, keyval uint, modifiers Modifier) bool { | 578 | + | func shouldPassthroughNativeVimModeNavigation(mode Mode, keyval uint, modifiers Modifier) bool { |
| 579 | - | if mode != ModePage || modifiers != ModNone { | 579 | + | if mode != ModeVim || modifiers != ModNone { |
| 580 | return false | 580 | return false | ||
| 581 | } | 581 | } | ||
| 582 | switch keyval { | 582 | switch keyval { | ||
| @@ -607,7 +607,7 @@ func (h *KeyboardHandler) dispatchAction(action Action, mode Mode) bool { | |||||
| 607 | if mode == ModeResize && isResizeAction(action) { | 607 | if mode == ModeResize && isResizeAction(action) { | ||
| 608 | h.modal.ResetTimeout(h.ctx) | 608 | h.modal.ResetTimeout(h.ctx) | ||
| 609 | } | 609 | } | ||
| 610 | - | if mode == ModePage && isPageScrollAction(action) { | 610 | + | if mode == ModeVim && isVimScrollAction(action) { |
| 611 | h.modal.ResetTimeout(h.ctx) | 611 | h.modal.ResetTimeout(h.ctx) | ||
| 612 | } | 612 | } | ||
| 613 | 613 | ||||
| @@ -644,7 +644,7 @@ func isRepeatedKeyboardActionSuppressed(action Action) bool { | |||||
| 644 | ActionEnterPaneMode, | 644 | ActionEnterPaneMode, | ||
| 645 | ActionEnterSessionMode, | 645 | ActionEnterSessionMode, | ||
| 646 | ActionEnterResizeMode, | 646 | ActionEnterResizeMode, | ||
| 647 | - | ActionEnterPageMode, | 647 | + | ActionEnterVimMode, |
| 648 | ActionNewTab, | 648 | ActionNewTab, | ||
| 649 | ActionRenameTab, | 649 | ActionRenameTab, | ||
| 650 | ActionSplitRight, | 650 | ActionSplitRight, | ||
| @@ -721,14 +721,14 @@ func isResizeAction(action Action) bool { | |||||
| 721 | } | 721 | } | ||
| 722 | } | 722 | } | ||
| 723 | 723 | ||||
| 724 | - | func isPageScrollAction(action Action) bool { | 724 | + | func isVimScrollAction(action Action) bool { |
| 725 | switch action { | 725 | switch action { | ||
| 726 | - | case ActionPageScrollLeft, | 726 | + | case ActionVimScrollLeft, |
| 727 | - | ActionPageScrollDown, | 727 | + | ActionVimScrollDown, |
| 728 | - | ActionPageScrollUp, | 728 | + | ActionVimScrollUp, |
| 729 | - | ActionPageScrollRight, | 729 | + | ActionVimScrollRight, |
| 730 | - | ActionPageScrollDownFast, | 730 | + | ActionVimScrollDownFast, |
| 731 | - | ActionPageScrollUpFast: | 731 | + | ActionVimScrollUpFast: |
| 732 | return true | 732 | return true | ||
| 733 | default: | 733 | default: | ||
| 734 | return false | 734 | return false | ||
| @@ -774,21 +774,21 @@ func (h *KeyboardHandler) handleModeAction(action Action) bool { | |||||
| 774 | } | 774 | } | ||
| 775 | h.modal.EnterResizeMode(h.ctx, time.Duration(ms)*time.Millisecond) | 775 | h.modal.EnterResizeMode(h.ctx, time.Duration(ms)*time.Millisecond) | ||
| 776 | return true | 776 | return true | ||
| 777 | - | case ActionEnterPageMode: | 777 | + | case ActionEnterVimMode: |
| 778 | - | if h.modal.Mode() == ModePage { | 778 | + | if h.modal.Mode() == ModeVim { |
| 779 | - | h.stopPageScrollRepeat() | 779 | + | h.stopVimScrollRepeat() |
| 780 | h.modal.ExitMode(h.ctx) | 780 | h.modal.ExitMode(h.ctx) | ||
| 781 | return true | 781 | return true | ||
| 782 | } | 782 | } | ||
| 783 | var pgms int | 783 | var pgms int | ||
| 784 | if workspace != nil { | 784 | if workspace != nil { | ||
| 785 | - | pgms = workspace.PageMode.TimeoutMilliseconds | 785 | + | pgms = workspace.VimMode.TimeoutMilliseconds |
| 786 | } | 786 | } | ||
| 787 | - | h.modal.EnterPageMode(h.ctx, time.Duration(pgms)*time.Millisecond) | 787 | + | h.modal.EnterVimMode(h.ctx, time.Duration(pgms)*time.Millisecond) |
| 788 | return true | 788 | return true | ||
| 789 | case ActionExitMode: | 789 | case ActionExitMode: | ||
| 790 | - | if h.modal.Mode() == ModePage { | 790 | + | if h.modal.Mode() == ModeVim { |
| 791 | - | h.stopPageScrollRepeat() | 791 | + | h.stopVimScrollRepeat() |
| 792 | } | 792 | } | ||
| 793 | h.modal.ExitMode(h.ctx) | 793 | h.modal.ExitMode(h.ctx) | ||
| 794 | return true | 794 | return true | ||
| @@ -836,17 +836,17 @@ func (h *KeyboardHandler) EnterSessionMode() { | |||||
| 836 | h.modal.EnterSessionMode(h.ctx, time.Duration(ms)*time.Millisecond) | 836 | h.modal.EnterSessionMode(h.ctx, time.Duration(ms)*time.Millisecond) | ||
| 837 | } | 837 | } | ||
| 838 | 838 | ||||
| 839 | - | // EnterPageMode programmatically enters page scrolling mode. | 839 | + | // EnterVimMode programmatically enters Vim-style scrolling mode. |
| 840 | // Useful for testing or programmatic mode changes. | 840 | // Useful for testing or programmatic mode changes. | ||
| 841 | - | func (h *KeyboardHandler) EnterPageMode() { | 841 | + | func (h *KeyboardHandler) EnterVimMode() { |
| 842 | h.mu.RLock() | 842 | h.mu.RLock() | ||
| 843 | workspace := h.workspace | 843 | workspace := h.workspace | ||
| 844 | h.mu.RUnlock() | 844 | h.mu.RUnlock() | ||
| 845 | var ms int | 845 | var ms int | ||
| 846 | if workspace != nil { | 846 | if workspace != nil { | ||
| 847 | - | ms = workspace.PageMode.TimeoutMilliseconds | 847 | + | ms = workspace.VimMode.TimeoutMilliseconds |
| 848 | } | 848 | } | ||
| 849 | - | h.modal.EnterPageMode(h.ctx, time.Duration(ms)*time.Millisecond) | 849 | + | h.modal.EnterVimMode(h.ctx, time.Duration(ms)*time.Millisecond) |
| 850 | } | 850 | } | ||
| 851 | 851 | ||||
| 852 | // ExitMode programmatically exits modal mode. | 852 | // ExitMode programmatically exits modal mode. | ||
| @@ -855,12 +855,12 @@ func (h *KeyboardHandler) ExitMode() { | |||||
| 855 | h.modal.ExitMode(h.ctx) | 855 | h.modal.ExitMode(h.ctx) | ||
| 856 | } | 856 | } | ||
| 857 | 857 | ||||
| 858 | - | func (h *KeyboardHandler) shouldPassthroughPageModeActivation(action Action, mode Mode) bool { | 858 | + | func (h *KeyboardHandler) shouldPassthroughVimModeActivation(action Action, mode Mode) bool { |
| 859 | - | if action != ActionEnterPageMode || mode != ModeNormal { | 859 | + | if action != ActionEnterVimMode || mode != ModeNormal { |
| 860 | return false | 860 | return false | ||
| 861 | } | 861 | } | ||
| 862 | h.mu.RLock() | 862 | h.mu.RLock() | ||
| 863 | - | passthrough := h.pageModeActivationPassthrough | 863 | + | passthrough := h.vimModeActivationPassthrough |
| 864 | h.mu.RUnlock() | 864 | h.mu.RUnlock() | ||
| 865 | return passthrough != nil && passthrough() | 865 | return passthrough != nil && passthrough() | ||
| 866 | } | 866 | } | ||
| @@ -870,7 +870,7 @@ func (h *KeyboardHandler) shouldPassthroughPageModeActivation(action Action, mod | |||||
| 870 | // to the registered action handler. It returns whether the action was consumed. | 870 | // to the registered action handler. It returns whether the action was consumed. | ||
| 871 | func (h *KeyboardHandler) DispatchAction(action Action) bool { | 871 | func (h *KeyboardHandler) DispatchAction(action Action) bool { | ||
| 872 | mode := h.modal.Mode() | 872 | mode := h.modal.Mode() | ||
| 873 | - | if h.shouldPassthroughPageModeActivation(action, mode) { | 873 | + | if h.shouldPassthroughVimModeActivation(action, mode) { |
| 874 | return false | 874 | return false | ||
| 875 | } | 875 | } | ||
| 876 | h.dispatchAction(action, mode) | 876 | h.dispatchAction(action, mode) | ||
| @@ -889,10 +889,10 @@ func (h *KeyboardHandler) handleKeyRelease(keyval uint) { | |||||
| 889 | } | 889 | } | ||
| 890 | } | 890 | } | ||
| 891 | } | 891 | } | ||
| 892 | - | stopPageScroll := h.pageScrollRepeatKeyval == keyval | 892 | + | stopVimScroll := h.vimScrollRepeatKeyval == keyval |
| 893 | h.mu.Unlock() | 893 | h.mu.Unlock() | ||
| 894 | - | if stopPageScroll { | 894 | + | if stopVimScroll { |
| 895 | - | h.stopPageScrollRepeat() | 895 | + | h.stopVimScrollRepeat() |
| 896 | } | 896 | } | ||
| 897 | 897 | ||||
| 898 | if accentHandler == nil { | 898 | if accentHandler == nil { | ||
M internal/ui/input/keyboard_test.go +159 -159
| @@ -369,10 +369,10 @@ func TestIsRepeatedKeyboardActionSuppressed_AllowsResizeStepRepeats(t *testing.T | |||||
| 369 | } | 369 | } | ||
| 370 | } | 370 | } | ||
| 371 | 371 | ||||
| 372 | - | func TestHandleKeyPress_EnterPageMode(t *testing.T) { | 372 | + | func TestHandleKeyPress_EnterVimMode(t *testing.T) { |
| 373 | ctx := context.Background() | 373 | ctx := context.Background() | ||
| 374 | workspace := newTestWorkspace() | 374 | workspace := newTestWorkspace() | ||
| 375 | - | workspace.PageMode = entity.PageModeConfig{ | 375 | + | workspace.VimMode = entity.VimModeConfig{ |
| 376 | ActivationShortcut: "ctrl+y", | 376 | ActivationShortcut: "ctrl+y", | ||
| 377 | } | 377 | } | ||
| 378 | 378 | ||||
| @@ -382,25 +382,25 @@ func TestHandleKeyPress_EnterPageMode(t *testing.T) { | |||||
| 382 | return nil | 382 | return nil | ||
| 383 | }) | 383 | }) | ||
| 384 | 384 | ||||
| 385 | - | // Ctrl+Y should enter page mode | 385 | + | // Ctrl+Y should enter vim mode |
| 386 | result := h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) | 386 | result := h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) | ||
| 387 | if !result { | 387 | if !result { | ||
| 388 | t.Fatal("Ctrl+Y should be consumed") | 388 | t.Fatal("Ctrl+Y should be consumed") | ||
| 389 | } | 389 | } | ||
| 390 | - | if h.Mode() != ModePage { | 390 | + | if h.Mode() != ModeVim { |
| 391 | - | t.Fatalf("mode = %v, want ModePage", h.Mode()) | 391 | + | t.Fatalf("mode = %v, want ModeVim", h.Mode()) |
| 392 | } | 392 | } | ||
| 393 | } | 393 | } | ||
| 394 | 394 | ||||
| 395 | - | func TestHandleKeyPress_PageModeStaysActiveAfterScroll(t *testing.T) { | 395 | + | func TestHandleKeyPress_VimModeStaysActiveAfterScroll(t *testing.T) { |
| 396 | ctx := context.Background() | 396 | ctx := context.Background() | ||
| 397 | workspace := newTestWorkspace() | 397 | workspace := newTestWorkspace() | ||
| 398 | - | workspace.PageMode = entity.PageModeConfig{ | 398 | + | workspace.VimMode = entity.VimModeConfig{ |
| 399 | ActivationShortcut: "ctrl+y", | 399 | ActivationShortcut: "ctrl+y", | ||
| 400 | Actions: map[string]entity.ActionBinding{ | 400 | Actions: map[string]entity.ActionBinding{ | ||
| 401 | - | "page-scroll-down": {Keys: []string{"j"}}, | 401 | + | "vim-scroll-down": {Keys: []string{"j"}}, |
| 402 | - | "page-scroll-up": {Keys: []string{"k"}}, | 402 | + | "vim-scroll-up": {Keys: []string{"k"}}, |
| 403 | - | "cancel": {Keys: []string{"escape"}}, | 403 | + | "cancel": {Keys: []string{"escape"}}, |
| 404 | }, | 404 | }, | ||
| 405 | } | 405 | } | ||
| 406 | 406 | ||||
| @@ -411,50 +411,50 @@ func TestHandleKeyPress_PageModeStaysActiveAfterScroll(t *testing.T) { | |||||
| 411 | return nil | 411 | return nil | ||
| 412 | }) | 412 | }) | ||
| 413 | 413 | ||||
| 414 | - | // Enter page mode | 414 | + | // Enter vim mode |
| 415 | result := h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) | 415 | result := h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) | ||
| 416 | - | if !result || h.Mode() != ModePage { | 416 | + | if !result || h.Mode() != ModeVim { |
| 417 | - | t.Fatal("failed to enter page mode") | 417 | + | t.Fatal("failed to enter vim mode") |
| 418 | } | 418 | } | ||
| 419 | 419 | ||||
| 420 | var repeatTick glib.SourceFunc | 420 | var repeatTick glib.SourceFunc | ||
| 421 | repeatNow := time.Unix(2_000, 0) | 421 | repeatNow := time.Unix(2_000, 0) | ||
| 422 | - | h.pageScrollNow = func() time.Time { return repeatNow } | 422 | + | h.vimScrollNow = func() time.Time { return repeatNow } |
| 423 | - | h.pageScrollRepeatAdd = func(_ uint, cb *glib.SourceFunc) uint { | 423 | + | h.vimScrollRepeatAdd = func(_ uint, cb *glib.SourceFunc) uint { |
| 424 | repeatTick = *cb | 424 | repeatTick = *cb | ||
| 425 | return 1 | 425 | return 1 | ||
| 426 | } | 426 | } | ||
| 427 | - | h.pageScrollRepeatRemove = func(uint) bool { return true } | 427 | + | h.vimScrollRepeatRemove = func(uint) bool { return true } |
| 428 | 428 | ||||
| 429 | - | // Scroll actions stay in page mode; the first press arms the smooth | 429 | + | // Scroll actions stay in vim mode; the first press arms the smooth |
| 430 | // repeater and later physical auto-repeat events are ignored. | 430 | // repeater and later physical auto-repeat events are ignored. | ||
| 431 | for i := 0; i < 2; i++ { | 431 | for i := 0; i < 2; i++ { | ||
| 432 | scrolled := h.handleKeyPress(uint('j'), 0, 0) | 432 | scrolled := h.handleKeyPress(uint('j'), 0, 0) | ||
| 433 | if !scrolled { | 433 | if !scrolled { | ||
| 434 | t.Fatalf("scroll down iteration %d: key not consumed", i) | 434 | t.Fatalf("scroll down iteration %d: key not consumed", i) | ||
| 435 | } | 435 | } | ||
| 436 | - | if h.Mode() != ModePage { | 436 | + | if h.Mode() != ModeVim { |
| 437 | - | t.Fatalf("scroll down iteration %d: mode = %v, want ModePage", i, h.Mode()) | 437 | + | t.Fatalf("scroll down iteration %d: mode = %v, want ModeVim", i, h.Mode()) |
| 438 | } | 438 | } | ||
| 439 | } | 439 | } | ||
| 440 | if repeatTick == nil { | 440 | if repeatTick == nil { | ||
| 441 | - | t.Fatal("expected held page scroll to register smooth repeater") | 441 | + | t.Fatal("expected held vim scroll to register smooth repeater") |
| 442 | } | 442 | } | ||
| 443 | - | repeatNow = repeatNow.Add(pageScrollHoldDelay) | 443 | + | repeatNow = repeatNow.Add(vimScrollHoldDelay) |
| 444 | for i := 0; i < 3; i++ { | 444 | for i := 0; i < 3; i++ { | ||
| 445 | if !repeatTick(0) { | 445 | if !repeatTick(0) { | ||
| 446 | t.Fatalf("repeat tick %d stopped unexpectedly", i) | 446 | t.Fatalf("repeat tick %d stopped unexpectedly", i) | ||
| 447 | } | 447 | } | ||
| 448 | - | if h.Mode() != ModePage { | 448 | + | if h.Mode() != ModeVim { |
| 449 | - | t.Fatalf("repeat tick %d: mode = %v, want ModePage", i, h.Mode()) | 449 | + | t.Fatalf("repeat tick %d: mode = %v, want ModeVim", i, h.Mode()) |
| 450 | } | 450 | } | ||
| 451 | } | 451 | } | ||
| 452 | h.handleKeyRelease(uint('j')) | 452 | h.handleKeyRelease(uint('j')) | ||
| 453 | 453 | ||||
| 454 | - | // Scroll up also stays in page mode | 454 | + | // Scroll up also stays in vim mode |
| 455 | result = h.handleKeyPress(uint('k'), 0, 0) | 455 | result = h.handleKeyPress(uint('k'), 0, 0) | ||
| 456 | - | if !result || h.Mode() != ModePage { | 456 | + | if !result || h.Mode() != ModeVim { |
| 457 | - | t.Fatal("scroll up should stay in page mode") | 457 | + | t.Fatal("scroll up should stay in vim mode") |
| 458 | } | 458 | } | ||
| 459 | 459 | ||||
| 460 | // Ensure scroll actions were dispatched | 460 | // Ensure scroll actions were dispatched | ||
| @@ -463,15 +463,15 @@ func TestHandleKeyPress_PageModeStaysActiveAfterScroll(t *testing.T) { | |||||
| 463 | } | 463 | } | ||
| 464 | } | 464 | } | ||
| 465 | 465 | ||||
| 466 | - | func TestHandleKeyPress_PageModeTimeoutReset(t *testing.T) { | 466 | + | func TestHandleKeyPress_VimModeTimeoutReset(t *testing.T) { |
| 467 | ctx := context.Background() | 467 | ctx := context.Background() | ||
| 468 | workspace := newTestWorkspace() | 468 | workspace := newTestWorkspace() | ||
| 469 | - | workspace.PageMode = entity.PageModeConfig{ | 469 | + | workspace.VimMode = entity.VimModeConfig{ |
| 470 | ActivationShortcut: "ctrl+y", | 470 | ActivationShortcut: "ctrl+y", | ||
| 471 | TimeoutMilliseconds: 100, | 471 | TimeoutMilliseconds: 100, | ||
| 472 | Actions: map[string]entity.ActionBinding{ | 472 | Actions: map[string]entity.ActionBinding{ | ||
| 473 | - | "page-scroll-down": {Keys: []string{"j"}}, | 473 | + | "vim-scroll-down": {Keys: []string{"j"}}, |
| 474 | - | "cancel": {Keys: []string{"escape"}}, | 474 | + | "cancel": {Keys: []string{"escape"}}, |
| 475 | }, | 475 | }, | ||
| 476 | } | 476 | } | ||
| 477 | 477 | ||||
| @@ -485,34 +485,34 @@ func TestHandleKeyPress_PageModeTimeoutReset(t *testing.T) { | |||||
| 485 | return nil | 485 | return nil | ||
| 486 | }) | 486 | }) | ||
| 487 | 487 | ||||
| 488 | - | // Enter page mode with timeout. | 488 | + | // Enter vim mode with timeout. |
| 489 | h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) | 489 | h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) | ||
| 490 | - | if h.Mode() != ModePage { | 490 | + | if h.Mode() != ModeVim { |
| 491 | - | t.Fatal("failed to enter page mode") | 491 | + | t.Fatal("failed to enter vim mode") |
| 492 | } | 492 | } | ||
| 493 | if timerStarts != 1 { | 493 | if timerStarts != 1 { | ||
| 494 | - | t.Fatalf("timer starts after entering page mode = %d, want 1", timerStarts) | 494 | + | t.Fatalf("timer starts after entering vim mode = %d, want 1", timerStarts) |
| 495 | } | 495 | } | ||
| 496 | 496 | ||||
| 497 | // Scroll action should reset the timeout by starting a replacement timer. | 497 | // Scroll action should reset the timeout by starting a replacement timer. | ||
| 498 | h.handleKeyPress(uint('j'), 0, 0) | 498 | h.handleKeyPress(uint('j'), 0, 0) | ||
| 499 | 499 | ||||
| 500 | - | if h.Mode() != ModePage { | 500 | + | if h.Mode() != ModeVim { |
| 501 | - | t.Fatalf("mode after scroll = %v, want ModePage", h.Mode()) | 501 | + | t.Fatalf("mode after scroll = %v, want ModeVim", h.Mode()) |
| 502 | } | 502 | } | ||
| 503 | if timerStarts != 2 { | 503 | if timerStarts != 2 { | ||
| 504 | t.Fatalf("timer starts after scroll reset = %d, want 2", timerStarts) | 504 | t.Fatalf("timer starts after scroll reset = %d, want 2", timerStarts) | ||
| 505 | } | 505 | } | ||
| 506 | } | 506 | } | ||
| 507 | 507 | ||||
| 508 | - | func TestPageScrollRepeatTick_RefreshesConfiguredPageModeTimeout(t *testing.T) { | 508 | + | func TestVimScrollRepeatTick_RefreshesConfiguredVimModeTimeout(t *testing.T) { |
| 509 | ctx := context.Background() | 509 | ctx := context.Background() | ||
| 510 | workspace := newTestWorkspace() | 510 | workspace := newTestWorkspace() | ||
| 511 | - | workspace.PageMode = entity.PageModeConfig{ | 511 | + | workspace.VimMode = entity.VimModeConfig{ |
| 512 | ActivationShortcut: "ctrl+y", | 512 | ActivationShortcut: "ctrl+y", | ||
| 513 | TimeoutMilliseconds: 100, | 513 | TimeoutMilliseconds: 100, | ||
| 514 | Actions: map[string]entity.ActionBinding{ | 514 | Actions: map[string]entity.ActionBinding{ | ||
| 515 | - | "page-scroll-down": {Keys: []string{"j"}}, | 515 | + | "vim-scroll-down": {Keys: []string{"j"}}, |
| 516 | }, | 516 | }, | ||
| 517 | } | 517 | } | ||
| 518 | 518 | ||||
| @@ -523,8 +523,8 @@ func TestPageScrollRepeatTick_RefreshesConfiguredPageModeTimeout(t *testing.T) { | |||||
| 523 | return &fakeModalTimer{} | 523 | return &fakeModalTimer{} | ||
| 524 | } | 524 | } | ||
| 525 | var continuous int | 525 | var continuous int | ||
| 526 | - | h.SetOnPageScrollLifecycle(func(_ context.Context, _ Action, phase PageScrollPhase) error { | 526 | + | h.SetOnVimScrollLifecycle(func(_ context.Context, _ Action, phase VimScrollPhase) error { |
| 527 | - | if phase == PageScrollContinuous { | 527 | + | if phase == VimScrollContinuous { |
| 528 | continuous++ | 528 | continuous++ | ||
| 529 | } | 529 | } | ||
| 530 | return nil | 530 | return nil | ||
| @@ -532,16 +532,16 @@ func TestPageScrollRepeatTick_RefreshesConfiguredPageModeTimeout(t *testing.T) { | |||||
| 532 | 532 | ||||
| 533 | var repeatTick glib.SourceFunc | 533 | var repeatTick glib.SourceFunc | ||
| 534 | repeatNow := time.Unix(3_000, 0) | 534 | repeatNow := time.Unix(3_000, 0) | ||
| 535 | - | h.pageScrollNow = func() time.Time { return repeatNow } | 535 | + | h.vimScrollNow = func() time.Time { return repeatNow } |
| 536 | - | h.pageScrollRepeatAdd = func(_ uint, cb *glib.SourceFunc) uint { | 536 | + | h.vimScrollRepeatAdd = func(_ uint, cb *glib.SourceFunc) uint { |
| 537 | repeatTick = *cb | 537 | repeatTick = *cb | ||
| 538 | return 1 | 538 | return 1 | ||
| 539 | } | 539 | } | ||
| 540 | - | h.pageScrollRepeatRemove = func(uint) bool { return true } | 540 | + | h.vimScrollRepeatRemove = func(uint) bool { return true } |
| 541 | 541 | ||||
| 542 | h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) | 542 | h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) | ||
| 543 | - | if h.Mode() != ModePage { | 543 | + | if h.Mode() != ModeVim { |
| 544 | - | t.Fatal("failed to enter page mode") | 544 | + | t.Fatal("failed to enter vim mode") |
| 545 | } | 545 | } | ||
| 546 | enterStarts := timerStarts | 546 | enterStarts := timerStarts | ||
| 547 | h.handleKeyPress(uint('j'), 0, 0) | 547 | h.handleKeyPress(uint('j'), 0, 0) | ||
| @@ -553,7 +553,7 @@ func TestPageScrollRepeatTick_RefreshesConfiguredPageModeTimeout(t *testing.T) { | |||||
| 553 | t.Fatalf("tap should refresh timeout: enter=%d afterTap=%d", enterStarts, afterTap) | 553 | t.Fatalf("tap should refresh timeout: enter=%d afterTap=%d", enterStarts, afterTap) | ||
| 554 | } | 554 | } | ||
| 555 | 555 | ||||
| 556 | - | repeatNow = repeatNow.Add(pageScrollHoldDelay) | 556 | + | repeatNow = repeatNow.Add(vimScrollHoldDelay) |
| 557 | if !repeatTick(0) { | 557 | if !repeatTick(0) { | ||
| 558 | t.Fatal("continuous tick stopped unexpectedly") | 558 | t.Fatal("continuous tick stopped unexpectedly") | ||
| 559 | } | 559 | } | ||
| @@ -561,21 +561,21 @@ func TestPageScrollRepeatTick_RefreshesConfiguredPageModeTimeout(t *testing.T) { | |||||
| 561 | t.Fatalf("continuous dispatches=%d, want 1", continuous) | 561 | t.Fatalf("continuous dispatches=%d, want 1", continuous) | ||
| 562 | } | 562 | } | ||
| 563 | if timerStarts <= afterTap { | 563 | if timerStarts <= afterTap { | ||
| 564 | - | t.Fatalf("continuous tick must refresh page-mode timeout: afterTap=%d afterTick=%d", afterTap, timerStarts) | 564 | + | t.Fatalf("continuous tick must refresh vim-mode timeout: afterTap=%d afterTick=%d", afterTap, timerStarts) |
| 565 | } | 565 | } | ||
| 566 | - | if h.Mode() != ModePage { | 566 | + | if h.Mode() != ModeVim { |
| 567 | - | t.Fatalf("mode after continuous tick = %v, want ModePage", h.Mode()) | 567 | + | t.Fatalf("mode after continuous tick = %v, want ModeVim", h.Mode()) |
| 568 | } | 568 | } | ||
| 569 | } | 569 | } | ||
| 570 | 570 | ||||
| 571 | - | func TestHandleKeyPress_PageModeEscapeExits(t *testing.T) { | 571 | + | func TestHandleKeyPress_VimModeEscapeExits(t *testing.T) { |
| 572 | ctx := context.Background() | 572 | ctx := context.Background() | ||
| 573 | workspace := newTestWorkspace() | 573 | workspace := newTestWorkspace() | ||
| 574 | - | workspace.PageMode = entity.PageModeConfig{ | 574 | + | workspace.VimMode = entity.VimModeConfig{ |
| 575 | ActivationShortcut: "ctrl+y", | 575 | ActivationShortcut: "ctrl+y", | ||
| 576 | Actions: map[string]entity.ActionBinding{ | 576 | Actions: map[string]entity.ActionBinding{ | ||
| 577 | - | "page-scroll-down": {Keys: []string{"j"}}, | 577 | + | "vim-scroll-down": {Keys: []string{"j"}}, |
| 578 | - | "cancel": {Keys: []string{"escape"}}, | 578 | + | "cancel": {Keys: []string{"escape"}}, |
| 579 | }, | 579 | }, | ||
| 580 | } | 580 | } | ||
| 581 | 581 | ||||
| @@ -584,13 +584,13 @@ func TestHandleKeyPress_PageModeEscapeExits(t *testing.T) { | |||||
| 584 | return nil | 584 | return nil | ||
| 585 | }) | 585 | }) | ||
| 586 | 586 | ||||
| 587 | - | // Enter page mode | 587 | + | // Enter vim mode |
| 588 | h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) | 588 | h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) | ||
| 589 | - | if h.Mode() != ModePage { | 589 | + | if h.Mode() != ModeVim { |
| 590 | - | t.Fatal("failed to enter page mode") | 590 | + | t.Fatal("failed to enter vim mode") |
| 591 | } | 591 | } | ||
| 592 | 592 | ||||
| 593 | - | // Escape should exit page mode | 593 | + | // Escape should exit vim mode |
| 594 | result := h.handleKeyPress(uint(gdk.KEY_Escape), 0, 0) | 594 | result := h.handleKeyPress(uint(gdk.KEY_Escape), 0, 0) | ||
| 595 | if !result { | 595 | if !result { | ||
| 596 | t.Fatal("Escape should be consumed") | 596 | t.Fatal("Escape should be consumed") | ||
| @@ -600,10 +600,10 @@ func TestHandleKeyPress_PageModeEscapeExits(t *testing.T) { | |||||
| 600 | } | 600 | } | ||
| 601 | } | 601 | } | ||
| 602 | 602 | ||||
| 603 | - | func TestHandleKeyPress_PageModeEnterExits(t *testing.T) { | 603 | + | func TestHandleKeyPress_VimModeEnterExits(t *testing.T) { |
| 604 | ctx := context.Background() | 604 | ctx := context.Background() | ||
| 605 | workspace := newTestWorkspace() | 605 | workspace := newTestWorkspace() | ||
| 606 | - | workspace.PageMode = entity.PageModeConfig{ | 606 | + | workspace.VimMode = entity.VimModeConfig{ |
| 607 | ActivationShortcut: "ctrl+y", | 607 | ActivationShortcut: "ctrl+y", | ||
| 608 | Actions: map[string]entity.ActionBinding{ | 608 | Actions: map[string]entity.ActionBinding{ | ||
| 609 | "confirm": {Keys: []string{"enter"}}, | 609 | "confirm": {Keys: []string{"enter"}}, | ||
| @@ -615,13 +615,13 @@ func TestHandleKeyPress_PageModeEnterExits(t *testing.T) { | |||||
| 615 | return nil | 615 | return nil | ||
| 616 | }) | 616 | }) | ||
| 617 | 617 | ||||
| 618 | - | // Enter page mode | 618 | + | // Enter vim mode |
| 619 | h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) | 619 | h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) | ||
| 620 | - | if h.Mode() != ModePage { | 620 | + | if h.Mode() != ModeVim { |
| 621 | - | t.Fatal("failed to enter page mode") | 621 | + | t.Fatal("failed to enter vim mode") |
| 622 | } | 622 | } | ||
| 623 | 623 | ||||
| 624 | - | // Enter should exit page mode | 624 | + | // Enter should exit vim mode |
| 625 | result := h.handleKeyPress(uint(gdk.KEY_Return), 0, 0) | 625 | result := h.handleKeyPress(uint(gdk.KEY_Return), 0, 0) | ||
| 626 | if !result { | 626 | if !result { | ||
| 627 | t.Fatal("Enter should be consumed") | 627 | t.Fatal("Enter should be consumed") | ||
| @@ -631,10 +631,10 @@ func TestHandleKeyPress_PageModeEnterExits(t *testing.T) { | |||||
| 631 | } | 631 | } | ||
| 632 | } | 632 | } | ||
| 633 | 633 | ||||
| 634 | - | func TestHandleKeyPress_PageModeActivationTogglesOff(t *testing.T) { | 634 | + | func TestHandleKeyPress_VimModeActivationTogglesOff(t *testing.T) { |
| 635 | ctx := context.Background() | 635 | ctx := context.Background() | ||
| 636 | workspace := newTestWorkspace() | 636 | workspace := newTestWorkspace() | ||
| 637 | - | workspace.PageMode = entity.PageModeConfig{ | 637 | + | workspace.VimMode = entity.VimModeConfig{ |
| 638 | ActivationShortcut: "ctrl+y", | 638 | ActivationShortcut: "ctrl+y", | ||
| 639 | } | 639 | } | ||
| 640 | 640 | ||||
| @@ -643,8 +643,8 @@ func TestHandleKeyPress_PageModeActivationTogglesOff(t *testing.T) { | |||||
| 643 | if !h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) { | 643 | if !h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) { | ||
| 644 | t.Fatal("first Ctrl+Y should be consumed") | 644 | t.Fatal("first Ctrl+Y should be consumed") | ||
| 645 | } | 645 | } | ||
| 646 | - | if h.Mode() != ModePage { | 646 | + | if h.Mode() != ModeVim { |
| 647 | - | t.Fatalf("mode after first Ctrl+Y = %v, want ModePage", h.Mode()) | 647 | + | t.Fatalf("mode after first Ctrl+Y = %v, want ModeVim", h.Mode()) |
| 648 | } | 648 | } | ||
| 649 | 649 | ||||
| 650 | h.handleKeyRelease(uint('y')) | 650 | h.handleKeyRelease(uint('y')) | ||
| @@ -656,13 +656,13 @@ func TestHandleKeyPress_PageModeActivationTogglesOff(t *testing.T) { | |||||
| 656 | } | 656 | } | ||
| 657 | } | 657 | } | ||
| 658 | 658 | ||||
| 659 | - | func TestHandleKeyPress_PageModeEscapeExitsWithoutCancelBinding(t *testing.T) { | 659 | + | func TestHandleKeyPress_VimModeEscapeExitsWithoutCancelBinding(t *testing.T) { |
| 660 | ctx := context.Background() | 660 | ctx := context.Background() | ||
| 661 | workspace := newTestWorkspace() | 661 | workspace := newTestWorkspace() | ||
| 662 | - | workspace.PageMode = entity.PageModeConfig{ | 662 | + | workspace.VimMode = entity.VimModeConfig{ |
| 663 | ActivationShortcut: "ctrl+y", | 663 | ActivationShortcut: "ctrl+y", | ||
| 664 | Actions: map[string]entity.ActionBinding{ | 664 | Actions: map[string]entity.ActionBinding{ | ||
| 665 | - | "page-scroll-down": {Keys: []string{"j"}}, | 665 | + | "vim-scroll-down": {Keys: []string{"j"}}, |
| 666 | }, | 666 | }, | ||
| 667 | } | 667 | } | ||
| 668 | 668 | ||||
| @@ -670,8 +670,8 @@ func TestHandleKeyPress_PageModeEscapeExitsWithoutCancelBinding(t *testing.T) { | |||||
| 670 | if !h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) { | 670 | if !h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) { | ||
| 671 | t.Fatal("Ctrl+Y should be consumed") | 671 | t.Fatal("Ctrl+Y should be consumed") | ||
| 672 | } | 672 | } | ||
| 673 | - | if h.Mode() != ModePage { | 673 | + | if h.Mode() != ModeVim { |
| 674 | - | t.Fatal("failed to enter page mode") | 674 | + | t.Fatal("failed to enter vim mode") |
| 675 | } | 675 | } | ||
| 676 | 676 | ||||
| 677 | if !h.handleKeyPress(uint(gdk.KEY_Escape), 0, 0) { | 677 | if !h.handleKeyPress(uint(gdk.KEY_Escape), 0, 0) { | ||
| @@ -682,10 +682,10 @@ func TestHandleKeyPress_PageModeEscapeExitsWithoutCancelBinding(t *testing.T) { | |||||
| 682 | } | 682 | } | ||
| 683 | } | 683 | } | ||
| 684 | 684 | ||||
| 685 | - | func TestHandleKeyPress_PageModeActivationNotInPassThrough(t *testing.T) { | 685 | + | func TestHandleKeyPress_VimModeActivationNotInPassThrough(t *testing.T) { |
| 686 | ctx := context.Background() | 686 | ctx := context.Background() | ||
| 687 | workspace := newTestWorkspace() | 687 | workspace := newTestWorkspace() | ||
| 688 | - | workspace.PageMode = entity.PageModeConfig{ | 688 | + | workspace.VimMode = entity.VimModeConfig{ |
| 689 | ActivationShortcut: "ctrl+y", | 689 | ActivationShortcut: "ctrl+y", | ||
| 690 | } | 690 | } | ||
| 691 | 691 | ||||
| @@ -696,55 +696,55 @@ func TestHandleKeyPress_PageModeActivationNotInPassThrough(t *testing.T) { | |||||
| 696 | return RoutePassToWidget | 696 | return RoutePassToWidget | ||
| 697 | }) | 697 | }) | ||
| 698 | 698 | ||||
| 699 | - | // Ctrl+Y should NOT activate page mode because route says pass to widget | 699 | + | // Ctrl+Y should NOT activate vim mode because route says pass to widget |
| 700 | result := h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) | 700 | result := h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) | ||
| 701 | if result { | 701 | if result { | ||
| 702 | t.Fatal("Ctrl+Y should NOT be consumed when routed to widget") | 702 | t.Fatal("Ctrl+Y should NOT be consumed when routed to widget") | ||
| 703 | } | 703 | } | ||
| 704 | if h.Mode() != ModeNormal { | 704 | if h.Mode() != ModeNormal { | ||
| 705 | - | t.Fatalf("mode = %v, want ModeNormal (should not enter page mode)", h.Mode()) | 705 | + | t.Fatalf("mode = %v, want ModeNormal (should not enter vim mode)", h.Mode()) |
| 706 | } | 706 | } | ||
| 707 | } | 707 | } | ||
| 708 | 708 | ||||
| 709 | - | func TestHandleKeyPress_PageModeActivationPassesThroughWhenBlocked(t *testing.T) { | 709 | + | func TestHandleKeyPress_VimModeActivationPassesThroughWhenBlocked(t *testing.T) { |
| 710 | ctx := context.Background() | 710 | ctx := context.Background() | ||
| 711 | workspace := newTestWorkspace() | 711 | workspace := newTestWorkspace() | ||
| 712 | - | workspace.PageMode = entity.PageModeConfig{ | 712 | + | workspace.VimMode = entity.VimModeConfig{ |
| 713 | ActivationShortcut: "ctrl+y", | 713 | ActivationShortcut: "ctrl+y", | ||
| 714 | } | 714 | } | ||
| 715 | 715 | ||||
| 716 | h := NewKeyboardHandler(ctx, workspace, newTestSession()) | 716 | h := NewKeyboardHandler(ctx, workspace, newTestSession()) | ||
| 717 | - | h.SetPageModeActivationPassthrough(func() bool { return true }) | 717 | + | h.SetVimModeActivationPassthrough(func() bool { return true }) |
| 718 | 718 | ||||
| 719 | result := h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) | 719 | result := h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) | ||
| 720 | if result { | 720 | if result { | ||
| 721 | - | t.Fatal("Ctrl+Y should pass through when page mode activation is blocked") | 721 | + | t.Fatal("Ctrl+Y should pass through when vim mode activation is blocked") |
| 722 | } | 722 | } | ||
| 723 | if h.Mode() != ModeNormal { | 723 | if h.Mode() != ModeNormal { | ||
| 724 | t.Fatalf("mode = %v, want ModeNormal", h.Mode()) | 724 | t.Fatalf("mode = %v, want ModeNormal", h.Mode()) | ||
| 725 | } | 725 | } | ||
| 726 | } | 726 | } | ||
| 727 | 727 | ||||
| 728 | - | func TestDispatchAction_PageModeActivationPassesThroughWhenBlocked(t *testing.T) { | 728 | + | func TestDispatchAction_VimModeActivationPassesThroughWhenBlocked(t *testing.T) { |
| 729 | ctx := context.Background() | 729 | ctx := context.Background() | ||
| 730 | workspace := newTestWorkspace() | 730 | workspace := newTestWorkspace() | ||
| 731 | - | workspace.PageMode = entity.PageModeConfig{ | 731 | + | workspace.VimMode = entity.VimModeConfig{ |
| 732 | ActivationShortcut: "ctrl+y", | 732 | ActivationShortcut: "ctrl+y", | ||
| 733 | } | 733 | } | ||
| 734 | 734 | ||||
| 735 | h := NewKeyboardHandler(ctx, workspace, newTestSession()) | 735 | h := NewKeyboardHandler(ctx, workspace, newTestSession()) | ||
| 736 | - | h.SetPageModeActivationPassthrough(func() bool { return true }) | 736 | + | h.SetVimModeActivationPassthrough(func() bool { return true }) |
| 737 | 737 | ||||
| 738 | - | consumed := h.DispatchAction(ActionEnterPageMode) | 738 | + | consumed := h.DispatchAction(ActionEnterVimMode) |
| 739 | if consumed { | 739 | if consumed { | ||
| 740 | - | t.Fatal("DispatchAction should not consume blocked page mode activation") | 740 | + | t.Fatal("DispatchAction should not consume blocked vim mode activation") |
| 741 | } | 741 | } | ||
| 742 | if h.Mode() != ModeNormal { | 742 | if h.Mode() != ModeNormal { | ||
| 743 | t.Fatalf("mode = %v, want ModeNormal", h.Mode()) | 743 | t.Fatalf("mode = %v, want ModeNormal", h.Mode()) | ||
| 744 | } | 744 | } | ||
| 745 | } | 745 | } | ||
| 746 | 746 | ||||
| 747 | - | func TestHandleKeyPress_PageModeFastScrollLookup(t *testing.T) { | 747 | + | func TestHandleKeyPress_VimModeFastScrollLookup(t *testing.T) { |
| 748 | tests := []struct { | 748 | tests := []struct { | ||
| 749 | name string | 749 | name string | ||
| 750 | configAction string | 750 | configAction string | ||
| @@ -753,15 +753,15 @@ func TestHandleKeyPress_PageModeFastScrollLookup(t *testing.T) { | |||||
| 753 | }{ | 753 | }{ | ||
| 754 | { | 754 | { | ||
| 755 | name: "scroll down fast", | 755 | name: "scroll down fast", | ||
| 756 | - | configAction: "page-scroll-down-fast", | 756 | + | configAction: "vim-scroll-down-fast", |
| 757 | key: uint('d'), | 757 | key: uint('d'), | ||
| 758 | - | expected: ActionPageScrollDownFast, | 758 | + | expected: ActionVimScrollDownFast, |
| 759 | }, | 759 | }, | ||
| 760 | { | 760 | { | ||
| 761 | name: "scroll up fast", | 761 | name: "scroll up fast", | ||
| 762 | - | configAction: "page-scroll-up-fast", | 762 | + | configAction: "vim-scroll-up-fast", |
| 763 | key: uint('u'), | 763 | key: uint('u'), | ||
| 764 | - | expected: ActionPageScrollUpFast, | 764 | + | expected: ActionVimScrollUpFast, |
| 765 | }, | 765 | }, | ||
| 766 | } | 766 | } | ||
| 767 | 767 | ||||
| @@ -769,7 +769,7 @@ func TestHandleKeyPress_PageModeFastScrollLookup(t *testing.T) { | |||||
| 769 | t.Run(tt.name, func(t *testing.T) { | 769 | t.Run(tt.name, func(t *testing.T) { | ||
| 770 | ctx := context.Background() | 770 | ctx := context.Background() | ||
| 771 | workspace := newTestWorkspace() | 771 | workspace := newTestWorkspace() | ||
| 772 | - | workspace.PageMode = entity.PageModeConfig{ | 772 | + | workspace.VimMode = entity.VimModeConfig{ |
| 773 | ActivationShortcut: "ctrl+y", | 773 | ActivationShortcut: "ctrl+y", | ||
| 774 | Actions: map[string]entity.ActionBinding{ | 774 | Actions: map[string]entity.ActionBinding{ | ||
| 775 | tt.configAction: {Keys: []string{"ctrl+" + string(rune(tt.key))}}, | 775 | tt.configAction: {Keys: []string{"ctrl+" + string(rune(tt.key))}}, | ||
| @@ -785,35 +785,35 @@ func TestHandleKeyPress_PageModeFastScrollLookup(t *testing.T) { | |||||
| 785 | return nil | 785 | return nil | ||
| 786 | }) | 786 | }) | ||
| 787 | 787 | ||||
| 788 | - | // Enter page mode. | 788 | + | // Enter vim mode. |
| 789 | h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) | 789 | h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) | ||
| 790 | - | if h.Mode() != ModePage { | 790 | + | if h.Mode() != ModeVim { |
| 791 | - | t.Fatal("failed to enter page mode") | 791 | + | t.Fatal("failed to enter vim mode") |
| 792 | } | 792 | } | ||
| 793 | 793 | ||||
| 794 | - | // Fast-scroll shortcut in page mode should dispatch the expected action. | 794 | + | // Fast-scroll shortcut in vim mode should dispatch the expected action. |
| 795 | result := h.handleKeyPress(tt.key, 0, gdk.ControlMaskValue) | 795 | result := h.handleKeyPress(tt.key, 0, gdk.ControlMaskValue) | ||
| 796 | if !result { | 796 | if !result { | ||
| 797 | - | t.Fatal("fast scroll shortcut in page mode should be consumed") | 797 | + | t.Fatal("fast scroll shortcut in vim mode should be consumed") |
| 798 | } | 798 | } | ||
| 799 | if actionCalls != 1 || lastAction != tt.expected { | 799 | if actionCalls != 1 || lastAction != tt.expected { | ||
| 800 | t.Fatalf("expected %s, got action=%s calls=%d", tt.expected, lastAction, actionCalls) | 800 | t.Fatalf("expected %s, got action=%s calls=%d", tt.expected, lastAction, actionCalls) | ||
| 801 | } | 801 | } | ||
| 802 | - | if h.Mode() != ModePage { | 802 | + | if h.Mode() != ModeVim { |
| 803 | - | t.Fatal("should stay in page mode after scroll action") | 803 | + | t.Fatal("should stay in vim mode after scroll action") |
| 804 | } | 804 | } | ||
| 805 | }) | 805 | }) | ||
| 806 | } | 806 | } | ||
| 807 | } | 807 | } | ||
| 808 | 808 | ||||
| 809 | - | func TestHandleKeyPress_PageModeNoAutoExitForScrollActions(t *testing.T) { | 809 | + | func TestHandleKeyPress_VimModeNoAutoExitForScrollActions(t *testing.T) { |
| 810 | ctx := context.Background() | 810 | ctx := context.Background() | ||
| 811 | workspace := newTestWorkspace() | 811 | workspace := newTestWorkspace() | ||
| 812 | - | workspace.PageMode = entity.PageModeConfig{ | 812 | + | workspace.VimMode = entity.VimModeConfig{ |
| 813 | ActivationShortcut: "ctrl+y", | 813 | ActivationShortcut: "ctrl+y", | ||
| 814 | Actions: map[string]entity.ActionBinding{ | 814 | Actions: map[string]entity.ActionBinding{ | ||
| 815 | - | "page-scroll-left": {Keys: []string{"h"}}, | 815 | + | "vim-scroll-left": {Keys: []string{"h"}}, |
| 816 | - | "page-scroll-right": {Keys: []string{"l"}}, | 816 | + | "vim-scroll-right": {Keys: []string{"l"}}, |
| 817 | }, | 817 | }, | ||
| 818 | } | 818 | } | ||
| 819 | 819 | ||||
| @@ -824,22 +824,22 @@ func TestHandleKeyPress_PageModeNoAutoExitForScrollActions(t *testing.T) { | |||||
| 824 | return nil | 824 | return nil | ||
| 825 | }) | 825 | }) | ||
| 826 | 826 | ||||
| 827 | - | // Enter page mode | 827 | + | // Enter vim mode |
| 828 | h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) | 828 | h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) | ||
| 829 | - | if h.Mode() != ModePage { | 829 | + | if h.Mode() != ModeVim { |
| 830 | - | t.Fatal("failed to enter page mode") | 830 | + | t.Fatal("failed to enter vim mode") |
| 831 | } | 831 | } | ||
| 832 | 832 | ||||
| 833 | // Scroll left should not auto-exit | 833 | // Scroll left should not auto-exit | ||
| 834 | h.handleKeyPress(uint('h'), 0, 0) | 834 | h.handleKeyPress(uint('h'), 0, 0) | ||
| 835 | - | if h.Mode() != ModePage { | 835 | + | if h.Mode() != ModeVim { |
| 836 | - | t.Fatal("scroll left should not exit page mode") | 836 | + | t.Fatal("scroll left should not exit vim mode") |
| 837 | } | 837 | } | ||
| 838 | 838 | ||||
| 839 | // Scroll right should not auto-exit | 839 | // Scroll right should not auto-exit | ||
| 840 | h.handleKeyPress(uint('l'), 0, 0) | 840 | h.handleKeyPress(uint('l'), 0, 0) | ||
| 841 | - | if h.Mode() != ModePage { | 841 | + | if h.Mode() != ModeVim { |
| 842 | - | t.Fatal("scroll right should not exit page mode") | 842 | + | t.Fatal("scroll right should not exit vim mode") |
| 843 | } | 843 | } | ||
| 844 | 844 | ||||
| 845 | if actionCalls != 2 { | 845 | if actionCalls != 2 { | ||
| @@ -847,13 +847,13 @@ func TestHandleKeyPress_PageModeNoAutoExitForScrollActions(t *testing.T) { | |||||
| 847 | } | 847 | } | ||
| 848 | } | 848 | } | ||
| 849 | 849 | ||||
| 850 | - | func TestHandleKeyPress_PageModeArrowKeysPassThroughNatively(t *testing.T) { | 850 | + | func TestHandleKeyPress_VimModeArrowKeysPassThroughNatively(t *testing.T) { |
| 851 | ctx := context.Background() | 851 | ctx := context.Background() | ||
| 852 | workspace := newTestWorkspace() | 852 | workspace := newTestWorkspace() | ||
| 853 | - | workspace.PageMode = entity.PageModeConfig{ | 853 | + | workspace.VimMode = entity.VimModeConfig{ |
| 854 | ActivationShortcut: "ctrl+y", | 854 | ActivationShortcut: "ctrl+y", | ||
| 855 | Actions: map[string]entity.ActionBinding{ | 855 | Actions: map[string]entity.ActionBinding{ | ||
| 856 | - | "page-scroll-down": {Keys: []string{"j"}}, | 856 | + | "vim-scroll-down": {Keys: []string{"j"}}, |
| 857 | }, | 857 | }, | ||
| 858 | } | 858 | } | ||
| 859 | 859 | ||||
| @@ -865,32 +865,32 @@ func TestHandleKeyPress_PageModeArrowKeysPassThroughNatively(t *testing.T) { | |||||
| 865 | }) | 865 | }) | ||
| 866 | 866 | ||||
| 867 | h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) | 867 | h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) | ||
| 868 | - | if h.Mode() != ModePage { | 868 | + | if h.Mode() != ModeVim { |
| 869 | - | t.Fatal("failed to enter page mode") | 869 | + | t.Fatal("failed to enter vim mode") |
| 870 | } | 870 | } | ||
| 871 | 871 | ||||
| 872 | for _, keyval := range []uint{uint(gdk.KEY_Left), uint(gdk.KEY_Right), uint(gdk.KEY_Up), uint(gdk.KEY_Down)} { | 872 | for _, keyval := range []uint{uint(gdk.KEY_Left), uint(gdk.KEY_Right), uint(gdk.KEY_Up), uint(gdk.KEY_Down)} { | ||
| 873 | consumed := h.handleKeyPress(keyval, 0, 0) | 873 | consumed := h.handleKeyPress(keyval, 0, 0) | ||
| 874 | if consumed { | 874 | if consumed { | ||
| 875 | - | t.Fatalf("arrow key %d should pass through to native page handling in page mode", keyval) | 875 | + | t.Fatalf("arrow key %d should pass through to native page handling in vim mode", keyval) |
| 876 | } | 876 | } | ||
| 877 | - | if h.Mode() != ModePage { | 877 | + | if h.Mode() != ModeVim { |
| 878 | - | t.Fatalf("arrow key %d should keep page mode active", keyval) | 878 | + | t.Fatalf("arrow key %d should keep vim mode active", keyval) |
| 879 | } | 879 | } | ||
| 880 | } | 880 | } | ||
| 881 | 881 | ||||
| 882 | if actionCalls != 0 { | 882 | if actionCalls != 0 { | ||
| 883 | - | t.Fatalf("arrow key passthrough should not dispatch page-mode action, got %d calls", actionCalls) | 883 | + | t.Fatalf("arrow key passthrough should not dispatch vim-mode action, got %d calls", actionCalls) |
| 884 | } | 884 | } | ||
| 885 | } | 885 | } | ||
| 886 | 886 | ||||
| 887 | - | func TestHandleKeyPress_PageModeBlocksGlobalShortcutFallback(t *testing.T) { | 887 | + | func TestHandleKeyPress_VimModeBlocksGlobalShortcutFallback(t *testing.T) { |
| 888 | ctx := context.Background() | 888 | ctx := context.Background() | ||
| 889 | workspace := newTestWorkspace() | 889 | workspace := newTestWorkspace() | ||
| 890 | - | workspace.PageMode = entity.PageModeConfig{ | 890 | + | workspace.VimMode = entity.VimModeConfig{ |
| 891 | ActivationShortcut: "ctrl+y", | 891 | ActivationShortcut: "ctrl+y", | ||
| 892 | Actions: map[string]entity.ActionBinding{ | 892 | Actions: map[string]entity.ActionBinding{ | ||
| 893 | - | "page-scroll-down": {Keys: []string{"j"}}, | 893 | + | "vim-scroll-down": {Keys: []string{"j"}}, |
| 894 | }, | 894 | }, | ||
| 895 | } | 895 | } | ||
| 896 | 896 | ||||
| @@ -906,28 +906,28 @@ func TestHandleKeyPress_PageModeBlocksGlobalShortcutFallback(t *testing.T) { | |||||
| 906 | if !h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) { | 906 | if !h.handleKeyPress(uint('y'), 0, gdk.ControlMaskValue) { | ||
| 907 | t.Fatal("Ctrl+Y should be consumed") | 907 | t.Fatal("Ctrl+Y should be consumed") | ||
| 908 | } | 908 | } | ||
| 909 | - | if h.Mode() != ModePage { | 909 | + | if h.Mode() != ModeVim { |
| 910 | - | t.Fatal("failed to enter page mode") | 910 | + | t.Fatal("failed to enter vim mode") |
| 911 | } | 911 | } | ||
| 912 | 912 | ||||
| 913 | if !h.handleKeyPress(uint('l'), 0, gdk.ControlMaskValue) { | 913 | if !h.handleKeyPress(uint('l'), 0, gdk.ControlMaskValue) { | ||
| 914 | - | t.Fatal("Ctrl+L should be consumed inside page mode") | 914 | + | t.Fatal("Ctrl+L should be consumed inside vim mode") |
| 915 | } | 915 | } | ||
| 916 | if actionCalls != 0 { | 916 | if actionCalls != 0 { | ||
| 917 | - | t.Fatalf("Ctrl+L should not dispatch a global action in page mode, got %d calls (%s)", actionCalls, lastAction) | 917 | + | t.Fatalf("Ctrl+L should not dispatch a global action in vim mode, got %d calls (%s)", actionCalls, lastAction) |
| 918 | } | 918 | } | ||
| 919 | - | if h.Mode() != ModePage { | 919 | + | if h.Mode() != ModeVim { |
| 920 | - | t.Fatalf("mode after blocked Ctrl+L = %v, want ModePage", h.Mode()) | 920 | + | t.Fatalf("mode after blocked Ctrl+L = %v, want ModeVim", h.Mode()) |
| 921 | } | 921 | } | ||
| 922 | } | 922 | } | ||
| 923 | 923 | ||||
| 924 | - | func TestPageScrollRepeat_FirstKeyDownDispatchesTapAndArmsHold(t *testing.T) { | 924 | + | func TestVimScrollRepeat_FirstKeyDownDispatchesTapAndArmsHold(t *testing.T) { |
| 925 | - | h, now, tick := newPageScrollRepeatTestHandler(t) | 925 | + | h, now, tick := newVimScrollRepeatTestHandler(t) |
| 926 | actionCalls := 0 | 926 | actionCalls := 0 | ||
| 927 | h.SetOnAction(func(_ context.Context, action Action) error { | 927 | h.SetOnAction(func(_ context.Context, action Action) error { | ||
| 928 | actionCalls++ | 928 | actionCalls++ | ||
| 929 | - | if action != ActionPageScrollDown { | 929 | + | if action != ActionVimScrollDown { |
| 930 | - | t.Fatalf("action = %s, want %s", action, ActionPageScrollDown) | 930 | + | t.Fatalf("action = %s, want %s", action, ActionVimScrollDown) |
| 931 | } | 931 | } | ||
| 932 | return nil | 932 | return nil | ||
| 933 | }) | 933 | }) | ||
| @@ -942,7 +942,7 @@ func TestPageScrollRepeat_FirstKeyDownDispatchesTapAndArmsHold(t *testing.T) { | |||||
| 942 | t.Fatal("first keydown must arm the hold source without an OS repeat") | 942 | t.Fatal("first keydown must arm the hold source without an OS repeat") | ||
| 943 | } | 943 | } | ||
| 944 | 944 | ||||
| 945 | - | *now = now.Add(pageScrollHoldDelay - time.Millisecond) | 945 | + | *now = now.Add(vimScrollHoldDelay - time.Millisecond) |
| 946 | if !(*tick)(0) { | 946 | if !(*tick)(0) { | ||
| 947 | t.Fatal("hold source should remain armed before the delay") | 947 | t.Fatal("hold source should remain armed before the delay") | ||
| 948 | } | 948 | } | ||
| @@ -951,20 +951,20 @@ func TestPageScrollRepeat_FirstKeyDownDispatchesTapAndArmsHold(t *testing.T) { | |||||
| 951 | } | 951 | } | ||
| 952 | } | 952 | } | ||
| 953 | 953 | ||||
| 954 | - | func TestPageScrollRepeat_HoldStartsWithoutOSRepeat(t *testing.T) { | 954 | + | func TestVimScrollRepeat_HoldStartsWithoutOSRepeat(t *testing.T) { |
| 955 | - | h, now, tick := newPageScrollRepeatTestHandler(t) | 955 | + | h, now, tick := newVimScrollRepeatTestHandler(t) |
| 956 | continuous := 0 | 956 | continuous := 0 | ||
| 957 | h.SetOnAction(func(context.Context, Action) error { return nil }) | 957 | h.SetOnAction(func(context.Context, Action) error { return nil }) | ||
| 958 | - | h.SetOnPageScrollLifecycle(func(_ context.Context, action Action, phase PageScrollPhase) error { | 958 | + | h.SetOnVimScrollLifecycle(func(_ context.Context, action Action, phase VimScrollPhase) error { |
| 959 | - | if action != ActionPageScrollDown || phase != PageScrollContinuous { | 959 | + | if action != ActionVimScrollDown || phase != VimScrollContinuous { |
| 960 | - | t.Fatalf("lifecycle = (%s, %v), want (%s, continuous)", action, phase, ActionPageScrollDown) | 960 | + | t.Fatalf("lifecycle = (%s, %v), want (%s, continuous)", action, phase, ActionVimScrollDown) |
| 961 | } | 961 | } | ||
| 962 | continuous++ | 962 | continuous++ | ||
| 963 | return nil | 963 | return nil | ||
| 964 | }) | 964 | }) | ||
| 965 | 965 | ||||
| 966 | h.handleKeyPress(uint('j'), 0, 0) | 966 | h.handleKeyPress(uint('j'), 0, 0) | ||
| 967 | - | *now = now.Add(pageScrollHoldDelay) | 967 | + | *now = now.Add(vimScrollHoldDelay) |
| 968 | if !(*tick)(0) { | 968 | if !(*tick)(0) { | ||
| 969 | t.Fatal("hold source stopped at the delay") | 969 | t.Fatal("hold source stopped at the delay") | ||
| 970 | } | 970 | } | ||
| @@ -973,19 +973,19 @@ func TestPageScrollRepeat_HoldStartsWithoutOSRepeat(t *testing.T) { | |||||
| 973 | } | 973 | } | ||
| 974 | } | 974 | } | ||
| 975 | 975 | ||||
| 976 | - | func TestPageScrollRepeat_FixedCadenceIgnoresDisplayAndOSRepeatRates(t *testing.T) { | 976 | + | func TestVimScrollRepeat_FixedCadenceIgnoresDisplayAndOSRepeatRates(t *testing.T) { |
| 977 | const simulatedHold = time.Second | 977 | const simulatedHold = time.Second | ||
| 978 | simulate := func(t *testing.T, externalHz int) int { | 978 | simulate := func(t *testing.T, externalHz int) int { | ||
| 979 | t.Helper() | 979 | t.Helper() | ||
| 980 | - | h, now, tick := newPageScrollRepeatTestHandler(t) | 980 | + | h, now, tick := newVimScrollRepeatTestHandler(t) |
| 981 | tapDistance := 0 | 981 | tapDistance := 0 | ||
| 982 | continuousDistance := 0 | 982 | continuousDistance := 0 | ||
| 983 | h.SetOnAction(func(context.Context, Action) error { | 983 | h.SetOnAction(func(context.Context, Action) error { | ||
| 984 | tapDistance += 80 | 984 | tapDistance += 80 | ||
| 985 | return nil | 985 | return nil | ||
| 986 | }) | 986 | }) | ||
| 987 | - | h.SetOnPageScrollLifecycle(func(_ context.Context, _ Action, phase PageScrollPhase) error { | 987 | + | h.SetOnVimScrollLifecycle(func(_ context.Context, _ Action, phase VimScrollPhase) error { |
| 988 | - | if phase == PageScrollContinuous { | 988 | + | if phase == VimScrollContinuous { |
| 989 | continuousDistance += 80 | 989 | continuousDistance += 80 | ||
| 990 | } | 990 | } | ||
| 991 | return nil | 991 | return nil | ||
| @@ -994,7 +994,7 @@ func TestPageScrollRepeat_FixedCadenceIgnoresDisplayAndOSRepeatRates(t *testing. | |||||
| 994 | 994 | ||||
| 995 | start := *now | 995 | start := *now | ||
| 996 | nextExternal := time.Second / time.Duration(externalHz) | 996 | nextExternal := time.Second / time.Duration(externalHz) | ||
| 997 | - | for elapsed := pageScrollCadence; elapsed <= simulatedHold; elapsed += pageScrollCadence { | 997 | + | for elapsed := vimScrollCadence; elapsed <= simulatedHold; elapsed += vimScrollCadence { |
| 998 | *now = start.Add(elapsed) | 998 | *now = start.Add(elapsed) | ||
| 999 | for elapsed >= nextExternal { | 999 | for elapsed >= nextExternal { | ||
| 1000 | h.handleKeyPress(uint('j'), 0, 0) // ignored OS auto-repeat | 1000 | h.handleKeyPress(uint('j'), 0, 0) // ignored OS auto-repeat | ||
| @@ -1017,7 +1017,7 @@ func TestPageScrollRepeat_FixedCadenceIgnoresDisplayAndOSRepeatRates(t *testing. | |||||
| 1017 | } | 1017 | } | ||
| 1018 | } | 1018 | } | ||
| 1019 | 1019 | ||||
| 1020 | - | func TestPageScrollRepeat_ReleaseAndEscapeStopImmediately(t *testing.T) { | 1020 | + | func TestVimScrollRepeat_ReleaseAndEscapeStopImmediately(t *testing.T) { |
| 1021 | for _, tc := range []struct { | 1021 | for _, tc := range []struct { | ||
| 1022 | name string | 1022 | name string | ||
| 1023 | stop func(*KeyboardHandler) | 1023 | stop func(*KeyboardHandler) | ||
| @@ -1026,19 +1026,19 @@ func TestPageScrollRepeat_ReleaseAndEscapeStopImmediately(t *testing.T) { | |||||
| 1026 | {name: "escape", stop: func(h *KeyboardHandler) { h.handleKeyPress(uint(gdk.KEY_Escape), 0, 0) }}, | 1026 | {name: "escape", stop: func(h *KeyboardHandler) { h.handleKeyPress(uint(gdk.KEY_Escape), 0, 0) }}, | ||
| 1027 | } { | 1027 | } { | ||
| 1028 | t.Run(tc.name, func(t *testing.T) { | 1028 | t.Run(tc.name, func(t *testing.T) { | ||
| 1029 | - | h, now, tick := newPageScrollRepeatTestHandler(t) | 1029 | + | h, now, tick := newVimScrollRepeatTestHandler(t) |
| 1030 | removed := 0 | 1030 | removed := 0 | ||
| 1031 | stops := 0 | 1031 | stops := 0 | ||
| 1032 | - | h.pageScrollRepeatRemove = func(uint) bool { removed++; return true } | 1032 | + | h.vimScrollRepeatRemove = func(uint) bool { removed++; return true } |
| 1033 | h.SetOnAction(func(context.Context, Action) error { return nil }) | 1033 | h.SetOnAction(func(context.Context, Action) error { return nil }) | ||
| 1034 | - | h.SetOnPageScrollLifecycle(func(_ context.Context, _ Action, phase PageScrollPhase) error { | 1034 | + | h.SetOnVimScrollLifecycle(func(_ context.Context, _ Action, phase VimScrollPhase) error { |
| 1035 | - | if phase == PageScrollStop { | 1035 | + | if phase == VimScrollStop { |
| 1036 | stops++ | 1036 | stops++ | ||
| 1037 | } | 1037 | } | ||
| 1038 | return nil | 1038 | return nil | ||
| 1039 | }) | 1039 | }) | ||
| 1040 | h.handleKeyPress(uint('j'), 0, 0) | 1040 | h.handleKeyPress(uint('j'), 0, 0) | ||
| 1041 | - | *now = now.Add(pageScrollHoldDelay) | 1041 | + | *now = now.Add(vimScrollHoldDelay) |
| 1042 | (*tick)(0) | 1042 | (*tick)(0) | ||
| 1043 | 1043 | ||||
| 1044 | tc.stop(h) | 1044 | tc.stop(h) | ||
| @@ -1052,24 +1052,24 @@ func TestPageScrollRepeat_ReleaseAndEscapeStopImmediately(t *testing.T) { | |||||
| 1052 | } | 1052 | } | ||
| 1053 | } | 1053 | } | ||
| 1054 | 1054 | ||||
| 1055 | - | func newPageScrollRepeatTestHandler(t *testing.T) (*KeyboardHandler, *time.Time, *glib.SourceFunc) { | 1055 | + | func newVimScrollRepeatTestHandler(t *testing.T) (*KeyboardHandler, *time.Time, *glib.SourceFunc) { |
| 1056 | t.Helper() | 1056 | t.Helper() | ||
| 1057 | workspace := newTestWorkspace() | 1057 | workspace := newTestWorkspace() | ||
| 1058 | - | workspace.PageMode = entity.PageModeConfig{ | 1058 | + | workspace.VimMode = entity.VimModeConfig{ |
| 1059 | - | Actions: map[string]entity.ActionBinding{"page-scroll-down": {Keys: []string{"j"}}}, | 1059 | + | Actions: map[string]entity.ActionBinding{"vim-scroll-down": {Keys: []string{"j"}}}, |
| 1060 | } | 1060 | } | ||
| 1061 | h := NewKeyboardHandler(context.Background(), workspace, newTestSession()) | 1061 | h := NewKeyboardHandler(context.Background(), workspace, newTestSession()) | ||
| 1062 | - | h.EnterPageMode() | 1062 | + | h.EnterVimMode() |
| 1063 | now := time.Unix(1_000, 0) | 1063 | now := time.Unix(1_000, 0) | ||
| 1064 | var tick glib.SourceFunc | 1064 | var tick glib.SourceFunc | ||
| 1065 | - | h.pageScrollNow = func() time.Time { return now } | 1065 | + | h.vimScrollNow = func() time.Time { return now } |
| 1066 | - | h.pageScrollRepeatAdd = func(intervalMS uint, cb *glib.SourceFunc) uint { | 1066 | + | h.vimScrollRepeatAdd = func(intervalMS uint, cb *glib.SourceFunc) uint { |
| 1067 | - | if intervalMS != uint(pageScrollCadence/time.Millisecond) { | 1067 | + | if intervalMS != uint(vimScrollCadence/time.Millisecond) { |
| 1068 | - | t.Fatalf("cadence = %dms, want %dms", intervalMS, pageScrollCadence/time.Millisecond) | 1068 | + | t.Fatalf("cadence = %dms, want %dms", intervalMS, vimScrollCadence/time.Millisecond) |
| 1069 | } | 1069 | } | ||
| 1070 | tick = *cb | 1070 | tick = *cb | ||
| 1071 | return 99 | 1071 | return 99 | ||
| 1072 | } | 1072 | } | ||
| 1073 | - | h.pageScrollRepeatRemove = func(uint) bool { return true } | 1073 | + | h.vimScrollRepeatRemove = func(uint) bool { return true } |
| 1074 | return h, &now, &tick | 1074 | return h, &now, &tick | ||
| 1075 | } | 1075 | } | ||
M internal/ui/input/modal.go +9 -9
| @@ -26,8 +26,8 @@ const ( | |||||
| 26 | ModeSession | 26 | ModeSession | ||
| 27 | // ModeResize is the modal pane resizing mode. | 27 | // ModeResize is the modal pane resizing mode. | ||
| 28 | ModeResize | 28 | ModeResize | ||
| 29 | - | // ModePage is the modal page scrolling mode. | 29 | + | // ModeVim is the modal Vim-style scrolling mode. |
| 30 | - | ModePage | 30 | + | ModeVim |
| 31 | ) | 31 | ) | ||
| 32 | 32 | ||||
| 33 | // String returns a human-readable mode name. | 33 | // String returns a human-readable mode name. | ||
| @@ -43,8 +43,8 @@ func (m Mode) String() string { | |||||
| 43 | return "session" | 43 | return "session" | ||
| 44 | case ModeResize: | 44 | case ModeResize: | ||
| 45 | return "resize" | 45 | return "resize" | ||
| 46 | - | case ModePage: | 46 | + | case ModeVim: |
| 47 | - | return "page" | 47 | + | return "vim" |
| 48 | default: | 48 | default: | ||
| 49 | return "unknown" | 49 | return "unknown" | ||
| 50 | } | 50 | } | ||
| @@ -61,8 +61,8 @@ func (m Mode) DisplayName() string { | |||||
| 61 | return "SESSION MODE" | 61 | return "SESSION MODE" | ||
| 62 | case ModeResize: | 62 | case ModeResize: | ||
| 63 | return "RESIZE MODE" | 63 | return "RESIZE MODE" | ||
| 64 | - | case ModePage: | 64 | + | case ModeVim: |
| 65 | - | return "PAGE MODE" | 65 | + | return "VIM MODE" |
| 66 | default: | 66 | default: | ||
| 67 | return "" | 67 | return "" | ||
| 68 | } | 68 | } | ||
| @@ -101,9 +101,9 @@ func NewModalState(ctx context.Context) *ModalState { | |||||
| 101 | } | 101 | } | ||
| 102 | } | 102 | } | ||
| 103 | 103 | ||||
| 104 | - | // EnterPageMode switches to page scrolling mode with an optional timeout. | 104 | + | // EnterVimMode switches to Vim-style scrolling mode with an optional timeout. |
| 105 | - | func (m *ModalState) EnterPageMode(ctx context.Context, timeout time.Duration) { | 105 | + | func (m *ModalState) EnterVimMode(ctx context.Context, timeout time.Duration) { |
| 106 | - | m.enterMode(ctx, ModePage, timeout) | 106 | + | m.enterMode(ctx, ModeVim, timeout) |
| 107 | } | 107 | } | ||
| 108 | 108 | ||||
| 109 | // SetMainThreadScheduler sets the function used to dispatch timer callbacks | 109 | // SetMainThreadScheduler sets the function used to dispatch timer callbacks | ||
M internal/ui/input/shortcuts.go +44 -44
| @@ -203,14 +203,14 @@ const ( | |||||
| 203 | // Application | 203 | // Application | ||
| 204 | ActionQuit Action = "quit" | 204 | ActionQuit Action = "quit" | ||
| 205 | 205 | ||||
| 206 | - | // Page mode actions | 206 | + | // Vim mode actions |
| 207 | - | ActionEnterPageMode Action = "enter_page_mode" | 207 | + | ActionEnterVimMode Action = "enter_vim_mode" |
| 208 | - | ActionPageScrollLeft Action = "page_scroll_left" | 208 | + | ActionVimScrollLeft Action = "vim_scroll_left" |
| 209 | - | ActionPageScrollDown Action = "page_scroll_down" | 209 | + | ActionVimScrollDown Action = "vim_scroll_down" |
| 210 | - | ActionPageScrollUp Action = "page_scroll_up" | 210 | + | ActionVimScrollUp Action = "vim_scroll_up" |
| 211 | - | ActionPageScrollRight Action = "page_scroll_right" | 211 | + | ActionVimScrollRight Action = "vim_scroll_right" |
| 212 | - | ActionPageScrollDownFast Action = "page_scroll_down_fast" | 212 | + | ActionVimScrollDownFast Action = "vim_scroll_down_fast" |
| 213 | - | ActionPageScrollUpFast Action = "page_scroll_up_fast" | 213 | + | ActionVimScrollUpFast Action = "vim_scroll_up_fast" |
| 214 | ) | 214 | ) | ||
| 215 | 215 | ||||
| 216 | const floatingProfileActionPrefix = "open_floating_profile:" | 216 | const floatingProfileActionPrefix = "open_floating_profile:" | ||
| @@ -231,8 +231,8 @@ type ShortcutSet struct { | |||||
| 231 | SessionMode ShortcutTable | 231 | SessionMode ShortcutTable | ||
| 232 | // ResizeMode shortcuts are only active in resize mode. | 232 | // ResizeMode shortcuts are only active in resize mode. | ||
| 233 | ResizeMode ShortcutTable | 233 | ResizeMode ShortcutTable | ||
| 234 | - | // PageMode shortcuts are only active in page mode. | 234 | + | // VimMode shortcuts are only active in vim mode. |
| 235 | - | PageMode ShortcutTable | 235 | + | VimMode ShortcutTable |
| 236 | } | 236 | } | ||
| 237 | 237 | ||||
| 238 | // NewShortcutSet creates a ShortcutSet from workspace and session configuration. | 238 | // NewShortcutSet creates a ShortcutSet from workspace and session configuration. | ||
| @@ -246,7 +246,7 @@ func NewShortcutSet(ctx context.Context, workspace *entity.WorkspaceConfig, sess | |||||
| 246 | PaneMode: make(ShortcutTable), | 246 | PaneMode: make(ShortcutTable), | ||
| 247 | SessionMode: make(ShortcutTable), | 247 | SessionMode: make(ShortcutTable), | ||
| 248 | ResizeMode: make(ShortcutTable), | 248 | ResizeMode: make(ShortcutTable), | ||
| 249 | - | PageMode: make(ShortcutTable), | 249 | + | VimMode: make(ShortcutTable), |
| 250 | } | 250 | } | ||
| 251 | 251 | ||||
| 252 | set.buildGlobalShortcutsFromParts(ctx, workspace, session) | 252 | set.buildGlobalShortcutsFromParts(ctx, workspace, session) | ||
| @@ -254,7 +254,7 @@ func NewShortcutSet(ctx context.Context, workspace *entity.WorkspaceConfig, sess | |||||
| 254 | set.buildTabModeShortcuts(ctx, workspace) | 254 | set.buildTabModeShortcuts(ctx, workspace) | ||
| 255 | set.buildPaneModeShortcuts(ctx, workspace) | 255 | set.buildPaneModeShortcuts(ctx, workspace) | ||
| 256 | set.buildResizeModeShortcuts(ctx, workspace) | 256 | set.buildResizeModeShortcuts(ctx, workspace) | ||
| 257 | - | set.buildPageModeShortcuts(ctx, workspace) | 257 | + | set.buildVimModeShortcuts(ctx, workspace) |
| 258 | } | 258 | } | ||
| 259 | if session != nil { | 259 | if session != nil { | ||
| 260 | set.buildSessionModeShortcuts(ctx, session) | 260 | set.buildSessionModeShortcuts(ctx, session) | ||
| @@ -266,7 +266,7 @@ func NewShortcutSet(ctx context.Context, workspace *entity.WorkspaceConfig, sess | |||||
| 266 | Int("pane", len(set.PaneMode)). | 266 | Int("pane", len(set.PaneMode)). | ||
| 267 | Int("resize", len(set.ResizeMode)). | 267 | Int("resize", len(set.ResizeMode)). | ||
| 268 | Int("session", len(set.SessionMode)). | 268 | Int("session", len(set.SessionMode)). | ||
| 269 | - | Int("page", len(set.PageMode)). | 269 | + | Int("vim", len(set.VimMode)). |
| 270 | Msg("shortcuts registered") | 270 | Msg("shortcuts registered") | ||
| 271 | 271 | ||||
| 272 | return set | 272 | return set | ||
| @@ -302,19 +302,19 @@ func (s *ShortcutSet) buildResizeModeShortcuts(ctx context.Context, cfg *entity. | |||||
| 302 | s.buildModeShortcuts(ctx, cfg.ResizeMode.GetKeyBindings(), s.ResizeMode, "resize") | 302 | s.buildModeShortcuts(ctx, cfg.ResizeMode.GetKeyBindings(), s.ResizeMode, "resize") | ||
| 303 | } | 303 | } | ||
| 304 | 304 | ||||
| 305 | - | // buildPageModeShortcuts populates page mode shortcuts from config. | 305 | + | // buildVimModeShortcuts populates vim mode shortcuts from config. |
| 306 | - | func (s *ShortcutSet) buildPageModeShortcuts(ctx context.Context, cfg *entity.WorkspaceConfig) { | 306 | + | func (s *ShortcutSet) buildVimModeShortcuts(ctx context.Context, cfg *entity.WorkspaceConfig) { |
| 307 | log := logging.FromContext(ctx) | 307 | log := logging.FromContext(ctx) | ||
| 308 | - | s.buildModeShortcuts(ctx, cfg.PageMode.GetKeyBindings(), s.PageMode, "page") | 308 | + | s.buildModeShortcuts(ctx, cfg.VimMode.GetKeyBindings(), s.VimMode, "vim") |
| 309 | - | if binding, ok := ParseKeyString(cfg.PageMode.ActivationShortcut); ok { | 309 | + | if binding, ok := ParseKeyString(cfg.VimMode.ActivationShortcut); ok { |
| 310 | - | s.PageMode[binding] = ActionEnterPageMode | 310 | + | s.VimMode[binding] = ActionEnterVimMode |
| 311 | log.Trace(). | 311 | log.Trace(). | ||
| 312 | - | Str("shortcut", cfg.PageMode.ActivationShortcut). | 312 | + | Str("shortcut", cfg.VimMode.ActivationShortcut). |
| 313 | Uint("keyval", binding.Keyval). | 313 | Uint("keyval", binding.Keyval). | ||
| 314 | Uint("mod", uint(binding.Modifiers)). | 314 | Uint("mod", uint(binding.Modifiers)). | ||
| 315 | - | Msg("page mode toggle registered in page mode table") | 315 | + | Msg("vim mode toggle registered in vim mode table") |
| 316 | } else { | 316 | } else { | ||
| 317 | - | log.Warn().Str("shortcut", cfg.PageMode.ActivationShortcut).Msg("failed to parse page mode activation shortcut for page mode table") | 317 | + | log.Warn().Str("shortcut", cfg.VimMode.ActivationShortcut).Msg("failed to parse vim mode activation shortcut for vim mode table") |
| 318 | } | 318 | } | ||
| 319 | } | 319 | } | ||
| 320 | 320 | ||||
| @@ -338,15 +338,15 @@ func (s *ShortcutSet) registerActivationShortcutsFromParts( | |||||
| 338 | } | 338 | } | ||
| 339 | return | 339 | return | ||
| 340 | } | 340 | } | ||
| 341 | - | if binding, ok := ParseKeyString(workspace.PageMode.ActivationShortcut); ok { | 341 | + | if binding, ok := ParseKeyString(workspace.VimMode.ActivationShortcut); ok { |
| 342 | - | s.Global[binding] = ActionEnterPageMode | 342 | + | s.Global[binding] = ActionEnterVimMode |
| 343 | log.Trace(). | 343 | log.Trace(). | ||
| 344 | - | Str("shortcut", workspace.PageMode.ActivationShortcut). | 344 | + | Str("shortcut", workspace.VimMode.ActivationShortcut). |
| 345 | Uint("keyval", binding.Keyval). | 345 | Uint("keyval", binding.Keyval). | ||
| 346 | Uint("mod", uint(binding.Modifiers)). | 346 | Uint("mod", uint(binding.Modifiers)). | ||
| 347 | - | Msg("page mode activation registered") | 347 | + | Msg("vim mode activation registered") |
| 348 | } else { | 348 | } else { | ||
| 349 | - | log.Warn().Str("shortcut", workspace.PageMode.ActivationShortcut).Msg("failed to parse page mode activation shortcut") | 349 | + | log.Warn().Str("shortcut", workspace.VimMode.ActivationShortcut).Msg("failed to parse vim mode activation shortcut") |
| 350 | } | 350 | } | ||
| 351 | if binding, ok := ParseKeyString(workspace.TabMode.ActivationShortcut); ok { | 351 | if binding, ok := ParseKeyString(workspace.TabMode.ActivationShortcut); ok { | ||
| 352 | s.Global[binding] = ActionEnterTabMode | 352 | s.Global[binding] = ActionEnterTabMode | ||
| @@ -683,19 +683,19 @@ var configActionToAction = map[string]Action{ | |||||
| 683 | // Session actions | 683 | // Session actions | ||
| 684 | "session-manager": ActionOpenSessionManager, | 684 | "session-manager": ActionOpenSessionManager, | ||
| 685 | 685 | ||||
| 686 | - | // Page mode scroll actions | 686 | + | // Vim mode scroll actions |
| 687 | - | "page_scroll_left": ActionPageScrollLeft, | 687 | + | "vim_scroll_left": ActionVimScrollLeft, |
| 688 | - | "page-scroll-left": ActionPageScrollLeft, | 688 | + | "vim-scroll-left": ActionVimScrollLeft, |
| 689 | - | "page_scroll_down": ActionPageScrollDown, | 689 | + | "vim_scroll_down": ActionVimScrollDown, |
| 690 | - | "page-scroll-down": ActionPageScrollDown, | 690 | + | "vim-scroll-down": ActionVimScrollDown, |
| 691 | - | "page_scroll_up": ActionPageScrollUp, | 691 | + | "vim_scroll_up": ActionVimScrollUp, |
| 692 | - | "page-scroll-up": ActionPageScrollUp, | 692 | + | "vim-scroll-up": ActionVimScrollUp, |
| 693 | - | "page_scroll_right": ActionPageScrollRight, | 693 | + | "vim_scroll_right": ActionVimScrollRight, |
| 694 | - | "page-scroll-right": ActionPageScrollRight, | 694 | + | "vim-scroll-right": ActionVimScrollRight, |
| 695 | - | "page_scroll_down_fast": ActionPageScrollDownFast, | 695 | + | "vim_scroll_down_fast": ActionVimScrollDownFast, |
| 696 | - | "page-scroll-down-fast": ActionPageScrollDownFast, | 696 | + | "vim-scroll-down-fast": ActionVimScrollDownFast, |
| 697 | - | "page_scroll_up_fast": ActionPageScrollUpFast, | 697 | + | "vim_scroll_up_fast": ActionVimScrollUpFast, |
| 698 | - | "page-scroll-up-fast": ActionPageScrollUpFast, | 698 | + | "vim-scroll-up-fast": ActionVimScrollUpFast, |
| 699 | } | 699 | } | ||
| 700 | 700 | ||||
| 701 | // FloatingProfileTarget carries the session identity and URL for a floating profile action. | 701 | // FloatingProfileTarget carries the session identity and URL for a floating profile action. | ||
| @@ -846,8 +846,8 @@ func stringToKeyval(s string) (uint, bool) { | |||||
| 846 | 846 | ||||
| 847 | // Lookup finds an action for the given key binding in the appropriate table. | 847 | // Lookup finds an action for the given key binding in the appropriate table. | ||
| 848 | // It first checks the mode-specific table, then falls back to global unless | 848 | // It first checks the mode-specific table, then falls back to global unless | ||
| 849 | - | // the current mode is Page Mode. Page Mode intentionally stays self-contained | 849 | + | // the current mode is Vim Mode. Vim Mode intentionally stays self-contained |
| 850 | - | // so app-global shortcuts do not interrupt held page scrolling. | 850 | + | // so app-global shortcuts do not interrupt held Vim-style scrolling. |
| 851 | func (s *ShortcutSet) Lookup(binding KeyBinding, mode Mode) (Action, bool) { | 851 | func (s *ShortcutSet) Lookup(binding KeyBinding, mode Mode) (Action, bool) { | ||
| 852 | // Normalize the binding modifiers | 852 | // Normalize the binding modifiers | ||
| 853 | binding.Modifiers &= modifierMask | 853 | binding.Modifiers &= modifierMask | ||
| @@ -863,15 +863,15 @@ func (s *ShortcutSet) Lookup(binding KeyBinding, mode Mode) (Action, bool) { | |||||
| 863 | modeTable = s.ResizeMode | 863 | modeTable = s.ResizeMode | ||
| 864 | case ModeSession: | 864 | case ModeSession: | ||
| 865 | modeTable = s.SessionMode | 865 | modeTable = s.SessionMode | ||
| 866 | - | case ModePage: | 866 | + | case ModeVim: |
| 867 | - | modeTable = s.PageMode | 867 | + | modeTable = s.VimMode |
| 868 | } | 868 | } | ||
| 869 | 869 | ||||
| 870 | if modeTable != nil { | 870 | if modeTable != nil { | ||
| 871 | if action, ok := modeTable[binding]; ok { | 871 | if action, ok := modeTable[binding]; ok { | ||
| 872 | return action, true | 872 | return action, true | ||
| 873 | } | 873 | } | ||
| 874 | - | if mode == ModePage { | 874 | + | if mode == ModeVim { |
| 875 | return "", false | 875 | return "", false | ||
| 876 | } | 876 | } | ||
| 877 | } | 877 | } | ||
M internal/ui/input/shortcuts_test.go +35 -35
| @@ -465,11 +465,11 @@ func TestGlobalShortcutActionMap_ConsumeOrExpelHyphenAliases(t *testing.T) { | |||||
| 465 | } | 465 | } | ||
| 466 | } | 466 | } | ||
| 467 | 467 | ||||
| 468 | - | func TestNewShortcutSet_PageModeActivationShortcut(t *testing.T) { | 468 | + | func TestNewShortcutSet_VimModeActivationShortcut(t *testing.T) { |
| 469 | workspace := &entity.WorkspaceConfig{ | 469 | workspace := &entity.WorkspaceConfig{ | ||
| 470 | TabMode: entity.TabModeConfig{ActivationShortcut: "ctrl+t"}, | 470 | TabMode: entity.TabModeConfig{ActivationShortcut: "ctrl+t"}, | ||
| 471 | PaneMode: entity.PaneModeConfig{ActivationShortcut: "ctrl+p"}, | 471 | PaneMode: entity.PaneModeConfig{ActivationShortcut: "ctrl+p"}, | ||
| 472 | - | PageMode: entity.PageModeConfig{ActivationShortcut: "ctrl+y"}, | 472 | + | VimMode: entity.VimModeConfig{ActivationShortcut: "ctrl+y"}, |
| 473 | } | 473 | } | ||
| 474 | 474 | ||||
| 475 | set := NewShortcutSet(context.Background(), workspace, nil) | 475 | set := NewShortcutSet(context.Background(), workspace, nil) | ||
| @@ -481,26 +481,26 @@ func TestNewShortcutSet_PageModeActivationShortcut(t *testing.T) { | |||||
| 481 | 481 | ||||
| 482 | action, found := set.Global[binding] | 482 | action, found := set.Global[binding] | ||
| 483 | if !found { | 483 | if !found { | ||
| 484 | - | t.Fatal("page mode activation shortcut not found in Global") | 484 | + | t.Fatal("vim mode activation shortcut not found in Global") |
| 485 | } | 485 | } | ||
| 486 | - | if action != ActionEnterPageMode { | 486 | + | if action != ActionEnterVimMode { |
| 487 | - | t.Fatalf("Global shortcut action = %s, want %s", action, ActionEnterPageMode) | 487 | + | t.Fatalf("Global shortcut action = %s, want %s", action, ActionEnterVimMode) |
| 488 | } | 488 | } | ||
| 489 | } | 489 | } | ||
| 490 | 490 | ||||
| 491 | - | func TestShortcutSet_Lookup_PageMode(t *testing.T) { | 491 | + | func TestShortcutSet_Lookup_VimMode(t *testing.T) { |
| 492 | set := &ShortcutSet{ | 492 | set := &ShortcutSet{ | ||
| 493 | Global: ShortcutTable{ | 493 | Global: ShortcutTable{ | ||
| 494 | {uint(gdk.KEY_q), ModCtrl}: ActionQuit, | 494 | {uint(gdk.KEY_q), ModCtrl}: ActionQuit, | ||
| 495 | }, | 495 | }, | ||
| 496 | - | PageMode: ShortcutTable{ | 496 | + | VimMode: ShortcutTable{ |
| 497 | - | {uint('h'), ModNone}: ActionPageScrollLeft, | 497 | + | {uint('h'), ModNone}: ActionVimScrollLeft, |
| 498 | - | {uint('j'), ModNone}: ActionPageScrollDown, | 498 | + | {uint('j'), ModNone}: ActionVimScrollDown, |
| 499 | - | {uint('k'), ModNone}: ActionPageScrollUp, | 499 | + | {uint('k'), ModNone}: ActionVimScrollUp, |
| 500 | - | {uint('l'), ModNone}: ActionPageScrollRight, | 500 | + | {uint('l'), ModNone}: ActionVimScrollRight, |
| 501 | {uint(gdk.KEY_Escape), ModNone}: ActionExitMode, | 501 | {uint(gdk.KEY_Escape), ModNone}: ActionExitMode, | ||
| 502 | {uint(gdk.KEY_Return), ModNone}: ActionExitMode, | 502 | {uint(gdk.KEY_Return), ModNone}: ActionExitMode, | ||
| 503 | - | {uint('y'), ModCtrl}: ActionEnterPageMode, | 503 | + | {uint('y'), ModCtrl}: ActionEnterVimMode, |
| 504 | }, | 504 | }, | ||
| 505 | } | 505 | } | ||
| 506 | 506 | ||||
| @@ -513,47 +513,47 @@ func TestShortcutSet_Lookup_PageMode(t *testing.T) { | |||||
| 513 | { | 513 | { | ||
| 514 | name: "scroll left", | 514 | name: "scroll left", | ||
| 515 | binding: KeyBinding{uint('h'), ModNone}, | 515 | binding: KeyBinding{uint('h'), ModNone}, | ||
| 516 | - | want: ActionPageScrollLeft, | 516 | + | want: ActionVimScrollLeft, |
| 517 | wantOk: true, | 517 | wantOk: true, | ||
| 518 | }, | 518 | }, | ||
| 519 | { | 519 | { | ||
| 520 | name: "scroll down", | 520 | name: "scroll down", | ||
| 521 | binding: KeyBinding{uint('j'), ModNone}, | 521 | binding: KeyBinding{uint('j'), ModNone}, | ||
| 522 | - | want: ActionPageScrollDown, | 522 | + | want: ActionVimScrollDown, |
| 523 | wantOk: true, | 523 | wantOk: true, | ||
| 524 | }, | 524 | }, | ||
| 525 | { | 525 | { | ||
| 526 | name: "scroll up", | 526 | name: "scroll up", | ||
| 527 | binding: KeyBinding{uint('k'), ModNone}, | 527 | binding: KeyBinding{uint('k'), ModNone}, | ||
| 528 | - | want: ActionPageScrollUp, | 528 | + | want: ActionVimScrollUp, |
| 529 | wantOk: true, | 529 | wantOk: true, | ||
| 530 | }, | 530 | }, | ||
| 531 | { | 531 | { | ||
| 532 | name: "scroll right", | 532 | name: "scroll right", | ||
| 533 | binding: KeyBinding{uint('l'), ModNone}, | 533 | binding: KeyBinding{uint('l'), ModNone}, | ||
| 534 | - | want: ActionPageScrollRight, | 534 | + | want: ActionVimScrollRight, |
| 535 | wantOk: true, | 535 | wantOk: true, | ||
| 536 | }, | 536 | }, | ||
| 537 | { | 537 | { | ||
| 538 | - | name: "escape exits page mode", | 538 | + | name: "escape exits vim mode", |
| 539 | binding: KeyBinding{uint(gdk.KEY_Escape), ModNone}, | 539 | binding: KeyBinding{uint(gdk.KEY_Escape), ModNone}, | ||
| 540 | want: ActionExitMode, | 540 | want: ActionExitMode, | ||
| 541 | wantOk: true, | 541 | wantOk: true, | ||
| 542 | }, | 542 | }, | ||
| 543 | { | 543 | { | ||
| 544 | - | name: "enter exits page mode", | 544 | + | name: "enter exits vim mode", |
| 545 | binding: KeyBinding{uint(gdk.KEY_Return), ModNone}, | 545 | binding: KeyBinding{uint(gdk.KEY_Return), ModNone}, | ||
| 546 | want: ActionExitMode, | 546 | want: ActionExitMode, | ||
| 547 | wantOk: true, | 547 | wantOk: true, | ||
| 548 | }, | 548 | }, | ||
| 549 | { | 549 | { | ||
| 550 | - | name: "activation shortcut still toggles page mode", | 550 | + | name: "activation shortcut still toggles vim mode", |
| 551 | binding: KeyBinding{uint('y'), ModCtrl}, | 551 | binding: KeyBinding{uint('y'), ModCtrl}, | ||
| 552 | - | want: ActionEnterPageMode, | 552 | + | want: ActionEnterVimMode, |
| 553 | wantOk: true, | 553 | wantOk: true, | ||
| 554 | }, | 554 | }, | ||
| 555 | { | 555 | { | ||
| 556 | - | name: "global shortcut does not fire inside page mode", | 556 | + | name: "global shortcut does not fire inside vim mode", |
| 557 | binding: KeyBinding{uint(gdk.KEY_q), ModCtrl}, | 557 | binding: KeyBinding{uint(gdk.KEY_q), ModCtrl}, | ||
| 558 | want: "", | 558 | want: "", | ||
| 559 | wantOk: false, | 559 | wantOk: false, | ||
| @@ -562,7 +562,7 @@ func TestShortcutSet_Lookup_PageMode(t *testing.T) { | |||||
| 562 | 562 | ||||
| 563 | for _, tt := range tests { | 563 | for _, tt := range tests { | ||
| 564 | t.Run(tt.name, func(t *testing.T) { | 564 | t.Run(tt.name, func(t *testing.T) { | ||
| 565 | - | got, ok := set.Lookup(tt.binding, ModePage) | 565 | + | got, ok := set.Lookup(tt.binding, ModeVim) |
| 566 | if ok != tt.wantOk { | 566 | if ok != tt.wantOk { | ||
| 567 | t.Errorf("Lookup() ok = %v, want %v", ok, tt.wantOk) | 567 | t.Errorf("Lookup() ok = %v, want %v", ok, tt.wantOk) | ||
| 568 | return | 568 | return | ||
| @@ -574,24 +574,24 @@ func TestShortcutSet_Lookup_PageMode(t *testing.T) { | |||||
| 574 | } | 574 | } | ||
| 575 | } | 575 | } | ||
| 576 | 576 | ||||
| 577 | - | func TestMapConfigAction_PageScrollActions(t *testing.T) { | 577 | + | func TestMapConfigAction_VimScrollActions(t *testing.T) { |
| 578 | tests := []struct { | 578 | tests := []struct { | ||
| 579 | name string | 579 | name string | ||
| 580 | config string | 580 | config string | ||
| 581 | expected Action | 581 | expected Action | ||
| 582 | }{ | 582 | }{ | ||
| 583 | - | {name: "page_scroll_left", config: "page_scroll_left", expected: ActionPageScrollLeft}, | 583 | + | {name: "vim_scroll_left", config: "vim_scroll_left", expected: ActionVimScrollLeft}, |
| 584 | - | {name: "page-scroll-left", config: "page-scroll-left", expected: ActionPageScrollLeft}, | 584 | + | {name: "vim-scroll-left", config: "vim-scroll-left", expected: ActionVimScrollLeft}, |
| 585 | - | {name: "page_scroll_down", config: "page_scroll_down", expected: ActionPageScrollDown}, | 585 | + | {name: "vim_scroll_down", config: "vim_scroll_down", expected: ActionVimScrollDown}, |
| 586 | - | {name: "page-scroll-down", config: "page-scroll-down", expected: ActionPageScrollDown}, | 586 | + | {name: "vim-scroll-down", config: "vim-scroll-down", expected: ActionVimScrollDown}, |
| 587 | - | {name: "page_scroll_up", config: "page_scroll_up", expected: ActionPageScrollUp}, | 587 | + | {name: "vim_scroll_up", config: "vim_scroll_up", expected: ActionVimScrollUp}, |
| 588 | - | {name: "page-scroll-up", config: "page-scroll-up", expected: ActionPageScrollUp}, | 588 | + | {name: "vim-scroll-up", config: "vim-scroll-up", expected: ActionVimScrollUp}, |
| 589 | - | {name: "page_scroll_right", config: "page_scroll_right", expected: ActionPageScrollRight}, | 589 | + | {name: "vim_scroll_right", config: "vim_scroll_right", expected: ActionVimScrollRight}, |
| 590 | - | {name: "page-scroll-right", config: "page-scroll-right", expected: ActionPageScrollRight}, | 590 | + | {name: "vim-scroll-right", config: "vim-scroll-right", expected: ActionVimScrollRight}, |
| 591 | - | {name: "page_scroll_down_fast", config: "page_scroll_down_fast", expected: ActionPageScrollDownFast}, | 591 | + | {name: "vim_scroll_down_fast", config: "vim_scroll_down_fast", expected: ActionVimScrollDownFast}, |
| 592 | - | {name: "page-scroll-down-fast", config: "page-scroll-down-fast", expected: ActionPageScrollDownFast}, | 592 | + | {name: "vim-scroll-down-fast", config: "vim-scroll-down-fast", expected: ActionVimScrollDownFast}, |
| 593 | - | {name: "page_scroll_up_fast", config: "page_scroll_up_fast", expected: ActionPageScrollUpFast}, | 593 | + | {name: "vim_scroll_up_fast", config: "vim_scroll_up_fast", expected: ActionVimScrollUpFast}, |
| 594 | - | {name: "page-scroll-up-fast", config: "page-scroll-up-fast", expected: ActionPageScrollUpFast}, | 594 | + | {name: "vim-scroll-up-fast", config: "vim-scroll-up-fast", expected: ActionVimScrollUpFast}, |
| 595 | } | 595 | } | ||
| 596 | 596 | ||||
| 597 | for _, tt := range tests { | 597 | for _, tt := range tests { | ||
M internal/ui/theme/css.go +25 -20
| @@ -111,8 +111,8 @@ func GenerateCSSFullWithTiming(p Palette, _ float64, fonts FontConfig, modeColor | |||||
| 111 | sb.WriteString(generateStackedPaneCSS(p)) | 111 | sb.WriteString(generateStackedPaneCSS(p)) | ||
| 112 | sb.WriteString("\n") | 112 | sb.WriteString("\n") | ||
| 113 | 113 | ||||
| 114 | - | // Page mode styling | 114 | + | // Vim mode styling |
| 115 | - | sb.WriteString(generatePageModeCSS(transitionDurationMs)) | 115 | + | sb.WriteString(generateVimModeCSS(transitionDurationMs)) |
| 116 | sb.WriteString("\n") | 116 | sb.WriteString("\n") | ||
| 117 | 117 | ||||
| 118 | // Progress bar styling | 118 | // Progress bar styling | ||
| @@ -593,65 +593,65 @@ entry.find-bar-entry:focus-visible { | |||||
| 593 | 593 | ||||
| 594 | // generatePaneCSS creates pane border styles. | 594 | // generatePaneCSS creates pane border styles. | ||
| 595 | // Uses em units for scalable UI. | 595 | // Uses em units for scalable UI. | ||
| 596 | - | // generatePageModeCSS creates the pane-local Page Mode accent and pulse styling. | 596 | + | // generateVimModeCSS creates the pane-local Vim Mode accent and pulse styling. |
| 597 | // It reuses --pane-mode-color (from workspace.styling.pane_mode_color). | 597 | // It reuses --pane-mode-color (from workspace.styling.pane_mode_color). | ||
| 598 | - | func generatePageModeCSS(transitionDurationMs int) string { | 598 | + | func generateVimModeCSS(transitionDurationMs int) string { |
| 599 | if transitionDurationMs < 0 { | 599 | if transitionDurationMs < 0 { | ||
| 600 | transitionDurationMs = defaultTransitionDurationMs | 600 | transitionDurationMs = defaultTransitionDurationMs | ||
| 601 | } | 601 | } | ||
| 602 | normalPulseMs := transitionDurationMs * 3 | 602 | normalPulseMs := transitionDurationMs * 3 | ||
| 603 | fastPulseMs := transitionDurationMs * 6 | 603 | fastPulseMs := transitionDurationMs * 6 | ||
| 604 | - | return fmt.Sprintf(`/* ===== Page Mode Styling ===== */ | 604 | + | return fmt.Sprintf(`/* ===== Vim Mode Styling ===== */ |
| 605 | 605 | ||||
| 606 | - | /* Page mode active — subtle local border accent on the pane overlay. | 606 | + | /* Vim mode active — subtle local border accent on the pane overlay. |
| 607 | Uses the existing pane mode color token via CSS variable. | 607 | Uses the existing pane mode color token via CSS variable. | ||
| 608 | This is independent of the workspace-level pane-mode border overlay. */ | 608 | This is independent of the workspace-level pane-mode border overlay. */ | ||
| 609 | - | .page-mode-active { | 609 | + | .vim-mode-active { |
| 610 | box-shadow: inset 0 0 0 0.125em alpha(var(--pane-mode-color), 0.35); | 610 | box-shadow: inset 0 0 0 0.125em alpha(var(--pane-mode-color), 0.35); | ||
| 611 | border-radius: 0; | 611 | border-radius: 0; | ||
| 612 | } | 612 | } | ||
| 613 | 613 | ||||
| 614 | /* Pane overlay pulse keyframes — start/end at the active accent state so the | 614 | /* Pane overlay pulse keyframes — start/end at the active accent state so the | ||
| 615 | - | pane keeps its Page mode border while briefly flaring brighter. */ | 615 | + | pane keeps its Vim mode border while briefly flaring brighter. */ |
| 616 | - | @keyframes page-mode-overlay-pulse-anim-a { | 616 | + | @keyframes vim-mode-overlay-pulse-anim-a { |
| 617 | 0%% { box-shadow: inset 0 0 0 0.125em alpha(var(--pane-mode-color), 0.35); } | 617 | 0%% { box-shadow: inset 0 0 0 0.125em alpha(var(--pane-mode-color), 0.35); } | ||
| 618 | 35%% { box-shadow: inset 0 0 0 0.2em alpha(var(--pane-mode-color), 0.78); } | 618 | 35%% { box-shadow: inset 0 0 0 0.2em alpha(var(--pane-mode-color), 0.78); } | ||
| 619 | 100%% { box-shadow: inset 0 0 0 0.125em alpha(var(--pane-mode-color), 0.35); } | 619 | 100%% { box-shadow: inset 0 0 0 0.125em alpha(var(--pane-mode-color), 0.35); } | ||
| 620 | } | 620 | } | ||
| 621 | 621 | ||||
| 622 | - | @keyframes page-mode-overlay-pulse-anim-b { | 622 | + | @keyframes vim-mode-overlay-pulse-anim-b { |
| 623 | 0%% { box-shadow: inset 0 0 0 0.125em alpha(var(--pane-mode-color), 0.35); } | 623 | 0%% { box-shadow: inset 0 0 0 0.125em alpha(var(--pane-mode-color), 0.35); } | ||
| 624 | 35%% { box-shadow: inset 0 0 0 0.2em alpha(var(--pane-mode-color), 0.78); } | 624 | 35%% { box-shadow: inset 0 0 0 0.2em alpha(var(--pane-mode-color), 0.78); } | ||
| 625 | 100%% { box-shadow: inset 0 0 0 0.125em alpha(var(--pane-mode-color), 0.35); } | 625 | 100%% { box-shadow: inset 0 0 0 0.125em alpha(var(--pane-mode-color), 0.35); } | ||
| 626 | } | 626 | } | ||
| 627 | 627 | ||||
| 628 | - | @keyframes page-mode-overlay-pulse-fast-anim-a { | 628 | + | @keyframes vim-mode-overlay-pulse-fast-anim-a { |
| 629 | 0%% { box-shadow: inset 0 0 0 0.125em alpha(var(--pane-mode-color), 0.35); } | 629 | 0%% { box-shadow: inset 0 0 0 0.125em alpha(var(--pane-mode-color), 0.35); } | ||
| 630 | 25%% { box-shadow: inset 0 0 0 0.26em alpha(var(--pane-mode-color), 0.95); } | 630 | 25%% { box-shadow: inset 0 0 0 0.26em alpha(var(--pane-mode-color), 0.95); } | ||
| 631 | 100%% { box-shadow: inset 0 0 0 0.125em alpha(var(--pane-mode-color), 0.35); } | 631 | 100%% { box-shadow: inset 0 0 0 0.125em alpha(var(--pane-mode-color), 0.35); } | ||
| 632 | } | 632 | } | ||
| 633 | 633 | ||||
| 634 | - | @keyframes page-mode-overlay-pulse-fast-anim-b { | 634 | + | @keyframes vim-mode-overlay-pulse-fast-anim-b { |
| 635 | 0%% { box-shadow: inset 0 0 0 0.125em alpha(var(--pane-mode-color), 0.35); } | 635 | 0%% { box-shadow: inset 0 0 0 0.125em alpha(var(--pane-mode-color), 0.35); } | ||
| 636 | 25%% { box-shadow: inset 0 0 0 0.26em alpha(var(--pane-mode-color), 0.95); } | 636 | 25%% { box-shadow: inset 0 0 0 0.26em alpha(var(--pane-mode-color), 0.95); } | ||
| 637 | 100%% { box-shadow: inset 0 0 0 0.125em alpha(var(--pane-mode-color), 0.35); } | 637 | 100%% { box-shadow: inset 0 0 0 0.125em alpha(var(--pane-mode-color), 0.35); } | ||
| 638 | } | 638 | } | ||
| 639 | 639 | ||||
| 640 | /* Pane overlay scroll pulse — derived from transition_duration. */ | 640 | /* Pane overlay scroll pulse — derived from transition_duration. */ | ||
| 641 | - | .page-mode-pulse.page-mode-pulse-cycle-a { | 641 | + | .vim-mode-pulse.vim-mode-pulse-cycle-a { |
| 642 | - | animation: page-mode-overlay-pulse-anim-a %dms ease-in-out; | 642 | + | animation: vim-mode-overlay-pulse-anim-a %dms ease-in-out; |
| 643 | } | 643 | } | ||
| 644 | 644 | ||||
| 645 | - | .page-mode-pulse.page-mode-pulse-cycle-b { | 645 | + | .vim-mode-pulse.vim-mode-pulse-cycle-b { |
| 646 | - | animation: page-mode-overlay-pulse-anim-b %dms ease-in-out; | 646 | + | animation: vim-mode-overlay-pulse-anim-b %dms ease-in-out; |
| 647 | } | 647 | } | ||
| 648 | 648 | ||||
| 649 | - | .page-mode-pulse-fast.page-mode-pulse-cycle-a { | 649 | + | .vim-mode-pulse-fast.vim-mode-pulse-cycle-a { |
| 650 | - | animation: page-mode-overlay-pulse-fast-anim-a %dms ease-in-out; | 650 | + | animation: vim-mode-overlay-pulse-fast-anim-a %dms ease-in-out; |
| 651 | } | 651 | } | ||
| 652 | 652 | ||||
| 653 | - | .page-mode-pulse-fast.page-mode-pulse-cycle-b { | 653 | + | .vim-mode-pulse-fast.vim-mode-pulse-cycle-b { |
| 654 | - | animation: page-mode-overlay-pulse-fast-anim-b %dms ease-in-out; | 654 | + | animation: vim-mode-overlay-pulse-fast-anim-b %dms ease-in-out; |
| 655 | } | 655 | } | ||
| 656 | `, normalPulseMs, normalPulseMs, fastPulseMs, fastPulseMs) | 656 | `, normalPulseMs, normalPulseMs, fastPulseMs, fastPulseMs) | ||
| 657 | } | 657 | } | ||
| @@ -1214,6 +1214,11 @@ func generateToasterCSS(p Palette) string { | |||||
| 1214 | color: #ffffff; | 1214 | color: #ffffff; | ||
| 1215 | } | 1215 | } | ||
| 1216 | 1216 | ||||
| 1217 | + | .toast-vim-mode { | |||
| 1218 | + | background-color: var(--pane-mode-color); | |||
| 1219 | + | color: #ffffff; | |||
| 1220 | + | } | |||
| 1221 | + | ||||
| 1217 | .toast-tab-mode { | 1222 | .toast-tab-mode { | ||
| 1218 | background-color: var(--tab-mode-color); | 1223 | background-color: var(--tab-mode-color); | ||
| 1219 | color: #ffffff; | 1224 | color: #ffffff; | ||
M internal/ui/theme/css_test.go +5 -5
| @@ -34,14 +34,14 @@ func TestGenerateCSS_StandaloneOmniboxWindowUsesTransparentBackground(t *testing | |||||
| 34 | } | 34 | } | ||
| 35 | } | 35 | } | ||
| 36 | 36 | ||||
| 37 | - | func TestGenerateCSSFullWithTiming_PageModePulseUsesConfiguredTransitionDuration(t *testing.T) { | 37 | + | func TestGenerateCSSFullWithTiming_VimModePulseUsesConfiguredTransitionDuration(t *testing.T) { |
| 38 | - | // Page Mode overlay pulse timings scale from the base transition duration: | 38 | + | // Vim Mode overlay pulse timings scale from the base transition duration: |
| 39 | // normal pulse = 3× (150ms -> 450ms), fast pulse = 6× (150ms -> 900ms). | 39 | // normal pulse = 3× (150ms -> 450ms), fast pulse = 6× (150ms -> 900ms). | ||
| 40 | css := GenerateCSSFullWithTiming(DefaultDarkPalette(), 1.0, DefaultFontConfig(), DefaultModeColors(), 150) | 40 | css := GenerateCSSFullWithTiming(DefaultDarkPalette(), 1.0, DefaultFontConfig(), DefaultModeColors(), 150) | ||
| 41 | 41 | ||||
| 42 | - | assert.Contains(t, css, "animation: page-mode-overlay-pulse-anim-a 450ms ease-in-out;") | 42 | + | assert.Contains(t, css, "animation: vim-mode-overlay-pulse-anim-a 450ms ease-in-out;") |
| 43 | - | assert.Contains(t, css, "animation: page-mode-overlay-pulse-fast-anim-a 900ms ease-in-out;") | 43 | + | assert.Contains(t, css, "animation: vim-mode-overlay-pulse-fast-anim-a 900ms ease-in-out;") |
| 44 | - | assert.NotContains(t, css, "page-mode-indicator") | 44 | + | assert.NotContains(t, css, "vim-mode-indicator") |
| 45 | } | 45 | } | ||
| 46 | 46 | ||||
| 47 | func TestGenerateCSS_OmniboxHeaderIsOpaque(t *testing.T) { | 47 | func TestGenerateCSS_OmniboxHeaderIsOpaque(t *testing.T) { | ||
M internal/ui/theme/manager.go +1 -1
| @@ -65,7 +65,7 @@ func (m *Manager) applyResolvedTheme(resolved entity.ResolvedTheme) { | |||||
| 65 | } | 65 | } | ||
| 66 | 66 | ||||
| 67 | // SetTransitionDuration sets the CSS transition duration used for theme-driven | 67 | // SetTransitionDuration sets the CSS transition duration used for theme-driven | ||
| 68 | - | // animations such as Page mode pulse timing. | 68 | + | // animations such as Vim mode pulse timing. |
| 69 | func (m *Manager) SetTransitionDuration(ms int) { | 69 | func (m *Manager) SetTransitionDuration(ms int) { | ||
| 70 | if ms < 0 { | 70 | if ms < 0 { | ||
| 71 | ms = defaultTransitionDurationMs | 71 | ms = defaultTransitionDurationMs | ||