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,13 +1,23 @@
|
|
|
1
|
-
import { Check, Copy, Download, Eye, EyeOff, Maximize, Save, WrapText, ZoomIn, ZoomOut } from 'lucide-preact';
|
|
1
|
+
import { AlertCircle, Check, Copy, Download, Eye, EyeOff, Maximize, Minimize, Save, Search, WrapText, ZoomIn, ZoomOut } from 'lucide-preact';
|
|
2
|
+
|
|
3
|
+
export type EditorSaveStatus = 'idle' | 'saving' | 'saved' | 'error';
|
|
2
4
|
|
|
3
5
|
interface EditorToolbarProps {
|
|
4
6
|
path: string;
|
|
5
|
-
saveStatus:
|
|
7
|
+
saveStatus: EditorSaveStatus;
|
|
6
8
|
wordWrap: boolean;
|
|
7
9
|
isMd: boolean;
|
|
8
10
|
isPreview: boolean;
|
|
11
|
+
/** True for the 1.5s after a copy landed, so the button can confirm it. */
|
|
12
|
+
copied: boolean;
|
|
9
13
|
/** Raster image: no text to save or wrap, and zoom belongs to the picture. */
|
|
10
14
|
isImage?: boolean;
|
|
15
|
+
/** A read failure left no trustworthy buffer, so there is nothing to save. */
|
|
16
|
+
saveDisabled?: boolean;
|
|
17
|
+
isMaximized?: boolean;
|
|
18
|
+
/** Find/replace is open — the button reflects it and toggles the bar. */
|
|
19
|
+
findOpen?: boolean;
|
|
20
|
+
onToggleFind: () => void;
|
|
11
21
|
onSave: () => void;
|
|
12
22
|
onTogglePreview: () => void;
|
|
13
23
|
onToggleWordWrap: () => void;
|
|
@@ -18,13 +28,27 @@ interface EditorToolbarProps {
|
|
|
18
28
|
onToggleMaximize: () => void;
|
|
19
29
|
}
|
|
20
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Shared icon-button shell. Every action here is icon-only, so each one carries
|
|
33
|
+
* a `title` (the tooltip) and an `aria-label` (the accessible name) — a bare
|
|
34
|
+
* `<svg onClick>` is unreachable by keyboard and unlabeled to a screen reader.
|
|
35
|
+
* `p-1.5` also gives the 14px glyph a ~26px hit target.
|
|
36
|
+
*/
|
|
37
|
+
const ACTION_CLASS =
|
|
38
|
+
'flex items-center justify-center p-1.5 rounded transition-colors cursor-pointer disabled:cursor-default disabled:opacity-50';
|
|
39
|
+
|
|
21
40
|
export function EditorToolbar({
|
|
22
41
|
path,
|
|
23
42
|
saveStatus,
|
|
24
43
|
wordWrap,
|
|
25
44
|
isMd,
|
|
26
45
|
isPreview,
|
|
46
|
+
copied,
|
|
27
47
|
isImage = false,
|
|
48
|
+
saveDisabled = false,
|
|
49
|
+
isMaximized = false,
|
|
50
|
+
findOpen = false,
|
|
51
|
+
onToggleFind,
|
|
28
52
|
onSave,
|
|
29
53
|
onTogglePreview,
|
|
30
54
|
onToggleWordWrap,
|
|
@@ -34,65 +58,144 @@ export function EditorToolbar({
|
|
|
34
58
|
onDownload,
|
|
35
59
|
onToggleMaximize,
|
|
36
60
|
}: EditorToolbarProps) {
|
|
61
|
+
const saveTitle =
|
|
62
|
+
saveDisabled
|
|
63
|
+
? 'Nothing to save — this file could not be read'
|
|
64
|
+
: saveStatus === 'saving'
|
|
65
|
+
? 'Saving…'
|
|
66
|
+
: saveStatus === 'error'
|
|
67
|
+
? 'Save failed — the file on disk was not changed'
|
|
68
|
+
: 'Save file (Ctrl+S)';
|
|
69
|
+
|
|
37
70
|
return (
|
|
38
|
-
<div className="flex items-center justify-between px-3 py-2 border-b border-ink/10 bg-paper">
|
|
71
|
+
<div className="flex items-center justify-between px-3 py-2 border-b border-ink/10 bg-paper flex-shrink-0">
|
|
39
72
|
{/* Left status */}
|
|
40
|
-
<div className="flex items-center space-x-2 text-xs font-mono text-ink/40">
|
|
73
|
+
<div className="flex items-center space-x-2 text-xs font-mono text-ink/40 min-w-0">
|
|
41
74
|
<span className="truncate max-w-[300px]" title={path}>{path}</span>
|
|
42
75
|
</div>
|
|
43
76
|
|
|
44
77
|
{/* Right actions */}
|
|
45
|
-
<div className="flex items-center space-x-
|
|
78
|
+
<div className="flex items-center space-x-1 text-ink/40 flex-shrink-0">
|
|
46
79
|
{!isImage && (
|
|
47
|
-
<div className="flex items-center pr-
|
|
80
|
+
<div className="flex items-center pr-2 mr-1 border-r border-ink/10">
|
|
48
81
|
<button
|
|
49
82
|
type="button"
|
|
50
83
|
onClick={onSave}
|
|
51
|
-
|
|
52
|
-
|
|
84
|
+
disabled={saveDisabled || saveStatus === 'saving'}
|
|
85
|
+
className={`${ACTION_CLASS} hover:bg-ink/5`}
|
|
86
|
+
title={saveTitle}
|
|
87
|
+
aria-label={saveTitle}
|
|
53
88
|
>
|
|
54
89
|
{saveStatus === 'saving' ? (
|
|
55
90
|
<Save size={14} className="text-amber-500 animate-pulse" />
|
|
56
91
|
) : saveStatus === 'saved' ? (
|
|
57
92
|
<Check size={14} className="text-success" />
|
|
93
|
+
) : saveStatus === 'error' ? (
|
|
94
|
+
<AlertCircle size={14} className="text-error" />
|
|
58
95
|
) : (
|
|
59
|
-
<Save size={14} className="hover:text-ink
|
|
96
|
+
<Save size={14} className="hover:text-ink" />
|
|
60
97
|
)}
|
|
61
98
|
</button>
|
|
62
99
|
</div>
|
|
63
100
|
)}
|
|
64
101
|
|
|
65
102
|
{isMd && (
|
|
66
|
-
<div
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
103
|
+
<div className="flex items-center pr-2 mr-1 border-r border-ink/10">
|
|
104
|
+
<button
|
|
105
|
+
type="button"
|
|
106
|
+
onClick={onTogglePreview}
|
|
107
|
+
className={`${ACTION_CLASS} hover:bg-ink/5 ${isPreview ? 'text-ink' : 'hover:text-ink'}`}
|
|
108
|
+
title={isPreview ? 'Show markdown source' : 'Preview markdown'}
|
|
109
|
+
aria-label={isPreview ? 'Show markdown source' : 'Preview markdown'}
|
|
110
|
+
aria-pressed={isPreview}
|
|
111
|
+
>
|
|
112
|
+
{isPreview ? <EyeOff size={14} /> : <Eye size={14} />}
|
|
113
|
+
</button>
|
|
72
114
|
</div>
|
|
73
115
|
)}
|
|
74
116
|
|
|
75
117
|
{!isImage && (
|
|
76
118
|
<>
|
|
77
|
-
<div className="flex items-center
|
|
78
|
-
<
|
|
119
|
+
<div className="flex items-center pr-2 mr-1 border-r border-ink/10">
|
|
120
|
+
<button
|
|
121
|
+
type="button"
|
|
122
|
+
onClick={onToggleFind}
|
|
123
|
+
className={`${ACTION_CLASS} hover:bg-ink/5 ${findOpen ? 'text-ink' : 'hover:text-ink'}`}
|
|
124
|
+
title="Find and replace (Ctrl+F)"
|
|
125
|
+
aria-label="Find and replace"
|
|
126
|
+
aria-pressed={findOpen}
|
|
127
|
+
>
|
|
128
|
+
<Search size={14} />
|
|
129
|
+
</button>
|
|
79
130
|
</div>
|
|
80
131
|
|
|
81
|
-
<div className="flex items-center
|
|
82
|
-
<
|
|
83
|
-
|
|
132
|
+
<div className="flex items-center pr-2 mr-1 border-r border-ink/10">
|
|
133
|
+
<button
|
|
134
|
+
type="button"
|
|
135
|
+
onClick={onToggleWordWrap}
|
|
136
|
+
className={`${ACTION_CLASS} hover:bg-ink/5 ${wordWrap ? 'text-ink' : 'hover:text-ink'}`}
|
|
137
|
+
title={wordWrap ? 'Word wrap on' : 'Word wrap off'}
|
|
138
|
+
aria-label={wordWrap ? 'Word wrap on' : 'Word wrap off'}
|
|
139
|
+
aria-pressed={wordWrap}
|
|
140
|
+
>
|
|
141
|
+
<WrapText size={14} />
|
|
142
|
+
</button>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<div className="flex items-center pr-2 mr-1 border-r border-ink/10">
|
|
146
|
+
<button
|
|
147
|
+
type="button"
|
|
148
|
+
onClick={onZoomOut}
|
|
149
|
+
className={`${ACTION_CLASS} hover:bg-ink/5 hover:text-ink`}
|
|
150
|
+
title="Decrease font size"
|
|
151
|
+
aria-label="Decrease font size"
|
|
152
|
+
>
|
|
153
|
+
<ZoomOut size={14} />
|
|
154
|
+
</button>
|
|
155
|
+
<button
|
|
156
|
+
type="button"
|
|
157
|
+
onClick={onZoomIn}
|
|
158
|
+
className={`${ACTION_CLASS} hover:bg-ink/5 hover:text-ink`}
|
|
159
|
+
title="Increase font size"
|
|
160
|
+
aria-label="Increase font size"
|
|
161
|
+
>
|
|
162
|
+
<ZoomIn size={14} />
|
|
163
|
+
</button>
|
|
84
164
|
</div>
|
|
85
165
|
</>
|
|
86
166
|
)}
|
|
87
167
|
|
|
88
|
-
<div className="flex items-center
|
|
89
|
-
<
|
|
90
|
-
|
|
168
|
+
<div className="flex items-center pr-2 mr-1 border-r border-ink/10">
|
|
169
|
+
<button
|
|
170
|
+
type="button"
|
|
171
|
+
onClick={onCopy}
|
|
172
|
+
className={`${ACTION_CLASS} hover:bg-ink/5 hover:text-ink`}
|
|
173
|
+
title={copied ? 'Copied' : isImage ? 'Copy image' : 'Copy content'}
|
|
174
|
+
aria-label={copied ? 'Copied' : isImage ? 'Copy image' : 'Copy content'}
|
|
175
|
+
>
|
|
176
|
+
{copied ? <Check size={14} className="text-success" /> : <Copy size={14} />}
|
|
177
|
+
</button>
|
|
178
|
+
<button
|
|
179
|
+
type="button"
|
|
180
|
+
onClick={onDownload}
|
|
181
|
+
className={`${ACTION_CLASS} hover:bg-ink/5 hover:text-ink`}
|
|
182
|
+
title="Download file"
|
|
183
|
+
aria-label="Download file"
|
|
184
|
+
>
|
|
185
|
+
<Download size={14} />
|
|
186
|
+
</button>
|
|
91
187
|
</div>
|
|
92
188
|
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
189
|
+
<button
|
|
190
|
+
type="button"
|
|
191
|
+
onClick={onToggleMaximize}
|
|
192
|
+
className={`${ACTION_CLASS} hover:bg-ink/5 hover:text-ink ${isMaximized ? 'text-ink' : ''}`}
|
|
193
|
+
title={isMaximized ? 'Exit full screen' : 'Full screen'}
|
|
194
|
+
aria-label={isMaximized ? 'Exit full screen' : 'Full screen'}
|
|
195
|
+
aria-pressed={isMaximized}
|
|
196
|
+
>
|
|
197
|
+
{isMaximized ? <Minimize size={14} /> : <Maximize size={14} />}
|
|
198
|
+
</button>
|
|
96
199
|
</div>
|
|
97
200
|
</div>
|
|
98
201
|
);
|
|
@@ -1,17 +1,24 @@
|
|
|
1
|
-
import { useEffect, useState } from 'preact/hooks';
|
|
1
|
+
import { useEffect, useRef, useState } from 'preact/hooks';
|
|
2
|
+
import type { TargetedKeyboardEvent } from 'preact';
|
|
3
|
+
import { AlertTriangle, Loader2 } from 'lucide-preact';
|
|
2
4
|
import { FileIcon } from '@/client/components/common/file-icon';
|
|
3
|
-
import { CodeSurface } from '@/client/components/common/code-surface';
|
|
5
|
+
import { CodeSurface, type CodeSurfaceHandle } from '@/client/components/common/code-surface';
|
|
4
6
|
import { MarkdownRenderer } from '@/client/components/common/MarkdownRenderer';
|
|
5
7
|
import { useScrollbarFade, scrollbarFadeClass } from '@/client/hooks/ui/scrollbar-fade';
|
|
6
8
|
import { EditorTabs } from '@/client/components/workspace/editor/Tabs';
|
|
7
9
|
import { EditorToolbar } from '@/client/components/workspace/editor/Toolbar';
|
|
10
|
+
import { FindWidget } from '@/client/components/workspace/editor/FindWidget';
|
|
11
|
+
import { CommandPalette } from '@/client/components/workspace/editor/CommandPalette';
|
|
12
|
+
import { EDITOR_KEY_BINDINGS, isFindBarCommand, type EditorCommand } from '@/shared/lib/code/editor/keymap';
|
|
13
|
+
import type { TextRange } from '@/shared/lib/code/editor/commands';
|
|
14
|
+
import { resolveBinding } from '@/shared/lib/ui/key-binding';
|
|
8
15
|
import { ImageViewer } from '@/client/components/common/image-viewer';
|
|
9
|
-
import { getDefaultContent } from '@/shared/lib/code/editor-utils';
|
|
10
16
|
import { DiffPanel } from '@/client/components/workspace/diff-panel';
|
|
11
17
|
import { useSessionState } from '@/client/hooks/workspace/session-state';
|
|
12
18
|
import { useSessionStateContext } from '@/client/hooks/workspace/session-state/context';
|
|
13
19
|
import { getSessionValue } from '@/shared/lib/workspace/session-state/store';
|
|
14
20
|
import { useFileEditor } from '@/client/hooks/editor/use-file-editor';
|
|
21
|
+
import { useEditorFind } from '@/client/hooks/editor/use-editor-find';
|
|
15
22
|
|
|
16
23
|
interface EditorProps {
|
|
17
24
|
className?: string;
|
|
@@ -19,6 +26,8 @@ interface EditorProps {
|
|
|
19
26
|
activeFileId: number | string | null;
|
|
20
27
|
onSelectFile: (id: number | string) => void;
|
|
21
28
|
onCloseFile: (id: number | string) => void;
|
|
29
|
+
/** Replace a diff tab with the plain editor tab for the same file. */
|
|
30
|
+
onConvertDiffToEditor: (id: number | string) => void;
|
|
22
31
|
refreshKey?: number;
|
|
23
32
|
onFileSaved?: () => void;
|
|
24
33
|
}
|
|
@@ -29,6 +38,7 @@ export function Editor({
|
|
|
29
38
|
activeFileId,
|
|
30
39
|
onSelectFile,
|
|
31
40
|
onCloseFile,
|
|
41
|
+
onConvertDiffToEditor,
|
|
32
42
|
refreshKey = 0,
|
|
33
43
|
onFileSaved
|
|
34
44
|
}: EditorProps) {
|
|
@@ -38,14 +48,35 @@ export function Editor({
|
|
|
38
48
|
const [zoomLevel, setZoomLevel] = useSessionState<number>('editor.zoomLevel', 12);
|
|
39
49
|
const [isMaximized, setIsMaximized] = useState(false);
|
|
40
50
|
const [wordWrap, setWordWrap] = useSessionState<boolean>('editor.wordWrap', true);
|
|
51
|
+
/** Extra ⌘D ranges; the surface paints them and the editor edits them by replication. */
|
|
52
|
+
const [occurrences, setOccurrences] = useState<readonly TextRange[]>([]);
|
|
53
|
+
const [paletteOpen, setPaletteOpen] = useState(false);
|
|
41
54
|
const { sessionId } = useSessionStateContext();
|
|
42
55
|
const { isScrolling, handleScroll } = useScrollbarFade();
|
|
56
|
+
const surfaceRef = useRef<CodeSurfaceHandle | null>(null);
|
|
43
57
|
|
|
44
58
|
const editor = useFileEditor(activeFile ?? null, {
|
|
45
|
-
|
|
59
|
+
// A file that could not be read must report the failure, not substitute
|
|
60
|
+
// placeholder text — the mock sample used to be written straight over the
|
|
61
|
+
// real file by the next save.
|
|
62
|
+
reportSaveError: true,
|
|
46
63
|
onFileSaved,
|
|
47
64
|
});
|
|
48
65
|
|
|
66
|
+
const find = useEditorFind({
|
|
67
|
+
text: editor.content,
|
|
68
|
+
documentKey: `${activeFile?.id ?? ''}\u0000${editor.language}`,
|
|
69
|
+
readSelection: () => surfaceRef.current?.getSelection() ?? null,
|
|
70
|
+
select: (start, end, focus = true) => surfaceRef.current?.select(start, end, focus),
|
|
71
|
+
reveal: (offset) => surfaceRef.current?.revealOffset(offset),
|
|
72
|
+
applyDocument: (value, caretStart, caretEnd) => surfaceRef.current?.applyDocument(value, caretStart, caretEnd),
|
|
73
|
+
toggleWordWrap: () => setWordWrap((previous) => !previous),
|
|
74
|
+
});
|
|
75
|
+
// The capture handler below is rebuilt on every render anyway (it reads the
|
|
76
|
+
// find state), so it is kept in a ref and the listener attached once.
|
|
77
|
+
const findRef = useRef(find);
|
|
78
|
+
findRef.current = find;
|
|
79
|
+
|
|
49
80
|
useEffect(() => {
|
|
50
81
|
if (refreshKey > 0) editor.reset();
|
|
51
82
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -84,6 +115,69 @@ export function Editor({
|
|
|
84
115
|
setPreviewMode(prev => ({ ...prev, [activeFile.id]: !prev[activeFile.id] }));
|
|
85
116
|
};
|
|
86
117
|
|
|
118
|
+
/**
|
|
119
|
+
* The one place a command runs, whatever asked for it — a chord, a toolbar
|
|
120
|
+
* button or the palette. Splitting this by source is how a command ends up
|
|
121
|
+
* working from the keyboard but not from the palette (or the reverse).
|
|
122
|
+
*/
|
|
123
|
+
const runCommand = (command: EditorCommand) => {
|
|
124
|
+
if (isFindBarCommand(command)) {
|
|
125
|
+
findRef.current.runCommand(command);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (command === 'toggleWordWrap') {
|
|
129
|
+
setWordWrap((previous) => !previous);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// Everything else is the editor's, and it runs through the editor's own
|
|
133
|
+
// dispatcher — the palette is a second way IN, never a second implementation.
|
|
134
|
+
surfaceRef.current?.runCommand(command);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Editor shortcuts, on the CAPTURE phase of the panel.
|
|
139
|
+
*
|
|
140
|
+
* Capture is what makes them work while focus is inside the document: the
|
|
141
|
+
* `<textarea>` has its own handler (Tab, undo, Escape-to-blur) and would
|
|
142
|
+
* otherwise see these first. Only the commands the PANEL owns are consumed
|
|
143
|
+
* here — the editor's own keymap runs in the textarea, where it has the
|
|
144
|
+
* buffer and the caret in hand.
|
|
145
|
+
*
|
|
146
|
+
* The panel is the scope, not the window: another panel's text field keeps
|
|
147
|
+
* its own ⌘F, and the composer is untouched.
|
|
148
|
+
*/
|
|
149
|
+
const handlePanelKeyDown = (event: TargetedKeyboardEvent<HTMLDivElement>) => {
|
|
150
|
+
const current = findRef.current;
|
|
151
|
+
if (event.key === 'Escape') {
|
|
152
|
+
if (paletteOpen) {
|
|
153
|
+
event.preventDefault();
|
|
154
|
+
event.stopPropagation();
|
|
155
|
+
setPaletteOpen(false);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (!current.open) return;
|
|
159
|
+
event.preventDefault();
|
|
160
|
+
event.stopPropagation();
|
|
161
|
+
current.close();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
// ⌘⇧P opens the palette — the one chord that is not an editor command.
|
|
165
|
+
if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.code === 'KeyP') {
|
|
166
|
+
event.preventDefault();
|
|
167
|
+
event.stopPropagation();
|
|
168
|
+
setPaletteOpen(true);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
// While the bar is open, an unmodified key in one of ITS fields belongs to
|
|
172
|
+
// the field — but F3 and the ⌘-chords below still dispatch, so stepping to
|
|
173
|
+
// the next match from inside the find field works.
|
|
174
|
+
const command = resolveBinding(EDITOR_KEY_BINDINGS, event);
|
|
175
|
+
if (!command || !isFindBarCommand(command)) return;
|
|
176
|
+
event.preventDefault();
|
|
177
|
+
event.stopPropagation();
|
|
178
|
+
runCommand(command);
|
|
179
|
+
};
|
|
180
|
+
|
|
87
181
|
if (openedFiles.length === 0) {
|
|
88
182
|
return (
|
|
89
183
|
<div className={`flex flex-col h-full bg-canvas items-center justify-center text-ink/40 ${className}`}>
|
|
@@ -96,14 +190,13 @@ export function Editor({
|
|
|
96
190
|
const currentContent = editor.content;
|
|
97
191
|
const isMd = activeFile?.name.endsWith('.md');
|
|
98
192
|
const isPreview = activeFile ? previewMode[activeFile.id] : false;
|
|
99
|
-
const toolbarSaveStatus = editor.saveStatus === 'error' ? 'idle' : editor.saveStatus;
|
|
100
193
|
|
|
101
194
|
const editorContainerClass = isMaximized
|
|
102
195
|
? 'fixed inset-0 z-50 flex flex-col bg-canvas'
|
|
103
196
|
: `flex flex-col h-full bg-canvas ${className}`;
|
|
104
197
|
|
|
105
198
|
return (
|
|
106
|
-
<div className={editorContainerClass}>
|
|
199
|
+
<div className={editorContainerClass} onKeyDownCapture={handlePanelKeyDown}>
|
|
107
200
|
<EditorTabs
|
|
108
201
|
openedFiles={openedFiles}
|
|
109
202
|
activeFileId={activeFileId}
|
|
@@ -119,11 +212,7 @@ export function Editor({
|
|
|
119
212
|
isStaged={Boolean(activeFile.diffStaged)}
|
|
120
213
|
root={activeFile.root}
|
|
121
214
|
repo={activeFile.repo || '.'}
|
|
122
|
-
onOpenInEditor={() =>
|
|
123
|
-
// Convert diff tab to regular editor tab
|
|
124
|
-
activeFile.isDiff = false;
|
|
125
|
-
onSelectFile(activeFile.id);
|
|
126
|
-
}}
|
|
215
|
+
onOpenInEditor={() => onConvertDiffToEditor(activeFile.id)}
|
|
127
216
|
onFileSaved={onFileSaved}
|
|
128
217
|
className="flex-1"
|
|
129
218
|
/>
|
|
@@ -131,11 +220,16 @@ export function Editor({
|
|
|
131
220
|
<>
|
|
132
221
|
<EditorToolbar
|
|
133
222
|
path={activeFile.path}
|
|
134
|
-
saveStatus={
|
|
223
|
+
saveStatus={editor.saveStatus}
|
|
135
224
|
wordWrap={wordWrap}
|
|
136
225
|
isMd={isMd}
|
|
137
226
|
isPreview={isPreview}
|
|
227
|
+
copied={editor.copied}
|
|
138
228
|
isImage={editor.isImage}
|
|
229
|
+
saveDisabled={Boolean(editor.loadError)}
|
|
230
|
+
isMaximized={isMaximized}
|
|
231
|
+
findOpen={find.open}
|
|
232
|
+
onToggleFind={() => (find.open ? find.close() : find.openFind())}
|
|
139
233
|
onSave={editor.saveNow}
|
|
140
234
|
onTogglePreview={togglePreview}
|
|
141
235
|
onToggleWordWrap={() => setWordWrap(!wordWrap)}
|
|
@@ -151,58 +245,82 @@ export function Editor({
|
|
|
151
245
|
// the scrolling text container — a wheel there would scroll the
|
|
152
246
|
// pane instead of zooming the image.
|
|
153
247
|
<ImageViewer src={editor.imageUrl} name={activeFile.name} className="flex-1 min-h-0" />
|
|
248
|
+
) : editor.isLoading ? (
|
|
249
|
+
<div className="flex-1 flex items-center justify-center gap-2 text-ink/40 text-xs font-mono">
|
|
250
|
+
<Loader2 size={14} className="animate-spin" />
|
|
251
|
+
<span>Loading file content…</span>
|
|
252
|
+
</div>
|
|
253
|
+
) : editor.loadError ? (
|
|
254
|
+
// A read that failed must say so. Without this the pane rendered
|
|
255
|
+
// an empty (or substituted) buffer, and the next save wrote it
|
|
256
|
+
// over the real file.
|
|
257
|
+
<div className="flex-1 flex flex-col items-center justify-center gap-2 px-6 text-center">
|
|
258
|
+
<AlertTriangle size={20} className="text-error" />
|
|
259
|
+
<span className="text-xs text-error font-sans">Could not read {activeFile.name}</span>
|
|
260
|
+
<span className="text-[11px] text-ink/50 font-mono break-all">{editor.loadError}</span>
|
|
261
|
+
</div>
|
|
154
262
|
) : (
|
|
155
|
-
<div
|
|
156
|
-
{
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
263
|
+
<div className="relative flex-1 min-h-0 flex flex-col">
|
|
264
|
+
{find.open ? <FindWidget find={find} /> : null}
|
|
265
|
+
{paletteOpen ? <CommandPalette onRun={runCommand} onClose={() => setPaletteOpen(false)} /> : null}
|
|
266
|
+
<div onScroll={handleScroll} className={`flex-1 overflow-auto bg-paper flex ${scrollbarFadeClass(isScrolling)}`}>
|
|
267
|
+
{(isMd && isPreview) ? (
|
|
268
|
+
// No `prose` wrapper: markdown is styled by `.prose-content`
|
|
269
|
+
// alone (the same system the chat timeline uses). The
|
|
270
|
+
// typography plugin fought it — `.prose img` added 2em
|
|
271
|
+
// vertical margins that ballooned a badge row into its own
|
|
272
|
+
// line box, and `--tw-prose-*` colors ignored the theme.
|
|
273
|
+
<div className="p-6 max-w-4xl mx-auto font-sans flex-1" style={{ fontSize: `${zoomLevel}px` }}>
|
|
274
|
+
<MarkdownRenderer
|
|
275
|
+
content={currentContent}
|
|
276
|
+
document
|
|
277
|
+
scope={{ path: activeFile.path, root: activeFile.root, repo: activeFile.repo }}
|
|
278
|
+
/>
|
|
279
|
+
</div>
|
|
280
|
+
) : (
|
|
281
|
+
<CodeSurface
|
|
282
|
+
ref={surfaceRef}
|
|
283
|
+
value={currentContent}
|
|
284
|
+
onValueChange={editor.onChange}
|
|
285
|
+
language={editor.language}
|
|
286
|
+
wordWrap={wordWrap}
|
|
287
|
+
marks={find.open ? find.matches : undefined}
|
|
288
|
+
currentMark={find.currentIndex}
|
|
289
|
+
occurrences={occurrences}
|
|
290
|
+
onOccurrencesChange={setOccurrences}
|
|
291
|
+
onCommand={runCommand}
|
|
292
|
+
rootClassName="flex"
|
|
293
|
+
gutterClassName="flex flex-col text-right pl-4 pr-3 select-none text-ink/30 font-mono border-r border-ink/10 bg-canvas sticky left-0 z-10"
|
|
294
|
+
gutterStyle={{
|
|
295
|
+
fontSize: zoomLevel,
|
|
296
|
+
paddingTop: 16,
|
|
297
|
+
paddingBottom: 16,
|
|
298
|
+
lineHeight: 1.5,
|
|
299
|
+
fontFamily: '"Fira Code", "JetBrains Mono", "SF Mono", Consolas, monospace',
|
|
300
|
+
}}
|
|
301
|
+
gutterLineClassName="min-w-[1.5rem]"
|
|
302
|
+
// `min-w-max` keeps a long line intact and lets the panel
|
|
303
|
+
// scroll sideways; while wrapping it would instead widen the
|
|
304
|
+
// column past the panel, so the toggle had no effect at all.
|
|
305
|
+
editorWrapperClassName={`flex-1 code-surface ${wordWrap ? 'min-w-0' : 'min-w-max'}`}
|
|
306
|
+
editorPadding={16}
|
|
307
|
+
editorClassName="font-mono focus:outline-none"
|
|
308
|
+
editorStyle={{
|
|
309
|
+
fontFamily: '"Fira Code", "JetBrains Mono", "SF Mono", Consolas, monospace',
|
|
310
|
+
fontSize: zoomLevel,
|
|
311
|
+
lineHeight: 1.5,
|
|
312
|
+
minHeight: '100%',
|
|
313
|
+
// Breathing room under the last line. It has to live on the
|
|
314
|
+
// surface, not on the scroll container: a scroll container's
|
|
315
|
+
// own bottom padding is not part of its scrollable overflow,
|
|
316
|
+
// so `pb-*` on the scroller left the last line flush with the
|
|
317
|
+
// panel's edge (measured: padding-bottom 32px did not change
|
|
318
|
+
// scrollHeight by a single pixel).
|
|
319
|
+
paddingBottom: 16,
|
|
320
|
+
}}
|
|
167
321
|
/>
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
<CodeSurface
|
|
171
|
-
value={currentContent}
|
|
172
|
-
onValueChange={editor.onChange}
|
|
173
|
-
language={editor.language}
|
|
174
|
-
wordWrap={wordWrap}
|
|
175
|
-
rootClassName="flex"
|
|
176
|
-
gutterClassName="flex flex-col text-right pl-4 pr-3 select-none text-ink/30 font-mono border-r border-ink/10 bg-canvas sticky left-0 z-10"
|
|
177
|
-
gutterStyle={{
|
|
178
|
-
fontSize: zoomLevel,
|
|
179
|
-
paddingTop: 16,
|
|
180
|
-
paddingBottom: 16,
|
|
181
|
-
lineHeight: 1.5,
|
|
182
|
-
fontFamily: '"Fira Code", "JetBrains Mono", "SF Mono", Consolas, monospace',
|
|
183
|
-
}}
|
|
184
|
-
gutterLineClassName="min-w-[1.5rem]"
|
|
185
|
-
// `min-w-max` keeps a long line intact and lets the panel
|
|
186
|
-
// scroll sideways; while wrapping it would instead widen the
|
|
187
|
-
// column past the panel, so the toggle had no effect at all.
|
|
188
|
-
editorWrapperClassName={`flex-1 code-surface ${wordWrap ? 'min-w-0' : 'min-w-max'}`}
|
|
189
|
-
editorPadding={16}
|
|
190
|
-
editorClassName="font-mono focus:outline-none"
|
|
191
|
-
editorStyle={{
|
|
192
|
-
fontFamily: '"Fira Code", "JetBrains Mono", "SF Mono", Consolas, monospace',
|
|
193
|
-
fontSize: zoomLevel,
|
|
194
|
-
lineHeight: 1.5,
|
|
195
|
-
minHeight: '100%',
|
|
196
|
-
// Breathing room under the last line. It has to live on the
|
|
197
|
-
// surface, not on the scroll container: a scroll container's
|
|
198
|
-
// own bottom padding is not part of its scrollable overflow,
|
|
199
|
-
// so `pb-*` on the scroller left the last line flush with the
|
|
200
|
-
// panel's edge (measured: padding-bottom 32px did not change
|
|
201
|
-
// scrollHeight by a single pixel).
|
|
202
|
-
paddingBottom: 16,
|
|
203
|
-
}}
|
|
204
|
-
/>
|
|
205
|
-
)}
|
|
322
|
+
)}
|
|
323
|
+
</div>
|
|
206
324
|
</div>
|
|
207
325
|
)}
|
|
208
326
|
</>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createPortal } from 'preact/compat';
|
|
2
2
|
import type { FormEvent, RefObject } from 'preact/compat';
|
|
3
|
-
import { Copy, Edit2, ExternalLink, Eye, GitCompare, History, RefreshCw, Trash2, X } from 'lucide-preact';
|
|
3
|
+
import { Copy, Edit2, ExternalLink, Eye, FilePlus, GitCompare, History, RefreshCw, Trash2, X } from 'lucide-preact';
|
|
4
4
|
|
|
5
5
|
interface ContextMenuProps {
|
|
6
6
|
x: number;
|
|
@@ -13,7 +13,9 @@ interface ContextMenuProps {
|
|
|
13
13
|
|
|
14
14
|
export function FileContextMenu({ x, y, isFolder, hasGitStatus, onAction, menuRef }: ContextMenuProps) {
|
|
15
15
|
const menuWidth = 190;
|
|
16
|
-
|
|
16
|
+
// The row count differs by kind, and the clamp below needs the real height —
|
|
17
|
+
// an under-estimate puts the menu's last rows off-screen at the bottom edge.
|
|
18
|
+
const menuHeight = isFolder ? 250 : hasGitStatus ? 285 : 255;
|
|
17
19
|
|
|
18
20
|
const posX = typeof window !== 'undefined' ? Math.max(10, Math.min(x, window.innerWidth - menuWidth - 12)) : x;
|
|
19
21
|
const posY = typeof window !== 'undefined' ? Math.max(10, Math.min(y, window.innerHeight - menuHeight - 12)) : y;
|
|
@@ -44,6 +46,15 @@ export function FileContextMenu({ x, y, isFolder, hasGitStatus, onAction, menuRe
|
|
|
44
46
|
)}
|
|
45
47
|
</>
|
|
46
48
|
)}
|
|
49
|
+
{isFolder && (
|
|
50
|
+
<>
|
|
51
|
+
<button onClick={() => onAction('new_file')} className="w-full text-left px-3 py-1.5 hover:bg-ink/5 flex items-center space-x-2 transition-colors">
|
|
52
|
+
<FilePlus size={13} className="text-ink/60" />
|
|
53
|
+
<span>New File</span>
|
|
54
|
+
</button>
|
|
55
|
+
<div className="my-1 border-t border-ink/10"></div>
|
|
56
|
+
</>
|
|
57
|
+
)}
|
|
47
58
|
<button onClick={() => onAction('explorer')} className="w-full text-left px-3 py-1.5 hover:bg-ink/5 flex items-center space-x-2 transition-colors">
|
|
48
59
|
<ExternalLink size={13} className="text-ink/60" />
|
|
49
60
|
<span>Go to Explorer</span>
|
|
@@ -77,6 +88,72 @@ export function FileContextMenu({ x, y, isFolder, hasGitStatus, onAction, menuRe
|
|
|
77
88
|
);
|
|
78
89
|
}
|
|
79
90
|
|
|
91
|
+
interface NewFileModalProps {
|
|
92
|
+
/** Folder the file will be created in, relative to the listed root. */
|
|
93
|
+
folderPath: string;
|
|
94
|
+
value: string;
|
|
95
|
+
/** Server's refusal (a name that already exists, a bad character), shown inline. */
|
|
96
|
+
error?: string | null;
|
|
97
|
+
isLoading: boolean;
|
|
98
|
+
onChange: (value: string) => void;
|
|
99
|
+
onCancel: () => void;
|
|
100
|
+
onSubmit: (e: FormEvent) => void;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The "New File" dialog.
|
|
105
|
+
*
|
|
106
|
+
* Only a name is asked for — the folder is the one that was right-clicked, and
|
|
107
|
+
* showing it as a fixed prefix is what makes the field's meaning unambiguous.
|
|
108
|
+
* The input starts empty (no `.txt` default): a name is what the user has in
|
|
109
|
+
* mind, and a prefilled suffix is a character to delete before every keystroke
|
|
110
|
+
* lands. The server's refusal is rendered INLINE rather than as a toast: the
|
|
111
|
+
* field is still open, and "already exists" is a message about what to type
|
|
112
|
+
* next, not a notification about something that happened.
|
|
113
|
+
*/
|
|
114
|
+
export function FileCreateModal({ folderPath, value, error, isLoading, onChange, onCancel, onSubmit }: NewFileModalProps) {
|
|
115
|
+
return createPortal(
|
|
116
|
+
<div className="fixed inset-0 bg-ink/20 z-[100] flex items-center justify-center p-4 backdrop-blur-sm">
|
|
117
|
+
<form onSubmit={onSubmit} className="bg-paper rounded shadow-xl p-4 w-full max-w-sm border border-ink/10 font-sans">
|
|
118
|
+
<h3 className="font-semibold text-ink mb-3 text-sm">New File</h3>
|
|
119
|
+
<div className="flex items-center gap-1 mb-1 text-xs font-mono text-ink/50 min-w-0">
|
|
120
|
+
<span className="truncate" title={folderPath}>{folderPath || '.'}/</span>
|
|
121
|
+
</div>
|
|
122
|
+
<input
|
|
123
|
+
autoFocus
|
|
124
|
+
type="text"
|
|
125
|
+
value={value}
|
|
126
|
+
onChange={e => onChange(e.currentTarget.value)}
|
|
127
|
+
placeholder="file name"
|
|
128
|
+
aria-label="New file name"
|
|
129
|
+
className={`w-full bg-paper border rounded px-2 py-1.5 text-xs font-mono focus:outline-none mb-2 ${
|
|
130
|
+
error ? 'border-error' : 'border-ink/20 focus:border-ink'
|
|
131
|
+
}`}
|
|
132
|
+
/>
|
|
133
|
+
{error && <p className="text-[11px] text-error mb-2">{error}</p>}
|
|
134
|
+
<div className="flex justify-end space-x-2 mt-2">
|
|
135
|
+
<button
|
|
136
|
+
type="button"
|
|
137
|
+
onClick={onCancel}
|
|
138
|
+
className="px-3 py-1.5 text-xs text-ink hover:bg-ink/5 rounded transition-colors"
|
|
139
|
+
disabled={isLoading}
|
|
140
|
+
>
|
|
141
|
+
Cancel
|
|
142
|
+
</button>
|
|
143
|
+
<button
|
|
144
|
+
type="submit"
|
|
145
|
+
className="px-3 py-1.5 text-xs bg-ink text-canvas rounded hover:bg-ink/90 transition-colors flex items-center"
|
|
146
|
+
disabled={isLoading || !value.trim()}
|
|
147
|
+
>
|
|
148
|
+
{isLoading ? 'Creating...' : 'Create'}
|
|
149
|
+
</button>
|
|
150
|
+
</div>
|
|
151
|
+
</form>
|
|
152
|
+
</div>,
|
|
153
|
+
document.body
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
80
157
|
interface DeleteModalProps {
|
|
81
158
|
fileName: string;
|
|
82
159
|
isFolder: boolean;
|