Skip to content

Commit 35b1a09

Browse files
iclantonclaude
andauthored
[terminal] Improve TerminalTable and PrintUtilities: color options, row separators, printToTerminal() (#5787)
* [terminal] Fix TerminalTable rendering: add row separators and gate horizontal lines on fill char - Render horizontal separators between data rows (same chars as the header/body separator), matching the visual convention of bordered tables - Fix renderSeparator to return undefined when fillChar is empty, preventing malformed lines like ├┼┤ when only corner/junction chars are set - Add test cases for >2 columns, single row, header-only, and empty fillChar Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * [terminal] Add borderColor and headingColor to TerminalTable; normalize test snapshots - Add borderColor option: a styling function applied to all border/gridline characters (horizontal separator lines colored as a whole, vertical chars pre-colorized once before rendering) - Add headingColor option: a styling function applied to header cell text only - Refactor getLines() to pre-compute separator lines and styled vertical chars so rendering functions contain no color logic - Add expectSnapshot() helper in tests using AnsiEscape.formatForTests so snapshot files show readable tokens like [cyan]/[bold] instead of raw escapes - Add tests for borderColor, headingColor, and their combination Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * [terminal] Add borderColor and messageColor to PrintUtilities.printMessageInBox - Add IPrintMessageInBoxOptions with borderColor and messageColor styling functions, matching the pattern used by TerminalTable - Add tests for both options and their combination, including the banner (wide-content) fallback layout - Fix validateOutput to measure visual width via AnsiEscape.removeCodes so ANSI escape codes don't inflate the length check Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * [terminal] API cleanup: export IPrintMessageInBoxOptions, fix TSDoc overload references - Export IPrintMessageInBoxOptions from the package entry point - Add JSDoc for the boxWidth option - Fix @example snippets to use the new 3-arg signature - Use {@Label WITH_OPTIONS} and (printMessageInBox:1) selector to unambiguously reference the primary overload - Update tests to use the options-based API Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * [terminal] Add TerminalTable.printToTerminal() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fixup! [terminal] Add TerminalTable.printToTerminal() * Rush change. * Make the next release of Rush a patch bump. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 947efd5 commit 35b1a09

16 files changed

Lines changed: 548 additions & 147 deletions
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"changes": [
3+
{
4+
"comment": "Fix an issue where `rush list --detailed` did not print horizontal table separators.",
5+
"type": "none",
6+
"packageName": "@microsoft/rush"
7+
}
8+
],
9+
"packageName": "@microsoft/rush",
10+
"email": "iclanton@users.noreply.github.com"
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"changes": [
3+
{
4+
"comment": "Fix `TerminalTable` rendering spurious border lines when separator characters are set to empty strings",
5+
"type": "patch",
6+
"packageName": "@rushstack/terminal"
7+
}
8+
],
9+
"packageName": "@rushstack/terminal",
10+
"email": "iclanton@users.noreply.github.com"
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"changes": [
3+
{
4+
"comment": "Add borderColor and headingColor styling options to `TerminalTable`",
5+
"type": "minor",
6+
"packageName": "@rushstack/terminal"
7+
}
8+
],
9+
"packageName": "@rushstack/terminal",
10+
"email": "iclanton@users.noreply.github.com"
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"changes": [
3+
{
4+
"comment": "Add borderColor and messageColor styling options to `PrintUtilities.printMessageInBox`",
5+
"type": "minor",
6+
"packageName": "@rushstack/terminal"
7+
}
8+
],
9+
"packageName": "@rushstack/terminal",
10+
"email": "iclanton@users.noreply.github.com"
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"changes": [
3+
{
4+
"comment": "Add row separators between data rows in `TerminalTable`.",
5+
"type": "patch",
6+
"packageName": "@rushstack/terminal"
7+
}
8+
],
9+
"packageName": "@rushstack/terminal",
10+
"email": "iclanton@users.noreply.github.com"
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"changes": [
3+
{
4+
"comment": "Add `TerminalTable.printToTerminal()` function.",
5+
"type": "minor",
6+
"packageName": "@rushstack/terminal"
7+
}
8+
],
9+
"packageName": "@rushstack/terminal",
10+
"email": "iclanton@users.noreply.github.com"
11+
}

