Ansi stuff works, some unicode translation implemented - baud.baby renders correctly!
This commit is contained in:
parent
ef83179147
commit
d8fcccecb5
|
@ -5,20 +5,28 @@ unit BrowserWidget;
|
|||
interface
|
||||
|
||||
uses
|
||||
DrawUtils,
|
||||
Logger,
|
||||
TurboGopherApplication,
|
||||
|
||||
Classes,
|
||||
CustApp,
|
||||
Drivers,
|
||||
Objects,
|
||||
Regexpr,
|
||||
StrUtils,
|
||||
SysUtils,
|
||||
Views;
|
||||
|
||||
type
|
||||
|
||||
TCsiParseResult = record
|
||||
attributes: byte;
|
||||
tokensConsumed: SizeInt;
|
||||
end;
|
||||
|
||||
TBrowserCharacter = record
|
||||
character: byte;
|
||||
attributtes: byte;
|
||||
attributes: byte;
|
||||
end;
|
||||
|
||||
TBrowserString = array of TBrowserCharacter;
|
||||
|
@ -32,90 +40,507 @@ type
|
|||
procedure Add(text: string);
|
||||
procedure Draw; virtual;
|
||||
private
|
||||
var App: TTurboGopherApplication;
|
||||
App: TTurboGopherApplication;
|
||||
Lines: array of TBrowserString;
|
||||
CurrentForegroundColor: byte;
|
||||
CurrentBackgroundColor: byte;
|
||||
const defaultAttrs = $10;
|
||||
const defaultAttrs = $1f;
|
||||
|
||||
end;
|
||||
PBrowserWidget = ^TBrowserWidget;
|
||||
|
||||
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(
|
||||
Logger: PLogger;
|
||||
token: string;
|
||||
currentAttrs, defaultAttrs: Byte): Byte;
|
||||
csi: AnsiString;
|
||||
currentAttrs, defaultAttrs: Byte): TCsiParseResult;
|
||||
var
|
||||
code: Integer;
|
||||
token: AnsiString;
|
||||
fg, bg, sepPos: Integer;
|
||||
begin
|
||||
{ default result if we can't parse anything is to preseve current attrs. }
|
||||
Result.tokensConsumed := 0;
|
||||
Result.attributes := currentAttrs;
|
||||
{ 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 := defaultAttrs;
|
||||
Result.attributes := defaultAttrs;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
{ split current attrs into a bg and fg. }
|
||||
bg := Hi(currentAttrs) * 16;
|
||||
fg := Lo(currentAttrs);
|
||||
{ parse token }
|
||||
case token of
|
||||
'30': Result := (Hi(currentAttrs) * $10) + $00; { fg = black }
|
||||
'31': Result := (Hi(currentAttrs) * $10) + $04; { fg = red }
|
||||
'32': Result := (Hi(currentAttrs) * $10) + $02; { fg = green }
|
||||
'33': Result := (Hi(currentAttrs) * $10) + $06; { fg = yellow }
|
||||
'34': Result := (Hi(currentAttrs) * $10) + $01; { fg = blue }
|
||||
'35': Result := (Hi(currentAttrs) * $10) + $05; { fg = magenta }
|
||||
'36': Result := (Hi(currentAttrs) * $10) + $03; { fg = cyan }
|
||||
'37': Result := (Hi(currentAttrs) * $10) + $07; { fg = white }
|
||||
'90': Result := (Hi(currentAttrs) * $10) + $08; { fg = bright black }
|
||||
'91': Result := (Hi(currentAttrs) * $10) + $0c; { fg = bright red }
|
||||
'92': Result := (Hi(currentAttrs) * $10) + $0a; { fg = bright green }
|
||||
'93': Result := (Hi(currentAttrs) * $10) + $0e; { fg = bright yellow }
|
||||
'94': Result := (Hi(currentAttrs) * $10) + $09; { fg = bright blue }
|
||||
'95': Result := (Hi(currentAttrs) * $10) + $0d; { fg = bright magenta }
|
||||
'96': Result := (Hi(currentAttrs) * $10) + $0b; { fg = bright cyan }
|
||||
'97': Result := (Hi(currentAttrs) * $10) + $0f; { fg = bright white }
|
||||
'40': Result := $00 + Lo(currentAttrs); { bg = black }
|
||||
'41': Result := $40 + Lo(currentAttrs); { bg = red }
|
||||
'42': Result := $20 + Lo(currentAttrs); { bg = green }
|
||||
'43': Result := $60 + Lo(currentAttrs); { bg = yellow }
|
||||
'44': Result := $10 + Lo(currentAttrs); { bg = blue }
|
||||
'45': Result := $50 + Lo(currentAttrs); { bg = magenta }
|
||||
'46': Result := $30 + Lo(currentAttrs); { bg = cyan }
|
||||
'47': Result := $70 + Lo(currentAttrs); { bg = white }
|
||||
'100': Result := $80 + Lo(currentAttrs); { bg = bright black }
|
||||
'101': Result := $c0 + Lo(currentAttrs); { bg = bright red }
|
||||
'102': Result := $a0 + Lo(currentAttrs); { bg = bright green }
|
||||
'103': Result := $e0 + Lo(currentAttrs); { bg = bright yellow }
|
||||
'104': Result := $90 + Lo(currentAttrs); { bg = bright blue }
|
||||
'105': Result := $d0 + Lo(currentAttrs); { bg = bright magenta }
|
||||
'106': Result := $b0 + Lo(currentAttrs); { bg = bright cyan }
|
||||
'107': Result := $f0 + Lo(currentAttrs); { bg = bright white }
|
||||
'30':
|
||||
begin { fg = black }
|
||||
Result.attributes := bg + $00;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'31':
|
||||
begin { fg = red }
|
||||
Result.attributes := bg + $04;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'32':
|
||||
begin { fg = green }
|
||||
Result.attributes := bg + $02;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'33':
|
||||
begin { fg = yellow }
|
||||
Result.attributes := bg + $06;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'34':
|
||||
begin { fg = blue }
|
||||
Result.attributes := bg + $01;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'35':
|
||||
begin { fg = magenta }
|
||||
Result.attributes := bg + $05;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'36':
|
||||
begin { fg = cyan }
|
||||
Result.attributes := bg + $03;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'37':
|
||||
begin { fg = white }
|
||||
Result.attributes := bg + $07;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'90':
|
||||
begin { fg = bright black }
|
||||
Result.attributes := bg + $08;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'91':
|
||||
begin { fg = bright red }
|
||||
Result.attributes := bg + $0c;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'92':
|
||||
begin { fg = bright green }
|
||||
Result.attributes := bg + $0a;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'93':
|
||||
begin { fg = bright yellow }
|
||||
Result.attributes := bg + $0e;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'94':
|
||||
begin { fg = bright blue }
|
||||
Result.attributes := bg + $09;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'95': { fg = bright magenta }
|
||||
begin
|
||||
Result.attributes := bg + $0d;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'96':
|
||||
begin { fg = bright cyan }
|
||||
Result.attributes := bg + $0b;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'97':
|
||||
begin { fg = bright white }
|
||||
Result.attributes := bg + $0f;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'40':
|
||||
begin { bg = black }
|
||||
Result.attributes := $00 + fg;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'41':
|
||||
begin { bg = red }
|
||||
Result.attributes := $40 + fg;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'42':
|
||||
begin { bg = green }
|
||||
Result.attributes := $20 + fg;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'43':
|
||||
begin { bg = yellow }
|
||||
Result.attributes := $60 + fg;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'44':
|
||||
begin { bg = blue }
|
||||
Result.attributes := $10 + fg;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'45':
|
||||
begin { bg = magenta }
|
||||
Result.attributes := $50 + fg;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'46':
|
||||
begin { bg = cyan }
|
||||
Result.attributes := $30 + fg;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'47': { bg = white }
|
||||
begin
|
||||
Result.attributes := $70 + fg;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'100':
|
||||
begin { bg = bright black }
|
||||
Result.attributes := $80 + fg;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'101':
|
||||
begin { bg = bright red }
|
||||
Result.attributes := $c0 + fg;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'102':
|
||||
begin { bg = bright green }
|
||||
Result.attributes := $a0 + fg;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'103':
|
||||
begin { bg = bright yellow }
|
||||
Result.attributes := $e0 + fg;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'104':
|
||||
begin { bg = bright blue }
|
||||
Result.attributes := $90 + fg;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'105':
|
||||
begin { bg = bright magenta }
|
||||
Result.attributes := $d0 + fg;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'106':
|
||||
begin { bg = bright cyan }
|
||||
Result.attributes := $b0 + fg;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'107': { bg = bright white }
|
||||
begin
|
||||
Result.attributes := $f0 + fg;
|
||||
Result.tokensConsumed := 1;
|
||||
Exit;
|
||||
end;
|
||||
'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.attributes := bg + $00;
|
||||
Result.tokensConsumed := 3;
|
||||
Exit;
|
||||
end;
|
||||
1, 52, 88, 124, 202..205:
|
||||
begin { red }
|
||||
Result.attributes := bg + $04;
|
||||
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.attributes := bg + $02;
|
||||
Result.tokensConsumed := 3;
|
||||
Exit;
|
||||
end;
|
||||
3, 136, 172, 208, 142..145, 178..181, 214..216:
|
||||
begin { yellow }
|
||||
Result.attributes := bg + $06;
|
||||
Result.tokensConsumed := 3;
|
||||
Exit;
|
||||
end;
|
||||
4, 17..21, 24..27:
|
||||
begin { blue }
|
||||
Result.attributes := bg + $01;
|
||||
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.attributes := bg + $05;
|
||||
Result.tokensConsumed := 3;
|
||||
Exit;
|
||||
end;
|
||||
6, 29..31, 66..67, 37..39, 73..75:
|
||||
begin { cyan }
|
||||
Result.attributes := bg + $03;
|
||||
Result.tokensConsumed := 3;
|
||||
Exit;
|
||||
end;
|
||||
7, 231, 101, 102, 244..251:
|
||||
begin { white }
|
||||
Result.attributes := bg + $07;
|
||||
Result.tokensConsumed := 3;
|
||||
Exit;
|
||||
end;
|
||||
8, 238..243:
|
||||
begin { bright black }
|
||||
Result.attributes := bg + $08;
|
||||
Result.tokensConsumed := 3;
|
||||
Exit;
|
||||
end;
|
||||
9, 160, 196, 217..218:
|
||||
begin { bright red }
|
||||
Result.attributes := bg + $0c;
|
||||
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.attributes := bg + $0a;
|
||||
Result.tokensConsumed := 3;
|
||||
Exit;
|
||||
end;
|
||||
11, 184..188, 220..223, 226..230:
|
||||
begin { bright yellow }
|
||||
Result.attributes := bg + $0e;
|
||||
Result.tokensConsumed := 3;
|
||||
Exit;
|
||||
end;
|
||||
12, 32..33, 68..69, 103..105, 111, 152..153, 87, 123, 159, 195:
|
||||
begin { bright blue }
|
||||
Result.attributes := bg + $09;
|
||||
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.attributes := bg + $0d;
|
||||
Result.tokensConsumed := 3;
|
||||
Exit;
|
||||
end;
|
||||
14, 44..45, 80..81, 116..117, 50..51, 85..86, 122, 158, 194:
|
||||
begin { bright cyan }
|
||||
Result.attributes := bg + $0b;
|
||||
Result.tokensConsumed := 3;
|
||||
Exit;
|
||||
end;
|
||||
15, 252..255:
|
||||
begin { bright white }
|
||||
Result.attributes := bg + $0f;
|
||||
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.attributes := $00 + fg;
|
||||
Result.tokensConsumed := 3;
|
||||
Exit;
|
||||
end;
|
||||
1, 52, 88, 124, 202..205:
|
||||
begin { red }
|
||||
Result.attributes := $40 + fg;
|
||||
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.attributes := $20 + fg;
|
||||
Result.tokensConsumed := 3;
|
||||
Exit;
|
||||
end;
|
||||
3, 136, 172, 208, 142..145, 178..181, 214..216:
|
||||
begin { yellow }
|
||||
Result.attributes := $60 + fg;
|
||||
Result.tokensConsumed := 3;
|
||||
Exit;
|
||||
end;
|
||||
4, 17..21, 24..27:
|
||||
begin { blue }
|
||||
Result.attributes := $10 + fg;
|
||||
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.attributes := $50 + fg;
|
||||
Result.tokensConsumed := 3;
|
||||
Exit;
|
||||
end;
|
||||
6, 29..31, 66..67, 37..39, 73..75:
|
||||
begin { cyan }
|
||||
Result.attributes := $30 + fg;
|
||||
Result.tokensConsumed := 3;
|
||||
Exit;
|
||||
end;
|
||||
7, 231, 101, 102, 244..251:
|
||||
begin { white }
|
||||
Result.attributes := $70 + fg;
|
||||
Result.tokensConsumed := 3;
|
||||
Exit;
|
||||
end;
|
||||
8, 238..243:
|
||||
begin { bright black }
|
||||
Result.attributes := $80 + fg;
|
||||
Result.tokensConsumed := 3;
|
||||
Exit;
|
||||
end;
|
||||
9, 160, 196, 217..218:
|
||||
begin { bright red }
|
||||
Result.attributes := $c0 + fg;
|
||||
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.attributes := $a0 + fg;
|
||||
Result.tokensConsumed := 3;
|
||||
Exit;
|
||||
end;
|
||||
11, 184..188, 220..223, 226..230:
|
||||
begin { bright yellow }
|
||||
Result.attributes := $e0 + fg;
|
||||
Result.tokensConsumed := 3;
|
||||
Exit;
|
||||
end;
|
||||
12, 32..33, 68..69, 103..105, 111, 152..153, 87, 123, 159, 195:
|
||||
begin { bright blue }
|
||||
Result.attributes := $90 + fg;
|
||||
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.attributes := $d0 + fg;
|
||||
Result.tokensConsumed := 3;
|
||||
Exit;
|
||||
end;
|
||||
14, 44..45, 80..81, 116..117, 50..51, 85..86, 122, 158, 194:
|
||||
begin { bright cyan }
|
||||
Result.attributes := $b0 + fg;
|
||||
Result.tokensConsumed := 3;
|
||||
Exit;
|
||||
end;
|
||||
15, 252..255:
|
||||
begin { bright white }
|
||||
Result.attributes := $f0 + fg;
|
||||
Result.tokensConsumed := 3;
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
function ParseCsi(Logger: PLogger; csi: string; defaultAttrs: Byte): Byte;
|
||||
(* ANSI SGR / CSI parsing helper *)
|
||||
function ParseCsi(
|
||||
Logger: PLogger;
|
||||
csi: string;
|
||||
currentAttrs, defaultAttrs: Byte
|
||||
): Byte;
|
||||
var
|
||||
I: Integer;
|
||||
prevPos, nextPos, I: Integer;
|
||||
sgrBuffer: AnsiString;
|
||||
parsedToken: TCsiParseResult;
|
||||
success: Boolean;
|
||||
begin
|
||||
if csi = '38;5;233m' then
|
||||
begin
|
||||
sgrBuffer := 'a';
|
||||
end;
|
||||
sgrBuffer := '';
|
||||
Result := defaultAttrs;
|
||||
Result := currentAttrs;
|
||||
success := False;
|
||||
if Length(csi) < 2 then
|
||||
begin
|
||||
Logger^.Info('Ignoring unsupported CSI sequence: ' + csi);
|
||||
|
@ -126,20 +551,30 @@ begin
|
|||
Logger^.Info('Ignoring unsupported CSI sequence: ' + csi);
|
||||
Exit;
|
||||
end;
|
||||
for I := 1 to Length(csi) do
|
||||
I := 1;
|
||||
prevPos := 0;
|
||||
while I <= Length(csi) do
|
||||
begin
|
||||
if csi[I] = 'm' then
|
||||
{ read up to the next token or terminator }
|
||||
nextPos := NPos(';', csi, I);
|
||||
if nextPos = 0 then nextPos := Pos('m', csi);
|
||||
if nextPos = 0 then break;
|
||||
sgrBuffer := Copy(csi, prevPos + 1, nextPos);
|
||||
{ attempt to parse the tokens so far }
|
||||
parsedToken := ParseCsiToken(sgrBuffer, Result, defaultAttrs);
|
||||
if parsedToken.tokensConsumed > 0 then
|
||||
begin
|
||||
Result := ParseCsiToken(Logger, sgrBuffer, Result, defaultAttrs);
|
||||
Exit;
|
||||
{ parse success, write attrs, advance counter #tokens consumed. }
|
||||
Result := parsedToken.attributes;
|
||||
prevPos := nextPos;
|
||||
success := True;
|
||||
end;
|
||||
if csi[I] = ';' then
|
||||
begin
|
||||
Result := ParseCsiToken(Logger, sgrBuffer, Result, defaultAttrs);
|
||||
sgrBuffer := '';
|
||||
continue;
|
||||
end;
|
||||
sgrBuffer += csi[I];
|
||||
{ parse fail, try next token. }
|
||||
I += 1;
|
||||
end;
|
||||
if success <> True then
|
||||
begin
|
||||
Logger^.Warning('Unable to parse csi token: ' + csi);
|
||||
end;
|
||||
end;
|
||||
|
||||
|
@ -157,6 +592,7 @@ begin
|
|||
CurrentBackgroundColor := Lo(defaultAttrs);
|
||||
GrowMode := gfGrowHiX + gfGrowHiY;
|
||||
SetLimit(128, 10);
|
||||
sgrTokenRe := TRegExpr.Create('^(\d+);(\d+);(\d+)');
|
||||
end;
|
||||
|
||||
procedure TBrowserWidget.Add(text: string);
|
||||
|
@ -250,7 +686,7 @@ begin
|
|||
end;
|
||||
if CsiStage = TCsiParseStage.Final then
|
||||
begin
|
||||
Attrs := ParseCsi(Logger, CsiBuffer, defaultAttrs);
|
||||
Attrs := ParseCsi(Logger, CsiBuffer, Attrs, defaultAttrs);
|
||||
CsiStage := TCsiParseStage.None;
|
||||
CsiBuffer := '';
|
||||
end;
|
||||
|
@ -275,7 +711,7 @@ begin
|
|||
continue;
|
||||
end;
|
||||
NewChar.character := ord(text[I + 1]);
|
||||
NewChar.attributtes := Attrs;
|
||||
NewChar.attributes := Attrs;
|
||||
SetLength(Line, Length(Line) + 1);
|
||||
Line[Length(Line) - 1] := NewChar;
|
||||
PC := CC;
|
||||
|
@ -292,14 +728,15 @@ procedure TBrowserWidget.Draw;
|
|||
var
|
||||
I: Integer;
|
||||
DrawBuffer: TDrawBuffer;
|
||||
X, Y, LineCount, LongestLine: SizeInt;
|
||||
DrawBufferIndex, X, Y, LineCount, LongestLine: SizeInt;
|
||||
C: TBrowserCharacter;
|
||||
begin
|
||||
DrawBuffer := default(TDrawBuffer);
|
||||
DrawBufferIndex := 0;
|
||||
LongestLine := 0;
|
||||
LineCount := Length(Lines);
|
||||
{ clear the screen }
|
||||
MoveChar(DrawBuffer, ' ', defaultAttrs, Size.X);
|
||||
AddToDrawBuf(DrawBuffer, 32, defaultAttrs, Size.X);
|
||||
WriteLine(0, 0, Size.X, Size.Y, DrawBuffer);
|
||||
DrawBuffer := default(TDrawBuffer);
|
||||
{ render characters }
|
||||
|
@ -313,9 +750,11 @@ begin
|
|||
begin
|
||||
if (Length(Lines[I]) - 1 < X) then continue;
|
||||
C := Lines[I][X];
|
||||
MoveChar(DrawBuffer, chr(C.character), C.attributtes, 1);
|
||||
WriteBuf(x - Delta.X, Y - Delta.Y, 1, 1, DrawBuffer);
|
||||
SetInDrawBuf(DrawBuffer, C.character, C.attributes, DrawBufferIndex);
|
||||
DrawBufferIndex += 1;
|
||||
end;
|
||||
WriteBuf(0, Y - Delta.Y, DrawBufferIndex, 1, DrawBuffer);
|
||||
DrawBufferIndex := 0;
|
||||
end;
|
||||
end;
|
||||
SetLimit(LongestLine, LineCount);
|
||||
|
|
|
@ -1,34 +1,39 @@
|
|||
unit MainWindow;
|
||||
unit BrowserWindow;
|
||||
|
||||
{$mode objfpc}{$H+}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
GopherClient,
|
||||
BrowserWidget,
|
||||
TurboGopherApplication,
|
||||
TurboGopherWindow,
|
||||
|
||||
App,
|
||||
Classes,
|
||||
SysUtils,
|
||||
CustApp,
|
||||
Objects,
|
||||
Views,
|
||||
TurboGopherApplication,
|
||||
GopherClient,
|
||||
BrowserWidget;
|
||||
SysUtils,
|
||||
Views;
|
||||
|
||||
type
|
||||
TMainWindow = class
|
||||
PBrowserWindow = ^TBrowserWindow;
|
||||
TBrowserWindow = class(TTurboGopherWindow)
|
||||
public
|
||||
constructor Create(var TheApp: TTurboGopherApplication);
|
||||
procedure Get(url: AnsiString);
|
||||
private
|
||||
Rect: TRect;
|
||||
Win: PWindow;
|
||||
var App: TTurboGopherApplication;
|
||||
Text: AnsiString;
|
||||
Browser: PBrowserWidget;
|
||||
public
|
||||
constructor Create(var TheApp: TTurboGopherApplication);
|
||||
procedure Get();
|
||||
App: TTurboGopherApplication;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
constructor TMainWindow.Create(var TheApp: TTurboGopherApplication);
|
||||
constructor TBrowserWindow.Create(var TheApp: TTurboGopherApplication);
|
||||
begin
|
||||
App := TheApp;
|
||||
(* Figure out where to position ourselves *)
|
||||
|
@ -53,16 +58,15 @@ implementation
|
|||
end;
|
||||
end;
|
||||
|
||||
procedure TMainWindow.Get();
|
||||
procedure TBrowserWindow.Get(url: AnsiString);
|
||||
var
|
||||
url: string;
|
||||
client: TGopherClient;
|
||||
begin
|
||||
client := App.GetClient();
|
||||
url := 'gopher://gopher.linkerror.com/0/testfile';
|
||||
text := client.Get(url);
|
||||
Browser^.Add(text);
|
||||
Browser^.Draw;
|
||||
Win^.Draw;
|
||||
end;
|
||||
|
||||
end.
|
|
@ -0,0 +1,46 @@
|
|||
unit DrawUtils;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Views;
|
||||
|
||||
(* This is a custom version of MoveChar that doesn't preserve the original
|
||||
attributes in the buffer if attributes equals zero. *)
|
||||
procedure AddToDrawBuf(
|
||||
var buffer: TDrawBuffer;
|
||||
character: Byte;
|
||||
attributes: Byte;
|
||||
count: SizeInt);
|
||||
|
||||
(* Same as above, but allows for setting characters at a specific index. *)
|
||||
procedure SetInDrawBuf(
|
||||
var buffer: TDrawBuffer;
|
||||
character: Byte;
|
||||
attributes: Byte;
|
||||
position: SizeInt);
|
||||
|
||||
implementation
|
||||
|
||||
procedure AddToDrawBuf(
|
||||
var buffer: TDrawBuffer;
|
||||
character: Byte;
|
||||
attributes: Byte;
|
||||
count: SizeInt);
|
||||
var
|
||||
P: SizeInt;
|
||||
begin
|
||||
for P := 0 to count do
|
||||
buffer[P] := (attributes * 256) + Byte(character);
|
||||
end;
|
||||
|
||||
procedure SetInDrawBuf(
|
||||
var buffer: TDrawBuffer;
|
||||
character: Byte;
|
||||
attributes: Byte;
|
||||
position: SizeInt);
|
||||
begin
|
||||
buffer[position] := (attributes * 256) + Byte(character);
|
||||
end;
|
||||
|
||||
end.
|
|
@ -45,6 +45,40 @@ type
|
|||
|
||||
implementation
|
||||
|
||||
{ Helper functions }
|
||||
|
||||
(* The idea of this is to replace widely used multibyte characters in UTF8
|
||||
(mostly block drawing stuff, with their nearest equivalent in ANSI rather
|
||||
than just showing an unknown glyph question mark. *)
|
||||
function UTF8Hack(const inputString: RawByteString): AnsiString;
|
||||
begin
|
||||
Result := inputString;
|
||||
Result := Result.Replace(chr($e2) + chr($95) + chr($b1), '/');
|
||||
Result := Result.Replace(chr($e2) + chr($95) + chr($b2), '\');
|
||||
Result := Result.Replace(chr($e2) + chr($96) + chr($80), chr($df));
|
||||
Result := Result.Replace(chr($e2) + chr($96) + chr($84), chr($dc));
|
||||
Result := Result.Replace(chr($e2) + chr($96) + chr($91), chr($b0));
|
||||
Result := Result.Replace(chr($e2) + chr($96) + chr($92), chr($b1));
|
||||
Result := Result.Replace(chr($e2) + chr($96) + chr($93), chr($b2));
|
||||
Result := Result.Replace(chr($e2) + chr($96) + chr($88), chr($db));
|
||||
Result := Result.Replace(chr($e2) + chr($96) + chr($8c), chr($dd));
|
||||
Result := Result.Replace(chr($e2) + chr($96) + chr($96), chr($dd));
|
||||
Result := Result.Replace(chr($e2) + chr($96) + chr($98), chr($dd));
|
||||
Result := Result.Replace(chr($e2) + chr($96) + chr($90), chr($de));
|
||||
Result := Result.Replace(chr($e2) + chr($96) + chr($97), chr($de));
|
||||
Result := Result.Replace(chr($e2) + chr($96) + chr($9c), chr($de));
|
||||
Result := Result.Replace(chr($e2) + chr($96) + chr($9d), chr($de));
|
||||
Result := Result.Replace(chr($e2) + chr($96) + chr($99), chr($db));
|
||||
Result := Result.Replace(chr($e2) + chr($96) + chr($9b), chr($db));
|
||||
Result := Result.Replace(chr($e2) + chr($96) + chr($9c), chr($db));
|
||||
Result := Result.Replace(chr($e2) + chr($96) + chr($9f), chr($db));
|
||||
Result := Result.Replace(chr($e2) + chr($88) + chr($99), chr($f9));
|
||||
Result := Result.Replace(chr($e2) + chr($80) + chr($ba), '>');
|
||||
Result := Result.Replace(chr($c2) + chr($b7), chr($fa));
|
||||
end;
|
||||
|
||||
{ TGopherClient }
|
||||
|
||||
constructor TGopherClient.Create(LoggerObject: PLogger);
|
||||
begin
|
||||
Logger := LoggerObject;
|
||||
|
@ -66,7 +100,7 @@ implementation
|
|||
|
||||
for I := 0 to Length(Lines) - 1 do
|
||||
begin
|
||||
if (Length(Lines) > 2) then
|
||||
if (Length(Lines) > 0) then
|
||||
begin
|
||||
if Lines[I] = '.' then Exit; { Single dot marks the end. }
|
||||
MenuItem := ParseMenuItem(Lines[I]);
|
||||
|
@ -174,8 +208,8 @@ implementation
|
|||
begin
|
||||
Logger^.Error('Could not connect to host: ' + TokenizedUrl.Host
|
||||
+ ' on port ' + IntToStr(TokenizedUrl.Port)
|
||||
+ ' - Error: ' + E. Message);
|
||||
ClientSocket.Free;
|
||||
+ ' - Error: ' + E.Message);
|
||||
if ClientSocket <> nil then ClientSocket.Free;
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
|
@ -201,6 +235,7 @@ implementation
|
|||
Result += Part;
|
||||
Buf := '';
|
||||
end;
|
||||
Result := UTF8Hack(Result);
|
||||
Logger^.Debug('Successfully retrieved ' + Url);
|
||||
ClientSocket.Free;
|
||||
Menu := ParseMenu(Result);
|
||||
|
|
|
@ -5,11 +5,14 @@ unit LogWindow;
|
|||
interface
|
||||
|
||||
uses
|
||||
App,
|
||||
|
||||
Logger,
|
||||
TurboGopherApplication,
|
||||
TurboGopherWindow,
|
||||
|
||||
App,
|
||||
Classes,
|
||||
CustApp,
|
||||
Drivers,
|
||||
Objects,
|
||||
SysUtils,
|
||||
|
@ -17,6 +20,8 @@ uses
|
|||
|
||||
type
|
||||
|
||||
PCustomApplication = ^TCustomApplication;
|
||||
|
||||
TLogWidget = object(TScroller)
|
||||
constructor Init(
|
||||
LoggerObject: PLogger;
|
||||
|
@ -27,30 +32,36 @@ type
|
|||
private
|
||||
var Logger: PLogger;
|
||||
var Filter: TLogLevelFilter;
|
||||
const defaultAttrs = $1f;
|
||||
end;
|
||||
PLogWidget = ^TLogWidget;
|
||||
|
||||
TLogWindow = class
|
||||
TLogWindow = class(TTurboGopherWindow)
|
||||
public
|
||||
constructor Create(var TheApp: TTurboGopherApplication);
|
||||
|
||||
constructor Init(TheApp: TTurboGopherApplication); virtual;
|
||||
procedure OnLogMessage(
|
||||
const Message: string;
|
||||
const Level: TLogLevel
|
||||
);
|
||||
private
|
||||
var App: TTurboGopherApplication;
|
||||
LogWidget: TLogWidget;
|
||||
LogWidget: PLogWidget;
|
||||
Rect: TRect;
|
||||
Win: PWindow;
|
||||
var App: TTurboGopherApplication;
|
||||
end;
|
||||
PLogWindow = ^TLogWindow;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
DrawUtils;
|
||||
|
||||
{ TLogWidget }
|
||||
|
||||
constructor TLogWidget.Init(
|
||||
LoggerObject: PLogger;
|
||||
Bounds: TRect;
|
||||
Bounds: Objects.TRect;
|
||||
AHScrollBar, AVScrollBar: PScrollBar);
|
||||
begin
|
||||
TScroller.Init(Bounds, AHScrollBar, AVScrollBar);
|
||||
|
@ -82,12 +93,13 @@ implementation
|
|||
ToLine := FromLine + Size.Y;
|
||||
LogEntries := Logger^.GetRange(FromLine, ToLine, Filter);
|
||||
|
||||
Color := GetColor($FF);
|
||||
Color := defaultAttrs;
|
||||
LineCount := Length(LogEntries);
|
||||
|
||||
{ clear the screen }
|
||||
MoveChar(DrawBuffer, ' ', Color, Size.X);
|
||||
WriteLine(0, 0, Size.X, Size.Y, DrawBuffer);
|
||||
DrawBuffer := default(TDrawBuffer);
|
||||
|
||||
{ render characters }
|
||||
for L := 0 to LineCount - 1 do
|
||||
|
@ -97,14 +109,22 @@ implementation
|
|||
if Length(LogEntry.Message) > LongestLine then
|
||||
LongestLine := Length(LogEntry.Message);
|
||||
Str := Copy(LogEntry.Message, Delta.X + 1, Size.X);
|
||||
WriteStr(0, Y, Str, Color);
|
||||
case LogEntry.Level of
|
||||
TLogLevel.debug: Color := (Hi(Color) * 16) + 2;
|
||||
TLogLevel.info: Color := (Hi(Color) * 16) + 15;
|
||||
TLogLevel.warning: Color := (Hi(Color) * 16) + 6;
|
||||
TLogLevel.error: Color := (Hi(Color) * 16) + 12;
|
||||
TLogLevel.fatal: Color := (4 * 16) + 0;
|
||||
end;
|
||||
MoveStr(DrawBuffer, Str, Color);
|
||||
WriteBuf(0, Y, Length(Str), 1, DrawBuffer);
|
||||
end;
|
||||
SetLimit(LongestLine, Logger^.Count(Filter));
|
||||
end;
|
||||
|
||||
{ TLogWindow }
|
||||
|
||||
constructor TLogWindow.Create(var TheApp: TTurboGopherApplication);
|
||||
constructor TLogWindow.Init(TheApp: TTurboGopherApplication);
|
||||
begin
|
||||
App := TheApp;
|
||||
(* Figure out where we're going to put ourselves - a window height of
|
||||
|
@ -114,20 +134,19 @@ implementation
|
|||
Rect.Move(0, -2); { move up to account for the borders }
|
||||
Win := New(PWindow, Init(Rect, 'Log messages', wnNoNumber));
|
||||
Desktop^.Insert(Win);
|
||||
|
||||
Win^.GetExtent(Rect);
|
||||
Rect.Grow(-2, -1);
|
||||
LogWidget.Init(
|
||||
LogWidget := New(PLogWidget, Init(
|
||||
App.GetLogger(),
|
||||
Rect,
|
||||
Win^.StandardScrollBar(sbHorizontal),
|
||||
Win^.StandardScrollBar(sbVertical)
|
||||
);
|
||||
Win^.Insert(@LogWidget);
|
||||
|
||||
));
|
||||
if LogWidget = nil then
|
||||
raise Exception.Create('Could not instantiate log widget.');
|
||||
Win^.Insert(LogWidget);
|
||||
(* Register our callback into the logger. *)
|
||||
App.GetLogger()^.RegisterCallback(@OnLogMessage);
|
||||
|
||||
end;
|
||||
|
||||
procedure TLogWindow.OnLogMessage(
|
||||
|
@ -135,7 +154,8 @@ implementation
|
|||
const Level: TLogLevel
|
||||
);
|
||||
begin
|
||||
LogWidget.Draw;
|
||||
LogWidget^.Draw;
|
||||
Win^.Draw;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
|
|
@ -49,11 +49,11 @@ implementation
|
|||
|
||||
uses
|
||||
LogWindow,
|
||||
MainWindow;
|
||||
BrowserWindow;
|
||||
|
||||
var
|
||||
FLogWindow: TLogWindow;
|
||||
FMainWindow: TMainWindow;
|
||||
FBrowserWindow: TBrowserWindow;
|
||||
|
||||
{ TTGApp }
|
||||
|
||||
|
@ -114,8 +114,8 @@ implementation
|
|||
Logger := TLogger.Create;
|
||||
FileLogger := TFileLogger.Create(@Logger, '/tmp/turbogopher_debug.txt');
|
||||
Client := TGopherClient.Create(@Logger);
|
||||
FLogWindow := TLogWindow.Create(Self);
|
||||
FMainWindow := TMainWindow.Create(Self);
|
||||
FLogWindow := TLogWindow.Init(Self);
|
||||
FBrowserWindow := TBrowserWindow.Create(Self);
|
||||
end;
|
||||
|
||||
procedure TTurboGopherApplication.DoRun;
|
||||
|
@ -138,7 +138,7 @@ implementation
|
|||
Exit;
|
||||
end;
|
||||
{ TEST }
|
||||
FMainWindow.Get;
|
||||
FBrowserWindow.Get('gopher://baud.baby');
|
||||
{ Run TG app }
|
||||
TurboGraphicsApplication.Run;
|
||||
{ Clean shutdown }
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
unit TurboGopherWindow;
|
||||
|
||||
{$mode objfpc}{$H+}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes;
|
||||
|
||||
type
|
||||
|
||||
TTurboGopherWindow = class abstract
|
||||
public
|
||||
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
end.
|
Loading…
Reference in New Issue