Skip to content

Instantly share code, notes, and snippets.

@jhoneill
Created May 9, 2023 14:09
Show Gist options
  • Save jhoneill/f327b7913735db4ab6f1adaaa7bcf67d to your computer and use it in GitHub Desktop.
Save jhoneill/f327b7913735db4ab6f1adaaa7bcf67d to your computer and use it in GitHub Desktop.
#requires -module psreadline
using namespace System.Collections
using namespace System.Collections.Generic
using namespace System.Management.Automation
using namespace System.Management.Automation.Language
#create a hash table of VT codes
# Reverse, underline and reset; the console colors as Name, NameBackground; Markdown theme colors if present, PsReadline theme colors, and out-stream colors
if (-not $host.UI.SupportsVirtualTerminal) {$Script:VTCodes = @{}}
else {
$esc = [char]0x1b
([System.ConsoleColor]).GetEnumNames() |
ForEach-Object -Begin {$Script:VTCodes = [ordered]@{"UnderLine"="$esc[4m";"Reverse"="$esc[7m";"Reset"="$esc[0m"}} -Process {
$Script:VTCodes[$_] = [Microsoft.PowerShell.VTColorUtils]::AsEscapeSequence($_)
$Script:VTCodes["$_`Background"] = [Microsoft.PowerShell.VTColorUtils]::AsEscapeSequence($_,$true)
}
try {
(Get-MarkdownOption).psobject.Properties | Where-Object -Property value -match "^\[\d" |
ForEach-Object -Process {$Script:VTCodes[$_.name] = $esc+ $_.value}
}
catch {Write-Verbose "Markdown not present, continuing"}
(Get-PSReadLineOption ).psobject.Properties | Where-Object -Property name -like "*color" |
ForEach-Object -Process {$Script:VTCodes[($_.name -Replace "color$","")] = [Microsoft.PowerShell.VTColorUtils]::AsEscapeSequence($_.value)}
$host.PrivateData.psobject.Properties | Where-Object -Property name -like "*color" |
ForEach-Object -Process {$Script:VTCodes[($_.name -Replace "(Foreground)?color$","")] = [Microsoft.PowerShell.VTColorUtils]::AsEscapeSequence($_.value,[boolean]($_.name -like "*BackgroundColor"))}
}
#completer class for VT codes - returns one of codes in the hash table above
class VTCodeCompleter : IArgumentCompleter {
[IEnumerable[CompletionResult]] CompleteArgument(
[string] $CommandName ,
[string] $ParameterName,
[string] $WordToComplete,
[CommandAst] $CommandAst,
[IDictionary] $FakeBoundParameters
)
{
$wildcard = ("*" + $wordToComplete + "*")
$completionResults = [List[CompletionResult]]::new()
$Script:VTCodes.keys.where({$_ -like $wildcard }) |
ForEach-Object {$completionResults.Add([CompletionResult]::new($_))}
return $completionResults
}
}
Function Add-VtFormat {
<#
.Synopsis
Adds VT formatting codes to strings, formatting either the whole string, or a matching part of it.
.Example
Add-VtFormat "arbitrary color" -vtcode 'DarkBlue', 'CyanBackground'
Sets the text to be Dark blue on a cyan background.
.Example
Add-vtformat "theme color" -VtCode 'Warning'
Sets the text to a the colour used for warning text.
.Example
Add-VtFormat "highlight this not that" -VtCode 'Reverse' -Select "this"
Highlights just word "this"
.Example
$s = select-string -path *.ps1 -SimpleMatch "\s*="
Add-VtFormat $s.line 'Reverse' -Select $s.Pattern -EscapeFirst
$S matchinfo object and $S.pattern holds a string which should NOT be treated as a regular
expression - Select String was told to find the litteral text "\s*=". To ensure that text is
highlighted , and not look for "space, any number of times, followed by =" -EscapeFirst is needed
.Example
Add-VtFormat "highlight this not that" -VtCode 'Reverse' -select "^.{4}"
Add-VtFormat "highlight this not that" -VtCode 'Reverse' -select ".{3}$"
Add-VtFormat "highlight this not that" -VtCode 'Reverse' -select "(?<=^.{15}).*"
Add-VtFormat "highlight this not that" -VtCode 'Reverse' -select "(?<=^.{15}).{3}"
Add-VtFormat "highlight this not that" -VtCode 'Reverse' -select "(?<=^.{15}).*(?=.{5})"
Add-VtFormat "highlight this not that" -VtCode 'Reverse' -select ".*(?=.{5})"
Using regular expressions to select first 4 chars, last 3 , all text after the first 15,
3 chars after the first 15, from the first 15 to the the last 5, everything up to the last 5.
#>
param (
#The Text to format
[Parameter(ValueFromPipeline=$true,Mandatory=$true)]
[string]$Text,
#One or more VT formatting code name(s) or escape string(s) to apply to the text
[ArgumentCompleter([VTCodeCompleter])]
[string[]]$VtCode,
#Applies the code to only a substring in -Text (by default the whole of text is format)
[string]$Select,
#If escapes any characters in -Select which have a special meaning in a regular expression
[switch]$EscapeFirst
)
process {
if ($DisableVTFormat -or -not $host.UI.SupportsVirtualTerminal) {$text}
else {
$codeString = ""
foreach ($code in $VTcode) {
if ($code -match "^\e\[\d.*m") {
$codeString += $code
}
else {
$codeString += $Script:VTCodes[$code]
}
}
if ($Select) {
if ($EscapeFirst) {$Select = [regex]::Escape($Select)}
$substition = $codeString + '$1' + $Script:VTCodes["Reset"]
$text -Replace "($Select)",$substition
}
else {
$codeString + $text + $Script:VTCodes["Reset"]
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment