@hatchway/cli 0.51.3 → 0.51.4

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 (29) hide show
  1. package/dist/chunks/{Banner-_pJB7dCO.js → Banner-Dslid-AI.js} +2 -3
  2. package/dist/chunks/{Banner-_pJB7dCO.js.map → Banner-Dslid-AI.js.map} +1 -1
  3. package/dist/chunks/devtools-CPruVlOo.js.map +1 -1
  4. package/dist/chunks/{index-CovlIWnu.js → index-y6cN9k8l.js} +2 -3
  5. package/dist/chunks/{index-CovlIWnu.js.map → index-y6cN9k8l.js.map} +1 -1
  6. package/dist/chunks/{init-Eo7XainT.js → init-Cw1DkJP-.js} +6 -5
  7. package/dist/chunks/{init-Eo7XainT.js.map → init-Cw1DkJP-.js.map} +1 -1
  8. package/dist/chunks/{init-tui-Cgxsmmn-.js → init-tui-WELhLH16.js} +7 -6
  9. package/dist/chunks/{init-tui-Cgxsmmn-.js.map → init-tui-WELhLH16.js.map} +1 -1
  10. package/dist/chunks/{main-tui-D580QurF.js → main-tui-BI9X6TRI.js} +8 -7
  11. package/dist/chunks/{main-tui-D580QurF.js.map → main-tui-BI9X6TRI.js.map} +1 -1
  12. package/dist/chunks/{manager-0U0BIO9r.js → manager-LKbmrJIf.js} +6 -97
  13. package/dist/chunks/manager-LKbmrJIf.js.map +1 -0
  14. package/dist/chunks/{run-DbXViD2C.js → run-Bvfox4jS.js} +10 -9
  15. package/dist/chunks/{run-DbXViD2C.js.map → run-Bvfox4jS.js.map} +1 -1
  16. package/dist/chunks/{start-BEIpWjyK.js → start-Drql12dh.js} +8 -7
  17. package/dist/chunks/{start-BEIpWjyK.js.map → start-Drql12dh.js.map} +1 -1
  18. package/dist/chunks/{theme-CzLXk_6s.js → theme-BmodcXss.js} +3430 -631
  19. package/dist/chunks/theme-BmodcXss.js.map +1 -0
  20. package/dist/chunks/{use-app-Dw8M2HNg.js → use-app-0PVAweWQ.js} +2 -2
  21. package/dist/chunks/{use-app-Dw8M2HNg.js.map → use-app-0PVAweWQ.js.map} +1 -1
  22. package/dist/chunks/{useBuildState-DjvkDA6v.js → useBuildState-CGyaE4Ou.js} +3 -3
  23. package/dist/chunks/{useBuildState-DjvkDA6v.js.map → useBuildState-CGyaE4Ou.js.map} +1 -1
  24. package/dist/cli/index.js +5 -5
  25. package/dist/index.js +1 -53
  26. package/dist/index.js.map +1 -1
  27. package/package.json +1 -1
  28. package/dist/chunks/manager-0U0BIO9r.js.map +0 -1
  29. package/dist/chunks/theme-CzLXk_6s.js.map +0 -1
@@ -3,10 +3,13 @@ import { PassThrough, Stream } from 'node:stream';
3
3
  import process$1, { env, cwd } from 'node:process';
4
4
  import require$$0$1 from 'assert';
5
5
  import require$$2 from 'events';
6
+ import { execFileSync } from 'node:child_process';
7
+ import * as fs from 'node:fs';
8
+ import fs__default from 'node:fs';
9
+ import tty from 'node:tty';
6
10
  import chalk from 'chalk';
7
11
  import os from 'node:os';
8
12
  import { EventEmitter } from 'node:events';
9
- import * as fs from 'node:fs';
10
13
  import require$$1 from 'module';
11
14
  import { Buffer as Buffer$1 } from 'node:buffer';
12
15
 
