Merge pull request #81 from Zhentar/rgb

[WIP] Add DbgMemory RGB output format
This commit is contained in:
Dan Thompson 2020-01-01 18:06:35 -08:00 коммит произвёл GitHub
Родитель a675c75ec4 ba144f5b77
Коммит 9175d49e05
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
15 изменённых файлов: 717 добавлений и 816 удалений

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

@ -73,8 +73,8 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.2.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.4.0\lib\netstandard1.1\System.Buffers.dll</HintPath>
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.5.0\lib\netstandard1.1\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.Core" />
<Reference Include="Microsoft.CSharp" />
@ -82,15 +82,14 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0\System.Management.Automation.dll</HintPath>
</Reference>
<Reference Include="System.Memory, Version=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.5.1\lib\netstandard1.1\System.Memory.dll</HintPath>
<Reference Include="System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.5.3\lib\netstandard1.1\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.5.0\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="internal\AnsiColorWriter.cs" />
<Compile Include="internal\BlockingCollectionHolder.cs" />
<Compile Include="internal\CaStringUtil.cs" />
<Compile Include="internal\CircularBuffer.cs" />
@ -117,6 +116,7 @@
<Compile Include="public\Commands\AddDbgExtensionCommand.cs" />
<Compile Include="public\Commands\AliasCommands.cs" />
<Compile Include="public\Commands\BreakpointListCommands.cs" />
<Compile Include="public\Commands\ConvertToDbgRgbCommand.cs" />
<Compile Include="public\Commands\FindWindbgDirCommand.cs" />
<Compile Include="public\Commands\GetClrHeapCommand.cs" />
<Compile Include="public\Commands\GetClrObjectCommand.cs" />
@ -292,6 +292,7 @@
<EmbeddedResource Include="resources.resx" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="Debugger.ArgumentCompleters.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

11
DbgProvider/app.config Normal file
Просмотреть файл

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

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

