зеркало из https://github.com/microsoft/DbgShell.git
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:
Родитель
fcf6689127
Коммит
b3e91e4472
|
@ -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 );
|
||||
return curIndex + 1;
|
||||
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
|
||||
{
|
||||
|
@ -561,9 +565,9 @@ 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 SGR = "m"; // SGR: "Select Graphics Rendition"
|
||||
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
|
||||
{
|
||||
public readonly IReadOnlyList< int > Parameters;
|
||||
|
||||
protected ControlSequence( IReadOnlyList< int > parameters )
|
||||
{
|
||||
Commands = commands ?? throw new ArgumentNullException( nameof(commands) );
|
||||
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,19 +180,21 @@ 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 );
|
||||
return curIndex + 1;
|
||||
if( _FindCommand( c, out Action< List< int > > command ) )
|
||||
{
|
||||
command( m_state.FinishParsingParams() );
|
||||
return curIndex + 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -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;
|
||||
|
|
Загрузка…
Ссылка в новой задаче