/********************************************************************++ 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(); } } }