Gumby.TextBuffer.psm1
using module Gumby.Log using module Gumby.String class TextBuffer { TextBuffer( [System.ConsoleColor] $defaultForegroundColor = $Global:Host.UI.RawUI.ForegroundColor, [System.ConsoleColor] $defaultBackgroundColor = $Global:Host.UI.RawUI.BackgroundColor) { $this.DefaultForegroundColor = $defaultForegroundColor $this.DefaultBackgroundColor = $defaultBackgroundColor } [void] SetScreenBuffer( [System.Management.Automation.Host.Rectangle] $targetArea, [System.Management.Automation.Host.Coordinates] $sourceOrigin) { $stripes = $this.GetStripes($targetArea, $sourceOrigin) foreach ($stripe in $stripes) { $Global:Host.UI.RawUI.SetBufferContents($stripe.Coordinates, $stripe.BufferCells) } } # The implementation below is an attempt to allocate a BufferCell array with a single allocation. # It results in about the same performance as the original implementation. <# [void] SetScreenBuffer( [System.Management.Automation.Host.Rectangle] $targetArea, [System.Management.Automation.Host.Coordinates] $sourceOrigin) { $targetAreaStringList = [System.Collections.ArrayList]::new() $targetWidth = $targetArea.Right - $targetArea.Left + 1 $targetHeight = $targetArea.Bottom - $targetArea.Top + 1 for ($i = 0; $i -lt $targetHeight; ++$i) { if (($sourceOrigin.Y + $i -lt 0) </# above the text #/> -or ($sourceOrigin.Y + $i -ge $this._lines.Count) </# below the text #/> -or ($sourceOrigin.X + $targetWidth -lt 0) </# to the left of the text #/> -or ($sourceOrigin.X -gt $this._lines[$sourceOrigin.Y + $i].Text.Length) </# to the right of the right #/>) { $targetAreaString = ' ' * $targetWidth } else { $line = $this._lines[$sourceOrigin.Y + $i] $targetAreaString = if ($sourceOrigin.X -lt 0) { EnsureStringLength ((" " * -$sourceOrigin.X) + $line.Text) $targetWidth } else { EnsureStringLength $line.Text.Substring($sourceOrigin.X) $targetWidth } } $targetAreaStringList.Add($targetAreaString) | Out-Null } $targetAreaStringArray = $targetAreaStringList.ToArray() [System.Management.Automation.Host.BufferCell[,]] $bufferCells = $Global:Host.UI.RawUI.NewBufferCellArray($targetAreaStringArray, $this.DefaultForegroundColor, $this.DefaultBackgroundColor) # correct colors for ($i = 0; $i -lt $targetHeight; ++$i) { [Log]::Trace("TB.SetScreenBuffer.CorrectColors: i=$i") if ($sourceOrigin.Y + $i -lt 0) { </# above the text #/> continue } if ($sourceOrigin.Y + $i -ge $this._lines.Count) {</# below the text #/> break } $line = $this._lines[$sourceOrigin.Y + $i] [Log]::Trace("TB.SetScreenBuffer.CorrectColors: FC=$($line.ForegroundColor); BC=$($line.BackgroundColor)") for ($j = 0; $j -lt $targetWidth; ++$j) { #[Log]::Trace("TB.SetScreenBuffer.CorrectColors: [$i, $j].FC=$($bufferCells[$i, $j].ForegroundColor); line.FC=$($line.ForegroundColor)") # Work around the fact that calling the 'ForegroundColor' and 'BackgroundColor' # setters on an array element seem to have no effect. $temp = $bufferCells[$i, $j] $temp.ForegroundColor = $line.ForegroundColor $temp.BackgroundColor = $line.BackgroundColor $bufferCells[$i, $j] = $temp #[Log]::Trace("TB.SetScreenBuffer.CorrectColors: [$i, $j].FC=$($bufferCells[$i, $j].ForegroundColor)") } } $targetOrigin = [System.Management.Automation.Host.Coordinates]::new($targetArea.Left, $targetArea.Top) $Global:Host.UI.RawUI.SetBufferContents($targetOrigin, $bufferCells) } #> # The implementation below is an attempt to allocate a BufferCell array with a single allocation. # It results in about the same performance as the original implementation. <# [void] SetScreenBuffer( [System.Management.Automation.Host.Rectangle] $targetArea, [System.Management.Automation.Host.Coordinates] $sourceOrigin) { # The structure of the code below is based on the fact the BufferCell.ForegroundColor and # BufferCell.BackgroundColor properties appear to be read-only. $targetAreaStringList = [System.Collections.ArrayList]::new() $targetWidth = $targetArea.Right - $targetArea.Left + 1 $targetHeight = $targetArea.Bottom - $targetArea.Top + 1 for ($i = 0; $i -lt $targetHeight; ++$i) { if (($sourceOrigin.Y + $i -lt 0) <!# above the text #!> -or ($sourceOrigin.Y + $i -ge $this._lines.Count) <!# below the text #!> -or ($sourceOrigin.X + $targetWidth -lt 0) <!# to the left of the text #!> -or ($sourceOrigin.X -gt $this._lines[$sourceOrigin.Y + $i].Text.Length) <!# to the right of the right #!>) { $targetAreaString = ' ' * $targetWidth } else { $line = $this._lines[$sourceOrigin.Y + $i] $targetAreaString = if ($sourceOrigin.X -lt 0) { EnsureStringLength ((" " * -$sourceOrigin.X) + $line.Text) $targetWidth } else { EnsureStringLength $line.Text.Substring($sourceOrigin.X) $targetWidth } } $targetAreaStringList.Add($targetAreaString) | Out-Null } $targetAreaStringArray = $targetAreaStringList.ToArray() $targetOrigin = [System.Management.Automation.Host.Coordinates]::new($targetArea.Left, $targetArea.Top) [System.Management.Automation.Host.BufferCell[,]] $bufferCells = $Global:Host.UI.RawUI.NewBufferCellArray( $targetAreaStringArray, $this.DefaultForegroundColor, $this.DefaultBackgroundColor) $Global:Host.UI.RawUI.SetBufferContents($targetOrigin, $bufferCells) # correct colors for ($i = 0; $i -lt $targetHeight; ++$i) { if ($sourceOrigin.Y + $i -lt 0 <!# above the text #!>) { continue } if ($sourceOrigin.Y + $i -ge $this._lines.Count <!# below the text #!>) { break } $line = $this._lines[$sourceOrigin.Y + $i] if ($line.ForegroundColor -ne $this.DefaultForegroundColor -or $line.BackgroundColor -ne $this.DefaultBackgroundColor) { $targetOrigin = [System.Management.Automation.Host.Coordinates]::new($targetArea.Left, $targetArea.Top + $i) [System.Management.Automation.Host.BufferCell[,]] $bufferCells = $Global:Host.UI.RawUI.NewBufferCellArray( @($targetAreaStringArray[$i]), $line.ForegroundColor, $line.BackgroundColor) $Global:Host.UI.RawUI.SetBufferContents($targetOrigin, $bufferCells) } } } #> # for unit testing [object] GetStripes( [System.Management.Automation.Host.Rectangle] $targetArea, [System.Management.Automation.Host.Coordinates] $sourceOrigin) { $stripes = [System.Collections.ArrayList]::new() $targetWidth = $targetArea.Right - $targetArea.Left + 1 $targetHeight = $targetArea.Bottom - $targetArea.Top + 1 for ($i = 0; $i -lt $targetHeight; ++$i) { $stripe = @{Coordinates = [System.Management.Automation.Host.Coordinates]::new($targetArea.Left, $targetArea.Top + $i)} if (($sourceOrigin.Y + $i -lt 0) <# above the text #> -or ($sourceOrigin.Y + $i -ge $this._lines.Count) <# below the text #>) { $stripe.BufferCells = $Global:Host.UI.RawUI.NewBufferCellArray( @(' ' * $targetWidth), $this.DefaultForegroundColor, $this.DefaultBackgroundColor) } else { $line = $this._lines[$sourceOrigin.Y + $i] if (($sourceOrigin.X + $targetWidth -lt 0) <# to the left of the text #> -or ($sourceOrigin.X -gt $line.Text.Length) <# to the right of the right#>) { $stripe.BufferCells = $Global:Host.UI.RawUI.NewBufferCellArray( @(' ' * $targetWidth), $line.ForegroundColor, $line.BackgroundColor) } else { $text = if ($sourceOrigin.X -lt 0) { EnsureStringLength ((" " * -$sourceOrigin.X) + $line.Text) $targetWidth } else { EnsureStringLength $line.Text.Substring($sourceOrigin.X) $targetWidth } $stripe.BufferCells = $Global:Host.UI.RawUI.NewBufferCellArray(@($text), $line.ForegroundColor, $line.BackgroundColor) } } [void] $stripes.Add($stripe) } return $stripes } [int] LineCount() { return $this._lines.Count } [int] ColumnCount() { return $this._lines[$this._maxLengthLineNumber].Text.Length } [void] AddLine([string] $text, [ConsoleColor] $foregroundColor, [ConsoleColor] $backgroundColor) { $i = $this._lines.Add(@{Text = $text; ForegroundColor = $foregroundColor; BackgroundColor = $backgroundColor}) if (($this._maxLengthLineNumber -eq -1) -or ($text.Length -gt $this._lines[$this._maxLengthLineNumber].Text.Length)) { $this._maxLengthLineNumber = $i } } [void] InsertLine([int] $lineNumber, [string] $text, [ConsoleColor] $foregroundColor, [ConsoleColor] $backgroundColor) { if (($this._maxLengthLineNumber -eq -1) -or ($text.Length -gt $this._lines[$this._maxLengthLineNumber].Text.Length)) { $this._maxLengthLineNumber = $lineNumber } $this._lines.Insert($lineNumber, @{Text = $text; ForegroundColor = $foregroundColor; BackgroundColor = $backgroundColor}) } [object] GetLine([int] $lineNumber) { return $this._lines[$lineNumber] } [void] RemoveLine([int] $lineNumber) { $this._lines.RemoveAt($lineNumber) if ($lineNumber -eq $this._maxLengthLineNumber) { $this.DetermineMaxLengthLineNumber() } } [void] ClearLines() { $this._lines.Clear() $this._maxLengthLineNumber = -1 } [ConsoleColor] $DefaultForegroundColor = $Global:Host.UI.RawUI.ForegroundColor [ConsoleColor] $DefaultBackgroundColor = $Global:Host.UI.RawUI.BackgroundColor hidden [void] DetermineMaxLengthLineNumber() { $this._maxLengthLineNumber = -1 $maxLength = -1 for ($i = 0; $i -lt $this._lines.Count; ++$i) { if ($this._lines[$i].Text.Length -gt $maxLength) { $maxLength = $this._lines[$i].Text.Length $this._maxLengthLineNumber = $i } } } hidden [System.Collections.ArrayList] $_lines = [System.Collections.ArrayList]::new() hidden [int] $_maxLengthLineNumber = -1 } |