/// <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(); }
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; }
private void EndEditGroup() { var groupEditCount = _edits.Count - _editGroupStart; var groupedEditItems = _edits.GetRange(_editGroupStart, groupEditCount); _edits.RemoveRange(_editGroupStart, groupEditCount); SaveEditItem(GroupedEdit.Create(groupedEditItems)); _editGroupStart = -1; }
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; }
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(); }
/// <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(); }
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 })); } }
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(); } }
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(); } }