linegauge 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ofri Peretz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,21 +1,104 @@
1
1
  # linegauge
2
2
 
3
- **Not yet released, and not yet on the roadmap.** This version reserves the name. The layer,
4
- its incumbents and the measurements behind it are documented in
5
- [`.sdlc/research/candidate-layers.md`](https://github.com/ofri-peretz/burgee/blob/main/.sdlc/research/candidate-layers.md).
6
- An intent and a working release follow if and when the roadmap takes it up.
3
+ **Measuring, wrapping, truncating and slicing styled terminal text — without the edge
4
+ fraying.**
7
5
 
8
- A **line gauge** is the printer's ruler — the steel rule marked in picas and points that a compositor uses to measure a line of type and check it fits the measure it was set to.
6
+ A printer's line gauge is the steel rule marked in picas and points: a compositor holds it
7
+ against a line of type and checks it fits the measure it was set to.
9
8
 
10
- That is this package's whole job. Width, wrap, truncate and slice are one problem wearing four names: **cutting styled text without letting the edge come apart** — no dangling escape sequence, no half a grapheme, no severed emoji cluster.
9
+ Zero dependencies. Grapheme-correct over the platform's own `Intl.Segmenter`.
11
10
 
12
- ## What it will be
11
+ ```bash
12
+ npm i linegauge
13
+ ```
13
14
 
14
- - **One package, not eleven.** `width` · `wrap` · `truncate` · `slice` · `strip` · `widest`. The incumbents split this across `string-width`, `wrap-ansi`, `strip-ansi`, `slice-ansi`, `cli-truncate`, `widest-line`, `string-length`, `wcwidth`, `eastasianwidth` and `get-east-asian-width`.
15
- - **Grapheme-correct by construction.** ZWJ families, regional-indicator flags, skin-tone modifiers, keycaps, combining marks and East Asian wide characters, over the platform's own `Intl.Segmenter`.
16
- - **Fast path for ASCII.** A byte scan when the string has no non-ASCII code unit; the segmenter only when it earns its cost.
17
- - **Drop-in paths** for `string-width`, `wrap-ansi`, `strip-ansi` and `slice-ansi`, graded by their own suites.
18
- - **Zero external dependencies.**
15
+ ## One problem wearing five names
16
+
17
+ `width` · `wrap` · `truncate` · `slice` · `widest`
18
+
19
+ They look like five utilities. They are one: **cutting styled text without letting the edge
20
+ come apart** — no dangling escape sequence, no half a grapheme, no severed emoji cluster.
21
+ Each has to know where the ANSI is and where the cluster boundaries are, and once you know
22
+ that, you may as well answer all five.
23
+
24
+ The ecosystem splits it across twelve packages — `strip-ansi`, `string-width`,
25
+ `ansi-regex`, `wrap-ansi`, `emoji-regex`, `slice-ansi`, `get-east-asian-width`,
26
+ `eastasianwidth`, `string-length`, `wcwidth`, `cli-truncate` and `widest-line` — which
27
+ between them sit under most of the terminal ecosystem.
28
+
29
+ ## Use
30
+
31
+ ```js
32
+ import { width, wrap, truncate, slice, widest } from 'linegauge';
33
+
34
+ width('古代'); // 4 — East Asian wide, two columns each
35
+ width('👨‍👩‍👧‍👦'); // 2 — one cluster, not four people
36
+
37
+ wrap('a long sentence that needs folding', 12);
38
+ truncate('the quick brown fox', 10); // 'the quick…'
39
+ slice(styled, 2, 4); // columns 2 and 3, styles intact
40
+ widest(['a', 'bbb', 'cc']); // 3
41
+ ```
42
+
43
+ ### The default export is `string-width`
44
+
45
+ Byte-for-byte call-compatible, so this resolves without a code change:
46
+
47
+ ```json
48
+ { "overrides": { "string-width": "npm:linegauge@^0.2" } }
49
+ ```
50
+
51
+ ## What "without the edge fraying" means
52
+
53
+ **A cluster is atomic.** A cut that would land inside a grapheme drops the whole cluster
54
+ rather than half of it. Half an emoji is not a narrower emoji, it is mojibake, and a flag
55
+ cut down the middle is two unrelated regional-indicator letters.
56
+
57
+ **A style that was open stays open — and gets closed.** A cut re-emits the styles active at
58
+ its start and closes them at its end, so the result is self-contained: paste it anywhere and
59
+ it neither loses its colour nor leaks it into what follows.
60
+
61
+ **The ellipsis is inside the budget, not on top of it.** `truncate(text, 10)` occupies ten
62
+ columns or fewer, never eleven. That is the property a table column depends on, and getting
63
+ it wrong is how a layout gains a phantom column under one input.
64
+
65
+ **`widest` takes lines, not a blob.** It accepts any iterable of strings, so the caller says
66
+ where the boundaries are rather than having a newline convention assumed for them.
67
+
68
+ ## Graded by the packages it replaces
69
+
70
+ The incumbent is the specification. `width` runs against `string-width`, `wrap` against
71
+ `wrap-ansi`, `slice` against `slice-ansi` and `truncate` against `cli-truncate`.
72
+
73
+ ## API
74
+
75
+ | | |
76
+ | :-- | :-- |
77
+ | `width(text, { countAnsiEscapeCodes })` | terminal columns the text occupies |
78
+ | `wrap(text, columns, options)` | fold to a width, styles preserved across rows |
79
+ | `truncate(text, columns, { position, ellipsis })` | cut to a budget, ellipsis counted inside it |
80
+ | `slice(text, start, end)` | the columns `[start, end)`, self-contained |
81
+ | `widest(lines)` | the width of the widest line of any iterable |
82
+ | `lineCount(text, columns)` | rows the text occupies at that width |
83
+ | `measure(text)` | columns of plain text, no escape scan |
84
+
85
+ Non-strings answer `0` rather than throwing, because a width function is usually reached
86
+ with whatever a template produced.
87
+
88
+ ## Design notes
89
+
90
+ **Ambiguous-width characters count narrow**, which is what a terminal does unless told it is
91
+ rendering an East Asian locale. `string-width` makes that an option; nothing above this has
92
+ ever needed the other answer, so it is not one here.
93
+
94
+ **Not a terminal emulator.** Semicolon-delimited SGR, colon-delimited extended colour and
95
+ OSC 8 hyperlinks are understood. Every other complete CSI or OSC command is carried through
96
+ as an opaque zero-width unit, and anything that only looks like an introducer stays plain
97
+ text.
98
+
99
+ **Still at the Design→Build gate:** an exported `strip`, and the ASCII fast path — a byte
100
+ scan when the string has no non-ASCII code unit, so the segmenter is reached only when it
101
+ earns its cost.
19
102
 
20
103
  ## Licence
21
104
 
package/dist/index.d.ts CHANGED
@@ -1,7 +1,33 @@
1
1
  /**
2
- * linegauge — name reserved, not yet on the roadmap. The layer and the measurements
3
- * behind it are documented in .sdlc/research/candidate-layers.md in the burgee
4
- * repository. Until an intent is opened this entry exports only its own name, so
5
- * that importing it costs nothing and promises nothing.
2
+ * Copyright (c) 2026 Ofri Peretz
3
+ * Licensed under the MIT License. Use of this source code is governed by the
4
+ * MIT license that can be found in the LICENSE file.
6
5
  */
7
- export declare const name: "linegauge";
6
+ /**
7
+ * linegauge — how much of a terminal line a string occupies, and how to fold it.
8
+ *
9
+ * F1 of the foundation tier, built by moving rather than by writing: `width` and `wrap`
10
+ * were already in `flagstaff`, already ported, already graded differentially against
11
+ * `string-width` and `wrap-ansi`. This package is where they belong, because measuring a
12
+ * line is not drawing one — a spinner, a box, a table and a status line all need the
13
+ * measurement, and nothing about the measurement needs any of them.
14
+ *
15
+ * The default export is `width`, byte-for-byte call-compatible with `string-width`'s
16
+ * default (R8), so `overrides: { "string-width": "npm:linegauge@^1" }` resolves.
17
+ *
18
+ * `slice`, `truncate` and `widest` came next, built on the style stack `wrap` already
19
+ * carried — which is the consolidation the design is named for: `slice-ansi`, `wrap-ansi`
20
+ * and `cli-truncate` each keep their own copy of it, and they disagree at the edges.
21
+ *
22
+ * `strip` (R3) followed, and it is where the measured divergence from Node's own
23
+ * `stripVTControlCharacters` is recorded — one shape in sixteen, and it was a live bug in
24
+ * `width()`.
25
+ *
26
+ * Still at the Design→Build gate: the R2 ASCII fast path.
27
+ */
28
+ export { lineCount, measure, width, width as default, type WidthOptions } from './width.js';
29
+ export { slice } from './slice.js';
30
+ export { strip } from './strip.js';
31
+ export { truncate, type TruncateOptions } from './truncate.js';
32
+ export { widest } from './widest.js';
33
+ export { wrap, type WrapOptions } from './wrap.js';
package/dist/index.js CHANGED
@@ -1,8 +1,34 @@
1
1
  /**
2
- * linegauge — name reserved, not yet on the roadmap. The layer and the measurements
3
- * behind it are documented in .sdlc/research/candidate-layers.md in the burgee
4
- * repository. Until an intent is opened this entry exports only its own name, so
5
- * that importing it costs nothing and promises nothing.
2
+ * Copyright (c) 2026 Ofri Peretz
3
+ * Licensed under the MIT License. Use of this source code is governed by the
4
+ * MIT license that can be found in the LICENSE file.
6
5
  */
7
- export const name = 'linegauge';
6
+ /**
7
+ * linegauge — how much of a terminal line a string occupies, and how to fold it.
8
+ *
9
+ * F1 of the foundation tier, built by moving rather than by writing: `width` and `wrap`
10
+ * were already in `flagstaff`, already ported, already graded differentially against
11
+ * `string-width` and `wrap-ansi`. This package is where they belong, because measuring a
12
+ * line is not drawing one — a spinner, a box, a table and a status line all need the
13
+ * measurement, and nothing about the measurement needs any of them.
14
+ *
15
+ * The default export is `width`, byte-for-byte call-compatible with `string-width`'s
16
+ * default (R8), so `overrides: { "string-width": "npm:linegauge@^1" }` resolves.
17
+ *
18
+ * `slice`, `truncate` and `widest` came next, built on the style stack `wrap` already
19
+ * carried — which is the consolidation the design is named for: `slice-ansi`, `wrap-ansi`
20
+ * and `cli-truncate` each keep their own copy of it, and they disagree at the edges.
21
+ *
22
+ * `strip` (R3) followed, and it is where the measured divergence from Node's own
23
+ * `stripVTControlCharacters` is recorded — one shape in sixteen, and it was a live bug in
24
+ * `width()`.
25
+ *
26
+ * Still at the Design→Build gate: the R2 ASCII fast path.
27
+ */
28
+ export { lineCount, measure, width, width as default } from './width.js';
29
+ export { slice } from './slice.js';
30
+ export { strip } from './strip.js';
31
+ export { truncate } from './truncate.js';
32
+ export { widest } from './widest.js';
33
+ export { wrap } from './wrap.js';
8
34
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Copyright (c) 2026 Ofri Peretz
3
+ * Licensed under the MIT License. Use of this source code is governed by the
4
+ * MIT license that can be found in the LICENSE file.
5
+ */
6
+ /**
7
+ * `[start, end)` in display columns. A negative or reversed range is empty rather than an
8
+ * error, matching `String.prototype.slice`'s temperament if not its units.
9
+ */
10
+ export declare function slice(string: string, start?: number, end?: number): string;
package/dist/slice.js ADDED
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Copyright (c) 2026 Ofri Peretz
3
+ * Licensed under the MIT License. Use of this source code is governed by the
4
+ * MIT license that can be found in the LICENSE file.
5
+ */
6
+ /**
7
+ * R4 — cut a styled string in **display columns**, never in code units.
8
+ *
9
+ * `"\u001B[31mred\u001B[39m".slice(0, 3)` returns three characters of an escape sequence
10
+ * and no red at all; that is the bug this exists to remove, and it is the same bug in every
11
+ * hand-rolled column-cutter. Three rules, none of which a code-unit slice can honour:
12
+ *
13
+ * 1. **Never split a grapheme cluster.** A cluster that straddles a boundary is included
14
+ * whole — the cut rounds *outward*, never inward, so a slice can be one column wider
15
+ * than asked but never returns half a family emoji.
16
+ * 2. **Close what is open at the cut, and reopen it at the start.** A style opened before
17
+ * `start` is re-emitted at the front of the result, because the caller is going to
18
+ * print this fragment somewhere the opener never reached.
19
+ * 3. **A hyperlink is a style too** (`OSC 8`), closed and reopened the same way.
20
+ *
21
+ * Built on the same stack `wrap` uses rather than a second copy of it, which is the whole
22
+ * consolidation: `slice-ansi` and `wrap-ansi` each carry their own, and they disagree.
23
+ */
24
+ import { applyParameters, closingSequence, hyperlink, matchEscape, openingSequence, segmenter } from './style.js';
25
+ import { measure } from './width.js';
26
+ /**
27
+ * Rule 2. Deliberately without `wrap`'s `applyLeadingResets`: that optimisation drops a
28
+ * style whose reset immediately follows the cut, which is right for a row boundary and
29
+ * wrong here — by the time the first kept cluster is known the walk has already run past
30
+ * the whole plain-text segment, so the "next" sequence it would inspect belongs to text
31
+ * this slice still contains. Reopening a style that is about to be reset costs bytes;
32
+ * dropping one that is not costs the colour.
33
+ */
34
+ function open(cut) {
35
+ if (cut.started)
36
+ return;
37
+ cut.started = true;
38
+ cut.body += openingSequence(cut.active);
39
+ if (cut.link !== undefined)
40
+ cut.body += hyperlink(cut.link.uri, cut.link.parameters);
41
+ }
42
+ /**
43
+ * An escape sequence: it always moves the stack, and is copied through only when the cut
44
+ * has started and has not finished. The stack is what gets re-emitted at `start`, which is
45
+ * why a sequence outside the range is still read.
46
+ */
47
+ function takeEscape(cut, escape, end) {
48
+ const groups = escape.groups ?? {};
49
+ if (groups['sgr'] !== undefined)
50
+ applyParameters(groups['sgr'], cut.active);
51
+ else if (groups['uri'] !== undefined)
52
+ cut.link = groups['uri'].length === 0 ? undefined : { parameters: groups['parameters'] ?? '', uri: groups['uri'] };
53
+ if (cut.started && cut.column < end)
54
+ cut.body += escape[0];
55
+ }
56
+ /** One run of plain text, cluster by cluster. Returns true when the range has been filled. */
57
+ function takeText(cut, run, start, end) {
58
+ for (const { segment } of segmenter.segment(run)) {
59
+ const columns = measure(segment);
60
+ // Rule 1: a cluster is in when any column it occupies is in, so a zero-width mark rides
61
+ // with the cluster it follows rather than falling off the front of a slice.
62
+ if (cut.column + Math.max(columns, 1) > start && cut.column < end) {
63
+ open(cut);
64
+ cut.body += segment;
65
+ }
66
+ cut.column += columns;
67
+ if (cut.column >= end && columns > 0)
68
+ return true;
69
+ }
70
+ return cut.column >= end;
71
+ }
72
+ /**
73
+ * `[start, end)` in display columns. A negative or reversed range is empty rather than an
74
+ * error, matching `String.prototype.slice`'s temperament if not its units.
75
+ */
76
+ export function slice(string, start = 0, end = Number.POSITIVE_INFINITY) {
77
+ if (end <= start || string.length === 0)
78
+ return '';
79
+ const cut = { active: [], link: undefined, column: 0, body: '', started: false };
80
+ let index = 0;
81
+ while (index < string.length) {
82
+ const escape = matchEscape(string, index);
83
+ if (escape !== undefined) {
84
+ takeEscape(cut, escape, end);
85
+ index += escape[0].length;
86
+ continue;
87
+ }
88
+ let run = '';
89
+ while (index < string.length && matchEscape(string, index) === undefined) {
90
+ run += string[index];
91
+ index += 1;
92
+ }
93
+ if (takeText(cut, run, start, end))
94
+ break;
95
+ }
96
+ if (!cut.started)
97
+ return '';
98
+ return cut.body + (cut.link === undefined ? '' : hyperlink('')) + closingSequence(cut.active);
99
+ }
100
+ //# sourceMappingURL=slice.js.map
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Copyright (c) 2026 Ofri Peretz
3
+ * Licensed under the MIT License. Use of this source code is governed by the
4
+ * MIT license that can be found in the LICENSE file.
5
+ */
6
+ /**
7
+ * Everything a terminal would print, with the escape sequences removed: CSI (SGR and the
8
+ * cursor and erase forms), OSC including `OSC 8` hyperlinks under both terminators, DCS, the
9
+ * charset selections, and the single-character escapes. A lone `ESC` with nothing that parses
10
+ * after it is text and is kept — the same answer `strip-ansi` gives.
11
+ *
12
+ * Two passes, which is the design's prescription taken literally: *"using
13
+ * `util.stripVTControlCharacters` where it is exact and a local scan where it is not."*
14
+ *
15
+ * 1. The local scan, over `style.ts`'s `ANSI_ESCAPE`. It removes CSI and OSC **including
16
+ * the colon form** of an extended colour, which is the one shape Node gets wrong.
17
+ * 2. Node's stripper on what is left — the single-character escapes (`ESC c`), the charset
18
+ * selections (`ESC ( B`) and a truncated sequence at the end of a string. `ANSI_ESCAPE`
19
+ * matches none of those, because `wrap` never needed them: it was built to find the
20
+ * sequences it has to *reopen*, and a charset selection is not one.
21
+ *
22
+ * Order is load-bearing. Ours runs first so the colon form is already gone by the time Node
23
+ * sees the string; reversed, pass 2 would leave `:2::255:0:0m` behind as text and pass 1
24
+ * would have nothing left to match.
25
+ */
26
+ export declare function strip(string: string): string;
package/dist/strip.js ADDED
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Copyright (c) 2026 Ofri Peretz
3
+ * Licensed under the MIT License. Use of this source code is governed by the
4
+ * MIT license that can be found in the LICENSE file.
5
+ */
6
+ /**
7
+ * R3 — remove the sequences a terminal consumes, and leave the text it prints.
8
+ *
9
+ * The design says to use `util.stripVTControlCharacters` "where it is exact and a local scan
10
+ * where it is not — measured, and the divergence recorded rather than assumed". Measured, on
11
+ * Node 24 over sixteen sequence shapes: **fifteen agree with `strip-ansi` and one does not.**
12
+ *
13
+ * ESC[38:2::255:0:0m node -> ":2::255:0:0m" strip-ansi -> ""
14
+ *
15
+ * That is the colon form of an extended colour (ITU T.416, which `chalk`, `wrap-ansi` and
16
+ * every 24-bit-colour library emit): Node's scanner stops at the first `:` and leaves the
17
+ * rest of the sequence in the output as text. One shape, and the one that matters most,
18
+ * because it is **not** a rare dialect — it is how a truecolor SGR is written when the
19
+ * sub-parameter form is used.
20
+ *
21
+ * It was a live bug here, not a theoretical one. `width.ts` called
22
+ * `stripVTControlCharacters`, so `width("ESC[38:2::255:0:0mred ESC[39m")` answered **15**
23
+ * where `string-width` answers **3** — and every caller that measures went with it: `wrap`,
24
+ * `slice`, `truncate`, `widest`, and in `flagstaff` the box, the table and the spinner.
25
+ *
26
+ * So the local scan is the whole implementation, over `style.ts`'s `ANSI_ESCAPE` — which has
27
+ * always handled the colon form, because `wrap` needs to reopen those colours across a row.
28
+ * The package understood the syntax in one module and mis-stripped it in another, which is
29
+ * precisely the duplication the consolidation exists to remove.
30
+ */
31
+ import { stripVTControlCharacters as nodeStrip } from 'node:util';
32
+ import { forEachSegment } from './style.js';
33
+ /**
34
+ * Everything a terminal would print, with the escape sequences removed: CSI (SGR and the
35
+ * cursor and erase forms), OSC including `OSC 8` hyperlinks under both terminators, DCS, the
36
+ * charset selections, and the single-character escapes. A lone `ESC` with nothing that parses
37
+ * after it is text and is kept — the same answer `strip-ansi` gives.
38
+ *
39
+ * Two passes, which is the design's prescription taken literally: *"using
40
+ * `util.stripVTControlCharacters` where it is exact and a local scan where it is not."*
41
+ *
42
+ * 1. The local scan, over `style.ts`'s `ANSI_ESCAPE`. It removes CSI and OSC **including
43
+ * the colon form** of an extended colour, which is the one shape Node gets wrong.
44
+ * 2. Node's stripper on what is left — the single-character escapes (`ESC c`), the charset
45
+ * selections (`ESC ( B`) and a truncated sequence at the end of a string. `ANSI_ESCAPE`
46
+ * matches none of those, because `wrap` never needed them: it was built to find the
47
+ * sequences it has to *reopen*, and a charset selection is not one.
48
+ *
49
+ * Order is load-bearing. Ours runs first so the colon form is already gone by the time Node
50
+ * sees the string; reversed, pass 2 would leave `:2::255:0:0m` behind as text and pass 1
51
+ * would have nothing left to match.
52
+ */
53
+ export function strip(string) {
54
+ if (string === '')
55
+ return '';
56
+ let out = '';
57
+ forEachSegment(string, (text) => {
58
+ out += text;
59
+ });
60
+ return nodeStrip(out);
61
+ }
62
+ //# sourceMappingURL=strip.js.map
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Copyright (c) 2026 Ofri Peretz
3
+ * Licensed under the MIT License. Use of this source code is governed by the
4
+ * MIT license that can be found in the LICENSE file.
5
+ */
6
+ /**
7
+ * The style stack — the part of this package that every cutting operation shares.
8
+ *
9
+ * `wrap`, `slice` and `truncate` are one algorithm wearing three names: walk the string,
10
+ * keep a stack of the SGR parameters a terminal currently has open, emit graphemes while
11
+ * inside the range you were asked for, and at every cut emit the closer for whatever is
12
+ * open and re-emit the openers on resume. The twelve incumbents write that three times
13
+ * between them — `wrap-ansi`, `slice-ansi` and `cli-truncate` each carry their own copy —
14
+ * and disagree at the edges, which is most of the reason this package exists.
15
+ *
16
+ * Extracted from `wrap.ts` when `slice` arrived, with wrap-ansi's own suite as the check:
17
+ * this module is a move, and `wrap.test.ts` grades every case against the real `wrap-ansi`,
18
+ * so a mistake in the extraction is a red suite rather than a subtle divergence.
19
+ *
20
+ * It is not a published subpath. `linegauge/wrap` and `linegauge/slice` each reach it, and
21
+ * R8 keeps them from reaching each other.
22
+ */
23
+ export declare const ESC = "\u001B";
24
+ export declare const BELL = "\u0007";
25
+ export declare const CSI = "[";
26
+ export declare const OSC = "]";
27
+ export declare const SGR_TERMINATOR = "m";
28
+ /** How many columns a tab advances to the next stop. */
29
+ export declare const TAB_SIZE = 8;
30
+ export declare const ESCAPES: Set<string>;
31
+ export declare const ANSI_ESCAPE: RegExp;
32
+ export declare const ROW_BOUNDARY: RegExp;
33
+ /** Every printable ASCII character is its own cluster of width one — skip the segmenter. */
34
+ export declare const ASCII_PRINTABLE: RegExp;
35
+ /**
36
+ * Which SGR code closes which modifier. This is ECMA-48, not any library's table — bold
37
+ * opens with 1 and closes with 22 wherever you read it — so it lives here rather than
38
+ * being imported, which keeps `wrap()` free of `roundel/chalk` and takes 18 KB off every
39
+ * subpath that wraps. The colour families close with 39, 49 and 59 and are handled by name
40
+ * above, before this map is consulted.
41
+ */
42
+ export declare const MODIFIER_CLOSE: Map<number, number>;
43
+ export declare const MODIFIER_CLOSE_CODES: Set<number>;
44
+ export declare const segmenter: Intl.Segmenter;
45
+ export declare const sgr: (code: number | string) => string;
46
+ export declare const hyperlink: (url: string, parameters?: string) => string;
47
+ /** The complete escape sequence starting at `index`, or nothing when none starts there. */
48
+ export declare function matchEscape(string: string, index: number): RegExpExecArray | undefined;
49
+ /**
50
+ * Walk a string as alternating runs of plain text and complete escape sequences. A
51
+ * character that looks like an introducer but starts no valid sequence stays plain text.
52
+ */
53
+ export declare function forEachSegment(string: string, onPlainText: (text: string) => void, onEscape?: (escape: string) => void): void;
54
+ export interface SgrToken {
55
+ code: number;
56
+ open: string;
57
+ hasArguments: boolean;
58
+ }
59
+ export interface ActiveStyle {
60
+ /** One slot per thing a terminal tracks separately, so a second red replaces the first. */
61
+ family: string;
62
+ open: string;
63
+ close: number;
64
+ }
65
+ export declare function sgrTokens(parameters: string): SgrToken[];
66
+ export declare function applyToken(token: SgrToken, active: ActiveStyle[]): void;
67
+ export declare const applyParameters: (parameters: string, active: ActiveStyle[]) => void;
68
+ /** A row that opens with its own resets should not have them undone by the reopening. */
69
+ export declare function applyLeadingResets(string: string, startIndex: number, active: ActiveStyle[]): void;
70
+ export declare const closingSequence: (active: ActiveStyle[]) => string;
71
+ export declare const openingSequence: (active: ActiveStyle[]) => string;