From 22c8d9c03bdf729d80fc307c2225db328446b9f1 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 26 Sep 2023 01:38:46 +0200 Subject: [PATCH] BlueScreen: implemented PHP syntax highlighter --- src/Tracy/BlueScreen/CodeHighlighter.php | 80 +++++++++++++------ src/Tracy/BlueScreen/assets/bluescreen.css | 2 +- .../expected/Debugger.exception.html.expect | 17 ++-- 3 files changed, 66 insertions(+), 33 deletions(-) diff --git a/src/Tracy/BlueScreen/CodeHighlighter.php b/src/Tracy/BlueScreen/CodeHighlighter.php index 09b9b5164..bb94d8c68 100644 --- a/src/Tracy/BlueScreen/CodeHighlighter.php +++ b/src/Tracy/BlueScreen/CodeHighlighter.php @@ -15,6 +15,15 @@ final class CodeHighlighter { private const DisplayLines = 15; + private const PhpColors = [ + 'comment' => '#998; font-style: italic', + 'default' => '#000', + 'html' => '#06B', + 'keyword' => '#D24; font-weight: bold', + 'string' => '#080', + ]; + + /** * Extract a snippet from the code, highlights the row and column, and adds line numbers. */ @@ -56,7 +65,7 @@ public static function highlightLine(string $html, int $line, int $column = 0): 1, ); } - $out .= sprintf("%{$numWidth}s: %s\n%s", $n, $s, implode('', $openTags)); + $out .= sprintf("%{$numWidth}s: %s\n%s", $n, $s, implode('', $openTags)); } else { $out .= sprintf("%{$numWidth}s: %s\n", $n, $lines[$n]); } @@ -70,25 +79,50 @@ public static function highlightLine(string $html, int $line, int $column = 0): /** * Returns syntax highlighted source code. */ - public static function highlightPhp(string $source, int $line, int $column = 0): string + public static function highlightPhp(string $code, int $line, int $column = 0): string { - if (function_exists('ini_set')) { - ini_set('highlight.comment', '#998; font-style: italic'); - ini_set('highlight.default', '#000'); - ini_set('highlight.html', '#06B'); - ini_set('highlight.keyword', '#D24; font-weight: bold'); - ini_set('highlight.string', '#080'); - } + $html = self::highlightPhpCode($code); + $html = self::highlightLine($html, $line, $column); + return "
$html
"; + } - $source = preg_replace('#(__halt_compiler\s*\(\)\s*;).*#is', '$1', $source); - $source = str_replace(["\r\n", "\r"], "\n", $source); - $source = preg_replace('#/\*sensitive\{\*/.*?/\*\}\*/#s', Dumper\Describer::HiddenValue, $source); - $source = explode("\n", highlight_string($source, true)); - $out = $source[0]; // - $tmp = str_replace('
', "\n", $source[1]); - $out .= self::highlightLine($tmp, $line, $column); - $out = str_replace(' ', ' ', $out) . $source[2] . @$source[3]; - return "
$out
"; + + private static function highlightPhpCode(string $code): string + { + $code = str_replace("\r\n", "\n", $code); + $code = preg_replace('#(__halt_compiler\s*\(\)\s*;).*#is', '$1', $code); + $code = rtrim($code); + $code = preg_replace('#/\*sensitive\{\*/.*?/\*\}\*/#s', Dumper\Describer::HiddenValue, $code); + + $lastColor = $baseColor = self::PhpColors['html']; + $out = ""; + foreach (\PhpToken::tokenize($code) as $token) { + $nextColor = match ($token->id) { + T_INLINE_HTML => $baseColor, + T_COMMENT, T_DOC_COMMENT => self::PhpColors['comment'], + T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO, T_CLOSE_TAG, T_LINE, T_FILE, T_DIR, T_TRAIT_C, T_METHOD_C, T_FUNC_C, T_NS_C, T_CLASS_C, + T_STRING, T_VARIABLE, T_LNUMBER, T_DNUMBER => self::PhpColors['default'], + '"', T_ENCAPSED_AND_WHITESPACE, T_CONSTANT_ENCAPSED_STRING => self::PhpColors['string'], + T_WHITESPACE => $lastColor, + default => self::PhpColors['keyword'], + }; + + if ($lastColor !== $nextColor) { + if ($lastColor !== $baseColor) { + $out .= ''; + } + $lastColor = $nextColor; + if ($lastColor !== $baseColor) { + $out .= ""; + } + } + + $out .= strtr($token->text, ['<' => '<', '>' => '>', '&' => '&', "\t" => ' ', "\n" => "\n"]); + } + if ($lastColor !== $baseColor) { + $out .= ''; + } + return $out; } @@ -98,11 +132,11 @@ public static function highlightPhp(string $source, int $line, int $column = 0): public static function highlightPhpCli(string $code, int $line, int $column = 0): string { $colors = [ - 'color: ' . ini_get('highlight.comment') => '1;30', - 'color: ' . ini_get('highlight.default') => '1;36', - 'color: ' . ini_get('highlight.html') => '1;35', - 'color: ' . ini_get('highlight.keyword') => '1;37', - 'color: ' . ini_get('highlight.string') => '1;32', + 'color: ' . self::PhpColors['comment'] => '1;30', + 'color: ' . self::PhpColors['default'] => '1;36', + 'color: ' . self::PhpColors['html'] => '1;35', + 'color: ' . self::PhpColors['keyword'] => '1;37', + 'color: ' . self::PhpColors['string'] => '1;32', 'tracy-line' => '1;30', 'tracy-line-highlight' => "1;37m\e[41", ]; diff --git a/src/Tracy/BlueScreen/assets/bluescreen.css b/src/Tracy/BlueScreen/assets/bluescreen.css index 7e007625f..13cceb324 100644 --- a/src/Tracy/BlueScreen/assets/bluescreen.css +++ b/src/Tracy/BlueScreen/assets/bluescreen.css @@ -274,7 +274,7 @@ html.tracy-bs-visible body { font-style: normal; display: block; padding: 0 1ch; - margin: 0 -1ch; + margin: 0 -1ch -1lh; } #tracy-bs .tracy-column-highlight { diff --git a/tests/Tracy/expected/Debugger.exception.html.expect b/tests/Tracy/expected/Debugger.exception.html.expect index 024c9af69..7c821bcc9 100644 --- a/tests/Tracy/expected/Debugger.exception.html.expect +++ b/tests/Tracy/expected/Debugger.exception.html.expect @@ -43,8 +43,8 @@ %d%: %d%: function
third($arg1) %d%: { -%d%: throw new Exception('The my exception', 123); -%d%: } +%d%: throw new Exception('The my exception', 123); +%d%: } %d%: %d%: %d%: define('MY_CONST', 123); @@ -77,8 +77,8 @@ %d%: %d%: function second($arg1, $arg2) %d%: { -%d%: third([1, 2, 3]); -%d%: } +%d%: third([1, 2, 3]); +%d%: } %d%: %d%: %d%: function third($arg1) @@ -109,8 +109,8 @@ %d%: %d%: function first($arg1, $arg2) %d%: { -%d%: second(true, false); -%d%: } +%d%: second(true, false); +%d%: } %d%: %d%: %d%: function second($arg1, $arg2) @@ -143,9 +143,8 @@ %d%: %d%: define('MY_CONST', 123); %d%: @hex2bin('a'); // E_WARNING -%d%: first(10, 'any string'); -%d%: -
+%d%: first(10, 'any string'); +
$arg1
10