Help with program that emulate VT100 terminal

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
foxidrive
Expert
Posts: 6031
Joined: 10 Feb 2012 02:20

Re: Help with program that emulate VT100 terminal

#31 Post by foxidrive » 12 Nov 2014 09:18

We were posting at the same time - in case you missed it see my post above, before your last post.

Aacini wrote:Perhaps we could set a standard right now. What do you think that should be the right way? Preserve on not preserve the previous Foregroung Light color (1) and Background Light color (5) attributes when a new attribute is set and it does not include 1 or 5?


I think that if a program like AnsiSys or Ansicon is being used then it should be up to the user to explicitly set the foreground, background, and brightness levels when they use it - because the whole point is to change the colours to something that they want to use.

Ergo I would suggest that AnsiSys should reset the colours to default windows standard of grey on black when it is called.
This would make it predictable for the user, and they will soon see that it's desirable to set the exact colours they want to use.

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

Re: Help with program that emulate VT100 terminal

#32 Post by siberia-man » 13 Nov 2014 19:58

One more instance of the ANSI emulator. It supports some of (but not all) ANSI sequences very close to those terminals.

+ No tricks with batch script attempting to colorize output
+ No executable binaries

- PowerShell required (can be absent on some hosts)
- No executable binaries (script works slower than an .exe file)

Usage
ansi --help wrote:

Code: Select all

ANSI [ --dos-colors ] [ --restore ] [ --no-new-line ] [text ...]

--dos-colors  Use DOS colors instead ANSI (See "COLOR /?")
--restore     Restore the colors to the values set before the starting
--no-new-line Discard printing the trailing new line

--help        Print this help
--man         Print the manual
--version     Print the version

--demo        Print demo with the file from the URL:
              http://www.robvanderwoude.com/files/apple_ansi.txt

Parse the specified text from the command line or pipe and output it
accordingly the ANSI codes provided within the text.

The first empty argument after possible options from the list above
stops processing of arguments and considers the rest of arguments as a
regular text.



Source code
Store this as ansi.bat

Code: Select all

