mirror of
https://github.com/PowerShell/PSReadLine
synced 2026-06-08 12:12:13 +00:00
Merge remote-tracking branch 'upstream/master'
Conflicts: PSReadLine/PSReadLineResources.Designer.cs PSReadLine/PSReadLineResources.resx PSReadLine/UndoRedo.cs Signed-off-by: srdubya <stevewy@gmail.com>
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
+221
-49
@@ -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
|
||||
/// </summary>
|
||||
public static void Complete(ConsoleKeyInfo? key = null, object arg = null)
|
||||
{
|
||||
var completions = _singleton.GetCompletions();
|
||||
_singleton.CompleteImpl(key, arg, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display the list of possible completions.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PSConsoleUtilities
|
||||
{
|
||||
internal class ConsoleBufferBuilder
|
||||
{
|
||||
private List<CHAR_INFO> buffer;
|
||||
|
||||
public ConsoleBufferBuilder()
|
||||
{
|
||||
buffer = new List<CHAR_INFO>();
|
||||
}
|
||||
|
||||
public ConsoleBufferBuilder(int capacity)
|
||||
{
|
||||
buffer = new List<CHAR_INFO>(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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
+8
-18
@@ -63,7 +63,8 @@ namespace PSConsoleUtilities
|
||||
public static void AddToHistory(string command)
|
||||
{
|
||||
command = command.Replace("\r\n", "\n");
|
||||
_singleton.MaybeAddToHistory(command, new List<EditItem>(), 0);
|
||||
var editItems = new List<EditItem> {EditItemInsertString.Create(command, 0)};
|
||||
_singleton.MaybeAddToHistory(command, editItems, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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<ConsoleKeyInfo>(_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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
<Compile Include="BasicEditing.cs" />
|
||||
<Compile Include="Cmdlets.cs" />
|
||||
<Compile Include="Completion.cs" />
|
||||
<Compile Include="ConsoleBufferBuilder.cs" />
|
||||
<Compile Include="ConsoleKeyChordConverter.cs" />
|
||||
<Compile Include="ConsoleLib.cs" />
|
||||
<Compile Include="History.cs" />
|
||||
@@ -85,9 +86,9 @@
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="PSReadLineResources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>PSReadLineResources.Designer.cs</LastGenOutput>
|
||||
<CustomToolNamespace>PSConsoleUtilities</CustomToolNamespace>
|
||||
<SubType>Designer</SubType>
|
||||
<LastGenOutput>PSReadLineResources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
+9
@@ -591,6 +591,15 @@ namespace PSConsoleUtilities {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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..
|
||||
/// </summary>
|
||||
internal static string MenuCompleteDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("MenuCompleteDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Move to the end of the line..
|
||||
/// </summary>
|
||||
|
||||
@@ -534,4 +534,7 @@
|
||||
<data name="ViBackwardReplaceWordDescription" xml:space="preserve">
|
||||
<value>Replace the previous word.</value>
|
||||
</data>
|
||||
<data name="MenuCompleteDescription" xml:space="preserve">
|
||||
<value>Complete the input if there is a single completion, otherwise complete the input by selecting from a menu of possible completions.</value>
|
||||
</data>
|
||||
</root>
|
||||
+21
-2
@@ -31,7 +31,6 @@ namespace PSConsoleUtilities
|
||||
private readonly StringBuilder _statusBuffer;
|
||||
private string _statusLinePrompt;
|
||||
private List<EditItem> _edits;
|
||||
private int _editGroupCount;
|
||||
private readonly Stack<int> _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<ConsoleKeyInfo>(_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<EditItem>();
|
||||
_undoEditIndex = 0;
|
||||
_editGroupCount = 0;
|
||||
_pushedEditGroupCount.Clear();
|
||||
_current = 0;
|
||||
_mark = 0;
|
||||
|
||||
+25
-5
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
+7
-10
@@ -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<ConsoleKeyInfo?, object> 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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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: <Ctrl+Space> Emacs: <Alt+Equals>)
|
||||
|
||||
Display the list of possible completions.
|
||||
|
||||
@@ -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, "", "");
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -1 +1 @@
|
||||
<Playlist Version="1.0"><Add Test="UnitTestPSReadLine.UnitTest.TestHistorySearchCursorMovesToEnd" /><Add Test="UnitTestPSReadLine.UnitTest.TestBeginningOfHistory" /><Add Test="UnitTestPSReadLine.UnitTest.TestInteractiveHistorySearch" /><Add Test="UnitTestPSReadLine.UnitTest.TestHistory" /><Add Test="UnitTestPSReadLine.UnitTest.TestAddToHistoryHandler" /><Add Test="UnitTestPSReadLine.UnitTest.TestHistoryCount" /><Add Test="UnitTestPSReadLine.UnitTest.TestSearchHistory" /><Add Test="UnitTestPSReadLine.UnitTest.TestInteractiveHistorySearchNoDuplicates" /><Add Test="UnitTestPSReadLine.UnitTest.TestHistorySearchNoDuplicates" /><Add Test="UnitTestPSReadLine.UnitTest.TestHistoryNoDuplicates" /></Playlist>
|
||||
<Playlist Version="1.0"><Add Test="UnitTestPSReadLine.UnitTest.TestHistorySearchCursorMovesToEnd" /><Add Test="UnitTestPSReadLine.UnitTest.TestBeginningOfHistory" /><Add Test="UnitTestPSReadLine.UnitTest.TestInteractiveHistorySearch" /><Add Test="UnitTestPSReadLine.UnitTest.TestHistory" /><Add Test="UnitTestPSReadLine.UnitTest.TestAddToHistoryHandler" /><Add Test="UnitTestPSReadLine.UnitTest.TestHistoryCount" /><Add Test="UnitTestPSReadLine.UnitTest.TestSearchHistory" /><Add Test="UnitTestPSReadLine.UnitTest.TestInteractiveHistorySearchNoDuplicates" /><Add Test="UnitTestPSReadLine.UnitTest.TestHistorySearchNoDuplicates" /><Add Test="UnitTestPSReadLine.UnitTest.TestHistoryNoDuplicates" /><Add Test="UnitTestPSReadLine.UnitTest.TestEndOfHistory" /></Playlist>
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user