Theme.PSReadLine.psm1
using namespace System.Collections.Generic using namespace System.Management.Automation.Language #Region '.\Private\ConvertToCssColor.ps1' -1 #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' -1 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' -1 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' -1 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' -1 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' -1 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 + "`e[0m") } #EndRegion '.\Private\WriteToken.ps1' 51 #Region '.\Public\Get-PSReadLineTheme.ps1' -1 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' -1 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' -1 function Set-PSReadLineTheme { <# .SYNOPSIS Set the theme for PSReadLine as escape sequences. .DESCRIPTION Set the theme for PSReadLine. Supports setting all the properties that are formatting related. Takes colors and other formatting options (bold, underline, etc.) as escape sequences. #> [CmdletBinding()] param( [Parameter(ValueFromPipelineByPropertyName)] $CommandColor, [Parameter(ValueFromPipelineByPropertyName)] $CommentColor, [Parameter(ValueFromPipelineByPropertyName)] $ContinuationPromptColor, [Parameter(ValueFromPipelineByPropertyName)] $DefaultTokenColor, [Parameter(ValueFromPipelineByPropertyName)] $EmphasisColor, [Parameter(ValueFromPipelineByPropertyName)] $ErrorColor, [Parameter(ValueFromPipelineByPropertyName)] $KeywordColor, [Parameter(ValueFromPipelineByPropertyName)] $MemberColor, [Parameter(ValueFromPipelineByPropertyName)] $NumberColor, [Parameter(ValueFromPipelineByPropertyName)] $OperatorColor, [Parameter(ValueFromPipelineByPropertyName)] $ParameterColor, [Parameter(ValueFromPipelineByPropertyName)] $InlinePredictionColor, [Parameter(ValueFromPipelineByPropertyName)] $SelectionColor, [Parameter(ValueFromPipelineByPropertyName)] $StringColor, [Parameter(ValueFromPipelineByPropertyName)] $TypeColor, [Parameter(ValueFromPipelineByPropertyName)] $VariableColor ) process { $ParameterNames = $PSBoundParameters.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' 60 #Region '.\Public\Show-Code.ps1' -1 #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 '.\postfix.ps1' -1 if (Get-Module EzTheme -ErrorAction SilentlyContinue) { Get-ModuleTheme | Set-PSReadLineTheme } #EndRegion '.\postfix.ps1' 4 |