@promptctl/rich-js 0.24.0 → 0.24.1

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 (95) hide show
  1. package/THIRD-PARTY-NOTICES +29 -1
  2. package/dist/core/animation.d.ts +78 -0
  3. package/dist/core/animation.js +107 -0
  4. package/dist/core/cell-widths.d.ts +9 -0
  5. package/dist/core/cell-widths.js +140 -0
  6. package/dist/core/cells.d.ts +17 -11
  7. package/dist/core/cells.js +100 -25
  8. package/dist/core/console.js +5 -5
  9. package/dist/core/easing.d.ts +110 -10
  10. package/dist/core/easing.js +269 -36
  11. package/dist/core/json.js +2 -1
  12. package/dist/core/measure.js +2 -2
  13. package/dist/core/place.js +2 -1
  14. package/dist/core/pretty.js +2 -2
  15. package/dist/core/protocol.d.ts +73 -19
  16. package/dist/core/protocol.js +56 -15
  17. package/dist/core/render.js +2 -1
  18. package/dist/core/segment.d.ts +18 -1
  19. package/dist/core/segment.js +45 -44
  20. package/dist/core/strip.d.ts +3 -2
  21. package/dist/core/strip.js +11 -7
  22. package/dist/core/style.js +17 -6
  23. package/dist/core/text.js +22 -14
  24. package/dist/core/timeline.d.ts +84 -0
  25. package/dist/core/timeline.js +144 -0
  26. package/dist/core/tween.d.ts +49 -0
  27. package/dist/core/tween.js +69 -0
  28. package/dist/host/app.js +2 -2
  29. package/dist/index.d.ts +10 -7
  30. package/dist/index.js +13 -4
  31. package/dist/renderables/align.d.ts +3 -2
  32. package/dist/renderables/align.js +3 -0
  33. package/dist/renderables/columns.d.ts +3 -2
  34. package/dist/renderables/columns.js +7 -2
  35. package/dist/renderables/constrain.d.ts +3 -2
  36. package/dist/renderables/constrain.js +5 -1
  37. package/dist/renderables/effect.d.ts +43 -58
  38. package/dist/renderables/effect.js +58 -64
  39. package/dist/renderables/effects.d.ts +16 -14
  40. package/dist/renderables/effects.js +64 -45
  41. package/dist/renderables/embed.js +3 -2
  42. package/dist/renderables/flexStrip.d.ts +3 -2
  43. package/dist/renderables/flexStrip.js +6 -1
  44. package/dist/renderables/frames.d.ts +40 -19
  45. package/dist/renderables/frames.js +48 -21
  46. package/dist/renderables/group.d.ts +3 -2
  47. package/dist/renderables/group.js +5 -2
  48. package/dist/renderables/html-entities.d.ts +8 -0
  49. package/dist/renderables/html-entities.js +386 -0
  50. package/dist/renderables/layout.d.ts +3 -2
  51. package/dist/renderables/layout.js +15 -2
  52. package/dist/renderables/list-view.d.ts +3 -2
  53. package/dist/renderables/list-view.js +12 -5
  54. package/dist/renderables/live.js +2 -2
  55. package/dist/renderables/markdown-parse.d.ts +109 -0
  56. package/dist/renderables/markdown-parse.js +1421 -0
  57. package/dist/renderables/markdown-url.d.ts +19 -0
  58. package/dist/renderables/markdown-url.js +314 -0
  59. package/dist/renderables/markdown.d.ts +5 -7
  60. package/dist/renderables/markdown.js +356 -683
  61. package/dist/renderables/padding.d.ts +8 -20
  62. package/dist/renderables/padding.js +32 -50
  63. package/dist/renderables/panel.d.ts +26 -26
  64. package/dist/renderables/panel.js +67 -103
  65. package/dist/renderables/progress.d.ts +4 -2
  66. package/dist/renderables/progress.js +11 -2
  67. package/dist/renderables/progressBar.js +2 -2
  68. package/dist/renderables/python-space.d.ts +13 -0
  69. package/dist/renderables/python-space.js +24 -0
  70. package/dist/renderables/spinner.d.ts +26 -14
  71. package/dist/renderables/spinner.js +49 -37
  72. package/dist/renderables/syntax.js +2 -1
  73. package/dist/renderables/table.d.ts +19 -11
  74. package/dist/renderables/table.js +140 -156
  75. package/dist/renderables/traceback.d.ts +1 -1
  76. package/dist/renderables/traceback.js +6 -5
  77. package/dist/renderables/tree.d.ts +6 -5
  78. package/dist/renderables/tree.js +11 -7
  79. package/dist/renderables/viewport.d.ts +13 -5
  80. package/dist/renderables/viewport.js +41 -20
  81. package/dist/template-bindings/animation-funcs.js +2 -2
  82. package/dist/template-bindings/index.js +2 -1
  83. package/dist/themes/ramp.d.ts +18 -3
  84. package/dist/themes/ramp.js +23 -2
  85. package/dist/widgets/focus-manager.d.ts +21 -14
  86. package/dist/widgets/focus-manager.js +28 -29
  87. package/dist/widgets/index.d.ts +1 -1
  88. package/dist/widgets/static-item.d.ts +4 -3
  89. package/dist/widgets/static-item.js +9 -7
  90. package/dist/widgets/text-input.d.ts +28 -18
  91. package/dist/widgets/text-input.js +144 -101
  92. package/dist/widgets/widget-app.d.ts +19 -3
  93. package/dist/widgets/widget-app.js +39 -21
  94. package/dist/widgets/widget-base.js +0 -3
  95. package/package.json +2 -5
