Skip to content

Commit 9f786b3

Browse files
authored
Fix three issues with the menu completion (#1946)
1. Fix menu completion to work with the predictive list view (fix #1935 -- regression introduced in 2.2.0-beta1) 2. Fix menu completion to clear lines properly when the menu top changes on re-drawing (regression introduced in 2.2.0-beta1) 3. Fix how `RightArrow`, `LeftArrow`, `UpArrow`, and `DownArrow` navigate in the menu view (enhancement)
1 parent 726e7c6 commit 9f786b3

File tree

4 files changed

+819
-33
lines changed

4 files changed

+819
-33
lines changed

PSReadLine/Completion.cs

Lines changed: 112 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -491,11 +491,18 @@ public void DrawMenu(Menu previousMenu, bool menuSelect)
491491
}
492492
}
493493

494+
bool extraPreRowsCleared = false;
494495
if (previousMenu != null)
495496
{
496497
if (Rows < previousMenu.Rows + previousMenu.ToolTipLines)
497498
{
498-
// Rest of the current line was erased, but the cursor was not moved to the next line.
499+
// If the last menu row took the whole buffer width, then the cursor could be pushed to the
500+
// beginning of the next line in the legacy console host (NOT in modern terminals such as
501+
// Windows Terminal, VSCode Terminal, or virtual-terminal-enabled console host). In such a
502+
// case, there is no need to move the cursor to the next line.
503+
//
504+
// If that is not the case, namely 'CursorLeft != 0', then the rest of the last menu row was
505+
// erased, but the cursor was not moved to the next line, so we will move the cursor.
499506
if (console.CursorLeft != 0)
500507
{
501508
// There are lines from the previous rendering that need to be cleared,
@@ -504,13 +511,25 @@ public void DrawMenu(Menu previousMenu, bool menuSelect)
504511
}
505512

506513
Singleton.WriteBlankLines(previousMenu.Rows + previousMenu.ToolTipLines - Rows);
514+
extraPreRowsCleared = true;
507515
}
508516
}
509517

510518
// if the menu has moved, we need to clear the lines under it
511519
if (bufferEndPoint.Y < PreviousTop)
512520
{
513-
console.BlankRestOfLine();
521+
// In either of the following two cases, we will need to move the cursor to the next line:
522+
// - if extra rows from previous menu were cleared, then we know the current line was erased
523+
// but the cursor was not moved to the next line.
524+
// - if 'CursorLeft != 0', then the rest of the last menu row was erased, but the cursor
525+
// was not moved to the next line.
526+
if (extraPreRowsCleared || console.CursorLeft != 0)
527+
{
528+
// There are lines from the previous rendering that need to be cleared,
529+
// so we are sure there is no need to scroll.
530+
MoveCursorDown(1);
531+
}
532+
514533
Singleton.WriteBlankLines(PreviousTop - bufferEndPoint.Y);
515534
}
516535

@@ -614,10 +633,62 @@ public void UpdateMenuSelection(int selectedItem, bool select, bool showTooltips
614633
RestoreCursor();
615634
}
616635

617-
public void MoveRight() => CurrentSelection = Math.Min(CurrentSelection + Rows, MenuItems.Count - 1);
618-
public void MoveLeft() => CurrentSelection = Math.Max(CurrentSelection - Rows, 0);
619-
public void MoveUp() => CurrentSelection = Math.Max(CurrentSelection - 1, 0);
620-
public void MoveDown() => CurrentSelection = Math.Min(CurrentSelection + 1, MenuItems.Count - 1);
636+
public void MoveRight()
637+
{
638+
int nextInSameRow = CurrentSelection + Rows;
639+
if (nextInSameRow <= MenuItems.Count - 1)
640+
{
641+
CurrentSelection = nextInSameRow;
642+
return;
643+
}
644+
645+
// Index of the column where 'CurrentSelection' is at, assuming columns start from left at index 0.
646+
int columnIndex = CurrentSelection / Rows;
647+
int leftmostItemInSameRow = CurrentSelection - columnIndex * Rows;
648+
649+
// Index of the row where 'leftMostItemAtSameRow' is at, assuming rows start from top at index 0.
650+
int rowIndex = leftmostItemInSameRow % Rows;
651+
652+
// If 'rowIndex == Rows - 1', then 'CurrentSelection' is at the rightmost position in the last row,
653+
// so moving-to-right again should move to the item at index 0.
654+
CurrentSelection = rowIndex == Rows - 1 ? 0 : leftmostItemInSameRow + 1;
655+
}
656+
657+
public void MoveLeft()
658+
{
659+
int previousInSameRow = CurrentSelection - Rows;
660+
if (previousInSameRow >= 0)
661+
{
662+
CurrentSelection = previousInSameRow;
663+
return;
664+
}
665+
666+
// Index of the row where 'CurrentSelection' is at, assuming rows start from top at index 0.
667+
int rowIndex = CurrentSelection % Rows;
668+
int leftmostItemInPreviousRow = rowIndex == 0 ? Rows - 1 : rowIndex - 1;
669+
670+
int lastItemIndex = MenuItems.Count - 1;
671+
// Index of the column where the last item is at, assuming columns start from left at index 0.
672+
int lastItemColumnIndex = lastItemIndex / Rows;
673+
674+
// Get the rightmost item in the previous row.
675+
CurrentSelection = leftmostItemInPreviousRow + lastItemColumnIndex * Rows;
676+
if (CurrentSelection > lastItemIndex)
677+
{
678+
CurrentSelection = leftmostItemInPreviousRow + (lastItemColumnIndex - 1) * Rows;
679+
}
680+
}
681+
682+
public void MoveUp()
683+
{
684+
CurrentSelection = CurrentSelection > 0 ? CurrentSelection - 1 : MenuItems.Count - 1;
685+
}
686+
687+
public void MoveDown()
688+
{
689+
CurrentSelection = CurrentSelection < (MenuItems.Count - 1) ? CurrentSelection + 1 : 0;
690+
}
691+
621692
public void MovePageDown() => CurrentSelection = Math.Min(CurrentSelection + Rows - (CurrentSelection % Rows) - 1,
622693
MenuItems.Count - 1);
623694
public void MovePageUp() => CurrentSelection = Math.Max(CurrentSelection - (CurrentSelection % Rows), 0);
@@ -808,9 +879,6 @@ private void MenuCompleteImpl(Menu menu, CommandCompletion completions)
808879

