@effect-tui/react 0.1.3 → 0.1.4
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/dist/jsx-runtime.d.ts +13 -0
- package/dist/jsx-runtime.d.ts.map +1 -1
- package/dist/jsx-runtime.js.map +1 -1
- package/dist/src/codeblock.d.ts.map +1 -1
- package/dist/src/codeblock.js.map +1 -1
- package/dist/src/components/Divider.d.ts +18 -0
- package/dist/src/components/Divider.d.ts.map +1 -0
- package/dist/src/components/Divider.js +17 -0
- package/dist/src/components/Divider.js.map +1 -0
- package/dist/src/components/Markdown.d.ts +66 -0
- package/dist/src/components/Markdown.d.ts.map +1 -0
- package/dist/src/components/Markdown.js +226 -0
- package/dist/src/components/Markdown.js.map +1 -0
- package/dist/src/components/MultilineTextInput.d.ts +65 -0
- package/dist/src/components/MultilineTextInput.d.ts.map +1 -0
- package/dist/src/components/MultilineTextInput.js +607 -0
- package/dist/src/components/MultilineTextInput.js.map +1 -0
- package/dist/src/components/Overlay.d.ts +46 -0
- package/dist/src/components/Overlay.d.ts.map +1 -0
- package/dist/src/components/Overlay.js +11 -0
- package/dist/src/components/Overlay.js.map +1 -0
- package/dist/src/components/Static.d.ts +44 -0
- package/dist/src/components/Static.d.ts.map +1 -0
- package/dist/src/components/Static.js +53 -0
- package/dist/src/components/Static.js.map +1 -0
- package/dist/src/components/TextInput.d.ts +53 -0
- package/dist/src/components/TextInput.d.ts.map +1 -0
- package/dist/src/components/TextInput.js +210 -0
- package/dist/src/components/TextInput.js.map +1 -0
- package/dist/src/components/index.d.ts +7 -0
- package/dist/src/components/index.d.ts.map +1 -0
- package/dist/src/components/index.js +7 -0
- package/dist/src/components/index.js.map +1 -0
- package/dist/src/components/text-editing.d.ts +62 -0
- package/dist/src/components/text-editing.d.ts.map +1 -0
- package/dist/src/components/text-editing.js +385 -0
- package/dist/src/components/text-editing.js.map +1 -0
- package/dist/src/console/ConsoleCapture.d.ts +36 -0
- package/dist/src/console/ConsoleCapture.d.ts.map +1 -0
- package/dist/src/console/ConsoleCapture.js +210 -0
- package/dist/src/console/ConsoleCapture.js.map +1 -0
- package/dist/src/console/ConsolePopover.d.ts +18 -0
- package/dist/src/console/ConsolePopover.d.ts.map +1 -0
- package/dist/src/console/ConsolePopover.js +324 -0
- package/dist/src/console/ConsolePopover.js.map +1 -0
- package/dist/src/console/clipboard.d.ts +10 -0
- package/dist/src/console/clipboard.d.ts.map +1 -0
- package/dist/src/console/clipboard.js +74 -0
- package/dist/src/console/clipboard.js.map +1 -0
- package/dist/src/console/index.d.ts +5 -0
- package/dist/src/console/index.d.ts.map +1 -0
- package/dist/src/console/index.js +33 -0
- package/dist/src/console/index.js.map +1 -0
- package/dist/src/console/useConsole.d.ts +44 -0
- package/dist/src/console/useConsole.d.ts.map +1 -0
- package/dist/src/console/useConsole.js +91 -0
- package/dist/src/console/useConsole.js.map +1 -0
- package/dist/src/debug/DebugOverlay.d.ts +49 -0
- package/dist/src/debug/DebugOverlay.d.ts.map +1 -0
- package/dist/src/debug/DebugOverlay.js +438 -0
- package/dist/src/debug/DebugOverlay.js.map +1 -0
- package/dist/src/debug/DiagnosticsPanel.d.ts.map +1 -1
- package/dist/src/debug/DiagnosticsPanel.js.map +1 -1
- package/dist/src/dev/Toast.d.ts +19 -0
- package/dist/src/dev/Toast.d.ts.map +1 -0
- package/dist/src/dev/Toast.js +72 -0
- package/dist/src/dev/Toast.js.map +1 -0
- package/dist/src/dev/index.d.ts +2 -0
- package/dist/src/dev/index.d.ts.map +1 -0
- package/dist/src/dev/index.js +3 -0
- package/dist/src/dev/index.js.map +1 -0
- package/dist/src/dev.d.ts +114 -0
- package/dist/src/dev.d.ts.map +1 -0
- package/dist/src/dev.js +373 -0
- package/dist/src/dev.js.map +1 -0
- package/dist/src/highlight.d.ts +3 -3
- package/dist/src/highlight.d.ts.map +1 -1
- package/dist/src/highlight.js.map +1 -1
- package/dist/src/hmr-plugin.d.ts +2 -0
- package/dist/src/hmr-plugin.d.ts.map +1 -0
- package/dist/src/hmr-plugin.js +53 -0
- package/dist/src/hmr-plugin.js.map +1 -0
- package/dist/src/hooks/index.d.ts +4 -0
- package/dist/src/hooks/index.d.ts.map +1 -1
- package/dist/src/hooks/index.js +2 -0
- package/dist/src/hooks/index.js.map +1 -1
- package/dist/src/hooks/use-keyboard.d.ts +11 -0
- package/dist/src/hooks/use-keyboard.d.ts.map +1 -1
- package/dist/src/hooks/use-keyboard.js +22 -4
- package/dist/src/hooks/use-keyboard.js.map +1 -1
- package/dist/src/hooks/use-mouse.d.ts +24 -0
- package/dist/src/hooks/use-mouse.d.ts.map +1 -0
- package/dist/src/hooks/use-mouse.js +41 -0
- package/dist/src/hooks/use-mouse.js.map +1 -0
- package/dist/src/hooks/use-paste.d.ts +11 -0
- package/dist/src/hooks/use-paste.d.ts.map +1 -1
- package/dist/src/hooks/use-paste.js +17 -3
- package/dist/src/hooks/use-paste.js.map +1 -1
- package/dist/src/hooks/use-scroll.d.ts +79 -0
- package/dist/src/hooks/use-scroll.d.ts.map +1 -0
- package/dist/src/hooks/use-scroll.js +239 -0
- package/dist/src/hooks/use-scroll.js.map +1 -0
- package/dist/src/hooks/useFrameStats.js.map +1 -1
- package/dist/src/hosts/base.d.ts +62 -1
- package/dist/src/hosts/base.d.ts.map +1 -1
- package/dist/src/hosts/base.js +118 -5
- package/dist/src/hosts/base.js.map +1 -1
- package/dist/src/hosts/box.d.ts +7 -7
- package/dist/src/hosts/box.d.ts.map +1 -1
- package/dist/src/hosts/box.js +30 -23
- package/dist/src/hosts/box.js.map +1 -1
- package/dist/src/hosts/canvas.d.ts +8 -8
- package/dist/src/hosts/canvas.d.ts.map +1 -1
- package/dist/src/hosts/canvas.js +13 -22
- package/dist/src/hosts/canvas.js.map +1 -1
- package/dist/src/hosts/codeblock.d.ts +7 -7
- package/dist/src/hosts/codeblock.d.ts.map +1 -1
- package/dist/src/hosts/codeblock.js +11 -20
- package/dist/src/hosts/codeblock.js.map +1 -1
- package/dist/src/hosts/flex-container.d.ts +45 -0
- package/dist/src/hosts/flex-container.d.ts.map +1 -0
- package/dist/src/hosts/flex-container.js +90 -0
- package/dist/src/hosts/flex-container.js.map +1 -0
- package/dist/src/hosts/hstack.d.ts +6 -11
- package/dist/src/hosts/hstack.d.ts.map +1 -1
- package/dist/src/hosts/hstack.js +6 -41
- package/dist/src/hosts/hstack.js.map +1 -1
- package/dist/src/hosts/index.d.ts +4 -0
- package/dist/src/hosts/index.d.ts.map +1 -1
- package/dist/src/hosts/index.js +10 -0
- package/dist/src/hosts/index.js.map +1 -1
- package/dist/src/hosts/overlay-item.d.ts +32 -0
- package/dist/src/hosts/overlay-item.d.ts.map +1 -0
- package/dist/src/hosts/overlay-item.js +54 -0
- package/dist/src/hosts/overlay-item.js.map +1 -0
- package/dist/src/hosts/overlay.d.ts +30 -0
- package/dist/src/hosts/overlay.d.ts.map +1 -0
- package/dist/src/hosts/overlay.js +105 -0
- package/dist/src/hosts/overlay.js.map +1 -0
- package/dist/src/hosts/scroll.d.ts +56 -0
- package/dist/src/hosts/scroll.d.ts.map +1 -0
- package/dist/src/hosts/scroll.js +204 -0
- package/dist/src/hosts/scroll.js.map +1 -0
- package/dist/src/hosts/single-child.d.ts +16 -0
- package/dist/src/hosts/single-child.d.ts.map +1 -0
- package/dist/src/hosts/single-child.js +45 -0
- package/dist/src/hosts/single-child.js.map +1 -0
- package/dist/src/hosts/spacer.d.ts.map +1 -1
- package/dist/src/hosts/spacer.js +7 -3
- package/dist/src/hosts/spacer.js.map +1 -1
- package/dist/src/hosts/text.d.ts +9 -6
- package/dist/src/hosts/text.d.ts.map +1 -1
- package/dist/src/hosts/text.js +49 -22
- package/dist/src/hosts/text.js.map +1 -1
- package/dist/src/hosts/vstack.d.ts +6 -11
- package/dist/src/hosts/vstack.d.ts.map +1 -1
- package/dist/src/hosts/vstack.js +6 -41
- package/dist/src/hosts/vstack.js.map +1 -1
- package/dist/src/hosts/zstack.d.ts.map +1 -1
- package/dist/src/hosts/zstack.js +16 -5
- package/dist/src/hosts/zstack.js.map +1 -1
- package/dist/src/index.d.ts +9 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +10 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/inline/index.d.ts.map +1 -1
- package/dist/src/inline/index.js.map +1 -1
- package/dist/src/motion/color-motion-value.d.ts.map +1 -1
- package/dist/src/motion/color-motion-value.js.map +1 -1
- package/dist/src/motion/color.d.ts +1 -29
- package/dist/src/motion/color.d.ts.map +1 -1
- package/dist/src/motion/color.js +2 -170
- package/dist/src/motion/color.js.map +1 -1
- package/dist/src/motion/color.test.js.map +1 -1
- package/dist/src/motion/event-emitter.d.ts.map +1 -1
- package/dist/src/motion/event-emitter.js.map +1 -1
- package/dist/src/motion/frame.js.map +1 -1
- package/dist/src/motion/hooks.d.ts.map +1 -1
- package/dist/src/motion/hooks.js +8 -3
- package/dist/src/motion/hooks.js.map +1 -1
- package/dist/src/motion/index.d.ts.map +1 -1
- package/dist/src/motion/index.js.map +1 -1
- package/dist/src/motion/motion-value.d.ts.map +1 -1
- package/dist/src/motion/motion-value.js.map +1 -1
- package/dist/src/motion/motion-value.test.js.map +1 -1
- package/dist/src/motion/spring-math.d.ts +6 -1
- package/dist/src/motion/spring-math.d.ts.map +1 -1
- package/dist/src/motion/spring-math.js +6 -1
- package/dist/src/motion/spring-math.js.map +1 -1
- package/dist/src/motion/types.d.ts.map +1 -1
- package/dist/src/motion/types.js.map +1 -1
- package/dist/src/profiler.js.map +1 -1
- package/dist/src/reconciler/host-config.d.ts +5 -5
- package/dist/src/reconciler/host-config.d.ts.map +1 -1
- package/dist/src/reconciler/host-config.js +43 -51
- package/dist/src/reconciler/host-config.js.map +1 -1
- package/dist/src/reconciler/noop-methods.d.ts +29 -0
- package/dist/src/reconciler/noop-methods.d.ts.map +1 -0
- package/dist/src/reconciler/noop-methods.js +43 -0
- package/dist/src/reconciler/noop-methods.js.map +1 -0
- package/dist/src/reconciler/types.d.ts +68 -14
- package/dist/src/reconciler/types.d.ts.map +1 -1
- package/dist/src/remote/Procedures.d.ts +22 -0
- package/dist/src/remote/Procedures.d.ts.map +1 -0
- package/dist/src/remote/Procedures.js +42 -0
- package/dist/src/remote/Procedures.js.map +1 -0
- package/dist/src/remote/Router.d.ts +20 -0
- package/dist/src/remote/Router.d.ts.map +1 -0
- package/dist/src/remote/Router.js +26 -0
- package/dist/src/remote/Router.js.map +1 -0
- package/dist/src/remote/Server.d.ts +6 -0
- package/dist/src/remote/Server.d.ts.map +1 -0
- package/dist/src/remote/Server.js +53 -0
- package/dist/src/remote/Server.js.map +1 -0
- package/dist/src/remote/index.d.ts +18 -0
- package/dist/src/remote/index.d.ts.map +1 -0
- package/dist/src/remote/index.js +74 -0
- package/dist/src/remote/index.js.map +1 -0
- package/dist/src/renderer/core/FrameBuilder.d.ts +18 -0
- package/dist/src/renderer/core/FrameBuilder.d.ts.map +1 -0
- package/dist/src/renderer/core/FrameBuilder.js +38 -0
- package/dist/src/renderer/core/FrameBuilder.js.map +1 -0
- package/dist/src/renderer/core/RendererState.d.ts +41 -0
- package/dist/src/renderer/core/RendererState.d.ts.map +1 -0
- package/dist/src/renderer/core/RendererState.js +70 -0
- package/dist/src/renderer/core/RendererState.js.map +1 -0
- package/dist/src/renderer/core/index.d.ts +3 -0
- package/dist/src/renderer/core/index.d.ts.map +1 -0
- package/dist/src/renderer/core/index.js +3 -0
- package/dist/src/renderer/core/index.js.map +1 -0
- package/dist/src/renderer/input/InputProcessor.d.ts +25 -0
- package/dist/src/renderer/input/InputProcessor.d.ts.map +1 -0
- package/dist/src/renderer/input/InputProcessor.js +81 -0
- package/dist/src/renderer/input/InputProcessor.js.map +1 -0
- package/dist/src/renderer/input/index.d.ts +2 -0
- package/dist/src/renderer/input/index.d.ts.map +1 -0
- package/dist/src/renderer/input/index.js +2 -0
- package/dist/src/renderer/input/index.js.map +1 -0
- package/dist/src/renderer/lifecycle/EventBus.d.ts +41 -0
- package/dist/src/renderer/lifecycle/EventBus.d.ts.map +1 -0
- package/dist/src/renderer/lifecycle/EventBus.js +78 -0
- package/dist/src/renderer/lifecycle/EventBus.js.map +1 -0
- package/dist/src/renderer/lifecycle/ResizeManager.d.ts +34 -0
- package/dist/src/renderer/lifecycle/ResizeManager.d.ts.map +1 -0
- package/dist/src/renderer/lifecycle/ResizeManager.js +47 -0
- package/dist/src/renderer/lifecycle/ResizeManager.js.map +1 -0
- package/dist/src/renderer/lifecycle/TerminalSetup.d.ts +36 -0
- package/dist/src/renderer/lifecycle/TerminalSetup.d.ts.map +1 -0
- package/dist/src/renderer/lifecycle/TerminalSetup.js +82 -0
- package/dist/src/renderer/lifecycle/TerminalSetup.js.map +1 -0
- package/dist/src/renderer/lifecycle/index.d.ts +4 -0
- package/dist/src/renderer/lifecycle/index.d.ts.map +1 -0
- package/dist/src/renderer/lifecycle/index.js +4 -0
- package/dist/src/renderer/lifecycle/index.js.map +1 -0
- package/dist/src/renderer/modes/FullscreenRenderer.d.ts +12 -0
- package/dist/src/renderer/modes/FullscreenRenderer.d.ts.map +1 -0
- package/dist/src/renderer/modes/FullscreenRenderer.js +52 -0
- package/dist/src/renderer/modes/FullscreenRenderer.js.map +1 -0
- package/dist/src/renderer/modes/InlineRenderer.d.ts +22 -0
- package/dist/src/renderer/modes/InlineRenderer.d.ts.map +1 -0
- package/dist/src/renderer/modes/InlineRenderer.js +154 -0
- package/dist/src/renderer/modes/InlineRenderer.js.map +1 -0
- package/dist/src/renderer/modes/RendererMode.d.ts +42 -0
- package/dist/src/renderer/modes/RendererMode.d.ts.map +1 -0
- package/dist/src/renderer/modes/RendererMode.js +2 -0
- package/dist/src/renderer/modes/RendererMode.js.map +1 -0
- package/dist/src/renderer/modes/StaticContentRenderer.d.ts +25 -0
- package/dist/src/renderer/modes/StaticContentRenderer.d.ts.map +1 -0
- package/dist/src/renderer/modes/StaticContentRenderer.js +47 -0
- package/dist/src/renderer/modes/StaticContentRenderer.js.map +1 -0
- package/dist/src/renderer/modes/index.d.ts +5 -0
- package/dist/src/renderer/modes/index.d.ts.map +1 -0
- package/dist/src/renderer/modes/index.js +4 -0
- package/dist/src/renderer/modes/index.js.map +1 -0
- package/dist/src/renderer-context.d.ts +9 -0
- package/dist/src/renderer-context.d.ts.map +1 -0
- package/dist/src/renderer-context.js +22 -0
- package/dist/src/renderer-context.js.map +1 -0
- package/dist/src/renderer-types.d.ts +103 -0
- package/dist/src/renderer-types.d.ts.map +1 -0
- package/dist/src/renderer-types.js +2 -0
- package/dist/src/renderer-types.js.map +1 -0
- package/dist/src/renderer.d.ts +4 -86
- package/dist/src/renderer.d.ts.map +1 -1
- package/dist/src/renderer.js +213 -384
- package/dist/src/renderer.js.map +1 -1
- package/dist/src/test/index.d.ts.map +1 -1
- package/dist/src/test/index.js.map +1 -1
- package/dist/src/test/mock-streams.d.ts.map +1 -1
- package/dist/src/test/mock-streams.js.map +1 -1
- package/dist/src/test/render-tui.d.ts.map +1 -1
- package/dist/src/test/render-tui.js +2 -5
- package/dist/src/test/render-tui.js.map +1 -1
- package/dist/src/trace/SpanTree.d.ts.map +1 -1
- package/dist/src/trace/SpanTree.js +21 -11
- package/dist/src/trace/SpanTree.js.map +1 -1
- package/dist/src/trace/format-value.d.ts +15 -0
- package/dist/src/trace/format-value.d.ts.map +1 -0
- package/dist/src/trace/format-value.js +77 -0
- package/dist/src/trace/format-value.js.map +1 -0
- package/dist/src/trace/index.d.ts.map +1 -1
- package/dist/src/trace/index.js.map +1 -1
- package/dist/src/trace/location.js +1 -1
- package/dist/src/trace/location.js.map +1 -1
- package/dist/src/trace/span-processor.d.ts.map +1 -1
- package/dist/src/trace/span-processor.js.map +1 -1
- package/dist/src/trace/span-state.d.ts +19 -2
- package/dist/src/trace/span-state.d.ts.map +1 -1
- package/dist/src/trace/span-state.js +62 -31
- package/dist/src/trace/span-state.js.map +1 -1
- package/dist/src/trace/tui-logger.js.map +1 -1
- package/dist/src/utils/border.d.ts +1 -1
- package/dist/src/utils/border.d.ts.map +1 -1
- package/dist/src/utils/border.js +6 -0
- package/dist/src/utils/border.js.map +1 -1
- package/dist/src/utils/flex-layout.d.ts +2 -1
- package/dist/src/utils/flex-layout.d.ts.map +1 -1
- package/dist/src/utils/flex-layout.js +22 -33
- package/dist/src/utils/flex-layout.js.map +1 -1
- package/dist/src/utils/index.d.ts +1 -1
- package/dist/src/utils/index.d.ts.map +1 -1
- package/dist/src/utils/index.js +1 -1
- package/dist/src/utils/index.js.map +1 -1
- package/dist/src/utils/padding.d.ts.map +1 -1
- package/dist/src/utils/padding.js.map +1 -1
- package/dist/src/utils/styles.d.ts +20 -1
- package/dist/src/utils/styles.d.ts.map +1 -1
- package/dist/src/utils/styles.js +36 -1
- package/dist/src/utils/styles.js.map +1 -1
- package/dist/src/visualize/index.d.ts +8 -19
- package/dist/src/visualize/index.d.ts.map +1 -1
- package/dist/src/visualize/index.js +11 -25
- package/dist/src/visualize/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/jsx-dev-runtime.ts +5 -0
- package/jsx-runtime.ts +54 -0
- package/package.json +124 -92
- package/src/codeblock.tsx +34 -34
- package/src/components/Divider.tsx +23 -0
- package/src/components/Markdown.tsx +380 -0
- package/src/components/MultilineTextInput.tsx +749 -0
- package/src/components/Overlay.tsx +56 -0
- package/src/components/Static.tsx +68 -0
- package/src/components/TextInput.tsx +285 -0
- package/src/components/index.ts +6 -0
- package/src/components/text-editing.ts +464 -0
- package/src/console/ConsoleCapture.ts +272 -0
- package/src/console/ConsolePopover.tsx +487 -0
- package/src/console/clipboard.ts +81 -0
- package/src/console/index.ts +42 -0
- package/src/console/useConsole.ts +129 -0
- package/src/debug/DebugOverlay.ts +557 -0
- package/src/debug/DiagnosticsPanel.tsx +27 -27
- package/src/dev/Toast.tsx +117 -0
- package/src/dev/index.ts +2 -0
- package/src/dev.tsx +489 -0
- package/src/highlight.ts +46 -46
- package/src/hmr-plugin.ts +61 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/use-keyboard.ts +44 -24
- package/src/hooks/use-mouse.ts +51 -0
- package/src/hooks/use-paste.ts +21 -6
- package/src/hooks/use-scroll.ts +386 -0
- package/src/hooks/useFrameStats.ts +17 -17
- package/src/hosts/base.ts +180 -59
- package/src/hosts/box.ts +117 -94
- package/src/hosts/canvas.ts +137 -141
- package/src/hosts/codeblock.ts +117 -133
- package/src/hosts/flex-container.ts +124 -0
- package/src/hosts/hstack.ts +11 -59
- package/src/hosts/index.ts +24 -14
- package/src/hosts/overlay-item.ts +72 -0
- package/src/hosts/overlay.ts +125 -0
- package/src/hosts/scroll.ts +255 -0
- package/src/hosts/single-child.ts +52 -0
- package/src/hosts/spacer.ts +30 -26
- package/src/hosts/text.ts +198 -164
- package/src/hosts/vstack.ts +11 -59
- package/src/hosts/zstack.ts +79 -67
- package/src/index.ts +44 -19
- package/src/inline/index.tsx +123 -123
- package/src/motion/color-motion-value.ts +67 -67
- package/src/motion/color.test.ts +107 -107
- package/src/motion/color.ts +9 -190
- package/src/motion/event-emitter.ts +20 -20
- package/src/motion/frame.ts +35 -35
- package/src/motion/hooks.ts +144 -139
- package/src/motion/index.ts +10 -10
- package/src/motion/motion-value.test.ts +207 -207
- package/src/motion/motion-value.ts +112 -112
- package/src/motion/spring-math.ts +88 -83
- package/src/motion/types.ts +25 -25
- package/src/profiler.ts +50 -50
- package/src/reconciler/host-config.ts +152 -174
- package/src/reconciler/noop-methods.ts +55 -0
- package/src/reconciler/types.ts +112 -46
- package/src/remote/Procedures.ts +52 -0
- package/src/remote/Router.ts +58 -0
- package/src/remote/Server.ts +76 -0
- package/src/remote/index.ts +90 -0
- package/src/renderer/core/FrameBuilder.ts +49 -0
- package/src/renderer/core/RendererState.ts +80 -0
- package/src/renderer/core/index.ts +2 -0
- package/src/renderer/input/InputProcessor.ts +94 -0
- package/src/renderer/input/index.ts +1 -0
- package/src/renderer/lifecycle/EventBus.ts +90 -0
- package/src/renderer/lifecycle/ResizeManager.ts +65 -0
- package/src/renderer/lifecycle/TerminalSetup.ts +105 -0
- package/src/renderer/lifecycle/index.ts +3 -0
- package/src/renderer/modes/FullscreenRenderer.ts +53 -0
- package/src/renderer/modes/InlineRenderer.ts +178 -0
- package/src/renderer/modes/RendererMode.ts +46 -0
- package/src/renderer/modes/StaticContentRenderer.ts +56 -0
- package/src/renderer/modes/index.ts +4 -0
- package/src/renderer-context.ts +27 -0
- package/src/renderer-types.ts +109 -0
- package/src/renderer.ts +391 -642
- package/src/test/index.ts +5 -5
- package/src/test/mock-streams.ts +115 -115
- package/src/test/render-tui.ts +84 -87
- package/src/utils/border.ts +79 -73
- package/src/utils/flex-layout.ts +80 -93
- package/src/utils/index.ts +1 -1
- package/src/utils/padding.ts +27 -27
- package/src/utils/styles.ts +50 -7
- package/src/visualize/index.tsx +225 -240
- package/dist/src/output.d.ts +0 -47
- package/dist/src/output.d.ts.map +0 -1
- package/dist/src/output.js +0 -125
- package/dist/src/output.js.map +0 -1
- package/dist/src/terminal.d.ts +0 -37
- package/dist/src/terminal.d.ts.map +0 -1
- package/dist/src/terminal.js +0 -65
- package/dist/src/terminal.js.map +0 -1
- package/src/output.ts +0 -156
- package/src/terminal.ts +0 -67
- package/src/trace/SpanTree.tsx +0 -195
- package/src/trace/index.tsx +0 -205
- package/src/trace/location.ts +0 -90
- package/src/trace/span-processor.ts +0 -65
- package/src/trace/span-state.ts +0 -286
- package/src/trace/tui-logger.ts +0 -72
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
import { useState, useCallback, useEffect, useMemo, useRef } from "react"
|
|
2
|
+
import { Colors, type Color, displayWidth, graphemes } from "@effect-tui/core"
|
|
3
|
+
import { useKeyboard } from "../hooks/use-keyboard.js"
|
|
4
|
+
import type { DrawContext } from "../hosts/canvas.js"
|
|
5
|
+
import {
|
|
6
|
+
type MultilineState,
|
|
7
|
+
graphemeColToCharIdx,
|
|
8
|
+
deleteWordBackwardMultiline,
|
|
9
|
+
killToEndMultiline,
|
|
10
|
+
killToStartMultiline,
|
|
11
|
+
transposeCharsMultiline,
|
|
12
|
+
deleteCharForwardMultiline,
|
|
13
|
+
deleteCharBackwardMultiline,
|
|
14
|
+
insertTextMultiline,
|
|
15
|
+
matchPrevWord,
|
|
16
|
+
matchNextWord,
|
|
17
|
+
} from "./text-editing.js"
|
|
18
|
+
|
|
19
|
+
export interface MultilineTextInputProps {
|
|
20
|
+
/** Current input value (controlled) */
|
|
21
|
+
value: string
|
|
22
|
+
/** Called when value changes */
|
|
23
|
+
onChange: (value: string) => void
|
|
24
|
+
/** Placeholder text shown when value is empty */
|
|
25
|
+
placeholder?: string
|
|
26
|
+
/** Width of the input (if omitted, fills available width) */
|
|
27
|
+
width?: number
|
|
28
|
+
/** Height of the input (visible lines) */
|
|
29
|
+
height?: number
|
|
30
|
+
/** Foreground color for text */
|
|
31
|
+
fg?: Color
|
|
32
|
+
/** Background color */
|
|
33
|
+
bg?: Color
|
|
34
|
+
/** Cursor foreground color */
|
|
35
|
+
cursorFg?: Color
|
|
36
|
+
/** Cursor background color */
|
|
37
|
+
cursorBg?: Color
|
|
38
|
+
/** Placeholder foreground color */
|
|
39
|
+
placeholderFg?: Color
|
|
40
|
+
/** Line number foreground color */
|
|
41
|
+
lineNumberFg?: Color
|
|
42
|
+
/** Called when Escape is pressed */
|
|
43
|
+
onCancel?: () => void
|
|
44
|
+
/** Called when Ctrl+Enter or Cmd+Enter is pressed */
|
|
45
|
+
onSubmit?: (value: string) => void
|
|
46
|
+
/** Whether the input is focused and accepts input */
|
|
47
|
+
focused?: boolean
|
|
48
|
+
/** Show cursor even when not focused */
|
|
49
|
+
showCursor?: boolean
|
|
50
|
+
/** Show line numbers (for logical lines) */
|
|
51
|
+
showLineNumbers?: boolean
|
|
52
|
+
/** Enable word wrap (default: true) */
|
|
53
|
+
wordWrap?: boolean
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Logical cursor position */
|
|
57
|
+
interface CursorPos {
|
|
58
|
+
row: number // logical line index
|
|
59
|
+
col: number // grapheme index within logical line
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** A visual line that maps back to a logical line */
|
|
63
|
+
interface VisualLine {
|
|
64
|
+
logicalRow: number
|
|
65
|
+
startCol: number // grapheme index in logical line (inclusive)
|
|
66
|
+
endCol: number // grapheme index in logical line (exclusive)
|
|
67
|
+
text: string // the actual text for this visual line
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Layout for a single logical line */
|
|
71
|
+
interface LineLayout {
|
|
72
|
+
graphemeList: string[]
|
|
73
|
+
widths: number[]
|
|
74
|
+
prefixWidths: number[] // cumulative widths, length = graphemes.length + 1
|
|
75
|
+
visualLines: VisualLine[]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Complete layout for all text */
|
|
79
|
+
interface Layout {
|
|
80
|
+
lines: LineLayout[]
|
|
81
|
+
allVisualLines: VisualLine[]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Wrap a single logical line into visual lines.
|
|
86
|
+
* Uses word boundaries with fallback to character wrap.
|
|
87
|
+
*/
|
|
88
|
+
function wrapLogicalLine(line: string, logicalRow: number, maxWidth: number): LineLayout {
|
|
89
|
+
const graphemeList = graphemes(line)
|
|
90
|
+
const widths = graphemeList.map((g) => displayWidth(g))
|
|
91
|
+
|
|
92
|
+
// Build prefix widths for O(1) range width queries
|
|
93
|
+
const prefixWidths: number[] = [0]
|
|
94
|
+
for (let i = 0; i < widths.length; i++) {
|
|
95
|
+
prefixWidths.push(prefixWidths[i] + widths[i])
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Find break opportunities (after spaces)
|
|
99
|
+
const isBreakable = graphemeList.map((g) => /\s/.test(g))
|
|
100
|
+
|
|
101
|
+
const visualLines: VisualLine[] = []
|
|
102
|
+
let start = 0
|
|
103
|
+
let currentWidth = 0
|
|
104
|
+
let lastBreak = -1
|
|
105
|
+
|
|
106
|
+
for (let i = 0; i < graphemeList.length; i++) {
|
|
107
|
+
const w = widths[i]
|
|
108
|
+
|
|
109
|
+
if (currentWidth + w > maxWidth && start < i) {
|
|
110
|
+
// Need to wrap - backtrack to last break if possible
|
|
111
|
+
const breakAt = lastBreak >= start ? lastBreak + 1 : i
|
|
112
|
+
|
|
113
|
+
visualLines.push({
|
|
114
|
+
logicalRow,
|
|
115
|
+
startCol: start,
|
|
116
|
+
endCol: breakAt,
|
|
117
|
+
text: graphemeList.slice(start, breakAt).join(""),
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
start = breakAt
|
|
121
|
+
currentWidth = prefixWidths[i + 1] - prefixWidths[start]
|
|
122
|
+
lastBreak = -1
|
|
123
|
+
} else {
|
|
124
|
+
currentWidth += w
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (isBreakable[i]) {
|
|
128
|
+
lastBreak = i
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Final segment
|
|
133
|
+
visualLines.push({
|
|
134
|
+
logicalRow,
|
|
135
|
+
startCol: start,
|
|
136
|
+
endCol: graphemeList.length,
|
|
137
|
+
text: graphemeList.slice(start).join(""),
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
return { graphemeList, widths, prefixWidths, visualLines }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Build complete layout for text with word wrap.
|
|
145
|
+
*/
|
|
146
|
+
function buildLayout(text: string, maxWidth: number): Layout {
|
|
147
|
+
const logicalLines = text.split("\n")
|
|
148
|
+
const lines: LineLayout[] = []
|
|
149
|
+
const allVisualLines: VisualLine[] = []
|
|
150
|
+
|
|
151
|
+
for (let row = 0; row < logicalLines.length; row++) {
|
|
152
|
+
const lineLayout = wrapLogicalLine(logicalLines[row], row, maxWidth)
|
|
153
|
+
lines.push(lineLayout)
|
|
154
|
+
allVisualLines.push(...lineLayout.visualLines)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return { lines, allVisualLines }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Convert logical cursor to visual row/col.
|
|
162
|
+
*/
|
|
163
|
+
function logicalToVisual(layout: Layout, cursor: CursorPos): { visualRow: number; visualCol: number } {
|
|
164
|
+
const lineLayout = layout.lines[cursor.row]
|
|
165
|
+
if (!lineLayout) {
|
|
166
|
+
return { visualRow: 0, visualCol: 0 }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Find which visual line contains this column
|
|
170
|
+
let visualRowOffset = 0
|
|
171
|
+
for (let r = 0; r < cursor.row; r++) {
|
|
172
|
+
visualRowOffset += layout.lines[r].visualLines.length
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
for (let i = 0; i < lineLayout.visualLines.length; i++) {
|
|
176
|
+
const vl = lineLayout.visualLines[i]
|
|
177
|
+
if (cursor.col >= vl.startCol && cursor.col < vl.endCol) {
|
|
178
|
+
// Cursor is in this visual line
|
|
179
|
+
const visualCol = lineLayout.prefixWidths[cursor.col] - lineLayout.prefixWidths[vl.startCol]
|
|
180
|
+
return { visualRow: visualRowOffset + i, visualCol }
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Cursor at end - use last visual line
|
|
185
|
+
const lastVl = lineLayout.visualLines[lineLayout.visualLines.length - 1]
|
|
186
|
+
const visualCol = lineLayout.prefixWidths[cursor.col] - lineLayout.prefixWidths[lastVl.startCol]
|
|
187
|
+
return {
|
|
188
|
+
visualRow: visualRowOffset + lineLayout.visualLines.length - 1,
|
|
189
|
+
visualCol,
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Convert visual row to logical cursor (for up/down navigation).
|
|
195
|
+
* Tries to preserve the visual X position.
|
|
196
|
+
*/
|
|
197
|
+
function visualToLogical(layout: Layout, visualRow: number, targetVisualX: number): CursorPos {
|
|
198
|
+
if (visualRow < 0) {
|
|
199
|
+
return { row: 0, col: 0 }
|
|
200
|
+
}
|
|
201
|
+
if (visualRow >= layout.allVisualLines.length) {
|
|
202
|
+
const lastLine = layout.lines[layout.lines.length - 1]
|
|
203
|
+
return {
|
|
204
|
+
row: layout.lines.length - 1,
|
|
205
|
+
col: lastLine?.graphemeList.length ?? 0,
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const vl = layout.allVisualLines[visualRow]
|
|
210
|
+
const lineLayout = layout.lines[vl.logicalRow]
|
|
211
|
+
|
|
212
|
+
// Binary search for the grapheme at targetVisualX
|
|
213
|
+
let col = vl.startCol
|
|
214
|
+
let currentX = 0
|
|
215
|
+
for (let i = vl.startCol; i < vl.endCol; i++) {
|
|
216
|
+
const w = lineLayout.widths[i]
|
|
217
|
+
if (currentX + w > targetVisualX) {
|
|
218
|
+
// If we're past halfway into this char, go to next
|
|
219
|
+
if (currentX + w / 2 <= targetVisualX) {
|
|
220
|
+
col = i + 1
|
|
221
|
+
} else {
|
|
222
|
+
col = i
|
|
223
|
+
}
|
|
224
|
+
break
|
|
225
|
+
}
|
|
226
|
+
currentX += w
|
|
227
|
+
col = i + 1
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return { row: vl.logicalRow, col: Math.min(col, vl.endCol) }
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* A controlled multiline text input component for terminal UIs.
|
|
235
|
+
*
|
|
236
|
+
* Features:
|
|
237
|
+
* - Word wrap (fills available width)
|
|
238
|
+
* - Arrow key navigation between lines (respects visual line breaks)
|
|
239
|
+
* - Word-by-word navigation with Option/Alt+Arrow
|
|
240
|
+
* - Emacs keybindings: Ctrl+A/E (start/end of line), Ctrl+B/F (back/forward char),
|
|
241
|
+
* Ctrl+H/D (delete back/forward char), Ctrl+K/U (kill to end/start of line),
|
|
242
|
+
* Ctrl+W (delete word back), Ctrl+T (transpose), Ctrl+Y (yank),
|
|
243
|
+
* Ctrl+N/P (next/previous line)
|
|
244
|
+
* - Enter inserts newline, Ctrl+Enter/Cmd+Enter submits
|
|
245
|
+
* - Kill ring for Ctrl+Y (yank last killed text)
|
|
246
|
+
* - Scrolling when content exceeds visible height
|
|
247
|
+
* - Optional line numbers
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* ```tsx
|
|
251
|
+
* const [text, setText] = useState("")
|
|
252
|
+
* <MultilineTextInput
|
|
253
|
+
* value={text}
|
|
254
|
+
* onChange={setText}
|
|
255
|
+
* height={10}
|
|
256
|
+
* />
|
|
257
|
+
* ```
|
|
258
|
+
*/
|
|
259
|
+
export function MultilineTextInput({
|
|
260
|
+
value,
|
|
261
|
+
onChange,
|
|
262
|
+
placeholder = "",
|
|
263
|
+
width,
|
|
264
|
+
height = 5,
|
|
265
|
+
fg = Colors.brightWhite,
|
|
266
|
+
bg,
|
|
267
|
+
cursorFg = Colors.black,
|
|
268
|
+
cursorBg = Colors.brightWhite,
|
|
269
|
+
placeholderFg = Colors.gray(10),
|
|
270
|
+
lineNumberFg = Colors.gray(12),
|
|
271
|
+
onCancel,
|
|
272
|
+
onSubmit,
|
|
273
|
+
focused = true,
|
|
274
|
+
showCursor = true,
|
|
275
|
+
showLineNumbers = false,
|
|
276
|
+
wordWrap = true,
|
|
277
|
+
}: MultilineTextInputProps) {
|
|
278
|
+
const logicalLines = useMemo(() => value.split("\n"), [value])
|
|
279
|
+
const [cursor, setCursor] = useState<CursorPos>({ row: 0, col: 0 })
|
|
280
|
+
const [scrollY, setScrollY] = useState(0)
|
|
281
|
+
const [killRing, setKillRing] = useState("")
|
|
282
|
+
const [preferredVisualX, setPreferredVisualX] = useState(0)
|
|
283
|
+
// Ref to track whether preferredVisualX should be updated after cursor change
|
|
284
|
+
// Set to false only during vertical movement, true otherwise
|
|
285
|
+
const shouldUpdatePreferredXRef = useRef(true)
|
|
286
|
+
|
|
287
|
+
// We need to know content width for layout - use a ref updated in draw
|
|
288
|
+
const [contentWidth, setContentWidth] = useState(80)
|
|
289
|
+
|
|
290
|
+
// Calculate line number gutter width
|
|
291
|
+
const lineNumGutterWidth = showLineNumbers ? String(logicalLines.length).length + 1 : 0
|
|
292
|
+
|
|
293
|
+
// Build layout with word wrap
|
|
294
|
+
const layout = useMemo(() => {
|
|
295
|
+
if (!wordWrap) {
|
|
296
|
+
// No wrap - each logical line is one visual line
|
|
297
|
+
const lines: LineLayout[] = logicalLines.map((line, row) => {
|
|
298
|
+
const graphemeList = graphemes(line)
|
|
299
|
+
const widths = graphemeList.map((g) => displayWidth(g))
|
|
300
|
+
const prefixWidths = [0]
|
|
301
|
+
for (let i = 0; i < widths.length; i++) {
|
|
302
|
+
prefixWidths.push(prefixWidths[i] + widths[i])
|
|
303
|
+
}
|
|
304
|
+
return {
|
|
305
|
+
graphemeList,
|
|
306
|
+
widths,
|
|
307
|
+
prefixWidths,
|
|
308
|
+
visualLines: [
|
|
309
|
+
{
|
|
310
|
+
logicalRow: row,
|
|
311
|
+
startCol: 0,
|
|
312
|
+
endCol: graphemeList.length,
|
|
313
|
+
text: line,
|
|
314
|
+
},
|
|
315
|
+
],
|
|
316
|
+
}
|
|
317
|
+
})
|
|
318
|
+
return {
|
|
319
|
+
lines,
|
|
320
|
+
allVisualLines: lines.flatMap((l) => l.visualLines),
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const effectiveWidth = Math.max(10, contentWidth - lineNumGutterWidth)
|
|
325
|
+
return buildLayout(value, effectiveWidth)
|
|
326
|
+
}, [value, contentWidth, wordWrap, lineNumGutterWidth, logicalLines])
|
|
327
|
+
|
|
328
|
+
// Keep cursor in bounds when value changes externally
|
|
329
|
+
useEffect(() => {
|
|
330
|
+
const maxRow = Math.max(0, logicalLines.length - 1)
|
|
331
|
+
const newRow = Math.min(cursor.row, maxRow)
|
|
332
|
+
const lineGraphemes = layout.lines[newRow]?.graphemeList ?? []
|
|
333
|
+
const maxCol = lineGraphemes.length
|
|
334
|
+
const newCol = Math.min(cursor.col, maxCol)
|
|
335
|
+
if (newRow !== cursor.row || newCol !== cursor.col) {
|
|
336
|
+
setCursor({ row: newRow, col: newCol })
|
|
337
|
+
}
|
|
338
|
+
}, [value, logicalLines, layout.lines, cursor.row, cursor.col])
|
|
339
|
+
|
|
340
|
+
// Keep scroll in bounds (visual lines)
|
|
341
|
+
useEffect(() => {
|
|
342
|
+
const { visualRow } = logicalToVisual(layout, cursor)
|
|
343
|
+
if (visualRow < scrollY) {
|
|
344
|
+
setScrollY(visualRow)
|
|
345
|
+
} else if (visualRow >= scrollY + height) {
|
|
346
|
+
setScrollY(visualRow - height + 1)
|
|
347
|
+
}
|
|
348
|
+
}, [cursor, layout, scrollY, height])
|
|
349
|
+
|
|
350
|
+
// Update preferredVisualX after cursor/layout changes (except during vertical movement)
|
|
351
|
+
useEffect(() => {
|
|
352
|
+
if (!shouldUpdatePreferredXRef.current) {
|
|
353
|
+
// Reset ref for next action
|
|
354
|
+
shouldUpdatePreferredXRef.current = true
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
const { visualCol } = logicalToVisual(layout, cursor)
|
|
358
|
+
setPreferredVisualX(visualCol)
|
|
359
|
+
}, [cursor, layout])
|
|
360
|
+
|
|
361
|
+
const currentLine = logicalLines[cursor.row] ?? ""
|
|
362
|
+
const currentLineLayout = layout.lines[cursor.row]
|
|
363
|
+
|
|
364
|
+
const handleKey = useCallback(
|
|
365
|
+
(key: { name: string; text?: string; ctrl?: boolean; meta?: boolean; shift?: boolean }) => {
|
|
366
|
+
if (!focused) return
|
|
367
|
+
|
|
368
|
+
const moveCursor = (row: number, col: number) => {
|
|
369
|
+
const newRow = Math.max(0, Math.min(logicalLines.length - 1, row))
|
|
370
|
+
const lineLayout = layout.lines[newRow]
|
|
371
|
+
const lineLen = lineLayout?.graphemeList.length ?? 0
|
|
372
|
+
const newCol = Math.max(0, Math.min(lineLen, col))
|
|
373
|
+
setCursor({ row: newRow, col: newCol })
|
|
374
|
+
// preferredVisualX will be updated by the effect
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const moveVisual = (deltaRows: number) => {
|
|
378
|
+
// Mark that we shouldn't update preferredVisualX after this move
|
|
379
|
+
shouldUpdatePreferredXRef.current = false
|
|
380
|
+
const { visualRow } = logicalToVisual(layout, cursor)
|
|
381
|
+
const newVisualRow = visualRow + deltaRows
|
|
382
|
+
const newCursor = visualToLogical(layout, newVisualRow, preferredVisualX)
|
|
383
|
+
setCursor(newCursor)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
switch (key.name) {
|
|
387
|
+
case "up":
|
|
388
|
+
if (key.meta) {
|
|
389
|
+
// Option+Up: Move to start of document
|
|
390
|
+
moveCursor(0, 0)
|
|
391
|
+
} else {
|
|
392
|
+
moveVisual(-1)
|
|
393
|
+
}
|
|
394
|
+
break
|
|
395
|
+
|
|
396
|
+
case "down":
|
|
397
|
+
if (key.meta) {
|
|
398
|
+
// Option+Down: Move to end of document
|
|
399
|
+
const lastRow = logicalLines.length - 1
|
|
400
|
+
const lastLineLen = layout.lines[lastRow]?.graphemeList.length ?? 0
|
|
401
|
+
moveCursor(lastRow, lastLineLen)
|
|
402
|
+
} else {
|
|
403
|
+
moveVisual(1)
|
|
404
|
+
}
|
|
405
|
+
break
|
|
406
|
+
|
|
407
|
+
case "left":
|
|
408
|
+
if (key.meta) {
|
|
409
|
+
// Option+Left: Move to previous word boundary
|
|
410
|
+
const lineLayout = layout.lines[cursor.row]
|
|
411
|
+
if (lineLayout) {
|
|
412
|
+
const beforeCursor = lineLayout.graphemeList.slice(0, cursor.col).join("")
|
|
413
|
+
const match = matchPrevWord(beforeCursor)
|
|
414
|
+
if (match) {
|
|
415
|
+
// Count graphemes in match
|
|
416
|
+
const matchGraphemes = graphemes(match).length
|
|
417
|
+
moveCursor(cursor.row, cursor.col - matchGraphemes)
|
|
418
|
+
} else if (cursor.col > 0) {
|
|
419
|
+
moveCursor(cursor.row, 0)
|
|
420
|
+
} else if (cursor.row > 0) {
|
|
421
|
+
const prevLineLen = layout.lines[cursor.row - 1]?.graphemeList.length ?? 0
|
|
422
|
+
moveCursor(cursor.row - 1, prevLineLen)
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
} else {
|
|
426
|
+
if (cursor.col > 0) {
|
|
427
|
+
moveCursor(cursor.row, cursor.col - 1)
|
|
428
|
+
} else if (cursor.row > 0) {
|
|
429
|
+
const prevLineLen = layout.lines[cursor.row - 1]?.graphemeList.length ?? 0
|
|
430
|
+
moveCursor(cursor.row - 1, prevLineLen)
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
break
|
|
434
|
+
|
|
435
|
+
case "right":
|
|
436
|
+
if (key.meta) {
|
|
437
|
+
// Option+Right: Move to next word boundary
|
|
438
|
+
const lineLayout = layout.lines[cursor.row]
|
|
439
|
+
if (lineLayout) {
|
|
440
|
+
const afterCursor = lineLayout.graphemeList.slice(cursor.col).join("")
|
|
441
|
+
const match = matchNextWord(afterCursor)
|
|
442
|
+
if (match) {
|
|
443
|
+
const matchGraphemes = graphemes(match).length
|
|
444
|
+
moveCursor(cursor.row, cursor.col + matchGraphemes)
|
|
445
|
+
} else if (cursor.col < lineLayout.graphemeList.length) {
|
|
446
|
+
moveCursor(cursor.row, lineLayout.graphemeList.length)
|
|
447
|
+
} else if (cursor.row < logicalLines.length - 1) {
|
|
448
|
+
moveCursor(cursor.row + 1, 0)
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
} else {
|
|
452
|
+
const lineLen = currentLineLayout?.graphemeList.length ?? 0
|
|
453
|
+
if (cursor.col < lineLen) {
|
|
454
|
+
moveCursor(cursor.row, cursor.col + 1)
|
|
455
|
+
} else if (cursor.row < logicalLines.length - 1) {
|
|
456
|
+
moveCursor(cursor.row + 1, 0)
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
break
|
|
460
|
+
|
|
461
|
+
case "home":
|
|
462
|
+
moveCursor(cursor.row, 0)
|
|
463
|
+
break
|
|
464
|
+
|
|
465
|
+
case "end":
|
|
466
|
+
moveCursor(cursor.row, currentLineLayout?.graphemeList.length ?? 0)
|
|
467
|
+
break
|
|
468
|
+
|
|
469
|
+
case "backspace":
|
|
470
|
+
if (key.meta && cursor.col > 0) {
|
|
471
|
+
// Option+Backspace: Delete previous word
|
|
472
|
+
const charIdx = graphemeColToCharIdx(currentLine, cursor.col)
|
|
473
|
+
const beforeCursor = currentLine.slice(0, charIdx)
|
|
474
|
+
const match = matchPrevWord(beforeCursor)
|
|
475
|
+
if (match) {
|
|
476
|
+
const newCharIdx = beforeCursor.length - match.length
|
|
477
|
+
const newCol = graphemes(currentLine.slice(0, newCharIdx)).length
|
|
478
|
+
const newLine = currentLine.slice(0, newCharIdx) + currentLine.slice(charIdx)
|
|
479
|
+
const newLines = [...logicalLines]
|
|
480
|
+
newLines[cursor.row] = newLine
|
|
481
|
+
onChange(newLines.join("\n"))
|
|
482
|
+
setCursor({ row: cursor.row, col: newCol })
|
|
483
|
+
}
|
|
484
|
+
} else {
|
|
485
|
+
// Delete character before cursor (or join with previous line)
|
|
486
|
+
const state: MultilineState = { lines: logicalLines, cursor, killRing }
|
|
487
|
+
const result = deleteCharBackwardMultiline(state)
|
|
488
|
+
if (result.changed) {
|
|
489
|
+
onChange(result.state.lines.join("\n"))
|
|
490
|
+
setCursor(result.state.cursor)
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
break
|
|
494
|
+
|
|
495
|
+
case "delete": {
|
|
496
|
+
const state: MultilineState = { lines: logicalLines, cursor, killRing }
|
|
497
|
+
const result = deleteCharForwardMultiline(state)
|
|
498
|
+
if (result.changed) {
|
|
499
|
+
onChange(result.state.lines.join("\n"))
|
|
500
|
+
}
|
|
501
|
+
break
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
case "enter":
|
|
505
|
+
if (key.ctrl || key.meta) {
|
|
506
|
+
onSubmit?.(value)
|
|
507
|
+
} else {
|
|
508
|
+
// Insert newline
|
|
509
|
+
const state: MultilineState = { lines: logicalLines, cursor, killRing }
|
|
510
|
+
const result = insertTextMultiline(state, "\n")
|
|
511
|
+
if (result.changed) {
|
|
512
|
+
onChange(result.state.lines.join("\n"))
|
|
513
|
+
setCursor(result.state.cursor)
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
break
|
|
517
|
+
|
|
518
|
+
case "escape":
|
|
519
|
+
onCancel?.()
|
|
520
|
+
break
|
|
521
|
+
|
|
522
|
+
case "char":
|
|
523
|
+
case "space":
|
|
524
|
+
if (key.ctrl && key.text) {
|
|
525
|
+
// Emacs-style keybindings
|
|
526
|
+
switch (key.text) {
|
|
527
|
+
case "a":
|
|
528
|
+
moveCursor(cursor.row, 0)
|
|
529
|
+
break
|
|
530
|
+
case "e":
|
|
531
|
+
moveCursor(cursor.row, currentLineLayout?.graphemeList.length ?? 0)
|
|
532
|
+
break
|
|
533
|
+
case "b":
|
|
534
|
+
if (cursor.col > 0) {
|
|
535
|
+
moveCursor(cursor.row, cursor.col - 1)
|
|
536
|
+
} else if (cursor.row > 0) {
|
|
537
|
+
const prevLen = layout.lines[cursor.row - 1]?.graphemeList.length ?? 0
|
|
538
|
+
moveCursor(cursor.row - 1, prevLen)
|
|
539
|
+
}
|
|
540
|
+
break
|
|
541
|
+
case "f":
|
|
542
|
+
if (cursor.col < (currentLineLayout?.graphemeList.length ?? 0)) {
|
|
543
|
+
moveCursor(cursor.row, cursor.col + 1)
|
|
544
|
+
} else if (cursor.row < logicalLines.length - 1) {
|
|
545
|
+
moveCursor(cursor.row + 1, 0)
|
|
546
|
+
}
|
|
547
|
+
break
|
|
548
|
+
case "n":
|
|
549
|
+
moveVisual(1)
|
|
550
|
+
break
|
|
551
|
+
case "p":
|
|
552
|
+
moveVisual(-1)
|
|
553
|
+
break
|
|
554
|
+
case "d": {
|
|
555
|
+
// Delete character at cursor
|
|
556
|
+
const state: MultilineState = { lines: logicalLines, cursor, killRing }
|
|
557
|
+
const result = deleteCharForwardMultiline(state)
|
|
558
|
+
if (result.changed) {
|
|
559
|
+
onChange(result.state.lines.join("\n"))
|
|
560
|
+
}
|
|
561
|
+
break
|
|
562
|
+
}
|
|
563
|
+
case "h": {
|
|
564
|
+
// Backspace
|
|
565
|
+
const state: MultilineState = { lines: logicalLines, cursor, killRing }
|
|
566
|
+
const result = deleteCharBackwardMultiline(state)
|
|
567
|
+
if (result.changed) {
|
|
568
|
+
onChange(result.state.lines.join("\n"))
|
|
569
|
+
setCursor(result.state.cursor)
|
|
570
|
+
}
|
|
571
|
+
break
|
|
572
|
+
}
|
|
573
|
+
case "k": {
|
|
574
|
+
// Kill to end of line
|
|
575
|
+
const state: MultilineState = { lines: logicalLines, cursor, killRing }
|
|
576
|
+
const result = killToEndMultiline(state)
|
|
577
|
+
if (result.changed) {
|
|
578
|
+
onChange(result.state.lines.join("\n"))
|
|
579
|
+
setCursor(result.state.cursor)
|
|
580
|
+
setKillRing(result.state.killRing)
|
|
581
|
+
}
|
|
582
|
+
break
|
|
583
|
+
}
|
|
584
|
+
case "u": {
|
|
585
|
+
// Kill to start of line
|
|
586
|
+
const state: MultilineState = { lines: logicalLines, cursor, killRing }
|
|
587
|
+
const result = killToStartMultiline(state)
|
|
588
|
+
if (result.changed) {
|
|
589
|
+
onChange(result.state.lines.join("\n"))
|
|
590
|
+
setCursor(result.state.cursor)
|
|
591
|
+
setKillRing(result.state.killRing)
|
|
592
|
+
}
|
|
593
|
+
break
|
|
594
|
+
}
|
|
595
|
+
case "w": {
|
|
596
|
+
// Delete word backward (crosses line boundaries)
|
|
597
|
+
const state: MultilineState = { lines: logicalLines, cursor, killRing }
|
|
598
|
+
const result = deleteWordBackwardMultiline(state)
|
|
599
|
+
if (result.changed) {
|
|
600
|
+
onChange(result.state.lines.join("\n"))
|
|
601
|
+
setCursor(result.state.cursor)
|
|
602
|
+
setKillRing(result.state.killRing)
|
|
603
|
+
}
|
|
604
|
+
break
|
|
605
|
+
}
|
|
606
|
+
case "t": {
|
|
607
|
+
// Transpose characters
|
|
608
|
+
const state: MultilineState = { lines: logicalLines, cursor, killRing }
|
|
609
|
+
const result = transposeCharsMultiline(state)
|
|
610
|
+
if (result.changed) {
|
|
611
|
+
onChange(result.state.lines.join("\n"))
|
|
612
|
+
setCursor(result.state.cursor)
|
|
613
|
+
}
|
|
614
|
+
break
|
|
615
|
+
}
|
|
616
|
+
case "y": {
|
|
617
|
+
// Yank from kill ring
|
|
618
|
+
const state: MultilineState = { lines: logicalLines, cursor, killRing }
|
|
619
|
+
const result = insertTextMultiline(state, killRing)
|
|
620
|
+
if (result.changed) {
|
|
621
|
+
onChange(result.state.lines.join("\n"))
|
|
622
|
+
setCursor(result.state.cursor)
|
|
623
|
+
}
|
|
624
|
+
break
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
} else if (key.text && !key.meta) {
|
|
628
|
+
// Insert character at cursor
|
|
629
|
+
const state: MultilineState = { lines: logicalLines, cursor, killRing }
|
|
630
|
+
const result = insertTextMultiline(state, key.text)
|
|
631
|
+
if (result.changed) {
|
|
632
|
+
onChange(result.state.lines.join("\n"))
|
|
633
|
+
setCursor(result.state.cursor)
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
break
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
[
|
|
640
|
+
value,
|
|
641
|
+
logicalLines,
|
|
642
|
+
layout,
|
|
643
|
+
cursor,
|
|
644
|
+
currentLine,
|
|
645
|
+
currentLineLayout,
|
|
646
|
+
focused,
|
|
647
|
+
onChange,
|
|
648
|
+
onSubmit,
|
|
649
|
+
onCancel,
|
|
650
|
+
killRing,
|
|
651
|
+
preferredVisualX,
|
|
652
|
+
],
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
useKeyboard(handleKey, { phase: "any" })
|
|
656
|
+
|
|
657
|
+
const draw = useCallback(
|
|
658
|
+
(ctx: DrawContext) => {
|
|
659
|
+
// Update content width for layout calculations
|
|
660
|
+
const newContentWidth = width ?? ctx.width
|
|
661
|
+
if (newContentWidth !== contentWidth) {
|
|
662
|
+
setContentWidth(newContentWidth)
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const lineNumWidth = showLineNumbers ? String(logicalLines.length).length + 1 : 0
|
|
666
|
+
|
|
667
|
+
const isPlaceholder = !value
|
|
668
|
+
const textColor = isPlaceholder ? placeholderFg : fg
|
|
669
|
+
|
|
670
|
+
// Clear with background
|
|
671
|
+
if (bg !== undefined) {
|
|
672
|
+
ctx.fill(0, 0, ctx.width, ctx.height, " ", { bg })
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Use placeholder layout if empty
|
|
676
|
+
const displayLayout = isPlaceholder ? buildLayout(placeholder, Math.max(10, ctx.width - lineNumWidth)) : layout
|
|
677
|
+
|
|
678
|
+
const { visualRow: cursorVisualRow } = isPlaceholder ? { visualRow: 0 } : logicalToVisual(layout, cursor)
|
|
679
|
+
|
|
680
|
+
// Draw visible visual lines
|
|
681
|
+
for (let y = 0; y < height && y + scrollY < displayLayout.allVisualLines.length; y++) {
|
|
682
|
+
const visualIdx = y + scrollY
|
|
683
|
+
const vl = displayLayout.allVisualLines[visualIdx]
|
|
684
|
+
const lineLayout = displayLayout.lines[vl.logicalRow]
|
|
685
|
+
|
|
686
|
+
let x = 0
|
|
687
|
+
|
|
688
|
+
// Draw line number (only on first visual line of logical line)
|
|
689
|
+
if (showLineNumbers) {
|
|
690
|
+
if (vl.startCol === 0) {
|
|
691
|
+
const lineNum = String(vl.logicalRow + 1).padStart(lineNumWidth - 1, " ")
|
|
692
|
+
ctx.text(0, y, lineNum, { fg: lineNumberFg, bg })
|
|
693
|
+
}
|
|
694
|
+
x = lineNumWidth
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Draw line content
|
|
698
|
+
for (let g = vl.startCol; g < vl.endCol; g++) {
|
|
699
|
+
const grapheme = lineLayout.graphemeList[g]
|
|
700
|
+
const charWidth = lineLayout.widths[g]
|
|
701
|
+
|
|
702
|
+
// Draw cursor if at this position
|
|
703
|
+
const isCursor = !isPlaceholder && vl.logicalRow === cursor.row && g === cursor.col && focused && showCursor
|
|
704
|
+
|
|
705
|
+
if (isCursor) {
|
|
706
|
+
ctx.text(x, y, grapheme, { fg: cursorFg, bg: cursorBg })
|
|
707
|
+
} else {
|
|
708
|
+
ctx.text(x, y, grapheme, { fg: textColor, bg })
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
x += charWidth
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Draw cursor at end of visual line if cursor is there
|
|
715
|
+
if (!isPlaceholder && visualIdx === cursorVisualRow && cursor.col >= vl.endCol && focused && showCursor) {
|
|
716
|
+
ctx.text(x, y, " ", { fg: cursorFg, bg: cursorBg })
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// If placeholder and focused, show cursor at start
|
|
721
|
+
if (isPlaceholder && focused && showCursor) {
|
|
722
|
+
const firstChar = placeholder[0] || " "
|
|
723
|
+
ctx.text(lineNumWidth, 0, firstChar, { fg: cursorFg, bg: cursorBg })
|
|
724
|
+
}
|
|
725
|
+
},
|
|
726
|
+
[
|
|
727
|
+
value,
|
|
728
|
+
placeholder,
|
|
729
|
+
layout,
|
|
730
|
+
logicalLines,
|
|
731
|
+
cursor,
|
|
732
|
+
scrollY,
|
|
733
|
+
height,
|
|
734
|
+
focused,
|
|
735
|
+
showCursor,
|
|
736
|
+
showLineNumbers,
|
|
737
|
+
fg,
|
|
738
|
+
bg,
|
|
739
|
+
cursorFg,
|
|
740
|
+
cursorBg,
|
|
741
|
+
placeholderFg,
|
|
742
|
+
lineNumberFg,
|
|
743
|
+
width,
|
|
744
|
+
contentWidth,
|
|
745
|
+
],
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
return <canvas draw={draw} width={width} height={height} />
|
|
749
|
+
}
|