Skip to content

Fix tooltips and menu at bottom of buffer #783

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 68 additions & 61 deletions PSReadLine/Completion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -579,7 +547,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++)
Expand Down Expand Up @@ -607,19 +575,14 @@ 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;
}
}

if (showTooltips)
{
EnsureMenuAndInputIsVisible(console, toolTipLines);
}

console.SaveCursor();
SaveCursor();

var row = Top + selectedItem % Rows;
var col = ColumnWidth * (selectedItem / Rows);
Expand All @@ -634,15 +597,18 @@ 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;

console.Write("\x1b[0m");
}

console.RestoreCursor();
RestoreCursor();
}

public void MoveRight() => CurrentSelection = Math.Min(CurrentSelection + Rows, MenuItems.Count - 1);
Expand All @@ -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<CompletionResult> matches)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -844,18 +851,18 @@ 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)
{
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));
Expand Down
10 changes: 0 additions & 10 deletions PSReadLine/ConsoleLib.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
3 changes: 0 additions & 3 deletions PSReadLine/PublicAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 0 additions & 13 deletions test/UnitTestReadLine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand Down