/// <summary> /// Invokes a custom ReadLine method that is similar to but more basic than PSReadLine. /// This method should be used when PSReadLine is disabled, either by user settings or /// unsupported PowerShell versions. /// </summary> /// <param name="isCommandLine"> /// Indicates whether ReadLine should act like a command line. /// </param> /// <param name="cancellationToken"> /// The cancellation token that will be checked prior to completing the returned task. /// </param> /// <returns> /// A task object representing the asynchronus operation. The Result property on /// the task object returns the user input string. /// </returns> internal async Task <string> InvokeLegacyReadLineAsync(bool isCommandLine, CancellationToken cancellationToken) { // TODO: Is inputBeforeCompletion used? string inputBeforeCompletion = null; string inputAfterCompletion = null; CommandCompletion currentCompletion = null; int historyIndex = -1; Collection <PSObject> currentHistory = null; StringBuilder inputLine = new StringBuilder(); int initialCursorCol = await ConsoleProxy.GetCursorLeftAsync(cancellationToken).ConfigureAwait(false); int initialCursorRow = await ConsoleProxy.GetCursorTopAsync(cancellationToken).ConfigureAwait(false); // TODO: Are these used? int initialWindowLeft = Console.WindowLeft; int initialWindowTop = Console.WindowTop; int currentCursorIndex = 0; Console.TreatControlCAsInput = true; try { while (!cancellationToken.IsCancellationRequested) { ConsoleKeyInfo keyInfo = await ReadKeyAsync(cancellationToken).ConfigureAwait(false); // 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; if ((int)keyInfo.Key == 3 || keyInfo.Key == ConsoleKey.C && keyInfo.Modifiers.HasFlag(ConsoleModifiers.Control)) { throw new PipelineStoppedException(); } else if (keyInfo.Key == ConsoleKey.Tab && isCommandLine) { if (currentCompletion == null) { inputBeforeCompletion = inputLine.ToString(); inputAfterCompletion = null; // TODO: This logic should be moved to AstOperations or similar! if (this.powerShellContext.IsDebuggerStopped) { PSCommand command = new PSCommand(); command.AddCommand("TabExpansion2"); command.AddParameter("InputScript", inputBeforeCompletion); command.AddParameter("CursorColumn", currentCursorIndex); command.AddParameter("Options", null); var results = await this.powerShellContext .ExecuteCommandAsync <CommandCompletion>(command, sendOutputToHost : false, sendErrorToHost : false) .ConfigureAwait(false); currentCompletion = results.FirstOrDefault(); } else { using (RunspaceHandle runspaceHandle = await this.powerShellContext.GetRunspaceHandleAsync().ConfigureAwait(false)) using (PowerShell powerShell = PowerShell.Create()) { powerShell.Runspace = runspaceHandle.Runspace; currentCompletion = CommandCompletion.CompleteInput( inputBeforeCompletion, currentCursorIndex, null, powerShell); 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 != null) { currentCursorIndex = this.InsertInput( inputLine, promptStartCol, promptStartRow, $"{completion.CompletionText}{inputAfterCompletion}", currentCursorIndex, insertIndex: currentCompletion.ReplacementIndex, replaceLength: inputLine.Length - currentCompletion.ReplacementIndex, finalCursorIndex: currentCompletion.ReplacementIndex + completion.CompletionText.Length); } } else if (keyInfo.Key == ConsoleKey.LeftArrow) { currentCompletion = null; if (currentCursorIndex > 0) { currentCursorIndex = this.MoveCursorToIndex( promptStartCol, promptStartRow, consoleWidth, currentCursorIndex - 1); } } else if (keyInfo.Key == ConsoleKey.Home) { currentCompletion = null; currentCursorIndex = this.MoveCursorToIndex( promptStartCol, promptStartRow, consoleWidth, 0); } else if (keyInfo.Key == ConsoleKey.RightArrow) { currentCompletion = null; if (currentCursorIndex < inputLine.Length) { currentCursorIndex = this.MoveCursorToIndex( promptStartCol, promptStartRow, consoleWidth, currentCursorIndex + 1); } } else if (keyInfo.Key == ConsoleKey.End) { currentCompletion = null; currentCursorIndex = this.MoveCursorToIndex( promptStartCol, promptStartRow, consoleWidth, inputLine.Length); } else if (keyInfo.Key == ConsoleKey.UpArrow && isCommandLine) { currentCompletion = null; // TODO: Ctrl+Up should allow navigation in multi-line input if (currentHistory == null) { historyIndex = -1; PSCommand command = new PSCommand(); command.AddCommand("Get-History"); currentHistory = await this.powerShellContext.ExecuteCommandAsync <PSObject>(command, sendOutputToHost : false, sendErrorToHost : false) .ConfigureAwait(false) as Collection <PSObject>; if (currentHistory != null) { historyIndex = currentHistory.Count; } } if (currentHistory != null && currentHistory.Count > 0 && historyIndex > 0) { historyIndex--; currentCursorIndex = this.InsertInput( inputLine, promptStartCol, promptStartRow, (string)currentHistory[historyIndex].Properties["CommandLine"].Value, currentCursorIndex, insertIndex: 0, replaceLength: inputLine.Length); } } else if (keyInfo.Key == ConsoleKey.DownArrow && isCommandLine) { 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 != null && currentHistory.Count > 0) { historyIndex++; if (historyIndex < currentHistory.Count) { currentCursorIndex = this.InsertInput( inputLine, promptStartCol, promptStartRow, (string)currentHistory[historyIndex].Properties["CommandLine"].Value, currentCursorIndex, insertIndex: 0, replaceLength: inputLine.Length); } else if (historyIndex == currentHistory.Count) { currentCursorIndex = this.InsertInput( inputLine, promptStartCol, promptStartRow, string.Empty, currentCursorIndex, insertIndex: 0, replaceLength: inputLine.Length); } } } else if (keyInfo.Key == ConsoleKey.Escape) { currentCompletion = null; historyIndex = currentHistory != null ? currentHistory.Count : -1; currentCursorIndex = this.InsertInput( inputLine, promptStartCol, promptStartRow, string.Empty, currentCursorIndex, insertIndex: 0, replaceLength: inputLine.Length); } else if (keyInfo.Key == ConsoleKey.Backspace) { currentCompletion = null; if (currentCursorIndex > 0) { currentCursorIndex = this.InsertInput( inputLine, promptStartCol, promptStartRow, string.Empty, currentCursorIndex, insertIndex: currentCursorIndex - 1, replaceLength: 1, finalCursorIndex: currentCursorIndex - 1); } } else if (keyInfo.Key == ConsoleKey.Delete) { currentCompletion = null; if (currentCursorIndex < inputLine.Length) { currentCursorIndex = this.InsertInput( inputLine, promptStartCol, promptStartRow, string.Empty, currentCursorIndex, replaceLength: 1, finalCursorIndex: currentCursorIndex); } } else if (keyInfo.Key == ConsoleKey.Enter) { string completedInput = inputLine.ToString(); currentCompletion = null; currentHistory = null; //if ((keyInfo.Modifiers & ConsoleModifiers.Shift) == ConsoleModifiers.Shift) //{ // // TODO: Start a new line! // continue; //} Parser.ParseInput( completedInput, out Token[] tokens, out ParseError[] parseErrors);
private static Task <ConsoleKeyInfo> ReadKeyAsync(CancellationToken cancellationToken) { return(ConsoleProxy.ReadKeyAsync(intercept: true, cancellationToken)); }
private async Task StartReplLoopAsync(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { string commandString = null; int originalCursorTop = 0; try { await this.WritePromptStringToHostAsync(cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { break; } try { originalCursorTop = await ConsoleProxy.GetCursorTopAsync(cancellationToken).ConfigureAwait(false); commandString = await this.ReadCommandLineAsync(cancellationToken).ConfigureAwait(false); } catch (PipelineStoppedException) { this.WriteOutput( "^C", true, OutputType.Normal, foregroundColor: ConsoleColor.Red); } // Do nothing here, the while loop condition will exit. catch (TaskCanceledException) { } catch (OperationCanceledException) { } catch (Exception e) // Narrow this if possible { this.WriteOutput( $"\n\nAn error occurred while reading input:\n\n{e.ToString()}\n", true, OutputType.Error); Logger.LogException("Caught exception while reading command line", e); } finally { // This supplies the newline in the Legacy ReadLine when executing code in the terminal via hitting the ENTER key. // Without this, hitting ENTER with a no input looks like it does nothing (no new prompt is written) // and also the output would show up on the same line as the code you wanted to execute (the prompt line). // Since PSReadLine handles ENTER internally to itself, we only want to do this when using the Legacy ReadLine. if (!_isPSReadLineEnabled && !cancellationToken.IsCancellationRequested && originalCursorTop == await ConsoleProxy.GetCursorTopAsync(cancellationToken).ConfigureAwait(false)) { this.WriteLine(); } } if (!string.IsNullOrWhiteSpace(commandString)) { var unusedTask = this.powerShellContext .ExecuteScriptStringAsync( commandString, writeInputToHost: false, writeOutputToHost: true, addToHistory: true) .ConfigureAwait(continueOnCapturedContext: false); break; } } }
public async static Task <SecureString> ReadSecureLineAsync(CancellationToken cancellationToken) { SecureString secureString = new SecureString(); // TODO: Are these values used? int initialPromptRow = await ConsoleProxy.GetCursorTopAsync(cancellationToken).ConfigureAwait(false); int initialPromptCol = await ConsoleProxy.GetCursorLeftAsync(cancellationToken).ConfigureAwait(false); int previousInputLength = 0; Console.TreatControlCAsInput = true; try { while (!cancellationToken.IsCancellationRequested) { ConsoleKeyInfo keyInfo = await ReadKeyAsync(cancellationToken).ConfigureAwait(false); if ((int)keyInfo.Key == 3 || keyInfo.Key == ConsoleKey.C && keyInfo.Modifiers.HasFlag(ConsoleModifiers.Control)) { throw new PipelineStoppedException(); } if (keyInfo.Key == ConsoleKey.Enter) { // Break to return the completed string break; } if (keyInfo.Key == ConsoleKey.Tab) { continue; } if (keyInfo.Key == ConsoleKey.Backspace) { if (secureString.Length > 0) { secureString.RemoveAt(secureString.Length - 1); } } else if (keyInfo.KeyChar != 0 && !char.IsControl(keyInfo.KeyChar)) { secureString.AppendChar(keyInfo.KeyChar); } // 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 = await ConsoleProxy.GetCursorTopAsync(cancellationToken).ConfigureAwait(false); int col = await ConsoleProxy.GetCursorLeftAsync(cancellationToken).ConfigureAwait(false); // 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); }
private async Task StartReplLoopAsync(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { string commandString = null; int originalCursorTop = 0; try { await this.WritePromptStringToHostAsync(cancellationToken); } catch (OperationCanceledException) { break; } try { originalCursorTop = await ConsoleProxy.GetCursorTopAsync(cancellationToken); commandString = await this.ReadCommandLineAsync(cancellationToken); } catch (PipelineStoppedException) { this.WriteOutput( "^C", true, OutputType.Normal, foregroundColor: ConsoleColor.Red); } // Do nothing here, the while loop condition will exit. catch (TaskCanceledException) { } catch (OperationCanceledException) { } catch (Exception e) // Narrow this if possible { this.WriteOutput( $"\n\nAn error occurred while reading input:\n\n{e.ToString()}\n", true, OutputType.Error); Logger.LogException("Caught exception while reading command line", e); } finally { if (!cancellationToken.IsCancellationRequested && originalCursorTop == await ConsoleProxy.GetCursorTopAsync(cancellationToken)) { this.WriteLine(); } } if (!string.IsNullOrWhiteSpace(commandString)) { var unusedTask = this.powerShellContext .ExecuteScriptStringAsync( commandString, writeInputToHost: false, writeOutputToHost: true, addToHistory: true) .ConfigureAwait(continueOnCapturedContext: false); break; } } }