completers/rustup_completer/rustup_completer.ps1
|
Set-StrictMode -Version 2.0 function Get-RustupCache { if (Test-Path -LiteralPath variable:script:RustupCompleterCache) { return $script:RustupCompleterCache } $cache = @{ CommandPath = $null HelpByKey = @{} Toolchains = $null Installed = $null Targets = @{} Components = @{} HostTriples = $null Profiles = @('minimal', 'default', 'complete') AutoSelfUpdate = @('enable', 'disable', 'check-only') AutoInstall = @('enable', 'disable') Shells = @('bash', 'elvish', 'fish', 'powershell', 'zsh') CompletionApps = @('rustup', 'cargo') } Set-Variable -Name RustupCompleterCache -Scope Script -Value $cache $cache } function Resolve-RustupCommandPath { $cache = Get-RustupCache if ($cache.CommandPath) { return $cache.CommandPath } $command = Get-Command -Name 'rustup.exe', 'rustup' -ErrorAction Ignore | Select-Object -First 1 if ($command) { $cache.CommandPath = if ($command.Source) { $command.Source } else { $command.Name } } $cache.CommandPath } function Invoke-RustupText { param([string[]]$Arguments) $commandPath = Resolve-RustupCommandPath if (-not $commandPath) { return @() } try { @(& $commandPath @Arguments 2>$null | ForEach-Object { $_ -replace '\e\[[0-9;?]*[ -/]*[@-~]', '' }) } catch { @() } } function Get-RustupHelpDefinition { param([string[]]$CommandPath) $normalizedPath = @($CommandPath) $cache = Get-RustupCache $key = if ($normalizedPath.Count -gt 0) { $normalizedPath -join ' ' } else { '<root>' } if ($cache.HelpByKey.ContainsKey($key)) { return $cache.HelpByKey[$key] } if ($normalizedPath.Count -gt 0 -and $normalizedPath[0] -eq 'help') { # 'rustup help <path>' takes the same subcommand names as '<path>' itself # and no options; 'rustup help --help' only prints a clap error. $target = Get-RustupHelpDefinition -CommandPath @($normalizedPath | Select-Object -Skip 1) $definition = @{ Key = $key CommandPath = $normalizedPath Commands = @($target.Commands) CommandMap = $target.CommandMap Options = @() OptionMap = @{} } $cache.HelpByKey[$key] = $definition return $definition } $arguments = @() if ($normalizedPath.Count -gt 0) { $arguments += $normalizedPath } $arguments += '--help' $lines = @(Invoke-RustupText -Arguments $arguments) $definition = @{ Key = $key CommandPath = $normalizedPath Commands = @() CommandMap = @{} Options = @() OptionMap = @{} } $section = '' foreach ($line in $lines) { if ($line -match '^Commands:$') { $section = 'Commands' continue } if ($line -match '^Options:$') { $section = 'Options' continue } if ($line -match '^[A-Z][A-Za-z ]+:$') { $section = '' continue } switch ($section) { 'Commands' { if ($line -match '^\s{2,}([A-Za-z][\w-]*)\s{2,}(.*)$') { $name = $matches[1] $description = $matches[2].Trim() $entry = [pscustomobject]@{ Name = $name Description = $description } $definition.Commands += $entry $definition.CommandMap[$name] = $entry } } 'Options' { if ($line -match '^\s{2,}(.+?)\s{2,}(.*)$') { $spec = $matches[1].Trim() $description = $matches[2].Trim() $names = @([regex]::Matches($spec, '(?:^|,\s*)((?:--?|\+)[A-Za-z0-9][A-Za-z0-9_-]*|/\?)') | ForEach-Object { $_.Groups[1].Value.Trim() }) if ($names.Count -eq 0) { continue } $takesValue = $spec -match '<[^>]+>' $valuePlaceholder = $null if ($takesValue -and $spec -match '<([^>]+)>') { $valuePlaceholder = $matches[1] } $entry = [pscustomobject]@{ Names = $names PrimaryName = $names[-1] Description = $description TakesValue = $takesValue ValuePlaceholder = $valuePlaceholder } $definition.Options += $entry foreach ($name in $names) { $definition.OptionMap[$name] = $entry } } } } } $cache.HelpByKey[$key] = $definition $definition } function New-RustupCompletionResult { param( [string]$CompletionText, [string]$ListItemText, [string]$ResultType, [string]$ToolTip ) if ([string]::IsNullOrWhiteSpace($ListItemText)) { $ListItemText = $CompletionText } if ([string]::IsNullOrWhiteSpace($ToolTip)) { $ToolTip = $CompletionText } [System.Management.Automation.CompletionResult]::new( $CompletionText, $ListItemText, $ResultType, $ToolTip ) } function ConvertFrom-RustupTypedWord { # The value of a typed word. A word opened with a quote (ASCII or typographic) is read by # the PowerShell tokenizer, which drops the quotes and undoes that quote style's escapes. param([string]$Value) if ($Value -notmatch '^[''"\u2018-\u201E]') { return $Value } $tokens = $null $parseErrors = $null [void][System.Management.Automation.Language.Parser]::ParseInput($Value, [ref]$tokens, [ref]$parseErrors) $tokens[0].Value } function ConvertTo-RustupQuotedValue { # Renders a value as one PowerShell argument: bare when safe and no quote was typed, # otherwise in the quote the user typed (single by default). PowerShell reads ' and # U+2018-U+201B as single quotes and " and U+201C-U+201E as double quotes. param( [string]$Value, [string]$QuoteChar = '', [bool]$AllowLeadingDash = $false ) if ([string]::IsNullOrEmpty($Value)) { return $Value } if (-not $QuoteChar) { if ($Value -notmatch '[\s{}();,|&<>''"`$@#\u2018-\u201E]' -and ($AllowLeadingDash -or $Value -notmatch '^[-\u2013-\u2015]')) { return $Value } $QuoteChar = "'" } if ($QuoteChar -match '^[''\u2018-\u201B]$') { return $QuoteChar + ($Value -replace '([''\u2018-\u201B])', '$1$1') + $QuoteChar } $QuoteChar + ($Value -replace '([`"$\u201C-\u201E])', '`$1') + $QuoteChar } function Remove-RustupOuterQuotes { param([string]$Value) if ($null -eq $Value) { return '' } $Value.Trim([char[]]@([char]34, [char]39)) } function Get-RustupCurrentToken { param( [System.Management.Automation.Language.CommandAst]$CommandAst, [int]$CursorPosition ) # The word as typed up to the cursor, which is what PowerShell replaces. The parser # keeps a quoted word (even unterminated, or with spaces) as one element. foreach ($element in ($CommandAst.CommandElements | Select-Object -Skip 1)) { $extent = $element.Extent if ($extent.StartOffset -lt $CursorPosition -and $CursorPosition -le $extent.EndOffset) { return $extent.Text.Substring(0, $CursorPosition - $extent.StartOffset) } } '' } function Get-RustupTokensBeforeCursor { param( [System.Management.Automation.Language.CommandAst]$CommandAst, [int]$CursorPosition ) $tokens = New-Object System.Collections.Generic.List[string] foreach ($element in $CommandAst.CommandElements | Select-Object -Skip 1) { if ($element.Extent.EndOffset -lt $CursorPosition) { $tokens.Add($element.Extent.Text) } } @($tokens.ToArray()) } function Get-RustupKnownToolchains { $cache = Get-RustupCache if ($cache.Toolchains) { return $cache.Toolchains } $names = New-Object System.Collections.Generic.List[string] foreach ($line in (Invoke-RustupText -Arguments @('toolchain', 'list'))) { if ($line -match '^([^\s]+)') { $value = $matches[1].Trim() if (-not [string]::IsNullOrWhiteSpace($value) -and -not $names.Contains($value)) { $names.Add($value) } } } $cache.Installed = @($names.ToArray()) foreach ($fallback in @('stable', 'beta', 'nightly')) { if (-not $names.Contains($fallback)) { $names.Add($fallback) } } $cache.Toolchains = @($names.ToArray()) $cache.Toolchains } function Resolve-RustupScopeToolchain { param([string]$Name) # Only an installed toolchain scopes a list: 'stable' or '1.95.0' match # their host-qualified names. Anything else keeps the unscoped list, so a # lookup never asks rustup about (or auto-installs) a missing toolchain. $Name = Remove-RustupOuterQuotes -Value $Name if ([string]::IsNullOrWhiteSpace($Name)) { return $null } $null = Get-RustupKnownToolchains foreach ($installed in @((Get-RustupCache).Installed)) { if ($installed -ceq $Name -or $installed.StartsWith($Name + '-', [System.StringComparison]::Ordinal)) { return $Name } } $null } function Get-RustupTargetValues { param( [bool]$InstalledOnly, [string]$Toolchain ) $cache = Get-RustupCache $key = $(if ($InstalledOnly) { 'installed' } else { 'all' }) + '|' + $Toolchain if ($cache.Targets.ContainsKey($key)) { return $cache.Targets[$key] } $args = @('target', 'list') if ($InstalledOnly) { $args += '--installed' } $scopeArgs = if ($Toolchain) { @('--toolchain', $Toolchain) } else { @() } $values = New-Object System.Collections.Generic.List[string] foreach ($line in (Invoke-RustupText -Arguments ($args + $scopeArgs))) { if ($line -match '^([^\s]+)') { $value = $matches[1].Trim() if (-not [string]::IsNullOrWhiteSpace($value) -and -not $values.Contains($value)) { $values.Add($value) } } } if (-not $InstalledOnly -and -not $values.Contains('all')) { $values.Add('all') } $cache.Targets[$key] = @($values.ToArray()) $cache.Targets[$key] } function Get-RustupComponentValues { param( [bool]$InstalledOnly, [string]$Toolchain ) $cache = Get-RustupCache $key = $(if ($InstalledOnly) { 'installed' } else { 'all' }) + '|' + $Toolchain if ($cache.Components.ContainsKey($key)) { return $cache.Components[$key] } $args = @('component', 'list') if ($InstalledOnly) { $args += '--installed' } $scopeArgs = if ($Toolchain) { @('--toolchain', $Toolchain) } else { @() } $values = New-Object System.Collections.Generic.List[string] foreach ($line in (Invoke-RustupText -Arguments ($args + $scopeArgs))) { if ($line -match '^([^\s]+)') { $value = $matches[1].Trim() if (-not [string]::IsNullOrWhiteSpace($value) -and -not $values.Contains($value)) { $values.Add($value) } } } $cache.Components[$key] = @($values.ToArray()) $cache.Components[$key] } function Get-RustupHostTriples { $cache = Get-RustupCache if ($cache.HostTriples) { return $cache.HostTriples } $values = New-Object System.Collections.Generic.List[string] foreach ($toolchain in (Get-RustupKnownToolchains)) { if ($toolchain -match '-([A-Za-z0-9_\.-]+)$') { $hostTriple = $matches[1] if (-not $values.Contains($hostTriple)) { $values.Add($hostTriple) } } } foreach ($target in (Get-RustupTargetValues -InstalledOnly $true)) { if (-not $values.Contains($target)) { $values.Add($target) } } $cache.HostTriples = @($values.ToArray()) $cache.HostTriples } function Get-RustupPathCompletions { # -Attached: the value follows '--opt=' in the same word, so a leading dash cannot be # read as a parameter there. param( [string]$InputPath, [switch]$Attached ) $cleanInput = ConvertFrom-RustupTypedWord -Value $InputPath $quoteChar = if ($InputPath -match '^[''"\u2018-\u201E]') { $InputPath.Substring(0, 1) } else { '' } # The typed directory part (kept exactly as typed, '.\' and './' included) and the leaf. $directoryPart = '' $leaf = '' if (-not [string]::IsNullOrWhiteSpace($cleanInput)) { $directoryPart = $cleanInput.Substring(0, $cleanInput.LastIndexOfAny([char[]]'\/') + 1) if (-not $directoryPart -and $cleanInput -match '^[A-Za-z]:') { $directoryPart = $cleanInput.Substring(0, 2) } $leaf = $cleanInput.Substring($directoryPart.Length) } $parent = if ($directoryPart) { $directoryPart } else { '.' } if (-not (Test-Path -LiteralPath $parent -PathType Container -ErrorAction Ignore)) { return @() } $separator = if ($directoryPart -match '[\\/]$') { $directoryPart.Substring($directoryPart.Length - 1) } else { [string][System.IO.Path]::DirectorySeparatorChar } $items = @(Get-ChildItem -LiteralPath $parent -ErrorAction Ignore) $items = $items | Where-Object { $_.Name -like ([System.Management.Automation.WildcardPattern]::Escape($leaf) + '*') } foreach ($item in ($items | Sort-Object -Property @{ Expression = 'PSIsContainer'; Descending = $true }, Name)) { $pathText = $directoryPart + $item.Name # A bare name starting with a dash would be read as a parameter: anchor it to the # current directory the way PowerShell's own file completion does. if (-not $Attached -and -not $directoryPart -and $pathText -match '^[-\u2013-\u2015]') { $pathText = '.' + $separator + $pathText } if ($item.PSIsContainer) { $pathText += $separator } $quoted = ConvertTo-RustupQuotedValue -Value $pathText -QuoteChar $quoteChar -AllowLeadingDash $Attached.IsPresent $type = if ($item.PSIsContainer) { 'ProviderContainer' } else { 'ParameterValue' } New-RustupCompletionResult -CompletionText $quoted -ListItemText $pathText -ResultType $type -ToolTip $item.FullName } } function Get-RustupValueCatalog { param( [string[]]$ContextPath, [string]$OptionName, [bool]$IsPositional, [int]$PositionalIndex = -1, [string]$Toolchain ) $key = if ($ContextPath.Count -gt 0) { $ContextPath -join ' ' } else { '<root>' } switch ($key) { '<root>' { } 'install' { if ($IsPositional) { return @(Get-RustupKnownToolchains) } switch ($OptionName) { '--profile' { return @((Get-RustupCache).Profiles) } '--target' { return @(Get-RustupTargetValues -InstalledOnly $false) } '--component' { return @(Get-RustupComponentValues -InstalledOnly $false) } } } 'toolchain install' { if ($IsPositional) { return @(Get-RustupKnownToolchains) } switch ($OptionName) { '--profile' { return @((Get-RustupCache).Profiles) } '--target' { return @(Get-RustupTargetValues -InstalledOnly $false) } '--component' { return @(Get-RustupComponentValues -InstalledOnly $false) } } } 'toolchain uninstall' { if ($IsPositional) { return @(Get-RustupKnownToolchains) } } 'uninstall' { if ($IsPositional) { return @(Get-RustupKnownToolchains) } } 'default' { if ($IsPositional) { return @('none') + @(Get-RustupKnownToolchains) } } 'update' { if ($IsPositional) { return @(Get-RustupKnownToolchains) } } 'target add' { if ($IsPositional) { return @(Get-RustupTargetValues -InstalledOnly $false -Toolchain $Toolchain) } if ($OptionName -eq '--toolchain') { return @(Get-RustupKnownToolchains) } } 'target remove' { if ($IsPositional) { return @(Get-RustupTargetValues -InstalledOnly $true -Toolchain $Toolchain) } if ($OptionName -eq '--toolchain') { return @(Get-RustupKnownToolchains) } } 'target list' { if ($OptionName -eq '--toolchain') { return @(Get-RustupKnownToolchains) } } 'component add' { if ($IsPositional) { return @(Get-RustupComponentValues -InstalledOnly $false -Toolchain $Toolchain) } switch ($OptionName) { '--toolchain' { return @(Get-RustupKnownToolchains) } '--target' { return @(Get-RustupTargetValues -InstalledOnly $false -Toolchain $Toolchain) } } } 'component remove' { if ($IsPositional) { return @(Get-RustupComponentValues -InstalledOnly $true -Toolchain $Toolchain) } switch ($OptionName) { '--toolchain' { return @(Get-RustupKnownToolchains) } '--target' { return @(Get-RustupTargetValues -InstalledOnly $true -Toolchain $Toolchain) } } } 'component list' { if ($OptionName -eq '--toolchain') { return @(Get-RustupKnownToolchains) } } 'toolchain link' { if (-not $OptionName -and $IsPositional) { return @('<toolchain-name>') } } 'override set' { if ($IsPositional) { return @(Get-RustupKnownToolchains) } } 'run' { if ($IsPositional -and $PositionalIndex -eq 0) { return @(Get-RustupKnownToolchains) } } 'doc' { if ($OptionName -eq '--toolchain') { return @(Get-RustupKnownToolchains) } } 'which' { if ($OptionName -eq '--toolchain') { return @(Get-RustupKnownToolchains) } if ($IsPositional) { return @('cargo', 'rustc', 'rustdoc', 'rustfmt', 'clippy-driver') } } 'completions' { if ($IsPositional -and $PositionalIndex -eq 0) { return @((Get-RustupCache).Shells) } if ($IsPositional -and $PositionalIndex -eq 1) { return @((Get-RustupCache).CompletionApps) } } 'set profile' { if ($IsPositional) { return @((Get-RustupCache).Profiles) } } 'set auto-self-update' { if ($IsPositional) { return @((Get-RustupCache).AutoSelfUpdate) } } 'set auto-install' { if ($IsPositional) { return @((Get-RustupCache).AutoInstall) } } 'set default-host' { if ($IsPositional) { return @(Get-RustupHostTriples) } } } @() } function Get-RustupPlaceholderText { param( [string[]]$ContextPath, [string]$OptionName, [bool]$IsPositional, [int]$PositionalIndex ) $key = if ($ContextPath.Count -gt 0) { $ContextPath -join ' ' } else { '<root>' } switch ($key) { '<root>' { } 'toolchain link' { if ($IsPositional -and $PositionalIndex -eq 0) { return '<toolchain-name>' } if ($IsPositional -and $PositionalIndex -eq 1) { return '<path>' } } 'override set' { if ($OptionName -eq '--path') { return '<path>' } } 'override unset' { if ($OptionName -eq '--path') { return '<path>' } } 'run' { if ($IsPositional -and $PositionalIndex -eq 1) { return '<command>' } if ($IsPositional -and $PositionalIndex -ge 2) { return '<argument>' } } 'doc' { if ($IsPositional) { return '<topic>' } } } if ($OptionName -eq '--path') { return '<path>' } $null } function Get-RustupCommandState { param([string[]]$TokensBeforeCurrent) $path = New-Object System.Collections.Generic.List[string] $positionals = New-Object System.Collections.Generic.List[string] $pendingOption = $null $pendingOptionPath = @() $optionValues = @{} $toolchainOverride = $null foreach ($rawToken in $TokensBeforeCurrent) { $token = Remove-RustupOuterQuotes -Value $rawToken if ([string]::IsNullOrWhiteSpace($token)) { continue } if ($pendingOption) { $optionValues[$pendingOption] = $token $pendingOption = $null $pendingOptionPath = @() continue } $definition = Get-RustupHelpDefinition -CommandPath @($path.ToArray()) if ($token.StartsWith('-') -or $token -eq '/?') { $attached = [regex]::Match($token, '^(?<name>--[^=]+)=(?<value>.*)$') if ($attached.Success) { # --opt=value carries its value, so nothing is pending afterwards. $optionValues[$attached.Groups['name'].Value] = $attached.Groups['value'].Value } elseif ($definition.OptionMap.ContainsKey($token)) { $entry = $definition.OptionMap[$token] if ($entry.TakesValue) { $pendingOption = $entry.PrimaryName $pendingOptionPath = @($path.ToArray()) } } continue } if ($token.StartsWith('+') -and $path.Count -eq 0 -and $positionals.Count -eq 0 -and -not $toolchainOverride) { # '+toolchain' selects the toolchain for the rest of the line; it is # not an operand and must not hide the subcommands. $toolchainOverride = $token continue } if ($definition.CommandMap.ContainsKey($token) -and $positionals.Count -eq 0) { $path.Add($token) continue } $positionals.Add($token) } [pscustomobject]@{ ContextPath = @($path.ToArray()) Positionals = @($positionals.ToArray()) PendingOption = $pendingOption PendingOptionPath = $pendingOptionPath OptionValues = $optionValues ToolchainOverride = $toolchainOverride } } function Get-RustupNamedCompletions { param( [string[]]$Values, [string]$CurrentWord, [string]$ToolTip, [string]$ResultType ) $cleanCurrent = Remove-RustupOuterQuotes -Value $CurrentWord foreach ($value in $Values) { if ($value.StartsWith($cleanCurrent, [System.StringComparison]::OrdinalIgnoreCase)) { New-RustupCompletionResult -CompletionText $value -ListItemText $value -ResultType $ResultType -ToolTip $ToolTip } } } function Get-RustupValueCompletions { param( [string[]]$ContextPath, [string]$OptionName, [bool]$IsPositional, [int]$PositionalIndex, [string]$CurrentWord, [string]$Toolchain ) $values = @(Get-RustupValueCatalog -ContextPath $ContextPath -OptionName $OptionName -IsPositional $IsPositional -PositionalIndex $PositionalIndex -Toolchain $Toolchain) if ($values.Count -gt 0) { return @(Get-RustupNamedCompletions -Values $values -CurrentWord $CurrentWord -ToolTip 'rustup value' -ResultType 'ParameterValue') } $placeholder = Get-RustupPlaceholderText -ContextPath $ContextPath -OptionName $OptionName -IsPositional $IsPositional -PositionalIndex $PositionalIndex if ($placeholder) { if ([string]::IsNullOrWhiteSpace($CurrentWord) -or $placeholder.StartsWith($CurrentWord, [System.StringComparison]::OrdinalIgnoreCase)) { return @( New-RustupCompletionResult -CompletionText $placeholder -ListItemText $placeholder -ResultType 'ParameterValue' -ToolTip ('rustup placeholder for ' + $placeholder) ) } } @() } function Complete-Rustup { param( [string]$wordToComplete, [System.Management.Automation.Language.CommandAst]$commandAst, [int]$cursorPosition ) $currentWord = Get-RustupCurrentToken -CommandAst $commandAst -CursorPosition $cursorPosition $tokensBeforeCurrent = @(Get-RustupTokensBeforeCursor -CommandAst $commandAst -CursorPosition $cursorPosition) if (($tokensBeforeCurrent.Count -eq 0) -and ($currentWord -match '^(?i)rustup(?:\.exe)?$')) { $currentWord = '' } elseif ([string]::IsNullOrEmpty($wordToComplete) -and -not [string]::IsNullOrEmpty($currentWord)) { $tokensBeforeCurrent = @($tokensBeforeCurrent + $currentWord) $currentWord = '' } $state = Get-RustupCommandState -TokensBeforeCurrent $tokensBeforeCurrent $contextPath = @( if ($state.PendingOption) { $state.PendingOptionPath } else { $state.ContextPath } ) $definition = Get-RustupHelpDefinition -CommandPath $contextPath # '--toolchain <name>' wins over a leading '+name'; either one scopes the # component and target lists. $scopeToolchain = if ($state.OptionValues.ContainsKey('--toolchain')) { $state.OptionValues['--toolchain'] } elseif ($state.ToolchainOverride) { $state.ToolchainOverride.Substring(1) } $scopeToolchain = Resolve-RustupScopeToolchain -Name $scopeToolchain if ($state.PendingOption) { if ($state.PendingOption -eq '--path') { return @(Get-RustupPathCompletions -InputPath $currentWord) } return @(Get-RustupValueCompletions -ContextPath $contextPath -OptionName $state.PendingOption -IsPositional $false -PositionalIndex -1 -CurrentWord $currentWord -Toolchain $scopeToolchain) } if (-not [string]::IsNullOrEmpty($currentWord) -and $currentWord.StartsWith('+') -and $state.ContextPath.Count -eq 0 -and $state.Positionals.Count -eq 0 -and -not $state.ToolchainOverride) { $typedToolchain = $currentWord.Substring(1) $overrideResults = New-Object System.Collections.Generic.List[object] foreach ($toolchain in (Get-RustupKnownToolchains)) { if ($toolchain.StartsWith($typedToolchain, [System.StringComparison]::OrdinalIgnoreCase)) { $overrideResults.Add((New-RustupCompletionResult -CompletionText ('+' + $toolchain) -ListItemText ('+' + $toolchain) -ResultType 'ParameterValue' -ToolTip 'Release channel or custom toolchain to use for this invocation.')) } } return @($overrideResults.ToArray()) } if (-not [string]::IsNullOrEmpty($currentWord) -and ($currentWord.StartsWith('-') -or $currentWord -eq '/')) { $cleanCurrent = Remove-RustupOuterQuotes -Value $currentWord $attached = [regex]::Match($currentWord, '^(?<name>--[^=]+)=(?<value>.*)$') if ($attached.Success) { # Attached form: complete the value while keeping the '--opt=' prefix. $attachedName = $attached.Groups['name'].Value $attachedValue = $attached.Groups['value'].Value $attachedPrefix = $attachedName + '=' if (-not $definition.OptionMap.ContainsKey($attachedName) -or -not $definition.OptionMap[$attachedName].TakesValue) { return @() } $primaryName = $definition.OptionMap[$attachedName].PrimaryName if ($primaryName -eq '--path') { return @(Get-RustupPathCompletions -InputPath $attachedValue -Attached | ForEach-Object { New-RustupCompletionResult -CompletionText ($attachedPrefix + $_.CompletionText) -ListItemText $_.ListItemText -ResultType $_.ResultType -ToolTip $_.ToolTip }) } return @(Get-RustupValueCompletions -ContextPath $contextPath -OptionName $primaryName -IsPositional $false -PositionalIndex -1 -CurrentWord $attachedValue -Toolchain $scopeToolchain | ForEach-Object { New-RustupCompletionResult -CompletionText ($attachedPrefix + $_.CompletionText) -ListItemText $_.ListItemText -ResultType $_.ResultType -ToolTip $_.ToolTip }) } $optionResults = New-Object System.Collections.Generic.List[object] foreach ($option in $definition.Options) { foreach ($name in $option.Names) { if ($name.StartsWith($cleanCurrent, [System.StringComparison]::OrdinalIgnoreCase)) { $optionResults.Add((New-RustupCompletionResult -CompletionText $name -ListItemText $name -ResultType 'ParameterName' -ToolTip $option.Description)) } } } return @($optionResults.ToArray()) } if (($state.ContextPath -join ' ') -eq 'toolchain link' -and $state.Positionals.Count -eq 1) { return @(Get-RustupPathCompletions -InputPath $currentWord) } if ([string]::IsNullOrEmpty($currentWord) -and (($state.ContextPath -join ' ') -eq 'run') -and $state.Positionals.Count -ge 2) { return @( New-RustupCompletionResult -CompletionText '<argument>' -ListItemText '<argument>' -ResultType 'ParameterValue' -ToolTip 'Additional argument for the command run through rustup.' ) } $results = New-Object System.Collections.Generic.List[object] $valueResults = @(Get-RustupValueCompletions -ContextPath $state.ContextPath -OptionName $null -IsPositional $true -PositionalIndex $state.Positionals.Count -CurrentWord $currentWord -Toolchain $scopeToolchain) foreach ($item in $valueResults) { $results.Add($item) } if ($state.Positionals.Count -eq 0) { foreach ($command in $definition.Commands) { if ($command.Name.StartsWith($currentWord, [System.StringComparison]::OrdinalIgnoreCase)) { $results.Add((New-RustupCompletionResult -CompletionText $command.Name -ListItemText $command.Name -ResultType 'ParameterValue' -ToolTip $command.Description)) } } } if ([string]::IsNullOrEmpty($currentWord)) { foreach ($option in $definition.Options) { foreach ($name in $option.Names) { $results.Add((New-RustupCompletionResult -CompletionText $name -ListItemText $name -ResultType 'ParameterName' -ToolTip $option.Description)) } } } @($results.ToArray()) } Register-ArgumentCompleter -Native -CommandName @('rustup', 'rustup.exe') -ScriptBlock { param($wordToComplete, $commandAst, $cursorPosition) Complete-Rustup -wordToComplete $wordToComplete -commandAst $commandAst -cursorPosition $cursorPosition } |