Private/Show-Token.ps1
|
. (Join-Path $PSScriptRoot "Get-TokenColor.ps1") . (Join-Path $PSScriptRoot "Show-Name.ps1") # Write a scrap of text, that is part of a token (may be the entire token) function Write-Scrap($scrap, $token) { $tokenColor = (Get-TokenColor $token.Kind $token.TokenFlags); $quoteColor = [System.ConsoleColor]::Cyan; $dollarColor = [System.ConsoleColor]::Gray; $secondTokenColor = [System.ConsoleColor]::DarkCyan; $commentHashColor = [System.ConsoleColor]::DarkGreen; # Controversial! Write '$' and '#' (at start of Variable and Comment respectively) # in a **different** color to the rest of the variable/comment! if ($token.Kind -eq [System.Management.Automation.Language.TokenKind]::Variable -and $scrap -like '$*') { Write-Host '$' -ForegroundColor $dollarColor -NoNewline # Very *very* controversial -- different pascalCaseWordsAreDifferentColors !! Show-Name ($scrap.Substring(1)) -ForegroundColor $tokenColor -SecondForeGroundColor $secondTokenColor -NoNewline } elseif ($token.Kind -eq [System.Management.Automation.Language.TokenKind]::DollarParen -and $scrap -like '$*') { # special case for '$( )' -- that's a `dollarParen` (and later, an `RParen`) # We use our 'dollarColor' for the dollar in '$(' too -- in contradistinction to other highlighters. Write-Host '$' -ForegroundColor $dollarColor -NoNewline # write the rest of it in the default token color... Write-Host ($scrap.Substring(1)) -ForegroundColor $tokenColor -NoNewline } elseif ($token.Kind -eq [System.Management.Automation.Language.TokenKind]::Comment -and $scrap -like '#*') { Write-Host '#' -ForegroundColor $commentHashColor -NoNewline Write-Host ($scrap.Substring(1)) -ForegroundColor $tokenColor -NoNewline } elseif ($token.Kind -eq [System.Management.Automation.Language.TokenKind]::Comment -and $scrap -like '<#*') { # it starts with <# ... if ($scrap -like '*#>') { # *and it ends with <#* Write-Host '<#' -ForegroundColor $commentHashColor -NoNewline Write-Host ($scrap.Substring(2, $scrap.length -4)) -ForegroundColor $tokenColor -NoNewline Write-Host '#>' -ForegroundColor $commentHashColor -NoNewline } else { Write-Host '<#' -ForegroundColor $commentHashColor -NoNewline Write-Host ($scrap.Substring(2)) -ForegroundColor $tokenColor -NoNewline } } elseif ( ($token.Kind -eq [System.Management.Automation.Language.TokenKind]::StringExpandable -or $token.Kind -eq [System.Management.Automation.Language.TokenKind]::StringLiteral -or $token.Kind -eq [System.Management.Automation.Language.TokenKind]::HereStringExpandable) -and ($scrap -like '"*' -or $scrap -like '*"' -or $scrap -like '''*' -or $scrap -like '*''' -or $scrap -like '@*' -or $scrap -like '*@')) { # Controversial: Write quote characters at start and end of strings in a different color. # Even VS Code doesn't attempt this. Write-Debug "<# tk: $($token.Kind), Tf: $($token.TokenFlags) #>" if ($scrap -like '"*' -or $scrap -like '''*' -or $scrap -like '@*') { # Starts with a quote, or a here-string '@' -- write the start character in special color Write-Host ($scrap.Substring(0, 1)) -ForegroundColor $quoteColor -NoNewline; } else { # Write the first character in regular color... Write-Host ($scrap.Substring(0, 1)) -ForegroundColor $tokenColor -NoNewline; } # At this point we've written precisely one character of our string. if ($scrap -like '*"' -or $scrap -like '*''' -or $scrap -like '*@' -and $scrap.length -gt 1) { # Ends with a quote or here-string '@' ? -- write the rest *before* the quote... in 'regular' color Write-Host ($scrap.Substring(1, $scrap.Length - 2)) -ForegroundColor $tokenColor -NoNewline; # ... and then write that final character in special color. Write-Host ($scrap.Substring($scrap.Length - 1)) -ForegroundColor $quoteColor -NoNewline; } elseif ($scrap.length -gt 1) { # just write the remainder in regular color Write-Host ($scrap.Substring(1)) -ForegroundColor $tokenColor -NoNewline; } } else { # Regular way to write a token in a single color: Write-Debug "<# tk: $($token.Kind), Tf: $($token.TokenFlags) #>"; Write-Host ($scrap) -ForegroundColor $tokenColor -NoNewline; } # Consider: VS Code colors brackets/braces/parens according to their 'depth' # -- cycling between 3 colors (Yellow, Pink, Blue...) # this makes "brace matching" easier for the coder. } function Show-Token { Param( [Parameter(Mandatory, ValueFromPipeline = $true, HelpMessage = 'Tokens to be highlighted', Position = 0)] [System.Management.Automation.Language.Token[]]$Tokens, $charNumX ) Begin { $charNum = $host.UI.RawUI.CursorPosition.X; if ($null -ne $charNumX) { $charNum = $charNumX; } else { if ($charNum -eq 0) { $charNum = 1; } } $lineNum = 1; if ($null -ne $Tokens -and $Tokens[0].Extent.StartLineNumber -gt 1) { $lineNum = $Tokens[0].Extent.StartLineNumber; } } Process { ForEach ($token in $tokens) { if ($token.Extent.StartLineNumber -gt $lineNum) { $charNum = 1; } if ($token.Extent.StartColumnNumber -gt $charNum) { $numSpaces = ($token.Extent.StartColumnNumber - $charNum); Write-Host (" " * $numSpaces) -NoNewline; $charNum += $numSpaces; } if ($null -ne $token.NestedTokens) { # NESTED TOKENS ARE FUN # Strings (and here-strings) can contain nested tokens (as do nested expressions) # # write-host "This is my name $myName and yours is $yourName I believe!" # |-----------------outer token ----------------------------| <-- $token # write-host "This is my name $myName and yours is $yourName I believe!" # |--t1-| |---t2--| <-- $token.NestedTokens # write-host "This is my name $myName and yours is $yourName I believe!" # |----between1----| |---between2---| <-- between Nested tokens # write-host "This is my name $myName and yours is $yourName I believe!" # |---after--| <-- after Nested tokens # # Observations: # - there are as many 'betweens' as there are nested tokens. # - there is exactly 1 'after'. # - the quotes (which differ from string, to herestring etc.) are part of between1 and after. $tokenStartOffset = $token.Extent.StartOffset; $upTo = $tokenStartOffset; ForEach ($innerToken in $token.NestedTokens) { if ($innerToken.Extent.StartOffset -gt $upTo) { # write the part of the 'outer token' that comes before the first nested token, or: # write the between: if ($token.Text.length -ge ($innerToken.Extent.StartOffset - $tokenStartOffset)) { #Write-Host ($token.Text.Substring($upTo - $tokenStartOffset, $innerToken.Extent.StartOffset - $upTo)) -f $tokenColor -n; Write-Scrap ($token.Text.Substring($upTo - $tokenStartOffset, $innerToken.Extent.StartOffset - $upTo)) $token; } else { #Write-Host "XX" -f red -n; (wonder if this is end of input?) } } # Here is the recursive part... # In the comment above, `t1` was simply "$myName" -- but it could be a complex # expression containing its own nested tokens... e.g. "$($myName)" (and much much more!) # So we need to recurse, and have show-token show that t1... Show-Token $innerToken -charNumX:$innerToken.Extent.StartColumnNumber; $upTo = $innerToken.Extent.EndOffset; } if ($upTo -lt ($token.Text.Length + $tokenStartOffset)) { # write the 'after' (see comment section above) Write-Scrap $token.Text.Substring($upTo - $tokenStartOffset) $token; } } else { Write-Scrap $token.Text $token; } $lineNum = $token.Extent.EndLineNumber; $charNum = $token.Extent.EndColumnNumber; }; } End { } } |