difit 4.0.5 → 4.0.7

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.
Files changed (176) hide show
  1. package/README.ja.md +1 -0
  2. package/README.ko.md +1 -0
  3. package/README.md +1 -0
  4. package/README.zh.md +1 -0
  5. package/dist/cli/comment.d.ts +2 -0
  6. package/dist/cli/comment.js +91 -0
  7. package/dist/cli/comment.test.d.ts +1 -0
  8. package/dist/cli/comment.test.js +164 -0
  9. package/dist/cli/index.js +103 -12
  10. package/dist/cli/utils.d.ts +1 -0
  11. package/dist/cli/utils.js +7 -0
  12. package/dist/client/assets/{_baseFor-DKyA49xd.js → _baseFor-Dq1lbcoh.js} +1 -1
  13. package/dist/client/assets/{arc-COOp7iVe.js → arc-DX2p9X2Y.js} +1 -1
  14. package/dist/client/assets/architecture-YZFGNWBL-2zVtKbnG.js +1 -0
  15. package/dist/client/assets/architectureDiagram-Q4EWVU46-FixTWViB.js +36 -0
  16. package/dist/client/assets/{blockDiagram-DXYQGD6D-CtNJnEWN.js → blockDiagram-DXYQGD6D-CUAMgGr9.js} +1 -1
  17. package/dist/client/assets/{c4Diagram-AHTNJAMY-BqG-1m6C.js → c4Diagram-AHTNJAMY-BM_HNNZe.js} +1 -1
  18. package/dist/client/assets/channel-B_ddQhpW.js +1 -0
  19. package/dist/client/assets/{chunk-2KRD3SAO-DqP2NJNd.js → chunk-2KRD3SAO-DeT59g2K.js} +1 -1
  20. package/dist/client/assets/chunk-336JU56O-CaGvJA86.js +2 -0
  21. package/dist/client/assets/chunk-426QAEUC-CMTCMPn4.js +1 -0
  22. package/dist/client/assets/{chunk-4BX2VUAB-BT78EnQ6.js → chunk-4BX2VUAB-D9mNDl5f.js} +1 -1
  23. package/dist/client/assets/{chunk-4TB4RGXK-C4w_Bwzw.js → chunk-4TB4RGXK-Df3b4HEG.js} +2 -2
  24. package/dist/client/assets/{chunk-55IACEB6-z3MQSTaj.js → chunk-55IACEB6-dCWLe_n4.js} +1 -1
  25. package/dist/client/assets/{chunk-5FUZZQ4R-Chei69aj.js → chunk-5FUZZQ4R-EScvXcSN.js} +1 -1
  26. package/dist/client/assets/{chunk-5PVQY5BW-HgRiIs0X.js → chunk-5PVQY5BW-ail-oj89.js} +1 -1
  27. package/dist/client/assets/{chunk-67CJDMHE-B2q10-fp.js → chunk-67CJDMHE-CHeCIL1u.js} +1 -1
  28. package/dist/client/assets/{chunk-7N4EOEYR-DPgxysWq.js → chunk-7N4EOEYR-P0tNRVMZ.js} +1 -1
  29. package/dist/client/assets/{chunk-AA7GKIK3-BqmVmKLq.js → chunk-AA7GKIK3-DloBHWSo.js} +1 -1
  30. package/dist/client/assets/{chunk-BSJP7CBP-CaIgleFn.js → chunk-BSJP7CBP-CGLThsR8.js} +1 -1
  31. package/dist/client/assets/{chunk-CIAEETIT-ByD-tlNF.js → chunk-CIAEETIT-rCt2IEMp.js} +1 -1
  32. package/dist/client/assets/{chunk-EDXVE4YY-d3RUKKAj.js → chunk-EDXVE4YY-DIJEIKIq.js} +1 -1
  33. package/dist/client/assets/{chunk-ENJZ2VHE-CNq5Qmg9.js → chunk-ENJZ2VHE-CdrdxFfV.js} +1 -1
  34. package/dist/client/assets/{chunk-FMBD7UC4-DYfHJ6MV.js → chunk-FMBD7UC4-BH_GgR9u.js} +1 -1
  35. package/dist/client/assets/{chunk-FOC6F5B3-BRpSWlZj.js → chunk-FOC6F5B3-D71VljSN.js} +1 -1
  36. package/dist/client/assets/{chunk-ICPOFSXX-B_MThwG6.js → chunk-ICPOFSXX-2vcQKuhB.js} +2 -2
  37. package/dist/client/assets/{chunk-K5T4RW27-DmamW1Ds.js → chunk-K5T4RW27-BWIFd7pZ.js} +10 -10
  38. package/dist/client/assets/{chunk-KGLVRYIC-CRbg4c4z.js → chunk-KGLVRYIC-Ck8I8tdt.js} +1 -1
  39. package/dist/client/assets/{chunk-LIHQZDEY-CHQPSdB3.js → chunk-LIHQZDEY-Cc7TtI-w.js} +1 -1
  40. package/dist/client/assets/{chunk-ORNJ4GCN-CIsQ4Zi4.js → chunk-ORNJ4GCN-BMSqiphc.js} +1 -1
  41. package/dist/client/assets/{chunk-OYMX7WX6-Cxi0kdGg.js → chunk-OYMX7WX6-B5faFb53.js} +1 -1
  42. package/dist/client/assets/chunk-QZHKN3VN-B-G9G-FB.js +1 -0
  43. package/dist/client/assets/{chunk-U2HBQHQK-V_hneCfR.js → chunk-U2HBQHQK-BILTfRyq.js} +1 -1
  44. package/dist/client/assets/{chunk-X2U36JSP-De4pvO-I.js → chunk-X2U36JSP-D4-56gWx.js} +1 -1
  45. package/dist/client/assets/{chunk-XPW4576I-B_osXKp6.js → chunk-XPW4576I-SxB401Zg.js} +1 -1
  46. package/dist/client/assets/{chunk-YZCP3GAM-C_kqXssD.js → chunk-YZCP3GAM-CWXUVxFj.js} +1 -1
  47. package/dist/client/assets/{chunk-ZZ45TVLE-B_xtlma5.js → chunk-ZZ45TVLE-CXjZua4f.js} +1 -1
  48. package/dist/client/assets/classDiagram-6PBFFD2Q-DnUQ2iGN.js +1 -0
  49. package/dist/client/assets/classDiagram-v2-HSJHXN6E-Dwp5vuOB.js +1 -0
  50. package/dist/client/assets/clone-aWrl-obY.js +1 -0
  51. package/dist/client/assets/cose-bilkent-S5V4N54A-YToNpueF.js +1 -0
  52. package/dist/client/assets/{cytoscape.esm-DRReFUEO.js → cytoscape.esm-DdcHPZAZ.js} +2 -2
  53. package/dist/client/assets/{dagre-KV5264BT-BWYGReXF.js → dagre-KV5264BT-QFYoTa0z.js} +1 -1
  54. package/dist/client/assets/{dagre-DU-XBdcU.js → dagre-tvaMpP4D.js} +1 -1
  55. package/dist/client/assets/{diagram-5BDNPKRD-DpUUhvWz.js → diagram-5BDNPKRD-DM0NNmEN.js} +1 -1
  56. package/dist/client/assets/{diagram-G4DWMVQ6-BJoTrUAx.js → diagram-G4DWMVQ6-TiLkMmwt.js} +1 -1
  57. package/dist/client/assets/{diagram-MMDJMWI5-CAk1GW5g.js → diagram-MMDJMWI5-DM1ykqrB.js} +1 -1
  58. package/dist/client/assets/{diagram-TYMM5635-Cct6g7FA.js → diagram-TYMM5635-BEOLX1wr.js} +1 -1
  59. package/dist/client/assets/{dist-61sCfOmN.js → dist-CCBhd9az.js} +1 -1
  60. package/dist/client/assets/{erDiagram-SMLLAGMA-DHs2bXUj.js → erDiagram-SMLLAGMA-DZcjZq6z.js} +1 -1
  61. package/dist/client/assets/{flatten-mnWyE-RB.js → flatten-SRIRKgqP.js} +1 -1
  62. package/dist/client/assets/{flowDiagram-DWJPFMVM-DLu-6dfC.js → flowDiagram-DWJPFMVM-B1AVT9es.js} +1 -1
  63. package/dist/client/assets/ganttDiagram-T4ZO3ILL-BCEXws9V.js +292 -0
  64. package/dist/client/assets/gitGraph-7Q5UKJZL-BE3Mcr-v.js +1 -0
  65. package/dist/client/assets/{gitGraphDiagram-UUTBAWPF-Bc_rL3_k.js → gitGraphDiagram-UUTBAWPF-CVznBDOl.js} +1 -1
  66. package/dist/client/assets/{graphlib-BVMK0xYE.js → graphlib-C4fWcyt1.js} +1 -1
  67. package/dist/client/assets/index-6LShOAAb.js +79 -0
  68. package/dist/client/assets/index-C16wNcPQ.css +2 -0
  69. package/dist/client/assets/info-OMHHGYJF-CBpXVhw-.js +1 -0
  70. package/dist/client/assets/{infoDiagram-42DDH7IO-Cf8u4jgP.js → infoDiagram-42DDH7IO-D8Oxr-KJ.js} +1 -1
  71. package/dist/client/assets/{isEmpty-CiiIHfXR.js → isEmpty-CStpjy4G.js} +1 -1
  72. package/dist/client/assets/{ishikawaDiagram-UXIWVN3A-7n7DvfEb.js → ishikawaDiagram-UXIWVN3A-BE9KniVE.js} +1 -1
  73. package/dist/client/assets/{journeyDiagram-VCZTEJTY-BMkeQqJb.js → journeyDiagram-VCZTEJTY-B3lGcz06.js} +1 -1
  74. package/dist/client/assets/{kanban-definition-6JOO6SKY-B8KkeZLS.js → kanban-definition-6JOO6SKY-Bs1QdB0j.js} +1 -1
  75. package/dist/client/assets/{line-CVpcI6kj.js → line-CO4-KhEq.js} +1 -1
  76. package/dist/client/assets/{linear-DmhiOOKU.js → linear-CnaJKs0I.js} +1 -1
  77. package/dist/client/assets/mermaid-parser.core-CravK6bS.js +4 -0
  78. package/dist/client/assets/{mermaid.core-R7nXpPx-.js → mermaid.core-DTh9KJvF.js} +3 -3
  79. package/dist/client/assets/{mindmap-definition-QFDTVHPH-CwcHocMZ.js → mindmap-definition-QFDTVHPH-D2xU2hfX.js} +1 -1
  80. package/dist/client/assets/{ordinal-k--hYEme.js → ordinal-DIg8h6NI.js} +1 -1
  81. package/dist/client/assets/packet-4T2RLAQJ-abaJ3V5T.js +1 -0
  82. package/dist/client/assets/pie-ZZUOXDRM-B12dpA7V.js +1 -0
  83. package/dist/client/assets/{pieDiagram-DEJITSTG-BVAn8Lmr.js → pieDiagram-DEJITSTG-CRX6y4IQ.js} +1 -1
  84. package/dist/client/assets/{quadrantDiagram-34T5L4WZ-C2XZ_zxa.js → quadrantDiagram-34T5L4WZ-K2HFp8O8.js} +1 -1
  85. package/dist/client/assets/radar-PYXPWWZC-BbBaJJN8.js +1 -0
  86. package/dist/client/assets/{reduce-BTlHjXna.js → reduce-CG4cgj93.js} +1 -1
  87. package/dist/client/assets/{requirementDiagram-MS252O5E-CfO16pkI.js → requirementDiagram-MS252O5E-C-8AW0uI.js} +1 -1
  88. package/dist/client/assets/{sankeyDiagram-XADWPNL6-D_4_234M.js → sankeyDiagram-XADWPNL6-Bv-_ZFS5.js} +1 -1
  89. package/dist/client/assets/{sequenceDiagram-FGHM5R23-B-yHKMuK.js → sequenceDiagram-FGHM5R23-Bk4QYIPk.js} +1 -1
  90. package/dist/client/assets/src-XMuEuFcU.js +1 -0
  91. package/dist/client/assets/{stateDiagram-FHFEXIEX-BeG2di4I.js → stateDiagram-FHFEXIEX-CI1G7zGC.js} +1 -1
  92. package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-DQ0U-oto.js +1 -0
  93. package/dist/client/assets/{timeline-definition-GMOUNBTQ-DhtnMGcE.js → timeline-definition-GMOUNBTQ-CnXv8xHg.js} +1 -1
  94. package/dist/client/assets/treeView-SZITEDCU-CM0rCBUc.js +1 -0
  95. package/dist/client/assets/treemap-W4RFUUIX-CXoNE_rL.js +1 -0
  96. package/dist/client/assets/{vennDiagram-DHZGUBPP-CBn69TcQ.js → vennDiagram-DHZGUBPP-M5x471Ar.js} +1 -1
  97. package/dist/client/assets/wardley-RL74JXVD-B_EtnvOk.js +1 -0
  98. package/dist/client/assets/{wardleyDiagram-NUSXRM2D-CEoSJmN1.js → wardleyDiagram-NUSXRM2D-BG99uPNN.js} +1 -1
  99. package/dist/client/assets/{xychartDiagram-5P7HB3ND-BZ_X9tkn.js → xychartDiagram-5P7HB3ND-DO7Upr9G.js} +1 -1
  100. package/dist/client/index.html +2 -4
  101. package/dist/client/site-data/blobs/080c0e6/cHVibGljL3NpdGUtZGF0YS9vZy1pbWFnZS5wbmc.png +0 -0
  102. package/dist/client/site-data/blobs/55f23a1/bGFuZGluZy9wdWJsaWMvZGlmaXQvbG9nby5wbmc.png +0 -0
  103. package/dist/client/site-data/blobs/66ff7c6/cHVibGljL2xvZ28ucG5n.png +0 -0
  104. package/dist/client/site-data/blobs/e6977fe/cHVibGljL2xvZ28ucG5n.png +0 -0
  105. package/dist/client/site-data/manifest.json +1 -0
  106. package/dist/client/site-data/og-image.png +0 -0
  107. package/dist/client/site-data/snapshots/55f23a1...080c0e6.json +1 -0
  108. package/dist/client/site-data/snapshots/66ff7c6...e6977fe.json +1 -0
  109. package/dist/client/site-data/snapshots/7d40fd4...a72112f-comments.json +1 -0
  110. package/dist/client/site-data/snapshots/7d40fd4...a72112f.json +1 -0
  111. package/dist/server/file-watcher.d.ts +2 -1
  112. package/dist/server/file-watcher.js +9 -3
  113. package/dist/server/git-diff.d.ts +5 -0
  114. package/dist/server/git-diff.js +65 -1
  115. package/dist/server/git-diff.test.js +50 -0
  116. package/dist/server/server.js +265 -68
  117. package/dist/server/server.test.js +228 -0
  118. package/dist/tui/App.js +0 -1
  119. package/dist/types/diff.d.ts +15 -4
  120. package/dist/types/watch.d.ts +30 -1
  121. package/dist/utils/commentImports.d.ts +2 -0
  122. package/dist/utils/commentImports.js +119 -1
  123. package/dist/utils/editorOptions.d.ts +58 -35
  124. package/dist/utils/editorOptions.js +150 -24
  125. package/dist/utils/editorOptions.test.js +201 -9
  126. package/package.json +7 -4
  127. package/dist/client/assets/architecture-YZFGNWBL-Cs2Q6RQP.js +0 -1
  128. package/dist/client/assets/architectureDiagram-Q4EWVU46-BO4dVPUA.js +0 -36
  129. package/dist/client/assets/channel-_xDT1u3-.js +0 -1
  130. package/dist/client/assets/chunk-336JU56O-D1qa7Qzb.js +0 -2
  131. package/dist/client/assets/chunk-426QAEUC-6J_A_wvD.js +0 -1
  132. package/dist/client/assets/chunk-CFjPhJqf.js +0 -1
  133. package/dist/client/assets/chunk-QZHKN3VN-C0QzfgZ8.js +0 -1
  134. package/dist/client/assets/classDiagram-6PBFFD2Q-5XrS-DAQ.js +0 -1
  135. package/dist/client/assets/classDiagram-v2-HSJHXN6E-Covl2vKy.js +0 -1
  136. package/dist/client/assets/clone-rhRH8pyW.js +0 -1
  137. package/dist/client/assets/cose-bilkent-S5V4N54A-BvXFc7Rr.js +0 -1
  138. package/dist/client/assets/ganttDiagram-T4ZO3ILL-CMIzlKAR.js +0 -292
  139. package/dist/client/assets/gitGraph-7Q5UKJZL-A_wWsXju.js +0 -1
  140. package/dist/client/assets/index-BPoqJmrs.js +0 -79
  141. package/dist/client/assets/index-Cq_APK7Y.css +0 -2
  142. package/dist/client/assets/info-OMHHGYJF-Bv3kK2Bb.js +0 -1
  143. package/dist/client/assets/mermaid-parser.core-CnJ9Tv8l.js +0 -4
  144. package/dist/client/assets/packet-4T2RLAQJ-D2q3-9ae.js +0 -1
  145. package/dist/client/assets/pie-ZZUOXDRM-GivlQcUF.js +0 -1
  146. package/dist/client/assets/preload-helper-DSXbuxSR.js +0 -1
  147. package/dist/client/assets/radar-PYXPWWZC-C9pD6VNR.js +0 -1
  148. package/dist/client/assets/src-CjDs0_Ij.js +0 -1
  149. package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-DvcSq7KE.js +0 -1
  150. package/dist/client/assets/treeView-SZITEDCU-BSNk8_yV.js +0 -1
  151. package/dist/client/assets/treemap-W4RFUUIX-ym4zQztE.js +0 -1
  152. package/dist/client/assets/wardley-RL74JXVD-B02H6ReJ.js +0 -1
  153. /package/dist/client/assets/{array-BNor45A1.js → array-DOVTz2Mq.js} +0 -0
  154. /package/dist/client/assets/{defaultLocale-DPzUsThw.js → defaultLocale-Ck2Xxk-C.js} +0 -0
  155. /package/dist/client/assets/{init-C0L3woqb.js → init-Bft5Ffpj.js} +0 -0
  156. /package/dist/client/assets/{katex-FOM3xZj7.js → katex-CeIlAR55.js} +0 -0
  157. /package/dist/client/assets/{path-sMK4d_s9.js → path-DfRbCp9y.js} +0 -0
  158. /package/dist/client/assets/{prism-bash-iQBez6et.js → prism-bash-CPkZUJMA.js} +0 -0
  159. /package/dist/client/assets/{prism-clojure-CTkJ-FW_.js → prism-clojure-BpoF2XhX.js} +0 -0
  160. /package/dist/client/assets/{prism-csharp-DAAROvjt.js → prism-csharp-BEk8D1-3.js} +0 -0
  161. /package/dist/client/assets/{prism-dart-CMjMHaBW.js → prism-dart-ByLYrdQB.js} +0 -0
  162. /package/dist/client/assets/{prism-elixir-B9cwzXs0.js → prism-elixir-BZtyIEab.js} +0 -0
  163. /package/dist/client/assets/{prism-haskell-Vgx7BCAm.js → prism-haskell-NAsbeo3V.js} +0 -0
  164. /package/dist/client/assets/{prism-hcl-Du4YC80h.js → prism-hcl-crnGqmVp.js} +0 -0
  165. /package/dist/client/assets/{prism-java-CWuFbfVD.js → prism-java-BovStacA.js} +0 -0
  166. /package/dist/client/assets/{prism-markup-templating-h9TC-ifW.js → prism-markup-templating-Cl8NiLjy.js} +0 -0
  167. /package/dist/client/assets/{prism-nix-CqauNIYa.js → prism-nix-BS_cm_1n.js} +0 -0
  168. /package/dist/client/assets/{prism-perl-DhcRwJzx.js → prism-perl-DGLVMq5H.js} +0 -0
  169. /package/dist/client/assets/{prism-php-DcBIrISj.js → prism-php-BskSwJN8.js} +0 -0
  170. /package/dist/client/assets/{prism-protobuf-DuPg7Jbg.js → prism-protobuf-DfbIYpO7.js} +0 -0
  171. /package/dist/client/assets/{prism-ruby-lhDmuasn.js → prism-ruby-FBVh1PRE.js} +0 -0
  172. /package/dist/client/assets/{prism-scala-YlPat9I4.js → prism-scala--9AfMHPY.js} +0 -0
  173. /package/dist/client/assets/{prism-solidity-C3nR0EVH.js → prism-solidity-BgJNkj1z.js} +0 -0
  174. /package/dist/client/assets/{prism-sql-Cz-8DmQS.js → prism-sql-C9Czmpov.js} +0 -0
  175. /package/dist/client/assets/{prism-vim-C3oukvmk.js → prism-vim-CzUNf0WQ.js} +0 -0
  176. /package/dist/client/assets/{rough.esm-DeLgKbOI.js → rough.esm-Bbn_-PMU.js} +0 -0
