Skip to content

Commit af8558d

Browse files
Add ConvertTo-FunctionDefinition Editor Command (#20)
* Add utility class SpecialVariables This class provides a uniform way to check if a variable is created by the PowerShell engine consistently within the module. * Refactor type inference Cleaned up the code in GetInferredType and GetType internal functions. GetInferredType now takes a few more cheaper routes first, and no longer uses the scope enumerator. * Fix error preference handling Error preferences were getting lost due to the main error handler invoking from different contexts. * Add internal functions for handling indents - AddIndent - Adds indentation to a string - NormalizeIndent - Removes extra indentation from a string * Fix AddIndent and add reference Utility in psm1 Add a check in AddIndent to ignore negative indent counts. * Add ConvertTo-FunctionDefinition function - Add the editor command ConvertTo-FunctionDefinition that generates function definitions from selected text - Remove strings that are no longer used from the localization file - Update manifest version number and release notes for the next release - Update README and module documentation with information for the new editor command * Various additional escaping - Stop indenting ending tag of here-strings - Check if variables are parsable and add logic for escaping them - When exporting from a selection inside a class method to a begin block don't count the FunctionMemberAst as a FunctionDefinitionAst - Added better detection of begin block brace position * Account for using statements Check for using statements when creating a begin block for an exported function in a functionless script file. * Fallback to workspace and/or PWD when untitled
1 parent 6aae020 commit af8558d

13 files changed

+1006
-89
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,9 @@ All commands unless otherwise noted target the closest relevant expression.
8080
|Function Name|Editor Command Name|Description|
8181
|---|---|---|
8282
|Add-CommandToManifest|Add Closest Function To Manifest|Add a function to the manifest fields ExportedFunctions and FileList|
83-
|Add-ModuleQualifcation|Add Module Name to Closest Command|Infers the origin module of the command closest to the cursor and prepends the module name|
83+
|Add-ModuleQualification|Add Module Name to Closest Command|Infers the origin module of the command closest to the cursor and prepends the module name|
8484
|Add-PinvokeMethod|Insert Pinvoke Method Definition|Searches the pinvoke.net web service for a matching function and creates a Add-Type expression with the signature|
85+
|ConvertTo-FunctionDefinition|Create New Function From Selection|Generate a function definition expression from current selection|
8586
|ConvertTo-LocalizationString|Add Closest String to Localization File|Replaces a string expression with a variable that references a localization file, and adds the string to that file|
8687
|ConvertTo-MarkdownHelp|Generate Markdown from Closest Function|Generate markdown using PlatyPS, add the markdown file to your docs folder, and replace the comment help with an external help file comment.|
8788
|ConvertTo-SplatExpression|Convert Command to Splat Expression|Create a splat hashtable variable from named parameters in a command and replace the named parameters with a a splat expression.|
@@ -97,4 +98,3 @@ All commands unless otherwise noted target the closest relevant expression.
9798

9899
We would love to incorporate community contributions into this project. If you would like to
99100
contribute code, documentation, tests, or bug reports, please read our [Contribution Guide](https://github.com/SeeminglyScience/EditorServicesCommandSuite/tree/master/docs/CONTRIBUTING.md) to learn more.
100-
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
---
2+
external help file: EditorServicesCommandSuite-help.xml
3+
online version: https://github.com/SeeminglyScience/EditorServicesCommandSuite/blob/master/docs/en-US/ConvertTo-FunctionDefinition.md
4+
schema: 2.0.0
5+
---
6+
7+
# ConvertTo-FunctionDefinition
8+
9+
## SYNOPSIS
10+
11+
Create a new function from a selection or specified script extent object.
12+
13+
## SYNTAX
14+
15+
### __AllParameterSets (Default)
16+
17+
```powershell
18+
ConvertTo-FunctionDefinition [-Extent <IScriptExtent>] [-FunctionName <String>]
19+
```
20+
21+
### ExternalFile
22+
23+
```powershell
24+
ConvertTo-FunctionDefinition [-Extent <IScriptExtent>] [-FunctionName <String>] [-DestinationPath <String>]
25+
```
26+
27+
### BeginBlock
28+
29+
```powershell
30+
ConvertTo-FunctionDefinition [-Extent <IScriptExtent>] [-FunctionName <String>] [-BeginBlock]
31+
```
32+
33+
### Inline
34+
35+
```powershell
36+
ConvertTo-FunctionDefinition [-Extent <IScriptExtent>] [-FunctionName <String>] [-Inline]
37+
```
38+
39+
## DESCRIPTION
40+
41+
The ConvertTo-FunctionDefintion function takes a section of the current file and creates a function
42+
definition from it. The generated function includes a parameter block with parameters for variables
43+
that are not defined in the selection. In the place of the selected text will be the invocation of
44+
the generated command including parameters.
45+
46+
## EXAMPLES
47+
48+
### -------------------------- EXAMPLE 1 --------------------------
49+
50+
```powershell
51+
# Open a new untitled file
52+
$psEditor.Workspace.NewFile()
53+
54+
# Insert some text into the file
55+
$psEditor.GetEditorContext().CurrentFile.InsertText('
56+
$myVar = "testing"
57+
Get-ChildItem $myVar
58+
')
59+
60+
# Select the Get-ChildItem line
61+
$psEditor.GetEditorContext().SetSelection(3, 1, 4, 1)
62+
63+
# Convert it to a function
64+
ConvertTo-FunctionDefinition -FunctionName GetMyDirectory -Inline
65+
66+
# Show the new contents of the file
67+
$psEditor.GetEditorContext.CurrentFile.GetText()
68+
69+
# $myVar = "testing"
70+
# function GetMyDirectory {
71+
# param([string] $MyVar)
72+
# end {
73+
# Get-ChildItem $MyVar
74+
# }
75+
# }
76+
#
77+
# GetMyDirectory -MyVar $myVar
78+
79+
```
80+
81+
Creates a new untitled file in the editor, inserts demo text, and then converts a line to a inline function.
82+
83+
## PARAMETERS
84+
85+
### -Extent
86+
87+
The ScriptExtent to convert to a function. If not specified, the currently selected text in the editor will be used.
88+
89+
```yaml
90+
Type: IScriptExtent
91+
Parameter Sets: (All)
92+
Aliases:
93+
94+
Required: False
95+
Position: Named
96+
Default value: None
97+
Accept pipeline input: False
98+
Accept wildcard characters: False
99+
```
100+
101+
### -FunctionName
102+
103+
Specifies the name to give the generated function. If not specified, a input prompt will be displayed
104+
in the editor.
105+
106+
```yaml
107+
Type: String
108+
Parameter Sets: (All)
109+
Aliases:
110+
111+
Required: False
112+
Position: Named
113+
Default value: None
114+
Accept pipeline input: False
115+
Accept wildcard characters: False
116+
```
117+
118+
### -DestinationPath
119+
120+
Specifies a path relative to the file open in the editor to save the function to. You can specify an
121+
existing or new file. If the file extension is omitted, the path is assumed to be a directory and a
122+
file name is assumed to be the function name.
123+
124+
```yaml
125+
Type: String
126+
Parameter Sets: ExternalFile
127+
Aliases:
128+
129+
Required: False
130+
Position: Named
131+
Default value: None
132+
Accept pipeline input: False
133+
Accept wildcard characters: False
134+
```
135+
136+
### -BeginBlock
137+
138+
If specified, the function will be saved to the Begin block of either the closest parent function
139+
definition, or of the root script block if no function definitions exist.
140+
141+
If there is no Begin block available, one will be created. If a begin block must be created and no
142+
named blocks exist yet, a separate End block will be created from the existing unnamed block.
143+
144+
```yaml
145+
Type: SwitchParameter
146+
Parameter Sets: BeginBlock
147+
Aliases:
148+
149+
Required: False
150+
Position: Named
151+
Default value: False
152+
Accept pipeline input: False
153+
Accept wildcard characters: False
154+
```
155+
156+
### -Inline
157+
158+
If specified, the function will be saved directly above the selection.
159+
160+
```yaml
161+
Type: SwitchParameter
162+
Parameter Sets: Inline
163+
Aliases:
164+
165+
Required: False
166+
Position: Named
167+
Default value: False
168+
Accept pipeline input: False
169+
Accept wildcard characters: False
170+
```
171+
172+
## INPUTS
173+
174+
### None
175+
176+
This function does not accept input from the pipeline.
177+
178+
## OUTPUTS
179+
180+
### None
181+
182+
This function does not return output to the pipeline.
183+
184+
## NOTES
185+
186+
## RELATED LINKS

docs/en-US/EditorServicesCommandSuite.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ The Add-ModuleQualification function retrieves the module a command belongs to a
2626

2727
The Add-PinvokeMethod function searches pinvoke.net for the requested function name and provides a list of matches to select from. Once selected, this function will get the signature and create a expression that uses the Add-Type cmdlet to create a type with the PInvoke method.
2828

29+
### [ConvertTo-FunctionDefinition](ConvertTo-FunctionDefinition.md)
30+
31+
The ConvertTo-FunctionDefintion function takes a section of the current file and creates a function
32+
definition from it. The generated function includes a parameter block with parameters for variables
33+
that are not defined in the selection. In the place of the selected text will be the invocation of
34+
the generated command including parameters.
35+
2936
### [ConvertTo-LocalizationString](ConvertTo-LocalizationString.md)
3037

3138
The ConvertTo-LocalizationString function will take the closest string expression and replace it with a variable that references a localization resource file.

module/Classes/Utility.ps1

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using namespace System.Management.Automation
2+
using namespace System.Management.Automation.Language
3+
4+
class SpecialVariables {
5+
static [System.Lazy[string[]]] $SpecialVariables = [Lazy[string[]]]::new(
6+
[Func[string[]]]{
7+
# Nothing public exists to get this unfortunately.
8+
return [ref].
9+
Assembly.
10+
GetType('System.Management.Automation.SpecialVariables').
11+
DeclaredFields.
12+
Where{ $PSItem.FieldType -eq [string] }.
13+
ForEach{ $PSItem.GetValue($null) }
14+
});
15+
16+
static [bool] IsSpecialVariable([VariableExpressionAst] $variable) {
17+
return [SpecialVariables]::IsSpecialVariable($variable.VariablePath)
18+
}
19+
20+
static [bool] IsSpecialVariable([VariablePath] $variable) {
21+
return [SpecialVariables]::IsSpecialVariable($variable.UserPath)
22+
}
23+
24+
static [bool] IsSpecialVariable([string] $variable) {
25+
if ([string]::IsNullOrEmpty($variable)) {
26+
return $false
27+
}
28+
29+
return $variable -in [SpecialVariables]::SpecialVariables.Value -or $variable -eq 'psEditor'
30+
}
31+
}

module/EditorServicesCommandSuite.psd1

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
RootModule = 'EditorServicesCommandSuite.psm1'
1313

1414
# Version number of this module.
15-
ModuleVersion = '0.3.0'
15+
ModuleVersion = '0.4.0'
1616

1717
# ID used to uniquely identify this module
1818
GUID = '97607afd-d9bd-4a2e-a9f9-70fe1a0a9e4c'
@@ -48,6 +48,7 @@ RequiredModules = 'PSStringTemplate'
4848
FunctionsToExport = 'Add-CommandToManifest',
4949
'Add-ModuleQualification',
5050
'Add-PinvokeMethod',
51+
'ConvertTo-FunctionDefinition',
5152
'ConvertTo-LocalizationString',
5253
'ConvertTo-MarkdownHelp',
5354
'ConvertTo-SplatExpression',
@@ -75,6 +76,7 @@ FileList = 'EditorServicesCommandSuite.psd1',
7576
'Public\Add-CommandToManifest.ps1',
7677
'Public\Add-ModuleQualification.ps1',
7778
'Public\Add-PinvokeMethod.ps1',
79+
'Public\ConvertTo-FunctionDefinition.ps1',
7880
'Public\ConvertTo-LocalizationString.ps1',
7981
'Public\ConvertTo-MarkdownHelp.ps1',
8082
'Public\ConvertTo-SplatExpression.ps1',
@@ -106,8 +108,7 @@ PrivateData = @{
106108

107109
# ReleaseNotes of this module
108110
ReleaseNotes = @'
109-
- New editor command Add-PinvokeMethod. Allows you to search the pinvoke.net web service for
110-
matching functions and inserts a Add-Type statement to the current file.
111+
- New editor command ConvertTo-FunctionDefinition for generating functions from selected text.
111112
'@
112113

113114
} # End of PSData hashtable

module/EditorServicesCommandSuite.psm1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ if (-not ('Antlr4.StringTemplate.StringRenderer' -as [type])) {
2222
. $PSScriptRoot\Classes\Expressions.ps1
2323
. $PSScriptRoot\Classes\Renderers.ps1
2424
. $PSScriptRoot\Classes\Async.ps1
25+
. $PSScriptRoot\Classes\Utility.ps1
2526

2627
Get-ChildItem $PSScriptRoot\Public, $PSScriptRoot\Private -Filter '*.ps1' | ForEach-Object {
2728
. $PSItem.FullName

module/Private/AddIndent.ps1

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
function AddIndent {
2+
[OutputType([string])]
3+
[CmdletBinding()]
4+
param(
5+
[Parameter(Mandatory, ValueFromPipeline)]
6+
[string[]] $Source,
7+
[string] $Indent = ' ',
8+
[int] $Amount = 4,
9+
[switch] $ExcludeFirstLine
10+
)
11+
begin {
12+
$stringList = [System.Collections.Generic.List[string]]::new()
13+
}
14+
process {
15+
if ($null -eq $Source) {
16+
return
17+
}
18+
19+
$stringList.AddRange($Source)
20+
}
21+
end {
22+
$sourceText = $stringList -join [Environment]::NewLine
23+
if ($Amount -lt 1) {
24+
return $sourceText
25+
}
26+
27+
$indentText = $Indent * $Amount
28+
# Preserve new line characters. Only works if not sent a stream.
29+
$newLine = [regex]::Match($sourceText, '\r?\n').Value
30+
$asLines = $sourceText -split '\r?\n'
31+
$first = $true
32+
$indentedLines = foreach ($line in $asLines) {
33+
if ($first) {
34+
$first = $false
35+
if ($ExcludeFirstLine.IsPresent) {
36+
$line
37+
continue
38+
}
39+
}
40+
41+
# Don't indent blank lines or here-string ending tags
42+
$shouldNotIndent = [string]::IsNullOrWhiteSpace($line) -or
43+
$line.StartsWith("'@") -or
44+
$line.StartsWith('"@')
45+
if ($shouldNotIndent) {
46+
$line
47+
continue
48+
}
49+
50+
$indentText + $line
51+
}
52+
53+
return $indentedLines -join $newLine
54+
}
55+
}

0 commit comments

Comments
 (0)