From 599349db4f3ae99315d0eaa9ea55b46bc41c12d5 Mon Sep 17 00:00:00 2001 From: Jason Shirk Date: Tue, 2 Oct 2018 22:00:19 -0700 Subject: [PATCH 1/2] Fix showing tooltips at bottom of buffer --- PSReadLine/Completion.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 98946458..5b4ff6c6 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -579,7 +579,7 @@ public void UpdateMenuSelection(int selectedItem, bool select, bool showTooltips var toolTipLines = 2; if (showTooltips) { - // Determine if showing the tooltip would scroll the screen, if so, also don't show it. + // Determine if showing the tooltip would scroll the top of our buffer off the screen. int lineLength = 0; for (var i = 0; i < toolTip.Length; i++) @@ -607,8 +607,8 @@ public void UpdateMenuSelection(int selectedItem, bool select, bool showTooltips } } - // The +1 is for a new line after showing the tool tips - if ((Top + Rows + toolTipLines + 1) > console.WindowHeight) + // The +1 is for the blank line between the menu and tooltips. + if (BufferLines + Rows + toolTipLines + 1 > console.WindowHeight) { showTooltips = false; } From cd25b1c85552c36f116bc3021f7ebeff11a8cba9 Mon Sep 17 00:00:00 2001 From: Jason shirk Date: Mon, 22 Oct 2018 21:33:28 -0700 Subject: [PATCH 2/2] Fix tooltips and menu issues at bottom of buffer A recent PR disabled the display of tooltips completely, and there were some problems with the menu display when the command line was at the bottom of the buffer and scrolling was necessary. Fix #763 and probably #765 --- PSReadLine/Completion.cs | 123 +++++++++++++++++++++------------------ PSReadLine/ConsoleLib.cs | 10 ---- PSReadLine/PublicAPI.cs | 3 - test/UnitTestReadLine.cs | 13 ----- 4 files changed, 65 insertions(+), 84 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 5b4ff6c6..5ec01f52 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -113,14 +113,6 @@ private static string GetUnquotedText(CompletionResult match, bool consistentQuo return GetUnquotedText(s, consistentQuoting); } - private void WriteBlankLines(int top, int count) - { - _console.SaveCursor(); - _console.SetCursorPosition(0, top); - WriteBlankLines(count); - _console.RestoreCursor(); - } - private void WriteBlankLines(int count) { var spaces = Spaces(_console.BufferWidth); @@ -464,48 +456,20 @@ private class Menu internal CompletionResult CurrentMenuItem => MenuItems[CurrentSelection]; internal int CurrentSelection; - void EnsureMenuAndInputIsVisible(IConsole console, int tooltipLineCount) - { - // The +1 lets us write a newline after the last row, which isn't strictly necessary - // It does help with: - // * Console selecting multiple lines of text - // * Adds a little extra space underneath the menu - var bottom = this.Top + this.Rows + tooltipLineCount + 1; - if (bottom > console.BufferHeight) - { - var toScroll = bottom - console.BufferHeight; - console.ScrollBuffer(toScroll); - Singleton._initialY -= toScroll; - this.Top -= toScroll; - - var point = Singleton.ConvertOffsetToPoint(Singleton._current); - Singleton.PlaceCursor(point.X, point.Y); - } - } - public void DrawMenu(Menu previousMenu, bool menuSelect) { IConsole console = Singleton._console; // Move cursor to the start of the first line after our input. - this.Top = Singleton.ConvertOffsetToPoint(Singleton._buffer.Length).Y + 1; + var bufferEndPoint = Singleton.ConvertOffsetToPoint(Singleton._buffer.Length); + console.SetCursorPosition(bufferEndPoint.X, bufferEndPoint.Y); + AdjustForPossibleScroll(1); + MoveCursorDown(1); + this.Top = bufferEndPoint.Y + 1; if (menuSelect) { - EnsureMenuAndInputIsVisible(console, tooltipLineCount: 0); - console.CursorVisible = false; - console.SaveCursor(); - console.SetCursorPosition(0, this.Top); - } - else - { - // Start a new line to show the menu contents - console.SetCursorPosition(0, this.Top); - // Scroll one line up if the cursor is at the bottom of the window - if (this.Top == console.BufferHeight) - { - console.Write("\n"); - } + SaveCursor(); } var bufferWidth = console.BufferWidth; @@ -533,8 +497,12 @@ public void DrawMenu(Menu previousMenu, bool menuSelect) console.BlankRestOfLine(); } - // Explicit newline so consoles see each row as distinct lines. - console.Write("\n"); + // Explicit newline so consoles see each row as distinct lines, but skip the + // last line so we don't scroll. + if (row != (this.Rows - 1) || !menuSelect) { + AdjustForPossibleScroll(1); + MoveCursorDown(1); + } } if (previousMenu != null) @@ -547,14 +515,14 @@ public void DrawMenu(Menu previousMenu, bool menuSelect) if (menuSelect) { - console.RestoreCursor(); + RestoreCursor(); console.CursorVisible = true; } } public void Clear() { - Singleton.WriteBlankLines(Top, Rows + ToolTipLines); + WriteBlankLines(Top, Rows + ToolTipLines); } public void UpdateMenuSelection(int selectedItem, bool select, bool showTooltips, string toolTipColor) @@ -614,12 +582,7 @@ public void UpdateMenuSelection(int selectedItem, bool select, bool showTooltips } } - if (showTooltips) - { - EnsureMenuAndInputIsVisible(console, toolTipLines); - } - - console.SaveCursor(); + SaveCursor(); var row = Top + selectedItem % Rows; var col = ColumnWidth * (selectedItem / Rows); @@ -634,7 +597,10 @@ public void UpdateMenuSelection(int selectedItem, bool select, bool showTooltips if (showTooltips) { Debug.Assert(select, "On unselect, caller must clear the tooltip"); - console.SetCursorPosition(0, Top + Rows + 1); + console.SetCursorPosition(0, Top + Rows - 1); + // Move down 2 so we have 1 blank line between the menu and buffer. + AdjustForPossibleScroll(toolTipLines); + MoveCursorDown(2); console.Write(toolTipColor); console.Write(toolTip); ToolTipLines = toolTipLines; @@ -642,7 +608,7 @@ public void UpdateMenuSelection(int selectedItem, bool select, bool showTooltips console.Write("\x1b[0m"); } - console.RestoreCursor(); + RestoreCursor(); } public void MoveRight() => CurrentSelection = Math.Min(CurrentSelection + Rows, MenuItems.Count - 1); @@ -661,6 +627,47 @@ public void MoveN(int n) CurrentSelection += MenuItems.Count; } } + + private void MoveCursorDown(int cnt) + { + IConsole console = Singleton._console; + while (cnt-- > 0) + { + console.Write("\n"); + } + } + + private void AdjustForPossibleScroll(int cnt) + { + IConsole console = Singleton._console; + var scrollCnt = console.CursorTop + cnt + 1 - console.BufferHeight; + if (scrollCnt > 0) + { + Top -= scrollCnt; + _singleton._initialY -= scrollCnt; + _savedCursorTop -= scrollCnt; + } + } + + public void WriteBlankLines(int top, int count) + { + SaveCursor(); + Singleton._console.SetCursorPosition(0, top); + Singleton.WriteBlankLines(count); + RestoreCursor(); + } + + private int _savedCursorLeft; + private int _savedCursorTop; + + public void SaveCursor() + { + IConsole console = Singleton._console; + _savedCursorLeft = console.CursorLeft; + _savedCursorTop = console.CursorTop; + } + + public void RestoreCursor() => Singleton._console.SetCursorPosition(_savedCursorLeft, _savedCursorTop); } private Menu CreateCompletionMenu(Collection matches) @@ -746,7 +753,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel // if not, we'll skip the menu. var endBufferPoint = ConvertOffsetToPoint(_buffer.Length); - menu.BufferLines = endBufferPoint.Y - _initialY + 1; + menu.BufferLines = endBufferPoint.Y - _initialY + 1 + _options.ExtraPromptLineCount; if (menu.BufferLines + menu.Rows > _console.WindowHeight) { menuSelect = false; @@ -844,10 +851,10 @@ private void MenuCompleteImpl(Menu menu, CommandCompletion completions) { // Render did not clear the rest of the command line which flowed // into the menu, so we must do that here. - _console.SaveCursor(); + menu.SaveCursor(); _console.SetCursorPosition(endOfCommandLine.X, endOfCommandLine.Y); _console.Write(Spaces(_console.BufferWidth - endOfCommandLine.X)); - _console.RestoreCursor(); + menu.RestoreCursor(); } if (previousSelection != -1) @@ -855,7 +862,7 @@ private void MenuCompleteImpl(Menu menu, CommandCompletion completions) if (menu.ToolTipLines > 0) { // Erase previous tooltip, taking into account if the menu moved up/down. - WriteBlankLines(menu.Top + menu.Rows, -topAdjustment + menu.ToolTipLines); + menu.WriteBlankLines(menu.Top + menu.Rows, -topAdjustment + menu.ToolTipLines); } menu.UpdateMenuSelection(previousSelection, /*select*/ false, /*showToolTips*/false, VTColorUtils.AsEscapeSequence(Options.EmphasisColor)); diff --git a/PSReadLine/ConsoleLib.cs b/PSReadLine/ConsoleLib.cs index 37b16557..43e307d7 100644 --- a/PSReadLine/ConsoleLib.cs +++ b/PSReadLine/ConsoleLib.cs @@ -115,15 +115,5 @@ public Encoding OutputEncoding 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"); - - private int _savedX, _savedY; - - public void SaveCursor() - { - _savedX = Console.CursorLeft; - _savedY = Console.CursorTop; - } - - public void RestoreCursor() => Console.SetCursorPosition(_savedX, _savedY); } } diff --git a/PSReadLine/PublicAPI.cs b/PSReadLine/PublicAPI.cs index be195098..c5170753 100644 --- a/PSReadLine/PublicAPI.cs +++ b/PSReadLine/PublicAPI.cs @@ -50,9 +50,6 @@ public interface IConsole void Write(string s); void ScrollBuffer(int lines); void BlankRestOfLine(); - - void SaveCursor(); - void RestoreCursor(); } #pragma warning restore 1591 diff --git a/test/UnitTestReadLine.cs b/test/UnitTestReadLine.cs index 390bc2ee..7675c5db 100644 --- a/test/UnitTestReadLine.cs +++ b/test/UnitTestReadLine.cs @@ -324,19 +324,6 @@ public CHAR_INFO[] ReadBufferLines(int top, int count) return result; } - private int _savedX, _savedY; - - public void SaveCursor() - { - _savedX = CursorLeft; - _savedY = CursorTop; - } - - public void RestoreCursor() - { - SetCursorPosition(_savedX, _savedY); - } - private static readonly ConsoleColor DefaultForeground = ReadLine.Colors[0]; private static readonly ConsoleColor DefaultBackground = ReadLine.BackgroundColors[0];