Skip to content

Commit bc40ae6

Browse files
authored
Remove uses of CSI # S / ScrollConsoleScreenBuffer (#790)
This fixes some weird issues in WSL and some Linux terminals. This also works around a Windows 1809 Console bug for the Ctrl+l binding to clear the screen by using the same escape sequence that bash uses. Fix #724
1 parent 14ddf0c commit bc40ae6

12 files changed

+85
-119
lines changed

PSReadLine/BasicEditing.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,8 @@ private bool AcceptLineImpl(bool validate)
270270
}
271271

272272
var point = ConvertOffsetToPoint(_current);
273-
PlaceCursor(0, point.Y + 1);
273+
_console.SetCursorPosition(point.X, point.Y);
274+
_console.Write("\n");
274275
_inputAccepted = true;
275276
return true;
276277
}

PSReadLine/ConsoleLib.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@ public Encoding OutputEncoding
113113
public void SetCursorPosition(int left, int top) => Console.SetCursorPosition(left, top);
114114
public virtual void Write(string value) => Console.Write(value);
115115
public virtual void WriteLine(string value) => Console.WriteLine(value);
116-
public virtual void ScrollBuffer(int lines) => Console.Write("\x1b[" + lines + "S");
117116
public virtual void BlankRestOfLine() => Console.Write("\x1b[K");
118117
}
119118
}

PSReadLine/KeyBindings.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,8 @@ public static void ShowKeyBindings(ConsoleKeyInfo? key = null, object arg = null
624624

625625
// Don't overwrite any of the line - so move to first line after the end of our buffer.
626626
var point = _singleton.ConvertOffsetToPoint(_singleton._buffer.Length);
627-
_singleton.PlaceCursor(0, point.Y + 1);
627+
console.SetCursorPosition(point.X, point.Y);
628+
console.Write("\n");
628629

629630
console.WriteLine(buffer.ToString());
630631
InvokePrompt(key: null, arg: _singleton._console.CursorTop);
@@ -677,12 +678,14 @@ public static void WhatIsKey(ConsoleKeyInfo? key = null, object arg = null)
677678

678679
_singleton.ClearStatusMessage(render: false);
679680

681+
var console = _singleton._console;
680682
// Don't overwrite any of the line - so move to first line after the end of our buffer.
681683
var point = _singleton.ConvertOffsetToPoint(_singleton._buffer.Length);
682-
_singleton.PlaceCursor(0, point.Y + 1);
684+
console.SetCursorPosition(point.X, point.Y);
685+
console.Write("\n");
683686

684-
_singleton._console.WriteLine(buffer.ToString());
685-
InvokePrompt(key: null, arg: _singleton._console.CursorTop);
687+
console.WriteLine(buffer.ToString());
688+
InvokePrompt(key: null, arg: console.CursorTop);
686689
}
687690
}
688691
}

PSReadLine/Movement.cs

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -394,18 +394,8 @@ public static void GotoBrace(ConsoleKeyInfo? key = null, object arg = null)
394394
public static void ClearScreen(ConsoleKeyInfo? key = null, object arg = null)
395395
{
396396
var console = _singleton._console;
397-
int newY = _singleton._initialY - _singleton.Options.ExtraPromptLineCount;
398-
if (newY + console.WindowHeight > console.BufferHeight)
399-
{
400-
var scrollCount = newY - console.WindowTop;
401-
console.ScrollBuffer(scrollCount);
402-
_singleton._initialY -= scrollCount;
403-
console.SetCursorPosition(console.CursorLeft, console.CursorTop - scrollCount);
404-
}
405-
else
406-
{
407-
console.SetWindowPosition(0, newY);
408-
}
397+
console.Write("\x1b[2J");
398+
InvokePrompt(null, console.WindowTop);
409399
}
410400

411401
// Try to convert the arg to a char, return 0 for failure

PSReadLine/PlatformWindows.cs

