2023-03-06 02:28:31 +00:00
// Copyright 2013-2023 The Cobra Authors
2022-09-16 13:55:56 +02:00
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
2018-12-17 23:01:34 +00:00
// The generated scripts require PowerShell v5.0+ (which comes Windows 10, but
// can be downloaded separately for windows 7 or 8.1).
package cobra
import (
"bytes"
"fmt"
"io"
"os"
2022-10-03 13:06:04 -04:00
"strings"
2018-12-17 23:01:34 +00:00
)
2021-02-08 00:08:50 +00:00
func genPowerShellComp ( buf io . StringWriter , name string , includeDesc bool ) {
2022-10-03 13:06:04 -04:00
// Variables should not contain a '-' or ':' character
nameForVar := name
2024-04-01 12:42:08 +00:00
nameForVar = strings . ReplaceAll ( nameForVar , "-" , "_" )
nameForVar = strings . ReplaceAll ( nameForVar , ":" , "_" )
2022-10-03 13:06:04 -04:00
2020-12-29 15:57:32 +01:00
compCmd := ShellCompRequestCmd
if ! includeDesc {
compCmd = ShellCompNoDescRequestCmd
2018-12-17 23:01:34 +00:00
}
2021-02-08 00:08:50 +00:00
WriteStringAndCheck ( buf , fmt . Sprintf ( ` # powershell completion for % - 36 [ 1 ] s - * - shell - script - * -
2018-12-17 23:01:34 +00:00
2020-12-29 15:57:32 +01:00
function __ % [ 1 ] s_debug {
if ( $ env : BASH_COMP_DEBUG_FILE ) {
"$args" | Out - File - Append - FilePath "$env:BASH_COMP_DEBUG_FILE"
}
2018-12-17 23:01:34 +00:00
}
2020-12-29 15:57:32 +01:00
filter __ % [ 1 ] s_escapeStringWithSpecialChars {
` +" $_ -replace '\\s|#|@|\\$|;|,|''|\\ { |\\}|\\(|\\)|\"| ` | \ \ || < | > | & ',' ` $&'"+ `
2018-12-17 23:01:34 +00:00
}
2023-06-19 18:16:18 +02:00
[ scriptblock ] $ { __ % [ 2 ] sCompleterBlock } = {
2020-12-29 15:57:32 +01:00
param (
$ WordToComplete ,
$ CommandAst ,
$ CursorPosition
)
# Get the current command line and convert into a string
$ Command = $ CommandAst . CommandElements
$ Command = "$Command"
__ % [ 1 ] s_debug ""
__ % [ 1 ] s_debug "========= starting completion logic =========="
__ % [ 1 ] s_debug "WordToComplete: $WordToComplete Command: $Command CursorPosition: $CursorPosition"
# The user could have moved the cursor backwards on the command - line .
# We need to trigger completion from the $ CursorPosition location , so we need
# to truncate the command - line ( $ Command ) up to the $ CursorPosition location .
# Make sure the $ Command is longer then the $ CursorPosition before we truncate .
2021-02-08 00:08:50 +00:00
# This happens because the $ Command does not include the last space .
2020-12-29 15:57:32 +01:00
if ( $ Command . Length - gt $ CursorPosition ) {
$ Command = $ Command . Substring ( 0 , $ CursorPosition )
}
2021-12-07 23:57:57 +01:00
__ % [ 1 ] s_debug "Truncated command: $Command"
2021-02-08 00:08:50 +00:00
2022-10-03 13:06:04 -04:00
$ ShellCompDirectiveError = % [ 4 ] d
$ ShellCompDirectiveNoSpace = % [ 5 ] d
$ ShellCompDirectiveNoFileComp = % [ 6 ] d
$ ShellCompDirectiveFilterFileExt = % [ 7 ] d
$ ShellCompDirectiveFilterDirs = % [ 8 ] d
2023-02-25 20:57:12 +00:00
$ ShellCompDirectiveKeepOrder = % [ 9 ] d
2020-12-29 15:57:32 +01:00
2021-12-07 23:57:57 +01:00
# Prepare the command to request completions for the program .
2020-12-29 15:57:32 +01:00
# Split the command at the first space to separate the program and arguments .
$ Program , $ Arguments = $ Command . Split ( " " , 2 )
2022-06-15 20:08:16 -04:00
2022-10-03 13:06:04 -04:00
$ RequestComp = "$Program %[3]s $Arguments"
2020-12-29 15:57:32 +01:00
__ % [ 1 ] s_debug "RequestComp: $RequestComp"
2021-02-08 00:08:50 +00:00
# we cannot use $ WordToComplete because it
2020-12-29 15:57:32 +01:00
# has the wrong values if the cursor was moved
# so use the last argument
if ( $ WordToComplete - ne "" ) {
$ WordToComplete = $ Arguments . Split ( " " ) [ - 1 ]
}
__ % [ 1 ] s_debug "New WordToComplete: $WordToComplete"
# Check for flag with equal sign
$ IsEqualFlag = ( $ WordToComplete - Like "--*=*" )
if ( $ IsEqualFlag ) {
__ % [ 1 ] s_debug "Completing equal sign flag"
# Remove the flag part
$ Flag , $ WordToComplete = $ WordToComplete . Split ( "=" , 2 )
}
if ( $ WordToComplete - eq "" - And ( - Not $ IsEqualFlag ) ) {
# If the last parameter is complete ( there is a space following it )
# We add an extra empty parameter so we can indicate this to the go method .
__ % [ 1 ] s_debug "Adding extra empty parameter"
2023-02-25 22:30:37 +01:00
# PowerShell 7.2 + changed the way how the arguments are passed to executables ,
# so for pre - 7.2 or when Legacy argument passing is enabled we need to use
` +" # ` \ "`\" to pass an empty argument, a \"\" or '' does not work!!!" + `
if ( $ PSVersionTable . PsVersion - lt [ version ] ' 7.2 .0 ' - or
( $ PSVersionTable . PsVersion - lt [ version ] ' 7.3 .0 ' - and - not [ ExperimentalFeature ] : : IsEnabled ( "PSNativeCommandArgumentPassing" ) ) - or
( ( $ PSVersionTable . PsVersion - ge [ version ] ' 7.3 .0 ' - or [ ExperimentalFeature ] : : IsEnabled ( "PSNativeCommandArgumentPassing" ) ) - and
$ PSNativeCommandArgumentPassing - eq ' Legacy ' ) ) {
` +" $RequestComp=\"$RequestComp\" + ' ` \ "`\"'" + `
} else {
$ RequestComp = "$RequestComp" + ' "" '
}
2020-12-29 15:57:32 +01:00
}
__ % [ 1 ] s_debug "Calling $RequestComp"
2022-06-15 20:08:16 -04:00
# First disable ActiveHelp which is not supported for Powershell
2023-06-19 18:16:18 +02:00
$ { env : % [ 10 ] s } = 0
2022-06-15 20:08:16 -04:00
2020-12-29 15:57:32 +01:00
# call the command store the output in $ out and redirect stderr and stdout to null
# $ Out is an array contains each line per element
Invoke - Expression - OutVariable out "$RequestComp" 2 > & 1 | Out - Null
# get directive from last line
[ int ] $ Directive = $ Out [ - 1 ] . TrimStart ( ':' )
if ( $ Directive - eq "" ) {
# There is no directive specified
$ Directive = 0
}
__ % [ 1 ] s_debug "The completion directive is: $Directive"
2021-02-08 00:08:50 +00:00
2020-12-29 15:57:32 +01:00
# remove directive ( last element ) from out
$ Out = $ Out | Where - Object { $ _ - ne $ Out [ - 1 ] }
__ % [ 1 ] s_debug "The completions are: $Out"
if ( ( $ Directive - band $ ShellCompDirectiveError ) - ne 0 ) {
# Error code . No completion .
__ % [ 1 ] s_debug "Received error from custom completion go code"
return
}
$ Longest = 0
2022-11-25 21:47:20 +01:00
[ Array ] $ Values = $ Out | ForEach - Object {
2020-12-29 15:57:32 +01:00
# Split the output in name and description
` +" $Name, $Description = $_.Split(\" ` t \ ",2)" + `
__ % [ 1 ] s_debug "Name: $Name Description: $Description"
# Look for the longest completion so that we can format things nicely
if ( $ Longest - lt $ Name . Length ) {
$ Longest = $ Name . Length
}
# Set the description to a one space string if there is none set .
# This is needed because the CompletionResult does not accept an empty string as argument
if ( - Not $ Description ) {
$ Description = " "
}
2024-11-04 01:45:01 +01:00
New - Object - TypeName PSCustomObject - Property @ {
Name = "$Name"
Description = "$Description"
}
2020-12-29 15:57:32 +01:00
}
$ Space = " "
if ( ( $ Directive - band $ ShellCompDirectiveNoSpace ) - ne 0 ) {
# remove the space here
__ % [ 1 ] s_debug "ShellCompDirectiveNoSpace is called"
$ Space = ""
}
2021-02-08 00:08:50 +00:00
if ( ( ( $ Directive - band $ ShellCompDirectiveFilterFileExt ) - ne 0 ) - or
2020-12-29 15:57:32 +01:00
( ( $ Directive - band $ ShellCompDirectiveFilterDirs ) - ne 0 ) ) {
__ % [ 1 ] s_debug "ShellCompDirectiveFilterFileExt ShellCompDirectiveFilterDirs are not supported"
# return here to prevent the completion of the extensions
return
}
$ Values = $ Values | Where - Object {
# filter the result
$ _ . Name - like "$WordToComplete*"
2018-12-17 23:01:34 +00:00
2021-05-11 08:19:33 +09:00
# Join the flag back if we have an equal sign flag
2020-12-29 15:57:32 +01:00
if ( $ IsEqualFlag ) {
__ % [ 1 ] s_debug "Join the equal sign flag back to the completion value"
$ _ . Name = $ Flag + "=" + $ _ . Name
}
2021-02-08 00:08:50 +00:00
}
2020-12-29 15:57:32 +01:00
2023-02-25 20:57:12 +00:00
# we sort the values in ascending order by name if keep order isn ' t passed
if ( ( $ Directive - band $ ShellCompDirectiveKeepOrder ) - eq 0 ) {
$ Values = $ Values | Sort - Object - Property Name
}
2021-05-03 18:25:30 +02:00
if ( ( $ Directive - band $ ShellCompDirectiveNoFileComp ) - ne 0 ) {
__ % [ 1 ] s_debug "ShellCompDirectiveNoFileComp is called"
if ( $ Values . Length - eq 0 ) {
# Just print an empty string here so the
# shell does not start to complete paths .
# We cannot use CompletionResult here because
# it does not accept an empty string as argument .
""
return
}
}
2020-12-29 15:57:32 +01:00
# Get the current mode
$ Mode = ( Get - PSReadLineKeyHandler | Where - Object { $ _ . Key - eq "Tab" } ) . Function
__ % [ 1 ] s_debug "Mode: $Mode"
$ Values | ForEach - Object {
2021-05-11 08:19:33 +09:00
# store temporary because switch will overwrite $ _
2020-12-29 15:57:32 +01:00
$ comp = $ _
2021-02-09 14:08:42 -07:00
# PowerShell supports three different completion modes
2020-12-29 15:57:32 +01:00
# - TabCompleteNext ( default windows style - on each key press the next option is displayed )
# - Complete ( works like bash )
# - MenuComplete ( works like zsh )
# You set the mode with Set - PSReadLineKeyHandler - Key Tab - Function < mode >
# CompletionResult Arguments :
# 1 ) CompletionText text to be used as the auto completion result
# 2 ) ListItemText text to be displayed in the suggestion list
# 3 ) ResultType type of completion result
# 4 ) ToolTip text for the tooltip with details about the object
switch ( $ Mode ) {
# bash like
"Complete" {
if ( $ Values . Length - eq 1 ) {
__ % [ 1 ] s_debug "Only one completion left"
2018-12-17 23:01:34 +00:00
2020-12-29 15:57:32 +01:00
# insert space after value
2024-11-04 01:45:01 +01:00
$ CompletionText = $ ( $ comp . Name | __ % [ 1 ] s_escapeStringWithSpecialChars ) + $ Space
if ( $ ExecutionContext . SessionState . LanguageMode - eq "FullLanguage" ) {
[ System . Management . Automation . CompletionResult ] : : new ( $ CompletionText , "$($comp.Name)" , ' ParameterValue ' , "$($comp.Description)" )
} else {
$ CompletionText
}
2020-12-29 15:57:32 +01:00
} else {
# Add the proper number of spaces to align the descriptions
while ( $ comp . Name . Length - lt $ Longest ) {
$ comp . Name = $ comp . Name + " "
}
# Check for empty description and only add parentheses if needed
if ( $ ( $ comp . Description ) - eq " " ) {
$ Description = ""
} else {
$ Description = " ($($comp.Description))"
}
2024-11-04 01:45:01 +01:00
$ CompletionText = "$($comp.Name)$Description"
if ( $ ExecutionContext . SessionState . LanguageMode - eq "FullLanguage" ) {
[ System . Management . Automation . CompletionResult ] : : new ( $ CompletionText , "$($comp.Name)$Description" , ' ParameterValue ' , "$($comp.Description)" )
} else {
$ CompletionText
}
2020-12-29 15:57:32 +01:00
}
}
# zsh like
"MenuComplete" {
# insert space after value
2021-02-08 00:08:50 +00:00
# MenuComplete will automatically show the ToolTip of
2020-12-29 15:57:32 +01:00
# the highlighted value at the bottom of the suggestions .
2024-11-04 01:45:01 +01:00
$ CompletionText = $ ( $ comp . Name | __ % [ 1 ] s_escapeStringWithSpecialChars ) + $ Space
if ( $ ExecutionContext . SessionState . LanguageMode - eq "FullLanguage" ) {
[ System . Management . Automation . CompletionResult ] : : new ( $ CompletionText , "$($comp.Name)" , ' ParameterValue ' , "$($comp.Description)" )
} else {
$ CompletionText
}
2020-12-29 15:57:32 +01:00
}
# TabCompleteNext and in case we get something unknown
Default {
2021-02-08 00:08:50 +00:00
# Like MenuComplete but we don ' t want to add a space here because
2020-12-29 15:57:32 +01:00
# the user need to press space anyway to get the completion .
2021-12-08 00:06:52 +01:00
# Description will not be shown because that ' s not possible with TabCompleteNext
2024-11-04 01:45:01 +01:00
$ CompletionText = $ ( $ comp . Name | __ % [ 1 ] s_escapeStringWithSpecialChars )
if ( $ ExecutionContext . SessionState . LanguageMode - eq "FullLanguage" ) {
[ System . Management . Automation . CompletionResult ] : : new ( $ CompletionText , "$($comp.Name)" , ' ParameterValue ' , "$($comp.Description)" )
} else {
$ CompletionText
}
2020-12-29 15:57:32 +01:00
}
}
}
}
2022-10-03 13:06:04 -04:00
2023-06-19 18:16:18 +02:00
Register - ArgumentCompleter - CommandName ' % [ 1 ] s ' - ScriptBlock $ { __ % [ 2 ] sCompleterBlock }
2022-10-03 13:06:04 -04:00
` , name , nameForVar , compCmd ,
2020-12-29 15:57:32 +01:00
ShellCompDirectiveError , ShellCompDirectiveNoSpace , ShellCompDirectiveNoFileComp ,
2023-02-25 20:57:12 +00:00
ShellCompDirectiveFilterFileExt , ShellCompDirectiveFilterDirs , ShellCompDirectiveKeepOrder , activeHelpEnvVar ( name ) ) )
2020-12-29 15:57:32 +01:00
}
2025-04-26 20:38:36 +00:00
// genPowerShellCompletion generates PowerShell completion script for the command.
// It writes the generated script to the provided writer and returns any errors encountered.
2020-12-29 15:57:32 +01:00
func ( c * Command ) genPowerShellCompletion ( w io . Writer , includeDesc bool ) error {
buf := new ( bytes . Buffer )
genPowerShellComp ( buf , c . Name ( ) , includeDesc )
2018-12-17 23:01:34 +00:00
_ , err := buf . WriteTo ( w )
return err
}
2025-04-26 20:38:36 +00:00
// genPowerShellCompletionFile generates a PowerShell completion file for the command.
//
// Parameters:
// - filename: The name of the file to generate.
// - includeDesc: A boolean indicating whether to include descriptions in the completion file.
//
// Returns:
// - error: If an error occurs during file creation or completion generation, it returns the error. Otherwise, it returns nil.
2020-12-29 15:57:32 +01:00
func ( c * Command ) genPowerShellCompletionFile ( filename string , includeDesc bool ) error {
2018-12-17 23:01:34 +00:00
outFile , err := os . Create ( filename )
if err != nil {
return err
}
defer outFile . Close ( )
2020-12-29 15:57:32 +01:00
return c . genPowerShellCompletion ( outFile , includeDesc )
}
2025-04-26 20:38:36 +00:00
// GenPowerShellCompletionFile generates a PowerShell completion file for the command without including descriptions.
//
// Parameters:
// - filename: The name of the file to generate.
//
// Returns:
// - An error if an error occurs during the generation process.
2020-12-29 15:57:32 +01:00
func ( c * Command ) GenPowerShellCompletionFile ( filename string ) error {
return c . genPowerShellCompletionFile ( filename , false )
}
2025-04-26 20:38:36 +00:00
// GenPowerShellCompletion generates a PowerShell completion script without descriptions
// and writes it to the provided writer. It returns an error if the operation fails.
2020-12-29 15:57:32 +01:00
func ( c * Command ) GenPowerShellCompletion ( w io . Writer ) error {
return c . genPowerShellCompletion ( w , false )
}
2025-04-26 20:38:36 +00:00
// GenPowerShellCompletionFileWithDesc generates a PowerShell completion file for the command with detailed descriptions. It takes a filename as input and returns an error if any occurs during the generation process. The boolean parameter indicates whether to include descriptions in the generated file.
2020-12-29 15:57:32 +01:00
func ( c * Command ) GenPowerShellCompletionFileWithDesc ( filename string ) error {
return c . genPowerShellCompletionFile ( filename , true )
}
2025-04-26 20:38:36 +00:00
// GenPowerShellCompletionWithDesc generates a PowerShell completion file with descriptions and writes it to the provided writer. It takes a writer as an argument and returns an error if any occurs during the generation process.
2020-12-29 15:57:32 +01:00
func ( c * Command ) GenPowerShellCompletionWithDesc ( w io . Writer ) error {
return c . genPowerShellCompletion ( w , true )
2018-12-17 23:01:34 +00:00
}