Skip to content

Commit e79c62f

Browse files
committed
Introduce TTYConsole for UNIX
1 parent 8e23432 commit e79c62f

5 files changed

Lines changed: 238 additions & 101 deletions

File tree

src/Microsoft.PowerShell.PSReadLine/Completion.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,9 +307,9 @@ private static void InvertSelectedCompletion(CHAR_INFO[] buffer, int selectedIte
307307
{
308308
int j = i + start;
309309
#if LINUX // TODO: use real inverse
310-
ConsoleColor tempColor = (int)buffer[j].ForegroundColor == -1
310+
ConsoleColor tempColor = buffer[j].ForegroundColor == UnknownColor
311311
? ConsoleColor.White : buffer[j].ForegroundColor;
312-
buffer[j].ForegroundColor = (int)buffer[j].BackgroundColor == -1
312+
buffer[j].ForegroundColor = buffer[j].BackgroundColor == UnknownColor
313313
? ConsoleColor.Black : buffer[j].BackgroundColor;
314314
buffer[j].BackgroundColor = tempColor;
315315
#else

src/Microsoft.PowerShell.PSReadLine/ConsoleLib.cs

Lines changed: 219 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ public struct CHAR_INFO
317317
public CHAR_INFO(char c, ConsoleColor foreground, ConsoleColor background)
318318
{
319319
UnicodeChar = c;
320-
Attributes = (ushort)(((int)background << 4) | (int)foreground);
320+
Attributes = (ushort)((((int)background << 4) & 0xf) | ((int)foreground & 0xf));
321321
}
322322

323323
[ExcludeFromCodeCoverage]
@@ -330,7 +330,7 @@ public ConsoleColor ForegroundColor
330330
[ExcludeFromCodeCoverage]
331331
public ConsoleColor BackgroundColor
332332
{
333-
get { return (ConsoleColor)((Attributes & 0xf0) >> 4); }
333+
get { return (ConsoleColor)((Attributes & 0x00f0) >> 4); }
334334
set { Attributes = (ushort)((Attributes & 0xff0f) | (((int)value & 0xf) << 4)); }
335335
}
336336

@@ -426,6 +426,7 @@ public static string ToGestureString(this ConsoleKeyInfo key)
426426
}
427427
}
428428

429+
#if !LINUX
429430
internal class ConhostConsole : IConsole
430431
{
431432
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
@@ -482,18 +483,42 @@ internal class ConhostConsole : IConsole
482483
return new SafeFileHandle(handle, true);
483484
});
484485

485-
public uint GetConsoleInputMode()
486+
public object GetConsoleInputMode()
486487
{
488+
uint mode;
487489
var handle = _inputHandle.Value.DangerousGetHandle();
488-
uint result;
489-
NativeMethods.GetConsoleMode(handle, out result);
490-
return result;
490+
NativeMethods.GetConsoleMode(handle, out mode);
491+
return mode;
491492
}
492493

493-
public void SetConsoleInputMode(uint mode)
494+
public void SetConsoleInputMode(object modeObj)
494495
{
495-
var handle = _inputHandle.Value.DangerousGetHandle();
496-
NativeMethods.SetConsoleMode(handle, mode);
496+
if (modeObj is uint)
497+
{
498+
// Clear a couple flags so we can actually receive certain keys:
499+
// ENABLE_PROCESSED_INPUT - enables Ctrl+C
500+
// ENABLE_LINE_INPUT - enables Ctrl+S
501+
// Also clear a couple flags so we don't mask the input that we ignore:
502+
// ENABLE_MOUSE_INPUT - mouse events
503+
// ENABLE_WINDOW_INPUT - window resize events
504+
var mode = (uint)modeObj &
505+
~(NativeMethods.ENABLE_PROCESSED_INPUT |
506+
NativeMethods.ENABLE_LINE_INPUT |
507+
NativeMethods.ENABLE_WINDOW_INPUT |
508+
NativeMethods.ENABLE_MOUSE_INPUT);
509+
510+
var handle = _inputHandle.Value.DangerousGetHandle();
511+
NativeMethods.SetConsoleMode(handle, mode);
512+
}
513+
}
514+
515+
public void RestoreConsoleInputMode(object modeObj)
516+
{
517+
if (modeObj is uint)
518+
{
519+
var handle = _inputHandle.Value.DangerousGetHandle();
520+
NativeMethods.SetConsoleMode(handle, (uint)modeObj);
521+
}
497522
}
498523

