Finish refactoring so unit tests run in VS2015

The mock of the console api is complete enough so unit tests run.

It's still a hack, not exactly factored nicely to support other hosts,
but at least some of the real interface is in one place, not scattered
about.
This commit is contained in:
jason@truewheels.net
2015-12-16 22:56:25 -08:00
parent 2ef73a9f58
commit accc20dfe9
4 changed files with 269 additions and 238 deletions
+224
View File
@@ -409,6 +409,38 @@ namespace Microsoft.PowerShell
internal class ConhostConsole : IConsole
{
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
private IntPtr _hwnd = (IntPtr)0;
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
private IntPtr _hDC = (IntPtr)0;
private uint _codePage;
private bool _istmInitialized = false;
private TEXTMETRIC _tm = new TEXTMETRIC();
private bool _trueTypeInUse = false;
private readonly Lazy<SafeFileHandle> _outputHandle = new Lazy<SafeFileHandle>(() =>
{
// We use CreateFile here instead of GetStdWin32Handle, as GetStdWin32Handle will return redirected handles
var handle = NativeMethods.CreateFile(
"CONOUT$",
(UInt32)(AccessQualifiers.GenericRead | AccessQualifiers.GenericWrite),
(UInt32)ShareModes.ShareWrite,
(IntPtr)0,
(UInt32)CreationDisposition.OpenExisting,
0,
(IntPtr)0);
if (handle == NativeMethods.INVALID_HANDLE_VALUE)
{
int err = Marshal.GetLastWin32Error();
Win32Exception innerException = new Win32Exception(err);
throw new Exception("Failed to retreive the input console handle.", innerException);
}
return new SafeFileHandle(handle, true);
}
);
private readonly Lazy<SafeFileHandle> _inputHandle = new Lazy<SafeFileHandle>(() =>
{
// We use CreateFile here instead of GetStdWin32Handle, as GetStdWin32Handle will return redirected handles
@@ -613,5 +645,197 @@ namespace Microsoft.PowerShell
readBufferSize, readBufferCoord, ref readRegion);
return result;
}
[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods",
Justification = "Then the API we pass the handle to will return an error if it is invalid. They are not exposed.")]
internal static CONSOLE_FONT_INFO_EX GetConsoleFontInfo(SafeFileHandle consoleHandle)
{
CONSOLE_FONT_INFO_EX fontInfo = new CONSOLE_FONT_INFO_EX();
fontInfo.cbSize = Marshal.SizeOf(fontInfo);
bool result = NativeMethods.GetCurrentConsoleFontEx(consoleHandle.DangerousGetHandle(), false, ref fontInfo);
if (result == false)
{
int err = Marshal.GetLastWin32Error();
Win32Exception innerException = new Win32Exception(err);
throw new Exception("Failed to get console font information.", innerException);
}
return fontInfo;
}
public int LengthInBufferCells(char c)
{
if (!IsCJKOutputCodePage() || !_trueTypeInUse)
return 1;
return LengthInBufferCellsFE(c);
}
internal static bool IsAnyDBCSCharSet(uint charSet)
{
const uint SHIFTJIS_CHARSET = 128;
const uint HANGEUL_CHARSET = 129;
const uint CHINESEBIG5_CHARSET = 136;
const uint GB2312_CHARSET = 134;
return charSet == SHIFTJIS_CHARSET || charSet == HANGEUL_CHARSET ||
charSet == CHINESEBIG5_CHARSET || charSet == GB2312_CHARSET;
}
internal uint CodePageToCharSet()
{
CHARSETINFO csi;
const uint TCI_SRCCODEPAGE = 2;
const uint OEM_CHARSET = 255;
if (!NativeMethods.TranslateCharsetInfo((IntPtr)_codePage, out csi, TCI_SRCCODEPAGE))
{
csi.ciCharset = OEM_CHARSET;
}
return csi.ciCharset;
}
/// <summary>
/// Check if the output buffer code page is Japanese, Simplified Chinese, Korean, or Traditional Chinese
/// </summary>
/// <returns>true if it is CJK code page; otherwise, false.</returns>
internal bool IsCJKOutputCodePage()
{
return _codePage == 932 || // Japanese
_codePage == 936 || // Simplified Chinese
_codePage == 949 || // Korean
_codePage == 950; // Traditional Chinese
}
internal bool IsAvailableFarEastCodePage()
{
uint charSet = CodePageToCharSet();
return IsAnyDBCSCharSet(charSet);
}
internal int LengthInBufferCellsFE(char c)
{
if (0x20 <= c && c <= 0x7e)
{
/* ASCII */
return 1;
}
else if (0x3041 <= c && c <= 0x3094)
{
/* Hiragana */
return 2;
}
else if (0x30a1 <= c && c <= 0x30f6)
{
/* Katakana */
return 2;
}
else if (0x3105 <= c && c <= 0x312c)
{
/* Bopomofo */
return 2;
}
else if (0x3131 <= c && c <= 0x318e)
{
/* Hangul Elements */
return 2;
}
else if (0xac00 <= c && c <= 0xd7a3)
{
/* Korean Hangul Syllables */
return 2;
}
else if (0xff01 <= c && c <= 0xff5e)
{
/* Fullwidth ASCII variants */
return 2;
}
else if (0xff61 <= c && c <= 0xff9f)
{
/* Halfwidth Katakana variants */
return 1;
}
else if ((0xffa0 <= c && c <= 0xffbe) ||
(0xffc2 <= c && c <= 0xffc7) ||
(0xffca <= c && c <= 0xffcf) ||
(0xffd2 <= c && c <= 0xffd7) ||
(0xffda <= c && c <= 0xffdc))
{
/* Halfwidth Hangule variants */
return 1;
}
else if (0xffe0 <= c && c <= 0xffe6)
{
/* Fullwidth symbol variants */
return 2;
}
else if (0x4e00 <= c && c <= 0x9fa5)
{
/* Han Ideographic */
return 2;
}
else if (0xf900 <= c && c <= 0xfa2d)
{
/* Han Compatibility Ideographs */
return 2;
}
else
{
/* Unknown character: need to use GDI*/
if (_hDC == (IntPtr)0)
{
_hwnd = NativeMethods.GetConsoleWindow();
if ((IntPtr)0 == _hwnd)
{
return 1;
}
_hDC = NativeMethods.GetDC(_hwnd);
if ((IntPtr)0 == _hDC)
{
//Don't throw exception so that output can continue
return 1;
}
}
bool result = true;
if (!_istmInitialized)
{
result = NativeMethods.GetTextMetrics(_hDC, out _tm);
if (!result)
{
return 1;
}
_istmInitialized = true;
}
int width;
result = NativeMethods.GetCharWidth32(_hDC, (uint)c, (uint)c, out width);
if (!result)
{
return 1;
}
if (width >= _tm.tmMaxCharWidth)
{
return 2;
}
}
return 1;
}
public void StartRender()
{
_codePage = NativeMethods.GetConsoleOutputCP();
_istmInitialized = false;
var consoleHandle = _outputHandle.Value;
CONSOLE_FONT_INFO_EX fontInfo = ConhostConsole.GetConsoleFontInfo(consoleHandle);
int fontType = fontInfo.FontFamily & NativeMethods.FontTypeMask;
_trueTypeInUse = (fontType & NativeMethods.TrueTypeFont) == NativeMethods.TrueTypeFont;
}
public void EndRender()
{
if (_hwnd != (IntPtr)0 && _hDC != (IntPtr)0)
{
NativeMethods.ReleaseDC(_hwnd, _hDC);
}
}
}
}
+4
View File
@@ -50,6 +50,10 @@ namespace Microsoft.PowerShell
void WriteBufferLines(CHAR_INFO[] buffer, ref int top, bool ensureBottomLineVisible);
void ScrollBuffer(int lines);
CHAR_INFO[] ReadBufferLines(int top, int count);
void StartRender();
int LengthInBufferCells(char c);
void EndRender();
}
}
+28 -238
View File
@@ -4,13 +4,10 @@ Copyright (c) Microsoft Corporation. All rights reserved.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Management.Automation.Language;
using System.Runtime.InteropServices;
using Microsoft.PowerShell.Internal;
using ConsoleHandle = Microsoft.Win32.SafeHandles.SafeFileHandle;
namespace Microsoft.PowerShell
{
@@ -26,37 +23,6 @@ namespace Microsoft.PowerShell
private int _current;
private int _emphasisStart;
private int _emphasisLength;
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
private IntPtr _hwnd = (IntPtr)0;
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
private IntPtr _hDC = (IntPtr)0;
private uint _codePage;
private bool _istmInitialized = false;
private TEXTMETRIC _tm = new TEXTMETRIC();
private bool _trueTypeInUse = false;
private readonly Lazy<ConsoleHandle> _outputHandle = new Lazy<ConsoleHandle>(() =>
{
// We use CreateFile here instead of GetStdWin32Handle, as GetStdWin32Handle will return redirected handles
var handle = NativeMethods.CreateFile(
"CONOUT$",
(UInt32)(AccessQualifiers.GenericRead | AccessQualifiers.GenericWrite),
(UInt32)ShareModes.ShareWrite,
(IntPtr)0,
(UInt32)CreationDisposition.OpenExisting,
0,
(IntPtr)0);
if (handle == NativeMethods.INVALID_HANDLE_VALUE)
{
int err = Marshal.GetLastWin32Error();
Win32Exception innerException = new Win32Exception(err);
throw new Exception("Failed to retreive the input console handle.", innerException);
}
return new ConsoleHandle(handle, true);
}
);
private class SavedTokenState
{
@@ -112,12 +78,6 @@ namespace Microsoft.PowerShell
private void ReallyRender()
{
var text = ParseInput();
_codePage = NativeMethods.GetConsoleOutputCP();
_istmInitialized = false;
ConsoleHandle consoleHandle = _outputHandle.Value;
CONSOLE_FONT_INFO_EX fontInfo = GetConsoleFontInfo(consoleHandle);
int fontType = fontInfo.FontFamily & NativeMethods.FontTypeMask;
_trueTypeInUse = (fontType & NativeMethods.TrueTypeFont) == NativeMethods.TrueTypeFont;
int statusLineCount = GetStatusLineCount();
int j = _initialX + (_bufferWidth * Options.ExtraPromptLineCount);
@@ -140,6 +100,8 @@ namespace Microsoft.PowerShell
try
{
_console.StartRender();
bufferLineCount = ConvertOffsetToCoordinates(text.Length).Y - _initialY + 1 + statusLineCount;
if (_consoleBuffer.Length != bufferLineCount * bufferWidth)
{
@@ -160,13 +122,12 @@ namespace Microsoft.PowerShell
for (int i = 0; i < text.Length; i++)
{
SavedTokenState state = null;
totalBytes = totalBytes % bufferWidth;
if (!afterLastToken)
{
// Figure out the color of the character - if it's in a token,
// use the tokens color otherwise use the initial color.
state = tokenStack.Peek();
var state = tokenStack.Peek();
var token = state.Tokens[state.Index];
if (i == token.Extent.EndOffset)
{
@@ -228,7 +189,8 @@ namespace Microsoft.PowerShell
}
}
if (text[i] == '\n')
var charToRender = text[i];
if (charToRender == '\n')
{
while ((j % bufferWidth) != 0)
{
@@ -244,7 +206,7 @@ namespace Microsoft.PowerShell
}
else
{
int size = LengthInBufferCells(text[i]);
int size = LengthInBufferCells(charToRender);
totalBytes += size;
//if there is no enough space for the character at the edge, fill in spaces at the end and
@@ -256,28 +218,28 @@ namespace Microsoft.PowerShell
totalBytes++;
}
if (char.IsControl(text[i]))
if (char.IsControl(charToRender))
{
_consoleBuffer[j].UnicodeChar = '^';
MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor);
_consoleBuffer[j].UnicodeChar = (char)('@' + text[i]);
_consoleBuffer[j].UnicodeChar = (char)('@' + charToRender);
MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor);
}
else if (size > 1 && IsCJKOutputCodePage() && _trueTypeInUse)
else if (size > 1)
{
_consoleBuffer[j].UnicodeChar = text[i];
_consoleBuffer[j].Attributes = (ushort)((uint)_consoleBuffer[j].Attributes |
_consoleBuffer[j].UnicodeChar = charToRender;
_consoleBuffer[j].Attributes = (ushort)(_consoleBuffer[j].Attributes |
(uint)CHAR_INFO_Attributes.COMMON_LVB_LEADING_BYTE);
MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor);
_consoleBuffer[j].UnicodeChar = text[i];
_consoleBuffer[j].Attributes = (ushort)((uint)_consoleBuffer[j].Attributes |
_consoleBuffer[j].UnicodeChar = charToRender;
_consoleBuffer[j].Attributes = (ushort)(_consoleBuffer[j].Attributes |
(uint)CHAR_INFO_Attributes.COMMON_LVB_TRAILING_BYTE);
MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor);
}
else
{
_consoleBuffer[j].UnicodeChar = text[i];
_consoleBuffer[j].UnicodeChar = charToRender;
MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor);
}
}
@@ -285,10 +247,7 @@ namespace Microsoft.PowerShell
}
finally
{
if (_hwnd != (IntPtr)0 && _hDC != (IntPtr)0)
{
NativeMethods.ReleaseDC(_hwnd, _hDC);
}
_console.EndRender();
}
for (; j < (_consoleBuffer.Length - (statusLineCount * _bufferWidth)); j++)
@@ -358,6 +317,16 @@ namespace Microsoft.PowerShell
_lastRenderTime.Restart();
}
private int LengthInBufferCells(char c)
{
int length = Char.IsControl(c) ? 1 : 0;
if (c < 256)
{
return length + 1;
}
return _console.LengthInBufferCells(c);
}
private static void WriteBlankLines(int count, int top)
{
var console = _singleton._console;
@@ -479,24 +448,6 @@ namespace Microsoft.PowerShell
return i >= start && i < end;
}
[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods",
Justification = "Then the API we pass the handle to will return an error if it is invalid. They are not exposed.")]
private static CONSOLE_FONT_INFO_EX GetConsoleFontInfo(ConsoleHandle consoleHandle)
{
CONSOLE_FONT_INFO_EX fontInfo = new CONSOLE_FONT_INFO_EX();
fontInfo.cbSize = Marshal.SizeOf(fontInfo);
bool result = NativeMethods.GetCurrentConsoleFontEx(consoleHandle.DangerousGetHandle(), false, ref fontInfo);
if (result == false)
{
int err = Marshal.GetLastWin32Error();
Win32Exception innerException = new Win32Exception(err);
throw new Exception("Failed to get console font information.", innerException);
}
return fontInfo;
}
private void MaybeEmphasize(ref CHAR_INFO charInfo, int i, ConsoleColor foregroundColor, ConsoleColor backgroundColor)
{
if (i >= _emphasisStart && i < (_emphasisStart + _emphasisLength))
@@ -537,163 +488,6 @@ namespace Microsoft.PowerShell
PlaceCursor(coordinates.X, ref y);
}
private int LengthInBufferCells(char c)
{
int length = char.IsControl(c) ? 1 : 0;
if (c < 256 || !IsAvailableFarEastCodePage())
{
return length + 1;
}
return length + LengthInBufferCellsFE(c);
}
private bool IsAnyDBCSCharSet(uint charSet)
{
const uint SHIFTJIS_CHARSET = 128;
const uint HANGEUL_CHARSET = 129;
const uint CHINESEBIG5_CHARSET = 136;
const uint GB2312_CHARSET = 134;
return charSet == SHIFTJIS_CHARSET || charSet == HANGEUL_CHARSET ||
charSet == CHINESEBIG5_CHARSET || charSet == GB2312_CHARSET;
}
private uint CodePageToCharSet()
{
CHARSETINFO csi;
const uint TCI_SRCCODEPAGE = 2;
const uint OEM_CHARSET = 255;
if (!NativeMethods.TranslateCharsetInfo((IntPtr)_codePage, out csi, TCI_SRCCODEPAGE))
{
csi.ciCharset = OEM_CHARSET;
}
return csi.ciCharset;
}
/// <summary>
/// Check if the output buffer code page is Japanese, Simplified Chinese, Korean, or Traditional Chinese
/// </summary>
/// <returns>true if it is CJK code page; otherwise, false.</returns>
private bool IsCJKOutputCodePage()
{
return _codePage == 932 || // Japanese
_codePage == 936 || // Simplified Chinese
_codePage == 949 || // Korean
_codePage == 950; // Traditional Chinese
}
private bool IsAvailableFarEastCodePage()
{
uint charSet = CodePageToCharSet();
return IsAnyDBCSCharSet(charSet);
}
private int LengthInBufferCellsFE(char c)
{
if (0x20 <= c && c <= 0x7e)
{
/* ASCII */
return 1;
}
else if (0x3041 <= c && c <= 0x3094)
{
/* Hiragana */
return 2;
}
else if (0x30a1 <= c && c <= 0x30f6)
{
/* Katakana */
return 2;
}
else if (0x3105 <= c && c <= 0x312c)
{
/* Bopomofo */
return 2;
}
else if (0x3131 <= c && c <= 0x318e)
{
/* Hangul Elements */
return 2;
}
else if (0xac00 <= c && c <= 0xd7a3)
{
/* Korean Hangul Syllables */
return 2;
}
else if (0xff01 <= c && c <= 0xff5e)
{
/* Fullwidth ASCII variants */
return 2;
}
else if (0xff61 <= c && c <= 0xff9f)
{
/* Halfwidth Katakana variants */
return 1;
}
else if ((0xffa0 <= c && c <= 0xffbe) ||
(0xffc2 <= c && c <= 0xffc7) ||
(0xffca <= c && c <= 0xffcf) ||
(0xffd2 <= c && c <= 0xffd7) ||
(0xffda <= c && c <= 0xffdc))
{
/* Halfwidth Hangule variants */
return 1;
}
else if (0xffe0 <= c && c <= 0xffe6)
{
/* Fullwidth symbol variants */
return 2;
}
else if (0x4e00 <= c && c <= 0x9fa5)
{
/* Han Ideographic */
return 2;
}
else if (0xf900 <= c && c <= 0xfa2d)
{
/* Han Compatibility Ideographs */
return 2;
}
else
{
/* Unknown character: need to use GDI*/
if (_hDC == (IntPtr)0)
{
_hwnd = NativeMethods.GetConsoleWindow();
if ((IntPtr)0 == _hwnd)
{
return 1;
}
_hDC = NativeMethods.GetDC(_hwnd);
if ((IntPtr)0 == _hDC)
{
//Don't throw exception so that output can continue
return 1;
}
}
bool result = true;
if (!_istmInitialized)
{
result = NativeMethods.GetTextMetrics(_hDC, out _tm);
if (!result)
{
return 1;
}
_istmInitialized = true;
}
int width;
result = NativeMethods.GetCharWidth32(_hDC, (uint)c, (uint)c, out width);
if (!result)
{
return 1;
}
if (width >= _tm.tmMaxCharWidth)
{
return 2;
}
}
return 1;
}
private COORD ConvertOffsetToCoordinates(int offset)
{
int x = _initialX;
@@ -752,19 +546,15 @@ namespace Microsoft.PowerShell
int j = startIndex;
for (int i = 0; i < offset; i++)
{
char c = _buffer[i];
if (_buffer[i] == '\n')
var c = _buffer[i];
if (c == '\n')
{
for (int k = 0; k < Options.ContinuationPrompt.Length; k++)
{
j++;
}
}
else if (char.IsControl(_buffer[i]))
{
j += 2;
}
else if (LengthInBufferCells(_buffer[i]) > 1 && IsCJKOutputCodePage() && _trueTypeInUse)
else if (LengthInBufferCells(c) > 1)
{
j += 2;
}
+13
View File
@@ -237,6 +237,19 @@ namespace UnitTestPSReadLine
Array.Copy(buffer, top * BufferWidth, result, 0, toCopy);
return result;
}
public void StartRender()
{
}
public int LengthInBufferCells(char c)
{
return 1;
}
public void EndRender()
{
}
}
[TestClass]