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.
This commit is contained in:
Jason Hood 2017-12-10 22:52:38 +10:00
parent 645f57e59c
commit d20ab7471f
4 changed files with 181 additions and 82 deletions

230
ANSI.c
View File

@ -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
{

View File

@ -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"

View File

@ -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.

View File

@ -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