@earendil-works/pi-tui 0.84.0 → 0.84.1
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.
- package/dist/keybindings.d.ts +10 -0
- package/dist/keybindings.d.ts.map +1 -1
- package/dist/keybindings.js +8 -0
- package/dist/keybindings.js.map +1 -1
- package/dist/latex.d.ts.map +1 -1
- package/dist/latex.js +188 -48
- package/dist/latex.js.map +1 -1
- package/dist/tui-alt-screen.d.ts +12 -0
- package/dist/tui-alt-screen.d.ts.map +1 -1
- package/dist/tui-alt-screen.js +146 -12
- package/dist/tui-alt-screen.js.map +1 -1
- package/package.json +1 -1
package/dist/latex.js
CHANGED
|
@@ -283,6 +283,89 @@ const DISPLAY_LIMIT_SYMBOLS = new Set([
|
|
|
283
283
|
"prod",
|
|
284
284
|
"sum",
|
|
285
285
|
]);
|
|
286
|
+
const RELATION_COMMANDS = new Set([
|
|
287
|
+
"Leftarrow",
|
|
288
|
+
"Leftrightarrow",
|
|
289
|
+
"Longleftarrow",
|
|
290
|
+
"Longleftrightarrow",
|
|
291
|
+
"Longrightarrow",
|
|
292
|
+
"Rightarrow",
|
|
293
|
+
"Vdash",
|
|
294
|
+
"Vvdash",
|
|
295
|
+
"approx",
|
|
296
|
+
"asymp",
|
|
297
|
+
"cong",
|
|
298
|
+
"dashv",
|
|
299
|
+
"doteq",
|
|
300
|
+
"downarrow",
|
|
301
|
+
"equiv",
|
|
302
|
+
"ge",
|
|
303
|
+
"geq",
|
|
304
|
+
"geqslant",
|
|
305
|
+
"gets",
|
|
306
|
+
"gg",
|
|
307
|
+
"hookleftarrow",
|
|
308
|
+
"hookrightarrow",
|
|
309
|
+
"iff",
|
|
310
|
+
"implies",
|
|
311
|
+
"in",
|
|
312
|
+
"leadsto",
|
|
313
|
+
"le",
|
|
314
|
+
"leftarrow",
|
|
315
|
+
"leftharpoondown",
|
|
316
|
+
"leftharpoonup",
|
|
317
|
+
"leftrightarrow",
|
|
318
|
+
"leftrightharpoons",
|
|
319
|
+
"leq",
|
|
320
|
+
"leqslant",
|
|
321
|
+
"ll",
|
|
322
|
+
"longleftarrow",
|
|
323
|
+
"longleftrightarrow",
|
|
324
|
+
"longmapsto",
|
|
325
|
+
"longrightarrow",
|
|
326
|
+
"mapsto",
|
|
327
|
+
"mid",
|
|
328
|
+
"models",
|
|
329
|
+
"ne",
|
|
330
|
+
"nearrow",
|
|
331
|
+
"neq",
|
|
332
|
+
"ni",
|
|
333
|
+
"notin",
|
|
334
|
+
"nvdash",
|
|
335
|
+
"nvDash",
|
|
336
|
+
"nwarrow",
|
|
337
|
+
"parallel",
|
|
338
|
+
"perp",
|
|
339
|
+
"prec",
|
|
340
|
+
"preceq",
|
|
341
|
+
"propto",
|
|
342
|
+
"rightharpoondown",
|
|
343
|
+
"rightharpoonup",
|
|
344
|
+
"rightleftharpoons",
|
|
345
|
+
"rightarrow",
|
|
346
|
+
"rightsquigarrow",
|
|
347
|
+
"searrow",
|
|
348
|
+
"sim",
|
|
349
|
+
"simeq",
|
|
350
|
+
"sqsubset",
|
|
351
|
+
"sqsubseteq",
|
|
352
|
+
"sqsupset",
|
|
353
|
+
"sqsupseteq",
|
|
354
|
+
"subset",
|
|
355
|
+
"subseteq",
|
|
356
|
+
"succ",
|
|
357
|
+
"succeq",
|
|
358
|
+
"supset",
|
|
359
|
+
"supseteq",
|
|
360
|
+
"swarrow",
|
|
361
|
+
"to",
|
|
362
|
+
"triangleleft",
|
|
363
|
+
"triangleright",
|
|
364
|
+
"twoheadleftarrow",
|
|
365
|
+
"twoheadrightarrow",
|
|
366
|
+
"uparrow",
|
|
367
|
+
"vdash",
|
|
368
|
+
]);
|
|
286
369
|
const NEGATED_SYMBOLS = {
|
|
287
370
|
"<": "≮",
|
|
288
371
|
">": "≯",
|
|
@@ -504,7 +587,7 @@ function replaceCharacters(value, replacements) {
|
|
|
504
587
|
function formatScript(value, kind) {
|
|
505
588
|
value = value.trim();
|
|
506
589
|
const replacements = kind === "sub" ? SUBSCRIPTS : SUPERSCRIPTS;
|
|
507
|
-
const unicode = replaceCharacters(value, replacements);
|
|
590
|
+
const unicode = replaceCharacters(value.replace(/\s*([=+-])\s*/g, "$1"), replacements);
|
|
508
591
|
if (unicode !== undefined) {
|
|
509
592
|
return unicode;
|
|
510
593
|
}
|
|
@@ -525,8 +608,16 @@ function formatRoot(value, symbol = "√") {
|
|
|
525
608
|
value = value.trim();
|
|
526
609
|
return /^[\p{L}\p{N}.]+$/u.test(value) ? `${symbol}${value}` : `${symbol}(${value})`;
|
|
527
610
|
}
|
|
611
|
+
const NAMED_OPERATOR_START = "\u{f0004}";
|
|
612
|
+
const NAMED_OPERATOR_END = "\u{f0005}";
|
|
613
|
+
const NAMED_OPERATOR_LEFT_SPACING_PATTERN = /(?<=[\p{L}\p{N})\]}\u{f0001}])\u{f0004}/gu;
|
|
614
|
+
const NAMED_OPERATOR_RIGHT_SPACING_PATTERN = /\u{f0005}(?=[\p{L}\p{N}√\u{f0000}])/gu;
|
|
528
615
|
function normalizeOutput(value) {
|
|
529
616
|
return value
|
|
617
|
+
.replace(NAMED_OPERATOR_LEFT_SPACING_PATTERN, " ")
|
|
618
|
+
.replaceAll(NAMED_OPERATOR_START, "")
|
|
619
|
+
.replace(NAMED_OPERATOR_RIGHT_SPACING_PATTERN, " ")
|
|
620
|
+
.replaceAll(NAMED_OPERATOR_END, "")
|
|
530
621
|
.split("\n")
|
|
531
622
|
.map((line) => line.replace(/[ \t]+/g, " ").trim())
|
|
532
623
|
.filter((line, index, lines) => line.length > 0 || (index > 0 && index < lines.length - 1))
|
|
@@ -536,7 +627,8 @@ function normalizeOutput(value) {
|
|
|
536
627
|
const LAYOUT_MARKER_START = "\u{f0000}";
|
|
537
628
|
const LAYOUT_MARKER_END = "\u{f0001}";
|
|
538
629
|
const LAYOUT_MARKER_PATTERN = /\u{f0000}(\d+)\u{f0001}/gu;
|
|
539
|
-
const
|
|
630
|
+
const TRAILING_LAYOUT_MARKER_PATTERN = /\u{f0000}(\d+)\u{f0001}$/u;
|
|
631
|
+
const PROTECTED_SPACE = "\u{f0002}";
|
|
540
632
|
function padLayoutLine(line, width, centered = false) {
|
|
541
633
|
const padding = Math.max(0, width - visibleWidth(line));
|
|
542
634
|
const left = centered ? Math.floor(padding / 2) : 0;
|
|
@@ -572,18 +664,25 @@ function renderLayout(source, nodes) {
|
|
|
572
664
|
for (const sourceLine of source.split("\n")) {
|
|
573
665
|
const layouts = [];
|
|
574
666
|
let position = 0;
|
|
575
|
-
let
|
|
667
|
+
let previousNode;
|
|
576
668
|
for (const match of sourceLine.matchAll(LAYOUT_MARKER_PATTERN)) {
|
|
577
669
|
const index = match.index;
|
|
578
|
-
if (index > position) {
|
|
579
|
-
const sliced = sourceLine.slice(position, index);
|
|
580
|
-
const text = (previousWasNode ? sliced.trimStart() : sliced).trimEnd();
|
|
581
|
-
layouts.push({ lines: [text], width: visibleWidth(text), baseline: 0 });
|
|
582
|
-
}
|
|
583
670
|
const node = nodes[Number(match[1])];
|
|
584
671
|
if (!node) {
|
|
585
672
|
continue;
|
|
586
673
|
}
|
|
674
|
+
if (index > position) {
|
|
675
|
+
const sliced = sourceLine.slice(position, index);
|
|
676
|
+
const trimmed = (previousNode ? sliced.trimStart() : sliced).trimEnd();
|
|
677
|
+
const preserveLeadingSpace = previousNode?.type === "matrix" && /^\s/.test(sliced);
|
|
678
|
+
const preserveTrailingSpace = node.type === "matrix" && /\s$/.test(sliced);
|
|
679
|
+
const text = trimmed
|
|
680
|
+
? `${preserveLeadingSpace ? " " : ""}${trimmed}${preserveTrailingSpace ? " " : ""}`
|
|
681
|
+
: preserveLeadingSpace || preserveTrailingSpace
|
|
682
|
+
? " "
|
|
683
|
+
: "";
|
|
684
|
+
layouts.push({ lines: [text], width: visibleWidth(text), baseline: 0 });
|
|
685
|
+
}
|
|
587
686
|
if (node.type === "fraction") {
|
|
588
687
|
const numerator = renderLayout(node.numerator, nodes);
|
|
589
688
|
const denominator = renderLayout(node.denominator, nodes);
|
|
@@ -599,7 +698,7 @@ function renderLayout(source, nodes) {
|
|
|
599
698
|
baseline: numerator.lines.length,
|
|
600
699
|
});
|
|
601
700
|
}
|
|
602
|
-
else {
|
|
701
|
+
else if (node.type === "operator") {
|
|
603
702
|
const contentWidth = Math.max(visibleWidth(node.operator), node.lower === undefined ? 0 : visibleWidth(node.lower), node.upper === undefined ? 0 : visibleWidth(node.upper));
|
|
604
703
|
const lines = [];
|
|
605
704
|
if (node.upper !== undefined) {
|
|
@@ -615,12 +714,21 @@ function renderLayout(source, nodes) {
|
|
|
615
714
|
baseline: node.upper === undefined ? 0 : 1,
|
|
616
715
|
});
|
|
617
716
|
}
|
|
717
|
+
else {
|
|
718
|
+
const width = Math.max(0, ...node.lines.map((line) => visibleWidth(line)));
|
|
719
|
+
layouts.push({
|
|
720
|
+
lines: node.lines.map((line) => padLayoutLine(line, width)),
|
|
721
|
+
width,
|
|
722
|
+
baseline: node.baseline,
|
|
723
|
+
});
|
|
724
|
+
}
|
|
618
725
|
position = index + match[0].length;
|
|
619
|
-
|
|
726
|
+
previousNode = node;
|
|
620
727
|
}
|
|
621
728
|
if (position < sourceLine.length) {
|
|
622
729
|
const sliced = sourceLine.slice(position);
|
|
623
|
-
const
|
|
730
|
+
const trimmed = previousNode ? sliced.trimStart() : sliced;
|
|
731
|
+
const text = previousNode?.type === "matrix" && /^\s/.test(sliced) ? ` ${trimmed}` : trimmed;
|
|
624
732
|
layouts.push({ lines: [text], width: visibleWidth(text), baseline: 0 });
|
|
625
733
|
}
|
|
626
734
|
const lineLayout = joinLayouts(layouts);
|
|
@@ -638,12 +746,14 @@ function renderLayout(source, nodes) {
|
|
|
638
746
|
class LatexParser {
|
|
639
747
|
source;
|
|
640
748
|
layoutNodes;
|
|
749
|
+
display;
|
|
641
750
|
position = 0;
|
|
642
751
|
supported = true;
|
|
643
752
|
stackFractions = true;
|
|
644
|
-
constructor(source, layoutNodes) {
|
|
753
|
+
constructor(source, layoutNodes, display) {
|
|
645
754
|
this.source = source;
|
|
646
755
|
this.layoutNodes = layoutNodes;
|
|
756
|
+
this.display = display;
|
|
647
757
|
}
|
|
648
758
|
render() {
|
|
649
759
|
const rendered = this.parseSequence();
|
|
@@ -673,6 +783,9 @@ class LatexParser {
|
|
|
673
783
|
const command = this.parseCommand();
|
|
674
784
|
if (command === NEGATIVE_SPACE) {
|
|
675
785
|
result = result.trimEnd();
|
|
786
|
+
if (result.endsWith(NAMED_OPERATOR_END)) {
|
|
787
|
+
result = result.slice(0, -NAMED_OPERATOR_END.length);
|
|
788
|
+
}
|
|
676
789
|
}
|
|
677
790
|
else {
|
|
678
791
|
result += command;
|
|
@@ -682,13 +795,24 @@ class LatexParser {
|
|
|
682
795
|
if (character === "^" || character === "_") {
|
|
683
796
|
this.position++;
|
|
684
797
|
result = result.trimEnd();
|
|
685
|
-
|
|
798
|
+
const script = formatScript(this.parseRequiredArgument(false), character === "_" ? "sub" : "sup");
|
|
799
|
+
if (result.endsWith(NAMED_OPERATOR_END)) {
|
|
800
|
+
result = `${result.slice(0, -NAMED_OPERATOR_END.length)}${script}${NAMED_OPERATOR_END}`;
|
|
801
|
+
}
|
|
802
|
+
else {
|
|
803
|
+
result += script;
|
|
804
|
+
}
|
|
686
805
|
continue;
|
|
687
806
|
}
|
|
688
807
|
if (/\s/.test(character)) {
|
|
689
808
|
result += this.parseWhitespace();
|
|
690
809
|
continue;
|
|
691
810
|
}
|
|
811
|
+
if (character === "=" || character === "<" || character === ">") {
|
|
812
|
+
result = `${result.trimEnd()} ${character} `;
|
|
813
|
+
this.position++;
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
692
816
|
if (character === "&") {
|
|
693
817
|
this.position++;
|
|
694
818
|
continue;
|
|
@@ -698,6 +822,16 @@ class LatexParser {
|
|
|
698
822
|
result += " ";
|
|
699
823
|
continue;
|
|
700
824
|
}
|
|
825
|
+
if (character === ".") {
|
|
826
|
+
const marker = TRAILING_LAYOUT_MARKER_PATTERN.exec(result);
|
|
827
|
+
const node = marker ? this.layoutNodes[Number(marker[1])] : undefined;
|
|
828
|
+
if (node?.type === "matrix") {
|
|
829
|
+
const lastLine = node.lines.length - 1;
|
|
830
|
+
node.lines[lastLine] = `${node.lines[lastLine] ?? ""}${character}`;
|
|
831
|
+
this.position++;
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
701
835
|
result += character;
|
|
702
836
|
this.position++;
|
|
703
837
|
}
|
|
@@ -759,24 +893,27 @@ class LatexParser {
|
|
|
759
893
|
const value = this.parseRequiredArgument(false).trim();
|
|
760
894
|
const negated = NEGATED_SYMBOLS[value];
|
|
761
895
|
if (negated !== undefined) {
|
|
762
|
-
return negated
|
|
896
|
+
return ` ${negated} `;
|
|
763
897
|
}
|
|
764
898
|
const characters = Array.from(value);
|
|
765
899
|
if (characters.length === 0) {
|
|
766
900
|
this.supported = false;
|
|
767
901
|
return "";
|
|
768
902
|
}
|
|
769
|
-
return
|
|
903
|
+
return ` ${characters[0]}\u0338${characters.slice(1).join("")} `;
|
|
770
904
|
}
|
|
771
905
|
if (LIMIT_OPERATORS.has(command)) {
|
|
772
906
|
return this.parseOperator(command, "bracket", true, true);
|
|
773
907
|
}
|
|
774
908
|
const symbol = SYMBOLS[command];
|
|
775
909
|
if (symbol !== undefined) {
|
|
776
|
-
|
|
910
|
+
if (DISPLAY_LIMIT_SYMBOLS.has(command)) {
|
|
911
|
+
return this.parseOperator(symbol, "script", true);
|
|
912
|
+
}
|
|
913
|
+
return command === "cdot" || command === "times" || RELATION_COMMANDS.has(command) ? ` ${symbol} ` : symbol;
|
|
777
914
|
}
|
|
778
915
|
if (NAMED_OPERATORS.has(command)) {
|
|
779
|
-
return
|
|
916
|
+
return `${NAMED_OPERATOR_START}${command}${NAMED_OPERATOR_END}`;
|
|
780
917
|
}
|
|
781
918
|
if (SIZE_COMMANDS.has(command)) {
|
|
782
919
|
return "";
|
|
@@ -788,10 +925,10 @@ class LatexParser {
|
|
|
788
925
|
return "";
|
|
789
926
|
}
|
|
790
927
|
if (command === "frac" || command === "dfrac" || command === "tfrac") {
|
|
791
|
-
const shouldStack = this.
|
|
928
|
+
const shouldStack = this.display && this.stackFractions && command !== "tfrac";
|
|
792
929
|
const numerator = this.parseRequiredArgument(!shouldStack);
|
|
793
930
|
const denominator = this.parseRequiredArgument(!shouldStack);
|
|
794
|
-
if (shouldStack
|
|
931
|
+
if (shouldStack) {
|
|
795
932
|
const index = this.layoutNodes.push({
|
|
796
933
|
type: "fraction",
|
|
797
934
|
numerator: normalizeOutput(numerator),
|
|
@@ -906,7 +1043,7 @@ class LatexParser {
|
|
|
906
1043
|
upper = value;
|
|
907
1044
|
}
|
|
908
1045
|
}
|
|
909
|
-
if (this.
|
|
1046
|
+
if (this.display && useDisplayLimits && (lower !== undefined || upper !== undefined)) {
|
|
910
1047
|
const index = this.layoutNodes.push({ type: "operator", operator, lower, upper }) - 1;
|
|
911
1048
|
return `${LAYOUT_MARKER_START}${index}${LAYOUT_MARKER_END}`;
|
|
912
1049
|
}
|
|
@@ -1065,34 +1202,37 @@ class LatexParser {
|
|
|
1065
1202
|
const cell = row[column] ?? "";
|
|
1066
1203
|
return `${cell}${PROTECTED_SPACE.repeat(Math.max(0, (columnWidths[column] ?? 0) - visibleWidth(cell)))}`;
|
|
1067
1204
|
}).join(" │ "));
|
|
1205
|
+
let lines;
|
|
1068
1206
|
if (environment === "array" || environment === "matrix" || environment === "smallmatrix") {
|
|
1069
|
-
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1207
|
+
lines = rows;
|
|
1208
|
+
}
|
|
1209
|
+
else {
|
|
1210
|
+
const delimiters = {
|
|
1211
|
+
pmatrix: ["⎛", "⎞", "⎜", "⎟", "⎝", "⎠"],
|
|
1212
|
+
bmatrix: ["⎡", "⎤", "⎢", "⎥", "⎣", "⎦"],
|
|
1213
|
+
Bmatrix: ["⎧", "⎫", "⎨", "⎬", "⎩", "⎭"],
|
|
1214
|
+
vmatrix: ["│", "│", "│", "│", "│", "│"],
|
|
1215
|
+
Vmatrix: ["║", "║", "║", "║", "║", "║"],
|
|
1216
|
+
};
|
|
1217
|
+
const delimiter = delimiters[environment];
|
|
1218
|
+
if (!delimiter) {
|
|
1219
|
+
this.supported = false;
|
|
1220
|
+
return rows.join("\n");
|
|
1221
|
+
}
|
|
1222
|
+
lines = rows.map((row, index) => {
|
|
1223
|
+
const left = index === 0 ? delimiter[0] : index === rows.length - 1 ? delimiter[4] : delimiter[2];
|
|
1224
|
+
const right = index === 0 ? delimiter[1] : index === rows.length - 1 ? delimiter[5] : delimiter[3];
|
|
1225
|
+
return `${left} ${row} ${right}`;
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
if (lines.length <= 1) {
|
|
1229
|
+
return lines[0] ?? "";
|
|
1230
|
+
}
|
|
1231
|
+
const index = this.layoutNodes.push({ type: "matrix", lines, baseline: 0 }) - 1;
|
|
1232
|
+
return `${LAYOUT_MARKER_START}${index}${LAYOUT_MARKER_END}`;
|
|
1093
1233
|
}
|
|
1094
1234
|
renderNested(source, stackFractions = true) {
|
|
1095
|
-
const rendered = new LatexParser(source,
|
|
1235
|
+
const rendered = new LatexParser(source, this.layoutNodes, this.display && stackFractions).render();
|
|
1096
1236
|
if (rendered === undefined) {
|
|
1097
1237
|
this.supported = false;
|
|
1098
1238
|
return source;
|
|
@@ -1105,12 +1245,12 @@ class LatexParser {
|
|
|
1105
1245
|
* Returns undefined when the expression contains unsupported or malformed syntax.
|
|
1106
1246
|
*/
|
|
1107
1247
|
export function renderLatex(source, options = {}) {
|
|
1108
|
-
const layoutNodes =
|
|
1109
|
-
const rendered = new LatexParser(source, layoutNodes).render();
|
|
1248
|
+
const layoutNodes = [];
|
|
1249
|
+
const rendered = new LatexParser(source, layoutNodes, options.display === true).render();
|
|
1110
1250
|
if (rendered === undefined) {
|
|
1111
1251
|
return undefined;
|
|
1112
1252
|
}
|
|
1113
|
-
if (
|
|
1253
|
+
if (layoutNodes.length === 0) {
|
|
1114
1254
|
return rendered.replaceAll(PROTECTED_SPACE, " ");
|
|
1115
1255
|
}
|
|
1116
1256
|
const lines = renderLayout(rendered, layoutNodes).lines;
|