completers/just_completer/just_completer.ps1

Set-StrictMode -Version 2.0

function Resolve-JustCommandName {
    if (Get-Variable -Name JustCompletionCommandName -Scope Script -ErrorAction Ignore) {
        return $script:JustCompletionCommandName
    }

    $command = Get-Command -Name just.exe, just -CommandType Application, ExternalScript -ErrorAction SilentlyContinue | Select-Object -First 1
    $script:JustCompletionCommandName = if ($command) {
        if ($command.Source) { $command.Source } else { $command.Name }
    } else {
        $null
    }

    $script:JustCompletionCommandName
}

function Get-JustCompletionArguments {
    param($CommandAst, [int]$CursorPosition, [string]$WordToComplete)

    # Project the AST to literal argument text without evaluating anything the user typed.
    $relativeCursor = $CursorPosition - $CommandAst.Extent.StartOffset
    $arguments = [System.Collections.Generic.List[string]]::new()

    foreach ($element in $CommandAst.CommandElements) {
        $start = $element.Extent.StartOffset - $CommandAst.Extent.StartOffset
        if ($start -ge $relativeCursor) {
            break
        }

        $end = $element.Extent.EndOffset - $CommandAst.Extent.StartOffset
        if ($end -gt $relativeCursor) {
            $arguments.Add($element.Extent.Text.Substring(0, $relativeCursor - $start))
            break
        }

        if ($element -is [System.Management.Automation.Language.StringConstantExpressionAst]) {
            $arguments.Add($element.Value)
        } else {
            $arguments.Add($element.Extent.Text)
        }
    }

    if ($WordToComplete -eq '') {
        $arguments.Add('')
    }

    $arguments.ToArray()
}

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

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

    try {
        $startInfo = [System.Diagnostics.ProcessStartInfo]::new()
        $startInfo.FileName = $commandName
        [void]$startInfo.ArgumentList.Add('--')
        foreach ($argument in $Arguments) {
            [void]$startInfo.ArgumentList.Add($argument)
        }
        $startInfo.UseShellExecute = $false
        $startInfo.CreateNoWindow = $true
        $startInfo.RedirectStandardInput = $true
        $startInfo.RedirectStandardOutput = $true
        $startInfo.RedirectStandardError = $true
        $startInfo.StandardOutputEncoding = [System.Text.Encoding]::UTF8
        $startInfo.Environment['JUST_COMPLETE'] = 'powershell'

        # A child inherits the process start directory, which Set-Location never updates;
        # hand it the session's filesystem location so just finds the right justfile.
        $location = Get-Location -PSProvider FileSystem -ErrorAction Ignore
        if ($location) {
            $startInfo.WorkingDirectory = $location.ProviderPath
        }

        $process = [System.Diagnostics.Process]::Start($startInfo)
        try {
            $process.StandardInput.Close()
            $outputTask = $process.StandardOutput.ReadToEndAsync()
            [void]$process.StandardError.ReadToEndAsync()
            if (-not $process.WaitForExit(5000)) {
                try { $process.Kill($true) } catch { Write-Debug -Message $_.Exception.Message }
                return @()
            }

            $text = ($outputTask.Result -replace '\e\[[0-9;?]*[ -/]*[@-~]', '')
            if ([string]::IsNullOrWhiteSpace($text)) {
                return @()
            }

            @($text -split '\r?\n' | Where-Object { $_ -ne '' })
        } finally {
            $process.Dispose()
        }
    } catch {
        @()
    }
}

Register-ArgumentCompleter -Native -CommandName @("just", "just.exe") -ScriptBlock {
    param($wordToComplete, $commandAst, $cursorPosition)

    $arguments = Get-JustCompletionArguments -CommandAst $commandAst -CursorPosition $cursorPosition -WordToComplete $wordToComplete
    $results = Invoke-JustCompletion -Arguments $arguments

    $results | ForEach-Object {
        $split = $_.Split("`t")
        $cmd = $split[0]

        if ($split.Length -eq 2) {
            $help = $split[1]
        }
        else {
            $help = $split[0]
        }

        [System.Management.Automation.CompletionResult]::new($cmd, $cmd, 'ParameterValue', $help)
    }
}