Skip to content

Commit 917e0a7

Browse files
committed
(#669) Improve PowerShell Tab Completion
This commit improves the PowerShell Tab Completion for Chocolatey CLI, by including completions for the following commands: * apikey * config * feature * pin * rule * source * template Each of these completions rely on directly calling the equivalent "list£ sub-command for each command (with the exception of one of the pin completions, which also relies on running choco list), which unfortunately can take a second to complete. As a future enhancement to this functionality, we should look to serializing these completions to disk, so that they can be loaded and shown from there. These files would be updated each time an associated command is executed. For example, when someone runs "choco source add" update the completions file for when doing "choco source remove". A group of Pester tests have been added to ensure that these completions work as expected.
1 parent 9328e59 commit 917e0a7

File tree

2 files changed

+242
-1
lines changed

2 files changed

+242
-1
lines changed

src/chocolatey.resources/helpers/ChocolateyTabExpansion.ps1

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,22 @@ function script:chocoCommands($filter) {
135135
$cmdList #| sort
136136
}
137137

138+
function script:chocoApiKeysList() {
139+
@(& $script:choco apikey list --limit-output) | ForEach-Object { $_.Split('|')[0] }
140+
}
141+
142+
function script:chocoConfigsList() {
143+
@(& $script:choco config list --limit-output) | ForEach-Object { $_.Split('|')[0] }
144+
}
145+
146+
function script:chocoFeaturesList() {
147+
@(& $script:choco feature list --limit-output) | ForEach-Object { $_.Split('|')[0] }
148+
}
149+
150+
function script:chocoLocalNonPinnedPackages() {
151+
@(& $script:choco list --limit-output --ignore-pinned) | ForEach-Object { $_.Split('|')[0] }
152+
}
153+
138154
function script:chocoLocalPackages($filter) {
139155
if ($filter -and $filter.StartsWith(".")) {
140156
return;
@@ -151,6 +167,10 @@ function script:chocoLocalPackagesUpgrade($filter) {
151167
ForEach-Object { $_.Split('|')[0] }
152168
}
153169

170+
function script:chocoLocalPinnedPackages() {
171+
@(& $script:choco pin list --limit-output) | ForEach-Object { $_.Split('|')[0] }
172+
}
173+
154174
function script:chocoRemotePackages($filter) {
155175
if ($filter -and $filter.StartsWith(".")) {
156176
return;
@@ -171,6 +191,18 @@ function script:chocoRemotePackageVersions($Name, $Version) {
171191
}
172192
}
173193

194+
function script:chocoRulesList() {
195+
@(& $script:choco rule list --limit-output) | ForEach-Object { $_.Split('|')[1] }
196+
}
197+
198+
function script:chocoSourcesList() {
199+
@(& $script:choco source list --limit-output) | ForEach-Object { $_.Split('|')[0] }
200+
}
201+
202+
function script:chocoTemplatesList() {
203+
@(& $script:choco template list --limit-output) | ForEach-Object { $_.Split('|')[0] }
204+
}
205+
174206
function Get-AliasPattern($exe) {
175207
$aliases = @($exe) + @(Get-Alias | Where-Object { $_.Definition -eq $exe } | Select-Object -Exp Name)
176208

@@ -201,6 +233,12 @@ function ChocolateyTabExpansion($lastBlock) {
201233
@('add', 'list', 'remove', '--help') | Where-Object { $_ -like "$($matches['subcommand'])*" }
202234
}
203235

236+
# Custom completion choco apikey remove
237+
"^apikey remove.*--source='?(?<source>.*)'?$" {
238+
$name = $matches['source']
239+
return chocoApiKeysList | Where-Object { $_ -like "*$source*" } | ForEach-Object { "--source='$_'"}
240+
}
241+
204242
# Handles cache first tab
205243
"^(cache)\s+(?<subcommand>[^-\s]*)$" {
206244
@('list', 'remove', '--help') | Where-Object { $_ -like "$($matches['subcommand'])*" }
@@ -211,11 +249,23 @@ function ChocolateyTabExpansion($lastBlock) {
211249
@('get', 'list', 'set', 'unset', '--help') | Where-Object { $_ -like "$($matches['subcommand'])*" }
212250
}
213251

252+
# Custom completion choco config get/set/unset
253+
"^config [get|set|unset].*--name='?(?<name>.*)'?$" {
254+
$name = $matches['name']
255+
return chocoConfigsList | Where-Object { $_ -like "*$name*" } | ForEach-Object { "--name='$_'"}
256+
}
257+
214258
# Handles feature first tab
215259
"^(feature)\s+(?<subcommand>[^-\s]*)$" {
216260
@('disable', 'enable', 'get', 'list', '--help') | Where-Object { $_ -like "$($matches['subcommand'])*" }
217261
}
218262

263+
# Custom completion choco feature get/enable/disable
264+
"^feature [get|disable|enable].*--name='?(?<name>.*)'?$" {
265+
$name = $matches['name']
266+
return chocoFeaturesList | Where-Object { $_ -like "*$name*" } | ForEach-Object { "--name='$_'"}
267+
}
268+
219269
# Handles install/upgrade package versions for a specific package
220270
"^(install|upgrade)\s+(?<package>[^\.\-][^\s]+).*--version='?(?<version>[^\s']*)'?$" {
221271
chocoRemotePackageVersions -Name $matches['package'] -Version $matches['version'] | ForEach-Object { "--version='$_'" }
@@ -251,6 +301,18 @@ function ChocolateyTabExpansion($lastBlock) {
251301
@('add', 'list', 'remove', '--help') | Where-Object { $_ -like "$($matches['subcommand'])*" }
252302
}
253303

304+
# Custom completion choco pin add
305+
"^pin add.*--name='?(?<name>.*)'?$" {
306+
$name = $matches['name']
307+
return chocoLocalNonPinnedPackages | Where-Object { $_ -like "*$name*" } | ForEach-Object { "--name='$_'"}
308+
}
309+
310+
# Custom completion choco pin remove
311+
"^pin remove.*--name='?(?<name>.*)'?$" {
312+
$name = $matches['name']
313+
return chocoLocalPinnedPackages | Where-Object { $_ -like "*$name*" } | ForEach-Object { "--name='$_'"}
314+
}
315+
254316
# Handles push first tab
255317
"^(push)\s+(?<subcommand>[^-\s]*)$" {
256318
@('<PathtoNupkg>', '--help') | Where-Object { $_ -like "$($matches['subcommand'])*" }
@@ -261,6 +323,12 @@ function ChocolateyTabExpansion($lastBlock) {
261323
@('get', 'list', '--help') | Where-Object { $_ -like "$($matches['subcommand'])*" }
262324
}
263325

326+
# Custom completion choco rule get
327+
"^rule get.*--name='?(?<name>.*)'?$" {
328+
$name = $matches['name']
329+
return chocoRulesList | Where-Object { $_ -like "*$name*" } | ForEach-Object { "--name='$_'"}
330+
}
331+
264332
# Handles search first tab
265333
"^(search)\s+(?<subcommand>[^-\s]*)$" {
266334
@('<filter>', '--help') | Where-Object { $_ -like "$($matches['subcommand'])*" }
@@ -271,11 +339,23 @@ function ChocolateyTabExpansion($lastBlock) {
271339
@('add', 'disable', 'enable', 'list', 'remove', '--help') | Where-Object { $_ -like "$($matches['subcommand'])*" }
272340
}
273341

342+
# Custom completion choco source disable/enable/remove
343+
"^source [disable|enable|remove].*--name='?(?<name>.*)'?$" {
344+
$name = $matches['name']
345+
return chocoSourcesList | Where-Object { $_ -like "*$name*" } | ForEach-Object { "--name='$_'"}
346+
}
347+
274348
# Handles template first tab
275349
"^(template)\s+(?<subcommand>[^-\s]*)$" {
276350
@('info', 'list', '--help') | Where-Object { $_ -like "$($matches['subcommand'])*" }
277351
}
278352

353+
# Custom completion choco template info
354+
"^template info.*--name='?(?<name>.*)'?$" {
355+
$name = $matches['name']
356+
return chocoTemplatesList | Where-Object { $_ -like "*$name*" } | ForEach-Object { "--name='$_'"}
357+
}
358+
279359
# Handles uninstall package names
280360
"^uninstall\s+(?<package>[^\.][^-\s]*)$" {
281361
chocoLocalPackages $matches['package']
@@ -292,7 +372,6 @@ function ChocolateyTabExpansion($lastBlock) {
292372
return Get-ChocoOrderByOptions | Where-Object { $_ -like "$prefix*" } | ForEach-Object { "--order-by='$_'"}
293373
}
294374

295-
296375
# Handles more options after others
297376
"^(?<cmd>$($commandOptions.Keys -join '|'))(?<currentArguments>.*)\s+(?<op>\S*)$" {
298377
chocoCmdOperations $commandOptions $matches['cmd'] $matches['op'] $matches['currentArguments']

tests/pester-tests/chocolateyProfile.Tests.ps1

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,28 @@ Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1"
44
Describe "Chocolatey Profile" -Tag Chocolatey, Profile, Environment {
55
# Because we're not modifying the install in any way, there is no need to Initialize-ChocolateyTestInstall
66
BeforeDiscovery {
7+
$isLicensed = Test-PackageIsEqualOrHigher "chocolatey.extension" "0.0.0"
8+
79
$ExportNotPresent = $true
810
if (Test-ChocolateyVersionEqualOrHigherThan -Version "0.10.16-beta") {
911
$ExportNotPresent = $false
1012
}
1113
}
1214

1315
Context "Tab Completion" {
16+
BeforeAll {
17+
Initialize-ChocolateyTestInstall
18+
19+
# These are needed in order to test the tab completions for some
20+
# Chocolatey operations
21+
$null = Invoke-Choco pin add --name="chocolatey"
22+
23+
$null = Invoke-Choco apikey add --source "https://test.com/api/add/" --api-key "test-api-key"
24+
25+
$null = Invoke-Choco install upgradepackage --version 1.0.0 --confirm
26+
27+
New-ChocolateyInstallSnapshot
28+
}
1429
It "Should list completions for all Top Level Commands, sorted alphabetically, but not aliases or unpackself" {
1530
$Command = "choco "
1631
$Completions = (TabExpansion2 -inputScript $Command -cursorColumn $Command.Length).CompletionMatches.CompletionText
@@ -434,6 +449,153 @@ Describe "Chocolatey Profile" -Tag Chocolatey, Profile, Environment {
434449
$Completions | Should -Contain "--version=''" -Because $becauseCompletions
435450
}
436451

452+
It "Should list completions for apikey remove" {
453+
$Command = "choco apikey remove --source='"
454+
$Completions = (TabExpansion2 -inputScript $Command -cursorColumn $Command.Length).CompletionMatches.CompletionText
455+
456+
$becauseCompletions = ($Completions -Join ", ")
457+
458+
$Completions | Should -Contain "--source='https://test.com/api/add/'" -Because $becauseCompletions
459+
}
460+
461+
It "Should list completions for feature enable" {
462+
$Command = "choco feature enable --name='"
463+
$Completions = (TabExpansion2 -inputScript $Command -cursorColumn $Command.Length).CompletionMatches.CompletionText
464+
465+
$becauseCompletions = ($Completions -Join ", ")
466+
467+
$Completions | Should -Contain "--name='allowEmptyChecksums'" -Because $becauseCompletions
468+
$Completions | Should -Contain "--name='useRememberedArgumentsForUpgrades'" -Because $becauseCompletions
469+
470+
if ($isLicensed) {
471+
$Completions | Should -Contain "--name='adminOnlyExecutionForAllChocolateyCommands'" -Because $becauseCompletions
472+
}
473+
}
474+
475+
It "Should list completions for feature disable" {
476+
$Command = "choco feature disable --name='"
477+
$Completions = (TabExpansion2 -inputScript $Command -cursorColumn $Command.Length).CompletionMatches.CompletionText
478+
479+
$becauseCompletions = ($Completions -Join ", ")
480+
481+
$Completions | Should -Contain "--name='allowEmptyChecksums'" -Because $becauseCompletions
482+
$Completions | Should -Contain "--name='useRememberedArgumentsForUpgrades'" -Because $becauseCompletions
483+
484+
if ($isLicensed) {
485+
$Completions | Should -Contain "--name='adminOnlyExecutionForAllChocolateyCommands'" -Because $becauseCompletions
486+
}
487+
}
488+
489+
It "Should list completions for feature get" {
490+
$Command = "choco feature get --name='"
491+
$Completions = (TabExpansion2 -inputScript $Command -cursorColumn $Command.Length).CompletionMatches.CompletionText
492+
493+
$becauseCompletions = ($Completions -Join ", ")
494+
495+
$Completions | Should -Contain "--name='allowEmptyChecksums'" -Because $becauseCompletions
496+
$Completions | Should -Contain "--name='useRememberedArgumentsForUpgrades'" -Because $becauseCompletions
497+
498+
if ($isLicensed) {
499+
$Completions | Should -Contain "--name='adminOnlyExecutionForAllChocolateyCommands'" -Because $becauseCompletions
500+
}
501+
}
502+
503+
It "Should list completions for config get" {
504+
$Command = "choco config get --name='"
505+
$Completions = (TabExpansion2 -inputScript $Command -cursorColumn $Command.Length).CompletionMatches.CompletionText
506+
507+
$becauseCompletions = ($Completions -Join ", ")
508+
509+
$Completions | Should -Contain "--name='cacheLocation'" -Because $becauseCompletions
510+
$Completions | Should -Contain "--name='webRequestTimeoutSeconds'" -Because $becauseCompletions
511+
512+
if ($isLicensed) {
513+
$Completions | Should -Contain "--name='virusScannerType'" -Because $becauseCompletions
514+
}
515+
}
516+
517+
It "Should list completions for config set" {
518+
$Command = "choco config set --name='"
519+
$Completions = (TabExpansion2 -inputScript $Command -cursorColumn $Command.Length).CompletionMatches.CompletionText
520+
521+
$becauseCompletions = ($Completions -Join ", ")
522+
523+
$Completions | Should -Contain "--name='cacheLocation'" -Because $becauseCompletions
524+
$Completions | Should -Contain "--name='webRequestTimeoutSeconds'" -Because $becauseCompletions
525+
526+
if ($isLicensed) {
527+
$Completions | Should -Contain "--name='virusScannerType'" -Because $becauseCompletions
528+
}
529+
}
530+
531+
It "Should list completions for config unset" {
532+
$Command = "choco config unset --name='"
533+
$Completions = (TabExpansion2 -inputScript $Command -cursorColumn $Command.Length).CompletionMatches.CompletionText
534+
535+
$becauseCompletions = ($Completions -Join ", ")
536+
537+
$Completions | Should -Contain "--name='cacheLocation'" -Because $becauseCompletions
538+
$Completions | Should -Contain "--name='webRequestTimeoutSeconds'" -Because $becauseCompletions
539+
540+
if ($isLicensed) {
541+
$Completions | Should -Contain "--name='virusScannerType'" -Because $becauseCompletions
542+
}
543+
}
544+
545+
It "Should list completions for pin add" {
546+
$Command = "choco pin add --name='"
547+
$Completions = (TabExpansion2 -inputScript $Command -cursorColumn $Command.Length).CompletionMatches.CompletionText
548+
549+
$becauseCompletions = ($Completions -Join ", ")
550+
551+
$Completions | Should -Contain "--name='upgradepackage'" -Because $becauseCompletions
552+
}
553+
554+
It "Should list completions for pin remove" {
555+
$Command = "choco pin remove --name='"
556+
$Completions = (TabExpansion2 -inputScript $Command -cursorColumn $Command.Length).CompletionMatches.CompletionText
557+
558+
$becauseCompletions = ($Completions -Join ", ")
559+
560+
$Completions | Should -Contain "--name='chocolatey'" -Because $becauseCompletions
561+
}
562+
563+
It "Should list completions for rule get" {
564+
$Command = "choco rule get --name='"
565+
$Completions = (TabExpansion2 -inputScript $Command -cursorColumn $Command.Length).CompletionMatches.CompletionText
566+
567+
$becauseCompletions = ($Completions -Join ", ")
568+
569+
$Completions | Should -Contain "--name='CHCU0001'" -Because $becauseCompletions
570+
}
571+
572+
It "Should list completions for source disable" {
573+
$Command = "choco source disable --name='"
574+
$Completions = (TabExpansion2 -inputScript $Command -cursorColumn $Command.Length).CompletionMatches.CompletionText
575+
576+
$becauseCompletions = ($Completions -Join ", ")
577+
578+
$Completions | Should -Contain "--name='chocolatey'" -Because $becauseCompletions
579+
}
580+
581+
It "Should list completions for source enable" {
582+
$Command = "choco source enable --name='"
583+
$Completions = (TabExpansion2 -inputScript $Command -cursorColumn $Command.Length).CompletionMatches.CompletionText
584+
585+
$becauseCompletions = ($Completions -Join ", ")
586+
587+
$Completions | Should -Contain "--name='chocolatey'" -Because $becauseCompletions
588+
}
589+
590+
It "Should list completions for source remove" {
591+
$Command = "choco source remove --name='"
592+
$Completions = (TabExpansion2 -inputScript $Command -cursorColumn $Command.Length).CompletionMatches.CompletionText
593+
594+
$becauseCompletions = ($Completions -Join ", ")
595+
596+
$Completions | Should -Contain "--name='chocolatey'" -Because $becauseCompletions
597+
}
598+
437599
It "Should list versions for <_> isdependency --version=" -ForEach @('install', 'upgrade') {
438600
$Command = "choco $_ isdependency --version="
439601
$Completions = (TabExpansion2 -inputScript $Command -cursorColumn $Command.Length).CompletionMatches.CompletionText

0 commit comments

Comments
 (0)