Only use CHAR_INFO on Windows

CHAR_INFO has 4 bits for ConsoleColor.  On Linux, ConsoleColor is sometimes -1 (unknown),
which is a 17th possible value for ConsoleColor, which can't be represented in CHAR_INFO.

To fix this, I've introduced a bigger type that gets converted to CHAR_INFO when necessary.
This commit is contained in:
Jason Shirk (POWERSHELL) 2016-07-07 11:45:08 -07:00
parent e79c62f9ec
commit 372d69583f
6 changed files with 128 additions and 96 deletions

View File

@ -298,24 +298,14 @@ namespace Microsoft.PowerShell
return replacementText; return replacementText;
} }
private static void InvertSelectedCompletion(CHAR_INFO[] buffer, int selectedItem, int menuColumnWidth, int menuRows) private static void InvertSelectedCompletion(BufferChar[] buffer, int selectedItem, int menuColumnWidth, int menuRows)
{ {
var selectedX = selectedItem / menuRows; var selectedX = selectedItem / menuRows;
var selectedY = selectedItem - (selectedX * menuRows); var selectedY = selectedItem - (selectedX * menuRows);
var start = selectedY * _singleton._console.BufferWidth + selectedX * menuColumnWidth; var start = selectedY * _singleton._console.BufferWidth + selectedX * menuColumnWidth;
for (int i = 0; i < menuColumnWidth; i++) for (int i = 0; i < menuColumnWidth; i++)
{ {
int j = i + start; buffer[i + start].Inverse = true;
#if LINUX // TODO: use real inverse
ConsoleColor tempColor = buffer[j].ForegroundColor == UnknownColor
? ConsoleColor.White : buffer[j].ForegroundColor;
buffer[j].ForegroundColor = buffer[j].BackgroundColor == UnknownColor
? ConsoleColor.Black : buffer[j].BackgroundColor;
buffer[j].BackgroundColor = tempColor;
#else
buffer[j].ForegroundColor = (ConsoleColor)((int)buffer[j].ForegroundColor ^ 7);
buffer[j].BackgroundColor = (ConsoleColor)((int)buffer[j].BackgroundColor ^ 7);
#endif
} }
} }

View File

