public CommandCompletion GetCompletions(string inputText, int currentIndex, bool?forward) { if (_lastCompletion is null || inputText != _lastInputText) { _lastInputText = inputText; _lastCompletion = CommandCompletion.CompleteInput(inputText, currentIndex, new(), _powershell); } if (forward.HasValue) { _lastCompletion.GetNextResult(forward.Value); } return(_lastCompletion); }
/// <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) { 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); int initialCursorRow = await ConsoleProxy.GetCursorTopAsync(cancellationToken); int initialWindowLeft = Console.WindowLeft; int initialWindowTop = Console.WindowTop; int currentCursorIndex = 0; Console.TreatControlCAsInput = true; try { while (!cancellationToken.IsCancellationRequested) { ConsoleKeyInfo keyInfo = await ReadKeyAsync(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; 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, false, false); currentCompletion = results.FirstOrDefault(); } else { using (RunspaceHandle runspaceHandle = await this.powerShellContext.GetRunspaceHandleAsync()) 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, false, 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);
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); }