sidediff 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (330) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +76 -0
  3. package/dist/cli.js +125 -0
  4. package/dist/git.js +42 -0
  5. package/dist/notes.js +66 -0
  6. package/dist/protocol.js +91 -0
  7. package/dist/repo.js +12 -0
  8. package/dist/server.js +139 -0
  9. package/dist/web/assets/abap-CLvhMVsD.js +1 -0
  10. package/dist/web/assets/actionscript-3-Cs6JJXT5.js +1 -0
  11. package/dist/web/assets/ada-C5qYipkI.js +1 -0
  12. package/dist/web/assets/ahk-CA9gdOdF.js +1 -0
  13. package/dist/web/assets/ahk2-_6rzW9BS.js +1 -0
  14. package/dist/web/assets/andromeeda-vGVdxbeo.js +1 -0
  15. package/dist/web/assets/angular-html-Dj8aBfh3.js +1 -0
  16. package/dist/web/assets/angular-ts-ftNhoKSB.js +1 -0
  17. package/dist/web/assets/apache-U0d_L8uA.js +1 -0
  18. package/dist/web/assets/apex-KFGFyKwG.js +1 -0
  19. package/dist/web/assets/apl-DbBzXXyq.js +1 -0
  20. package/dist/web/assets/applescript-CCn79oCD.js +1 -0
  21. package/dist/web/assets/ara-4CJ0cIlV.js +1 -0
  22. package/dist/web/assets/asciidoc-SCjQUq34.js +1 -0
  23. package/dist/web/assets/asm-Cmm7eHzH.js +1 -0
  24. package/dist/web/assets/astro-TyaaHnrT.js +1 -0
  25. package/dist/web/assets/aurora-x-CDeNXAV0.js +1 -0
  26. package/dist/web/assets/awk-BWXHIvNe.js +1 -0
  27. package/dist/web/assets/ayu-dark-DluEY0Gj.js +1 -0
  28. package/dist/web/assets/ayu-light-C3h-C4tm.js +1 -0
  29. package/dist/web/assets/ayu-mirage-Bqwy1Gya.js +1 -0
  30. package/dist/web/assets/ballerina-B7ZEbQpA.js +1 -0
  31. package/dist/web/assets/bat-qdxAdBWQ.js +1 -0
  32. package/dist/web/assets/beancount-D-usSTwE.js +1 -0
  33. package/dist/web/assets/berry-DKpUyyne.js +1 -0
  34. package/dist/web/assets/bibtex-Ci_nEsc7.js +1 -0
  35. package/dist/web/assets/bicep-CUHmPFLl.js +1 -0
  36. package/dist/web/assets/bird2-CYIUgYqB.js +1 -0
  37. package/dist/web/assets/blade-BA1GKJkc.js +1 -0
  38. package/dist/web/assets/bsl-_VjlB9mv.js +1 -0
  39. package/dist/web/assets/c-DDH9g_2O.js +1 -0
  40. package/dist/web/assets/c3-D0apG41V.js +1 -0
  41. package/dist/web/assets/cadence-CQ2zXKGN.js +1 -0
  42. package/dist/web/assets/cairo-DLTphjLi.js +1 -0
  43. package/dist/web/assets/catppuccin-frappe-C9Q5zVZY.js +1 -0
  44. package/dist/web/assets/catppuccin-latte-CtsyrtPq.js +1 -0
  45. package/dist/web/assets/catppuccin-macchiato-R8nAf1vR.js +1 -0
  46. package/dist/web/assets/catppuccin-mocha-DjuaAJKi.js +1 -0
  47. package/dist/web/assets/chapel-29dipupd.js +1 -0
  48. package/dist/web/assets/clarity-CtfBBspg.js +1 -0
  49. package/dist/web/assets/clojure-DqKBuwfJ.js +1 -0
  50. package/dist/web/assets/cmake-Bj61d0ZC.js +1 -0
  51. package/dist/web/assets/cobol-BzMyTAzq.js +1 -0
  52. package/dist/web/assets/codeowners-C8r90Shi.js +1 -0
  53. package/dist/web/assets/codeql-oeQT6MSM.js +1 -0
  54. package/dist/web/assets/coffee-BWiwUSVA.js +1 -0
  55. package/dist/web/assets/common-lisp-Cv5bFMCO.js +1 -0
  56. package/dist/web/assets/coq-DuBRPxYg.js +1 -0
  57. package/dist/web/assets/cpp-CUlwn2OF.js +1 -0
  58. package/dist/web/assets/crystal-Ddv8Z1dX.js +1 -0
  59. package/dist/web/assets/csharp-oqKa8noW.js +1 -0
  60. package/dist/web/assets/css-CYo760jy.js +1 -0
  61. package/dist/web/assets/csv-Dx-8-gkx.js +1 -0
  62. package/dist/web/assets/cue-CE9AQfxI.js +1 -0
  63. package/dist/web/assets/cypher-ClKdZ_lG.js +1 -0
  64. package/dist/web/assets/d-qD-0Kul2.js +1 -0
  65. package/dist/web/assets/dark-plus-Cs2F2srj.js +1 -0
  66. package/dist/web/assets/dart-CnvKMtbv.js +1 -0
  67. package/dist/web/assets/dax-BkyTk9wS.js +1 -0
  68. package/dist/web/assets/desktop-Dlh5hvp9.js +1 -0
  69. package/dist/web/assets/diff-woXpYk--.js +1 -0
  70. package/dist/web/assets/docker-IyjqRm3v.js +1 -0
  71. package/dist/web/assets/dotenv-_5a1GRtc.js +1 -0
  72. package/dist/web/assets/dracula-BHWKrbxM.js +1 -0
  73. package/dist/web/assets/dracula-soft-5eyTD99u.js +1 -0
  74. package/dist/web/assets/dream-maker-DW3nJb8Q.js +1 -0
  75. package/dist/web/assets/edge-zjTGWdK2.js +1 -0
  76. package/dist/web/assets/elixir-srAq9Nfu.js +1 -0
  77. package/dist/web/assets/elm-CicaJIim.js +1 -0
  78. package/dist/web/assets/emacs-lisp-c_oH4hRZ.js +1 -0
  79. package/dist/web/assets/erb-hGpVpQFs.js +1 -0
  80. package/dist/web/assets/erlang-Cphh6RMH.js +1 -0
  81. package/dist/web/assets/everforest-dark-sB-x3p7T.js +1 -0
  82. package/dist/web/assets/everforest-light-Df2xbC6M.js +1 -0
  83. package/dist/web/assets/fennel-DQxkIbk2.js +1 -0
  84. package/dist/web/assets/fish-BJitypiv.js +1 -0
  85. package/dist/web/assets/fluent-C03EYrpw.js +1 -0
  86. package/dist/web/assets/fortran-fixed-form-DEKoE2YW.js +1 -0
  87. package/dist/web/assets/fortran-free-form-CYNrtFtB.js +1 -0
  88. package/dist/web/assets/fsharp-D13ZGOAj.js +1 -0
  89. package/dist/web/assets/gdresource-xMC7jr7x.js +1 -0
  90. package/dist/web/assets/gdscript-DYvRD3hg.js +1 -0
  91. package/dist/web/assets/gdshader-CBce3t8t.js +1 -0
  92. package/dist/web/assets/genie-CV2tkWYe.js +1 -0
  93. package/dist/web/assets/gherkin-DExj1W_8.js +1 -0
  94. package/dist/web/assets/git-commit-BSykSTBG.js +1 -0
  95. package/dist/web/assets/git-rebase-BGxv3-fx.js +1 -0
  96. package/dist/web/assets/github-dark-C-LZuMrd.js +1 -0
  97. package/dist/web/assets/github-dark-default-DXG-b-1a.js +1 -0
  98. package/dist/web/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
  99. package/dist/web/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
  100. package/dist/web/assets/github-light-EUqPIrTm.js +1 -0
  101. package/dist/web/assets/github-light-default-BXViO-2h.js +1 -0
  102. package/dist/web/assets/github-light-high-contrast-B68TUdTA.js +1 -0
  103. package/dist/web/assets/gleam-CSRkHgEL.js +1 -0
  104. package/dist/web/assets/glimmer-js-CsH02tq2.js +1 -0
  105. package/dist/web/assets/glimmer-ts-CZM2Jwd7.js +1 -0
  106. package/dist/web/assets/glsl-Ch75cjmr.js +1 -0
  107. package/dist/web/assets/gn-ilITqXS6.js +1 -0
  108. package/dist/web/assets/gnuplot-7GGW24-e.js +1 -0
  109. package/dist/web/assets/go-rLFTqkRN.js +1 -0
  110. package/dist/web/assets/graphql-CoisBgeB.js +1 -0
  111. package/dist/web/assets/groovy-CacY0gHj.js +1 -0
  112. package/dist/web/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
  113. package/dist/web/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
  114. package/dist/web/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
  115. package/dist/web/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
  116. package/dist/web/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
  117. package/dist/web/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
  118. package/dist/web/assets/hack-BYn8wpdu.js +1 -0
  119. package/dist/web/assets/haml-Cphyb1cU.js +1 -0
  120. package/dist/web/assets/handlebars-954iSt8h.js +1 -0
  121. package/dist/web/assets/haskell-D8IpX4py.js +1 -0
  122. package/dist/web/assets/haxe-C8oCnwJJ.js +1 -0
  123. package/dist/web/assets/hcl-Dh228itO.js +1 -0
  124. package/dist/web/assets/hjson-CxZEssPk.js +1 -0
  125. package/dist/web/assets/hlsl-Cvrh5tZx.js +1 -0
  126. package/dist/web/assets/horizon-CE9ld1lL.js +1 -0
  127. package/dist/web/assets/horizon-bright-DSNQnXHK.js +1 -0
  128. package/dist/web/assets/houston-CsvMBhTu.js +1 -0
  129. package/dist/web/assets/html-CW0XP_9m.js +1 -0
  130. package/dist/web/assets/html-derivative-DeoJclAr.js +1 -0
  131. package/dist/web/assets/http-BF73-SbY.js +1 -0
  132. package/dist/web/assets/hurl-DanfLF4A.js +1 -0
  133. package/dist/web/assets/hxml-Dcm88srW.js +1 -0
  134. package/dist/web/assets/hy-CZbG8q4J.js +1 -0
  135. package/dist/web/assets/imba-DsUTQ-LC.js +1 -0
  136. package/dist/web/assets/index-BQY0pgMH.css +1 -0
  137. package/dist/web/assets/index-YO0PrGPk.js +1584 -0
  138. package/dist/web/assets/ini-B5eOa1yu.js +1 -0
  139. package/dist/web/assets/java-Nn-I9aNh.js +1 -0
  140. package/dist/web/assets/javascript-BlcaFq6f.js +1 -0
  141. package/dist/web/assets/jinja-FusoLKWE.js +1 -0
  142. package/dist/web/assets/jison-DjZ7HIST.js +1 -0
  143. package/dist/web/assets/json-BCYD24v5.js +1 -0
  144. package/dist/web/assets/json5-BR5RXkoi.js +1 -0
  145. package/dist/web/assets/jsonc-CYpm1nAK.js +1 -0
  146. package/dist/web/assets/jsonl-CmCQp5Yx.js +1 -0
  147. package/dist/web/assets/jsonnet-CJTPZ8u_.js +1 -0
  148. package/dist/web/assets/jssm-DXw9l8Rf.js +1 -0
  149. package/dist/web/assets/jsx-CNOvdA8h.js +1 -0
  150. package/dist/web/assets/julia-DkIA25lm.js +1 -0
  151. package/dist/web/assets/just-BUPCC9US.js +1 -0
  152. package/dist/web/assets/kanagawa-dragon-Bozd7klA.js +1 -0
  153. package/dist/web/assets/kanagawa-lotus-BmuOG9ZW.js +1 -0
  154. package/dist/web/assets/kanagawa-wave-BML5mF2J.js +1 -0
  155. package/dist/web/assets/kdl-CsD5j6eV.js +1 -0
  156. package/dist/web/assets/kotlin-DhhofPvG.js +1 -0
  157. package/dist/web/assets/kusto-C7mF5XQf.js +1 -0
  158. package/dist/web/assets/laserwave-C_8bwKvT.js +1 -0
  159. package/dist/web/assets/latex-BiQh0rst.js +1 -0
  160. package/dist/web/assets/lean-CewbzKMR.js +1 -0
  161. package/dist/web/assets/less-DVTAwKKz.js +1 -0
  162. package/dist/web/assets/light-plus-DVQuIRkW.js +1 -0
  163. package/dist/web/assets/liquid-P42zqSks.js +1 -0
  164. package/dist/web/assets/llvm-DqHOMS-s.js +1 -0
  165. package/dist/web/assets/log-BNLmms1o.js +1 -0
  166. package/dist/web/assets/logo-Cluzi2Zq.js +1 -0
  167. package/dist/web/assets/lua-D7V43U4m.js +1 -0
  168. package/dist/web/assets/luau-C2pBCSgt.js +1 -0
  169. package/dist/web/assets/make-Dixweg8N.js +1 -0
  170. package/dist/web/assets/markdown-BYOwaDjH.js +1 -0
  171. package/dist/web/assets/marko-DYlx_Ple.js +1 -0
  172. package/dist/web/assets/material-theme-Bm3Qr25_.js +1 -0
  173. package/dist/web/assets/material-theme-darker-2IIEA8gg.js +1 -0
  174. package/dist/web/assets/material-theme-lighter-uhdI0v04.js +1 -0
  175. package/dist/web/assets/material-theme-ocean-CHQ94UKr.js +1 -0
  176. package/dist/web/assets/material-theme-palenight-B5W6OYN7.js +1 -0
  177. package/dist/web/assets/matlab-D7qyCx1q.js +1 -0
  178. package/dist/web/assets/mdc-0IgF0rRD.js +1 -0
  179. package/dist/web/assets/mdx-DQZ5AkYe.js +1 -0
  180. package/dist/web/assets/mermaid-BxpfTIzt.js +1 -0
  181. package/dist/web/assets/min-dark-BSWPekZh.js +1 -0
  182. package/dist/web/assets/min-light-DDpmG2fV.js +1 -0
  183. package/dist/web/assets/mipsasm-BMqwQI7S.js +1 -0
  184. package/dist/web/assets/mojo-fg40_ntQ.js +1 -0
  185. package/dist/web/assets/monokai-B_bV9PQ9.js +1 -0
  186. package/dist/web/assets/moonbit-DgjMoLWr.js +1 -0
  187. package/dist/web/assets/move-BYnGhZoM.js +1 -0
  188. package/dist/web/assets/narrat-_X_XdTYD.js +1 -0
  189. package/dist/web/assets/nextflow-BJtWHP5T.js +1 -0
  190. package/dist/web/assets/nextflow-groovy-DJMQeKeT.js +1 -0
  191. package/dist/web/assets/nginx-DJNCyRLb.js +1 -0
  192. package/dist/web/assets/night-owl-DhmEMT88.js +1 -0
  193. package/dist/web/assets/night-owl-light-eJ-hLW7d.js +1 -0
  194. package/dist/web/assets/nim-OqNPuVQu.js +1 -0
  195. package/dist/web/assets/nix-IvuFDN5E.js +1 -0
  196. package/dist/web/assets/nord-Cb4Vim4T.js +1 -0
  197. package/dist/web/assets/nsis-CjNLYCLg.js +1 -0
  198. package/dist/web/assets/nushell-DHYfO66s.js +1 -0
  199. package/dist/web/assets/objective-c-D1A_Heim.js +1 -0
  200. package/dist/web/assets/objective-cpp-BsSzOQcm.js +1 -0
  201. package/dist/web/assets/ocaml-O90oeIOV.js +1 -0
  202. package/dist/web/assets/odin-B1RWQWA5.js +1 -0
  203. package/dist/web/assets/one-dark-pro-CLwyXe_n.js +1 -0
  204. package/dist/web/assets/one-light-D7Lr4KcI.js +1 -0
  205. package/dist/web/assets/openscad-BUDT5pXO.js +1 -0
  206. package/dist/web/assets/org-8FRCUuP_.js +1 -0
  207. package/dist/web/assets/pascal-4ZHwLPI5.js +1 -0
  208. package/dist/web/assets/perl-DD32dEVq.js +1 -0
  209. package/dist/web/assets/php-CGrYiunG.js +1 -0
  210. package/dist/web/assets/pierre-dark-CpLgRqie.js +1 -0
  211. package/dist/web/assets/pierre-dark-protanopia-deuteranopia-B35FxJx-.js +1 -0
  212. package/dist/web/assets/pierre-dark-soft-kZQmAZld.js +1 -0
  213. package/dist/web/assets/pierre-dark-tritanopia-CpjhbsIL.js +1 -0
  214. package/dist/web/assets/pierre-dark-vibrant-CpQYzh95.js +1 -0
  215. package/dist/web/assets/pierre-light-CoaEpmwp.js +1 -0
  216. package/dist/web/assets/pierre-light-protanopia-deuteranopia-0fSaH845.js +1 -0
  217. package/dist/web/assets/pierre-light-soft-lWLdNTOI.js +1 -0
  218. package/dist/web/assets/pierre-light-tritanopia-CEbqgOJL.js +1 -0
  219. package/dist/web/assets/pierre-light-vibrant-D80Fkn33.js +1 -0
  220. package/dist/web/assets/pkl-ot-7Btpt.js +1 -0
  221. package/dist/web/assets/plastic-DQwYfKfQ.js +1 -0
  222. package/dist/web/assets/plsql-DGHpHOYJ.js +1 -0
  223. package/dist/web/assets/po-BiJDBrnU.js +1 -0
  224. package/dist/web/assets/poimandres-DRFjx7u4.js +1 -0
  225. package/dist/web/assets/polar-C7UOKdEL.js +1 -0
  226. package/dist/web/assets/postcss-BXeXVLqQ.js +1 -0
  227. package/dist/web/assets/powerquery-DNMTfnFr.js +1 -0
  228. package/dist/web/assets/powershell-CCSX75yM.js +1 -0
  229. package/dist/web/assets/prisma-DMgNhnYS.js +1 -0
  230. package/dist/web/assets/prolog-iXnhIJG7.js +1 -0
  231. package/dist/web/assets/proto-DB4EqR-F.js +1 -0
  232. package/dist/web/assets/pug-dus5AV95.js +1 -0
  233. package/dist/web/assets/puppet-CDv2pdJW.js +1 -0
  234. package/dist/web/assets/purescript-9MfHhQsQ.js +1 -0
  235. package/dist/web/assets/python-gzcpVVnB.js +1 -0
  236. package/dist/web/assets/qml-Bkqr1Ngf.js +1 -0
  237. package/dist/web/assets/qmldir-DCQb3MpD.js +1 -0
  238. package/dist/web/assets/qss-Fe1Jh2GI.js +1 -0
  239. package/dist/web/assets/r-Wf5YaD8c.js +1 -0
  240. package/dist/web/assets/racket-DcIDlBhZ.js +1 -0
  241. package/dist/web/assets/raku-B3gFvitq.js +1 -0
  242. package/dist/web/assets/razor-Bb1_Fy1w.js +1 -0
  243. package/dist/web/assets/rbs-C0MpOrj4.js +1 -0
  244. package/dist/web/assets/red-DX1BQOs8.js +1 -0
  245. package/dist/web/assets/reg-CRGYupPL.js +1 -0
  246. package/dist/web/assets/regexp-DUqOy6G0.js +1 -0
  247. package/dist/web/assets/rel-BtDbiS_P.js +1 -0
  248. package/dist/web/assets/riscv-Ckw8ddFX.js +1 -0
  249. package/dist/web/assets/ron-VUp2lXgN.js +1 -0
  250. package/dist/web/assets/rose-pine-aMsaMD1D.js +1 -0
  251. package/dist/web/assets/rose-pine-dawn-8PwUyIlP.js +1 -0
  252. package/dist/web/assets/rose-pine-moon-DO-qXg-I.js +1 -0
  253. package/dist/web/assets/rosmsg-CAekHB0j.js +1 -0
  254. package/dist/web/assets/rst-DXbLQF2R.js +1 -0
  255. package/dist/web/assets/ruby-DC91uSyS.js +1 -0
  256. package/dist/web/assets/rust-Cfkwpbl8.js +1 -0
  257. package/dist/web/assets/sas-IZvF2UQ2.js +1 -0
  258. package/dist/web/assets/sass-DXrisJhu.js +1 -0
  259. package/dist/web/assets/scala-BiFt_7mu.js +1 -0
  260. package/dist/web/assets/scheme-DQCgrYNe.js +1 -0
  261. package/dist/web/assets/scss-F-mJY8RB.js +1 -0
  262. package/dist/web/assets/sdbl-bTVj8UrX.js +1 -0
  263. package/dist/web/assets/shaderlab-TOUzSsQk.js +1 -0
  264. package/dist/web/assets/shellscript-DF1WoJ0v.js +1 -0
  265. package/dist/web/assets/shellsession-Dic3oc5J.js +1 -0
  266. package/dist/web/assets/slack-dark-DnToyrRv.js +1 -0
  267. package/dist/web/assets/slack-ochin-B2OO5cIa.js +1 -0
  268. package/dist/web/assets/smalltalk-BHpQb5nM.js +1 -0
  269. package/dist/web/assets/smithy-Dgaz7aur.js +1 -0
  270. package/dist/web/assets/snazzy-light-4G7pJPwS.js +1 -0
  271. package/dist/web/assets/solarized-dark-UdsmMDCL.js +1 -0
  272. package/dist/web/assets/solarized-light-C1IUL_tW.js +1 -0
  273. package/dist/web/assets/solidity-Bj7E1mRn.js +1 -0
  274. package/dist/web/assets/soy-Ci77iUlD.js +1 -0
  275. package/dist/web/assets/sparql-D_iOobhT.js +1 -0
  276. package/dist/web/assets/splunk-BC2Px7Mm.js +1 -0
  277. package/dist/web/assets/sql-D9MqrRAN.js +1 -0
  278. package/dist/web/assets/ssh-config-BgfXC-Er.js +1 -0
  279. package/dist/web/assets/stata-UXNKNK6i.js +1 -0
  280. package/dist/web/assets/stylus-B6D30XZt.js +1 -0
  281. package/dist/web/assets/surrealql-Cgfwe1Vw.js +1 -0
  282. package/dist/web/assets/svelte-BhO1uvdv.js +1 -0
  283. package/dist/web/assets/swift-CyEgAFGc.js +1 -0
  284. package/dist/web/assets/synthwave-84-nFMaYfgc.js +1 -0
  285. package/dist/web/assets/system-verilog-Dh5ihWbf.js +1 -0
  286. package/dist/web/assets/systemd-BxMlprV5.js +1 -0
  287. package/dist/web/assets/talonscript-CohzipZa.js +1 -0
  288. package/dist/web/assets/tasl-DMoTqEGO.js +1 -0
  289. package/dist/web/assets/tcl-CZd0xW_V.js +1 -0
  290. package/dist/web/assets/templ-7o-QkWPh.js +1 -0
  291. package/dist/web/assets/terraform-DswuEJGm.js +1 -0
  292. package/dist/web/assets/tex-Cixhwf9g.js +1 -0
  293. package/dist/web/assets/tokyo-night-oM2G3aXe.js +1 -0
  294. package/dist/web/assets/toml-CcmNWLt0.js +1 -0
  295. package/dist/web/assets/ts-tags-en1NVoz-.js +1 -0
  296. package/dist/web/assets/tsv-sltzmVWM.js +1 -0
  297. package/dist/web/assets/tsx-D9KJ7A9A.js +1 -0
  298. package/dist/web/assets/turtle-ByJddavk.js +1 -0
  299. package/dist/web/assets/twig-CSb4sWqm.js +1 -0
  300. package/dist/web/assets/typescript-CLVSDbAp.js +1 -0
  301. package/dist/web/assets/typespec-BRdr0IET.js +1 -0
  302. package/dist/web/assets/typst-RHEHp6C0.js +1 -0
  303. package/dist/web/assets/v-DBj4K9d_.js +1 -0
  304. package/dist/web/assets/vala-zf12oZj6.js +1 -0
  305. package/dist/web/assets/vb-DOwzaHZ0.js +1 -0
  306. package/dist/web/assets/verilog-B2gwz-sJ.js +1 -0
  307. package/dist/web/assets/vesper-DdrHHSXu.js +1 -0
  308. package/dist/web/assets/vhdl-BroJfC0k.js +1 -0
  309. package/dist/web/assets/viml-DvXPmvsu.js +1 -0
  310. package/dist/web/assets/vitesse-black-fwtXNY1n.js +1 -0
  311. package/dist/web/assets/vitesse-dark-BZCL-v6S.js +1 -0
  312. package/dist/web/assets/vitesse-light-VbXTXTou.js +1 -0
  313. package/dist/web/assets/vue-DBEThQjT.js +1 -0
  314. package/dist/web/assets/vue-html-CsA9N5LL.js +1 -0
  315. package/dist/web/assets/vue-vine-NVEuBf8f.js +1 -0
  316. package/dist/web/assets/vyper-CgoNMtux.js +1 -0
  317. package/dist/web/assets/wasm-BnjxR4X6.js +1 -0
  318. package/dist/web/assets/wasm-ByWQv1Qj.js +1 -0
  319. package/dist/web/assets/wenyan-C8pVoKbM.js +1 -0
  320. package/dist/web/assets/wgsl-BsKzXJz4.js +1 -0
  321. package/dist/web/assets/wikitext-ClFFjSW2.js +1 -0
  322. package/dist/web/assets/wit-DdvCle-K.js +1 -0
  323. package/dist/web/assets/wolfram-DLL8P-h_.js +1 -0
  324. package/dist/web/assets/xml-C5qzJV_N.js +1 -0
  325. package/dist/web/assets/xsl-CZ2SCCYZ.js +1 -0
  326. package/dist/web/assets/yaml-RslzJFWV.js +1 -0
  327. package/dist/web/assets/zenscript-BnlCZFoB.js +1 -0
  328. package/dist/web/assets/zig-CMLA9XwU.js +1 -0
  329. package/dist/web/index.html +13 -0
  330. package/package.json +38 -0
