jj-plus 0.10.10
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 +30 -0
- package/README.md +102 -0
- package/assets/jj-plus-icon.png +0 -0
- package/dist/_virtual/_rolldown/runtime.cjs +23 -0
- package/dist/bin.cjs +112 -0
- package/dist/bin.cjs.map +1 -0
- package/dist/cli/options.cjs +376 -0
- package/dist/cli/options.cjs.map +1 -0
- package/dist/cli/timeline-json.cjs +29 -0
- package/dist/cli/timeline-json.cjs.map +1 -0
- package/dist/extension/blame-codelens.cjs +228 -0
- package/dist/extension/blame-codelens.cjs.map +1 -0
- package/dist/extension/command-runner.cjs +99 -0
- package/dist/extension/command-runner.cjs.map +1 -0
- package/dist/extension/constants.cjs +56 -0
- package/dist/extension/constants.cjs.map +1 -0
- package/dist/extension/editor-extra-features.cjs +464 -0
- package/dist/extension/editor-extra-features.cjs.map +1 -0
- package/dist/extension/history-adapters.cjs +702 -0
- package/dist/extension/history-adapters.cjs.map +1 -0
- package/dist/extension/index.cjs +657 -0
- package/dist/extension/index.cjs.map +1 -0
- package/dist/extension/repo-timeline-panel.cjs +402 -0
- package/dist/extension/repo-timeline-panel.cjs.map +1 -0
- package/dist/extension/resolve-file-path.cjs +16 -0
- package/dist/extension/resolve-file-path.cjs.map +1 -0
- package/dist/extension/revision-diff-nav.cjs +169 -0
- package/dist/extension/revision-diff-nav.cjs.map +1 -0
- package/dist/extension/settings.cjs +41 -0
- package/dist/extension/settings.cjs.map +1 -0
- package/dist/extension/timeline-at-line.cjs +72 -0
- package/dist/extension/timeline-at-line.cjs.map +1 -0
- package/dist/extension/timeline-panel.cjs +1113 -0
- package/dist/extension/timeline-panel.cjs.map +1 -0
- package/dist/extension/timeline-service.cjs +1154 -0
- package/dist/extension/timeline-service.cjs.map +1 -0
- package/dist/extension/uri-utils.cjs +75 -0
- package/dist/extension/uri-utils.cjs.map +1 -0
- package/dist/package.cjs +11 -0
- package/dist/package.cjs.map +1 -0
- package/dist/shared/blame.cjs +211 -0
- package/dist/shared/blame.cjs.map +1 -0
- package/dist/shared/diff-content-search.cjs +23 -0
- package/dist/shared/diff-content-search.cjs.map +1 -0
- package/dist/shared/diff-helpers.cjs +12 -0
- package/dist/shared/diff-helpers.cjs.map +1 -0
- package/dist/shared/diff-stats.cjs +129 -0
- package/dist/shared/diff-stats.cjs.map +1 -0
- package/dist/shared/file-oplog.cjs +82 -0
- package/dist/shared/file-oplog.cjs.map +1 -0
- package/dist/shared/history-helpers.cjs +257 -0
- package/dist/shared/history-helpers.cjs.map +1 -0
- package/dist/shared/history-search.cjs +85 -0
- package/dist/shared/history-search.cjs.map +1 -0
- package/dist/shared/jj-revset-suggestions.cjs +41 -0
- package/dist/shared/jj-revset-suggestions.cjs.map +1 -0
- package/dist/shared/line-history.cjs +181 -0
- package/dist/shared/line-history.cjs.map +1 -0
- package/dist/shared/list-file-revisions.cjs +36 -0
- package/dist/shared/list-file-revisions.cjs.map +1 -0
- package/dist/shared/non-textual-details.cjs +43 -0
- package/dist/shared/non-textual-details.cjs.map +1 -0
- package/dist/shared/resolve-file-path.cjs +14 -0
- package/dist/shared/resolve-file-path.cjs.map +1 -0
- package/dist/shared/revision-diff-availability.cjs +18 -0
- package/dist/shared/revision-diff-availability.cjs.map +1 -0
- package/dist/shared/revision-diff-pair.cjs +42 -0
- package/dist/shared/revision-diff-pair.cjs.map +1 -0
- package/dist/shared/revset.cjs +16 -0
- package/dist/shared/revset.cjs.map +1 -0
- package/dist/shared/timeline-model.cjs +29 -0
- package/dist/shared/timeline-model.cjs.map +1 -0
- package/dist/standalone/server.cjs +1733 -0
- package/dist/standalone/server.cjs.map +1 -0
- package/dist/webview/timeline-template.cjs +214 -0
- package/dist/webview/timeline-template.cjs.map +1 -0
- package/package.json +320 -0
- package/webview-dist/assets/worker-DuV13XDT.js +161 -0
- package/webview-dist/chunks/abap-CLvhMVsD.js +1 -0
- package/webview-dist/chunks/actionscript-3-Cs6JJXT5.js +1 -0
- package/webview-dist/chunks/ada-C5qYipkI.js +1 -0
- package/webview-dist/chunks/ahk-CA9gdOdF.js +1 -0
- package/webview-dist/chunks/ahk2-_6rzW9BS.js +1 -0
- package/webview-dist/chunks/andromeeda-vGVdxbeo.js +1 -0
- package/webview-dist/chunks/angular-html-DTpR04n4.js +1 -0
- package/webview-dist/chunks/angular-ts-BCtbMvbl.js +1 -0
- package/webview-dist/chunks/apache-U0d_L8uA.js +1 -0
- package/webview-dist/chunks/apex-KFGFyKwG.js +1 -0
- package/webview-dist/chunks/apl-BMZuN1Ql.js +1 -0
- package/webview-dist/chunks/applescript-CCn79oCD.js +1 -0
- package/webview-dist/chunks/ara-4CJ0cIlV.js +1 -0
- package/webview-dist/chunks/asciidoc-SCjQUq34.js +1 -0
- package/webview-dist/chunks/asm-Cmm7eHzH.js +1 -0
- package/webview-dist/chunks/astro-DrbV4Z7r.js +1 -0
- package/webview-dist/chunks/aurora-x-CDeNXAV0.js +1 -0
- package/webview-dist/chunks/awk-BWXHIvNe.js +1 -0
- package/webview-dist/chunks/ayu-dark-DluEY0Gj.js +1 -0
- package/webview-dist/chunks/ayu-light-C3h-C4tm.js +1 -0
- package/webview-dist/chunks/ayu-mirage-Bqwy1Gya.js +1 -0
- package/webview-dist/chunks/ballerina-B7ZEbQpA.js +1 -0
- package/webview-dist/chunks/bat-qdxAdBWQ.js +1 -0
- package/webview-dist/chunks/beancount-D-usSTwE.js +1 -0
- package/webview-dist/chunks/berry-DKpUyyne.js +1 -0
- package/webview-dist/chunks/bibtex-Ci_nEsc7.js +1 -0
- package/webview-dist/chunks/bicep-CUHmPFLl.js +1 -0
- package/webview-dist/chunks/bird2-CYIUgYqB.js +1 -0
- package/webview-dist/chunks/blade-oPV8S6rR.js +1 -0
- package/webview-dist/chunks/bsl-_VjlB9mv.js +1 -0
- package/webview-dist/chunks/c-DYf-3tob.js +1 -0
- package/webview-dist/chunks/c3-D0apG41V.js +1 -0
- package/webview-dist/chunks/cadence-CQ2zXKGN.js +1 -0
- package/webview-dist/chunks/cairo-DLTphjLi.js +1 -0
- package/webview-dist/chunks/catppuccin-frappe-C9Q5zVZY.js +1 -0
- package/webview-dist/chunks/catppuccin-latte-CtsyrtPq.js +1 -0
- package/webview-dist/chunks/catppuccin-macchiato-R8nAf1vR.js +1 -0
- package/webview-dist/chunks/catppuccin-mocha-DjuaAJKi.js +1 -0
- package/webview-dist/chunks/chapel-DY-WYB1D.js +1 -0
- package/webview-dist/chunks/clarity-CtfBBspg.js +1 -0
- package/webview-dist/chunks/clojure-DqKBuwfJ.js +1 -0
- package/webview-dist/chunks/cmake-Bj61d0ZC.js +1 -0
- package/webview-dist/chunks/cobol-DvdX_6CO.js +1 -0
- package/webview-dist/chunks/codeowners-C8r90Shi.js +1 -0
- package/webview-dist/chunks/codeql-oeQT6MSM.js +1 -0
- package/webview-dist/chunks/coffee-C1kO7jr_.js +1 -0
- package/webview-dist/chunks/common-lisp-Cv5bFMCO.js +1 -0
- package/webview-dist/chunks/coq-DuBRPxYg.js +1 -0
- package/webview-dist/chunks/cpp-CoSarUrI.js +1 -0
- package/webview-dist/chunks/crystal-SLJeZ5EP.js +1 -0
- package/webview-dist/chunks/csharp-oqKa8noW.js +1 -0
- package/webview-dist/chunks/css-cfScyrSP.js +1 -0
- package/webview-dist/chunks/csv-Dx-8-gkx.js +1 -0
- package/webview-dist/chunks/cue-CE9AQfxI.js +1 -0
- package/webview-dist/chunks/cypher-ClKdZ_lG.js +1 -0
- package/webview-dist/chunks/d-qD-0Kul2.js +1 -0
- package/webview-dist/chunks/dark-plus-Cs2F2srj.js +1 -0
- package/webview-dist/chunks/dart-CnvKMtbv.js +1 -0
- package/webview-dist/chunks/dax-BkyTk9wS.js +1 -0
- package/webview-dist/chunks/desktop-Dlh5hvp9.js +1 -0
- package/webview-dist/chunks/diff-woXpYk--.js +1 -0
- package/webview-dist/chunks/docker-IyjqRm3v.js +1 -0
- package/webview-dist/chunks/dotenv-_5a1GRtc.js +1 -0
- package/webview-dist/chunks/dracula-BHWKrbxM.js +1 -0
- package/webview-dist/chunks/dracula-soft-5eyTD99u.js +1 -0
- package/webview-dist/chunks/dream-maker-DW3nJb8Q.js +1 -0
- package/webview-dist/chunks/edge-lvBr3t3s.js +1 -0
- package/webview-dist/chunks/elixir-BLoPe0KG.js +1 -0
- package/webview-dist/chunks/elm-BveBINBn.js +1 -0
- package/webview-dist/chunks/emacs-lisp-c_oH4hRZ.js +1 -0
- package/webview-dist/chunks/erb-Bmf8z4ph.js +1 -0
- package/webview-dist/chunks/erlang-Cphh6RMH.js +1 -0
- package/webview-dist/chunks/everforest-dark-sB-x3p7T.js +1 -0
- package/webview-dist/chunks/everforest-light-Df2xbC6M.js +1 -0
- package/webview-dist/chunks/fennel-DQxkIbk2.js +1 -0
- package/webview-dist/chunks/fish-BJitypiv.js +1 -0
- package/webview-dist/chunks/fluent-C03EYrpw.js +1 -0
- package/webview-dist/chunks/fortran-fixed-form-DEKoE2YW.js +1 -0
- package/webview-dist/chunks/fortran-free-form-CYNrtFtB.js +1 -0
- package/webview-dist/chunks/fsharp-D13ZGOAj.js +1 -0
- package/webview-dist/chunks/gdresource-xMC7jr7x.js +1 -0
- package/webview-dist/chunks/gdscript-DYvRD3hg.js +1 -0
- package/webview-dist/chunks/gdshader-CBce3t8t.js +1 -0
- package/webview-dist/chunks/genie-CV2tkWYe.js +1 -0
- package/webview-dist/chunks/gherkin-DExj1W_8.js +1 -0
- package/webview-dist/chunks/git-commit-BSykSTBG.js +1 -0
- package/webview-dist/chunks/git-rebase-BBii7GBM.js +1 -0
- package/webview-dist/chunks/github-dark-C-LZuMrd.js +1 -0
- package/webview-dist/chunks/github-dark-default-DXG-b-1a.js +1 -0
- package/webview-dist/chunks/github-dark-dimmed-Bx1FflLF.js +1 -0
- package/webview-dist/chunks/github-dark-high-contrast-B_tTalzw.js +1 -0
- package/webview-dist/chunks/github-light-EUqPIrTm.js +1 -0
- package/webview-dist/chunks/github-light-default-BXViO-2h.js +1 -0
- package/webview-dist/chunks/github-light-high-contrast-B68TUdTA.js +1 -0
- package/webview-dist/chunks/gleam-CSRkHgEL.js +1 -0
- package/webview-dist/chunks/glimmer-js-CuCPna-X.js +1 -0
- package/webview-dist/chunks/glimmer-ts-DNAjZNDK.js +1 -0
- package/webview-dist/chunks/glsl-COQvRxdz.js +1 -0
- package/webview-dist/chunks/gn-ilITqXS6.js +1 -0
- package/webview-dist/chunks/gnuplot-7GGW24-e.js +1 -0
- package/webview-dist/chunks/go-rLFTqkRN.js +1 -0
- package/webview-dist/chunks/graphql-1cKCVreZ.js +1 -0
- package/webview-dist/chunks/groovy-CacY0gHj.js +1 -0
- package/webview-dist/chunks/gruvbox-dark-hard-C820rvS2.js +1 -0
- package/webview-dist/chunks/gruvbox-dark-medium-BPjhmG05.js +1 -0
- package/webview-dist/chunks/gruvbox-dark-soft-MrdJrrXF.js +1 -0
- package/webview-dist/chunks/gruvbox-light-hard-BC_s9l72.js +1 -0
- package/webview-dist/chunks/gruvbox-light-medium-BAWPOn9u.js +1 -0
- package/webview-dist/chunks/gruvbox-light-soft-BSMLrYjP.js +1 -0
- package/webview-dist/chunks/hack-7CXYxWGO.js +1 -0
- package/webview-dist/chunks/haml-CeE16iG9.js +1 -0
- package/webview-dist/chunks/handlebars-CLm7Miue.js +1 -0
- package/webview-dist/chunks/haskell-D8IpX4py.js +1 -0
- package/webview-dist/chunks/haxe-C8oCnwJJ.js +1 -0
- package/webview-dist/chunks/hcl-Dh228itO.js +1 -0
- package/webview-dist/chunks/hjson-CxZEssPk.js +1 -0
- package/webview-dist/chunks/hlsl-Cvrh5tZx.js +1 -0
- package/webview-dist/chunks/horizon-CE9ld1lL.js +1 -0
- package/webview-dist/chunks/horizon-bright-DSNQnXHK.js +1 -0
- package/webview-dist/chunks/houston-CsvMBhTu.js +1 -0
- package/webview-dist/chunks/html-EbCri-_j.js +1 -0
- package/webview-dist/chunks/html-derivative-DGcE2kQi.js +1 -0
- package/webview-dist/chunks/http-DpnqgCg2.js +1 -0
- package/webview-dist/chunks/hurl-B6NGpamo.js +1 -0
- package/webview-dist/chunks/hxml-Dcm88srW.js +1 -0
- package/webview-dist/chunks/hy-CZbG8q4J.js +1 -0
- package/webview-dist/chunks/imba-DsUTQ-LC.js +1 -0
- package/webview-dist/chunks/ini-B5eOa1yu.js +1 -0
- package/webview-dist/chunks/java-ZkUoUSJy.js +1 -0
- package/webview-dist/chunks/javascript-DLd0EIw7.js +1 -0
- package/webview-dist/chunks/jinja-WhIqNp3q.js +1 -0
- package/webview-dist/chunks/jison-CIn1t7S8.js +1 -0
- package/webview-dist/chunks/json-AHG_JuKN.js +1 -0
- package/webview-dist/chunks/json5-BR5RXkoi.js +1 -0
- package/webview-dist/chunks/jsonc-CYpm1nAK.js +1 -0
- package/webview-dist/chunks/jsonl-CmCQp5Yx.js +1 -0
- package/webview-dist/chunks/jsonnet-CJTPZ8u_.js +1 -0
- package/webview-dist/chunks/jssm-DXw9l8Rf.js +1 -0
- package/webview-dist/chunks/jsx-BLy2KGb2.js +1 -0
- package/webview-dist/chunks/julia-XrhdGMY0.js +1 -0
- package/webview-dist/chunks/just-CiO9rOBq.js +1 -0
- package/webview-dist/chunks/kanagawa-dragon-Bozd7klA.js +1 -0
- package/webview-dist/chunks/kanagawa-lotus-BmuOG9ZW.js +1 -0
- package/webview-dist/chunks/kanagawa-wave-BML5mF2J.js +1 -0
- package/webview-dist/chunks/kdl-CsD5j6eV.js +1 -0
- package/webview-dist/chunks/kotlin-DhhofPvG.js +1 -0
- package/webview-dist/chunks/kusto-C7mF5XQf.js +1 -0
- package/webview-dist/chunks/laserwave-C_8bwKvT.js +1 -0
- package/webview-dist/chunks/latex-4tqdYtbu.js +1 -0
- package/webview-dist/chunks/lean-CewbzKMR.js +1 -0
- package/webview-dist/chunks/less-DVTAwKKz.js +1 -0
- package/webview-dist/chunks/light-plus-DVQuIRkW.js +1 -0
- package/webview-dist/chunks/liquid-BiiWRCLG.js +1 -0
- package/webview-dist/chunks/llvm-DqHOMS-s.js +1 -0
- package/webview-dist/chunks/log-BNLmms1o.js +1 -0
- package/webview-dist/chunks/logo-Cluzi2Zq.js +1 -0
- package/webview-dist/chunks/lua-BPFdS2Mc.js +1 -0
- package/webview-dist/chunks/luau-C2pBCSgt.js +1 -0
- package/webview-dist/chunks/make-Dixweg8N.js +1 -0
- package/webview-dist/chunks/markdown-BYOwaDjH.js +1 -0
- package/webview-dist/chunks/marko-BLi1Yy1V.js +1 -0
- package/webview-dist/chunks/material-theme-Bm3Qr25_.js +1 -0
- package/webview-dist/chunks/material-theme-darker-2IIEA8gg.js +1 -0
- package/webview-dist/chunks/material-theme-lighter-uhdI0v04.js +1 -0
- package/webview-dist/chunks/material-theme-ocean-CHQ94UKr.js +1 -0
- package/webview-dist/chunks/material-theme-palenight-B5W6OYN7.js +1 -0
- package/webview-dist/chunks/matlab-D7qyCx1q.js +1 -0
- package/webview-dist/chunks/mdc-Dx-qyM1x.js +1 -0
- package/webview-dist/chunks/mdx-DQZ5AkYe.js +1 -0
- package/webview-dist/chunks/mermaid-BxpfTIzt.js +1 -0
- package/webview-dist/chunks/min-dark-BSWPekZh.js +1 -0
- package/webview-dist/chunks/min-light-DDpmG2fV.js +1 -0
- package/webview-dist/chunks/mipsasm-BMqwQI7S.js +1 -0
- package/webview-dist/chunks/mojo-fg40_ntQ.js +1 -0
- package/webview-dist/chunks/monokai-B_bV9PQ9.js +1 -0
- package/webview-dist/chunks/moonbit-DgjMoLWr.js +1 -0
- package/webview-dist/chunks/move-BYnGhZoM.js +1 -0
- package/webview-dist/chunks/narrat-_X_XdTYD.js +1 -0
- package/webview-dist/chunks/nextflow-BJtWHP5T.js +1 -0
- package/webview-dist/chunks/nextflow-groovy-DJMQeKeT.js +1 -0
- package/webview-dist/chunks/nginx-rnpjAIVq.js +1 -0
- package/webview-dist/chunks/night-owl-DhmEMT88.js +1 -0
- package/webview-dist/chunks/night-owl-light-eJ-hLW7d.js +1 -0
- package/webview-dist/chunks/nim-kKTJ2fIs.js +1 -0
- package/webview-dist/chunks/nix-IvuFDN5E.js +1 -0
- package/webview-dist/chunks/nord-Cb4Vim4T.js +1 -0
- package/webview-dist/chunks/nsis-CjNLYCLg.js +1 -0
- package/webview-dist/chunks/nushell-DHYfO66s.js +1 -0
- package/webview-dist/chunks/objective-c-D1A_Heim.js +1 -0
- package/webview-dist/chunks/objective-cpp-BsSzOQcm.js +1 -0
- package/webview-dist/chunks/ocaml-O90oeIOV.js +1 -0
- package/webview-dist/chunks/odin-B1RWQWA5.js +1 -0
- package/webview-dist/chunks/one-dark-pro-CLwyXe_n.js +1 -0
- package/webview-dist/chunks/one-light-D7Lr4KcI.js +1 -0
- package/webview-dist/chunks/openscad-BUDT5pXO.js +1 -0
- package/webview-dist/chunks/org-BGWCjkJX.js +1 -0
- package/webview-dist/chunks/pascal-4ZHwLPI5.js +1 -0
- package/webview-dist/chunks/perl-DEimDILQ.js +1 -0
- package/webview-dist/chunks/php-QnaNN--1.js +1 -0
- package/webview-dist/chunks/pierre-dark-CpLgRqie.js +1 -0
- package/webview-dist/chunks/pierre-dark-protanopia-deuteranopia-B35FxJx-.js +1 -0
- package/webview-dist/chunks/pierre-dark-soft-kZQmAZld.js +1 -0
- package/webview-dist/chunks/pierre-dark-tritanopia-CpjhbsIL.js +1 -0
- package/webview-dist/chunks/pierre-dark-vibrant-CpQYzh95.js +1 -0
- package/webview-dist/chunks/pierre-light-CoaEpmwp.js +1 -0
- package/webview-dist/chunks/pierre-light-protanopia-deuteranopia-0fSaH845.js +1 -0
- package/webview-dist/chunks/pierre-light-soft-lWLdNTOI.js +1 -0
- package/webview-dist/chunks/pierre-light-tritanopia-CEbqgOJL.js +1 -0
- package/webview-dist/chunks/pierre-light-vibrant-D80Fkn33.js +1 -0
- package/webview-dist/chunks/pkl-ot-7Btpt.js +1 -0
- package/webview-dist/chunks/plastic-DQwYfKfQ.js +1 -0
- package/webview-dist/chunks/plsql-DGHpHOYJ.js +1 -0
- package/webview-dist/chunks/po-BiJDBrnU.js +1 -0
- package/webview-dist/chunks/poimandres-DRFjx7u4.js +1 -0
- package/webview-dist/chunks/polar-C7UOKdEL.js +1 -0
- package/webview-dist/chunks/postcss-BXeXVLqQ.js +1 -0
- package/webview-dist/chunks/powerquery-DNMTfnFr.js +1 -0
- package/webview-dist/chunks/powershell-CCSX75yM.js +1 -0
- package/webview-dist/chunks/prisma-DMgNhnYS.js +1 -0
- package/webview-dist/chunks/prolog-iXnhIJG7.js +1 -0
- package/webview-dist/chunks/proto-DB4EqR-F.js +1 -0
- package/webview-dist/chunks/pug-D5rsKmMX.js +1 -0
- package/webview-dist/chunks/puppet-CDv2pdJW.js +1 -0
- package/webview-dist/chunks/purescript-9MfHhQsQ.js +1 -0
- package/webview-dist/chunks/python-gzcpVVnB.js +1 -0
- package/webview-dist/chunks/qml-wvbZb8UE.js +1 -0
- package/webview-dist/chunks/qmldir-DCQb3MpD.js +1 -0
- package/webview-dist/chunks/qss-Fe1Jh2GI.js +1 -0
- package/webview-dist/chunks/r-BNyBFaU1.js +1 -0
- package/webview-dist/chunks/racket-DcIDlBhZ.js +1 -0
- package/webview-dist/chunks/raku-B3gFvitq.js +1 -0
- package/webview-dist/chunks/razor-bKaHS6Kz.js +1 -0
- package/webview-dist/chunks/rbs-C0MpOrj4.js +1 -0
- package/webview-dist/chunks/red-DX1BQOs8.js +1 -0
- package/webview-dist/chunks/reg-CRGYupPL.js +1 -0
- package/webview-dist/chunks/regexp-B-mZuWUh.js +1 -0
- package/webview-dist/chunks/rel-BtDbiS_P.js +1 -0
- package/webview-dist/chunks/riscv-Ckw8ddFX.js +1 -0
- package/webview-dist/chunks/ron-VUp2lXgN.js +1 -0
- package/webview-dist/chunks/rose-pine-aMsaMD1D.js +1 -0
- package/webview-dist/chunks/rose-pine-dawn-8PwUyIlP.js +1 -0
- package/webview-dist/chunks/rose-pine-moon-DO-qXg-I.js +1 -0
- package/webview-dist/chunks/rosmsg-CAekHB0j.js +1 -0
- package/webview-dist/chunks/rst-Qq8ybXHe.js +1 -0
- package/webview-dist/chunks/ruby-U659LEwU.js +1 -0
- package/webview-dist/chunks/rust-Cfkwpbl8.js +1 -0
- package/webview-dist/chunks/sas-C4NtLApe.js +1 -0
- package/webview-dist/chunks/sass-DXrisJhu.js +1 -0
- package/webview-dist/chunks/scala-BiFt_7mu.js +1 -0
- package/webview-dist/chunks/scheme-DQCgrYNe.js +1 -0
- package/webview-dist/chunks/scss-KAaA4sHk.js +1 -0
- package/webview-dist/chunks/sdbl-bTVj8UrX.js +1 -0
- package/webview-dist/chunks/shaderlab-TOUzSsQk.js +1 -0
- package/webview-dist/chunks/shellscript-_7bOVsRC.js +1 -0
- package/webview-dist/chunks/shellsession-Bs_5k0qk.js +1 -0
- package/webview-dist/chunks/slack-dark-DnToyrRv.js +1 -0
- package/webview-dist/chunks/slack-ochin-B2OO5cIa.js +1 -0
- package/webview-dist/chunks/smalltalk-BHpQb5nM.js +1 -0
- package/webview-dist/chunks/smithy-Dgaz7aur.js +1 -0
- package/webview-dist/chunks/snazzy-light-4G7pJPwS.js +1 -0
- package/webview-dist/chunks/solarized-dark-UdsmMDCL.js +1 -0
- package/webview-dist/chunks/solarized-light-C1IUL_tW.js +1 -0
- package/webview-dist/chunks/solidity-Bj7E1mRn.js +1 -0
- package/webview-dist/chunks/soy-C5iuoFCh.js +1 -0
- package/webview-dist/chunks/sparql-D_iOobhT.js +1 -0
- package/webview-dist/chunks/splunk-BC2Px7Mm.js +1 -0
- package/webview-dist/chunks/sql-JX305rZh.js +1 -0
- package/webview-dist/chunks/ssh-config-BgfXC-Er.js +1 -0
- package/webview-dist/chunks/stata-2Rtik3TV.js +1 -0
- package/webview-dist/chunks/stylus-B6D30XZt.js +1 -0
- package/webview-dist/chunks/surrealql-IvpiLAIH.js +1 -0
- package/webview-dist/chunks/svelte-DeZcV_1u.js +1 -0
- package/webview-dist/chunks/swift-CyEgAFGc.js +1 -0
- package/webview-dist/chunks/synthwave-84-nFMaYfgc.js +1 -0
- package/webview-dist/chunks/system-verilog-Dh5ihWbf.js +1 -0
- package/webview-dist/chunks/systemd-BxMlprV5.js +1 -0
- package/webview-dist/chunks/talonscript-CohzipZa.js +1 -0
- package/webview-dist/chunks/tasl-DMoTqEGO.js +1 -0
- package/webview-dist/chunks/tcl-CZd0xW_V.js +1 -0
- package/webview-dist/chunks/templ-CcwjPXJr.js +1 -0
- package/webview-dist/chunks/terraform-DswuEJGm.js +1 -0
- package/webview-dist/chunks/tex-CZYHgHl8.js +1 -0
- package/webview-dist/chunks/tokyo-night-oM2G3aXe.js +1 -0
- package/webview-dist/chunks/toml-CcmNWLt0.js +1 -0
- package/webview-dist/chunks/ts-tags-DPINfEO_.js +1 -0
- package/webview-dist/chunks/tsv-sltzmVWM.js +1 -0
- package/webview-dist/chunks/tsx-Dq7pSIyE.js +1 -0
- package/webview-dist/chunks/turtle-ByJddavk.js +1 -0
- package/webview-dist/chunks/twig-DFpC8yDL.js +1 -0
- package/webview-dist/chunks/typescript-CN-guYmK.js +1 -0
- package/webview-dist/chunks/typespec-BRdr0IET.js +1 -0
- package/webview-dist/chunks/typst-hgW4q6mN.js +1 -0
- package/webview-dist/chunks/v-DBj4K9d_.js +1 -0
- package/webview-dist/chunks/vala-zf12oZj6.js +1 -0
- package/webview-dist/chunks/vb-DOwzaHZ0.js +1 -0
- package/webview-dist/chunks/verilog-B2gwz-sJ.js +1 -0
- package/webview-dist/chunks/vesper-DdrHHSXu.js +1 -0
- package/webview-dist/chunks/vhdl-BroJfC0k.js +1 -0
- package/webview-dist/chunks/viml-DvXPmvsu.js +1 -0
- package/webview-dist/chunks/vitesse-black-fwtXNY1n.js +1 -0
- package/webview-dist/chunks/vitesse-dark-BZCL-v6S.js +1 -0
- package/webview-dist/chunks/vitesse-light-VbXTXTou.js +1 -0
- package/webview-dist/chunks/vue-Dkz9ntel.js +1 -0
- package/webview-dist/chunks/vue-html-Dg8LQ5v3.js +1 -0
- package/webview-dist/chunks/vue-vine-CIEXICEN.js +1 -0
- package/webview-dist/chunks/vyper-CgoNMtux.js +1 -0
- package/webview-dist/chunks/wasm-BnjxR4X6.js +1 -0
- package/webview-dist/chunks/wasm-ByWQv1Qj.js +1 -0
- package/webview-dist/chunks/wenyan-C8pVoKbM.js +1 -0
- package/webview-dist/chunks/wgsl-BsKzXJz4.js +1 -0
- package/webview-dist/chunks/wikitext-ClFFjSW2.js +1 -0
- package/webview-dist/chunks/wit-DdvCle-K.js +1 -0
- package/webview-dist/chunks/wolfram-DLL8P-h_.js +1 -0
- package/webview-dist/chunks/worker-D52zl71O.js +1 -0
- package/webview-dist/chunks/worker-pool-lOjh8Q2A.js +1543 -0
- package/webview-dist/chunks/xml-X7YOen_c.js +1 -0
- package/webview-dist/chunks/xsl-h4d_Px-X.js +1 -0
- package/webview-dist/chunks/yaml-BRDpsfaj.js +1 -0
- package/webview-dist/chunks/zenscript-BnlCZFoB.js +1 -0
- package/webview-dist/chunks/zig-CMLA9XwU.js +1 -0
- package/webview-dist/index.html +18 -0
- package/webview-dist/pierre-worker-portable.js +13633 -0
- package/webview-dist/repo-timeline-app.js +7 -0
- package/webview-dist/repo-timeline.html +14 -0
- package/webview-dist/timeline-app.css +2 -0
- package/webview-dist/timeline-app.js +85 -0
|
@@ -0,0 +1,1733 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
|
|
3
|
+
const require_package = require("../package.cjs");
|
|
4
|
+
const require_history_helpers = require("../shared/history-helpers.cjs");
|
|
5
|
+
const require_non_textual_details = require("../shared/non-textual-details.cjs");
|
|
6
|
+
const require_diff_stats = require("../shared/diff-stats.cjs");
|
|
7
|
+
const require_file_oplog = require("../shared/file-oplog.cjs");
|
|
8
|
+
const require_timeline_model = require("../shared/timeline-model.cjs");
|
|
9
|
+
let node_fs = require("node:fs");
|
|
10
|
+
node_fs = require_runtime.__toESM(node_fs, 1);
|
|
11
|
+
let node_path = require("node:path");
|
|
12
|
+
node_path = require_runtime.__toESM(node_path, 1);
|
|
13
|
+
let node_child_process = require("node:child_process");
|
|
14
|
+
let node_fs_promises = require("node:fs/promises");
|
|
15
|
+
node_fs_promises = require_runtime.__toESM(node_fs_promises, 1);
|
|
16
|
+
let node_http = require("node:http");
|
|
17
|
+
node_http = require_runtime.__toESM(node_http, 1);
|
|
18
|
+
let node_os = require("node:os");
|
|
19
|
+
node_os = require_runtime.__toESM(node_os, 1);
|
|
20
|
+
let node_url = require("node:url");
|
|
21
|
+
//#region src/standalone/server.ts
|
|
22
|
+
const execFileAsync = (0, require("node:util").promisify)(node_child_process.execFile);
|
|
23
|
+
const __dirname$1 = node_path.default.dirname((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
|
|
24
|
+
const rootDir = node_path.default.resolve(__dirname$1, "..", "..");
|
|
25
|
+
const distDir = node_path.default.join(rootDir, "webview-dist");
|
|
26
|
+
const displayLocale = "en-US";
|
|
27
|
+
const displayTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
28
|
+
const monthFormatter = new Intl.DateTimeFormat(displayLocale, {
|
|
29
|
+
month: "long",
|
|
30
|
+
timeZone: displayTimeZone
|
|
31
|
+
});
|
|
32
|
+
const shortDateFormatter = new Intl.DateTimeFormat(displayLocale, {
|
|
33
|
+
month: "short",
|
|
34
|
+
day: "numeric",
|
|
35
|
+
year: "numeric",
|
|
36
|
+
timeZone: displayTimeZone
|
|
37
|
+
});
|
|
38
|
+
const subtitleDateFormatter = new Intl.DateTimeFormat(displayLocale, {
|
|
39
|
+
year: "numeric",
|
|
40
|
+
month: "numeric",
|
|
41
|
+
day: "numeric",
|
|
42
|
+
hour: "numeric",
|
|
43
|
+
minute: "2-digit",
|
|
44
|
+
second: "2-digit",
|
|
45
|
+
hour12: true,
|
|
46
|
+
timeZone: displayTimeZone
|
|
47
|
+
});
|
|
48
|
+
const timelinePresets = {
|
|
49
|
+
year: 365,
|
|
50
|
+
"7d": 7,
|
|
51
|
+
"30d": 30,
|
|
52
|
+
"90d": 90,
|
|
53
|
+
all: Number.POSITIVE_INFINITY
|
|
54
|
+
};
|
|
55
|
+
const ignoredWorkspaceEntries = /* @__PURE__ */ new Set([
|
|
56
|
+
".git",
|
|
57
|
+
".jj",
|
|
58
|
+
"node_modules",
|
|
59
|
+
"webview-dist",
|
|
60
|
+
".e2e-runtime"
|
|
61
|
+
]);
|
|
62
|
+
const distAssets = [
|
|
63
|
+
"index.html",
|
|
64
|
+
"timeline-app.js",
|
|
65
|
+
"timeline-app.css",
|
|
66
|
+
"pierre-worker-portable.js"
|
|
67
|
+
];
|
|
68
|
+
async function startStandaloneTimelineServer(args) {
|
|
69
|
+
await ensureDistReady();
|
|
70
|
+
const runtime = await createStandaloneRuntime({
|
|
71
|
+
workspacePath: args.workspacePath,
|
|
72
|
+
filePath: args.filePath
|
|
73
|
+
});
|
|
74
|
+
const server = node_http.default.createServer((request, response) => {
|
|
75
|
+
handleRequest({
|
|
76
|
+
request,
|
|
77
|
+
response,
|
|
78
|
+
runtime,
|
|
79
|
+
verbose: args.verbose === true
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
await new Promise((resolve, reject) => {
|
|
83
|
+
server.once("error", reject);
|
|
84
|
+
server.listen(args.port || 0, "127.0.0.1", () => {
|
|
85
|
+
server.off("error", reject);
|
|
86
|
+
resolve();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
const address = server.address();
|
|
90
|
+
const url = `http://127.0.0.1:${typeof address === "object" && address ? address.port : args.port || 0}/?standalone=1`;
|
|
91
|
+
if (args.openBrowser !== false) try {
|
|
92
|
+
await openTarget(url);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
95
|
+
process.stderr.write(`Failed to open the browser automatically: ${message}\n`);
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
url,
|
|
99
|
+
async close() {
|
|
100
|
+
await new Promise((resolve, reject) => {
|
|
101
|
+
server.close((error) => {
|
|
102
|
+
if (error) {
|
|
103
|
+
reject(error);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
resolve();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
async function createStandaloneRuntime(args) {
|
|
113
|
+
const repoContext = await resolveRepoContext(args.workspacePath);
|
|
114
|
+
const absoluteFilePath = node_path.default.resolve(args.filePath);
|
|
115
|
+
const relativePath = node_path.default.relative(repoContext.repoRoot, absoluteFilePath);
|
|
116
|
+
if (!relativePath || relativePath.startsWith("..")) throw new Error(`The file must be inside the repository root: ${repoContext.repoRoot}`);
|
|
117
|
+
const preferencesByPath = /* @__PURE__ */ new Map();
|
|
118
|
+
const fileCache = /* @__PURE__ */ new Map();
|
|
119
|
+
const rangeOverviewCache = /* @__PURE__ */ new Map();
|
|
120
|
+
let activeRelativePath = normalizePath(relativePath);
|
|
121
|
+
return { async handleCommand(command) {
|
|
122
|
+
switch (command.command) {
|
|
123
|
+
case "ready": return emitTimeline();
|
|
124
|
+
case "refresh":
|
|
125
|
+
fileCache.delete(activeRelativePath);
|
|
126
|
+
clearRangeOverviewCache(activeRelativePath);
|
|
127
|
+
return emitTimeline();
|
|
128
|
+
case "switch-file":
|
|
129
|
+
activeRelativePath = normalizePath(command.relativePath);
|
|
130
|
+
await getActiveFileFixture();
|
|
131
|
+
return emitTimeline();
|
|
132
|
+
case "persist-state":
|
|
133
|
+
preferencesByPath.set(activeRelativePath, extractPersistedPreferences(command));
|
|
134
|
+
return [];
|
|
135
|
+
case "select-entry": return [{
|
|
136
|
+
type: "diff-preview",
|
|
137
|
+
payload: getFixturePreview({
|
|
138
|
+
fileFixture: await getActiveFileFixture(),
|
|
139
|
+
fromIndex: command.fromIndex,
|
|
140
|
+
toIndex: command.toIndex,
|
|
141
|
+
comparisonSource: command.comparisonSource
|
|
142
|
+
})
|
|
143
|
+
}];
|
|
144
|
+
case "load-range-overview": {
|
|
145
|
+
const fileFixture = await getActiveFileFixture();
|
|
146
|
+
const normalizedFromIndex = Math.max(0, Math.min(command.fromIndex, command.toIndex));
|
|
147
|
+
const normalizedToIndex = Math.max(normalizedFromIndex, Math.max(command.fromIndex, command.toIndex));
|
|
148
|
+
const comparisonSource = command.comparisonSource === "snapshot" ? "snapshot" : "revision";
|
|
149
|
+
const selectedEntryIndexes = command.selectedEntryIndexes || [];
|
|
150
|
+
const overviewKey = `${activeRelativePath}:${comparisonSource}:${normalizedFromIndex}:${normalizedToIndex}:${selectedEntryIndexes.join(",") || "all"}`;
|
|
151
|
+
let items = rangeOverviewCache.get(overviewKey);
|
|
152
|
+
if (!items) {
|
|
153
|
+
items = await buildRangeOverview({
|
|
154
|
+
repoRoot: repoContext.repoRoot,
|
|
155
|
+
fileFixture,
|
|
156
|
+
currentRelativePath: activeRelativePath,
|
|
157
|
+
fromIndex: normalizedFromIndex,
|
|
158
|
+
toIndex: normalizedToIndex,
|
|
159
|
+
comparisonSource,
|
|
160
|
+
selectedEntryIndexes
|
|
161
|
+
});
|
|
162
|
+
rangeOverviewCache.set(overviewKey, items);
|
|
163
|
+
}
|
|
164
|
+
return [{
|
|
165
|
+
type: "range-overview",
|
|
166
|
+
payload: {
|
|
167
|
+
fromIndex: normalizedFromIndex,
|
|
168
|
+
toIndex: normalizedToIndex,
|
|
169
|
+
comparisonSource,
|
|
170
|
+
selectedEntryIndexes,
|
|
171
|
+
items
|
|
172
|
+
}
|
|
173
|
+
}];
|
|
174
|
+
}
|
|
175
|
+
case "load-range-stack": {
|
|
176
|
+
const normalizedFromIndex = Math.max(0, Math.min(command.fromIndex, command.toIndex));
|
|
177
|
+
const normalizedToIndex = Math.max(normalizedFromIndex, Math.max(command.fromIndex, command.toIndex));
|
|
178
|
+
const comparisonSource = command.comparisonSource === "snapshot" ? "snapshot" : "revision";
|
|
179
|
+
const items = [];
|
|
180
|
+
for (const stackPath of command.relativePaths) {
|
|
181
|
+
const normalizedPath = normalizePath(stackPath);
|
|
182
|
+
const fileFixture = normalizedPath === activeRelativePath ? await getActiveFileFixture() : await getFileFixture(normalizedPath);
|
|
183
|
+
items.push({
|
|
184
|
+
relativePath: normalizedPath,
|
|
185
|
+
preview: getFixturePreview({
|
|
186
|
+
fileFixture,
|
|
187
|
+
fromIndex: normalizedFromIndex,
|
|
188
|
+
toIndex: normalizedToIndex,
|
|
189
|
+
comparisonSource
|
|
190
|
+
})
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return [{
|
|
194
|
+
type: "range-stack-previews",
|
|
195
|
+
payload: {
|
|
196
|
+
fromIndex: normalizedFromIndex,
|
|
197
|
+
toIndex: normalizedToIndex,
|
|
198
|
+
comparisonSource,
|
|
199
|
+
items
|
|
200
|
+
}
|
|
201
|
+
}];
|
|
202
|
+
}
|
|
203
|
+
case "search-history": {
|
|
204
|
+
const entries = (await getActiveFileFixture()).timelineData.entries.filter((entry) => !entry.isWorkingTree);
|
|
205
|
+
const tip = entries[Math.max(0, entries.length - 1)];
|
|
206
|
+
const introducedAt = (entries[Math.max(0, entries.length - 2)] || tip)?.index ?? 0;
|
|
207
|
+
const tipIndex = tip?.index ?? introducedAt;
|
|
208
|
+
const hits = [{
|
|
209
|
+
entryIndex: introducedAt,
|
|
210
|
+
kind: "introduced"
|
|
211
|
+
}];
|
|
212
|
+
if (tipIndex !== introducedAt) hits.push({
|
|
213
|
+
entryIndex: tipIndex,
|
|
214
|
+
kind: "present"
|
|
215
|
+
});
|
|
216
|
+
return [{
|
|
217
|
+
type: "history-search",
|
|
218
|
+
payload: {
|
|
219
|
+
query: command.query,
|
|
220
|
+
introducedAt,
|
|
221
|
+
removedAt: null,
|
|
222
|
+
hits,
|
|
223
|
+
purpose: command.purpose === "sidebar" ? "sidebar" : "history"
|
|
224
|
+
}
|
|
225
|
+
}];
|
|
226
|
+
}
|
|
227
|
+
case "load-revision-tree-files": {
|
|
228
|
+
const fileFixture = await getActiveFileFixture();
|
|
229
|
+
return [{
|
|
230
|
+
type: "revision-tree-files",
|
|
231
|
+
payload: {
|
|
232
|
+
revision: command.revision,
|
|
233
|
+
files: fileFixture.timelineData.workspaceFiles || []
|
|
234
|
+
}
|
|
235
|
+
}];
|
|
236
|
+
}
|
|
237
|
+
case "load-file-oplog": {
|
|
238
|
+
const fileFixture = await getActiveFileFixture();
|
|
239
|
+
if (fileFixture.timelineData.backend !== "jj") return [{
|
|
240
|
+
type: "file-oplog",
|
|
241
|
+
payload: {
|
|
242
|
+
relativePath: fileFixture.timelineData.relativePath,
|
|
243
|
+
entries: []
|
|
244
|
+
}
|
|
245
|
+
}];
|
|
246
|
+
try {
|
|
247
|
+
const { stdout } = await run({
|
|
248
|
+
command: "jj",
|
|
249
|
+
args: [
|
|
250
|
+
"op",
|
|
251
|
+
"log",
|
|
252
|
+
"--limit",
|
|
253
|
+
"80",
|
|
254
|
+
"-T",
|
|
255
|
+
require_file_oplog.JJ_OP_LOG_TEMPLATE
|
|
256
|
+
],
|
|
257
|
+
cwd: fileFixture.timelineData.workspacePath || process.cwd()
|
|
258
|
+
});
|
|
259
|
+
const fileEntries = [...fileFixture.timelineData.snapshotEntries || [], ...fileFixture.timelineData.entries.filter((entry) => Boolean(entry.operationId))];
|
|
260
|
+
return [{
|
|
261
|
+
type: "file-oplog",
|
|
262
|
+
payload: {
|
|
263
|
+
relativePath: fileFixture.timelineData.relativePath,
|
|
264
|
+
entries: require_file_oplog.filterFileOpLogPeek({
|
|
265
|
+
opLog: require_file_oplog.parseJjOpLogOutput(stdout),
|
|
266
|
+
fileEntries,
|
|
267
|
+
relativePath: fileFixture.timelineData.relativePath,
|
|
268
|
+
limit: 12
|
|
269
|
+
})
|
|
270
|
+
}
|
|
271
|
+
}];
|
|
272
|
+
} catch {
|
|
273
|
+
return [{
|
|
274
|
+
type: "file-oplog",
|
|
275
|
+
payload: {
|
|
276
|
+
relativePath: fileFixture.timelineData.relativePath,
|
|
277
|
+
entries: []
|
|
278
|
+
}
|
|
279
|
+
}];
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
case "load-entry-diff-counts": {
|
|
283
|
+
const fileFixture = await getActiveFileFixture();
|
|
284
|
+
return [{
|
|
285
|
+
type: "entry-diff-counts",
|
|
286
|
+
payload: {
|
|
287
|
+
comparisonSource: command.comparisonSource,
|
|
288
|
+
counts: await Promise.all(command.entryIndexes.map(async (entryIndex) => ({
|
|
289
|
+
entryIndex,
|
|
290
|
+
diffCount: await getStandaloneEntryDiffCount({
|
|
291
|
+
repoRoot: repoContext.repoRoot,
|
|
292
|
+
fileFixture,
|
|
293
|
+
entryIndex,
|
|
294
|
+
comparisonSource: command.comparisonSource
|
|
295
|
+
})
|
|
296
|
+
})))
|
|
297
|
+
}
|
|
298
|
+
}];
|
|
299
|
+
}
|
|
300
|
+
case "resolve-nonempty-range": return [{
|
|
301
|
+
type: "resolved-range",
|
|
302
|
+
payload: resolveNonEmptyRange(await getActiveFileFixture(), command.candidateIndexes)
|
|
303
|
+
}];
|
|
304
|
+
case "hydrate-snapshot-entries": {
|
|
305
|
+
const fileFixture = await getActiveFileFixture();
|
|
306
|
+
return [{
|
|
307
|
+
type: "snapshot-entries",
|
|
308
|
+
payload: {
|
|
309
|
+
snapshotEntries: clone(fileFixture.timelineData.snapshotEntries),
|
|
310
|
+
snapshotState: clone(fileFixture.timelineData.snapshotState || { loadedChangeIds: [] })
|
|
311
|
+
}
|
|
312
|
+
}];
|
|
313
|
+
}
|
|
314
|
+
case "open-current-file":
|
|
315
|
+
await openTarget(node_path.default.join(repoContext.repoRoot, activeRelativePath));
|
|
316
|
+
return [];
|
|
317
|
+
case "open-editor-diff":
|
|
318
|
+
case "open-range-files-diff":
|
|
319
|
+
if (command.command === "open-range-files-diff" && command.editorCommand) {
|
|
320
|
+
const fileFixture = await getActiveFileFixture();
|
|
321
|
+
const plan = await buildStandaloneRangeMultiDiffPlan({
|
|
322
|
+
repoRoot: repoContext.repoRoot,
|
|
323
|
+
fileFixture,
|
|
324
|
+
fromIndex: command.fromIndex,
|
|
325
|
+
toIndex: command.toIndex,
|
|
326
|
+
comparisonSource: command.comparisonSource,
|
|
327
|
+
selectedEntryIndexes: command.selectedEntryIndexes || []
|
|
328
|
+
});
|
|
329
|
+
if (plan.files.length) await openMultiDiffWithEditor({
|
|
330
|
+
repoRoot: repoContext.repoRoot,
|
|
331
|
+
editorCommand: command.editorCommand,
|
|
332
|
+
files: plan.files
|
|
333
|
+
});
|
|
334
|
+
return [];
|
|
335
|
+
}
|
|
336
|
+
await openPreviewDocument({
|
|
337
|
+
preview: getFixturePreview({
|
|
338
|
+
fileFixture: await getActiveFileFixture(),
|
|
339
|
+
fromIndex: command.fromIndex,
|
|
340
|
+
toIndex: command.toIndex,
|
|
341
|
+
comparisonSource: command.comparisonSource
|
|
342
|
+
}),
|
|
343
|
+
relativePath: activeRelativePath
|
|
344
|
+
});
|
|
345
|
+
return [];
|
|
346
|
+
case "open-revision-files-diff": {
|
|
347
|
+
if (command.editorCommand) {
|
|
348
|
+
const fileFixture = await getActiveFileFixture();
|
|
349
|
+
const plan = await buildStandaloneEntryMultiDiffPlan({
|
|
350
|
+
repoRoot: repoContext.repoRoot,
|
|
351
|
+
fileFixture,
|
|
352
|
+
entryIndex: command.entryIndex,
|
|
353
|
+
comparisonSource: command.comparisonSource
|
|
354
|
+
});
|
|
355
|
+
if (plan.files.length) await openMultiDiffWithEditor({
|
|
356
|
+
repoRoot: repoContext.repoRoot,
|
|
357
|
+
editorCommand: command.editorCommand,
|
|
358
|
+
files: plan.files
|
|
359
|
+
});
|
|
360
|
+
return [];
|
|
361
|
+
}
|
|
362
|
+
const preview = getUnitPreview({
|
|
363
|
+
fileFixture: await getActiveFileFixture(),
|
|
364
|
+
entryIndex: command.entryIndex,
|
|
365
|
+
comparisonSource: command.comparisonSource
|
|
366
|
+
});
|
|
367
|
+
if (preview) await openPreviewDocument({
|
|
368
|
+
preview,
|
|
369
|
+
relativePath: activeRelativePath
|
|
370
|
+
});
|
|
371
|
+
return [];
|
|
372
|
+
}
|
|
373
|
+
case "open-revision-remote": {
|
|
374
|
+
const fileFixture = await getActiveFileFixture();
|
|
375
|
+
const entry = require_timeline_model.getEntriesForSource(fileFixture.timelineData, command.comparisonSource).find((candidate) => candidate.index === command.entryIndex);
|
|
376
|
+
if (entry?.remoteUrl) await openTarget(entry.remoteUrl);
|
|
377
|
+
return [];
|
|
378
|
+
}
|
|
379
|
+
default: return [];
|
|
380
|
+
}
|
|
381
|
+
} };
|
|
382
|
+
async function emitTimeline() {
|
|
383
|
+
const fileFixture = await getActiveFileFixture();
|
|
384
|
+
const timelineData = withPersistedPreferences(fileFixture.timelineData);
|
|
385
|
+
const defaultFromIndex = Math.max(0, timelineData.defaultIndex - 1);
|
|
386
|
+
const messages = [{
|
|
387
|
+
type: "timeline-data",
|
|
388
|
+
payload: timelineData
|
|
389
|
+
}, {
|
|
390
|
+
type: "diff-preview",
|
|
391
|
+
payload: getFixturePreview({
|
|
392
|
+
fileFixture,
|
|
393
|
+
fromIndex: defaultFromIndex,
|
|
394
|
+
toIndex: timelineData.defaultIndex,
|
|
395
|
+
comparisonSource: timelineData.preferences.comparisonSource || "revision"
|
|
396
|
+
})
|
|
397
|
+
}];
|
|
398
|
+
if (timelineData.backend === "jj") messages.push({
|
|
399
|
+
type: "snapshot-entries",
|
|
400
|
+
payload: {
|
|
401
|
+
snapshotEntries: clone(fileFixture.timelineData.snapshotEntries || []),
|
|
402
|
+
snapshotState: clone(fileFixture.timelineData.snapshotState || { loadedChangeIds: [] })
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
return messages;
|
|
406
|
+
}
|
|
407
|
+
async function getActiveFileFixture() {
|
|
408
|
+
return getFileFixture(activeRelativePath);
|
|
409
|
+
}
|
|
410
|
+
async function getFileFixture(relativeFilePath) {
|
|
411
|
+
const normalizedRelativePath = normalizePath(relativeFilePath);
|
|
412
|
+
if (!fileCache.has(normalizedRelativePath)) {
|
|
413
|
+
const nextFixture = await buildFileFixture({
|
|
414
|
+
backend: repoContext.backend,
|
|
415
|
+
relativePath: normalizedRelativePath,
|
|
416
|
+
repoRoot: repoContext.repoRoot,
|
|
417
|
+
remoteBaseUrl: repoContext.remoteBaseUrl
|
|
418
|
+
});
|
|
419
|
+
fileCache.set(normalizedRelativePath, nextFixture);
|
|
420
|
+
}
|
|
421
|
+
return fileCache.get(normalizedRelativePath);
|
|
422
|
+
}
|
|
423
|
+
function withPersistedPreferences(timelineData) {
|
|
424
|
+
const persistedPreferences = preferencesByPath.get(timelineData.relativePath) || {};
|
|
425
|
+
return {
|
|
426
|
+
...clone(timelineData),
|
|
427
|
+
preferences: {
|
|
428
|
+
...clone(timelineData.preferences),
|
|
429
|
+
...persistedPreferences
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
function clearRangeOverviewCache(relativeFilePath) {
|
|
434
|
+
for (const cacheKey of rangeOverviewCache.keys()) if (cacheKey.startsWith(`${relativeFilePath}:`)) rangeOverviewCache.delete(cacheKey);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
async function handleRequest(args) {
|
|
438
|
+
try {
|
|
439
|
+
const requestUrl = new URL(args.request.url || "/", "http://127.0.0.1");
|
|
440
|
+
if (args.request.method === "POST" && requestUrl.pathname === "/api/command") {
|
|
441
|
+
const command = await readJsonBody(args.request);
|
|
442
|
+
const messages = await args.runtime.handleCommand(command);
|
|
443
|
+
writeJson(args.response, 200, { messages });
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
if (args.request.method !== "GET" && args.request.method !== "HEAD") {
|
|
447
|
+
writeJson(args.response, 405, { error: "Method Not Allowed" });
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
const targetPath = resolveStaticPath(requestUrl.pathname);
|
|
451
|
+
if (!targetPath) {
|
|
452
|
+
writeJson(args.response, 403, { error: "Forbidden" });
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
if (!node_fs.default.existsSync(targetPath) || !node_fs.default.statSync(targetPath).isFile()) {
|
|
456
|
+
writeJson(args.response, 404, { error: "Not Found" });
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
const body = await node_fs_promises.default.readFile(targetPath);
|
|
460
|
+
args.response.writeHead(200, {
|
|
461
|
+
"content-type": getContentType(targetPath),
|
|
462
|
+
"cache-control": "no-store"
|
|
463
|
+
});
|
|
464
|
+
if (args.request.method === "HEAD") {
|
|
465
|
+
args.response.end();
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
args.response.end(body);
|
|
469
|
+
} catch (error) {
|
|
470
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
471
|
+
if (args.verbose) process.stderr.write(`Standalone timeline request failed: ${message}\n`);
|
|
472
|
+
writeJson(args.response, 500, { error: message });
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
async function buildFileFixture(args) {
|
|
476
|
+
const workspaceFiles = await getWorkspaceFiles(args.repoRoot);
|
|
477
|
+
const entries = args.backend === "jj" ? await getJjEntries({
|
|
478
|
+
repoRoot: args.repoRoot,
|
|
479
|
+
relativePath: args.relativePath,
|
|
480
|
+
remoteBaseUrl: args.remoteBaseUrl
|
|
481
|
+
}) : await getGitEntries({
|
|
482
|
+
repoRoot: args.repoRoot,
|
|
483
|
+
relativePath: args.relativePath,
|
|
484
|
+
remoteBaseUrl: args.remoteBaseUrl
|
|
485
|
+
});
|
|
486
|
+
const snapshotEntries = args.backend === "jj" ? await getJjSnapshotEntries({
|
|
487
|
+
repoRoot: args.repoRoot,
|
|
488
|
+
relativePath: args.relativePath,
|
|
489
|
+
revisionEntries: entries,
|
|
490
|
+
remoteBaseUrl: args.remoteBaseUrl
|
|
491
|
+
}) : [];
|
|
492
|
+
const loadedChangeIds = args.backend === "jj" ? [...new Set(entries.map((entry) => entry.changeId).filter(Boolean))] : [];
|
|
493
|
+
const snapshotSourceEntries = args.backend === "jj" ? composeSnapshotEntries({
|
|
494
|
+
revisionEntries: entries,
|
|
495
|
+
snapshotEntries,
|
|
496
|
+
loadedChangeIds: new Set(loadedChangeIds)
|
|
497
|
+
}) : entries;
|
|
498
|
+
const defaultIndex = Math.max(0, entries.length - 1);
|
|
499
|
+
const pathCache = /* @__PURE__ */ new Map();
|
|
500
|
+
const contentCache = /* @__PURE__ */ new Map();
|
|
501
|
+
return {
|
|
502
|
+
timelineData: {
|
|
503
|
+
backend: args.backend,
|
|
504
|
+
workspacePath: args.repoRoot,
|
|
505
|
+
relativePath: args.relativePath,
|
|
506
|
+
fileName: node_path.default.basename(args.relativePath),
|
|
507
|
+
version: require_package.version,
|
|
508
|
+
presets: timelinePresets,
|
|
509
|
+
defaultIndex,
|
|
510
|
+
latestIndex: defaultIndex,
|
|
511
|
+
preferences: {
|
|
512
|
+
sidebarWidth: 280,
|
|
513
|
+
layoutMode: "split",
|
|
514
|
+
contentMode: "diffs",
|
|
515
|
+
comparisonMode: "range",
|
|
516
|
+
comparisonSource: args.backend === "jj" ? "snapshot" : "revision",
|
|
517
|
+
preset: "90d",
|
|
518
|
+
showIntermediateRevisions: true
|
|
519
|
+
},
|
|
520
|
+
workspaceFiles,
|
|
521
|
+
hasIntermediateRevisions: entries.some((entry) => !entry.touchesFile),
|
|
522
|
+
entries,
|
|
523
|
+
snapshotEntries,
|
|
524
|
+
snapshotState: { loadedChangeIds }
|
|
525
|
+
},
|
|
526
|
+
previews: {
|
|
527
|
+
revision: await buildPreviewMap({
|
|
528
|
+
repoRoot: args.repoRoot,
|
|
529
|
+
entries,
|
|
530
|
+
backend: args.backend,
|
|
531
|
+
relativePath: args.relativePath,
|
|
532
|
+
comparisonSource: "revision",
|
|
533
|
+
pathCache,
|
|
534
|
+
contentCache
|
|
535
|
+
}),
|
|
536
|
+
snapshot: args.backend === "jj" ? await buildPreviewMap({
|
|
537
|
+
repoRoot: args.repoRoot,
|
|
538
|
+
entries: snapshotSourceEntries,
|
|
539
|
+
backend: args.backend,
|
|
540
|
+
relativePath: args.relativePath,
|
|
541
|
+
comparisonSource: "snapshot",
|
|
542
|
+
pathCache,
|
|
543
|
+
contentCache
|
|
544
|
+
}) : await buildPreviewMap({
|
|
545
|
+
repoRoot: args.repoRoot,
|
|
546
|
+
entries,
|
|
547
|
+
backend: args.backend,
|
|
548
|
+
relativePath: args.relativePath,
|
|
549
|
+
comparisonSource: "revision",
|
|
550
|
+
pathCache,
|
|
551
|
+
contentCache
|
|
552
|
+
})
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
async function getGitEntries(args) {
|
|
557
|
+
const touchingEntries = await getGitHistoryEntries(args);
|
|
558
|
+
const entries = touchingEntries.length >= 2 ? mergeTimelineEntries(await getGitRepositoryEntries(args), touchingEntries) : touchingEntries;
|
|
559
|
+
seedLatestEntryPath(entries, args.relativePath);
|
|
560
|
+
const workingTreeEntry = await makeWorkingTreeEntry({
|
|
561
|
+
repoRoot: args.repoRoot,
|
|
562
|
+
previousEntry: entries.at(-1),
|
|
563
|
+
relativePath: args.relativePath,
|
|
564
|
+
backend: "git",
|
|
565
|
+
remoteBaseUrl: args.remoteBaseUrl
|
|
566
|
+
});
|
|
567
|
+
if (workingTreeEntry) entries.push(workingTreeEntry);
|
|
568
|
+
return reindexEntries(entries);
|
|
569
|
+
}
|
|
570
|
+
async function getGitHistoryEntries(args) {
|
|
571
|
+
const { stdout } = await run({
|
|
572
|
+
command: "git",
|
|
573
|
+
args: [
|
|
574
|
+
"log",
|
|
575
|
+
"--follow",
|
|
576
|
+
"--reverse",
|
|
577
|
+
"--decorate=short",
|
|
578
|
+
"--date=iso-strict",
|
|
579
|
+
"--format=%H%x09%ad%x09%an%x09%D%x09%s",
|
|
580
|
+
"--max-count=200",
|
|
581
|
+
"--",
|
|
582
|
+
args.relativePath
|
|
583
|
+
],
|
|
584
|
+
cwd: args.repoRoot
|
|
585
|
+
});
|
|
586
|
+
const revisions = stdout.trim().split(/\r?\n/).filter(Boolean);
|
|
587
|
+
const entries = [];
|
|
588
|
+
for (const [index, line] of revisions.entries()) {
|
|
589
|
+
const [revision, authorDate, authorName, branchNames, description] = line.split(" ");
|
|
590
|
+
entries.push(makeEntry({
|
|
591
|
+
id: revision || "",
|
|
592
|
+
index,
|
|
593
|
+
revision: revision || "",
|
|
594
|
+
shortRevision: (revision || "").slice(0, 8),
|
|
595
|
+
changeId: void 0,
|
|
596
|
+
branchNames: require_history_helpers.parseGitBranchNames(branchNames || ""),
|
|
597
|
+
authorDate: authorDate || "",
|
|
598
|
+
authorName: authorName || "",
|
|
599
|
+
description: description || "",
|
|
600
|
+
touchesFile: true,
|
|
601
|
+
isWorkingTree: false,
|
|
602
|
+
filePath: args.relativePath,
|
|
603
|
+
remoteBaseUrl: args.remoteBaseUrl
|
|
604
|
+
}));
|
|
605
|
+
}
|
|
606
|
+
return entries;
|
|
607
|
+
}
|
|
608
|
+
async function getGitRepositoryEntries(args) {
|
|
609
|
+
const { stdout } = await run({
|
|
610
|
+
command: "git",
|
|
611
|
+
args: [
|
|
612
|
+
"log",
|
|
613
|
+
"--reverse",
|
|
614
|
+
"--decorate=short",
|
|
615
|
+
"--date=iso-strict",
|
|
616
|
+
"--format=%H%x09%ad%x09%an%x09%D%x09%s",
|
|
617
|
+
"--max-count=200"
|
|
618
|
+
],
|
|
619
|
+
cwd: args.repoRoot
|
|
620
|
+
});
|
|
621
|
+
const revisions = stdout.trim().split(/\r?\n/).filter(Boolean);
|
|
622
|
+
const entries = [];
|
|
623
|
+
for (const [index, line] of revisions.entries()) {
|
|
624
|
+
const [revision, authorDate, authorName, branchNames, description] = line.split(" ");
|
|
625
|
+
entries.push(makeEntry({
|
|
626
|
+
id: revision || "",
|
|
627
|
+
index,
|
|
628
|
+
revision: revision || "",
|
|
629
|
+
shortRevision: (revision || "").slice(0, 8),
|
|
630
|
+
changeId: void 0,
|
|
631
|
+
branchNames: require_history_helpers.parseGitBranchNames(branchNames || ""),
|
|
632
|
+
authorDate: authorDate || "",
|
|
633
|
+
authorName: authorName || "",
|
|
634
|
+
description: description || "",
|
|
635
|
+
touchesFile: false,
|
|
636
|
+
isWorkingTree: false,
|
|
637
|
+
filePath: void 0,
|
|
638
|
+
remoteBaseUrl: args.remoteBaseUrl
|
|
639
|
+
}));
|
|
640
|
+
}
|
|
641
|
+
return entries;
|
|
642
|
+
}
|
|
643
|
+
async function getJjEntries(args) {
|
|
644
|
+
const { stdout } = await run({
|
|
645
|
+
command: "jj",
|
|
646
|
+
args: [
|
|
647
|
+
"log",
|
|
648
|
+
"--no-graph",
|
|
649
|
+
"--reversed",
|
|
650
|
+
"--limit",
|
|
651
|
+
"100",
|
|
652
|
+
"-T",
|
|
653
|
+
require_history_helpers.buildJjHistoryLogTemplate()
|
|
654
|
+
],
|
|
655
|
+
cwd: args.repoRoot
|
|
656
|
+
});
|
|
657
|
+
const revisions = stdout.trim().split(/\r?\n/).filter(Boolean);
|
|
658
|
+
const entries = [];
|
|
659
|
+
for (const [index, line] of revisions.entries()) {
|
|
660
|
+
const parsed = require_history_helpers.parseJjHistoryLine(line);
|
|
661
|
+
if (/^0+$/u.test(parsed.revision || "")) continue;
|
|
662
|
+
const touchesFile = await jjTouchesFile({
|
|
663
|
+
repoRoot: args.repoRoot,
|
|
664
|
+
revision: parsed.revision || "",
|
|
665
|
+
relativePath: args.relativePath
|
|
666
|
+
});
|
|
667
|
+
entries.push(makeEntry({
|
|
668
|
+
...parsed,
|
|
669
|
+
index,
|
|
670
|
+
touchesFile,
|
|
671
|
+
filePath: args.relativePath,
|
|
672
|
+
remoteBaseUrl: args.remoteBaseUrl
|
|
673
|
+
}));
|
|
674
|
+
}
|
|
675
|
+
const lastEntry = entries.at(-1);
|
|
676
|
+
if (lastEntry && !lastEntry.description) {
|
|
677
|
+
lastEntry.revision = "WORKTREE";
|
|
678
|
+
lastEntry.shortRevision = "Current";
|
|
679
|
+
lastEntry.description = "Working tree";
|
|
680
|
+
lastEntry.isWorkingTree = true;
|
|
681
|
+
lastEntry.changeId = void 0;
|
|
682
|
+
lastEntry.remoteUrl = void 0;
|
|
683
|
+
}
|
|
684
|
+
seedLatestEntryPath(entries, args.relativePath);
|
|
685
|
+
const workingTreeEntry = await makeWorkingTreeEntry({
|
|
686
|
+
repoRoot: args.repoRoot,
|
|
687
|
+
previousEntry: entries.at(-1),
|
|
688
|
+
relativePath: args.relativePath,
|
|
689
|
+
backend: "jj",
|
|
690
|
+
remoteBaseUrl: args.remoteBaseUrl
|
|
691
|
+
});
|
|
692
|
+
if (workingTreeEntry) entries.push(workingTreeEntry);
|
|
693
|
+
return reindexEntries(dedupeAdjacentTimelineEntries(entries));
|
|
694
|
+
}
|
|
695
|
+
async function getJjSnapshotEntries(args) {
|
|
696
|
+
const snapshotEntries = [];
|
|
697
|
+
for (const entry of args.revisionEntries) {
|
|
698
|
+
if (!entry.changeId || entry.isWorkingTree || !entry.touchesFile) continue;
|
|
699
|
+
let stdout = "";
|
|
700
|
+
try {
|
|
701
|
+
({stdout} = await run({
|
|
702
|
+
command: "jj",
|
|
703
|
+
args: [
|
|
704
|
+
"evolog",
|
|
705
|
+
"--no-graph",
|
|
706
|
+
"--summary",
|
|
707
|
+
"--limit",
|
|
708
|
+
"100",
|
|
709
|
+
"-r",
|
|
710
|
+
entry.revision
|
|
711
|
+
],
|
|
712
|
+
cwd: args.repoRoot
|
|
713
|
+
}));
|
|
714
|
+
} catch {
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
const evolutionEntries = require_history_helpers.parseJjEvolutionSummaryEntries(stdout).filter((evolutionEntry) => require_history_helpers.parseJjSummaryChangedPaths(evolutionEntry.summaryLines).includes(args.relativePath)).map((evolutionEntry, evolutionIndex) => makeEntry({
|
|
718
|
+
id: `snapshot:${entry.changeId}:${evolutionEntry.operationId || evolutionEntry.changeKey || evolutionEntry.revision}:${evolutionIndex}`,
|
|
719
|
+
index: 0,
|
|
720
|
+
revision: evolutionEntry.revision,
|
|
721
|
+
shortRevision: require_history_helpers.normalizeSnapshotOperationKey(evolutionEntry.changeKey) || evolutionEntry.revision.slice(0, 8),
|
|
722
|
+
changeId: entry.changeId,
|
|
723
|
+
bookmarkNames: evolutionEntry.bookmarkNames,
|
|
724
|
+
authorDate: normalizeSnapshotAuthorDate(evolutionEntry.authorDate, entry.authorDate),
|
|
725
|
+
authorName: evolutionEntry.authorName || entry.authorName,
|
|
726
|
+
description: normalizeSnapshotDescription(evolutionEntry.description, evolutionEntry.operationDescription),
|
|
727
|
+
touchesFile: true,
|
|
728
|
+
isWorkingTree: false,
|
|
729
|
+
filePath: args.relativePath,
|
|
730
|
+
operationId: evolutionEntry.operationId,
|
|
731
|
+
operationIndex: evolutionEntry.operationIndex,
|
|
732
|
+
operationKey: evolutionEntry.changeKey,
|
|
733
|
+
remoteBaseUrl: args.remoteBaseUrl
|
|
734
|
+
}));
|
|
735
|
+
snapshotEntries.push(...evolutionEntries);
|
|
736
|
+
}
|
|
737
|
+
snapshotEntries.sort((left, right) => left.timestamp - right.timestamp || left.revision.localeCompare(right.revision));
|
|
738
|
+
return reindexEntries(snapshotEntries);
|
|
739
|
+
}
|
|
740
|
+
function composeSnapshotEntries(args) {
|
|
741
|
+
if (!args.loadedChangeIds.size) return args.revisionEntries;
|
|
742
|
+
const snapshotEntriesByChangeId = args.snapshotEntries.reduce((groups, entry) => {
|
|
743
|
+
if (!entry.changeId) return groups;
|
|
744
|
+
const existing = groups.get(entry.changeId) || [];
|
|
745
|
+
existing.push(entry);
|
|
746
|
+
groups.set(entry.changeId, existing);
|
|
747
|
+
return groups;
|
|
748
|
+
}, /* @__PURE__ */ new Map());
|
|
749
|
+
return reindexEntries(args.revisionEntries.flatMap((entry) => {
|
|
750
|
+
if (!entry.changeId || entry.isWorkingTree || !entry.touchesFile || !args.loadedChangeIds.has(entry.changeId)) return [entry];
|
|
751
|
+
return snapshotEntriesByChangeId.get(entry.changeId) || [entry];
|
|
752
|
+
}));
|
|
753
|
+
}
|
|
754
|
+
function seedLatestEntryPath(entries, relativePath) {
|
|
755
|
+
for (const entry of entries) if (!entry.isWorkingTree && entry.filePath) delete entry.filePath;
|
|
756
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
757
|
+
const entry = entries[index];
|
|
758
|
+
if (!entry || entry.isWorkingTree || !entry.touchesFile) continue;
|
|
759
|
+
entry.filePath = relativePath;
|
|
760
|
+
break;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
function mergeTimelineEntries(repositoryEntries, fileEntries) {
|
|
764
|
+
const touchingByRevision = new Map(fileEntries.map((entry) => [entry.revision, entry]));
|
|
765
|
+
const repoIndexes = fileEntries.map((entry) => repositoryEntries.findIndex((candidate) => candidate.revision === entry.revision)).filter((index) => index >= 0);
|
|
766
|
+
if (!repoIndexes.length) return fileEntries;
|
|
767
|
+
const startIndex = Math.min(...repoIndexes);
|
|
768
|
+
const endIndex = Math.max(...repoIndexes);
|
|
769
|
+
const merged = repositoryEntries.slice(startIndex, endIndex + 1).map((entry) => touchingByRevision.get(entry.revision) || entry);
|
|
770
|
+
for (const entry of fileEntries) if (!merged.some((candidate) => candidate.revision === entry.revision)) merged.push(entry);
|
|
771
|
+
return merged.toSorted((left, right) => left.timestamp - right.timestamp || left.revision.localeCompare(right.revision));
|
|
772
|
+
}
|
|
773
|
+
function dedupeAdjacentTimelineEntries(entries) {
|
|
774
|
+
return entries.reduce((deduped, entry) => {
|
|
775
|
+
const previousEntry = deduped.at(-1);
|
|
776
|
+
if (!previousEntry?.changeId || previousEntry.changeId !== entry.changeId) {
|
|
777
|
+
deduped.push(entry);
|
|
778
|
+
return deduped;
|
|
779
|
+
}
|
|
780
|
+
if (!previousEntry.touchesFile && entry.touchesFile) deduped[deduped.length - 1] = entry;
|
|
781
|
+
return deduped;
|
|
782
|
+
}, []);
|
|
783
|
+
}
|
|
784
|
+
async function buildPreviewMap(args) {
|
|
785
|
+
const previews = {};
|
|
786
|
+
for (let toIndex = 1; toIndex < args.entries.length; toIndex += 1) for (let fromIndex = 0; fromIndex < toIndex; fromIndex += 1) previews[`${fromIndex}:${toIndex}`] = await buildPreview({
|
|
787
|
+
repoRoot: args.repoRoot,
|
|
788
|
+
entries: args.entries,
|
|
789
|
+
backend: args.backend,
|
|
790
|
+
relativePath: args.relativePath,
|
|
791
|
+
comparisonSource: args.comparisonSource,
|
|
792
|
+
fromIndex,
|
|
793
|
+
toIndex,
|
|
794
|
+
pathCache: args.pathCache,
|
|
795
|
+
contentCache: args.contentCache
|
|
796
|
+
});
|
|
797
|
+
return previews;
|
|
798
|
+
}
|
|
799
|
+
async function buildPreview(args) {
|
|
800
|
+
const fromEntry = args.entries[args.fromIndex];
|
|
801
|
+
const toEntry = args.entries[args.toIndex];
|
|
802
|
+
const beforePath = await resolveEntryFilePath({
|
|
803
|
+
repoRoot: args.repoRoot,
|
|
804
|
+
backend: args.backend,
|
|
805
|
+
entries: args.entries,
|
|
806
|
+
entryIndex: args.fromIndex,
|
|
807
|
+
relativePath: args.relativePath,
|
|
808
|
+
pathCache: args.pathCache
|
|
809
|
+
});
|
|
810
|
+
const afterPath = await resolveEntryFilePath({
|
|
811
|
+
repoRoot: args.repoRoot,
|
|
812
|
+
backend: args.backend,
|
|
813
|
+
entries: args.entries,
|
|
814
|
+
entryIndex: args.toIndex,
|
|
815
|
+
relativePath: args.relativePath,
|
|
816
|
+
pathCache: args.pathCache
|
|
817
|
+
});
|
|
818
|
+
const beforeText = await getEntryContent({
|
|
819
|
+
repoRoot: args.repoRoot,
|
|
820
|
+
backend: args.backend,
|
|
821
|
+
entries: args.entries,
|
|
822
|
+
entryIndex: args.fromIndex,
|
|
823
|
+
relativePath: args.relativePath,
|
|
824
|
+
pathCache: args.pathCache,
|
|
825
|
+
contentCache: args.contentCache
|
|
826
|
+
});
|
|
827
|
+
const afterText = await getEntryContent({
|
|
828
|
+
repoRoot: args.repoRoot,
|
|
829
|
+
backend: args.backend,
|
|
830
|
+
entries: args.entries,
|
|
831
|
+
entryIndex: args.toIndex,
|
|
832
|
+
relativePath: args.relativePath,
|
|
833
|
+
pathCache: args.pathCache,
|
|
834
|
+
contentCache: args.contentCache
|
|
835
|
+
});
|
|
836
|
+
const stats = require_diff_stats.computeDiffStats(beforeText, afterText);
|
|
837
|
+
return {
|
|
838
|
+
index: args.toIndex,
|
|
839
|
+
title: `${fromEntry?.shortRevision || ""} -> ${toEntry?.shortRevision || ""}`,
|
|
840
|
+
subtitle: toEntry?.isWorkingTree ? "Working tree" : `${subtitleDateFormatter.format(new Date(toEntry?.authorDate || 0))} · ${toEntry?.description || ""}`,
|
|
841
|
+
diffCount: 0,
|
|
842
|
+
additions: stats.additions,
|
|
843
|
+
deletions: stats.deletions,
|
|
844
|
+
hunkCount: stats.hunkCount,
|
|
845
|
+
hasChanges: stats.hasChanges,
|
|
846
|
+
fromIndex: args.fromIndex,
|
|
847
|
+
toIndex: args.toIndex,
|
|
848
|
+
comparisonSource: args.comparisonSource,
|
|
849
|
+
beforePath,
|
|
850
|
+
afterPath,
|
|
851
|
+
beforeText,
|
|
852
|
+
afterText,
|
|
853
|
+
nonTextualDetails: stats.hasChanges ? [] : require_non_textual_details.buildNonTextualDetails({
|
|
854
|
+
beforeText,
|
|
855
|
+
afterText,
|
|
856
|
+
beforePath,
|
|
857
|
+
afterPath,
|
|
858
|
+
isWorkingTree: toEntry?.isWorkingTree
|
|
859
|
+
})
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
async function getEntryContent(args) {
|
|
863
|
+
const entry = args.entries[args.entryIndex];
|
|
864
|
+
if (!entry) return "";
|
|
865
|
+
const resolvedPath = entry.isWorkingTree ? args.relativePath : await resolveEntryFilePath(args);
|
|
866
|
+
const cacheKey = `${entry.revision}:${resolvedPath}`;
|
|
867
|
+
if (args.contentCache.has(cacheKey)) return args.contentCache.get(cacheKey) || "";
|
|
868
|
+
let content = "";
|
|
869
|
+
if (entry.isWorkingTree) {
|
|
870
|
+
try {
|
|
871
|
+
content = await node_fs_promises.default.readFile(node_path.default.join(args.repoRoot, args.relativePath), "utf8");
|
|
872
|
+
} catch (error) {
|
|
873
|
+
if (!isMissingFileError(error)) throw error;
|
|
874
|
+
}
|
|
875
|
+
args.contentCache.set(cacheKey, content);
|
|
876
|
+
return content;
|
|
877
|
+
}
|
|
878
|
+
content = args.backend === "jj" ? await showJjFileAtRevision({
|
|
879
|
+
repoRoot: args.repoRoot,
|
|
880
|
+
revision: entry.revision,
|
|
881
|
+
relativePath: resolvedPath
|
|
882
|
+
}) : await showGitFileAtRevision({
|
|
883
|
+
repoRoot: args.repoRoot,
|
|
884
|
+
revision: entry.revision,
|
|
885
|
+
relativePath: resolvedPath
|
|
886
|
+
});
|
|
887
|
+
args.contentCache.set(cacheKey, content);
|
|
888
|
+
return content;
|
|
889
|
+
}
|
|
890
|
+
async function resolveEntryFilePath(args) {
|
|
891
|
+
const entry = args.entries[args.entryIndex];
|
|
892
|
+
if (!entry || entry.isWorkingTree) return args.relativePath;
|
|
893
|
+
if (entry.filePath) return entry.filePath;
|
|
894
|
+
await ensureEntryFilePath(args);
|
|
895
|
+
return entry.filePath || args.relativePath;
|
|
896
|
+
}
|
|
897
|
+
async function ensureEntryFilePath(args) {
|
|
898
|
+
const targetEntry = args.entries[args.entryIndex];
|
|
899
|
+
if (!targetEntry || targetEntry.filePath || targetEntry.isWorkingTree) return;
|
|
900
|
+
let knownIndex = -1;
|
|
901
|
+
for (let index = args.entryIndex + 1; index < args.entries.length; index += 1) if (args.entries[index]?.filePath) {
|
|
902
|
+
knownIndex = index;
|
|
903
|
+
break;
|
|
904
|
+
}
|
|
905
|
+
if (knownIndex < 0) {
|
|
906
|
+
targetEntry.filePath = args.relativePath;
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
let currentPath = args.entries[knownIndex].filePath || args.relativePath;
|
|
910
|
+
for (let index = knownIndex; index > args.entryIndex; index -= 1) {
|
|
911
|
+
const currentEntry = args.entries[index];
|
|
912
|
+
const previousEntry = args.entries[index - 1];
|
|
913
|
+
if (!previousEntry) break;
|
|
914
|
+
if (!previousEntry.filePath) {
|
|
915
|
+
let previousPath = currentPath;
|
|
916
|
+
if (currentEntry.touchesFile && !currentEntry.isWorkingTree) previousPath = await resolvePreviousPathAcrossRevision({
|
|
917
|
+
repoRoot: args.repoRoot,
|
|
918
|
+
backend: args.backend,
|
|
919
|
+
revision: currentEntry.revision,
|
|
920
|
+
currentPath,
|
|
921
|
+
pathCache: args.pathCache
|
|
922
|
+
});
|
|
923
|
+
previousEntry.filePath = previousPath;
|
|
924
|
+
}
|
|
925
|
+
currentPath = previousEntry.filePath || currentPath;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
async function resolvePreviousPathAcrossRevision(args) {
|
|
929
|
+
const cacheKey = `${args.revision}:${args.currentPath}`;
|
|
930
|
+
if (args.pathCache.has(cacheKey)) return args.pathCache.get(cacheKey) || args.currentPath;
|
|
931
|
+
const previousPath = args.backend === "git" ? await resolveGitPreviousPath({
|
|
932
|
+
repoRoot: args.repoRoot,
|
|
933
|
+
revision: args.revision,
|
|
934
|
+
currentPath: args.currentPath
|
|
935
|
+
}) : await resolveJjPreviousPath({
|
|
936
|
+
repoRoot: args.repoRoot,
|
|
937
|
+
revision: args.revision,
|
|
938
|
+
currentPath: args.currentPath
|
|
939
|
+
});
|
|
940
|
+
args.pathCache.set(cacheKey, previousPath);
|
|
941
|
+
return previousPath;
|
|
942
|
+
}
|
|
943
|
+
async function resolveGitPreviousPath(args) {
|
|
944
|
+
const { stdout } = await run({
|
|
945
|
+
command: "git",
|
|
946
|
+
args: [
|
|
947
|
+
"diff-tree",
|
|
948
|
+
"--root",
|
|
949
|
+
"--no-commit-id",
|
|
950
|
+
"--name-status",
|
|
951
|
+
"--find-renames",
|
|
952
|
+
"-r",
|
|
953
|
+
args.revision
|
|
954
|
+
],
|
|
955
|
+
cwd: args.repoRoot
|
|
956
|
+
});
|
|
957
|
+
for (const line of stdout.split(/\r?\n/u).map((value) => value.trim()).filter(Boolean)) {
|
|
958
|
+
const [, fromPath = "", toPath = ""] = line.split(" ");
|
|
959
|
+
if (toPath === args.currentPath) return fromPath || args.currentPath;
|
|
960
|
+
}
|
|
961
|
+
return args.currentPath;
|
|
962
|
+
}
|
|
963
|
+
async function resolveJjPreviousPath(args) {
|
|
964
|
+
try {
|
|
965
|
+
const { stdout } = await run({
|
|
966
|
+
command: "jj",
|
|
967
|
+
args: [
|
|
968
|
+
"diff",
|
|
969
|
+
"--summary",
|
|
970
|
+
"-r",
|
|
971
|
+
args.revision
|
|
972
|
+
],
|
|
973
|
+
cwd: args.repoRoot
|
|
974
|
+
});
|
|
975
|
+
const rename = require_history_helpers.parseJjSummaryRenameLines(stdout).find((entry) => entry.toPath === args.currentPath);
|
|
976
|
+
return rename ? rename.fromPath : args.currentPath;
|
|
977
|
+
} catch {
|
|
978
|
+
return args.currentPath;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
async function showGitFileAtRevision(args) {
|
|
982
|
+
try {
|
|
983
|
+
const { stdout } = await run({
|
|
984
|
+
command: "git",
|
|
985
|
+
args: ["show", `${args.revision}:${args.relativePath}`],
|
|
986
|
+
cwd: args.repoRoot
|
|
987
|
+
});
|
|
988
|
+
return stdout;
|
|
989
|
+
} catch (error) {
|
|
990
|
+
if (isMissingFileAtRevisionError(error)) return "";
|
|
991
|
+
throw error;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
async function showJjFileAtRevision(args) {
|
|
995
|
+
try {
|
|
996
|
+
const { stdout } = await run({
|
|
997
|
+
command: "jj",
|
|
998
|
+
args: [
|
|
999
|
+
"file",
|
|
1000
|
+
"show",
|
|
1001
|
+
"-r",
|
|
1002
|
+
args.revision,
|
|
1003
|
+
require_history_helpers.toJjRootFileFileset(args.relativePath)
|
|
1004
|
+
],
|
|
1005
|
+
cwd: args.repoRoot
|
|
1006
|
+
});
|
|
1007
|
+
return stdout;
|
|
1008
|
+
} catch (error) {
|
|
1009
|
+
if (isMissingFileAtRevisionError(error)) return "";
|
|
1010
|
+
throw error;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
async function jjTouchesFile(args) {
|
|
1014
|
+
const { stdout } = await run({
|
|
1015
|
+
command: "jj",
|
|
1016
|
+
args: [
|
|
1017
|
+
"diff",
|
|
1018
|
+
"--name-only",
|
|
1019
|
+
"-r",
|
|
1020
|
+
args.revision
|
|
1021
|
+
],
|
|
1022
|
+
cwd: args.repoRoot
|
|
1023
|
+
});
|
|
1024
|
+
return stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).includes(args.relativePath);
|
|
1025
|
+
}
|
|
1026
|
+
async function listGitRevisionFiles(args) {
|
|
1027
|
+
const { stdout } = await run({
|
|
1028
|
+
command: "git",
|
|
1029
|
+
args: [
|
|
1030
|
+
"diff-tree",
|
|
1031
|
+
"--root",
|
|
1032
|
+
"--no-commit-id",
|
|
1033
|
+
"--name-only",
|
|
1034
|
+
"-r",
|
|
1035
|
+
args.revision
|
|
1036
|
+
],
|
|
1037
|
+
cwd: args.repoRoot
|
|
1038
|
+
});
|
|
1039
|
+
return stdout.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean);
|
|
1040
|
+
}
|
|
1041
|
+
async function resolveGitParentRevision(args) {
|
|
1042
|
+
try {
|
|
1043
|
+
const { stdout } = await run({
|
|
1044
|
+
command: "git",
|
|
1045
|
+
args: ["rev-parse", `${args.revision}^`],
|
|
1046
|
+
cwd: args.repoRoot
|
|
1047
|
+
});
|
|
1048
|
+
return stdout.trim() || void 0;
|
|
1049
|
+
} catch {
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
async function listGitWorkingTreeFiles(args) {
|
|
1054
|
+
const files = /* @__PURE__ */ new Set();
|
|
1055
|
+
try {
|
|
1056
|
+
const { stdout } = await run({
|
|
1057
|
+
command: "git",
|
|
1058
|
+
args: [
|
|
1059
|
+
"diff",
|
|
1060
|
+
"--name-only",
|
|
1061
|
+
"HEAD",
|
|
1062
|
+
"--"
|
|
1063
|
+
],
|
|
1064
|
+
cwd: args.repoRoot
|
|
1065
|
+
});
|
|
1066
|
+
for (const relativePath of stdout.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean)) files.add(relativePath);
|
|
1067
|
+
} catch {}
|
|
1068
|
+
return [...files].toSorted((left, right) => left.localeCompare(right));
|
|
1069
|
+
}
|
|
1070
|
+
async function listJjRevisionFiles(args) {
|
|
1071
|
+
const { stdout } = await run({
|
|
1072
|
+
command: "jj",
|
|
1073
|
+
args: [
|
|
1074
|
+
"diff",
|
|
1075
|
+
"--name-only",
|
|
1076
|
+
"-r",
|
|
1077
|
+
args.revision
|
|
1078
|
+
],
|
|
1079
|
+
cwd: args.repoRoot
|
|
1080
|
+
});
|
|
1081
|
+
return stdout.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean);
|
|
1082
|
+
}
|
|
1083
|
+
async function listJjChangedFiles(args) {
|
|
1084
|
+
const { stdout } = await run({
|
|
1085
|
+
command: "jj",
|
|
1086
|
+
args: [
|
|
1087
|
+
"diff",
|
|
1088
|
+
"--name-only",
|
|
1089
|
+
"--from",
|
|
1090
|
+
args.base,
|
|
1091
|
+
"--to",
|
|
1092
|
+
args.target
|
|
1093
|
+
],
|
|
1094
|
+
cwd: args.repoRoot
|
|
1095
|
+
});
|
|
1096
|
+
return stdout.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean);
|
|
1097
|
+
}
|
|
1098
|
+
async function buildStandaloneEntryMultiDiffPlan(args) {
|
|
1099
|
+
const backend = args.fileFixture.timelineData.backend;
|
|
1100
|
+
const sourceEntries = require_timeline_model.getEntriesForSource(args.fileFixture.timelineData, args.comparisonSource);
|
|
1101
|
+
const entry = sourceEntries[args.entryIndex];
|
|
1102
|
+
if (!entry) return {
|
|
1103
|
+
title: "",
|
|
1104
|
+
files: []
|
|
1105
|
+
};
|
|
1106
|
+
if (backend === "git") {
|
|
1107
|
+
if (entry.isWorkingTree) {
|
|
1108
|
+
const previousEntry = sourceEntries[Math.max(0, args.entryIndex - 1)];
|
|
1109
|
+
if (!previousEntry) return {
|
|
1110
|
+
title: entry.shortRevision,
|
|
1111
|
+
files: []
|
|
1112
|
+
};
|
|
1113
|
+
const files = await listGitWorkingTreeFiles({ repoRoot: args.repoRoot });
|
|
1114
|
+
return {
|
|
1115
|
+
title: `${previousEntry.shortRevision}..${entry.shortRevision}`,
|
|
1116
|
+
files: files.map((relativePath) => ({
|
|
1117
|
+
relativePath,
|
|
1118
|
+
originalRevset: previousEntry.revision,
|
|
1119
|
+
modifiedRevset: "@",
|
|
1120
|
+
backend
|
|
1121
|
+
}))
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
const files = await listGitRevisionFiles({
|
|
1125
|
+
repoRoot: args.repoRoot,
|
|
1126
|
+
revision: entry.revision
|
|
1127
|
+
});
|
|
1128
|
+
const parentRevision = await resolveGitParentRevision({
|
|
1129
|
+
repoRoot: args.repoRoot,
|
|
1130
|
+
revision: entry.revision
|
|
1131
|
+
});
|
|
1132
|
+
return {
|
|
1133
|
+
title: entry.shortRevision,
|
|
1134
|
+
files: files.map((relativePath) => ({
|
|
1135
|
+
relativePath,
|
|
1136
|
+
originalRevset: parentRevision || "EMPTY",
|
|
1137
|
+
modifiedRevset: entry.revision,
|
|
1138
|
+
backend
|
|
1139
|
+
}))
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
if (entry.isWorkingTree) {
|
|
1143
|
+
const previousEntry = sourceEntries[Math.max(0, args.entryIndex - 1)];
|
|
1144
|
+
if (!previousEntry) return {
|
|
1145
|
+
title: entry.shortRevision,
|
|
1146
|
+
files: []
|
|
1147
|
+
};
|
|
1148
|
+
const files = await listJjChangedFiles({
|
|
1149
|
+
repoRoot: args.repoRoot,
|
|
1150
|
+
base: previousEntry.revision,
|
|
1151
|
+
target: "@"
|
|
1152
|
+
});
|
|
1153
|
+
return {
|
|
1154
|
+
title: `${previousEntry.shortRevision}..${entry.shortRevision}`,
|
|
1155
|
+
files: files.map((relativePath) => ({
|
|
1156
|
+
relativePath,
|
|
1157
|
+
originalRevset: previousEntry.revision,
|
|
1158
|
+
modifiedRevset: "@",
|
|
1159
|
+
backend
|
|
1160
|
+
}))
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
const baseRevision = args.comparisonSource === "snapshot" ? sourceEntries[Math.max(0, args.entryIndex - 1)]?.revision || `${entry.revision}-` : `${entry.revision}-`;
|
|
1164
|
+
const files = args.comparisonSource === "snapshot" ? await listJjChangedFiles({
|
|
1165
|
+
repoRoot: args.repoRoot,
|
|
1166
|
+
base: baseRevision,
|
|
1167
|
+
target: entry.revision
|
|
1168
|
+
}) : await listJjRevisionFiles({
|
|
1169
|
+
repoRoot: args.repoRoot,
|
|
1170
|
+
revision: entry.revision
|
|
1171
|
+
});
|
|
1172
|
+
return {
|
|
1173
|
+
title: entry.shortRevision,
|
|
1174
|
+
files: files.map((relativePath) => ({
|
|
1175
|
+
relativePath,
|
|
1176
|
+
originalRevset: baseRevision,
|
|
1177
|
+
modifiedRevset: entry.revision,
|
|
1178
|
+
backend
|
|
1179
|
+
}))
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
async function buildStandaloneRangeMultiDiffPlan(args) {
|
|
1183
|
+
const sourceEntries = require_timeline_model.getEntriesForSource(args.fileFixture.timelineData, args.comparisonSource);
|
|
1184
|
+
const fromEntry = sourceEntries[Math.min(args.fromIndex, args.toIndex)];
|
|
1185
|
+
const toEntry = sourceEntries[Math.max(args.fromIndex, args.toIndex)];
|
|
1186
|
+
const title = fromEntry && toEntry ? `${fromEntry.shortRevision}..${toEntry.shortRevision}` : "Selection";
|
|
1187
|
+
if (!args.selectedEntryIndexes.length) return {
|
|
1188
|
+
title,
|
|
1189
|
+
files: []
|
|
1190
|
+
};
|
|
1191
|
+
return {
|
|
1192
|
+
title,
|
|
1193
|
+
files: (await Promise.all(args.selectedEntryIndexes.map((entryIndex) => buildStandaloneEntryMultiDiffPlan({
|
|
1194
|
+
repoRoot: args.repoRoot,
|
|
1195
|
+
fileFixture: args.fileFixture,
|
|
1196
|
+
entryIndex,
|
|
1197
|
+
comparisonSource: args.comparisonSource
|
|
1198
|
+
})))).flatMap((plan) => plan.files)
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
async function getStandaloneEntryDiffCount(args) {
|
|
1202
|
+
return (await buildStandaloneEntryMultiDiffPlan(args)).files.length;
|
|
1203
|
+
}
|
|
1204
|
+
async function openMultiDiffWithEditor(args) {
|
|
1205
|
+
const { command, args: baseArgs } = parseEditorCommand(args.editorCommand);
|
|
1206
|
+
const tempRoot = node_path.default.join(node_os.default.tmpdir(), "jj-plus-editor", `${Date.now()}`);
|
|
1207
|
+
await node_fs_promises.default.mkdir(tempRoot, { recursive: true });
|
|
1208
|
+
for (const [index, file] of args.files.entries()) {
|
|
1209
|
+
const originalPath = await materializeEditorDiffSide({
|
|
1210
|
+
repoRoot: args.repoRoot,
|
|
1211
|
+
backend: file.backend,
|
|
1212
|
+
revset: file.originalRevset,
|
|
1213
|
+
relativePath: file.relativePath,
|
|
1214
|
+
tempRoot,
|
|
1215
|
+
suffix: `${index}-original`
|
|
1216
|
+
});
|
|
1217
|
+
const modifiedPath = await materializeEditorDiffSide({
|
|
1218
|
+
repoRoot: args.repoRoot,
|
|
1219
|
+
backend: file.backend,
|
|
1220
|
+
revset: file.modifiedRevset,
|
|
1221
|
+
relativePath: file.relativePath,
|
|
1222
|
+
tempRoot,
|
|
1223
|
+
suffix: `${index}-modified`
|
|
1224
|
+
});
|
|
1225
|
+
await spawnDetached(command, [
|
|
1226
|
+
...baseArgs,
|
|
1227
|
+
"--reuse-window",
|
|
1228
|
+
"--diff",
|
|
1229
|
+
originalPath,
|
|
1230
|
+
modifiedPath
|
|
1231
|
+
]);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
async function materializeEditorDiffSide(args) {
|
|
1235
|
+
const safePath = args.relativePath.replace(/[^a-zA-Z0-9._/-]+/g, "_");
|
|
1236
|
+
const targetPath = node_path.default.join(args.tempRoot, `${args.suffix}-${safePath}`);
|
|
1237
|
+
await node_fs_promises.default.mkdir(node_path.default.dirname(targetPath), { recursive: true });
|
|
1238
|
+
const content = await readStandaloneRevsetFile({
|
|
1239
|
+
repoRoot: args.repoRoot,
|
|
1240
|
+
backend: args.backend,
|
|
1241
|
+
revset: args.revset,
|
|
1242
|
+
relativePath: args.relativePath
|
|
1243
|
+
});
|
|
1244
|
+
await node_fs_promises.default.writeFile(targetPath, content, "utf8");
|
|
1245
|
+
return targetPath;
|
|
1246
|
+
}
|
|
1247
|
+
async function readStandaloneRevsetFile(args) {
|
|
1248
|
+
if (args.revset === "EMPTY") return "";
|
|
1249
|
+
if (args.revset === "@") try {
|
|
1250
|
+
return await node_fs_promises.default.readFile(node_path.default.join(args.repoRoot, args.relativePath), "utf8");
|
|
1251
|
+
} catch (error) {
|
|
1252
|
+
if (isMissingFileError(error)) return "";
|
|
1253
|
+
throw error;
|
|
1254
|
+
}
|
|
1255
|
+
return args.backend === "jj" ? showJjFileAtRevision({
|
|
1256
|
+
repoRoot: args.repoRoot,
|
|
1257
|
+
revision: args.revset,
|
|
1258
|
+
relativePath: args.relativePath
|
|
1259
|
+
}) : showGitFileAtRevision({
|
|
1260
|
+
repoRoot: args.repoRoot,
|
|
1261
|
+
revision: args.revset,
|
|
1262
|
+
relativePath: args.relativePath
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
function parseEditorCommand(editorCommand) {
|
|
1266
|
+
const parts = editorCommand.trim().split(/\s+/u).filter(Boolean);
|
|
1267
|
+
if (!parts.length) throw new Error("Missing editor command.");
|
|
1268
|
+
return {
|
|
1269
|
+
command: parts[0],
|
|
1270
|
+
args: parts.slice(1)
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
async function spawnDetached(command, args) {
|
|
1274
|
+
await new Promise((resolve, reject) => {
|
|
1275
|
+
const child = (0, node_child_process.spawn)(command, args, {
|
|
1276
|
+
detached: true,
|
|
1277
|
+
stdio: "ignore"
|
|
1278
|
+
});
|
|
1279
|
+
child.once("error", reject);
|
|
1280
|
+
child.once("spawn", () => {
|
|
1281
|
+
child.unref();
|
|
1282
|
+
resolve();
|
|
1283
|
+
});
|
|
1284
|
+
});
|
|
1285
|
+
}
|
|
1286
|
+
async function buildRangeOverview(args) {
|
|
1287
|
+
const sourceEntries = require_timeline_model.getEntriesForSource(args.fileFixture.timelineData, args.comparisonSource);
|
|
1288
|
+
const workspaceFileSet = new Set(args.fileFixture.timelineData.workspaceFiles || []);
|
|
1289
|
+
const countedPaths = /* @__PURE__ */ new Map();
|
|
1290
|
+
const entryIndexesToScan = args.selectedEntryIndexes?.length ? args.selectedEntryIndexes : Array.from({ length: args.toIndex - args.fromIndex }, (_, index) => args.fromIndex + index + 1);
|
|
1291
|
+
for (const index of entryIndexesToScan) {
|
|
1292
|
+
const entry = sourceEntries[index];
|
|
1293
|
+
if (!entry) continue;
|
|
1294
|
+
const changedFiles = args.fileFixture.timelineData.backend === "git" ? entry.isWorkingTree ? await listGitWorkingTreeFiles({ repoRoot: args.repoRoot }) : await listGitRevisionFiles({
|
|
1295
|
+
repoRoot: args.repoRoot,
|
|
1296
|
+
revision: entry.revision
|
|
1297
|
+
}) : entry.isWorkingTree ? await listJjRevisionFiles({
|
|
1298
|
+
repoRoot: args.repoRoot,
|
|
1299
|
+
revision: "@"
|
|
1300
|
+
}) : await listJjRevisionFiles({
|
|
1301
|
+
repoRoot: args.repoRoot,
|
|
1302
|
+
revision: entry.revision
|
|
1303
|
+
});
|
|
1304
|
+
for (const relativePath of changedFiles) {
|
|
1305
|
+
if (!relativePath || !workspaceFileSet.has(relativePath) && relativePath !== args.currentRelativePath) continue;
|
|
1306
|
+
countedPaths.set(relativePath, (countedPaths.get(relativePath) || 0) + 1);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
return [...countedPaths.entries()].map(([relativePath, changeCount]) => ({
|
|
1310
|
+
relativePath,
|
|
1311
|
+
changeCount,
|
|
1312
|
+
isCurrentFile: relativePath === args.currentRelativePath
|
|
1313
|
+
})).toSorted((left, right) => {
|
|
1314
|
+
if (left.changeCount !== right.changeCount) return right.changeCount - left.changeCount;
|
|
1315
|
+
if (left.isCurrentFile !== right.isCurrentFile) return Number(right.isCurrentFile) - Number(left.isCurrentFile);
|
|
1316
|
+
return left.relativePath.localeCompare(right.relativePath);
|
|
1317
|
+
}).slice(0, 150);
|
|
1318
|
+
}
|
|
1319
|
+
async function getWorkspaceFiles(repoRoot) {
|
|
1320
|
+
try {
|
|
1321
|
+
const { stdout } = await run({
|
|
1322
|
+
command: "jj",
|
|
1323
|
+
args: ["file", "list"],
|
|
1324
|
+
cwd: repoRoot
|
|
1325
|
+
});
|
|
1326
|
+
return stdout.split(/\r?\n/).filter(Boolean).toSorted((left, right) => left.localeCompare(right));
|
|
1327
|
+
} catch {
|
|
1328
|
+
try {
|
|
1329
|
+
const { stdout } = await run({
|
|
1330
|
+
command: "git",
|
|
1331
|
+
args: ["ls-files", "--cached"],
|
|
1332
|
+
cwd: repoRoot
|
|
1333
|
+
});
|
|
1334
|
+
return stdout.split(/\r?\n/).filter(Boolean).toSorted((left, right) => left.localeCompare(right));
|
|
1335
|
+
} catch {
|
|
1336
|
+
const files = [];
|
|
1337
|
+
await collectWorkspaceFiles({
|
|
1338
|
+
repoRoot,
|
|
1339
|
+
currentDir: repoRoot,
|
|
1340
|
+
files
|
|
1341
|
+
});
|
|
1342
|
+
return files.toSorted((left, right) => left.localeCompare(right));
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
async function collectWorkspaceFiles(args) {
|
|
1347
|
+
const entries = await node_fs_promises.default.readdir(args.currentDir, { withFileTypes: true });
|
|
1348
|
+
for (const entry of entries) {
|
|
1349
|
+
if (ignoredWorkspaceEntries.has(entry.name)) continue;
|
|
1350
|
+
const absolutePath = node_path.default.join(args.currentDir, entry.name);
|
|
1351
|
+
if (entry.isDirectory()) {
|
|
1352
|
+
await collectWorkspaceFiles({
|
|
1353
|
+
repoRoot: args.repoRoot,
|
|
1354
|
+
currentDir: absolutePath,
|
|
1355
|
+
files: args.files
|
|
1356
|
+
});
|
|
1357
|
+
continue;
|
|
1358
|
+
}
|
|
1359
|
+
if (!entry.isFile()) continue;
|
|
1360
|
+
args.files.push(normalizePath(node_path.default.relative(args.repoRoot, absolutePath)));
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
async function makeWorkingTreeEntry(args) {
|
|
1364
|
+
let content = "";
|
|
1365
|
+
let stats = null;
|
|
1366
|
+
try {
|
|
1367
|
+
stats = await node_fs_promises.default.stat(node_path.default.join(args.repoRoot, args.relativePath));
|
|
1368
|
+
content = await node_fs_promises.default.readFile(node_path.default.join(args.repoRoot, args.relativePath), "utf8");
|
|
1369
|
+
} catch (error) {
|
|
1370
|
+
if (!isMissingFileError(error)) throw error;
|
|
1371
|
+
}
|
|
1372
|
+
const previousContent = args.previousEntry ? args.previousEntry.isWorkingTree ? content : args.backend === "jj" ? await showJjFileAtRevision({
|
|
1373
|
+
repoRoot: args.repoRoot,
|
|
1374
|
+
revision: args.previousEntry.revision,
|
|
1375
|
+
relativePath: args.previousEntry.filePath || args.relativePath
|
|
1376
|
+
}) : await showGitFileAtRevision({
|
|
1377
|
+
repoRoot: args.repoRoot,
|
|
1378
|
+
revision: args.previousEntry.revision,
|
|
1379
|
+
relativePath: args.previousEntry.filePath || args.relativePath
|
|
1380
|
+
}) : "";
|
|
1381
|
+
if (content === previousContent) return null;
|
|
1382
|
+
return makeEntry({
|
|
1383
|
+
id: `working-tree:${args.relativePath}`,
|
|
1384
|
+
index: args.previousEntry ? (args.previousEntry.index || 0) + 1 : 0,
|
|
1385
|
+
revision: "WORKTREE",
|
|
1386
|
+
shortRevision: "Current",
|
|
1387
|
+
changeId: void 0,
|
|
1388
|
+
authorDate: (stats?.mtime || /* @__PURE__ */ new Date()).toISOString(),
|
|
1389
|
+
authorName: process.env.GIT_AUTHOR_NAME || process.env.USER || "CLI User",
|
|
1390
|
+
description: "Working tree",
|
|
1391
|
+
touchesFile: true,
|
|
1392
|
+
isWorkingTree: true,
|
|
1393
|
+
filePath: args.relativePath,
|
|
1394
|
+
remoteBaseUrl: args.remoteBaseUrl
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
function makeEntry(args) {
|
|
1398
|
+
const timestamp = Date.parse(args.authorDate);
|
|
1399
|
+
return {
|
|
1400
|
+
id: args.id,
|
|
1401
|
+
index: args.index,
|
|
1402
|
+
revision: args.revision,
|
|
1403
|
+
shortRevision: args.shortRevision,
|
|
1404
|
+
changeId: args.changeId,
|
|
1405
|
+
bookmarkNames: args.bookmarkNames,
|
|
1406
|
+
branchNames: args.branchNames,
|
|
1407
|
+
authorDate: args.authorDate,
|
|
1408
|
+
authorName: args.authorName,
|
|
1409
|
+
description: args.description || "",
|
|
1410
|
+
isWorkingTree: args.isWorkingTree,
|
|
1411
|
+
touchesFile: args.touchesFile,
|
|
1412
|
+
timestamp,
|
|
1413
|
+
filePath: args.filePath,
|
|
1414
|
+
operationId: args.operationId,
|
|
1415
|
+
operationIndex: args.operationIndex,
|
|
1416
|
+
operationKey: args.operationKey,
|
|
1417
|
+
monthLabel: monthFormatter.format(timestamp),
|
|
1418
|
+
shortDate: shortDateFormatter.format(timestamp),
|
|
1419
|
+
relativeDate: relativeTime(timestamp),
|
|
1420
|
+
hasPreviousEntry: args.index > 0,
|
|
1421
|
+
remoteUrl: args.isWorkingTree || !args.remoteBaseUrl ? void 0 : `${args.remoteBaseUrl}/commit/${args.revision}`,
|
|
1422
|
+
...args.isEmpty ? { isEmpty: true } : {},
|
|
1423
|
+
...args.hasConflict ? { hasConflict: true } : {},
|
|
1424
|
+
...args.isImmutable ? { isImmutable: true } : {}
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1427
|
+
function reindexEntries(entries) {
|
|
1428
|
+
return entries.map((entry, index) => ({
|
|
1429
|
+
...entry,
|
|
1430
|
+
index,
|
|
1431
|
+
hasPreviousEntry: index > 0
|
|
1432
|
+
}));
|
|
1433
|
+
}
|
|
1434
|
+
function normalizeSnapshotAuthorDate(authorDate, fallbackAuthorDate) {
|
|
1435
|
+
const value = String(authorDate || "").trim();
|
|
1436
|
+
if (value && !Number.isNaN(Date.parse(value))) return value;
|
|
1437
|
+
return fallbackAuthorDate;
|
|
1438
|
+
}
|
|
1439
|
+
function normalizeSnapshotDescription(description, operationDescription) {
|
|
1440
|
+
const trimmed = String(description || "").trim();
|
|
1441
|
+
if (!trimmed || trimmed === "(no description set)" || trimmed === "(empty) (no description set)") return operationDescription || "Snapshot";
|
|
1442
|
+
return trimmed;
|
|
1443
|
+
}
|
|
1444
|
+
function relativeTime(timestamp) {
|
|
1445
|
+
const deltaHours = Math.round((Date.now() - timestamp) / 36e5);
|
|
1446
|
+
if (deltaHours <= 1) return "this minute";
|
|
1447
|
+
if (deltaHours < 36) return "yesterday";
|
|
1448
|
+
if (deltaHours < 720) return `${Math.round(deltaHours / 24)} days ago`;
|
|
1449
|
+
return `${Math.round(deltaHours / 720)} months ago`;
|
|
1450
|
+
}
|
|
1451
|
+
async function resolveRepoContext(workspacePath) {
|
|
1452
|
+
const [gitResult, jjResult] = await Promise.allSettled([run({
|
|
1453
|
+
command: "git",
|
|
1454
|
+
args: ["rev-parse", "--show-toplevel"],
|
|
1455
|
+
cwd: workspacePath
|
|
1456
|
+
}), run({
|
|
1457
|
+
command: "jj",
|
|
1458
|
+
args: ["root"],
|
|
1459
|
+
cwd: workspacePath
|
|
1460
|
+
})]);
|
|
1461
|
+
const gitRoot = gitResult.status === "fulfilled" ? gitResult.value.stdout.trim() : void 0;
|
|
1462
|
+
const jjRoot = jjResult.status === "fulfilled" ? jjResult.value.stdout.trim() : void 0;
|
|
1463
|
+
const backend = require_history_helpers.resolvePreferredHistoryBackend({
|
|
1464
|
+
workspacePath,
|
|
1465
|
+
gitRoot,
|
|
1466
|
+
jjRoot
|
|
1467
|
+
});
|
|
1468
|
+
if (backend === "jj" && jjRoot) return {
|
|
1469
|
+
backend: "jj",
|
|
1470
|
+
repoRoot: jjRoot,
|
|
1471
|
+
remoteBaseUrl: await resolveRemoteBaseUrl(jjRoot)
|
|
1472
|
+
};
|
|
1473
|
+
if (backend === "git" && gitRoot) return {
|
|
1474
|
+
backend: "git",
|
|
1475
|
+
repoRoot: gitRoot,
|
|
1476
|
+
remoteBaseUrl: await resolveRemoteBaseUrl(gitRoot)
|
|
1477
|
+
};
|
|
1478
|
+
throw new Error("Standalone timeline requires a Git or JJ repository.");
|
|
1479
|
+
}
|
|
1480
|
+
async function resolveRemoteBaseUrl(repoRoot) {
|
|
1481
|
+
try {
|
|
1482
|
+
const { stdout } = await run({
|
|
1483
|
+
command: "git",
|
|
1484
|
+
args: [
|
|
1485
|
+
"remote",
|
|
1486
|
+
"get-url",
|
|
1487
|
+
"origin"
|
|
1488
|
+
],
|
|
1489
|
+
cwd: repoRoot
|
|
1490
|
+
});
|
|
1491
|
+
return require_history_helpers.getGitHubRemoteBaseUrl(stdout.trim());
|
|
1492
|
+
} catch {
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
function extractPersistedPreferences(command) {
|
|
1497
|
+
return {
|
|
1498
|
+
sidebarWidth: command.sidebarWidth,
|
|
1499
|
+
sidebarCollapsed: command.sidebarCollapsed,
|
|
1500
|
+
timelinePaneHeight: command.timelinePaneHeight,
|
|
1501
|
+
timelinePaneCollapsed: command.timelinePaneCollapsed,
|
|
1502
|
+
layoutMode: command.layoutMode,
|
|
1503
|
+
contentMode: command.contentMode,
|
|
1504
|
+
comparisonMode: command.comparisonMode,
|
|
1505
|
+
comparisonSource: command.comparisonSource,
|
|
1506
|
+
showIntermediateRevisions: command.showIntermediateRevisions,
|
|
1507
|
+
preset: command.preset,
|
|
1508
|
+
themePreference: command.themePreference,
|
|
1509
|
+
heatmapOpen: command.heatmapOpen === true,
|
|
1510
|
+
lineDiffType: command.lineDiffType
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
function getFixturePreview(args) {
|
|
1514
|
+
const normalizedFromIndex = Math.min(args.fromIndex, args.toIndex);
|
|
1515
|
+
const normalizedToIndex = Math.max(args.fromIndex, args.toIndex);
|
|
1516
|
+
const key = `${normalizedFromIndex}:${normalizedToIndex}`;
|
|
1517
|
+
const preview = (args.fileFixture.previews[args.comparisonSource] || args.fileFixture.previews.revision || {})[key];
|
|
1518
|
+
if (preview) return clone(preview);
|
|
1519
|
+
return {
|
|
1520
|
+
index: normalizedToIndex,
|
|
1521
|
+
title: "No diff available",
|
|
1522
|
+
subtitle: "",
|
|
1523
|
+
diffCount: 0,
|
|
1524
|
+
additions: 0,
|
|
1525
|
+
deletions: 0,
|
|
1526
|
+
hunkCount: 0,
|
|
1527
|
+
hasChanges: false,
|
|
1528
|
+
fromIndex: normalizedFromIndex,
|
|
1529
|
+
toIndex: normalizedToIndex,
|
|
1530
|
+
comparisonSource: args.comparisonSource,
|
|
1531
|
+
beforePath: "",
|
|
1532
|
+
afterPath: "",
|
|
1533
|
+
beforeText: "",
|
|
1534
|
+
afterText: "",
|
|
1535
|
+
nonTextualDetails: []
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
function resolveNonEmptyRange(fileFixture, candidateIndexes) {
|
|
1539
|
+
const previewMap = fileFixture.previews.revision || {};
|
|
1540
|
+
for (let index = candidateIndexes.length - 1; index > 0; index -= 1) {
|
|
1541
|
+
const fromIndex = candidateIndexes[index - 1];
|
|
1542
|
+
const toIndex = candidateIndexes[index];
|
|
1543
|
+
if (previewMap[`${Math.min(fromIndex, toIndex)}:${Math.max(fromIndex, toIndex)}`]?.hasChanges) return {
|
|
1544
|
+
fromIndex,
|
|
1545
|
+
toIndex
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
return null;
|
|
1549
|
+
}
|
|
1550
|
+
function getUnitPreview(args) {
|
|
1551
|
+
const sourceEntries = require_timeline_model.getEntriesForSource(args.fileFixture.timelineData, args.comparisonSource);
|
|
1552
|
+
const currentIndex = sourceEntries.findIndex((entry) => entry.index === args.entryIndex);
|
|
1553
|
+
if (currentIndex <= 0) return null;
|
|
1554
|
+
return getFixturePreview({
|
|
1555
|
+
fileFixture: args.fileFixture,
|
|
1556
|
+
fromIndex: sourceEntries[currentIndex - 1].index || 0,
|
|
1557
|
+
toIndex: sourceEntries[currentIndex].index || 0,
|
|
1558
|
+
comparisonSource: args.comparisonSource
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
async function openPreviewDocument(args) {
|
|
1562
|
+
const previewDirectory = node_path.default.join(node_os.default.tmpdir(), "jj-plus-previews");
|
|
1563
|
+
await node_fs_promises.default.mkdir(previewDirectory, { recursive: true });
|
|
1564
|
+
const sanitizedPath = args.relativePath.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
1565
|
+
const previewPath = node_path.default.join(previewDirectory, `${Date.now()}-${sanitizedPath}.diff.txt`);
|
|
1566
|
+
await node_fs_promises.default.writeFile(previewPath, renderPreviewText(args.preview, args.relativePath), "utf8");
|
|
1567
|
+
await openTarget(previewPath);
|
|
1568
|
+
}
|
|
1569
|
+
function renderPreviewText(preview, relativePath) {
|
|
1570
|
+
const summary = `+${preview.additions} -${preview.deletions} ${preview.hunkCount} hunks`;
|
|
1571
|
+
const detailLines = preview.nonTextualDetails?.length ? [...preview.nonTextualDetails, ""] : [];
|
|
1572
|
+
const pathLines = [
|
|
1573
|
+
`--- ${preview.beforePath || relativePath}`,
|
|
1574
|
+
`+++ ${preview.afterPath || relativePath}`,
|
|
1575
|
+
""
|
|
1576
|
+
];
|
|
1577
|
+
const contentLines = [
|
|
1578
|
+
"===== before =====",
|
|
1579
|
+
preview.beforeText,
|
|
1580
|
+
"===== after =====",
|
|
1581
|
+
preview.afterText
|
|
1582
|
+
];
|
|
1583
|
+
return [
|
|
1584
|
+
relativePath,
|
|
1585
|
+
preview.title,
|
|
1586
|
+
preview.subtitle,
|
|
1587
|
+
summary,
|
|
1588
|
+
"",
|
|
1589
|
+
...detailLines,
|
|
1590
|
+
...pathLines,
|
|
1591
|
+
...contentLines,
|
|
1592
|
+
""
|
|
1593
|
+
].join("\n");
|
|
1594
|
+
}
|
|
1595
|
+
async function openTarget(target) {
|
|
1596
|
+
const launcher = getSystemLauncher(target);
|
|
1597
|
+
await new Promise((resolve, reject) => {
|
|
1598
|
+
const child = (0, node_child_process.spawn)(launcher.command, launcher.args, {
|
|
1599
|
+
detached: true,
|
|
1600
|
+
stdio: "ignore"
|
|
1601
|
+
});
|
|
1602
|
+
child.once("error", reject);
|
|
1603
|
+
child.once("spawn", () => {
|
|
1604
|
+
child.unref();
|
|
1605
|
+
resolve();
|
|
1606
|
+
});
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
function getSystemLauncher(target) {
|
|
1610
|
+
if (process.platform === "darwin") return {
|
|
1611
|
+
command: "open",
|
|
1612
|
+
args: [target]
|
|
1613
|
+
};
|
|
1614
|
+
if (process.platform === "win32") return {
|
|
1615
|
+
command: "cmd",
|
|
1616
|
+
args: [
|
|
1617
|
+
"/c",
|
|
1618
|
+
"start",
|
|
1619
|
+
"",
|
|
1620
|
+
target
|
|
1621
|
+
]
|
|
1622
|
+
};
|
|
1623
|
+
return {
|
|
1624
|
+
command: "xdg-open",
|
|
1625
|
+
args: [target]
|
|
1626
|
+
};
|
|
1627
|
+
}
|
|
1628
|
+
async function ensureDistReady() {
|
|
1629
|
+
const assetPaths = distAssets.map((assetPath) => node_path.default.join(distDir, assetPath));
|
|
1630
|
+
if (!assetPaths.every((assetPath) => node_fs.default.existsSync(assetPath)) || await isDistStale(assetPaths)) await buildWebviewDist();
|
|
1631
|
+
if (!assetPaths.every((assetPath) => node_fs.default.existsSync(assetPath))) throw new Error("Missing webview-dist assets. Run pnpm build:webview before launching the standalone timeline.");
|
|
1632
|
+
}
|
|
1633
|
+
async function isDistStale(assetPaths) {
|
|
1634
|
+
const sourcePaths = await collectBuildInputFiles(node_path.default.join(rootDir, "webview"));
|
|
1635
|
+
return (sourcePaths.length ? Math.max(...await Promise.all(sourcePaths.map((filePath) => node_fs_promises.default.stat(filePath).then((stats) => stats.mtimeMs)))) : 0) > Math.min(...await Promise.all(assetPaths.map((assetPath) => node_fs_promises.default.stat(assetPath).then((stats) => stats.mtimeMs))));
|
|
1636
|
+
}
|
|
1637
|
+
async function collectBuildInputFiles(currentPath) {
|
|
1638
|
+
const entries = await node_fs_promises.default.readdir(currentPath, { withFileTypes: true });
|
|
1639
|
+
const filePaths = [];
|
|
1640
|
+
for (const entry of entries) {
|
|
1641
|
+
const absolutePath = node_path.default.join(currentPath, entry.name);
|
|
1642
|
+
if (entry.isDirectory()) {
|
|
1643
|
+
filePaths.push(...await collectBuildInputFiles(absolutePath));
|
|
1644
|
+
continue;
|
|
1645
|
+
}
|
|
1646
|
+
if (entry.isFile()) filePaths.push(absolutePath);
|
|
1647
|
+
}
|
|
1648
|
+
return filePaths;
|
|
1649
|
+
}
|
|
1650
|
+
async function buildWebviewDist() {
|
|
1651
|
+
const pnpmCommand = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
|
|
1652
|
+
try {
|
|
1653
|
+
await run({
|
|
1654
|
+
command: pnpmCommand,
|
|
1655
|
+
args: ["build:webview"],
|
|
1656
|
+
cwd: rootDir
|
|
1657
|
+
});
|
|
1658
|
+
return;
|
|
1659
|
+
} catch (error) {
|
|
1660
|
+
if ((error && typeof error === "object" && "code" in error ? error.code : void 0) === "ENOENT") try {
|
|
1661
|
+
await run({
|
|
1662
|
+
command: "corepack",
|
|
1663
|
+
args: ["pnpm", "build:webview"],
|
|
1664
|
+
cwd: rootDir
|
|
1665
|
+
});
|
|
1666
|
+
return;
|
|
1667
|
+
} catch {}
|
|
1668
|
+
const cause = error instanceof Error ? error : new Error(String(error));
|
|
1669
|
+
cause.message = `Failed to build the standalone webview assets automatically: ${cause.message}`;
|
|
1670
|
+
throw cause;
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
function resolveStaticPath(requestPath) {
|
|
1674
|
+
const decodedPath = decodeURIComponent(requestPath === "/" ? "/index.html" : requestPath);
|
|
1675
|
+
const candidatePath = node_path.default.normalize(node_path.default.join(distDir, decodedPath));
|
|
1676
|
+
if (candidatePath !== distDir && !candidatePath.startsWith(`${distDir}${node_path.default.sep}`)) return null;
|
|
1677
|
+
return candidatePath;
|
|
1678
|
+
}
|
|
1679
|
+
function getContentType(filePath) {
|
|
1680
|
+
const extension = node_path.default.extname(filePath).toLowerCase();
|
|
1681
|
+
if (extension === ".html") return "text/html; charset=utf-8";
|
|
1682
|
+
if (extension === ".js") return "application/javascript; charset=utf-8";
|
|
1683
|
+
if (extension === ".css") return "text/css; charset=utf-8";
|
|
1684
|
+
if (extension === ".json") return "application/json; charset=utf-8";
|
|
1685
|
+
if (extension === ".svg") return "image/svg+xml";
|
|
1686
|
+
return "application/octet-stream";
|
|
1687
|
+
}
|
|
1688
|
+
async function readJsonBody(request) {
|
|
1689
|
+
const chunks = [];
|
|
1690
|
+
let totalBytes = 0;
|
|
1691
|
+
for await (const chunk of request) {
|
|
1692
|
+
const bufferChunk = typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk);
|
|
1693
|
+
totalBytes += bufferChunk.length;
|
|
1694
|
+
if (totalBytes > 1048576) throw new Error("Request body is too large.");
|
|
1695
|
+
chunks.push(bufferChunk);
|
|
1696
|
+
}
|
|
1697
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
1698
|
+
return text ? JSON.parse(text) : {};
|
|
1699
|
+
}
|
|
1700
|
+
function writeJson(response, statusCode, payload) {
|
|
1701
|
+
response.writeHead(statusCode, {
|
|
1702
|
+
"content-type": "application/json; charset=utf-8",
|
|
1703
|
+
"cache-control": "no-store"
|
|
1704
|
+
});
|
|
1705
|
+
response.end(JSON.stringify(payload));
|
|
1706
|
+
}
|
|
1707
|
+
function isMissingFileAtRevisionError(error) {
|
|
1708
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1709
|
+
return /exists on disk, but not in/i.test(message) || /path .* does not exist in/i.test(message) || /no such path/i.test(message) || /no matching entries/i.test(message) || /No such file or directory/i.test(message) || /Path .* not found/i.test(message);
|
|
1710
|
+
}
|
|
1711
|
+
function isMissingFileError(error) {
|
|
1712
|
+
return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
|
|
1713
|
+
}
|
|
1714
|
+
function normalizePath(relativePath) {
|
|
1715
|
+
return relativePath.split(node_path.default.sep).join("/");
|
|
1716
|
+
}
|
|
1717
|
+
function clone(value) {
|
|
1718
|
+
return JSON.parse(JSON.stringify(value));
|
|
1719
|
+
}
|
|
1720
|
+
async function run(args) {
|
|
1721
|
+
return execFileAsync(args.command, args.args, {
|
|
1722
|
+
cwd: args.cwd,
|
|
1723
|
+
env: {
|
|
1724
|
+
...process.env,
|
|
1725
|
+
...args.env
|
|
1726
|
+
}
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
//#endregion
|
|
1730
|
+
exports.renderPreviewText = renderPreviewText;
|
|
1731
|
+
exports.startStandaloneTimelineServer = startStandaloneTimelineServer;
|
|
1732
|
+
|
|
1733
|
+
//# sourceMappingURL=server.cjs.map
|