Skip to content

Commit accc20d

Browse files
committed
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.
1 parent 2ef73a9 commit accc20d

File tree

4 files changed

+269
-238
lines changed

4 files changed

+269
-238
lines changed

PSReadLine/ConsoleLib.cs

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,38 @@ public static string ToGestureString(this ConsoleKeyInfo key)
409409

410410
internal class ConhostConsole : IConsole
411411
{
412+
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
413+
private IntPtr _hwnd = (IntPtr)0;
414+
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
415+
private IntPtr _hDC = (IntPtr)0;
416+
private uint _codePage;
417+
private bool _istmInitialized = false;
418+
private TEXTMETRIC _tm = new TEXTMETRIC();
419+
private bool _trueTypeInUse = false;
420+
421+
private readonly Lazy<SafeFileHandle> _outputHandle = new Lazy<SafeFileHandle>(() =>
422+
{
423+
// We use CreateFile here instead of GetStdWin32Handle, as GetStdWin32Handle will return redirected handles
424+
var handle = NativeMethods.CreateFile(
425+
"CONOUT$",
426+
(UInt32)(AccessQualifiers.GenericRead | AccessQualifiers.GenericWrite),
427+
(UInt32)ShareModes.ShareWrite,
428+
(IntPtr)0,
429+
(UInt32)CreationDisposition.OpenExisting,
430+
0,
431+
(IntPtr)0);
432+
433+
if (handle == NativeMethods.INVALID_HANDLE_VALUE)
434+
{
435+
int err = Marshal.GetLastWin32Error();
436+
Win32Exception innerException = new Win32Exception(err);
437+
throw new Exception("Failed to retreive the input console handle.", innerException);
438+
}
439+
440+
return new SafeFileHandle(handle, true);
441+
}
442+
);
443+
412444
private readonly Lazy<SafeFileHandle> _inputHandle = new Lazy<SafeFileHandle>(() =>
413445
{
414446
// We use CreateFile here instead of GetStdWin32Handle, as GetStdWin32Handle will return redirected handles
@@ -613,5 +645,197 @@ public CHAR_INFO[] ReadBufferLines(int top, int count)
613645
readBufferSize, readBufferCoord, ref readRegion);
614646
return result;
615647
}
648+
649+
[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods",
650+
Justification = "Then the API we pass the handle to will return an error if it is invalid. They are not exposed.")]
651+
internal static CONSOLE_FONT_INFO_EX GetConsoleFontInfo(SafeFileHandle consoleHandle)
652+
{
653+
654+
CONSOLE_FONT_INFO_EX fontInfo = new CONSOLE_FONT_INFO_EX();
655+
fontInfo.cbSize = Marshal.SizeOf(fontInfo);
656+
bool result = NativeMethods.GetCurrentConsoleFontEx(consoleHandle.DangerousGetHandle(), false, ref fontInfo);
657+
658+
if (result == false)
659+
{
660+
int err = Marshal.GetLastWin32Error();
661+
Win32Exception innerException = new Win32Exception(err);
662+
throw new Exception("Failed to get console font information.", innerException);
663+
}
664+
return fontInfo;
665+
}
666+
667+
public int LengthInBufferCells(char c)
668+
{
669+
if (!IsCJKOutputCodePage() || !_trueTypeInUse)
670+
return 1;
671+
672+
return LengthInBufferCellsFE(c);
673+
}
674+
675+
internal static bool IsAnyDBCSCharSet(uint charSet)
676+
{
677+
const uint SHIFTJIS_CHARSET = 128;
678+
const uint HANGEUL_CHARSET = 129;
679+
const uint CHINESEBIG5_CHARSET = 136;
680+
const uint GB2312_CHARSET = 134;
681+
return charSet == SHIFTJIS_CHARSET || charSet == HANGEUL_CHARSET ||
682+
charSet == CHINESEBIG5_CHARSET || charSet == GB2312_CHARSET;
683+
}
684+
685+
internal uint CodePageToCharSet()
686+
{
687+
CHARSETINFO csi;
688+
const uint TCI_SRCCODEPAGE = 2;
689+
const uint OEM_CHARSET = 255;
690+
if (!NativeMethods.TranslateCharsetInfo((IntPtr)_codePage, out csi, TCI_SRCCODEPAGE))
691+
{
692+
csi.ciCharset = OEM_CHARSET;
693+
}
694+
return csi.ciCharset;
695+
}
696+
697+
/// <summary>
698+
/// Check if the output buffer code page is Japanese, Simplified Chinese, Korean, or Traditional Chinese
699+
/// </summary>
700+
/// <returns>true if it is CJK code page; otherwise, false.</returns>
701+
internal bool IsCJKOutputCodePage()
702+
{
703+
return _codePage == 932 || // Japanese
704+
_codePage == 936 || // Simplified Chinese
705+
_codePage == 949 || // Korean
706+
_codePage == 950; // Traditional Chinese
707+
}
708+
709+
internal bool IsAvailableFarEastCodePage()
710+
{
711+
uint charSet = CodePageToCharSet();
712+
return IsAnyDBCSCharSet(charSet);
713+
}
714+
715+
internal int LengthInBufferCellsFE(char c)
716+
{
717+
if (0x20 <= c && c <= 0x7e)
718+
{
719+
/* ASCII */
720+
return 1;
721+
}
722+
else if (0x3041 <= c && c <= 0x3094)
723+
{
724+
/* Hiragana */
725+
return 2;
726+
}
727+
else if (0x30a1 <= c && c <= 0x30f6)
728+
{
729+
/* Katakana */
730+
return 2;
731+
}
732+
else if (0x3105 <= c && c <= 0x312c)
733+
{
734+
/* Bopomofo */
735+
return 2;
736+
}
737+
else if (0x3131 <= c && c <= 0x318e)
738+
{
739+
/* Hangul Elements */
740+
return 2;
741+
}
742+
else if (0xac00 <= c && c <= 0xd7a3)
743+
{
744+
/* Korean Hangul Syllables */
745+
return 2;
746+
}
747+
else if (0xff01 <= c && c <= 0xff5e)
748+
{
749+
/* Fullwidth ASCII variants */
750+
return 2;
751+
}
752+
else if (0xff61 <= c && c <= 0xff9f)
753+
{
754+
/* Halfwidth Katakana variants */
755+
return 1;
756+
}
757+
else if ((0xffa0 <= c && c <= 0xffbe) ||
758+
(0xffc2 <= c && c <= 0xffc7) ||
759+
(0xffca <= c && c <= 0xffcf) ||
760+
(0xffd2 <= c && c <= 0xffd7) ||
761+
(0xffda <= c && c <= 0xffdc))
762+
{
763+
/* Halfwidth Hangule variants */
764+
return 1;
765+
}
766+
else if (0xffe0 <= c && c <= 0xffe6)
767+
{
768+
/* Fullwidth symbol variants */
769+
return 2;
770+
}
771+
else if (0x4e00 <= c && c <= 0x9fa5)
772+
{
773+
/* Han Ideographic */
774+
return 2;
775+
}
776+
else if (0xf900 <= c && c <= 0xfa2d)
777+
{
778+
/* Han Compatibility Ideographs */
779+
return 2;
780+
}
781+
else
782+
{
783+
/* Unknown character: need to use GDI*/
784+
if (_hDC == (IntPtr)0)
785+
{
786+
_hwnd = NativeMethods.GetConsoleWindow();
787+
if ((IntPtr)0 == _hwnd)
788+
{
789+
return 1;
790+
}
791+
_hDC = NativeMethods.GetDC(_hwnd);
792+
if ((IntPtr)0 == _hDC)
793+
{
794+
//Don't throw exception so that output can continue
795+
return 1;
796+
}
797+
}
798+
bool result = true;
799+
if (!_istmInitialized)
800+
{
801+
result = NativeMethods.GetTextMetrics(_hDC, out _tm);
802+
if (!result)
803+
{
804+
return 1;
805+
}
806+
_istmInitialized = true;
807+
}
808+
int width;
809+
result = NativeMethods.GetCharWidth32(_hDC, (uint)c, (uint)c, out width);
810+
if (!result)
811+
{
812+
return 1;
813+
}
814+
if (width >= _tm.tmMaxCharWidth)
815+
{
816+
return 2;
817+
}
818+
}
819+
return 1;
820+
}
821+
822+
public void StartRender()
823+
{
824+
_codePage = NativeMethods.GetConsoleOutputCP();
825+
_istmInitialized = false;
826+
var consoleHandle = _outputHandle.Value;
827+
CONSOLE_FONT_INFO_EX fontInfo = ConhostConsole.GetConsoleFontInfo(consoleHandle);
828+
int fontType = fontInfo.FontFamily & NativeMethods.FontTypeMask;
829+
_trueTypeInUse = (fontType & NativeMethods.TrueTypeFont) == NativeMethods.TrueTypeFont;
830+
831+
}
832+
833+
public void EndRender()
834+
{
835+
if (_hwnd != (IntPtr)0 && _hDC != (IntPtr)0)
836+
{
837+
NativeMethods.ReleaseDC(_hwnd, _hDC);
838+
}
839+
}
616840
}
617841
}

PSReadLine/PublicAPI.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ public interface IConsole
5050
void WriteBufferLines(CHAR_INFO[] buffer, ref int top, bool ensureBottomLineVisible);
5151
void ScrollBuffer(int lines);
5252
CHAR_INFO[] ReadBufferLines(int top, int count);
53+
54+
void StartRender();
55+
int LengthInBufferCells(char c);
56+
void EndRender();
5357
}
5458
}
5559

0 commit comments

Comments
 (0)