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
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The editor's keydown dispatcher, exercised with a fake textarea.
|
|
8
|
+
*
|
|
9
|
+
* These assertions are the routing contract: which layer owns a key, and what
|
|
10
|
+
* happens to the event when nobody does. The failures they guard against are
|
|
11
|
+
* all silent — a chord that reaches the browser's own find bar, an Escape that
|
|
12
|
+
* throws away a multi-selection instead of dropping it, a Tab that escapes the
|
|
13
|
+
* editor mid-line.
|
|
14
|
+
*
|
|
15
|
+
* The platform is PINNED rather than detected. The dispatcher resolves chords
|
|
16
|
+
* through `isMacPlatform()`, so a test written with ⌘ chords passes on a
|
|
17
|
+
* developer's Mac and fails on the Linux CI runner — which is exactly what
|
|
18
|
+
* happened. `usePlatform()` below makes each case state the platform it means.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { describe, expect, test } from 'bun:test';
|
|
22
|
+
|
|
23
|
+
import { handleEditorKeydown, runEditorCommand, type EditorKeydownDeps } from '@/client/components/common/code-editor/handler';
|
|
24
|
+
import { createHistory, pushRecord } from '@/shared/lib/code/editor/history';
|
|
25
|
+
import type { EditorHistory } from '@/client/hooks/editor/use-editor-history';
|
|
26
|
+
import type { OccurrencesApi } from '@/client/hooks/editor/use-occurrences';
|
|
27
|
+
import type { EditorCommand } from '@/shared/lib/code/editor/keymap';
|
|
28
|
+
import type { TextRange } from '@/shared/lib/code/editor/commands';
|
|
29
|
+
|
|
30
|
+
/** A textarea stand-in: the dispatcher only reads value/selection and calls blur. */
|
|
31
|
+
function fakeInput(value: string, start: number, end = start) {
|
|
32
|
+
return {
|
|
33
|
+
value,
|
|
34
|
+
selectionStart: start,
|
|
35
|
+
selectionEnd: end,
|
|
36
|
+
blurred: false,
|
|
37
|
+
blur() {
|
|
38
|
+
this.blurred = true;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface Harness {
|
|
44
|
+
deps: EditorKeydownDeps;
|
|
45
|
+
committed: Array<{ value: string; selectionStart: number; selectionEnd: number }>;
|
|
46
|
+
steps: string[];
|
|
47
|
+
delegated: EditorCommand[];
|
|
48
|
+
cleared: number;
|
|
49
|
+
selected: Array<[number, number]>;
|
|
50
|
+
ranges: TextRange[];
|
|
51
|
+
captureToggles: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function harness(options: { value: string; start?: number; end?: number; ranges?: TextRange[]; language?: string; delegate?: (c: EditorCommand) => boolean } = { value: '' }): Harness {
|
|
55
|
+
const committed: Harness['committed'] = [];
|
|
56
|
+
const steps: string[] = [];
|
|
57
|
+
const delegated: EditorCommand[] = [];
|
|
58
|
+
const selected: Array<[number, number]> = [];
|
|
59
|
+
const state = { ranges: options.ranges ? [...options.ranges] : [] as TextRange[] };
|
|
60
|
+
let cleared = 0;
|
|
61
|
+
let captureToggles = 0;
|
|
62
|
+
|
|
63
|
+
const history: EditorHistory = {
|
|
64
|
+
history: createHistory(),
|
|
65
|
+
buffer: options.value,
|
|
66
|
+
commit: (edit) => committed.push(edit),
|
|
67
|
+
commitEdit: (edit) => committed.push(edit),
|
|
68
|
+
step: (direction) => steps.push(direction),
|
|
69
|
+
markApplied: () => {},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const multi: OccurrencesApi = {
|
|
73
|
+
get ranges() {
|
|
74
|
+
return state.ranges;
|
|
75
|
+
},
|
|
76
|
+
set: (ranges) => {
|
|
77
|
+
state.ranges = [...ranges];
|
|
78
|
+
},
|
|
79
|
+
clear: () => {
|
|
80
|
+
cleared++;
|
|
81
|
+
state.ranges = [];
|
|
82
|
+
},
|
|
83
|
+
selectNext: (input, select) => select(input.selectionStart, input.selectionEnd + 1),
|
|
84
|
+
selectAll: (input, select) => select(0, input.value.length),
|
|
85
|
+
insertCursorBelow: () => {},
|
|
86
|
+
replicate: () => null,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const input = fakeInput(options.value, options.start ?? 0, options.end ?? options.start ?? 0);
|
|
90
|
+
const deps: EditorKeydownDeps = {
|
|
91
|
+
input: input as unknown as HTMLTextAreaElement,
|
|
92
|
+
language: options.language ?? 'typescript',
|
|
93
|
+
captureTab: true,
|
|
94
|
+
toggleCaptureTab: () => captureToggles++,
|
|
95
|
+
history,
|
|
96
|
+
multi,
|
|
97
|
+
select: (start, end) => selected.push([start, end]),
|
|
98
|
+
delegate: (command) => {
|
|
99
|
+
delegated.push(command);
|
|
100
|
+
options.delegate?.(command);
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
deps,
|
|
106
|
+
committed,
|
|
107
|
+
steps,
|
|
108
|
+
delegated,
|
|
109
|
+
selected,
|
|
110
|
+
get cleared() {
|
|
111
|
+
return cleared;
|
|
112
|
+
},
|
|
113
|
+
get captureToggles() {
|
|
114
|
+
return captureToggles;
|
|
115
|
+
},
|
|
116
|
+
get ranges() {
|
|
117
|
+
return state.ranges;
|
|
118
|
+
},
|
|
119
|
+
} as Harness;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Run `body` with `navigator.platform` reporting `platform`. */
|
|
123
|
+
function usePlatform<T>(platform: string, body: () => T): T {
|
|
124
|
+
const globals = globalThis as Record<string, unknown>;
|
|
125
|
+
const previous = globals.navigator;
|
|
126
|
+
globals.navigator = { platform };
|
|
127
|
+
try {
|
|
128
|
+
return body();
|
|
129
|
+
} finally {
|
|
130
|
+
if (previous === undefined) delete globals.navigator;
|
|
131
|
+
else globals.navigator = previous;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** A keydown event with only the fields the dispatcher reads. */
|
|
136
|
+
function press(key: string, code: string, modifiers: Partial<{ metaKey: boolean; ctrlKey: boolean; altKey: boolean; shiftKey: boolean }> = {}) {
|
|
137
|
+
const prevented = { value: false };
|
|
138
|
+
const event = {
|
|
139
|
+
key,
|
|
140
|
+
code,
|
|
141
|
+
metaKey: false,
|
|
142
|
+
ctrlKey: false,
|
|
143
|
+
altKey: false,
|
|
144
|
+
shiftKey: false,
|
|
145
|
+
...modifiers,
|
|
146
|
+
preventDefault: () => {
|
|
147
|
+
prevented.value = true;
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
return { event: event as never, prevented };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
describe('handleEditorKeydown', () => {
|
|
154
|
+
test('Escape drops a live multi-selection before it leaves the editor', () => {
|
|
155
|
+
const first = harness({ value: 'abc', ranges: [{ start: 1, end: 2 }] });
|
|
156
|
+
const one = press('Escape', 'Escape');
|
|
157
|
+
handleEditorKeydown(one.event, first.deps);
|
|
158
|
+
|
|
159
|
+
expect(first.cleared).toBe(1);
|
|
160
|
+
expect(one.prevented.value).toBe(true);
|
|
161
|
+
|
|
162
|
+
// With no extra ranges left, Escape is a plain blur.
|
|
163
|
+
const input = first.deps.input as unknown as { blurred: boolean };
|
|
164
|
+
const two = press('Escape', 'Escape');
|
|
165
|
+
handleEditorKeydown(two.event, first.deps);
|
|
166
|
+
|
|
167
|
+
expect(input.blurred).toBe(true);
|
|
168
|
+
expect(two.prevented.value).toBe(false);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('Tab indents through the history, not through a raw write', () => {
|
|
172
|
+
const h = harness({ value: 'one', start: 0 });
|
|
173
|
+
const { event, prevented } = press('Tab', 'Tab');
|
|
174
|
+
handleEditorKeydown(event, h.deps);
|
|
175
|
+
|
|
176
|
+
expect(prevented.value).toBe(true);
|
|
177
|
+
expect(h.committed[0].value).toBe(' one');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('undo and redo step the history and drop the extra ranges', () => {
|
|
181
|
+
const h = harness({ value: 'abc', ranges: [{ start: 1, end: 2 }] });
|
|
182
|
+
usePlatform('MacIntel', () => handleEditorKeydown(press('z', 'KeyZ', { metaKey: true }).event, h.deps));
|
|
183
|
+
|
|
184
|
+
expect(h.steps).toEqual(['undo']);
|
|
185
|
+
expect(h.cleared).toBe(1);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('a line command commits and clears the ranges it just invalidated', () => {
|
|
189
|
+
const h = harness({ value: 'one\ntwo', start: 0, end: 3, ranges: [{ start: 8, end: 9 }] });
|
|
190
|
+
handleEditorKeydown(press('ArrowDown', 'ArrowDown', { altKey: true }).event, h.deps);
|
|
191
|
+
|
|
192
|
+
expect(h.committed[0].value).toBe('two\none');
|
|
193
|
+
expect(h.cleared).toBe(1);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test('⌘D routes to the occurrence grower with the editor’s own select', () => {
|
|
197
|
+
const h = harness({ value: 'alpha', start: 0, end: 5 });
|
|
198
|
+
usePlatform('MacIntel', () => handleEditorKeydown(press('d', 'KeyD', { metaKey: true }).event, h.deps));
|
|
199
|
+
|
|
200
|
+
expect(h.selected).toEqual([[0, 6]]);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test('a find-bar chord is delegated and consumed', () => {
|
|
204
|
+
// The bar may not be open: nothing handles ⌘F, but the browser's own find
|
|
205
|
+
// bar must not open over the editor either — so the chord is consumed
|
|
206
|
+
// whatever the panel does with it.
|
|
207
|
+
const h = harness({ value: 'abc' });
|
|
208
|
+
const { event, prevented } = press('f', 'KeyF', { metaKey: true });
|
|
209
|
+
usePlatform('MacIntel', () => handleEditorKeydown(event, h.deps));
|
|
210
|
+
|
|
211
|
+
expect(h.delegated).toEqual(['find']);
|
|
212
|
+
expect(prevented.value).toBe(true);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('a palette-style call runs a text command with no event at all', () => {
|
|
216
|
+
const h = harness({ value: 'one\ntwo', start: 0, end: 3 });
|
|
217
|
+
runEditorCommand('copyLineDown', h.deps);
|
|
218
|
+
|
|
219
|
+
expect(h.committed[0].value).toBe('one\none\ntwo');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('an unbound chord is left to the browser', () => {
|
|
223
|
+
const h = harness({ value: 'abc' });
|
|
224
|
+
const { event, prevented } = press('a', 'KeyA', { metaKey: true });
|
|
225
|
+
handleEditorKeydown(event, h.deps);
|
|
226
|
+
|
|
227
|
+
expect(h.delegated).toEqual([]);
|
|
228
|
+
expect(prevented.value).toBe(false);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('a chord built on Enter reaches the keymap instead of the plain-Enter branch', () => {
|
|
232
|
+
// ⌘⏎ is `insertLineBelow`; the plain-Enter auto-indent branch used to catch
|
|
233
|
+
// it first and do nothing on an unindented line, making the command dead.
|
|
234
|
+
const h = harness({ value: 'const a = 1;\nconst b = 2;', start: 20 });
|
|
235
|
+
usePlatform('MacIntel', () => handleEditorKeydown(press('Enter', 'Enter', { metaKey: true }).event, h.deps));
|
|
236
|
+
|
|
237
|
+
expect(h.committed[0].value).toBe('const a = 1;\nconst b = 2;\n');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test('plain Enter still auto-indents', () => {
|
|
241
|
+
const h = harness({ value: ' one', start: 5 });
|
|
242
|
+
handleEditorKeydown(press('Enter', 'Enter').event, h.deps);
|
|
243
|
+
|
|
244
|
+
expect(h.committed[0].value).toBe(' one\n ');
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test('⌘[ outdents the line the caret is on, with nothing selected', () => {
|
|
248
|
+
const h = harness({ value: ' one', start: 2 });
|
|
249
|
+
usePlatform('MacIntel', () => handleEditorKeydown(press('[', 'BracketLeft', { metaKey: true }).event, h.deps));
|
|
250
|
+
|
|
251
|
+
expect(h.committed[0].value).toBe('one');
|
|
252
|
+
expect(h.committed[0].selectionStart).toBe(0);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test('Ctrl+M still toggles Tab capture', () => {
|
|
256
|
+
const h = harness({ value: 'abc' });
|
|
257
|
+
// On macOS the chord needs Shift (Ctrl+M alone is Return in the terminal's
|
|
258
|
+
// own vocabulary), which is what the handler encodes. Both platforms are
|
|
259
|
+
// exercised explicitly rather than detected.
|
|
260
|
+
usePlatform('Win32', () => handleEditorKeydown(press('m', 'KeyM', { ctrlKey: true }).event, h.deps));
|
|
261
|
+
expect(h.captureToggles).toBe(1);
|
|
262
|
+
|
|
263
|
+
usePlatform('MacIntel', () => handleEditorKeydown(press('m', 'KeyM', { ctrlKey: true, shiftKey: true }).event, h.deps));
|
|
264
|
+
expect(h.captureToggles).toBe(2);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
describe('editor keymap ownership', () => {
|
|
269
|
+
test('the editor’s own history is seeded, not the browser’s', () => {
|
|
270
|
+
const history = createHistory();
|
|
271
|
+
pushRecord(history, { value: 'a', selectionStart: 1, selectionEnd: 1, timestamp: 1 });
|
|
272
|
+
expect(history.stack.length).toBe(1);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The editor's keydown handler.
|
|
8
|
+
*
|
|
9
|
+
* This is dispatch, not rendering: it reads the textarea, resolves the event
|
|
10
|
+
* against the keymap, and routes the command to whoever owns it — the undo
|
|
11
|
+
* stack, the multi-selection, the pure text commands, or the panel (the find
|
|
12
|
+
* bar's chords, word wrap). It lives outside the component because the
|
|
13
|
+
* component is at the repository's line ceiling and because this is the one
|
|
14
|
+
* place that must agree with the keymap about what each key means.
|
|
15
|
+
*
|
|
16
|
+
* The rules that are NOT obvious, each of which was a bug in the naive version:
|
|
17
|
+
*
|
|
18
|
+
* - **Escape is not a plain blur.** With extra ⌘D ranges live, its first job is
|
|
19
|
+
* to drop them; only the next press hands the keyboard back to the page.
|
|
20
|
+
* - **Tab / Backspace / Enter are not keymap entries.** Their behaviour is
|
|
21
|
+
* about the DOM node (indentation, focus, the Tab-capture toggle) as much as
|
|
22
|
+
* the buffer, so they are handled before the keymap is consulted.
|
|
23
|
+
* - **A command the editor does not own is not consumed** unless the panel
|
|
24
|
+
* accepts it, so ⌘F still reaches the panel's capture handler and the
|
|
25
|
+
* browser's own find bar never opens by accident.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import type { TargetedKeyboardEvent } from 'preact';
|
|
29
|
+
|
|
30
|
+
import { editForBackspace, editForEnter, editForTab } from '@/shared/lib/code/editor/edits';
|
|
31
|
+
import { isTextCommand, runTextCommand, type CommittedEdit } from '@/shared/lib/code/editor/text-commands';
|
|
32
|
+
import type { OccurrencesApi } from '@/client/hooks/editor/use-occurrences';
|
|
33
|
+
import type { EditorHistory } from '@/client/hooks/editor/use-editor-history';
|
|
34
|
+
import { EDITOR_KEY_BINDINGS, isFindBarCommand, type EditorCommand } from '@/shared/lib/code/editor/keymap';
|
|
35
|
+
import { resolveBinding } from '@/shared/lib/ui/key-binding';
|
|
36
|
+
import { isMacPlatform } from '@/shared/lib/util/platform';
|
|
37
|
+
|
|
38
|
+
export interface EditorKeydownDeps {
|
|
39
|
+
/** The live `<textarea>`, which owns the buffer and the primary selection. */
|
|
40
|
+
input: HTMLTextAreaElement;
|
|
41
|
+
/** Shiki language id, for the comment syntax. */
|
|
42
|
+
language: string;
|
|
43
|
+
/** Whether Tab is captured (Ctrl+M toggles it). */
|
|
44
|
+
captureTab: boolean;
|
|
45
|
+
toggleCaptureTab: () => void;
|
|
46
|
+
history: EditorHistory;
|
|
47
|
+
multi: OccurrencesApi;
|
|
48
|
+
/** Select `[start, end)` and focus the field. */
|
|
49
|
+
select: (start: number, end: number) => void;
|
|
50
|
+
/** Hand a command the editor does not own to the panel. */
|
|
51
|
+
delegate: (command: EditorCommand) => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Run a command that is already resolved — the routing half of the handler,
|
|
56
|
+
* shared by the keydown path and by callers that have no event at all (the
|
|
57
|
+
* command palette, a toolbar button). Splitting it out is what stops the
|
|
58
|
+
* palette from being a second, partial dispatcher: a command reachable from
|
|
59
|
+
* the keyboard is reachable from the palette by construction.
|
|
60
|
+
*/
|
|
61
|
+
export function runEditorCommand(command: EditorCommand, deps: EditorKeydownDeps, extend = false): void {
|
|
62
|
+
const { input, history, multi, select } = deps;
|
|
63
|
+
const { value: text, selectionStart, selectionEnd } = input;
|
|
64
|
+
|
|
65
|
+
if (command === 'undo' || command === 'redo') {
|
|
66
|
+
multi.clear();
|
|
67
|
+
history.step(command === 'undo' ? 'undo' : 'redo');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (command === 'selectNextOccurrence' || command === 'selectAllOccurrences') {
|
|
71
|
+
if (command === 'selectNextOccurrence') multi.selectNext(input, select);
|
|
72
|
+
else multi.selectAll(input, select);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (command === 'insertCursorBelow') {
|
|
76
|
+
multi.insertCursorBelow(input);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (isTextCommand(command)) {
|
|
81
|
+
runTextCommand(command, {
|
|
82
|
+
value: text,
|
|
83
|
+
selectionStart,
|
|
84
|
+
selectionEnd,
|
|
85
|
+
extend,
|
|
86
|
+
language: deps.language,
|
|
87
|
+
commit: (edit: CommittedEdit) => {
|
|
88
|
+
// A line command replaces the whole selection model: the extra ranges
|
|
89
|
+
// pointed at text that has just moved, and keeping them would leave
|
|
90
|
+
// carets on the wrong lines.
|
|
91
|
+
multi.clear();
|
|
92
|
+
history.commitEdit(edit);
|
|
93
|
+
},
|
|
94
|
+
setCaret: (offset) => select(offset, offset),
|
|
95
|
+
selectRange: (start, end) => select(start, end),
|
|
96
|
+
setDocumentEdge: (edge) => {
|
|
97
|
+
const offset = edge === 'start' ? 0 : text.length;
|
|
98
|
+
select(offset, offset);
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
deps.delegate(command);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Run the keydown handler. Every branch returns, so exactly one action fires. */
|
|
108
|
+
export function handleEditorKeydown(e: TargetedKeyboardEvent<HTMLTextAreaElement>, deps: EditorKeydownDeps): void {
|
|
109
|
+
const { input, history, multi } = deps;
|
|
110
|
+
const { value: text, selectionStart, selectionEnd } = input;
|
|
111
|
+
const isMac = isMacPlatform();
|
|
112
|
+
|
|
113
|
+
if (e.key === 'Escape') {
|
|
114
|
+
if (multi.ranges.length > 0) {
|
|
115
|
+
e.preventDefault();
|
|
116
|
+
multi.clear();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
input.blur();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// The bare-key handling below runs only for an UNMODIFIED key: ⌘⏎ and ⌘[
|
|
124
|
+
// are chords in the keymap, and catching them here first is what made the
|
|
125
|
+
// insert-line and indent commands unreachable (the plain-Enter branch
|
|
126
|
+
// swallowed them and did nothing, because the line had no indentation).
|
|
127
|
+
const unmodified = !e.metaKey && !e.ctrlKey && !e.altKey;
|
|
128
|
+
|
|
129
|
+
if (e.key === 'Tab' && deps.captureTab && unmodified) {
|
|
130
|
+
e.preventDefault();
|
|
131
|
+
// ⇧Tab with nothing selected is not an unindent — `editForTab` returns the
|
|
132
|
+
// buffer unchanged, and the key is consumed so focus cannot escape the
|
|
133
|
+
// editor mid-line.
|
|
134
|
+
const edit = editForTab(text, selectionStart, selectionEnd, e.shiftKey);
|
|
135
|
+
if (edit.value !== text) history.commitEdit(edit);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (e.key === 'Backspace' && unmodified) {
|
|
139
|
+
const edit = editForBackspace(text, selectionStart, selectionEnd);
|
|
140
|
+
if (edit) {
|
|
141
|
+
e.preventDefault();
|
|
142
|
+
history.commitEdit(edit);
|
|
143
|
+
}
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (e.key === 'Enter' && unmodified) {
|
|
147
|
+
const edit = editForEnter(text, selectionStart, selectionEnd);
|
|
148
|
+
if (edit) {
|
|
149
|
+
e.preventDefault();
|
|
150
|
+
history.commitEdit(edit);
|
|
151
|
+
}
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const command = resolveBinding(EDITOR_KEY_BINDINGS, e);
|
|
156
|
+
if (!command) {
|
|
157
|
+
// Ctrl+M / Ctrl+Shift+M toggles Tab capture so keyboard users can leave.
|
|
158
|
+
if (e.ctrlKey && e.key.toLowerCase() === 'm' && (isMac ? e.shiftKey : true)) {
|
|
159
|
+
e.preventDefault();
|
|
160
|
+
deps.toggleCaptureTab();
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// The editor owns everything except the panel's own commands, which the
|
|
166
|
+
// delegate reports on. A find-bar chord is consumed either way, so the
|
|
167
|
+
// browser's find bar never opens over the editor.
|
|
168
|
+
e.preventDefault();
|
|
169
|
+
if (isFindBarCommand(command)) {
|
|
170
|
+
deps.delegate(command);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
runEditorCommand(command, deps, e.shiftKey);
|
|
174
|
+
|
|
175
|
+
}
|