Skip to content

Commit 31899ae

Browse files
committed
FEAT: ANSI escape sequence emulation under Windows.
1 parent 81016ec commit 31899ae

File tree

1 file changed

+251
-9
lines changed

1 file changed

+251
-9
lines changed

src/os/win32/dev-stdio.c

+251-9
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646

4747
#include "reb-host.h"
4848
#include "host-lib.h"
49+
#include "sys-scan.h"
4950

5051
#define BUF_SIZE (16*1024) // MS restrictions apply
5152

@@ -66,6 +67,32 @@ static BOOL Redir_Inp = 0;
6667
extern REBDEV *Devices[];
6768

6869

70+
//** ANSI emulation definition ******************************************
71+
#define FOREGROUND_BLACK 0x0000
72+
//#define FOREGROUND_BLUE 0x0001
73+
//#define FOREGROUND_GREEN 0x0002
74+
#define FOREGROUND_CYAN 0x0003
75+
//#define FOREGROUND_RED 0x0004
76+
#define FOREGROUND_MAGENTA 0x0005
77+
#define FOREGROUND_YELLOW 0x0006
78+
#define FOREGROUND_GREY 0x0007
79+
//#define FOREGROUND_INTENSITY 0x0008
80+
#define FOREGROUND_WHITE 0x000F
81+
//#define BACKGROUND_BLUE 0x0010
82+
#define BACKGROUND_CYAN 0x0030
83+
//#define BACKGROUND_GREEN 0x0020
84+
//#define BACKGROUND_RED 0x0040
85+
#define BACKGROUND_MAGENTA 0x0050
86+
#define BACKGROUND_YELLOW 0x0060
87+
#define BACKGROUND_GREY 0x0070
88+
//#define BACKGROUND_INTENSITY 0x0080
89+
#define COMMON_LVB_UNDERSCORE 0x8000
90+
91+
static COORD Std_coord = {0, 0};
92+
93+
int Update_Graphic_Mode(int attribute, int value);
94+
REBYTE* Parse_ANSI_sequence(REBYTE *cp, REBYTE *ep);
95+
6996
//**********************************************************************
7097

