From 1fe1d8f1305ef9ef7e8d8b57e3281c69a9e563e9 Mon Sep 17 00:00:00 2001 From: Jason Hood Date: Wed, 26 Feb 2014 21:18:45 +1000 Subject: [PATCH] Recognise more sequences; ignore newline immediately after wrap. Added support for CHT & CBT (move forward/backward by tabs), DECAWM (don't wrap at EOL), CRM (display control characters, but still perform newline) and REP (repeat last character, including BEL, BS, TAB, LF and CR). It always bugged me that newline would add an unneeded blank line due to wrap, but not enough to do anything about it. For some reason, adding CRM got me to thinking about it, so I finally did it. Stopped \e[K from erasing the first character of the next line. Restore cursor visibility on unload. --- ANSI.c | 289 +++++++++++++++++++++++++++++++++++++++++++------- ansicon.c | 2 +- readme.txt | 18 +++- sequences.txt | 15 ++- 4 files changed, 278 insertions(+), 46 deletions(-) diff --git a/ANSI.c b/ANSI.c index 975817f..06f3007 100644 --- a/ANSI.c +++ b/ANSI.c @@ -111,7 +111,7 @@ v1.66, 20 & 21 September, 2013: fix 32-bit process trying to detect 64-bit process. - v1.70, 25 January to 20 February, 2014: + v1.70, 25 January to 26 February, 2014: don't hook ourself from LoadLibrary or LoadLibraryEx; update the LoadLibraryEx flags that should not cause hooking; inject by manipulating the import directory table; for 64-bit AnyCPU use @@ -127,7 +127,10 @@ better parsing of escape & CSI sequences; ignore xterm 38 & 48 SGR values; change G1 blank from space to U+00A0 - No-Break Space; - use window height, not buffer. + use window height, not buffer; + added more sequences; + don't add a newline immediately after a wrap; + restore cursor visibility on unload. */ #include "ansicon.h" @@ -140,6 +143,8 @@ HANDLE hConOut; // handle to CONOUT$ WORD orgattr; // original attributes +DWORD orgmode; // original mode +CONSOLE_CURSOR_INFO orgcci; // original cursor state #define ESC '\x1B' // ESCape character #define BEL '\x07' @@ -258,6 +263,7 @@ typedef struct BYTE rvideo; // swap foreground/bold & background/underline BYTE concealed; // set foreground/bold to background/underline BYTE reverse; // swap console foreground & background attributes + BYTE crm; // showing control characters? COORD SavePos; // saved cursor position } STATE, *PSTATE; @@ -396,6 +402,8 @@ BOOL search_env( LPCTSTR var, LPCTSTR val ) int nCharInBuffer; WCHAR ChBuffer[BUFFER_SIZE]; +WCHAR ChPrev; +BOOL fWrapped; //----------------------------------------------------------------------------- // FlushBuffer() @@ -405,8 +413,65 @@ WCHAR ChBuffer[BUFFER_SIZE]; void FlushBuffer( void ) { DWORD nWritten; + if (nCharInBuffer <= 0) return; - WriteConsole( hConOut, ChBuffer, nCharInBuffer, &nWritten, NULL ); + + if (pState->crm) + { + DWORD mode; + GetConsoleMode( hConOut, &mode ); + SetConsoleMode( hConOut, mode & ~ENABLE_PROCESSED_OUTPUT ); + WriteConsole( hConOut, ChBuffer, nCharInBuffer, &nWritten, NULL ); + SetConsoleMode( hConOut, mode ); + } + else + { + HANDLE hConWrap; + CONSOLE_CURSOR_INFO cci; + CONSOLE_SCREEN_BUFFER_INFO csbi; + + if (nCharInBuffer < 4) + { + LPWSTR b = ChBuffer; + do + { + WriteConsole( hConOut, b, 1, &nWritten, NULL ); + if (*b != '\r' && *b != '\b' && *b != '\a') + { + GetConsoleScreenBufferInfo( hConOut, &csbi ); + if (csbi.dwCursorPosition.X == 0) + fWrapped = TRUE; + } + } while (++b, --nCharInBuffer); + } + else + { + // To detect wrapping of multiple characters, create a new buffer, write + // to the top of it and see if the cursor changes line. This doesn't + // always work on the normal buffer, since if you're already on the last + // line, wrapping scrolls everything up and still leaves you on the last. + hConWrap = CreateConsoleScreenBuffer( GENERIC_READ|GENERIC_WRITE, 0, NULL, + CONSOLE_TEXTMODE_BUFFER, NULL ); + // Even though the buffer isn't visible, the cursor still shows up. + cci.dwSize = 1; + cci.bVisible = FALSE; + SetConsoleCursorInfo( hConWrap, &cci ); + // Ensure the buffer is the same width (it gets created using the window + // width) and more than one line. + GetConsoleScreenBufferInfo( hConOut, &csbi ); + csbi.dwSize.Y = csbi.srWindow.Bottom - csbi.srWindow.Top + 2; + SetConsoleScreenBufferSize( hConWrap, csbi.dwSize ); + // Put the cursor on the top line, in the same column. + csbi.dwCursorPosition.Y = 0; + SetConsoleCursorPosition( hConWrap, csbi.dwCursorPosition ); + WriteConsole( hConWrap, ChBuffer, nCharInBuffer, &nWritten, NULL ); + GetConsoleScreenBufferInfo( hConWrap, &csbi ); + if (csbi.dwCursorPosition.Y != 0) + fWrapped = TRUE; + CloseHandle( hConWrap ); + WriteConsole( hConOut, ChBuffer, nCharInBuffer, &nWritten, NULL ); + } + } nCharInBuffer = 0; } @@ -417,11 +482,73 @@ void FlushBuffer( void ) void PushBuffer( WCHAR c ) { - if (shifted && c >= FIRST_G1 && c <= LAST_G1) - c = G1[c-FIRST_G1]; - ChBuffer[nCharInBuffer] = c; - if (++nCharInBuffer == BUFFER_SIZE) + CONSOLE_SCREEN_BUFFER_INFO csbi; + DWORD nWritten; + + ChPrev = c; + + if (c == '\n') + { + if (pState->crm) + ChBuffer[nCharInBuffer++] = c; FlushBuffer(); + // Avoid writing the newline if wrap has already occurred. + GetConsoleScreenBufferInfo( hConOut, &csbi ); + if (pState->crm) + { + // If we're displaying controls, then the only way we can be on the left + // margin is if wrap occurred. + if (csbi.dwCursorPosition.X != 0) + WriteConsole( hConOut, L"\n", 1, &nWritten, NULL ); + } + else + { + LPCWSTR nl = L"\n"; + if (fWrapped) + { + // It's wrapped, but was anything more written? Look at the current + // row, checking that each character is space in current attributes. + // If it's all blank we can drop the newline. If the cursor isn't + // already at the margin, then it was spaces or tabs that caused the + // wrap, which can be ignored and overwritten. + CHAR_INFO blank; + PCHAR_INFO row; + row = malloc( csbi.dwSize.X * sizeof(CHAR_INFO) ); + if (row != NULL) + { + COORD s, c; + SMALL_RECT r; + s.X = csbi.dwSize.X; + s.Y = 1; + c.X = c.Y = 0; + r.Left = 0; + r.Right = s.X - 1; + r.Top = r.Bottom = csbi.dwCursorPosition.Y; + ReadConsoleOutput( hConOut, row, s, c, &r ); + blank.Char.UnicodeChar = ' '; + blank.Attributes = csbi.wAttributes; + while (*(PDWORD)&row[c.X] == *(PDWORD)&blank) + if (++c.X == s.X) + { + nl = (csbi.dwCursorPosition.X == 0) ? NULL : L"\r"; + break; + } + free( row ); + } + fWrapped = FALSE; + } + if (nl) + WriteConsole( hConOut, nl, 1, &nWritten, NULL ); + } + } + else + { + if (shifted && c >= FIRST_G1 && c <= LAST_G1) + c = G1[c-FIRST_G1]; + ChBuffer[nCharInBuffer] = c; + if (++nCharInBuffer == BUFFER_SIZE) + FlushBuffer(); + } } //----------------------------------------------------------------------------- @@ -474,12 +601,15 @@ void InterpretEscSeq( void ) COORD Pos; SMALL_RECT Rect; CHAR_INFO CharInfo; + DWORD mode; #define WIDTH Info.dwSize.X #define CUR Info.dwCursorPosition #define WIN Info.srWindow #define TOP WIN.Top #define BOTTOM WIN.Bottom +#define LEFT 0 +#define RIGHT (WIDTH - 1) #define FillBlank( len, Pos ) \ FillConsoleOutputCharacter( hConOut, ' ', len, Pos, &NumberOfCharsWritten );\ @@ -488,13 +618,21 @@ void InterpretEscSeq( void ) if (prefix == '[') { - if (prefix2 == '?' && (suffix == 'h' || suffix == 'l')) + if (prefix2 == '?' && (suffix == 'h' || suffix == 'l') && es_argc == 1) { - if (es_argc == 1 && es_argv[0] == 25) + switch (es_argv[0]) { - GetConsoleCursorInfo( hConOut, &CursInfo ); - CursInfo.bVisible = (suffix == 'h'); - SetConsoleCursorInfo( hConOut, &CursInfo ); + case 25: + GetConsoleCursorInfo( hConOut, &CursInfo ); + CursInfo.bVisible = (suffix == 'h'); + SetConsoleCursorInfo( hConOut, &CursInfo ); + return; + + case 7: + mode = ENABLE_PROCESSED_OUTPUT; + if (suffix == 'h') + mode |= ENABLE_WRAP_AT_EOL_OUTPUT; + SetConsoleMode( hConOut, mode ); return; } } @@ -506,7 +644,6 @@ void InterpretEscSeq( void ) switch (suffix) { case 'm': - get_state(); if (es_argc == 0) es_argv[es_argc++] = 0; for (i = 0; i < es_argc; i++) { @@ -653,8 +790,8 @@ void InterpretEscSeq( void ) { BOTTOM = Info.dwSize.Y - 1; TOP = BOTTOM - range; - Rect.Left = 0; - Rect.Right = WIDTH - 1; + Rect.Left = LEFT; + Rect.Right = RIGHT; Rect.Top = CUR.Y - TOP; Rect.Bottom = CUR.Y - 1; Pos.X = Pos.Y = 0; @@ -665,7 +802,7 @@ void InterpretEscSeq( void ) SetConsoleWindowInfo( hConOut, TRUE, &WIN ); screen_top = TOP; } - Pos.X = 0; + Pos.X = LEFT; Pos.Y = TOP; len = (BOTTOM - TOP + 1) * WIDTH; FillBlank( len, Pos ); @@ -683,18 +820,18 @@ void InterpretEscSeq( void ) switch (es_argv[0]) { case 0: // ESC[0K Clear to end of line - len = WIDTH - CUR.X + 1; + len = WIDTH - CUR.X; FillBlank( len, CUR ); return; case 1: // ESC[1K Clear from start of line to cursor - Pos.X = 0; + Pos.X = LEFT; Pos.Y = CUR.Y; FillBlank( CUR.X + 1, Pos ); return; case 2: // ESC[2K Clear whole line. - Pos.X = 0; + Pos.X = LEFT; Pos.Y = CUR.Y; FillBlank( WIDTH, Pos ); return; @@ -712,11 +849,11 @@ void InterpretEscSeq( void ) case 'L': // ESC[#L Insert # blank lines. if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[L == ESC[1L if (es_argc != 1) return; - Rect.Left = WIN.Left = 0; - Rect.Right = WIN.Right = WIDTH - 1; + Rect.Left = WIN.Left = LEFT; + Rect.Right = WIN.Right = RIGHT; Rect.Top = CUR.Y; Rect.Bottom = BOTTOM; - Pos.X = 0; + Pos.X = LEFT; Pos.Y = CUR.Y + es_argv[0]; CharInfo.Char.UnicodeChar = ' '; CharInfo.Attributes = Info.wAttributes; @@ -727,11 +864,11 @@ void InterpretEscSeq( void ) case 'M': // ESC[#M Delete # lines. if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[M == ESC[1M if (es_argc != 1) return; - Rect.Left = WIN.Left = 0; - Rect.Right = WIN.Right = WIDTH - 1; + Rect.Left = WIN.Left = LEFT; + Rect.Right = WIN.Right = RIGHT; Rect.Bottom = BOTTOM; Rect.Top = CUR.Y - es_argv[0]; - Pos.X = 0; + Pos.X = LEFT; Pos.Y = TOP = CUR.Y; CharInfo.Char.UnicodeChar = ' '; CharInfo.Attributes = Info.wAttributes; @@ -743,7 +880,7 @@ void InterpretEscSeq( void ) if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[P == ESC[1P if (es_argc != 1) return; Rect.Left = WIN.Left = CUR.X; - Rect.Right = WIN.Right = WIDTH - 1; + Rect.Right = WIN.Right = RIGHT; Pos.X = CUR.X - es_argv[0]; Pos.Y = Rect.Top = @@ -757,7 +894,7 @@ void InterpretEscSeq( void ) if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[@ == ESC[1@ if (es_argc != 1) return; Rect.Left = WIN.Left = CUR.X; - Rect.Right = WIN.Right = WIDTH - 1; + Rect.Right = WIN.Right = RIGHT; Pos.X = CUR.X + es_argv[0]; Pos.Y = Rect.Top = @@ -792,7 +929,7 @@ void InterpretEscSeq( void ) if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[C == ESC[1C if (es_argc != 1) return; Pos.X = CUR.X + es_argv[0]; - if (Pos.X >= WIDTH) Pos.X = WIDTH - 1; + if (Pos.X > RIGHT) Pos.X = RIGHT; Pos.Y = CUR.Y; SetConsoleCursorPosition( hConOut, Pos ); return; @@ -802,7 +939,7 @@ void InterpretEscSeq( void ) if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[D == ESC[1D if (es_argc != 1) return; Pos.X = CUR.X - es_argv[0]; - if (Pos.X < 0) Pos.X = 0; + if (Pos.X < LEFT) Pos.X = LEFT; Pos.Y = CUR.Y; SetConsoleCursorPosition( hConOut, Pos ); return; @@ -812,7 +949,7 @@ void InterpretEscSeq( void ) if (es_argc != 1) return; Pos.Y = CUR.Y + es_argv[0]; if (Pos.Y > BOTTOM) Pos.Y = BOTTOM; - Pos.X = 0; + Pos.X = LEFT; SetConsoleCursorPosition( hConOut, Pos ); return; @@ -821,7 +958,7 @@ void InterpretEscSeq( void ) if (es_argc != 1) return; Pos.Y = CUR.Y - es_argv[0]; if (Pos.Y < TOP) Pos.Y = TOP; - Pos.X = 0; + Pos.X = LEFT; SetConsoleCursorPosition( hConOut, Pos ); return; @@ -830,8 +967,8 @@ void InterpretEscSeq( void ) if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[G == ESC[1G if (es_argc != 1) return; Pos.X = es_argv[0] - 1; - if (Pos.X >= WIDTH) Pos.X = WIDTH - 1; - if (Pos.X < 0) Pos.X = 0; + if (Pos.X > RIGHT) Pos.X = RIGHT; + if (Pos.X < LEFT) Pos.X = LEFT; Pos.Y = CUR.Y; SetConsoleCursorPosition( hConOut, Pos ); return; @@ -853,27 +990,53 @@ void InterpretEscSeq( void ) es_argv[es_argc++] = 1; // ESC[#H == ESC[#;1H if (es_argc > 2) return; Pos.X = es_argv[1] - 1; - if (Pos.X < 0) Pos.X = 0; - if (Pos.X >= WIDTH) Pos.X = WIDTH - 1; + if (Pos.X < LEFT) Pos.X = LEFT; + if (Pos.X > RIGHT) Pos.X = RIGHT; Pos.Y = es_argv[0] - 1; if (Pos.Y < TOP) Pos.Y = TOP; if (Pos.Y > BOTTOM) Pos.Y = BOTTOM; SetConsoleCursorPosition( hConOut, Pos ); return; + case 'I': // ESC[#I Moves cursor forward # tabs + if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[I == ESC[1I + if (es_argc != 1) return; + Pos.Y = CUR.Y; + Pos.X = (CUR.X & -8) + es_argv[0] * 8; + if (Pos.X > RIGHT) Pos.X = RIGHT; + SetConsoleCursorPosition( hConOut, Pos ); + return; + + case 'Z': // ESC[#Z Moves cursor back # tabs + if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[Z == ESC[1Z + if (es_argc != 1) return; + Pos.Y = CUR.Y; + if ((CUR.X & 7) == 0) + Pos.X = CUR.X - es_argv[0] * 8; + else + Pos.X = (CUR.X & -8) - (es_argv[0] - 1) * 8; + if (Pos.X < LEFT) Pos.X = LEFT; + SetConsoleCursorPosition( hConOut, Pos ); + return; + + case 'b': // ESC[#b Repeat character + if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[b == ESC[1b + if (es_argc != 1) return; + while (--es_argv[0] >= 0) + PushBuffer( ChPrev ); + return; + case 's': // ESC[s Saves cursor position for recall later if (es_argc != 0) return; - get_state(); pState->SavePos.X = CUR.X; pState->SavePos.Y = CUR.Y - TOP; return; case 'u': // ESC[u Return to saved cursor position if (es_argc != 0) return; - get_state(); Pos.X = pState->SavePos.X; Pos.Y = pState->SavePos.Y + TOP; - if (Pos.X >= WIDTH) Pos.X = WIDTH - 1; + if (Pos.X > RIGHT) Pos.X = RIGHT; if (Pos.Y > BOTTOM) Pos.Y = BOTTOM; SetConsoleCursorPosition( hConOut, Pos ); return; @@ -915,6 +1078,14 @@ void InterpretEscSeq( void ) } return; + case 'h': // ESC[#h Set Mode + if (es_argc == 1 && es_argv[0] == 3) + pState->crm = TRUE; + return; + + case 'l': // ESC[#l Reset Mode + return; // ESC[3l is handled during parsing + default: return; } @@ -965,7 +1136,8 @@ ParseAndPrintString( HANDLE hDev, if (*s == ESC) { suffix2 = 0; - state = 2; + get_state(); + state = (pState->crm) ? 7 : 2; } else if (*s == SO) shifted = TRUE; else if (*s == SI) shifted = FALSE; @@ -1094,6 +1266,39 @@ ParseAndPrintString( HANDLE hDev, else *Pt_arg = *s; } + else if (state == 7) + { + if (*s == '[') state = 8; + else + { + PushBuffer( ESC ); + PushBuffer( *s ); + state = 1; + } + } + else if (state == 8) + { + if (*s == '3') state = 9; + else + { + PushBuffer( ESC ); + PushBuffer( '[' ); + PushBuffer( *s ); + state = 1; + } + } + else if (state == 9) + { + if (*s == 'l') pState->crm = FALSE; + else + { + PushBuffer( ESC ); + PushBuffer( '[' ); + PushBuffer( '3' ); + PushBuffer( *s ); + } + state = 1; + } } FlushBuffer(); if (lpNumberOfBytesWritten != NULL) @@ -1953,7 +2158,6 @@ void OriginalAttr( PVOID lpReserved ) NULL, OPEN_EXISTING, 0, 0 ); if (!GetConsoleScreenBufferInfo( hConOut, &csbi )) csbi.wAttributes = 7; - CloseHandle( hConOut ); // If we were loaded dynamically, remember the current attributes to restore // upon unloading. However, if we're the 64-bit DLL, but the image is 32- @@ -1968,7 +2172,10 @@ void OriginalAttr( PVOID lpReserved ) if (pNTHeader->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64) #endif orgattr = csbi.wAttributes; + GetConsoleMode( hConOut, &orgmode ); + GetConsoleCursorInfo( hConOut, &orgcci ); } + CloseHandle( hConOut ); get_state(); } @@ -2044,6 +2251,8 @@ BOOL WINAPI DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved ) FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0 ); SetConsoleTextAttribute( hConOut, orgattr ); + SetConsoleMode( hConOut, orgmode ); + SetConsoleCursorInfo( hConOut, &orgcci ); CloseHandle( hConOut ); } CloseHandle( pState ); diff --git a/ansicon.c b/ansicon.c index 6c17550..972eaac 100644 --- a/ansicon.c +++ b/ansicon.c @@ -87,7 +87,7 @@ add error codes to some message. */ -#define PDATE L"20 February, 2014" +#define PDATE L"26 February, 2014" #include "ansicon.h" #include "version.h" diff --git a/readme.txt b/readme.txt index 250c23b..8a101a8 100644 --- a/readme.txt +++ b/readme.txt @@ -152,15 +152,21 @@ Sequences Recognised \e[21t xterm: Report window's title \e[s ANSI.SYS: Save Cursor Position \e[u ANSI.SYS: Restore Cursor Position + \e[#Z CBT Cursor Backward Tabulation \e[#G CHA Cursor Character Absolute + \e[#I CHT Cursor Forward Tabulation \e[#E CNL Cursor Next Line \e[#F CPL Cursor Preceding Line + \e[3h CRM Control Representation Mode (display controls) + \e[3l CRM Control Representation Mode (perform controls) \e[#D CUB Cursor Left \e[#B CUD Cursor Down \e[#C CUF Cursor Right \e[#;#H CUP Cursor Position \e[#A CUU Cursor Up \e[#P DCH Delete Character + \e[?7h DECAWM DEC Autowrap Mode (autowrap) + \e[?7l DECAWM DEC Autowrap Mode (no autowrap) \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 @@ -176,6 +182,7 @@ Sequences Recognised \e[#L IL Insert Line SI LS0 Locking-shift Zero (see below) SO LS1 Locking-shift One + \e[#b REP Repeat \e[#;#;#m SGR Select Graphic Rendition \e[#d VPA Line Position Absolute \e[#k VPB Line Position Backward @@ -268,6 +275,7 @@ Limitations =========== Line sequences use the window; column sequences use the buffer. + Tabs are fixed at eight columns. There's a conflict with NVIDIA's drivers, requiring the setting of the Environment Variable: @@ -283,7 +291,7 @@ Version History Legend: + added, - bug-fixed, * changed. - 1.70 - 20 February, 2014: + 1.70 - 26 February, 2014: - don't hook again if using LoadLibrary or LoadLibraryEx; - update the LoadLibraryEx flags that shouldn't hook; - restore original attributes on detach (for LoadLibrary/FreeLibrary usage); @@ -292,13 +300,17 @@ Version History - attributes and saved position are local to each console window; - improved recognition of unsupported sequences; - restore cursor to bounds, if size reduced; + - stop \e[K from erasing first character of next line; + - restore cursor visibility on unload; * inject into a created process by modifying the import descriptor table (-p will use CreateRemoteThread); * log: remove the quotes around the CreateProcess command line; add an underscore in 64-bit addresses to distinguish 8-digit groups; * ANSICON_EXC can exclude entire programs; * switch G1 blank from space (U+0020) to No-Break Space (U+00A0); - * use window height, not buffer. + * use window height, not buffer; + * remove newline after wrap; + + recognise more sequences. 1.66 - 20 September, 2013: - fix 32-bit process trying to detect 64-bit process. @@ -486,4 +498,4 @@ Distribution ============================== -Jason Hood, 20 February, 2014. +Jason Hood, 26 February, 2014. diff --git a/sequences.txt b/sequences.txt index d62aae1..0b1e252 100644 --- a/sequences.txt +++ b/sequences.txt @@ -1,6 +1,6 @@ ANSICON - Version 1.60 + Version 1.70 This is a complete list of the ANSI escape sequences recognised by ANSICON, roughly ordered by function. The initial escape character is assumed. @@ -64,6 +64,9 @@ roughly ordered by function. The initial escape character is assumed. [@ insert one blank character [#@ insert # blank characters +[b repeat the previous character +[#b repeat the previous character # times + [A move cursor up one line [#A move cursor up # lines [B move cursor down one line @@ -72,6 +75,10 @@ roughly ordered by function. The initial escape character is assumed. [#C move cursor right # characters [D move cursor left one character [#D move cursor left # characters +[I move cursor forward one tab +[#I move cursor forward # tabs +[Z move cursor back one tab +[#Z move cursor back # tabs [k move cursor up one line [#k move cursor up # lines @@ -105,8 +112,12 @@ roughly ordered by function. The initial escape character is assumed. [#;#f move cursor to line #, column # [s save cursor position -[u move cursor to saved position +[u move cursor to saved position (or top-left, if nothing was saved) +[3h display control characters (LF is also performed) +[3l perform control functions (the only such recognised during above) +[?7h wrap lines at screen edge +[?7l don't wrap lines at screen edge [?25h show cursor [?25l hide cursor