/// <summary> /// Creates a new instance of the ConsoleServicePSHostUserInterface /// class with the given IConsoleHost implementation. /// </summary> /// <param name="powerShellContext">The PowerShellContext to use for executing commands.</param> /// <param name="logger">An ILogger implementation to use for this host.</param> public TerminalPSHostUserInterface( PowerShellContext powerShellContext, ILogger logger) : base( powerShellContext, new TerminalPSHostRawUserInterface(logger), logger) { this.consoleReadLine = new ConsoleReadLine(powerShellContext); // Set the output encoding to UTF-8 so that special // characters are written to the console correctly System.Console.OutputEncoding = System.Text.Encoding.UTF8; System.Console.CancelKeyPress += (obj, args) => { if (!this.IsNativeApplicationRunning) { // We'll handle Ctrl+C args.Cancel = true; this.SendControlC(); } }; }
private string ResolveFilePath(string filePath) { if (!IsPathInMemory(filePath)) { if (filePath.StartsWith(@"file://")) { // Client sent the path in URI format, extract the local path and trim // any extraneous slashes Uri fileUri = new Uri(filePath); filePath = fileUri.LocalPath.TrimStart('/'); } // Some clients send paths with UNIX-style slashes, replace those if necessary filePath = filePath.Replace('/', '\\'); // Clients could specify paths with escaped space, [ and ] characters which .NET APIs // will not handle. These paths will get appropriately escaped just before being passed // into the PowerShell engine. filePath = PowerShellContext.UnescapePath(filePath); // Get the absolute file path filePath = Path.GetFullPath(filePath); } Logger.Write(LogLevel.Verbose, "Resolved path: " + filePath); return(filePath); }
/// <summary> /// Gets the CommandInfo instance for a command with a particular name. /// </summary> /// <param name="commandName">The name of the command.</param> /// <param name="powerShellContext">The PowerShellContext to use for running Get-Command.</param> /// <returns>A CommandInfo object with details about the specified command.</returns> public static async Task <CommandInfo> GetCommandInfo( string commandName, PowerShellContext powerShellContext) { Validate.IsNotNull(nameof(commandName), commandName); // Make sure the command's noun isn't blacklisted. This is // currently necessary to make sure that Get-Command doesn't // load PackageManagement or PowerShellGet because they cause // a major slowdown in IntelliSense. var commandParts = commandName.Split('-'); if (commandParts.Length == 2 && NounBlackList.Contains(commandParts[1])) { return(null); } PSCommand command = new PSCommand(); command.AddCommand(@"Microsoft.PowerShell.Core\Get-Command"); command.AddArgument(commandName); command.AddParameter("ErrorAction", "Ignore"); return ((await powerShellContext .ExecuteCommand <PSObject>(command, false, false)) .Select(o => o.BaseObject) .OfType <CommandInfo>() .FirstOrDefault()); }
/// <summary> /// Sets the list of breakpoints for the current debugging session. /// </summary> /// <param name="scriptFile">The ScriptFile in which breakpoints will be set.</param> /// <param name="lineNumbers">The line numbers at which breakpoints will be set.</param> /// <param name="clearExisting">If true, causes all existing breakpoints to be cleared before setting new ones.</param> /// <returns>An awaitable Task that will provide details about the breakpoints that were set.</returns> public async Task <BreakpointDetails[]> SetBreakpoints( ScriptFile scriptFile, int[] lineNumbers, bool clearExisting = true) { IEnumerable <Breakpoint> resultBreakpoints = null; if (clearExisting) { await this.ClearBreakpointsInFile(scriptFile); } if (lineNumbers.Length > 0) { // Fix for issue #123 - file paths that contain wildcard chars [ and ] need to // quoted and have those wildcard chars escaped. string escapedScriptPath = PowerShellContext.EscapeWildcardsInPath(scriptFile.FilePath); PSCommand psCommand = new PSCommand(); psCommand.AddCommand("Set-PSBreakpoint"); psCommand.AddParameter("Script", escapedScriptPath); psCommand.AddParameter("Line", lineNumbers.Length > 0 ? lineNumbers : null); resultBreakpoints = await this.powerShellContext.ExecuteCommand <Breakpoint>( psCommand); return (resultBreakpoints .Select(BreakpointDetails.Create) .ToArray()); } return(new BreakpointDetails[0]); }
/// <summary> /// Initializes a new instance of the DebugService class and uses /// the given PowerShellContext for all future operations. /// </summary> /// <param name="powerShellContext"> /// The PowerShellContext to use for all debugging operations. /// </param> public DebugService(PowerShellContext powerShellContext) { Validate.IsNotNull("powerShellContext", powerShellContext); this.powerShellContext = powerShellContext; this.powerShellContext.DebuggerStop += this.OnDebuggerStop; this.powerShellContext.BreakpointUpdated += this.OnBreakpointUpdated; }
public PipelineExecutionRequest( PowerShellContext powerShellContext, PSCommand psCommand, bool sendOutputToHost) { this.powerShellContext = powerShellContext; this.psCommand = psCommand; this.sendOutputToHost = sendOutputToHost; }
/// <summary> /// Constructs an instance of the LanguageService class and uses /// the given Runspace to execute language service operations. /// </summary> /// <param name="powerShellContext"> /// The PowerShellContext in which language service operations will be executed. /// </param> public LanguageService(PowerShellContext powerShellContext) { Validate.IsNotNull("powerShellContext", powerShellContext); this.powerShellContext = powerShellContext; this.CmdletToAliasDictionary = new Dictionary <String, List <String> >(StringComparer.OrdinalIgnoreCase); this.AliasToCmdletDictionary = new Dictionary <String, String>(StringComparer.OrdinalIgnoreCase); }
/// <summary> /// Creates a new instance of the ConsoleServicePSHost class /// with the given IConsoleHost implementation. /// </summary> /// <param name="powerShellContext"> /// An implementation of IHostSupportsInteractiveSession for runspace management. /// </param> /// <param name="hostDetails"> /// Provides details about the host application. /// </param> /// <param name="hostUserInterface"> /// The EditorServicesPSHostUserInterface implementation to use for this host. /// </param> /// <param name="logger">An ILogger implementation to use for this host.</param> public EditorServicesPSHost( PowerShellContext powerShellContext, HostDetails hostDetails, EditorServicesPSHostUserInterface hostUserInterface, ILogger logger) { this.Logger = logger; this.hostDetails = hostDetails; this.hostUserInterface = hostUserInterface; this.hostSupportsInteractiveSession = powerShellContext; }
static internal async Task <SymbolDetails> CreateAsync( SymbolReference symbolReference, PowerShellContext powerShellContext) { SymbolDetails symbolDetails = new SymbolDetails(); symbolDetails.SymbolReference = symbolReference; // If the symbol is a command, get its documentation if (symbolReference.SymbolType == SymbolType.Function) { CommandInfo commandInfo = await CommandHelpers.GetCommandInfoAsync( symbolReference.SymbolName, powerShellContext); if (commandInfo != null) { symbolDetails.Documentation = await CommandHelpers.GetCommandSynopsisAsync( commandInfo, powerShellContext); if (commandInfo.CommandType == CommandTypes.Application) { symbolDetails.DisplayString = "(application) " + symbolReference.SymbolName; } else { symbolDetails.DisplayString = "function " + symbolReference.SymbolName; } } else { // Command information can't be loaded. This is likely due to // the symbol being a function that is defined in a file that // hasn't been loaded in the runspace yet. symbolDetails.DisplayString = "function " + symbolReference.SymbolName; } } else if (symbolReference.SymbolType == SymbolType.Parameter) { // TODO: Get parameter help symbolDetails.DisplayString = "(parameter) " + symbolReference.SymbolName; } else if (symbolReference.SymbolType == SymbolType.Variable) { symbolDetails.DisplayString = symbolReference.SymbolName; } return(symbolDetails); }
/// <summary> /// Starts a debug-only session using the provided IConsoleHost implementation /// for the ConsoleService. /// </summary> /// <param name="powerShellContext"></param> /// <param name="editorOperations"> /// An IEditorOperations implementation used to interact with the editor. /// </param> public void StartDebugSession( PowerShellContext powerShellContext, IEditorOperations editorOperations) { this.PowerShellContext = powerShellContext; // Initialize all services this.RemoteFileManager = new RemoteFileManager(this.PowerShellContext, editorOperations, logger); this.DebugService = new DebugService(this.PowerShellContext, this.RemoteFileManager, logger); // Create a workspace to contain open files this.Workspace = new Workspace(this.PowerShellContext.LocalPowerShellVersion.Version, this.logger); }
/// <summary> /// Gets the CommandInfo instance for a command with a particular name. /// </summary> /// <param name="commandName">The name of the command.</param> /// <param name="powerShellContext">The PowerShellContext to use for running Get-Command.</param> /// <returns>A CommandInfo object with details about the specified command.</returns> public static async Task <CommandInfo> GetCommandInfo( string commandName, PowerShellContext powerShellContext) { PSCommand command = new PSCommand(); command.AddCommand(@"Microsoft.PowerShell.Core\Get-Command"); command.AddArgument(commandName); var results = await powerShellContext.ExecuteCommand <CommandInfo>(command, false, false); return(results.FirstOrDefault()); }
/// <summary> /// Creates a new instance of the ConsoleServicePSHostUserInterface /// class with the given IConsoleHost implementation. /// </summary> /// <param name="powerShellContext">The PowerShellContext to use for executing commands.</param> /// <param name="rawUserInterface">The PSHostRawUserInterface implementation to use for this host.</param> /// <param name="logger">An ILogger implementation to use for this host.</param> public EditorServicesPSHostUserInterface( PowerShellContext powerShellContext, PSHostRawUserInterface rawUserInterface, ILogger logger) { this.Logger = logger; this.powerShellContext = powerShellContext; this.rawUserInterface = rawUserInterface; this.powerShellContext.DebuggerStop += PowerShellContext_DebuggerStop; this.powerShellContext.DebuggerResumed += PowerShellContext_DebuggerResumed; this.powerShellContext.ExecutionStatusChanged += PowerShellContext_ExecutionStatusChanged; }
/// <summary> /// Gets the CommandInfo instance for a command with a particular name. /// </summary> /// <param name="commandName">The name of the command.</param> /// <param name="powerShellContext">The PowerShellContext to use for running Get-Command.</param> /// <returns>A CommandInfo object with details about the specified command.</returns> public static async Task <CommandInfo> GetCommandInfo( string commandName, PowerShellContext powerShellContext) { PSCommand command = new PSCommand(); command.AddCommand(@"Microsoft.PowerShell.Core\Get-Command"); command.AddArgument(commandName); return ((await powerShellContext .ExecuteCommand <PSObject>(command, false, false)) .Select(o => o.BaseObject) .OfType <CommandInfo>() .FirstOrDefault()); }
/// <summary> /// Starts the session using the provided IConsoleHost implementation /// for the ConsoleService. /// </summary> /// <param name="powerShellContext"></param> /// <param name="hostInput"></param> public void StartSession( PowerShellContext powerShellContext, IHostInput hostInput) { this.PowerShellContext = powerShellContext; this.HostInput = hostInput; // Initialize all services this.LanguageService = new LanguageService(this.PowerShellContext, this.logger); this.ExtensionService = new ExtensionService(this.PowerShellContext); this.TemplateService = new TemplateService(this.PowerShellContext, this.logger); this.InstantiateAnalysisService(); // Create a workspace to contain open files this.Workspace = new Workspace(this.PowerShellContext.LocalPowerShellVersion.Version, this.logger); }
/// <summary> /// Constructs an instance of the LanguageService class and uses /// the given Runspace to execute language service operations. /// </summary> /// <param name="powerShellContext"> /// The PowerShellContext in which language service operations will be executed. /// </param> /// <param name="logger">An ILogger implementation used for writing log messages.</param> public LanguageService( PowerShellContext powerShellContext, ILogger logger) { Validate.IsNotNull("powerShellContext", powerShellContext); this.powerShellContext = powerShellContext; this.logger = logger; this.CmdletToAliasDictionary = new Dictionary <String, List <String> >(StringComparer.OrdinalIgnoreCase); this.AliasToCmdletDictionary = new Dictionary <String, String>(StringComparer.OrdinalIgnoreCase); this.documentSymbolProviders = new IDocumentSymbolProvider[] { new ScriptDocumentSymbolProvider(powerShellContext.LocalPowerShellVersion.Version), new PsdDocumentSymbolProvider(), new PesterDocumentSymbolProvider() }; }
/// <summary> /// Gets the command's "Synopsis" documentation section. /// </summary> /// <param name="commandInfo">The CommandInfo instance for the command.</param> /// <param name="powerShellContext">The PowerShellContext to use for getting command documentation.</param> /// <returns></returns> public static async Task <string> GetCommandSynopsis( CommandInfo commandInfo, PowerShellContext powerShellContext) { string synopsisString = string.Empty; PSObject helpObject = null; if (commandInfo != null && (commandInfo.CommandType == CommandTypes.Cmdlet || commandInfo.CommandType == CommandTypes.Function || commandInfo.CommandType == CommandTypes.Filter)) { PSCommand command = new PSCommand(); command.AddCommand(@"Microsoft.PowerShell.Core\Get-Help"); command.AddArgument(commandInfo); command.AddParameter("ErrorAction", "Ignore"); var results = await powerShellContext.ExecuteCommand <PSObject>(command, false, false); helpObject = results.FirstOrDefault(); if (helpObject != null) { // Extract the synopsis string from the object synopsisString = (string)helpObject.Properties["synopsis"].Value ?? string.Empty; // Ignore the placeholder value for this field if (string.Equals(synopsisString, "SHORT DESCRIPTION", System.StringComparison.CurrentCultureIgnoreCase)) { synopsisString = string.Empty; } } } return(synopsisString); }
internal string ResolveFilePath(string filePath) { if (!IsPathInMemory(filePath)) { if (filePath.StartsWith(@"file://")) { filePath = Workspace.UnescapeDriveColon(filePath); // Client sent the path in URI format, extract the local path filePath = new Uri(filePath).LocalPath; } // Clients could specify paths with escaped space, [ and ] characters which .NET APIs // will not handle. These paths will get appropriately escaped just before being passed // into the PowerShell engine. filePath = PowerShellContext.UnescapeWildcardEscapedPath(filePath); // Get the absolute file path filePath = Path.GetFullPath(filePath); } this.logger.Write(LogLevel.Verbose, "Resolved path: " + filePath); return(filePath); }
/// <summary> /// Initializes a new instance of the RunspaceHandle class using the /// given runspace. /// </summary> /// <param name="powerShellContext">The PowerShellContext instance which manages the runspace.</param> public RunspaceHandle(PowerShellContext powerShellContext) : this(powerShellContext, false) { }
/// <summary> /// Initializes a new instance of the RunspaceHandle class using the /// given runspace. /// </summary> /// <param name="runspace">The runspace instance which is temporarily owned by this handle.</param> /// <param name="powerShellContext">The PowerShellContext instance which manages the runspace.</param> public RunspaceHandle(Runspace runspace, PowerShellContext powerShellContext) { this.Runspace = runspace; this.powerShellContext = powerShellContext; }
internal RunspaceHandle(PowerShellContext powerShellContext, bool isReadLine) { this.powerShellContext = powerShellContext; this.IsReadLine = isReadLine; }
/// <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> static public async Task <CommandCompletion> GetCompletionsAsync( Ast scriptAst, Token[] currentTokens, int fileOffset, PowerShellContext 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.Write( LogLevel.Verbose, 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 (PowerShell powerShell = PowerShell.Create()) { powerShell.Runspace = runspaceHandle.Runspace; stopwatch.Start(); try { return(CommandCompletion.CompleteInput( scriptAst, currentTokens, cursorPosition, options: null, powershell: powerShell)); } finally { stopwatch.Stop(); logger.Write(LogLevel.Verbose, $"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.Write(LogLevel.Verbose, $"IntelliSense completed in {stopwatch.ElapsedMilliseconds}ms."); return(commandCompletion); } finally { s_completionHandle.Release(); } }
/// <summary> /// Sets the list of line breakpoints for the current debugging session. /// </summary> /// <param name="scriptFile">The ScriptFile in which breakpoints will be set.</param> /// <param name="breakpoints">BreakpointDetails for each breakpoint that will be set.</param> /// <param name="clearExisting">If true, causes all existing breakpoints to be cleared before setting new ones.</param> /// <returns>An awaitable Task that will provide details about the breakpoints that were set.</returns> public async Task <BreakpointDetails[]> SetLineBreakpoints( ScriptFile scriptFile, BreakpointDetails[] breakpoints, bool clearExisting = true) { var resultBreakpointDetails = new List <BreakpointDetails>(); if (clearExisting) { await this.ClearBreakpointsInFile(scriptFile); } if (breakpoints.Length > 0) { // Fix for issue #123 - file paths that contain wildcard chars [ and ] need to // quoted and have those wildcard chars escaped. string escapedScriptPath = PowerShellContext.EscapePath(scriptFile.FilePath, escapeSpaces: false); foreach (BreakpointDetails breakpoint in breakpoints) { PSCommand psCommand = new PSCommand(); psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Set-PSBreakpoint"); psCommand.AddParameter("Script", escapedScriptPath); psCommand.AddParameter("Line", breakpoint.LineNumber); // Check if the user has specified the column number for the breakpoint. if (breakpoint.ColumnNumber.HasValue) { // It bums me out that PowerShell will silently ignore a breakpoint // where either the line or the column is invalid. I'd rather have an // error or warning message I could relay back to the client. psCommand.AddParameter("Column", breakpoint.ColumnNumber.Value); } // Check if this is a "conditional" line breakpoint. if (breakpoint.Condition != null) { ScriptBlock actionScriptBlock = GetBreakpointActionScriptBlock(breakpoint); // If there was a problem with the condition string, // move onto the next breakpoint. if (actionScriptBlock == null) { resultBreakpointDetails.Add(breakpoint); continue; } psCommand.AddParameter("Action", actionScriptBlock); } IEnumerable <Breakpoint> configuredBreakpoints = await this.powerShellContext.ExecuteCommand <Breakpoint>(psCommand); // The order in which the breakpoints are returned is significant to the // VSCode client and should match the order in which they are passed in. resultBreakpointDetails.AddRange( configuredBreakpoints.Select(BreakpointDetails.Create)); } } return(resultBreakpointDetails.ToArray()); }
/// <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> static public async Task <CommandCompletion> GetCompletions( Ast scriptAst, Token[] currentTokens, int fileOffset, PowerShellContext powerShellContext, ILogger logger, CancellationToken cancellationToken) { var type = scriptAst.Extent.StartScriptPosition.GetType(); var method = #if CoreCLR type.GetMethod( "CloneWithNewOffset", BindingFlags.Instance | BindingFlags.NonPublic); #else type.GetMethod( "CloneWithNewOffset", BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(int) }, null); #endif IScriptPosition cursorPosition = (IScriptPosition)method.Invoke( scriptAst.Extent.StartScriptPosition, new object[] { fileOffset }); logger.Write( LogLevel.Verbose, string.Format( "Getting completions at offset {0} (line: {1}, column: {2})", fileOffset, cursorPosition.LineNumber, cursorPosition.ColumnNumber)); CommandCompletion commandCompletion = null; if (powerShellContext.IsDebuggerStopped) { PSCommand command = new PSCommand(); command.AddCommand("TabExpansion2"); command.AddParameter("Ast", scriptAst); command.AddParameter("Tokens", currentTokens); command.AddParameter("PositionOfCursor", cursorPosition); command.AddParameter("Options", null); PSObject outputObject = (await powerShellContext.ExecuteCommand <PSObject>(command, false, false)) .FirstOrDefault(); if (outputObject != null) { ErrorRecord errorRecord = outputObject.BaseObject as ErrorRecord; if (errorRecord != null) { logger.WriteException( "Encountered an error while invoking TabExpansion2 in the debugger", errorRecord.Exception); } else { commandCompletion = outputObject.BaseObject as CommandCompletion; } } } else if (powerShellContext.CurrentRunspace.Runspace.RunspaceAvailability == RunspaceAvailability.Available) { using (RunspaceHandle runspaceHandle = await powerShellContext.GetRunspaceHandle(cancellationToken)) using (PowerShell powerShell = PowerShell.Create()) { powerShell.Runspace = runspaceHandle.Runspace; Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); commandCompletion = CommandCompletion.CompleteInput( scriptAst, currentTokens, cursorPosition, null, powerShell); stopwatch.Stop(); logger.Write(LogLevel.Verbose, $"IntelliSense completed in {stopwatch.ElapsedMilliseconds}ms."); } } return(commandCompletion); }
/// <summary> /// Initializes a new instance of the RunspaceHandle class using the /// given runspace. /// </summary> /// <param name="powerShellContext">The PowerShellContext instance which manages the runspace.</param> public RunspaceHandle(PowerShellContext powerShellContext) { this.powerShellContext = powerShellContext; }