completers/cargo_clippy_completer/cargo_clippy_completer.ps1

# cargo-clippy native argument completer for PowerShell
# Help-driven completer for cargo-clippy.exe using local cargo/clippy help text.

Set-StrictMode -Version 2.0

if ($true) {
function Get-CargoClippyCompletionCache {
    $cache = Get-Variable -Name CargoClippyCompletionCache -Scope Script -ErrorAction Ignore
    if ($null -ne $cache) {
        return $cache.Value
    }

    $newCache = @{
        Initialized          = $false
        CargoClippyCommand   = $null
        CargoCommand         = $null
        CommandOptions       = @()
        CommandDescriptions  = @{}
        CommandValueMap      = @{}
        PathOptions          = @('-m', '--manifest-path', '--target-dir')
        LintOptions          = @('-W', '--warn', '-A', '--allow', '-D', '--deny', '-F', '--forbid')
        LintDescriptions     = @{}
        LintValueMap         = @{}
        UnstableFlags        = @()
        LintNames            = @()
        LintNamesLoaded      = $false
        TargetTriples        = @()
        TargetTriplesLoaded  = $false
        ManifestFacts        = $null
        ManifestLoadedFor    = $null
    }

    Set-Variable -Name CargoClippyCompletionCache -Scope Script -Value $newCache
    $newCache
}

function Resolve-CargoClippyCommandName {
    $cache = Get-CargoClippyCompletionCache
    if ($cache.CargoClippyCommand) {
        return $cache.CargoClippyCommand
    }

    $command = Get-Command -Name cargo-clippy.exe, cargo-clippy -ErrorAction SilentlyContinue | Select-Object -First 1
    if ($command) {
        $cache.CargoClippyCommand = if ($command.Source) { $command.Source } else { $command.Name }
    }

    $cache.CargoClippyCommand
}

function Resolve-CargoCommandName {
    $cache = Get-CargoClippyCompletionCache
    if ($cache.CargoCommand) {
        return $cache.CargoCommand
    }

    $command = Get-Command -Name cargo.exe, cargo -ErrorAction SilentlyContinue | Select-Object -First 1
    if ($command) {
        $cache.CargoCommand = if ($command.Source) { $command.Source } else { $command.Name }
    }

    $cache.CargoCommand
}

function Invoke-CargoClippyText {
    param([string[]]$Arguments)

    $commandName = Resolve-CargoClippyCommandName
    if (-not $commandName) {
        return @()
    }

    try {
        @(& $commandName @Arguments 2>&1 | ForEach-Object { $_.ToString() -replace '\e\[[0-9;?]*[ -/]*[@-~]', '' })
    } catch {
        @()
    }
}

function Invoke-CargoText {
    param([string[]]$Arguments)

    $commandName = Resolve-CargoCommandName
    if (-not $commandName) {
        return @()
    }

    try {
        @(& $commandName @Arguments 2>&1 | ForEach-Object { $_.ToString() -replace '\e\[[0-9;?]*[ -/]*[@-~]', '' })
    } catch {
        @()
    }
}

function New-CargoClippyCompletionResult {
    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 Remove-CargoClippyOuterQuotes {
    param([string]$Value)

    if ($null -eq $Value) {
        return ''
    }

    $Value.Trim([char[]]@([char]34, [char]39))
}

function ConvertTo-CargoClippyQuotedPath {
    param(
        [string]$Value,
        [bool]$AlwaysQuote = $false
    )

    if ([string]::IsNullOrWhiteSpace($Value)) {
        return $Value
    }

    if (($AlwaysQuote -or $Value -match '\s') -and -not ($Value.StartsWith('"') -and $Value.EndsWith('"'))) {
        $escaped = $Value.Replace('`', '``').Replace('"', '`"')
        return '"' + $escaped + '"'
    }

    $Value
}

function Get-CargoClippyPathCompletions {
    param([string]$InputPath)

    $cleanInput = Remove-CargoClippyOuterQuotes -Value $InputPath
    $alwaysQuote = -not [string]::IsNullOrEmpty($InputPath) -and ($InputPath.StartsWith('"') -or $InputPath.StartsWith("'"))

    [System.Management.Automation.CompletionCompleters]::CompleteFilename($cleanInput) |
        ForEach-Object {
            $completionText = ConvertTo-CargoClippyQuotedPath -Value $_.CompletionText -AlwaysQuote $alwaysQuote
            New-CargoClippyCompletionResult -CompletionText $completionText -ListItemText $_.ListItemText -ResultType $_.ResultType -ToolTip $_.ToolTip
        }
}

function Get-CargoClippyCurrentWord {
    param(
        [string]$Line,
        [int]$CursorPosition,
        [string]$Fallback
    )

    if ([string]::IsNullOrWhiteSpace($Line)) {
        return $Fallback
    }

    $safeCursor = [Math]::Min([Math]::Max($CursorPosition, 0), $Line.Length)
    $prefix = $Line.Substring(0, $safeCursor)
    if ($prefix -match '\s$') {
        return ''
    }

    $parts = @([regex]::Matches($prefix, '"[^"]*"|''[^'']*''|\S+') | ForEach-Object { $_.Value })
    if ($parts.Count -gt 0) {
        return $parts[-1]
    }

    $Fallback
}

function Get-CargoClippyArgumentTokens {
    param(
        [System.Management.Automation.Language.CommandAst]$CommandAst,
        [int]$CursorPosition
    )

    $tokens = @()
    foreach ($element in $CommandAst.CommandElements | Select-Object -Skip 1) {
        if ($element.Extent.EndOffset -lt $CursorPosition) {
            $tokens += $element.Extent.Text
        }
    }

    $tokens
}

function Get-CargoClippyOptionMetadataFromLines {
    param([string[]]$Lines)

    # -F/--forbid and -F/--features are different options, so the table has to be case-exact.
    $result = [System.Collections.Generic.Dictionary[string, string]]::new([System.StringComparer]::Ordinal)

    foreach ($line in $Lines) {
        $trimmedLine = $line.TrimStart()
        $parts = $trimmedLine -split '\s{2,}', 2
        if ($parts.Count -lt 2) {
            continue
        }

        $spec = $parts[0].Trim()
        $description = $parts[1].Trim()
        if (-not $spec.StartsWith('-')) {
            continue
        }

        # clippy writes its lint-level pairs as '-W / --warn [LINT]', cargo writes '-W, --warn'.
        $tokens = @([regex]::Matches($spec, '(?:^|,\s*|\s*/\s*)(-[A-Za-z]|--[A-Za-z0-9][A-Za-z0-9\-]*)') | ForEach-Object { $_.Groups[1].Value })
        if ($tokens.Count -eq 0) {
            continue
        }

        foreach ($token in $tokens) {
            if (-not $result.ContainsKey($token)) {
                $result[$token] = $description
            }
        }
    }

    $result
}

function Get-CargoClippyCommandValueMap {
    @{
        '--color'          = @('auto', 'always', 'never')
        '--config'         = @('<KEY=VALUE>', '<path>')
        '--explain'        = @('<lint>')
        '--message-format' = @('human', 'short', 'json', 'json-diagnostic-short', 'json-diagnostic-rendered-ansi', 'json-render-diagnostics')
        '--package'        = @('<package-spec>')
        '-p'               = @('<package-spec>')
        '--exclude'        = @('<package-spec>')
        '--bin'            = @('<target-name>')
        '--example'        = @('<target-name>')
        '--test'           = @('<target-name>')
        '--bench'          = @('<target-name>')
        '--features'       = @('<features>')
        '-F'               = @('<features>')
        '--profile'        = @('<profile-name>')
        '--target'         = @('<target-triple>')
        '--manifest-path'  = @('<path>')
        '-m'               = @('<path>')
        '--target-dir'     = @('<path>')
        '--jobs'           = @('<jobs>')
        '-j'               = @('<jobs>')
        '-Z'               = @()
    }
}

function Get-CargoClippyLintValueMap {
    @{
        '-W'      = @('<lint>', 'clippy::<lint>')
        '--warn'  = @('<lint>', 'clippy::<lint>')
        '-A'      = @('<lint>', 'clippy::<lint>')
        '--allow' = @('<lint>', 'clippy::<lint>')
        '-D'      = @('<lint>', 'clippy::<lint>')
        '--deny'  = @('<lint>', 'clippy::<lint>')
        '-F'      = @('<lint>', 'clippy::<lint>')
        '--forbid' = @('<lint>', 'clippy::<lint>')
    }
}

function Get-CargoClippyUnstableFlags {
    $cache = Get-CargoClippyCompletionCache
    if ($cache.UnstableFlags.Count -gt 0) {
        return $cache.UnstableFlags
    }

    $lines = Invoke-CargoText -Arguments @('-Z', 'help')
    $flags = foreach ($line in $lines) {
        if ($line -match '^\s+-Z\s+([a-z0-9][a-z0-9\-]*)\b') {
            $matches[1]
        }
    }

    $cache.UnstableFlags = @($flags | Sort-Object -Unique -CaseSensitive)
    $cache.UnstableFlags
}

function Get-CargoClippyLintCatalog {
    $cache = Get-CargoClippyCompletionCache
    if ($cache.LintNamesLoaded) {
        return $cache.LintNames
    }

    $cache.LintNamesLoaded = $true

    $driver = Get-Command -Name clippy-driver.exe, clippy-driver -ErrorAction SilentlyContinue | Select-Object -First 1
    if (-not $driver) {
        return $cache.LintNames
    }

    $raw = try {
        $null | & $driver.Source '-W' 'help' 2>&1 | ForEach-Object { $_.ToString() -replace '\e\[[0-9;?]*[ -/]*[@-~]', '' }
    } catch {
        @()
    }

    $lines = @($raw)

    $names = New-Object System.Collections.Generic.List[object]
    $seen = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal)
    [void]$seen.Add('warnings')
    [void]$names.Add([pscustomobject]@{ Name = 'warnings'; ToolTip = 'The group of every lint that is set to warn.' })

    $inGroups = $false
    foreach ($line in $lines) {
        if ($line -match '^Lint groups ') {
            $inGroups = $true
            continue
        }

        if ($line -match '^Lint checks ') {
            $inGroups = $false
            continue
        }

        if ($line -notmatch '^\s+((?:clippy::)?[a-z][a-z0-9_-]*)\s{2,}(\S.*)$') {
            continue
        }

        $name = $matches[1]
        $detail = $matches[2].Trim()
        if ($name -eq 'name' -or -not $seen.Add($name)) {
            continue
        }

        $toolTip = if ($inGroups) { "Lint group: $detail" } else { $detail }
        [void]$names.Add([pscustomobject]@{ Name = $name; ToolTip = $toolTip })
    }

    $cache.LintNames = @($names.ToArray())
    $cache.LintNames
}

function Get-CargoClippyTargetTripleList {
    $cache = Get-CargoClippyCompletionCache
    if ($cache.TargetTriplesLoaded) {
        return $cache.TargetTriples
    }

    $cache.TargetTriplesLoaded = $true

    $rustup = Get-Command -Name rustup.exe, rustup -ErrorAction SilentlyContinue | Select-Object -First 1
    if ($rustup) {
        $raw = try {
            $null | & $rustup.Source 'target' 'list' '--installed' 2>$null | ForEach-Object { $_.Trim() } | Where-Object { $_ }
        } catch {
            @()
        }

        $cache.TargetTriples = @($raw)
    }

    if (@($cache.TargetTriples).Count -eq 0) {
        $rustc = Get-Command -Name rustc.exe, rustc -ErrorAction SilentlyContinue | Select-Object -First 1
        if ($rustc) {
            $raw = try {
                $null | & $rustc.Source '--print' 'target-list' 2>$null | ForEach-Object { $_.Trim() } | Where-Object { $_ }
            } catch {
                @()
            }

            $cache.TargetTriples = @($raw)
        }
    }

    $cache.TargetTriples
}

function Get-CargoClippyManifestInfo {
    $cache = Get-CargoClippyCompletionCache

    $manifestPath = $null
    $directory = $PWD.ProviderPath
    while (-not [string]::IsNullOrWhiteSpace($directory)) {
        $candidate = Join-Path -Path $directory -ChildPath 'Cargo.toml'
        if (Test-Path -LiteralPath $candidate -PathType Leaf) {
            $manifestPath = $candidate
            break
        }

        $directory = Split-Path -Path $directory -Parent
    }

    if ($cache.ManifestLoadedFor -eq $manifestPath -and $null -ne $cache.ManifestFacts) {
        return $cache.ManifestFacts
    }

    $profiles = New-Object System.Collections.Generic.List[string]
    foreach ($builtIn in @('dev', 'release', 'test', 'bench')) {
        [void]$profiles.Add($builtIn)
    }

    $features = New-Object System.Collections.Generic.List[string]
    $packages = New-Object System.Collections.Generic.List[string]
    $targets = [System.Collections.Generic.Dictionary[string, object]]::new([System.StringComparer]::Ordinal)
    foreach ($kind in @('bin', 'example', 'test', 'bench')) {
        $targets[$kind] = New-Object System.Collections.Generic.List[string]
    }

    $raw = if ($manifestPath) {
        try {
            Get-Content -LiteralPath $manifestPath -ErrorAction Stop
        } catch {
            @()
        }
    } else {
        @()
    }

    $lines = @($raw)

    $section = ''
    foreach ($line in $lines) {
        $trimmed = $line.Trim()
        if ($trimmed -match '^\[\[?([^\]]+)\]\]?$') {
            $section = $matches[1].Trim()
            if ($section -match '^profile\.([^.]+)') {
                [void]$profiles.Add($matches[1].Trim('"', "'"))
            }
            continue
        }

        if ($trimmed -match '^([A-Za-z0-9_"''.-]+)\s*=') {
            $key = $matches[1].Trim('"', "'")
            switch -Regex ($section) {
                '^features$' { [void]$features.Add($key) }
                '^(package|bin|example|test|bench)$' {
                    if ($key -eq 'name' -and $trimmed -match '=\s*["'']([^"'']+)["'']') {
                        if ($section -eq 'package') {
                            [void]$packages.Add($matches[1])
                        } else {
                            [void]$targets[$section].Add($matches[1])
                        }
                    }
                }
            }
        }
    }

    $facts = [pscustomobject]@{
        Profiles = @($profiles | Sort-Object -Unique -CaseSensitive)
        Features = @($features | Sort-Object -Unique -CaseSensitive)
        Packages = @($packages | Sort-Object -Unique -CaseSensitive)
        Targets  = $targets
    }

    $cache.ManifestFacts = $facts
    $cache.ManifestLoadedFor = $manifestPath
    $facts
}

function Get-CargoClippyDynamicValueList {
    param(
        [string]$OptionName,
        [bool]$AfterDoubleDash
    )

    if ($AfterDoubleDash) {
        return @(Get-CargoClippyLintCatalog)
    }

    if ($OptionName -eq '--explain') {
        return @(Get-CargoClippyLintCatalog)
    }

    if ($OptionName -eq '--target') {
        return @(Get-CargoClippyTargetTripleList | ForEach-Object { [pscustomobject]@{ Name = $_; ToolTip = 'Rust target triple.' } })
    }

    $facts = Get-CargoClippyManifestInfo
    $values = switch ($OptionName) {
        '--profile' { @($facts.Profiles); break }
        '--features' { @($facts.Features); break }
        '-F' { @($facts.Features); break }
        '--package' { @($facts.Packages); break }
        '-p' { @($facts.Packages); break }
        '--exclude' { @($facts.Packages); break }
        '--bin' { @($facts.Targets['bin']); break }
        '--example' { @($facts.Targets['example']); break }
        '--test' { @($facts.Targets['test']); break }
        '--bench' { @($facts.Targets['bench']); break }
        default { @() }
    }

    @($values | ForEach-Object { [pscustomobject]@{ Name = $_; ToolTip = "$OptionName value from the local Cargo.toml." } })
}

function Initialize-CargoClippyCompletionCache {
    $cache = Get-CargoClippyCompletionCache
    if ($cache.Initialized) {
        return
    }

    $clippyLines = Invoke-CargoClippyText -Arguments @('--help')
    $checkLines = Invoke-CargoText -Arguments @('check', '--help')

    $descriptions = [System.Collections.Generic.Dictionary[string, string]]::new([System.StringComparer]::Ordinal)
    foreach ($entry in (Get-CargoClippyOptionMetadataFromLines -Lines $checkLines).GetEnumerator()) {
        $descriptions[$entry.Key] = $entry.Value
    }
    foreach ($entry in (Get-CargoClippyOptionMetadataFromLines -Lines $clippyLines).GetEnumerator()) {
        # The lint-level flags clippy documents are only accepted after '--'; keeping them out here
        # also stops clippy's '-F / --forbid' text from overwriting cargo's '-F, --features'.
        if ($cache.LintOptions -ccontains $entry.Key) {
            continue
        }

        $descriptions[$entry.Key] = $entry.Value
    }

    $cache.CommandDescriptions = $descriptions
    $cache.CommandOptions = @($descriptions.Keys + '--') | Sort-Object -Unique -CaseSensitive
    $cache.CommandValueMap = Get-CargoClippyCommandValueMap
    $cache.LintDescriptions = @{
        '-W'       = 'Set lint warnings.'
        '--warn'   = 'Set lint warnings.'
        '-A'       = 'Set lint allowed.'
        '--allow'  = 'Set lint allowed.'
        '-D'       = 'Set lint denied.'
        '--deny'   = 'Set lint denied.'
        '-F'       = 'Set lint forbidden.'
        '--forbid' = 'Set lint forbidden.'
    }
    $cache.LintValueMap = Get-CargoClippyLintValueMap
    $cache.Initialized = $true
}

function Get-CargoClippyState {
    param([string[]]$TokensBeforeCurrent)

    Initialize-CargoClippyCompletionCache
    $cache = Get-CargoClippyCompletionCache

    $pendingOption = $null
    $afterDoubleDash = $false

    foreach ($token in $TokensBeforeCurrent) {
        $cleanToken = Remove-CargoClippyOuterQuotes -Value $token
        if ([string]::IsNullOrWhiteSpace($cleanToken)) {
            continue
        }

        if ($pendingOption) {
            $pendingOption = $null
            continue
        }

        if ($afterDoubleDash) {
            if ($cleanToken -match '^(--[A-Za-z0-9][A-Za-z0-9\-]*)=(.*)$') {
                continue
            }

            if ($cache.LintValueMap.ContainsKey($cleanToken)) {
                $pendingOption = $cleanToken
            }
            continue
        }

        if ($cleanToken -eq '--') {
            $afterDoubleDash = $true
            continue
        }

        if ($cleanToken -match '^(--[A-Za-z0-9][A-Za-z0-9\-]*)=(.*)$') {
            continue
        }

        if ($cache.CommandValueMap.ContainsKey($cleanToken) -or $cache.PathOptions -contains $cleanToken) {
            $pendingOption = $cleanToken
        }
    }

    [pscustomobject]@{
        PendingOption   = $pendingOption
        AfterDoubleDash = $afterDoubleDash
    }
}

function Get-CargoClippyInlineValueState {
    param(
        [string]$CurrentWord,
        [bool]$AfterDoubleDash
    )

    if ([string]::IsNullOrWhiteSpace($CurrentWord)) {
        return $null
    }

    if ($CurrentWord -match '^(--[A-Za-z0-9][A-Za-z0-9\-]*)=(.*)$') {
        $optionName = $matches[1]
        $currentValue = $matches[2]
        return [pscustomobject]@{
            OptionName   = $optionName
            ValuePrefix  = $currentValue
            PrefixText   = "$optionName="
            AfterDoubleDash = $AfterDoubleDash
        }
    }

    $null
}

function Get-CargoClippyValueCompletions {
    param(
        [string]$OptionName,
        [string]$CurrentWord,
        [bool]$AfterDoubleDash,
        [string]$PrefixText = ''
    )

    Initialize-CargoClippyCompletionCache
    $cache = Get-CargoClippyCompletionCache
    $current = Remove-CargoClippyOuterQuotes -Value $CurrentWord

    if ($OptionName -eq '-Z' -and -not $AfterDoubleDash) {
        return @(
            Get-CargoClippyUnstableFlags |
                Where-Object { $_.StartsWith($current, [System.StringComparison]::OrdinalIgnoreCase) } |
                ForEach-Object {
                    New-CargoClippyCompletionResult -CompletionText ($PrefixText + $_) -ListItemText $_ -ResultType 'ParameterValue' -ToolTip 'Cargo unstable flag.'
                }
        )
    }

    if (-not $AfterDoubleDash -and $cache.PathOptions -contains $OptionName) {
        return @(
            Get-CargoClippyPathCompletions -InputPath $CurrentWord |
                ForEach-Object {
                    New-CargoClippyCompletionResult -CompletionText ($PrefixText + $_.CompletionText) -ListItemText $_.ListItemText -ResultType $_.ResultType -ToolTip $_.ToolTip
                }
        )
    }

    if ($OptionName -eq '--config' -and -not $AfterDoubleDash) {
        $results = @()
        $results += @(
            Get-CargoClippyPathCompletions -InputPath $CurrentWord |
                ForEach-Object {
                    New-CargoClippyCompletionResult -CompletionText ($PrefixText + $_.CompletionText) -ListItemText $_.ListItemText -ResultType $_.ResultType -ToolTip $_.ToolTip
                }
        )

        foreach ($hint in $cache.CommandValueMap[$OptionName]) {
            if ($hint.StartsWith($current, [System.StringComparison]::OrdinalIgnoreCase)) {
                $results += New-CargoClippyCompletionResult -CompletionText ($PrefixText + $hint) -ListItemText $hint -ResultType 'ParameterValue' -ToolTip 'Cargo config override.'
            }
        }

        return @($results)
    }

    $valueMap = if ($AfterDoubleDash) { $cache.LintValueMap } else { $cache.CommandValueMap }
    if (-not $valueMap.ContainsKey($OptionName)) {
        return @()
    }

    $dynamic = @(Get-CargoClippyDynamicValueList -OptionName $OptionName -AfterDoubleDash $AfterDoubleDash |
            Where-Object { $_.Name.StartsWith($current, [System.StringComparison]::OrdinalIgnoreCase) })
    if ($dynamic.Count -gt 0) {
        return @(
            $dynamic | ForEach-Object {
                New-CargoClippyCompletionResult -CompletionText ($PrefixText + $_.Name) -ListItemText $_.Name -ResultType 'ParameterValue' -ToolTip $_.ToolTip
            }
        )
    }

    $literal = @(
        $valueMap[$OptionName] |
            Where-Object { $_.StartsWith($current, [System.StringComparison]::OrdinalIgnoreCase) } |
            ForEach-Object {
                $toolTip = if ($AfterDoubleDash) { 'Clippy lint name.' } else { "$OptionName value" }
                New-CargoClippyCompletionResult -CompletionText ($PrefixText + $_) -ListItemText $_ -ResultType 'ParameterValue' -ToolTip $toolTip
            }
    )
    if ($literal.Count -gt 0) {
        return $literal
    }

    # A free-form value slot must keep what the user typed rather than collapse to nothing, which
    # would hand the slot to PowerShell's filename fallback.
    if (-not [string]::IsNullOrWhiteSpace($current)) {
        $toolTip = if ($AfterDoubleDash) { 'Clippy lint name.' } else { "$OptionName value" }
        return @(New-CargoClippyCompletionResult -CompletionText ($PrefixText + $current) -ListItemText $current -ResultType 'ParameterValue' -ToolTip $toolTip)
    }

    @()
}

function Get-CargoClippyOptionCompletions {
    param(
        [string[]]$Options,
        [object]$Descriptions,
        [string]$CurrentWord
    )

    $current = Remove-CargoClippyOuterQuotes -Value $CurrentWord

    # -V (--version) and -v (--verbose) are different options, so a single-dash word must be
    # matched ordinally or tab would silently rewrite one into the other.
    $comparison = if ($current -match '^-[^-]') {
        [System.StringComparison]::Ordinal
    } else {
        [System.StringComparison]::OrdinalIgnoreCase
    }

    foreach ($option in $Options | Sort-Object -Unique -CaseSensitive) {
        if ($option.StartsWith($current, $comparison)) {
            $resultType = if ($option -eq '--') { 'ParameterValue' } else { 'ParameterName' }
            $toolTip = if ($Descriptions.ContainsKey($option)) { $Descriptions[$option] } else { 'Pass remaining arguments to Clippy/rustc.' }
            New-CargoClippyCompletionResult -CompletionText $option -ListItemText $option -ResultType $resultType -ToolTip $toolTip
        }
    }
}

function Complete-CargoClippy {
    param(
        [string]$wordToComplete,
        [System.Management.Automation.Language.CommandAst]$commandAst,
        [int]$cursorPosition
    )

    Initialize-CargoClippyCompletionCache
    $cache = Get-CargoClippyCompletionCache

    $currentWord = if ($cursorPosition -gt $commandAst.Extent.EndOffset) {
        ''
    } else {
        Get-CargoClippyCurrentWord -Line $commandAst.ToString() -CursorPosition ($cursorPosition - $commandAst.Extent.StartOffset) -Fallback $wordToComplete
    }

    $state = Get-CargoClippyState -TokensBeforeCurrent @(Get-CargoClippyArgumentTokens -CommandAst $commandAst -CursorPosition $cursorPosition)
    $inlineValue = Get-CargoClippyInlineValueState -CurrentWord $currentWord -AfterDoubleDash $state.AfterDoubleDash
    if ($inlineValue) {
        return @(Get-CargoClippyValueCompletions -OptionName $inlineValue.OptionName -CurrentWord $inlineValue.ValuePrefix -AfterDoubleDash $inlineValue.AfterDoubleDash -PrefixText $inlineValue.PrefixText)
    }

    if ($state.PendingOption) {
        return @(Get-CargoClippyValueCompletions -OptionName $state.PendingOption -CurrentWord $currentWord -AfterDoubleDash $state.AfterDoubleDash)
    }

    if ($state.AfterDoubleDash) {
        if ([string]::IsNullOrWhiteSpace($currentWord) -or $currentWord.StartsWith('-')) {
            return @(Get-CargoClippyOptionCompletions -Options $cache.LintOptions -Descriptions $cache.LintDescriptions -CurrentWord $currentWord)
        }

        @()
        return
    }

    if ([string]::IsNullOrWhiteSpace($currentWord) -or $currentWord.StartsWith('-')) {
        return @(Get-CargoClippyOptionCompletions -Options $cache.CommandOptions -Descriptions $cache.CommandDescriptions -CurrentWord $currentWord)
    }

    @()
}

}

Register-ArgumentCompleter -Native -CommandName @('cargo-clippy', 'cargo-clippy.exe') -ScriptBlock {
    param($wordToComplete, $commandAst, $cursorPosition)

    Complete-CargoClippy -wordToComplete $wordToComplete -commandAst $commandAst -cursorPosition $cursorPosition
}