core/init.ps1

using namespace System.Management.Automation
$_ = Split-Path $PSScriptRoot -Parent
New-Variable -Name PSCompletions -Value @{
    version                 = '5.1.4'
    path                    = @{
        root             = $_
        completions      = Join-Path $_ 'completions'
        core             = Join-Path $_ 'core'
        data             = Join-Path $_ 'data.json'
        temp             = Join-Path $_ 'temp'
        completions_json = Join-Path $_ 'temp\completions.json'
        update           = Join-Path $_ 'temp\update.txt'
        change           = Join-Path $_ 'temp\change.txt'
    }
    order                   = [ordered]@{}
    completions_data        = @{}
    language                = $PSUICulture
    encoding                = [console]::OutputEncoding
    separator               = [System.IO.Path]::DirectorySeparatorChar
    wc                      = New-Object System.Net.WebClient
    menu                    = @{
        const = @{
            symbol_item = @('SpaceTab', 'WriteSpaceTab', 'OptionTab')
            line_item   = @('horizontal', 'vertical', 'top_left', 'bottom_left', 'top_right', 'bottom_right')
            color_item  = @('item_text', 'item_back', 'selected_text', 'selected_back', 'filter_text', 'filter_back', 'border_text', 'border_back', 'status_text', 'status_back', 'tip_text', 'tip_back')
            color_value = @('White', 'Black', 'Gray', 'DarkGray', 'Red', 'DarkRed', 'Green', 'DarkGreen', 'Blue', 'DarkBlue', 'Cyan', 'DarkCyan', 'Yellow', 'DarkYellow', 'Magenta', 'DarkMagenta')
            config_item = @(
                'trigger_key', 'between_item_and_symbol', 'status_symbol', 'filter_symbol', 'enable_menu', 'enable_menu_enhance', 'enable_tip', 'enable_tip_when_enhance', 'enable_completions_sort', 'enable_tip_follow_cursor', 'enable_list_follow_cursor', 'enable_tip_cover_buffer', 'enable_list_cover_buffer', 'enable_list_loop', 'enable_selection_with_margin', 'enable_enter_when_single', 'enable_prefix_match_in_filter', 'list_min_width', 'list_max_count_when_above', 'list_max_count_when_below', 'width_from_menu_left_to_item', 'width_from_menu_right_to_item', 'height_from_menu_bottom_to_cursor_when_above'
            )
        }
    }
    default_config          = [ordered]@{
        # config
        url                                          = ''
        language                                     = $PSUICulture
        enable_completions_update                    = 1
        enable_module_update                         = 1
        disable_cache                                = 0
        function_name                                = 'PSCompletions'

        # menu symbol
        SpaceTab                                     = '→'
        WriteSpaceTab                                = '↓'
        OptionTab                                    = '?'

        # menu line
        horizontal                                   = '═'
        vertical                                     = '║'
        top_left                                     = '╔'
        bottom_left                                  = '╚'
        top_right                                    = '╗'
        bottom_right                                 = '╝'

        # menu color
        item_text                                    = 'Blue'
        item_back                                    = 'Black'
        selected_text                                = 'white'
        selected_back                                = 'DarkGray'
        filter_text                                  = 'Yellow'
        filter_back                                  = 'Black'
        border_text                                  = 'DarkGray'
        border_back                                  = 'Black'
        status_text                                  = 'Blue'
        status_back                                  = 'Black'
        tip_text                                     = 'Cyan'
        tip_back                                     = 'Black'

        # menu config
        trigger_key                                  = 'Tab'
        between_item_and_symbol                      = ' '
        status_symbol                                = '/'
        filter_symbol                                = '[]'

        enable_menu                                  = 1
        enable_menu_enhance                          = 1
        enable_tip                                   = 1
        enable_tip_when_enhance                      = 1
        enable_completions_sort                      = 1
        enable_tip_follow_cursor                     = 1
        enable_list_follow_cursor                    = 1
        enable_tip_cover_buffer                      = 1
        enable_list_cover_buffer                     = 0

        enable_list_loop                             = 1
        enable_selection_with_margin                 = 1
        enable_enter_when_single                     = 0
        enable_prefix_match_in_filter                = 0

        list_min_width                               = 10
        list_max_count_when_above                    = -1
        list_max_count_when_below                    = -1
        width_from_menu_left_to_item                 = 0
        width_from_menu_right_to_item                = 0
        height_from_menu_bottom_to_cursor_when_above = 0
    }
    # 每个补全都默认带有的配置项
    default_completion_item = @('language', 'enable_tip')
    config_item             = @('url', 'language', 'enable_completions_update', 'enable_module_update', 'disable_cache', 'function_name')
} -Option ReadOnly

if ($IsWindows -or $PSEdition -eq 'Desktop') {
    # Windows...
    . $PSScriptRoot\completion\win.ps1
    . $PSScriptRoot\menu\win.ps1
}
else {
    # WSL/Unix...
    . $PSScriptRoot\completion\unix.ps1
}
. $PSScriptRoot\utils\$PSEdition.ps1

