@react-x11/components 0.1.0 → 0.2.1
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/README.md +979 -21
- package/dist/ansi/cast.d.ts +48 -0
- package/dist/ansi/cast.d.ts.map +1 -0
- package/dist/ansi/cast.js +135 -0
- package/dist/ansi/cast.js.map +1 -0
- package/dist/ansi/index.d.ts +10 -0
- package/dist/ansi/index.d.ts.map +1 -0
- package/dist/ansi/index.js +27 -0
- package/dist/ansi/index.js.map +1 -0
- package/dist/ansi/palette.d.ts +79 -0
- package/dist/ansi/palette.d.ts.map +1 -0
- package/dist/ansi/palette.js +155 -0
- package/dist/ansi/palette.js.map +1 -0
- package/dist/ansi/parse.d.ts +114 -0
- package/dist/ansi/parse.d.ts.map +1 -0
- package/dist/ansi/parse.js +687 -0
- package/dist/ansi/parse.js.map +1 -0
- package/dist/ansi/sgr.d.ts +87 -0
- package/dist/ansi/sgr.d.ts.map +1 -0
- package/dist/ansi/sgr.js +302 -0
- package/dist/ansi/sgr.js.map +1 -0
- package/dist/ansi/utf8.d.ts +15 -0
- package/dist/ansi/utf8.d.ts.map +1 -0
- package/dist/ansi/utf8.js +82 -0
- package/dist/ansi/utf8.js.map +1 -0
- package/dist/calendar/Calendar.d.ts +136 -0
- package/dist/calendar/Calendar.d.ts.map +1 -0
- package/dist/calendar/Calendar.js +460 -0
- package/dist/calendar/Calendar.js.map +1 -0
- package/dist/calendar/DatePicker.d.ts +52 -0
- package/dist/calendar/DatePicker.d.ts.map +1 -0
- package/dist/calendar/DatePicker.js +257 -0
- package/dist/calendar/DatePicker.js.map +1 -0
- package/dist/calendar/dates.d.ts +90 -0
- package/dist/calendar/dates.d.ts.map +1 -0
- package/dist/calendar/dates.js +236 -0
- package/dist/calendar/dates.js.map +1 -0
- package/dist/calendar/index.d.ts +8 -0
- package/dist/calendar/index.d.ts.map +1 -0
- package/dist/calendar/index.js +15 -0
- package/dist/calendar/index.js.map +1 -0
- package/dist/charts/components.d.ts +141 -0
- package/dist/charts/components.d.ts.map +1 -0
- package/dist/charts/components.js +580 -0
- package/dist/charts/components.js.map +1 -0
- package/dist/charts/data.d.ts +198 -0
- package/dist/charts/data.d.ts.map +1 -0
- package/dist/charts/data.js +616 -0
- package/dist/charts/data.js.map +1 -0
- package/dist/charts/hx.d.ts +21 -0
- package/dist/charts/hx.d.ts.map +1 -0
- package/dist/charts/hx.js +15 -0
- package/dist/charts/hx.js.map +1 -0
- package/dist/charts/index.d.ts +27 -0
- package/dist/charts/index.d.ts.map +1 -0
- package/dist/charts/index.js +38 -0
- package/dist/charts/index.js.map +1 -0
- package/dist/charts/node.d.ts +96 -0
- package/dist/charts/node.d.ts.map +1 -0
- package/dist/charts/node.js +720 -0
- package/dist/charts/node.js.map +1 -0
- package/dist/charts/render.d.ts +153 -0
- package/dist/charts/render.d.ts.map +1 -0
- package/dist/charts/render.js +762 -0
- package/dist/charts/render.js.map +1 -0
- package/dist/charts/scale.d.ts +42 -0
- package/dist/charts/scale.d.ts.map +1 -0
- package/dist/charts/scale.js +173 -0
- package/dist/charts/scale.js.map +1 -0
- package/dist/charts/spec.d.ts +91 -0
- package/dist/charts/spec.d.ts.map +1 -0
- package/dist/charts/spec.js +33 -0
- package/dist/charts/spec.js.map +1 -0
- package/dist/charts/timers.d.ts +6 -0
- package/dist/charts/timers.d.ts.map +1 -0
- package/dist/charts/timers.js +14 -0
- package/dist/charts/timers.js.map +1 -0
- package/dist/code/hx.d.ts +24 -0
- package/dist/code/hx.d.ts.map +1 -0
- package/dist/code/hx.js +25 -0
- package/dist/code/hx.js.map +1 -0
- package/dist/code/index.d.ts +45 -0
- package/dist/code/index.d.ts.map +1 -0
- package/dist/code/index.js +93 -0
- package/dist/code/index.js.map +1 -0
- package/dist/code-editor/completion.d.ts +26 -0
- package/dist/code-editor/completion.d.ts.map +1 -0
- package/dist/code-editor/completion.js +129 -0
- package/dist/code-editor/completion.js.map +1 -0
- package/dist/code-editor/doc.d.ts +35 -0
- package/dist/code-editor/doc.d.ts.map +1 -0
- package/dist/code-editor/doc.js +162 -0
- package/dist/code-editor/doc.js.map +1 -0
- package/dist/code-editor/hx.d.ts +13 -0
- package/dist/code-editor/hx.d.ts.map +1 -0
- package/dist/code-editor/hx.js +10 -0
- package/dist/code-editor/hx.js.map +1 -0
- package/dist/code-editor/index.d.ts +88 -0
- package/dist/code-editor/index.d.ts.map +1 -0
- package/dist/code-editor/index.js +401 -0
- package/dist/code-editor/index.js.map +1 -0
- package/dist/code-editor/node.d.ts +343 -0
- package/dist/code-editor/node.d.ts.map +1 -0
- package/dist/code-editor/node.js +1521 -0
- package/dist/code-editor/node.js.map +1 -0
- package/dist/code-language/hljs.d.ts +47 -0
- package/dist/code-language/hljs.d.ts.map +1 -0
- package/dist/code-language/hljs.js +211 -0
- package/dist/code-language/hljs.js.map +1 -0
- package/dist/code-language/index.d.ts +22 -0
- package/dist/code-language/index.d.ts.map +1 -0
- package/dist/code-language/index.js +20 -0
- package/dist/code-language/index.js.map +1 -0
- package/dist/code-language/languages/glsl.d.ts +4 -0
- package/dist/code-language/languages/glsl.d.ts.map +1 -0
- package/dist/code-language/languages/glsl.js +99 -0
- package/dist/code-language/languages/glsl.js.map +1 -0
- package/dist/code-language/languages/javascript.d.ts +10 -0
- package/dist/code-language/languages/javascript.d.ts.map +1 -0
- package/dist/code-language/languages/javascript.js +360 -0
- package/dist/code-language/languages/javascript.js.map +1 -0
- package/dist/code-language/languages/json.d.ts +4 -0
- package/dist/code-language/languages/json.d.ts.map +1 -0
- package/dist/code-language/languages/json.js +68 -0
- package/dist/code-language/languages/json.js.map +1 -0
- package/dist/code-language/languages/shell.d.ts +4 -0
- package/dist/code-language/languages/shell.d.ts.map +1 -0
- package/dist/code-language/languages/shell.js +189 -0
- package/dist/code-language/languages/shell.js.map +1 -0
- package/dist/code-language/languages/sql.d.ts +11 -0
- package/dist/code-language/languages/sql.d.ts.map +1 -0
- package/dist/code-language/languages/sql.js +133 -0
- package/dist/code-language/languages/sql.js.map +1 -0
- package/dist/code-language/lezer.d.ts +24 -0
- package/dist/code-language/lezer.d.ts.map +1 -0
- package/dist/code-language/lezer.js +148 -0
- package/dist/code-language/lezer.js.map +1 -0
- package/dist/code-language/registry.d.ts +19 -0
- package/dist/code-language/registry.d.ts.map +1 -0
- package/dist/code-language/registry.js +56 -0
- package/dist/code-language/registry.js.map +1 -0
- package/dist/code-language/runs.d.ts +35 -0
- package/dist/code-language/runs.d.ts.map +1 -0
- package/dist/code-language/runs.js +88 -0
- package/dist/code-language/runs.js.map +1 -0
- package/dist/code-language/stream.d.ts +70 -0
- package/dist/code-language/stream.d.ts.map +1 -0
- package/dist/code-language/stream.js +243 -0
- package/dist/code-language/stream.js.map +1 -0
- package/dist/code-language/textmate.d.ts +27 -0
- package/dist/code-language/textmate.d.ts.map +1 -0
- package/dist/code-language/textmate.js +86 -0
- package/dist/code-language/textmate.js.map +1 -0
- package/dist/code-language/theme.d.ts +26 -0
- package/dist/code-language/theme.d.ts.map +1 -0
- package/dist/code-language/theme.js +108 -0
- package/dist/code-language/theme.js.map +1 -0
- package/dist/code-language/timers.d.ts +7 -0
- package/dist/code-language/timers.d.ts.map +1 -0
- package/dist/code-language/timers.js +22 -0
- package/dist/code-language/timers.js.map +1 -0
- package/dist/code-language/types.d.ts +173 -0
- package/dist/code-language/types.d.ts.map +1 -0
- package/dist/code-language/types.js +41 -0
- package/dist/code-language/types.js.map +1 -0
- package/dist/codeblock/index.d.ts +69 -0
- package/dist/codeblock/index.d.ts.map +1 -0
- package/dist/codeblock/index.js +85 -0
- package/dist/codeblock/index.js.map +1 -0
- package/dist/color-picker/ColorField.d.ts +39 -0
- package/dist/color-picker/ColorField.d.ts.map +1 -0
- package/dist/color-picker/ColorField.js +239 -0
- package/dist/color-picker/ColorField.js.map +1 -0
- package/dist/color-picker/ColorPicker.d.ts +109 -0
- package/dist/color-picker/ColorPicker.d.ts.map +1 -0
- package/dist/color-picker/ColorPicker.js +757 -0
- package/dist/color-picker/ColorPicker.js.map +1 -0
- package/dist/color-picker/color.d.ts +105 -0
- package/dist/color-picker/color.d.ts.map +1 -0
- package/dist/color-picker/color.js +313 -0
- package/dist/color-picker/color.js.map +1 -0
- package/dist/color-picker/index.d.ts +8 -0
- package/dist/color-picker/index.d.ts.map +1 -0
- package/dist/color-picker/index.js +19 -0
- package/dist/color-picker/index.js.map +1 -0
- package/dist/desktop-calendar/eds.d.ts +102 -0
- package/dist/desktop-calendar/eds.d.ts.map +1 -0
- package/dist/desktop-calendar/eds.js +356 -0
- package/dist/desktop-calendar/eds.js.map +1 -0
- package/dist/desktop-calendar/ical.d.ts +63 -0
- package/dist/desktop-calendar/ical.d.ts.map +1 -0
- package/dist/desktop-calendar/ical.js +57 -0
- package/dist/desktop-calendar/ical.js.map +1 -0
- package/dist/desktop-calendar/index.d.ts +72 -0
- package/dist/desktop-calendar/index.d.ts.map +1 -0
- package/dist/desktop-calendar/index.js +213 -0
- package/dist/desktop-calendar/index.js.map +1 -0
- package/dist/embed/client.d.ts +121 -0
- package/dist/embed/client.d.ts.map +1 -0
- package/dist/embed/client.js +203 -0
- package/dist/embed/client.js.map +1 -0
- package/dist/embed/host.d.ts +96 -0
- package/dist/embed/host.d.ts.map +1 -0
- package/dist/embed/host.js +266 -0
- package/dist/embed/host.js.map +1 -0
- package/dist/embed/index.d.ts +5 -0
- package/dist/embed/index.d.ts.map +1 -0
- package/dist/embed/index.js +13 -0
- package/dist/embed/index.js.map +1 -0
- package/dist/embed/timers.d.ts +6 -0
- package/dist/embed/timers.d.ts.map +1 -0
- package/dist/embed/timers.js +16 -0
- package/dist/embed/timers.js.map +1 -0
- package/dist/flow/draw.d.ts +45 -0
- package/dist/flow/draw.d.ts.map +1 -0
- package/dist/flow/draw.js +295 -0
- package/dist/flow/draw.js.map +1 -0
- package/dist/flow/index.d.ts +64 -0
- package/dist/flow/index.d.ts.map +1 -0
- package/dist/flow/index.js +306 -0
- package/dist/flow/index.js.map +1 -0
- package/dist/flow/model.d.ts +158 -0
- package/dist/flow/model.d.ts.map +1 -0
- package/dist/flow/model.js +566 -0
- package/dist/flow/model.js.map +1 -0
- package/dist/flow/node.d.ts +429 -0
- package/dist/flow/node.d.ts.map +1 -0
- package/dist/flow/node.js +3025 -0
- package/dist/flow/node.js.map +1 -0
- package/dist/flow/paths.d.ts +43 -0
- package/dist/flow/paths.d.ts.map +1 -0
- package/dist/flow/paths.js +327 -0
- package/dist/flow/paths.js.map +1 -0
- package/dist/flow/types.d.ts +561 -0
- package/dist/flow/types.d.ts.map +1 -0
- package/dist/flow/types.js +2 -0
- package/dist/flow/types.js.map +1 -0
- package/dist/formula/hx.d.ts +24 -0
- package/dist/formula/hx.d.ts.map +1 -0
- package/dist/formula/hx.js +25 -0
- package/dist/formula/hx.js.map +1 -0
- package/dist/formula/index.d.ts +74 -0
- package/dist/formula/index.d.ts.map +1 -0
- package/dist/formula/index.js +155 -0
- package/dist/formula/index.js.map +1 -0
- package/dist/formula/katex.d.ts +78 -0
- package/dist/formula/katex.d.ts.map +1 -0
- package/dist/formula/katex.js +130 -0
- package/dist/formula/katex.js.map +1 -0
- package/dist/formula/layout.d.ts +103 -0
- package/dist/formula/layout.d.ts.map +1 -0
- package/dist/formula/layout.js +716 -0
- package/dist/formula/layout.js.map +1 -0
- package/dist/formula/node.d.ts +50 -0
- package/dist/formula/node.d.ts.map +1 -0
- package/dist/formula/node.js +351 -0
- package/dist/formula/node.js.map +1 -0
- package/dist/html/controls.d.ts +48 -0
- package/dist/html/controls.d.ts.map +1 -0
- package/dist/html/controls.js +195 -0
- package/dist/html/controls.js.map +1 -0
- package/dist/html/css/cascade.d.ts +51 -0
- package/dist/html/css/cascade.d.ts.map +1 -0
- package/dist/html/css/cascade.js +552 -0
- package/dist/html/css/cascade.js.map +1 -0
- package/dist/html/css/parse.d.ts +74 -0
- package/dist/html/css/parse.d.ts.map +1 -0
- package/dist/html/css/parse.js +464 -0
- package/dist/html/css/parse.js.map +1 -0
- package/dist/html/css/style.d.ts +142 -0
- package/dist/html/css/style.d.ts.map +1 -0
- package/dist/html/css/style.js +1145 -0
- package/dist/html/css/style.js.map +1 -0
- package/dist/html/css/ua.d.ts +5 -0
- package/dist/html/css/ua.d.ts.map +1 -0
- package/dist/html/css/ua.js +171 -0
- package/dist/html/css/ua.js.map +1 -0
- package/dist/html/css/values.d.ts +78 -0
- package/dist/html/css/values.d.ts.map +1 -0
- package/dist/html/css/values.js +330 -0
- package/dist/html/css/values.js.map +1 -0
- package/dist/html/dom.d.ts +112 -0
- package/dist/html/dom.d.ts.map +1 -0
- package/dist/html/dom.js +374 -0
- package/dist/html/dom.js.map +1 -0
- package/dist/html/hx.d.ts +24 -0
- package/dist/html/hx.d.ts.map +1 -0
- package/dist/html/hx.js +29 -0
- package/dist/html/hx.js.map +1 -0
- package/dist/html/index.d.ts +137 -0
- package/dist/html/index.d.ts.map +1 -0
- package/dist/html/index.js +339 -0
- package/dist/html/index.js.map +1 -0
- package/dist/html/layout/block.d.ts +58 -0
- package/dist/html/layout/block.d.ts.map +1 -0
- package/dist/html/layout/block.js +724 -0
- package/dist/html/layout/block.js.map +1 -0
- package/dist/html/layout/boxes.d.ts +261 -0
- package/dist/html/layout/boxes.d.ts.map +1 -0
- package/dist/html/layout/boxes.js +725 -0
- package/dist/html/layout/boxes.js.map +1 -0
- package/dist/html/layout/flex.d.ts +16 -0
- package/dist/html/layout/flex.d.ts.map +1 -0
- package/dist/html/layout/flex.js +242 -0
- package/dist/html/layout/flex.js.map +1 -0
- package/dist/html/layout/floats.d.ts +50 -0
- package/dist/html/layout/floats.d.ts.map +1 -0
- package/dist/html/layout/floats.js +110 -0
- package/dist/html/layout/floats.js.map +1 -0
- package/dist/html/layout/inline.d.ts +44 -0
- package/dist/html/layout/inline.d.ts.map +1 -0
- package/dist/html/layout/inline.js +700 -0
- package/dist/html/layout/inline.js.map +1 -0
- package/dist/html/layout/table.d.ts +5 -0
- package/dist/html/layout/table.d.ts.map +1 -0
- package/dist/html/layout/table.js +340 -0
- package/dist/html/layout/table.js.map +1 -0
- package/dist/html/node.d.ts +149 -0
- package/dist/html/node.d.ts.map +1 -0
- package/dist/html/node.js +861 -0
- package/dist/html/node.js.map +1 -0
- package/dist/html/paint.d.ts +48 -0
- package/dist/html/paint.d.ts.map +1 -0
- package/dist/html/paint.js +495 -0
- package/dist/html/paint.js.map +1 -0
- package/dist/html/resources.d.ts +48 -0
- package/dist/html/resources.d.ts.map +1 -0
- package/dist/html/resources.js +156 -0
- package/dist/html/resources.js.map +1 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +92 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/heights.d.ts +88 -0
- package/dist/internal/heights.d.ts.map +1 -0
- package/dist/internal/heights.js +203 -0
- package/dist/internal/heights.js.map +1 -0
- package/dist/internal/hx.d.ts +24 -0
- package/dist/internal/hx.d.ts.map +1 -0
- package/dist/internal/hx.js +27 -0
- package/dist/internal/hx.js.map +1 -0
- package/dist/internal/scroll.d.ts +62 -0
- package/dist/internal/scroll.d.ts.map +1 -0
- package/dist/internal/scroll.js +224 -0
- package/dist/internal/scroll.js.map +1 -0
- package/dist/internal/text.d.ts +6 -0
- package/dist/internal/text.d.ts.map +1 -0
- package/dist/internal/text.js +44 -0
- package/dist/internal/text.js.map +1 -0
- package/dist/internal/timers.d.ts +8 -0
- package/dist/internal/timers.d.ts.map +1 -0
- package/dist/internal/timers.js +22 -0
- package/dist/internal/timers.js.map +1 -0
- package/dist/internal/widget.d.ts +40 -0
- package/dist/internal/widget.d.ts.map +1 -0
- package/dist/internal/widget.js +44 -0
- package/dist/internal/widget.js.map +1 -0
- package/dist/markdown/ast.d.ts +126 -0
- package/dist/markdown/ast.d.ts.map +1 -0
- package/dist/markdown/ast.js +8 -0
- package/dist/markdown/ast.js.map +1 -0
- package/dist/markdown/hx.d.ts +24 -0
- package/dist/markdown/hx.d.ts.map +1 -0
- package/dist/markdown/hx.js +25 -0
- package/dist/markdown/hx.js.map +1 -0
- package/dist/markdown/index.d.ts +90 -0
- package/dist/markdown/index.d.ts.map +1 -0
- package/dist/markdown/index.js +413 -0
- package/dist/markdown/index.js.map +1 -0
- package/dist/markdown/parse.d.ts +8 -0
- package/dist/markdown/parse.d.ts.map +1 -0
- package/dist/markdown/parse.js +911 -0
- package/dist/markdown/parse.js.map +1 -0
- package/dist/markdown/spans.d.ts +20 -0
- package/dist/markdown/spans.d.ts.map +1 -0
- package/dist/markdown/spans.js +96 -0
- package/dist/markdown/spans.js.map +1 -0
- package/dist/media-player/backends.d.ts +64 -0
- package/dist/media-player/backends.d.ts.map +1 -0
- package/dist/media-player/backends.js +110 -0
- package/dist/media-player/backends.js.map +1 -0
- package/dist/media-player/control.d.ts +40 -0
- package/dist/media-player/control.d.ts.map +1 -0
- package/dist/media-player/control.js +44 -0
- package/dist/media-player/control.js.map +1 -0
- package/dist/media-player/hx.d.ts +24 -0
- package/dist/media-player/hx.d.ts.map +1 -0
- package/dist/media-player/hx.js +25 -0
- package/dist/media-player/hx.js.map +1 -0
- package/dist/media-player/index.d.ts +107 -0
- package/dist/media-player/index.d.ts.map +1 -0
- package/dist/media-player/index.js +219 -0
- package/dist/media-player/index.js.map +1 -0
- package/dist/media-player/mpv.d.ts +4 -0
- package/dist/media-player/mpv.d.ts.map +1 -0
- package/dist/media-player/mpv.js +120 -0
- package/dist/media-player/mpv.js.map +1 -0
- package/dist/media-player/vlc.d.ts +8 -0
- package/dist/media-player/vlc.d.ts.map +1 -0
- package/dist/media-player/vlc.js +83 -0
- package/dist/media-player/vlc.js.map +1 -0
- package/dist/richtext/index.d.ts +19 -0
- package/dist/richtext/index.d.ts.map +1 -0
- package/dist/richtext/index.js +4 -0
- package/dist/richtext/index.js.map +1 -0
- package/dist/richtext/links.d.ts +12 -0
- package/dist/richtext/links.d.ts.map +1 -0
- package/dist/richtext/links.js +64 -0
- package/dist/richtext/links.js.map +1 -0
- package/dist/richtext/menu.d.ts +11 -0
- package/dist/richtext/menu.d.ts.map +1 -0
- package/dist/richtext/menu.js +43 -0
- package/dist/richtext/menu.js.map +1 -0
- package/dist/richtext/node.d.ts +151 -0
- package/dist/richtext/node.d.ts.map +1 -0
- package/dist/richtext/node.js +255 -0
- package/dist/richtext/node.js.map +1 -0
- package/dist/richtext/runs.d.ts +92 -0
- package/dist/richtext/runs.d.ts.map +1 -0
- package/dist/richtext/runs.js +158 -0
- package/dist/richtext/runs.js.map +1 -0
- package/dist/table/hx.d.ts +24 -0
- package/dist/table/hx.d.ts.map +1 -0
- package/dist/table/hx.js +25 -0
- package/dist/table/hx.js.map +1 -0
- package/dist/table/index.d.ts +169 -0
- package/dist/table/index.d.ts.map +1 -0
- package/dist/table/index.js +890 -0
- package/dist/table/index.js.map +1 -0
- package/dist/table/rows.d.ts +136 -0
- package/dist/table/rows.d.ts.map +1 -0
- package/dist/table/rows.js +146 -0
- package/dist/table/rows.js.map +1 -0
- package/dist/terminal/backends.d.ts +105 -0
- package/dist/terminal/backends.d.ts.map +1 -0
- package/dist/terminal/backends.js +185 -0
- package/dist/terminal/backends.js.map +1 -0
- package/dist/terminal/hx.d.ts +24 -0
- package/dist/terminal/hx.d.ts.map +1 -0
- package/dist/terminal/hx.js +25 -0
- package/dist/terminal/hx.js.map +1 -0
- package/dist/terminal/index.d.ts +160 -0
- package/dist/terminal/index.d.ts.map +1 -0
- package/dist/terminal/index.js +344 -0
- package/dist/terminal/index.js.map +1 -0
- package/dist/terminal/title.d.ts +24 -0
- package/dist/terminal/title.d.ts.map +1 -0
- package/dist/terminal/title.js +129 -0
- package/dist/terminal/title.js.map +1 -0
- package/dist/terminal/vt/colors.d.ts +63 -0
- package/dist/terminal/vt/colors.d.ts.map +1 -0
- package/dist/terminal/vt/colors.js +203 -0
- package/dist/terminal/vt/colors.js.map +1 -0
- package/dist/terminal/vt/diff.d.ts +117 -0
- package/dist/terminal/vt/diff.d.ts.map +1 -0
- package/dist/terminal/vt/diff.js +358 -0
- package/dist/terminal/vt/diff.js.map +1 -0
- package/dist/terminal/vt/fonts.d.ts +92 -0
- package/dist/terminal/vt/fonts.d.ts.map +1 -0
- package/dist/terminal/vt/fonts.js +178 -0
- package/dist/terminal/vt/fonts.js.map +1 -0
- package/dist/terminal/vt/index.d.ts +78 -0
- package/dist/terminal/vt/index.d.ts.map +1 -0
- package/dist/terminal/vt/index.js +312 -0
- package/dist/terminal/vt/index.js.map +1 -0
- package/dist/terminal/vt/keys.d.ts +74 -0
- package/dist/terminal/vt/keys.d.ts.map +1 -0
- package/dist/terminal/vt/keys.js +309 -0
- package/dist/terminal/vt/keys.js.map +1 -0
- package/dist/terminal/vt/mouse.d.ts +44 -0
- package/dist/terminal/vt/mouse.d.ts.map +1 -0
- package/dist/terminal/vt/mouse.js +106 -0
- package/dist/terminal/vt/mouse.js.map +1 -0
- package/dist/terminal/vt/node.d.ts +246 -0
- package/dist/terminal/vt/node.d.ts.map +1 -0
- package/dist/terminal/vt/node.js +1068 -0
- package/dist/terminal/vt/node.js.map +1 -0
- package/dist/terminal/vt/pty.d.ts +81 -0
- package/dist/terminal/vt/pty.d.ts.map +1 -0
- package/dist/terminal/vt/pty.js +201 -0
- package/dist/terminal/vt/pty.js.map +1 -0
- package/dist/terminal/vt/renderer.d.ts +179 -0
- package/dist/terminal/vt/renderer.d.ts.map +1 -0
- package/dist/terminal/vt/renderer.js +357 -0
- package/dist/terminal/vt/renderer.js.map +1 -0
- package/dist/terminal/vt/timers.d.ts +6 -0
- package/dist/terminal/vt/timers.d.ts.map +1 -0
- package/dist/terminal/vt/timers.js +16 -0
- package/dist/terminal/vt/timers.js.map +1 -0
- package/dist/terminal/vt/xterm.d.ts +142 -0
- package/dist/terminal/vt/xterm.d.ts.map +1 -0
- package/dist/terminal/vt/xterm.js +32 -0
- package/dist/terminal/vt/xterm.js.map +1 -0
- package/dist/terminal-output/hx.d.ts +24 -0
- package/dist/terminal-output/hx.d.ts.map +1 -0
- package/dist/terminal-output/hx.js +25 -0
- package/dist/terminal-output/hx.js.map +1 -0
- package/dist/terminal-output/index.d.ts +92 -0
- package/dist/terminal-output/index.d.ts.map +1 -0
- package/dist/terminal-output/index.js +179 -0
- package/dist/terminal-output/index.js.map +1 -0
- package/dist/terminal-output/runs.d.ts +27 -0
- package/dist/terminal-output/runs.d.ts.map +1 -0
- package/dist/terminal-output/runs.js +66 -0
- package/dist/terminal-output/runs.js.map +1 -0
- package/dist/three/canvas.d.ts +46 -0
- package/dist/three/canvas.d.ts.map +1 -0
- package/dist/three/canvas.js +305 -0
- package/dist/three/canvas.js.map +1 -0
- package/dist/three/events.d.ts +64 -0
- package/dist/three/events.d.ts.map +1 -0
- package/dist/three/events.js +146 -0
- package/dist/three/events.js.map +1 -0
- package/dist/three/geometries.d.ts +89 -0
- package/dist/three/geometries.d.ts.map +1 -0
- package/dist/three/geometries.js +345 -0
- package/dist/three/geometries.js.map +1 -0
- package/dist/three/globals.d.ts +7 -0
- package/dist/three/globals.d.ts.map +1 -0
- package/dist/three/globals.js +20 -0
- package/dist/three/globals.js.map +1 -0
- package/dist/three/hx.d.ts +9 -0
- package/dist/three/hx.d.ts.map +1 -0
- package/dist/three/hx.js +10 -0
- package/dist/three/hx.js.map +1 -0
- package/dist/three/index.d.ts +22 -0
- package/dist/three/index.d.ts.map +1 -0
- package/dist/three/index.js +24 -0
- package/dist/three/index.js.map +1 -0
- package/dist/three/jsx-dev-runtime.d.ts +19 -0
- package/dist/three/jsx-dev-runtime.d.ts.map +1 -0
- package/dist/three/jsx-dev-runtime.js +2 -0
- package/dist/three/jsx-dev-runtime.js.map +1 -0
- package/dist/three/jsx-runtime.d.ts +19 -0
- package/dist/three/jsx-runtime.d.ts.map +1 -0
- package/dist/three/jsx-runtime.js +2 -0
- package/dist/three/jsx-runtime.js.map +1 -0
- package/dist/three/jsx.d.ts +198 -0
- package/dist/three/jsx.d.ts.map +1 -0
- package/dist/three/jsx.js +2 -0
- package/dist/three/jsx.js.map +1 -0
- package/dist/three/materials.d.ts +125 -0
- package/dist/three/materials.d.ts.map +1 -0
- package/dist/three/materials.js +229 -0
- package/dist/three/materials.js.map +1 -0
- package/dist/three/math.d.ts +146 -0
- package/dist/three/math.d.ts.map +1 -0
- package/dist/three/math.js +464 -0
- package/dist/three/math.js.map +1 -0
- package/dist/three/objects.d.ts +185 -0
- package/dist/three/objects.d.ts.map +1 -0
- package/dist/three/objects.js +309 -0
- package/dist/three/objects.js.map +1 -0
- package/dist/three/passes.d.ts +54 -0
- package/dist/three/passes.d.ts.map +1 -0
- package/dist/three/passes.js +78 -0
- package/dist/three/passes.js.map +1 -0
- package/dist/three/postprocess.d.ts +85 -0
- package/dist/three/postprocess.d.ts.map +1 -0
- package/dist/three/postprocess.js +503 -0
- package/dist/three/postprocess.js.map +1 -0
- package/dist/three/raycast.d.ts +24 -0
- package/dist/three/raycast.d.ts.map +1 -0
- package/dist/three/raycast.js +138 -0
- package/dist/three/raycast.js.map +1 -0
- package/dist/three/reconciler.d.ts +48 -0
- package/dist/three/reconciler.d.ts.map +1 -0
- package/dist/three/reconciler.js +416 -0
- package/dist/three/reconciler.js.map +1 -0
- package/dist/three/renderer-direct.d.ts +102 -0
- package/dist/three/renderer-direct.d.ts.map +1 -0
- package/dist/three/renderer-direct.js +782 -0
- package/dist/three/renderer-direct.js.map +1 -0
- package/dist/three/renderer-indirect.d.ts +68 -0
- package/dist/three/renderer-indirect.d.ts.map +1 -0
- package/dist/three/renderer-indirect.js +438 -0
- package/dist/three/renderer-indirect.js.map +1 -0
- package/dist/three/store.d.ts +81 -0
- package/dist/three/store.d.ts.map +1 -0
- package/dist/three/store.js +110 -0
- package/dist/three/store.js.map +1 -0
- package/dist/timeline/hx.d.ts +18 -0
- package/dist/timeline/hx.d.ts.map +1 -0
- package/dist/timeline/hx.js +10 -0
- package/dist/timeline/hx.js.map +1 -0
- package/dist/timeline/index.d.ts +152 -0
- package/dist/timeline/index.d.ts.map +1 -0
- package/dist/timeline/index.js +383 -0
- package/dist/timeline/index.js.map +1 -0
- package/dist/tray-host/hx.d.ts +24 -0
- package/dist/tray-host/hx.d.ts.map +1 -0
- package/dist/tray-host/hx.js +25 -0
- package/dist/tray-host/hx.js.map +1 -0
- package/dist/tray-host/index.d.ts +110 -0
- package/dist/tray-host/index.d.ts.map +1 -0
- package/dist/tray-host/index.js +191 -0
- package/dist/tray-host/index.js.map +1 -0
- package/dist/tray-host/manager.d.ts +137 -0
- package/dist/tray-host/manager.d.ts.map +1 -0
- package/dist/tray-host/manager.js +462 -0
- package/dist/tray-host/manager.js.map +1 -0
- package/dist/tray-host/notify.d.ts +22 -0
- package/dist/tray-host/notify.d.ts.map +1 -0
- package/dist/tray-host/notify.js +74 -0
- package/dist/tray-host/notify.js.map +1 -0
- package/dist/tray-host/protocol.d.ts +106 -0
- package/dist/tray-host/protocol.d.ts.map +1 -0
- package/dist/tray-host/protocol.js +191 -0
- package/dist/tray-host/protocol.js.map +1 -0
- package/dist/tree/hx.d.ts +24 -0
- package/dist/tree/hx.d.ts.map +1 -0
- package/dist/tree/hx.js +25 -0
- package/dist/tree/hx.js.map +1 -0
- package/dist/tree/index.d.ts +221 -0
- package/dist/tree/index.d.ts.map +1 -0
- package/dist/tree/index.js +751 -0
- package/dist/tree/index.js.map +1 -0
- package/dist/tree/internal.d.ts +18 -0
- package/dist/tree/internal.d.ts.map +1 -0
- package/dist/tree/internal.js +66 -0
- package/dist/tree/internal.js.map +1 -0
- package/dist/tree/rows.d.ts +177 -0
- package/dist/tree/rows.d.ts.map +1 -0
- package/dist/tree/rows.js +179 -0
- package/dist/tree/rows.js.map +1 -0
- package/package.json +166 -12
- package/src/ansi/cast.ts +159 -0
- package/src/ansi/index.ts +53 -0
- package/src/ansi/palette.ts +226 -0
- package/src/ansi/parse.ts +780 -0
- package/src/ansi/sgr.ts +380 -0
- package/src/ansi/utf8.ts +84 -0
- package/src/calendar/Calendar.ts +744 -0
- package/src/calendar/DatePicker.ts +368 -0
- package/src/calendar/dates.ts +307 -0
- package/src/calendar/index.ts +51 -0
- package/src/charts/components.ts +864 -0
- package/src/charts/data.ts +776 -0
- package/src/charts/hx.ts +39 -0
- package/src/charts/index.ts +99 -0
- package/src/charts/node.ts +932 -0
- package/src/charts/render.ts +982 -0
- package/src/charts/scale.ts +214 -0
- package/src/charts/spec.ts +122 -0
- package/src/charts/timers.ts +24 -0
- package/src/code/hx.ts +51 -0
- package/src/code/index.ts +174 -0
- package/src/code-editor/completion.ts +161 -0
- package/src/code-editor/doc.ts +198 -0
- package/src/code-editor/hx.ts +26 -0
- package/src/code-editor/index.ts +615 -0
- package/src/code-editor/node.ts +1926 -0
- package/src/code-language/hljs.ts +302 -0
- package/src/code-language/index.ts +51 -0
- package/src/code-language/languages/glsl.ts +120 -0
- package/src/code-language/languages/javascript.ts +419 -0
- package/src/code-language/languages/json.ts +72 -0
- package/src/code-language/languages/shell.ts +221 -0
- package/src/code-language/languages/sql.ts +165 -0
- package/src/code-language/lezer.ts +190 -0
- package/src/code-language/registry.ts +66 -0
- package/src/code-language/runs.ts +128 -0
- package/src/code-language/stream.ts +306 -0
- package/src/code-language/textmate.ts +134 -0
- package/src/code-language/theme.ts +124 -0
- package/src/code-language/timers.ts +36 -0
- package/src/code-language/types.ts +252 -0
- package/src/codeblock/index.ts +146 -0
- package/src/color-picker/ColorField.ts +333 -0
- package/src/color-picker/ColorPicker.ts +1069 -0
- package/src/color-picker/color.ts +399 -0
- package/src/color-picker/index.ts +50 -0
- package/src/desktop-calendar/eds.ts +484 -0
- package/src/desktop-calendar/ical.ts +119 -0
- package/src/desktop-calendar/index.ts +287 -0
- package/src/embed/client.ts +311 -0
- package/src/embed/host.ts +432 -0
- package/src/embed/index.ts +33 -0
- package/src/embed/timers.ts +32 -0
- package/src/flow/draw.ts +418 -0
- package/src/flow/index.ts +473 -0
- package/src/flow/model.ts +687 -0
- package/src/flow/node.ts +3694 -0
- package/src/flow/paths.ts +403 -0
- package/src/flow/types.ts +634 -0
- package/src/formula/hx.ts +51 -0
- package/src/formula/index.ts +243 -0
- package/src/formula/katex.ts +209 -0
- package/src/formula/layout.ts +876 -0
- package/src/formula/node.ts +467 -0
- package/src/html/controls.ts +244 -0
- package/src/html/css/cascade.ts +606 -0
- package/src/html/css/parse.ts +500 -0
- package/src/html/css/style.ts +1371 -0
- package/src/html/css/ua.ts +174 -0
- package/src/html/css/values.ts +358 -0
- package/src/html/dom.ts +410 -0
- package/src/html/hx.ts +55 -0
- package/src/html/index.ts +545 -0
- package/src/html/layout/block.ts +876 -0
- package/src/html/layout/boxes.ts +981 -0
- package/src/html/layout/flex.ts +297 -0
- package/src/html/layout/floats.ts +125 -0
- package/src/html/layout/inline.ts +870 -0
- package/src/html/layout/table.ts +380 -0
- package/src/html/node.ts +967 -0
- package/src/html/paint.ts +605 -0
- package/src/html/resources.ts +214 -0
- package/src/index.ts +638 -0
- package/src/internal/heights.ts +222 -0
- package/src/internal/hx.ts +53 -0
- package/src/internal/scroll.ts +288 -0
- package/src/internal/text.ts +44 -0
- package/src/internal/timers.ts +44 -0
- package/src/internal/widget.ts +92 -0
- package/src/markdown/ast.ts +163 -0
- package/src/markdown/hx.ts +51 -0
- package/src/markdown/index.ts +686 -0
- package/src/markdown/parse.ts +1037 -0
- package/src/markdown/spans.ts +143 -0
- package/src/media-player/backends.ts +155 -0
- package/src/media-player/control.ts +70 -0
- package/src/media-player/hx.ts +51 -0
- package/src/media-player/index.ts +378 -0
- package/src/media-player/mpv.ts +146 -0
- package/src/media-player/vlc.ts +97 -0
- package/src/richtext/index.ts +42 -0
- package/src/richtext/links.ts +78 -0
- package/src/richtext/menu.ts +49 -0
- package/src/richtext/node.ts +406 -0
- package/src/richtext/runs.ts +253 -0
- package/src/table/hx.ts +51 -0
- package/src/table/index.ts +1270 -0
- package/src/table/rows.ts +275 -0
- package/src/terminal/backends.ts +242 -0
- package/src/terminal/hx.ts +51 -0
- package/src/terminal/index.ts +574 -0
- package/src/terminal/title.ts +172 -0
- package/src/terminal/vt/colors.ts +247 -0
- package/src/terminal/vt/diff.ts +467 -0
- package/src/terminal/vt/fonts.ts +233 -0
- package/src/terminal/vt/index.ts +443 -0
- package/src/terminal/vt/keys.ts +369 -0
- package/src/terminal/vt/mouse.ts +144 -0
- package/src/terminal/vt/node.ts +1242 -0
- package/src/terminal/vt/pty.ts +334 -0
- package/src/terminal/vt/renderer.ts +555 -0
- package/src/terminal/vt/timers.ts +31 -0
- package/src/terminal/vt/xterm.ts +193 -0
- package/src/terminal-output/hx.ts +51 -0
- package/src/terminal-output/index.ts +340 -0
- package/src/terminal-output/runs.ts +79 -0
- package/src/three/canvas.ts +451 -0
- package/src/three/events.ts +197 -0
- package/src/three/geometries.ts +429 -0
- package/src/three/globals.ts +34 -0
- package/src/three/hx.ts +22 -0
- package/src/three/index.ts +98 -0
- package/src/three/jsx-dev-runtime.ts +27 -0
- package/src/three/jsx-runtime.ts +38 -0
- package/src/three/jsx.ts +275 -0
- package/src/three/materials.ts +270 -0
- package/src/three/math.ts +585 -0
- package/src/three/objects.ts +397 -0
- package/src/three/passes.ts +90 -0
- package/src/three/postprocess.ts +677 -0
- package/src/three/raycast.ts +189 -0
- package/src/three/react-reconciler.d.ts +50 -0
- package/src/three/reconciler.ts +624 -0
- package/src/three/renderer-direct.ts +1055 -0
- package/src/three/renderer-indirect.ts +570 -0
- package/src/three/store.ts +207 -0
- package/src/timeline/hx.ts +31 -0
- package/src/timeline/index.ts +603 -0
- package/src/tray-host/hx.ts +51 -0
- package/src/tray-host/index.ts +331 -0
- package/src/tray-host/manager.ts +639 -0
- package/src/tray-host/notify.ts +102 -0
- package/src/tray-host/protocol.ts +235 -0
- package/src/tree/hx.ts +51 -0
- package/src/tree/index.ts +1127 -0
- package/src/tree/internal.ts +87 -0
- package/src/tree/rows.ts +334 -0
- package/src/index.d.ts +0 -2
- package/src/index.js +0 -9
- package/src/sparkline/index.d.ts +0 -29
- package/src/sparkline/index.js +0 -50
- package/src/sparkline/node.js +0 -72
|
@@ -0,0 +1,1521 @@
|
|
|
1
|
+
// The retained node behind `<codeeditor>`: the text model (lines, caret,
|
|
2
|
+
// selection, undo), scrolling, and all painting (gutter, tokens, selection,
|
|
3
|
+
// caret, bracket match, diagnostics). `./index.ts` registers it and wires
|
|
4
|
+
// input to it.
|
|
5
|
+
//
|
|
6
|
+
// **How input reaches this node.** Through the default-action seam
|
|
7
|
+
// react-x11#266 made public: `defaultKeyDown`, `defaultMouseDown`,
|
|
8
|
+
// `defaultMouseDrag`, `defaultMouseUp`, `defaultFocus` and `defaultBlur`
|
|
9
|
+
// are methods the EventManager calls on the focused or pressed node, after
|
|
10
|
+
// the application's own `onKeyDown`/`onMouseDown` handlers and not at all
|
|
11
|
+
// if one of them called `preventDefault()`. So a bare `<codeeditor>` in
|
|
12
|
+
// JSX edits, selects and blinks the way `<textinput>` does, with no
|
|
13
|
+
// wrapping component required — and an app still vetoes or extends any of
|
|
14
|
+
// it without knowing how the element is built.
|
|
15
|
+
//
|
|
16
|
+
// `<CodeEditor>` is now only the completion popup: it is an ordinary
|
|
17
|
+
// `onKeyDown` handler that consumes the keys the popup owns (arrows,
|
|
18
|
+
// Return, Tab-accept, Escape-close) and, by consuming them, keeps this
|
|
19
|
+
// node's default action from also acting on them.
|
|
20
|
+
import { CARET_BLINK_MS, Node } from 'react-x11/node';
|
|
21
|
+
import { ctrlChordLetter, XK_BACKSPACE, XK_DELETE, XK_DOWN, XK_END, XK_ESCAPE, XK_HOME, XK_KP_ENTER, XK_LEFT, XK_PAGE_DOWN, XK_PAGE_UP, XK_RETURN, XK_RIGHT, XK_TAB, XK_UP, } from 'react-x11/keysyms';
|
|
22
|
+
import { startInterval, stopInterval } from '../code-language/timers.js';
|
|
23
|
+
import { clampPos, comparePos, maxPos, minPos, movePos, posEqual, tabMap, wordBoundary, wordRangeAt, } from './doc.js';
|
|
24
|
+
import { DARK_TOKEN_STYLES, isDarkBackground, LIGHT_TOKEN_STYLES, tokenStyleFor, } from '../code-language/theme.js';
|
|
25
|
+
/**
|
|
26
|
+
* The element name — registration key, `kind`, and JSX tag, one string.
|
|
27
|
+
* react-x11 rejects a node whose `kind` is not the name it was registered
|
|
28
|
+
* under, because `kind` is what paint order, the test queries and the DEV
|
|
29
|
+
* assertion all match on. One constant, used in all three places.
|
|
30
|
+
*/
|
|
31
|
+
export const ELEMENT = 'codeeditor';
|
|
32
|
+
const UNDO_LIMIT = 200;
|
|
33
|
+
const DEFAULT_ROWS = 6;
|
|
34
|
+
const PREFERRED_WIDTH = 360;
|
|
35
|
+
const GUTTER_PAD = 8; // each side of the line numbers
|
|
36
|
+
const CARET_MARGIN = 24; // keep this many px visible beside the caret
|
|
37
|
+
const MATCH_SCAN_LIMIT = 20000; // chars examined looking for a bracket match
|
|
38
|
+
const BRACKETS = {
|
|
39
|
+
'(': { match: ')', dir: 1 },
|
|
40
|
+
'[': { match: ']', dir: 1 },
|
|
41
|
+
'{': { match: '}', dir: 1 },
|
|
42
|
+
')': { match: '(', dir: -1 },
|
|
43
|
+
']': { match: '[', dir: -1 },
|
|
44
|
+
'}': { match: '{', dir: -1 },
|
|
45
|
+
};
|
|
46
|
+
/** Token types the bracket matcher must not look inside. */
|
|
47
|
+
const OPAQUE_TOKENS = new Set(['string', 'string2', 'comment', 'docComment']);
|
|
48
|
+
const SEVERITY_COLORS = {
|
|
49
|
+
error: '#e5484d',
|
|
50
|
+
warning: '#d5a021',
|
|
51
|
+
information: '#3b82f6',
|
|
52
|
+
hint: '#8b8f98',
|
|
53
|
+
};
|
|
54
|
+
function cpToUtf16(text, cp) {
|
|
55
|
+
let i = 0;
|
|
56
|
+
for (let n = 0; n < cp && i < text.length; n++) {
|
|
57
|
+
const code = text.codePointAt(i);
|
|
58
|
+
i += code > 0xffff ? 2 : 1;
|
|
59
|
+
}
|
|
60
|
+
return i;
|
|
61
|
+
}
|
|
62
|
+
function utf16ToCp(text, index) {
|
|
63
|
+
let cp = 0;
|
|
64
|
+
for (let i = 0; i < index && i < text.length;) {
|
|
65
|
+
const code = text.codePointAt(i);
|
|
66
|
+
i += code > 0xffff ? 2 : 1;
|
|
67
|
+
cp++;
|
|
68
|
+
}
|
|
69
|
+
return cp;
|
|
70
|
+
}
|
|
71
|
+
export class CodeEditorNode extends Node {
|
|
72
|
+
_lines;
|
|
73
|
+
/** What `_lines` was last built from — how a controlled `value` that the
|
|
74
|
+
* parent refused to update snaps back on the next read (see `_syncProps`). */
|
|
75
|
+
_synced;
|
|
76
|
+
_caret = { line: 0, ch: 0 };
|
|
77
|
+
_anchor = { line: 0, ch: 0 };
|
|
78
|
+
_scrollX = 0;
|
|
79
|
+
_scrollY = 0;
|
|
80
|
+
_focused = false;
|
|
81
|
+
_caretOn = false;
|
|
82
|
+
_blinkTimer = null;
|
|
83
|
+
_goalX = null;
|
|
84
|
+
_drag = null;
|
|
85
|
+
_dragOrigin = { line: 0, ch: 0 };
|
|
86
|
+
/** Escape was the last key, so the next Tab leaves instead of indenting —
|
|
87
|
+
* the editor's way of not being a keyboard trap (see `defaultKeyDown`). */
|
|
88
|
+
_tabEscapes = false;
|
|
89
|
+
_history;
|
|
90
|
+
_historyIndex = 0;
|
|
91
|
+
_undoRun = null;
|
|
92
|
+
_pendingValue = null;
|
|
93
|
+
_keyNative = null;
|
|
94
|
+
_tok = null;
|
|
95
|
+
_tokLanguage;
|
|
96
|
+
_lineCache = new Map();
|
|
97
|
+
_metricsKey = '';
|
|
98
|
+
_lineH = 0;
|
|
99
|
+
_charW = 0;
|
|
100
|
+
_widest = 0;
|
|
101
|
+
constructor(props, app) {
|
|
102
|
+
super(ELEMENT, props, app);
|
|
103
|
+
const initial = props.value != null
|
|
104
|
+
? String(props.value)
|
|
105
|
+
: props.defaultValue != null
|
|
106
|
+
? String(props.defaultValue)
|
|
107
|
+
: '';
|
|
108
|
+
this._lines = initial.split('\n');
|
|
109
|
+
this._synced = initial;
|
|
110
|
+
this._history = [
|
|
111
|
+
{ value: initial, caret: this._caret, anchor: this._anchor },
|
|
112
|
+
];
|
|
113
|
+
// Without this nothing focuses the editor and no key ever reaches it —
|
|
114
|
+
// an app's `focusable`/`tabIndex` prop still overrides either way, which
|
|
115
|
+
// is how `<CodeEditor disabled>` stays out of the tab order.
|
|
116
|
+
this.focusableByDefault = true;
|
|
117
|
+
this.defaultCursor = 'text';
|
|
118
|
+
// The editor's selection is its own (react-x11#291). A `selectable`
|
|
119
|
+
// document around it — a `<Markdown>` with a live editor in it, a form
|
|
120
|
+
// on a selectable pane — skips this subtree whole rather than lighting
|
|
121
|
+
// up half the text somebody is typing in, and a press here never
|
|
122
|
+
// reaches the surface above. That is the same rule `<textinput>` keeps,
|
|
123
|
+
// and the reason the `default*` handlers below can own the gesture
|
|
124
|
+
// without calling `super`.
|
|
125
|
+
this.hasOwnSelection = true;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The editor's own size: a comfortable width, never wider than what is on
|
|
129
|
+
* offer, and `rows` text lines tall. An unbounded axis arrives as
|
|
130
|
+
* `Infinity`, so the `Math.min` is the whole rule. The height comes from a
|
|
131
|
+
* prop rather than from the style, which is what makes the
|
|
132
|
+
* `invalidateMeasure` in `applyProps` necessary.
|
|
133
|
+
*/
|
|
134
|
+
measureContent({ width }) {
|
|
135
|
+
const rows = Math.max(1, Number(this.props.rows ?? DEFAULT_ROWS));
|
|
136
|
+
return {
|
|
137
|
+
width: Math.min(PREFERRED_WIDTH, width),
|
|
138
|
+
height: Math.ceil(this._lineHeight() * rows),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// --- value & props sync --------------------------------------------------
|
|
142
|
+
get value() {
|
|
143
|
+
if (this._pendingValue !== null)
|
|
144
|
+
return this._pendingValue;
|
|
145
|
+
if (this.props.value != null)
|
|
146
|
+
return String(this.props.value);
|
|
147
|
+
return this._lines.join('\n');
|
|
148
|
+
}
|
|
149
|
+
/** Programmatic set, no `onChange` — the DOM-input contract form libraries
|
|
150
|
+
* rely on. On a controlled editor `props.value` wins the next render. */
|
|
151
|
+
set value(next) {
|
|
152
|
+
const text = next == null ? '' : String(next);
|
|
153
|
+
if (text === this._lines.join('\n'))
|
|
154
|
+
return;
|
|
155
|
+
this._setText(text);
|
|
156
|
+
this._noteExternal();
|
|
157
|
+
this._repaint();
|
|
158
|
+
}
|
|
159
|
+
get name() {
|
|
160
|
+
return this.props.name;
|
|
161
|
+
}
|
|
162
|
+
get selection() {
|
|
163
|
+
return { anchor: { ...this._anchor }, head: { ...this._caret } };
|
|
164
|
+
}
|
|
165
|
+
get lines() {
|
|
166
|
+
this._syncProps();
|
|
167
|
+
return this._lines;
|
|
168
|
+
}
|
|
169
|
+
get language() {
|
|
170
|
+
return this.props.language ?? null;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Pull a controlled `value` into the model. Called at the top of every
|
|
174
|
+
* command and paint: a parent that ignored an `onChange` never re-renders,
|
|
175
|
+
* so there is no `applyProps` moment to catch the divergence — the next
|
|
176
|
+
* read is that moment.
|
|
177
|
+
*/
|
|
178
|
+
_syncProps() {
|
|
179
|
+
const v = this.props.value;
|
|
180
|
+
if (v == null)
|
|
181
|
+
return;
|
|
182
|
+
const text = String(v);
|
|
183
|
+
if (text === this._synced)
|
|
184
|
+
return;
|
|
185
|
+
this._setText(text);
|
|
186
|
+
this._noteExternal();
|
|
187
|
+
}
|
|
188
|
+
_setText(text) {
|
|
189
|
+
this._lines = text.split('\n');
|
|
190
|
+
this._synced = text;
|
|
191
|
+
this._caret = clampPos(this._lines, this._caret);
|
|
192
|
+
this._anchor = clampPos(this._lines, this._anchor);
|
|
193
|
+
this._lineCache.clear();
|
|
194
|
+
this._widest = 0;
|
|
195
|
+
this._tokenizer()?.setLines(this._lines);
|
|
196
|
+
}
|
|
197
|
+
// --- tokenizer -----------------------------------------------------------
|
|
198
|
+
_tokenizer() {
|
|
199
|
+
const lang = this.language;
|
|
200
|
+
if (lang !== this._tokLanguage) {
|
|
201
|
+
this._tok?.dispose?.();
|
|
202
|
+
this._tokLanguage = lang;
|
|
203
|
+
this._tok = lang
|
|
204
|
+
? lang.createTokenizer({
|
|
205
|
+
invalidate: (fromLine) => {
|
|
206
|
+
for (const key of this._lineCache.keys()) {
|
|
207
|
+
if (key >= fromLine)
|
|
208
|
+
this._lineCache.delete(key);
|
|
209
|
+
}
|
|
210
|
+
this._repaint();
|
|
211
|
+
},
|
|
212
|
+
})
|
|
213
|
+
: null;
|
|
214
|
+
this._tok?.setLines(this._lines);
|
|
215
|
+
this._lineCache.clear();
|
|
216
|
+
}
|
|
217
|
+
return this._tok;
|
|
218
|
+
}
|
|
219
|
+
// --- metrics & style -----------------------------------------------------
|
|
220
|
+
_textStyle() {
|
|
221
|
+
const s = this.style;
|
|
222
|
+
return {
|
|
223
|
+
family: s.fontFamily ?? 'monospace',
|
|
224
|
+
size: s.fontSize ?? 13,
|
|
225
|
+
weight: s.fontWeight ?? 400,
|
|
226
|
+
style: s.fontStyle ?? 'normal',
|
|
227
|
+
// theme text before hardcoded ink: on a dark desktop the palette's
|
|
228
|
+
// `text` is light, and a near-black fallback would vanish — the
|
|
229
|
+
// component's default style also asks for `color: '$text'`, so this
|
|
230
|
+
// branch is for a bare <codeeditor> under a themeless tree
|
|
231
|
+
color: s.color ?? this._themeColor('text') ?? '#24292f',
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
_themeColor(token) {
|
|
235
|
+
const theme = this.theme;
|
|
236
|
+
const v = theme?.[token];
|
|
237
|
+
return typeof v === 'string' ? v : undefined;
|
|
238
|
+
}
|
|
239
|
+
_fonts() {
|
|
240
|
+
return (this.app?.fonts ?? null);
|
|
241
|
+
}
|
|
242
|
+
_metrics() {
|
|
243
|
+
const s = this._textStyle();
|
|
244
|
+
const key = `${s.family}|${s.size}|${s.weight}`;
|
|
245
|
+
if (key === this._metricsKey && this._lineH > 0)
|
|
246
|
+
return;
|
|
247
|
+
const fonts = this._fonts();
|
|
248
|
+
this._metricsKey = key;
|
|
249
|
+
if (!fonts) {
|
|
250
|
+
this._lineH = Number(s.size) * 1.4;
|
|
251
|
+
this._charW = Number(s.size) * 0.6;
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const probe = fonts.layout('Mg', s);
|
|
255
|
+
this._lineH = probe.height || Number(s.size) * 1.4;
|
|
256
|
+
this._charW = fonts.layout('0', s).width || Number(s.size) * 0.6;
|
|
257
|
+
}
|
|
258
|
+
_lineHeight() {
|
|
259
|
+
this._metrics();
|
|
260
|
+
return this._lineH;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* The content box: `abs` inset by border and padding, computed from the
|
|
264
|
+
* flattened style. [react-x11 gap] built-in fields read an internal
|
|
265
|
+
* `contentBox()`; a public equivalent would remove this arithmetic —
|
|
266
|
+
* filed as react-x11#254.
|
|
267
|
+
*/
|
|
268
|
+
_contentRect() {
|
|
269
|
+
const s = this.style;
|
|
270
|
+
const num = (v) => (typeof v === 'number' ? v : 0);
|
|
271
|
+
const border = num(s.borderWidth);
|
|
272
|
+
const pad = num(s.padding);
|
|
273
|
+
const top = num(s.borderTopWidth ?? border) + num(s.paddingTop ?? pad);
|
|
274
|
+
const right = num(s.borderRightWidth ?? border) + num(s.paddingRight ?? pad);
|
|
275
|
+
const bottom = num(s.borderBottomWidth ?? border) + num(s.paddingBottom ?? pad);
|
|
276
|
+
const left = num(s.borderLeftWidth ?? border) + num(s.paddingLeft ?? pad);
|
|
277
|
+
const { x, y, width, height } = this.abs;
|
|
278
|
+
return {
|
|
279
|
+
x: x + left,
|
|
280
|
+
y: y + top,
|
|
281
|
+
width: Math.max(0, width - left - right),
|
|
282
|
+
height: Math.max(0, height - top - bottom),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
_gutterWidth() {
|
|
286
|
+
if (!this.props.lineNumbers)
|
|
287
|
+
return 0;
|
|
288
|
+
this._metrics();
|
|
289
|
+
const digits = Math.max(2, String(this._lines.length).length);
|
|
290
|
+
return Math.ceil(digits * this._charW) + GUTTER_PAD * 2;
|
|
291
|
+
}
|
|
292
|
+
_tabSize() {
|
|
293
|
+
const n = Number(this.props.tabSize ?? 4);
|
|
294
|
+
return Number.isFinite(n) && n >= 1 ? Math.floor(n) : 4;
|
|
295
|
+
}
|
|
296
|
+
// --- per-line layout -----------------------------------------------------
|
|
297
|
+
_resolveColor(color) {
|
|
298
|
+
if (!color)
|
|
299
|
+
return color;
|
|
300
|
+
if (color.startsWith('$'))
|
|
301
|
+
return this._themeColor(color.slice(1));
|
|
302
|
+
return color;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* The token palette in force: the prop when given, else whichever
|
|
306
|
+
* built-in set reads on this editor's background — the resolved
|
|
307
|
+
* `backgroundColor` when it is a judgeable colour, the theme's
|
|
308
|
+
* `background` otherwise. This is what makes one `<CodeEditor>` legible
|
|
309
|
+
* on a light *and* a dark desktop with no props at all.
|
|
310
|
+
*/
|
|
311
|
+
_tokenStyles() {
|
|
312
|
+
const explicit = this.props.tokenStyles;
|
|
313
|
+
if (explicit)
|
|
314
|
+
return explicit;
|
|
315
|
+
const s = this.style;
|
|
316
|
+
const own = isDarkBackground(s.backgroundColor);
|
|
317
|
+
const dark = own ?? isDarkBackground(this._themeColor('background'));
|
|
318
|
+
return dark ? DARK_TOKEN_STYLES : LIGHT_TOKEN_STYLES;
|
|
319
|
+
}
|
|
320
|
+
/** Shared by the paint defaults that need a light/dark answer. */
|
|
321
|
+
_onDark() {
|
|
322
|
+
const s = this.style;
|
|
323
|
+
return ((isDarkBackground(s.backgroundColor) ??
|
|
324
|
+
isDarkBackground(this._themeColor('background'))) === true);
|
|
325
|
+
}
|
|
326
|
+
_lineEntry(line) {
|
|
327
|
+
const raw = this._lines[line];
|
|
328
|
+
const map = tabMap(raw, this._tabSize());
|
|
329
|
+
const tokens = this._tokenizer()?.lineTokens(line) ?? [];
|
|
330
|
+
const styleKey = this._metricsKey;
|
|
331
|
+
const cached = this._lineCache.get(line);
|
|
332
|
+
if (cached &&
|
|
333
|
+
cached.display === map.display &&
|
|
334
|
+
cached.tokens === tokens &&
|
|
335
|
+
cached.styleKey === styleKey) {
|
|
336
|
+
return cached;
|
|
337
|
+
}
|
|
338
|
+
const fonts = this._fonts();
|
|
339
|
+
const base = this._textStyle();
|
|
340
|
+
let layout = null;
|
|
341
|
+
if (fonts) {
|
|
342
|
+
const styles = this._tokenStyles();
|
|
343
|
+
const spans = [];
|
|
344
|
+
let cursor = 0;
|
|
345
|
+
for (const t of tokens) {
|
|
346
|
+
const from = map.toDisplay(t.from);
|
|
347
|
+
const to = map.toDisplay(t.to);
|
|
348
|
+
if (from > cursor) {
|
|
349
|
+
spans.push({ text: map.display.slice(cursor, from) });
|
|
350
|
+
}
|
|
351
|
+
if (to > from) {
|
|
352
|
+
const st = tokenStyleFor(styles, t.type);
|
|
353
|
+
const span = {
|
|
354
|
+
text: map.display.slice(from, to),
|
|
355
|
+
};
|
|
356
|
+
if (st) {
|
|
357
|
+
const color = this._resolveColor(st.color);
|
|
358
|
+
if (color)
|
|
359
|
+
span.color = color;
|
|
360
|
+
if (st.weight)
|
|
361
|
+
span.weight = st.weight;
|
|
362
|
+
if (st.fontStyle)
|
|
363
|
+
span.style = st.fontStyle;
|
|
364
|
+
}
|
|
365
|
+
spans.push(span);
|
|
366
|
+
}
|
|
367
|
+
cursor = Math.max(cursor, to);
|
|
368
|
+
}
|
|
369
|
+
if (cursor < map.display.length) {
|
|
370
|
+
spans.push({ text: map.display.slice(cursor) });
|
|
371
|
+
}
|
|
372
|
+
layout = fonts.layout(spans.length > 0 ? spans : [{ text: '' }], base);
|
|
373
|
+
}
|
|
374
|
+
const entry = {
|
|
375
|
+
display: map.display,
|
|
376
|
+
tokens,
|
|
377
|
+
styleKey,
|
|
378
|
+
layout,
|
|
379
|
+
map,
|
|
380
|
+
};
|
|
381
|
+
this._lineCache.set(line, entry);
|
|
382
|
+
if (layout && layout.width > this._widest)
|
|
383
|
+
this._widest = layout.width;
|
|
384
|
+
return entry;
|
|
385
|
+
}
|
|
386
|
+
/** Caret x (display pixels from the text origin) for a position. */
|
|
387
|
+
_caretX(pos) {
|
|
388
|
+
const entry = this._lineEntry(pos.line);
|
|
389
|
+
if (!entry.layout)
|
|
390
|
+
return 0;
|
|
391
|
+
const disp = entry.map.toDisplay(pos.ch);
|
|
392
|
+
return entry.layout.caretPosition(utf16ToCp(entry.display, disp)).x;
|
|
393
|
+
}
|
|
394
|
+
/** Window coordinates → document position. */
|
|
395
|
+
_posAt(x, y) {
|
|
396
|
+
this._metrics();
|
|
397
|
+
const content = this._contentRect();
|
|
398
|
+
const textX = content.x + this._gutterWidth();
|
|
399
|
+
const line = Math.max(0, Math.min(this._lines.length - 1, Math.floor((y - content.y + this._scrollY) / this._lineH)));
|
|
400
|
+
const entry = this._lineEntry(line);
|
|
401
|
+
if (!entry.layout)
|
|
402
|
+
return { line, ch: 0 };
|
|
403
|
+
const cp = entry.layout.indexAt(x - textX + this._scrollX, 0);
|
|
404
|
+
const disp = cpToUtf16(entry.display, cp);
|
|
405
|
+
return clampPos(this._lines, { line, ch: entry.map.toRaw(disp) });
|
|
406
|
+
}
|
|
407
|
+
/** Font facts the completion popup sizes itself with. */
|
|
408
|
+
metrics() {
|
|
409
|
+
this._metrics();
|
|
410
|
+
const s = this._textStyle();
|
|
411
|
+
return {
|
|
412
|
+
lineHeight: this._lineH,
|
|
413
|
+
charWidth: this._charW,
|
|
414
|
+
family: String(s.family),
|
|
415
|
+
size: Number(s.size),
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
/** Text width in this editor's font. The built-in completion popup no
|
|
419
|
+
* longer needs it — a `<popup anchor>` measures its own rows — but an app
|
|
420
|
+
* laying out its own overlay against the text still does. */
|
|
421
|
+
measureText(text) {
|
|
422
|
+
const fonts = this._fonts();
|
|
423
|
+
if (!fonts)
|
|
424
|
+
return text.length * this._charW;
|
|
425
|
+
return fonts.layout(text, this._textStyle()).width;
|
|
426
|
+
}
|
|
427
|
+
/** Caret geometry relative to this node's origin — node coordinates, which
|
|
428
|
+
* is exactly what a `<popup anchor={{at}}>` takes, so the completion popup
|
|
429
|
+
* hands this straight through and nothing here has to know where the
|
|
430
|
+
* editor sits on screen. */
|
|
431
|
+
caretRect() {
|
|
432
|
+
this._syncProps();
|
|
433
|
+
this._metrics();
|
|
434
|
+
const content = this._contentRect();
|
|
435
|
+
const textX = content.x + this._gutterWidth() - this.abs.x;
|
|
436
|
+
return {
|
|
437
|
+
x: textX + this._caretX(this._caret) - this._scrollX,
|
|
438
|
+
y: content.y - this.abs.y + this._caret.line * this._lineH - this._scrollY,
|
|
439
|
+
height: this._lineH,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
// --- scrolling -----------------------------------------------------------
|
|
443
|
+
_maxScrollY() {
|
|
444
|
+
const content = this._contentRect();
|
|
445
|
+
return Math.max(0, this._lines.length * this._lineHeight() - content.height);
|
|
446
|
+
}
|
|
447
|
+
_maxScrollX() {
|
|
448
|
+
const content = this._contentRect();
|
|
449
|
+
const textW = content.width - this._gutterWidth();
|
|
450
|
+
return Math.max(0, this._widest + CARET_MARGIN - textW);
|
|
451
|
+
}
|
|
452
|
+
/** Wheel notches (deltaX/deltaY arrive as ±1 per notch) → pixels. */
|
|
453
|
+
handleWheel(ev) {
|
|
454
|
+
const step = this._lineHeight() * 3;
|
|
455
|
+
return this.scrollBy(ev.deltaX * step, ev.deltaY * step);
|
|
456
|
+
}
|
|
457
|
+
/** Returns true when the offset moved (the component consumes the wheel
|
|
458
|
+
* event exactly then, so an unscrollable editor lets the page scroll). */
|
|
459
|
+
scrollBy(dx, dy) {
|
|
460
|
+
const nx = Math.max(0, Math.min(this._scrollX + dx, this._maxScrollX()));
|
|
461
|
+
const ny = Math.max(0, Math.min(this._scrollY + dy, this._maxScrollY()));
|
|
462
|
+
if (nx === this._scrollX && ny === this._scrollY)
|
|
463
|
+
return false;
|
|
464
|
+
this._scrollX = nx;
|
|
465
|
+
this._scrollY = ny;
|
|
466
|
+
this.root?.invalidate(false, this.abs, 'scroll');
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
469
|
+
_ensureCaretVisible() {
|
|
470
|
+
this._metrics();
|
|
471
|
+
const content = this._contentRect();
|
|
472
|
+
const lineTop = this._caret.line * this._lineH;
|
|
473
|
+
if (lineTop < this._scrollY)
|
|
474
|
+
this._scrollY = lineTop;
|
|
475
|
+
const lineBottom = lineTop + this._lineH;
|
|
476
|
+
if (lineBottom > this._scrollY + content.height) {
|
|
477
|
+
this._scrollY = lineBottom - content.height;
|
|
478
|
+
}
|
|
479
|
+
const textW = content.width - this._gutterWidth();
|
|
480
|
+
const x = this._caretX(this._caret);
|
|
481
|
+
if (x < this._scrollX + CARET_MARGIN) {
|
|
482
|
+
this._scrollX = Math.max(0, x - CARET_MARGIN);
|
|
483
|
+
}
|
|
484
|
+
if (x > this._scrollX + textW - CARET_MARGIN) {
|
|
485
|
+
this._scrollX = x - textW + CARET_MARGIN;
|
|
486
|
+
}
|
|
487
|
+
this._scrollY = Math.max(0, Math.min(this._scrollY, this._maxScrollY()));
|
|
488
|
+
this._scrollX = Math.max(0, this._scrollX);
|
|
489
|
+
}
|
|
490
|
+
// --- events out ----------------------------------------------------------
|
|
491
|
+
_makeEvent(type) {
|
|
492
|
+
const self = this;
|
|
493
|
+
const ev = {
|
|
494
|
+
type,
|
|
495
|
+
value: this.value,
|
|
496
|
+
name: this.name,
|
|
497
|
+
selection: this.selection,
|
|
498
|
+
target: self,
|
|
499
|
+
currentTarget: self,
|
|
500
|
+
nativeEvent: this._keyNative ?? null,
|
|
501
|
+
defaultPrevented: false,
|
|
502
|
+
preventDefault() {
|
|
503
|
+
ev.defaultPrevented = true;
|
|
504
|
+
},
|
|
505
|
+
stopPropagation() { },
|
|
506
|
+
};
|
|
507
|
+
return ev;
|
|
508
|
+
}
|
|
509
|
+
_fire(prop, value) {
|
|
510
|
+
const handler = this.props[prop];
|
|
511
|
+
if (!handler)
|
|
512
|
+
return;
|
|
513
|
+
const previous = this._pendingValue;
|
|
514
|
+
this._pendingValue = value;
|
|
515
|
+
try {
|
|
516
|
+
handler(this._makeEvent(prop === 'onSubmit'
|
|
517
|
+
? 'submit'
|
|
518
|
+
: prop === 'onSelectionChange'
|
|
519
|
+
? 'selectionchange'
|
|
520
|
+
: 'change'));
|
|
521
|
+
}
|
|
522
|
+
finally {
|
|
523
|
+
this._pendingValue = previous;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
_repaint() {
|
|
527
|
+
this._caretOn = true;
|
|
528
|
+
this.root?.invalidate(false, this.abs, 'text');
|
|
529
|
+
}
|
|
530
|
+
// --- history -------------------------------------------------------------
|
|
531
|
+
_noteExternal() {
|
|
532
|
+
this._undoRun = null;
|
|
533
|
+
const value = this._lines.join('\n');
|
|
534
|
+
const top = this._history[this._historyIndex];
|
|
535
|
+
if (top && top.value === value)
|
|
536
|
+
return;
|
|
537
|
+
this._pushHistory({ value, caret: this._caret, anchor: this._anchor });
|
|
538
|
+
}
|
|
539
|
+
_pushHistory(entry) {
|
|
540
|
+
this._history.length = this._historyIndex + 1;
|
|
541
|
+
this._history.push(entry);
|
|
542
|
+
if (this._history.length > UNDO_LIMIT) {
|
|
543
|
+
this._history.splice(0, this._history.length - UNDO_LIMIT);
|
|
544
|
+
}
|
|
545
|
+
this._historyIndex = this._history.length - 1;
|
|
546
|
+
}
|
|
547
|
+
breakUndoRun() {
|
|
548
|
+
this._undoRun = null;
|
|
549
|
+
}
|
|
550
|
+
get canUndo() {
|
|
551
|
+
return this._historyIndex > 0;
|
|
552
|
+
}
|
|
553
|
+
get canRedo() {
|
|
554
|
+
return this._historyIndex < this._history.length - 1;
|
|
555
|
+
}
|
|
556
|
+
undo() {
|
|
557
|
+
this._syncProps();
|
|
558
|
+
if (!this.canUndo)
|
|
559
|
+
return;
|
|
560
|
+
this._historyIndex--;
|
|
561
|
+
this._applyHistory(this._history[this._historyIndex]);
|
|
562
|
+
}
|
|
563
|
+
redo() {
|
|
564
|
+
this._syncProps();
|
|
565
|
+
if (!this.canRedo)
|
|
566
|
+
return;
|
|
567
|
+
this._historyIndex++;
|
|
568
|
+
this._applyHistory(this._history[this._historyIndex]);
|
|
569
|
+
}
|
|
570
|
+
_applyHistory(entry) {
|
|
571
|
+
this._undoRun = null;
|
|
572
|
+
this._goalX = null;
|
|
573
|
+
this._applyLines(entry.value.split('\n'), null);
|
|
574
|
+
this._caret = clampPos(this._lines, entry.caret);
|
|
575
|
+
this._anchor = clampPos(this._lines, entry.anchor);
|
|
576
|
+
this._afterEdit(entry.value);
|
|
577
|
+
}
|
|
578
|
+
// --- editing core --------------------------------------------------------
|
|
579
|
+
_selectionRange() {
|
|
580
|
+
return [
|
|
581
|
+
minPos(this._anchor, this._caret),
|
|
582
|
+
maxPos(this._anchor, this._caret),
|
|
583
|
+
];
|
|
584
|
+
}
|
|
585
|
+
_hasSelection() {
|
|
586
|
+
return !posEqual(this._anchor, this._caret);
|
|
587
|
+
}
|
|
588
|
+
selectedText() {
|
|
589
|
+
this._syncProps();
|
|
590
|
+
const [a, b] = this._selectionRange();
|
|
591
|
+
if (posEqual(a, b))
|
|
592
|
+
return '';
|
|
593
|
+
if (a.line === b.line)
|
|
594
|
+
return this._lines[a.line].slice(a.ch, b.ch);
|
|
595
|
+
const parts = [this._lines[a.line].slice(a.ch)];
|
|
596
|
+
for (let i = a.line + 1; i < b.line; i++)
|
|
597
|
+
parts.push(this._lines[i]);
|
|
598
|
+
parts.push(this._lines[b.line].slice(0, b.ch));
|
|
599
|
+
return parts.join('\n');
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* The one place text changes. Replaces `[from, to)` with `text`, moves the
|
|
603
|
+
* caret to the end of the insertion, fires `onChange`, and manages undo —
|
|
604
|
+
* `run` names a coalescable edit kind (`'type'`, `'delete-back'`,
|
|
605
|
+
* `'delete-forward'`); anything else is its own undo step.
|
|
606
|
+
*/
|
|
607
|
+
replaceRange(from, to, text, run = null) {
|
|
608
|
+
this._syncProps();
|
|
609
|
+
if (this.props.readOnly)
|
|
610
|
+
return;
|
|
611
|
+
const a = clampPos(this._lines, minPos(from, to));
|
|
612
|
+
const b = clampPos(this._lines, maxPos(from, to));
|
|
613
|
+
const insert = String(text).replace(/\r\n?/g, '\n');
|
|
614
|
+
const before = this._lines[a.line].slice(0, a.ch);
|
|
615
|
+
const after = this._lines[b.line].slice(b.ch);
|
|
616
|
+
const inserted = insert.split('\n');
|
|
617
|
+
inserted[0] = before + inserted[0];
|
|
618
|
+
const caretCh = inserted[inserted.length - 1].length;
|
|
619
|
+
inserted[inserted.length - 1] += after;
|
|
620
|
+
const newLines = this._lines.slice();
|
|
621
|
+
newLines.splice(a.line, b.line - a.line + 1, ...inserted);
|
|
622
|
+
const coalesce = run !== null &&
|
|
623
|
+
run === this._undoRun &&
|
|
624
|
+
this._historyIndex === this._history.length - 1;
|
|
625
|
+
this._applyLines(newLines, {
|
|
626
|
+
fromLine: a.line,
|
|
627
|
+
removed: b.line - a.line + 1,
|
|
628
|
+
inserted: inserted.length,
|
|
629
|
+
});
|
|
630
|
+
this._caret = clampPos(this._lines, {
|
|
631
|
+
line: a.line + inserted.length - 1,
|
|
632
|
+
ch: caretCh,
|
|
633
|
+
});
|
|
634
|
+
this._anchor = { ...this._caret };
|
|
635
|
+
this._goalX = null;
|
|
636
|
+
const value = this._lines.join('\n');
|
|
637
|
+
if (coalesce) {
|
|
638
|
+
this._history[this._historyIndex] = {
|
|
639
|
+
value,
|
|
640
|
+
caret: this._caret,
|
|
641
|
+
anchor: this._anchor,
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
this._pushHistory({ value, caret: this._caret, anchor: this._anchor });
|
|
646
|
+
}
|
|
647
|
+
this._undoRun = run;
|
|
648
|
+
this._afterEdit(value);
|
|
649
|
+
}
|
|
650
|
+
_applyLines(newLines, edit) {
|
|
651
|
+
if (edit) {
|
|
652
|
+
// A tokenizer keeps the array `setLines` handed it (that is the
|
|
653
|
+
// contract in `Tokenizer`) and is told about changes through `edit()`
|
|
654
|
+
// alone, so an incremental edit has to *mutate* the live array —
|
|
655
|
+
// swapping in a fresh one would leave every engine tokenizing the
|
|
656
|
+
// pre-edit text at the new line numbers, painting a split line with
|
|
657
|
+
// its neighbour's colours. Callers only touch the edited span, so
|
|
658
|
+
// splicing that span across reproduces `newLines` exactly.
|
|
659
|
+
this._lines.splice(edit.fromLine, edit.removed, ...newLines.slice(edit.fromLine, edit.fromLine + edit.inserted));
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
this._lines = newLines;
|
|
663
|
+
}
|
|
664
|
+
const tok = this._tokenizer();
|
|
665
|
+
if (edit && tok) {
|
|
666
|
+
tok.edit(edit);
|
|
667
|
+
// line indices at and below the edit moved or changed; drop them
|
|
668
|
+
for (const key of [...this._lineCache.keys()]) {
|
|
669
|
+
if (key >= edit.fromLine)
|
|
670
|
+
this._lineCache.delete(key);
|
|
671
|
+
}
|
|
672
|
+
if (edit.removed !== edit.inserted)
|
|
673
|
+
this._lineCache.clear();
|
|
674
|
+
}
|
|
675
|
+
else {
|
|
676
|
+
tok?.setLines(this._lines);
|
|
677
|
+
this._lineCache.clear();
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
_afterEdit(value) {
|
|
681
|
+
this._synced = value;
|
|
682
|
+
this._ensureCaretVisible();
|
|
683
|
+
this._repaint();
|
|
684
|
+
this._fire('onChange', value);
|
|
685
|
+
this._fire('onSelectionChange', value);
|
|
686
|
+
}
|
|
687
|
+
insertText(text, run = null) {
|
|
688
|
+
const [a, b] = this._selectionRange();
|
|
689
|
+
this.replaceRange(a, b, text, this._hasSelection() ? null : run);
|
|
690
|
+
}
|
|
691
|
+
moveCaret(pos, extend) {
|
|
692
|
+
this._syncProps();
|
|
693
|
+
this.breakUndoRun();
|
|
694
|
+
this._caret = clampPos(this._lines, pos);
|
|
695
|
+
if (!extend)
|
|
696
|
+
this._anchor = { ...this._caret };
|
|
697
|
+
this._ensureCaretVisible();
|
|
698
|
+
this._repaint();
|
|
699
|
+
this._fire('onSelectionChange', this.value);
|
|
700
|
+
}
|
|
701
|
+
select(anchor, head) {
|
|
702
|
+
this._syncProps();
|
|
703
|
+
this.breakUndoRun();
|
|
704
|
+
this._anchor = clampPos(this._lines, anchor);
|
|
705
|
+
this._caret = clampPos(this._lines, head);
|
|
706
|
+
this._ensureCaretVisible();
|
|
707
|
+
this._repaint();
|
|
708
|
+
this._fire('onSelectionChange', this.value);
|
|
709
|
+
}
|
|
710
|
+
// `: this`, matching the base declaration core grew in #294 — a subclass
|
|
711
|
+
// narrowing it to void stops being structurally a DrawnNode.
|
|
712
|
+
selectAll() {
|
|
713
|
+
const last = this._lines.length - 1;
|
|
714
|
+
this.select({ line: 0, ch: 0 }, { line: last, ch: this._lines[last].length });
|
|
715
|
+
this._ownPrimary();
|
|
716
|
+
return this;
|
|
717
|
+
}
|
|
718
|
+
// --- clipboard -----------------------------------------------------------
|
|
719
|
+
_clipboard() {
|
|
720
|
+
const app = this.app;
|
|
721
|
+
return app?.clipboard ?? null;
|
|
722
|
+
}
|
|
723
|
+
copySelection(selection = 'CLIPBOARD') {
|
|
724
|
+
const text = this.selectedText();
|
|
725
|
+
if (!text)
|
|
726
|
+
return;
|
|
727
|
+
this._clipboard()
|
|
728
|
+
?.write(text, { selection })
|
|
729
|
+
.catch(() => { });
|
|
730
|
+
}
|
|
731
|
+
_ownPrimary() {
|
|
732
|
+
if (this._hasSelection())
|
|
733
|
+
this.copySelection('PRIMARY');
|
|
734
|
+
}
|
|
735
|
+
pasteFrom(selection = 'CLIPBOARD') {
|
|
736
|
+
this._clipboard()
|
|
737
|
+
?.read({ selection })
|
|
738
|
+
.then((text) => {
|
|
739
|
+
if (!this.destroyed && typeof text === 'string' && text) {
|
|
740
|
+
this.insertText(text);
|
|
741
|
+
}
|
|
742
|
+
})
|
|
743
|
+
.catch(() => { });
|
|
744
|
+
}
|
|
745
|
+
// --- indentation & comments ---------------------------------------------
|
|
746
|
+
_indentUnit() {
|
|
747
|
+
return this.props.insertSpaces === false
|
|
748
|
+
? '\t'
|
|
749
|
+
: ' '.repeat(this._tabSize());
|
|
750
|
+
}
|
|
751
|
+
/** Tab with a multi-line selection (or dir < 0): shift the lines. */
|
|
752
|
+
indentSelection(dir) {
|
|
753
|
+
this._syncProps();
|
|
754
|
+
if (this.props.readOnly)
|
|
755
|
+
return;
|
|
756
|
+
const [a, b] = this._selectionRange();
|
|
757
|
+
const endLine = b.line > a.line && b.ch === 0 ? b.line - 1 : b.line;
|
|
758
|
+
const unit = this._indentUnit();
|
|
759
|
+
const size = this._tabSize();
|
|
760
|
+
const newLines = this._lines.slice();
|
|
761
|
+
for (let i = a.line; i <= endLine; i++) {
|
|
762
|
+
const line = newLines[i];
|
|
763
|
+
if (dir > 0) {
|
|
764
|
+
if (line.length > 0)
|
|
765
|
+
newLines[i] = unit + line;
|
|
766
|
+
}
|
|
767
|
+
else {
|
|
768
|
+
if (line.startsWith('\t'))
|
|
769
|
+
newLines[i] = line.slice(1);
|
|
770
|
+
else {
|
|
771
|
+
let strip = 0;
|
|
772
|
+
while (strip < size && line.charAt(strip) === ' ')
|
|
773
|
+
strip++;
|
|
774
|
+
newLines[i] = line.slice(strip);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
this._replaceAllPreservingSelection(newLines, a.line, endLine);
|
|
779
|
+
}
|
|
780
|
+
toggleLineComment() {
|
|
781
|
+
this._syncProps();
|
|
782
|
+
if (this.props.readOnly)
|
|
783
|
+
return;
|
|
784
|
+
const marker = this.language?.data?.lineComment;
|
|
785
|
+
if (!marker)
|
|
786
|
+
return;
|
|
787
|
+
const [a, b] = this._selectionRange();
|
|
788
|
+
const endLine = b.line > a.line && b.ch === 0 ? b.line - 1 : b.line;
|
|
789
|
+
const targets = [];
|
|
790
|
+
for (let i = a.line; i <= endLine; i++) {
|
|
791
|
+
if (this._lines[i].trim().length > 0)
|
|
792
|
+
targets.push(i);
|
|
793
|
+
}
|
|
794
|
+
if (targets.length === 0)
|
|
795
|
+
return;
|
|
796
|
+
const allCommented = targets.every((i) => this._lines[i].trimStart().startsWith(marker));
|
|
797
|
+
const newLines = this._lines.slice();
|
|
798
|
+
for (const i of targets) {
|
|
799
|
+
const line = newLines[i];
|
|
800
|
+
const indent = line.length - line.trimStart().length;
|
|
801
|
+
if (allCommented) {
|
|
802
|
+
const rest = line.slice(indent + marker.length);
|
|
803
|
+
newLines[i] =
|
|
804
|
+
line.slice(0, indent) + (rest.startsWith(' ') ? rest.slice(1) : rest);
|
|
805
|
+
}
|
|
806
|
+
else {
|
|
807
|
+
newLines[i] = line.slice(0, indent) + marker + ' ' + line.slice(indent);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
this._replaceAllPreservingSelection(newLines, a.line, endLine);
|
|
811
|
+
}
|
|
812
|
+
/** Line-rewriting commands share this: one undo step, selection kept on
|
|
813
|
+
* the same lines (clamped to their new lengths). */
|
|
814
|
+
_replaceAllPreservingSelection(newLines, fromLine, toLine) {
|
|
815
|
+
const caret = this._caret;
|
|
816
|
+
const anchor = this._anchor;
|
|
817
|
+
this._applyLines(newLines, {
|
|
818
|
+
fromLine,
|
|
819
|
+
removed: toLine - fromLine + 1,
|
|
820
|
+
inserted: toLine - fromLine + 1,
|
|
821
|
+
});
|
|
822
|
+
this._caret = clampPos(this._lines, caret);
|
|
823
|
+
this._anchor = clampPos(this._lines, anchor);
|
|
824
|
+
this._undoRun = null;
|
|
825
|
+
const value = this._lines.join('\n');
|
|
826
|
+
this._pushHistory({ value, caret: this._caret, anchor: this._anchor });
|
|
827
|
+
this._afterEdit(value);
|
|
828
|
+
}
|
|
829
|
+
// --- input: keyboard -----------------------------------------------------
|
|
830
|
+
/**
|
|
831
|
+
* The editor's own key behaviour, run by the EventManager after the app's
|
|
832
|
+
* `onKeyDown` handlers and not at all if one of them called
|
|
833
|
+
* `preventDefault()` — so `<CodeEditor>`'s completion popup vetoes this
|
|
834
|
+
* simply by consuming the key first, and so does an application handler.
|
|
835
|
+
*
|
|
836
|
+
* Tab is an ordinary key here, ahead of focus traversal, which is what
|
|
837
|
+
* lets a bare `<codeeditor>` indent with it. Owning Tab means owning the
|
|
838
|
+
* keyboard user's way out, hence the convention `docs/extending.md` asks
|
|
839
|
+
* elements to converge on: **Escape arms one pass-through Tab**. Escape on
|
|
840
|
+
* its own does nothing visible, the next Tab leaves, and the one after
|
|
841
|
+
* that indents again.
|
|
842
|
+
*/
|
|
843
|
+
defaultKeyDown(ev) {
|
|
844
|
+
if (this.props.disabled)
|
|
845
|
+
return;
|
|
846
|
+
const k = ev.keysym;
|
|
847
|
+
if (k === XK_ESCAPE) {
|
|
848
|
+
this._tabEscapes = true;
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
if (k === XK_TAB && this._tabEscapes) {
|
|
852
|
+
this._tabEscapes = false;
|
|
853
|
+
return; // this one belongs to the focus cycle
|
|
854
|
+
}
|
|
855
|
+
this._tabEscapes = false;
|
|
856
|
+
if (this.handleKeyDown(ev))
|
|
857
|
+
ev.preventDefault?.();
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* The editor's key behaviour as a predicate — true when the key was
|
|
861
|
+
* consumed. Kept apart from {@link defaultKeyDown} because the editing
|
|
862
|
+
* model is also driven directly in tests and by an app that wires the raw
|
|
863
|
+
* element itself; the default action is this plus the Tab/Escape bargain.
|
|
864
|
+
*/
|
|
865
|
+
handleKeyDown(ev) {
|
|
866
|
+
this._syncProps();
|
|
867
|
+
const previousNative = this._keyNative;
|
|
868
|
+
this._keyNative = ev.nativeEvent ?? null;
|
|
869
|
+
try {
|
|
870
|
+
return this._editKey(ev);
|
|
871
|
+
}
|
|
872
|
+
finally {
|
|
873
|
+
this._keyNative = previousNative;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
_editKey(ev) {
|
|
877
|
+
const k = ev.keysym;
|
|
878
|
+
const readOnly = Boolean(this.props.readOnly);
|
|
879
|
+
const [a, b] = this._selectionRange();
|
|
880
|
+
const hasSelection = this._hasSelection();
|
|
881
|
+
const wordChars = this.language?.data?.wordChars ?? '';
|
|
882
|
+
if (k === XK_RETURN || k === XK_KP_ENTER) {
|
|
883
|
+
if (ev.ctrlKey) {
|
|
884
|
+
this._fire('onSubmit', this.value);
|
|
885
|
+
return true;
|
|
886
|
+
}
|
|
887
|
+
if (readOnly)
|
|
888
|
+
return false;
|
|
889
|
+
const lineText = this._lines[a.line];
|
|
890
|
+
const indentMatch = /^[\t ]*/.exec(lineText)?.[0] ?? '';
|
|
891
|
+
const indent = indentMatch.slice(0, Math.min(indentMatch.length, a.ch));
|
|
892
|
+
const before = lineText.slice(0, a.ch);
|
|
893
|
+
const rule = this.language?.data?.indentAfter;
|
|
894
|
+
const deeper = rule ? rule.test(before) : false;
|
|
895
|
+
const after = this._lines[b.line].slice(b.ch);
|
|
896
|
+
if (deeper && /^[)\]}]/.test(after)) {
|
|
897
|
+
// the caret sits between a fresh pair — `{|}` — so Enter opens it
|
|
898
|
+
// the way every editor does: an indented line for the caret, the
|
|
899
|
+
// closer on its own line at the old depth
|
|
900
|
+
const unit = this._indentUnit();
|
|
901
|
+
this.replaceRange(a, b, '\n' + indent + unit + '\n' + indent);
|
|
902
|
+
this.moveCaret({ line: a.line + 1, ch: indent.length + unit.length }, false);
|
|
903
|
+
return true;
|
|
904
|
+
}
|
|
905
|
+
this.insertText('\n' + indent + (deeper ? this._indentUnit() : ''));
|
|
906
|
+
return true;
|
|
907
|
+
}
|
|
908
|
+
if (k === XK_BACKSPACE) {
|
|
909
|
+
if (readOnly)
|
|
910
|
+
return true;
|
|
911
|
+
if (hasSelection)
|
|
912
|
+
this.replaceRange(a, b, '');
|
|
913
|
+
else if (ev.ctrlKey) {
|
|
914
|
+
this.replaceRange(wordBoundary(this._lines, a, -1, wordChars), a, '');
|
|
915
|
+
}
|
|
916
|
+
else {
|
|
917
|
+
const from = movePos(this._lines, a, -1);
|
|
918
|
+
if (!posEqual(from, a))
|
|
919
|
+
this.replaceRange(from, a, '', 'delete-back');
|
|
920
|
+
}
|
|
921
|
+
return true;
|
|
922
|
+
}
|
|
923
|
+
if (k === XK_DELETE) {
|
|
924
|
+
if (readOnly)
|
|
925
|
+
return true;
|
|
926
|
+
if (hasSelection)
|
|
927
|
+
this.replaceRange(a, b, '');
|
|
928
|
+
else if (ev.ctrlKey) {
|
|
929
|
+
this.replaceRange(a, wordBoundary(this._lines, a, 1, wordChars), '');
|
|
930
|
+
}
|
|
931
|
+
else {
|
|
932
|
+
const to = movePos(this._lines, a, 1);
|
|
933
|
+
if (!posEqual(to, a))
|
|
934
|
+
this.replaceRange(a, to, '', 'delete-forward');
|
|
935
|
+
}
|
|
936
|
+
return true;
|
|
937
|
+
}
|
|
938
|
+
if (k === XK_LEFT || k === XK_RIGHT) {
|
|
939
|
+
const dir = k === XK_LEFT ? -1 : 1;
|
|
940
|
+
let target;
|
|
941
|
+
if (ev.ctrlKey) {
|
|
942
|
+
target = wordBoundary(this._lines, this._caret, dir, wordChars);
|
|
943
|
+
}
|
|
944
|
+
else if (!ev.shiftKey && hasSelection) {
|
|
945
|
+
target = dir < 0 ? a : b;
|
|
946
|
+
}
|
|
947
|
+
else {
|
|
948
|
+
target = movePos(this._lines, this._caret, dir);
|
|
949
|
+
}
|
|
950
|
+
this.moveCaret(target, ev.shiftKey);
|
|
951
|
+
if (ev.shiftKey)
|
|
952
|
+
this._ownPrimary();
|
|
953
|
+
return true;
|
|
954
|
+
}
|
|
955
|
+
if (k === XK_UP || k === XK_DOWN) {
|
|
956
|
+
this._verticalMove(k === XK_UP ? -1 : 1, ev.shiftKey);
|
|
957
|
+
return true;
|
|
958
|
+
}
|
|
959
|
+
if (k === XK_PAGE_UP || k === XK_PAGE_DOWN) {
|
|
960
|
+
const content = this._contentRect();
|
|
961
|
+
const page = Math.max(1, Math.floor(content.height / this._lineHeight()) - 1);
|
|
962
|
+
this._verticalMove((k === XK_PAGE_UP ? -1 : 1) * page, ev.shiftKey);
|
|
963
|
+
return true;
|
|
964
|
+
}
|
|
965
|
+
if (k === XK_HOME) {
|
|
966
|
+
if (ev.ctrlKey) {
|
|
967
|
+
this.moveCaret({ line: 0, ch: 0 }, ev.shiftKey);
|
|
968
|
+
return true;
|
|
969
|
+
}
|
|
970
|
+
const text = this._lines[this._caret.line];
|
|
971
|
+
const first = text.length - text.trimStart().length;
|
|
972
|
+
const ch = this._caret.ch === first ? 0 : first;
|
|
973
|
+
this.moveCaret({ line: this._caret.line, ch }, ev.shiftKey);
|
|
974
|
+
return true;
|
|
975
|
+
}
|
|
976
|
+
if (k === XK_END) {
|
|
977
|
+
if (ev.ctrlKey) {
|
|
978
|
+
const last = this._lines.length - 1;
|
|
979
|
+
this.moveCaret({ line: last, ch: this._lines[last].length }, ev.shiftKey);
|
|
980
|
+
return true;
|
|
981
|
+
}
|
|
982
|
+
this.moveCaret({ line: this._caret.line, ch: this._lines[this._caret.line].length }, ev.shiftKey);
|
|
983
|
+
return true;
|
|
984
|
+
}
|
|
985
|
+
if (k === XK_TAB) {
|
|
986
|
+
if (readOnly)
|
|
987
|
+
return false;
|
|
988
|
+
if (ev.shiftKey) {
|
|
989
|
+
this.indentSelection(-1);
|
|
990
|
+
return true;
|
|
991
|
+
}
|
|
992
|
+
if (a.line !== b.line) {
|
|
993
|
+
this.indentSelection(1);
|
|
994
|
+
return true;
|
|
995
|
+
}
|
|
996
|
+
if (this.props.insertSpaces === false) {
|
|
997
|
+
this.insertText('\t', 'type');
|
|
998
|
+
return true;
|
|
999
|
+
}
|
|
1000
|
+
// spaces to the next tab stop, measured in display columns
|
|
1001
|
+
const map = tabMap(this._lines[a.line].slice(0, a.ch), this._tabSize());
|
|
1002
|
+
const col = map.display.length;
|
|
1003
|
+
const size = this._tabSize();
|
|
1004
|
+
this.insertText(' '.repeat(size - (col % size)), 'type');
|
|
1005
|
+
return true;
|
|
1006
|
+
}
|
|
1007
|
+
if (k === XK_ESCAPE) {
|
|
1008
|
+
return false; // the component owns Escape (completion, then Tab-out)
|
|
1009
|
+
}
|
|
1010
|
+
if (ev.ctrlKey) {
|
|
1011
|
+
const letter = ctrlChordLetter(ev);
|
|
1012
|
+
if (letter === 0x61 /* a */) {
|
|
1013
|
+
this.selectAll();
|
|
1014
|
+
return true;
|
|
1015
|
+
}
|
|
1016
|
+
if (letter === 0x63 /* c */) {
|
|
1017
|
+
this.copySelection();
|
|
1018
|
+
return true;
|
|
1019
|
+
}
|
|
1020
|
+
if (letter === 0x78 /* x */) {
|
|
1021
|
+
this.copySelection();
|
|
1022
|
+
if (!readOnly && hasSelection)
|
|
1023
|
+
this.replaceRange(a, b, '');
|
|
1024
|
+
return true;
|
|
1025
|
+
}
|
|
1026
|
+
if (letter === 0x76 /* v */) {
|
|
1027
|
+
if (!readOnly)
|
|
1028
|
+
this.pasteFrom();
|
|
1029
|
+
return true;
|
|
1030
|
+
}
|
|
1031
|
+
if (letter === 0x7a /* z */) {
|
|
1032
|
+
if (!readOnly) {
|
|
1033
|
+
if (ev.shiftKey)
|
|
1034
|
+
this.redo();
|
|
1035
|
+
else
|
|
1036
|
+
this.undo();
|
|
1037
|
+
}
|
|
1038
|
+
return true;
|
|
1039
|
+
}
|
|
1040
|
+
if (letter === 0x79 /* y */) {
|
|
1041
|
+
if (!readOnly)
|
|
1042
|
+
this.redo();
|
|
1043
|
+
return true;
|
|
1044
|
+
}
|
|
1045
|
+
if (letter === 0x2f /* / */) {
|
|
1046
|
+
this.toggleLineComment();
|
|
1047
|
+
return true;
|
|
1048
|
+
}
|
|
1049
|
+
return false;
|
|
1050
|
+
}
|
|
1051
|
+
if (!readOnly &&
|
|
1052
|
+
ev.codepoint != null &&
|
|
1053
|
+
ev.codepoint >= 0x20 &&
|
|
1054
|
+
ev.codepoint !== 0x7f) {
|
|
1055
|
+
const ch = String.fromCodePoint(ev.codepoint);
|
|
1056
|
+
this.insertText(ch, 'type');
|
|
1057
|
+
if (/\s/.test(ch))
|
|
1058
|
+
this.breakUndoRun();
|
|
1059
|
+
return true;
|
|
1060
|
+
}
|
|
1061
|
+
return false;
|
|
1062
|
+
}
|
|
1063
|
+
_verticalMove(deltaLines, extend) {
|
|
1064
|
+
if (this._goalX == null)
|
|
1065
|
+
this._goalX = this._caretX(this._caret);
|
|
1066
|
+
const line = Math.max(0, Math.min(this._lines.length - 1, this._caret.line + deltaLines));
|
|
1067
|
+
if (line === this._caret.line) {
|
|
1068
|
+
// pressing against the document's edge: land on its corner, the way
|
|
1069
|
+
// every editor does
|
|
1070
|
+
const ch = deltaLines < 0 ? 0 : this._lines[line].length;
|
|
1071
|
+
this.moveCaret({ line, ch }, extend);
|
|
1072
|
+
if (extend)
|
|
1073
|
+
this._ownPrimary();
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
const entry = this._lineEntry(line);
|
|
1077
|
+
let ch = 0;
|
|
1078
|
+
if (entry.layout) {
|
|
1079
|
+
const cp = entry.layout.indexAt(this._goalX, 0);
|
|
1080
|
+
ch = entry.map.toRaw(cpToUtf16(entry.display, cp));
|
|
1081
|
+
}
|
|
1082
|
+
const goal = this._goalX; // moveCaret clears it; this move keeps it
|
|
1083
|
+
this.moveCaret({ line, ch }, extend);
|
|
1084
|
+
this._goalX = goal;
|
|
1085
|
+
if (extend)
|
|
1086
|
+
this._ownPrimary();
|
|
1087
|
+
}
|
|
1088
|
+
// --- input: mouse --------------------------------------------------------
|
|
1089
|
+
/**
|
|
1090
|
+
* A press: place the caret, start a drag-selection, or paste PRIMARY on
|
|
1091
|
+
* the middle button. The drag and release below are the continuation of
|
|
1092
|
+
* *this* press — core vetoes a gesture once, at its press, so an editor
|
|
1093
|
+
* whose `defaultMouseDown` never ran hears neither, and needs no
|
|
1094
|
+
* `dragging` flag to discover that.
|
|
1095
|
+
*/
|
|
1096
|
+
defaultMouseDown(ev) {
|
|
1097
|
+
if (this.props.disabled)
|
|
1098
|
+
return;
|
|
1099
|
+
this.focus();
|
|
1100
|
+
if (this.handleMouseDown(ev))
|
|
1101
|
+
ev.preventDefault?.();
|
|
1102
|
+
}
|
|
1103
|
+
defaultMouseDrag(ev) {
|
|
1104
|
+
if (this.handleMouseMove(ev))
|
|
1105
|
+
ev.preventDefault?.();
|
|
1106
|
+
}
|
|
1107
|
+
defaultMouseUp() {
|
|
1108
|
+
this.handleMouseUp();
|
|
1109
|
+
}
|
|
1110
|
+
handleMouseDown(ev) {
|
|
1111
|
+
this._syncProps();
|
|
1112
|
+
this.breakUndoRun();
|
|
1113
|
+
const pos = this._posAt(ev.x, ev.y);
|
|
1114
|
+
if (ev.button === 3) {
|
|
1115
|
+
const [a, b] = this._selectionRange();
|
|
1116
|
+
if (comparePos(pos, a) < 0 || comparePos(pos, b) > 0) {
|
|
1117
|
+
this.moveCaret(pos, false);
|
|
1118
|
+
}
|
|
1119
|
+
return true;
|
|
1120
|
+
}
|
|
1121
|
+
if (ev.button === 2) {
|
|
1122
|
+
this.moveCaret(pos, false);
|
|
1123
|
+
if (!this.props.readOnly)
|
|
1124
|
+
this.pasteFrom('PRIMARY');
|
|
1125
|
+
return true;
|
|
1126
|
+
}
|
|
1127
|
+
const detail = ev.detail ?? 1;
|
|
1128
|
+
if (detail >= 3) {
|
|
1129
|
+
this._drag = 'line';
|
|
1130
|
+
this._dragOrigin = pos;
|
|
1131
|
+
this._selectLineRange(pos, pos);
|
|
1132
|
+
}
|
|
1133
|
+
else if (detail === 2) {
|
|
1134
|
+
this._drag = 'word';
|
|
1135
|
+
this._dragOrigin = pos;
|
|
1136
|
+
this._selectWordRange(pos, pos);
|
|
1137
|
+
}
|
|
1138
|
+
else {
|
|
1139
|
+
this._drag = 'char';
|
|
1140
|
+
this._dragOrigin = pos;
|
|
1141
|
+
this.moveCaret(pos, ev.shiftKey);
|
|
1142
|
+
}
|
|
1143
|
+
ev.capturePointer?.();
|
|
1144
|
+
return true;
|
|
1145
|
+
}
|
|
1146
|
+
handleMouseMove(ev) {
|
|
1147
|
+
if (!this._drag)
|
|
1148
|
+
return false;
|
|
1149
|
+
const pos = this._posAt(ev.x, ev.y);
|
|
1150
|
+
if (this._drag === 'char')
|
|
1151
|
+
this.moveCaret(pos, true);
|
|
1152
|
+
else if (this._drag === 'word')
|
|
1153
|
+
this._selectWordRange(this._dragOrigin, pos);
|
|
1154
|
+
else
|
|
1155
|
+
this._selectLineRange(this._dragOrigin, pos);
|
|
1156
|
+
return true;
|
|
1157
|
+
}
|
|
1158
|
+
handleMouseUp() {
|
|
1159
|
+
if (!this._drag)
|
|
1160
|
+
return false;
|
|
1161
|
+
this._drag = null;
|
|
1162
|
+
this._ownPrimary();
|
|
1163
|
+
return true;
|
|
1164
|
+
}
|
|
1165
|
+
_selectWordRange(origin, to) {
|
|
1166
|
+
const extra = this.language?.data?.wordChars ?? '';
|
|
1167
|
+
const w1 = wordRangeAt(this._lines[origin.line], origin.ch, extra);
|
|
1168
|
+
const w2 = wordRangeAt(this._lines[to.line], to.ch, extra);
|
|
1169
|
+
const start = { line: origin.line, ch: w1.from };
|
|
1170
|
+
const end = { line: to.line, ch: w2.to };
|
|
1171
|
+
if (comparePos(to, origin) < 0) {
|
|
1172
|
+
this.select({ line: origin.line, ch: w1.to }, { line: to.line, ch: w2.from });
|
|
1173
|
+
}
|
|
1174
|
+
else {
|
|
1175
|
+
this.select(start, end);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
_selectLineRange(origin, to) {
|
|
1179
|
+
const [first, last] = to.line < origin.line ? [to.line, origin.line] : [origin.line, to.line];
|
|
1180
|
+
if (last + 1 < this._lines.length) {
|
|
1181
|
+
this.select({ line: first, ch: 0 }, { line: last + 1, ch: 0 });
|
|
1182
|
+
}
|
|
1183
|
+
else {
|
|
1184
|
+
this.select({ line: first, ch: 0 }, { line: last, ch: this._lines[last].length });
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
// --- focus ---------------------------------------------------------------
|
|
1188
|
+
// `focus()`/`blur()` come from `Node` — declared as well as implemented
|
|
1189
|
+
// since react-x11#267, so the forwarding shims this file used to carry are
|
|
1190
|
+
// gone. `CodeEditorHandle` keeps promising `void`: the base returns `this`,
|
|
1191
|
+
// and widening the handle to the node type would drag the class's nominal
|
|
1192
|
+
// type into the exported props (see the note on `CodeEditorHandle`).
|
|
1193
|
+
/** Focus is a state, not an event: `onFocus` is where the app hears about
|
|
1194
|
+
* it, and this is where the caret starts blinking. The timer is also
|
|
1195
|
+
* released in `destroySubtree`, because a node that unmounts while focused
|
|
1196
|
+
* is forgotten rather than blurred and this would never run. */
|
|
1197
|
+
defaultFocus() {
|
|
1198
|
+
this.handleFocus();
|
|
1199
|
+
}
|
|
1200
|
+
defaultBlur() {
|
|
1201
|
+
this.handleBlur();
|
|
1202
|
+
}
|
|
1203
|
+
handleFocus() {
|
|
1204
|
+
this._focused = true;
|
|
1205
|
+
this._caretOn = true;
|
|
1206
|
+
if (this._blinkTimer == null) {
|
|
1207
|
+
this._blinkTimer = startInterval(() => {
|
|
1208
|
+
this._caretOn = !this._caretOn;
|
|
1209
|
+
this.root?.invalidate(false, this.abs, 'text');
|
|
1210
|
+
}, CARET_BLINK_MS);
|
|
1211
|
+
}
|
|
1212
|
+
this._repaint();
|
|
1213
|
+
}
|
|
1214
|
+
handleBlur() {
|
|
1215
|
+
this._focused = false;
|
|
1216
|
+
this.breakUndoRun();
|
|
1217
|
+
stopInterval(this._blinkTimer);
|
|
1218
|
+
this._blinkTimer = null;
|
|
1219
|
+
this._repaint();
|
|
1220
|
+
}
|
|
1221
|
+
// --- lifecycle -----------------------------------------------------------
|
|
1222
|
+
applyProps(nextProps, prevProps) {
|
|
1223
|
+
const before = prevProps ?? this.props;
|
|
1224
|
+
super.applyProps(nextProps, prevProps);
|
|
1225
|
+
if (nextProps.value != null)
|
|
1226
|
+
this._syncProps();
|
|
1227
|
+
if (nextProps.language !== before.language) {
|
|
1228
|
+
this._tokenizer(); // re-resolves against the new props
|
|
1229
|
+
this._repaint();
|
|
1230
|
+
}
|
|
1231
|
+
if (nextProps.rows !== before.rows)
|
|
1232
|
+
this.invalidateMeasure('props');
|
|
1233
|
+
if (nextProps.tokenStyles !== before.tokenStyles ||
|
|
1234
|
+
nextProps.diagnostics !== before.diagnostics ||
|
|
1235
|
+
nextProps.tabSize !== before.tabSize ||
|
|
1236
|
+
nextProps.lineNumbers !== before.lineNumbers) {
|
|
1237
|
+
this._lineCache.clear();
|
|
1238
|
+
this._repaint();
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
destroySubtree() {
|
|
1242
|
+
stopInterval(this._blinkTimer);
|
|
1243
|
+
this._blinkTimer = null;
|
|
1244
|
+
this._tok?.dispose?.();
|
|
1245
|
+
this._tok = null;
|
|
1246
|
+
super.destroySubtree();
|
|
1247
|
+
}
|
|
1248
|
+
// --- painting ------------------------------------------------------------
|
|
1249
|
+
paint(ctx) {
|
|
1250
|
+
super.paint(ctx);
|
|
1251
|
+
this._syncProps();
|
|
1252
|
+
this._metrics();
|
|
1253
|
+
const c = ctx;
|
|
1254
|
+
if (typeof c.fillRect !== 'function')
|
|
1255
|
+
return; // mock backend: geometry only
|
|
1256
|
+
const content = this._contentRect();
|
|
1257
|
+
if (content.width <= 0 || content.height <= 0)
|
|
1258
|
+
return;
|
|
1259
|
+
const lineH = this._lineH;
|
|
1260
|
+
const gutterW = this._gutterWidth();
|
|
1261
|
+
const textX = content.x + gutterW - this._scrollX;
|
|
1262
|
+
const props = this.props;
|
|
1263
|
+
const styles = this._textStyle();
|
|
1264
|
+
const baseColor = styles.color;
|
|
1265
|
+
const canClip = typeof c.save === 'function' &&
|
|
1266
|
+
typeof c.beginPath === 'function' &&
|
|
1267
|
+
typeof c.rect === 'function' &&
|
|
1268
|
+
typeof c.clip === 'function';
|
|
1269
|
+
const clipTo = (x, y, w, h) => {
|
|
1270
|
+
if (!canClip)
|
|
1271
|
+
return false;
|
|
1272
|
+
c.save();
|
|
1273
|
+
c.beginPath();
|
|
1274
|
+
c.rect(x, y, w, h);
|
|
1275
|
+
c.clip();
|
|
1276
|
+
return true;
|
|
1277
|
+
};
|
|
1278
|
+
const dark = this._onDark();
|
|
1279
|
+
const first = Math.max(0, Math.floor(this._scrollY / lineH));
|
|
1280
|
+
const last = Math.min(this._lines.length - 1, Math.ceil((this._scrollY + content.height) / lineH));
|
|
1281
|
+
const lineY = (i) => content.y + i * lineH - this._scrollY;
|
|
1282
|
+
// Everything the editor draws — gutter included — stays inside the
|
|
1283
|
+
// content box. The first and last visible rows are partial when
|
|
1284
|
+
// scrolled, and without this their overhang (up to a line each way)
|
|
1285
|
+
// walks into the padding and past it.
|
|
1286
|
+
const outerClip = clipTo(content.x, content.y, content.width, content.height);
|
|
1287
|
+
// gutter background
|
|
1288
|
+
if (gutterW > 0 && props.gutterBackground) {
|
|
1289
|
+
c.fillStyle = this._resolveColor(props.gutterBackground) ?? '#00000008';
|
|
1290
|
+
c.fillRect(content.x, content.y, gutterW, content.height);
|
|
1291
|
+
}
|
|
1292
|
+
// the text region clips one step narrower, so a horizontally scrolled
|
|
1293
|
+
// line cannot run underneath the line numbers
|
|
1294
|
+
const textClip = clipTo(content.x + gutterW, content.y, content.width - gutterW, content.height);
|
|
1295
|
+
// active line
|
|
1296
|
+
if (props.activeLine &&
|
|
1297
|
+
this._focused &&
|
|
1298
|
+
!this._hasSelection() &&
|
|
1299
|
+
this._caret.line >= first &&
|
|
1300
|
+
this._caret.line <= last) {
|
|
1301
|
+
c.fillStyle =
|
|
1302
|
+
this._resolveColor(props.activeLineColor) ??
|
|
1303
|
+
(dark ? 'rgba(255,255,255,0.06)' : 'rgba(120,140,180,0.10)');
|
|
1304
|
+
c.fillRect(content.x + gutterW, lineY(this._caret.line), content.width - gutterW, lineH);
|
|
1305
|
+
}
|
|
1306
|
+
// selection bands
|
|
1307
|
+
if (this._hasSelection()) {
|
|
1308
|
+
const [a, b] = this._selectionRange();
|
|
1309
|
+
c.fillStyle =
|
|
1310
|
+
this._resolveColor(props.selectionColor) ??
|
|
1311
|
+
(dark ? 'rgba(82,139,255,0.35)' : 'rgba(80,140,255,0.28)');
|
|
1312
|
+
for (let i = Math.max(a.line, first); i <= Math.min(b.line, last); i++) {
|
|
1313
|
+
const entry = this._lineEntry(i);
|
|
1314
|
+
const startX = i === a.line && entry.layout
|
|
1315
|
+
? this._caretX({ line: i, ch: a.ch })
|
|
1316
|
+
: 0;
|
|
1317
|
+
const endX = i === b.line
|
|
1318
|
+
? this._caretX({ line: i, ch: b.ch })
|
|
1319
|
+
: (entry.layout?.width ?? 0) + this._charW * 0.5; // the newline
|
|
1320
|
+
c.fillRect(textX + startX, lineY(i), Math.max(2, endX - startX), lineH);
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
// matching brackets
|
|
1324
|
+
if (props.matchBrackets !== false && this._focused) {
|
|
1325
|
+
const match = this._findBracketMatch();
|
|
1326
|
+
if (match) {
|
|
1327
|
+
c.fillStyle =
|
|
1328
|
+
this._resolveColor(props.matchingBracketColor) ??
|
|
1329
|
+
(dark ? 'rgba(90,190,130,0.35)' : 'rgba(80,180,120,0.30)');
|
|
1330
|
+
for (const pos of match) {
|
|
1331
|
+
if (pos.line < first || pos.line > last)
|
|
1332
|
+
continue;
|
|
1333
|
+
const x0 = this._caretX(pos);
|
|
1334
|
+
const x1 = this._caretX({ line: pos.line, ch: pos.ch + 1 });
|
|
1335
|
+
c.fillRect(textX + x0, lineY(pos.line), Math.max(1, x1 - x0), lineH);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
// text
|
|
1340
|
+
const empty = this._lines.length === 1 &&
|
|
1341
|
+
this._lines[0].length === 0 &&
|
|
1342
|
+
props.placeholder;
|
|
1343
|
+
if (empty) {
|
|
1344
|
+
const fonts = this._fonts();
|
|
1345
|
+
if (fonts) {
|
|
1346
|
+
const layout = fonts.layout([
|
|
1347
|
+
{
|
|
1348
|
+
text: String(props.placeholder),
|
|
1349
|
+
color: this._resolveColor(props.placeholderColor) ??
|
|
1350
|
+
this._themeColor('dim') ??
|
|
1351
|
+
'#9aa0a6',
|
|
1352
|
+
},
|
|
1353
|
+
], styles);
|
|
1354
|
+
layout.draw(ctx, textX, lineY(0) + (lineH - layout.height) / 2);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
else {
|
|
1358
|
+
for (let i = first; i <= last; i++) {
|
|
1359
|
+
const entry = this._lineEntry(i);
|
|
1360
|
+
entry.layout?.draw(ctx, textX, lineY(i) + (lineH - (entry.layout.height || lineH)) / 2);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
// diagnostics
|
|
1364
|
+
const diagnostics = props.diagnostics ?? [];
|
|
1365
|
+
for (const d of diagnostics) {
|
|
1366
|
+
const color = SEVERITY_COLORS[d.severity ?? 'error'] ?? '#e5484d';
|
|
1367
|
+
const from = clampPos(this._lines, d.from);
|
|
1368
|
+
const to = clampPos(this._lines, maxPos(d.from, d.to));
|
|
1369
|
+
for (let i = Math.max(from.line, first); i <= Math.min(to.line, last); i++) {
|
|
1370
|
+
const x0 = i === from.line ? this._caretX({ line: i, ch: from.ch }) : 0;
|
|
1371
|
+
const lineText = this._lines[i];
|
|
1372
|
+
const x1 = i === to.line
|
|
1373
|
+
? this._caretX({ line: i, ch: to.ch })
|
|
1374
|
+
: this._caretX({ line: i, ch: lineText.length });
|
|
1375
|
+
// hug the glyphs, not the line box: the text is centred in its
|
|
1376
|
+
// band, so a squiggle at the band's bottom floats visibly below
|
|
1377
|
+
// short text — measure where the drawn layout actually ends
|
|
1378
|
+
const entry = this._lineEntry(i);
|
|
1379
|
+
const glyphBottom = lineY(i) +
|
|
1380
|
+
(lineH - (entry.layout?.height || lineH)) / 2 +
|
|
1381
|
+
(entry.layout?.height || lineH);
|
|
1382
|
+
const y = Math.min(lineY(i) + lineH - 2, glyphBottom - 1);
|
|
1383
|
+
const width = Math.max(this._charW * 0.6, x1 - x0);
|
|
1384
|
+
if (typeof c.beginPath === 'function' &&
|
|
1385
|
+
typeof c.moveTo === 'function' &&
|
|
1386
|
+
typeof c.lineTo === 'function' &&
|
|
1387
|
+
typeof c.stroke === 'function') {
|
|
1388
|
+
c.beginPath();
|
|
1389
|
+
const step = 4;
|
|
1390
|
+
let up = true;
|
|
1391
|
+
for (let x = 0; x <= width; x += step) {
|
|
1392
|
+
const px = textX + x0 + x;
|
|
1393
|
+
const py = y + (up ? 0 : 2);
|
|
1394
|
+
if (x === 0)
|
|
1395
|
+
c.moveTo(px, py);
|
|
1396
|
+
else
|
|
1397
|
+
c.lineTo(px, py);
|
|
1398
|
+
up = !up;
|
|
1399
|
+
}
|
|
1400
|
+
c.strokeStyle = color;
|
|
1401
|
+
c.lineWidth = 1;
|
|
1402
|
+
c.stroke();
|
|
1403
|
+
}
|
|
1404
|
+
else {
|
|
1405
|
+
c.fillStyle = color;
|
|
1406
|
+
c.fillRect(textX + x0, y + 1, width, 1);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
// caret — 2px, in the (theme-resolved) text colour
|
|
1411
|
+
if (this._focused && this._caretOn) {
|
|
1412
|
+
const x = this._caretX(this._caret);
|
|
1413
|
+
c.fillStyle = this._resolveColor(props.caretColor) ?? baseColor;
|
|
1414
|
+
c.fillRect(Math.round(textX + x), lineY(this._caret.line) + 1, 2, lineH - 2);
|
|
1415
|
+
}
|
|
1416
|
+
if (textClip)
|
|
1417
|
+
c.restore();
|
|
1418
|
+
// gutter numbers: inside the content clip, outside the text clip
|
|
1419
|
+
if (gutterW > 0) {
|
|
1420
|
+
const fonts = this._fonts();
|
|
1421
|
+
if (fonts) {
|
|
1422
|
+
const dim = this._resolveColor(props.gutterColor) ??
|
|
1423
|
+
this._themeColor('dim') ??
|
|
1424
|
+
'#9aa0a6';
|
|
1425
|
+
for (let i = first; i <= last; i++) {
|
|
1426
|
+
const active = i === this._caret.line;
|
|
1427
|
+
const layout = fonts.layout([
|
|
1428
|
+
{
|
|
1429
|
+
text: String(i + 1),
|
|
1430
|
+
color: active ? baseColor : dim,
|
|
1431
|
+
},
|
|
1432
|
+
], styles);
|
|
1433
|
+
layout.draw(ctx, content.x + gutterW - GUTTER_PAD - layout.width, lineY(i) + (lineH - layout.height) / 2);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
// scroll thumbs — minimal, panel-style
|
|
1438
|
+
const maxY = this._maxScrollY();
|
|
1439
|
+
if (maxY > 0) {
|
|
1440
|
+
const trackH = content.height;
|
|
1441
|
+
const thumbH = Math.max(24, (trackH * trackH) / (this._lines.length * lineH));
|
|
1442
|
+
const yOff = (this._scrollY / maxY) * (trackH - thumbH);
|
|
1443
|
+
c.fillStyle = 'rgba(128,128,128,0.35)';
|
|
1444
|
+
c.fillRect(content.x + content.width - 4, content.y + yOff, 3, thumbH);
|
|
1445
|
+
}
|
|
1446
|
+
const maxX = this._maxScrollX();
|
|
1447
|
+
if (maxX > 0) {
|
|
1448
|
+
const textW = content.width - gutterW;
|
|
1449
|
+
const trackW = textW;
|
|
1450
|
+
const thumbW = Math.max(24, (trackW * trackW) / (this._widest + CARET_MARGIN));
|
|
1451
|
+
const xOff = (this._scrollX / maxX) * (trackW - thumbW);
|
|
1452
|
+
c.fillStyle = 'rgba(128,128,128,0.35)';
|
|
1453
|
+
c.fillRect(content.x + gutterW + xOff, content.y + content.height - 4, thumbW, 3);
|
|
1454
|
+
}
|
|
1455
|
+
if (outerClip)
|
|
1456
|
+
c.restore();
|
|
1457
|
+
}
|
|
1458
|
+
/** The bracket pair to highlight: the caret sits beside one of them. */
|
|
1459
|
+
_findBracketMatch() {
|
|
1460
|
+
const pos = this._caret;
|
|
1461
|
+
const line = this._lines[pos.line];
|
|
1462
|
+
const before = pos.ch > 0 ? line.charAt(pos.ch - 1) : '';
|
|
1463
|
+
const after = pos.ch < line.length ? line.charAt(pos.ch) : '';
|
|
1464
|
+
let at;
|
|
1465
|
+
let ch;
|
|
1466
|
+
if (after in BRACKETS) {
|
|
1467
|
+
at = { line: pos.line, ch: pos.ch };
|
|
1468
|
+
ch = after;
|
|
1469
|
+
}
|
|
1470
|
+
else if (before in BRACKETS) {
|
|
1471
|
+
at = { line: pos.line, ch: pos.ch - 1 };
|
|
1472
|
+
ch = before;
|
|
1473
|
+
}
|
|
1474
|
+
else {
|
|
1475
|
+
return null;
|
|
1476
|
+
}
|
|
1477
|
+
if (this._isOpaqueAt(at))
|
|
1478
|
+
return null;
|
|
1479
|
+
const { match, dir } = BRACKETS[ch];
|
|
1480
|
+
let depth = 0;
|
|
1481
|
+
let scanned = 0;
|
|
1482
|
+
let l = at.line;
|
|
1483
|
+
let i = at.ch;
|
|
1484
|
+
while (l >= 0 && l < this._lines.length && scanned < MATCH_SCAN_LIMIT) {
|
|
1485
|
+
const text = this._lines[l];
|
|
1486
|
+
while (dir > 0 ? i < text.length : i >= 0) {
|
|
1487
|
+
const cur = text.charAt(i);
|
|
1488
|
+
if ((cur === ch || cur === match) &&
|
|
1489
|
+
!this._isOpaqueAt({ line: l, ch: i })) {
|
|
1490
|
+
if (cur === ch)
|
|
1491
|
+
depth++;
|
|
1492
|
+
else {
|
|
1493
|
+
depth--;
|
|
1494
|
+
if (depth === 0)
|
|
1495
|
+
return [at, { line: l, ch: i }];
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
i += dir;
|
|
1499
|
+
scanned++;
|
|
1500
|
+
}
|
|
1501
|
+
l += dir;
|
|
1502
|
+
if (l >= 0 && l < this._lines.length) {
|
|
1503
|
+
i = dir > 0 ? 0 : this._lines[l].length - 1;
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
return null;
|
|
1507
|
+
}
|
|
1508
|
+
_isOpaqueAt(pos) {
|
|
1509
|
+
const tokens = this._tokenizer()?.lineTokens(pos.line);
|
|
1510
|
+
if (!tokens)
|
|
1511
|
+
return false;
|
|
1512
|
+
for (const t of tokens) {
|
|
1513
|
+
if (t.from <= pos.ch && pos.ch < t.to)
|
|
1514
|
+
return OPAQUE_TOKENS.has(t.type);
|
|
1515
|
+
if (t.from > pos.ch)
|
|
1516
|
+
break;
|
|
1517
|
+
}
|
|
1518
|
+
return false;
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
//# sourceMappingURL=node.js.map
|