@code-yeongyu/senpi-tui 2026.11.12 → 2026.11.13
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/components/box.js +32 -7
- package/dist/components/markdown-source.d.ts +17 -0
- package/dist/components/markdown-source.js +262 -0
- package/dist/components/markdown.d.ts +7 -0
- package/dist/components/markdown.js +412 -48
- package/dist/components/scroll-view.js +4 -1
- package/dist/components/spacer.js +2 -0
- package/dist/components/text.js +5 -0
- package/dist/components/v-stack.js +11 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/selection-source.d.ts +43 -0
- package/dist/selection-source.js +200 -0
- package/dist/source-ranges.d.ts +25 -0
- package/dist/source-ranges.js +122 -0
- package/dist/tui-alt-screen.d.ts +1 -0
- package/dist/tui-alt-screen.js +20 -5
- package/dist/tui.js +12 -4
- package/dist/utils.d.ts +2 -1
- package/dist/utils.js +39 -6
- package/package.json +1 -1
package/dist/utils.js
CHANGED
|
@@ -883,8 +883,10 @@ export function flattenLines(lines) {
|
|
|
883
883
|
for (const line of lines)
|
|
884
884
|
Number(line);
|
|
885
885
|
}
|
|
886
|
-
|
|
886
|
+
/** onLine receives each output line's consumed UTF-16 interval in the ANSI-stripped input. */
|
|
887
|
+
export function wrapTextWithAnsi(text, width, onLine) {
|
|
887
888
|
if (!text) {
|
|
889
|
+
onLine?.(0, 0);
|
|
888
890
|
return [""];
|
|
889
891
|
}
|
|
890
892
|
// Handle newlines by processing each line separately
|
|
@@ -892,24 +894,31 @@ export function wrapTextWithAnsi(text, width) {
|
|
|
892
894
|
const inputLines = text.split(/\r\n|\r|\n/);
|
|
893
895
|
const result = [];
|
|
894
896
|
const tracker = new AnsiCodeTracker();
|
|
895
|
-
|
|
897
|
+
let sourceOffset = 0;
|
|
898
|
+
const separators = onLine ? (text.match(/\r\n|\r|\n/g) ?? []) : [];
|
|
899
|
+
for (let index = 0; index < inputLines.length; index++) {
|
|
900
|
+
const inputLine = inputLines[index];
|
|
896
901
|
// Prepend active ANSI codes from previous lines (except for first line)
|
|
897
902
|
const prefix = result.length > 0 ? tracker.getActiveCodes() : "";
|
|
898
|
-
const wrappedLines = wrapSingleLine(prefix + inputLine, width);
|
|
903
|
+
const wrappedLines = wrapSingleLine(prefix + inputLine, width, onLine && ((start, end) => onLine(sourceOffset + start, sourceOffset + end)));
|
|
899
904
|
for (const wrappedLine of wrappedLines) {
|
|
900
905
|
result.push(wrappedLine);
|
|
901
906
|
}
|
|
902
907
|
// Update tracker with codes from this line for next iteration
|
|
903
908
|
updateTrackerFromText(inputLine, tracker);
|
|
909
|
+
if (onLine)
|
|
910
|
+
sourceOffset += stripTerminalSequences(inputLine).length + (separators[index]?.length ?? 0);
|
|
904
911
|
}
|
|
905
912
|
return result.length > 0 ? result : [""];
|
|
906
913
|
}
|
|
907
|
-
function wrapSingleLine(line, width) {
|
|
914
|
+
function wrapSingleLine(line, width, onLine) {
|
|
908
915
|
if (!line) {
|
|
916
|
+
onLine?.(0, 0);
|
|
909
917
|
return [""];
|
|
910
918
|
}
|
|
911
919
|
const visibleLength = visibleWidth(line);
|
|
912
920
|
if (visibleLength <= width) {
|
|
921
|
+
onLine?.(0, stripTerminalSequences(line).length);
|
|
913
922
|
return [line];
|
|
914
923
|
}
|
|
915
924
|
const wrapped = [];
|
|
@@ -917,7 +926,15 @@ function wrapSingleLine(line, width) {
|
|
|
917
926
|
const tokens = splitIntoTokensWithAnsi(line);
|
|
918
927
|
let currentLine = "";
|
|
919
928
|
let currentVisibleLength = 0;
|
|
929
|
+
let sourceOffset = 0;
|
|
930
|
+
let lineStart = 0;
|
|
931
|
+
const emit = (text) => {
|
|
932
|
+
onLine?.(lineStart, lineStart + stripTerminalSequences(text.trimEnd()).length);
|
|
933
|
+
};
|
|
920
934
|
for (const token of tokens) {
|
|
935
|
+
const tokenStart = sourceOffset;
|
|
936
|
+
if (onLine)
|
|
937
|
+
sourceOffset += stripTerminalSequences(token).length;
|
|
921
938
|
const tokenVisibleLength = visibleWidth(token);
|
|
922
939
|
const isWhitespace = token.trim() === "";
|
|
923
940
|
// Token itself is too long - break it character by character
|
|
@@ -929,11 +946,18 @@ function wrapSingleLine(line, width) {
|
|
|
929
946
|
currentLine += lineEndReset;
|
|
930
947
|
}
|
|
931
948
|
wrapped.push(currentLine);
|
|
949
|
+
emit(currentLine);
|
|
932
950
|
currentLine = "";
|
|
933
951
|
currentVisibleLength = 0;
|
|
934
952
|
}
|
|
935
953
|
// Break long token - breakLongWord handles its own resets
|
|
936
|
-
const broken = breakLongWord(token, width, tracker
|
|
954
|
+
const broken = breakLongWord(token, width, tracker, onLine &&
|
|
955
|
+
((start, end) => {
|
|
956
|
+
onLine(tokenStart + start, tokenStart + end);
|
|
957
|
+
}));
|
|
958
|
+
// The final fragment remains in currentLine and is emitted with the next token.
|
|
959
|
+
if (onLine)
|
|
960
|
+
lineStart = sourceOffset - stripTerminalSequences(broken[broken.length - 1]).length;
|
|
937
961
|
for (let i = 0; i < broken.length - 1; i++) {
|
|
938
962
|
wrapped.push(broken[i]);
|
|
939
963
|
}
|
|
@@ -951,14 +975,17 @@ function wrapSingleLine(line, width) {
|
|
|
951
975
|
lineToWrap += lineEndReset;
|
|
952
976
|
}
|
|
953
977
|
wrapped.push(lineToWrap);
|
|
978
|
+
emit(lineToWrap);
|
|
954
979
|
if (isWhitespace) {
|
|
955
980
|
// Don't start new line with whitespace
|
|
956
981
|
currentLine = tracker.getActiveCodes();
|
|
957
982
|
currentVisibleLength = 0;
|
|
983
|
+
lineStart = sourceOffset;
|
|
958
984
|
}
|
|
959
985
|
else {
|
|
960
986
|
currentLine = tracker.getActiveCodes() + token;
|
|
961
987
|
currentVisibleLength = tokenVisibleLength;
|
|
988
|
+
lineStart = tokenStart;
|
|
962
989
|
}
|
|
963
990
|
}
|
|
964
991
|
else {
|
|
@@ -971,6 +998,7 @@ function wrapSingleLine(line, width) {
|
|
|
971
998
|
if (currentLine) {
|
|
972
999
|
// No reset at end of final line - let caller handle it
|
|
973
1000
|
wrapped.push(currentLine);
|
|
1001
|
+
emit(currentLine);
|
|
974
1002
|
}
|
|
975
1003
|
// Trailing whitespace can cause lines to exceed the requested width
|
|
976
1004
|
return wrapped.length > 0 ? wrapped.map((line) => line.trimEnd()) : [""];
|
|
@@ -982,10 +1010,12 @@ export const PUNCTUATION_REGEX = /[(){}[\]<>.,;:'"!?+\-=*/\\|&%^$#@~`]/;
|
|
|
982
1010
|
export function isWhitespaceChar(char) {
|
|
983
1011
|
return /\s/.test(char);
|
|
984
1012
|
}
|
|
985
|
-
function breakLongWord(word, width, tracker) {
|
|
1013
|
+
function breakLongWord(word, width, tracker, onLine) {
|
|
986
1014
|
const lines = [];
|
|
987
1015
|
let currentLine = tracker.getActiveCodes();
|
|
988
1016
|
let currentWidth = 0;
|
|
1017
|
+
let sourceOffset = 0;
|
|
1018
|
+
let lineStart = 0;
|
|
989
1019
|
// First, separate ANSI codes from visible content
|
|
990
1020
|
// We need to handle ANSI codes specially since they're not graphemes
|
|
991
1021
|
let i = 0;
|
|
@@ -1032,11 +1062,14 @@ function breakLongWord(word, width, tracker) {
|
|
|
1032
1062
|
currentLine += lineEndReset;
|
|
1033
1063
|
}
|
|
1034
1064
|
lines.push(currentLine);
|
|
1065
|
+
onLine?.(lineStart, sourceOffset);
|
|
1066
|
+
lineStart = sourceOffset;
|
|
1035
1067
|
currentLine = tracker.getActiveCodes();
|
|
1036
1068
|
currentWidth = 0;
|
|
1037
1069
|
}
|
|
1038
1070
|
currentLine += grapheme;
|
|
1039
1071
|
currentWidth += graphemeWidth;
|
|
1072
|
+
sourceOffset += grapheme.length;
|
|
1040
1073
|
}
|
|
1041
1074
|
if (currentLine) {
|
|
1042
1075
|
// No reset at end of final segment - caller handles continuation
|
package/package.json
CHANGED