parseman 0.45.0 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -3
- package/dist/analysis/choice-cost-gate.d.ts +158 -0
- package/dist/analysis/choice-cost-gate.d.ts.map +1 -0
- package/dist/analysis/choice-cost-render.d.ts +96 -0
- package/dist/analysis/choice-cost-render.d.ts.map +1 -0
- package/dist/analysis/choice-cost.d.ts +383 -0
- package/dist/analysis/choice-cost.d.ts.map +1 -0
- package/dist/analysis/commitment.d.ts +131 -0
- package/dist/analysis/commitment.d.ts.map +1 -0
- package/dist/analysis/corpus.d.ts +73 -0
- package/dist/analysis/corpus.d.ts.map +1 -0
- package/dist/analysis/diagnose-render.d.ts +65 -0
- package/dist/analysis/diagnose-render.d.ts.map +1 -0
- package/dist/analysis/diagnose.d.ts +15 -0
- package/dist/analysis/diagnose.d.ts.map +1 -1
- package/dist/analysis/diagnostics.cjs +13736 -0
- package/dist/analysis/diagnostics.cjs.map +6 -0
- package/dist/analysis/diagnostics.d.ts +27 -0
- package/dist/analysis/diagnostics.d.ts.map +1 -0
- package/dist/analysis/diagnostics.js +13713 -0
- package/dist/analysis/diagnostics.js.map +6 -0
- package/dist/analysis/duplication.d.ts.map +1 -1
- package/dist/analysis/fix-render.d.ts +43 -0
- package/dist/analysis/fix-render.d.ts.map +1 -0
- package/dist/analysis/fix.d.ts +172 -0
- package/dist/analysis/fix.d.ts.map +1 -0
- package/dist/analysis/format-number.d.ts +23 -0
- package/dist/analysis/format-number.d.ts.map +1 -0
- package/dist/analysis/gating.d.ts +47 -2
- package/dist/analysis/gating.d.ts.map +1 -1
- package/dist/analysis/rebuild.d.ts +61 -0
- package/dist/analysis/rebuild.d.ts.map +1 -0
- package/dist/analysis/terminal.d.ts +145 -0
- package/dist/analysis/terminal.d.ts.map +1 -0
- package/dist/cli/index.cjs +14837 -0
- package/dist/cli/index.cjs.map +6 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +14814 -0
- package/dist/cli/index.js.map +6 -0
- package/dist/combinators/adjacency.d.ts +100 -0
- package/dist/combinators/adjacency.d.ts.map +1 -0
- package/dist/combinators/choice.d.ts.map +1 -1
- package/dist/combinators/completions.d.ts.map +1 -1
- package/dist/combinators/dispatch.d.ts +8 -1
- package/dist/combinators/dispatch.d.ts.map +1 -1
- package/dist/combinators/expect.d.ts.map +1 -1
- package/dist/combinators/first-set.d.ts.map +1 -1
- package/dist/combinators/gate.d.ts.map +1 -1
- package/dist/combinators/grammar.d.ts +22 -0
- package/dist/combinators/grammar.d.ts.map +1 -1
- package/dist/combinators/literal.d.ts +8 -0
- package/dist/combinators/literal.d.ts.map +1 -1
- package/dist/combinators/map.d.ts +0 -1
- package/dist/combinators/map.d.ts.map +1 -1
- package/dist/combinators/node.d.ts +13 -3
- package/dist/combinators/node.d.ts.map +1 -1
- package/dist/combinators/parser.d.ts +1 -1
- package/dist/combinators/parser.d.ts.map +1 -1
- package/dist/combinators/ref.d.ts.map +1 -1
- package/dist/combinators/regex.d.ts +1 -16
- package/dist/combinators/regex.d.ts.map +1 -1
- package/dist/combinators/repeat.d.ts +30 -2
- package/dist/combinators/repeat.d.ts.map +1 -1
- package/dist/combinators/scanTo.d.ts +71 -1
- package/dist/combinators/scanTo.d.ts.map +1 -1
- package/dist/combinators/sequence.d.ts.map +1 -1
- package/dist/combinators/token.d.ts.map +1 -1
- package/dist/combinators/trivia-boundary.d.ts +26 -0
- package/dist/combinators/trivia-boundary.d.ts.map +1 -0
- package/dist/combinators/trivia-skip.d.ts +40 -1
- package/dist/combinators/trivia-skip.d.ts.map +1 -1
- package/dist/combinators/withCtx.d.ts.map +1 -1
- package/dist/compiler/build-arity.d.ts.map +1 -1
- package/dist/compiler/compile-linkable-table.d.ts +104 -0
- package/dist/compiler/compile-linkable-table.d.ts.map +1 -0
- package/dist/compiler/degradation.d.ts +0 -2
- package/dist/compiler/degradation.d.ts.map +1 -1
- package/dist/compiler/fields.d.ts +13 -0
- package/dist/compiler/fields.d.ts.map +1 -1
- package/dist/compiler/grammar-coverage-ids.d.ts +5 -5
- package/dist/compiler/grammar-coverage-ids.d.ts.map +1 -1
- package/dist/compiler/ir-serialize.d.ts.map +1 -1
- package/dist/compiler/linker.d.ts +79 -47
- package/dist/compiler/linker.d.ts.map +1 -1
- package/dist/compiler/token-alphabet.d.ts +81 -0
- package/dist/compiler/token-alphabet.d.ts.map +1 -0
- package/dist/compiler/token-dispatch.d.ts +95 -0
- package/dist/compiler/token-dispatch.d.ts.map +1 -0
- package/dist/compiler/token-scanner.d.ts +53 -0
- package/dist/compiler/token-scanner.d.ts.map +1 -0
- package/dist/compiler/value-usage.d.ts.map +1 -1
- package/dist/coverage.d.ts +1 -1
- package/dist/coverage.d.ts.map +1 -1
- package/dist/cst/capture-buffer.d.ts +35 -0
- package/dist/cst/capture-buffer.d.ts.map +1 -1
- package/dist/cst/reflection-attach.d.ts +8 -0
- package/dist/cst/reflection-attach.d.ts.map +1 -0
- package/dist/cst/reflection-symbols.d.ts +10 -0
- package/dist/cst/reflection-symbols.d.ts.map +1 -0
- package/dist/cst/reflection.d.ts +2 -4
- package/dist/cst/reflection.d.ts.map +1 -1
- package/dist/cst/root-trivia-scope.d.ts +26 -0
- package/dist/cst/root-trivia-scope.d.ts.map +1 -0
- package/dist/cst/trivia-charscan.d.ts +18 -0
- package/dist/cst/trivia-charscan.d.ts.map +1 -0
- package/dist/cst/trivia-kinds.d.ts +19 -0
- package/dist/cst/trivia-kinds.d.ts.map +1 -1
- package/dist/functional/doc.d.ts.map +1 -1
- package/dist/functional/run-tabled.d.ts +12 -0
- package/dist/functional/run-tabled.d.ts.map +1 -0
- package/dist/functional/run.d.ts +25 -42
- package/dist/functional/run.d.ts.map +1 -1
- package/dist/grammar-metadata.d.ts +3 -0
- package/dist/grammar-metadata.d.ts.map +1 -0
- package/dist/index.cjs +10499 -7478
- package/dist/index.cjs.map +3 -4
- package/dist/index.d.ts +24 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10499 -7478
- package/dist/index.js.map +3 -4
- package/dist/language-service/index.cjs +311 -121
- package/dist/language-service/index.cjs.map +3 -4
- package/dist/language-service/index.js +311 -121
- package/dist/language-service/index.js.map +3 -4
- package/dist/oracle/index.cjs.map +0 -1
- package/dist/oracle/index.js.map +0 -1
- package/dist/parse-context.d.ts +49 -0
- package/dist/parse-context.d.ts.map +1 -0
- package/dist/plugin/evaluator.d.ts +41 -2
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/free-identifiers.d.ts +23 -0
- package/dist/plugin/free-identifiers.d.ts.map +1 -0
- package/dist/plugin/index.cjs +11381 -9442
- package/dist/plugin/index.cjs.map +3 -4
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +11381 -9442
- package/dist/plugin/index.js.map +3 -4
- package/dist/plugin/reducer-resolver.d.ts +17 -1
- package/dist/plugin/reducer-resolver.d.ts.map +1 -1
- package/dist/regex/classes.d.ts +28 -0
- package/dist/regex/classes.d.ts.map +1 -1
- package/dist/regex/first-set.d.ts +14 -0
- package/dist/regex/first-set.d.ts.map +1 -1
- package/dist/run/index.cjs +136 -51
- package/dist/run/index.cjs.map +2 -3
- package/dist/run/index.js +136 -51
- package/dist/run/index.js.map +2 -3
- package/dist/spec/index.cjs +2898 -1622
- package/dist/spec/index.cjs.map +3 -4
- package/dist/spec/index.js +2898 -1622
- package/dist/spec/index.js.map +3 -4
- package/dist/spec/model.d.ts +8 -0
- package/dist/spec/model.d.ts.map +1 -1
- package/dist/table/assemble.d.ts +307 -0
- package/dist/table/assemble.d.ts.map +1 -0
- package/dist/table/cell.d.ts +42 -0
- package/dist/table/cell.d.ts.map +1 -0
- package/dist/table/child-slots.d.ts +30 -0
- package/dist/table/child-slots.d.ts.map +1 -0
- package/dist/table/compile-rule-map.d.ts +136 -0
- package/dist/table/compile-rule-map.d.ts.map +1 -0
- package/dist/table/compile.d.ts +56 -0
- package/dist/table/compile.d.ts.map +1 -0
- package/dist/table/duplication-hook.d.ts +32 -0
- package/dist/table/duplication-hook.d.ts.map +1 -0
- package/dist/table/emit-assembly.d.ts +139 -0
- package/dist/table/emit-assembly.d.ts.map +1 -0
- package/dist/table/emit.d.ts +113 -0
- package/dist/table/emit.d.ts.map +1 -0
- package/dist/table/encode-baseline.d.ts +16 -0
- package/dist/table/encode-baseline.d.ts.map +1 -0
- package/dist/table/encode.d.ts +71 -0
- package/dist/table/encode.d.ts.map +1 -0
- package/dist/table/exec-baseline.d.ts +9 -0
- package/dist/table/exec-baseline.d.ts.map +1 -0
- package/dist/table/exec.d.ts +72 -0
- package/dist/table/exec.d.ts.map +1 -0
- package/dist/table/fold.d.ts +25 -0
- package/dist/table/fold.d.ts.map +1 -0
- package/dist/table/index.cjs +11375 -0
- package/dist/table/index.cjs.map +6 -0
- package/dist/table/index.d.ts +100 -0
- package/dist/table/index.d.ts.map +1 -0
- package/dist/table/index.js +11352 -0
- package/dist/table/index.js.map +6 -0
- package/dist/table/inspect.d.ts +17 -0
- package/dist/table/inspect.d.ts.map +1 -0
- package/dist/table/ops.d.ts +446 -0
- package/dist/table/ops.d.ts.map +1 -0
- package/dist/table/program.d.ts +493 -0
- package/dist/table/program.d.ts.map +1 -0
- package/dist/table/run-support.d.ts +38 -0
- package/dist/table/run-support.d.ts.map +1 -0
- package/dist/{compiler/scannable-run.d.ts → table/scan-shapes.d.ts} +14 -13
- package/dist/table/scan-shapes.d.ts.map +1 -0
- package/dist/table/site-labels.d.ts +58 -0
- package/dist/table/site-labels.d.ts.map +1 -0
- package/dist/table/stamp.d.ts +70 -0
- package/dist/table/stamp.d.ts.map +1 -0
- package/dist/types.d.ts +80 -24
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +41 -3
- package/src/analysis/choice-cost-gate.ts +359 -0
- package/src/analysis/choice-cost-render.ts +293 -0
- package/src/analysis/choice-cost.ts +1008 -0
- package/src/analysis/commitment.ts +557 -0
- package/src/analysis/corpus.ts +130 -0
- package/src/analysis/diagnose-render.ts +625 -0
- package/src/analysis/diagnose.ts +317 -0
- package/src/analysis/diagnostics.ts +33 -0
- package/src/analysis/duplication.ts +1955 -0
- package/src/analysis/fix-render.ts +217 -0
- package/src/analysis/fix.ts +669 -0
- package/src/analysis/format-number.ts +31 -0
- package/src/analysis/gating.ts +792 -0
- package/src/analysis/grammar.ts +72 -0
- package/src/analysis/rebuild.ts +287 -0
- package/src/analysis/terminal.ts +336 -0
- package/src/cli/index.ts +413 -0
- package/src/combinators/adjacency.ts +171 -0
- package/src/combinators/attempt.ts +41 -0
- package/src/combinators/case-fold.ts +90 -0
- package/src/combinators/choice.ts +466 -0
- package/src/combinators/completions.ts +64 -0
- package/src/combinators/dispatch.ts +358 -0
- package/src/combinators/expect.ts +159 -0
- package/src/combinators/first-set.ts +322 -0
- package/src/combinators/gate.ts +50 -0
- package/src/combinators/grammar.ts +272 -0
- package/src/combinators/keywords.ts +145 -0
- package/src/combinators/literal.ts +102 -0
- package/src/combinators/map.ts +106 -0
- package/src/combinators/node.ts +345 -0
- package/src/combinators/not.ts +55 -0
- package/src/combinators/parser.ts +266 -0
- package/src/combinators/peek.ts +63 -0
- package/src/combinators/probe.ts +25 -0
- package/src/combinators/ref.ts +109 -0
- package/src/combinators/regex.ts +170 -0
- package/src/combinators/repeat.ts +561 -0
- package/src/combinators/scanTo.ts +393 -0
- package/src/combinators/sequence.ts +221 -0
- package/src/combinators/token.ts +120 -0
- package/src/combinators/trivia-boundary.ts +48 -0
- package/src/combinators/trivia-skip.ts +821 -0
- package/src/combinators/withCtx.ts +45 -0
- package/src/compiler/build-arity.ts +330 -0
- package/src/compiler/compile-linkable-table.ts +209 -0
- package/src/compiler/degradation.ts +266 -0
- package/src/compiler/fields.ts +191 -0
- package/src/compiler/grammar-coverage-ids.ts +129 -0
- package/src/compiler/ir-serialize.ts +615 -0
- package/src/compiler/linker.ts +954 -0
- package/src/compiler/token-alphabet.ts +209 -0
- package/src/compiler/token-dispatch.ts +353 -0
- package/src/compiler/token-scanner.ts +318 -0
- package/src/compiler/value-usage.ts +147 -0
- package/src/coverage.ts +383 -0
- package/src/cst/capture-buffer.ts +297 -0
- package/src/cst/host-mode.ts +69 -0
- package/src/cst/reflection-attach.ts +11 -0
- package/src/cst/reflection-symbols.ts +9 -0
- package/src/cst/reflection.ts +149 -0
- package/src/cst/relative-spans.ts +207 -0
- package/src/cst/root-trivia-scope.ts +30 -0
- package/src/cst/trivia-charscan.ts +312 -0
- package/src/cst/trivia-entries.ts +285 -0
- package/src/cst/trivia-index.ts +131 -0
- package/src/cst/trivia-kinds.ts +358 -0
- package/src/cst/types.ts +76 -0
- package/src/cst/walk.ts +101 -0
- package/src/functional/doc.ts +959 -0
- package/src/functional/run-tabled.ts +153 -0
- package/src/functional/run.ts +558 -0
- package/src/grammar-metadata.ts +2 -0
- package/src/index.ts +189 -0
- package/src/language-service/index.ts +221 -0
- package/src/line-index.ts +131 -0
- package/src/oracle/digest.ts +620 -0
- package/src/oracle/index.ts +55 -0
- package/src/parse-context.ts +91 -0
- package/src/plugin/direct-builder-static.ts +102 -0
- package/src/plugin/evaluator.ts +1505 -0
- package/src/plugin/free-identifiers.ts +338 -0
- package/src/plugin/index.ts +2281 -0
- package/src/plugin/reducer-resolver.ts +606 -0
- package/src/recovery/scan.ts +155 -0
- package/src/regex/classes.ts +183 -0
- package/src/regex/first-set.ts +316 -0
- package/src/run/index.ts +31 -0
- package/src/spec/ebnf.ts +128 -0
- package/src/spec/index.ts +54 -0
- package/src/spec/model.ts +636 -0
- package/src/spec/railroad-lib.ts +559 -0
- package/src/spec/railroad.ts +256 -0
- package/src/table/assemble.ts +2994 -0
- package/src/table/cell.ts +42 -0
- package/src/table/child-slots.ts +113 -0
- package/src/table/compile-rule-map.ts +336 -0
- package/src/table/compile.ts +244 -0
- package/src/table/duplication-hook.ts +89 -0
- package/src/table/emit-assembly.ts +1766 -0
- package/src/table/emit.ts +418 -0
- package/src/table/encode-baseline.ts +340 -0
- package/src/table/encode.ts +1569 -0
- package/src/table/exec-baseline.ts +537 -0
- package/src/table/exec.ts +1672 -0
- package/src/table/fold.ts +38 -0
- package/src/table/index.ts +108 -0
- package/src/table/inspect.ts +52 -0
- package/src/table/ops.ts +459 -0
- package/src/table/program.ts +855 -0
- package/src/table/run-support.ts +66 -0
- package/src/table/scan-shapes.ts +1578 -0
- package/src/table/site-labels.ts +228 -0
- package/src/table/stamp.ts +229 -0
- package/src/types.ts +563 -0
- package/src/version.ts +16 -0
- package/dist/compiler/codegen.d.ts +0 -334
- package/dist/compiler/codegen.d.ts.map +0 -1
- package/dist/compiler/inline-build.d.ts +0 -11
- package/dist/compiler/inline-build.d.ts.map +0 -1
- package/dist/compiler/inline-callback.d.ts +0 -21
- package/dist/compiler/inline-callback.d.ts.map +0 -1
- package/dist/compiler/line-index.d.ts +0 -2
- package/dist/compiler/line-index.d.ts.map +0 -1
- package/dist/compiler/scannable-run.d.ts.map +0 -1
- package/dist/compiler/scannable-terminal.d.ts +0 -23
- package/dist/compiler/scannable-terminal.d.ts.map +0 -1
- package/dist/compiler/trivia-fast-path.d.ts +0 -38
- package/dist/compiler/trivia-fast-path.d.ts.map +0 -1
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONLY module in parseman that talks to a terminal.
|
|
3
|
+
*
|
|
4
|
+
* Everything above it produces DATA — lines made of spans, each span carrying text and a
|
|
5
|
+
* semantic tone — and this file turns lines into bytes. That split is not tidiness; it is
|
|
6
|
+
* what makes the rendering diffable. A renderer that emits escape sequences inline has
|
|
7
|
+
* two outputs (styled and plain) that can drift apart, and the plain one is what a
|
|
8
|
+
* snapshot, a CI log and `docs/samples/` all read.
|
|
9
|
+
*
|
|
10
|
+
* WHY LINECRAFT AND NOT HAND-ROLLED ANSI
|
|
11
|
+
* --------------------------------------
|
|
12
|
+
* The first cut of this CLI carried its own `ANSI` constant table. Writing that file
|
|
13
|
+
* silently dropped the ESC bytes, so colour was dead for an entire session and nothing
|
|
14
|
+
* said so — the output still looked structured, because an escape-less `[2m` prints as
|
|
15
|
+
* `[2m`. Escape sequences are invisible failure surface, and a library that owns them
|
|
16
|
+
* removes the surface. jess already renders its diagnostics through linecraft
|
|
17
|
+
* (`packages/compiler/src/diagnostics.ts`, `packages/lint/src/index.ts`), so following
|
|
18
|
+
* the same idiom also means a jess user and a parseman user see the same shape of output
|
|
19
|
+
* rather than two parallel inventions. Pinned to `0.2.6`, exactly matching jess's pins.
|
|
20
|
+
*
|
|
21
|
+
* SPANS, NOT WHOLE-LINE STYLES
|
|
22
|
+
* ----------------------------
|
|
23
|
+
* jess's lint table styles a whole row at a time, which is right for a table of one
|
|
24
|
+
* thing per line. A grammar finding is not that: within one line the arm index, the
|
|
25
|
+
* production, its first set and the annotation all mean different things, and the
|
|
26
|
+
* annotation is the part that should be shouting. So a line here is a list of SPANS, and
|
|
27
|
+
* a styled line is a linecraft `Grid` of `Styled` cells — which also gives the columns
|
|
28
|
+
* their alignment for free.
|
|
29
|
+
*
|
|
30
|
+
* THE INVARIANT THAT MUST NOT BREAK
|
|
31
|
+
* ---------------------------------
|
|
32
|
+
* Padding happens in ONE place, before either path sees it, and the plain form is the
|
|
33
|
+
* concatenation of the padded span texts. So the two paths differ in styling and in
|
|
34
|
+
* nothing else — not in width, not in alignment, not in content. No escape byte is
|
|
35
|
+
* produced at all when colour is off, so the diffable output is byte-stable by
|
|
36
|
+
* construction rather than by stripping. `width` is passed explicitly and defaults to 80
|
|
37
|
+
* off-TTY, so a piped run cannot vary with the terminal it was piped from.
|
|
38
|
+
*/
|
|
39
|
+
import { CodeDebug, Region, Styled, type TextStyle } from 'linecraft'
|
|
40
|
+
import { groupDigits } from './format-number.ts'
|
|
41
|
+
|
|
42
|
+
export type Tone = TextStyle
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Semantic tones, named once so the two renderers cannot drift apart.
|
|
46
|
+
*
|
|
47
|
+
* The ladder matters more than the individual colours: within a finding, exactly one
|
|
48
|
+
* thing should be the brightest, and provenance / accept keys / byte costs should
|
|
49
|
+
* recede. A report where everything is emphasised is a report where nothing is.
|
|
50
|
+
*/
|
|
51
|
+
export const TONE = {
|
|
52
|
+
/** The single most important thing in its block. */
|
|
53
|
+
loud: { color: 'brightRed', bold: true } as Tone,
|
|
54
|
+
/** A heading, a count, the thing being named. */
|
|
55
|
+
strong: { bold: true } as Tone,
|
|
56
|
+
/** Supporting detail — never the finding itself. */
|
|
57
|
+
quiet: { color: 'brightBlack' } as Tone,
|
|
58
|
+
/** Recedes furthest: provenance, snapshot keys, byte costs. */
|
|
59
|
+
faint: { color: 'brightBlack', dim: true } as Tone,
|
|
60
|
+
/** This span IS the problem. */
|
|
61
|
+
bad: { color: 'red' } as Tone,
|
|
62
|
+
/** Real, but not blocking. */
|
|
63
|
+
warn: { color: 'yellow' } as Tone,
|
|
64
|
+
/** A location or an identifier the reader will search for. */
|
|
65
|
+
ident: { color: 'cyan', bold: true } as Tone,
|
|
66
|
+
/** Clean, or the thing to do. */
|
|
67
|
+
good: { color: 'green' } as Tone,
|
|
68
|
+
/** Structure: rules, box drawing, group frames. */
|
|
69
|
+
frame: { color: 'blue' } as Tone,
|
|
70
|
+
} as const
|
|
71
|
+
|
|
72
|
+
/** One styled run of text. `width` pads it, in BOTH paths, so columns line up. */
|
|
73
|
+
export type Span = {
|
|
74
|
+
text: string
|
|
75
|
+
style?: Tone
|
|
76
|
+
/** Pad to this column width. Applied once, before either path renders. */
|
|
77
|
+
width?: number
|
|
78
|
+
/** Text that ALREADY contains escapes (a linecraft component's own output). */
|
|
79
|
+
raw?: boolean
|
|
80
|
+
/**
|
|
81
|
+
* Absolute path this span points at. In the STYLED path it becomes a clickable
|
|
82
|
+
* terminal hyperlink; the plain path ignores it entirely, which is what keeps the
|
|
83
|
+
* diffable output free of escapes and of absolute paths.
|
|
84
|
+
*/
|
|
85
|
+
link?: string
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** A line is its spans. An empty array is a blank line. */
|
|
89
|
+
export type Line = Span[]
|
|
90
|
+
|
|
91
|
+
export const t = (text: string, style?: Tone, width?: number): Span =>
|
|
92
|
+
({ text, ...(style === undefined ? {} : { style }), ...(width === undefined ? {} : { width }) })
|
|
93
|
+
|
|
94
|
+
export const blank = (): Line => []
|
|
95
|
+
|
|
96
|
+
/** A horizontal rule. Segmentation is what turns eighty undifferentiated lines into a
|
|
97
|
+
* handful of blocks a reader can skip between. */
|
|
98
|
+
export const rule = (width: number, style: Tone = TONE.frame, char = '─'): Line =>
|
|
99
|
+
[{ text: char.repeat(Math.max(0, width)), style }]
|
|
100
|
+
|
|
101
|
+
export type RenderTarget = {
|
|
102
|
+
/** Emit ANSI. Default false — never sniffed here; the CLI decides. */
|
|
103
|
+
color?: boolean
|
|
104
|
+
/** Columns. Default 80, which is also what an off-TTY stream reports as nothing. */
|
|
105
|
+
width?: number
|
|
106
|
+
/**
|
|
107
|
+
* Emit OSC-8 terminal hyperlinks on file locations. Only meaningful with `color`,
|
|
108
|
+
* since both are escape sequences. Default: on when colouring. A terminal without
|
|
109
|
+
* OSC-8 support prints the URL as visible junk, so this is the escape hatch.
|
|
110
|
+
*/
|
|
111
|
+
links?: boolean
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export const DEFAULT_WIDTH = 80
|
|
115
|
+
|
|
116
|
+
const padTo = (s: string, w: number): string => (s.length >= w ? s : s + ' '.repeat(w - s.length))
|
|
117
|
+
|
|
118
|
+
/** The one place padding happens. Both render paths consume the result of this. */
|
|
119
|
+
const spanText = (s: Span): string => (s.width === undefined ? s.text : padTo(s.text, s.width))
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The padded, trailing-trimmed spans of one line — the SINGLE source both paths read.
|
|
123
|
+
*
|
|
124
|
+
* Both `plain` and `render` consume exactly this, so they cannot disagree about content,
|
|
125
|
+
* padding or alignment. Only what wraps each span differs.
|
|
126
|
+
*/
|
|
127
|
+
function cellsOf(line: Line): Span[] {
|
|
128
|
+
const cells = line.map(s => ({ ...s, text: spanText(s) })).filter(s => s.text !== '')
|
|
129
|
+
// Trailing whitespace is noise in a log and makes two otherwise-identical renderings
|
|
130
|
+
// compare unequal. Trim it off the END of the line, span by span.
|
|
131
|
+
for (let i = cells.length - 1; i >= 0; i--) {
|
|
132
|
+
const trimmed = cells[i]!.text.replace(/ +$/, '')
|
|
133
|
+
cells[i]!.text = trimmed
|
|
134
|
+
if (trimmed !== '') break
|
|
135
|
+
cells.pop()
|
|
136
|
+
}
|
|
137
|
+
return cells
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Plain text of a line list — the byte-stable form, produced without a terminal. */
|
|
141
|
+
export const plain = (lines: readonly Line[]): string =>
|
|
142
|
+
lines.map(l => cellsOf(l).map(c => c.text).join('')).join('\n')
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The escape codes linecraft emits for a tone, learned FROM linecraft and cached.
|
|
146
|
+
*
|
|
147
|
+
* The alternative — a `Grid` of `Styled` cells — loses to the component's own layout:
|
|
148
|
+
* `Styled` left-trims its content and the grid re-flows a long cell, so the styled line
|
|
149
|
+
* ended up disagreeing with the plain one in CONTENT, which is the one divergence this
|
|
150
|
+
* module exists to prevent. Asking the library what a tone looks like and wrapping
|
|
151
|
+
* UNCHANGED text in it makes parity structural rather than something a test has to
|
|
152
|
+
* catch: the text between the codes is byte-identical to the plain form because it is
|
|
153
|
+
* the same string.
|
|
154
|
+
*
|
|
155
|
+
* This is not hand-rolled ANSI. No escape sequence is written down anywhere in parseman;
|
|
156
|
+
* every byte of styling comes out of linecraft, once per distinct tone.
|
|
157
|
+
*/
|
|
158
|
+
const CODE_CACHE = new Map<string, { pre: string; post: string }>()
|
|
159
|
+
|
|
160
|
+
function codesFor(style: Tone | undefined): { pre: string; post: string } {
|
|
161
|
+
if (style === undefined) return { pre: '', post: '' }
|
|
162
|
+
const key = JSON.stringify(style)
|
|
163
|
+
const hit = CODE_CACHE.get(key)
|
|
164
|
+
if (hit !== undefined) return hit
|
|
165
|
+
const region = Region({ disableRendering: true, width: 8 })
|
|
166
|
+
region.set(Styled(style, 'X'))
|
|
167
|
+
const line = region.getLine(1)
|
|
168
|
+
region.destroy(false)
|
|
169
|
+
const at = line.indexOf('X')
|
|
170
|
+
const codes = at === -1
|
|
171
|
+
? { pre: '', post: '' }
|
|
172
|
+
: { pre: line.slice(0, at), post: line.slice(at + 1).replace(/ /g, '') }
|
|
173
|
+
CODE_CACHE.set(key, codes)
|
|
174
|
+
return codes
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* The OSC-8 hyperlink wrapper, also learned FROM linecraft rather than written down.
|
|
179
|
+
*
|
|
180
|
+
* linecraft has `fileLink()`, but its package `exports` map publishes only `.` and
|
|
181
|
+
* `./components`, so it cannot be imported. `CodeDebug` DOES emit one for its file
|
|
182
|
+
* header, so the sequence is recovered the same way tone codes are: render the component
|
|
183
|
+
* once with sentinel values and read back what it wrapped them in. If the shape ever
|
|
184
|
+
* changes — or is not found — links are simply not emitted, which degrades to plain text
|
|
185
|
+
* rather than to garbage on screen.
|
|
186
|
+
*/
|
|
187
|
+
let LINK_TEMPLATE: { pre: string; mid: string; post: string } | null | undefined
|
|
188
|
+
|
|
189
|
+
function linkTemplate(): { pre: string; mid: string; post: string } | null {
|
|
190
|
+
if (LINK_TEMPLATE !== undefined) return LINK_TEMPLATE
|
|
191
|
+
const URL_SENTINEL = '/PM_LINK_URL'
|
|
192
|
+
const TEXT_SENTINEL = 'PM_LINK_TEXT'
|
|
193
|
+
const region = Region({ disableRendering: true, width: 120 })
|
|
194
|
+
region.set(CodeDebug({
|
|
195
|
+
startLine: 1, startColumn: 1, errorLine: 'x', message: 'm',
|
|
196
|
+
filePath: TEXT_SENTINEL, fullPath: URL_SENTINEL, type: 'info',
|
|
197
|
+
}))
|
|
198
|
+
let found: { pre: string; mid: string; post: string } | null = null
|
|
199
|
+
for (let i = 1; i <= region.height && found === null; i++) {
|
|
200
|
+
const line = region.getLine(i)
|
|
201
|
+
const u = line.indexOf(URL_SENTINEL)
|
|
202
|
+
const x = line.indexOf(TEXT_SENTINEL)
|
|
203
|
+
if (u === -1 || x === -1 || x < u) continue
|
|
204
|
+
// Everything from the last ESC before the URL, so the whole introducer is captured.
|
|
205
|
+
const start = line.lastIndexOf(ESC, u)
|
|
206
|
+
if (start === -1) continue
|
|
207
|
+
const after = x + TEXT_SENTINEL.length
|
|
208
|
+
const endEsc = line.indexOf(ESC, after)
|
|
209
|
+
if (endEsc === -1) continue
|
|
210
|
+
// The closing OSC-8 runs to the terminator after the empty URL.
|
|
211
|
+
const close = line.indexOf('\\', line.indexOf(ESC, endEsc + 1))
|
|
212
|
+
if (close === -1) continue
|
|
213
|
+
found = {
|
|
214
|
+
pre: line.slice(start, u),
|
|
215
|
+
mid: line.slice(u + URL_SENTINEL.length, x),
|
|
216
|
+
post: line.slice(after, close + 1),
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
region.destroy(false)
|
|
220
|
+
LINK_TEMPLATE = found
|
|
221
|
+
return found
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Wrap `text` as a hyperlink to `path`. Zero-width: the visible text is unchanged, so
|
|
225
|
+
* every width and alignment calculation upstream stays correct. */
|
|
226
|
+
function linked(text: string, path: string): string {
|
|
227
|
+
const tpl = linkTemplate()
|
|
228
|
+
if (tpl === null) return text
|
|
229
|
+
// Link the VISIBLE text only; column padding stays outside the link so a reader is
|
|
230
|
+
// not clicking on empty space.
|
|
231
|
+
const body = text.replace(/ +$/, '')
|
|
232
|
+
const trail = text.slice(body.length)
|
|
233
|
+
return `${tpl.pre}${path}${tpl.mid}${body}${tpl.post}${trail}`
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Lines → a string. Without colour this never constructs a Region at all, so nothing on
|
|
238
|
+
* the diffable path depends on linecraft's environment detection.
|
|
239
|
+
*/
|
|
240
|
+
export function render(lines: readonly Line[], target: RenderTarget = {}): string {
|
|
241
|
+
if (target.color !== true) return plain(lines)
|
|
242
|
+
const wantLinks = target.links !== false
|
|
243
|
+
return lines.map((line) => {
|
|
244
|
+
// A pre-rendered component line (a code frame) already carries its own escapes.
|
|
245
|
+
if (line.length === 1 && line[0]!.raw === true) return line[0]!.text
|
|
246
|
+
return cellsOf(line).map((c) => {
|
|
247
|
+
const { pre, post } = codesFor(c.style)
|
|
248
|
+
const body = wantLinks && c.link !== undefined ? linked(c.text, c.link) : c.text
|
|
249
|
+
return `${pre}${body}${post}`
|
|
250
|
+
}).join('')
|
|
251
|
+
}).join('\n')
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// linecraft's own escapes, for the one component whose plain form is not otherwise
|
|
255
|
+
// reachable. Mirrors `packages/compiler/src/diagnostics.ts:246`. Built from a char code
|
|
256
|
+
// so the ESC byte cannot be lost in transit the way the old constant table's was.
|
|
257
|
+
const ESC = String.fromCharCode(27)
|
|
258
|
+
const OSC8 = new RegExp(`${ESC}\\]8;;.*?${ESC}\\\\`, 'g')
|
|
259
|
+
const SGR = new RegExp(`${ESC}\\[[0-9;]*m`, 'g')
|
|
260
|
+
|
|
261
|
+
export type CodeFrame = {
|
|
262
|
+
/** Path as the reader should see it — relative, so no absolute path can be diffed. */
|
|
263
|
+
path: string
|
|
264
|
+
/** Absolute path, used ONLY for the terminal hyperlink, which the plain form strips. */
|
|
265
|
+
fullPath: string
|
|
266
|
+
line: number
|
|
267
|
+
column: number
|
|
268
|
+
endLine?: number
|
|
269
|
+
endColumn?: number
|
|
270
|
+
lineBefore?: string | null
|
|
271
|
+
lineText: string
|
|
272
|
+
lineAfter?: string | null
|
|
273
|
+
message: string
|
|
274
|
+
/** Short label attached to the underline — the one sentence the caret is making. */
|
|
275
|
+
shortMessage?: string
|
|
276
|
+
type?: 'error' | 'warning' | 'info'
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* A source frame with the caret under the offending span — linecraft's `CodeDebug`, the
|
|
281
|
+
* same component jess renders its compiler errors with.
|
|
282
|
+
*
|
|
283
|
+
* Its lines come back as RAW spans: the component styles per token, which a span list
|
|
284
|
+
* cannot describe, and it is already the shape a reader wants. The plain form is
|
|
285
|
+
* recovered by stripping — the one place in this file that has to — and is asserted
|
|
286
|
+
* escape-free and absolute-path-free by test.
|
|
287
|
+
*/
|
|
288
|
+
export function codeFrame(frame: CodeFrame, target: RenderTarget = {}, indent = ''): Line[] {
|
|
289
|
+
const region = Region({ disableRendering: true, width: (target.width ?? DEFAULT_WIDTH) - indent.length })
|
|
290
|
+
region.set(CodeDebug({
|
|
291
|
+
startLine: frame.line,
|
|
292
|
+
startColumn: frame.column,
|
|
293
|
+
...(frame.endLine === undefined ? {} : { endLine: frame.endLine }),
|
|
294
|
+
...(frame.endColumn === undefined ? {} : { endColumn: frame.endColumn }),
|
|
295
|
+
errorLine: frame.lineText,
|
|
296
|
+
lineBefore: frame.lineBefore ?? null,
|
|
297
|
+
lineAfter: frame.lineAfter ?? null,
|
|
298
|
+
message: frame.message,
|
|
299
|
+
...(frame.shortMessage === undefined ? {} : { shortMessage: frame.shortMessage }),
|
|
300
|
+
filePath: frame.path,
|
|
301
|
+
// The component links its own header. Without colour (or with links off) it is
|
|
302
|
+
// handed the relative path, so no absolute path can reach the diffable output.
|
|
303
|
+
fullPath: target.color === true && target.links !== false ? frame.fullPath : frame.path,
|
|
304
|
+
type: frame.type ?? 'error',
|
|
305
|
+
}))
|
|
306
|
+
const out: Line[] = []
|
|
307
|
+
for (let line = 1; line <= region.height; line++) {
|
|
308
|
+
const text = region.getLine(line)
|
|
309
|
+
const body = target.color === true ? text : text.replace(OSC8, '').replace(SGR, '').replace(/\s+$/, '')
|
|
310
|
+
out.push(body === '' ? [] : [{ text: indent + body, raw: target.color === true }])
|
|
311
|
+
}
|
|
312
|
+
region.destroy(false)
|
|
313
|
+
return out
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Deterministic thousands grouping — `toLocaleString()` differs between machines.
|
|
317
|
+
* Defined in `./format-number.ts`, which depends on nothing, and re-exported here
|
|
318
|
+
* because this module's callers import the formatting primitives from here. */
|
|
319
|
+
export { groupDigits }
|
|
320
|
+
|
|
321
|
+
export const pad = padTo
|
|
322
|
+
|
|
323
|
+
/** Hard-wrap on spaces at `width`, prefixing every line with `indent`. */
|
|
324
|
+
export function wrap(text: string, width: number, indent: string): string[] {
|
|
325
|
+
const out: string[] = []
|
|
326
|
+
for (const para of text.split('\n')) {
|
|
327
|
+
let line = ''
|
|
328
|
+
for (const word of para.split(/\s+/).filter(w => w !== '')) {
|
|
329
|
+
if (line === '') line = word
|
|
330
|
+
else if (line.length + 1 + word.length <= width) line += ` ${word}`
|
|
331
|
+
else { out.push(indent + line); line = word }
|
|
332
|
+
}
|
|
333
|
+
out.push(indent + line)
|
|
334
|
+
}
|
|
335
|
+
return out
|
|
336
|
+
}
|