256 color support and rgb color support

This commit is contained in:
John Sennesael 2022-10-14 16:51:24 -05:00
parent 5f759df74e
commit b1b87b4cd2
3 changed files with 401 additions and 471 deletions

View File

@ -4,6 +4,7 @@ unit BrowserWidget;
interface
uses
AnsiParser,
DrawUtils,
Logger,
SideBarWidget,
@ -11,7 +12,6 @@ uses
Classes,
Objects,
Regexpr,
StrUtils,
SysUtils,
UDrivers,
@ -19,11 +19,6 @@ uses
video;
type
TCsiParseResult = record
cell: TEnhancedVideoCell;
tokensConsumed: SizeInt;
end;
TBrowserString = array of TEnhancedVideoCell;
TBrowserWidget = object(TScroller)
@ -37,10 +32,12 @@ type
procedure Draw; virtual;
procedure Reset;
private
AnsiParser: TAnsiParser;
App: TTurboGopherApplication;
Lines: array of TBrowserString;
CurrentForegroundColor: byte;
CurrentBackgroundColor: byte;
DefaultCell: TEnhancedVideoCell;
SideBar: PSideBarWidget;
const defaultFgColor = 2;
const defaultBgColor = 0;
@ -50,379 +47,6 @@ type
implementation
var
sgrTokenRe: TRegExpr;
{ some static helper funcs }
(* ANSI SGR / CSI parsing helper *)
function IsCsiParam(character: Char): Boolean;
begin
Result := false;
if (ord(character) >= $30) and (ord(character) <= $3f) then Result := true;
end;
(* ANSI SGR / CSI parsing helper *)
function IsCsiIntermediate(character: Char): Boolean;
begin
Result := false;
if (ord(character) >= $20) and (ord(character) <= $2f) then Result := true;
end;
(* ANSI SGR / CSI parsing helper *)
function IsCsiFinal(character: Char): Boolean;
begin
Result := false;
if (ord(character) >= $40) and (ord(character) <= $7e) then Result := true;
end;
(* ANSI SGR / CSI parsing helper *)
function ParseCsiToken(
csi: AnsiString;
currentCell, defaultCell: TEnhancedVideoCell): TCsiParseResult;
var
code: Integer = 0;
token: AnsiString = '';
tokenIsNumber: Boolean = False;
sepPos: Integer = 0;
tokenAsInt: Integer = 0;
tokenAsColor: Integer = 0;
begin
{ default result if we can't parse anything is to preseve current attrs. }
Result.tokensConsumed := 0;
Result.cell := currentCell;
{ get position of first token end. (either up to the first ; or m) }
sepPos := Pos(';', csi);
if sepPos = 0 then sepPos := Pos('m', csi);
{ if we couldn't get a position, we can't parse anything. }
if sepPos = 0 then Exit;
{ use position to get first token }
token := Copy(csi, 1, sepPos - 1);
{ CSI Reset }
if token = '0' then
begin
Result.cell := defaultCell;
Result.tokensConsumed := 1;
Exit;
end;
{ parse token }
try
tokenAsInt := StrToInt(token);
tokenIsNumber := True;
except
on E: EConvertError do
tokenIsNumber := False;
end;
if tokenIsNumber = True then
begin
tokenAsColor := tokenAsInt - 30;
if (tokenAsColor >= 0) and (tokenAsColor <= 7) then
begin
{ parse as regular fg color }
Result.cell.ForegroundColor := tokenAsColor;
Result.tokensConsumed := 1;
end;
tokenAsColor := tokenAsInt - 90;
if (tokenAsColor >= 0) and (tokenAsColor <= 7) then
begin
{ parse as bright fg color }
Result.cell.ForegroundColor := tokenAsColor + 7;
Result.tokensConsumed := 1;
end;
tokenAsColor := tokenAsInt - 40;
if (tokenAsColor >= 0) and (tokenAsColor <= 7) then
begin
{ parse as regular bg color }
Result.cell.BackgroundColor := tokenAsColor;
Result.tokensConsumed := 1;
end;
tokenAsColor := tokenAsInt - 100;
if (tokenAsColor >= 0) and (tokenAsColor <= 7) then
begin
{ parse as bright bg color }
Result.cell.BackgroundColor := tokenAsColor + 7;
Result.tokensConsumed := 1;
end;
end;
case token of
'38': { indexed fg color }
begin
if sgrTokenRe.Exec(csi) then
begin
if sgrTokenRe.Match[2] <> '5' then
begin
Result.tokensConsumed := 0;
Exit;
end;
code := StrToInt(sgrTokenRe.Match[3]);
if code = 233 then
begin
Result.tokensConsumed := 0;
end;
case code of
0, 16, 232..237:
begin { black }
Result.cell.ForegroundColor := 0;
Result.tokensConsumed := 3;
Exit;
end;
1, 52, 88, 124, 202..205:
begin { red }
Result.cell.ForegroundColor := 4;
Result.tokensConsumed := 3;
Exit;
end;
2, 22, 23, 58, 94, 130, 166, 28, 64, 65, 100, 34..36, 70..72, 106..108:
begin { green }
Result.cell.ForegroundColor := 2;
Result.tokensConsumed := 3;
Exit;
end;
3, 136, 172, 208, 142..145, 178..181, 214..216:
begin { yellow }
Result.cell.ForegroundColor := 6;
Result.tokensConsumed := 3;
Exit;
end;
4, 17..21, 24..27:
begin { blue }
Result.cell.ForegroundColor := 1;
Result.tokensConsumed := 3;
Exit;
end;
5, 53..57, 89..93, 125..129, 59..63, 95..99, 131..135, 109..110, 146..147, 182, 183:
begin { magenta }
Result.cell.ForegroundColor := 5;
Result.tokensConsumed := 3;
Exit;
end;
6, 29..31, 66..67, 37..39, 73..75:
begin { cyan }
Result.cell.ForegroundColor := 3;
Result.tokensConsumed := 3;
Exit;
end;
7, 231, 101, 102, 244..251:
begin { white }
Result.cell.ForegroundColor := 7;
Result.tokensConsumed := 3;
Exit;
end;
8, 238..243:
begin { bright black }
Result.cell.ForegroundColor := 8;
Result.tokensConsumed := 3;
Exit;
end;
9, 160, 196, 217..218:
begin { bright red }
Result.cell.ForegroundColor := 12;
Result.tokensConsumed := 3;
Exit;
end;
10, 40..43, 76..79, 112..115, 148..151, 46..49, 82..84, 118..121, 154..157, 190..193:
begin { bright green }
Result.cell.ForegroundColor := 10;
Result.tokensConsumed := 3;
Exit;
end;
11, 184..188, 220..223, 226..230:
begin { bright yellow }
Result.cell.ForegroundColor := 14;
Result.tokensConsumed := 3;
Exit;
end;
12, 32..33, 68..69, 103..105, 111, 152..153, 87, 123, 159, 195:
begin { bright blue }
Result.cell.ForegroundColor := 9;
Result.tokensConsumed := 3;
Exit;
end;
13, 161..165, 197..201, 167..171, 206..207, 137..141, 173..177, 209..213, 219, 189, 224, 225:
begin { bright magenta }
Result.cell.ForegroundColor := 13;
Result.tokensConsumed := 3;
Exit;
end;
14, 44..45, 80..81, 116..117, 50..51, 85..86, 122, 158, 194:
begin { bright cyan }
Result.cell.ForegroundColor := 11;
Result.tokensConsumed := 3;
Exit;
end;
15, 252..255:
begin { bright white }
Result.cell.ForegroundColor := 15;
Result.tokensConsumed := 3;
Exit;
end;
end;
end;
end;
'48': { indexed bg color }
begin
if sgrTokenRe.Exec(csi) then
begin
code := StrToInt(sgrTokenRe.Match[3]);
case code of
0, 16, 232..237:
begin { black }
Result.cell.BackgroundColor := 0;
Result.tokensConsumed := 3;
Exit;
end;
1, 52, 88, 124, 202..205:
begin { red }
Result.cell.BackgroundColor := 4;
Result.tokensConsumed := 3;
Exit;
end;
2, 22, 23, 58, 94, 130, 166, 28, 64, 65, 100, 34..36, 70..72, 106..108:
begin { green }
Result.cell.BackgroundColor := 2;
Result.tokensConsumed := 3;
Exit;
end;
3, 136, 172, 208, 142..145, 178..181, 214..216:
begin { yellow }
Result.cell.BackgroundColor := 6;
Result.tokensConsumed := 3;
Exit;
end;
4, 17..21, 24..27:
begin { blue }
Result.cell.BackgroundColor := 1;
Result.tokensConsumed := 3;
Exit;
end;
5, 53..57, 89..93, 125..129, 59..63, 95..99, 131..135, 109..110, 146..147, 182, 183:
begin { magenta }
Result.cell.BackgroundColor := 5;
Result.tokensConsumed := 3;
Exit;
end;
6, 29..31, 66..67, 37..39, 73..75:
begin { cyan }
Result.cell.BackgroundColor := 3;
Result.tokensConsumed := 3;
Exit;
end;
7, 231, 101, 102, 244..251:
begin { white }
Result.cell.BackgroundColor := 7;
Result.tokensConsumed := 3;
Exit;
end;
8, 238..243:
begin { bright black }
Result.cell.BackgroundColor := 8;
Result.tokensConsumed := 3;
Exit;
end;
9, 160, 196, 217..218:
begin { bright red }
Result.cell.BackgroundColor := 12;
Result.tokensConsumed := 3;
Exit;
end;
10, 40..43, 76..79, 112..115, 148..151, 46..49, 82..84, 118..121, 154..157, 190..193:
begin { bright green }
Result.cell.BackgroundColor := 10;
Result.tokensConsumed := 3;
Exit;
end;
11, 184..188, 220..223, 226..230:
begin { bright yellow }
Result.cell.BackgroundColor := 14;
Result.tokensConsumed := 3;
Exit;
end;
12, 32..33, 68..69, 103..105, 111, 152..153, 87, 123, 159, 195:
begin { bright blue }
Result.cell.BackgroundColor := 9;
Result.tokensConsumed := 3;
Exit;
end;
13, 161..165, 197..201, 167..171, 206..207, 137..141, 173..177, 209..213, 219, 189, 224, 225:
begin { bright magenta }
Result.cell.BackgroundColor := 13;
Result.tokensConsumed := 3;
Exit;
end;
14, 44..45, 80..81, 116..117, 50..51, 85..86, 122, 158, 194:
begin { bright cyan }
Result.cell.BackgroundColor := 11;
Result.tokensConsumed := 3;
Exit;
end;
15, 252..255:
begin { bright white }
Result.cell.BackgroundColor := 15;
Result.tokensConsumed := 3;
Exit;
end;
end;
end;
end;
end;
end;
(* ANSI SGR / CSI parsing helper *)
function ParseCsi(
Logger: PLogger;
csi: UnicodeString;
currentCell, defaultCell: TEnhancedVideoCell
): TEnhancedVideoCell;
var
prevPos, nextPos, I: Integer;
sgrBuffer: AnsiString;
parsedToken: TCsiParseResult;
success: Boolean;
begin
if csi = '38;5;233m' then
begin
sgrBuffer := 'a';
end;
sgrBuffer := '';
Result := currentCell;
success := False;
if Length(csi) < 2 then
begin
Logger^.Info('Ignoring unsupported CSI sequence: ' + csi);
Exit;
end;
if csi[Length(csi)] <> 'm' then
begin
Logger^.Info('Ignoring unsupported CSI sequence: ' + csi);
Exit;
end;
I := 1;
prevPos := 0;
while I <= Length(csi) do
begin
{ read up to the next token or terminator }
nextPos := NPos(';', UTF8Encode(csi), I);
if nextPos = 0 then nextPos := Pos('m', csi);
if nextPos = 0 then break;
sgrBuffer := Copy(UTF8Encode(csi), prevPos + 1, nextPos);
{ attempt to parse the tokens so far }
parsedToken := ParseCsiToken(sgrBuffer, Result, defaultCell);
if parsedToken.tokensConsumed > 0 then
begin
{ parse success, write attrs, advance counter #tokens consumed. }
Result := parsedToken.cell;
prevPos := nextPos;
success := True;
end;
{ parse fail, try next token. }
I += 1;
end;
if success <> True then
begin
Logger^.Warning('Unable to parse csi token: ' + csi);
end;
end;
{ TBrowserWidget }
constructor TBrowserWidget.Init(
@ -439,28 +63,22 @@ begin
CurrentBackgroundColor := defaultBgColor;
GrowMode := gfGrowHiX + gfGrowHiY;
SetLimit(128, 10);
sgrTokenRe := TRegExpr.Create('^(\d+);(\d+);(\d+)');
end;
procedure TBrowserWidget.Add(text: UnicodeString);
type TCsiParseStage = (None, Parameter, Intermediate, Final);
var
CC, PC: UnicodeChar;
NewChar, DefaultCell: TEnhancedVideoCell;
I: SizeInt;
Line: TBrowserString;
Logger: PLogger;
InAnsiParse: Boolean;
CsiStage: TCsiParseStage;
CsiBuffer: UnicodeString;
begin
DefaultCell := default(TEnhancedVideoCell);
DefaultCell.ForegroundColor := DefaultFgColor;
DefaultCell.BackgroundColor := DefaultBgColor;
AnsiParser.Init(App, DefaultCell);
end;
procedure TBrowserWidget.Add(text: UnicodeString);
var
CC, PC: UnicodeChar;
NewChar: TEnhancedVideoCell;
I: SizeInt;
Line: TBrowserString;
Logger: PLogger;
begin
Logger := App.GetLogger();
CsiBuffer := '';
CsiStage := None;
InAnsiParse := False;
PC := chr(0);
NewChar := default(TEnhancedVideoCell);
Line := default(TBrowserString);
@ -489,78 +107,9 @@ begin
end;
(* Parse CSI escapes. *)
if CsiStage <> TCsiParseStage.None then
begin
{ detect parameter bytes }
if CsiStage = TCsiParseStage.Parameter then
begin
if IsCsiParam(CC) then
begin
CsiBuffer += CC;
end
else if IsCsiIntermediate(CC) then
begin
CsiBuffer += CC;
CsiStage := TCsiParseStage.Intermediate;
end
else if IsCsiFinal(CC) then
begin
CsiBuffer += CC;
CsiStage := TCsiParseStage.Final;
end
else
begin
Logger^.Warning(
'Could not parse ANSI CSI sequence (after parsing parameter): '
+ 'Character: ' + CC + ' Buffer: ' + CsiBuffer);
CsiStage := TCsiParseStage.None;
CsiBuffer := '';
end;
end
else if CsiStage = TCsiParseStage.Intermediate then
begin
if IsCsiIntermediate(CC) then
begin
CsiBuffer += CC;
end
else if IsCsiFinal(CC) then
begin
CsiBuffer += CC;
CsiStage := TCsiParseStage.Final;
end
else
begin
Logger^.Warning(
'Could not parse ANSI CSI sequence (after parsing intermediate): '
+ 'Character: ' + CC + ' Buffer: ' + CsiBuffer);
CsiStage := TCsiParseStage.None;
CsiBuffer := '';
end;
end;
if CsiStage = TCsiParseStage.Final then
begin
NewChar := ParseCsi(Logger, CsiBuffer, NewChar, DefaultCell);
CsiStage := TCsiParseStage.None;
CsiBuffer := '';
end;
PC := CC;
continue;
end;
(* Parse ANSI escapes. *)
if InAnsiParse = true then
begin
case text[I + 1] of
'[': CsiStage := TCsiParseStage.Parameter;
end;
PC := CC;
InAnsiParse := False;
continue;
end;
(* Initiate ANSI escape parsing. *)
if text[I + 1] = chr(27) then
if AnsiParser.ParseChar(CC, NewChar, text, I) <> True then
begin
PC := CC;
InAnsiParse := True;
continue;
end;
NewChar.EGC_SingleChar := text[I + 1];
@ -619,6 +168,7 @@ end;
procedure TBrowserWidget.Reset;
begin
SetLength(Lines, 0);
AnsiParser.Reset;
end;
end.

View File

@ -141,7 +141,7 @@ begin
evKeyDown:
begin
case Event.KeyCode of
kbAltLeft:
kbCtrlLeft:
begin
if HistoryIndex > 0 then
begin
@ -149,7 +149,7 @@ begin
Get(History[HistoryIndex]);
end;
end;
kbAltRight:
kbCtrlRight:
begin
if HistoryIndex < (Length(History) - 1) then
begin
@ -157,13 +157,13 @@ begin
Get(History[HistoryIndex]);
end;
end;
kbAltUp:
kbCtrlUp:
begin
SideBar^.SelectPrevious(Browser^);
ClearEvent(Event);
Draw;
end;
kbAltDown:
kbCtrlDown:
begin
SideBar^.SelectNext(Browser^);
ClearEvent(Event);

380
src/ansiparser.pas Normal file
View File

@ -0,0 +1,380 @@
unit AnsiParser;
{$mode ObjFPC}{$H+}
{$codepage UTF8}
interface
uses
Logger,
TurboGopherApplication,
Classes,
Objects,
StrUtils,
SysUtils,
URegexpr,
video;
type
TCsiParseResult = record
cell: TEnhancedVideoCell;
tokensConsumed: SizeInt;
end;
type TCsiParseStage = (None, Parameter, Intermediate, Final);
TAnsiParser = object(TObject)
constructor Init(
var TheApp: TTurboGopherApplication;
defaultCell: TEnhancedVideoCell
);
function ParseChar(
character: UnicodeChar;
var cell: TEnhancedVideoCell;
text: UnicodeString;
textIndex: Integer
): Boolean;
function Parsing: Boolean;
procedure Reset;
private
App: TTurboGopherApplication;
DefaultCell: TEnhancedVideoCell;
InAnsiParse: Boolean;
CsiStage: TCsiParseStage;
CsiBuffer: UnicodeString;
Logger: PLogger;
sgrRgbTokenRe, sgrTokenRe: TRegExpr;
function IsCsiParam(character: UnicodeChar): Boolean;
function IsCsiIntermediate(character: UnicodeChar): Boolean;
function IsCsiFinal(character: UnicodeChar): Boolean;
function ParseCsi(
csi: UnicodeString;
currentCell: TEnhancedVideoCell
): TEnhancedVideoCell;
function ParseCsiToken(
csi: UnicodeString;
currentCell: TEnhancedVideoCell
): TCsiParseResult;
end;
implementation
constructor TAnsiParser.Init(
var TheApp: TTurboGopherApplication;
defaultCell: TEnhancedVideoCell);
begin
App := TheApp;
sgrTokenRe := TRegExpr.Create('^(\d+);(\d+);(\d+)');
sgrRgbTokenRe := TRegExpr.Create('^(\d+);(\d+);(\d+);(\d+);(\d+)');
Logger := App.GetLogger();
DefaultCell := defaultCell;
Reset;
end;
procedure TAnsiParser.Reset;
begin
CsiStage := TCsiParseStage.None;
InAnsiParse := False;
end;
function TAnsiParser.ParseChar(
character: UnicodeChar;
var cell: TEnhancedVideoCell;
text: UnicodeString;
textIndex: Integer): Boolean;
begin
Result := True;
if CsiStage <> TCsiParseStage.None then
begin
Result := False;
(* detect parameter bytes *)
if CsiStage = TCsiParseStage.Parameter then
begin
if IsCsiParam(character) then
begin
CsiBuffer += character;
end
else if IsCsiIntermediate(character) then
begin
CsiBuffer += character;
CsiStage := TCsiParseStage.Intermediate;
end
else if IsCsiFinal(character) then
begin
CsiBuffer += character;
CsiStage := TCsiParseStage.Final;
end
else
begin
Logger^.Warning(
'Could not parse ANSI CSI sequence (after parsing parameter): '
+ 'Character: ' + character + ' Buffer: ' + CsiBuffer);
CsiStage := TCsiParseStage.None;
CsiBuffer := '';
end;
end
else if CsiStage = TCsiParseStage.Intermediate then
begin
if IsCsiIntermediate(character) then
begin
CsiBuffer += character;
end
else if IsCsiFinal(character) then
begin
CsiBuffer += character;
CsiStage := TCsiParseStage.Final;
end
else
begin
Logger^.Warning(
'Could not parse ANSI CSI sequence (after parsing intermediate): '
+ 'Character: ' + character + ' Buffer: ' + CsiBuffer);
CsiStage := TCsiParseStage.None;
CsiBuffer := '';
end;
end;
if CsiStage = TCsiParseStage.Final then
begin
cell := ParseCsi(CsiBuffer, cell);
CsiStage := TCsiParseStage.None;
CsiBuffer := '';
end;
Exit;
end;
(* Parse ANSI escapes. *)
if InAnsiParse = true then
begin
case text[textIndex + 1] of
'[': CsiStage := TCsiParseStage.Parameter;
end;
InAnsiParse := False;
Result := False;
Exit;
end;
(* Initiate ANSI escape parsing. *)
if text[textIndex + 1] = chr(27) then
begin
InAnsiParse := True;
Result := False;
Exit;
end;
end;
function TAnsiParser.Parsing: Boolean;
begin
if (CsiStage = None) then
begin
Result := InAnsiParse;
Exit;
end;
Result := True;
end;
(* ANSI SGR / CSI parsing helper *)
function TAnsiParser.IsCsiParam(character: UnicodeChar): Boolean;
begin
Result := false;
if (ord(character) >= $30) and (ord(character) <= $3f) then Result := true;
end;
(* ANSI SGR / CSI parsing helper *)
function TAnsiParser.IsCsiIntermediate(character: UnicodeChar): Boolean;
begin
Result := false;
if (ord(character) >= $20) and (ord(character) <= $2f) then Result := true;
end;
(* ANSI SGR / CSI parsing helper *)
function TAnsiParser.IsCsiFinal(character: UnicodeChar): Boolean;
begin
Result := false;
if (ord(character) >= $40) and (ord(character) <= $7e) then Result := true;
end;
(* ANSI SGR / CSI parsing helper *)
function TAnsiParser.ParseCsiToken(
csi: UnicodeString;
currentCell: TEnhancedVideoCell): TCsiParseResult;
var
red, green, blue, codeDouble: Double;
code: Integer = 0;
token: UnicodeString = '';
tokenIsNumber: Boolean = False;
savedAttributes: TEnhancedVideoAttributes;
sepPos: Integer = 0;
tokenAsInt: Integer = 0;
tokenAsColor: Integer = 0;
begin
savedAttributes := currentCell.EnhancedVideoAttributes;
red := 0;
green := 0;
blue := 0;
codeDouble := 0;
{ default result if we can't parse anything is to preseve current attrs. }
Result.tokensConsumed := 0;
Result.cell := currentCell;
{ get position of first token end. (either up to the first ; or m) }
sepPos := Pos(';', csi);
if sepPos = 0 then sepPos := Pos('m', csi);
{ if we couldn't get a position, we can't parse anything. }
if sepPos = 0 then Exit;
{ use position to get first token }
token := Copy(csi, 1, sepPos - 1);
{ CSI Reset }
if token = '0' then
begin
Result.cell := defaultCell;
Result.tokensConsumed := 1;
Exit;
end;
{ parse token }
try
tokenAsInt := StrToInt(UTF8Encode(token));
tokenIsNumber := True;
except
on E: EConvertError do
tokenIsNumber := False;
end;
if tokenIsNumber <> True then Exit;
(* Try to parse the token as a regular 16 color value *)
tokenAsColor := tokenAsInt - 30;
if (tokenAsColor >= 0) and (tokenAsColor <= 7) then
begin
{ parse as regular fg color }
Result.cell.ForegroundColor := tokenAsColor;
Result.tokensConsumed := 1;
end;
tokenAsColor := tokenAsInt - 90;
if (tokenAsColor >= 0) and (tokenAsColor <= 7) then
begin
{ parse as bright fg color }
Result.cell.ForegroundColor := tokenAsColor + 7;
Result.tokensConsumed := 1;
end;
tokenAsColor := tokenAsInt - 40;
if (tokenAsColor >= 0) and (tokenAsColor <= 7) then
begin
{ parse as regular bg color }
Result.cell.BackgroundColor := tokenAsColor;
Result.tokensConsumed := 1;
end;
tokenAsColor := tokenAsInt - 100;
if (tokenAsColor >= 0) and (tokenAsColor <= 7) then
begin
{ parse as bright bg color }
Result.cell.BackgroundColor := tokenAsColor + 7;
Result.tokensConsumed := 1;
end;
(* Try to parse fg or bg color settings of rgb and indexed colors *)
if (tokenAsInt = 48) or (tokenAsInt = 38) then
begin
if sgrRgbTokenRe.exec(csi) then
begin
if sgrRgbTokenRe.Match[2] = '2' then (* 2 = rgb *)
begin
red := StrToInt(UTF8Encode(sgrRgbTokenRe.Match[3]));
green := StrToInt(UTF8Encode(sgrRgbTokenRe.Match[4]));
blue := StrToInt(UTF8Encode(sgrRgbTokenRe.Match[5]));
codeDouble := (((red * 6) / 255) * 36)
+ (((green * 6) / 255) * 6)
+ ((blue * 6) / 255);
code := Round(codeDouble);
if code > 255 then code := 255;
if code < 0 then code := 0;
Result.tokensConsumed := 5;
end;
end
else if sgrTokenRe.Exec(csi) then
begin
if sgrTokenRe.Match[2] = '5' then (* 5 = indexed *)
begin
code := StrToInt(UTF8Encode(sgrTokenRe.Match[3]));
Result.tokensConsumed := 3;
end;
end;
(* Now that we have the indexed color set in 'code' we can set
either the fg or bg color. *)
if (code < 0) or (code > 255) then
begin
Logger^.Error(
'Index color out of range: '
+ UTF8Decode(IntToStr(code))
);
Exit;
end;
case tokenAsInt of
38: Result.cell.ForegroundColor := code;
48: Result.cell.BackgroundColor := code;
end;
Result.cell.EnhancedVideoAttributes := savedAttributes;
end;
end;
(* ANSI SGR / CSI parsing helper *)
function TAnsiParser.ParseCsi(
csi: UnicodeString;
currentCell: TEnhancedVideoCell
): TEnhancedVideoCell;
var
prevPos, nextPos, I: Integer;
sgrBuffer: UnicodeString;
parsedToken: TCsiParseResult;
success: Boolean;
begin
if csi = '38;5;233m' then
begin
sgrBuffer := 'a';
end;
sgrBuffer := '';
Result := currentCell;
success := False;
if Length(csi) < 2 then
begin
Logger^.Info('Ignoring unsupported CSI sequence: ' + csi);
Exit;
end;
if (csi[Length(csi)] <> 'm') and (csi[Length(csi)] <> 'n') then
begin
Logger^.Info('Ignoring unsupported CSI sequence: ' + csi);
Exit;
end;
I := 1;
prevPos := 0;
while I <= Length(csi) do
begin
{ read up to the next token or terminator }
nextPos := NPos(';', UTF8Encode(csi), I);
if nextPos = 0 then nextPos := Pos('m', csi);
if nextPos = 0 then break;
sgrBuffer := UTF8Decode(Copy(UTF8Encode(csi), prevPos + 1, nextPos));
{ attempt to parse the tokens so far }
parsedToken := ParseCsiToken(sgrBuffer, Result);
if parsedToken.tokensConsumed > 0 then
begin
{ parse success, write attrs, advance counter #tokens consumed. }
Result := parsedToken.cell;
prevPos := nextPos;
success := True;
end;
{ parse fail, try next token. }
I += 1;
end;
if success <> True then
begin
Logger^.Warning('Unable to parse csi token: ' + csi);
end;
end;
end.