Introduce TTYConsole for UNIX

This commit is contained in:
Jason Shirk (POWERSHELL) 2016-07-06 17:30:16 -07:00
parent 8e234322c4
commit e79c62f9ec
5 changed files with 238 additions and 101 deletions

View File

@ -307,9 +307,9 @@ namespace Microsoft.PowerShell
{
int j = i + start;
#if LINUX // TODO: use real inverse
ConsoleColor tempColor = (int)buffer[j].ForegroundColor == -1
ConsoleColor tempColor = buffer[j].ForegroundColor == UnknownColor
? ConsoleColor.White : buffer[j].ForegroundColor;
buffer[j].ForegroundColor = (int)buffer[j].BackgroundColor == -1
buffer[j].ForegroundColor = buffer[j].BackgroundColor == UnknownColor
? ConsoleColor.Black : buffer[j].BackgroundColor;
buffer[j].BackgroundColor = tempColor;
#else

View File

@ -317,7 +317,7 @@ namespace Microsoft.PowerShell.Internal
public CHAR_INFO(char c, ConsoleColor foreground, ConsoleColor background)
{
UnicodeChar = c;
Attributes = (ushort)(((int)background << 4) | (int)foreground);
Attributes = (ushort)((((int)background << 4) & 0xf) | ((int)foreground & 0xf));
}
[ExcludeFromCodeCoverage]
@ -330,7 +330,7 @@ namespace Microsoft.PowerShell.Internal
[ExcludeFromCodeCoverage]
public ConsoleColor BackgroundColor
{
get { return (ConsoleColor)((Attributes & 0xf0) >> 4); }
get { return (ConsoleColor)((Attributes & 0x00f0) >> 4); }
set { Attributes = (ushort)((Attributes & 0xff0f) | (((int)value & 0xf) << 4)); }
}
@ -426,6 +426,7 @@ namespace Microsoft.PowerShell.Internal
}
}
#if !LINUX
internal class ConhostConsole : IConsole
{
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
@ -482,18 +483,42 @@ namespace Microsoft.PowerShell.Internal
return new SafeFileHandle(handle, true);
});
public uint GetConsoleInputMode()
public object GetConsoleInputMode()
{
uint mode;
var handle = _inputHandle.Value.DangerousGetHandle();
uint result;
NativeMethods.GetConsoleMode(handle, out result);
return result;
NativeMethods.GetConsoleMode(handle, out mode);
return mode;
}
public void SetConsoleInputMode(uint mode)
public void SetConsoleInputMode(object modeObj)
{
var handle = _inputHandle.Value.DangerousGetHandle();
NativeMethods.SetConsoleMode(handle, mode);
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()
@ -601,24 +626,7 @@ namespace Microsoft.PowerShell.Internal
ScrollBuffer(scrollCount);
top -= scrollCount;
}
#if LINUX
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((char)buffer[i].UnicodeChar);
}
Console.BackgroundColor = backgroundColor;
Console.ForegroundColor = foregroundColor;
#else
var handle = NativeMethods.GetStdHandle((uint) StandardHandleId.Output);
var bufferSize = new COORD
{
@ -643,18 +651,10 @@ namespace Microsoft.PowerShell.Internal
{
Console.CursorTop = bottom;
}
#endif
}
public void ScrollBuffer(int lines)
{
#if LINUX
for (int i=0; i<lines; ++i)
{
Console.SetCursorPosition(Console.BufferWidth, Console.BufferHeight - 1);
Console.WriteLine();
}
#else
var handle = NativeMethods.GetStdHandle((uint) StandardHandleId.Output);
var scrollRectangle = new SMALL_RECT
@ -667,20 +667,11 @@ namespace Microsoft.PowerShell.Internal
var destinationOrigin = new COORD {X = 0, Y = 0};
var fillChar = new CHAR_INFO(' ', Console.ForegroundColor, Console.BackgroundColor);
NativeMethods.ScrollConsoleScreenBuffer(handle, ref scrollRectangle, IntPtr.Zero, destinationOrigin, ref fillChar);
#endif
}
public CHAR_INFO[] ReadBufferLines(int top, int count)
{
var result = new CHAR_INFO[BufferWidth * count];
#if LINUX
for (int i=0; i<BufferWidth*count; ++i)
{
result[i].UnicodeChar = ' ';
result[i].ForegroundColor = Console.ForegroundColor;
result[i].BackgroundColor = Console.BackgroundColor;
}
#else
var handle = NativeMethods.GetStdHandle((uint) StandardHandleId.Output);
var readBufferSize = new COORD {
@ -696,7 +687,7 @@ namespace Microsoft.PowerShell.Internal
};
NativeMethods.ReadConsoleOutput(handle, result,
readBufferSize, readBufferCoord, ref readRegion);
#endif
return result;
}
@ -891,14 +882,196 @@ namespace Microsoft.PowerShell.Internal
NativeMethods.ReleaseDC(_hwnd, _hDC);
}
}
}
#if LINUX // TODO: this is not correct, PSReadline never clears the screen, it should only scroll
#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(CHAR_INFO[] buffer, ref int top)
{
WriteBufferLines(buffer, ref top, true);
}
public void WriteBufferLines(CHAR_INFO[] 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((char)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 CHAR_INFO[] ReadBufferLines(int top, int count)
{
var result = new CHAR_INFO[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
}
#endif
#if CORECLR // TODO: remove if CORECLR adds this attribute back
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event)]

View File

@ -28,8 +28,9 @@ namespace Microsoft.PowerShell
[SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")]
public interface IConsole
{
uint GetConsoleInputMode();
void SetConsoleInputMode(uint mode);
object GetConsoleInputMode();
void SetConsoleInputMode(object mode);
void RestoreConsoleInputMode(object mode);
ConsoleKeyInfo ReadKey();
bool KeyAvailable { get; }
int CursorLeft { get; set; }

View File

@ -43,11 +43,7 @@ namespace Microsoft.PowerShell
private ManualResetEvent _closingWaitHandle;
private WaitHandle[] _threadProcWaitHandles;
private WaitHandle[] _requestKeyWaitHandles;
#if LINUX // TODO: move to IConsole when there is a Linux IConsole implementation
private bool _prePSReadlineControlCMode;
#else
private uint _prePSReadlineConsoleMode;
#endif
private object _savedConsoleInputMode;
private readonly StringBuilder _buffer;
private readonly StringBuilder _statusBuffer;
@ -266,32 +262,13 @@ namespace Microsoft.PowerShell
throw new NotSupportedException();
}
#if LINUX // TODO: move to IConsole when there is a Linux IConsole implementation
_singleton._prePSReadlineControlCMode = Console.TreatControlCAsInput;
#else
_singleton._prePSReadlineConsoleMode = console.GetConsoleInputMode();
#endif
_singleton._savedConsoleInputMode = _singleton._console.GetConsoleInputMode();
bool firstTime = true;
while (true)
{
try
{
#if LINUX // TODO: move to IConsole when there is a Linux IConsole implementation
Console.TreatControlCAsInput = true;
#else
// 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 = _singleton._prePSReadlineConsoleMode &
~(NativeMethods.ENABLE_PROCESSED_INPUT |
NativeMethods.ENABLE_LINE_INPUT |
NativeMethods.ENABLE_WINDOW_INPUT |
NativeMethods.ENABLE_MOUSE_INPUT);
console.SetConsoleInputMode(mode);
#endif
_singleton._console.SetConsoleInputMode(_singleton._savedConsoleInputMode);
if (firstTime)
{
@ -362,11 +339,7 @@ namespace Microsoft.PowerShell
}
finally
{
#if LINUX // TODO: move to IConsole when there is a Linux IConsole implementation
Console.TreatControlCAsInput = _singleton._prePSReadlineControlCMode;
#else
console.SetConsoleInputMode(_singleton._prePSReadlineConsoleMode);
#endif
_singleton._console.RestoreConsoleInputMode(_singleton._savedConsoleInputMode);
}
}
}
@ -454,29 +427,16 @@ namespace Microsoft.PowerShell
T CalloutUsingDefaultConsoleMode<T>(Func<T> func)
{
#if LINUX // TODO: move to IConsole when there is a Linux IConsole implementation
bool psReadlineControlCMode = Console.TreatControlCAsInput;
var currentMode = _console.GetConsoleInputMode();
try
{
Console.TreatControlCAsInput = _prePSReadlineControlCMode;
_console.RestoreConsoleInputMode(_savedConsoleInputMode);
return func();
}
finally
{
Console.TreatControlCAsInput = psReadlineControlCMode;
_console.RestoreConsoleInputMode(currentMode);
}
#else
uint psReadlineConsoleMode = _console.GetConsoleInputMode();
try
{
_console.SetConsoleInputMode(_prePSReadlineConsoleMode);
return func();
}
finally
{
_console.SetConsoleInputMode(psReadlineConsoleMode);
}
#endif
}
void CalloutUsingDefaultConsoleMode(Action action)
@ -519,7 +479,11 @@ namespace Microsoft.PowerShell
private PSConsoleReadLine()
{
_mockableMethods = this;
#if LINUX
_console = new TTYConsole();
#else
_console = new ConhostConsole();
#endif
SetDefaultWindowsBindings();
@ -788,13 +752,11 @@ namespace Microsoft.PowerShell
return;
}
#region VI special case
if (_singleton._options.EditMode == EditMode.Vi && key.Value.KeyChar == '0')
{
BeginningOfLine();
return;
}
#endregion VI special case
bool sawDigit = false;
_singleton._statusLinePrompt = "digit-argument: ";

View File

@ -13,6 +13,7 @@ namespace Microsoft.PowerShell
{
public partial class PSConsoleReadLine
{
private const ConsoleColor UnknownColor = (ConsoleColor) (-1);
private CHAR_INFO[] _consoleBuffer;
private int _initialX;
private int _initialY;
@ -464,8 +465,8 @@ namespace Microsoft.PowerShell
// but looks best with the 2 default color schemes - starting PowerShell
// from it's shortcut or from a cmd shortcut.
#if LINUX // TODO: set Inverse attribute and let render choose what to do.
ConsoleColor tempColor = ((int)foregroundColor == -1) ? ConsoleColor.White : foregroundColor;
foregroundColor = ((int)backgroundColor == -1) ? ConsoleColor.Black : backgroundColor;
ConsoleColor tempColor = (foregroundColor == UnknownColor) ? ConsoleColor.White : foregroundColor;
foregroundColor = (backgroundColor == UnknownColor) ? ConsoleColor.Black : backgroundColor;
backgroundColor = tempColor;
#else
foregroundColor = (ConsoleColor)((int)foregroundColor ^ 7);