Theme.PSReadLine.psm1
using namespace System.Collections.Generic using namespace System.Management.Automation.Language #Region '.\Private\ConvertToCssColor.ps1' 0 #using namespace System.Collections.Generic function ConvertToCssColor { [CmdletBinding()] param( [Parameter(ParameterSetName="PListColorDictionary", Mandatory, Position = 0)] [Dictionary[string,object]]$colors, [Parameter(ParameterSetName="ColorValue", Mandatory, Position = 0)] [string]$color, [switch]$Background ) end { if ($PSCmdlet.ParameterSetName -eq "PListColorDictionary") { [int]$r = 255 * $colors["Red Component"] [int]$g = 255 * $colors["Green Component"] [int]$b = 255 * $colors["Blue Component"] [PoshCode.Pansies.RgbColor]::new($r, $g, $b).ToVtEscapeSequence($Background) } if ($PSCmdlet.ParameterSetName -eq "ColorValue") { $color = $color -replace '^#[0-9a-f]{2}([0-9a-f]{6})$', '#$1' [PoshCode.Pansies.RgbColor]::new($color).ToVtEscapeSequence($Background) } } } #EndRegion '.\Private\ConvertToCssColor.ps1' 27 #Region '.\Private\FindVSCodeTheme.ps1' 0 function FindVsCodeTheme { [CmdletBinding(DefaultParameterSetName = "Specific")] param( [Parameter(ParameterSetName = "List")] [Parameter(ParameterSetName = "Specific", Mandatory, Position = 0)] [string]$Name, [Parameter(ParameterSetName = "List", Mandatory)] [switch]$List ) $VSCodeExtensions = @( # VS Code themes are in one of two places: in the app, or in your profile folder: Convert-Path "~\.vscode*\extensions\" # If `code` is in your path, we can guess where that is... Get-Command Code-Insiders, Code -ErrorAction Ignore | Split-Path | Split-Path | Join-Path -ChildPath "resources\app\extensions\" ) $Warnings = @() $Themes = @( # If they passed a file path that exists, use just that one file if (-not $List -and ($Specific = Test-Path -LiteralPath $Name)) { $File = Convert-Path $Name $( if ($File.EndsWith(".json")) { try { # Write-Debug "Parsing json file: $File" ConvertFrom-Json (Get-Content -Path $File -Raw -Encoding utf8) -ErrorAction SilentlyContinue } catch { Write-Error "Couldn't parse '$File'. $( if($PSVersionTable.PSVersion.Major -lt 6) { 'You could try again with PowerShell Core, the JSON parser there works much better!' })" } } else { # Write-Debug "Parsing PList file: $File" Import-PList -Path $File } ) | Select-Object @{ Name = "Name" Expr = { if ($_.name) { $_.name } else { [IO.Path]::GetFileNameWithoutExtension($File) } } }, @{ Name = "Path" Expr = {$File} } } else { $VSCodeExtensions = $VSCodeExtensions | Join-Path -ChildPath "\*\package.json" -Resolve foreach ($File in $VSCodeExtensions) { Write-Debug "Considering VSCode Extention $([IO.Path]::GetFileName([IO.Path]::GetDirectoryName($File)))" $JSON = Get-Content -Path $File -Raw -Encoding utf8 try { $Extension = ConvertFrom-Json $JSON -ErrorAction Stop # if ($Extension.contributes.themes) { # Write-Debug "Found $($Extension.contributes.themes.Count) themes" # } $Extension.contributes.themes | Select-Object @{Name="Name" ; Expr={$_.label}}, @{Name="Style"; Expr={$_.uiTheme}}, @{Name="Path" ; Expr={Join-Path (Split-Path $File) $_.path -resolve}} } catch { $Warning = "Couldn't parse some VSCode extensions." } } } ) if ($Themes.Count -eq 0) { throw "Could not find any VSCode themes. Please use a full path." } if ($Specific -and $Themes.Count -eq 1) { $Themes } $Themes = $Themes | Sort-Object Name if ($List) { Write-Verbose "Found $($Themes.Count) Themes" $Themes return } # Make sure we're comparing the name to a name $Name = [IO.Path]::GetFileName(($Name -replace "\.json$|\.tmtheme$")) Write-Debug "Testing theme names for '$Name'" # increasingly fuzzy search: (eq -> like -> match) if (!($Theme = $Themes.Where{$_.name -eq $Name})) { if (!($Theme = $Themes.Where{$_.name -like $Name})) { if (!($Theme = $Themes.Where{$_.name -like "*$Name*"})) { foreach($Warning in $Warnings) { Write-Warning $Warning } Write-Error "Couldn't find the theme '$Name', please try another: $(($Themes.name | Select-Object -Unique) -join ', ')" } } } if (@($Theme).Count -gt 1) { $Dupes = $(if (@($Theme.Name | Sort-Object -Unique).Count -gt 1) {$Theme.Name} else {$Theme.Path}) -join ", " Write-Warning "Found more than one theme for '$Name'. Using '$(@($Theme)[0].Path)', but you could try again for one of: $Dupes)" } @($Theme)[0] } #EndRegion '.\Private\FindVSCodeTheme.ps1' 110 #Region '.\Private\GetColorProperty.ps1' 0 function GetColorProperty{ <# .SYNOPSIS Search the colors for a matching theme color name and returns the foreground #> param( # The array of colors [Array]$colors, # An array of (partial) scope names in priority order # The foreground color of the first matching scope in the tokens will be returned [string[]]$name, [switch]$background ) # Since we loaded the themes in order of prescedence, we take the first match that has a foreground color foreach ($pattern in $name) { if ($foreground = @($colors.$pattern).Where{ $_ }[0]) { if ($pattern -match "Background(Color)?") { $background = $true } ConvertToCssColor $foreground -Background:$background return } if ($key, $property = $pattern -split "\.") { if ($foreground = @($colors.$key.$property).Where{ $_ }[0]) { if ($property -match "Background(Color)?") { $background = $true } ConvertToCssColor $foreground -Background:$background return } } # Normalize color } } #EndRegion '.\Private\GetColorProperty.ps1' 37 #Region '.\Private\GetColorScopeForeground.ps1' 0 function GetColorScopeForeground { <# .SYNOPSIS Search the tokens for a scope name with a foreground color #> param( # The array of tokens [Array]$tokens, # An array of (partial) scope names in priority order # The foreground color of the first matching scope in the tokens will be returned [string[]]$name ) # Since we loaded the themes in order of prescedence, we take the first match that has a foreground color foreach ($pattern in $name) { foreach ($token in $tokens) { if (($token.scope -split "\s*,\s*" -match $pattern) -and $token.settings.foreground) { ConvertToCssColor $token.settings.foreground return } } } } #EndRegion '.\Private\GetColorScopeForeground.ps1' 24 #Region '.\Private\ImportJsonIncludeLast.ps1' 0 function ImportJsonIncludeLast { <# .SYNOPSIS Import VSCode json themes, including any included themes #> [CmdletBinding()] param([string[]]$Path) # take the first $themeFile, $Path = $Path $theme = Get-Content $themeFile | ConvertFrom-Json # Output all the colors or token colors if ($theme.colors) { $theme.colors } if ($theme.tokenColors) { $theme.tokenColors } # Recurse includes if ($theme.include) { $Path += $themeFile | Split-Path | Join-Path -Child $theme.include | convert-path } if ($Path) { ImportJsonIncludeLast $Path } } #EndRegion '.\Private\ImportJsonIncludeLast.ps1' 29 #Region '.\Private\WriteToken.ps1' 0 function WriteToken { [CmdletBinding()] param( [Parameter(Mandatory)] [System.Management.Automation.Language.Token] $Token, [Parameter(Mandatory)] [Text.StringBuilder] $StringBuilder, $Theme ) $null = switch ($token) { { $_ -is [StringExpandableToken] } { $startingOffset = $_.Extent.StartOffset $lastEndOffset = $startingOffset foreach ($nestedToken in $_.NestedTokens) { $StringBuilder.Append($Theme.StringColor) $StringBuilder.Append($_.Text, $lastEndOffset - $startingOffset, $nestedToken.Extent.StartOffset - $lastEndOffset) WriteToken -Token $nestedToken -StringBuilder $StringBuilder -Theme $Theme $lastEndOffset = $nestedToken.Extent.EndOffset } $StringBuilder.Append($Theme.StringColor) $StringBuilder.Append($_.Text, $lastEndOffset - $startingOffset, $_.Extent.EndOffset - $lastEndOffset) return } { $_ -is [StringToken] } { if ($_.TokenFlags.HasFlag([TokenFlags]::CommandName)) { $StringBuilder.Append($Theme.CommandColor) break } $StringBuilder.Append($Theme.StringColor) break } { $_ -is [NumberToken] } { $StringBuilder.Append($Theme.NumberColor); break } { $_ -is [ParameterToken] } { $StringBuilder.Append($Theme.ParameterColor); break } { $_ -is [VariableToken] } { $StringBuilder.Append($Theme.VariableColor); break } { $_.TokenFlags.HasFlag([TokenFlags]::BinaryOperator) } { $StringBuilder.Append($Theme.OperatorColor); break } { $_.TokenFlags.HasFlag([TokenFlags]::UnaryOperator) } { $StringBuilder.Append($Theme.OperatorColor); break } { $_.TokenFlags.HasFlag([TokenFlags]::CommandName) } { $StringBuilder.Append($Theme.CommandColor); break; } { $_.TokenFlags.HasFlag([TokenFlags]::MemberName) } { $StringBuilder.Append($Theme.MemberColor); break; } { $_.TokenFlags.HasFlag([TokenFlags]::TypeName) } { $StringBuilder.Append($Theme.TypeColor); break; } { $_.TokenFlags.HasFlag([TokenFlags]::Keyword) } { $StringBuilder.Append($Theme.KeywordColor); break; } { $_ -is [StringToken] } { $StringBuilder.Append($Theme.StringColor); break } default { $StringBuilder.Append($Theme.DefaultTokenColor); break } } $null = $StringBuilder.Append($token.Text) } #EndRegion '.\Private\WriteToken.ps1' 51 #Region '.\Public\Get-PSReadLineTheme.ps1' 0 function Get-PSReadLineTheme { <# .SYNOPSIS Returns a hashtable of the _current_ values that can be splatted to Set-Theme #> [CmdletBinding()] param() Get-PSReadLineOption | Select-Object *Color } #EndRegion '.\Public\Get-PSReadLineTheme.ps1' 10 #Region '.\Public\Get-VSCodeTheme.ps1' 0 function Get-VSCodeTheme { <# .SYNOPSIS Get a PSReadLine theme from a VS Code Theme that you have installed locally. .DESCRIPTION Gets PSReadLine colors from a Visual Studio Code Theme. Only works with locally installed Themes, but includes tab-completion for theme names so you can Ctrl+Space to list the ones you have available. The default output will show a little preview of what PSReadLine will look like. Note that the PSReadLine theme will _not_ set the background color. You can pipe the output to Set-PSReadLineTheme to import the theme for the PSReadLine module. Note that you may want to use -Verbose to see details of the import. In some cases, Get-VSCodeTheme will not be able to determine values for all PSReadLine colors, and there is a verbose output showing the colors that get the default value. .Example Get-VSCodeTheme 'Light+ (default light)' Gets the default "Dark+" theme from Code and shows you a preview. Note that to use this theme effectively, you need to have your terminal background color set to a light color like the white in the preview. .Example Get-VSCodeTheme 'Dark+ (default dark)' | Set-PSReadLineTheme Imports the "Dark+" theme from Code and sets it as your PSReadLine color theme. .Link Set-PSReadLineTheme Get-PSReadLineTheme #> [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = "ByName")] param( # The name of (or full path to) a vscode json theme which you have installed # E.g. 'Dark+' or 'Monokai' [ArgumentCompleter({ param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) Get-VSCodeTheme -List | ForEach-Object { if ($_.Name -match "[\s'`"]") { "'{0}'" -f ($_.Name -replace "'", "''") } else { $_.Name } } | Where-Object { $_.StartsWith($wordToComplete) } })] [Alias("PSPath", "Name")] [Parameter(ParameterSetName = "ByName", ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)] [string]$Theme, # List the VSCode themes available [Parameter(ParameterSetName = "ListOnly", Mandatory)] [switch]$List ) process { if ($List) { FindVsCodeTheme -List return } else { $VsCodeTheme = FindVsCodeTheme $Theme -ErrorAction Stop } if ($PSCmdlet.ShouldProcess($VsCodeTheme.Path, "Import PSReadLine colors from theme")) { # Load the theme file and split the output into colors and tokencolors if ($VsCodeTheme.Path.endswith(".json")) { $colors, $tokens = (ImportJsonIncludeLast $VsCodeTheme.Path).Where({!$_.scope}, 'Split', 2) } else { $colors, $tokens = (Import-PList $VsCodeTheme.Path).settings.Where({!$_.scope}, 'Split', 2) $colors = $colors.settings } $ThemeOutput = [Ordered]@{ PSTypeName = 'Selected.Microsoft.PowerShell.PSConsoleReadLineOptions' # These should come from the base colors, rather than token scopes BackgroundColor = GetColorProperty $colors 'editor.background', 'background', 'settings.background', 'terminal.background' DefaultTokenColor = GetColorProperty $colors 'editor.foreground', 'foreground', 'settings.foreground', 'terminal.foreground' SelectionColor = GetColorProperty $colors 'editor.selectionBackground', 'editor.selectionHighlightBackground', 'selection' -Background ErrorColor = @(@(GetColorProperty $colors 'errorForeground', 'editorError.foreground') + @(GetColorScopeForeground $tokens 'invalid'))[0] # The rest of these come from token scopes CommandColor = GetColorScopeForeground $tokens 'support.function' CommentColor = GetColorScopeForeground $tokens 'comment' ContinuationPromptColor = GetColorScopeForeground $tokens 'constant.character' EmphasisColor = GetColorScopeForeground $tokens 'markup.bold', 'markup.italic', 'emphasis', 'strong', 'constant.other.color', 'markup.heading' InlinePredictionColor = GetColorScopeForeground $tokens 'markup.underline', KeywordColor = GetColorScopeForeground $tokens '^keyword.control$', '^keyword$', 'keyword.control', 'keyword' MemberColor = GetColorScopeForeground $tokens 'variable.other.object.property', 'member', 'type.property', 'support.function.any-method', 'entity.name.function' NumberColor = GetColorScopeForeground $tokens 'constant.numeric', 'constant' OperatorColor = GetColorScopeForeground $tokens 'keyword.operator$', 'keyword' ParameterColor = GetColorScopeForeground $tokens 'parameter' StringColor = GetColorScopeForeground $tokens '^string$' TypeColor = GetColorScopeForeground $tokens '^storage.type$', '^support.class$', '^entity.name.type.class$', '^entity.name.type$' VariableColor = GetColorScopeForeground $tokens '^variable$', '^entity.name.variable$', '^variable.other$' } <# ###### We *COULD* map some colors to other themable modules ##### # If the VSCode Theme has terminal colors, and you had Theme.Terminal or Theme.WindowsTerminal or Theme.WindowsConsole if ($colors.'terminal.ansiBrightYellow') { Write-Verbose "Exporting Theme.Terminal" $ThemeOutput['Theme.Terminal'] = @( GetColorProperty $colors "terminal.ansiBlack" GetColorProperty $colors "terminal.ansiRed" GetColorProperty $colors "terminal.ansiGreen" GetColorProperty $colors "terminal.ansiYellow" GetColorProperty $colors "terminal.ansiBlue" GetColorProperty $colors "terminal.ansiMagenta" GetColorProperty $colors "terminal.ansiCyan" GetColorProperty $colors "terminal.ansiWhite" GetColorProperty $colors "terminal.ansiBrightBlack" GetColorProperty $colors "terminal.ansiBrightRed" GetColorProperty $colors "terminal.ansiBrightGreen" GetColorProperty $colors "terminal.ansiBrightYellow" GetColorProperty $colors "terminal.ansiBrightBlue" GetColorProperty $colors "terminal.ansiBrightMagenta" GetColorProperty $colors "terminal.ansiBrightCyan" GetColorProperty $colors "terminal.ansiBrightWhite" ) if ($colors."terminal.background") { $ThemeOutput['Theme.Terminal']['background'] = GetColorProperty $colors "terminal.background" } if ($colors."terminal.foreground") { $ThemeOutput['Theme.Terminal']['foreground'] = GetColorProperty $colors "terminal.foreground" } } # If the VSCode Theme has warning/info colors, and you had Theme.PowerShell if (GetColorProperty $colors 'editorWarning.foreground') { $ThemeOutput['Theme.PowerShell'] = @{ WarningForegroundColor = GetColorProperty $colors 'editorWarning.foreground' ErrorForegroundColor = GetColorProperty $Colors 'editorError.foreground' VerboseForegroundColor = GetColorProperty $Colors 'editorInfo.foreground' ProgressForegroundColor = GetColorProperty $Colors 'notifications.foreground' ProgressBackgroundColor = GetColorProperty $Colors 'notifications.background' } } #> if ($DebugPreference -in "Continue", "Inquire") { $global:colors = $colors $global:tokens = $tokens $global:Theme = $ThemeOutput ${function:global:Get-VSColorScope} = ${function:GetColorScopeForeground} ${function:global:Get-VSColor} = ${function:GetColorProperty} Write-Debug "For debugging, `$Theme, `$Colors, `$Tokens were copied to global variables, and Get-VSColor and Get-VSColorScope exported." } if ($ThemeOutput.Values -contains $null) { [string[]]$missing = @() foreach ($kv in @($ThemeOutput.GetEnumerator())) { if ($null -eq $kv.Value) { $missing += $kv.Key $ThemeOutput[$kv.Key] = $ThemeOutput["DefaultToken"] } } Write-Verbose "Used DefaultTokenColor for some colors: $($missing -join ', ')" } [PSCustomObject]$ThemeOutput } } } #EndRegion '.\Public\Get-VSCodeTheme.ps1' 152 #Region '.\Public\Set-PSReadLineTheme.ps1' 0 function Set-PSReadLineTheme { <# .SYNOPSIS Set the theme for PSReadLine Has parameters for each thing that's themeable #> [CmdletBinding()] param( [Parameter(ValueFromPipelineByPropertyName)] $CommandColor = "$([char]27)[93m", [Parameter(ValueFromPipelineByPropertyName)] $CommentColor = "$([char]27)[32m", [Parameter(ValueFromPipelineByPropertyName)] $ContinuationPromptColor = "$([char]27)[97m", [Parameter(ValueFromPipelineByPropertyName)] $DefaultTokenColor = "$([char]27)[97m", [Parameter(ValueFromPipelineByPropertyName)] $EmphasisColor = "$([char]27)[96m", [Parameter(ValueFromPipelineByPropertyName)] $ErrorColor = "$([char]27)[91m", [Parameter(ValueFromPipelineByPropertyName)] $KeywordColor = "$([char]27)[92m", [Parameter(ValueFromPipelineByPropertyName)] $MemberColor = "$([char]27)[97m", [Parameter(ValueFromPipelineByPropertyName)] $NumberColor = "$([char]27)[97m", [Parameter(ValueFromPipelineByPropertyName)] $OperatorColor = "$([char]27)[90m", [Parameter(ValueFromPipelineByPropertyName)] $ParameterColor = "$([char]27)[90m", [Parameter(ValueFromPipelineByPropertyName)] $InlinePredictionColor = "$([char]27)[90m", [Parameter(ValueFromPipelineByPropertyName)] $SelectionColor = "$([char]27)[30;107m", [Parameter(ValueFromPipelineByPropertyName)] $StringColor = "$([char]27)[36m", [Parameter(ValueFromPipelineByPropertyName)] $TypeColor = "$([char]27)[37m", [Parameter(ValueFromPipelineByPropertyName)] $VariableColor = "$([char]27)[92m" ) process { $ParameterNames = $MyInvocation.MyCommand.Parameters.Keys.Where{ $_ -notin [System.Management.Automation.PSCmdlet]::CommonParameters } $Colors = @{} foreach ($ParameterName in $ParameterNames) { if (($Value = Get-Variable -Scope Local -Name $ParameterName -ValueOnly)) { $ColorName = $ParameterName -replace "(token)?color" # This is only working when I use the AsEscapeSequence, but the input values are already escape sequences! $Colors[$ColorName] = [Microsoft.PowerShell.VTColorUtils]::AsEscapeSequence( $Value ) } } Set-PSReadLineOption -Colors $Colors } } #EndRegion '.\Public\Set-PSReadLineTheme.ps1' 58 #Region '.\Public\Show-Code.ps1' 0 #using namespace System.Management.Automation.Language function Show-Code { [CmdletBinding(DefaultParameterSetName = 'HistoryId')] param( # A script block or a path to a script file [Parameter(Mandatory, ParameterSetName = 'Script', Position = 0)] [string]$Script, # The history id of the script to show [Parameter(Mandatory, ParameterSetName = 'History', Position = 0)] [int]$HistoryId, # The PSReadLine theme to use. If not specified, the current theme is used. # Can be the output of Get-VSCodeTheme or Get-PSReadLineTheme $Theme = (Get-PSReadLineTheme) ) if ($HistoryId) { $Script = (Get-History -Id $HistoryId).CommandLine } $ParseErrors = $null $Tokens = $null $null = if (Test-Path "$Script" -ErrorAction SilentlyContinue) { [System.Management.Automation.Language.Parser]::ParseFile((Convert-Path $Script), [ref]$Tokens, [ref]$ParseErrors) } else { [System.Management.Automation.Language.Parser]::ParseInput([String]$Script, [ref]$Tokens, [ref]$ParseErrors) } $lastEndOffset = 0 $StringBuilder = [Text.StringBuilder]::new() foreach ($token in $tokens) { $null = $StringBuilder.Append([char]' ', ($token.Extent.StartOffset - $lastEndOffset)) $lastEndOffset = $token.Extent.EndOffset $null = WriteToken -Token $token -StringBuilder $StringBuilder -Theme $Theme } # Reset the colors $null = $StringBuilder.Append("$([char]0x1b)[0m$([char]0x1b)[24m$([char]0x1b)[27m") $StringBuilder.ToString() } #EndRegion '.\Public\Show-Code.ps1' 41 #Region '.\Public\test.ps1' 0 #EndRegion '.\Public\test.ps1' 1 #Region '.\postfix.ps1' 0 if (Get-Module EzTheme -ErrorAction SilentlyContinue) { Get-ModuleTheme | Set-PSReadLineTheme } #EndRegion '.\postfix.ps1' 4 |