256 color support and rgb color support
This commit is contained in:
parent
5f759df74e
commit
b1b87b4cd2
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
Loading…
Reference in New Issue