@ -1,412 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace MS.Dbg
{
// This class interprets ANSI escape sequences to alter the color of console output. Only
// a limited set of control sequences are supported, namely a subset of SGR (Select
// Graphics Rendition) commands.
//
// See:
//
// http://bjh21.me.uk/all-escapes/all-escapes.txt
// http://en.wikipedia.org/wiki/ISO/IEC_6429
// http://en.wikipedia.org/wiki/ISO_6429
// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
//
internal class AnsiColorWriter : TextWriter
{
private const char CSI = '\x9b'; // "Control Sequence Initiator" (single character, as opposed to '\x1b' + '[')
private ConsoleColor DefaultForeground;
private ConsoleColor DefaultBackground;
private readonly IReadOnlyDictionary< char, Func< Action< List< int > > > > m_commandTreeRoot;
private readonly IReadOnlyDictionary< char, Func< Action< List< int > > > > m_hashCommands;
private ControlSequenceParseState m_state;
// We support control sequences being broken across calls to Write methods. This
// struct keeps track of state in between calls.
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 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( !IsParsing )
{
CurrentCommands = m_commandTreeRoot;
m_list = new List< int >();
m_isParsingParam = false;
}
}
public void AccumParamDigit( int digit )
{
m_accum *= 10;
m_accum += digit;
m_isParsingParam = true;
}
public void EnterParam()
{
Util.Assert( m_isParsingParam );
m_list.Add( m_accum );
m_isParsingParam = false;
m_accum = 0;
}
public List< int > FinishParsingParams()
{
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
public AnsiColorWriter()
{
DefaultForeground = Console.ForegroundColor;
DefaultBackground = Console.BackgroundColor;
m_commandTreeRoot = new Dictionary< char, Func< Action< List< int > > > >()
{
{ 'm', () => _SelectGraphicsRendition },
{ '#', _ProcessHashCommand },
};
m_hashCommands = new Dictionary< char, Func< Action< List< int > > > >()
{
// TROUBLE: The current definition of XTPUSHSGR and XTPOPSGR use curly
// brackets, which turns out to conflict badly with C# string formatting.
// For example, this:
//
//
// $csFmt = (New-ColorString).AppendPushFg( 'Cyan' ).Append( 'this should all be cyan: {0}' )
// [string]::format( $csFmt.ToString( $true ), 'blah' )
//
// will blow up.
//
// For now, I'm going to switch to some other characters while we see if
// we get can something worked out with xterm.
//{ '{', () => _PushSgr },
//{ '}', () => _PopSgr },
{ 'p', () => _PushSgr },
{ 'q', () => _PopSgr },
};
m_state = new ControlSequenceParseState( m_commandTreeRoot );
} // end constructor
public override Encoding Encoding { get { return Console.Out.Encoding; } }
public override void Write( char[] charBuffer, int index, int count)
{
Write( new String( charBuffer, index, count ) );
}
public override void Write( char c )
{
Write( c.ToString() );
}
public override void Write( string s )
{
int startIndex = 0;
if( m_state.IsParsing )
{
// Need to continue.
startIndex = _HandleControlSequence( s, -1 );
}
int escIndex = s.IndexOf( CSI, startIndex );
while( escIndex >= 0 )
{
//Tool.WL( "escIndex: {0}, startIndex: {1}", escIndex, startIndex );
string chunk = s.Substring( startIndex, escIndex - startIndex );
Console.Write( chunk );
startIndex = _HandleControlSequence( s, escIndex );
escIndex = s.IndexOf( CSI, startIndex );
}
if( !(startIndex >= s.Length) )
{
Console.Write( s.Substring( startIndex ) );
}
} // 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...
char c;
int curIndex = escIndex;
while( ++curIndex < s.Length )
{
c = s[ curIndex ];
if( (c >= '0') && (c <= '9') )
{
m_state.AccumParamDigit( ((int) c) - 0x30 );
continue;
}
else if( ';' == c )
{
m_state.EnterParam();
continue;
}
else if( ((c >= '@') && (c <= '~')) || (c == '#') )
{
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 ) );
}
}
// Finished parsing the whole string--the control sequence must be broken across
// strings.
return curIndex;
} // end _HandleControlSequence()
/// <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 )
{
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 ) );
}
command = commandSearcher();
return command != null;
} // end _FindCommand()
// Info sources:
// http://bjh21.me.uk/all-escapes/all-escapes.txt
// http://en.wikipedia.org/wiki/ISO/IEC_6429
// http://en.wikipedia.org/wiki/ISO_6429
// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
private static ConsoleColor[] AnsiNormalColorMap =
{
// Foreground Background
ConsoleColor.Black, // 30 40
ConsoleColor.DarkRed, // 31 41
ConsoleColor.DarkGreen, // 32 42
ConsoleColor.DarkYellow, // 33 43
ConsoleColor.DarkBlue, // 34 44
ConsoleColor.DarkMagenta, // 35 45
ConsoleColor.DarkCyan, // 36 46
ConsoleColor.Gray // 37 47
};
private static ConsoleColor[] AnsiBrightColorMap =
{ // Foreground Background
ConsoleColor.DarkGray, // 90 100
ConsoleColor.Red, // 91 101
ConsoleColor.Green, // 92 102
ConsoleColor.Yellow, // 93 103
ConsoleColor.Blue, // 94 104
ConsoleColor.Magenta, // 95 105
ConsoleColor.Cyan, // 96 106
ConsoleColor.White // 97 107
};
// In addition to the color codes, I've added support for two additional
// (non-standard) codes:
//
// 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 )
{
if( 0 == args.Count )
args.Add( 0 ); // no args counts as a reset
foreach( int arg in args )
{
_ProcessSgrCode( arg );
}
} // 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; }
public ConsoleColor Background { get; private set; }
public ColorPair( ConsoleColor foreground, ConsoleColor background )
{
Foreground = foreground;
Background = background;
}
}
private Stack< ColorPair > m_colorStack = new Stack< ColorPair >();
private void _ProcessSgrCode( int code )
{
if( 0 == code )
{
Console.ForegroundColor = DefaultForeground;
Console.BackgroundColor = DefaultBackground;
}
else if( (code <= 37) && (code >= 30) )
{
Console.ForegroundColor = AnsiNormalColorMap[ (code - 30) ];
}
else if( (code <= 47) && (code >= 40) )
{
Console.BackgroundColor = AnsiNormalColorMap[ (code - 40) ];
}
else if( (code <= 97) && (code >= 90) )
{
Console.ForegroundColor = AnsiBrightColorMap[ (code - 90) ];
}
else if( (code <= 107) && (code >= 100) )
{
Console.BackgroundColor = AnsiBrightColorMap[ (code - 100) ];
}
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;
}
else
{
throw new NotSupportedException( String.Format( "SGR code '{0}' not supported.", code ) );
}
} // end _ProcessSgrCode()
public static void Test()
{
AnsiColorWriter colorWriter = new AnsiColorWriter();
colorWriter.Write( "\u009b91mThis should be red.\u009bm This should not.\r\n" );
colorWriter.Write( "\u009b91;41mThis should be red on red.\u009bm This should not.\r\n" );
colorWriter.Write( "\u009b91;100mThis should be red on gray.\u009b" );
colorWriter.Write( "m This should not.\r\n" );
colorWriter.Write( "\u009b" );
colorWriter.Write( "9" );
colorWriter.Write( "1;10" );
colorWriter.Write( "6mThis should be red on cyan.\u009b" );
colorWriter.Write( "m This should \u009bm\u009bmnot.\r\n" );
} // end Test
} // end class AnsiColorWriter
}

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

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="AddGitVersionInfo" version="1.0.0.1" targetFramework="net45" developmentDependency="true" />
<package id="System.Buffers" version="4.4.0" targetFramework="net46" />
<package id="System.Memory" version="4.5.1" targetFramework="net46" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.0" targetFramework="net46" />
<package id="System.Buffers" version="4.5.0" targetFramework="net46" />
<package id="System.Memory" version="4.5.3" targetFramework="net46" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.2" targetFramework="net46" />
<package id="Unofficial.Microsoft.DebuggerBinaries" version="10.0.17763.132" targetFramework="net45" />
<package id="Unofficial.Microsoft.TextTemplating.BuildTasks" version="16.0.0.1" targetFramework="net46" developmentDependency="true" />
</packages>

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

@ -313,6 +313,24 @@ namespace MS.Dbg
m_apparentLength += other.m_apparentLength;
return this;
}
public ColorString AppendFgRgb( byte r, byte g, byte b, ColorString other = null)
{
_CheckReadOnly( true );
m_elements.Add( new SgrControlSequence( new[] { 38, 2, r, g, b } ) );
if( null == other )
return this;
return Append( other );
}
public ColorString AppendBgRgb( byte r, byte g, byte b, ColorString other = null )
{
_CheckReadOnly( true );
m_elements.Add( new SgrControlSequence( new[] { 48, 2, r, g, b } ) );
if( null == other )
return this;
return Append( other );
}
public ColorString AppendLine( ColorString other )
{

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

@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Text;
using System.Threading.Tasks;
namespace MS.Dbg.Commands
{
[Cmdlet( VerbsData.ConvertTo, "DbgRgb" )]
[OutputType( typeof( ColorString ) )]
public class ConvertToDbgRgbCommand : DbgBaseCommand
{
[Parameter( Mandatory = true,
Position = 0,
ValueFromPipeline = true)]
public DbgMemory Memory { get; set; }
[Parameter( Mandatory = false )]
public uint Columns { get; set; }
[Parameter( Mandatory = false )]
public SwitchParameter Alpha { get; set; }
protected override void ProcessRecord()
{
base.ProcessRecord();
WriteObject( FormatRGB( Columns, Alpha ) );
}
private static readonly string[] AlphaChars = { "░", "▒", "▓", "█" };
private ColorString FormatRGB( uint numColumns, bool withAlpha )
{
var bytes = Memory.Bytes;
bool is32Bit = Debugger.TargetIs32Bit;
ulong startAddress = Memory.StartAddress;
if( 0 == numColumns )
numColumns = 64;
ColorString cs = new ColorString();
var bytesPerCharacter = withAlpha ? 4 : 3;
int bytesPerRow = (int) numColumns * bytesPerCharacter + 3 & ~(3); //round up
for( int rowStart = 0; rowStart + bytesPerCharacter < bytes.Count; rowStart += bytesPerRow )
{
if( rowStart != 0 )
{
cs.AppendLine();
}
cs.Append( DbgProvider.FormatAddress( startAddress + (uint) rowStart, is32Bit, true, true ) ).Append( " " );
var rowLen = Math.Min( bytes.Count - rowStart, bytesPerRow );
for( int colOffset = 0; colOffset + bytesPerCharacter < rowLen; colOffset += bytesPerCharacter )
{
byte b = bytes[ rowStart + colOffset + 0 ];
byte g = bytes[ rowStart + colOffset + 1 ];
byte r = bytes[ rowStart + colOffset + 2 ];
string ch = "█";
if( withAlpha )
{
ch = AlphaChars[ bytes[ rowStart + colOffset + 3 ] >> 6 ];
}
cs.AppendFgRgb( r, g, b, ch );
}
}
return cs.MakeReadOnly();
}
}
}

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

@ -37,7 +37,7 @@ namespace MS.Dbg
Pointers,
PointersWithSymbols,
PointersWithAscii,
PointersWithSymbolsAndAscii
PointersWithSymbolsAndAscii,
}
public class DbgMemory : ISupportColor, ICloneable

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

