1
2# Define our constant variables.
3[string]$NEW_LINE_STRING = "`r`n"
4[string]$COMMENT_STRING = "#"
5
6function Select-EntireLinesInIseSelectedTextAndReturnFirstAndLastSelectedLineNumbers([bool]$DoNothingWhenNotCertainOfWhichLinesToSelect = $false)
7{
8<#
9 .SYNOPSIS
10 Exands the selected text to make sure the entire lines are selected.
11 Returns $null if we can't determine with certainty which lines to select and the
12
13 .DESCRIPTION
14 Exands the selected text to make sure the entire lines are selected.
15
16 .PARAMETER DoNothingWhenNotCertainOfWhichLinesToSelect
17 Under the following edge case we can't determine for sure which lines in the file are selected.
18 If this switch is not provided and the edge case is encountered, we will guess and attempt to select the entire selected lines, but we may guess wrong and select the lines above/below the selected lines.
19 If this switch is provided and the edge case is encountered, no lines will be selected.
20
21 Edge Case:
22 - When the selected text occurs multiple times in the document, directly above or below the selected text.
23
24 Example:
25 abc
26 abc
27 abc
28
29 - If only the first two lines are selected, when you run this command it may comment out the 1st and 2nd lines correctly, or it may comment out the 2nd and 3rd lines, depending on
30 if the caret is on the 1st line or 2nd line when selecting the text (i.e. the text is selected bottom-to-top vs. top-to-bottom).
31 - Since the lines are typically identical for this edge case to occur, you likely won't really care which 2 of the 3 lines get selected, so it shouldn't be a big deal.
32 But if it bugs you, you can provide this switch.
33
34 .OUTPUT
35 PSObject. Returns a PSObject with the properties FirstLineNumber and LastLineNumber, which correspond to the first and last line numbers of the selected text.
36#>
37
38 # Backup all of the original info before we modify it.
39 [int]$originalCaretLine = $psISE.CurrentFile.Editor.CaretLine
40 [string]$originalSelectedText = $psISE.CurrentFile.Editor.SelectedText
41 [string]$originalCaretLineText = $psISE.CurrentFile.Editor.CaretLineText
42
43 # Assume only one line is selected.
44 [int]$textToSelectFirstLine = $originalCaretLine
45 [int]$textToSelectLastLine = $originalCaretLine
46
47 #------------------------
48 # Before we process the selected text, we need to make sure all selected lines are fully selected (i.e. the entire line is selected).
49 #------------------------
50
51 # If no text is selected, OR only part of one line is selected (and it doesn't include the start of the line), select the entire line that the caret is currently on.
52 if (($psISE.CurrentFile.Editor.SelectedText.Length -le 0) -or !$psISE.CurrentFile.Editor.SelectedText.Contains($NEW_LINE_STRING))
53 {
54 $psISE.CurrentFile.Editor.SelectCaretLine()
55 }
56 # Else the first part of one line (or the entire line), or multiple lines are selected.
57 else
58 {
59 # Get the number of lines in the originally selected text.
60 [string[]] $originalSelectedTextArray = $originalSelectedText.Split([string[]]$NEW_LINE_STRING, [StringSplitOptions]::None)
61 [int]$numberOfLinesInSelectedText = $originalSelectedTextArray.Length
62
63 # If only one line is selected, make sure it is fully selected.
64 if ($numberOfLinesInSelectedText -le 1)
65 {
66 $psISE.CurrentFile.Editor.SelectCaretLine()
67 }
68 # Else there are multiple lines selected, so make sure the first character of the top line is selected (so that we put the comment character at the start of the top line, not in the middle).
69 # The first character of the bottom line will always be selected when multiple lines are selected, so we don't have to worry about making sure it is selected; only the top line.
70 else
71 {
72 # Determine if the caret is on the first or last line of the selected text.
73 [bool]$isCaretOnFirstLineOfSelectedText = $false
74 [string]$firstLineOfOriginalSelectedText = $originalSelectedTextArray[0]
75 [string]$lastLineOfOriginalSelectedText = $originalSelectedTextArray[$originalSelectedTextArray.Length - 1]
76
77 # If the caret is definitely on the first line.
78 if ($originalCaretLineText.EndsWith($firstLineOfOriginalSelectedText) -and !$originalCaretLineText.StartsWith($lastLineOfOriginalSelectedText))
79 {
80 $isCaretOnFirstLineOfSelectedText = $true
81 }
82 # Else if the caret is definitely on the last line.
83 elseif ($originalCaretLineText.StartsWith($lastLineOfOriginalSelectedText) -and !$originalCaretLineText.EndsWith($firstLineOfOriginalSelectedText))
84 {
85 $isCaretOnFirstLineOfSelectedText = $false
86 }
87 # Else we need to do further analysis to determine if the caret is on the first or last line of the selected text.
88 else
89 {
90 [int]$numberOfLinesInFile = $psISE.CurrentFile.Editor.LineCount
91
92 [string]$caretOnFirstLineText = [string]::Empty
93 [int]$caretOnFirstLineArrayStartIndex = ($originalCaretLine - 1) # -1 because array starts at 0 and file lines start at 1.
94 [int]$caretOnFirstLineArrayStopIndex = $caretOnFirstLineArrayStartIndex + ($numberOfLinesInSelectedText - 1) # -1 because the starting line is inclusive (i.e. if we want 1 line the start and stop lines should be the same).
95
96 [string]$caretOnLastLineText = [string]::Empty
97 [int]$caretOnLastLineArrayStopIndex = ($originalCaretLine - 1) # -1 because array starts at 0 and file lines start at 1.
98 [int]$caretOnLastLineArrayStartIndex = $caretOnLastLineArrayStopIndex - ($numberOfLinesInSelectedText - 1) # -1 because the stopping line is inclusive (i.e. if we want 1 line the start and stop lines should be the same).
99
100 # If the caret being on the first line would cause us to go "off the file", then we know the caret is on the last line.
101 if (($caretOnFirstLineArrayStartIndex -lt 0) -or ($caretOnFirstLineArrayStopIndex -ge $numberOfLinesInFile))
102 {
103 $isCaretOnFirstLineOfSelectedText = $false
104 }
105 # If the caret being on the last line would cause us to go "off the file", then we know the caret is on the first line.
106 elseif (($caretOnLastLineArrayStartIndex -lt 0) -or ($caretOnLastLineArrayStopIndex -ge $numberOfLinesInFile))
107 {
108 $isCaretOnFirstLineOfSelectedText = $true
109 }
110 # Else we still don't know where the caret is.
111 else
112 {
113 [string[]]$filesTextArray = $psISE.CurrentFile.Editor.Text.Split([string[]]$NEW_LINE_STRING, [StringSplitOptions]::None)
114
115 # Get the text of the lines where the caret is on the first line of the selected text.
116 [string[]]$caretOnFirstLineTextArray = @([string]::Empty) * $numberOfLinesInSelectedText # Declare an array with the number of elements required.
117 [System.Array]::Copy($filesTextArray, $caretOnFirstLineArrayStartIndex, $caretOnFirstLineTextArray, 0, $numberOfLinesInSelectedText)
118 $caretOnFirstLineText = $caretOnFirstLineTextArray -join $NEW_LINE_STRING
119
120 # Get the text of the lines where the caret is on the last line of the selected text.
121 [string[]]$caretOnLastLineTextArray = @([string]::Empty) * $numberOfLinesInSelectedText # Declare an array with the number of elements required.
122 [System.Array]::Copy($filesTextArray, $caretOnLastLineArrayStartIndex, $caretOnLastLineTextArray, 0, $numberOfLinesInSelectedText)
123 $caretOnLastLineText = $caretOnLastLineTextArray -join $NEW_LINE_STRING
124
125 [bool]$caretOnFirstLineTextContainsOriginalSelectedText = $caretOnFirstLineText.Contains($originalSelectedText)
126 [bool]$caretOnLastLineTextContainsOriginalSelectedText = $caretOnLastLineText.Contains($originalSelectedText)
127
128 # If the selected text is only within the text of when the caret is on the first line, then we know for sure the caret is on the first line.
129 if ($caretOnFirstLineTextContainsOriginalSelectedText -and !$caretOnLastLineTextContainsOriginalSelectedText)
130 {
131 $isCaretOnFirstLineOfSelectedText = $true
132 }
133 # Else if the selected text is only within the text of when the caret is on the last line, then we know for sure the caret is on the last line.
134 elseif ($caretOnLastLineTextContainsOriginalSelectedText -and !$caretOnFirstLineTextContainsOriginalSelectedText)
135 {
136 $isCaretOnFirstLineOfSelectedText = $false
137 }
138 # Else if the selected text is in both sets of text, then we don't know for sure if the caret is on the first or last line.
139 elseif ($caretOnFirstLineTextContainsOriginalSelectedText -and $caretOnLastLineTextContainsOriginalSelectedText)
140 {
141 # If we shouldn't do anything since we might comment out text that is not selected by the user, just exit this function and return null.
142 if ($DoNothingWhenNotCertainOfWhichLinesToSelect)
143 {
144 return $null
145 }
146 }
147 # Else something went wrong and there is a flaw in this logic, since the selected text should be in one of our two strings, so let's just guess!
148 else
149 {
150 Write-Error "WHAT HAPPENED?!?! This line should never be reached. There is a flaw in our logic!"
151 return $null
152 }
153 }
154 }
155
156 # Assume the caret is on the first line of the selected text, so we want to select text from the caret's line downward.
157 $textToSelectFirstLine = $originalCaretLine
158 $textToSelectLastLine = $originalCaretLine + ($numberOfLinesInSelectedText - 1) # -1 because the starting line is inclusive (i.e. if we want 1 line the start and stop lines should be the same).
159
160 # If the caret is actually on the last line of the selected text, we want to select text from the caret's line upward.
161 if (!$isCaretOnFirstLineOfSelectedText)
162 {
163 $textToSelectFirstLine = $originalCaretLine - ($numberOfLinesInSelectedText - 1) # -1 because the stopping line is inclusive (i.e. if we want 1 line the start and stop lines should be the same).
164 $textToSelectLastLine = $originalCaretLine
165 }
166
167 # Re-select the text, making sure the entire first and last lines are selected. +1 on EndLineWidth because column starts at 1, not 0.
168 $psISE.CurrentFile.Editor.Select($textToSelectFirstLine, 1, $textToSelectLastLine, $psISE.CurrentFile.Editor.GetLineLength($textToSelectLastLine) + 1)
169 }
170 }
171
172 # Return the first and last line numbers selected.
173 $selectedTextFirstAndLastLineNumbers = New-Object PSObject -Property @{
174 FirstLineNumber = $textToSelectFirstLine
175 LastLineNumber = $textToSelectLastLine
176 }
177 return $selectedTextFirstAndLastLineNumbers
178}
179
180function CommentOrUncommentIseSelectedLines([bool]$CommentLines = $false, [bool]$DoNothingWhenNotCertainOfWhichLinesToSelect = $false)
181{
182 $selectedTextFirstAndLastLineNumbers = Select-EntireLinesInIseSelectedTextAndReturnFirstAndLastSelectedLineNumbers $DoNothingWhenNotCertainOfWhichLinesToSelect
183
184 # If we couldn't determine which lines to select, just exit without changing anything.
185 if ($selectedTextFirstAndLastLineNumbers -eq $null) { return }
186
187 # Get the text lines selected.
188 [int]$selectedTextFirstLineNumber = $selectedTextFirstAndLastLineNumbers.FirstLineNumber
189 [int]$selectedTextLastLineNumber = $selectedTextFirstAndLastLineNumbers.LastLineNumber
190
191 # Get the Selected Text and convert it into an array of strings so we can easily process each line.
192 [string]$selectedText = $psISE.CurrentFile.Editor.SelectedText
193 [string[]] $selectedTextArray = $selectedText.Split([string[]]$NEW_LINE_STRING, [StringSplitOptions]::None)
194
195 # Process each line of the Selected Text, and save the modified lines into a text array.
196 [string[]]$newSelectedTextArray = @()
197 $selectedTextArray | foreach {
198 # If the line is not blank, add a comment character to the start of it.
199 [string]$lineText = $_
200 if ([string]::IsNullOrWhiteSpace($lineText)) { $newSelectedTextArray += $lineText }
201 else
202 {
203 # If we should be commenting the lines out, add a comment character to the start of the line.
204 if ($CommentLines)
205 { $newSelectedTextArray += "$COMMENT_STRING$lineText" }
206 # Else we should be uncommenting, so remove a comment character from the start of the line if it exists.
207 else
208 {
209 # If the line begins with a comment, remove one (and only one) comment character.
210 if ($lineText.StartsWith($COMMENT_STRING))
211 {
212 $lineText = $lineText.Substring($COMMENT_STRING.Length)
213 }
214 $newSelectedTextArray += $lineText
215 }
216 }
217 }
218
219 # Join the text array back together to get the new Selected Text string.
220 [string]$newSelectedText = $newSelectedTextArray -join $NEW_LINE_STRING
221
222 # Overwrite the currently Selected Text with the new Selected Text.
223 $psISE.CurrentFile.Editor.InsertText($newSelectedText)
224
225 # Fully select all of the lines that were modified. +1 on End Line's Width because column starts at 1, not 0.
226 $psISE.CurrentFile.Editor.Select($selectedTextFirstLineNumber, 1, $selectedTextLastLineNumber, $psISE.CurrentFile.Editor.GetLineLength($selectedTextLastLineNumber) + 1)
227}
228
229function Comment-IseSelectedLines([switch]$DoNothingWhenNotCertainOfWhichLinesToComment)
230{
231<#
232 .SYNOPSIS
233 Places a comment character at the start of each line of the selected text in the current PS ISE file.
234 If no text is selected, it will comment out the line that the caret is on.
235
236 .DESCRIPTION
237 Places a comment character at the start of each line of the selected text in the current PS ISE file.
238 If no text is selected, it will comment out the line that the caret is on.
239
240 .PARAMETER DoNothingWhenNotCertainOfWhichLinesToComment
241 Under the following edge case we can't determine for sure which lines in the file are selected.
242 If this switch is not provided and the edge case is encountered, we will guess and attempt to comment out the selected lines, but we may guess wrong and comment out the lines above/below the selected lines.
243 If this switch is provided and the edge case is encountered, no lines will be commented out.
244
245 Edge Case:
246 - When the selected text occurs multiple times in the document, directly above or below the selected text.
247
248 Example:
249 abc
250 abc
251 abc
252
253 - If only the first two lines are selected, when you run this command it may comment out the 1st and 2nd lines correctly, or it may comment out the 2nd and 3rd lines, depending on
254 if the caret is on the 1st line or 2nd line when selecting the text (i.e. the text is selected bottom-to-top vs. top-to-bottom).
255 - Since the lines are typically identical for this edge case to occur, you likely won't really care which 2 of the 3 lines get commented out, so it shouldn't be a big deal.
256 But if it bugs you, you can provide this switch.
257#>
258 CommentOrUncommentIseSelectedLines -CommentLines $true -DoNothingWhenNotCertainOfWhichLinesToSelect $DoNothingWhenNotCertainOfWhichLinesToComment
259}
260
261function Uncomment-IseSelectedLines([switch]$DoNothingWhenNotCertainOfWhichLinesToUncomment)
262{
263<#
264 .SYNOPSIS
265 Removes the comment character from the start of each line of the selected text in the current PS ISE file (if it is commented out).
266 If no text is selected, it will uncomment the line that the caret is on.
267
268 .DESCRIPTION
269 Removes the comment character from the start of each line of the selected text in the current PS ISE file (if it is commented out).
270 If no text is selected, it will uncomment the line that the caret is on.
271
272 .PARAMETER DoNothingWhenNotCertainOfWhichLinesToUncomment
273 Under the following edge case we can't determine for sure which lines in the file are selected.
274 If this switch is not provided and the edge case is encountered, we will guess and attempt to uncomment the selected lines, but we may guess wrong and uncomment out the lines above/below the selected lines.
275 If this switch is provided and the edge case is encountered, no lines will be uncommentet.
276
277 Edge Case:
278 - When the selected text occurs multiple times in the document, directly above or below the selected text.
279
280 Example:
281 abc
282 abc
283 abc
284
285 - If only the first two lines are selected, when you run this command it may uncomment the 1st and 2nd lines correctly, or it may uncomment the 2nd and 3rd lines, depending on
286 if the caret is on the 1st line or 2nd line when selecting the text (i.e. the text is selected bottom-to-top vs. top-to-bottom).
287 - Since the lines are typically identical for this edge case to occur, you likely won't really care which 2 of the 3 lines get uncommented, so it shouldn't be a big deal.
288 But if it bugs you, you can provide this switch.
289#>
290 CommentOrUncommentIseSelectedLines -CommentLines $false -DoNothingWhenNotCertainOfWhichLinesToSelect $DoNothingWhenNotCertainOfWhichLinesToUncomment
291}
292
293#==========================================================
294# Add ISE Add-ons.
295#==========================================================
296
297# Add a new option in the Add-ons menu to comment all selected lines.
298if (!($psISE.CurrentPowerShellTab.AddOnsMenu.Submenus | Where-Object { $_.DisplayName -eq "Comment Selected Lines" }))
299{
300 $psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add("Comment Selected Lines",{Comment-IseSelectedLines},"Ctrl+K")
301}
302
303# Add a new option in the Add-ons menu to uncomment all selected lines.
304if (!($psISE.CurrentPowerShellTab.AddOnsMenu.Submenus | Where-Object { $_.DisplayName -eq "Uncomment Selected Lines" }))
305{
306 $psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add("Uncomment Selected Lines",{Uncomment-IseSelectedLines},"Ctrl+Shift+K")
307}
Comments