<# :
@echo off
setlocal
set "POWERSHELL_BAT_ARGS=%*"
if defined POWERSHELL_BAT_ARGS set "POWERSHELL_BAT_ARGS=%POWERSHELL_BAT_ARGS:"="""%"
endlocal & powershell -NoLogo -NoProfile -Command "$input | &{ [ScriptBlock]::Create( ( Get-Content \"%~f0\" ) -join [char]10 ).Invoke( @( &{ $args } %POWERSHELL_BAT_ARGS% ) ) }"
goto :EOF
#>

# =========================================================================

$ProgName = if ( $MyInvocation.MyCommand.Name ) { $MyInvocation.MyCommand.Name } else { "ANSI" };

$Version = "0.5 Beta";

$DemoURL = "http://www.robvanderwoude.com/files/apple_ansi.txt";

$Help = @"
$ProgName [ --dos-colors ] [ --restore ] [ --no-new-line ] [text ...]

--dos-colors  Use DOS colors instead ANSI (See "COLOR /?")
--restore     Restore the colors to the values set before the starting
--no-new-line Discard printing the trailing new line

--help        Print this help
--man         Print the manual
--version     Print the version

--demo        Print demo with the file from the URL:
              $DemoURL

Parse the specified text from the command line or pipe and output it
accordingly the ANSI codes provided within the text.

The first empty argument after possible options from the list above
stops processing of arguments and considers the rest of arguments as a
regular text.
"@;

$Manual = @"
$Help

ESCAPING

Interpret the following escaped characters:
  \a        Bell
  \b        Backspace
  \c        Suppress further output
  \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)
  7         Reverse (invert the foreground and background colors)
  30-37     Set foreground color (30+x, where x from the tables below)
  39        Default foreground text color
  40-47     Set background color (40+x)
  49        Default background color
  90-97     Set foreground color, high intensity (90+x)
  100-107   Set background color, high intensity (100+x)

DIFFERENCES BETWEEN ANSI AND DOS COLORS

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

DOS colors (available by the "--dos-colors" option)
  Intensity 0       1       2       3       4       5       6       7
  Normal    Black   Blue    Green   Aqua    Red     Purple  Yellow  White
  Bright    Gray    Blue    Green   Aqua    Red     Purple  Yellow  White

REFERENCES

http://en.wikipedia.org/wiki/ANSI_escape_code
http://misc.flogisoft.com/bash/tip_colors_and_formatting
http://ss64.com/nt/color.html
http://stackoverflow.com/a/24273024/3627676
http://www.robvanderwoude.com/ansi.php#AnsiArt
"@;

# =========================================================================

$HostColor = @{};

function save-host-colors {
   $Script:HostColor = @{
      "foreground" = $Host.UI.RawUI.ForegroundColor;
      "background" = $Host.UI.RawUI.BackgroundColor;
   };
}

function restore-host-colors {
   if ( ! $RestoreColors ) {
      return;
   }
   $Host.UI.RawUI.ForegroundColor = $HostColor.foreground;
   $Host.UI.RawUI.BackgroundColor = $HostColor.background;
}

# =========================================================================

function set-ansi-color( [array]$colors ) {
   for ($i = 0; $i -lt $colors.count; $i++) {
      switch -wildcard ( $colors[$i] ) {
      0 {
         # Reset / Normal
         # restore-host-colors;
         $Host.UI.RawUI.ForegroundColor = 7;
         $Host.UI.RawUI.BackgroundColor = 0;
         break;
         }
      1 {
         # Bold or increased intensity
         $Host.UI.RawUI.ForegroundColor = $Host.UI.RawUI.ForegroundColor -bor 0x08;
         break;
         }
      2 {
         # Faint (decreased intensity)
         $Host.UI.RawUI.ForegroundColor = $Host.UI.RawUI.ForegroundColor -band 0xf8;
         break;
         }
      7 {
         # Reverse (invert the foreground and background colors)
         $Host.UI.RawUI.ForegroundColor, $Host.UI.RawUI.BackgroundColor = $Host.UI.RawUI.BackgroundColor, $Host.UI.RawUI.ForegroundColor;
         break;
         }
      { $_ -ge 30 -and $_ -le 37 } {
         # Set text color (foreground)
         $_ = $ColorIndex[ $_ - 30 ];
         $Host.UI.RawUI.ForegroundColor = $Host.UI.RawUI.ForegroundColor -band 0xf8 -bor $_;
         break;
         }
      39 {
         # Default text color (foreground)
         # $Host.UI.RawUI.ForegroundColor = $Host.UI.RawUI.ForegroundColor -band 0xf8 -bor $HostColor.foreground;
         $Host.UI.RawUI.ForegroundColor = 7;
         break;
         }
      { $_ -ge 40 -and $_ -le 47 } {
         # Set background color
         $_ = $ColorIndex[ $_ - 40 ];
         $Host.UI.RawUI.BackgroundColor = $Host.UI.RawUI.BackgroundColor -band 0xf8 -bor $_;
         break;
         }
      49 {
         # Default background color
         # $Host.UI.RawUI.BackgroundColor = $Host.UI.RawUI.BackgroundColor -band 0xf8 -bor $HostColor.background;
         $Host.UI.RawUI.BackgroundColor = 0;
         break;
         }
      { $_ -ge 90 -and $_ -le 97 } {
         # Set foreground text color, high intensity
         $_ = $ColorIndex[ $_ - 90 ] + 8;
         $Host.UI.RawUI.ForegroundColor = $_;
         break;
         }
      { $_ -ge 100 -and $_ -le 107 } {
         # Set background color, high intensity
         $_ = $ColorIndex[ $_ - 100 ] + 8;
         $Host.UI.RawUI.BackgroundColor = $_;
         break;
         }
      }
   }
}

# =========================================================================

function set-cursor( $x, $y ) {
    [System.Console]::CursorTop = $y;
    [System.Console]::CursorLeft = $x;
}

# =========================================================================

function set-cursor-position( [array]$position, [string]$movement ) {
   if ($position.count -ne 2 ) {
      $position += 1;
   }
   for ($i = 0; $i -lt $position.count; $i++) {
      if ( $position[$i] -eq 0 ) {
         $position[$i] = 1;
      }
   }

   $row = $Host.UI.RawUI.CursorPosition.Y - $Host.UI.RawUI.WindowPosition.Y;
   $col = $Host.UI.RawUI.CursorPosition.X - $Host.UI.RawUI.WindowPosition.X;

   switch ( $movement ) {
   "A" {
      # Cursor Up
      $row = $row - $position[0];
      break;
      }
   "B" {
      # Cursor Down
      $row = $row + $position[0];
      break;
      }
   "C" {
      # Cursor Forward
      $col = $col + $position[0];
      break;
      }
   "D" {
      # Cursor Back
      $col = $col - $position[0];
      break;
      }
   "E" {
      # Cursor Next Line. Moves cursor to beginning of the line
      # n (default 1) lines down.
      $row = $row + $position[0];
      $col = $Host.UI.RawUI.WindowPosition.X;
      break;
      }
   "F" {
      # Cursor Previous Line. Moves cursor to beginning of the
      # line n (default 1) lines up.
      $row = $row - $position[0];
      $col = $Host.UI.RawUI.WindowPosition.X;
      break;
      }
   "G" {
      # Moves the cursor to column n.
      $col = $position[0] - 1;
      break;
      }
   { $_ -eq "H" -or $_ -eq "f" } {
      # Cursor Position. Moves the cursor to row n, column m.
      $row = $position[0] - 1;
      $col = $position[1] - 1;
      break;
      }
   }

   if ( $row -lt $Host.UI.RawUI.WindowSize.Height ) {
      $row = [Math]::min( [Math]::max($row, 0), $Host.UI.RawUI.WindowSize.Height - 1 ) + $Host.UI.RawUI.WindowPosition.Y;
   } else {
      $row = [Math]::min( [Math]::max($row + $Host.UI.RawUI.WindowPosition.Y, 0), $Host.UI.RawUI.BufferSize.Height - 1 );
   }

   if ( $col -lt $Host.UI.RawUI.WindowSize.Width ) {
      $col = [Math]::min( [Math]::max($col, 0), $Host.UI.RawUI.WindowSize.Width - 1 ) + $Host.UI.RawUI.WindowPosition.X;
   } else {
      $col = [Math]::min( [Math]::max($col + $Host.UI.RawUI.WindowPosition.X, 0), $Host.UI.RawUI.BufferSize.Width - 1 );
   }

   set-cursor $col $row;
}

# =========================================================================

function erase-display( [array]$mode ) {
   if ( $mode[0] -eq 2 ) {
      # clear entire screen.
      Clear-Host;
      return;
   }

   # Store the original cursor position
   $posX = $Host.UI.RawUI.CursorPosition.X;
   $posY = $Host.UI.RawUI.CursorPosition.Y;

   # Cursor position within the window
   $x = $posX - $Host.UI.RawUI.WindowPosition.X;
   $y = $posY - $Host.UI.RawUI.WindowPosition.Y;

   $w = $Host.UI.RawUI.WindowSize.Width;
   $h = $Host.UI.RawUI.WindowSize.Height;

   switch ( $mode[0] ) {
   0 {
      # clear from cursor to end of screen.
      $s = " " * ( $h * $w - ( $y * $w + $x ) );
      Write-Host -NoNewLine $s;

      break;
      }
   1 {
      # clear from cursor to beginning of the screen.
      set-cursor $Host.UI.RawUI.WindowPosition.X $Host.UI.RawUI.WindowPosition.Y;

      $s = " " * ( $y * $w + $x );
      Write-Host -NoNewLine $s;

      break;
      }
   default {
      return;
      }
   }

   # Restore to the original cursor position
   set-cursor $posX $posY;
}

# =========================================================================

function erase-line( [array]$mode ) {
   # Store the original cursor position
   $posX = $Host.UI.RawUI.CursorPosition.X;
   $posY = $Host.UI.RawUI.CursorPosition.Y;

   # Cursor position within the window
   $x = $posX - $Host.UI.RawUI.WindowPosition.X;
   $y = $posY - $Host.UI.RawUI.WindowPosition.Y;

   $w = $Host.UI.RawUI.WindowSize.Width;
   $h = $Host.UI.RawUI.WindowSize.Height;

   switch ( $mode[0] ) {
   0 {
      # clear from cursor to end of the line.
      $s = " " * ( $w - $x );
      break;
      }
   1 {
      # clear from cursor to beginning of the line.
      $s = " " * $x;
      break;
      }
   2 {
      # clear entire line.
      $s = " " * $w;
      break;
      }
   default {
      return;
      }
   }

   if ( $mode[0] -ne 0 ) {
      # Set the cursor to beginning of the line
      set-cursor $Host.UI.RawUI.WindowPosition.X $posY;
   }

   Write-Host -NoNewLine $s;

   # Restore to the original cursor position
   set-cursor $posX $posY;
}

# =========================================================================

function process-ansi-sequence ( [string]$sequence, [string]$code ) {
   $seq = $sequence.split(";");
   for ($i = 0; $i -lt $seq.count; $i++) {
      $seq[$i] = [int]$seq[$i];
   }

   switch -wildcard ( $code ) {
   "m" {
      set-ansi-color $seq;
      break;
      }
   "[A-Hf]" {
      set-cursor-position $seq $code;
      break;
      }
   "J"   {
      erase-display $seq;
      break;
      }
   "K"   {
      erase-line $seq;
      break;
      }
   }
}

# =========================================================================

$MetaChars = @{
   "\\a" = [char]7;
   "\\b" = [char]8;
   "\\e" = [char]27;
   "\\f" = [char]12;
   "\\n" = [char]10;
   "\\r" = [char]13;
   "\\t" = [char]9;
   "\\v" = [char]11;
   "\\\\" = "\\";
};

function parse-metachar-string( [string]$string ) {
   # 1. Don't display anything following "\c"
   $string = $string -replace "\\c.*", "";

   # 2. Convert metacharacters to the proper characters
   foreach ( $p in $MetaChars.GetEnumerator() ) {
      $string = $string -replace $p.Name, $p.Value;
   }

   # NB: Code optimization is expected for 3.a and 3.b

   # 3.a. Convert the octal presentation to characters
   $re = [regex]"\\0[0-7]{1,3}";
   $found = @( $re.Matches($string) | select -uniq );
   for ( $i = 0; $i -lt $found.count; $i++ ) {
      $code = ( [string]$found[$i] ).Substring(2);
      $char = [char][Convert]::toInt16( $code, 8 );
      $string = $string.Replace( $found[$i], $char );
   }

   # 3.b. Convert the hexadecimal presentation to characters
   $re = [regex]"\\x[0-9A-Fa-f]{1,2}";
   $found = @( $re.Matches($string) | select -uniq );
   for ( $i = 0; $i -lt $found.count; $i++ ) {
      $code = ( [string]$found[$i] ).Substring(2);
      $char = [char][Convert]::toInt16( $code, 16 );
      $string = $string.Replace( $found[$i], $char );
   }

   return $string;
}

# =========================================================================

function parse-ansi-string( [string]$string ) {
   $string = parse-metachar-string($string);

   $re = [regex]"(?:\x1b|\^\[|\\e)\[((?:\d+;)*\d+)?([A-JKSTfhilmnsu])";
   $found = $re.Matches($string);

   $pos = 0;

   for ( $i = 0; $i -lt $found.count; $i++ ) {
      Write-Host -NoNewLine $string.Substring( $pos, $found[$i].Index - $pos );
      $pos = $found[$i].Index + $found[$i].Length;
      process-ansi-sequence $found[$i].Groups[1].Value $found[$i].Groups[2].Value;
   }

   Write-Host -NoNewLine $string.Substring($pos);
   if ( $PrintNewLine ) {
      Write-Host;
   }
}

# =========================================================================

$AnsiColor = @(
   0,  4,  2,  6,  1,  5,  3,  7,
   8, 12, 10, 14,  9, 13, 11, 15
);

$DosColor = @(
   0,  1,  2,  3,  4,  5,  6,  7,
   8,  9, 10, 11, 12, 13, 14, 15
);

$ColorIndex = $AnsiColor;
$RestoreColors = $False;
$PrintNewLine = $True;

:parse_args
for ($i = 0; $i -lt $args.count; $i++) {
   switch ( $args[$i] ) {
   "--help" {
      Write-Host $Help;
      exit;
      }
   "--man" {
      Write-Host $Manual;
      exit;
      }
   "--version" {
      Write-Host "$ProgName $Version";
      exit;
      }
   "--demo" {
      $wc = New-Object System.Net.WebClient;
      $wc.DownloadString($DemoURL) | % { parse-ansi-string $_ };
      exit;
      }
   "--restore" {
      $RestoreColors = $True;
      break;
      }
   "--dos-colors" {
      $ColorIndex = $DosColor;
      break;
      }
   "--no-new-line" {
      $PrintNewLine = $False;
      break;
      }
   "" {
      $i++;
      break parse_args;
      }
   default {
      break parse_args;
      }
   }
}

$args = $args[$i..$args.count];

# =========================================================================

save-host-colors;

if ( $args.length -gt 0 ) {
   parse-ansi-string $args;
} else {
   $input | % { parse-ansi-string $_ };
}

restore-host-colors;

# =========================================================================

# EOF


The latest actual version is here.
Last edited by siberia-man on 22 Mar 2015 18:26, edited 4 times in total.

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

Re: Help with program that emulate VT100 terminal

#33 Post by siberia-man » 26 Nov 2014 04:11

For those guys who find it as the interesting and useful stuff. I've updated the post above.

-- better processing of the command line options
-- new option --no-new-line was added for discarding the trailing new lines
-- new feature: if the first argument is empty string (like this ""), then the rest of arguments is considered as a regular text

Post Reply