@cardstack/boxel-cli 0.11.0-unstable.1 → 0.11.0-unstable.3
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/bundled-realms/pretui/components/agent-mark.gts +98 -0
- package/bundled-realms/pretui/components/agent-question.gts +340 -37
- package/bundled-realms/pretui/components/answer-footer.gts +249 -0
- package/bundled-realms/pretui/components/card-embed.gts +523 -0
- package/bundled-realms/pretui/components/card-pills.gts +725 -0
- package/bundled-realms/pretui/components/code-block.gts +87 -45
- package/bundled-realms/pretui/components/collapsed-turn.gts +133 -12
- package/bundled-realms/pretui/components/compaction-chip.gts +81 -67
- package/bundled-realms/pretui/components/composer-cap.gts +516 -0
- package/bundled-realms/pretui/components/context-ring.gts +108 -0
- package/bundled-realms/pretui/components/diff-block.gts +1002 -31
- package/bundled-realms/pretui/components/diff-stat.gts +102 -0
- package/bundled-realms/pretui/components/jump-chip.gts +86 -53
- package/bundled-realms/pretui/components/markdown-stream.gts +799 -0
- package/bundled-realms/pretui/components/markdown-surface.gts +71 -0
- package/bundled-realms/pretui/components/plan-mark.gts +147 -0
- package/bundled-realms/pretui/components/prose.gts +185 -8
- package/bundled-realms/pretui/components/prose.md +6 -8
- package/bundled-realms/pretui/components/quick-look.gts +377 -0
- package/bundled-realms/pretui/components/reactions.gts +136 -0
- package/bundled-realms/pretui/components/recommendation.gts +67 -36
- package/bundled-realms/pretui/components/result-card.gts +58 -22
- package/bundled-realms/pretui/components/segmented-control.gts +13 -8
- package/bundled-realms/pretui/components/session-prep.gts +12 -11
- package/bundled-realms/pretui/components/sliding-highlight.gts +8 -3
- package/bundled-realms/pretui/components/speaker-label.gts +132 -0
- package/bundled-realms/pretui/components/state-glyph.gts +121 -0
- package/bundled-realms/pretui/components/step-list.gts +62 -60
- package/bundled-realms/pretui/components/stick-to-bottom.gts +1150 -0
- package/bundled-realms/pretui/components/system-line.gts +143 -0
- package/bundled-realms/pretui/components/task-checklist.gts +184 -0
- package/bundled-realms/pretui/components/thinking.gts +63 -94
- package/bundled-realms/pretui/components/tool-change-table.gts +285 -0
- package/bundled-realms/pretui/components/tool-gate.gts +462 -0
- package/bundled-realms/pretui/components/tool-group.gts +920 -0
- package/bundled-realms/pretui/components/tool-icon.ts +64 -0
- package/bundled-realms/pretui/components/tool-meta.gts +82 -0
- package/bundled-realms/pretui/components/tool-plan.gts +197 -0
- package/bundled-realms/pretui/components/tool-row.gts +2087 -0
- package/bundled-realms/pretui/components/turn-changes.gts +227 -0
- package/bundled-realms/pretui/components/user-bubble.gts +575 -0
- package/bundled-realms/pretui/components/work-item.gts +185 -48
- package/bundled-realms/pretui/docs/scroll-anchoring-protocol.md +136 -0
- package/bundled-realms/pretui/internal/markdown-projection.ts +438 -0
- package/bundled-realms/pretui/internal/mount-queue.ts +79 -0
- package/bundled-realms/pretui/internal/tool-phrases.ts +168 -0
- package/bundled-realms/pretui/package.json +2 -1
- package/bundled-test-harness/assets/{ai-module-creation-test-beRNwH-e.js → ai-module-creation-test-B6FQKqIl.js} +1 -1
- package/bundled-test-harness/assets/{app-QAuw0l8v.js → app-KpPqdyX3.js} +4 -4
- package/bundled-test-harness/assets/{binary-upload-dedupe-test-bI8T0QLl.js → binary-upload-dedupe-test-DN00y1tM.js} +1 -1
- package/bundled-test-harness/assets/{can-invoke-test-DVaU_yRT.js → can-invoke-test-Bl-EUFM3.js} +1 -1
- package/bundled-test-harness/assets/{card-chooser-paging-test-DnxMnXKR.js → card-chooser-paging-test-BNuZRHX3.js} +1 -1
- package/bundled-test-harness/assets/{card-chooser-test-DUSnLr-u.js → card-chooser-test-wKfPOOT4.js} +1 -1
- package/bundled-test-harness/assets/{card-def-field-def-relationships-test-GBUr3tFK.js → card-def-field-def-relationships-test-CduQ6zan.js} +1 -1
- package/bundled-test-harness/assets/{card-delete-test-CQveO1VC.js → card-delete-test-DKxolj5o.js} +1 -1
- package/bundled-test-harness/assets/{check-correctness-test-D2M2p66N.js → check-correctness-test-B-kgHztp.js} +1 -1
- package/bundled-test-harness/assets/choreo-BEHW-NOc.js +2 -0
- package/bundled-test-harness/assets/{choreo-CJfBb_EY.js → choreo-C2cpCfhA.js} +164 -13
- package/bundled-test-harness/assets/{choreo-feature-reel-test-D2OrygZv.js → choreo-feature-reel-test-CPI_6nkT.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-w07xqtO-.js → choreo-gallery-BxA9cyzF.js} +3 -3
- package/bundled-test-harness/assets/{choreo-gallery-build-order-test-yQg4JER6.js → choreo-gallery-build-order-test-CLVoseAJ.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-compositor-test-C2THCXJN.js → choreo-gallery-compositor-test-BpT5Ia6B.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-cover-test-D4T_Tk4s.js → choreo-gallery-cover-test-DL_fdSsh.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-crossing-stage-test-tHUPJ-Hy.js → choreo-gallery-crossing-stage-test-BcKHhpOK.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-demos-alive-test-BOtBOnEZ.js → choreo-gallery-demos-alive-test-KufLhXG8.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-drift-test-D_6U1Z2c.js → choreo-gallery-drift-test-Cx58rYIA.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-escort-test-DDBU0n4C.js → choreo-gallery-escort-test-Wz-emmID.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-films-test-CgW1NSdr.js → choreo-gallery-films-test-BFrVs5WW.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-filter-test-DUIsNwdO.js → choreo-gallery-filter-test-DCKQkhwe.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-fold-test-Chzlxvdr.js → choreo-gallery-fold-test-UtpDgpG9.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-guide-pages-test-DOWnqALT.js → choreo-gallery-guide-pages-test-rXDU9JLM.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-guides-test-Dra2J4LJ.js → choreo-gallery-guides-test-DfVFdIuU.js} +3 -3
- package/bundled-test-harness/assets/{choreo-gallery-hang-test-zzn1eiB-.js → choreo-gallery-hang-test-Cj7MbkFf.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-highlight-test-CW99hncQ.js → choreo-gallery-highlight-test-D6CF3IYZ.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-inline-edit-test-CYFm1kUz.js → choreo-gallery-inline-edit-test-LfHWsbqc.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-interruption-test-CYIBrKV9.js → choreo-gallery-interruption-test-CvQQAgTp.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-lone-stack-test-DL8R3SG7.js → choreo-gallery-lone-stack-test-D7Zci7fm.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-navigation-test-Dcv04kDx.js → choreo-gallery-navigation-test-DB1EnBEW.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-playhead-transport-test-Cfz7GsXl.js → choreo-gallery-playhead-transport-test-CtBlc6AJ.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-poplayout-subtree-test-CwoklBkW.js → choreo-gallery-poplayout-subtree-test-BQbmjsvI.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-presence-modes-test-BDCIZzJ0.js → choreo-gallery-presence-modes-test-CIdXVG1K.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-presentation-test-DlyiHwFf.js → choreo-gallery-presentation-test-C_rPFp29.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-reduced-motion-test-DVz5SKNR.js → choreo-gallery-reduced-motion-test-CH5w8Lw-.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-sheet-standalone-test-CpD_-BvS.js → choreo-gallery-sheet-standalone-test-CDgI4mGX.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-sheet-test-BjvAKHDL.js → choreo-gallery-sheet-test-5lsFX7Rk.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-site-test-oq_7y_PN.js → choreo-gallery-site-test-DbMa2xeC.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-stage-Bzr4_8Xd.js → choreo-gallery-stage-b3Sk86hp.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-stage-keyboard-test-a83d2euJ.js → choreo-gallery-stage-keyboard-test-ds6fT5Eg.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-stages-test-C9Sl1kH6.js → choreo-gallery-stages-test-ahioKCct.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-subdivision-test-BVVnkOwk.js → choreo-gallery-subdivision-test-g7anTDhT.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-table-plan-test-B0YdYEA0.js → choreo-gallery-table-plan-test-Bozc4uUf.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-theater-test-BLA147xw.js → choreo-gallery-theater-test-kE535d-b.js} +3 -3
- package/bundled-test-harness/assets/{choreo-gallery-tuning-test-DSXiNzCh.js → choreo-gallery-tuning-test-6rHkQBnZ.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-widget-room-test-BI574PdO.js → choreo-gallery-widget-room-test-0Vq-uTo1.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-wires-test-B6Mz2WZl.js → choreo-gallery-wires-test-CdtnAWYO.js} +1 -1
- package/bundled-test-harness/assets/clip-BLfuHi8g.js +2 -0
- package/bundled-test-harness/assets/{clip-C_pXjVvY.js → clip-q1QE5f73.js} +1 -1
- package/bundled-test-harness/assets/{codeblocks-test-AdolhdCA.js → codeblocks-test-BDD7qK_V.js} +1 -1
- package/bundled-test-harness/assets/{compaction-test-DvyAQq56.js → compaction-test-BweolkhG.js} +1 -1
- package/bundled-test-harness/assets/{container-DIF_9lZ9.css → container-B5B6u-xm.css} +29 -22
- package/bundled-test-harness/assets/{container-DvvqmNpg.js → container-BnJjIM6J.js} +9 -6
- package/bundled-test-harness/assets/{create-listing-modal-test-D95JfQab.js → create-listing-modal-test-FvcAKpIa.js} +1 -1
- package/bundled-test-harness/assets/{debug-message-test-BcNtj7Wr.js → debug-message-test-CJ18Evt7.js} +1 -1
- package/bundled-test-harness/assets/{externals-6ZWftYIb.js → externals-BWjhmCD4.js} +12 -12
- package/bundled-test-harness/assets/{fallback-banner-live-delete-test-5lnN1pv2.js → fallback-banner-live-delete-test-C2H3BrT-.js} +1 -1
- package/bundled-test-harness/assets/{fallback-banner-test-DFb_m_Kq.js → fallback-banner-test-C10aYjkj.js} +1 -1
- package/bundled-test-harness/assets/{fallback-models-test-p3YIwj1L.js → fallback-models-test-BxoPiaGF.js} +1 -1
- package/bundled-test-harness/assets/{file-attachment-test-CmyjNWRR.js → file-attachment-test-DeKyzYaz.js} +1 -1
- package/bundled-test-harness/assets/film-B68PjRlr.js +2 -0
- package/bundled-test-harness/assets/{film-D7vtJO5q.js → film-CeUpnHN_.js} +6 -6
- package/bundled-test-harness/assets/{film-DN0A8JHx.js → film-DBZqyqW4.js} +6 -6
- package/bundled-test-harness/assets/{general-test-CX-TFDuX.js → general-test-BewzrpoB.js} +1 -1
- package/bundled-test-harness/assets/{glimmer-motion-and-choreo-in-cards-test-BHte2t9G.js → glimmer-motion-and-choreo-in-cards-test-HA-INN3d.js} +14 -14
- package/bundled-test-harness/assets/{glimmer-motion-and-choreo-test-BPNVeIio.js → glimmer-motion-and-choreo-test-BZ0ZuuAw.js} +3 -3
- package/bundled-test-harness/assets/{index-query-engine-test-Dz7Q4Xmk.js → index-query-engine-test-BtI8Y2LR.js} +1 -1
- package/bundled-test-harness/assets/{instance-filter-matcher-test-BoLU3bVg.js → instance-filter-matcher-test-BHXHGHjE.js} +1 -1
- package/bundled-test-harness/assets/{main-Dlgp8c7v.js → main-CqAHZ2kK.js} +1 -1
- package/bundled-test-harness/assets/{message-tool-test-CfjRSJvt.js → message-tool-test-NMaPJFsd.js} +1 -1
- package/bundled-test-harness/assets/{model-cost-tier-test-mZG84Uso.js → model-cost-tier-test-BgbfN2LH.js} +1 -1
- package/bundled-test-harness/assets/{operation-lowering-test-B0KpI1xu.js → operation-lowering-test-C4IaOjsA.js} +1 -1
- package/bundled-test-harness/assets/{operator-mode-card-chooser-test-KWS8lq16.js → operator-mode-card-chooser-test-Bwe47Rn7.js} +1 -1
- package/bundled-test-harness/assets/{operator-mode-links-test-CViFt0Ws.js → operator-mode-links-test-BU4jkC-B.js} +1 -1
- package/bundled-test-harness/assets/{operator-mode-test-D0gZM2yw.js → operator-mode-test-Bh0rGM9z.js} +1 -1
- package/bundled-test-harness/assets/{operator-mode-ui-test-CEvHJ9DS.js → operator-mode-ui-test-whgUw0ju.js} +1 -1
- package/bundled-test-harness/assets/{overlay-menu-items-test-Pw80RXsr.js → overlay-menu-items-test-Cwn1vr8q.js} +1 -1
- package/bundled-test-harness/assets/{overlays-BnOM360u.js → overlays-BOzLbVDK.js} +1 -1
- package/bundled-test-harness/assets/{overlays-kBVfa4i8.js → overlays-C_PuGDGh.js} +1 -1
- package/bundled-test-harness/assets/{past-sessions-test-CW0-Bjav.js → past-sessions-test-Bh24N-Kz.js} +1 -1
- package/bundled-test-harness/assets/{permission-checking-realm-test-D5uXQypS.js → permission-checking-realm-test-DKnnAy0f.js} +1 -1
- package/bundled-test-harness/assets/{plate-CFvCDv2t.js → plate-BL7UtyKE.js} +1 -1
- package/bundled-test-harness/assets/plate-CqG3igCJ.js +2 -0
- package/bundled-test-harness/assets/player-BJfsgdSI.js +2 -0
- package/bundled-test-harness/assets/{player-DzuYjrTU.js → player-Da_CIsp_.js} +1 -1
- package/bundled-test-harness/assets/{realm-config-policy-standing-test-Btatheni.js → realm-config-policy-standing-test-DngdYypq.js} +1 -1
- package/bundled-test-harness/assets/{realm-config-routing-rule-edit-test-Dh5zW13o.js → realm-config-routing-rule-edit-test-_rkFU8SN.js} +1 -1
- package/bundled-test-harness/assets/{realm-config-settings-test-BJa8gv_C.js → realm-config-settings-test-CR9NH0Zn.js} +1 -1
- package/bundled-test-harness/assets/{reasoning-test-2Zp37cQB.js → reasoning-test-0lH10kRl.js} +1 -1
- package/bundled-test-harness/assets/{room-message-test-BsrON13e.js → room-message-test-C-PddE4Y.js} +1 -1
- package/bundled-test-harness/assets/{run-BMyvn_UL.js → run-D4n4LPcK.js} +96 -6
- package/bundled-test-harness/assets/{run-realm-code-BxktQf6R.js → run-realm-code-C8N7DtN-.js} +1 -1
- package/bundled-test-harness/assets/{run-realm-code-test-7grpTos-.js → run-realm-code-test-DBdp147q.js} +1 -1
- package/bundled-test-harness/assets/{schema-generation-test-CM9ggQji.js → schema-generation-test-CB1Hb7GQ.js} +1 -1
- package/bundled-test-harness/assets/{scrolling-test-BER00htB.js → scrolling-test-3PhVin8h.js} +1 -1
- package/bundled-test-harness/assets/{searchable-option-test-BBr5BPow.js → searchable-option-test-BAdOiL4U.js} +1 -1
- package/bundled-test-harness/assets/{sending-test-BJGobs2E.js → sending-test-KSmu1tSJ.js} +1 -1
- package/bundled-test-harness/assets/{skills-test-GPOsHRN6.js → skills-test-D-dlcFpd.js} +1 -1
- package/bundled-test-harness/assets/{src-Df148w_b.js → src-B_DEhPze.js} +4 -4
- package/bundled-test-harness/assets/{src-Cf2wv-Le.js → src-BoTgwqUA.js} +2 -2
- package/bundled-test-harness/assets/{store-test-QvvHimzH.js → store-test-CEW7xQjZ.js} +1 -1
- package/bundled-test-harness/assets/{test-support-rNHuieEc.js → test-support-FXDPpIl3.js} +1 -1
- package/bundled-test-harness/assets/{test-support-C6N_2X-l.js → test-support-VX6ukQNR.js} +1 -1
- package/bundled-test-harness/assets/{tests-CmsQ-Z_3.js → tests-QYqqa6nv.js} +84 -84
- package/bundled-test-harness/assets/{text-input-validator-test-CYYH2N0L.js → text-input-validator-test-D8iNOWep.js} +1 -1
- package/bundled-test-harness/assets/{titles-B1pTaIfP.js → titles-DFJj5O0E.js} +1 -1
- package/bundled-test-harness/assets/titles-xGlgu7ys.js +2 -0
- package/bundled-test-harness/assets/{to-device-streaming-test-Ctqa0bVp.js → to-device-streaming-test-trCX-505.js} +1 -1
- package/bundled-test-harness/assets/{token-usage-test-BQVbK3X0.js → token-usage-test-C6sULooH.js} +1 -1
- package/bundled-test-harness/assets/{tools-DU8Rc15s.js → tools-UJfD-_al.js} +1 -1
- package/bundled-test-harness/assets/{tools-test-B13yvEKE.js → tools-test-Brav31AV.js} +1 -1
- package/bundled-test-harness/assets/{worker-DHBEBihX.js → worker-9ph9_Lpv.js} +31 -26
- package/bundled-test-harness/index.html +8 -8
- package/bundled-test-harness/tests/index.html +8 -8
- package/bundled-types/choreo/beacons.ts +16 -4
- package/bundled-types/choreo/changeset.ts +25 -5
- package/bundled-types/choreo/choreo.gts +245 -3
- package/bundled-types/choreo/far.ts +39 -0
- package/bundled-types/choreo/index.ts +2 -0
- package/bundled-types/choreo/run.ts +104 -1
- package/bundled-types/host-app/lib/realm-runner/worker.ts +8 -51
- package/bundled-types/runtime-common/realm-runner-script.d.ts +3 -0
- package/package.json +1 -1
- package/bundled-test-harness/assets/choreo-B3Z9QhDH.js +0 -2
- package/bundled-test-harness/assets/clip-BjcXuTwN.js +0 -2
- package/bundled-test-harness/assets/film-DMDPP7ma.js +0 -2
- package/bundled-test-harness/assets/plate-66qnQ-M9.js +0 -2
- package/bundled-test-harness/assets/player-pygg1PwA.js +0 -2
- package/bundled-test-harness/assets/titles-Ccpcl-jq.js +0 -2
|
@@ -1,86 +1,1057 @@
|
|
|
1
|
-
// Pretui — DiffBlock: a before/after change rendered as a line diff.
|
|
1
|
+
// Pretui — DiffBlock: a before/after change rendered as a line diff, syntax-highlighted when it knows the language.
|
|
2
2
|
import Component from '@glimmer/component';
|
|
3
|
-
import
|
|
3
|
+
import CheckIcon from '@cardstack/boxel-icons/check';
|
|
4
|
+
import { cached, tracked } from '@glimmer/tracking';
|
|
4
5
|
import { on } from '@ember/modifier';
|
|
6
|
+
import { fn } from '@ember/helper';
|
|
7
|
+
import { modifier } from 'ember-modifier';
|
|
8
|
+
import { DiffStat } from './diff-stat';
|
|
9
|
+
import { languageForFilename, tokenizeCode } from './code-block';
|
|
10
|
+
import type { CodeToken } from './code-block';
|
|
5
11
|
|
|
6
12
|
export interface DiffLine {
|
|
7
13
|
op?: 'add' | 'del';
|
|
8
14
|
text: string;
|
|
15
|
+
/** this line stands for that many unchanged lines left out between two
|
|
16
|
+
* hunks; `text` is ignored */
|
|
17
|
+
skip?: number;
|
|
18
|
+
/** the line's number: in the new file, or the old one for a removal */
|
|
19
|
+
n?: number;
|
|
9
20
|
}
|
|
10
21
|
|
|
22
|
+
// Above this many cells the middle of a diff is not aligned line by line: it
|
|
23
|
+
// reads as everything removed, then everything added. A file the runner
|
|
24
|
+
// rewrites wholesale says so honestly, and the tab never stalls on it.
|
|
25
|
+
const MAX_DIFF_CELLS = 4_000_000;
|
|
26
|
+
|
|
27
|
+
function splitLines(text: string | undefined): string[] {
|
|
28
|
+
if (!text) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
let lines = text.split('\n');
|
|
32
|
+
// a trailing newline ends the last line; it does not start another
|
|
33
|
+
if (lines[lines.length - 1] === '') {
|
|
34
|
+
lines.pop();
|
|
35
|
+
}
|
|
36
|
+
return lines;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The lines that changed from `before` to `after`, with `context` unchanged
|
|
41
|
+
* lines around each change and a `skip` line for each run left out. An
|
|
42
|
+
* absent `before` is a new file: every line is added.
|
|
43
|
+
*
|
|
44
|
+
* Common lines at either end are trimmed before the middle is aligned by
|
|
45
|
+
* longest common subsequence, so an edit to a long file costs its changed
|
|
46
|
+
* region, not the whole file.
|
|
47
|
+
*/
|
|
48
|
+
export function lineDiff(
|
|
49
|
+
before: string | undefined,
|
|
50
|
+
after: string | undefined,
|
|
51
|
+
options: { context?: number } = {},
|
|
52
|
+
): DiffLine[] {
|
|
53
|
+
let context = Math.max(0, options.context ?? 3);
|
|
54
|
+
let a = splitLines(before);
|
|
55
|
+
let b = splitLines(after);
|
|
56
|
+
let start = 0;
|
|
57
|
+
while (start < a.length && start < b.length && a[start] === b[start]) {
|
|
58
|
+
start++;
|
|
59
|
+
}
|
|
60
|
+
let endA = a.length;
|
|
61
|
+
let endB = b.length;
|
|
62
|
+
while (endA > start && endB > start && a[endA - 1] === b[endB - 1]) {
|
|
63
|
+
endA--;
|
|
64
|
+
endB--;
|
|
65
|
+
}
|
|
66
|
+
let middle: DiffLine[] = [];
|
|
67
|
+
let n = endA - start;
|
|
68
|
+
let m = endB - start;
|
|
69
|
+
if (n * m > MAX_DIFF_CELLS) {
|
|
70
|
+
for (let i = start; i < endA; i++) {
|
|
71
|
+
middle.push({ op: 'del', text: a[i]! });
|
|
72
|
+
}
|
|
73
|
+
for (let j = start; j < endB; j++) {
|
|
74
|
+
middle.push({ op: 'add', text: b[j]! });
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
// lcs[i][j]: the longest common run of a[start+i..] and b[start+j..]
|
|
78
|
+
let width = m + 1;
|
|
79
|
+
let lcs = new Uint32Array((n + 1) * width);
|
|
80
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
81
|
+
for (let j = m - 1; j >= 0; j--) {
|
|
82
|
+
lcs[i * width + j] =
|
|
83
|
+
a[start + i] === b[start + j]
|
|
84
|
+
? lcs[(i + 1) * width + j + 1]! + 1
|
|
85
|
+
: Math.max(lcs[(i + 1) * width + j]!, lcs[i * width + j + 1]!);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
let i = 0;
|
|
89
|
+
let j = 0;
|
|
90
|
+
while (i < n || j < m) {
|
|
91
|
+
if (i < n && j < m && a[start + i] === b[start + j]) {
|
|
92
|
+
middle.push({ text: a[start + i]! });
|
|
93
|
+
i++;
|
|
94
|
+
j++;
|
|
95
|
+
} else if (
|
|
96
|
+
i < n &&
|
|
97
|
+
(j === m || lcs[(i + 1) * width + j]! >= lcs[i * width + j + 1]!)
|
|
98
|
+
) {
|
|
99
|
+
// a removal reads before the line that replaces it
|
|
100
|
+
middle.push({ op: 'del', text: a[start + i]! });
|
|
101
|
+
i++;
|
|
102
|
+
} else {
|
|
103
|
+
middle.push({ op: 'add', text: b[start + j]! });
|
|
104
|
+
j++;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (!middle.some((line) => line.op)) {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
let all: DiffLine[] = [
|
|
112
|
+
...a.slice(0, start).map((text) => ({ text })),
|
|
113
|
+
...middle,
|
|
114
|
+
...a.slice(endA).map((text) => ({ text })),
|
|
115
|
+
];
|
|
116
|
+
let oldN = 0;
|
|
117
|
+
let newN = 0;
|
|
118
|
+
for (let line of all) {
|
|
119
|
+
if (line.op === 'del') {
|
|
120
|
+
line.n = ++oldN;
|
|
121
|
+
} else if (line.op === 'add') {
|
|
122
|
+
line.n = ++newN;
|
|
123
|
+
} else {
|
|
124
|
+
oldN++;
|
|
125
|
+
line.n = ++newN;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// keep each unchanged line within `context` of a change; fold the rest
|
|
129
|
+
let keep = all.map(() => false);
|
|
130
|
+
all.forEach((line, index) => {
|
|
131
|
+
if (line.op) {
|
|
132
|
+
for (
|
|
133
|
+
let k = Math.max(0, index - context);
|
|
134
|
+
k <= Math.min(all.length - 1, index + context);
|
|
135
|
+
k++
|
|
136
|
+
) {
|
|
137
|
+
keep[k] = true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
let out: DiffLine[] = [];
|
|
142
|
+
let skipped = 0;
|
|
143
|
+
all.forEach((line, index) => {
|
|
144
|
+
if (keep[index]) {
|
|
145
|
+
if (skipped) {
|
|
146
|
+
out.push({ text: '', skip: skipped });
|
|
147
|
+
skipped = 0;
|
|
148
|
+
}
|
|
149
|
+
out.push(line);
|
|
150
|
+
} else {
|
|
151
|
+
skipped++;
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
if (skipped) {
|
|
155
|
+
out.push({ text: '', skip: skipped });
|
|
156
|
+
}
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
/** which file a line number counts in: the old file for a removed line, the
|
|
162
|
+
* new file for an added or unchanged one */
|
|
163
|
+
export type DiffSide = 'old' | 'new';
|
|
164
|
+
|
|
11
165
|
export interface DiffBlockSignature {
|
|
12
|
-
Args: {
|
|
166
|
+
Args: {
|
|
167
|
+
lines: DiffLine[];
|
|
168
|
+
receipt?: string;
|
|
169
|
+
maxLines?: number;
|
|
170
|
+
/** highlight each line with CodeBlock's grammar for this language */
|
|
171
|
+
language?: string;
|
|
172
|
+
/** shown as a header with the change counts; also guesses the
|
|
173
|
+
* language when @language is absent */
|
|
174
|
+
filename?: string;
|
|
175
|
+
/** the collapsed toggle's verb, before its count: 'Show all' reads
|
|
176
|
+
* "Show all · 14 more lines". Default: "14 more lines" */
|
|
177
|
+
expandLabel?: string;
|
|
178
|
+
/** a gutter with each line's number, where the lines carry one */
|
|
179
|
+
lineNumbers?: boolean;
|
|
180
|
+
/** review a hunk at a time: each hunk gets Keep and Revert on its bar */
|
|
181
|
+
onKeepHunk?: (hunk: DiffHunk) => unknown;
|
|
182
|
+
onRevertHunk?: (hunk: DiffHunk) => unknown;
|
|
183
|
+
/** what was decided per hunk, by index: a kept hunk says so, a
|
|
184
|
+
* reverted one is struck */
|
|
185
|
+
hunkStates?: Record<number, DiffHunkState>;
|
|
186
|
+
/** comments on lines, shown under their line */
|
|
187
|
+
comments?: DiffComment[];
|
|
188
|
+
/** with it, a line's number is a button that opens a comment there.
|
|
189
|
+
* `side` says which file `line` counts in, so a removed line and the
|
|
190
|
+
* added line that replaces it, which can share a number, stay apart */
|
|
191
|
+
onComment?: (line: number, text: string, side: DiffSide) => unknown;
|
|
192
|
+
/** 'unified' (default), 'split' (old left, new right), or 'auto':
|
|
193
|
+
* split once the block is 640px wide. Both views carry the same review
|
|
194
|
+
* controls and comments */
|
|
195
|
+
view?: 'unified' | 'split' | 'auto';
|
|
196
|
+
/** soft-wrap long lines instead of scrolling them */
|
|
197
|
+
wrap?: boolean;
|
|
198
|
+
/** above this many characters the diff is not drawn until asked:
|
|
199
|
+
* "Large diff · 12,400 lines · Render anyway" */
|
|
200
|
+
maxBytes?: number;
|
|
201
|
+
};
|
|
13
202
|
Element: HTMLDivElement;
|
|
14
203
|
}
|
|
15
204
|
|
|
205
|
+
interface ShownLine {
|
|
206
|
+
/** the line's index in @lines */
|
|
207
|
+
index: number;
|
|
208
|
+
op?: 'add' | 'del';
|
|
209
|
+
n?: number;
|
|
210
|
+
side: DiffSide;
|
|
211
|
+
sign: string;
|
|
212
|
+
tokens: CodeToken[];
|
|
213
|
+
skip?: string;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function skipText(count: number): string {
|
|
217
|
+
return `${count} unchanged ${count === 1 ? 'line' : 'lines'}`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** a run of changed lines between two skips: what Keep and Revert act on */
|
|
221
|
+
export interface DiffHunk {
|
|
222
|
+
index: number;
|
|
223
|
+
/** the first and last line of the hunk, as indexes into the lines */
|
|
224
|
+
start: number;
|
|
225
|
+
end: number;
|
|
226
|
+
added: number;
|
|
227
|
+
removed: number;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** the hunks of a diff: the runs of lines between skip lines that change
|
|
231
|
+
* something */
|
|
232
|
+
export function diffHunks(lines: DiffLine[]): DiffHunk[] {
|
|
233
|
+
let hunks: DiffHunk[] = [];
|
|
234
|
+
let start = -1;
|
|
235
|
+
let flush = (end: number) => {
|
|
236
|
+
if (start < 0) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
let run = lines.slice(start, end + 1);
|
|
240
|
+
let added = run.filter((l) => l.op === 'add').length;
|
|
241
|
+
let removed = run.filter((l) => l.op === 'del').length;
|
|
242
|
+
if (added || removed) {
|
|
243
|
+
hunks.push({ index: hunks.length, start, end, added, removed });
|
|
244
|
+
}
|
|
245
|
+
start = -1;
|
|
246
|
+
};
|
|
247
|
+
lines.forEach((line, i) => {
|
|
248
|
+
if (line.skip) {
|
|
249
|
+
flush(i - 1);
|
|
250
|
+
} else if (start < 0) {
|
|
251
|
+
start = i;
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
flush(lines.length - 1);
|
|
255
|
+
return hunks;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export interface DiffComment {
|
|
259
|
+
/** the line it is on, numbered in the file `side` names */
|
|
260
|
+
line: number;
|
|
261
|
+
/** 'old' for a comment on a removed line; 'new' (the default) for an
|
|
262
|
+
* added or unchanged one */
|
|
263
|
+
side?: DiffSide;
|
|
264
|
+
author: string;
|
|
265
|
+
text: string;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export type DiffHunkState = 'kept' | 'reverted';
|
|
269
|
+
|
|
270
|
+
/** one line's place in a row: the whole row in the unified view, one half
|
|
271
|
+
* of it in the split view */
|
|
272
|
+
interface DiffCell {
|
|
273
|
+
line?: ShownLine;
|
|
274
|
+
/** whether the line's number opens a comment and its comments show here.
|
|
275
|
+
* In the split view an unchanged line shows on both halves, and only the
|
|
276
|
+
* new half carries its comments */
|
|
277
|
+
commentable: boolean;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
interface DiffRow {
|
|
281
|
+
key: string;
|
|
282
|
+
/** the hunk whose bar sits above this row, when hunks are reviewed */
|
|
283
|
+
hunk?: DiffHunk;
|
|
284
|
+
skip?: string;
|
|
285
|
+
cells: DiffCell[];
|
|
286
|
+
}
|
|
287
|
+
|
|
16
288
|
// Staged change with an immutable receipt line; long diffs collapse.
|
|
17
289
|
export class DiffBlock extends Component<DiffBlockSignature> {
|
|
18
290
|
@tracked collapsed = true;
|
|
291
|
+
@tracked renderAnyway = false;
|
|
292
|
+
@tracked wide = false;
|
|
293
|
+
@tracked commentingOn: { side: DiffSide; n: number } | undefined;
|
|
294
|
+
@tracked commentDraft = '';
|
|
295
|
+
/** the hunk whose Keep or Revert was just pressed: its decision takes the
|
|
296
|
+
* focus the removed button held */
|
|
297
|
+
private focusHunk: number | undefined;
|
|
298
|
+
/** the line number that opened the comment editor, to take focus back */
|
|
299
|
+
private commentOpener: HTMLElement | undefined;
|
|
300
|
+
|
|
301
|
+
get tooLarge(): boolean {
|
|
302
|
+
let max = this.args.maxBytes;
|
|
303
|
+
if (!max || this.renderAnyway) {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
let size = 0;
|
|
307
|
+
for (let line of this.args.lines) {
|
|
308
|
+
size += line.text.length + 1;
|
|
309
|
+
if (size > max) {
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
get largeLabel(): string {
|
|
316
|
+
let n = this.args.lines.length;
|
|
317
|
+
return `Large diff · ${n.toLocaleString()} ${n === 1 ? 'line' : 'lines'}`;
|
|
318
|
+
}
|
|
319
|
+
showLarge = () => (this.renderAnyway = true);
|
|
320
|
+
|
|
321
|
+
get split(): boolean {
|
|
322
|
+
let view = this.args.view ?? 'unified';
|
|
323
|
+
return view === 'split' || (view === 'auto' && this.wide);
|
|
324
|
+
}
|
|
325
|
+
// 'auto' measures its own width: split from 640px
|
|
326
|
+
watchWidth = modifier(
|
|
327
|
+
(element: HTMLElement, [view]: [string | undefined]) => {
|
|
328
|
+
if (view !== 'auto' || typeof ResizeObserver !== 'function') {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
let observer = new ResizeObserver(([entry]) => {
|
|
332
|
+
let wide = (entry?.contentRect.width ?? 0) >= 640;
|
|
333
|
+
if (wide !== this.wide) {
|
|
334
|
+
this.wide = wide;
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
observer.observe(element);
|
|
338
|
+
return () => observer.disconnect();
|
|
339
|
+
},
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
@cached
|
|
343
|
+
get hunks(): DiffHunk[] {
|
|
344
|
+
return diffHunks(this.args.lines);
|
|
345
|
+
}
|
|
346
|
+
get reviews(): boolean {
|
|
347
|
+
return Boolean(this.args.onKeepHunk || this.args.onRevertHunk);
|
|
348
|
+
}
|
|
349
|
+
/** the hunk that starts at a line index, for its bar */
|
|
350
|
+
private hunkAt(index: number): DiffHunk | undefined {
|
|
351
|
+
return this.reviews
|
|
352
|
+
? this.hunks.find((h) => h.start === index)
|
|
353
|
+
: undefined;
|
|
354
|
+
}
|
|
355
|
+
private hunkOf(index: number): DiffHunk | undefined {
|
|
356
|
+
return this.hunks.find((h) => index >= h.start && index <= h.end);
|
|
357
|
+
}
|
|
358
|
+
stateOf = (hunk: DiffHunk) => this.args.hunkStates?.[hunk.index];
|
|
359
|
+
isKept = (hunk: DiffHunk) => this.stateOf(hunk) === 'kept';
|
|
360
|
+
lineState = (line: ShownLine) => {
|
|
361
|
+
let hunk = this.hunkOf(line.index);
|
|
362
|
+
return hunk ? this.stateOf(hunk) : undefined;
|
|
363
|
+
};
|
|
364
|
+
hunkLabel = (hunk: DiffHunk) =>
|
|
365
|
+
`Change ${hunk.index + 1} of ${this.hunks.length} · +${hunk.added} −${hunk.removed}`;
|
|
366
|
+
keepHunk = (hunk: DiffHunk) => {
|
|
367
|
+
this.focusHunk = hunk.index;
|
|
368
|
+
return this.args.onKeepHunk?.(hunk);
|
|
369
|
+
};
|
|
370
|
+
revertHunk = (hunk: DiffHunk) => {
|
|
371
|
+
this.focusHunk = hunk.index;
|
|
372
|
+
return this.args.onRevertHunk?.(hunk);
|
|
373
|
+
};
|
|
374
|
+
/** a decision replaces the Keep and Revert buttons; when one of them had
|
|
375
|
+
* focus, the decision takes it, so the keyboard keeps its place */
|
|
376
|
+
claimHunkFocus = modifier((element: HTMLElement, [index]: [number]) => {
|
|
377
|
+
if (this.focusHunk !== index) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
this.focusHunk = undefined;
|
|
381
|
+
let active = document.activeElement;
|
|
382
|
+
if (!active || active === document.body || !active.isConnected) {
|
|
383
|
+
element.focus({ preventScroll: true });
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
commentsOn = (line: ShownLine) =>
|
|
388
|
+
(this.args.comments ?? []).filter(
|
|
389
|
+
(c) => c.line === line.n && (c.side ?? 'new') === line.side,
|
|
390
|
+
);
|
|
391
|
+
isCommenting = (line: ShownLine) =>
|
|
392
|
+
this.commentingOn !== undefined &&
|
|
393
|
+
this.commentingOn.n === line.n &&
|
|
394
|
+
this.commentingOn.side === line.side;
|
|
395
|
+
commentLabel = (line: ShownLine) =>
|
|
396
|
+
line.side === 'old'
|
|
397
|
+
? `Comment on removed line ${line.n}`
|
|
398
|
+
: `Comment on line ${line.n}`;
|
|
399
|
+
openComment = (line: ShownLine, event: Event) => {
|
|
400
|
+
if (line.n === undefined) {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
this.commentOpener = event.currentTarget as HTMLElement;
|
|
404
|
+
this.commentingOn = { side: line.side, n: line.n };
|
|
405
|
+
this.commentDraft = '';
|
|
406
|
+
};
|
|
407
|
+
typeComment = (event: Event) =>
|
|
408
|
+
(this.commentDraft = (event.target as HTMLTextAreaElement).value);
|
|
409
|
+
private closeComment() {
|
|
410
|
+
this.commentingOn = undefined;
|
|
411
|
+
let opener = this.commentOpener;
|
|
412
|
+
this.commentOpener = undefined;
|
|
413
|
+
if (opener?.isConnected) {
|
|
414
|
+
opener.focus({ preventScroll: true });
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
cancelComment = () => this.closeComment();
|
|
418
|
+
saveComment = async () => {
|
|
419
|
+
let target = this.commentingOn;
|
|
420
|
+
let text = this.commentDraft.trim();
|
|
421
|
+
if (target && text) {
|
|
422
|
+
await this.args.onComment?.(target.n, text, target.side);
|
|
423
|
+
}
|
|
424
|
+
this.closeComment();
|
|
425
|
+
};
|
|
426
|
+
commentKey = (e: Event) => {
|
|
427
|
+
let event = e as KeyboardEvent;
|
|
428
|
+
if (event.key === 'Escape') {
|
|
429
|
+
// the editor handles Escape; a host listening for it must not also act
|
|
430
|
+
event.stopPropagation();
|
|
431
|
+
this.cancelComment();
|
|
432
|
+
} else if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {
|
|
433
|
+
event.preventDefault();
|
|
434
|
+
this.saveComment();
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
focusComment = modifier((element: HTMLTextAreaElement) => element.focus());
|
|
438
|
+
|
|
439
|
+
/** The rows either view draws. Unified: one line per row. Split: removals
|
|
440
|
+
* on the left and additions on the right, paired within each change, and
|
|
441
|
+
* unchanged lines on both sides. A hunk's bar sits above the row its first
|
|
442
|
+
* line is in. */
|
|
443
|
+
@cached
|
|
444
|
+
get rows(): DiffRow[] {
|
|
445
|
+
let rows: DiffRow[] = [];
|
|
446
|
+
let hunkFor = (...lines: (ShownLine | undefined)[]) => {
|
|
447
|
+
for (let line of lines) {
|
|
448
|
+
let hunk = line ? this.hunkAt(line.index) : undefined;
|
|
449
|
+
if (hunk) {
|
|
450
|
+
return hunk;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return undefined;
|
|
454
|
+
};
|
|
455
|
+
if (!this.split) {
|
|
456
|
+
for (let line of this.shown) {
|
|
457
|
+
rows.push(
|
|
458
|
+
line.skip
|
|
459
|
+
? { key: `s${line.index}`, skip: line.skip, cells: [] }
|
|
460
|
+
: {
|
|
461
|
+
key: `l${line.index}`,
|
|
462
|
+
hunk: hunkFor(line),
|
|
463
|
+
cells: [{ line, commentable: true }],
|
|
464
|
+
},
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
return rows;
|
|
468
|
+
}
|
|
469
|
+
let dels: ShownLine[] = [];
|
|
470
|
+
let adds: ShownLine[] = [];
|
|
471
|
+
let flush = () => {
|
|
472
|
+
for (let k = 0; k < Math.max(dels.length, adds.length); k++) {
|
|
473
|
+
let left = dels[k];
|
|
474
|
+
let right = adds[k];
|
|
475
|
+
rows.push({
|
|
476
|
+
key: `p${(left ?? right)!.index}`,
|
|
477
|
+
// a pending hunk opens with a removal or an addition: whichever
|
|
478
|
+
// comes first in the file
|
|
479
|
+
hunk: hunkFor(left, right),
|
|
480
|
+
cells: [
|
|
481
|
+
{ line: left, commentable: true },
|
|
482
|
+
{ line: right, commentable: true },
|
|
483
|
+
],
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
dels = [];
|
|
487
|
+
adds = [];
|
|
488
|
+
};
|
|
489
|
+
for (let line of this.shown) {
|
|
490
|
+
if (line.skip) {
|
|
491
|
+
flush();
|
|
492
|
+
rows.push({ key: `s${line.index}`, skip: line.skip, cells: [] });
|
|
493
|
+
} else if (line.op === 'del') {
|
|
494
|
+
dels.push(line);
|
|
495
|
+
} else if (line.op === 'add') {
|
|
496
|
+
adds.push(line);
|
|
497
|
+
} else {
|
|
498
|
+
flush();
|
|
499
|
+
rows.push({
|
|
500
|
+
key: `c${line.index}`,
|
|
501
|
+
hunk: hunkFor(line),
|
|
502
|
+
cells: [
|
|
503
|
+
{ line, commentable: false },
|
|
504
|
+
{ line, commentable: true },
|
|
505
|
+
],
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
flush();
|
|
510
|
+
return rows;
|
|
511
|
+
}
|
|
19
512
|
get maxLines() {
|
|
20
513
|
return this.args.maxLines ?? 8;
|
|
21
514
|
}
|
|
22
515
|
get overflows() {
|
|
23
516
|
return this.args.lines.length > this.maxLines;
|
|
24
517
|
}
|
|
25
|
-
get
|
|
26
|
-
return this.
|
|
27
|
-
|
|
28
|
-
|
|
518
|
+
get language(): string | undefined {
|
|
519
|
+
return this.args.language ?? languageForFilename(this.args.filename);
|
|
520
|
+
}
|
|
521
|
+
@cached
|
|
522
|
+
get shown(): ShownLine[] {
|
|
523
|
+
let lines =
|
|
524
|
+
this.collapsed && this.overflows
|
|
525
|
+
? this.args.lines.slice(0, this.maxLines)
|
|
526
|
+
: this.args.lines;
|
|
527
|
+
let language = this.language;
|
|
528
|
+
// Each line is tokenized on its own: a diff shows fragments, and a
|
|
529
|
+
// fragment that opens a string or comment must not colour the lines
|
|
530
|
+
// after it.
|
|
531
|
+
return lines.map((line, index): ShownLine => {
|
|
532
|
+
let side: DiffSide = line.op === 'del' ? 'old' : 'new';
|
|
533
|
+
return line.skip
|
|
534
|
+
? { index, side, sign: '', tokens: [], skip: skipText(line.skip) }
|
|
535
|
+
: {
|
|
536
|
+
index,
|
|
537
|
+
op: line.op,
|
|
538
|
+
n: line.n,
|
|
539
|
+
side,
|
|
540
|
+
sign: line.op === 'add' ? '+ ' : line.op === 'del' ? '- ' : ' ',
|
|
541
|
+
tokens: tokenizeCode(line.text, language),
|
|
542
|
+
};
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
get added(): number {
|
|
546
|
+
return this.args.lines.filter((line) => line.op === 'add' && !line.skip)
|
|
547
|
+
.length;
|
|
548
|
+
}
|
|
549
|
+
get removed(): number {
|
|
550
|
+
return this.args.lines.filter((line) => line.op === 'del').length;
|
|
29
551
|
}
|
|
30
552
|
get toggleLabel() {
|
|
31
|
-
|
|
553
|
+
if (!this.collapsed) {
|
|
554
|
+
return 'collapse';
|
|
555
|
+
}
|
|
556
|
+
let hidden = this.args.lines.length - this.maxLines;
|
|
557
|
+
let more = `${hidden} more ${hidden === 1 ? 'line' : 'lines'}`;
|
|
558
|
+
return this.args.expandLabel ? `${this.args.expandLabel} · ${more}` : more;
|
|
559
|
+
}
|
|
560
|
+
get toggleExpanded() {
|
|
561
|
+
return this.collapsed ? 'false' : 'true';
|
|
32
562
|
}
|
|
33
|
-
prefix = (line: DiffLine) => (line.op === 'add' ? '+ ' : line.op === 'del' ? '- ' : ' ');
|
|
34
563
|
toggle = () => {
|
|
35
564
|
this.collapsed = !this.collapsed;
|
|
36
565
|
};
|
|
37
566
|
<template>
|
|
38
|
-
<div
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
{{
|
|
42
|
-
{{
|
|
43
|
-
|
|
567
|
+
<div
|
|
568
|
+
class='pretui-diff'
|
|
569
|
+
data-language={{this.language}}
|
|
570
|
+
data-numbered={{if @lineNumbers 'true'}}
|
|
571
|
+
data-wrap={{if @wrap 'true'}}
|
|
572
|
+
data-view={{if this.split 'split' 'unified'}}
|
|
573
|
+
{{this.watchWidth @view}}
|
|
574
|
+
data-test-pretui-diff-block
|
|
575
|
+
...attributes
|
|
576
|
+
>
|
|
577
|
+
{{#if @filename}}
|
|
578
|
+
<div class='pretui-diff-head' data-test-pretui-diff-head>
|
|
579
|
+
<span class='pretui-diff-file'>{{@filename}}</span>
|
|
580
|
+
{{! the change's size at a glance, as DiffStat draws it }}
|
|
581
|
+
<DiffStat
|
|
582
|
+
class='pretui-diff-counts'
|
|
583
|
+
@added={{this.added}}
|
|
584
|
+
@removed={{this.removed}}
|
|
585
|
+
/>
|
|
586
|
+
</div>
|
|
587
|
+
{{/if}}
|
|
588
|
+
{{#if this.tooLarge}}
|
|
589
|
+
<div class='pretui-diff-large' data-test-pretui-diff-large>
|
|
590
|
+
<span>{{this.largeLabel}}</span>
|
|
591
|
+
<button
|
|
592
|
+
type='button'
|
|
593
|
+
class='pretui-diff-action'
|
|
594
|
+
{{on 'click' this.showLarge}}
|
|
595
|
+
data-test-pretui-diff-render-anyway
|
|
596
|
+
>Render anyway</button>
|
|
597
|
+
</div>
|
|
598
|
+
{{else}}
|
|
599
|
+
{{#each this.rows key='key' as |row|}}
|
|
600
|
+
{{#if row.hunk}}
|
|
601
|
+
{{#let row.hunk as |hunk|}}
|
|
602
|
+
<div
|
|
603
|
+
class='pretui-diff-hunk'
|
|
604
|
+
data-state={{this.stateOf hunk}}
|
|
605
|
+
data-test-pretui-diff-hunk={{hunk.index}}
|
|
606
|
+
>
|
|
607
|
+
<span class='pretui-diff-hunk-label'>{{this.hunkLabel
|
|
608
|
+
hunk
|
|
609
|
+
}}</span>
|
|
610
|
+
{{#if (this.stateOf hunk)}}
|
|
611
|
+
<span
|
|
612
|
+
class='pretui-diff-hunk-state'
|
|
613
|
+
tabindex='-1'
|
|
614
|
+
{{this.claimHunkFocus hunk.index}}
|
|
615
|
+
data-test-pretui-diff-hunk-state={{hunk.index}}
|
|
616
|
+
>{{#if (this.isKept hunk)}}<CheckIcon
|
|
617
|
+
width='11'
|
|
618
|
+
height='11'
|
|
619
|
+
aria-hidden='true'
|
|
620
|
+
/>{{/if}}{{this.stateOf hunk}}</span>
|
|
621
|
+
{{else}}
|
|
622
|
+
{{#if @onKeepHunk}}<button
|
|
623
|
+
type='button'
|
|
624
|
+
class='pretui-diff-action'
|
|
625
|
+
{{on 'click' (fn this.keepHunk hunk)}}
|
|
626
|
+
data-test-pretui-diff-keep={{hunk.index}}
|
|
627
|
+
>Keep</button>{{/if}}
|
|
628
|
+
{{#if @onRevertHunk}}<button
|
|
629
|
+
type='button'
|
|
630
|
+
class='pretui-diff-action'
|
|
631
|
+
data-kind='quiet'
|
|
632
|
+
{{on 'click' (fn this.revertHunk hunk)}}
|
|
633
|
+
data-test-pretui-diff-revert={{hunk.index}}
|
|
634
|
+
>Revert</button>{{/if}}
|
|
635
|
+
{{/if}}
|
|
636
|
+
</div>
|
|
637
|
+
{{/let}}
|
|
638
|
+
{{/if}}
|
|
639
|
+
{{#if row.skip}}
|
|
640
|
+
<div
|
|
641
|
+
class='pretui-diff-line pretui-diff-skip'
|
|
642
|
+
data-skip
|
|
643
|
+
data-test-pretui-diff-skip
|
|
644
|
+
>{{row.skip}}</div>
|
|
645
|
+
{{else}}
|
|
646
|
+
<div class='pretui-diff-row' data-test-pretui-diff-row>
|
|
647
|
+
{{#each row.cells as |cell|}}
|
|
648
|
+
<div
|
|
649
|
+
class='pretui-diff-cell'
|
|
650
|
+
data-side={{if cell.line cell.line.side}}
|
|
651
|
+
data-test-pretui-diff-cell
|
|
652
|
+
>
|
|
653
|
+
{{#if cell.line}}
|
|
654
|
+
{{#let cell.line as |line|}}
|
|
655
|
+
<div
|
|
656
|
+
class='pretui-diff-line'
|
|
657
|
+
data-op={{line.op}}
|
|
658
|
+
data-state={{this.lineState line}}
|
|
659
|
+
data-test-pretui-diff-line
|
|
660
|
+
>{{#if @lineNumbers}}{{#if
|
|
661
|
+
(if @onComment cell.commentable false)
|
|
662
|
+
}}<button
|
|
663
|
+
type='button'
|
|
664
|
+
class='pretui-diff-n'
|
|
665
|
+
aria-label={{this.commentLabel line}}
|
|
666
|
+
{{on 'click' (fn this.openComment line)}}
|
|
667
|
+
data-test-pretui-diff-comment-on='{{line.side}}:{{line.n}}'
|
|
668
|
+
>{{line.n}}</button>{{else}}<span
|
|
669
|
+
class='pretui-diff-n'
|
|
670
|
+
aria-hidden='true'
|
|
671
|
+
>{{line.n}}</span>{{/if}}{{/if}}<span
|
|
672
|
+
class='pretui-diff-sign'
|
|
673
|
+
>{{line.sign}}</span>{{#each
|
|
674
|
+
line.tokens key='@index'
|
|
675
|
+
as |part|
|
|
676
|
+
}}<span
|
|
677
|
+
class={{part.cls}}
|
|
678
|
+
>{{part.text}}</span>{{/each}}</div>
|
|
679
|
+
{{#if cell.commentable}}
|
|
680
|
+
{{#each (this.commentsOn line) as |comment|}}
|
|
681
|
+
<div
|
|
682
|
+
class='pretui-diff-comment'
|
|
683
|
+
data-test-pretui-diff-comment='{{line.side}}:{{comment.line}}'
|
|
684
|
+
>
|
|
685
|
+
<span
|
|
686
|
+
class='pretui-diff-comment-author'
|
|
687
|
+
>{{comment.author}}</span>
|
|
688
|
+
<span
|
|
689
|
+
class='pretui-diff-comment-text'
|
|
690
|
+
>{{comment.text}}</span>
|
|
691
|
+
</div>
|
|
692
|
+
{{/each}}
|
|
693
|
+
{{#if (this.isCommenting line)}}
|
|
694
|
+
<div
|
|
695
|
+
class='pretui-diff-comment pretui-diff-comment-new'
|
|
696
|
+
data-test-pretui-diff-comment-editor='{{line.side}}:{{line.n}}'
|
|
697
|
+
>
|
|
698
|
+
<textarea
|
|
699
|
+
class='pretui-diff-comment-field'
|
|
700
|
+
aria-label={{this.commentLabel line}}
|
|
701
|
+
rows='2'
|
|
702
|
+
value={{this.commentDraft}}
|
|
703
|
+
{{on 'input' this.typeComment}}
|
|
704
|
+
{{on 'keydown' this.commentKey}}
|
|
705
|
+
{{this.focusComment}}
|
|
706
|
+
data-test-pretui-diff-comment-field
|
|
707
|
+
></textarea>
|
|
708
|
+
<span class='pretui-diff-comment-actions'>
|
|
709
|
+
<button
|
|
710
|
+
type='button'
|
|
711
|
+
class='pretui-diff-action'
|
|
712
|
+
data-kind='quiet'
|
|
713
|
+
{{on 'click' this.cancelComment}}
|
|
714
|
+
data-test-pretui-diff-comment-cancel
|
|
715
|
+
>Cancel</button>
|
|
716
|
+
<button
|
|
717
|
+
type='button'
|
|
718
|
+
class='pretui-diff-action'
|
|
719
|
+
{{on 'click' this.saveComment}}
|
|
720
|
+
data-test-pretui-diff-comment-save
|
|
721
|
+
>Comment</button>
|
|
722
|
+
</span>
|
|
723
|
+
</div>
|
|
724
|
+
{{/if}}
|
|
725
|
+
{{/if}}
|
|
726
|
+
{{/let}}
|
|
727
|
+
{{/if}}
|
|
728
|
+
</div>
|
|
729
|
+
{{/each}}
|
|
730
|
+
</div>
|
|
731
|
+
{{/if}}
|
|
732
|
+
{{/each}}
|
|
44
733
|
{{/if}}
|
|
45
|
-
{{#if
|
|
734
|
+
{{#if (if this.overflows (if this.tooLarge false true))}}
|
|
735
|
+
<button
|
|
736
|
+
type='button'
|
|
737
|
+
class='pretui-diff-line pretui-diff-toggle'
|
|
738
|
+
aria-expanded={{this.toggleExpanded}}
|
|
739
|
+
{{on 'click' this.toggle}}
|
|
740
|
+
data-test-pretui-diff-toggle
|
|
741
|
+
>{{this.toggleLabel}}</button>
|
|
742
|
+
{{/if}}
|
|
743
|
+
{{#if @receipt}}<div
|
|
744
|
+
class='pretui-diff-receipt'
|
|
745
|
+
data-test-pretui-diff-receipt
|
|
746
|
+
>{{@receipt}}</div>{{/if}}
|
|
46
747
|
</div>
|
|
47
748
|
<style scoped>
|
|
48
749
|
@layer PretComponent {
|
|
750
|
+
/* a diff is pasteable: mono at the data size, on the inset well */
|
|
49
751
|
.pretui-diff {
|
|
50
752
|
font-family: var(--font-mono);
|
|
51
|
-
font-size: var(--
|
|
52
|
-
line-height:
|
|
53
|
-
background: var(--inset
|
|
54
|
-
|
|
753
|
+
font-size: var(--boxel-font-size-xs);
|
|
754
|
+
line-height: var(--boxel-line-height-xs);
|
|
755
|
+
background-color: var(--inset);
|
|
756
|
+
color: var(--foreground);
|
|
757
|
+
border-radius: var(--boxel-border-radius-sm);
|
|
55
758
|
box-shadow: var(--pretui-shadow-hairline, 0 0 0 1px var(--border));
|
|
56
759
|
overflow: hidden;
|
|
57
760
|
}
|
|
761
|
+
.pretui-diff[data-wrap] .pretui-diff-line {
|
|
762
|
+
white-space: pre-wrap;
|
|
763
|
+
overflow-wrap: anywhere;
|
|
764
|
+
}
|
|
765
|
+
.pretui-diff[data-view='split'] .pretui-diff-row {
|
|
766
|
+
display: grid;
|
|
767
|
+
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
|
768
|
+
}
|
|
769
|
+
.pretui-diff-cell {
|
|
770
|
+
min-width: 0;
|
|
771
|
+
}
|
|
772
|
+
.pretui-diff[data-view='split'] .pretui-diff-cell:first-child {
|
|
773
|
+
box-shadow: inset -1px 0 0 var(--border);
|
|
774
|
+
}
|
|
775
|
+
.pretui-diff-hunk {
|
|
776
|
+
display: flex;
|
|
777
|
+
align-items: center;
|
|
778
|
+
gap: var(--boxel-sp-xs);
|
|
779
|
+
min-height: 1.625rem;
|
|
780
|
+
padding: 0 var(--boxel-sp-sm);
|
|
781
|
+
font-family: var(--font-sans);
|
|
782
|
+
font-size: var(--boxel-caption-font-size);
|
|
783
|
+
line-height: var(--boxel-caption-line-height);
|
|
784
|
+
color: var(--muted-foreground);
|
|
785
|
+
box-shadow: inset 0 1px 0 var(--border);
|
|
786
|
+
}
|
|
787
|
+
/* "Change 1 of 3 · +2 −1" is a count, not a string to paste: sans */
|
|
788
|
+
.pretui-diff-hunk-label {
|
|
789
|
+
margin-inline-end: auto;
|
|
790
|
+
font-variant-numeric: tabular-nums;
|
|
791
|
+
}
|
|
792
|
+
.pretui-diff-hunk-state {
|
|
793
|
+
font-weight: 600;
|
|
794
|
+
}
|
|
795
|
+
.pretui-diff-hunk-state:focus-visible {
|
|
796
|
+
outline: 2px solid var(--ring);
|
|
797
|
+
outline-offset: 2px;
|
|
798
|
+
}
|
|
799
|
+
/* kept is done: ink with a check, not green */
|
|
800
|
+
.pretui-diff-hunk[data-state='kept'] .pretui-diff-hunk-state {
|
|
801
|
+
display: inline-flex;
|
|
802
|
+
align-items: center;
|
|
803
|
+
gap: var(--boxel-sp-4xs);
|
|
804
|
+
color: var(--foreground);
|
|
805
|
+
}
|
|
806
|
+
.pretui-diff-line[data-state='reverted'] {
|
|
807
|
+
text-decoration: line-through;
|
|
808
|
+
opacity: 0.55;
|
|
809
|
+
}
|
|
810
|
+
.pretui-diff-action {
|
|
811
|
+
height: 1.375rem;
|
|
812
|
+
padding: 0 var(--boxel-sp-xs);
|
|
813
|
+
border: 0;
|
|
814
|
+
border-radius: var(--boxel-border-radius-sm);
|
|
815
|
+
background-color: transparent;
|
|
816
|
+
box-shadow: inset 0 0 0 1px var(--border-strong);
|
|
817
|
+
font-family: var(--font-sans);
|
|
818
|
+
font-size: var(--boxel-caption-font-size);
|
|
819
|
+
line-height: var(--boxel-caption-line-height);
|
|
820
|
+
font-weight: 400;
|
|
821
|
+
color: var(--foreground);
|
|
822
|
+
cursor: pointer;
|
|
823
|
+
}
|
|
824
|
+
.pretui-diff-action[data-kind='quiet'] {
|
|
825
|
+
box-shadow: none;
|
|
826
|
+
color: var(--muted-foreground);
|
|
827
|
+
}
|
|
828
|
+
.pretui-diff-action:hover {
|
|
829
|
+
color: var(--primary-ink);
|
|
830
|
+
}
|
|
831
|
+
.pretui-diff-action:focus-visible,
|
|
832
|
+
button.pretui-diff-n:focus-visible {
|
|
833
|
+
outline: 2px solid var(--ring);
|
|
834
|
+
outline-offset: -2px;
|
|
835
|
+
}
|
|
836
|
+
button.pretui-diff-n {
|
|
837
|
+
padding: 0;
|
|
838
|
+
border: 0;
|
|
839
|
+
background-color: transparent;
|
|
840
|
+
font-family: inherit;
|
|
841
|
+
font-size: inherit;
|
|
842
|
+
line-height: inherit;
|
|
843
|
+
color: inherit;
|
|
844
|
+
cursor: pointer;
|
|
845
|
+
}
|
|
846
|
+
button.pretui-diff-n:hover {
|
|
847
|
+
color: var(--primary-ink);
|
|
848
|
+
}
|
|
849
|
+
.pretui-diff-comment {
|
|
850
|
+
display: grid;
|
|
851
|
+
gap: var(--boxel-sp-6xs);
|
|
852
|
+
margin: var(--boxel-sp-6xs) var(--boxel-sp-xs) var(--boxel-sp-4xs)
|
|
853
|
+
2.5rem;
|
|
854
|
+
padding: var(--boxel-sp-4xs) var(--boxel-sp-xs);
|
|
855
|
+
border-radius: var(--boxel-border-radius-sm);
|
|
856
|
+
background-color: var(--card);
|
|
857
|
+
box-shadow: 0 0 0 1px var(--border);
|
|
858
|
+
font-family: var(--font-sans);
|
|
859
|
+
font-size: var(--boxel-ui-label-font-size);
|
|
860
|
+
line-height: var(--boxel-ui-label-line-height);
|
|
861
|
+
color: var(--card-foreground);
|
|
862
|
+
white-space: normal;
|
|
863
|
+
}
|
|
864
|
+
.pretui-diff-comment-author {
|
|
865
|
+
font-weight: 600;
|
|
866
|
+
}
|
|
867
|
+
.pretui-diff-comment-field {
|
|
868
|
+
width: 100%;
|
|
869
|
+
box-sizing: border-box;
|
|
870
|
+
padding: var(--boxel-sp-4xs) var(--boxel-sp-xs);
|
|
871
|
+
border: 0;
|
|
872
|
+
border-radius: var(--boxel-border-radius-xs);
|
|
873
|
+
background-color: var(--field);
|
|
874
|
+
box-shadow: inset 0 0 0 1px var(--input);
|
|
875
|
+
font-family: inherit;
|
|
876
|
+
font-size: inherit;
|
|
877
|
+
line-height: inherit;
|
|
878
|
+
color: var(--foreground);
|
|
879
|
+
resize: vertical;
|
|
880
|
+
}
|
|
881
|
+
.pretui-diff-comment-actions {
|
|
882
|
+
display: flex;
|
|
883
|
+
justify-content: flex-end;
|
|
884
|
+
gap: var(--boxel-sp-4xs);
|
|
885
|
+
}
|
|
886
|
+
.pretui-diff-large {
|
|
887
|
+
display: flex;
|
|
888
|
+
align-items: center;
|
|
889
|
+
justify-content: space-between;
|
|
890
|
+
gap: var(--boxel-sp-xs);
|
|
891
|
+
padding: var(--boxel-sp-sm);
|
|
892
|
+
font-family: var(--font-sans);
|
|
893
|
+
font-size: var(--boxel-ui-label-font-size);
|
|
894
|
+
line-height: var(--boxel-ui-label-line-height);
|
|
895
|
+
color: var(--muted-foreground);
|
|
896
|
+
}
|
|
897
|
+
.pretui-diff-head {
|
|
898
|
+
display: flex;
|
|
899
|
+
align-items: center;
|
|
900
|
+
gap: var(--boxel-sp-xs);
|
|
901
|
+
padding: var(--boxel-sp-4xs) var(--boxel-sp-sm);
|
|
902
|
+
font-family: var(--font-sans);
|
|
903
|
+
font-size: var(--boxel-ui-label-font-size);
|
|
904
|
+
line-height: var(--boxel-ui-label-line-height);
|
|
905
|
+
font-weight: 400;
|
|
906
|
+
box-shadow: 0 1px 0 var(--border);
|
|
907
|
+
}
|
|
908
|
+
/* the file is a path, a pasteable string: mono at the data size */
|
|
909
|
+
.pretui-diff-file {
|
|
910
|
+
min-width: 0;
|
|
911
|
+
flex: 1;
|
|
912
|
+
overflow: hidden;
|
|
913
|
+
text-overflow: ellipsis;
|
|
914
|
+
white-space: nowrap;
|
|
915
|
+
font-family: var(--font-mono);
|
|
916
|
+
font-size: var(--boxel-font-size-xs);
|
|
917
|
+
line-height: var(--boxel-line-height-xs);
|
|
918
|
+
}
|
|
919
|
+
/* the counts are figures: sans, tabular */
|
|
920
|
+
.pretui-diff-counts {
|
|
921
|
+
display: flex;
|
|
922
|
+
gap: var(--boxel-sp-4xs);
|
|
923
|
+
font-size: var(--boxel-caption-font-size);
|
|
924
|
+
line-height: var(--boxel-caption-line-height);
|
|
925
|
+
font-variant-numeric: tabular-nums;
|
|
926
|
+
}
|
|
58
927
|
.pretui-diff-line {
|
|
59
|
-
padding: 0
|
|
928
|
+
padding: 0 var(--boxel-sp-sm);
|
|
60
929
|
white-space: pre;
|
|
61
930
|
}
|
|
931
|
+
/* the tint is the fill, the line's text is the hue's ink */
|
|
62
932
|
.pretui-diff-line[data-op='add'] {
|
|
63
|
-
background: color-mix(
|
|
64
|
-
|
|
933
|
+
background-color: color-mix(
|
|
934
|
+
in oklch,
|
|
935
|
+
var(--success) 12%,
|
|
936
|
+
transparent
|
|
937
|
+
);
|
|
938
|
+
color: var(--success-ink);
|
|
65
939
|
}
|
|
66
940
|
.pretui-diff-line[data-op='del'] {
|
|
67
|
-
background: color-mix(
|
|
68
|
-
|
|
941
|
+
background-color: color-mix(
|
|
942
|
+
in oklch,
|
|
943
|
+
var(--destructive) 10%,
|
|
944
|
+
transparent
|
|
945
|
+
);
|
|
946
|
+
color: var(--destructive-ink);
|
|
947
|
+
}
|
|
948
|
+
.pretui-diff-sign {
|
|
949
|
+
user-select: none;
|
|
950
|
+
}
|
|
951
|
+
/* the number gutter: the line's place in the file, out of the way */
|
|
952
|
+
.pretui-diff-n {
|
|
953
|
+
display: inline-block;
|
|
954
|
+
width: 3ch;
|
|
955
|
+
margin-inline-end: 1ch;
|
|
956
|
+
text-align: end;
|
|
957
|
+
/* muted, not subtle: the number sits on the add and remove tints,
|
|
958
|
+
where the subtle ink falls under 4.5:1 */
|
|
959
|
+
color: var(--muted-foreground);
|
|
960
|
+
user-select: none;
|
|
961
|
+
}
|
|
962
|
+
/* With a language, the tint says added or removed and the tokens
|
|
963
|
+
keep their own colours, as in CodeBlock; the sign keeps the op's
|
|
964
|
+
colour so the change still reads in greyscale. */
|
|
965
|
+
.pretui-diff[data-language] .pretui-diff-line[data-op] {
|
|
966
|
+
color: var(--foreground);
|
|
967
|
+
}
|
|
968
|
+
.pretui-diff[data-language]
|
|
969
|
+
.pretui-diff-line[data-op='add']
|
|
970
|
+
.pretui-diff-sign {
|
|
971
|
+
color: var(--success-ink);
|
|
972
|
+
}
|
|
973
|
+
.pretui-diff[data-language]
|
|
974
|
+
.pretui-diff-line[data-op='del']
|
|
975
|
+
.pretui-diff-sign {
|
|
976
|
+
color: var(--destructive-ink);
|
|
977
|
+
}
|
|
978
|
+
.pretui-code-t {
|
|
979
|
+
color: inherit;
|
|
980
|
+
}
|
|
981
|
+
.pretui-code-comment {
|
|
982
|
+
color: color-mix(
|
|
983
|
+
in oklch,
|
|
984
|
+
var(--foreground) 8%,
|
|
985
|
+
var(--pretui-code-comment, var(--muted-foreground))
|
|
986
|
+
);
|
|
987
|
+
font-style: italic;
|
|
988
|
+
}
|
|
989
|
+
.pretui-code-string {
|
|
990
|
+
color: color-mix(
|
|
991
|
+
in oklch,
|
|
992
|
+
var(--foreground) 18%,
|
|
993
|
+
var(--pretui-code-string, var(--chart-1))
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
.pretui-code-number {
|
|
997
|
+
color: color-mix(
|
|
998
|
+
in oklch,
|
|
999
|
+
var(--foreground) 18%,
|
|
1000
|
+
var(--pretui-code-number, var(--chart-3))
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
.pretui-code-keyword {
|
|
1004
|
+
color: color-mix(
|
|
1005
|
+
in oklch,
|
|
1006
|
+
var(--foreground) 18%,
|
|
1007
|
+
var(--pretui-code-keyword, var(--chart-4))
|
|
1008
|
+
);
|
|
1009
|
+
font-weight: 400;
|
|
1010
|
+
}
|
|
1011
|
+
.pretui-code-name {
|
|
1012
|
+
color: color-mix(
|
|
1013
|
+
in oklch,
|
|
1014
|
+
var(--foreground) 18%,
|
|
1015
|
+
var(--pretui-code-name, var(--chart-2))
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
.pretui-code-punct {
|
|
1019
|
+
color: color-mix(
|
|
1020
|
+
in oklch,
|
|
1021
|
+
var(--muted-foreground) 70%,
|
|
1022
|
+
var(--foreground)
|
|
1023
|
+
);
|
|
69
1024
|
}
|
|
70
1025
|
.pretui-diff-toggle {
|
|
71
1026
|
display: block;
|
|
72
1027
|
width: 100%;
|
|
73
1028
|
text-align: left;
|
|
74
1029
|
border: 0;
|
|
75
|
-
background:
|
|
76
|
-
font:
|
|
77
|
-
|
|
1030
|
+
background-color: transparent;
|
|
1031
|
+
font-family: var(--font-sans);
|
|
1032
|
+
font-size: var(--boxel-caption-font-size);
|
|
1033
|
+
line-height: var(--boxel-caption-line-height);
|
|
1034
|
+
font-variant-numeric: tabular-nums;
|
|
1035
|
+
color: var(--primary-ink);
|
|
78
1036
|
cursor: pointer;
|
|
79
1037
|
}
|
|
1038
|
+
/* the unchanged run between two hunks: a quiet gap, never a line
|
|
1039
|
+
of the file */
|
|
1040
|
+
.pretui-diff-skip {
|
|
1041
|
+
font-family: var(--font-sans);
|
|
1042
|
+
font-size: var(--boxel-caption-font-size);
|
|
1043
|
+
line-height: var(--boxel-caption-line-height);
|
|
1044
|
+
color: var(--muted-foreground);
|
|
1045
|
+
background-color: color-mix(in oklch, var(--border) 35%, transparent);
|
|
1046
|
+
user-select: none;
|
|
1047
|
+
}
|
|
80
1048
|
.pretui-diff-receipt {
|
|
81
|
-
padding:
|
|
82
|
-
font-
|
|
83
|
-
|
|
1049
|
+
padding: var(--boxel-sp-4xs) var(--boxel-sp-sm);
|
|
1050
|
+
font-family: var(--font-sans);
|
|
1051
|
+
font-size: var(--boxel-caption-font-size);
|
|
1052
|
+
line-height: var(--boxel-caption-line-height);
|
|
1053
|
+
font-variant-numeric: tabular-nums;
|
|
1054
|
+
color: var(--subtle-foreground);
|
|
84
1055
|
box-shadow: 0 -1px 0 var(--border);
|
|
85
1056
|
}
|
|
86
1057
|
}
|