diff --git a/PSReadLine/BasicEditing.cs b/PSReadLine/BasicEditing.cs index 0f99ff1..9aac1e9 100644 --- a/PSReadLine/BasicEditing.cs +++ b/PSReadLine/BasicEditing.cs @@ -88,7 +88,8 @@ namespace PSConsoleUtilities _singleton._consoleBuffer[i+1].BackgroundColor = Console.BackgroundColor; WriteBufferLines(_singleton._consoleBuffer, ref _singleton._initialY); - _singleton.PlaceCursor(0, coordinates.Y + 1); + var y = coordinates.Y + 1; + _singleton.PlaceCursor(0, ref y); _singleton._buffer.Clear(); // Clear so we don't actually run the input _singleton._currentHistoryIndex = _singleton._history.Count; _singleton._inputAccepted = true; @@ -216,7 +217,8 @@ namespace PSConsoleUtilities ReallyRender(); } var coordinates = ConvertOffsetToCoordinates(_current); - PlaceCursor(0, coordinates.Y + 1); + var y = coordinates.Y + 1; + PlaceCursor(0, ref y); _inputAccepted = true; return true; } diff --git a/PSReadLine/Changes.txt b/PSReadLine/Changes.txt index de1b007..051de94 100644 --- a/PSReadLine/Changes.txt +++ b/PSReadLine/Changes.txt @@ -1,3 +1,9 @@ + +### Version 1.0.0.9 + +Bug fixes: +* Items loaded from history work with RevertLine + ### Version 1.0.0.8 New features: diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 1588e65..cd56cd6 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Text; -using System.Text.RegularExpressions; using PSConsoleUtilities.Internal; namespace PSConsoleUtilities @@ -83,11 +82,27 @@ namespace PSConsoleUtilities /// public static void Complete(ConsoleKeyInfo? key = null, object arg = null) { - var completions = _singleton.GetCompletions(); + _singleton.CompleteImpl(key, arg, false); + } + + /// + /// Attempt to perform completion on the text surrounding the cursor. + /// If there are multiple possible completions, the longest unambiguous + /// prefix is used for completion. If trying to complete the longest + /// unambiguous completion, a list of possible completions is displayed. + /// + public static void MenuComplete(ConsoleKeyInfo? key = null, object arg = null) + { + _singleton.CompleteImpl(key, arg, true); + } + + private void CompleteImpl(ConsoleKeyInfo? key, object arg, bool menuSelect) + { + var completions = GetCompletions(); if (completions == null || completions.CompletionMatches.Count == 0) return; - if (_singleton._tabCommandCount > 0) + if (_tabCommandCount > 0) { if (completions.CompletionMatches.Count == 1) { @@ -95,7 +110,7 @@ namespace PSConsoleUtilities } else { - PossibleCompletions(); + PossibleCompletionsImpl(completions, menuSelect); } return; } @@ -107,7 +122,13 @@ namespace PSConsoleUtilities // completions, then we'll be showing the possible completions where it's very // unlikely that we would add a trailing backslash. - _singleton.DoReplacementForCompletion(completions.CompletionMatches[0], completions); + DoReplacementForCompletion(completions.CompletionMatches[0], completions); + return; + } + + if (menuSelect) + { + PossibleCompletionsImpl(completions, true); return; } @@ -159,10 +180,10 @@ namespace PSConsoleUtilities { // No common prefix, don't wait for a second tab, just show the possible completions // right away. - PossibleCompletions(); + PossibleCompletionsImpl(completions, false); } - _singleton._tabCommandCount += 1; + _tabCommandCount += 1; } private CommandCompletion GetCompletions() @@ -259,96 +280,247 @@ namespace PSConsoleUtilities return replacementText; } + private static void InvertSelectedCompletion(CHAR_INFO[] buffer, int selectedItem, int menuColumnWidth, int menuRows) + { + var selectedX = selectedItem / menuRows; + var selectedY = selectedItem - (selectedX * menuRows); + var start = selectedY * Console.BufferWidth + selectedX * menuColumnWidth; + for (int i = 0; i < menuColumnWidth; i++) + { + int j = i + start; + buffer[j].ForegroundColor = (ConsoleColor)((int)buffer[j].ForegroundColor ^ 7); + buffer[j].BackgroundColor = (ConsoleColor)((int)buffer[j].BackgroundColor ^ 7); + } + } + /// /// Display the list of possible completions. /// public static void PossibleCompletions(ConsoleKeyInfo? key = null, object arg = null) { var completions = _singleton.GetCompletions(); + _singleton.PossibleCompletionsImpl(completions, menuSelect: false); + } + + private static string HandleNewlinesForPossibleCompletions(string s) + { + s = s.Trim(); + var newlineIndex = s.IndexOfAny(new []{'\r', '\n'}); + if (newlineIndex >= 0) + { + s = s.Substring(0, newlineIndex) + "..."; + } + return s; + } + + private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSelect) + { if (completions == null || completions.CompletionMatches.Count == 0) { Ding(); return; } - if (completions.CompletionMatches.Count >= _singleton._options.CompletionQueryItems) + if (completions.CompletionMatches.Count >= _options.CompletionQueryItems) { - if (!_singleton.PromptYesOrNo(string.Format(PSReadLineResources.DisplayAllPossibilities, completions.CompletionMatches.Count))) + if (!PromptYesOrNo(string.Format(PSReadLineResources.DisplayAllPossibilities, completions.CompletionMatches.Count))) { return; } } - // Don't overwrite any of the line - so move to first line after the end of our buffer. - var coords = _singleton.ConvertOffsetToCoordinates(_singleton._buffer.Length); - _singleton.PlaceCursor(0, coords.Y + 1); - - var sb = new StringBuilder(); - var matches = completions.CompletionMatches.Select( - completion => - new {ListItemText = Regex.Replace(completion.ListItemText, "\n.*", "..."), - ToolTip = Regex.Replace(completion.ToolTip, "\n.*", "...")}) - .ToArray(); + var matches = completions.CompletionMatches; var minColWidth = matches.Max(c => c.ListItemText.Length); minColWidth += 2; + var menuColumnWidth = minColWidth; - if (_singleton.Options.ShowToolTips) + int displayRows; + var bufferWidth = Console.BufferWidth; + ConsoleBufferBuilder cb; + if (Options.ShowToolTips) { const string seperator = "- "; - var maxTooltipWidth = Console.BufferWidth - minColWidth - seperator.Length; + var maxTooltipWidth = bufferWidth - minColWidth - seperator.Length; - foreach (var match in matches) + displayRows = matches.Count; + cb = new ConsoleBufferBuilder(displayRows * bufferWidth); + for (int index = 0; index < matches.Count; index++) { - sb.Append(match.ListItemText); - var spacesNeeded = minColWidth - match.ListItemText.Length; - if (spacesNeeded > 0) - sb.Append(' ', spacesNeeded); - sb.Append(seperator); - var toolTip = match.ToolTip.Length <= maxTooltipWidth - ? match.ToolTip - : match.ToolTip.Substring(0, maxTooltipWidth); - sb.Append(toolTip.Trim()); - - // Make sure we always write out exactly 1 buffer width - spacesNeeded = Console.BufferWidth - sb.Length; + var match = matches[index]; + var listItemText = HandleNewlinesForPossibleCompletions(match.ListItemText); + cb.Append(listItemText); + var spacesNeeded = minColWidth - listItemText.Length; if (spacesNeeded > 0) { - sb.Append(' ', spacesNeeded); + 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); } - Console.Write(sb.ToString()); - sb.Clear(); } + menuColumnWidth = bufferWidth; } else { - var screenColumns = Console.BufferWidth; + var screenColumns = bufferWidth; var displayColumns = Math.Max(1, screenColumns / minColWidth); - var displayRows = (completions.CompletionMatches.Count + displayColumns - 1) / displayColumns; + 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.Length) + if (index >= matches.Count) break; - var item = matches[index].ListItemText; - sb.Append(item); - sb.Append(' ', minColWidth - item.Length); + 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 = Console.BufferWidth - sb.Length; + var spacesNeeded = (bufferWidth * (row + 1)) - cb.Length; if (spacesNeeded > 0) { - sb.Append(' ', spacesNeeded); + cb.Append(' ', spacesNeeded); } - Console.Write(sb.ToString()); - sb.Clear(); } } - _singleton._initialY = Console.CursorTop; - _singleton.Render(); + 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) + { + StartEditGroup(); + + 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); + + if (previousMenuTop != menuAreaTop) + { + // Showing the menu scrolled the screen, 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); + + EndEditGroup(); + + if (undo) + { + // Pretend it never happened. + Undo(); + } + } + else + { + var endBufferCoords = ConvertOffsetToCoordinates(_buffer.Length); + var menuAreaTop = endBufferCoords.Y + 1; + + WriteBufferLines(menuBuffer, ref menuAreaTop); + _initialY = menuAreaTop + displayRows; + Render(); + } } } } diff --git a/PSReadLine/ConsoleBufferBuilder.cs b/PSReadLine/ConsoleBufferBuilder.cs new file mode 100644 index 0000000..34331a6 --- /dev/null +++ b/PSReadLine/ConsoleBufferBuilder.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; + +namespace PSConsoleUtilities +{ + internal class ConsoleBufferBuilder + { + private List buffer; + + public ConsoleBufferBuilder() + { + buffer = new List(); + } + + public ConsoleBufferBuilder(int capacity) + { + buffer = new List(capacity); + } + + CHAR_INFO NewCharInfo(char c) + { + return new CHAR_INFO + { + UnicodeChar = c, + BackgroundColor = Console.BackgroundColor, + ForegroundColor = Console.ForegroundColor + }; + } + public void Append(char c) + { + buffer.Add(NewCharInfo(c)); + } + + public void Append(string s) + { + foreach (char c in s) + { + buffer.Add(NewCharInfo(c)); + } + } + + public void Append(char c, int count) + { + while (count-- > 0) + { + buffer.Add(NewCharInfo(c)); + } + } + + public CHAR_INFO[] ToArray() + { + return buffer.ToArray(); + } + + public int Length + { + get { return buffer.Count; } + } + } +} + diff --git a/PSReadLine/ConsoleLib.cs b/PSReadLine/ConsoleLib.cs index 18ab214..4e9570d 100644 --- a/PSReadLine/ConsoleLib.cs +++ b/PSReadLine/ConsoleLib.cs @@ -33,7 +33,7 @@ namespace PSConsoleUtilities [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern bool ScrollConsoleScreenBuffer(IntPtr hConsoleOutput, ref SMALL_RECT lpScrollRectangle, - ref SMALL_RECT lpClipRectangle, + IntPtr lpClipRectangle, COORD dwDestinationOrigin, ref CHAR_INFO lpFill); diff --git a/PSReadLine/History.cs b/PSReadLine/History.cs index 85d329c..b46728d 100644 --- a/PSReadLine/History.cs +++ b/PSReadLine/History.cs @@ -63,7 +63,8 @@ namespace PSConsoleUtilities public static void AddToHistory(string command) { command = command.Replace("\r\n", "\n"); - _singleton.MaybeAddToHistory(command, new List(), 0); + var editItems = new List {EditItemInsertString.Create(command, 0)}; + _singleton.MaybeAddToHistory(command, editItems, 0); } /// @@ -127,7 +128,7 @@ namespace PSConsoleUtilities while (true) { newHistoryIndex = newHistoryIndex + direction; - if (newHistoryIndex < 0 && newHistoryIndex >= _history.Count) + if (newHistoryIndex < 0 || newHistoryIndex >= _history.Count) { break; } @@ -149,12 +150,11 @@ namespace PSConsoleUtilities newHistoryIndex = _currentHistoryIndex + direction; } _recallHistoryCommandCount += 1; - if (newHistoryIndex >= 0 && newHistoryIndex < _history.Count) + if (newHistoryIndex >= 0 && newHistoryIndex <= _history.Count) { _currentHistoryIndex = newHistoryIndex; UpdateFromHistory(moveCursor: true); } - } /// @@ -189,9 +189,9 @@ namespace PSConsoleUtilities } _searchHistoryCommandCount += 1; - for (int i = _currentHistoryIndex + direction; i >=0 && i < _history.Count; i += direction) + for (int i = _currentHistoryIndex + direction; i >= 0 && i <= _history.Count; i += direction) { - var line = _history[i]._line; + var line = i == _history.Count ? _savedCurrentLine._line : _history[i]._line; if (line.StartsWith(_searchHistoryPrefix, Options.HistoryStringComparison)) { if (Options.HistoryNoDuplicates) @@ -376,18 +376,7 @@ namespace PSConsoleUtilities } else if (EndInteractiveHistorySearch(key, function)) { - if (_queuedKeys.Count > 0) - { - // This should almost never happen so being inefficient is fine. - var list = new List(_queuedKeys); - _queuedKeys.Clear(); - _queuedKeys.Enqueue(key); - list.ForEach(k => _queuedKeys.Enqueue(k)); - } - else - { - _queuedKeys.Enqueue(key); - } + PrependQueuedKeys(key); break; } else @@ -441,6 +430,7 @@ namespace PSConsoleUtilities InteractiveHistorySearchLoop(direction); _hashedHistory = null; + _currentHistoryIndex = _history.Count; // Remove our status line _statusBuffer.Clear(); diff --git a/PSReadLine/KeyBindings.cs b/PSReadLine/KeyBindings.cs index 1422dca..fa1e0b8 100644 --- a/PSReadLine/KeyBindings.cs +++ b/PSReadLine/KeyBindings.cs @@ -108,7 +108,6 @@ namespace PSConsoleUtilities { Keys.CtrlSpace, MakeKeyHandler(PossibleCompletions, "PossibleCompletions") }, { Keys.Tab, MakeKeyHandler(TabCompleteNext, "TabCompleteNext") }, { Keys.ShiftTab, MakeKeyHandler(TabCompletePrevious, "TabCompletePrevious") }, - { Keys.CtrlV, MakeKeyHandler(Paste, "Paste") }, { Keys.VolumeDown, MakeKeyHandler(Ignore, "Ignore") }, { Keys.VolumeUp, MakeKeyHandler(Ignore, "Ignore") }, { Keys.VolumeMute, MakeKeyHandler(Ignore, "Ignore") }, @@ -118,6 +117,7 @@ namespace PSConsoleUtilities { Keys.CtrlL, MakeKeyHandler(ClearScreen, "ClearScreen") }, { Keys.CtrlR, MakeKeyHandler(ReverseSearchHistory, "ReverseSearchHistory") }, { Keys.CtrlS, MakeKeyHandler(ForwardSearchHistory, "ForwardSearchHistory") }, + { Keys.CtrlV, MakeKeyHandler(Paste, "Paste") }, { Keys.CtrlX, MakeKeyHandler(Cut, "Cut") }, { Keys.CtrlY, MakeKeyHandler(Redo, "Redo") }, { Keys.CtrlZ, MakeKeyHandler(Undo, "Undo") }, @@ -275,7 +275,8 @@ namespace PSConsoleUtilities // Don't overwrite any of the line - so move to first line after the end of our buffer. var coords = _singleton.ConvertOffsetToCoordinates(_singleton._buffer.Length); - _singleton.PlaceCursor(0, coords.Y + 1); + var y = coords.Y + 1; + _singleton.PlaceCursor(0, ref y); Console.WriteLine(buffer.ToString()); _singleton._initialY = Console.CursorTop; @@ -334,7 +335,8 @@ namespace PSConsoleUtilities // Don't overwrite any of the line - so move to first line after the end of our buffer. var coords = _singleton.ConvertOffsetToCoordinates(_singleton._buffer.Length); - _singleton.PlaceCursor(0, coords.Y + 1); + var y = coords.Y + 1; + _singleton.PlaceCursor(0, ref y); Console.WriteLine(buffer.ToString()); _singleton._initialY = Console.CursorTop; diff --git a/PSReadLine/PSReadLine.csproj b/PSReadLine/PSReadLine.csproj index 277968a..c67ac5b 100644 --- a/PSReadLine/PSReadLine.csproj +++ b/PSReadLine/PSReadLine.csproj @@ -53,6 +53,7 @@ + @@ -85,9 +86,9 @@ ResXFileCodeGenerator - PSReadLineResources.Designer.cs PSConsoleUtilities Designer + PSReadLineResources.Designer.cs diff --git a/PSReadLine/PSReadLineResources.Designer.cs b/PSReadLine/PSReadLineResources.Designer.cs index 65b4914..159ba9d 100644 --- a/PSReadLine/PSReadLineResources.Designer.cs +++ b/PSReadLine/PSReadLineResources.Designer.cs @@ -591,6 +591,15 @@ namespace PSConsoleUtilities { } } + /// + /// Looks up a localized string similar to Complete the input if there is a single completion, otherwise complete the input by selecting from a menu of possible completions.. + /// + internal static string MenuCompleteDescription { + get { + return ResourceManager.GetString("MenuCompleteDescription", resourceCulture); + } + } + /// /// Looks up a localized string similar to Move to the end of the line.. /// diff --git a/PSReadLine/PSReadLineResources.resx b/PSReadLine/PSReadLineResources.resx index 64cb7da..8f6f3fe 100644 --- a/PSReadLine/PSReadLineResources.resx +++ b/PSReadLine/PSReadLineResources.resx @@ -534,4 +534,7 @@ Replace the previous word. + + Complete the input if there is a single completion, otherwise complete the input by selecting from a menu of possible completions. + \ No newline at end of file diff --git a/PSReadLine/ReadLine.cs b/PSReadLine/ReadLine.cs index 85e84ea..fae21ce 100644 --- a/PSReadLine/ReadLine.cs +++ b/PSReadLine/ReadLine.cs @@ -31,7 +31,6 @@ namespace PSConsoleUtilities private readonly StringBuilder _statusBuffer; private string _statusLinePrompt; private List _edits; - private int _editGroupCount; private readonly Stack _pushedEditGroupCount; private int _undoEditIndex; private int _mark; @@ -124,6 +123,22 @@ namespace PSConsoleUtilities return key; } + private void PrependQueuedKeys(ConsoleKeyInfo key) + { + if (_queuedKeys.Count > 0) + { + // This should almost never happen so being inefficient is fine. + var list = new List(_queuedKeys); + _queuedKeys.Clear(); + _queuedKeys.Enqueue(key); + list.ForEach(k => _queuedKeys.Enqueue(k)); + } + else + { + _queuedKeys.Enqueue(key); + } + } + private bool BreakHandler(ConsoleBreakSignal signal) { if (signal == ConsoleBreakSignal.Close || signal == ConsoleBreakSignal.Shutdown) @@ -215,12 +230,17 @@ namespace PSConsoleUtilities _emphasisStart = -1; _emphasisLength = 0; Render(); + _currentHistoryIndex = _history.Count; } _searchHistoryCommandCount = 0; _searchHistoryPrefix = null; } if (recallHistoryCommandCount == _recallHistoryCommandCount) { + if (_recallHistoryCommandCount > 0) + { + _currentHistoryIndex = _history.Count; + } _recallHistoryCommandCount = 0; } if (searchHistoryCommandCount == _searchHistoryCommandCount && @@ -315,7 +335,6 @@ namespace PSConsoleUtilities _buffer.Clear(); _edits = new List(); _undoEditIndex = 0; - _editGroupCount = 0; _pushedEditGroupCount.Clear(); _current = 0; _mark = 0; diff --git a/PSReadLine/Render.cs b/PSReadLine/Render.cs index 76176a7..2c7f73a 100644 --- a/PSReadLine/Render.cs +++ b/PSReadLine/Render.cs @@ -255,15 +255,34 @@ namespace PSConsoleUtilities Y = (short) bufferLineCount }; var bufferCoord = new COORD {X = 0, Y = 0}; + var bottom = top + bufferLineCount - 1; var writeRegion = new SMALL_RECT { Top = (short) top, Left = 0, - Bottom = (short) (top + bufferLineCount - 1), + Bottom = (short) bottom, Right = (short) bufferWidth }; NativeMethods.WriteConsoleOutput(handle, buffer, bufferSize, bufferCoord, ref writeRegion); + + // Now make sure the bottom line is visible + if (bottom >= (Console.WindowTop + Console.WindowHeight)) + { + Console.CursorTop = bottom; + } + } + + private static void WriteBlankLines(int count, int top) + { + var blanks = new CHAR_INFO[count * Console.BufferWidth]; + for (int i = 0; i < count; i++) + { + blanks[i].BackgroundColor = Console.BackgroundColor; + blanks[i].ForegroundColor = Console.ForegroundColor; + blanks[i].UnicodeChar = ' '; + } + WriteBufferLines(blanks, ref top); } private static CHAR_INFO[] ReadBufferLines(int top, int count) @@ -420,15 +439,15 @@ namespace PSConsoleUtilities { Top = (short) lines, Left = 0, - Bottom = (short) (lines + Console.BufferHeight - 1), + Bottom = (short) (Console.BufferHeight - 1), Right = (short)Console.BufferWidth }; var destinationOrigin = new COORD {X = 0, Y = 0}; var fillChar = new CHAR_INFO(' ', Console.ForegroundColor, Console.BackgroundColor); - NativeMethods.ScrollConsoleScreenBuffer(handle, ref scrollRectangle, ref scrollRectangle, destinationOrigin, ref fillChar); + NativeMethods.ScrollConsoleScreenBuffer(handle, ref scrollRectangle, IntPtr.Zero, destinationOrigin, ref fillChar); } - private void PlaceCursor(int x, int y) + private void PlaceCursor(int x, ref int y) { int statusLineCount = GetStatusLineCount(); if ((y + _demoWindowLineCount + statusLineCount) >= Console.BufferHeight) @@ -442,7 +461,8 @@ namespace PSConsoleUtilities private void PlaceCursor() { var coordinates = ConvertOffsetToCoordinates(_current); - PlaceCursor(coordinates.X, coordinates.Y); + int y = coordinates.Y; + PlaceCursor(coordinates.X, ref y); } private COORD ConvertOffsetToCoordinates(int offset) diff --git a/PSReadLine/SamplePSReadlineProfile.ps1 b/PSReadLine/SamplePSReadlineProfile.ps1 index 1f99189..9628b40 100644 --- a/PSReadLine/SamplePSReadlineProfile.ps1 +++ b/PSReadLine/SamplePSReadlineProfile.ps1 @@ -355,3 +355,50 @@ Set-PSReadlineKeyHandler -Key F1 ` } } } + + +# +# Ctrl+Shift+j then type a key to mark the current directory. +# Ctrj+j then the same key will change back to that directory without +# needing to type cd and won't change the command line. + +# +$global:PSReadlineMarks = @{} + +Set-PSReadlineKeyHandler -Key Ctrl+Shift+j ` + -BriefDescription MarkDirectory ` + -LongDescription "Mark the current directory" ` + -ScriptBlock { + param($key, $arg) + + $key = [Console]::ReadKey($true) + $global:PSReadlineMarks[$key.KeyChar] = $pwd +} + +Set-PSReadlineKeyHandler -Key Ctrl+j ` + -BriefDescription JumpDirectory ` + -LongDescription "Goto the marked directory" ` + -ScriptBlock { + param($key, $arg) + + $key = [Console]::ReadKey() + $dir = $global:PSReadlineMarks[$key.KeyChar] + if ($dir) + { + cd $dir + [PSConsoleUtilities.PSConsoleReadLine]::InvokePrompt() + } +} + +Set-PSReadlineKeyHandler -Key Alt+j ` + -BriefDescription ShowDirectoryMarks ` + -LongDescription "Show the currently marked directories" ` + -ScriptBlock { + param($key, $arg) + + $global:PSReadlineMarks.GetEnumerator() | % { + [PSCustomObject]@{Key = $_.Key; Dir = $_.Value} } | + Format-Table -AutoSize | Out-Host + + [PSConsoleUtilities.PSConsoleReadLine]::InvokePrompt() +} diff --git a/PSReadLine/UndoRedo.cs b/PSReadLine/UndoRedo.cs index f3997db..1423f27 100644 --- a/PSReadLine/UndoRedo.cs +++ b/PSReadLine/UndoRedo.cs @@ -9,7 +9,7 @@ namespace PSConsoleUtilities private void SaveEditItem(EditItem editItem) { // If there is some sort of edit after an undo, forget - // the + // any edit items that were undone. int removeCount = _edits.Count - _undoEditIndex; if (removeCount > 0) { @@ -17,23 +17,20 @@ namespace PSConsoleUtilities } _edits.Add(editItem); _undoEditIndex = _edits.Count; - _editGroupCount++; } private void StartEditGroup() { - _pushedEditGroupCount.Push(_editGroupCount); - _editGroupCount = 0; + _pushedEditGroupCount.Push(_edits.Count); } private void EndEditGroup(Action instigator = null, object instigatorArg = null) { - // 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, instigator, instigatorArg)); - _editGroupCount = _pushedEditGroupCount.Pop(); + var groupEditStart = _pushedEditGroupCount.Pop(); + var groupEditCount = _edits.Count - groupEditStart; + var groupedEditItems = _edits.GetRange(groupEditStart, groupEditCount); + _edits.RemoveRange(groupEditStart, groupEditCount); + SaveEditItem(GroupedEdit.Create(groupedEditItems)); } /// diff --git a/PSReadLine/en-US/about_PSReadline.help.txt b/PSReadLine/en-US/about_PSReadline.help.txt index 8c129c6..98e01bc 100644 --- a/PSReadLine/en-US/about_PSReadline.help.txt +++ b/PSReadLine/en-US/about_PSReadline.help.txt @@ -247,6 +247,15 @@ LONG DESCRIPTION prefix is used for completion. If trying to complete the longest unambiguous completion, a list of possible completions is displayed. + MenuComplete (Cmd: unbound Emacs: unbound) + + Attempt to perform completion on the text surrounding the cursor. + If there are multiple possible completions, a list of possible + completions is displayed and you can select the correct completion + using the arrow keys or Tab/Shift+Tab. Escape and Ctrl+G cancel + the menu selection and reverts the line to the state before invoking + MenuComplete. + PossibleCompletions (Cmd: Emacs: ) Display the list of possible completions. diff --git a/TestPSReadLine/Program.cs b/TestPSReadLine/Program.cs index 173863e..860637c 100644 --- a/TestPSReadLine/Program.cs +++ b/TestPSReadLine/Program.cs @@ -100,10 +100,13 @@ namespace TestPSReadLine PSConsoleReadLine.SetOptions(new SetPSReadlineOption { - EditMode = EditMode.Emacs, + EditMode = EditMode.Windows, + HistoryNoDuplicates = true, }); - //PSConsoleReadLine.SetKeyHandler(new[] {"UpArrow"}, PSConsoleReadLine.HistorySearchBackward, "", ""); - //PSConsoleReadLine.SetKeyHandler(new[] {"DownArrow"}, PSConsoleReadLine.HistorySearchForward, "", ""); + PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+LeftArrow"}, PSConsoleReadLine.ShellBackwardWord, "", ""); + PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+RightArrow"}, PSConsoleReadLine.ShellNextWord, "", ""); + PSConsoleReadLine.SetKeyHandler(new[] {"UpArrow"}, PSConsoleReadLine.HistorySearchBackward, "", ""); + PSConsoleReadLine.SetKeyHandler(new[] {"DownArrow"}, PSConsoleReadLine.HistorySearchForward, "", ""); //PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+D,Ctrl+E"}, PSConsoleReadLine.EnableDemoMode, "", ""); //PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+D,Ctrl+D"}, PSConsoleReadLine.DisableDemoMode, "", ""); //PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+D,Ctrl+C"}, PSConsoleReadLine.CaptureScreen, "", ""); diff --git a/UnitTestPSReadLine/HistoryTest.cs b/UnitTestPSReadLine/HistoryTest.cs index 0fbbcd9..5948607 100644 --- a/UnitTestPSReadLine/HistoryTest.cs +++ b/UnitTestPSReadLine/HistoryTest.cs @@ -34,6 +34,26 @@ namespace UnitTestPSReadLine Test("dir c*", Keys(_.UpArrow, _.UpArrow, _.DownArrow)); } + [TestMethod] + public void TestHistoryRecallCurrentLine() + { + TestSetup(KeyMode.Cmd); + + SetHistory("echo foo", "echo bar"); + Test("ec", Keys("ec", _.UpArrow, _.UpArrow, _.DownArrow, _.DownArrow)); + } + + [TestMethod] + public void TestHistorySearchCurrentLine() + { + TestSetup(KeyMode.Cmd, + new KeyHandler("UpArrow", PSConsoleReadLine.HistorySearchBackward), + new KeyHandler("DownArrow", PSConsoleReadLine.HistorySearchForward)); + + SetHistory("echo foo", "echo bar"); + Test("ec", Keys("ec", _.UpArrow, _.UpArrow, _.DownArrow, _.DownArrow)); + } + [TestMethod] public void TestSearchHistory() { @@ -422,6 +442,10 @@ namespace UnitTestPSReadLine Test("cccc", Keys( Enumerable.Repeat(_.UpArrow, 3), Enumerable.Repeat(_.DownArrow, 2))); + + // No history + SetHistory(); + Test("", Keys(_.UpArrow, _.DownArrow)); } [TestMethod] diff --git a/UnitTestPSReadLine/HistoryTests.playlist b/UnitTestPSReadLine/HistoryTests.playlist index faaa7b3..d1da242 100644 --- a/UnitTestPSReadLine/HistoryTests.playlist +++ b/UnitTestPSReadLine/HistoryTests.playlist @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/UnitTestPSReadLine/UnitTestReadLine.cs b/UnitTestPSReadLine/UnitTestReadLine.cs index 6fcb770..9d9ba7f 100644 --- a/UnitTestPSReadLine/UnitTestReadLine.cs +++ b/UnitTestPSReadLine/UnitTestReadLine.cs @@ -455,7 +455,7 @@ namespace UnitTestPSReadLine }; var destinationOrigin = new COORD {X = 0, Y = 0}; var fillChar = new CHAR_INFO(' ', Console.ForegroundColor, Console.BackgroundColor); - NativeMethods.ScrollConsoleScreenBuffer(handle, ref scrollRectangle, ref scrollRectangle, destinationOrigin, ref fillChar); + NativeMethods.ScrollConsoleScreenBuffer(handle, ref scrollRectangle, IntPtr.Zero, destinationOrigin, ref fillChar); } private static void WriteBufferLines(CHAR_INFO[] buffer, ref int top)