Пример #1
0
        /// <summary>
        /// Invert the case of the current character and move to the next one.
        /// </summary>
        public static void InvertCase(ConsoleKeyInfo?key = null, object arg = null)
        {
            if (_singleton._current >= _singleton._buffer.Length)
            {
                Ding();
                return;
            }

            int qty = (arg is int) ? (int)arg : 1;

            for (; qty > 0 && _singleton._current < _singleton._buffer.Length; qty--)
            {
                char c = _singleton._buffer[_singleton._current];
                if (Char.IsLetter(c))
                {
                    char     newChar     = Char.IsUpper(c) ? Char.ToLower(c) : char.ToUpper(c);
                    EditItem delEditItem = EditItemDelete.Create(c.ToString(), _singleton._current);
                    EditItem insEditItem = EditItemInsertChar.Create(newChar, _singleton._current);
                    _singleton.SaveEditItem(GroupedEdit.Create(new List <EditItem>
                    {
                        delEditItem,
                        insEditItem
                    },
                                                               InvertCase,
                                                               arg
                                                               ));

                    _singleton._buffer[_singleton._current] = newChar;
                }
                _singleton._current = Math.Min(_singleton._current + 1, _singleton._buffer.Length);
                _singleton.PlaceCursor();
            }
            _singleton.Render();
        }
Пример #2
0
        private void EndEditGroup(Action <ConsoleKeyInfo?, object> instigator = null, object instigatorArg = null)
        {
            // Remove the undone edits when closing an edit group, so the generated group
            // doesn't contain those edits that were already undone.
            RemoveEditsAfterUndo();

            // If any edits before the start mark were done, the start mark will be reset
            // and no need to generate the edit group.
            if (_editGroupStart < 0)
            {
                return;
            }

            var groupEditCount = _edits.Count - _editGroupStart;

            // It's possible that just enough edits were undone and now 'groupEditCount' is 0.
            // We don't generate the edit group in that case.
            if (groupEditCount > 0)
            {
                var groupedEditItems = _edits.GetRange(_editGroupStart, groupEditCount);
                _edits.RemoveRange(_editGroupStart, groupEditCount);
                SaveEditItem(GroupedEdit.Create(groupedEditItems, instigator, instigatorArg));
            }
            _editGroupStart = -1;
        }
Пример #3
0
        private void EndEditGroup()
        {
            var groupEditCount   = _edits.Count - _editGroupStart;
            var groupedEditItems = _edits.GetRange(_editGroupStart, groupEditCount);

            _edits.RemoveRange(_editGroupStart, groupEditCount);
            SaveEditItem(GroupedEdit.Create(groupedEditItems));
            _editGroupStart = -1;
        }
Пример #4
0
        private void EndEditGroup(Action <ConsoleKeyInfo?, object> instigator = null, object instigatorArg = null)
        {
            var groupEditCount   = _edits.Count - _editGroupStart;
            var groupedEditItems = _edits.GetRange(_editGroupStart, groupEditCount);

            _edits.RemoveRange(_editGroupStart, groupEditCount);
            SaveEditItem(GroupedEdit.Create(groupedEditItems, instigator, instigatorArg));
            _editGroupStart = -1;
        }
Пример #5
0
        private void EndEditGroup()
        {
            // The last _editGroupCount edits are treated as a single item for undo
            var start            = _edits.Count - _editGroupCount;
            var groupedEditItems = _edits.GetRange(start, _editGroupCount);

            _edits.RemoveRange(start, _editGroupCount);
            SaveEditItem(GroupedEdit.Create(groupedEditItems));
            _editGroupCount = _pushedEditGroupCount.Pop();
        }
Пример #6
0
        /// <summary>
        /// Invert the case of the current character and move to the next one.
        /// </summary>
        public static void InvertCase(ConsoleKeyInfo?key = null, object arg = null)
        {
            if (_singleton._current >= _singleton._buffer.Length)
            {
                Ding();
                return;
            }

            int qty = arg as int? ?? 1;

            for (; qty > 0 && _singleton._current < _singleton._buffer.Length; qty--)
            {
                char c = _singleton._buffer[_singleton._current];
                if (Char.IsLetter(c))
                {
                    char     newChar     = Char.IsUpper(c) ? Char.ToLower(c, CultureInfo.CurrentCulture) : char.ToUpper(c, CultureInfo.CurrentCulture);
                    EditItem delEditItem = EditItemDelete.Create(
                        c.ToString(),
                        _singleton._current,
                        InvertCase,
                        arg,
                        moveCursorToEndWhenUndo: false);

                    EditItem insEditItem = EditItemInsertChar.Create(newChar, _singleton._current);
                    _singleton.SaveEditItem(GroupedEdit.Create(new List <EditItem>
                    {
                        delEditItem,
                        insEditItem
                    },
                                                               InvertCase,
                                                               arg
                                                               ));

                    _singleton._buffer[_singleton._current] = newChar;
                }
                _singleton.MoveCursor(Math.Min(_singleton._current + 1, _singleton._buffer.Length));
            }
            _singleton.Render();
        }
