Skip to content

Commit

Permalink
Remove uses of CSI # S / ScrollConsoleScreenBuffer (#790)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
lzybkr authored Nov 5, 2018
1 parent 14ddf0c commit bc40ae6
Show file tree
Hide file tree
Showing 12 changed files with 85 additions and 119 deletions.
3 changes: 2 additions & 1 deletion PSReadLine/BasicEditing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,8 @@ private bool AcceptLineImpl(bool validate)
}

var point = ConvertOffsetToPoint(_current);
PlaceCursor(0, point.Y + 1);
_console.SetCursorPosition(point.X, point.Y);
_console.Write("\n");
_inputAccepted = true;
return true;
}
Expand Down
1 change: 0 additions & 1 deletion PSReadLine/ConsoleLib.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ public Encoding OutputEncoding
public void SetCursorPosition(int left, int top) => Console.SetCursorPosition(left, top);
public virtual void Write(string value) => Console.Write(value);
public virtual void WriteLine(string value) => Console.WriteLine(value);
public virtual void ScrollBuffer(int lines) => Console.Write("\x1b[" + lines + "S");
public virtual void BlankRestOfLine() => Console.Write("\x1b[K");
}
}
11 changes: 7 additions & 4 deletions PSReadLine/KeyBindings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,8 @@ public static void ShowKeyBindings(ConsoleKeyInfo? key = null, object arg = null

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

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

_singleton.ClearStatusMessage(render: false);

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

_singleton._console.WriteLine(buffer.ToString());
InvokePrompt(key: null, arg: _singleton._console.CursorTop);
console.WriteLine(buffer.ToString());
InvokePrompt(key: null, arg: console.CursorTop);
}
}
}
14 changes: 2 additions & 12 deletions PSReadLine/Movement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -394,18 +394,8 @@ public static void GotoBrace(ConsoleKeyInfo? key = null, object arg = null)
public static void ClearScreen(ConsoleKeyInfo? key = null, object arg = null)
{
var console = _singleton._console;
int newY = _singleton._initialY - _singleton.Options.ExtraPromptLineCount;
if (newY + console.WindowHeight > console.BufferHeight)
{
var scrollCount = newY - console.WindowTop;
console.ScrollBuffer(scrollCount);
_singleton._initialY -= scrollCount;
console.SetCursorPosition(console.CursorLeft, console.CursorTop - scrollCount);
}
else
{
console.SetWindowPosition(0, newY);
}
console.Write("\x1b[2J");
InvokePrompt(null, console.WindowTop);
}

// Try to convert the arg to a char, return 0 for failure
Expand Down
77 changes: 38 additions & 39 deletions PSReadLine/PlatformWindows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.PowerShell;
using Microsoft.PowerShell.Internal;
Expand Down Expand Up @@ -414,6 +415,7 @@ internal class LegacyWin32Console : VirtualTerminal
{
private static ConsoleColor InitialFG = Console.ForegroundColor;
private static ConsoleColor InitialBG = Console.BackgroundColor;
private static int maxTop = 0;

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

Console.Write(s.Substring(from, i - from));
var prefix = s.Substring(from, i - from);
if (prefix.Length > 0)
{
Console.Write(prefix);
maxTop = Console.CursorTop;
}
from = i;

Action action1 = null;
Expand Down Expand Up @@ -492,6 +499,23 @@ private void WriteHelper(string s, bool line)
done = true;
goto case ';';

case 'J':
// We'll only support entire display for ED (Erase in Display)
if (color == 2) {
var cursorVisible = Console.CursorVisible;
var left = Console.CursorLeft;
var toScroll = maxTop - Console.WindowTop + 1;
Console.CursorVisible = false;
Console.SetCursorPosition(0, Console.WindowTop + Console.WindowHeight - 1);
for (int k = 0; k < toScroll; k++)
{
Console.WriteLine();
}
Console.SetCursorPosition(left, Console.WindowTop + toScroll - 1);
Console.CursorVisible = cursorVisible;
}
break;

case ';':
if (VTColorAction.TryGetValue(color, out var action))
{
Expand Down Expand Up @@ -525,8 +549,19 @@ private void WriteHelper(string s, bool line)
}

var tailSegment = s.Substring(from);
if (line) Console.WriteLine(tailSegment);
else Console.Write(tailSegment);
if (line)
{
Console.WriteLine(tailSegment);
maxTop = Console.CursorTop;
}
else
{
Console.Write(tailSegment);
if (tailSegment.Length > 0)
{
maxTop = Console.CursorTop;
}
}
}

public override void Write(string s)
Expand All @@ -539,20 +574,6 @@ public override void WriteLine(string s)
WriteHelper(s, true);
}

public struct SMALL_RECT
{
public short Left;
public short Top;
public short Right;
public short Bottom;
}

