completers/wt_completer/wt_completer.ps1

# PowerShell Argument Completer for wt.exe
# Provides tab completion for top-level wt options, subcommands, and selected subcommand arguments.

Set-StrictMode -Version Latest

function Get-WtSettingsNames {
    param([string]$Kind)

    $cache = Get-Variable -Name 'WtSettingsNames' -Scope Script -ErrorAction Ignore
    if ($null -eq $cache -or $null -eq $cache.Value) {
        $names = @{ profiles = @(); schemes = @() }
        foreach ($package in 'Microsoft.WindowsTerminal_8wekyb3d8bbwe', 'Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe') {
            $settingsPath = Join-Path -Path $env:LOCALAPPDATA -ChildPath "Packages\$package\LocalState\settings.json"
            if (-not (Test-Path -LiteralPath $settingsPath)) {
                continue
            }

            try {
                $settings = Get-Content -LiteralPath $settingsPath -Raw | ConvertFrom-Json
            } catch {
                continue
            }

            if ($settings.PSObject.Properties['profiles'] -and $settings.profiles.PSObject.Properties['list']) {
                $names.profiles += @($settings.profiles.list | ForEach-Object { $_.name } | Where-Object { $_ })
            }

            if ($settings.PSObject.Properties['schemes']) {
                $names.schemes += @($settings.schemes | ForEach-Object { $_.name } | Where-Object { $_ })
            }
        }

        $names.profiles = @($names.profiles | Sort-Object -Unique)
        $names.schemes = @($names.schemes | Sort-Object -Unique)
        Set-Variable -Name 'WtSettingsNames' -Value $names -Scope Script
        $cache = Get-Variable -Name 'WtSettingsNames' -Scope Script
    }

    @($cache.Value[$Kind])
}

function Get-WtValueCompletionData {
    param([string]$Option)

    $kind = if ($Option -in '-p', '--profile') { 'profiles' } elseif ($Option -eq '--colorScheme') { 'schemes' } else { $null }
    if (-not $kind) {
        return @()
    }

    @(
        foreach ($name in Get-WtSettingsNames -Kind $kind) {
            $text = if ($name -match '\s') { '"' + $name + '"' } else { $name }
            @{ Text = $text; Display = $name; Type = 'ParameterValue'; Tooltip = "Windows Terminal $kind entry" }
        }
    )
}