Lines changed: 38 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System;
66
using System.Collections.Generic;
77
using System.ComponentModel;
8+
using System.Diagnostics;
89
using System.Runtime.InteropServices;
910
using Microsoft.PowerShell;
1011
using Microsoft.PowerShell.Internal;
@@ -414,6 +415,7 @@ internal class LegacyWin32Console : VirtualTerminal
414415
{
415416
private static ConsoleColor InitialFG = Console.ForegroundColor;
416417
private static ConsoleColor InitialBG = Console.BackgroundColor;
418+
private static int maxTop = 0;
417419

418420
private static readonly Dictionary<int, Action> VTColorAction = new Dictionary<int, Action> {
419421
{40, () => Console.BackgroundColor = ConsoleColor.Black},
@@ -463,7 +465,12 @@ private void WriteHelper(string s, bool line)
463465
// The shortest pattern is 4 characters, <ESC>[0m
464466
if (s[i] != '\x1b' || (i + 3) >= s.Length || s[i + 1] != '[') continue;
465467

466-
Console.Write(s.Substring(from, i - from));
468+
var prefix = s.Substring(from, i - from);
469+
if (prefix.Length > 0)
470+
{
471+
Console.Write(prefix);
472+
maxTop = Console.CursorTop;
473+
}
467474
from = i;
468475

469476
Action action1 = null;
@@ -492,6 +499,23 @@ private void WriteHelper(string s, bool line)
492499
done = true;
493500
goto case ';';
494501

502+
case 'J':
503+
// We'll only support entire display for ED (Erase in Display)
504+
if (color == 2) {
505+
var cursorVisible = Console.CursorVisible;
506+
var left = Console.CursorLeft;
507+
var toScroll = maxTop - Console.WindowTop + 1;
508+
Console.CursorVisible = false;
509+
Console.SetCursorPosition(0, Console.WindowTop + Console.WindowHeight - 1);
510+
for (int k = 0; k < toScroll; k++)
511+
{
512+
Console.WriteLine();
513+
}
514+
Console.SetCursorPosition(left, Console.WindowTop + toScroll - 1);
515+
Console.CursorVisible = cursorVisible;
516+
}
517+
break;
518+
495519
case ';':
496520
if (VTColorAction.TryGetValue(color, out var action))
497521
{
@@ -525,8 +549,19 @@ private void WriteHelper(string s, bool line)
525549
}
526550

527551
var tailSegment = s.Substring(from);
528-
if (line) Console.WriteLine(tailSegment);
529-
else Console.Write(tailSegment);
552+
if (line)
553+
{
554+
Console.WriteLine(tailSegment);
555+
maxTop = Console.CursorTop;
556+
}
557+
else
558+
{
559+
Console.Write(tailSegment);
560+
if (tailSegment.Length > 0)
561+
{
562+
maxTop = Console.CursorTop;
563+
}
564+
}
530565
}
531566

532567
public override void Write(string s)
@@ -539,20 +574,6 @@ public override void WriteLine(string s)
539574
WriteHelper(s, true);
540575
}
541576

542-
public struct SMALL_RECT
543-
{
544-
public short Left;
545-
public short Top;
546-
public short Right;
547-
public short Bottom;
548-
}
549-
550-
internal struct COORD
551-
{
552-
public short X;
553-
public short Y;
554-
}
555-
556577
public struct CHAR_INFO
557578
{
558579
public ushort UnicodeChar;
@@ -564,28 +585,6 @@ public CHAR_INFO(char c, ConsoleColor foreground, ConsoleColor background)
564585
}
565586
}
566587

567-
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
568-
public static extern bool ScrollConsoleScreenBuffer(IntPtr hConsoleOutput,
569-
ref SMALL_RECT lpScrollRectangle,
570-
IntPtr lpClipRectangle,
571-
COORD dwDestinationOrigin,
572-
ref CHAR_INFO lpFill);
573-
574-
public override void ScrollBuffer(int lines)
575-
{
576-
var handle = GetStdHandle((uint) StandardHandleId.Output);
577-
var scrollRectangle = new SMALL_RECT
578-
{
579-
Top = (short) lines,
580-
Left = 0,
581-
Bottom = (short)(Console.BufferHeight - 1),
582-
Right = (short)Console.BufferWidth
583-
};
584-
var destinationOrigin = new COORD {X = 0, Y = 0};
585-
var fillChar = new CHAR_INFO(' ', Console.ForegroundColor, Console.BackgroundColor);
586-
ScrollConsoleScreenBuffer(handle, ref scrollRectangle, IntPtr.Zero, destinationOrigin, ref fillChar);
587-
}
588-
589588
public override int CursorSize
590589
{
591590
get => IsConsoleApiAvailable(input: false, output: true) ? Console.CursorSize : _unixCursorSize;

PSReadLine/PublicAPI.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ public interface IConsole
4848
void SetCursorPosition(int left, int top);
4949
void WriteLine(string s);
5050
void Write(string s);
51-
void ScrollBuffer(int lines);
5251
void BlankRestOfLine();
5352
}
5453

PSReadLine/ReadLine.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -955,12 +955,6 @@ public static void InvokePrompt(ConsoleKeyInfo? key = null, object arg = null)
955955

