@visual-z/xeno 0.2.0 → 0.3.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 (259) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +106 -26
  3. package/docs/install.md +52 -9
  4. package/docs/providers.md +11 -0
  5. package/package.json +5 -4
  6. package/src/apps/approve.ts +3 -3
  7. package/src/apps/cli.ts +10 -11
  8. package/src/apps/commands.ts +38 -5
  9. package/src/apps/completions.ts +73 -19
  10. package/src/apps/image-draft.ts +46 -0
  11. package/src/apps/project-trust.ts +2 -42
  12. package/src/apps/provider-connect.ts +346 -0
  13. package/src/apps/render.ts +22 -47
  14. package/src/apps/resources.ts +5 -2
  15. package/src/apps/shell.ts +298 -1526
  16. package/src/apps/startup-input.ts +130 -0
  17. package/src/apps/startup.ts +143 -115
  18. package/src/apps/ui/app.ts +1383 -0
  19. package/src/apps/ui/extension-host.ts +194 -0
  20. package/src/apps/ui/image-cache.ts +78 -0
  21. package/src/apps/ui/palette.ts +88 -0
  22. package/src/apps/ui/project.ts +127 -0
  23. package/src/apps/ui/standalone.ts +146 -0
  24. package/src/apps/ui/status-model.ts +68 -0
  25. package/src/apps/ui/surface.ts +84 -0
  26. package/src/apps/ui/theme.ts +86 -0
  27. package/src/core/acceptance.ts +107 -0
  28. package/src/core/action-defaults.ts +47 -295
  29. package/src/core/benchmark.ts +2 -2
  30. package/src/core/branch-receipt.ts +24 -0
  31. package/src/core/checkpoints.ts +64 -0
  32. package/src/core/churn.ts +2 -0
  33. package/src/core/command-artifacts.ts +94 -0
  34. package/src/core/command-contract.ts +35 -0
  35. package/src/core/completion-review.ts +21 -5
  36. package/src/core/context-detail.ts +37 -0
  37. package/src/core/context-policy.ts +74 -1
  38. package/src/core/continuation.ts +17 -2
  39. package/src/core/derive.ts +29 -3
  40. package/src/core/draft-images.ts +56 -0
  41. package/src/core/environment.ts +145 -0
  42. package/src/core/evaluation-coverage.ts +74 -0
  43. package/src/core/event-validation.ts +80 -5
  44. package/src/core/events.ts +97 -3
  45. package/src/core/evidence-links.ts +7 -0
  46. package/src/core/evidence.ts +13 -3
  47. package/src/core/execution.ts +8 -2
  48. package/src/core/experiment.ts +133 -0
  49. package/src/core/export.ts +9 -0
  50. package/src/core/failure-recovery.ts +173 -0
  51. package/src/core/file-precondition.ts +72 -0
  52. package/src/core/handoff.ts +65 -4
  53. package/src/core/history-query.ts +38 -2
  54. package/src/core/history.ts +38 -1
  55. package/src/core/integration-health.ts +34 -0
  56. package/src/core/keymap.ts +8 -20
  57. package/src/core/model-picker.ts +64 -0
  58. package/src/core/native-eval.ts +25 -0
  59. package/src/core/note-guidance.ts +34 -0
  60. package/src/core/observation-view.ts +49 -8
  61. package/src/core/permissions.ts +24 -2
  62. package/src/core/process-receipt.ts +105 -0
  63. package/src/core/process-state.ts +37 -0
  64. package/src/core/project.ts +45 -18
  65. package/src/core/prompt-policy.ts +37 -0
  66. package/src/core/query-eval-contract.ts +14 -0
  67. package/src/core/read-observation.ts +82 -0
  68. package/src/core/recovery-corpus.ts +17 -0
  69. package/src/core/recovery-evaluation.ts +60 -0
  70. package/src/core/recovery-sources.ts +74 -0
  71. package/src/core/runtime-target.ts +94 -0
  72. package/src/core/search-observation.ts +97 -0
  73. package/src/core/session-tree.ts +147 -0
  74. package/src/core/settings.ts +26 -10
  75. package/src/core/skills.ts +44 -0
  76. package/src/core/sql-query.ts +211 -0
  77. package/src/core/theme.ts +36 -58
  78. package/src/core/tool-contract.ts +51 -2
  79. package/src/core/tool-discovery.ts +87 -0
  80. package/src/core/tool-failure.ts +60 -0
  81. package/src/core/tool-mode-protocol.ts +25 -0
  82. package/src/core/transcript.ts +19 -4
  83. package/src/core/trial-lifecycle.ts +14 -0
  84. package/src/core/tui-mode.ts +29 -0
  85. package/src/core/update-check.ts +97 -0
  86. package/src/core/version.ts +55 -0
  87. package/src/core/work-state.ts +49 -3
  88. package/src/core/working-set.ts +91 -0
  89. package/src/runtime/auth.ts +2 -2
  90. package/src/runtime/branch-jobs.ts +36 -0
  91. package/src/runtime/clipboard.ts +15 -4
  92. package/src/runtime/command-artifacts.ts +64 -0
  93. package/src/runtime/environment-tools.ts +60 -0
  94. package/src/runtime/evidence-notes.ts +4 -2
  95. package/src/runtime/exec-sessions.ts +206 -61
  96. package/src/runtime/execution-control.ts +1 -0
  97. package/src/runtime/extension-ui.ts +22 -30
  98. package/src/runtime/extensions.ts +95 -16
  99. package/src/runtime/harness-comparison.ts +27 -7
  100. package/src/runtime/harness-eval.ts +460 -0
  101. package/src/runtime/history-cache.ts +57 -0
  102. package/src/runtime/history-tools.ts +62 -12
  103. package/src/runtime/integrations.ts +210 -0
  104. package/src/runtime/loop.ts +1021 -268
  105. package/src/runtime/model-discovery.ts +208 -0
  106. package/src/runtime/native-eval-fixtures.ts +33 -0
  107. package/src/runtime/native-eval.ts +143 -0
  108. package/src/runtime/oauth.ts +1 -1
  109. package/src/runtime/provider-connect.ts +109 -0
  110. package/src/runtime/provider-presets.ts +87 -0
  111. package/src/runtime/query-eval-verifier.ts +28 -0
  112. package/src/runtime/recovery-coverage.ts +71 -0
  113. package/src/runtime/recovery-evidence.ts +76 -0
  114. package/src/runtime/recovery-fixtures.ts +116 -0
  115. package/src/runtime/runtime-target.ts +149 -0
  116. package/src/runtime/tool-broker.ts +137 -0
  117. package/src/runtime/tool-discovery.ts +57 -0
  118. package/src/runtime/tool-mode-eval.ts +146 -0
  119. package/src/runtime/tool-queue.ts +77 -0
  120. package/src/runtime/tools.ts +628 -183
  121. package/src/runtime/trial-control.ts +45 -0
  122. package/src/runtime/update-check.ts +47 -0
  123. package/src/runtime/window-trial.ts +2 -1
  124. package/src/runtime/working-notes.ts +33 -3
  125. package/src/runtime/workspace-lifecycle.ts +97 -0
  126. package/src/store/acceptance.ts +15 -0
  127. package/src/store/checkpoint-history.ts +43 -0
  128. package/src/store/command-artifacts.ts +96 -0
  129. package/src/store/command.ts +65 -37
  130. package/src/store/file-checkpoints.ts +104 -0
  131. package/src/store/file-index.ts +13 -1
  132. package/src/store/git-checkpoints.ts +67 -0
  133. package/src/store/observations.ts +61 -20
  134. package/src/store/process-receipts.ts +34 -0
  135. package/src/store/query-source.ts +13 -8
  136. package/src/store/recovery.ts +2 -3
  137. package/src/store/replay.ts +16 -4
  138. package/src/store/runtime-target.ts +64 -0
  139. package/src/store/search.ts +171 -132
  140. package/src/store/session-archive.ts +4 -0
  141. package/src/store/source-cancellation.ts +6 -0
  142. package/src/store/update-cache.ts +36 -0
  143. package/src/store/workspace-observation.ts +113 -0
  144. package/src/tui/ansi-spans.ts +126 -0
  145. package/src/tui/style.ts +19 -16
  146. package/src/tui/ui/atomic-draft.ts +121 -0
  147. package/src/tui/ui/canvas.ts +96 -0
  148. package/src/tui/ui/colors.ts +70 -0
  149. package/src/tui/ui/composer.ts +199 -0
  150. package/src/tui/ui/dialog.ts +379 -0
  151. package/src/tui/ui/draft.ts +23 -0
  152. package/src/tui/ui/edge.ts +186 -0
  153. package/src/tui/ui/image-size.ts +40 -0
  154. package/src/tui/ui/ink.ts +210 -0
  155. package/src/tui/ui/items.ts +40 -0
  156. package/src/tui/ui/keys.ts +40 -0
  157. package/src/tui/ui/layout.ts +74 -0
  158. package/src/tui/ui/list-model.ts +86 -0
  159. package/src/tui/ui/list-view.ts +172 -0
  160. package/src/tui/ui/mascot.ts +67 -0
  161. package/src/tui/ui/messages.ts +518 -0
  162. package/src/tui/ui/motion.ts +28 -0
  163. package/src/tui/ui/paths.ts +10 -0
  164. package/src/tui/ui/pictures.ts +78 -0
  165. package/src/tui/ui/screen.ts +421 -0
  166. package/src/tui/ui/status.ts +271 -0
  167. package/src/tui/ui/styled.ts +85 -0
  168. package/src/tui/ui/testing.ts +5 -0
  169. package/src/tui/ui/tool-card.ts +150 -0
  170. package/src/apps/extension-ui.ts +0 -330
  171. package/src/apps/resource-browser.ts +0 -75
  172. package/src/apps/session-browser.ts +0 -113
  173. package/src/apps/session-picker.ts +0 -55
  174. package/src/tui/ansi.ts +0 -67
  175. package/src/tui/autocomplete.ts +0 -29
  176. package/src/tui/chat-viewport.ts +0 -47
  177. package/src/tui/complete.ts +0 -87
  178. package/src/tui/editor.ts +0 -940
  179. package/src/tui/host.ts +0 -264
  180. package/src/tui/input.ts +0 -75
  181. package/src/tui/keys.ts +0 -378
  182. package/src/tui/markdown.ts +0 -17
  183. package/src/tui/render.ts +0 -153
  184. package/src/tui/select.ts +0 -258
  185. package/src/tui/settings.ts +0 -44
  186. package/src/tui/statusline.ts +0 -162
  187. package/src/tui/system-theme.ts +0 -700
  188. package/src/tui/terminal.ts +0 -30
  189. package/src/tui/text.ts +0 -52
  190. package/src/tui/toolview.ts +0 -386
  191. package/src/tui/transcript.ts +0 -123
  192. package/src/tui/tree.ts +0 -471
  193. package/src/vendor/pi-tui/LICENSE.upstream +0 -21
  194. package/src/vendor/pi-tui/UPSTREAM.json +0 -50
  195. package/src/vendor/pi-tui/VENDOR.md +0 -25
  196. package/src/vendor/pi-tui/alt-screen-search.ts +0 -327
  197. package/src/vendor/pi-tui/autocomplete.ts +0 -860
  198. package/src/vendor/pi-tui/colors.ts +0 -367
  199. package/src/vendor/pi-tui/components/alt-screen-flash.ts +0 -51
  200. package/src/vendor/pi-tui/components/box.ts +0 -166
  201. package/src/vendor/pi-tui/components/cancellable-loader.ts +0 -40
  202. package/src/vendor/pi-tui/components/editor.ts +0 -2472
  203. package/src/vendor/pi-tui/components/h-stack.ts +0 -44
  204. package/src/vendor/pi-tui/components/image.ts +0 -127
  205. package/src/vendor/pi-tui/components/input.ts +0 -494
  206. package/src/vendor/pi-tui/components/loader.ts +0 -101
  207. package/src/vendor/pi-tui/components/markdown.ts +0 -1021
  208. package/src/vendor/pi-tui/components/mouse-region.ts +0 -33
  209. package/src/vendor/pi-tui/components/scroll-view.ts +0 -224
  210. package/src/vendor/pi-tui/components/select-list.ts +0 -273
  211. package/src/vendor/pi-tui/components/settings-list.ts +0 -328
  212. package/src/vendor/pi-tui/components/spacer.ts +0 -28
  213. package/src/vendor/pi-tui/components/stack.ts +0 -154
  214. package/src/vendor/pi-tui/components/text.ts +0 -107
  215. package/src/vendor/pi-tui/components/truncated-text.ts +0 -65
  216. package/src/vendor/pi-tui/components/v-stack.ts +0 -33
  217. package/src/vendor/pi-tui/editor-component.ts +0 -74
  218. package/src/vendor/pi-tui/fuzzy.ts +0 -138
  219. package/src/vendor/pi-tui/index.ts +0 -186
  220. package/src/vendor/pi-tui/keybindings.ts +0 -320
  221. package/src/vendor/pi-tui/keys.ts +0 -1401
  222. package/src/vendor/pi-tui/kill-ring.ts +0 -46
  223. package/src/vendor/pi-tui/latex.ts +0 -1506
  224. package/src/vendor/pi-tui/layout-node.ts +0 -51
  225. package/src/vendor/pi-tui/layout.ts +0 -449
  226. package/src/vendor/pi-tui/native/UPSTREAM.json +0 -23
  227. package/src/vendor/pi-tui/native/clipboard.h +0 -208
  228. package/src/vendor/pi-tui/native/darwin/README.md +0 -22
  229. package/src/vendor/pi-tui/native/darwin/build.sh +0 -69
  230. package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
  231. package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
  232. package/src/vendor/pi-tui/native/darwin/src/darwin-platform.m +0 -132
  233. package/src/vendor/pi-tui/native/linux/README.md +0 -23
  234. package/src/vendor/pi-tui/native/linux/build.sh +0 -65
  235. package/src/vendor/pi-tui/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
  236. package/src/vendor/pi-tui/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
  237. package/src/vendor/pi-tui/native/linux/src/clipboard-worker.h +0 -70
  238. package/src/vendor/pi-tui/native/linux/src/linux-platform-x11.c +0 -274
  239. package/src/vendor/pi-tui/native/napi.h +0 -91
  240. package/src/vendor/pi-tui/native/win32/README.md +0 -31
  241. package/src/vendor/pi-tui/native/win32/build.mjs +0 -232
  242. package/src/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
  243. package/src/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
  244. package/src/vendor/pi-tui/native/win32/src/win32-platform.c +0 -241
  245. package/src/vendor/pi-tui/native-modifiers.ts +0 -13
  246. package/src/vendor/pi-tui/native-module-path.ts +0 -31
  247. package/src/vendor/pi-tui/native-platform.ts +0 -65
  248. package/src/vendor/pi-tui/oklab.ts +0 -233
  249. package/src/vendor/pi-tui/stdin-buffer.ts +0 -444
  250. package/src/vendor/pi-tui/terminal-colors.ts +0 -91
  251. package/src/vendor/pi-tui/terminal-image.ts +0 -731
  252. package/src/vendor/pi-tui/terminal.ts +0 -554
  253. package/src/vendor/pi-tui/tui-alt-screen.ts +0 -1750
  254. package/src/vendor/pi-tui/tui-main-screen.ts +0 -655
  255. package/src/vendor/pi-tui/tui.ts +0 -1493
  256. package/src/vendor/pi-tui/undo-stack.ts +0 -28
  257. package/src/vendor/pi-tui/utils.ts +0 -1385
  258. package/src/vendor/pi-tui/wheel-scroll.ts +0 -82
  259. 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
- }