Private/Console/Internal.psm1

using namespace System
using namespace System.Collections.Generic

using module ..\Enums.psm1
using module ..\Abstracts.psm1
using module .\Colors.psm1


class Cell {
  static [int] GetCellLength([string]$text) {
    if ([string]::IsNullOrEmpty($text)) { return 0 }
    # Simple implementation: account for surrogate pairs using StringInfo.
    # Note: This doesn't handle full-width characters (CJK) or emojis perfectly,
    # but it's a good start for a core engine.
    $enumerator = [Globalization.StringInfo]::GetTextElementEnumerator($text)
    $sum = 0
    while ($enumerator.MoveNext()) {
      $sum++
    }
    return $sum
  }
}

class Constants {
}

class DecorationTable {
  static hidden [System.Collections.Generic.Dictionary[string, [Nullable[Decoration]]]] $_lookup
  static hidden [System.Collections.Generic.Dictionary[Decoration, string]] $_reverseLookup

  static DecorationTable() {
    $dict = [System.Collections.Generic.Dictionary[string, [Nullable[Decoration]]]]::new([System.StringComparer]::OrdinalIgnoreCase)
    $dict.Add("none", [Decoration]::None)
    $dict.Add("bold", [Decoration]::Bold)
    $dict.Add("b", [Decoration]::Bold)
    $dict.Add("dim", [Decoration]::Dim)
    $dict.Add("italic", [Decoration]::Italic)
    $dict.Add("i", [Decoration]::Italic)
    $dict.Add("underline", [Decoration]::Underline)
    $dict.Add("u", [Decoration]::Underline)
    $dict.Add("invert", [Decoration]::Invert)
    $dict.Add("reverse", [Decoration]::Invert)
    $dict.Add("conceal", [Decoration]::Conceal)
    $dict.Add("blink", [Decoration]::SlowBlink)
    $dict.Add("slowblink", [Decoration]::SlowBlink)
    $dict.Add("rapidblink", [Decoration]::RapidBlink)
    $dict.Add("strike", [Decoration]::Strikethrough)
    $dict.Add("strikethrough", [Decoration]::Strikethrough)
    $dict.Add("s", [Decoration]::Strikethrough)
    [DecorationTable]::_lookup = $dict

    $rev = [System.Collections.Generic.Dictionary[Decoration, string]]::new()
    foreach ($pair in $dict) {
      if ($pair.Value.HasValue -and $pair.Value.Value -ne [Decoration]::None) {
        if (!$rev.ContainsKey($pair.Value.Value)) {
          $rev.Add($pair.Value.Value, $pair.Key)
        }
      }
    }
    [DecorationTable]::_reverseLookup = $rev
  }

  static [Nullable[Decoration]] GetDecoration([string]$name) {
    if ([DecorationTable]::_lookup.ContainsKey($name)) {
      return [DecorationTable]::_lookup[$name]
    }
    return $null
  }

  static [System.Collections.Generic.List[string]] GetMarkupNames([Decoration]$decoration) {
    $result = [System.Collections.Generic.List[string]]::new()
    foreach ($value in [Enum]::GetValues([Decoration])) {
      $flag = [Decoration]$value
      if ($flag -ne [Decoration]::None -and ($decoration -band $flag) -eq $flag) {
        if ([DecorationTable]::_reverseLookup.ContainsKey($flag)) {
          $result.Add([DecorationTable]::_reverseLookup[$flag])
        }
      }
    }
    return $result
  }
}


class DefaultExclusivityMode : IExclusivityMode {
}


class ResourceReader {
}

class TypeConverterHelper {
}

class NoopCursor : IAnsiConsoleCursor {
}

class NoopExclusivityMode : IExclusivityMode {
}

class ConsoleCoordinate {
  [int]$X
  [int]$Y
  ConsoleCoordinate([int]$X, [int]$Y) { $this.X = $X; $this.Y = $Y }
}

class ConsoleReader : System.IO.TextReader {
  ConsoleReader() : base() { }
  [int] Read() {
    $key = [Console]::ReadKey($true)
    return [int][char]$key.KeyChar
  }
  [int] Read([char[]]$buffer, [int]$index, [int]$count) {
    if ($null -eq $buffer) {
      throw [ArgumentNullException]::new("buffer")
    }
    if ($index -lt 0 -or $count -lt 0) {
      throw [ArgumentOutOfRangeException]::new("Index and count must be non-negative")
    }
    if ($buffer.Length - $index -lt $count) {
      throw [ArgumentException]::new("Buffer too small")
    }

    $charsRead = 0
    while ($charsRead -lt $count -and [Console]::KeyAvailable) {
      $key = [Console]::ReadKey($true)
      $buffer[$index + $charsRead] = $key.KeyChar
      $charsRead++
    }
    return $charsRead
  }
  [string] ReadLine() {
    return [Console]::ReadLine()
  }
}

class ConsoleWriter : System.IO.TextWriter {
  # WriteRaw tells the writer to Write text directly to stdout without any color/animation processing. This is the correct method for ANSI escape sequences emitted by AnsiWriter.
  [bool] $WriteRaw = $false
  hidden [string] $LeadPreffix
  static hidden [string[]] $Colors = [ConsoleWriter]::get_ColorNames()
  hidden [ValidateNotNull()][scriptblock]$ValidateScript = { param($arg) if ($null -eq $arg) { throw [ArgumentNullException]::new('text', 'Cannot be Null') } }