809880
var userInitialCompletionLength = userCompletionText.Length;
810881

811-
completions.CurrentMatchIndex = 0;
812-
menu.DrawMenu(null, menuSelect:true);
813-
814882
bool processingKeys = true;
815883
int previousSelection = -1;
816884

@@ -832,38 +900,51 @@ private void MenuCompleteImpl(Menu menu, CommandCompletion completions)
832900

833901
ExchangePointAndMark();
834902

835-
// After replacement, the menu might be misplaced from the command line
836-
// getting shorter or longer.
837-
var endOfCommandLine = ConvertOffsetToPoint(_buffer.Length);
838-
var topAdjustment = (endOfCommandLine.Y + 1) - menu.Top;
839-
840-
if (topAdjustment != 0)
903+
if (previousSelection == -1)
841904
{
842-
menu.Top += topAdjustment;
843-
menu.DrawMenu(null, menuSelect:true);
905+
completions.CurrentMatchIndex = 0;
906+
menu.DrawMenu(null, menuSelect: true);
844907
}
845-
if (topAdjustment > 0)
908+
else
846909
{
847-
// Render did not clear the rest of the command line which flowed
848-
// into the menu, so we must do that here.
849-
menu.SaveCursor();
850-
_console.SetCursorPosition(endOfCommandLine.X, endOfCommandLine.Y);
851-
_console.Write(Spaces(_console.BufferWidth - endOfCommandLine.X));
852-
menu.RestoreCursor();
853-
}
910+
// After replacement, the menu might be misplaced from the command line
911+
// getting shorter or longer.
912+
var endOfCommandLine = ConvertOffsetToPoint(_buffer.Length);
913+
var topAdjustment = (endOfCommandLine.Y + 1) - menu.Top;
914+
915+
if (topAdjustment != 0)
916+
{
917+
menu.Top += topAdjustment;
918+
menu.DrawMenu(null, menuSelect: true);
919+
}
920+
if (topAdjustment > 0)
921+
{
922+
// Render did not clear the rest of the command line which flowed
923+
// into the menu, so we must do that here.
924+
menu.SaveCursor();
925+
_console.SetCursorPosition(endOfCommandLine.X, endOfCommandLine.Y);
926+
_console.Write(Spaces(_console.BufferWidth - endOfCommandLine.X));
927+
menu.RestoreCursor();
928+
}
854929

855-
if (previousSelection != -1)
856-
{
857930
if (menu.ToolTipLines > 0)
858931
{
859932
// Erase previous tooltip, taking into account if the menu moved up/down.
860933
WriteBlankLines(menu.Top + menu.Rows, -topAdjustment + menu.ToolTipLines);
861934
}
862-
menu.UpdateMenuSelection(previousSelection, /*select*/ false,
863-
/*showToolTips*/false, Options._emphasisColor);
935+
936+
menu.UpdateMenuSelection(
937+
previousSelection,
938+
select: false,
939+
showTooltips: false,
940+
Options._emphasisColor);
864941
}
865-
menu.UpdateMenuSelection(menu.CurrentSelection, /*select*/ true,
866-
Options.ShowToolTips, Options._emphasisColor);
942+
943+
menu.UpdateMenuSelection(
944+
menu.CurrentSelection,
945+
select: true,
946+
Options.ShowToolTips,
947+
Options._emphasisColor);
867948

868949
previousSelection = menu.CurrentSelection;
869950
}

PSReadLine/PlatformWindows.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,8 @@ public override void BlankRestOfLine()
601601
var y = CursorTop;
602602

603603
for (int i = 0; i < BufferWidth - x; i++) Console.Write(' ');
604+
605+
// Last step may result in scrolling.
604606
if (CursorTop != y+1) y -= 1;
605607

606608
SetCursorPosition(x, y);

0 commit comments

Comments
 (0)