@ -10,18 +10,18 @@ namespace Microsoft.PowerShell
{ {
internal class ConsoleBufferBuilder internal class ConsoleBufferBuilder
{ {
private List<CHAR_INFO> buffer; private List<BufferChar> buffer;
private IConsole _console; private IConsole _console;
public ConsoleBufferBuilder(int capacity, IConsole console) public ConsoleBufferBuilder(int capacity, IConsole console)
{ {
buffer = new List<CHAR_INFO>(capacity); buffer = new List<BufferChar>(capacity);
_console = console; _console = console;
} }
CHAR_INFO NewCharInfo(char c) BufferChar NewCharInfo(char c)
{ {
return new CHAR_INFO return new BufferChar
{ {
UnicodeChar = c, UnicodeChar = c,
BackgroundColor = _console.BackgroundColor, BackgroundColor = _console.BackgroundColor,
@ -45,7 +45,7 @@ namespace Microsoft.PowerShell
} }
} }
public CHAR_INFO[] ToArray() public BufferChar[] ToArray()
{ {
return buffer.ToArray(); return buffer.ToArray();
} }

View File

@ -309,60 +309,63 @@ namespace Microsoft.PowerShell.Internal
internal string FontFace; internal string FontFace;
} }
public struct BufferChar
{
public char UnicodeChar;
public ConsoleColor ForegroundColor;
public ConsoleColor BackgroundColor;
#if !LINUX
public bool IsLeadByte;
public bool IsTrailByte;
#endif
public bool Inverse;
#if !LINUX
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 struct CHAR_INFO
{ {
public ushort UnicodeChar; public ushort UnicodeChar;
public ushort Attributes; public ushort Attributes;
public CHAR_INFO(char c, ConsoleColor foreground, ConsoleColor background)
{
UnicodeChar = c;
Attributes = (ushort)((((int)background << 4) & 0xf) | ((int)foreground & 0xf));
}
[ExcludeFromCodeCoverage]
public ConsoleColor ForegroundColor
{
get { return (ConsoleColor)(Attributes & 0xf); }
set { Attributes = (ushort)((Attributes & 0xfff0) | ((int)value & 0xf)); }
}
[ExcludeFromCodeCoverage]
public ConsoleColor BackgroundColor
{
get { return (ConsoleColor)((Attributes & 0x00f0) >> 4); }
set { Attributes = (ushort)((Attributes & 0xff0f) | (((int)value & 0xf) << 4)); }
}
[ExcludeFromCodeCoverage]
public override string ToString()
{
var sb = new StringBuilder();
sb.Append((char)UnicodeChar);
if (ForegroundColor != Console.ForegroundColor)
sb.AppendFormat(" fg: {0}", ForegroundColor);
if (BackgroundColor != Console.BackgroundColor)
sb.AppendFormat(" bg: {0}", BackgroundColor);
return sb.ToString();
}
[ExcludeFromCodeCoverage]
public override bool Equals(object obj)
{
if (!(obj is CHAR_INFO))
{
return false;
}
var other = (CHAR_INFO)obj;
return this.UnicodeChar == other.UnicodeChar && this.Attributes == other.Attributes;
}
[ExcludeFromCodeCoverage]
public override int GetHashCode()
{
return UnicodeChar.GetHashCode() + Attributes.GetHashCode();
}
} }
internal static class ConsoleKeyInfoExtension internal static class ConsoleKeyInfoExtension
@ -611,12 +614,12 @@ namespace Microsoft.PowerShell.Internal
Console.WriteLine(value); Console.WriteLine(value);
} }
public void WriteBufferLines(CHAR_INFO[] buffer, ref int top) public void WriteBufferLines(BufferChar[] buffer, ref int top)
{ {
WriteBufferLines(buffer, ref top, true); WriteBufferLines(buffer, ref top, true);
} }
public void WriteBufferLines(CHAR_INFO[] buffer, ref int top, bool ensureBottomLineVisible) public void WriteBufferLines(BufferChar[] buffer, ref int top, bool ensureBottomLineVisible)
{ {
int bufferWidth = Console.BufferWidth; int bufferWidth = Console.BufferWidth;
int bufferLineCount = buffer.Length / bufferWidth; int bufferLineCount = buffer.Length / bufferWidth;
@ -642,7 +645,7 @@ namespace Microsoft.PowerShell.Internal
Bottom = (short) bottom, Bottom = (short) bottom,
Right = (short) (bufferWidth - 1) Right = (short) (bufferWidth - 1)
}; };
NativeMethods.WriteConsoleOutput(handle, buffer, NativeMethods.WriteConsoleOutput(handle, ToCharInfoBuffer(buffer),
bufferSize, bufferCoord, ref writeRegion); bufferSize, bufferCoord, ref writeRegion);
// Now make sure the bottom line is visible // Now make sure the bottom line is visible
@ -665,11 +668,15 @@ namespace Microsoft.PowerShell.Internal
Right = (short)Console.BufferWidth Right = (short)Console.BufferWidth
}; };
var destinationOrigin = new COORD {X = 0, Y = 0}; var destinationOrigin = new COORD {X = 0, Y = 0};
var fillChar = new CHAR_INFO(' ', Console.ForegroundColor, Console.BackgroundColor); 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); NativeMethods.ScrollConsoleScreenBuffer(handle, ref scrollRectangle, IntPtr.Zero, destinationOrigin, ref fillChar);
} }
public CHAR_INFO[] ReadBufferLines(int top, int count) public BufferChar[] ReadBufferLines(int top, int count)
{ {
var result = new CHAR_INFO[BufferWidth * count]; var result = new CHAR_INFO[BufferWidth * count];
var handle = NativeMethods.GetStdHandle((uint) StandardHandleId.Output); var handle = NativeMethods.GetStdHandle((uint) StandardHandleId.Output);
@ -688,7 +695,7 @@ namespace Microsoft.PowerShell.Internal
NativeMethods.ReadConsoleOutput(handle, result, NativeMethods.ReadConsoleOutput(handle, result,
readBufferSize, readBufferCoord, ref readRegion); readBufferSize, readBufferCoord, ref readRegion);
return result; return ToBufferCharBuffer(result);
} }
[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods",
@ -882,6 +889,35 @@ namespace Microsoft.PowerShell.Internal
NativeMethods.ReleaseDC(_hwnd, _hDC); 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 #else
@ -996,12 +1032,12 @@ namespace Microsoft.PowerShell.Internal
Console.WriteLine(value); Console.WriteLine(value);
} }
public void WriteBufferLines(CHAR_INFO[] buffer, ref int top) public void WriteBufferLines(BufferChar[] buffer, ref int top)
{ {
WriteBufferLines(buffer, ref top, true); WriteBufferLines(buffer, ref top, true);
} }
public void WriteBufferLines(CHAR_INFO[] buffer, ref int top, bool ensureBottomLineVisible) public void WriteBufferLines(BufferChar[] buffer, ref int top, bool ensureBottomLineVisible)
{ {
int bufferWidth = Console.BufferWidth; int bufferWidth = Console.BufferWidth;
int bufferLineCount = buffer.Length / bufferWidth; int bufferLineCount = buffer.Length / bufferWidth;
@ -1023,7 +1059,7 @@ namespace Microsoft.PowerShell.Internal
Console.ForegroundColor = buffer[i].ForegroundColor; Console.ForegroundColor = buffer[i].ForegroundColor;
Console.BackgroundColor = buffer[i].BackgroundColor; Console.BackgroundColor = buffer[i].BackgroundColor;
Console.Write((char)buffer[i].UnicodeChar); Console.Write(buffer[i].UnicodeChar);
} }
Console.BackgroundColor = backgroundColor; Console.BackgroundColor = backgroundColor;
@ -1039,9 +1075,9 @@ namespace Microsoft.PowerShell.Internal
} }
} }
public CHAR_INFO[] ReadBufferLines(int top, int count) public BufferChar[] ReadBufferLines(int top, int count)
{ {
var result = new CHAR_INFO[BufferWidth * count]; var result = new BufferChar[BufferWidth * count];
for (int i=0; i<BufferWidth*count; ++i) for (int i=0; i<BufferWidth*count; ++i)
{ {
result[i].UnicodeChar = ' '; result[i].UnicodeChar = ' ';
@ -1056,7 +1092,6 @@ namespace Microsoft.PowerShell.Internal
return 1; return 1;
} }
public void StartRender() public void StartRender()
{ {
} }

View File

@ -47,10 +47,10 @@ namespace Microsoft.PowerShell
void SetCursorPosition(int left, int top); void SetCursorPosition(int left, int top);
void WriteLine(string s); void WriteLine(string s);
void Write(string s); void Write(string s);
void WriteBufferLines(CHAR_INFO[] buffer, ref int top); void WriteBufferLines(BufferChar[] buffer, ref int top);
void WriteBufferLines(CHAR_INFO[] buffer, ref int top, bool ensureBottomLineVisible); void WriteBufferLines(BufferChar[] buffer, ref int top, bool ensureBottomLineVisible);
void ScrollBuffer(int lines); void ScrollBuffer(int lines);
CHAR_INFO[] ReadBufferLines(int top, int count); BufferChar[] ReadBufferLines(int top, int count);
void StartRender(); void StartRender();
int LengthInBufferCells(char c); int LengthInBufferCells(char c);

View File

@ -544,7 +544,12 @@ namespace Microsoft.PowerShell
_initialY = _console.CursorTop - Options.ExtraPromptLineCount; _initialY = _console.CursorTop - Options.ExtraPromptLineCount;
_initialBackgroundColor = _console.BackgroundColor; _initialBackgroundColor = _console.BackgroundColor;
_initialForegroundColor = _console.ForegroundColor; _initialForegroundColor = _console.ForegroundColor;
_space = new CHAR_INFO(' ', _initialForegroundColor, _initialBackgroundColor); _space = new BufferChar
{
UnicodeChar = ' ',
BackgroundColor = _initialBackgroundColor,
ForegroundColor = _initialForegroundColor
};
_bufferWidth = _console.BufferWidth; _bufferWidth = _console.BufferWidth;
_killCommandCount = 0; _killCommandCount = 0;
_yankCommandCount = 0; _yankCommandCount = 0;

View File

@ -13,14 +13,16 @@ namespace Microsoft.PowerShell
{ {
public partial class PSConsoleReadLine public partial class PSConsoleReadLine
{ {
#if LINUX
private const ConsoleColor UnknownColor = (ConsoleColor) (-1); private const ConsoleColor UnknownColor = (ConsoleColor) (-1);
private CHAR_INFO[] _consoleBuffer; #endif
private BufferChar[] _consoleBuffer;
private int _initialX; private int _initialX;
private int _initialY; private int _initialY;
private int _bufferWidth; private int _bufferWidth;
private ConsoleColor _initialBackgroundColor; private ConsoleColor _initialBackgroundColor;
private ConsoleColor _initialForegroundColor; private ConsoleColor _initialForegroundColor;
private CHAR_INFO _space; private BufferChar _space;
private int _current; private int _current;
private int _emphasisStart; private int _emphasisStart;
private int _emphasisLength; private int _emphasisLength;
@ -105,7 +107,7 @@ namespace Microsoft.PowerShell
bufferLineCount = ConvertOffsetToCoordinates(text.Length).Y - _initialY + 1 + statusLineCount; bufferLineCount = ConvertOffsetToCoordinates(text.Length).Y - _initialY + 1 + statusLineCount;
if (_consoleBuffer.Length != bufferLineCount * bufferWidth) if (_consoleBuffer.Length != bufferLineCount * bufferWidth)
{ {
var newBuffer = new CHAR_INFO[bufferLineCount * bufferWidth]; var newBuffer = new BufferChar[bufferLineCount * bufferWidth];
Array.Copy(_consoleBuffer, newBuffer, _initialX + (Options.ExtraPromptLineCount * _bufferWidth)); Array.Copy(_consoleBuffer, newBuffer, _initialX + (Options.ExtraPromptLineCount * _bufferWidth));
if (_consoleBuffer.Length > bufferLineCount * bufferWidth) if (_consoleBuffer.Length > bufferLineCount * bufferWidth)
{ {
@ -226,17 +228,17 @@ namespace Microsoft.PowerShell
MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor); MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor);
} }
#if !LINUX
else if (size > 1) else if (size > 1)
{ {
_consoleBuffer[j].UnicodeChar = charToRender; _consoleBuffer[j].UnicodeChar = charToRender;
_consoleBuffer[j].Attributes = (ushort)(_consoleBuffer[j].Attributes | _consoleBuffer[j].IsLeadByte = true;
(uint)CHAR_INFO_Attributes.COMMON_LVB_LEADING_BYTE);
MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor); MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor);
_consoleBuffer[j].UnicodeChar = charToRender; _consoleBuffer[j].UnicodeChar = charToRender;
_consoleBuffer[j].Attributes = (ushort)(_consoleBuffer[j].Attributes | _consoleBuffer[j].IsTrailByte = true;
(uint)CHAR_INFO_Attributes.COMMON_LVB_TRAILING_BYTE);
MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor); MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor);
} }
#endif
else else
{ {
_consoleBuffer[j].UnicodeChar = charToRender; _consoleBuffer[j].UnicodeChar = charToRender;
@ -286,7 +288,7 @@ namespace Microsoft.PowerShell
while (promptChar >= 0) while (promptChar >= 0)
{ {
var c = (char)_consoleBuffer[promptChar].UnicodeChar; var c = _consoleBuffer[promptChar].UnicodeChar;
if (char.IsWhiteSpace(c)) if (char.IsWhiteSpace(c))
{ {
promptChar -= 1; promptChar -= 1;
@ -332,7 +334,7 @@ namespace Microsoft.PowerShell
private static void WriteBlankLines(int count, int top) private static void WriteBlankLines(int count, int top)
{ {
var console = _singleton._console; var console = _singleton._console;
var blanks = new CHAR_INFO[count * console.BufferWidth]; var blanks = new BufferChar[count * console.BufferWidth];
for (int i = 0; i < blanks.Length; i++) for (int i = 0; i < blanks.Length; i++)
{ {
blanks[i].BackgroundColor = console.BackgroundColor; blanks[i].BackgroundColor = console.BackgroundColor;
@ -342,7 +344,7 @@ namespace Microsoft.PowerShell
console.WriteBufferLines(blanks, ref top); console.WriteBufferLines(blanks, ref top);
} }
private static CHAR_INFO[] ReadBufferLines(int top, int count) private static BufferChar[] ReadBufferLines(int top, int count)
{ {
return _singleton._console.ReadBufferLines(top, count); return _singleton._console.ReadBufferLines(top, count);
} }
@ -450,7 +452,7 @@ namespace Microsoft.PowerShell
return i >= start && i < end; return i >= start && i < end;
} }
private void MaybeEmphasize(ref CHAR_INFO charInfo, int i, ConsoleColor foregroundColor, ConsoleColor backgroundColor) private void MaybeEmphasize(ref BufferChar charInfo, int i, ConsoleColor foregroundColor, ConsoleColor backgroundColor)
{ {
if (i >= _emphasisStart && i < (_emphasisStart + _emphasisLength)) if (i >= _emphasisStart && i < (_emphasisStart + _emphasisLength))
{ {
@ -682,7 +684,7 @@ namespace Microsoft.PowerShell
return key.Key == ConsoleKey.Y; return key.Key == ConsoleKey.Y;
} }
#region Screen scrolling #region Screen scrolling
#if !LINUX #if !LINUX
/// <summary> /// <summary>
@ -804,6 +806,6 @@ namespace Microsoft.PowerShell
} }
#endif #endif
#endregion Screen scrolling #endregion Screen scrolling
} }
} }