core/menu/win.ps1
Add-Member -InputObject $PSCompletions.menu -MemberType ScriptMethod handle_list_first { param([array]$filter_list) $max_width = 0 $PSCompletions.menu.ui_size.width = $PSCompletions.menu.list_max_width + 2 + $PSCompletions.menu.config.width_from_menu_left_to_item + $PSCompletions.menu.config.width_from_menu_right_to_item if ($PSCompletions.menu.is_show_tip) { $filterListTasks = @() $runspacePool = [runspacefactory]::CreateRunspacePool(1, [Environment]::ProcessorCount) $runspacePool.Open() $scriptBlock = { param ($items, $PSCompletions, $Host_UI) function Get-MultilineTruncatedString { param ([string]$inputString, $Host_UI = $Host.UI) # 指定一行的最大宽度 $lineWidth = $Host_UI.RawUI.BufferSize.Width if ($PSCompletions.config.enable_tip_follow_cursor -eq 1) { $w = $lineWidth - $Host_UI.RawUI.CursorPosition.X $lineWidth = [Math]::Max($w, $PSCompletions.menu.ui_size.Width) } $PSCompletions.menu.tip_width = $lineWidth # 初始化一些变量 $currentWidth = 0 $outputString = "" $currentLine = "" foreach ($char in $inputString.ToCharArray()) { # 获取当前字符的宽度 $charWidth = [System.Text.Encoding]::UTF8.GetByteCount($char) # 如果添加这个字符会超过最大宽度,就换行 if ($currentWidth + $charWidth -gt $lineWidth) { # 添加当前行到输出字符串并换行 $outputString += $currentLine + "`n" # 重置当前行和宽度 $currentLine = "" $currentWidth = 0 } # 添加字符到当前行,并更新当前宽度 $currentLine += $char $currentWidth += $charWidth } # 添加最后一行到输出字符串 $outputString += $currentLine return $outputString } 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 } } if ($PSCompletions.current_cmd -ne $null) { $json = $PSCompletions.completions.$($PSCompletions.current_cmd) $info = $json.info } foreach ($item in $items) { $tip_arr = @() if ($item.ToolTip -ne $null) { $tip = _replace $item.ToolTip foreach ($v in ($tip -split "`n")) { $tip_arr += (Get-MultilineTruncatedString $v $Host_UI) -split "`n" } } @{ ListItemText = $item.ListItemText CompletionText = $item.CompletionText ToolTip = $tip_arr } } } foreach ($arr in $PSCompletions.split_array($filter_list, [Environment]::ProcessorCount, $true)) { $filterListTasks += [powershell]::Create().AddScript($scriptBlock).AddArgument($arr).AddArgument($PSCompletions).AddArgument($Host.UI) } $runspaces = foreach ($task in $filterListTasks) { $task.RunspacePool = $runspacePool $job = $task.BeginInvoke() @{ Runspace = $task; Job = $job } } foreach ($rs in $runspaces) { $results = $rs.Runspace.EndInvoke($rs.Job) $rs.Runspace.Dispose() foreach ($result in $results) { $PSCompletions.menu.tip_max_height = [Math]::Max($PSCompletions.menu.tip_max_height, $result.ToolTip.Count) $result = @{ ListItemText = $result.ListItemText CompletionText = $result.CompletionText ToolTip = $result.ToolTip } $PSCompletions.menu.origin_filter_list += $result $PSCompletions.menu.filter_list += $result } } $runspacePool.Close() $runspacePool.Dispose() } else { $PSCompletions.menu.origin_filter_list = [array]$filter_list $PSCompletions.menu.filter_list = [array]$filter_list } } Add-Member -InputObject $PSCompletions.menu -MemberType ScriptMethod parse_list { # X if ($PSCompletions.config.enable_list_follow_cursor -eq 1) { $PSCompletions.menu.pos.X = $Host.UI.RawUI.CursorPosition.X # 如果跟随鼠标,且超过右侧边界,则向左偏移 $PSCompletions.menu.pos.X = [Math]::Min($PSCompletions.menu.pos.X, $Host.UI.RawUI.BufferSize.Width - 1 - $PSCompletions.menu.ui_size.Width) } else { $PSCompletions.menu.pos.X = 0 } # 当为 0 时,有几率出现渲染错误,所以坐标右移一点 if ($PSCompletions.menu.pos.x -eq 0) { $PSCompletions.menu.pos.x ++ } if ($PSCompletions.config.enable_tip_follow_cursor -eq 1) { $PSCompletions.menu.pos_tip.X = $PSCompletions.menu.pos.X } else { $PSCompletions.menu.pos_tip.X = 0 } # Y $PSCompletions.menu.cursor_to_bottom = $Host.UI.RawUI.BufferSize.Height - $Host.UI.RawUI.CursorPosition.Y - 1 $PSCompletions.menu.cursor_to_top = $Host.UI.RawUI.CursorPosition.Y - $PSCompletions.config.height_from_menu_bottom_to_cursor_when_above - 1 $PSCompletions.menu.is_show_above = if ($PSCompletions.menu.cursor_to_bottom -ge $PSCompletions.menu.cursor_to_top) { $false }else { $true } $PSCompletions.menu.ui_size.height = $PSCompletions.menu.filter_list.Count + 2 if ($PSCompletions.menu.is_show_above) { if ($PScompletions.config.list_max_count_when_above -ne -1) { $PSCompletions.menu.ui_size.height = [Math]::Min($PSCompletions.menu.ui_size.height, $PScompletions.config.list_max_count_when_above + 2) } else { $PSCompletions.menu.ui_size.height = [Math]::Min($PSCompletions.menu.ui_size.height, $PSCompletions.menu.cursor_to_top) } } else { if ($PScompletions.config.list_max_count_when_below -ne -1) { $PSCompletions.menu.ui_size.height = [Math]::Min($PSCompletions.menu.ui_size.height, $PScompletions.config.list_max_count_when_below + 2) } else { $PSCompletions.menu.ui_size.height = [Math]::Min($PSCompletions.menu.ui_size.height, $PSCompletions.menu.cursor_to_bottom) } } if ($PSCompletions.menu.is_show_above) { $new_ui_height = $PSCompletions.menu.cursor_to_top - $PSCompletions.menu.tip_max_height - 2 if ($new_ui_height -lt 5) { $PSCompletions.menu.is_show_tip = $false } else { $PSCompletions.menu.ui_size.height = [Math]::Min($new_ui_height, $PSCompletions.menu.filter_list.Count + 2) } $PSCompletions.menu.ui_size.height = [Math]::Max($PSCompletions.menu.ui_size.height, 3) $PSCompletions.menu.pos.Y = $Host.UI.RawUI.CursorPosition.Y - $PSCompletions.menu.ui_size.height - $PSCompletions.config.height_from_menu_bottom_to_cursor_when_above # 设置 tip 的 起始位置 $PSCompletions.menu.pos_tip.Y = $PSCompletions.menu.pos.Y - $PSCompletions.menu.tip_max_height - 1 if ($PSCompletions.menu.pos_tip.Y -lt 0) { $PSCompletions.menu.is_show_tip = $false } } else { $new_ui_height = $PSCompletions.menu.cursor_to_bottom - $PSCompletions.menu.tip_max_height - 2 if ($new_ui_height -lt 5) { $PSCompletions.menu.is_show_tip = $false } else { $PSCompletions.menu.ui_size.height = [Math]::Min($new_ui_height, $PSCompletions.menu.filter_list.Count + 2) } $PSCompletions.menu.ui_size.height = [Math]::Max($PSCompletions.menu.ui_size.height, 3) $PSCompletions.menu.pos.Y = $Host.UI.RawUI.CursorPosition.Y + 1 $PSCompletions.menu.pos_tip.Y = $PSCompletions.menu.pos.Y + $PSCompletions.menu.ui_size.height + 1 if ($PSCompletions.menu.pos_tip.Y -ge $Host.UI.RawUI.BufferSize.Height - 1) { $PSCompletions.menu.is_show_tip = $false } } $PSCompletions.menu.page_max_index = $PSCompletions.menu.ui_size.height - 3 } Add-Member -InputObject $PSCompletions.menu -MemberType ScriptMethod get_buffer { param($startPos, $endPos) $top = New-Object System.Management.Automation.Host.Coordinates $startPos.X, $startPos.Y $bottom = New-Object System.Management.Automation.Host.Coordinates $endPos.X , $endPos.Y $buffer = $Host.UI.RawUI.GetBufferContents((New-Object System.Management.Automation.Host.Rectangle $top, $bottom)) @{ top = $top bottom = $bottom buffer = $buffer } } Add-Member -InputObject $PSCompletions.menu -MemberType ScriptMethod get_pos { if ($PSCompletions.menu.is_show_above) { $menu_start_pos = @{ X = $Host.UI.RawUI.WindowPosition.X Y = 0 } $menu_end_pos = @{ X = $PSCompletions.menu.pos.X + $PSCompletions.menu.ui_size.Width Y = $Host.UI.RawUI.CursorPosition.Y - 1 - $PSCompletions.config.height_from_menu_bottom_to_cursor_when_above } $menu_start_pos.Y = [Math]::Max($menu_start_pos.Y, $PSCompletions.menu.pos.Y) } else { $menu_start_pos = $PSCompletions.menu.pos $menu_end_pos = @{ X = $menu_start_pos.X + $PSCompletions.menu.ui_size.Width Y = $menu_start_pos.Y + $PSCompletions.menu.ui_size.Height } $menu_end_pos.Y = [Math]::Min($menu_end_pos.Y, $Host.UI.RawUI.BufferSize.Height - 1) } if ($PSCompletions.menu.is_show_tip) { $tip_start_pos = @{ X = $Host.UI.RawUI.WindowPosition.X Y = $PSCompletions.menu.pos.Y - $PSCompletions.menu.tip_max_height - 1 } if ($PSCompletions.menu.is_show_above) { $tip_end_pos = @{ X = $Host.UI.RawUI.BufferSize.Width Y = $Host.UI.RawUI.CursorPosition.Y - 1 - $PSCompletions.config.height_from_menu_bottom_to_cursor_when_above } $tip_start_pos.Y = [Math]::Min($tip_start_pos.Y, $Host.UI.RawUI.WindowPosition.Y) } else { $tip_start_pos.Y = $PSCompletions.menu.pos.Y $tip_end_pos = @{ X = $Host.UI.RawUI.BufferSize.Width Y = $tip_start_pos.Y + $PSCompletions.menu.ui_size.Height + $PSCompletions.menu.tip_max_height + 1 } $tip_end_pos.Y = [Math]::Min($tip_end_pos.Y, $Host.UI.RawUI.BufferSize.Height - 1) } } @{ menu = @{ start_pos = $menu_start_pos end_pos = $menu_end_pos } tip = @{ start_pos = $tip_start_pos end_pos = $tip_end_pos } } } Add-Member -InputObject $PSCompletions.menu -MemberType ScriptMethod new_cover_buffer { if ($PSCompletions.config.enable_tip_cover_buffer -eq 1) { $box = @() $line = ' ' * $Host.UI.RawUI.BufferSize.Width if ($PSCompletions.menu.is_show_above) { foreach ($_ in 0..($PSCompletions.menu.pos.Y - 2)) { $box += $line } $pos = $Host.UI.RawUI.WindowPosition } else { foreach ($_ in 0..($Host.UI.RawUI.BufferSize.Height - $PSCompletions.menu.pos_tip.Y)) { $box += $line } $pos = @{ X = 0 Y = $PSCompletions.menu.pos_tip.Y - 1 } $pos.Y = [Math]::Min($pos.Y, $Host.UI.RawUI.BufferSize.Height - 1) } $buffer = $Host.UI.RawUI.NewBufferCellArray($box, $host.UI.RawUI.BackgroundColor, $host.UI.RawUI.BackgroundColor) $Host.UI.RawUI.SetBufferContents($pos, $buffer) } } Add-Member -InputObject $PSCompletions.menu -MemberType ScriptMethod new_buffer { if ($PSCompletions.config.enable_list_cover_buffer -eq 1) { $box = @() $line = ' ' * $Host.UI.RawUI.BufferSize.Width foreach ($_ in 0..$PSCompletions.menu.ui_size.Height) { $box += $line } $pos = @{ X = 0 Y = $PSCompletions.menu.pos.Y } if ($PSCompletions.menu.is_show_above) { $pos.Y -- } $buffer = $Host.UI.RawUI.NewBufferCellArray($box, $host.UI.RawUI.BackgroundColor, $host.UI.RawUI.BackgroundColor) $Host.UI.RawUI.SetBufferContents($pos, $buffer) } $border_box = @() $content_box = @() $line_top = [string]$PSCompletions.config.top_left + $PSCompletions.config.horizontal * ($PSCompletions.menu.list_max_width + $PSCompletions.config.width_from_menu_left_to_item + $PSCompletions.config.width_from_menu_right_to_item) + $PSCompletions.config.top_right $border_box += $line_top $line = [string]$PSCompletions.config.vertical + (' ' * ($PSCompletions.config.width_from_menu_left_to_item + $PSCompletions.menu.list_max_width + $PSCompletions.config.width_from_menu_right_to_item)) + [string]$PSCompletions.config.vertical $left = ' ' * $PSCompletions.config.width_from_menu_left_to_item $right = ' ' * $PSCompletions.config.width_from_menu_right_to_item foreach ($_ in 0..($PSCompletions.menu.ui_size.height - 3)) { $content_length = $PSCompletions.menu.get_length($PSCompletions.menu.filter_list[$_].ListItemText) $content = $PSCompletions.menu.filter_list[$_].ListItemText + ' ' * ($PSCompletions.menu.list_max_width - $content_length) $border_box += $line $content_box += $left + $content + $right } $status = "$(([string]($PSCompletions.menu.selected_index + 1)).PadLeft($PSCompletions.menu.filter_list.Count.ToString().Length, ' '))" $line_bottom = [string]$PSCompletions.config.bottom_left + $PSCompletions.config.horizontal * 2 + ' ' * ($status.Length + 1) + $PSCompletions.config.horizontal * ($PSCompletions.menu.list_max_width + $PSCompletions.config.width_from_menu_left_to_item + $PSCompletions.config.width_from_menu_right_to_item - $status.Length - 3) + $PSCompletions.config.bottom_right $border_box += $line_bottom $buffer = $Host.UI.RawUI.NewBufferCellArray($border_box, $PSCompletions.config.border_text, $PSCompletions.config.border_back) $Host.UI.RawUI.SetBufferContents($PSCompletions.menu.pos, $buffer) $buffer = $Host.UI.RawUI.NewBufferCellArray($content_box, $PSCompletions.config.item_text, $PSCompletions.config.item_back) $pos = @{ X = $PSCompletions.menu.pos.X + 1 Y = $PSCompletions.menu.pos.Y + 1 } $Host.UI.RawUI.SetBufferContents($pos, $buffer) } Add-Member -InputObject $PSCompletions.menu -MemberType ScriptMethod new_list_buffer { param([int]$offset) $content_box = @() $left = ' ' * $PSCompletions.config.width_from_menu_left_to_item $right = ' ' * $PSCompletions.config.width_from_menu_right_to_item foreach ($_ in $offset..($PSCompletions.menu.ui_size.height - 3 + $offset)) { $content_length = $PSCompletions.menu.get_length($PSCompletions.menu.filter_list[$_].ListItemText) $content = $PSCompletions.menu.filter_list[$_].ListItemText + ' ' * ($PSCompletions.menu.list_max_width - $content_length) $content_box += $left + $content + $right } $buffer = $Host.UI.RawUI.NewBufferCellArray($content_box, $PSCompletions.config.item_text, $PSCompletions.config.item_back) $pos = @{ X = $PSCompletions.menu.pos.X + 1 Y = $PSCompletions.menu.pos.Y + 1 } $Host.UI.RawUI.SetBufferContents($pos, $buffer) } Add-Member -InputObject $PSCompletions.menu -MemberType ScriptMethod new_filter_buffer { param([string]$filter) if ($PSCompletions.menu.old_filter_buffer) { $Host.UI.RawUI.SetBufferContents($PSCompletions.menu.old_filter_pos, $PSCompletions.menu.old_filter_buffer) } $X = $PSCompletions.menu.pos.X $Y = $PSCompletions.menu.pos.Y $old_top = New-Object System.Management.Automation.Host.Coordinates $X, $Y $old_bottom = New-Object System.Management.Automation.Host.Coordinates $Host.ui.RawUI.BufferSize.Width, $Y $old_buffer = $Host.UI.RawUI.GetBufferContents((New-Object System.Management.Automation.Host.Rectangle $old_top, $old_bottom)) $PSCompletions.menu.old_filter_buffer = $old_buffer # 之前的内容 $PSCompletions.menu.old_filter_pos = $old_top # 之前的位置 $char = $PSCompletions.config.filter_symbol $middle = [System.Math]::Ceiling($char.Length / 2) $start = $char.Substring(0, $middle) $end = $char.Substring($middle) $buffer_filter = $Host.UI.RawUI.NewBufferCellArray(@(@($start, $filter, $end) -join ''), $PSCompletions.config.filter_text, $PSCompletions.config.filter_back) $pos_filter = $Host.UI.RawUI.CursorPosition $pos_filter.X = $PSCompletions.menu.pos.X + 2 $pos_filter.Y = $PSCompletions.menu.pos.Y $Host.UI.RawUI.SetBufferContents($pos_filter, $buffer_filter) } Add-Member -InputObject $PSCompletions.menu -MemberType ScriptMethod new_status_buffer { if ($PSCompletions.menu.old_status_buffer) { $Host.UI.RawUI.SetBufferContents($PSCompletions.menu.old_status_pos, $PSCompletions.menu.old_status_buffer) } $X = $PSCompletions.menu.pos.X + 3 if ($PSCompletions.menu.is_show_above) { $Y = $Host.UI.RawUI.CursorPosition.Y - 1 - $PSCompletions.config.height_from_menu_bottom_to_cursor_when_above } else { $Y = $PSCompletions.menu.pos.Y + $PSCompletions.menu.ui_size.height - 1 } $old_top = New-Object System.Management.Automation.Host.Coordinates $X, $Y $old_bottom = New-Object System.Management.Automation.Host.Coordinates $Host.UI.RawUI.BufferSize.Width, $Y $old_buffer = $Host.UI.RawUI.GetBufferContents((New-Object System.Management.Automation.Host.Rectangle $old_top, $old_bottom)) $PSCompletions.menu.old_status_buffer = $old_buffer # 之前的内容 $PSCompletions.menu.old_status_pos = $old_top # 之前的位置 $current = "$(([string]($PSCompletions.menu.selected_index + 1)).PadLeft($PSCompletions.menu.filter_list.Count.ToString().Length, ' '))" $buffer_status = $Host.UI.RawUI.NewBufferCellArray(@("$current$($PSCompletions.config.status_symbol)$($PSCompletions.menu.filter_list.Count)"), $PSCompletions.config.status_text, $PSCompletions.config.status_back) $Host.UI.RawUI.SetBufferContents(@{ X = $X; Y = $Y }, $buffer_status) } Add-Member -InputObject $PSCompletions.menu -MemberType ScriptMethod get_old_tip_buffer { param([int]$X, [int]$Y) if ($PSCompletions.config.enable_tip_cover_buffer -eq 1) { if ($PSCompletions.menu.is_show_above) { $Y = 0 $to_Y = $PSCompletions.menu.pos.Y } else { $Y = $PSCompletions.menu.pos_tip.Y - 1 $to_Y = $Host.UI.RawUI.BufferSize.Height - 1 } } else { $Y = $PSCompletions.menu.pos_tip.Y - 1 if ($PSCompletions.menu.is_show_above) { $to_Y = $PSCompletions.menu.pos.Y } else { $to_Y = $PSCompletions.menu.pos_tip.Y + $PSCompletions.menu.tip_max_height } } $PSCompletions.menu.old_tip_buffer = $PSCompletions.menu.get_buffer(@{ X = 0; Y = $Y }, @{ X = $Host.UI.RawUI.BufferSize.Width; Y = $to_Y }) } Add-Member -InputObject $PSCompletions.menu -MemberType ScriptMethod new_tip_buffer { param([int]$index) $box = @() $line = ' ' * $Host.UI.RawUI.BufferSize.Width if ($PSCompletions.config.enable_tip_cover_buffer -eq 1) { if ($PSCompletions.menu.is_show_above) { foreach ($_ in 0..($PSCompletions.menu.pos.Y - 1)) { $box += $line } $pos = @{ X = 0 Y = 0 } } else { foreach ($_ in 0..($Host.UI.RawUI.BufferSize.Height - $PSCompletions.menu.pos_tip.Y)) { $box += $line } $pos = @{ X = 0 Y = $PSCompletions.menu.pos_tip.Y - 1 } } } else { foreach ($_ in 0..($PSCompletions.menu.tip_max_height + 1)) { $box += $line } $pos = @{ X = 0 Y = $PSCompletions.menu.pos_tip.Y - 1 } if ($PSCompletions.menu.is_show_above) { $pos.Y = [Math]::Max($pos.Y, 0) } } $buffer = $Host.UI.RawUI.NewBufferCellArray($box, $host.UI.RawUI.BackgroundColor, $host.UI.RawUI.BackgroundColor) $Host.UI.RawUI.SetBufferContents($pos, $buffer) if ($PSCompletions.menu.filter_list[$index].ToolTip -ne $null) { $pos = @{ X = $PSCompletions.menu.pos_tip.X Y = $PSCompletions.menu.pos_tip.Y } $buffer = $Host.UI.RawUI.NewBufferCellArray($PSCompletions.menu.filter_list[$index].ToolTip, $PSCompletions.config.tip_text, $PSCompletions.config.tip_back) $Host.UI.RawUI.SetBufferContents($pos, $buffer) } } Add-Member -InputObject $PSCompletions.menu -MemberType ScriptMethod set_selection { if ($PSCompletions.menu.old_selection) { $Host.UI.RawUI.SetBufferContents($PSCompletions.menu.old_selection.pos, $PSCompletions.menu.old_selection.buffer) } $X = $PSCompletions.menu.pos.X + 1 $to_X = $X + $PSCompletions.menu.list_max_width - 1 # 如果高亮需要包含 margin if ($PSCompletions.config.enable_selection_with_margin -eq 1) { $to_X += $PSCompletions.config.width_from_menu_left_to_item + $PSCompletions.config.width_from_menu_right_to_item } else { $X += $PSCompletions.config.width_from_menu_left_to_item } # 当前页的第几个 $Y = $PSCompletions.menu.pos.Y + 1 + $PSCompletions.menu.page_current_index # 根据坐标,生成需要被改变内容的矩形,也就是要被选中的项 $Rectangle = New-Object System.Management.Automation.Host.Rectangle $X, $Y, $to_X, $Y # 通过矩形,获取到这个矩形中的原本的内容 $LineBuffer = $Host.UI.RawUI.GetBufferContents($Rectangle) $PSCompletions.menu.old_selection = @{ pos = @{ X = $X Y = $Y } buffer = $LineBuffer } # 给原本的内容设置颜色和背景颜色 # XXX: 对于多字节字符,需要过滤掉 Trailing 类型字符以确保正确渲染 # $content = foreach ($i in $LineBuffer) { $i.Character } $content = foreach ($i in $LineBuffer.Where({ $_.BufferCellType -ne 'Trailing' })) { $i.Character } $LineBuffer = $Host.UI.RawUI.NewBufferCellArray( @([string]::Join('', $content)), $PSCompletions.config.selected_text, $PSCompletions.config.selected_back ) $Host.UI.RawUI.SetBufferContents(@{ X = $X; Y = $Y }, $LineBuffer) } Add-Member -InputObject $PSCompletions.menu -MemberType ScriptMethod move_selection { param([bool]$isDown) $moveDirection = if ($isDown) { 1 } else { -1 } $is_move = if ($isDown) { $PSCompletions.menu.page_current_index -lt $PSCompletions.menu.page_max_index } else { $PSCompletions.menu.page_current_index -gt 0 } $new_selected_index = $PSCompletions.menu.selected_index + $moveDirection if ($PSCompletions.config.enable_list_loop -eq 1) { $PSCompletions.menu.selected_index = ($new_selected_index + $PSCompletions.menu.filter_list.Count) % $PSCompletions.menu.filter_list.Count } else { # Handle no loop $PSCompletions.menu.selected_index = if ($new_selected_index -lt 0) { 0 } elseif ($new_selected_index -ge $PSCompletions.menu.filter_list.Count) { $PSCompletions.menu.filter_list.Count - 1 } else { $new_selected_index } } if ($is_move) { $PSCompletions.menu.page_current_index = ($PSCompletions.menu.page_current_index + $moveDirection) % ($PSCompletions.menu.page_max_index + 1) if ($PSCompletions.menu.page_current_index -lt 0) { $PSCompletions.menu.page_current_index += $PSCompletions.menu.page_max_index + 1 } } elseif ($PSCompletions.config.enable_list_loop -eq 1 -or ($new_selected_index -ge 0 -and $new_selected_index -lt $PSCompletions.menu.filter_list.Count)) { if (!$isDown -and $PSCompletions.menu.selected_index -eq $PSCompletions.menu.filter_list.Count - 1) { $PSCompletions.menu.page_current_index += $PSCompletions.menu.page_max_index } elseif ($isDown -and $PSCompletions.menu.selected_index -eq 0) { $PSCompletions.menu.page_current_index -= $PSCompletions.menu.page_max_index } $PSCompletions.menu.offset = ($PSCompletions.menu.offset + $moveDirection) % ($PSCompletions.menu.filter_list.Count - $PSCompletions.menu.page_max_index) if ($PSCompletions.menu.offset -lt 0) { $PSCompletions.menu.offset += $PSCompletions.menu.filter_list.Count - $PSCompletions.menu.page_max_index } $PSCompletions.menu.new_list_buffer($PSCompletions.menu.offset) $PSCompletions.menu.old_selection = $null } $PSCompletions.menu.set_selection() } Add-Member -InputObject $PSCompletions.menu -MemberType ScriptMethod get_prefix { $prefix = $PSCompletions.menu.filter_list[-1].CompletionText for ($i = $PSCompletions.menu.filter_list.count - 2; $i -ge 0 -and $prefix; --$i) { $text = $PSCompletions.menu.filter_list[$i].CompletionText if ($text -ne $null) { while ($prefix -and !$text.StartsWith($prefix, [StringComparison]::OrdinalIgnoreCase)) { $prefix = $prefix.Substring(0, $prefix.Length - 1) } } } $PSCompletions.menu.filter = $PSCompletions.menu.filter_by_auto_pick = $prefix } Add-Member -InputObject $PSCompletions.menu -MemberType ScriptMethod filter_completions { param([array]$filter_list) # 如果是前缀匹配 if ($PSCompletions.config.enable_prefix_match_in_filter -eq 1) { $match = "$($PSCompletions.menu.filter)*" } else { $match = "*$($PSCompletions.menu.filter)*" } $PSCompletions.menu.filter_list = @() foreach ($f in $filter_list) { if ($f.CompletionText -and $f.CompletionText -like $match) { $PSCompletions.menu.filter_list += $f } } } Add-Member -InputObject $PSCompletions.menu -MemberType ScriptMethod reset { param([bool]$clearAll, [bool]$enable_menu_enhance) # reset content if ($PSCompletions.menu.old_tip_buffer) { $Host.UI.RawUI.SetBufferContents($PSCompletions.menu.old_tip_buffer.top, $PSCompletions.menu.old_tip_buffer.buffer) } if ($PSCompletions.menu.old_menu_buffer) { $Host.UI.RawUI.SetBufferContents($PSCompletions.menu.old_menu_buffer.top, $PSCompletions.menu.old_menu_buffer.buffer) } if ($clearAll) { $PSCompletions.menu.old_tip_buffer = $null $PSCompletions.menu.old_menu_buffer = $null if ($PSCompletions.menu.origin_menu_buffer) { $Host.UI.RawUI.SetBufferContents($PSCompletions.menu.origin_menu_buffer.top, $PSCompletions.menu.origin_menu_buffer.buffer) $PSCompletions.menu.origin_menu_buffer = $null } if ($PSCompletions.menu.origin_tip_buffer) { $Host.UI.RawUI.SetBufferContents($PSCompletions.menu.origin_tip_buffer.top, $PSCompletions.menu.origin_tip_buffer.buffer) $PSCompletions.menu.origin_tip_buffer = $null } } $PSCompletions.menu.old_selection = $null $PSCompletions.menu.old_filter_buffer = $null $PSCompletions.menu.old_filter_pos = $null $PSCompletions.menu.old_status_buffer = $null $PSCompletions.menu.old_status_pos = $null $PSCompletions.menu.offset = 0 $PSCompletions.menu.selected_index = 0 $PSCompletions.menu.page_current_index = 0 if ($enable_menu_enhance) { $PSCompletions.menu.is_show_tip = $PSCompletions.config.enable_tip_when_enhance -eq 1 } else { if ($PSCompletions.current_cmd -ne $null) { $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 } } } Add-Member -InputObject $PSCompletions.menu -MemberType ScriptMethod show_module_menu { param($filter_list, [bool]$enable_menu_enhance) function handleOutput($item) { $out = $item.CompletionText try { if ((Get-ItemProperty ($item.ToolTip)).Attributes -like '*Directory*') { if ($out[-1] -match "^['`"]$") { if ($out[-2] -match "^[/\\]$") { return $out } return $out.Insert($out.Length - 1, $PSCompletions.separator) } if ($out[-1] -match "^[/\\]$") { return $out } return $out + $PSCompletions.separator } } catch {} return $out } if (!$filter_list) { return } $current_encoding = [console]::OutputEncoding [console]::OutputEncoding = $PSCompletions.encoding $PSCompletions.menu.pos = @{ X = 0; Y = 0 } $PSCompletions.menu.pos_tip = @{ X = 0; Y = 0 } $PSCompletions.menu.list_max_width = [Math]::Max($PSCompletions.menu.list_max_width, $PSCompletions.config.list_min_width) $PSCompletions.menu.ui_size = $Host.UI.RawUI.BufferSize $PSCompletions.menu.filter = '' # 过滤的关键词 $PSCompletions.menu.old_filter = '' $PSCompletions.menu.page_current_index = 0 # 当前显示第几个 $PSCompletions.menu.selected_index = 0 # 当前选中的索引 $PSCompletions.menu.old_selected_index = 0 $PSCompletions.menu.offset = 0 # 索引的偏移量,用于滚动翻页 $PSCompletions.menu.reset($true, $enable_menu_enhance) $PSCompletions.menu.origin_filter_list = @() $PSCompletions.menu.filter_list = @() $PSCompletions.menu.handle_list_first($filter_list) if ($PSCompletions.config.enable_enter_when_single -eq 1 -and $PSCompletions.menu.filter_list.Count -eq 1) { return handleOutput $PSCompletions.menu.filter_list[$PSCompletions.menu.selected_index] } $PSCompletions.menu.parse_list() # 如果解析后的菜单高度小于 3 (上下边框 + 1个补全项) if ($PSCompletions.menu.ui_size.Height -lt 3 -or $PSCompletions.menu.ui_size.Width -gt $Host.ui.RawUI.BufferSize.Width - 2) { [Microsoft.PowerShell.PSConsoleReadLine]::UndoAll() [Microsoft.PowerShell.PSConsoleReadLine]::Insert($PSCompletions.info.min_area) '' return } # 显示菜单之前,记录 buffer $pos = $PSCompletions.menu.get_pos() $PSCompletions.menu.origin_menu_buffer = $PSCompletions.menu.get_buffer($pos.menu.start_pos, $pos.menu.end_pos) $PSCompletions.menu.origin_tip_buffer = $PSCompletions.menu.get_buffer($pos.tip.start_pos, $pos.tip.end_pos) $PSCompletions.menu.old_menu_buffer = $PSCompletions.menu.get_buffer( @{ X = $Host.UI.RawUI.WindowPosition.X Y = $PSCompletions.menu.pos.Y }, @{ X = $Host.ui.RawUI.BufferSize.Width Y = $PSCompletions.menu.pos.Y + $PSCompletions.menu.ui_size.height - 1 }) if ($PSCompletions.menu.is_show_tip) { $PSCompletions.menu.get_old_tip_buffer($PSCompletions.menu.pos_tip.X, $PSCompletions.menu.pos_tip.Y) } # 显示菜单 $PSCompletions.menu.new_buffer() if ($PSCompletions.menu.is_show_tip) { $PSCompletions.menu.new_tip_buffer($PSCompletions.menu.selected_index) } if ($PSCompletions.config.enable_prefix_match_in_filter -eq 1) { $PSCompletions.menu.get_prefix() } $PSCompletions.menu.new_filter_buffer($PSCompletions.menu.filter) $PSCompletions.menu.new_status_buffer() $PSCompletions.menu.set_selection() $old_filter_list = $PSCompletions.menu.filter_list :loop while (($PressKey = $host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown,AllowCtrlC')).VirtualKeyCode) { $shift_pressed = 0x10 -band [int]$PressKey.ControlKeyState if ($PressKey.ControlKeyState -like '*CtrlPressed*') { switch ($PressKey.VirtualKeyCode) { 67 { # 67: Ctrl + c $PSCompletions.menu.reset($true) '' break loop } { $_ -eq 85 -or $_ -eq 80 } { # 85: Ctrl + u # 80: Ctrl + p $PSCompletions.menu.move_selection($false) break } { $_ -eq 68 -or $_ -eq 78 } { # 68: Ctrl + d # 78: Ctrl + n $PSCompletions.menu.move_selection($true) break } } } switch ($PressKey.VirtualKeyCode) { { $_ -in @(9, 32) } { # 9: Tab # 32: Space if ($PSCompletions.menu.filter_list.Count -eq 1) { $PSCompletions.menu.reset($true) $PSCompletions.menu.filter_list[$PSCompletions.menu.selected_index].CompletionText break loop } if ($shift_pressed) { # Up $PSCompletions.menu.move_selection($false) } else { # Down $PSCompletions.menu.move_selection($true) } break } 27 { # 27: ESC $PSCompletions.menu.reset($true) '' break loop } 13 { # 13: Enter handleOutput $PSCompletions.menu.filter_list[$PSCompletions.menu.selected_index] $PSCompletions.menu.reset($true) break loop } # 向上 # 37: Up # 38: Left { $_ -in @(37, 38) } { $PSCompletions.menu.move_selection($false) break } # 向下 # 39: Right # 40: Down { $_ -in @(39, 40) } { $PSCompletions.menu.move_selection($true) break } # Character/Backspace { $PressKey.Character } { # update filter if ($PressKey.Character -eq 8) { # 8: backspace if ($PSCompletions.menu.filter -eq $PSCompletions.menu.filter_by_auto_pick) { $PSCompletions.menu.filter = '' $PSCompletions.menu.filter_by_auto_pick = '' $PSCompletions.menu.reset($true) '' break loop } if ($PSCompletions.menu.filter) { $PSCompletions.menu.filter = $PSCompletions.menu.filter.Substring(0, $PSCompletions.menu.filter.Length - 1) } else { $PSCompletions.menu.reset($true) '' break loop } $PSCompletions.menu.new_cover_buffer() $PSCompletions.menu.reset() $PSCompletions.menu.filter_completions($PSCompletions.menu.origin_filter_list) foreach ($item in $PSCompletions.menu.filter_list) { $PSCompletions.menu.tip_max_height = [Math]::Max($PSCompletions.menu.tip_max_height, $item.ToolTip.Count) } $PSCompletions.menu.parse_list() $PSCompletions.menu.new_buffer() if ($PSCompletions.menu.is_show_tip) { $PSCompletions.menu.new_tip_buffer($PSCompletions.menu.selected_index) } $PSCompletions.menu.new_status_buffer() $PSCompletions.menu.set_selection(0) } else { # add char $PSCompletions.menu.filter += $PressKey.Character $PSCompletions.menu.filter_completions($PSCompletions.menu.filter_list) if (!$PSCompletions.menu.filter_list) { $PSCompletions.menu.filter = $PSCompletions.menu.old_filter $PSCompletions.menu.filter_list = $old_filter_list } else { $PSCompletions.menu.new_cover_buffer() $PSCompletions.menu.reset() foreach ($item in $PSCompletions.menu.filter_list) { $PSCompletions.menu.tip_max_height = [Math]::Max($PSCompletions.menu.tip_max_height, $item.ToolTip.Count) } $PSCompletions.menu.parse_list() $PSCompletions.menu.new_buffer() if ($PSCompletions.menu.is_show_tip) { $PSCompletions.menu.new_tip_buffer($PSCompletions.menu.selected_index) } $PSCompletions.menu.new_status_buffer() $PSCompletions.menu.set_selection(0) } } break } } if ($PSCompletions.menu.selected_index -ne $PSCompletions.menu.old_selected_index) { $PSCompletions.menu.new_status_buffer() if ($PSCompletions.menu.is_show_tip) { $PSCompletions.menu.new_tip_buffer($PSCompletions.menu.selected_index) } $PSCompletions.menu.old_selected_index = $PSCompletions.menu.selected_index } if ($PSCompletions.menu.filter -ne $PSCompletions.menu.old_filter) { $PSCompletions.menu.new_filter_buffer($PSCompletions.menu.filter) $PSCompletions.menu.old_filter = $PSCompletions.menu.filter $old_filter_list = $PSCompletions.menu.filter_list } } [console]::OutputEncoding = $current_encoding $PSCompletions.menu.list_max_width = 0 } |