  ConsoleWriter() : base() {
    $this.PsObject.Properties.Add([psscriptproperty]::new("UseLeadPreffix", { return ![string]::IsNullOrWhiteSpace($this.LeadPreffix) }))
    $this.PsObject.Properties.Add([PsNoteProperty]::new("UseTypingEffect", $false ))
  }

  [string] write([string]$text) {
    return $this.Write($text, 20, 1200)
  }
  [string] write([string]$text, [bool]$AddPreffix) {
    return $this.Write($text, 20, 1200, $AddPreffix)
  }
  [string] write([string]$text, [int]$Speed, [int]$Duration) {
    return $this.Write($text, 20, 1200, $this.UseLeadPreffix)
  }
  [string] write([string]$text, [ConsoleColor]$color) {
    return $this.Write($text, $color, $this.UseTypingEffect)
  }
  [string] write([string]$text, [ConsoleColor]$color, [bool]$Animate) {
    return $this.Write($text, $this.LeadPreffix, 20, 1200, $color, $Animate, $this.LeadPreffix)
  }
  [string] write([string]$text, [int]$Speed, [int]$Duration, [bool]$AddPreffix) {
    return $this.Write($text, $this.LeadPreffix, $Speed, $Duration, [ConsoleColor]::White, $this.UseTypingEffect, $AddPreffix)
  }
  [string] write([string]$text, [ConsoleColor]$color, [bool]$Animate, [bool]$AddPreffix) {
    return $this.Write($text, $this.LeadPreffix, 20, 1200, $color, $Animate, $AddPreffix)
  }
  [string] write([string]$text, [string]$Preffix, [System.ConsoleColor]$color) {
    return $this.Write($text, $Preffix, $color, $this.UseTypingEffect)
  }
  [string] write([string]$text, [string]$Preffix, [System.ConsoleColor]$color, [bool]$Animate) {
    return $this.Write($text, $Preffix, 20, 1200, $color, $Animate, $this.LeadPreffix)
  }
  [string] write([string]$text, [string]$Preffix, [int]$Speed, [int]$Duration, [bool]$AddPreffix) {
    return $this.Write($text, $Preffix, $Speed, $Duration, [ConsoleColor]::White, $this.UseTypingEffect, $AddPreffix)
  }
  [string] write([string]$text, [string]$Preffix, [int]$Speed, [int]$Duration, [ConsoleColor]$color, [bool]$Animate, [bool]$AddPreffix) {
    return $this.Write($text, $Preffix, $Speed, $Duration, $color, $Animate, $AddPreffix, $this.ValidateScript)
  }
  [string] write([string]$text, [string]$Preffix, [int]$Speed, [int]$Duration, [ConsoleColor]$color, [bool]$Animate, [bool]$AddPreffix, [scriptblock]$ValidateScript) {
    if ($null -ne $ValidateScript -and !$this.WriteRaw) {
      [void]$ValidateScript.Invoke($text)
    } elseif ($null -eq $text) {
      return $text
    }
    [int]$length = $text.Length; $delay = 0
    # Check if delay time is required:
    $delayIsRequired = if (!$this.WriteRaw -and $length -ge 50) { $delay = $Duration - $length * $Speed; $delay -gt 0 } else { $false }
    if ($AddPreffix -and ![string]::IsNullOrEmpty($Preffix)) {
      [void]$this.Write($Preffix, [string]::Empty, 1, 100, [ConsoleColor]::Green, $false, $false);
    }
    $FgColr = [Console]::ForegroundColor
    if (!$this.WriteRaw) { [Console]::ForegroundColor = $color }
    $hostUI = (Get-Host).UI
    if ($Animate) {
      for ($i = 0; $i -lt $length; $i++) {
        if ($this.WriteRaw) { [System.Console]::Write($text[$i]) } else { $hostUI.Write($text[$i]) }
        Start-Sleep -Milliseconds $Speed;
      }
    } else {
      if ($this.WriteRaw) {
        [System.Console]::Write($text)
      } else {
        $hostUI.Write($text)
      }
    }
    if ($delayIsRequired) { Start-Sleep -Milliseconds $delay }
    if (!$this.WriteRaw) { [Console]::ForegroundColor = $FgColr }
    return $text
  }
  static [byte[]] Encode([string]$text) {
    return [System.Text.Encoding]::UTF8.GetBytes($text)
  }
  static [string[]] get_ColorNames() {
    return [RGB].GetMethods().Where({ $_.IsStatic -and $_.Name -like "Get_*" }).Name.Substring(4)
  }
  static [int] get_ConsoleWidth() {
    # Force a refresh of the console information
    [System.Console]::SetCursorPosition([System.Console]::CursorLeft, [System.Console]::CursorTop)
    return [System.Console]::WindowWidth
  }
  hidden [System.Text.Encoding] get_Encoding() { return [System.Text.Encoding]::UTF8 }
  static [void] Clear() {
    [System.Console]::Clear()
  }
  static [void] SetCursorPosition([int]$X, [int]$Y) {
    [System.Console]::SetCursorPosition($X, $Y)
  }
  static [void] SetCursorPosition([ConsoleCoordinate]$Coordinate) {
    [System.Console]::SetCursorPosition($Coordinate.X, $Coordinate.Y)
  }
  static [void] ResetColor() {
    [System.Console]::ResetColor()
  }
  [void] WriteLine() {
    $this.WriteRaw ? [System.Console]::WriteLine() : (Get-Host).UI.WriteLine()
  }
  [void] WriteLine([string]$text) {
    [void]$this.Write($text)
    $this.WriteLine()
  }
  [void] WriteLine([string]$text, [ConsoleColor]$color) {
    [void]$this.Write($text, $color)
    $this.WriteLine()
  }
}