ansi.bat: handling ANSI sequences in nowadays WIN terminals

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
siberia-man
Posts: 208
Joined: 26 Dec 2013 09:28
Contact:

ansi.bat: handling ANSI sequences in nowadays WIN terminals

#1 Post by siberia-man » 24 Nov 2020 07:00

A few days ago i found out that windows terminal supports ANSI sequences.

It's cool, of course. However, as it's usual in Windows world, every cool feature has worse things. VT is off, by default. Luckily, it can be turned on easily.

ENABLE/DISABLE

Turn on:

Code: Select all

reg add HKCU\Console /v VirtualTerminalLevel /t REG_DWORD /d 1
Turn off:

Code: Select all

reg delete HKCU\Console /v VirtualTerminalLevel
EXAMPLES

Independently on this feature is turned on or off, both type and echo support ANSI:

Code: Select all

for /f %a in ('echo prompt $e^| cmd') do @set "ESC=%a"
echo %ESC%[107m    %ESC%[0m & echo %ESC%[104m    %ESC%[0m & echo %ESC%[101m    %ESC%[0m

Code: Select all

for /f %a in ('echo prompt $e^| cmd') do @set "ESC=%a"
echo %ESC%[107m    %ESC%[0m >> example.txt
echo %ESC%[104m    %ESC%[0m >> example.txt
echo %ESC%[101m    %ESC%[0m >> example.txt
type example.txt
Powershell supports as well:

Code: Select all

$ESC = [char]27
"$ESC[107m    $ESC[0m`n$ESC[104m    $ESC[0m`n$ESC[101m    $ESC[0m"
Other commands do not support.

ENHANCEMENT AND TOOL

In Unix it's allowed to specify escape sequences in the forms:

Code: Select all

"\e["   <options> <command>
"\033[" <options> <command>
"\x1b[" <options> <command>
"^[["   <options> <command> # in Windows "^^^^[[" is required to print
Windows doesn't support them. To enable them in Windows as well, I developed a simple script (hybrid of CMD+WSF+JS). It works as a pipe filter, capturing input and transforming into ANSI printable sequences.

The script has a comprehensive description and couple of examples.

It checks if VT setting enabled and prints result directly, otherwise emulate via echo.

SOURCE CODE

Code: Select all

<?xml :
: version="1.0" encoding="utf-8" ?>
<!-- :
@echo off

if "%~1" == "" timeout /t 0 1>nul 2>&1 && (
	cscript //nologo "%~f0?.wsf" /?
	goto :EOF
)

reg query "HKCU\Console" /v VirtualTerminalLevel 2>nul | find "0x1" >nul && (
	cscript //nologo "%~f0?.wsf" %*
	goto :EOF
)

for /f "tokens=* delims=" %%s in ( '
	cscript //nologo "%~f0?.wsf" %*
' ) do echo:%%s

goto :EOF
: -->

<package>
<job id="ansi">
<?job error="false" debug="false" ?>

<script language="javascript"><![CDATA[

var NAME    = 'ANSI';
var VERSION = '0.0.1';

]]></script>

<runtime>
<description><![CDATA[
Parse the specified text from the input file or pipe and output it
accordingly the ANSI codes provided within the text.

Escaping

Interpret the following escaped characters:
  \a        Bell
  \b        Backspace
  \e        Escape character
  \f        Form feed
  \n        New line
  \r        Carriage return
  \t        Horizontal tabulation
  \v        Vertical tabulation
  \\        Backslash
  \0nnn     The character by its ASCII code (octal)
  \xHH      The character by its ASCII code (hexadecimal)

ANSI sequences

  <ESC> [ <list> <code>

  <ESC>     Escape character in the form "\e", "\033", "\x1B", "^["
  <list>    The list of numeric codes
  <code>    The sequence code

Moves the cursor n (default 1) cells in the given direction. If the cursor
is already at the edge of the screen, this has no effect.
  \e[nA     Cursor Up
  \e[nB     Cursor Down
  \e[nC     Cursor Forward
  \e[nD     Cursor Back

Moves cursor to beginning of the line n (default 1).
  \e[nE     Cursor Next Line
  \e[nF     Cursor Previous Line

Cursor position
  \e[nG     Moves the cursor to column n.
  \e[n;mH   Moves the cursor to row n, column m.
  \e[n;mf   The same as above.

Erasing
  \e[nJ     Clears part of the screen. If n is 0 (or missing), clear from
            cursor to end of screen. If n is 1, clear from cursor to
            beginning of the screen. If n is 2, clear entire screen.
  \e[nK     Erases part of the line. If n is zero (or missing), clear from
            cursor to the end of the line. If n is one, clear from cursor
            to beginning of the line. If n is two, clear entire line.
            Cursor position does not change.

Colorizing
  \e[n1[;n2;...]m, where n's are as follows:

  0         All attributes off
  1         Increase intensity
  2         Faint (decreased intensity)
  3         Italic (not widely supported)
  4         Underline
  5         Slow blink
  6         Rapid blink
  7         Reverse (invert the foreground and background colors)
  8-29      Rarely supported
  30-37     Set foreground color (30+x, where x from the tables below)
  38        Set foreground color (Next arguments are 5;n or 2;r;g;b)
  39        Default foreground text color
  40-47     Set background color (40+x)
  48        Set foreground color (Next arguments are 5;n or 2;r;g;b)
  49        Default background color
  50-74     Rarely supported
  90-97     Set foreground color, high intensity (90+x)
  100-107   Set background color, high intensity (100+x)

ANSI colors (default usage)
  Intensity 0       1       2       3       4       5       6       7
  Normal    Black   Red     Green   Yellow  Blue    Magenta Cyan    White
  Bright    Black   Red     Green   Yellow  Blue    Magenta Cyan    White

References

http://en.wikipedia.org/wiki/ANSI_escape_code
http://misc.flogisoft.com/bash/tip_colors_and_formatting
http://stackoverflow.com/a/24273024/3627676
http://www.robvanderwoude.com/ansi.php#AnsiArt
]]></description>
<example><![CDATA[
Examples:

- Print data from pipe:
  echo \e[107m    \e[0m\n\e[104m    \e[0m\n\e[101m    \e[0m | ansi

- Prepare data in the file and print:
  del example.txt
  echo \e[107m    \e[0m>>example.txt
  echo \e[104m    \e[0m>>example.txt
  echo \e[101m    \e[0m>>example.txt
  ansi < example.txt
]]></example>
<named
	name="no-eol"
	helpstring="Skip explicit end of line (skip explicit CRLF)"
	type="simple"
	required="false"
	/>
<named
	name="no-space"
	helpstring="Skip the last trailing space printed by echo in DOS sessions"
	type="simple"
	required="false"
	/>
<named
	name="safe"
	helpstring="Reset all color attributes to defaults at the end of execution"
	type="simple"
	required="false"
	/>
</runtime>

<!-- <script language="javascript" src="./ansi.parse.js"></script> -->
<script language="javascript"><![CDATA[
function ansi_parse(text, options) {
	options = options || {};

	if ( options.safe ) {
		text += '\\e[0m';
	}

	var chars = {
		'a': String.fromCharCode(7),
		'b': '\b',
		'e': String.fromCharCode(27),
		'f': '\f',
		'n': '\n',
		'r': '\r',
		't': '\t',
		'v': '\v',
		'\\': '\\'
	};

	var re_src = [
		'(?:\\\\(' + '[abefnrtv\\\\]' + '))'	// Escaped chars above
	,       '|'
	,       '(?:\\\\(' + '0[0-7]{1,3}' + '))'	// ASCII code (oct)
	,       '|'
	,       '(?:\\\\(' + 'x[0-9a-fA-F]{1,2}' + '))'	// ASCII code (hex)
	,	'|'
	,	'(' + '\\^\\[' + ')'			// ^[ stands for <ESC>
	].join('');

	if ( options.no_eol && options.no_space ) {
		re_src += '|[ ]\\r?\\n';
	} else if ( options.no_eol ) {
		re_src += '|\\r?\\n';
	} else if ( options.no_space ) {
		re_src += '|[ ](?=\\r?\\n)';
	}

	var re = new RegExp(re_src, 'g');

	return text.replace(re, function($0, $1, $2, $3, $4) {
		return $1 ? chars[$1] :
			$2 ? String.fromCharCode(parseInt($2)) :
			$3 ? String.fromCharCode(parseInt('0' + $3)) :
			$4 ? chars.e : '';
	});
}
]]></script>
<!-- <script language="javascript" src="./ansi.main.js"></script> -->
<script language="javascript"><![CDATA[
var opts = WScript.Arguments.Named;

var text = WScript.StdIn.ReadAll();

var text = ansi_parse(text, {
	no_eol: opts.Exists('no-eol'),
	no_space: opts.Exists('no-space'),
	safe: opts.Exists('safe')
});

WScript.StdOut.Write(text);
]]></script>

</job>
</package>

ShadowThief
Expert
Posts: 1160
Joined: 06 Sep 2013 21:28
Location: Virginia, United States

Re: ansi.bat: handling ANSI sequences in nowadays WIN terminals

#2 Post by ShadowThief » 24 Nov 2020 08:02

siberia-man wrote:
24 Nov 2020 07:00
Independently on this feature is turned on or off, both type and echo support ANSI:
I was going to ask how I've been able to use ANSI this whole time without touching the registry, but that explains it; I've only been using them with echo.

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: ansi.bat: handling ANSI sequences in nowadays WIN terminals

#3 Post by dbenham » 24 Nov 2020 09:04

I wanted to provide match highlighting with JREPL.BAT without requiring the registry setting. JREPL uses JSCRIPT run by CSCRIPT to write output, so the escape sequences are not enabled by default.

aGerman discovered that CSCRIPT can enable the escape sequences simply by running an innocuous PowerShell command - PowerShell enables the escape sequences, and then the setting remains enabled for the remainder of the CSCRIPT program. So my JScript program simply needs to run the following:

Code: Select all

_g.objExec=_g.objSh.Exec("powershell.exe -nop -ep Bypass -c \"exit\"");

Dave Benham

T3RRY
Posts: 243
Joined: 06 May 2020 10:14

Re: ansi.bat: handling ANSI sequences in nowadays WIN terminals

#4 Post by T3RRY » 24 Nov 2020 09:09

ShadowThief wrote:
24 Nov 2020 08:02
siberia-man wrote:
24 Nov 2020 07:00
Independently on this feature is turned on or off, both type and echo support ANSI:
I was going to ask how I've been able to use ANSI this whole time without touching the registry, but that explains it; I've only been using them with echo.
Set /P also supports Ansi sequences by default on windows 10.

siberia-man
Posts: 208
Joined: 26 Dec 2013 09:28
Contact:

Re: ansi.bat: handling ANSI sequences in nowadays WIN terminals

#5 Post by siberia-man » 24 Nov 2020 10:19

dbenham wrote:
24 Nov 2020 09:04
enable the escape sequences simply by running an innocuous PowerShell command
Cool trick! I've just tested it with wsx.bat, my own REPL tool announced here viewtopic.php?f=3&t=9476&hilit=wsx. And it works. Here is the example. It's long one-liner separated into few sub-lines for readability

Code: Select all

wsx /set:sh=WScript.Shell ^
/e:"s=sh.Exec('powershell -nop -ep bypass -c exit')" ^
/e:"while(s.status==0)sleep(100)" ^
/e:"esc=String.fromCharCode(27)" ^
/e:"print(esc+'[1;107m    '+esc+'[0m')" ^
/e:"print(esc+'[1;104m    '+esc+'[0m')" ^
/e:"print(esc+'[1;101m    '+esc+'[0m')"

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: ansi.bat: handling ANSI sequences in nowadays WIN terminals

#6 Post by dbenham » 24 Nov 2020 10:36

All cmd.exe output supports escape sequences. This includes prompt output, display of lines when ECHO is ON, and display of variable(s) value with SET command (variable name and/or value). But external commands invoked by cmd.exe do not have escape sequences enabled by default.


Dave Benham

aGerman
Expert
Posts: 4654
Joined: 22 Jan 2010 18:01
Location: Germany

Re: ansi.bat: handling ANSI sequences in nowadays WIN terminals

#7 Post by aGerman » 24 Nov 2020 13:20

Right. The SetConsoleMode API has to be called for both the console window's input and output handle if the registry values are not set. While PowerShell obviously configures the console window for its living time, the CMD seems to reset the previous setting after each IO. And that's certainly the reason why for external commands (which don't configure the console) ANSI sequences are not rendered.

Steffen

// EDIT: Nope. Apparently my assumption is wrong. This is still not how PowerShell works.

Code: Select all

$e = [char]0x1b
"$e[107m    $e[0m`r`n$e[104m    $e[0m`r`n$e[101m    $e[0m" | out-file -FilePath "foo.txt" -Encoding ascii
get-content "foo.txt"
&'find.exe' '/v' '""' '"foo.txt"'
&'find.exe' '/v' '""' '"foo.txt"' | write-output
&'findstr.exe' '"^"' '"foo.txt"'
&'findstr.exe' '"^"' '"foo.txt"' | write-output
Hmm. But in this case I have no good explanation for the PowerShell hack ¯\_(ツ)_/¯

siberia-man
Posts: 208
Joined: 26 Dec 2013 09:28
Contact:

Re: ansi.bat: handling ANSI sequences in nowadays WIN terminals

#8 Post by siberia-man » 25 Nov 2020 04:26

@aGerman

Dave said:
aGerman discovered that CSCRIPT can enable the escape sequences simply by running an innocuous PowerShell command
Steffen, is it your personal finding? Or it was found somewhere in the Internet?

aGerman
Expert
Posts: 4654
Joined: 22 Jan 2010 18:01
Location: Germany

Re: ansi.bat: handling ANSI sequences in nowadays WIN terminals

#9 Post by aGerman » 25 Nov 2020 06:48

Originally I wrote a short PowerShell snippet using p/invoke to set VT mode in a SetConsoleMode call.
viewtopic.php?f=3&t=9144&p=59691#p59691
Further in this thread you'll see that I found out that just calling PowerShell is already good enough.
I can't tell if someone else discoverd this before. I knew how to do it using the Windows API and just tried to transcribe it. The simple solution was found by a fluke while playing around with it.

Steffen

Post Reply