parseAnsi function
Parse ANSI escape sequences in text.
Implementation
List<StyledSpan> parseAnsi(String input) {
final spans = <StyledSpan>[];
var style = const AnsiStyle();
final buffer = StringBuffer();
var i = 0;
void flushBuffer() {
if (buffer.isNotEmpty) {
spans.add(StyledSpan(buffer.toString(), style));
buffer.clear();
}
}
while (i < input.length) {
// Check for ESC[
if (i + 1 < input.length &&
input.codeUnitAt(i) == 0x1B &&
input[i + 1] == '[') {
flushBuffer();
i += 2;
// Parse CSI sequence
final paramStart = i;
while (i < input.length &&
(input.codeUnitAt(i) >= 0x30 && input.codeUnitAt(i) <= 0x3F)) {
i++;
}
// Skip intermediate bytes
while (i < input.length &&
(input.codeUnitAt(i) >= 0x20 && input.codeUnitAt(i) <= 0x2F)) {
i++;
}
// Final byte
if (i < input.length) {
final finalByte = input[i];
i++;
if (finalByte == 'm') {
// SGR sequence
final params = input
.substring(paramStart, i - 1)
.split(';')
.map((s) => int.tryParse(s) ?? 0)
.toList();
if (params.isEmpty || (params.length == 1 && params[0] == 0)) {
style = const AnsiStyle();
} else {
var j = 0;
while (j < params.length) {
final p = params[j];
switch (p) {
case 0:
style = const AnsiStyle();
break;
case 1:
style = style.copyWith(bold: true);
break;
case 2:
style = style.copyWith(dim: true);
break;
case 3:
style = style.copyWith(italic: true);
break;
case 4:
style = style.copyWith(underline: true);
break;
case 7:
style = style.copyWith(inverse: true);
break;
case 9:
style = style.copyWith(strikethrough: true);
break;
case 22:
style = style.copyWith(bold: false, dim: false);
break;
case 23:
style = style.copyWith(italic: false);
break;
case 24:
style = style.copyWith(underline: false);
break;
case 27:
style = style.copyWith(inverse: false);
break;
case 29:
style = style.copyWith(strikethrough: false);
break;
case >= 30 && <= 37:
style = style.copyWith(
foreground: AnsiColors.standard[p - 30],
);
break;
case 38:
// Extended foreground
if (j + 1 < params.length &&
params[j + 1] == 5 &&
j + 2 < params.length) {
style = style.copyWith(
foreground: AnsiColors.color256(params[j + 2]),
);
j += 2;
} else if (j + 1 < params.length &&
params[j + 1] == 2 &&
j + 4 < params.length) {
style = style.copyWith(
foreground: Color.fromARGB(
255,
params[j + 2],
params[j + 3],
params[j + 4],
),
);
j += 4;
}
break;
case 39:
style = style.copyWith(foreground: null);
break;
case >= 40 && <= 47:
style = style.copyWith(
background: AnsiColors.standard[p - 40],
);
break;
case 48:
// Extended background
if (j + 1 < params.length &&
params[j + 1] == 5 &&
j + 2 < params.length) {
style = style.copyWith(
background: AnsiColors.color256(params[j + 2]),
);
j += 2;
} else if (j + 1 < params.length &&
params[j + 1] == 2 &&
j + 4 < params.length) {
style = style.copyWith(
background: Color.fromARGB(
255,
params[j + 2],
params[j + 3],
params[j + 4],
),
);
j += 4;
}
break;
case 49:
style = style.copyWith(background: null);
break;
case >= 90 && <= 97:
style = style.copyWith(
foreground: AnsiColors.standard[p - 90 + 8],
);
break;
case >= 100 && <= 107:
style = style.copyWith(
background: AnsiColors.standard[p - 100 + 8],
);
break;
}
j++;
}
}
}
// Ignore other CSI sequences (cursor movement, etc.)
}
} else {
buffer.writeCharCode(input.codeUnitAt(i));
i++;
}
}
flushBuffer();
return spans;
}