@kabmo-labs/kabmo 0.7.0 → 0.9.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 +7 -0
- package/dist/client/index.html +1 -1
- package/dist/client/index.js +403 -384
- package/dist/client/static/js/{index-0wvbca0b.js → index-bpptfkz2.js} +905 -875
- package/dist/client/static/js/index-k315p1e8.css +1 -0
- package/package.json +10 -1
- package/src/client/components/common/code-surface/documents.test.ts +120 -0
- package/src/client/components/common/code-surface/documents.ts +201 -0
- package/src/client/components/common/code-surface/extensions.test.ts +110 -0
- package/src/client/components/common/code-surface/extensions.ts +162 -0
- package/src/client/components/common/code-surface/handle.ts +102 -0
- package/src/client/components/common/code-surface/index.tsx +253 -314
- package/src/client/components/common/code-surface/keymap.test.ts +133 -0
- package/src/client/components/common/code-surface/keymap.ts +267 -0
- package/src/client/components/common/code-surface/marks.ts +74 -0
- package/src/client/components/common/code-surface/shiki.ts +234 -0
- package/src/client/components/common/code-surface/sync.test.ts +185 -0
- package/src/client/components/common/code-surface/sync.ts +58 -0
- package/src/client/components/common/code-surface/theme.test.ts +202 -0
- package/src/client/components/common/code-surface/theme.ts +111 -0
- package/src/client/components/common/editor-view-buttons/index.test.tsx +148 -0
- package/src/client/components/common/editor-view-buttons/index.tsx +109 -0
- package/src/client/components/common/header-controls/index.tsx +24 -1
- package/src/client/components/common/header-controls/shell-chrome.test.tsx +272 -0
- package/src/client/components/common/shell-update-button/Modal.tsx +163 -0
- package/src/client/components/common/shell-update-button/index.test.tsx +251 -0
- package/src/client/components/common/shell-update-button/index.tsx +125 -0
- package/src/client/components/common/toolbar-row/index.test.tsx +166 -0
- package/src/client/components/common/toolbar-row/index.tsx +189 -0
- package/src/client/components/common/workspace-options-menu/index.tsx +19 -1
- package/src/client/components/layout/desktop-layout/TopNavbar.tsx +5 -6
- package/src/client/components/layout/desktop-layout/WorkspacePanels.tsx +41 -78
- package/src/client/components/layout/desktop-layout/index.tsx +55 -69
- package/src/client/components/layout/desktop-layout/resizer/index.tsx +6 -3
- package/src/client/components/layout/session-sidebar/CategoryHeader.tsx +24 -0
- package/src/client/components/layout/session-sidebar/CategoryHeader.worktree.test.tsx +126 -0
- package/src/client/components/layout/session-sidebar/CategoryItem.tsx +4 -0
- package/src/client/components/layout/session-sidebar/Footer.tsx +8 -15
- package/src/client/components/layout/session-sidebar/SessionItem.tsx +12 -0
- package/src/client/components/layout/session-sidebar/SessionList.tsx +4 -0
- package/src/client/components/layout/session-sidebar/SessionsView.tsx +24 -0
- package/src/client/components/layout/session-sidebar/WorktreeBadge.test.ts +48 -0
- package/src/client/components/layout/session-sidebar/WorktreeBadge.tsx +55 -0
- package/src/client/components/mobile/LayoutWrapper.tsx +23 -16
- package/src/client/components/mobile/RightSidebar.tsx +21 -10
- package/src/client/components/mobile/SessionSidebar.tsx +8 -0
- package/src/client/components/mobile/header-titlebar.test.tsx +172 -0
- package/src/client/components/mobile/mobile-main-view/Header.tsx +10 -1
- package/src/client/components/mobile/mobile-right-sidebar/FullDiff.tsx +3 -0
- package/src/client/components/mobile/mobile-right-sidebar/FullEditor.tsx +63 -38
- package/src/client/components/mobile/mobile-right-sidebar/OutlineButton.test.tsx +137 -0
- package/src/client/components/mobile/mobile-right-sidebar/OutlineButton.tsx +14 -3
- package/src/client/components/mobile/mobile-session-sidebar/Footer.tsx +9 -21
- package/src/client/components/mobile/mobile-session-sidebar/Header.tsx +6 -2
- package/src/client/components/mobile/mobile-session-sidebar/Item.tsx +16 -0
- package/src/client/components/mobile/mobile-session-sidebar/List.tsx +4 -0
- package/src/client/components/mobile/mobile-session-sidebar/SessionRow.tsx +5 -0
- package/src/client/components/mobile/mobile-session-sidebar/WorktreeDialog.tsx +49 -0
- package/src/client/components/mobile/worktree-session.ts +51 -0
- package/src/client/components/settings/Modal.tsx +8 -0
- package/src/client/components/settings/categories/AppearanceSettings.tsx +3 -0
- package/src/client/components/settings/categories/ProjectSettings.tsx +22 -8
- package/src/client/components/settings/categories/appearance-settings/EditorThemeSection.test.tsx +167 -0
- package/src/client/components/settings/categories/appearance-settings/EditorThemeSection.tsx +184 -0
- package/src/client/components/settings/categories/project-settings/WorktreeSettings.tsx +146 -0
- package/src/client/components/settings/categories/project-settings/worktree-row.test.ts +83 -0
- package/src/client/components/settings/categories/project-settings/worktree-row.ts +47 -0
- package/src/client/components/workspace/chat-timeline/EmptyWorkspacePrompt.tsx +36 -55
- package/src/client/components/workspace/chat-timeline/PendingChatView.tsx +8 -0
- package/src/client/components/workspace/chat-timeline/SystemNotice.tsx +9 -4
- package/src/client/components/workspace/chat-timeline/TaskResultContent.tsx +9 -7
- package/src/client/components/workspace/chat-timeline/index.tsx +5 -0
- package/src/client/components/workspace/chat-timeline/target-picker/WorkspacePicker.test.tsx +132 -0
- package/src/client/components/workspace/chat-timeline/target-picker/WorkspacePicker.tsx +120 -0
- package/src/client/components/workspace/chat-timeline/target-picker/WorktreePicker.test.tsx +319 -0
- package/src/client/components/workspace/chat-timeline/target-picker/WorktreePicker.tsx +285 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/index.test.ts +2 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/hashline-patch/index.tsx +34 -29
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/AstEdit.tsx +11 -9
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Bash.tsx +10 -7
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Edit.tsx +25 -8
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Eval.test.tsx +146 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Eval.tsx +32 -17
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/FileMatches.test.ts +114 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/FileMatches.tsx +111 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Read.tsx +7 -7
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/SearchTool.tsx +9 -59
- package/src/client/components/workspace/chat-timeline/tool-renderers/shared/ArtifactBody.tsx +2 -7
- package/src/client/components/workspace/chat-timeline/tool-renderers/shared/DiffView.test.ts +27 -28
- package/src/client/components/workspace/chat-timeline/tool-renderers/shared/DiffView.tsx +39 -26
- package/src/client/components/workspace/chat-timeline/tool-renderers/shared/ExcerptCode.tsx +8 -8
- package/src/client/components/workspace/chat-timeline/tool-renderers/shared/HighlightedCode.tsx +35 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/shared/JsonCodeBlock.tsx +4 -4
- package/src/client/components/workspace/diff-panel/MergeDiffView.tsx +179 -0
- package/src/client/components/workspace/diff-panel/ThemedMergeDiffView.tsx +24 -0
- package/src/client/components/workspace/diff-panel/Toolbar.tsx +224 -186
- package/src/client/components/workspace/diff-panel/ToolbarSegments.tsx +98 -0
- package/src/client/components/workspace/diff-panel/index.tsx +76 -145
- package/src/client/components/workspace/diff-panel/merge.ts +315 -0
- package/src/client/components/workspace/editor/Body.tsx +4 -7
- package/src/client/components/workspace/editor/CommandPalette.tsx +2 -2
- package/src/client/components/workspace/editor/FileDocument.tsx +58 -63
- package/src/client/components/workspace/editor/FilePane.tsx +4 -7
- package/src/client/components/workspace/editor/FindWidget.tsx +7 -2
- package/src/client/components/workspace/editor/Toolbar.tsx +220 -123
- package/src/client/components/workspace/editor/index.tsx +7 -9
- package/src/client/components/workspace/file-explorer/ContextMenu.tsx +2 -1
- package/src/client/components/workspace/file-explorer/Modals.tsx +40 -12
- package/src/client/components/workspace/file-explorer/OutlineSection.test.tsx +18 -0
- package/src/client/components/workspace/file-explorer/OutlineSection.tsx +10 -3
- package/src/client/components/workspace/file-explorer/RootMenu.tsx +14 -5
- package/src/client/components/workspace/file-explorer/Toolbar.tsx +74 -0
- package/src/client/components/workspace/file-explorer/TreeItem.tsx +38 -3
- package/src/client/components/workspace/file-explorer/filter.ts +47 -0
- package/src/client/components/workspace/file-explorer/index.tsx +77 -64
- package/src/client/components/workspace/file-explorer/local-edit.test.ts +135 -0
- package/src/client/components/workspace/search-panel/Body.tsx +1 -1
- package/src/client/components/workspace/worktree-modal/index.test.tsx +118 -0
- package/src/client/components/workspace/worktree-modal/index.tsx +261 -0
- package/src/client/hooks/chat/omp/reconnect.test.ts +338 -0
- package/src/client/hooks/chat/omp/reconnect.ts +187 -0
- package/src/client/hooks/chat/omp/run-liveness.test.ts +71 -0
- package/src/client/hooks/chat/omp/run-liveness.ts +65 -0
- package/src/client/hooks/chat/omp/session-list.activity.test.ts +114 -0
- package/src/client/hooks/chat/omp/session-list.test-util.ts +164 -0
- package/src/client/hooks/chat/omp/session-list.test.ts +30 -150
- package/src/client/hooks/chat/omp/session-list.tsx +69 -9
- package/src/client/hooks/chat/timeline/generating.ts +19 -2
- package/src/client/hooks/chat/timeline/index.ts +27 -28
- package/src/client/hooks/chat/timeline/lost-run.test.ts +329 -0
- package/src/client/hooks/chat/timeline/lost-run.ts +177 -0
- package/src/client/hooks/chat/timeline/send-execute.test.ts +2 -0
- package/src/client/hooks/chat/timeline/send-steer.test.ts +2 -0
- package/src/client/hooks/chat/timeline/send.test.ts +2 -0
- package/src/client/hooks/chat/timeline/send.ts +11 -1
- package/src/client/hooks/chat/timeline/session-load.test-util.ts +205 -0
- package/src/client/hooks/chat/timeline/session-load.test.ts +61 -162
- package/src/client/hooks/chat/timeline/session-load.ts +69 -37
- package/src/client/hooks/chat/timeline/session-switch.test.ts +65 -0
- package/src/client/hooks/chat/timeline/session-switch.ts +63 -0
- package/src/client/hooks/chat/timeline/spawn-target.ts +52 -0
- package/src/client/hooks/chat/timeline/tool-open.test.ts +74 -0
- package/src/client/hooks/chat/timeline/tool-open.ts +52 -8
- package/src/client/hooks/code/deferred-highlight.test.ts +182 -0
- package/src/client/hooks/code/deferred-highlight.ts +129 -0
- package/src/client/hooks/editor/find-state.ts +1 -1
- package/src/client/hooks/editor/theme.test.ts +206 -0
- package/src/client/hooks/editor/theme.ts +82 -0
- package/src/client/hooks/editor/use-editor-drop.test.ts +101 -0
- package/src/client/hooks/editor/use-editor-drop.ts +67 -14
- package/src/client/hooks/editor/use-editor-find.ts +7 -7
- package/src/client/hooks/editor/use-outline-view.test.ts +182 -0
- package/src/client/hooks/editor/use-outline-view.ts +94 -7
- package/src/client/hooks/editor/use-scroll-restore.test.ts +55 -2
- package/src/client/hooks/editor/use-scroll-restore.ts +124 -4
- package/src/client/hooks/editor/use-search-reveal.ts +1 -1
- package/src/client/hooks/ui/long-press.test.ts +174 -0
- package/src/client/hooks/ui/long-press.ts +112 -0
- package/src/client/hooks/ui/settings-modal.ts +56 -0
- package/src/client/hooks/ui/updates/shell-download.test.ts +75 -0
- package/src/client/hooks/ui/updates/shell-download.ts +153 -28
- package/src/client/hooks/ui/updates/shell-offer.test.ts +213 -0
- package/src/client/hooks/ui/updates/shell-update.test.ts +140 -0
- package/src/client/hooks/ui/updates/shell-update.ts +119 -0
- package/src/client/hooks/workspace/diff-file.ts +29 -10
- package/src/client/hooks/workspace/diff-git-actions.ts +147 -0
- package/src/client/hooks/workspace/editor-view.ts +61 -0
- package/src/client/hooks/workspace/file-create.ts +41 -11
- package/src/client/hooks/workspace/file-listing.ts +56 -1
- package/src/client/hooks/workspace/file-tabs.test.ts +62 -5
- package/src/client/hooks/workspace/file-tabs.ts +24 -11
- package/src/client/hooks/workspace/file-tree-actions.ts +37 -5
- package/src/client/hooks/workspace/navbar-session-actions.ts +72 -0
- package/src/client/hooks/workspace/worktree/index.ts +165 -0
- package/src/client/lib/desktop/bridge.ts +45 -0
- package/src/client/lib/search-reveal.ts +1 -1
- package/src/client/main.tsx +5 -1
- package/src/client/tailwind.css +56 -8
- package/src/server/index.ts +7 -2
- package/src/server/lib/auth/guard.test.ts +28 -0
- package/src/server/lib/auth/guard.ts +10 -0
- package/src/server/lib/bundler/titlebar-css.test.ts +42 -1
- package/src/server/lib/db/schema.ts +4 -0
- package/src/server/lib/db/worktree-registry.server.ts +91 -0
- package/src/server/lib/fs/canonical.ts +52 -0
- package/src/server/lib/fs/relative-path.ts +48 -0
- package/src/server/lib/fs/root-worktree.test.ts +116 -0
- package/src/server/lib/fs/root.ts +39 -10
- package/src/server/lib/fs/worktree/carry.ts +134 -0
- package/src/server/lib/fs/worktree/create.test.ts +249 -0
- package/src/server/lib/fs/worktree/create.ts +262 -0
- package/src/server/lib/fs/worktree/errors.ts +43 -0
- package/src/server/lib/fs/worktree/list.test.ts +207 -0
- package/src/server/lib/fs/worktree/list.ts +142 -0
- package/src/server/lib/fs/worktree/location.test.ts +206 -0
- package/src/server/lib/fs/worktree/location.ts +114 -0
- package/src/server/lib/fs/worktree/project-id.ts +53 -0
- package/src/server/lib/fs/worktree/remove.test.ts +242 -0
- package/src/server/lib/fs/worktree/remove.ts +152 -0
- package/src/server/lib/fs/worktree/settings.server.ts +56 -0
- package/src/server/lib/fs/worktree/topology.ts +142 -0
- package/src/server/lib/lifecycle/claim-port.test.ts +156 -0
- package/src/server/lib/lifecycle/port-guard.test.ts +20 -164
- package/src/server/lib/lifecycle/port-guard.ts +39 -7
- package/src/server/lib/lifecycle/probe-health.test.ts +166 -0
- package/src/server/lib/lifecycle/probe.test.ts +77 -164
- package/src/server/lib/lifecycle/probe.ts +121 -13
- package/src/server/lib/omp/rpc/frame-fold-signals.test.ts +112 -0
- package/src/server/lib/omp/rpc/frame-fold.ts +10 -5
- package/src/server/lib/omp/session/sidebar-activity.server.test.ts +162 -0
- package/src/server/lib/omp/session/sidebar-activity.server.ts +70 -0
- package/src/server/lib/omp/session/sidebar-data.canonical.test.ts +155 -0
- package/src/server/lib/omp/session/sidebar-data.server.ts +28 -1
- package/src/server/lib/omp/session/telemetry/compute.ts +13 -1
- package/src/server/lib/omp/session/telemetry.test.ts +33 -0
- package/src/server/lib/panels/local-edits.server.ts +33 -0
- package/src/server/lib/panels/sync.fixture.test-util.ts +92 -0
- package/src/server/lib/panels/sync.local-edits.test.ts +150 -0
- package/src/server/lib/panels/sync.server.test.ts +125 -147
- package/src/server/lib/panels/sync.server.ts +153 -48
- package/src/server/lib/realtime/signals.server.ts +10 -0
- package/src/server/lib/realtime/topics/catalog.server.ts +4 -1
- package/src/server/lib/realtime/topics.server.ts +16 -0
- package/src/server/lib/updates/shell-assets.test.ts +114 -0
- package/src/server/lib/updates/shell-assets.ts +200 -0
- package/src/server/lib/updates/shell-cache.ts +105 -0
- package/src/server/lib/updates/shell-pages.test.ts +187 -0
- package/src/server/lib/updates/shell-pages.ts +157 -0
- package/src/server/lib/updates/shell-release-desktop.test.ts +166 -0
- package/src/server/lib/updates/shell-release.test.ts +59 -71
- package/src/server/lib/updates/shell-release.ts +178 -198
- package/src/server/routes/fs/action-create.test.ts +18 -13
- package/src/server/routes/fs/action-nested.test.ts +184 -0
- package/src/server/routes/fs/action.ts +47 -16
- package/src/server/routes/fs/git-merge.test.ts +11 -1
- package/src/server/routes/fs/read.test.ts +24 -7
- package/src/server/routes/fs/scope.test.ts +15 -10
- package/src/server/routes/fs/scope.ts +3 -14
- package/src/server/routes/index.ts +2 -0
- package/src/server/routes/updates/android.ts +6 -1
- package/src/server/routes/updates/desktop.ts +59 -0
- package/src/server/routes/updates/index.ts +2 -0
- package/src/server/routes/updates/ios.ts +5 -1
- package/src/server/routes/worktrees/create.ts +138 -0
- package/src/server/routes/worktrees/index.ts +25 -0
- package/src/server/routes/worktrees/list.ts +134 -0
- package/src/server/routes/worktrees/remove.ts +102 -0
- package/src/server/routes/worktrees/routes.test.ts +326 -0
- package/src/server/routes/worktrees/shared.ts +90 -0
- package/src/shared/lib/chat/timeline/omp-callbacks.test.ts +36 -0
- package/src/shared/lib/chat/timeline/omp-callbacks.ts +18 -0
- package/src/shared/lib/chat/timeline/run-signals.test.ts +2 -0
- package/src/shared/lib/code/editor/comment-syntax.test.ts +37 -0
- package/src/shared/lib/code/editor/comment-syntax.ts +55 -0
- package/src/shared/lib/code/editor/find-replace.test.ts +134 -0
- package/src/shared/lib/code/editor/{find.ts → find-replace.ts} +23 -107
- package/src/shared/lib/code/editor/{find.test.ts → find-scan.test.ts} +19 -73
- package/src/shared/lib/code/editor/find-scan.ts +177 -0
- package/src/shared/lib/code/editor/keymap.ts +3 -3
- package/src/shared/lib/code/editor/lines.test.ts +7 -55
- package/src/shared/lib/code/editor/lines.ts +8 -48
- package/src/shared/lib/code/editor/marks.test.ts +4 -5
- package/src/shared/lib/code/editor/marks.ts +13 -25
- package/src/shared/lib/code/editor/theme-loader.ts +97 -0
- package/src/shared/lib/code/editor/themes.generated.ts +156 -0
- package/src/shared/lib/code/editor/themes.test.ts +165 -0
- package/src/shared/lib/code/editor/themes.ts +165 -0
- package/src/shared/lib/code/highlight-memo.test.ts +103 -0
- package/src/shared/lib/code/highlight-memo.ts +149 -0
- package/src/shared/lib/code/highlighter-warmup.ts +112 -0
- package/src/shared/lib/code/highlighter.ts +3 -1
- package/src/shared/lib/code/idle.ts +37 -0
- package/src/shared/lib/code/outline/rows.test.ts +96 -0
- package/src/shared/lib/code/outline/rows.ts +94 -0
- package/src/shared/lib/code/syntax-highlight.test.ts +1 -79
- package/src/shared/lib/code/syntax-highlight.ts +2 -3
- package/src/shared/lib/fs/file-kind-classify.test.ts +25 -11
- package/src/shared/lib/fs/file-kind.ts +47 -13
- package/src/shared/lib/fs/file-tree.test.ts +183 -0
- package/src/shared/lib/fs/file-tree.ts +159 -0
- package/src/shared/lib/fs/file-url.test.ts +52 -0
- package/src/shared/lib/fs/file-url.ts +37 -0
- package/src/shared/lib/fs/patch-sides.test.ts +114 -0
- package/src/shared/lib/fs/patch-sides.ts +139 -0
- package/src/shared/lib/fs/paths.test.ts +24 -1
- package/src/shared/lib/fs/paths.ts +17 -0
- package/src/shared/lib/fs/worktree/settings-keys.ts +18 -0
- package/src/shared/lib/omp/session/prewarm.ts +7 -0
- package/src/shared/lib/realtime/protocol.ts +16 -0
- package/src/shared/lib/ui/toolbar-overflow.test.ts +83 -0
- package/src/shared/lib/ui/toolbar-overflow.ts +86 -0
- package/src/shared/lib/updates/shell-arch.ts +54 -0
- package/src/shared/lib/updates/shell-ua.ts +24 -4
- package/src/shared/lib/workspace/active-project.ts +23 -0
- package/src/shared/lib/workspace/editor-view-mode.test.ts +70 -0
- package/src/shared/lib/workspace/editor-view-mode.ts +48 -0
- package/src/shared/lib/workspace/panel-widths.test.ts +30 -0
- package/src/shared/lib/workspace/panel-widths.ts +25 -5
- package/src/shared/lib/workspace/session-state/cache.ts +159 -0
- package/src/shared/lib/workspace/session-state/harness.test-util.ts +55 -0
- package/src/shared/lib/workspace/session-state/merge.ts +100 -0
- package/src/shared/lib/workspace/session-state/store-load.test.ts +206 -0
- package/src/shared/lib/workspace/session-state/store.test.ts +17 -139
- package/src/shared/lib/workspace/session-state/store.ts +92 -130
- package/src/shared/lib/workspace/workspace-modules.test.ts +49 -1
- package/src/shared/lib/workspace/workspace-panel-widths.ts +124 -0
- package/src/shared/lib/workspace/worktree/branch-name.test.ts +95 -0
- package/src/shared/lib/workspace/worktree/branch-name.ts +115 -0
- package/src/shared/lib/workspace/worktree/types.ts +104 -0
- package/src/shared/types/fs.ts +10 -0
- package/src/shared/types/realtime.ts +29 -0
- package/src/shared/types/settings/state.ts +10 -0
- package/src/shared/types/workspace.ts +14 -0
- package/src/test-support/file-explorer-disk.test-util.ts +76 -0
- package/src/test-support/file-explorer.test-util.ts +71 -40
- package/src/test-support/loopback.ts +183 -0
- package/dist/client/static/js/index-nzyjf92m.css +0 -1
- package/src/client/components/common/code-editor/handler.test.ts +0 -274
- package/src/client/components/common/code-editor/handler.ts +0 -175
- package/src/client/components/common/code-editor/index.tsx +0 -314
- package/src/client/components/common/code-surface/measure.ts +0 -148
- package/src/client/components/workspace/diff-panel/SplitView.tsx +0 -209
- package/src/client/components/workspace/diff-panel/UnifiedView.tsx +0 -150
- package/src/client/hooks/editor/scroll-container.test.ts +0 -102
- package/src/client/hooks/editor/scroll-container.ts +0 -26
- package/src/client/hooks/editor/use-code-window.ts +0 -100
- package/src/client/hooks/editor/use-editor-history.test.ts +0 -216
- package/src/client/hooks/editor/use-editor-history.ts +0 -134
- package/src/client/hooks/editor/use-occurrences.test.ts +0 -236
- package/src/client/hooks/editor/use-occurrences.ts +0 -98
- package/src/shared/lib/code/editor/commands.test.ts +0 -155
- package/src/shared/lib/code/editor/commands.ts +0 -260
- package/src/shared/lib/code/editor/edits.test.ts +0 -100
- package/src/shared/lib/code/editor/edits.ts +0 -122
- package/src/shared/lib/code/editor/history.test.ts +0 -118
- package/src/shared/lib/code/editor/history.ts +0 -101
- package/src/shared/lib/code/editor/layers.test.ts +0 -77
- package/src/shared/lib/code/editor/layers.ts +0 -67
- package/src/shared/lib/code/editor/line-edits.test.ts +0 -131
- package/src/shared/lib/code/editor/line-edits.ts +0 -159
- package/src/shared/lib/code/editor/occurrences.test.ts +0 -117
- package/src/shared/lib/code/editor/occurrences.ts +0 -111
- package/src/shared/lib/code/editor/reset-css.test.ts +0 -35
- package/src/shared/lib/code/editor/reset-css.ts +0 -32
- package/src/shared/lib/code/editor/text-commands.test.ts +0 -178
- package/src/shared/lib/code/editor/text-commands.ts +0 -154
- package/src/shared/lib/code/lazy-window.test.ts +0 -161
- package/src/shared/lib/code/lazy-window.ts +0 -156
- package/src/shared/lib/code/windowed-highlight.ts +0 -114
- package/src/shared/lib/fs/diff-parser.test.ts +0 -116
- package/src/shared/lib/fs/diff-parser.ts +0 -213
|
@@ -1,348 +1,287 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The code surface: CodeMirror 6, coloured by Shiki.
|
|
8
|
+
*
|
|
9
|
+
* What this replaced, and why it is a mount rather than a component tree: the
|
|
10
|
+
* old surface was a transparent `<textarea>` over a Shiki-highlighted `<pre>`,
|
|
11
|
+
* with Kabmo's own line windowing, wrap measurement, undo stack, multi-cursor
|
|
12
|
+
* replication, find matcher and gutter geometry. Typing on a 4 800-line file
|
|
13
|
+
* cost a measured 59.6 ms median, because every keystroke re-tokenized the
|
|
14
|
+
* visible window and replaced the whole highlight layer's HTML; CM6 costs
|
|
15
|
+
* 2.1 ms and the figure is flat from 8 000 to 20 000 lines, because it renders
|
|
16
|
+
* and tokenizes only its viewport.
|
|
17
|
+
*
|
|
18
|
+
* Two decisions are load-bearing and both are stated elsewhere: Shiki stays the
|
|
19
|
+
* tokenizer (`shiki.ts` — the app has one palette, and a lezer build would cover
|
|
20
|
+
* 48 of the 59 ids `shiki-langs` knows), and the handle's contract is unchanged
|
|
21
|
+
* (`CodeSurfaceHandle`), which is what keeps the find widget, the outline jump,
|
|
22
|
+
* the line jump and the search reveal working without knowing about CM6.
|
|
23
|
+
*
|
|
24
|
+
* The host is a BOUNDED box. Measured: an unconstrained host grows to the
|
|
25
|
+
* document's height (363 758 px at 20 000 lines), and then the scroll geometry,
|
|
26
|
+
* the gutter and the restore path all become the outer scroller's problem. CM6
|
|
27
|
+
* owns its own scroller, and it hands that element to its owner through
|
|
28
|
+
* `onScroller` (a `ref` cannot reach a node the component does not render, and
|
|
29
|
+
* the restore has to re-arm when the view mounts).
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { useImperativeHandle, useLayoutEffect, useRef } from 'preact/hooks';
|
|
2
33
|
import { forwardRef } from 'preact/compat';
|
|
3
34
|
import type { CSSProperties } from 'preact';
|
|
35
|
+
import { EditorView } from '@codemirror/view';
|
|
4
36
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
measuredGeometry,
|
|
12
|
-
shouldWindow,
|
|
13
|
-
uniformGeometry,
|
|
14
|
-
type LineGeometry,
|
|
15
|
-
type LineWindow,
|
|
16
|
-
} from '@/shared/lib/code/lazy-window';
|
|
17
|
-
import type { FindMatch } from '@/shared/lib/code/editor/find';
|
|
37
|
+
import { createDocumentCache, type DocumentCache } from '@/client/components/common/code-surface/documents';
|
|
38
|
+
import type { ShikiThemeNames } from '@/client/components/common/code-surface/shiki';
|
|
39
|
+
import { SHIKI_THEMES } from '@/shared/lib/code/shiki-themes';
|
|
40
|
+
import { createSurfaceHandle, type CodeSurfaceHandle } from '@/client/components/common/code-surface/handle';
|
|
41
|
+
import { setFindMarks, type MarkRange } from '@/client/components/common/code-surface/marks';
|
|
42
|
+
import { syncExternal } from '@/client/components/common/code-surface/sync';
|
|
18
43
|
import type { EditorCommand } from '@/shared/lib/code/editor/keymap';
|
|
19
|
-
import { lineIndexAt } from '@/shared/lib/code/editor/lines';
|
|
20
|
-
import type { TextRange } from '@/shared/lib/code/editor/commands';
|
|
21
|
-
import { countLines } from '@/shared/lib/code/editor/lines';
|
|
22
|
-
import { highlightCode } from '@/shared/lib/code/syntax-highlight';
|
|
23
|
-
import { highlightCodeWindow } from '@/shared/lib/code/windowed-highlight';
|
|
24
|
-
|
|
25
|
-
const WRAP_ON = '!whitespace-pre-wrap !break-words';
|
|
26
|
-
const WRAP_OFF = '!whitespace-pre !break-normal';
|
|
27
44
|
|
|
28
|
-
export
|
|
29
|
-
/** Current selection offsets in the buffer, or null before the field mounts. */
|
|
30
|
-
getSelection(): { start: number; end: number } | null;
|
|
31
|
-
/** Select `[start, end)`; `focus` moves keyboard focus into the editor. */
|
|
32
|
-
select(start: number, end: number, focus?: boolean): void;
|
|
33
|
-
/** Replace the buffer as one undoable edit, caret at `[caretStart, caretEnd)`, focus unchanged. */
|
|
34
|
-
applyDocument(value: string, caretStart: number, caretEnd: number): void;
|
|
35
|
-
/** Run an editor command by name (the palette's path into the editor). */
|
|
36
|
-
runCommand(command: EditorCommand): void;
|
|
37
|
-
/**
|
|
38
|
-
* Bring the line holding `offset` into view WITHOUT moving the caret — what
|
|
39
|
-
* "step to the next match" needs: the user's focus stays in the find field
|
|
40
|
-
* while the match scrolls under it.
|
|
41
|
-
*/
|
|
42
|
-
revealOffset(offset: number): void;
|
|
43
|
-
}
|
|
45
|
+
export type { CodeSurfaceHandle } from '@/client/components/common/code-surface/handle';
|
|
44
46
|
|
|
45
|
-
interface CodeSurfaceProps {
|
|
47
|
+
export interface CodeSurfaceProps {
|
|
46
48
|
value: string;
|
|
47
49
|
onValueChange: (value: string) => void;
|
|
48
50
|
/** Shiki language id, already resolved from the file name. */
|
|
49
51
|
language: string;
|
|
50
52
|
wordWrap: boolean;
|
|
53
|
+
/** Which cached `EditorState` this document owns — a tab switch applies another. */
|
|
54
|
+
documentKey: string;
|
|
51
55
|
/** Find matches (document offsets) to paint; the current one is marked. */
|
|
52
|
-
marks?: readonly
|
|
56
|
+
marks?: readonly MarkRange[];
|
|
53
57
|
currentMark?: number;
|
|
54
|
-
/**
|
|
55
|
-
* Extra ranges the ⌘D workflow has selected. The textarea can only hold one
|
|
56
|
-
* selection, so the surface paints the others from here — and it must, or
|
|
57
|
-
* ⌘D would look like it did nothing.
|
|
58
|
-
*/
|
|
59
|
-
occurrences?: readonly TextRange[];
|
|
60
58
|
/** Commands the editor does not own (find bar chords, word wrap, save). */
|
|
61
59
|
onCommand?: (command: EditorCommand) => void;
|
|
62
|
-
/**
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
*
|
|
91
|
-
* `align-self: flex-start` is what escapes the stretch; `min-height: 100%` is
|
|
92
|
-
* the floor that keeps a SHORT document's gutter full-height. Together they
|
|
93
|
-
* give the box the height the phone's layout already had (`min-h-full` inside a
|
|
94
|
-
* non-flex scroller), so the two layouts no longer differ here — and the rule
|
|
95
|
-
* lives in the component, because it is a property of the surface, not of a
|
|
96
|
-
* caller's chrome. `align-self` is inert outside a flex parent.
|
|
97
|
-
*/
|
|
98
|
-
const ROOT_STYLE: CSSProperties = { alignSelf: 'flex-start', minHeight: '100%' };
|
|
99
|
-
|
|
100
|
-
/** Same measurement, so a re-render does not resize every gutter row. */
|
|
101
|
-
function sameWrappedLines(previous: WrappedLines | null, next: WrappedLines | null): boolean {
|
|
102
|
-
if (previous === next) return true;
|
|
103
|
-
if (!previous || !next || previous.rowHeight !== next.rowHeight) return false;
|
|
104
|
-
return (
|
|
105
|
-
previous.heights.length === next.heights.length &&
|
|
106
|
-
previous.heights.every((height, index) => height === next.heights[index])
|
|
107
|
-
);
|
|
60
|
+
/** A viewer: both `readOnly` and `editable(false)`, so nothing can write. */
|
|
61
|
+
readOnly?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* The editor THEME, as CSS custom properties (`editorThemeVars`).
|
|
64
|
+
*
|
|
65
|
+
* Written on the host element, so the chrome repaints through CSS and a theme
|
|
66
|
+
* switch costs one attribute write. Empty/absent means `auto`: every
|
|
67
|
+
* declaration in `theme.ts` falls back to the app palette.
|
|
68
|
+
*/
|
|
69
|
+
themeVars?: Record<string, string>;
|
|
70
|
+
/** The two Shiki theme names to tokenize with (the same theme twice when the
|
|
71
|
+
* editor theme is explicit). Defaults to the app's dual pair. */
|
|
72
|
+
themes?: ShikiThemeNames;
|
|
73
|
+
/** Font family / size / line height (the zoom control and the font setting). */
|
|
74
|
+
style?: CSSProperties;
|
|
75
|
+
/** Padding around the document, in px. */
|
|
76
|
+
padding?: number;
|
|
77
|
+
/** `data-testid` for the scroller, so the e2e layer can address it. */
|
|
78
|
+
scrollTestId?: string;
|
|
79
|
+
/**
|
|
80
|
+
* The element that actually scrolls, reported on mount and cleared on unmount.
|
|
81
|
+
*
|
|
82
|
+
* It exists only once the view does, and it is CodeMirror's (`view.scrollDOM`)
|
|
83
|
+
* — a caller that needs to remember the reader's position (`useScrollRestore`)
|
|
84
|
+
* cannot reach it through a `ref`, so the surface hands it over.
|
|
85
|
+
*/
|
|
86
|
+
onScroller?: (element: HTMLElement | null) => void;
|
|
87
|
+
className?: string;
|
|
108
88
|
}
|
|
109
89
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
90
|
+
export const CodeSurface = forwardRef<CodeSurfaceHandle, CodeSurfaceProps>(function CodeSurface(
|
|
91
|
+
{
|
|
92
|
+
value,
|
|
93
|
+
onValueChange,
|
|
94
|
+
language,
|
|
95
|
+
wordWrap,
|
|
96
|
+
documentKey,
|
|
97
|
+
marks,
|
|
98
|
+
currentMark,
|
|
99
|
+
onCommand,
|
|
100
|
+
readOnly = false,
|
|
101
|
+
themeVars,
|
|
102
|
+
themes,
|
|
103
|
+
style,
|
|
104
|
+
padding = 16,
|
|
105
|
+
scrollTestId,
|
|
106
|
+
onScroller,
|
|
107
|
+
className,
|
|
108
|
+
},
|
|
109
|
+
ref,
|
|
110
|
+
) {
|
|
111
|
+
const hostRef = useRef<HTMLDivElement | null>(null);
|
|
112
|
+
const viewRef = useRef<EditorView | null>(null);
|
|
113
|
+
/** The document the live state holds, so a switch can park it under the right key. */
|
|
114
|
+
const docKeyRef = useRef<string>('');
|
|
115
|
+
/** The last mark signature applied, so a re-render does not re-dispatch it. */
|
|
116
|
+
const markSignatureRef = useRef<string>('');
|
|
117
|
+
/**
|
|
118
|
+
* True while this component writes the buffer itself.
|
|
119
|
+
*
|
|
120
|
+
* Load-bearing: the update listener reports every document change upward, and
|
|
121
|
+
* an external sync (a file re-read, a tab switch) must not be reported back as
|
|
122
|
+
* the user's edit — the write path would then arm an autosave for a change
|
|
123
|
+
* nobody typed.
|
|
124
|
+
*/
|
|
125
|
+
const writingRef = useRef(false);
|
|
126
|
+
/**
|
|
127
|
+
* The text the view last REPORTED (or last wrote), so the per-render sync can
|
|
128
|
+
* skip without materializing the document.
|
|
129
|
+
*
|
|
130
|
+
* `syncExternal` used to compare `value` against `doc.toString()`, which is a
|
|
131
|
+
* full stringify on every render — and the parent re-renders on every
|
|
132
|
+
* keystroke, so a large file paid it twice per character. Measured in Bun:
|
|
133
|
+
* `toString()` is 7.2 ms at 20 MB, 13.7 ms at 30 MB, and the browser showed
|
|
134
|
+
* the knee — 34 ms per keystroke at 20 MB, 67 ms at 30, 83 ms at 40. The
|
|
135
|
+
* parent's `value` is the very string this component handed it, so the usual
|
|
136
|
+
* comparison is one reference check.
|
|
137
|
+
*/
|
|
138
|
+
const lastValueRef = useRef(value);
|
|
155
139
|
|
|
156
|
-
|
|
140
|
+
// Live mirrors: the state's own extensions close over these, and rebuilding
|
|
141
|
+
// the state because a callback identity moved would drop the undo history.
|
|
142
|
+
const onChangeRef = useRef(onValueChange);
|
|
143
|
+
onChangeRef.current = onValueChange;
|
|
144
|
+
const onCommandRef = useRef(onCommand);
|
|
145
|
+
onCommandRef.current = onCommand;
|
|
146
|
+
const onScrollerRef = useRef(onScroller);
|
|
147
|
+
onScrollerRef.current = onScroller;
|
|
148
|
+
/** The marks as of the last render, for the re-apply a document switch owes. */
|
|
149
|
+
const marksRef = useRef<readonly MarkRange[]>(marks ?? []);
|
|
150
|
+
const currentMarkRef = useRef(currentMark ?? -1);
|
|
151
|
+
marksRef.current = marks ?? [];
|
|
152
|
+
currentMarkRef.current = currentMark ?? -1;
|
|
153
|
+
/** The tokenizer themes, mirrored for the cache's first build. */
|
|
154
|
+
const activeThemes = themes ?? SHIKI_THEMES;
|
|
155
|
+
const themesRef = useRef<ShikiThemeNames>(activeThemes);
|
|
156
|
+
themesRef.current = activeThemes;
|
|
157
157
|
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
158
|
+
// One cache for the component's life: it owns the per-document states.
|
|
159
|
+
const cacheRef = useRef<DocumentCache | null>(null);
|
|
160
|
+
if (!cacheRef.current) {
|
|
161
|
+
cacheRef.current = createDocumentCache({
|
|
162
|
+
wordWrap,
|
|
163
|
+
readOnly,
|
|
164
|
+
themes: themesRef.current,
|
|
165
|
+
onDocChange: (next) => {
|
|
166
|
+
// Recorded BEFORE the writing guard: this is what the document holds,
|
|
167
|
+
// whether or not the change came from the user.
|
|
168
|
+
lastValueRef.current = next;
|
|
169
|
+
if (writingRef.current) return;
|
|
170
|
+
onChangeRef.current(next);
|
|
171
|
+
},
|
|
172
|
+
onState: (state) => {
|
|
173
|
+
const key = docKeyRef.current;
|
|
174
|
+
if (key) cacheRef.current?.remember(key, state);
|
|
175
|
+
},
|
|
176
|
+
onCommand: (command) => onCommandRef.current?.(command),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
172
179
|
|
|
180
|
+
// Mount once. Every later change is a state swap or a dispatch — never a
|
|
181
|
+
// second view, which would drop the undo history the cache exists to keep.
|
|
173
182
|
useLayoutEffect(() => {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
// The wrap width also changes without a re-render — dragging the panel
|
|
178
|
-
// splitter, resizing the window, a zoom that reflows — so the editor's own
|
|
179
|
-
// box drives a re-measure too.
|
|
180
|
-
const measureRef = useRef(measure);
|
|
181
|
-
measureRef.current = measure;
|
|
182
|
-
useEffect(() => {
|
|
183
|
-
const pre = preRef.current;
|
|
184
|
-
if (!pre || typeof ResizeObserver === 'undefined') return;
|
|
185
|
-
const observer = new ResizeObserver(() => measureRef.current());
|
|
186
|
-
observer.observe(pre);
|
|
187
|
-
return () => observer.disconnect();
|
|
188
|
-
}, []);
|
|
183
|
+
const host = hostRef.current;
|
|
184
|
+
const cache = cacheRef.current;
|
|
185
|
+
if (!host || !cache) return;
|
|
189
186
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const lazy = shouldWindow(lineCount, value.length);
|
|
200
|
-
const virtual = useCodeWindow({
|
|
201
|
-
rootRef,
|
|
202
|
-
lineCount,
|
|
203
|
-
geometry,
|
|
204
|
-
// A document whose geometry could not be measured still falls back to the
|
|
205
|
-
// plain surface — but one whose measurement has not run *yet* must not:
|
|
206
|
-
// that path would draw the whole file, which is what windowing exists to
|
|
207
|
-
// avoid. `measuredLines` is why switching files (which leaves a geometry
|
|
208
|
-
// for the previous document behind) does not take it either.
|
|
209
|
-
enabled: lazy && (measuredLines !== lineCount || geometry !== null),
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* What the highlight layer paints: the find matches, plus every extra ⌘D
|
|
214
|
-
* range. The find widget's current match is the one the caret is aimed at, so
|
|
215
|
-
* it keeps `currentMark` — an occurrence range never claims that role.
|
|
216
|
-
*/
|
|
217
|
-
const painted = useMemo<readonly FindMatch[]>(() => {
|
|
218
|
-
if (!occurrences || occurrences.length === 0) return marks ?? [];
|
|
219
|
-
return [...(marks ?? []), ...occurrences];
|
|
220
|
-
}, [marks, occurrences]);
|
|
221
|
-
/** Where the extra selections start inside `painted`; -1 when there are none. */
|
|
222
|
-
const occurrenceFrom = occurrences && occurrences.length > 0 ? (marks?.length ?? 0) : -1;
|
|
187
|
+
docKeyRef.current = documentKey;
|
|
188
|
+
const view = new EditorView({
|
|
189
|
+
state: cache.stateFor(documentKey, language, value),
|
|
190
|
+
parent: host,
|
|
191
|
+
});
|
|
192
|
+
viewRef.current = view;
|
|
193
|
+
lastValueRef.current = value;
|
|
194
|
+
if (scrollTestId) view.scrollDOM.dataset.testid = scrollTestId;
|
|
195
|
+
onScrollerRef.current?.(view.scrollDOM);
|
|
223
196
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
197
|
+
return () => {
|
|
198
|
+
onScrollerRef.current?.(null);
|
|
199
|
+
view.destroy();
|
|
200
|
+
viewRef.current = null;
|
|
201
|
+
};
|
|
202
|
+
// Mount only: the effects below own every later change, and re-running this
|
|
203
|
+
// one would rebuild the view (and its history).
|
|
204
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
205
|
+
}, []);
|
|
231
206
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
207
|
+
// A different document: park the live state, then apply the other one.
|
|
208
|
+
useLayoutEffect(() => {
|
|
209
|
+
const view = viewRef.current;
|
|
210
|
+
const cache = cacheRef.current;
|
|
211
|
+
if (!view || !cache) return;
|
|
212
|
+
if (docKeyRef.current === documentKey) return;
|
|
213
|
+
cache.remember(docKeyRef.current, view.state);
|
|
214
|
+
docKeyRef.current = documentKey;
|
|
215
|
+
view.setState(cache.stateFor(documentKey, language, value));
|
|
216
|
+
// The cached state carries the configuration it was CREATED with, and
|
|
217
|
+
// `setState` installs that wholesale — so the current wrap/read-only pair
|
|
218
|
+
// and the current editor theme are re-applied, or a tab opened while wrap
|
|
219
|
+
// was off comes back unwrapped and a theme chosen since comes back stale.
|
|
220
|
+
view.dispatch({ effects: cache.currentEffects(documentKey) });
|
|
221
|
+
syncExternal(view, value, writingRef, lastValueRef);
|
|
222
|
+
// The marks belonged to the previous document's offsets, and the field lives
|
|
223
|
+
// in that state. A fresh state starts empty, so this re-applies whatever the
|
|
224
|
+
// new document's owner asked for rather than leaving a stale set behind.
|
|
225
|
+
markSignatureRef.current = '';
|
|
226
|
+
view.dispatch({
|
|
227
|
+
effects: setFindMarks.of({ marks: marksRef.current, current: currentMarkRef.current }),
|
|
228
|
+
});
|
|
229
|
+
// `value` is deliberately absent: this effect is about the KEY moving, and
|
|
230
|
+
// the external sync below covers a document whose bytes were replaced.
|
|
231
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
232
|
+
}, [documentKey]);
|
|
252
233
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if (!container) return;
|
|
260
|
-
const frameTop = root.getBoundingClientRect().top - container.getBoundingClientRect().top + container.scrollTop;
|
|
261
|
-
const band = container.clientHeight;
|
|
262
|
-
const target = frameTop + rowTop;
|
|
263
|
-
// Only scroll when the row is actually outside the band; a reveal that
|
|
264
|
-
// always re-centers would make every "next match" jump the whole view.
|
|
265
|
-
if (target < container.scrollTop + 8 || target + geometry.rowHeight > container.scrollTop + band - 8) {
|
|
266
|
-
container.scrollTop = Math.max(0, target - band / 3);
|
|
267
|
-
}
|
|
268
|
-
},
|
|
269
|
-
[geometry, value],
|
|
270
|
-
);
|
|
234
|
+
// The parent replaced the buffer (a re-read, a refresh, a replace-all).
|
|
235
|
+
useLayoutEffect(() => {
|
|
236
|
+
const view = viewRef.current;
|
|
237
|
+
if (!view || docKeyRef.current !== documentKey) return;
|
|
238
|
+
syncExternal(view, value, writingRef, lastValueRef);
|
|
239
|
+
}, [value, documentKey]);
|
|
271
240
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
241
|
+
// Wrap, read-only and the editor theme are per-view settings: reconfiguring
|
|
242
|
+
// keeps the document, the selection and the history, which is what a wrap
|
|
243
|
+
// toggle — or a colour change — must not disturb.
|
|
244
|
+
useLayoutEffect(() => {
|
|
245
|
+
const view = viewRef.current;
|
|
246
|
+
const cache = cacheRef.current;
|
|
247
|
+
if (!view || !cache) return;
|
|
248
|
+
const effects = cache.optionsEffects({ wordWrap, readOnly, themes: activeThemes }, docKeyRef.current);
|
|
249
|
+
if (effects.length > 0) view.dispatch({ effects });
|
|
250
|
+
}, [wordWrap, readOnly, activeThemes.light, activeThemes.dark]);
|
|
281
251
|
|
|
282
|
-
//
|
|
283
|
-
// document has settled (or the window closed).
|
|
252
|
+
// Find marks: a field write, never a re-tokenize.
|
|
284
253
|
useLayoutEffect(() => {
|
|
285
|
-
const
|
|
286
|
-
if (!
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}, [
|
|
254
|
+
const view = viewRef.current;
|
|
255
|
+
if (!view) return;
|
|
256
|
+
const list = marks ?? [];
|
|
257
|
+
const signature = `${currentMark ?? -1}:${list.map((mark) => `${mark.start}-${mark.end}`).join(',')}`;
|
|
258
|
+
if (signature === markSignatureRef.current) return;
|
|
259
|
+
markSignatureRef.current = signature;
|
|
260
|
+
view.dispatch({ effects: setFindMarks.of({ marks: list, current: currentMark ?? -1 }) });
|
|
261
|
+
}, [marks, currentMark]);
|
|
293
262
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
getSelection: () => editorRef.current?.getSelection() ?? null,
|
|
298
|
-
select: (start, end, focus = true) => editorRef.current?.select(start, end, focus),
|
|
299
|
-
applyDocument: (next, caretStart, caretEnd) => editorRef.current?.applyDocument(next, caretStart, caretEnd),
|
|
300
|
-
runCommand: (command) => editorRef.current?.run(command),
|
|
301
|
-
revealOffset,
|
|
302
|
-
}),
|
|
303
|
-
[revealOffset],
|
|
304
|
-
);
|
|
263
|
+
// Built once: the handle closes over refs, so a consumer holding it never
|
|
264
|
+
// re-subscribes, and the view it reaches is whatever is live at the call.
|
|
265
|
+
useImperativeHandle(ref, () => createSurfaceHandle({ view: viewRef, onCommand: onCommandRef }), []);
|
|
305
266
|
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
267
|
+
const hostStyle = {
|
|
268
|
+
...themeVars,
|
|
269
|
+
...style,
|
|
270
|
+
// The document's padding, read back by the theme as a CSS variable so a
|
|
271
|
+
// per-layout value needs no theme reconfigure.
|
|
272
|
+
'--surface-pad': `${padding}px`,
|
|
273
|
+
} as CSSProperties;
|
|
312
274
|
|
|
313
275
|
return (
|
|
314
|
-
<div
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
</div>
|
|
325
|
-
))}
|
|
326
|
-
</div>
|
|
327
|
-
<div className={editorWrapperClassName}>
|
|
328
|
-
<CodeEditor
|
|
329
|
-
value={value}
|
|
330
|
-
onValueChange={onValueChange}
|
|
331
|
-
highlight={highlight}
|
|
332
|
-
padding={editorPadding}
|
|
333
|
-
preRef={preRef}
|
|
334
|
-
handleRef={editorRef}
|
|
335
|
-
textareaClassName={`focus:outline-none ${wrapClass}`}
|
|
336
|
-
preClassName={wrapClass}
|
|
337
|
-
style={editorStyle}
|
|
338
|
-
className={editorClassName}
|
|
339
|
-
virtual={virtual}
|
|
340
|
-
language={language}
|
|
341
|
-
occurrences={occurrences}
|
|
342
|
-
onCommand={onCommand}
|
|
343
|
-
onOccurrencesChange={onOccurrencesChange}
|
|
344
|
-
/>
|
|
345
|
-
</div>
|
|
346
|
-
</div>
|
|
276
|
+
<div
|
|
277
|
+
ref={hostRef}
|
|
278
|
+
className={className}
|
|
279
|
+
style={hostStyle}
|
|
280
|
+
// The panel's drop handler claims a file drop in the CAPTURE phase when
|
|
281
|
+
// the drag is over this element: CodeMirror's own `drop` handler inserts a
|
|
282
|
+
// dropped file's TEXT into the document, which would rewrite the open file
|
|
283
|
+
// on the next autosave. See `use-editor-drop.ts`.
|
|
284
|
+
data-code-surface=""
|
|
285
|
+
/>
|
|
347
286
|
);
|
|
348
287
|
});
|