Skip to content

Commit cd25b1c

Browse files
committed
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
1 parent 599349d commit cd25b1c

File tree

4 files changed

+65
-84
lines changed

4 files changed

+65
-84
lines changed

PSReadLine/Completion.cs

Lines changed: 65 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -113,14 +113,6 @@ private static string GetUnquotedText(CompletionResult match, bool consistentQuo
113113
return GetUnquotedText(s, consistentQuoting);
114114
}
115115

116-
private void WriteBlankLines(int top, int count)
117-
{
118-
_console.SaveCursor();
119-
_console.SetCursorPosition(0, top);
120-
WriteBlankLines(count);
121-
_console.RestoreCursor();
122-
}
123-
124116
private void WriteBlankLines(int count)
125117
{
126118
var spaces = Spaces(_console.BufferWidth);
@@ -464,48 +456,20 @@ private class Menu
464456
internal CompletionResult CurrentMenuItem => MenuItems[CurrentSelection];
465457
internal int CurrentSelection;
466458

467-
void EnsureMenuAndInputIsVisible(IConsole console, int tooltipLineCount)
468-
{
469-
// The +1 lets us write a newline after the last row, which isn't strictly necessary
470-
// It does help with:
471-
// * Console selecting multiple lines of text
472-
// * Adds a little extra space underneath the menu
473-
var bottom = this.Top + this.Rows + tooltipLineCount + 1;
474-
if (bottom > console.BufferHeight)
475-
{
476-
var toScroll = bottom - console.BufferHeight;
477-
console.ScrollBuffer(toScroll);
478-
Singleton._initialY -= toScroll;
479-
this.Top -= toScroll;
480-
481-
var point = Singleton.ConvertOffsetToPoint(Singleton._current);
482-
Singleton.PlaceCursor(point.X, point.Y);
483-
}
484-
}
485-
486459
public void DrawMenu(Menu previousMenu, bool menuSelect)
487460
{
488461
IConsole console = Singleton._console;
489462

490463
// Move cursor to the start of the first line after our input.
491-
this.Top = Singleton.ConvertOffsetToPoint(Singleton._buffer.Length).Y + 1;
464+
var bufferEndPoint = Singleton.ConvertOffsetToPoint(Singleton._buffer.Length);
465+
console.SetCursorPosition(bufferEndPoint.X, bufferEndPoint.Y);
466+
AdjustForPossibleScroll(1);
467+
MoveCursorDown(1);
468+
this.Top = bufferEndPoint.Y + 1;
492469
if (menuSelect)
493470
{
494-
EnsureMenuAndInputIsVisible(console, tooltipLineCount: 0);
495-
496471
console.CursorVisible = false;
497-
console.SaveCursor();
498-
console.SetCursorPosition(0, this.Top);
499-
}
500-
else
501-
{
502-
// Start a new line to show the menu contents
503-
console.SetCursorPosition(0, this.Top);
504-
// Scroll one line up if the cursor is at the bottom of the window
505-
if (this.Top == console.BufferHeight)
506-
{
507-
console.Write("\n");
508-
}
472+
SaveCursor();
509473
}
510474

511475
var bufferWidth = console.BufferWidth;
@@ -533,8 +497,12 @@ public void DrawMenu(Menu previousMenu, bool menuSelect)
533497
console.BlankRestOfLine();
534498
}
535499

536-
// Explicit newline so consoles see each row as distinct lines.
537-
console.Write("\n");
500+
// Explicit newline so consoles see each row as distinct lines, but skip the
501+
// last line so we don't scroll.
502+
if (row != (this.Rows - 1) || !menuSelect) {
503+
AdjustForPossibleScroll(1);
504+
MoveCursorDown(1);
505+
}
538506
}
539507

540508
if (previousMenu != null)
@@ -547,14 +515,14 @@ public void DrawMenu(Menu previousMenu, bool menuSelect)
547515

548516
if (menuSelect)
549517
{
550-
console.RestoreCursor();
518+
RestoreCursor();
551519
console.CursorVisible = true;
552520
}
553521
}
554522

555523
public void Clear()
556524
{
557-
Singleton.WriteBlankLines(Top, Rows + ToolTipLines);
525+
WriteBlankLines(Top, Rows + ToolTipLines);
558526
}
559527

560528
public void UpdateMenuSelection(int selectedItem, bool select, bool showTooltips, string toolTipColor)
@@ -614,12 +582,7 @@ public void UpdateMenuSelection(int selectedItem, bool select, bool showTooltips
614582
}
615583
}
616584

617-
if (showTooltips)
618-
{
619-
EnsureMenuAndInputIsVisible(console, toolTipLines);
620-
}
621-
622-
console.SaveCursor();
585+
SaveCursor();
623586