internal struct COORD
{
public short X;
public short Y;
}

public struct CHAR_INFO
{
public ushort UnicodeChar;
Expand All @@ -564,28 +585,6 @@ public CHAR_INFO(char c, ConsoleColor foreground, ConsoleColor background)
}
}

[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool ScrollConsoleScreenBuffer(IntPtr hConsoleOutput,
ref SMALL_RECT lpScrollRectangle,
IntPtr lpClipRectangle,
COORD dwDestinationOrigin,
ref CHAR_INFO lpFill);

public override void ScrollBuffer(int lines)
{
var handle = GetStdHandle((uint) StandardHandleId.Output);
var scrollRectangle = new SMALL_RECT
{
Top = (short) lines,
Left = 0,
Bottom = (short)(Console.BufferHeight - 1),
Right = (short)Console.BufferWidth
};
var destinationOrigin = new COORD {X = 0, Y = 0};
var fillChar = new CHAR_INFO(' ', Console.ForegroundColor, Console.BackgroundColor);
ScrollConsoleScreenBuffer(handle, ref scrollRectangle, IntPtr.Zero, destinationOrigin, ref fillChar);
}

public override int CursorSize
{
get => IsConsoleApiAvailable(input: false, output: true) ? Console.CursorSize : _unixCursorSize;
Expand Down
1 change: 0 additions & 1 deletion PSReadLine/PublicAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ public interface IConsole
void SetCursorPosition(int left, int top);
void WriteLine(string s);
void Write(string s);
void ScrollBuffer(int lines);
void BlankRestOfLine();
}

Expand Down
6 changes: 0 additions & 6 deletions PSReadLine/ReadLine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -955,12 +955,6 @@ public static void InvokePrompt(ConsoleKeyInfo? key = null, object arg = null)

if (arg is int newY)
{
if (newY >= console.BufferHeight)
{
var toScroll = newY - console.BufferHeight + 1;
console.ScrollBuffer(toScroll);
newY -= toScroll;
}
console.SetCursorPosition(0, newY);
}
else
Expand Down
21 changes: 5 additions & 16 deletions PSReadLine/Render.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
Expand Down Expand Up @@ -51,6 +52,7 @@ class RenderData
};
private int _initialX;
private int _initialY;

private ConsoleColor _initialForeground;
private ConsoleColor _initialBackground;
private int _current;
Expand Down Expand Up @@ -363,7 +365,7 @@ void UpdateColorsIfNecessary(string newColor)

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

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

var point = ConvertOffsetToPoint(_current);
PlaceCursor(point.X, point.Y);
_console.SetCursorPosition(point.X, point.Y);
_console.CursorVisible = true;

// TODO: set WindowTop if necessary
Expand Down Expand Up @@ -661,19 +663,6 @@ private void RecomputeInitialCoords()
}
}

private void PlaceCursor(int x, int y)
{
int statusLineCount = GetStatusLineCount();
if ((y + statusLineCount) >= _console.BufferHeight)
{
var scrollCount = y + statusLineCount - _console.BufferHeight + 1;
_console.ScrollBuffer(scrollCount);
_initialY -= scrollCount;
y -= scrollCount;
}
_console.SetCursorPosition(x, y);
}

private void MoveCursor(int newCursor)
{
// In case the buffer was resized
Expand All @@ -682,7 +671,7 @@ private void MoveCursor(int newCursor)
_previousRender.bufferHeight = _console.BufferHeight;

var point = ConvertOffsetToPoint(newCursor);
PlaceCursor(point.X, point.Y);
_console.SetCursorPosition(point.X, point.Y);
_current = newCursor;
}

