diff --git a/PSReadLine/BasicEditing.cs b/PSReadLine/BasicEditing.cs index e96fd87a..600edeca 100644 --- a/PSReadLine/BasicEditing.cs +++ b/PSReadLine/BasicEditing.cs @@ -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; } diff --git a/PSReadLine/ConsoleLib.cs b/PSReadLine/ConsoleLib.cs index 43e307d7..ff8c1e6d 100644 --- a/PSReadLine/ConsoleLib.cs +++ b/PSReadLine/ConsoleLib.cs @@ -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"); } } diff --git a/PSReadLine/KeyBindings.cs b/PSReadLine/KeyBindings.cs index 0c87d56c..27b9100d 100644 --- a/PSReadLine/KeyBindings.cs +++ b/PSReadLine/KeyBindings.cs @@ -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); @@ -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); } } } diff --git a/PSReadLine/Movement.cs b/PSReadLine/Movement.cs index 9dbfdeb0..5cebd7bb 100644 --- a/PSReadLine/Movement.cs +++ b/PSReadLine/Movement.cs @@ -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 diff --git a/PSReadLine/PlatformWindows.cs b/PSReadLine/PlatformWindows.cs index eddbca0a..e62b7776 100644 --- a/PSReadLine/PlatformWindows.cs +++ b/PSReadLine/PlatformWindows.cs @@ -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; @@ -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 VTColorAction = new Dictionary { {40, () => Console.BackgroundColor = ConsoleColor.Black}, @@ -463,7 +465,12 @@ private void WriteHelper(string s, bool line) // The shortest pattern is 4 characters, [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; @@ -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)) { @@ -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) @@ -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; @@ -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; diff --git a/PSReadLine/PublicAPI.cs b/PSReadLine/PublicAPI.cs index c5170753..94137411 100644 --- a/PSReadLine/PublicAPI.cs +++ b/PSReadLine/PublicAPI.cs @@ -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(); } diff --git a/PSReadLine/ReadLine.cs b/PSReadLine/ReadLine.cs index 5dd32271..7133689e 100644 --- a/PSReadLine/ReadLine.cs +++ b/PSReadLine/ReadLine.cs @@ -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 diff --git a/PSReadLine/Render.cs b/PSReadLine/Render.cs index 791c2046..b5a54e2e 100644 --- a/PSReadLine/Render.cs +++ b/PSReadLine/Render.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; @@ -51,6 +52,7 @@ class RenderData }; private int _initialX; private int _initialY; + private ConsoleColor _initialForeground; private ConsoleColor _initialBackground; private int _current; @@ -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; @@ -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 @@ -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 @@ -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; } diff --git a/PSReadLine/ScreenCapture.cs b/PSReadLine/ScreenCapture.cs index b88bda19..a4933ed2 100644 --- a/PSReadLine/ScreenCapture.cs +++ b/PSReadLine/ScreenCapture.cs @@ -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, diff --git a/TestPSReadLine/Program.cs b/TestPSReadLine/Program.cs index 345bbf87..af3c591c 100644 --- a/TestPSReadLine/Program.cs +++ b/TestPSReadLine/Program.cs @@ -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); @@ -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().FirstOrDefault(); + var executionContext = ps.AddScript("$ExecutionContext").Invoke().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"); diff --git a/test/CompletionTest.cs b/test/CompletionTest.cs index dcb32ba2..1ec259fe 100644 --- a/test/CompletionTest.cs +++ b/test/CompletionTest.cs @@ -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( @@ -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(); } diff --git a/test/UnitTestReadLine.cs b/test/UnitTestReadLine.cs index 7675c5db..95daa58c 100644 --- a/test/UnitTestReadLine.cs +++ b/test/UnitTestReadLine.cs @@ -8,7 +8,6 @@ using System.Reflection; using System.Text; using System.Threading; -using System.Windows; using Microsoft.PowerShell; using Microsoft.PowerShell.Internal; using Xunit; @@ -224,6 +223,7 @@ public void WriteLine(string s) CursorTop += 1; } + static readonly char[] endEscapeChars = { 'm', 'J' }; public void Write(string s) { // Crappy code here - no checks for a string that's too long, no scrolling. @@ -235,8 +235,9 @@ public void Write(string s) // Escape sequence - limited support here, and assumed to be well formed. if (s[i+1] != '[') throw new ArgumentException("Unexpected escape sequence", nameof(s)); - var endSequence = s.IndexOf("m", i, StringComparison.Ordinal); - var escapeSequence = s.Substring(i + 2, endSequence - i - 2); + var endSequence = s.IndexOfAny(endEscapeChars, i); + var len = endSequence - i - (s[endSequence] != 'm' ? 1 : 2); + var escapeSequence = s.Substring(i + 2, len); foreach (var subsequence in escapeSequence.Split(';')) { EscapeSequenceActions[subsequence](this); @@ -287,10 +288,6 @@ public void Write(string s) } } - public void ScrollBuffer(int lines) - { - } - public void BlankRestOfLine() { var writePos = CursorTop * BufferWidth + CursorLeft; @@ -372,7 +369,8 @@ private static void ToggleNegative(TestConsole c, bool b) {"0", c => { c.ForegroundColor = DefaultForeground; c.BackgroundColor = DefaultBackground; - }} + }}, + {"2J", c => c.SetCursorPosition(0, 0) } }; }