624587
var row = Top + selectedItem % Rows;
625588
var col = ColumnWidth * (selectedItem / Rows);
@@ -634,15 +597,18 @@ public void UpdateMenuSelection(int selectedItem, bool select, bool showTooltips
634597
if (showTooltips)
635598
{
636599
Debug.Assert(select, "On unselect, caller must clear the tooltip");
637-
console.SetCursorPosition(0, Top + Rows + 1);
600+
console.SetCursorPosition(0, Top + Rows - 1);
601+
// Move down 2 so we have 1 blank line between the menu and buffer.
602+
AdjustForPossibleScroll(toolTipLines);
603+
MoveCursorDown(2);
638604
console.Write(toolTipColor);
639605
console.Write(toolTip);
640606
ToolTipLines = toolTipLines;
641607

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

645-
console.RestoreCursor();
611+
RestoreCursor();
646612
}
647613

648614
public void MoveRight() => CurrentSelection = Math.Min(CurrentSelection + Rows, MenuItems.Count - 1);
@@ -661,6 +627,47 @@ public void MoveN(int n)
661627
CurrentSelection += MenuItems.Count;
662628
}
663629
}
630+
631+
private void MoveCursorDown(int cnt)
632+
{
633+
IConsole console = Singleton._console;
634+
while (cnt-- > 0)
635+
{
636+
console.Write("\n");
637+
}
638+
}
639+
640+
private void AdjustForPossibleScroll(int cnt)
641+
{
642+
IConsole console = Singleton._console;
643+
var scrollCnt = console.CursorTop + cnt + 1 - console.BufferHeight;
644+
if (scrollCnt > 0)
645+
{
646+
Top -= scrollCnt;
647+
_singleton._initialY -= scrollCnt;
648+
_savedCursorTop -= scrollCnt;
649+
}
650+
}
651+
652+
public void WriteBlankLines(int top, int count)
653+
{
654+
SaveCursor();
655+
Singleton._console.SetCursorPosition(0, top);
656+
Singleton.WriteBlankLines(count);
657+
RestoreCursor();
658+
}
659+
660+
private int _savedCursorLeft;
661+
private int _savedCursorTop;
662+
663+
public void SaveCursor()
664+
{
665+
IConsole console = Singleton._console;
666+
_savedCursorLeft = console.CursorLeft;
667+
_savedCursorTop = console.CursorTop;
668+
}
669+
670+
public void RestoreCursor() => Singleton._console.SetCursorPosition(_savedCursorLeft, _savedCursorTop);
664671
}
665672

666673
private Menu CreateCompletionMenu(Collection<CompletionResult> matches)
@@ -746,7 +753,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel
746753
// if not, we'll skip the menu.
747754

748755
var endBufferPoint = ConvertOffsetToPoint(_buffer.Length);
749-
menu.BufferLines = endBufferPoint.Y - _initialY + 1;
756+
menu.BufferLines = endBufferPoint.Y - _initialY + 1 + _options.ExtraPromptLineCount;
750757
if (menu.BufferLines + menu.Rows > _console.WindowHeight)
751758
{
752759
menuSelect = false;
@@ -844,18 +851,18 @@ private void MenuCompleteImpl(Menu menu, CommandCompletion completions)
844851
{
845852
// Render did not clear the rest of the command line which flowed
846853
// into the menu, so we must do that here.
847-
_console.SaveCursor();
854+
menu.SaveCursor();
848855
_console.SetCursorPosition(endOfCommandLine.X, endOfCommandLine.Y);
849856
_console.Write(Spaces(_console.BufferWidth - endOfCommandLine.X));
850-
_console.RestoreCursor();
857+
menu.RestoreCursor();
851858
}
852859

853860
if (previousSelection != -1)
854861
{
855862
if (menu.ToolTipLines > 0)
856863
{
857864
// Erase previous tooltip, taking into account if the menu moved up/down.
858-
WriteBlankLines(menu.Top + menu.Rows, -topAdjustment + menu.ToolTipLines);
865+
menu.WriteBlankLines(menu.Top + menu.Rows, -topAdjustment + menu.ToolTipLines);
859866
}
860867
menu.UpdateMenuSelection(previousSelection, /*select*/ false,
861868
/*showToolTips*/false, VTColorUtils.AsEscapeSequence(Options.EmphasisColor));

PSReadLine/ConsoleLib.cs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -115,15 +115,5 @@ public Encoding OutputEncoding
115115
public virtual void WriteLine(string value) => Console.WriteLine(value);
116116
public virtual void ScrollBuffer(int lines) => Console.Write("\x1b[" + lines + "S");
117117
public virtual void BlankRestOfLine() => Console.Write("\x1b[K");
118-
119-
private int _savedX, _savedY;
120-
121-
public void SaveCursor()
122-
{
123-
_savedX = Console.CursorLeft;
124-
_savedY = Console.CursorTop;
125-
}
126-
127-
public void RestoreCursor() => Console.SetCursorPosition(_savedX, _savedY);
128118
}
129119
}

PSReadLine/PublicAPI.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,6 @@ public interface IConsole
5050
void Write(string s);
5151
void ScrollBuffer(int lines);
5252
void BlankRestOfLine();
53-
54-
void SaveCursor();
55-
void RestoreCursor();
5653
}
5754

5855
#pragma warning restore 1591

test/UnitTestReadLine.cs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -324,19 +324,6 @@ public CHAR_INFO[] ReadBufferLines(int top, int count)
324324
return result;
325325
}
326326

327-
private int _savedX, _savedY;
328-
329-
public void SaveCursor()
330-
{
331-
_savedX = CursorLeft;
332-
_savedY = CursorTop;
333-
}
334-
335-
public void RestoreCursor()
336-
{
337-
SetCursorPosition(_savedX, _savedY);
338-
}
339-
340327
private static readonly ConsoleColor DefaultForeground = ReadLine.Colors[0];
341328
private static readonly ConsoleColor DefaultBackground = ReadLine.BackgroundColors[0];
342329

0 commit comments

Comments
 (0)