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 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 static CHAR_INFO[] CreateCompletionMenu(System.Collections.ObjectModel.Collection <CompletionResult> matches, IConsole console, bool showToolTips, string currentCompletionText, out int currentCompletionIndex, out int MenuColumnWidth, out int DisplayRows) { var minColWidth = matches.Max(c => c.ListItemText.Length); minColWidth += 2; var bufferWidth = console.BufferWidth; var displayColumns = Math.Max(1, bufferWidth / minColWidth); currentCompletionIndex = 0; ConsoleBufferBuilder cb; if (displayColumns == 1 || showToolTips) { const string seperator = "- "; var maxTooltipWidth = bufferWidth - minColWidth - seperator.Length; // switch off tooltips if it too short if (maxTooltipWidth < 5) { minColWidth = bufferWidth; showToolTips = false; } DisplayRows = matches.Count; cb = new ConsoleBufferBuilder(DisplayRows * bufferWidth, console); for (int index = 0; index < matches.Count; index++) { var match = matches[index]; if (match.CompletionText.Equals(currentCompletionText)) { currentCompletionIndex = index; } var listItemText = ShortenLongCompletions(HandleNewlinesForPossibleCompletions(match.ListItemText), minColWidth); cb.Append(listItemText); var spacesNeeded = minColWidth - listItemText.Length; if (spacesNeeded > 0) { cb.Append(' ', spacesNeeded); } if (showToolTips) { 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 { DisplayRows = (matches.Count + displayColumns - 1) / displayColumns; cb = new ConsoleBufferBuilder(DisplayRows * bufferWidth, console); 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]; if (match.CompletionText.Equals(currentCompletionText)) { currentCompletionIndex = 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); } } MenuColumnWidth = minColWidth; } return(cb.ToArray()); }