private/functions/tabcompletion/tabcompletion.ps1

function global:New-DbaTeppCompletionResult {
    <#
        .SYNOPSIS
            Generates a completion result for dbatools internal tab completion.
 
        .DESCRIPTION
            Generates a completion result for dbatools internal tab completion.
 
        .PARAMETER CompletionText
            The text to propose.
 
        .PARAMETER ToolTip
            The tooltip to show in tooltip-aware hosts (ISE, mostly)
 
        .PARAMETER ListItemText
            ???
 
        .PARAMETER CompletionResultType
            The type of object that is being completed.
            By default it generates one of type parameter value.
 
        .PARAMETER NoQuotes
            Whether to put the result in quotes or not.
 
        .EXAMPLE
            New-DbaTeppCompletionResult -CompletionText 'master' -ToolTip 'master'
 
            Returns a CompletionResult with the text and tooltip 'master'
    #>

    param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory, ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [string]
        $CompletionText,

        [Parameter(Position = 1, ValueFromPipelineByPropertyName)]
        [string]
        $ToolTip,

        [Parameter(Position = 2, ValueFromPipelineByPropertyName)]
        [string]
        $ListItemText,

        [System.Management.Automation.CompletionResultType]
        $CompletionResultType = [System.Management.Automation.CompletionResultType]::ParameterValue,

        [switch]
        $NoQuotes = $false
    )

    process {
        $toolTipToUse = if ($ToolTip -eq '') { $CompletionText }
        else { $ToolTip }
        $listItemToUse = if ($ListItemText -eq '') { $CompletionText }
        else { $ListItemText }

        # If the caller explicitly requests that quotes
        # not be included, via the -NoQuotes parameter,
        # then skip adding quotes.

        if ($CompletionResultType -eq [System.Management.Automation.CompletionResultType]::ParameterValue -and -not $NoQuotes) {
            # Add single quotes for the caller in case they are needed.
            # We use the parser to robustly determine how it will treat
            # the argument. If we end up with too many tokens, or if
            # the parser found something expandable in the results, we
            # know quotes are needed.

            $tokens = $null
            $null = [System.Management.Automation.Language.Parser]::ParseInput("echo $CompletionText", [ref]$tokens, [ref]$null)
            if ($tokens.Length -ne 3 -or ($tokens[1] -is [System.Management.Automation.Language.StringExpandableToken] -and $tokens[1].Kind -eq [System.Management.Automation.Language.TokenKind]::Generic)) {
                $CompletionText = "'$CompletionText'"
            }
        }
        return New-Object System.Management.Automation.CompletionResult($CompletionText, $listItemToUse, $CompletionResultType, $toolTipToUse.Trim())
    }
}

