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