@fairyhunter13/opentui-core 0.1.112 → 0.1.114
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/dev/keypress-debug-renderer.ts +148 -0
- package/dev/keypress-debug.ts +43 -0
- package/dev/print-env-vars.ts +32 -0
- package/dev/test-tmux-graphics-334.sh +68 -0
- package/dev/thai-debug-test.ts +68 -0
- package/docs/development.md +144 -0
- package/package.json +63 -51
- package/scripts/build.ts +400 -0
- package/scripts/publish.ts +60 -0
- package/src/3d/SpriteResourceManager.ts +286 -0
- package/src/3d/SpriteUtils.ts +70 -0
- package/src/3d/TextureUtils.ts +196 -0
- package/src/3d/ThreeRenderable.ts +197 -0
- package/src/3d/WGPURenderer.ts +294 -0
- package/src/3d/animation/ExplodingSpriteEffect.ts +513 -0
- package/src/3d/animation/PhysicsExplodingSpriteEffect.ts +429 -0
- package/src/3d/animation/SpriteAnimator.ts +633 -0
- package/src/3d/animation/SpriteParticleGenerator.ts +435 -0
- package/src/3d/canvas.ts +464 -0
- package/src/3d/index.ts +12 -0
- package/src/3d/physics/PlanckPhysicsAdapter.ts +72 -0
- package/src/3d/physics/RapierPhysicsAdapter.ts +66 -0
- package/src/3d/physics/physics-interface.ts +31 -0
- package/src/3d/shaders/supersampling.wgsl +201 -0
- package/src/3d.ts +3 -0
- package/src/NativeSpanFeed.ts +300 -0
- package/src/Renderable.ts +1704 -0
- package/src/__snapshots__/buffer.test.ts.snap +28 -0
- package/src/animation/Timeline.test.ts +2709 -0
- package/src/animation/Timeline.ts +598 -0
- package/src/ansi.ts +18 -0
- package/src/benchmark/attenuation-benchmark.ts +81 -0
- package/src/benchmark/colormatrix-benchmark.ts +128 -0
- package/src/benchmark/gain-benchmark.ts +80 -0
- package/src/benchmark/latest-all-bench-run.json +707 -0
- package/src/benchmark/latest-async-bench-run.json +336 -0
- package/src/benchmark/latest-default-bench-run.json +657 -0
- package/src/benchmark/latest-large-bench-run.json +707 -0
- package/src/benchmark/latest-quick-bench-run.json +207 -0
- package/src/benchmark/markdown-benchmark.ts +1796 -0
- package/src/benchmark/native-span-feed-async-benchmark.ts +355 -0
- package/src/benchmark/native-span-feed-benchmark.md +56 -0
- package/src/benchmark/native-span-feed-benchmark.ts +596 -0
- package/src/benchmark/native-span-feed-compare.ts +280 -0
- package/src/benchmark/renderer-benchmark.ts +754 -0
- package/src/benchmark/text-table-benchmark.ts +948 -0
- package/src/buffer.test.ts +291 -0
- package/src/buffer.ts +554 -0
- package/src/console.test.ts +612 -0
- package/src/console.ts +1254 -0
- package/src/edit-buffer.test.ts +1769 -0
- package/src/edit-buffer.ts +411 -0
- package/src/editor-view.test.ts +1032 -0
- package/src/editor-view.ts +284 -0
- package/src/examples/ascii-font-selection-demo.ts +245 -0
- package/src/examples/assets/Water_2_M_Normal.jpg +0 -0
- package/src/examples/assets/concrete.png +0 -0
- package/src/examples/assets/crate.png +0 -0
- package/src/examples/assets/crate_emissive.png +0 -0
- package/src/examples/assets/forrest_background.png +0 -0
- package/src/examples/assets/hast-example.json +1018 -0
- package/src/examples/assets/heart.png +0 -0
- package/src/examples/assets/main_char_heavy_attack.png +0 -0
- package/src/examples/assets/main_char_idle.png +0 -0
- package/src/examples/assets/main_char_jump_end.png +0 -0
- package/src/examples/assets/main_char_jump_landing.png +0 -0
- package/src/examples/assets/main_char_jump_start.png +0 -0
- package/src/examples/assets/main_char_run_loop.png +0 -0
- package/src/examples/assets/roughness_map.jpg +0 -0
- package/src/examples/build.ts +115 -0
- package/src/examples/code-demo.ts +924 -0
- package/src/examples/console-demo.ts +358 -0
- package/src/examples/core-plugin-slots-demo.ts +759 -0
- package/src/examples/diff-demo.ts +701 -0
- package/src/examples/draggable-three-demo.ts +259 -0
- package/src/examples/editor-demo.ts +322 -0
- package/src/examples/extmarks-demo.ts +196 -0
- package/src/examples/focus-restore-demo.ts +310 -0
- package/src/examples/fonts.ts +245 -0
- package/src/examples/fractal-shader-demo.ts +268 -0
- package/src/examples/framebuffer-demo.ts +674 -0
- package/src/examples/full-unicode-demo.ts +241 -0
- package/src/examples/golden-star-demo.ts +933 -0
- package/src/examples/grayscale-buffer-demo.ts +249 -0
- package/src/examples/hast-syntax-highlighting-demo.ts +129 -0
- package/src/examples/index.ts +926 -0
- package/src/examples/input-demo.ts +377 -0
- package/src/examples/input-select-layout-demo.ts +425 -0
- package/src/examples/install.sh +143 -0
- package/src/examples/keypress-debug-demo.ts +452 -0
- package/src/examples/lib/HexList.ts +122 -0
- package/src/examples/lib/PaletteGrid.ts +125 -0
- package/src/examples/lib/standalone-keys.ts +25 -0
- package/src/examples/lib/tab-controller.ts +243 -0
- package/src/examples/lights-phong-demo.ts +290 -0
- package/src/examples/link-demo.ts +220 -0
- package/src/examples/live-state-demo.ts +480 -0
- package/src/examples/markdown-demo.ts +725 -0
- package/src/examples/mouse-interaction-demo.ts +428 -0
- package/src/examples/nested-zindex-demo.ts +357 -0
- package/src/examples/opacity-example.ts +235 -0
- package/src/examples/opentui-demo.ts +1057 -0
- package/src/examples/physx-planck-2d-demo.ts +623 -0
- package/src/examples/physx-rapier-2d-demo.ts +655 -0
- package/src/examples/relative-positioning-demo.ts +323 -0
- package/src/examples/scroll-example.ts +214 -0
- package/src/examples/scrollbox-mouse-test.ts +112 -0
- package/src/examples/scrollbox-overlay-hit-test.ts +206 -0
- package/src/examples/select-demo.ts +237 -0
- package/src/examples/shader-cube-demo.ts +1015 -0
- package/src/examples/simple-layout-example.ts +591 -0
- package/src/examples/slider-demo.ts +617 -0
- package/src/examples/split-mode-demo.ts +453 -0
- package/src/examples/sprite-animation-demo.ts +443 -0
- package/src/examples/sprite-particle-generator-demo.ts +486 -0
- package/src/examples/static-sprite-demo.ts +193 -0
- package/src/examples/sticky-scroll-example.ts +308 -0
- package/src/examples/styled-text-demo.ts +282 -0
- package/src/examples/tab-select-demo.ts +219 -0
- package/src/examples/terminal-title.ts +29 -0
- package/src/examples/terminal.ts +305 -0
- package/src/examples/text-node-demo.ts +416 -0
- package/src/examples/text-selection-demo.ts +377 -0
- package/src/examples/text-table-demo.ts +503 -0
- package/src/examples/text-truncation-demo.ts +481 -0
- package/src/examples/text-wrap.ts +757 -0
- package/src/examples/texture-loading-demo.ts +259 -0
- package/src/examples/timeline-example.ts +670 -0
- package/src/examples/transparency-demo.ts +400 -0
- package/src/examples/vnode-composition-demo.ts +404 -0
- package/src/examples/wide-grapheme-overlay-demo.ts +280 -0
- package/src/index.ts +24 -0
- package/src/lib/KeyHandler.integration.test.ts +292 -0
- package/src/lib/KeyHandler.stopPropagation.test.ts +289 -0
- package/src/lib/KeyHandler.test.ts +662 -0
- package/src/lib/KeyHandler.ts +222 -0
- package/src/lib/RGBA.test.ts +984 -0
- package/src/lib/RGBA.ts +204 -0
- package/src/lib/ascii.font.ts +330 -0
- package/src/lib/border.test.ts +83 -0
- package/src/lib/border.ts +170 -0
- package/src/lib/bunfs.test.ts +27 -0
- package/src/lib/bunfs.ts +18 -0
- package/src/lib/clipboard.test.ts +41 -0
- package/src/lib/clipboard.ts +47 -0
- package/src/lib/clock.ts +35 -0
- package/src/lib/data-paths.test.ts +133 -0
- package/src/lib/data-paths.ts +109 -0
- package/src/lib/debounce.ts +106 -0
- package/src/lib/detect-links.test.ts +98 -0
- package/src/lib/detect-links.ts +56 -0
- package/src/lib/env.test.ts +228 -0
- package/src/lib/env.ts +209 -0
- package/src/lib/extmarks-history.ts +51 -0
- package/src/lib/extmarks-multiwidth.test.ts +322 -0
- package/src/lib/extmarks.test.ts +3457 -0
- package/src/lib/extmarks.ts +843 -0
- package/src/lib/fonts/block.json +405 -0
- package/src/lib/fonts/grid.json +265 -0
- package/src/lib/fonts/huge.json +741 -0
- package/src/lib/fonts/pallet.json +314 -0
- package/src/lib/fonts/shade.json +591 -0
- package/src/lib/fonts/slick.json +321 -0
- package/src/lib/fonts/tiny.json +69 -0
- package/src/lib/hast-styled-text.ts +59 -0
- package/src/lib/index.ts +21 -0
- package/src/lib/keymapping.test.ts +317 -0
- package/src/lib/keymapping.ts +115 -0
- package/src/lib/objects-in-viewport.test.ts +787 -0
- package/src/lib/objects-in-viewport.ts +153 -0
- package/src/lib/output.capture.ts +58 -0
- package/src/lib/parse.keypress-kitty.protocol.test.ts +340 -0
- package/src/lib/parse.keypress-kitty.test.ts +663 -0
- package/src/lib/parse.keypress-kitty.ts +439 -0
- package/src/lib/parse.keypress.test.ts +1849 -0
- package/src/lib/parse.keypress.ts +397 -0
- package/src/lib/parse.mouse.test.ts +552 -0
- package/src/lib/parse.mouse.ts +232 -0
- package/src/lib/paste.ts +16 -0
- package/src/lib/queue.ts +65 -0
- package/src/lib/renderable.validations.test.ts +87 -0
- package/src/lib/renderable.validations.ts +83 -0
- package/src/lib/scroll-acceleration.ts +98 -0
- package/src/lib/selection.ts +240 -0
- package/src/lib/singleton.ts +28 -0
- package/src/lib/stdin-parser.test.ts +2290 -0
- package/src/lib/stdin-parser.ts +1810 -0
- package/src/lib/styled-text.ts +178 -0
- package/src/lib/terminal-capability-detection.test.ts +202 -0
- package/src/lib/terminal-capability-detection.ts +79 -0
- package/src/lib/terminal-palette.test.ts +878 -0
- package/src/lib/terminal-palette.ts +383 -0
- package/src/lib/tree-sitter/assets/README.md +118 -0
- package/src/lib/tree-sitter/assets/update.ts +334 -0
- package/src/lib/tree-sitter/assets.d.ts +9 -0
- package/src/lib/tree-sitter/cache.test.ts +273 -0
- package/src/lib/tree-sitter/client.test.ts +1165 -0
- package/src/lib/tree-sitter/client.ts +607 -0
- package/src/lib/tree-sitter/default-parsers.ts +86 -0
- package/src/lib/tree-sitter/download-utils.ts +148 -0
- package/src/lib/tree-sitter/index.ts +28 -0
- package/src/lib/tree-sitter/parser.worker.ts +1042 -0
- package/src/lib/tree-sitter/parsers-config.ts +81 -0
- package/src/lib/tree-sitter/resolve-ft.test.ts +55 -0
- package/src/lib/tree-sitter/resolve-ft.ts +189 -0
- package/src/lib/tree-sitter/types.ts +82 -0
- package/src/lib/tree-sitter-styled-text.test.ts +1253 -0
- package/src/lib/tree-sitter-styled-text.ts +306 -0
- package/src/lib/validate-dir-name.ts +55 -0
- package/src/lib/yoga.options.test.ts +628 -0
- package/src/lib/yoga.options.ts +346 -0
- package/src/plugins/core-slot.ts +579 -0
- package/src/plugins/registry.ts +402 -0
- package/src/plugins/types.ts +46 -0
- package/src/post/effects.ts +930 -0
- package/src/post/filters.ts +489 -0
- package/src/post/matrices.ts +288 -0
- package/src/renderables/ASCIIFont.ts +219 -0
- package/src/renderables/Box.test.ts +205 -0
- package/src/renderables/Box.ts +326 -0
- package/src/renderables/Code.test.ts +2062 -0
- package/src/renderables/Code.ts +357 -0
- package/src/renderables/Diff.regression.test.ts +226 -0
- package/src/renderables/Diff.test.ts +3101 -0
- package/src/renderables/Diff.ts +1211 -0
- package/src/renderables/EditBufferRenderable.test.ts +288 -0
- package/src/renderables/EditBufferRenderable.ts +1166 -0
- package/src/renderables/FrameBuffer.ts +47 -0
- package/src/renderables/Input.test.ts +1228 -0
- package/src/renderables/Input.ts +247 -0
- package/src/renderables/LineNumberRenderable.ts +724 -0
- package/src/renderables/Markdown.ts +1393 -0
- package/src/renderables/ScrollBar.ts +422 -0
- package/src/renderables/ScrollBox.ts +883 -0
- package/src/renderables/Select.test.ts +1033 -0
- package/src/renderables/Select.ts +524 -0
- package/src/renderables/Slider.test.ts +456 -0
- package/src/renderables/Slider.ts +342 -0
- package/src/renderables/TabSelect.test.ts +197 -0
- package/src/renderables/TabSelect.ts +455 -0
- package/src/renderables/Text.selection-buffer.test.ts +123 -0
- package/src/renderables/Text.test.ts +2660 -0
- package/src/renderables/Text.ts +147 -0
- package/src/renderables/TextBufferRenderable.ts +518 -0
- package/src/renderables/TextNode.test.ts +1058 -0
- package/src/renderables/TextNode.ts +325 -0
- package/src/renderables/TextTable.test.ts +1421 -0
- package/src/renderables/TextTable.ts +1344 -0
- package/src/renderables/Textarea.ts +430 -0
- package/src/renderables/TimeToFirstDraw.ts +89 -0
- package/src/renderables/__snapshots__/Code.test.ts.snap +13 -0
- package/src/renderables/__snapshots__/Diff.test.ts.snap +785 -0
- package/src/renderables/__snapshots__/Text.test.ts.snap +421 -0
- package/src/renderables/__snapshots__/TextTable.test.ts.snap +215 -0
- package/src/renderables/__tests__/LineNumberRenderable.scrollbox-simple.test.ts +144 -0
- package/src/renderables/__tests__/LineNumberRenderable.scrollbox.test.ts +816 -0
- package/src/renderables/__tests__/LineNumberRenderable.test.ts +1865 -0
- package/src/renderables/__tests__/LineNumberRenderable.wrapping.test.ts +85 -0
- package/src/renderables/__tests__/Markdown.code-colors.test.ts +242 -0
- package/src/renderables/__tests__/Markdown.test.ts +2518 -0
- package/src/renderables/__tests__/MultiRenderable.selection.test.ts +87 -0
- package/src/renderables/__tests__/Textarea.buffer.test.ts +682 -0
- package/src/renderables/__tests__/Textarea.destroyed-events.test.ts +675 -0
- package/src/renderables/__tests__/Textarea.editing.test.ts +2041 -0
- package/src/renderables/__tests__/Textarea.error-handling.test.ts +35 -0
- package/src/renderables/__tests__/Textarea.events.test.ts +738 -0
- package/src/renderables/__tests__/Textarea.highlights.test.ts +590 -0
- package/src/renderables/__tests__/Textarea.keybinding.test.ts +3149 -0
- package/src/renderables/__tests__/Textarea.paste.test.ts +357 -0
- package/src/renderables/__tests__/Textarea.rendering.test.ts +1866 -0
- package/src/renderables/__tests__/Textarea.scroll.test.ts +733 -0
- package/src/renderables/__tests__/Textarea.selection.test.ts +1590 -0
- package/src/renderables/__tests__/Textarea.stress.test.ts +670 -0
- package/src/renderables/__tests__/Textarea.undo-redo.test.ts +383 -0
- package/src/renderables/__tests__/Textarea.visual-lines.test.ts +310 -0
- package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.code.test.ts.snap +221 -0
- package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox-simple.test.ts.snap +89 -0
- package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox.test.ts.snap +457 -0
- package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.test.ts.snap +158 -0
- package/src/renderables/__tests__/__snapshots__/Textarea.rendering.test.ts.snap +387 -0
- package/src/renderables/__tests__/markdown-parser.test.ts +217 -0
- package/src/renderables/__tests__/renderable-test-utils.ts +60 -0
- package/src/renderables/composition/README.md +8 -0
- package/src/renderables/composition/VRenderable.ts +32 -0
- package/src/renderables/composition/constructs.ts +127 -0
- package/src/renderables/composition/vnode.ts +289 -0
- package/src/renderables/index.ts +23 -0
- package/src/renderables/markdown-parser.ts +66 -0
- package/src/renderer.ts +2681 -0
- package/src/runtime-plugin-support.ts +39 -0
- package/src/runtime-plugin.ts +615 -0
- package/src/syntax-style.test.ts +841 -0
- package/src/syntax-style.ts +257 -0
- package/src/testing/README.md +210 -0
- package/src/testing/capture-spans.test.ts +194 -0
- package/src/testing/integration.test.ts +276 -0
- package/src/testing/manual-clock.ts +117 -0
- package/src/testing/mock-keys.test.ts +1378 -0
- package/src/testing/mock-keys.ts +457 -0
- package/src/testing/mock-mouse.test.ts +218 -0
- package/src/testing/mock-mouse.ts +247 -0
- package/src/testing/mock-tree-sitter-client.ts +73 -0
- package/src/testing/spy.ts +13 -0
- package/src/testing/test-recorder.test.ts +415 -0
- package/src/testing/test-recorder.ts +145 -0
- package/src/testing/test-renderer.ts +132 -0
- package/src/testing.ts +7 -0
- package/src/tests/__snapshots__/absolute-positioning.snapshot.test.ts.snap +481 -0
- package/src/tests/__snapshots__/renderable.snapshot.test.ts.snap +19 -0
- package/src/tests/__snapshots__/scrollbox.test.ts.snap +29 -0
- package/src/tests/absolute-positioning.snapshot.test.ts +638 -0
- package/src/tests/allocator-stats.test.ts +38 -0
- package/src/tests/destroy-during-render.test.ts +200 -0
- package/src/tests/destroy-on-exit.fixture.ts +36 -0
- package/src/tests/destroy-on-exit.test.ts +41 -0
- package/src/tests/hover-cursor.test.ts +98 -0
- package/src/tests/native-span-feed-async.test.ts +173 -0
- package/src/tests/native-span-feed-close.test.ts +120 -0
- package/src/tests/native-span-feed-coverage.test.ts +227 -0
- package/src/tests/native-span-feed-edge-cases.test.ts +352 -0
- package/src/tests/native-span-feed-use-after-free.test.ts +45 -0
- package/src/tests/opacity.test.ts +123 -0
- package/src/tests/renderable.snapshot.test.ts +524 -0
- package/src/tests/renderable.test.ts +1281 -0
- package/src/tests/renderer.clock.test.ts +158 -0
- package/src/tests/renderer.console-startup.test.ts +185 -0
- package/src/tests/renderer.control.test.ts +425 -0
- package/src/tests/renderer.core-slot-binding.test.ts +952 -0
- package/src/tests/renderer.cursor.test.ts +26 -0
- package/src/tests/renderer.destroy-during-render.test.ts +147 -0
- package/src/tests/renderer.focus-restore.test.ts +257 -0
- package/src/tests/renderer.focus.test.ts +294 -0
- package/src/tests/renderer.idle.test.ts +219 -0
- package/src/tests/renderer.input.test.ts +2237 -0
- package/src/tests/renderer.kitty-flags.test.ts +195 -0
- package/src/tests/renderer.mouse.test.ts +1274 -0
- package/src/tests/renderer.palette.test.ts +629 -0
- package/src/tests/renderer.selection.test.ts +49 -0
- package/src/tests/renderer.slot-registry.test.ts +684 -0
- package/src/tests/renderer.useMouse.test.ts +47 -0
- package/src/tests/runtime-plugin-node-modules-cycle.fixture.ts +76 -0
- package/src/tests/runtime-plugin-node-modules-mjs.fixture.ts +43 -0
- package/src/tests/runtime-plugin-node-modules-no-bare-rewrite.fixture.ts +67 -0
- package/src/tests/runtime-plugin-node-modules-package-type-cache.fixture.ts +72 -0
- package/src/tests/runtime-plugin-node-modules-runtime-specifier.fixture.ts +44 -0
- package/src/tests/runtime-plugin-node-modules-scoped-package-bare-rewrite.fixture.ts +85 -0
- package/src/tests/runtime-plugin-path-alias.fixture.ts +43 -0
- package/src/tests/runtime-plugin-resolve-roots.fixture.ts +65 -0
- package/src/tests/runtime-plugin-support.fixture.ts +11 -0
- package/src/tests/runtime-plugin-support.test.ts +19 -0
- package/src/tests/runtime-plugin-windows-file-url.fixture.ts +30 -0
- package/src/tests/runtime-plugin.fixture.ts +40 -0
- package/src/tests/runtime-plugin.test.ts +354 -0
- package/src/tests/scrollbox-culling-bug.test.ts +114 -0
- package/src/tests/scrollbox-hitgrid-resize.test.ts +136 -0
- package/src/tests/scrollbox-hitgrid.test.ts +909 -0
- package/src/tests/scrollbox.test.ts +1530 -0
- package/src/tests/wrap-resize-perf.test.ts +276 -0
- package/src/tests/yoga-setters.test.ts +921 -0
- package/src/text-buffer-view.test.ts +705 -0
- package/src/text-buffer-view.ts +189 -0
- package/src/text-buffer.test.ts +347 -0
- package/src/text-buffer.ts +250 -0
- package/src/types.ts +161 -0
- package/src/utils.ts +88 -0
- package/src/zig/ansi.zig +268 -0
- package/src/zig/bench/README.md +50 -0
- package/src/zig/bench/buffer-draw-text-buffer_bench.zig +887 -0
- package/src/zig/bench/edit-buffer_bench.zig +476 -0
- package/src/zig/bench/native-span-feed_bench.zig +100 -0
- package/src/zig/bench/rope-markers_bench.zig +713 -0
- package/src/zig/bench/rope_bench.zig +514 -0
- package/src/zig/bench/styled-text_bench.zig +470 -0
- package/src/zig/bench/text-buffer-coords_bench.zig +362 -0
- package/src/zig/bench/text-buffer-view_bench.zig +459 -0
- package/src/zig/bench/text-chunk-graphemes_bench.zig +273 -0
- package/src/zig/bench/utf8_bench.zig +799 -0
- package/src/zig/bench-utils.zig +431 -0
- package/src/zig/bench.zig +217 -0
- package/src/zig/buffer-methods.zig +211 -0
- package/src/zig/buffer.zig +2281 -0
- package/src/zig/build.zig +289 -0
- package/src/zig/build.zig.zon +16 -0
- package/src/zig/edit-buffer.zig +825 -0
- package/src/zig/editor-view.zig +802 -0
- package/src/zig/event-bus.zig +13 -0
- package/src/zig/event-emitter.zig +65 -0
- package/src/zig/file-logger.zig +92 -0
- package/src/zig/grapheme.zig +599 -0
- package/src/zig/lib.zig +1854 -0
- package/src/zig/link.zig +333 -0
- package/src/zig/logger.zig +43 -0
- package/src/zig/mem-registry.zig +125 -0
- package/src/zig/native-span-feed-bench-lib.zig +7 -0
- package/src/zig/native-span-feed.zig +708 -0
- package/src/zig/renderer.zig +1393 -0
- package/src/zig/rope.zig +1220 -0
- package/src/zig/syntax-style.zig +161 -0
- package/src/zig/terminal.zig +987 -0
- package/src/zig/test.zig +72 -0
- package/src/zig/tests/README.md +18 -0
- package/src/zig/tests/buffer-methods_test.zig +1109 -0
- package/src/zig/tests/buffer_test.zig +2557 -0
- package/src/zig/tests/edit-buffer-history_test.zig +271 -0
- package/src/zig/tests/edit-buffer_test.zig +1689 -0
- package/src/zig/tests/editor-view_test.zig +3299 -0
- package/src/zig/tests/event-emitter_test.zig +249 -0
- package/src/zig/tests/grapheme_test.zig +1304 -0
- package/src/zig/tests/link_test.zig +190 -0
- package/src/zig/tests/mem-registry_test.zig +473 -0
- package/src/zig/tests/memory_leak_regression_test.zig +159 -0
- package/src/zig/tests/native-span-feed_test.zig +1264 -0
- package/src/zig/tests/renderer_test.zig +1017 -0
- package/src/zig/tests/rope-nested_test.zig +712 -0
- package/src/zig/tests/rope_fuzz_test.zig +238 -0
- package/src/zig/tests/rope_test.zig +2362 -0
- package/src/zig/tests/segment-merge.test.zig +148 -0
- package/src/zig/tests/syntax-style_test.zig +557 -0
- package/src/zig/tests/terminal_test.zig +754 -0
- package/src/zig/tests/text-buffer-drawing_test.zig +3237 -0
- package/src/zig/tests/text-buffer-highlights_test.zig +666 -0
- package/src/zig/tests/text-buffer-iterators_test.zig +776 -0
- package/src/zig/tests/text-buffer-segment_test.zig +320 -0
- package/src/zig/tests/text-buffer-selection_test.zig +1035 -0
- package/src/zig/tests/text-buffer-selection_viewport_test.zig +358 -0
- package/src/zig/tests/text-buffer-view_test.zig +3649 -0
- package/src/zig/tests/text-buffer_test.zig +2191 -0
- package/src/zig/tests/unicode-width-map.zon +3909 -0
- package/src/zig/tests/utf8_no_zwj_test.zig +260 -0
- package/src/zig/tests/utf8_test.zig +4057 -0
- package/src/zig/tests/utf8_wcwidth_cursor_test.zig +267 -0
- package/src/zig/tests/utf8_wcwidth_test.zig +357 -0
- package/src/zig/tests/word-wrap-editing_test.zig +498 -0
- package/src/zig/tests/wrap-cache-perf_test.zig +113 -0
- package/src/zig/text-buffer-iterators.zig +499 -0
- package/src/zig/text-buffer-segment.zig +404 -0
- package/src/zig/text-buffer-view.zig +1371 -0
- package/src/zig/text-buffer.zig +1180 -0
- package/src/zig/utf8.zig +1948 -0
- package/src/zig/utils.zig +9 -0
- package/src/zig-structs.ts +261 -0
- package/src/zig.ts +3884 -0
- package/tsconfig.build.json +24 -0
- package/tsconfig.json +27 -0
- package/3d/SpriteResourceManager.d.ts +0 -74
- package/3d/SpriteUtils.d.ts +0 -13
- package/3d/TextureUtils.d.ts +0 -24
- package/3d/ThreeRenderable.d.ts +0 -40
- package/3d/WGPURenderer.d.ts +0 -61
- package/3d/animation/ExplodingSpriteEffect.d.ts +0 -71
- package/3d/animation/PhysicsExplodingSpriteEffect.d.ts +0 -76
- package/3d/animation/SpriteAnimator.d.ts +0 -124
- package/3d/animation/SpriteParticleGenerator.d.ts +0 -62
- package/3d/canvas.d.ts +0 -44
- package/3d/index.d.ts +0 -12
- package/3d/physics/PlanckPhysicsAdapter.d.ts +0 -19
- package/3d/physics/RapierPhysicsAdapter.d.ts +0 -19
- package/3d/physics/physics-interface.d.ts +0 -27
- package/3d.d.ts +0 -2
- package/3d.js +0 -34041
- package/3d.js.map +0 -155
- package/LICENSE +0 -21
- package/NativeSpanFeed.d.ts +0 -41
- package/Renderable.d.ts +0 -334
- package/animation/Timeline.d.ts +0 -126
- package/ansi.d.ts +0 -13
- package/buffer.d.ts +0 -111
- package/console.d.ts +0 -144
- package/edit-buffer.d.ts +0 -98
- package/editor-view.d.ts +0 -73
- package/index-8fks7yv1.js +0 -411
- package/index-8fks7yv1.js.map +0 -10
- package/index-egy5e2rs.js +0 -12267
- package/index-egy5e2rs.js.map +0 -42
- package/index-tse8gzh0.js +0 -20614
- package/index-tse8gzh0.js.map +0 -67
- package/index.d.ts +0 -23
- package/index.js +0 -478
- package/index.js.map +0 -9
- package/lib/KeyHandler.d.ts +0 -61
- package/lib/RGBA.d.ts +0 -25
- package/lib/ascii.font.d.ts +0 -508
- package/lib/border.d.ts +0 -51
- package/lib/bunfs.d.ts +0 -7
- package/lib/clipboard.d.ts +0 -17
- package/lib/clock.d.ts +0 -15
- package/lib/data-paths.d.ts +0 -26
- package/lib/debounce.d.ts +0 -42
- package/lib/detect-links.d.ts +0 -6
- package/lib/env.d.ts +0 -42
- package/lib/extmarks-history.d.ts +0 -17
- package/lib/extmarks.d.ts +0 -89
- package/lib/hast-styled-text.d.ts +0 -17
- package/lib/index.d.ts +0 -21
- package/lib/keymapping.d.ts +0 -25
- package/lib/objects-in-viewport.d.ts +0 -24
- package/lib/output.capture.d.ts +0 -24
- package/lib/parse.keypress-kitty.d.ts +0 -2
- package/lib/parse.keypress.d.ts +0 -26
- package/lib/parse.mouse.d.ts +0 -30
- package/lib/paste.d.ts +0 -7
- package/lib/queue.d.ts +0 -15
- package/lib/renderable.validations.d.ts +0 -12
- package/lib/scroll-acceleration.d.ts +0 -43
- package/lib/selection.d.ts +0 -63
- package/lib/singleton.d.ts +0 -7
- package/lib/stdin-parser.d.ts +0 -87
- package/lib/styled-text.d.ts +0 -63
- package/lib/terminal-capability-detection.d.ts +0 -30
- package/lib/terminal-palette.d.ts +0 -50
- package/lib/tree-sitter/assets/update.d.ts +0 -11
- package/lib/tree-sitter/client.d.ts +0 -47
- package/lib/tree-sitter/default-parsers.d.ts +0 -2
- package/lib/tree-sitter/download-utils.d.ts +0 -21
- package/lib/tree-sitter/index.d.ts +0 -8
- package/lib/tree-sitter/parser.worker.d.ts +0 -1
- package/lib/tree-sitter/parsers-config.d.ts +0 -53
- package/lib/tree-sitter/resolve-ft.d.ts +0 -5
- package/lib/tree-sitter/types.d.ts +0 -82
- package/lib/tree-sitter-styled-text.d.ts +0 -14
- package/lib/validate-dir-name.d.ts +0 -1
- package/lib/yoga.options.d.ts +0 -32
- package/parser.worker.js +0 -899
- package/parser.worker.js.map +0 -12
- package/plugins/core-slot.d.ts +0 -72
- package/plugins/registry.d.ts +0 -42
- package/plugins/types.d.ts +0 -34
- package/post/effects.d.ts +0 -147
- package/post/filters.d.ts +0 -65
- package/post/matrices.d.ts +0 -20
- package/renderables/ASCIIFont.d.ts +0 -52
- package/renderables/Box.d.ts +0 -81
- package/renderables/Code.d.ts +0 -78
- package/renderables/Diff.d.ts +0 -142
- package/renderables/EditBufferRenderable.d.ts +0 -237
- package/renderables/FrameBuffer.d.ts +0 -16
- package/renderables/Input.d.ts +0 -67
- package/renderables/LineNumberRenderable.d.ts +0 -78
- package/renderables/Markdown.d.ts +0 -185
- package/renderables/ScrollBar.d.ts +0 -77
- package/renderables/ScrollBox.d.ts +0 -124
- package/renderables/Select.d.ts +0 -115
- package/renderables/Slider.d.ts +0 -47
- package/renderables/TabSelect.d.ts +0 -96
- package/renderables/Text.d.ts +0 -36
- package/renderables/TextBufferRenderable.d.ts +0 -105
- package/renderables/TextNode.d.ts +0 -91
- package/renderables/TextTable.d.ts +0 -140
- package/renderables/Textarea.d.ts +0 -63
- package/renderables/TimeToFirstDraw.d.ts +0 -24
- package/renderables/__tests__/renderable-test-utils.d.ts +0 -12
- package/renderables/composition/VRenderable.d.ts +0 -16
- package/renderables/composition/constructs.d.ts +0 -35
- package/renderables/composition/vnode.d.ts +0 -46
- package/renderables/index.d.ts +0 -23
- package/renderables/markdown-parser.d.ts +0 -10
- package/renderer.d.ts +0 -419
- package/runtime-plugin-support.d.ts +0 -3
- package/runtime-plugin-support.js +0 -29
- package/runtime-plugin-support.js.map +0 -10
- package/runtime-plugin.d.ts +0 -16
- package/runtime-plugin.js +0 -16
- package/runtime-plugin.js.map +0 -9
- package/syntax-style.d.ts +0 -54
- package/testing/manual-clock.d.ts +0 -17
- package/testing/mock-keys.d.ts +0 -81
- package/testing/mock-mouse.d.ts +0 -38
- package/testing/mock-tree-sitter-client.d.ts +0 -23
- package/testing/spy.d.ts +0 -7
- package/testing/test-recorder.d.ts +0 -61
- package/testing/test-renderer.d.ts +0 -23
- package/testing.d.ts +0 -6
- package/testing.js +0 -697
- package/testing.js.map +0 -15
- package/text-buffer-view.d.ts +0 -42
- package/text-buffer.d.ts +0 -67
- package/types.d.ts +0 -139
- package/utils.d.ts +0 -14
- package/zig-structs.d.ts +0 -155
- package/zig.d.ts +0 -353
- /package/{assets → src/lib/tree-sitter/assets}/javascript/highlights.scm +0 -0
- /package/{assets → src/lib/tree-sitter/assets}/javascript/tree-sitter-javascript.wasm +0 -0
- /package/{assets → src/lib/tree-sitter/assets}/markdown/highlights.scm +0 -0
- /package/{assets → src/lib/tree-sitter/assets}/markdown/injections.scm +0 -0
- /package/{assets → src/lib/tree-sitter/assets}/markdown/tree-sitter-markdown.wasm +0 -0
- /package/{assets → src/lib/tree-sitter/assets}/markdown_inline/highlights.scm +0 -0
- /package/{assets → src/lib/tree-sitter/assets}/markdown_inline/tree-sitter-markdown_inline.wasm +0 -0
- /package/{assets → src/lib/tree-sitter/assets}/typescript/highlights.scm +0 -0
- /package/{assets → src/lib/tree-sitter/assets}/typescript/tree-sitter-typescript.wasm +0 -0
- /package/{assets → src/lib/tree-sitter/assets}/zig/highlights.scm +0 -0
- /package/{assets → src/lib/tree-sitter/assets}/zig/tree-sitter-zig.wasm +0 -0
|
@@ -0,0 +1,2237 @@
|
|
|
1
|
+
import { test, expect, beforeEach, afterEach, describe } from "bun:test"
|
|
2
|
+
import { decodePasteBytes } from "../lib/paste.js"
|
|
3
|
+
import { nonAlphanumericKeys, type KeyEventType, type ParsedKey } from "../lib/parse.keypress.js"
|
|
4
|
+
import { type KeyEvent } from "../lib/KeyHandler.js"
|
|
5
|
+
import { Buffer } from "node:buffer"
|
|
6
|
+
import { Renderable, type RenderableOptions } from "../Renderable.js"
|
|
7
|
+
import { createTestRenderer, type TestRenderer, type TestRendererOptions } from "../testing/test-renderer.js"
|
|
8
|
+
import { ManualClock } from "../testing/manual-clock.js"
|
|
9
|
+
import type { RenderContext } from "../types.js"
|
|
10
|
+
|
|
11
|
+
let currentRenderer: TestRenderer
|
|
12
|
+
let kittyRenderer: TestRenderer
|
|
13
|
+
let mockProcessCapabilityResponse: any
|
|
14
|
+
let mockGetTerminalCapabilities: any
|
|
15
|
+
let currentClock: ManualClock
|
|
16
|
+
let kittyClock: ManualClock
|
|
17
|
+
|
|
18
|
+
beforeEach(async () => {
|
|
19
|
+
currentClock = new ManualClock()
|
|
20
|
+
kittyClock = new ManualClock()
|
|
21
|
+
;({ renderer: currentRenderer } = await createTestRenderer({ clock: currentClock }))
|
|
22
|
+
;({ renderer: kittyRenderer } = await createTestRenderer({ kittyKeyboard: true, clock: kittyClock }))
|
|
23
|
+
|
|
24
|
+
// Mock native capability functions to avoid interfering with the test terminal
|
|
25
|
+
// @ts-expect-error - mocking for test
|
|
26
|
+
mockProcessCapabilityResponse = currentRenderer.lib.processCapabilityResponse
|
|
27
|
+
// @ts-expect-error - mocking for test
|
|
28
|
+
mockGetTerminalCapabilities = currentRenderer.lib.getTerminalCapabilities
|
|
29
|
+
|
|
30
|
+
// @ts-expect-error - mocking for test
|
|
31
|
+
currentRenderer.lib.processCapabilityResponse = () => {}
|
|
32
|
+
// @ts-expect-error - mocking for test
|
|
33
|
+
currentRenderer.lib.getTerminalCapabilities = () => ({ unicode: "unicode" })
|
|
34
|
+
|
|
35
|
+
// @ts-expect-error - mocking for test
|
|
36
|
+
kittyRenderer.lib.processCapabilityResponse = () => {}
|
|
37
|
+
// @ts-expect-error - mocking for test
|
|
38
|
+
kittyRenderer.lib.getTerminalCapabilities = () => ({ unicode: "unicode" })
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
// Restore mocks
|
|
43
|
+
// @ts-expect-error - restore mock
|
|
44
|
+
currentRenderer.lib.processCapabilityResponse = mockProcessCapabilityResponse
|
|
45
|
+
// @ts-expect-error - restore mock
|
|
46
|
+
currentRenderer.lib.getTerminalCapabilities = mockGetTerminalCapabilities
|
|
47
|
+
// @ts-expect-error - restore mock
|
|
48
|
+
kittyRenderer.lib.processCapabilityResponse = mockProcessCapabilityResponse
|
|
49
|
+
// @ts-expect-error - restore mock
|
|
50
|
+
kittyRenderer.lib.getTerminalCapabilities = mockGetTerminalCapabilities
|
|
51
|
+
|
|
52
|
+
currentRenderer.destroy()
|
|
53
|
+
kittyRenderer.destroy()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
async function triggerInput(sequence: string): Promise<KeyEvent> {
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
const onKeypress = (parsedKey: KeyEvent) => {
|
|
59
|
+
currentRenderer.keyInput.removeListener("keypress", onKeypress)
|
|
60
|
+
resolve(parsedKey)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
currentRenderer.keyInput.once("keypress", onKeypress)
|
|
64
|
+
|
|
65
|
+
currentRenderer.stdin.emit("data", Buffer.from(sequence))
|
|
66
|
+
advanceCurrentClock()
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function triggerKittyInput(sequence: string): Promise<KeyEvent> {
|
|
71
|
+
return new Promise((resolve) => {
|
|
72
|
+
const onKeypress = (parsedKey: KeyEvent) => {
|
|
73
|
+
kittyRenderer.keyInput.removeListener("keypress", onKeypress)
|
|
74
|
+
kittyRenderer.keyInput.removeListener("keyrelease", onKeypress)
|
|
75
|
+
resolve(parsedKey)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
kittyRenderer.keyInput.on("keypress", onKeypress)
|
|
79
|
+
kittyRenderer.keyInput.on("keyrelease", onKeypress)
|
|
80
|
+
|
|
81
|
+
kittyRenderer.stdin.emit("data", Buffer.from(sequence))
|
|
82
|
+
advanceKittyClock()
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function advanceCurrentClock(ms: number = 20): void {
|
|
87
|
+
currentClock.advance(ms)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function advanceKittyClock(ms: number = 20): void {
|
|
91
|
+
kittyClock.advance(ms)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
class MouseTarget extends Renderable {
|
|
95
|
+
constructor(context: RenderContext, options: RenderableOptions) {
|
|
96
|
+
super(context, options)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function advanceClock(clock: ManualClock, ms: number = 20): void {
|
|
101
|
+
clock.advance(ms)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function createRoutingRenderer(options: Partial<TestRendererOptions> = {}): Promise<{
|
|
105
|
+
renderer: TestRenderer
|
|
106
|
+
renderOnce: () => Promise<void>
|
|
107
|
+
resize: (width: number, height: number) => void
|
|
108
|
+
clock: ManualClock
|
|
109
|
+
}> {
|
|
110
|
+
const clock = new ManualClock()
|
|
111
|
+
const { renderer, renderOnce, resize } = await createTestRenderer({
|
|
112
|
+
width: 40,
|
|
113
|
+
height: 20,
|
|
114
|
+
useMouse: true,
|
|
115
|
+
clock,
|
|
116
|
+
...options,
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
return { renderer, renderOnce, resize, clock }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
test("basic letters via keyInput events", async () => {
|
|
123
|
+
const result = await triggerInput("a")
|
|
124
|
+
expect(result).toMatchObject({
|
|
125
|
+
name: "a",
|
|
126
|
+
ctrl: false,
|
|
127
|
+
meta: false,
|
|
128
|
+
shift: false,
|
|
129
|
+
option: false,
|
|
130
|
+
number: false,
|
|
131
|
+
sequence: "a",
|
|
132
|
+
raw: "a",
|
|
133
|
+
eventType: "press",
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
const resultShift = await triggerInput("A")
|
|
137
|
+
expect(resultShift).toMatchObject({
|
|
138
|
+
eventType: "press",
|
|
139
|
+
name: "a",
|
|
140
|
+
ctrl: false,
|
|
141
|
+
meta: false,
|
|
142
|
+
shift: true,
|
|
143
|
+
option: false,
|
|
144
|
+
number: false,
|
|
145
|
+
sequence: "A",
|
|
146
|
+
raw: "A",
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
test("numbers via keyInput events", async () => {
|
|
151
|
+
const result = await triggerInput("1")
|
|
152
|
+
expect(result).toMatchObject({
|
|
153
|
+
eventType: "press",
|
|
154
|
+
name: "1",
|
|
155
|
+
ctrl: false,
|
|
156
|
+
meta: false,
|
|
157
|
+
shift: false,
|
|
158
|
+
option: false,
|
|
159
|
+
number: true,
|
|
160
|
+
sequence: "1",
|
|
161
|
+
raw: "1",
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
test("special keys via keyInput events", async () => {
|
|
166
|
+
const resultReturn = await triggerInput("\r")
|
|
167
|
+
expect(resultReturn).toMatchObject({
|
|
168
|
+
eventType: "press",
|
|
169
|
+
name: "return",
|
|
170
|
+
ctrl: false,
|
|
171
|
+
meta: false,
|
|
172
|
+
shift: false,
|
|
173
|
+
option: false,
|
|
174
|
+
number: false,
|
|
175
|
+
sequence: "\r",
|
|
176
|
+
raw: "\r",
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
const resultEnter = await triggerInput("\n")
|
|
180
|
+
expect(resultEnter).toMatchObject({
|
|
181
|
+
eventType: "press",
|
|
182
|
+
name: "linefeed",
|
|
183
|
+
ctrl: false,
|
|
184
|
+
meta: false,
|
|
185
|
+
shift: false,
|
|
186
|
+
option: false,
|
|
187
|
+
number: false,
|
|
188
|
+
sequence: "\n",
|
|
189
|
+
raw: "\n",
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
const resultTab = await triggerInput("\t")
|
|
193
|
+
expect(resultTab).toMatchObject({
|
|
194
|
+
eventType: "press",
|
|
195
|
+
name: "tab",
|
|
196
|
+
ctrl: false,
|
|
197
|
+
meta: false,
|
|
198
|
+
shift: false,
|
|
199
|
+
option: false,
|
|
200
|
+
number: false,
|
|
201
|
+
sequence: "\t",
|
|
202
|
+
raw: "\t",
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
const resultBackspace = await triggerInput("\b")
|
|
206
|
+
expect(resultBackspace).toMatchObject({
|
|
207
|
+
eventType: "press",
|
|
208
|
+
name: "backspace",
|
|
209
|
+
ctrl: false,
|
|
210
|
+
meta: false,
|
|
211
|
+
shift: false,
|
|
212
|
+
option: false,
|
|
213
|
+
number: false,
|
|
214
|
+
sequence: "\b",
|
|
215
|
+
raw: "\b",
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
const resultEscape = await triggerInput("\x1b")
|
|
219
|
+
expect(resultEscape).toMatchObject({
|
|
220
|
+
name: "escape",
|
|
221
|
+
ctrl: false,
|
|
222
|
+
meta: false,
|
|
223
|
+
shift: false,
|
|
224
|
+
option: false,
|
|
225
|
+
number: false,
|
|
226
|
+
sequence: "\x1b",
|
|
227
|
+
raw: "\x1b",
|
|
228
|
+
eventType: "press",
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
const resultSpace = await triggerInput(" ")
|
|
232
|
+
expect(resultSpace).toMatchObject({
|
|
233
|
+
eventType: "press",
|
|
234
|
+
name: "space",
|
|
235
|
+
ctrl: false,
|
|
236
|
+
meta: false,
|
|
237
|
+
shift: false,
|
|
238
|
+
option: false,
|
|
239
|
+
number: false,
|
|
240
|
+
sequence: " ",
|
|
241
|
+
raw: " ",
|
|
242
|
+
})
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
test("ctrl+letter combinations via keyInput events", async () => {
|
|
246
|
+
const resultCtrlA = await triggerInput("\x01")
|
|
247
|
+
expect(resultCtrlA).toMatchObject({
|
|
248
|
+
eventType: "press",
|
|
249
|
+
name: "a",
|
|
250
|
+
ctrl: true,
|
|
251
|
+
meta: false,
|
|
252
|
+
shift: false,
|
|
253
|
+
option: false,
|
|
254
|
+
number: false,
|
|
255
|
+
sequence: "\x01",
|
|
256
|
+
raw: "\x01",
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
const resultCtrlZ = await triggerInput("\x1a")
|
|
260
|
+
expect(resultCtrlZ).toMatchObject({
|
|
261
|
+
eventType: "press",
|
|
262
|
+
name: "z",
|
|
263
|
+
ctrl: true,
|
|
264
|
+
meta: false,
|
|
265
|
+
shift: false,
|
|
266
|
+
option: false,
|
|
267
|
+
number: false,
|
|
268
|
+
sequence: "\x1a",
|
|
269
|
+
raw: "\x1a",
|
|
270
|
+
})
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
test("meta+character combinations via keyInput events", async () => {
|
|
274
|
+
const resultMetaA = await triggerInput("\x1ba")
|
|
275
|
+
expect(resultMetaA).toMatchObject({
|
|
276
|
+
eventType: "press",
|
|
277
|
+
name: "a",
|
|
278
|
+
ctrl: false,
|
|
279
|
+
meta: true,
|
|
280
|
+
shift: false,
|
|
281
|
+
option: false,
|
|
282
|
+
number: false,
|
|
283
|
+
sequence: "\x1ba",
|
|
284
|
+
raw: "\x1ba",
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
const resultMetaShiftA = await triggerInput("\x1bA")
|
|
288
|
+
expect(resultMetaShiftA).toMatchObject({
|
|
289
|
+
eventType: "press",
|
|
290
|
+
name: "A",
|
|
291
|
+
ctrl: false,
|
|
292
|
+
meta: true,
|
|
293
|
+
shift: true,
|
|
294
|
+
option: false,
|
|
295
|
+
number: false,
|
|
296
|
+
sequence: "\x1bA",
|
|
297
|
+
raw: "\x1bA",
|
|
298
|
+
})
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
test("function keys via keyInput events", async () => {
|
|
302
|
+
const resultF1 = await triggerInput("\x1bOP")
|
|
303
|
+
expect(resultF1).toMatchObject({
|
|
304
|
+
eventType: "press",
|
|
305
|
+
name: "f1",
|
|
306
|
+
ctrl: false,
|
|
307
|
+
meta: false,
|
|
308
|
+
shift: false,
|
|
309
|
+
option: false,
|
|
310
|
+
number: false,
|
|
311
|
+
sequence: "\x1bOP",
|
|
312
|
+
raw: "\x1bOP",
|
|
313
|
+
code: "OP",
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
const resultF1Alt = await triggerInput("\x1b[11~")
|
|
317
|
+
expect(resultF1Alt).toMatchObject({
|
|
318
|
+
eventType: "press",
|
|
319
|
+
name: "f1",
|
|
320
|
+
ctrl: false,
|
|
321
|
+
meta: false,
|
|
322
|
+
shift: false,
|
|
323
|
+
option: false,
|
|
324
|
+
number: false,
|
|
325
|
+
sequence: "\x1b[11~",
|
|
326
|
+
raw: "\x1b[11~",
|
|
327
|
+
code: "[11~",
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
const resultF12 = await triggerInput("\x1b[24~")
|
|
331
|
+
expect(resultF12).toMatchObject({
|
|
332
|
+
eventType: "press",
|
|
333
|
+
name: "f12",
|
|
334
|
+
ctrl: false,
|
|
335
|
+
meta: false,
|
|
336
|
+
shift: false,
|
|
337
|
+
option: false,
|
|
338
|
+
number: false,
|
|
339
|
+
sequence: "\x1b[24~",
|
|
340
|
+
raw: "\x1b[24~",
|
|
341
|
+
code: "[24~",
|
|
342
|
+
})
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
test("arrow keys via keyInput events", async () => {
|
|
346
|
+
const resultUp = await triggerInput("\x1b[A")
|
|
347
|
+
expect(resultUp).toMatchObject({
|
|
348
|
+
eventType: "press",
|
|
349
|
+
name: "up",
|
|
350
|
+
ctrl: false,
|
|
351
|
+
meta: false,
|
|
352
|
+
shift: false,
|
|
353
|
+
option: false,
|
|
354
|
+
number: false,
|
|
355
|
+
sequence: "\x1b[A",
|
|
356
|
+
raw: "\x1b[A",
|
|
357
|
+
code: "[A",
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
const resultDown = await triggerInput("\x1b[B")
|
|
361
|
+
expect(resultDown).toMatchObject({
|
|
362
|
+
eventType: "press",
|
|
363
|
+
name: "down",
|
|
364
|
+
ctrl: false,
|
|
365
|
+
meta: false,
|
|
366
|
+
shift: false,
|
|
367
|
+
option: false,
|
|
368
|
+
number: false,
|
|
369
|
+
sequence: "\x1b[B",
|
|
370
|
+
raw: "\x1b[B",
|
|
371
|
+
code: "[B",
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
const resultRight = await triggerInput("\x1b[C")
|
|
375
|
+
expect(resultRight).toMatchObject({
|
|
376
|
+
eventType: "press",
|
|
377
|
+
name: "right",
|
|
378
|
+
ctrl: false,
|
|
379
|
+
meta: false,
|
|
380
|
+
shift: false,
|
|
381
|
+
option: false,
|
|
382
|
+
number: false,
|
|
383
|
+
sequence: "\x1b[C",
|
|
384
|
+
raw: "\x1b[C",
|
|
385
|
+
code: "[C",
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
const resultLeft = await triggerInput("\x1b[D")
|
|
389
|
+
expect(resultLeft).toMatchObject({
|
|
390
|
+
eventType: "press",
|
|
391
|
+
name: "left",
|
|
392
|
+
ctrl: false,
|
|
393
|
+
meta: false,
|
|
394
|
+
shift: false,
|
|
395
|
+
option: false,
|
|
396
|
+
number: false,
|
|
397
|
+
sequence: "\x1b[D",
|
|
398
|
+
raw: "\x1b[D",
|
|
399
|
+
code: "[D",
|
|
400
|
+
})
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
test("navigation keys via keyInput events", async () => {
|
|
404
|
+
const resultHome = await triggerInput("\x1b[H")
|
|
405
|
+
expect(resultHome).toMatchObject({
|
|
406
|
+
eventType: "press",
|
|
407
|
+
name: "home",
|
|
408
|
+
ctrl: false,
|
|
409
|
+
meta: false,
|
|
410
|
+
shift: false,
|
|
411
|
+
option: false,
|
|
412
|
+
number: false,
|
|
413
|
+
sequence: "\x1b[H",
|
|
414
|
+
raw: "\x1b[H",
|
|
415
|
+
code: "[H",
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
const resultEnd = await triggerInput("\x1b[F")
|
|
419
|
+
expect(resultEnd).toMatchObject({
|
|
420
|
+
eventType: "press",
|
|
421
|
+
name: "end",
|
|
422
|
+
ctrl: false,
|
|
423
|
+
meta: false,
|
|
424
|
+
shift: false,
|
|
425
|
+
option: false,
|
|
426
|
+
number: false,
|
|
427
|
+
sequence: "\x1b[F",
|
|
428
|
+
raw: "\x1b[F",
|
|
429
|
+
code: "[F",
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
const resultPageUp = await triggerInput("\x1b[5~")
|
|
433
|
+
expect(resultPageUp).toMatchObject({
|
|
434
|
+
eventType: "press",
|
|
435
|
+
name: "pageup",
|
|
436
|
+
ctrl: false,
|
|
437
|
+
meta: false,
|
|
438
|
+
shift: false,
|
|
439
|
+
option: false,
|
|
440
|
+
number: false,
|
|
441
|
+
sequence: "\x1b[5~",
|
|
442
|
+
raw: "\x1b[5~",
|
|
443
|
+
code: "[5~",
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
const resultPageDown = await triggerInput("\x1b[6~")
|
|
447
|
+
expect(resultPageDown).toMatchObject({
|
|
448
|
+
eventType: "press",
|
|
449
|
+
name: "pagedown",
|
|
450
|
+
ctrl: false,
|
|
451
|
+
meta: false,
|
|
452
|
+
shift: false,
|
|
453
|
+
option: false,
|
|
454
|
+
number: false,
|
|
455
|
+
sequence: "\x1b[6~",
|
|
456
|
+
raw: "\x1b[6~",
|
|
457
|
+
code: "[6~",
|
|
458
|
+
})
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
test("modifier combinations via keyInput events", async () => {
|
|
462
|
+
const resultShiftUp = await triggerInput("\x1b[1;2A")
|
|
463
|
+
expect(resultShiftUp).toMatchObject({
|
|
464
|
+
eventType: "press",
|
|
465
|
+
name: "up",
|
|
466
|
+
ctrl: false,
|
|
467
|
+
meta: false,
|
|
468
|
+
shift: true,
|
|
469
|
+
option: false,
|
|
470
|
+
number: false,
|
|
471
|
+
sequence: "\x1b[1;2A",
|
|
472
|
+
raw: "\x1b[1;2A",
|
|
473
|
+
code: "[A",
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
const resultMetaAltUp = await triggerInput("\x1b[1;4A")
|
|
477
|
+
expect(resultMetaAltUp).toMatchObject({
|
|
478
|
+
eventType: "press",
|
|
479
|
+
name: "up",
|
|
480
|
+
ctrl: false,
|
|
481
|
+
meta: true,
|
|
482
|
+
shift: true,
|
|
483
|
+
option: true,
|
|
484
|
+
number: false,
|
|
485
|
+
sequence: "\x1b[1;4A",
|
|
486
|
+
raw: "\x1b[1;4A",
|
|
487
|
+
code: "[A",
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
const resultAllModsUp = await triggerInput("\x1b[1;8A")
|
|
491
|
+
expect(resultAllModsUp).toMatchObject({
|
|
492
|
+
eventType: "press",
|
|
493
|
+
name: "up",
|
|
494
|
+
ctrl: true,
|
|
495
|
+
meta: true,
|
|
496
|
+
shift: true,
|
|
497
|
+
option: true,
|
|
498
|
+
number: false,
|
|
499
|
+
sequence: "\x1b[1;8A",
|
|
500
|
+
raw: "\x1b[1;8A",
|
|
501
|
+
code: "[A",
|
|
502
|
+
})
|
|
503
|
+
})
|
|
504
|
+
|
|
505
|
+
test("delete key via keyInput events", async () => {
|
|
506
|
+
const resultDelete = await triggerInput("\x1b[3~")
|
|
507
|
+
expect(resultDelete).toMatchObject({
|
|
508
|
+
eventType: "press",
|
|
509
|
+
name: "delete",
|
|
510
|
+
ctrl: false,
|
|
511
|
+
meta: false,
|
|
512
|
+
shift: false,
|
|
513
|
+
option: false,
|
|
514
|
+
number: false,
|
|
515
|
+
sequence: "\x1b[3~",
|
|
516
|
+
raw: "\x1b[3~",
|
|
517
|
+
code: "[3~",
|
|
518
|
+
})
|
|
519
|
+
})
|
|
520
|
+
|
|
521
|
+
test("Buffer input via keyInput events", async () => {
|
|
522
|
+
// Test with Buffer input by emitting buffer data directly
|
|
523
|
+
const result = await new Promise<KeyEvent>((resolve) => {
|
|
524
|
+
const onKeypress = (parsedKey: KeyEvent) => {
|
|
525
|
+
currentRenderer.keyInput.removeListener("keypress", onKeypress)
|
|
526
|
+
resolve(parsedKey)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
currentRenderer.keyInput.on("keypress", onKeypress)
|
|
530
|
+
currentRenderer.stdin.emit("data", Buffer.from("a"))
|
|
531
|
+
})
|
|
532
|
+
|
|
533
|
+
expect(result).toMatchObject({
|
|
534
|
+
eventType: "press",
|
|
535
|
+
name: "a",
|
|
536
|
+
ctrl: false,
|
|
537
|
+
meta: false,
|
|
538
|
+
shift: false,
|
|
539
|
+
option: false,
|
|
540
|
+
number: false,
|
|
541
|
+
sequence: "a",
|
|
542
|
+
raw: "a",
|
|
543
|
+
})
|
|
544
|
+
})
|
|
545
|
+
|
|
546
|
+
test("special characters via keyInput events", async () => {
|
|
547
|
+
const resultExclamation = await triggerInput("!")
|
|
548
|
+
expect(resultExclamation).toMatchObject({
|
|
549
|
+
eventType: "press",
|
|
550
|
+
name: "!",
|
|
551
|
+
ctrl: false,
|
|
552
|
+
meta: false,
|
|
553
|
+
shift: false,
|
|
554
|
+
option: false,
|
|
555
|
+
number: false,
|
|
556
|
+
sequence: "!",
|
|
557
|
+
raw: "!",
|
|
558
|
+
})
|
|
559
|
+
|
|
560
|
+
const resultAt = await triggerInput("@")
|
|
561
|
+
expect(resultAt).toMatchObject({
|
|
562
|
+
eventType: "press",
|
|
563
|
+
name: "@",
|
|
564
|
+
ctrl: false,
|
|
565
|
+
meta: false,
|
|
566
|
+
shift: false,
|
|
567
|
+
option: false,
|
|
568
|
+
number: false,
|
|
569
|
+
sequence: "@",
|
|
570
|
+
raw: "@",
|
|
571
|
+
})
|
|
572
|
+
})
|
|
573
|
+
|
|
574
|
+
test("meta space and escape combinations via keyInput events", async () => {
|
|
575
|
+
const resultMetaSpace = await triggerInput("\x1b ")
|
|
576
|
+
expect(resultMetaSpace).toMatchObject({
|
|
577
|
+
eventType: "press",
|
|
578
|
+
name: "space",
|
|
579
|
+
ctrl: false,
|
|
580
|
+
meta: true,
|
|
581
|
+
shift: false,
|
|
582
|
+
option: false,
|
|
583
|
+
number: false,
|
|
584
|
+
sequence: "\x1b ",
|
|
585
|
+
raw: "\x1b ",
|
|
586
|
+
})
|
|
587
|
+
|
|
588
|
+
const resultDoubleEscape = await triggerInput("\x1b\x1b")
|
|
589
|
+
expect(resultDoubleEscape).toMatchObject({
|
|
590
|
+
eventType: "press",
|
|
591
|
+
name: "escape",
|
|
592
|
+
ctrl: false,
|
|
593
|
+
meta: true,
|
|
594
|
+
shift: false,
|
|
595
|
+
option: false,
|
|
596
|
+
number: false,
|
|
597
|
+
sequence: "\x1b\x1b",
|
|
598
|
+
raw: "\x1b\x1b",
|
|
599
|
+
})
|
|
600
|
+
})
|
|
601
|
+
|
|
602
|
+
// ===== KITTY KEYBOARD PROTOCOL INTEGRATION TESTS =====
|
|
603
|
+
|
|
604
|
+
test("Kitty keyboard basic key via keyInput events", async () => {
|
|
605
|
+
const result = await triggerKittyInput("\x1b[97u")
|
|
606
|
+
expect(result).toMatchObject({
|
|
607
|
+
eventType: "press",
|
|
608
|
+
name: "a",
|
|
609
|
+
ctrl: false,
|
|
610
|
+
meta: false,
|
|
611
|
+
shift: false,
|
|
612
|
+
option: false,
|
|
613
|
+
number: false,
|
|
614
|
+
sequence: "a",
|
|
615
|
+
raw: "\x1b[97u",
|
|
616
|
+
super: false,
|
|
617
|
+
hyper: false,
|
|
618
|
+
capsLock: false,
|
|
619
|
+
numLock: false,
|
|
620
|
+
})
|
|
621
|
+
})
|
|
622
|
+
|
|
623
|
+
test("Kitty keyboard shift+a via keyInput events", async () => {
|
|
624
|
+
const result = await triggerKittyInput("\x1b[97:65;2u")
|
|
625
|
+
expect(result).toMatchObject({
|
|
626
|
+
eventType: "press",
|
|
627
|
+
name: "a",
|
|
628
|
+
ctrl: false,
|
|
629
|
+
meta: false,
|
|
630
|
+
shift: true,
|
|
631
|
+
option: false,
|
|
632
|
+
number: false,
|
|
633
|
+
sequence: "A",
|
|
634
|
+
raw: "\x1b[97:65;2u",
|
|
635
|
+
super: false,
|
|
636
|
+
hyper: false,
|
|
637
|
+
capsLock: false,
|
|
638
|
+
numLock: false,
|
|
639
|
+
})
|
|
640
|
+
})
|
|
641
|
+
|
|
642
|
+
test("Kitty keyboard ctrl+a via keyInput events", async () => {
|
|
643
|
+
const result = await triggerKittyInput("\x1b[97;5u")
|
|
644
|
+
expect(result).toMatchObject({
|
|
645
|
+
eventType: "press",
|
|
646
|
+
name: "a",
|
|
647
|
+
ctrl: true,
|
|
648
|
+
meta: false,
|
|
649
|
+
shift: false,
|
|
650
|
+
option: false,
|
|
651
|
+
number: false,
|
|
652
|
+
sequence: "a",
|
|
653
|
+
raw: "\x1b[97;5u",
|
|
654
|
+
super: false,
|
|
655
|
+
hyper: false,
|
|
656
|
+
capsLock: false,
|
|
657
|
+
numLock: false,
|
|
658
|
+
})
|
|
659
|
+
})
|
|
660
|
+
|
|
661
|
+
test("Kitty keyboard alt+a via keyInput events", async () => {
|
|
662
|
+
const result = await triggerKittyInput("\x1b[97;3u")
|
|
663
|
+
expect(result).toMatchObject({
|
|
664
|
+
eventType: "press",
|
|
665
|
+
name: "a",
|
|
666
|
+
ctrl: false,
|
|
667
|
+
meta: true,
|
|
668
|
+
shift: false,
|
|
669
|
+
option: true,
|
|
670
|
+
number: false,
|
|
671
|
+
sequence: "a",
|
|
672
|
+
raw: "\x1b[97;3u",
|
|
673
|
+
super: false,
|
|
674
|
+
hyper: false,
|
|
675
|
+
capsLock: false,
|
|
676
|
+
numLock: false,
|
|
677
|
+
})
|
|
678
|
+
})
|
|
679
|
+
|
|
680
|
+
test("Kitty keyboard function key via keyInput events", async () => {
|
|
681
|
+
const result = await triggerKittyInput("\x1b[57364u")
|
|
682
|
+
expect(result).toMatchObject({
|
|
683
|
+
eventType: "press",
|
|
684
|
+
name: "f1",
|
|
685
|
+
ctrl: false,
|
|
686
|
+
meta: false,
|
|
687
|
+
shift: false,
|
|
688
|
+
option: false,
|
|
689
|
+
number: false,
|
|
690
|
+
sequence: "\x1b[57364u",
|
|
691
|
+
raw: "\x1b[57364u",
|
|
692
|
+
code: "[57364u",
|
|
693
|
+
super: false,
|
|
694
|
+
hyper: false,
|
|
695
|
+
capsLock: false,
|
|
696
|
+
numLock: false,
|
|
697
|
+
})
|
|
698
|
+
})
|
|
699
|
+
|
|
700
|
+
test("Kitty keyboard arrow key via keyInput events", async () => {
|
|
701
|
+
const result = await triggerKittyInput("\x1b[57352u")
|
|
702
|
+
expect(result).toMatchObject({
|
|
703
|
+
eventType: "press",
|
|
704
|
+
name: "up",
|
|
705
|
+
ctrl: false,
|
|
706
|
+
meta: false,
|
|
707
|
+
shift: false,
|
|
708
|
+
option: false,
|
|
709
|
+
number: false,
|
|
710
|
+
sequence: "\x1b[57352u",
|
|
711
|
+
raw: "\x1b[57352u",
|
|
712
|
+
code: "[57352u",
|
|
713
|
+
super: false,
|
|
714
|
+
hyper: false,
|
|
715
|
+
capsLock: false,
|
|
716
|
+
numLock: false,
|
|
717
|
+
})
|
|
718
|
+
})
|
|
719
|
+
|
|
720
|
+
test("Kitty keyboard shift+space via keyInput events", async () => {
|
|
721
|
+
const result = await triggerKittyInput("\x1b[32;2u")
|
|
722
|
+
expect(result).toMatchObject({
|
|
723
|
+
eventType: "press",
|
|
724
|
+
name: " ",
|
|
725
|
+
ctrl: false,
|
|
726
|
+
meta: false,
|
|
727
|
+
shift: true,
|
|
728
|
+
option: false,
|
|
729
|
+
number: false,
|
|
730
|
+
sequence: " ",
|
|
731
|
+
raw: "\x1b[32;2u",
|
|
732
|
+
super: false,
|
|
733
|
+
hyper: false,
|
|
734
|
+
capsLock: false,
|
|
735
|
+
numLock: false,
|
|
736
|
+
})
|
|
737
|
+
})
|
|
738
|
+
|
|
739
|
+
test("Kitty keyboard event types via keyInput events", async () => {
|
|
740
|
+
// Press event (explicit)
|
|
741
|
+
const pressExplicit = await triggerKittyInput("\x1b[97;1:1u")
|
|
742
|
+
expect(pressExplicit).toMatchObject({
|
|
743
|
+
eventType: "press",
|
|
744
|
+
name: "a",
|
|
745
|
+
ctrl: false,
|
|
746
|
+
meta: false,
|
|
747
|
+
shift: false,
|
|
748
|
+
option: false,
|
|
749
|
+
number: false,
|
|
750
|
+
sequence: "a",
|
|
751
|
+
raw: "\x1b[97;1:1u",
|
|
752
|
+
super: false,
|
|
753
|
+
hyper: false,
|
|
754
|
+
capsLock: false,
|
|
755
|
+
numLock: false,
|
|
756
|
+
})
|
|
757
|
+
|
|
758
|
+
// Press event (default when no event type specified)
|
|
759
|
+
const pressDefault = await triggerKittyInput("\x1b[97u")
|
|
760
|
+
expect(pressDefault).toMatchObject({
|
|
761
|
+
eventType: "press",
|
|
762
|
+
name: "a",
|
|
763
|
+
ctrl: false,
|
|
764
|
+
meta: false,
|
|
765
|
+
shift: false,
|
|
766
|
+
option: false,
|
|
767
|
+
number: false,
|
|
768
|
+
sequence: "a",
|
|
769
|
+
raw: "\x1b[97u",
|
|
770
|
+
super: false,
|
|
771
|
+
hyper: false,
|
|
772
|
+
capsLock: false,
|
|
773
|
+
numLock: false,
|
|
774
|
+
})
|
|
775
|
+
|
|
776
|
+
// Press event (modifier without event type)
|
|
777
|
+
const pressWithModifier = await triggerKittyInput("\x1b[97;5u") // Ctrl+a
|
|
778
|
+
expect(pressWithModifier).toMatchObject({
|
|
779
|
+
eventType: "press",
|
|
780
|
+
name: "a",
|
|
781
|
+
ctrl: true,
|
|
782
|
+
meta: false,
|
|
783
|
+
shift: false,
|
|
784
|
+
option: false,
|
|
785
|
+
number: false,
|
|
786
|
+
sequence: "a",
|
|
787
|
+
raw: "\x1b[97;5u",
|
|
788
|
+
super: false,
|
|
789
|
+
hyper: false,
|
|
790
|
+
capsLock: false,
|
|
791
|
+
numLock: false,
|
|
792
|
+
})
|
|
793
|
+
|
|
794
|
+
// Repeat event (emitted as press with repeated=true)
|
|
795
|
+
const repeat = await triggerKittyInput("\x1b[97;1:2u")
|
|
796
|
+
expect(repeat).toMatchObject({
|
|
797
|
+
eventType: "press",
|
|
798
|
+
repeated: true,
|
|
799
|
+
name: "a",
|
|
800
|
+
ctrl: false,
|
|
801
|
+
meta: false,
|
|
802
|
+
shift: false,
|
|
803
|
+
option: false,
|
|
804
|
+
number: false,
|
|
805
|
+
sequence: "a",
|
|
806
|
+
raw: "\x1b[97;1:2u",
|
|
807
|
+
super: false,
|
|
808
|
+
hyper: false,
|
|
809
|
+
capsLock: false,
|
|
810
|
+
numLock: false,
|
|
811
|
+
})
|
|
812
|
+
|
|
813
|
+
// Release event
|
|
814
|
+
const release = await triggerKittyInput("\x1b[97;1:3u")
|
|
815
|
+
expect(release).toMatchObject({
|
|
816
|
+
eventType: "release",
|
|
817
|
+
name: "a",
|
|
818
|
+
ctrl: false,
|
|
819
|
+
meta: false,
|
|
820
|
+
shift: false,
|
|
821
|
+
option: false,
|
|
822
|
+
number: false,
|
|
823
|
+
sequence: "a",
|
|
824
|
+
raw: "\x1b[97;1:3u",
|
|
825
|
+
super: false,
|
|
826
|
+
hyper: false,
|
|
827
|
+
capsLock: false,
|
|
828
|
+
numLock: false,
|
|
829
|
+
})
|
|
830
|
+
|
|
831
|
+
// Repeat event with modifier (emitted as press with repeated=true)
|
|
832
|
+
const repeatWithCtrl = await triggerKittyInput("\x1b[97;5:2u")
|
|
833
|
+
expect(repeatWithCtrl).toMatchObject({
|
|
834
|
+
eventType: "press",
|
|
835
|
+
repeated: true,
|
|
836
|
+
name: "a",
|
|
837
|
+
ctrl: true,
|
|
838
|
+
meta: false,
|
|
839
|
+
shift: false,
|
|
840
|
+
option: false,
|
|
841
|
+
number: false,
|
|
842
|
+
sequence: "a",
|
|
843
|
+
raw: "\x1b[97;5:2u",
|
|
844
|
+
super: false,
|
|
845
|
+
hyper: false,
|
|
846
|
+
capsLock: false,
|
|
847
|
+
numLock: false,
|
|
848
|
+
})
|
|
849
|
+
|
|
850
|
+
// Release event with modifier
|
|
851
|
+
const releaseWithShift = await triggerKittyInput("\x1b[97;2:3u")
|
|
852
|
+
expect(releaseWithShift).toMatchObject({
|
|
853
|
+
eventType: "release",
|
|
854
|
+
name: "a",
|
|
855
|
+
ctrl: false,
|
|
856
|
+
meta: false,
|
|
857
|
+
shift: true,
|
|
858
|
+
option: false,
|
|
859
|
+
number: false,
|
|
860
|
+
sequence: "A",
|
|
861
|
+
raw: "\x1b[97;2:3u",
|
|
862
|
+
super: false,
|
|
863
|
+
hyper: false,
|
|
864
|
+
capsLock: false,
|
|
865
|
+
numLock: false,
|
|
866
|
+
})
|
|
867
|
+
})
|
|
868
|
+
|
|
869
|
+
test("Kitty keyboard with text via keyInput events", async () => {
|
|
870
|
+
const result = await triggerKittyInput("\x1b[97;1;97u")
|
|
871
|
+
expect(result).toMatchObject({
|
|
872
|
+
eventType: "press",
|
|
873
|
+
name: "a",
|
|
874
|
+
ctrl: false,
|
|
875
|
+
meta: false,
|
|
876
|
+
shift: false,
|
|
877
|
+
option: false,
|
|
878
|
+
number: false,
|
|
879
|
+
sequence: "a",
|
|
880
|
+
raw: "\x1b[97;1;97u",
|
|
881
|
+
super: false,
|
|
882
|
+
hyper: false,
|
|
883
|
+
capsLock: false,
|
|
884
|
+
numLock: false,
|
|
885
|
+
})
|
|
886
|
+
})
|
|
887
|
+
|
|
888
|
+
test("Kitty keyboard ctrl+shift+a via keyInput events", async () => {
|
|
889
|
+
const result = await triggerKittyInput("\x1b[97;6u")
|
|
890
|
+
expect(result).toMatchObject({
|
|
891
|
+
eventType: "press",
|
|
892
|
+
name: "a",
|
|
893
|
+
ctrl: true,
|
|
894
|
+
meta: false,
|
|
895
|
+
shift: true,
|
|
896
|
+
option: false,
|
|
897
|
+
number: false,
|
|
898
|
+
sequence: "A",
|
|
899
|
+
raw: "\x1b[97;6u",
|
|
900
|
+
super: false,
|
|
901
|
+
hyper: false,
|
|
902
|
+
capsLock: false,
|
|
903
|
+
numLock: false,
|
|
904
|
+
})
|
|
905
|
+
})
|
|
906
|
+
|
|
907
|
+
test("Kitty keyboard alt+shift+a via keyInput events", async () => {
|
|
908
|
+
const result = await triggerKittyInput("\x1b[97;4u")
|
|
909
|
+
expect(result).toMatchObject({
|
|
910
|
+
eventType: "press",
|
|
911
|
+
name: "a",
|
|
912
|
+
ctrl: false,
|
|
913
|
+
meta: true,
|
|
914
|
+
shift: true,
|
|
915
|
+
option: true,
|
|
916
|
+
number: false,
|
|
917
|
+
sequence: "A",
|
|
918
|
+
raw: "\x1b[97;4u",
|
|
919
|
+
super: false,
|
|
920
|
+
hyper: false,
|
|
921
|
+
capsLock: false,
|
|
922
|
+
numLock: false,
|
|
923
|
+
})
|
|
924
|
+
})
|
|
925
|
+
|
|
926
|
+
test("Kitty keyboard super+a via keyInput events", async () => {
|
|
927
|
+
const result = await triggerKittyInput("\x1b[97;9u") // modifier 9 - 1 = 8 = super
|
|
928
|
+
expect(result).toMatchObject({
|
|
929
|
+
eventType: "press",
|
|
930
|
+
name: "a",
|
|
931
|
+
ctrl: false,
|
|
932
|
+
meta: false,
|
|
933
|
+
shift: false,
|
|
934
|
+
option: false,
|
|
935
|
+
number: false,
|
|
936
|
+
sequence: "a",
|
|
937
|
+
raw: "\x1b[97;9u",
|
|
938
|
+
super: true,
|
|
939
|
+
hyper: false,
|
|
940
|
+
capsLock: false,
|
|
941
|
+
numLock: false,
|
|
942
|
+
})
|
|
943
|
+
})
|
|
944
|
+
|
|
945
|
+
test("Kitty keyboard hyper+a via keyInput events", async () => {
|
|
946
|
+
const result = await triggerKittyInput("\x1b[97;17u") // modifier 17 - 1 = 16 = hyper
|
|
947
|
+
expect(result).toMatchObject({
|
|
948
|
+
eventType: "press",
|
|
949
|
+
name: "a",
|
|
950
|
+
ctrl: false,
|
|
951
|
+
meta: false,
|
|
952
|
+
shift: false,
|
|
953
|
+
option: false,
|
|
954
|
+
number: false,
|
|
955
|
+
sequence: "a",
|
|
956
|
+
raw: "\x1b[97;17u",
|
|
957
|
+
super: false,
|
|
958
|
+
hyper: true,
|
|
959
|
+
capsLock: false,
|
|
960
|
+
numLock: false,
|
|
961
|
+
})
|
|
962
|
+
})
|
|
963
|
+
|
|
964
|
+
test("Kitty keyboard caps lock via keyInput events", async () => {
|
|
965
|
+
const result = await triggerKittyInput("\x1b[97;65u") // modifier 65 - 1 = 64 = caps lock
|
|
966
|
+
expect(result).toMatchObject({
|
|
967
|
+
eventType: "press",
|
|
968
|
+
name: "a",
|
|
969
|
+
ctrl: false,
|
|
970
|
+
meta: false,
|
|
971
|
+
shift: false,
|
|
972
|
+
option: false,
|
|
973
|
+
number: false,
|
|
974
|
+
sequence: "a",
|
|
975
|
+
raw: "\x1b[97;65u",
|
|
976
|
+
super: false,
|
|
977
|
+
hyper: false,
|
|
978
|
+
capsLock: true,
|
|
979
|
+
numLock: false,
|
|
980
|
+
})
|
|
981
|
+
})
|
|
982
|
+
|
|
983
|
+
test("Kitty keyboard num lock via keyInput events", async () => {
|
|
984
|
+
const result = await triggerKittyInput("\x1b[97;129u") // modifier 129 - 1 = 128 = num lock
|
|
985
|
+
expect(result).toMatchObject({
|
|
986
|
+
eventType: "press",
|
|
987
|
+
name: "a",
|
|
988
|
+
ctrl: false,
|
|
989
|
+
meta: false,
|
|
990
|
+
shift: false,
|
|
991
|
+
option: false,
|
|
992
|
+
number: false,
|
|
993
|
+
sequence: "a",
|
|
994
|
+
raw: "\x1b[97;129u",
|
|
995
|
+
super: false,
|
|
996
|
+
hyper: false,
|
|
997
|
+
capsLock: false,
|
|
998
|
+
numLock: true,
|
|
999
|
+
})
|
|
1000
|
+
})
|
|
1001
|
+
|
|
1002
|
+
test("Kitty keyboard unicode character via keyInput events", async () => {
|
|
1003
|
+
const result = await triggerKittyInput("\x1b[233u") // é
|
|
1004
|
+
expect(result).toMatchObject({
|
|
1005
|
+
eventType: "press",
|
|
1006
|
+
name: "é",
|
|
1007
|
+
ctrl: false,
|
|
1008
|
+
meta: false,
|
|
1009
|
+
shift: false,
|
|
1010
|
+
option: false,
|
|
1011
|
+
number: false,
|
|
1012
|
+
sequence: "é",
|
|
1013
|
+
raw: "\x1b[233u",
|
|
1014
|
+
super: false,
|
|
1015
|
+
hyper: false,
|
|
1016
|
+
capsLock: false,
|
|
1017
|
+
numLock: false,
|
|
1018
|
+
})
|
|
1019
|
+
})
|
|
1020
|
+
|
|
1021
|
+
test("Kitty keyboard emoji via keyInput events", async () => {
|
|
1022
|
+
const result = await triggerKittyInput("\x1b[128512u") // 😀
|
|
1023
|
+
expect(result).toMatchObject({
|
|
1024
|
+
eventType: "press",
|
|
1025
|
+
name: "😀",
|
|
1026
|
+
ctrl: false,
|
|
1027
|
+
meta: false,
|
|
1028
|
+
shift: false,
|
|
1029
|
+
option: false,
|
|
1030
|
+
number: false,
|
|
1031
|
+
sequence: "😀",
|
|
1032
|
+
raw: "\x1b[128512u",
|
|
1033
|
+
super: false,
|
|
1034
|
+
hyper: false,
|
|
1035
|
+
capsLock: false,
|
|
1036
|
+
numLock: false,
|
|
1037
|
+
})
|
|
1038
|
+
})
|
|
1039
|
+
|
|
1040
|
+
test("Kitty keyboard keypad keys via keyInput events", async () => {
|
|
1041
|
+
const kp0 = await triggerKittyInput("\x1b[57399u")
|
|
1042
|
+
expect(kp0?.name).toBe("kp0")
|
|
1043
|
+
|
|
1044
|
+
const kpEnter = await triggerKittyInput("\x1b[57414u")
|
|
1045
|
+
expect(kpEnter?.name).toBe("kpenter")
|
|
1046
|
+
})
|
|
1047
|
+
|
|
1048
|
+
test("Kitty keyboard media keys via keyInput events", async () => {
|
|
1049
|
+
const play = await triggerKittyInput("\x1b[57428u")
|
|
1050
|
+
expect(play?.name).toBe("mediaplay")
|
|
1051
|
+
|
|
1052
|
+
const volumeUp = await triggerKittyInput("\x1b[57439u")
|
|
1053
|
+
expect(volumeUp?.name).toBe("volumeup")
|
|
1054
|
+
})
|
|
1055
|
+
|
|
1056
|
+
test("Kitty keyboard modifier keys via keyInput events", async () => {
|
|
1057
|
+
const leftShift = await triggerKittyInput("\x1b[57441u")
|
|
1058
|
+
expect(leftShift?.name).toBe("leftshift")
|
|
1059
|
+
expect(leftShift?.eventType).toBe("press")
|
|
1060
|
+
|
|
1061
|
+
const rightCtrl = await triggerKittyInput("\x1b[57448u")
|
|
1062
|
+
expect(rightCtrl?.name).toBe("rightctrl")
|
|
1063
|
+
expect(rightCtrl?.eventType).toBe("press")
|
|
1064
|
+
})
|
|
1065
|
+
|
|
1066
|
+
test("Kitty keyboard function keys with event types via keyInput events", async () => {
|
|
1067
|
+
// F1 press
|
|
1068
|
+
const f1Press = await triggerKittyInput("\x1b[57364u")
|
|
1069
|
+
expect(f1Press.name).toBe("f1")
|
|
1070
|
+
expect(f1Press.eventType).toBe("press")
|
|
1071
|
+
expect(f1Press.super ?? false).toBe(false)
|
|
1072
|
+
expect(f1Press.hyper ?? false).toBe(false)
|
|
1073
|
+
expect(f1Press.capsLock ?? false).toBe(false)
|
|
1074
|
+
expect(f1Press.numLock ?? false).toBe(false)
|
|
1075
|
+
|
|
1076
|
+
// F1 repeat (emitted as press with repeated=true)
|
|
1077
|
+
const f1Repeat = await triggerKittyInput("\x1b[57364;1:2u")
|
|
1078
|
+
expect(f1Repeat.name).toBe("f1")
|
|
1079
|
+
expect(f1Repeat.eventType).toBe("press")
|
|
1080
|
+
expect((f1Repeat as any).repeated).toBe(true)
|
|
1081
|
+
expect(f1Repeat.super ?? false).toBe(false)
|
|
1082
|
+
expect(f1Repeat.hyper ?? false).toBe(false)
|
|
1083
|
+
expect(f1Repeat.capsLock ?? false).toBe(false)
|
|
1084
|
+
expect(f1Repeat.numLock ?? false).toBe(false)
|
|
1085
|
+
|
|
1086
|
+
// F1 release
|
|
1087
|
+
const f1Release = await triggerKittyInput("\x1b[57364;1:3u")
|
|
1088
|
+
expect(f1Release.name).toBe("f1")
|
|
1089
|
+
expect(f1Release.eventType).toBe("release")
|
|
1090
|
+
expect(f1Release.super ?? false).toBe(false)
|
|
1091
|
+
expect(f1Release.hyper ?? false).toBe(false)
|
|
1092
|
+
expect(f1Release.capsLock ?? false).toBe(false)
|
|
1093
|
+
expect(f1Release.numLock ?? false).toBe(false)
|
|
1094
|
+
})
|
|
1095
|
+
|
|
1096
|
+
test("Kitty keyboard arrow keys with event types via keyInput events", async () => {
|
|
1097
|
+
// Up arrow press
|
|
1098
|
+
const upPress = await triggerKittyInput("\x1b[57352u")
|
|
1099
|
+
expect(upPress.name).toBe("up")
|
|
1100
|
+
expect(upPress.eventType).toBe("press")
|
|
1101
|
+
expect(upPress.super ?? false).toBe(false)
|
|
1102
|
+
expect(upPress.hyper ?? false).toBe(false)
|
|
1103
|
+
expect(upPress.capsLock ?? false).toBe(false)
|
|
1104
|
+
expect(upPress.numLock ?? false).toBe(false)
|
|
1105
|
+
|
|
1106
|
+
// Up arrow repeat with Ctrl (emitted as press with repeated=true)
|
|
1107
|
+
const upRepeatCtrl = await triggerKittyInput("\x1b[57352;5:2u")
|
|
1108
|
+
expect(upRepeatCtrl.name).toBe("up")
|
|
1109
|
+
expect(upRepeatCtrl.ctrl).toBe(true)
|
|
1110
|
+
expect(upRepeatCtrl.eventType).toBe("press")
|
|
1111
|
+
expect((upRepeatCtrl as any).repeated).toBe(true)
|
|
1112
|
+
expect(upRepeatCtrl.super).toBe(false)
|
|
1113
|
+
expect(upRepeatCtrl.hyper).toBe(false)
|
|
1114
|
+
expect(upRepeatCtrl.capsLock).toBe(false)
|
|
1115
|
+
expect(upRepeatCtrl.numLock).toBe(false)
|
|
1116
|
+
|
|
1117
|
+
// Down arrow release
|
|
1118
|
+
const downRelease = await triggerKittyInput("\x1b[57353;1:3u")
|
|
1119
|
+
expect(downRelease.name).toBe("down")
|
|
1120
|
+
expect(downRelease.eventType).toBe("release")
|
|
1121
|
+
expect(downRelease.super).toBe(false)
|
|
1122
|
+
expect(downRelease.hyper).toBe(false)
|
|
1123
|
+
expect(downRelease.capsLock).toBe(false)
|
|
1124
|
+
expect(downRelease.numLock).toBe(false)
|
|
1125
|
+
})
|
|
1126
|
+
|
|
1127
|
+
// ===== MISSING UNIT TEST CASES INTEGRATION TESTS =====
|
|
1128
|
+
|
|
1129
|
+
test("high byte buffer handling via keyInput events", async () => {
|
|
1130
|
+
// Test with Buffer input by emitting buffer data directly
|
|
1131
|
+
const result = await new Promise<KeyEvent>((resolve) => {
|
|
1132
|
+
const onKeypress = (parsedKey: KeyEvent) => {
|
|
1133
|
+
currentRenderer.keyInput.removeListener("keypress", onKeypress)
|
|
1134
|
+
resolve(parsedKey)
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
currentRenderer.keyInput.on("keypress", onKeypress)
|
|
1138
|
+
// 128 + 32 = 160, should become \x1b + " "
|
|
1139
|
+
currentRenderer.stdin.emit("data", Buffer.from([160]))
|
|
1140
|
+
advanceCurrentClock()
|
|
1141
|
+
})
|
|
1142
|
+
|
|
1143
|
+
expect(result).toMatchObject({
|
|
1144
|
+
eventType: "press",
|
|
1145
|
+
name: "space",
|
|
1146
|
+
ctrl: false,
|
|
1147
|
+
meta: true,
|
|
1148
|
+
shift: false,
|
|
1149
|
+
option: false,
|
|
1150
|
+
number: false,
|
|
1151
|
+
sequence: "\x1b ",
|
|
1152
|
+
raw: "\x1b ",
|
|
1153
|
+
})
|
|
1154
|
+
})
|
|
1155
|
+
|
|
1156
|
+
test("high byte UTF-8 lead byte does not stall indefinitely", async () => {
|
|
1157
|
+
const result = await new Promise<KeyEvent>((resolve, reject) => {
|
|
1158
|
+
const timeout = setTimeout(() => {
|
|
1159
|
+
currentRenderer.keyInput.removeListener("keypress", onKeypress)
|
|
1160
|
+
reject(new Error("timed out waiting for high-byte keypress"))
|
|
1161
|
+
}, 300)
|
|
1162
|
+
|
|
1163
|
+
const onKeypress = (parsedKey: KeyEvent) => {
|
|
1164
|
+
clearTimeout(timeout)
|
|
1165
|
+
currentRenderer.keyInput.removeListener("keypress", onKeypress)
|
|
1166
|
+
resolve(parsedKey)
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
currentRenderer.keyInput.on("keypress", onKeypress)
|
|
1170
|
+
// 128 + 105 = 233, should become \x1b + "i"
|
|
1171
|
+
currentRenderer.stdin.emit("data", Buffer.from([233]))
|
|
1172
|
+
advanceCurrentClock()
|
|
1173
|
+
})
|
|
1174
|
+
|
|
1175
|
+
expect(result).toMatchObject({
|
|
1176
|
+
eventType: "press",
|
|
1177
|
+
name: "i",
|
|
1178
|
+
ctrl: false,
|
|
1179
|
+
meta: true,
|
|
1180
|
+
shift: false,
|
|
1181
|
+
option: false,
|
|
1182
|
+
number: false,
|
|
1183
|
+
sequence: "\x1bi",
|
|
1184
|
+
raw: "\x1bi",
|
|
1185
|
+
})
|
|
1186
|
+
})
|
|
1187
|
+
|
|
1188
|
+
test("empty input via keyInput events", async () => {
|
|
1189
|
+
const result = await triggerInput("")
|
|
1190
|
+
expect(result).toMatchObject({
|
|
1191
|
+
eventType: "press",
|
|
1192
|
+
name: "",
|
|
1193
|
+
ctrl: false,
|
|
1194
|
+
meta: false,
|
|
1195
|
+
shift: false,
|
|
1196
|
+
option: false,
|
|
1197
|
+
number: false,
|
|
1198
|
+
sequence: "",
|
|
1199
|
+
raw: "",
|
|
1200
|
+
})
|
|
1201
|
+
})
|
|
1202
|
+
|
|
1203
|
+
test("rxvt style arrow keys with modifiers via keyInput events", async () => {
|
|
1204
|
+
const resultShiftUp = await triggerInput("\x1b[a")
|
|
1205
|
+
expect(resultShiftUp).toMatchObject({
|
|
1206
|
+
eventType: "press",
|
|
1207
|
+
name: "up",
|
|
1208
|
+
ctrl: false,
|
|
1209
|
+
meta: false,
|
|
1210
|
+
shift: true,
|
|
1211
|
+
option: false,
|
|
1212
|
+
number: false,
|
|
1213
|
+
sequence: "\x1b[a",
|
|
1214
|
+
raw: "\x1b[a",
|
|
1215
|
+
code: "[a",
|
|
1216
|
+
})
|
|
1217
|
+
|
|
1218
|
+
const resultShiftInsert = await triggerInput("\x1b[2$")
|
|
1219
|
+
expect(resultShiftInsert).toMatchObject({
|
|
1220
|
+
eventType: "press",
|
|
1221
|
+
name: "insert",
|
|
1222
|
+
ctrl: false,
|
|
1223
|
+
meta: false,
|
|
1224
|
+
shift: true,
|
|
1225
|
+
option: false,
|
|
1226
|
+
number: false,
|
|
1227
|
+
sequence: "\x1b[2$",
|
|
1228
|
+
raw: "\x1b[2$",
|
|
1229
|
+
code: "[2$",
|
|
1230
|
+
})
|
|
1231
|
+
})
|
|
1232
|
+
|
|
1233
|
+
test("ctrl modifier keys via keyInput events", async () => {
|
|
1234
|
+
const resultCtrlUp = await triggerInput("\x1bOa")
|
|
1235
|
+
expect(resultCtrlUp).toMatchObject({
|
|
1236
|
+
eventType: "press",
|
|
1237
|
+
name: "up",
|
|
1238
|
+
ctrl: true,
|
|
1239
|
+
meta: false,
|
|
1240
|
+
shift: false,
|
|
1241
|
+
option: false,
|
|
1242
|
+
number: false,
|
|
1243
|
+
sequence: "\x1bOa",
|
|
1244
|
+
raw: "\x1bOa",
|
|
1245
|
+
code: "Oa",
|
|
1246
|
+
})
|
|
1247
|
+
|
|
1248
|
+
const resultCtrlInsert = await triggerInput("\x1b[2^")
|
|
1249
|
+
expect(resultCtrlInsert).toMatchObject({
|
|
1250
|
+
eventType: "press",
|
|
1251
|
+
name: "insert",
|
|
1252
|
+
ctrl: true,
|
|
1253
|
+
meta: false,
|
|
1254
|
+
shift: false,
|
|
1255
|
+
option: false,
|
|
1256
|
+
number: false,
|
|
1257
|
+
sequence: "\x1b[2^",
|
|
1258
|
+
raw: "\x1b[2^",
|
|
1259
|
+
code: "[2^",
|
|
1260
|
+
})
|
|
1261
|
+
})
|
|
1262
|
+
|
|
1263
|
+
test("modifier bit calculations and meta/alt relationship via keyInput events", async () => {
|
|
1264
|
+
// Super modifier is bit 8, so modifier value 9 = 8 + 1 (base)
|
|
1265
|
+
const superOnly = await triggerInput("\x1b[1;9A")
|
|
1266
|
+
expect(superOnly.name).toBe("up")
|
|
1267
|
+
expect(superOnly.meta).toBe(false)
|
|
1268
|
+
expect(superOnly.ctrl).toBe(false)
|
|
1269
|
+
expect(superOnly.shift).toBe(false)
|
|
1270
|
+
expect(superOnly.option).toBe(false)
|
|
1271
|
+
expect((superOnly as any).super).toBe(true)
|
|
1272
|
+
expect((superOnly as any).hyper).toBe(false)
|
|
1273
|
+
|
|
1274
|
+
// Alt/Option modifier is bit 1 (value 2), so modifier value 3 = 2 + 1
|
|
1275
|
+
const altOnly = await triggerInput("\x1b[1;3A")
|
|
1276
|
+
expect(altOnly.name).toBe("up")
|
|
1277
|
+
expect(altOnly.meta).toBe(true) // Alt sets meta flag (by design)
|
|
1278
|
+
expect(altOnly.option).toBe(true)
|
|
1279
|
+
expect(altOnly.ctrl).toBe(false)
|
|
1280
|
+
expect(altOnly.shift).toBe(false)
|
|
1281
|
+
|
|
1282
|
+
// Ctrl modifier is bit 2 (value 4), so modifier value 5 = 4 + 1
|
|
1283
|
+
const ctrlOnly = await triggerInput("\x1b[1;5A")
|
|
1284
|
+
expect(ctrlOnly.name).toBe("up")
|
|
1285
|
+
expect(ctrlOnly.ctrl).toBe(true)
|
|
1286
|
+
expect(ctrlOnly.meta).toBe(false)
|
|
1287
|
+
expect(ctrlOnly.shift).toBe(false)
|
|
1288
|
+
expect(ctrlOnly.option).toBe(false)
|
|
1289
|
+
|
|
1290
|
+
// Shift modifier is bit 0 (value 1), so modifier value 2 = 1 + 1
|
|
1291
|
+
const shiftOnly = await triggerInput("\x1b[1;2A")
|
|
1292
|
+
expect(shiftOnly.name).toBe("up")
|
|
1293
|
+
expect(shiftOnly.shift).toBe(true)
|
|
1294
|
+
expect(shiftOnly.ctrl).toBe(false)
|
|
1295
|
+
expect(shiftOnly.meta).toBe(false)
|
|
1296
|
+
expect(shiftOnly.option).toBe(false)
|
|
1297
|
+
|
|
1298
|
+
// Combined modifiers
|
|
1299
|
+
// Ctrl+Super = 4 + 8 = 12, so modifier value 13 = 12 + 1
|
|
1300
|
+
const ctrlSuper = await triggerInput("\x1b[1;13A")
|
|
1301
|
+
expect(ctrlSuper.name).toBe("up")
|
|
1302
|
+
expect(ctrlSuper.ctrl).toBe(true)
|
|
1303
|
+
expect(ctrlSuper.meta).toBe(false)
|
|
1304
|
+
expect(ctrlSuper.shift).toBe(false)
|
|
1305
|
+
expect(ctrlSuper.option).toBe(false)
|
|
1306
|
+
expect((ctrlSuper as any).super).toBe(true)
|
|
1307
|
+
expect((ctrlSuper as any).hyper).toBe(false)
|
|
1308
|
+
|
|
1309
|
+
// Shift+Alt = 1 + 2 = 3, so modifier value 4 = 3 + 1
|
|
1310
|
+
const shiftAlt = await triggerInput("\x1b[1;4A")
|
|
1311
|
+
expect(shiftAlt.name).toBe("up")
|
|
1312
|
+
expect(shiftAlt.shift).toBe(true)
|
|
1313
|
+
expect(shiftAlt.option).toBe(true)
|
|
1314
|
+
expect(shiftAlt.meta).toBe(true) // Alt sets meta flag
|
|
1315
|
+
expect(shiftAlt.ctrl).toBe(false)
|
|
1316
|
+
|
|
1317
|
+
// All modifiers: Shift(1) + Alt(2) + Ctrl(4) + Meta(8) = 15, so modifier value 16 = 15 + 1
|
|
1318
|
+
const allMods = await triggerInput("\x1b[1;16A")
|
|
1319
|
+
expect(allMods.name).toBe("up")
|
|
1320
|
+
expect(allMods.shift).toBe(true)
|
|
1321
|
+
expect(allMods.option).toBe(true)
|
|
1322
|
+
expect(allMods.ctrl).toBe(true)
|
|
1323
|
+
expect(allMods.meta).toBe(true)
|
|
1324
|
+
})
|
|
1325
|
+
|
|
1326
|
+
test("modifier combinations with function keys via keyInput events", async () => {
|
|
1327
|
+
// Ctrl+F1
|
|
1328
|
+
const ctrlF1 = await triggerInput("\x1b[11;5~")
|
|
1329
|
+
expect(ctrlF1.name).toBe("f1")
|
|
1330
|
+
expect(ctrlF1.ctrl).toBe(true)
|
|
1331
|
+
expect(ctrlF1.meta).toBe(false)
|
|
1332
|
+
expect(ctrlF1.eventType).toBe("press")
|
|
1333
|
+
|
|
1334
|
+
// Super+F1
|
|
1335
|
+
const superF1 = await triggerInput("\x1b[11;9~")
|
|
1336
|
+
expect(superF1.name).toBe("f1")
|
|
1337
|
+
expect(superF1.meta).toBe(false)
|
|
1338
|
+
expect(superF1.ctrl).toBe(false)
|
|
1339
|
+
expect(superF1.super).toBe(true)
|
|
1340
|
+
expect(superF1.hyper).toBe(false)
|
|
1341
|
+
expect(superF1.eventType).toBe("press")
|
|
1342
|
+
|
|
1343
|
+
// Shift+Ctrl+F1
|
|
1344
|
+
const shiftCtrlF1 = await triggerInput("\x1b[11;6~")
|
|
1345
|
+
expect(shiftCtrlF1.name).toBe("f1")
|
|
1346
|
+
expect(shiftCtrlF1.shift).toBe(true)
|
|
1347
|
+
expect(shiftCtrlF1.ctrl).toBe(true)
|
|
1348
|
+
expect(shiftCtrlF1.meta).toBe(false)
|
|
1349
|
+
expect(shiftCtrlF1.eventType).toBe("press")
|
|
1350
|
+
})
|
|
1351
|
+
|
|
1352
|
+
test("regular parsing always defaults to press event type via keyInput events", async () => {
|
|
1353
|
+
// Test various regular key sequences to ensure they all default to "press"
|
|
1354
|
+
const keys = [
|
|
1355
|
+
"a",
|
|
1356
|
+
"A",
|
|
1357
|
+
"1",
|
|
1358
|
+
"!",
|
|
1359
|
+
"\t",
|
|
1360
|
+
"\r",
|
|
1361
|
+
"\n",
|
|
1362
|
+
" ",
|
|
1363
|
+
"\x1b",
|
|
1364
|
+
"\x01", // Ctrl+A
|
|
1365
|
+
"\x1ba", // Alt+A
|
|
1366
|
+
"\x1b[A", // Up arrow
|
|
1367
|
+
"\x1b[11~", // F1
|
|
1368
|
+
"\x1b[1;2A", // Shift+Up
|
|
1369
|
+
"\x1b[3~", // Delete
|
|
1370
|
+
]
|
|
1371
|
+
|
|
1372
|
+
for (const keySeq of keys) {
|
|
1373
|
+
const result = await triggerInput(keySeq)
|
|
1374
|
+
expect(result.eventType).toBe("press")
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
// Test with Buffer input too
|
|
1378
|
+
const bufResult = await new Promise<KeyEvent>((resolve) => {
|
|
1379
|
+
const onKeypress = (parsedKey: KeyEvent) => {
|
|
1380
|
+
currentRenderer.keyInput.removeListener("keypress", onKeypress)
|
|
1381
|
+
resolve(parsedKey)
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
currentRenderer.keyInput.once("keypress", onKeypress)
|
|
1385
|
+
currentRenderer.stdin.emit("data", Buffer.from("x"))
|
|
1386
|
+
})
|
|
1387
|
+
expect(bufResult.eventType).toBe("press")
|
|
1388
|
+
})
|
|
1389
|
+
|
|
1390
|
+
test("nonAlphanumericKeys export validation", async () => {
|
|
1391
|
+
expect(Array.isArray(nonAlphanumericKeys)).toBe(true)
|
|
1392
|
+
expect(nonAlphanumericKeys.length).toBeGreaterThan(0)
|
|
1393
|
+
expect(nonAlphanumericKeys).toContain("up")
|
|
1394
|
+
expect(nonAlphanumericKeys).toContain("down")
|
|
1395
|
+
expect(nonAlphanumericKeys).toContain("f1")
|
|
1396
|
+
expect(nonAlphanumericKeys).toContain("backspace")
|
|
1397
|
+
expect(nonAlphanumericKeys).toContain("tab")
|
|
1398
|
+
expect(nonAlphanumericKeys).toContain("left")
|
|
1399
|
+
expect(nonAlphanumericKeys).toContain("right")
|
|
1400
|
+
})
|
|
1401
|
+
|
|
1402
|
+
test("ParsedKey type structure validation", async () => {
|
|
1403
|
+
const key: ParsedKey = {
|
|
1404
|
+
name: "test",
|
|
1405
|
+
ctrl: false,
|
|
1406
|
+
meta: false,
|
|
1407
|
+
shift: false,
|
|
1408
|
+
option: false,
|
|
1409
|
+
sequence: "test",
|
|
1410
|
+
raw: "test",
|
|
1411
|
+
number: false,
|
|
1412
|
+
eventType: "press",
|
|
1413
|
+
source: "raw",
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
expect(key).toHaveProperty("name")
|
|
1417
|
+
expect(key).toHaveProperty("ctrl")
|
|
1418
|
+
expect(key).toHaveProperty("meta")
|
|
1419
|
+
expect(key).toHaveProperty("shift")
|
|
1420
|
+
expect(key).toHaveProperty("option")
|
|
1421
|
+
expect(key).toHaveProperty("sequence")
|
|
1422
|
+
expect(key).toHaveProperty("raw")
|
|
1423
|
+
expect(key).toHaveProperty("number")
|
|
1424
|
+
|
|
1425
|
+
// Test that a key with code property works
|
|
1426
|
+
const keyWithCode: ParsedKey = {
|
|
1427
|
+
name: "up",
|
|
1428
|
+
ctrl: false,
|
|
1429
|
+
meta: false,
|
|
1430
|
+
shift: false,
|
|
1431
|
+
option: false,
|
|
1432
|
+
sequence: "\x1b[A",
|
|
1433
|
+
raw: "\x1b[A",
|
|
1434
|
+
number: false,
|
|
1435
|
+
code: "[A",
|
|
1436
|
+
eventType: "press",
|
|
1437
|
+
source: "raw",
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
expect(keyWithCode).toHaveProperty("code")
|
|
1441
|
+
expect(keyWithCode.code).toBe("[A")
|
|
1442
|
+
})
|
|
1443
|
+
|
|
1444
|
+
test("KeyEventType type validation", async () => {
|
|
1445
|
+
// Test that KeyEventType only allows valid values
|
|
1446
|
+
const validEventTypes: KeyEventType[] = ["press", "repeat", "release"]
|
|
1447
|
+
|
|
1448
|
+
for (const eventType of validEventTypes) {
|
|
1449
|
+
// This should compile without errors
|
|
1450
|
+
const mockKey: ParsedKey = {
|
|
1451
|
+
name: "test",
|
|
1452
|
+
ctrl: false,
|
|
1453
|
+
meta: false,
|
|
1454
|
+
shift: false,
|
|
1455
|
+
option: false,
|
|
1456
|
+
sequence: "test",
|
|
1457
|
+
raw: "test",
|
|
1458
|
+
number: false,
|
|
1459
|
+
eventType: eventType,
|
|
1460
|
+
source: "raw",
|
|
1461
|
+
}
|
|
1462
|
+
expect(mockKey.eventType).toBe(eventType)
|
|
1463
|
+
}
|
|
1464
|
+
})
|
|
1465
|
+
|
|
1466
|
+
// ===== CAPABILITY RESPONSE HANDLING TESTS =====
|
|
1467
|
+
|
|
1468
|
+
test("capability responses should not trigger keypress events", async () => {
|
|
1469
|
+
const keypresses: KeyEvent[] = []
|
|
1470
|
+
currentRenderer.keyInput.on("keypress", (event) => {
|
|
1471
|
+
keypresses.push(event)
|
|
1472
|
+
})
|
|
1473
|
+
|
|
1474
|
+
// Send various capability responses - none should trigger keypresses
|
|
1475
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b[?1016;2$y")) // DECRPM
|
|
1476
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b[1;2R")) // CPR
|
|
1477
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b[?62;c")) // DA1
|
|
1478
|
+
|
|
1479
|
+
// Wait for stdin parser timeout
|
|
1480
|
+
advanceCurrentClock()
|
|
1481
|
+
|
|
1482
|
+
expect(keypresses).toHaveLength(0)
|
|
1483
|
+
})
|
|
1484
|
+
|
|
1485
|
+
test("capability response followed by keypress", async () => {
|
|
1486
|
+
const keypresses: KeyEvent[] = []
|
|
1487
|
+
currentRenderer.keyInput.on("keypress", (event) => {
|
|
1488
|
+
keypresses.push(event)
|
|
1489
|
+
})
|
|
1490
|
+
|
|
1491
|
+
// Send capability response followed by 'a'
|
|
1492
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b[?1016;2$ya"))
|
|
1493
|
+
|
|
1494
|
+
// Wait for processing
|
|
1495
|
+
advanceCurrentClock()
|
|
1496
|
+
|
|
1497
|
+
expect(keypresses).toHaveLength(1)
|
|
1498
|
+
expect(keypresses[0].name).toBe("a")
|
|
1499
|
+
})
|
|
1500
|
+
|
|
1501
|
+
test("partial SGR mouse stays pending on timeout, completes when rest arrives", async () => {
|
|
1502
|
+
const keypresses: KeyEvent[] = []
|
|
1503
|
+
currentRenderer.keyInput.on("keypress", (event) => {
|
|
1504
|
+
keypresses.push(event)
|
|
1505
|
+
})
|
|
1506
|
+
|
|
1507
|
+
// Incomplete SGR mouse sequence; stays pending (not flushed on timeout).
|
|
1508
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b[<35;20"))
|
|
1509
|
+
|
|
1510
|
+
// Wait past native stdin parser timeout (20ms)
|
|
1511
|
+
advanceCurrentClock()
|
|
1512
|
+
expect(keypresses).toHaveLength(0)
|
|
1513
|
+
|
|
1514
|
+
// Completing the mouse sequence should not trigger keypress either
|
|
1515
|
+
currentRenderer.stdin.emit("data", Buffer.from(";5m"))
|
|
1516
|
+
advanceCurrentClock()
|
|
1517
|
+
expect(keypresses).toHaveLength(0)
|
|
1518
|
+
|
|
1519
|
+
// Normal key input still works after
|
|
1520
|
+
currentRenderer.stdin.emit("data", Buffer.from("x"))
|
|
1521
|
+
advanceCurrentClock()
|
|
1522
|
+
|
|
1523
|
+
expect(keypresses).toHaveLength(1)
|
|
1524
|
+
expect(keypresses[0].name).toBe("x")
|
|
1525
|
+
})
|
|
1526
|
+
|
|
1527
|
+
test("partial OSC flushed on timeout should not block later text", async () => {
|
|
1528
|
+
const keypresses: KeyEvent[] = []
|
|
1529
|
+
currentRenderer.keyInput.on("keypress", (event) => {
|
|
1530
|
+
keypresses.push(event)
|
|
1531
|
+
})
|
|
1532
|
+
|
|
1533
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b]52;c;"))
|
|
1534
|
+
advanceCurrentClock()
|
|
1535
|
+
expect(keypresses).toHaveLength(0)
|
|
1536
|
+
|
|
1537
|
+
currentRenderer.stdin.emit("data", Buffer.from("abc"))
|
|
1538
|
+
advanceCurrentClock()
|
|
1539
|
+
|
|
1540
|
+
expect(keypresses).toHaveLength(3)
|
|
1541
|
+
expect(keypresses.map((event) => event.name)).toEqual(["a", "b", "c"])
|
|
1542
|
+
})
|
|
1543
|
+
|
|
1544
|
+
test("partial OSC flushed on timeout should not block later escape sequences", async () => {
|
|
1545
|
+
const keypresses: KeyEvent[] = []
|
|
1546
|
+
currentRenderer.keyInput.on("keypress", (event) => {
|
|
1547
|
+
keypresses.push(event)
|
|
1548
|
+
})
|
|
1549
|
+
|
|
1550
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b]52;c;"))
|
|
1551
|
+
advanceCurrentClock()
|
|
1552
|
+
expect(keypresses).toHaveLength(0)
|
|
1553
|
+
|
|
1554
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b[A"))
|
|
1555
|
+
advanceCurrentClock()
|
|
1556
|
+
|
|
1557
|
+
expect(keypresses).toHaveLength(1)
|
|
1558
|
+
expect(keypresses[0].name).toBe("up")
|
|
1559
|
+
})
|
|
1560
|
+
|
|
1561
|
+
test("incomplete mouse input resets the timeout when more bytes arrive", async () => {
|
|
1562
|
+
const keypresses: KeyEvent[] = []
|
|
1563
|
+
currentRenderer.keyInput.on("keypress", (event) => {
|
|
1564
|
+
keypresses.push(event)
|
|
1565
|
+
})
|
|
1566
|
+
|
|
1567
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b[<35;20;"))
|
|
1568
|
+
advanceCurrentClock(19)
|
|
1569
|
+
currentRenderer.stdin.emit("data", Buffer.from("5"))
|
|
1570
|
+
advanceCurrentClock(19)
|
|
1571
|
+
|
|
1572
|
+
expect(keypresses).toHaveLength(0)
|
|
1573
|
+
|
|
1574
|
+
currentRenderer.stdin.emit("data", Buffer.from("m"))
|
|
1575
|
+
advanceCurrentClock()
|
|
1576
|
+
|
|
1577
|
+
expect(keypresses).toHaveLength(0)
|
|
1578
|
+
})
|
|
1579
|
+
|
|
1580
|
+
test("chunked XTVersion response should not trigger keypresses", async () => {
|
|
1581
|
+
const keypresses: KeyEvent[] = []
|
|
1582
|
+
currentRenderer.keyInput.on("keypress", (event) => {
|
|
1583
|
+
keypresses.push(event)
|
|
1584
|
+
})
|
|
1585
|
+
|
|
1586
|
+
// Send XTVersion in chunks (chunks arrive quickly, within stdin parser timeout)
|
|
1587
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1bP>|kit"))
|
|
1588
|
+
currentRenderer.stdin.emit("data", Buffer.from("ty(0.40"))
|
|
1589
|
+
currentRenderer.stdin.emit("data", Buffer.from(".1)\x1b\\"))
|
|
1590
|
+
|
|
1591
|
+
// Wait for stdin parser to process
|
|
1592
|
+
advanceCurrentClock()
|
|
1593
|
+
|
|
1594
|
+
expect(keypresses).toHaveLength(0)
|
|
1595
|
+
})
|
|
1596
|
+
|
|
1597
|
+
test("chunked XTVersion followed by keypress", async () => {
|
|
1598
|
+
const keypresses: KeyEvent[] = []
|
|
1599
|
+
currentRenderer.keyInput.on("keypress", (event) => {
|
|
1600
|
+
keypresses.push(event)
|
|
1601
|
+
})
|
|
1602
|
+
|
|
1603
|
+
// Send XTVersion in chunks followed by 'x'
|
|
1604
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1bP>|ghostty"))
|
|
1605
|
+
currentRenderer.stdin.emit("data", Buffer.from(" 1.1.3\x1b\\x"))
|
|
1606
|
+
|
|
1607
|
+
// Wait for processing
|
|
1608
|
+
advanceCurrentClock()
|
|
1609
|
+
|
|
1610
|
+
expect(keypresses).toHaveLength(1)
|
|
1611
|
+
expect(keypresses[0].name).toBe("x")
|
|
1612
|
+
})
|
|
1613
|
+
|
|
1614
|
+
test("chunked Kitty graphics response should not trigger keypresses", async () => {
|
|
1615
|
+
const keypresses: KeyEvent[] = []
|
|
1616
|
+
currentRenderer.keyInput.on("keypress", (event) => {
|
|
1617
|
+
keypresses.push(event)
|
|
1618
|
+
})
|
|
1619
|
+
|
|
1620
|
+
// Send Kitty graphics response in chunks (arriving quickly)
|
|
1621
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b_Gi=1;"))
|
|
1622
|
+
currentRenderer.stdin.emit("data", Buffer.from("EINVAL:"))
|
|
1623
|
+
currentRenderer.stdin.emit("data", Buffer.from("Zero width/height not allowed\x1b\\"))
|
|
1624
|
+
|
|
1625
|
+
// Wait for processing
|
|
1626
|
+
advanceCurrentClock()
|
|
1627
|
+
|
|
1628
|
+
expect(keypresses).toHaveLength(0)
|
|
1629
|
+
})
|
|
1630
|
+
|
|
1631
|
+
test("multiple DECRPM responses in sequence", async () => {
|
|
1632
|
+
const keypresses: KeyEvent[] = []
|
|
1633
|
+
currentRenderer.keyInput.on("keypress", (event) => {
|
|
1634
|
+
keypresses.push(event)
|
|
1635
|
+
})
|
|
1636
|
+
|
|
1637
|
+
// Simulate multiple DECRPM responses arriving together
|
|
1638
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b[?1016;2$y\x1b[?2027;0$y\x1b[?2031;2$y"))
|
|
1639
|
+
advanceCurrentClock()
|
|
1640
|
+
|
|
1641
|
+
expect(keypresses).toHaveLength(0)
|
|
1642
|
+
})
|
|
1643
|
+
|
|
1644
|
+
test("pixel resolution response should not trigger keypress", async () => {
|
|
1645
|
+
const keypresses: KeyEvent[] = []
|
|
1646
|
+
currentRenderer.keyInput.on("keypress", (event) => {
|
|
1647
|
+
keypresses.push(event)
|
|
1648
|
+
})
|
|
1649
|
+
|
|
1650
|
+
// Mark as waiting for resolution
|
|
1651
|
+
// @ts-expect-error - accessing private property for testing
|
|
1652
|
+
currentRenderer.waitingForPixelResolution = true
|
|
1653
|
+
|
|
1654
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b[4;720;1280t"))
|
|
1655
|
+
advanceCurrentClock()
|
|
1656
|
+
|
|
1657
|
+
expect(keypresses).toHaveLength(0)
|
|
1658
|
+
expect(currentRenderer.resolution).toEqual({ width: 1280, height: 720 })
|
|
1659
|
+
})
|
|
1660
|
+
|
|
1661
|
+
test("chunked pixel resolution response", async () => {
|
|
1662
|
+
const keypresses: KeyEvent[] = []
|
|
1663
|
+
currentRenderer.keyInput.on("keypress", (event) => {
|
|
1664
|
+
keypresses.push(event)
|
|
1665
|
+
})
|
|
1666
|
+
|
|
1667
|
+
// @ts-expect-error - accessing private property for testing
|
|
1668
|
+
currentRenderer.waitingForPixelResolution = true
|
|
1669
|
+
|
|
1670
|
+
// Send pixel resolution in chunks (arriving quickly)
|
|
1671
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b[4;72"))
|
|
1672
|
+
currentRenderer.stdin.emit("data", Buffer.from("0;1280t"))
|
|
1673
|
+
|
|
1674
|
+
// Wait for processing
|
|
1675
|
+
advanceCurrentClock()
|
|
1676
|
+
|
|
1677
|
+
expect(keypresses).toHaveLength(0)
|
|
1678
|
+
expect(currentRenderer.resolution).toEqual({ width: 1280, height: 720 })
|
|
1679
|
+
})
|
|
1680
|
+
|
|
1681
|
+
test("kitty full capability response arriving in realistic chunks", async () => {
|
|
1682
|
+
const keypresses: KeyEvent[] = []
|
|
1683
|
+
currentRenderer.keyInput.on("keypress", (event) => {
|
|
1684
|
+
keypresses.push(event)
|
|
1685
|
+
})
|
|
1686
|
+
|
|
1687
|
+
// Simulate how kitty might send its full response in a few chunks
|
|
1688
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b[?1016;2$y\x1b[?2027;0$y"))
|
|
1689
|
+
advanceCurrentClock(1)
|
|
1690
|
+
|
|
1691
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b[?2031;2$y\x1b[?1004;1$y\x1b[1;2R\x1b[1;3R"))
|
|
1692
|
+
advanceCurrentClock(1)
|
|
1693
|
+
|
|
1694
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1bP>|kitty(0."))
|
|
1695
|
+
advanceCurrentClock(1)
|
|
1696
|
+
|
|
1697
|
+
currentRenderer.stdin.emit("data", Buffer.from("40.1)\x1b\\\x1b_Gi=1;OK\x1b\\"))
|
|
1698
|
+
advanceCurrentClock(1)
|
|
1699
|
+
|
|
1700
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b[?62;c"))
|
|
1701
|
+
|
|
1702
|
+
// Wait for processing
|
|
1703
|
+
advanceCurrentClock()
|
|
1704
|
+
|
|
1705
|
+
expect(keypresses).toHaveLength(0)
|
|
1706
|
+
})
|
|
1707
|
+
|
|
1708
|
+
test("capability response interleaved with user input", async () => {
|
|
1709
|
+
const keypresses: KeyEvent[] = []
|
|
1710
|
+
currentRenderer.keyInput.on("keypress", (event) => {
|
|
1711
|
+
keypresses.push(event)
|
|
1712
|
+
})
|
|
1713
|
+
|
|
1714
|
+
// User types 'h'
|
|
1715
|
+
currentRenderer.stdin.emit("data", Buffer.from("h"))
|
|
1716
|
+
|
|
1717
|
+
// Capability response arrives
|
|
1718
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b[?1016;2$y"))
|
|
1719
|
+
|
|
1720
|
+
// User types 'e'
|
|
1721
|
+
currentRenderer.stdin.emit("data", Buffer.from("e"))
|
|
1722
|
+
|
|
1723
|
+
// More capability responses
|
|
1724
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1bP>|kitty(0.40.1)\x1b\\"))
|
|
1725
|
+
|
|
1726
|
+
// User types 'llo'
|
|
1727
|
+
currentRenderer.stdin.emit("data", Buffer.from("llo"))
|
|
1728
|
+
|
|
1729
|
+
// Wait for processing
|
|
1730
|
+
advanceCurrentClock()
|
|
1731
|
+
|
|
1732
|
+
// Should only have user keypresses
|
|
1733
|
+
expect(keypresses).toHaveLength(5)
|
|
1734
|
+
expect(keypresses.map((k) => k.name)).toEqual(["h", "e", "l", "l", "o"])
|
|
1735
|
+
})
|
|
1736
|
+
|
|
1737
|
+
test("delayed capability responses should be processed", async () => {
|
|
1738
|
+
const keypresses: KeyEvent[] = []
|
|
1739
|
+
currentRenderer.keyInput.on("keypress", (event) => {
|
|
1740
|
+
keypresses.push(event)
|
|
1741
|
+
})
|
|
1742
|
+
|
|
1743
|
+
// User input first
|
|
1744
|
+
currentRenderer.stdin.emit("data", Buffer.from("abc"))
|
|
1745
|
+
|
|
1746
|
+
// Late capability response (e.g., terminal was slow to respond)
|
|
1747
|
+
advanceCurrentClock(50)
|
|
1748
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b[?2027;2$y"))
|
|
1749
|
+
|
|
1750
|
+
// Wait for processing
|
|
1751
|
+
advanceCurrentClock()
|
|
1752
|
+
|
|
1753
|
+
// Should have user input but not capability
|
|
1754
|
+
expect(keypresses).toHaveLength(3)
|
|
1755
|
+
expect(keypresses.map((k) => k.name)).toEqual(["a", "b", "c"])
|
|
1756
|
+
})
|
|
1757
|
+
|
|
1758
|
+
test("delayed explicit-width CPR stays in response path while setup probe is active", async () => {
|
|
1759
|
+
const keypresses: KeyEvent[] = []
|
|
1760
|
+
currentRenderer.keyInput.on("keypress", (event) => {
|
|
1761
|
+
keypresses.push(event)
|
|
1762
|
+
})
|
|
1763
|
+
|
|
1764
|
+
// @ts-expect-error - accessing private helper for test coverage
|
|
1765
|
+
currentRenderer.updateStdinParserProtocolContext({ explicitWidthCprActive: true })
|
|
1766
|
+
|
|
1767
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b[1;2"))
|
|
1768
|
+
advanceCurrentClock()
|
|
1769
|
+
currentRenderer.stdin.emit("data", Buffer.from("R"))
|
|
1770
|
+
advanceCurrentClock()
|
|
1771
|
+
|
|
1772
|
+
expect(keypresses).toHaveLength(0)
|
|
1773
|
+
})
|
|
1774
|
+
|
|
1775
|
+
test("delayed DECRPM stays in response path while capability probing is active", async () => {
|
|
1776
|
+
const keypresses: KeyEvent[] = []
|
|
1777
|
+
currentRenderer.keyInput.on("keypress", (event) => {
|
|
1778
|
+
keypresses.push(event)
|
|
1779
|
+
})
|
|
1780
|
+
|
|
1781
|
+
// @ts-expect-error - accessing private helper for test coverage
|
|
1782
|
+
currentRenderer.updateStdinParserProtocolContext({ privateCapabilityRepliesActive: true })
|
|
1783
|
+
|
|
1784
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b[?1016;2$"))
|
|
1785
|
+
advanceCurrentClock()
|
|
1786
|
+
currentRenderer.stdin.emit("data", Buffer.from("y"))
|
|
1787
|
+
advanceCurrentClock()
|
|
1788
|
+
|
|
1789
|
+
expect(keypresses).toHaveLength(0)
|
|
1790
|
+
})
|
|
1791
|
+
|
|
1792
|
+
test("delayed pixel resolution response stays in response path while query is active", async () => {
|
|
1793
|
+
const keypresses: KeyEvent[] = []
|
|
1794
|
+
currentRenderer.keyInput.on("keypress", (event) => {
|
|
1795
|
+
keypresses.push(event)
|
|
1796
|
+
})
|
|
1797
|
+
|
|
1798
|
+
// @ts-expect-error - accessing private property for testing
|
|
1799
|
+
currentRenderer.waitingForPixelResolution = true
|
|
1800
|
+
// @ts-expect-error - accessing private helper for test coverage
|
|
1801
|
+
currentRenderer.updateStdinParserProtocolContext({ pixelResolutionQueryActive: true })
|
|
1802
|
+
|
|
1803
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b[4;1080;192"))
|
|
1804
|
+
advanceCurrentClock()
|
|
1805
|
+
currentRenderer.stdin.emit("data", Buffer.from("0t"))
|
|
1806
|
+
advanceCurrentClock()
|
|
1807
|
+
|
|
1808
|
+
expect(keypresses).toHaveLength(0)
|
|
1809
|
+
expect(currentRenderer.resolution).toEqual({ width: 1920, height: 1080 })
|
|
1810
|
+
})
|
|
1811
|
+
|
|
1812
|
+
test("vscode minimal capability response", async () => {
|
|
1813
|
+
const keypresses: KeyEvent[] = []
|
|
1814
|
+
currentRenderer.keyInput.on("keypress", (event) => {
|
|
1815
|
+
keypresses.push(event)
|
|
1816
|
+
})
|
|
1817
|
+
|
|
1818
|
+
// VSCode often sends just one DECRPM
|
|
1819
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b[?1016;2$y"))
|
|
1820
|
+
advanceCurrentClock()
|
|
1821
|
+
|
|
1822
|
+
expect(keypresses).toHaveLength(0)
|
|
1823
|
+
})
|
|
1824
|
+
|
|
1825
|
+
test("alacritty capability response sequence", async () => {
|
|
1826
|
+
const keypresses: KeyEvent[] = []
|
|
1827
|
+
currentRenderer.keyInput.on("keypress", (event) => {
|
|
1828
|
+
keypresses.push(event)
|
|
1829
|
+
})
|
|
1830
|
+
|
|
1831
|
+
// Simulate alacritty's response pattern
|
|
1832
|
+
const alacrittyResponse =
|
|
1833
|
+
"\x1b[?1016;0$y\x1b[?2027;0$y\x1b[?2031;0$y\x1b[?1004;2$y\x1b[?2004;2$y\x1b[?2026;2$y\x1b[1;1R\x1b[1;1R\x1b[?0u\x1b[?6c"
|
|
1834
|
+
currentRenderer.stdin.emit("data", Buffer.from(alacrittyResponse))
|
|
1835
|
+
advanceCurrentClock()
|
|
1836
|
+
|
|
1837
|
+
expect(keypresses).toHaveLength(0)
|
|
1838
|
+
})
|
|
1839
|
+
|
|
1840
|
+
test("focus and blur events", async () => {
|
|
1841
|
+
const events: string[] = []
|
|
1842
|
+
|
|
1843
|
+
currentRenderer.on("focus", () => {
|
|
1844
|
+
events.push("focus")
|
|
1845
|
+
})
|
|
1846
|
+
|
|
1847
|
+
currentRenderer.on("blur", () => {
|
|
1848
|
+
events.push("blur")
|
|
1849
|
+
})
|
|
1850
|
+
|
|
1851
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b[I"))
|
|
1852
|
+
advanceCurrentClock()
|
|
1853
|
+
|
|
1854
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b[O"))
|
|
1855
|
+
advanceCurrentClock()
|
|
1856
|
+
|
|
1857
|
+
expect(events).toEqual(["focus", "blur"])
|
|
1858
|
+
})
|
|
1859
|
+
|
|
1860
|
+
test("focus events should not trigger keypress", async () => {
|
|
1861
|
+
const keypresses: KeyEvent[] = []
|
|
1862
|
+
const focusEvents: string[] = []
|
|
1863
|
+
|
|
1864
|
+
currentRenderer.keyInput.on("keypress", (event) => {
|
|
1865
|
+
keypresses.push(event)
|
|
1866
|
+
})
|
|
1867
|
+
|
|
1868
|
+
currentRenderer.on("focus", () => {
|
|
1869
|
+
focusEvents.push("focus")
|
|
1870
|
+
})
|
|
1871
|
+
|
|
1872
|
+
currentRenderer.on("blur", () => {
|
|
1873
|
+
focusEvents.push("blur")
|
|
1874
|
+
})
|
|
1875
|
+
|
|
1876
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b[I"))
|
|
1877
|
+
advanceCurrentClock()
|
|
1878
|
+
currentRenderer.stdin.emit("data", Buffer.from("\x1b[O"))
|
|
1879
|
+
advanceCurrentClock()
|
|
1880
|
+
|
|
1881
|
+
expect(focusEvents).toEqual(["focus", "blur"])
|
|
1882
|
+
expect(keypresses).toHaveLength(0)
|
|
1883
|
+
})
|
|
1884
|
+
|
|
1885
|
+
describe("stdin routing", () => {
|
|
1886
|
+
test("mouse then key in one chunk", async () => {
|
|
1887
|
+
const { renderer, renderOnce, clock } = await createRoutingRenderer()
|
|
1888
|
+
try {
|
|
1889
|
+
const target = new MouseTarget(renderer, {
|
|
1890
|
+
id: "target-mouse-then-key",
|
|
1891
|
+
position: "absolute",
|
|
1892
|
+
left: 0,
|
|
1893
|
+
top: 0,
|
|
1894
|
+
width: renderer.width,
|
|
1895
|
+
height: renderer.height,
|
|
1896
|
+
})
|
|
1897
|
+
renderer.root.add(target)
|
|
1898
|
+
await renderOnce()
|
|
1899
|
+
|
|
1900
|
+
const keys: string[] = []
|
|
1901
|
+
let scrollCount = 0
|
|
1902
|
+
|
|
1903
|
+
renderer.keyInput.on("keypress", (event) => {
|
|
1904
|
+
keys.push(event.name)
|
|
1905
|
+
})
|
|
1906
|
+
|
|
1907
|
+
target.onMouseScroll = () => {
|
|
1908
|
+
scrollCount++
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
renderer.stdin.emit("data", Buffer.from("\x1b[<64;10;5Mx"))
|
|
1912
|
+
advanceClock(clock)
|
|
1913
|
+
|
|
1914
|
+
expect(scrollCount).toBe(1)
|
|
1915
|
+
expect(keys).toEqual(["x"])
|
|
1916
|
+
} finally {
|
|
1917
|
+
renderer.destroy()
|
|
1918
|
+
}
|
|
1919
|
+
})
|
|
1920
|
+
|
|
1921
|
+
test("key then mouse in one chunk", async () => {
|
|
1922
|
+
const { renderer, renderOnce, clock } = await createRoutingRenderer()
|
|
1923
|
+
try {
|
|
1924
|
+
const target = new MouseTarget(renderer, {
|
|
1925
|
+
id: "target-key-then-mouse",
|
|
1926
|
+
position: "absolute",
|
|
1927
|
+
left: 0,
|
|
1928
|
+
top: 0,
|
|
1929
|
+
width: renderer.width,
|
|
1930
|
+
height: renderer.height,
|
|
1931
|
+
})
|
|
1932
|
+
renderer.root.add(target)
|
|
1933
|
+
await renderOnce()
|
|
1934
|
+
|
|
1935
|
+
const keys: string[] = []
|
|
1936
|
+
let scrollCount = 0
|
|
1937
|
+
|
|
1938
|
+
renderer.keyInput.on("keypress", (event) => {
|
|
1939
|
+
keys.push(event.name)
|
|
1940
|
+
})
|
|
1941
|
+
|
|
1942
|
+
target.onMouseScroll = () => {
|
|
1943
|
+
scrollCount++
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
renderer.stdin.emit("data", Buffer.from("x\x1b[<64;10;5M"))
|
|
1947
|
+
advanceClock(clock)
|
|
1948
|
+
|
|
1949
|
+
expect(keys).toEqual(["x"])
|
|
1950
|
+
expect(scrollCount).toBe(1)
|
|
1951
|
+
} finally {
|
|
1952
|
+
renderer.destroy()
|
|
1953
|
+
}
|
|
1954
|
+
})
|
|
1955
|
+
|
|
1956
|
+
test("focus and key mixed in one chunk", async () => {
|
|
1957
|
+
const { renderer, clock } = await createRoutingRenderer()
|
|
1958
|
+
try {
|
|
1959
|
+
const events: string[] = []
|
|
1960
|
+
const keys: string[] = []
|
|
1961
|
+
|
|
1962
|
+
renderer.on("focus", () => {
|
|
1963
|
+
events.push("focus")
|
|
1964
|
+
})
|
|
1965
|
+
|
|
1966
|
+
renderer.keyInput.on("keypress", (event) => {
|
|
1967
|
+
keys.push(event.name)
|
|
1968
|
+
})
|
|
1969
|
+
|
|
1970
|
+
renderer.stdin.emit("data", Buffer.from("\x1b[Ix"))
|
|
1971
|
+
advanceClock(clock)
|
|
1972
|
+
|
|
1973
|
+
expect(events).toEqual(["focus"])
|
|
1974
|
+
expect(keys).toEqual(["x"])
|
|
1975
|
+
} finally {
|
|
1976
|
+
renderer.destroy()
|
|
1977
|
+
}
|
|
1978
|
+
})
|
|
1979
|
+
|
|
1980
|
+
test("focus and mouse mixed in one chunk", async () => {
|
|
1981
|
+
const { renderer, renderOnce, clock } = await createRoutingRenderer()
|
|
1982
|
+
try {
|
|
1983
|
+
const events: string[] = []
|
|
1984
|
+
let scrollCount = 0
|
|
1985
|
+
|
|
1986
|
+
const target = new MouseTarget(renderer, {
|
|
1987
|
+
id: "target-focus-then-mouse",
|
|
1988
|
+
position: "absolute",
|
|
1989
|
+
left: 0,
|
|
1990
|
+
top: 0,
|
|
1991
|
+
width: renderer.width,
|
|
1992
|
+
height: renderer.height,
|
|
1993
|
+
})
|
|
1994
|
+
renderer.root.add(target)
|
|
1995
|
+
await renderOnce()
|
|
1996
|
+
|
|
1997
|
+
renderer.on("focus", () => {
|
|
1998
|
+
events.push("focus")
|
|
1999
|
+
})
|
|
2000
|
+
|
|
2001
|
+
target.onMouseScroll = () => {
|
|
2002
|
+
scrollCount++
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
renderer.stdin.emit("data", Buffer.from("\x1b[I\x1b[<64;10;5M"))
|
|
2006
|
+
advanceClock(clock)
|
|
2007
|
+
|
|
2008
|
+
expect(events).toEqual(["focus"])
|
|
2009
|
+
expect(scrollCount).toBe(1)
|
|
2010
|
+
} finally {
|
|
2011
|
+
renderer.destroy()
|
|
2012
|
+
}
|
|
2013
|
+
})
|
|
2014
|
+
|
|
2015
|
+
test("mouse state resets when mouse mode toggles", async () => {
|
|
2016
|
+
const { renderer, renderOnce, clock } = await createRoutingRenderer()
|
|
2017
|
+
try {
|
|
2018
|
+
const target = new MouseTarget(renderer, {
|
|
2019
|
+
id: "target-mouse-toggle-reset",
|
|
2020
|
+
position: "absolute",
|
|
2021
|
+
left: 0,
|
|
2022
|
+
top: 0,
|
|
2023
|
+
width: renderer.width,
|
|
2024
|
+
height: renderer.height,
|
|
2025
|
+
})
|
|
2026
|
+
renderer.root.add(target)
|
|
2027
|
+
await renderOnce()
|
|
2028
|
+
|
|
2029
|
+
let moveCount = 0
|
|
2030
|
+
let dragCount = 0
|
|
2031
|
+
target.onMouseMove = () => {
|
|
2032
|
+
moveCount++
|
|
2033
|
+
}
|
|
2034
|
+
target.onMouseDrag = () => {
|
|
2035
|
+
dragCount++
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
renderer.stdin.emit("data", Buffer.from("\x1b[<0;1;1M"))
|
|
2039
|
+
advanceClock(clock)
|
|
2040
|
+
|
|
2041
|
+
renderer.useMouse = false
|
|
2042
|
+
renderer.useMouse = true
|
|
2043
|
+
|
|
2044
|
+
renderer.stdin.emit("data", Buffer.from("\x1b[<32;2;2M"))
|
|
2045
|
+
advanceClock(clock)
|
|
2046
|
+
|
|
2047
|
+
expect(moveCount).toBe(1)
|
|
2048
|
+
expect(dragCount).toBe(0)
|
|
2049
|
+
} finally {
|
|
2050
|
+
renderer.destroy()
|
|
2051
|
+
}
|
|
2052
|
+
})
|
|
2053
|
+
|
|
2054
|
+
test("mouse state resets on resize", async () => {
|
|
2055
|
+
const { renderer, renderOnce, resize, clock } = await createRoutingRenderer()
|
|
2056
|
+
try {
|
|
2057
|
+
const target = new MouseTarget(renderer, {
|
|
2058
|
+
id: "target-resize-reset",
|
|
2059
|
+
position: "absolute",
|
|
2060
|
+
left: 0,
|
|
2061
|
+
top: 0,
|
|
2062
|
+
width: renderer.width,
|
|
2063
|
+
height: renderer.height,
|
|
2064
|
+
})
|
|
2065
|
+
renderer.root.add(target)
|
|
2066
|
+
await renderOnce()
|
|
2067
|
+
|
|
2068
|
+
let moveCount = 0
|
|
2069
|
+
let dragCount = 0
|
|
2070
|
+
target.onMouseMove = () => {
|
|
2071
|
+
moveCount++
|
|
2072
|
+
}
|
|
2073
|
+
target.onMouseDrag = () => {
|
|
2074
|
+
dragCount++
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
renderer.stdin.emit("data", Buffer.from("\x1b[<0;1;1M"))
|
|
2078
|
+
advanceClock(clock)
|
|
2079
|
+
|
|
2080
|
+
resize(41, 20)
|
|
2081
|
+
await renderOnce()
|
|
2082
|
+
|
|
2083
|
+
renderer.stdin.emit("data", Buffer.from("\x1b[<32;2;2M"))
|
|
2084
|
+
advanceClock(clock)
|
|
2085
|
+
|
|
2086
|
+
expect(moveCount).toBe(1)
|
|
2087
|
+
expect(dragCount).toBe(0)
|
|
2088
|
+
} finally {
|
|
2089
|
+
renderer.destroy()
|
|
2090
|
+
}
|
|
2091
|
+
})
|
|
2092
|
+
|
|
2093
|
+
test("suspend resets parser state before resume", async () => {
|
|
2094
|
+
const { renderer, clock } = await createRoutingRenderer()
|
|
2095
|
+
|
|
2096
|
+
try {
|
|
2097
|
+
const events: Array<{ name: string; meta: boolean }> = []
|
|
2098
|
+
renderer.keyInput.on("keypress", (event) => {
|
|
2099
|
+
events.push({ name: event.name, meta: event.meta })
|
|
2100
|
+
})
|
|
2101
|
+
|
|
2102
|
+
renderer.stdin.emit("data", Buffer.from("\x1b["))
|
|
2103
|
+
advanceClock(clock, 5)
|
|
2104
|
+
|
|
2105
|
+
renderer.suspend()
|
|
2106
|
+
renderer.resume()
|
|
2107
|
+
|
|
2108
|
+
renderer.stdin.emit("data", Buffer.from("x"))
|
|
2109
|
+
advanceClock(clock)
|
|
2110
|
+
|
|
2111
|
+
expect(events).toEqual([{ name: "x", meta: false }])
|
|
2112
|
+
} finally {
|
|
2113
|
+
renderer.destroy()
|
|
2114
|
+
}
|
|
2115
|
+
})
|
|
2116
|
+
|
|
2117
|
+
test("streams large paste bodies without dropping them and resumes afterward", async () => {
|
|
2118
|
+
const { renderer, clock } = await createRoutingRenderer({
|
|
2119
|
+
stdinParserMaxBufferBytes: 64 * 1024,
|
|
2120
|
+
})
|
|
2121
|
+
|
|
2122
|
+
try {
|
|
2123
|
+
const keys: string[] = []
|
|
2124
|
+
const pastes: string[] = []
|
|
2125
|
+
renderer.keyInput.on("keypress", (event) => {
|
|
2126
|
+
keys.push(event.name)
|
|
2127
|
+
})
|
|
2128
|
+
renderer.keyInput.on("paste", (event) => {
|
|
2129
|
+
pastes.push(decodePasteBytes(event.bytes))
|
|
2130
|
+
})
|
|
2131
|
+
|
|
2132
|
+
const largeChunk = Buffer.alloc(16 * 1024, "x")
|
|
2133
|
+
const expectedPaste = largeChunk.toString().repeat(5) + "z"
|
|
2134
|
+
|
|
2135
|
+
expect(() => {
|
|
2136
|
+
renderer.stdin.emit("data", Buffer.from("\x1b[200~"))
|
|
2137
|
+
for (let i = 0; i < 5; i++) {
|
|
2138
|
+
renderer.stdin.emit("data", largeChunk)
|
|
2139
|
+
}
|
|
2140
|
+
renderer.stdin.emit("data", Buffer.from("z"))
|
|
2141
|
+
renderer.stdin.emit("data", Buffer.from("\x1b[20"))
|
|
2142
|
+
renderer.stdin.emit("data", Buffer.from("1~"))
|
|
2143
|
+
renderer.stdin.emit("data", Buffer.from("q"))
|
|
2144
|
+
}).not.toThrow()
|
|
2145
|
+
|
|
2146
|
+
advanceClock(clock)
|
|
2147
|
+
|
|
2148
|
+
expect(keys).toEqual(["q"])
|
|
2149
|
+
expect(pastes).toEqual([expectedPaste])
|
|
2150
|
+
} finally {
|
|
2151
|
+
renderer.destroy()
|
|
2152
|
+
}
|
|
2153
|
+
})
|
|
2154
|
+
|
|
2155
|
+
test("emits paste event for large bracketed paste under configured limit", async () => {
|
|
2156
|
+
const { renderer, clock } = await createRoutingRenderer({
|
|
2157
|
+
stdinParserMaxBufferBytes: 512 * 1024,
|
|
2158
|
+
})
|
|
2159
|
+
|
|
2160
|
+
try {
|
|
2161
|
+
const payloadSize = 256 * 1024
|
|
2162
|
+
let pasteCount = 0
|
|
2163
|
+
let pastedBytes = 0
|
|
2164
|
+
|
|
2165
|
+
renderer.keyInput.on("paste", (event) => {
|
|
2166
|
+
pasteCount += 1
|
|
2167
|
+
pastedBytes += event.bytes.length
|
|
2168
|
+
})
|
|
2169
|
+
|
|
2170
|
+
const chunk = Buffer.alloc(payloadSize, "x")
|
|
2171
|
+
const stream = Buffer.concat([Buffer.from("\x1b[200~"), chunk, Buffer.from("\x1b[201~")])
|
|
2172
|
+
renderer.stdin.emit("data", stream)
|
|
2173
|
+
advanceClock(clock)
|
|
2174
|
+
|
|
2175
|
+
expect(pasteCount).toBe(1)
|
|
2176
|
+
expect(pastedBytes).toBe(payloadSize)
|
|
2177
|
+
} finally {
|
|
2178
|
+
renderer.destroy()
|
|
2179
|
+
}
|
|
2180
|
+
})
|
|
2181
|
+
|
|
2182
|
+
test("emits one paste event for one bracketed paste", async () => {
|
|
2183
|
+
const { renderer, clock } = await createRoutingRenderer()
|
|
2184
|
+
|
|
2185
|
+
try {
|
|
2186
|
+
const payload = "x".repeat(70_000)
|
|
2187
|
+
const pastes: string[] = []
|
|
2188
|
+
renderer.keyInput.on("paste", (event) => {
|
|
2189
|
+
pastes.push(decodePasteBytes(event.bytes))
|
|
2190
|
+
})
|
|
2191
|
+
|
|
2192
|
+
renderer.stdin.emit("data", Buffer.from(`\x1b[200~${payload}\x1b[201~`))
|
|
2193
|
+
advanceClock(clock)
|
|
2194
|
+
|
|
2195
|
+
expect(pastes).toEqual([payload])
|
|
2196
|
+
} finally {
|
|
2197
|
+
renderer.destroy()
|
|
2198
|
+
}
|
|
2199
|
+
})
|
|
2200
|
+
|
|
2201
|
+
test("emits empty paste for empty bracketed paste", async () => {
|
|
2202
|
+
const { renderer, clock } = await createRoutingRenderer()
|
|
2203
|
+
|
|
2204
|
+
try {
|
|
2205
|
+
const pastes: string[] = []
|
|
2206
|
+
renderer.keyInput.on("paste", (event) => {
|
|
2207
|
+
pastes.push(decodePasteBytes(event.bytes))
|
|
2208
|
+
})
|
|
2209
|
+
|
|
2210
|
+
renderer.stdin.emit("data", Buffer.from("\x1b[200~\x1b[201~"))
|
|
2211
|
+
advanceClock(clock)
|
|
2212
|
+
|
|
2213
|
+
expect(pastes).toEqual([""])
|
|
2214
|
+
} finally {
|
|
2215
|
+
renderer.destroy()
|
|
2216
|
+
}
|
|
2217
|
+
})
|
|
2218
|
+
|
|
2219
|
+
test("preserves UTF-8 across bracketed paste chunk boundaries", async () => {
|
|
2220
|
+
const { renderer, clock } = await createRoutingRenderer()
|
|
2221
|
+
|
|
2222
|
+
try {
|
|
2223
|
+
const payload = "a".repeat(4095) + "é"
|
|
2224
|
+
const pastes: string[] = []
|
|
2225
|
+
renderer.keyInput.on("paste", (event) => {
|
|
2226
|
+
pastes.push(decodePasteBytes(event.bytes))
|
|
2227
|
+
})
|
|
2228
|
+
|
|
2229
|
+
renderer.stdin.emit("data", Buffer.from(`\x1b[200~${payload}\x1b[201~`))
|
|
2230
|
+
advanceClock(clock)
|
|
2231
|
+
|
|
2232
|
+
expect(pastes.join("")).toBe(payload)
|
|
2233
|
+
} finally {
|
|
2234
|
+
renderer.destroy()
|
|
2235
|
+
}
|
|
2236
|
+
})
|
|
2237
|
+
})
|