@@ -1882,117 +1885,170 @@ function requireReact () {
1882
1885
  var reactExports = requireReact();
1883
1886
  var React = /*@__PURE__*/getDefaultExportFromCjs(reactExports);
1884
1887
 
1888
+ //#region src/function/debounce.ts
1889
+ /**
1890
+ * Creates a debounced function that delays invoking the provided function until after `debounceMs` milliseconds
1891
+ * have elapsed since the last time the debounced function was invoked. The debounced function also has a `cancel`
1892
+ * method to cancel any pending execution.
1893
+ *
1894
+ * @template F - The type of function.
1895
+ * @param func - The function to debounce.
1896
+ * @param debounceMs - The number of milliseconds to delay.
1897
+ * @param options - The options object
1898
+ * @param options.signal - An optional AbortSignal to cancel the debounced function.
1899
+ * @param options.edges - An optional array specifying whether the function should be invoked on the leading edge, trailing edge, or both.
1900
+ * @returns A new debounced function with a `cancel` method.
1901
+ *
1902
+ * @example
1903
+ * const debouncedFunction = debounce(() => {
1904
+ * console.log('Function executed');
1905
+ * }, 1000);
1906
+ *
1907
+ * // Will log 'Function executed' after 1 second if not called again in that time
1908
+ * debouncedFunction();
1909
+ *
1910
+ * // Will not log anything as the previous call is canceled
1911
+ * debouncedFunction.cancel();
1912
+ *
1913
+ * // With AbortSignal
1914
+ * const controller = new AbortController();
1915
+ * const signal = controller.signal;
1916
+ * const debouncedWithSignal = debounce(() => {
1917
+ * console.log('Function executed');
1918
+ * }, 1000, { signal });
1919
+ *
1920
+ * debouncedWithSignal();
1921
+ *
1922
+ * // Will cancel the debounced function call
1923
+ * controller.abort();
1924
+ */
1885
1925
  function debounce$1(func, debounceMs, { signal, edges } = {}) {
1886
- let pendingThis = undefined;
1887
- let pendingArgs = null;
1888
- const leading = edges != null && edges.includes('leading');
1889
- const trailing = edges == null || edges.includes('trailing');
1890
- const invoke = () => {
1891
- if (pendingArgs !== null) {
1892
- func.apply(pendingThis, pendingArgs);
1893
- pendingThis = undefined;
1894
- pendingArgs = null;
1895
- }
1896
- };
1897
- const onTimerEnd = () => {
1898
- if (trailing) {
1899
- invoke();
1900
- }
1901
- cancel();
1902
- };
1903
- let timeoutId = null;
1904
- const schedule = () => {
1905
- if (timeoutId != null) {
1906
- clearTimeout(timeoutId);
1907
- }
1908
- timeoutId = setTimeout(() => {
1909
- timeoutId = null;
1910
- onTimerEnd();
1911
- }, debounceMs);
1912
- };
1913
- const cancelTimer = () => {
1914
- if (timeoutId !== null) {
1915
- clearTimeout(timeoutId);
1916
- timeoutId = null;
1917
- }
1918
- };
1919
- const cancel = () => {
1920
- cancelTimer();
1921
- pendingThis = undefined;
1922
- pendingArgs = null;
1923
- };
1924
- const flush = () => {
1925
- invoke();
1926
- };
1927
- const debounced = function (...args) {
1928
- if (signal?.aborted) {
1929
- return;
1930
- }
1931
- pendingThis = this;
1932
- pendingArgs = args;
1933
- const isFirstCall = timeoutId == null;
1934
- schedule();
1935
- if (leading && isFirstCall) {
1936
- invoke();
1937
- }
1938
- };
1939
- debounced.schedule = schedule;
1940
- debounced.cancel = cancel;
1941
- debounced.flush = flush;
1942
- signal?.addEventListener('abort', cancel, { once: true });
1943
- return debounced;
1926
+ let pendingThis = void 0;
1927
+ let pendingArgs = null;
1928
+ const leading = edges != null && edges.includes("leading");
1929
+ const trailing = edges == null || edges.includes("trailing");
1930
+ const invoke = () => {
1931
+ if (pendingArgs !== null) {
1932
+ func.apply(pendingThis, pendingArgs);
1933
+ pendingThis = void 0;
1934
+ pendingArgs = null;
1935
+ }
1936
+ };
1937
+ const onTimerEnd = () => {
1938
+ if (trailing) invoke();
1939
+ cancel();
1940
+ };
1941
+ let timeoutId = null;
1942
+ const schedule = () => {
1943
+ if (timeoutId != null) clearTimeout(timeoutId);
1944
+ timeoutId = setTimeout(() => {
1945
+ timeoutId = null;
1946
+ onTimerEnd();
1947
+ }, debounceMs);
1948
+ };
1949
+ const cancelTimer = () => {
1950
+ if (timeoutId !== null) {
1951
+ clearTimeout(timeoutId);
1952
+ timeoutId = null;
1953
+ }
1954
+ };
1955
+ const cancel = () => {
1956
+ cancelTimer();
1957
+ pendingThis = void 0;
1958
+ pendingArgs = null;
1959
+ };
1960
+ const flush = () => {
1961
+ invoke();
1962
+ };
1963
+ const debounced = function(...args) {
1964
+ if (signal?.aborted) return;
1965
+ pendingThis = this;
1966
+ pendingArgs = args;
1967
+ const isFirstCall = timeoutId == null;
1968
+ schedule();
1969
+ if (leading && isFirstCall) invoke();
1970
+ };
1971
+ debounced.schedule = schedule;
1972
+ debounced.cancel = cancel;
1973
+ debounced.flush = flush;
1974
+ signal?.addEventListener("abort", cancel, { once: true });
1975
+ return debounced;
1944
1976
  }
1945
1977
 
1978
+ //#region src/compat/function/debounce.ts
1946
1979
  function debounce(func, debounceMs = 0, options = {}) {
1947
- if (typeof options !== 'object') {
1948
- options = {};
1949
- }
1950
- const { leading = false, trailing = true, maxWait } = options;
1951
- const edges = Array(2);
1952
- if (leading) {
1953
- edges[0] = 'leading';
1954
- }
1955
- if (trailing) {
1956
- edges[1] = 'trailing';
1957
- }
1958
- let result = undefined;
1959
- let pendingAt = null;
1960
- const _debounced = debounce$1(function (...args) {
1961
- result = func.apply(this, args);
1962
- pendingAt = null;
1963
- }, debounceMs, { edges });
1964
- const debounced = function (...args) {
1965
- if (maxWait != null) {
1966
- if (pendingAt === null) {
1967
- pendingAt = Date.now();
1968
- }
1969
- if (Date.now() - pendingAt >= maxWait) {
1970
- result = func.apply(this, args);
1971
- pendingAt = Date.now();
1972
- _debounced.cancel();
1973
- _debounced.schedule();
1974
- return result;
1975
- }
1976
- }
1977
- _debounced.apply(this, args);
1978
- return result;
1979
- };
1980
- const flush = () => {
1981
- _debounced.flush();
1982
- return result;
1983
- };
1984
- debounced.cancel = _debounced.cancel;
1985
- debounced.flush = flush;
1986
- return debounced;
1980
+ if (typeof options !== "object") options = {};
1981
+ const { leading = false, trailing = true, maxWait } = options;
1982
+ const edges = Array(2);
1983
+ if (leading) edges[0] = "leading";
1984
+ if (trailing) edges[1] = "trailing";
1985
+ let result = void 0;
1986
+ let pendingAt = null;
1987
+ const _debounced = debounce$1(function(...args) {
1988
+ result = func.apply(this, args);
1989
+ pendingAt = null;
1990
+ }, debounceMs, { edges });
1991
+ const debounced = function(...args) {
1992
+ if (maxWait != null) {
1993
+ if (pendingAt === null) pendingAt = Date.now();
1994
+ if (Date.now() - pendingAt >= maxWait) {
1995
+ result = func.apply(this, args);
1996
+ pendingAt = Date.now();
1997
+ _debounced.cancel();
1998
+ _debounced.schedule();
1999
+ return result;
2000
+ }
2001
+ }
2002
+ _debounced.apply(this, args);
2003
+ return result;
2004
+ };
2005
+ const flush = () => {
2006
+ _debounced.flush();
2007
+ return result;
2008
+ };
2009
+ debounced.cancel = _debounced.cancel;
2010
+ debounced.flush = flush;
2011
+ return debounced;
1987
2012
  }
1988
2013
 
2014
+ //#region src/compat/function/throttle.ts
2015
+ /**
2016
+ * Creates a throttled function that only invokes the provided function at most once
2017
+ * per every `throttleMs` milliseconds. Subsequent calls to the throttled function
2018
+ * within the wait time will not trigger the execution of the original function.
2019
+ *
2020
+ * @template F - The type of function.
2021
+ * @param func - The function to throttle.
2022
+ * @param throttleMs - The number of milliseconds to throttle executions to.
2023
+ * @param options - The options object
2024
+ * @param options.signal - An optional AbortSignal to cancel the throttled function.
2025
+ * @param options.leading - If `true`, the function will be invoked on the leading edge of the timeout.
2026
+ * @param options.trailing - If `true`, the function will be invoked on the trailing edge of the timeout.
2027
+ * @returns A new throttled function that accepts the same parameters as the original function.
2028
+ *
2029
+ * @example
2030
+ * const throttledFunction = throttle(() => {
2031
+ * console.log('Function executed');
2032
+ * }, 1000);
2033
+ *
2034
+ * // Will log 'Function executed' immediately
2035
+ * throttledFunction();
2036
+ *
2037
+ * // Will not log anything as it is within the throttle time
2038
+ * throttledFunction();
2039
+ *
2040
+ * // After 1 second
2041
+ * setTimeout(() => {
2042
+ * throttledFunction(); // Will log 'Function executed'
2043
+ * }, 1000);
2044
+ */
1989
2045
  function throttle(func, throttleMs = 0, options = {}) {
1990
- const { leading = true, trailing = true } = options;
1991
- return debounce(func, throttleMs, {
1992
- leading,
1993
- maxWait: throttleMs,
1994
- trailing,
1995
- });
2046
+ const { leading = true, trailing = true } = options;
2047
+ return debounce(func, throttleMs, {
2048
+ leading,
2049
+ maxWait: throttleMs,
2050
+ trailing
2051
+ });
1996
2052
  }
1997
2053
 
1998
2054
  /* globals WorkerGlobalScope, DedicatedWorkerGlobalScope, SharedWorkerGlobalScope, ServiceWorkerGlobalScope */
@@ -2049,7 +2105,18 @@ isBrowser ? () => {
2049
2105
  throw new Error('`process.cwd()` only works in Node.js, not the browser.');
2050
2106
  } : process$1.cwd;
2051
2107
 
2108
+ const cursorTo = (x, y) => {
2109
+ if (typeof x !== 'number') {
2110
+ throw new TypeError('The `x` argument is required');
2111
+ }
2112
+
2113
+ {
2114
+ return ESC + (x + 1) + 'G';
2115
+ }
2116
+ };
2117
+
2052
2118
  const cursorUp = (count = 1) => ESC + count + 'A';
2119
+ const cursorDown = (count = 1) => ESC + count + 'B';
2053
2120
 
2054
2121
  const cursorLeft = ESC + 'G';
2055
2122
  const cursorNextLine = ESC + 'E';
@@ -2067,6 +2134,8 @@ const eraseLines = count => {
2067
2134
 
2068
2135
  return clear;
2069
2136
  };
2137
+
2138
+ const eraseEndLine = ESC + 'K';
2070
2139
  const eraseLine = ESC + '2K';
2071
2140
  const eraseScreen = ESC + '2J';
2072
2141
 
@@ -2987,7 +3056,7 @@ function stripAnsi(string) {
2987
3056
 
2988
3057
  // Generated code.
2989
3058
 
2990
- function isAmbiguous(x) {
3059
+ function isAmbiguous$1(x) {
2991
3060
  return x === 0xA1
2992
3061
  || x === 0xA4
2993
3062
  || x === 0xA7
@@ -3205,13 +3274,13 @@ function isAmbiguous(x) {
3205
3274
  || x >= 0x100000 && x <= 0x10FFFD;
3206
3275
  }
3207
3276
 
3208
- function isFullWidth(x) {
3277
+ function isFullWidth$1(x) {
3209
3278
  return x === 0x3000
3210
3279
  || x >= 0xFF01 && x <= 0xFF60
3211
3280
  || x >= 0xFFE0 && x <= 0xFFE6;
3212
3281
  }
3213
3282
 
3214
- function isWide(x) {
3283
+ function isWide$1(x) {
3215
3284
  return x >= 0x1100 && x <= 0x115F
3216
3285
  || x === 0x231A
3217
3286
  || x === 0x231B
@@ -3350,19 +3419,19 @@ function isWide(x) {
3350
3419
  || x >= 0x30000 && x <= 0x3FFFD;
3351
3420
  }
3352
3421
 
3353
- function validate(codePoint) {
3422
+ function validate$1(codePoint) {
3354
3423
  if (!Number.isSafeInteger(codePoint)) {
3355
3424
  throw new TypeError(`Expected a code point, got \`${typeof codePoint}\`.`);
3356
3425
  }
3357
3426
  }
3358
3427
 
3359
- function eastAsianWidth(codePoint, {ambiguousAsWide = false} = {}) {
3360
- validate(codePoint);
3428
+ function eastAsianWidth$1(codePoint, {ambiguousAsWide = false} = {}) {
3429
+ validate$1(codePoint);
3361
3430
 
3362
3431
  if (
3363
- isFullWidth(codePoint)
3364
- || isWide(codePoint)
3365
- || (ambiguousAsWide && isAmbiguous(codePoint))
3432
+ isFullWidth$1(codePoint)
3433
+ || isWide$1(codePoint)
3434
+ || (ambiguousAsWide && isAmbiguous$1(codePoint))
3366
3435
  ) {
3367
3436
  return 2;
3368
3437
  }
@@ -3375,7 +3444,7 @@ var emojiRegex = () => {
3375
3444
  return /[#*0-9]\uFE0F?\u20E3|[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23ED-\u23EF\u23F1\u23F2\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB\u25FC\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692\u2694-\u2697\u2699\u269B\u269C\u26A0\u26A7\u26AA\u26B0\u26B1\u26BD\u26BE\u26C4\u26C8\u26CF\u26D1\u26E9\u26F0-\u26F5\u26F7\u26F8\u26FA\u2702\u2708\u2709\u270F\u2712\u2714\u2716\u271D\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u27A1\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B55\u3030\u303D\u3297\u3299]\uFE0F?|[\u261D\u270C\u270D](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?|[\u270A\u270B](?:\uD83C[\uDFFB-\uDFFF])?|[\u23E9-\u23EC\u23F0\u23F3\u25FD\u2693\u26A1\u26AB\u26C5\u26CE\u26D4\u26EA\u26FD\u2705\u2728\u274C\u274E\u2753-\u2755\u2795-\u2797\u27B0\u27BF\u2B50]|\u26D3\uFE0F?(?:\u200D\uD83D\uDCA5)?|\u26F9(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|\u2764\uFE0F?(?:\u200D(?:\uD83D\uDD25|\uD83E\uDE79))?|\uD83C(?:[\uDC04\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37\uDF21\uDF24-\uDF2C\uDF36\uDF7D\uDF96\uDF97\uDF99-\uDF9B\uDF9E\uDF9F\uDFCD\uDFCE\uDFD4-\uDFDF\uDFF5\uDFF7]\uFE0F?|[\uDF85\uDFC2\uDFC7](?:\uD83C[\uDFFB-\uDFFF])?|[\uDFC4\uDFCA](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDFCB\uDFCC](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDCCF\uDD8E\uDD91-\uDD9A\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF43\uDF45-\uDF4A\uDF4C-\uDF7C\uDF7E-\uDF84\uDF86-\uDF93\uDFA0-\uDFC1\uDFC5\uDFC6\uDFC8\uDFC9\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF8-\uDFFF]|\uDDE6\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF]|\uDDE7\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF]|\uDDE8\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF7\uDDFA-\uDDFF]|\uDDE9\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF]|\uDDEA\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA]|\uDDEB\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7]|\uDDEC\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE]|\uDDED\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA]|\uDDEE\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9]|\uDDEF\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5]|\uDDF0\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF]|\uDDF1\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE]|\uDDF2\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF]|\uDDF3\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF]|\uDDF4\uD83C\uDDF2|\uDDF5\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE]|\uDDF6\uD83C\uDDE6|\uDDF7\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC]|\uDDF8\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF]|\uDDF9\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF]|\uDDFA\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF]|\uDDFB\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA]|\uDDFC\uD83C[\uDDEB\uDDF8]|\uDDFD\uD83C\uDDF0|\uDDFE\uD83C[\uDDEA\uDDF9]|\uDDFF\uD83C[\uDDE6\uDDF2\uDDFC]|\uDF44(?:\u200D\uD83D\uDFEB)?|\uDF4B(?:\u200D\uD83D\uDFE9)?|\uDFC3(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?|\uDFF3\uFE0F?(?:\u200D(?:\u26A7\uFE0F?|\uD83C\uDF08))?|\uDFF4(?:\u200D\u2620\uFE0F?|\uDB40\uDC67\uDB40\uDC62\uDB40(?:\uDC65\uDB40\uDC6E\uDB40\uDC67|\uDC73\uDB40\uDC63\uDB40\uDC74|\uDC77\uDB40\uDC6C\uDB40\uDC73)\uDB40\uDC7F)?)|\uD83D(?:[\uDC3F\uDCFD\uDD49\uDD4A\uDD6F\uDD70\uDD73\uDD76-\uDD79\uDD87\uDD8A-\uDD8D\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA\uDECB\uDECD-\uDECF\uDEE0-\uDEE5\uDEE9\uDEF0\uDEF3]\uFE0F?|[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC](?:\uD83C[\uDFFB-\uDFFF])?|[\uDC6E-\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4\uDEB5](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD74\uDD90](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?|[\uDC00-\uDC07\uDC09-\uDC14\uDC16-\uDC25\uDC27-\uDC3A\uDC3C-\uDC3E\uDC40\uDC44\uDC45\uDC51-\uDC65\uDC6A\uDC79-\uDC7B\uDC7D-\uDC80\uDC84\uDC88-\uDC8E\uDC90\uDC92-\uDCA9\uDCAB-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDDA4\uDDFB-\uDE2D\uDE2F-\uDE34\uDE37-\uDE41\uDE43\uDE44\uDE48-\uDE4A\uDE80-\uDEA2\uDEA4-\uDEB3\uDEB7-\uDEBF\uDEC1-\uDEC5\uDED0-\uDED2\uDED5-\uDED8\uDEDC-\uDEDF\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uDC08(?:\u200D\u2B1B)?|\uDC15(?:\u200D\uD83E\uDDBA)?|\uDC26(?:\u200D(?:\u2B1B|\uD83D\uDD25))?|\uDC3B(?:\u200D\u2744\uFE0F?)?|\uDC41\uFE0F?(?:\u200D\uD83D\uDDE8\uFE0F?)?|\uDC68(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDC68\uDC69]\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFC-\uDFFF])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFC-\uDFFF]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFD-\uDFFF]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFD\uDFFF]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFE])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFE]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?))?|\uDC69(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?[\uDC68\uDC69]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?|\uDC69\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?))|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFC-\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFC-\uDFFF]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFD-\uDFFF]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFD\uDFFF]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFB-\uDFFE])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFE]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFB-\uDFFE])))?))?|\uDD75(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDE2E(?:\u200D\uD83D\uDCA8)?|\uDE35(?:\u200D\uD83D\uDCAB)?|\uDE36(?:\u200D\uD83C\uDF2B\uFE0F?)?|\uDE42(?:\u200D[\u2194\u2195]\uFE0F?)?|\uDEB6(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?)|\uD83E(?:[\uDD0C\uDD0F\uDD18-\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5\uDEC3-\uDEC5\uDEF0\uDEF2-\uDEF8](?:\uD83C[\uDFFB-\uDFFF])?|[\uDD26\uDD35\uDD37-\uDD39\uDD3C-\uDD3E\uDDB8\uDDB9\uDDCD\uDDCF\uDDD4\uDDD6-\uDDDD](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDDDE\uDDDF](?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD0D\uDD0E\uDD10-\uDD17\uDD20-\uDD25\uDD27-\uDD2F\uDD3A\uDD3F-\uDD45\uDD47-\uDD76\uDD78-\uDDB4\uDDB7\uDDBA\uDDBC-\uDDCC\uDDD0\uDDE0-\uDDFF\uDE70-\uDE7C\uDE80-\uDE8A\uDE8E-\uDEC2\uDEC6\uDEC8\uDECD-\uDEDC\uDEDF-\uDEEA\uDEEF]|\uDDCE(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?|\uDDD1(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1|\uDDD1\u200D\uD83E\uDDD2(?:\u200D\uD83E\uDDD2)?|\uDDD2(?:\u200D\uD83E\uDDD2)?))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE])))?))?|\uDEF1(?:\uD83C(?:\uDFFB(?:\u200D\uD83E\uDEF2\uD83C[\uDFFC-\uDFFF])?|\uDFFC(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFD-\uDFFF])?|\uDFFD(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])?|\uDFFE(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFD\uDFFF])?|\uDFFF(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFE])?))?)/g;
3376
3445
  };
3377
3446
 
3378
- const segmenter$1 = new Intl.Segmenter();
3447
+ const segmenter$2 = new Intl.Segmenter();
3379
3448
 
3380
3449
  const defaultIgnorableCodePointRegex = /^\p{Default_Ignorable_Code_Point}$/u;
3381
3450
 
@@ -3400,7 +3469,7 @@ function stringWidth$1(string, options = {}) {
3400
3469
  let width = 0;
3401
3470
  const eastAsianWidthOptions = {ambiguousAsWide: !ambiguousIsNarrow};
3402
3471
 
3403
- for (const {segment: character} of segmenter$1.segment(string)) {
3472
+ for (const {segment: character} of segmenter$2.segment(string)) {
3404
3473
  const codePoint = character.codePointAt(0);
3405
3474
 
3406
3475
  // Ignore control characters
@@ -3448,7 +3517,7 @@ function stringWidth$1(string, options = {}) {
3448
3517
  continue;
3449
3518
  }
3450
3519
 
3451
- width += eastAsianWidth(codePoint, eastAsianWidthOptions);
3520
+ width += eastAsianWidth$1(codePoint, eastAsianWidthOptions);
3452
3521
  }
3453
3522
 
3454
3523
  return width;
@@ -3683,12 +3752,12 @@ const ESCAPES$2 = new Set([
3683
3752
 
3684
3753
  const END_CODE = 39;
3685
3754
  const ANSI_ESCAPE_BELL = '\u0007';
3686
- const ANSI_CSI = '[';
3687
- const ANSI_OSC = ']';
3688
- const ANSI_SGR_TERMINATOR = 'm';
3689
- const ANSI_ESCAPE_LINK = `${ANSI_OSC}8;;`;
3755
+ const ANSI_CSI$1 = '[';
3756
+ const ANSI_OSC$1 = ']';
3757
+ const ANSI_SGR_TERMINATOR$1 = 'm';
3758
+ const ANSI_ESCAPE_LINK = `${ANSI_OSC$1}8;;`;
3690
3759
 
3691
- const wrapAnsiCode = code => `${ESCAPES$2.values().next().value}${ANSI_CSI}${code}${ANSI_SGR_TERMINATOR}`;
3760
+ const wrapAnsiCode = code => `${ESCAPES$2.values().next().value}${ANSI_CSI$1}${code}${ANSI_SGR_TERMINATOR$1}`;
3692
3761
  const wrapAnsiHyperlink = url => `${ESCAPES$2.values().next().value}${ANSI_ESCAPE_LINK}${url}${ANSI_ESCAPE_BELL}`;
3693
3762
 
3694
3763
  // Calculate the length of words split on ' ', ignoring
@@ -3727,7 +3796,7 @@ const wrapWord = (rows, word, columns) => {
3727
3796
  isInsideEscape = false;
3728
3797
  isInsideLinkEscape = false;
3729
3798
  }
3730
- } else if (character === ANSI_SGR_TERMINATOR) {
3799
+ } else if (character === ANSI_SGR_TERMINATOR$1) {
3731
3800
  isInsideEscape = false;
3732
3801
  }
3733
3802
 
@@ -3774,7 +3843,7 @@ const stringVisibleTrimSpacesRight = string => {
3774
3843
  // 'hard' will never allow a string to take up more than columns characters.
3775
3844
  //
3776
3845
  // 'soft' allows long words to expand past the column length.
3777
- const exec = (string, columns, options = {}) => {
3846
+ const exec$1 = (string, columns, options = {}) => {
3778
3847
  if (options.trim !== false && string.trim() === '') {
3779
3848
  return '';
3780
3849
  }
@@ -3850,7 +3919,7 @@ const exec = (string, columns, options = {}) => {
3850
3919
  returnValue += character;
3851
3920
 
3852
3921
  if (ESCAPES$2.has(character)) {
3853
- const {groups} = new RegExp(`(?:\\${ANSI_CSI}(?<code>\\d+)m|\\${ANSI_ESCAPE_LINK}(?<uri>.*)${ANSI_ESCAPE_BELL})`).exec(preString.slice(preStringIndex)) || {groups: {}};
3922
+ const {groups} = new RegExp(`(?:\\${ANSI_CSI$1}(?<code>\\d+)m|\\${ANSI_ESCAPE_LINK}(?<uri>.*)${ANSI_ESCAPE_BELL})`).exec(preString.slice(preStringIndex)) || {groups: {}};
3854
3923
  if (groups.code !== undefined) {
3855
3924
  const code = Number.parseFloat(groups.code);
3856
3925
  escapeCode = code === END_CODE ? undefined : code;
@@ -3891,10 +3960,147 @@ function wrapAnsi(string, columns, options) {
3891
3960
  .normalize()
3892
3961
  .replaceAll('\r\n', '\n')
3893
3962
  .split('\n')
3894
- .map(line => exec(line, columns, options))
3963
+ .map(line => exec$1(line, columns, options))
3895
3964
  .join('\n');
3896
3965
  }
3897
3966
 
3967
+ const defaultColumns = 80;
3968
+ const defaultRows = 24;
3969
+
3970
+ const exec = (command, arguments_, {shell, env} = {}) =>
3971
+ execFileSync(command, arguments_, {
3972
+ encoding: 'utf8',
3973
+ stdio: ['ignore', 'pipe', 'ignore'],
3974
+ timeout: 500,
3975
+ shell,
3976
+ env,
3977
+ }).trim();
3978
+
3979
+ const create$1 = (columns, rows) => ({
3980
+ columns: Number.parseInt(columns, 10),
3981
+ rows: Number.parseInt(rows, 10),
3982
+ });
3983
+
3984
+ const createIfNotDefault = (maybeColumns, maybeRows) => {
3985
+ const {columns, rows} = create$1(maybeColumns, maybeRows);
3986
+
3987
+ if (Number.isNaN(columns) || Number.isNaN(rows)) {
3988
+ return;
3989
+ }
3990
+
3991
+ if (columns === defaultColumns && rows === defaultRows) {
3992
+ return;
3993
+ }
3994
+
3995
+ return {columns, rows};
3996
+ };
3997
+
3998
+ const isForegroundProcess = () => {
3999
+ if (process$1.platform !== 'linux') {
4000
+ return true;
4001
+ }
4002
+
4003
+ try {
4004
+ const statContents = fs__default.readFileSync('/proc/self/stat', 'utf8');
4005
+ const closingParenthesisIndex = statContents.lastIndexOf(') ');
4006
+
4007
+ if (closingParenthesisIndex === -1) {
4008
+ return false;
4009
+ }
4010
+
4011
+ const statFields = statContents.slice(closingParenthesisIndex + 2).trim().split(/\s+/);
4012
+ const processGroupId = Number.parseInt(statFields[2], 10);
4013
+ const foregroundProcessGroupId = Number.parseInt(statFields[5], 10);
4014
+
4015
+ if (Number.isNaN(processGroupId) || Number.isNaN(foregroundProcessGroupId)) {
4016
+ return false;
4017
+ }
4018
+
4019
+ if (foregroundProcessGroupId <= 0) {
4020
+ return false;
4021
+ }
4022
+
4023
+ return processGroupId === foregroundProcessGroupId;
4024
+ } catch {
4025
+ return false;
4026
+ }
4027
+ };
4028
+
4029
+ function terminalSize() {
4030
+ const {env, stdout, stderr} = process$1;
4031
+
4032
+ if (stdout?.columns && stdout?.rows) {
4033
+ return create$1(stdout.columns, stdout.rows);
4034
+ }
4035
+
4036
+ if (stderr?.columns && stderr?.rows) {
4037
+ return create$1(stderr.columns, stderr.rows);
4038
+ }
4039
+
4040
+ // These values are static, so not the first choice.
4041
+ if (env.COLUMNS && env.LINES) {
4042
+ return create$1(env.COLUMNS, env.LINES);
4043
+ }
4044
+
4045
+ const fallback = {
4046
+ columns: defaultColumns,
4047
+ rows: defaultRows,
4048
+ };
4049
+
4050
+ if (process$1.platform === 'win32') {
4051
+ // We include `tput` for Windows users using Git Bash.
4052
+ return tput() ?? fallback;
4053
+ }
4054
+
4055
+ if (process$1.platform === 'darwin') {
4056
+ return devTty() ?? tput() ?? fallback;
4057
+ }
4058
+
4059
+ return devTty() ?? tput() ?? resize() ?? fallback;
4060
+ }
4061
+
4062
+ const devTty = () => {
4063
+ try {
4064
+ // eslint-disable-next-line no-bitwise
4065
+ const flags = process$1.platform === 'darwin' ? fs__default.constants.O_EVTONLY | fs__default.constants.O_NONBLOCK : fs__default.constants.O_NONBLOCK;
4066
+ // eslint-disable-next-line new-cap
4067
+ const {columns, rows} = tty.WriteStream(fs__default.openSync('/dev/tty', flags));
4068
+ return {columns, rows};
4069
+ } catch {}
4070
+ };
4071
+
4072
+ // On macOS, this only returns correct values when stdout is not redirected.
4073
+ const tput = () => {
4074
+ try {
4075
+ // `tput` requires the `TERM` environment variable to be set.
4076
+ const columns = exec('tput', ['cols'], {env: {TERM: 'dumb', ...process$1.env}});
4077
+ const rows = exec('tput', ['lines'], {env: {TERM: 'dumb', ...process$1.env}});
4078
+
4079
+ if (columns && rows) {
4080
+ return createIfNotDefault(columns, rows);
4081
+ }
4082
+ } catch {}
4083
+ };
4084
+
4085
+ // Only exists on Linux.
4086
+ const resize = () => {
4087
+ // `resize` is preferred as it works even when all file descriptors are redirected
4088
+ // https://linux.die.net/man/1/resize
4089
+ try {
4090
+ if (!isForegroundProcess()) {
4091
+ return;
4092
+ }
4093
+
4094
+ const size = exec('resize', ['-u']).match(/\d+/g);
4095
+
4096
+ if (size.length === 2) {
4097
+ return createIfNotDefault(size[0], size[1]);
4098
+ }
4099
+ } catch {}
4100
+ };
4101
+
4102
+ const isDev = () => process$1.env['DEV'] === 'true';
4103
+
3898
4104
  var reactReconciler = {exports: {}};
3899
4105
 
3900
4106
  var reactReconciler_production = {exports: {}};
@@ -35970,11 +36176,343 @@ function requireReactReconciler () {
35970
36176
  var reactReconcilerExports = requireReactReconciler();
35971
36177
  var createReconciler = /*@__PURE__*/getDefaultExportFromCjs(reactReconcilerExports);
35972
36178
 
36179
+ var schedulerExports = requireScheduler();
36180
+
36181
+ // Generated by scripts/build.js
36182
+
36183
+ const ambiguousMinimalCodePoint = 161;
36184
+ const ambiguousMaximumCodePoint = 1114109;
36185
+ const ambiguousRanges = [161, 161, 164, 164, 167, 168, 170, 170, 173, 174, 176, 180, 182, 186, 188, 191, 198, 198, 208, 208, 215, 216, 222, 225, 230, 230, 232, 234, 236, 237, 240, 240, 242, 243, 247, 250, 252, 252, 254, 254, 257, 257, 273, 273, 275, 275, 283, 283, 294, 295, 299, 299, 305, 307, 312, 312, 319, 322, 324, 324, 328, 331, 333, 333, 338, 339, 358, 359, 363, 363, 462, 462, 464, 464, 466, 466, 468, 468, 470, 470, 472, 472, 474, 474, 476, 476, 593, 593, 609, 609, 708, 708, 711, 711, 713, 715, 717, 717, 720, 720, 728, 731, 733, 733, 735, 735, 768, 879, 913, 929, 931, 937, 945, 961, 963, 969, 1025, 1025, 1040, 1103, 1105, 1105, 8208, 8208, 8211, 8214, 8216, 8217, 8220, 8221, 8224, 8226, 8228, 8231, 8240, 8240, 8242, 8243, 8245, 8245, 8251, 8251, 8254, 8254, 8308, 8308, 8319, 8319, 8321, 8324, 8364, 8364, 8451, 8451, 8453, 8453, 8457, 8457, 8467, 8467, 8470, 8470, 8481, 8482, 8486, 8486, 8491, 8491, 8531, 8532, 8539, 8542, 8544, 8555, 8560, 8569, 8585, 8585, 8592, 8601, 8632, 8633, 8658, 8658, 8660, 8660, 8679, 8679, 8704, 8704, 8706, 8707, 8711, 8712, 8715, 8715, 8719, 8719, 8721, 8721, 8725, 8725, 8730, 8730, 8733, 8736, 8739, 8739, 8741, 8741, 8743, 8748, 8750, 8750, 8756, 8759, 8764, 8765, 8776, 8776, 8780, 8780, 8786, 8786, 8800, 8801, 8804, 8807, 8810, 8811, 8814, 8815, 8834, 8835, 8838, 8839, 8853, 8853, 8857, 8857, 8869, 8869, 8895, 8895, 8978, 8978, 9312, 9449, 9451, 9547, 9552, 9587, 9600, 9615, 9618, 9621, 9632, 9633, 9635, 9641, 9650, 9651, 9654, 9655, 9660, 9661, 9664, 9665, 9670, 9672, 9675, 9675, 9678, 9681, 9698, 9701, 9711, 9711, 9733, 9734, 9737, 9737, 9742, 9743, 9756, 9756, 9758, 9758, 9792, 9792, 9794, 9794, 9824, 9825, 9827, 9829, 9831, 9834, 9836, 9837, 9839, 9839, 9886, 9887, 9919, 9919, 9926, 9933, 9935, 9939, 9941, 9953, 9955, 9955, 9960, 9961, 9963, 9969, 9972, 9972, 9974, 9977, 9979, 9980, 9982, 9983, 10045, 10045, 10102, 10111, 11094, 11097, 12872, 12879, 57344, 63743, 65024, 65039, 65533, 65533, 127232, 127242, 127248, 127277, 127280, 127337, 127344, 127373, 127375, 127376, 127387, 127404, 917760, 917999, 983040, 1048573, 1048576, 1114109];
36186
+
36187
+ const fullwidthMinimalCodePoint = 12288;
36188
+ const fullwidthMaximumCodePoint = 65510;
36189
+ const fullwidthRanges = [12288, 12288, 65281, 65376, 65504, 65510];
36190
+
36191
+ const wideMinimalCodePoint = 4352;
36192
+ const wideMaximumCodePoint = 262141;
36193
+ const wideRanges = [4352, 4447, 8986, 8987, 9001, 9002, 9193, 9196, 9200, 9200, 9203, 9203, 9725, 9726, 9748, 9749, 9776, 9783, 9800, 9811, 9855, 9855, 9866, 9871, 9875, 9875, 9889, 9889, 9898, 9899, 9917, 9918, 9924, 9925, 9934, 9934, 9940, 9940, 9962, 9962, 9970, 9971, 9973, 9973, 9978, 9978, 9981, 9981, 9989, 9989, 9994, 9995, 10024, 10024, 10060, 10060, 10062, 10062, 10067, 10069, 10071, 10071, 10133, 10135, 10160, 10160, 10175, 10175, 11035, 11036, 11088, 11088, 11093, 11093, 11904, 11929, 11931, 12019, 12032, 12245, 12272, 12287, 12289, 12350, 12353, 12438, 12441, 12543, 12549, 12591, 12593, 12686, 12688, 12773, 12783, 12830, 12832, 12871, 12880, 42124, 42128, 42182, 43360, 43388, 44032, 55203, 63744, 64255, 65040, 65049, 65072, 65106, 65108, 65126, 65128, 65131, 94176, 94180, 94192, 94198, 94208, 101589, 101631, 101662, 101760, 101874, 110576, 110579, 110581, 110587, 110589, 110590, 110592, 110882, 110898, 110898, 110928, 110930, 110933, 110933, 110948, 110951, 110960, 111355, 119552, 119638, 119648, 119670, 126980, 126980, 127183, 127183, 127374, 127374, 127377, 127386, 127488, 127490, 127504, 127547, 127552, 127560, 127568, 127569, 127584, 127589, 127744, 127776, 127789, 127797, 127799, 127868, 127870, 127891, 127904, 127946, 127951, 127955, 127968, 127984, 127988, 127988, 127992, 128062, 128064, 128064, 128066, 128252, 128255, 128317, 128331, 128334, 128336, 128359, 128378, 128378, 128405, 128406, 128420, 128420, 128507, 128591, 128640, 128709, 128716, 128716, 128720, 128722, 128725, 128728, 128732, 128735, 128747, 128748, 128756, 128764, 128992, 129003, 129008, 129008, 129292, 129338, 129340, 129349, 129351, 129535, 129648, 129660, 129664, 129674, 129678, 129734, 129736, 129736, 129741, 129756, 129759, 129770, 129775, 129784, 131072, 196605, 196608, 262141];
36194
+
36195
+ /**
36196
+ Binary search on a sorted flat array of [start, end] pairs.
36197
+
36198
+ @param {number[]} ranges - Flat array of inclusive [start, end] range pairs, e.g. [0, 5, 10, 20].
36199
+ @param {number} codePoint - The value to search for.
36200
+ @returns {boolean} Whether the value falls within any of the ranges.
36201
+ */
36202
+ const isInRange = (ranges, codePoint) => {
36203
+ let low = 0;
36204
+ let high = Math.floor(ranges.length / 2) - 1;
36205
+ while (low <= high) {
36206
+ const mid = Math.floor((low + high) / 2);
36207
+ const i = mid * 2;
36208
+ if (codePoint < ranges[i]) {
36209
+ high = mid - 1;
36210
+ } else if (codePoint > ranges[i + 1]) {
36211
+ low = mid + 1;
36212
+ } else {
36213
+ return true;
36214
+ }
36215
+ }
36216
+
36217
+ return false;
36218
+ };
36219
+
36220
+ const commonCjkCodePoint = 0x4E_00;
36221
+ const [wideFastPathStart, wideFastPathEnd] = /* #__PURE__ */ findWideFastPathRange(wideRanges);
36222
+
36223
+ // Use a hot-path range so common `isWide` calls can skip binary search.
36224
+ // The range containing U+4E00 covers common CJK ideographs;
36225
+ // fallback to the largest range for resilience to Unicode table changes.
36226
+ function findWideFastPathRange(ranges) {
36227
+ let fastPathStart = ranges[0];
36228
+ let fastPathEnd = ranges[1];
36229
+
36230
+ for (let index = 0; index < ranges.length; index += 2) {
36231
+ const start = ranges[index];
36232
+ const end = ranges[index + 1];
36233
+
36234
+ if (
36235
+ commonCjkCodePoint >= start
36236
+ && commonCjkCodePoint <= end
36237
+ ) {
36238
+ return [start, end];
36239
+ }
36240
+
36241
+ if ((end - start) > (fastPathEnd - fastPathStart)) {
36242
+ fastPathStart = start;
36243
+ fastPathEnd = end;
36244
+ }
36245
+ }
36246
+
36247
+ return [fastPathStart, fastPathEnd];
36248
+ }
36249
+
36250
+ const isAmbiguous = codePoint => {
36251
+ if (
36252
+ codePoint < ambiguousMinimalCodePoint
36253
+ || codePoint > ambiguousMaximumCodePoint
36254
+ ) {
36255
+ return false;
36256
+ }
36257
+
36258
+ return isInRange(ambiguousRanges, codePoint);
36259
+ };
36260
+
36261
+ const isFullWidth = codePoint => {
36262
+ if (
36263
+ codePoint < fullwidthMinimalCodePoint
36264
+ || codePoint > fullwidthMaximumCodePoint
36265
+ ) {
36266
+ return false;
36267
+ }
36268
+
36269
+ return isInRange(fullwidthRanges, codePoint);
36270
+ };
36271
+
36272
+ const isWide = codePoint => {
36273
+ if (
36274
+ codePoint >= wideFastPathStart
36275
+ && codePoint <= wideFastPathEnd
36276
+ ) {
36277
+ return true;
36278
+ }
36279
+
36280
+ if (
36281
+ codePoint < wideMinimalCodePoint
36282
+ || codePoint > wideMaximumCodePoint
36283
+ ) {
36284
+ return false;
36285
+ }
36286
+
36287
+ return isInRange(wideRanges, codePoint);
36288
+ };
36289
+
36290
+ function validate(codePoint) {
36291
+ if (!Number.isSafeInteger(codePoint)) {
36292
+ throw new TypeError(`Expected a code point, got \`${typeof codePoint}\`.`);
36293
+ }
36294
+ }
36295
+
36296
+ function eastAsianWidth(codePoint, {ambiguousAsWide = false} = {}) {
36297
+ validate(codePoint);
36298
+
36299
+ if (
36300
+ isFullWidth(codePoint)
36301
+ || isWide(codePoint)
36302
+ || (ambiguousAsWide && isAmbiguous(codePoint))
36303
+ ) {
36304
+ return 2;
36305
+ }
36306
+
36307
+ return 1;
36308
+ }
36309
+
36310
+ /**
36311
+ Logic:
36312
+ - Segment graphemes to match how terminals render clusters.
36313
+ - Width rules:
36314
+ 1. Skip non-printing clusters (Default_Ignorable, Control, pure Mark, lone Surrogates). Tabs are ignored by design.
36315
+ 2. RGI emoji clusters (\p{RGI_Emoji}) are double-width.
36316
+ 3. Minimally-qualified/unqualified emoji clusters (ZWJ sequences with 2+ Extended_Pictographic, or keycap sequences) are double-width.
36317
+ 4. Hangul jamo collapse each standard modern Hangul L+V or L+V+T syllable piece to width 2.
36318
+ Unmatched repeated leading/vowel/trailing jamo stay additive because that matches how the terminals we target render them.
36319
+ 5. Otherwise use East Asian Width of the cluster's first visible code point, and add widths for trailing Halfwidth/Fullwidth Forms within the same cluster (e.g., dakuten/handakuten/prolonged sound mark).
36320
+ */
36321
+
36322
+ const segmenter$1 = new Intl.Segmenter();
36323
+
36324
+ // Whole-cluster zero-width
36325
+ const zeroWidthClusterRegex = /^(?:\p{Default_Ignorable_Code_Point}|\p{Control}|\p{Format}|\p{Mark}|\p{Surrogate})+$/v;
36326
+
36327
+ // Pick the base scalar if the cluster starts with Prepend/Format/Marks
36328
+ const leadingNonPrintingRegex = /^[\p{Default_Ignorable_Code_Point}\p{Control}\p{Format}\p{Mark}\p{Surrogate}]+/v;
36329
+
36330
+ // RGI emoji sequences
36331
+ const rgiEmojiRegex = /^\p{RGI_Emoji}$/v;
36332
+
36333
+ // Detect minimally-qualified/unqualified emoji sequences (missing VS16 but still render as double-width)
36334
+ const unqualifiedKeycapRegex = /^[\d#*]\u20E3$/;
36335
+ const extendedPictographicRegex = /\p{Extended_Pictographic}/gu;
36336
+
36337
+ function isDoubleWidthNonRgiEmojiSequence(segment) {
36338
+ // Real emoji clusters are < 30 chars; guard against pathological input
36339
+ if (segment.length > 50) {
36340
+ return false;
36341
+ }
36342
+
36343
+ if (unqualifiedKeycapRegex.test(segment)) {
36344
+ return true;
36345
+ }
36346
+
36347
+ // ZWJ sequences with 2+ Extended_Pictographic
36348
+ if (segment.includes('\u200D')) {
36349
+ const pictographics = segment.match(extendedPictographicRegex);
36350
+ return pictographics !== null && pictographics.length >= 2;
36351
+ }
36352
+
36353
+ return false;
36354
+ }
36355
+
36356
+ function baseVisible(segment) {
36357
+ return segment.replace(leadingNonPrintingRegex, '');
36358
+ }
36359
+
36360
+ function isZeroWidthCluster(segment) {
36361
+ return zeroWidthClusterRegex.test(segment);
36362
+ }
36363
+
36364
+ function isHangulLeadingJamo(codePoint) {
36365
+ return (codePoint >= 0x11_00 && codePoint <= 0x11_5F)
36366
+ || (codePoint >= 0xA9_60 && codePoint <= 0xA9_7C);
36367
+ }
36368
+
36369
+ function isHangulVowelJamo(codePoint) {
36370
+ return (codePoint >= 0x11_60 && codePoint <= 0x11_A7)
36371
+ || (codePoint >= 0xD7_B0 && codePoint <= 0xD7_C6);
36372
+ }
36373
+
36374
+ function isHangulTrailingJamo(codePoint) {
36375
+ return (codePoint >= 0x11_A8 && codePoint <= 0x11_FF)
36376
+ || (codePoint >= 0xD7_CB && codePoint <= 0xD7_FB);
36377
+ }
36378
+
36379
+ function isHangulJamo(codePoint) {
36380
+ return isHangulLeadingJamo(codePoint)
36381
+ || isHangulVowelJamo(codePoint)
36382
+ || isHangulTrailingJamo(codePoint);
36383
+ }
36384
+
36385
+ function hangulClusterWidth(visibleSegment, eastAsianWidthOptions) {
36386
+ const codePoints = [];
36387
+
36388
+ for (const character of visibleSegment) {
36389
+ if (zeroWidthClusterRegex.test(character)) {
36390
+ continue;
36391
+ }
36392
+
36393
+ codePoints.push(character.codePointAt(0));
36394
+ }
36395
+
36396
+ if (codePoints.length === 0) {
36397
+ return undefined;
36398
+ }
36399
+
36400
+ let width = 0;
36401
+
36402
+ for (let index = 0; index < codePoints.length; index++) {
36403
+ const codePoint = codePoints[index];
36404
+ if (!isHangulJamo(codePoint)) {
36405
+ if (width === 0) {
36406
+ return undefined;
36407
+ }
36408
+
36409
+ // Mixed cluster (e.g., L + precomposed syllable): use EAW for non-jamo remainder
36410
+ for (let remaining = index; remaining < codePoints.length; remaining++) {
36411
+ width += eastAsianWidth(codePoints[remaining], eastAsianWidthOptions);
36412
+ }
36413
+
36414
+ return width;
36415
+ }
36416
+
36417
+ // Modern Hangul L+V(+T) shapes as one syllable block. Unmatched jamo stay additive:
36418
+ // U+1100 U+1100 U+1161 => U+1100 + (U+1100 U+1161) => 2 + 2.
36419
+ if (
36420
+ isHangulLeadingJamo(codePoint)
36421
+ && isHangulVowelJamo(codePoints[index + 1])
36422
+ ) {
36423
+ width += 2;
36424
+ index += isHangulTrailingJamo(codePoints[index + 2]) ? 2 : 1;
36425
+ continue;
36426
+ }
36427
+
36428
+ width += eastAsianWidth(codePoint, eastAsianWidthOptions);
36429
+ }
36430
+
36431
+ return width;
36432
+ }
36433
+
36434
+ function trailingHalfwidthWidth(visibleSegment, eastAsianWidthOptions) {
36435
+ let extra = 0;
36436
+ let first = true;
36437
+
36438
+ for (const character of visibleSegment) {
36439
+ if (first) {
36440
+ first = false;
36441
+ continue;
36442
+ }
36443
+
36444
+ if (character >= '\uFF00' && character <= '\uFFEF') {
36445
+ extra += eastAsianWidth(character.codePointAt(0), eastAsianWidthOptions);
36446
+ }
36447
+ }
36448
+
36449
+ return extra;
36450
+ }
36451
+
36452
+ function stringWidth(input, options = {}) {
36453
+ if (typeof input !== 'string' || input.length === 0) {
36454
+ return 0;
36455
+ }
36456
+
36457
+ const {
36458
+ ambiguousIsNarrow = true,
36459
+ countAnsiEscapeCodes = false,
36460
+ } = options;
36461
+
36462
+ let string = input;
36463
+
36464
+ // Avoid calling stripAnsi when there are no ANSI escape sequences (ESC = 0x1B, CSI = 0x9B)
36465
+ if (!countAnsiEscapeCodes && (string.includes('\u001B') || string.includes('\u009B'))) {
36466
+ string = stripAnsi(string);
36467
+ }
36468
+
36469
+ if (string.length === 0) {
36470
+ return 0;
36471
+ }
36472
+
36473
+ // Fast path: printable ASCII (0x20–0x7E) needs no segmenter, regex, or EAW lookup — width equals length.
36474
+ if (/^[\u0020-\u007E]*$/.test(string)) {
36475
+ return string.length;
36476
+ }
36477
+
36478
+ let width = 0;
36479
+ const eastAsianWidthOptions = {ambiguousAsWide: !ambiguousIsNarrow};
36480
+
36481
+ for (const {segment} of segmenter$1.segment(string)) {
36482
+ // Zero-width / non-printing clusters
36483
+ if (isZeroWidthCluster(segment)) {
36484
+ continue;
36485
+ }
36486
+
36487
+ // Emoji width logic
36488
+ if (rgiEmojiRegex.test(segment) || isDoubleWidthNonRgiEmojiSequence(segment)) {
36489
+ width += 2;
36490
+ continue;
36491
+ }
36492
+
36493
+ const visibleSegment = baseVisible(segment);
36494
+ const hangulWidth = hangulClusterWidth(visibleSegment, eastAsianWidthOptions);
36495
+ if (hangulWidth !== undefined) {
36496
+ width += hangulWidth;
36497
+ continue;
36498
+ }
36499
+
36500
+ // Everything else: EAW of the cluster’s first visible scalar
36501
+ const codePoint = visibleSegment.codePointAt(0);
36502
+ width += eastAsianWidth(codePoint, eastAsianWidthOptions);
36503
+
36504
+ // Add width for trailing Halfwidth and Fullwidth Forms (e.g., ゙, ゚, ー)
36505
+ width += trailingHalfwidthWidth(visibleSegment, eastAsianWidthOptions);
36506
+ }
36507
+
36508
+ return width;
36509
+ }
36510
+
35973
36511
  function widestLine(string) {
35974
36512
  let lineWidth = 0;
35975
36513
 
35976
36514
  for (const line of string.split('\n')) {
35977
- lineWidth = Math.max(lineWidth, stringWidth$1(line));
36515
+ lineWidth = Math.max(lineWidth, stringWidth(line));
35978
36516
  }
35979
36517
 
35980
36518
  return lineWidth;
@@ -36004,74 +36542,609 @@ function isFullwidthCodePoint(codePoint) {
36004
36542
  return false;
36005
36543
  }
36006
36544
 
36007
- return isFullWidth(codePoint) || isWide(codePoint);
36545
+ return isFullWidth$1(codePoint) || isWide$1(codePoint);
36008
36546
  }
36009
36547
 
36010
- // \x1b and \x9b
36011
- const ESCAPES$1 = new Set([27, 155]);
36548
+ const ESCAPE_CODE_POINT = 27;
36549
+ const C1_DCS_CODE_POINT = 144;
36550
+ const C1_SOS_CODE_POINT = 152;
36551
+ const C1_CSI_CODE_POINT = 155;
36552
+ const C1_ST_CODE_POINT = 156;
36553
+ const C1_OSC_CODE_POINT = 157;
36554
+ const C1_PM_CODE_POINT = 158;
36555
+ const C1_APC_CODE_POINT = 159;
36556
+ const ESCAPES$1 = new Set([
36557
+ ESCAPE_CODE_POINT,
36558
+ C1_DCS_CODE_POINT,
36559
+ C1_SOS_CODE_POINT,
36560
+ C1_CSI_CODE_POINT,
36561
+ C1_ST_CODE_POINT,
36562
+ C1_OSC_CODE_POINT,
36563
+ C1_PM_CODE_POINT,
36564
+ C1_APC_CODE_POINT,
36565
+ ]);
36566
+
36567
+ const ESCAPE = '\u001B';
36568
+ const ANSI_BELL = '\u0007';
36569
+ const ANSI_CSI = '[';
36570
+ const ANSI_OSC = ']';
36571
+ const ANSI_DCS = 'P';
36572
+ const ANSI_SOS = 'X';
36573
+ const ANSI_PM = '^';
36574
+ const ANSI_APC = '_';
36575
+ const ANSI_SGR_TERMINATOR = 'm';
36576
+ const ANSI_OSC_TERMINATOR = '\\';
36577
+ const ANSI_STRING_TERMINATOR = `${ESCAPE}${ANSI_OSC_TERMINATOR}`;
36578
+ const C1_OSC = '\u009D';
36579
+ const C1_STRING_TERMINATOR = '\u009C';
36580
+ const ANSI_HYPERLINK_ESC_PREFIX = `${ESCAPE}${ANSI_OSC}8;`;
36581
+ const ANSI_HYPERLINK_C1_PREFIX = `${C1_OSC}8;`;
36582
+ const ANSI_HYPERLINK_ESC_CLOSE = `${ANSI_HYPERLINK_ESC_PREFIX};`;
36583
+ const ANSI_HYPERLINK_C1_CLOSE = `${ANSI_HYPERLINK_C1_PREFIX};`;
36012
36584
 
36013
36585
  const CODE_POINT_0 = '0'.codePointAt(0);
36014
36586
  const CODE_POINT_9 = '9'.codePointAt(0);
36587
+ const CODE_POINT_SEMICOLON = ';'.codePointAt(0);
36588
+ const CODE_POINT_COLON = ':'.codePointAt(0);
36589
+ // ECMA-48 CSI format: parameter bytes 0x30-0x3F, intermediates 0x20-0x2F, final 0x40-0x7E.
36590
+ const CODE_POINT_CSI_PARAMETER_START = '0'.codePointAt(0);
36591
+ const CODE_POINT_CSI_PARAMETER_END = '?'.codePointAt(0);
36592
+ const CODE_POINT_CSI_INTERMEDIATE_START = ' '.codePointAt(0);
36593
+ const CODE_POINT_CSI_INTERMEDIATE_END = '/'.codePointAt(0);
36594
+ const CODE_POINT_CSI_FINAL_START = '@'.codePointAt(0);
36595
+ const CODE_POINT_CSI_FINAL_END = '~'.codePointAt(0);
36596
+ const REGIONAL_INDICATOR_SYMBOL_LETTER_A = 127_462;
36597
+ const REGIONAL_INDICATOR_SYMBOL_LETTER_Z = 127_487;
36598
+ const SGR_RESET_CODE = 0;
36599
+ const SGR_EXTENDED_FOREGROUND_CODE = 38;
36600
+ const SGR_DEFAULT_FOREGROUND_CODE = 39;
36601
+ const SGR_EXTENDED_BACKGROUND_CODE = 48;
36602
+ const SGR_DEFAULT_BACKGROUND_CODE = 49;
36603
+ const SGR_COLOR_TYPE_ANSI_256 = 5;
36604
+ const SGR_COLOR_TYPE_TRUECOLOR = 2;
36605
+ const SGR_ANSI_256_FRAGMENT_LENGTH = 3;
36606
+ const SGR_TRUECOLOR_FRAGMENT_LENGTH = 5;
36607
+ const SGR_ANSI_256_LAST_PARAMETER_OFFSET = 2;
36608
+ const SGR_TRUECOLOR_LAST_PARAMETER_OFFSET = 4;
36609
+ const VARIATION_SELECTOR_16_CODE_POINT = 65_039;
36610
+ const COMBINING_ENCLOSING_KEYCAP_CODE_POINT = 8419;
36611
+ const EMOJI_PRESENTATION_GRAPHEME_REGEX = /\p{Emoji_Presentation}/u;
36612
+ const GRAPHEME_SEGMENTER = new Intl.Segmenter(undefined, {granularity: 'grapheme'});
36613
+
36614
+ const endCodeNumbers = new Set();
36615
+ for (const [, end] of ansiStyles.codes) {
36616
+ endCodeNumbers.add(end);
36617
+ }
36015
36618
 
36016
- const MAX_ANSI_SEQUENCE_LENGTH = 19;
36619
+ function isSgrParameterCharacter(codePoint) {
36620
+ return (
36621
+ (codePoint >= CODE_POINT_0 && codePoint <= CODE_POINT_9)
36622
+ || codePoint === CODE_POINT_SEMICOLON
36623
+ || codePoint === CODE_POINT_COLON
36624
+ );
36625
+ }
36017
36626
 
36018
- const endCodesSet$1 = new Set();
36019
- const endCodesMap$1 = new Map();
36020
- for (const [start, end] of ansiStyles.codes) {
36021
- endCodesSet$1.add(ansiStyles.color.ansi(end));
36022
- endCodesMap$1.set(ansiStyles.color.ansi(start), ansiStyles.color.ansi(end));
36627
+ function isCsiParameterCharacter$1(codePoint) {
36628
+ return codePoint >= CODE_POINT_CSI_PARAMETER_START && codePoint <= CODE_POINT_CSI_PARAMETER_END;
36629
+ }
36630
+
36631
+ function isCsiIntermediateCharacter$1(codePoint) {
36632
+ return codePoint >= CODE_POINT_CSI_INTERMEDIATE_START && codePoint <= CODE_POINT_CSI_INTERMEDIATE_END;
36633
+ }
36634
+
36635
+ function isCsiFinalCharacter$1(codePoint) {
36636
+ return codePoint >= CODE_POINT_CSI_FINAL_START && codePoint <= CODE_POINT_CSI_FINAL_END;
36637
+ }
36638
+
36639
+ function isRegionalIndicatorCodePoint(codePoint) {
36640
+ return codePoint >= REGIONAL_INDICATOR_SYMBOL_LETTER_A && codePoint <= REGIONAL_INDICATOR_SYMBOL_LETTER_Z;
36641
+ }
36642
+
36643
+ function createControlParseResult(code, endIndex) {
36644
+ return {
36645
+ token: {
36646
+ type: 'control',
36647
+ code,
36648
+ },
36649
+ endIndex,
36650
+ };
36651
+ }
36652
+
36653
+ function isEmojiStyleGrapheme(grapheme) {
36654
+ if (EMOJI_PRESENTATION_GRAPHEME_REGEX.test(grapheme)) {
36655
+ return true;
36656
+ }
36657
+
36658
+ for (const character of grapheme) {
36659
+ const codePoint = character.codePointAt(0);
36660
+ if (
36661
+ codePoint === VARIATION_SELECTOR_16_CODE_POINT
36662
+ || codePoint === COMBINING_ENCLOSING_KEYCAP_CODE_POINT
36663
+ ) {
36664
+ return true;
36665
+ }
36666
+ }
36667
+
36668
+ return false;
36669
+ }
36670
+
36671
+ function getGraphemeWidth(grapheme) {
36672
+ let regionalIndicatorCount = 0;
36673
+ for (const character of grapheme) {
36674
+ const codePoint = character.codePointAt(0);
36675
+ if (isFullwidthCodePoint(codePoint)) {
36676
+ return 2;
36677
+ }
36678
+
36679
+ if (isRegionalIndicatorCodePoint(codePoint)) {
36680
+ regionalIndicatorCount++;
36681
+ }
36682
+ }
36683
+
36684
+ if (regionalIndicatorCount >= 1) {
36685
+ return 2;
36686
+ }
36687
+
36688
+ if (isEmojiStyleGrapheme(grapheme)) {
36689
+ return 2;
36690
+ }
36691
+
36692
+ return 1;
36693
+ }
36694
+
36695
+ function getSgrPrefix(code) {
36696
+ if (code.startsWith('\u009B')) {
36697
+ return '\u009B';
36698
+ }
36699
+
36700
+ return `${ESCAPE}${ANSI_CSI}`;
36701
+ }
36702
+
36703
+ function createSgrCode(prefix, values) {
36704
+ return `${prefix}${values.join(';')}${ANSI_SGR_TERMINATOR}`;
36705
+ }
36706
+
36707
+ function getSgrFragments(code) {
36708
+ const fragments = [];
36709
+ const sgrPrefix = getSgrPrefix(code);
36710
+ let parameterString;
36711
+
36712
+ if (code.startsWith(`${ESCAPE}${ANSI_CSI}`)) {
36713
+ parameterString = code.slice(2, -1);
36714
+ } else if (code.startsWith('\u009B')) {
36715
+ parameterString = code.slice(1, -1);
36716
+ } else {
36717
+ return fragments;
36718
+ }
36719
+
36720
+ const rawCodes = parameterString.length === 0 ? [String(SGR_RESET_CODE)] : parameterString.split(';');
36721
+ let index = 0;
36722
+ while (index < rawCodes.length) {
36723
+ const codeNumber = Number.parseInt(rawCodes[index], 10);
36724
+ if (Number.isNaN(codeNumber)) {
36725
+ index++;
36726
+ continue;
36727
+ }
36728
+
36729
+ if (codeNumber === SGR_RESET_CODE) {
36730
+ fragments.push({type: 'reset'});
36731
+ index++;
36732
+ continue;
36733
+ }
36734
+
36735
+ if (codeNumber === SGR_EXTENDED_FOREGROUND_CODE || codeNumber === SGR_EXTENDED_BACKGROUND_CODE) {
36736
+ const colorType = Number.parseInt(rawCodes[index + 1], 10);
36737
+ if (colorType === SGR_COLOR_TYPE_ANSI_256 && index + SGR_ANSI_256_LAST_PARAMETER_OFFSET < rawCodes.length) {
36738
+ const openCode = createSgrCode(sgrPrefix, rawCodes.slice(index, index + SGR_ANSI_256_FRAGMENT_LENGTH));
36739
+ fragments.push({
36740
+ type: 'start',
36741
+ code: openCode,
36742
+ endCode: ansiStyles.color.ansi(codeNumber === SGR_EXTENDED_FOREGROUND_CODE ? SGR_DEFAULT_FOREGROUND_CODE : SGR_DEFAULT_BACKGROUND_CODE),
36743
+ });
36744
+ index += SGR_ANSI_256_FRAGMENT_LENGTH;
36745
+ continue;
36746
+ }
36747
+
36748
+ if (colorType === SGR_COLOR_TYPE_TRUECOLOR && index + SGR_TRUECOLOR_LAST_PARAMETER_OFFSET < rawCodes.length) {
36749
+ const openCode = createSgrCode(sgrPrefix, rawCodes.slice(index, index + SGR_TRUECOLOR_FRAGMENT_LENGTH));
36750
+ fragments.push({
36751
+ type: 'start',
36752
+ code: openCode,
36753
+ endCode: ansiStyles.color.ansi(codeNumber === SGR_EXTENDED_FOREGROUND_CODE ? SGR_DEFAULT_FOREGROUND_CODE : SGR_DEFAULT_BACKGROUND_CODE),
36754
+ });
36755
+ index += SGR_TRUECOLOR_FRAGMENT_LENGTH;
36756
+ continue;
36757
+ }
36758
+
36759
+ const openCode = createSgrCode(sgrPrefix, [rawCodes[index]]);
36760
+ fragments.push({
36761
+ type: 'start',
36762
+ code: openCode,
36763
+ endCode: ansiStyles.color.ansi(codeNumber === SGR_EXTENDED_FOREGROUND_CODE ? SGR_DEFAULT_FOREGROUND_CODE : SGR_DEFAULT_BACKGROUND_CODE),
36764
+ });
36765
+ index++;
36766
+ continue;
36767
+ }
36768
+
36769
+ if (endCodeNumbers.has(codeNumber)) {
36770
+ fragments.push({
36771
+ type: 'end',
36772
+ endCode: ansiStyles.color.ansi(codeNumber),
36773
+ });
36774
+ index++;
36775
+ continue;
36776
+ }
36777
+
36778
+ const mappedEndCode = ansiStyles.codes.get(codeNumber);
36779
+ if (mappedEndCode !== undefined) {
36780
+ const openCode = createSgrCode(sgrPrefix, [rawCodes[index]]);
36781
+ fragments.push({
36782
+ type: 'start',
36783
+ code: openCode,
36784
+ endCode: ansiStyles.color.ansi(mappedEndCode),
36785
+ });
36786
+ index++;
36787
+ continue;
36788
+ }
36789
+
36790
+ const openCode = createSgrCode(sgrPrefix, [rawCodes[index]]);
36791
+ fragments.push({
36792
+ type: 'start',
36793
+ code: openCode,
36794
+ endCode: ansiStyles.reset.open,
36795
+ });
36796
+ index++;
36797
+ }
36798
+
36799
+ if (fragments.length === 0) {
36800
+ fragments.push({type: 'reset'});
36801
+ }
36802
+
36803
+ return fragments;
36804
+ }
36805
+
36806
+ function parseCsiCode(string, index) {
36807
+ const escapeCodePoint = string.codePointAt(index);
36808
+ let sequenceStartIndex;
36809
+
36810
+ if (escapeCodePoint === ESCAPE_CODE_POINT) {
36811
+ if (string[index + 1] !== ANSI_CSI) {
36812
+ return;
36813
+ }
36814
+
36815
+ sequenceStartIndex = index + 2;
36816
+ } else if (escapeCodePoint === C1_CSI_CODE_POINT) {
36817
+ sequenceStartIndex = index + 1;
36818
+ } else {
36819
+ return;
36820
+ }
36821
+
36822
+ let hasCanonicalSgrParameters = true;
36823
+ for (let sequenceIndex = sequenceStartIndex; sequenceIndex < string.length; sequenceIndex++) {
36824
+ const codePoint = string.codePointAt(sequenceIndex);
36825
+
36826
+ if (isCsiFinalCharacter$1(codePoint)) {
36827
+ const code = string.slice(index, sequenceIndex + 1);
36828
+ if (string[sequenceIndex] !== ANSI_SGR_TERMINATOR || !hasCanonicalSgrParameters) {
36829
+ return createControlParseResult(code, sequenceIndex + 1);
36830
+ }
36831
+
36832
+ return {
36833
+ token: {
36834
+ type: 'sgr',
36835
+ code,
36836
+ fragments: getSgrFragments(code),
36837
+ },
36838
+ endIndex: sequenceIndex + 1,
36839
+ };
36840
+ }
36841
+
36842
+ if (isCsiParameterCharacter$1(codePoint)) {
36843
+ if (!isSgrParameterCharacter(codePoint)) {
36844
+ hasCanonicalSgrParameters = false;
36845
+ }
36846
+
36847
+ continue;
36848
+ }
36849
+
36850
+ if (isCsiIntermediateCharacter$1(codePoint)) {
36851
+ hasCanonicalSgrParameters = false;
36852
+ continue;
36853
+ }
36854
+
36855
+ const endIndex = sequenceIndex;
36856
+ return createControlParseResult(string.slice(index, endIndex), endIndex);
36857
+ }
36858
+
36859
+ return createControlParseResult(string.slice(index), string.length);
36860
+ }
36861
+
36862
+ function parseHyperlinkCode(string, index) {
36863
+ let hyperlinkPrefix;
36864
+ let hyperlinkClose;
36865
+ const codePoint = string.codePointAt(index);
36866
+
36867
+ if (
36868
+ codePoint === ESCAPE_CODE_POINT
36869
+ && string.startsWith(ANSI_HYPERLINK_ESC_PREFIX, index)
36870
+ ) {
36871
+ hyperlinkPrefix = ANSI_HYPERLINK_ESC_PREFIX;
36872
+ hyperlinkClose = ANSI_HYPERLINK_ESC_CLOSE;
36873
+ } else if (
36874
+ codePoint === C1_OSC_CODE_POINT
36875
+ && string.startsWith(ANSI_HYPERLINK_C1_PREFIX, index)
36876
+ ) {
36877
+ hyperlinkPrefix = ANSI_HYPERLINK_C1_PREFIX;
36878
+ hyperlinkClose = ANSI_HYPERLINK_C1_CLOSE;
36879
+ } else {
36880
+ return;
36881
+ }
36882
+
36883
+ const uriStart = string.indexOf(';', index + hyperlinkPrefix.length);
36884
+ if (uriStart === -1) {
36885
+ return createControlParseResult(string.slice(index), string.length);
36886
+ }
36887
+
36888
+ for (let sequenceIndex = uriStart + 1; sequenceIndex < string.length; sequenceIndex++) {
36889
+ const character = string[sequenceIndex];
36890
+
36891
+ if (character === ANSI_BELL) {
36892
+ const code = string.slice(index, sequenceIndex + 1);
36893
+ const action = sequenceIndex === uriStart + 1 ? 'close' : 'open';
36894
+ return {
36895
+ token: {
36896
+ type: 'hyperlink',
36897
+ code,
36898
+ action,
36899
+ closePrefix: hyperlinkClose,
36900
+ terminator: ANSI_BELL,
36901
+ },
36902
+ endIndex: sequenceIndex + 1,
36903
+ };
36904
+ }
36905
+
36906
+ if (
36907
+ character === ESCAPE
36908
+ && string[sequenceIndex + 1] === ANSI_OSC_TERMINATOR
36909
+ ) {
36910
+ const code = string.slice(index, sequenceIndex + 2);
36911
+ const action = sequenceIndex === uriStart + 1 ? 'close' : 'open';
36912
+ return {
36913
+ token: {
36914
+ type: 'hyperlink',
36915
+ code,
36916
+ action,
36917
+ closePrefix: hyperlinkClose,
36918
+ terminator: ANSI_STRING_TERMINATOR,
36919
+ },
36920
+ endIndex: sequenceIndex + 2,
36921
+ };
36922
+ }
36923
+
36924
+ if (character === C1_STRING_TERMINATOR) {
36925
+ const code = string.slice(index, sequenceIndex + 1);
36926
+ const action = sequenceIndex === uriStart + 1 ? 'close' : 'open';
36927
+ return {
36928
+ token: {
36929
+ type: 'hyperlink',
36930
+ code,
36931
+ action,
36932
+ closePrefix: hyperlinkClose,
36933
+ terminator: C1_STRING_TERMINATOR,
36934
+ },
36935
+ endIndex: sequenceIndex + 1,
36936
+ };
36937
+ }
36938
+ }
36939
+
36940
+ return createControlParseResult(string.slice(index), string.length);
36941
+ }
36942
+
36943
+ function parseControlStringCode(string, index) {
36944
+ const codePoint = string.codePointAt(index);
36945
+ let sequenceStartIndex;
36946
+ let supportsBellTerminator = false;
36947
+
36948
+ switch (codePoint) {
36949
+ case ESCAPE_CODE_POINT: {
36950
+ const command = string[index + 1];
36951
+ switch (command) {
36952
+ case ANSI_OSC: {
36953
+ // OSC accepts ST (ECMA-48) and BEL (xterm compatibility extension).
36954
+ sequenceStartIndex = index + 2;
36955
+ supportsBellTerminator = true;
36956
+ break;
36957
+ }
36958
+
36959
+ case ANSI_DCS:
36960
+ case ANSI_SOS:
36961
+ case ANSI_PM:
36962
+ case ANSI_APC: {
36963
+ sequenceStartIndex = index + 2;
36964
+ break;
36965
+ }
36966
+
36967
+ case ANSI_OSC_TERMINATOR: {
36968
+ return createControlParseResult(ANSI_STRING_TERMINATOR, index + 2);
36969
+ }
36970
+
36971
+ default: {
36972
+ return;
36973
+ }
36974
+ }
36975
+
36976
+ break;
36977
+ }
36978
+
36979
+ case C1_OSC_CODE_POINT: {
36980
+ sequenceStartIndex = index + 1;
36981
+ supportsBellTerminator = true;
36982
+ break;
36983
+ }
36984
+
36985
+ case C1_DCS_CODE_POINT:
36986
+ case C1_SOS_CODE_POINT:
36987
+ case C1_PM_CODE_POINT:
36988
+ case C1_APC_CODE_POINT: {
36989
+ sequenceStartIndex = index + 1;
36990
+ break;
36991
+ }
36992
+
36993
+ case C1_ST_CODE_POINT: {
36994
+ return createControlParseResult(C1_STRING_TERMINATOR, index + 1);
36995
+ }
36996
+
36997
+ default: {
36998
+ return;
36999
+ }
37000
+ }
37001
+
37002
+ for (let sequenceIndex = sequenceStartIndex; sequenceIndex < string.length; sequenceIndex++) {
37003
+ if (supportsBellTerminator && string[sequenceIndex] === ANSI_BELL) {
37004
+ return createControlParseResult(string.slice(index, sequenceIndex + 1), sequenceIndex + 1);
37005
+ }
37006
+
37007
+ if (
37008
+ string[sequenceIndex] === ESCAPE
37009
+ && string[sequenceIndex + 1] === ANSI_OSC_TERMINATOR
37010
+ ) {
37011
+ return createControlParseResult(string.slice(index, sequenceIndex + 2), sequenceIndex + 2);
37012
+ }
37013
+
37014
+ if (string[sequenceIndex] === C1_STRING_TERMINATOR) {
37015
+ return createControlParseResult(string.slice(index, sequenceIndex + 1), sequenceIndex + 1);
37016
+ }
37017
+ }
37018
+
37019
+ return createControlParseResult(string.slice(index), string.length);
36023
37020
  }
36024
37021
 
36025
- function getEndCode$1(code) {
36026
- if (endCodesSet$1.has(code)) {
36027
- return code;
37022
+ function parseAnsiCode(string, index) {
37023
+ const codePoint = string.codePointAt(index);
37024
+ if (codePoint === ESCAPE_CODE_POINT || codePoint === C1_OSC_CODE_POINT) {
37025
+ const hyperlinkCode = parseHyperlinkCode(string, index);
37026
+ if (hyperlinkCode) {
37027
+ return hyperlinkCode;
37028
+ }
36028
37029
  }
36029
37030
 
36030
- if (endCodesMap$1.has(code)) {
36031
- return endCodesMap$1.get(code);
37031
+ const controlStringCode = parseControlStringCode(string, index);
37032
+ if (controlStringCode) {
37033
+ return controlStringCode;
36032
37034
  }
36033
37035
 
36034
- code = code.slice(2);
36035
- if (code.includes(';')) {
36036
- code = code[0] + '0';
37036
+ return parseCsiCode(string, index);
37037
+ }
37038
+
37039
+ function appendTrailingAnsiTokens(string, index, tokens) {
37040
+ while (index < string.length) {
37041
+ const nextCodePoint = string.codePointAt(index);
37042
+ if (!ESCAPES$1.has(nextCodePoint)) {
37043
+ break;
37044
+ }
37045
+
37046
+ const escapeCode = parseAnsiCode(string, index);
37047
+ if (!escapeCode) {
37048
+ break;
37049
+ }
37050
+
37051
+ tokens.push(escapeCode.token);
37052
+ index = escapeCode.endIndex;
36037
37053
  }
36038
37054
 
36039
- const returnValue = ansiStyles.codes.get(Number.parseInt(code, 10));
36040
- if (returnValue) {
36041
- return ansiStyles.color.ansi(returnValue);
37055
+ return index;
37056
+ }
37057
+
37058
+ function parseCharacterTokenWithRawSegmentation(string, index, graphemeSegments) {
37059
+ const segment = graphemeSegments.containing(index);
37060
+ if (!segment || segment.index !== index) {
37061
+ return;
36042
37062
  }
36043
37063
 
36044
- return ansiStyles.reset.open;
37064
+ return {
37065
+ token: {
37066
+ type: 'character',
37067
+ // Intentionally preserve UAX29 behavior (GB3): CRLF is one grapheme cluster.
37068
+ value: segment.segment,
37069
+ visibleWidth: getGraphemeWidth(segment.segment),
37070
+ isGraphemeContinuation: false,
37071
+ },
37072
+ endIndex: index + segment.segment.length,
37073
+ };
36045
37074
  }
36046
37075
 
36047
- function findNumberIndex(string) {
36048
- for (let index = 0; index < string.length; index++) {
37076
+ function collectVisibleCharacters(string) {
37077
+ const visibleCharacters = [];
37078
+ let index = 0;
37079
+
37080
+ while (index < string.length) {
36049
37081
  const codePoint = string.codePointAt(index);
36050
- if (codePoint >= CODE_POINT_0 && codePoint <= CODE_POINT_9) {
36051
- return index;
37082
+ if (ESCAPES$1.has(codePoint)) {
37083
+ const code = parseAnsiCode(string, index);
37084
+ if (code) {
37085
+ index = code.endIndex;
37086
+ continue;
37087
+ }
36052
37088
  }
37089
+
37090
+ const value = String.fromCodePoint(codePoint);
37091
+ visibleCharacters.push({
37092
+ value,
37093
+ visibleWidth: 1,
37094
+ isGraphemeContinuation: false,
37095
+ });
37096
+ index += value.length;
36053
37097
  }
36054
37098
 
36055
- return -1;
37099
+ return visibleCharacters;
36056
37100
  }
36057
37101
 
36058
- function parseAnsiCode(string, offset) {
36059
- string = string.slice(offset, offset + MAX_ANSI_SEQUENCE_LENGTH);
36060
- const startIndex = findNumberIndex(string);
36061
- if (startIndex !== -1) {
36062
- let endIndex = string.indexOf('m', startIndex);
36063
- if (endIndex === -1) {
36064
- endIndex = string.length;
37102
+ function applyGraphemeMetadata(visibleCharacters) {
37103
+ if (visibleCharacters.length === 0) {
37104
+ return;
37105
+ }
37106
+
37107
+ const visibleString = visibleCharacters.map(({value}) => value).join('');
37108
+ const scalarOffsets = [];
37109
+ let scalarOffset = 0;
37110
+
37111
+ for (const visibleCharacter of visibleCharacters) {
37112
+ scalarOffsets.push(scalarOffset);
37113
+ scalarOffset += visibleCharacter.value.length;
37114
+ }
37115
+
37116
+ let scalarIndex = 0;
37117
+ for (const segment of GRAPHEME_SEGMENTER.segment(visibleString)) {
37118
+ while (
37119
+ scalarIndex < visibleCharacters.length
37120
+ && scalarOffsets[scalarIndex] < segment.index
37121
+ ) {
37122
+ scalarIndex++;
37123
+ }
37124
+
37125
+ let graphemeIndex = scalarIndex;
37126
+ let isFirstInGrapheme = true;
37127
+ while (
37128
+ graphemeIndex < visibleCharacters.length
37129
+ && scalarOffsets[graphemeIndex] < segment.index + segment.segment.length
37130
+ ) {
37131
+ visibleCharacters[graphemeIndex].visibleWidth = isFirstInGrapheme ? getGraphemeWidth(segment.segment) : 0;
37132
+ visibleCharacters[graphemeIndex].isGraphemeContinuation = !isFirstInGrapheme;
37133
+ isFirstInGrapheme = false;
37134
+ graphemeIndex++;
36065
37135
  }
36066
37136
 
36067
- return string.slice(0, endIndex + 1);
37137
+ scalarIndex = graphemeIndex;
36068
37138
  }
36069
37139
  }
36070
37140
 
36071
- function tokenize$1(string, endCharacter = Number.POSITIVE_INFINITY) {
36072
- const returnValue = [];
37141
+ function tokenizeAnsiWithVisibleSegmentation(string, {endCharacter = Number.POSITIVE_INFINITY} = {}) {
37142
+ const tokens = [];
37143
+ const visibleCharacters = collectVisibleCharacters(string);
37144
+ applyGraphemeMetadata(visibleCharacters);
36073
37145
 
36074
37146
  let index = 0;
37147
+ let visibleCharacterIndex = 0;
36075
37148
  let visibleCount = 0;
36076
37149
  while (index < string.length) {
36077
37150
  const codePoint = string.codePointAt(index);
@@ -36079,186 +37152,402 @@ function tokenize$1(string, endCharacter = Number.POSITIVE_INFINITY) {
36079
37152
  if (ESCAPES$1.has(codePoint)) {
36080
37153
  const code = parseAnsiCode(string, index);
36081
37154
  if (code) {
36082
- returnValue.push({
36083
- type: 'ansi',
36084
- code,
36085
- endCode: getEndCode$1(code),
36086
- });
36087
- index += code.length;
37155
+ tokens.push(code.token);
37156
+ index = code.endIndex;
36088
37157
  continue;
36089
37158
  }
36090
37159
  }
36091
37160
 
36092
- const isFullWidth = isFullwidthCodePoint(codePoint);
36093
- const character = String.fromCodePoint(codePoint);
37161
+ const value = String.fromCodePoint(codePoint);
37162
+ const visibleCharacter = visibleCharacters[visibleCharacterIndex];
37163
+ let visibleWidth = isFullwidthCodePoint(codePoint) ? 2 : value.length;
37164
+ if (visibleCharacter) {
37165
+ visibleWidth = visibleCharacter.visibleWidth;
37166
+ }
36094
37167
 
36095
- returnValue.push({
37168
+ const token = {
36096
37169
  type: 'character',
36097
- value: character,
36098
- isFullWidth,
36099
- });
37170
+ value,
37171
+ visibleWidth,
37172
+ isGraphemeContinuation: visibleCharacter ? visibleCharacter.isGraphemeContinuation : false,
37173
+ };
36100
37174
 
36101
- index += character.length;
36102
- visibleCount += isFullWidth ? 2 : character.length;
37175
+ tokens.push(token);
37176
+ index += value.length;
37177
+ visibleCharacterIndex++;
37178
+ visibleCount += token.visibleWidth;
36103
37179
 
36104
37180
  if (visibleCount >= endCharacter) {
36105
- break;
37181
+ const nextVisibleCharacter = visibleCharacters[visibleCharacterIndex];
37182
+ if (
37183
+ !nextVisibleCharacter
37184
+ || !nextVisibleCharacter.isGraphemeContinuation
37185
+ ) {
37186
+ index = appendTrailingAnsiTokens(string, index, tokens);
37187
+ break;
37188
+ }
36106
37189
  }
36107
37190
  }
36108
37191
 
36109
- return returnValue;
37192
+ return tokens;
36110
37193
  }
36111
37194
 
36112
- function reduceAnsiCodes$1(codes) {
36113
- let returnValue = [];
37195
+ function areValuesInSameGrapheme(leftValue, rightValue) {
37196
+ const pair = `${leftValue}${rightValue}`;
37197
+ const splitIndex = leftValue.length;
36114
37198
 
36115
- for (const code of codes) {
36116
- if (code.code === ansiStyles.reset.open) {
36117
- // Reset code, disable all codes
36118
- returnValue = [];
36119
- } else if (endCodesSet$1.has(code.code)) {
36120
- // This is an end code, disable all matching start codes
36121
- returnValue = returnValue.filter(returnValueCode => returnValueCode.endCode !== code.code);
36122
- } else {
36123
- // This is a start code. Disable all styles this "overrides", then enable it
36124
- returnValue = returnValue.filter(returnValueCode => returnValueCode.endCode !== code.endCode);
36125
- returnValue.push(code);
37199
+ for (const segment of GRAPHEME_SEGMENTER.segment(pair)) {
37200
+ if (segment.index === splitIndex) {
37201
+ return false;
37202
+ }
37203
+
37204
+ if (segment.index > splitIndex) {
37205
+ return true;
36126
37206
  }
36127
37207
  }
36128
37208
 
36129
- return returnValue;
37209
+ return true;
36130
37210
  }
36131
37211
 
36132
- function undoAnsiCodes$1(codes) {
36133
- const reduced = reduceAnsiCodes$1(codes);
36134
- const endCodes = reduced.map(({endCode}) => endCode);
36135
- return endCodes.reverse().join('');
37212
+ function hasAnsiSplitContinuationAhead(string, startIndex, previousVisibleValue, graphemeSegments) {
37213
+ if (!previousVisibleValue) {
37214
+ return false;
37215
+ }
37216
+
37217
+ let index = startIndex;
37218
+ let hasAnsiCode = false;
37219
+ while (index < string.length) {
37220
+ const codePoint = string.codePointAt(index);
37221
+ if (ESCAPES$1.has(codePoint)) {
37222
+ const code = parseAnsiCode(string, index);
37223
+ if (code) {
37224
+ hasAnsiCode = true;
37225
+ index = code.endIndex;
37226
+ continue;
37227
+ }
37228
+ }
37229
+
37230
+ if (!hasAnsiCode) {
37231
+ return false;
37232
+ }
37233
+
37234
+ const characterToken = parseCharacterTokenWithRawSegmentation(string, index, graphemeSegments);
37235
+ if (!characterToken) {
37236
+ return true;
37237
+ }
37238
+
37239
+ return areValuesInSameGrapheme(previousVisibleValue, characterToken.token.value);
37240
+ }
37241
+
37242
+ return false;
36136
37243
  }
36137
37244
 
36138
- function sliceAnsi(string, start, end) {
36139
- const tokens = tokenize$1(string, end);
36140
- let activeCodes = [];
36141
- let position = 0;
36142
- let returnValue = '';
36143
- let include = false;
37245
+ function tokenizeAnsi$1(string, {endCharacter = Number.POSITIVE_INFINITY} = {}) {
37246
+ const tokens = [];
37247
+ const graphemeSegments = GRAPHEME_SEGMENTER.segment(string);
37248
+
37249
+ let index = 0;
37250
+ let visibleCount = 0;
37251
+ let previousVisibleValue;
37252
+ let hasAnsiSinceLastVisible = false;
37253
+ while (index < string.length) {
37254
+ const codePoint = string.codePointAt(index);
37255
+
37256
+ if (ESCAPES$1.has(codePoint)) {
37257
+ const code = parseAnsiCode(string, index);
37258
+ if (code) {
37259
+ tokens.push(code.token);
37260
+ index = code.endIndex;
37261
+ hasAnsiSinceLastVisible = true;
37262
+ continue;
37263
+ }
37264
+ }
37265
+
37266
+ const characterToken = parseCharacterTokenWithRawSegmentation(string, index, graphemeSegments);
37267
+ if (!characterToken) {
37268
+ return tokenizeAnsiWithVisibleSegmentation(string, {endCharacter});
37269
+ }
37270
+
37271
+ if (
37272
+ hasAnsiSinceLastVisible
37273
+ && previousVisibleValue
37274
+ && areValuesInSameGrapheme(previousVisibleValue, characterToken.token.value)
37275
+ ) {
37276
+ return tokenizeAnsiWithVisibleSegmentation(string, {endCharacter});
37277
+ }
37278
+
37279
+ tokens.push(characterToken.token);
37280
+ index = characterToken.endIndex;
37281
+ visibleCount += characterToken.token.visibleWidth;
37282
+ hasAnsiSinceLastVisible = false;
37283
+ previousVisibleValue = characterToken.token.value;
37284
+
37285
+ if (visibleCount >= endCharacter) {
37286
+ if (hasAnsiSplitContinuationAhead(string, index, previousVisibleValue, graphemeSegments)) {
37287
+ return tokenizeAnsiWithVisibleSegmentation(string, {endCharacter});
37288
+ }
36144
37289
 
36145
- for (const token of tokens) {
36146
- if (end !== undefined && position >= end) {
37290
+ index = appendTrailingAnsiTokens(string, index, tokens);
36147
37291
  break;
36148
37292
  }
37293
+ }
37294
+
37295
+ return tokens;
37296
+ }
36149
37297
 
36150
- if (token.type === 'ansi') {
36151
- activeCodes.push(token);
36152
- if (include) {
36153
- returnValue += token.code;
37298
+ function applySgrFragments(activeStyles, fragments) {
37299
+ for (const fragment of fragments) {
37300
+ switch (fragment.type) {
37301
+ case 'reset': {
37302
+ activeStyles.clear();
37303
+ break;
36154
37304
  }
36155
- } else {
36156
- // Character
36157
- if (!include && position >= start) {
36158
- include = true;
36159
- // Simplify active codes
36160
- activeCodes = reduceAnsiCodes$1(activeCodes);
36161
- returnValue = activeCodes.map(({code}) => code).join('');
37305
+
37306
+ case 'end': {
37307
+ activeStyles.delete(fragment.endCode);
37308
+ break;
36162
37309
  }
36163
37310
 
36164
- if (include) {
36165
- returnValue += token.value;
37311
+ case 'start': {
37312
+ activeStyles.delete(fragment.endCode);
37313
+ activeStyles.set(fragment.endCode, fragment.code);
37314
+ break;
36166
37315
  }
37316
+ }
37317
+ }
37318
+
37319
+ return activeStyles;
37320
+ }
37321
+
37322
+ function undoAnsiCodes$1(activeStyles) {
37323
+ return [...activeStyles.keys()].reverse().join('');
37324
+ }
37325
+
37326
+ function closeHyperlink(hyperlinkToken) {
37327
+ return `${hyperlinkToken.closePrefix}${hyperlinkToken.terminator}`;
37328
+ }
37329
+
37330
+ function shouldIncludeSgrAfterEnd(token, activeStyles) {
37331
+ let hasStartFragment = false;
37332
+ let hasClosingEffect = false;
37333
+
37334
+ for (const fragment of token.fragments) {
37335
+ if (fragment.type === 'start') {
37336
+ hasStartFragment = true;
37337
+ continue;
37338
+ }
37339
+
37340
+ if (fragment.type === 'reset' && activeStyles.size > 0) {
37341
+ hasClosingEffect = true;
37342
+ continue;
37343
+ }
36167
37344
 
36168
- position += token.isFullWidth ? 2 : token.value.length;
37345
+ if (fragment.type === 'end' && activeStyles.has(fragment.endCode)) {
37346
+ hasClosingEffect = true;
36169
37347
  }
36170
37348
  }
36171
37349
 
36172
- // Disable active codes at the end
36173
- returnValue += undoAnsiCodes$1(activeCodes);
36174
- return returnValue;
37350
+ return hasClosingEffect && !hasStartFragment;
36175
37351
  }
36176
37352
 
36177
- /**
36178
- Logic:
36179
- - Segment graphemes to match how terminals render clusters.
36180
- - Width rules:
36181
- 1. Skip non-printing clusters (Default_Ignorable, Control, pure Mark, lone Surrogates). Tabs are ignored by design.
36182
- 2. RGI emoji clusters (\p{RGI_Emoji}) are double-width.
36183
- 3. Otherwise use East Asian Width of the cluster’s first visible code point, and add widths for trailing Halfwidth/Fullwidth Forms within the same cluster (e.g., dakuten/handakuten/prolonged sound mark).
36184
- */
37353
+ function applySgrToken({token, isPastEnd, activeStyles, returnValue, include, activeHyperlink, position}) {
37354
+ if (isPastEnd && !shouldIncludeSgrAfterEnd(token, activeStyles)) {
37355
+ return {
37356
+ activeStyles,
37357
+ activeHyperlink,
37358
+ position,
37359
+ returnValue,
37360
+ include,
37361
+ };
37362
+ }
36185
37363
 
36186
- const segmenter = new Intl.Segmenter();
37364
+ activeStyles = applySgrFragments(activeStyles, token.fragments);
37365
+ if (include) {
37366
+ returnValue += token.code;
37367
+ }
36187
37368
 
36188
- // Whole-cluster zero-width
36189
- const zeroWidthClusterRegex = /^(?:\p{Default_Ignorable_Code_Point}|\p{Control}|\p{Mark}|\p{Surrogate})+$/v;
37369
+ return {
37370
+ activeStyles,
37371
+ activeHyperlink,
37372
+ position,
37373
+ returnValue,
37374
+ include,
37375
+ };
37376
+ }
36190
37377
 
36191
- // Pick the base scalar if the cluster starts with Prepend/Format/Marks
36192
- const leadingNonPrintingRegex = /^[\p{Default_Ignorable_Code_Point}\p{Control}\p{Format}\p{Mark}\p{Surrogate}]+/v;
37378
+ function applyHyperlinkToken({token, isPastEnd, activeStyles, activeHyperlink, position, returnValue, include}) {
37379
+ if (
37380
+ isPastEnd
37381
+ && (
37382
+ token.action !== 'close'
37383
+ || !activeHyperlink
37384
+ )
37385
+ ) {
37386
+ return {
37387
+ activeStyles,
37388
+ activeHyperlink,
37389
+ position,
37390
+ returnValue,
37391
+ include,
37392
+ };
37393
+ }
36193
37394
 
36194
- // RGI emoji sequences
36195
- const rgiEmojiRegex = /^\p{RGI_Emoji}$/v;
37395
+ if (token.action === 'open') {
37396
+ activeHyperlink = token;
37397
+ } else if (token.action === 'close') {
37398
+ activeHyperlink = undefined;
37399
+ }
36196
37400
 
36197
- function baseVisible(segment) {
36198
- return segment.replace(leadingNonPrintingRegex, '');
37401
+ if (include) {
37402
+ returnValue += token.code;
37403
+ }
37404
+
37405
+ return {
37406
+ activeStyles,
37407
+ activeHyperlink,
37408
+ position,
37409
+ returnValue,
37410
+ include,
37411
+ };
36199
37412
  }
36200
37413
 
36201
- function isZeroWidthCluster(segment) {
36202
- return zeroWidthClusterRegex.test(segment);
37414
+ function applyControlToken({token, isPastEnd, activeStyles, activeHyperlink, position, returnValue, include}) {
37415
+ if (!isPastEnd && include) {
37416
+ returnValue += token.code;
37417
+ }
37418
+
37419
+ return {
37420
+ activeStyles,
37421
+ activeHyperlink,
37422
+ position,
37423
+ returnValue,
37424
+ include,
37425
+ };
36203
37426
  }
36204
37427
 
36205
- function trailingHalfwidthWidth(segment, eastAsianWidthOptions) {
36206
- let extra = 0;
36207
- if (segment.length > 1) {
36208
- for (const char of segment.slice(1)) {
36209
- if (char >= '\uFF00' && char <= '\uFFEF') {
36210
- extra += eastAsianWidth(char.codePointAt(0), eastAsianWidthOptions);
36211
- }
37428
+ function applyCharacterToken({token, start, activeStyles, activeHyperlink, position, returnValue, include}) {
37429
+ if (
37430
+ !include
37431
+ && position >= start
37432
+ && !token.isGraphemeContinuation
37433
+ ) {
37434
+ include = true;
37435
+ returnValue = [...activeStyles.values()].join('');
37436
+ if (activeHyperlink) {
37437
+ returnValue += activeHyperlink.code;
36212
37438
  }
36213
37439
  }
36214
37440
 
36215
- return extra;
37441
+ if (include) {
37442
+ returnValue += token.value;
37443
+ }
37444
+
37445
+ position += token.visibleWidth;
37446
+ return {
37447
+ activeStyles,
37448
+ activeHyperlink,
37449
+ position,
37450
+ returnValue,
37451
+ include,
37452
+ };
36216
37453
  }
36217
37454
 
36218
- function stringWidth(input, options = {}) {
36219
- if (typeof input !== 'string' || input.length === 0) {
36220
- return 0;
37455
+ const tokenHandlers = {
37456
+ sgr: applySgrToken,
37457
+ hyperlink: applyHyperlinkToken,
37458
+ control: applyControlToken,
37459
+ character: applyCharacterToken,
37460
+ };
37461
+
37462
+ function applyToken(parameters) {
37463
+ const tokenHandler = tokenHandlers[parameters.token.type];
37464
+ if (!tokenHandler) {
37465
+ const {
37466
+ activeStyles,
37467
+ activeHyperlink,
37468
+ position,
37469
+ returnValue,
37470
+ include,
37471
+ } = parameters;
37472
+
37473
+ return {
37474
+ activeStyles,
37475
+ activeHyperlink,
37476
+ position,
37477
+ returnValue,
37478
+ include,
37479
+ };
36221
37480
  }
36222
37481
 
36223
- const {
36224
- ambiguousIsNarrow = true,
36225
- countAnsiEscapeCodes = false,
36226
- } = options;
37482
+ return tokenHandler(parameters);
37483
+ }
36227
37484
 
36228
- let string = input;
37485
+ function createHasContinuationAheadMap(tokens) {
37486
+ const hasContinuationAhead = Array.from({length: tokens.length}, () => false);
37487
+ let nextCharacterIsContinuation = false;
36229
37488
 
36230
- if (!countAnsiEscapeCodes) {
36231
- string = stripAnsi(string);
37489
+ for (let tokenIndex = tokens.length - 1; tokenIndex >= 0; tokenIndex--) {
37490
+ const token = tokens[tokenIndex];
37491
+ hasContinuationAhead[tokenIndex] = nextCharacterIsContinuation;
37492
+ if (token.type === 'character') {
37493
+ nextCharacterIsContinuation = Boolean(token.isGraphemeContinuation);
37494
+ }
36232
37495
  }
36233
37496
 
36234
- if (string.length === 0) {
36235
- return 0;
36236
- }
37497
+ return hasContinuationAhead;
37498
+ }
36237
37499
 
36238
- let width = 0;
36239
- const eastAsianWidthOptions = {ambiguousAsWide: !ambiguousIsNarrow};
37500
+ function sliceAnsi(string, start, end) {
37501
+ const tokens = tokenizeAnsi$1(string, {endCharacter: end});
37502
+ const hasContinuationAhead = createHasContinuationAheadMap(tokens);
37503
+ let activeStyles = new Map();
37504
+ let activeHyperlink;
37505
+ let position = 0;
37506
+ let returnValue = '';
37507
+ let include = false;
36240
37508
 
36241
- for (const {segment} of segmenter.segment(string)) {
36242
- // Zero-width / non-printing clusters
36243
- if (isZeroWidthCluster(segment)) {
36244
- continue;
37509
+ for (const [tokenIndex, token] of tokens.entries()) {
37510
+ let isPastEnd = end !== undefined && position >= end;
37511
+ if (
37512
+ isPastEnd
37513
+ && token.type !== 'character'
37514
+ && hasContinuationAhead[tokenIndex]
37515
+ ) {
37516
+ isPastEnd = false;
36245
37517
  }
36246
37518
 
36247
- // Emoji width logic
36248
- if (rgiEmojiRegex.test(segment)) {
36249
- width += 2;
36250
- continue;
37519
+ if (
37520
+ isPastEnd
37521
+ && token.type === 'character'
37522
+ && !token.isGraphemeContinuation
37523
+ ) {
37524
+ break;
36251
37525
  }
36252
37526
 
36253
- // Everything else: EAW of the cluster’s first visible scalar
36254
- const codePoint = baseVisible(segment).codePointAt(0);
36255
- width += eastAsianWidth(codePoint, eastAsianWidthOptions);
37527
+ ({activeStyles, activeHyperlink, position, returnValue, include} = applyToken({
37528
+ token,
37529
+ isPastEnd,
37530
+ start,
37531
+ activeStyles,
37532
+ activeHyperlink,
37533
+ position,
37534
+ returnValue,
37535
+ include,
37536
+ }));
37537
+ }
36256
37538
 
36257
- // Add width for trailing Halfwidth and Fullwidth Forms (e.g., ゙, ゚, ー)
36258
- width += trailingHalfwidthWidth(segment, eastAsianWidthOptions);
37539
+ if (!include) {
37540
+ return '';
36259
37541
  }
36260
37542
 
36261
- return width;
37543
+ if (activeHyperlink) {
37544
+ returnValue += closeHyperlink(activeHyperlink);
37545
+ }
37546
+
37547
+ // Disable active codes at the end
37548
+ returnValue += undoAnsiCodes$1(activeStyles);
37549
+
37550
+ return returnValue;
36262
37551
  }
36263
37552
 
36264
37553
  function getIndexOfNearestSpace(string, wantedIndex, shouldSearchRight) {
@@ -36455,6 +37744,347 @@ const wrapText = (text, maxWidth, wrapType) => {
36455
37744
  return wrappedText;
36456
37745
  };
36457
37746
 
37747
+ const bellCharacter = '\u0007';
37748
+ const escapeCharacter = '\u001B';
37749
+ const stringTerminatorCharacter = '\u009C';
37750
+ const csiCharacter = '\u009B';
37751
+ const oscCharacter = '\u009D';
37752
+ const dcsCharacter = '\u0090';
37753
+ const pmCharacter = '\u009E';
37754
+ const apcCharacter = '\u009F';
37755
+ const sosCharacter = '\u0098';
37756
+ const isCsiParameterCharacter = (character) => {
37757
+ const codePoint = character.codePointAt(0);
37758
+ return codePoint !== undefined && codePoint >= 0x30 && codePoint <= 0x3f;
37759
+ };
37760
+ const isCsiIntermediateCharacter = (character) => {
37761
+ const codePoint = character.codePointAt(0);
37762
+ return codePoint !== undefined && codePoint >= 0x20 && codePoint <= 0x2f;
37763
+ };
37764
+ const isCsiFinalCharacter = (character) => {
37765
+ const codePoint = character.codePointAt(0);
37766
+ return codePoint !== undefined && codePoint >= 0x40 && codePoint <= 0x7e;
37767
+ };
37768
+ const isEscapeIntermediateCharacter = (character) => {
37769
+ const codePoint = character.codePointAt(0);
37770
+ return codePoint !== undefined && codePoint >= 0x20 && codePoint <= 0x2f;
37771
+ };
37772
+ const isEscapeFinalCharacter = (character) => {
37773
+ const codePoint = character.codePointAt(0);
37774
+ return codePoint !== undefined && codePoint >= 0x30 && codePoint <= 0x7e;
37775
+ };
37776
+ const isC1ControlCharacter = (character) => {
37777
+ const codePoint = character.codePointAt(0);
37778
+ return codePoint !== undefined && codePoint >= 0x80 && codePoint <= 0x9f;
37779
+ };
37780
+ // Standards references:
37781
+ // ECMA-48 control functions and CSI byte classes: https://ecma-international.org/publications-and-standards/standards/ecma-48/
37782
+ // xterm CSI parameter/intermediate/final format notes: https://invisible-island.net/xterm/ecma-48-parameter-format.html
37783
+ // xterm/OSC BEL termination behavior: https://davidrg.github.io/ckwin/dev/ctlseqs.html
37784
+ const readCsiSequence = (text, fromIndex) => {
37785
+ let index = fromIndex;
37786
+ while (index < text.length) {
37787
+ const character = text[index];
37788
+ if (!isCsiParameterCharacter(character)) {
37789
+ break;
37790
+ }
37791
+ index++;
37792
+ }
37793
+ const parameterString = text.slice(fromIndex, index);
37794
+ const intermediateStartIndex = index;
37795
+ while (index < text.length) {
37796
+ const character = text[index];
37797
+ if (!isCsiIntermediateCharacter(character)) {
37798
+ break;
37799
+ }
37800
+ index++;
37801
+ }
37802
+ const intermediateString = text.slice(intermediateStartIndex, index);
37803
+ const finalCharacter = text[index];
37804
+ if (finalCharacter === undefined || !isCsiFinalCharacter(finalCharacter)) {
37805
+ return undefined;
37806
+ }
37807
+ return {
37808
+ endIndex: index + 1,
37809
+ parameterString,
37810
+ intermediateString,
37811
+ finalCharacter,
37812
+ };
37813
+ };
37814
+ const findControlStringTerminatorIndex = (text, fromIndex, allowBellTerminator) => {
37815
+ for (let index = fromIndex; index < text.length; index++) {
37816
+ const character = text[index];
37817
+ if (allowBellTerminator && character === bellCharacter) {
37818
+ return index + 1;
37819
+ }
37820
+ if (character === stringTerminatorCharacter) {
37821
+ return index + 1;
37822
+ }
37823
+ if (character === escapeCharacter) {
37824
+ const followingCharacter = text[index + 1];
37825
+ // Tmux escapes ESC bytes in payload as ESC ESC.
37826
+ if (followingCharacter === escapeCharacter) {
37827
+ index++;
37828
+ continue;
37829
+ }
37830
+ if (followingCharacter === '\\') {
37831
+ return index + 2;
37832
+ }
37833
+ }
37834
+ }
37835
+ return undefined;
37836
+ };
37837
+ const readEscapeSequence = (text, fromIndex) => {
37838
+ let index = fromIndex;
37839
+ while (index < text.length) {
37840
+ const character = text[index];
37841
+ if (!isEscapeIntermediateCharacter(character)) {
37842
+ break;
37843
+ }
37844
+ index++;
37845
+ }
37846
+ const intermediateString = text.slice(fromIndex, index);
37847
+ const finalCharacter = text[index];
37848
+ if (finalCharacter === undefined || !isEscapeFinalCharacter(finalCharacter)) {
37849
+ return undefined;
37850
+ }
37851
+ return {
37852
+ endIndex: index + 1,
37853
+ intermediateString,
37854
+ finalCharacter,
37855
+ };
37856
+ };
37857
+ // Centralize control-string rules so ESC and C1 paths do not diverge.
37858
+ const getControlStringFromEscapeIntroducer = (character) => {
37859
+ switch (character) {
37860
+ case ']': {
37861
+ return { type: 'osc', allowBellTerminator: true };
37862
+ }
37863
+ case 'P': {
37864
+ return { type: 'dcs', allowBellTerminator: false };
37865
+ }
37866
+ case '^': {
37867
+ return { type: 'pm', allowBellTerminator: false };
37868
+ }
37869
+ case '_': {
37870
+ return { type: 'apc', allowBellTerminator: false };
37871
+ }
37872
+ case 'X': {
37873
+ return { type: 'sos', allowBellTerminator: false };
37874
+ }
37875
+ default: {
37876
+ return undefined;
37877
+ }
37878
+ }
37879
+ };
37880
+ const getControlStringFromC1Introducer = (character) => {
37881
+ switch (character) {
37882
+ case oscCharacter: {
37883
+ return { type: 'osc', allowBellTerminator: true };
37884
+ }
37885
+ case dcsCharacter: {
37886
+ return { type: 'dcs', allowBellTerminator: false };
37887
+ }
37888
+ case pmCharacter: {
37889
+ return { type: 'pm', allowBellTerminator: false };
37890
+ }
37891
+ case apcCharacter: {
37892
+ return { type: 'apc', allowBellTerminator: false };
37893
+ }
37894
+ case sosCharacter: {
37895
+ return { type: 'sos', allowBellTerminator: false };
37896
+ }
37897
+ default: {
37898
+ return undefined;
37899
+ }
37900
+ }
37901
+ };
37902
+ const hasAnsiControlCharacters = (text) => {
37903
+ if (text.includes(escapeCharacter)) {
37904
+ return true;
37905
+ }
37906
+ for (const character of text) {
37907
+ if (isC1ControlCharacter(character)) {
37908
+ return true;
37909
+ }
37910
+ }
37911
+ return false;
37912
+ };
37913
+ const malformedFromIndex = (tokens, text, textStartIndex, fromIndex) => {
37914
+ if (fromIndex > textStartIndex) {
37915
+ tokens.push({ type: 'text', value: text.slice(textStartIndex, fromIndex) });
37916
+ }
37917
+ // Treat the remainder as invalid so callers can drop it as one unsafe unit.
37918
+ tokens.push({ type: 'invalid', value: text.slice(fromIndex) });
37919
+ return tokens;
37920
+ };
37921
+ const tokenizeAnsi = (text) => {
37922
+ if (!hasAnsiControlCharacters(text)) {
37923
+ return [{ type: 'text', value: text }];
37924
+ }
37925
+ const tokens = [];
37926
+ let textStartIndex = 0;
37927
+ for (let index = 0; index < text.length;) {
37928
+ const character = text[index];
37929
+ if (character === undefined) {
37930
+ break;
37931
+ }
37932
+ if (character === escapeCharacter) {
37933
+ const followingCharacter = text[index + 1];
37934
+ if (followingCharacter === undefined) {
37935
+ return malformedFromIndex(tokens, text, textStartIndex, index);
37936
+ }
37937
+ if (followingCharacter === '[') {
37938
+ const csiSequence = readCsiSequence(text, index + 2);
37939
+ if (csiSequence === undefined) {
37940
+ return malformedFromIndex(tokens, text, textStartIndex, index);
37941
+ }
37942
+ if (index > textStartIndex) {
37943
+ tokens.push({ type: 'text', value: text.slice(textStartIndex, index) });
37944
+ }
37945
+ tokens.push({
37946
+ type: 'csi',
37947
+ value: text.slice(index, csiSequence.endIndex),
37948
+ parameterString: csiSequence.parameterString,
37949
+ intermediateString: csiSequence.intermediateString,
37950
+ finalCharacter: csiSequence.finalCharacter,
37951
+ });
37952
+ index = csiSequence.endIndex;
37953
+ textStartIndex = index;
37954
+ continue;
37955
+ }
37956
+ const escapeControlString = getControlStringFromEscapeIntroducer(followingCharacter);
37957
+ if (escapeControlString !== undefined) {
37958
+ const controlStringTerminatorIndex = findControlStringTerminatorIndex(text, index + 2, escapeControlString.allowBellTerminator);
37959
+ if (controlStringTerminatorIndex === undefined) {
37960
+ return malformedFromIndex(tokens, text, textStartIndex, index);
37961
+ }
37962
+ if (index > textStartIndex) {
37963
+ tokens.push({ type: 'text', value: text.slice(textStartIndex, index) });
37964
+ }
37965
+ tokens.push({
37966
+ type: escapeControlString.type,
37967
+ value: text.slice(index, controlStringTerminatorIndex),
37968
+ });
37969
+ index = controlStringTerminatorIndex;
37970
+ textStartIndex = index;
37971
+ continue;
37972
+ }
37973
+ const escapeSequence = readEscapeSequence(text, index + 1);
37974
+ if (escapeSequence === undefined) {
37975
+ // Incomplete escape sequences with intermediates are malformed control strings.
37976
+ if (isEscapeIntermediateCharacter(followingCharacter)) {
37977
+ return malformedFromIndex(tokens, text, textStartIndex, index);
37978
+ }
37979
+ if (index > textStartIndex) {
37980
+ tokens.push({ type: 'text', value: text.slice(textStartIndex, index) });
37981
+ }
37982
+ // Ignore lone ESC and continue tokenizing the rest.
37983
+ index++;
37984
+ textStartIndex = index;
37985
+ continue;
37986
+ }
37987
+ if (index > textStartIndex) {
37988
+ tokens.push({ type: 'text', value: text.slice(textStartIndex, index) });
37989
+ }
37990
+ tokens.push({
37991
+ type: 'esc',
37992
+ value: text.slice(index, escapeSequence.endIndex),
37993
+ intermediateString: escapeSequence.intermediateString,
37994
+ finalCharacter: escapeSequence.finalCharacter,
37995
+ });
37996
+ index = escapeSequence.endIndex;
37997
+ textStartIndex = index;
37998
+ continue;
37999
+ }
38000
+ if (character === csiCharacter) {
38001
+ const csiSequence = readCsiSequence(text, index + 1);
38002
+ if (csiSequence === undefined) {
38003
+ return malformedFromIndex(tokens, text, textStartIndex, index);
38004
+ }
38005
+ if (index > textStartIndex) {
38006
+ tokens.push({ type: 'text', value: text.slice(textStartIndex, index) });
38007
+ }
38008
+ tokens.push({
38009
+ type: 'csi',
38010
+ value: text.slice(index, csiSequence.endIndex),
38011
+ parameterString: csiSequence.parameterString,
38012
+ intermediateString: csiSequence.intermediateString,
38013
+ finalCharacter: csiSequence.finalCharacter,
38014
+ });
38015
+ index = csiSequence.endIndex;
38016
+ textStartIndex = index;
38017
+ continue;
38018
+ }
38019
+ const c1ControlString = getControlStringFromC1Introducer(character);
38020
+ if (c1ControlString !== undefined) {
38021
+ const controlStringTerminatorIndex = findControlStringTerminatorIndex(text, index + 1, c1ControlString.allowBellTerminator);
38022
+ if (controlStringTerminatorIndex === undefined) {
38023
+ return malformedFromIndex(tokens, text, textStartIndex, index);
38024
+ }
38025
+ if (index > textStartIndex) {
38026
+ tokens.push({ type: 'text', value: text.slice(textStartIndex, index) });
38027
+ }
38028
+ tokens.push({
38029
+ type: c1ControlString.type,
38030
+ value: text.slice(index, controlStringTerminatorIndex),
38031
+ });
38032
+ index = controlStringTerminatorIndex;
38033
+ textStartIndex = index;
38034
+ continue;
38035
+ }
38036
+ if (character === stringTerminatorCharacter) {
38037
+ if (index > textStartIndex) {
38038
+ tokens.push({ type: 'text', value: text.slice(textStartIndex, index) });
38039
+ }
38040
+ tokens.push({ type: 'st', value: character });
38041
+ index++;
38042
+ textStartIndex = index;
38043
+ continue;
38044
+ }
38045
+ // Strip remaining C1 controls as standalone functions.
38046
+ if (isC1ControlCharacter(character)) {
38047
+ if (index > textStartIndex) {
38048
+ tokens.push({ type: 'text', value: text.slice(textStartIndex, index) });
38049
+ }
38050
+ tokens.push({ type: 'c1', value: character });
38051
+ index++;
38052
+ textStartIndex = index;
38053
+ continue;
38054
+ }
38055
+ index++;
38056
+ }
38057
+ if (textStartIndex < text.length) {
38058
+ tokens.push({ type: 'text', value: text.slice(textStartIndex) });
38059
+ }
38060
+ return tokens;
38061
+ };
38062
+
38063
+ const sgrParametersRegex = /^[\d:;]*$/;
38064
+ // Strip ANSI escape sequences that would conflict with Ink's layout.
38065
+ // Preserved: SGR sequences (colors, bold, etc. - end with 'm') and
38066
+ // OSC sequences (hyperlinks, etc. - ESC ] or C1 OSC).
38067
+ // Stripped: cursor movement, screen clearing, and other control sequences.
38068
+ const sanitizeAnsi = (text) => {
38069
+ if (!hasAnsiControlCharacters(text)) {
38070
+ return text;
38071
+ }
38072
+ let output = '';
38073
+ for (const token of tokenizeAnsi(text)) {
38074
+ if (token.type === 'text' || token.type === 'osc') {
38075
+ output += token.value;
38076
+ continue;
38077
+ }
38078
+ if (token.type === 'csi' &&
38079
+ token.finalCharacter === 'm' &&
38080
+ token.intermediateString === '' &&
38081
+ sgrParametersRegex.test(token.parameterString)) {
38082
+ output += token.value;
38083
+ }
38084
+ }
38085
+ return output;
38086
+ };
38087
+
36458
38088
  // Squashing text nodes allows to combine multiple text nodes into one and write
36459
38089
  // to `Output` instance only once. For example, <Text>hello{' '}world</Text>
36460
38090
  // is actually 3 text nodes, which would result 3 writes to `Output`.
@@ -36486,7 +38116,7 @@ const squashTextNodes = (node) => {
36486
38116
  }
36487
38117
  text += nodeText;
36488
38118
  }
36489
- return text;
38119
+ return sanitizeAnsi(text);
36490
38120
  };
36491
38121
 
36492
38122
  const createNode = (nodeName) => {
@@ -36529,11 +38159,12 @@ const insertBeforeNode = (node, newChildNode, beforeChildNode) => {
36529
38159
  if (newChildNode.yogaNode) {
36530
38160
  node.yogaNode?.insertChild(newChildNode.yogaNode, index);
36531
38161
  }
36532
- return;
36533
38162
  }
36534
- node.childNodes.push(newChildNode);
36535
- if (newChildNode.yogaNode) {
36536
- node.yogaNode?.insertChild(newChildNode.yogaNode, node.yogaNode.getChildCount());
38163
+ else {
38164
+ node.childNodes.push(newChildNode);
38165
+ if (newChildNode.yogaNode) {
38166
+ node.yogaNode?.insertChild(newChildNode.yogaNode, node.yogaNode.getChildCount());
38167
+ }
36537
38168
  }
36538
38169
  if (node.nodeName === 'ink-text' || node.nodeName === 'ink-virtual-text') {
36539
38170
  markNodeAsDirty(node);
@@ -36841,7 +38472,7 @@ const styles = (node, style = {}) => {
36841
38472
  // We need to conditionally perform devtools connection to avoid
36842
38473
  // accidentally breaking other third-party code.
36843
38474
  // See https://github.com/vadimdemedes/ink/issues/384
36844
- if (process$1.env['DEV'] === 'true') {
38475
+ if (isDev()) {
36845
38476
  try {
36846
38477
  await import('./devtools-CPruVlOo.js');
36847
38478
  }
@@ -36894,6 +38525,14 @@ const cleanupYogaNode = (node) => {
36894
38525
  };
36895
38526
  let currentUpdatePriority = constantsExports.NoEventPriority;
36896
38527
  let currentRootNode;
38528
+ async function loadPackageJson() {
38529
+ const fs = await import('node:fs');
38530
+ const content = fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8');
38531
+ return JSON.parse(content);
38532
+ }
38533
+ const packageJson = isDev()
38534
+ ? await loadPackageJson()
38535
+ : { name: undefined, version: undefined };
36897
38536
  var reconciler = createReconciler({
36898
38537
  getRootHostContext: () => ({
36899
38538
  isInsideText: false,
@@ -36994,6 +38633,14 @@ var reconciler = createReconciler({
36994
38633
  supportsMutation: true,
36995
38634
  supportsPersistence: false,
36996
38635
  supportsHydration: false,
38636
+ // Scheduler integration for concurrent mode
38637
+ supportsMicrotasks: true,
38638
+ scheduleMicrotask: queueMicrotask,
38639
+ // @ts-expect-error @types/react-reconciler is outdated and doesn't include scheduleCallback
38640
+ scheduleCallback: schedulerExports.unstable_scheduleCallback,
38641
+ cancelCallback: schedulerExports.unstable_cancelCallback,
38642
+ shouldYield: schedulerExports.unstable_shouldYield,
38643
+ now: schedulerExports.unstable_now,
36997
38644
  scheduleTimeout: setTimeout,
36998
38645
  cancelTimeout: clearTimeout,
36999
38646
  noTimeout: -1,
@@ -37057,7 +38704,8 @@ var reconciler = createReconciler({
37057
38704
  return constantsExports.DefaultEventPriority;
37058
38705
  },
37059
38706
  maySuspendCommit() {
37060
- return false;
38707
+ // Return true to enable Suspense resource preloading
38708
+ return true;
37061
38709
  },
37062
38710
  // eslint-disable-next-line @typescript-eslint/naming-convention
37063
38711
  NotPendingTransition: undefined,
@@ -37083,6 +38731,8 @@ var reconciler = createReconciler({
37083
38731
  waitForCommitToBeReady() {
37084
38732
  return null;
37085
38733
  },
38734
+ rendererPackageName: packageJson.name,
38735
+ rendererVersion: packageJson.version,
37086
38736
  });
37087
38737
 
37088
38738
  function indentString(string, count = 1, options = {}) {
@@ -37526,10 +39176,8 @@ for (const [start, end] of ansiStyles.codes) {
37526
39176
  endCodesSet.add(ansiStyles.color.ansi(end));
37527
39177
  endCodesMap.set(ansiStyles.color.ansi(start), ansiStyles.color.ansi(end));
37528
39178
  }
37529
- const linkStartCodePrefix = "\x1B]8;;";
37530
- const linkStartCodePrefixCharCodes = linkStartCodePrefix
37531
- .split("")
37532
- .map((char) => char.charCodeAt(0));
39179
+ const linkCodePrefix = "\x1B]8;"; // OSC 8 link prefix (params and URL follow)
39180
+ const linkCodePrefixCharCodes = linkCodePrefix.split("").map((char) => char.charCodeAt(0));
37533
39181
  const linkCodeSuffix = "\x07";
37534
39182
  const linkEndCode = `\x1B]8;;${linkCodeSuffix}`;
37535
39183
  function getEndCode(code) {
@@ -37539,7 +39187,7 @@ function getEndCode(code) {
37539
39187
  return endCodesMap.get(code);
37540
39188
  // We have a few special cases to handle here:
37541
39189
  // Links:
37542
- if (code.startsWith(linkStartCodePrefix))
39190
+ if (code.startsWith(linkCodePrefix))
37543
39191
  return linkEndCode;
37544
39192
  code = code.slice(2);
37545
39193
  // 8-bit/24-bit colors:
@@ -37559,7 +39207,13 @@ function getEndCode(code) {
37559
39207
  }
37560
39208
  }
37561
39209
  function ansiCodesToString(codes) {
37562
- return codes.map((code) => code.code).join("");
39210
+ // Deduplicate ANSI code strings before joining
39211
+ const deduplicated = new Set(codes.map((code) => code.code));
39212
+ return [...deduplicated].join("");
39213
+ }
39214
+ /** Check if a code is an intensity code (bold or dim) - these share endCode 22m but can coexist */
39215
+ function isIntensityCode(code) {
39216
+ return code.code === ansiStyles.bold.open || code.code === ansiStyles.dim.open;
37563
39217
  }
37564
39218
 
37565
39219
  /** Reduces the given array of ANSI codes to the minimum necessary to render with the same style */
@@ -37582,9 +39236,8 @@ function reduceAnsiCodesIncremental(codes, newCodes) {
37582
39236
  // This is a start code. Remove codes it "overrides", then add it.
37583
39237
  // If a new code has the same endCode, it "overrides" existing ones.
37584
39238
  // Special case: Intensity codes (1m, 2m) can coexist (both end with 22m).
37585
- const isIntensityCode = code.code === ansiStyles.bold.open || code.code === ansiStyles.dim.open;
37586
- // Add intensity codes only if not already present
37587
- if (isIntensityCode) {
39239
+ // We only add those if the exact same code is not already present.
39240
+ if (isIntensityCode(code)) {
37588
39241
  if (!ret.find((retCode) => retCode.code === code.code && retCode.endCode === code.endCode)) {
37589
39242
  ret.push(code);
37590
39243
  }
@@ -37614,11 +39267,19 @@ function undoAnsiCodes(codes) {
37614
39267
  */
37615
39268
  function diffAnsiCodes(from, to) {
37616
39269
  const endCodesInTo = new Set(to.map((code) => code.endCode));
39270
+ const startCodesInTo = new Set(to.map((code) => code.code));
37617
39271
  const startCodesInFrom = new Set(from.map((code) => code.code));
37618
39272
  return [
37619
39273
  // Ignore all styles in `from` that are not overwritten or removed by `to`
37620
39274
  // Disable all styles in `from` that are removed in `to`
37621
- ...undoAnsiCodes(from.filter((code) => !endCodesInTo.has(code.endCode))),
39275
+ ...undoAnsiCodes(from.filter((code) => {
39276
+ // Special case: Intensity codes (1m, 2m) can coexist (both end with 22m).
39277
+ // We have to check the start codes for those, otherwise we might miss a reset.
39278
+ if (isIntensityCode(code)) {
39279
+ return !startCodesInTo.has(code.code);
39280
+ }
39281
+ return !endCodesInTo.has(code.endCode);
39282
+ })),
37622
39283
  // Add all styles in `to` that don't exist in `from`
37623
39284
  ...to.filter((code) => !startCodesInFrom.has(code.code)),
37624
39285
  ];
@@ -37659,16 +39320,32 @@ function styledCharsToString(chars) {
37659
39320
  return ret;
37660
39321
  }
37661
39322
 
39323
+ const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
39324
+ function isFullwidthGrapheme(grapheme, baseCodePoint) {
39325
+ if (isFullwidthCodePoint(baseCodePoint))
39326
+ return true;
39327
+ // Variation Selector 16 forces emoji presentation (2 columns wide)
39328
+ if (grapheme.includes("\uFE0F"))
39329
+ return true;
39330
+ // Regional indicator pairs form flag emoji (2 columns wide)
39331
+ if (baseCodePoint >= 0x1f1e6 && baseCodePoint <= 0x1f1ff)
39332
+ return true;
39333
+ return false;
39334
+ }
37662
39335
  // HOT PATH: Use only basic string/char code operations for maximum performance
37663
39336
  function parseLinkCode(string, offset) {
37664
39337
  string = string.slice(offset);
37665
- for (let index = 1; index < linkStartCodePrefixCharCodes.length; index++) {
37666
- if (string.charCodeAt(index) !== linkStartCodePrefixCharCodes[index]) {
39338
+ for (let index = 1; index < linkCodePrefixCharCodes.length; index++) {
39339
+ if (string.charCodeAt(index) !== linkCodePrefixCharCodes[index]) {
37667
39340
  return undefined;
37668
39341
  }
37669
39342
  }
39343
+ // Find the semicolon that ends params
39344
+ const paramsEndIndex = string.indexOf(";", linkCodePrefix.length);
39345
+ if (paramsEndIndex === -1)
39346
+ return undefined;
37670
39347
  // This is a link code (with or without the URL part). Find the end of it.
37671
- const endIndex = string.indexOf("\x07", linkStartCodePrefix.length);
39348
+ const endIndex = string.indexOf("\x07", paramsEndIndex + 1);
37672
39349
  if (endIndex === -1)
37673
39350
  return undefined;
37674
39351
  return string.slice(0, endIndex + 1);
@@ -37743,10 +39420,13 @@ function splitCompoundSGRSequences(code) {
37743
39420
  }
37744
39421
  function tokenize(str, endChar = Number.POSITIVE_INFINITY) {
37745
39422
  const ret = [];
37746
- let index = 0;
37747
39423
  let visible = 0;
37748
- while (index < str.length) {
37749
- const codePoint = str.codePointAt(index);
39424
+ let codeEndIndex = 0;
39425
+ for (const { segment, index } of segmenter.segment(str)) {
39426
+ // Skip segments consumed as part of an ANSI sequence
39427
+ if (index < codeEndIndex)
39428
+ continue;
39429
+ const codePoint = segment.codePointAt(0);
37750
39430
  if (ESCAPES.has(codePoint)) {
37751
39431
  let code;
37752
39432
  // Peek the next code point to determine the type of ANSI sequence
@@ -37778,19 +39458,17 @@ function tokenize(str, endChar = Number.POSITIVE_INFINITY) {
37778
39458
  }
37779
39459
  }
37780
39460
  if (code) {
37781
- index += code.length;
39461
+ codeEndIndex = index + code.length;
37782
39462
  continue;
37783
39463
  }
37784
39464
  }
37785
- const fullWidth = isFullwidthCodePoint(codePoint);
37786
- const character = String.fromCodePoint(codePoint);
39465
+ const fullWidth = isFullwidthGrapheme(segment, codePoint);
37787
39466
  ret.push({
37788
39467
  type: "char",
37789
- value: character,
39468
+ value: segment,
37790
39469
  fullWidth,
37791
39470
  });
37792
- index += character.length;
37793
- visible += fullWidth ? 2 : character.length;
39471
+ visible += fullWidth ? 2 : 1;
37794
39472
  if (visible >= endChar) {
37795
39473
  break;
37796
39474
  }
@@ -37798,10 +39476,44 @@ function tokenize(str, endChar = Number.POSITIVE_INFINITY) {
37798
39476
  return ret;
37799
39477
  }
37800
39478
 
39479
+ class OutputCaches {
39480
+ widths = new Map();
39481
+ blockWidths = new Map();
39482
+ styledChars = new Map();
39483
+ getStyledChars(line) {
39484
+ let cached = this.styledChars.get(line);
39485
+ if (cached === undefined) {
39486
+ cached = styledCharsFromTokens(tokenize(line));
39487
+ this.styledChars.set(line, cached);
39488
+ }
39489
+ return cached;
39490
+ }
39491
+ getStringWidth(text) {
39492
+ let cached = this.widths.get(text);
39493
+ if (cached === undefined) {
39494
+ cached = stringWidth(text);
39495
+ this.widths.set(text, cached);
39496
+ }
39497
+ return cached;
39498
+ }
39499
+ getWidestLine(text) {
39500
+ let cached = this.blockWidths.get(text);
39501
+ if (cached === undefined) {
39502
+ let lineWidth = 0;
39503
+ for (const line of text.split('\n')) {
39504
+ lineWidth = Math.max(lineWidth, this.getStringWidth(line));
39505
+ }
39506
+ cached = lineWidth;
39507
+ this.blockWidths.set(text, cached);
39508
+ }
39509
+ return cached;
39510
+ }
39511
+ }
37801
39512
  class Output {
37802
39513
  width;
37803
39514
  height;
37804
39515
  operations = [];
39516
+ caches = new OutputCaches();
37805
39517
  constructor(options) {
37806
39518
  const { width, height } = options;
37807
39519
  this.width = width;
@@ -37865,7 +39577,7 @@ class Output {
37865
39577
  // If text is positioned outside of clipping area altogether,
37866
39578
  // skip to the next operation to avoid unnecessary calculations
37867
39579
  if (clipHorizontally) {
37868
- const width = widestLine(text);
39580
+ const width = this.caches.getWidestLine(text);
37869
39581
  if (x + width < clip.x1 || x > clip.x2) {
37870
39582
  continue;
37871
39583
  }
@@ -37879,7 +39591,7 @@ class Output {
37879
39591
  if (clipHorizontally) {
37880
39592
  lines = lines.map(line => {
37881
39593
  const from = x < clip.x1 ? clip.x1 - x : 0;
37882
- const width = stringWidth(line);
39594
+ const width = this.caches.getStringWidth(line);
37883
39595
  const to = x + width > clip.x2 ? clip.x2 - x : width;
37884
39596
  return sliceAnsi(line, from, to);
37885
39597
  });
@@ -37907,12 +39619,12 @@ class Output {
37907
39619
  for (const transformer of transformers) {
37908
39620
  line = transformer(line, index);
37909
39621
  }
37910
- const characters = styledCharsFromTokens(tokenize(line));
39622
+ const characters = this.caches.getStyledChars(line);
37911
39623
  let offsetX = x;
37912
39624
  for (const character of characters) {
37913
39625
  currentLine[offsetX] = character;
37914
39626
  // Determine printed width using string-width to align with measurement
37915
- const characterWidth = Math.max(1, stringWidth(character.value));
39627
+ const characterWidth = Math.max(1, this.caches.getStringWidth(character.value));
37916
39628
  // For multi-column characters, clear following cells to avoid stray spaces/artifacts
37917
39629
  if (characterWidth > 1) {
37918
39630
  for (let index = 1; index < characterWidth; index++) {
@@ -38115,109 +39827,288 @@ cliCursor.toggle = (force, writableStream) => {
38115
39827
  }
38116
39828
  };
38117
39829
 
39830
+ const showCursorEscape = '\u001B[?25h';
39831
+ const hideCursorEscape = '\u001B[?25l';
39832
+ /**
39833
+ Compare two cursor positions. Returns true if they differ.
39834
+ */
39835
+ const cursorPositionChanged = (a, b) => a?.x !== b?.x || a?.y !== b?.y;
39836
+ /**
39837
+ Build escape sequence to move cursor from bottom of output to the target position and show it.
39838
+ Assumes cursor is at (col 0, line visibleLineCount) — i.e. just after the last output line.
39839
+ */
39840
+ const buildCursorSuffix = (visibleLineCount, cursorPosition) => {
39841
+ if (!cursorPosition) {
39842
+ return '';
39843
+ }
39844
+ const moveUp = visibleLineCount - cursorPosition.y;
39845
+ return ((moveUp > 0 ? cursorUp(moveUp) : '') +
39846
+ cursorTo(cursorPosition.x) +
39847
+ showCursorEscape);
39848
+ };
39849
+ /**
39850
+ Build escape sequence to move cursor from previousCursorPosition back to the bottom of output.
39851
+ This must be done before eraseLines or any operation that assumes cursor is at the bottom.
39852
+ */
39853
+ const buildReturnToBottom = (previousLineCount, previousCursorPosition) => {
39854
+ if (!previousCursorPosition) {
39855
+ return '';
39856
+ }
39857
+ // PreviousLineCount includes trailing newline, so visible lines = previousLineCount - 1
39858
+ // cursor is at previousCursorPosition.y, need to go to line (previousLineCount - 1)
39859
+ const down = previousLineCount - 1 - previousCursorPosition.y;
39860
+ return ((down > 0 ? cursorDown(down) : '') + cursorTo(0));
39861
+ };
39862
+ /**
39863
+ Build the escape sequence for cursor-only updates (output unchanged, cursor moved).
39864
+ Hides cursor if it was previously shown, returns to bottom, then repositions.
39865
+ */
39866
+ const buildCursorOnlySequence = (input) => {
39867
+ const hidePrefix = input.cursorWasShown ? hideCursorEscape : '';
39868
+ const returnToBottom = buildReturnToBottom(input.previousLineCount, input.previousCursorPosition);
39869
+ const cursorSuffix = buildCursorSuffix(input.visibleLineCount, input.cursorPosition);
39870
+ return hidePrefix + returnToBottom + cursorSuffix;
39871
+ };
39872
+ /**
39873
+ Build the prefix that hides cursor and returns to bottom before erasing or rewriting.
39874
+ Returns empty string if cursor was not shown.
39875
+ */
39876
+ const buildReturnToBottomPrefix = (cursorWasShown, previousLineCount, previousCursorPosition) => {
39877
+ if (!cursorWasShown) {
39878
+ return '';
39879
+ }
39880
+ return (hideCursorEscape +
39881
+ buildReturnToBottom(previousLineCount, previousCursorPosition));
39882
+ };
39883
+
39884
+ // Count visible lines in a string, ignoring the trailing empty element
39885
+ // that `split('\n')` produces when the string ends with '\n'.
39886
+ const visibleLineCount = (lines, str) => str.endsWith('\n') ? lines.length - 1 : lines.length;
38118
39887
  const createStandard = (stream, { showCursor = false } = {}) => {
38119
39888
  let previousLineCount = 0;
38120
39889
  let previousOutput = '';
38121
39890
  let hasHiddenCursor = false;
39891
+ let cursorPosition;
39892
+ let cursorDirty = false;
39893
+ let previousCursorPosition;
39894
+ let cursorWasShown = false;
39895
+ const getActiveCursor = () => (cursorDirty ? cursorPosition : undefined);
39896
+ const hasChanges = (str, activeCursor) => {
39897
+ const cursorChanged = cursorPositionChanged(activeCursor, previousCursorPosition);
39898
+ return str !== previousOutput || cursorChanged;
39899
+ };
38122
39900
  const render = (str) => {
38123
39901
  if (!showCursor && !hasHiddenCursor) {
38124
- cliCursor.hide();
39902
+ cliCursor.hide(stream);
38125
39903
  hasHiddenCursor = true;
38126
39904
  }
38127
- const output = str + '\n';
38128
- if (output === previousOutput) {
38129
- return;
39905
+ // Only use cursor if setCursorPosition was called since last render.
39906
+ // This ensures stale positions don't persist after component unmount.
39907
+ const activeCursor = getActiveCursor();
39908
+ cursorDirty = false;
39909
+ const cursorChanged = cursorPositionChanged(activeCursor, previousCursorPosition);
39910
+ if (!hasChanges(str, activeCursor)) {
39911
+ return false;
39912
+ }
39913
+ const lines = str.split('\n');
39914
+ const visibleCount = visibleLineCount(lines, str);
39915
+ const cursorSuffix = buildCursorSuffix(visibleCount, activeCursor);
39916
+ if (str === previousOutput && cursorChanged) {
39917
+ stream.write(buildCursorOnlySequence({
39918
+ cursorWasShown,
39919
+ previousLineCount,
39920
+ previousCursorPosition,
39921
+ visibleLineCount: visibleCount,
39922
+ cursorPosition: activeCursor,
39923
+ }));
38130
39924
  }
38131
- previousOutput = output;
38132
- stream.write(eraseLines(previousLineCount) + output);
38133
- previousLineCount = output.split('\n').length;
39925
+ else {
39926
+ previousOutput = str;
39927
+ const returnPrefix = buildReturnToBottomPrefix(cursorWasShown, previousLineCount, previousCursorPosition);
39928
+ stream.write(returnPrefix +
39929
+ eraseLines(previousLineCount) +
39930
+ str +
39931
+ cursorSuffix);
39932
+ previousLineCount = lines.length;
39933
+ }
39934
+ previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
39935
+ cursorWasShown = activeCursor !== undefined;
39936
+ return true;
38134
39937
  };
38135
39938
  render.clear = () => {
38136
- stream.write(eraseLines(previousLineCount));
39939
+ const prefix = buildReturnToBottomPrefix(cursorWasShown, previousLineCount, previousCursorPosition);
39940
+ stream.write(prefix + eraseLines(previousLineCount));
38137
39941
  previousOutput = '';
38138
39942
  previousLineCount = 0;
39943
+ previousCursorPosition = undefined;
39944
+ cursorWasShown = false;
38139
39945
  };
38140
39946
  render.done = () => {
38141
39947
  previousOutput = '';
38142
39948
  previousLineCount = 0;
39949
+ previousCursorPosition = undefined;
39950
+ cursorWasShown = false;
38143
39951
  if (!showCursor) {
38144
- cliCursor.show();
39952
+ cliCursor.show(stream);
38145
39953
  hasHiddenCursor = false;
38146
39954
  }
38147
39955
  };
38148
39956
  render.sync = (str) => {
38149
- const output = str + '\n';
38150
- previousOutput = output;
38151
- previousLineCount = output.split('\n').length;
39957
+ const activeCursor = cursorDirty ? cursorPosition : undefined;
39958
+ cursorDirty = false;
39959
+ const lines = str.split('\n');
39960
+ previousOutput = str;
39961
+ previousLineCount = lines.length;
39962
+ if (!activeCursor && cursorWasShown) {
39963
+ stream.write(hideCursorEscape);
39964
+ }
39965
+ if (activeCursor) {
39966
+ stream.write(buildCursorSuffix(visibleLineCount(lines, str), activeCursor));
39967
+ }
39968
+ previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
39969
+ cursorWasShown = activeCursor !== undefined;
38152
39970
  };
39971
+ render.setCursorPosition = (position) => {
39972
+ cursorPosition = position;
39973
+ cursorDirty = true;
39974
+ };
39975
+ render.isCursorDirty = () => cursorDirty;
39976
+ render.willRender = (str) => hasChanges(str, getActiveCursor());
38153
39977
  return render;
38154
39978
  };
38155
39979
  const createIncremental = (stream, { showCursor = false } = {}) => {
38156
39980
  let previousLines = [];
38157
39981
  let previousOutput = '';
38158
39982
  let hasHiddenCursor = false;
39983
+ let cursorPosition;
39984
+ let cursorDirty = false;
39985
+ let previousCursorPosition;
39986
+ let cursorWasShown = false;
39987
+ const getActiveCursor = () => (cursorDirty ? cursorPosition : undefined);
39988
+ const hasChanges = (str, activeCursor) => {
39989
+ const cursorChanged = cursorPositionChanged(activeCursor, previousCursorPosition);
39990
+ return str !== previousOutput || cursorChanged;
39991
+ };
38159
39992
  const render = (str) => {
38160
39993
  if (!showCursor && !hasHiddenCursor) {
38161
- cliCursor.hide();
39994
+ cliCursor.hide(stream);
38162
39995
  hasHiddenCursor = true;
38163
39996
  }
38164
- const output = str + '\n';
38165
- if (output === previousOutput) {
38166
- return;
39997
+ // Only use cursor if setCursorPosition was called since last render.
39998
+ // This ensures stale positions don't persist after component unmount.
39999
+ const activeCursor = getActiveCursor();
40000
+ cursorDirty = false;
40001
+ const cursorChanged = cursorPositionChanged(activeCursor, previousCursorPosition);
40002
+ if (!hasChanges(str, activeCursor)) {
40003
+ return false;
38167
40004
  }
38168
- const previousCount = previousLines.length;
38169
- const nextLines = output.split('\n');
38170
- const nextCount = nextLines.length;
38171
- const visibleCount = nextCount - 1;
38172
- if (output === '\n' || previousOutput.length === 0) {
38173
- stream.write(eraseLines(previousCount) + output);
38174
- previousOutput = output;
40005
+ const nextLines = str.split('\n');
40006
+ const visibleCount = visibleLineCount(nextLines, str);
40007
+ const previousVisible = visibleLineCount(previousLines, previousOutput);
40008
+ if (str === previousOutput && cursorChanged) {
40009
+ stream.write(buildCursorOnlySequence({
40010
+ cursorWasShown,
40011
+ previousLineCount: previousLines.length,
40012
+ previousCursorPosition,
40013
+ visibleLineCount: visibleCount,
40014
+ cursorPosition: activeCursor,
40015
+ }));
40016
+ previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
40017
+ cursorWasShown = activeCursor !== undefined;
40018
+ return true;
40019
+ }
40020
+ const returnPrefix = buildReturnToBottomPrefix(cursorWasShown, previousLines.length, previousCursorPosition);
40021
+ if (str === '\n' || previousOutput.length === 0) {
40022
+ const cursorSuffix = buildCursorSuffix(visibleCount, activeCursor);
40023
+ stream.write(returnPrefix +
40024
+ eraseLines(previousLines.length) +
40025
+ str +
40026
+ cursorSuffix);
40027
+ cursorWasShown = activeCursor !== undefined;
40028
+ previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
40029
+ previousOutput = str;
38175
40030
  previousLines = nextLines;
38176
- return;
40031
+ return true;
38177
40032
  }
40033
+ const hasTrailingNewline = str.endsWith('\n');
38178
40034
  // We aggregate all chunks for incremental rendering into a buffer, and then write them to stdout at the end.
38179
40035
  const buffer = [];
40036
+ buffer.push(returnPrefix);
38180
40037
  // Clear extra lines if the current content's line count is lower than the previous.
38181
- if (nextCount < previousCount) {
38182
- buffer.push(
38183
- // Erases the trailing lines and the final newline slot.
38184
- eraseLines(previousCount - nextCount + 1),
38185
- // Positions cursor to the top of the rendered output.
38186
- cursorUp(visibleCount));
40038
+ if (visibleCount < previousVisible) {
40039
+ const previousHadTrailingNewline = previousOutput.endsWith('\n');
40040
+ const extraSlot = previousHadTrailingNewline ? 1 : 0;
40041
+ buffer.push(eraseLines(previousVisible - visibleCount + extraSlot), cursorUp(visibleCount));
38187
40042
  }
38188
40043
  else {
38189
- buffer.push(cursorUp(previousCount - 1));
40044
+ buffer.push(cursorUp(previousVisible - 1));
38190
40045
  }
38191
40046
  for (let i = 0; i < visibleCount; i++) {
40047
+ const isLastLine = i === visibleCount - 1;
38192
40048
  // We do not write lines if the contents are the same. This prevents flickering during renders.
38193
40049
  if (nextLines[i] === previousLines[i]) {
38194
- buffer.push(cursorNextLine);
40050
+ // Don't move past the last line when there's no trailing newline,
40051
+ // otherwise the cursor overshoots the rendered block.
40052
+ if (!isLastLine || hasTrailingNewline) {
40053
+ buffer.push(cursorNextLine);
40054
+ }
38195
40055
  continue;
38196
40056
  }
38197
- buffer.push(eraseLine + nextLines[i] + '\n');
40057
+ buffer.push(cursorTo(0) +
40058
+ nextLines[i] +
40059
+ eraseEndLine +
40060
+ // Don't append newline after the last line when the input
40061
+ // has no trailing newline (fullscreen mode).
40062
+ (isLastLine && !hasTrailingNewline ? '' : '\n'));
38198
40063
  }
40064
+ const cursorSuffix = buildCursorSuffix(visibleCount, activeCursor);
40065
+ buffer.push(cursorSuffix);
38199
40066
  stream.write(buffer.join(''));
38200
- previousOutput = output;
40067
+ cursorWasShown = activeCursor !== undefined;
40068
+ previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
40069
+ previousOutput = str;
38201
40070
  previousLines = nextLines;
40071
+ return true;
38202
40072
  };
38203
40073
  render.clear = () => {
38204
- stream.write(eraseLines(previousLines.length));
40074
+ const prefix = buildReturnToBottomPrefix(cursorWasShown, previousLines.length, previousCursorPosition);
40075
+ stream.write(prefix + eraseLines(previousLines.length));
38205
40076
  previousOutput = '';
38206
40077
  previousLines = [];
40078
+ previousCursorPosition = undefined;
40079
+ cursorWasShown = false;
38207
40080
  };
38208
40081
  render.done = () => {
38209
40082
  previousOutput = '';
38210
40083
  previousLines = [];
40084
+ previousCursorPosition = undefined;
40085
+ cursorWasShown = false;
38211
40086
  if (!showCursor) {
38212
- cliCursor.show();
40087
+ cliCursor.show(stream);
38213
40088
  hasHiddenCursor = false;
38214
40089
  }
38215
40090
  };
38216
40091
  render.sync = (str) => {
38217
- const output = str + '\n';
38218
- previousOutput = output;
38219
- previousLines = output.split('\n');
40092
+ const activeCursor = cursorDirty ? cursorPosition : undefined;
40093
+ cursorDirty = false;
40094
+ const lines = str.split('\n');
40095
+ previousOutput = str;
40096
+ previousLines = lines;
40097
+ if (!activeCursor && cursorWasShown) {
40098
+ stream.write(hideCursorEscape);
40099
+ }
40100
+ if (activeCursor) {
40101
+ stream.write(buildCursorSuffix(visibleLineCount(lines, str), activeCursor));
40102
+ }
40103
+ previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
40104
+ cursorWasShown = activeCursor !== undefined;
40105
+ };
40106
+ render.setCursorPosition = (position) => {
40107
+ cursorPosition = position;
40108
+ cursorDirty = true;
38220
40109
  };
40110
+ render.isCursorDirty = () => cursorDirty;
40111
+ render.willRender = (str) => hasChanges(str, getActiveCursor());
38221
40112
  return render;
38222
40113
  };
38223
40114
  const create = (stream, { showCursor = false, incremental = false } = {}) => {
@@ -38228,6 +40119,12 @@ const create = (stream, { showCursor = false, incremental = false } = {}) => {
38228
40119
  };
38229
40120
  const logUpdate = { create };
38230
40121
 
40122
+ const bsu = '\u001B[?2026h';
40123
+ const esu = '\u001B[?2026l';
40124
+ function shouldSynchronize(stream) {
40125
+ return 'isTTY' in stream && stream.isTTY === true && !isInCi;
40126
+ }
40127
+
38231
40128
  // Store all instances of Ink (instance.js) to ensure that consecutive render() calls
38232
40129
  // use the same instance of Ink and don't create a new one
38233
40130
  //
@@ -38235,6 +40132,160 @@ const logUpdate = { create };
38235
40132
  // but instance.js should delete itself from the map on unmount
38236
40133
  const instances = new WeakMap();
38237
40134
 
40135
+ const escape$1 = '\u001B';
40136
+ const isCsiParameterByte = (byte) => {
40137
+ return byte >= 0x30 && byte <= 0x3f;
40138
+ };
40139
+ const isCsiIntermediateByte = (byte) => {
40140
+ return byte >= 0x20 && byte <= 0x2f;
40141
+ };
40142
+ const isCsiFinalByte = (byte) => {
40143
+ return byte >= 0x40 && byte <= 0x7e;
40144
+ };
40145
+ const parseCsiSequence = (input, startIndex, prefixLength) => {
40146
+ const csiPayloadStart = startIndex + prefixLength + 1;
40147
+ let index = csiPayloadStart;
40148
+ for (; index < input.length; index++) {
40149
+ const byte = input.codePointAt(index);
40150
+ if (byte === undefined) {
40151
+ return 'pending';
40152
+ }
40153
+ if (isCsiParameterByte(byte) || isCsiIntermediateByte(byte)) {
40154
+ continue;
40155
+ }
40156
+ // Preserve legacy terminal function-key sequences like ESC[[A and ESC[[5~.
40157
+ if (byte === 0x5b && index === csiPayloadStart) {
40158
+ continue;
40159
+ }
40160
+ if (isCsiFinalByte(byte)) {
40161
+ return {
40162
+ sequence: input.slice(startIndex, index + 1),
40163
+ nextIndex: index + 1,
40164
+ };
40165
+ }
40166
+ return undefined;
40167
+ }
40168
+ return 'pending';
40169
+ };
40170
+ const parseSs3Sequence = (input, startIndex, prefixLength) => {
40171
+ const nextIndex = startIndex + prefixLength + 2;
40172
+ if (nextIndex > input.length) {
40173
+ return 'pending';
40174
+ }
40175
+ const finalByte = input.codePointAt(nextIndex - 1);
40176
+ if (finalByte === undefined || !isCsiFinalByte(finalByte)) {
40177
+ return undefined;
40178
+ }
40179
+ return {
40180
+ sequence: input.slice(startIndex, nextIndex),
40181
+ nextIndex,
40182
+ };
40183
+ };
40184
+ const parseControlSequence = (input, startIndex, prefixLength) => {
40185
+ const sequenceType = input[startIndex + prefixLength];
40186
+ if (sequenceType === undefined) {
40187
+ return 'pending';
40188
+ }
40189
+ if (sequenceType === '[') {
40190
+ return parseCsiSequence(input, startIndex, prefixLength);
40191
+ }
40192
+ if (sequenceType === 'O') {
40193
+ return parseSs3Sequence(input, startIndex, prefixLength);
40194
+ }
40195
+ return undefined;
40196
+ };
40197
+ const parseEscapedCodePoint = (input, escapeIndex) => {
40198
+ const nextCodePoint = input.codePointAt(escapeIndex + 1);
40199
+ const nextCodePointLength = nextCodePoint !== undefined && nextCodePoint > 0xff_ff ? 2 : 1;
40200
+ const nextIndex = escapeIndex + 1 + nextCodePointLength;
40201
+ return {
40202
+ sequence: input.slice(escapeIndex, nextIndex),
40203
+ nextIndex,
40204
+ };
40205
+ };
40206
+ const parseKeypresses = (input) => {
40207
+ const events = [];
40208
+ let index = 0;
40209
+ const pendingFrom = (pendingStartIndex) => ({
40210
+ events,
40211
+ pending: input.slice(pendingStartIndex),
40212
+ });
40213
+ while (index < input.length) {
40214
+ const escapeIndex = input.indexOf(escape$1, index);
40215
+ if (escapeIndex === -1) {
40216
+ events.push(input.slice(index));
40217
+ return {
40218
+ events,
40219
+ pending: '',
40220
+ };
40221
+ }
40222
+ if (escapeIndex > index) {
40223
+ events.push(input.slice(index, escapeIndex));
40224
+ }
40225
+ if (escapeIndex === input.length - 1) {
40226
+ return pendingFrom(escapeIndex);
40227
+ }
40228
+ const parsedSequence = parseControlSequence(input, escapeIndex, 1);
40229
+ if (parsedSequence === 'pending') {
40230
+ return pendingFrom(escapeIndex);
40231
+ }
40232
+ if (parsedSequence) {
40233
+ events.push(parsedSequence.sequence);
40234
+ index = parsedSequence.nextIndex;
40235
+ continue;
40236
+ }
40237
+ const next = input[escapeIndex + 1];
40238
+ if (next === escape$1) {
40239
+ if (escapeIndex + 2 >= input.length) {
40240
+ return pendingFrom(escapeIndex);
40241
+ }
40242
+ const doubleEscapeSequence = parseControlSequence(input, escapeIndex, 2);
40243
+ if (doubleEscapeSequence === 'pending') {
40244
+ return pendingFrom(escapeIndex);
40245
+ }
40246
+ if (doubleEscapeSequence) {
40247
+ events.push(doubleEscapeSequence.sequence);
40248
+ index = doubleEscapeSequence.nextIndex;
40249
+ continue;
40250
+ }
40251
+ events.push(input.slice(escapeIndex, escapeIndex + 2));
40252
+ index = escapeIndex + 2;
40253
+ continue;
40254
+ }
40255
+ const escapedCodePoint = parseEscapedCodePoint(input, escapeIndex);
40256
+ events.push(escapedCodePoint.sequence);
40257
+ index = escapedCodePoint.nextIndex;
40258
+ }
40259
+ return {
40260
+ events,
40261
+ pending: '',
40262
+ };
40263
+ };
40264
+ const createInputParser = () => {
40265
+ let pending = '';
40266
+ return {
40267
+ push(chunk) {
40268
+ const parsedInput = parseKeypresses(pending + chunk);
40269
+ pending = parsedInput.pending;
40270
+ return parsedInput.events;
40271
+ },
40272
+ hasPendingEscape() {
40273
+ return pending.startsWith(escape$1);
40274
+ },
40275
+ flushPendingEscape() {
40276
+ if (!pending.startsWith(escape$1)) {
40277
+ return undefined;
40278
+ }
40279
+ const pendingEscape = pending;
40280
+ pending = '';
40281
+ return pendingEscape;
40282
+ },
40283
+ reset() {
40284
+ pending = '';
40285
+ },
40286
+ };
40287
+ };
40288
+
38238
40289
  /**
38239
40290
  `AppContext` is a React context that exposes a method to manually exit the app (unmount).
38240
40291
  */
@@ -38294,6 +40345,12 @@ const FocusContext = reactExports.createContext({
38294
40345
  });
38295
40346
  FocusContext.displayName = 'InternalFocusContext';
38296
40347
 
40348
+ // eslint-disable-next-line @typescript-eslint/naming-convention
40349
+ const CursorContext = reactExports.createContext({
40350
+ setCursorPosition() { },
40351
+ });
40352
+ CursorContext.displayName = 'InternalCursorContext';
40353
+
38297
40354
  var escapeStringRegexp;
38298
40355
  var hasRequiredEscapeStringRegexp;
38299
40356
 
@@ -38856,102 +40913,130 @@ function ErrorOverview({ error }) {
38856
40913
  })))));
38857
40914
  }
38858
40915
 
38859
- const tab = '\t';
38860
- const shiftTab = '\u001B[Z';
38861
- const escape = '\u001B';
38862
- // Root component for all Ink apps
38863
- // It renders stdin and stdout contexts, so that children can access them if needed
38864
- // It also handles Ctrl+C exiting and cursor visibility
38865
- class App extends reactExports.PureComponent {
38866
- static displayName = 'InternalApp';
40916
+ // Error boundary must be a class component since getDerivedStateFromError
40917
+ // and componentDidCatch are not available as hooks
40918
+ class ErrorBoundary extends reactExports.PureComponent {
40919
+ static displayName = 'InternalErrorBoundary';
38867
40920
  static getDerivedStateFromError(error) {
38868
40921
  return { error };
38869
40922
  }
38870
40923
  state = {
38871
- isFocusEnabled: true,
38872
- activeFocusId: undefined,
38873
- focusables: [],
38874
40924
  error: undefined,
38875
40925
  };
40926
+ componentDidCatch(error) {
40927
+ this.props.onError(error);
40928
+ }
40929
+ render() {
40930
+ if (this.state.error) {
40931
+ return React.createElement(ErrorOverview, { error: this.state.error });
40932
+ }
40933
+ return this.props.children;
40934
+ }
40935
+ }
40936
+
40937
+ const tab = '\t';
40938
+ const shiftTab = '\u001B[Z';
40939
+ const escape = '\u001B';
40940
+ // Root component for all Ink apps
40941
+ // It renders stdin and stdout contexts, so that children can access them if needed
40942
+ // It also handles Ctrl+C exiting and cursor visibility
40943
+ function App({ children, stdin, stdout, stderr, writeToStdout, writeToStderr, exitOnCtrlC, onExit, setCursorPosition, }) {
40944
+ const [isFocusEnabled, setIsFocusEnabled] = reactExports.useState(true);
40945
+ const [activeFocusId, setActiveFocusId] = reactExports.useState(undefined);
40946
+ // Focusables array is managed internally via setFocusables callback pattern
40947
+ // eslint-disable-next-line react/hook-use-state
40948
+ const [, setFocusables] = reactExports.useState([]);
40949
+ // Track focusables count for tab navigation check (avoids stale closure)
40950
+ const focusablesCountRef = reactExports.useRef(0);
38876
40951
  // Count how many components enabled raw mode to avoid disabling
38877
40952
  // raw mode until all components don't need it anymore
38878
- rawModeEnabledCount = 0;
40953
+ const rawModeEnabledCount = reactExports.useRef(0);
38879
40954
  // eslint-disable-next-line @typescript-eslint/naming-convention
38880
- internal_eventEmitter = new EventEmitter();
40955
+ const internal_eventEmitter = reactExports.useRef(new EventEmitter());
40956
+ // Each useInput hook adds a listener, so the count can legitimately exceed the default limit of 10.
40957
+ internal_eventEmitter.current.setMaxListeners(Infinity);
40958
+ // Store the currently attached readable listener to avoid stale closure issues
40959
+ const readableListenerRef = reactExports.useRef(undefined);
40960
+ const inputParserRef = reactExports.useRef(createInputParser());
40961
+ const pendingInputFlushRef = reactExports.useRef(undefined);
40962
+ const clearPendingInputFlush = reactExports.useCallback(() => {
40963
+ if (!pendingInputFlushRef.current) {
40964
+ return;
40965
+ }
40966
+ clearImmediate(pendingInputFlushRef.current);
40967
+ pendingInputFlushRef.current = undefined;
40968
+ }, []);
38881
40969
  // Determines if TTY is supported on the provided stdin
38882
- isRawModeSupported() {
38883
- return this.props.stdin.isTTY;
38884
- }
38885
- render() {
38886
- return (React.createElement(AppContext.Provider
38887
- // eslint-disable-next-line react/jsx-no-constructed-context-values
38888
- , {
38889
- // eslint-disable-next-line react/jsx-no-constructed-context-values
38890
- value: {
38891
- exit: this.handleExit,
38892
- } },
38893
- React.createElement(StdinContext.Provider
38894
- // eslint-disable-next-line react/jsx-no-constructed-context-values
38895
- , {
38896
- // eslint-disable-next-line react/jsx-no-constructed-context-values
38897
- value: {
38898
- stdin: this.props.stdin,
38899
- setRawMode: this.handleSetRawMode,
38900
- isRawModeSupported: this.isRawModeSupported(),
38901
- // eslint-disable-next-line @typescript-eslint/naming-convention
38902
- internal_exitOnCtrlC: this.props.exitOnCtrlC,
38903
- // eslint-disable-next-line @typescript-eslint/naming-convention
38904
- internal_eventEmitter: this.internal_eventEmitter,
38905
- } },
38906
- React.createElement(StdoutContext.Provider
38907
- // eslint-disable-next-line react/jsx-no-constructed-context-values
38908
- , {
38909
- // eslint-disable-next-line react/jsx-no-constructed-context-values
38910
- value: {
38911
- stdout: this.props.stdout,
38912
- write: this.props.writeToStdout,
38913
- } },
38914
- React.createElement(StderrContext.Provider
38915
- // eslint-disable-next-line react/jsx-no-constructed-context-values
38916
- , {
38917
- // eslint-disable-next-line react/jsx-no-constructed-context-values
38918
- value: {
38919
- stderr: this.props.stderr,
38920
- write: this.props.writeToStderr,
38921
- } },
38922
- React.createElement(FocusContext.Provider
38923
- // eslint-disable-next-line react/jsx-no-constructed-context-values
38924
- , {
38925
- // eslint-disable-next-line react/jsx-no-constructed-context-values
38926
- value: {
38927
- activeId: this.state.activeFocusId,
38928
- add: this.addFocusable,
38929
- remove: this.removeFocusable,
38930
- activate: this.activateFocusable,
38931
- deactivate: this.deactivateFocusable,
38932
- enableFocus: this.enableFocus,
38933
- disableFocus: this.disableFocus,
38934
- focusNext: this.focusNext,
38935
- focusPrevious: this.focusPrevious,
38936
- focus: this.focus,
38937
- } }, this.state.error ? (React.createElement(ErrorOverview, { error: this.state.error })) : (this.props.children)))))));
38938
- }
38939
- componentDidMount() {
38940
- cliCursor.hide(this.props.stdout);
38941
- }
38942
- componentWillUnmount() {
38943
- cliCursor.show(this.props.stdout);
38944
- // ignore calling setRawMode on an handle stdin it cannot be called
38945
- if (this.isRawModeSupported()) {
38946
- this.handleSetRawMode(false);
40970
+ const isRawModeSupported = stdin.isTTY;
40971
+ const detachReadableListener = reactExports.useCallback(() => {
40972
+ if (!readableListenerRef.current) {
40973
+ return;
38947
40974
  }
38948
- }
38949
- componentDidCatch(error) {
38950
- this.handleExit(error);
38951
- }
38952
- handleSetRawMode = (isEnabled) => {
38953
- const { stdin } = this.props;
38954
- if (!this.isRawModeSupported()) {
40975
+ stdin.removeListener('readable', readableListenerRef.current);
40976
+ readableListenerRef.current = undefined;
40977
+ }, [stdin]);
40978
+ const disableRawMode = reactExports.useCallback(() => {
40979
+ stdin.setRawMode(false);
40980
+ detachReadableListener();
40981
+ stdin.unref();
40982
+ rawModeEnabledCount.current = 0;
40983
+ inputParserRef.current.reset();
40984
+ clearPendingInputFlush();
40985
+ }, [stdin, detachReadableListener, clearPendingInputFlush]);
40986
+ const handleExit = reactExports.useCallback((errorOrResult) => {
40987
+ if (isRawModeSupported && rawModeEnabledCount.current > 0) {
40988
+ disableRawMode();
40989
+ }
40990
+ onExit(errorOrResult);
40991
+ }, [isRawModeSupported, disableRawMode, onExit]);
40992
+ const handleInput = reactExports.useCallback((input) => {
40993
+ // Exit on Ctrl+C
40994
+ // eslint-disable-next-line unicorn/no-hex-escape
40995
+ if (input === '\x03' && exitOnCtrlC) {
40996
+ handleExit();
40997
+ return;
40998
+ }
40999
+ // Reset focus when there's an active focused component on Esc
41000
+ if (input === escape) {
41001
+ setActiveFocusId(currentActiveFocusId => {
41002
+ if (currentActiveFocusId) {
41003
+ return undefined;
41004
+ }
41005
+ return currentActiveFocusId;
41006
+ });
41007
+ }
41008
+ }, [exitOnCtrlC, handleExit]);
41009
+ const emitInput = reactExports.useCallback((input) => {
41010
+ handleInput(input);
41011
+ internal_eventEmitter.current.emit('input', input);
41012
+ }, [handleInput]);
41013
+ const schedulePendingInputFlush = reactExports.useCallback(() => {
41014
+ clearPendingInputFlush();
41015
+ pendingInputFlushRef.current = setImmediate(() => {
41016
+ pendingInputFlushRef.current = undefined;
41017
+ const pendingEscape = inputParserRef.current.flushPendingEscape();
41018
+ if (!pendingEscape) {
41019
+ return;
41020
+ }
41021
+ emitInput(pendingEscape);
41022
+ });
41023
+ }, [clearPendingInputFlush, emitInput]);
41024
+ const handleReadable = reactExports.useCallback(() => {
41025
+ clearPendingInputFlush();
41026
+ let chunk;
41027
+ // eslint-disable-next-line @typescript-eslint/ban-types
41028
+ while ((chunk = stdin.read()) !== null) {
41029
+ const inputEvents = inputParserRef.current.push(chunk);
41030
+ for (const input of inputEvents) {
41031
+ emitInput(input);
41032
+ }
41033
+ }
41034
+ if (inputParserRef.current.hasPendingEscape()) {
41035
+ schedulePendingInputFlush();
41036
+ }
41037
+ }, [stdin, emitInput, clearPendingInputFlush, schedulePendingInputFlush]);
41038
+ const handleSetRawMode = reactExports.useCallback((isEnabled) => {
41039
+ if (!isRawModeSupported) {
38955
41040
  if (stdin === process$1.stdin) {
38956
41041
  throw new Error('Raw mode is not supported on the current process.stdin, which Ink uses as input stream by default.\nRead about how to prevent this error on https://github.com/vadimdemedes/ink/#israwmodesupported');
38957
41042
  }
@@ -38962,185 +41047,338 @@ class App extends reactExports.PureComponent {
38962
41047
  stdin.setEncoding('utf8');
38963
41048
  if (isEnabled) {
38964
41049
  // Ensure raw mode is enabled only once
38965
- if (this.rawModeEnabledCount === 0) {
41050
+ if (rawModeEnabledCount.current === 0) {
38966
41051
  stdin.ref();
38967
41052
  stdin.setRawMode(true);
38968
- stdin.addListener('readable', this.handleReadable);
41053
+ // Store the listener reference to avoid stale closure when removing
41054
+ readableListenerRef.current = handleReadable;
41055
+ stdin.addListener('readable', handleReadable);
38969
41056
  }
38970
- this.rawModeEnabledCount++;
41057
+ rawModeEnabledCount.current++;
38971
41058
  return;
38972
41059
  }
38973
41060
  // Disable raw mode only when no components left that are using it
38974
- if (--this.rawModeEnabledCount === 0) {
38975
- stdin.setRawMode(false);
38976
- stdin.removeListener('readable', this.handleReadable);
38977
- stdin.unref();
41061
+ if (rawModeEnabledCount.current === 0) {
41062
+ return;
38978
41063
  }
38979
- };
38980
- handleReadable = () => {
38981
- let chunk;
38982
- // eslint-disable-next-line @typescript-eslint/ban-types
38983
- while ((chunk = this.props.stdin.read()) !== null) {
38984
- this.handleInput(chunk);
38985
- this.internal_eventEmitter.emit('input', chunk);
41064
+ if (--rawModeEnabledCount.current === 0) {
41065
+ disableRawMode();
38986
41066
  }
38987
- };
38988
- handleInput = (input) => {
38989
- // Exit on Ctrl+C
38990
- // eslint-disable-next-line unicorn/no-hex-escape
38991
- if (input === '\x03' && this.props.exitOnCtrlC) {
38992
- this.handleExit();
41067
+ }, [isRawModeSupported, stdin, handleReadable, disableRawMode]);
41068
+ // Focus navigation helpers
41069
+ const findNextFocusable = reactExports.useCallback((currentFocusables, currentActiveFocusId) => {
41070
+ const activeIndex = currentFocusables.findIndex(focusable => {
41071
+ return focusable.id === currentActiveFocusId;
41072
+ });
41073
+ for (let index = activeIndex + 1; index < currentFocusables.length; index++) {
41074
+ const focusable = currentFocusables[index];
41075
+ if (focusable?.isActive) {
41076
+ return focusable.id;
41077
+ }
38993
41078
  }
38994
- // Reset focus when there's an active focused component on Esc
38995
- if (input === escape && this.state.activeFocusId) {
38996
- this.setState({
38997
- activeFocusId: undefined,
38998
- });
41079
+ return undefined;
41080
+ }, []);
41081
+ const findPreviousFocusable = reactExports.useCallback((currentFocusables, currentActiveFocusId) => {
41082
+ const activeIndex = currentFocusables.findIndex(focusable => {
41083
+ return focusable.id === currentActiveFocusId;
41084
+ });
41085
+ for (let index = activeIndex - 1; index >= 0; index--) {
41086
+ const focusable = currentFocusables[index];
41087
+ if (focusable?.isActive) {
41088
+ return focusable.id;
41089
+ }
38999
41090
  }
39000
- if (this.state.isFocusEnabled && this.state.focusables.length > 0) {
41091
+ return undefined;
41092
+ }, []);
41093
+ const focusNext = reactExports.useCallback(() => {
41094
+ setFocusables(currentFocusables => {
41095
+ setActiveFocusId(currentActiveFocusId => {
41096
+ const firstFocusableId = currentFocusables.find(focusable => focusable.isActive)?.id;
41097
+ const nextFocusableId = findNextFocusable(currentFocusables, currentActiveFocusId);
41098
+ return nextFocusableId ?? firstFocusableId;
41099
+ });
41100
+ return currentFocusables;
41101
+ });
41102
+ }, [findNextFocusable]);
41103
+ const focusPrevious = reactExports.useCallback(() => {
41104
+ setFocusables(currentFocusables => {
41105
+ setActiveFocusId(currentActiveFocusId => {
41106
+ const lastFocusableId = currentFocusables.findLast(focusable => focusable.isActive)?.id;
41107
+ const previousFocusableId = findPreviousFocusable(currentFocusables, currentActiveFocusId);
41108
+ return previousFocusableId ?? lastFocusableId;
41109
+ });
41110
+ return currentFocusables;
41111
+ });
41112
+ }, [findPreviousFocusable]);
41113
+ // Handle tab navigation via effect that subscribes to input events
41114
+ reactExports.useEffect(() => {
41115
+ const handleTabNavigation = (input) => {
41116
+ if (!isFocusEnabled || focusablesCountRef.current === 0)
41117
+ return;
39001
41118
  if (input === tab) {
39002
- this.focusNext();
41119
+ focusNext();
39003
41120
  }
39004
41121
  if (input === shiftTab) {
39005
- this.focusPrevious();
41122
+ focusPrevious();
39006
41123
  }
39007
- }
39008
- };
39009
- handleExit = (error) => {
39010
- if (this.isRawModeSupported()) {
39011
- this.handleSetRawMode(false);
39012
- }
39013
- this.props.onExit(error);
39014
- };
39015
- enableFocus = () => {
39016
- this.setState({
39017
- isFocusEnabled: true,
41124
+ };
41125
+ internal_eventEmitter.current.on('input', handleTabNavigation);
41126
+ const emitter = internal_eventEmitter.current;
41127
+ return () => {
41128
+ emitter.off('input', handleTabNavigation);
41129
+ };
41130
+ }, [isFocusEnabled, focusNext, focusPrevious]);
41131
+ const enableFocus = reactExports.useCallback(() => {
41132
+ setIsFocusEnabled(true);
41133
+ }, []);
41134
+ const disableFocus = reactExports.useCallback(() => {
41135
+ setIsFocusEnabled(false);
41136
+ }, []);
41137
+ const focus = reactExports.useCallback((id) => {
41138
+ setFocusables(currentFocusables => {
41139
+ const hasFocusableId = currentFocusables.some(focusable => focusable?.id === id);
41140
+ if (hasFocusableId) {
41141
+ setActiveFocusId(id);
41142
+ }
41143
+ return currentFocusables;
39018
41144
  });
39019
- };
39020
- disableFocus = () => {
39021
- this.setState({
39022
- isFocusEnabled: false,
41145
+ }, []);
41146
+ const addFocusable = reactExports.useCallback((id, { autoFocus }) => {
41147
+ setFocusables(currentFocusables => {
41148
+ focusablesCountRef.current = currentFocusables.length + 1;
41149
+ return [
41150
+ ...currentFocusables,
41151
+ {
41152
+ id,
41153
+ isActive: true,
41154
+ },
41155
+ ];
39023
41156
  });
39024
- };
39025
- focus = (id) => {
39026
- this.setState(previousState => {
39027
- const hasFocusableId = previousState.focusables.some(focusable => focusable?.id === id);
39028
- if (!hasFocusableId) {
39029
- return previousState;
41157
+ if (autoFocus) {
41158
+ setActiveFocusId(currentActiveFocusId => {
41159
+ if (!currentActiveFocusId) {
41160
+ return id;
41161
+ }
41162
+ return currentActiveFocusId;
41163
+ });
41164
+ }
41165
+ }, []);
41166
+ const removeFocusable = reactExports.useCallback((id) => {
41167
+ setActiveFocusId(currentActiveFocusId => {
41168
+ if (currentActiveFocusId === id) {
41169
+ return undefined;
39030
41170
  }
39031
- return { activeFocusId: id };
41171
+ return currentActiveFocusId;
39032
41172
  });
39033
- };
39034
- focusNext = () => {
39035
- this.setState(previousState => {
39036
- const firstFocusableId = previousState.focusables.find(focusable => focusable.isActive)?.id;
39037
- const nextFocusableId = this.findNextFocusable(previousState);
39038
- return {
39039
- activeFocusId: nextFocusableId ?? firstFocusableId,
39040
- };
41173
+ setFocusables(currentFocusables => {
41174
+ const filtered = currentFocusables.filter(focusable => {
41175
+ return focusable.id !== id;
41176
+ });
41177
+ focusablesCountRef.current = filtered.length;
41178
+ return filtered;
39041
41179
  });
39042
- };
39043
- focusPrevious = () => {
39044
- this.setState(previousState => {
39045
- const lastFocusableId = previousState.focusables.findLast(focusable => focusable.isActive)?.id;
39046
- const previousFocusableId = this.findPreviousFocusable(previousState);
41180
+ }, []);
41181
+ const activateFocusable = reactExports.useCallback((id) => {
41182
+ setFocusables(currentFocusables => currentFocusables.map(focusable => {
41183
+ if (focusable.id !== id) {
41184
+ return focusable;
41185
+ }
39047
41186
  return {
39048
- activeFocusId: previousFocusableId ?? lastFocusableId,
41187
+ id,
41188
+ isActive: true,
39049
41189
  };
41190
+ }));
41191
+ }, []);
41192
+ const deactivateFocusable = reactExports.useCallback((id) => {
41193
+ setActiveFocusId(currentActiveFocusId => {
41194
+ if (currentActiveFocusId === id) {
41195
+ return undefined;
41196
+ }
41197
+ return currentActiveFocusId;
39050
41198
  });
39051
- };
39052
- addFocusable = (id, { autoFocus }) => {
39053
- this.setState(previousState => {
39054
- let nextFocusId = previousState.activeFocusId;
39055
- if (!nextFocusId && autoFocus) {
39056
- nextFocusId = id;
41199
+ setFocusables(currentFocusables => currentFocusables.map(focusable => {
41200
+ if (focusable.id !== id) {
41201
+ return focusable;
39057
41202
  }
39058
41203
  return {
39059
- activeFocusId: nextFocusId,
39060
- focusables: [
39061
- ...previousState.focusables,
39062
- {
39063
- id,
39064
- isActive: true,
39065
- },
39066
- ],
41204
+ id,
41205
+ isActive: false,
39067
41206
  };
39068
- });
39069
- };
39070
- removeFocusable = (id) => {
39071
- this.setState(previousState => ({
39072
- activeFocusId: previousState.activeFocusId === id
39073
- ? undefined
39074
- : previousState.activeFocusId,
39075
- focusables: previousState.focusables.filter(focusable => {
39076
- return focusable.id !== id;
39077
- }),
39078
41207
  }));
39079
- };
39080
- activateFocusable = (id) => {
39081
- this.setState(previousState => ({
39082
- focusables: previousState.focusables.map(focusable => {
39083
- if (focusable.id !== id) {
39084
- return focusable;
39085
- }
39086
- return {
39087
- id,
39088
- isActive: true,
39089
- };
39090
- }),
39091
- }));
39092
- };
39093
- deactivateFocusable = (id) => {
39094
- this.setState(previousState => ({
39095
- activeFocusId: previousState.activeFocusId === id
39096
- ? undefined
39097
- : previousState.activeFocusId,
39098
- focusables: previousState.focusables.map(focusable => {
39099
- if (focusable.id !== id) {
39100
- return focusable;
39101
- }
39102
- return {
39103
- id,
39104
- isActive: false,
39105
- };
39106
- }),
39107
- }));
39108
- };
39109
- findNextFocusable = (state) => {
39110
- const activeIndex = state.focusables.findIndex(focusable => {
39111
- return focusable.id === state.activeFocusId;
39112
- });
39113
- for (let index = activeIndex + 1; index < state.focusables.length; index++) {
39114
- const focusable = state.focusables[index];
39115
- if (focusable?.isActive) {
39116
- return focusable.id;
39117
- }
39118
- }
39119
- return undefined;
39120
- };
39121
- findPreviousFocusable = (state) => {
39122
- const activeIndex = state.focusables.findIndex(focusable => {
39123
- return focusable.id === state.activeFocusId;
39124
- });
39125
- for (let index = activeIndex - 1; index >= 0; index--) {
39126
- const focusable = state.focusables[index];
39127
- if (focusable?.isActive) {
39128
- return focusable.id;
41208
+ }, []);
41209
+ // Handle cursor visibility and raw mode cleanup on unmount
41210
+ reactExports.useEffect(() => {
41211
+ return () => {
41212
+ cliCursor.show(stdout);
41213
+ if (isRawModeSupported && rawModeEnabledCount.current > 0) {
41214
+ disableRawMode();
39129
41215
  }
39130
- }
39131
- return undefined;
39132
- };
41216
+ };
41217
+ }, [stdout, isRawModeSupported, disableRawMode]);
41218
+ // Memoize context values to prevent unnecessary re-renders
41219
+ const appContextValue = reactExports.useMemo(() => ({
41220
+ exit: handleExit,
41221
+ }), [handleExit]);
41222
+ const stdinContextValue = reactExports.useMemo(() => ({
41223
+ stdin,
41224
+ setRawMode: handleSetRawMode,
41225
+ isRawModeSupported,
41226
+ // eslint-disable-next-line @typescript-eslint/naming-convention
41227
+ internal_exitOnCtrlC: exitOnCtrlC,
41228
+ // eslint-disable-next-line @typescript-eslint/naming-convention
41229
+ internal_eventEmitter: internal_eventEmitter.current,
41230
+ }), [stdin, handleSetRawMode, isRawModeSupported, exitOnCtrlC]);
41231
+ const stdoutContextValue = reactExports.useMemo(() => ({
41232
+ stdout,
41233
+ write: writeToStdout,
41234
+ }), [stdout, writeToStdout]);
41235
+ const stderrContextValue = reactExports.useMemo(() => ({
41236
+ stderr,
41237
+ write: writeToStderr,
41238
+ }), [stderr, writeToStderr]);
41239
+ const cursorContextValue = reactExports.useMemo(() => ({
41240
+ setCursorPosition,
41241
+ }), [setCursorPosition]);
41242
+ const focusContextValue = reactExports.useMemo(() => ({
41243
+ activeId: activeFocusId,
41244
+ add: addFocusable,
41245
+ remove: removeFocusable,
41246
+ activate: activateFocusable,
41247
+ deactivate: deactivateFocusable,
41248
+ enableFocus,
41249
+ disableFocus,
41250
+ focusNext,
41251
+ focusPrevious,
41252
+ focus,
41253
+ }), [
41254
+ activeFocusId,
41255
+ addFocusable,
41256
+ removeFocusable,
41257
+ activateFocusable,
41258
+ deactivateFocusable,
41259
+ enableFocus,
41260
+ disableFocus,
41261
+ focusNext,
41262
+ focusPrevious,
41263
+ focus,
41264
+ ]);
41265
+ return (React.createElement(AppContext.Provider, { value: appContextValue },
41266
+ React.createElement(StdinContext.Provider, { value: stdinContextValue },
41267
+ React.createElement(StdoutContext.Provider, { value: stdoutContextValue },
41268
+ React.createElement(StderrContext.Provider, { value: stderrContextValue },
41269
+ React.createElement(FocusContext.Provider, { value: focusContextValue },
41270
+ React.createElement(CursorContext.Provider, { value: cursorContextValue },
41271
+ React.createElement(ErrorBoundary, { onError: handleExit }, children))))))));
41272
+ }
41273
+ App.displayName = 'InternalApp';
41274
+
41275
+ // Kitty keyboard protocol flags.
41276
+ // @see https://sw.kovidgoyal.net/kitty/keyboard-protocol/
41277
+ const kittyFlags = {
41278
+ disambiguateEscapeCodes: 1,
41279
+ reportEventTypes: 2,
41280
+ reportAlternateKeys: 4,
41281
+ reportAllKeysAsEscapeCodes: 8,
41282
+ reportAssociatedText: 16,
41283
+ };
41284
+ // Converts an array of flag names to the corresponding bitmask value.
41285
+ function resolveFlags(flags) {
41286
+ let result = 0;
41287
+ for (const flag of flags) {
41288
+ // eslint-disable-next-line no-bitwise
41289
+ result |= kittyFlags[flag];
41290
+ }
41291
+ return result;
39133
41292
  }
41293
+ // Kitty keyboard modifier bits.
41294
+ // These are used in the modifier parameter of CSI u sequences.
41295
+ // Note: The actual modifier value is (modifiers - 1) as per the protocol.
41296
+ const kittyModifiers = {
41297
+ shift: 1,
41298
+ alt: 2,
41299
+ ctrl: 4,
41300
+ super: 8,
41301
+ hyper: 16,
41302
+ meta: 32,
41303
+ capsLock: 64,
41304
+ numLock: 128,
41305
+ };
39134
41306
 
39135
41307
  const noop = () => { };
41308
+ const kittyQueryEscapeByte = 0x1b;
41309
+ const kittyQueryOpenBracketByte = 0x5b;
41310
+ const kittyQueryQuestionMarkByte = 0x3f;
41311
+ const kittyQueryLetterByte = 0x75;
41312
+ const zeroByte = 0x30;
41313
+ const nineByte = 0x39;
41314
+ const isDigitByte = (byte) => byte >= zeroByte && byte <= nineByte;
41315
+ const matchKittyQueryResponse = (buffer, startIndex) => {
41316
+ if (buffer[startIndex] !== kittyQueryEscapeByte ||
41317
+ buffer[startIndex + 1] !== kittyQueryOpenBracketByte ||
41318
+ buffer[startIndex + 2] !== kittyQueryQuestionMarkByte) {
41319
+ return undefined;
41320
+ }
41321
+ let index = startIndex + 3;
41322
+ const digitsStartIndex = index;
41323
+ while (index < buffer.length && isDigitByte(buffer[index])) {
41324
+ index++;
41325
+ }
41326
+ if (index === digitsStartIndex) {
41327
+ return undefined;
41328
+ }
41329
+ if (index === buffer.length) {
41330
+ return { state: 'partial' };
41331
+ }
41332
+ if (buffer[index] === kittyQueryLetterByte) {
41333
+ return { state: 'complete', endIndex: index };
41334
+ }
41335
+ return undefined;
41336
+ };
41337
+ const hasCompleteKittyQueryResponse = (buffer) => {
41338
+ for (let index = 0; index < buffer.length; index++) {
41339
+ const match = matchKittyQueryResponse(buffer, index);
41340
+ if (match?.state === 'complete') {
41341
+ return true;
41342
+ }
41343
+ }
41344
+ return false;
41345
+ };
41346
+ const stripKittyQueryResponsesAndTrailingPartial = (buffer) => {
41347
+ const keptBytes = [];
41348
+ let index = 0;
41349
+ while (index < buffer.length) {
41350
+ const match = matchKittyQueryResponse(buffer, index);
41351
+ if (match?.state === 'complete') {
41352
+ index = match.endIndex + 1;
41353
+ continue;
41354
+ }
41355
+ if (match?.state === 'partial') {
41356
+ break;
41357
+ }
41358
+ keptBytes.push(buffer[index]);
41359
+ index++;
41360
+ }
41361
+ return keptBytes;
41362
+ };
41363
+ const isErrorInput = (value) => {
41364
+ return (value instanceof Error ||
41365
+ Object.prototype.toString.call(value) === '[object Error]');
41366
+ };
39136
41367
  class Ink {
41368
+ /**
41369
+ Whether this instance is using concurrent rendering mode.
41370
+ */
41371
+ isConcurrent;
39137
41372
  options;
39138
41373
  log;
41374
+ cursorPosition;
39139
41375
  throttledLog;
39140
41376
  isScreenReaderEnabled;
39141
41377
  // Ignore last render after unmounting a tree to prevent empty output before exit
39142
41378
  isUnmounted;
41379
+ isUnmounting;
39143
41380
  lastOutput;
41381
+ lastOutputToRender;
39144
41382
  lastOutputHeight;
39145
41383
  lastTerminalWidth;
39146
41384
  container;
@@ -39149,8 +41387,14 @@ class Ink {
39149
41387
  // so that it's rerendered every time, not just new static parts, like in non-debug mode
39150
41388
  fullStaticOutput;
39151
41389
  exitPromise;
41390
+ exitResult;
41391
+ beforeExitHandler;
39152
41392
  restoreConsole;
39153
41393
  unsubscribeResize;
41394
+ throttledOnRender;
41395
+ hasPendingThrottledRender = false;
41396
+ kittyProtocolEnabled = false;
41397
+ cancelKittyDetection;
39154
41398
  constructor(options) {
39155
41399
  autoBind(this);
39156
41400
  this.options = options;
@@ -39162,43 +41406,64 @@ class Ink {
39162
41406
  const unthrottled = options.debug || this.isScreenReaderEnabled;
39163
41407
  const maxFps = options.maxFps ?? 30;
39164
41408
  const renderThrottleMs = maxFps > 0 ? Math.max(1, Math.ceil(1000 / maxFps)) : 0;
39165
- this.rootNode.onRender = unthrottled
39166
- ? this.onRender
39167
- : throttle(this.onRender, renderThrottleMs, {
41409
+ if (unthrottled) {
41410
+ this.rootNode.onRender = this.onRender;
41411
+ this.throttledOnRender = undefined;
41412
+ }
41413
+ else {
41414
+ const throttled = throttle(this.onRender, renderThrottleMs, {
39168
41415
  leading: true,
39169
41416
  trailing: true,
39170
41417
  });
41418
+ this.rootNode.onRender = () => {
41419
+ this.hasPendingThrottledRender = true;
41420
+ throttled();
41421
+ };
41422
+ this.throttledOnRender = throttled;
41423
+ }
39171
41424
  this.rootNode.onImmediateRender = this.onRender;
39172
41425
  this.log = logUpdate.create(options.stdout, {
39173
41426
  incremental: options.incrementalRendering,
39174
41427
  });
41428
+ this.cursorPosition = undefined;
39175
41429
  this.throttledLog = unthrottled
39176
41430
  ? this.log
39177
- : throttle(this.log, undefined, {
41431
+ : throttle((output) => {
41432
+ const shouldWrite = this.log.willRender(output);
41433
+ const sync = shouldSynchronize(this.options.stdout);
41434
+ if (sync && shouldWrite) {
41435
+ this.options.stdout.write(bsu);
41436
+ }
41437
+ this.log(output);
41438
+ if (sync && shouldWrite) {
41439
+ this.options.stdout.write(esu);
41440
+ }
41441
+ }, undefined, {
39178
41442
  leading: true,
39179
41443
  trailing: true,
39180
41444
  });
39181
41445
  // Ignore last render after unmounting a tree to prevent empty output before exit
39182
41446
  this.isUnmounted = false;
41447
+ this.isUnmounting = false;
41448
+ // Store concurrent mode setting
41449
+ this.isConcurrent = options.concurrent ?? false;
39183
41450
  // Store last output to only rerender when needed
39184
41451
  this.lastOutput = '';
41452
+ this.lastOutputToRender = '';
39185
41453
  this.lastOutputHeight = 0;
39186
41454
  this.lastTerminalWidth = this.getTerminalWidth();
39187
41455
  // This variable is used only in debug mode to store full static output
39188
41456
  // so that it's rerendered every time, not just new static parts, like in non-debug mode
39189
41457
  this.fullStaticOutput = '';
41458
+ // Use ConcurrentRoot for concurrent mode, LegacyRoot for legacy mode
41459
+ const rootTag = options.concurrent ? constantsExports.ConcurrentRoot : constantsExports.LegacyRoot;
39190
41460
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
39191
- this.container = reconciler.createContainer(this.rootNode, constantsExports.LegacyRoot, null, false, null, 'id', () => { }, () => { }, () => { }, () => { }, null);
41461
+ this.container = reconciler.createContainer(this.rootNode, rootTag, null, false, null, 'id', () => { }, () => { }, () => { }, () => { });
39192
41462
  // Unmount when process exits
39193
41463
  this.unsubscribeExit = signalExit(this.unmount, { alwaysLast: false });
39194
- if (process$1.env['DEV'] === 'true') {
39195
- reconciler.injectIntoDevTools({
39196
- bundleType: 0,
39197
- // Reporting React DOM's version, not Ink's
39198
- // See https://github.com/facebook/react/issues/16666#issuecomment-532639905
39199
- version: '16.13.1',
39200
- rendererPackageName: 'ink',
39201
- });
41464
+ if (isDev()) {
41465
+ // @ts-expect-error outdated types
41466
+ reconciler.injectIntoDevTools();
39202
41467
  }
39203
41468
  if (options.patchConsole) {
39204
41469
  this.patchConsole();
@@ -39209,11 +41474,16 @@ class Ink {
39209
41474
  options.stdout.off('resize', this.resized);
39210
41475
  };
39211
41476
  }
41477
+ this.initKittyKeyboard();
39212
41478
  }
39213
41479
  getTerminalWidth = () => {
39214
41480
  // The 'columns' property can be undefined or 0 when not using a TTY.
39215
- // In that case we fall back to 80.
39216
- return this.options.stdout.columns || 80;
41481
+ // Use terminal-size as a fallback for piped processes, then default to 80.
41482
+ if (this.options.stdout.columns) {
41483
+ return this.options.stdout.columns;
41484
+ }
41485
+ const size = terminalSize();
41486
+ return size?.columns ?? 80;
39217
41487
  };
39218
41488
  resized = () => {
39219
41489
  const currentWidth = this.getTerminalWidth();
@@ -39221,6 +41491,7 @@ class Ink {
39221
41491
  // We clear the screen when decreasing terminal width to prevent duplicate overlapping re-renders.
39222
41492
  this.log.clear();
39223
41493
  this.lastOutput = '';
41494
+ this.lastOutputToRender = '';
39224
41495
  }
39225
41496
  this.calculateLayout();
39226
41497
  this.onRender();
@@ -39229,12 +41500,34 @@ class Ink {
39229
41500
  resolveExitPromise = () => { };
39230
41501
  rejectExitPromise = () => { };
39231
41502
  unsubscribeExit = () => { };
41503
+ handleAppExit = (errorOrResult) => {
41504
+ if (this.isUnmounted || this.isUnmounting) {
41505
+ return;
41506
+ }
41507
+ if (isErrorInput(errorOrResult)) {
41508
+ this.unmount(errorOrResult);
41509
+ return;
41510
+ }
41511
+ this.exitResult = errorOrResult;
41512
+ this.unmount();
41513
+ };
41514
+ setCursorPosition = (position) => {
41515
+ this.cursorPosition = position;
41516
+ this.log.setCursorPosition(position);
41517
+ };
41518
+ restoreLastOutput = () => {
41519
+ // Clear() resets log-update's cursor state, so replay the latest cursor intent
41520
+ // before restoring output after external stdout/stderr writes.
41521
+ this.log.setCursorPosition(this.cursorPosition);
41522
+ this.log(this.lastOutputToRender || this.lastOutput + '\n');
41523
+ };
39232
41524
  calculateLayout = () => {
39233
41525
  const terminalWidth = this.getTerminalWidth();
39234
41526
  this.rootNode.yogaNode.setWidth(terminalWidth);
39235
41527
  this.rootNode.yogaNode.calculateLayout(undefined, undefined, Yoga.DIRECTION_LTR);
39236
41528
  };
39237
41529
  onRender = () => {
41530
+ this.hasPendingThrottledRender = false;
39238
41531
  if (this.isUnmounted) {
39239
41532
  return;
39240
41533
  }
@@ -39255,10 +41548,15 @@ class Ink {
39255
41548
  this.options.stdout.write(staticOutput);
39256
41549
  }
39257
41550
  this.lastOutput = output;
41551
+ this.lastOutputToRender = output + '\n';
39258
41552
  this.lastOutputHeight = outputHeight;
39259
41553
  return;
39260
41554
  }
39261
41555
  if (this.isScreenReaderEnabled) {
41556
+ const sync = shouldSynchronize(this.options.stdout);
41557
+ if (sync) {
41558
+ this.options.stdout.write(bsu);
41559
+ }
39262
41560
  if (hasStaticOutput) {
39263
41561
  // We need to erase the main output before writing new static output
39264
41562
  const erase = this.lastOutputHeight > 0
@@ -39269,9 +41567,12 @@ class Ink {
39269
41567
  this.lastOutputHeight = 0;
39270
41568
  }
39271
41569
  if (output === this.lastOutput && !hasStaticOutput) {
41570
+ if (sync) {
41571
+ this.options.stdout.write(esu);
41572
+ }
39272
41573
  return;
39273
41574
  }
39274
- const terminalWidth = this.options.stdout.columns || 80;
41575
+ const terminalWidth = this.getTerminalWidth();
39275
41576
  const wrappedOutput = wrapAnsi(output, terminalWidth, {
39276
41577
  trim: false,
39277
41578
  hard: true,
@@ -39287,41 +41588,69 @@ class Ink {
39287
41588
  this.options.stdout.write(erase + wrappedOutput);
39288
41589
  }
39289
41590
  this.lastOutput = output;
41591
+ this.lastOutputToRender = wrappedOutput;
39290
41592
  this.lastOutputHeight =
39291
41593
  wrappedOutput === '' ? 0 : wrappedOutput.split('\n').length;
41594
+ if (sync) {
41595
+ this.options.stdout.write(esu);
41596
+ }
39292
41597
  return;
39293
41598
  }
39294
41599
  if (hasStaticOutput) {
39295
41600
  this.fullStaticOutput += staticOutput;
39296
41601
  }
41602
+ // Detect fullscreen: output fills or exceeds terminal height.
41603
+ // Only apply when writing to a real TTY — piped output always gets trailing newlines.
41604
+ const isFullscreen = this.options.stdout.isTTY && outputHeight >= this.options.stdout.rows;
41605
+ const outputToRender = isFullscreen ? output : output + '\n';
39297
41606
  if (this.lastOutputHeight >= this.options.stdout.rows) {
41607
+ const sync = shouldSynchronize(this.options.stdout);
41608
+ if (sync) {
41609
+ this.options.stdout.write(bsu);
41610
+ }
39298
41611
  this.options.stdout.write(clearTerminal + this.fullStaticOutput + output);
39299
41612
  this.lastOutput = output;
41613
+ this.lastOutputToRender = outputToRender;
39300
41614
  this.lastOutputHeight = outputHeight;
39301
- this.log.sync(output);
41615
+ this.log.sync(outputToRender);
41616
+ if (sync) {
41617
+ this.options.stdout.write(esu);
41618
+ }
39302
41619
  return;
39303
41620
  }
39304
41621
  // To ensure static output is cleanly rendered before main output, clear main output first
39305
41622
  if (hasStaticOutput) {
41623
+ const sync = shouldSynchronize(this.options.stdout);
41624
+ if (sync) {
41625
+ this.options.stdout.write(bsu);
41626
+ }
39306
41627
  this.log.clear();
39307
41628
  this.options.stdout.write(staticOutput);
39308
- this.log(output);
41629
+ this.log(outputToRender);
41630
+ if (sync) {
41631
+ this.options.stdout.write(esu);
41632
+ }
39309
41633
  }
39310
- if (!hasStaticOutput && output !== this.lastOutput) {
39311
- this.throttledLog(output);
41634
+ else if (output !== this.lastOutput || this.log.isCursorDirty()) {
41635
+ // ThrottledLog manages its own bsu/esu at actual write time
41636
+ this.throttledLog(outputToRender);
39312
41637
  }
39313
41638
  this.lastOutput = output;
41639
+ this.lastOutputToRender = outputToRender;
39314
41640
  this.lastOutputHeight = outputHeight;
39315
41641
  };
39316
41642
  render(node) {
39317
41643
  const tree = (React.createElement(accessibilityContext.Provider, { value: { isScreenReaderEnabled: this.isScreenReaderEnabled } },
39318
- React.createElement(App, { stdin: this.options.stdin, stdout: this.options.stdout, stderr: this.options.stderr, writeToStdout: this.writeToStdout, writeToStderr: this.writeToStderr, exitOnCtrlC: this.options.exitOnCtrlC, onExit: this.unmount }, node)));
39319
- // @ts-expect-error the types for `react-reconciler` are not up to date with the library.
39320
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call
39321
- reconciler.updateContainerSync(tree, this.container, null, noop);
39322
- // @ts-expect-error the types for `react-reconciler` are not up to date with the library.
39323
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call
39324
- reconciler.flushSyncWork();
41644
+ React.createElement(App, { stdin: this.options.stdin, stdout: this.options.stdout, stderr: this.options.stderr, exitOnCtrlC: this.options.exitOnCtrlC, writeToStdout: this.writeToStdout, writeToStderr: this.writeToStderr, setCursorPosition: this.setCursorPosition, onExit: this.handleAppExit }, node)));
41645
+ if (this.options.concurrent) {
41646
+ // Concurrent mode: use updateContainer (async scheduling)
41647
+ reconciler.updateContainer(tree, this.container, null, noop);
41648
+ }
41649
+ else {
41650
+ // Legacy mode: use updateContainerSync + flushSyncWork (sync)
41651
+ reconciler.updateContainerSync(tree, this.container, null, noop);
41652
+ reconciler.flushSyncWork();
41653
+ }
39325
41654
  }
39326
41655
  writeToStdout(data) {
39327
41656
  if (this.isUnmounted) {
@@ -39335,9 +41664,16 @@ class Ink {
39335
41664
  this.options.stdout.write(data);
39336
41665
  return;
39337
41666
  }
41667
+ const sync = shouldSynchronize(this.options.stdout);
41668
+ if (sync) {
41669
+ this.options.stdout.write(bsu);
41670
+ }
39338
41671
  this.log.clear();
39339
41672
  this.options.stdout.write(data);
39340
- this.log(this.lastOutput);
41673
+ this.restoreLastOutput();
41674
+ if (sync) {
41675
+ this.options.stdout.write(esu);
41676
+ }
39341
41677
  }
39342
41678
  writeToStderr(data) {
39343
41679
  if (this.isUnmounted) {
@@ -39352,17 +41688,57 @@ class Ink {
39352
41688
  this.options.stderr.write(data);
39353
41689
  return;
39354
41690
  }
41691
+ const sync = shouldSynchronize(this.options.stdout);
41692
+ if (sync) {
41693
+ this.options.stdout.write(bsu);
41694
+ }
39355
41695
  this.log.clear();
39356
41696
  this.options.stderr.write(data);
39357
- this.log(this.lastOutput);
41697
+ this.restoreLastOutput();
41698
+ if (sync) {
41699
+ this.options.stdout.write(esu);
41700
+ }
39358
41701
  }
39359
41702
  // eslint-disable-next-line @typescript-eslint/ban-types
39360
41703
  unmount(error) {
39361
- if (this.isUnmounted) {
41704
+ if (this.isUnmounted || this.isUnmounting) {
39362
41705
  return;
39363
41706
  }
39364
- this.calculateLayout();
39365
- this.onRender();
41707
+ this.isUnmounting = true;
41708
+ if (this.beforeExitHandler) {
41709
+ process$1.off('beforeExit', this.beforeExitHandler);
41710
+ this.beforeExitHandler = undefined;
41711
+ }
41712
+ const stdout = this.options.stdout;
41713
+ const canWriteToStdout = !stdout.destroyed && !stdout.writableEnded && (stdout.writable ?? true);
41714
+ const settleThrottle = (throttled) => {
41715
+ if (typeof throttled.flush !== 'function') {
41716
+ return;
41717
+ }
41718
+ if (canWriteToStdout) {
41719
+ throttled.flush();
41720
+ }
41721
+ else if (typeof throttled.cancel === 'function') {
41722
+ throttled.cancel();
41723
+ }
41724
+ };
41725
+ // Clear any pending throttled render timer on unmount. When stdout is writable,
41726
+ // flush so the final frame is emitted; otherwise cancel to avoid delayed callbacks.
41727
+ settleThrottle(this.throttledOnRender ?? {});
41728
+ if (canWriteToStdout) {
41729
+ // If throttling is enabled and there is already a pending render, flushing above
41730
+ // is sufficient. Also avoid calling onRender() again when static output already
41731
+ // exists, as that can duplicate <Static> children output on exit (see issue #397).
41732
+ const shouldRenderFinalFrame = !this.throttledOnRender ||
41733
+ (!this.hasPendingThrottledRender && this.fullStaticOutput === '');
41734
+ if (shouldRenderFinalFrame) {
41735
+ this.calculateLayout();
41736
+ this.onRender();
41737
+ }
41738
+ }
41739
+ // Mark as unmounted after the final render but before stdout writes
41740
+ // that could re-enter exit() via synchronous write callbacks.
41741
+ this.isUnmounted = true;
39366
41742
  this.unsubscribeExit();
39367
41743
  if (typeof this.restoreConsole === 'function') {
39368
41744
  this.restoreConsole();
@@ -39370,27 +41746,71 @@ class Ink {
39370
41746
  if (typeof this.unsubscribeResize === 'function') {
39371
41747
  this.unsubscribeResize();
39372
41748
  }
39373
- // CIs don't handle erasing ansi escapes well, so it's better to
39374
- // only render last frame of non-static output
39375
- if (isInCi) {
39376
- this.options.stdout.write(this.lastOutput + '\n');
41749
+ // Cancel any in-progress auto-detection before checking protocol state
41750
+ if (this.cancelKittyDetection) {
41751
+ this.cancelKittyDetection();
41752
+ }
41753
+ // Flush any pending throttled log writes if possible, otherwise cancel to
41754
+ // prevent delayed callbacks from writing to a closed stream.
41755
+ const throttledLog = this.throttledLog;
41756
+ settleThrottle(throttledLog);
41757
+ if (canWriteToStdout) {
41758
+ if (this.kittyProtocolEnabled) {
41759
+ try {
41760
+ this.options.stdout.write('\u001B[<u');
41761
+ }
41762
+ catch {
41763
+ // Best-effort: stdout may already be destroyed during shutdown
41764
+ }
41765
+ }
41766
+ // CIs don't handle erasing ansi escapes well, so it's better to
41767
+ // only render last frame of non-static output
41768
+ if (isInCi) {
41769
+ this.options.stdout.write(this.lastOutput + '\n');
41770
+ }
41771
+ else if (!this.options.debug) {
41772
+ this.log.done();
41773
+ }
39377
41774
  }
39378
- else if (!this.options.debug) {
39379
- this.log.done();
41775
+ this.kittyProtocolEnabled = false;
41776
+ if (this.options.concurrent) {
41777
+ // Concurrent mode: use updateContainer (async scheduling)
41778
+ reconciler.updateContainer(null, this.container, null, noop);
41779
+ }
41780
+ else {
41781
+ // Legacy mode: use updateContainerSync + flushSyncWork (sync)
41782
+ reconciler.updateContainerSync(null, this.container, null, noop);
41783
+ reconciler.flushSyncWork();
39380
41784
  }
39381
- this.isUnmounted = true;
39382
- // @ts-expect-error the types for `react-reconciler` are not up to date with the library.
39383
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call
39384
- reconciler.updateContainerSync(null, this.container, null, noop);
39385
- // @ts-expect-error the types for `react-reconciler` are not up to date with the library.
39386
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call
39387
- reconciler.flushSyncWork();
39388
41785
  instances.delete(this.options.stdout);
39389
- if (error instanceof Error) {
39390
- this.rejectExitPromise(error);
41786
+ // Ensure all queued writes have been processed before resolving the
41787
+ // exit promise. For real writable streams, queue an empty write as a
41788
+ // barrier — its callback fires only after all prior writes complete.
41789
+ // For non-stream objects (e.g. test spies), resolve on next tick.
41790
+ //
41791
+ // When called from signal-exit during process shutdown (error is a
41792
+ // number or null rather than undefined/Error), resolve synchronously
41793
+ // because the event loop is draining and async callbacks won't fire.
41794
+ const { exitResult } = this;
41795
+ const resolveOrReject = () => {
41796
+ if (isErrorInput(error)) {
41797
+ this.rejectExitPromise(error);
41798
+ }
41799
+ else {
41800
+ this.resolveExitPromise(exitResult);
41801
+ }
41802
+ };
41803
+ const isProcessExiting = error !== undefined && !isErrorInput(error);
41804
+ const hasWritableState = stdout._writableState !== undefined ||
41805
+ stdout.writableLength !== undefined;
41806
+ if (isProcessExiting) {
41807
+ resolveOrReject();
41808
+ }
41809
+ else if (canWriteToStdout && hasWritableState) {
41810
+ this.options.stdout.write('', resolveOrReject);
39391
41811
  }
39392
41812
  else {
39393
- this.resolveExitPromise();
41813
+ setImmediate(resolveOrReject);
39394
41814
  }
39395
41815
  }
39396
41816
  async waitUntilExit() {
@@ -39398,11 +41818,20 @@ class Ink {
39398
41818
  this.resolveExitPromise = resolve;
39399
41819
  this.rejectExitPromise = reject;
39400
41820
  });
41821
+ if (!this.beforeExitHandler) {
41822
+ this.beforeExitHandler = () => {
41823
+ this.unmount();
41824
+ };
41825
+ process$1.once('beforeExit', this.beforeExitHandler);
41826
+ }
39401
41827
  return this.exitPromise;
39402
41828
  }
39403
41829
  clear() {
39404
41830
  if (!isInCi && !this.options.debug) {
39405
41831
  this.log.clear();
41832
+ // Sync lastOutput so that unmount's final onRender
41833
+ // sees it as unchanged and log-update skips it
41834
+ this.log.sync(this.lastOutputToRender || this.lastOutput + '\n');
39406
41835
  }
39407
41836
  }
39408
41837
  patchConsole() {
@@ -39421,6 +41850,73 @@ class Ink {
39421
41850
  }
39422
41851
  });
39423
41852
  }
41853
+ initKittyKeyboard() {
41854
+ // Protocol is opt-in: if kittyKeyboard is not specified, do nothing
41855
+ if (!this.options.kittyKeyboard) {
41856
+ return;
41857
+ }
41858
+ const opts = this.options.kittyKeyboard;
41859
+ const mode = opts.mode ?? 'auto';
41860
+ if (mode === 'disabled' ||
41861
+ !this.options.stdin.isTTY ||
41862
+ !this.options.stdout.isTTY) {
41863
+ return;
41864
+ }
41865
+ const flags = opts.flags ?? ['disambiguateEscapeCodes'];
41866
+ if (mode === 'enabled') {
41867
+ this.enableKittyProtocol(flags);
41868
+ return;
41869
+ }
41870
+ // Auto mode: use heuristic precheck, then confirm with protocol query
41871
+ const term = process$1.env['TERM'] ?? '';
41872
+ const termProgram = process$1.env['TERM_PROGRAM'] ?? '';
41873
+ const isKnownSupportingTerminal = 'KITTY_WINDOW_ID' in process$1.env ||
41874
+ term === 'xterm-kitty' ||
41875
+ termProgram === 'WezTerm' ||
41876
+ termProgram === 'ghostty';
41877
+ if (!isInCi && isKnownSupportingTerminal) {
41878
+ this.confirmKittySupport(flags);
41879
+ }
41880
+ }
41881
+ confirmKittySupport(flags) {
41882
+ const { stdin, stdout } = this.options;
41883
+ let responseBuffer = [];
41884
+ const cleanup = () => {
41885
+ this.cancelKittyDetection = undefined;
41886
+ clearTimeout(timer);
41887
+ stdin.removeListener('data', onData);
41888
+ // Re-emit any buffered data that wasn't the protocol response,
41889
+ // so it isn't lost from Ink's normal input pipeline.
41890
+ // Clear responseBuffer afterwards to make cleanup idempotent.
41891
+ const remaining = stripKittyQueryResponsesAndTrailingPartial(responseBuffer);
41892
+ responseBuffer = [];
41893
+ if (remaining.length > 0) {
41894
+ stdin.unshift(Buffer.from(remaining));
41895
+ }
41896
+ };
41897
+ const onData = (data) => {
41898
+ const chunk = typeof data === 'string' ? Buffer.from(data) : data;
41899
+ for (const byte of chunk) {
41900
+ responseBuffer.push(byte);
41901
+ }
41902
+ if (hasCompleteKittyQueryResponse(responseBuffer)) {
41903
+ cleanup();
41904
+ if (!this.isUnmounted) {
41905
+ this.enableKittyProtocol(flags);
41906
+ }
41907
+ }
41908
+ };
41909
+ // Attach listener before writing the query so that synchronous
41910
+ // or immediate responses are not missed.
41911
+ stdin.on('data', onData);
41912
+ const timer = setTimeout(cleanup, 200);
41913
+ this.cancelKittyDetection = cleanup;
41914
+ stdout.write('\u001B[?u');
41915
+ }
41916
+ enableKittyProtocol(flags) {
41917
+ this.options.stdout.write(`\u001B[>${resolveFlags(flags)}u`);
41918
+ this.kittyProtocolEnabled = true;
41919
+ }
39424
41920
  }
39425
41921
 
39426
41922
  /**
@@ -39436,9 +41932,10 @@ const render = (node, options) => {
39436
41932
  patchConsole: true,
39437
41933
  maxFps: 30,
39438
41934
  incrementalRendering: false,
41935
+ concurrent: false,
39439
41936
  ...getOptions(options),
39440
41937
  };
39441
- const instance = getInstance(inkOptions.stdout, () => new Ink(inkOptions));
41938
+ const instance = getInstance(inkOptions.stdout, () => new Ink(inkOptions), inkOptions.concurrent ?? false);
39442
41939
  instance.render(node);
39443
41940
  return {
39444
41941
  rerender: instance.render,
@@ -39459,12 +41956,16 @@ const getOptions = (stdout = {}) => {
39459
41956
  }
39460
41957
  return stdout;
39461
41958
  };
39462
- const getInstance = (stdout, createInstance) => {
41959
+ const getInstance = (stdout, createInstance, concurrent) => {
39463
41960
  let instance = instances.get(stdout);
39464
41961
  if (!instance) {
39465
41962
  instance = createInstance();
39466
41963
  instances.set(stdout, instance);
39467
41964
  }
41965
+ else if (instance.isConcurrent !== concurrent) {
41966
+ console.warn(`Warning: render() was called with concurrent: ${concurrent}, but the existing instance for this stdout uses concurrent: ${instance.isConcurrent}. ` +
41967
+ `The concurrent option only takes effect on the first render. Call unmount() first if you need to change the rendering mode.`);
41968
+ }
39468
41969
  return instance;
39469
41970
  };
39470
41971
 
@@ -39584,6 +42085,249 @@ const isCtrlKey = (code) => {
39584
42085
  '[8^',
39585
42086
  ].includes(code);
39586
42087
  };
42088
+ // Kitty keyboard protocol: CSI codepoint ; modifiers [: eventType] [; text-as-codepoints] u
42089
+ const kittyKeyRe = /^\x1b\[(\d+)(?:;(\d+)(?::(\d+))?(?:;([\d:]+))?)?u$/;
42090
+ // Kitty-enhanced special keys: CSI number ; modifiers : eventType {letter|~}
42091
+ // These are legacy CSI sequences enhanced with the :eventType field.
42092
+ // Examples: \x1b[1;1:1A (up arrow press), \x1b[3;1:3~ (delete release)
42093
+ const kittySpecialKeyRe = /^\x1b\[(\d+);(\d+):(\d+)([A-Za-z~])$/;
42094
+ // Letter-terminated special key names (CSI 1 ; mods letter)
42095
+ const kittySpecialLetterKeys = {
42096
+ A: 'up',
42097
+ B: 'down',
42098
+ C: 'right',
42099
+ D: 'left',
42100
+ E: 'clear',
42101
+ F: 'end',
42102
+ H: 'home',
42103
+ P: 'f1',
42104
+ Q: 'f2',
42105
+ R: 'f3',
42106
+ S: 'f4',
42107
+ };
42108
+ // Number-terminated special key names (CSI number ; mods ~)
42109
+ const kittySpecialNumberKeys = {
42110
+ 2: 'insert',
42111
+ 3: 'delete',
42112
+ 5: 'pageup',
42113
+ 6: 'pagedown',
42114
+ 7: 'home',
42115
+ 8: 'end',
42116
+ 11: 'f1',
42117
+ 12: 'f2',
42118
+ 13: 'f3',
42119
+ 14: 'f4',
42120
+ 15: 'f5',
42121
+ 17: 'f6',
42122
+ 18: 'f7',
42123
+ 19: 'f8',
42124
+ 20: 'f9',
42125
+ 21: 'f10',
42126
+ 23: 'f11',
42127
+ 24: 'f12',
42128
+ };
42129
+ // Map of special codepoints to key names in kitty protocol
42130
+ const kittyCodepointNames = {
42131
+ 27: 'escape',
42132
+ // 13 (return) and 32 (space) are handled before this lookup
42133
+ // in parseKittyKeypress so they can be marked as printable.
42134
+ 9: 'tab',
42135
+ 127: 'delete',
42136
+ 8: 'backspace',
42137
+ 57358: 'capslock',
42138
+ 57359: 'scrolllock',
42139
+ 57360: 'numlock',
42140
+ 57361: 'printscreen',
42141
+ 57362: 'pause',
42142
+ 57363: 'menu',
42143
+ 57376: 'f13',
42144
+ 57377: 'f14',
42145
+ 57378: 'f15',
42146
+ 57379: 'f16',
42147
+ 57380: 'f17',
42148
+ 57381: 'f18',
42149
+ 57382: 'f19',
42150
+ 57383: 'f20',
42151
+ 57384: 'f21',
42152
+ 57385: 'f22',
42153
+ 57386: 'f23',
42154
+ 57387: 'f24',
42155
+ 57388: 'f25',
42156
+ 57389: 'f26',
42157
+ 57390: 'f27',
42158
+ 57391: 'f28',
42159
+ 57392: 'f29',
42160
+ 57393: 'f30',
42161
+ 57394: 'f31',
42162
+ 57395: 'f32',
42163
+ 57396: 'f33',
42164
+ 57397: 'f34',
42165
+ 57398: 'f35',
42166
+ 57399: 'kp0',
42167
+ 57400: 'kp1',
42168
+ 57401: 'kp2',
42169
+ 57402: 'kp3',
42170
+ 57403: 'kp4',
42171
+ 57404: 'kp5',
42172
+ 57405: 'kp6',
42173
+ 57406: 'kp7',
42174
+ 57407: 'kp8',
42175
+ 57408: 'kp9',
42176
+ 57409: 'kpdecimal',
42177
+ 57410: 'kpdivide',
42178
+ 57411: 'kpmultiply',
42179
+ 57412: 'kpsubtract',
42180
+ 57413: 'kpadd',
42181
+ 57414: 'kpenter',
42182
+ 57415: 'kpequal',
42183
+ 57416: 'kpseparator',
42184
+ 57417: 'kpleft',
42185
+ 57418: 'kpright',
42186
+ 57419: 'kpup',
42187
+ 57420: 'kpdown',
42188
+ 57421: 'kppageup',
42189
+ 57422: 'kppagedown',
42190
+ 57423: 'kphome',
42191
+ 57424: 'kpend',
42192
+ 57425: 'kpinsert',
42193
+ 57426: 'kpdelete',
42194
+ 57427: 'kpbegin',
42195
+ 57428: 'mediaplay',
42196
+ 57429: 'mediapause',
42197
+ 57430: 'mediaplaypause',
42198
+ 57431: 'mediareverse',
42199
+ 57432: 'mediastop',
42200
+ 57433: 'mediafastforward',
42201
+ 57434: 'mediarewind',
42202
+ 57435: 'mediatracknext',
42203
+ 57436: 'mediatrackprevious',
42204
+ 57437: 'mediarecord',
42205
+ 57438: 'lowervolume',
42206
+ 57439: 'raisevolume',
42207
+ 57440: 'mutevolume',
42208
+ 57441: 'leftshift',
42209
+ 57442: 'leftcontrol',
42210
+ 57443: 'leftalt',
42211
+ 57444: 'leftsuper',
42212
+ 57445: 'lefthyper',
42213
+ 57446: 'leftmeta',
42214
+ 57447: 'rightshift',
42215
+ 57448: 'rightcontrol',
42216
+ 57449: 'rightalt',
42217
+ 57450: 'rightsuper',
42218
+ 57451: 'righthyper',
42219
+ 57452: 'rightmeta',
42220
+ 57453: 'isoLevel3Shift',
42221
+ 57454: 'isoLevel5Shift',
42222
+ };
42223
+ // Valid Unicode codepoint range, excluding surrogates
42224
+ const isValidCodepoint = (cp) => cp >= 0 && cp <= 0x10_ffff && !(cp >= 0xd8_00 && cp <= 0xdf_ff);
42225
+ const safeFromCodePoint = (cp) => isValidCodepoint(cp) ? String.fromCodePoint(cp) : '?';
42226
+ function resolveEventType(value) {
42227
+ if (value === 3)
42228
+ return 'release';
42229
+ if (value === 2)
42230
+ return 'repeat';
42231
+ return 'press';
42232
+ }
42233
+ function parseKittyModifiers(modifiers) {
42234
+ return {
42235
+ ctrl: !!(modifiers & kittyModifiers.ctrl),
42236
+ shift: !!(modifiers & kittyModifiers.shift),
42237
+ meta: !!(modifiers & kittyModifiers.meta),
42238
+ option: !!(modifiers & kittyModifiers.alt),
42239
+ super: !!(modifiers & kittyModifiers.super),
42240
+ hyper: !!(modifiers & kittyModifiers.hyper),
42241
+ capsLock: !!(modifiers & kittyModifiers.capsLock),
42242
+ numLock: !!(modifiers & kittyModifiers.numLock),
42243
+ };
42244
+ }
42245
+ const parseKittyKeypress = (s) => {
42246
+ const match = kittyKeyRe.exec(s);
42247
+ if (!match)
42248
+ return null;
42249
+ const codepoint = parseInt(match[1], 10);
42250
+ const modifiers = match[2] ? Math.max(0, parseInt(match[2], 10) - 1) : 0;
42251
+ const eventType = match[3] ? parseInt(match[3], 10) : 1;
42252
+ const textField = match[4];
42253
+ // Bail on invalid primary codepoint
42254
+ if (!isValidCodepoint(codepoint)) {
42255
+ return null;
42256
+ }
42257
+ // Parse text-as-codepoints field (colon-separated Unicode codepoints)
42258
+ let text;
42259
+ if (textField) {
42260
+ text = textField
42261
+ .split(':')
42262
+ .map(cp => safeFromCodePoint(parseInt(cp, 10)))
42263
+ .join('');
42264
+ }
42265
+ // Determine key name from codepoint
42266
+ let name;
42267
+ let isPrintable;
42268
+ if (codepoint === 32) {
42269
+ name = 'space';
42270
+ isPrintable = true;
42271
+ }
42272
+ else if (codepoint === 13) {
42273
+ name = 'return';
42274
+ isPrintable = true;
42275
+ }
42276
+ else if (kittyCodepointNames[codepoint]) {
42277
+ name = kittyCodepointNames[codepoint];
42278
+ isPrintable = false;
42279
+ }
42280
+ else if (codepoint >= 1 && codepoint <= 26) {
42281
+ // Ctrl+letter comes as codepoint 1-26
42282
+ name = String.fromCodePoint(codepoint + 96); // 'a' is 97
42283
+ isPrintable = false;
42284
+ }
42285
+ else {
42286
+ name = safeFromCodePoint(codepoint).toLowerCase();
42287
+ isPrintable = true;
42288
+ }
42289
+ // Default text to the character from the codepoint when not explicitly
42290
+ // provided by the protocol, so keys like space and return produce their
42291
+ // expected text input (' ' and '\r' respectively).
42292
+ if (isPrintable && !text) {
42293
+ text = safeFromCodePoint(codepoint);
42294
+ }
42295
+ return {
42296
+ name,
42297
+ ...parseKittyModifiers(modifiers),
42298
+ eventType: resolveEventType(eventType),
42299
+ sequence: s,
42300
+ raw: s,
42301
+ isKittyProtocol: true,
42302
+ isPrintable,
42303
+ text,
42304
+ };
42305
+ };
42306
+ // Parse kitty-enhanced special key sequences (arrow keys, function keys, etc.)
42307
+ // These use the legacy CSI format but with an added :eventType field.
42308
+ const parseKittySpecialKey = (s) => {
42309
+ const match = kittySpecialKeyRe.exec(s);
42310
+ if (!match)
42311
+ return null;
42312
+ const number = parseInt(match[1], 10);
42313
+ const modifiers = Math.max(0, parseInt(match[2], 10) - 1);
42314
+ const eventType = parseInt(match[3], 10);
42315
+ const terminator = match[4];
42316
+ const name = terminator === '~'
42317
+ ? kittySpecialNumberKeys[number]
42318
+ : kittySpecialLetterKeys[terminator];
42319
+ if (!name)
42320
+ return null;
42321
+ return {
42322
+ name,
42323
+ ...parseKittyModifiers(modifiers),
42324
+ eventType: resolveEventType(eventType),
42325
+ sequence: s,
42326
+ raw: s,
42327
+ isKittyProtocol: true,
42328
+ isPrintable: false,
42329
+ };
42330
+ };
39587
42331
  const parseKeypress = (s = '') => {
39588
42332
  let parts;
39589
42333
  if (Buffer$1.isBuffer(s)) {
@@ -39601,6 +42345,29 @@ const parseKeypress = (s = '') => {
39601
42345
  else if (!s) {
39602
42346
  s = '';
39603
42347
  }
42348
+ // Try kitty keyboard protocol parsers first
42349
+ const kittyResult = parseKittyKeypress(s);
42350
+ if (kittyResult)
42351
+ return kittyResult;
42352
+ const kittySpecialResult = parseKittySpecialKey(s);
42353
+ if (kittySpecialResult)
42354
+ return kittySpecialResult;
42355
+ // If the input matched the kitty CSI-u pattern but was rejected (e.g.,
42356
+ // invalid codepoint), return a safe empty keypress instead of falling
42357
+ // through to legacy parsing which can produce unsafe states (undefined name)
42358
+ if (kittyKeyRe.test(s)) {
42359
+ return {
42360
+ name: '',
42361
+ ctrl: false,
42362
+ meta: false,
42363
+ shift: false,
42364
+ option: false,
42365
+ sequence: s,
42366
+ raw: s,
42367
+ isKittyProtocol: true,
42368
+ isPrintable: false,
42369
+ };
42370
+ }
39604
42371
  const key = {
39605
42372
  name: '',
39606
42373
  ctrl: false,
@@ -39611,10 +42378,11 @@ const parseKeypress = (s = '') => {
39611
42378
  raw: s,
39612
42379
  };
39613
42380
  key.sequence = key.sequence || s || key.name;
39614
- if (s === '\r') {
39615
- // carriage return
42381
+ if (s === '\r' || s === '\x1b\r') {
42382
+ // carriage return (or option+return on macOS)
39616
42383
  key.raw = undefined;
39617
42384
  key.name = 'return';
42385
+ key.option = s.length === 2;
39618
42386
  }
39619
42387
  else if (s === '\n') {
39620
42388
  // enter, should have been called linefeed
@@ -39742,6 +42510,8 @@ const useInput = (inputHandler, options = {}) => {
39742
42510
  rightArrow: keypress.name === 'right',
39743
42511
  pageDown: keypress.name === 'pagedown',
39744
42512
  pageUp: keypress.name === 'pageup',
42513
+ home: keypress.name === 'home',
42514
+ end: keypress.name === 'end',
39745
42515
  return: keypress.name === 'return',
39746
42516
  escape: keypress.name === 'escape',
39747
42517
  ctrl: keypress.ctrl,
@@ -39754,9 +42524,38 @@ const useInput = (inputHandler, options = {}) => {
39754
42524
  // to avoid breaking changes in Ink.
39755
42525
  // TODO(vadimdemedes): consider removing this in the next major version.
39756
42526
  meta: keypress.meta || keypress.name === 'escape' || keypress.option,
42527
+ // Kitty keyboard protocol modifiers
42528
+ super: keypress.super ?? false,
42529
+ hyper: keypress.hyper ?? false,
42530
+ capsLock: keypress.capsLock ?? false,
42531
+ numLock: keypress.numLock ?? false,
42532
+ eventType: keypress.eventType,
39757
42533
  };
39758
- let input = keypress.ctrl ? keypress.name : keypress.sequence;
39759
- if (nonAlphanumericKeys.includes(keypress.name)) {
42534
+ let input;
42535
+ if (keypress.isKittyProtocol) {
42536
+ // Use text-as-codepoints field for printable keys (needed when
42537
+ // reportAllKeysAsEscapeCodes flag is enabled), suppress non-printable
42538
+ if (keypress.isPrintable) {
42539
+ input = keypress.text ?? keypress.name;
42540
+ }
42541
+ else if (keypress.ctrl && keypress.name.length === 1) {
42542
+ // Ctrl+letter via codepoint 1-26 form: not printable text, but
42543
+ // the letter name must flow through so handlers (e.g. exitOnCtrlC
42544
+ // checking `input === 'c' && key.ctrl`) still work.
42545
+ input = keypress.name;
42546
+ }
42547
+ else {
42548
+ input = '';
42549
+ }
42550
+ }
42551
+ else if (keypress.ctrl) {
42552
+ input = keypress.name;
42553
+ }
42554
+ else {
42555
+ input = keypress.sequence;
42556
+ }
42557
+ if (!keypress.isKittyProtocol &&
42558
+ nonAlphanumericKeys.includes(keypress.name)) {
39760
42559
  input = '';
39761
42560
  }
39762
42561
  // Strip meta if it's still remaining after `parseKeypress`
@@ -40253,4 +43052,4 @@ const layout = {
40253
43052
  };
40254
43053
 
40255
43054
  export { AppContext as A, Box as B, React as R, Text as T, useStdout as a, render as b, colors as c, jsxRuntimeExports as j, layout as l, reactExports as r, symbols as s, useInput as u };
40256
- //# sourceMappingURL=theme-CzLXk_6s.js.map
43055
+ //# sourceMappingURL=theme-BmodcXss.js.map