956956
if (arg is int newY)
957957
{
958-
if (newY >= console.BufferHeight)
959-
{
960-
var toScroll = newY - console.BufferHeight + 1;
961-
console.ScrollBuffer(toScroll);
962-
newY -= toScroll;
963-
}
964958
console.SetCursorPosition(0, newY);
965959
}
966960
else

PSReadLine/Render.cs

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
using System;
66
using System.Collections.Generic;
7+
using System.Diagnostics;
78
using System.Diagnostics.CodeAnalysis;
89
using System.Globalization;
910
using System.Linq;
@@ -51,6 +52,7 @@ class RenderData
5152
};
5253
private int _initialX;
5354
private int _initialY;
55+
5456
private ConsoleColor _initialForeground;
5557
private ConsoleColor _initialBackground;
5658
private int _current;
@@ -363,7 +365,7 @@ void UpdateColorsIfNecessary(string newColor)
363365

364366
// Move the cursor to where we started, but make cursor invisible while we're rendering.
365367
_console.CursorVisible = false;
366-
PlaceCursor(_initialX, _initialY);
368+
_console.SetCursorPosition(_initialX, _initialY);
367369

368370
// Possibly need to flip the color on the prompt if the error state changed.
369371
var promptText = _options.PromptText;
@@ -512,7 +514,7 @@ int PhysicalLineCount(int columns, bool isFirstLogicalLine, out int lenLastPhysi
512514
}
513515

514516
var point = ConvertOffsetToPoint(_current);
515-
PlaceCursor(point.X, point.Y);
517+
_console.SetCursorPosition(point.X, point.Y);
516518
_console.CursorVisible = true;
517519

518520
// TODO: set WindowTop if necessary
@@ -661,19 +663,6 @@ private void RecomputeInitialCoords()
661663
}
662664
}
663665

664-
private void PlaceCursor(int x, int y)
665-
{
666-
int statusLineCount = GetStatusLineCount();
667-
if ((y + statusLineCount) >= _console.BufferHeight)
668-
{
669-
var scrollCount = y + statusLineCount - _console.BufferHeight + 1;
670-
_console.ScrollBuffer(scrollCount);
671-
_initialY -= scrollCount;
672-
y -= scrollCount;
673-
}
674-
_console.SetCursorPosition(x, y);
675-
}
676-
677666
private void MoveCursor(int newCursor)
678667
{
679668
// In case the buffer was resized
@@ -682,7 +671,7 @@ private void MoveCursor(int newCursor)
682671
_previousRender.bufferHeight = _console.BufferHeight;
683672

684673
var point = ConvertOffsetToPoint(newCursor);
685-
PlaceCursor(point.X, point.Y);
674+
_console.SetCursorPosition(point.X, point.Y);
686675
_current = newCursor;
687676
}
688677

