@linxiraos/pi-tui 1.1.26 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -491
- package/README.md +13 -7
- package/dist/types/active-terminal.d.ts +30 -0
- package/dist/types/app-keybindings.d.ts +7 -2
- package/dist/types/apps/cleanse-board.d.ts +1 -1
- package/dist/types/apps/debug/log-viewer.d.ts +4 -0
- package/dist/types/apps/debug/raw-sse-buffer.d.ts +2 -0
- package/dist/types/apps/git/git-tui.d.ts +5 -4
- package/dist/types/apps/git/help.d.ts +3 -1
- package/dist/types/apps/if-bench-board.d.ts +2 -2
- package/dist/types/apps/setup-model-picker.d.ts +1 -1
- package/dist/types/autocomplete.d.ts +5 -2
- package/dist/types/charts/chart-plan.d.ts +674 -0
- package/dist/types/charts/chart-svg.d.ts +59 -0
- package/dist/types/charts/table-data.d.ts +175 -0
- package/dist/types/chat/assistant-message.d.ts +11 -1
- package/dist/types/chat/chat-transcript-builder.d.ts +3 -0
- package/dist/types/chat/compaction-summary-message.d.ts +1 -0
- package/dist/types/chat/execution-shared.d.ts +6 -5
- package/dist/types/chat/fence-figure.d.ts +42 -0
- package/dist/types/chat/figure-markdown.d.ts +42 -0
- package/dist/types/chat/messages.d.ts +1 -1
- package/dist/types/chat/svg-figure.d.ts +33 -0
- package/dist/types/chat/svg-source.d.ts +49 -0
- package/dist/types/chat/table-chart.d.ts +89 -0
- package/dist/types/chat/tool-execution.d.ts +2 -0
- package/dist/types/chat/user-message.d.ts +17 -4
- package/dist/types/chrome/shared.d.ts +9 -0
- package/dist/types/chrome/status-notice.d.ts +15 -3
- package/dist/types/chrome/transcript-container.d.ts +6 -0
- package/dist/types/components/box.d.ts +1 -0
- package/dist/types/components/disclosure.d.ts +1 -0
- package/dist/types/components/editor.d.ts +23 -5
- package/dist/types/components/form.d.ts +1 -0
- package/dist/types/components/image.d.ts +26 -44
- package/dist/types/components/input.d.ts +2 -1
- package/dist/types/components/key-value-list.d.ts +1 -0
- package/dist/types/components/layout/row.d.ts +1 -0
- package/dist/types/components/layout/stack.d.ts +1 -0
- package/dist/types/components/loader.d.ts +5 -6
- package/dist/types/components/markdown.d.ts +47 -5
- package/dist/types/components/scroll-view.d.ts +1 -0
- package/dist/types/components/section.d.ts +1 -0
- package/dist/types/components/table.d.ts +1 -0
- package/dist/types/components/text.d.ts +1 -0
- package/dist/types/components/tree-view.d.ts +7 -0
- package/dist/types/components/truncated-text.d.ts +1 -0
- package/dist/types/fuzzy.d.ts +29 -1
- package/dist/types/key-hint-format.d.ts +9 -1
- package/dist/types/keybinding-matchers.d.ts +3 -0
- package/dist/types/keybindings.d.ts +1 -1
- package/dist/types/kitty-graphics.d.ts +2 -2
- package/dist/types/loop-watchdog.d.ts +8 -7
- package/dist/types/multiplexers/cmux.d.ts +16 -0
- package/dist/types/multiplexers/herdr.d.ts +26 -0
- package/dist/types/multiplexers/notify.d.ts +13 -0
- package/dist/types/multiplexers/orca.d.ts +12 -0
- package/dist/types/multiplexers/screen.d.ts +9 -0
- package/dist/types/multiplexers/tmux.d.ts +29 -0
- package/dist/types/multiplexers/types.d.ts +100 -0
- package/dist/types/multiplexers/wmux.d.ts +12 -0
- package/dist/types/multiplexers/zellij.d.ts +12 -0
- package/dist/types/native/apply.d.ts +4 -3
- package/dist/types/native/backend.d.ts +5 -4
- package/dist/types/native/blobs.d.ts +7 -3
- package/dist/types/native/encode.d.ts +8 -6
- package/dist/types/native/node.d.ts +36 -5
- package/dist/types/native/overlay.d.ts +8 -3
- package/dist/types/native/settle.d.ts +4 -3
- package/dist/types/overlays/agent-hub-projection.d.ts +6 -5
- package/dist/types/overlays/agent-hub-types.d.ts +9 -0
- package/dist/types/overlays/agent-hub.d.ts +3 -0
- package/dist/types/overlays/agent-transcript-viewer.d.ts +12 -2
- package/dist/types/overlays/agents-hub.d.ts +9 -0
- package/dist/types/overlays/annotation-overlay.d.ts +19 -2
- package/dist/types/overlays/annotation-types.d.ts +2 -0
- package/dist/types/overlays/ask-dialog.d.ts +5 -2
- package/dist/types/overlays/btw-history-panel.d.ts +13 -1
- package/dist/types/overlays/copy-selector.d.ts +1 -0
- package/dist/types/overlays/history-search.d.ts +10 -0
- package/dist/types/overlays/hook-selector.d.ts +9 -4
- package/dist/types/overlays/hub-frame.d.ts +4 -1
- package/dist/types/overlays/jobs-panel.d.ts +1 -7
- package/dist/types/overlays/model-browser.d.ts +54 -7
- package/dist/types/overlays/model-hub.d.ts +37 -0
- package/dist/types/overlays/oauth-selector.d.ts +0 -1
- package/dist/types/overlays/plan-review-overlay.d.ts +3 -2
- package/dist/types/overlays/report-panel.d.ts +68 -0
- package/dist/types/overlays/rewind-selector.d.ts +1 -0
- package/dist/types/overlays/session-observer-registry.d.ts +2 -0
- package/dist/types/overlays/session-selector.d.ts +6 -2
- package/dist/types/overlays/settings-defs.d.ts +1 -0
- package/dist/types/overlays/thinking-selector.d.ts +2 -2
- package/dist/types/overlays/usage-dashboard.d.ts +12 -8
- package/dist/types/overlays/usage-display.d.ts +3 -1
- package/dist/types/prompt/composer-cache.d.ts +0 -16
- package/dist/types/prompt/composer.d.ts +18 -3
- package/dist/types/prompt/custom-editor.d.ts +25 -13
- package/dist/types/prompt/image-source.d.ts +2 -1
- package/dist/types/prompt/interactive-context-helpers.d.ts +5 -1
- package/dist/types/prompt/model-mention-autocomplete.d.ts +1 -1
- package/dist/types/prompt/welcome.d.ts +7 -2
- package/dist/types/render/composite.d.ts +2 -0
- package/dist/types/render/output-pane.d.ts +1 -0
- package/dist/types/render/render-utils.d.ts +1 -0
- package/dist/types/render/tool-card.d.ts +6 -0
- package/dist/types/render/width-aware-text.d.ts +2 -0
- package/dist/types/setup/scenes/types.d.ts +1 -0
- package/dist/types/space-hold.d.ts +40 -25
- package/dist/types/status-line/component.d.ts +3 -0
- package/dist/types/status-line/context-usage.d.ts +7 -5
- package/dist/types/status-line/host.d.ts +4 -1
- package/dist/types/status-line/segments.d.ts +2 -0
- package/dist/types/status-line/types.d.ts +14 -3
- package/dist/types/terminal-capabilities.d.ts +41 -36
- package/dist/types/terminal-multiplexer.d.ts +127 -14
- package/dist/types/terminal.d.ts +23 -16
- package/dist/types/theme/active-symbols.d.ts +1 -1
- package/dist/types/theme/schema.d.ts +11 -2
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/theme/theme-class.d.ts +7 -0
- package/dist/types/theme/theme.d.ts +1 -1
- package/dist/types/theme/tui-adapters.d.ts +3 -0
- package/dist/types/thinking.d.ts +1 -1
- package/dist/types/tmux.d.ts +11 -4
- package/dist/types/tools/bash.d.ts +7 -2
- package/dist/types/tools/daemon.d.ts +1 -1
- package/dist/types/tools/eval.d.ts +22 -3
- package/dist/types/tools/glob.d.ts +4 -0
- package/dist/types/tools/native-view.d.ts +47 -0
- package/dist/types/tools/output-meta.d.ts +11 -1
- package/dist/types/tools/renderer.d.ts +37 -2
- package/dist/types/tools/streaming-output.d.ts +20 -1
- package/dist/types/tools/task.d.ts +2 -3
- package/dist/types/tools/write.d.ts +9 -2
- package/dist/types/tools/yield.d.ts +22 -0
- package/dist/types/tui.d.ts +36 -0
- package/dist/types/utils.d.ts +6 -0
- package/dist/types/vim.d.ts +19 -5
- package/dist/types/windows-input-mode.d.ts +33 -1
- package/package.json +13 -9
- package/src/active-terminal.d.ts +30 -0
- package/src/active-terminal.ts +80 -0
- package/src/app-keybindings.d.ts +7 -2
- package/src/app-keybindings.ts +7 -2
- package/src/apps/autoresearch-dashboard.ts +7 -3
- package/src/apps/cleanse-board.ts +2 -2
- package/src/apps/cleanse-picker.ts +1 -1
- package/src/apps/debug/log-viewer.ts +188 -72
- package/src/apps/debug/raw-sse-buffer.ts +8 -0
- package/src/apps/debug/raw-sse.ts +39 -5
- package/src/apps/git/diff-pane.ts +96 -23
- package/src/apps/git/git-tui.ts +23 -16
- package/src/apps/git/help.ts +29 -20
- package/src/apps/git/sidebar.ts +8 -3
- package/src/apps/if-bench-board.ts +2 -2
- package/src/apps/ps-top.ts +2 -2
- package/src/apps/setup-model-picker.ts +1 -1
- package/src/autocomplete.d.ts +5 -2
- package/src/autocomplete.ts +67 -43
- package/src/bracketed-paste.ts +10 -3
- package/src/charts/chart-plan.d.ts +674 -0
- package/src/charts/chart-plan.ts +5081 -0
- package/src/charts/chart-svg.d.ts +59 -0
- package/src/charts/chart-svg.ts +4657 -0
- package/src/charts/table-data.d.ts +175 -0
- package/src/charts/table-data.ts +1478 -0
- package/src/chat/advisor-message.ts +11 -0
- package/src/chat/assistant-message.d.ts +11 -1
- package/src/chat/assistant-message.ts +245 -54
- package/src/chat/chat-transcript-builder.d.ts +3 -0
- package/src/chat/chat-transcript-builder.ts +9 -3
- package/src/chat/compaction-summary-message.d.ts +1 -0
- package/src/chat/compaction-summary-message.ts +8 -0
- package/src/chat/execution-shared.d.ts +6 -5
- package/src/chat/execution-shared.ts +42 -25
- package/src/chat/fence-figure.d.ts +42 -0
- package/src/chat/fence-figure.ts +80 -0
- package/src/chat/figure-markdown.d.ts +42 -0
- package/src/chat/figure-markdown.ts +174 -0
- package/src/chat/messages.d.ts +1 -1
- package/src/chat/messages.ts +1 -1
- package/src/chat/read-tool-group.ts +6 -4
- package/src/chat/skill-message.ts +6 -0
- package/src/chat/svg-figure.d.ts +33 -0
- package/src/chat/svg-figure.ts +399 -0
- package/src/chat/svg-source.d.ts +49 -0
- package/src/chat/svg-source.ts +188 -0
- package/src/chat/table-chart.d.ts +89 -0
- package/src/chat/table-chart.ts +294 -0
- package/src/chat/tool-execution.d.ts +2 -0
- package/src/chat/tool-execution.ts +86 -7
- package/src/chat/user-message.d.ts +17 -4
- package/src/chat/user-message.ts +71 -8
- package/src/chrome/shared.ts +53 -0
- package/src/chrome/status-notice.ts +25 -7
- package/src/chrome/transcript-container.d.ts +6 -0
- package/src/chrome/transcript-container.ts +162 -19
- package/src/components/box.d.ts +1 -0
- package/src/components/box.ts +5 -0
- package/src/components/disclosure.d.ts +1 -0
- package/src/components/disclosure.ts +8 -0
- package/src/components/editor.d.ts +23 -5
- package/src/components/editor.ts +316 -47
- package/src/components/form.d.ts +1 -0
- package/src/components/form.ts +8 -0
- package/src/components/image.d.ts +26 -44
- package/src/components/image.ts +115 -152
- package/src/components/input.d.ts +2 -1
- package/src/components/input.ts +17 -7
- package/src/components/key-value-list.d.ts +1 -0
- package/src/components/key-value-list.ts +4 -0
- package/src/components/layout/row.d.ts +1 -0
- package/src/components/layout/row.ts +7 -0
- package/src/components/layout/stack.d.ts +1 -0
- package/src/components/layout/stack.ts +7 -0
- package/src/components/loader.d.ts +5 -6
- package/src/components/loader.ts +10 -17
- package/src/components/markdown.d.ts +47 -5
- package/src/components/markdown.ts +786 -303
- package/src/components/scroll-view.d.ts +1 -0
- package/src/components/scroll-view.ts +5 -0
- package/src/components/section.d.ts +1 -0
- package/src/components/section.ts +5 -0
- package/src/components/settings-list.ts +2 -0
- package/src/components/table.d.ts +1 -0
- package/src/components/table.ts +4 -0
- package/src/components/text.d.ts +1 -0
- package/src/components/text.ts +7 -0
- package/src/components/tree-view.d.ts +7 -0
- package/src/components/tree-view.ts +38 -8
- package/src/components/truncated-text.d.ts +1 -0
- package/src/components/truncated-text.ts +5 -0
- package/src/desktop-notify.ts +3 -2
- package/src/fuzzy.d.ts +29 -1
- package/src/fuzzy.ts +115 -30
- package/src/hotkeys-markdown.ts +9 -2
- package/src/key-hint-format.d.ts +9 -1
- package/src/key-hint-format.ts +26 -11
- package/src/keybinding-matchers.d.ts +3 -0
- package/src/keybinding-matchers.ts +6 -2
- package/src/keybindings.d.ts +1 -1
- package/src/keybindings.ts +1 -1
- package/src/kitty-graphics.d.ts +2 -2
- package/src/kitty-graphics.ts +11 -5
- package/src/loop-watchdog.d.ts +8 -7
- package/src/loop-watchdog.ts +18 -13
- package/src/multiplexers/cmux.d.ts +16 -0
- package/src/multiplexers/cmux.ts +29 -0
- package/src/multiplexers/herdr.d.ts +26 -0
- package/src/multiplexers/herdr.ts +64 -0
- package/src/multiplexers/notify.d.ts +13 -0
- package/src/multiplexers/notify.ts +24 -0
- package/src/multiplexers/orca.d.ts +12 -0
- package/src/multiplexers/orca.ts +16 -0
- package/src/multiplexers/screen.d.ts +9 -0
- package/src/multiplexers/screen.ts +15 -0
- package/src/multiplexers/tmux.d.ts +29 -0
- package/src/multiplexers/tmux.ts +40 -0
- package/src/multiplexers/types.d.ts +100 -0
- package/src/multiplexers/types.ts +110 -0
- package/src/multiplexers/wmux.d.ts +12 -0
- package/src/multiplexers/wmux.ts +16 -0
- package/src/multiplexers/zellij.d.ts +12 -0
- package/src/multiplexers/zellij.ts +24 -0
- package/src/native/apply.d.ts +4 -3
- package/src/native/apply.ts +4 -3
- package/src/native/backend.d.ts +5 -4
- package/src/native/backend.ts +199 -22
- package/src/native/blobs.d.ts +7 -3
- package/src/native/blobs.ts +47 -12
- package/src/native/encode.d.ts +8 -6
- package/src/native/encode.ts +76 -34
- package/src/native/node.d.ts +36 -5
- package/src/native/node.ts +31 -5
- package/src/native/overlay.d.ts +8 -3
- package/src/native/overlay.ts +17 -4
- package/src/native/reconcile.ts +16 -4
- package/src/native/settle.d.ts +4 -3
- package/src/native/settle.ts +4 -3
- package/src/overlays/agent-hub-projection.d.ts +6 -5
- package/src/overlays/agent-hub-projection.ts +124 -13
- package/src/overlays/agent-hub-types.d.ts +9 -0
- package/src/overlays/agent-hub-types.ts +6 -0
- package/src/overlays/agent-hub.ts +48 -29
- package/src/overlays/agent-transcript-viewer.ts +93 -23
- package/src/overlays/agents-hub.ts +28 -8
- package/src/overlays/annotation-overlay.ts +293 -90
- package/src/overlays/annotation-types.ts +2 -0
- package/src/overlays/ask-dialog.ts +10 -17
- package/src/overlays/btw-history-panel.ts +136 -39
- package/src/overlays/copy-selector.d.ts +1 -0
- package/src/overlays/copy-selector.ts +32 -8
- package/src/overlays/extensions/extension-dashboard.ts +4 -4
- package/src/overlays/extensions/state-manager.ts +52 -12
- package/src/overlays/history-search.ts +100 -7
- package/src/overlays/hook-selector.ts +12 -5
- package/src/overlays/hub-frame.ts +19 -1
- package/src/overlays/jobs-panel.ts +33 -52
- package/src/overlays/model-browser.ts +367 -122
- package/src/overlays/model-hub.ts +435 -59
- package/src/overlays/move-overlay.ts +28 -14
- package/src/overlays/oauth-selector.ts +54 -70
- package/src/overlays/pause-screen.ts +2 -2
- package/src/overlays/plan-review-overlay.ts +208 -70
- package/src/overlays/plugin-settings.ts +3 -3
- package/src/overlays/report-panel.ts +191 -0
- package/src/overlays/rewind-selector.ts +88 -17
- package/src/overlays/session-observer-registry.d.ts +2 -0
- package/src/overlays/session-observer-registry.ts +10 -2
- package/src/overlays/session-selector.ts +64 -26
- package/src/overlays/settings-defs.ts +5 -4
- package/src/overlays/settings-selector.ts +33 -8
- package/src/overlays/snapcompact-shape-preview.ts +2 -3
- package/src/overlays/stats-notice.ts +1 -1
- package/src/overlays/thinking-selector.ts +11 -10
- package/src/overlays/tree-selector.ts +41 -20
- package/src/overlays/usage-dashboard.ts +117 -58
- package/src/overlays/usage-display.ts +10 -4
- package/src/overlays/usage-row.ts +22 -8
- package/src/prompt/attachment-chips.ts +15 -11
- package/src/prompt/composer-attachments.ts +6 -4
- package/src/prompt/composer-cache.ts +7 -86
- package/src/prompt/composer.ts +37 -26
- package/src/prompt/custom-editor.ts +282 -105
- package/src/prompt/image-source.ts +2 -1
- package/src/prompt/interactive-context-helpers.ts +32 -18
- package/src/prompt/macos-spelling.ts +7 -0
- package/src/prompt/magic-keywords.ts +11 -2
- package/src/prompt/model-mention-autocomplete.ts +14 -14
- package/src/prompt/queued-messages.ts +6 -4
- package/src/prompt/tips.txt +13 -9
- package/src/prompt/welcome.ts +64 -46
- package/src/prompt/word-completion.ts +38 -5
- package/src/render/composite.d.ts +2 -0
- package/src/render/composite.ts +64 -0
- package/src/render/output-pane.d.ts +1 -0
- package/src/render/output-pane.ts +6 -0
- package/src/render/render-utils.d.ts +1 -0
- package/src/render/render-utils.ts +16 -1
- package/src/render/tool-card.d.ts +6 -0
- package/src/render/tool-card.ts +15 -0
- package/src/render/width-aware-text.d.ts +2 -0
- package/src/render/width-aware-text.ts +5 -0
- package/src/setup/scenes/model.ts +2 -1
- package/src/setup/scenes/types.ts +1 -0
- package/src/space-hold.d.ts +40 -25
- package/src/space-hold.ts +156 -52
- package/src/status-line/component.ts +292 -78
- package/src/status-line/context-usage.ts +21 -46
- package/src/status-line/host.ts +4 -1
- package/src/status-line/segments.ts +40 -22
- package/src/status-line/types.ts +15 -3
- package/src/terminal-capabilities.d.ts +41 -36
- package/src/terminal-capabilities.ts +137 -213
- package/src/terminal-multiplexer.d.ts +127 -14
- package/src/terminal-multiplexer.ts +112 -33
- package/src/terminal.d.ts +23 -16
- package/src/terminal.ts +203 -99
- package/src/theme/active-symbols.d.ts +1 -1
- package/src/theme/active-symbols.ts +1 -1
- package/src/theme/schema-validation.ts +1 -0
- package/src/theme/schema.d.ts +11 -2
- package/src/theme/schema.ts +14 -1
- package/src/theme/session-color.ts +1 -7
- package/src/theme/symbols.d.ts +1 -1
- package/src/theme/symbols.ts +11 -4
- package/src/theme/theme-class.d.ts +7 -0
- package/src/theme/theme-class.ts +26 -1
- package/src/theme/theme-schema.json +5 -1
- package/src/theme/theme.d.ts +1 -1
- package/src/theme/theme.ts +8 -4
- package/src/theme/tui-adapters.d.ts +3 -0
- package/src/theme/tui-adapters.ts +68 -0
- package/src/thinking.d.ts +1 -1
- package/src/thinking.ts +1 -1
- package/src/tmux.d.ts +11 -4
- package/src/tmux.ts +47 -11
- package/src/tools/ast-edit.ts +59 -35
- package/src/tools/ast-grep.ts +35 -16
- package/src/tools/bash.d.ts +7 -2
- package/src/tools/bash.ts +116 -47
- package/src/tools/daemon.d.ts +1 -1
- package/src/tools/daemon.ts +1 -1
- package/src/tools/default-renderer.ts +35 -19
- package/src/tools/edit.ts +142 -120
- package/src/tools/eval-format/index.ts +28 -2
- package/src/tools/eval-format/javascript.ts +7 -5
- package/src/tools/eval.d.ts +22 -3
- package/src/tools/eval.ts +364 -208
- package/src/tools/fetch.ts +5 -6
- package/src/tools/glob.d.ts +4 -0
- package/src/tools/glob.ts +33 -65
- package/src/tools/grep.ts +81 -48
- package/src/tools/index.ts +2 -0
- package/src/tools/lsp.ts +81 -59
- package/src/tools/mcp.ts +111 -68
- package/src/tools/native-view.d.ts +47 -0
- package/src/tools/native-view.ts +102 -1
- package/src/tools/output-meta.d.ts +11 -1
- package/src/tools/output-meta.ts +23 -3
- package/src/tools/read.ts +7 -5
- package/src/tools/renderer.d.ts +37 -2
- package/src/tools/renderer.ts +42 -2
- package/src/tools/streaming-output.d.ts +20 -1
- package/src/tools/streaming-output.ts +154 -50
- package/src/tools/task.d.ts +2 -3
- package/src/tools/task.ts +115 -62
- package/src/tools/todo.ts +119 -30
- package/src/tools/wait.ts +34 -24
- package/src/tools/web-search.ts +1 -1
- package/src/tools/write.d.ts +9 -2
- package/src/tools/write.ts +147 -95
- package/src/tools/yield.d.ts +22 -0
- package/src/tools/yield.ts +225 -0
- package/src/tui.d.ts +36 -0
- package/src/tui.ts +666 -217
- package/src/utils.d.ts +6 -0
- package/src/utils.ts +19 -0
- package/src/vim.d.ts +19 -5
- package/src/vim.ts +333 -18
- package/src/windows-input-mode.d.ts +33 -1
- package/src/windows-input-mode.ts +212 -6
package/src/utils.d.ts
CHANGED
|
@@ -26,6 +26,12 @@ export interface TextSizingOptions {
|
|
|
26
26
|
*/
|
|
27
27
|
export declare function encodeTextSized(text: string, options?: TextSizingOptions): string;
|
|
28
28
|
export declare function sliceWithWidth(line: string, startCol: number, length: number, strict?: boolean | null): SliceResult;
|
|
29
|
+
/**
|
|
30
|
+
* Hard-wrap one literal terminal row without trimming spaces or splitting graphemes.
|
|
31
|
+
* An oversized grapheme is emitted intact even when it exceeds the requested width.
|
|
32
|
+
* Zero-cell rows remain one empty row, matching restored-terminal reflow.
|
|
33
|
+
*/
|
|
34
|
+
export declare function wrapLiteralLine(line: string, width: number): string[];
|
|
29
35
|
export declare function truncateToWidth(text: string, maxWidth: number, ellipsisKind?: Ellipsis | null | "", pad?: boolean | null): string;
|
|
30
36
|
export declare function wrapTextWithAnsi(text: string, width: number): string[];
|
|
31
37
|
export declare function extractSegments(line: string, beforeEnd: number, afterStart: number, afterLen: number, strictAfter: boolean): ExtractSegmentsResult;
|
package/src/utils.ts
CHANGED
|
@@ -154,6 +154,25 @@ export function sliceWithWidth(line: string, startCol: number, length: number, s
|
|
|
154
154
|
return nativeSliceWithWidth(line, startCol, length, strict ?? null, DEFAULT_TAB_WIDTH);
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Hard-wrap one literal terminal row without trimming spaces or splitting graphemes.
|
|
159
|
+
* An oversized grapheme is emitted intact even when it exceeds the requested width.
|
|
160
|
+
* Zero-cell rows remain one empty row, matching restored-terminal reflow.
|
|
161
|
+
*/
|
|
162
|
+
export function wrapLiteralLine(line: string, width: number): string[] {
|
|
163
|
+
const columns = Math.max(1, width);
|
|
164
|
+
const lineWidth = visibleWidth(line);
|
|
165
|
+
if (lineWidth === 0) return [""];
|
|
166
|
+
const rows: string[] = [];
|
|
167
|
+
for (let column = 0; column < lineWidth;) {
|
|
168
|
+
let slice = sliceWithWidth(line, column, columns, true);
|
|
169
|
+
if (slice.width === 0) slice = sliceWithWidth(line, column, columns);
|
|
170
|
+
rows.push(slice.text);
|
|
171
|
+
column += Math.max(1, slice.width);
|
|
172
|
+
}
|
|
173
|
+
return rows;
|
|
174
|
+
}
|
|
175
|
+
|
|
157
176
|
export function truncateToWidth(
|
|
158
177
|
text: string,
|
|
159
178
|
maxWidth: number,
|
package/src/vim.d.ts
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* This is a usable subset of Vim, not a reimplementation of it (see issue #3299): Normal and Visual
|
|
9
9
|
* modes, the common motions, and operators built from those motions.
|
|
10
10
|
*/
|
|
11
|
-
export type VimMode = "insert" | "normal" | "visual" | "visual-line";
|
|
12
|
-
export type VimOperator = "d" | "y" | "c";
|
|
11
|
+
export type VimMode = "insert" | "normal" | "visual" | "visual-line" | "replace";
|
|
12
|
+
export type VimOperator = "d" | "y" | "c" | ">" | "<";
|
|
13
13
|
export interface VimPosition {
|
|
14
14
|
line: number;
|
|
15
15
|
col: number;
|
|
@@ -50,6 +50,20 @@ export type VimCommand = {
|
|
|
50
50
|
count: number;
|
|
51
51
|
} | {
|
|
52
52
|
kind: "undo";
|
|
53
|
+
} | {
|
|
54
|
+
kind: "replace";
|
|
55
|
+
from: VimPosition;
|
|
56
|
+
to: VimPosition;
|
|
57
|
+
text: string;
|
|
58
|
+
} | {
|
|
59
|
+
kind: "join";
|
|
60
|
+
fromLine: number;
|
|
61
|
+
lines: number;
|
|
62
|
+
} | {
|
|
63
|
+
kind: "indent";
|
|
64
|
+
fromLine: number;
|
|
65
|
+
toLine: number;
|
|
66
|
+
out: boolean;
|
|
53
67
|
};
|
|
54
68
|
/** Start offset of the grapheme after `col`, clamped to `text.length`. */
|
|
55
69
|
export declare function nextGraphemeStart(text: string, col: number): number;
|
|
@@ -62,11 +76,11 @@ export declare class VimState {
|
|
|
62
76
|
mode: VimMode;
|
|
63
77
|
/** Fixed end of a Visual selection; the cursor is the moving end. */
|
|
64
78
|
anchor: VimPosition | null;
|
|
65
|
-
/** True while a count, operator, `g`,
|
|
79
|
+
/** True while a count, operator, `g`, text-object, find, or `r` prefix is half-typed — Escape cancels it. */
|
|
66
80
|
get pending(): boolean;
|
|
67
81
|
/**
|
|
68
|
-
* The half-typed command as Vim would echo it (`"2"`, `"d"`, `"2d"`, `"di"`) — empty
|
|
69
|
-
* nothing is pending. Hosts render this next to the mode so a partially entered operator is
|
|
82
|
+
* The half-typed command as Vim would echo it (`"2"`, `"d"`, `"2d"`, `"di"`, `"df"`, `"r"`) — empty
|
|
83
|
+
* when nothing is pending. Hosts render this next to the mode so a partially entered operator is
|
|
70
84
|
* visible instead of silently swallowing the next keystroke.
|
|
71
85
|
*/
|
|
72
86
|
get pendingText(): string;
|
package/src/vim.ts
CHANGED
|
@@ -11,9 +11,9 @@ import { getSegmenter, moveWordLeft, moveWordRight } from "./utils";
|
|
|
11
11
|
* modes, the common motions, and operators built from those motions.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
export type VimMode = "insert" | "normal" | "visual" | "visual-line";
|
|
14
|
+
export type VimMode = "insert" | "normal" | "visual" | "visual-line" | "replace";
|
|
15
15
|
|
|
16
|
-
export type VimOperator = "d" | "y" | "c";
|
|
16
|
+
export type VimOperator = "d" | "y" | "c" | ">" | "<";
|
|
17
17
|
|
|
18
18
|
export interface VimPosition {
|
|
19
19
|
line: number;
|
|
@@ -38,7 +38,10 @@ export type VimCommand =
|
|
|
38
38
|
| { kind: "delete"; from: VimPosition; to: VimPosition; linewise: boolean; insert: boolean }
|
|
39
39
|
| { kind: "openLine"; below: boolean }
|
|
40
40
|
| { kind: "paste"; after: boolean; count: number }
|
|
41
|
-
| { kind: "undo" }
|
|
41
|
+
| { kind: "undo" }
|
|
42
|
+
| { kind: "replace"; from: VimPosition; to: VimPosition; text: string }
|
|
43
|
+
| { kind: "join"; fromLine: number; lines: number }
|
|
44
|
+
| { kind: "indent"; fromLine: number; toLine: number; out: boolean };
|
|
42
45
|
|
|
43
46
|
const segmenter = getSegmenter();
|
|
44
47
|
|
|
@@ -75,6 +78,35 @@ function firstNonBlank(text: string): number {
|
|
|
75
78
|
}
|
|
76
79
|
return 0;
|
|
77
80
|
}
|
|
81
|
+
function findPendingKey(pending: { forward: boolean; till: boolean } | null): string {
|
|
82
|
+
if (!pending) return "";
|
|
83
|
+
return pending.till ? (pending.forward ? "t" : "T") : pending.forward ? "f" : "F";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Column of the `count`th grapheme equal to `target`, searching strictly after `fromCol` when
|
|
88
|
+
* `forward` and strictly before it otherwise. Line-local, like Vim's `f`/`F`.
|
|
89
|
+
*/
|
|
90
|
+
function findGrapheme(text: string, fromCol: number, target: string, forward: boolean, count: number): number | null {
|
|
91
|
+
if (count < 1 || target.length === 0) return null;
|
|
92
|
+
const segs = [...segmenter.segment(text)];
|
|
93
|
+
let here = -1;
|
|
94
|
+
for (let i = 0; i < segs.length; i++) {
|
|
95
|
+
if (segs[i]!.index <= fromCol) here = i;
|
|
96
|
+
else break;
|
|
97
|
+
}
|
|
98
|
+
let seen = 0;
|
|
99
|
+
if (forward) {
|
|
100
|
+
for (let i = here + 1; i < segs.length; i++) {
|
|
101
|
+
if (segs[i]!.segment === target && ++seen === count) return segs[i]!.index;
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
for (let i = here - 1; i >= 0; i--) {
|
|
105
|
+
if (segs[i]!.segment === target && ++seen === count) return segs[i]!.index;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
78
110
|
|
|
79
111
|
/** Vim's `w`: `moveWordRight` stops at the end of the current word, so skip the gap that follows. */
|
|
80
112
|
function wordForward(text: string, col: number): number {
|
|
@@ -157,11 +189,7 @@ function wordObject(text: string, col: number, around: boolean, big: boolean, co
|
|
|
157
189
|
* leading run when there is none.
|
|
158
190
|
*/
|
|
159
191
|
function quoteObject(text: string, col: number, quote: string, around: boolean): [number, number] | null {
|
|
160
|
-
const marks
|
|
161
|
-
for (let i = 0; i < text.length; i++) {
|
|
162
|
-
if (text.charAt(i) === "\\") i++;
|
|
163
|
-
else if (text.charAt(i) === quote) marks.push(i);
|
|
164
|
-
}
|
|
192
|
+
const marks = quoteMarks(text, quote);
|
|
165
193
|
for (let p = 0; p + 1 < marks.length; p += 2) {
|
|
166
194
|
const open = marks[p]!;
|
|
167
195
|
const close = marks[p + 1]!;
|
|
@@ -254,6 +282,113 @@ function paragraphObject(buf: VimBuffer, around: boolean): TextObjectRange {
|
|
|
254
282
|
return { from: { line: first, col: 0 }, to: { line: end, col: (buf.lines[end] ?? "").length }, linewise: true };
|
|
255
283
|
}
|
|
256
284
|
|
|
285
|
+
const BRACKET_MATCH: Record<string, { mate: string; forward: boolean }> = {
|
|
286
|
+
"(": { mate: ")", forward: true },
|
|
287
|
+
")": { mate: "(", forward: false },
|
|
288
|
+
"[": { mate: "]", forward: true },
|
|
289
|
+
"]": { mate: "[", forward: false },
|
|
290
|
+
"{": { mate: "}", forward: true },
|
|
291
|
+
"}": { mate: "{", forward: false },
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
/** Quote columns, skipping a backslash and the character after it, same as `i"`/`a"`. */
|
|
295
|
+
function quoteMarks(text: string, quote: string): number[] {
|
|
296
|
+
const marks: number[] = [];
|
|
297
|
+
for (let i = 0; i < text.length; i++) {
|
|
298
|
+
if (text.charAt(i) === "\\") i++;
|
|
299
|
+
else if (text.charAt(i) === quote) marks.push(i);
|
|
300
|
+
}
|
|
301
|
+
return marks;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function quoteMate(marks: readonly number[], col: number): number | null {
|
|
305
|
+
for (let p = 0; p + 1 < marks.length; p += 2) {
|
|
306
|
+
const open = marks[p]!;
|
|
307
|
+
const close = marks[p + 1]!;
|
|
308
|
+
if (col === open) return close;
|
|
309
|
+
if (col === close) return open;
|
|
310
|
+
}
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** Column of the match for `%`: the next bracket or quote on this line, then its pair. */
|
|
315
|
+
function matchDelimiter(buf: VimBuffer): VimPosition | null {
|
|
316
|
+
const line = buf.lines[buf.cursorLine] ?? "";
|
|
317
|
+
const quotes = {
|
|
318
|
+
'"': quoteMarks(line, '"'),
|
|
319
|
+
"'": quoteMarks(line, "'"),
|
|
320
|
+
};
|
|
321
|
+
const quoteAt = (col: number): '"' | "'" | null =>
|
|
322
|
+
quotes['"'].includes(col) ? '"' : quotes["'"].includes(col) ? "'" : null;
|
|
323
|
+
let col = buf.cursorCol;
|
|
324
|
+
while (col < line.length && BRACKET_MATCH[line.charAt(col)] === undefined && quoteAt(col) === null) col++;
|
|
325
|
+
const quote = quoteAt(col);
|
|
326
|
+
if (quote) {
|
|
327
|
+
const mate = quoteMate(quotes[quote], col);
|
|
328
|
+
return mate === null ? null : { line: buf.cursorLine, col: mate };
|
|
329
|
+
}
|
|
330
|
+
const spec = BRACKET_MATCH[line.charAt(col)];
|
|
331
|
+
if (!spec) return null;
|
|
332
|
+
const { text, cursor } = flatten({ lines: buf.lines, cursorLine: buf.cursorLine, cursorCol: col });
|
|
333
|
+
const open = spec.forward ? line.charAt(col) : spec.mate;
|
|
334
|
+
const close = spec.forward ? spec.mate : line.charAt(col);
|
|
335
|
+
let depth = 0;
|
|
336
|
+
let match = -1;
|
|
337
|
+
if (spec.forward) {
|
|
338
|
+
for (let i = cursor + 1; i < text.length; i++) {
|
|
339
|
+
const ch = text.charAt(i);
|
|
340
|
+
if (ch === open) depth++;
|
|
341
|
+
else if (ch === close) {
|
|
342
|
+
if (depth === 0) {
|
|
343
|
+
match = i;
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
depth--;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
for (let i = cursor - 1; i >= 0; i--) {
|
|
351
|
+
const ch = text.charAt(i);
|
|
352
|
+
if (ch === close) depth++;
|
|
353
|
+
else if (ch === open) {
|
|
354
|
+
if (depth === 0) {
|
|
355
|
+
match = i;
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
depth--;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return match < 0 ? null : offsetToPos(buf.lines, match);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function isParagraphBlank(lines: readonly string[], line: number): boolean {
|
|
366
|
+
return (lines[line] ?? "").trim() === "";
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** Line `{`/`}` land on. A missing boundary stays on the first or last line. */
|
|
370
|
+
function paragraphBoundary(lines: readonly string[], line: number, forward: boolean): number {
|
|
371
|
+
const last = lines.length - 1;
|
|
372
|
+
if (last < 0) return 0;
|
|
373
|
+
let i = Math.max(0, Math.min(line, last));
|
|
374
|
+
if (forward) {
|
|
375
|
+
if (i < last && !isParagraphBlank(lines, i)) {
|
|
376
|
+
while (i < last && !isParagraphBlank(lines, i + 1)) i++;
|
|
377
|
+
return i < last ? i + 1 : last;
|
|
378
|
+
}
|
|
379
|
+
while (i < last && isParagraphBlank(lines, i)) i++;
|
|
380
|
+
while (i < last && !isParagraphBlank(lines, i)) i++;
|
|
381
|
+
return i;
|
|
382
|
+
}
|
|
383
|
+
if (i > 0 && !isParagraphBlank(lines, i)) {
|
|
384
|
+
while (i > 0 && !isParagraphBlank(lines, i - 1)) i--;
|
|
385
|
+
return i > 0 ? i - 1 : 0;
|
|
386
|
+
}
|
|
387
|
+
while (i > 0 && isParagraphBlank(lines, i)) i--;
|
|
388
|
+
while (i > 0 && !isParagraphBlank(lines, i - 1)) i--;
|
|
389
|
+
return i > 0 ? i - 1 : 0;
|
|
390
|
+
}
|
|
391
|
+
|
|
257
392
|
interface Motion {
|
|
258
393
|
to: VimPosition;
|
|
259
394
|
/** Inclusive motions cover the grapheme under `to` when used with an operator. */
|
|
@@ -271,6 +406,13 @@ export class VimState {
|
|
|
271
406
|
#pendingG = false;
|
|
272
407
|
/** `i` or `a` typed after an operator or in Visual mode — waiting for the object key. */
|
|
273
408
|
#textObject: "i" | "a" | null = null;
|
|
409
|
+
/** `f`/`F`/`t`/`T` waiting for the target grapheme. */
|
|
410
|
+
#findPending: { forward: boolean; till: boolean } | null = null;
|
|
411
|
+
/** Last find attempt, so `;` repeats it and `,` reverses it. */
|
|
412
|
+
#lastFind: { forward: boolean; till: boolean; target: string } | null = null;
|
|
413
|
+
/** `r` waiting for the replacement grapheme. */
|
|
414
|
+
#replaceChar = false;
|
|
415
|
+
|
|
274
416
|
/**
|
|
275
417
|
* Vim's "desired column": `j`/`k` remember the column you started from, so descending through a
|
|
276
418
|
* short line and back out returns to it instead of collapsing permanently. `null` means the
|
|
@@ -279,18 +421,25 @@ export class VimState {
|
|
|
279
421
|
*/
|
|
280
422
|
#desiredCol: number | null = null;
|
|
281
423
|
|
|
282
|
-
/** True while a count, operator, `g`,
|
|
424
|
+
/** True while a count, operator, `g`, text-object, find, or `r` prefix is half-typed — Escape cancels it. */
|
|
283
425
|
get pending(): boolean {
|
|
284
|
-
return
|
|
426
|
+
return (
|
|
427
|
+
this.#count.length > 0 ||
|
|
428
|
+
this.#operator !== null ||
|
|
429
|
+
this.#pendingG ||
|
|
430
|
+
this.#textObject !== null ||
|
|
431
|
+
this.#findPending !== null ||
|
|
432
|
+
this.#replaceChar
|
|
433
|
+
);
|
|
285
434
|
}
|
|
286
435
|
|
|
287
436
|
/**
|
|
288
|
-
* The half-typed command as Vim would echo it (`"2"`, `"d"`, `"2d"`, `"di"`) — empty
|
|
289
|
-
* nothing is pending. Hosts render this next to the mode so a partially entered operator is
|
|
437
|
+
* The half-typed command as Vim would echo it (`"2"`, `"d"`, `"2d"`, `"di"`, `"df"`, `"r"`) — empty
|
|
438
|
+
* when nothing is pending. Hosts render this next to the mode so a partially entered operator is
|
|
290
439
|
* visible instead of silently swallowing the next keystroke.
|
|
291
440
|
*/
|
|
292
441
|
get pendingText(): string {
|
|
293
|
-
return `${this.#count}${this.#operator ?? ""}${this.#pendingG ? "g" : ""}${this.#textObject ?? ""}`;
|
|
442
|
+
return `${this.#count}${this.#operator ?? ""}${this.#pendingG ? "g" : ""}${this.#textObject ?? ""}${findPendingKey(this.#findPending)}${this.#replaceChar ? "r" : ""}`;
|
|
294
443
|
}
|
|
295
444
|
|
|
296
445
|
get visual(): boolean {
|
|
@@ -302,6 +451,7 @@ export class VimState {
|
|
|
302
451
|
this.anchor = null;
|
|
303
452
|
this.#desiredCol = null;
|
|
304
453
|
this.#clearPending();
|
|
454
|
+
this.#lastFind = null;
|
|
305
455
|
}
|
|
306
456
|
|
|
307
457
|
#clearPending(): void {
|
|
@@ -309,6 +459,8 @@ export class VimState {
|
|
|
309
459
|
this.#operator = null;
|
|
310
460
|
this.#pendingG = false;
|
|
311
461
|
this.#textObject = null;
|
|
462
|
+
this.#findPending = null;
|
|
463
|
+
this.#replaceChar = false;
|
|
312
464
|
}
|
|
313
465
|
|
|
314
466
|
#takeCount(): number {
|
|
@@ -334,6 +486,46 @@ export class VimState {
|
|
|
334
486
|
handleKey(key: string, buf: VimBuffer): VimCommand[] | null {
|
|
335
487
|
if (key === "escape") return this.#handleEscape(buf);
|
|
336
488
|
if (this.mode === "insert") return null;
|
|
489
|
+
// The character after `f`/`t` is the target, including digits. Resolve it before the count prefix.
|
|
490
|
+
if (this.#findPending) {
|
|
491
|
+
const spec = this.#findPending;
|
|
492
|
+
this.#findPending = null;
|
|
493
|
+
this.#desiredCol = null;
|
|
494
|
+
this.#lastFind = { ...spec, target: key };
|
|
495
|
+
const count = this.#count.length > 0 ? Math.max(1, Number.parseInt(this.#count, 10)) : 1;
|
|
496
|
+
const motion = this.#seek(key, buf, spec, count);
|
|
497
|
+
if (!motion) {
|
|
498
|
+
this.#operator = null;
|
|
499
|
+
this.#count = "";
|
|
500
|
+
return [];
|
|
501
|
+
}
|
|
502
|
+
return this.#applyMotion(buf, motion);
|
|
503
|
+
}
|
|
504
|
+
if (this.#replaceChar) {
|
|
505
|
+
this.#replaceChar = false;
|
|
506
|
+
const count = this.#count.length > 0 ? Math.max(1, Number.parseInt(this.#count, 10)) : 1;
|
|
507
|
+
const line = buf.lines[buf.cursorLine] ?? "";
|
|
508
|
+
// One segmenter pass: stepping with `nextGraphemeStart` would rescan the line per grapheme.
|
|
509
|
+
let end = -1;
|
|
510
|
+
let taken = 0;
|
|
511
|
+
for (const seg of segmenter.segment(line)) {
|
|
512
|
+
if (seg.index < buf.cursorCol) continue;
|
|
513
|
+
if (++taken === count) {
|
|
514
|
+
end = seg.index + seg.segment.length;
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
this.#count = "";
|
|
519
|
+
if (end < 0) return [];
|
|
520
|
+
return [
|
|
521
|
+
{
|
|
522
|
+
kind: "replace",
|
|
523
|
+
from: { line: buf.cursorLine, col: buf.cursorCol },
|
|
524
|
+
to: { line: buf.cursorLine, col: end },
|
|
525
|
+
text: key.repeat(count),
|
|
526
|
+
},
|
|
527
|
+
];
|
|
528
|
+
}
|
|
337
529
|
|
|
338
530
|
// Count prefix. `0` is the line-start motion unless it extends a count already being typed.
|
|
339
531
|
if ((key >= "1" && key <= "9") || (key === "0" && this.#count.length > 0)) {
|
|
@@ -364,6 +556,21 @@ export class VimState {
|
|
|
364
556
|
this.#textObject = key;
|
|
365
557
|
return [];
|
|
366
558
|
}
|
|
559
|
+
if (key === "f" || key === "F" || key === "t" || key === "T") {
|
|
560
|
+
this.#desiredCol = null;
|
|
561
|
+
this.#findPending = { forward: key === "f" || key === "t", till: key === "t" || key === "T" };
|
|
562
|
+
return [];
|
|
563
|
+
}
|
|
564
|
+
if ((key === "r" || key === "R") && this.#operator === null && !this.visual) {
|
|
565
|
+
this.#desiredCol = null;
|
|
566
|
+
if (key === "R") {
|
|
567
|
+
this.#takeCount();
|
|
568
|
+
this.mode = "replace";
|
|
569
|
+
return [{ kind: "mode", mode: "replace" }];
|
|
570
|
+
}
|
|
571
|
+
this.#replaceChar = true;
|
|
572
|
+
return [];
|
|
573
|
+
}
|
|
367
574
|
|
|
368
575
|
// Only consecutive `j`/`k` carry the desired column; anything else re-anchors it. Counts and
|
|
369
576
|
// the `g` prefix returned above, so `2j` still continues an established column.
|
|
@@ -388,7 +595,7 @@ export class VimState {
|
|
|
388
595
|
{ kind: "move", to: this.#clampNormal(buf, cursorOf(buf)) },
|
|
389
596
|
];
|
|
390
597
|
}
|
|
391
|
-
if (this.mode === "insert") {
|
|
598
|
+
if (this.mode === "insert" || this.mode === "replace") {
|
|
392
599
|
this.mode = "normal";
|
|
393
600
|
const text = buf.lines[buf.cursorLine] ?? "";
|
|
394
601
|
return [
|
|
@@ -454,6 +661,31 @@ export class VimState {
|
|
|
454
661
|
for (let i = 0; i < count; i++) col = wordEnd(line, col);
|
|
455
662
|
return { to: at(col), inclusive: true, linewise: false };
|
|
456
663
|
}
|
|
664
|
+
case "%": {
|
|
665
|
+
// `{count}%` jumps to that percentage of the buffer, linewise, like Vim.
|
|
666
|
+
if (this.#count.length > 0) {
|
|
667
|
+
if (count > 100) return null;
|
|
668
|
+
const target = Math.max(0, Math.floor((count * buf.lines.length + 99) / 100) - 1);
|
|
669
|
+
const text = buf.lines[target] ?? "";
|
|
670
|
+
return { to: { line: target, col: firstNonBlank(text) }, inclusive: false, linewise: true };
|
|
671
|
+
}
|
|
672
|
+
const to = matchDelimiter(buf);
|
|
673
|
+
return to === null ? null : { to, inclusive: true, linewise: false };
|
|
674
|
+
}
|
|
675
|
+
case "{":
|
|
676
|
+
case "}": {
|
|
677
|
+
let line = buf.cursorLine;
|
|
678
|
+
for (let n = 0; n < count; n++) {
|
|
679
|
+
const next = paragraphBoundary(buf.lines, line, key === "}");
|
|
680
|
+
if (next === line) break;
|
|
681
|
+
line = next;
|
|
682
|
+
}
|
|
683
|
+
const text = buf.lines[line] ?? "";
|
|
684
|
+
// No blank line ahead: Vim parks at the end of the buffer, not column 0. A bare move
|
|
685
|
+
// clamps back onto the last grapheme; an operator still covers the rest of the line.
|
|
686
|
+
const atBufferEnd = key === "}" && line === buf.lines.length - 1 && text.trim() !== "";
|
|
687
|
+
return { to: { line, col: atBufferEnd ? text.length : 0 }, inclusive: false, linewise: false };
|
|
688
|
+
}
|
|
457
689
|
case "G": {
|
|
458
690
|
const target = this.#count.length > 0 ? count - 1 : buf.lines.length - 1;
|
|
459
691
|
return {
|
|
@@ -462,23 +694,66 @@ export class VimState {
|
|
|
462
694
|
linewise: true,
|
|
463
695
|
};
|
|
464
696
|
}
|
|
697
|
+
case ";":
|
|
698
|
+
case ",":
|
|
699
|
+
return this.#repeatFind(key === ";", buf, count);
|
|
465
700
|
default:
|
|
466
701
|
return null;
|
|
467
702
|
}
|
|
468
703
|
}
|
|
469
704
|
|
|
705
|
+
#repeatFind(sameDirection: boolean, buf: VimBuffer, count: number): Motion | null {
|
|
706
|
+
const last = this.#lastFind;
|
|
707
|
+
if (!last) return null;
|
|
708
|
+
const forward = sameDirection ? last.forward : !last.forward;
|
|
709
|
+
const text = buf.lines[buf.cursorLine] ?? "";
|
|
710
|
+
// A till lands beside its target, so repeating from the cursor rediscovers that same target.
|
|
711
|
+
const fromCol = last.till
|
|
712
|
+
? forward
|
|
713
|
+
? nextGraphemeStart(text, buf.cursorCol)
|
|
714
|
+
: prevGraphemeStart(text, buf.cursorCol)
|
|
715
|
+
: buf.cursorCol;
|
|
716
|
+
return this.#seek(last.target, buf, { forward, till: last.till }, count, fromCol);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* `f`/`F` land on the match and include it. `t`/`T` land on the adjacent grapheme and include
|
|
721
|
+
* that grapheme, so the operator stops short of the target. A till that would not move fails.
|
|
722
|
+
*/
|
|
723
|
+
#seek(
|
|
724
|
+
target: string,
|
|
725
|
+
buf: VimBuffer,
|
|
726
|
+
spec: { forward: boolean; till: boolean },
|
|
727
|
+
count: number,
|
|
728
|
+
fromCol = buf.cursorCol,
|
|
729
|
+
): Motion | null {
|
|
730
|
+
const text = buf.lines[buf.cursorLine] ?? "";
|
|
731
|
+
const match = findGrapheme(text, fromCol, target, spec.forward, count);
|
|
732
|
+
if (match === null) return null;
|
|
733
|
+
const land = spec.till ? (spec.forward ? prevGraphemeStart(text, match) : nextGraphemeStart(text, match)) : match;
|
|
734
|
+
if (land === buf.cursorCol) return null;
|
|
735
|
+
return { to: { line: buf.cursorLine, col: land }, inclusive: true, linewise: false };
|
|
736
|
+
}
|
|
737
|
+
|
|
470
738
|
/** Turn a resolved motion into either a cursor move, a selection extension, or an operator range. */
|
|
471
739
|
#applyMotion(buf: VimBuffer, motion: Motion): VimCommand[] {
|
|
472
740
|
const operator = this.#operator;
|
|
473
741
|
this.#operator = null;
|
|
474
742
|
this.#takeCount();
|
|
743
|
+
if (operator === ">" || operator === "<") {
|
|
744
|
+
const fromLine = Math.min(buf.cursorLine, motion.to.line);
|
|
745
|
+
const toLine = Math.max(buf.cursorLine, motion.to.line);
|
|
746
|
+
return [{ kind: "indent", fromLine, toLine, out: operator === "<" }];
|
|
747
|
+
}
|
|
475
748
|
|
|
476
749
|
if (operator === null) {
|
|
477
750
|
// `$` parks on the last grapheme in Normal mode but must still be able to select the
|
|
478
|
-
// final character in Visual mode, where the cursor is allowed one past it.
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
751
|
+
// final character in Visual mode, where the cursor is allowed one past it. Replace mode
|
|
752
|
+
// may also sit past the end, so typing there appends instead of overwriting the last grapheme.
|
|
753
|
+
const to =
|
|
754
|
+
this.visual || this.mode === "replace"
|
|
755
|
+
? { line: motion.to.line, col: Math.min(motion.to.col, (buf.lines[motion.to.line] ?? "").length) }
|
|
756
|
+
: this.#clampNormal(buf, motion.to);
|
|
482
757
|
return [{ kind: "move", to }];
|
|
483
758
|
}
|
|
484
759
|
|
|
@@ -494,6 +769,11 @@ export class VimState {
|
|
|
494
769
|
}
|
|
495
770
|
|
|
496
771
|
#operate(operator: VimOperator, from: VimPosition, to: VimPosition, linewise: boolean): VimCommand[] {
|
|
772
|
+
if (operator === ">" || operator === "<") {
|
|
773
|
+
const fromLine = Math.min(from.line, to.line);
|
|
774
|
+
const toLine = Math.max(from.line, to.line);
|
|
775
|
+
return [{ kind: "indent", fromLine, toLine, out: operator === "<" }];
|
|
776
|
+
}
|
|
497
777
|
if (operator === "y") {
|
|
498
778
|
return [
|
|
499
779
|
{ kind: "yank", from, to, linewise },
|
|
@@ -683,6 +963,28 @@ export class VimState {
|
|
|
683
963
|
case "u":
|
|
684
964
|
this.#takeCount();
|
|
685
965
|
return [{ kind: "undo" }];
|
|
966
|
+
case "J": {
|
|
967
|
+
if (this.#operator !== null) {
|
|
968
|
+
this.#clearPending();
|
|
969
|
+
return [];
|
|
970
|
+
}
|
|
971
|
+
return [{ kind: "join", fromLine: buf.cursorLine, lines: Math.max(2, this.#takeCount()) }];
|
|
972
|
+
}
|
|
973
|
+
case ">":
|
|
974
|
+
case "<": {
|
|
975
|
+
if (this.#operator === key) {
|
|
976
|
+
const span = this.#takeCount();
|
|
977
|
+
const last = Math.min(buf.cursorLine + span - 1, buf.lines.length - 1);
|
|
978
|
+
this.#operator = null;
|
|
979
|
+
return [{ kind: "indent", fromLine: buf.cursorLine, toLine: last, out: key === "<" }];
|
|
980
|
+
}
|
|
981
|
+
if (this.#operator !== null) {
|
|
982
|
+
this.#clearPending();
|
|
983
|
+
return [];
|
|
984
|
+
}
|
|
985
|
+
this.#operator = key;
|
|
986
|
+
return [];
|
|
987
|
+
}
|
|
686
988
|
default:
|
|
687
989
|
// Normal mode swallows unknown printable keys rather than typing them into the buffer.
|
|
688
990
|
this.#clearPending();
|
|
@@ -727,6 +1029,19 @@ export class VimState {
|
|
|
727
1029
|
const commands = this.#operate(operator, from, to, linewise);
|
|
728
1030
|
return operator === "c" ? commands : [...commands, { kind: "mode", mode: "normal" }];
|
|
729
1031
|
}
|
|
1032
|
+
case ">":
|
|
1033
|
+
case "<":
|
|
1034
|
+
case "J": {
|
|
1035
|
+
const { from, to } = visualRange(buf, anchor, linewise);
|
|
1036
|
+
this.#clearPending();
|
|
1037
|
+
this.anchor = null;
|
|
1038
|
+
this.mode = "normal";
|
|
1039
|
+
const edit: VimCommand =
|
|
1040
|
+
key === "J"
|
|
1041
|
+
? { kind: "join", fromLine: from.line, lines: Math.max(2, to.line - from.line + 1) }
|
|
1042
|
+
: { kind: "indent", fromLine: from.line, toLine: to.line, out: key === "<" };
|
|
1043
|
+
return [edit, { kind: "mode", mode: "normal" }];
|
|
1044
|
+
}
|
|
730
1045
|
default:
|
|
731
1046
|
this.#clearPending();
|
|
732
1047
|
return [];
|
|
@@ -16,10 +16,26 @@
|
|
|
16
16
|
* terminal sends; chords legacy encoding loses (modified Enter/Tab/Escape,
|
|
17
17
|
* Ctrl+Shift+letter, …) translate to kitty CSI-u, which key matching already
|
|
18
18
|
* parses.
|
|
19
|
+
*
|
|
20
|
+
* A host terminal that writes VT instead of key records (any ConPTY host
|
|
21
|
+
* besides Windows Terminal's native path) reaches us as one `Vk = 0` text
|
|
22
|
+
* record per byte: Alt+Up arrives as `ESC`, `[`, `1`, `;`, `3`, `A`. Those
|
|
23
|
+
* bytes are reassembled into the escape sequence they spell; delivering them
|
|
24
|
+
* one at a time turns every such key into a bare Escape plus typed garbage.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Stateful decoder: joins UTF-16 surrogate halves that arrive as separate
|
|
28
|
+
* records, and reassembles VT escape sequences relayed as text records.
|
|
19
29
|
*/
|
|
20
|
-
/** Stateful decoder: joins UTF-16 surrogate halves that arrive as separate records. */
|
|
21
30
|
export declare class Win32InputModeDecoder {
|
|
22
31
|
#private;
|
|
32
|
+
/** Whether relayed VT text is held waiting for the rest of its sequence. */
|
|
33
|
+
get hasPendingSequence(): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Release held VT text as-is. The caller invokes this once no further bytes
|
|
36
|
+
* arrived in time, so a lone relayed ESC still acts as the Escape key.
|
|
37
|
+
*/
|
|
38
|
+
flushPendingSequence(): string[];
|
|
23
39
|
/**
|
|
24
40
|
* Translate one stdin sequence. Returns `undefined` when `data` is not a
|
|
25
41
|
* win32-input-mode record (pass it through unchanged), otherwise the key
|
|
@@ -35,3 +51,19 @@ export declare class Win32InputModeDecoder {
|
|
|
35
51
|
*/
|
|
36
52
|
decodePaste(content: string): string;
|
|
37
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Restores paste delimiters that a Windows console host emitted as individual
|
|
56
|
+
* win32-input-mode key records before StdinBuffer searches for the delimiters.
|
|
57
|
+
* Non-matching records remain untouched for normal key and paste decoding.
|
|
58
|
+
*/
|
|
59
|
+
export declare class Win32PasteMarkerNormalizer {
|
|
60
|
+
#private;
|
|
61
|
+
/**
|
|
62
|
+
* `onExpired` runs after held records are released because their wait timed
|
|
63
|
+
* out, so a downstream holder can treat the same wait as already elapsed.
|
|
64
|
+
*/
|
|
65
|
+
constructor(onInput: (data: string) => void, onExpired?: () => void);
|
|
66
|
+
process(data: string): void;
|
|
67
|
+
/** Release an incomplete marker as its original key records. */
|
|
68
|
+
flush(): void;
|
|
69
|
+
}
|