@visual-z/xeno 0.2.1 → 0.4.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 (282) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +126 -34
  3. package/docs/app-server.md +84 -0
  4. package/docs/install.md +51 -13
  5. package/docs/providers.md +24 -3
  6. package/package.json +7 -5
  7. package/src/apps/app-server.ts +28 -0
  8. package/src/apps/approve.ts +3 -3
  9. package/src/apps/cli.ts +25 -10
  10. package/src/apps/commands.ts +45 -8
  11. package/src/apps/completions.ts +78 -22
  12. package/src/apps/control-transport.ts +89 -0
  13. package/src/apps/image-draft.ts +46 -0
  14. package/src/apps/model-catalog.ts +2 -1
  15. package/src/apps/project-trust.ts +2 -42
  16. package/src/apps/provider-connect.ts +346 -0
  17. package/src/apps/render.ts +22 -47
  18. package/src/apps/resources.ts +5 -2
  19. package/src/apps/shell.ts +333 -1542
  20. package/src/apps/startup-input.ts +45 -9
  21. package/src/apps/startup.ts +149 -94
  22. package/src/apps/ui/app.ts +1553 -0
  23. package/src/apps/ui/control-model.ts +26 -0
  24. package/src/apps/ui/extension-host.ts +194 -0
  25. package/src/apps/ui/image-cache.ts +78 -0
  26. package/src/apps/ui/palette.ts +88 -0
  27. package/src/apps/ui/project.ts +127 -0
  28. package/src/apps/ui/standalone.ts +146 -0
  29. package/src/apps/ui/status-model.ts +68 -0
  30. package/src/apps/ui/surface.ts +86 -0
  31. package/src/apps/ui/theme.ts +86 -0
  32. package/src/core/acceptance.ts +107 -0
  33. package/src/core/action-defaults.ts +47 -295
  34. package/src/core/benchmark.ts +2 -2
  35. package/src/core/branch-receipt.ts +24 -0
  36. package/src/core/checkpoints.ts +64 -0
  37. package/src/core/churn.ts +2 -0
  38. package/src/core/command-artifacts.ts +94 -0
  39. package/src/core/command-contract.ts +35 -0
  40. package/src/core/completion-action.ts +20 -0
  41. package/src/core/completion-review.ts +37 -21
  42. package/src/core/context-detail.ts +37 -0
  43. package/src/core/context-policy.ts +74 -1
  44. package/src/core/continuation.ts +17 -2
  45. package/src/core/control-protocol.ts +62 -0
  46. package/src/core/control-view.ts +42 -0
  47. package/src/core/derive.ts +29 -3
  48. package/src/core/draft-images.ts +56 -0
  49. package/src/core/environment.ts +145 -0
  50. package/src/core/evaluation-coverage.ts +74 -0
  51. package/src/core/event-validation.ts +81 -6
  52. package/src/core/events.ts +99 -3
  53. package/src/core/evidence-links.ts +7 -0
  54. package/src/core/evidence.ts +13 -3
  55. package/src/core/execution.ts +8 -2
  56. package/src/core/experiment.ts +133 -0
  57. package/src/core/export.ts +9 -0
  58. package/src/core/failure-recovery.ts +173 -0
  59. package/src/core/file-precondition.ts +72 -0
  60. package/src/core/handoff.ts +65 -4
  61. package/src/core/harness-diagnostics.ts +206 -0
  62. package/src/core/history-query.ts +38 -2
  63. package/src/core/history.ts +38 -1
  64. package/src/core/integration-health.ts +34 -0
  65. package/src/core/keymap.ts +8 -20
  66. package/src/core/model-picker.ts +64 -0
  67. package/src/core/native-eval.ts +25 -0
  68. package/src/core/note-guidance.ts +34 -0
  69. package/src/core/oauth-fields.ts +44 -0
  70. package/src/core/observation-view.ts +49 -8
  71. package/src/core/permissions.ts +24 -2
  72. package/src/core/process-receipt.ts +105 -0
  73. package/src/core/process-state.ts +37 -0
  74. package/src/core/program-status.ts +31 -0
  75. package/src/core/project.ts +70 -19
  76. package/src/core/prompt-policy.ts +37 -0
  77. package/src/core/query-eval-contract.ts +14 -0
  78. package/src/core/read-observation.ts +82 -0
  79. package/src/core/recovery-corpus.ts +17 -0
  80. package/src/core/recovery-evaluation.ts +60 -0
  81. package/src/core/recovery-sources.ts +74 -0
  82. package/src/core/request-progress.ts +87 -0
  83. package/src/core/request-state.ts +114 -0
  84. package/src/core/runtime-target.ts +94 -0
  85. package/src/core/search-observation.ts +97 -0
  86. package/src/core/session-tree.ts +147 -0
  87. package/src/core/settings.ts +33 -10
  88. package/src/core/skills.ts +44 -0
  89. package/src/core/sql-query.ts +211 -0
  90. package/src/core/theme.ts +36 -58
  91. package/src/core/tool-contract.ts +51 -2
  92. package/src/core/tool-discovery.ts +87 -0
  93. package/src/core/tool-failure.ts +60 -0
  94. package/src/core/tool-input.ts +6 -0
  95. package/src/core/tool-mode-protocol.ts +25 -0
  96. package/src/core/transcript.ts +19 -4
  97. package/src/core/trial-lifecycle.ts +14 -0
  98. package/src/core/tui-mode.ts +29 -0
  99. package/src/core/update-check.ts +97 -0
  100. package/src/core/version.ts +55 -0
  101. package/src/core/work-state.ts +49 -3
  102. package/src/core/working-set.ts +91 -0
  103. package/src/runtime/auth.ts +11 -4
  104. package/src/runtime/branch-jobs.ts +36 -0
  105. package/src/runtime/catalog.ts +13 -3
  106. package/src/runtime/clipboard.ts +15 -4
  107. package/src/runtime/command-artifacts.ts +64 -0
  108. package/src/runtime/control-connection.ts +104 -0
  109. package/src/runtime/environment-tools.ts +60 -0
  110. package/src/runtime/evidence-notes.ts +4 -2
  111. package/src/runtime/exec-sessions.ts +206 -61
  112. package/src/runtime/execution-control.ts +1 -0
  113. package/src/runtime/extension-ui.ts +22 -30
  114. package/src/runtime/extensions.ts +95 -16
  115. package/src/runtime/harness-comparison.ts +27 -7
  116. package/src/runtime/harness-eval.ts +462 -0
  117. package/src/runtime/history-cache.ts +57 -0
  118. package/src/runtime/history-tools.ts +62 -12
  119. package/src/runtime/integrations.ts +210 -0
  120. package/src/runtime/loop.ts +1088 -282
  121. package/src/runtime/model-discovery.ts +208 -0
  122. package/src/runtime/model.ts +9 -0
  123. package/src/runtime/native-eval-fixtures.ts +33 -0
  124. package/src/runtime/native-eval.ts +146 -0
  125. package/src/runtime/oauth.ts +80 -3
  126. package/src/runtime/pi-provider.ts +169 -12
  127. package/src/runtime/program-status.ts +38 -0
  128. package/src/runtime/provider-connect.ts +109 -0
  129. package/src/runtime/provider-presets.ts +87 -0
  130. package/src/runtime/provider-request.ts +13 -2
  131. package/src/runtime/provider.ts +23 -9
  132. package/src/runtime/query-eval-verifier.ts +28 -0
  133. package/src/runtime/recovery-coverage.ts +71 -0
  134. package/src/runtime/recovery-evidence.ts +76 -0
  135. package/src/runtime/recovery-fixtures.ts +116 -0
  136. package/src/runtime/runtime-target.ts +149 -0
  137. package/src/runtime/session-control.ts +198 -0
  138. package/src/runtime/tool-broker.ts +137 -0
  139. package/src/runtime/tool-discovery.ts +57 -0
  140. package/src/runtime/tool-mode-eval.ts +146 -0
  141. package/src/runtime/tool-queue.ts +77 -0
  142. package/src/runtime/tools.ts +628 -183
  143. package/src/runtime/trial-control.ts +45 -0
  144. package/src/runtime/update-check.ts +47 -0
  145. package/src/runtime/window-trial.ts +2 -1
  146. package/src/runtime/working-notes.ts +33 -3
  147. package/src/runtime/workspace-lifecycle.ts +97 -0
  148. package/src/store/acceptance.ts +15 -0
  149. package/src/store/checkpoint-history.ts +43 -0
  150. package/src/store/command-artifacts.ts +96 -0
  151. package/src/store/command.ts +65 -37
  152. package/src/store/file-checkpoints.ts +104 -0
  153. package/src/store/file-index.ts +13 -1
  154. package/src/store/git-checkpoints.ts +67 -0
  155. package/src/store/observations.ts +61 -20
  156. package/src/store/process-receipts.ts +34 -0
  157. package/src/store/query-source.ts +13 -8
  158. package/src/store/recovery.ts +2 -3
  159. package/src/store/replay.ts +16 -4
  160. package/src/store/runtime-target.ts +64 -0
  161. package/src/store/search.ts +171 -132
  162. package/src/store/session-archive.ts +4 -0
  163. package/src/store/source-cancellation.ts +6 -0
  164. package/src/store/update-cache.ts +36 -0
  165. package/src/store/workspace-observation.ts +113 -0
  166. package/src/tui/ansi-spans.ts +126 -0
  167. package/src/tui/style.ts +19 -16
  168. package/src/tui/ui/atomic-draft.ts +121 -0
  169. package/src/tui/ui/canvas.ts +96 -0
  170. package/src/tui/ui/colors.ts +70 -0
  171. package/src/tui/ui/composer.ts +200 -0
  172. package/src/tui/ui/control-panel.ts +23 -0
  173. package/src/tui/ui/dialog.ts +379 -0
  174. package/src/tui/ui/draft.ts +23 -0
  175. package/src/tui/ui/edge.ts +186 -0
  176. package/src/tui/ui/image-size.ts +40 -0
  177. package/src/tui/ui/ink.ts +210 -0
  178. package/src/tui/ui/items.ts +40 -0
  179. package/src/tui/ui/keys.ts +40 -0
  180. package/src/tui/ui/layout.ts +74 -0
  181. package/src/tui/ui/list-model.ts +86 -0
  182. package/src/tui/ui/list-view.ts +172 -0
  183. package/src/tui/ui/mascot.ts +67 -0
  184. package/src/tui/ui/messages.ts +518 -0
  185. package/src/tui/ui/motion.ts +28 -0
  186. package/src/tui/ui/paths.ts +10 -0
  187. package/src/tui/ui/pictures.ts +78 -0
  188. package/src/tui/ui/screen.ts +421 -0
  189. package/src/tui/ui/status.ts +279 -0
  190. package/src/tui/ui/styled.ts +85 -0
  191. package/src/tui/ui/testing.ts +5 -0
  192. package/src/tui/ui/tool-card.ts +150 -0
  193. package/src/apps/extension-ui.ts +0 -330
  194. package/src/apps/resource-browser.ts +0 -75
  195. package/src/apps/session-browser.ts +0 -113
  196. package/src/apps/session-picker.ts +0 -55
  197. package/src/tui/ansi.ts +0 -67
  198. package/src/tui/autocomplete.ts +0 -29
  199. package/src/tui/chat-viewport.ts +0 -47
  200. package/src/tui/complete.ts +0 -87
  201. package/src/tui/editor.ts +0 -940
  202. package/src/tui/host.ts +0 -264
  203. package/src/tui/input.ts +0 -75
  204. package/src/tui/keys.ts +0 -378
  205. package/src/tui/markdown.ts +0 -17
  206. package/src/tui/render.ts +0 -153
  207. package/src/tui/select.ts +0 -258
  208. package/src/tui/settings.ts +0 -44
  209. package/src/tui/statusline.ts +0 -162
  210. package/src/tui/system-theme.ts +0 -700
  211. package/src/tui/terminal.ts +0 -30
  212. package/src/tui/text.ts +0 -52
  213. package/src/tui/toolview.ts +0 -386
  214. package/src/tui/transcript.ts +0 -123
  215. package/src/tui/tree.ts +0 -471
  216. package/src/vendor/pi-tui/LICENSE.upstream +0 -21
  217. package/src/vendor/pi-tui/UPSTREAM.json +0 -50
  218. package/src/vendor/pi-tui/VENDOR.md +0 -25
  219. package/src/vendor/pi-tui/alt-screen-search.ts +0 -327
  220. package/src/vendor/pi-tui/autocomplete.ts +0 -860
  221. package/src/vendor/pi-tui/colors.ts +0 -367
  222. package/src/vendor/pi-tui/components/alt-screen-flash.ts +0 -51
  223. package/src/vendor/pi-tui/components/box.ts +0 -166
  224. package/src/vendor/pi-tui/components/cancellable-loader.ts +0 -40
  225. package/src/vendor/pi-tui/components/editor.ts +0 -2472
  226. package/src/vendor/pi-tui/components/h-stack.ts +0 -44
  227. package/src/vendor/pi-tui/components/image.ts +0 -127
  228. package/src/vendor/pi-tui/components/input.ts +0 -494
  229. package/src/vendor/pi-tui/components/loader.ts +0 -101
  230. package/src/vendor/pi-tui/components/markdown.ts +0 -1021
  231. package/src/vendor/pi-tui/components/mouse-region.ts +0 -33
  232. package/src/vendor/pi-tui/components/scroll-view.ts +0 -224
  233. package/src/vendor/pi-tui/components/select-list.ts +0 -273
  234. package/src/vendor/pi-tui/components/settings-list.ts +0 -328
  235. package/src/vendor/pi-tui/components/spacer.ts +0 -28
  236. package/src/vendor/pi-tui/components/stack.ts +0 -154
  237. package/src/vendor/pi-tui/components/text.ts +0 -107
  238. package/src/vendor/pi-tui/components/truncated-text.ts +0 -65
  239. package/src/vendor/pi-tui/components/v-stack.ts +0 -33
  240. package/src/vendor/pi-tui/editor-component.ts +0 -74
  241. package/src/vendor/pi-tui/fuzzy.ts +0 -138
  242. package/src/vendor/pi-tui/index.ts +0 -186
  243. package/src/vendor/pi-tui/keybindings.ts +0 -320
  244. package/src/vendor/pi-tui/keys.ts +0 -1401
  245. package/src/vendor/pi-tui/kill-ring.ts +0 -46
  246. package/src/vendor/pi-tui/latex.ts +0 -1506
  247. package/src/vendor/pi-tui/layout-node.ts +0 -51
  248. package/src/vendor/pi-tui/layout.ts +0 -449
  249. package/src/vendor/pi-tui/native/UPSTREAM.json +0 -23
  250. package/src/vendor/pi-tui/native/clipboard.h +0 -208
  251. package/src/vendor/pi-tui/native/darwin/README.md +0 -22
  252. package/src/vendor/pi-tui/native/darwin/build.sh +0 -69
  253. package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
  254. package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
  255. package/src/vendor/pi-tui/native/darwin/src/darwin-platform.m +0 -132
  256. package/src/vendor/pi-tui/native/linux/README.md +0 -23
  257. package/src/vendor/pi-tui/native/linux/build.sh +0 -65
  258. package/src/vendor/pi-tui/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
  259. package/src/vendor/pi-tui/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
  260. package/src/vendor/pi-tui/native/linux/src/clipboard-worker.h +0 -70
  261. package/src/vendor/pi-tui/native/linux/src/linux-platform-x11.c +0 -274
  262. package/src/vendor/pi-tui/native/napi.h +0 -91
  263. package/src/vendor/pi-tui/native/win32/README.md +0 -31
  264. package/src/vendor/pi-tui/native/win32/build.mjs +0 -232
  265. package/src/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
  266. package/src/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
  267. package/src/vendor/pi-tui/native/win32/src/win32-platform.c +0 -241
  268. package/src/vendor/pi-tui/native-modifiers.ts +0 -13
  269. package/src/vendor/pi-tui/native-module-path.ts +0 -31
  270. package/src/vendor/pi-tui/native-platform.ts +0 -65
  271. package/src/vendor/pi-tui/oklab.ts +0 -233
  272. package/src/vendor/pi-tui/stdin-buffer.ts +0 -444
  273. package/src/vendor/pi-tui/terminal-colors.ts +0 -91
  274. package/src/vendor/pi-tui/terminal-image.ts +0 -731
  275. package/src/vendor/pi-tui/terminal.ts +0 -554
  276. package/src/vendor/pi-tui/tui-alt-screen.ts +0 -1750
  277. package/src/vendor/pi-tui/tui-main-screen.ts +0 -655
  278. package/src/vendor/pi-tui/tui.ts +0 -1493
  279. package/src/vendor/pi-tui/undo-stack.ts +0 -28
  280. package/src/vendor/pi-tui/utils.ts +0 -1385
  281. package/src/vendor/pi-tui/wheel-scroll.ts +0 -82
  282. package/src/vendor/pi-tui/word-navigation.ts +0 -117
