core/ui/utils.ps1
$PSCompletions.ui | Add-Member -MemberType ScriptMethod get_list { param( [object[]]$content, [ref]$repeat ) $is_single = if ($content[1]) { $false }else { $true } # create console list $prefix = $PSCompletions.ui.get_prefix($content) $filter = '' $color = $PSCompletions.ui.color $ListHandle = $PSCompletions.ui.new_list($content) function set_status { # filter buffer (header) shows the current filter after the last word $filter_buffer = $PSCompletions.ui.new_buffer_cell(" $prefix$($PSCompletions.ui.config.filter_symbol[0])$filter$($PSCompletions.ui.config.filter_symbol[1]) ", $color.filter, $color.filter_back) $filter_position = $ListHandle.Position $filter_position.X += 2 $filter_handle = $PSCompletions.ui.new_buffer($filter_position, $filter_buffer) # status buffer (footer) shows selected item number, visible items number range, total item count $status_buffer = $PSCompletions.ui.new_buffer_status($ListHandle) $status_position = $ListHandle.Position $status_position.X += 2 $status_position.Y += $listHandle.ListConfig.ListHeight - 1 $status_handle = $PSCompletions.ui.new_buffer($status_position , $status_buffer) } . set_status function show_tip { $tip_buffer = $PSCompletions.ui.get_tip($ListHandle.items[$ListHandle.selected_item].CompletionText) $tip_position = $ListHandle.Position $tip_position.X += $PSCompletions.ui.layout.list_w $tip_position.Y += 1 $tip_handle = $PSCompletions.ui.new_buffer($tip_position, $tip_buffer, $tip_position.X) } . show_tip # select the first item $selected_item = 0 $PSCompletions.ui.set_selection(1 , ($selected_item + 1) , ($ListHandle.ListConfig.ListWidth - 3), $color.selected, $color.selected_back) ### Keys :loop while (($Key = $PSCompletions.ui.ui.ReadKey('NoEcho,IncludeKeyDown,AllowCtrlC')).VirtualKeyCode -ne 27) { $shift_pressed = 0x10 -band [int]$Key.ControlKeyState switch ($Key.VirtualKeyCode) { ### Tab 9 { if ($is_single -or $items.count -eq 1) { # out selected $ListHandle.items[$ListHandle.selected_item].CompletionText break loop } if ($shift_pressed) { # Up $PSCompletions.ui.move_selection(-1) } else { # Down $PSCompletions.ui.move_selection(1) } break } ### Up Arrow 38 { if ($shift_pressed) { # fast scroll selected if ($PSCompletions.ui.config.fast_scroll_item_count -gt ($ListHandle.items.count - 1)) { $count = $ListHandle.items.count - 1 } else { $count = $PSCompletions.ui.config.fast_scroll_item_count } $PSCompletions.ui.move_selection(-$count) } else { $PSCompletions.ui.move_selection(-1) } break } ### Down Arrow 40 { if ($shift_pressed) { # fast scroll selected if ($PSCompletions.ui.config.fast_scroll_item_count -gt ($ListHandle.items.count - 1)) { $count = $ListHandle.items.count - 1 } else { $count = $PSCompletions.ui.config.fast_scroll_item_count } $PSCompletions.ui.move_selection($count) } else { $PSCompletions.ui.move_selection(1) } break } ### Page Up 33 { $count = $ListHandle.items.count if ($count -gt $ListHandle.max_items) { $count = $ListHandle.max_items } $PSCompletions.ui.move_selection(1 - $count) break } ### Page Down 34 { $count = $ListHandle.items.count if ($count -gt $ListHandle.max_items) { $count = $ListHandle.max_items } $PSCompletions.ui.move_selection($count - 1) break } ### Enter 13 { # out selected $ListHandle.items[$ListHandle.selected_item].CompletionText break loop } ### Character/Backspace { $Key.Character } { # update filter $old_filter = $filter if ($Key.Character -eq 8) { if ($filter) { $filter = $filter.Substring(0, $filter.Length - 1) } else { break loop } } else { # add char $filter += $Key.Character } # get new items $items = @($PSCompletions.ui.select_item($content, $prefix, $filter)) if (!$items) { # new filter gives no items, undo $filter = $old_filter } elseif ($items.count -ne $ListHandle.items.count) { # items changed, update $ListHandle.clear() $ListHandle = $PSCompletions.ui.new_list($items) # update status buffer . set_status . show_tip # select first item $selected_item = 0 $PSCompletions.ui.set_selection(1 , ($selected_item + 1) , ($ListHandle.ListConfig.ListWidth - 3) , $color.selected, $color.selected_back) } else { # update status buffer . set_status . show_tip } break } } } $ListHandle.clear() } $PSCompletions.ui | Add-Member -MemberType ScriptMethod new_box { param( $size, [System.ConsoleColor]$color, [System.ConsoleColor]$bg_color ) $margin = $PSCompletions.ui.config.tip_margin_right $line_top = $PSCompletions.ui.config.line.top_left + $PSCompletions.ui.config.line.horizontal * ($size.width - 5 + $margin) + $PSCompletions.ui.config.line.top_right $line_field = $PSCompletions.ui.config.line.vertical + ' ' * ($size.width - 5 + $margin) + $PSCompletions.ui.config.line.vertical $line_bottom = $PSCompletions.ui.config.line.bottom_left + $PSCompletions.ui.config.line.horizontal * ($size.width - 5 + $margin) + $PSCompletions.ui.config.line.bottom_right $Box = $( $line_top 1..($size.Height - 2) | .{ process { $line_field } } $line_bottom ) $BoxBuffer = $PSCompletions.ui.ui.NewBufferCellArray($Box, $color, $bg_color) , $BoxBuffer } $PSCompletions.ui | Add-Member -MemberType ScriptMethod get_size { param([object[]]$content) $max_width = ($content | ForEach-Object { $PSCompletions.ui.get_length($_.ListItemText) } | Measure-Object -Maximum).Maximum @{ Width = $max_width Height = $content.Length } } $PSCompletions.ui | Add-Member -MemberType ScriptMethod new_position { param( [int]$X, [int]$Y ) $position = $PSCompletions.ui.ui.WindowPosition if ($PSCompletions.ui.config.follow_cursor) { $position.X += $X } else { $position.X += 0 } $position.Y += $Y $position } $PSCompletions.ui | Add-Member -MemberType ScriptMethod new_buffer { param( [System.Management.Automation.Host.Coordinates]$Position, [System.Management.Automation.Host.BufferCell[, ]]$Buffer, [int]$offsetX = 0 ) $BufferBottom = $BufferTop = $Position $BufferBottom.X += ($Buffer.GetUpperBound(1)) $BufferBottom.Y += ($Buffer.GetUpperBound(0)) $OldTop = New-Object System.Management.Automation.Host.Coordinates $offsetX, $BufferTop.Y $OldBottom = New-Object System.Management.Automation.Host.Coordinates ($PSCompletions.ui.ui.BufferSize.Width - 1), $BufferBottom.Y $OldBuffer = $PSCompletions.ui.ui.GetBufferContents((New-Object System.Management.Automation.Host.Rectangle $OldTop, $OldBottom)) $PSCompletions.ui.ui.SetBufferContents($BufferTop, $Buffer) $Handle = New-Object System.Management.Automation.PSObject -Property @{ Content = $Buffer OldContent = $OldBuffer Location = $BufferTop OldLocation = $OldTop } Add-Member -InputObject $Handle -MemberType ScriptMethod -Name clear -Value { $PSCompletions.ui.ui.SetBufferContents($this.OldLocation, $this.OldContent) } Add-Member -InputObject $Handle -MemberType ScriptMethod -Name Show -Value { $PSCompletions.ui.ui.SetBufferContents($this.Location, $this.Content) } $Handle } $PSCompletions.ui | Add-Member -MemberType ScriptMethod new_buffer_status { param($ListHandle) , $PSCompletions.ui.new_buffer_cell(" $($ListHandle.selected_item + 1) $($PSCompletions.ui.config.count_symbol) $($ListHandle.items.count) ", $PSCompletions.ui.color.status , $PSCompletions.ui.color.status_back) } $PSCompletions.ui | Add-Member -MemberType ScriptMethod new_buffer_cell { param( [string[]]$content, [System.ConsoleColor]$color, [System.ConsoleColor]$bg_color ) , $PSCompletions.ui.ui.NewBufferCellArray($content, $color, $bg_color) } $PSCompletions.ui | Add-Member -MemberType ScriptMethod parse_list { param($size) $WindowPosition = $PSCompletions.ui.ui.WindowPosition $WindowSize = $PSCompletions.ui.ui.WindowSize $Cursor = $PSCompletions.ui.ui.CursorPosition $Center = [int]($WindowSize.Height / 2) $CursorOffset = $Cursor.Y - $WindowPosition.Y $CursorOffsetBottom = $WindowSize.Height - $CursorOffset # vertical size and placement $ListHeight = $size.Height + 2 if (!$PSCompletions.ui.layout.Above) { $PSCompletions.ui.layout.Above = $WindowSize.Height - $Cursor.Y -lt $Cursor.Y - $PSCompletions.ui.style_h } if ($PSCompletions.ui.layout.Above) { $MaxListHeight = $CursorOffset - 1 if ($MaxListHeight -lt $ListHeight) { $ListHeight = $MaxListHeight } $Y = 0 $PSCompletions.ui.layout.Above_list_h = $Cursor.Y - ($PSCompletions.ui.style_h - 1) } else { $MaxListHeight = $CursorOffsetBottom - 2 if ($MaxListHeight -lt $ListHeight) { $ListHeight = $MaxListHeight } $Y = $CursorOffSet + 1 } $max_items = $MaxListHeight - 2 # horizontal $ListWidth = $size.Width + 2 + $PSCompletions.ui.config.list_margin_right if ($ListWidth -gt $WindowSize.Width) { $ListWidth = $Windowsize.Width } $Max = $ListWidth if (($Cursor.X + $Max) -lt ($WindowSize.Width - 2)) { $X = $Cursor.X } else { if (($Cursor.X - $Max) -gt 0) { $X = $Cursor.X - $Max } else { $X = $windowSize.Width - $Max } } # output @{ TopX = $X TopY = $Y ListHeight = $ListHeight ListWidth = $ListWidth max_items = $max_items } } $PSCompletions.ui | Add-Member -MemberType ScriptMethod new_list { param([object[]]$content) $size = $PSCompletions.ui.get_size($content) $MinWidth = ([string]$content.count).Length * 4 if ($size.Width -lt $MinWidth) { $size.Width = $MinWidth } $Lines = @(foreach ($Item in $content) { "$($Item.ListItemText) ".PadRight($size.Width + 2) }) $ListConfig = $PSCompletions.ui.parse_list($size) if ($PSCompletions.ui.layout.Above_list_h) { $ListConfig.ListHeight = $PSCompletions.ui.layout.Above_list_h } if ($ListConfig.ListHeight -lt $PSCompletions.completion_max[1] + 2) { $ListConfig.ListHeight = $PSCompletions.completion_max[1] + 2 } $BoxSize = @{ Width = $ListConfig.ListWidth + $PSCompletions.completion_max[0] + 1 Height = $ListConfig.ListHeight } $rest_w = [System.Console]::WindowWidth - $BoxSize.Width $BoxSize.Width += if ($rest_w -gt 10) { 5 }elseif ($rest_w -gt 0) { $rest_w } $PSCompletions.ui.layout.list_h = $BoxSize.Height $Box = $PSCompletions.ui.new_box($BoxSize, $PSCompletions.ui.color.border, $PSCompletions.ui.color.border_back) $Position = $PSCompletions.ui.new_position($ListConfig.TopX, $ListConfig.TopY) $box_handle = $PSCompletions.ui.new_buffer($Position, $Box) # place content $Position.X += 1 $Position.Y += 1 $contentBuffer = $PSCompletions.ui.new_buffer_cell(($Lines[0..($ListConfig.ListHeight - 3)]) , $PSCompletions.ui.color.item , $PSCompletions.ui.color.item_back) $contentHandle = $PSCompletions.ui.new_buffer($Position, $contentBuffer) $Handle = New-Object System.Management.Automation.PSObject -Property @{ Position = $PSCompletions.ui.new_position($ListConfig.TopX, $ListConfig.TopY) ListConfig = $ListConfig ContentSize = $size BoxSize = $BoxSize Box = $box_handle Content = $contentHandle selected_item = 0 selected_line = 1 items = $content Lines = $Lines FirstItem = 0 LastItem = $Listconfig.ListHeight - 3 max_items = $Listconfig.max_items } Add-Member -InputObject $Handle -MemberType ScriptMethod -Name clear -Value { $this.Box.clear() } Add-Member -InputObject $Handle -MemberType ScriptMethod -Name Show -Value { $this.Box.Show(); $this.Content.Show() } $Handle } $PSCompletions.ui | Add-Member -MemberType ScriptMethod get_length { param([string]$str) $matches = [System.Text.RegularExpressions.Regex]::Matches($str, '😄') $len = 0 + $matches.Count * 2 $str = $str -replace '😄', '' foreach ($i in $str.ToCharArray()) { if ($i -match '[A-Za-z\d\s!@#\$%^&\*\(\)_\-\+\=\[\]{}|;:''",\.<>/?~`]') { $len++ } else { $len += 2 } } $len } $PSCompletions.ui | Add-Member -MemberType ScriptMethod get_tip { param($select_item) $tip = $PSCompletions.completion.$select_item while ($tip.count -lt $PSCompletions.completion_max[1]) { $tip += '' } , $PSCompletions.ui.new_buffer_cell($tip, $PSCompletions.ui.color.tip , $PSCompletions.ui.color.tip_back) } $PSCompletions.ui | Add-Member -MemberType ScriptMethod mv_list { param( [int]$X, [int]$Y, [int]$Width, [int]$Height, [int]$Offset ) $Position = $ListHandle.Position $Position.X += $X $Position.Y += $Y $Rectangle = New-Object System.Management.Automation.Host.Rectangle $Position.X, $Position.Y, ($Position.X + $Width), ($Position.Y + $Height - 1) $Position.Y += $OffSet $BufferCell = New-Object System.Management.Automation.Host.BufferCell $BufferCell.BackgroundColor = $PSCompletions.ui.color.item_back $PSCompletions.ui.ui.ScrollBufferContents($Rectangle, $Position, $Rectangle, $BufferCell) } $PSCompletions.ui | Add-Member -MemberType ScriptMethod set_selection { param( [int]$X, [int]$Y, [int]$Width, [System.ConsoleColor]$ForegroundColor, [System.ConsoleColor]$BackgroundColor ) $Position = $ListHandle.Position $Position.X += $X $Position.Y += $Y $Rectangle = New-Object System.Management.Automation.Host.Rectangle $Position.X, $Position.Y, ($Position.X + $Width), $Position.Y $LineBuffer = $PSCompletions.ui.ui.GetBufferContents($Rectangle) $LineBuffer = $PSCompletions.ui.ui.NewBufferCellArray( @([string]::Join('', ($LineBuffer | .{ process { $_.Character } }))), $ForegroundColor, $BackgroundColor ) $PSCompletions.ui.ui.SetBufferContents($Position, $LineBuffer) } $PSCompletions.ui | Add-Member -MemberType ScriptMethod move_selection { param([int]$count) $color = $PSCompletions.ui.color $selected_item = $ListHandle.selected_item $Line = $ListHandle.selected_line if ($count -ge 0) { ## Down in list if ($selected_item -eq ($ListHandle.items.count - 1)) { return } $One = 1 if ($selected_item + $count -gt $ListHandle.items.count - 1) { $count = $ListHandle.items.count - 1 - $selected_item } if ($selected_item -eq $ListHandle.LastItem) { $Move = $true } else { $Move = $false if (($ListHandle.max_items - $Line) -lt $count) { $count = $ListHandle.max_items - $Line } } } else { if ($selected_item -eq 0) { return } $One = -1 if ($selected_item -eq $ListHandle.FirstItem) { $Move = $true if ($selected_item + $count -lt 0) { $count = - $selected_item } } else { $Move = $false if ($Line + $count -lt 1) { $count = 1 - $Line } } } if ($Move) { $PSCompletions.ui.set_selection(1 , $Line, ($ListHandle.ListConfig.ListWidth - 3), $color.item, $color.item_back) $PSCompletions.ui.mv_list(1, 1, ($ListHandle.ListConfig.ListWidth - 3), ($ListHandle.ListConfig.ListHeight - 2), ( - $count)) $selected_item += $count $ListHandle.FirstItem += $count $ListHandle.LastItem += $count $line_position = $ListHandle.Position $line_position.X += 1 if ($One -eq 1) { $line_position.Y += $Line - ($count - $One) $ItemLines = $ListHandle.Lines[($selected_item - ($count - $One)) .. $selected_item] } else { $line_position.Y += 1 $ItemLines = $ListHandle.Lines[($selected_item..($selected_item - ($count - $One)))] } $null = $PSCompletions.ui.new_buffer($line_position , $PSCompletions.ui.new_buffer_cell($ItemLines, $color.item, $color.item_back)) $PSCompletions.ui.set_selection(1, $Line, ($ListHandle.ListConfig.ListWidth - 3) , $color.selected , $color.selected_back) } else { $PSCompletions.ui.set_selection(1, $Line, ($ListHandle.ListConfig.ListWidth - 3), $color.item , $color.item_back) $selected_item += $count $Line += $count $PSCompletions.ui.set_selection(1 , $Line, ($ListHandle.ListConfig.ListWidth - 3), $color.selected, $color.selected_back) } $ListHandle.selected_item = $selected_item $ListHandle.selected_line = $Line # new status buffer $status_handle.clear() $status_buffer = $PSCompletions.ui.new_buffer_status($ListHandle) $status_handle = $PSCompletions.ui.new_buffer($status_handle.Location, $status_buffer) # new tip buffer $tip_handle.clear() $tip_buffer = $PSCompletions.ui.get_tip($ListHandle.items[$ListHandle.selected_item].CompletionText) $tip_handle = $PSCompletions.ui.new_buffer($tip_handle.Location, $tip_buffer) } $PSCompletions.ui | Add-Member -MemberType ScriptMethod select_item { param($content, $prefix, $filter) $pattern = '^' + [regex]::Escape($prefix) + '.*?' foreach ($c in $filter.ToCharArray()) { $pattern += [regex]::Escape($c) + '.*?' } $prefix += $filter foreach ($_ in $content) { if ($_.ListItemText.StartsWith($prefix, [StringComparison]::OrdinalIgnoreCase)) { $_ } } foreach ($_ in $content) { $s = $_.ListItemText if (!$s.StartsWith($prefix, [StringComparison]::OrdinalIgnoreCase) -and $s -match $pattern) { $_ } } } $PSCompletions.ui | Add-Member -MemberType ScriptMethod get_prefix { param($content) $prefix = $content[-1].ListItemText for ($i = $content.count - 2; $i -ge 0 -and $prefix; --$i) { $text = $content[$i].ListItemText while ($prefix -and !$text.StartsWith($prefix, [StringComparison]::OrdinalIgnoreCase)) { $prefix = $prefix.Substring(0, $prefix.Length - 1) } } $prefix } |