@oh-my-pi/pi-tui 9.6.0 → 9.6.3
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/README.md +3 -3
- package/package.json +2 -2
- package/src/components/editor.ts +1 -1
- package/src/components/select-list.ts +10 -10
- package/src/components/settings-list.ts +6 -3
- package/src/index.ts +1 -1
- package/src/keys.ts +12 -27
- package/src/symbols.ts +0 -1
- package/src/utils.ts +27 -106
package/README.md
CHANGED
|
@@ -542,16 +542,16 @@ interface Terminal {
|
|
|
542
542
|
## Utilities
|
|
543
543
|
|
|
544
544
|
```typescript
|
|
545
|
-
import { visibleWidth, truncateToWidth, wrapTextWithAnsi } from "@oh-my-pi/pi-tui";
|
|
545
|
+
import { Ellipsis, visibleWidth, truncateToWidth, wrapTextWithAnsi } from "@oh-my-pi/pi-tui";
|
|
546
546
|
|
|
547
547
|
// Get visible width of string (ignoring ANSI codes, uses Bun.stringWidth)
|
|
548
548
|
const width = visibleWidth("\x1b[31mHello\x1b[0m"); // 5
|
|
549
549
|
|
|
550
550
|
// Truncate string to width (preserving ANSI codes, adds ellipsis)
|
|
551
|
-
const truncated = truncateToWidth("Hello World", 8); // "Hello
|
|
551
|
+
const truncated = truncateToWidth("Hello World", 8); // "Hello…" (default: Ellipsis.Unicode)
|
|
552
552
|
|
|
553
553
|
// Truncate without ellipsis
|
|
554
|
-
const truncatedNoEllipsis = truncateToWidth("Hello World", 8,
|
|
554
|
+
const truncatedNoEllipsis = truncateToWidth("Hello World", 8, Ellipsis.Omit); // "Hello Wo"
|
|
555
555
|
|
|
556
556
|
// Wrap text to width (Bun.wrapAnsi word wrap, trims line ends, preserves ANSI)
|
|
557
557
|
const lines = wrapTextWithAnsi("This is a long line that needs wrapping", 20);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-tui",
|
|
3
|
-
"version": "9.6.
|
|
3
|
+
"version": "9.6.3",
|
|
4
4
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"bun": ">=1.3.7"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@oh-my-pi/pi-natives": "9.6.
|
|
50
|
+
"@oh-my-pi/pi-natives": "9.6.3",
|
|
51
51
|
"@types/mime-types": "^3.0.1",
|
|
52
52
|
"chalk": "^5.6.2",
|
|
53
53
|
"marked": "^17.0.1",
|
package/src/components/editor.ts
CHANGED
|
@@ -516,7 +516,7 @@ export class Editor implements Component, Focusable {
|
|
|
516
516
|
result.push(topLeft + content + this.borderColor(box.horizontal.repeat(fillWidth)) + topRight);
|
|
517
517
|
} else {
|
|
518
518
|
// Status too long - truncate it
|
|
519
|
-
const truncated = truncateToWidth(content, topFillWidth - 1
|
|
519
|
+
const truncated = truncateToWidth(content, topFillWidth - 1);
|
|
520
520
|
const truncatedWidth = visibleWidth(truncated);
|
|
521
521
|
const fillWidth = Math.max(0, topFillWidth - truncatedWidth);
|
|
522
522
|
result.push(topLeft + truncated + this.borderColor(box.horizontal.repeat(fillWidth)) + topRight);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { matchesKey } from "../keys";
|
|
2
2
|
import type { SymbolTheme } from "../symbols";
|
|
3
3
|
import type { Component } from "../tui";
|
|
4
|
-
import { padding, truncateToWidth, visibleWidth } from "../utils";
|
|
4
|
+
import { Ellipsis, padding, truncateToWidth, visibleWidth } from "../utils";
|
|
5
5
|
|
|
6
6
|
export interface SelectItem {
|
|
7
7
|
value: string;
|
|
@@ -83,7 +83,7 @@ export class SelectList implements Component {
|
|
|
83
83
|
if (item.description && width > 40) {
|
|
84
84
|
// Calculate how much space we have for value + description
|
|
85
85
|
const maxValueWidth = Math.min(30, width - prefixWidth - 4);
|
|
86
|
-
const truncatedValue = truncateToWidth(displayValue, maxValueWidth,
|
|
86
|
+
const truncatedValue = truncateToWidth(displayValue, maxValueWidth, Ellipsis.Omit);
|
|
87
87
|
const spacing = padding(Math.max(1, 32 - truncatedValue.length));
|
|
88
88
|
|
|
89
89
|
// Calculate remaining space for description using visible widths
|
|
@@ -91,18 +91,18 @@ export class SelectList implements Component {
|
|
|
91
91
|
const remainingWidth = width - descriptionStart - 2; // -2 for safety
|
|
92
92
|
|
|
93
93
|
if (remainingWidth > 10) {
|
|
94
|
-
const truncatedDesc = truncateToWidth(item.description, remainingWidth,
|
|
94
|
+
const truncatedDesc = truncateToWidth(item.description, remainingWidth, Ellipsis.Omit);
|
|
95
95
|
// Apply selectedText to entire line content
|
|
96
96
|
line = this.theme.selectedText(`${prefix}${truncatedValue}${spacing}${truncatedDesc}`);
|
|
97
97
|
} else {
|
|
98
98
|
// Not enough space for description
|
|
99
99
|
const maxWidth = width - prefixWidth - 2;
|
|
100
|
-
line = this.theme.selectedText(`${prefix}${truncateToWidth(displayValue, maxWidth,
|
|
100
|
+
line = this.theme.selectedText(`${prefix}${truncateToWidth(displayValue, maxWidth, Ellipsis.Omit)}`);
|
|
101
101
|
}
|
|
102
102
|
} else {
|
|
103
103
|
// No description or not enough width
|
|
104
104
|
const maxWidth = width - prefixWidth - 2;
|
|
105
|
-
line = this.theme.selectedText(`${prefix}${truncateToWidth(displayValue, maxWidth,
|
|
105
|
+
line = this.theme.selectedText(`${prefix}${truncateToWidth(displayValue, maxWidth, Ellipsis.Omit)}`);
|
|
106
106
|
}
|
|
107
107
|
} else {
|
|
108
108
|
const displayValue = item.label || item.value;
|
|
@@ -111,7 +111,7 @@ export class SelectList implements Component {
|
|
|
111
111
|
if (item.description && width > 40) {
|
|
112
112
|
// Calculate how much space we have for value + description
|
|
113
113
|
const maxValueWidth = Math.min(30, width - prefix.length - 4);
|
|
114
|
-
const truncatedValue = truncateToWidth(displayValue, maxValueWidth,
|
|
114
|
+
const truncatedValue = truncateToWidth(displayValue, maxValueWidth, Ellipsis.Omit);
|
|
115
115
|
const spacing = padding(Math.max(1, 32 - truncatedValue.length));
|
|
116
116
|
|
|
117
117
|
// Calculate remaining space for description
|
|
@@ -119,18 +119,18 @@ export class SelectList implements Component {
|
|
|
119
119
|
const remainingWidth = width - descriptionStart - 2; // -2 for safety
|
|
120
120
|
|
|
121
121
|
if (remainingWidth > 10) {
|
|
122
|
-
const truncatedDesc = truncateToWidth(item.description, remainingWidth,
|
|
122
|
+
const truncatedDesc = truncateToWidth(item.description, remainingWidth, Ellipsis.Omit);
|
|
123
123
|
const descText = this.theme.description(spacing + truncatedDesc);
|
|
124
124
|
line = prefix + truncatedValue + descText;
|
|
125
125
|
} else {
|
|
126
126
|
// Not enough space for description
|
|
127
127
|
const maxWidth = width - prefix.length - 2;
|
|
128
|
-
line = prefix + truncateToWidth(displayValue, maxWidth,
|
|
128
|
+
line = prefix + truncateToWidth(displayValue, maxWidth, Ellipsis.Omit);
|
|
129
129
|
}
|
|
130
130
|
} else {
|
|
131
131
|
// No description or not enough width
|
|
132
132
|
const maxWidth = width - prefix.length - 2;
|
|
133
|
-
line = prefix + truncateToWidth(displayValue, maxWidth,
|
|
133
|
+
line = prefix + truncateToWidth(displayValue, maxWidth, Ellipsis.Omit);
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
@@ -141,7 +141,7 @@ export class SelectList implements Component {
|
|
|
141
141
|
if (startIndex > 0 || endIndex < this.filteredItems.length) {
|
|
142
142
|
const scrollText = ` (${this.selectedIndex + 1}/${this.filteredItems.length})`;
|
|
143
143
|
// Truncate if too long for terminal
|
|
144
|
-
lines.push(this.theme.scrollInfo(truncateToWidth(scrollText, width - 2,
|
|
144
|
+
lines.push(this.theme.scrollInfo(truncateToWidth(scrollText, width - 2, Ellipsis.Omit)));
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
return lines;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { matchesKey } from "../keys";
|
|
2
2
|
import type { Component } from "../tui";
|
|
3
|
-
import { padding, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "../utils";
|
|
3
|
+
import { Ellipsis, padding, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "../utils";
|
|
4
4
|
|
|
5
5
|
export interface SettingItem {
|
|
6
6
|
/** Unique identifier for this setting */
|
|
@@ -108,7 +108,10 @@ export class SettingsList implements Component {
|
|
|
108
108
|
const usedWidth = prefixWidth + maxLabelWidth + visibleWidth(separator);
|
|
109
109
|
const valueMaxWidth = width - usedWidth - 2;
|
|
110
110
|
|
|
111
|
-
const valueText = this.theme.value(
|
|
111
|
+
const valueText = this.theme.value(
|
|
112
|
+
truncateToWidth(item.currentValue, valueMaxWidth, Ellipsis.Omit),
|
|
113
|
+
isSelected,
|
|
114
|
+
);
|
|
112
115
|
|
|
113
116
|
lines.push(prefix + labelText + separator + valueText);
|
|
114
117
|
}
|
|
@@ -116,7 +119,7 @@ export class SettingsList implements Component {
|
|
|
116
119
|
// Add scroll indicator if needed
|
|
117
120
|
if (startIndex > 0 || endIndex < this.items.length) {
|
|
118
121
|
const scrollText = ` (${this.selectedIndex + 1}/${this.items.length})`;
|
|
119
|
-
lines.push(this.theme.hint(truncateToWidth(scrollText, width - 2,
|
|
122
|
+
lines.push(this.theme.hint(truncateToWidth(scrollText, width - 2, Ellipsis.Omit)));
|
|
120
123
|
}
|
|
121
124
|
|
|
122
125
|
// Add description for selected item
|
package/src/index.ts
CHANGED
|
@@ -85,4 +85,4 @@ export {
|
|
|
85
85
|
} from "./terminal-image";
|
|
86
86
|
export { type Component, Container, type OverlayHandle, type SizeValue, TUI } from "./tui";
|
|
87
87
|
// Utilities
|
|
88
|
-
export { padding, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "./utils";
|
|
88
|
+
export { Ellipsis, padding, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "./utils";
|
package/src/keys.ts
CHANGED
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
* - isKittyProtocolActive() - Query global Kitty protocol state
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
+
import { matchesKittySequence } from "@oh-my-pi/pi-natives";
|
|
22
|
+
|
|
21
23
|
// =============================================================================
|
|
22
24
|
// Global Kitty Protocol State
|
|
23
25
|
// =============================================================================
|
|
@@ -620,26 +622,6 @@ export function parseKittySequence(data: string): ParsedKittySequence | null {
|
|
|
620
622
|
return null;
|
|
621
623
|
}
|
|
622
624
|
|
|
623
|
-
function matchesKittySequence(data: string, expectedCodepoint: number, expectedModifier: number): boolean {
|
|
624
|
-
const parsed = parseKittySequence(data);
|
|
625
|
-
if (!parsed) return false;
|
|
626
|
-
const actualMod = parsed.modifier & ~LOCK_MASK;
|
|
627
|
-
const expectedMod = expectedModifier & ~LOCK_MASK;
|
|
628
|
-
|
|
629
|
-
// Check if modifiers match
|
|
630
|
-
if (actualMod !== expectedMod) return false;
|
|
631
|
-
|
|
632
|
-
// Primary match: codepoint matches directly
|
|
633
|
-
if (parsed.codepoint === expectedCodepoint) return true;
|
|
634
|
-
|
|
635
|
-
// Alternate match: use base layout key for non-Latin keyboard layouts
|
|
636
|
-
// This allows Ctrl+С (Cyrillic) to match Ctrl+c (Latin) when terminal reports
|
|
637
|
-
// the base layout key (the key in standard PC-101 layout)
|
|
638
|
-
if (parsed.baseLayoutKey !== undefined && parsed.baseLayoutKey === expectedCodepoint) return true;
|
|
639
|
-
|
|
640
|
-
return false;
|
|
641
|
-
}
|
|
642
|
-
|
|
643
625
|
/**
|
|
644
626
|
* Match xterm modifyOtherKeys format: CSI 27 ; modifiers ; keycode ~
|
|
645
627
|
* This is used by terminals when Kitty protocol is not enabled.
|
|
@@ -666,23 +648,26 @@ function rawCtrlChar(letter: string): string {
|
|
|
666
648
|
|
|
667
649
|
type ParsedKeyId = { key: string; ctrl: boolean; shift: boolean; alt: boolean };
|
|
668
650
|
|
|
669
|
-
const PARSED_KEY_ID_CACHE = new Map<string, ParsedKeyId>();
|
|
651
|
+
const PARSED_KEY_ID_CACHE = new Map<string, ParsedKeyId | null>();
|
|
670
652
|
|
|
671
|
-
function
|
|
653
|
+
function parseKeyIdSlow(keyId: string): ParsedKeyId | null {
|
|
672
654
|
const normalizedKeyId = keyId.toLowerCase();
|
|
673
|
-
const cached = PARSED_KEY_ID_CACHE.get(normalizedKeyId);
|
|
674
|
-
if (cached) return cached;
|
|
675
|
-
|
|
676
655
|
const parts = normalizedKeyId.split("+");
|
|
677
656
|
const key = parts[parts.length - 1];
|
|
678
657
|
if (!key) return null;
|
|
679
|
-
|
|
658
|
+
return {
|
|
680
659
|
key,
|
|
681
660
|
ctrl: parts.includes("ctrl"),
|
|
682
661
|
shift: parts.includes("shift"),
|
|
683
662
|
alt: parts.includes("alt"),
|
|
684
663
|
};
|
|
685
|
-
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function parseKeyId(keyId: string): ParsedKeyId | null {
|
|
667
|
+
const cached = PARSED_KEY_ID_CACHE.get(keyId);
|
|
668
|
+
if (cached !== undefined) return cached;
|
|
669
|
+
const parsed = parseKeyIdSlow(keyId);
|
|
670
|
+
PARSED_KEY_ID_CACHE.set(keyId, parsed);
|
|
686
671
|
return parsed;
|
|
687
672
|
}
|
|
688
673
|
|
package/src/symbols.ts
CHANGED
package/src/utils.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
truncateToWidth as nativeTruncateToWidth,
|
|
5
|
-
visibleWidth as nativeVisibleWidth,
|
|
6
|
-
} from "@oh-my-pi/pi-natives";
|
|
1
|
+
import { sliceWithWidth } from "@oh-my-pi/pi-natives";
|
|
2
|
+
|
|
3
|
+
export { Ellipsis, extractSegments, sliceWithWidth, truncateToWidth } from "@oh-my-pi/pi-natives";
|
|
7
4
|
|
|
8
5
|
// Pre-allocated space buffer for padding
|
|
9
6
|
const SPACE_BUFFER = " ".repeat(512);
|
|
@@ -28,56 +25,53 @@ export function getSegmenter(): Intl.Segmenter {
|
|
|
28
25
|
}
|
|
29
26
|
|
|
30
27
|
// Cache for non-ASCII strings
|
|
31
|
-
const WIDTH_CACHE_SIZE = 512;
|
|
32
|
-
const widthCache = new Map<string, number>();
|
|
33
|
-
const NATIVE_WIDTH_THRESHOLD = 256;
|
|
28
|
+
//const WIDTH_CACHE_SIZE = 512;
|
|
29
|
+
//const widthCache = new Map<string, number>();
|
|
34
30
|
|
|
35
31
|
/**
|
|
36
32
|
* Calculate the visible width of a string in terminal columns.
|
|
37
33
|
*/
|
|
38
|
-
export function
|
|
34
|
+
export function visibleWidthRaw(str: string): number {
|
|
39
35
|
if (str.length === 0) {
|
|
40
36
|
return 0;
|
|
41
37
|
}
|
|
42
38
|
|
|
43
39
|
// Fast path: pure ASCII printable
|
|
44
40
|
let isPureAscii = true;
|
|
41
|
+
let tabLength = 0;
|
|
45
42
|
for (let i = 0; i < str.length; i++) {
|
|
46
43
|
const code = str.charCodeAt(i);
|
|
47
|
-
if (code
|
|
44
|
+
if (code === 9) {
|
|
45
|
+
tabLength += 3;
|
|
46
|
+
} else if (code < 0x20 || code > 0x7e) {
|
|
48
47
|
isPureAscii = false;
|
|
49
|
-
break;
|
|
50
48
|
}
|
|
51
49
|
}
|
|
52
50
|
if (isPureAscii) {
|
|
53
|
-
return str.length;
|
|
51
|
+
return str.length + tabLength;
|
|
54
52
|
}
|
|
53
|
+
return Bun.stringWidth(str) + tabLength;
|
|
54
|
+
}
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Calculate the visible width of a string in terminal columns.
|
|
58
|
+
*/
|
|
59
|
+
export function visibleWidth(str: string): number {
|
|
60
|
+
if (str.length === 0) {
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
return visibleWidthRaw(str);
|
|
64
|
+
|
|
65
|
+
// === Disabled cache ===
|
|
66
|
+
|
|
67
|
+
/*
|
|
56
68
|
// Check cache
|
|
57
69
|
const cached = widthCache.get(str);
|
|
58
70
|
if (cached !== undefined) {
|
|
59
71
|
return cached;
|
|
60
72
|
}
|
|
61
73
|
|
|
62
|
-
|
|
63
|
-
if (str.length <= NATIVE_WIDTH_THRESHOLD) {
|
|
64
|
-
// Normalize: tabs to 3 spaces, strip ANSI escape codes
|
|
65
|
-
let clean = str;
|
|
66
|
-
if (str.includes("\t")) {
|
|
67
|
-
clean = clean.replace(/\t/g, " ");
|
|
68
|
-
}
|
|
69
|
-
if (clean.includes("\x1b")) {
|
|
70
|
-
// Strip SGR codes (\x1b[...m) and cursor codes (\x1b[...G/K/H/J)
|
|
71
|
-
clean = clean.replace(/\x1b\[[0-9;]*[mGKHJ]/g, "");
|
|
72
|
-
// Strip OSC 8 hyperlinks: \x1b]8;;URL\x07 and \x1b]8;;\x07
|
|
73
|
-
clean = clean.replace(/\x1b\]8;;[^\x07]*\x07/g, "");
|
|
74
|
-
}
|
|
75
|
-
width = Bun.stringWidth(clean);
|
|
76
|
-
} else {
|
|
77
|
-
width = nativeVisibleWidth(str);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Cache result
|
|
74
|
+
const width = visibleWidthRaw(str);
|
|
81
75
|
if (widthCache.size >= WIDTH_CACHE_SIZE) {
|
|
82
76
|
const firstKey = widthCache.keys().next().value;
|
|
83
77
|
if (firstKey !== undefined) {
|
|
@@ -87,39 +81,7 @@ export function visibleWidth(str: string): number {
|
|
|
87
81
|
widthCache.set(str, width);
|
|
88
82
|
|
|
89
83
|
return width;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Extract ANSI escape sequences from a string at the given position.
|
|
94
|
-
*/
|
|
95
|
-
export function extractAnsiCode(str: string, pos: number): { code: string; length: number } | null {
|
|
96
|
-
if (pos >= str.length || str[pos] !== "\x1b") return null;
|
|
97
|
-
|
|
98
|
-
const next = str[pos + 1];
|
|
99
|
-
|
|
100
|
-
// CSI sequence: ESC [ ... m/G/K/H/J
|
|
101
|
-
if (next === "[") {
|
|
102
|
-
let j = pos + 2;
|
|
103
|
-
while (j < str.length && !/[mGKHJ]/.test(str[j]!)) j++;
|
|
104
|
-
if (j < str.length) return { code: str.substring(pos, j + 1), length: j + 1 - pos };
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// OSC sequence: ESC ] ... BEL or ESC ] ... ST (ESC \)
|
|
109
|
-
// Used for hyperlinks (OSC 8), window titles, etc.
|
|
110
|
-
if (next === "]") {
|
|
111
|
-
let j = pos + 2;
|
|
112
|
-
while (j < str.length) {
|
|
113
|
-
if (str[j] === "\x07") return { code: str.substring(pos, j + 1), length: j + 1 - pos };
|
|
114
|
-
if (str[j] === "\x1b" && str[j + 1] === "\\") {
|
|
115
|
-
return { code: str.substring(pos, j + 2), length: j + 2 - pos };
|
|
116
|
-
}
|
|
117
|
-
j++;
|
|
118
|
-
}
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return null;
|
|
84
|
+
*/
|
|
123
85
|
}
|
|
124
86
|
|
|
125
87
|
const WRAP_OPTIONS = { wordWrap: true, hard: true, trim: false } as const;
|
|
@@ -173,21 +135,6 @@ export function applyBackgroundToLine(line: string, width: number, bgFn: (text:
|
|
|
173
135
|
return bgFn(withPadding);
|
|
174
136
|
}
|
|
175
137
|
|
|
176
|
-
/**
|
|
177
|
-
* Truncate text to fit within a maximum visible width, adding ellipsis if needed.
|
|
178
|
-
* Optionally pad with spaces to reach exactly maxWidth.
|
|
179
|
-
* Properly handles ANSI escape codes (they don't count toward width).
|
|
180
|
-
*
|
|
181
|
-
* @param text - Text to truncate (may contain ANSI codes)
|
|
182
|
-
* @param maxWidth - Maximum visible width
|
|
183
|
-
* @param ellipsis - Ellipsis string to append when truncating (default: "…")
|
|
184
|
-
* @param pad - If true, pad result with spaces to exactly maxWidth (default: false)
|
|
185
|
-
* @returns Truncated text, optionally padded to exactly maxWidth
|
|
186
|
-
*/
|
|
187
|
-
export function truncateToWidth(text: string, maxWidth: number, ellipsis: string = "…", pad: boolean = false): string {
|
|
188
|
-
return nativeTruncateToWidth(text, maxWidth, ellipsis, pad);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
138
|
/**
|
|
192
139
|
* Extract a range of visible columns from a line. Handles ANSI codes and wide chars.
|
|
193
140
|
* @param strict - If true, exclude wide chars at boundary that would extend past the range
|
|
@@ -195,29 +142,3 @@ export function truncateToWidth(text: string, maxWidth: number, ellipsis: string
|
|
|
195
142
|
export function sliceByColumn(line: string, startCol: number, length: number, strict = false): string {
|
|
196
143
|
return sliceWithWidth(line, startCol, length, strict).text;
|
|
197
144
|
}
|
|
198
|
-
|
|
199
|
-
/** Like sliceByColumn but also returns the actual visible width of the result. */
|
|
200
|
-
export function sliceWithWidth(
|
|
201
|
-
line: string,
|
|
202
|
-
startCol: number,
|
|
203
|
-
length: number,
|
|
204
|
-
strict = false,
|
|
205
|
-
): { text: string; width: number } {
|
|
206
|
-
if (length <= 0) return { text: "", width: 0 };
|
|
207
|
-
return nativeSliceWithWidth(line, startCol, length, strict);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Extract "before" and "after" segments from a line in a single pass.
|
|
212
|
-
* Used for overlay compositing where we need content before and after the overlay region.
|
|
213
|
-
* Preserves styling from before the overlay that should affect content after it.
|
|
214
|
-
*/
|
|
215
|
-
export function extractSegments(
|
|
216
|
-
line: string,
|
|
217
|
-
beforeEnd: number,
|
|
218
|
-
afterStart: number,
|
|
219
|
-
afterLen: number,
|
|
220
|
-
strictAfter = false,
|
|
221
|
-
): { before: string; beforeWidth: number; after: string; afterWidth: number } {
|
|
222
|
-
return nativeExtractSegments(line, beforeEnd, afterStart, afterLen, strictAfter);
|
|
223
|
-
}
|