@pellux/goodvibes-tui 2.0.21 → 2.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/CHANGELOG.md +58 -0
- package/README.md +16 -16
- package/docs/README.md +4 -4
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +16 -6
- package/src/cli/management.ts +5 -2
- package/src/cli/tui-startup.ts +34 -39
- package/src/core/agent-view-render.ts +157 -0
- package/src/core/approval-alert.ts +49 -11
- package/src/core/bookmark-navigation.ts +7 -1
- package/src/core/budget-breach-notifier.ts +27 -8
- package/src/core/compaction-receipt.ts +1 -1
- package/src/core/context-usage.ts +1 -1
- package/src/core/conversation-compaction-render.ts +3 -3
- package/src/core/conversation-display-only.ts +86 -0
- package/src/core/conversation-event-navigation.ts +27 -0
- package/src/core/conversation-line-cache.ts +216 -447
- package/src/core/conversation-render-context.ts +20 -27
- package/src/core/conversation-rendering.ts +157 -575
- package/src/core/conversation-splash-state.ts +6 -1
- package/src/core/conversation-types.ts +35 -0
- package/src/core/conversation-usage.ts +12 -0
- package/src/core/conversation.ts +130 -131
- package/src/core/long-task-notifier.ts +100 -31
- package/src/core/notices.ts +75 -0
- package/src/core/pairing-tailscale-gateway.ts +1 -1
- package/src/core/process-output-log.ts +143 -0
- package/src/core/rewind-receipt.ts +1 -1
- package/src/core/session-focus.ts +190 -0
- package/src/core/session-resume-core.ts +23 -81
- package/src/core/stream-event-wiring.ts +31 -5
- package/src/core/system-message-noise.ts +2 -2
- package/src/core/system-message-router.ts +16 -18
- package/src/core/turn-event-wiring.ts +272 -33
- package/src/core/work-tree-focus.ts +290 -0
- package/src/core/work-tree-fold-store.ts +143 -0
- package/src/core/work-tree-model.ts +799 -0
- package/src/core/work-tree-render.ts +384 -0
- package/src/core/work-tree-sources.ts +184 -0
- package/src/core/work-tree-wiring.ts +176 -0
- package/src/core/workstream-notification.ts +36 -6
- package/src/core/wrfc-notice-titles.ts +37 -0
- package/src/input/agents-modal-text.ts +119 -0
- package/src/input/agents-modal.ts +699 -0
- package/src/input/bookmark-modal.ts +33 -9
- package/src/input/changes-git.ts +251 -0
- package/src/input/changes-modal.ts +629 -0
- package/src/input/changes-model.ts +125 -0
- package/src/input/command-palette-types.ts +41 -0
- package/src/input/command-palette.ts +272 -0
- package/src/input/command-registry.ts +77 -35
- package/src/input/commands/acp-runtime.ts +19 -9
- package/src/input/commands/channel-runtime.ts +2 -2
- package/src/input/commands/ci-runtime.ts +1 -1
- package/src/input/commands/codebase-runtime.ts +1 -1
- package/src/input/commands/config.ts +7 -1
- package/src/input/commands/context-window.ts +25 -19
- package/src/input/commands/control-room-runtime.ts +18 -18
- package/src/input/commands/conversation-runtime.ts +0 -1
- package/src/input/commands/cost-runtime.ts +29 -20
- package/src/input/commands/diff-runtime.ts +35 -181
- package/src/input/commands/eval.ts +1 -1
- package/src/input/commands/experience-runtime.ts +3 -7
- package/src/input/commands/fresh-conversation.ts +86 -0
- package/src/input/commands/git-runtime.ts +5 -22
- package/src/input/commands/graph-runtime.ts +1 -1
- package/src/input/commands/guidance-runtime.ts +1 -1
- package/src/input/commands/health-runtime.ts +8 -9
- package/src/input/commands/hooks-runtime.ts +2 -2
- package/src/input/commands/hosted-runtime.ts +3 -3
- package/src/input/commands/incident-runtime.ts +3 -3
- package/src/input/commands/intelligence-runtime.ts +6 -9
- package/src/input/commands/knowledge.ts +2 -2
- package/src/input/commands/legacy-panel-command.ts +57 -0
- package/src/input/commands/local-auth-runtime.ts +8 -9
- package/src/input/commands/local-provider-runtime.ts +7 -4
- package/src/input/commands/local-runtime.ts +12 -15
- package/src/input/commands/local-setup-review.ts +1 -1
- package/src/input/commands/local-setup.ts +13 -19
- package/src/input/commands/memory.ts +1 -1
- package/src/input/commands/notify-runtime.ts +9 -0
- package/src/input/commands/operator-runtime.ts +10 -12
- package/src/input/commands/permissions-provenance.ts +3 -3
- package/src/input/commands/planning-runtime.ts +7 -7
- package/src/input/commands/platform-sandbox-runtime.ts +2 -2
- package/src/input/commands/platform-services-runtime.ts +1 -1
- package/src/input/commands/policy-dispatch.ts +2 -2
- package/src/input/commands/policy.ts +3 -3
- package/src/input/commands/provider.ts +1 -1
- package/src/input/commands/remote-runtime.ts +1 -1
- package/src/input/commands/review-runtime.ts +39 -94
- package/src/input/commands/rewind-runtime.ts +81 -108
- package/src/input/commands/runtime-services.ts +15 -17
- package/src/input/commands/services-runtime.ts +2 -2
- package/src/input/commands/session-content.ts +21 -5
- package/src/input/commands/session-workflow.ts +13 -38
- package/src/input/commands/settings-sync-runtime.ts +2 -2
- package/src/input/commands/shell-core.ts +19 -4
- package/src/input/commands/skills-runtime.ts +2 -2
- package/src/input/commands/status-runtime.ts +13 -0
- package/src/input/commands/subscription-runtime.ts +1 -1
- package/src/input/commands/tasks-runtime.ts +2 -8
- package/src/input/commands/work-plan-runtime.ts +4 -4
- package/src/input/commands/workstream-attempts.ts +24 -47
- package/src/input/commands/workstream-runtime.ts +2 -2
- package/src/input/commands/worktree-runtime.ts +3 -3
- package/src/input/commands.ts +3 -1
- package/src/input/composer-editor.ts +44 -0
- package/src/input/concealed-input.ts +1 -1
- package/src/input/config-modal-types.ts +20 -8
- package/src/input/config-modal.ts +106 -105
- package/src/input/confirm-dialog-types.ts +28 -0
- package/src/input/confirm-dialog.ts +70 -0
- package/src/input/delete-key-policy.ts +3 -3
- package/src/input/feed-context-factory.ts +15 -16
- package/src/input/handler-command-route.ts +7 -42
- package/src/input/handler-content-actions.ts +16 -15
- package/src/input/handler-feed-routes.ts +26 -211
- package/src/input/handler-feed.ts +68 -95
- package/src/input/handler-interactions.ts +5 -3
- package/src/input/handler-modal-routes.ts +215 -216
- package/src/input/handler-modal-stack.ts +81 -11
- package/src/input/handler-modal-token-routes.ts +4 -0
- package/src/input/handler-onboarding.ts +0 -1
- package/src/input/handler-picker-routes.ts +65 -107
- package/src/input/handler-session-view-route.ts +149 -0
- package/src/input/handler-shortcuts.ts +16 -154
- package/src/input/handler-types.ts +4 -2
- package/src/input/handler-ui-state.ts +45 -15
- package/src/input/handler-work-tree-route.ts +123 -0
- package/src/input/handler.ts +25 -70
- package/src/input/keybindings.ts +16 -71
- package/src/input/masked-entry-modal.ts +117 -0
- package/src/input/mcp-workspace.ts +48 -17
- package/src/input/model-picker-filter.ts +8 -0
- package/src/input/model-picker-open.ts +99 -0
- package/src/input/model-picker-types.ts +1 -1
- package/src/input/model-picker.ts +92 -9
- package/src/input/notifications-modal.ts +123 -0
- package/src/input/overlay-filter.ts +26 -0
- package/src/input/profile-picker-modal.ts +37 -8
- package/src/input/search.ts +3 -3
- package/src/input/selection-modal-queue.ts +2 -0
- package/src/input/selection-modal.ts +28 -16
- package/src/input/session-picker-modal.ts +33 -8
- package/src/input/settings-modal-activation.ts +7 -2
- package/src/input/settings-modal-data.ts +11 -6
- package/src/input/settings-modal-types.ts +1 -1
- package/src/input/settings-modal.ts +20 -20
- package/src/input/submission-intent.ts +1 -1
- package/src/input/submission-router.ts +4 -9
- package/src/input/surface-modal-host.ts +110 -0
- package/src/input/theme-settings-actions.ts +82 -0
- package/src/input/usage-modal.ts +183 -0
- package/src/input/views.ts +97 -0
- package/src/main.ts +175 -165
- package/src/permissions/hunk-selection.ts +1 -1
- package/src/permissions/prompt-card.ts +511 -0
- package/src/permissions/prompt.ts +59 -622
- package/src/renderer/agents-modal.ts +310 -0
- package/src/renderer/autocomplete-overlay.ts +42 -220
- package/src/renderer/back-to-bottom.ts +80 -0
- package/src/renderer/block-actions-overlay.ts +30 -48
- package/src/renderer/bookmark-modal.ts +48 -79
- package/src/renderer/changes-modal.ts +325 -0
- package/src/renderer/code-block.ts +184 -106
- package/src/renderer/command-palette.ts +93 -0
- package/src/renderer/composer-fragments.ts +4 -4
- package/src/renderer/composer.ts +146 -0
- package/src/renderer/compositor.ts +26 -160
- package/src/renderer/config-modal.ts +185 -81
- package/src/renderer/confirm-dialog.ts +50 -0
- package/src/renderer/context-inspector.ts +116 -154
- package/src/renderer/context-status-hint.ts +4 -4
- package/src/renderer/conversation-layout.ts +20 -0
- package/src/renderer/conversation-overlays.ts +94 -84
- package/src/renderer/conversation-surface.ts +19 -38
- package/src/renderer/diff-tint.ts +98 -0
- package/src/renderer/file-picker-overlay.ts +63 -90
- package/src/renderer/footer-targets.ts +38 -0
- package/src/renderer/git-status.ts +3 -4
- package/src/renderer/header-line.ts +140 -117
- package/src/renderer/help-overlay.ts +133 -219
- package/src/renderer/history-search-overlay.ts +15 -12
- package/src/renderer/lane-graph/bead.ts +800 -0
- package/src/renderer/lane-graph/glyphs.ts +134 -0
- package/src/renderer/lane-graph/layout.ts +248 -0
- package/src/renderer/lane-graph/paint.ts +537 -0
- package/src/renderer/lane-graph/semantic-memo.ts +77 -0
- package/src/renderer/layout-engine.ts +2 -57
- package/src/renderer/layout.ts +7 -13
- package/src/renderer/markdown-inline.ts +2 -1
- package/src/renderer/markdown-table.ts +17 -12
- package/src/renderer/markdown.ts +92 -34
- package/src/renderer/masked-entry-modal.ts +78 -0
- package/src/renderer/mcp-workspace.ts +166 -203
- package/src/renderer/model-workspace.ts +381 -436
- package/src/renderer/notifications-modal.ts +116 -0
- package/src/renderer/onboarding/onboarding-wizard.ts +178 -588
- package/src/renderer/process-view.ts +192 -0
- package/src/renderer/profile-picker-modal.ts +58 -110
- package/src/renderer/search-overlay.ts +6 -3
- package/src/renderer/selection-modal-overlay.ts +87 -327
- package/src/renderer/semantic-diff.ts +2 -2
- package/src/renderer/session-chips.ts +102 -0
- package/src/renderer/session-picker-modal.ts +177 -297
- package/src/renderer/settings-modal-connections.ts +14 -23
- package/src/renderer/settings-modal-context.ts +367 -0
- package/src/renderer/settings-modal-helpers.ts +6 -11
- package/src/renderer/settings-modal.ts +322 -726
- package/src/renderer/shell-surface.ts +277 -175
- package/src/renderer/shortcuts-overlay.ts +217 -0
- package/src/renderer/status-line.ts +475 -0
- package/src/renderer/surface-compose.ts +174 -0
- package/src/renderer/surface-kit-extra.ts +303 -0
- package/src/renderer/surface-kit-list.ts +366 -0
- package/src/renderer/surface-kit-parts.ts +360 -0
- package/src/renderer/surface-kit.ts +568 -0
- package/src/renderer/syntax-highlighter.ts +280 -197
- package/src/renderer/syntax-theme.ts +51 -0
- package/src/renderer/system-message.ts +0 -1
- package/src/renderer/term-caps.ts +42 -1
- package/src/renderer/terminal-bg-probe.ts +116 -19
- package/src/renderer/terminal-palette-probe.ts +322 -0
- package/src/renderer/terminal-palette-reader.ts +183 -0
- package/src/renderer/terminal-palette.ts +58 -0
- package/src/renderer/theme-mode-config.ts +16 -1
- package/src/renderer/theme.ts +234 -207
- package/src/renderer/thinking.ts +0 -1
- package/src/renderer/throbber.ts +188 -0
- package/src/renderer/toast-center.ts +119 -0
- package/src/renderer/ui-factory.ts +39 -602
- package/src/renderer/ui-primitives.ts +8 -25
- package/src/renderer/usage-modal.ts +353 -0
- package/src/renderer/user-message.ts +58 -0
- package/src/renderer/voice-capture-chip.ts +53 -0
- package/src/runtime/bootstrap-command-context.ts +0 -5
- package/src/runtime/bootstrap-command-parts.ts +38 -89
- package/src/runtime/bootstrap-core.ts +13 -15
- package/src/runtime/bootstrap-hook-bridge.ts +12 -21
- package/src/runtime/bootstrap-notifier-sync.ts +17 -1
- package/src/runtime/bootstrap-shell.ts +16 -50
- package/src/runtime/bootstrap.ts +14 -14
- package/src/runtime/client/fleet-union.ts +5 -5
- package/src/runtime/client/hosted-exit.ts +2 -2
- package/src/runtime/client/hosted-sessions.ts +1 -1
- package/src/runtime/client/spine-adoption.ts +1 -1
- package/src/runtime/context-accounting-source.ts +5 -4
- package/src/runtime/index.ts +1 -0
- package/src/runtime/notification-dispatch.ts +102 -29
- package/src/runtime/recovery-offer-wiring.ts +4 -1
- package/src/runtime/recovery-prompt.ts +31 -15
- package/src/runtime/runtime-services-types.ts +10 -6
- package/src/runtime/services.ts +17 -12
- package/src/runtime/session-continuity-hints.ts +2 -5
- package/src/runtime/session-inbound-inputs.ts +1 -1
- package/src/runtime/session-resume-liveness-confirm.ts +14 -2
- package/src/runtime/startup-typeahead-gate.ts +103 -0
- package/src/runtime/store/domains/domain-read-matrix.ts +0 -1
- package/src/runtime/store/domains/index.ts +0 -8
- package/src/runtime/store/selectors/index.ts +0 -28
- package/src/runtime/store/state.ts +0 -8
- package/src/runtime/ui-services.ts +4 -6
- package/src/runtime/usage-tracker.ts +348 -0
- package/src/runtime/wrfc-persistence.ts +4 -4
- package/src/shell/blocking-input.ts +92 -2
- package/src/shell/recovery-input-helpers.ts +1 -1
- package/src/shell/session-views.ts +539 -0
- package/src/shell/status-report.ts +86 -0
- package/src/shell/transcript-scroll.ts +68 -0
- package/src/shell/ui-openers.ts +84 -172
- package/src/shell/view-openers.ts +205 -0
- package/src/tools/wrfc-agent-guard.ts +22 -2
- package/src/utils/format-duration.ts +4 -8
- package/src/utils/splash-lines.ts +9 -1
- package/src/utils/terminal-width.ts +56 -0
- package/src/verification/verification-ledger.ts +18 -16
- package/src/{panels → views}/agent-inspector-shared.ts +2 -2
- package/src/{panels → views}/builtin-modals.ts +22 -21
- package/src/views/builtin-views.ts +135 -0
- package/src/{panels → views}/confirm-state.ts +9 -9
- package/src/{panels → views}/diff-review-model.ts +10 -3
- package/src/{panels → views}/eval-registry.ts +1 -1
- package/src/{panels → views}/fleet-acts.ts +49 -85
- package/src/{panels/fleet-panel-format.ts → views/fleet-format.ts} +40 -40
- package/src/{panels → views}/fleet-gateway.ts +5 -5
- package/src/{panels → views}/fleet-observed-render.ts +16 -16
- package/src/{panels → views}/fleet-read-model.ts +9 -9
- package/src/{panels/fleet-tabs.ts → views/fleet-session-tabs.ts} +13 -13
- package/src/{panels → views}/fleet-spawn.ts +33 -49
- package/src/{panels → views}/fleet-steer.ts +12 -12
- package/src/{panels → views}/fleet-stop.ts +4 -4
- package/src/{panels → views}/fleet-transcript.ts +26 -27
- package/src/{panels/fleet-panel-worktree-detail.ts → views/fleet-worktree-detail.ts} +7 -7
- package/src/{panels → views}/hosted-session-feed.ts +6 -6
- package/src/views/modal-surface-registry.ts +41 -0
- package/src/{panels → views}/modals/devices-modal.ts +2 -2
- package/src/{panels → views}/modals/hooks-modal.ts +13 -13
- package/src/{panels → views}/modals/keybindings-modal.ts +19 -27
- package/src/{panels → views}/modals/knowledge-modal.ts +6 -5
- package/src/{panels → views}/modals/local-auth-modal.ts +22 -21
- package/src/{panels → views}/modals/marketplace-modal.ts +11 -11
- package/src/{panels → views}/modals/memory-modal.ts +8 -8
- package/src/{panels → views}/modals/modal-surface-helpers.ts +15 -5
- package/src/{panels → views}/modals/pairing-modal.ts +9 -9
- package/src/{panels → views}/modals/planning-modal.ts +15 -20
- package/src/{panels → views}/modals/plugins-modal.ts +6 -6
- package/src/{panels → views}/modals/policy-modal.ts +13 -5
- package/src/{panels → views}/modals/provider-health-modal.ts +4 -4
- package/src/{panels → views}/modals/remote-modal.ts +3 -3
- package/src/{panels → views}/modals/sandbox-modal.ts +4 -4
- package/src/{panels → views}/modals/security-modal.ts +22 -16
- package/src/{panels → views}/modals/services-modal.ts +3 -3
- package/src/{panels → views}/modals/settings-sync-modal.ts +4 -4
- package/src/{panels → views}/modals/skills-modal.ts +9 -9
- package/src/{panels → views}/modals/subscription-modal.ts +3 -3
- package/src/{panels → views}/modals/work-plan-modal.ts +10 -13
- package/src/views/notifications-feed.ts +314 -0
- package/src/{panels → views}/polish-core.ts +38 -36
- package/src/{panels → views}/polish-tables.ts +35 -26
- package/src/{panels → views}/polish.ts +101 -95
- package/src/{panels → views}/project-planning-answer-actions.ts +3 -3
- package/src/{panels → views}/provider-health-routes.ts +14 -14
- package/src/{panels → views}/search-focus.ts +8 -8
- package/src/{panels → views}/session-maintenance.ts +11 -11
- package/src/views/skills-discovery.ts +159 -0
- package/src/views/view-deps.ts +80 -0
- package/src/views/view-panel-adapter.ts +53 -0
- package/src/{panels → views}/workstream-graph-render.ts +1 -1
- package/src/core/conversation-fold.ts +0 -101
- package/src/core/conversation-turn-structure.ts +0 -464
- package/src/core/conversation-user-receipts.ts +0 -42
- package/src/input/commands/operator-panel-runtime.ts +0 -186
- package/src/input/panel-integration-actions.ts +0 -33
- package/src/input/panel-mouse-geometry.ts +0 -97
- package/src/input/panel-paste-flood-guard.ts +0 -86
- package/src/panels/base-panel.ts +0 -301
- package/src/panels/builtin/agent.ts +0 -27
- package/src/panels/builtin/development.ts +0 -92
- package/src/panels/builtin/knowledge.ts +0 -12
- package/src/panels/builtin/operations.ts +0 -177
- package/src/panels/builtin/session.ts +0 -81
- package/src/panels/builtin/shared.ts +0 -357
- package/src/panels/builtin-panels.ts +0 -28
- package/src/panels/cost-tracker-panel.ts +0 -746
- package/src/panels/diff-panel.ts +0 -797
- package/src/panels/diff-review-panel.ts +0 -395
- package/src/panels/expandable-list-panel.ts +0 -189
- package/src/panels/fleet-deep-link.ts +0 -31
- package/src/panels/fleet-panel.ts +0 -799
- package/src/panels/git-panel.ts +0 -800
- package/src/panels/hosted-session-panel.ts +0 -221
- package/src/panels/index.ts +0 -21
- package/src/panels/local-auth-panel.ts +0 -478
- package/src/panels/notifications-feed.ts +0 -127
- package/src/panels/notifications-panel.ts +0 -160
- package/src/panels/panel-confirm-overlay.ts +0 -72
- package/src/panels/panel-manager.ts +0 -791
- package/src/panels/plugins-panel.ts +0 -371
- package/src/panels/scrollable-list-panel.ts +0 -498
- package/src/panels/skills-panel.ts +0 -454
- package/src/panels/token-budget-panel.ts +0 -688
- package/src/panels/types.ts +0 -191
- package/src/panels/wrfc-panel-format.ts +0 -133
- package/src/renderer/diff-view.ts +0 -134
- package/src/renderer/fleet-tab-strip.ts +0 -56
- package/src/renderer/footer-tips.ts +0 -53
- package/src/renderer/fullscreen-primitives.ts +0 -146
- package/src/renderer/fullscreen-workspace.ts +0 -194
- package/src/renderer/hint-grammar.ts +0 -52
- package/src/renderer/modal-factory.ts +0 -480
- package/src/renderer/overlay-box.ts +0 -154
- package/src/renderer/overlay-viewport.ts +0 -104
- package/src/renderer/panel-composite.ts +0 -145
- package/src/renderer/panel-workspace-bar.ts +0 -54
- package/src/renderer/process-indicator.ts +0 -163
- package/src/renderer/tab-strip.ts +0 -222
- package/src/renderer/text-layout.ts +0 -31
- package/src/renderer/tool-call.ts +0 -276
- package/src/renderer/tool-result-expanded-lines.ts +0 -48
- package/src/renderer/tool-result-summary.ts +0 -219
- package/src/runtime/diagnostics/panels/index.ts +0 -21
- package/src/runtime/diagnostics/panels/ops.ts +0 -156
- package/src/runtime/perf/panel-contracts.ts +0 -32
- package/src/runtime/store/domains/panels.ts +0 -117
- /package/src/{panels → views}/memory-consolidation-gateway.ts +0 -0
- /package/src/{panels → views}/modals/modal-theme.ts +0 -0
- /package/src/{panels → views}/provider-health-domains.ts +0 -0
|
@@ -7,22 +7,16 @@
|
|
|
7
7
|
* - Falls back to empty array (caller uses regex tokenizer) when parser not ready
|
|
8
8
|
* - Caches parsed results keyed by language + content hash to avoid re-parsing
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* Comments: #666666 (dim grey)
|
|
15
|
-
* Functions/methods: #00ffff (cyan)
|
|
16
|
-
* Types/classes: #ff6b9d (pink)
|
|
17
|
-
* Operators: #ffffff (white)
|
|
18
|
-
* Properties: #87ceeb (light blue)
|
|
19
|
-
* Built-ins/special: #ff8c00 (orange)
|
|
20
|
-
* Default: 252 (light grey)
|
|
10
|
+
* Colours: every node type maps to a syntax role (keyword, string, number,
|
|
11
|
+
* comment, function, type, operator, property, builtin, plain); the active
|
|
12
|
+
* theme's syntax tokens colour each role (syntax-theme.ts). The cache holds
|
|
13
|
+
* roles, so a theme change re-colours cached blocks without re-parsing.
|
|
21
14
|
*/
|
|
22
15
|
import type { Node } from 'web-tree-sitter';
|
|
23
16
|
import { TreeSitterService } from '@pellux/goodvibes-sdk/platform/intelligence';
|
|
24
17
|
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
25
18
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
19
|
+
import { syntaxStyles, type SyntaxRole, type SyntaxStyle } from './syntax-theme.ts';
|
|
26
20
|
|
|
27
21
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
28
22
|
|
|
@@ -62,193 +56,194 @@ const FENCE_TO_LANG_ID: Record<string, string> = {
|
|
|
62
56
|
zsh: 'bash',
|
|
63
57
|
};
|
|
64
58
|
|
|
65
|
-
// ───
|
|
59
|
+
// ─── Node Type → Syntax Role ─────────────────────────────────────────────────
|
|
66
60
|
|
|
67
|
-
// Map tree-sitter node types to
|
|
61
|
+
// Map tree-sitter node types to syntax roles; syntax-theme.ts turns a role into
|
|
62
|
+
// the active theme's colour (shared with code-block.ts's regex fallback).
|
|
68
63
|
// The node types are specific to each grammar's output.
|
|
69
|
-
const NODE_TYPE_COLORS: Record<string,
|
|
64
|
+
const NODE_TYPE_COLORS: Record<string, SyntaxRole> = {
|
|
70
65
|
// ── Keywords
|
|
71
|
-
'if':
|
|
72
|
-
'else':
|
|
73
|
-
'return':
|
|
74
|
-
'const':
|
|
75
|
-
'let':
|
|
76
|
-
'var':
|
|
77
|
-
'function':
|
|
78
|
-
'class':
|
|
79
|
-
'import':
|
|
80
|
-
'export':
|
|
81
|
-
'from':
|
|
82
|
-
'new':
|
|
83
|
-
'typeof':
|
|
84
|
-
'instanceof':
|
|
85
|
-
'in':
|
|
86
|
-
'of':
|
|
87
|
-
'for':
|
|
88
|
-
'while':
|
|
89
|
-
'do':
|
|
90
|
-
'switch':
|
|
91
|
-
'case':
|
|
92
|
-
'break':
|
|
93
|
-
'continue':
|
|
94
|
-
'throw':
|
|
95
|
-
'try':
|
|
96
|
-
'catch':
|
|
97
|
-
'finally':
|
|
98
|
-
'async':
|
|
99
|
-
'await':
|
|
100
|
-
'yield':
|
|
101
|
-
'delete':
|
|
102
|
-
'void':
|
|
103
|
-
'static':
|
|
104
|
-
'extends':
|
|
105
|
-
'implements':
|
|
106
|
-
'interface':
|
|
107
|
-
'type':
|
|
108
|
-
'enum':
|
|
109
|
-
'namespace':
|
|
110
|
-
'abstract':
|
|
111
|
-
'readonly':
|
|
112
|
-
'as':
|
|
113
|
-
'satisfies':
|
|
66
|
+
'if': 'keyword',
|
|
67
|
+
'else': 'keyword',
|
|
68
|
+
'return': 'keyword',
|
|
69
|
+
'const': 'keyword',
|
|
70
|
+
'let': 'keyword',
|
|
71
|
+
'var': 'keyword',
|
|
72
|
+
'function': 'keyword',
|
|
73
|
+
'class': 'keyword',
|
|
74
|
+
'import': 'keyword',
|
|
75
|
+
'export': 'keyword',
|
|
76
|
+
'from': 'keyword',
|
|
77
|
+
'new': 'keyword',
|
|
78
|
+
'typeof': 'keyword',
|
|
79
|
+
'instanceof': 'keyword',
|
|
80
|
+
'in': 'keyword',
|
|
81
|
+
'of': 'keyword',
|
|
82
|
+
'for': 'keyword',
|
|
83
|
+
'while': 'keyword',
|
|
84
|
+
'do': 'keyword',
|
|
85
|
+
'switch': 'keyword',
|
|
86
|
+
'case': 'keyword',
|
|
87
|
+
'break': 'keyword',
|
|
88
|
+
'continue': 'keyword',
|
|
89
|
+
'throw': 'keyword',
|
|
90
|
+
'try': 'keyword',
|
|
91
|
+
'catch': 'keyword',
|
|
92
|
+
'finally': 'keyword',
|
|
93
|
+
'async': 'keyword',
|
|
94
|
+
'await': 'keyword',
|
|
95
|
+
'yield': 'keyword',
|
|
96
|
+
'delete': 'keyword',
|
|
97
|
+
'void': 'keyword',
|
|
98
|
+
'static': 'keyword',
|
|
99
|
+
'extends': 'keyword',
|
|
100
|
+
'implements': 'keyword',
|
|
101
|
+
'interface': 'keyword',
|
|
102
|
+
'type': 'keyword',
|
|
103
|
+
'enum': 'keyword',
|
|
104
|
+
'namespace': 'keyword',
|
|
105
|
+
'abstract': 'keyword',
|
|
106
|
+
'readonly': 'keyword',
|
|
107
|
+
'as': 'keyword',
|
|
108
|
+
'satisfies': 'keyword',
|
|
114
109
|
// Python keywords
|
|
115
|
-
'def':
|
|
116
|
-
'lambda':
|
|
117
|
-
'with':
|
|
118
|
-
'pass':
|
|
119
|
-
'global':
|
|
120
|
-
'nonlocal':
|
|
121
|
-
'assert':
|
|
122
|
-
'raise':
|
|
123
|
-
'except':
|
|
124
|
-
'elif':
|
|
125
|
-
'and':
|
|
126
|
-
'or':
|
|
127
|
-
'not':
|
|
128
|
-
'is':
|
|
110
|
+
'def': 'keyword',
|
|
111
|
+
'lambda': 'keyword',
|
|
112
|
+
'with': 'keyword',
|
|
113
|
+
'pass': 'keyword',
|
|
114
|
+
'global': 'keyword',
|
|
115
|
+
'nonlocal': 'keyword',
|
|
116
|
+
'assert': 'keyword',
|
|
117
|
+
'raise': 'keyword',
|
|
118
|
+
'except': 'keyword',
|
|
119
|
+
'elif': 'keyword',
|
|
120
|
+
'and': 'keyword',
|
|
121
|
+
'or': 'keyword',
|
|
122
|
+
'not': 'keyword',
|
|
123
|
+
'is': 'keyword',
|
|
129
124
|
// Bash keywords
|
|
130
|
-
'then':
|
|
131
|
-
'fi':
|
|
132
|
-
'done':
|
|
133
|
-
'esac':
|
|
125
|
+
'then': 'keyword',
|
|
126
|
+
'fi': 'keyword',
|
|
127
|
+
'done': 'keyword',
|
|
128
|
+
'esac': 'keyword',
|
|
134
129
|
|
|
135
130
|
// ── Strings
|
|
136
|
-
'string':
|
|
137
|
-
'string_fragment':
|
|
138
|
-
'template_string':
|
|
139
|
-
'escape_sequence':
|
|
140
|
-
'raw_string':
|
|
141
|
-
'concatenated_string':
|
|
142
|
-
'string_content':
|
|
143
|
-
'quoted_attribute_value':
|
|
144
|
-
'attribute_value':
|
|
145
|
-
'pair_value':
|
|
146
|
-
'plain_value':
|
|
131
|
+
'string': 'string',
|
|
132
|
+
'string_fragment': 'string',
|
|
133
|
+
'template_string': 'string',
|
|
134
|
+
'escape_sequence': 'string',
|
|
135
|
+
'raw_string': 'string',
|
|
136
|
+
'concatenated_string': 'string',
|
|
137
|
+
'string_content': 'string',
|
|
138
|
+
'quoted_attribute_value': 'string',
|
|
139
|
+
'attribute_value': 'string',
|
|
140
|
+
'pair_value': 'string',
|
|
141
|
+
'plain_value': 'string',
|
|
147
142
|
|
|
148
143
|
// ── Numbers
|
|
149
|
-
'number':
|
|
150
|
-
'integer':
|
|
151
|
-
'float':
|
|
152
|
-
'decimal_integer_literal':
|
|
153
|
-
'hex_integer_literal':
|
|
154
|
-
'octal_integer_literal':
|
|
155
|
-
'binary_integer_literal':
|
|
144
|
+
'number': 'number',
|
|
145
|
+
'integer': 'number',
|
|
146
|
+
'float': 'number',
|
|
147
|
+
'decimal_integer_literal': 'number',
|
|
148
|
+
'hex_integer_literal': 'number',
|
|
149
|
+
'octal_integer_literal': 'number',
|
|
150
|
+
'binary_integer_literal': 'number',
|
|
156
151
|
|
|
157
152
|
// ── Comments
|
|
158
|
-
'comment':
|
|
159
|
-
'line_comment':
|
|
160
|
-
'block_comment':
|
|
161
|
-
'shebang':
|
|
153
|
+
'comment': 'comment',
|
|
154
|
+
'line_comment': 'comment',
|
|
155
|
+
'block_comment': 'comment',
|
|
156
|
+
'shebang': 'comment',
|
|
162
157
|
|
|
163
158
|
// ── Functions/methods
|
|
164
|
-
'function_declaration':
|
|
165
|
-
'method_declaration':
|
|
166
|
-
'method_definition':
|
|
167
|
-
'arrow_function':
|
|
168
|
-
'function_expression':
|
|
169
|
-
'call_expression':
|
|
170
|
-
'function_definition':
|
|
159
|
+
'function_declaration': 'function',
|
|
160
|
+
'method_declaration': 'function',
|
|
161
|
+
'method_definition': 'function',
|
|
162
|
+
'arrow_function': 'function',
|
|
163
|
+
'function_expression': 'function',
|
|
164
|
+
'call_expression': 'function',
|
|
165
|
+
'function_definition': 'function', // Python
|
|
171
166
|
|
|
172
167
|
// ── Types and classes
|
|
173
|
-
'type_identifier':
|
|
174
|
-
'type_annotation':
|
|
175
|
-
'class_declaration':
|
|
176
|
-
'class_definition':
|
|
177
|
-
'interface_declaration':
|
|
178
|
-
'type_alias_declaration':
|
|
179
|
-
'predefined_type':
|
|
180
|
-
'builtin_type':
|
|
181
|
-
'tag_name':
|
|
182
|
-
'element':
|
|
168
|
+
'type_identifier': 'type',
|
|
169
|
+
'type_annotation': 'type',
|
|
170
|
+
'class_declaration': 'type',
|
|
171
|
+
'class_definition': 'type', // Python
|
|
172
|
+
'interface_declaration': 'type',
|
|
173
|
+
'type_alias_declaration': 'type',
|
|
174
|
+
'predefined_type': 'type',
|
|
175
|
+
'builtin_type': 'type',
|
|
176
|
+
'tag_name': 'type',
|
|
177
|
+
'element': 'type',
|
|
183
178
|
|
|
184
179
|
// ── Operators
|
|
185
|
-
'+':
|
|
186
|
-
'-':
|
|
187
|
-
'*':
|
|
188
|
-
'/':
|
|
189
|
-
'%':
|
|
190
|
-
'=':
|
|
191
|
-
'==':
|
|
192
|
-
'===':
|
|
193
|
-
'!=':
|
|
194
|
-
'!==':
|
|
195
|
-
'<':
|
|
196
|
-
'>':
|
|
197
|
-
'<=':
|
|
198
|
-
'>=':
|
|
199
|
-
'&&':
|
|
200
|
-
'||':
|
|
201
|
-
'??':
|
|
202
|
-
'=>':
|
|
203
|
-
'!':
|
|
204
|
-
'&':
|
|
205
|
-
'|':
|
|
206
|
-
'^':
|
|
207
|
-
'~':
|
|
208
|
-
'<<':
|
|
209
|
-
'>>':
|
|
210
|
-
'>>>':
|
|
180
|
+
'+': 'operator',
|
|
181
|
+
'-': 'operator',
|
|
182
|
+
'*': 'operator',
|
|
183
|
+
'/': 'operator',
|
|
184
|
+
'%': 'operator',
|
|
185
|
+
'=': 'operator',
|
|
186
|
+
'==': 'operator',
|
|
187
|
+
'===': 'operator',
|
|
188
|
+
'!=': 'operator',
|
|
189
|
+
'!==': 'operator',
|
|
190
|
+
'<': 'operator',
|
|
191
|
+
'>': 'operator',
|
|
192
|
+
'<=': 'operator',
|
|
193
|
+
'>=': 'operator',
|
|
194
|
+
'&&': 'operator',
|
|
195
|
+
'||': 'operator',
|
|
196
|
+
'??': 'operator',
|
|
197
|
+
'=>': 'operator',
|
|
198
|
+
'!': 'operator',
|
|
199
|
+
'&': 'operator',
|
|
200
|
+
'|': 'operator',
|
|
201
|
+
'^': 'operator',
|
|
202
|
+
'~': 'operator',
|
|
203
|
+
'<<': 'operator',
|
|
204
|
+
'>>': 'operator',
|
|
205
|
+
'>>>': 'operator',
|
|
211
206
|
|
|
212
207
|
// ── Properties
|
|
213
|
-
'property_identifier':
|
|
214
|
-
'shorthand_property_identifier':
|
|
215
|
-
'attribute_name':
|
|
216
|
-
'property_name':
|
|
217
|
-
'pair_key':
|
|
208
|
+
'property_identifier': 'property',
|
|
209
|
+
'shorthand_property_identifier': 'property',
|
|
210
|
+
'attribute_name': 'property',
|
|
211
|
+
'property_name': 'property',
|
|
212
|
+
'pair_key': 'property',
|
|
218
213
|
|
|
219
214
|
// ── Built-ins / special values
|
|
220
|
-
'true':
|
|
221
|
-
'false':
|
|
222
|
-
'null':
|
|
223
|
-
'undefined':
|
|
224
|
-
'none':
|
|
225
|
-
'None':
|
|
226
|
-
'True':
|
|
227
|
-
'False':
|
|
228
|
-
'this':
|
|
229
|
-
'super':
|
|
230
|
-
'self':
|
|
231
|
-
'boolean':
|
|
215
|
+
'true': 'builtin',
|
|
216
|
+
'false': 'builtin',
|
|
217
|
+
'null': 'builtin',
|
|
218
|
+
'undefined': 'builtin',
|
|
219
|
+
'none': 'builtin',
|
|
220
|
+
'None': 'builtin',
|
|
221
|
+
'True': 'builtin',
|
|
222
|
+
'False': 'builtin',
|
|
223
|
+
'this': 'builtin',
|
|
224
|
+
'super': 'builtin',
|
|
225
|
+
'self': 'builtin',
|
|
226
|
+
'boolean': 'builtin',
|
|
232
227
|
|
|
233
228
|
// ── JSON specific
|
|
234
|
-
'json_string':
|
|
235
|
-
'json_key':
|
|
236
|
-
'json_number':
|
|
229
|
+
'json_string': 'string',
|
|
230
|
+
'json_key': 'property',
|
|
231
|
+
'json_number': 'number',
|
|
237
232
|
|
|
238
233
|
// ── CSS specific
|
|
239
|
-
'class_selector':
|
|
240
|
-
'id_selector':
|
|
241
|
-
'pseudo_class_selector':
|
|
242
|
-
'pseudo_element_selector':
|
|
243
|
-
'property_name_css':
|
|
244
|
-
'unit':
|
|
245
|
-
'color_value':
|
|
246
|
-
'at_keyword':
|
|
247
|
-
'important':
|
|
234
|
+
'class_selector': 'type',
|
|
235
|
+
'id_selector': 'type',
|
|
236
|
+
'pseudo_class_selector': 'keyword',
|
|
237
|
+
'pseudo_element_selector': 'keyword',
|
|
238
|
+
'property_name_css': 'property',
|
|
239
|
+
'unit': 'number',
|
|
240
|
+
'color_value': 'string',
|
|
241
|
+
'at_keyword': 'keyword',
|
|
242
|
+
'important': 'keyword',
|
|
248
243
|
};
|
|
249
244
|
|
|
250
|
-
// Default
|
|
251
|
-
const
|
|
245
|
+
// Default role for unrecognized node types
|
|
246
|
+
const DEFAULT_ROLE: SyntaxRole = 'plain';
|
|
252
247
|
|
|
253
248
|
// ─── Content Hash ─────────────────────────────────────────────────────────────
|
|
254
249
|
|
|
@@ -270,11 +265,17 @@ interface Span {
|
|
|
270
265
|
endRow: number;
|
|
271
266
|
endCol: number;
|
|
272
267
|
text: string;
|
|
273
|
-
|
|
274
|
-
bold?: boolean;
|
|
275
|
-
italic?: boolean;
|
|
268
|
+
role: SyntaxRole;
|
|
276
269
|
}
|
|
277
270
|
|
|
271
|
+
/** A cached token: text plus its role (colour resolved per theme on read). */
|
|
272
|
+
interface RoleToken {
|
|
273
|
+
text: string;
|
|
274
|
+
role: SyntaxRole;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
type RoleLine = RoleToken[];
|
|
278
|
+
|
|
278
279
|
/**
|
|
279
280
|
* Walk the AST and collect leaf nodes with their positions and colors.
|
|
280
281
|
* Returns a flat array of colored spans that covers all tokens in the code.
|
|
@@ -282,7 +283,7 @@ interface Span {
|
|
|
282
283
|
function collectSpans(root: Node, code: string): Span[] {
|
|
283
284
|
const spans: Span[] = [];
|
|
284
285
|
|
|
285
|
-
function getStyle(node: Node):
|
|
286
|
+
function getStyle(node: Node): SyntaxRole | null {
|
|
286
287
|
// Named nodes (keywords, identifiers, etc.)
|
|
287
288
|
const namedStyle = NODE_TYPE_COLORS[node.type];
|
|
288
289
|
if (namedStyle) return namedStyle;
|
|
@@ -309,9 +310,7 @@ function collectSpans(root: Node, code: string): Span[] {
|
|
|
309
310
|
endRow: node.endPosition.row,
|
|
310
311
|
endCol: node.endPosition.column,
|
|
311
312
|
text,
|
|
312
|
-
|
|
313
|
-
bold: style?.bold,
|
|
314
|
-
italic: style?.italic,
|
|
313
|
+
role: style ?? DEFAULT_ROLE,
|
|
315
314
|
});
|
|
316
315
|
return;
|
|
317
316
|
}
|
|
@@ -329,9 +328,7 @@ function collectSpans(root: Node, code: string): Span[] {
|
|
|
329
328
|
endRow: node.endPosition.row,
|
|
330
329
|
endCol: node.endPosition.column,
|
|
331
330
|
text,
|
|
332
|
-
|
|
333
|
-
bold: style.bold,
|
|
334
|
-
italic: style.italic,
|
|
331
|
+
role: style,
|
|
335
332
|
});
|
|
336
333
|
return;
|
|
337
334
|
}
|
|
@@ -371,8 +368,8 @@ function isLeafLike(node: Node): boolean {
|
|
|
371
368
|
* Convert a flat list of positioned spans into per-line SyntaxToken arrays.
|
|
372
369
|
* Handles multi-line spans (e.g., block comments, template literals).
|
|
373
370
|
*/
|
|
374
|
-
function spansToLines(spans: Span[], codeLines: string[]):
|
|
375
|
-
const result:
|
|
371
|
+
function spansToLines(spans: Span[], codeLines: string[]): RoleLine[] {
|
|
372
|
+
const result: RoleLine[] = codeLines.map(() => []);
|
|
376
373
|
|
|
377
374
|
// Track the current position to emit default-colored text for gaps
|
|
378
375
|
const linePositions: number[] = codeLines.map(() => 0);
|
|
@@ -390,12 +387,12 @@ function spansToLines(spans: Span[], codeLines: string[]): HighlightedLine[] {
|
|
|
390
387
|
const currentCol = linePositions[row];
|
|
391
388
|
if (currentCol < span.startCol) {
|
|
392
389
|
const gapText = codeLines[row].slice(currentCol, span.startCol);
|
|
393
|
-
if (gapText) result[row].push({ text: gapText,
|
|
390
|
+
if (gapText) result[row].push({ text: gapText, role: DEFAULT_ROLE });
|
|
394
391
|
}
|
|
395
392
|
|
|
396
393
|
const tokenText = codeLines[row].slice(span.startCol, span.endCol);
|
|
397
394
|
if (tokenText) {
|
|
398
|
-
result[row].push({ text: tokenText,
|
|
395
|
+
result[row].push({ text: tokenText, role: span.role });
|
|
399
396
|
}
|
|
400
397
|
linePositions[row] = span.endCol;
|
|
401
398
|
} else {
|
|
@@ -413,12 +410,12 @@ function spansToLines(spans: Span[], codeLines: string[]): HighlightedLine[] {
|
|
|
413
410
|
const currentCol = linePositions[r];
|
|
414
411
|
if (currentCol < colStart) {
|
|
415
412
|
const gapText = codeLines[r].slice(currentCol, colStart);
|
|
416
|
-
if (gapText) result[r].push({ text: gapText,
|
|
413
|
+
if (gapText) result[r].push({ text: gapText, role: DEFAULT_ROLE });
|
|
417
414
|
}
|
|
418
415
|
|
|
419
416
|
const tokenText = codeLines[r].slice(colStart, colEnd);
|
|
420
417
|
if (tokenText) {
|
|
421
|
-
result[r].push({ text: tokenText,
|
|
418
|
+
result[r].push({ text: tokenText, role: span.role });
|
|
422
419
|
}
|
|
423
420
|
linePositions[r] = colEnd;
|
|
424
421
|
}
|
|
@@ -428,7 +425,7 @@ function spansToLines(spans: Span[], codeLines: string[]): HighlightedLine[] {
|
|
|
428
425
|
// Fill remaining text on each line with default color
|
|
429
426
|
for (let r = 0; r < codeLines.length; r++) {
|
|
430
427
|
const remaining = codeLines[r].slice(linePositions[r]);
|
|
431
|
-
if (remaining) result[r].push({ text: remaining,
|
|
428
|
+
if (remaining) result[r].push({ text: remaining, role: DEFAULT_ROLE });
|
|
432
429
|
}
|
|
433
430
|
|
|
434
431
|
return result;
|
|
@@ -438,10 +435,32 @@ function spansToLines(spans: Span[], codeLines: string[]): HighlightedLine[] {
|
|
|
438
435
|
|
|
439
436
|
const MAX_HIGHLIGHT_CACHE = 200;
|
|
440
437
|
|
|
438
|
+
/** A parsed block: roles, plus the colours resolved for the last theme read. */
|
|
439
|
+
interface HighlightEntry {
|
|
440
|
+
readonly roles: RoleLine[];
|
|
441
|
+
resolved?: HighlightedLine[];
|
|
442
|
+
resolvedFor?: Readonly<Record<SyntaxRole, SyntaxStyle>>;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function resolveRoleLines(roles: RoleLine[], styles: Readonly<Record<SyntaxRole, SyntaxStyle>>): HighlightedLine[] {
|
|
446
|
+
return roles.map((line) => line.map(({ text, role }) => {
|
|
447
|
+
const style = styles[role];
|
|
448
|
+
return style.italic ? { text, fg: style.fg, italic: true } : { text, fg: style.fg };
|
|
449
|
+
}));
|
|
450
|
+
}
|
|
451
|
+
|
|
441
452
|
export class SyntaxHighlighter {
|
|
442
453
|
private service: TreeSitterService;
|
|
443
|
-
private cache: Map<string,
|
|
454
|
+
private cache: Map<string, HighlightEntry> = new Map();
|
|
444
455
|
private pending: Set<string> = new Set();
|
|
456
|
+
/** Blocks whose parse cannot succeed (no grammar, parse failed): never rescheduled, always the regex tokens. */
|
|
457
|
+
private failed: Set<string> = new Set();
|
|
458
|
+
private inflight: Set<Promise<void>> = new Set();
|
|
459
|
+
private readonly readyListeners = new Set<() => void>();
|
|
460
|
+
private _generation = 0;
|
|
461
|
+
private _misses = 0;
|
|
462
|
+
/** Numbers each parse's virtual path, so no two parses in flight share a tree. */
|
|
463
|
+
private parseSeq = 0;
|
|
445
464
|
|
|
446
465
|
constructor() {
|
|
447
466
|
this.service = new TreeSitterService();
|
|
@@ -477,13 +496,24 @@ export class SyntaxHighlighter {
|
|
|
477
496
|
|
|
478
497
|
const key = `${langId}:${hashString(code)}`;
|
|
479
498
|
const cached = this.cache.get(key);
|
|
480
|
-
if (cached)
|
|
499
|
+
if (cached) {
|
|
500
|
+
// Colours follow the active theme: re-resolve when the theme changed.
|
|
501
|
+
const styles = syntaxStyles();
|
|
502
|
+
if (cached.resolvedFor !== styles || cached.resolved === undefined) {
|
|
503
|
+
cached.resolved = resolveRoleLines(cached.roles, styles);
|
|
504
|
+
cached.resolvedFor = styles;
|
|
505
|
+
}
|
|
506
|
+
return cached.resolved;
|
|
507
|
+
}
|
|
481
508
|
|
|
482
509
|
// Do not schedule background parse while the block is still being streamed.
|
|
483
510
|
// The regex tokenizer serves during streaming (as designed). Schedule parse
|
|
484
511
|
// only when isStreaming=false, i.e., the block has been finalized.
|
|
485
|
-
if (!isStreaming && !this.
|
|
486
|
-
|
|
512
|
+
if (!isStreaming && !this.failed.has(key)) {
|
|
513
|
+
// The caller draws the regex placeholder and a parse result is on its
|
|
514
|
+
// way: whatever it caches must be redrawn when the result lands.
|
|
515
|
+
this._misses++;
|
|
516
|
+
if (!this.pending.has(key)) this.scheduleParse(code, langId, key);
|
|
487
517
|
}
|
|
488
518
|
|
|
489
519
|
return null; // not ready yet
|
|
@@ -496,22 +526,33 @@ export class SyntaxHighlighter {
|
|
|
496
526
|
private scheduleParse(code: string, langId: string, key: string): void {
|
|
497
527
|
this.pending.add(key);
|
|
498
528
|
|
|
499
|
-
//
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
529
|
+
// Each parse gets its own virtual path. The service keeps one tree per
|
|
530
|
+
// path and deletes the previous tree when the same path is parsed again,
|
|
531
|
+
// so a shared path let a second parse in flight free this parse's tree
|
|
532
|
+
// before collectSpans walked it: the spans came from freed memory, the
|
|
533
|
+
// wrong colours were cached for good, and which ones depended on what
|
|
534
|
+
// else was being highlighted at the time. The tree is released (the
|
|
535
|
+
// service's invalidate) once this parse has walked it.
|
|
536
|
+
const virtualPath = `__highlight__/${++this.parseSeq}.${langId}`;
|
|
537
|
+
|
|
538
|
+
const run = Promise.resolve().then(async () => {
|
|
539
|
+
let landed = false;
|
|
540
|
+
let parsed = false;
|
|
503
541
|
try {
|
|
504
542
|
// Ensure the grammar is loaded
|
|
505
543
|
const language = await this.service.loadLanguage(langId);
|
|
506
544
|
if (!language) {
|
|
507
545
|
logger.debug('SyntaxHighlighter: grammar not available', { langId });
|
|
546
|
+
this.markFailed(key);
|
|
508
547
|
return;
|
|
509
548
|
}
|
|
510
549
|
|
|
511
550
|
// Parse the code
|
|
512
551
|
const tree = await this.service.parse(virtualPath, code, langId);
|
|
552
|
+
parsed = true;
|
|
513
553
|
if (!tree) {
|
|
514
554
|
logger.debug('SyntaxHighlighter: parse returned null', { langId });
|
|
555
|
+
this.markFailed(key);
|
|
515
556
|
return;
|
|
516
557
|
}
|
|
517
558
|
|
|
@@ -526,19 +567,61 @@ export class SyntaxHighlighter {
|
|
|
526
567
|
if (firstKey !== undefined) this.cache.delete(firstKey);
|
|
527
568
|
}
|
|
528
569
|
|
|
529
|
-
this.cache.set(key, highlighted);
|
|
570
|
+
this.cache.set(key, { roles: highlighted });
|
|
571
|
+
this._generation++;
|
|
572
|
+
landed = true;
|
|
530
573
|
logger.debug('SyntaxHighlighter: parsed and cached', { langId, lines: codeLines.length });
|
|
531
574
|
} catch (err) {
|
|
532
575
|
logger.warn('SyntaxHighlighter: parse error', { langId, error: summarizeError(err) });
|
|
576
|
+
this.markFailed(key);
|
|
533
577
|
} finally {
|
|
578
|
+
if (parsed) this.service.invalidate(virtualPath);
|
|
534
579
|
this.pending.delete(key);
|
|
535
580
|
}
|
|
581
|
+
if (landed) for (const listener of this.readyListeners) listener();
|
|
536
582
|
});
|
|
583
|
+
this.inflight.add(run);
|
|
584
|
+
void run.finally(() => { this.inflight.delete(run); });
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
private markFailed(key: string): void {
|
|
588
|
+
if (this.failed.size >= MAX_HIGHLIGHT_CACHE) {
|
|
589
|
+
const first = this.failed.values().next().value;
|
|
590
|
+
if (first !== undefined) this.failed.delete(first);
|
|
591
|
+
}
|
|
592
|
+
this.failed.add(key);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Bumped every time a parse result lands (and when the cache is cleared).
|
|
597
|
+
* Anything drawn while a parse was on its way (a miss) is stale once this
|
|
598
|
+
* moves on.
|
|
599
|
+
*/
|
|
600
|
+
get generation(): number {
|
|
601
|
+
return this._generation;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/** Bumped every time highlight() hands back the placeholder while a parse result is on its way. */
|
|
605
|
+
get missCount(): number {
|
|
606
|
+
return this._misses;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/** Be told when a parse result lands (repaint what drew the placeholder). */
|
|
610
|
+
onReady(listener: () => void): () => void {
|
|
611
|
+
this.readyListeners.add(listener);
|
|
612
|
+
return () => { this.readyListeners.delete(listener); };
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/** Resolves once no parse is in flight (tests capture settled frames with it). */
|
|
616
|
+
async settle(): Promise<void> {
|
|
617
|
+
while (this.inflight.size > 0) await Promise.all([...this.inflight]);
|
|
537
618
|
}
|
|
538
619
|
|
|
539
620
|
/** Clear all cached highlights (e.g., on theme change). */
|
|
540
621
|
clearCache(): void {
|
|
541
622
|
this.cache.clear();
|
|
623
|
+
this.failed.clear();
|
|
624
|
+
this._generation++;
|
|
542
625
|
}
|
|
543
626
|
|
|
544
627
|
/** Current cache size (for diagnostics). */
|