Skip to content

Commit de5542f

Browse files
SeeminglySciencedaviwil
authored andcommitted
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
1 parent 2f37941 commit de5542f

File tree

8 files changed

+683
-0
lines changed

8 files changed

+683
-0
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
function ConvertFrom-ScriptExtent {
2+
<#
3+
.EXTERNALHELP ..\PowerShellEditorServices.Commands-help.xml
4+
#>
5+
[CmdletBinding()]
6+
[OutputType([Microsoft.PowerShell.EditorServices.BufferRange], ParameterSetName='BufferRange')]
7+
[OutputType([Microsoft.PowerShell.EditorServices.BufferPosition], ParameterSetName='BufferPosition')]
8+
param(
9+
[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
10+
[ValidateNotNullOrEmpty()]
11+
[System.Management.Automation.Language.IScriptExtent[]]
12+
$Extent,
13+
14+
[Parameter(ParameterSetName='BufferRange')]
15+
[switch]
16+
$BufferRange,
17+
18+
[Parameter(ParameterSetName='BufferPosition')]
19+
[switch]
20+
$BufferPosition,
21+
22+
[Parameter(ParameterSetName='BufferPosition')]
23+
[switch]
24+
$Start,
25+
26+
[Parameter(ParameterSetName='BufferPosition')]
27+
[switch]
28+
$End
29+
)
30+
process {
31+
foreach ($aExtent in $Extent) {
32+
switch ($PSCmdlet.ParameterSetName) {
33+
BufferRange {
34+
# yield
35+
New-Object Microsoft.PowerShell.EditorServices.BufferRange @(
36+
$aExtent.StartLineNumber,
37+
$aExtent.StartColumnNumber,
38+
$aExtent.EndLineNumber,
39+
$aExtent.EndColumnNumber)
40+
}
41+
BufferPosition {
42+
if ($End) {
43+
$line = $aExtent.EndLineNumber
44+
$column = $aExtent.EndLineNumber
45+
} else {
46+
$line = $aExtent.StartLineNumber
47+
$column = $aExtent.StartLineNumber
48+
}
49+
# yield
50+
New-Object Microsoft.PowerShell.EditorServices.BufferPosition @(
51+
$line,
52+
$column)
53+
}
54+
}
55+
}
56+
}
57+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
function ConvertTo-ScriptExtent {
2+
<#
3+
.EXTERNALHELP ..\PowerShellEditorServices.Commands-help.xml
4+
#>
5+
[CmdletBinding()]
6+
[OutputType([System.Management.Automation.Language.IScriptExtent])]
7+
param(
8+
[Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByPosition')]
9+
[Alias('StartLine', 'Line')]
10+
[int]
11+
$StartLineNumber,
12+
13+
[Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByPosition')]
14+
[Alias('StartColumn', 'Column')]
15+
[int]
16+
$StartColumnNumber,
17+
18+
[Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByPosition')]
19+
[Alias('EndLine')]
20+
[int]
21+
$EndLineNumber,
22+
23+
[Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByPosition')]
24+
[Alias('EndColumn')]
25+
[int]
26+
$EndColumnNumber,
27+
28+
[Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByOffset')]
29+
[Alias('StartOffset', 'Offset')]
30+
[int]
31+
$StartOffsetNumber,
32+
33+
[Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByOffset')]
34+
[Alias('EndOffset')]
35+
[int]
36+
$EndOffsetNumber,
37+
38+
[Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByPosition')]
39+
[Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByOffset')]
40+
[Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByBuffer')]
41+
[Alias('File', 'FileName')]
42+
[string]
43+
$FilePath = $psEditor.GetEditorContext().CurrentFile.Path,
44+
45+
[Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByBuffer')]
46+
[Alias('Start')]
47+
[Microsoft.PowerShell.EditorServices.BufferPosition]
48+
$StartBuffer,
49+
50+
[Parameter(ValueFromPipelineByPropertyName, ParameterSetName='ByBuffer')]
51+
[Alias('End')]
52+
[Microsoft.PowerShell.EditorServices.BufferPosition]
53+
$EndBuffer,
54+
55+
[Parameter(ValueFromPipeline)]
56+
[System.Management.Automation.Language.IScriptExtent]
57+
$Extent
58+
)
59+
begin {
60+
$fileContext = $psEditor.GetEditorContext().CurrentFile
61+
$emptyExtent = New-Object Microsoft.PowerShell.EditorServices.FullScriptExtent @(
62+
<# filecontext: #> $fileContext,
63+
<# startOffset: #> 0,
64+
<# endOffset: #> 0)
65+
}
66+
process {
67+
# Already a InternalScriptExtent, FullScriptExtent or is empty.
68+
$returnAsIs = $Extent -and
69+
(0 -ne $Extent.StartOffset -or
70+
0 -ne $Extent.EndOffset -or
71+
$Extent -eq $emptyExtent)
72+
73+
if ($returnAsIs) { return $Extent }
74+
75+
if ($StartOffsetNumber) {
76+
$startOffset = $StartOffsetNumber
77+
$endOffset = $EndOffsetNumber
78+
79+
# Allow creating a single position extent with just the offset parameter.
80+
if (-not $EndOffsetNumber) {
81+
$endOffset = $startOffset
82+
}
83+
return New-Object Microsoft.PowerShell.EditorServices.FullScriptExtent @(
84+
$fileContext,
85+
$startOffset,
86+
$endOffset)
87+
}
88+
if (-not $StartBuffer) {
89+
if (-not $StartColumnNumber) { $StartColumnNumber = 1 }
90+
if (-not $StartLineNumber) { $StartLineNumber = 1 }
91+
$StartBuffer = New-Object Microsoft.PowerShell.EditorServices.BufferPosition @(
92+
$StartColumnNumber,
93+
$StartLineNumber)
94+
95+
if ($EndLineNumber -and $EndColumnNumber) {
96+
$EndBuffer = New-Object Microsoft.PowerShell.EditorServices.BufferPosition @(
97+
$EndLineNumber,
98+
$EndColumnNumber)
99+
}
100+
}
101+
if (-not $EndBuffer) { $EndBuffer = $StartBuffer }
102+
103+
$bufferRange = New-Object Microsoft.PowerShell.EditorServices.BufferRange @(
104+
$StartBuffer,
105+
$EndBuffer)
106+
107+
return New-Object Microsoft.PowerShell.EditorServices.FullScriptExtent @(
108+
$fileContext,
109+
$bufferRange)
110+
}
111+
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
function Find-Ast {
2+
<#
3+
.EXTERNALHELP ..\PowerShellEditorServices.Commands-help.xml
4+
#>
5+
[CmdletBinding(PositionalBinding=$false, DefaultParameterSetName='FilterScript')]
6+
param(
7+
[Parameter(Position=0, ParameterSetName='FilterScript')]
8+
[ValidateNotNullOrEmpty()]
9+
[scriptblock]
10+
$FilterScript = { $true },
11+
12+
[Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName='FilterScript')]
13+
[ValidateNotNullOrEmpty()]
14+
[System.Management.Automation.Language.Ast]
15+
$Ast,
16+
17+
[Parameter(ParameterSetName='FilterScript')]
18+
[switch]
19+
$Before,
20+
21+
[Parameter(ParameterSetName='FilterScript')]
22+
[switch]
23+
$Family,
24+
25+
[Parameter(ParameterSetName='FilterScript')]
26+
[Alias('Closest', 'F')]
27+
[switch]
28+
$First,
29+
30+
[Parameter(ParameterSetName='FilterScript')]
31+
[Alias('Furthest')]
32+
[switch]
33+
$Last,
34+
35+
[Parameter(ParameterSetName='FilterScript')]
36+
[Alias('Parent')]
37+
[switch]
38+
$Ancestor,
39+
40+
[Parameter(ParameterSetName='FilterScript')]
41+
[switch]
42+
$IncludeStartingAst,
43+
44+
[Parameter(ParameterSetName='AtCursor')]
45+
[switch]
46+
$AtCursor
47+
)
48+
begin {
49+
# InvokeWithContext method is PS4+, but it's significantly faster for large files.
50+
if ($PSVersionTable.PSVersion.Major -ge 4) {
51+
52+
$variableType = [System.Management.Automation.PSVariable]
53+
function InvokeWithContext {
54+
param([scriptblock]$Filter, [System.Management.Automation.Language.Ast]$DollarUnder)
55+
56+
return $Filter.InvokeWithContext(
57+
<# functionsToDefine: #> $null,
58+
<# variablesToDefine: #> [Activator]::CreateInstance($variableType, @('_', $DollarUnder)),
59+
<# args: #> $aAst)
60+
}
61+
} else {
62+
$FilterScript = [scriptblock]::Create($FilterScript.ToString())
63+
function InvokeWithContext {
64+
param([scriptblock]$Filter, [System.Management.Automation.Language.Ast]$DollarUnder)
65+
66+
return $DollarUnder | & { process { $Filter.InvokeReturnAsIs($DollarUnder) } }
67+
}
68+
}
69+
# Get all children or ancestors.
70+
function GetAllFamily {
71+
param($Start)
72+
73+
if ($Before.IsPresent) {
74+
$parent = $Start
75+
for ($parent; $parent = $parent.Parent) { $parent }
76+
return
77+
}
78+
return $Start.FindAll({ $true }, $true)
79+
}
80+
# Get all asts regardless of structure, in either direction from the starting ast.
81+
function GetAllAsts {
82+
param($Start)
83+
84+
$predicate = [Func[System.Management.Automation.Language.Ast,bool]]{
85+
$args[0] -ne $Ast
86+
}
87+
88+
$topParent = Find-Ast -Ast $Start -Ancestor -Last -IncludeStartingAst
89+
if (-not $topParent) { $topParent = $Start }
90+
91+
if ($Before.IsPresent) {
92+
# Need to store so we can reverse the collection.
93+
$result = [Linq.Enumerable]::TakeWhile(
94+
$topParent.FindAll({ $true }, $true),
95+
$predicate)
96+
97+
[array]::Reverse($result)
98+
return $result
99+
}
100+
return [Linq.Enumerable]::SkipWhile(
101+
$topParent.FindAll({ $true }, $true),
102+
$predicate)
103+
}
104+
}
105+
process {
106+
if ($Ancestor.IsPresent) {
107+
$Family = $Before = $true
108+
}
109+
$context = $psEditor.GetEditorContext()
110+
111+
if (-not $Ast -and $context) {
112+
$Ast = $context.CurrentFile.Ast
113+
}
114+
switch ($PSCmdlet.ParameterSetName) {
115+
AtCursor {
116+
$cursorLine = $context.CursorPosition.Line - 1
117+
$cursorColumn = $context.CursorPosition.Column - 1
118+
$cursorOffset = $Ast.Extent.Text |
119+
Select-String "(.*\r?\n){$cursorLine}.{$cursorColumn}" |
120+
ForEach-Object { $PSItem.Matches.Value.Length }
121+
122+
# yield
123+
Find-Ast -Last {
124+
$cursorOffset -ge $PSItem.Extent.StartOffset -and
125+
$cursorOffset -le $PSItem.Extent.EndOffset
126+
}
127+
}
128+
FilterScript {
129+
if (-not $Ast) { return }
130+
131+
# Check if we're trying to get the top level ancestor when we're already there.
132+
if ($Before.IsPresent -and
133+
$Family.IsPresent -and
134+
$Last.IsPresent -and -not
135+
$Ast.Parent -and
136+
$Ast -is [System.Management.Automation.Language.ScriptBlockAst])
137+
{ return $Ast }
138+
139+
if ($Family.IsPresent) {
140+
$asts = GetAllFamily $Ast
141+
} else {
142+
$asts = GetAllAsts $Ast
143+
}
144+
# Check the first ast to see if it's our starting ast, unless
145+
$checkFirstAst = -not $IncludeStartingAst
146+
foreach ($aAst in $asts) {
147+
if ($checkFirstAst) {
148+
if ($aAst -eq $Ast) {
149+
$checkFirstAst = $false
150+
continue
151+
}
152+
}
153+
$shouldReturn = InvokeWithContext $FilterScript $aAst
154+
155+
if (-not $shouldReturn) { continue }
156+
157+
# Return first, last, both, or all depending on the combination of switches.
158+
if (-not $Last.IsPresent) {
159+
$aAst # yield
160+
if ($First.IsPresent) { break }
161+
} else {
162+
$lastMatch = $aAst
163+
if ($First.IsPresent) {
164+
$aAst # yield
165+
$First = $false
166+
}
167+
}
168+
}
169+
# yield
170+
if ($Last.IsPresent) { return $lastMatch }
171+
}
172+
}
173+
}
174+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
function Get-Token {
2+
<#
3+
.EXTERNALHELP ..\PowerShellEditorServices.Commands-help.xml
4+
#>
5+
[CmdletBinding()]
6+
[OutputType([System.Management.Automation.Language.Token])]
7+
[System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseOutputTypeCorrectly', '', Justification='Issue #676')]
8+
param(
9+
[Parameter(Position=0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
10+
[ValidateNotNullOrEmpty()]
11+
[System.Management.Automation.Language.IScriptExtent]
12+
$Extent
13+
)
14+
process {
15+
if (-not $Extent) {
16+
if (-not $PSCmdlet.MyInvocation.ExpectingInput) {
17+
# yield
18+
$psEditor.GetEditorContext().CurrentFile.Tokens
19+
}
20+
return
21+
}
22+
23+
$tokens = $psEditor.GetEditorContext().CurrentFile.Tokens
24+
$predicate = [Func[System.Management.Automation.Language.Token, bool]]{
25+
param($Token)
26+
27+
($Token.Extent.StartOffset -ge $Extent.StartOffset -and
28+
$Token.Extent.EndOffset -le $Extent.EndOffset)
29+
}
30+
if ($tokens){
31+
$result = [Linq.Enumerable]::Where($tokens, $predicate)
32+
}
33+
# yield
34+
$result
35+
}
36+
}

0 commit comments

Comments
 (0)