diff --git a/PSReadLine/ConsoleLib.cs b/PSReadLine/ConsoleLib.cs index 4e9570d8c..cc19bb0eb 100644 --- a/PSReadLine/ConsoleLib.cs +++ b/PSReadLine/ConsoleLib.cs @@ -21,6 +21,11 @@ public static class NativeMethods public const uint ENABLE_PROCESSED_INPUT = 0x0001; public const uint ENABLE_LINE_INPUT = 0x0002; + 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); @@ -58,6 +63,42 @@ public static extern int ToUnicode(uint uVirtKey, uint uScanCode, byte[] lpKeySt [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); } public delegate bool BreakHandler(ConsoleBreakSignal ConsoleBreakSignal); @@ -72,6 +113,12 @@ public enum ConsoleBreakSignal : uint None = 255, } + public enum CHAR_INFO_Attributes : ushort + { + COMMON_LVB_LEADING_BYTE = 0x0100, + COMMON_LVB_TRAILING_BYTE = 0x0200 + } + public enum StandardHandleId : uint { Error = unchecked((uint)-12), @@ -79,6 +126,32 @@ public enum StandardHandleId : uint Input = unchecked((uint)-10), } + [Flags] + public enum AccessQualifiers : uint + { + // From winnt.h + GenericRead = 0x80000000, + GenericWrite = 0x40000000 + } + + public enum CreationDisposition : uint + { + // From winbase.h + CreateNew = 1, + CreateAlways = 2, + OpenExisting = 3, + OpenAlways = 4, + TruncateExisting = 5 + } + + [Flags] + public enum ShareModes : uint + { + // From winnt.h + ShareRead = 0x00000001, + ShareWrite = 0x00000002 + } + public struct SMALL_RECT { public short Left; @@ -119,6 +192,69 @@ public override string ToString() } } + [StructLayout(LayoutKind.Sequential)] + public 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)] + public 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)] + public 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)] + public 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 CHAR_INFO { public ushort UnicodeChar; diff --git a/PSReadLine/Render.cs b/PSReadLine/Render.cs index bb5b8af74..0cfac145d 100644 --- a/PSReadLine/Render.cs +++ b/PSReadLine/Render.cs @@ -2,8 +2,14 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Management.Automation; +using System.Management.Automation.Host; using System.Management.Automation.Language; +using System.Runtime.InteropServices; +using System.Security; using PSConsoleUtilities.Internal; +using ConsoleHandle = Microsoft.Win32.SafeHandles.SafeFileHandle; +using System.ComponentModel; namespace PSConsoleUtilities { @@ -19,6 +25,35 @@ public partial class PSConsoleReadLine private int _current; private int _emphasisStart; private int _emphasisLength; + private IntPtr _hwnd = (IntPtr)0; + private IntPtr _hDC = (IntPtr)0; + private uint _codePage; + private bool _istmInitialized = false; + private TEXTMETRIC _tm = new TEXTMETRIC(); + private bool _trueTypeInUse = false; + + private readonly Lazy _outputHandle = new Lazy(() => + { + // 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 { @@ -73,131 +108,178 @@ private void Render() 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 bufferLineCount = ConvertOffsetToCoordinates(text.Length).Y - _initialY + 1 + statusLineCount; + int j = _initialX + (_bufferWidth * Options.ExtraPromptLineCount); + var backgroundColor = _initialBackgroundColor; + var foregroundColor = _initialForegroundColor; + bool afterLastToken = false; + int totalBytes = j; int bufferWidth = Console.BufferWidth; - if (_consoleBuffer.Length != bufferLineCount * bufferWidth) - { - var newBuffer = new CHAR_INFO[bufferLineCount * bufferWidth]; - Array.Copy(_consoleBuffer, newBuffer, _initialX + (Options.ExtraPromptLineCount * _bufferWidth)); - if (_consoleBuffer.Length > bufferLineCount * bufferWidth) - { - // Need to erase the extra lines that we won't draw again - for (int i = bufferLineCount * bufferWidth; i < _consoleBuffer.Length; i++) - { - _consoleBuffer[i] = _space; - } - WriteBufferLines(_consoleBuffer, ref _initialY); - } - _consoleBuffer = newBuffer; - } var tokenStack = new Stack(); tokenStack.Push(new SavedTokenState { - Tokens = _tokens, - Index = 0, + Tokens = _tokens, + Index = 0, BackgroundColor = _initialBackgroundColor, ForegroundColor = _initialForegroundColor }); - int j = _initialX + (_bufferWidth * Options.ExtraPromptLineCount); - var backgroundColor = _initialBackgroundColor; - var foregroundColor = _initialForegroundColor; - bool afterLastToken = false; + int bufferLineCount; - for (int i = 0; i < text.Length; i++) + try { - SavedTokenState state = null; - - if (!afterLastToken) + bufferLineCount = ConvertOffsetToCoordinates(text.Length).Y - _initialY + 1 + statusLineCount; + if (_consoleBuffer.Length != bufferLineCount * bufferWidth) { - // 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 token = state.Tokens[state.Index]; - if (i == token.Extent.EndOffset) + var newBuffer = new CHAR_INFO[bufferLineCount * bufferWidth]; + Array.Copy(_consoleBuffer, newBuffer, _initialX + (Options.ExtraPromptLineCount * _bufferWidth)); + if (_consoleBuffer.Length > bufferLineCount * bufferWidth) { - if (token == state.Tokens[state.Tokens.Length - 1]) + int consoleBufferOffset = ConvertOffsetToConsoleBufferOffset(text.Length, _initialX + (Options.ExtraPromptLineCount * _bufferWidth)); + // Need to erase the extra lines that we won't draw again + for (int i = consoleBufferOffset; i < _consoleBuffer.Length; i++) { - tokenStack.Pop(); - if (tokenStack.Count == 0) + _consoleBuffer[i] = _space; + } + WriteBufferLines(_consoleBuffer, ref _initialY); + } + _consoleBuffer = newBuffer; + } + + 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 token = state.Tokens[state.Index]; + if (i == token.Extent.EndOffset) + { + if (token == state.Tokens[state.Tokens.Length - 1]) { - afterLastToken = true; - token = null; - foregroundColor = _initialForegroundColor; - backgroundColor = _initialBackgroundColor; + tokenStack.Pop(); + if (tokenStack.Count == 0) + { + afterLastToken = true; + token = null; + foregroundColor = _initialForegroundColor; + backgroundColor = _initialBackgroundColor; + } + else + { + state = tokenStack.Peek(); + } } - else + + if (!afterLastToken) { - state = tokenStack.Peek(); + foregroundColor = state.ForegroundColor; + backgroundColor = state.BackgroundColor; + + token = state.Tokens[++state.Index]; } } - if (!afterLastToken) + if (!afterLastToken && i == token.Extent.StartOffset) { - foregroundColor = state.ForegroundColor; - backgroundColor = state.BackgroundColor; + GetTokenColors(token, out foregroundColor, out backgroundColor); - token = state.Tokens[++state.Index]; + var stringToken = token as StringExpandableToken; + if (stringToken != null) + { + // We might have nested tokens. + if (stringToken.NestedTokens != null && stringToken.NestedTokens.Any()) + { + var tokens = new Token[stringToken.NestedTokens.Count + 1]; + stringToken.NestedTokens.CopyTo(tokens, 0); + // NestedTokens doesn't have an "EOS" token, so we use + // the string literal token for that purpose. + tokens[tokens.Length - 1] = stringToken; + + tokenStack.Push(new SavedTokenState + { + Tokens = tokens, + Index = 0, + BackgroundColor = backgroundColor, + ForegroundColor = foregroundColor + }); + } + } } } - if (!afterLastToken && i == token.Extent.StartOffset) + if (text[i] == '\n') { - GetTokenColors(token, out foregroundColor, out backgroundColor); - - var stringToken = token as StringExpandableToken; - if (stringToken != null) + while ((j%bufferWidth) != 0) { - // We might have nested tokens. - if (stringToken.NestedTokens != null && stringToken.NestedTokens.Any()) - { - var tokens = new Token[stringToken.NestedTokens.Count + 1]; - stringToken.NestedTokens.CopyTo(tokens, 0); - // NestedTokens doesn't have an "EOS" token, so we use - // the string literal token for that purpose. - tokens[tokens.Length - 1] = stringToken; + _consoleBuffer[j++] = _space; + } - tokenStack.Push(new SavedTokenState - { - Tokens = tokens, - Index = 0, - BackgroundColor = backgroundColor, - ForegroundColor = foregroundColor - }); - } + for (int k = 0; k < Options.ContinuationPrompt.Length; k++, j++) + { + _consoleBuffer[j].UnicodeChar = Options.ContinuationPrompt[k]; + _consoleBuffer[j].ForegroundColor = Options.ContinuationPromptForegroundColor; + _consoleBuffer[j].BackgroundColor = Options.ContinuationPromptBackgroundColor; } } - } - - if (text[i] == '\n') - { - while ((j % bufferWidth) != 0) + else { - _consoleBuffer[j++] = _space; - } + int size = LengthInBufferCells(text[i]); + totalBytes += size; - for (int k = 0; k < Options.ContinuationPrompt.Length; k++, j++) - { - _consoleBuffer[j].UnicodeChar = Options.ContinuationPrompt[k]; - _consoleBuffer[j].ForegroundColor = Options.ContinuationPromptForegroundColor; - _consoleBuffer[j].BackgroundColor = Options.ContinuationPromptBackgroundColor; + //if there is no enough space for the character at the edge, fill in spaces at the end and + //put the character to next line. + int filling = totalBytes > bufferWidth ? (totalBytes - bufferWidth)%size : 0; + for (int f = 0; f < filling; f++) + { + _consoleBuffer[j++] = _space; + totalBytes++; + } + + if (char.IsControl(text[i])) + { + _consoleBuffer[j].UnicodeChar = '^'; + MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor); + _consoleBuffer[j].UnicodeChar = (char) ('@' + text[i]); + MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor); + + } + else if (size > 1 && IsCJKOutputCodePage() && _trueTypeInUse) + { + _consoleBuffer[j].UnicodeChar = text[i]; + _consoleBuffer[j].Attributes = (ushort) ((uint)_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 | + (uint) CHAR_INFO_Attributes.COMMON_LVB_TRAILING_BYTE); + MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor); + } + else + { + _consoleBuffer[j].UnicodeChar = text[i]; + MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor); + } } } - else if (char.IsControl(text[i])) - { - _consoleBuffer[j].UnicodeChar = '^'; - MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor); - _consoleBuffer[j].UnicodeChar = (char)('@' + text[i]); - MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor); - } - else - { - _consoleBuffer[j].UnicodeChar = text[i]; - MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor); - } + } + finally + { + if (_hwnd != (IntPtr)0 && _hDC != (IntPtr)0) + { + NativeMethods.ReleaseDC(_hwnd, _hDC); + } } for (; j < (_consoleBuffer.Length - (statusLineCount * _bufferWidth)); j++) @@ -439,6 +521,22 @@ private bool InRegion(int i) return i >= start && i < end; } + 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)) @@ -494,6 +592,167 @@ private void PlaceCursor() int y = coordinates.Y; 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; + } + + /// + /// Check if the output buffer code page is Japanese, Simplified Chinese, Korean, or Traditional Chinese + /// + /// true if it is CJK code page; otherwise, false. + 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) + { + int err = Marshal.GetLastWin32Error(); + return 1; + } + _hDC = NativeMethods.GetDC(_hwnd); + if ((IntPtr)0 == _hDC) + { + int err = Marshal.GetLastWin32Error(); + //Don't throw exception so that output can continue + return 1; + } + } + bool result = true; + if (!_istmInitialized) + { + result = NativeMethods.GetTextMetrics(_hDC, out _tm); + if (!result) + { + int err = Marshal.GetLastWin32Error(); + return 1; + } + _istmInitialized = true; + } + int width; + result = NativeMethods.GetCharWidth32(_hDC, (uint)c, (uint)c, out width); + if (!result) + { + int err = Marshal.GetLastWin32Error(); + return 1; + } + if (width >= _tm.tmMaxCharWidth) + { + return 2; + } + } + return 1; + } private COORD ConvertOffsetToCoordinates(int offset) { @@ -502,6 +761,7 @@ private COORD ConvertOffsetToCoordinates(int offset) int bufferWidth = Console.BufferWidth; var continuationPromptLength = Options.ContinuationPrompt.Length; + for (int i = 0; i < offset; i++) { char c = _buffer[i]; @@ -512,19 +772,70 @@ private COORD ConvertOffsetToCoordinates(int offset) } else { - x += char.IsControl(c) ? 2 : 1; + int size = LengthInBufferCells(c); + x += size; // Wrap? No prompt when wrapping if (x >= bufferWidth) { - x -= bufferWidth; + int offsize = x - bufferWidth; + if (offsize % size == 0) + { + x -= bufferWidth; + } + else + { + x = size; + } y += 1; } } } + //if the next character has bigger size than the remain space on this line, + //the cursor goes to next line where the next character is. + if (_buffer.Length > offset) + { + int size = LengthInBufferCells(_buffer[offset]); + // next one is Wrapped to next line + if (x + size > bufferWidth && (x + size - bufferWidth) % size != 0) + { + x = 0; + y++; + } + } + return new COORD {X = (short)x, Y = (short)y}; } + private int ConvertOffsetToConsoleBufferOffset(int offset, int startIndex) + { + int j = startIndex; + for (int i = 0; i < offset; i++) + { + char c = _buffer[i]; + if (_buffer[i] == '\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) + { + j += 2; + } + else + { + j++; + } + } + return j; + } + private int ConvertLineAndColumnToOffset(COORD coord) { int offset; @@ -555,18 +866,27 @@ private int ConvertLineAndColumnToOffset(COORD coord) } else { - x += char.IsControl(c) ? 2 : 1; + int size = LengthInBufferCells(c); + x += size; // Wrap? No prompt when wrapping if (x >= bufferWidth) { - x -= bufferWidth; + int offsize = x - bufferWidth; + if (offsize % size == 0) + { + x -= bufferWidth; + } + else + { + x = size; + } y += 1; } } } // Return -1 if y is out of range, otherwise the last line was shorter - // than we wanted, but still in range so just return the last offset.B + // than we wanted, but still in range so just return the last offset. return (coord.Y == y) ? offset : -1; } diff --git a/UnitTestPSReadLine/CompletionTest.cs b/UnitTestPSReadLine/CompletionTest.cs index a82829652..83276d1f5 100644 --- a/UnitTestPSReadLine/CompletionTest.cs +++ b/UnitTestPSReadLine/CompletionTest.cs @@ -164,7 +164,7 @@ public void TestDirectoryCompletion() static internal CommandCompletion MockedCompleteInput(string input, int cursor, Hashtable options, PowerShell powerShell) { var ctor = typeof (CommandCompletion).GetConstructor( - BindingFlags.NonPublic | BindingFlags.Instance, null, + BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, new [] {typeof (Collection), typeof (int), typeof (int), typeof (int)}, null); var completions = new Collection();