Files

1120 lines
36 KiB
C#

/********************************************************************++
Copyright (c) Microsoft Corporation. All rights reserved.
--********************************************************************/
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;
namespace Microsoft.PowerShell.Internal
{
#pragma warning disable 1591
internal static class NativeMethods
{
public const uint MAPVK_VK_TO_VSC = 0x00;
public const uint MAPVK_VSC_TO_VK = 0x01;
public const uint MAPVK_VK_TO_CHAR = 0x02;
public const byte VK_SHIFT = 0x10;
public const byte VK_CONTROL = 0x11;
public const byte VK_ALT = 0x12;
public const uint MENU_IS_ACTIVE = 0x01;
public const uint MENU_IS_INACTIVE = 0x00; // windows key
public const uint ENABLE_PROCESSED_INPUT = 0x0001;
public const uint ENABLE_LINE_INPUT = 0x0002;
public const uint ENABLE_WINDOW_INPUT = 0x0008;
public const uint ENABLE_MOUSE_INPUT = 0x0010;
public const int FontTypeMask = 0x06;
public const int TrueTypeFont = 0x04;
internal static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); // WinBase.h
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr GetStdHandle(uint handleId);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool GetConsoleMode(IntPtr hConsoleOutput, out uint dwMode);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool SetConsoleMode(IntPtr hConsoleOutput, uint dwMode);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool ScrollConsoleScreenBuffer(IntPtr hConsoleOutput,
ref SMALL_RECT lpScrollRectangle,
IntPtr lpClipRectangle,
COORD dwDestinationOrigin,
ref CHAR_INFO lpFill);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool WriteConsole(IntPtr hConsoleOutput, string lpBuffer, uint nNumberOfCharsToWrite, out uint lpNumberOfCharsWritten, IntPtr lpReserved);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool SetConsoleCtrlHandler(BreakHandler handlerRoutine, bool add);
[DllImport("KERNEL32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool WriteConsoleOutput(IntPtr consoleOutput, CHAR_INFO[] buffer, COORD bufferSize, COORD bufferCoord, ref SMALL_RECT writeRegion);
[DllImport("KERNEL32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool ReadConsoleOutput(IntPtr consoleOutput, [Out] CHAR_INFO[] buffer, COORD bufferSize, COORD bufferCoord, ref SMALL_RECT readRegion);
[DllImport("user32.dll", SetLastError = true)]
public static extern uint MapVirtualKey(uint uCode, uint uMapType);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int ToUnicode(uint uVirtKey, uint uScanCode, byte[] lpKeyState,
[MarshalAs(UnmanagedType.LPArray)] [Out] char[] chars, int charMaxCount, uint flags);
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern short VkKeyScan(char @char);
[DllImport("kernel32.dll", SetLastError = false, CharSet = CharSet.Unicode)]
public static extern uint GetConsoleOutputCP();
[DllImport("User32.dll", SetLastError = false, CharSet = CharSet.Unicode)]
public static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern IntPtr GetConsoleWindow();
[DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern IntPtr GetDC(IntPtr hwnd);
[DllImport("GDI32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool TranslateCharsetInfo(IntPtr src, out CHARSETINFO Cs, uint options);
[DllImport("GDI32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool GetTextMetrics(IntPtr hdc, out TEXTMETRIC tm);
[DllImport("GDI32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool GetCharWidth32(IntPtr hdc, uint first, uint last, out int width);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern IntPtr CreateFile
(
string fileName,
uint desiredAccess,
uint ShareModes,
IntPtr securityAttributes,
uint creationDisposition,
uint flagsAndAttributes,
IntPtr templateFileWin32Handle
);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool GetCurrentConsoleFontEx(IntPtr consoleOutput, bool bMaximumWindow, ref CONSOLE_FONT_INFO_EX consoleFontInfo);
[StructLayout(LayoutKind.Sequential)]
internal struct COLORREF
{
internal uint ColorDWORD;
internal uint R
{
get { return ColorDWORD & 0xff; }
}
internal uint G
{
get { return (ColorDWORD >> 8) & 0xff; }
}
internal uint B
{
get { return (ColorDWORD >> 16) & 0xff; }
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct CONSOLE_SCREEN_BUFFER_INFO_EX
{
internal int cbSize;
internal COORD dwSize;
internal COORD dwCursorPosition;
internal ushort wAttributes;
internal SMALL_RECT srWindow;
internal COORD dwMaximumWindowSize;
internal ushort wPopupAttributes;
internal bool bFullscreenSupported;
internal COLORREF Black;
internal COLORREF DarkBlue;
internal COLORREF DarkGreen;
internal COLORREF DarkCyan;
internal COLORREF DarkRed;
internal COLORREF DarkMagenta;
internal COLORREF DarkYellow;
internal COLORREF Gray;
internal COLORREF DarkGray;
internal COLORREF Blue;
internal COLORREF Green;
internal COLORREF Cyan;
internal COLORREF Red;
internal COLORREF Magenta;
internal COLORREF Yellow;
internal COLORREF White;
}
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool GetConsoleScreenBufferInfoEx(IntPtr hConsoleOutput,
ref CONSOLE_SCREEN_BUFFER_INFO_EX csbe);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool SetConsoleScreenBufferInfoEx(IntPtr hConsoleOutput,
ref CONSOLE_SCREEN_BUFFER_INFO_EX csbe);
}
public delegate bool BreakHandler(ConsoleBreakSignal ConsoleBreakSignal);
public enum ConsoleBreakSignal : uint
{
CtrlC = 0,
CtrlBreak = 1,
Close = 2,
Logoff = 5,
Shutdown = 6,
None = 255,
}
internal enum CHAR_INFO_Attributes : ushort
{
COMMON_LVB_LEADING_BYTE = 0x0100,
COMMON_LVB_TRAILING_BYTE = 0x0200
}
public enum StandardHandleId : uint
{
Error = unchecked((uint)-12),
Output = unchecked((uint)-11),
Input = unchecked((uint)-10),
}
[Flags]
internal enum AccessQualifiers : uint
{
// From winnt.h
GenericRead = 0x80000000,
GenericWrite = 0x40000000
}
internal enum CreationDisposition : uint
{
// From winbase.h
CreateNew = 1,
CreateAlways = 2,
OpenExisting = 3,
OpenAlways = 4,
TruncateExisting = 5
}
[Flags]
internal enum ShareModes : uint
{
// From winnt.h
ShareRead = 0x00000001,
ShareWrite = 0x00000002
}
public struct SMALL_RECT
{
public short Left;
public short Top;
public short Right;
public short Bottom;
[ExcludeFromCodeCoverage]
public override string ToString()
{
return String.Format(CultureInfo.InvariantCulture, "{0},{1},{2},{3}", Left, Top, Right, Bottom);
}
}
internal struct COORD
{
public short X;
public short Y;
[ExcludeFromCodeCoverage]
public override string ToString()
{
return String.Format(CultureInfo.InvariantCulture, "{0},{1}", X, Y);
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct FONTSIGNATURE
{
//From public\sdk\inc\wingdi.h
// fsUsb*: A 128-bit Unicode subset bitfield (USB) identifying up to 126 Unicode subranges
internal uint fsUsb0;
internal uint fsUsb1;
internal uint fsUsb2;
internal uint fsUsb3;
// fsCsb*: A 64-bit, code-page bitfield (CPB) that identifies a specific character set or code page.
internal uint fsCsb0;
internal uint fsCsb1;
}
[StructLayout(LayoutKind.Sequential)]
internal struct CHARSETINFO
{
//From public\sdk\inc\wingdi.h
internal uint ciCharset; // Character set value.
internal uint ciACP; // ANSI code-page identifier.
internal FONTSIGNATURE fs;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct TEXTMETRIC
{
//From public\sdk\inc\wingdi.h
public int tmHeight;
public int tmAscent;
public int tmDescent;
public int tmInternalLeading;
public int tmExternalLeading;
public int tmAveCharWidth;
public int tmMaxCharWidth;
public int tmWeight;
public int tmOverhang;
public int tmDigitizedAspectX;
public int tmDigitizedAspectY;
public char tmFirstChar;
public char tmLastChar;
public char tmDefaultChar;
public char tmBreakChar;
public byte tmItalic;
public byte tmUnderlined;
public byte tmStruckOut;
public byte tmPitchAndFamily;
public byte tmCharSet;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct CONSOLE_FONT_INFO_EX
{
internal int cbSize;
internal int nFont;
internal short FontWidth;
internal short FontHeight;
internal int FontFamily;
internal int FontWeight;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
internal string FontFace;
}
public struct BufferChar
{
public char UnicodeChar;
public ConsoleColor ForegroundColor;
public ConsoleColor BackgroundColor;
#if !UNIX
public bool IsLeadByte;
public bool IsTrailByte;
#endif
public bool Inverse;
#if !UNIX
public CHAR_INFO ToCharInfo()
{
int fg = (int) ForegroundColor;
int bg = (int) BackgroundColor;
if (fg < 0 || fg > 0xf || bg < 0 || bg > 0xf)
throw new InvalidOperationException();
if (Inverse)
{
// TODO: check $host.UI.SupportsVirtualTerminal and maybe set Inverse instead (it does look weird)
fg = fg ^ 7;
bg = bg ^ 7;
}
ushort attrs = (ushort)(fg | (bg << 4));
if (IsLeadByte)
attrs |= (ushort)CHAR_INFO_Attributes.COMMON_LVB_LEADING_BYTE;
if (IsTrailByte)
attrs |= (ushort)CHAR_INFO_Attributes.COMMON_LVB_TRAILING_BYTE;
CHAR_INFO result = new CHAR_INFO
{
UnicodeChar = UnicodeChar,
Attributes = attrs,
};
return result;
}
public static BufferChar FromCharInfo(CHAR_INFO charInfo)
{
BufferChar result = new BufferChar
{
UnicodeChar = (char) charInfo.UnicodeChar,
ForegroundColor = (ConsoleColor)(charInfo.Attributes & 0xf),
BackgroundColor = (ConsoleColor)((charInfo.Attributes & 0x00f0) >> 4),
};
return result;
}
#endif
}
public struct CHAR_INFO
{
public ushort UnicodeChar;
public ushort Attributes;
}
internal static class ConsoleKeyInfoExtension
{
public static string ToGestureString(this ConsoleKeyInfo key)
{
var mods = key.Modifiers;
var sb = new StringBuilder();
if (key.Modifiers.HasFlag(ConsoleModifiers.Control))
{
sb.Append("Ctrl");
}
if (key.Modifiers.HasFlag(ConsoleModifiers.Alt))
{
if (sb.Length > 0)
sb.Append("+");
sb.Append("Alt");
}
#if UNIX
if (sb.Length > 0)
sb.Append("+");
// TODO: find better way to map these characters to something more friendly
if ((key.Key >= ConsoleKey.D0 && key.Key <= ConsoleKey.D9)
|| (key.Key >= ConsoleKey.Oem1 && key.Key <= ConsoleKey.Oem8))
{
sb.Append(key.KeyChar);
}
else
{
if (key.Modifiers.HasFlag(ConsoleModifiers.Shift))
{
sb.Append("Shift+");
}
sb.Append(key.Key);
}
#else
char c = ConsoleKeyChordConverter.GetCharFromConsoleKey(key.Key,
(mods & ConsoleModifiers.Shift) != 0 ? ConsoleModifiers.Shift : 0);
if (char.IsControl(c) || char.IsWhiteSpace(c))
{
if (key.Modifiers.HasFlag(ConsoleModifiers.Shift))
{
if (sb.Length > 0)
sb.Append("+");
sb.Append("Shift");
}
if (sb.Length > 0)
sb.Append("+");
sb.Append(key.Key);
}
else
{
if (sb.Length > 0)
sb.Append("+");
sb.Append(c);
}
#endif
return sb.ToString();
}
}
#if !UNIX
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
var handle = NativeMethods.CreateFile(
"CONIN$",
(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);
});
public object GetConsoleInputMode()
{
uint mode;
var handle = _inputHandle.Value.DangerousGetHandle();
NativeMethods.GetConsoleMode(handle, out mode);
return mode;
}
public void SetConsoleInputMode(object modeObj)
{
if (modeObj is uint)
{
// Clear a couple flags so we can actually receive certain keys:
// ENABLE_PROCESSED_INPUT - enables Ctrl+C
// ENABLE_LINE_INPUT - enables Ctrl+S
// Also clear a couple flags so we don't mask the input that we ignore:
// ENABLE_MOUSE_INPUT - mouse events
// ENABLE_WINDOW_INPUT - window resize events
var mode = (uint)modeObj &
~(NativeMethods.ENABLE_PROCESSED_INPUT |
NativeMethods.ENABLE_LINE_INPUT |
NativeMethods.ENABLE_WINDOW_INPUT |
NativeMethods.ENABLE_MOUSE_INPUT);
var handle = _inputHandle.Value.DangerousGetHandle();
NativeMethods.SetConsoleMode(handle, mode);
}
}
public void RestoreConsoleInputMode(object modeObj)
{
if (modeObj is uint)
{
var handle = _inputHandle.Value.DangerousGetHandle();
NativeMethods.SetConsoleMode(handle, (uint)modeObj);
}
}
public ConsoleKeyInfo ReadKey()
{
return Console.ReadKey(true);
}
public bool KeyAvailable
{
get { return Console.KeyAvailable; }
}
public int CursorLeft
{
get { return Console.CursorLeft; }
set { Console.CursorLeft = value; }
}
public int CursorTop
{
get { return Console.CursorTop; }
set { Console.CursorTop = value; }
}
public int CursorSize
{
get { return Console.CursorSize; }
set { Console.CursorSize = value; }
}
public int BufferWidth
{
get { return Console.BufferWidth; }
set { Console.BufferWidth = value; }
}
public int BufferHeight
{
get { return Console.BufferHeight; }
set { Console.BufferHeight = value; }
}
public int WindowWidth
{
get { return Console.WindowWidth; }
set { Console.WindowWidth = value; }
}
public int WindowHeight
{
get { return Console.WindowHeight; }
set { Console.WindowHeight = value; }
}
public int WindowTop
{
get { return Console.WindowTop; }
set { Console.WindowTop = value; }
}
public ConsoleColor BackgroundColor
{
get { return Console.BackgroundColor; }
set { Console.BackgroundColor = value; }
}
public ConsoleColor ForegroundColor
{
get { return Console.ForegroundColor; }
set { Console.ForegroundColor = value; }
}
public void SetWindowPosition(int left, int top)
{
Console.SetWindowPosition(left, top);
}
public void SetCursorPosition(int left, int top)
{
Console.SetCursorPosition(left, top);
}
public void Write(string value)
{
Console.Write(value);
}
public void WriteLine(string value)
{
Console.WriteLine(value);
}
public void WriteBufferLines(BufferChar[] buffer, ref int top)
{
WriteBufferLines(buffer, ref top, true);
}
public void WriteBufferLines(BufferChar[] buffer, ref int top, bool ensureBottomLineVisible)
{
int bufferWidth = Console.BufferWidth;
int bufferLineCount = buffer.Length / bufferWidth;
if ((top + bufferLineCount) > Console.BufferHeight)
{
var scrollCount = (top + bufferLineCount) - Console.BufferHeight;
ScrollBuffer(scrollCount);
top -= scrollCount;
}
var handle = NativeMethods.GetStdHandle((uint) StandardHandleId.Output);
var bufferSize = new COORD
{
X = (short) bufferWidth,
Y = (short) bufferLineCount
};
var bufferCoord = new COORD {X = 0, Y = 0};
var bottom = top + bufferLineCount - 1;
var writeRegion = new SMALL_RECT
{
Top = (short) top,
Left = 0,
Bottom = (short) bottom,
Right = (short) (bufferWidth - 1)
};
NativeMethods.WriteConsoleOutput(handle, ToCharInfoBuffer(buffer),
bufferSize, bufferCoord, ref writeRegion);
// Now make sure the bottom line is visible
if (ensureBottomLineVisible &&
(bottom >= (Console.WindowTop + Console.WindowHeight)))
{
Console.CursorTop = bottom;
}
}
public void ScrollBuffer(int lines)
{
var handle = NativeMethods.GetStdHandle((uint) StandardHandleId.Output);
var scrollRectangle = new SMALL_RECT
{
Top = (short) lines,
Left = 0,
Bottom = (short)(Console.BufferHeight - 1),
Right = (short)Console.BufferWidth
};
var destinationOrigin = new COORD {X = 0, Y = 0};
var fillChar = new CHAR_INFO
{
UnicodeChar = ' ',
Attributes = (ushort)((int)Console.ForegroundColor | ((int)Console.BackgroundColor << 4))
};
NativeMethods.ScrollConsoleScreenBuffer(handle, ref scrollRectangle, IntPtr.Zero, destinationOrigin, ref fillChar);
}
public BufferChar[] ReadBufferLines(int top, int count)
{
var result = new CHAR_INFO[BufferWidth * count];
var handle = NativeMethods.GetStdHandle((uint) StandardHandleId.Output);
var readBufferSize = new COORD {
X = (short)BufferWidth,
Y = (short)count};
var readBufferCoord = new COORD {X = 0, Y = 0};
var readRegion = new SMALL_RECT
{
Top = (short)top,
Left = 0,
Bottom = (short)(top + count),
Right = (short)(BufferWidth - 1)
};
NativeMethods.ReadConsoleOutput(handle, result,
readBufferSize, readBufferCoord, ref readRegion);
return ToBufferCharBuffer(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);
}
}
private CHAR_INFO[] cachedBuffer;
private CHAR_INFO[] ToCharInfoBuffer(BufferChar[] buffer)
{
if (cachedBuffer == null || cachedBuffer.Length != buffer.Length)
{
cachedBuffer = new CHAR_INFO[buffer.Length];
}
for (int i = 0; i < buffer.Length; i++)
{
cachedBuffer[i] = buffer[i].ToCharInfo();
}
return cachedBuffer;
}
private BufferChar[] ToBufferCharBuffer(CHAR_INFO[] buffer)
{
var result = new BufferChar[buffer.Length];
for (int i = 0; i < buffer.Length; i++)
{
result[i] = BufferChar.FromCharInfo(buffer[i]);
}
return result;
}
}
#else
internal class TTYConsole : IConsole
{
public object GetConsoleInputMode()
{
return Console.TreatControlCAsInput;
}
public void SetConsoleInputMode(object modeObj)
{
Console.TreatControlCAsInput = true;
}
public void RestoreConsoleInputMode(object modeObj)
{
if (modeObj is bool)
{
Console.TreatControlCAsInput = (bool)modeObj;
}
}
public ConsoleKeyInfo ReadKey()
{
return Console.ReadKey(true);
}
public bool KeyAvailable
{
get { return Console.KeyAvailable; }
}
public int CursorLeft
{
get { return Console.CursorLeft; }
set { Console.CursorLeft = value; }
}
public int CursorTop
{
get { return Console.CursorTop; }
set { Console.CursorTop = value; }
}
public int CursorSize
{
get { return Console.CursorSize; }
set { Console.CursorSize = value; }
}
public int BufferWidth
{
get { return Console.BufferWidth; }
set { Console.BufferWidth = value; }
}
public int BufferHeight
{
get { return Console.BufferHeight; }
set { Console.BufferHeight = value; }
}
public int WindowWidth
{
get { return Console.WindowWidth; }
set { Console.WindowWidth = value; }
}
public int WindowHeight
{
get { return Console.WindowHeight; }
set { Console.WindowHeight = value; }
}
public int WindowTop
{
get { return Console.WindowTop; }
set { Console.WindowTop = value; }
}
public ConsoleColor BackgroundColor
{
get { return Console.BackgroundColor; }
set { Console.BackgroundColor = value; }
}
public ConsoleColor ForegroundColor
{
get { return Console.ForegroundColor; }
set { Console.ForegroundColor = value; }
}
public void SetWindowPosition(int left, int top)
{
Console.SetWindowPosition(left, top);
}
public void SetCursorPosition(int left, int top)
{
Console.SetCursorPosition(left, top);
}
public void Write(string value)
{
Console.Write(value);
}
public void WriteLine(string value)
{
Console.WriteLine(value);
}
public void WriteBufferLines(BufferChar[] buffer, ref int top)
{
WriteBufferLines(buffer, ref top, true);
}
public void WriteBufferLines(BufferChar[] buffer, ref int top, bool ensureBottomLineVisible)
{
int bufferWidth = Console.BufferWidth;
int bufferLineCount = buffer.Length / bufferWidth;
if ((top + bufferLineCount) > Console.BufferHeight)
{
var scrollCount = (top + bufferLineCount) - Console.BufferHeight;
ScrollBuffer(scrollCount);
top -= scrollCount;
}
ConsoleColor foregroundColor = Console.ForegroundColor;
ConsoleColor backgroundColor = Console.BackgroundColor;
Console.SetCursorPosition(0, (top>=0) ? top : 0);
for (int i = 0; i < buffer.Length; ++i)
{
// TODO: use escape sequences for better perf
Console.ForegroundColor = buffer[i].ForegroundColor;
Console.BackgroundColor = buffer[i].BackgroundColor;
Console.Write(buffer[i].UnicodeChar);
}
Console.BackgroundColor = backgroundColor;
Console.ForegroundColor = foregroundColor;
}
public void ScrollBuffer(int lines)
{
for (int i=0; i<lines; ++i)
{
Console.SetCursorPosition(Console.BufferWidth, Console.BufferHeight - 1);
Console.WriteLine();
}
}
public BufferChar[] ReadBufferLines(int top, int count)
{
var result = new BufferChar[BufferWidth * count];
for (int i=0; i<BufferWidth*count; ++i)
{
result[i].UnicodeChar = ' ';
result[i].ForegroundColor = Console.ForegroundColor;
result[i].BackgroundColor = Console.BackgroundColor;
}
return result;
}
public int LengthInBufferCells(char c)
{
return 1;
}
public void StartRender()
{
}
public void EndRender()
{
}
// TODO: this is not correct, PSReadline never clears the screen, it should only scroll
public void Clear()
{
Console.Clear();
}
}
#endif
#if CORECLR // TODO: remove if CORECLR adds this attribute back
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event)]
sealed class ExcludeFromCodeCoverageAttribute : Attribute
{
}
#endif
#pragma warning restore 1591
}