Пример #7
0
        private void MenuCompleteImpl(Menu menu, CommandCompletion completions)
        {
            var menuStack = new Stack <Menu>(10);

            menuStack.Push(null);  // Used to help detect excess backspaces

            RemoveEditsAfterUndo();
            var undoPoint = _edits.Count;

            bool undo = false;

            int savedUserMark = _mark;

            _visualSelectionCommandCount++;

            // Get the unambiguous prefix, possibly removing the first quote
            var userCompletionText = GetUnambiguousPrefix(menu.MenuItems, out var ambiguous);

            if (userCompletionText.Length > 0)
            {
                var c = userCompletionText[0];
                if (IsSingleQuote(c) || IsDoubleQuote(c))
                {
                    userCompletionText = userCompletionText.Substring(1);
                }
            }

            var userInitialCompletionLength = userCompletionText.Length;

            bool processingKeys    = true;
            int  previousSelection = -1;

            while (processingKeys)
            {
                if (menu.CurrentSelection != previousSelection)
                {
                    var currentMenuItem = menu.CurrentMenuItem;
                    int curPos          = FindUserCompletionTextPosition(currentMenuItem, userCompletionText);
                    if (userCompletionText.Length == 0 &&
                        (IsSingleQuote(currentMenuItem.CompletionText[0]) || IsDoubleQuote(currentMenuItem.CompletionText[0])))
                    {
                        curPos++;
                    }

                    // set mark to the end of UserCompletion but in real completion (because of .\ and so on)
                    _mark = completions.ReplacementIndex + curPos + userCompletionText.Length;
                    DoReplacementForCompletion(currentMenuItem, completions);

                    ExchangePointAndMark();

                    if (previousSelection == -1)
                    {
                        completions.CurrentMatchIndex = 0;
                        menu.DrawMenu(null, menuSelect: true);
                    }
                    else
                    {
                        // After replacement, the menu might be misplaced from the command line
                        // getting shorter or longer.
                        var endOfCommandLine = ConvertOffsetToPoint(_buffer.Length);
                        var topAdjustment    = (endOfCommandLine.Y + 1) - menu.Top;

                        if (topAdjustment != 0)
                        {
                            menu.Top += topAdjustment;
                            menu.DrawMenu(null, menuSelect: true);
                        }
                        if (topAdjustment > 0)
                        {
                            // Render did not clear the rest of the command line which flowed
                            // into the menu, so we must do that here.
                            menu.SaveCursor();
                            _console.SetCursorPosition(endOfCommandLine.X, endOfCommandLine.Y);
                            _console.Write(Spaces(_console.BufferWidth - endOfCommandLine.X));
                            menu.RestoreCursor();
                        }

                        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.UpdateMenuSelection(
                            previousSelection,
                            select: false,
                            showTooltips: false,
                            Options._emphasisColor);
                    }

                    menu.UpdateMenuSelection(
                        menu.CurrentSelection,
                        select: true,
                        Options.ShowToolTips,
                        Options._emphasisColor);

                    previousSelection = menu.CurrentSelection;
                }

                var nextKey = ReadKey();
                if (nextKey == Keys.RightArrow)
                {
                    menu.MoveRight();
                }
                else if (nextKey == Keys.LeftArrow)
                {
                    menu.MoveLeft();
                }
                else if (nextKey == Keys.DownArrow)
                {
                    menu.MoveDown();
                }
                else if (nextKey == Keys.UpArrow)
                {
                    menu.MoveUp();
                }
                else if (nextKey == Keys.PageDown)
                {
                    menu.MovePageDown();
                }
                else if (nextKey == Keys.PageUp)
                {
                    menu.MovePageUp();
                }
                else if (nextKey == Keys.Tab)
                {
                    // Search for possible unambiguous common prefix.
                    string unAmbiguousText = GetUnambiguousPrefix(menu.MenuItems, out ambiguous);
                    int    userComplPos    = unAmbiguousText.IndexOf(userCompletionText, StringComparison.OrdinalIgnoreCase);

                    // ... If found - advance IncrementalCompletion ...
                    if (unAmbiguousText.Length > 0 && userComplPos >= 0 &&
                        unAmbiguousText.Length > (userComplPos + userCompletionText.Length))
                    {
                        userCompletionText = unAmbiguousText.Substring(userComplPos);
                        _current           = completions.ReplacementIndex +
                                             FindUserCompletionTextPosition(menu.MenuItems[menu.CurrentSelection], userCompletionText) +
                                             userCompletionText.Length;
                        Render();
                        Ding();
                    }
                    // ... if no - usual Tab behaviour
                    else
                    {
                        menu.MoveN(1);
                    }
                }
                else if (nextKey == Keys.ShiftTab)
                {
                    menu.MoveN(-1);
                }
                else if (nextKey == Keys.CtrlG ||
                         nextKey == Keys.Escape)
                {
                    undo           = true;
                    processingKeys = false;
                    _visualSelectionCommandCount = 0;
                    _mark = savedUserMark;
                }
                else if (nextKey == Keys.Backspace)
                {
                    // TODO: Shift + Backspace does not fail here?
                    if (menuStack.Count > 1)
                    {
                        var newMenu = menuStack.Pop();

                        newMenu.DrawMenu(menu, menuSelect: true);
                        previousSelection = -1;

                        menu = newMenu;

                        userCompletionText = userCompletionText.Substring(0, userCompletionText.Length - 1);
                    }
                    else if (menuStack.Count == 1)
                    {
                        Ding();

                        Debug.Assert(menuStack.Peek() == null, "sentinel value expected");
                        // Pop so the next backspace sends us to the else block and out of the loop.
                        menuStack.Pop();
                    }
                    else
                    {
                        processingKeys = false;
                        undo           = true;
                        _visualSelectionCommandCount = 0;
                        _mark = savedUserMark;
                        PrependQueuedKeys(nextKey);
                    }
                }
                else
                {
                    bool prependNextKey            = false;
                    int  cursorAdjustment          = 0;
                    bool truncateCurrentCompletion = false;
                    bool keepSelection             = false;

                    var currentMenuItem = menu.CurrentMenuItem;
                    if (IsDoneWithCompletions(currentMenuItem, nextKey))
                    {
                        processingKeys = false;
                        ExchangePointAndMark(); // cursor to the end of Completion
                        if (nextKey != Keys.Enter)
                        {
                            if (currentMenuItem.ResultType == CompletionResultType.ProviderContainer)
                            {
                                userCompletionText = GetUnquotedText(
                                    GetReplacementTextForDirectory(currentMenuItem.CompletionText, ref cursorAdjustment),
                                    consistentQuoting: false);
                            }
                            else
                            {
                                userCompletionText = GetUnquotedText(currentMenuItem, consistentQuoting: false);
                            }

                            // do not append the same char as last char in CompletionText (works for '(', '\')
                            prependNextKey = userCompletionText[userCompletionText.Length - 1] != nextKey.KeyChar;
                        }
                    }
                    else if (nextKey.KeyChar > 0 && !char.IsControl(nextKey.KeyChar))
                    {
                        userCompletionText += nextKey.KeyChar;
                        // filter out matches and redraw menu
                        var newMatches = FilterCompletions(completions, userCompletionText);
                        if (newMatches.Count > 0)
                        {
                            var newMenu = CreateCompletionMenu(newMatches);

                            newMenu.DrawMenu(menu, menuSelect: true);
                            previousSelection = -1;

                            // Remember the current menu for when we see Backspace.
                            menu.ToolTipLines = 0;
                            if (menuStack.Count == 0)
                            {
                                // The user hit backspace before there were any items on the stack
                                // and we removed the sentinel - so put it back now.
                                menuStack.Push(null);
                            }

                            menuStack.Push(menu);
                            menu = newMenu;
                        }
                        else
                        {
                            processingKeys = false;
                            prependNextKey = true;
                            // we exit loop with current completion up to cursor
                            truncateCurrentCompletion = true;
                            if (userInitialCompletionLength == 0)
                            {
                                undo = true;
                            }
                        }
                    }
                    else // exit with any other Key chord
                    {
                        processingKeys = false;
                        prependNextKey = true;

                        // without this branch experience doesnt look naturally
                        if (_dispatchTable.TryGetValue(nextKey, out var handler) &&
                            (
                                handler.Action == CopyOrCancelLine ||
                                handler.Action == Cut ||
                                handler.Action == DeleteChar ||
                                handler.Action == Paste
                            )
                            )
                        {
                            keepSelection = true;
                        }
                    }

                    if (!processingKeys) // time to exit loop
                    {
                        if (truncateCurrentCompletion && !undo)
                        {
                            CompletionResult r = new CompletionResult(currentMenuItem
                                                                      .CompletionText.Substring(0, _current - completions.ReplacementIndex));
                            DoReplacementForCompletion(r, completions);
                        }
                        if (keepSelection)
                        {
                            _visualSelectionCommandCount = 1;
                        }
                        else
                        {
                            _visualSelectionCommandCount = 0;
                            // if mark was set after cursor, it restored in uninspected position, because text before mark now longer
                            // should we correct it ? I think not, beause any other text insertion does not correct it
                            _mark = savedUserMark;
                        }
                        // without render all key chords that just move cursor leave selection visible, but it can be wrong
                        if (!undo && !keepSelection)
                        {
                            Render();
                        }
                        if (prependNextKey)
                        {
                            _current -= cursorAdjustment;
                            PrependQueuedKeys(nextKey);
                        }
                    }
                }
            }

            menu.Clear();

            var lastInsert = ((GroupedEdit)_edits[_edits.Count - 1])._groupedEditItems[1];

            Debug.Assert(lastInsert is EditItemInsertString, "The only edits possible here are pairs of Delete/Insert");
            var firstDelete = ((GroupedEdit)_edits[undoPoint])._groupedEditItems[0];

            Debug.Assert(firstDelete is EditItemDelete, "The only edits possible here are pairs of Delete/Insert");

            var groupEditCount = _edits.Count - undoPoint;

            _edits.RemoveRange(undoPoint, groupEditCount);
            _undoEditIndex = undoPoint;

            if (undo)
            {
                // Pretend it never happened.
                lastInsert.Undo();
                firstDelete.Undo();
                Render();
            }
            else
            {
                // Leave one edit instead of possibly many to undo
                SaveEditItem(GroupedEdit.Create(new List <EditItem> {
                    firstDelete, lastInsert
                }));
            }
        }
