Switch from custom SGR push/pop to xterm's XTPUSHSGR / XTPOPSGR

Some of this code is ugly, but it works.
This commit is contained in:
Dan Thompson (SBS) 2019-04-17 07:39:48 -07:00 коммит произвёл Dan Thompson
Родитель fcf6689127
Коммит b3e91e4472
4 изменённых файлов: 374 добавлений и 128 удалений

Просмотреть файл

@ -18,11 +18,12 @@ namespace MS.Dbg
//
internal class AnsiColorWriter : TextWriter
{
private const char CSI = '\x9b'; // "Control Sequence Initiator"
private const char CSI = '\x9b'; // "Control Sequence Initiator" (single character, as opposed to '\x1b' + '[')
private ConsoleColor DefaultForeground;
private ConsoleColor DefaultBackground;
private Dictionary< char, Action< List< int > > > m_commands;
private readonly IReadOnlyDictionary< char, Func< Action< List< int > > > > m_commandTreeRoot;
private readonly IReadOnlyDictionary< char, Func< Action< List< int > > > > m_hashCommands;
private ControlSequenceParseState m_state;
@ -31,42 +32,79 @@ namespace MS.Dbg
private struct ControlSequenceParseState
{
private int m_accum;
private bool m_isParsingParam;
private List< int > m_list;
// True if we are in the middle of parsing a sequence. Useful for when sequences
// are broken across multiple calls.
public bool Parsing { get; private set; }
public bool IsParsing
{
get
{
return null != CurrentCommands;
}
}
private IReadOnlyDictionary< char, Func< Action< List< int > > > > m_commandTreeRoot;
public ControlSequenceParseState( IReadOnlyDictionary< char, Func< Action< List< int > > > > rootCommands )
{
m_commandTreeRoot = rootCommands;
CurrentCommands = null;
m_accum = 0;
m_isParsingParam = false;
m_list = null;
}
public IReadOnlyDictionary< char, Func< Action< List< int > > > > CurrentCommands;
// This can tolerate multiple Begin calls without destroying state.
public void Begin()
{
if( !Parsing )
if( !IsParsing )
{
Parsing = true;
CurrentCommands = m_commandTreeRoot;
m_list = new List< int >();
m_isParsingParam = false;
}
}
public void Accum( int digit )
public void AccumParamDigit( int digit )
{
m_accum *= 10;
m_accum += digit;
m_isParsingParam = true;
}
public void Enter()
public void EnterParam()
{
Util.Assert( m_isParsingParam );
m_list.Add( m_accum );
m_isParsingParam = false;
m_accum = 0;
}
public List< int > Finish()
public List< int > FinishParsingParams()
{
Enter();
Parsing = false;
if( m_isParsingParam )
EnterParam();
CurrentCommands = null;
List< int > list = m_list;
m_list = null;
return list;
}
/// <summary>
/// To recover from bad input.
/// </summary>
public void Reset()
{
CurrentCommands = null;
m_isParsingParam = false;
m_list = null;
m_accum = 0;
}
} // end class ControlSequenceParseState
@ -75,10 +113,19 @@ namespace MS.Dbg
DefaultForeground = Console.ForegroundColor;
DefaultBackground = Console.BackgroundColor;
m_commands = new Dictionary< char, Action< List< int > > >()
m_commandTreeRoot = new Dictionary< char, Func< Action< List< int > > > >()
{
{ 'm', _SelectGraphicsRendition },
{ 'm', () => _SelectGraphicsRendition },
{ '#', _ProcessHashCommand },
};
m_hashCommands = new Dictionary< char, Func< Action< List< int > > > >()
{
{ '{', () => _PushSgr },
{ '}', () => _PopSgr },
};
m_state = new ControlSequenceParseState( m_commandTreeRoot );
} // end constructor
@ -99,7 +146,7 @@ namespace MS.Dbg
{
int startIndex = 0;
if( m_state.Parsing )
if( m_state.IsParsing )
{
// Need to continue.
startIndex = _HandleControlSequence( s, -1 );
@ -123,6 +170,11 @@ namespace MS.Dbg
} // end Write()
/// <summary>
/// Returns the index of the first character past the control sequence (which
/// could be past the end of the string, or could be the start of another
/// control sequence).
/// </summary>
private int _HandleControlSequence( string s, int escIndex )
{
m_state.Begin(); // we may actually be continuing...
@ -134,24 +186,34 @@ namespace MS.Dbg
c = s[ curIndex ];
if( (c >= '0') && (c <= '9') )
{
m_state.Accum( ((int) c) - 0x30 );
m_state.AccumParamDigit( ((int) c) - 0x30 );
continue;
}
else if( ';' == c )
{
m_state.Enter();
m_state.EnterParam();
continue;
}
else if( (c >= '@') && (c <= '~') )
else if( ((c >= '@') && (c <= '~')) || (c == '#') )
{
List< int > args = m_state.Finish();
_ProcessCommand( c, args );
if( _FindCommand( c, out Action< List< int > > command ) )
{
command( m_state.FinishParsingParams() );
return curIndex + 1;
}
}
else
{
// You're supposed to be able to have anonther character, like a space
// (0x20) before the command code character, but I'm not going to do that.
// TODO: Unfortunately, we can get into this scenario. Say somebody wrote a string to the
// output stream, and that string had control sequences. And say then somebody tried to process
// that string in a pipeline, for instance tried to find some property on it, that didn't
// exist, causing an error to be written, where the "target object" is the string... and stuff
// maybe gets truncated or ellipsis-ized... and then we're hosed. What should I do about this?
// Try to sanitize higher-level output? Seems daunting... Just reset state and ignore it? Hm.
m_state.Reset();
throw new ArgumentException( String.Format( "Invalid command sequence character at position {0} (0x{1:x}).", curIndex, (int) c ) );
}
}
@ -161,16 +223,23 @@ namespace MS.Dbg
} // end _HandleControlSequence()
private void _ProcessCommand( char commandChar, List< int > args )
/// <summary>
/// Returns true if it successfully found a command to execute; false if we
/// need to read more characters (some commands are expressed with multiple
/// characters).
/// </summary>
private bool _FindCommand( char commandChar, out Action< List< int > > command )
{
Action< List< int > > action;
if( !m_commands.TryGetValue( commandChar, out action ) )
Func< Action< List< int > > > commandSearcher;
if( !m_state.CurrentCommands.TryGetValue( commandChar, out commandSearcher ) )
{
throw new NotSupportedException( String.Format( "The command code '{0}' (0x{1:x}) is not supported.", commandChar, (int) commandChar ) );
}
action( args );
} // end _ProcessCommand()
command = commandSearcher();
return command != null;
} // end _FindCommand()
// Info sources:
@ -209,6 +278,8 @@ namespace MS.Dbg
// 56: Push fg/bg color pair
// 57: Pop fg/bg color pair
//
// However, THESE ARE DEPRECATED. Use XTPUSHSGR/XTPOPSGR instead.
//
private void _SelectGraphicsRendition( List< int > args )
{
@ -222,6 +293,38 @@ namespace MS.Dbg
} // end _SelectGraphicsRendition()
private void _PushSgr( List< int > args )
{
if( (args != null) && (args.Count != 0) )
{
throw new NotSupportedException( "Optional arguments to the XTPUSHSGR command are not currently implemented." );
}
m_colorStack.Push( new ColorPair( Console.ForegroundColor, Console.BackgroundColor ) );
} // end _PushSgr()
private void _PopSgr( List< int > args )
{
if( (args != null) && (args.Count != 0) )
{
throw new InvalidOperationException( "The XTPOPSGR command does not accept arguments." );
}
ColorPair cp = m_colorStack.Pop();
Console.ForegroundColor = cp.Foreground;
Console.BackgroundColor = cp.Background;
} // end _PopSgr()
private Action< List< int > > _ProcessHashCommand()
{
m_state.CurrentCommands = m_hashCommands;
return null;
} // end _SelectGraphicsRendition()
private class ColorPair
{
public ConsoleColor Foreground { get; private set; }
@ -260,10 +363,12 @@ namespace MS.Dbg
}
else if( 56 == code ) // NON-STANDARD (I made this one up)
{
Util.Fail( "The 56/57 SGR codes (non-standard push/pop) are deprecated. Use XTPUSHSGR/XTPOPSGR instead." );
m_colorStack.Push( new ColorPair( Console.ForegroundColor, Console.BackgroundColor ) );
}
else if( 57 == code ) // NON-STANDARD (I made this one up)
{
Util.Fail( "The 56/57 SGR codes (non-standard push/pop) are deprecated. Use XTPUSHSGR/XTPOPSGR instead." );
ColorPair cp = m_colorStack.Pop();
Console.ForegroundColor = cp.Foreground;
Console.BackgroundColor = cp.Background;

Просмотреть файл

@ -56,46 +56,46 @@ namespace MS.Dbg
return Length( s, 0 );
}
public static int Length( string s, int startIndex )
{
if( null == s )
throw new ArgumentNullException( "s" );
if( startIndex == s.Length ) // special case for zero-length string
return 0;
if( startIndex > s.Length )
throw new ArgumentOutOfRangeException( nameof( startIndex ), startIndex, "startIndex is past the end of the string" );
int length = 0;
int escIndex = s.IndexOf( CSI, startIndex );
if( escIndex < 0 )
return s.Length - startIndex; // TODO: String caches its length, right?
int length = escIndex - startIndex; // not including command sequence stuff.
int curIndex = escIndex;
char c;
while( ++curIndex < s.Length )
while( escIndex >= 0 )
{
c = s[ curIndex ];
if( (c >= '0') && (c <= '9') )
{
continue;
}
else if( ';' == c )
{
continue;
}
else if( (c >= '@') && (c <= '~') ) // The command code character.
{
if( s.Length == (curIndex + 1) )
return length; // the command sequence is at the very end of the string.
length += escIndex - startIndex;
return length + Length( s, curIndex + 1 );
startIndex = _SkipControlSequence( s, escIndex );
if( startIndex < s.Length )
{
escIndex = s.IndexOf( CSI, startIndex );
}
else
{
// You're supposed to be able to have anonther character, like a space
// (0x20) before the command code character, but I'm not going to do that.
throw new ArgumentException( String.Format( "Invalid command sequence character at position {0} (0x{1:x}).", curIndex, (int) c ) );
// The control sequence was at the end of the string; we're done.
return length;
}
}
// Finished parsing the whole string, without seeing the end of the control
// sequence--the control sequence must be broken across strings.
throw _CreateBrokenSequenceException();
// No more control sequences left.
Util.Assert( startIndex < s.Length );
length += s.Length - startIndex; // TODO: String caches its length, right?
return length;
} // end Length()
@ -139,9 +139,13 @@ namespace MS.Dbg
if( _IsDigitOrSemi( c ) )
continue;
if( (c >= '@') && (c <= '~') ) // The command code character.
if( ((c >= '@') && (c <= '~')) || (c == '#') ) // The command code character.
{
return curIdx + 1; // note that this could be just past the end of the string.
int commandLen = 1;
if( c == '#' )
commandLen = 2; // '#' is the first char of a command like "#{" or "#}"
return curIdx + commandLen; // note that this could be just past the end of the string.
}
else
{
@ -562,8 +566,8 @@ namespace MS.Dbg
internal const string SGR = "m"; // SGR: "Select Graphics Rendition"
internal const string PUSH = "56";
internal const string POP = "57";
internal const string PUSH = "#{"; // XTPUSHSGR
internal const string POP = "#}"; // XTPOPSGR
// public static string FG( ConsoleColor foreground )
// {
@ -695,8 +699,8 @@ namespace MS.Dbg
// leading space is longer than the entire outputWidth
}
private const string c_PushAndReset = "\u009b56;0m";
private const string c_StandalonePop = "\u009b57m";
private const string c_PushAndReset = "\u009b#{\u009b0m";
private const string c_StandalonePop = "\u009b#}";
public static string IndentAndWrap( string str,
int outputWidth,
@ -1252,6 +1256,10 @@ namespace MS.Dbg
0 ),
new CaStringUtilLengthTestCase( "\u009bm",
0 ),
new CaStringUtilLengthTestCase( "\u009b#{",
0 ),
new CaStringUtilLengthTestCase( "\u009b#{\u009b91mRED\u009b#}",
3 ),
new CaStringUtilLengthTestCase( "\u009bm123",
3 ),
new CaStringUtilLengthTestCase( "123\u009bm123",
@ -1842,7 +1850,16 @@ namespace MS.Dbg
try
{
int actual = Length( testCase.Input );
if( actual != testCase.ExpectedLength )
if( testCase.ExpectedExceptionType != null )
{
failures++;
Console.WriteLine( "Length test case {0} failed. Expected an exception of type: {1}, Actual: computed length of {2}.",
i,
Util.GetGenericTypeName( testCase.ExpectedExceptionType ),
actual );
}
else if( actual != testCase.ExpectedLength )
{
failures++;
//Util.Fail( Util.Sprintf( "Test case {0} failed. Expected: {1}, Actual: {2}.",

Просмотреть файл

@ -215,30 +215,26 @@ namespace MS.Dbg
return Append( Environment.NewLine );
}
private const int PUSH = 56;
private const int POP = 57;
private static readonly int[] PopArray = { POP };
public ColorString AppendPush()
{
_CheckReadOnly( false );
m_elements.Add( new SgrControlSequence( new int[] { PUSH } ) );
m_elements.Add( PushSgrSequence.Instance );
return this;
}
public ColorString AppendPushFg( ConsoleColor foreground )
{
_CheckReadOnly( false );
m_elements.Add( new SgrControlSequence( new int[] { PUSH,
CaStringUtil.ForegroundColorMap[ foreground ] } ) );
m_elements.Add( PushSgrSequence.Instance );
m_elements.Add( new SgrControlSequence( new int[] { CaStringUtil.ForegroundColorMap[ foreground ] } ) );
return this;
}
public ColorString AppendPushBg( ConsoleColor background )
{
_CheckReadOnly( false );
m_elements.Add( new SgrControlSequence( new int[] { PUSH,
CaStringUtil.BackgroundColorMap[ background ] } ) );
m_elements.Add( PushSgrSequence.Instance );
m_elements.Add( new SgrControlSequence( new int[] { CaStringUtil.BackgroundColorMap[ background ] } ) );
return this;
}
@ -246,8 +242,8 @@ namespace MS.Dbg
public ColorString AppendPushFgBg( ConsoleColor foreground, ConsoleColor background )
{
_CheckReadOnly( false );
m_elements.Add( new SgrControlSequence( new int[] { PUSH,
CaStringUtil.ForegroundColorMap[ foreground ],
m_elements.Add( PushSgrSequence.Instance );
m_elements.Add( new SgrControlSequence( new int[] { CaStringUtil.ForegroundColorMap[ foreground ],
CaStringUtil.BackgroundColorMap[ background ] } ) );
return this;
}
@ -255,32 +251,32 @@ namespace MS.Dbg
public ColorString AppendPop()
{
_CheckReadOnly( false );
m_elements.Add( new SgrControlSequence( PopArray ) );
m_elements.Add( PopSgrSequence.Instance );
return this;
}
public ColorString AppendPushPopFg( ConsoleColor foreground, string content )
{
_CheckReadOnly( true );
m_elements.Add( new SgrControlSequence( new int[] { PUSH,
CaStringUtil.ForegroundColorMap[ foreground ] } ) );
m_elements.Add( PushSgrSequence.Instance );
m_elements.Add( new SgrControlSequence( new int[] { CaStringUtil.ForegroundColorMap[ foreground ] } ) );
m_elements.Add( new ContentElement( content ) );
// The content might not be "pure"; it might be pre-rendered colorized text.
m_apparentLength += CaStringUtil.Length( content );
m_elements.Add( new SgrControlSequence( PopArray ) );
m_elements.Add( PopSgrSequence.Instance );
return this;
}
public ColorString AppendPushPopFgBg( ConsoleColor foreground, ConsoleColor background, string content )
{
_CheckReadOnly( true );
m_elements.Add( new SgrControlSequence( new int[] { PUSH,
CaStringUtil.ForegroundColorMap[ foreground ],
m_elements.Add( PushSgrSequence.Instance );
m_elements.Add( new SgrControlSequence( new int[] { CaStringUtil.ForegroundColorMap[ foreground ],
CaStringUtil.BackgroundColorMap[ background ] } ) );
m_elements.Add( new ContentElement( content ) );
// The content might not be "pure"; it might be pre-rendered colorized text.
m_apparentLength += CaStringUtil.Length( content );
m_elements.Add( new SgrControlSequence( PopArray ) );
m_elements.Add( PopSgrSequence.Instance );
return this;
}
@ -512,38 +508,82 @@ namespace MS.Dbg
} // end AppendTo();
} // end class ContentElement
private class SgrControlSequence : ColorStringElement
{
public readonly IReadOnlyList< int > Commands;
public SgrControlSequence( IReadOnlyList< int > commands )
private abstract class ControlSequence : ColorStringElement
{
Commands = commands ?? throw new ArgumentNullException( nameof(commands) );
public readonly IReadOnlyList< int > Parameters;
protected ControlSequence( IReadOnlyList< int > parameters )
{
Parameters = parameters; // optional
} // end constructor
private const char CSI = '\x9b'; // "Control Sequence Initiator"
private const char SGR = 'm'; // "Select Graphics Rendition"
protected const char CSI = '\x9b'; // "Control Sequence Initiator"
protected abstract string Command { get; }
public override StringBuilder AppendTo( StringBuilder sb, bool withColor )
{
return withColor ? AppendCommands(sb, Commands) : sb;
return withColor ? _AppendCommands( sb, Parameters ) : sb;
} // end AppendTo();
public static StringBuilder AppendCommands(StringBuilder sb, IReadOnlyList<int> commands)
private StringBuilder _AppendCommands( StringBuilder sb, IReadOnlyList< int > parameters )
{
sb.Append( CSI );
for( int i = 0; i < commands.Count; i++ )
{
sb.Append( commands[ i ].ToString( CultureInfo.InvariantCulture ) );
if( i != (commands.Count - 1) )
sb.Append( ';' );
}
return sb.Append( SGR );
return AppendCommand( sb, parameters, Command );
} // end AppendCommands();
internal static StringBuilder AppendCommand( StringBuilder sb, IReadOnlyList< int > parameters, string command )
{
sb.Append( CSI );
if( parameters != null )
{
for( int i = 0; i < parameters.Count; i++ )
{
sb.Append( parameters[ i ].ToString( CultureInfo.InvariantCulture ) );
if( i != (parameters.Count - 1) )
sb.Append( ';' );
}
}
return sb.Append( command );
} // end AppendCommand();
} // end class ControlSequence
private class SgrControlSequence : ControlSequence
{
public SgrControlSequence( IReadOnlyList< int > parameters ) : base( parameters )
{
if( null == parameters )
throw new ArgumentNullException( nameof( parameters ) );
} // end constructor
protected override string Command { get { return "m"; } } // "Select Graphics Rendition"
} // end class SgrControlSequence
private class PushSgrSequence : ControlSequence
{
private PushSgrSequence() : base( null )
{
}
protected override string Command { get { return "#{"; } } // "XTPUSHSGR"
public static readonly PushSgrSequence Instance = new PushSgrSequence();
} // end class PushSgrSequence
private class PopSgrSequence : ControlSequence
{
private PopSgrSequence() : base( null )
{
}
protected override string Command { get { return "#}"; } } // "XTPOPSGR"
public static readonly PopSgrSequence Instance = new PopSgrSequence();
} // end class PopSgrSequence
private class FormatStringElement : ColorStringElement
{
private readonly string m_formatString;
@ -604,11 +644,11 @@ namespace MS.Dbg
if( hasFg || hasBg )
{
var sb = new StringBuilder();
var commands = new List<int> { PUSH };
if( hasFg ) { commands.Add( CaStringUtil.ForegroundColorMap[ fgColor ] ); }
if( hasBg ) { commands.Add( CaStringUtil.BackgroundColorMap[ bgColor ] ); }
SgrControlSequence.AppendCommands( sb, commands );
var sb = new StringBuilder( PushSgrSequence.Instance.ToString() );
var parameters = new List< int >( 2 );
if( hasFg ) { parameters.Add( CaStringUtil.ForegroundColorMap[ fgColor ] ); }
if( hasBg ) { parameters.Add( CaStringUtil.BackgroundColorMap[ bgColor ] ); }
SgrControlSequence.AppendCommand( sb, parameters, "m" );
if( arg is ISupportColor asColor )
{
@ -619,7 +659,7 @@ namespace MS.Dbg
sb.Append( ArgumentToString( arg, formatPieces[ 0 ], true ) );
}
SgrControlSequence.AppendCommands( sb, PopArray );
sb.Append( PopSgrSequence.Instance.ToString() );
return sb.ToString();
}

Просмотреть файл

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using MS.Dbg;
using ConsoleHandle = Microsoft.Win32.SafeHandles.SafeFileHandle;
@ -21,11 +23,12 @@ internal static partial class ConsoleControl
//
internal class AnsiColorWriter
{
private const char CSI = '\x9b'; // "Control Sequence Initiator"
private const char CSI = '\x9b'; // "Control Sequence Initiator" (single character, as opposed to '\x1b' + '[')
private ConsoleColor DefaultForeground;
private ConsoleColor DefaultBackground;
private Dictionary< char, Action< List< int > > > m_commands;
private readonly IReadOnlyDictionary< char, Func< Action< List< int > > > > m_commandTreeRoot;
private readonly IReadOnlyDictionary< char, Func< Action< List< int > > > > m_hashCommands;
private ControlSequenceParseState m_state;
@ -34,38 +37,64 @@ internal static partial class ConsoleControl
private struct ControlSequenceParseState
{
private int m_accum;
private bool m_isParsingParam;
private List< int > m_list;
// True if we are in the middle of parsing a sequence. Useful for when sequences
// are broken across multiple calls.
public bool Parsing { get; private set; }
public bool IsParsing
{
get
{
return null != CurrentCommands;
}
}
private IReadOnlyDictionary< char, Func< Action< List< int > > > > m_commandTreeRoot;
public ControlSequenceParseState( IReadOnlyDictionary< char, Func< Action< List< int > > > > rootCommands )
{
m_commandTreeRoot = rootCommands;
CurrentCommands = null;
m_accum = 0;
m_isParsingParam = false;
m_list = null;
}
public IReadOnlyDictionary< char, Func< Action< List< int > > > > CurrentCommands;
// This can tolerate multiple Begin calls without destroying state.
public void Begin()
{
if( !Parsing )
if( !IsParsing )
{
Parsing = true;
CurrentCommands = m_commandTreeRoot;
m_list = new List< int >();
m_isParsingParam = false;
}
}
public void Accum( int digit )
public void AccumParamDigit( int digit )
{
m_accum *= 10;
m_accum += digit;
m_isParsingParam = true;
}
public void Enter()
public void EnterParam()
{
Util.Assert( m_isParsingParam );
m_list.Add( m_accum );
m_isParsingParam = false;
m_accum = 0;
}
public List< int > Finish()
public List< int > FinishParsingParams()
{
Enter();
Parsing = false;
if( m_isParsingParam )
EnterParam();
CurrentCommands = null;
List< int > list = m_list;
m_list = null;
return list;
@ -76,7 +105,8 @@ internal static partial class ConsoleControl
/// </summary>
public void Reset()
{
Parsing = false;
CurrentCommands = null;
m_isParsingParam = false;
m_list = null;
m_accum = 0;
}
@ -88,10 +118,19 @@ internal static partial class ConsoleControl
DefaultForeground = ConsoleControl.ForegroundColor;
DefaultBackground = ConsoleControl.BackgroundColor;
m_commands = new Dictionary< char, Action< List< int > > >()
m_commandTreeRoot = new Dictionary< char, Func< Action< List< int > > > >()
{
{ 'm', _SelectGraphicsRendition },
{ 'm', () => _SelectGraphicsRendition },
{ '#', _ProcessHashCommand },
};
m_hashCommands = new Dictionary< char, Func< Action< List< int > > > >()
{
{ '{', () => _PushSgr },
{ '}', () => _PopSgr },
};
m_state = new ControlSequenceParseState( m_commandTreeRoot );
} // end constructor
@ -99,7 +138,7 @@ internal static partial class ConsoleControl
{
int startIndex = 0;
if( m_state.Parsing )
if( m_state.IsParsing )
{
// Need to continue.
startIndex = _HandleControlSequence( s, -1 );
@ -126,7 +165,9 @@ internal static partial class ConsoleControl
/// <summary>
/// Returns the index of the next character after the escape sequence and code(s).
/// Returns the index of the first character past the control sequence (which
/// could be past the end of the string, or could be the start of another
/// control sequence).
/// </summary>
private int _HandleControlSequence( string s, int escIndex )
{
@ -139,20 +180,22 @@ internal static partial class ConsoleControl
c = s[ curIndex ];
if( (c >= '0') && (c <= '9') )
{
m_state.Accum( ((int) c) - 0x30 );
m_state.AccumParamDigit( ((int) c) - 0x30 );
continue;
}
else if( ';' == c )
{
m_state.Enter();
m_state.EnterParam();
continue;
}
else if( (c >= '@') && (c <= '~') )
else if( ((c >= '@') && (c <= '~')) || (c == '#') )
{
List< int > args = m_state.Finish();
_ProcessCommand( c, args );
if( _FindCommand( c, out Action< List< int > > command ) )
{
command( m_state.FinishParsingParams() );
return curIndex + 1;
}
}
else
{
// You're supposed to be able to have anonther character, like a space
@ -174,18 +217,23 @@ internal static partial class ConsoleControl
} // end _HandleControlSequence()
private void _ProcessCommand( char commandChar, List< int > args )
/// <summary>
/// Returns true if it successfully found a command to execute; false if we
/// need to read more characters (some commands are expressed with multiple
/// characters).
/// </summary>
private bool _FindCommand( char commandChar, out Action< List< int > > command )
{
Action< List< int > > action;
if( !m_commands.TryGetValue( commandChar, out action ) )
Func< Action< List< int > > > commandSearcher;
if( !m_state.CurrentCommands.TryGetValue( commandChar, out commandSearcher ) )
{
// See big comment above the other throw above.
m_state.Reset();
throw new NotSupportedException( String.Format( "The command code '{0}' (0x{1:x}) is not supported.", commandChar, (int) commandChar ) );
}
action( args );
} // end _ProcessCommand()
command = commandSearcher();
return command != null;
} // end _FindCommand()
// Info sources:
@ -224,6 +272,8 @@ internal static partial class ConsoleControl
// 56: Push fg/bg color pair
// 57: Pop fg/bg color pair
//
// However, THESE ARE DEPRECATED. Use XTPUSHSGR/XTPOPSGR instead.
//
private void _SelectGraphicsRendition( List< int > args )
{
@ -237,6 +287,38 @@ internal static partial class ConsoleControl
} // end _SelectGraphicsRendition()
private void _PushSgr( List< int > args )
{
if( (args != null) && (args.Count != 0) )
{
throw new NotSupportedException( "Optional arguments to the XTPUSHSGR command are not currently implemented." );
}
m_colorStack.Push( new ColorPair( Console.ForegroundColor, Console.BackgroundColor ) );
} // end _PushSgr()
private void _PopSgr( List< int > args )
{
if( (args != null) && (args.Count != 0) )
{
throw new InvalidOperationException( "The XTPOPSGR command does not accept arguments." );
}
ColorPair cp = m_colorStack.Pop();
Console.ForegroundColor = cp.Foreground;
Console.BackgroundColor = cp.Background;
} // end _PopSgr()
private Action< List< int > > _ProcessHashCommand()
{
m_state.CurrentCommands = m_hashCommands;
return null;
} // end _SelectGraphicsRendition()
private class ColorPair
{
public ConsoleColor Foreground { get; private set; }
@ -275,10 +357,12 @@ internal static partial class ConsoleControl
}
else if( 56 == code ) // NON-STANDARD (I made this one up)
{
Util.Fail( "The 56/57 SGR codes (non-standard push/pop) are deprecated. Use XTPUSHSGR/XTPOPSGR instead." );
m_colorStack.Push( new ColorPair( ConsoleControl.ForegroundColor, ConsoleControl.BackgroundColor ) );
}
else if( 57 == code ) // NON-STANDARD (I made this one up)
{
Util.Fail( "The 56/57 SGR codes (non-standard push/pop) are deprecated. Use XTPUSHSGR/XTPOPSGR instead." );
ColorPair cp = m_colorStack.Pop();
ConsoleControl.ForegroundColor = cp.Foreground;
ConsoleControl.BackgroundColor = cp.Background;