Example #1
0
        public SecureString ReadSecureLine(CancellationToken cancellationToken)
        {
            Console.TreatControlCAsInput = true;
            int          previousInputLength = 0;
            SecureString secureString        = new();

            try
            {
                bool enterPressed = false;
                while (!enterPressed && !cancellationToken.IsCancellationRequested)
                {
                    ConsoleKeyInfo keyInfo = ReadKey(cancellationToken);

                    if (keyInfo.IsCtrlC())
                    {
                        throw new PipelineStoppedException();
                    }

                    switch (keyInfo.Key)
                    {
                    case ConsoleKey.Enter:
                        // Stop the while loop so we can realign the cursor
                        // and then return the entered string
                        enterPressed = true;
                        continue;

                    case ConsoleKey.Tab:
                        break;

                    case ConsoleKey.Backspace:
                        if (secureString.Length > 0)
                        {
                            secureString.RemoveAt(secureString.Length - 1);
                        }
                        break;

                    default:
                        if (keyInfo.KeyChar != 0 && !char.IsControl(keyInfo.KeyChar))
                        {
                            secureString.AppendChar(keyInfo.KeyChar);
                        }
                        break;
                    }

                    // Re-render the secure string characters
                    int currentInputLength = secureString.Length;
                    int consoleWidth       = Console.WindowWidth;

                    if (currentInputLength > previousInputLength)
                    {
                        Console.Write('*');
                    }
                    else if (previousInputLength > 0 && currentInputLength < previousInputLength)
                    {
                        int row = ConsoleProxy.GetCursorTop(cancellationToken);
                        int col = ConsoleProxy.GetCursorLeft(cancellationToken);

                        // Back up the cursor before clearing the character
                        col--;
                        if (col < 0)
                        {
                            col = consoleWidth - 1;
                            row--;
                        }

                        Console.SetCursorPosition(col, row);
                        Console.Write(' ');
                        Console.SetCursorPosition(col, row);
                    }

                    previousInputLength = currentInputLength;
                }
            }
            finally
            {
                Console.TreatControlCAsInput = false;
            }

            return(secureString);
        }
