@promptctl/rich-js 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +20 -110
- package/THIRD-PARTY-NOTICES +81 -0
- package/dist/core/box.d.ts +0 -1
- package/dist/core/box.js +0 -1
- package/dist/core/cells.d.ts +0 -1
- package/dist/core/cells.js +0 -1
- package/dist/core/color.d.ts +6 -1
- package/dist/core/color.js +6 -1
- package/dist/core/console.d.ts +13 -4
- package/dist/core/console.js +78 -87
- package/dist/core/emoji.d.ts +0 -1
- package/dist/core/emoji.js +0 -1
- package/dist/core/export-html.d.ts +24 -0
- package/dist/core/export-html.js +106 -0
- package/dist/core/export-lines.d.ts +121 -0
- package/dist/core/export-lines.js +141 -0
- package/dist/core/highlighter.d.ts +0 -1
- package/dist/core/highlighter.js +0 -1
- package/dist/core/json.d.ts +0 -1
- package/dist/core/json.js +0 -1
- package/dist/core/markup.d.ts +33 -2
- package/dist/core/markup.js +128 -16
- package/dist/core/measure.d.ts +0 -1
- package/dist/core/measure.js +0 -1
- package/dist/core/oklch.d.ts +0 -1
- package/dist/core/oklch.js +0 -1
- package/dist/core/osc8.d.ts +68 -0
- package/dist/core/osc8.js +90 -0
- package/dist/core/pretty.d.ts +21 -2
- package/dist/core/pretty.js +90 -16
- package/dist/core/protocol.d.ts +18 -2
- package/dist/core/protocol.js +0 -1
- package/dist/core/render.d.ts +0 -1
- package/dist/core/render.js +3 -10
- package/dist/core/segment.d.ts +0 -1
- package/dist/core/segment.js +0 -1
- package/dist/core/spinnerData.d.ts +0 -1
- package/dist/core/spinnerData.js +0 -1
- package/dist/core/strip.d.ts +0 -1
- package/dist/core/strip.js +12 -1
- package/dist/core/style.d.ts +1 -7
- package/dist/core/style.js +84 -67
- package/dist/core/subscription.d.ts +0 -1
- package/dist/core/subscription.js +0 -1
- package/dist/core/text.d.ts +1 -8
- package/dist/core/text.js +25 -29
- package/dist/core/wrap.d.ts +0 -1
- package/dist/core/wrap.js +0 -1
- package/dist/host/host-stream.d.ts +0 -1
- package/dist/host/host-stream.js +0 -1
- package/dist/host/index.d.ts +0 -1
- package/dist/host/index.js +0 -1
- package/dist/host/terminal-host.d.ts +0 -1
- package/dist/host/terminal-host.js +0 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -2
- package/dist/node/prompt.d.ts +0 -1
- package/dist/node/prompt.js +0 -1
- package/dist/node/save.d.ts +2 -7
- package/dist/node/save.js +0 -1
- package/dist/node/terminal-host.d.ts +0 -1
- package/dist/node/terminal-host.js +0 -1
- package/dist/node/traceback.d.ts +0 -1
- package/dist/node/traceback.js +0 -1
- package/dist/renderables/align.d.ts +0 -1
- package/dist/renderables/align.js +0 -1
- package/dist/renderables/columns.d.ts +12 -1
- package/dist/renderables/columns.js +29 -5
- package/dist/renderables/constrain.d.ts +0 -1
- package/dist/renderables/constrain.js +0 -1
- package/dist/renderables/flexStrip.d.ts +0 -1
- package/dist/renderables/flexStrip.js +0 -1
- package/dist/renderables/group.d.ts +0 -1
- package/dist/renderables/group.js +0 -1
- package/dist/renderables/layout.d.ts +0 -1
- package/dist/renderables/layout.js +20 -7
- package/dist/renderables/live.d.ts +0 -1
- package/dist/renderables/live.js +0 -1
- package/dist/renderables/markdown.d.ts +0 -1
- package/dist/renderables/markdown.js +0 -1
- package/dist/renderables/padding.d.ts +0 -1
- package/dist/renderables/padding.js +0 -1
- package/dist/renderables/panel.d.ts +0 -1
- package/dist/renderables/panel.js +12 -4
- package/dist/renderables/progress.d.ts +0 -1
- package/dist/renderables/progress.js +7 -2
- package/dist/renderables/progressBar.d.ts +0 -1
- package/dist/renderables/progressBar.js +0 -1
- package/dist/renderables/prompt.d.ts +0 -1
- package/dist/renderables/prompt.js +0 -1
- package/dist/renderables/rule.d.ts +0 -1
- package/dist/renderables/rule.js +0 -1
- package/dist/renderables/spinner.d.ts +0 -1
- package/dist/renderables/spinner.js +0 -1
- package/dist/renderables/status.d.ts +0 -1
- package/dist/renderables/status.js +0 -1
- package/dist/renderables/syntax.d.ts +0 -1
- package/dist/renderables/syntax.js +0 -1
- package/dist/renderables/table.d.ts +0 -1
- package/dist/renderables/table.js +8 -3
- package/dist/renderables/traceback.d.ts +8 -7
- package/dist/renderables/traceback.js +14 -10
- package/dist/renderables/tree.d.ts +0 -1
- package/dist/renderables/tree.js +0 -1
- package/dist/template-bindings/color-funcs.d.ts +0 -1
- package/dist/template-bindings/color-funcs.js +0 -1
- package/dist/template-bindings/helpers.d.ts +0 -1
- package/dist/template-bindings/helpers.js +0 -1
- package/dist/template-bindings/index.d.ts +0 -1
- package/dist/template-bindings/index.js +0 -1
- package/dist/template-bindings/palette-funcs.d.ts +0 -1
- package/dist/template-bindings/palette-funcs.js +0 -1
- package/dist/template-bindings/style-funcs.d.ts +0 -1
- package/dist/template-bindings/style-funcs.js +0 -1
- package/dist/themes/buildPalette.d.ts +0 -1
- package/dist/themes/buildPalette.js +0 -1
- package/dist/themes/colorMath.d.ts +0 -1
- package/dist/themes/colorMath.js +0 -1
- package/dist/themes/colorRef.d.ts +0 -1
- package/dist/themes/colorRef.js +0 -1
- package/dist/themes/data/atom-one-dark.d.ts +0 -1
- package/dist/themes/data/atom-one-dark.js +0 -1
- package/dist/themes/data/atom-one-light.d.ts +0 -1
- package/dist/themes/data/atom-one-light.js +0 -1
- package/dist/themes/data/catppuccin-frappe.d.ts +0 -1
- package/dist/themes/data/catppuccin-frappe.js +0 -1
- package/dist/themes/data/catppuccin-latte.d.ts +0 -1
- package/dist/themes/data/catppuccin-latte.js +0 -1
- package/dist/themes/data/catppuccin-macchiato.d.ts +0 -1
- package/dist/themes/data/catppuccin-macchiato.js +0 -1
- package/dist/themes/data/catppuccin-mocha.d.ts +0 -1
- package/dist/themes/data/catppuccin-mocha.js +0 -1
- package/dist/themes/data/cyberpunk.d.ts +0 -1
- package/dist/themes/data/cyberpunk.js +0 -1
- package/dist/themes/data/default.d.ts +0 -1
- package/dist/themes/data/default.js +0 -1
- package/dist/themes/data/dracula.d.ts +0 -1
- package/dist/themes/data/dracula.js +0 -1
- package/dist/themes/data/flexoki.d.ts +0 -1
- package/dist/themes/data/flexoki.js +0 -1
- package/dist/themes/data/gruvbox.d.ts +0 -1
- package/dist/themes/data/gruvbox.js +0 -1
- package/dist/themes/data/index.d.ts +0 -1
- package/dist/themes/data/index.js +0 -1
- package/dist/themes/data/monokai.d.ts +0 -1
- package/dist/themes/data/monokai.js +0 -1
- package/dist/themes/data/nord.d.ts +0 -1
- package/dist/themes/data/nord.js +0 -1
- package/dist/themes/data/rose-pine-dawn.d.ts +0 -1
- package/dist/themes/data/rose-pine-dawn.js +0 -1
- package/dist/themes/data/rose-pine-moon.d.ts +0 -1
- package/dist/themes/data/rose-pine-moon.js +0 -1
- package/dist/themes/data/rose-pine.d.ts +0 -1
- package/dist/themes/data/rose-pine.js +0 -1
- package/dist/themes/data/solarized-dark.d.ts +0 -1
- package/dist/themes/data/solarized-dark.js +0 -1
- package/dist/themes/data/solarized-light.d.ts +0 -1
- package/dist/themes/data/solarized-light.js +0 -1
- package/dist/themes/data/svg-export.d.ts +0 -1
- package/dist/themes/data/svg-export.js +0 -1
- package/dist/themes/data/textual-ansi.d.ts +0 -1
- package/dist/themes/data/textual-ansi.js +0 -1
- package/dist/themes/data/textual-dark.d.ts +0 -1
- package/dist/themes/data/textual-dark.js +0 -1
- package/dist/themes/data/textual-light.d.ts +0 -1
- package/dist/themes/data/textual-light.js +0 -1
- package/dist/themes/data/tokyo-night.d.ts +0 -1
- package/dist/themes/data/tokyo-night.js +0 -1
- package/dist/themes/data/types.d.ts +0 -1
- package/dist/themes/data/types.js +0 -1
- package/dist/themes/palette.d.ts +0 -1
- package/dist/themes/palette.js +0 -1
- package/dist/themes/ramp.d.ts +0 -1
- package/dist/themes/ramp.js +0 -1
- package/dist/themes/registry.d.ts +0 -1
- package/dist/themes/registry.js +0 -1
- package/dist/themes/terminalThemes.d.ts +0 -1
- package/dist/themes/terminalThemes.js +0 -1
- package/dist/themes/transpose.d.ts +0 -1
- package/dist/themes/transpose.js +0 -1
- package/dist/widgets/button.d.ts +0 -1
- package/dist/widgets/button.js +4 -6
- package/dist/widgets/checkbox.d.ts +0 -1
- package/dist/widgets/checkbox.js +3 -5
- package/dist/widgets/dropdown.d.ts +0 -1
- package/dist/widgets/dropdown.js +4 -6
- package/dist/widgets/event-router.d.ts +0 -1
- package/dist/widgets/event-router.js +0 -1
- package/dist/widgets/focus-manager.d.ts +0 -2
- package/dist/widgets/focus-manager.js +3 -8
- package/dist/widgets/index.d.ts +0 -1
- package/dist/widgets/index.js +0 -1
- package/dist/widgets/screen.d.ts +0 -1
- package/dist/widgets/screen.js +2 -5
- package/dist/widgets/slider.d.ts +0 -1
- package/dist/widgets/slider.js +7 -9
- package/dist/widgets/static-item.d.ts +0 -1
- package/dist/widgets/static-item.js +0 -1
- package/dist/widgets/text-input.d.ts +0 -1
- package/dist/widgets/text-input.js +5 -7
- package/dist/widgets/toggle.d.ts +0 -1
- package/dist/widgets/toggle.js +4 -6
- package/dist/widgets/types.d.ts +0 -1
- package/dist/widgets/types.js +0 -1
- package/dist/widgets/widget-base.d.ts +3 -4
- package/dist/widgets/widget-base.js +4 -7
- package/package.json +14 -13
- package/dist/core/box.d.ts.map +0 -1
- package/dist/core/box.js.map +0 -1
- package/dist/core/cells.d.ts.map +0 -1
- package/dist/core/cells.js.map +0 -1
- package/dist/core/color.d.ts.map +0 -1
- package/dist/core/color.js.map +0 -1
- package/dist/core/console.d.ts.map +0 -1
- package/dist/core/console.js.map +0 -1
- package/dist/core/emoji.d.ts.map +0 -1
- package/dist/core/emoji.js.map +0 -1
- package/dist/core/highlighter.d.ts.map +0 -1
- package/dist/core/highlighter.js.map +0 -1
- package/dist/core/json.d.ts.map +0 -1
- package/dist/core/json.js.map +0 -1
- package/dist/core/markup.d.ts.map +0 -1
- package/dist/core/markup.js.map +0 -1
- package/dist/core/measure.d.ts.map +0 -1
- package/dist/core/measure.js.map +0 -1
- package/dist/core/oklch.d.ts.map +0 -1
- package/dist/core/oklch.js.map +0 -1
- package/dist/core/pretty.d.ts.map +0 -1
- package/dist/core/pretty.js.map +0 -1
- package/dist/core/protocol.d.ts.map +0 -1
- package/dist/core/protocol.js.map +0 -1
- package/dist/core/render.d.ts.map +0 -1
- package/dist/core/render.js.map +0 -1
- package/dist/core/sanitize.d.ts +0 -16
- package/dist/core/sanitize.d.ts.map +0 -1
- package/dist/core/sanitize.js +0 -19
- package/dist/core/sanitize.js.map +0 -1
- package/dist/core/segment.d.ts.map +0 -1
- package/dist/core/segment.js.map +0 -1
- package/dist/core/spinnerData.d.ts.map +0 -1
- package/dist/core/spinnerData.js.map +0 -1
- package/dist/core/strip.d.ts.map +0 -1
- package/dist/core/strip.js.map +0 -1
- package/dist/core/style.d.ts.map +0 -1
- package/dist/core/style.js.map +0 -1
- package/dist/core/subscription.d.ts.map +0 -1
- package/dist/core/subscription.js.map +0 -1
- package/dist/core/text.d.ts.map +0 -1
- package/dist/core/text.js.map +0 -1
- package/dist/core/wrap.d.ts.map +0 -1
- package/dist/core/wrap.js.map +0 -1
- package/dist/host/host-stream.d.ts.map +0 -1
- package/dist/host/host-stream.js.map +0 -1
- package/dist/host/index.d.ts.map +0 -1
- package/dist/host/index.js.map +0 -1
- package/dist/host/terminal-host.d.ts.map +0 -1
- package/dist/host/terminal-host.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/node/prompt.d.ts.map +0 -1
- package/dist/node/prompt.js.map +0 -1
- package/dist/node/save.d.ts.map +0 -1
- package/dist/node/save.js.map +0 -1
- package/dist/node/terminal-host.d.ts.map +0 -1
- package/dist/node/terminal-host.js.map +0 -1
- package/dist/node/traceback.d.ts.map +0 -1
- package/dist/node/traceback.js.map +0 -1
- package/dist/renderables/align.d.ts.map +0 -1
- package/dist/renderables/align.js.map +0 -1
- package/dist/renderables/columns.d.ts.map +0 -1
- package/dist/renderables/columns.js.map +0 -1
- package/dist/renderables/constrain.d.ts.map +0 -1
- package/dist/renderables/constrain.js.map +0 -1
- package/dist/renderables/flexStrip.d.ts.map +0 -1
- package/dist/renderables/flexStrip.js.map +0 -1
- package/dist/renderables/group.d.ts.map +0 -1
- package/dist/renderables/group.js.map +0 -1
- package/dist/renderables/layout.d.ts.map +0 -1
- package/dist/renderables/layout.js.map +0 -1
- package/dist/renderables/live.d.ts.map +0 -1
- package/dist/renderables/live.js.map +0 -1
- package/dist/renderables/markdown.d.ts.map +0 -1
- package/dist/renderables/markdown.js.map +0 -1
- package/dist/renderables/padding.d.ts.map +0 -1
- package/dist/renderables/padding.js.map +0 -1
- package/dist/renderables/panel.d.ts.map +0 -1
- package/dist/renderables/panel.js.map +0 -1
- package/dist/renderables/progress.d.ts.map +0 -1
- package/dist/renderables/progress.js.map +0 -1
- package/dist/renderables/progressBar.d.ts.map +0 -1
- package/dist/renderables/progressBar.js.map +0 -1
- package/dist/renderables/prompt.d.ts.map +0 -1
- package/dist/renderables/prompt.js.map +0 -1
- package/dist/renderables/rule.d.ts.map +0 -1
- package/dist/renderables/rule.js.map +0 -1
- package/dist/renderables/spinner.d.ts.map +0 -1
- package/dist/renderables/spinner.js.map +0 -1
- package/dist/renderables/status.d.ts.map +0 -1
- package/dist/renderables/status.js.map +0 -1
- package/dist/renderables/syntax.d.ts.map +0 -1
- package/dist/renderables/syntax.js.map +0 -1
- package/dist/renderables/table.d.ts.map +0 -1
- package/dist/renderables/table.js.map +0 -1
- package/dist/renderables/traceback.d.ts.map +0 -1
- package/dist/renderables/traceback.js.map +0 -1
- package/dist/renderables/tree.d.ts.map +0 -1
- package/dist/renderables/tree.js.map +0 -1
- package/dist/template-bindings/color-funcs.d.ts.map +0 -1
- package/dist/template-bindings/color-funcs.js.map +0 -1
- package/dist/template-bindings/helpers.d.ts.map +0 -1
- package/dist/template-bindings/helpers.js.map +0 -1
- package/dist/template-bindings/index.d.ts.map +0 -1
- package/dist/template-bindings/index.js.map +0 -1
- package/dist/template-bindings/palette-funcs.d.ts.map +0 -1
- package/dist/template-bindings/palette-funcs.js.map +0 -1
- package/dist/template-bindings/style-funcs.d.ts.map +0 -1
- package/dist/template-bindings/style-funcs.js.map +0 -1
- package/dist/themes/buildPalette.d.ts.map +0 -1
- package/dist/themes/buildPalette.js.map +0 -1
- package/dist/themes/colorMath.d.ts.map +0 -1
- package/dist/themes/colorMath.js.map +0 -1
- package/dist/themes/colorRef.d.ts.map +0 -1
- package/dist/themes/colorRef.js.map +0 -1
- package/dist/themes/data/atom-one-dark.d.ts.map +0 -1
- package/dist/themes/data/atom-one-dark.js.map +0 -1
- package/dist/themes/data/atom-one-light.d.ts.map +0 -1
- package/dist/themes/data/atom-one-light.js.map +0 -1
- package/dist/themes/data/catppuccin-frappe.d.ts.map +0 -1
- package/dist/themes/data/catppuccin-frappe.js.map +0 -1
- package/dist/themes/data/catppuccin-latte.d.ts.map +0 -1
- package/dist/themes/data/catppuccin-latte.js.map +0 -1
- package/dist/themes/data/catppuccin-macchiato.d.ts.map +0 -1
- package/dist/themes/data/catppuccin-macchiato.js.map +0 -1
- package/dist/themes/data/catppuccin-mocha.d.ts.map +0 -1
- package/dist/themes/data/catppuccin-mocha.js.map +0 -1
- package/dist/themes/data/cyberpunk.d.ts.map +0 -1
- package/dist/themes/data/cyberpunk.js.map +0 -1
- package/dist/themes/data/default.d.ts.map +0 -1
- package/dist/themes/data/default.js.map +0 -1
- package/dist/themes/data/dracula.d.ts.map +0 -1
- package/dist/themes/data/dracula.js.map +0 -1
- package/dist/themes/data/flexoki.d.ts.map +0 -1
- package/dist/themes/data/flexoki.js.map +0 -1
- package/dist/themes/data/gruvbox.d.ts.map +0 -1
- package/dist/themes/data/gruvbox.js.map +0 -1
- package/dist/themes/data/index.d.ts.map +0 -1
- package/dist/themes/data/index.js.map +0 -1
- package/dist/themes/data/monokai.d.ts.map +0 -1
- package/dist/themes/data/monokai.js.map +0 -1
- package/dist/themes/data/nord.d.ts.map +0 -1
- package/dist/themes/data/nord.js.map +0 -1
- package/dist/themes/data/rose-pine-dawn.d.ts.map +0 -1
- package/dist/themes/data/rose-pine-dawn.js.map +0 -1
- package/dist/themes/data/rose-pine-moon.d.ts.map +0 -1
- package/dist/themes/data/rose-pine-moon.js.map +0 -1
- package/dist/themes/data/rose-pine.d.ts.map +0 -1
- package/dist/themes/data/rose-pine.js.map +0 -1
- package/dist/themes/data/solarized-dark.d.ts.map +0 -1
- package/dist/themes/data/solarized-dark.js.map +0 -1
- package/dist/themes/data/solarized-light.d.ts.map +0 -1
- package/dist/themes/data/solarized-light.js.map +0 -1
- package/dist/themes/data/svg-export.d.ts.map +0 -1
- package/dist/themes/data/svg-export.js.map +0 -1
- package/dist/themes/data/textual-ansi.d.ts.map +0 -1
- package/dist/themes/data/textual-ansi.js.map +0 -1
- package/dist/themes/data/textual-dark.d.ts.map +0 -1
- package/dist/themes/data/textual-dark.js.map +0 -1
- package/dist/themes/data/textual-light.d.ts.map +0 -1
- package/dist/themes/data/textual-light.js.map +0 -1
- package/dist/themes/data/tokyo-night.d.ts.map +0 -1
- package/dist/themes/data/tokyo-night.js.map +0 -1
- package/dist/themes/data/types.d.ts.map +0 -1
- package/dist/themes/data/types.js.map +0 -1
- package/dist/themes/palette.d.ts.map +0 -1
- package/dist/themes/palette.js.map +0 -1
- package/dist/themes/ramp.d.ts.map +0 -1
- package/dist/themes/ramp.js.map +0 -1
- package/dist/themes/registry.d.ts.map +0 -1
- package/dist/themes/registry.js.map +0 -1
- package/dist/themes/terminalThemes.d.ts.map +0 -1
- package/dist/themes/terminalThemes.js.map +0 -1
- package/dist/themes/transpose.d.ts.map +0 -1
- package/dist/themes/transpose.js.map +0 -1
- package/dist/widgets/button.d.ts.map +0 -1
- package/dist/widgets/button.js.map +0 -1
- package/dist/widgets/checkbox.d.ts.map +0 -1
- package/dist/widgets/checkbox.js.map +0 -1
- package/dist/widgets/dropdown.d.ts.map +0 -1
- package/dist/widgets/dropdown.js.map +0 -1
- package/dist/widgets/event-router.d.ts.map +0 -1
- package/dist/widgets/event-router.js.map +0 -1
- package/dist/widgets/focus-manager.d.ts.map +0 -1
- package/dist/widgets/focus-manager.js.map +0 -1
- package/dist/widgets/index.d.ts.map +0 -1
- package/dist/widgets/index.js.map +0 -1
- package/dist/widgets/screen.d.ts.map +0 -1
- package/dist/widgets/screen.js.map +0 -1
- package/dist/widgets/slider.d.ts.map +0 -1
- package/dist/widgets/slider.js.map +0 -1
- package/dist/widgets/static-item.d.ts.map +0 -1
- package/dist/widgets/static-item.js.map +0 -1
- package/dist/widgets/text-input.d.ts.map +0 -1
- package/dist/widgets/text-input.js.map +0 -1
- package/dist/widgets/toggle.d.ts.map +0 -1
- package/dist/widgets/toggle.js.map +0 -1
- package/dist/widgets/types.d.ts.map +0 -1
- package/dist/widgets/types.js.map +0 -1
- package/dist/widgets/widget-base.d.ts.map +0 -1
- package/dist/widgets/widget-base.js.map +0 -1
package/dist/core/markup.js
CHANGED
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
* (`[bold]a[italic]b[/bold]c[/italic]`) because a style is an annotation and
|
|
15
15
|
* annotations may overlap freely; a plugin tag is a replacement whose handler
|
|
16
16
|
* takes one contiguous `inner`, so an overlapping pair has no slice to hand it
|
|
17
|
-
* and is rejected with a `
|
|
17
|
+
* and is rejected with a `MarkupSyntaxError`.
|
|
18
18
|
*/
|
|
19
|
+
import { cellLen } from "./cells.js";
|
|
19
20
|
import { Style, StyleSyntaxError } from "./style.js";
|
|
20
21
|
import { RichText, Span } from "./text.js";
|
|
21
22
|
import { emojiReplace } from "./emoji.js";
|
|
@@ -45,6 +46,94 @@ export class MarkupError extends Error {
|
|
|
45
46
|
this.name = "MarkupError";
|
|
46
47
|
}
|
|
47
48
|
}
|
|
49
|
+
// --- MarkupSyntaxError ---
|
|
50
|
+
/**
|
|
51
|
+
* A markup string that cannot be parsed, with where it went wrong.
|
|
52
|
+
*
|
|
53
|
+
* [LAW:types-are-the-program] A subclass rather than optional fields on
|
|
54
|
+
* `MarkupError`, because the parent is also what `MarkupRegistry.register`
|
|
55
|
+
* throws, and a registration has no source text to point into. Every error this
|
|
56
|
+
* class describes has a location, so none of its fields is optional and no
|
|
57
|
+
* caller has to ask whether one is present — `instanceof` is the only question.
|
|
58
|
+
*
|
|
59
|
+
* [LAW:one-source-of-truth] The constructor takes the facts the parser holds —
|
|
60
|
+
* the string, the offset of the offending tag, what was open — and derives
|
|
61
|
+
* `line`, `column` and the message from them, so the numbers a caller reads
|
|
62
|
+
* and the caret the message draws cannot disagree.
|
|
63
|
+
*/
|
|
64
|
+
export class MarkupSyntaxError extends MarkupError {
|
|
65
|
+
/** The problem alone, with no location, for callers building their own message. */
|
|
66
|
+
reason;
|
|
67
|
+
/** The whole markup string that failed, as the caller passed it. */
|
|
68
|
+
markup;
|
|
69
|
+
/** Index into `markup` of the tag the parser rejected. */
|
|
70
|
+
offset;
|
|
71
|
+
/** 1-based line of `offset`; lines are separated by `\n`. */
|
|
72
|
+
line;
|
|
73
|
+
/** 1-based column of `offset` within its line, in UTF-16 code units. */
|
|
74
|
+
column;
|
|
75
|
+
/**
|
|
76
|
+
* The opening tags still open at `offset`, outermost first, as written. For
|
|
77
|
+
* overlapping plugin tags, only plugin tags are named.
|
|
78
|
+
*/
|
|
79
|
+
openTags;
|
|
80
|
+
constructor(reason, markup, offset, openTags) {
|
|
81
|
+
const lineStart = markup.lastIndexOf("\n", offset - 1) + 1;
|
|
82
|
+
const lineEnd = markup.indexOf("\n", offset);
|
|
83
|
+
const text = markup.slice(lineStart, lineEnd === -1 ? markup.length : lineEnd);
|
|
84
|
+
const line = countNewlines(markup.slice(0, lineStart)) + 1;
|
|
85
|
+
const column = offset - lineStart + 1;
|
|
86
|
+
const open = openTags.length > 0 ? openTags.join(" ") : "none";
|
|
87
|
+
super(`${printable(reason)} (line ${line}, column ${column})\n` +
|
|
88
|
+
`${excerpt(text, offset - lineStart)}\n` +
|
|
89
|
+
`Open tags: ${printable(open)}`);
|
|
90
|
+
this.name = "MarkupSyntaxError";
|
|
91
|
+
this.reason = reason;
|
|
92
|
+
this.markup = markup;
|
|
93
|
+
this.offset = offset;
|
|
94
|
+
this.line = line;
|
|
95
|
+
this.column = column;
|
|
96
|
+
this.openTags = openTags;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function countNewlines(text) {
|
|
100
|
+
return text.split("\n").length - 1;
|
|
101
|
+
}
|
|
102
|
+
// Code points shown either side of the error before the excerpt is cut and
|
|
103
|
+
// marked with an ellipsis. Enough to recognise the surrounding tags on a line
|
|
104
|
+
// that is long, templated, or machine-assembled, without printing all of it.
|
|
105
|
+
const EXCERPT_CONTEXT = 30;
|
|
106
|
+
/**
|
|
107
|
+
* `text` with each C0 control and DEL shown as one space.
|
|
108
|
+
*
|
|
109
|
+
* [LAW:single-enforcer] Every part of the message that quotes the caller's
|
|
110
|
+
* markup passes through here: the reason and the open tags quote tag text, and
|
|
111
|
+
* the tag grammar admits control characters, so an `ESC ]` inside a tag would
|
|
112
|
+
* otherwise open an OSC sequence in the terminal printing the error. In the
|
|
113
|
+
* excerpt it also keeps the caret aligned, since a control occupies no cell.
|
|
114
|
+
* The fields keep the text as written; only the message is made printable.
|
|
115
|
+
*/
|
|
116
|
+
function printable(text) {
|
|
117
|
+
return text.replace(/[\x00-\x1f\x7f]/g, " ");
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Two lines: the source line around `index`, and a caret under it.
|
|
121
|
+
*
|
|
122
|
+
* The window is cut in code points, so a surrogate pair is never split, and the
|
|
123
|
+
* caret is placed by cell width, so a wide character before the error still
|
|
124
|
+
* leaves it under the right cell.
|
|
125
|
+
*/
|
|
126
|
+
function excerpt(text, index) {
|
|
127
|
+
const chars = Array.from(printable(text));
|
|
128
|
+
const at = Array.from(text.slice(0, index)).length;
|
|
129
|
+
const from = Math.max(0, at - EXCERPT_CONTEXT);
|
|
130
|
+
const to = Math.min(chars.length, at + EXCERPT_CONTEXT);
|
|
131
|
+
const head = from > 0 ? "…" : "";
|
|
132
|
+
const tail = to < chars.length ? "…" : "";
|
|
133
|
+
const before = head + chars.slice(from, at).join("");
|
|
134
|
+
const shown = before + chars.slice(at, to).join("") + tail;
|
|
135
|
+
return ` ${shown}\n ${" ".repeat(cellLen(before))}^`;
|
|
136
|
+
}
|
|
48
137
|
// --- escape ---
|
|
49
138
|
/**
|
|
50
139
|
* Escapes markup characters so they render as literal text.
|
|
@@ -106,6 +195,9 @@ function parseTags(markup) {
|
|
|
106
195
|
}
|
|
107
196
|
return tags;
|
|
108
197
|
}
|
|
198
|
+
function wholeString(markup) {
|
|
199
|
+
return { source: markup, base: 0, enclosing: [] };
|
|
200
|
+
}
|
|
109
201
|
/**
|
|
110
202
|
* Parses the built-in style dialect — `[bold red]text[/bold red]` — into a
|
|
111
203
|
* `RichText` with styled spans. Module-private on purpose: `renderMarkup` is
|
|
@@ -127,7 +219,7 @@ function parseTags(markup) {
|
|
|
127
219
|
* function names; an empty registry puts it in the data, where the one
|
|
128
220
|
* boundary admits both.
|
|
129
221
|
*/
|
|
130
|
-
function render(markup, baseStyle, options) {
|
|
222
|
+
function render(markup, origin, baseStyle, options) {
|
|
131
223
|
// Fast path: no brackets at all
|
|
132
224
|
if (!HAS_TAG_RE.test(markup)) {
|
|
133
225
|
let text = markup;
|
|
@@ -145,6 +237,10 @@ function render(markup, baseStyle, options) {
|
|
|
145
237
|
let plainText = "";
|
|
146
238
|
const spans = [];
|
|
147
239
|
const openStack = [];
|
|
240
|
+
const unparsable = (reason, tag) => new MarkupSyntaxError(reason, origin.source, origin.base + tag.start, [
|
|
241
|
+
...origin.enclosing,
|
|
242
|
+
...openStack.map((opened) => opened.tag),
|
|
243
|
+
]);
|
|
148
244
|
let lastEnd = 0;
|
|
149
245
|
for (const tag of tags) {
|
|
150
246
|
// Add text before this tag
|
|
@@ -154,7 +250,9 @@ function render(markup, baseStyle, options) {
|
|
|
154
250
|
if (tag.isImplicitClose) {
|
|
155
251
|
// [/] — close the most recent open tag
|
|
156
252
|
if (openStack.length === 0) {
|
|
157
|
-
|
|
253
|
+
// [/] never closes a plugin tag, so an enclosing plugin tag can be open
|
|
254
|
+
// here while no style tag is.
|
|
255
|
+
throw unparsable(`Closing tag ${tag.fullMatch} has no open style tag to close`, tag);
|
|
158
256
|
}
|
|
159
257
|
const opened = openStack.pop();
|
|
160
258
|
spans.push(new Span(opened.textStart, plainText.length, openTagStyle(opened)));
|
|
@@ -163,7 +261,7 @@ function render(markup, baseStyle, options) {
|
|
|
163
261
|
// [/style] — find and close matching open tag
|
|
164
262
|
const idx = findLastOpen(openStack, tag.styleName);
|
|
165
263
|
if (idx === -1) {
|
|
166
|
-
throw
|
|
264
|
+
throw unparsable(`Closing tag ${tag.fullMatch} doesn't match any open tag`, tag);
|
|
167
265
|
}
|
|
168
266
|
const opened = openStack[idx];
|
|
169
267
|
spans.push(new Span(opened.textStart, plainText.length, openTagStyle(opened)));
|
|
@@ -172,6 +270,7 @@ function render(markup, baseStyle, options) {
|
|
|
172
270
|
else {
|
|
173
271
|
// Opening tag
|
|
174
272
|
openStack.push({
|
|
273
|
+
tag: tag.fullMatch,
|
|
175
274
|
styleName: tag.styleName,
|
|
176
275
|
parameters: tag.parameters,
|
|
177
276
|
textStart: plainText.length,
|
|
@@ -390,20 +489,23 @@ function parsePluginAttrs(after) {
|
|
|
390
489
|
* append path is uniform.
|
|
391
490
|
*/
|
|
392
491
|
export function renderMarkup(markup, options) {
|
|
492
|
+
return renderSlice(markup, wholeString(markup), options);
|
|
493
|
+
}
|
|
494
|
+
function renderSlice(markup, origin, options) {
|
|
393
495
|
const registry = options?.registry ?? globalMarkupRegistry;
|
|
394
496
|
const baseStyle = options?.baseStyle;
|
|
395
497
|
const doEmoji = options?.emoji !== false;
|
|
396
498
|
// Fast path: no `[` at all → no possible tags.
|
|
397
499
|
if (!HAS_TAG_RE.test(markup)) {
|
|
398
|
-
return render(markup, baseStyle, { emoji: doEmoji });
|
|
500
|
+
return render(markup, origin, baseStyle, { emoji: doEmoji });
|
|
399
501
|
}
|
|
400
502
|
const tags = parseTags(markup);
|
|
401
503
|
// Pair each opening plugin tag with its matching closer up-front, so the
|
|
402
504
|
// splice walk can just iterate top-level pairs in source order with no
|
|
403
505
|
// nested-state book-keeping.
|
|
404
|
-
const { annotated, topLevel: tagPairs } = pairPluginTags(tags, registry);
|
|
506
|
+
const { annotated, topLevel: tagPairs } = pairPluginTags(tags, registry, origin);
|
|
405
507
|
if (tagPairs.size === 0) {
|
|
406
|
-
return render(markup, baseStyle, { emoji: doEmoji });
|
|
508
|
+
return render(markup, origin, baseStyle, { emoji: doEmoji });
|
|
407
509
|
}
|
|
408
510
|
// [LAW:one-type-per-behavior] Always assemble a single RichText. Fragments
|
|
409
511
|
// between plugin pairs are parsed by the built-in `render`, plugin pairs
|
|
@@ -415,22 +517,26 @@ export function renderMarkup(markup, options) {
|
|
|
415
517
|
const open = annotated[openIdx];
|
|
416
518
|
const close = annotated[closeIdx];
|
|
417
519
|
if (open.start > cursor) {
|
|
418
|
-
out.append(render(markup.slice(cursor, open.start), baseStyle, { emoji: doEmoji }));
|
|
520
|
+
out.append(render(markup.slice(cursor, open.start), offsetBy(origin, cursor), baseStyle, { emoji: doEmoji }));
|
|
419
521
|
}
|
|
420
522
|
const innerRaw = markup.slice(open.end, close.start);
|
|
421
|
-
const innerRichText =
|
|
523
|
+
const innerRichText = renderSlice(innerRaw, { ...offsetBy(origin, open.end), enclosing: [...origin.enclosing, open.fullMatch] }, options);
|
|
422
524
|
const handler = registry.get(open.pluginName);
|
|
423
525
|
out.append(handler({ attrs: open.attrs, children: innerRichText, raw: innerRaw }));
|
|
424
526
|
cursor = close.end;
|
|
425
527
|
}
|
|
426
528
|
if (cursor < markup.length) {
|
|
427
|
-
out.append(render(markup.slice(cursor), baseStyle, { emoji: doEmoji }));
|
|
529
|
+
out.append(render(markup.slice(cursor), offsetBy(origin, cursor), baseStyle, { emoji: doEmoji }));
|
|
428
530
|
}
|
|
429
531
|
if (baseStyle)
|
|
430
532
|
out.stylize(baseStyle);
|
|
431
533
|
return out;
|
|
432
534
|
}
|
|
433
|
-
|
|
535
|
+
/** The origin of a sub-slice starting `offset` into the slice `origin` describes. */
|
|
536
|
+
function offsetBy(origin, offset) {
|
|
537
|
+
return { ...origin, base: origin.base + offset };
|
|
538
|
+
}
|
|
539
|
+
function pairPluginTags(tags, registry, origin) {
|
|
434
540
|
// Annotate tags with plugin info, then pair openers with closers. Only
|
|
435
541
|
// top-level pairs are returned; inner pairs will be re-discovered by the
|
|
436
542
|
// recursive `renderMarkup` call on the inner slice.
|
|
@@ -477,11 +583,18 @@ function pairPluginTags(tags, registry) {
|
|
|
477
583
|
// hand it. A style span may overlap because it annotates; a plugin pair
|
|
478
584
|
// may not because it replaces.
|
|
479
585
|
if (annotated[closeIdx].end > outerEnd) {
|
|
480
|
-
const inner = annotated[openIdx]
|
|
481
|
-
const outer = annotated[outerOpenIdx]
|
|
482
|
-
|
|
586
|
+
const inner = annotated[openIdx];
|
|
587
|
+
const outer = annotated[outerOpenIdx];
|
|
588
|
+
// The caret goes under the outer pair's closing tag, the point where
|
|
589
|
+
// nesting breaks. This pass sees plugin tags only; of those, a pair is
|
|
590
|
+
// open there when it opens before that tag and closes at or after it.
|
|
591
|
+
const caret = annotated[pairs.get(outerOpenIdx)].start;
|
|
592
|
+
const openAtCaret = sortedOpens
|
|
593
|
+
.filter((o) => annotated[o].start < caret && annotated[pairs.get(o)].start >= caret)
|
|
594
|
+
.map((o) => annotated[o].fullMatch);
|
|
595
|
+
throw new MarkupSyntaxError(`Plugin tag [${inner.pluginName}] overlaps [${outer.pluginName}]: plugin tags must nest, ` +
|
|
483
596
|
`because a handler receives one contiguous slice. ` +
|
|
484
|
-
`Close [/${inner}] before [/${outer}]
|
|
597
|
+
`Close [/${inner.pluginName}] before [/${outer.pluginName}].`, origin.source, origin.base + caret, [...origin.enclosing, ...openAtCaret]);
|
|
485
598
|
}
|
|
486
599
|
continue;
|
|
487
600
|
}
|
|
@@ -504,4 +617,3 @@ function annotatePluginTag(tag, registry) {
|
|
|
504
617
|
const attrs = tag.isClosing ? {} : parsePluginAttrs(after);
|
|
505
618
|
return { ...tag, pluginName: name, attrs };
|
|
506
619
|
}
|
|
507
|
-
//# sourceMappingURL=markup.js.map
|
package/dist/core/measure.d.ts
CHANGED
package/dist/core/measure.js
CHANGED
package/dist/core/oklch.d.ts
CHANGED
package/dist/core/oklch.js
CHANGED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The OSC 8 hyperlink wire grammar — which bytes may not appear inside a
|
|
3
|
+
* link, how a link becomes bytes, and how bytes are read back as a link.
|
|
4
|
+
*
|
|
5
|
+
* [LAW:one-source-of-truth] Every producer (`segmentsToString`,
|
|
6
|
+
* `Style.render`) opens a link through `osc8Open` and closes it with
|
|
7
|
+
* `OSC8_CLOSE`; every consumer that reads rendered bytes (a width measure, a
|
|
8
|
+
* test extracting URLs) matches them with `OSC8`; the data-model boundary
|
|
9
|
+
* (RichText) cleans URLs with `stripOscTerminators`. All four read the one
|
|
10
|
+
* terminator set below, so the bytes a sanitizer removes and the bytes a
|
|
11
|
+
* reader stops at cannot disagree.
|
|
12
|
+
*
|
|
13
|
+
* [LAW:locality-or-seam] This module depends on nothing; every consumer
|
|
14
|
+
* imports downward.
|
|
15
|
+
*
|
|
16
|
+
* The `id=` parameter is what makes one link hover as one link. A terminal
|
|
17
|
+
* treats cells as the same hyperlink when they share BOTH the URI and the id
|
|
18
|
+
* (the OSC 8 spec, and VTE / iTerm2 / kitty / WezTerm alike); without an id,
|
|
19
|
+
* each open sequence is its own link. The coalescer can only share one OSC 8
|
|
20
|
+
* pair across a run of identical SGR, so a link whose text changes style
|
|
21
|
+
* mid-span — a bold glyph beside plain text, a padded cell — is emitted as
|
|
22
|
+
* several pairs, and would highlight piecewise on hover.
|
|
23
|
+
*
|
|
24
|
+
* [LAW:types-are-the-program] The id is a pure function of the URI, so the
|
|
25
|
+
* byte stream stays a pure function of (style, text, colorSystem): no counter,
|
|
26
|
+
* no construction-order dependence. The consequence is deliberate: two spans
|
|
27
|
+
* with the same URI hover as one link wherever they sit on screen, adjacent
|
|
28
|
+
* or not — a click on either does the same thing, so they ARE one link.
|
|
29
|
+
* Because terminals key on the (id, URI) PAIR, a hash collision between two
|
|
30
|
+
* different URIs merges nothing — the URIs still differ — so a 32-bit hash is
|
|
31
|
+
* exact, not approximate.
|
|
32
|
+
*/
|
|
33
|
+
/** Remove every byte that could break a URL out of its OSC 8 wrap. */
|
|
34
|
+
export declare function stripOscTerminators(url: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* The bytes that open a hyperlink to `url`.
|
|
37
|
+
*
|
|
38
|
+
* [LAW:single-enforcer] Wire-byte trust boundary — the URL is sanitized at
|
|
39
|
+
* the one place it becomes an OSC 8 sequence, whichever upstream API attached
|
|
40
|
+
* it to the Style (RichText sanitizes at its data-model boundary too; a Style
|
|
41
|
+
* built directly with `new Style({ link })` reaches here unsanitized).
|
|
42
|
+
*/
|
|
43
|
+
export declare function osc8Open(url: string): string;
|
|
44
|
+
/** The bytes that close the current hyperlink. */
|
|
45
|
+
export declare const OSC8_CLOSE = "\u001B]8;;\u001B\\";
|
|
46
|
+
/**
|
|
47
|
+
* Matches one OSC 8 sequence — an open or a close — terminated by `ESC \`,
|
|
48
|
+
* BEL, or the 8-bit ST. Group 1 is the params, group 2 the URI; both are empty
|
|
49
|
+
* on a close. To read bytes back use `osc8Sequences`; this pattern is exported
|
|
50
|
+
* for composing into a larger one (a width measure's zero-width alternation).
|
|
51
|
+
*/
|
|
52
|
+
export declare const OSC8: RegExp;
|
|
53
|
+
/** One OSC 8 sequence found in rendered bytes. A close has `uri === ""`. */
|
|
54
|
+
export interface Osc8Sequence {
|
|
55
|
+
/** Offset of the sequence in the scanned string. */
|
|
56
|
+
readonly index: number;
|
|
57
|
+
/** Length of the whole sequence, terminator included. */
|
|
58
|
+
readonly length: number;
|
|
59
|
+
/** The params field (`id=…`); empty on a close. */
|
|
60
|
+
readonly params: string;
|
|
61
|
+
/** The URI; empty on a close. */
|
|
62
|
+
readonly uri: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Every OSC 8 sequence in `text`, in order — the typed way to read rendered
|
|
66
|
+
* bytes back.
|
|
67
|
+
*/
|
|
68
|
+
export declare function osc8Sequences(text: string): Osc8Sequence[];
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The OSC 8 hyperlink wire grammar — which bytes may not appear inside a
|
|
3
|
+
* link, how a link becomes bytes, and how bytes are read back as a link.
|
|
4
|
+
*
|
|
5
|
+
* [LAW:one-source-of-truth] Every producer (`segmentsToString`,
|
|
6
|
+
* `Style.render`) opens a link through `osc8Open` and closes it with
|
|
7
|
+
* `OSC8_CLOSE`; every consumer that reads rendered bytes (a width measure, a
|
|
8
|
+
* test extracting URLs) matches them with `OSC8`; the data-model boundary
|
|
9
|
+
* (RichText) cleans URLs with `stripOscTerminators`. All four read the one
|
|
10
|
+
* terminator set below, so the bytes a sanitizer removes and the bytes a
|
|
11
|
+
* reader stops at cannot disagree.
|
|
12
|
+
*
|
|
13
|
+
* [LAW:locality-or-seam] This module depends on nothing; every consumer
|
|
14
|
+
* imports downward.
|
|
15
|
+
*
|
|
16
|
+
* The `id=` parameter is what makes one link hover as one link. A terminal
|
|
17
|
+
* treats cells as the same hyperlink when they share BOTH the URI and the id
|
|
18
|
+
* (the OSC 8 spec, and VTE / iTerm2 / kitty / WezTerm alike); without an id,
|
|
19
|
+
* each open sequence is its own link. The coalescer can only share one OSC 8
|
|
20
|
+
* pair across a run of identical SGR, so a link whose text changes style
|
|
21
|
+
* mid-span — a bold glyph beside plain text, a padded cell — is emitted as
|
|
22
|
+
* several pairs, and would highlight piecewise on hover.
|
|
23
|
+
*
|
|
24
|
+
* [LAW:types-are-the-program] The id is a pure function of the URI, so the
|
|
25
|
+
* byte stream stays a pure function of (style, text, colorSystem): no counter,
|
|
26
|
+
* no construction-order dependence. The consequence is deliberate: two spans
|
|
27
|
+
* with the same URI hover as one link wherever they sit on screen, adjacent
|
|
28
|
+
* or not — a click on either does the same thing, so they ARE one link.
|
|
29
|
+
* Because terminals key on the (id, URI) PAIR, a hash collision between two
|
|
30
|
+
* different URIs merges nothing — the URIs still differ — so a 32-bit hash is
|
|
31
|
+
* exact, not approximate.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* The bytes that prematurely terminate an OSC 8 sequence: ESC (`\x1b`, which
|
|
35
|
+
* begins ST `ESC \`), BEL (`\x07`), and the 8-bit ST (`\x9c`).
|
|
36
|
+
*/
|
|
37
|
+
const TERMINATORS = "\\x1b\\x07\\x9c";
|
|
38
|
+
const TERMINATOR_RE = new RegExp(`[${TERMINATORS}]`, "g");
|
|
39
|
+
/** Remove every byte that could break a URL out of its OSC 8 wrap. */
|
|
40
|
+
export function stripOscTerminators(url) {
|
|
41
|
+
return url.replace(TERMINATOR_RE, "");
|
|
42
|
+
}
|
|
43
|
+
const utf8 = new TextEncoder();
|
|
44
|
+
/**
|
|
45
|
+
* FNV-1a, 32-bit, over the URL's UTF-8 bytes, as 8 lowercase hex digits — a
|
|
46
|
+
* legal `id=` value (no `:`/`;`). UTF-8 rather than UTF-16 code units, so any
|
|
47
|
+
* other runtime computing standard FNV-1a over the same URL gets the same id.
|
|
48
|
+
*/
|
|
49
|
+
function linkId(url) {
|
|
50
|
+
let h = 0x811c9dc5;
|
|
51
|
+
for (const byte of utf8.encode(url)) {
|
|
52
|
+
h ^= byte;
|
|
53
|
+
h = Math.imul(h, 0x01000193);
|
|
54
|
+
}
|
|
55
|
+
return (h >>> 0).toString(16).padStart(8, "0");
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The bytes that open a hyperlink to `url`.
|
|
59
|
+
*
|
|
60
|
+
* [LAW:single-enforcer] Wire-byte trust boundary — the URL is sanitized at
|
|
61
|
+
* the one place it becomes an OSC 8 sequence, whichever upstream API attached
|
|
62
|
+
* it to the Style (RichText sanitizes at its data-model boundary too; a Style
|
|
63
|
+
* built directly with `new Style({ link })` reaches here unsanitized).
|
|
64
|
+
*/
|
|
65
|
+
export function osc8Open(url) {
|
|
66
|
+
const clean = stripOscTerminators(url);
|
|
67
|
+
return `\x1b]8;id=${linkId(clean)};${clean}\x1b\\`;
|
|
68
|
+
}
|
|
69
|
+
/** The bytes that close the current hyperlink. */
|
|
70
|
+
export const OSC8_CLOSE = "\x1b]8;;\x1b\\";
|
|
71
|
+
/**
|
|
72
|
+
* Matches one OSC 8 sequence — an open or a close — terminated by `ESC \`,
|
|
73
|
+
* BEL, or the 8-bit ST. Group 1 is the params, group 2 the URI; both are empty
|
|
74
|
+
* on a close. To read bytes back use `osc8Sequences`; this pattern is exported
|
|
75
|
+
* for composing into a larger one (a width measure's zero-width alternation).
|
|
76
|
+
*/
|
|
77
|
+
export const OSC8 = new RegExp(`\\x1b\\]8;([^;${TERMINATORS}]*);([^${TERMINATORS}]*)(?:\\x1b\\\\|\\x07|\\x9c)`);
|
|
78
|
+
const OSC8_SCAN = new RegExp(OSC8.source, "g");
|
|
79
|
+
/**
|
|
80
|
+
* Every OSC 8 sequence in `text`, in order — the typed way to read rendered
|
|
81
|
+
* bytes back.
|
|
82
|
+
*/
|
|
83
|
+
export function osc8Sequences(text) {
|
|
84
|
+
return [...text.matchAll(OSC8_SCAN)].map((m) => ({
|
|
85
|
+
index: m.index,
|
|
86
|
+
length: m[0].length,
|
|
87
|
+
params: m[1],
|
|
88
|
+
uri: m[2],
|
|
89
|
+
}));
|
|
90
|
+
}
|
package/dist/core/pretty.d.ts
CHANGED
|
@@ -97,7 +97,27 @@ export declare class Pretty implements Renderable, Measurable {
|
|
|
97
97
|
private _format;
|
|
98
98
|
/** The arms for a non-null object, with `value` already on the open path. */
|
|
99
99
|
private _formatObject;
|
|
100
|
-
/**
|
|
100
|
+
/**
|
|
101
|
+
* One position, with every value in it laid out.
|
|
102
|
+
*
|
|
103
|
+
* `at.column` is where `slot.head` begins. Rather than hand-tracking a
|
|
104
|
+
* second, parallel "column so far" alongside `out` — two values a future
|
|
105
|
+
* edit could update out of step, silently reintroducing a stale-budget bug
|
|
106
|
+
* of the same shape this file just fixed — each hole derives its own
|
|
107
|
+
* column fresh from `out` via `lineColumn`, the one place "text just
|
|
108
|
+
* emitted" becomes "column now". [LAW:one-source-of-truth] `out` is
|
|
109
|
+
* already the complete record; `column` is a read of it, not a second copy
|
|
110
|
+
* of the same fact. `_joinOneLine`'s probe sibling tracks the analogous
|
|
111
|
+
* thing via `budget` shrinking instead, because a probe already discards
|
|
112
|
+
* anything that overruns and so never needs to know where a multi-line
|
|
113
|
+
* insert's last line ends.
|
|
114
|
+
*
|
|
115
|
+
* Each hole's `reserve` is its own `tail` plus, only for the slot's last
|
|
116
|
+
* hole, whatever `at.reserve` already asked this whole slot to leave room
|
|
117
|
+
* for (`_formatObject`'s trailing `,`). An earlier hole's tail is never
|
|
118
|
+
* folded into a later hole's reserve — it is spent, not carried, the moment
|
|
119
|
+
* `out` grows past it.
|
|
120
|
+
*/
|
|
101
121
|
private _expandSlot;
|
|
102
122
|
/** The one-line form of a value, or `null` when it will not fit `at.budget`. */
|
|
103
123
|
private _oneLine;
|
|
@@ -119,4 +139,3 @@ export declare class Pretty implements Renderable, Measurable {
|
|
|
119
139
|
private _slotOneLine;
|
|
120
140
|
private _addIndentGuides;
|
|
121
141
|
}
|
|
122
|
-
//# sourceMappingURL=pretty.d.ts.map
|
package/dist/core/pretty.js
CHANGED
|
@@ -20,7 +20,28 @@ import { cellLen } from "./cells.js";
|
|
|
20
20
|
import { RichText } from "./text.js";
|
|
21
21
|
import { ReprHighlighter } from "./highlighter.js";
|
|
22
22
|
const reprHighlighter = new ReprHighlighter();
|
|
23
|
-
const rootFrame = (maxWidth) => ({
|
|
23
|
+
const rootFrame = (maxWidth) => ({
|
|
24
|
+
inset: 0,
|
|
25
|
+
level: 0,
|
|
26
|
+
maxWidth,
|
|
27
|
+
column: 0,
|
|
28
|
+
reserve: 0,
|
|
29
|
+
open: new WeakSet(),
|
|
30
|
+
});
|
|
31
|
+
/**
|
|
32
|
+
* The column reached after `text` is appended to a line currently at `column`.
|
|
33
|
+
*
|
|
34
|
+
* A hole's formatted value can itself span multiple lines — a nested
|
|
35
|
+
* container that failed its own compact try. What comes after it (a Map
|
|
36
|
+
* entry's tail, the next hole) sits after `text`'s *last* line, not at
|
|
37
|
+
* `column + cellLen(text)`, so a multi-line insert resets the column to that
|
|
38
|
+
* last line's width instead of accumulating across lines that were never on
|
|
39
|
+
* the same row.
|
|
40
|
+
*/
|
|
41
|
+
function lineColumn(column, text) {
|
|
42
|
+
const lastNewline = text.lastIndexOf("\n");
|
|
43
|
+
return lastNewline === -1 ? column + cellLen(text) : cellLen(text.slice(lastNewline + 1));
|
|
44
|
+
}
|
|
24
45
|
/**
|
|
25
46
|
* The text, when it is a single line no wider than `budget` — otherwise `null`.
|
|
26
47
|
*
|
|
@@ -111,6 +132,15 @@ function describesItself(value) {
|
|
|
111
132
|
}
|
|
112
133
|
/** What joins a container's positions on one line. Its width is charged for, so it is named once. */
|
|
113
134
|
const SEPARATOR = ", ";
|
|
135
|
+
/**
|
|
136
|
+
* What joins a container's positions across lines, once it has expanded —
|
|
137
|
+
* `SEPARATOR` without the trailing space a newline already provides.
|
|
138
|
+
* rich-pretty-xms: named, and derived rather than a second hand-typed
|
|
139
|
+
* literal, so the width it costs a non-last slot's own compact try —
|
|
140
|
+
* reserved via `Frame.reserve` — cannot drift from what `_formatObject`
|
|
141
|
+
* actually joins with.
|
|
142
|
+
*/
|
|
143
|
+
const EXPAND_SEPARATOR = SEPARATOR.trimEnd();
|
|
114
144
|
/** The marker for positions the bound dropped, or nothing when it dropped none. */
|
|
115
145
|
const elided = (dropped) => dropped > 0 ? [{ head: `... +${dropped}`, holes: [] }] : [];
|
|
116
146
|
export class Pretty {
|
|
@@ -165,11 +195,15 @@ export class Pretty {
|
|
|
165
195
|
return "undefined";
|
|
166
196
|
switch (typeof value) {
|
|
167
197
|
case "string": {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
198
|
+
// The dropped count is an annotation about the value, not part of it,
|
|
199
|
+
// so it goes after the closing quote — the way `maxLength`'s `... +N`
|
|
200
|
+
// sits beside the kept entries rather than inside the last one. Inside
|
|
201
|
+
// the quotes it reads as content, and copying it out yields a string
|
|
202
|
+
// the program never held.
|
|
203
|
+
if (this.maxString === undefined || value.length <= this.maxString) {
|
|
204
|
+
return JSON.stringify(value);
|
|
171
205
|
}
|
|
172
|
-
return JSON.stringify(
|
|
206
|
+
return JSON.stringify(value.slice(0, this.maxString)) + `+${value.length - this.maxString}`;
|
|
173
207
|
}
|
|
174
208
|
case "number":
|
|
175
209
|
case "bigint":
|
|
@@ -281,35 +315,76 @@ export class Pretty {
|
|
|
281
315
|
const shape = this._shape(value, at.level, Infinity);
|
|
282
316
|
if (shape.kind === "text")
|
|
283
317
|
return shape.text;
|
|
284
|
-
const indentStr = " ".repeat(this.indent * at.inset);
|
|
285
318
|
if (!this.expandAll) {
|
|
319
|
+
// rich-pretty-xms: budget from `at.column` and `at.reserve`, not from
|
|
320
|
+
// `at.inset` alone. `at.column` is where *this* value's text actually
|
|
321
|
+
// starts on its line, which already includes whatever `_expandSlot`
|
|
322
|
+
// prepended for us (a key and its separator, a Map entry's `" => "`,
|
|
323
|
+
// ...); `at.reserve` is what a fixed piece of literal text — the rest
|
|
324
|
+
// of this hole's own tail, or the trailing `,` a non-last slot gets
|
|
325
|
+
// joined with — is known to cost right after, before this value's own
|
|
326
|
+
// text even starts. Deriving the budget from the indent alone assumed
|
|
327
|
+
// nothing precedes *or follows* the value on its line, which is only
|
|
328
|
+
// true at the root and for the last hole of an array's last element.
|
|
286
329
|
const compact = this._joinOneLine(shape, {
|
|
287
330
|
level: at.level + 1,
|
|
288
|
-
budget: at.maxWidth -
|
|
331
|
+
budget: at.maxWidth - at.column - at.reserve,
|
|
289
332
|
open: at.open,
|
|
290
333
|
});
|
|
291
334
|
if (compact !== null)
|
|
292
335
|
return compact;
|
|
293
336
|
}
|
|
337
|
+
const indentStr = " ".repeat(this.indent * at.inset);
|
|
294
338
|
const innerIndent = " ".repeat(this.indent * (at.inset + 1));
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
339
|
+
// Every non-last slot gets `EXPAND_SEPARATOR` appended right after it
|
|
340
|
+
// below, on the same line as whatever its own last character was — the
|
|
341
|
+
// last slot doesn't. Two frames, not one per slot: `reserve` is the only
|
|
342
|
+
// field that varies, and it only ever takes these two values.
|
|
343
|
+
const base = { inset: at.inset + 1, level: at.level + 1, maxWidth: at.maxWidth, column: cellLen(innerIndent), open: at.open };
|
|
344
|
+
const midFrame = { ...base, reserve: cellLen(EXPAND_SEPARATOR) };
|
|
345
|
+
const lastFrame = { ...base, reserve: 0 };
|
|
346
|
+
const lastSlot = shape.slots.length - 1;
|
|
347
|
+
const parts = shape.slots.map((slot, i) => innerIndent + this._expandSlot(slot, i === lastSlot ? lastFrame : midFrame));
|
|
348
|
+
return shape.open + "\n" + parts.join(EXPAND_SEPARATOR + "\n") + "\n" + indentStr + shape.close;
|
|
298
349
|
}
|
|
299
|
-
/**
|
|
350
|
+
/**
|
|
351
|
+
* One position, with every value in it laid out.
|
|
352
|
+
*
|
|
353
|
+
* `at.column` is where `slot.head` begins. Rather than hand-tracking a
|
|
354
|
+
* second, parallel "column so far" alongside `out` — two values a future
|
|
355
|
+
* edit could update out of step, silently reintroducing a stale-budget bug
|
|
356
|
+
* of the same shape this file just fixed — each hole derives its own
|
|
357
|
+
* column fresh from `out` via `lineColumn`, the one place "text just
|
|
358
|
+
* emitted" becomes "column now". [LAW:one-source-of-truth] `out` is
|
|
359
|
+
* already the complete record; `column` is a read of it, not a second copy
|
|
360
|
+
* of the same fact. `_joinOneLine`'s probe sibling tracks the analogous
|
|
361
|
+
* thing via `budget` shrinking instead, because a probe already discards
|
|
362
|
+
* anything that overruns and so never needs to know where a multi-line
|
|
363
|
+
* insert's last line ends.
|
|
364
|
+
*
|
|
365
|
+
* Each hole's `reserve` is its own `tail` plus, only for the slot's last
|
|
366
|
+
* hole, whatever `at.reserve` already asked this whole slot to leave room
|
|
367
|
+
* for (`_formatObject`'s trailing `,`). An earlier hole's tail is never
|
|
368
|
+
* folded into a later hole's reserve — it is spent, not carried, the moment
|
|
369
|
+
* `out` grows past it.
|
|
370
|
+
*/
|
|
300
371
|
_expandSlot(slot, at) {
|
|
301
372
|
let out = slot.head;
|
|
302
|
-
|
|
373
|
+
const lastHole = slot.holes.length - 1;
|
|
374
|
+
for (let i = 0; i < slot.holes.length; i++) {
|
|
375
|
+
const hole = slot.holes[i];
|
|
303
376
|
// This is the read that costs the least when it fails: neighbours are
|
|
304
377
|
// unaffected, so `{ a: 1, b: [Threw: …], c: 3 }` still shows everything
|
|
305
378
|
// that could be read.
|
|
379
|
+
let text;
|
|
306
380
|
try {
|
|
307
|
-
|
|
381
|
+
const reserve = cellLen(hole.tail) + (i === lastHole ? at.reserve : 0);
|
|
382
|
+
text = this._format(hole.read(), { ...at, column: lineColumn(at.column, out), reserve });
|
|
308
383
|
}
|
|
309
384
|
catch (error) {
|
|
310
|
-
|
|
385
|
+
text = threw(error);
|
|
311
386
|
}
|
|
312
|
-
out += hole.tail;
|
|
387
|
+
out += text + hole.tail;
|
|
313
388
|
}
|
|
314
389
|
return out;
|
|
315
390
|
}
|
|
@@ -395,4 +470,3 @@ export class Pretty {
|
|
|
395
470
|
}
|
|
396
471
|
}
|
|
397
472
|
}
|
|
398
|
-
//# sourceMappingURL=pretty.js.map
|
package/dist/core/protocol.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* [LAW:one-source-of-truth] These interfaces are the single authority for the rendering contract.
|
|
4
4
|
*/
|
|
5
5
|
import type { Segment } from "./segment.js";
|
|
6
|
-
import { type Style, type Theme } from "./style.js";
|
|
6
|
+
import { type Style, type StyleSyntaxError, type Theme } from "./style.js";
|
|
7
7
|
export interface RenderOptions {
|
|
8
8
|
/**
|
|
9
9
|
* The cells this renderable may occupy. A count of cells, so a non-negative
|
|
@@ -30,7 +30,24 @@ export interface RenderOptions {
|
|
|
30
30
|
* built-in defaults apply. Read it through `getStyle` rather than directly.
|
|
31
31
|
*/
|
|
32
32
|
theme?: Theme;
|
|
33
|
+
/**
|
|
34
|
+
* Called each time a render degrades a style string it cannot parse to
|
|
35
|
+
* unstyled, once per resolution — a style the render reads twice is reported
|
|
36
|
+
* twice. Absent, the failure passes silently. A handler that throws makes
|
|
37
|
+
* the render strict: the error leaves `render`.
|
|
38
|
+
*/
|
|
39
|
+
onStyleError?: StyleErrorHandler;
|
|
33
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Receives a style error a render would otherwise absorb: the parse failure,
|
|
43
|
+
* and the whole style string it came from — the error names only the token
|
|
44
|
+
* that failed, and `"bold rd"` loses its `bold` too.
|
|
45
|
+
*
|
|
46
|
+
* [LAW:no-mode-explosion] Strict mode is not a second option beside this one.
|
|
47
|
+
* It is a handler that throws, so there is no "strict and a callback" state
|
|
48
|
+
* whose order anyone has to define.
|
|
49
|
+
*/
|
|
50
|
+
export type StyleErrorHandler = (error: StyleSyntaxError, style: string) => void;
|
|
34
51
|
/**
|
|
35
52
|
* The style a `string | Style` stands for in this render.
|
|
36
53
|
*
|
|
@@ -109,4 +126,3 @@ export interface Measurable {
|
|
|
109
126
|
}
|
|
110
127
|
export declare function isRenderable(obj: unknown): obj is Renderable;
|
|
111
128
|
export declare function isMeasurable(obj: unknown): obj is Measurable;
|
|
112
|
-
//# sourceMappingURL=protocol.d.ts.map
|