From d20ab7471fe4412ad69db57d537f713e2b299f93 Mon Sep 17 00:00:00 2001 From: Jason Hood Date: Sun, 10 Dec 2017 22:52:38 +1000 Subject: [PATCH] Add tab handling Support setting (HTS & DECST8C) and clearing (TBC) tabs, overriding the console's own tab processing. I've extended TBC with `\e[8g` to restore console processing and added an extra parameter to DECST8C to set a particular tab size. --- ANSI.c | 230 ++++++++++++++++++++++++++++++++++---------------- ansicon.c | 2 +- readme.txt | 23 +++-- sequences.txt | 8 ++ 4 files changed, 181 insertions(+), 82 deletions(-) diff --git a/ANSI.c b/ANSI.c index 3859921..6519c80 100644 --- a/ANSI.c +++ b/ANSI.c @@ -169,7 +169,8 @@ added IND, NEL & RI (using buffer, in keeping with LF); added DA, DECCOLM, DECNCSM, DECSC & DECRC; an explicit zero parameter should still default to one; - restrict parameters to a maximum value of 32767. + restrict parameters to a maximum value of 32767; + added tab handling. */ #include "ansicon.h" @@ -199,7 +200,8 @@ struct Cache } cache[CACHE]; #define ESC '\x1B' // ESCape character -#define BEL '\x07' +#define BEL '\x07' // BELl +#define HT '\x09' // Horizontal Tabulation #define SO '\x0E' // Shift Out #define SI '\x0F' // Shift In @@ -334,6 +336,8 @@ typedef BOOL (WINAPI *PHCSBIX)( PHCSBIX GetConsoleScreenBufferInfoX, SetConsoleScreenBufferInfoX; +#define MAX_TABS 2048 + typedef struct { BYTE foreground; // ANSI base color (0 to 7; add 30) @@ -349,6 +353,8 @@ typedef struct SHORT buf_width; // buffer width prior to setting 132 columns SHORT win_width; // window width prior to setting 132 columns BYTE noclear; // don't clear the screen on column mode change + BYTE tabs; // handle tabs directly + BYTE tab_stop[MAX_TABS]; } STATE, *PSTATE; PSTATE pState; @@ -704,6 +710,19 @@ void send_palette_sequence( COLORREF c ) SendSequence( buf ); } + +// Clear existing tabs and set tab stops at every size columns. +void init_tabs( int size ) +{ + int i; + + memset( pState->tab_stop, FALSE, MAX_TABS ); + for (i = 0; i < MAX_TABS; i += size) + pState->tab_stop[i] = TRUE; + pState->tabs = TRUE; +} + + // ========== Print functions //----------------------------------------------------------------------------- @@ -751,80 +770,91 @@ void InterpretEscSeq( void ) if (prefix == '[') { - if (prefix2 == '?' && (suffix == 'h' || suffix == 'l') && es_argc == 1) + if (prefix2 == '?') { - switch (es_argv[0]) + if (suffix == 'h' || suffix == 'l') { - case 25: - GetConsoleCursorInfo( hConOut, &CursInfo ); - CursInfo.bVisible = (suffix == 'h'); - SetConsoleCursorInfo( hConOut, &CursInfo ); - return; - - case 7: - mode = cache[0].mode; - if (suffix == 'h') - mode |= ENABLE_WRAP_AT_EOL_OUTPUT; - else - mode &= ~ENABLE_WRAP_AT_EOL_OUTPUT; - SetConsoleMode( hConOut, mode ); - return; - - case 95: - pState->noclear = (suffix == 'h'); - return; - - case 3: + if (es_argc != 1) return; + switch (es_argv[0]) { - COORD buf; - SMALL_RECT win; - buf.X = (suffix == 'l') ? pState->buf_width : 132; - if (buf.X == 0) - return; - GetConsoleScreenBufferInfo( hConOut, &Info ); - buf.Y = HEIGHT; - win.Left = 0; - win.Top = TOP; - win.Bottom = BOTTOM; - if (suffix == 'h') + case 25: + GetConsoleCursorInfo( hConOut, &CursInfo ); + CursInfo.bVisible = (suffix == 'h'); + SetConsoleCursorInfo( hConOut, &CursInfo ); + return; + + case 7: + mode = cache[0].mode; + if (suffix == 'h') + mode |= ENABLE_WRAP_AT_EOL_OUTPUT; + else + mode &= ~ENABLE_WRAP_AT_EOL_OUTPUT; + SetConsoleMode( hConOut, mode ); + return; + + case 95: + pState->noclear = (suffix == 'h'); + return; + + case 3: { - pState->buf_width = WIDTH; - pState->win_width = WIN.Right - WIN.Left; - win.Right = 131; + COORD buf; + SMALL_RECT win; + buf.X = (suffix == 'l') ? pState->buf_width : 132; + if (buf.X == 0) + return; + GetConsoleScreenBufferInfo( hConOut, &Info ); + buf.Y = HEIGHT; + win.Left = 0; + win.Top = TOP; + win.Bottom = BOTTOM; + if (suffix == 'h') + { + pState->buf_width = WIDTH; + pState->win_width = WIN.Right - WIN.Left; + win.Right = 131; + } + else + { + win.Right = pState->win_width; + pState->buf_width = 0; + } + // The buffer cannot be smaller than the window; the window cannot + // be bigger than the buffer. + if (WIN.Right - WIN.Left > win.Right) + { + SetConsoleWindowInfo( hConOut, TRUE, &win ); + SetConsoleScreenBufferSize( hConOut, buf ); + } + else + { + SetConsoleScreenBufferSize( hConOut, buf ); + SetConsoleWindowInfo( hConOut, TRUE, &win ); + } + // Even if the screen is not cleared, scroll in a new window the + // first time this is used. + if (pState->noclear && + (suffix2 == '+' || (TOP == screen_top && CUR.Y != LAST))) + { + CUR.X = LEFT; + CUR.Y = (suffix2 == '+') ? 0 : TOP; + SetConsoleCursorPosition( hConOut, CUR ); + return; + } + prefix2 = 0; + es_argv[0] = 2; + suffix = 'J'; + break; } - else - { - win.Right = pState->win_width; - pState->buf_width = 0; - } - // The buffer cannot be smaller than the window; the window cannot - // be bigger than the buffer. - if (WIN.Right - WIN.Left > win.Right) - { - SetConsoleWindowInfo( hConOut, TRUE, &win ); - SetConsoleScreenBufferSize( hConOut, buf ); - } - else - { - SetConsoleScreenBufferSize( hConOut, buf ); - SetConsoleWindowInfo( hConOut, TRUE, &win ); - } - // Even if the screen is not cleared, scroll in a new window the - // first time this is used. - if (pState->noclear && - (suffix2 == '+' || (TOP == screen_top && CUR.Y != LAST))) - { - CUR.X = LEFT; - CUR.Y = (suffix2 == '+') ? 0 : TOP; - SetConsoleCursorPosition( hConOut, CUR ); - return; - } - prefix2 = 0; - es_argv[0] = 2; - suffix = 'J'; - break; } } + else if (suffix == 'W') + { + if (es_argv[0] != 5 || es_argc > 2) return; + if (es_argc == 1) es_argv[1] = 8; + init_tabs( es_argv[1] ); + return; + } } // Ignore any other private sequences. if (prefix2 != 0) @@ -1185,10 +1215,38 @@ void InterpretEscSeq( void ) SetConsoleCursorPosition( hConOut, Pos ); return; + case 'g': + if (es_argc > 1) return; // ESC[g == ESC[0g + switch (es_argv[0]) + { + case 0: // ESC[0g Clear tab at cursor + if (!pState->tabs) init_tabs( 8 ); + if (CUR.X < MAX_TABS) pState->tab_stop[CUR.X] = FALSE; + return; + + case 3: // ESC[3g Clear all tabs + memset( pState->tab_stop, FALSE, MAX_TABS ); + pState->tabs = TRUE; + return; + + case 8: // ESC[8g Let console handle tabs + pState->tabs = FALSE; + return; + + default: + return; + } + case 'I': // ESC[#I Moves cursor forward # tabs if (es_argc > 1) return; // ESC[I == ESC[1I Pos.Y = CUR.Y; - Pos.X = (CUR.X & -8) + p1 * 8; + if (pState->tabs) + { + Pos.X = CUR.X; + while (++Pos.X < MAX_TABS && (!pState->tab_stop[Pos.X] || --p1 > 0)) ; + } + else + Pos.X = (CUR.X & -8) + p1 * 8; if (Pos.X > RIGHT) Pos.X = RIGHT; SetConsoleCursorPosition( hConOut, Pos ); return; @@ -1196,10 +1254,18 @@ void InterpretEscSeq( void ) case 'Z': // ESC[#Z Moves cursor back # tabs if (es_argc > 1) return; // ESC[Z == ESC[1Z Pos.Y = CUR.Y; - if ((CUR.X & 7) == 0) - Pos.X = CUR.X - p1 * 8; + if (pState->tabs) + { + Pos.X = (CUR.X < MAX_TABS) ? CUR.X : MAX_TABS; + while (--Pos.X > 0 && (!pState->tab_stop[Pos.X] || --p1 > 0)) ; + } else - Pos.X = (CUR.X & -8) - (p1 - 1) * 8; + { + if ((CUR.X & 7) == 0) + Pos.X = CUR.X - p1 * 8; + else + Pos.X = (CUR.X & -8) - (p1 - 1) * 8; + } if (Pos.X < LEFT) Pos.X = LEFT; SetConsoleCursorPosition( hConOut, Pos ); return; @@ -1579,6 +1645,15 @@ ParseAndPrintString( HANDLE hDev, } else if (c == SO) shifted = TRUE; else if (c == SI) shifted = G0_special; + else if (c == HT && pState != NULL && pState->tabs) + { + CONSOLE_SCREEN_BUFFER_INFO Info; + FlushBuffer(); + GetConsoleScreenBufferInfo( hConOut, &Info ); + while (++CUR.X < MAX_TABS && !pState->tab_stop[CUR.X]) ; + if (CUR.X > RIGHT) CUR.X = RIGHT; + SetConsoleCursorPosition( hConOut, CUR ); + } else PushBuffer( (WCHAR)c ); } else if (state == 2) @@ -1622,6 +1697,15 @@ ParseAndPrintString( HANDLE hDev, ScrollUp(); state = 1; } + else if (c == 'H') // HTS Character Tabulation Set + { + CONSOLE_SCREEN_BUFFER_INFO Info; + if (!pState->tabs) init_tabs( 8 ); + FlushBuffer(); + GetConsoleScreenBufferInfo( hConOut, &Info ); + if (CUR.X < MAX_TABS) pState->tab_stop[CUR.X] = TRUE; + state = 1; + } else if (c == '7' || // DECSC Save Cursor c == '8') // DECRC Restore Cursor { diff --git a/ansicon.c b/ansicon.c index fa74051..aa9a768 100644 --- a/ansicon.c +++ b/ansicon.c @@ -91,7 +91,7 @@ use -pu to unload from the parent. */ -#define PDATE L"9 December, 2017" +#define PDATE L"10 December, 2017" #include "ansicon.h" #include "version.h" diff --git a/readme.txt b/readme.txt index 2a39341..f4f6f52 100644 --- a/readme.txt +++ b/readme.txt @@ -150,7 +150,8 @@ Usage Sequences Recognised ==================== - The following escape sequences are recognised. + The following escape sequences are recognised (see "sequences.txt" for a + more complete description). \e]0;titleBEL xterm: Set window's title (and icon, ignored) \e]2;titleBEL xterm: Set window's title @@ -183,6 +184,8 @@ Sequences Recognised \e[#;#;#...,~ DECPS DEC Play Sound \e8 DECRC DEC Restore Cursor \e7 DECSC DEC Save Cursor + \e[?5W DECST8C DEC Set Tab at Every 8 Columns + \e[?5;#W DECST8C DEC Set Tab at Every # Columns (ANSICON extension) \e[?25h DECTCEM DEC Text Cursor Enable Mode (show cursor) \e[?25l DECTCEM DEC Text Cursor Enable Mode (hide cursor) \e[#M DL Delete Line @@ -193,6 +196,8 @@ Sequences Recognised \e[#` HPA Character Position Absolute \e[#j HPB Character Position Backward \e[#a HPR Character Position Forward + HT HT Character Tabulation + \eH HTS Character Tabulation Set \e[#;#f HVP Character And Line Position \e[#@ ICH Insert Character \e[#L IL Insert Line @@ -205,13 +210,14 @@ Sequences Recognised \e(0 SCS Select Character Set (DEC special graphics) \e(B SCS Select Character Set (ASCII) \e[#;#;#m SGR Select Graphic Rendition + \e[#g TBC Tabulation Clear \e[#d VPA Line Position Absolute \e[#k VPB Line Position Backward \e[#e VPR Line Position Forward '\e' represents the escape character (ASCII 27); '#' represents a decimal - number (optional, in most cases defaulting to 1); BEL, SO and SI are ASCII - 7, 14 and 15. See "sequences.txt" for a more complete description. + number (optional, in most cases defaulting to 1); BEL, HT, SO and SI are + ASCII 7, 9, 14 and 15. Escape followed by a control character will display that character, not perform its function; an unrecognised character will preserve the escape. @@ -286,7 +292,7 @@ DEC Special Graphics Character Set Limitations =========== - Tabs are fixed at eight columns. + Tabs can only be set up to column 2048. The saved position will not be restored correctly if the buffer scrolls. Palette sequences only work from Vista. @@ -304,7 +310,7 @@ Version History Legend: + added, - bug-fixed, * changed. - 1.80 - 9 December, 2017: + 1.80 - 10 December, 2017: - fix unloading; - fix -e et al when redirecting to CON; - hook CreateFile and CreateConsoleScreenBuffer to force read/write access @@ -323,7 +329,8 @@ Version History + added palette sequences; + added -pu to unload from the parent; + added IND, NEL, RI, DA, DECCOLM, DECNCSM, DECSC & DECRC; - + added SCS, but only for special/ASCII (same as Win10). + + added SCS, but only for special/ASCII (same as Win10); + + added tab handling (HT, HTS, TBC & DECST8C). 1.72 - 24 December, 2015: - handle STD_OUTPUT_HANDLE & STD_ERROR_HANDLE in WriteFile; @@ -551,5 +558,5 @@ Distribution in LICENSE.txt. -============================ -Jason Hood, 9 December, 2017. +============================== +Jason Hood, 10 December, 2017. diff --git a/sequences.txt b/sequences.txt index 38af483..28d7a9f 100644 --- a/sequences.txt +++ b/sequences.txt @@ -83,6 +83,14 @@ M move cursor up one line (scroll if necessary; always uses buffer) [#C move cursor right # characters [D move cursor left one character [#D move cursor left # characters + +H set tab stop +[g remove tab stop at cursor +[0g as above +[3g remove all tab stops +[8g restore console tab handling (ANSICON extension) +[?5W set tab stops every 8 columns +[?5;#W set tab stops every # columns (ANSICON extension) [I move cursor forward one tab [#I move cursor forward # tabs [Z move cursor back one tab