Add-Member -InputObject $PSCompletions -MemberType ScriptMethod return_completion {
    param([string]$name, [string]$tip = ' ', [array]$symbols)
    $_symbol = foreach ($c in $symbols) { $PSCompletions.config.$c }
    $padSymbols = if ($_symbol) { "$($PSCompletions.config.between_item_and_symbol)$($_symbol -join '')" }else { '' }
    $cmd_arr = $name -split ' '
    @{
        name           = $name
        ListItemText   = "$($cmd_arr[-1])$($padSymbols)"
        CompletionText = $cmd_arr[-1]
        ToolTip        = $tip
    }
}
Add-Member -InputObject $PSCompletions -MemberType ScriptMethod get_completion {
    function getCompletions {
        $completions = @()

        $runspacePool = [runspacefactory]::CreateRunspacePool(1, [Environment]::ProcessorCount)
        $runspacePool.Open()
        $runspaces = @()

        $tasks = @(
            @{
                node     = $PSCompletions.completions.$root.root
                isOption = $false
            },
            @{
                node     = $PSCompletions.completions.$root.options
                isOption = $true
            }
        )
        foreach ($task in $tasks) {
            $runspace = [powershell]::Create().AddScript({
                    param($obj, $PSCompletions)
                    $completions = [System.Collections.Generic.List[System.Object]]@()
                    $_alias_map = @{}
                    $temp = @{
                        WriteSpaceTab              = @()
                        WriteSpaceTab_and_SpaceTab = @()
                    }
                    function _replace {
                        param ($data, $separator = '')
                        $data = $data -join $separator
                        $pattern = '\{\{(.*?(\})*)(?=\}\})\}\}'
                        $matches = [regex]::Matches($data, $pattern)
                        foreach ($match in $matches) {
                            $data = $data.Replace($match.Value, (Invoke-Expression $match.Groups[1].Value) -join $separator )
                        }
                        if ($data -match $pattern) { (_replace $data) }else { return $data }
                    }
                    function parseCompletions {
                        param($node, [string]$pre, [bool]$isOption)
                        foreach ($_ in $node) {
                            $pad = if ($pre) { ' ' }else { '' }
                            $symbols = @()
                            if ($isOption) {
                                $symbols += 'OptionTab'
                            }
                            if ($_.next -or $_.options) {
                                $symbols += 'SpaceTab'
                                if ($isOption) {
                                    $symbols += 'WriteSpaceTab'
                                }
                            }
                            if ($_.symbol) {
                                $symbols += (_replace $_.symbol ' ') -split ' '
                            }
                            $symbols = $symbols | Select-Object -Unique

                            if ($symbols) {
                                if ('WriteSpaceTab' -in $symbols) {
                                    $temp.WriteSpaceTab += $_.name
                                    if ($_.alias) {
                                        foreach ($a in $_.alias) { $temp.WriteSpaceTab += $a }
                                    }
                                    if ('SpaceTab' -in $symbols) {
                                        $temp.WriteSpaceTab_and_SpaceTab += $_.name
                                        if ($_.alias) {
                                            foreach ($a in $_.alias) { $temp.WriteSpaceTab_and_SpaceTab += $a }
                                        }
                                    }
                                }
                            }

                            $symbols = foreach ($c in $symbols) { $PSCompletions.config.$c }
                            $symbols = $symbols -join ''
                            $padSymbols = if ($symbols) { "$($PSCompletions.config.between_item_and_symbol)$symbols" }else { '' }

                            $completions.Add(@{
                                    name           = $pre + $pad + $_.name
                                    ListItemText   = "$($_.name)$padSymbols"
                                    CompletionText = $_.name
                                    ToolTip        = $_.tip
                                })
                            if ($_.alias) {
                                if ($isOption) {
                                    foreach ($a in $_.alias) {
                                        $completions.Add(@{
                                                name           = $pre + $pad + $a
                                                ListItemText   = "$a$padSymbols"
                                                CompletionText = $a
                                                ToolTip        = $_.tip
                                            })
                                        if ($_.next) { parseCompletions $_.next ($pre + $pad + $a) }
                                    }
                                }
                                else {
                                    foreach ($a in $_.alias) {
                                        # 判断别名出现的位置
                                        $index = (($pre + $pad + $_.name) -split ' ').Length - 1
                                        # 用这个位置创建一个数组,将所有在这个位置出现的别名全部写入这个数组
                                        if (!$_alias_map.$index) { $_alias_map.$index = @() }
                                        $_alias_map.$index += @{
                                            name  = $_.name
                                            alias = $a
                                        }
                                        $completions.Add(@{
                                                name           = $pre + $pad + $a
                                                ListItemText   = "$a$padSymbols"
                                                CompletionText = $a
                                                ToolTip        = $_.tip
                                            })
                                    }
                                }
                            }
                            if ($_.next) { parseCompletions $_.next ($pre + $pad + $_.name) }
                            if ($_.options) { parseCompletions $_.options ($pre + $pad + $_.name) $true }
                        }
                    }
                    parseCompletions $obj.node '' $obj.isOption
                    @{
                        completions = $completions
                        alias_map   = $_alias_map
                        temp        = $temp
                    }
                }).AddArgument($task).AddArgument($PSCompletions)
            $runspace.RunspacePool = $runspacePool
            $runspaces += @{ Runspace = $runspace; Job = $runspace.BeginInvoke() }
        }

        # 等待所有任务完成
        foreach ($rs in $runspaces) {
            $result = $rs.Runspace.EndInvoke($rs.Job)
            $rs.Runspace.Dispose()
            $completions += $result.completions
            $PSCompletions.completions_data."$($root)_WriteSpaceTab" += $result.temp.WriteSpaceTab
            $PSCompletions.completions_data."$($root)_WriteSpaceTab_and_SpaceTab" += $result.temp.WriteSpaceTab_and_SpaceTab
            foreach ($a in $result.alias_map.Keys) {
                if ($PSCompletions.completions_data."$($root)_alias_map".$a) {
                    $PSCompletions.completions_data."$($root)_alias_map".$a += $result.alias_map.$a
                }
                else {
                    $PSCompletions.completions_data."$($root)_alias_map".$a = $result.alias_map.$a
                }
            }
        }
        return $completions
    }
    function handleCompletions {
        param($completions)
        return $completions
    }
    function filterCompletions {
        param($completions, [string]$root)

        # 当这个 options 是 WriteSpaceTab 时,将下一个值直接过滤掉
        $need_skip = $false

        $filter_input_arr = @()
        foreach ($_ in $input_arr) {
            if ($_ -like '-*' -or $need_skip) {
                if ($need_skip) { $need_skip = $false }
                if ($_ -in $PSCompletions.completions_data."$($root)_WriteSpaceTab") {
                    if ($input_arr[-1 - !$space_tab] -eq $_ -and $_ -in $PSCompletions.completions_data."$($root)_WriteSpaceTab_and_SpaceTab") {
                        $need_add = $true
                    }
                    else {
                        $need_skip = $true
                    }
                }
            }
            else { $need_add = $true }
            if ($need_add -and $_ -notin $common_options) {
                $filter_input_arr += $_
                $need_add = $false
            }
        }

        if (!$space_tab) {
            # 如果是输入 -* 过程中触发的补全,则需要把最后一个 -* 加入其中
            if ($input_arr[-1] -like '-*') {
                $filter_input_arr += $input_arr[-1]
            }
        }

        if ($filter_input_arr.Count) {
            $match = if ($space_tab) { ' *' }else { '*' }
        }
        else {
            # 如果过滤出来为空,则是只是输入了根命令,没有输入其他内容
            $match = '*'
        }

        $alias_input_arr = $filter_input_arr

        # 循环命令的长度,针对每一个位置去 $PSCompletions.completions_data."$($root)_alias_map" 找到对应的数组,然后把数组里的值拿出来比对,如果有匹配的,替换掉原来的命令名
        # 用位置的好处是,这样遍历是依赖于命令的长度,而命令长度一般不长
        for ($i = 0; $i -lt $filter_input_arr.Count; $i++) {
            foreach ($obj in $PSCompletions.completions_data."$($root)_alias_map".$i) {
                if ($obj.alias -eq $filter_input_arr[$i]) {
                    $alias_input_arr[$i] = $obj.name
                    break
                }
            }
        }

        $filter_list = @()

        $runspacePool = [runspacefactory]::CreateRunspacePool(1, [Environment]::ProcessorCount)
        $runspacePool.Open()
        $runspaces = @()

        foreach ($completions in $PSCompletions.split_array($completions, [Environment]::ProcessorCount, $true)) {
            $runspace = [powershell]::Create().AddScript({
                    param($completions, [array]$input_arr, [array]$filter_input_arr, [string]$match, [array]$alias_input_arr, [bool]$space_tab, $host_ui)
                    $max_width = 0
                    $results = @()
                    function get_length {
                        param([string]$str)
                        $host_ui.RawUI.NewBufferCellArray($str, $host_ui.RawUI.BackgroundColor, $host_ui.RawUI.BackgroundColor).LongLength
                    }
                    foreach ($completion in $completions) {
                        $matches = [regex]::Matches($completion.name, "(?:`"[^`"]*`"|'[^']*'|\S)+")
                        $cmd = @()
                        foreach ($m in $matches) { $cmd += $m.Value }

                        # 判断选项是否使用过了,如果使用过了,$no_used 为 $true
                        # 这里的判断对于 --file="abc" 这样的命令无法使用,因为这里和用户输入的 "abc"是连着的
                        $no_used = if ($cmd[-1] -like '-*') {
                            $cmd[-1] -notin $input_arr
                        }
                        else { $true }

                        $isLike = ($completion.name -like ([WildcardPattern]::Escape($filter_input_arr -join ' ') + $match)) -or ($completion.name -like ([WildcardPattern]::Escape($alias_input_arr -join ' ') + $match))
                        if ($no_used -and $cmd.Count -eq ($filter_input_arr.Count + $space_tab) -and $isLike) {
                            $results += $completion
                            $max_width = [Math]::Max($max_width, (get_length $completion.ListItemText))
                        }
                    }
                    @{
                        results   = $results
                        max_width = $max_width
                    }
                }).AddArgument($completions).AddArgument($input_arr).AddArgument($filter_input_arr).AddArgument($match).AddArgument($alias_input_arr).AddArgument($space_tab).AddArgument($Host.UI)


            $runspace.RunspacePool = $runspacePool
            $runspaces += @{ Runspace = $runspace; Job = $runspace.BeginInvoke() }
        }

        # 等待所有任务完成
        foreach ($rs in $runspaces) {
            $result = $rs.Runspace.EndInvoke($rs.Job)
            $rs.Runspace.Dispose()
            if ($result.results) {
                $filter_list += $result.results
            }
            $PSCompletions.menu.list_max_width = [Math]::Max($PSCompletions.menu.list_max_width, $result.max_width)
        }

        # 处理 common_options
        if ($PSCompletions.completions.$root.common_options) {
            function Get-PadSymbols {
                $symbols = @('OptionTab')
                if ($_.next) {
                    $symbols += 'SpaceTab'
                    $symbols += 'WriteSpaceTab'
                }
                if ($_.symbol) {
                    $symbols += $PSCompletions.replace_content($_.symbol, ' ') -split ' '
                }
                $symbols = $symbols | Select-Object -Unique

                $symbols = foreach ($c in $symbols) { $PSCompletions.config.$c }
                $symbols = $symbols -join ''
                if ($symbols) {
                    "$($PSCompletions.config.between_item_and_symbol)$symbols"
                }
                else {
                    ''
                }
            }
            if ($space_tab) {
                if ($input_arr[-1] -in $common_options_with_next -and ($input_arr -notlike "*$($input_arr[-1])*$($input_arr[-1])*" -or $input_arr -like "*$($input_arr[-1])")) {
                    $filter_list = @()
                    $filter = $PSCompletions.completions.$root.common_options.Where({ $_.name -eq $input_arr[-1] -or $_.alias -contains $input_arr[-1] })
                    foreach ($_ in $filter) {
                        foreach ($n in $_.next) {
                            $filter_list += @{
                                ListItemText   = $n.name
                                CompletionText = $n.name
                                ToolTip        = $n.tip
                            }
                            $PSCompletions.menu.list_max_width = [Math]::Max($PSCompletions.menu.list_max_width, $PSCompletions.menu.get_length($n.name))
                        }
                    }
                }
                foreach ($_ in $PSCompletions.completions.$root.common_options) {
                    if ($_.name -notin $input_arr) {
                        $isExist = $false
                        $temp_list = @()
                        $name_with_symbol = "$($_.name)$(Get-PadSymbols)"
                        $temp_list += @{
                            ListItemText   = $name_with_symbol
                            CompletionText = $_.name
                            ToolTip        = $_.tip
                        }
                        $PSCompletions.menu.list_max_width = [Math]::Max($PSCompletions.menu.list_max_width, $PSCompletions.menu.get_length($name_with_symbol))

                        foreach ($a in $_.alias) {
                            if ($a -notin $input_arr) {
                                $name_with_symbol = "$a$(Get-PadSymbols)"
                                $temp_list += @{
                                    ListItemText   = $name_with_symbol
                                    CompletionText = $a
                                    ToolTip        = $_.tip
                                }
                                $PSCompletions.menu.list_max_width = [Math]::Max($PSCompletions.menu.list_max_width, $PSCompletions.menu.get_length($name_with_symbol))
                            }
                            else {
                                $temp_list = @()
                                break
                            }
                        }
                        $filter_list += $temp_list
                    }
                }
            }
            else {
                if ($input_arr[-2] -in $common_options_with_next -and $input_arr -notlike "*$($input_arr[-2])*$($input_arr[-2])*") {
                    $filter_list = @()
                    $filter = $PSCompletions.completions.$root.common_options.Where({ $_.name -eq $input_arr[-2] -or $_.alias -contains $input_arr[-2] })
                    foreach ($_ in $filter) {
                        foreach ($n in $_.next) {
                            if ($n.name -like "$($input_arr[-1])*") {
                                $filter_list += @{
                                    ListItemText   = $n.name
                                    CompletionText = $n.name
                                    ToolTip        = $n.tip
                                }
                                $PSCompletions.menu.list_max_width = [Math]::Max($PSCompletions.menu.list_max_width, $PSCompletions.menu.get_length($n.name))
                            }
                        }
                    }
                }
                foreach ($_ in $PSCompletions.completions.$root.common_options) {
                    if ($_.name -notin $input_arr -and $_.name -like "$($input_arr[-1])*") {
                        $name_with_symbol = "$($_.name)$(Get-PadSymbols)"
                        $filter_list += @{
                            ListItemText   = $name_with_symbol
                            CompletionText = $_.name
                            ToolTip        = $_.tip
                        }
                        $PSCompletions.menu.list_max_width = [Math]::Max($PSCompletions.menu.list_max_width, $PSCompletions.menu.get_length($name_with_symbol))
                    }
                    foreach ($a in $_.alias) {
                        if ($a -notin $input_arr -and $a -like "$($input_arr[-1])*") {
                            $name_with_symbol = "$a$(Get-PadSymbols)"
                            $filter_list += @{
                                ListItemText   = $name_with_symbol
                                CompletionText = $a
                                ToolTip        = $_.tip
                            }
                            $PSCompletions.menu.list_max_width = [Math]::Max($PSCompletions.menu.list_max_width, $PSCompletions.menu.get_length($name_with_symbol))
                        }
                    }
                }
            }
        }
        return $filter_list
    }

    if ($PSCompletions.job.State -eq 'Completed') {
        $PSCompletions.completions = Receive-Job $PSCompletions.job
        Remove-Job $PSCompletions.job
        $PSCompletions.job = $null
    }
    if ($PSCompletions.config.disable_cache -eq 1) {
        $PSCompletions.completions.$root = $null
        $PSCompletions.completions_data.$root = $null
    }

    if (!$PSCompletions.completions.$root) {
        $language = $PSCompletions.get_language($root)
        $PSCompletions.completions.$root = $PSCompletions.ConvertFrom_JsonToHashtable($PSCompletions.get_raw_content("$($PSCompletions.path.completions)/$root/language/$language.json"))
    }

    $PSCompletions.cmd = $input_arr

    $input_arr = [array]$input_arr

    # 使用 hooks 覆盖默认的函数,实现在一些特殊的需求,比如一些补全的动态加载
    if ($PSCompletions.config.comp_config.$root.disable_hooks -notin @($null, 1)) {
        . "$($PSCompletions.path.completions)/$root/hooks.ps1"
    }
    if (!$PSCompletions.completions_data.$root) {
        $common_options = @()
        $common_options_with_next = @()
        foreach ($_ in $PSCompletions.completions.$root.common_options) {
            foreach ($a in $_.alias) {
                $common_options += $a
                if ($_.next) { $common_options_with_next += $a }
            }
            $common_options += $_.name
            if ($_.next) { $common_options_with_next += $_.name }
        }
        $PSCompletions.completions_data."$($root)_WriteSpaceTab" = $common_options_with_next

        $PSCompletions.completions_data."$($root)_WriteSpaceTab_and_SpaceTab" = @()
    
        # 存储别名的映射,用于在过滤时允许别名
        $PSCompletions.completions_data."$($root)_alias_map" = @{}

        $PSCompletions.completions_data.$root = getCompletions
    }
    $completions = $PSCompletions.completions_data.$root
    $completions = handleCompletions $completions
    $filter_list = filterCompletions $completions $root

    # 排序
    if ($PSCompletions.config.enable_completions_sort -eq 1) {
        $path_order = "$($PSCompletions.path.completions)/$root/order.json"
        if ($PSCompletions.order."$($root)_job") {
            if ($PSCompletions.order."$($root)_job".State -eq 'Completed') {
                $PSCompletions.order.$root = Receive-Job $PSCompletions.order."$($root)_job"
                Remove-Job $PSCompletions.order."$($root)_job"
                $PSCompletions.order.Remove("$($root)_job")
            }
        }
        else {
            if (Test-Path $path_order) {
                try {
                    $PSCompletions.order.$root = $PSCompletions.ConvertFrom_JsonToHashtable($PSCompletions.get_raw_content($path_order))
                }
                catch {
                    $PSCompletions.order.$root = $null
                }
            }
            else {
                $PSCompletions.order.$root = $null
            }
        }
        $order = $PSCompletions.order.$root
        if ($order) {
            $filter_list = $filter_list | Sort-Object {
                $o = $order.$($_.name -join ' ')
                if ($o) { $o }else { 999999999 }
            }
        }
        $PSCompletions.order_job($completions, (Get-PSReadLineOption).HistorySavePath, $root, $path_order)
    }
    return $filter_list
}
Add-Member -InputObject $PSCompletions -MemberType ScriptMethod split_array {
    <#
    .Synopsis
        分割数组
    .Description
        三个参数:
        $array: 数组
        $count: 每个数组的元素个数
        $by_count: 如果此值为 $true, 则 $count 为指定的数组个数,否则为每个数组的元素个数
    #>

    param ([array]$array, [int]$count, [bool]$by_count)
    if ($by_count) {
        $ChunkSize = [math]::Ceiling($array.Length / $count)
    }
    else {
        $ChunkSize = $count
    }
    $chunks = for ($i = 0; $i -lt $array.Length; $i += $ChunkSize) {
        , ($array[$i..([math]::Min($i + $ChunkSize - 1, $array.Length - 1))])
    }
    $chunks
}
Add-Member -InputObject $PSCompletions -MemberType ScriptMethod ensure_dir {
    param([string]$path)
    if (!(Test-Path $path)) { New-Item -ItemType Directory $path > $null }
}
Add-Member -InputObject $PSCompletions -MemberType ScriptMethod get_language {
    param ([string]$completion)
    $path_config = "$($PSCompletions.path.completions)/$completion/config.json"
    $content_config = $PSCompletions.get_raw_content($path_config) | ConvertFrom-Json
    if ($PSCompletions.config.comp_config.$completion.language) {
        $config_language = $PSCompletions.config.comp_config.$completion.language
    }
    else {
        $config_language = $null
    }
    if ($config_language) {
        $language = if ($config_language -in $content_config.language) { $config_language }else { $content_config.language[0] }
    }
    else {
        $language = if ($PSCompletions.language -in $content_config.language) { $PSCompletions.language }else { $content_config.language[0] }
    }
    $language
}
Add-Member -InputObject $PSCompletions -MemberType ScriptMethod get_content {
    param ([string]$path)
    $res = (Get-Content $path -Encoding utf8 -ErrorAction SilentlyContinue).Where({ $_ -ne '' })
    if ($res) { return $res }
    , @()
}
Add-Member -InputObject $PSCompletions -MemberType ScriptMethod get_raw_content {
    param ([string]$path, [bool]$trim = $true)
    $res = Get-Content $path -Raw -Encoding utf8 -ErrorAction SilentlyContinue
    if ($res) {
        if ($trim) { return $res.Trim() }
        return $res
    }
    ''
}
Add-Member -InputObject $PSCompletions -MemberType ScriptMethod replace_content {
    param ($data, $separator = '')
    $data = $data -join $separator
    $pattern = '\{\{(.*?(\})*)(?=\}\})\}\}'
    $matches = [regex]::Matches($data, $pattern)
    foreach ($match in $matches) {
        $data = $data.Replace($match.Value, (Invoke-Expression $match.Groups[1].Value) -join $separator )
    }
    if ($data -match $pattern) { $PSCompletions.replace_content($data) }else { return $data }
}
Add-Member -InputObject $PSCompletions -MemberType ScriptMethod write_with_color {
    param([string]$str)
    $color_list = @()
    $str = $str -replace "`n", 'n&&_n_n&&'
    $str_list = foreach ($_ in ($str -split '(<\@[^>]+>.*?(?=<\@|$))').Where({ $_ -ne '' })) {
        if ($_ -match '<\@([\s\w]+)>(.*)') {
            ($matches[2] -replace 'n&&_n_n&&', "`n") -replace '^<\@>', ''
            $color = $matches[1] -split ' '
            $color_list += @{
                color   = $color[0]
                bgcolor = $color[1]
            }
        }
        else {
            ($_ -replace 'n&&_n_n&&', "`n") -replace '^<\@>', ''
            $color_list += @{}
        }
    }
    $str_list = [array]$str_list
    for ($i = 0; $i -lt $str_list.Count; $i++) {
        $color = $color_list[$i].color
        $bgcolor = $color_list[$i].bgcolor
        if ($color) {
            if ($bgcolor) {
                Write-Host $str_list[$i] -f $color -b $bgcolor -NoNewline
            }
            else {
                Write-Host $str_list[$i] -f $color -NoNewline
            }
        }
        else {
            Write-Host $str_list[$i] -NoNewline
        }
    }
    Write-Host ''
}
Add-Member -InputObject $PSCompletions -MemberType ScriptMethod show_with_less {
    param (
        $str_list,
        [string]$color = 'Green',
        [int]$show_line = [System.Console]::WindowHeight - 7
    )
    if ($str_list -is [string]) {
        $str_list = $str_list -split "`n"
    }
    $i = 0
    $need_less = [System.Console]::WindowHeight -lt ($str_list.Count + 2)
    if ($need_less) {
        $lines = $str_list.Count - $show_line
        $PSCompletions.write_with_color($PSCompletions.replace_content($PSCompletions.info.less_tip))
        while ($i -lt $str_list.Count -and $i -lt $show_line) {
            Write-Host $str_list[$i] -f $color
            $i++
        }
        while ($i -lt $str_list.Count) {
            $keyCode = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown').VirtualKeyCode
            if ($keyCode -ne 13) {
                break
            }
            Write-Host $str_list[$i] -f $color
            $i++
        }
        $end = if ($i -lt $str_list.Count) { $false }else { $true }
        if ($end) {
            Write-Host ' '
            Write-Host '(End)' -f Black -b White -NoNewline
            while ($end) {
                $keyCode = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown').VirtualKeyCode
                if ($keyCode -ne 13) {
                    Write-Host ' '
                    break
                }
            }
        }
    }
    else {
        foreach ($_ in $str_list) { Write-Host $_ -f $color }
    }
}
Add-Member -InputObject $PSCompletions -MemberType ScriptMethod show_with_less_table {
    param (
        [array]$str_list,
        [array]$header = @('key', 'value', 5),
        [scriptblock]$do = {},
        [int]$show_line = [System.Console]::WindowHeight - 7
    )
    $str_list = @(
        @{
            content = "`n{0,-$($header[2] + 3)} {1}" -f $header[0], $header[1]
            color   = 'Cyan'
        },
        @{
            content = "{0,-$($header[2] + 3)} {1}" -f ('-' * $header[0].Length), ('-' * $header[1].Length)
            color   = 'Cyan'
        }
    ) + $str_list
    $i = 0
    $need_less = [System.Console]::WindowHeight -lt ($str_list.Count + 2)
    if ($need_less) {
        $lines = $str_list.Count - $show_line
        $PSCompletions.write_with_color($PSCompletions.replace_content($PSCompletions.info.less_tip))
        & $do
        while ($i -lt $str_list.Count -and $i -lt $show_line) {
            if ($str_list[$i].bgColor) {
                Write-Host $str_list[$i].content -f $str_list[$i].color -b $str_list[$i].bgColor
            }
            else {
                Write-Host $str_list[$i].content -f $str_list[$i].color
            }
            $i++
        }
        while ($i -lt $str_list.Count) {
            $keyCode = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown').VirtualKeyCode
            if ($keyCode -ne 13) {
                break
            }
            if ($str_list[$i].bgColor) {
                Write-Host $str_list[$i].content -f $str_list[$i].color -b $str_list[$i].bgColor
            }
            else { Write-Host $str_list[$i].content -f $str_list[$i].color }
            $i++
        }
        $end = if ($i -lt $str_list.Count) { $false }else { $true }
        if ($end) {
            Write-Host ' '
            Write-Host '(End)' -f Black -b White -NoNewline
            while ($end) {
                $keyCode = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown').VirtualKeyCode
                if ($keyCode -ne 13) {
                    Write-Host ' '
                    break
                }
            }
        }
    }
    else {
        & $do
        foreach ($_ in $str_list) {
            if ($_.bgColor) {
                Write-Host $_.content -f $_.color -b $_.bgColor[2]
            }
            else {
                Write-Host $_.content -f $_.color
            }
        }
    }
}
Add-Member -InputObject $PSCompletions -MemberType ScriptMethod confirm_do {
    param ([string]$tip, [scriptblock]$confirm_event, [bool]$write_empty_line = $true)
    $PSCompletions.write_with_color($PSCompletions.replace_content($tip))

    while (($PressKey = $host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')).VirtualKeyCode) {
        if ($PressKey.ControlKeyState -notlike '*CtrlPressed*') {
            if ($write_empty_line) { Write-Host '' }
            if ($PressKey.VirtualKeyCode -eq 13) {
                # 13: Enter
                & $confirm_event
                return $true
            }
            else {
                $PSCompletions.write_with_color($PSCompletions.replace_content($PSCompletions.info.confirm_cancel))
                return $false
            }
        }
    }
}
Add-Member -InputObject $PSCompletions -MemberType ScriptMethod download_list {
    if (!(Test-Path $PSCompletions.path.completions_json)) {
        @{ list = @('psc') } | ConvertTo-Json -Compress | Out-File $PSCompletions.path.completions_json -Encoding utf8 -Force
    }
    $current_list = ($PSCompletions.get_raw_content($PSCompletions.path.completions_json) | ConvertFrom-Json).list
    try {
        $content = (Invoke-WebRequest -Uri "$($PSCompletions.url)/completions.json").Content | ConvertFrom-Json

        $remote_list = $content.list

        $diff = Compare-Object $remote_list $current_list -PassThru
        if ($diff) {
            $diff | Out-File $PSCompletions.path.change -Force -Encoding utf8
            $content | ConvertTo-Json -Depth 100 -Compress | Out-File $PSCompletions.path.completions_json -Encoding utf8 -Force
            $PSCompletions.list = $remote_list
        }
        else {
            Clear-Content $PSCompletions.path.change -Force
            $PSCompletions.list = $current_list
        }
        return $remote_list
    }
    catch {
        $PSCompletions.list = $current_list
        $PSCompletions._invalid_url = "$($PSCompletions.url)/completions.json"
        return $false
    }
}
Add-Member -InputObject $PSCompletions -MemberType ScriptMethod download_file {
    param([string]$url, [string]$file)
    try {
        $PSCompletions.wc.DownloadFile($url, $file)
    }
    catch {
        if ($PSCompletions.info) {
            $download_info = @{
                url  = $url
                file = $file
            }
            $PSCompletions.write_with_color($PSCompletions.replace_content($PSCompletions.info.err.download_file))
        }
        else {
            Write-Host "File ($(Split-Path $url -Leaf)) download failed, please check your network connection or try again later." -ForegroundColor Red
            Write-Host "File download Url: $url" -ForegroundColor Red
            Write-Host "File save path: $file" -ForegroundColor Red
            Write-Host "If you are sure that it is not a network problem, please submit an issue`n" -ForegroundColor Red
        }
        throw $_
    }
}
Add-Member -InputObject $PSCompletions -MemberType ScriptMethod add_completion {
    param (
        [string]$completion,
        [bool]$log = $true,
        [bool]$is_update = $true
    )
    $url = "$($PSCompletions.url)/completions/$completion"

    $is_update = (Test-Path "$($PSCompletions.path.completions)/$completion") -and $is_update

    $completion_dir = Join-Path $PSCompletions.path.completions $completion
    $language_dir = Join-Path $completion_dir 'language'

    $PSCompletions.ensure_dir($PSCompletions.path.completions)
    $PSCompletions.ensure_dir($completion_dir)
    $PSCompletions.ensure_dir($language_dir)

    $download_info = @{
        url  = "$url/config.json"
        file = Join-Path $completion_dir 'config.json'
    }
    $PSCompletions.download_file($download_info.url, $download_info.file)

    $config = $PSCompletions.get_raw_content("$completion_dir/config.json") | ConvertFrom-Json
    $config | ConvertTo-Json -Compress -Depth 100 | Out-File $download_info.file -Encoding utf8 -Force

    $files = @(
        @{
            Uri     = "$url/guid.txt"
            OutFile = Join-Path $completion_dir 'guid.txt'
        }
    )
    foreach ($_ in $config.language) {
        $files += @{
            Uri     = "$url/language/$_.json"
            OutFile = Join-Path $language_dir "$_.json"
        }
    }
    if ($config.hooks) {
        $files += @{
            Uri     = "$url/hooks.ps1"
            OutFile = Join-Path $completion_dir 'hooks.ps1'
        }
    }

    foreach ($file in $files) {
        $download_info = @{
            url  = $file.Uri
            file = $file.OutFile
        }
        try {
            $PSCompletions.wc.DownloadFile($download_info.url, $download_info.file)
            if ($download_info.file -match '\.json$') {
                $PSCompletions.get_raw_content($download_info.file) | ConvertFrom-Json | ConvertTo-Json -Compress -Depth 100 | Out-File $download_info.file -Encoding utf8 -Force
            }
        }
        catch {
            Remove-Item $completion_dir -Force -Recurse -ErrorAction SilentlyContinue
            throw
        }
    }

    # 显示下载信息
    $download = if ($is_update) { $PSCompletions.info.update.doing }else { $PSCompletions.info.add.doing }
    if ($log) { $PSCompletions.write_with_color("`n" + $PSCompletions.replace_content($download)) }

    $done = if ($is_update) { $PSCompletions.info.update.done }else { $PSCompletions.info.add.done }

    if ($completion -notin $PSCompletions.data.list) {
        $PSCompletions.data.list += $completion
        $PSCompletions._need_update_data = $true
    }
    if (!$PSCompletions.data.alias.$completion) {
        $PSCompletions.data.alias.$completion = @()
    }

    $PSCompletions._alias_conflict = $false
    $conflict_alias_list = @()
    if ($config.alias) {
        foreach ($a in $config.alias) {
            if ($a -notin $PSCompletions.data.alias.$completion) {
                $PSCompletions.data.alias.$completion += $a
                if ($PSCompletions.data.aliasMap.$a) {
                    $PSCompletions._alias_conflict = $true
                    $conflict_alias_list += $a
                }
                else {
                    $PSCompletions.data.aliasMap.$a = $completion
                }
                $PSCompletions._need_update_data = $true
            }
        }
    }
    else {
        if ($completion -notin $PSCompletions.data.alias.$completion) {
            $PSCompletions.data.alias.$completion += $completion
            if ($PSCompletions.data.aliasMap.$completion) {
                $PSCompletions._alias_conflict = $true
                $conflict_alias_list += $completion
            }
            else {
                $PSCompletions.data.aliasMap.$completion = $completion
            }
            $PSCompletions._need_update_data = $true
        }
    }

    if ($PSCompletions._alias_conflict) {
        $PSCompletions.write_with_color($PSCompletions.replace_content($PSCompletions.info.err.alias_conflict))
    }

    $language = $PSCompletions.get_language($completion)
    $json = $PSCompletions.ConvertFrom_JsonToHashtable($PSCompletions.get_raw_content("$completion_dir/language/$language.json"))
    if (!$PSCompletions.completions) {
        $PSCompletions.completions = @{}
    }
    $PSCompletions.completions.$completion = $json

    if ($log) { $PSCompletions.write_with_color($PSCompletions.replace_content($done)) }

    if ($json.config) {
        if (!$PSCompletions.config.comp_config.$completion) {
            $PSCompletions.config.comp_config.$completion = @{}
        }
        foreach ($_ in $json.config) {
            if (!($PSCompletions.config.comp_config.$completion.$($_.name))) {
                $PSCompletions.config.comp_config.$completion.$($_.name) = $_.value
                $PSCompletions._need_update_data = $true
            }
        }
    }
}
Add-Member -InputObject $PSCompletions -MemberType ScriptMethod init_data {
    $PSCompletions.completions = @{}

    if (Test-Path $PSCompletions.path.data) {
        $PSCompletions.data = $PSCompletions.ConvertFrom_JsonToHashtable($PSCompletions.get_raw_content($PSCompletions.path.data))
    }
    else {
        $data = @{
            list     = @()
            alias    = @{}
            aliasMap = @{}
            config   = $PSCompletions.default_config
        }
        $data.config.comp_config = @{}
        foreach ($_ in Get-ChildItem -Path $PSCompletions.path.completions -Directory) {
            $name = $_.Name
            $data.list += $name
            $data.alias.$name = @()
            $path_config = Join-Path $_.FullName 'config.json'
            if (!(Test-Path $path_config)) {
                continue
            }
            $config = $PSCompletions.get_raw_content($path_config) | ConvertFrom-Json
            if ($config.alias) {
                foreach ($a in $config.alias) {
                    $data.alias.$name += $a
                    $data.aliasMap.$a = $name
                }
            }
            else {
                $data.alias.$name += $name
                $data.aliasMap.$name = $name
            }
            $language = if ($PSCompletions.language -eq 'zh-CN') { 'zh-CN' }else { 'en-US' }
            $json = $PSCompletions.ConvertFrom_JsonToHashtable($PSCompletions.get_raw_content("$($_.FullName)/language/$language.json"))
            $data.config.comp_config.$name = @{}
            foreach ($_ in $json.config) {
                $data.config.comp_config.$name.$($_.name) = $_.value
            }
        }
        $data | ConvertTo-Json -Depth 100 -Compress | Out-File $PSCompletions.path.data -Force -Encoding utf8
        $PSCompletions.data = $data
        $null = $PSCompletions.download_list()
    }
    $PSCompletions.config = $PSCompletions.data.config
    $PSCompletions.language = $PSCompletions.config.language

    if ($PSCompletions.config.url) {
        $PSCompletions.url = $PSCompletions.config.url
    }
    else {
        if ($PSCompletions.language -eq 'zh-CN') {
            $PSCompletions.url = 'https://gitee.com/abgox/PSCompletions/raw/main'
        }
        else {
            $PSCompletions.url = 'https://raw.githubusercontent.com/abgox/PSCompletions/main'
        }
    }

    $PSCompletions.list = ($PSCompletions.get_raw_content($PSCompletions.path.completions_json) | ConvertFrom-Json).list

    $PSCompletions.update = $PSCompletions.get_content($PSCompletions.path.update)
    if ('psc' -notin $PSCompletions.data.list -or $PSCompletions._update_version) {
        $PSCompletions.add_completion('psc', $false, $false)
        $PSCompletions.data | ConvertTo-Json -Depth 100 -Compress | Out-File $PSCompletions.path.data -Force -Encoding utf8
        $PSCompletions._update_version = $null
    }
    if ($PSCompletions.completions.psc.info) {
        $PSCompletions.info = $PSCompletions.completions.psc.info
    }
    else {
        $language = if ($PSCompletions.language -eq 'zh-CN') { 'zh-CN' }else { 'en-US' }
        $PSCompletions.info = $PSCompletions.ConvertFrom_JsonToHashtable($PSCompletions.get_raw_content("$($PSCompletions.path.completions)/psc/language/$language.json")).info
    }
}
Add-Member -InputObject $PSCompletions.menu -MemberType ScriptMethod get_length {
    param([string]$str)
    $Host.UI.RawUI.NewBufferCellArray($str, $Host.UI.RawUI.BackgroundColor, $Host.UI.RawUI.BackgroundColor).LongLength
}
Add-Member -InputObject $PSCompletions.menu -MemberType ScriptMethod show_powershell_menu {
    param([array]$filter_list)
    if ($Host.UI.RawUI.BufferSize.Height -lt 5) {
        [Microsoft.PowerShell.PSConsoleReadLine]::UndoAll()
        [Microsoft.PowerShell.PSConsoleReadLine]::Insert($PSCompletions.info.min_area)
        ''
        return
    }

    # is_show_tip
    if ($PSCompletions.current_cmd -ne $null) {
        $json = $PSCompletions.completions.$($PSCompletions.current_cmd)
        $info = $json.info

        $enable_tip = $PSCompletions.config.comp_config.$($PSCompletions.current_cmd).enable_tip
        if ($enable_tip -ne $null) {
            $PSCompletions.menu.is_show_tip = $enable_tip -eq 1
        }
        else {
            $PSCompletions.menu.is_show_tip = $PSCompletions.config.enable_tip -eq 1
        }
    }
    else {
        $PSCompletions.menu.is_show_tip = $PSCompletions.config.enable_tip -eq 1
    }

    if ($PSCompletions.menu.is_show_tip) {
        foreach ($_ in $filter_list) {
            if ($_.ToolTip -ne $null) {
                $tip = $PSCompletions.replace_content($_.ToolTip)
                $tip_arr = $tip -split "`n"
            }
            else {
                $tip = ' '
                $tip_arr = @()
            }
            $PSCompletions.menu.tip_max_height = [Math]::Max($PSCompletions.menu.tip_max_height, $tip_arr.Count)
            [CompletionResult]::new($_.CompletionText, $_.ListItemText, 'ParameterValue', $tip)
        }
    }
    else {
        foreach ($_ in $filter_list) {
            [CompletionResult]::new($_.CompletionText, $_.ListItemText, 'ParameterValue', ' ')
        }
    }
    $PSCompletions.current_cmd = $null
}

Add-Member -InputObject $PSCompletions -MemberType ScriptMethod argc_completions {
    param(
        [array]$completions, # The list of completions.
        [bool]$isShowTip = $PSCompletions.config.enable_tip_when_enhance # Set whether to display the tooltip or not.
    )

    $PSCompletions.menu.is_show_tip = $isShowTip
    foreach ($_ in $completions) {
        Register-ArgumentCompleter -Native -CommandName $_ -ScriptBlock {
            param($wordToComplete, $commandAst, $cursorPosition)
            $words = @($commandAst.CommandElements | Where { $_.Extent.StartOffset -lt $cursorPosition } | ForEach-Object {
                    $word = $_.ToString()
                    if ($word.Length -gt 2) {
                        if (($word.StartsWith('"') -and $word.EndsWith('"')) -or ($word.StartsWith("'") -and $word.EndsWith("'"))) {
                            $word = $word.Substring(1, $word.Length - 2)
                        }
                    }
                    return $word
                })
            $emptyS = ''
            if ($PSVersionTable.PSVersion.Major -eq 5) {
                $emptyS = '""'
            }
            $lastElemIndex = -1
            if ($words.Count -lt $commandAst.CommandElements.Count) {
                $lastElemIndex = $words.Count - 1
            }
            if ($commandAst.CommandElements[$lastElemIndex].Extent.EndOffset -lt $cursorPosition) {
                $words += $emptyS
            }
            @((argc --argc-compgen powershell $emptyS $words) -split "`n") | ForEach-Object {
                $parts = ($_ -split "`t")

                if ($PSCompletions.menu.is_show_tip) {
                    $tip = if ($parts[3] -eq '') { ' ' }else { $parts[3] }
                    [CompletionResult]::new($parts[0], $parts[0], [CompletionResultType]::ParameterValue, $tip)
                }
                else {
                    [CompletionResult]::new($parts[0], $parts[0], [CompletionResultType]::ParameterValue, ' ')
                }
            }
        }
    }
}

if (!(Test-Path $PSCompletions.path.temp)) {
    $PSCompletions.ensure_dir($PSCompletions.path.temp)
    Add-Member -InputObject $PSCompletions -MemberType ScriptMethod move_old_version {
        $version = (Get-ChildItem (Split-Path $PSCompletions.path.root -Parent) -ErrorAction SilentlyContinue).Name | Sort-Object { [Version]$_ } -ErrorAction SilentlyContinue | Where-Object { $_ -match '^\d+\.\d.*' }
        if ($version -is [array]) {
            $old_version = $version[-2]
            if ($old_version -match '^\d+\.\d.*' -and $old_version -ge '4') {
                $PSCompletions._update_version = $true
                $old_version_dir = Join-Path (Split-Path $PSCompletions.path.root -Parent) $old_version
                $PSCompletions.ensure_dir($PSCompletions.path.completions)

                if (Test-Path "$old_version_dir/data.json") {
                    foreach ($_ in Get-ChildItem "$old_version_dir/completions" -Directory -ErrorAction SilentlyContinue) {
                        if ($_.Name -ne 'psc') {
                            Move-Item $_.FullName $PSCompletions.path.completions -Force -ErrorAction SilentlyContinue
                        }
                    }
                    Move-Item "$old_version_dir/data.json" $PSCompletions.path.data -Force -ErrorAction SilentlyContinue
                }
                else {
                    $data = @{
                        list     = @()
                        alias    = @{}
                        aliasMap = @{}
                        config   = $PSCompletions.default_config
                    }
                    $data.config.comp_config = @{}
                    foreach ($_ in Get-ChildItem "$old_version_dir/completions" -Directory -ErrorAction SilentlyContinue) {
                        $name = $_.Name
                        $data.list += $name
                        $data.alias.$name = @()
                        $path_alias = Join-Path $_.FullName 'alias.txt'
                        if (Test-Path $path_alias) {
                            $alias_list = $PSCompletions.get_content($path_alias)
                            foreach ($a in $alias_list) {
                                $data.alias.$name += $a
                                $data.aliasMap.$a = $name
                            }
                        }
                        else {
                            $path_config = Join-Path $_.FullName 'config.json'
                            $config = $PSCompletions.get_raw_content($path_config) | ConvertFrom-Json
                            if ($config.alias) {
                                foreach ($a in $config.alias) {
                                    $data.alias.$name += $a
                                    $data.aliasMap.$a = $name
                                }
                            }
                            else {
                                $data.alias.$name += $name
                                $data.aliasMap.$name = $name
                            }
                        }

                        $path_data_config = "$old_version_dir/config.json"
                        if (Test-Path $path_data_config) {
                            $configMap = @{
                                url                          = 'url'
                                language                     = 'language'
                                update                       = 'enable_completions_update'
                                module_update                = 'enable_module_update'
                                disable_cache                = 'disable_cache'
                                function_name                = 'function_name'
                                symbol_SpaceTab              = 'SpaceTab'
                                symbol_WriteSpaceTab         = 'WriteSpaceTab'
                                symbol_OptionTab             = 'OptionTab'
                                menu_line_horizontal         = 'horizontal'
                                menu_line_vertical           = 'vertical'
                                menu_line_top_left           = 'top_left'
                                menu_line_bottom_left        = 'bottom_left'
                                menu_line_top_right          = 'top_right'
                                menu_line_bottom_right       = 'bottom_right'
                                menu_color_item_text         = 'item_text'
                                menu_color_item_back         = 'item_back'
                                menu_color_selected_text     = 'selected_text'
                                menu_color_selected_back     = 'selected_back'
                                menu_color_filter_text       = 'filter_text'
                                menu_color_filter_back       = 'filter_back'
                                menu_color_border_text       = 'border_text'
                                menu_color_border_back       = 'border_back'
                                menu_color_status_text       = 'status_text'
                                menu_color_status_back       = 'status_back'
                                menu_color_tip_text          = 'tip_text'
                                menu_color_tip_back          = 'tip_back'
                                menu_trigger_key             = 'trigger_key'
                                menu_between_item_and_symbol = 'between_item_and_symbol'
                                menu_status_symbol           = 'status_symbol'
                                menu_filter_symbol           = 'filter_symbol'
                                menu_enable                  = 'enable_menu'
                                menu_enhance                 = 'enable_menu_enhance'
                                menu_show_tip                = 'enable_tip'
                                menu_show_tip_when_enhance   = 'enable_tip_when_enhance'
                                menu_completions_sort        = 'enable_completions_sort'
                                menu_tip_follow_cursor       = 'enable_tip_follow_cursor'
                                menu_list_follow_cursor      = 'enable_list_follow_cursor'
                                menu_tip_cover_buffer        = 'enable_tip_cover_buffer'
                                menu_list_cover_buffer       = 'enable_list_cover_buffer'
                                menu_is_loop                 = 'enable_list_loop'
                                menu_selection_with_margin   = 'enable_selection_with_margin'
                                enter_when_single            = 'enable_enter_when_single'
                                menu_is_prefix_match         = 'enable_prefix_match_in_filter'
                                menu_list_min_width          = 'list_min_width'
                                menu_above_list_max_count    = 'list_max_count_when_above'
                                menu_below_list_max_count    = 'list_max_count_when_below'
                                menu_list_margin_left        = 'width_from_menu_left_to_item'
                                menu_list_margin_right       = 'width_from_menu_right_to_item'
                                menu_above_margin_bottom     = 'height_from_menu_bottom_to_cursor_when_above'
                            }

                            $data_config = $PSCompletions.ConvertFrom_JsonToHashtable($PSCompletions.get_raw_content($path_data_config))
                            foreach ($c in $data_config.Keys) {
                                if ($configMap[$c]) {
                                    $data.config.$($configMap[$c]) = $data_config.$c
                                }
                            }
                        }
                        if ($_.Name -ne 'psc') {
                            Move-Item $_.FullName $PSCompletions.path.completions -Force -ErrorAction SilentlyContinue
                        }
                    }
                    $data | ConvertTo-Json -Depth 100 -Compress | Out-File $PSCompletions.path.data -Force -Encoding utf8
                }
                Move-Item "$old_version_dir/temp/update.txt" $PSCompletions.path.update -Force -ErrorAction SilentlyContinue
                Move-Item "$old_version_dir/temp/change.txt" $PSCompletions.path.change -Force -ErrorAction SilentlyContinue
                Move-Item "$old_version_dir/temp/completions.json" $PSCompletions.path.completions_json -Force -ErrorAction SilentlyContinue
            }
        }
        else {
            if ($PSUICulture -eq 'zh-CN') {
                $language = 'zh-CN'
                $PSCompletions.url = 'https://gitee.com/abgox/PSCompletions/raw/main'
            }
            else {
                $language = 'en-US'
                $PSCompletions.url = 'https://raw.githubusercontent.com/abgox/PSCompletions/main'
            }

            $PSCompletions.ensure_dir("$($PSCompletions.path.completions)/psc/language")

            $file_list = @('language/zh-CN.json', 'language/en-US.json', 'config.json', 'guid.txt', 'hooks.ps1')
            foreach ($_ in $file_list) {
                $outFile = "$($PSCompletions.path.completions)/psc/$_"
                $PSCompletions.download_file("$($PSCompletions.url)/completions/psc/$_", $outFile)
                if ($outFile -match '\.json$') {
                    $PSCompletions.get_raw_content($outFile) | ConvertFrom-Json | ConvertTo-Json -Compress -Depth 100 | Out-File $outFile -Encoding utf8 -Force
                }
            }
            $PSCompletions.info = $PSCompletions.ConvertFrom_JsonToHashtable($PSCompletions.get_raw_content("$($PSCompletions.path.completions)/psc/language/$language.json")).info
            $PSCompletions.write_with_color($PSCompletions.replace_content($PSCompletions.info.init_info))
        }
    }
    $PSCompletions.move_old_version()
}

$PSCompletions.init_data()

Set-PSReadLineKeyHandler $PSCompletions.config.trigger_key MenuComplete
$PSCompletions.generate_completion()
$PSCompletions.handle_completion()

foreach ($_ in $PSCompletions.data.aliasMap.Keys) {
    if ($PSCompletions.data.aliasMap.$_ -eq 'psc') {
        Set-Alias $_ $PSCompletions.config.function_name
    }
    else {
        if ($_ -ne $PSCompletions.data.aliasMap.$_) {
            Set-Alias $_ $PSCompletions.data.aliasMap.$_
        }
    }
}

if ($PSCompletions.config.enable_module_update -notin @(0, 1)) {
    $PSCompletions.version_list = $PSCompletions.config.enable_module_update, $PSCompletions.version | Sort-Object { [version] $_ } -Descending -ErrorAction SilentlyContinue
    if ($PSCompletions.version_list[0] -ne $PSCompletions.version) {
        $PSCompletions.wc.DownloadFile("$($PSCompletions.url)/module/CHANGELOG.json", (Join-Path $PSCompletions.path.temp 'CHANGELOG.json'))
        $null = $PSCompletions.confirm_do($PSCompletions.info.module.update, {
                $cmd_list = @(
                    "Update-Module PSCompletions -RequiredVersion $($PSCompletions.version_list[0]) -Force -ErrorAction Stop",
                    "Update-PSResource PSCompletions -Version $($PSCompletions.version_list[0]) -Force -ErrorAction Stop",
                    "Scoop update pscompletions"
                )
                foreach ($update_cmd in $cmd_list) {
                    try {
                        $PSCompletions.write_with_color($PSCompletions.replace_content($PSCompletions.info.module.updating))
                        Invoke-Expression $update_cmd
                        break
                    }
                    catch {
                        Write-Host $_ -ForegroundColor Red
                    }
                }
            })
    }
    else {
        $PSCompletions.config.enable_module_update = 1
        $PSCompletions.data | ConvertTo-Json -Depth 100 -Compress | Out-File $PSCompletions.path.data -Force -Encoding utf8
    }
}
else {
    if ($PSCompletions.config.enable_completions_update -eq 1) {
        if ($PSCompletions.update -or $PSCompletions.get_content($PSCompletions.path.change)) {
            $PSCompletions.write_with_color($PSCompletions.replace_content($PSCompletions.info.update_info))
        }
    }
}

$PSCompletions.start_job()