@@ -1,45 +1,171 @@
1
+ export const CUSTOM_EDITOR_ID = 'custom';
2
+ export const NONE_EDITOR_ID = 'none';
3
+ export const DEFAULT_EDITOR_ID = 'vscode';
1
4
  export const EDITOR_OPTIONS = [
2
5
  {
3
6
  id: 'vscode',
4
7
  label: 'VS Code',
5
- protocol: 'vscode',
6
- cliCommand: 'code',
7
- cliArgs: [],
8
- lineFormat: 'goto-flag',
9
- aliases: ['vscode', 'code'],
8
+ command: 'code',
9
+ argsTemplate: '-g %file:%line',
10
10
  },
11
11
  {
12
12
  id: 'cursor',
13
13
  label: 'Cursor',
14
- protocol: 'cursor',
15
- cliCommand: 'cursor',
16
- cliArgs: [],
17
- lineFormat: 'goto-flag',
18
- aliases: ['cursor'],
14
+ command: 'cursor',
15
+ argsTemplate: '-g %file:%line',
19
16
  },
20
17
  {
21
18
  id: 'zed',
22
19
  label: 'Zed',
23
- protocol: 'zed',
24
- cliCommand: 'zed',
25
- cliArgs: [],
26
- lineFormat: 'path-suffix',
27
- aliases: ['zed'],
20
+ command: 'zed',
21
+ argsTemplate: '%file:%line',
22
+ },
23
+ {
24
+ id: 'textmate',
25
+ label: 'TextMate',
26
+ command: 'mate',
27
+ argsTemplate: '-l %line %file',
28
+ },
29
+ {
30
+ id: 'bbedit',
31
+ label: 'BBEdit',
32
+ command: 'bbedit',
33
+ argsTemplate: '+%line %file',
34
+ },
35
+ {
36
+ id: 'emacs',
37
+ label: 'Emacs',
38
+ command: 'emacsclient',
39
+ argsTemplate: '--no-wait +%line %file',
40
+ },
41
+ {
42
+ id: 'macvim',
43
+ label: 'MacVim',
44
+ command: 'mvim',
45
+ argsTemplate: '--remote-silent +%line %file',
46
+ },
47
+ {
48
+ id: 'sublime',
49
+ label: 'Sublime Text',
50
+ command: 'subl',
51
+ argsTemplate: '%file:%line',
52
+ },
53
+ {
54
+ id: 'nova',
55
+ label: 'Nova',
56
+ command: 'nova',
57
+ argsTemplate: 'open %file -l %line',
58
+ },
59
+ {
60
+ id: 'custom',
61
+ label: 'Custom…',
62
+ command: '',
63
+ argsTemplate: '',
28
64
  },
29
65
  {
30
66
  id: 'none',
31
67
  label: 'Hide “Open in editor” button',
32
- protocol: null,
33
- cliCommand: null,
34
- cliArgs: [],
35
- lineFormat: 'goto-flag',
36
- aliases: ['none', 'disabled', 'off'],
68
+ command: '',
69
+ argsTemplate: '',
37
70
  },
38
71
  ];