Пример #8
0
        private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSelect)
        {
            if (completions == null || completions.CompletionMatches.Count == 0)
            {
                Ding();
                return;
            }

            if (completions.CompletionMatches.Count >= _options.CompletionQueryItems)
            {
                if (!PromptYesOrNo(string.Format(CultureInfo.CurrentCulture, PSReadLineResources.DisplayAllPossibilities, completions.CompletionMatches.Count)))
                {
                    return;
                }
            }

            var matches     = completions.CompletionMatches;
            var minColWidth = matches.Max(c => c.ListItemText.Length);

            minColWidth += 2;
            var menuColumnWidth = minColWidth;

            int displayRows;
            var bufferWidth = Console.BufferWidth;
            ConsoleBufferBuilder cb;

            if (Options.ShowToolTips)
            {
                const string seperator       = "- ";
                var          maxTooltipWidth = bufferWidth - minColWidth - seperator.Length;

                displayRows = matches.Count;
                cb          = new ConsoleBufferBuilder(displayRows * bufferWidth);
                for (int index = 0; index < matches.Count; index++)
                {
                    var match        = matches[index];
                    var listItemText = HandleNewlinesForPossibleCompletions(match.ListItemText);
                    cb.Append(listItemText);
                    var spacesNeeded = minColWidth - listItemText.Length;
                    if (spacesNeeded > 0)
                    {
                        cb.Append(' ', spacesNeeded);
                    }
                    cb.Append(seperator);
                    var toolTip = HandleNewlinesForPossibleCompletions(match.ToolTip);
                    toolTip = toolTip.Length <= maxTooltipWidth
                                  ? toolTip
                                  : toolTip.Substring(0, maxTooltipWidth);
                    cb.Append(toolTip);

                    // Make sure we always write out exactly 1 buffer width
                    spacesNeeded = (bufferWidth * (index + 1)) - cb.Length;
                    if (spacesNeeded > 0)
                    {
                        cb.Append(' ', spacesNeeded);
                    }
                }
                menuColumnWidth = bufferWidth;
            }
            else
            {
                var screenColumns  = bufferWidth;
                var displayColumns = Math.Max(1, screenColumns / minColWidth);
                displayRows = (completions.CompletionMatches.Count + displayColumns - 1) / displayColumns;
                cb          = new ConsoleBufferBuilder(displayRows * bufferWidth);
                for (var row = 0; row < displayRows; row++)
                {
                    for (var col = 0; col < displayColumns; col++)
                    {
                        var index = row + (displayRows * col);
                        if (index >= matches.Count)
                        {
                            break;
                        }
                        var match = matches[index];
                        var item  = HandleNewlinesForPossibleCompletions(match.ListItemText);
                        cb.Append(item);
                        cb.Append(' ', minColWidth - item.Length);
                    }

                    // Make sure we always write out exactly 1 buffer width
                    var spacesNeeded = (bufferWidth * (row + 1)) - cb.Length;
                    if (spacesNeeded > 0)
                    {
                        cb.Append(' ', spacesNeeded);
                    }
                }
            }

            var menuBuffer = cb.ToArray();

            if (menuSelect)
            {
                // Make sure the menu and line can appear on the screen at the same time,
                // if not, we'll skip the menu.

                var endBufferCoords = ConvertOffsetToCoordinates(_buffer.Length);
                var bufferLines     = endBufferCoords.Y - _initialY + 1;
                if ((bufferLines + displayRows) > Console.WindowHeight)
                {
                    menuSelect = false;
                }
            }

            if (menuSelect)
            {
                RemoveEditsAfterUndo();
                var undoPoint = _edits.Count;

                int  selectedItem = 0;
                bool undo         = false;

                DoReplacementForCompletion(matches[0], completions);

                // Recompute end of buffer coordinates as the replacement could have
                // added a line.
                var endBufferCoords = ConvertOffsetToCoordinates(_buffer.Length);
                var menuAreaTop     = endBufferCoords.Y + 1;
                var previousMenuTop = menuAreaTop;

                InvertSelectedCompletion(menuBuffer, selectedItem, menuColumnWidth, displayRows);
                WriteBufferLines(menuBuffer, ref menuAreaTop);

                // Showing the menu may have scrolled the screen or moved the cursor, update initialY to reflect that.
                _initialY -= (previousMenuTop - menuAreaTop);
                PlaceCursor();
                previousMenuTop = menuAreaTop;

                int previousItem = selectedItem;

                bool processingKeys = true;
                while (processingKeys)
                {
                    var nextKey = ReadKey();
                    if (nextKey == Keys.RightArrow)
                    {
                        selectedItem = Math.Min(selectedItem + displayRows, matches.Count - 1);
                    }
                    else if (nextKey == Keys.LeftArrow)
                    {
                        selectedItem = Math.Max(selectedItem - displayRows, 0);
                    }
                    else if (nextKey == Keys.DownArrow)
                    {
                        selectedItem = Math.Min(selectedItem + 1, matches.Count - 1);
                    }
                    else if (nextKey == Keys.UpArrow)
                    {
                        selectedItem = Math.Max(selectedItem - 1, 0);
                    }
                    else if (nextKey == Keys.Tab)
                    {
                        selectedItem = (selectedItem + 1) % matches.Count;
                    }
                    else if (nextKey == Keys.ShiftTab)
                    {
                        selectedItem = (selectedItem - 1) % matches.Count;
                        if (selectedItem < 0)
                        {
                            selectedItem += matches.Count;
                        }
                    }
                    else if (nextKey == Keys.CtrlG || nextKey == Keys.Escape)
                    {
                        undo           = true;
                        processingKeys = false;
                    }
                    else
                    {
                        PrependQueuedKeys(nextKey);
                        processingKeys = false;
                    }

                    if (selectedItem != previousItem)
                    {
                        DoReplacementForCompletion(matches[selectedItem], completions);

                        endBufferCoords = ConvertOffsetToCoordinates(_buffer.Length);
                        menuAreaTop     = endBufferCoords.Y + 1;

                        InvertSelectedCompletion(menuBuffer, previousItem, menuColumnWidth, displayRows);
                        InvertSelectedCompletion(menuBuffer, selectedItem, menuColumnWidth, displayRows);
                        WriteBufferLines(menuBuffer, ref menuAreaTop);
                        previousItem = selectedItem;

                        if (previousMenuTop > menuAreaTop)
                        {
                            WriteBlankLines(previousMenuTop - menuAreaTop, menuAreaTop + displayRows);
                        }
                    }
                }

                WriteBlankLines(displayRows, menuAreaTop);

                var lastInsert = ((GroupedEdit)_edits[_edits.Count - 1])._groupedEditItems[1];
                Debug.Assert(lastInsert is EditItemInsertString, "The only edits possible here are pairs of Delete/Insert");
                var firstDelete = ((GroupedEdit)_edits[undoPoint])._groupedEditItems[0];
                Debug.Assert(firstDelete is EditItemDelete, "The only edits possible here are pairs of Delete/Insert");

                var groupEditCount = _edits.Count - undoPoint;
                _edits.RemoveRange(undoPoint, groupEditCount);
                _undoEditIndex = undoPoint;

                if (undo)
                {
                    // Pretend it never happened.
                    lastInsert.Undo();
                    firstDelete.Undo();
                    Render();
                }
                else
                {
                    // Leave one edit instead of possibly many to undo
                    SaveEditItem(GroupedEdit.Create(new List <EditItem> {
                        firstDelete, lastInsert
                    }));
                }
            }
            else
            {
                var endBufferCoords = ConvertOffsetToCoordinates(_buffer.Length);
                var menuAreaTop     = endBufferCoords.Y + 1;

                WriteBufferLines(menuBuffer, ref menuAreaTop);
                _initialY = menuAreaTop + displayRows;
                Render();
            }
        }
