@narumitw/pi-statusline 0.42.0 → 0.46.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.
- package/README.md +9 -3
- package/package.json +1 -1
- package/src/directory.ts +54 -0
- package/src/git-status.ts +30 -7
- package/src/render.ts +12 -65
- package/src/statusline.ts +17 -1
- package/src/terminal.ts +60 -0
package/README.md
CHANGED
|
@@ -109,11 +109,16 @@ Retention priority is highest to lowest:
|
|
|
109
109
|
context model branch tools cwd thinking cost provider cache tokens time turn brand
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
-
Explicit `line_break` entries remain row boundaries.
|
|
113
|
-
|
|
112
|
+
Explicit `line_break` entries remain row boundaries. If the last remaining segment is itself wider
|
|
113
|
+
than the row, that row renders empty rather than emitting an over-width line.
|
|
114
114
|
|
|
115
|
-
###
|
|
115
|
+
### Directory, activity, Git, and PR state
|
|
116
116
|
|
|
117
|
+
- `cwd` uses Starship's directory presentation defaults: contract the home directory to `~`, contract
|
|
118
|
+
to the Git repository root when available, then retain at most the last three path components. This
|
|
119
|
+
changes display only; the configured segment list and Pi working directory are untouched.
|
|
120
|
+
- Repository-root discovery is cached with Git status outside footer rendering; a failed root query
|
|
121
|
+
falls back to home/path-component contraction without hiding the segment.
|
|
117
122
|
- During active work, `tools` shows `💭 thinking` or `⚙ <tool>` with parallel counts.
|
|
118
123
|
- Activity disappears after the agent settles and resets across session replacement or shutdown.
|
|
119
124
|
- Clean repositories show no Git counters.
|
|
@@ -334,6 +339,7 @@ extensions/pi-statusline/
|
|
|
334
339
|
│ ├── index.ts
|
|
335
340
|
│ ├── statusline.ts
|
|
336
341
|
│ ├── render.ts
|
|
342
|
+
│ ├── directory.ts
|
|
337
343
|
│ ├── usage.ts
|
|
338
344
|
│ ├── powerline.ts
|
|
339
345
|
│ ├── information-profiles.ts
|
package/package.json
CHANGED
package/src/directory.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { basename, isAbsolute, relative, sep } from "node:path";
|
|
2
|
+
import { sanitizeTerminalText } from "./terminal.js";
|
|
3
|
+
|
|
4
|
+
const DEFAULT_TRUNCATION_LENGTH = 3;
|
|
5
|
+
const DEFAULT_HOME_SYMBOL = "~";
|
|
6
|
+
|
|
7
|
+
/** Apply Starship's default directory presentation without exposing a format language. */
|
|
8
|
+
export function formatDirectoryPath(
|
|
9
|
+
cwd: string,
|
|
10
|
+
homeDir: string | undefined,
|
|
11
|
+
gitRoot: string | undefined,
|
|
12
|
+
): string {
|
|
13
|
+
const contracted =
|
|
14
|
+
gitRoot && (!homeDir || !samePath(gitRoot, homeDir))
|
|
15
|
+
? (contractRepositoryPath(cwd, gitRoot) ?? contractPath(cwd, homeDir, DEFAULT_HOME_SYMBOL))
|
|
16
|
+
: contractPath(cwd, homeDir, DEFAULT_HOME_SYMBOL);
|
|
17
|
+
const safeContracted = sanitizeTerminalText(contracted);
|
|
18
|
+
const components = safeContracted.split("/").filter((component) => component.length > 0);
|
|
19
|
+
const displayed =
|
|
20
|
+
components.length > DEFAULT_TRUNCATION_LENGTH
|
|
21
|
+
? components.slice(-DEFAULT_TRUNCATION_LENGTH).join("/")
|
|
22
|
+
: safeContracted;
|
|
23
|
+
const native = sep === "/" ? displayed : displayed.replaceAll("/", sep);
|
|
24
|
+
return sanitizeTerminalText(native || basename(cwd) || cwd);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function toSlashPath(value: string): string {
|
|
28
|
+
return sep === "\\" ? value.replaceAll("\\", "/") : value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function contractPath(path: string, root: string | undefined, replacement: string): string {
|
|
32
|
+
if (!root) return toSlashPath(path);
|
|
33
|
+
const child = relativeWithin(path, root);
|
|
34
|
+
if (child === undefined) return toSlashPath(path);
|
|
35
|
+
return child ? `${replacement}/${child}` : replacement;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function contractRepositoryPath(path: string, root: string): string | undefined {
|
|
39
|
+
const child = relativeWithin(path, root);
|
|
40
|
+
if (child === undefined) return undefined;
|
|
41
|
+
const name = basename(root) || toSlashPath(root);
|
|
42
|
+
return child ? `${name}/${child}` : name;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function relativeWithin(path: string, root: string): string | undefined {
|
|
46
|
+
const child = relative(root, path);
|
|
47
|
+
if (child === "") return "";
|
|
48
|
+
if (child === ".." || child.startsWith(`..${sep}`) || isAbsolute(child)) return undefined;
|
|
49
|
+
return toSlashPath(child);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function samePath(left: string, right: string): boolean {
|
|
53
|
+
return relative(left, right) === "";
|
|
54
|
+
}
|
package/src/git-status.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { isAbsolute } from "node:path";
|
|
1
2
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
3
|
|
|
3
4
|
const GIT_STATUS_TIMEOUT_MS = 3_000;
|
|
4
5
|
|
|
5
6
|
export interface GitStatusSummary {
|
|
7
|
+
root?: string;
|
|
6
8
|
ahead: number;
|
|
7
9
|
behind: number;
|
|
8
10
|
staged: number;
|
|
@@ -14,14 +16,29 @@ export interface GitStatusSummary {
|
|
|
14
16
|
export async function readGitStatus(
|
|
15
17
|
pi: ExtensionAPI,
|
|
16
18
|
cwd: string,
|
|
19
|
+
signal?: AbortSignal,
|
|
17
20
|
): Promise<GitStatusSummary | undefined> {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
const [statusResult, rootResult] = await Promise.all([
|
|
22
|
+
pi.exec(
|
|
23
|
+
"git",
|
|
24
|
+
["--no-optional-locks", "status", "--porcelain=v1", "--branch", "--untracked-files=normal"],
|
|
25
|
+
{ cwd, timeout: GIT_STATUS_TIMEOUT_MS, signal },
|
|
26
|
+
),
|
|
27
|
+
pi
|
|
28
|
+
.exec("git", ["rev-parse", "--path-format=absolute", "--show-toplevel"], {
|
|
29
|
+
cwd,
|
|
30
|
+
timeout: GIT_STATUS_TIMEOUT_MS,
|
|
31
|
+
signal,
|
|
32
|
+
})
|
|
33
|
+
.catch(() => undefined),
|
|
34
|
+
]);
|
|
35
|
+
if (statusResult.code !== 0 || statusResult.killed) return undefined;
|
|
36
|
+
const summary = parseGitStatusPorcelain(statusResult.stdout);
|
|
37
|
+
if (rootResult && rootResult.code === 0 && !rootResult.killed) {
|
|
38
|
+
const root = parseGitRoot(rootResult.stdout);
|
|
39
|
+
if (root) summary.root = root;
|
|
40
|
+
}
|
|
41
|
+
return summary;
|
|
25
42
|
}
|
|
26
43
|
|
|
27
44
|
export function parseGitStatusPorcelain(output: string): GitStatusSummary {
|
|
@@ -71,6 +88,11 @@ function isChangedStatus(status: string): boolean {
|
|
|
71
88
|
return status !== " " && status !== "?" && status !== "!";
|
|
72
89
|
}
|
|
73
90
|
|
|
91
|
+
export function parseGitRoot(output: string): string | undefined {
|
|
92
|
+
const root = output.split(/\r?\n/u)[0]?.trim();
|
|
93
|
+
return root && isAbsolute(root) ? root : undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
74
96
|
export function formatGitStatusSummary(summary: GitStatusSummary | undefined): string {
|
|
75
97
|
if (!summary) return "";
|
|
76
98
|
const tokens = [
|
|
@@ -111,6 +133,7 @@ export function gitStatusSummaryEqual(
|
|
|
111
133
|
): boolean {
|
|
112
134
|
if (!left || !right) return left === right;
|
|
113
135
|
return (
|
|
136
|
+
left.root === right.root &&
|
|
114
137
|
left.ahead === right.ahead &&
|
|
115
138
|
left.behind === right.behind &&
|
|
116
139
|
left.staged === right.staged &&
|
package/src/render.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { basename } from "node:path";
|
|
2
1
|
import type {
|
|
3
2
|
ExtensionAPI,
|
|
4
3
|
ExtensionContext,
|
|
@@ -6,6 +5,7 @@ import type {
|
|
|
6
5
|
Theme,
|
|
7
6
|
ThemeColor,
|
|
8
7
|
} from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { formatDirectoryPath } from "./directory.js";
|
|
9
9
|
import {
|
|
10
10
|
type ExtensionStatusRuntime,
|
|
11
11
|
formatExtensionStatuses,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "./extension-status.js";
|
|
14
14
|
import { formatGitBranchValue, type GitStatusSummary } from "./git-status.js";
|
|
15
15
|
import { renderPowerlineStatusline } from "./powerline.js";
|
|
16
|
+
import { sanitizeTerminalText } from "./terminal.js";
|
|
16
17
|
import {
|
|
17
18
|
LINE_BREAK_SEGMENT_NAME,
|
|
18
19
|
type PowerlineBlockName,
|
|
@@ -26,6 +27,7 @@ import { type FooterUsageSummary, summarizeFooterUsage } from "./usage.js";
|
|
|
26
27
|
|
|
27
28
|
type ThinkingLevel = ReturnType<ExtensionAPI["getThinkingLevel"]>;
|
|
28
29
|
export interface RuntimeState extends ExtensionStatusRuntime {
|
|
30
|
+
homeDir?: string;
|
|
29
31
|
turnCount: number;
|
|
30
32
|
activeTools: Map<string, number>;
|
|
31
33
|
isStreaming: boolean;
|
|
@@ -138,7 +140,13 @@ function buildSegment(
|
|
|
138
140
|
);
|
|
139
141
|
}
|
|
140
142
|
case "cwd":
|
|
141
|
-
return segment(
|
|
143
|
+
return segment(
|
|
144
|
+
name,
|
|
145
|
+
formatDirectoryPath(ctx.cwd, runtime.homeDir, runtime.gitStatus?.root),
|
|
146
|
+
config,
|
|
147
|
+
"accent",
|
|
148
|
+
"directory",
|
|
149
|
+
);
|
|
142
150
|
case "tools": {
|
|
143
151
|
const activity = formatToolActivity(runtime);
|
|
144
152
|
return activity ? segment(name, activity, config, "accent", "runtime") : undefined;
|
|
@@ -320,11 +328,11 @@ export function truncateModel(
|
|
|
320
328
|
symbol: string,
|
|
321
329
|
direction: TruncationDirection,
|
|
322
330
|
): string {
|
|
323
|
-
const safeModel =
|
|
331
|
+
const safeModel = sanitizeTerminalText(model);
|
|
324
332
|
if (length === 0) return safeModel;
|
|
325
333
|
const graphemes = [...graphemeSegmenter.segment(safeModel)].map(({ segment }) => segment);
|
|
326
334
|
if (graphemes.length <= length) return safeModel;
|
|
327
|
-
const safeSymbol =
|
|
335
|
+
const safeSymbol = sanitizeTerminalText(symbol);
|
|
328
336
|
|
|
329
337
|
switch (direction) {
|
|
330
338
|
case "start":
|
|
@@ -340,67 +348,6 @@ export function truncateModel(
|
|
|
340
348
|
}
|
|
341
349
|
}
|
|
342
350
|
|
|
343
|
-
function sanitizeModelDisplay(value: string): string {
|
|
344
|
-
let result = "";
|
|
345
|
-
for (let index = 0; index < value.length; ) {
|
|
346
|
-
const codePoint = value.codePointAt(index) ?? 0;
|
|
347
|
-
const character = String.fromCodePoint(codePoint);
|
|
348
|
-
if (codePoint === 0x1b || codePoint === 0x9b || codePoint === 0x9d) {
|
|
349
|
-
index = skipTerminalEscape(value, index, codePoint);
|
|
350
|
-
continue;
|
|
351
|
-
}
|
|
352
|
-
if (
|
|
353
|
-
codePoint <= 0x1f ||
|
|
354
|
-
(codePoint >= 0x7f && codePoint <= 0x9f) ||
|
|
355
|
-
codePoint === 0x2028 ||
|
|
356
|
-
codePoint === 0x2029
|
|
357
|
-
) {
|
|
358
|
-
if (
|
|
359
|
-
codePoint === 0x09 ||
|
|
360
|
-
codePoint === 0x0a ||
|
|
361
|
-
codePoint === 0x0d ||
|
|
362
|
-
codePoint === 0x85 ||
|
|
363
|
-
codePoint === 0x2028 ||
|
|
364
|
-
codePoint === 0x2029
|
|
365
|
-
) {
|
|
366
|
-
result += " ";
|
|
367
|
-
}
|
|
368
|
-
index += character.length;
|
|
369
|
-
continue;
|
|
370
|
-
}
|
|
371
|
-
result += character;
|
|
372
|
-
index += character.length;
|
|
373
|
-
}
|
|
374
|
-
return result;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
function skipTerminalEscape(value: string, start: number, codePoint: number): number {
|
|
378
|
-
let index = start + 1;
|
|
379
|
-
const next = value.charCodeAt(index);
|
|
380
|
-
const isOsc = codePoint === 0x9d || (codePoint === 0x1b && next === 0x5d);
|
|
381
|
-
if (isOsc) {
|
|
382
|
-
if (codePoint === 0x1b) index += 1;
|
|
383
|
-
while (index < value.length) {
|
|
384
|
-
const current = value.charCodeAt(index);
|
|
385
|
-
if (current === 0x07 || current === 0x9c) return index + 1;
|
|
386
|
-
if (current === 0x1b && value.charCodeAt(index + 1) === 0x5c) return index + 2;
|
|
387
|
-
index += 1;
|
|
388
|
-
}
|
|
389
|
-
return index;
|
|
390
|
-
}
|
|
391
|
-
const isCsi = codePoint === 0x9b || (codePoint === 0x1b && next === 0x5b);
|
|
392
|
-
if (isCsi) {
|
|
393
|
-
if (codePoint === 0x1b) index += 1;
|
|
394
|
-
while (index < value.length) {
|
|
395
|
-
const current = value.charCodeAt(index);
|
|
396
|
-
index += 1;
|
|
397
|
-
if (current >= 0x40 && current <= 0x7e) break;
|
|
398
|
-
}
|
|
399
|
-
return index;
|
|
400
|
-
}
|
|
401
|
-
return Math.min(value.length, start + (codePoint === 0x1b ? 2 : 1));
|
|
402
|
-
}
|
|
403
|
-
|
|
404
351
|
export function shortenModel(model: string): string {
|
|
405
352
|
return model
|
|
406
353
|
.replace(/^claude-/, "")
|
package/src/statusline.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
1
2
|
import {
|
|
2
3
|
type ExtensionAPI,
|
|
3
4
|
type ExtensionContext,
|
|
@@ -44,6 +45,7 @@ export default function statusline(pi: ExtensionAPI) {
|
|
|
44
45
|
let gitStatusRequestId = 0;
|
|
45
46
|
let activeGitStatusTarget: { cwd: string; generation: number } | undefined;
|
|
46
47
|
let gitStatusRefreshInFlight = false;
|
|
48
|
+
let gitStatusAbortController: AbortController | undefined;
|
|
47
49
|
let gitStatusDebounceTimer: ReturnType<typeof setTimeout> | undefined;
|
|
48
50
|
let pendingGitStatusRefresh: { cwd: string; generation: number; requestId: number } | undefined;
|
|
49
51
|
|
|
@@ -62,6 +64,10 @@ export default function statusline(pi: ExtensionAPI) {
|
|
|
62
64
|
gitStatusDebounceTimer = undefined;
|
|
63
65
|
};
|
|
64
66
|
|
|
67
|
+
const abortGitStatusRefresh = (reason: string) => {
|
|
68
|
+
gitStatusAbortController?.abort(new DOMException(reason, "AbortError"));
|
|
69
|
+
};
|
|
70
|
+
|
|
65
71
|
const isActiveGitStatusTarget = (cwd: string, generation: number) =>
|
|
66
72
|
activeGitStatusTarget?.cwd === cwd &&
|
|
67
73
|
activeGitStatusTarget.generation === generation &&
|
|
@@ -78,14 +84,17 @@ export default function statusline(pi: ExtensionAPI) {
|
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
gitStatusRefreshInFlight = true;
|
|
87
|
+
const abortController = new AbortController();
|
|
88
|
+
gitStatusAbortController = abortController;
|
|
81
89
|
void (async () => {
|
|
82
90
|
try {
|
|
83
|
-
const summary = await readGitStatus(pi, cwd);
|
|
91
|
+
const summary = await readGitStatus(pi, cwd, abortController.signal);
|
|
84
92
|
if (isCurrentGitStatusRequest(cwd, generation, requestId)) setGitStatus(summary);
|
|
85
93
|
} catch {
|
|
86
94
|
if (isCurrentGitStatusRequest(cwd, generation, requestId)) setGitStatus(undefined);
|
|
87
95
|
} finally {
|
|
88
96
|
gitStatusRefreshInFlight = false;
|
|
97
|
+
if (gitStatusAbortController === abortController) gitStatusAbortController = undefined;
|
|
89
98
|
const pending = pendingGitStatusRefresh;
|
|
90
99
|
pendingGitStatusRefresh = undefined;
|
|
91
100
|
if (pending) runGitStatusRefresh(pending.cwd, pending.generation, pending.requestId);
|
|
@@ -119,7 +128,9 @@ export default function statusline(pi: ExtensionAPI) {
|
|
|
119
128
|
menuController = new AbortController();
|
|
120
129
|
const cwd = ctx.cwd;
|
|
121
130
|
activeSessionManager = ctx.sessionManager;
|
|
131
|
+
runtime.homeDir = homedir();
|
|
122
132
|
previewPalettePreset = undefined;
|
|
133
|
+
abortGitStatusRefresh("Statusline session context replaced");
|
|
123
134
|
clearGitStatusDebounce();
|
|
124
135
|
activeGitStatusTarget = ctx.mode === "tui" ? { cwd, generation } : undefined;
|
|
125
136
|
runtime.gitStatus = undefined;
|
|
@@ -136,6 +147,7 @@ export default function statusline(pi: ExtensionAPI) {
|
|
|
136
147
|
const refreshFooterGitStatus = () => refreshGitStatus(cwd, generation);
|
|
137
148
|
const branchUnsubscribe = footerData.onBranchChange(() => {
|
|
138
149
|
runtime.gitStatus = undefined;
|
|
150
|
+
abortGitStatusRefresh("Statusline Git branch changed");
|
|
139
151
|
clearGitStatusDebounce();
|
|
140
152
|
refreshFooterGitStatus();
|
|
141
153
|
tui.requestRender();
|
|
@@ -152,6 +164,7 @@ export default function statusline(pi: ExtensionAPI) {
|
|
|
152
164
|
clearInterval(clock);
|
|
153
165
|
if (isActiveGitStatusTarget(cwd, generation)) {
|
|
154
166
|
activeGitStatusTarget = undefined;
|
|
167
|
+
abortGitStatusRefresh("Statusline footer disposed");
|
|
155
168
|
clearGitStatusDebounce();
|
|
156
169
|
pendingGitStatusRefresh = undefined;
|
|
157
170
|
runtime.gitStatus = undefined;
|
|
@@ -229,6 +242,7 @@ export default function statusline(pi: ExtensionAPI) {
|
|
|
229
242
|
activeSessionManager = undefined;
|
|
230
243
|
previewPalettePreset = undefined;
|
|
231
244
|
activeGitStatusTarget = undefined;
|
|
245
|
+
abortGitStatusRefresh("Statusline session shut down");
|
|
232
246
|
clearGitStatusDebounce();
|
|
233
247
|
pendingGitStatusRefresh = undefined;
|
|
234
248
|
runtime.gitStatus = undefined;
|
|
@@ -324,7 +338,9 @@ export {
|
|
|
324
338
|
formatGitBranchValue,
|
|
325
339
|
formatGitStatusSummary,
|
|
326
340
|
type GitStatusSummary,
|
|
341
|
+
parseGitRoot,
|
|
327
342
|
parseGitStatusPorcelain,
|
|
343
|
+
readGitStatus,
|
|
328
344
|
} from "./git-status.js";
|
|
329
345
|
export {
|
|
330
346
|
contextColor,
|
package/src/terminal.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export function sanitizeTerminalText(value: string): string {
|
|
2
|
+
let result = "";
|
|
3
|
+
for (let index = 0; index < value.length; ) {
|
|
4
|
+
const codePoint = value.codePointAt(index) ?? 0;
|
|
5
|
+
const character = String.fromCodePoint(codePoint);
|
|
6
|
+
if (codePoint === 0x1b || codePoint === 0x9b || codePoint === 0x9d) {
|
|
7
|
+
index = skipTerminalEscape(value, index, codePoint);
|
|
8
|
+
continue;
|
|
9
|
+
}
|
|
10
|
+
if (
|
|
11
|
+
codePoint <= 0x1f ||
|
|
12
|
+
(codePoint >= 0x7f && codePoint <= 0x9f) ||
|
|
13
|
+
codePoint === 0x2028 ||
|
|
14
|
+
codePoint === 0x2029
|
|
15
|
+
) {
|
|
16
|
+
if (
|
|
17
|
+
codePoint === 0x09 ||
|
|
18
|
+
codePoint === 0x0a ||
|
|
19
|
+
codePoint === 0x0d ||
|
|
20
|
+
codePoint === 0x85 ||
|
|
21
|
+
codePoint === 0x2028 ||
|
|
22
|
+
codePoint === 0x2029
|
|
23
|
+
) {
|
|
24
|
+
result += " ";
|
|
25
|
+
}
|
|
26
|
+
index += character.length;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
result += character;
|
|
30
|
+
index += character.length;
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function skipTerminalEscape(value: string, start: number, codePoint: number): number {
|
|
36
|
+
let index = start + 1;
|
|
37
|
+
const next = value.charCodeAt(index);
|
|
38
|
+
const isOsc = codePoint === 0x9d || (codePoint === 0x1b && next === 0x5d);
|
|
39
|
+
if (isOsc) {
|
|
40
|
+
if (codePoint === 0x1b) index += 1;
|
|
41
|
+
while (index < value.length) {
|
|
42
|
+
const current = value.charCodeAt(index);
|
|
43
|
+
if (current === 0x07 || current === 0x9c) return index + 1;
|
|
44
|
+
if (current === 0x1b && value.charCodeAt(index + 1) === 0x5c) return index + 2;
|
|
45
|
+
index += 1;
|
|
46
|
+
}
|
|
47
|
+
return index;
|
|
48
|
+
}
|
|
49
|
+
const isCsi = codePoint === 0x9b || (codePoint === 0x1b && next === 0x5b);
|
|
50
|
+
if (isCsi) {
|
|
51
|
+
if (codePoint === 0x1b) index += 1;
|
|
52
|
+
while (index < value.length) {
|
|
53
|
+
const current = value.charCodeAt(index);
|
|
54
|
+
index += 1;
|
|
55
|
+
if (current >= 0x40 && current <= 0x7e) break;
|
|
56
|
+
}
|
|
57
|
+
return index;
|
|
58
|
+
}
|
|
59
|
+
return Math.min(value.length, start + (codePoint === 0x1b ? 2 : 1));
|
|
60
|
+
}
|