39
- export const DEFAULT_EDITOR_ID = 'vscode';
72
+ export const DEFAULT_EDITOR_OPTION = EDITOR_OPTIONS.find((option) => option.id === DEFAULT_EDITOR_ID) ?? EDITOR_OPTIONS[0];
73
+ /**
74
+ * Case-insensitive lookup by id. Used mostly for the `DIFIT_EDITOR` / `EDITOR`
75
+ * environment-variable fallback on the server; the browser UI passes the full
76
+ * `{id, command, argsTemplate}` shape so it does not rely on this helper.
77
+ */
40
78
  export const resolveEditorOption = (input) => {
41
79
  const normalized = (input ?? DEFAULT_EDITOR_ID).toLowerCase();
42
- const matched = EDITOR_OPTIONS.find((option) => option.id === normalized || option.aliases.some((alias) => alias === normalized));
43
- const fallback = EDITOR_OPTIONS.find((option) => option.id === DEFAULT_EDITOR_ID);
44
- return matched ?? fallback ?? EDITOR_OPTIONS[0];
80
+ const matched = EDITOR_OPTIONS.find((option) => option.id === normalized);
81
+ return matched ?? DEFAULT_EDITOR_OPTION;
82
+ };
83
+ /**
84
+ * Tokenise a user-supplied arguments template string into individual CLI
85
+ * arguments. Supports single and double quoted segments so paths and flags
86
+ * containing spaces can be preserved.
87
+ *
88
+ * Inside double-quoted segments `\"` decodes to a literal `"` and `\\` to a
89
+ * literal `\`, mirroring common shell intuition. Other `\x` sequences pass
90
+ * through untouched so Lisp/regex fragments like `\s` or `\$` stay intact.
91
+ * Single-quoted segments are fully literal. The template is handed to
92
+ * `spawn` as argv directly – no shell is involved.
93
+ */
94
+ export const parseEditorArgsTemplate = (template) => {
95
+ const input = template ?? '';
96
+ const tokens = [];
97
+ const n = input.length;
98
+ let i = 0;
99
+ while (i < n) {
100
+ while (i < n && /\s/.test(input[i] ?? ''))
101
+ i++;
102
+ if (i >= n)
103
+ break;
104
+ let token = '';
105
+ let inQuote = null;
106
+ while (i < n) {
107
+ const ch = input[i];
108
+ if (ch === undefined)
109
+ break;
110
+ if (inQuote === '"') {
111
+ if (ch === '\\' && i + 1 < n) {
112
+ const next = input[i + 1];
113
+ if (next === '"' || next === '\\') {
114
+ token += next;
115
+ i += 2;
116
+ continue;
117
+ }
118
+ }
119
+ if (ch === inQuote) {
120
+ inQuote = null;
121
+ i++;
122
+ }
123
+ else {
124
+ token += ch;
125
+ i++;
126
+ }
127
+ continue;
128
+ }
129
+ if (inQuote === "'") {
130
+ if (ch === inQuote) {
131
+ inQuote = null;
132
+ i++;
133
+ }
134
+ else {
135
+ token += ch;
136
+ i++;
137
+ }
138
+ continue;
139
+ }
140
+ if (ch === '"' || ch === "'") {
141
+ inQuote = ch;
142
+ i++;
143
+ continue;
144
+ }
145
+ if (/\s/.test(ch))
146
+ break;
147
+ token += ch;
148
+ i++;
149
+ }
150
+ tokens.push(token);
151
+ }
152
+ return tokens;
153
+ };
154
+ /**
155
+ * Build the final `{ command, args }` tuple to hand to `child_process.spawn`.
156
+ * Returns `null` when the command is empty so callers can surface a clear
157
+ * "not configured" error instead of spawning an empty process.
158
+ *
159
+ * `%file` and `%line` are substituted as-is inside each token, so composite
160
+ * tokens such as `"%file:%line"` become `"/abs/file.ts:42"`. When no line
161
+ * number is available the substitution falls back to `1`.
162
+ */
163
+ export const buildEditorSpawnSpec = (input) => {
164
+ const command = input.command.trim();
165
+ if (!command)
166
+ return null;
167
+ const tokens = parseEditorArgsTemplate(input.argsTemplate);
168
+ const lineValue = String(input.lineNumber ?? 1);
169
+ const args = tokens.map((piece) => piece.replaceAll('%file', input.filePath).replaceAll('%line', lineValue));
170
+ return { command, args };
45
171
  };