Expand Down
6 changes: 0 additions & 6 deletions PSReadLine/ScreenCapture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,6 @@ internal static void WriteBufferLines(CHAR_INFO[] buffer, int top, IConsole cons

int bufferWidth = Console.BufferWidth;
int bufferLineCount = buffer.Length / bufferWidth;
if ((top + bufferLineCount) > Console.BufferHeight)
{
var scrollCount = (top + bufferLineCount) - Console.BufferHeight;
console.ScrollBuffer(scrollCount);
top -= scrollCount;
}
var bufferSize = new COORD
{
X = (short) bufferWidth,
Expand Down
47 changes: 23 additions & 24 deletions TestPSReadLine/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,8 @@ static void CauseCrash(ConsoleKeyInfo? key = null, object arg = null)
static void Main()
{
var handle = GetStdHandle((uint)StandardHandleId.Output);
uint mode;
GetConsoleMode(handle, out mode);
var b = SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
GetConsoleMode(handle, out var mode);
var vtEnabled = SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);

var iss = InitialSessionState.CreateDefault2();
var rs = RunspaceFactory.CreateRunspace(iss);
Expand All @@ -51,33 +50,33 @@ static void Main()
EditMode = EditMode.Emacs,
HistoryNoDuplicates = false,
});
var options = PSConsoleReadLine.GetOptions();
options.CommandColor = "#8181f7";
options.StringColor = "\x1b[38;5;100m";
PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+LeftArrow"}, PSConsoleReadLine.ShellBackwardWord, "", "");
PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+RightArrow"}, PSConsoleReadLine.ShellNextWord, "", "");
PSConsoleReadLine.SetKeyHandler(new[] {"F4"}, PSConsoleReadLine.HistorySearchBackward, "", "");
PSConsoleReadLine.SetKeyHandler(new[] {"F5"}, PSConsoleReadLine.HistorySearchForward, "", "");
PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+D,Ctrl+C"}, PSConsoleReadLine.CaptureScreen, "", "");
PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+D,Ctrl+P"}, PSConsoleReadLine.InvokePrompt, "", "");
PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+D,Ctrl+X"}, CauseCrash, "", "");
PSConsoleReadLine.SetKeyHandler(new[] {"F6"}, PSConsoleReadLine.PreviousLine, "", "");
PSConsoleReadLine.SetKeyHandler(new[] {"F7"}, PSConsoleReadLine.NextLine, "", "");
PSConsoleReadLine.SetKeyHandler(new[] {"F2"}, PSConsoleReadLine.ValidateAndAcceptLine, "", "");
PSConsoleReadLine.SetKeyHandler(new[] {"Enter"}, PSConsoleReadLine.AcceptLine, "", "");


EngineIntrinsics executionContext;

if (vtEnabled)
{
var options = PSConsoleReadLine.GetOptions();
options.CommandColor = "#8181f7";
options.StringColor = "\x1b[38;5;100m";
}
PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+LeftArrow"}, PSConsoleReadLine.ShellBackwardWord, "ShellBackwardWord", "");
PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+RightArrow"}, PSConsoleReadLine.ShellNextWord, "ShellNextWord", "");
PSConsoleReadLine.SetKeyHandler(new[] {"F4"}, PSConsoleReadLine.HistorySearchBackward, "HistorySearchBackward", "");
PSConsoleReadLine.SetKeyHandler(new[] {"F5"}, PSConsoleReadLine.HistorySearchForward, "HistorySearchForward", "");
PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+d,Ctrl+c"}, PSConsoleReadLine.CaptureScreen, "CaptureScreen", "");
PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+d,Ctrl+p"}, PSConsoleReadLine.InvokePrompt, "InvokePrompt", "");
PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+d,Ctrl+x"}, CauseCrash, "CauseCrash", "Throw exception to test error handling");
PSConsoleReadLine.SetKeyHandler(new[] {"F6"}, PSConsoleReadLine.PreviousLine, "PreviousLine", "");
PSConsoleReadLine.SetKeyHandler(new[] {"F7"}, PSConsoleReadLine.NextLine, "NextLine", "");
PSConsoleReadLine.SetKeyHandler(new[] {"F2"}, PSConsoleReadLine.ValidateAndAcceptLine, "ValidateAndAcceptLine", "");
PSConsoleReadLine.SetKeyHandler(new[] {"Enter"}, PSConsoleReadLine.AcceptLine, "AcceptLine", "");

using (var ps = PowerShell.Create(RunspaceMode.CurrentRunspace))
{
executionContext =
ps.AddScript("$ExecutionContext").Invoke<EngineIntrinsics>().FirstOrDefault();
var executionContext = ps.AddScript("$ExecutionContext").Invoke<EngineIntrinsics>().First();

// Detect if the read loop will enter VT input mode.
var vtInputEnvVar = Environment.GetEnvironmentVariable("PSREADLINE_VTINPUT");
var stdin = GetStdHandle((uint)StandardHandleId.Input);
uint inputMode;
GetConsoleMode(stdin, out inputMode);
GetConsoleMode(stdin, out var inputMode);
if (vtInputEnvVar == "1" || (inputMode & ENABLE_VIRTUAL_TERMINAL_INPUT) != 0)
{
Console.WriteLine("\x1b[33mDefault input mode = virtual terminal\x1b[m");
Expand Down
3 changes: 2 additions & 1 deletion test/CompletionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public void PossibleCompletions()
ps.AddScript($@"function prompt {{ ""{promptLine1}`n{promptLine2}"" }}");
ps.Invoke();
}
PSConsoleReadLine.SetOptions(new SetPSReadLineOption {ExtraPromptLineCount = 1});

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

using (var ps = PowerShell.Create(RunspaceMode.CurrentRunspace))
{
ps.AddCommand("Remove-Item").AddArgument("prompt");
ps.AddCommand("Remove-Item").AddArgument("function:prompt");
ps.Invoke();
}

Expand Down
Loading

0 comments on commit bc40ae6

Please sign in to comment.