@@ -4,7 +4,7 @@
4
4
  import { Segment } from "../core/segment.js";
5
5
  import { Style } from "../core/style.js";
6
6
  import type { RichText } from "../core/text.js";
7
- import type { Renderable, Measurable, RenderOptions } from "../core/protocol.js";
7
+ import type { Composite, Renderable, Measurable, RenderOptions } from "../core/protocol.js";
8
8
  export type PaddingDimensions = number | [number, number] | [number, number, number, number];
9
9
  /**
10
10
  * Parses the shapes a caller may write padding in — one number, a vertical/
@@ -19,26 +19,14 @@ export type PaddingDimensions = number | [number, number] | [number, number, num
19
19
  * border and `Table` threw outright.
20
20
  */
21
21
  export declare function normalizePadding(padding: PaddingDimensions): [number, number, number, number];
22
- /** The three spans of a padded row, summing to exactly the requested width. */
23
- interface PaddingGeometry {
24
- readonly left: number;
25
- readonly contentWidth: number;
26
- readonly right: number;
27
- }
28
22
  /**
29
- * Divides the requested width once into the three spans a padded row is made
30
- * of, in the order they are worth spending cells on.
31
- *
32
- * The three used to be derived separately and disagree where it matters:
33
- * `render` took the canvas as `Math.max(1, maxWidth - left - right)`, which
34
- * clamps *up*, so a padding of 1 inside a 2-cell request drew 3-cell content
35
- * rows beside 2-cell blank rows — an open frame the terminal then soft-wraps.
36
- * Content takes its first cell before padding takes any, for the reason
37
- * `layoutPanel` gives: a squeeze should cost you the decoration, not the thing
38
- * being decorated.
23
+ * The width a padding leaves its content: what the request holds once both
24
+ * sides are paid for, as Rich's `Padding` divides it. The sides are paid in
25
+ * full, so a request too narrow for them leaves the content no column, and it
26
+ * is drawn as nothing — Rich's `Console.render` under one column.
39
27
  */
40
- export declare function layoutPadding(outerWidth: number, leftWanted: number, rightWanted: number): PaddingGeometry;
41
- export declare class Padding implements Renderable, Measurable {
28
+ export declare function paddedContentWidth(outerWidth: number, left: number, right: number): number;
29
+ export declare class Padding implements Renderable, Measurable, Composite {
42
30
  readonly renderable: Renderable;
43
31
  readonly top: number;
44
32
  readonly right: number;
@@ -50,10 +38,10 @@ export declare class Padding implements Renderable, Measurable {
50
38
  style?: string | Style;
51
39
  expand?: boolean;
52
40
  });
41
+ contents(): Iterable<Renderable>;
53
42
  render(rawOptions: RenderOptions): Iterable<Segment>;
54
43
  measure(rawOptions: RenderOptions): {
55
44
  minimum: number;
56
45
  maximum: number;
57
46
  };
58
47
  }
59
- export {};
@@ -5,7 +5,7 @@ import { Segment } from "../core/segment.js";
5
5
  import { cellCount } from "../core/cells.js";
6
6
  import { NULL_STYLE } from "../core/style.js";
7
7
  import { Measurement } from "../core/measure.js";
8
- import { fitHeight, getStyle, insetHeight, withBoundedWidth, withCellWidth } from "../core/protocol.js";
8
+ import { fitHeight, getStyle, insetHeight, render, withBoundedWidth, withCellWidth } from "../core/protocol.js";
9
9
  import { embed } from "./embed.js";
10
10
  /**
11
11
  * Parses the shapes a caller may write padding in — one number, a vertical/
@@ -41,32 +41,13 @@ export function normalizePadding(padding) {
41
41
  return [side(sides[0]), side(sides[1]), side(sides[2]), side(sides[3])];
42
42
  }
43
43
  /**
44
- * Divides the requested width once into the three spans a padded row is made
45
- * of, in the order they are worth spending cells on.
46
- *
47
- * The three used to be derived separately and disagree where it matters:
48
- * `render` took the canvas as `Math.max(1, maxWidth - left - right)`, which
49
- * clamps *up*, so a padding of 1 inside a 2-cell request drew 3-cell content
50
- * rows beside 2-cell blank rows — an open frame the terminal then soft-wraps.
51
- * Content takes its first cell before padding takes any, for the reason
52
- * `layoutPanel` gives: a squeeze should cost you the decoration, not the thing
53
- * being decorated.
44
+ * The width a padding leaves its content: what the request holds once both
45
+ * sides are paid for, as Rich's `Padding` divides it. The sides are paid in
46
+ * full, so a request too narrow for them leaves the content no column, and it
47
+ * is drawn as nothing — Rich's `Console.render` under one column.
54
48
  */
