mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
551 lines
19 KiB
C#
551 lines
19 KiB
C#
/********************************************************************++
|
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
--********************************************************************/
|
|
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Management.Automation.Language;
|
|
using Microsoft.PowerShell.Internal;
|
|
|
|
namespace Microsoft.PowerShell
|
|
{
|
|
public partial class PSConsoleReadLine
|
|
{
|
|
private int _moveToLineCommandCount;
|
|
private int _moveToLineDesiredColumn;
|
|
|
|
/// <summary>
|
|
/// If the input has multiple lines, move to the end of the current line,
|
|
/// or if already at the end of the line, move to the end of the input.
|
|
/// If the input has a single line, move to the end of the input.
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
|
|
public static void EndOfLine(ConsoleKeyInfo? key = null, object arg = null)
|
|
{
|
|
if (_singleton.LineIsMultiLine())
|
|
{
|
|
int i = _singleton._current;
|
|
for (; i < _singleton._buffer.Length; i++)
|
|
{
|
|
if (_singleton._buffer[i] == '\n')
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
_singleton._current = (i == _singleton._current) ? _singleton._buffer.Length : i;
|
|
}
|
|
else
|
|
{
|
|
_singleton._current = _singleton._buffer.Length;
|
|
}
|
|
_singleton.PlaceCursor();
|
|
}
|
|
|
|
/// <summary>
|
|
/// If the input has multiple lines, move to the start of the current line,
|
|
/// or if already at the start of the line, move to the start of the input.
|
|
/// If the input has a single line, move to the start of the input.
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
|
|
public static void BeginningOfLine(ConsoleKeyInfo? key = null, object arg = null)
|
|
{
|
|
if (_singleton.LineIsMultiLine())
|
|
{
|
|
int i = Math.Max(0, _singleton._current - 1);
|
|
for (; i > 0; i--)
|
|
{
|
|
if (_singleton._buffer[i] == '\n')
|
|
{
|
|
i += 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
_singleton._current = (i == _singleton._current) ? 0 : i;
|
|
}
|
|
else
|
|
{
|
|
_singleton._current = 0;
|
|
}
|
|
_singleton.PlaceCursor();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move the cursor one character to the right. This may move the cursor to the next
|
|
/// line of multi-line input.
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
|
|
public static void ForwardChar(ConsoleKeyInfo? key = null, object arg = null)
|
|
{
|
|
int numericArg;
|
|
if (TryGetArgAsInt(arg, out numericArg, 1))
|
|
{
|
|
SetCursorPosition(_singleton._current + numericArg);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move the cursor one character to the left. This may move the cursor to the previous
|
|
/// line of multi-line input.
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
|
|
public static void BackwardChar(ConsoleKeyInfo? key = null, object arg = null)
|
|
{
|
|
int numericArg;
|
|
if (TryGetArgAsInt(arg, out numericArg, 1))
|
|
{
|
|
SetCursorPosition(_singleton._current - numericArg);
|
|
}
|
|
}
|
|
|
|
private void MoveToLine(int numericArg)
|
|
{
|
|
const int endOfLine = int.MaxValue;
|
|
|
|
_moveToLineCommandCount += 1;
|
|
var coords = ConvertOffsetToCoordinates(_current);
|
|
if (_moveToLineCommandCount == 1)
|
|
{
|
|
_moveToLineDesiredColumn =
|
|
(_current == _buffer.Length || _buffer[_current] == '\n')
|
|
? endOfLine
|
|
: coords.X;
|
|
}
|
|
|
|
var topLine = _initialY + Options.ExtraPromptLineCount;
|
|
|
|
var newY = coords.Y + numericArg;
|
|
coords.Y = (short)Math.Max(newY, topLine);
|
|
if (_moveToLineDesiredColumn != endOfLine)
|
|
{
|
|
coords.X = (short)_moveToLineDesiredColumn;
|
|
}
|
|
|
|
var newCurrent = ConvertLineAndColumnToOffset(coords);
|
|
if (newCurrent != -1)
|
|
{
|
|
_current = newCurrent;
|
|
if (_moveToLineDesiredColumn == endOfLine)
|
|
{
|
|
while (_current < _buffer.Length && _buffer[_current] != '\n')
|
|
{
|
|
_current += 1;
|
|
}
|
|
}
|
|
PlaceCursor();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move the cursor to the previous line.
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
|
|
public static void PreviousLine(ConsoleKeyInfo? key = null, object arg = null)
|
|
{
|
|
int numericArg;
|
|
if (TryGetArgAsInt(arg, out numericArg, 1))
|
|
{
|
|
_singleton.MoveToLine(-numericArg);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move the cursor to the next line.
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
|
|
public static void NextLine(ConsoleKeyInfo? key = null, object arg = null)
|
|
{
|
|
int numericArg;
|
|
if (TryGetArgAsInt(arg, out numericArg, 1))
|
|
{
|
|
_singleton.MoveToLine(numericArg);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move the cursor forward to the start of the next word.
|
|
/// Word boundaries are defined by a configurable set of characters.
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
|
|
public static void NextWord(ConsoleKeyInfo? key = null, object arg = null)
|
|
{
|
|
int numericArg;
|
|
if (!TryGetArgAsInt(arg, out numericArg, 1))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (numericArg < 0)
|
|
{
|
|
BackwardWord(key, -numericArg);
|
|
return;
|
|
}
|
|
|
|
while (numericArg-- > 0)
|
|
{
|
|
int i = _singleton.FindNextWordPoint(_singleton.Options.WordDelimiters);
|
|
_singleton._current = i;
|
|
_singleton.PlaceCursor();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move the cursor forward to the end of the current word, or if between words,
|
|
/// to the end of the next word. Word boundaries are defined by PowerShell tokens.
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
|
|
public static void ShellNextWord(ConsoleKeyInfo? key = null, object arg = null)
|
|
{
|
|
int numericArg;
|
|
if (!TryGetArgAsInt(arg, out numericArg, 1))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (numericArg < 0)
|
|
{
|
|
ShellBackwardWord(key, -numericArg);
|
|
return;
|
|
}
|
|
|
|
while (numericArg-- > 0)
|
|
{
|
|
var token = _singleton.FindToken(_singleton._current, FindTokenMode.Next);
|
|
|
|
Debug.Assert(token != null, "We'll always find EOF");
|
|
|
|
_singleton._current = token.Kind == TokenKind.EndOfInput
|
|
? _singleton._buffer.Length
|
|
: token.Extent.StartOffset;
|
|
_singleton.PlaceCursor();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move the cursor forward to the end of the current word, or if between words,
|
|
/// to the end of the next word. Word boundaries are defined by a configurable
|
|
/// set of characters.
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
|
|
public static void ForwardWord(ConsoleKeyInfo? key = null, object arg = null)
|
|
{
|
|
int numericArg;
|
|
if (!TryGetArgAsInt(arg, out numericArg, 1))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (numericArg < 0)
|
|
{
|
|
BackwardWord(key, -numericArg);
|
|
return;
|
|
}
|
|
|
|
while (numericArg-- > 0)
|
|
{
|
|
int i = _singleton.FindForwardWordPoint(_singleton.Options.WordDelimiters);
|
|
_singleton._current = i;
|
|
_singleton.PlaceCursor();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move the cursor forward to the start of the next word.
|
|
/// Word boundaries are defined by PowerShell tokens.
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
|
|
public static void ShellForwardWord(ConsoleKeyInfo? key = null, object arg = null)
|
|
{
|
|
int numericArg;
|
|
if (!TryGetArgAsInt(arg, out numericArg, 1))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (numericArg < 0)
|
|
{
|
|
ShellBackwardWord(key, -numericArg);
|
|
return;
|
|
}
|
|
|
|
while (numericArg-- > 0)
|
|
{
|
|
var token = _singleton.FindToken(_singleton._current, FindTokenMode.CurrentOrNext);
|
|
|
|
Debug.Assert(token != null, "We'll always find EOF");
|
|
|
|
_singleton._current = token.Kind == TokenKind.EndOfInput
|
|
? _singleton._buffer.Length
|
|
: token.Extent.EndOffset;
|
|
_singleton.PlaceCursor();
|
|
}
|
|
}
|
|
|
|
private static bool CheckIsBound(Action<ConsoleKeyInfo?, object> action)
|
|
{
|
|
foreach (var entry in _singleton._dispatchTable)
|
|
{
|
|
if (entry.Value.Action == action)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move the cursor back to the start of the current word, or if between words,
|
|
/// the start of the previous word. Word boundaries are defined by a configurable
|
|
/// set of characters.
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
|
|
public static void BackwardWord(ConsoleKeyInfo? key = null, object arg = null)
|
|
{
|
|
int numericArg;
|
|
if (!TryGetArgAsInt(arg, out numericArg, 1))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (numericArg < 0)
|
|
{
|
|
if (CheckIsBound(ForwardWord))
|
|
{
|
|
ForwardWord(key, -numericArg);
|
|
}
|
|
else
|
|
{
|
|
NextWord(key, -numericArg);
|
|
}
|
|
return;
|
|
}
|
|
|
|
while (numericArg-- > 0)
|
|
{
|
|
int i = _singleton.FindBackwardWordPoint(_singleton.Options.WordDelimiters);
|
|
_singleton._current = i;
|
|
_singleton.PlaceCursor();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move the cursor back to the start of the current word, or if between words,
|
|
/// the start of the previous word. Word boundaries are defined by PowerShell tokens.
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
|
|
public static void ShellBackwardWord(ConsoleKeyInfo? key = null, object arg = null)
|
|
{
|
|
int numericArg;
|
|
if (!TryGetArgAsInt(arg, out numericArg, 1))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (numericArg < 0)
|
|
{
|
|
if (CheckIsBound(ShellForwardWord))
|
|
{
|
|
ShellForwardWord(key, -numericArg);
|
|
}
|
|
else
|
|
{
|
|
ShellNextWord(key, -numericArg);
|
|
}
|
|
return;
|
|
}
|
|
|
|
while (numericArg-- > 0)
|
|
{
|
|
var token = _singleton.FindToken(_singleton._current, FindTokenMode.Previous);
|
|
|
|
_singleton._current = (token != null) ? token.Extent.StartOffset : 0;
|
|
_singleton.PlaceCursor();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go to the matching brace, paren, or square bracket
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
|
|
public static void GotoBrace(ConsoleKeyInfo? key = null, object arg = null)
|
|
{
|
|
if (_singleton._current >= _singleton._buffer.Length)
|
|
{
|
|
Ding();
|
|
return;
|
|
}
|
|
|
|
_singleton.MaybeParseInput();
|
|
|
|
Token token = null;
|
|
var index = 0;
|
|
for (; index < _singleton._tokens.Length; index++)
|
|
{
|
|
token = _singleton._tokens[index];
|
|
if (token.Extent.StartOffset == _singleton._current)
|
|
break;
|
|
}
|
|
|
|
TokenKind toMatch;
|
|
int direction;
|
|
switch (token.Kind)
|
|
{
|
|
case TokenKind.LParen: toMatch = TokenKind.RParen; direction = 1; break;
|
|
case TokenKind.LCurly: toMatch = TokenKind.RCurly; direction = 1; break;
|
|
case TokenKind.LBracket: toMatch = TokenKind.RBracket; direction = 1; break;
|
|
|
|
case TokenKind.RParen: toMatch = TokenKind.LParen; direction = -1; break;
|
|
case TokenKind.RCurly: toMatch = TokenKind.LCurly; direction = -1; break;
|
|
case TokenKind.RBracket: toMatch = TokenKind.LBracket; direction = -1; break;
|
|
|
|
default:
|
|
// Nothing to match (don't match inside strings/comments)
|
|
Ding();
|
|
return;
|
|
}
|
|
|
|
var matchCount = 0;
|
|
var limit = (direction > 0) ? _singleton._tokens.Length - 1 : -1;
|
|
for (; index != limit; index += direction)
|
|
{
|
|
var t = _singleton._tokens[index];
|
|
if (t.Kind == token.Kind)
|
|
{
|
|
matchCount++;
|
|
}
|
|
else if (t.Kind == toMatch)
|
|
{
|
|
matchCount--;
|
|
if (matchCount == 0)
|
|
{
|
|
_singleton._current = t.Extent.StartOffset;
|
|
_singleton.PlaceCursor();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
Ding();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear the screen and draw the current line at the top of the screen.
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
|
|
public static void ClearScreen(ConsoleKeyInfo? key = null, object arg = null)
|
|
{
|
|
var console = _singleton._console;
|
|
#if UNIX // TODO: this is not correct, it should only scroll
|
|
console.Clear();
|
|
_singleton._initialY = 0;
|
|
_singleton.Render();
|
|
#else
|
|
if (_singleton._initialY + console.WindowHeight > console.BufferHeight)
|
|
{
|
|
var scrollCount = _singleton._initialY - console.WindowTop;
|
|
console.ScrollBuffer(scrollCount);
|
|
_singleton._initialY -= scrollCount;
|
|
console.SetCursorPosition(console.CursorLeft, console.CursorTop - scrollCount);
|
|
}
|
|
else
|
|
{
|
|
console.SetWindowPosition(0, _singleton._initialY);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Try to convert the arg to a char, return 0 for failure
|
|
private static char TryGetArgAsChar(object arg)
|
|
{
|
|
if (arg is char)
|
|
{
|
|
return (char)arg;
|
|
}
|
|
|
|
var s = arg as string;
|
|
if (s != null && s.Length == 1)
|
|
{
|
|
return s[0];
|
|
}
|
|
|
|
return (char)0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read a character and search forward for the next occurence of that character.
|
|
/// If an argument is specified, search forward (or backward if negative) for the
|
|
/// nth occurence.
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
|
|
public static void CharacterSearch(ConsoleKeyInfo? key = null, object arg = null)
|
|
{
|
|
int occurence = (arg is int) ? (int)arg : 1;
|
|
if (occurence < 0)
|
|
{
|
|
CharacterSearchBackward(key, -occurence);
|
|
return;
|
|
}
|
|
|
|
char toFind = TryGetArgAsChar(arg);
|
|
if (toFind == (char)0)
|
|
{
|
|
// Should we prompt?
|
|
toFind = ReadKey().KeyChar;
|
|
}
|
|
for (int i = _singleton._current + 1; i < _singleton._buffer.Length; i++)
|
|
{
|
|
if (_singleton._buffer[i] == toFind)
|
|
{
|
|
occurence -= 1;
|
|
if (occurence == 0)
|
|
{
|
|
_singleton._current = i;
|
|
_singleton.PlaceCursor();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (occurence > 0)
|
|
{
|
|
Ding();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read a character and search backward for the next occurence of that character.
|
|
/// If an argument is specified, search backward (or forward if negative) for the
|
|
/// nth occurence.
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
|
|
public static void CharacterSearchBackward(ConsoleKeyInfo? key = null, object arg = null)
|
|
{
|
|
int occurence = (arg is int) ? (int)arg : 1;
|
|
if (occurence < 0)
|
|
{
|
|
CharacterSearch(key, -occurence);
|
|
return;
|
|
}
|
|
|
|
char toFind = TryGetArgAsChar(arg);
|
|
if (toFind == (char)0)
|
|
{
|
|
// Should we prompt?
|
|
toFind = ReadKey().KeyChar;
|
|
}
|
|
for (int i = _singleton._current - 1; i >= 0; i--)
|
|
{
|
|
if (_singleton._buffer[i] == toFind)
|
|
{
|
|
occurence -= 1;
|
|
if (occurence == 0)
|
|
{
|
|
_singleton._current = i;
|
|
_singleton.PlaceCursor();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
Ding();
|
|
}
|
|
}
|
|
}
|