From 124da70ff30dce2b028ccb7d6f3fbe7b4443f91a Mon Sep 17 00:00:00 2001 From: Maxwell Brown Date: Wed, 29 Nov 2023 15:47:17 -0500 Subject: [PATCH] Refactor and enhance the external api of printer-ansi (#444) --- .changeset/nice-coats-add.md | 5 + .eslintrc.cjs | 2 +- flake.lock | 12 +- packages/printer-ansi/examples/main.ts | 42 +- packages/printer-ansi/src/Ansi.ts | 516 +++++++++++ packages/printer-ansi/src/AnsiDoc.ts | 851 +++++++++++++++++- packages/printer-ansi/src/AnsiRender.ts | 68 -- packages/printer-ansi/src/AnsiStyle.ts | 108 --- packages/printer-ansi/src/RenderLayer.ts | 46 - packages/printer-ansi/src/SGR.ts | 113 --- packages/printer-ansi/src/index.ts | 19 +- packages/printer-ansi/src/internal/ansi.ts | 370 ++++++++ packages/printer-ansi/src/internal/ansiDoc.ts | 81 ++ .../printer-ansi/src/internal/ansiRender.ts | 81 +- .../printer-ansi/src/internal/ansiStyle.ts | 100 -- .../printer-ansi/src/internal/renderLayer.ts | 11 - packages/printer-ansi/src/internal/sgr.ts | 163 +++- packages/printer-ansi/test/terminal.test.ts | 344 +++++-- packages/printer/src/Doc.ts | 12 +- packages/printer/src/DocStream.ts | 6 +- packages/printer/src/DocTree.ts | 3 +- packages/printer/src/PageWidth.ts | 6 +- packages/printer/src/internal/doc.ts | 85 +- packages/printer/src/internal/docStream.ts | 33 +- packages/printer/src/internal/docTree.ts | 41 +- packages/printer/src/internal/flatten.ts | 23 +- packages/printer/src/internal/layout.ts | 18 +- packages/printer/src/internal/optimize.ts | 9 +- packages/printer/src/internal/pageWidth.ts | 15 +- packages/printer/src/internal/render.ts | 6 +- packages/printer/test/doc.test.ts | 17 +- packages/printer/test/docStream.test.ts | 4 +- packages/printer/test/docTree.test.ts | 25 +- 33 files changed, 2481 insertions(+), 754 deletions(-) create mode 100644 .changeset/nice-coats-add.md create mode 100644 packages/printer-ansi/src/Ansi.ts delete mode 100644 packages/printer-ansi/src/AnsiRender.ts delete mode 100644 packages/printer-ansi/src/AnsiStyle.ts delete mode 100644 packages/printer-ansi/src/RenderLayer.ts delete mode 100644 packages/printer-ansi/src/SGR.ts create mode 100644 packages/printer-ansi/src/internal/ansi.ts create mode 100644 packages/printer-ansi/src/internal/ansiDoc.ts delete mode 100644 packages/printer-ansi/src/internal/ansiStyle.ts delete mode 100644 packages/printer-ansi/src/internal/renderLayer.ts diff --git a/.changeset/nice-coats-add.md b/.changeset/nice-coats-add.md new file mode 100644 index 0000000..c697bac --- /dev/null +++ b/.changeset/nice-coats-add.md @@ -0,0 +1,5 @@ +--- +"@effect/printer-ansi": minor +--- + +refactor and enhance the external api of printer-ansi diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 7809abb..723c4fa 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -66,7 +66,7 @@ module.exports = { { config: { "indentWidth": 2, - "lineWidth": 120, + "lineWidth": 100, "semiColons": "asi", "quoteStyle": "alwaysDouble", "trailingCommas": "never", diff --git a/flake.lock b/flake.lock index 6432723..c875998 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1689068808, - "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", + "lastModified": 1694529238, + "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", "owner": "numtide", "repo": "flake-utils", - "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", + "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", "type": "github" }, "original": { @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1690083312, - "narHash": "sha256-I3egwgNXavad1eIjWu1kYyi0u73di/sMmlnQIuzQASk=", + "lastModified": 1701174899, + "narHash": "sha256-1W+FMe8mWsJKXoBc+QgKmEeRj33kTFnPq7XCjU+bfnA=", "owner": "nixos", "repo": "nixpkgs", - "rev": "af8cd5ded7735ca1df1a1174864daab75feeb64a", + "rev": "010c7296f3b19a58b206fdf7d68d75a5b0a09e9e", "type": "github" }, "original": { diff --git a/packages/printer-ansi/examples/main.ts b/packages/printer-ansi/examples/main.ts index b5fc87d..3ecae1e 100644 --- a/packages/printer-ansi/examples/main.ts +++ b/packages/printer-ansi/examples/main.ts @@ -1,28 +1,16 @@ -import * as AnsiRender from "@effect/printer-ansi/AnsiRender" -import * as AnsiStyle from "@effect/printer-ansi/AnsiStyle" -import * as Color from "@effect/printer-ansi/Color" -import * as Doc from "@effect/printer/Doc" +import * as Ansi from "@effect/printer-ansi/Ansi" +import * as Doc from "@effect/printer-ansi/AnsiDoc" -const doc = Doc.annotate( - Doc.hsep([ - Doc.text("red"), - Doc.align( - Doc.vsep([ - Doc.annotate( - Doc.hsep([ - Doc.text("blue+u"), - Doc.annotate( - Doc.text("bold"), - AnsiStyle.combine(AnsiStyle.color(Color.blue), AnsiStyle.bold) - ), - Doc.text("blue+u") - ]), - AnsiStyle.combine(AnsiStyle.color(Color.blue), AnsiStyle.underlined) - ), - Doc.text("red") - ]) - ) - ]), - AnsiStyle.color(Color.red) -) -console.log(AnsiRender.prettyDefault(doc)) +const doc = Doc.hsep([ + Doc.text("red"), + Doc.align(Doc.vsep([ + Doc.hsep([ + Doc.text("blue+u"), + Doc.text("bold").pipe(Doc.annotate(Ansi.bold)), + Doc.text("blue+u") + ]).pipe(Doc.annotate(Ansi.combine(Ansi.blue, Ansi.underlined))), + Doc.text("red") + ])) +]).pipe(Doc.annotate(Ansi.red)) + +console.log(Doc.render(doc, { style: "pretty" })) diff --git a/packages/printer-ansi/src/Ansi.ts b/packages/printer-ansi/src/Ansi.ts new file mode 100644 index 0000000..4f2dad4 --- /dev/null +++ b/packages/printer-ansi/src/Ansi.ts @@ -0,0 +1,516 @@ +/** + * @since 1.0.0 + */ +import type { Color } from "./Color.js" +import * as InternalAnsi from "./internal/ansi.js" + +// ----------------------------------------------------------------------------- +// Models +// ----------------------------------------------------------------------------- + +/** + * @since 1.0.0 + * @category symbol + */ +export const AnsiTypeId: unique symbol = InternalAnsi.AnsiTypeId as AnsiTypeId + +/** + * @since 1.0.0 + * @category symbol + */ +export type AnsiTypeId = typeof AnsiTypeId + +/** + * @since 1.0.0 + * @category model + */ +export interface Ansi extends Ansi.Proto {} + +/** + * @since 1.0.0 + */ +export declare namespace Ansi { + /** + * @since 1.0.0 + * @category model + */ + export interface Proto { + readonly [AnsiTypeId]: AnsiTypeId + } +} + +// ----------------------------------------------------------------------------- +// Style Constructors +// ----------------------------------------------------------------------------- + +/** + * @since 1.0.0 + * @category constructors + */ +export const bold: Ansi = InternalAnsi.bold + +/** + * @since 1.0.0 + * @category constructors + */ +export const italicized: Ansi = InternalAnsi.italicized + +/** + * @since 1.0.0 + * @category constructors + */ +export const strikethrough: Ansi = InternalAnsi.strikethrough + +/** + * @since 1.0.0 + * @category constructors + */ +export const underlined: Ansi = InternalAnsi.underlined + +// ----------------------------------------------------------------------------- +// Color Constructors +// ----------------------------------------------------------------------------- + +/** + * @since 1.0.0 + * @category constructors + */ +export const color: (color: Color) => Ansi = InternalAnsi.color + +/** + * @since 1.0.0 + * @category constructors + */ +export const brightColor: (color: Color) => Ansi = InternalAnsi.brightColor + +/** + * @since 1.0.0 + * @category constructors + */ +export const bgColor: (color: Color) => Ansi = InternalAnsi.bgColor + +/** + * @since 1.0.0 + * @category constructors + */ +export const bgColorBright: (color: Color) => Ansi = InternalAnsi.bgColorBright + +/** + * @since 1.0.0 + * @category colors + */ +export const black: Ansi = InternalAnsi.black + +/** + * @since 1.0.0 + * @category colors + */ +export const red: Ansi = InternalAnsi.red + +/** + * @since 1.0.0 + * @category colors + */ +export const green: Ansi = InternalAnsi.green + +/** + * @since 1.0.0 + * @category colors + */ +export const yellow: Ansi = InternalAnsi.yellow + +/** + * @since 1.0.0 + * @category colors + */ +export const blue: Ansi = InternalAnsi.blue + +/** + * @since 1.0.0 + * @category colors + */ +export const magenta: Ansi = InternalAnsi.magenta + +/** + * @since 1.0.0 + * @category colors + */ +export const cyan: Ansi = InternalAnsi.cyan + +/** + * @since 1.0.0 + * @category colors + */ +export const white: Ansi = InternalAnsi.white + +/** + * @since 1.0.0 + * @category colors + */ +export const blackBright: Ansi = InternalAnsi.blackBright + +/** + * @since 1.0.0 + * @category colors + */ +export const redBright: Ansi = InternalAnsi.redBright + +/** + * @since 1.0.0 + * @category colors + */ +export const greenBright: Ansi = InternalAnsi.greenBright + +/** + * @since 1.0.0 + * @category colors + */ +export const yellowBright: Ansi = InternalAnsi.yellowBright + +/** + * @since 1.0.0 + * @category colors + */ +export const blueBright: Ansi = InternalAnsi.blueBright + +/** + * @since 1.0.0 + * @category colors + */ +export const magentaBright: Ansi = InternalAnsi.magentaBright + +/** + * @since 1.0.0 + * @category colors + */ +export const cyanBright: Ansi = InternalAnsi.cyanBright + +/** + * @since 1.0.0 + * @category colors + */ +export const whiteBright: Ansi = InternalAnsi.whiteBright + +/** + * @since 1.0.0 + * @category colors + */ +export const bgBlack: Ansi = InternalAnsi.bgBlack + +/** + * @since 1.0.0 + * @category colors + */ +export const bgRed: Ansi = InternalAnsi.bgRed + +/** + * @since 1.0.0 + * @category colors + */ +export const bgGreen: Ansi = InternalAnsi.bgGreen + +/** + * @since 1.0.0 + * @category colors + */ +export const bgYellow: Ansi = InternalAnsi.bgYellow + +/** + * @since 1.0.0 + * @category colors + */ +export const bgBlue: Ansi = InternalAnsi.bgBlue + +/** + * @since 1.0.0 + * @category colors + */ +export const bgMagenta: Ansi = InternalAnsi.bgMagenta + +/** + * @since 1.0.0 + * @category colors + */ +export const bgCyan: Ansi = InternalAnsi.bgCyan + +/** + * @since 1.0.0 + * @category colors + */ +export const bgWhite: Ansi = InternalAnsi.bgWhite + +/** + * @since 1.0.0 + * @category colors + */ +export const bgBlackBright: Ansi = InternalAnsi.bgBlackBright + +/** + * @since 1.0.0 + * @category colors + */ +export const bgRedBright: Ansi = InternalAnsi.bgRedBright + +/** + * @since 1.0.0 + * @category colors + */ +export const bgGreenBright: Ansi = InternalAnsi.bgGreenBright + +/** + * @since 1.0.0 + * @category colors + */ +export const bgYellowBright: Ansi = InternalAnsi.bgYellowBright + +/** + * @since 1.0.0 + * @category colors + */ +export const bgBlueBright: Ansi = InternalAnsi.bgBlueBright + +/** + * @since 1.0.0 + * @category colors + */ +export const bgMagentaBright: Ansi = InternalAnsi.bgMagentaBright + +/** + * @since 1.0.0 + * @category colors + */ +export const bgCyanBright: Ansi = InternalAnsi.bgCyanBright + +/** + * @since 1.0.0 + * @category colors + */ +export const bgWhiteBright: Ansi = InternalAnsi.bgWhiteBright + +// ----------------------------------------------------------------------------- +// Command Constructors +// ----------------------------------------------------------------------------- + +/** + * Play a beeping sound. + * + * @since 1.0.0 + * @category commands + */ +export const beep: Ansi = InternalAnsi.beep + +/** + * Moves the cursor to the specified `row` and `column`. + * + * Though the ANSI Control Sequence for Cursor Position is `1`-based, this + * method takes row and column values starting from `0` and adjusts them to `1`- + * based values. + * + * @since 1.0.0 + * @category commands + */ +export const cursorTo: (column: number, row?: number) => Ansi = InternalAnsi.cursorTo + +/** + * Move the cursor position the specified number of `rows` and `columns` + * relative to the current cursor position. + * + * If the cursor is already at the edge of the screen in either direction, then + * additional movement will have no effect. + * + * @since 1.0.0 + * @category commands + */ +export const cursorMove: (column: number, row?: number) => Ansi = InternalAnsi.cursorMove + +/** + * Moves the cursor up by the specified number of `lines` (default `1`) relative + * to the current cursor position. + * + * If the cursor is already at the edge of the screen, this has no effect. + * + * @since 1.0.0 + * @category commands + */ +export const cursorUp: (lines?: number) => Ansi = InternalAnsi.cursorUp + +/** + * Moves the cursor down by the specified number of `lines` (default `1`) + * relative to the current cursor position. + * + * If the cursor is already at the edge of the screen, this has no effect. + * + * @since 1.0.0 + * @category commands + */ +export const cursorDown: (lines?: number) => Ansi = InternalAnsi.cursorDown + +/** + * Moves the cursor forward by the specified number of `columns` (default `1`) + * relative to the current cursor position. + * + * If the cursor is already at the edge of the screen, this has no effect. + * + * @since 1.0.0 + * @category commands + */ +export const cursorForward: (columns?: number) => Ansi = InternalAnsi.cursorForward + +/** + * Moves the cursor backward by the specified number of `columns` (default `1`) + * relative to the current cursor position. + * + * If the cursor is already at the edge of the screen, this has no effect. + * + * @since 1.0.0 + * @category commands + */ +export const cursorBackward: (columns?: number) => Ansi = InternalAnsi.cursorBackward + +/** + * Moves the cursor to the first column of the current row. + * + * @since 1.0.0 + * @category commands + */ +export const cursorLeft: Ansi = InternalAnsi.cursorLeft + +/** + * Saves the cursor position, encoding shift state and formatting attributes. + * + * @since 1.0.0 + * @category commands + */ +export const cursorSavePosition: Ansi = InternalAnsi.cursorSavePosition + +/** + * Restores the cursor position, encoding shift state and formatting attributes + * from the previous save, if any, otherwise resets these all to their defaults. + * + * @since 1.0.0 + * @category commands + */ +export const cursorRestorePosition: Ansi = InternalAnsi.cursorRestorePosition + +/** + * Moves cursor to beginning of the line the specified number of rows down + * (default `1`). + * + * @since 1.0.0 + * @category commands + */ +export const cursorNextLine: (rows?: number) => Ansi = InternalAnsi.cursorNextLine + +/** + * Moves cursor to beginning of the line the specified number of rows up + * (default `1`). + * + * @since 1.0.0 + * @category commands + */ +export const cursorPrevLine: (rows?: number) => Ansi = InternalAnsi.cursorPrevLine + +/** + * Hides the cursor. + * + * @since 1.0.0 + * @category commands + */ +export const cursorHide: Ansi = InternalAnsi.cursorHide + +/** + * Shows the cursor. + * + * @since 1.0.0 + * @category commands + */ +export const cursorShow: Ansi = InternalAnsi.cursorShow + +/** + * Erase from the current cursor position up the specified amount of rows. + * + * @since 1.0.0 + * @category commands + */ +export const eraseLines: (rows: number) => Ansi = InternalAnsi.eraseLines + +/** + * Clears from the current cursor position to the end of the current line. + * + * The current cursor position does not change. + * + * @since 1.0.0 + * @category commands + */ +export const eraseEndLine: Ansi = InternalAnsi.eraseEndLine + +/** + * Clears from the current cursor position to the start of the current line. + * + * The current cursor position does not change. + * + * @since 1.0.0 + * @category commands + */ +export const eraseStartLine: Ansi = InternalAnsi.eraseStartLine + +/** + * Clears the current line. + * + * The current cursor position does not change. + * + * @since 1.0.0 + * @category commands + */ +export const eraseLine: Ansi = InternalAnsi.eraseLine + +/** + * Clears from the current cursor position to the end of the screen. + * + * The current cursor position does not change. + * + * @since 1.0.0 + * @category commands + */ +export const eraseDown: Ansi = InternalAnsi.eraseDown + +/** + * Clears from the current cursor position to the beginning of the screen. + * + * The current cursor position does not change. + * + * @since 1.0.0 + * @category commands + */ +export const eraseUp: Ansi = InternalAnsi.eraseUp + +/** + * Clears the entire screen and move the cursor to the upper left. + * + * @since 1.0.0 + * @category commands + */ +export const eraseScreen: Ansi = InternalAnsi.eraseScreen + +// ----------------------------------------------------------------------------- +// Destructors +// ----------------------------------------------------------------------------- + +/** + * @since 1.0.0 + * @category destructors + */ +export const stringify: (self: Ansi) => string = InternalAnsi.stringify + +// ----------------------------------------------------------------------------- +// Instances +// ----------------------------------------------------------------------------- + +/** + * @since 1.0.0 + * @categrory combinators + */ +export const combine: { + (that: Ansi): (self: Ansi) => Ansi + (self: Ansi, that: Ansi): Ansi +} = InternalAnsi.combine diff --git a/packages/printer-ansi/src/AnsiDoc.ts b/packages/printer-ansi/src/AnsiDoc.ts index 6f0faa4..0e35d00 100644 --- a/packages/printer-ansi/src/AnsiDoc.ts +++ b/packages/printer-ansi/src/AnsiDoc.ts @@ -1,9 +1,11 @@ /** * @since 1.0.0 */ - import type { Doc } from "@effect/printer/Doc" -import type { AnsiStyle } from "./AnsiStyle.js" +import type { AvailablePerLine } from "@effect/printer/PageWidth" +import type { Ansi } from "./Ansi.js" +import * as InternalAnsiDoc from "./internal/ansiDoc.js" +import * as InternalAnsiRender from "./internal/ansiRender.js" // ----------------------------------------------------------------------------- // Models @@ -13,4 +15,847 @@ import type { AnsiStyle } from "./AnsiStyle.js" * @since 1.0.0 * @category model */ -export type AnsiDoc = Doc +export type AnsiDoc = Doc + +/** + * @since 1.0.0 + */ +export declare namespace AnsiDoc { + /** + * @since 1.0.0 + * @category model + */ + export type RenderConfig = Compact | Pretty | Smart + /** + * @since 1.0.0 + * @category model + */ + export interface Compact { + readonly style: "compact" + } + + /** + * @since 1.0.0 + * @category model + */ + export interface Pretty { + readonly style: "pretty" + readonly options?: Partial> + } + + /** + * @since 1.0.0 + * @category model + */ + export interface Smart { + readonly style: "smart" + readonly options?: Partial> + } +} + +// ----------------------------------------------------------------------------- +// Constructors +// ----------------------------------------------------------------------------- + +/** + * Play a beeping sound. + * + * @since 1.0.0 + * @category constructors + */ +export const beep: AnsiDoc = InternalAnsiDoc.beep + +/** + * Moves the cursor to the specified `row` and `column`. + * + * Though the ANSI Control Sequence for Cursor Position is `1`-based, this + * method takes row and column values starting from `0` and adjusts them to `1`- + * based values. + * + * @since 1.0.0 + * @category constructors + */ +export const cursorTo: (column: number, row?: number) => AnsiDoc = InternalAnsiDoc.cursorTo + +/** + * Move the cursor position the specified number of `rows` and `columns` + * relative to the current cursor position. + * + * If the cursor is already at the edge of the screen in either direction, then + * additional movement will have no effect. + * + * @since 1.0.0 + * @category constructors + */ +export const cursorMove: (column: number, row?: number) => AnsiDoc = InternalAnsiDoc.cursorMove + +/** + * Moves the cursor up by the specified number of `lines` (default `1`) relative + * to the current cursor position. + * + * If the cursor is already at the edge of the screen, this has no effect. + * + * @since 1.0.0 + * @category commands + */ +export const cursorUp: (lines?: number) => AnsiDoc = InternalAnsiDoc.cursorUp + +/** + * Moves the cursor down by the specified number of `lines` (default `1`) + * relative to the current cursor position. + * + * If the cursor is already at the edge of the screen, this has no effect. + * + * @since 1.0.0 + * @category commands + */ +export const cursorDown: (lines?: number) => AnsiDoc = InternalAnsiDoc.cursorDown + +/** + * Moves the cursor forward by the specified number of `columns` (default `1`) + * relative to the current cursor position. + * + * If the cursor is already at the edge of the screen, this has no effect. + * + * @since 1.0.0 + * @category commands + */ +export const cursorForward: (columns?: number) => AnsiDoc = InternalAnsiDoc.cursorForward + +/** + * Moves the cursor backward by the specified number of `columns` (default `1`) + * relative to the current cursor position. + * + * If the cursor is already at the edge of the screen, this has no effect. + * + * @since 1.0.0 + * @category commands + */ +export const cursorBackward: (columns?: number) => AnsiDoc = InternalAnsiDoc.cursorBackward + +/** + * Moves the cursor to the first column of the current row. + * + * @since 1.0.0 + * @category commands + */ +export const cursorLeft: AnsiDoc = InternalAnsiDoc.cursorLeft + +/** + * Saves the cursor position, encoding shift state and formatting attributes. + * + * @since 1.0.0 + * @category commands + */ +export const cursorSavePosition: AnsiDoc = InternalAnsiDoc.cursorSavePosition + +/** + * Restores the cursor position, encoding shift state and formatting attributes + * from the previous save, if any, otherwise resets these all to their defaults. + * + * @since 1.0.0 + * @category commands + */ +export const cursorRestorePosition: AnsiDoc = InternalAnsiDoc.cursorRestorePosition + +/** + * Moves cursor to beginning of the line the specified number of rows down + * (default `1`). + * + * @since 1.0.0 + * @category commands + */ +export const cursorNextLine: (rows?: number) => AnsiDoc = InternalAnsiDoc.cursorNextLine + +/** + * Moves cursor to beginning of the line the specified number of rows up + * (default `1`). + * + * @since 1.0.0 + * @category commands + */ +export const cursorPrevLine: (rows?: number) => AnsiDoc = InternalAnsiDoc.cursorPrevLine + +/** + * Hides the cursor. + * + * @since 1.0.0 + * @category commands + */ +export const cursorHide: AnsiDoc = InternalAnsiDoc.cursorHide + +/** + * Shows the cursor. + * + * @since 1.0.0 + * @category commands + */ +export const cursorShow: AnsiDoc = InternalAnsiDoc.cursorShow + +/** + * Erase from the current cursor position up the specified amount of rows. + * + * @since 1.0.0 + * @category commands + */ +export const eraseLines: (rows: number) => AnsiDoc = InternalAnsiDoc.eraseLines + +/** + * Clears from the current cursor position to the end of the current line. + * + * The current cursor position does not change. + * + * @since 1.0.0 + * @category commands + */ +export const eraseEndLine: AnsiDoc = InternalAnsiDoc.eraseEndLine + +/** + * Clears from the current cursor position to the start of the current line. + * + * The current cursor position does not change. + * + * @since 1.0.0 + * @category commands + */ +export const eraseStartLine: AnsiDoc = InternalAnsiDoc.eraseStartLine + +/** + * Clears the current line. + * + * The current cursor position does not change. + * + * @since 1.0.0 + * @category commands + */ +export const eraseLine: AnsiDoc = InternalAnsiDoc.eraseLine + +/** + * Clears from the current cursor position to the end of the screen. + * + * The current cursor position does not change. + * + * @since 1.0.0 + * @category commands + */ +export const eraseDown: AnsiDoc = InternalAnsiDoc.eraseDown + +/** + * Clears from the current cursor position to the beginning of the screen. + * + * The current cursor position does not change. + * + * @since 1.0.0 + * @category commands + */ +export const eraseUp: AnsiDoc = InternalAnsiDoc.eraseUp + +/** + * Clears the entire screen and move the cursor to the upper left. + * + * @since 1.0.0 + * @category commands + */ +export const eraseScreen: AnsiDoc = InternalAnsiDoc.eraseScreen + +// ----------------------------------------------------------------------------- +// Destructors +// ----------------------------------------------------------------------------- + +export const render: { + (config: AnsiDoc.RenderConfig): (self: AnsiDoc) => string + (self: AnsiDoc, config: AnsiDoc.RenderConfig): string +} = InternalAnsiRender.render + +// ----------------------------------------------------------------------------- +// Re-Exports +// ----------------------------------------------------------------------------- + +export type { + /** + * @since 1.0.0 + * @category model + */ + Annotated, + /** + * @since 1.0.0 + * @category model + */ + Cat, + /** + * @since 1.0.0 + * @category model + */ + Char, + /** + * @since 1.0.0 + * @category model + */ + Column, + /** + * @since 1.0.0 + * @category model + */ + Covariant, + /** + * @since 1.0.0 + * @category model + */ + Doc, + /** + * @since 1.0.0 + * @category model + */ + DocTypeId, + /** + * @since 1.0.0 + * @category model + */ + DocTypeLambda, + /** + * @since 1.0.0 + * @category model + */ + Empty, + /** + * @since 1.0.0 + * @category model + */ + Fail, + /** + * @since 1.0.0 + * @category model + */ + FlatAlt, + /** + * @since 1.0.0 + * @category model + */ + Line, + /** + * @since 1.0.0 + * @category model + */ + Nest, + /** + * @since 1.0.0 + * @category model + */ + Nesting, + /** + * @since 1.0.0 + * @category model + */ + Text, + /** + * @since 1.0.0 + * @category model + */ + Union, + /** + * @since 1.0.0 + * @category model + */ + WithPageWidth +} from "@effect/printer/Doc" + +export type { + /** + * @since 1.0.0 + * @category model + */ + AlreadyFlat, + /** + * @since 1.0.0 + * @category model + */ + Flatten, + /** + * @since 1.0.0 + * @category model + */ + Flattened, + /** + * @since 1.0.0 + * @category model + */ + NeverFlat +} from "@effect/printer/Flatten" + +export { + /** + * @since 1.0.0 + * @category alignment + */ + align, + /** + * @since 1.0.0 + * @category annotations + */ + alterAnnotations, + /** + * @since 1.0.0 + * @category utilities + */ + angleBracketed, + /** + * @since 1.0.0 + * @category annotations + */ + annotate, + /** + * @since 1.0.0 + * @category primitives + */ + backslash, + /** + * @since 1.0.0 + * @category concatenation + */ + cat, + /** + * @since 1.0.0 + * @category concatenation + */ + cats, + /** + * @since 1.0.0 + * @category concatenation + */ + catWithLine, + /** + * @since 1.0.0 + * @category concatenation + */ + catWithLineBreak, + /** + * @since 1.0.0 + * @category concatenation + */ + catWithSoftLine, + /** + * @since 1.0.0 + * @category concatenation + */ + catWithSoftLineBreak, + /** + * @since 1.0.0 + * @category concatenation + */ + catWithSpace, + /** + * @since 1.0.0 + * @category flattening + */ + changesUponFlattening, + /** + * @since 1.0.0 + * @category constructors + */ + char, + /** + * @since 1.0.0 + * @category primitives + */ + colon, + /** + * @since 1.0.0 + * @category reactive layouts + */ + column, + /** + * @since 1.0.0 + * @category primitives + */ + comma, + /** + * @since 1.0.0 + * @category concatenation + */ + concatWith, + /** + * @since 1.0.0 + * @category utilities + */ + curlyBraced, + /** + * @since 1.0.0 + * @category primitives + */ + dot, + /** + * @since 1.0.0 + * @category utilities + */ + doubleQuoted, + /** + * @since 1.0.0 + * @category primitives + */ + dquote, + /** + * @since 1.0.0 + * @category primitives + */ + empty, + /** + * @since 1.0.0 + * @category alignment + */ + encloseSep, + /** + * @since 1.0.0 + * @category primitives + */ + equalSign, + /** + * @since 1.0.0 + * @category primitives + */ + fail, + /** + * @since 1.0.0 + * @category filling + */ + fill, + /** + * @since 1.0.0 + * @category filling + */ + fillBreak, + /** + * @since 1.0.0 + * @category filling + */ + fillCat, + /** + * @since 1.0.0 + * @category filling + */ + fillSep, + /** + * @since 1.0.0 + * @category alternative layouts + */ + flatAlt, + /** + * @since 1.0.0 + * @category flattening + */ + flatten, + /** + * @since 1.0.0 + * @category alternative layouts + */ + group, + /** + * @since 1.0.0 + * @category alignment + */ + hang, + /** + * @since 1.0.0 + * @category primitives + */ + hardLine, + /** + * @since 1.0.0 + * @category concatenation + */ + hcat, + /** + * @since 1.0.0 + * @category separation + */ + hsep, + /** + * @since 1.0.0 + * @category alignment + */ + indent, + /** + * @since 1.0.0 + * @category instances + */ + Invariant, + /** + * @since 1.0.0 + * @category refinements + */ + isAnnotated, + /** + * @since 1.0.0 + * @category refinements + */ + isCat, + /** + * @since 1.0.0 + * @category refinements + */ + isChar, + /** + * @since 1.0.0 + * @category refinements + */ + isColumn, + /** + * @since 1.0.0 + * @category refinements + */ + isDoc, + /** + * @since 1.0.0 + * @category refinements + */ + isEmpty, + /** + * @since 1.0.0 + * @category refinements + */ + isFail, + /** + * @since 1.0.0 + * @category refinements + */ + isFlatAlt, + /** + * @since 1.0.0 + * @category refinements + */ + isLine, + /** + * @since 1.0.0 + * @category refinements + */ + isNest, + /** + * @since 1.0.0 + * @category refinements + */ + isNesting, + /** + * @since 1.0.0 + * @category refinements + */ + isText, + /** + * @since 1.0.0 + * @category refinements + */ + isUnion, + /** + * @since 1.0.0 + * @category refinements + */ + isWithPageWidth, + /** + * @since 1.0.0 + * @category primitives + */ + langle, + /** + * @since 1.0.0 + * @category primitives + */ + lbrace, + /** + * @since 1.0.0 + * @category primitives + */ + lbracket, + /** + * @since 1.0.0 + * @category primitives + */ + line, + /** + * @since 1.0.0 + * @category primitives + */ + lineBreak, + /** + * @since 1.0.0 + * @category alignment + */ + list, + /** + * @since 1.0.0 + * @category primitives + */ + lparen, + /** + * @since 1.0.0 + * @category combinators + */ + map, + /** + * @since 1.0.0 + * @category matching + */ + match, + /** + * @since 1.0.0 + * @category alignment + */ + nest, + /** + * @since 1.0.0 + * @category reactive layouts + */ + nesting, + /** + * @since 1.0.0 + * @category reactive layouts + */ + pageWidth, + /** + * @since 1.0.0 + * @category utilities + */ + parenthesized, + /** + * @since 1.0.0 + * @category utilities + */ + punctuate, + /** + * @since 1.0.0 + * @category primitives + */ + rangle, + /** + * @since 1.0.0 + * @category primitives + */ + rbrace, + /** + * @since 1.0.0 + * @category primitives + */ + rbracket, + /** + * @since 1.0.0 + * @category annotations + */ + reAnnotate, + /** + * @since 1.0.0 + * @category utilities + */ + reflow, + /** + * @since 1.0.0 + * @category primitives + */ + rparen, + /** + * @since 1.0.0 + * @category primitives + */ + semi, + /** + * @since 1.0.0 + * @category separation + */ + seps, + /** + * @since 1.0.0 + * @category utilities + */ + singleQuoted, + /** + * @since 1.0.0 + * @category primitives + */ + slash, + /** + * @since 1.0.0 + * @category primitives + */ + softLine, + /** + * @since 1.0.0 + * @category primitives + */ + softLineBreak, + /** + * @since 1.0.0 + * @category primitives + */ + space, + /** + * @since 1.0.0 + * @category utilities + */ + spaces, + /** + * @since 1.0.0 + * @category utilities + */ + squareBracketed, + /** + * @since 1.0.0 + * @category primitives + */ + squote, + /** + * @since 1.0.0 + * @category constructors + */ + string, + /** + * @since 1.0.0 + * @category utilities + */ + surround, + /** + * @since 1.0.0 + * @category constructors + */ + text, + /** + * @since 1.0.0 + * @category utilities + */ + textSpaces, + /** + * @since 1.0.0 + * @category alignment + */ + tupled, + /** + * @since 1.0.0 + * @category annotations + */ + unAnnotate, + /** + * @since 1.0.0 + * @category alternative layouts + */ + union, + /** + * @since 1.0.0 + * @category primitives + */ + vbar, + /** + * @since 1.0.0 + * @category concatenation + */ + vcat, + /** + * @since 1.0.0 + * @category separation + */ + vsep, + /** + * @since 1.0.0 + * @category reactive layouts + */ + width, + /** + * @since 1.0.0 + * @category utilities + */ + words +} from "@effect/printer/Doc" diff --git a/packages/printer-ansi/src/AnsiRender.ts b/packages/printer-ansi/src/AnsiRender.ts deleted file mode 100644 index 6b56363..0000000 --- a/packages/printer-ansi/src/AnsiRender.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * @since 1.0.0 - */ - -import type { Doc } from "@effect/printer/Doc" -import type { DocStream } from "@effect/printer/DocStream" -import type { AvailablePerLine } from "@effect/printer/PageWidth" -import type { AnsiDoc } from "./AnsiDoc.js" -import type { AnsiStyle } from "./AnsiStyle.js" -import * as internal from "./internal/ansiRender.js" - -// ----------------------------------------------------------------------------- -// Rendering Algorithms -// ----------------------------------------------------------------------------- - -/** - * @since 1.0.0 - * @category rendering algorithms - */ -export const render: (self: DocStream) => string = internal.render - -/** - * @since 1.0.0 - * @category rendering algorithms - */ -export const compact: (self: AnsiDoc) => string = internal.compact - -/** - * @since 1.0.0 - * @category rendering algorithms - */ -export const pretty: { - (options: Partial>): (self: Doc) => string - (self: Doc, options: Partial>): string -} = internal.pretty - -/** - * @since 1.0.0 - * @category rendering algorithms - */ -export const prettyDefault: (self: AnsiDoc) => string = internal.prettyDefault - -/** - * @since 1.0.0 - * @category rendering algorithms - */ -export const prettyUnbounded: (self: AnsiDoc) => string = internal.prettyUnbounded - -/** - * @since 1.0.0 - * @category rendering algorithms - */ -export const smart: { - (options: Partial>): (self: Doc) => string - (self: Doc, options: Partial>): string -} = internal.smart - -/** - * @since 1.0.0 - * @category rendering algorithms - */ -export const smartDefault: (self: AnsiDoc) => string = internal.smartDefault - -/** - * @since 1.0.0 - * @category rendering algorithms - */ -export const smartUnbounded: (self: Doc) => string = internal.smartUnbounded diff --git a/packages/printer-ansi/src/AnsiStyle.ts b/packages/printer-ansi/src/AnsiStyle.ts deleted file mode 100644 index 11b868e..0000000 --- a/packages/printer-ansi/src/AnsiStyle.ts +++ /dev/null @@ -1,108 +0,0 @@ -/** - * @since 1.0.0 - */ - -import type * as monoid from "@effect/typeclass/Monoid" -import type * as semigroup from "@effect/typeclass/Semigroup" -import type { Option } from "effect/Option" -import type { Color } from "./Color.js" -import * as internal from "./internal/ansiStyle.js" -import type { SGR } from "./SGR.js" - -// ----------------------------------------------------------------------------- -// Models -// ----------------------------------------------------------------------------- - -/** - * @since 1.0.0 - * @category model - */ -export interface AnsiStyle { - readonly foreground: Option - readonly background: Option - readonly bold: Option - readonly italicized: Option - readonly underlined: Option -} - -// ----------------------------------------------------------------------------- -// Constructors -// ----------------------------------------------------------------------------- - -/** - * @since 1.0.0 - * @category constructors - */ -export const bold: AnsiStyle = internal.bold - -/** - * @since 1.0.0 - * @category constructors - */ -export const italicized: AnsiStyle = internal.italicized - -/** - * @since 1.0.0 - * @category constructors - */ -export const underlined: AnsiStyle = internal.underlined - -/** - * @since 1.0.0 - * @category constructors - */ -export const color: (color: Color) => AnsiStyle = internal.color - -/** - * @since 1.0.0 - * @category constructors - */ -export const dullColor: (color: Color) => AnsiStyle = internal.dullColor - -/** - * @since 1.0.0 - * @category constructors - */ -export const backgroundColor: (color: Color) => AnsiStyle = internal.backgroundColor - -/** - * @since 1.0.0 - * @category constructors - */ -export const dullBackgroundColor: (color: Color) => AnsiStyle = internal.dullBackgroundColor - -// ----------------------------------------------------------------------------- -// Destructors -// ----------------------------------------------------------------------------- - -/** - * @since 1.0.0 - * @category destructors - */ -export const stringify: (self: AnsiStyle) => string = internal.stringify - -// ----------------------------------------------------------------------------- -// Instances -// ----------------------------------------------------------------------------- - -/** - * @since 1.0.0 - * @category instances - */ -export const Semigroup: semigroup.Semigroup = internal.Semigroup - -/** - * @since 1.0.0 - * @category instances - */ -export const Monoid: monoid.Monoid = internal.Monoid - -// ----------------------------------------------------------------------------- -// Instances -// ----------------------------------------------------------------------------- - -/** - * @since 1.0.0 - * @categrory combinators - */ -export const combine: (self: AnsiStyle, that: AnsiStyle) => AnsiStyle = Semigroup.combine diff --git a/packages/printer-ansi/src/RenderLayer.ts b/packages/printer-ansi/src/RenderLayer.ts deleted file mode 100644 index 8be2928..0000000 --- a/packages/printer-ansi/src/RenderLayer.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * @since 1.0.0 - */ -import * as internal from "./internal/renderLayer.js" - -// ----------------------------------------------------------------------------- -// Models -// ----------------------------------------------------------------------------- - -/** - * @since 1.0.0 - * @category model - */ -export type RenderLayer = Background | Foreground - -/** - * @since 1.0.0 - * @category model - */ -export interface Background { - readonly _tag: "Background" -} - -/** - * @since 1.0.0 - * @category model - */ -export interface Foreground { - readonly _tag: "Foreground" -} - -// ----------------------------------------------------------------------------- -// Constructors -// ----------------------------------------------------------------------------- - -/** - * @since 1.0.0 - * @category constructors - */ -export const foreground: RenderLayer = internal.foreground - -/** - * @since 1.0.0 - * @category constructors - */ -export const background: RenderLayer = internal.background diff --git a/packages/printer-ansi/src/SGR.ts b/packages/printer-ansi/src/SGR.ts deleted file mode 100644 index 5f2dafb..0000000 --- a/packages/printer-ansi/src/SGR.ts +++ /dev/null @@ -1,113 +0,0 @@ -/** - * @since 1.0.0 - */ - -import type { Color } from "./Color.js" -import * as internal from "./internal/sgr.js" -import type { RenderLayer } from "./RenderLayer.js" - -// ----------------------------------------------------------------------------- -// Models -// ----------------------------------------------------------------------------- - -/** - * @since 1.0.0 - * @category model - */ -export type SGR = Reset | SetBold | SetItalicized | SetUnderlined | SetColor - -/** - * @since 1.0.0 - * @category model - */ -export interface Reset { - readonly _tag: "Reset" -} - -/** - * @since 1.0.0 - * @category model - */ -export interface SetBold { - readonly _tag: "SetBold" - readonly bold: boolean -} - -/** - * @since 1.0.0 - * @category model - */ -export interface SetItalicized { - readonly _tag: "SetItalicized" - readonly italicized: boolean -} - -/** - * @since 1.0.0 - * @category model - */ -export interface SetUnderlined { - readonly _tag: "SetUnderlined" - readonly underlined: boolean -} - -/** - * @since 1.0.0 - * @category model - */ -export interface SetColor { - readonly _tag: "SetColor" - readonly color: Color - readonly vivid: boolean - readonly layer: RenderLayer -} - -// ----------------------------------------------------------------------------- -// Constructors -// ----------------------------------------------------------------------------- - -/** - * @since 1.0.0 - * @category constructors - */ -export const reset: SGR = internal.reset - -/** - * @since 1.0.0 - * @category constructors - */ -export const setBold: (bold: boolean) => SGR = internal.setBold - -/** - * @since 1.0.0 - * @category constructors - */ -export const setItalicized: (italicized: boolean) => SGR = internal.setItalicized - -/** - * @since 1.0.0 - * @category constructors - */ -export const setUnderlined: (underlined: boolean) => SGR = internal.setUnderlined - -/** - * @since 1.0.0 - * @category constructors - */ -export const setColor: (color: Color, vivid: boolean, layer: RenderLayer) => SGR = internal.setColor - -// ----------------------------------------------------------------------------- -// Destructors -// ----------------------------------------------------------------------------- - -/** - * @since 1.0.0 - * @category destructors - */ -export const toCode: (self: SGR) => number = internal.toCode - -/** - * @since 1.0.0 - * @category destructors - */ -export const toEscapeSequence: (sgrs: Iterable) => string = internal.toEscapeSequence diff --git a/packages/printer-ansi/src/index.ts b/packages/printer-ansi/src/index.ts index 83f88b4..3495b7a 100644 --- a/packages/printer-ansi/src/index.ts +++ b/packages/printer-ansi/src/index.ts @@ -1,29 +1,14 @@ /** * @since 1.0.0 */ -export * as AnsiDoc from "./AnsiDoc.js" +export * as Ansi from "./Ansi.js" /** * @since 1.0.0 */ -export * as AnsiRender from "./AnsiRender.js" - -/** - * @since 1.0.0 - */ -export * as AnsiStyle from "./AnsiStyle.js" +export * as AnsiDoc from "./AnsiDoc.js" /** * @since 1.0.0 */ export * as Color from "./Color.js" - -/** - * @since 1.0.0 - */ -export * as RenderLayer from "./RenderLayer.js" - -/** - * @since 1.0.0 - */ -export * as SGR from "./SGR.js" diff --git a/packages/printer-ansi/src/internal/ansi.ts b/packages/printer-ansi/src/internal/ansi.ts new file mode 100644 index 0000000..90ef2ed --- /dev/null +++ b/packages/printer-ansi/src/internal/ansi.ts @@ -0,0 +1,370 @@ +import * as Monoid from "@effect/typeclass/Monoid" +import * as Semigroup from "@effect/typeclass/Semigroup" +import { dual } from "effect/Function" +import * as Option from "effect/Option" +import * as ReadonlyArray from "effect/ReadonlyArray" +import type * as Ansi from "../Ansi.js" +import type * as Color from "../Color.js" +import * as InternalColor from "./color.js" +import * as SGR from "./sgr.js" + +const AnsiSymbolKey = "@effect/printer-ansi/Ansi" + +/** @internal */ +export const AnsiTypeId: Ansi.AnsiTypeId = Symbol.for(AnsiSymbolKey) as Ansi.AnsiTypeId + +interface AnsiImpl extends Ansi.Ansi { + readonly commands: ReadonlyArray + readonly foreground: Option.Option + readonly background: Option.Option + readonly bold: Option.Option + readonly strikethrough: Option.Option + readonly italicized: Option.Option + readonly underlined: Option.Option +} + +const make = ( + params: Partial<{ + readonly commands: ReadonlyArray + readonly foreground: Option.Option + readonly background: Option.Option + readonly bold: Option.Option + readonly strikethrough: Option.Option + readonly italicized: Option.Option + readonly underlined: Option.Option + }> +): Ansi.Ansi => ({ + ...AnsiMonoid.empty, + ...params +}) + +// ----------------------------------------------------------------------------- +// Instances +// ----------------------------------------------------------------------------- + +const typeIdSemigroup = Semigroup.first() + +const getFirstSomeSemigroup: Semigroup.Semigroup> = Semigroup.make( + (self, that) => Option.isSome(self) ? self : that +) + +const AnsiSemigroup: Semigroup.Semigroup = Semigroup.struct({ + [AnsiTypeId]: typeIdSemigroup, + commands: Semigroup.array(), + foreground: getFirstSomeSemigroup, + background: getFirstSomeSemigroup, + bold: getFirstSomeSemigroup, + italicized: getFirstSomeSemigroup, + strikethrough: getFirstSomeSemigroup, + underlined: getFirstSomeSemigroup +}) + +const typeIdMonoid = Monoid.fromSemigroup(typeIdSemigroup, AnsiTypeId) + +const monoidOrElse = Monoid.fromSemigroup(getFirstSomeSemigroup, Option.none()) + +const AnsiMonoid: Monoid.Monoid = Monoid.struct({ + [AnsiTypeId]: typeIdMonoid, + commands: Monoid.array(), + foreground: monoidOrElse, + background: monoidOrElse, + bold: monoidOrElse, + italicized: monoidOrElse, + strikethrough: monoidOrElse, + underlined: monoidOrElse +}) + +/** @internal */ +export const none: Ansi.Ansi = AnsiMonoid.empty + +const ESC = "\u001B[" +const BEL = "\u0007" +const SEP = ";" + +// ----------------------------------------------------------------------------- +// Styles +// ----------------------------------------------------------------------------- + +/** @internal */ +export const bold: Ansi.Ansi = make({ + bold: Option.some(SGR.setBold(true)) +}) + +/** @internal */ +export const italicized: Ansi.Ansi = make({ italicized: Option.some(SGR.setItalicized(true)) }) + +/** @internal */ +export const strikethrough: Ansi.Ansi = make({ + strikethrough: Option.some(SGR.setStrikethrough(true)) +}) + +/** @internal */ +export const underlined: Ansi.Ansi = make({ underlined: Option.some(SGR.setUnderlined(true)) }) + +// ----------------------------------------------------------------------------- +// Colors +// ----------------------------------------------------------------------------- + +/** @internal */ +export const brightColor = (color: Color.Color): Ansi.Ansi => + make({ foreground: Option.some(SGR.setColor(color, true, "foreground")) }) + +/** @internal */ +export const color = (color: Color.Color): Ansi.Ansi => + make({ foreground: Option.some(SGR.setColor(color, false, "foreground")) }) + +/** @internal */ +export const bgColorBright = (color: Color.Color): Ansi.Ansi => + make({ background: Option.some(SGR.setColor(color, true, "background")) }) + +/** @internal */ +export const bgColor = (color: Color.Color): Ansi.Ansi => + make({ background: Option.some(SGR.setColor(color, false, "background")) }) + +/** @internal */ +export const black: Ansi.Ansi = color(InternalColor.black) + +/** @internal */ +export const red: Ansi.Ansi = color(InternalColor.red) + +/** @internal */ +export const green: Ansi.Ansi = color(InternalColor.green) + +/** @internal */ +export const yellow: Ansi.Ansi = color(InternalColor.yellow) + +/** @internal */ +export const blue: Ansi.Ansi = color(InternalColor.blue) + +/** @internal */ +export const magenta: Ansi.Ansi = color(InternalColor.magenta) + +/** @internal */ +export const cyan: Ansi.Ansi = color(InternalColor.cyan) + +/** @internal */ +export const white: Ansi.Ansi = color(InternalColor.white) + +/** @internal */ +export const blackBright: Ansi.Ansi = brightColor(InternalColor.black) + +/** @internal */ +export const redBright: Ansi.Ansi = brightColor(InternalColor.red) + +/** @internal */ +export const greenBright: Ansi.Ansi = brightColor(InternalColor.green) + +/** @internal */ +export const yellowBright: Ansi.Ansi = brightColor(InternalColor.yellow) + +/** @internal */ +export const blueBright: Ansi.Ansi = brightColor(InternalColor.blue) + +/** @internal */ +export const magentaBright: Ansi.Ansi = brightColor(InternalColor.magenta) + +/** @internal */ +export const cyanBright: Ansi.Ansi = brightColor(InternalColor.cyan) + +/** @internal */ +export const whiteBright: Ansi.Ansi = brightColor(InternalColor.white) + +/** @internal */ +export const bgBlack: Ansi.Ansi = bgColor(InternalColor.black) + +/** @internal */ +export const bgRed: Ansi.Ansi = bgColor(InternalColor.red) + +/** @internal */ +export const bgGreen: Ansi.Ansi = bgColor(InternalColor.green) + +/** @internal */ +export const bgYellow: Ansi.Ansi = bgColor(InternalColor.yellow) + +/** @internal */ +export const bgBlue: Ansi.Ansi = bgColor(InternalColor.blue) + +/** @internal */ +export const bgMagenta: Ansi.Ansi = bgColor(InternalColor.magenta) + +/** @internal */ +export const bgCyan: Ansi.Ansi = bgColor(InternalColor.cyan) + +/** @internal */ +export const bgWhite: Ansi.Ansi = bgColor(InternalColor.white) + +/** @internal */ +export const bgBlackBright: Ansi.Ansi = bgColorBright(InternalColor.black) + +/** @internal */ +export const bgRedBright: Ansi.Ansi = bgColorBright(InternalColor.red) + +/** @internal */ +export const bgGreenBright: Ansi.Ansi = bgColorBright(InternalColor.green) + +/** @internal */ +export const bgYellowBright: Ansi.Ansi = bgColorBright(InternalColor.yellow) + +/** @internal */ +export const bgBlueBright: Ansi.Ansi = bgColorBright(InternalColor.blue) + +/** @internal */ +export const bgMagentaBright: Ansi.Ansi = bgColorBright(InternalColor.magenta) + +/** @internal */ +export const bgCyanBright: Ansi.Ansi = bgColorBright(InternalColor.cyan) + +/** @internal */ +export const bgWhiteBright: Ansi.Ansi = bgColorBright(InternalColor.white) + +// ----------------------------------------------------------------------------- +// Commands +// ----------------------------------------------------------------------------- + +/** @internal */ +export const beep: Ansi.Ansi = make({ commands: ReadonlyArray.of(BEL) }) + +/** @internal */ +export const cursorTo = (column: number, row?: number): Ansi.Ansi => { + if (row === undefined) { + const command = `${ESC}${Math.max(column + 1, 0)}G` + return make({ commands: ReadonlyArray.of(command) }) + } + const command = `${ESC}${row + 1}${SEP}${Math.max(column + 1, 0)}H` + return make({ commands: ReadonlyArray.of(command) }) +} + +/** @internal */ +export const cursorMove = (column: number, row: number = 0): Ansi.Ansi => { + let command = "" + if (row < 0) { + command += `${ESC}${-row}A` + } + if (row > 0) { + command += `${ESC}${row}B` + } + if (column > 0) { + command += `${ESC}${column}C` + } + if (column < 0) { + command += `${ESC}${-column}D` + } + return make({ commands: ReadonlyArray.of(command) }) +} + +/** @internal */ +export const cursorUp = (lines: number = 1): Ansi.Ansi => { + const command = `${ESC}${lines}A` + return make({ commands: ReadonlyArray.of(command) }) +} + +/** @internal */ +export const cursorDown = (lines: number = 1): Ansi.Ansi => { + const command = `${ESC}${lines}B` + return make({ commands: ReadonlyArray.of(command) }) +} + +/** @internal */ +export const cursorForward = (columns: number = 1): Ansi.Ansi => { + const command = `${ESC}${columns}C` + return make({ commands: ReadonlyArray.of(command) }) +} + +/** @internal */ +export const cursorBackward = (columns: number = 1): Ansi.Ansi => { + const command = `${ESC}${columns}D` + return make({ commands: ReadonlyArray.of(command) }) +} + +/** @internal */ +export const cursorLeft: Ansi.Ansi = make({ commands: ReadonlyArray.of(`${ESC}G`) }) + +/** @internal */ +export const cursorSavePosition: Ansi.Ansi = make({ commands: ReadonlyArray.of(`${ESC}s`) }) + +/** @internal */ +export const cursorRestorePosition: Ansi.Ansi = make({ commands: ReadonlyArray.of(`${ESC}u`) }) + +/** @internal */ +export const cursorNextLine = (rows: number = 1): Ansi.Ansi => + make({ commands: ReadonlyArray.of(`${ESC}${rows}E`) }) + +/** @internal */ +export const cursorPrevLine = (rows: number = 1): Ansi.Ansi => + make({ commands: ReadonlyArray.of(`${ESC}${rows}F`) }) + +/** @internal */ +export const cursorHide: Ansi.Ansi = make({ commands: ReadonlyArray.of(`${ESC}?25l`) }) + +/** @internal */ +export const cursorShow: Ansi.Ansi = make({ commands: ReadonlyArray.of(`${ESC}?25h`) }) + +/** @internal */ +export const eraseLines = (rows: number): Ansi.Ansi => { + let command = "" + for (let i = 0; i < rows; i++) { + command += `${ESC}2K` + (i < rows - 1 ? `${ESC}1A` : "") + } + if (rows > 0) { + command += `${ESC}G` + } + return make({ commands: ReadonlyArray.of(command) }) +} + +/** @internal */ +export const eraseEndLine: Ansi.Ansi = make({ commands: ReadonlyArray.of(`${ESC}K`) }) + +/** @internal */ +export const eraseStartLine: Ansi.Ansi = make({ commands: ReadonlyArray.of(`${ESC}1K`) }) + +/** @internal */ +export const eraseLine: Ansi.Ansi = make({ commands: ReadonlyArray.of(`${ESC}2K`) }) + +/** @internal */ +export const eraseDown: Ansi.Ansi = make({ commands: ReadonlyArray.of(`${ESC}J`) }) + +/** @internal */ +export const eraseUp: Ansi.Ansi = make({ commands: ReadonlyArray.of(`${ESC}1J`) }) + +/** @internal */ +export const eraseScreen: Ansi.Ansi = make({ commands: ReadonlyArray.of(`${ESC}2J`) }) + +// ----------------------------------------------------------------------------- +// Destructors +// ----------------------------------------------------------------------------- + +/** @internal */ +export const stringify = (self: Ansi.Ansi): string => stringifyInternal(self as AnsiImpl) + +// ----------------------------------------------------------------------------- +// Combinators +// ----------------------------------------------------------------------------- + +/** @internal */ +export const combine = dual< + (that: Ansi.Ansi) => (self: Ansi.Ansi) => Ansi.Ansi, + (self: Ansi.Ansi, that: Ansi.Ansi) => Ansi.Ansi +>(2, (self, that) => combineInternal(self as AnsiImpl, that as AnsiImpl)) + +// ----------------------------------------------------------------------------- +// Internal +// ----------------------------------------------------------------------------- + +const combineInternal = (self: AnsiImpl, that: AnsiImpl): Ansi.Ansi => + AnsiSemigroup.combine(self, that) + +const stringifyInternal = (self: AnsiImpl): string => { + const displaySequence = SGR.toEscapeSequence( + ReadonlyArray.compact([ + Option.some(SGR.reset), + self.foreground, + self.background, + self.bold, + self.italicized, + self.strikethrough, + self.underlined + ]) + ) + const commandSequence = ReadonlyArray.join(self.commands, "") + return `${displaySequence}${commandSequence}` +} diff --git a/packages/printer-ansi/src/internal/ansiDoc.ts b/packages/printer-ansi/src/internal/ansiDoc.ts new file mode 100644 index 0000000..1773b30 --- /dev/null +++ b/packages/printer-ansi/src/internal/ansiDoc.ts @@ -0,0 +1,81 @@ +import * as Doc from "@effect/printer/Doc" +import type * as AnsiDoc from "../AnsiDoc.js" +import * as InternalAnsi from "./ansi.js" + +/** @internal */ +export const beep: AnsiDoc.AnsiDoc = Doc.annotate(Doc.empty, InternalAnsi.beep) + +/** @internal */ +export const cursorTo = (column: number, row?: number): AnsiDoc.AnsiDoc => + Doc.annotate(Doc.empty, InternalAnsi.cursorTo(column, row)) + +/** @internal */ +export const cursorMove = (column: number, row?: number): AnsiDoc.AnsiDoc => + Doc.annotate(Doc.empty, InternalAnsi.cursorMove(column, row)) + +/** @internal */ +export const cursorUp = (lines: number = 1): AnsiDoc.AnsiDoc => + Doc.annotate(Doc.empty, InternalAnsi.cursorUp(lines)) + +/** @internal */ +export const cursorDown = (lines: number = 1): AnsiDoc.AnsiDoc => + Doc.annotate(Doc.empty, InternalAnsi.cursorDown(lines)) + +/** @internal */ +export const cursorForward = (columns: number = 1): AnsiDoc.AnsiDoc => + Doc.annotate(Doc.empty, InternalAnsi.cursorForward(columns)) + +/** @internal */ +export const cursorBackward = (columns: number = 1): AnsiDoc.AnsiDoc => + Doc.annotate(Doc.empty, InternalAnsi.cursorBackward(columns)) + +/** @internal */ +export const cursorLeft: AnsiDoc.AnsiDoc = Doc.annotate(Doc.empty, InternalAnsi.cursorLeft) + +/** @internal */ +export const cursorSavePosition: AnsiDoc.AnsiDoc = Doc.annotate( + Doc.empty, + InternalAnsi.cursorSavePosition +) + +/** @internal */ +export const cursorRestorePosition: AnsiDoc.AnsiDoc = Doc.annotate( + Doc.empty, + InternalAnsi.cursorRestorePosition +) + +/** @internal */ +export const cursorNextLine = (rows: number = 1): AnsiDoc.AnsiDoc => + Doc.annotate(Doc.empty, InternalAnsi.cursorNextLine(rows)) + +/** @internal */ +export const cursorPrevLine = (rows: number = 1): AnsiDoc.AnsiDoc => + Doc.annotate(Doc.empty, InternalAnsi.cursorPrevLine(rows)) + +/** @internal */ +export const cursorHide: AnsiDoc.AnsiDoc = Doc.annotate(Doc.empty, InternalAnsi.cursorHide) + +/** @internal */ +export const cursorShow: AnsiDoc.AnsiDoc = Doc.annotate(Doc.empty, InternalAnsi.cursorShow) + +/** @internal */ +export const eraseLines = (rows: number): AnsiDoc.AnsiDoc => + Doc.annotate(Doc.empty, InternalAnsi.eraseLines(rows)) + +/** @internal */ +export const eraseEndLine: AnsiDoc.AnsiDoc = Doc.annotate(Doc.empty, InternalAnsi.eraseEndLine) + +/** @internal */ +export const eraseStartLine: AnsiDoc.AnsiDoc = Doc.annotate(Doc.empty, InternalAnsi.eraseStartLine) + +/** @internal */ +export const eraseLine: AnsiDoc.AnsiDoc = Doc.annotate(Doc.empty, InternalAnsi.eraseLine) + +/** @internal */ +export const eraseDown: AnsiDoc.AnsiDoc = Doc.annotate(Doc.empty, InternalAnsi.eraseDown) + +/** @internal */ +export const eraseUp: AnsiDoc.AnsiDoc = Doc.annotate(Doc.empty, InternalAnsi.eraseUp) + +/** @internal */ +export const eraseScreen: AnsiDoc.AnsiDoc = Doc.annotate(Doc.empty, InternalAnsi.eraseScreen) diff --git a/packages/printer-ansi/src/internal/ansiRender.ts b/packages/printer-ansi/src/internal/ansiRender.ts index 14d318e..31f778a 100644 --- a/packages/printer-ansi/src/internal/ansiRender.ts +++ b/packages/printer-ansi/src/internal/ansiRender.ts @@ -4,19 +4,39 @@ import * as PageWidth from "@effect/printer/PageWidth" import * as Effect from "effect/Effect" import { dual } from "effect/Function" import * as List from "effect/List" +import type * as Ansi from "../Ansi.js" import type * as AnsiDoc from "../AnsiDoc.js" -import type * as AnsiStyle from "../AnsiStyle.js" -import * as ansiStyle from "./ansiStyle.js" +import * as InternalAnsi from "./ansi.js" // ----------------------------------------------------------------------------- // Rendering Algorithms // ----------------------------------------------------------------------------- /** @internal */ -export const render = (self: DocStream.DocStream): string => - Effect.runSync(renderSafe(self, List.of(ansiStyle.Monoid.empty))) +export const render = dual< + (config: AnsiDoc.AnsiDoc.RenderConfig) => (self: AnsiDoc.AnsiDoc) => string, + (self: AnsiDoc.AnsiDoc, config: AnsiDoc.AnsiDoc.RenderConfig) => string +>(2, (self, config) => { + switch (config.style) { + case "compact": { + return renderStream(Layout.compact(self)) + } + case "pretty": { + const width = Object.assign({}, PageWidth.defaultPageWidth, config.options) + return renderStream(Layout.pretty(self, Layout.options(width))) + } + case "smart": { + const width = Object.assign({}, PageWidth.defaultPageWidth, config.options) + return renderStream(Layout.smart(self, Layout.options(width))) + } + } +}) + +/** @internal */ +export const renderStream = (self: DocStream.DocStream): string => + Effect.runSync(renderSafe(self, List.of(InternalAnsi.none))) -const unsafePeek = (stack: List.List): AnsiStyle.AnsiStyle => { +const unsafePeek = (stack: List.List): Ansi.Ansi => { if (List.isNil(stack)) { throw new Error( "BUG: AnsiRender.unsafePeek - peeked at an empty stack" + @@ -27,8 +47,8 @@ const unsafePeek = (stack: List.List): AnsiStyle.AnsiStyle } const unsafePop = ( - stack: List.List -): readonly [AnsiStyle.AnsiStyle, List.List] => { + stack: List.List +): readonly [Ansi.Ansi, List.List] => { if (List.isNil(stack)) { throw new Error( "BUG: AnsiRender.unsafePop - popped from an empty stack" + @@ -39,8 +59,8 @@ const unsafePop = ( } const renderSafe = ( - self: DocStream.DocStream, - stack: List.List + self: DocStream.DocStream, + stack: List.List ): Effect.Effect => { switch (self._tag) { case "FailedStream": { @@ -76,10 +96,10 @@ const renderSafe = ( } case "PushAnnotationStream": { const currentStyle = unsafePeek(stack) - const nextStyle = ansiStyle.Monoid.combine(self.annotation, currentStyle) + const nextStyle = InternalAnsi.combine(self.annotation, currentStyle) return Effect.map( Effect.suspend(() => renderSafe(self.stream, List.cons(self.annotation, stack))), - (rest) => ansiStyle.stringify(nextStyle) + rest + (rest) => InternalAnsi.stringify(nextStyle) + rest ) } case "PopAnnotationStream": { @@ -87,45 +107,8 @@ const renderSafe = ( const nextStyle = unsafePeek(styles) return Effect.map( Effect.suspend(() => renderSafe(self.stream, styles)), - (rest) => ansiStyle.stringify(nextStyle) + rest + (rest) => InternalAnsi.stringify(nextStyle) + rest ) } } } - -/** @internal */ -export const compact = (self: AnsiDoc.AnsiDoc): string => render(Layout.compact(self)) - -/** @internal */ -export const pretty = dual< - (options: Partial>) => (self: AnsiDoc.AnsiDoc) => string, - (self: AnsiDoc.AnsiDoc, options: Partial>) => string ->(2, (self, options) => { - const width = Object.assign({}, PageWidth.defaultPageWidth, options) - const layoutOptions = Layout.options(width) - return render(Layout.pretty(self, layoutOptions)) -}) - -/** @internal */ -export const prettyDefault = (self: AnsiDoc.AnsiDoc): string => render(Layout.pretty(self, Layout.defaultOptions)) - -/** @internal */ -export const prettyUnbounded = (self: AnsiDoc.AnsiDoc): string => - render(Layout.pretty(self, Layout.options(PageWidth.unbounded))) - -/** @internal */ -export const smart = dual< - (options: Partial>) => (self: AnsiDoc.AnsiDoc) => string, - (self: AnsiDoc.AnsiDoc, options: Partial>) => string ->(2, (self, options) => { - const width = Object.assign({}, PageWidth.defaultPageWidth, options) - const layoutOptions = Layout.options(width) - return render(Layout.smart(self, layoutOptions)) -}) - -/** @internal */ -export const smartDefault = (self: AnsiDoc.AnsiDoc): string => render(Layout.smart(self, Layout.defaultOptions)) - -/** @internal */ -export const smartUnbounded = (self: AnsiDoc.AnsiDoc): string => - render(Layout.smart(self, Layout.options(PageWidth.unbounded))) diff --git a/packages/printer-ansi/src/internal/ansiStyle.ts b/packages/printer-ansi/src/internal/ansiStyle.ts deleted file mode 100644 index ec9f8e2..0000000 --- a/packages/printer-ansi/src/internal/ansiStyle.ts +++ /dev/null @@ -1,100 +0,0 @@ -import * as monoid from "@effect/typeclass/Monoid" -import * as semigroup from "@effect/typeclass/Semigroup" -import * as Option from "effect/Option" -import * as ReadonlyArray from "effect/ReadonlyArray" -import type * as AnsiStyle from "../AnsiStyle.js" -import type * as Color from "../Color.js" -import type * as SGR from "../SGR.js" -import * as renderLayer from "./renderLayer.js" -import * as sgr from "./sgr.js" - -// ----------------------------------------------------------------------------- -// Instances -// ----------------------------------------------------------------------------- - -const getFirstSomeSemigroup: semigroup.Semigroup> = semigroup.make((self, that) => - Option.isSome(self) ? self : that -) - -/** @internal */ -export const Semigroup: semigroup.Semigroup = semigroup.struct({ - foreground: getFirstSomeSemigroup, - background: getFirstSomeSemigroup, - bold: getFirstSomeSemigroup, - italicized: getFirstSomeSemigroup, - underlined: getFirstSomeSemigroup -}) - -const monoidOrElse = monoid.fromSemigroup(getFirstSomeSemigroup, Option.none()) - -/** @internal */ -export const Monoid: monoid.Monoid = monoid.struct({ - foreground: monoidOrElse, - background: monoidOrElse, - bold: monoidOrElse, - italicized: monoidOrElse, - underlined: monoidOrElse -}) - -// ----------------------------------------------------------------------------- -// Constructors -// ----------------------------------------------------------------------------- - -/** @internal */ -export const bold: AnsiStyle.AnsiStyle = { - ...Monoid.empty, - bold: Option.some(sgr.setBold(true)) -} - -/** @internal */ -export const italicized: AnsiStyle.AnsiStyle = { - ...Monoid.empty, - italicized: Option.some(sgr.setItalicized(true)) -} - -/** @internal */ -export const underlined: AnsiStyle.AnsiStyle = { - ...Monoid.empty, - underlined: Option.some(sgr.setUnderlined(true)) -} - -/** @internal */ -export const color = (color: Color.Color): AnsiStyle.AnsiStyle => ({ - ...Monoid.empty, - foreground: Option.some(sgr.setColor(color, true, renderLayer.foreground)) -}) - -/** @internal */ -export const dullColor = (color: Color.Color): AnsiStyle.AnsiStyle => ({ - ...Monoid.empty, - foreground: Option.some(sgr.setColor(color, false, renderLayer.foreground)) -}) - -/** @internal */ -export const backgroundColor = (color: Color.Color): AnsiStyle.AnsiStyle => ({ - ...Monoid.empty, - background: Option.some(sgr.setColor(color, true, renderLayer.background)) -}) - -/** @internal */ -export const dullBackgroundColor = (color: Color.Color): AnsiStyle.AnsiStyle => ({ - ...Monoid.empty, - background: Option.some(sgr.setColor(color, false, renderLayer.background)) -}) - -// ----------------------------------------------------------------------------- -// Destructors -// ----------------------------------------------------------------------------- - -/** @internal */ -export const stringify = (self: AnsiStyle.AnsiStyle): string => - sgr.toEscapeSequence( - ReadonlyArray.compact([ - Option.some(sgr.reset), - self.foreground, - self.background, - self.bold, - self.italicized, - self.underlined - ]) - ) diff --git a/packages/printer-ansi/src/internal/renderLayer.ts b/packages/printer-ansi/src/internal/renderLayer.ts deleted file mode 100644 index 51eee75..0000000 --- a/packages/printer-ansi/src/internal/renderLayer.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type * as RenderLayer from "../RenderLayer.js" - -/** @internal */ -export const foreground: RenderLayer.RenderLayer = { - _tag: "Foreground" -} - -/** @internal */ -export const background: RenderLayer.RenderLayer = { - _tag: "Background" -} diff --git a/packages/printer-ansi/src/internal/sgr.ts b/packages/printer-ansi/src/internal/sgr.ts index 9d1963e..d256d0e 100644 --- a/packages/printer-ansi/src/internal/sgr.ts +++ b/packages/printer-ansi/src/internal/sgr.ts @@ -1,39 +1,145 @@ +/** + * This module contains the definition of a data structure meant to represent + * the control sequence introducer `CSI n m`, also known as the Select Graphic + * Rendition (SGR), where `n` is used to control terminal display attributes. + * + * Several SGR display attributes can be set in the same CSI sequence, separated + * by semicolons. + * + * Each display attribute remains in effect in the terminal until a following + * occurrence of SGR resets it. + * + * The SGR display attributes available in this module are **not** meant to be + * exhaustive but instead are meant to provide the most common and + * well-supported SGR display attributes (with a few exceptions). + */ import type * as Color from "../Color.js" -import type * as RenderLayer from "../RenderLayer.js" -import type * as SGR from "../SGR.js" import * as color from "./color.js" // ----------------------------------------------------------------------------- -// Destructors +// Models +// ----------------------------------------------------------------------------- + +/** + * Represents the the control sequence introducer `CSI n m`, also called the + * Select Graphic Rendition (SGR), which controls terminal display attributes. + * + * @internal + */ +export type SGR = + | Reset + | SetBold + | SetColor + | SetItalicized + | SetStrikethrough + | SetUnderlined + +/** + * Resets all SGR attributes to their default values. + * + * @internal + */ +export interface Reset { + readonly _tag: "Reset" +} + +/** + * Controls whether the text displayed in the terminal is bold. + * + * @internal + */ +export interface SetBold { + readonly _tag: "SetBold" + readonly bold: boolean +} + +/** + * Controls the color of the text displayed in the terminal. + * + * @internal + */ +export interface SetColor { + readonly _tag: "SetColor" + readonly color: Color.Color + readonly vivid: boolean + readonly layer: SGR.Layer +} + +/** + * Controls whether the text displayed in the terminal is italicized. + * + * **NOTE**: not widely supported. + * + * @internal + */ +export interface SetItalicized { + readonly _tag: "SetItalicized" + readonly italicized: boolean +} + +/** + * Controls whether the text displayed in the terminal has a strikethrough. + * + * @internal + */ +export interface SetStrikethrough { + readonly _tag: "SetStrikethrough" + readonly strikethrough: boolean +} + +/** + * Controls whether the text displayed in the terminal is underlined. + * + * @internal + */ +export interface SetUnderlined { + readonly _tag: "SetUnderlined" + readonly underlined: boolean +} + +/** @internal */ +export declare namespace SGR { + /** @internal */ + export type Layer = "foreground" | "background" +} + +// ----------------------------------------------------------------------------- +// Constructors // ----------------------------------------------------------------------------- /** @internal */ -export const reset: SGR.SGR = { _tag: "Reset" } +export const reset: SGR = { _tag: "Reset" } /** @internal */ -export const setBold = (bold: boolean): SGR.SGR => ({ +export const setBold = (bold: boolean): SGR => ({ _tag: "SetBold", bold }) /** @internal */ -export const setItalicized = (italicized: boolean): SGR.SGR => ({ +export const setColor = (color: Color.Color, vivid: boolean, layer: SGR.Layer): SGR => ({ + _tag: "SetColor", + color, + vivid, + layer +}) + +/** @internal */ +export const setItalicized = (italicized: boolean): SGR => ({ _tag: "SetItalicized", italicized }) /** @internal */ -export const setUnderlined = (underlined: boolean): SGR.SGR => ({ - _tag: "SetUnderlined", - underlined +export const setStrikethrough = (strikethrough: boolean): SGR => ({ + _tag: "SetStrikethrough", + strikethrough }) /** @internal */ -export const setColor = (color: Color.Color, vivid: boolean, layer: RenderLayer.RenderLayer): SGR.SGR => ({ - _tag: "SetColor", - color, - vivid, - layer +export const setUnderlined = (underlined: boolean): SGR => ({ + _tag: "SetUnderlined", + underlined }) // ----------------------------------------------------------------------------- @@ -41,7 +147,7 @@ export const setColor = (color: Color.Color, vivid: boolean, layer: RenderLayer. // ----------------------------------------------------------------------------- /** @internal */ -export const toCode = (self: SGR.SGR): number => { +export const toCode = (self: SGR): number => { switch (self._tag) { case "Reset": { return 0 @@ -49,29 +155,32 @@ export const toCode = (self: SGR.SGR): number => { case "SetBold": { return self.bold ? 1 : 22 } - case "SetItalicized": { - return self.italicized ? 3 : 23 - } - case "SetUnderlined": { - return self.underlined ? 4 : 24 - } case "SetColor": { - switch (self.layer._tag) { - case "Foreground": { + switch (self.layer) { + case "foreground": { return self.vivid ? 90 + color.toCode(self.color) : 30 + color.toCode(self.color) } - case "Background": { + case "background": { return self.vivid ? 100 + color.toCode(self.color) : 40 + color.toCode(self.color) } } } + case "SetItalicized": { + return self.italicized ? 3 : 23 + } + case "SetStrikethrough": { + return self.strikethrough ? 9 : 29 + } + case "SetUnderlined": { + return self.underlined ? 4 : 24 + } } } /** @internal */ -export const toEscapeSequence = (sgrs: Iterable): string => csi("m", sgrs) +export const toEscapeSequence = (sgrs: Iterable): string => csi("m", sgrs) -const csi = (controlFunction: string, sgrs: Iterable): string => { - const params = Array.from(sgrs).map((sgr) => toCode(sgr).toString()).join(";") +const csi = (controlFunction: string, sgrs: Iterable): string => { + const params = Array.from(sgrs).map((sgr) => `${toCode(sgr)}`).join(";") return `\u001b[${params}${controlFunction}` } diff --git a/packages/printer-ansi/test/terminal.test.ts b/packages/printer-ansi/test/terminal.test.ts index bb76446..06a2f3d 100644 --- a/packages/printer-ansi/test/terminal.test.ts +++ b/packages/printer-ansi/test/terminal.test.ts @@ -1,147 +1,311 @@ -import type * as AnsiDoc from "@effect/printer-ansi/AnsiDoc" -import * as AnsiRender from "@effect/printer-ansi/AnsiRender" -import * as AnsiStyle from "@effect/printer-ansi/AnsiStyle" +import * as Ansi from "@effect/printer-ansi/Ansi" +import * as Doc from "@effect/printer-ansi/AnsiDoc" import * as Color from "@effect/printer-ansi/Color" -import * as Doc from "@effect/printer/Doc" import * as String from "effect/String" import { describe, expect, it } from "vitest" -export const complex = Doc.annotate( - Doc.hsep([ - Doc.text("red"), - Doc.align( - Doc.vsep([ - Doc.annotate( - Doc.hsep([ - Doc.text("blue+u"), - Doc.annotate( - Doc.text("bold"), - AnsiStyle.combine(AnsiStyle.color(Color.blue), AnsiStyle.bold) - ), - Doc.text("blue+u") - ]), - AnsiStyle.combine(AnsiStyle.color(Color.blue), AnsiStyle.underlined) - ), - Doc.text("red") - ]) - ) - ]), - AnsiStyle.color(Color.red) -) - -describe.concurrent("Terminal", () => { - describe.concurrent("Colors/Layers", () => { - const foreground = (color: Color.Color): AnsiDoc.AnsiDoc => Doc.annotate(Doc.text("foo"), AnsiStyle.color(color)) - - const dullForeground = (color: Color.Color): AnsiDoc.AnsiDoc => - Doc.annotate(Doc.text("foo"), AnsiStyle.dullColor(color)) - - const background = (color: Color.Color): AnsiDoc.AnsiDoc => - Doc.annotate(Doc.text("foo"), AnsiStyle.backgroundColor(color)) - - const dullBackground = (color: Color.Color): AnsiDoc.AnsiDoc => - Doc.annotate(Doc.text("foo"), AnsiStyle.dullBackgroundColor(color)) +const simple = Doc.text("foo") +const complex = Doc.hsep([ + Doc.text("red"), + Doc.align(Doc.vsep([ + Doc.hsep([ + Doc.text("blue+u"), + Doc.text("bold").pipe(Doc.annotate(Ansi.bold)), + Doc.text("blue+u") + ]).pipe(Doc.annotate(Ansi.combine(Ansi.color(Color.blue), Ansi.underlined))), + Doc.text("red") + ])) +]).pipe(Doc.annotate(Ansi.red)) + +const render = (doc: Doc.AnsiDoc): string => Doc.render(doc, { style: "pretty" }) + +describe("Terminal", () => { + describe("Colors", () => { it("black", () => { - expect(AnsiRender.prettyDefault(foreground(Color.black))).toBe("\u001b[0;90mfoo\u001b[0m") - expect(AnsiRender.prettyDefault(dullForeground(Color.black))).toBe("\u001b[0;30mfoo\u001b[0m") - expect(AnsiRender.prettyDefault(background(Color.black))).toBe("\u001b[0;100mfoo\u001b[0m") - expect(AnsiRender.prettyDefault(dullBackground(Color.black))).toBe("\u001b[0;40mfoo\u001b[0m") + expect(render(Doc.annotate(simple, Ansi.black))).toBe( + "\u001b[0;30mfoo\u001b[0m" + ) + expect(render(Doc.annotate(simple, Ansi.blackBright))).toBe( + "\u001b[0;90mfoo\u001b[0m" + ) + expect(render(Doc.annotate(simple, Ansi.bgBlack))).toBe( + "\u001b[0;40mfoo\u001b[0m" + ) + expect(render(Doc.annotate(simple, Ansi.bgBlackBright))).toBe( + "\u001b[0;100mfoo\u001b[0m" + ) }) it("red", () => { - expect(AnsiRender.prettyDefault(foreground(Color.red))).toBe("\u001b[0;91mfoo\u001b[0m") - expect(AnsiRender.prettyDefault(dullForeground(Color.red))).toBe("\u001b[0;31mfoo\u001b[0m") - expect(AnsiRender.prettyDefault(background(Color.red))).toBe("\u001b[0;101mfoo\u001b[0m") - expect(AnsiRender.prettyDefault(dullBackground(Color.red))).toBe("\u001b[0;41mfoo\u001b[0m") + expect(render(Doc.annotate(simple, Ansi.red))).toBe( + "\u001b[0;31mfoo\u001b[0m" + ) + expect(render(Doc.annotate(simple, Ansi.redBright))).toBe( + "\u001b[0;91mfoo\u001b[0m" + ) + expect(render(Doc.annotate(simple, Ansi.bgRed))).toBe( + "\u001b[0;41mfoo\u001b[0m" + ) + expect(render(Doc.annotate(simple, Ansi.bgRedBright))).toBe( + "\u001b[0;101mfoo\u001b[0m" + ) }) it("green", () => { - expect(AnsiRender.prettyDefault(foreground(Color.green))).toBe("\u001b[0;92mfoo\u001b[0m") - expect(AnsiRender.prettyDefault(dullForeground(Color.green))).toBe("\u001b[0;32mfoo\u001b[0m") - expect(AnsiRender.prettyDefault(background(Color.green))).toBe("\u001b[0;102mfoo\u001b[0m") - expect(AnsiRender.prettyDefault(dullBackground(Color.green))).toBe("\u001b[0;42mfoo\u001b[0m") + expect(render(Doc.annotate(simple, Ansi.green))).toBe( + "\u001b[0;32mfoo\u001b[0m" + ) + expect(render(Doc.annotate(simple, Ansi.greenBright))).toBe( + "\u001b[0;92mfoo\u001b[0m" + ) + expect(render(Doc.annotate(simple, Ansi.bgGreen))).toBe( + "\u001b[0;42mfoo\u001b[0m" + ) + expect(render(Doc.annotate(simple, Ansi.bgGreenBright))).toBe( + "\u001b[0;102mfoo\u001b[0m" + ) }) it("yellow", () => { - expect(AnsiRender.prettyDefault(foreground(Color.yellow))).toBe("\u001b[0;93mfoo\u001b[0m") - expect(AnsiRender.prettyDefault(dullForeground(Color.yellow))).toBe("\u001b[0;33mfoo\u001b[0m") - expect(AnsiRender.prettyDefault(background(Color.yellow))).toBe("\u001b[0;103mfoo\u001b[0m") - expect(AnsiRender.prettyDefault(dullBackground(Color.yellow))).toBe("\u001b[0;43mfoo\u001b[0m") + expect(render(Doc.annotate(simple, Ansi.yellow))).toBe( + "\u001b[0;33mfoo\u001b[0m" + ) + expect(render(Doc.annotate(simple, Ansi.yellowBright))).toBe( + "\u001b[0;93mfoo\u001b[0m" + ) + expect(render(Doc.annotate(simple, Ansi.bgYellow))).toBe( + "\u001b[0;43mfoo\u001b[0m" + ) + expect(render(Doc.annotate(simple, Ansi.bgYellowBright))).toBe( + "\u001b[0;103mfoo\u001b[0m" + ) }) it("blue", () => { - expect(AnsiRender.prettyDefault(foreground(Color.blue))).toBe("\u001b[0;94mfoo\u001b[0m") - expect(AnsiRender.prettyDefault(dullForeground(Color.blue))).toBe("\u001b[0;34mfoo\u001b[0m") - expect(AnsiRender.prettyDefault(background(Color.blue))).toBe("\u001b[0;104mfoo\u001b[0m") - expect(AnsiRender.prettyDefault(dullBackground(Color.blue))).toBe("\u001b[0;44mfoo\u001b[0m") + expect(render(Doc.annotate(simple, Ansi.blue))).toBe( + "\u001b[0;34mfoo\u001b[0m" + ) + expect(render(Doc.annotate(simple, Ansi.blueBright))).toBe( + "\u001b[0;94mfoo\u001b[0m" + ) + expect(render(Doc.annotate(simple, Ansi.bgBlue))).toBe( + "\u001b[0;44mfoo\u001b[0m" + ) + expect(render(Doc.annotate(simple, Ansi.bgBlueBright))).toBe( + "\u001b[0;104mfoo\u001b[0m" + ) }) it("magenta", () => { - expect(AnsiRender.prettyDefault(foreground(Color.magenta))).toBe("\u001b[0;95mfoo\u001b[0m") - expect(AnsiRender.prettyDefault(dullForeground(Color.magenta))).toBe("\u001b[0;35mfoo\u001b[0m") - expect(AnsiRender.prettyDefault(background(Color.magenta))).toBe("\u001b[0;105mfoo\u001b[0m") - expect(AnsiRender.prettyDefault(dullBackground(Color.magenta))).toBe("\u001b[0;45mfoo\u001b[0m") + expect(render(Doc.annotate(simple, Ansi.magenta))).toBe( + "\u001b[0;35mfoo\u001b[0m" + ) + expect(render(Doc.annotate(simple, Ansi.magentaBright))).toBe( + "\u001b[0;95mfoo\u001b[0m" + ) + expect(render(Doc.annotate(simple, Ansi.bgMagenta))).toBe( + "\u001b[0;45mfoo\u001b[0m" + ) + expect(render(Doc.annotate(simple, Ansi.bgMagentaBright))).toBe( + "\u001b[0;105mfoo\u001b[0m" + ) }) it("cyan", () => { - expect(AnsiRender.prettyDefault(foreground(Color.cyan))).toBe("\u001b[0;96mfoo\u001b[0m") - expect(AnsiRender.prettyDefault(dullForeground(Color.cyan))).toBe("\u001b[0;36mfoo\u001b[0m") - expect(AnsiRender.prettyDefault(background(Color.cyan))).toBe("\u001b[0;106mfoo\u001b[0m") - expect(AnsiRender.prettyDefault(dullBackground(Color.cyan))).toBe("\u001b[0;46mfoo\u001b[0m") + expect(render(Doc.annotate(simple, Ansi.cyan))).toBe( + "\u001b[0;36mfoo\u001b[0m" + ) + expect(render(Doc.annotate(simple, Ansi.cyanBright))).toBe( + "\u001b[0;96mfoo\u001b[0m" + ) + expect(render(Doc.annotate(simple, Ansi.bgCyan))).toBe( + "\u001b[0;46mfoo\u001b[0m" + ) + expect(render(Doc.annotate(simple, Ansi.bgCyanBright))).toBe( + "\u001b[0;106mfoo\u001b[0m" + ) }) it("white", () => { - expect(AnsiRender.prettyDefault(foreground(Color.white))).toBe("\u001b[0;97mfoo\u001b[0m") - expect(AnsiRender.prettyDefault(dullForeground(Color.white))).toBe("\u001b[0;37mfoo\u001b[0m") - expect(AnsiRender.prettyDefault(background(Color.white))).toBe("\u001b[0;107mfoo\u001b[0m") - expect(AnsiRender.prettyDefault(dullBackground(Color.white))).toBe("\u001b[0;47mfoo\u001b[0m") + expect(render(Doc.annotate(simple, Ansi.white))).toBe( + "\u001b[0;37mfoo\u001b[0m" + ) + expect(render(Doc.annotate(simple, Ansi.whiteBright))).toBe( + "\u001b[0;97mfoo\u001b[0m" + ) + expect(render(Doc.annotate(simple, Ansi.bgWhite))).toBe( + "\u001b[0;47mfoo\u001b[0m" + ) + expect(render(Doc.annotate(simple, Ansi.bgWhiteBright))).toBe( + "\u001b[0;107mfoo\u001b[0m" + ) }) }) - describe.concurrent("Underlined", () => { + describe("Styles", () => { + it("bold", () => { + expect(render(Doc.annotate(simple, Ansi.bold))).toBe( + "\u001b[0;1mfoo\u001b[0m" + ) + }) + + it("italicized", () => { + expect(render(Doc.annotate(simple, Ansi.italicized))).toBe( + "\u001b[0;3mfoo\u001b[0m" + ) + }) + + it("strikethrough", () => { + expect(render(Doc.annotate(simple, Ansi.strikethrough))).toBe( + "\u001b[0;9mfoo\u001b[0m" + ) + }) + it("underlined", () => { - const doc = Doc.annotate(Doc.text("foo"), AnsiStyle.underlined) - expect(AnsiRender.prettyDefault(doc)).toBe("\u001b[0;4mfoo\u001b[0m") + expect(render(Doc.annotate(simple, Ansi.underlined))).toBe( + "\u001b[0;4mfoo\u001b[0m" + ) }) }) - describe.concurrent("Bold", () => { - it("bold", () => { - const doc = Doc.annotate(Doc.text("foo"), AnsiStyle.bold) - expect(AnsiRender.prettyDefault(doc)).toBe("\u001b[0;1mfoo\u001b[0m") + describe("Commands", () => { + it("should render a beep", () => { + expect(render(Doc.beep)).toBe("\u001b[0m\u0007\u001b[0m") + }) + + it("should move the cursor to the specified row and column", () => { + expect(render(Doc.cursorTo(1))).toBe("\u001b[0m\u001b[2G\u001b[0m") + expect(render(Doc.cursorTo(1, 1))).toBe("\u001b[0m\u001b[2;2H\u001b[0m") + }) + + it("should move the cursor to the specified row and column relative to the current cursor position", () => { + expect(render(Doc.cursorMove(1))).toBe( + "\u001b[0m\u001b[1C\u001b[0m" + ) + expect(render(Doc.cursorMove(1, 1))).toBe( + "\u001b[0m\u001b[1B\u001b[1C\u001b[0m" + ) + }) + + it("should move the cursor up by the specified number of lines", () => { + expect(render(Doc.cursorUp(2))).toBe("\u001b[0m\u001b[2A\u001b[0m") + }) + + it("should move the cursor down by the specified number of lines", () => { + expect(render(Doc.cursorDown(2))).toBe("\u001b[0m\u001b[2B\u001b[0m") + }) + + it("should move the cursor forward by the specified number of columns", () => { + expect(render(Doc.cursorForward(2))).toBe("\u001b[0m\u001b[2C\u001b[0m") + }) + + it("should move the cursor backward by the specified number of columns", () => { + expect(render(Doc.cursorBackward(2))).toBe("\u001b[0m\u001b[2D\u001b[0m") + }) + + it("should move the cursor all the way to the left", () => { + expect(render(Doc.cursorLeft)).toBe("\u001b[0m\u001b[G\u001b[0m") + }) + + it("should save the current cursor position", () => { + expect(render(Doc.cursorSavePosition)).toBe("\u001b[0m\u001b[s\u001b[0m") + }) + + it("should restore the current cursor position", () => { + expect(render(Doc.cursorRestorePosition)).toBe("\u001b[0m\u001b[u\u001b[0m") + }) + + it("should move the cursor down to the beginning of the next row specified", () => { + expect(render(Doc.cursorNextLine())).toBe("\u001b[0m\u001b[1E\u001b[0m") + expect(render(Doc.cursorNextLine(2))).toBe("\u001b[0m\u001b[2E\u001b[0m") + }) + + it("should move the cursor up to the beginning of the previous row specified", () => { + expect(render(Doc.cursorPrevLine())).toBe("\u001b[0m\u001b[1F\u001b[0m") + expect(render(Doc.cursorPrevLine(2))).toBe("\u001b[0m\u001b[2F\u001b[0m") + }) + + it("should hide the cursor", () => { + expect(render(Doc.cursorHide)).toBe("\u001b[0m\u001b[?25l\u001b[0m") + }) + + it("should show the cursor", () => { + expect(render(Doc.cursorShow)).toBe("\u001b[0m\u001b[?25h\u001b[0m") + }) + + it("should erase the specified number of rows above the current cursor", () => { + expect(render(Doc.eraseLines(2))).toBe( + "\u001b[0m\u001b[2K\u001b[1A\u001b[2K\u001b[G\u001b[0m" + ) + }) + + it("should erase from the current cursor position to the end of the current line", () => { + expect(render(Doc.eraseEndLine)).toBe( + "\u001b[0m\u001b[K\u001b[0m" + ) + }) + + it("should erase from the current cursor position to the beginning of the current line", () => { + expect(render(Doc.eraseStartLine)).toBe( + "\u001b[0m\u001b[1K\u001b[0m" + ) + }) + + it("should erase the current line", () => { + expect(render(Doc.eraseLine)).toBe( + "\u001b[0m\u001b[2K\u001b[0m" + ) + }) + + it("should erase from the current cursor position to the end of the screen", () => { + expect(render(Doc.eraseDown)).toBe( + "\u001b[0m\u001b[J\u001b[0m" + ) + }) + + it("should erase from the current cursor position to the beginning of the screen", () => { + expect(render(Doc.eraseUp)).toBe( + "\u001b[0m\u001b[1J\u001b[0m" + ) + }) + + it("should erase the entire screen", () => { + expect(render(Doc.eraseScreen)).toBe( + "\u001b[0m\u001b[2J\u001b[0m" + ) }) }) - describe.concurrent("Complex Example", () => { + describe("Complex Example", () => { it("should combine annotations appropriately", () => { - expect(AnsiRender.prettyDefault(complex)).toBe(String.stripMargin( - `|\u001b[0;91mred \u001b[0;94;4mblue+u \u001b[0;94;1;4mbold\u001b[0;94;4m blue+u\u001b[0;91m + expect(render(complex)).toBe(String.stripMargin( + `|\u001b[0;31mred \u001b[0;34;4mblue+u \u001b[0;34;1;4mbold\u001b[0;34;4m blue+u\u001b[0;31m | red\u001b[0m` )) }) }) - describe.concurrent("Annotations", () => { + describe("Annotations", () => { it("should re-annotate a document", () => { - const doc = Doc.map(complex, (style) => AnsiStyle.combine(AnsiStyle.backgroundColor(Color.white), style)) - expect(AnsiRender.prettyDefault(doc)).toBe(String.stripMargin( - `|\u001b[0;91;107mred \u001b[0;94;107;4mblue+u \u001b[0;94;107;1;4mbold\u001b[0;94;107;4m blue+u\u001b[0;91;107m - | red\u001b[0m` + const doc = Doc.map(complex, (style) => Ansi.combine(Ansi.bgColor(Color.white), style)) + expect(render(doc)).toBe(String.stripMargin( + `|\u001b[0;31;47mred \u001b[0;34;47;4mblue+u \u001b[0;34;47;1;4mbold\u001b[0;34;47;4m blue+u\u001b[0;31;47m + | red\u001b[0m` )) }) it("should alter existing annotations", () => { - const doc = Doc.alterAnnotations(complex, () => [AnsiStyle.bold, AnsiStyle.color(Color.green)]) - expect(AnsiRender.prettyDefault(doc)).toBe(String.stripMargin( - `|\u001b[0;1m\u001b[0;92;1mred \u001b[0;92;1m\u001b[0;92;1mblue+u \u001b[0;92;1m\u001b[0;92;1mbold\u001b[0;1m\u001b[0;92m blue+u\u001b[0;1m\u001b[0;92m + const doc = Doc.alterAnnotations(complex, () => [Ansi.bold, Ansi.color(Color.green)]) + expect(render(doc)).toBe(String.stripMargin( + `|\u001b[0;1m\u001b[0;32;1mred \u001b[0;32;1m\u001b[0;32;1mblue+u \u001b[0;32;1m\u001b[0;32;1mbold\u001b[0;1m\u001b[0;32m blue+u\u001b[0;1m\u001b[0;32m | red\u001b[0;1m\u001b[0m` )) }) it("should remove all annotations", () => { const doc = Doc.unAnnotate(complex) - expect(AnsiRender.prettyDefault(doc)).toBe(String.stripMargin( + expect(render(doc)).toBe(String.stripMargin( `|red blue+u bold blue+u | red` )) diff --git a/packages/printer/src/Doc.ts b/packages/printer/src/Doc.ts index 2aa21bc..223abdf 100644 --- a/packages/printer/src/Doc.ts +++ b/packages/printer/src/Doc.ts @@ -23,6 +23,7 @@ import type { Monoid } from "@effect/typeclass/Monoid" import type { Semigroup } from "@effect/typeclass/Semigroup" import type { Equal } from "effect/Equal" import type { TypeLambda } from "effect/HKT" +import type { Pipeable } from "effect/Pipeable" import type { Flatten } from "./Flatten.js" import * as internal from "./internal/doc.js" import type { PageWidth } from "./PageWidth.js" @@ -73,7 +74,7 @@ export declare namespace Doc { * @since 1.0.0 * @category model */ - export interface Variance extends Equal { + export interface Variance extends Equal, Pipeable { readonly [DocTypeId]: { readonly _A: () => A } @@ -1700,7 +1701,11 @@ export const indent: { * @category alignment */ export const encloseSep: { - (left: Doc, right: Doc, sep: Doc): (docs: Iterable>) => Doc + ( + left: Doc, + right: Doc, + sep: Doc + ): (docs: Iterable>) => Doc (docs: Iterable>, left: Doc, right: Doc, sep: Doc): Doc } = internal.encloseSep @@ -1884,7 +1889,8 @@ export const flatten: (self: Doc) => Doc = internal.flatten * @since 1.0.0 * @category flattening */ -export const changesUponFlattening: (self: Doc) => Flatten> = internal.changesUponFlattening +export const changesUponFlattening: (self: Doc) => Flatten> = + internal.changesUponFlattening // ----------------------------------------------------------------------------- // Annotations diff --git a/packages/printer/src/DocStream.ts b/packages/printer/src/DocStream.ts index 9c9152f..7d1d1ba 100644 --- a/packages/printer/src/DocStream.ts +++ b/packages/printer/src/DocStream.ts @@ -176,7 +176,8 @@ export const isDocStream: (u: unknown) => u is DocStream = internal.isD * @since 1.0.0 * @category refinements */ -export const isFailedStream: (self: DocStream) => self is FailedStream = internal.isFailedStream +export const isFailedStream: (self: DocStream) => self is FailedStream = + internal.isFailedStream /** * Returns `true` if the specified `DocStream` is a `EmptyStream`, `false` otherwise. @@ -184,7 +185,8 @@ export const isFailedStream: (self: DocStream) => self is FailedStream * @since 1.0.0 * @category refinements */ -export const isEmptyStream: (self: DocStream) => self is EmptyStream = internal.isEmptyStream +export const isEmptyStream: (self: DocStream) => self is EmptyStream = + internal.isEmptyStream /** * Returns `true` if the specified `DocStream` is a `CharStream`, `false` otherwise. diff --git a/packages/printer/src/DocTree.ts b/packages/printer/src/DocTree.ts index c55b7e0..12df7c2 100644 --- a/packages/printer/src/DocTree.ts +++ b/packages/printer/src/DocTree.ts @@ -180,7 +180,8 @@ export const isLineTree: (self: DocTree) => self is LineTree = internal * @since 1.0.0 * @category refinements */ -export const isAnnotationTree: (self: DocTree) => self is AnnotationTree = internal.isAnnotationTree +export const isAnnotationTree: (self: DocTree) => self is AnnotationTree = + internal.isAnnotationTree /** * Returns `true` if the specified `DocTree` is an `ConcatTree`, `false` otherwise. diff --git a/packages/printer/src/PageWidth.ts b/packages/printer/src/PageWidth.ts index 53fae35..42e1b2c 100644 --- a/packages/printer/src/PageWidth.ts +++ b/packages/printer/src/PageWidth.ts @@ -96,7 +96,8 @@ export const isPageWidth: (u: unknown) => u is PageWidth = internal.isPageWidth * @since 1.0.0 * @category refinements */ -export const isAvailablePerLine: (self: PageWidth) => self is AvailablePerLine = internal.isAvailablePerLine +export const isAvailablePerLine: (self: PageWidth) => self is AvailablePerLine = + internal.isAvailablePerLine /** * Returns `true` if the specified `PageWidth` is an `Unbounded`, `false` @@ -115,7 +116,8 @@ export const isUnbounded: (self: PageWidth) => self is Unbounded = internal.isUn * @since 1.0.0 * @category constructors */ -export const availablePerLine: (lineWidth: number, ribbonFraction: number) => PageWidth = internal.availablePerLine +export const availablePerLine: (lineWidth: number, ribbonFraction: number) => PageWidth = + internal.availablePerLine /** * @since 1.0.0 diff --git a/packages/printer/src/internal/doc.ts b/packages/printer/src/internal/doc.ts index 22c19fa..2689a97 100644 --- a/packages/printer/src/internal/doc.ts +++ b/packages/printer/src/internal/doc.ts @@ -6,6 +6,7 @@ import * as Effect from "effect/Effect" import * as Equal from "effect/Equal" import { dual, pipe } from "effect/Function" import * as Hash from "effect/Hash" +import { pipeArguments } from "effect/Pipeable" import * as ReadonlyArray from "effect/ReadonlyArray" import type * as Doc from "../Doc.js" import type * as Flatten from "../Flatten.js" @@ -18,31 +19,47 @@ const DocSymbolKey = "@effect/printer/Doc" export const DocTypeId: Doc.DocTypeId = Symbol.for(DocSymbolKey) as Doc.DocTypeId const protoHash = { - Fail: (_: Doc.Fail) => Hash.combine(Hash.hash(DocSymbolKey))(Hash.hash("@effect/printer/Doc/Fail")), - Empty: (_: Doc.Empty) => Hash.combine(Hash.hash(DocSymbolKey))(Hash.hash("@effect/printer/Doc/Empty")), + Fail: (_: Doc.Fail) => + Hash.combine(Hash.hash(DocSymbolKey))(Hash.hash("@effect/printer/Doc/Fail")), + Empty: (_: Doc.Empty) => + Hash.combine(Hash.hash(DocSymbolKey))(Hash.hash("@effect/printer/Doc/Empty")), Char: (self: Doc.Char) => Hash.combine(Hash.hash(DocSymbolKey))(Hash.string(self.char)), Text: (self: Doc.Text) => Hash.combine(Hash.hash(DocSymbolKey))(Hash.string(self.text)), - Line: (_: Doc.Line) => Hash.combine(Hash.hash(DocSymbolKey))(Hash.hash("@effect/printer/Doc/Line")), + Line: (_: Doc.Line) => + Hash.combine(Hash.hash(DocSymbolKey))(Hash.hash("@effect/printer/Doc/Line")), FlatAlt: (self: Doc.FlatAlt) => - Hash.combine(Hash.hash(DocSymbolKey))(Hash.combine(Hash.hash(self.left))(Hash.hash(self.right))), + Hash.combine(Hash.hash(DocSymbolKey))( + Hash.combine(Hash.hash(self.left))(Hash.hash(self.right)) + ), Cat: (self: Doc.Cat) => - Hash.combine(Hash.hash(DocSymbolKey))(Hash.combine(Hash.hash(self.left))(Hash.hash(self.right))), + Hash.combine(Hash.hash(DocSymbolKey))( + Hash.combine(Hash.hash(self.left))(Hash.hash(self.right)) + ), Nest: (self: Doc.Nest) => - Hash.combine(Hash.hash(DocSymbolKey))(Hash.combine(Hash.hash(self.indent))(Hash.hash(self.doc))), + Hash.combine(Hash.hash(DocSymbolKey))( + Hash.combine(Hash.hash(self.indent))(Hash.hash(self.doc)) + ), Union: (self: Doc.Union) => - Hash.combine(Hash.hash(DocSymbolKey))(Hash.combine(Hash.hash(self.left))(Hash.hash(self.right))), + Hash.combine(Hash.hash(DocSymbolKey))( + Hash.combine(Hash.hash(self.left))(Hash.hash(self.right)) + ), Column: (self: Doc.Column) => Hash.combine(Hash.hash(DocSymbolKey))(Hash.hash(self.react)), - WithPageWidth: (self: Doc.WithPageWidth) => Hash.combine(Hash.hash(DocSymbolKey))(Hash.hash(self.react)), + WithPageWidth: (self: Doc.WithPageWidth) => + Hash.combine(Hash.hash(DocSymbolKey))(Hash.hash(self.react)), Nesting: (self: Doc.Nesting) => Hash.combine(Hash.hash(DocSymbolKey))(Hash.hash(self.react)), Annotated: (self: Doc.Annotated) => - Hash.combine(Hash.hash(DocSymbolKey))(Hash.combine(Hash.hash(self.annotation))(Hash.hash(self.doc))) + Hash.combine(Hash.hash(DocSymbolKey))( + Hash.combine(Hash.hash(self.annotation))(Hash.hash(self.doc)) + ) } as const const protoEqual = { Fail: (_: Doc.Fail, that: unknown) => isDoc(that) && that._tag === "Fail", Empty: (_: Doc.Empty, that: unknown) => isDoc(that) && that._tag === "Empty", - Char: (self: Doc.Char, that: unknown) => isDoc(that) && that._tag === "Char" && self.char === that.char, - Text: (self: Doc.Text, that: unknown) => isDoc(that) && that._tag === "Text" && self.text === that.text, + Char: (self: Doc.Char, that: unknown) => + isDoc(that) && that._tag === "Char" && self.char === that.char, + Text: (self: Doc.Text, that: unknown) => + isDoc(that) && that._tag === "Text" && self.text === that.text, Line: (_: Doc.Line, that: unknown) => isDoc(that) && that._tag === "Line", FlatAlt: (self: Doc.FlatAlt, that: unknown) => isDoc(that) && @@ -90,6 +107,9 @@ const proto = { }, [Equal.symbol](this: Doc.Doc, that: unknown): boolean { return protoEqual[this._tag](this as any, that as any) + }, + pipe() { + return pipeArguments(this, arguments) } } @@ -98,7 +118,8 @@ const proto = { // ----------------------------------------------------------------------------- /** @internal */ -export const isDoc = (u: unknown): u is Doc.Doc => typeof u === "object" && u != null && DocTypeId in u +export const isDoc = (u: unknown): u is Doc.Doc => + typeof u === "object" && u != null && DocTypeId in u /** @internal */ export const isFail = (self: Doc.Doc): self is Doc.Fail => self._tag === "Fail" @@ -131,13 +152,15 @@ export const isUnion = (self: Doc.Doc): self is Doc.Union => self._tag export const isColumn = (self: Doc.Doc): self is Doc.Column => self._tag === "Column" /** @internal */ -export const isWithPageWidth = (self: Doc.Doc): self is Doc.WithPageWidth => self._tag === "WithPageWidth" +export const isWithPageWidth = (self: Doc.Doc): self is Doc.WithPageWidth => + self._tag === "WithPageWidth" /** @internal */ export const isNesting = (self: Doc.Doc): self is Doc.Nesting => self._tag === "Nesting" /** @internal */ -export const isAnnotated = (self: Doc.Doc): self is Doc.Annotated => self._tag === "Annotated" +export const isAnnotated = (self: Doc.Doc): self is Doc.Annotated => + self._tag === "Annotated" // ----------------------------------------------------------------------------- // Primitives @@ -320,8 +343,13 @@ export const catWithSpace = dual< /** @internal */ export const concatWith = dual< - (f: (left: Doc.Doc, right: Doc.Doc) => Doc.Doc) => (docs: Iterable>) => Doc.Doc, - (docs: Iterable>, f: (left: Doc.Doc, right: Doc.Doc) => Doc.Doc) => Doc.Doc + ( + f: (left: Doc.Doc, right: Doc.Doc) => Doc.Doc + ) => (docs: Iterable>) => Doc.Doc, + ( + docs: Iterable>, + f: (left: Doc.Doc, right: Doc.Doc) => Doc.Doc + ) => Doc.Doc >(2, (docs, f) => { const documents = ReadonlyArray.fromIterable(docs) if (ReadonlyArray.isNonEmptyReadonlyArray(documents)) { @@ -337,7 +365,8 @@ export const vcat = (docs: Iterable>): Doc.Doc => concatWith(docs, (left, right) => catWithLineBreak(left, right)) /** @internal */ -export const hcat = (docs: Iterable>): Doc.Doc => concatWith(docs, (left, right) => cat(left, right)) +export const hcat = (docs: Iterable>): Doc.Doc => + concatWith(docs, (left, right) => cat(left, right)) /** @internal */ export const fillCat = (docs: Iterable>): Doc.Doc => @@ -585,7 +614,9 @@ const flattenSafe = (self: Doc.Doc): Effect.Effect(self: Doc.Doc): Flatten.Flatten> => Effect.runSync(changesUponFlatteningSafe(self)) -const changesUponFlatteningSafe = (self: Doc.Doc): Effect.Effect>> => { +const changesUponFlatteningSafe = ( + self: Doc.Doc +): Effect.Effect>> => { switch (self._tag) { case "Fail": { return Effect.succeed(_flatten.neverFlat) @@ -633,7 +664,9 @@ const changesUponFlatteningSafe = (self: Doc.Doc): Effect.Effect flatten(self.react(position))))) } case "WithPageWidth": { - return Effect.succeed(_flatten.flattened(pageWidth((pageWidth) => flatten(self.react(pageWidth))))) + return Effect.succeed( + _flatten.flattened(pageWidth((pageWidth) => flatten(self.react(pageWidth)))) + ) } case "Nesting": { return Effect.succeed(_flatten.flattened(nesting((level) => flatten(self.react(level))))) @@ -705,7 +738,9 @@ const alterAnnotationsSafe = ( ) } case "Column": { - return Effect.succeed(column((position) => Effect.runSync(alterAnnotationsSafe(self.react(position), f)))) + return Effect.succeed( + column((position) => Effect.runSync(alterAnnotationsSafe(self.react(position), f))) + ) } case "WithPageWidth": { return Effect.succeed( @@ -713,7 +748,9 @@ const alterAnnotationsSafe = ( ) } case "Nesting": { - return Effect.succeed(nesting((level) => Effect.runSync(alterAnnotationsSafe(self.react(level), f)))) + return Effect.succeed( + nesting((level) => Effect.runSync(alterAnnotationsSafe(self.react(level), f))) + ) } case "Annotated": { return Effect.map(alterAnnotationsSafe(self.doc, f), (doc) => @@ -812,7 +849,8 @@ export const parenthesized = (self: Doc.Doc): Doc.Doc => surround(self, export const angleBracketed = (self: Doc.Doc): Doc.Doc => surround(self, langle, rangle) /** @internal */ -export const squareBracketed = (self: Doc.Doc): Doc.Doc => surround(self, lbracket, rbracket) +export const squareBracketed = (self: Doc.Doc): Doc.Doc => + surround(self, lbracket, rbracket) /** @internal */ export const curlyBraced = (self: Doc.Doc): Doc.Doc => surround(self, lbrace, rbrace) @@ -829,7 +867,8 @@ export const spaces = (n: number): Doc.Doc => { } /** @internal */ -export const words = (s: string, char = " "): ReadonlyArray> => s.split(char).map(string) +export const words = (s: string, char = " "): ReadonlyArray> => + s.split(char).map(string) /** @internal */ export const reflow = (s: string, char = " "): Doc.Doc => fillSep(words(s, char)) diff --git a/packages/printer/src/internal/docStream.ts b/packages/printer/src/internal/docStream.ts index e6acb44..6217ac8 100644 --- a/packages/printer/src/internal/docStream.ts +++ b/packages/printer/src/internal/docStream.ts @@ -16,7 +16,9 @@ import type * as DocStream from "../DocStream.js" const DocStreamSymbolKey = "@effect/printer/DocStream" /** @internal */ -export const DocStreamTypeId: DocStream.DocStreamTypeId = Symbol.for(DocStreamSymbolKey) as DocStream.DocStreamTypeId +export const DocStreamTypeId: DocStream.DocStreamTypeId = Symbol.for( + DocStreamSymbolKey +) as DocStream.DocStreamTypeId const protoHash = { FailedStream: (_: DocStream.FailedStream) => @@ -65,8 +67,10 @@ const protoHash = { } const protoEqual = { - FailedStream: (self: DocStream.FailedStream, that: unknown) => isDocStream(that) && that._tag === "FailedStream", - EmptyStream: (self: DocStream.EmptyStream, that: unknown) => isDocStream(that) && that._tag === "EmptyStream", + FailedStream: (self: DocStream.FailedStream, that: unknown) => + isDocStream(that) && that._tag === "FailedStream", + EmptyStream: (self: DocStream.EmptyStream, that: unknown) => + isDocStream(that) && that._tag === "EmptyStream", CharStream: (self: DocStream.CharStream, that: unknown) => isDocStream(that) && that._tag === "CharStream" && @@ -111,8 +115,9 @@ export const isDocStream = (u: unknown): u is DocStream.DocStream => typeof u === "object" && u != null && DocStreamTypeId in u /** @internal */ -export const isFailedStream = (self: DocStream.DocStream): self is DocStream.FailedStream => - self._tag === "FailedStream" +export const isFailedStream = ( + self: DocStream.DocStream +): self is DocStream.FailedStream => self._tag === "FailedStream" /** @internal */ export const isEmptyStream = (self: DocStream.DocStream): self is DocStream.EmptyStream => @@ -131,12 +136,14 @@ export const isLineStream = (self: DocStream.DocStream): self is DocStream self._tag === "LineStream" /** @internal */ -export const isPushAnnotationStream = (self: DocStream.DocStream): self is DocStream.PushAnnotationStream => - self._tag === "PushAnnotationStream" +export const isPushAnnotationStream = ( + self: DocStream.DocStream +): self is DocStream.PushAnnotationStream => self._tag === "PushAnnotationStream" /** @internal */ -export const isPopAnnotationStream = (self: DocStream.DocStream): self is DocStream.PopAnnotationStream => - self._tag === "PopAnnotationStream" +export const isPopAnnotationStream = ( + self: DocStream.DocStream +): self is DocStream.PopAnnotationStream => self._tag === "PopAnnotationStream" // ----------------------------------------------------------------------------- // Constructors @@ -256,7 +263,9 @@ const alterAnnotationSafe = ( const altered = f(self.annotation) if (Option.isSome(altered)) { return Effect.map( - Effect.suspend(() => alterAnnotationSafe(self.stream, f, List.prepend(stack, DontRemove))), + Effect.suspend(() => + alterAnnotationSafe(self.stream, f, List.prepend(stack, DontRemove)) + ), pushAnnotation(altered.value) ) } @@ -331,7 +340,9 @@ const reAnnotateSafe = ( export const unAnnotate = (self: DocStream.DocStream): DocStream.DocStream => Effect.runSync(unAnnotateSafe(self)) -const unAnnotateSafe = (self: DocStream.DocStream): Effect.Effect> => { +const unAnnotateSafe = ( + self: DocStream.DocStream +): Effect.Effect> => { switch (self._tag) { case "CharStream": { return Effect.map( diff --git a/packages/printer/src/internal/docTree.ts b/packages/printer/src/internal/docTree.ts index 8686773..26213fc 100644 --- a/packages/printer/src/internal/docTree.ts +++ b/packages/printer/src/internal/docTree.ts @@ -20,7 +20,9 @@ import * as docTreeToken from "./docTreeToken.js" const DocTreeSymbolKey = "@effect/printer/DocTree" /** @internal */ -export const DocTreeTypeId: DocTree.DocTreeTypeId = Symbol.for(DocTreeSymbolKey) as DocTree.DocTreeTypeId +export const DocTreeTypeId: DocTree.DocTreeTypeId = Symbol.for( + DocTreeSymbolKey +) as DocTree.DocTreeTypeId const protoHash = { EmptyTree: (_: DocTree.EmptyTree) => @@ -62,7 +64,8 @@ const protoHash = { } const protoEqual = { - EmptyTree: (_: DocTree.EmptyTree, that: unknown) => isDocTree(that) && that._tag === "EmptyTree", + EmptyTree: (_: DocTree.EmptyTree, that: unknown) => + isDocTree(that) && that._tag === "EmptyTree", CharTree: (self: DocTree.CharTree, that: unknown) => isDocTree(that) && that._tag === "CharTree" && self.char === that.char, TextTree: (self: DocTree.TextTree, that: unknown) => @@ -99,23 +102,28 @@ export const isDocTree = (u: unknown): u is DocTree.DocTree => typeof u === "object" && u != null && DocTreeTypeId in u /** @internal */ -export const isEmptyTree = (self: DocTree.DocTree): self is DocTree.EmptyTree => self._tag === "EmptyTree" +export const isEmptyTree = (self: DocTree.DocTree): self is DocTree.EmptyTree => + self._tag === "EmptyTree" /** @internal */ -export const isCharTree = (self: DocTree.DocTree): self is DocTree.CharTree => self._tag === "CharTree" +export const isCharTree = (self: DocTree.DocTree): self is DocTree.CharTree => + self._tag === "CharTree" /** @internal */ -export const isTextTree = (self: DocTree.DocTree): self is DocTree.TextTree => self._tag === "TextTree" +export const isTextTree = (self: DocTree.DocTree): self is DocTree.TextTree => + self._tag === "TextTree" /** @internal */ -export const isLineTree = (self: DocTree.DocTree): self is DocTree.LineTree => self._tag === "LineTree" +export const isLineTree = (self: DocTree.DocTree): self is DocTree.LineTree => + self._tag === "LineTree" /** @internal */ export const isAnnotationTree = (self: DocTree.DocTree): self is DocTree.AnnotationTree => self._tag === "AnnotationTree" /** @internal */ -export const isConcatTree = (self: DocTree.DocTree): self is DocTree.ConcatTree => self._tag === "ConcatTree" +export const isConcatTree = (self: DocTree.DocTree): self is DocTree.ConcatTree => + self._tag === "ConcatTree" // ----------------------------------------------------------------------------- // Constructors @@ -222,7 +230,8 @@ export const reAnnotate = dual< >(2, (self, f) => alterAnnotations(self, (a) => [f(a)])) /** @internal */ -export const unAnnotate = (self: DocTree.DocTree): DocTree.DocTree => alterAnnotations(self, () => []) +export const unAnnotate = (self: DocTree.DocTree): DocTree.DocTree => + alterAnnotations(self, () => []) // ----------------------------------------------------------------------------- // Folding @@ -308,7 +317,9 @@ const renderSimplyDecoratedSafe = ( return Effect.succeed(renderText(self.text)) } case "LineTree": { - return Effect.succeed(M.combine(renderText("\n"), renderText(doc.textSpaces(self.indentation)))) + return Effect.succeed( + M.combine(renderText("\n"), renderText(doc.textSpaces(self.indentation))) + ) } case "AnnotationTree": { return Effect.map( @@ -371,14 +382,16 @@ interface DocTreeParser { (stream: S): Option.Option } -const parserSucceed = (value: A): DocTreeParser => (stream) => Option.some([value, stream] as const) +const parserSucceed = (value: A): DocTreeParser => (stream) => + Option.some([value, stream] as const) -const parserMap = (self: DocTreeParser, f: (a: A) => B): DocTreeParser => (stream) => - Option.map(self(stream), ([a, s]) => [f(a), s] as const) +const parserMap = + (self: DocTreeParser, f: (a: A) => B): DocTreeParser => (stream) => + Option.map(self(stream), ([a, s]) => [f(a), s] as const) const parserFlatMap = - (self: DocTreeParser, f: (a: A) => DocTreeParser): DocTreeParser => (stream) => - Option.flatMap(self(stream), ([a, s1]) => f(a)(s1)) + (self: DocTreeParser, f: (a: A) => DocTreeParser): DocTreeParser => + (stream) => Option.flatMap(self(stream), ([a, s1]) => f(a)(s1)) function many(parser: DocTreeParser): DocTreeParser> { return (stream) => diff --git a/packages/printer/src/internal/flatten.ts b/packages/printer/src/internal/flatten.ts index 7a7e871..d65cdca 100644 --- a/packages/printer/src/internal/flatten.ts +++ b/packages/printer/src/internal/flatten.ts @@ -10,12 +10,17 @@ import type * as Flatten from "../Flatten.js" const FlattenSymbolKey = "@effect/printer/Flatten" /** @internal */ -export const FlattenTypeId: Flatten.FlattenTypeId = Symbol.for(FlattenSymbolKey) as Flatten.FlattenTypeId +export const FlattenTypeId: Flatten.FlattenTypeId = Symbol.for( + FlattenSymbolKey +) as Flatten.FlattenTypeId const protoHash = { - Flattened: (self: Flatten.Flattened) => Hash.combine(Hash.hash(self.value))(Hash.string(FlattenSymbolKey)), + Flattened: (self: Flatten.Flattened) => + Hash.combine(Hash.hash(self.value))(Hash.string(FlattenSymbolKey)), AlreadyFlat: (_: Flatten.AlreadyFlat) => - Hash.combine(Hash.string("@effect/printer/Flattened/AlreadyFlat"))(Hash.string(FlattenSymbolKey)), + Hash.combine(Hash.string("@effect/printer/Flattened/AlreadyFlat"))( + Hash.string(FlattenSymbolKey) + ), NeverFlat: (_: Flatten.AlreadyFlat) => Hash.combine(Hash.string("@effect/printer/Flattened/NeverFlat"))(Hash.string(FlattenSymbolKey)) } @@ -23,8 +28,10 @@ const protoHash = { const protoEqual = { Flattened: (self: Flatten.Flattened, that: unknown) => isFlatten(that) && that._tag === "Flattened" && Equal.equals(self.value, that.value), - AlreadyFlat: (_: Flatten.AlreadyFlat, that: unknown) => isFlatten(that) && that._tag === "AlreadyFlat", - NeverFlat: (_: Flatten.AlreadyFlat, that: unknown) => isFlatten(that) && that._tag === "NeverFlat" + AlreadyFlat: (_: Flatten.AlreadyFlat, that: unknown) => + isFlatten(that) && that._tag === "AlreadyFlat", + NeverFlat: (_: Flatten.AlreadyFlat, that: unknown) => + isFlatten(that) && that._tag === "NeverFlat" } const proto = { @@ -46,14 +53,16 @@ export const isFlatten = (u: unknown): u is Flatten.Flatten => typeof u === "object" && u != null && "_id" in u && FlattenTypeId in u /** @internal */ -export const isFlattened = (self: Flatten.Flatten): self is Flatten.Flattened => self._tag === "Flattened" +export const isFlattened = (self: Flatten.Flatten): self is Flatten.Flattened => + self._tag === "Flattened" /** @internal */ export const isAlreadyFlat = (self: Flatten.Flatten): self is Flatten.AlreadyFlat => self._tag === "AlreadyFlat" /** @internal */ -export const isNeverFlat = (a: Flatten.Flatten): a is Flatten.NeverFlat => a._tag === "NeverFlat" +export const isNeverFlat = (a: Flatten.Flatten): a is Flatten.NeverFlat => + a._tag === "NeverFlat" // ----------------------------------------------------------------------------- // Constructors diff --git a/packages/printer/src/internal/layout.ts b/packages/printer/src/internal/layout.ts index f336f69..b8d1282 100644 --- a/packages/printer/src/internal/layout.ts +++ b/packages/printer/src/internal/layout.ts @@ -33,7 +33,8 @@ export const wadlerLeijen = dual< ) => DocStream.DocStream >( 3, - (self, fits, options) => Effect.runSync(wadlerLeijenSafe(0, 0, pipeline.cons(0, self, pipeline.nil), fits, options)) + (self, fits, options) => + Effect.runSync(wadlerLeijenSafe(0, 0, pipeline.cons(0, self, pipeline.nil), fits, options)) ) const wadlerLeijenSafe = ( @@ -107,7 +108,11 @@ const wadlerLeijenSafe = ( return Effect.suspend(() => wadlerLeijenSafe(nl, cc, layoutPipeline, fits, options)) } case "WithPageWidth": { - const layoutPipeline = pipeline.cons(x.indent, x.document.react(options.pageWidth), x.pipeline) + const layoutPipeline = pipeline.cons( + x.indent, + x.document.react(options.pageWidth), + x.pipeline + ) return Effect.suspend(() => wadlerLeijenSafe(nl, cc, layoutPipeline, fits, options)) } case "Nesting": { @@ -116,7 +121,11 @@ const wadlerLeijenSafe = ( } case "Annotated": { const annotation = x.document.annotation - const layoutPipeline = pipeline.cons(x.indent, x.document.doc, pipeline.undoAnnotation(x.pipeline)) + const layoutPipeline = pipeline.cons( + x.indent, + x.document.doc, + pipeline.undoAnnotation(x.pipeline) + ) return Effect.map( Effect.suspend(() => wadlerLeijenSafe(nl, cc, layoutPipeline, fits, options)), (stream) => docStream.pushAnnotation(annotation)(stream) @@ -160,7 +169,8 @@ const selectNicer = ( // ----------------------------------------------------------------------------- /** @internal */ -export const compact = (self: Doc.Doc): DocStream.DocStream => Effect.runSync(compactSafe(List.of(self), 0)) +export const compact = (self: Doc.Doc): DocStream.DocStream => + Effect.runSync(compactSafe(List.of(self), 0)) const compactSafe = ( docs: List.List>, diff --git a/packages/printer/src/internal/optimize.ts b/packages/printer/src/internal/optimize.ts index 0db456e..6529739 100644 --- a/packages/printer/src/internal/optimize.ts +++ b/packages/printer/src/internal/optimize.ts @@ -10,7 +10,10 @@ export const optimize = dual< (self: Doc.Doc, depth: Optimize.Optimize.Depth) => Doc.Doc >(2, (self, depth) => Effect.runSync(optimizeSafe(self, depth))) -const optimizeSafe = (self: Doc.Doc, depth: Optimize.Optimize.Depth): Effect.Effect> => { +const optimizeSafe = ( + self: Doc.Doc, + depth: Optimize.Optimize.Depth +): Effect.Effect> => { switch (self._tag) { case "FlatAlt": { return Effect.zipWith( @@ -107,7 +110,9 @@ const optimizeSafe = (self: Doc.Doc, depth: Optimize.Optimize.Depth): Effe } if (_doc.isNest(self.doc)) { const doc = self.doc - return Effect.suspend(() => optimizeSafe(_doc.nest(doc.doc, self.indent + doc.indent), depth)) + return Effect.suspend(() => + optimizeSafe(_doc.nest(doc.doc, self.indent + doc.indent), depth) + ) } if (self.indent === 0) { return Effect.suspend(() => optimizeSafe(self.doc, depth)) diff --git a/packages/printer/src/internal/pageWidth.ts b/packages/printer/src/internal/pageWidth.ts index 13d176c..157fd9a 100644 --- a/packages/printer/src/internal/pageWidth.ts +++ b/packages/printer/src/internal/pageWidth.ts @@ -10,7 +10,9 @@ import type * as PageWidth from "../PageWidth.js" const PageWidthSymbolKey = "@effect/printer/PageWidth" /** @internal */ -export const PageWidthTypeId: PageWidth.PageWidthTypeId = Symbol.for(PageWidthSymbolKey) as PageWidth.PageWidthTypeId +export const PageWidthTypeId: PageWidth.PageWidthTypeId = Symbol.for( + PageWidthSymbolKey +) as PageWidth.PageWidthTypeId const protoHash = { AvailablePerLine: (self: PageWidth.AvailablePerLine) => @@ -33,7 +35,8 @@ const protoEqual = { that._tag === "AvailablePerLine" && self.lineWidth === that.lineWidth && self.ribbonFraction === that.ribbonFraction, - Unbounded: (self: PageWidth.Unbounded, that: unknown) => isPageWidth(that) && that._tag === "Unbounded" + Unbounded: (self: PageWidth.Unbounded, that: unknown) => + isPageWidth(that) && that._tag === "Unbounded" } const proto = { @@ -59,14 +62,18 @@ export const isAvailablePerLine = (self: PageWidth.PageWidth): self is PageWidth self._tag === "AvailablePerLine" /** @internal */ -export const isUnbounded = (self: PageWidth.PageWidth): self is PageWidth.Unbounded => self._tag === "AvailablePerLine" +export const isUnbounded = (self: PageWidth.PageWidth): self is PageWidth.Unbounded => + self._tag === "AvailablePerLine" // ----------------------------------------------------------------------------- // Constructors // ----------------------------------------------------------------------------- /** @internal */ -export const availablePerLine = (lineWidth: number, ribbonFraction: number): PageWidth.PageWidth => { +export const availablePerLine = ( + lineWidth: number, + ribbonFraction: number +): PageWidth.PageWidth => { const op = Object.create(proto) op._tag = "AvailablePerLine" op.lineWidth = lineWidth diff --git a/packages/printer/src/internal/render.ts b/packages/printer/src/internal/render.ts index f7465ae..127d101 100644 --- a/packages/printer/src/internal/render.ts +++ b/packages/printer/src/internal/render.ts @@ -64,7 +64,8 @@ export const pretty = dual< }) /** @internal */ -export const prettyDefault = (self: Doc.Doc): string => render(layout.pretty(self, layout.defaultOptions)) +export const prettyDefault = (self: Doc.Doc): string => + render(layout.pretty(self, layout.defaultOptions)) /** @internal */ export const prettyUnbounded = (self: Doc.Doc): string => @@ -81,7 +82,8 @@ export const smart = dual< }) /** @internal */ -export const smartDefault = (self: Doc.Doc): string => render(layout.smart(self, layout.defaultOptions)) +export const smartDefault = (self: Doc.Doc): string => + render(layout.smart(self, layout.defaultOptions)) /** @internal */ export const smartUnbounded = (self: Doc.Doc): string => diff --git a/packages/printer/test/doc.test.ts b/packages/printer/test/doc.test.ts index da55386..74bdefd 100644 --- a/packages/printer/test/doc.test.ts +++ b/packages/printer/test/doc.test.ts @@ -182,7 +182,9 @@ describe.concurrent("Doc", () => { case "AvailablePerLine": { const lineWidth = pageWidth.lineWidth const ribbonFraction = pageWidth.ribbonFraction - return Doc.squareBracketed(Doc.text(`Width: ${lineWidth}, Ribbon Fraction: ${ribbonFraction}`)) + return Doc.squareBracketed( + Doc.text(`Width: ${lineWidth}, Ribbon Fraction: ${ribbonFraction}`) + ) } case "Unbounded": { return Doc.empty @@ -311,12 +313,16 @@ describe.concurrent("Doc", () => { }) it("should flatten the right document", () => { - const doc = Doc.group(Doc.flatAlt(Doc.char("x"), Doc.hcat([Doc.char("y"), Doc.line, Doc.char("y")]))) + const doc = Doc.group( + Doc.flatAlt(Doc.char("x"), Doc.hcat([Doc.char("y"), Doc.line, Doc.char("y")])) + ) expect(Render.prettyDefault(doc)).toBe("y y") }) it("should never render an unflattenable `right` document", () => { - const doc = Doc.group(Doc.flatAlt(Doc.char("x"), Doc.hcat([Doc.char("y"), Doc.hardLine, Doc.char("y")]))) + const doc = Doc.group( + Doc.flatAlt(Doc.char("x"), Doc.hcat([Doc.char("y"), Doc.hardLine, Doc.char("y")])) + ) expect(Render.prettyDefault(doc)).toBe("x") }) }) @@ -638,7 +644,10 @@ describe.concurrent("Doc", () => { it("Monoid", () => { const M = Doc.getMonoid() - const doc = M.combine(M.combine(Doc.text("hello"), Doc.parenthesized(M.empty)), Doc.text("world")) + const doc = M.combine( + M.combine(Doc.text("hello"), Doc.parenthesized(M.empty)), + Doc.text("world") + ) expect(Render.prettyDefault(doc)).toBe("hello()world") }) }) diff --git a/packages/printer/test/docStream.test.ts b/packages/printer/test/docStream.test.ts index c7741b0..d9c7ccb 100644 --- a/packages/printer/test/docStream.test.ts +++ b/packages/printer/test/docStream.test.ts @@ -28,7 +28,9 @@ describe.concurrent("DocStream", () => { }) it("isPushAnnotationStream", () => { - expect(DocStream.isPushAnnotationStream(DocStream.pushAnnotation(DocStream.empty, 1))).toBe(true) + expect(DocStream.isPushAnnotationStream(DocStream.pushAnnotation(DocStream.empty, 1))).toBe( + true + ) expect(DocStream.isPushAnnotationStream(DocStream.empty)).toBe(false) }) diff --git a/packages/printer/test/docTree.test.ts b/packages/printer/test/docTree.test.ts index 3e8115b..9420dcb 100644 --- a/packages/printer/test/docTree.test.ts +++ b/packages/printer/test/docTree.test.ts @@ -97,14 +97,19 @@ describe.concurrent("DocTree", () => { readonly level: number } - const bold = (doc: Doc.Doc): Doc.Doc => Doc.annotate(doc, { _tag: "Bold" }) + const bold = (doc: Doc.Doc): Doc.Doc => + Doc.annotate(doc, { _tag: "Bold" }) - const color = (doc: Doc.Doc, color: "Red" | "Green" | "Blue"): Doc.Doc => - Doc.annotate(doc, { _tag: "Color", color }) + const color = ( + doc: Doc.Doc, + color: "Red" | "Green" | "Blue" + ): Doc.Doc => Doc.annotate(doc, { _tag: "Color", color }) - const italicized = (doc: Doc.Doc): Doc.Doc => Doc.annotate(doc, { _tag: "Italicized" }) + const italicized = (doc: Doc.Doc): Doc.Doc => + Doc.annotate(doc, { _tag: "Italicized" }) - const paragraph = (doc: Doc.Doc): Doc.Doc => Doc.annotate(doc, { _tag: "Paragraph" }) + const paragraph = (doc: Doc.Doc): Doc.Doc => + Doc.annotate(doc, { _tag: "Paragraph" }) const header = (doc: Doc.Doc, level: number): Doc.Doc => Doc.annotate(doc, { _tag: "Header", level }) @@ -143,7 +148,9 @@ describe.concurrent("DocTree", () => { } } - const renderTreeSafe = (tree: DocTree.DocTree): Effect.Effect => { + const renderTreeSafe = ( + tree: DocTree.DocTree + ): Effect.Effect => { switch (tree._tag) { case "EmptyTree": { return Effect.succeed("") @@ -183,9 +190,11 @@ describe.concurrent("DocTree", () => { } } - const renderTree = (tree: DocTree.DocTree): string => Effect.runSync(renderTreeSafe(tree)) + const renderTree = (tree: DocTree.DocTree): string => + Effect.runSync(renderTreeSafe(tree)) - const render = (stream: DocStream.DocStream): string => renderTree(DocTree.treeForm(stream)) + const render = (stream: DocStream.DocStream): string => + renderTree(DocTree.treeForm(stream)) const document = Doc.vsep([ header(Doc.text("Example document"), 1),