@ -1,11 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="Debugger"/>
<probing privatePath="Debugger" />
<dependentAssembly>
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

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

@ -12,6 +12,7 @@ using System.Text;
using System.Management.Automation;
using System.Management.Automation.Internal;
using System.Management.Automation.Host;
using System.Runtime.CompilerServices;
using System.Security;
using ConsoleHandle = Microsoft.Win32.SafeHandles.SafeFileHandle;
@ -59,15 +60,7 @@ namespace MS.DbgShell
// Turn on virtual terminal if possible.
// This might throw - not sure how exactly (no console), but if it does, we shouldn't fail to start.
var handle = ConsoleControl.GetActiveScreenBufferHandle();
var m = ConsoleControl.GetMode(handle);
if (ConsoleControl.NativeMethods.SetConsoleMode(handle.DangerousGetHandle(), (uint)(m | ConsoleControl.ConsoleModes.VirtualTerminal)))
{
// We only know if vt100 is supported if the previous call actually set the new flag, older
// systems ignore the setting.
m = ConsoleControl.GetMode(handle);
this.SupportsVirtualTerminal = (m & ConsoleControl.ConsoleModes.VirtualTerminal) != 0;
}
SupportsVirtualTerminal = ConsoleControl.CheckVirtualTerminalSupported();
}
catch
{
@ -639,7 +632,26 @@ namespace MS.DbgShell
#region WriteToConsole
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void WriteToConsole(char c, bool transcribeResult)
{
ReadOnlySpan<char> value = stackalloc char[1] {c};
WriteToConsole(value, transcribeResult);
}
internal void WriteToConsole(string value, bool transcribeResult)
{
WriteToConsole(value.AsSpan(), transcribeResult, newLine: false);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void WriteToConsole(ReadOnlySpan<char> value, bool transcribeResult)
{
WriteToConsole(value, transcribeResult, newLine: false);
}
internal void WriteToConsole(ReadOnlySpan<char> value, bool transcribeResult, bool newLine)
{
ConsoleHandle handle = ConsoleControl.GetActiveScreenBufferHandle();
@ -662,7 +674,7 @@ namespace MS.DbgShell
// This is atomic, so we don't lock here...
ConsoleControl.WriteConsole(handle, value);
ConsoleControl.WriteConsole(handle, value, newLine);
if (transcribeResult)
{
@ -697,12 +709,10 @@ namespace MS.DbgShell
private void WriteLineToConsole(string text)
{
WriteToConsole(text, true);
WriteToConsole(Crlf, true);
WriteToConsole(text.AsSpan(), transcribeResult: true, newLine: true );
}
private void WriteLineToConsole()
{
WriteToConsole(Crlf, true);
@ -832,8 +842,7 @@ namespace MS.DbgShell
lock (_instanceLock)
{
this.Write(value);
this.Write(Crlf);
this.WriteToConsole(value.AsSpan(), transcribeResult: true, newLine: true);
}
}

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

@ -154,7 +154,7 @@ namespace MS.DbgShell
private
void
PostWrite(string value)
PostWrite(ReadOnlySpan<char> value)
{
PostWrite();

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

@ -7,124 +7,136 @@ using ConsoleHandle = Microsoft.Win32.SafeHandles.SafeFileHandle;
namespace MS.DbgShell
{
// (unindented a level to simplify the diff with other similar copies of this code)
internal static partial class ConsoleControl
{
// This class interprets ANSI escape sequences to alter the color of console output. Only
// a limited set of control sequences are supported, namely a subset of SGR (Select
// Graphics Rendition) commands.
//
// See:
//
// http://bjh21.me.uk/all-escapes/all-escapes.txt
// http://en.wikipedia.org/wiki/ISO/IEC_6429
// http://en.wikipedia.org/wiki/ISO_6429
// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
//
internal class AnsiColorWriter
internal static partial class ConsoleControl
{
private const char CSI = '\x9b'; // "Control Sequence Initiator" (single character, as opposed to '\x1b' + '[')
private ConsoleColor DefaultForeground;
private ConsoleColor DefaultBackground;
private readonly IReadOnlyDictionary< char, Func< Action< List< int > > > > m_commandTreeRoot;
private readonly IReadOnlyDictionary< char, Func< Action< List< int > > > > m_hashCommands;
private ControlSequenceParseState m_state;
// We support control sequences being broken across calls to Write methods. This
// struct keeps track of state in between calls.
private struct ControlSequenceParseState
// This class interprets ANSI escape sequences to alter the color of console output. Only
// a limited set of control sequences are supported, namely a subset of SGR (Select
// Graphics Rendition) commands.
//
// See:
//
// http://bjh21.me.uk/all-escapes/all-escapes.txt
// http://en.wikipedia.org/wiki/ISO/IEC_6429
// http://en.wikipedia.org/wiki/ISO_6429
// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
//
internal class AnsiColorWriter
{
private int m_accum;
private bool m_isParsingParam;
private List< int > m_list;
private const char CSI = '\x9b'; // "Control Sequence Initiator" (single character, as opposed to '\x1b' + '[')
private const string CSI_str = "\x9b"; //String version for printing (was sticking the char in a stackalloc span, but hit https://github.com/dotnet/roslyn/issues/35874 )
// True if we are in the middle of parsing a sequence. Useful for when sequences
// are broken across multiple calls.
public bool IsParsing
private readonly ConsoleColor DefaultForeground;
private readonly ConsoleColor DefaultBackground;
private ControlSequenceParseState m_state;
public bool VirtualTerminalSupported { get; set; }
// We support control sequences being broken across calls to Write methods. This
// struct keeps track of state in between calls.
private struct ControlSequenceParseState
{
get
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 IsParsing
{
return null != CurrentCommands;
get
{
return null != CurrentCommandHandler;
}
}
}
private IReadOnlyDictionary< char, Func< Action< List< int > > > > m_commandTreeRoot;
private readonly Func< char, bool > m_commandTreeRootDelegate;
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( !IsParsing )
public ControlSequenceParseState( Func< char, bool > rootCommands )
{
CurrentCommands = m_commandTreeRoot;
m_list = new List< int >();
m_commandTreeRootDelegate = rootCommands;
CurrentCommandHandler = null;
m_accum = 0;
m_isParsingParam = false;
m_list = null;
}
public Func< char, bool > CurrentCommandHandler;
// This can tolerate multiple Begin calls without destroying state.
public void Begin()
{
if( !IsParsing )
{
CurrentCommandHandler = m_commandTreeRootDelegate;
m_list = new List< int >();
m_isParsingParam = false;
}
}
public void AccumParamDigit( int digit )
{
m_accum *= 10;
m_accum += digit;
m_isParsingParam = true;
}
public void EnterParam()
{
Util.Assert( m_isParsingParam );
m_list.Add( m_accum );
m_isParsingParam = false;
m_accum = 0;
}
public List< int > FinishParsingParams()
{
if( m_isParsingParam )
EnterParam();
CurrentCommandHandler = null;
List< int > list = m_list;
m_list = null;
return list;
}
/// <summary>
/// To recover from bad input.
/// </summary>
public void Reset()
{
CurrentCommandHandler = null;
m_isParsingParam = false;
m_list = null;
m_accum = 0;
}
} // end class ControlSequenceParseState
public AnsiColorWriter()
{
DefaultForeground = ConsoleControl.ForegroundColor;
DefaultBackground = ConsoleControl.BackgroundColor;
m_state = new ControlSequenceParseState( CommandTreeRootStateHandler );
} // end constructor
private bool CommandTreeRootStateHandler( char commandChar )
{
switch( commandChar )
{
case 'm':
_SelectGraphicsRendition( m_state.FinishParsingParams() );
return true;
case '#':
m_state.CurrentCommandHandler = HashCommandStateHandler;
return false;
default:
throw new NotSupportedException( String.Format( "The command code '{0}' (0x{1:x}) is not supported.", commandChar, (int) commandChar ) );
}
}
public void AccumParamDigit( int digit )
{
m_accum *= 10;
m_accum += digit;
m_isParsingParam = true;
}
public void EnterParam()
{
Util.Assert( m_isParsingParam );
m_list.Add( m_accum );
m_isParsingParam = false;
m_accum = 0;
}
public List< int > FinishParsingParams()
{
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
public AnsiColorWriter()
{
DefaultForeground = ConsoleControl.ForegroundColor;
DefaultBackground = ConsoleControl.BackgroundColor;
m_commandTreeRoot = new Dictionary< char, Func< Action< List< int > > > >()
{
{ 'm', () => _SelectGraphicsRendition },
{ '#', _ProcessHashCommand },
};
m_hashCommands = new Dictionary< char, Func< Action< List< int > > > >()
private bool HashCommandStateHandler( char commandChar )
{
// TROUBLE: The current definition of XTPUSHSGR and XTPOPSGR use curly
// brackets, which turns out to conflict badly with C# string formatting.
@ -140,253 +152,425 @@ internal static partial class ConsoleControl
// we get can something worked out with xterm.
//{ '{', () => _PushSgr },
//{ '}', () => _PopSgr },
{ 'p', () => _PushSgr },
{ 'q', () => _PopSgr },
};
m_state = new ControlSequenceParseState( m_commandTreeRoot );
} // end constructor
public void Write( ConsoleHandle handle, string s )
{
int startIndex = 0;
if( m_state.IsParsing )
{
// Need to continue.
startIndex = _HandleControlSequence( s, -1 );
}
int escIndex = s.IndexOf( CSI, startIndex );
while( escIndex >= 0 )
{
//Tool.WL( "escIndex: {0}, startIndex: {1}", escIndex, startIndex );
string chunk = s.Substring( startIndex, escIndex - startIndex );
//Console.Write( chunk );
ConsoleControl._RealWriteConsole( handle, chunk );
startIndex = _HandleControlSequence( s, escIndex );
escIndex = s.IndexOf( CSI, startIndex );
}
if( !(startIndex >= s.Length) )
{
//Console.Write( s.Substring( startIndex ) );
ConsoleControl._RealWriteConsole( handle, s.Substring( startIndex ) );
}
} // 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...
char c;
int curIndex = escIndex;
while( ++curIndex < s.Length )
{
c = s[ curIndex ];
if( (c >= '0') && (c <= '9') )
switch( commandChar )
{
m_state.AccumParamDigit( ((int) c) - 0x30 );
continue;
case 'p':
_PushSgr( m_state.FinishParsingParams() );
return true;
case 'q':
_PopSgr( m_state.FinishParsingParams() );
return true;
default:
throw new NotSupportedException( String.Format( "The command code '{0}' (0x{1:x}) is not supported.", commandChar, (int) commandChar ) );
}
else if( ';' == c )
}
private char[] m_outputCharBuffer = new char[ 16384 ];
private int m_outputBufferPos = 0;
public void Write( ConsoleHandle handle, ReadOnlySpan< char > s, bool appendNewline )
{
m_outputBufferPos = 0;
if( m_state.IsParsing )
{
m_state.EnterParam();
continue;
// Need to continue.
int startIndex = _HandleControlSequence( s, -1 );
s = s.Slice( startIndex );
}
else if( ((c >= '@') && (c <= '~')) || (c == '#') )
int escIndex = s.IndexOf( CSI );
while( escIndex >= 0 )
{
if( _FindCommand( c, out Action< List< int > > command ) )
{
command( m_state.FinishParsingParams() );
return curIndex + 1;
}
var chunk = s.Slice( 0, escIndex );
WriteConsoleWrapper( handle, chunk );
int startIndex = _HandleControlSequence( s, escIndex );
s = s.Slice( startIndex );
escIndex = s.IndexOf( CSI );
}
if( !s.IsEmpty )
{
WriteConsoleWrapper( handle, s );
}
if( appendNewline )
{
WriteConsoleWrapper( handle, ColorHostUserInterface.Crlf.AsSpan() );
}
FinalizeWrite( handle );
} // end Write()
private void WriteConsoleWrapper( ConsoleHandle handle, ReadOnlySpan<char> value )
{
if( VirtualTerminalSupported )
{
AppendToOutputBuffer( value );
}
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.
// [zhent] the PowerShell source claims that WriteConsole breaks down past 64KB. But it also seems to think `char` is/can be 4 bytes, so I'm skeptical.
// In my tests on Windows 10, it can handle at least up to 4GB (with enough patience), so I'll leave the chunking for versions that aren't recent enough to support VT
while( value.Length > 16383 )
{
_RealWriteConsole( handle, value.Slice( 0, 16383 ) );
value = value.Slice( 16383 );
}
// 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 ) );
_RealWriteConsole( handle, value );
}
}
// Finished parsing the whole string--the control sequence must be broken across
// strings.
return curIndex;
} // end _HandleControlSequence()
/// <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 )
{
Func< Action< List< int > > > commandSearcher;
if( !m_state.CurrentCommands.TryGetValue( commandChar, out commandSearcher ) )
private void AppendToOutputBuffer( ReadOnlySpan<char> value )
{
throw new NotSupportedException( String.Format( "The command code '{0}' (0x{1:x}) is not supported.", commandChar, (int) commandChar ) );
var destSpan = m_outputCharBuffer.AsSpan( m_outputBufferPos );
while( value.Length > destSpan.Length )
{
var newBuff = new char[ m_outputCharBuffer.Length * 2 ];
m_outputCharBuffer.AsSpan( 0, m_outputBufferPos ).CopyTo( newBuff.AsSpan() );
m_outputCharBuffer = newBuff;
destSpan = m_outputCharBuffer.AsSpan( m_outputBufferPos );
}
value.CopyTo( destSpan );
m_outputBufferPos += value.Length;
}
command = commandSearcher();
return command != null;
} // end _FindCommand()
// Commented out due to https://github.com/dotnet/roslyn/issues/35874
//private void AppendToOutputBuffer( char value )
//{
// Span< char > tempBuff = stackalloc char[ 1 ];
// tempBuff[ 0 ] = value;
// AppendToOutputBuffer( tempBuff );
//}
// Info sources:
// http://bjh21.me.uk/all-escapes/all-escapes.txt
// http://en.wikipedia.org/wiki/ISO/IEC_6429
// http://en.wikipedia.org/wiki/ISO_6429
// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
private static ConsoleColor[] AnsiNormalColorMap =
{
// Foreground Background
ConsoleColor.Black, // 30 40
ConsoleColor.DarkRed, // 31 41
ConsoleColor.DarkGreen, // 32 42
ConsoleColor.DarkYellow, // 33 43
ConsoleColor.DarkBlue, // 34 44
ConsoleColor.DarkMagenta, // 35 45
ConsoleColor.DarkCyan, // 36 46
ConsoleColor.Gray // 37 47
};
private static ConsoleColor[] AnsiBrightColorMap =
{ // Foreground Background
ConsoleColor.DarkGray, // 90 100
ConsoleColor.Red, // 91 101
ConsoleColor.Green, // 92 102
ConsoleColor.Yellow, // 93 103
ConsoleColor.Blue, // 94 104
ConsoleColor.Magenta, // 95 105
ConsoleColor.Cyan, // 96 106
ConsoleColor.White // 97 107
};
// In addition to the color codes, I've added support for two additional
// (non-standard) codes:
//
// 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 )
{
if( 0 == args.Count )
args.Add( 0 ); // no args counts as a reset
foreach( int arg in args )
private void FinalizeWrite( ConsoleHandle handle )
{
_ProcessSgrCode( arg );
}
} // 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." );
if( VirtualTerminalSupported )
{
_RealWriteConsole( handle, m_outputCharBuffer.AsSpan( 0, m_outputBufferPos ) );
}
}
m_colorStack.Push( new ColorPair( Console.ForegroundColor, Console.BackgroundColor ) );
} // end _PushSgr()
/// <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( ReadOnlySpan< char > s, int escIndex )
{
m_state.Begin(); // we may actually be continuing...
int curIndex = escIndex;
while( ++curIndex < s.Length )
{
char c = s[ curIndex ];
if( (c >= '0') && (c <= '9') )
{
m_state.AccumParamDigit( ((int) c) - 0x30 );
continue;
}
else if( ';' == c )
{
m_state.EnterParam();
continue;
}
else if( ((c >= '@') && (c <= '~')) || (c == '#') )
{
if( m_state.CurrentCommandHandler( c ) )
{
return curIndex + 1;
}
}
else
{
// You're supposed to be able to have another 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 ) );
}
}
// Finished parsing the whole string--the control sequence must be broken across
// strings.
return curIndex;
} // end _HandleControlSequence()
private void _PopSgr( List< int > args )
{
if( (args != null) && (args.Count != 0) )
// Info sources:
// http://bjh21.me.uk/all-escapes/all-escapes.txt
// http://en.wikipedia.org/wiki/ISO/IEC_6429
// http://en.wikipedia.org/wiki/ISO_6429
// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
private static ConsoleColor[] AnsiNormalColorMap =
{
throw new InvalidOperationException( "The XTPOPSGR command does not accept arguments." );
}
// Foreground Background
ConsoleColor.Black, // 30 40
ConsoleColor.DarkRed, // 31 41
ConsoleColor.DarkGreen, // 32 42
ConsoleColor.DarkYellow, // 33 43
ConsoleColor.DarkBlue, // 34 44
ConsoleColor.DarkMagenta, // 35 45
ConsoleColor.DarkCyan, // 36 46
ConsoleColor.Gray // 37 47
};
ColorPair cp = m_colorStack.Pop();
Console.ForegroundColor = cp.Foreground;
Console.BackgroundColor = cp.Background;
} // end _PopSgr()
private static ConsoleColor[] AnsiBrightColorMap =
{ // Foreground Background
ConsoleColor.DarkGray, // 90 100
ConsoleColor.Red, // 91 101
ConsoleColor.Green, // 92 102
ConsoleColor.Yellow, // 93 103
ConsoleColor.Blue, // 94 104
ConsoleColor.Magenta, // 95 105
ConsoleColor.Cyan, // 96 106
ConsoleColor.White // 97 107
};
// In addition to the color codes, I've added support for two additional
// (non-standard) codes:
//
// 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 )
{
if( 0 == args.Count )
args.Add( 0 ); // no args counts as a reset
int index = 0;
while( index < args.Count )
{
_ProcessSgrCode( args, ref index );
}
} // end _SelectGraphicsRendition()
private Action< List< int > > _ProcessHashCommand()
{
m_state.CurrentCommands = m_hashCommands;
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." );
}
return null;
} // end _SelectGraphicsRendition()
m_colorCodeStack.Push( new ColorCodePair( ForegroundCode, BackgroundCode ) );
} // end _PushSgr()
private class ColorPair
{
public ConsoleColor Foreground { get; private set; }
public ConsoleColor Background { get; private set; }
public ColorPair( ConsoleColor foreground, ConsoleColor background )
private void _PopSgr( List< int > args )
{
Foreground = foreground;
Background = background;
}
}
if( (args != null) && (args.Count != 0) )
{
throw new InvalidOperationException( "The XTPOPSGR command does not accept arguments." );
}
private Stack< ColorPair > m_colorStack = new Stack< ColorPair >();
var pair = m_colorCodeStack.Pop();
ForegroundCode = pair.Foreground;
BackgroundCode = pair.Background;
} // end _PopSgr()
private struct VtColorCode
{
public VtColorCode( int commandCode ) : this()
{
CommandCode = (byte)commandCode;
}
private void _ProcessSgrCode( int code )
{
if( 0 == code )
{
ConsoleControl.ForegroundColor = DefaultForeground;
ConsoleControl.BackgroundColor = DefaultBackground;
public VtColorCode( byte commandCode, byte r, byte g, byte b )
{
CommandCode = commandCode;
R = r;
G = g;
B = b;
}
public byte CommandCode { get; }
public byte R { get; }
public byte G { get; }
public byte B { get; }
}
else if( (code <= 37) && (code >= 30) )
private struct ColorCodePair
{
ConsoleControl.ForegroundColor = AnsiNormalColorMap[ (code - 30) ];
public VtColorCode Foreground { get; }
public VtColorCode Background { get; }
public ColorCodePair( VtColorCode foreground, VtColorCode background )
{
Foreground = foreground;
Background = background;
}
}
else if( (code <= 47) && (code >= 40) )
private readonly Stack<ColorCodePair> m_colorCodeStack = new Stack<ColorCodePair>();
private VtColorCode m_activeForegroundCode = new VtColorCode( 39 );
private VtColorCode m_activeBackgroundCode = new VtColorCode( 49 );
private VtColorCode ForegroundCode
{
ConsoleControl.BackgroundColor = AnsiNormalColorMap[ (code - 40) ];
get
{
return m_activeForegroundCode;
}
set
{
m_activeForegroundCode = value;
if( VirtualTerminalSupported )
{
_WriteCodeToBuffer( value );
}
else
{
if( value.CommandCode == 0 || value.CommandCode == 39 || value.CommandCode == 38 ) //Treat RGB codes as default since we can't render them correctly
{
ConsoleControl.ForegroundColor = DefaultForeground;
}
else if( (value.CommandCode <= 37) && (value.CommandCode >= 30) )
{
ConsoleControl.ForegroundColor = AnsiNormalColorMap[ (value.CommandCode - 30) ];
}
else if( (value.CommandCode <= 97) && (value.CommandCode >= 90) )
{
ConsoleControl.ForegroundColor = AnsiBrightColorMap[ (value.CommandCode - 90) ];
}
else
{
throw new InvalidOperationException();
}
}
}
}
else if( (code <= 97) && (code >= 90) )
private VtColorCode BackgroundCode
{
ConsoleControl.ForegroundColor = AnsiBrightColorMap[ (code - 90) ];
get
{
return m_activeBackgroundCode;
}
set
{
m_activeBackgroundCode = value;
if( VirtualTerminalSupported )
{
_WriteCodeToBuffer( value );
}
else
{
if( value.CommandCode == 0 || value.CommandCode == 49 || value.CommandCode == 48 ) //Treat RGB codes as default since we can't render them correctly
{
ConsoleControl.BackgroundColor = DefaultBackground;
}
else if( (value.CommandCode <= 47) && (value.CommandCode >= 40) )
{
ConsoleControl.BackgroundColor = AnsiNormalColorMap[ (value.CommandCode - 40) ];
}
else if( (value.CommandCode <= 107) && (value.CommandCode >= 100) )
{
ConsoleControl.BackgroundColor = AnsiBrightColorMap[ (value.CommandCode - 100) ];
}
else
{
throw new InvalidOperationException();
}
}
}
}
else if( (code <= 107) && (code >= 100) )
private void _WriteCodeToBuffer( VtColorCode code )
{
ConsoleControl.BackgroundColor = AnsiBrightColorMap[ (code - 100) ];
AppendToOutputBuffer( CSI_str.AsSpan() );
AppendToOutputBuffer( sm_byteStrings[ code.CommandCode ].AsSpan() );
if( code.CommandCode == 38 || code.CommandCode == 48 )
{
AppendToOutputBuffer( ";2;".AsSpan() );
AppendToOutputBuffer( sm_byteStrings[ code.R ].AsSpan() );
AppendToOutputBuffer( ";".AsSpan() );
AppendToOutputBuffer( sm_byteStrings[ code.G ].AsSpan() );
AppendToOutputBuffer( ";".AsSpan() );
AppendToOutputBuffer( sm_byteStrings[ code.B ].AsSpan() );
}
AppendToOutputBuffer( "m".AsSpan() );
}
else if( 56 == code ) // NON-STANDARD (I made this one up)
private void _ProcessSgrCode( List< int > codes, ref int index )
{
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)
var code = codes[ index ];
index++;
if( 0 == code )
{
//using default foreground/background codes instead of full reset so that the colorcodestack can maintain either one as default independently
ForegroundCode = new VtColorCode( 39 );
BackgroundCode = new VtColorCode( 49 );
}
else if( ((code <= 37) && (code >= 30)) || ((code <= 97) && (code >= 90)) || code == 39 )
{
ForegroundCode = new VtColorCode( code );
}
else if( ((code <= 47) && (code >= 40)) || ((code <= 107) && (code >= 100)) || code == 49 )
{
BackgroundCode = new VtColorCode( code );
}
else if( code == 38 )
{
ForegroundCode = _ProcessTrueColorSgrCode( code, codes, ref index );
}
else if( code == 48 )
{
BackgroundCode = _ProcessTrueColorSgrCode( code, codes, ref index );
}
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_colorCodeStack.Push( new ColorCodePair( ForegroundCode, BackgroundCode ) );
}
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." );
var pair = m_colorCodeStack.Pop();
ForegroundCode = pair.Foreground;
BackgroundCode = pair.Background;
}
else
{
throw new NotSupportedException( String.Format( "SGR code '{0}' not supported.", code ) );
}
} // end _ProcessSgrCode()
private static VtColorCode _ProcessTrueColorSgrCode( int command, List< int > codes, ref int index )
{
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;
if( codes.Count - index < 4 ) { throw new ArgumentException( "Extended SGR sequence does not have enough arguments for RGB colors" ); }
if( codes[index] != 2 ) { throw new NotSupportedException( $"Extended SGR mode '{codes[ index + 1 ]}' not supported." ); }
index++;
return new VtColorCode( (byte)command, (byte) codes[ index++ ], (byte) codes[ index++ ], (byte) codes[ index++ ] );
}
else
{
throw new NotSupportedException( String.Format( "SGR code '{0}' not supported.", code ) );
}
} // end _ProcessSgrCode()
} // end class AnsiColorWriter
} // class ConsoleControl
//[zhent] Because the cost of .ToString() is just too much to bear.
private static readonly string[] sm_byteStrings =
{ "0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31",
"32","33","34","35","36","37","38","39","40","41","42","43","44","45","46","47","48","49","50","51","52","53","54","55","56","57","58","59","60","61","62","63",
"64","65","66","67","68","69","70","71","72","73","74","75","76","77","78","79","80","81","82","83","84","85","86","87","88","89","90","91","92","93","94","95",
"96","97","98","99","100","101","102","103","104","105","106","107","108","109","110","111","112","113","114","115","116","117","118","119","120","121","122","123","124","125","126","127",
"128","129","130","131","132","133","134","135","136","137","138","139","140","141","142","143","144","145","146","147","148","149","150","151","152","153","154","155","156","157","158","159",
"160","161","162","163","164","165","166","167","168","169","170","171","172","173","174","175","176","177","178","179","180","181","182","183","184","185","186","187","188","189","190","191",
"192","193","194","195","196","197","198","199","200","201","202","203","204","205","206","207","208","209","210","211","212","213","214","215","216","217","218","219","220","221","222","223",
"224","225","226","227","228","229","230","231","232","233","234","235","236","237","238","239","240","241","242","243","244","245","246","247","248","249","250","251","252","253","254","255"};
} // end class AnsiColorWriter
} // class ConsoleControl
} // namespace

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