499524
public ConsoleKeyInfo ReadKey()
@@ -601,24 +626,7 @@ public void WriteBufferLines(CHAR_INFO[] buffer, ref int top, bool ensureBottomL
601626
ScrollBuffer(scrollCount);
602627
top -= scrollCount;
603628
}
604-
#if LINUX
605-
ConsoleColor foregroundColor = Console.ForegroundColor;
606-
ConsoleColor backgroundColor = Console.BackgroundColor;
607-
608-
Console.SetCursorPosition(0, (top>=0) ? top : 0);
609-
610-
for (int i = 0; i < buffer.Length; ++i)
611-
{
612-
// TODO: use escape sequences for better perf
613-
Console.ForegroundColor = buffer[i].ForegroundColor;
614-
Console.BackgroundColor = buffer[i].BackgroundColor;
615-
616-
Console.Write((char)buffer[i].UnicodeChar);
617-
}
618629

619-
Console.BackgroundColor = backgroundColor;
620-
Console.ForegroundColor = foregroundColor;
621-
#else
622630
var handle = NativeMethods.GetStdHandle((uint) StandardHandleId.Output);
623631
var bufferSize = new COORD
624632
{
@@ -643,18 +651,10 @@ public void WriteBufferLines(CHAR_INFO[] buffer, ref int top, bool ensureBottomL
643651
{
644652
Console.CursorTop = bottom;
645653
}
646-
#endif
647654
}
648655

649656
public void ScrollBuffer(int lines)
650657
{
651-
#if LINUX
652-
for (int i=0; i<lines; ++i)
653-
{
654-
Console.SetCursorPosition(Console.BufferWidth, Console.BufferHeight - 1);
655-
Console.WriteLine();
656-
}
657-
#else
658658
var handle = NativeMethods.GetStdHandle((uint) StandardHandleId.Output);
659659

660660
var scrollRectangle = new SMALL_RECT
@@ -667,20 +667,11 @@ public void ScrollBuffer(int lines)
667667
var destinationOrigin = new COORD {X = 0, Y = 0};
668668
var fillChar = new CHAR_INFO(' ', Console.ForegroundColor, Console.BackgroundColor);
669669
NativeMethods.ScrollConsoleScreenBuffer(handle, ref scrollRectangle, IntPtr.Zero, destinationOrigin, ref fillChar);
670-
#endif
671670
}
672671

673672
public CHAR_INFO[] ReadBufferLines(int top, int count)
674673
{
675674
var result = new CHAR_INFO[BufferWidth * count];
676-
#if LINUX
677-
for (int i=0; i<BufferWidth*count; ++i)
678-
{
679-
result[i].UnicodeChar = ' ';
680-
result[i].ForegroundColor = Console.ForegroundColor;
681-
result[i].BackgroundColor = Console.BackgroundColor;
682-
}
683-
#else
684675
var handle = NativeMethods.GetStdHandle((uint) StandardHandleId.Output);
685676

686677
var readBufferSize = new COORD {
@@ -696,7 +687,7 @@ public CHAR_INFO[] ReadBufferLines(int top, int count)
696687
};
697688
NativeMethods.ReadConsoleOutput(handle, result,
698689
readBufferSize, readBufferCoord, ref readRegion);
699-
#endif
690+
700691
return result;
701692
}
702693

@@ -891,14 +882,196 @@ public void EndRender()
891882
NativeMethods.ReleaseDC(_hwnd, _hDC);
892883
}
893884
}
885+
}
886+
887+
#else
888+
889+
internal class TTYConsole : IConsole
890+
{
891+
public object GetConsoleInputMode()
892+
{
893+
return Console.TreatControlCAsInput;
894+
}
894895

895-
#if LINUX // TODO: this is not correct, PSReadline never clears the screen, it should only scroll
896+
public void SetConsoleInputMode(object modeObj)
897+
{
898+
Console.TreatControlCAsInput = true;
899+
}
900+
901+
public void RestoreConsoleInputMode(object modeObj)
902+
{
903+
if (modeObj is bool)
904+
{
905+
Console.TreatControlCAsInput = (bool)modeObj;
906+
}
907+
}
908+
909+
public ConsoleKeyInfo ReadKey()
910+
{
911+
return Console.ReadKey(true);
912+
}
913+
914+
public bool KeyAvailable
915+
{
916+
get { return Console.KeyAvailable; }
917+
}
918+
919+
public int CursorLeft
920+
{
921+
get { return Console.CursorLeft; }
922+
set { Console.CursorLeft = value; }
923+
}
924+
925+
public int CursorTop
926+
{
927+
get { return Console.CursorTop; }
928+
set { Console.CursorTop = value; }
929+
}
930+
931+
public int CursorSize
932+
{
933+
get { return Console.CursorSize; }
934+
set { Console.CursorSize = value; }
935+
}
936+
937+
public int BufferWidth
938+
{
939+
get { return Console.BufferWidth; }
940+
set { Console.BufferWidth = value; }
941+
}
942+
943+
public int BufferHeight
944+
{
945+
get { return Console.BufferHeight; }
946+
set { Console.BufferHeight = value; }
947+
}
948+
949+
public int WindowWidth
950+
{
951+
get { return Console.WindowWidth; }
952+
set { Console.WindowWidth = value; }
953+
}
954+
955+
public int WindowHeight
956+
{
957+
get { return Console.WindowHeight; }
958+
set { Console.WindowHeight = value; }
959+
}
960+
961+
public int WindowTop
962+
{
963+
get { return Console.WindowTop; }
964+
set { Console.WindowTop = value; }
965+
}
966+
967+
public ConsoleColor BackgroundColor
968+
{
969+
get { return Console.BackgroundColor; }
970+
set { Console.BackgroundColor = value; }
971+
}
972+
973+
public ConsoleColor ForegroundColor
974+
{
975+
get { return Console.ForegroundColor; }
976+
set { Console.ForegroundColor = value; }
977+
}
978+
979+
public void SetWindowPosition(int left, int top)
980+
{
981+
Console.SetWindowPosition(left, top);
982+
}
983+
984+
public void SetCursorPosition(int left, int top)
985+
{
986+
Console.SetCursorPosition(left, top);
987+
}
988+
989+
public void Write(string value)
990+
{
991+
Console.Write(value);
992+
}
993+
994+
public void WriteLine(string value)
995+
{
996+
Console.WriteLine(value);
997+
}
998+
999+
public void WriteBufferLines(CHAR_INFO[] buffer, ref int top)
1000+
{
1001+
WriteBufferLines(buffer, ref top, true);
1002+
}
1003+
1004+
public void WriteBufferLines(CHAR_INFO[] buffer, ref int top, bool ensureBottomLineVisible)
1005+
{
1006+
int bufferWidth = Console.BufferWidth;
1007+
int bufferLineCount = buffer.Length / bufferWidth;
1008+
if ((top + bufferLineCount) > Console.BufferHeight)
1009+
{
1010+
var scrollCount = (top + bufferLineCount) - Console.BufferHeight;
1011+
ScrollBuffer(scrollCount);
1012+
top -= scrollCount;
1013+
}
1014+
1015+
ConsoleColor foregroundColor = Console.ForegroundColor;
1016+
ConsoleColor backgroundColor = Console.BackgroundColor;
1017+
1018+
Console.SetCursorPosition(0, (top>=0) ? top : 0);
1019+
1020+
for (int i = 0; i < buffer.Length; ++i)
1021+
{
1022+
// TODO: use escape sequences for better perf
1023+
Console.ForegroundColor = buffer[i].ForegroundColor;
1024+
Console.BackgroundColor = buffer[i].BackgroundColor;
1025+
1026+
Console.Write((char)buffer[i].UnicodeChar);
1027+
}
1028+
1029+
Console.BackgroundColor = backgroundColor;
1030+
Console.ForegroundColor = foregroundColor;
1031+
}
1032+
1033+
public void ScrollBuffer(int lines)
1034+
{
1035+
for (int i=0; i<lines; ++i)
1036+
{
1037+
Console.SetCursorPosition(Console.BufferWidth, Console.BufferHeight - 1);
1038+
Console.WriteLine();
1039+
}
1040+
}
1041+
1042+
public CHAR_INFO[] ReadBufferLines(int top, int count)
1043+
{
1044+
var result = new CHAR_INFO[BufferWidth * count];
1045+
for (int i=0; i<BufferWidth*count; ++i)
1046+
{
1047+
result[i].UnicodeChar = ' ';
1048+
result[i].ForegroundColor = Console.ForegroundColor;
1049+
result[i].BackgroundColor = Console.BackgroundColor;
1050+
}
1051+
return result;
1052+
}
1053+
1054+
public int LengthInBufferCells(char c)
1055+
{
1056+
return 1;
1057+
}
1058+
1059+
1060+
public void StartRender()
1061+
{
1062+
}
1063+
1064+
public void EndRender()
1065+
{
1066+
}
1067+
1068+
// TODO: this is not correct, PSReadline never clears the screen, it should only scroll
8961069
public void Clear()
8971070
{
8981071
Console.Clear();
8991072
}
900-
#endif
9011073
}
1074+
#endif
9021075

9031076
#if CORECLR // TODO: remove if CORECLR adds this attribute back
9041077
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event)]

src/Microsoft.PowerShell.PSReadLine/PublicAPI.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ public interface IPSConsoleReadLineMockableMethods
2828
[SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")]
2929
public interface IConsole
3030
{
31-
uint GetConsoleInputMode();
32-
void SetConsoleInputMode(uint mode);
31+
object GetConsoleInputMode();
32+
void SetConsoleInputMode(object mode);
33+
void RestoreConsoleInputMode(object mode);
3334
ConsoleKeyInfo ReadKey();
3435
bool KeyAvailable { get; }
3536
int CursorLeft { get; set; }

0 commit comments

Comments
 (0)