From 8eeecc2b2276a2bef5981589515bf2c9c15280a1 Mon Sep 17 00:00:00 2001 From: "Patrick M. Meinecke" Date: Tue, 6 Jun 2017 22:23:48 -0400 Subject: [PATCH 1/9] Add position classes for new functions This change adds a custom implementation of IScriptExtent similar to InternalScriptExtent. The major difference being the replacement of PositionHelper with FileContext. The PositionHelper class was used to store the full script text and line start mapping for all ScriptExtent objects of the same file. FileContext is a great replacement for this because it's already kept unique by PSES and it adapts to changes. - Added FullScriptExtent and FullScriptPosition classes. - Changed the scriptFile field on FileContext from private to internal. --- .../Extensions/FileContext.cs | 2 +- .../Language/FullScriptExtent.cs | 168 ++++++++++++++++++ .../Language/FullScriptPosition.cs | 57 ++++++ 3 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 src/PowerShellEditorServices/Language/FullScriptExtent.cs create mode 100644 src/PowerShellEditorServices/Language/FullScriptPosition.cs diff --git a/src/PowerShellEditorServices/Extensions/FileContext.cs b/src/PowerShellEditorServices/Extensions/FileContext.cs index 8eb8d2812..0227415ae 100644 --- a/src/PowerShellEditorServices/Extensions/FileContext.cs +++ b/src/PowerShellEditorServices/Extensions/FileContext.cs @@ -16,7 +16,7 @@ public class FileContext { #region Private Fields - private ScriptFile scriptFile; + internal ScriptFile scriptFile; private EditorContext editorContext; private IEditorOperations editorOperations; diff --git a/src/PowerShellEditorServices/Language/FullScriptExtent.cs b/src/PowerShellEditorServices/Language/FullScriptExtent.cs new file mode 100644 index 000000000..454d4d368 --- /dev/null +++ b/src/PowerShellEditorServices/Language/FullScriptExtent.cs @@ -0,0 +1,168 @@ +using System.Management.Automation.Language; +using Microsoft.PowerShell.EditorServices.Extensions; + +namespace Microsoft.PowerShell.EditorServices +{ + /// + /// Provides an IScriptExtent implementation that is aware of editor context + /// and can adjust to changes. + /// + public class FullScriptExtent : IScriptExtent + { + #region Properties + + /// + /// Gets the buffer range of the extent. + /// + public BufferRange BufferRange { get; private set; } + + /// + /// Gets the FileContext that this extent refers to. + /// + public FileContext FileContext { get; } + + /// + /// Gets the file path of the script file in which this extent is contained. + /// + public string File + { + get { return FileContext.Path; } + } + + /// + /// Gets the starting script position of the extent. + /// + public IScriptPosition StartScriptPosition + { + get { return new FullScriptPosition(FileContext, BufferRange.Start, StartOffset); } + } + + /// + /// Gets the ending script position of the extent. + /// + public IScriptPosition EndScriptPosition + { + get { return new FullScriptPosition(FileContext, BufferRange.End, EndOffset); } + } + + /// + /// Gets the starting line number of the extent. + /// + public int StartLineNumber + { + get { return BufferRange.Start.Line; } + } + + + /// + /// Gets the starting column number of the extent. + /// + public int StartColumnNumber + { + get { return BufferRange.Start.Column; } + } + + /// + /// Gets the ending line number of the extent. + /// + public int EndLineNumber + { + get { return BufferRange.End.Line; } + } + + /// + /// Gets the ending column number of the extent. + /// + public int EndColumnNumber + { + get { return BufferRange.End.Column; } + } + + /// + /// Gets the text that is contained within the extent. + /// + public string Text + { + get + { + // StartOffset can be > the length for the EOF token. + if (StartOffset > FileContext.scriptFile.Contents.Length) + { + return ""; + } + + return FileContext.GetText(BufferRange); + } + } + + /// + /// Gets the starting file offset of the extent. + /// + public int StartOffset { get; private set; } + + /// + /// Gets the ending file offset of the extent. + /// + public int EndOffset { get; private set; } + + #endregion + + #region Constructors + + /// + /// Creates a new instance of the FullScriptExtent class. + /// + /// The FileContext this extent refers to. + /// The buffer range this extent is located at. + public FullScriptExtent(FileContext fileContext, BufferRange bufferRange) + { + BufferRange = bufferRange; + FileContext = fileContext; + + StartOffset = fileContext.scriptFile.GetOffsetAtPosition( + bufferRange.Start.Line, + bufferRange.Start.Column); + + EndOffset = fileContext.scriptFile.GetOffsetAtPosition( + bufferRange.End.Line, + bufferRange.End.Column); + } + + /// + /// Creates an new instance of the FullScriptExtent class. + /// + /// The FileContext this extent refers to. + /// The zero based offset this extent starts at. + /// The zero based offset this extent ends at. + public FullScriptExtent(FileContext fileContext, int startOffset, int endOffset) + { + FileContext = fileContext; + StartOffset = startOffset; + EndOffset = endOffset; + BufferRange = fileContext.scriptFile.GetRangeBetweenOffsets(startOffset, endOffset); + } + + #endregion + + #region Public Methods + + public override string ToString() + { + return Text; + } + + /// + /// Moves the start and end positions of the extent by an offset. Can + /// be used to move forwards or backwards. + /// + /// The amount to move the extent. + public void AddOffset(int offset) { + StartOffset += offset; + EndOffset += offset; + + BufferRange = FileContext.scriptFile.GetRangeBetweenOffsets(StartOffset, EndOffset); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/PowerShellEditorServices/Language/FullScriptPosition.cs b/src/PowerShellEditorServices/Language/FullScriptPosition.cs new file mode 100644 index 000000000..c9a24b6fd --- /dev/null +++ b/src/PowerShellEditorServices/Language/FullScriptPosition.cs @@ -0,0 +1,57 @@ +using System.Management.Automation.Language; +using Microsoft.PowerShell.EditorServices.Extensions; + +namespace Microsoft.PowerShell.EditorServices +{ + internal class FullScriptPosition : IScriptPosition + { + #region Fields + private readonly FileContext fileContext; + + private readonly BufferPosition bufferPosition; + + #endregion + + #region Properties + public string File + { + get { return fileContext.Path; } + } + public int LineNumber + { + get { return bufferPosition.Line; } + } + public int ColumnNumber + { + get { return bufferPosition.Column; } + } + public string Line + { + get { return fileContext.scriptFile.GetLine(LineNumber); } + } + public int Offset { get; } + + #endregion + + #region Constructors + + internal FullScriptPosition(FileContext context, BufferPosition position, int offset) + { + fileContext = context; + bufferPosition = position; + Offset = offset; + } + + #endregion + + + #region Public Methods + + public string GetFullScript() + { + return fileContext.GetText(); + } + + #endregion + } +} \ No newline at end of file From a17fca929028f9662fc2f75c2030f02ee933fbe0 Mon Sep 17 00:00:00 2001 From: "Patrick M. Meinecke" Date: Tue, 6 Jun 2017 22:25:57 -0400 Subject: [PATCH 2/9] Add EditorCommandAttribute class This class will be used by Import-EditorCommand to target commands for registration as editor commands. --- .../Extensions/EditorCommandAttribute.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/PowerShellEditorServices/Extensions/EditorCommandAttribute.cs diff --git a/src/PowerShellEditorServices/Extensions/EditorCommandAttribute.cs b/src/PowerShellEditorServices/Extensions/EditorCommandAttribute.cs new file mode 100644 index 000000000..8020cb844 --- /dev/null +++ b/src/PowerShellEditorServices/Extensions/EditorCommandAttribute.cs @@ -0,0 +1,33 @@ +using System; + +namespace Microsoft.PowerShell.EditorServices.Extensions +{ + /// + /// Provides an attribute that can be used to target PowerShell + /// commands for import as editor commands. + /// + [AttributeUsage(AttributeTargets.Class)] + public class EditorCommandAttribute : Attribute + { + + #region Properties + + /// + /// Gets or sets the name which uniquely identifies the command. + /// + public string Name { get; set; } + + /// + /// Gets or sets the display name for the command. + /// + public string DisplayName { get; set; } + + /// + /// Gets or sets a value indicating whether this command's output + /// should be suppressed. + /// + public bool SuppressOutput { get; set; } + + #endregion + } +} \ No newline at end of file From 4322fe161d7dd84706eb3eaa92c2280d2490d348 Mon Sep 17 00:00:00 2001 From: "Patrick M. Meinecke" Date: Tue, 6 Jun 2017 22:32:57 -0400 Subject: [PATCH 3/9] Add functions ported from PSESHL This changes adds commands from PSESHelperLibrary that assist with locating and manipulating text based on the syntax tree or position. - Changed functions to be compatible with PowerShell 3. - Adapted functions to use FullScriptExtent objects for position instead of using reflection to create InternalScriptExtents --- .../Public/ConvertFrom-ScriptExtent.ps1 | 57 ++++++ .../Public/ConvertTo-ScriptExtent.ps1 | 111 +++++++++++ .../Commands/Public/Find-Ast.ps1 | 174 ++++++++++++++++++ .../Commands/Public/Get-Token.ps1 | 36 ++++ .../Commands/Public/Import-EditorCommand.ps1 | 139 ++++++++++++++ .../Commands/Public/Join-ScriptExtent.ps1 | 31 ++++ .../Commands/Public/Set-ScriptExtent.ps1 | 92 +++++++++ .../Commands/Public/Test-ScriptExtent.ps1 | 43 +++++ 8 files changed, 683 insertions(+) create mode 100644 module/PowerShellEditorServices/Commands/Public/ConvertFrom-ScriptExtent.ps1 create mode 100644 module/PowerShellEditorServices/Commands/Public/ConvertTo-ScriptExtent.ps1 create mode 100644 module/PowerShellEditorServices/Commands/Public/Find-Ast.ps1 create mode 100644 module/PowerShellEditorServices/Commands/Public/Get-Token.ps1 create mode 100644 module/PowerShellEditorServices/Commands/Public/Import-EditorCommand.ps1 create mode 100644 module/PowerShellEditorServices/Commands/Public/Join-ScriptExtent.ps1 create mode 100644 module/PowerShellEditorServices/Commands/Public/Set-ScriptExtent.ps1 create mode 100644 module/PowerShellEditorServices/Commands/Public/Test-ScriptExtent.ps1 diff --git a/module/PowerShellEditorServices/Commands/Public/ConvertFrom-ScriptExtent.ps1 b/module/PowerShellEditorServices/Commands/Public/ConvertFrom-ScriptExtent.ps1 new file mode 100644 index 000000000..f23ab850c --- /dev/null +++ b/module/PowerShellEditorServices/Commands/Public/ConvertFrom-ScriptExtent.ps1 @@ -0,0 +1,57 @@ +function ConvertFrom-ScriptExtent { + <# + .EXTERNALHELP ..\PowerShellEditorServices.Commands-help.xml + #> + [CmdletBinding()] + [OutputType([Microsoft.PowerShell.EditorServices.BufferRange], ParameterSetName='BufferRange')] + [OutputType([Microsoft.PowerShell.EditorServices.BufferPosition], ParameterSetName='BufferPosition')] + param( + [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.Language.IScriptExtent[]] + $Extent, + + [Parameter(ParameterSetName='BufferRange')] + [switch] + $BufferRange, + + [Parameter(ParameterSetName='BufferPosition')] + [switch] + $BufferPosition, + + [Parameter(ParameterSetName='BufferPosition')] + [switch] + $Start, + + [Parameter(ParameterSetName='BufferPosition')] + [switch] + $End + ) + process { + foreach ($aExtent in $Extent) { + switch ($PSCmdlet.ParameterSetName) { + BufferRange { + # yield + New-Object Microsoft.PowerShell.EditorServices.BufferRange @( + $aExtent.StartLineNumber, + $aExtent.StartColumnNumber, + $aExtent.EndLineNumber, + $aExtent.EndColumnNumber) + } + BufferPosition { + if ($End) { + $line = $aExtent.EndLineNumber + $column = $aExtent.EndLineNumber + } else { + $line = $aExtent.StartLineNumber + $column = $aExtent.StartLineNumber + } + # yield + New-Object Microsoft.PowerShell.EditorServices.BufferPosition @( + $line, + $column) + } + } + } + } +} diff --git a/module/PowerShellEditorServices/Commands/Public/ConvertTo-ScriptExtent.ps1 b/module/PowerShellEditorServices/Commands/Public/ConvertTo-ScriptExtent.ps1 new file mode 100644 index 000000000..6e913aa78 --- /dev/null +++ b/module/PowerShellEditorServices/Commands/Public/ConvertTo-ScriptExtent.ps1 @@ -0,0 +1,111 @@ +function ConvertTo-ScriptExtent { + <# + .EXTERNALHELP ..\PowerShellEditorServices.Commands-help.xml + #> + [CmdletBinding()] + [OutputType([System.Management.Automation.Language.IScriptExtent])] + param( + [Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByPosition')] + [Alias('StartLine', 'Line')] + [int] + $StartLineNumber, + + [Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByPosition')] + [Alias('StartColumn', 'Column')] + [int] + $StartColumnNumber, + + [Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByPosition')] + [Alias('EndLine')] + [int] + $EndLineNumber, + + [Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByPosition')] + [Alias('EndColumn')] + [int] + $EndColumnNumber, + + [Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByOffset')] + [Alias('StartOffset', 'Offset')] + [int] + $StartOffsetNumber, + + [Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByOffset')] + [Alias('EndOffset')] + [int] + $EndOffsetNumber, + + [Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByPosition')] + [Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByOffset')] + [Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByBuffer')] + [Alias('File', 'FileName')] + [string] + $FilePath = $psEditor.GetEditorContext().CurrentFile.Path, + + [Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByBuffer')] + [Alias('Start')] + [Microsoft.PowerShell.EditorServices.BufferPosition] + $StartBuffer, + + [Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByBuffer')] + [Alias('End')] + [Microsoft.PowerShell.EditorServices.BufferPosition] + $EndBuffer, + + [Parameter(ValueFromPipeline)] + [System.Management.Automation.Language.IScriptExtent] + $Extent + ) + begin { + $fileContext = $psEditor.GetEditorContext().CurrentFile + $emptyExtent = New-Object Microsoft.PowerShell.EditorServices.FullScriptExtent @( + <# filecontext: #> $fileContext, + <# startOffset: #> 0, + <# endOffset: #> 0) + } + process { + # Already a InternalScriptExtent, FullScriptExtent or is empty. + $returnAsIs = $Extent -and + (0 -ne $Extent.StartOffset -or + 0 -ne $Extent.EndOffset -or + $Extent -eq $emptyExtent) + + if ($returnAsIs) { return $Extent } + + if ($StartOffsetNumber) { + $startOffset = $StartOffsetNumber + $endOffset = $EndOffsetNumber + + # Allow creating a single position extent with just the offset parameter. + if (-not $EndOffsetNumber) { + $endOffset = $startOffset + } + return New-Object Microsoft.PowerShell.EditorServices.FullScriptExtent @( + $fileContext, + $startOffset, + $endOffset) + } + if (-not $StartBuffer) { + if (-not $StartColumnNumber) { $StartColumnNumber = 1 } + if (-not $StartLineNumber) { $StartLineNumber = 1 } + $StartBuffer = New-Object Microsoft.PowerShell.EditorServices.BufferPosition @( + $StartColumnNumber, + $StartLineNumber) + + if ($EndLineNumber -and $EndColumnNumber) { + $EndBuffer = New-Object Microsoft.PowerShell.EditorServices.BufferPosition @( + $EndLineNumber, + $EndColumnNumber) + } + } + if (-not $EndBuffer) { $EndBuffer = $StartBuffer } + + $bufferRange = New-Object Microsoft.PowerShell.EditorServices.BufferRange @( + $StartBuffer, + $EndBuffer) + + return New-Object Microsoft.PowerShell.EditorServices.FullScriptExtent @( + $fileContext, + $bufferRange) + } +} diff --git a/module/PowerShellEditorServices/Commands/Public/Find-Ast.ps1 b/module/PowerShellEditorServices/Commands/Public/Find-Ast.ps1 new file mode 100644 index 000000000..a496b5f23 --- /dev/null +++ b/module/PowerShellEditorServices/Commands/Public/Find-Ast.ps1 @@ -0,0 +1,174 @@ +function Find-Ast { + <# + .EXTERNALHELP ..\PowerShellEditorServices.Commands-help.xml + #> + [CmdletBinding(PositionalBinding=$false, DefaultParameterSetName='FilterScript')] + param( + [Parameter(Position=0, ParameterSetName='FilterScript')] + [ValidateNotNullOrEmpty()] + [scriptblock] + $FilterScript = { $true }, + + [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName='FilterScript')] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.Language.Ast] + $Ast, + + [Parameter(ParameterSetName='FilterScript')] + [switch] + $Before, + + [Parameter(ParameterSetName='FilterScript')] + [switch] + $Family, + + [Parameter(ParameterSetName='FilterScript')] + [Alias('Closest', 'F')] + [switch] + $First, + + [Parameter(ParameterSetName='FilterScript')] + [Alias('Furthest')] + [switch] + $Last, + + [Parameter(ParameterSetName='FilterScript')] + [Alias('Parent')] + [switch] + $Ancestor, + + [Parameter(ParameterSetName='FilterScript')] + [switch] + $IncludeStartingAst, + + [Parameter(ParameterSetName='AtCursor')] + [switch] + $AtCursor + ) + begin { + # InvokeWithContext method is PS4+, but it's significantly faster for large files. + if ($PSVersionTable.PSVersion.Major -ge 4) { + + $variableType = [System.Management.Automation.PSVariable] + function InvokeWithContext { + param([scriptblock]$Filter, [System.Management.Automation.Language.Ast]$DollarUnder) + + return $Filter.InvokeWithContext( + <# functionsToDefine: #> $null, + <# variablesToDefine: #> [Activator]::CreateInstance($variableType, @('_', $DollarUnder)), + <# args: #> $aAst) + } + } else { + $FilterScript = [scriptblock]::Create($FilterScript.ToString()) + function InvokeWithContext { + param([scriptblock]$Filter, [System.Management.Automation.Language.Ast]$DollarUnder) + + return $DollarUnder | & { process { $Filter.InvokeReturnAsIs($DollarUnder) } } + } + } + # Get all children or ancestors. + function GetAllFamily { + param($Start) + + if ($Before.IsPresent) { + $parent = $Start + for ($parent; $parent = $parent.Parent) { $parent } + return + } + return $Start.FindAll({ $true }, $true) + } + # Get all asts regardless of structure, in either direction from the starting ast. + function GetAllAsts { + param($Start) + + $predicate = [Func[System.Management.Automation.Language.Ast,bool]]{ + $args[0] -ne $Ast + } + + $topParent = Find-Ast -Ast $Start -Ancestor -Last -IncludeStartingAst + if (-not $topParent) { $topParent = $Start } + + if ($Before.IsPresent) { + # Need to store so we can reverse the collection. + $result = [Linq.Enumerable]::TakeWhile( + $topParent.FindAll({ $true }, $true), + $predicate) + + [array]::Reverse($result) + return $result + } + return [Linq.Enumerable]::SkipWhile( + $topParent.FindAll({ $true }, $true), + $predicate) + } + } + process { + if ($Ancestor.IsPresent) { + $Family = $Before = $true + } + $context = $psEditor.GetEditorContext() + + if (-not $Ast -and $context) { + $Ast = $context.CurrentFile.Ast + } + switch ($PSCmdlet.ParameterSetName) { + AtCursor { + $cursorLine = $context.CursorPosition.Line - 1 + $cursorColumn = $context.CursorPosition.Column - 1 + $cursorOffset = $Ast.Extent.Text | + Select-String "(.*\r?\n){$cursorLine}.{$cursorColumn}" | + ForEach-Object { $PSItem.Matches.Value.Length } + + # yield + Find-Ast -Last { + $cursorOffset -ge $PSItem.Extent.StartOffset -and + $cursorOffset -le $PSItem.Extent.EndOffset + } + } + FilterScript { + if (-not $Ast) { return } + + # Check if we're trying to get the top level ancestor when we're already there. + if ($Before.IsPresent -and + $Family.IsPresent -and + $Last.IsPresent -and -not + $Ast.Parent -and + $Ast -is [System.Management.Automation.Language.ScriptBlockAst]) + { return $Ast } + + if ($Family.IsPresent) { + $asts = GetAllFamily $Ast + } else { + $asts = GetAllAsts $Ast + } + # Check the first ast to see if it's our starting ast, unless + $checkFirstAst = -not $IncludeStartingAst + foreach ($aAst in $asts) { + if ($checkFirstAst) { + if ($aAst -eq $Ast) { + $checkFirstAst = $false + continue + } + } + $shouldReturn = InvokeWithContext $FilterScript $aAst + + if (-not $shouldReturn) { continue } + + # Return first, last, both, or all depending on the combination of switches. + if (-not $Last.IsPresent) { + $aAst # yield + if ($First.IsPresent) { break } + } else { + $lastMatch = $aAst + if ($First.IsPresent) { + $aAst # yield + $First = $false + } + } + } + # yield + if ($Last.IsPresent) { return $lastMatch } + } + } + } +} diff --git a/module/PowerShellEditorServices/Commands/Public/Get-Token.ps1 b/module/PowerShellEditorServices/Commands/Public/Get-Token.ps1 new file mode 100644 index 000000000..a8bcfa32d --- /dev/null +++ b/module/PowerShellEditorServices/Commands/Public/Get-Token.ps1 @@ -0,0 +1,36 @@ +function Get-Token { + <# + .EXTERNALHELP ..\PowerShellEditorServices.Commands-help.xml + #> + [CmdletBinding()] + [OutputType([System.Management.Automation.Language.Token])] + [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseOutputTypeCorrectly', '', Justification='Issue #676')] + param( + [Parameter(Position=0, ValueFromPipeline, ValueFromPipelineByPropertyName)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.Language.IScriptExtent] + $Extent + ) + process { + if (-not $Extent) { + if (-not $PSCmdlet.MyInvocation.ExpectingInput) { + # yield + $psEditor.GetEditorContext().CurrentFile.Tokens + } + return + } + + $tokens = $psEditor.GetEditorContext().CurrentFile.Tokens + $predicate = [Func[System.Management.Automation.Language.Token, bool]]{ + param($Token) + + ($Token.Extent.StartOffset -ge $Extent.StartOffset -and + $Token.Extent.EndOffset -le $Extent.EndOffset) + } + if ($tokens){ + $result = [Linq.Enumerable]::Where($tokens, $predicate) + } + # yield + $result + } +} diff --git a/module/PowerShellEditorServices/Commands/Public/Import-EditorCommand.ps1 b/module/PowerShellEditorServices/Commands/Public/Import-EditorCommand.ps1 new file mode 100644 index 000000000..000eabf81 --- /dev/null +++ b/module/PowerShellEditorServices/Commands/Public/Import-EditorCommand.ps1 @@ -0,0 +1,139 @@ +function Import-EditorCommand { + <# + .EXTERNALHELP ..\PowerShellEditorServices.Commands-help.xml + #> + [OutputType([Microsoft.PowerShell.EditorServices.Extensions.EditorCommand])] + [CmdletBinding(DefaultParameterSetName='ByCommand')] + param( + [Parameter(Position=0, + Mandatory, + ValueFromPipeline, + ValueFromPipelineByPropertyName, + ParameterSetName='ByCommand')] + [ValidateNotNullOrEmpty()] + [string[]] + $Command, + + [Parameter(Position=0, Mandatory, ParameterSetName='ByModule')] + [ValidateNotNullOrEmpty()] + [string[]] + $Module, + + [switch] + $Force, + + [switch] + $PassThru + ) + begin { + function GetCommandsFromModule { + param( + [Parameter(ValueFromPipeline)] + [string] + $ModuleToSearch + ) + process { + if (-not $ModuleToSearch) { return } + + $caller = (Get-PSCallStack)[1] + + if ($caller.InvocationInfo.MyCommand.Module.Name -eq $ModuleToSearch) { + $moduleInfo = $caller.InvocationInfo.MyCommand.Module + + return $moduleInfo.Invoke( + { + $ExecutionContext.SessionState.InvokeProvider.Item.Get('function:\*') | + Where-Object ModuleName -eq $args[0] + }, + $ModuleToSearch) + } + + $moduleInfo = Get-Module $ModuleToSearch -ErrorAction SilentlyContinue + return $moduleInfo.ExportedFunctions.Values + } + } + $flags = [Reflection.BindingFlags]'Instance, NonPublic' + $extensionService = $psEditor.GetType(). + GetField('extensionService', $flags). + GetValue($psEditor) + + $editorCommands = $extensionService.GetType(). + GetField('editorCommands', $flags). + GetValue($extensionService) + } + process { + switch ($PSCmdlet.ParameterSetName) { + ByModule { + $commands = $Module | GetCommandsFromModule + } + ByCommand { + $commands = $Command | Get-Command -ErrorAction SilentlyContinue + } + } + $attributeType = [Microsoft.PowerShell.EditorServices.Extensions.EditorCommandAttribute] + foreach ($aCommand in $commands) { + # Get the attribute from our command to get name info. + $details = $aCommand.ScriptBlock.Attributes | Where-Object TypeId -eq $attributeType + + if ($details) { + # TODO: Add module name to this? + # Name: Expand-Expression becomes ExpandExpression + if (-not $details.Name) { $details.Name = $aCommand.Name -replace '-' } + + # DisplayName: Expand-Expression becomes Expand Expression + if (-not $details.DisplayName) { $details.DisplayName = $aCommand.Name -replace '-', ' ' } + + # If the editor command is already loaded skip unless force is specified. + if ($editorCommands.ContainsKey($details.Name)) { + if ($Force.IsPresent) { + $null = $psEditor.UnregisterCommand($details.Name) + } else { + $PSCmdlet.WriteVerbose($Strings.EditorCommandExists -f $details.Name) + continue + } + } + # Check for a context parameter. + $contextParameter = $aCommand.Parameters.Values | + Where-Object ParameterType -eq ([Microsoft.PowerShell.EditorServices.Extensions.EditorContext]) + + # If one is found then add a named argument. Otherwise call the command directly. + if ($contextParameter) { + $sbText = '{0} -{1} $args[0]' -f $aCommand.Name, $contextParameter.Name + $scriptBlock = [scriptblock]::Create($sbText) + } else { + $scriptBlock = [scriptblock]::Create($aCommand.Name) + } + + $editorCommand = New-Object Microsoft.PowerShell.EditorServices.Extensions.EditorCommand @( + <# commandName: #> $details.Name, + <# displayName: #> $details.DisplayName, + <# suppressOutput: #> $details.SuppressOutput, + <# scriptBlock: #> $scriptBlock) + + $PSCmdlet.WriteVerbose($Strings.EditorCommandRegistering -f $details.Name) + $null = $psEditor.RegisterCommand($editorCommand) + + if ($PassThru.IsPresent -and $editorCommand) { + $editorCommand # yield + } + } + } + } +} + +if ($PSVersionTable.PSVersion.Major -ge 5) { + Register-ArgumentCompleter -CommandName Import-EditorCommand -ParameterName Module -ScriptBlock { + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) + + (Get-Module).Name -like ($wordToComplete + '*') | ForEach-Object { + [System.Management.Automation.CompletionResult]::new($PSItem, $PSItem, 'ParameterValue', $PSItem) + } + } + Register-ArgumentCompleter -CommandName Import-EditorCommand -ParameterName Command -ScriptBlock { + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) + + (Get-Command -ListImported).Name -like ($wordToComplete + '*') | ForEach-Object { + [System.Management.Automation.CompletionResult]::new($PSItem, $PSItem, 'ParameterValue', $PSItem) + } + } +} \ No newline at end of file diff --git a/module/PowerShellEditorServices/Commands/Public/Join-ScriptExtent.ps1 b/module/PowerShellEditorServices/Commands/Public/Join-ScriptExtent.ps1 new file mode 100644 index 000000000..46b79bcd1 --- /dev/null +++ b/module/PowerShellEditorServices/Commands/Public/Join-ScriptExtent.ps1 @@ -0,0 +1,31 @@ +function Join-ScriptExtent { + <# + .EXTERNALHELP ..\PowerShellEditorServices.Commands-help.xml + #> + [CmdletBinding()] + [OutputType([System.Management.Automation.Language.IScriptExtent])] + param( + [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] + [System.Management.Automation.Language.IScriptExtent[]] + $Extent + ) + begin { + $extentList = New-Object System.Collections.Generic.List[System.Management.Automation.Language.IScriptExtent] + } + process { + if ($Extent) { + $extentList.AddRange($Extent) + } + } + end { + if (-not $extentList) { return } + + $startOffset = [Linq.Enumerable]::Min($extentList.StartOffset -as [int[]]) + $endOffset = [Linq.Enumerable]::Max($extentList.EndOffset -as [int[]]) + + return New-Object Microsoft.PowerShell.EditorServices.FullScriptExtent @( + $psEditor.GetEditorContext().CurrentFile, + $startOffset, + $endOffset) + } +} diff --git a/module/PowerShellEditorServices/Commands/Public/Set-ScriptExtent.ps1 b/module/PowerShellEditorServices/Commands/Public/Set-ScriptExtent.ps1 new file mode 100644 index 000000000..600244c09 --- /dev/null +++ b/module/PowerShellEditorServices/Commands/Public/Set-ScriptExtent.ps1 @@ -0,0 +1,92 @@ +function Set-ScriptExtent { + <# + .EXTERNALHELP ..\PowerShellEditorServices.Commands-help.xml + #> + [CmdletBinding(PositionalBinding=$false, DefaultParameterSetName='__AllParameterSets')] + param( + [Parameter(Position=0, Mandatory)] + [psobject] + $Text, + + [Parameter(Mandatory, ParameterSetName='AsString')] + [switch] + $AsString, + + [Parameter(Mandatory, ParameterSetName='AsArray')] + [switch] + $AsArray, + + [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] + [System.Management.Automation.Language.IScriptExtent] + $Extent = (Find-Ast -AtCursor).Extent + ) + begin { + $fileContext = $psEditor.GetEditorContext().CurrentFile + $extentList = New-Object System.Collections.Generic.List[Microsoft.PowerShell.EditorServices.FullScriptExtent] + } + process { + if ($Extent -isnot [Microsoft.PowerShell.EditorServices.FullScriptExtent]) { + $Extent = New-Object Microsoft.PowerShell.EditorServices.FullScriptExtent @( + $fileContext, + $Extent.StartOffset, + $Extent.EndOffset) + } + $extentList.Add($Extent) + } + # Currently this kills the pipeline because we need to keep track and edit all extents for position tracking. + # TODO: Consider queueing changes in a static property and adding a PassThru switch. + end { + switch ($PSCmdlet.ParameterSetName) { + # Insert text as a single string expression. + AsString { + $Text = "'{0}'" -f $Text.Replace("'", "''") + } + # Create a string expression for each line, separated by a comma. + AsArray { + $newLine = [Environment]::NewLine + $Text = "'" + ($Text.Replace("'", "''") -split '\r?\n' -join "',$newLine'") + "'" + + if ($Text.Split("`n", [StringSplitOptions]::RemoveEmptyEntries).Count -gt 1) { + $needsIndentFix = $true + } + } + } + + foreach ($aExtent in $extentList) { + $aText = $Text + + if ($needsIndentFix) { + # I'd rather let PSSA handle this when there are more formatting options. + $indentOffset = ' ' * ($aExtent.StartColumnNumber - 1) + $aText = $aText -split '\r?\n' ` + -join ([Environment]::NewLine + $indentOffset) + } + $differenceOffset = $aText.Length - $aExtent.Text.Length + $scriptText = $fileContext.GetText() + + $fileContext.InsertText($aText, $aExtent.BufferRange) + + $newText = $scriptText.Remove($aExtent.StartOffset, $aExtent.Text.Length).Insert($aExtent.StartOffset, $aText) + + $timeoutLoop = 0 + while ($fileContext.GetText() -ne $newText) { + Start-Sleep -Milliseconds 30 + $timeoutLoop++ + + if ($timeoutLoop -gt 20) { + $PSCmdlet.WriteDebug(('Timed out waiting for change at range {0}, {1}' -f $aExtent.StartOffset, + $aExtent.EndOffset)) + break + } + } + + if ($differenceOffset) { + $extentList.ForEach({ + if ($args[0].StartOffset -ge $aExtent.EndOffset) { + $args[0].AddOffset($differenceOffset) + } + }) + } + } + } +} diff --git a/module/PowerShellEditorServices/Commands/Public/Test-ScriptExtent.ps1 b/module/PowerShellEditorServices/Commands/Public/Test-ScriptExtent.ps1 new file mode 100644 index 000000000..d99869d04 --- /dev/null +++ b/module/PowerShellEditorServices/Commands/Public/Test-ScriptExtent.ps1 @@ -0,0 +1,43 @@ +function Test-ScriptExtent { + <# + .EXTERNALHELP ..\PowerShellEditorServices.Commands-help.xml + #> + [OutputType([bool], ParameterSetName='__AllParameterSets')] + [OutputType([System.Management.Automation.Language.IScriptExtent], ParameterSetName='PassThru')] + [CmdletBinding()] + param( + [Parameter(Position=0, ValueFromPipeline, ValueFromPipelineByPropertyName)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.Language.IScriptExtent] + $Extent, + + [ValidateNotNullOrEmpty()] + [System.Management.Automation.Language.IScriptExtent] + $Inside, + + [ValidateNotNullOrEmpty()] + [System.Management.Automation.Language.IScriptExtent] + $After, + + [ValidateNotNullOrEmpty()] + [System.Management.Automation.Language.IScriptExtent] + $Before, + + [Parameter(ParameterSetName='PassThru')] + [switch] + $PassThru + ) + process { + if (-not $Extent) { return $false } + $passes = (-not $After -or $Extent.StartOffset -gt $After.EndOffset) -and + (-not $Before -or $Extent.EndOffset -lt $Before.StartOffset) -and + (-not $Inside -or ($Extent.StartOffset -ge $Inside.StartOffset -and + $Extent.EndOffset -le $Inside.EndOffset)) + + if (-not $PassThru.IsPresent) { return $passes } + + if ($passes) { + $Extent # yield + } + } +} From 5bc6c293336a761843077be634c81314989056c2 Mon Sep 17 00:00:00 2001 From: "Patrick M. Meinecke" Date: Tue, 6 Jun 2017 22:37:08 -0400 Subject: [PATCH 4/9] Fix module loading process - Add standard function loading process. - Add functions to export list in manifest. - Update manifest with a unique guid. - Add localized data file loading. --- .../Commands/PowerShellEditorServices.Commands.psd1 | 12 ++++++++++-- .../Commands/PowerShellEditorServices.Commands.psm1 | 6 ++++-- .../Commands/en-US/Strings.psd1 | 5 +++++ 3 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 module/PowerShellEditorServices/Commands/en-US/Strings.psd1 diff --git a/module/PowerShellEditorServices/Commands/PowerShellEditorServices.Commands.psd1 b/module/PowerShellEditorServices/Commands/PowerShellEditorServices.Commands.psd1 index ba6bd62a9..605c4980b 100644 --- a/module/PowerShellEditorServices/Commands/PowerShellEditorServices.Commands.psd1 +++ b/module/PowerShellEditorServices/Commands/PowerShellEditorServices.Commands.psd1 @@ -15,7 +15,7 @@ RootModule = 'PowerShellEditorServices.Commands.psm1' ModuleVersion = '1.0.0' # ID used to uniquely identify this module -GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' +GUID = '6064d846-0fa0-4b6d-afc1-11e5bed3c4a9' # Author of this module Author = 'Microsoft' @@ -66,7 +66,15 @@ Description = 'Provides internal commands for PowerShell Editor Services that on # NestedModules = @() # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. -FunctionsToExport = @('Register-EditorCommand', 'Unregister-EditorCommand') +FunctionsToExport = @('Register-EditorCommand', + 'Unregister-EditorCommand', + 'Set-ScriptExtent', + 'Find-Ast', + 'Import-EditorCommand', + 'ConvertFrom-ScriptExtent', + 'ConvertTo-ScriptExtent', + 'Get-Token', + 'Join-ScriptExtent') # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. CmdletsToExport = @() diff --git a/module/PowerShellEditorServices/Commands/PowerShellEditorServices.Commands.psm1 b/module/PowerShellEditorServices/Commands/PowerShellEditorServices.Commands.psm1 index 62ac9af39..c818523d1 100644 --- a/module/PowerShellEditorServices/Commands/PowerShellEditorServices.Commands.psm1 +++ b/module/PowerShellEditorServices/Commands/PowerShellEditorServices.Commands.psm1 @@ -1,3 +1,5 @@ +Import-LocalizedData -BindingVariable Strings -FileName Strings -# TODO: Use a better script loading process here -. $PSScriptRoot\Public\CmdletInterface.ps1 \ No newline at end of file +Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 | ForEach-Object { + . $PSItem.FullName +} diff --git a/module/PowerShellEditorServices/Commands/en-US/Strings.psd1 b/module/PowerShellEditorServices/Commands/en-US/Strings.psd1 new file mode 100644 index 000000000..d5db71a6b --- /dev/null +++ b/module/PowerShellEditorServices/Commands/en-US/Strings.psd1 @@ -0,0 +1,5 @@ +ConvertFrom-StringData @' +EditorCommandExists=Editor command '{0}' already exists, skipping. +EditorCommandImporting=Importing editor command '{0}'. +MissingEditorContext=Unable to obtain editor context. Make sure PowerShell Editor Services is running and then try the command again. +'@ From 7c6743e865ec047050f5fdfcf52a80bdac079f7d Mon Sep 17 00:00:00 2001 From: "Patrick M. Meinecke" Date: Tue, 6 Jun 2017 22:41:05 -0400 Subject: [PATCH 5/9] Add markdown help This change adds markdown help for all added functions. It also adds a step in the build process to create the MAML XML file. --- PowerShellEditorServices.build.ps1 | 2 + module/docs/ConvertFrom-ScriptExtent.md | 146 ++++++++++ module/docs/ConvertTo-ScriptExtent.md | 251 ++++++++++++++++++ module/docs/Find-Ast.md | 249 +++++++++++++++++ module/docs/Get-Token.md | 72 +++++ module/docs/Import-EditorCommand.md | 137 ++++++++++ module/docs/Join-ScriptExtent.md | 72 +++++ .../docs/PowerShellEditorServices.Commands.md | 53 ++++ module/docs/Set-ScriptExtent.md | 141 ++++++++++ module/docs/Test-ScriptExtent.md | 140 ++++++++++ 10 files changed, 1263 insertions(+) create mode 100644 module/docs/ConvertFrom-ScriptExtent.md create mode 100644 module/docs/ConvertTo-ScriptExtent.md create mode 100644 module/docs/Find-Ast.md create mode 100644 module/docs/Get-Token.md create mode 100644 module/docs/Import-EditorCommand.md create mode 100644 module/docs/Join-ScriptExtent.md create mode 100644 module/docs/PowerShellEditorServices.Commands.md create mode 100644 module/docs/Set-ScriptExtent.md create mode 100644 module/docs/Test-ScriptExtent.md diff --git a/PowerShellEditorServices.build.ps1 b/PowerShellEditorServices.build.ps1 index 30e65dea0..4a7dad300 100644 --- a/PowerShellEditorServices.build.ps1 +++ b/PowerShellEditorServices.build.ps1 @@ -196,6 +196,8 @@ task LayoutModule -After Build { Copy-Item -Force -Path $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\net451\Newtonsoft.Json.dll -Destination $PSScriptRoot\module\PowerShellEditorServices\bin\Desktop\ } Copy-Item -Force -Path $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\netstandard1.6\* -Filter Microsoft.PowerShell.EditorServices*.dll -Destination $PSScriptRoot\module\PowerShellEditorServices\bin\Core\ + + New-ExternalHelp -Path $PSScriptRoot\module\docs -OutputPath $PSScriptRoot\module\PowerShellEditorServices\Commands\en-US -Force } task PackageNuGet { diff --git a/module/docs/ConvertFrom-ScriptExtent.md b/module/docs/ConvertFrom-ScriptExtent.md new file mode 100644 index 000000000..b4f8ed22d --- /dev/null +++ b/module/docs/ConvertFrom-ScriptExtent.md @@ -0,0 +1,146 @@ +--- +external help file: PowerShellEditorServices.Commands-help.xml +online version: +schema: 2.0.0 +--- + +# ConvertFrom-ScriptExtent + +## SYNOPSIS + +Converts IScriptExtent objects to some common EditorServices types. + +## SYNTAX + +### BufferRange + +```powershell +ConvertFrom-ScriptExtent -Extent [-BufferRange] [] +``` + +### BufferPosition + +```powershell +ConvertFrom-ScriptExtent -Extent [-BufferPosition] [-Start] [-End] [] +``` + +## DESCRIPTION + +Translates IScriptExtent object properties into constructors for some common PowerShell EditorServices types. + +## EXAMPLES + +### -------------------------- EXAMPLE 1 -------------------------- + +```powershell +$sb = { Get-ChildItem 'Documents' } +$sb.Ast | Find-Ast { $_ -eq 'Documents' } | ConvertFrom-ScriptExtent -BufferRange +``` + +Gets the buffer range of the string expression "Documents". + +## PARAMETERS + +### -Extent + +Specifies the extent to be converted. + +```yaml +Type: IScriptExtent[] +Parameter Sets: (All) +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName, ByValue) +Accept wildcard characters: False +``` + +### -BufferRange + +If specified will convert extents to BufferRange objects. + +```yaml +Type: SwitchParameter +Parameter Sets: BufferRange +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -BufferPosition + +If specified will convert extents to BufferPosition objects. + +```yaml +Type: SwitchParameter +Parameter Sets: BufferPosition +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Start + +Specifies to use the start of the extent when converting to types with no range. This is the default. + +```yaml +Type: SwitchParameter +Parameter Sets: BufferPosition +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -End + +Specifies to use the end of the extent when converting to types with no range. + +```yaml +Type: SwitchParameter +Parameter Sets: BufferPosition +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters + +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.Management.Automation.Language.IScriptExtent + +You can pipe IScriptExtent objects to be converted. + +## OUTPUTS + +### Microsoft.PowerShell.EditorServices.BufferRange + +### Microsoft.PowerShell.EditorServices.BufferPosition + +This function will return an extent converted to one of the above types depending on switch +choices. + +## NOTES + +## RELATED LINKS + diff --git a/module/docs/ConvertTo-ScriptExtent.md b/module/docs/ConvertTo-ScriptExtent.md new file mode 100644 index 000000000..155cd2d97 --- /dev/null +++ b/module/docs/ConvertTo-ScriptExtent.md @@ -0,0 +1,251 @@ +--- +external help file: PowerShellEditorServices.Commands-help.xml +online version: +schema: 2.0.0 +--- + +# ConvertTo-ScriptExtent + +## SYNOPSIS + +Converts position and range objects from PowerShellEditorServices to ScriptExtent objects. + +## SYNTAX + +### ByObject + +```powershell +ConvertTo-ScriptExtent [-InputObject ] [] +``` + +### ByPosition + +```powershell +ConvertTo-ScriptExtent [-StartLineNumber ] [-StartColumnNumber ] [-EndLineNumber ] + [-EndColumnNumber ] [-FilePath ] [] +``` + +### ByOffset + +```powershell +ConvertTo-ScriptExtent [-StartOffsetNumber ] [-EndOffsetNumber ] [-FilePath ] + [] +``` + +### ByBuffer + +```powershell +ConvertTo-ScriptExtent [-FilePath ] [-StartBuffer ] [-EndBuffer ] + [] +``` + +## DESCRIPTION + +Converts position and range objects from PowerShellEditorServices to ScriptExtent objects. + +## EXAMPLES + +### -------------------------- EXAMPLE 1 -------------------------- + +```powershell +$psEditor.GetEditorContext().SelectedRange | ConvertTo-ScriptExtent +``` + +Returns a InternalScriptExtent object of the currently selected range. + +## PARAMETERS + +### -InputObject + +This is here so we can pass script extent objects through without any processing. + +```yaml +Type: IScriptExtent +Parameter Sets: ByObject +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +### -StartLineNumber + +Specifies the starting line number. + +```yaml +Type: Int32 +Parameter Sets: ByPosition +Aliases: StartLine, Line + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -StartColumnNumber + +Specifies the starting column number. + +```yaml +Type: Int32 +Parameter Sets: ByPosition +Aliases: StartColumn, Column + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -EndLineNumber + +Specifies the ending line number. + +```yaml +Type: Int32 +Parameter Sets: ByPosition +Aliases: EndLine + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -EndColumnNumber + +Specifies the ending column number. + +```yaml +Type: Int32 +Parameter Sets: ByPosition +Aliases: EndColumn + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -StartOffsetNumber + +Specifies the starting offset number. + +```yaml +Type: Int32 +Parameter Sets: ByOffset +Aliases: StartOffset, Offset + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -EndOffsetNumber + +Specifies the ending offset number. + +```yaml +Type: Int32 +Parameter Sets: ByOffset +Aliases: EndOffset + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -FilePath + +Specifies the path of the source script file. + +```yaml +Type: String +Parameter Sets: ByPosition, ByOffset, ByBuffer +Aliases: File, FileName + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -StartBuffer + +Specifies the starting buffer position. + +```yaml +Type: BufferPosition +Parameter Sets: ByBuffer +Aliases: Start + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -EndBuffer + +Specifies the ending buffer position. + +```yaml +Type: BufferPosition +Parameter Sets: ByBuffer +Aliases: End + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### CommonParameters + +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.Object + +You can pass any object with any of the following properties. + +StartLineNumber, StartLine, Line +EndLineNumber, EndLine +StartColumnNumber, StartColumn, Column +EndColumnNumber, EndColumn +StartOffsetNumber, StartOffset, Offset +EndOffsetNumber, EndOffset +StartBuffer, Start +EndBuffer, End + +Objects of type IScriptExtent will be passed through with no processing. + +## OUTPUTS + +### System.Management.Automation.Language.IScriptExtent + +### System.Management.Automation.Language.InternalScriptExtent + +This function will return any IScriptExtent object passed without processing. Objects created +by this function will be of type InternalScriptExtent. + +## NOTES + +## RELATED LINKS + diff --git a/module/docs/Find-Ast.md b/module/docs/Find-Ast.md new file mode 100644 index 000000000..90f78a531 --- /dev/null +++ b/module/docs/Find-Ast.md @@ -0,0 +1,249 @@ +--- +external help file: PowerShellEditorServices.Commands-help.xml +online version: +schema: 2.0.0 +--- + +# Find-Ast + +## SYNOPSIS + +Search for a ast within an ast. + +## SYNTAX + +### FilterScript (Default) + +```powershell +Find-Ast [[-FilterScript] ] [-Ast ] [-Before] [-Family] [-First] [-Last] [-Ancestor] + [-IncludeStartingAst] [] +``` + +### AtCursor + +```powershell +Find-Ast [-AtCursor] [] +``` + +## DESCRIPTION + +The Find-Ast function can be used to easily find a specific ast from a starting ast. By default children asts will be searched, but ancestor asts can also be searched by specifying the "Ancestor" switch parameter. + +Additionally, you can find the Ast closest to the cursor with the "AtCursor" switch parameter. + +## EXAMPLES + +### -------------------------- EXAMPLE 1 -------------------------- + +```powershell +Find-Ast +``` + +Returns all asts in the currently open file in the editor. + +### -------------------------- EXAMPLE 2 -------------------------- + +```powershell +Find-Ast -First -IncludeStartingAst +``` + +Returns the top level ast in the currently open file in the editor. + +### -------------------------- EXAMPLE 3 -------------------------- + +```powershell +Find-Ast { $PSItem -is [FunctionDefinitionAst] } +``` + +Returns all function definition asts in the ast of file currently open in the editor. + +### -------------------------- EXAMPLE 4 -------------------------- + +```powershell +Find-Ast { $_.Member } +``` + +Returns all member expressions in the file currently open in the editor. + +### -------------------------- EXAMPLE 5 -------------------------- + +```powershell +Find-Ast { $_.InvocationOperator -eq 'Dot' } | Find-Ast -Family { $_.VariablePath } +``` + +Returns all variable expressions used in a dot source expression. + +### -------------------------- EXAMPLE 6 -------------------------- + +```powershell +Find-Ast { 'PowerShellVersion' -eq $_ } | Find-Ast -First | Set-ScriptExtent -Text "'4.0'" +``` + +First finds the ast of the PowerShellVersion manifest tag, then finds the first ast after it and changes the text to '4.0'. This will not work as is if the field is commented. + +## PARAMETERS + +### -FilterScript + +Specifies a ScriptBlock that returns $true if an ast should be returned. Uses $PSItem and $_ like Where-Object. If not specified all asts will be returned. + +```yaml +Type: ScriptBlock +Parameter Sets: FilterScript +Aliases: + +Required: False +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Ast + +Specifies the starting ast. The default is the ast of the current file in PowerShell Editor Services. + +```yaml +Type: Ast +Parameter Sets: FilterScript +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName, ByValue) +Accept wildcard characters: False +``` + +### -Before + +If specified the direction of the search will be reversed. + +```yaml +Type: SwitchParameter +Parameter Sets: FilterScript +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Family + +If specified only children of the starting ast will be searched. If specified with the "Before" parameter then only ancestors will be searched. + +```yaml +Type: SwitchParameter +Parameter Sets: FilterScript +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -First + +If specified will return only the first result. This will be the closest ast that matches. + +```yaml +Type: SwitchParameter +Parameter Sets: FilterScript +Aliases: Closest, F + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Last + +If specified will return only the last result. This will be the furthest ast that matches. + +```yaml +Type: SwitchParameter +Parameter Sets: FilterScript +Aliases: Furthest + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Ancestor + +If specified will only search ancestors of the starting ast. This is a convenience parameter that acts the same as the "Family" and "Before" parameters when used together. + +```yaml +Type: SwitchParameter +Parameter Sets: FilterScript +Aliases: Parent + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -IncludeStartingAst + +If specified the starting ast will be included if matched. + +```yaml +Type: SwitchParameter +Parameter Sets: FilterScript +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -AtCursor + +If specified, this function will return the smallest ast that the cursor is within. Requires PowerShell Editor Services. + +```yaml +Type: SwitchParameter +Parameter Sets: AtCursor +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters + +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.Management.Automation.Language.Ast + +You can pass asts to search to this function. + +## OUTPUTS + +### System.Management.Automation.Language.Ast + +Asts that match the criteria will be returned to the pipeline. + +## NOTES + +## RELATED LINKS + diff --git a/module/docs/Get-Token.md b/module/docs/Get-Token.md new file mode 100644 index 000000000..182653883 --- /dev/null +++ b/module/docs/Get-Token.md @@ -0,0 +1,72 @@ +--- +external help file: PowerShellEditorServices.Commands-help.xml +online version: +schema: 2.0.0 +--- + +# Get-Token + +## SYNOPSIS + +Get parser tokens from a script position. + +## SYNTAX + +```powershell +Get-Token [[-Extent] ] +``` + +## DESCRIPTION + +The Get-Token function can retrieve tokens from the current editor context, or from a ScriptExtent object. You can then use the ScriptExtent functions to manipulate the text at it's location. + +## EXAMPLES + +### -------------------------- EXAMPLE 1 -------------------------- + +```powershell +using namespace System.Management.Automation.Language +Find-Ast { $_ -is [IfStatementAst] } -First | Get-Token +``` + +Gets all tokens from the first IfStatementAst. + +### -------------------------- EXAMPLE 2 -------------------------- + +```powershell +Get-Token | Where-Object { $_.Kind -eq 'Comment' } +``` + +Gets all comment tokens. + +## PARAMETERS + +### -Extent + +Specifies the extent that a token must be within to be returned. + +```yaml +Type: IScriptExtent +Parameter Sets: (All) +Aliases: + +Required: False +Position: 1 +Default value: None +Accept pipeline input: True (ByPropertyName, ByValue) +Accept wildcard characters: False +``` + +## INPUTS + +### System.Management.Automation.Language.IScriptExtent + +You can pass extents to get tokens from to this function. You can also pass objects that with a property named "Extent", like Ast objects from the Find-Ast function. + +## OUTPUTS + +### System.Management.Automation.Language.Token + +## NOTES + +## RELATED LINKS diff --git a/module/docs/Import-EditorCommand.md b/module/docs/Import-EditorCommand.md new file mode 100644 index 000000000..5ad184317 --- /dev/null +++ b/module/docs/Import-EditorCommand.md @@ -0,0 +1,137 @@ +--- +external help file: PowerShellEditorServices.Commands-help.xml +online version: +schema: 2.0.0 +--- + +# Import-EditorCommand + +## SYNOPSIS + +Imports commands with the PSEditorCommand attribute into PowerShell Editor Services. + +## SYNTAX + +### ByModule + +```powershell +Import-EditorCommand [-Module] [-Force] [-PassThru] [] +``` + +### ByCommand + +```powershell +Import-EditorCommand [-Command] [-Force] [-PassThru] [] +``` + +## DESCRIPTION + +The Import-EditorCommand function will search the specified module for functions tagged as editor commands and register them with PowerShell Editor Services. By default, if a module is specified only exported functions will be processed. + +Alternatively, you can specify command info objects (like those from the Get-Command cmdlet) to be processed directly. + +## EXAMPLES + +### -------------------------- EXAMPLE 1 -------------------------- + +```powershell +Import-EditorCommand -Module PowerShellEditorServices.Commands +``` + +Registers all editor commands in the module PowerShellEditorServices.Commands. + +### -------------------------- EXAMPLE 2 -------------------------- + +```powershell +Get-Command *Editor* | Import-EditorCommand -PassThru +``` + +Registers all editor commands that contain "Editor" in the name and return all successful imports. + +## PARAMETERS + +### -Module + +Specifies the module to search for exportable editor commands. + +```yaml +Type: string[] +Parameter Sets: ByModule +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Command + +Specifies the functions to register as editor commands. If the function does not have the PSEditorCommand attribute it will be ignored. + +```yaml +Type: string[] +Parameter Sets: ByCommand +Aliases: + +Required: True +Position: 1 +Default value: None +Accept pipeline input: True (ByPropertyName, ByValue) +Accept wildcard characters: False +``` + +### -Force + +If specified will replace existing editor commands. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -PassThru + +If specified will return an EditorCommand object for each imported command. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters + +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.Management.Automation.CommandInfo + +You can pass commands to register as editor commands. + +## OUTPUTS + +### Microsoft.PowerShell.EditorServices.Extensions.EditorCommand + +If the "PassThru" parameter is specified editor commands that were successfully registered +will be returned. This function does not output to the pipeline otherwise. + +## NOTES + +## RELATED LINKS + diff --git a/module/docs/Join-ScriptExtent.md b/module/docs/Join-ScriptExtent.md new file mode 100644 index 000000000..e5bdd91c0 --- /dev/null +++ b/module/docs/Join-ScriptExtent.md @@ -0,0 +1,72 @@ +--- +external help file: PowerShellEditorServices.Commands-help.xml +online version: +schema: 2.0.0 +--- + +# Join-ScriptExtent + +## SYNOPSIS + +Combine script extents. + +## SYNTAX + +```powershell +Join-ScriptExtent [[-Extent] ] [] +``` + +## DESCRIPTION + +The Join-ScriptExtent function will combine all ScriptExtent objects piped to it into a single extent. + +## EXAMPLES + +### -------------------------- EXAMPLE 1 -------------------------- + +```powershell +$ast = Find-Ast { $_.Arguments.Count -gt 4 } -First +$ast.Arguments[0..1] | Join-ScriptExtent | Set-ScriptExtent -Text '' +``` + +Finds the first InvokeMemberExpression ast that has over 4 arguments and removes the first two. + +## PARAMETERS + +### -Extent + +Specifies the extents to combine. If a single extent is passed, it will be returned as is. If no extents are passed nothing will be returned. Extents passed from the pipeline are processed after pipeline input completes. + +```yaml +Type: IScriptExtent[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: 1 +Default value: None +Accept pipeline input: True (ByPropertyName, ByValue) +Accept wildcard characters: False +``` + +### CommonParameters + +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.Management.Automation.Language.IScriptExtent + +You can pass script extent objects to this function. You can also pass objects with a property +named "Extent". + +## OUTPUTS + +### System.Management.Automation.Language.IScriptExtent + +The combined extent is returned. + +## NOTES + +## RELATED LINKS + diff --git a/module/docs/PowerShellEditorServices.Commands.md b/module/docs/PowerShellEditorServices.Commands.md new file mode 100644 index 000000000..070844b42 --- /dev/null +++ b/module/docs/PowerShellEditorServices.Commands.md @@ -0,0 +1,53 @@ +--- +Module Name: PowerShellEditorServices.Commands +Module Guid: 6064d846-0fa0-4b6d-afc1-11e5bed3c4a9 +Download Help Link: +Help Version: +Locale: en-US +--- + +# PowerShellEditorServices.Commands Module + +## Description + +Module to facilitate easy manipulation of script files and editor features. + +## PowerShellEditorServices.Commands Cmdlets + +### [ConvertFrom-ScriptExtent](ConvertFrom-ScriptExtent.md) + +Translates IScriptExtent object properties into constructors for some common PowerShell EditorServices types. + +### [ConvertTo-ScriptExtent](ConvertTo-ScriptExtent.md) + +Converts position and range objects from PowerShellEditorServices to ScriptExtent objects. + +### [Find-Ast](Find-Ast.md) + +The Find-Ast function can be used to easily find a specific ast from a starting ast. By +default children asts will be searched, but ancestor asts can also be searched by specifying +the "Ancestor" switch parameter. + +### [Get-Token](Get-Token.md) + +The Get-Token function can retrieve tokens from the current editor context, or from a ScriptExtent object. You can then use the ScriptExtent functions to manipulate the text at it's location. + +### [Import-EditorCommand](Import-EditorCommand.md) + +The Import-EditorCommand function will search the specified module for functions tagged as editor commands and register them with PowerShell Editor Services. By default, if a module is specified only exported functions will be processed. + +Alternatively, you can specify command info objects (like those from the Get-Command cmdlet) to be processed directly. + +### [Join-ScriptExtent](Join-ScriptExtent.md) + +The Join-ScriptExtent function will combine all ScriptExtent objects piped to it into a single extent. + +### [Set-ScriptExtent](Set-ScriptExtent.md) + +The Set-ScriptExtent function can insert or replace text at a specified position in a file open in PowerShell Editor Services. + +You can use the Find-Ast function to easily find the desired extent. + +### [Test-ScriptExtent](Test-ScriptExtent.md) + +The Test-ScriptExtent function can be used to determine if a ScriptExtent object is before, after, or inside another ScriptExtent object. You can also test for any combination of these with separate ScriptExtent objects to test against. diff --git a/module/docs/Set-ScriptExtent.md b/module/docs/Set-ScriptExtent.md new file mode 100644 index 000000000..f33d0e809 --- /dev/null +++ b/module/docs/Set-ScriptExtent.md @@ -0,0 +1,141 @@ +--- +external help file: PowerShellEditorServices.Commands-help.xml +online version: +schema: 2.0.0 +--- + +# Set-ScriptExtent + +## SYNOPSIS + +Replaces text at a specified IScriptExtent object. + +## SYNTAX + +### __AllParameterSets (Default) + +```powershell +Set-ScriptExtent [-Text] [-Extent ] [] +``` + +### AsString + +```powershell +Set-ScriptExtent [-Text] [-AsString] [-Extent ] [] +``` + +### AsArray + +```powershell +Set-ScriptExtent [-Text] [-AsArray] [-Extent ] [] +``` + +## DESCRIPTION + +The Set-ScriptExtent function can insert or replace text at a specified position in a file open in PowerShell Editor Services. + +You can use the Find-Ast function to easily find the desired extent. + +## EXAMPLES + +### -------------------------- EXAMPLE 1 -------------------------- + +```powershell +Find-Ast { 'gci' -eq $_ } | Set-ScriptExtent -Text 'Get-ChildItem' +``` + +Replaces all instances of 'gci' with 'Get-ChildItem' + +### -------------------------- EXAMPLE 2 -------------------------- + +```powershell +$manifestAst = Find-Ast { 'FunctionsToExport' -eq $_ } | Find-Ast -First +$manifestAst | Set-ScriptExtent -Text (gci .\src\Public).BaseName -AsArray +``` + +Replaces the current value of FunctionsToExport in a module manifest with a list of files in the Public folder as a string array literal expression. + +## PARAMETERS + +### -Text + +Specifies the text to insert in place of the extent. Any object can be specified, but will be converted to a string before being passed to PowerShell Editor Services. + +```yaml +Type: PSObject +Parameter Sets: (All) +Aliases: Value + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -AsString + +Specifies to insert as a single quoted string expression. + +```yaml +Type: SwitchParameter +Parameter Sets: AsString +Aliases: + +Required: True +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -AsArray + +Specifies to insert as a single quoted string list. The list is separated by comma and new line, and will be adjusted to a hanging indent. + +```yaml +Type: SwitchParameter +Parameter Sets: AsArray +Aliases: + +Required: True +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Extent + +Specifies the extent to replace within the editor. + +```yaml +Type: IScriptExtent +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: (Find-Ast -AtCursor).Extent +Accept pipeline input: True (ByPropertyName, ByValue) +Accept wildcard characters: False +``` + +### CommonParameters + +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.Management.Automation.Language.IScriptExtent + +You can pass script extent objects to this function. You can also pass objects with a property named "Extent". + +## OUTPUTS + +### None + +## NOTES + +## RELATED LINKS + diff --git a/module/docs/Test-ScriptExtent.md b/module/docs/Test-ScriptExtent.md new file mode 100644 index 000000000..254d1a573 --- /dev/null +++ b/module/docs/Test-ScriptExtent.md @@ -0,0 +1,140 @@ +--- +external help file: PowerShellEditorServices.Commands-help.xml +online version: +schema: 2.0.0 +--- + +# Test-ScriptExtent + +## SYNOPSIS + +Test the position of a ScriptExtent object in relation to another. + +## SYNTAX + +```powershell +Test-ScriptExtent [[-Extent] ] [-Inside ] [-After ] + [-Before ] [-PassThru] +``` + +## DESCRIPTION + +The Test-ScriptExtent function can be used to determine if a ScriptExtent object is before, after, or inside another ScriptExtent object. You can also test for any combination of these with separate ScriptExtent objects to test against. + +## EXAMPLES + +### -------------------------- EXAMPLE 1 -------------------------- + +```powershell +Test-ScriptExtent -Extent $extent1 -Inside $extent2 +``` + +Test if $extent1 is inside $extent2. + +### -------------------------- EXAMPLE 2 -------------------------- + +```powershell +$extentList | Test-ScriptExtent -Before $extent1 -After $extent2 -PassThru +``` + +Return all extents in $extentList that are before $extent1 but after $extent2. + +## PARAMETERS + +### -Extent + +Specifies the extent to test against. + +```yaml +Type: IScriptExtent +Parameter Sets: (All) +Aliases: + +Required: False +Position: 1 +Default value: None +Accept pipeline input: True (ByPropertyName, ByValue) +Accept wildcard characters: False +``` + +### -Inside + +Specifies that the reference extent must be inside this extent for the test to pass. + +```yaml +Type: IScriptExtent +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -After + +Specifies that the reference extent must be after this extent for the test to pass. + +```yaml +Type: IScriptExtent +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Before + +Specifies that the reference extent must be before this extent for the test to pass. + +```yaml +Type: IScriptExtent +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -PassThru + +If specified this function will return the reference extent if the test passed instead of returning a boolean. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +## INPUTS + +### System.Management.Automation.Language.IScriptExtent + +You can pass reference script extent objects to this function. + +## OUTPUTS + +### Boolean, System.Management.Automation.Language.IScriptExtent + +The result of the test will be returned to the pipeline. + +If the "PassThru" parameter is specified and the test passed, the reference script extent will be returned instead. + +## NOTES + +## RELATED LINKS From 34b57647b5ed188b3e0b88297fd5b810630d6945 Mon Sep 17 00:00:00 2001 From: "Patrick M. Meinecke" Date: Wed, 7 Jun 2017 18:57:25 -0400 Subject: [PATCH 6/9] A few minor fixes - Fix an issue with parameter set resolution in ConvertTo-ScriptExtent. - Fix importing editor commands from a module that is in the middle of being imported. - Add parameter validation to FullScriptExtent constructors. --- .../Public/ConvertTo-ScriptExtent.ps1 | 25 +++++++++++-------- .../Commands/Public/Import-EditorCommand.ps1 | 6 ++--- .../Language/FullScriptExtent.cs | 11 ++++++++ 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/module/PowerShellEditorServices/Commands/Public/ConvertTo-ScriptExtent.ps1 b/module/PowerShellEditorServices/Commands/Public/ConvertTo-ScriptExtent.ps1 index 6e913aa78..12c9c9495 100644 --- a/module/PowerShellEditorServices/Commands/Public/ConvertTo-ScriptExtent.ps1 +++ b/module/PowerShellEditorServices/Commands/Public/ConvertTo-ScriptExtent.ps1 @@ -5,6 +5,16 @@ function ConvertTo-ScriptExtent { [CmdletBinding()] [OutputType([System.Management.Automation.Language.IScriptExtent])] param( + [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName='ByOffset')] + [Alias('StartOffset', 'Offset')] + [int] + $StartOffsetNumber, + + [Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByOffset')] + [Alias('EndOffset')] + [int] + $EndOffsetNumber, + [Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByPosition')] [Alias('StartLine', 'Line')] [int] @@ -25,16 +35,6 @@ function ConvertTo-ScriptExtent { [int] $EndColumnNumber, - [Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByOffset')] - [Alias('StartOffset', 'Offset')] - [int] - $StartOffsetNumber, - - [Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByOffset')] - [Alias('EndOffset')] - [int] - $EndOffsetNumber, - [Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByPosition')] [Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByOffset')] [Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByBuffer')] @@ -52,7 +52,10 @@ function ConvertTo-ScriptExtent { [Microsoft.PowerShell.EditorServices.BufferPosition] $EndBuffer, - [Parameter(ValueFromPipeline)] + [Parameter(Mandatory, + ValueFromPipeline, + ValueFromPipelineByPropertyName, + ParameterSetName='ByExtent')] [System.Management.Automation.Language.IScriptExtent] $Extent ) diff --git a/module/PowerShellEditorServices/Commands/Public/Import-EditorCommand.ps1 b/module/PowerShellEditorServices/Commands/Public/Import-EditorCommand.ps1 index 000eabf81..b4957d9e0 100644 --- a/module/PowerShellEditorServices/Commands/Public/Import-EditorCommand.ps1 +++ b/module/PowerShellEditorServices/Commands/Public/Import-EditorCommand.ps1 @@ -35,10 +35,10 @@ function Import-EditorCommand { process { if (-not $ModuleToSearch) { return } - $caller = (Get-PSCallStack)[1] + $caller = (Get-PSCallStack)[2] - if ($caller.InvocationInfo.MyCommand.Module.Name -eq $ModuleToSearch) { - $moduleInfo = $caller.InvocationInfo.MyCommand.Module + if ($caller.InvocationInfo.MyCommand.ScriptBlock.Module.Name -eq $ModuleToSearch) { + $moduleInfo = $caller.InvocationInfo.MyCommand.ScriptBlock.Module return $moduleInfo.Invoke( { diff --git a/src/PowerShellEditorServices/Language/FullScriptExtent.cs b/src/PowerShellEditorServices/Language/FullScriptExtent.cs index 454d4d368..3db5b9f5d 100644 --- a/src/PowerShellEditorServices/Language/FullScriptExtent.cs +++ b/src/PowerShellEditorServices/Language/FullScriptExtent.cs @@ -1,5 +1,6 @@ using System.Management.Automation.Language; using Microsoft.PowerShell.EditorServices.Extensions; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices { @@ -116,6 +117,9 @@ public string Text /// The buffer range this extent is located at. public FullScriptExtent(FileContext fileContext, BufferRange bufferRange) { + Validate.IsNotNull(nameof(fileContext), fileContext); + Validate.IsNotNull(nameof(bufferRange), bufferRange); + BufferRange = bufferRange; FileContext = fileContext; @@ -136,6 +140,10 @@ public FullScriptExtent(FileContext fileContext, BufferRange bufferRange) /// The zero based offset this extent ends at. public FullScriptExtent(FileContext fileContext, int startOffset, int endOffset) { + Validate.IsNotNull(nameof(fileContext), fileContext); + Validate.IsNotNull(nameof(startOffset), startOffset); + Validate.IsNotNull(nameof(endOffset), endOffset); + FileContext = fileContext; StartOffset = startOffset; EndOffset = endOffset; @@ -146,6 +154,9 @@ public FullScriptExtent(FileContext fileContext, int startOffset, int endOffset) #region Public Methods + /// + /// Return the text this extent refers to. + /// public override string ToString() { return Text; From 48abc14d7fd097548cc2df77e878623120f3e3be Mon Sep 17 00:00:00 2001 From: "Patrick M. Meinecke" Date: Wed, 7 Jun 2017 20:16:39 -0400 Subject: [PATCH 7/9] Help and manifest fixes - Added online link to markdown help. - Converted comment based help of Register-EditorCommand and UnregisterEditorCommand to markdown. - Fixed psedit not being exported. - Added cleanup of generated MAML file to build script. --- .gitignore | 3 + PowerShellEditorServices.build.ps1 | 1 + .../PowerShellEditorServices.Commands.psd1 | 6 +- .../PowerShellEditorServices.Commands.psm1 | 2 + ...rShellEditorServices.Commands.types.ps1xml | 28 ++++ .../Commands/Public/CmdletInterface.ps1 | 64 +------- module/docs/ConvertFrom-ScriptExtent.md | 2 +- module/docs/ConvertTo-ScriptExtent.md | 2 +- module/docs/Find-Ast.md | 2 +- module/docs/Get-Token.md | 2 +- module/docs/Import-EditorCommand.md | 2 +- module/docs/Join-ScriptExtent.md | 2 +- module/docs/Register-EditorCommand.md | 153 ++++++++++++++++++ module/docs/Set-ScriptExtent.md | 2 +- module/docs/Test-ScriptExtent.md | 2 +- module/docs/Unregister-EditorCommand.md | 59 +++++++ 16 files changed, 261 insertions(+), 71 deletions(-) create mode 100644 module/PowerShellEditorServices/Commands/PowerShellEditorServices.Commands.types.ps1xml create mode 100644 module/docs/Register-EditorCommand.md create mode 100644 module/docs/Unregister-EditorCommand.md diff --git a/.gitignore b/.gitignore index b6c409934..d209cf419 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,6 @@ PowerShellEditorServices.sln.ide/edb.chk PowerShellEditorServices.sln.ide/edbres00001.jrs PowerShellEditorServices.sln.ide/storage.ide *.jrs + +# Don't include PlatyPS generated MAML +module/PowerShellEditorServices/Commands/en-US/*-help.xml \ No newline at end of file diff --git a/PowerShellEditorServices.build.ps1 b/PowerShellEditorServices.build.ps1 index 4a7dad300..fea8c0340 100644 --- a/PowerShellEditorServices.build.ps1 +++ b/PowerShellEditorServices.build.ps1 @@ -100,6 +100,7 @@ task Clean { Remove-Item .\module\PowerShellEditorServices\bin -Recurse -Force -ErrorAction Ignore Get-ChildItem -Recurse src\*.nupkg | Remove-Item -Force -ErrorAction Ignore Get-ChildItem .\module\PowerShellEditorServices\*.zip | Remove-Item -Force -ErrorAction Ignore + Get-ChildItem .\module\PowerShellEditorServices\Commands\en-US\*-help.xml | Remove-Item -Force -ErrorAction Ignore } task GetProductVersion -Before PackageNuGet, PackageModule, UploadArtifacts { diff --git a/module/PowerShellEditorServices/Commands/PowerShellEditorServices.Commands.psd1 b/module/PowerShellEditorServices/Commands/PowerShellEditorServices.Commands.psd1 index 605c4980b..41fa124d9 100644 --- a/module/PowerShellEditorServices/Commands/PowerShellEditorServices.Commands.psd1 +++ b/module/PowerShellEditorServices/Commands/PowerShellEditorServices.Commands.psd1 @@ -57,7 +57,7 @@ Description = 'Provides internal commands for PowerShell Editor Services that on # ScriptsToProcess = @() # Type files (.ps1xml) to be loaded when importing this module -# TypesToProcess = @() +TypesToProcess = @('PowerShellEditorServices.Commands.types.ps1xml') # Format files (.ps1xml) to be loaded when importing this module # FormatsToProcess = @() @@ -74,7 +74,9 @@ FunctionsToExport = @('Register-EditorCommand', 'ConvertFrom-ScriptExtent', 'ConvertTo-ScriptExtent', 'Get-Token', - 'Join-ScriptExtent') + 'Join-ScriptExtent', + 'Test-ScriptExtent', + 'psedit') # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. CmdletsToExport = @() diff --git a/module/PowerShellEditorServices/Commands/PowerShellEditorServices.Commands.psm1 b/module/PowerShellEditorServices/Commands/PowerShellEditorServices.Commands.psm1 index c818523d1..2b64b7bc9 100644 --- a/module/PowerShellEditorServices/Commands/PowerShellEditorServices.Commands.psm1 +++ b/module/PowerShellEditorServices/Commands/PowerShellEditorServices.Commands.psm1 @@ -3,3 +3,5 @@ Import-LocalizedData -BindingVariable Strings -FileName Strings Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 | ForEach-Object { . $PSItem.FullName } + +Export-ModuleMember -Function *-* \ No newline at end of file diff --git a/module/PowerShellEditorServices/Commands/PowerShellEditorServices.Commands.types.ps1xml b/module/PowerShellEditorServices/Commands/PowerShellEditorServices.Commands.types.ps1xml new file mode 100644 index 000000000..eaebcb72a --- /dev/null +++ b/module/PowerShellEditorServices/Commands/PowerShellEditorServices.Commands.types.ps1xml @@ -0,0 +1,28 @@ + + + + Microsoft.PowerShell.EditorServices.FullScriptExtent + + + PSStandardMembers + + + DefaultDisplayPropertySet + + File + StartScriptPosition + EndScriptPosition + StartLineNumber + StartColumnNumber + EndLineNumber + EndColumnNumber + StartOffset + EndOffset + Text + + + + + + + \ No newline at end of file diff --git a/module/PowerShellEditorServices/Commands/Public/CmdletInterface.ps1 b/module/PowerShellEditorServices/Commands/Public/CmdletInterface.ps1 index 1ebe07b81..b3e883ab6 100644 --- a/module/PowerShellEditorServices/Commands/Public/CmdletInterface.ps1 +++ b/module/PowerShellEditorServices/Commands/Public/CmdletInterface.ps1 @@ -1,50 +1,5 @@ <# - .SYNOPSIS - Registers a command which can be executed in the host editor. - - .DESCRIPTION - Registers a command which can be executed in the host editor. This - command will be shown to the user either in a menu or command palette. - Upon invoking this command, either a function/cmdlet or ScriptBlock will - be executed depending on whether the -Function or -ScriptBlock parameter - was used when the command was registered. - - This command can be run multiple times for the same command so that its - details can be updated. However, re-registration of commands should only - be used for development purposes, not for dynamic behavior. - - .PARAMETER Name - Specifies a unique name which can be used to identify this command. - This name is not displayed to the user. - - .PARAMETER DisplayName - Specifies a display name which is displayed to the user. - - .PARAMETER Function - Specifies a function or cmdlet name which will be executed when the user - invokes this command. This function may take a parameter called $context - which will be populated with an EditorContext object containing information - about the host editor's state at the time the command was executed. - - .PARAMETER ScriptBlock - Specifies a ScriptBlock which will be executed when the user invokes this - command. This ScriptBlock may take a parameter called $context - which will be populated with an EditorContext object containing information - about the host editor's state at the time the command was executed. - - .PARAMETER SuppressOutput - If provided, causes the output of the editor command to be suppressed when - it is run. Errors that occur while running this command will still be - written to the host. - - .EXAMPLE - PS> Register-EditorCommand -Name "MyModule.MyFunctionCommand" -DisplayName "My function command" -Function Invoke-MyCommand -SuppressOutput - - .EXAMPLE - PS> Register-EditorCommand -Name "MyModule.MyScriptBlockCommand" -DisplayName "My ScriptBlock command" -ScriptBlock { Write-Output "Hello from my command!" } - - .LINK - Unregister-EditorCommand +.EXTERNALHELP ..\PowerShellEditorServices.Commands-help.xml #> function Register-EditorCommand { [CmdletBinding()] @@ -100,21 +55,7 @@ function Register-EditorCommand { } <# - .SYNOPSIS - Unregisters a command which has already been registered in the host editor. - - .DESCRIPTION - Unregisters a command which has already been registered in the host editor. - An error will be thrown if the specified Name is unknown. - - .PARAMETER Name - Specifies a unique name which identifies a command which has already been registered. - - .EXAMPLE - PS> Unregister-EditorCommand -Name "MyModule.MyFunctionCommand" - - .LINK - Register-EditorCommand +.EXTERNALHELP ..\PowerShellEditorServices.Commands-help.xml #> function Unregister-EditorCommand { [CmdletBinding()] @@ -138,3 +79,4 @@ function psedit { $psEditor.Workspace.OpenFile($_.FullName) } } +Export-ModuleMember -Function psedit diff --git a/module/docs/ConvertFrom-ScriptExtent.md b/module/docs/ConvertFrom-ScriptExtent.md index b4f8ed22d..69c84462a 100644 --- a/module/docs/ConvertFrom-ScriptExtent.md +++ b/module/docs/ConvertFrom-ScriptExtent.md @@ -1,6 +1,6 @@ --- external help file: PowerShellEditorServices.Commands-help.xml -online version: +online version: https://github.com/PowerShell/PowerShellEditorServices/tree/master/module/docs/ConvertFrom-ScriptExtent.md schema: 2.0.0 --- diff --git a/module/docs/ConvertTo-ScriptExtent.md b/module/docs/ConvertTo-ScriptExtent.md index 155cd2d97..ed0a3e95b 100644 --- a/module/docs/ConvertTo-ScriptExtent.md +++ b/module/docs/ConvertTo-ScriptExtent.md @@ -1,6 +1,6 @@ --- external help file: PowerShellEditorServices.Commands-help.xml -online version: +online version: https://github.com/PowerShell/PowerShellEditorServices/tree/master/module/docs/ConvertTo-ScriptExtent.md schema: 2.0.0 --- diff --git a/module/docs/Find-Ast.md b/module/docs/Find-Ast.md index 90f78a531..9752e143e 100644 --- a/module/docs/Find-Ast.md +++ b/module/docs/Find-Ast.md @@ -1,6 +1,6 @@ --- external help file: PowerShellEditorServices.Commands-help.xml -online version: +online version: https://github.com/PowerShell/PowerShellEditorServices/tree/master/module/docs/Find-Ast.md schema: 2.0.0 --- diff --git a/module/docs/Get-Token.md b/module/docs/Get-Token.md index 182653883..71744b5f2 100644 --- a/module/docs/Get-Token.md +++ b/module/docs/Get-Token.md @@ -1,6 +1,6 @@ --- external help file: PowerShellEditorServices.Commands-help.xml -online version: +online version: https://github.com/PowerShell/PowerShellEditorServices/tree/master/module/docs/Get-Token.md schema: 2.0.0 --- diff --git a/module/docs/Import-EditorCommand.md b/module/docs/Import-EditorCommand.md index 5ad184317..fef2cbea2 100644 --- a/module/docs/Import-EditorCommand.md +++ b/module/docs/Import-EditorCommand.md @@ -1,6 +1,6 @@ --- external help file: PowerShellEditorServices.Commands-help.xml -online version: +online version: https://github.com/PowerShell/PowerShellEditorServices/tree/master/module/docs/Import-EditorCommand.md schema: 2.0.0 --- diff --git a/module/docs/Join-ScriptExtent.md b/module/docs/Join-ScriptExtent.md index e5bdd91c0..37fa97f8c 100644 --- a/module/docs/Join-ScriptExtent.md +++ b/module/docs/Join-ScriptExtent.md @@ -1,6 +1,6 @@ --- external help file: PowerShellEditorServices.Commands-help.xml -online version: +online version: https://github.com/PowerShell/PowerShellEditorServices/tree/master/module/docs/Join-ScriptExtent.md schema: 2.0.0 --- diff --git a/module/docs/Register-EditorCommand.md b/module/docs/Register-EditorCommand.md new file mode 100644 index 000000000..6e8ab61a9 --- /dev/null +++ b/module/docs/Register-EditorCommand.md @@ -0,0 +1,153 @@ +--- +external help file: PowerShellEditorServices.Commands-help.xml +online version: https://github.com/PowerShell/PowerShellEditorServices/tree/master/module/docs/Register-EditorCommand.md +schema: 2.0.0 +--- + +# Register-EditorCommand + +## SYNOPSIS + +Registers a command which can be executed in the host editor. + +## SYNTAX + +### Function + +```powershell +Register-EditorCommand -Name -DisplayName -Function [-SuppressOutput] +``` + +### ScriptBlock + +```powershell +Register-EditorCommand -Name -DisplayName -ScriptBlock [-SuppressOutput] +``` + +## DESCRIPTION + +Registers a command which can be executed in the host editor. This +command will be shown to the user either in a menu or command palette. +Upon invoking this command, either a function/cmdlet or ScriptBlock will +be executed depending on whether the -Function or -ScriptBlock parameter +was used when the command was registered. + +This command can be run multiple times for the same command so that its +details can be updated. However, re-registration of commands should only +be used for development purposes, not for dynamic behavior. + +## EXAMPLES + +### -------------------------- EXAMPLE 1 -------------------------- + +```powershell +Register-EditorCommand -Name "MyModule.MyFunctionCommand" -DisplayName "My function command" -Function Invoke-MyCommand -SuppressOutput +``` + +### -------------------------- EXAMPLE 2 -------------------------- + +```powershell +Register-EditorCommand -Name "MyModule.MyScriptBlockCommand" -DisplayName "My ScriptBlock command" -ScriptBlock { Write-Output "Hello from my command!" } +``` + +## PARAMETERS + +### -Name + +Specifies a unique name which can be used to identify this command. +This name is not displayed to the user. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -DisplayName + +Specifies a display name which is displayed to the user. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Function + +Specifies a function or cmdlet name which will be executed when the user +invokes this command. This function may take a parameter called $context +which will be populated with an EditorContext object containing information +about the host editor's state at the time the command was executed. + +```yaml +Type: String +Parameter Sets: Function +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ScriptBlock + +Specifies a ScriptBlock which will be executed when the user invokes this +command. This ScriptBlock may take a parameter called $context +which will be populated with an EditorContext object containing information +about the host editor's state at the time the command was executed. + +```yaml +Type: ScriptBlock +Parameter Sets: ScriptBlock +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -SuppressOutput + +If provided, causes the output of the editor command to be suppressed when +it is run. Errors that occur while running this command will still be +written to the host. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +## INPUTS + +## OUTPUTS + +## NOTES + +## RELATED LINKS + +[Unregister-EditorCommand](Unregister-EditorCommand.md) + diff --git a/module/docs/Set-ScriptExtent.md b/module/docs/Set-ScriptExtent.md index f33d0e809..a65181e81 100644 --- a/module/docs/Set-ScriptExtent.md +++ b/module/docs/Set-ScriptExtent.md @@ -1,6 +1,6 @@ --- external help file: PowerShellEditorServices.Commands-help.xml -online version: +online version: https://github.com/PowerShell/PowerShellEditorServices/tree/master/module/docs/Set-ScriptExtent.md schema: 2.0.0 --- diff --git a/module/docs/Test-ScriptExtent.md b/module/docs/Test-ScriptExtent.md index 254d1a573..39177c7f8 100644 --- a/module/docs/Test-ScriptExtent.md +++ b/module/docs/Test-ScriptExtent.md @@ -1,6 +1,6 @@ --- external help file: PowerShellEditorServices.Commands-help.xml -online version: +online version: https://github.com/PowerShell/PowerShellEditorServices/tree/master/module/docs/Test-ScriptExtent.md schema: 2.0.0 --- diff --git a/module/docs/Unregister-EditorCommand.md b/module/docs/Unregister-EditorCommand.md new file mode 100644 index 000000000..0c9c23c95 --- /dev/null +++ b/module/docs/Unregister-EditorCommand.md @@ -0,0 +1,59 @@ +--- +external help file: PowerShellEditorServices.Commands-help.xml +online version: https://github.com/PowerShell/PowerShellEditorServices/tree/master/module/docs/Unregister-EditorCommand.md +schema: 2.0.0 +--- + +# Unregister-EditorCommand + +## SYNOPSIS + +Unregisters a command which has already been registered in the host editor. + +## SYNTAX + +```powershell +Unregister-EditorCommand [-Name] +``` + +## DESCRIPTION + +Unregisters a command which has already been registered in the host editor. +An error will be thrown if the specified Name is unknown. + +## EXAMPLES + +### -------------------------- EXAMPLE 1 -------------------------- + +```powershell +Unregister-EditorCommand -Name "MyModule.MyFunctionCommand" +``` + +## PARAMETERS + +### -Name + +Specifies a unique name which identifies a command which has already been registered. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +## INPUTS + +## OUTPUTS + +## NOTES + +## RELATED LINKS + +[Register-EditorCommand](Register-EditorCommand.md) + From e1e629ed72a312c3a2ed80e5bd724349f92ddcdc Mon Sep 17 00:00:00 2001 From: "Patrick M. Meinecke" Date: Wed, 7 Jun 2017 20:39:44 -0400 Subject: [PATCH 8/9] Add platyPS to appveyor --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index 5a8981880..ed4db7048 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -18,6 +18,7 @@ install: Import-PackageProvider NuGet -Force | Out-Null Set-PSRepository -Name PSGallery -InstallationPolicy Trusted | Out-Null Install-Module InvokeBuild -RequiredVersion 3.2.1 -Scope CurrentUser -Force | Out-Null + Install-Module platyPS -RequiredVersion 0.7.6 -Scope CurrentUser -Force | Out-Null build_script: - ps: Invoke-Build -Configuration Release From cadc4bd348d46e1f604e3109f6bbb7fcbab2a865 Mon Sep 17 00:00:00 2001 From: "Patrick M. Meinecke" Date: Wed, 7 Jun 2017 20:58:09 -0400 Subject: [PATCH 9/9] Add platyPS to travis --- scripts/travis.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/travis.ps1 b/scripts/travis.ps1 index 1076c195f..676ef1dbc 100644 --- a/scripts/travis.ps1 +++ b/scripts/travis.ps1 @@ -2,6 +2,7 @@ # Install InvokeBuild Install-Module InvokeBuild -Scope CurrentUser -Force +Install-Module PlatyPS -Scope CurrentUser -Force # Build the code and perform tests