Пример #9
0
        private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSelect)
        {
            if (completions == null || completions.CompletionMatches.Count == 0)
            {
                Ding();
                return;
            }

            if (completions.CompletionMatches.Count >= _options.CompletionQueryItems)
            {
                if (!PromptYesOrNo(string.Format(CultureInfo.CurrentCulture, PSReadLineResources.DisplayAllPossibilities, completions.CompletionMatches.Count)))
                {
                    return;
                }
            }

            var matches = completions.CompletionMatches;

            var menuBuffer = CreateCompletionMenu(matches, _console, Options.ShowToolTips,
                                                  "", out var selectedItem, out var menuColumnWidth, out var displayRows);

            if (menuSelect)
            {
                // Make sure the menu and line can appear on the screen at the same time,
                // if not, we'll skip the menu.

                var endBufferCoords = ConvertOffsetToCoordinates(_buffer.Length);
                var bufferLines     = endBufferCoords.Y - _initialY + 1;
                if ((bufferLines + displayRows) > _console.WindowHeight)
                {
                    menuSelect = false;
                }
            }

            if (menuSelect)
            {
                RemoveEditsAfterUndo();
                var undoPoint = _edits.Count;

                bool undo = false;

                int savedUserMark = _mark;
                _visualSelectionCommandCount++;

                var userCompletionText = GetUnambiguousPrefix(matches, out var ambiguous);
                // remove possible first quote
                if (userCompletionText.Length > 0 &&
                    (IsSingleQuote(userCompletionText[0]) ||
                     IsDoubleQuote(userCompletionText[0])
                    )
                    )
                {
                    userCompletionText = userCompletionText.Substring(1);
                }
                int userInitialCompletionLength = userCompletionText.Length;

                DoReplacementForCompletion(matches[0], completions);
                // Recompute end of buffer coordinates as the replacement could have
                // added a line.
                var endBufferCoords = ConvertOffsetToCoordinates(_buffer.Length);
                var menuAreaTop     = endBufferCoords.Y + 1;
                var previousMenuTop = menuAreaTop;

                int previousItem = -1;

                bool processingKeys   = true;
                int  backspaceCounter = 0;
                while (processingKeys)
                {
                    int cycleBackspaceCounter = backspaceCounter;
                    if (selectedItem != previousItem)
                    {
                        int curPos = FindUserCompletinTextPosition(matches[selectedItem], userCompletionText);
                        if (userCompletionText.Length == 0 &&
                            (IsSingleQuote(matches[selectedItem].CompletionText[0]) ||
                             IsDoubleQuote(matches[selectedItem].CompletionText[0])
                            )
                            )
                        {
                            curPos++;
                        }
                        // set mark to the end of UserCompletion but in real completion (because of .\ and so on)
                        _mark = completions.ReplacementIndex + curPos + userCompletionText.Length;
                        DoReplacementForCompletion(matches[selectedItem], completions);

                        endBufferCoords = ConvertOffsetToCoordinates(_buffer.Length);
                        menuAreaTop     = endBufferCoords.Y + 1;

                        if (previousItem != -1)
                        {
                            InvertSelectedCompletion(menuBuffer, previousItem, menuColumnWidth, displayRows);
                        }
                        InvertSelectedCompletion(menuBuffer, selectedItem, menuColumnWidth, displayRows);

                        var blanklinesCount = previousMenuTop - menuAreaTop;
                        previousMenuTop = menuAreaTop;
                        _console.WriteBufferLines(menuBuffer, ref menuAreaTop);

                        // Showing the menu may have scrolled the screen or moved the cursor, update initialY to reflect that.
                        _initialY -= (previousMenuTop - menuAreaTop);
                        // return cursor in place even if screen was scrolled
                        ExchangePointAndMark();

                        if (previousItem != -1 && blanklinesCount > 0)
                        {
                            WriteBlankLines(blanklinesCount, menuAreaTop + displayRows);
                        }
                        previousMenuTop = menuAreaTop;
                        previousItem    = selectedItem;
                    }

                    var nextKey = ReadKey();
                    if (nextKey == Keys.RightArrow)
                    {
                        selectedItem = Math.Min(selectedItem + displayRows, matches.Count - 1);
                    }
                    else if (nextKey == Keys.LeftArrow)
                    {
                        selectedItem = Math.Max(selectedItem - displayRows, 0);
                    }
                    else if (nextKey == Keys.DownArrow)
                    {
                        selectedItem = Math.Min(selectedItem + 1, matches.Count - 1);
                    }
                    else if (nextKey == Keys.UpArrow)
                    {
                        selectedItem = Math.Max(selectedItem - 1, 0);
                    }
                    else if (nextKey == Keys.PageDown)
                    {
                        selectedItem = Math.Min(selectedItem + displayRows - (selectedItem % displayRows) - 1, matches.Count - 1);
                    }
                    else if (nextKey == Keys.PageUp)
                    {
                        selectedItem = Math.Max(selectedItem - (selectedItem % displayRows), 0);
                    }
                    else if (nextKey == Keys.Tab)
                    {
                        // Search for possible unAmbiguous common prefix. ...
                        string unAmbiguousText = GetUnambiguousPrefix(matches, out ambiguous);
                        int    userComplPos    = unAmbiguousText.IndexOf(userCompletionText, StringComparison.OrdinalIgnoreCase);
                        // ... If found - advance IncrementalCompletion ...
                        if (unAmbiguousText.Length > 0 && userComplPos >= 0 && unAmbiguousText.Length > (userComplPos + userCompletionText.Length))
                        {
                            userCompletionText = unAmbiguousText.Substring(userComplPos);
                            _current           = completions.ReplacementIndex + FindUserCompletinTextPosition(matches[selectedItem], userCompletionText) + userCompletionText.Length;
                            Render();
                            Ding();
                        }
                        // ... if no - usual Tab behaviour
                        else
                        {
                            selectedItem = (selectedItem + 1) % matches.Count;
                        }
                    }
                    else if (nextKey == Keys.ShiftTab)
                    {
                        selectedItem = (selectedItem - 1) % matches.Count;
                        if (selectedItem < 0)
                        {
                            selectedItem += matches.Count;
                        }
                    }
                    else if (nextKey == Keys.CtrlG || nextKey == Keys.Escape)
                    {
                        undo           = true;
                        processingKeys = false;
                        _visualSelectionCommandCount = 0;
                        _mark = savedUserMark;
                    }
                    else
                    {
                        bool prependNextKey            = false;
                        int  cursorAdjustment          = 0;
                        bool truncateCurrentCompletion = false;
                        bool keepSelection             = false;

                        if (IsDoneWithCompletions(matches[selectedItem], nextKey))
                        {
                            processingKeys = false;
                            ExchangePointAndMark(); // cursor to the end of Completion
                            if (nextKey != Keys.Enter)
                            {
                                if (matches[selectedItem].ResultType == CompletionResultType.ProviderContainer)
                                {
                                    userCompletionText = GetUnquotedText(GetReplacementTextForDirectory(matches[selectedItem].CompletionText, ref cursorAdjustment), consistentQuoting: false);
                                }
                                else
                                {
                                    userCompletionText = GetUnquotedText(matches[selectedItem], consistentQuoting: false);
                                }

                                // do not append the same char as last char in CompletionText (works for for '(', '\')
                                prependNextKey = userCompletionText[userCompletionText.Length - 1] != nextKey.KeyChar;
                            }
                        }
                        else if (nextKey == Keys.Backspace ||
                                 (nextKey.KeyChar > 0 && !char.IsControl(nextKey.KeyChar)))
                        {
                            // TODO: Shift + Backspace does not fall here ?
                            if (nextKey == Keys.Backspace)
                            {
                                if (userCompletionText.Length > userInitialCompletionLength)
                                {
                                    userCompletionText = userCompletionText.Substring(0, userCompletionText.Length - 1);
                                }
                                else if (backspaceCounter == 0)
                                {
                                    Ding();
                                    backspaceCounter++;
                                }
                                else
                                {
                                    processingKeys = false;
                                    prependNextKey = true;
                                    // we exit loop with current completion up to cursor
                                    truncateCurrentCompletion = true;
                                    if (userInitialCompletionLength == 0)
                                    {
                                        undo = true;
                                    }
                                }
                            }
                            else
                            {
                                userCompletionText += nextKey.KeyChar;
                            }
                            if (processingKeys && backspaceCounter == 0) // do not rebuild menu on backspace with Ding()
                            {
                                // filter out matches and redraw menu
                                var tmpMatches = FilterCompletions(completions.CompletionMatches, userCompletionText);
                                if (tmpMatches.Count > 0)
                                {
                                    WriteBlankLines(displayRows, menuAreaTop);
                                    var selectedCompletionText = matches[selectedItem].CompletionText;
                                    matches      = tmpMatches;
                                    previousItem = -1;
                                    menuBuffer   = CreateCompletionMenu(matches, _console, Options.ShowToolTips,
                                                                        selectedCompletionText, out selectedItem, out menuColumnWidth, out displayRows);
                                }
                                else
                                {
                                    processingKeys = false;
                                    prependNextKey = true;
                                    // we exit loop with current completion up to cursor
                                    truncateCurrentCompletion = true;
                                    if (userInitialCompletionLength == 0)
                                    {
                                        undo = true;
                                    }
                                }
                            }
                        }
                        else // exit with any other Key chord
                        {
                            processingKeys = false;
                            prependNextKey = true;

                            // without this branch experience doesnt look naturally
                            if (_dispatchTable.TryGetValue(nextKey, out var handler) &&
                                (
                                    handler.Action == CopyOrCancelLine ||
                                    handler.Action == Cut ||
                                    handler.Action == DeleteChar ||
                                    handler.Action == Paste
                                )
                                )
                            {
                                keepSelection = true;
                            }
                        }
                        if (!processingKeys) // time to exit loop
                        {
                            if (truncateCurrentCompletion && !undo)
                            {
                                CompletionResult r = new CompletionResult(matches[selectedItem].CompletionText.Substring(0, _current - completions.ReplacementIndex));
                                DoReplacementForCompletion(r, completions);
                            }
                            if (keepSelection)
                            {
                                _visualSelectionCommandCount = 1;
                            }
                            else
                            {
                                _visualSelectionCommandCount = 0;
                                // if mark was set after cursor, it restored in uninspected position, because text before mark now longer
                                // should we correct it ? I think not, beause any other text insertion does not correct it
                                _mark = savedUserMark;
                            }
                            // without render all key chords that just move cursor leave selection visible, but it can be wrong
                            if (!undo && !keepSelection)
                            {
                                Render();
                            }
                            if (prependNextKey)
                            {
                                _current -= cursorAdjustment;
                                PrependQueuedKeys(nextKey);
                            }
                        }
                    }
                    if (backspaceCounter == cycleBackspaceCounter)
                    {
                        backspaceCounter = 0;
                    }
                }

                WriteBlankLines(displayRows, menuAreaTop);

                var lastInsert = ((GroupedEdit)_edits[_edits.Count - 1])._groupedEditItems[1];
                Debug.Assert(lastInsert is EditItemInsertString, "The only edits possible here are pairs of Delete/Insert");
                var firstDelete = ((GroupedEdit)_edits[undoPoint])._groupedEditItems[0];
                Debug.Assert(firstDelete is EditItemDelete, "The only edits possible here are pairs of Delete/Insert");

                var groupEditCount = _edits.Count - undoPoint;
                _edits.RemoveRange(undoPoint, groupEditCount);
                _undoEditIndex = undoPoint;

                if (undo)
                {
                    // Pretend it never happened.
                    lastInsert.Undo();
                    firstDelete.Undo();
                    Render();
                }
                else
                {
                    // Leave one edit instead of possibly many to undo
                    SaveEditItem(GroupedEdit.Create(new List <EditItem> {
                        firstDelete, lastInsert
                    }));
                }
            }
            else
            {
                var endBufferCoords = ConvertOffsetToCoordinates(_buffer.Length);
                var menuAreaTop     = endBufferCoords.Y + 1;

                _console.WriteBufferLines(menuBuffer, ref menuAreaTop);
                _initialY = menuAreaTop + displayRows;
                Render();
            }
        }