Skip to content

Commit a603e55

Browse files
committed
refactor: color methods, parser clean ups
1 parent ae0364d commit a603e55

File tree

4 files changed

+132
-38
lines changed

4 files changed

+132
-38
lines changed

example/example.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ void main(List<String> args) async {
2121
ColorToken(text: 'Hello, '),
2222
ColorToken(
2323
text: 'world',
24-
fgColor: Color(32),
25-
styles: {TermStyle.underline},
24+
fgColor: Color.fg(32),
25+
styles: {TermStyle.underline,TermStyle.reset},
2626
),
2727
ColorToken(text: '!'),
2828
];

lib/src/color.dart

Lines changed: 89 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,41 @@
1+
/// The color mode, either foreground or background.
2+
enum ColorMode { foreground, background }
3+
4+
/// Represents a color in the terminal.
5+
/// This is a base class for all color types.
16
class Color {
7+
/// The value of the color
28
final dynamic value;
39

4-
const Color(this.value);
10+
/// The mode of the color, either foreground or background.
11+
final ColorMode mode;
12+
13+
/// Creates a new color with the given value and mode.
14+
const Color(this.value, this.mode);
15+
16+
/// Creates a new foreground color with the given value.
17+
const Color.fg(this.value) : mode = ColorMode.foreground;
18+
19+
/// Creates a new background color with the given value.
20+
const Color.bg(this.value) : mode = ColorMode.background;
521

6-
static const Color none = Color(0);
22+
/// This color should be ignored for rendering.
23+
static const Color none = Color.fg(0);
724

25+
/// Returns true if the color is the default color.
26+
bool get isNone => value == 0;
27+
28+
/// Returns true is the color is not the default color.
29+
bool get isNotNone => !isNone;
30+
31+
/// The formatted value of the color, used in ANSI escape codes.
832
String get formatted => value.toString();
933

34+
/// The string representation of the color mode.
35+
String get modeString => mode.toString().split('.').last;
36+
1037
@override
11-
String toString() => 'Color($value)';
38+
String toString() => isNone ? 'Color.none' : 'Color($value, $modeString)';
1239

1340
@override
1441
operator ==(Object other) {
@@ -22,35 +49,85 @@ class Color {
2249
int get hashCode => value.hashCode;
2350
}
2451

52+
/// Represents an RGB color in the terminal.
2553
class RGBColor extends Color {
54+
/// The red value of the color.
2655
final int red;
56+
57+
/// The green value of the color.
2758
final int green;
59+
60+
/// The blue value of the color.
2861
final int blue;
2962

30-
const RGBColor(this.red, this.green, this.blue) : super('$red;$green;$blue');
63+
/// Creates a new RGB color with the given red, green, and blue values.
64+
const RGBColor(
65+
this.red,
66+
this.green,
67+
this.blue, [
68+
ColorMode mode = ColorMode.foreground,
69+
]) : super('$red;$green;$blue', mode);
70+
71+
/// Creates a new RGB foreground color with the given red, green, and blue values.
72+
const RGBColor.fg(
73+
this.red,
74+
this.green,
75+
this.blue,
76+
) : super('$red;$green;$blue', ColorMode.foreground);
77+
78+
/// Creates a new RGB background color with the given red, green, and blue values.
79+
const RGBColor.bg(
80+
this.red,
81+
this.green,
82+
this.blue,
83+
) : super('$red;$green;$blue', ColorMode.background);
3184

3285
@override
33-
String get formatted => '$red;$green;$blue';
86+
String get formatted => '$formattedMode;$red;$green;$blue';
3487

35-
String get formattedForeground => '38;2;$formatted';
36-
String get formattedBackground => '48;2;$formatted';
88+
/// The formatted mode of the color, used in ANSI escape codes.
89+
/// It is usually followed by the r;g;b components of the color.
90+
String get formattedMode => mode == ColorMode.foreground ? '38;2' : '48;2';
3791

3892
@override
39-
String toString() => 'RGBColor($red, $green, $blue)';
93+
String toString() => isNone ? 'RGBColor.none' : 'RGBColor($red, $green, $blue)';
4094
}
4195

4296
class ANSIColor extends Color {
4397
final int color;
4498

45-
const ANSIColor(this.color) : super(color);
99+
/// Creates a new ANSI color with the given value.
100+
ANSIColor(this.color)
101+
: super(
102+
color,
103+
isForegroundColor(color)
104+
? ColorMode.foreground
105+
: ColorMode.background);
106+
107+
/// Creates a new ANSI foreground color with the given value.
108+
const ANSIColor.fg(this.color) : super.fg(color);
109+
110+
/// Creates a new ANSI background color with the given value.
111+
const ANSIColor.bg(this.color) : super.bg(color);
112+
113+
/// Returns true if the color is a foreground color.
114+
bool get isForeground => mode == ColorMode.foreground;
115+
116+
/// Returns true if the color is a background color.
117+
bool get isBackground => mode == ColorMode.background;
118+
119+
/// Returns true if the color is a foreground color.
120+
static bool isForegroundColor(int color) =>
121+
((color >= 30 && color <= 37) || (color >= 90 && color <= 97));
46122

47-
bool get isForeground => (color >= 30 && color <= 37) && (color >= 90 && color <= 97);
48-
bool get isBackground => (color >= 40 && color <= 47) && (color >= 100 && color <= 107);
123+
/// Returns true if the color is a background color.
124+
static bool isBackgroundColor(int color) =>
125+
((color >= 40 && color <= 47) || (color >= 100 && color <= 107));
49126

50127
@override
51128
String get formatted => color.toString();
52129

53130
@override
54-
String toString() => 'ANSIColor($color)';
131+
String toString() => isNone ? 'ANSIColor.none' : 'ANSIColor($color)';
55132
}
56133

lib/src/parser.dart

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -69,31 +69,40 @@ class ColorParser implements IReader<StringTokenValue> {
6969
}
7070

7171
ColorToken _parseStyleToken(ColorToken token, List<String> colors) {
72+
if (colors.isEmpty) {
73+
return token;
74+
}
75+
7276
final colorNums = colors.map((n) => int.parse(n)).toList();
77+
final first = colorNums.first;
78+
79+
final isRgb = _checkPair(colorNums, 38, 2) || _checkPair(colorNums, 48, 2);
80+
final isXterm256 =
81+
_checkPair(colorNums, 38, 5) || _checkPair(colorNums, 48, 5);
82+
final isAnsiFg =
83+
_checkBetween(first, 30, 37) || _checkBetween(first, 90, 97);
84+
final isAnsiBg =
85+
_checkBetween(first, 40, 47) || _checkBetween(first, 100, 107);
86+
7387
// Special cases
74-
if (_checkPair(colorNums, 38, 2) || _checkPair(colors, 48, 2)) {
88+
if (isRgb) {
7589
// RGB format is 38;2;R;G;B or 48;2;R;G;B
7690
token = _consumeRgbToken(token, colors);
7791
colors.removeRange(0, 5);
78-
} else if (_checkPair(colorNums, 38, 5) || _checkPair(colorNums, 48, 5)) {
92+
} else if (isXterm256) {
7993
// Xterm256 format is 38;5;N or 48;5;N
8094
token = _consumeXterm256Token(token, colors);
8195
colors.removeRange(0, 3);
96+
} else if (isAnsiFg) {
97+
token.fgColor = ANSIColor.fg(first);
98+
colors.removeAt(0);
99+
} else if (isAnsiBg) {
100+
token.bgColor = ANSIColor.bg(first);
101+
colors.removeAt(0);
82102
} else {
83103
// Other style codes
84104
for (var color in colorNums) {
85-
if (color < 30) {
86-
token.setStyle(color);
87-
} else if (_checkBetween(color, 30, 37) ||
88-
_checkBetween(color, 90, 97)) {
89-
token.fgColor = ANSIColor(color);
90-
} else if (_checkBetween(color, 40, 47) ||
91-
_checkBetween(color, 100, 107)) {
92-
token.bgColor = ANSIColor(color);
93-
} else {
94-
// Catch arbitrary/unhandled codes
95-
token.setStyle(color);
96-
}
105+
token.setStyle(color);
97106
colors.removeAt(0);
98107
}
99108
}
@@ -116,9 +125,9 @@ class ColorParser implements IReader<StringTokenValue> {
116125
final g = int.parse(colors[3]);
117126
final b = int.parse(colors[4]);
118127
if (colors[0] == '38') {
119-
token.fgColor = RGBColor(r, g, b);
128+
token.fgColor = RGBColor.fg(r, g, b);
120129
} else if (colors[0] == '48') {
121-
token.bgColor = RGBColor(r, g, b);
130+
token.bgColor = RGBColor.bg(r, g, b);
122131
}
123132
return token;
124133
}
@@ -129,9 +138,9 @@ class ColorParser implements IReader<StringTokenValue> {
129138
}
130139
final color = int.parse(colors[2]);
131140
if (colors[0] == '38') {
132-
token.fgColor = ANSIColor(color);
141+
token.fgColor = ANSIColor.fg(color);
133142
} else if (colors[0] == '48') {
134-
token.bgColor = ANSIColor(color);
143+
token.bgColor = ANSIColor.bg(color);
135144
}
136145
return token;
137146
}

lib/src/token.dart

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,29 +89,37 @@ class ColorToken {
8989
/// and other [styles], and construct it to the desired output format.
9090
String get formatted {
9191
final parts = <String>[];
92+
var post = '';
9293

9394
// foreground
9495
if (fgColor is RGBColor) {
9596
parts.add('38;2;${fgColor.formatted}');
96-
} else if (fgColor != Color.none) {
97+
} else if (fgColor.isNotNone) {
9798
parts.add(fgColor.formatted);
9899
}
99100

100101
// background
101102
if (bgColor is RGBColor) {
102103
parts.add('48;2;${bgColor.formatted}');
103-
} else if (bgColor != Color.none) {
104+
} else if (bgColor.isNotNone) {
104105
parts.add(bgColor.formatted);
105106
}
106107

107-
final styleParts =
108-
styles.map((s) => Consts.codeMap[s]?.toString()).whereType<String>();
109-
108+
// other styles
109+
final styleParts = styles
110+
.where((s) => s != TermStyle.reset)
111+
.map((s) => Consts.codeMap[s]?.toString())
112+
.whereType<String>();
110113
parts.addAll(styleParts);
111114

115+
if (reset) {
116+
post = _tokenString(Consts.codeMap[TermStyle.reset].toString());
117+
}
118+
119+
// collct all tokens
112120
final tokens = _tokenString(parts.where((s) => s.isNotEmpty).join(';'));
113121

114-
return '$tokens$text';
122+
return '$tokens$text$post';
115123
}
116124

117125
@override

0 commit comments

Comments
 (0)