зеркало из https://github.com/microsoft/DbgShell.git
Merge pull request #81 from Zhentar/rgb
[WIP] Add DbgMemory RGB output format
This commit is contained in:
Коммит
9175d49e05
|
@ -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>
|
||||
|
|
|
@ -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>
|
Загрузка…
Ссылка в новой задаче