@@ -1,24 +1,216 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { DEFAULT_EDITOR_ID, EDITOR_OPTIONS, resolveEditorOption } from './editorOptions';
2
+ import { buildEditorSpawnSpec, CUSTOM_EDITOR_ID, DEFAULT_EDITOR_ID, EDITOR_OPTIONS, NONE_EDITOR_ID, parseEditorArgsTemplate, resolveEditorOption, } from './editorOptions';
3
3
  describe('editorOptions', () => {
4
- it('includes Zed in editor options', () => {
4
+ it('includes Zed in editor options with the unified command+args shape', () => {
5
5
  const zed = EDITOR_OPTIONS.find((option) => option.id === 'zed');
6
6
  expect(zed).toBeDefined();
7
7
  expect(zed).toMatchObject({
8
8
  id: 'zed',
9
9
  label: 'Zed',
10
- protocol: 'zed',
11
- cliCommand: 'zed',
12
- lineFormat: 'path-suffix',
10
+ command: 'zed',
11
+ argsTemplate: '%file:%line',
13
12
  });
14
13
  });
15
- it('resolves zed editor from id', () => {
14
+ it('resolves zed editor from id and case-insensitively', () => {
16
15
  expect(resolveEditorOption('zed').id).toBe('zed');
17
- });
18
- it('resolves zed editor case-insensitively', () => {
19
16
  expect(resolveEditorOption('ZED').id).toBe('zed');
20
17
  });
21
- it('falls back to default editor when unknown value is passed', () => {
18
+ it('falls back to the default editor when an unknown value is passed', () => {
22
19
  expect(resolveEditorOption('unknown-editor').id).toBe(DEFAULT_EDITOR_ID);
23
20
  });
21
+ it('keeps the "none" option with empty command/args so the UI can hide inputs', () => {
22
+ const none = EDITOR_OPTIONS.find((option) => option.id === NONE_EDITOR_ID);
23
+ expect(none).toBeDefined();
24
+ expect(none?.command).toBe('');
25
+ expect(none?.argsTemplate).toBe('');
26
+ });
27
+ it('exposes a "custom" placeholder entry with empty command/args', () => {
28
+ const custom = EDITOR_OPTIONS.find((option) => option.id === CUSTOM_EDITOR_ID);
29
+ expect(custom).toBeDefined();
30
+ expect(custom?.label).toBe('Custom…');
31
+ expect(custom?.command).toBe('');
32
+ expect(custom?.argsTemplate).toBe('');
33
+ });
34
+ it('excludes editors that are no longer widely used for code review', () => {
35
+ const ids = EDITOR_OPTIONS.map((option) => option.id);
36
+ for (const deprecated of ['textwrangler', 'lyx', 'texmaker', 'alpha', 'atom', 'textadept']) {
37
+ expect(ids, `${deprecated} should not be registered`).not.toContain(deprecated);
38
+ }
39
+ });
40
+ describe('preset editor templates', () => {
41
+ const cases = [
42
+ {
43
+ id: 'vscode',
44
+ command: 'code',
45
+ argsTemplate: '-g %file:%line',
46
+ expectedArgs: ['-g', '/tmp/file.ts:42'],
47
+ },
48
+ {
49
+ id: 'cursor',
50
+ command: 'cursor',
51
+ argsTemplate: '-g %file:%line',
52
+ expectedArgs: ['-g', '/tmp/file.ts:42'],
53
+ },
54
+ {
55
+ id: 'zed',
56
+ command: 'zed',
57
+ argsTemplate: '%file:%line',
58
+ expectedArgs: ['/tmp/file.ts:42'],
59
+ },
60
+ {
61
+ id: 'textmate',
62
+ command: 'mate',
63
+ argsTemplate: '-l %line %file',
64
+ expectedArgs: ['-l', '42', '/tmp/file.ts'],
65
+ },
66
+ {
67
+ id: 'bbedit',
68
+ command: 'bbedit',
69
+ argsTemplate: '+%line %file',
70
+ expectedArgs: ['+42', '/tmp/file.ts'],
71
+ },
72
+ {
73
+ id: 'emacs',
74
+ command: 'emacsclient',
75
+ argsTemplate: '--no-wait +%line %file',
76
+ expectedArgs: ['--no-wait', '+42', '/tmp/file.ts'],
77
+ },
78
+ {
79
+ id: 'macvim',
80
+ command: 'mvim',
81
+ argsTemplate: '--remote-silent +%line %file',
82
+ expectedArgs: ['--remote-silent', '+42', '/tmp/file.ts'],
83
+ },
84
+ {
85
+ id: 'sublime',
86
+ command: 'subl',
87
+ argsTemplate: '%file:%line',
88
+ expectedArgs: ['/tmp/file.ts:42'],
89
+ },
90
+ {
91
+ id: 'nova',
92
+ command: 'nova',
93
+ argsTemplate: 'open %file -l %line',
94
+ expectedArgs: ['open', '/tmp/file.ts', '-l', '42'],
95
+ },
96
+ ];
97
+ for (const testCase of cases) {
98
+ it(`registers ${testCase.id} with the expected command and template`, () => {
99
+ const option = EDITOR_OPTIONS.find((entry) => entry.id === testCase.id);
100
+ expect(option, `${testCase.id} should be registered`).toBeDefined();
101
+ expect(option?.command).toBe(testCase.command);
102
+ expect(option?.argsTemplate).toBe(testCase.argsTemplate);
103
+ });
104
+ it(`builds a spawn spec for ${testCase.id} with %file/%line substituted`, () => {
105
+ const option = resolveEditorOption(testCase.id);
106
+ const spec = buildEditorSpawnSpec({
107
+ command: option.command,
108
+ argsTemplate: option.argsTemplate,
109
+ filePath: '/tmp/file.ts',
110
+ lineNumber: 42,
111
+ });
112
+ expect(spec).not.toBeNull();
113
+ expect(spec?.command).toBe(testCase.command);
114
+ expect(spec?.args).toEqual(testCase.expectedArgs);
115
+ });
116
+ }
117
+ });
118
+ describe('parseEditorArgsTemplate', () => {
119
+ it('splits whitespace-separated tokens', () => {
120
+ expect(parseEditorArgsTemplate('-l %line %file')).toEqual(['-l', '%line', '%file']);
121
+ });
122
+ it('collapses runs of whitespace', () => {
123
+ expect(parseEditorArgsTemplate(' -l %line\t%file ')).toEqual(['-l', '%line', '%file']);
124
+ });
125
+ it('preserves double-quoted segments', () => {
126
+ expect(parseEditorArgsTemplate('+%line "%file"')).toEqual(['+%line', '%file']);
127
+ });
128
+ it('preserves single-quoted segments', () => {
129
+ expect(parseEditorArgsTemplate("'%file' -line %line")).toEqual(['%file', '-line', '%line']);
130
+ });
131
+ it('keeps adjacent quoted and unquoted fragments in a single token', () => {
132
+ expect(parseEditorArgsTemplate('"%file":%line')).toEqual(['%file:%line']);
133
+ });
134
+ it('returns an empty array for empty or whitespace-only input', () => {
135
+ expect(parseEditorArgsTemplate('')).toEqual([]);
136
+ expect(parseEditorArgsTemplate(' \t ')).toEqual([]);
137
+ expect(parseEditorArgsTemplate(undefined)).toEqual([]);
138
+ expect(parseEditorArgsTemplate(null)).toEqual([]);
139
+ });
140
+ it('decodes \\" and \\\\ inside double-quoted segments', () => {
141
+ expect(parseEditorArgsTemplate('--msg "say \\"hi\\""')).toEqual(['--msg', 'say "hi"']);
142
+ expect(parseEditorArgsTemplate('"a\\\\b"')).toEqual(['a\\b']);
143
+ });
144
+ it('leaves other backslash sequences inside double quotes untouched', () => {
145
+ expect(parseEditorArgsTemplate('"\\s+ \\$PATH"')).toEqual(['\\s+ \\$PATH']);
146
+ });
147
+ it('treats single-quoted segments as fully literal', () => {
148
+ expect(parseEditorArgsTemplate('\'say \\"hi\\"\'')).toEqual(['say \\"hi\\"']);
149
+ });
150
+ it('supports an emacsclient --eval template with escaped double quotes', () => {
151
+ const template = '--eval "(tctony/persp-view-file-line-external \\"%file\\" %line)"';
152
+ expect(parseEditorArgsTemplate(template)).toEqual([
153
+ '--eval',
154
+ '(tctony/persp-view-file-line-external "%file" %line)',
155
+ ]);
156
+ });
157
+ });
158
+ describe('buildEditorSpawnSpec', () => {
159
+ it('returns null when the command is blank', () => {
160
+ expect(buildEditorSpawnSpec({
161
+ command: '',
162
+ argsTemplate: '-l %line %file',
163
+ filePath: '/tmp/file.ts',
164
+ lineNumber: 1,
165
+ })).toBeNull();
166
+ expect(buildEditorSpawnSpec({
167
+ command: ' ',
168
+ argsTemplate: '%file',
169
+ filePath: '/tmp/file.ts',
170
+ lineNumber: 1,
171
+ })).toBeNull();
172
+ });
173
+ it('trims the command and substitutes %file and %line', () => {
174
+ const spec = buildEditorSpawnSpec({
175
+ command: ' mate ',
176
+ argsTemplate: '-l %line "%file"',
177
+ filePath: '/tmp/file.ts',
178
+ lineNumber: 12,
179
+ });
180
+ expect(spec).toEqual({
181
+ command: 'mate',
182
+ args: ['-l', '12', '/tmp/file.ts'],
183
+ });
184
+ });
185
+ it('substitutes line 1 when no line number is provided', () => {
186
+ const spec = buildEditorSpawnSpec({
187
+ command: 'mate',
188
+ argsTemplate: '-l %line %file',
189
+ filePath: '/tmp/file.ts',
190
+ lineNumber: null,
191
+ });
192
+ expect(spec?.args).toEqual(['-l', '1', '/tmp/file.ts']);
193
+ });
194
+ it('returns an empty args array when the template is empty', () => {
195
+ const spec = buildEditorSpawnSpec({
196
+ command: 'my-editor',
197
+ argsTemplate: '',
198
+ filePath: '/tmp/file.ts',
199
+ lineNumber: 3,
200
+ });
201
+ expect(spec).toEqual({ command: 'my-editor', args: [] });
202
+ });
203
+ it('builds a spawn spec for an emacsclient --eval elisp invocation', () => {
204
+ const spec = buildEditorSpawnSpec({
205
+ command: 'emacsclient',
206
+ argsTemplate: '--eval "(tctony/persp-view-file-line-external \\"%file\\" %line)"',
207
+ filePath: '/tmp/foo.ts',
208
+ lineNumber: 42,
209
+ });
210
+ expect(spec).toEqual({
211
+ command: 'emacsclient',
212
+ args: ['--eval', '(tctony/persp-view-file-line-external "/tmp/foo.ts" 42)'],
213
+ });
214
+ });
215
+ });
24
216
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "difit",
3
- "version": "4.0.5",
3
+ "version": "4.0.7",
4
4
  "description": "A lightweight command-line tool that spins up a local web server to display Git commit diffs in a GitHub-like Files changed view",
5
5
  "keywords": [
6
6
  "cli",
@@ -37,9 +37,12 @@
37
37
  },
38
38
  "scripts": {
39
39
  "dev": "node scripts/dev.js",
40
+ "dev:site": "pnpm run build:cli && node scripts/export-site-data.js && vite --config vite.config.site.ts --open /",
40
41
  "dev:cli": "tsc --project tsconfig.cli.json && NODE_ENV=development node dist/cli/index.js",
41
- "build": "tsc -b && vite build",
42
+ "build": "pnpm run build:cli && vite build",
43
+ "build:site": "pnpm run build:cli && GITHUB_PAGES=true vite build --config vite.config.site.ts",
42
44
  "build:cli": "tsc --project tsconfig.cli.json",
45
+ "export:site-data": "node scripts/export-site-data.js",
43
46
  "package:vscode": "pnpm -C packages/vscode run package",
44
47
  "start": "pnpm run build && node dist/cli/index.js",
45
48
  "check": "oxlint . --type-aware --type-check --deny-warnings --report-unused-disable-directives",
@@ -93,7 +96,7 @@
93
96
  "happy-dom": "^20.0.0",
94
97
  "knip": "^6.0.0",
95
98
  "lefthook": "^2.0.0",
96
- "oxfmt": "^0.45.0",
99
+ "oxfmt": "^0.46.0",
97
100
  "oxlint": "^1.49.0",
98
101
  "oxlint-tsgolint": "^0.21.0",
99
102
  "playwright": "^1.54.1",
@@ -107,5 +110,5 @@
107
110
  "engines": {
108
111
  "node": ">=21.0.0"
109
112
  },
110
- "packageManager": "pnpm@10.33.0"
113
+ "packageManager": "pnpm@10.33.2"
111
114
  }
@@ -1 +0,0 @@
1
- import"./chunk-K5T4RW27-DmamW1Ds.js";import{n as e}from"./chunk-7N4EOEYR-DPgxysWq.js";export{e as createArchitectureServices};