(Get-Item Function:\New-DbaTeppCompletionResult).Visibility = "Private"
function Register-DbaTeppArgumentCompleter {
    <#
        .SYNOPSIS
            Registers a parameter for a prestored Tepp.
 
        .DESCRIPTION
            Registers a parameter for a prestored Tepp.
            This function allows easily registering a function's parameter for Tepp in the function-file, rather than in a centralized location.
 
        .PARAMETER Command
            Name of the command whose parameter should receive Tepp.
            Supports multiple commands at the same time in order to optimize performance.
 
        .PARAMETER Parameter
            Name of the parameter that should be Tepp'ed.
 
        .PARAMETER Name
            Name of the Tepp Completioner to use.
            Defaults to the parameter name.
            Best practice requires a Completioner to be named the same as the completed parameter, in which case this parameter needs not be specified.
            However sometimes that may not be universally possible, which is when this parameter comes in.
 
        .PARAMETER All
            Whether this TEPP applies to all commands in dbatools that have the specified parameter.
 
        .EXAMPLE
            Register-DbaTeppArgumentCompleter -Command Get-DbaDbBackupHistory -Parameter Database
 
            Registers the "Database" parameter of the Get-DbaDbBackupHistory to receive Database-Tepp
       #>

    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingEmptyCatchBlock", "")]
    param (
        [string[]]$Command,
        [string[]]$Parameter,
        [string]$Name,
        [switch]$All
    )

    #region ScriptBlock
    $scriptBlock = {
        param (
            $commandName,
            $parameterName,
            $wordToComplete,
            $commandAst,
            $fakeBoundParameter
        )

        if ($teppScript = [Dataplat.Dbatools.TabExpansion.TabExpansionHost]::GetTeppScript($commandName, $parameterName)) {
            $start = Get-Date
            $teppScript.LastExecution = $start
            $teppScript.LastDuration = New-Object System.TimeSpan(-1) # Null it, just in case. It's a new start.

            try {
                $ExecutionContext.InvokeCommand.InvokeScript($true, ([System.Management.Automation.ScriptBlock]::Create($teppScript.ScriptBlock.ToString())), $null, @($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter))
            } catch {
                $null = 1
            }

            $teppScript.LastDuration = (Get-Date) - $start
        }
    }
    #endregion ScriptBlock

    foreach ($p in $Parameter) {
        $lowername = $PSBoundParameters.Name

        if ($null -eq $lowername) {
            $lowername = $p.ToLowerInvariant()
        } else {
            $lowername = $lowername.ToLowerInvariant()
        }

        if ($All) {
            [Dataplat.Dbatools.TabExpansion.TabExpansionHost]::AddTabCompletionSet("*", $p, $lowername)
        } else {
            foreach ($c in $Command) {
                [Dataplat.Dbatools.TabExpansion.TabExpansionHost]::AddTabCompletionSet($c, $p, $lowername)
            }
        }

        if ($script:TEPP) {
            TabExpansionPlusPlus\Register-ArgumentCompleter -CommandName $Command -ParameterName $p -ScriptBlock $scriptBlock
        } else {
            Register-ArgumentCompleter -CommandName $Command -ParameterName $p -ScriptBlock $scriptBlock
        }
    }
}
function Register-DbaTeppInstanceCacheBuilder {
    <#
        .SYNOPSIS
            Registers a scriptblock used to build the TEPP cache from an instance connection.
 
        .DESCRIPTION
            Registers a scriptblock used to build the TEPP cache from an instance connection.
            Used only on import of the module.
 
        .PARAMETER ScriptBlock
            The ScriptBlock used to build the cache.
 
            The ScriptBlock may assume the following two variables to exist:
            - $FullSmoName (A string containing the full SMO name as presented by the DbaInstanceParameter class-interpreted input)
            - $server (An SMO connection object)
 
        .PARAMETER Slow
            This switch implies a gathering process that takes too much time to be performed synchronously.
            Basically, when retrieving the information takes more than 25ms on an average server (on top of establishing the original connection), this switch should be set.
 
        .EXAMPLE
            Register-DbaTeppInstanceCacheBuilder -ScriptBlock $ScriptBlock
 
            Registers the scriptblock stored in the aptly named variable $ScriptBlock as a fest cache building scriptblock.
            Note: The scriptblock must execute swiftly! (less than 25ms)
 
        .EXAMPLE
            Register-DbaTeppInstanceCacheBuilder -ScriptBlock $ScriptBlock -Slow
 
            Registers the scriptblock stored in the aptly named variable $ScriptBlock as a slow cache building scriptblock.
            This is suitable for cache building scriptblocks that take a while to execute.
 
        .NOTES
            Additional information about the function.
       #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [System.Management.Automation.ScriptBlock]
        $ScriptBlock,

        [switch]
        $Slow
    )

    if ($Slow -and ([Dataplat.Dbatools.TabExpansion.TabExpansionHost]::TeppGatherScriptsSlow -notcontains $ScriptBlock)) {
        [Dataplat.Dbatools.TabExpansion.TabExpansionHost]::TeppGatherScriptsSlow.Add($ScriptBlock)
    } elseif ([Dataplat.Dbatools.TabExpansion.TabExpansionHost]::TeppGatherScriptsFast -notcontains $ScriptBlock) {
        [Dataplat.Dbatools.TabExpansion.TabExpansionHost]::TeppGatherScriptsFast.Add($ScriptBlock)
    }
}
function Register-DbaTeppScriptblock {
    <#
        .SYNOPSIS
            Registers a scriptblock under name, to later be available for TabExpansion.
 
        .DESCRIPTION
            Registers a scriptblock under name, to later be available for TabExpansion.
 
        .PARAMETER ScriptBlock
            The scriptblock to register.
 
        .PARAMETER Name
            The name under which the scriptblock should be registered.
 
        .EXAMPLE
            Register-DbaTeppScriptblock -ScriptBlock $scriptBlock -Name MyFirstTeppScriptBlock
 
            Stores the scriptblock stored in $scriptBlock under the name "MyFirstTeppScriptBlock"
       #>

    [CmdletBinding()]
    param (
        [System.Management.Automation.ScriptBlock]
        $ScriptBlock,

        [string]
        $Name
    )

    $scp = New-Object Dataplat.Dbatools.TabExpansion.ScriptContainer
    $scp.Name = $Name.ToLowerInvariant()
    $scp.ScriptBlock = $ScriptBlock
    $scp.LastDuration = New-TimeSpan -Seconds -1

    [Dataplat.Dbatools.TabExpansion.TabExpansionHost]::Scripts[$Name.ToLowerInvariant()] = $scp
}