public async Task CanQueueParallelRunspaceRequests() { // Concurrently initiate 4 requests in the session this.powerShellContext.ExecuteScriptString("$x = 100"); Task <RunspaceHandle> handleTask = this.powerShellContext.GetRunspaceHandle(); this.powerShellContext.ExecuteScriptString("$x += 200"); this.powerShellContext.ExecuteScriptString("$x = $x / 100"); PSCommand psCommand = new PSCommand(); psCommand.AddScript("$x"); Task <IEnumerable <int> > resultTask = this.powerShellContext.ExecuteCommand <int>(psCommand); // Wait for the requested runspace handle and then dispose it RunspaceHandle handle = await handleTask; handle.Dispose(); // At this point, the remaining command executions should execute and complete int result = (await resultTask).FirstOrDefault(); // 100 + 200 = 300, then divided by 100 is 3. We are ensuring that // the commands were executed in the sequence they were called. Assert.Equal(3, result); }
/// <summary> /// Initializes this ExtensionService using the provided IEditorOperations /// implementation for future interaction with the host editor. /// </summary> /// <param name="editorOperations">An IEditorOperations implementation.</param> /// <returns>A Task that can be awaited for completion.</returns> internal async Task InitializeAsync( IServiceProvider serviceProvider, IEditorOperations editorOperations) { // Attach to ExtensionService events this.CommandAdded += ExtensionService_ExtensionAddedAsync; this.CommandUpdated += ExtensionService_ExtensionUpdatedAsync; this.CommandRemoved += ExtensionService_ExtensionRemovedAsync; this.EditorObject = new EditorObject( serviceProvider, this, editorOperations); // Assign the new EditorObject to be the static instance available to binary APIs this.EditorObject.SetAsStaticInstance(); // Register the editor object in the runspace PSCommand variableCommand = new PSCommand(); using (RunspaceHandle handle = await this.PowerShellContext.GetRunspaceHandleAsync().ConfigureAwait(false)) { handle.Runspace.SessionStateProxy.PSVariable.Set( "psEditor", this.EditorObject); } }
/// <summary> /// Initializes this ExtensionService using the provided IEditorOperations /// implementation for future interaction with the host editor. /// </summary> /// <param name="editorOperations">An IEditorOperations implementation.</param> /// <returns>A Task that can be awaited for completion.</returns> public async Task InitializeAsync( IServiceProvider serviceProvider, IEditorOperations editorOperations) { // Attach to ExtensionService events this.CommandAdded += ExtensionService_ExtensionAddedAsync; this.CommandUpdated += ExtensionService_ExtensionUpdatedAsync; this.CommandRemoved += ExtensionService_ExtensionRemovedAsync; this.EditorObject = new EditorObject( serviceProvider, this, editorOperations); // Register the editor object in the runspace PSCommand variableCommand = new PSCommand(); using (RunspaceHandle handle = await this.PowerShellContext.GetRunspaceHandleAsync()) { handle.Runspace.SessionStateProxy.PSVariable.Set( "psEditor", this.EditorObject); } }
protected async Task HandleCompletionResolveRequest( CompletionItem completionItem, RequestContext <CompletionItem> requestContext) { if (completionItem.Kind == CompletionItemKind.Function) { RunspaceHandle runspaceHandle = await editorSession.PowerShellContext.GetRunspaceHandle(); // Get the documentation for the function CommandInfo commandInfo = CommandHelpers.GetCommandInfo( completionItem.Label, runspaceHandle.Runspace); completionItem.Documentation = CommandHelpers.GetCommandSynopsis( commandInfo, runspaceHandle.Runspace); runspaceHandle.Dispose(); } // Send back the updated CompletionItem await requestContext.SendResult(completionItem); }
/// <summary> /// Initializes this ExtensionService using the provided IEditorOperations /// implementation for future interaction with the host editor. /// </summary> /// <param name="editorOperations">An IEditorOperations implementation.</param> /// <returns>A Task that can be awaited for completion.</returns> public async Task Initialize(IEditorOperations editorOperations) { this.EditorObject = new EditorObject(this, editorOperations); // Register the editor object in the runspace PSCommand variableCommand = new PSCommand(); using (RunspaceHandle handle = await this.PowerShellContext.GetRunspaceHandle()) { handle.Runspace.SessionStateProxy.PSVariable.Set( "psEditor", this.EditorObject); } // Load the cmdlet interface Type thisType = this.GetType(); Stream resourceStream = thisType.Assembly.GetManifestResourceStream( thisType.Namespace + ".CmdletInterface.ps1"); using (StreamReader reader = new StreamReader(resourceStream)) { // Create a temporary folder path string randomFileNamePart = Path.GetFileNameWithoutExtension( Path.GetRandomFileName()); string tempScriptPath = Path.Combine( Path.GetTempPath(), "PSES_ExtensionCmdlets_" + randomFileNamePart + ".ps1"); Logger.Write( LogLevel.Verbose, "Executing extension API cmdlet script at path: " + tempScriptPath); // Read the cmdlet interface script and write it to a temporary // file so that we don't have to execute the full file contents // directly. This keeps the script execution from creating a // lot of noise in the verbose logs. string cmdletInterfaceScript = reader.ReadToEnd(); File.WriteAllText( tempScriptPath, cmdletInterfaceScript); await this.PowerShellContext.ExecuteScriptString( ". " + tempScriptPath, writeInputToHost : false, writeOutputToHost : false); // Delete the temporary file File.Delete(tempScriptPath); } }
/// <summary> /// Releases control of the runspace aquired via the <see cref="RunspaceHandle" />. /// </summary> /// <param name="runspaceHandle"> /// The <see cref="RunspaceHandle" /> representing the control to release. /// </param> internal void ReleaseRunspaceHandle(RunspaceHandle runspaceHandle) { if (_isDisposed) { return; } ReleaseRunspaceHandleImpl(runspaceHandle.IsReadLine); if (runspaceHandle.IsReadLine && !_powerShellContext.IsCurrentRunspaceOutOfProcess()) { ReleaseRunspaceHandleImpl(isReadLine: false); } }
/// <summary> /// Initializes this ExtensionService using the provided IEditorOperations /// implementation for future interaction with the host editor. /// </summary> /// <param name="editorOperations">An IEditorOperations implementation.</param> /// <returns>A Task that can be awaited for completion.</returns> public async Task Initialize(IEditorOperations editorOperations) { this.EditorObject = new EditorObject(this, editorOperations); // Register the editor object in the runspace PSCommand variableCommand = new PSCommand(); using (RunspaceHandle handle = await this.PowerShellContext.GetRunspaceHandle()) { handle.Runspace.SessionStateProxy.PSVariable.Set( "psEditor", this.EditorObject); } }
/// <summary> /// Releases control of the runspace aquired via the <see cref="RunspaceHandle" />. /// </summary> /// <param name="runspaceHandle"> /// The <see cref="RunspaceHandle" /> representing the control to release. /// </param> /// <returns> /// A <see cref="Task" /> object representing the release of the /// <see cref="RunspaceHandle" />. /// </returns> internal async Task ReleaseRunspaceHandleAsync(RunspaceHandle runspaceHandle) { if (_isDisposed) { return; } await ReleaseRunspaceHandleImplAsync(runspaceHandle.IsReadLine); if (runspaceHandle.IsReadLine && !_powerShellContext.IsCurrentRunspaceOutOfProcess()) { await ReleaseRunspaceHandleImplAsync(isReadLine : false); } }
// TODO: BRING THIS BACK /// <summary> /// Gets completions for the symbol found in the Ast at /// the given file offset. /// </summary> /// <param name="scriptAst"> /// The Ast which will be traversed to find a completable symbol. /// </param> /// <param name="currentTokens"> /// The array of tokens corresponding to the scriptAst parameter. /// </param> /// <param name="fileOffset"> /// The 1-based file offset at which a symbol will be located. /// </param> /// <param name="powerShellContext"> /// The PowerShellContext to use for gathering completions. /// </param> /// <param name="logger">An ILogger implementation used for writing log messages.</param> /// <param name="cancellationToken"> /// A CancellationToken to cancel completion requests. /// </param> /// <returns> /// A CommandCompletion instance that contains completions for the /// symbol at the given offset. /// </returns> public static async Task <CommandCompletion> GetCompletionsAsync( Ast scriptAst, Token[] currentTokens, int fileOffset, PowerShellContextService powerShellContext, ILogger logger, CancellationToken cancellationToken) { if (!s_completionHandle.Wait(0)) { return(null); } try { IScriptPosition cursorPosition = (IScriptPosition)s_extentCloneWithNewOffset.Invoke( scriptAst.Extent.StartScriptPosition, new object[] { fileOffset }); logger.LogTrace( string.Format( "Getting completions at offset {0} (line: {1}, column: {2})", fileOffset, cursorPosition.LineNumber, cursorPosition.ColumnNumber)); if (!powerShellContext.IsAvailable) { return(null); } var stopwatch = new Stopwatch(); // If the current runspace is out of process we can use // CommandCompletion.CompleteInput because PSReadLine won't be taking up the // main runspace. if (powerShellContext.IsCurrentRunspaceOutOfProcess()) { using (RunspaceHandle runspaceHandle = await powerShellContext.GetRunspaceHandleAsync(cancellationToken)) using (System.Management.Automation.PowerShell powerShell = System.Management.Automation.PowerShell.Create()) { powerShell.Runspace = runspaceHandle.Runspace; stopwatch.Start(); try { return(CommandCompletion.CompleteInput( scriptAst, currentTokens, cursorPosition, options: null, powershell: powerShell)); } finally { stopwatch.Stop(); logger.LogTrace($"IntelliSense completed in {stopwatch.ElapsedMilliseconds}ms."); } } } CommandCompletion commandCompletion = null; await powerShellContext.InvokeOnPipelineThreadAsync( pwsh => { stopwatch.Start(); commandCompletion = CommandCompletion.CompleteInput( scriptAst, currentTokens, cursorPosition, options: null, powershell: pwsh); }); stopwatch.Stop(); logger.LogTrace($"IntelliSense completed in {stopwatch.ElapsedMilliseconds}ms."); return(commandCompletion); } finally { s_completionHandle.Release(); } }
/// <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 async Task CanResolveAndLoadProfilesForHostId() { string testProfilePath = Path.GetFullPath( @"..\..\..\PowerShellEditorServices.Test.Shared\Profile\Profile.ps1"); string profileName = string.Format( "{0}_{1}", TestHostDetails.ProfileId, ProfilePaths.AllHostsProfileName); string currentUserPath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "WindowsPowerShell"); string allUsersPath = null; // To be set later using (RunspaceHandle runspaceHandle = await this.powerShellContext.GetRunspaceHandle()) { allUsersPath = (string)runspaceHandle .Runspace .SessionStateProxy .PSVariable .Get("PsHome") .Value; } string[] expectedProfilePaths = new string[] { Path.Combine(allUsersPath, ProfilePaths.AllHostsProfileName), Path.Combine(allUsersPath, profileName), Path.Combine(currentUserPath, ProfilePaths.AllHostsProfileName), Path.Combine(currentUserPath, profileName) }; // Copy the test profile to the current user's host profile path File.Copy(testProfilePath, expectedProfilePaths[3], true); // Load the profiles for the test host name await this.powerShellContext.LoadHostProfiles(); // Delete the test profile before any assert failures // cause the function to exit File.Delete(expectedProfilePaths[3]); // Ensure that all the paths are set in the correct variables // and that the current user's host profile got loaded PSCommand psCommand = new PSCommand(); psCommand.AddScript( "\"$($profile.AllUsersAllHosts) " + "$($profile.AllUsersCurrentHost) " + "$($profile.CurrentUserAllHosts) " + "$($profile.CurrentUserCurrentHost) " + "$(Assert-ProfileLoaded)\""); var result = await this.powerShellContext.ExecuteCommand <string>( psCommand); string expectedString = string.Format( "{0} True", string.Join( " ", expectedProfilePaths)); Assert.Equal(expectedString, result.FirstOrDefault(), true); }