@ -747,10 +747,27 @@ namespace MS.DbgShell
}
}
internal static bool CheckVirtualTerminalSupported()
{
var handle = GetActiveScreenBufferHandle();
var m = GetMode( handle );
if( NativeMethods.SetConsoleMode( handle.DangerousGetHandle(), (uint) (m | ConsoleControl.ConsoleModes.VirtualTerminal) ) )
{
// We only know if vt100 is supported if the previous call actually set the new flag, older
// systems ignore the setting.
m = GetMode( handle );
if( (m & ConsoleModes.VirtualTerminal) != 0 )
{
sm_colorWriter.VirtualTerminalSupported = true;
return true;
}
}
return false;
}
#endregion
#endregion
#region Input
#region Input
@ -2727,95 +2744,72 @@ namespace MS.DbgShell
return LucidaSupportedCodePages.Contains(currentLocaleCodePage);
}
#endregion
#endregion
/// <summary>
///
/// Wrap Win32 WriteConsole
///
/// </summary>
/// <param name="consoleHandle">
///
/// handle for the console where the string is written
///
/// </param>
/// <param name="output">
///
/// string that is written
///
/// </param>
/// <param name="newLine">
/// New line is written.
/// </param>
/// <exception cref="HostException">
///
/// if the Win32's WriteConsole fails
///
/// </exception>
internal static void WriteConsole(ConsoleHandle consoleHandle, string output)
internal static void WriteConsole( ConsoleHandle consoleHandle, ReadOnlySpan< char > output, bool newLine = false )
{
Util.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid");
Util.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed");
Util.Assert( !consoleHandle.IsInvalid, "ConsoleHandle is not valid" );
Util.Assert( !consoleHandle.IsClosed, "ConsoleHandle is closed" );
if (String.IsNullOrEmpty(output))
return;
// Native WriteConsole doesn't support output buffer longer than 64K.
// We need to chop the output string if it is too long.
int cursor = 0; // This records the chopping position in output string
const int maxBufferSize = 16383; // this is 64K/4 - 1 to account for possible width of each character.
while (cursor < output.Length)
if( output.IsEmpty )
{
string outBuffer;
if (cursor + maxBufferSize < output.Length)
if( newLine )
{
outBuffer = output.Substring(cursor, maxBufferSize);
cursor += maxBufferSize;
}
else
{
outBuffer = output.Substring(cursor);
cursor = output.Length;
}
//_RealWriteConsole( consoleHandle, outBuffer );
// [danthom] Debugging note: put a breakpoint here to catch output as it
// is going out.
// If a newline gets injected between strings where a color control
// sequence is broken across them, things blow up terribly.
if( 0 == Util.Strcmp_OI( ColorHostUserInterface.Crlf, outBuffer ) )
{
_RealWriteConsole( consoleHandle, outBuffer );
}
else
{
sm_colorWriter.Write( consoleHandle, outBuffer );
_RealWriteConsole( consoleHandle, Environment.NewLine.AsSpan() );
}
return;
}
} // end WriteConsole()
// [danthom] Debugging note: put a breakpoint here to catch output as it
// is going out.
// If a newline gets injected between strings where a color control
// sequence is broken across them, things blow up terribly.
if( ColorHostUserInterface.Crlf.AsSpan().SequenceEqual( output ) )
{
_RealWriteConsole( consoleHandle, output );
}
else
{
sm_colorWriter.Write( consoleHandle, output, newLine );
}
}// end WriteConsole()
// A problem with doing this at such an incredibly low level is that if there is any
// kind of problem with it, and then we try to WriteErrorLine... and hilarity ensues.
// TODO: a possible mitigation is if we detect an invalid control code or somesuch,
// reset the colors and parse state.
private static AnsiColorWriter sm_colorWriter = new AnsiColorWriter();
private static readonly AnsiColorWriter sm_colorWriter = new AnsiColorWriter();
private static void _RealWriteConsole( ConsoleHandle handle, string s )
private static void _RealWriteConsole( ConsoleHandle handle, ReadOnlySpan< char > s )
{
DWORD charsWritten;
bool itWorked = NativeMethods.WriteConsole( handle.DangerousGetHandle(),
s,
bool itWorked = NativeMethods.WriteConsole( handle,
in MemoryMarshal.GetReference( s ),
(DWORD) s.Length,
out charsWritten,
out uint _,
IntPtr.Zero );
if( !itWorked )
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "WriteConsole",
HostException e = CreateHostException( err, "WriteConsole",
ErrorCategory.WriteError, "The Win32 internal error \"{0}\" 0x{1:X} occurred while writing to the console output buffer at the current cursor position. Contact Microsoft Customer Support Services." ); //ConsoleControlStrings.WriteConsoleExceptionTemplate);
throw e;
}
@ -3496,11 +3490,13 @@ namespace MS.DbgShell
out DWORD numberOfCharsWritten
);
[DllImport("KERNEL32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
// [zhent] unlike PowerShell Core, we use a ref char instead of char*. This doesn't require compiling with /unsafe,
// allowing us pretend we are doing something more safe (we aren't).
[DllImport( "KERNEL32.dll", EntryPoint = "WriteConsole", SetLastError = true, CharSet = CharSet.Unicode )]
internal static extern bool WriteConsole
(
NakedWin32Handle consoleOutput,
string buffer,
SafeHandle consoleOutput,
in char buffer,
DWORD numberOfCharsToWrite,
out DWORD numberOfCharsWritten,
IntPtr reserved

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

@ -66,7 +66,7 @@ namespace MS.DbgShell
void
WriteLine(string value)
{
this.Write(value + ColorHostUserInterface.Crlf);
_ui.WriteToConsole( value.AsSpan(), true, newLine: true );
}
@ -84,7 +84,7 @@ namespace MS.DbgShell
void
Write(Char c)
{
this.Write(new String(c, 1));
_ui.WriteToConsole( c, true );
}
@ -93,7 +93,7 @@ namespace MS.DbgShell
void
Write(Char[] a)
{
this.Write(new String(a));
_ui.WriteToConsole( a.AsSpan(), true );
}

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

@ -46,6 +46,7 @@
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>false</Prefer32Bit>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>..\bin\Release\x64\</OutputPath>
@ -57,6 +58,7 @@
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>false</Prefer32Bit>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<!--
<PropertyGroup>
@ -69,12 +71,21 @@
<HintPath>..\..\..\..\..\..\..\Program Files\Reference Assemblies\Microsoft\WindowsPowerShell\3.0\Microsoft.PowerShell.ConsoleHost.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.5.0\lib\netstandard1.1\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.Core" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\..\Program Files\Reference Assemblies\Microsoft\WindowsPowerShell\3.0\System.Management.Automation.dll</HintPath>
</Reference>
<Reference Include="System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.5.3\lib\netstandard1.1\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="ColorConsoleHost.cs" />

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

@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="AddGitVersionInfo" version="1.0.0.1" targetFramework="net45" developmentDependency="true" />
<package id="System.Buffers" version="4.5.0" targetFramework="net46" />
<package id="System.Memory" version="4.5.3" targetFramework="net46" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.2" targetFramework="net46" />
</packages>