7198
BOOL WINAPI Handle_Break(DWORD dwCtrlType)
@@ -82,7 +109,7 @@ BOOL WINAPI Handle_Break(DWORD dwCtrlType)
82109
static dbgout(char *fmt, int d, char *s)
83110
{
84111
char buf[255];
85-
FILE *f = fopen("dbgout.txt", "w");
112+
FILE *f = fopen("dbgout.txt", "a");
86113
sprintf(buf, fmt, d, s);
87114
fwrite(buf, strlen(buf), 1, f);
88115
fclose(f);
@@ -234,6 +261,9 @@ static void close_stdio(void)
234261
long len;
235262
long total = 0;
236263
BOOL ok = FALSE;
264+
REBYTE *bp;
265+
REBYTE *cp;
266+
REBYTE *ep;
237267

238268
if (GET_FLAG(req->modes, RDM_NULL)) {
239269
req->actual = req->length;
@@ -244,21 +274,41 @@ static void close_stdio(void)
244274

245275
if (Redir_Out) { // Always UTF-8
246276
ok = WriteFile(Std_Out, req->data, req->length, &total, 0);
277+
if (!ok) {
278+
req->error = GetLastError();
279+
return DR_ERROR;
280+
}
247281
}
248282
else {
249283
// Convert UTF-8 buffer to Win32 wide-char format for console.
250284
// Thankfully, MS provides something other than mbstowcs();
251285
// however, if our buffer overflows, it's an error. There's no
252286
// efficient way at this level to split-up the input data,
253287
// because its UTF-8 with variable char sizes.
254-
len = MultiByteToWideChar(CP_UTF8, 0, req->data, req->length, Std_Buf, BUF_SIZE);
255-
if (len > 0) // no error
256-
ok = WriteConsoleW(Std_Out, Std_Buf, len, &total, 0);
257-
}
258-
259-
if (!ok) {
260-
req->error = GetLastError();
261-
return DR_ERROR;
288+
bp = req->data;
289+
ep = bp + req->length;
290+
291+
do {
292+
cp = Skip_To_Char(bp, ep, (REBYTE)27); //find ANSI escape char "^["
293+
//if found, write to the console content before it starts, else everything
294+
if (cp){
295+
len = MultiByteToWideChar(CP_UTF8, 0, bp, cp - bp, Std_Buf, BUF_SIZE);
296+
} else {
297+
len = MultiByteToWideChar(CP_UTF8, 0, bp, ep - bp, Std_Buf, BUF_SIZE);
298+
bp = ep;
299+
}
300+
if (len > 0) {// no error
301+
ok = WriteConsoleW(Std_Out, Std_Buf, len, &total, 0);
302+
if (!ok) {
303+
req->error = GetLastError();
304+
return DR_ERROR;
305+
}
306+
}
307+
//is escape char was found, parse the ANSI sequence...
308+
if (cp) {
309+
bp = Parse_ANSI_sequence(++cp, ep);
310+
}
311+
} while (bp < ep);
262312
}
263313

264314
req->actual = req->length; // do not use "total" (can be byte or wide)
@@ -377,6 +427,198 @@ DEFINE_DEV(Dev_StdIO, "Standard IO", 1, Dev_Cmds, RDC_MAX, 0);
377427

378428

379429

430+
/***********************************************************************
431+
**
432+
*/ int Update_Graphic_Mode(int attribute, int value)
433+
/*
434+
**
435+
***********************************************************************/
436+
{
437+
CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
438+
int tmp;
439+
440+
if (attribute < 0) {
441+
GetConsoleScreenBufferInfo(Std_Out, &csbiInfo);
442+
attribute = csbiInfo.wAttributes;
443+
}
444+
445+
switch (value) {
446+
case 0: attribute = FOREGROUND_GREY; break;
447+
case 1: attribute = attribute | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; break;
448+
case 4: attribute = attribute | COMMON_LVB_UNDERSCORE; break;
449+
case 7: tmp = (attribute & 0xF0) >> 4;
450+
attribute = ((attribute & 0x0F) << 4) | tmp; break; //reverse
451+
case 30: attribute = attribute & 0xF8; break;
452+
case 31: attribute = (attribute & 0xF8) | FOREGROUND_RED; break;
453+
case 32: attribute = (attribute & 0xF8) | FOREGROUND_GREEN; break;
454+
case 33: attribute = (attribute & 0xF8) | FOREGROUND_YELLOW; break;
455+
case 34: attribute = (attribute & 0xF8) | FOREGROUND_BLUE; break;
456+
case 35: attribute = (attribute & 0xF8) | FOREGROUND_MAGENTA; break;
457+
case 36: attribute = (attribute & 0xF8) | FOREGROUND_CYAN; break;
458+
case 37: attribute = (attribute & 0xF8) | FOREGROUND_GREY; break;
459+
case 39: attribute = attribute & 0xF7; break; //FOREGROUND_INTENSITY reset
460+
case 40: attribute = attribute & 0x8F; break;
461+
case 41: attribute = (attribute & 0x8F) | BACKGROUND_RED; break;
462+
case 42: attribute = (attribute & 0x8F) | BACKGROUND_GREEN; break;
463+
case 43: attribute = (attribute & 0x8F) | BACKGROUND_YELLOW; break;
464+
case 44: attribute = (attribute & 0x8F) | BACKGROUND_BLUE; break;
465+
case 45: attribute = (attribute & 0x8F) | BACKGROUND_MAGENTA; break;
466+
case 46: attribute = (attribute & 0x8F) | BACKGROUND_CYAN; break;
467+
case 47: attribute = (attribute & 0x8F) | BACKGROUND_GREY; break;
468+
case 49: attribute = attribute & 0x7F; break; //BACKGROUND_INTENSITY reset
469+
default: attribute = value;
470+
}
471+
return attribute;
472+
}
473+
474+
/***********************************************************************
475+
**
476+
*/ REBYTE* Parse_ANSI_sequence(REBYTE *cp, REBYTE *ep)
477+
/*
478+
** Parses ANSI sequence and return number of bytes used.
479+
** Based on http://ascii-table.com/ansi-escape-sequences.php
480+
**
481+
***********************************************************************/
482+
{
483+
if(*cp != '[') return cp;
484+
485+
int state = 1;
486+
int value1 = 0;
487+
int value2 = 0;
488+
int attribute = -1;
489+
int num;
490+
int len;
491+
COORD coordScreen;
492+
CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
493+
494+
495+
do {
496+
if(++cp == ep) return cp;
497+
498+
switch (state) {
499+
500+
case 1: //value1 start
501+
if( *cp >= (int)'0' && *cp <= (int)'9') {
502+
value1 = ((value1 * 10) + (*cp - (int)'0')) % 0xFFFF;
503+
state = 2;
504+
} else if (*cp == ';') {
505+
//do nothing
506+
} else if (*cp == 's') {
507+
//Saves the current cursor position.
508+
GetConsoleScreenBufferInfo(Std_Out, &csbiInfo);
509+
Std_coord.X = csbiInfo.dwCursorPosition.X;
510+
Std_coord.Y = csbiInfo.dwCursorPosition.Y;
511+
state = -1;
512+
} else if (*cp == 'u') {
513+
//Returns the cursor to the position stored by the Save Cursor Position sequence.
514+
SetConsoleCursorPosition(Std_Out, Std_coord);
515+
state = -1;
516+
} else if (*cp == 'K') {
517+
//TODO: Erase Line.
518+
state = -1;
519+
} else if (*cp == 'J') {
520+
//TODO: Clear screen from cursor down.
521+
state = -1;
522+
} else if (*cp == 'H' || *cp == 'f') {
523+
coordScreen.X = 0;
524+
coordScreen.Y = 0;
525+
SetConsoleCursorPosition(Std_Out, coordScreen);
526+
state = -1;
527+
} else {
528+
state = -1;
529+
}
530+
break;
531+
case 2: //value1 continue
532+
if( *cp >= (int)'0' && *cp <= (int)'9') {
533+
value1 = ((value1 * 10) + (*cp - (int)'0')) % 0xFFFF;
534+
state = 2;
535+
} else if (*cp == ';') {
536+
state = 3;
537+
} else if (*cp == 'm') {
538+
attribute = Update_Graphic_Mode(attribute, value1);
539+
SetConsoleTextAttribute(Std_Out, attribute);
540+
state = -1;
541+
} else if (*cp == 'A') {
542+
//Cursor Up.
543+
GetConsoleScreenBufferInfo(Std_Out, &csbiInfo);
544+
csbiInfo.dwCursorPosition.Y = MAX(0, csbiInfo.dwCursorPosition.Y - value1);
545+
SetConsoleCursorPosition(Std_Out, csbiInfo.dwCursorPosition);
546+
state = -1;
547+
} else if (*cp == 'B') {
548+
//Cursor Down.
549+
GetConsoleScreenBufferInfo(Std_Out, &csbiInfo);
550+
csbiInfo.dwCursorPosition.Y = MIN(csbiInfo.dwSize.Y, csbiInfo.dwCursorPosition.Y + value1);
551+
SetConsoleCursorPosition(Std_Out, csbiInfo.dwCursorPosition);
552+
state = -1;
553+
} else if (*cp == 'C') {
554+
//Cursor Forward.
555+
GetConsoleScreenBufferInfo(Std_Out, &csbiInfo);
556+
csbiInfo.dwCursorPosition.X = MIN(csbiInfo.dwSize.X, csbiInfo.dwCursorPosition.X + value1);
557+
SetConsoleCursorPosition(Std_Out, csbiInfo.dwCursorPosition);
558+
state = -1;
559+
} else if (*cp == 'D') {
560+
//Cursor Backward.
561+
GetConsoleScreenBufferInfo(Std_Out, &csbiInfo);
562+
csbiInfo.dwCursorPosition.X = MAX(0, csbiInfo.dwCursorPosition.X - value1);
563+
SetConsoleCursorPosition(Std_Out, csbiInfo.dwCursorPosition);
564+
state = -1;
565+
} else if (*cp == 'J') {
566+
if (value1 == 2) {
567+
GetConsoleScreenBufferInfo(Std_Out, &csbiInfo);
568+
len = csbiInfo.dwSize.X * csbiInfo.dwSize.Y;
569+
coordScreen.X = 0;
570+
coordScreen.Y = 0;
571+
FillConsoleOutputCharacter(Std_Out, (TCHAR)' ', len, coordScreen, &num);
572+
FillConsoleOutputAttribute(Std_Out, csbiInfo.wAttributes, len, coordScreen, &num);
573+
SetConsoleCursorPosition(Std_Out, coordScreen);
574+
}
575+
state = -1;
576+
} else {
577+
state = -1;
578+
}
579+
break; //End CASE 2
580+
case 3: //value2 start
581+
if( *cp >= (int)'0' && *cp <= (int)'9') {
582+
value2 = ((value2 * 10) + (*cp - (int)'0')) % 0xFFFF;
583+
state = 4;
584+
} else if (*cp == ';') {
585+
//do nothing
586+
} else {
587+
state = -1;
588+
}
589+
break; //End CASE 3
590+
case 4: //value2 continue
591+
if( *cp >= (int)'0' && *cp <= (int)'9') {
592+
value2 = ((value2 * 10) + (*cp - (int)'0')) % 0xFFFF;
593+
state = 4;
594+
} else if (*cp == 'm') {
595+
attribute = Update_Graphic_Mode(attribute, value1);
596+
attribute = Update_Graphic_Mode(attribute, value2);
597+
SetConsoleTextAttribute(Std_Out, attribute);
598+
state = -1;
599+
} else if (*cp == ';') {
600+
attribute = Update_Graphic_Mode(attribute, value1);
601+
attribute = Update_Graphic_Mode(attribute, value2);
602+
SetConsoleTextAttribute(Std_Out, attribute);
603+
value1 = 0;
604+
value2 = 0;
605+
state = 1;
606+
} else if (*cp == 'H' || *cp == 'f') {
607+
coordScreen.X = value1;
608+
coordScreen.Y = value2;
609+
SetConsoleCursorPosition(Std_Out, coordScreen);
610+
state = -1;
611+
} else {
612+
state = -1;
613+
}
614+
615+
616+
} //End: switch (state)
617+
} while (state >= 0);
618+
619+
return ++cp;
620+
}
621+
380622
//*** Old fragments ***************************************************
381623

382624
#if OLD_CONSOLE_FILE_IO

0 commit comments

Comments
 (0)