package/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shane Keulen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # sidediff
2
+
3
+ GitHub-style diff review in the browser, with a live notes column. Built on [`@pierre/diffs`](https://diffs.com).
4
+
5
+ - GitHub-style review: a collapsible file tree, split or unified diffs, per-file Viewed checkboxes with a progress bar, and a third column of notes aligned to the lines they describe.
6
+ - Base16 Tomorrow Night and Tomorrow Night Eighties themes with muted change highlighting and a near-black code background.
7
+ - Expand unmodified lines between hunks, like GitHub; the server serves both sides of the range.
8
+ - Viewed marks are stored per repository in the browser and clear themselves when a file changes again, like GitHub.
9
+ - Watch mode by default: edits, commits and checkouts re-render the page without a refresh.
10
+ - Notes are plain files that any tool or agent can write while you read; they appear immediately.
11
+
12
+ ## Install
13
+
14
+ Not published to npm. Install from source:
15
+
16
+ ```sh
17
+ git clone https://github.com/SeeThruHead/sidediff.git
18
+ cd sidediff
19
+ pnpm install
20
+ pnpm run build
21
+ npm link
22
+ ```
23
+
24
+ ## Use
25
+
26
+ ```sh
27
+ sidediff # working tree against HEAD
28
+ sidediff origin/main...HEAD # a branch against its base
29
+ sidediff HEAD~1 --no-watch # a fixed range, no watching
30
+ ```
31
+
32
+ Options: `--port` (default 4977, falls back to the next free port), `--host` (default 127.0.0.1), `--no-open`, `--no-watch`. Any other arguments are passed to `git diff`.
33
+
34
+ Keys: `s` split or unified, `a` show or hide notes, `n` / `p` next or previous note.
35
+
36
+ ## Notes
37
+
38
+ ```sh
39
+ sidediff note add --file src/app.ts --new-line 42 --summary "Why this changed" --rationale "Longer explanation"
40
+ echo '{"comments":[{"filePath":"src/app.ts","newLine":42,"summary":"Why"}]}' | sidediff note apply
41
+ sidediff note list [--json] [--file src/app.ts]
42
+ sidediff note rm <id>
43
+ sidediff note clear [--file src/app.ts]
44
+ ```
45
+
46
+ `--new-line` targets the new side of the diff, `--old-line` the old side. The batch format matches Hunk's `session comment apply`, so existing agent tooling can target either.
47
+
48
+ Notes are stored one file per note under `<git dir>/sidediff/notes`, so concurrent writers never collide and nothing is committed to the repository.
49
+
50
+ ## Guided tours and conversation
51
+
52
+ A running server accepts commands that drive the open page, so a person or an agent can walk someone through a change:
53
+
54
+ ```sh
55
+ sidediff show src/app.ts:40 # scroll there
56
+ sidediff highlight src/app.ts:40-48 --text userId # select lines and mark text
57
+ sidediff explain src/app.ts:40-48 --title "Why" --body "..." [--speak] # zoomed popover over the diff
58
+ sidediff say "Next, the worker" # spoken through the browser, with a caption
59
+ sidediff clear
60
+ sidediff tour tour.json # steps array of the commands above; Next/Back in the page, or ] and [
61
+ sidediff where # which file the reader is on
62
+ sidediff listen [--after <id>] [--wait 600] # blocks until the reader says something
63
+ ```
64
+
65
+ Press Talk (or `m`) in the page to speak. Recognition uses the browser's built-in speech service (Chrome sends audio to Google; Safari uses Apple's), each finished sentence is posted to the server, and `sidediff listen` returns it. Replies are read aloud with the browser's speech synthesis.
66
+
67
+ ## Develop
68
+
69
+ ```sh
70
+ pnpm install
71
+ pnpm run build # client and CLI
72
+ pnpm run dev # rebuild the client on change
73
+ pnpm run typecheck
74
+ ```
75
+
76
+ MIT licensed.
package/dist/cli.js ADDED
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+ import { NodeRuntime, NodeServices, NodeSocket } from '@effect/platform-node';
3
+ import { Console, Context, Duration, Effect, FileSystem, Layer, Option, Schema, Stdio, Stream } from 'effect';
4
+ import { Argument, Command, Flag } from 'effect/unstable/cli';
5
+ import { ChildProcess, ChildProcessSpawner } from 'effect/unstable/process';
6
+ import { RpcClient, RpcSerialization } from 'effect/unstable/rpc';
7
+ import { Git } from './git.js';
8
+ import { Notes } from './notes.js';
9
+ import { NoteBatch, SidediffRpcs, Steps } from './protocol.js';
10
+ import { Repo } from './repo.js';
11
+ import { ServerInfo, freePort, serverLayer } from './server.js';
12
+ class NoServer extends Schema.TaggedError()('NoServer', { serverFile: Schema.String }) {
13
+ get message() {
14
+ return 'no sidediff server is running for this repository; start one with `sidediff <range>`';
15
+ }
16
+ }
17
+ class BadLocation extends Schema.TaggedError()('BadLocation', { value: Schema.String }) {
18
+ get message() {
19
+ return `cannot read location ${this.value}, expected <file>[:line[-end]]`;
20
+ }
21
+ }
22
+ class Client extends Context.Service()('sidediff/Client') {
23
+ static layer = Layer.effect(Client, RpcClient.make(SidediffRpcs)).pipe(Layer.provide(RpcClient.layerProtocolSocket()), Layer.provide(Layer.unwrap(Effect.gen(function* () {
24
+ const fs = yield* FileSystem.FileSystem;
25
+ const { serverFile } = yield* Repo;
26
+ const info = yield* fs.readFileString(serverFile).pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.fromJsonString(ServerInfo))), Effect.mapError(() => new NoServer({ serverFile })));
27
+ return NodeSocket.layerWebSocket(`ws://${info.host}:${info.port}/rpc`);
28
+ }))), Layer.provide(RpcSerialization.layerNdjson));
29
+ }
30
+ const print = (value) => Console.log(JSON.stringify(value));
31
+ const withClient = (effect) => Effect.provide(effect, Client.layer);
32
+ const send = (command) => Client.use((client) => client.Send({ command })).pipe(Effect.flatMap(print), withClient);
33
+ const location = Argument.String('location').pipe(Argument.withDescription('<file>[:line[-end]]'));
34
+ const parseLocation = (value) => {
35
+ const match = /^(.+?)(?::(\d+)(?:-(\d+))?)?$/.exec(value);
36
+ if (match === null || match[1] === undefined)
37
+ return Effect.fail(new BadLocation({ value }));
38
+ const start = match[2] === undefined ? undefined : Number(match[2]);
39
+ const end = match[3] === undefined ? start : Number(match[3]);
40
+ return Effect.succeed({
41
+ file: match[1],
42
+ ...(start === undefined ? {} : { start }),
43
+ ...(end === undefined ? {} : { end }),
44
+ });
45
+ };
46
+ const side = Flag.Literals('side', ['old', 'new']).pipe(Flag.withDefault('new'), Flag.map((value) => (value === 'old' ? 'deletions' : 'additions')));
47
+ const optionalText = (name) => Flag.String(name).pipe(Flag.optional, Flag.map(Option.getOrUndefined));
48
+ const defined = (fields) => Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined));
49
+ const speak = Flag.Boolean('speak').pipe(Flag.withDefault(false));
50
+ const show = Command.make('show', { location, side }, ({ location, side }) => Effect.flatMap(parseLocation(location), (target) => send({ type: 'show', ...target, side }))).pipe(Command.withDescription('scroll the open view to <file>[:line[-end]]'));
51
+ const highlight = Command.make('highlight', { location, side, text: optionalText('text') }, ({ location, side, text }) => Effect.flatMap(parseLocation(location), (target) => send({ type: 'highlight', ...target, side, ...defined({ text }) }))).pipe(Command.withDescription('select lines, optionally marking a substring'));
52
+ const explain = Command.make('explain', { location, side, title: optionalText('title'), body: optionalText('body'), speak }, ({ location, side, title, body, speak }) => Effect.flatMap(parseLocation(location), (target) => send({ type: 'explain', ...target, side, speak, ...defined({ title, body }) }))).pipe(Command.withDescription('open a zoomed popover with an explanation'));
53
+ const say = Command.make('say', { text: Argument.String('text').pipe(Argument.variadic({ min: 1 })), speak }, ({ text, speak }) => send({ type: 'say', text: text.join(' '), speak })).pipe(Command.withDescription('show a caption'));
54
+ const clear = Command.make('clear', {}, () => send({ type: 'clear' })).pipe(Command.withDescription('remove highlights and popovers'));
55
+ const tour = Command.make('tour', { steps: Argument.FileSchema('steps', Steps) }, ({ steps }) => send({ type: 'tour', steps })).pipe(Command.withDescription('load a guided tour from a JSON file of steps'));
56
+ const next = Command.make('next', {}, () => send({ type: 'next' })).pipe(Command.withDescription('next tour step'));
57
+ const back = Command.make('back', {}, () => send({ type: 'back' })).pipe(Command.withDescription('previous tour step'));
58
+ const goto = Command.make('goto', { step: Argument.Int('step') }, ({ step }) => send({ type: 'goto', index: step - 1 })).pipe(Command.withDescription('jump to tour step <n>'));
59
+ const where = Command.make('where', {}, () => Client.use((client) => client.View()).pipe(Effect.flatMap((view) => Console.log(JSON.stringify(view, null, 2))), withClient)).pipe(Command.withDescription('what the reader is looking at'));
60
+ const listen = Command.make('listen', {
61
+ after: Flag.Int('after').pipe(Flag.optional, Flag.map(Option.getOrUndefined)),
62
+ wait: Flag.Int('wait').pipe(Flag.withDefault(600)),
63
+ }, ({ after, wait }) => Effect.gen(function* () {
64
+ const client = yield* Client;
65
+ const heard = yield* client.Listen(after === undefined ? {} : { after }).pipe(Stream.take(1), Stream.runHead, Effect.timeoutOption(Duration.seconds(wait)), Effect.map(Option.flatten));
66
+ yield* Effect.forEach(Option.getOrElse(heard, () => []), (utterance) => Console.log(`${utterance.id}\t${utterance.text}`));
67
+ }).pipe(withClient)).pipe(Command.withDescription('wait for the next thing said into the mic'));
68
+ const noteAdd = Command.make('add', {
69
+ file: Flag.String('file'),
70
+ newLine: Flag.Int('new-line').pipe(Flag.optional, Flag.map(Option.getOrUndefined)),
71
+ oldLine: Flag.Int('old-line').pipe(Flag.optional, Flag.map(Option.getOrUndefined)),
72
+ summary: Flag.String('summary'),
73
+ rationale: optionalText('rationale'),
74
+ author: optionalText('author'),
75
+ }, ({ file, newLine, oldLine, summary, rationale, author }) => Effect.gen(function* () {
76
+ const notes = yield* Notes;
77
+ const [note] = yield* notes.add([
78
+ { filePath: file, summary, ...defined({ newLine, oldLine, rationale, author }) },
79
+ ]);
80
+ yield* Console.log(`added ${note?.id} on ${note?.filePath}:${note?.line}`);
81
+ })).pipe(Command.withDescription('add one note'));
82
+ const noteApply = Command.make('apply', {}, () => Effect.gen(function* () {
83
+ const stdio = yield* Stdio.Stdio;
84
+ const notes = yield* Notes;
85
+ const text = yield* Stream.mkString(Stream.decodeText(stdio.stdin));
86
+ const batch = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(NoteBatch))(text);
87
+ const added = yield* notes.add('comments' in batch ? batch.comments : batch);
88
+ yield* Console.log(`added ${added.length} notes`);
89
+ })).pipe(Command.withDescription('add a batch from stdin: {"comments":[{filePath,newLine|oldLine,summary,...}]}'));
90
+ const noteList = Command.make('list', { json: Flag.Boolean('json').pipe(Flag.withDefault(false)), file: optionalText('file') }, ({ json, file }) => Effect.gen(function* () {
91
+ const notes = yield* Notes;
92
+ const shown = (yield* notes.list).filter((note) => file === undefined || note.filePath === file);
93
+ if (json)
94
+ return yield* Console.log(JSON.stringify(shown, null, 2));
95
+ yield* Effect.forEach(shown, (note) => Console.log(`${note.id} ${note.filePath}:${note.line} ${note.summary}`));
96
+ })).pipe(Command.withDescription('list notes'));
97
+ const noteRemove = Command.make('rm', { id: Argument.String('id') }, ({ id }) => Effect.gen(function* () {
98
+ const notes = yield* Notes;
99
+ yield* notes.remove(id);
100
+ yield* Console.log(`removed ${id}`);
101
+ })).pipe(Command.withDescription('remove a note'));
102
+ const noteClear = Command.make('clear', { file: optionalText('file') }, ({ file }) => Effect.gen(function* () {
103
+ const notes = yield* Notes;
104
+ const count = yield* notes.clear(file);
105
+ yield* Console.log(`cleared ${count} notes`);
106
+ })).pipe(Command.withDescription('remove every note, or every note on one file'));
107
+ const note = Command.make('note').pipe(Command.withDescription('notes live in <git dir>/sidediff/notes and appear in the browser as they are written'), Command.withSubcommands([noteAdd, noteApply, noteList, noteRemove, noteClear]));
108
+ const openBrowser = (url) => ChildProcessSpawner.ChildProcessSpawner.use((spawner) => spawner.exitCode(process.platform === 'darwin'
109
+ ? ChildProcess.make('open', [url])
110
+ : process.platform === 'win32'
111
+ ? ChildProcess.make('cmd', ['/c', 'start', '', url])
112
+ : ChildProcess.make('xdg-open', [url]))).pipe(Effect.ignore);
113
+ const sidediff = Command.make('sidediff', {
114
+ range: Argument.String('range').pipe(Argument.variadic()),
115
+ port: Flag.Int('port').pipe(Flag.withDefault(4977)),
116
+ host: Flag.String('host').pipe(Flag.withDefault('127.0.0.1')),
117
+ open: Flag.Boolean('open').pipe(Flag.withDefault(true)),
118
+ watch: Flag.Boolean('watch').pipe(Flag.withDefault(true)),
119
+ }, ({ range, port, host, open, watch }) => Effect.gen(function* () {
120
+ const chosen = yield* freePort(port, host);
121
+ const url = `http://${host}:${chosen}`;
122
+ yield* Layer.launch(serverLayer({ range, port: chosen, host, watch }).pipe(Layer.merge(Layer.effectDiscard(Effect.andThen(Console.log(`sidediff: ${url} (${watch ? 'watching for changes' : 'watch off'})`), open ? openBrowser(url) : Effect.void)))));
123
+ })).pipe(Command.withDescription('GitHub-style diff review in the browser, with a live notes column'), Command.withSubcommands([note, show, highlight, explain, say, clear, tour, next, back, goto, where, listen]));
124
+ const AppLayer = Notes.layer.pipe(Layer.provideMerge(Repo.layer), Layer.provideMerge(Git.layer), Layer.provideMerge(NodeServices.layer));
125
+ Command.run(sidediff, { version: '0.2.0' }).pipe(Effect.provide(AppLayer), NodeRuntime.runMain);
package/dist/git.js ADDED
@@ -0,0 +1,42 @@
1
+ import { Context, Effect, Layer, Schema, Stream } from 'effect';
2
+ import { ChildProcess, ChildProcessSpawner } from 'effect/unstable/process';
3
+ export class GitFailed extends Schema.TaggedError()('GitFailed', {
4
+ args: Schema.Array(Schema.String),
5
+ exitCode: Schema.Number,
6
+ }) {
7
+ }
8
+ export class Git extends Context.Service()('sidediff/Git') {
9
+ static layer = Layer.effect(Git, Effect.gen(function* () {
10
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
11
+ const run = (cwd, args) => Effect.scoped(Effect.gen(function* () {
12
+ const handle = yield* spawner.spawn(ChildProcess.make('git', [...args], { cwd }));
13
+ const [stdout, exitCode] = yield* Effect.all([Stream.mkString(Stream.decodeText(handle.stdout)), handle.exitCode], { concurrency: 2 });
14
+ return exitCode === 0 ? stdout : yield* new GitFailed({ args, exitCode });
15
+ })).pipe(Effect.catchTag('PlatformError', () => Effect.fail(new GitFailed({ args, exitCode: -1 }))));
16
+ const trimmed = (cwd, args) => Effect.map(run(cwd, args), (out) => out.trim());
17
+ const sides = (cwd, range) => {
18
+ const [first, second] = range.filter((arg) => !arg.startsWith('-'));
19
+ if (first === undefined)
20
+ return Effect.succeed({ old: '', new: null });
21
+ if (first.includes('...')) {
22
+ const [base, head] = first.split('...');
23
+ const tip = head || 'HEAD';
24
+ return Effect.map(trimmed(cwd, ['merge-base', base || 'HEAD', tip]), (old) => ({ old, new: tip }));
25
+ }
26
+ if (first.includes('..')) {
27
+ const [base, head] = first.split('..');
28
+ return Effect.succeed({ old: base || 'HEAD', new: head || 'HEAD' });
29
+ }
30
+ return Effect.succeed({ old: first, new: second ?? null });
31
+ };
32
+ return {
33
+ run,
34
+ repoRoot: (cwd) => trimmed(cwd, ['rev-parse', '--show-toplevel']),
35
+ commonDir: (cwd) => trimmed(cwd, ['rev-parse', '--path-format=absolute', '--git-common-dir']),
36
+ branch: (cwd) => trimmed(cwd, ['rev-parse', '--abbrev-ref', 'HEAD']).pipe(Effect.orElseSucceed(() => 'HEAD')),
37
+ diff: (cwd, range) => run(cwd, ['-c', 'core.quotepath=false', 'diff', '--no-color', '--no-ext-diff', '--find-renames', ...range]),
38
+ sides,
39
+ show: (cwd, rev, path) => run(cwd, ['show', `${rev}:${path}`]),
40
+ };
41
+ }));
42
+ }
package/dist/notes.js ADDED
@@ -0,0 +1,66 @@
1
+ import { Context, DateTime, Effect, FileSystem, Layer, Option, Path, Random, Schema } from 'effect';
2
+ import { Note } from './protocol.js';
3
+ import { Repo } from './repo.js';
4
+ export class NoteWithoutLine extends Schema.TaggedError()('NoteWithoutLine', {
5
+ filePath: Schema.String,
6
+ }) {
7
+ get message() {
8
+ return `A note on ${this.filePath} needs a positive newLine or oldLine`;
9
+ }
10
+ }
11
+ export class NotesUnwritable extends Schema.TaggedError()('NotesUnwritable', {
12
+ directory: Schema.String,
13
+ }) {
14
+ get message() {
15
+ return `cannot write notes in ${this.directory}`;
16
+ }
17
+ }
18
+ const decodeNote = Schema.decodeUnknownOption(Schema.fromJsonString(Note));
19
+ const hex = (value) => value.toString(16).padStart(8, '0');
20
+ export class Notes extends Context.Service()('sidediff/Notes') {
21
+ static layer = Layer.effect(Notes, Effect.gen(function* () {
22
+ const fs = yield* FileSystem.FileSystem;
23
+ const path = yield* Path.Path;
24
+ const { notes: directory } = yield* Repo;
25
+ const unwritable = () => new NotesUnwritable({ directory });
26
+ const fileOf = (id) => path.join(directory, `${id}.json`);
27
+ const newId = Effect.map(Effect.all([Random.nextIntBetween(0, 0xffffffff), Random.nextIntBetween(0, 0xffffffff)]), ([a, b]) => `${hex(a)}${hex(b)}`);
28
+ const toNote = (input) => Effect.gen(function* () {
29
+ const line = input.newLine ?? input.oldLine;
30
+ if (line === undefined)
31
+ return yield* new NoteWithoutLine({ filePath: input.filePath });
32
+ const id = yield* newId;
33
+ const now = yield* DateTime.now;
34
+ return {
35
+ id,
36
+ filePath: input.filePath,
37
+ side: input.newLine === undefined ? 'deletions' : 'additions',
38
+ line,
39
+ summary: input.summary,
40
+ ...(input.rationale === undefined ? {} : { rationale: input.rationale }),
41
+ author: input.author ?? 'agent',
42
+ createdAt: DateTime.formatIso(now),
43
+ };
44
+ });
45
+ const list = Effect.gen(function* () {
46
+ const names = yield* fs.readDirectory(directory).pipe(Effect.orElseSucceed(() => []));
47
+ const read = yield* Effect.forEach(names.filter((name) => name.endsWith('.json')), (name) => fs.readFileString(path.join(directory, name)).pipe(Effect.map(decodeNote), Effect.orElseSucceed(() => Option.none())), { concurrency: 16 });
48
+ return read
49
+ .flatMap((note) => (Option.isSome(note) ? [note.value] : []))
50
+ .toSorted((a, b) => a.filePath.localeCompare(b.filePath) || a.line - b.line);
51
+ });
52
+ const remove = (id) => fs.remove(fileOf(id), { force: true }).pipe(Effect.mapError(unwritable));
53
+ const add = (inputs) => Effect.gen(function* () {
54
+ const created = yield* Effect.forEach(inputs, toNote);
55
+ yield* fs.makeDirectory(directory, { recursive: true }).pipe(Effect.mapError(unwritable));
56
+ yield* Effect.forEach(created, (note) => fs.writeFileString(fileOf(note.id), JSON.stringify(note)).pipe(Effect.mapError(unwritable)), { concurrency: 16, discard: true });
57
+ return created;
58
+ });
59
+ const clear = (filePath) => Effect.gen(function* () {
60
+ const doomed = (yield* list).filter((note) => filePath === undefined || note.filePath === filePath);
61
+ yield* Effect.forEach(doomed, (note) => remove(note.id), { concurrency: 16, discard: true });
62
+ return doomed.length;
63
+ });
64
+ return { add, list, remove, clear };
65
+ }));
66
+ }
@@ -0,0 +1,91 @@
1
+ import { Schema } from 'effect';
2
+ import { Rpc, RpcGroup } from 'effect/unstable/rpc';
3
+ export const Side = Schema.Literals(['additions', 'deletions']);
4
+ const Line = Schema.Int.check(Schema.isGreaterThan(0));
5
+ export const Note = Schema.Struct({
6
+ id: Schema.String,
7
+ filePath: Schema.String,
8
+ side: Side,
9
+ line: Line,
10
+ summary: Schema.String,
11
+ rationale: Schema.optionalKey(Schema.String),
12
+ author: Schema.String,
13
+ createdAt: Schema.String,
14
+ });
15
+ export const NoteInput = Schema.Struct({
16
+ filePath: Schema.NonEmptyString,
17
+ newLine: Schema.optionalKey(Line),
18
+ oldLine: Schema.optionalKey(Line),
19
+ summary: Schema.NonEmptyString,
20
+ rationale: Schema.optionalKey(Schema.String),
21
+ author: Schema.optionalKey(Schema.String),
22
+ });
23
+ export const NoteBatch = Schema.Union([
24
+ Schema.Array(NoteInput),
25
+ Schema.Struct({ comments: Schema.Array(NoteInput) }),
26
+ ]);
27
+ export const Snapshot = Schema.Struct({
28
+ repo: Schema.String,
29
+ branch: Schema.String,
30
+ range: Schema.Array(Schema.String),
31
+ patch: Schema.String,
32
+ notes: Schema.Array(Note),
33
+ version: Schema.String,
34
+ watching: Schema.Boolean,
35
+ updatedAt: Schema.String,
36
+ });
37
+ const targetFields = {
38
+ file: Schema.String,
39
+ start: Schema.optionalKey(Line),
40
+ end: Schema.optionalKey(Line),
41
+ side: Side,
42
+ text: Schema.optionalKey(Schema.String),
43
+ };
44
+ export const Target = Schema.Struct(targetFields);
45
+ const explanationFields = {
46
+ ...targetFields,
47
+ title: Schema.optionalKey(Schema.String),
48
+ body: Schema.optionalKey(Schema.String),
49
+ };
50
+ export const Explanation = Schema.Struct(explanationFields);
51
+ export const Step = Schema.Union([
52
+ Schema.Struct({ type: Schema.Literal('show'), ...targetFields }),
53
+ Schema.Struct({ type: Schema.Literal('highlight'), ...targetFields }),
54
+ Schema.Struct({ type: Schema.Literal('explain'), speak: Schema.optionalKey(Schema.Boolean), ...explanationFields }),
55
+ Schema.Struct({ type: Schema.Literal('say'), text: Schema.String, speak: Schema.optionalKey(Schema.Boolean) }),
56
+ Schema.Struct({ type: Schema.Literal('clear') }),
57
+ ]);
58
+ export const Steps = Schema.Array(Step);
59
+ export const Command = Schema.Union([
60
+ Step,
61
+ Schema.Struct({ type: Schema.Literal('tour'), steps: Steps }),
62
+ Schema.Struct({ type: Schema.Literal('next') }),
63
+ Schema.Struct({ type: Schema.Literal('back') }),
64
+ Schema.Struct({ type: Schema.Literal('goto'), index: Schema.Int }),
65
+ ]);
66
+ export const View = Schema.Struct({
67
+ file: Schema.NullOr(Schema.String),
68
+ activeNote: Schema.NullOr(Schema.String),
69
+ tourStep: Schema.NullOr(Schema.Int),
70
+ explaining: Schema.NullOr(Schema.String),
71
+ });
72
+ export const Utterance = Schema.Struct({
73
+ id: Schema.Int,
74
+ text: Schema.String,
75
+ at: Schema.String,
76
+ handled: Schema.Boolean,
77
+ });
78
+ export class FileMissing extends Schema.TaggedError()('FileMissing', {
79
+ side: Schema.Literals(['old', 'new']),
80
+ path: Schema.String,
81
+ }) {
82
+ }
83
+ export const SidediffRpcs = RpcGroup.make(Rpc.make('Snapshots', { success: Snapshot, stream: true }), Rpc.make('File', {
84
+ payload: { side: Schema.Literals(['old', 'new']), path: Schema.NonEmptyString },
85
+ success: Schema.String,
86
+ error: FileMissing,
87
+ }), Rpc.make('Send', { payload: { command: Command }, success: Schema.Struct({ delivered: Schema.Int }) }), Rpc.make('Commands', { success: Command, stream: true }), Rpc.make('ReportView', { payload: { view: View } }), Rpc.make('View', { success: Schema.NullOr(View) }), Rpc.make('Utter', { payload: { text: Schema.NonEmptyString, handled: Schema.Boolean }, success: Utterance }), Rpc.make('Listen', {
88
+ payload: { after: Schema.optionalKey(Schema.Int) },
89
+ success: Schema.Array(Utterance),
90
+ stream: true,
91
+ }));
package/dist/repo.js ADDED
@@ -0,0 +1,12 @@
1
+ import { Context, Effect, Layer, Path } from 'effect';
2
+ import { Git } from './git.js';
3
+ export class Repo extends Context.Service()('sidediff/Repo') {
4
+ static layer = Layer.effect(Repo, Effect.gen(function* () {
5
+ const git = yield* Git;
6
+ const path = yield* Path.Path;
7
+ const root = yield* git.repoRoot(process.cwd());
8
+ const common = yield* git.commonDir(root);
9
+ const state = path.join(common, 'sidediff');
10
+ return { root, notes: path.join(state, 'notes'), state, serverFile: path.join(state, 'server.json') };
11
+ }));
12
+ }
package/dist/server.js ADDED
@@ -0,0 +1,139 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { createServer } from 'node:http';
3
+ import { createServer as createProbe } from 'node:net';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { NodeHttpServer } from '@effect/platform-node';
6
+ import { Context, DateTime, Duration, Effect, FileSystem, Layer, Path, PubSub, Ref, Schema, Stream, SubscriptionRef, } from 'effect';
7
+ import { HttpRouter, HttpServerResponse } from 'effect/unstable/http';
8
+ import { RpcSerialization, RpcServer } from 'effect/unstable/rpc';
9
+ import { Git } from './git.js';
10
+ import { Notes } from './notes.js';
11
+ import { FileMissing, SidediffRpcs } from './protocol.js';
12
+ import { Repo } from './repo.js';
13
+ export class ServeOptions extends Context.Service()('sidediff/ServeOptions') {
14
+ }
15
+ export const ServerInfo = Schema.Struct({ port: Schema.Int, host: Schema.String, pid: Schema.Int });
16
+ const ignoredSegments = new Set(['node_modules', 'dist', '.next', '.turbo', '.cache', 'coverage']);
17
+ const isRelevant = (file) => {
18
+ const parts = file.split('/');
19
+ if (parts.some((part) => ignoredSegments.has(part)))
20
+ return false;
21
+ if (parts[0] !== '.git')
22
+ return true;
23
+ return file.includes('HEAD') || file.includes('refs') || file.endsWith('index');
24
+ };
25
+ const versionOf = (patch, notes) => createHash('sha1').update(patch).update(JSON.stringify(notes)).digest('hex').slice(0, 12);
26
+ export class Review extends Context.Service()('sidediff/Review') {
27
+ static layer = Layer.effect(Review, Effect.gen(function* () {
28
+ const git = yield* Git;
29
+ const notes = yield* Notes;
30
+ const repo = yield* Repo;
31
+ const options = yield* ServeOptions;
32
+ const fs = yield* FileSystem.FileSystem;
33
+ const path = yield* Path.Path;
34
+ const build = Effect.gen(function* () {
35
+ const [patch, current, branch, now] = yield* Effect.all([git.diff(repo.root, options.range), notes.list, git.branch(repo.root), DateTime.now], { concurrency: 'unbounded' });
36
+ return {
37
+ repo: repo.root,
38
+ branch,
39
+ range: options.range,
40
+ patch,
41
+ notes: current,
42
+ version: versionOf(patch, current),
43
+ watching: options.watch,
44
+ updatedAt: DateTime.formatIso(now),
45
+ };
46
+ });
47
+ const snapshot = yield* SubscriptionRef.make(yield* build);
48
+ const refresh = build.pipe(Effect.flatMap((next) => SubscriptionRef.update(snapshot, (current) => (current.version === next.version ? current : next))), Effect.catch((error) => Effect.logWarning('[refresh] diff failed', error)));
49
+ yield* fs.makeDirectory(repo.notes, { recursive: true });
50
+ const changes = Stream.merge(fs.watch(repo.notes), options.watch
51
+ ? fs.watch(repo.root, { recursive: true }).pipe(Stream.filter((event) => isRelevant(event.path)))
52
+ : Stream.empty);
53
+ yield* changes.pipe(Stream.debounce(Duration.millis(250)), Stream.runForEach(() => refresh), Effect.catch((error) => Effect.logWarning('[watch] stopped', error)), Effect.forkScoped);
54
+ const commands = yield* PubSub.unbounded();
55
+ const watchers = yield* Ref.make(0);
56
+ const views = yield* Ref.make(null);
57
+ const heard = yield* Ref.make([]);
58
+ const spoken = yield* PubSub.unbounded();
59
+ const file = (side, relative) => Effect.gen(function* () {
60
+ const missing = new FileMissing({ side, path: relative });
61
+ const inside = path.join(repo.root, relative);
62
+ if (path.relative(repo.root, inside).startsWith('..'))
63
+ return yield* missing;
64
+ const sides = yield* git.sides(repo.root, options.range).pipe(Effect.mapError(() => missing));
65
+ return side === 'new' && sides.new === null
66
+ ? yield* fs.readFileString(inside).pipe(Effect.mapError(() => missing))
67
+ : yield* git
68
+ .show(repo.root, side === 'new' ? (sides.new ?? 'HEAD') : sides.old, relative)
69
+ .pipe(Effect.mapError(() => missing));
70
+ });
71
+ const utter = (text, handled) => Effect.gen(function* () {
72
+ const now = yield* DateTime.now;
73
+ const utterance = yield* Ref.modify(heard, (all) => {
74
+ const next = { id: all.length + 1, text: text.trim(), at: DateTime.formatIso(now), handled };
75
+ return [next, [...all, next]];
76
+ });
77
+ if (!handled)
78
+ yield* PubSub.publish(spoken, utterance);
79
+ return utterance;
80
+ });
81
+ const listen = (after) => Stream.unwrap(Effect.map(Ref.get(heard), (all) => {
82
+ const since = after ?? all.length;
83
+ const pending = all.filter((utterance) => utterance.id > since && !utterance.handled);
84
+ const live = Stream.fromPubSub(spoken).pipe(Stream.filter((utterance) => utterance.id > since), Stream.map((utterance) => [utterance]));
85
+ return pending.length > 0 ? Stream.concat(Stream.make(pending), live) : live;
86
+ }));
87
+ return {
88
+ snapshots: SubscriptionRef.changes(snapshot),
89
+ file,
90
+ send: (command) => Effect.andThen(PubSub.publish(commands, command), Ref.get(watchers)),
91
+ commands: Stream.unwrap(Effect.as(Ref.update(watchers, (count) => count + 1), Stream.fromPubSub(commands).pipe(Stream.ensuring(Ref.update(watchers, (count) => count - 1))))),
92
+ reportView: (view) => Ref.set(views, view),
93
+ view: Ref.get(views),
94
+ utter,
95
+ listen,
96
+ };
97
+ }));
98
+ }
99
+ const RpcHandlers = SidediffRpcs.toLayer(Effect.gen(function* () {
100
+ const review = yield* Review;
101
+ return SidediffRpcs.of({
102
+ Snapshots: () => review.snapshots,
103
+ File: ({ side, path }) => review.file(side, path),
104
+ Send: ({ command }) => Effect.map(review.send(command), (delivered) => ({ delivered })),
105
+ Commands: () => review.commands,
106
+ ReportView: ({ view }) => review.reportView(view),
107
+ View: () => review.view,
108
+ Utter: ({ text, handled }) => review.utter(text, handled),
109
+ Listen: ({ after }) => review.listen(after),
110
+ });
111
+ }));
112
+ const webRoot = fileURLToPath(new URL('./web', import.meta.url));
113
+ const StaticFiles = HttpRouter.add('GET', '/*', (request) => Effect.gen(function* () {
114
+ const path = yield* Path.Path;
115
+ const pathname = new URL(request.url, 'http://localhost').pathname;
116
+ const asset = path.join(webRoot, pathname === '/' ? 'index.html' : pathname.slice(1));
117
+ if (path.relative(webRoot, asset).startsWith('..'))
118
+ return HttpServerResponse.empty({ status: 404 });
119
+ return yield* HttpServerResponse.file(asset, { headers: { 'cache-control': 'no-store' } }).pipe(Effect.orElseSucceed(() => HttpServerResponse.empty({ status: 404 })));
120
+ }));
121
+ const RpcRoute = RpcServer.layerHttp({ group: SidediffRpcs, path: '/rpc', protocol: 'websocket' }).pipe(Layer.provide(RpcHandlers), Layer.provide(RpcSerialization.layerNdjson));
122
+ const portFree = (port, host) => Effect.callback((resume) => {
123
+ const probe = createProbe();
124
+ probe.once('error', () => resume(Effect.succeed(false)));
125
+ probe.listen(port, host, () => probe.close(() => resume(Effect.succeed(true))));
126
+ });
127
+ export const freePort = (start, host) => Effect.flatMap(portFree(start, host), (free) => (free ? Effect.succeed(start) : freePort(start + 1, host)));
128
+ const Announce = Layer.effectDiscard(Effect.gen(function* () {
129
+ const fs = yield* FileSystem.FileSystem;
130
+ const repo = yield* Repo;
131
+ const options = yield* ServeOptions;
132
+ const info = { port: options.port, host: options.host, pid: process.pid };
133
+ yield* Effect.acquireRelease(fs.writeFileString(repo.serverFile, JSON.stringify(Schema.encodeSync(ServerInfo)(info))), () => fs.readFileString(repo.serverFile).pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.fromJsonString(ServerInfo))), Effect.flatMap((current) => current.pid === info.pid ? fs.remove(repo.serverFile, { force: true }) : Effect.void), Effect.ignore));
134
+ }));
135
+ export const serverLayer = (options) => HttpRouter.serve(Layer.mergeAll(RpcRoute, StaticFiles), { disableListenLog: true, disableLogger: true }).pipe(Layer.provide(Review.layer), Layer.merge(Announce), Layer.provide(NodeHttpServer.layer(createServer, {
136
+ port: options.port,
137
+ host: options.host,
138
+ gracefulShutdownTimeout: Duration.millis(300),
139
+ })), Layer.provide(Layer.succeed(ServeOptions, options)));
@@ -0,0 +1 @@
1
+ var e=[Object.freeze(JSON.parse(`{"displayName":"ABAP","fileTypes":["abap","ABAP"],"foldingStartMarker":"/\\\\*\\\\*|\\\\{\\\\s*$","foldingStopMarker":"\\\\*\\\\*/|^\\\\s*}","name":"abap","patterns":[{"captures":{"1":{"name":"punctuation.definition.comment.abap"}},"match":"^\\\\*.*\\\\n?","name":"comment.line.full.abap"},{"captures":{"1":{"name":"punctuation.definition.comment.abap"}},"match":"\\".*\\\\n?","name":"comment.line.partial.abap"},{"match":"(?<!\\\\S)##.*?(?=([,.:\\\\s]))","name":"comment.line.pragma.abap"},{"match":"(?i)(?<=[-~\\\\s])(?<=[-=]>)([/_a-z][/-9_a-z]*)(?=\\\\s+(?:|[-*+/]|&&?)=\\\\s+)","name":"variable.other.abap"},{"match":"\\\\b[0-9]+(\\\\b|[,.])","name":"constant.numeric.abap"},{"match":"(?i)(^|\\\\s+)((P(?:UBLIC|RIVATE|ROTECTED))\\\\sSECTION)(?=\\\\s+|[.:])","name":"storage.modifier.class.abap"},{"begin":"(?<!\\\\\\\\)(\\\\|)(.*?)","beginCaptures":{"1":{"name":"constant.character.escape.abap"}},"end":"(?<!\\\\\\\\)(\\\\||(\\\\\\\\\\\\\\\\\\\\|))","endCaptures":{"1":{"name":"constant.character.escape.abap"}},"name":"string.interpolated.abap","patterns":[{"match":"(\\\\{ )|( })","name":"constant.character.escape"},{"match":"\\\\\\\\\\\\|","name":"constant.character.escape.abap"},{"match":"(?i)(?<=\\\\s)(align|alpha|case|country|currency|date|decimals|exponent|number|pad|sign|style|time|timestamp|timezone|width|xsd|zero)(?=\\\\s=)","name":"entity.name.property.stringtemplate.abap"},{"match":"(?i)(?<==\\\\s)(center|engineering|environment|in|iso|left|leftplus|leftspace|lower|no|out|raw|right|rightplus|rightspace|scale_preserving|scale_preserving_scientific|scientific|scientific_with_leading_zero|sign_as_postfix|simple|space|upper|user|yes)(?=\\\\s)","name":"entity.value.property.stringtemplate.abap"}]},{"begin":"'","end":"'","name":"string.quoted.single.abap","patterns":[{"match":"''","name":"constant.character.escape.abap"}]},{"begin":"\`","end":"\`","name":"string.quoted.single.abap","patterns":[{"match":"\`\`","name":"constant.character.escape.abap"}]},{"begin":"(?i)^\\\\s*(class)\\\\s([/_a-z][/-9_a-z]*)","beginCaptures":{"1":{"name":"storage.type.block.abap"},"2":{"name":"entity.name.type.block.abap"}},"end":"\\\\s*\\\\.\\\\s*\\\\n?","name":"meta.block.begin.implementation.abap","patterns":[{"match":"(?i)(^|\\\\s+)(definition|implementation|public|inheriting\\\\s+from|final|deferred|abstract|shared\\\\s+memory\\\\s+enabled|(global|local)*\\\\s*friends|(create\\\\s+(p(?:ublic|rotected|rivate)))|for\\\\s+behavior\\\\s+of|for\\\\s+testing|risk\\\\s+level\\\\s+(critical|dangerous|harmless))|duration\\\\s(short|medium|long)(?=\\\\s+|\\\\.)","name":"storage.modifier.class.abap"},{"begin":"(?=[A-Z_a-z][0-9A-Z_a-z]*)","contentName":"entity.name.type.block.abap","end":"(?![0-9A-Z_a-z])","patterns":[{"include":"#generic_names"}]}]},{"begin":"(?i)^\\\\s*(method)\\\\s(?:([/_a-z][/-9_a-z]*)~)?([/_a-z][/-9_a-z]*)","beginCaptures":{"1":{"name":"storage.type.block.abap"},"2":{"name":"entity.name.type.abap"},"3":{"name":"entity.name.function.abap"}},"end":"\\\\s*\\\\.\\\\s*\\\\n?","patterns":[{"match":"(?i)(?<=^|\\\\s)(BY(?:\\\\s+DATABASE(\\\\s+PROCEDURE|\\\\s+FUNCTION|\\\\s+GRAPH\\\\s+WORKSPACE)|\\\\s+KERNEL\\\\s+MODULE))(?=\\\\s+|\\\\.)","name":"storage.modifier.method.abap"},{"match":"(?i)(?<=^|\\\\s)(FOR\\\\s+(HDB|LLANG))(?=\\\\s+|\\\\.)","name":"storage.modifier.method.abap"},{"match":"(?i)(?<=\\\\s)(OPTIONS\\\\s+(READ-ONLY|DETERMINISTIC|SUPPRESS\\\\s+SYNTAX\\\\s+ERRORS))(?=\\\\s+|\\\\.)","name":"storage.modifier.method.abap"},{"match":"(?i)(?<=^|\\\\s)(LANGUAGE\\\\s+(SQLSCRIPT|SQL|GRAPH))(?=\\\\s+|\\\\.)","name":"storage.modifier.method.abap"},{"captures":{"1":{"name":"storage.modifier.method.abap"}},"match":"(?i)(?<=\\\\s)(USING)\\\\s+([/_a-z][/-9=>_a-z]*)+(?=\\\\s+|\\\\.)"},{"begin":"(?=[A-Z_a-z][0-9A-Z_a-z]*)","end":"(?![0-9A-Z_a-z])","patterns":[{"include":"#generic_names"}]}]},{"begin":"(?i)^\\\\s*(INTERFACE)\\\\s([/_a-z][/-9_a-z]*)","beginCaptures":{"1":{"name":"storage.type.block.abap"},"2":{"name":"entity.name.type.abap"}},"end":"\\\\s*\\\\.\\\\s*\\\\n?","patterns":[{"match":"(?i)(?<=^|\\\\s)(DEFERRED|PUBLIC)(?=\\\\s+|\\\\.)","name":"storage.modifier.method.abap"}]},{"begin":"(?i)^\\\\s*(FORM)\\\\s([/_a-z][-/-9?_a-z]*)","beginCaptures":{"1":{"name":"storage.type.block.abap"},"2":{"name":"entity.name.type.abap"}},"end":"\\\\s*\\\\.\\\\s*\\\\n?","patterns":[{"match":"(?i)(?<=^|\\\\s)(USING|TABLES|CHANGING|RAISING|IMPLEMENTATION|DEFINITION)(?=\\\\s+|\\\\.)","name":"storage.modifier.form.abap"},{"include":"#abaptypes"},{"include":"#keywords_followed_by_braces"}]},{"match":"(?i)(end(?:class|method|form|interface))","name":"storage.type.block.end.abap"},{"match":"(?i)(<[A-Z_a-z][0-9A-Z_a-z]*>)","name":"variable.other.field.symbol.abap"},{"include":"#keywords"},{"include":"#abap_constants"},{"include":"#reserved_names"},{"include":"#operators"},{"include":"#builtin_functions"},{"include":"#abaptypes"},{"include":"#system_fields"},{"include":"#sql_functions"},{"include":"#sql_types"}],"repository":{"abap_constants":{"match":"(?i)(?<=\\\\s)(initial|null|@?space|@?abap_true|@?abap_false|@?abap_undefined|table_line|%_final|%_hints|%_predefined|col_background|col_group|col_heading|col_key|col_negative|col_normal|col_positive|col_total|adabas|as400|db2|db6|hdb|oracle|sybase|mssqlnt|pos_low|pos_high)(?=[,.\\\\s])","name":"constant.language.abap"},"abaptypes":{"patterns":[{"match":"(?i)\\\\s(abap_bool|string|xstring|any|clike|csequence|numeric|xsequence|decfloat|decfloat16|decfloat34|utclong|simple|int8|[cdfinptx])(?=[,.\\\\s])","name":"support.type.abap"},{"match":"(?i)\\\\s(TYPE|REF|TO|LIKE|LINE|OF|STRUCTURE|STANDARD|SORTED|HASHED|INDEX|TABLE|WITH|UNIQUE|NON-UNIQUE|SECONDARY|DEFAULT|KEY)(?=[,.\\\\s])","name":"keyword.control.simple.abap"}]},"arithmetic_operator":{"match":"(?i)(?<=\\\\s)([-*+]|\\\\*\\\\*|[%/]|DIV|MOD|BIT-AND|BIT-OR|BIT-XOR|BIT-NOT)(?=\\\\s)","name":"keyword.control.simple.abap"},"builtin_functions":{"match":"(?i)(?<=\\\\s)(abs|sign|ceil|floor|trunc|frac|acos|asin|atan|cos|sin|tan|cosh|sinh|tanh|exp|log|log10|sqrt|strlen|xstrlen|charlen|lines|numofchar|dbmaxlen|round|rescale|nmax|nmin|cmax|cmin|boolc|boolx|xsdbool|contains|contains_any_of|contains_any_not_of|matches|line_exists|ipow|char_off|count|count_any_of|count_any_not_of|distance|condense|concat_lines_of|escape|find|find_end|find_any_of|find_any_not_of|insert|match|repeat|replace|reverse|segment|shift_left|shift_right|substring|substring_after|substring_from|substring_before|substring_to|to_upper|to_lower|to_mixed|from_mixed|translate|bit-set|line_index)(?=\\\\()","name":"entity.name.function.builtin.abap"},"comparison_operator":{"match":"(?i)(?<=\\\\s)([<>]|<=|>=|=|<>|eq|ne|lt|le|gt|ge|cs|cp|co|cn|ca|na|ns|np|byte-co|byte-cn|byte-ca|byte-na|byte-cs|byte-ns|[moz])(?=\\\\s)","name":"keyword.control.simple.abap"},"control_keywords":{"match":"(?i)(^|\\\\s)(at|case|catch|continue|do|elseif|else|endat|endcase|endcatch|enddo|endif|endloop|endon|endtry|endwhile|if|loop|on|raise|try|while)(?=[.:\\\\s])","name":"keyword.control.flow.abap"},"generic_names":{"match":"[A-Z_a-z][0-9A-Z_a-z]*"},"keywords":{"patterns":[{"include":"#main_keywords"},{"include":"#text_symbols"},{"include":"#control_keywords"},{"include":"#keywords_followed_by_braces"}]},"keywords_followed_by_braces":{"captures":{"1":{"name":"keyword.control.simple.abap"},"2":{"name":"variable.other.abap"}},"match":"(?i)\\\\b(data|value|field-symbol|final|reference|resumable)\\\\((<?[/_a-z][/-9_a-z]*>?)\\\\)"},"logical_operator":{"match":"(?i)(?<=\\\\s)(not|or|and)(?=\\\\s)","name":"keyword.control.simple.abap"},"main_keywords":{"match":"(?i)(?<=^|\\\\s)(abap-source|abstract|accept|accepting|access|according|action|activation|actual|add|add-corresponding|adjacent|after|alias|aliases|all|allocate|amdp|analysis|analyzer|append|appending|application|archive|area|arithmetic|as|ascending|assert|assign|assigned|assigning|association|asynchronous|at|attributes|authority|authority-check|authorization|auto|back|background|backward|badi|base|before|begin|behavior|between|binary|bit|blanks??|blocks??|bound|boundaries|bounds|boxed|break|break-point|buffer|by|bypassing|byte|byte-order|call|calling|cast|casting|cds|centered|change|changing|channels|char-to-hex|character|check|checkbox|cid|circular|class|class-data|class-events|class-methods??|class-pool|cleanup|clear|clients??|clock|clone|close|cnt|code|collect|color|column|comments??|commit|common|communication|comparing|components??|compression|compute|concatenate|cond|condense|condition|connection|constants??|contexts??|controls??|conv|conversion|convert|copy|corresponding|count|country|cover|create|currency|current|cursor|customer-function|data|database|datainfo|dataset|date|daylight|ddl|deallocate|decimals|declarations|deep|default|deferred|define|delete|deleting|demand|descending|describe|destination|detail|determine|dialog|did|directory|discarding|display|display-mode|distance|distinct|divide|divide-corresponding|dummy|duplicates??|duration|during|dynpro|edit|editor-call|empty|enabled|enabling|encoding|end|end-enhancement-section|end-of-definition|end-of-page|end-of-selection|end-test-injection|end-test-seam|endenhancement|endexec|endfunction|endian|ending|endmodule|endprovide|endselect|endwith|enhancement|enhancement-point|enhancement-section|enhancements|entities|entity|entries|entry|enum|equiv|errors|escape|escaping|events??|exact|except|exception|exception-table|exceptions|excluding|exec|execute|exists|exit|exit-command|expanding|explicit|exponent|export|exporting|extended|extension|extract|fail|failed|features|fetch|field|field-groups|field-symbols|fields|file|fill|filters??|final|find|first|first-line|fixed-point|flush|following|for|format|forward|found|frames??|free|from|full|function|function-pool|generate|get|giving|graph|groups??|handler??|hashed|having|headers??|heading|help-id|help-request|hide|hint|hold|hotspot|icon|id|identification|identifier|ignore|ignoring|immediately|implemented|implicit|import|importing|in|inactive|incl|includes??|including|increment|index|index-line|indicators|infotypes|inheriting|init|initial|initialization|inner|input|insert|instances??|intensified|interface|interface-pool|interfaces|internal|intervals|into|inverse|inverted-date|is|job|join|keep|keeping|kernel|keys??|keywords|kind|language|last|late|layout|leading|leave|left|left-justified|legacy|length|let|levels??|like|line|line-count|line-selection|line-size|linefeed|lines|link|list|list-processing|listbox|load|load-of-program|locale??|locks??|log-point|logical|lower|mapped|mapping|margin|mark|mask|match|matchcode|maximum|members|memory|mesh|message|message-id|messages|messaging|methods??|mode|modif|modifier|modify|module|move|move-corresponding|multiply|multiply-corresponding|name|nametab|native|nested|nesting|new|new-line|new-page|new-section|next|no-display|no-extension|no-gaps??|no-grouping|no-heading|no-scrolling|no-sign|no-title|no-zero|nodes|non-unicode|non-unique|number|objects??|objmgr|obligatory|occurences??|occurrences??|occurs|of|offset|on|only|open|optional|options??|order|others|out|outer|output|output-length|overflow|overlay|pack|package|padding|page|parameter|parameter-table|parameters|part|partially|pcre|perform|performing|permissions|pf-status|places|pool|position|pragmas|preceding|precompiled|preferred|preserving|primary|print|print-control|private|privileged|procedure|process|program|property|protected|provide|push|pushbutton|put|query|queue-only|queueonly|quickinfo|radiobutton|raising|ranges??|read|read-only|received??|receiving|redefinition|reduce|ref|reference|refresh|regex|reject|renaming|replace|replacement|replacing|report|reported|request|requested|required|reserve|reset|resolution|respecting|response|restore|results??|resumable|resume|retry|return|returning|right|right-justified|rollback|rows|rp-provide-from-last|run|sap|sap-spool|save|saving|scan|screen|scroll|scroll-boundary|scrolling|search|seconds|section|select|select-options|selection|selection-screen|selection-sets??|selection-table|selections|send|separated??|session|set|shared|shift|shortdump|shortdump-id|sign|simple|simulation|single|size|skip|skipping|smart|some|sort|sortable|sorted|source|specified|split|spool|spots|sql|stable|stamp|standard|start-of-selection|starting|state|statements??|statics??|statusinfo|step|step-loop|stop|structures??|style|subkey|submatches|submit|subroutine|subscreen|substring|subtract|subtract-corresponding|suffix|sum|summary|supplied|supply|suppress|switch|symbol|syntax-check|syntax-trace|system-call|system-exceptions|tab|tabbed|tables??|tableview|tabstrip|target|tasks??|test|test-injection|test-seam|testing|text|textpool|then|throw|times??|title|titlebar|to|tokens|top-lines|top-of-page|trace-file|trace-table|trailing|transaction|transfer|transformation|translate|transporting|trmac|truncate|truncation|type|type-pools??|types|uline|unassign|unbounded|under|unicode|union|unique|unit|unix|unpack|until|unwind|up|update|upper|user|user-command|using|utf-8|uuid|valid|validate|value|value-request|values|vary|varying|version|via|visible|wait|when|where|windows??|with|with-heading|with-title|without|word|work|workspace|write|xml|zone)(?=[,.:\\\\s])","name":"keyword.control.simple.abap"},"operators":{"patterns":[{"include":"#other_operator"},{"include":"#arithmetic_operator"},{"include":"#comparison_operator"},{"include":"#logical_operator"}]},"other_operator":{"match":"(?<=\\\\s)(&&?|\\\\?=|\\\\+=|-=|/=|\\\\*=|&&=|&=)(?=\\\\s)","name":"keyword.control.simple.abap"},"reserved_names":{"match":"(?i)(?<=\\\\s)(me|super)(?=[,.\\\\s]|->)","name":"constant.language.abap"},"sql_functions":{"match":"(?i)(?<=\\\\s)(abap_system_timezone|abap_user_timezone|abs|add_days|add_months|allow_precision_loss|as_geo_json|avg|bintohex|cast|ceil|coalesce|concat_with_space|concat|corr_spearman|corr|count|currency_conversion|datn_add_days|datn_add_months|datn_days_between|dats_add_days|dats_add_months|dats_days_between|dats_from_datn|dats_is_valid|dats_tims_to_tstmp|dats_to_datn|dayname|days_between|dense_rank|division|div|extract_day|extract_hour|extract_minute|extract_month|extract_second|extract_year|first_value|floor|grouping|hextobin|initcap|instr|is_valid|lag|last_value|lead|left|length|like_regexpr|locate_regexpr_after|locate_regexpr|locate|lower|lpad|ltrim|max|median|min|mod|monthname|ntile|occurrences_regexpr|over|product|rank|replace_regexpr|replace|rigth|round|row_number|rpad|rtrim|stddev|string_agg|substring_regexpr|substring|sum|tims_from_timn|tims_is_valid|tims_to_timn|to_blob|to_clob|tstmp_add_seconds|tstmp_current_utctimestamp|tstmp_is_valid|tstmp_seconds_between|tstmp_to_dats|tstmp_to_dst|tstmp_to_tims|tstmpl_from_utcl|tstmpl_to_utcl|unit_conversion|upper|utcl_add_seconds|utcl_current|utcl_seconds_between|uuid|var|weekday)(?=\\\\()","name":"entity.name.function.sql.abap"},"sql_types":{"match":"(?i)(?<=\\\\s)(char|clnt|cuky|curr|datn|dats|dec|decfloat16|decfloat34|fltp|int1|int2|int4|int8|lang|numc|quan|raw|sstring|timn|tims|unit|utclong)(?=[()\\\\s])","name":"entity.name.type.sql.abap"},"system_fields":{"captures":{"1":{"name":"variable.language.abap"},"2":{"name":"variable.language.abap"}},"match":"(?i)\\\\b(sy)-(abcde|batch|binpt|calld|callr|colno|cpage|cprog|cucol|curow|datar|datlo|datum|dayst|dbcnt|dbnam|dbsysc|dyngr|dynnr|fdayw|fdpos|host|index|langu|ldbpg|lilli|linct|linno|linsz|lisel|listi|loopc|lsind|macol|mandt|marow|modno|msgid|msgli|msgno|msgty|msgv[1-4]|opsysc|pagno|pfkey|repid|saprl|scols|slset|spono|srows|staco|staro|stepl|subrc|sysid|tabix|tcode|tfill|timlo|title|tleng|tvar[0-9]|tzone|ucomm|uline|uname|uzeit|vline|wtitl|zonlo)(?=[.\\\\s])"},"text_symbols":{"captures":{"1":{"name":"keyword.control.simple.abap"},"2":{"name":"constant.numeric.abap"}},"match":"(?i)(?<=^|\\\\s)(text)-([0-9A-Z]{1,3})(?=[,.:\\\\s])"}},"scopeName":"source.abap"}`))];export{e as default};