55
- export function layoutPadding(outerWidth, leftWanted, rightWanted) {
56
- let budget = cellCount(outerWidth);
57
- const take = (want) => {
58
- const got = Math.min(want, budget);
59
- budget -= got;
60
- return got;
61
- };
62
- const firstContentCell = take(1);
63
- const left = take(leftWanted);
64
- const right = take(rightWanted);
65
- return { left, contentWidth: firstContentCell + budget, right };
66
- }
67
- /** The full width of a padded row — the division, added back up. */
68
- function rowWidth(geometry) {
69
- return geometry.left + geometry.contentWidth + geometry.right;
49
+ export function paddedContentWidth(outerWidth, left, right) {
50
+ return cellCount(cellCount(outerWidth) - left - right);
70
51
  }
71
52
  export class Padding {
72
53
  renderable;
@@ -86,6 +67,9 @@ export class Padding {
86
67
  this.style = options?.style ?? NULL_STYLE;
87
68
  this.expand = options?.expand !== false;
88
69
  }
70
+ *contents() {
71
+ yield this.renderable;
72
+ }
89
73
  *render(rawOptions) {
90
74
  const options = withBoundedWidth(rawOptions, this);
91
75
  // `expand` decides how wide the block is, never whether its rows reach
@@ -94,10 +78,10 @@ export class Padding {
94
78
  // rows instead left the content rows ragged under a full-width blank row,
95
79
  // and a `style` ground with holes down its right-hand side.
96
80
  const width = this.expand ? options.maxWidth : Measurement.get(options, this).maximum;
97
- const geometry = layoutPadding(width, this.left, this.right);
81
+ const contentWidth = paddedContentWidth(width, this.left, this.right);
98
82
  const innerOptions = {
99
83
  ...options,
100
- maxWidth: geometry.contentWidth,
84
+ maxWidth: contentWidth,
101
85
  height: insetHeight(options.height, this.top + this.bottom),
102
86
  };
103
87
  const resolved = getStyle(options, this.style);
@@ -105,35 +89,33 @@ export class Padding {
105
89
  // The style is the ground the content is drawn on, not only the spaces
106
90
  // around it — Rich's `render_lines(..., style=style)`. Applied to the
107
91
  // padding alone, a background left a hole behind the content.
108
- const segments = [...Segment.applyStyle(this.renderable.render(innerOptions), style)];
109
- const lines = fitHeight(Segment.splitLines(segments), innerOptions.height);
110
- // Zero-length spans need no branch to suppress: the wire boundary drops
111
- // empty segments, so a padding the width could not afford emits nothing.
112
- const leftPad = new Segment(" ".repeat(geometry.left), style);
113
- const rightPad = new Segment(" ".repeat(geometry.right), style);
114
- const blankLine = new Segment(" ".repeat(rowWidth(geometry)), style);
115
- for (let i = 0; i < this.top; i++) {
116
- yield blankLine;
117
- yield Segment.line();
118
- }
92
+ const drawn = Segment.splitLines(Segment.applyStyle(render(this.renderable, innerOptions), style));
93
+ const lines = fitHeight(drawn, innerOptions.height);
94
+ const leftPad = new Segment(" ".repeat(this.left), style);
95
+ const rightPad = new Segment(" ".repeat(this.right), style);
96
+ for (let i = 0; i < this.top; i++)
97
+ yield* row([]);
98
+ // Every row is the block's width: content rows pad and crop the content to
99
+ // its canvas — a child that ignored the canvas it was handed (a Table at
100
+ // its natural width) would otherwise burst the padding — and the row to
101
+ // the block, so the rows a region asks for under sides the width cannot
102
+ // hold are cut at its edge rather than drawn past it.
119
103
  for (const line of lines) {
120
- yield leftPad;
121
- // Crops as well as pads: a child that ignored the canvas it was handed
122
- // (a Table at its natural width) would otherwise burst the padding.
123
- yield* Segment.adjustLineLength(line, geometry.contentWidth, style);
124
- yield rightPad;
125
- yield Segment.line();
104
+ yield* row([leftPad, ...Segment.adjustLineLength(line, contentWidth, style), rightPad]);
126
105
  }
127
- for (let i = 0; i < this.bottom; i++) {
128
- yield blankLine;
106
+ for (let i = 0; i < this.bottom; i++)
107
+ yield* row([]);
108
+ function* row(cells) {
109
+ yield* Segment.adjustLineLength(cells, width, style);
129
110
  yield Segment.line();
130
111
  }
131
112
  }
132
113
  measure(rawOptions) {
133
114
  const options = withCellWidth(rawOptions);
134
- const geometry = layoutPadding(options.maxWidth, this.left, this.right);
135
- const overhead = geometry.left + geometry.right;
136
- const measurement = Measurement.get({ ...options, maxWidth: geometry.contentWidth }, this.renderable);
115
+ const overhead = this.left + this.right;
116
+ // Content left no column measures as nothing, so the padding asks for the
117
+ // whole offer — Rich's `Padding.__rich_measure__`.
118
+ const measurement = Measurement.get({ ...options, maxWidth: paddedContentWidth(options.maxWidth, this.left, this.right) }, this.renderable);
137
119
  // The ceiling is what was offered, and it wins over what the child asks
138
120
  // for: a range whose floor sits above its own ceiling — this returned
139
121
  // {8, 8} for three cells of padding inside a two-cell offer — is one no
@@ -6,7 +6,7 @@ import { Style } from "../core/style.js";
6
6
  import { Box } from "../core/box.js";
7
7
  import { RichText } from "../core/text.js";
8
8
  import type { PaddingDimensions } from "./padding.js";
9
- import type { Renderable, Measurable, RenderOptions } from "../core/protocol.js";
9
+ import type { Composite, Renderable, Measurable, RenderOptions } from "../core/protocol.js";
10
10
  /**
11
11
  * A lazily-resolved border accessory. Strings render inline in the
12
12
  * border style; a `RichText` accessory lays ONLY its wrapping `style` over
@@ -15,7 +15,9 @@ import type { Renderable, Measurable, RenderOptions } from "../core/protocol.js"
15
15
  * A function form is evaluated at render time, *after* content has been
16
16
  * rendered for the current frame — use this when the accessory mirrors
17
17
  * state that the wrapped renderable populates during its own `render()`
18
- * (e.g. a widget's post-render scroll position).
18
+ * (e.g. a widget's post-render scroll position). A width that leaves the
19
+ * content no column does not render it, as Rich's does not, and the thunk
20
+ * then reads the state of the last frame that did.
19
21
  */
20
22
  export type BorderAccessory = string | RichText | (() => string | RichText | undefined);
21
23
  export interface PanelOptions {
@@ -46,7 +48,7 @@ export interface PanelOptions {
46
48
  width?: number;
47
49
  padding?: PaddingDimensions;
48
50
  }
49
- export declare class Panel implements Renderable, Measurable {
51
+ export declare class Panel implements Renderable, Measurable, Composite {
50
52
  readonly renderable: Renderable;
51
53
  readonly box: Box;
52
54
  readonly title: string | RichText | undefined;
@@ -58,39 +60,37 @@ export declare class Panel implements Renderable, Measurable {
58
60
  readonly titleStyle: string | Style | undefined;
59
61
  readonly subtitleStyle: string | Style | undefined;
60
62
  readonly width: number | undefined;
61
- readonly padding: [number, number, number, number];
62
63
  private readonly _titleLabel;
63
64
  private readonly _subtitleLabel;
64
- constructor(content: string | RichText | Renderable, options?: PanelOptions);
65
- render(rawOptions: RenderOptions): Iterable<Segment>;
66
65
  /**
67
- * The wrapped renderable's lines, laid out on a canvas `contentWidth` cells
68
- * wide. Below width 3 a panel is all frame and the canvas holds no lines —
69
- * but the render still happens, because a `bottomRightAccessory` thunk
70
- * fires at every width and reads state this render populates.
66
+ * What the frame is drawn around: the content inside its padding, on the
67
+ * panel's ground. Rich's `Panel` draws its body as this same `Padding`, so
68
+ * how a squeezed width divides between padding and content is answered once,
69
+ * by `Padding`, for both.
71
70
  */
72
- private _renderContent;
73
- /**
74
- * One row of the panel body, cut where Rich cuts it: frame column, left
75
- * padding, the content line made exactly `contentWidth` wide, right padding,
76
- * frame column — each its own segment, so each its own SGR run.
77
- *
78
- * [LAW:single-enforcer] `adjustLineLength` is the one place the content's
79
- * width is decided: content that rendered wider than its canvas (a `Table`
80
- * at its natural width, say) is cropped back to it, and a short line is
81
- * filled out to it, so the right padding always starts at the same column.
82
- */
83
- private _renderRow;
71
+ private readonly _body;
72
+ /** The padding inside the frame, top, right, bottom, left — the body's, read back. */
73
+ get padding(): readonly [number, number, number, number];
74
+ constructor(content: string | RichText | Renderable, options?: PanelOptions);
75
+ render(rawOptions: RenderOptions): Iterable<Segment>;
76
+ contents(): Iterable<Renderable>;
84
77
  /**
85
- * A row of vertical padding: one blank run across the whole span, as Rich's
86
- * `Padding` draws its blank lines, not a content row with nothing in it.
78
+ * The body's rows, held to the region as Rich's `render_lines` holds them —
79
+ * padded below, cropped from the bottom — and each exactly `spanWidth`
80
+ * cells: `Padding` makes every row its block's width, cropping content that
81
+ * ignored its canvas. A span of no cells renders nothing, as Rich's
82
+ * `Console.render` renders nothing under one column, so outside a region it
83
+ * draws no rows, padding rows included, and inside one the frame stands
84
+ * around empty rows. The render happens before either border is drawn,
85
+ * because a `bottomRightAccessory` thunk reads state the content's render
86
+ * populates.
87
87
  */
88
- private _renderPaddingRow;
88
+ private _renderBody;
89
89
  measure(rawOptions: RenderOptions): {
90
90
  minimum: number;
91
91
  maximum: number;
92
92
  };
93
- /** The content plus its own frame, both read off the division it will render against. */
93
+ /** The body plus the frame around it, both read off the division it will render against. */
94
94
  private _contentRange;
95
95
  /** The cells the title's label takes, its padding included; none without a title. */
96
96
  private _labelWidth;
@@ -8,45 +8,22 @@ import { ROUNDED } from "../core/box.js";
8
8
  import { Measurement } from "../core/measure.js";
9
9
  import { RichText } from "../core/text.js";
10
10
  import { cutLabel, drawLabel, embed, inlineLabel } from "./embed.js";
11
- import { normalizePadding } from "./padding.js";
12
- import { drawable, fitHeight, getStyle, insetHeight, withBoundedWidth, withCellWidth } from "../core/protocol.js";
13
- function layoutPanel(outerWidth, padding) {
14
- const [, padRightWanted, , padLeftWanted] = padding;
11
+ import { Padding } from "./padding.js";
12
+ import { drawable, fitHeight, getStyle, insetHeight, render, withBoundedWidth, withCellWidth } from "../core/protocol.js";
13
+ function layoutPanel(outerWidth) {
15
14
  // Panel's one division point, and the width reaching it is not always one
16
15
  // this file produced: `_getPanelWidth` derives the fit-mode width from
17
16
  // `Measurement.get` on an arbitrary `Measurable`, so a renderable reporting a
18
17
  // negative maximum arrives here unparsed and reaches `repeat` as a negative
19
18
  // count.
20
- let budget = cellCount(outerWidth);
21
- const take = (want) => {
22
- const got = Math.min(want, budget);
23
- budget -= got;
24
- return got;
25
- };
26
- const left = take(1);
27
- const right = take(1);
28
- const firstContentCell = take(1);
29
- const padLeft = take(padLeftWanted);
30
- const padRight = take(padRightWanted);
31
- const contentWidth = firstContentCell + budget;
32
- return {
33
- left,
34
- right,
35
- padLeft,
36
- contentWidth,
37
- padRight,
38
- spanWidth: padLeft + contentWidth + padRight,
39
- };
19
+ const width = cellCount(outerWidth);
20
+ const left = Math.min(1, width);
21
+ const right = Math.min(1, width - left);
22
+ return { left, right, spanWidth: width - left - right };
40
23
  }
41
- /**
42
- * Every cell of a panel that is not content canvas. Read off the geometry
43
- * rather than recomputed as `2 + padLeft + padRight`, because the two differ
44
- * exactly where this panel is squeezed: a width that cannot afford its right
45
- * frame column or its padding does not spend cells on them, and a measurement
46
- * that assumed it did would report a minimum larger than its own maximum.
47
- */
48
- function frameOverhead(geometry) {
49
- return geometry.left + geometry.right + geometry.padLeft + geometry.padRight;
24
+ /** The cells a panel's frame takes from the width it is given. */
25
+ function frameWidth(geometry) {
26
+ return geometry.left + geometry.right;
50
27
  }
51
28
  /**
52
29
  * How a title or subtitle string is read: always as markup and never
@@ -114,9 +91,20 @@ export class Panel {
114
91
  titleStyle;
115
92
  subtitleStyle;
116
93
  width;
117
- padding;
118
94
  _titleLabel;
119
95
  _subtitleLabel;
96
+ /**
97
+ * What the frame is drawn around: the content inside its padding, on the
98
+ * panel's ground. Rich's `Panel` draws its body as this same `Padding`, so
99
+ * how a squeezed width divides between padding and content is answered once,
100
+ * by `Padding`, for both.
101
+ */
102
+ _body;
103
+ /** The padding inside the frame, top, right, bottom, left — the body's, read back. */
104
+ get padding() {
105
+ const body = this._body;
106
+ return [body.top, body.right, body.bottom, body.left];
107
+ }
120
108
  constructor(content, options) {
121
109
  this.renderable = embed(content);
122
110
  this.box = options?.box ?? ROUNDED;
@@ -131,7 +119,7 @@ export class Panel {
131
119
  this.titleStyle = options?.titleStyle;
132
120
  this.subtitleStyle = options?.subtitleStyle;
133
121
  this.width = options?.width;
134
- this.padding = normalizePadding(options?.padding ?? [0, 1, 0, 1]);
122
+ this._body = new Padding(this.renderable, options?.padding ?? [0, 1, 0, 1], { style: this.style });
135
123
  }
136
124
  *render(rawOptions) {
137
125
  const options = withBoundedWidth(rawOptions, this);
@@ -140,72 +128,50 @@ export class Panel {
140
128
  // on, as Rich's is: the frame's style is the border style laid over it,
141
129
  // and the content is rendered on top of it. Applied to the padding alone,
142
130
  // "white on dark_blue" drew a blue ring with a hole where the text was.
143
- const style = getStyle(options, this.style);
144
- const borderStyle = style.add(getStyle(options, this.borderStyle));
131
+ const borderStyle = getStyle(options, this.style).add(getStyle(options, this.borderStyle));
145
132
  const border = borderStyle.isNull ? undefined : borderStyle;
146
- const contentStyle = style.isNull ? undefined : style;
147
- const geometry = layoutPanel(this._getPanelWidth(options), this.padding);
148
- const [padTop, , padBottom] = this.padding;
149
- const contentLines = this._renderContent(options, geometry.contentWidth, contentStyle);
133
+ const geometry = layoutPanel(this._getPanelWidth(options));
134
+ const bodyLines = this._renderBody(options, geometry.spanWidth);
150
135
  yield* this._renderTopBorder(options, box, geometry, border);
151
- for (let i = 0; i < padTop; i++) {
152
- yield* this._renderPaddingRow(box, geometry, border, contentStyle);
153
- }
154
- for (const line of contentLines) {
155
- yield* this._renderRow(box, geometry, line, border, contentStyle);
156
- }
157
- for (let i = 0; i < padBottom; i++) {
158
- yield* this._renderPaddingRow(box, geometry, border, contentStyle);
136
+ const frame = box.getContentChars("row");
137
+ for (const line of bodyLines) {
138
+ yield new Segment(frame.left.repeat(geometry.left), border);
139
+ yield* line;
140
+ yield new Segment(frame.right.repeat(geometry.right), border);
141
+ yield Segment.line();
159
142
  }
160
143
  yield* this._renderBottomBorder(options, box, geometry, border);
161
144
  }
162
- /**
163
- * The wrapped renderable's lines, laid out on a canvas `contentWidth` cells
164
- * wide. Below width 3 a panel is all frame and the canvas holds no lines —
165
- * but the render still happens, because a `bottomRightAccessory` thunk
166
- * fires at every width and reads state this render populates.
167
- */
168
- _renderContent(options, contentWidth, style) {
169
- // Two border rows and the vertical padding are the panel's own. Handed on
170
- // as a region, the rest is the panel's to shape, which is what stretches
171
- // its frame down a pane and keeps its bottom border inside one.
172
- const [padTop, , padBottom] = this.padding;
173
- const height = insetHeight(options.height, 2 + padTop + padBottom);
174
- // No highlighter: what the panel holds is drawn plain, as Rich's `Panel`
175
- // hands its body `highlight=False`.
176
- const innerOptions = { ...options, highlight: false, maxWidth: contentWidth, height };
177
- const lines = fitHeight(Segment.splitLines([...Segment.applyStyle(this.renderable.render(innerOptions), style)]), height);
178
- return contentWidth === 0 ? [] : lines;
145
+ // The body is the one thing a panel draws through `render`: its title and
146
+ // subtitle are text it builds.
147
+ *contents() {
148
+ yield this._body;
179
149
  }
180
150
  /**
181
- * One row of the panel body, cut where Rich cuts it: frame column, left
182
- * padding, the content line made exactly `contentWidth` wide, right padding,
183
- * frame column — each its own segment, so each its own SGR run.
184
- *
185
- * [LAW:single-enforcer] `adjustLineLength` is the one place the content's
186
- * width is decided: content that rendered wider than its canvas (a `Table`
187
- * at its natural width, say) is cropped back to it, and a short line is
188
- * filled out to it, so the right padding always starts at the same column.
151
+ * The body's rows, held to the region as Rich's `render_lines` holds them —
152
+ * padded below, cropped from the bottom — and each exactly `spanWidth`
153
+ * cells: `Padding` makes every row its block's width, cropping content that
154
+ * ignored its canvas. A span of no cells renders nothing, as Rich's
155
+ * `Console.render` renders nothing under one column, so outside a region it
156
+ * draws no rows, padding rows included, and inside one the frame stands
157
+ * around empty rows. The render happens before either border is drawn,
158
+ * because a `bottomRightAccessory` thunk reads state the content's render
159
+ * populates.
189
160
  */
190
- *_renderRow(box, geometry, line, border, contentStyle) {
191
- const frame = box.getContentChars("row");
192
- yield new Segment(frame.left.repeat(geometry.left), border);
193
- yield new Segment(" ".repeat(geometry.padLeft), contentStyle);
194
- yield* Segment.adjustLineLength(line, geometry.contentWidth, contentStyle);
195
- yield new Segment(" ".repeat(geometry.padRight), contentStyle);
196
- yield new Segment(frame.right.repeat(geometry.right), border);
197
- yield Segment.line();
198
- }
199
- /**
200
- * A row of vertical padding: one blank run across the whole span, as Rich's
201
- * `Padding` draws its blank lines, not a content row with nothing in it.
202
- */
203
- *_renderPaddingRow(box, geometry, border, contentStyle) {
204
- const frame = box.getContentChars("row");
205
- yield new Segment(frame.left.repeat(geometry.left), border);
206
- yield new Segment(" ".repeat(geometry.spanWidth), contentStyle);
207
- yield new Segment(frame.right.repeat(geometry.right), border);
208
- yield Segment.line();
161
+ _renderBody(options, spanWidth) {
162
+ // Two border rows are the panel's own, and the vertical padding is the
163
+ // body's. Handed on as a region, the rest is the panel's to shape, which
164
+ // is what stretches its frame down a pane and keeps its bottom border
165
+ // inside one. No highlighter: what the panel holds is drawn plain, as
166
+ // Rich's `Panel` hands its body `highlight=False`.
167
+ const bodyOptions = {
168
+ ...options,
169
+ highlight: false,
170
+ maxWidth: spanWidth,
171
+ height: insetHeight(options.height, 2),
172
+ };
173
+ const lines = Segment.splitLines(render(this._body, bodyOptions));
174
+ return fitHeight(lines, bodyOptions.height);
209
175
  }
210
176
  measure(rawOptions) {
211
177
  const options = withCellWidth(rawOptions);
@@ -220,18 +186,17 @@ export class Panel {
220
186
  return { minimum: width, maximum: width };
221
187
  }
222
188
  const content = this._contentRange(options);
223
- const overhead = frameOverhead(layoutPanel(options.maxWidth, this.padding));
189
+ const overhead = frameWidth(layoutPanel(options.maxWidth)) + this._body.left + this._body.right;
224
190
  const maximum = Math.min(options.maxWidth, Math.max(content.maximum, this._labelWidth(options) + overhead));
225
191
  return { minimum: Math.min(content.minimum, maximum), maximum };
226
192
  }
227
- /** The content plus its own frame, both read off the division it will render against. */
193
+ /** The body plus the frame around it, both read off the division it will render against. */
228
194
  _contentRange(options) {
229
- const geometry = layoutPanel(options.maxWidth, this.padding);
230
- const overhead = frameOverhead(geometry);
231
- const measurement = Measurement.get({ ...options, maxWidth: geometry.contentWidth }, this.renderable);
195
+ const geometry = layoutPanel(options.maxWidth);
196
+ const measurement = Measurement.get({ ...options, maxWidth: geometry.spanWidth }, this._body);
232
197
  return {
233
- minimum: measurement.minimum + overhead,
234
- maximum: Math.min(options.maxWidth, measurement.maximum + overhead),
198
+ minimum: measurement.minimum + frameWidth(geometry),
199
+ maximum: measurement.maximum + frameWidth(geometry),
235
200
  };
236
201
  }
237
202
  /** The cells the title's label takes, its padding included; none without a title. */
@@ -266,9 +231,8 @@ export class Panel {
266
231
  yield Segment.line();
267
232
  }
268
233
  *_renderBottomBorder(options, box, geometry, border) {
269
- // Resolve the right accessory *now*. Function form evaluates after
270
- // content has been rendered (Panel.render collects content segments
271
- // before yielding any borders), so the thunk sees fresh widget state.
234
+ // Resolve the right accessory *now*, after the body has rendered —
235
+ // `BorderAccessory` owns what a thunk sees.
272
236
  const accessory = this._resolveAccessory(this.bottomRightAccessory);
273
237
  const accessoryDisplay = accessory === undefined
274
238
  ? ""
@@ -8,7 +8,7 @@ import type { Style } from "../core/style.js";
8
8
  import { ProgressBar } from "./progressBar.js";
9
9
  import { type Clock } from "../core/clock.js";
10
10
  import { type ColumnOptions } from "./table.js";
11
- import type { Renderable, RenderOptions } from "../core/protocol.js";
11
+ import type { Composite, Renderable, RenderOptions } from "../core/protocol.js";
12
12
  export interface TaskOptions {
13
13
  total?: number;
14
14
  start?: boolean;
@@ -156,7 +156,7 @@ export interface ProgressOptions {
156
156
  */
157
157
  clock?: Clock;
158
158
  }
159
- export declare class Progress implements Renderable {
159
+ export declare class Progress implements Renderable, Composite {
160
160
  private _columns;
161
161
  private _tasks;
162
162
  private _nextId;
@@ -176,6 +176,8 @@ export declare class Progress implements Renderable {
176
176
  stop(): void;
177
177
  refresh(): void;
178
178
  render(options: RenderOptions): Iterable<Segment>;
179
+ contents(): Iterable<Renderable>;
180
+ private _table;
179
181
  }
180
182
  export declare function track<T>(iterable: Iterable<T>, options?: {
181
183
  description?: string;
@@ -9,6 +9,7 @@ import { Spinner } from "./spinner.js";
9
9
  import { Live } from "./live.js";
10
10
  import { systemClock } from "../core/clock.js";
11
11
  import { Table } from "./table.js";
12
+ import { render } from "../core/protocol.js";
12
13
  /**
13
14
  * Rich's defaults for `speed_estimate_period` and its sample deque's
14
15
  * `maxlen`: an update forgets samples older than the period, and the newest
@@ -328,7 +329,15 @@ export class Progress {
328
329
  this._live.refresh();
329
330
  }
330
331
  *render(options) {
331
- // Rich's `make_tasks_table`: each column laid out as it says.
332
+ yield* render(this._table(), options);
333
+ }
334
+ // What each column renders for each task is what the table holds, and so is
335
+ // all a progress display holds.
336
+ *contents() {
337
+ yield this._table();
338
+ }
339
+ // Rich's `make_tasks_table`: each column laid out as it says.
340
+ _table() {
332
341
  const table = Table.grid({ expand: this.expand });
333
342
  for (const col of this._columns) {
334
343
  table.addColumn(undefined, col.tableColumn);
@@ -349,7 +358,7 @@ export class Progress {
349
358
  };
350
359
  table.addRow(...this._columns.map((col) => col.render(task)));
351
360
  }
352
- yield* table.render(options);
361
+ return table;
353
362
  }
354
363
  }
355
364
  // --- track ---
@@ -5,7 +5,7 @@ import { cellCount } from "../core/cells.js";
5
5
  import { ColorDepth, ColorRgba, ColorSpec, resolveTerminal } from "../core/color.js";
6
6
  import { Segment } from "../core/segment.js";
7
7
  import { Style } from "../core/style.js";
8
- import { drawable, getStyle } from "../core/protocol.js";
8
+ import { drawable, getStyle, render } from "../core/protocol.js";
9
9
  import { Effected } from "./effect.js";
10
10
  import { EFFECT_CURVES, atShares, shimmer } from "./effects.js";
11
11
  const BAR = "━";
@@ -59,7 +59,7 @@ export class ProgressBar {
59
59
  // [LAW:no-ambient-temporal-coupling] The moment is the caller's; the bar
60
60
  // only says what it looks like then. An empty track, lit by the shimmer.
61
61
  const track = { render: (o) => this.track(o, width, 0, back) };
62
- yield* new Effected(track, this.pulseEffect(options, back, depth, theme), { t: this.pulse.t, key: "progress-bar", theme }).render(options);
62
+ yield* render(new Effected(track, this.pulseEffect(options, back, depth, theme), { t: this.pulse.t, key: "progress-bar", theme }), options);
63
63
  }
64
64
  /**
65
65
  * The shimmer over this bar's back colour as `Effected` sees it drawn at
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Whitespace as Python reads it — `str.isspace`, `str.strip`, and `\s` in a
3
+ * `str` pattern of `re`, which are one set. markdown-it-py, the markdown-it
4
+ * Rich runs, reads whitespace through all three, and that set is not
5
+ * JavaScript's: `\s` and `trim` take U+FEFF, which Python does not, and leave
6
+ * U+001C–U+001F and U+0085, which Python takes.
7
+ */
8
+ /** The set as a regex character class, every member of it a single UTF-16 unit, for composing into a pattern where markdown-it-py's has `\s`. */
9
+ export declare const PY_WHITESPACE: string;
10
+ /** Python's `str.strip`, a scan in from each end, so a run of interior whitespace costs one pass, as it does there. */
11
+ export declare function pyStrip(text: string): string;
12
+ /** Python's `str.rstrip`, a scan in from the end — not a `+$` pattern, which retries from every cell of an interior run. */
13
+ export declare function pyRstrip(text: string): string;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Whitespace as Python reads it — `str.isspace`, `str.strip`, and `\s` in a
3
+ * `str` pattern of `re`, which are one set. markdown-it-py, the markdown-it
4
+ * Rich runs, reads whitespace through all three, and that set is not
5
+ * JavaScript's: `\s` and `trim` take U+FEFF, which Python does not, and leave
6
+ * U+001C–U+001F and U+0085, which Python takes.
7
+ */
8
+ /** The set as a regex character class, every member of it a single UTF-16 unit, for composing into a pattern where markdown-it-py's has `\s`. */
9
+ export const PY_WHITESPACE = String.raw `[\t-\r\x1c-\x20\x85\xa0  - 

   ]`;
10
+ const PY_SPACE = new RegExp(`^${PY_WHITESPACE}$`);
11
+ /** Python's `str.strip`, a scan in from each end, so a run of interior whitespace costs one pass, as it does there. */
12
+ export function pyStrip(text) {
13
+ let start = 0;
14
+ while (start < text.length && PY_SPACE.test(text[start]))
15
+ start++;
16
+ return pyRstrip(text.slice(start));
17
+ }
18
+ /** Python's `str.rstrip`, a scan in from the end — not a `+$` pattern, which retries from every cell of an interior run. */
19
+ export function pyRstrip(text) {
20
+ let end = text.length;
21
+ while (end > 0 && PY_SPACE.test(text[end - 1]))
22
+ end--;
23
+ return text.slice(0, end);
24
+ }