PSReadLine/ScreenCapture.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -161,12 +161,6 @@ internal static void WriteBufferLines(CHAR_INFO[] buffer, int top, IConsole cons
161161

162162
int bufferWidth = Console.BufferWidth;
163163
int bufferLineCount = buffer.Length / bufferWidth;
164-
if ((top + bufferLineCount) > Console.BufferHeight)
165-
{
166-
var scrollCount = (top + bufferLineCount) - Console.BufferHeight;
167-
console.ScrollBuffer(scrollCount);
168-
top -= scrollCount;
169-
}
170164
var bufferSize = new COORD
171165
{
172166
X = (short) bufferWidth,

TestPSReadLine/Program.cs

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,8 @@ static void CauseCrash(ConsoleKeyInfo? key = null, object arg = null)
3737
static void Main()
3838
{
3939
var handle = GetStdHandle((uint)StandardHandleId.Output);
40-
uint mode;
41-
GetConsoleMode(handle, out mode);
42-
var b = SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
40+
GetConsoleMode(handle, out var mode);
41+
var vtEnabled = SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
4342

4443
var iss = InitialSessionState.CreateDefault2();
4544
var rs = RunspaceFactory.CreateRunspace(iss);
@@ -51,33 +50,33 @@ static void Main()
5150
EditMode = EditMode.Emacs,
5251
HistoryNoDuplicates = false,
5352
});
54-
var options = PSConsoleReadLine.GetOptions();
55-
options.CommandColor = "#8181f7";
56-
options.StringColor = "\x1b[38;5;100m";
57-
PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+LeftArrow"}, PSConsoleReadLine.ShellBackwardWord, "", "");
58-
PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+RightArrow"}, PSConsoleReadLine.ShellNextWord, "", "");
59-
PSConsoleReadLine.SetKeyHandler(new[] {"F4"}, PSConsoleReadLine.HistorySearchBackward, "", "");
60-
PSConsoleReadLine.SetKeyHandler(new[] {"F5"}, PSConsoleReadLine.HistorySearchForward, "", "");
61-
PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+D,Ctrl+C"}, PSConsoleReadLine.CaptureScreen, "", "");
62-
PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+D,Ctrl+P"}, PSConsoleReadLine.InvokePrompt, "", "");
63-
PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+D,Ctrl+X"}, CauseCrash, "", "");
64-
PSConsoleReadLine.SetKeyHandler(new[] {"F6"}, PSConsoleReadLine.PreviousLine, "", "");
65-
PSConsoleReadLine.SetKeyHandler(new[] {"F7"}, PSConsoleReadLine.NextLine, "", "");
66-
PSConsoleReadLine.SetKeyHandler(new[] {"F2"}, PSConsoleReadLine.ValidateAndAcceptLine, "", "");
67-
PSConsoleReadLine.SetKeyHandler(new[] {"Enter"}, PSConsoleReadLine.AcceptLine, "", "");
68-
69-
70-
EngineIntrinsics executionContext;
53+
54+
if (vtEnabled)
55+
{
56+
var options = PSConsoleReadLine.GetOptions();
57+
options.CommandColor = "#8181f7";
58+
options.StringColor = "\x1b[38;5;100m";
59+
}
60+
PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+LeftArrow"}, PSConsoleReadLine.ShellBackwardWord, "ShellBackwardWord", "");
61+
PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+RightArrow"}, PSConsoleReadLine.ShellNextWord, "ShellNextWord", "");
62+
PSConsoleReadLine.SetKeyHandler(new[] {"F4"}, PSConsoleReadLine.HistorySearchBackward, "HistorySearchBackward", "");
63+
PSConsoleReadLine.SetKeyHandler(new[] {"F5"}, PSConsoleReadLine.HistorySearchForward, "HistorySearchForward", "");
64+
PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+d,Ctrl+c"}, PSConsoleReadLine.CaptureScreen, "CaptureScreen", "");
65+
PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+d,Ctrl+p"}, PSConsoleReadLine.InvokePrompt, "InvokePrompt", "");
66+
PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+d,Ctrl+x"}, CauseCrash, "CauseCrash", "Throw exception to test error handling");
67+
PSConsoleReadLine.SetKeyHandler(new[] {"F6"}, PSConsoleReadLine.PreviousLine, "PreviousLine", "");
68+
PSConsoleReadLine.SetKeyHandler(new[] {"F7"}, PSConsoleReadLine.NextLine, "NextLine", "");
69+
PSConsoleReadLine.SetKeyHandler(new[] {"F2"}, PSConsoleReadLine.ValidateAndAcceptLine, "ValidateAndAcceptLine", "");
70+
PSConsoleReadLine.SetKeyHandler(new[] {"Enter"}, PSConsoleReadLine.AcceptLine, "AcceptLine", "");
71+
7172
using (var ps = PowerShell.Create(RunspaceMode.CurrentRunspace))
7273
{
73-
executionContext =
74-
ps.AddScript("$ExecutionContext").Invoke<EngineIntrinsics>().FirstOrDefault();
74+
var executionContext = ps.AddScript("$ExecutionContext").Invoke<EngineIntrinsics>().First();
7575

7676
// Detect if the read loop will enter VT input mode.
7777
var vtInputEnvVar = Environment.GetEnvironmentVariable("PSREADLINE_VTINPUT");
7878
var stdin = GetStdHandle((uint)StandardHandleId.Input);
79-
uint inputMode;
80-
GetConsoleMode(stdin, out inputMode);
79+
GetConsoleMode(stdin, out var inputMode);
8180
if (vtInputEnvVar == "1" || (inputMode & ENABLE_VIRTUAL_TERMINAL_INPUT) != 0)
8281
{
8382
Console.WriteLine("\x1b[33mDefault input mode = virtual terminal\x1b[m");

test/CompletionTest.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public void PossibleCompletions()
8787
ps.AddScript($@"function prompt {{ ""{promptLine1}`n{promptLine2}"" }}");
8888
ps.Invoke();
8989
}
90+
PSConsoleReadLine.SetOptions(new SetPSReadLineOption {ExtraPromptLineCount = 1});
9091

9192
_console.Clear();
9293
Test("psvar", Keys(
@@ -108,7 +109,7 @@ public void PossibleCompletions()
108109

109110
using (var ps = PowerShell.Create(RunspaceMode.CurrentRunspace))
110111
{
111-
ps.AddCommand("Remove-Item").AddArgument("prompt");
112+
ps.AddCommand("Remove-Item").AddArgument("function:prompt");
112113
ps.Invoke();
113114
}
114115

0 commit comments

Comments
 (0)