ompchamber 2.0.2 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -11
- package/bunfig.toml +11 -0
- package/dist/client/index.html +16 -5
- package/dist/client/index.js +1393 -0
- package/dist/client/static/js/index-8t70jhyj.js +5149 -0
- package/dist/client/static/js/index-n4wkqj7t.css +1 -0
- package/index.html +54 -0
- package/package.json +9 -10
- package/public/apple-touch-icon.png +0 -0
- package/public/icon-192x192.png +0 -0
- package/public/icon.svg +60 -0
- package/public/manifest.webmanifest +38 -0
- package/src/cli/lib/commands/serve.js +9 -5
- package/src/cli/lib/runtime.js +18 -7
- package/src/client/components/common/code-editor/handler.test.ts +274 -0
- package/src/client/components/common/code-editor/handler.ts +175 -0
- package/src/client/components/common/code-editor/index.tsx +185 -220
- package/src/client/components/common/code-surface/index.tsx +109 -12
- package/src/client/components/common/lazy-panels.tsx +5 -1
- package/src/client/components/common/provider-icon/glyphs.ts +118 -0
- package/src/client/components/common/provider-icon/index.tsx +85 -0
- package/src/client/components/common/viewport.ts +5 -3
- package/src/client/components/layout/RightActivityBar.tsx +2 -1
- package/src/client/components/layout/desktop-layout/TopNavbar.tsx +21 -4
- package/src/client/components/layout/desktop-layout/WorkspacePanels.tsx +4 -0
- package/src/client/components/layout/desktop-layout/index.tsx +26 -4
- package/src/client/components/mobile/LayoutWrapper.tsx +16 -0
- package/src/client/components/mobile/RightSidebar.tsx +11 -4
- package/src/client/components/mobile/SessionSidebar.tsx +10 -0
- package/src/client/components/mobile/mobile-right-sidebar/FullEditor.tsx +0 -1
- package/src/client/components/mobile/mobile-session-sidebar/Item.tsx +16 -1
- package/src/client/components/mobile/mobile-session-sidebar/List.tsx +3 -0
- package/src/client/components/settings/categories/ProviderSettings.tsx +45 -4
- package/src/client/components/settings/categories/UsageSettings.tsx +22 -7
- package/src/client/components/settings/categories/provider-settings/AddModelModal.tsx +322 -0
- package/src/client/components/settings/categories/provider-settings/AddProviderModal.tsx +152 -56
- package/src/client/components/settings/categories/provider-settings/AuthSection.tsx +52 -1
- package/src/client/components/settings/categories/provider-settings/DeleteProviderModal.tsx +133 -0
- package/src/client/components/settings/categories/provider-settings/DialectFields.tsx +136 -0
- package/src/client/components/settings/categories/provider-settings/EffortLadder.tsx +89 -0
- package/src/client/components/settings/categories/provider-settings/Header.tsx +2 -2
- package/src/client/components/settings/categories/provider-settings/LoginModal.tsx +2 -2
- package/src/client/components/settings/categories/provider-settings/ModelsList.tsx +22 -2
- package/src/client/components/settings/categories/provider-settings/PresetPicker.tsx +82 -0
- package/src/client/components/settings/categories/provider-settings/PriceFields.tsx +107 -0
- package/src/client/components/settings/categories/provider-settings/ReconnectModal.tsx +43 -22
- package/src/client/components/settings/categories/provider-settings/SidebarList.tsx +2 -2
- package/src/client/components/settings/categories/usage-settings/LimitsSection.tsx +115 -0
- package/src/client/components/settings/categories/usage-settings/ProviderCard.tsx +19 -26
- package/src/client/components/settings/categories/usage-settings/ProviderDetail.tsx +29 -22
- package/src/client/components/settings/categories/usage-settings/SidebarList.tsx +60 -48
- package/src/client/components/settings/categories/usage-settings/format.ts +36 -0
- package/src/client/components/settings/categories/usage-settings/providers.ts +52 -28
- package/src/client/components/workspace/browser-panel/AddressBar.tsx +1 -10
- package/src/client/components/workspace/browser-panel/Screencast.tsx +4 -5
- package/src/client/components/workspace/browser-panel/index.tsx +6 -6
- package/src/client/components/workspace/browser-toolbar/index.tsx +22 -12
- package/src/client/components/workspace/chat-timeline/AiMessageFooter.tsx +1 -1
- package/src/client/components/workspace/chat-timeline/GeneratingIndicator.tsx +1 -1
- package/src/client/components/workspace/chat-timeline/ToolCallCard.tsx +1 -1
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Todo.tsx +1 -1
- package/src/client/components/workspace/diff-panel/Toolbar.tsx +71 -38
- package/src/client/components/workspace/diff-panel/index.tsx +139 -53
- package/src/client/components/workspace/editor/CommandPalette.test.ts +242 -0
- package/src/client/components/workspace/editor/CommandPalette.tsx +138 -0
- package/src/client/components/workspace/editor/FindWidget.test.ts +261 -0
- package/src/client/components/workspace/editor/FindWidget.tsx +219 -0
- package/src/client/components/workspace/editor/Tabs.tsx +2 -2
- package/src/client/components/workspace/editor/Toolbar.tsx +129 -26
- package/src/client/components/workspace/editor/index.tsx +180 -62
- package/src/client/components/workspace/file-explorer/Modals.tsx +79 -2
- package/src/client/components/workspace/file-explorer/TreeItem.tsx +43 -113
- package/src/client/components/workspace/model-dropdown/Item.tsx +13 -23
- package/src/client/components/workspace/model-dropdown/Panel.tsx +1 -1
- package/src/client/components/workspace/model-dropdown/Section.tsx +13 -4
- package/src/client/components/workspace/model-dropdown/index.tsx +7 -3
- package/src/client/components/workspace/todo-panel/PhaseGroup.tsx +76 -0
- package/src/client/components/workspace/todo-panel/Row.tsx +69 -0
- package/src/client/components/workspace/todo-panel/index.tsx +161 -0
- package/src/client/components/workspace/usage-panel/ProviderSelect.tsx +10 -5
- package/src/client/components/workspace/usage-panel/index.tsx +36 -18
- package/src/client/data/samples/dialogue-session.ts +1 -1
- package/src/client/data/samples/sample-tools/other.ts +1 -1
- package/src/client/data/settings/provider.ts +141 -9
- package/src/client/hooks/editor/find-harness.test-util.ts +170 -0
- package/src/client/hooks/editor/scroll-container.test.ts +102 -0
- package/src/client/hooks/editor/scroll-container.ts +26 -0
- package/src/client/hooks/editor/use-code-window.ts +2 -20
- package/src/client/hooks/editor/use-editor-find-write.test.ts +170 -0
- package/src/client/hooks/editor/use-editor-find.test.ts +118 -0
- package/src/client/hooks/editor/use-editor-find.ts +321 -0
- package/src/client/hooks/editor/use-editor-history.test.ts +216 -0
- package/src/client/hooks/editor/use-editor-history.ts +134 -0
- package/src/client/hooks/editor/use-file-editor.ts +24 -27
- package/src/client/hooks/editor/use-occurrences.test.ts +236 -0
- package/src/client/hooks/editor/use-occurrences.ts +98 -0
- package/src/client/hooks/models/use-provider-names.ts +1 -1
- package/src/client/hooks/settings/providers/actions.ts +337 -0
- package/src/client/hooks/settings/providers/api.ts +69 -0
- package/src/client/hooks/settings/providers/index.ts +138 -0
- package/src/client/hooks/settings/providers/messages.ts +77 -0
- package/src/client/hooks/settings/useUsageReport.ts +8 -3
- package/src/client/hooks/ui/theme.ts +3 -3
- package/src/client/hooks/workspace/file-tabs.ts +62 -11
- package/src/client/hooks/workspace/file-tree-actions.test.ts +348 -0
- package/src/client/hooks/workspace/file-tree-actions.ts +250 -0
- package/src/client/hooks/workspace/session-todos.ts +102 -0
- package/src/client/stubs/elkjs.ts +6 -1
- package/src/client/tailwind.css +30 -0
- package/src/server/index.ts +20 -2
- package/src/server/lib/assets/font-css.server.ts +86 -0
- package/src/server/lib/assets/fonts.server.ts +117 -0
- package/src/server/lib/bundler/css.ts +116 -0
- package/src/server/lib/lifecycle/shell-route.ts +17 -0
- package/src/server/lib/models/provider-listing.server.ts +257 -0
- package/src/server/lib/models/provider-registry.server.test.ts +122 -0
- package/src/server/lib/models/provider-registry.server.ts +76 -25
- package/src/server/lib/omp/config/models-config.ts +11 -0
- package/src/server/lib/omp/config/models-validation.test.ts +137 -0
- package/src/server/lib/omp/config/models-validation.ts +111 -2
- package/src/server/lib/omp/config/provider-seeds.test.ts +167 -0
- package/src/server/lib/omp/config/provider-seeds.ts +176 -0
- package/src/server/lib/omp/config/providers.ts +94 -100
- package/src/server/lib/omp/core/auth-credentials.ts +45 -0
- package/src/server/lib/omp/session/todos.ts +100 -0
- package/src/server/lib/omp/session/{usage.ts → usage/aggregate.ts} +1 -118
- package/src/server/lib/omp/session/usage/shaping.ts +130 -0
- package/src/server/lib/usage/deepseek.ts +4 -5
- package/src/server/lib/usage/kenari.ts +7 -6
- package/src/server/lib/usage/omp-usage.server.test.ts +126 -0
- package/src/server/lib/usage/omp-usage.server.ts +194 -0
- package/src/server/lib/usage/provider-key.ts +81 -3
- package/src/server/lib/usage/providers.server.test.ts +47 -0
- package/src/server/lib/usage/providers.server.ts +208 -0
- package/src/server/lib/usage/vendor-balance.server.test.ts +104 -0
- package/src/server/lib/usage/vendor-balance.server.ts +284 -0
- package/src/server/plugins/shell.server.ts +56 -0
- package/src/server/plugins/ssr.ts +24 -36
- package/src/server/plugins/static.test.ts +26 -29
- package/src/server/plugins/static.ts +40 -19
- package/src/server/routes/fs/action-create.test.ts +90 -0
- package/src/server/routes/fs/action.ts +23 -0
- package/src/server/routes/omp/index.ts +2 -0
- package/src/server/routes/omp/session-todos.ts +80 -0
- package/src/server/routes/settings/index.ts +2 -0
- package/src/server/routes/settings/provider-model.ts +107 -0
- package/src/server/routes/settings/provider-models.ts +65 -202
- package/src/server/routes/settings/providers.ts +22 -7
- package/src/server/routes/settings/usage.ts +125 -45
- package/src/server/routes/telemetry/tokens.ts +2 -1
- package/src/shared/lib/chat/{todo-parser.ts → todo/parser.ts} +17 -0
- package/src/shared/lib/chat/todo/progress.ts +97 -0
- package/src/shared/lib/chat/todo/snapshot.test.ts +156 -0
- package/src/shared/lib/chat/todo/snapshot.ts +215 -0
- package/src/shared/lib/code/editor/commands.test.ts +155 -0
- package/src/shared/lib/code/editor/commands.ts +260 -0
- package/src/shared/lib/code/editor/edits.test.ts +100 -0
- package/src/shared/lib/code/editor/edits.ts +122 -0
- package/src/shared/lib/code/editor/find-status.test.ts +50 -0
- package/src/shared/lib/code/editor/find-status.ts +36 -0
- package/src/shared/lib/code/editor/find.test.ts +203 -0
- package/src/shared/lib/code/editor/find.ts +224 -0
- package/src/{client/components/common/code-editor → shared/lib/code/editor}/history.test.ts +1 -1
- package/src/shared/lib/code/editor/keymap.test.ts +109 -0
- package/src/shared/lib/code/editor/keymap.ts +151 -0
- package/src/shared/lib/code/editor/layers.test.ts +77 -0
- package/src/shared/lib/code/editor/layers.ts +67 -0
- package/src/shared/lib/code/editor/line-edits.test.ts +131 -0
- package/src/shared/lib/code/editor/line-edits.ts +159 -0
- package/src/shared/lib/code/editor/lines.test.ts +81 -0
- package/src/shared/lib/code/editor/lines.ts +83 -0
- package/src/shared/lib/code/editor/marks.test.ts +86 -0
- package/src/shared/lib/code/editor/marks.ts +106 -0
- package/src/shared/lib/code/editor/occurrences.test.ts +117 -0
- package/src/shared/lib/code/editor/occurrences.ts +111 -0
- package/src/shared/lib/code/editor/palette.test.ts +91 -0
- package/src/shared/lib/code/editor/palette.ts +92 -0
- package/src/shared/lib/code/editor/reset-css.test.ts +35 -0
- package/src/shared/lib/code/editor/reset-css.ts +32 -0
- package/src/shared/lib/code/editor/text-commands.test.ts +178 -0
- package/src/shared/lib/code/editor/text-commands.ts +154 -0
- package/src/shared/lib/code/syntax-highlight.test.ts +75 -0
- package/src/shared/lib/code/syntax-highlight.ts +29 -17
- package/src/shared/lib/code/windowed-highlight.ts +52 -1
- package/src/shared/lib/fs/diff-parser.test.ts +116 -0
- package/src/shared/lib/fs/diff-parser.ts +86 -33
- package/src/shared/lib/models/{provider-connection.ts → provider/connection.ts} +37 -0
- package/src/shared/lib/models/provider/dialect.ts +179 -0
- package/src/shared/lib/models/provider/glyph.test.ts +116 -0
- package/src/shared/lib/models/provider/glyph.ts +200 -0
- package/src/shared/lib/models/{provider-models.ts → provider/models.ts} +91 -6
- package/src/shared/lib/models/provider/presets.test.ts +172 -0
- package/src/shared/lib/models/provider/presets.ts +83 -0
- package/src/shared/lib/ui/key-binding.test.ts +115 -0
- package/src/shared/lib/ui/key-binding.ts +143 -0
- package/src/shared/lib/util/platform.test.ts +87 -0
- package/src/shared/lib/util/platform.ts +30 -0
- package/src/shared/lib/workspace/file-tab-id.ts +29 -0
- package/src/shared/lib/workspace/keymap.test.ts +82 -0
- package/src/shared/lib/workspace/keymap.ts +30 -0
- package/src/shared/lib/workspace/refresh-cadence.ts +9 -0
- package/src/shared/lib/workspace/right-panels.ts +8 -0
- package/src/shared/lib/workspace/session-state/store.ts +0 -1
- package/src/shared/types/index.ts +1 -0
- package/src/shared/types/settings/provider.ts +81 -0
- package/src/shared/types/settings/usage.ts +88 -14
- package/src/shared/types/todo.ts +108 -0
- package/tsconfig.json +3 -1
- package/dist/client/static/css/index.5f630421b7.css +0 -2
- package/dist/client/static/font/KaTeX_AMS-Regular.a79f1c3119.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Caligraphic-Bold.ec17d13264.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Caligraphic-Regular.55fac25845.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Fraktur-Bold.d42a5579b0.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Fraktur-Regular.d3c882a649.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Main-Bold.c3fb5ac22f.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Main-BoldItalic.6f2bb1dff2.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Main-Italic.8916142bec.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Main-Regular.0462f03bdf.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Math-BoldItalic.572d331f69.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Math-Italic.f28c23acad.woff2 +0 -0
- package/dist/client/static/font/KaTeX_SansSerif-Bold.8c5b5494b6.woff2 +0 -0
- package/dist/client/static/font/KaTeX_SansSerif-Italic.3b1e59b3ba.woff2 +0 -0
- package/dist/client/static/font/KaTeX_SansSerif-Regular.ba21ed5f84.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Script-Regular.03e9641d6f.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Size1-Regular.eae34984b3.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Size2-Regular.5916a24fa3.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Size4-Regular.10d95fd3a2.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Typewriter-Regular.a8709e3622.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-400-normal.2655b10c55.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-500-normal.92a991f204.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-600-normal.43788eb7f1.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-700-normal.2667215d33.woff2 +0 -0
- package/dist/client/static/js/77.0d81c74cf0.js +0 -139
- package/dist/client/static/js/77.0d81c74cf0.js.LICENSE.txt +0 -8
- package/dist/client/static/js/async/0.8e59f2eb5b.js +0 -1
- package/dist/client/static/js/async/0k.5852e6b4fa.js +0 -1
- package/dist/client/static/js/async/0w.9ed36d5860.js +0 -1
- package/dist/client/static/js/async/1.f387c12b63.js +0 -1
- package/dist/client/static/js/async/12.d091f91c99.js +0 -1
- package/dist/client/static/js/async/13.8f810a8a6c.js +0 -1
- package/dist/client/static/js/async/19.6fe909d55a.js +0 -160
- package/dist/client/static/js/async/1b.23f1141a38.js +0 -1
- package/dist/client/static/js/async/1k.973e8e21fc.js +0 -2
- package/dist/client/static/js/async/1k.973e8e21fc.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/1p.0d5cddda96.js +0 -1
- package/dist/client/static/js/async/2.b589f1733b.js +0 -1
- package/dist/client/static/js/async/2v.96165101de.js +0 -1
- package/dist/client/static/js/async/3.0cacc08423.js +0 -1
- package/dist/client/static/js/async/37.b28b2f61cd.js +0 -1
- package/dist/client/static/js/async/3d.5085fb913f.js +0 -1
- package/dist/client/static/js/async/3j.357c80ff38.js +0 -2
- package/dist/client/static/js/async/3j.357c80ff38.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/3p.bcf32a0983.js +0 -1
- package/dist/client/static/js/async/3r.3a69e98ee0.js +0 -64
- package/dist/client/static/js/async/3u.1228460e2a.js +0 -1
- package/dist/client/static/js/async/3y.3530b824ad.js +0 -198
- package/dist/client/static/js/async/3y.3530b824ad.js.LICENSE.txt +0 -5
- package/dist/client/static/js/async/4.9442eeb472.js +0 -1
- package/dist/client/static/js/async/4e.678285ca55.js +0 -1
- package/dist/client/static/js/async/4k.33a0c7eeb4.js +0 -1
- package/dist/client/static/js/async/4m.928a1dde85.js +0 -31
- package/dist/client/static/js/async/4m.928a1dde85.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/4q.aad20dab25.js +0 -1
- package/dist/client/static/js/async/5.ebaf8b89cc.js +0 -36
- package/dist/client/static/js/async/51.95bcae98af.js +0 -2
- package/dist/client/static/js/async/51.95bcae98af.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/5b.d88304bc13.js +0 -1
- package/dist/client/static/js/async/5j.d6407c4248.js +0 -287
- package/dist/client/static/js/async/5o.88316d4460.js +0 -62
- package/dist/client/static/js/async/6.f034c9b528.js +0 -1
- package/dist/client/static/js/async/66.5dfa29f957.js +0 -93
- package/dist/client/static/js/async/6o.5b1a06a382.js +0 -1
- package/dist/client/static/js/async/7.e4a9288dd0.js +0 -1
- package/dist/client/static/js/async/73.308dff4da8.js +0 -1
- package/dist/client/static/js/async/7e.a1cc9bfd88.js +0 -1
- package/dist/client/static/js/async/7g.a7814350c3.js +0 -1
- package/dist/client/static/js/async/7l.fadbbc0583.js +0 -1
- package/dist/client/static/js/async/8.5067439d8c.js +0 -1
- package/dist/client/static/js/async/8j.ea110663fb.js +0 -1
- package/dist/client/static/js/async/a.bce4542392.js +0 -1
- package/dist/client/static/js/async/aa.65d3852576.js +0 -1
- package/dist/client/static/js/async/ar.9303e5f2e8.js +0 -2
- package/dist/client/static/js/async/ar.9303e5f2e8.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/az.d656062b0f.js +0 -1
- package/dist/client/static/js/async/b.b4f6f06515.js +0 -1
- package/dist/client/static/js/async/bi.09688ba75c.js +0 -1
- package/dist/client/static/js/async/bq.586bc738d1.js +0 -1
- package/dist/client/static/js/async/c.797d933882.js +0 -1
- package/dist/client/static/js/async/c0.0af1e640d3.js +0 -24
- package/dist/client/static/js/async/ca.8a52cfd447.js +0 -1
- package/dist/client/static/js/async/cf.d34fff5d67.js +0 -1
- package/dist/client/static/js/async/ct.b74928f7a3.js +0 -1
- package/dist/client/static/js/async/cx.3cb0242a3a.js +0 -226
- package/dist/client/static/js/async/d.a5fc8fe1b3.js +0 -1
- package/dist/client/static/js/async/da.f62c70f6c8.js +0 -1
- package/dist/client/static/js/async/dp.1725d6432c.js +0 -1
- package/dist/client/static/js/async/e.93214d622b.js +0 -79
- package/dist/client/static/js/async/f.ab50467719.js +0 -19
- package/dist/client/static/js/async/f.ab50467719.js.LICENSE.txt +0 -12
- package/dist/client/static/js/async/f7.46971e9d6d.js +0 -2
- package/dist/client/static/js/async/f7.46971e9d6d.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/f8.bbf0dbae70.js +0 -1
- package/dist/client/static/js/async/g.cb81980498.js +0 -2
- package/dist/client/static/js/async/g.cb81980498.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/h.4e790471e4.js +0 -27
- package/dist/client/static/js/async/h1.393f066d09.js +0 -1
- package/dist/client/static/js/async/h3.216e9798ab.js +0 -148
- package/dist/client/static/js/async/hd.0da931b622.js +0 -1
- package/dist/client/static/js/async/hk.ba7fb04d41.js +0 -1
- package/dist/client/static/js/async/hn.b645041e00.js +0 -1
- package/dist/client/static/js/async/ho.475a5e1ae4.js +0 -1
- package/dist/client/static/js/async/hp.e9fe2e395a.js +0 -2
- package/dist/client/static/js/async/hp.e9fe2e395a.js.LICENSE.txt +0 -17
- package/dist/client/static/js/async/hs.e7c59d8090.js +0 -41
- package/dist/client/static/js/async/i.7c148e8440.js +0 -1
- package/dist/client/static/js/async/id.573df20f29.js +0 -1
- package/dist/client/static/js/async/j.ab925882d8.js +0 -1
- package/dist/client/static/js/async/jd.63886a1f09.js +0 -1
- package/dist/client/static/js/async/jg.d86ebd4013.js +0 -86
- package/dist/client/static/js/async/jl.874d352278.js +0 -1
- package/dist/client/static/js/async/jll.11313b455a.js +0 -1
- package/dist/client/static/js/async/js.67182759ef.js +0 -1
- package/dist/client/static/js/async/jsc.23b70d6f40.js +0 -1
- package/dist/client/static/js/async/jse.334588ff5d.js +0 -1
- package/dist/client/static/js/async/jz.72eb929106.js +0 -77
- package/dist/client/static/js/async/k.3210286401.js +0 -217
- package/dist/client/static/js/async/ka.c1990ecaa3.js +0 -115
- package/dist/client/static/js/async/katex-vendor.de6a87c308.js +0 -1
- package/dist/client/static/js/async/ku.29c8a59d0c.js +0 -1
- package/dist/client/static/js/async/ky.8dab1dade0.js +0 -1
- package/dist/client/static/js/async/l.12dd34e1be.js +0 -1
- package/dist/client/static/js/async/l9.949710adaf.js +0 -83
- package/dist/client/static/js/async/l9.949710adaf.js.LICENSE.txt +0 -13
- package/dist/client/static/js/async/ls.9d9a01026c.js +0 -104
- package/dist/client/static/js/async/m.eb2e6752e2.js +0 -28
- package/dist/client/static/js/async/m.eb2e6752e2.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/m3.d2b05f9604.js +0 -2
- package/dist/client/static/js/async/m3.d2b05f9604.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/ma.2c261b04dd.js +0 -2
- package/dist/client/static/js/async/ma.2c261b04dd.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/me.8ace414f80.js +0 -23
- package/dist/client/static/js/async/ms.98960af4eb.js +0 -67
- package/dist/client/static/js/async/ms.98960af4eb.js.LICENSE.txt +0 -12
- package/dist/client/static/js/async/mt.0352e976d8.js +0 -1
- package/dist/client/static/js/async/n.a9f1c51701.js +0 -1
- package/dist/client/static/js/async/na.a36cd532d6.js +0 -1
- package/dist/client/static/js/async/nk.fc7e58325e.js +0 -1
- package/dist/client/static/js/async/o.36353dfb88.js +0 -1
- package/dist/client/static/js/async/oc.b73042e8bd.js +0 -8
- package/dist/client/static/js/async/og.8eb577893a.js +0 -193
- package/dist/client/static/js/async/p.48ecfcd14e.js +0 -1
- package/dist/client/static/js/async/px.fc6877924b.js +0 -1
- package/dist/client/static/js/async/q.22588845a9.js +0 -21
- package/dist/client/static/js/async/q.22588845a9.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/q5.97dc1481b6.js +0 -286
- package/dist/client/static/js/async/qs.2a9ddb3c72.js +0 -1
- package/dist/client/static/js/async/r.51010ad87c.js +0 -1
- package/dist/client/static/js/async/rd.cb6bf495bb.js +0 -2
- package/dist/client/static/js/async/rd.cb6bf495bb.js.LICENSE.txt +0 -4
- package/dist/client/static/js/async/s.0f395cdda5.js +0 -94
- package/dist/client/static/js/async/t.4dca0e2ba8.js +0 -1
- package/dist/client/static/js/async/t9.d44f57d518.js +0 -1
- package/dist/client/static/js/async/ta.973534bbec.js +0 -1
- package/dist/client/static/js/async/tr.e878658140.js +0 -309
- package/dist/client/static/js/async/u.0871ac5b22.js +0 -1
- package/dist/client/static/js/async/ud.3065bfee31.js +0 -2
- package/dist/client/static/js/async/ud.3065bfee31.js.LICENSE.txt +0 -4
- package/dist/client/static/js/async/up.ea990da0ff.js +0 -32
- package/dist/client/static/js/async/uw.8890abbabc.js +0 -1
- package/dist/client/static/js/async/v.7d28d3c241.js +0 -34
- package/dist/client/static/js/async/vp.9b13bf8610.js +0 -1
- package/dist/client/static/js/async/vt.42416b707d.js +0 -1
- package/dist/client/static/js/async/w.08ac210f60.js +0 -1
- package/dist/client/static/js/async/wt.ad92393a90.js +0 -1
- package/dist/client/static/js/async/x.563f72ca6b.js +0 -1
- package/dist/client/static/js/async/x0.475516418f.js +0 -1
- package/dist/client/static/js/async/x1.da8687159b.js +0 -90
- package/dist/client/static/js/async/xe.da8dcc2015.js +0 -2
- package/dist/client/static/js/async/xe.da8dcc2015.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/xl.a3c562a58c.js +0 -1
- package/dist/client/static/js/async/xn.6510c0abe2.js +0 -2
- package/dist/client/static/js/async/xn.6510c0abe2.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/xn1.83a7784fe1.js +0 -10
- package/dist/client/static/js/async/xn1.83a7784fe1.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/xnl.feb0021e50.js +0 -2
- package/dist/client/static/js/async/xnl.feb0021e50.js.LICENSE.txt +0 -12
- package/dist/client/static/js/async/y.1a9ed7a5d8.js +0 -1
- package/dist/client/static/js/async/y6.43c57defe4.js +0 -1
- package/dist/client/static/js/async/yb.8c1342401d.js +0 -1
- package/dist/client/static/js/async/ybq.4491f2f2dc.js +0 -1
- package/dist/client/static/js/async/yg.35bebd6d0c.js +0 -149
- package/dist/client/static/js/async/yq.917ddd7ba2.js +0 -1
- package/dist/client/static/js/async/ys.00687cf82d.js +0 -38
- package/dist/client/static/js/async/yv.d99d829136.js +0 -1
- package/dist/client/static/js/async/z.56f61a4374.js +0 -1
- package/dist/client/static/js/async/zi.5fdb07b86a.js +0 -24
- package/dist/client/static/js/async/zj.654f37e8ff.js +0 -1
- package/dist/client/static/js/async/zn.966d6f1cc6.js +0 -1
- package/dist/client/static/js/async/zo.26213b40c0.js +0 -1
- package/dist/client/static/js/async/zx.e9f4105e00.js +0 -1
- package/dist/client/static/js/index.e6a257402a.js +0 -38
- package/dist/client/static/js/index.e6a257402a.js.LICENSE.txt +0 -4
- package/dist/client/static/js/markdown-vendor.71e43278b3.js +0 -144
- package/dist/client/static/js/shiki-vendor.cb8452ddcd.js +0 -1
- package/src/client/components/settings/categories/provider-settings/Icons.tsx +0 -35
- package/src/client/hooks/settings/providers.ts +0 -326
- package/src/server/plugins/dev-assets.ts +0 -49
- package/src/shared/lib/code/editor-utils.ts +0 -16
- package/src/shared/lib/models/provider-presets.ts +0 -29
- /package/dist/client/{apple-touch-icon.png → static/apple-touch-icon-dskx5sr3.png} +0 -0
- /package/dist/client/{icon-192x192.png → static/icon-192x192-6gn5cr2f.png} +0 -0
- /package/dist/client/{icon.svg → static/icon-d07z9dtz.svg} +0 -0
- /package/dist/client/{manifest.webmanifest → static/manifest-bnyy75kt.webmanifest} +0 -0
- /package/{dist/client → public}/icon-512x512.png +0 -0
- /package/{dist/client → public}/icon-maskable-512x512.png +0 -0
- /package/{dist/client → public}/sw.js +0 -0
- /package/src/{client/components/common/code-editor → shared/lib/code/editor}/history.ts +0 -0
- /package/src/shared/lib/models/{provider-cleanup.ts → provider/cleanup.ts} +0 -0
- /package/src/shared/lib/models/{provider-label.ts → provider/label.ts} +0 -0
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useImperativeHandle, useLayoutEffect, useRef, useState } from 'preact/hooks';
|
|
2
2
|
import type { CSSProperties, Ref, TargetedKeyboardEvent } from 'preact';
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} from '@/client/components/common/code-editor/history';
|
|
4
|
+
import { continuesTyping, mergeTyping, pushRecord } from '@/shared/lib/code/editor/history';
|
|
5
|
+
import { handleEditorKeydown, runEditorCommand, type EditorKeydownDeps } from '@/client/components/common/code-editor/handler';
|
|
6
|
+
import { layerStyle, layerStyles } from '@/shared/lib/code/editor/layers';
|
|
7
|
+
import { CODE_EDITOR_RESET_CSS } from '@/shared/lib/code/editor/reset-css';
|
|
8
|
+
import type { EditorCommand } from '@/shared/lib/code/editor/keymap';
|
|
9
|
+
import { useOccurrences } from '@/client/hooks/editor/use-occurrences';
|
|
10
|
+
import { useEditorHistory } from '@/client/hooks/editor/use-editor-history';
|
|
11
|
+
import type { TextRange } from '@/shared/lib/code/editor/commands';
|
|
13
12
|
import type { CodeWindow, LineWindow } from '@/shared/lib/code/lazy-window';
|
|
14
13
|
|
|
15
14
|
/**
|
|
@@ -27,6 +26,30 @@ import type { CodeWindow, LineWindow } from '@/shared/lib/code/lazy-window';
|
|
|
27
26
|
* rest as padding — the alignment that keeps the caret over its own row.
|
|
28
27
|
*/
|
|
29
28
|
|
|
29
|
+
export interface CodeEditorHandle {
|
|
30
|
+
/** The `<textarea>` that owns the document — measurements and focus anchor on it. */
|
|
31
|
+
textarea(): HTMLTextAreaElement | null;
|
|
32
|
+
/** Current selection offsets, or null before the field is mounted. */
|
|
33
|
+
getSelection(): { start: number; end: number } | null;
|
|
34
|
+
/** Select a range; `focus` also moves the keyboard focus into the editor. */
|
|
35
|
+
select(start: number, end: number, focus?: boolean): void;
|
|
36
|
+
/**
|
|
37
|
+
* Replace the whole buffer as ONE undoable edit and leave the caret at
|
|
38
|
+
* `caretStart`/`caretEnd`, WITHOUT moving focus. Used by the find widget's
|
|
39
|
+
* replace, which must not look like a burst of typing to the history (⌘Z has
|
|
40
|
+
* to undo it in one step) and must not steal focus from its own field.
|
|
41
|
+
*/
|
|
42
|
+
applyDocument(value: string, caretStart: number, caretEnd: number): void;
|
|
43
|
+
/** Every range a ⌘D workflow has selected, primary included — the surface paints them. */
|
|
44
|
+
occurrences(): readonly TextRange[];
|
|
45
|
+
/**
|
|
46
|
+
* Run an editor command by name — the palette's path into the editor. It goes
|
|
47
|
+
* through the same dispatcher the keyboard uses, so a command cannot work
|
|
48
|
+
* from a chord and do nothing from the palette.
|
|
49
|
+
*/
|
|
50
|
+
run(command: EditorCommand): void;
|
|
51
|
+
}
|
|
52
|
+
|
|
30
53
|
export interface CodeEditorProps {
|
|
31
54
|
value: string;
|
|
32
55
|
onValueChange: (value: string) => void;
|
|
@@ -47,29 +70,28 @@ export interface CodeEditorProps {
|
|
|
47
70
|
autoFocus?: boolean;
|
|
48
71
|
/** Renders only this window of lines, with the rest reserved as space. Omit to mirror the whole value. */
|
|
49
72
|
virtual?: CodeWindow | null;
|
|
73
|
+
/** Imperative surface for the find widget (selection, reveal, one-shot replace). */
|
|
74
|
+
handleRef?: Ref<CodeEditorHandle>;
|
|
75
|
+
/**
|
|
76
|
+
* Called when the extra selection ranges change, so the surface can paint
|
|
77
|
+
* them. The textarea holds only the primary selection; everything ⌘D adds
|
|
78
|
+
* lives here.
|
|
79
|
+
*/
|
|
80
|
+
onOccurrencesChange?: (ranges: readonly TextRange[]) => void;
|
|
81
|
+
/** Commands this editor does not own (find bar chords, word wrap, save). */
|
|
82
|
+
onCommand?: (command: EditorCommand) => void;
|
|
83
|
+
/**
|
|
84
|
+
* The extra ⌘D ranges, held by the CALLER. The editor keeps its own copy in a
|
|
85
|
+
* ref for synchronous reads; this prop is what makes the surface a controlled
|
|
86
|
+
* view of them, so the paint and the editing model cannot disagree.
|
|
87
|
+
*/
|
|
88
|
+
occurrences?: readonly TextRange[];
|
|
89
|
+
/** Shiki language id, for the comment syntax. */
|
|
90
|
+
language?: string;
|
|
50
91
|
}
|
|
51
92
|
|
|
52
93
|
const BASE_TEXTAREA_CLASS = 'code-editor-native-textarea';
|
|
53
94
|
|
|
54
|
-
const RESET_CSS = `
|
|
55
|
-
.code-editor-native-textarea {
|
|
56
|
-
-webkit-text-fill-color: transparent;
|
|
57
|
-
}
|
|
58
|
-
.code-editor-native-textarea::placeholder {
|
|
59
|
-
-webkit-text-fill-color: var(--theme-ink);
|
|
60
|
-
opacity: 0.45;
|
|
61
|
-
}
|
|
62
|
-
`;
|
|
63
|
-
|
|
64
|
-
/** Shared <style> injected once per page. */
|
|
65
|
-
function EditorResetStyle() {
|
|
66
|
-
return <style dangerouslySetInnerHTML={{ __html: RESET_CSS }} />;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function getLines(text: string, position: number): string[] {
|
|
70
|
-
return text.substring(0, position).split('\n');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
95
|
export function CodeEditor({
|
|
74
96
|
value,
|
|
75
97
|
onValueChange,
|
|
@@ -84,175 +106,147 @@ export function CodeEditor({
|
|
|
84
106
|
readOnly,
|
|
85
107
|
autoFocus,
|
|
86
108
|
virtual,
|
|
109
|
+
handleRef,
|
|
110
|
+
occurrences,
|
|
111
|
+
onOccurrencesChange,
|
|
112
|
+
onCommand,
|
|
113
|
+
language = 'javascript',
|
|
87
114
|
}: CodeEditorProps) {
|
|
88
115
|
const inputRef = useRef<HTMLTextAreaElement | null>(null);
|
|
89
|
-
const historyRef = useRef(createHistory());
|
|
90
116
|
const [captureTab, setCaptureTab] = useState(true);
|
|
117
|
+
const multi = useOccurrences({
|
|
118
|
+
controlled: occurrences,
|
|
119
|
+
onChange: (ranges) => onOccurrencesChange?.(ranges),
|
|
120
|
+
});
|
|
91
121
|
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
const input = inputRef.current;
|
|
96
|
-
if (!input) return;
|
|
97
|
-
pushRecord(historyRef.current, {
|
|
98
|
-
value: input.value,
|
|
99
|
-
selectionStart: input.selectionStart,
|
|
100
|
-
selectionEnd: input.selectionEnd,
|
|
101
|
-
timestamp: Date.now(),
|
|
102
|
-
});
|
|
103
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
104
|
-
}, [value]);
|
|
122
|
+
// A selection asked for before the field exists (the find widget opens and
|
|
123
|
+
// the panel re-renders in the same tick) is applied as soon as it mounts.
|
|
124
|
+
const pendingSelectionRef = useRef<{ start: number; end: number; focus: boolean } | null>(null);
|
|
105
125
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
input.selectionStart = record.selectionStart;
|
|
123
|
-
input.selectionEnd = record.selectionEnd;
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const handleKeyDown = (e: TargetedKeyboardEvent<HTMLTextAreaElement>) => {
|
|
128
|
-
if (e.key === 'Escape') {
|
|
129
|
-
e.currentTarget.blur();
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
const { value: text, selectionStart, selectionEnd } = e.currentTarget;
|
|
133
|
-
const tabCharacter = ' ';
|
|
134
|
-
const isMac = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
|
|
135
|
-
const isWindows = /Win/i.test(navigator.platform);
|
|
126
|
+
/**
|
|
127
|
+
* The buffer and selection as of the last event that could precede an edit.
|
|
128
|
+
*
|
|
129
|
+
* `handleChange` has to know which range the BROWSER replaced, and the `input`
|
|
130
|
+
* event reports the state AFTER the edit — useless for locating it. Keydown,
|
|
131
|
+
* select and the component's own writes all land before the edit, so
|
|
132
|
+
* recording there is what makes the diff in `handleChange` unambiguous.
|
|
133
|
+
*
|
|
134
|
+
* The buffer that goes with it comes from the history hook (`undoStack.buffer`),
|
|
135
|
+
* NOT from the `value` prop: the prop lags a keystroke — it only moves once
|
|
136
|
+
* the panel's state has re-rendered, and the next keystroke can arrive first
|
|
137
|
+
* (measured: typing at several carets with a 30 ms gap). A stale `before`
|
|
138
|
+
* turns the diff into a replacement of the whole word, which is what made a
|
|
139
|
+
* five-letter burst take five undos.
|
|
140
|
+
*/
|
|
141
|
+
const lastSelectionRef = useRef<TextRange>({ start: 0, end: 0 });
|
|
136
142
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const startLine = linesBeforeCaret.length - 1;
|
|
143
|
-
const endLine = getLines(text, selectionEnd).length - 1;
|
|
144
|
-
const nextValue = text
|
|
145
|
-
.split('\n')
|
|
146
|
-
.map((line, i) => (i >= startLine && i <= endLine && line.startsWith(tabCharacter) ? line.substring(tabCharacter.length) : line))
|
|
147
|
-
.join('\n');
|
|
148
|
-
if (nextValue !== text) {
|
|
149
|
-
const startLineText = linesBeforeCaret[startLine];
|
|
150
|
-
applyEdits({
|
|
151
|
-
value: nextValue,
|
|
152
|
-
selectionStart: startLineText?.startsWith(tabCharacter) ? selectionStart - tabCharacter.length : selectionStart,
|
|
153
|
-
selectionEnd: selectionEnd - (text.length - nextValue.length),
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
} else if (selectionStart !== selectionEnd) {
|
|
157
|
-
// Indent selected lines
|
|
158
|
-
const linesBeforeCaret = getLines(text, selectionStart);
|
|
159
|
-
const startLine = linesBeforeCaret.length - 1;
|
|
160
|
-
const endLine = getLines(text, selectionEnd).length - 1;
|
|
161
|
-
const startLineText = linesBeforeCaret[startLine];
|
|
162
|
-
applyEdits({
|
|
163
|
-
value: text
|
|
164
|
-
.split('\n')
|
|
165
|
-
.map((line, i) => (i >= startLine && i <= endLine ? tabCharacter + line : line))
|
|
166
|
-
.join('\n'),
|
|
167
|
-
selectionStart: startLineText && /\S/.test(startLineText) ? selectionStart + tabCharacter.length : selectionStart,
|
|
168
|
-
selectionEnd: selectionEnd + tabCharacter.length * (endLine - startLine + 1),
|
|
169
|
-
});
|
|
170
|
-
} else {
|
|
171
|
-
const updatedSelection = selectionStart + tabCharacter.length;
|
|
172
|
-
applyEdits({
|
|
173
|
-
value: text.substring(0, selectionStart) + tabCharacter + text.substring(selectionEnd),
|
|
174
|
-
selectionStart: updatedSelection,
|
|
175
|
-
selectionEnd: updatedSelection,
|
|
176
|
-
});
|
|
177
|
-
}
|
|
143
|
+
const setSelection = (start: number, end: number, focus: boolean) => {
|
|
144
|
+
lastSelectionRef.current = { start, end };
|
|
145
|
+
const input = inputRef.current;
|
|
146
|
+
if (!input) {
|
|
147
|
+
pendingSelectionRef.current = { start, end, focus };
|
|
178
148
|
return;
|
|
179
149
|
}
|
|
150
|
+
input.selectionStart = start;
|
|
151
|
+
input.selectionEnd = end;
|
|
152
|
+
if (focus) input.focus();
|
|
153
|
+
};
|
|
180
154
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
155
|
+
useImperativeHandle(
|
|
156
|
+
handleRef ?? null,
|
|
157
|
+
() => ({
|
|
158
|
+
textarea: () => inputRef.current,
|
|
159
|
+
getSelection: () => {
|
|
160
|
+
const input = inputRef.current;
|
|
161
|
+
return input ? { start: input.selectionStart, end: input.selectionEnd } : null;
|
|
162
|
+
},
|
|
163
|
+
select: setSelection,
|
|
164
|
+
applyDocument: (next, caretStart, caretEnd) => {
|
|
165
|
+
undoStack.commitEdit({ value: next, selectionStart: caretStart, selectionEnd: caretEnd });
|
|
166
|
+
// Focus is deliberately left where it was: the find widget's Replace
|
|
167
|
+
// button is about to be pressed again, and pulling focus into the
|
|
168
|
+
// document would eat the next keystroke.
|
|
169
|
+
setSelection(caretStart, caretEnd, false);
|
|
170
|
+
},
|
|
171
|
+
occurrences: () => multi.ranges,
|
|
172
|
+
run: (command) => {
|
|
173
|
+
const input = inputRef.current;
|
|
174
|
+
if (input) runEditorCommand(command, keydownDeps(input));
|
|
175
|
+
},
|
|
176
|
+
}),
|
|
177
|
+
// `applyEdits` closes over `onValueChange` and the history ref only, both of
|
|
178
|
+
// which are stable for the component's life.
|
|
179
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
180
|
+
[onValueChange],
|
|
181
|
+
);
|
|
194
182
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
selectionStart: updatedSelection,
|
|
206
|
-
selectionEnd: updatedSelection,
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
183
|
+
// Runs on every render: a selection asked for while the field was not yet
|
|
184
|
+
// mounted (the find widget opens in the same tick as the panel's first
|
|
185
|
+
// render) has to land as soon as the textarea exists.
|
|
186
|
+
useLayoutEffect(() => {
|
|
187
|
+
const pending = pendingSelectionRef.current;
|
|
188
|
+
if (!pending || !inputRef.current) return;
|
|
189
|
+
pendingSelectionRef.current = null;
|
|
190
|
+
setSelection(pending.start, pending.end, pending.focus);
|
|
191
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
192
|
+
});
|
|
211
193
|
|
|
212
|
-
|
|
213
|
-
// desync the textarea from the highlighted <pre>.
|
|
214
|
-
const undoKey = isMac ? e.metaKey && e.key.toLowerCase() === 'z' : e.ctrlKey && e.key.toLowerCase() === 'z';
|
|
215
|
-
if (undoKey && !e.shiftKey && !e.altKey) {
|
|
216
|
-
e.preventDefault();
|
|
217
|
-
const record = stepHistory(historyRef.current, 'undo');
|
|
218
|
-
if (record && inputRef.current) {
|
|
219
|
-
inputRef.current.value = record.value;
|
|
220
|
-
inputRef.current.selectionStart = record.selectionStart;
|
|
221
|
-
inputRef.current.selectionEnd = record.selectionEnd;
|
|
222
|
-
onValueChange(record.value);
|
|
223
|
-
}
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
194
|
+
const undoStack = useEditorHistory({ value, inputRef, onValueChange });
|
|
226
195
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
196
|
+
/** Everything the dispatcher needs, built once per call from the live refs. */
|
|
197
|
+
const keydownDeps = (input: HTMLTextAreaElement): EditorKeydownDeps => ({
|
|
198
|
+
input,
|
|
199
|
+
language,
|
|
200
|
+
captureTab,
|
|
201
|
+
toggleCaptureTab: () => setCaptureTab((prev) => !prev),
|
|
202
|
+
history: undoStack,
|
|
203
|
+
multi,
|
|
204
|
+
select: (start, end) => setSelection(start, end, true),
|
|
205
|
+
delegate: (command) => {
|
|
206
|
+
onCommand?.(command);
|
|
207
|
+
},
|
|
208
|
+
});
|
|
243
209
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
210
|
+
const handleKeyDown = (e: TargetedKeyboardEvent<HTMLTextAreaElement>) => {
|
|
211
|
+
// The selection as of the last event that could precede an edit:
|
|
212
|
+
// `handleChange` needs to know which range the browser replaced, and the
|
|
213
|
+
// `input` event reports the selection AFTER the edit.
|
|
214
|
+
lastSelectionRef.current = { start: e.currentTarget.selectionStart, end: e.currentTarget.selectionEnd };
|
|
215
|
+
handleEditorKeydown(e, keydownDeps(e.currentTarget));
|
|
249
216
|
};
|
|
250
217
|
|
|
251
218
|
const handleChange = (e: Event) => {
|
|
252
219
|
const input = e.target as HTMLTextAreaElement;
|
|
253
220
|
const { value: next, selectionStart, selectionEnd } = input;
|
|
254
|
-
const history =
|
|
221
|
+
const history = undoStack.history;
|
|
255
222
|
const timestamp = Date.now();
|
|
223
|
+
// With extra ranges live, the browser's edit becomes a template: it is
|
|
224
|
+
// diffed out of the OLD buffer and replayed at every other range in one
|
|
225
|
+
// pass, so a keystroke, a paste and an IME composition all need no special
|
|
226
|
+
// case. The result is committed as one history entry, and the ranges
|
|
227
|
+
// collapse to carets so a second keystroke continues at all of them.
|
|
228
|
+
const replicated = multi.replicate(undoStack.buffer, next, lastSelectionRef.current);
|
|
229
|
+
if (replicated) {
|
|
230
|
+
undoStack.markApplied(replicated.value);
|
|
231
|
+
input.value = replicated.value;
|
|
232
|
+
input.selectionStart = replicated.caret;
|
|
233
|
+
input.selectionEnd = replicated.caret;
|
|
234
|
+
lastSelectionRef.current = { start: replicated.caret, end: replicated.caret };
|
|
235
|
+
multi.set(replicated.rest);
|
|
236
|
+
// The burst merge applies here too: without it each character of a word
|
|
237
|
+
// typed at several carets became its own undo entry, so ⌘Z peeled off one
|
|
238
|
+
// letter instead of the word — the exact behaviour the single-caret path
|
|
239
|
+
// had already been fixed for.
|
|
240
|
+
if (continuesTyping(history.stack[history.offset], replicated.value, replicated.caret, timestamp)) {
|
|
241
|
+
mergeTyping(history, replicated.value, replicated.caret, replicated.caret, timestamp);
|
|
242
|
+
} else {
|
|
243
|
+
pushRecord(history, { value: replicated.value, selectionStart: replicated.caret, selectionEnd: replicated.caret, timestamp });
|
|
244
|
+
}
|
|
245
|
+
onValueChange(replicated.value);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
undoStack.markApplied(next);
|
|
256
250
|
// Merge typing bursts into one word-level undo entry.
|
|
257
251
|
if (continuesTyping(history.stack[history.offset], next, selectionStart, timestamp)) {
|
|
258
252
|
mergeTyping(history, next, selectionStart, selectionEnd, timestamp);
|
|
@@ -263,44 +257,8 @@ export function CodeEditor({
|
|
|
263
257
|
onValueChange(next);
|
|
264
258
|
};
|
|
265
259
|
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
border: 0,
|
|
269
|
-
background: 'none',
|
|
270
|
-
boxSizing: 'inherit',
|
|
271
|
-
display: 'inherit',
|
|
272
|
-
fontFamily: 'inherit',
|
|
273
|
-
fontSize: 'inherit',
|
|
274
|
-
fontStyle: 'inherit',
|
|
275
|
-
fontVariantLigatures: 'inherit',
|
|
276
|
-
fontWeight: 'inherit',
|
|
277
|
-
letterSpacing: 'inherit',
|
|
278
|
-
lineHeight: 'inherit',
|
|
279
|
-
tabSize: 'inherit',
|
|
280
|
-
textIndent: 'inherit',
|
|
281
|
-
textRendering: 'inherit',
|
|
282
|
-
textTransform: 'inherit',
|
|
283
|
-
whiteSpace: 'pre-wrap',
|
|
284
|
-
wordBreak: 'keep-all',
|
|
285
|
-
overflowWrap: 'break-word',
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
// The textarea owns the full document, so its padding is the plain inset and
|
|
289
|
-
// its lines land exactly where the geometry says line 0 starts.
|
|
290
|
-
const contentStyle: CSSProperties = {
|
|
291
|
-
paddingTop: padding,
|
|
292
|
-
paddingRight: padding,
|
|
293
|
-
paddingBottom: padding,
|
|
294
|
-
paddingLeft: padding,
|
|
295
|
-
};
|
|
296
|
-
|
|
297
|
-
// The `<pre>` carries only the window; the lines it is not given are reserved
|
|
298
|
-
// as padding, so the aligned layers stay aligned.
|
|
299
|
-
const windowStyle: CSSProperties = {
|
|
300
|
-
...contentStyle,
|
|
301
|
-
paddingTop: padding + (virtual?.top ?? 0),
|
|
302
|
-
paddingBottom: padding + (virtual?.bottom ?? 0),
|
|
303
|
-
};
|
|
260
|
+
const { pre: preStyle, textarea: textareaStyle } = layerStyles(padding, virtual);
|
|
261
|
+
const layer = layerStyle();
|
|
304
262
|
|
|
305
263
|
const highlighted = highlight(value, virtual ?? null);
|
|
306
264
|
|
|
@@ -310,16 +268,21 @@ export function CodeEditor({
|
|
|
310
268
|
ref={preRef}
|
|
311
269
|
className={preClassName}
|
|
312
270
|
aria-hidden="true"
|
|
313
|
-
style={{ ...
|
|
271
|
+
style={{ ...layer, ...preStyle, position: 'relative', pointerEvents: 'none' }}
|
|
314
272
|
dangerouslySetInnerHTML={{ __html: highlighted + (virtual && !virtual.breakAtEnd ? '' : '<br />') }}
|
|
315
273
|
/>
|
|
316
274
|
<textarea
|
|
317
275
|
ref={inputRef}
|
|
318
276
|
className={`${BASE_TEXTAREA_CLASS}${textareaClassName ? ` ${textareaClassName}` : ''}`}
|
|
319
|
-
style={{ ...
|
|
277
|
+
style={{ ...layer, ...textareaStyle, position: 'absolute', top: 0, left: 0, height: '100%', width: '100%', resize: 'none', color: 'inherit', overflow: 'hidden', WebkitTextFillColor: 'transparent' }}
|
|
320
278
|
value={value}
|
|
321
279
|
onChange={handleChange}
|
|
322
280
|
onKeyDown={handleKeyDown}
|
|
281
|
+
onSelect={() => {
|
|
282
|
+
const input = inputRef.current;
|
|
283
|
+
if (input) lastSelectionRef.current = { start: input.selectionStart, end: input.selectionEnd };
|
|
284
|
+
}}
|
|
285
|
+
onBlur={() => multi.clear()}
|
|
323
286
|
placeholder={placeholder}
|
|
324
287
|
readOnly={readOnly}
|
|
325
288
|
autoFocus={autoFocus}
|
|
@@ -329,7 +292,9 @@ export function CodeEditor({
|
|
|
329
292
|
spellcheck={false}
|
|
330
293
|
data-gramm={false}
|
|
331
294
|
/>
|
|
332
|
-
<
|
|
295
|
+
{/* One <style> per editor instance; the rule is a constant so the
|
|
296
|
+
duplicate elements are the same three lines the browser dedupes. */}
|
|
297
|
+
<style dangerouslySetInnerHTML={{ __html: CODE_EDITOR_RESET_CSS }} />
|
|
333
298
|
</div>
|
|
334
299
|
);
|
|
335
300
|
}
|