common/config/rush/version-policies.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@
103103
"policyName": "rush",
104104
"definitionName": "lockStepVersion",
105105
"version": "5.175.0",
106-
"nextBump": "minor",
106+
"nextBump": "patch",
107107
"mainProject": "@microsoft/rush"
108108
}
109109
]

common/reviews/api/terminal.api.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,13 @@ export interface IPrefixProxyTerminalProviderOptionsBase {
167167
terminalProvider: ITerminalProvider;
168168
}
169169

170+
// @public
171+
export interface IPrintMessageInBoxOptions {
172+
borderColor?: (text: string) => string;
173+
boxWidth?: number;
174+
messageColor?: (text: string) => string;
175+
}
176+
170177
// @beta
171178
export interface IProblemCollector {
172179
get problems(): ReadonlySet<IProblem>;
@@ -268,9 +275,11 @@ export interface ITerminalTableChars {
268275
// @public
269276
export interface ITerminalTableOptions {
270277
borderCharacters?: Partial<ITerminalTableChars>;
278+
borderColor?: (text: string) => string;
271279
borderless?: boolean;
272280
colWidths?: number[];
273281
head?: string[];
282+
headingColor?: (text: string) => string;
274283
}
275284

276285
// @public
@@ -347,6 +356,10 @@ export class PrefixProxyTerminalProvider implements ITerminalProvider {
347356
export class PrintUtilities {
348357
static getConsoleWidth(): number | undefined;
349358
// Warning: (ae-incompatible-release-tags) The symbol "printMessageInBox" is marked as @public, but its signature references "ITerminal" which is marked as @beta
359+
static printMessageInBox(message: string, terminal: ITerminal, options?: IPrintMessageInBoxOptions): void;
360+
// Warning: (ae-incompatible-release-tags) The symbol "printMessageInBox" is marked as @public, but its signature references "ITerminal" which is marked as @beta
361+
//
362+
// @deprecated (undocumented)
350363
static printMessageInBox(message: string, terminal: ITerminal, boxWidth?: number): void;
351364
static wrapWords(text: string, maxLineLength?: number, indent?: number): string;
352365
static wrapWords(text: string, maxLineLength?: number, linePrefix?: string): string;
@@ -491,6 +504,8 @@ export class TerminalTable {
491504
constructor(options?: ITerminalTableOptions);
492505
// (undocumented)
493506
getLines(): string[];
507+
// Warning: (ae-incompatible-release-tags) The symbol "printToTerminal" is marked as @public, but its signature references "ITerminal" which is marked as @beta
508+
printToTerminal(terminal: ITerminal): void;
494509
push(...rows: string[][]): void;
495510
toString(): string;
496511
}

libraries/rush-lib/src/cli/actions/ListAction.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,6 @@ export class ListAction extends BaseRushAction {
277277
table.push(packageRow);
278278
}
279279

280-
// eslint-disable-next-line no-console
281-
console.log(table.toString());
280+
table.printToTerminal(this.terminal);
282281
}
283282
}

libraries/terminal/src/PrintUtilities.ts

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,40 @@ import { Text } from '@rushstack/node-core-library';
55

66
import type { ITerminal } from './ITerminal';
77

8+
/**
9+
* Options for {@link PrintUtilities.(printMessageInBox:1)}.
10+
*
11+
* @public
12+
*/
13+
export interface IPrintMessageInBoxOptions {
14+
/**
15+
* The width of the box in characters. Defaults to half of the console width.
16+
*/
17+
boxWidth?: number;
18+
19+
/**
20+
* A function to apply styling to the box border characters.
21+
*
22+
* @example
23+
* ```typescript
24+
* import { Colorize } from '@rushstack/terminal';
25+
* PrintUtilities.printMessageInBox('Hello!', terminal, { borderColor: Colorize.cyan })
26+
* ```
27+
*/
28+
borderColor?: (text: string) => string;
29+
30+
/**
31+
* A function to apply styling to the message text inside the box.
32+
*
33+
* @example
34+
* ```typescript
35+
* import { Colorize } from '@rushstack/terminal';
36+
* PrintUtilities.printMessageInBox('Hello!', terminal, { messageColor: Colorize.bold })
37+
* ```
38+
*/
39+
messageColor?: (text: string) => string;
40+
}
41+
842
/**
943
* A sensible fallback column width for consoles.
1044
*
@@ -186,14 +220,45 @@ export class PrintUtilities {
186220
*
187221
* @param message - The message to display.
188222
* @param terminal - The terminal to write the message to.
189-
* @param boxWidth - The width of the box, defaults to half of the console width.
223+
* @param options - Controls the box width and optional styling for the border and message text.
224+
* {@label WITH_OPTIONS}
225+
*/
226+
public static printMessageInBox(
227+
message: string,
228+
terminal: ITerminal,
229+
options?: IPrintMessageInBoxOptions
230+
): void;
231+
/**
232+
* @deprecated Use the {@link PrintUtilities.(printMessageInBox:1)} overload instead.
233+
* Pass `boxWidth` via the {@link IPrintMessageInBoxOptions.boxWidth} property.
190234
*/
191-
public static printMessageInBox(message: string, terminal: ITerminal, boxWidth?: number): void {
192-
if (!boxWidth) {
235+
public static printMessageInBox(message: string, terminal: ITerminal, boxWidth?: number): void;
236+
public static printMessageInBox(
237+
message: string,
238+
terminal: ITerminal,
239+
optionsOrBoxWidth?: IPrintMessageInBoxOptions | number
240+
): void {
241+
let options: IPrintMessageInBoxOptions;
242+
if (typeof optionsOrBoxWidth === 'number') {
243+
options = {
244+
boxWidth: optionsOrBoxWidth
245+
};
246+
} else {
247+
options = optionsOrBoxWidth ?? {};
248+
}
249+
250+
const { borderColor, messageColor, boxWidth: optionsBoxWidth } = options ?? {};
251+
let boxWidth: number;
252+
if (!optionsBoxWidth) {
193253
const consoleWidth: number = PrintUtilities.getConsoleWidth() || DEFAULT_CONSOLE_WIDTH;
194254
boxWidth = Math.floor(consoleWidth / 2);
255+
} else {
256+
boxWidth = optionsBoxWidth;
195257
}
196258

259+
const styleBorder = (s: string): string => borderColor?.(s) ?? s;
260+
const styleMessage = (s: string): string => messageColor?.(s) ?? s;
261+
197262
const maxLineLength: number = boxWidth - 10;
198263
const wrappedMessageLines: string[] = PrintUtilities.wrapWordsToLines(message, maxLineLength);
199264
let longestLineLength: number = 0;
@@ -209,25 +274,29 @@ export class PrintUtilities {
209274
// ═════════════
210275
// Message
211276
// ═════════════
212-
const headerAndFooter: string = ` ${'═'.repeat(boxWidth)}`;
277+
const headerAndFooter: string = ` ${styleBorder('═'.repeat(boxWidth))}`;
213278
terminal.writeLine(headerAndFooter);
214279
for (const line of wrappedMessageLines) {
215-
terminal.writeLine(` ${line}`);
280+
terminal.writeLine(` ${styleMessage(line)}`);
216281
}
217282

218283
terminal.writeLine(headerAndFooter);
219284
} else {
285+
const verticalBorder: string = styleBorder('║');
286+
220287
// ╔═══════════╗
221288
// ║ Message ║
222289
// ╚═══════════╝
223-
terminal.writeLine(` ╔${'═'.repeat(boxWidth - 2)}╗`);
290+
terminal.writeLine(` ${styleBorder(`${'═'.repeat(boxWidth - 2)}`)}`);
224291
for (const trimmedLine of trimmedLines) {
225292
const padding: number = boxWidth - trimmedLine.length - 2;
226293
const leftPadding: number = Math.floor(padding / 2);
227294
const rightPadding: number = padding - leftPadding;
228-
terminal.writeLine(` ║${' '.repeat(leftPadding)}${trimmedLine}${' '.repeat(rightPadding)}║`);
295+
terminal.writeLine(
296+
` ${verticalBorder}${' '.repeat(leftPadding)}${styleMessage(trimmedLine)}${' '.repeat(rightPadding)}${verticalBorder}`
297+
);
229298
}
230-
terminal.writeLine(` ╚${'═'.repeat(boxWidth - 2)}╝`);
299+
terminal.writeLine(` ${styleBorder(`${'═'.repeat(boxWidth - 2)}`)}`);
231300
}
232301
}
233302
}

0 commit comments

Comments
 (0)