@logtape/pretty 1.4.0-dev.408 → 1.4.0-dev.413
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -2
- package/deno.json +0 -39
- package/src/formatter.test.ts +0 -954
- package/src/formatter.ts +0 -1070
- package/src/mod.ts +0 -17
- package/src/terminal.test.ts +0 -67
- package/src/terminal.ts +0 -94
- package/src/truncate.test.ts +0 -104
- package/src/truncate.ts +0 -90
- package/src/util.deno.ts +0 -21
- package/src/util.node.ts +0 -14
- package/src/util.ts +0 -13
- package/src/wcwidth.test.ts +0 -60
- package/src/wcwidth.ts +0 -414
- package/src/wordwrap.test.ts +0 -114
- package/src/wordwrap.ts +0 -148
- package/tsdown.config.ts +0 -24
package/src/mod.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @logtape/pretty - Beautiful console formatter for LogTape
|
|
3
|
-
*
|
|
4
|
-
* A Signale-inspired formatter designed for local development with:
|
|
5
|
-
* - Colorful icons for each log level
|
|
6
|
-
* - Smart category truncation
|
|
7
|
-
* - Dimmed styling for better readability
|
|
8
|
-
* - Perfect column alignment
|
|
9
|
-
*
|
|
10
|
-
* @module
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
export {
|
|
14
|
-
getPrettyFormatter,
|
|
15
|
-
prettyFormatter,
|
|
16
|
-
type PrettyFormatterOptions,
|
|
17
|
-
} from "./formatter.ts";
|
package/src/terminal.test.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { suite } from "@alinea/suite";
|
|
2
|
-
import { assert } from "@std/assert/assert";
|
|
3
|
-
import { assertEquals } from "@std/assert/equals";
|
|
4
|
-
import {
|
|
5
|
-
getOptimalWordWrapWidth,
|
|
6
|
-
getTerminalWidth,
|
|
7
|
-
isTerminal,
|
|
8
|
-
} from "./terminal.ts";
|
|
9
|
-
|
|
10
|
-
const test = suite(import.meta);
|
|
11
|
-
|
|
12
|
-
test("isTerminal() returns boolean", () => {
|
|
13
|
-
const result = isTerminal();
|
|
14
|
-
assert(typeof result === "boolean", "isTerminal should return a boolean");
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
test("getTerminalWidth() returns number or null", () => {
|
|
18
|
-
const result = getTerminalWidth();
|
|
19
|
-
assert(
|
|
20
|
-
result === null || (typeof result === "number" && result > 0),
|
|
21
|
-
"getTerminalWidth should return null or a positive number",
|
|
22
|
-
);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test("getOptimalWordWrapWidth() returns default when not in terminal", () => {
|
|
26
|
-
// We can't easily mock the function, so we'll test with a large default
|
|
27
|
-
// to distinguish from common terminal widths
|
|
28
|
-
const result = getOptimalWordWrapWidth(123);
|
|
29
|
-
|
|
30
|
-
assert(
|
|
31
|
-
typeof result === "number" && result > 0,
|
|
32
|
-
"getOptimalWordWrapWidth should return a positive number",
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
// If we're not in a terminal (which is likely in test environment),
|
|
36
|
-
// we should get the default value
|
|
37
|
-
if (!isTerminal()) {
|
|
38
|
-
assertEquals(result, 123, "Should return default when not in terminal");
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test("getOptimalWordWrapWidth() uses reasonable default", () => {
|
|
43
|
-
const result = getOptimalWordWrapWidth();
|
|
44
|
-
|
|
45
|
-
assert(
|
|
46
|
-
typeof result === "number" && result > 0,
|
|
47
|
-
"Should return a positive number",
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
assert(
|
|
51
|
-
result >= 20 && result <= 1000,
|
|
52
|
-
"Should return a reasonable terminal width (20-1000 columns)",
|
|
53
|
-
);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("getOptimalWordWrapWidth() respects custom default", () => {
|
|
57
|
-
const customDefault = 150;
|
|
58
|
-
const result = getOptimalWordWrapWidth(customDefault);
|
|
59
|
-
|
|
60
|
-
// If not in terminal, should return custom default
|
|
61
|
-
if (!isTerminal()) {
|
|
62
|
-
assertEquals(result, customDefault);
|
|
63
|
-
} else {
|
|
64
|
-
// If in terminal, should return detected width (which may differ)
|
|
65
|
-
assert(typeof result === "number" && result > 0);
|
|
66
|
-
}
|
|
67
|
-
});
|
package/src/terminal.ts
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
// deno-lint-ignore-file no-process-global
|
|
2
|
-
/// <reference types="@types/node" />
|
|
3
|
-
/**
|
|
4
|
-
* @fileoverview
|
|
5
|
-
* Terminal detection and width calculation utilities
|
|
6
|
-
*
|
|
7
|
-
* Provides cross-runtime compatible functions to detect if the process
|
|
8
|
-
* is attached to a terminal and get the terminal width.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Detect if the current process is attached to a terminal (TTY).
|
|
13
|
-
*
|
|
14
|
-
* @returns True if stdout is connected to a terminal
|
|
15
|
-
*/
|
|
16
|
-
export function isTerminal(): boolean {
|
|
17
|
-
try {
|
|
18
|
-
// Deno runtime
|
|
19
|
-
if (typeof Deno !== "undefined") {
|
|
20
|
-
// Use modern Deno API
|
|
21
|
-
if (Deno.stdout.isTerminal) {
|
|
22
|
-
return Deno.stdout.isTerminal();
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Node.js/Bun runtime
|
|
27
|
-
if (typeof process !== "undefined" && process.stdout) {
|
|
28
|
-
return Boolean(process.stdout.isTTY);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Browser environment - never a terminal
|
|
32
|
-
if (typeof window !== "undefined") {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Unknown environment - assume not a terminal
|
|
37
|
-
return false;
|
|
38
|
-
} catch {
|
|
39
|
-
// If any detection method fails, assume not a terminal
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Get the current terminal width in columns.
|
|
46
|
-
*
|
|
47
|
-
* @returns Terminal width in columns, or null if not available
|
|
48
|
-
*/
|
|
49
|
-
export function getTerminalWidth(): number | null {
|
|
50
|
-
try {
|
|
51
|
-
// Deno runtime
|
|
52
|
-
if (typeof Deno !== "undefined") {
|
|
53
|
-
// Try to get console size
|
|
54
|
-
if (Deno.consoleSize) {
|
|
55
|
-
const size = Deno.consoleSize();
|
|
56
|
-
return size?.columns || null;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Node.js/Bun runtime
|
|
61
|
-
if (typeof process !== "undefined" && process.stdout) {
|
|
62
|
-
return process.stdout.columns || null;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Fallback to environment variable
|
|
66
|
-
const envColumns = typeof Deno !== "undefined"
|
|
67
|
-
? Deno.env.get("COLUMNS")
|
|
68
|
-
: process?.env?.COLUMNS;
|
|
69
|
-
|
|
70
|
-
if (envColumns) {
|
|
71
|
-
const parsed = parseInt(envColumns, 10);
|
|
72
|
-
return isNaN(parsed) ? null : parsed;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return null;
|
|
76
|
-
} catch {
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Get the optimal word wrap width based on terminal detection.
|
|
83
|
-
*
|
|
84
|
-
* @param defaultWidth Default width to use when not in a terminal
|
|
85
|
-
* @returns The optimal width
|
|
86
|
-
*/
|
|
87
|
-
export function getOptimalWordWrapWidth(defaultWidth: number = 80): number {
|
|
88
|
-
if (!isTerminal()) {
|
|
89
|
-
return defaultWidth;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const terminalWidth = getTerminalWidth();
|
|
93
|
-
return terminalWidth || defaultWidth;
|
|
94
|
-
}
|
package/src/truncate.test.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { suite } from "@alinea/suite";
|
|
2
|
-
import { assertEquals } from "@std/assert/equals";
|
|
3
|
-
import { truncateCategory } from "./truncate.ts";
|
|
4
|
-
|
|
5
|
-
const test = suite(import.meta);
|
|
6
|
-
|
|
7
|
-
test("truncateCategory() with no truncation", () => {
|
|
8
|
-
const category = ["app", "server"];
|
|
9
|
-
|
|
10
|
-
// Strategy false - no truncation
|
|
11
|
-
assertEquals(
|
|
12
|
-
truncateCategory(category, 10, ".", false),
|
|
13
|
-
"app.server",
|
|
14
|
-
);
|
|
15
|
-
|
|
16
|
-
// Width is enough - no truncation needed
|
|
17
|
-
assertEquals(
|
|
18
|
-
truncateCategory(category, 20, ".", "middle"),
|
|
19
|
-
"app.server",
|
|
20
|
-
);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
test("truncateCategory() with end truncation", () => {
|
|
24
|
-
const category = ["app", "server", "http", "middleware"];
|
|
25
|
-
|
|
26
|
-
assertEquals(
|
|
27
|
-
truncateCategory(category, 15, ".", "end"),
|
|
28
|
-
"app.server.htt…",
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
assertEquals(
|
|
32
|
-
truncateCategory(category, 10, ".", "end"),
|
|
33
|
-
"app.serve…",
|
|
34
|
-
);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test("truncateCategory() with middle truncation", () => {
|
|
38
|
-
const category = ["app", "server", "http", "middleware", "auth"];
|
|
39
|
-
|
|
40
|
-
// Should keep first and last parts
|
|
41
|
-
assertEquals(
|
|
42
|
-
truncateCategory(category, 20, ".", "middle"),
|
|
43
|
-
"app…auth",
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
// With more space, still just first…last for simplicity
|
|
47
|
-
assertEquals(
|
|
48
|
-
truncateCategory(category, 25, ".", "middle"),
|
|
49
|
-
"app…auth",
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
// Very long category names
|
|
53
|
-
const longCategory = [
|
|
54
|
-
"application",
|
|
55
|
-
"server",
|
|
56
|
-
"http",
|
|
57
|
-
"middleware",
|
|
58
|
-
"authentication",
|
|
59
|
-
];
|
|
60
|
-
assertEquals(
|
|
61
|
-
truncateCategory(longCategory, 30, ".", "middle"),
|
|
62
|
-
"application…authentication",
|
|
63
|
-
);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test("truncateCategory() edge cases", () => {
|
|
67
|
-
// Very small width
|
|
68
|
-
assertEquals(
|
|
69
|
-
truncateCategory(["app", "server"], 4, ".", "middle"),
|
|
70
|
-
"…",
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
// Single segment - falls back to end truncation
|
|
74
|
-
assertEquals(
|
|
75
|
-
truncateCategory(["verylongcategoryname"], 10, ".", "middle"),
|
|
76
|
-
"verylongc…",
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
// Two segments that are too long together - should fall back to end truncation
|
|
80
|
-
assertEquals(
|
|
81
|
-
truncateCategory(["verylongname", "anotherlongname"], 15, ".", "middle"),
|
|
82
|
-
"verylongname.a…",
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
// Empty category
|
|
86
|
-
assertEquals(
|
|
87
|
-
truncateCategory([], 10, ".", "middle"),
|
|
88
|
-
"",
|
|
89
|
-
);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test("truncateCategory() with custom separator", () => {
|
|
93
|
-
const category = ["app", "server", "http"];
|
|
94
|
-
|
|
95
|
-
assertEquals(
|
|
96
|
-
truncateCategory(category, 20, "::", "middle"),
|
|
97
|
-
"app::server::http",
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
assertEquals(
|
|
101
|
-
truncateCategory(category, 10, "::", "middle"),
|
|
102
|
-
"app…http",
|
|
103
|
-
);
|
|
104
|
-
});
|
package/src/truncate.ts
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Truncation strategies for category names.
|
|
3
|
-
*
|
|
4
|
-
* @since 1.0.0
|
|
5
|
-
*/
|
|
6
|
-
export type TruncationStrategy = "middle" | "end" | false;
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Truncates a category array to fit within a maximum width using the specified strategy.
|
|
10
|
-
*
|
|
11
|
-
* This function intelligently shortens long hierarchical category names while
|
|
12
|
-
* preserving important context. The truncation behavior depends on the chosen
|
|
13
|
-
* strategy:
|
|
14
|
-
*
|
|
15
|
-
* - `"middle"`: Keeps the first and last segments with "…" in between
|
|
16
|
-
* - `"end"`: Truncates at the end with "…" suffix
|
|
17
|
-
* - `false`: No truncation (returns full category string)
|
|
18
|
-
*
|
|
19
|
-
* When the category is too long even for middle truncation (first + "…" + last
|
|
20
|
-
* exceeds maxWidth), it falls back to end truncation.
|
|
21
|
-
*
|
|
22
|
-
* @param category The category segments to truncate.
|
|
23
|
-
* @param maxWidth Maximum width for the category string.
|
|
24
|
-
* @param separator Category separator (default: ".").
|
|
25
|
-
* @param strategy Truncation strategy to use (default: "middle").
|
|
26
|
-
* @returns The truncated category string.
|
|
27
|
-
*
|
|
28
|
-
* @example
|
|
29
|
-
* ```typescript
|
|
30
|
-
* // Middle truncation
|
|
31
|
-
* truncateCategory(["app", "server", "http", "auth"], 15, ".", "middle");
|
|
32
|
-
* // Returns: "app…auth"
|
|
33
|
-
*
|
|
34
|
-
* // End truncation
|
|
35
|
-
* truncateCategory(["app", "server", "http", "auth"], 15, ".", "end");
|
|
36
|
-
* // Returns: "app.server.h…"
|
|
37
|
-
*
|
|
38
|
-
* // No truncation
|
|
39
|
-
* truncateCategory(["app", "auth"], 20, ".", false);
|
|
40
|
-
* // Returns: "app.auth"
|
|
41
|
-
* ```
|
|
42
|
-
*
|
|
43
|
-
* @since 1.0.0
|
|
44
|
-
*/
|
|
45
|
-
export function truncateCategory(
|
|
46
|
-
category: readonly string[],
|
|
47
|
-
maxWidth: number,
|
|
48
|
-
separator: string = ".",
|
|
49
|
-
strategy: TruncationStrategy = "middle",
|
|
50
|
-
): string {
|
|
51
|
-
if (!strategy || maxWidth <= 0) {
|
|
52
|
-
return category.join(separator);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const full = category.join(separator);
|
|
56
|
-
if (full.length <= maxWidth) {
|
|
57
|
-
return full;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Minimum width needed for truncation with ellipsis
|
|
61
|
-
const minWidth = 5; // e.g., "a…z"
|
|
62
|
-
if (maxWidth < minWidth) {
|
|
63
|
-
return "…";
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (strategy === "end") {
|
|
67
|
-
return full.substring(0, maxWidth - 1) + "…";
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Middle truncation strategy
|
|
71
|
-
if (category.length <= 2) {
|
|
72
|
-
// For short categories, just truncate the end
|
|
73
|
-
return full.substring(0, maxWidth - 1) + "…";
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Try to keep first and last segments
|
|
77
|
-
const first = category[0];
|
|
78
|
-
const last = category[category.length - 1];
|
|
79
|
-
const ellipsis = "…";
|
|
80
|
-
|
|
81
|
-
// Check if we can at least fit first…last
|
|
82
|
-
const minimalLength = first.length + ellipsis.length + last.length;
|
|
83
|
-
if (minimalLength > maxWidth) {
|
|
84
|
-
// Even minimal format is too long, fallback to end truncation
|
|
85
|
-
return full.substring(0, maxWidth - 1) + "…";
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// For simple case with limited space, just do first…last
|
|
89
|
-
return `${first}${ellipsis}${last}`;
|
|
90
|
-
}
|
package/src/util.deno.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export interface InspectOptions {
|
|
2
|
-
colors?: boolean;
|
|
3
|
-
depth?: number | null;
|
|
4
|
-
compact?: boolean;
|
|
5
|
-
getters?: boolean;
|
|
6
|
-
showProxy?: boolean;
|
|
7
|
-
[key: string]: unknown;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function inspect(obj: unknown, options?: InspectOptions): string {
|
|
11
|
-
if ("Deno" in globalThis) {
|
|
12
|
-
return Deno.inspect(obj, {
|
|
13
|
-
colors: options?.colors,
|
|
14
|
-
depth: options?.depth ?? undefined,
|
|
15
|
-
compact: options?.compact ?? true,
|
|
16
|
-
});
|
|
17
|
-
} else {
|
|
18
|
-
const indent = options?.compact === true ? undefined : 2;
|
|
19
|
-
return JSON.stringify(obj, null, indent);
|
|
20
|
-
}
|
|
21
|
-
}
|
package/src/util.node.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import util from "node:util";
|
|
2
|
-
|
|
3
|
-
export interface InspectOptions {
|
|
4
|
-
colors?: boolean;
|
|
5
|
-
depth?: number | null;
|
|
6
|
-
compact?: boolean;
|
|
7
|
-
getters?: boolean;
|
|
8
|
-
showProxy?: boolean;
|
|
9
|
-
[key: string]: unknown;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function inspect(obj: unknown, options?: InspectOptions): string {
|
|
13
|
-
return util.inspect(obj, options);
|
|
14
|
-
}
|
package/src/util.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export interface InspectOptions {
|
|
2
|
-
colors?: boolean;
|
|
3
|
-
depth?: number | null;
|
|
4
|
-
compact?: boolean;
|
|
5
|
-
getters?: boolean;
|
|
6
|
-
showProxy?: boolean;
|
|
7
|
-
[key: string]: unknown;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function inspect(obj: unknown, options?: InspectOptions): string {
|
|
11
|
-
const indent = options?.compact === true ? undefined : 2;
|
|
12
|
-
return JSON.stringify(obj, null, indent);
|
|
13
|
-
}
|
package/src/wcwidth.test.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { suite } from "@alinea/suite";
|
|
2
|
-
import { assertEquals } from "@std/assert/equals";
|
|
3
|
-
import { getDisplayWidth, wcwidth } from "./wcwidth.ts";
|
|
4
|
-
|
|
5
|
-
const test = suite(import.meta);
|
|
6
|
-
|
|
7
|
-
test("wcwidth() basic functionality", () => {
|
|
8
|
-
// Control characters should return -1
|
|
9
|
-
assertEquals(wcwidth(0x0A), -1); // newline
|
|
10
|
-
assertEquals(wcwidth(0x1B), -1); // escape
|
|
11
|
-
|
|
12
|
-
// Regular ASCII characters should return 1
|
|
13
|
-
assertEquals(wcwidth(0x41), 1); // 'A'
|
|
14
|
-
assertEquals(wcwidth(0x20), 1); // space
|
|
15
|
-
|
|
16
|
-
// Wide characters should return 2
|
|
17
|
-
assertEquals(wcwidth(0x2728), 2); // ✨ sparkles
|
|
18
|
-
assertEquals(wcwidth(0x274C), 2); // ❌ cross mark
|
|
19
|
-
assertEquals(wcwidth(0x4E00), 2); // CJK ideograph
|
|
20
|
-
|
|
21
|
-
// Zero-width characters should return 0
|
|
22
|
-
assertEquals(wcwidth(0x0300), 0); // combining grave accent
|
|
23
|
-
assertEquals(wcwidth(0xFE0F), 0); // variation selector
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test("getDisplayWidth() with regular text", () => {
|
|
27
|
-
assertEquals(getDisplayWidth("hello"), 5);
|
|
28
|
-
assertEquals(getDisplayWidth(""), 0);
|
|
29
|
-
assertEquals(getDisplayWidth("A"), 1);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test("getDisplayWidth() with emojis", () => {
|
|
33
|
-
assertEquals(getDisplayWidth("✨"), 2);
|
|
34
|
-
assertEquals(getDisplayWidth("❌"), 2);
|
|
35
|
-
assertEquals(getDisplayWidth("🔍"), 2);
|
|
36
|
-
assertEquals(getDisplayWidth("⚠️"), 2); // includes variation selector
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test("getDisplayWidth() with mixed content", () => {
|
|
40
|
-
assertEquals(getDisplayWidth("A✨B"), 4); // 1 + 2 + 1
|
|
41
|
-
assertEquals(getDisplayWidth("test ❌ end"), 11); // 4 + 1 + 2 + 1 + 3
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("getDisplayWidth() with ANSI codes", () => {
|
|
45
|
-
assertEquals(getDisplayWidth("\x1b[31mred\x1b[0m"), 3);
|
|
46
|
-
assertEquals(getDisplayWidth("\x1b[1m\x1b[31mbold red\x1b[0m"), 8);
|
|
47
|
-
assertEquals(getDisplayWidth("\x1b[38;2;255;0;0m✨\x1b[0m"), 2);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("getDisplayWidth() with zero-width characters", () => {
|
|
51
|
-
// Combining diacritical marks should not add to width
|
|
52
|
-
assertEquals(getDisplayWidth("e\u0301"), 1); // e with acute accent
|
|
53
|
-
assertEquals(getDisplayWidth("a\u0300"), 1); // a with grave accent
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("getDisplayWidth() with CJK characters", () => {
|
|
57
|
-
assertEquals(getDisplayWidth("你好"), 4); // two CJK chars = 4 width
|
|
58
|
-
assertEquals(getDisplayWidth("こんにちは"), 10); // five hiragana = 10 width
|
|
59
|
-
assertEquals(getDisplayWidth("안녕"), 4); // two hangul = 4 width
|
|
60
|
-
});
|