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 30e65dea0..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 {
@@ -196,6 +197,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/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
diff --git a/module/PowerShellEditorServices/Commands/PowerShellEditorServices.Commands.psd1 b/module/PowerShellEditorServices/Commands/PowerShellEditorServices.Commands.psd1
index ba6bd62a9..41fa124d9 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'
@@ -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 = @()
@@ -66,7 +66,17 @@ 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',
+ '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 62ac9af39..2b64b7bc9 100644
--- a/module/PowerShellEditorServices/Commands/PowerShellEditorServices.Commands.psm1
+++ b/module/PowerShellEditorServices/Commands/PowerShellEditorServices.Commands.psm1
@@ -1,3 +1,7 @@
+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
+}
+
+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/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..12c9c9495
--- /dev/null
+++ b/module/PowerShellEditorServices/Commands/Public/ConvertTo-ScriptExtent.ps1
@@ -0,0 +1,114 @@
+function ConvertTo-ScriptExtent {
+ <#
+ .EXTERNALHELP ..\PowerShellEditorServices.Commands-help.xml
+ #>
+ [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]
+ $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='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(Mandatory,
+ ValueFromPipeline,
+ ValueFromPipelineByPropertyName,
+ ParameterSetName='ByExtent')]
+ [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..b4957d9e0
--- /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)[2]
+
+ if ($caller.InvocationInfo.MyCommand.ScriptBlock.Module.Name -eq $ModuleToSearch) {
+ $moduleInfo = $caller.InvocationInfo.MyCommand.ScriptBlock.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
+ }
+ }
+}
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.
+'@
diff --git a/module/docs/ConvertFrom-ScriptExtent.md b/module/docs/ConvertFrom-ScriptExtent.md
new file mode 100644
index 000000000..69c84462a
--- /dev/null
+++ b/module/docs/ConvertFrom-ScriptExtent.md
@@ -0,0 +1,146 @@
+---
+external help file: PowerShellEditorServices.Commands-help.xml
+online version: https://github.com/PowerShell/PowerShellEditorServices/tree/master/module/docs/ConvertFrom-ScriptExtent.md
+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..ed0a3e95b
--- /dev/null
+++ b/module/docs/ConvertTo-ScriptExtent.md
@@ -0,0 +1,251 @@
+---
+external help file: PowerShellEditorServices.Commands-help.xml
+online version: https://github.com/PowerShell/PowerShellEditorServices/tree/master/module/docs/ConvertTo-ScriptExtent.md
+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..9752e143e
--- /dev/null
+++ b/module/docs/Find-Ast.md
@@ -0,0 +1,249 @@
+---
+external help file: PowerShellEditorServices.Commands-help.xml
+online version: https://github.com/PowerShell/PowerShellEditorServices/tree/master/module/docs/Find-Ast.md
+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..71744b5f2
--- /dev/null
+++ b/module/docs/Get-Token.md
@@ -0,0 +1,72 @@
+---
+external help file: PowerShellEditorServices.Commands-help.xml
+online version: https://github.com/PowerShell/PowerShellEditorServices/tree/master/module/docs/Get-Token.md
+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..fef2cbea2
--- /dev/null
+++ b/module/docs/Import-EditorCommand.md
@@ -0,0 +1,137 @@
+---
+external help file: PowerShellEditorServices.Commands-help.xml
+online version: https://github.com/PowerShell/PowerShellEditorServices/tree/master/module/docs/Import-EditorCommand.md
+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..37fa97f8c
--- /dev/null
+++ b/module/docs/Join-ScriptExtent.md
@@ -0,0 +1,72 @@
+---
+external help file: PowerShellEditorServices.Commands-help.xml
+online version: https://github.com/PowerShell/PowerShellEditorServices/tree/master/module/docs/Join-ScriptExtent.md
+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/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
new file mode 100644
index 000000000..a65181e81
--- /dev/null
+++ b/module/docs/Set-ScriptExtent.md
@@ -0,0 +1,141 @@
+---
+external help file: PowerShellEditorServices.Commands-help.xml
+online version: https://github.com/PowerShell/PowerShellEditorServices/tree/master/module/docs/Set-ScriptExtent.md
+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..39177c7f8
--- /dev/null
+++ b/module/docs/Test-ScriptExtent.md
@@ -0,0 +1,140 @@
+---
+external help file: PowerShellEditorServices.Commands-help.xml
+online version: https://github.com/PowerShell/PowerShellEditorServices/tree/master/module/docs/Test-ScriptExtent.md
+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
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)
+
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
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
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..3db5b9f5d
--- /dev/null
+++ b/src/PowerShellEditorServices/Language/FullScriptExtent.cs
@@ -0,0 +1,179 @@
+using System.Management.Automation.Language;
+using Microsoft.PowerShell.EditorServices.Extensions;
+using Microsoft.PowerShell.EditorServices.Utility;
+
+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)
+ {
+ Validate.IsNotNull(nameof(fileContext), fileContext);
+ Validate.IsNotNull(nameof(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)
+ {
+ Validate.IsNotNull(nameof(fileContext), fileContext);
+ Validate.IsNotNull(nameof(startOffset), startOffset);
+ Validate.IsNotNull(nameof(endOffset), endOffset);
+
+ FileContext = fileContext;
+ StartOffset = startOffset;
+ EndOffset = endOffset;
+ BufferRange = fileContext.scriptFile.GetRangeBetweenOffsets(startOffset, endOffset);
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Return the text this extent refers to.
+ ///
+ 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