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:
parent
645f57e59c
commit
d20ab7471f
230
ANSI.c
230
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
|
||||
{
|
||||
|
@ -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"
|
||||
|
23
readme.txt
23
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.
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user