function Complete-WtNative {
    param($wordToComplete, $commandAst, $cursorPosition)

    if (-not (Get-Command wt.exe -ErrorAction SilentlyContinue) -and
        -not (Get-Command wt -ErrorAction SilentlyContinue)) {
        return
    }

    $topLevelOptionData = @(
        @{ Text = '-h'; Display = '-h'; Type = 'ParameterName'; Tooltip = 'Print this help message and exit' }
        @{ Text = '--help'; Display = '--help'; Type = 'ParameterName'; Tooltip = 'Print this help message and exit' }
        @{ Text = '-v'; Display = '-v'; Type = 'ParameterName'; Tooltip = 'Display the application version' }
        @{ Text = '--version'; Display = '--version'; Type = 'ParameterName'; Tooltip = 'Display the application version' }
        @{ Text = '-M'; Display = '-M'; Type = 'ParameterName'; Tooltip = 'Launch the window maximized' }
        @{ Text = '--maximized'; Display = '--maximized'; Type = 'ParameterName'; Tooltip = 'Launch the window maximized' }
        @{ Text = '-F'; Display = '-F'; Type = 'ParameterName'; Tooltip = 'Launch the window in fullscreen mode' }
        @{ Text = '--fullscreen'; Display = '--fullscreen'; Type = 'ParameterName'; Tooltip = 'Launch the window in fullscreen mode' }
        @{ Text = '-f'; Display = '-f'; Type = 'ParameterName'; Tooltip = 'Launch the window in focus mode' }
        @{ Text = '--focus'; Display = '--focus'; Type = 'ParameterName'; Tooltip = 'Launch the window in focus mode' }
        @{ Text = '--pos'; Display = '--pos'; Type = 'ParameterName'; Tooltip = 'Specify the position for the terminal, in ''x,y'' format' }
        @{ Text = '--size'; Display = '--size'; Type = 'ParameterName'; Tooltip = 'Specify the number of columns and rows for the terminal, in ''c,r'' format' }
        @{ Text = '-w'; Display = '-w'; Type = 'ParameterName'; Tooltip = 'Specify a terminal window to run the given commandline in' }
        @{ Text = '--window'; Display = '--window'; Type = 'ParameterName'; Tooltip = 'Specify a terminal window to run the given commandline in' }
        @{ Text = '-s'; Display = '-s'; Type = 'ParameterName'; Tooltip = 'Internal parameter for saved command lines' }
        @{ Text = '--saved'; Display = '--saved'; Type = 'ParameterName'; Tooltip = 'Internal parameter for saved command lines' }
    )

    $newTerminalOptionData = @(
        @{ Text = '-p'; Display = '-p'; Type = 'ParameterName'; Tooltip = 'Use the specified profile' }
        @{ Text = '--profile'; Display = '--profile'; Type = 'ParameterName'; Tooltip = 'Use the specified profile' }
        @{ Text = '--sessionId'; Display = '--sessionId'; Type = 'ParameterName'; Tooltip = 'Reuse or assign the specified session ID' }
        @{ Text = '-d'; Display = '-d'; Type = 'ParameterName'; Tooltip = 'Set the starting directory' }
        @{ Text = '--startingDirectory'; Display = '--startingDirectory'; Type = 'ParameterName'; Tooltip = 'Set the starting directory' }
        @{ Text = '--title'; Display = '--title'; Type = 'ParameterName'; Tooltip = 'Set the starting tab title' }
        @{ Text = '--tabColor'; Display = '--tabColor'; Type = 'ParameterName'; Tooltip = 'Set the starting tab color' }
        @{ Text = '--suppressApplicationTitle'; Display = '--suppressApplicationTitle'; Type = 'ParameterName'; Tooltip = 'Prevent the terminal application from changing the tab title' }
        @{ Text = '--useApplicationTitle'; Display = '--useApplicationTitle'; Type = 'ParameterName'; Tooltip = 'Allow the terminal application to change the tab title' }
        @{ Text = '--colorScheme'; Display = '--colorScheme'; Type = 'ParameterName'; Tooltip = 'Set the color scheme' }
        @{ Text = '--appendCommandLine'; Display = '--appendCommandLine'; Type = 'ParameterName'; Tooltip = 'Append the provided command line to the profile command line' }
        @{ Text = '--inheritEnvironment'; Display = '--inheritEnvironment'; Type = 'ParameterName'; Tooltip = 'Inherit environment variables into the new terminal' }
        @{ Text = '--reloadEnvironment'; Display = '--reloadEnvironment'; Type = 'ParameterName'; Tooltip = 'Reload the environment instead of inheriting it' }
    )

    $splitPaneOptionData = @(
        @{ Text = '-H'; Display = '-H'; Type = 'ParameterName'; Tooltip = 'Split horizontally' }
        @{ Text = '--horizontal'; Display = '--horizontal'; Type = 'ParameterName'; Tooltip = 'Split horizontally' }
        @{ Text = '-V'; Display = '-V'; Type = 'ParameterName'; Tooltip = 'Split vertically' }
        @{ Text = '--vertical'; Display = '--vertical'; Type = 'ParameterName'; Tooltip = 'Split vertically' }
        @{ Text = '-s'; Display = '-s'; Type = 'ParameterName'; Tooltip = 'Set the pane size ratio between 0.01 and 0.99' }
        @{ Text = '--size'; Display = '--size'; Type = 'ParameterName'; Tooltip = 'Set the pane size ratio between 0.01 and 0.99' }
        @{ Text = '-D'; Display = '-D'; Type = 'ParameterName'; Tooltip = 'Duplicate the focused pane instead of launching a new terminal' }
        @{ Text = '--duplicate'; Display = '--duplicate'; Type = 'ParameterName'; Tooltip = 'Duplicate the focused pane instead of launching a new terminal' }
    )

    $focusTabOptionData = @(
        @{ Text = '-t'; Display = '-t'; Type = 'ParameterName'; Tooltip = 'Target the specified tab index' }
        @{ Text = '--target'; Display = '--target'; Type = 'ParameterName'; Tooltip = 'Target the specified tab index' }
        @{ Text = '-n'; Display = '-n'; Type = 'ParameterName'; Tooltip = 'Move focus to the next tab' }
        @{ Text = '--next'; Display = '--next'; Type = 'ParameterName'; Tooltip = 'Move focus to the next tab' }
        @{ Text = '-p'; Display = '-p'; Type = 'ParameterName'; Tooltip = 'Move focus to the previous tab' }
        @{ Text = '--previous'; Display = '--previous'; Type = 'ParameterName'; Tooltip = 'Move focus to the previous tab' }
    )

    $movePaneOptionData = @(
        @{ Text = '-t'; Display = '-t'; Type = 'ParameterName'; Tooltip = 'Move the focused pane to the specified tab index' }
        @{ Text = '--tab'; Display = '--tab'; Type = 'ParameterName'; Tooltip = 'Move the focused pane to the specified tab index' }
    )

    $focusPaneOptionData = @(
        @{ Text = '-t'; Display = '-t'; Type = 'ParameterName'; Tooltip = 'Focus the specified pane index' }
        @{ Text = '--target'; Display = '--target'; Type = 'ParameterName'; Tooltip = 'Focus the specified pane index' }
    )

    $directionValueData = @(
        @{ Text = 'left'; Display = 'left'; Type = 'ParameterValue'; Tooltip = 'Direction: left' }
        @{ Text = 'right'; Display = 'right'; Type = 'ParameterValue'; Tooltip = 'Direction: right' }
        @{ Text = 'up'; Display = 'up'; Type = 'ParameterValue'; Tooltip = 'Direction: up' }
        @{ Text = 'down'; Display = 'down'; Type = 'ParameterValue'; Tooltip = 'Direction: down' }
        @{ Text = 'previous'; Display = 'previous'; Type = 'ParameterValue'; Tooltip = 'Direction: previous focused pane' }
        @{ Text = 'nextInOrder'; Display = 'nextInOrder'; Type = 'ParameterValue'; Tooltip = 'Direction: next pane in order' }
        @{ Text = 'previousInOrder'; Display = 'previousInOrder'; Type = 'ParameterValue'; Tooltip = 'Direction: previous pane in order' }
        @{ Text = 'first'; Display = 'first'; Type = 'ParameterValue'; Tooltip = 'Direction: first pane' }
    )

    $subcommandData = @(
        @{ Text = 'new-tab'; Display = 'new-tab'; Type = 'ParameterValue'; Tooltip = 'Create a new tab' }
        @{ Text = 'nt'; Display = 'nt'; Type = 'ParameterValue'; Tooltip = 'Alias for new-tab' }
        @{ Text = 'split-pane'; Display = 'split-pane'; Type = 'ParameterValue'; Tooltip = 'Create a new split pane' }
        @{ Text = 'sp'; Display = 'sp'; Type = 'ParameterValue'; Tooltip = 'Alias for split-pane' }
        @{ Text = 'focus-tab'; Display = 'focus-tab'; Type = 'ParameterValue'; Tooltip = 'Move focus to another tab' }
        @{ Text = 'ft'; Display = 'ft'; Type = 'ParameterValue'; Tooltip = 'Alias for focus-tab' }
        @{ Text = 'move-focus'; Display = 'move-focus'; Type = 'ParameterValue'; Tooltip = 'Move focus to the adjacent pane in the specified direction' }
        @{ Text = 'mf'; Display = 'mf'; Type = 'ParameterValue'; Tooltip = 'Alias for move-focus' }
        @{ Text = 'move-pane'; Display = 'move-pane'; Type = 'ParameterValue'; Tooltip = 'Move focused pane to another tab' }
        @{ Text = 'mp'; Display = 'mp'; Type = 'ParameterValue'; Tooltip = 'Alias for move-pane' }
        @{ Text = 'swap-pane'; Display = 'swap-pane'; Type = 'ParameterValue'; Tooltip = 'Swap the focused pane with the adjacent pane in the specified direction' }
        @{ Text = 'focus-pane'; Display = 'focus-pane'; Type = 'ParameterValue'; Tooltip = 'Move focus to another pane' }
        @{ Text = 'fp'; Display = 'fp'; Type = 'ParameterValue'; Tooltip = 'Alias for focus-pane' }
        @{ Text = 'x-save'; Display = 'x-save'; Type = 'ParameterValue'; Tooltip = 'Save command line as input action' }
    )

    $subcommandMap = @{
        'new-tab' = 'new-tab'
        'nt' = 'new-tab'
        'split-pane' = 'split-pane'
        'sp' = 'split-pane'
        'focus-tab' = 'focus-tab'
        'ft' = 'focus-tab'
        'move-focus' = 'move-focus'
        'mf' = 'move-focus'
        'move-pane' = 'move-pane'
        'mp' = 'move-pane'
        'swap-pane' = 'swap-pane'
        'focus-pane' = 'focus-pane'
        'fp' = 'focus-pane'
        'x-save' = 'x-save'
    }

    $topLevelValueOptions = @('--pos', '--size', '-w', '--window', '-s', '--saved')
    $newTerminalValueOptions = @('-p', '--profile', '--sessionId', '-d', '--startingDirectory', '--title', '--tabColor', '--colorScheme')
    $splitPaneValueOptions = $newTerminalValueOptions + @('-s', '--size')
    $focusTabValueOptions = @('-t', '--target')
    $movePaneValueOptions = @('-t', '--tab')
    $focusPaneValueOptions = @('-t', '--target')

    $subcommandOptionData = @{
        'new-tab' = $newTerminalOptionData
        'split-pane' = $newTerminalOptionData + $splitPaneOptionData
        'focus-tab' = $focusTabOptionData
        'move-focus' = @()
        'move-pane' = $movePaneOptionData
        'swap-pane' = @()
        'focus-pane' = $focusPaneOptionData
        'x-save' = @()
    }

    $subcommandValueOptions = @{
        'new-tab' = $newTerminalValueOptions
        'split-pane' = $splitPaneValueOptions
        'focus-tab' = $focusTabValueOptions
        'move-focus' = @()
        'move-pane' = $movePaneValueOptions
        'swap-pane' = @()
        'focus-pane' = $focusPaneValueOptions
        'x-save' = @()
    }

    $line = $commandAst.ToString()
    $relativeCursor = $cursorPosition - $commandAst.Extent.StartOffset
    $prefixLength = [Math]::Max(0, [Math]::Min($relativeCursor, $line.Length))
    $linePrefix = $line.Substring(0, $prefixLength)
    $tokens = @([regex]::Matches($linePrefix, '\S+') | ForEach-Object { $_.Value })
    $hasTrailingSpace = ($linePrefix -match '\s$') -or ($relativeCursor -gt $line.Length)
    $matchPrefix = if ((-not $hasTrailingSpace) -and $tokens.Count -gt 0 -and $tokens[-1] -like '-*') {
        $tokens[-1]
    }
    elseif ($wordToComplete) {
        $wordToComplete
    }
    else {
        if ($hasTrailingSpace -or $tokens.Count -eq 0) { '' } else { $tokens[-1] }
    }
    $previousToken = if ($hasTrailingSpace) {
        if ($tokens.Count -gt 0) { $tokens[-1] } else { '' }
    }
    elseif ($tokens.Count -gt 1) {
        $tokens[-2]
    }
    else {
        ''
    }

    [object[]]$argumentTokens = if ($tokens.Count -gt 1) {
        @($tokens[1..($tokens.Count - 1)])
    }
    else {
        @()
    }

    [object[]]$completedTokens = if ($hasTrailingSpace) {
        @($argumentTokens)
    }
    elseif ($argumentTokens.Count -gt 0) {
        @($argumentTokens | Select-Object -First ($argumentTokens.Count - 1))
    }
    else {
        @()
    }

    $expandedTokens = New-Object System.Collections.Generic.List[string]
    foreach ($token in $completedTokens) {
        if ($token -eq ';' -or $token -eq '`;') {
            $expandedTokens.Add(';')
            continue
        }

        if ($token -match '^(.+?)`?;$') {
            $expandedTokens.Add($Matches[1])
            $expandedTokens.Add(';')
            continue
        }

        $expandedTokens.Add($token)
    }
    $completedTokens = @($expandedTokens.ToArray())

    $selectedSubcommand = $null
    $expectingValueOption = $null
    foreach ($token in $completedTokens) {
        if ($token -eq ';') {
            $selectedSubcommand = $null
            $expectingValueOption = $null
            continue
        }

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

        if (-not $selectedSubcommand) {
            if (($topLevelValueOptions + $newTerminalValueOptions) -contains $token) {
                $expectingValueOption = $token
                continue
            }

            if ($subcommandMap.ContainsKey($token)) {
                $selectedSubcommand = $subcommandMap[$token]
                continue
            }

            continue
        }

        if ($subcommandValueOptions[$selectedSubcommand] -contains $token) {
            $expectingValueOption = $token
        }
    }

    [object[]]$completionData = @()
    if (-not $selectedSubcommand) {
        if (($topLevelValueOptions + $newTerminalValueOptions) -contains $previousToken) {
            $completionData = Get-WtValueCompletionData -Option $previousToken
        }
        elseif ($matchPrefix -like '-*') {
            $completionData = $topLevelOptionData + $newTerminalOptionData
        }
        else {
            $completionData = $topLevelOptionData + $newTerminalOptionData + $subcommandData
        }
    }
    else {
        switch ($selectedSubcommand) {
            'move-focus' {
                if ($matchPrefix -like '-*') {
                    $completionData = @()
                }
                else {
                    $completionData = $directionValueData
                }
            }
            'swap-pane' {
                if ($matchPrefix -like '-*') {
                    $completionData = @()
                }
                else {
                    $completionData = $directionValueData
                }
            }
            default {
                if ($subcommandValueOptions[$selectedSubcommand] -contains $previousToken) {
                    $completionData = Get-WtValueCompletionData -Option $previousToken
                }
                elseif ($matchPrefix -like '-*') {
                    $completionData = $subcommandOptionData[$selectedSubcommand]
                }
                else {
                    $completionData = @()
                }
            }
        }
    }

    foreach ($item in $completionData) {
        if ($item.Text -notlike "$matchPrefix*" -and $item.Display -notlike "$matchPrefix*") {
            continue
        }

        [System.Management.Automation.CompletionResult]::new(
            $item.Text,
            $item.Display,
            [System.Management.Automation.CompletionResultType]::$($item.Type),
            $item.Tooltip
        )
    }
}

Register-ArgumentCompleter -Native -CommandName @('wt', 'wt.exe') -ScriptBlock {
    param($wordToComplete, $commandAst, $cursorPosition)
    Complete-WtNative -wordToComplete $wordToComplete -commandAst $commandAst -cursorPosition $cursorPosition
}