/********************************************************************++
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;
///
/// 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.
///
[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();
}
///
/// 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.
///
[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();
}
///
/// Move the cursor one character to the right. This may move the cursor to the next
/// line of multi-line input.
///
[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);
}
}
///
/// Move the cursor one character to the left. This may move the cursor to the previous
/// line of multi-line input.
///
[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();
}
}
///
/// Move the cursor to the previous line.
///
[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);
}
}
///
/// Move the cursor to the next line.
///
[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);
}
}
///
/// Move the cursor forward to the start of the next word.
/// Word boundaries are defined by a configurable set of characters.
///
[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();
}
}
///
/// 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.
///
[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();
}
}
///
/// 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.
///
[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();
}
}
///
/// Move the cursor forward to the start of the next word.
/// Word boundaries are defined by PowerShell tokens.
///
[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 action)
{
foreach (var entry in _singleton._dispatchTable)
{
if (entry.Value.Action == action)
return true;
}
return false;
}
///
/// 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.
///
[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();
}
}
///
/// 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.
///
[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();
}
}
///
/// Go to the matching brace, paren, or square bracket
///
[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();
}
///
/// Clear the screen and draw the current line at the top of the screen.
///
[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;
}
///
/// 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.
///
[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();
}
}
///
/// 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.
///
[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();
}
}
}