@@ -1,1506 +0,0 @@
1
- import { visibleWidth } from "./utils.ts";
2
-
3
- const SYMBOLS: Readonly<Record<string, string>> = {
4
- alpha: "α",
5
- beta: "β",
6
- gamma: "γ",
7
- delta: "δ",
8
- epsilon: "ϵ",
9
- varepsilon: "ε",
10
- zeta: "ζ",
11
- eta: "η",
12
- theta: "θ",
13
- vartheta: "ϑ",
14
- iota: "ι",
15
- kappa: "κ",
16
- varkappa: "ϰ",
17
- lambda: "λ",
18
- mu: "μ",
19
- nu: "ν",
20
- xi: "ξ",
21
- pi: "π",
22
- varpi: "ϖ",
23
- rho: "ρ",
24
- varrho: "ϱ",
25
- sigma: "σ",
26
- varsigma: "ς",
27
- tau: "τ",
28
- upsilon: "υ",
29
- phi: "ϕ",
30
- varphi: "φ",
31
- chi: "χ",
32
- psi: "ψ",
33
- omega: "ω",
34
- Gamma: "Γ",
35
- Delta: "Δ",
36
- Theta: "Θ",
37
- Lambda: "Λ",
38
- Xi: "Ξ",
39
- Pi: "Π",
40
- Sigma: "Σ",
41
- Upsilon: "Υ",
42
- Phi: "Φ",
43
- Psi: "Ψ",
44
- Omega: "Ω",
45
- pm: "±",
46
- mp: "∓",
47
- times: "×",
48
- div: "÷",
49
- cdot: "·",
50
- ast: "∗",
51
- star: "⋆",
52
- circ: "∘",
53
- bullet: "•",
54
- oplus: "⊕",
55
- ominus: "⊖",
56
- otimes: "⊗",
57
- oslash: "⊘",
58
- odot: "⊙",
59
- bigcirc: "○",
60
- dagger: "†",
61
- ddagger: "‡",
62
- amalg: "⨿",
63
- uplus: "⊎",
64
- sqcap: "⊓",
65
- sqcup: "⊔",
66
- bowtie: "⋈",
67
- Join: "⋈",
68
- ltimes: "⋉",
69
- rtimes: "⋊",
70
- leftouterjoin: "⟕",
71
- rightouterjoin: "⟖",
72
- fullouterjoin: "⟗",
73
- triangleleft: "◁",
74
- triangleright: "▷",
75
- wr: "≀",
76
- cap: "∩",
77
- cup: "∪",
78
- bigcap: "⋂",
79
- bigcup: "⋃",
80
- bigwedge: "⋀",
81
- bigvee: "⋁",
82
- bigsqcup: "⨆",
83
- biguplus: "⨄",
84
- bigoplus: "⨁",
85
- bigotimes: "⨂",
86
- bigodot: "⨀",
87
- setminus: "∖",
88
- in: "∈",
89
- notin: "∉",
90
- ni: "∋",
91
- subset: "⊂",
92
- supset: "⊃",
93
- subseteq: "⊆",
94
- supseteq: "⊇",
95
- sqsubset: "⊏",
96
- sqsupset: "⊐",
97
- sqsubseteq: "⊑",
98
- sqsupseteq: "⊒",
99
- prec: "≺",
100
- preceq: "≼",
101
- succ: "≻",
102
- succeq: "≽",
103
- ll: "≪",
104
- gg: "≫",
105
- le: "≤",
106
- leq: "≤",
107
- leqslant: "≤",
108
- ge: "≥",
109
- geq: "≥",
110
- geqslant: "≥",
111
- ne: "≠",
112
- neq: "≠",
113
- equiv: "≡",
114
- approx: "≈",
115
- sim: "∼",
116
- simeq: "≃",
117
- cong: "≅",
118
- asymp: "≍",
119
- doteq: "≐",
120
- propto: "∝",
121
- parallel: "∥",
122
- perp: "⊥",
123
- mid: "∣",
124
- vdash: "⊢",
125
- dashv: "⊣",
126
- models: "⊨",
127
- Vdash: "⊩",
128
- Vvdash: "⊪",
129
- nvdash: "⊬",
130
- nvDash: "⊭",
131
- forall: "∀",
132
- exists: "∃",
133
- nexists: "∄",
134
- neg: "¬",
135
- land: "∧",
136
- wedge: "∧",
137
- lor: "∨",
138
- vee: "∨",
139
- to: "→",
140
- rightarrow: "→",
141
- longrightarrow: "→",
142
- leftarrow: "←",
143
- longleftarrow: "←",
144
- gets: "←",
145
- leftrightarrow: "↔",
146
- longleftrightarrow: "↔",
147
- hookleftarrow: "↩",
148
- hookrightarrow: "↪",
149
- twoheadleftarrow: "↞",
150
- twoheadrightarrow: "↠",
151
- leftharpoonup: "↼",
152
- leftharpoondown: "↽",
153
- rightharpoonup: "⇀",
154
- rightharpoondown: "⇁",
155
- rightleftharpoons: "⇌",
156
- leftrightharpoons: "⇋",
157
- nearrow: "↗",
158
- searrow: "↘",
159
- swarrow: "↙",
160
- nwarrow: "↖",
161
- rightsquigarrow: "⇝",
162
- leadsto: "⇝",
163
- Rightarrow: "⇒",
164
- Longrightarrow: "⇒",
165
- Leftarrow: "⇐",
166
- Longleftarrow: "⇐",
167
- Leftrightarrow: "⇔",
168
- Longleftrightarrow: "⇔",
169
- implies: "⇒",
170
- iff: "⇔",
171
- mapsto: "↦",
172
- longmapsto: "↦",
173
- uparrow: "↑",
174
- downarrow: "↓",
175
- partial: "∂",
176
- nabla: "∇",
177
- int: "∫",
178
- iint: "∬",
179
- iiint: "∭",
180
- oint: "∮",
181
- sum: "∑",
182
- prod: "∏",
183
- coprod: "∐",
184
- infty: "∞",
185
- emptyset: "∅",
186
- varnothing: "∅",
187
- angle: "∠",
188
- therefore: "∴",
189
- because: "∵",
190
- aleph: "ℵ",
191
- beth: "ℶ",
192
- gimel: "ℷ",
193
- daleth: "ℸ",
194
- top: "⊤",
195
- bot: "⊥",
196
- triangle: "△",
197
- square: "□",
198
- lozenge: "◊",
199
- checkmark: "✓",
200
- complement: "∁",
201
- wp: "℘",
202
- prime: "′",
203
- ldots: "…",
204
- dots: "…",
205
- cdots: "⋯",
206
- vdots: "⋮",
207
- ddots: "⋱",
208
- ell: "ℓ",
209
- hbar: "ℏ",
210
- Im: "ℑ",
211
- Re: "ℜ",
212
- langle: "⟨",
213
- rangle: "⟩",
214
- vert: "|",
215
- lvert: "|",
216
- rvert: "|",
217
- Vert: "‖",
218
- lVert: "‖",
219
- rVert: "‖",
220
- lbrace: "{",
221
- rbrace: "}",
222
- backslash: "\\",
223
- lfloor: "⌊",
224
- rfloor: "⌋",
225
- lceil: "⌈",
226
- rceil: "⌉",
227
- colon: ":",
228
- };
229
-
230
- const NAMED_OPERATORS = new Set([
231
- "arccos",
232
- "arcsin",
233
- "arctan",
234
- "arg",
235
- "cos",
236
- "cosh",
237
- "cot",
238
- "coth",
239
- "csc",
240
- "deg",
241
- "det",
242
- "dim",
243
- "exp",
244
- "gcd",
245
- "hom",
246
- "inf",
247
- "ker",
248
- "lg",
249
- "lim",
250
- "liminf",
251
- "limsup",
252
- "ln",
253
- "log",
254
- "max",
255
- "min",
256
- "Pr",
257
- "sec",
258
- "sin",
259
- "sinh",
260
- "sup",
261
- "tan",
262
- "tanh",
263
- ]);
264
-
265
- const LIMIT_OPERATORS = new Set([
266
- "argmax",
267
- "argmin",
268
- "inf",
269
- "injlim",
270
- "lim",
271
- "liminf",
272
- "limsup",
273
- "max",
274
- "min",
275
- "projlim",
276
- "sup",
277
- ]);
278
-
279
- const DISPLAY_LIMIT_SYMBOLS = new Set([
280
- "bigcap",
281
- "bigcup",
282
- "bigodot",
283
- "bigoplus",
284
- "bigotimes",
285
- "bigsqcup",
286
- "biguplus",
287
- "bigvee",
288
- "bigwedge",
289
- "coprod",
290
- "int",
291
- "iint",
292
- "iiint",
293
- "oint",
294
- "prod",
295
- "sum",
296
- ]);
297
-
298
- const RELATION_COMMANDS = new Set([
299
- "Leftarrow",
300
- "Leftrightarrow",
301
- "Longleftarrow",
302
- "Longleftrightarrow",
303
- "Longrightarrow",
304
- "Rightarrow",
305
- "Join",
306
- "Vdash",
307
- "Vvdash",
308
- "approx",
309
- "asymp",
310
- "bowtie",
311
- "cong",
312
- "dashv",
313
- "fullouterjoin",
314
- "doteq",
315
- "downarrow",
316
- "equiv",
317
- "ge",
318
- "geq",
319
- "geqslant",
320
- "gets",
321
- "gg",
322
- "hookleftarrow",
323
- "hookrightarrow",
324
- "iff",
325
- "implies",
326
- "in",
327
- "leadsto",
328
- "le",
329
- "leftarrow",
330
- "leftharpoondown",
331
- "leftharpoonup",
332
- "leftrightarrow",
333
- "leftrightharpoons",
334
- "leftouterjoin",
335
- "leq",
336
- "leqslant",
337
- "ll",
338
- "longleftarrow",
339
- "longleftrightarrow",
340
- "longmapsto",
341
- "longrightarrow",
342
- "ltimes",
343
- "mapsto",
344
- "mid",
345
- "models",
346
- "ne",
347
- "nearrow",
348
- "neq",
349
- "ni",
350
- "notin",
351
- "nvdash",
352
- "nvDash",
353
- "nwarrow",
354
- "parallel",
355
- "perp",
356
- "prec",
357
- "preceq",
358
- "propto",
359
- "rightharpoondown",
360
- "rightharpoonup",
361
- "rightleftharpoons",
362
- "rightouterjoin",
363
- "rightarrow",
364
- "rightsquigarrow",
365
- "rtimes",
366
- "searrow",
367
- "sim",
368
- "simeq",
369
- "sqsubset",
370
- "sqsubseteq",
371
- "sqsupset",
372
- "sqsupseteq",
373
- "subset",
374
- "subseteq",
375
- "succ",
376
- "succeq",
377
- "supset",
378
- "supseteq",
379
- "swarrow",
380
- "to",
381
- "triangleleft",
382
- "triangleright",
383
- "twoheadleftarrow",
384
- "twoheadrightarrow",
385
- "uparrow",
386
- "vdash",
387
- ]);
388
-
389
- const NEGATED_SYMBOLS: Readonly<Record<string, string>> = {
390
- "<": "≮",
391
- ">": "≯",
392
- "=": "≠",
393
- "∈": "∉",
394
- "∋": "∌",
395
- "∣": "∤",
396
- "∥": "∦",
397
- "∼": "≁",
398
- "≃": "≄",
399
- "≅": "≇",
400
- "≈": "≉",
401
- "≡": "≢",
402
- "≤": "≰",
403
- "≥": "≱",
404
- "≺": "⊀",
405
- "≻": "⊁",
406
- "⊂": "⊄",
407
- "⊃": "⊅",
408
- "⊆": "⊈",
409
- "⊇": "⊉",
410
- "⊢": "⊬",
411
- "⊨": "⊭",
412
- "↔": "↮",
413
- "←": "↚",
414
- "→": "↛",
415
- "⇒": "⇏",
416
- "⇐": "⇍",
417
- "⇔": "⇎",
418
- "≼": "⋠",
419
- "≽": "⋡",
420
- };
421
-
422
- const BLACKBOARD: Readonly<Record<string, string>> = {
423
- C: "ℂ",
424
- H: "ℍ",
425
- N: "ℕ",
426
- P: "ℙ",
427
- Q: "ℚ",
428
- R: "ℝ",
429
- Z: "ℤ",
430
- };
431
-
432
- const SUPERSCRIPTS: Readonly<Record<string, string>> = {
433
- "0": "⁰",
434
- "1": "¹",
435
- "2": "²",
436
- "3": "³",
437
- "4": "⁴",
438
- "5": "⁵",
439
- "6": "⁶",
440
- "7": "⁷",
441
- "8": "⁸",
442
- "9": "⁹",
443
- "+": "⁺",
444
- "-": "⁻",
445
- "=": "⁼",
446
- "(": "⁽",
447
- ")": "⁾",
448
- a: "ᵃ",
449
- b: "ᵇ",
450
- c: "ᶜ",
451
- d: "ᵈ",
452
- e: "ᵉ",
453
- f: "ᶠ",
454
- g: "ᵍ",
455
- h: "ʰ",
456
- i: "ⁱ",
457
- j: "ʲ",
458
- k: "ᵏ",
459
- l: "ˡ",
460
- m: "ᵐ",
461
- n: "ⁿ",
462
- o: "ᵒ",
463
- p: "ᵖ",
464
- r: "ʳ",
465
- s: "ˢ",
466
- t: "ᵗ",
467
- u: "ᵘ",
468
- v: "ᵛ",
469
- w: "ʷ",
470
- x: "ˣ",
471
- y: "ʸ",
472
- z: "ᶻ",
473
- };
474
-
475
- const SUBSCRIPTS: Readonly<Record<string, string>> = {
476
- "0": "₀",
477
- "1": "₁",
478
- "2": "₂",
479
- "3": "₃",
480
- "4": "₄",
481
- "5": "₅",
482
- "6": "₆",
483
- "7": "₇",
484
- "8": "₈",
485
- "9": "₉",
486
- "+": "₊",
487
- "-": "₋",
488
- "=": "₌",
489
- "(": "₍",
490
- ")": "₎",
491
- a: "ₐ",
492
- e: "ₑ",
493
- h: "ₕ",
494
- i: "ᵢ",
495
- j: "ⱼ",
496
- k: "ₖ",
497
- l: "ₗ",
498
- m: "ₘ",
499
- n: "ₙ",
500
- o: "ₒ",
501
- p: "ₚ",
502
- r: "ᵣ",
503
- s: "ₛ",
504
- t: "ₜ",
505
- u: "ᵤ",
506
- v: "ᵥ",
507
- x: "ₓ",
508
- };
509
-
510
- const SPACING_COMMANDS = new Set([
511
- ",",
512
- ":",
513
- ";",
514
- " ",
515
- ">",
516
- "enspace",
517
- "enskip",
518
- "medspace",
519
- "quad",
520
- "qquad",
521
- "thickspace",
522
- "thinspace",
523
- ]);
524
- const NEGATIVE_SPACING_COMMANDS = new Set(["!", "negmedspace", "negthickspace", "negthinspace"]);
525
- const NEGATIVE_SPACE = "\u0000";
526
- const FONT_SWITCH_COMMANDS = new Set(["bf", "cal", "it", "rm", "sf", "sl", "tt"]);
527
- const IGNORED_COMMANDS = new Set([
528
- "displaystyle",
529
- "limits",
530
- "nolimits",
531
- "scriptstyle",
532
- "scriptscriptstyle",
533
- "textstyle",
534
- ]);
535
- const SIZE_COMMANDS = new Set([
536
- "big",
537
- "Big",
538
- "bigg",
539
- "Bigg",
540
- "bigl",
541
- "Bigl",
542
- "biggl",
543
- "Biggl",
544
- "bigr",
545
- "Bigr",
546
- "biggr",
547
- "Biggr",
548
- ]);
549
- const PLAIN_WRAPPERS = new Set([
550
- "emph",
551
- "mathcal",
552
- "mathbf",
553
- "mathfrak",
554
- "mathit",
555
- "mathrm",
556
- "mathnormal",
557
- "mathscr",
558
- "mathsf",
559
- "mathtt",
560
- "mathup",
561
- "mbox",
562
- "overbrace",
563
- "pmb",
564
- "smash",
565
- "substack",
566
- "text",
567
- "textbf",
568
- "textit",
569
- "textmd",
570
- "textnormal",
571
- "textrm",
572
- "textsc",
573
- "textsf",
574
- "textsl",
575
- "texttt",
576
- "textup",
577
- "underbrace",
578
- "bm",
579
- "boldsymbol",
580
- ]);
581
- const ACCENTS: Readonly<Record<string, string>> = {
582
- acute: "\u0301",
583
- bar: "\u0305",
584
- breve: "\u0306",
585
- check: "\u030c",
586
- ddot: "\u0308",
587
- dot: "\u0307",
588
- grave: "\u0300",
589
- hat: "\u0302",
590
- mathring: "\u030a",
591
- overleftarrow: "\u20d6",
592
- overleftrightarrow: "\u20e1",
593
- overline: "\u0305",
594
- overrightarrow: "\u20d7",
595
- tilde: "\u0303",
596
- underline: "\u0332",
597
- vec: "\u20d7",
598
- widehat: "\u0302",
599
- widetilde: "\u0303",
600
- };
601
-
602
- function replaceCharacters(value: string, replacements: Readonly<Record<string, string>>): string | undefined {
603
- let result = "";
604
- for (const character of value) {
605
- const replacement = replacements[character];
606
- if (replacement === undefined) {
607
- return undefined;
608
- }
609
- result += replacement;
610
- }
611
- return result;
612
- }
613
-
614
- function normalizeScriptValue(value: string): string {
615
- return value.trim().replace(/\s*([=+-])\s*/g, "$1");
616
- }
617
-
618
- function formatUnicodeScript(value: string, kind: "sub" | "sup"): string | undefined {
619
- return replaceCharacters(normalizeScriptValue(value), kind === "sub" ? SUBSCRIPTS : SUPERSCRIPTS);
620
- }
621
-
622
- function formatScript(value: string, kind: "sub" | "sup"): string {
623
- value = normalizeScriptValue(value);
624
- const unicode = formatUnicodeScript(value, kind);
625
- if (unicode !== undefined) {
626
- return unicode;
627
- }
628
-
629
- const prefix = kind === "sub" ? "_" : "^";
630
- if (Array.from(value).length === 1 || (kind === "sub" && /^[A-Za-z]+$/.test(value))) {
631
- return `${prefix}${value}`;
632
- }
633
- return `${prefix}(${value})`;
634
- }
635
-
636
- function formatFraction(numerator: string, denominator: string): string {
637
- numerator = numerator.trim();
638
- denominator = denominator.trim();
639
- const simpleNumerator = /^[\p{L}\p{N}.]+$/u.test(numerator);
640
- const simpleDenominator = /^[\p{N}.]+$/u.test(denominator) || Array.from(denominator).length === 1;
641
- return `${simpleNumerator ? numerator : `(${numerator})`}/${simpleDenominator ? denominator : `(${denominator})`}`;
642
- }
643
-
644
- function formatRoot(value: string, symbol = "√"): string {
645
- value = value.trim();
646
- return /^[\p{L}\p{N}.]+$/u.test(value) ? `${symbol}${value}` : `${symbol}(${value})`;
647
- }
648
-
649
- const NAMED_OPERATOR_START = "\u{f0004}";
650
- const NAMED_OPERATOR_END = "\u{f0005}";
651
- const NAMED_OPERATOR_LEFT_SPACING_PATTERN = /(?<=[\p{L}\p{N})\]}\u{f0001}])\u{f0004}/gu;
652
- const NAMED_OPERATOR_RIGHT_SPACING_PATTERN = /\u{f0005}(?=[\p{L}\p{N}√\u{f0000}])/gu;
653
-
654
- function normalizeOutput(value: string): string {
655
- return value
656
- .replace(NAMED_OPERATOR_LEFT_SPACING_PATTERN, " ")
657
- .replaceAll(NAMED_OPERATOR_START, "")
658
- .replace(NAMED_OPERATOR_RIGHT_SPACING_PATTERN, " ")
659
- .replaceAll(NAMED_OPERATOR_END, "")
660
- .split("\n")
661
- .map((line) => line.replace(/[ \t]+/g, " ").trim())
662
- .filter((line, index, lines) => line.length > 0 || (index > 0 && index < lines.length - 1))
663
- .join("\n")
664
- .trim();
665
- }
666
-
667
- interface FractionNode {
668
- type: "fraction";
669
- numerator: string;
670
- denominator: string;
671
- }
672
-
673
- interface OperatorNode {
674
- type: "operator";
675
- operator: string;
676
- lower?: string;
677
- upper?: string;
678
- }
679
-
680
- interface ScriptNode {
681
- type: "script";
682
- lower?: string;
683
- upper?: string;
684
- }
685
-
686
- interface MatrixNode {
687
- type: "matrix";
688
- lines: string[];
689
- baseline: number;
690
- }
691
-
692
- type LayoutNode = FractionNode | OperatorNode | ScriptNode | MatrixNode;
693
-
694
- interface Layout {
695
- lines: string[];
696
- width: number;
697
- baseline: number;
698
- }
699
-
700
- const LAYOUT_MARKER_START = "\u{f0000}";
701
- const LAYOUT_MARKER_END = "\u{f0001}";
702
- const LAYOUT_MARKER_PATTERN = /\u{f0000}(\d+)\u{f0001}/gu;
703
- const TRAILING_LAYOUT_MARKER_PATTERN = /\u{f0000}(\d+)\u{f0001}$/u;
704
- const PROTECTED_SPACE = "\u{f0002}";
705
-
706
- function padLayoutLine(line: string, width: number, centered = false): string {
707
- const padding = Math.max(0, width - visibleWidth(line));
708
- const left = centered ? Math.floor(padding / 2) : 0;
709
- return `${" ".repeat(left)}${line}${" ".repeat(padding - left)}`;
710
- }
711
-
712
- function joinLayouts(layouts: readonly Layout[]): Layout {
713
- if (layouts.length === 0) {
714
- return { lines: [""], width: 0, baseline: 0 };
715
- }
716
- const baseline = Math.max(...layouts.map((layout) => layout.baseline));
717
- const below = Math.max(...layouts.map((layout) => layout.lines.length - layout.baseline - 1));
718
- const lines: string[] = [];
719
- for (let row = 0; row <= baseline + below; row++) {
720
- let line = "";
721
- for (const layout of layouts) {
722
- const sourceRow = row - baseline + layout.baseline;
723
- line +=
724
- sourceRow >= 0 && sourceRow < layout.lines.length
725
- ? padLayoutLine(layout.lines[sourceRow] ?? "", layout.width)
726
- : " ".repeat(layout.width);
727
- }
728
- lines.push(line.trimEnd());
729
- }
730
- return {
731
- lines,
732
- width: layouts.reduce((width, layout) => width + layout.width, 0),
733
- baseline,
734
- };
735
- }
736
-
737
- function renderLayout(source: string, nodes: readonly LayoutNode[]): Layout {
738
- const renderedLines: string[] = [];
739
- let firstBaseline = 0;
740
- for (const sourceLine of source.split("\n")) {
741
- const layouts: Layout[] = [];
742
- let position = 0;
743
- let previousNode: LayoutNode | undefined;
744
- for (const match of sourceLine.matchAll(LAYOUT_MARKER_PATTERN)) {
745
- const index = match.index;
746
- const node = nodes[Number(match[1])];
747
- if (!node) {
748
- continue;
749
- }
750
- if (index > position) {
751
- const sliced = sourceLine.slice(position, index);
752
- const trimmed = (previousNode ? sliced.trimStart() : sliced).trimEnd();
753
- const preserveLeadingSpace = previousNode?.type === "matrix" && /^\s/.test(sliced);
754
- const preserveTrailingSpace = node.type === "matrix" && /\s$/.test(sliced);
755
- const text = trimmed
756
- ? `${preserveLeadingSpace ? " " : ""}${trimmed}${preserveTrailingSpace ? " " : ""}`
757
- : preserveLeadingSpace || preserveTrailingSpace
758
- ? " "
759
- : "";
760
- layouts.push({ lines: [text], width: visibleWidth(text), baseline: 0 });
761
- }
762
- if (node.type === "fraction") {
763
- const numerator = renderLayout(node.numerator, nodes);
764
- const denominator = renderLayout(node.denominator, nodes);
765
- const contentWidth = Math.max(numerator.width, denominator.width, 1);
766
- const width = contentWidth + 2;
767
- layouts.push({
768
- lines: [
769
- ...numerator.lines.map((line) => padLayoutLine(line, width, true)),
770
- ` ${"─".repeat(contentWidth)} `,
771
- ...denominator.lines.map((line) => padLayoutLine(line, width, true)),
772
- ],
773
- width,
774
- baseline: numerator.lines.length,
775
- });
776
- } else if (node.type === "operator") {
777
- const contentWidth = Math.max(
778
- visibleWidth(node.operator),
779
- node.lower === undefined ? 0 : visibleWidth(node.lower),
780
- node.upper === undefined ? 0 : visibleWidth(node.upper),
781
- );
782
- const lines: string[] = [];
783
- if (node.upper !== undefined) {
784
- lines.push(`${padLayoutLine(node.upper, contentWidth, true)} `);
785
- }
786
- lines.push(`${padLayoutLine(node.operator, contentWidth, true)} `);
787
- if (node.lower !== undefined) {
788
- lines.push(`${padLayoutLine(node.lower, contentWidth, true)} `);
789
- }
790
- layouts.push({
791
- lines,
792
- width: contentWidth + 1,
793
- baseline: node.upper === undefined ? 0 : 1,
794
- });
795
- } else if (node.type === "script") {
796
- const upper = node.upper === undefined ? undefined : renderLayout(node.upper, nodes);
797
- const lower = node.lower === undefined ? undefined : renderLayout(node.lower, nodes);
798
- const width = Math.max(upper?.width ?? 0, lower?.width ?? 0);
799
- layouts.push({
800
- lines: [
801
- ...(upper?.lines.map((line) => padLayoutLine(line, width)) ?? []),
802
- " ".repeat(width),
803
- ...(lower?.lines.map((line) => padLayoutLine(line, width)) ?? []),
804
- ],
805
- width,
806
- baseline: upper?.lines.length ?? 0,
807
- });
808
- } else {
809
- const width = Math.max(0, ...node.lines.map((line) => visibleWidth(line)));
810
- layouts.push({
811
- lines: node.lines.map((line) => padLayoutLine(line, width)),
812
- width,
813
- baseline: node.baseline,
814
- });
815
- }
816
- position = index + match[0].length;
817
- previousNode = node;
818
- }
819
- if (position < sourceLine.length) {
820
- const sliced = sourceLine.slice(position);
821
- const trimmed = previousNode ? sliced.trimStart() : sliced;
822
- const text = previousNode?.type === "matrix" && /^\s/.test(sliced) ? ` ${trimmed}` : trimmed;
823
- layouts.push({ lines: [text], width: visibleWidth(text), baseline: 0 });
824
- }
825
- const lineLayout = joinLayouts(layouts);
826
- if (renderedLines.length === 0) {
827
- firstBaseline = lineLayout.baseline;
828
- }
829
- renderedLines.push(...lineLayout.lines);
830
- }
831
- return {
832
- lines: renderedLines,
833
- width: Math.max(0, ...renderedLines.map((line) => visibleWidth(line))),
834
- baseline: firstBaseline,
835
- };
836
- }
837
-
838
- class LatexParser {
839
- private readonly source: string;
840
- private readonly layoutNodes: LayoutNode[];
841
- private readonly display: boolean;
842
- private position = 0;
843
- private supported = true;
844
- private stackFractions = true;
845
- private scriptDepth = 0;
846
-
847
- constructor(source: string, layoutNodes: LayoutNode[], display: boolean) {
848
- this.source = source;
849
- this.layoutNodes = layoutNodes;
850
- this.display = display;
851
- }
852
-
853
- render(): string | undefined {
854
- const rendered = this.parseSequence();
855
- if (!this.supported || this.position !== this.source.length) {
856
- return undefined;
857
- }
858
- return normalizeOutput(rendered);
859
- }
860
-
861
- private parseSequence(endCharacter?: string): string {
862
- let result = "";
863
- while (this.position < this.source.length) {
864
- const character = this.source[this.position];
865
- if (endCharacter && character === endCharacter) {
866
- this.position++;
867
- return result;
868
- }
869
-
870
- if (character === "}") {
871
- this.supported = false;
872
- return result;
873
- }
874
-
875
- if (character === "{") {
876
- this.position++;
877
- result += this.parseSequence("}");
878
- continue;
879
- }
880
-
881
- if (character === "\\") {
882
- const command = this.parseCommand();
883
- if (command === NEGATIVE_SPACE) {
884
- result = result.trimEnd();
885
- if (result.endsWith(NAMED_OPERATOR_END)) {
886
- result = result.slice(0, -NAMED_OPERATOR_END.length);
887
- }
888
- } else {
889
- result += command;
890
- }
891
- continue;
892
- }
893
-
894
- if (character === "^" || character === "_") {
895
- this.position++;
896
- result = result.trimEnd();
897
- const script = this.parseScripts(character);
898
- if (result.endsWith(NAMED_OPERATOR_END)) {
899
- result = `${result.slice(0, -NAMED_OPERATOR_END.length)}${script}${NAMED_OPERATOR_END}`;
900
- } else {
901
- result += script;
902
- }
903
- continue;
904
- }
905
-
906
- if (/\s/.test(character)) {
907
- result += this.parseWhitespace();
908
- continue;
909
- }
910
-
911
- if (character === "=" || character === "<" || character === ">") {
912
- result = `${result.trimEnd()} ${character} `;
913
- this.position++;
914
- continue;
915
- }
916
-
917
- if (character === "&") {
918
- this.position++;
919
- continue;
920
- }
921
-
922
- if (character === "~") {
923
- this.position++;
924
- result += " ";
925
- continue;
926
- }
927
-
928
- if (character === ".") {
929
- const marker = TRAILING_LAYOUT_MARKER_PATTERN.exec(result);
930
- const node = marker ? this.layoutNodes[Number(marker[1])] : undefined;
931
- if (node?.type === "matrix") {
932
- const lastLine = node.lines.length - 1;
933
- node.lines[lastLine] = `${node.lines[lastLine] ?? ""}${character}`;
934
- this.position++;
935
- continue;
936
- }
937
- }
938
-
939
- result += character;
940
- this.position++;
941
- }
942
-
943
- if (endCharacter) {
944
- this.supported = false;
945
- }
946
- return result;
947
- }
948
-
949
- private parseScripts(initialMarker: "^" | "_"): string {
950
- const scripts: { sub?: string; sup?: string } = {};
951
- const order: Array<"sub" | "sup"> = [];
952
- const parse = (marker: "^" | "_"): void => {
953
- const kind = marker === "_" ? "sub" : "sup";
954
- this.scriptDepth++;
955
- try {
956
- scripts[kind] = this.parseRequiredArgument(false);
957
- } finally {
958
- this.scriptDepth--;
959
- }
960
- order.push(kind);
961
- };
962
-
963
- parse(initialMarker);
964
- let nextPosition = this.position;
965
- while (nextPosition < this.source.length && /\s/.test(this.source[nextPosition] ?? "")) {
966
- nextPosition++;
967
- }
968
- const nextMarker = this.source[nextPosition];
969
- if ((nextMarker === "^" || nextMarker === "_") && nextMarker !== initialMarker) {
970
- this.position = nextPosition + 1;
971
- parse(nextMarker);
972
- }
973
-
974
- const subUnicode = scripts.sub === undefined ? undefined : formatUnicodeScript(scripts.sub, "sub");
975
- const supUnicode = scripts.sup === undefined ? undefined : formatUnicodeScript(scripts.sup, "sup");
976
- const canUseLayout = ![scripts.sub, scripts.sup].some(
977
- (value) =>
978
- value !== undefined &&
979
- (value.includes("/") ||
980
- (!value.includes(LAYOUT_MARKER_START) && Array.from(value).length > 1 && !/[A-Z*∗]/.test(value))),
981
- );
982
- const needsLayout =
983
- this.display &&
984
- canUseLayout &&
985
- (this.scriptDepth > 0 ||
986
- (scripts.sub !== undefined && subUnicode === undefined) ||
987
- (scripts.sup !== undefined && supUnicode === undefined));
988
- if (!needsLayout) {
989
- return order
990
- .map((kind) =>
991
- kind === "sub"
992
- ? (subUnicode ?? formatScript(scripts.sub ?? "", kind))
993
- : (supUnicode ?? formatScript(scripts.sup ?? "", kind)),
994
- )
995
- .join("");
996
- }
997
-
998
- const index =
999
- this.layoutNodes.push({
1000
- type: "script",
1001
- lower: scripts.sub === undefined ? undefined : normalizeOutput(scripts.sub),
1002
- upper: scripts.sup === undefined ? undefined : normalizeOutput(scripts.sup),
1003
- }) - 1;
1004
- return `${LAYOUT_MARKER_START}${index}${LAYOUT_MARKER_END}`;
1005
- }
1006
-
1007
- private parseWhitespace(): string {
1008
- while (this.position < this.source.length && /\s/.test(this.source[this.position] ?? "")) {
1009
- this.position++;
1010
- }
1011
- return " ";
1012
- }
1013
-
1014
- private parseCommand(): string {
1015
- this.position++;
1016
- if (this.position >= this.source.length) {
1017
- this.supported = false;
1018
- return "";
1019
- }
1020
-
1021
- let command = "";
1022
- const first = this.source[this.position] ?? "";
1023
- if (first === "\n" || first === "\r") {
1024
- this.position++;
1025
- if (first === "\r" && this.source[this.position] === "\n") {
1026
- this.position++;
1027
- }
1028
- return " ";
1029
- }
1030
- if (/[A-Za-z]/.test(first)) {
1031
- const start = this.position;
1032
- while (this.position < this.source.length && /[A-Za-z]/.test(this.source[this.position] ?? "")) {
1033
- this.position++;
1034
- }
1035
- command = this.source.slice(start, this.position);
1036
- } else {
1037
- command = first;
1038
- this.position++;
1039
- }
1040
-
1041
- if (command === "\\") {
1042
- return "\n";
1043
- }
1044
- if (SPACING_COMMANDS.has(command)) {
1045
- return " ";
1046
- }
1047
- if (NEGATIVE_SPACING_COMMANDS.has(command)) {
1048
- return NEGATIVE_SPACE;
1049
- }
1050
- if (FONT_SWITCH_COMMANDS.has(command)) {
1051
- while (this.position < this.source.length && /\s/.test(this.source[this.position] ?? "")) {
1052
- this.position++;
1053
- }
1054
- return "";
1055
- }
1056
- if (IGNORED_COMMANDS.has(command)) {
1057
- return "";
1058
- }
1059
- if (
1060
- command === "{" ||
1061
- command === "}" ||
1062
- command === "$" ||
1063
- command === "%" ||
1064
- command === "#" ||
1065
- command === "_" ||
1066
- command === "&"
1067
- ) {
1068
- return command;
1069
- }
1070
- if (command === "|") {
1071
- return "‖";
1072
- }
1073
- if (command === "not") {
1074
- const value = this.parseRequiredArgument(false).trim();
1075
- const negated = NEGATED_SYMBOLS[value];
1076
- if (negated !== undefined) {
1077
- return ` ${negated} `;
1078
- }
1079
- const characters = Array.from(value);
1080
- if (characters.length === 0) {
1081
- this.supported = false;
1082
- return "";
1083
- }
1084
- return ` ${characters[0]}\u0338${characters.slice(1).join("")} `;
1085
- }
1086
- if (LIMIT_OPERATORS.has(command)) {
1087
- return this.parseOperator(command, "bracket", true, true);
1088
- }
1089
-
1090
- const symbol = SYMBOLS[command];
1091
- if (symbol !== undefined) {
1092
- if (DISPLAY_LIMIT_SYMBOLS.has(command)) {
1093
- return this.parseOperator(symbol, "script", true);
1094
- }
1095
- return command === "cdot" || command === "times" || RELATION_COMMANDS.has(command) ? ` ${symbol} ` : symbol;
1096
- }
1097
- if (NAMED_OPERATORS.has(command)) {
1098
- return `${NAMED_OPERATOR_START}${command}${NAMED_OPERATOR_END}`;
1099
- }
1100
- if (SIZE_COMMANDS.has(command)) {
1101
- return "";
1102
- }
1103
- if (command === "left" || command === "middle" || command === "right") {
1104
- if (this.source[this.position] === ".") {
1105
- this.position++;
1106
- }
1107
- return "";
1108
- }
1109
- if (command === "frac" || command === "dfrac" || command === "tfrac") {
1110
- const shouldStack = this.display && this.stackFractions && command !== "tfrac";
1111
- const numerator = this.parseRequiredArgument(!shouldStack);
1112
- const denominator = this.parseRequiredArgument(!shouldStack);
1113
- if (shouldStack) {
1114
- const index =
1115
- this.layoutNodes.push({
1116
- type: "fraction",
1117
- numerator: normalizeOutput(numerator),
1118
- denominator: normalizeOutput(denominator),
1119
- }) - 1;
1120
- return `${LAYOUT_MARKER_START}${index}${LAYOUT_MARKER_END}`;
1121
- }
1122
- return formatFraction(numerator, denominator);
1123
- }
1124
- if (command === "sqrt") {
1125
- const degree = this.parseOptionalArgument()?.trim();
1126
- const value = this.parseRequiredArgument();
1127
- if (degree === undefined || degree === "2") {
1128
- return formatRoot(value);
1129
- }
1130
- if (degree === "3") {
1131
- return formatRoot(value, "∛");
1132
- }
1133
- if (degree === "4") {
1134
- return formatRoot(value, "∜");
1135
- }
1136
- return `${formatScript(degree, "sup")}${formatRoot(value)}`;
1137
- }
1138
- if (command === "boxed" || command === "fbox") {
1139
- return `[${this.parseRequiredArgument().trim()}]`;
1140
- }
1141
- if (command === "binom" || command === "dbinom" || command === "tbinom") {
1142
- return `(${this.parseRequiredArgument()} choose ${this.parseRequiredArgument()})`;
1143
- }
1144
- const accent = ACCENTS[command];
1145
- if (accent !== undefined) {
1146
- const value = this.parseRequiredArgument();
1147
- return Array.from(value).length === 1 ? `${value}${accent}` : `${command}(${value})`;
1148
- }
1149
- if (command === "mathbb") {
1150
- const value = this.parseRequiredArgument();
1151
- return Array.from(value, (character) => BLACKBOARD[character] ?? character).join("");
1152
- }
1153
- if (command === "operatorname") {
1154
- const starred = this.source[this.position] === "*";
1155
- if (starred) {
1156
- this.position++;
1157
- }
1158
- const operator = normalizeOutput(this.parseRequiredArgument()).trim();
1159
- return this.parseOperator(operator, "bracket", starred, true);
1160
- }
1161
- if (command === "mod" || command === "bmod") {
1162
- return " mod ";
1163
- }
1164
- if (command === "pmod" || command === "pod") {
1165
- const value = this.parseRequiredArgument().trim();
1166
- return command === "pmod" ? ` (mod ${value})` : ` (${value})`;
1167
- }
1168
- if (command === "overset" || command === "stackrel") {
1169
- const upper = this.parseRequiredArgument();
1170
- const value = this.parseRequiredArgument().trim();
1171
- return `${value}${formatScript(upper, "sup")}`;
1172
- }
1173
- if (command === "underset") {
1174
- const lower = this.parseRequiredArgument();
1175
- const value = this.parseRequiredArgument().trim();
1176
- return `${value}${formatScript(lower, "sub")}`;
1177
- }
1178
- if (PLAIN_WRAPPERS.has(command)) {
1179
- const value = this.parseRequiredArgument();
1180
- return command.startsWith("text") || command === "mbox" ? value : value.trim();
1181
- }
1182
- if (command === "begin") {
1183
- return this.parseEnvironment();
1184
- }
1185
- if (command === "end") {
1186
- this.supported = false;
1187
- return "";
1188
- }
1189
-
1190
- this.supported = false;
1191
- return `\\${command}`;
1192
- }
1193
-
1194
- private parseOperator(
1195
- operator: string,
1196
- inlineLowerStyle: "bracket" | "script",
1197
- displayLimits: boolean,
1198
- spaced = false,
1199
- ): string {
1200
- let useDisplayLimits = displayLimits;
1201
- let modifierPosition = this.position;
1202
- while (modifierPosition < this.source.length && /[ \t]/.test(this.source[modifierPosition] ?? "")) {
1203
- modifierPosition++;
1204
- }
1205
- const modifier = /^\\(limits|nolimits)(?![A-Za-z])/.exec(this.source.slice(modifierPosition));
1206
- if (modifier) {
1207
- useDisplayLimits = modifier[1] === "limits";
1208
- this.position = modifierPosition + modifier[0].length;
1209
- }
1210
-
1211
- let lower: string | undefined;
1212
- let upper: string | undefined;
1213
- while (true) {
1214
- let scriptPosition = this.position;
1215
- while (scriptPosition < this.source.length && /[ \t]/.test(this.source[scriptPosition] ?? "")) {
1216
- scriptPosition++;
1217
- }
1218
- const kind = this.source[scriptPosition];
1219
- if (kind !== "_" && kind !== "^") {
1220
- break;
1221
- }
1222
- this.position = scriptPosition + 1;
1223
- const value = normalizeOutput(this.parseRequiredArgument(false)).replaceAll(" ", "");
1224
- if (kind === "_") {
1225
- if (lower !== undefined) {
1226
- this.supported = false;
1227
- }
1228
- lower = value;
1229
- } else {
1230
- if (upper !== undefined) {
1231
- this.supported = false;
1232
- }
1233
- upper = value;
1234
- }
1235
- }
1236
-
1237
- if (this.display && useDisplayLimits && (lower !== undefined || upper !== undefined)) {
1238
- const index = this.layoutNodes.push({ type: "operator", operator, lower, upper }) - 1;
1239
- return `${LAYOUT_MARKER_START}${index}${LAYOUT_MARKER_END}`;
1240
- }
1241
-
1242
- let rendered = operator;
1243
- if (lower !== undefined) {
1244
- rendered += inlineLowerStyle === "bracket" ? `[${lower}]` : formatScript(lower, "sub");
1245
- }
1246
- if (upper !== undefined) {
1247
- rendered += formatScript(upper, "sup");
1248
- }
1249
- return spaced ? ` ${rendered} ` : rendered;
1250
- }
1251
-
1252
- private parseRequiredArgument(stackFractions = true): string {
1253
- const previousStackFractions = this.stackFractions;
1254
- this.stackFractions = previousStackFractions && stackFractions;
1255
- const value = this.parseRequiredArgumentValue();
1256
- this.stackFractions = previousStackFractions;
1257
- return value;
1258
- }
1259
-
1260
- private parseRequiredArgumentValue(): string {
1261
- while (this.position < this.source.length && /\s/.test(this.source[this.position] ?? "")) {
1262
- this.position++;
1263
- }
1264
- if (this.position >= this.source.length) {
1265
- this.supported = false;
1266
- return "";
1267
- }
1268
- if (this.source[this.position] === "{") {
1269
- this.position++;
1270
- return this.parseSequence("}");
1271
- }
1272
- if (this.source[this.position] === "\\") {
1273
- return this.parseCommand();
1274
- }
1275
- const value = this.source[this.position] ?? "";
1276
- this.position++;
1277
- return value;
1278
- }
1279
-
1280
- private parseOptionalArgument(): string | undefined {
1281
- while (this.position < this.source.length && /[ \t]/.test(this.source[this.position] ?? "")) {
1282
- this.position++;
1283
- }
1284
- if (this.source[this.position] !== "[") {
1285
- return undefined;
1286
- }
1287
- const end = this.source.indexOf("]", this.position + 1);
1288
- if (end < 0) {
1289
- this.supported = false;
1290
- return undefined;
1291
- }
1292
- const value = this.source.slice(this.position + 1, end);
1293
- this.position = end + 1;
1294
- return this.renderNested(value);
1295
- }
1296
-
1297
- private readRawGroup(): string | undefined {
1298
- while (this.position < this.source.length && /[ \t]/.test(this.source[this.position] ?? "")) {
1299
- this.position++;
1300
- }
1301
- if (this.source[this.position] !== "{") {
1302
- this.supported = false;
1303
- return undefined;
1304
- }
1305
-
1306
- const start = ++this.position;
1307
- let depth = 1;
1308
- while (this.position < this.source.length) {
1309
- const character = this.source[this.position];
1310
- if (character === "\\") {
1311
- this.position += 2;
1312
- continue;
1313
- }
1314
- if (character === "{") depth++;
1315
- if (character === "}") depth--;
1316
- if (depth === 0) {
1317
- const value = this.source.slice(start, this.position);
1318
- this.position++;
1319
- return value;
1320
- }
1321
- this.position++;
1322
- }
1323
- this.supported = false;
1324
- return undefined;
1325
- }
1326
-
1327
- private splitEnvironmentRows(body: string): string[] {
1328
- return body.split(/\\\\(?:\[[^\]\n]*\])?/);
1329
- }
1330
-
1331
- private parseEnvironment(): string {
1332
- const environment = this.readRawGroup();
1333
- if (!environment) {
1334
- return "";
1335
- }
1336
- const endMarker = `\\end{${environment}}`;
1337
- const end = this.source.indexOf(endMarker, this.position);
1338
- if (end < 0) {
1339
- this.supported = false;
1340
- return "";
1341
- }
1342
- const body = this.source.slice(this.position, end);
1343
- this.position = end + endMarker.length;
1344
-
1345
- if (environment === "equation" || environment === "equation*" || environment === "displaymath") {
1346
- return this.renderNested(body).trim();
1347
- }
1348
-
1349
- if (
1350
- environment === "aligned" ||
1351
- environment === "align" ||
1352
- environment === "align*" ||
1353
- environment === "alignedat" ||
1354
- environment === "alignat" ||
1355
- environment === "alignat*" ||
1356
- environment === "gather" ||
1357
- environment === "gathered" ||
1358
- environment === "multline" ||
1359
- environment === "multline*" ||
1360
- environment === "split"
1361
- ) {
1362
- const alignedAt = ["alignedat", "alignat", "alignat*"].includes(environment);
1363
- const alignedBody = alignedAt ? body.replace(/^\s*\{[^}]*\}/, "") : body;
1364
- return this.splitEnvironmentRows(alignedBody)
1365
- .map((row) => {
1366
- const cells = row.split("&");
1367
- const source = alignedAt
1368
- ? Array.from({ length: Math.ceil(cells.length / 2) }, (_, index) =>
1369
- cells.slice(index * 2, index * 2 + 2).join(""),
1370
- ).join(" ")
1371
- : cells.join("");
1372
- return this.renderNested(source).trim();
1373
- })
1374
- .filter(Boolean)
1375
- .join("\n");
1376
- }
1377
-
1378
- if (environment === "cases" || environment === "cases*") {
1379
- return this.renderCases(body);
1380
- }
1381
-
1382
- if (
1383
- ["array", "matrix", "smallmatrix", "pmatrix", "bmatrix", "Bmatrix", "vmatrix", "Vmatrix"].includes(environment)
1384
- ) {
1385
- const matrixBody = environment === "array" ? body.replace(/^\s*\{[^}]*\}/, "") : body;
1386
- return this.renderMatrix(environment, matrixBody);
1387
- }
1388
-
1389
- this.supported = false;
1390
- return body;
1391
- }
1392
-
1393
- private renderCases(body: string): string {
1394
- const rows = this.splitEnvironmentRows(body)
1395
- .map((row) => row.split("&").map((cell) => this.renderNested(cell, false).trim()))
1396
- .filter((row) => row.some(Boolean));
1397
- const valueWidth = Math.max(0, ...rows.map((row) => visibleWidth((row[0] ?? "").replace(/,\s*$/, ""))));
1398
- const contents = rows.map((row) => {
1399
- const value = (row[0] ?? "").replace(/,\s*$/, "");
1400
- const condition = row[1] ?? "";
1401
- if (!condition) {
1402
- return value;
1403
- }
1404
- const conditionPrefix = /^(?:if|when|for|otherwise)\b/i.test(condition) ? " " : " if ";
1405
- return `${value}${PROTECTED_SPACE.repeat(valueWidth - visibleWidth(value))}${conditionPrefix}${condition}`;
1406
- });
1407
- if (contents.length <= 1) {
1408
- return contents.length === 0 ? "" : `⎧ ${contents[0]}`;
1409
- }
1410
-
1411
- const middle = Math.floor(contents.length / 2);
1412
- const visualRows: Array<string | undefined> = [...contents];
1413
- if (contents.length % 2 === 0) {
1414
- visualRows.splice(middle, 0, undefined);
1415
- }
1416
- const lines = visualRows.map((content, index) => {
1417
- const delimiter = index === 0 ? "⎧" : index === visualRows.length - 1 ? "⎩" : "⎨";
1418
- return content === undefined ? delimiter : `${delimiter} ${content}`;
1419
- });
1420
- const index = this.layoutNodes.push({ type: "matrix", lines, baseline: middle }) - 1;
1421
- return `${LAYOUT_MARKER_START}${index}${LAYOUT_MARKER_END}`;
1422
- }
1423
-
1424
- private renderMatrix(environment: string, body: string): string {
1425
- const matrix = this.splitEnvironmentRows(body)
1426
- .map((row) => row.split("&").map((cell) => this.renderNested(cell, false).trim()))
1427
- .filter((row) => row.some(Boolean));
1428
- const columnCount = Math.max(0, ...matrix.map((row) => row.length));
1429
- const columnWidths = Array.from({ length: columnCount }, (_, column) =>
1430
- Math.max(0, ...matrix.map((row) => visibleWidth(row[column] ?? ""))),
1431
- );
1432
- const rows = matrix.map((row) =>
1433
- Array.from({ length: columnCount }, (_, column) => {
1434
- const cell = row[column] ?? "";
1435
- return `${cell}${PROTECTED_SPACE.repeat(Math.max(0, (columnWidths[column] ?? 0) - visibleWidth(cell)))}`;
1436
- }).join(" │ "),
1437
- );
1438
-
1439
- let lines: string[];
1440
- if (environment === "array" || environment === "matrix" || environment === "smallmatrix") {
1441
- lines = rows;
1442
- } else {
1443
- const delimiters: Readonly<Record<string, readonly [string, string, string, string, string, string]>> = {
1444
- pmatrix: ["⎛", "⎞", "⎜", "⎟", "⎝", "⎠"],
1445
- bmatrix: ["⎡", "⎤", "⎢", "⎥", "⎣", "⎦"],
1446
- Bmatrix: ["⎧", "⎫", "⎨", "⎬", "⎩", "⎭"],
1447
- vmatrix: ["│", "│", "│", "│", "│", "│"],
1448
- Vmatrix: ["║", "║", "║", "║", "║", "║"],
1449
- };
1450
- const delimiter = delimiters[environment];
1451
- if (!delimiter) {
1452
- this.supported = false;
1453
- return rows.join("\n");
1454
- }
1455
- lines = rows.map((row, index) => {
1456
- const left = index === 0 ? delimiter[0] : index === rows.length - 1 ? delimiter[4] : delimiter[2];
1457
- const right = index === 0 ? delimiter[1] : index === rows.length - 1 ? delimiter[5] : delimiter[3];
1458
- return `${left} ${row} ${right}`;
1459
- });
1460
- }
1461
-
1462
- if (lines.length <= 1) {
1463
- return lines[0] ?? "";
1464
- }
1465
- const index = this.layoutNodes.push({ type: "matrix", lines, baseline: 0 }) - 1;
1466
- return `${LAYOUT_MARKER_START}${index}${LAYOUT_MARKER_END}`;
1467
- }
1468
-
1469
- private renderNested(source: string, stackFractions = true): string {
1470
- const rendered = new LatexParser(source, this.layoutNodes, this.display && stackFractions).render();
1471
- if (rendered === undefined) {
1472
- this.supported = false;
1473
- return source;
1474
- }
1475
- return rendered;
1476
- }
1477
- }
1478
-
1479
- export interface RenderLatexOptions {
1480
- /** Stack fractions and operator limits vertically for display math (default: false). */
1481
- display?: boolean;
1482
- }
1483
-
1484
- /**
1485
- * Render a basic LaTeX math expression as terminal-friendly Unicode text.
1486
- * Returns undefined when the expression contains unsupported or malformed syntax.
1487
- */
1488
- export function renderLatex(source: string, options: RenderLatexOptions = {}): string | undefined {
1489
- const layoutNodes: LayoutNode[] = [];
1490
- const rendered = new LatexParser(source, layoutNodes, options.display === true).render();
1491
- if (rendered === undefined) {
1492
- return undefined;
1493
- }
1494
- if (layoutNodes.length === 0) {
1495
- return rendered.replaceAll(PROTECTED_SPACE, " ");
1496
- }
1497
- const lines = renderLayout(rendered, layoutNodes).lines;
1498
- const indentation = Math.min(
1499
- ...lines.filter((line) => line.trim()).map((line) => line.length - line.trimStart().length),
1500
- );
1501
- return lines
1502
- .map((line) => line.slice(indentation).trimEnd())
1503
- .join("\n")
1504
- .trimEnd()
1505
- .replaceAll(PROTECTED_SPACE, " ");
1506
- }