Example #2
0
        public override string ReadLine(CancellationToken cancellationToken)
        {
            string            inputBeforeCompletion = null;
            string            inputAfterCompletion  = null;
            CommandCompletion currentCompletion     = null;

            int historyIndex = -1;
            IReadOnlyList <PSObject> currentHistory = null;

            StringBuilder inputLine = new();

            int initialCursorCol = Console.CursorLeft;
            int initialCursorRow = Console.CursorTop;

            int currentCursorIndex = 0;

            Console.TreatControlCAsInput = true;

            try
            {
                while (!cancellationToken.IsCancellationRequested)
                {
                    ConsoleKeyInfo keyInfo = ReadKey(cancellationToken);

                    // Do final position calculation after the key has been pressed
                    // because the window could have been resized before then
                    int promptStartCol = initialCursorCol;
                    int promptStartRow = initialCursorRow;
                    int consoleWidth   = Console.WindowWidth;

                    switch (keyInfo.Key)
                    {
                    case ConsoleKey.Tab:
                        if (currentCompletion is null)
                        {
                            inputBeforeCompletion = inputLine.ToString();
                            inputAfterCompletion  = null;

                            // TODO: This logic should be moved to AstOperations or similar!

                            if (_psesHost.DebugContext.IsStopped)
                            {
                                PSCommand command = new PSCommand()
                                                    .AddCommand("TabExpansion2")
                                                    .AddParameter("InputScript", inputBeforeCompletion)
                                                    .AddParameter("CursorColumn", currentCursorIndex)
                                                    .AddParameter("Options", null);

                                currentCompletion = _psesHost.InvokePSCommand <CommandCompletion>(command, executionOptions: null, cancellationToken).FirstOrDefault();
                            }
                            else
                            {
                                currentCompletion = _psesHost.InvokePSDelegate(
                                    "Legacy readline inline command completion",
                                    executionOptions: null,
                                    (pwsh, _) => CommandCompletion.CompleteInput(inputAfterCompletion, currentCursorIndex, options: null, pwsh),
                                    cancellationToken);

                                if (currentCompletion.CompletionMatches.Count > 0)
                                {
                                    int replacementEndIndex =
                                        currentCompletion.ReplacementIndex +
                                        currentCompletion.ReplacementLength;

                                    inputAfterCompletion =
                                        inputLine.ToString(
                                            replacementEndIndex,
                                            inputLine.Length - replacementEndIndex);
                                }
                                else
                                {
                                    currentCompletion = null;
                                }
                            }
                        }

                        CompletionResult completion =
                            currentCompletion?.GetNextResult(
                                !keyInfo.Modifiers.HasFlag(ConsoleModifiers.Shift));

                        if (completion is not null)
                        {
                            currentCursorIndex =
                                InsertInput(
                                    inputLine,
                                    promptStartCol,
                                    promptStartRow,
                                    completion.CompletionText + inputAfterCompletion,
                                    currentCursorIndex,
                                    insertIndex: currentCompletion.ReplacementIndex,
                                    replaceLength: inputLine.Length - currentCompletion.ReplacementIndex,
                                    finalCursorIndex: currentCompletion.ReplacementIndex + completion.CompletionText.Length);
                        }

                        continue;

                    case ConsoleKey.LeftArrow:
                        currentCompletion = null;

                        if (currentCursorIndex > 0)
                        {
                            currentCursorIndex =
                                MoveCursorToIndex(
                                    promptStartCol,
                                    promptStartRow,
                                    consoleWidth,
                                    currentCursorIndex - 1);
                        }

                        continue;

                    case ConsoleKey.Home:
                        currentCompletion = null;

                        currentCursorIndex =
                            MoveCursorToIndex(
                                promptStartCol,
                                promptStartRow,
                                consoleWidth,
                                0);

                        continue;

                    case ConsoleKey.RightArrow:
                        currentCompletion = null;

                        if (currentCursorIndex < inputLine.Length)
                        {
                            currentCursorIndex =
                                MoveCursorToIndex(
                                    promptStartCol,
                                    promptStartRow,
                                    consoleWidth,
                                    currentCursorIndex + 1);
                        }

                        continue;

                    case ConsoleKey.End:
                        currentCompletion = null;

                        currentCursorIndex =
                            MoveCursorToIndex(
                                promptStartCol,
                                promptStartRow,
                                consoleWidth,
                                inputLine.Length);

                        continue;

                    case ConsoleKey.UpArrow:
                        currentCompletion = null;

                        // TODO: Ctrl+Up should allow navigation in multi-line input
                        if (currentHistory is null)
                        {
                            historyIndex = -1;

                            PSCommand command = new PSCommand()
                                                .AddCommand("Get-History");

                            currentHistory = _psesHost.InvokePSCommand <PSObject>(command, executionOptions: null, cancellationToken);

                            if (currentHistory is not null)
                            {
                                historyIndex = currentHistory.Count;
                            }
                        }

                        if (currentHistory?.Count > 0 && historyIndex > 0)
                        {
                            historyIndex--;

                            currentCursorIndex =
                                InsertInput(
                                    inputLine,
                                    promptStartCol,
                                    promptStartRow,
                                    (string)currentHistory[historyIndex].Properties["CommandLine"].Value,
                                    currentCursorIndex,
                                    insertIndex: 0,
                                    replaceLength: inputLine.Length);
                        }

                        continue;

                    case ConsoleKey.DownArrow:
                        currentCompletion = null;

                        // The down arrow shouldn't cause history to be loaded,
                        // it's only for navigating an active history array
                        if (historyIndex > -1 && historyIndex < currentHistory.Count &&
                            currentHistory?.Count > 0)
                        {
                            historyIndex++;

                            if (historyIndex < currentHistory.Count)
                            {
                                currentCursorIndex =
                                    InsertInput(
                                        inputLine,
                                        promptStartCol,
                                        promptStartRow,
                                        (string)currentHistory[historyIndex].Properties["CommandLine"].Value,
                                        currentCursorIndex,
                                        insertIndex: 0,
                                        replaceLength: inputLine.Length);
                            }
                            else if (historyIndex == currentHistory.Count)
                            {
                                currentCursorIndex =
                                    InsertInput(
                                        inputLine,
                                        promptStartCol,
                                        promptStartRow,
                                        string.Empty,
                                        currentCursorIndex,
                                        insertIndex: 0,
                                        replaceLength: inputLine.Length);
                            }
                        }

                        continue;

                    case ConsoleKey.Escape:
                        currentCompletion = null;
                        historyIndex      = currentHistory != null ? currentHistory.Count : -1;

                        currentCursorIndex =
                            InsertInput(
                                inputLine,
                                promptStartCol,
                                promptStartRow,
                                string.Empty,
                                currentCursorIndex,
                                insertIndex: 0,
                                replaceLength: inputLine.Length);

                        continue;

                    case ConsoleKey.Backspace:
                        currentCompletion = null;

                        if (currentCursorIndex > 0)
                        {
                            currentCursorIndex =
                                InsertInput(
                                    inputLine,
                                    promptStartCol,
                                    promptStartRow,
                                    string.Empty,
                                    currentCursorIndex,
                                    insertIndex: currentCursorIndex - 1,
                                    replaceLength: 1,
                                    finalCursorIndex: currentCursorIndex - 1);
                        }

                        continue;

                    case ConsoleKey.Delete:
                        currentCompletion = null;

                        if (currentCursorIndex < inputLine.Length)
                        {
                            currentCursorIndex =
                                InsertInput(
                                    inputLine,
                                    promptStartCol,
                                    promptStartRow,
                                    string.Empty,
                                    currentCursorIndex,
                                    replaceLength: 1,
                                    finalCursorIndex: currentCursorIndex);
                        }

                        continue;

                    case ConsoleKey.Enter:
                        string completedInput = inputLine.ToString();
                        currentCompletion = null;
                        currentHistory    = null;

                        // TODO: Add line continuation support:
                        // - When shift+enter is pressed, or
                        // - When the parse indicates incomplete input

                        //if ((keyInfo.Modifiers & ConsoleModifiers.Shift) == ConsoleModifiers.Shift)
                        //{
                        //    // TODO: Start a new line!
                        //    continue;
                        //}
                        //Parser.ParseInput(
                        //    completedInput,
                        //    out Token[] tokens,
                        //    out ParseError[] parseErrors);
                        //if (parseErrors.Any(e => e.IncompleteInput))
                        //{
                        //    // TODO: Start a new line!
                        //    continue;
                        //}

                        return(completedInput);

                    default:
                        if (keyInfo.IsCtrlC())
                        {
                            throw new PipelineStoppedException();
                        }

                        // Normal character input
                        if (keyInfo.KeyChar != 0 && !char.IsControl(keyInfo.KeyChar))
                        {
                            currentCompletion = null;

                            currentCursorIndex =
                                InsertInput(
                                    inputLine,
                                    promptStartCol,
                                    promptStartRow,
                                    keyInfo.KeyChar.ToString(),     // TODO: Determine whether this should take culture into account
                                    currentCursorIndex,
                                    finalCursorIndex: currentCursorIndex + 1);
                        }

                        continue;
                    }
                }
            }
            catch (OperationCanceledException)
            {
                // We've broken out of the loop
            }
            finally
            {
                Console.TreatControlCAsInput = false;
            }

            // If we break out of the loop without returning (because of the Enter key)
            // then the readline has been aborted in some way and we should return nothing
            return(null);
        }