diff --git a/.gitattributes b/.gitattributes index 433a2de9a..3ea365552 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,10 +1,14 @@ .gitattributes export-ignore .github/ export-ignore .gitignore export-ignore +CLAUDE.md export-ignore ncs.* export-ignore phpstan*.neon export-ignore src/**/*.latte export-ignore tests/ export-ignore +tools/latte-convert/ export-ignore *.php* diff=php *.sh text eol=lf +tools/latte-convert/tests/fixtures/*.latte text eol=lf +tools/latte-convert/tests/fixtures/*.phtml text eol=lf diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..8defaa420 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,573 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Tracy is a debugging and error visualization library for PHP (8.2-8.5). It provides beautiful error pages (BlueScreen), an interactive debug toolbar (Bar), advanced variable dumping, and production-ready error logging. + +**Key features:** +- BlueScreen: Beautiful error/exception visualization with stack traces +- Tracy Bar: Floating debug toolbar with extensible panel system +- Dumper: Advanced variable dumping with multiple output formats +- Logger: Error logging with email notifications +- Production/development mode auto-detection +- AI agent support: automated browsers (navigator.webdriver) get markdown output via console.error/console.log + +## Essential Commands + +### Testing +```bash +# Run all tests - ALWAYS use php-cgi (html tests only run with php-cgi) +vendor/bin/tester tests -p php-cgi -s + +# Run single test file +vendor/bin/tester tests/Tracy/Debugger.timer().phpt -p php-cgi -s + +# Run tests in specific directory +vendor/bin/tester tests/Dumper/ -s +``` + +### Code Quality +```bash +# Run PHPStan static analysis (level 7) +composer run phpstan + +# Lint JavaScript assets +npm run lint +npm run lint:fix +``` + +## Core Architecture + +### Main Components (Facade Pattern) + +**Tracy\Debugger** (src/Tracy/Debugger/Debugger.php) - Central facade +- `enable()` - Initialize Tracy +- `dump()` / `bdump()` - Variable dumping +- `log()` - Error logging +- `timer()` - Performance profiling +- Global functions available: `dump()`, `dumpe()`, `bdump()` + +**Strategy Pattern:** +- `DevelopmentStrategy` - Shows full debug info, Tracy Bar +- `ProductionStrategy` - Logs errors, shows user-friendly messages +- `DeferredContent` - Manages deferred rendering for AJAX/redirect requests +- Auto-detection: localhost = development, otherwise production +- Agent detection: `Helpers::isAgent()` checks `$_COOKIE['tracy-webdriver']` — set by `bar.js` when `navigator.webdriver` is `true` (Chrome MCP, Playwright, Puppeteer, Selenium) + +### Core Components + +**BlueScreen** (src/Tracy/BlueScreen/) +- Error/exception page rendering with stack traces +- Multiple output formats: HTML, CLI, markdown (for AI agents via console) +- `renderAgent()` returns markdown string, output via `console.error()` in page ` + +``` + +### Session Storage + +**Use native PHP session:** +```php +session_start(); +Debugger::setSessionStorage(new Tracy\NativeSession); +Debugger::enable(); +``` + +**Complex session initialization:** +```php +Debugger::setSessionStorage(new Tracy\NativeSession); +Debugger::enable(); + +// Custom session initialization +session_start(); + +Debugger::dispatch(); // Inform Tracy session is ready +``` + +### nginx Configuration + +If Tracy doesn't work on nginx, fix the `try_files` directive: + +```nginx +# Wrong +try_files $uri $uri/ /index.php; + +# Correct +try_files $uri $uri/ /index.php$is_args$args; +``` + +### IDE Integration + +Tracy can open files directly in your editor when clicking file names in error pages. + +**Editor integration scripts:** +- Windows: `tools/open-in-editor/windows/` +- Linux: `tools/open-in-editor/linux/` +- macOS: Use built-in URL schemes + +**Built-in editor URLs (macOS):** +```php +// PhpStorm +Tracy\Debugger::$editor = 'phpstorm://open?file=%file&line=%line'; + +// VS Code +Tracy\Debugger::$editor = 'vscode://file/%file:%line'; + +// TextMate +Tracy\Debugger::$editor = 'txmt://open/?url=file://%file&line=%line'; +``` + +**Editor path mapping for remote/Docker:** +```php +Debugger::$editorMapping = [ + '/var/www/html' => 'W:\\Projects\\myapp', // Docker to Windows + '/app' => '/Users/dev/projects/myapp', // Container to macOS +]; +``` diff --git a/composer.json b/composer.json index 1c213cbfb..a772617ef 100644 --- a/composer.json +++ b/composer.json @@ -44,11 +44,12 @@ "minimum-stability": "dev", "scripts": { "phpstan": "phpstan analyse", - "tester": "tester tests" + "tester": "tester tests", + "compile-templates": "@php tools/latte-convert/compile.php src/Tracy" }, "extra": { "branch-alias": { - "dev-master": "2.12-dev" + "dev-master": "3.0-dev" } }, "config": { diff --git a/readme.md b/readme.md index 933419c07..2298b2d43 100644 --- a/readme.md +++ b/readme.md @@ -457,5 +457,6 @@ This is a list of unofficial integrations to other frameworks and CMS: - Symfony framework: [kutny/tracy-bundle](https://github.com/kutny/tracy-bundle), [VasekPurchart/Tracy-Blue-Screen-Bundle](https://github.com/VasekPurchart/Tracy-Blue-Screen-Bundle) - [Wordpress](https://github.com/ktstudio/WP-Tracy) - [Joomla! CMS](https://n3t.bitbucket.io/extension/n3t-debug/) +- [Yii3 framework](https://github.com/beastbytes/yii-tracy) ... feel free to be famous, create an integration for your favourite platform! diff --git a/src/Bridges/Nette/TracyExtension.php b/src/Bridges/Nette/TracyExtension.php index 1dfeb7965..f56227b85 100644 --- a/src/Bridges/Nette/TracyExtension.php +++ b/src/Bridges/Nette/TracyExtension.php @@ -129,14 +129,14 @@ public function afterCompile(Nette\PhpGenerator\ClassType $class): void } $initialize->addBody($builder->formatPhp('if ($logger instanceof Tracy\Logger) $logger->mailer = ?;', [ - [new Statement(Tracy\Bridges\Nette\MailSender::class, $params), 'send'], + [new Statement(Tracy\Bridges\Nette\MailSender::class, $params), 'send'], // TODO: nette/di must be able to create closures ])); } if ($this->debugMode) { foreach ($config->bar as $item) { if (is_string($item) && str_starts_with($item, '@')) { - $item = new Statement(['@' . $builder::THIS_CONTAINER, 'getService'], [substr($item, 1)]); + $item = new Statement(['@' . $builder::ThisContainer, 'getService'], [substr($item, 1)]); } elseif (is_string($item)) { $item = new Statement($item); } diff --git a/src/Tracy/Bar/assets/bar.css b/src/Tracy/Bar/assets/bar.css index 5f71d82b8..c31c8e09a 100644 --- a/src/Tracy/Bar/assets/bar.css +++ b/src/Tracy/Bar/assets/bar.css @@ -2,40 +2,53 @@ * This file is part of the Tracy (https://tracy.nette.org) */ -/* common styles */ -#tracy-debug { +@layer components { + +/* shadow DOM host */ +:host { --tracy-space: 10px; - display: none; direction: ltr; + display: none; } -body#tracy-debug { /* in popup window */ +body.tracy-debug { /* in popup window */ display: block; } -#tracy-debug:not(body) { - position: absolute; - left: 0; - top: 0; -} +/* popup window overrides (higher specificity to beat bluescreen bare selectors) */ +body.tracy-debug a { color: #125EAE; text-decoration: none; padding: 0; margin: 0; } +body.tracy-debug a:hover, +body.tracy-debug a:focus { background-color: #125EAE; color: white; } +body.tracy-debug h1 { font: normal normal 23px/1.4 Tahoma, sans-serif; line-height: 1; color: #575753; margin: 0; } +body.tracy-debug h2, +body.tracy-debug h3 { font-size: inherit; font-weight: bold; } +body.tracy-debug table { background: #FDF5CE; width: 100%; } +body.tracy-debug td, +body.tracy-debug th { border: 1px solid #E6DFBF; padding: 2px 5px; vertical-align: top; text-align: left; } +body.tracy-debug th { background: #F4F3F1; color: #655E5E; font-size: 90%; font-weight: bold; } +body.tracy-debug pre, +body.tracy-debug code { font: 9pt/1.5 Consolas, monospace; } +body.tracy-debug svg { display: inline; } -#tracy-debug a { +/* common styles */ +a { color: #125EAE; text-decoration: none; } -#tracy-debug a:hover, -#tracy-debug a:focus { +a:hover, +a:focus { background-color: #125EAE; color: white; } -#tracy-debug h2, -#tracy-debug h3 { +h2, +h3 { + font-size: inherit; font-weight: bold; } -#tracy-debug :where(:is( +:where(:is( h1, h2, h3, h4, h5, h6, p, ol, ul, dl, @@ -47,44 +60,44 @@ body#tracy-debug { /* in popup window */ margin-top: var(--tracy-space); } -#tracy-debug table { +table { background: #FDF5CE; width: 100%; } -#tracy-debug tr:nth-child(2n) td { +tr:nth-child(2n) td { background: rgba(0, 0, 0, 0.02); } -#tracy-debug td, -#tracy-debug th { +td, +th { border: 1px solid #E6DFBF; padding: 2px 5px; vertical-align: top; text-align: left; } -#tracy-debug th { +th { background: #F4F3F1; color: #655E5E; font-size: 90%; font-weight: bold; } -#tracy-debug pre, -#tracy-debug code { +pre, +code { font: 9pt/1.5 Consolas, monospace; } -#tracy-debug table .tracy-right { +table .tracy-right { text-align: right; } -#tracy-debug svg { +svg { display: inline; } -#tracy-debug .tracy-dump { +.tracy-dump { margin: 0; padding: 2px 5px; } @@ -182,7 +195,7 @@ body#tracy-debug { /* in popup window */ /* panels */ -#tracy-debug .tracy-panel { +.tracy-panel { display: none; font: normal normal 12px/1.5 sans-serif; background: white; @@ -190,11 +203,11 @@ body#tracy-debug { /* in popup window */ text-align: left; } -body#tracy-debug .tracy-panel { /* in popup window */ +body.tracy-debug .tracy-panel { /* in popup window */ display: block; } -#tracy-debug h1 { +h1 { font: normal normal 23px/1.4 Tahoma, sans-serif; line-height: 1; color: #575753; @@ -202,29 +215,29 @@ body#tracy-debug .tracy-panel { /* in popup window */ word-wrap: break-word; } -#tracy-debug .tracy-inner { +.tracy-inner { overflow: auto; flex: 1; } -#tracy-debug .tracy-panel .tracy-icons { +.tracy-panel .tracy-icons { display: none; } -#tracy-debug .tracy-panel-ajax h1::after, -#tracy-debug .tracy-panel-redirect h1::after { +.tracy-panel-ajax h1::after, +.tracy-panel-redirect h1::after { content: 'ajax'; float: right; font-size: 65%; margin: 0 .3em; } -#tracy-debug .tracy-panel-redirect h1::after { +.tracy-panel-redirect h1::after { content: 'redirect'; } -#tracy-debug .tracy-mode-peek, -#tracy-debug .tracy-mode-float { +.tracy-mode-peek, +.tracy-mode-float { position: fixed; flex-direction: column; padding: var(--tracy-space); @@ -235,24 +248,24 @@ body#tracy-debug .tracy-panel { /* in popup window */ border: 1px solid rgba(0, 0, 0, 0.1); } -#tracy-debug .tracy-mode-peek, -#tracy-debug .tracy-mode-float:not(.tracy-panel-resized) { +.tracy-mode-peek, +.tracy-mode-float:not(.tracy-panel-resized) { max-width: 700px; max-height: 500px; } @media (max-height: 555px) { - #tracy-debug .tracy-mode-peek, - #tracy-debug .tracy-mode-float:not(.tracy-panel-resized) { + .tracy-mode-peek, + .tracy-mode-float:not(.tracy-panel-resized) { max-height: 100vh; } } -#tracy-debug .tracy-mode-peek h1 { +.tracy-mode-peek h1 { cursor: move; } -#tracy-debug .tracy-mode-float { +.tracy-mode-float { display: flex; opacity: .95; transition: opacity 0.2s; @@ -261,18 +274,18 @@ body#tracy-debug .tracy-panel { /* in popup window */ resize: both; } -#tracy-debug .tracy-focused { +.tracy-focused { display: flex; opacity: 1; transition: opacity 0.1s; } -#tracy-debug .tracy-mode-float h1 { +.tracy-mode-float h1 { cursor: move; padding-right: 25px; } -#tracy-debug .tracy-mode-float .tracy-icons { +.tracy-mode-float .tracy-icons { display: block; position: absolute; top: 0; @@ -280,25 +293,27 @@ body#tracy-debug .tracy-panel { /* in popup window */ font-size: 18px; } -#tracy-debug .tracy-mode-window { +.tracy-mode-window { padding: var(--tracy-space); } -#tracy-debug .tracy-icons a { +.tracy-icons a { color: #575753; } -#tracy-debug .tracy-icons a:hover { +.tracy-icons a:hover { color: white; } -#tracy-debug .tracy-inner-container { +.tracy-inner-container { min-width: fit-content; } @media print { - #tracy-debug * { + * { display: none; } } + +} diff --git a/src/Tracy/Bar/assets/bar.js b/src/Tracy/Bar/assets/bar.js index f90637e81..98ee65579 100644 --- a/src/Tracy/Bar/assets/bar.js +++ b/src/Tracy/Bar/assets/bar.js @@ -26,7 +26,7 @@ function getOption(key) { class Panel { constructor(id) { this.id = id; - this.elem = document.getElementById(this.id); + this.elem = Debug.shadow.querySelector('#' + CSS.escape(this.id)); this.elem.Tracy = this.elem.Tracy || {}; } @@ -37,7 +37,7 @@ class Panel { this.init = function () {}; elem.innerHTML = elem.tracyContent = elem.dataset.tracyContent; delete elem.dataset.tracyContent; - Tracy.Dumper.init(Debug.layer); + Tracy.Dumper.init(Debug.shadow); evalScripts(elem); draggable(elem, { @@ -154,7 +154,7 @@ class Panel { } let doc = win.document; - doc.write('
'); + doc.write(''); let script = doc.createElement('script'); script.src = baseUrl + '_tracy_bar=js&XDEBUG_SESSION_STOP=1'; @@ -163,10 +163,10 @@ class Panel { doc.head.appendChild(script); let meta = this.elem.parentElement.lastElementChild; - doc.body.innerHTML = '';
echo Tracy\Helpers::escapeHtml($message) /* pos 10:14 */;
diff --git a/src/Tracy/Bar/panels/info.panel.latte b/src/Tracy/Bar/panels/info.panel.latte
index d2335706e..747857ae1 100644
--- a/src/Tracy/Bar/panels/info.panel.latte
+++ b/src/Tracy/Bar/panels/info.panel.latte
@@ -34,14 +34,14 @@
{if $packages || $devPackages}
- Composer Packages ({count($packages)}{$devPackages ? ' + ' . count($devPackages) . ' dev' : ''})
+ Composer Packages ({count($packages)}{$devPackages ? ' + ' . count($devPackages) . ' dev'})
{$package->name}
- {$package->version}{strpos($package->version, dev) !== false && $package->hash ? ' #' . substr($package->hash, 0, 4) : ''}
+ {$package->version}{strpos($package->version, dev) !== false && $package->hash ? ' #' . substr($package->hash, 0, 4)}
@@ -51,7 +51,7 @@
{$package->name}
- {$package->version}{strpos($package->version, dev) !== false && $package->hash ? ' #' . substr($package->hash, 0, 4) : ''}
+ {$package->version}{strpos($package->version, dev) !== false && $package->hash ? ' #' . substr($package->hash, 0, 4)}
{/if}
diff --git a/src/Tracy/Bar/panels/warnings.panel.latte b/src/Tracy/Bar/panels/warnings.panel.latte
index e639c71c0..0d50b83e3 100644
--- a/src/Tracy/Bar/panels/warnings.panel.latte
+++ b/src/Tracy/Bar/panels/warnings.panel.latte
@@ -6,7 +6,7 @@
{foreach $data as $item => $count}
{do [$file, $line, $message] = explode('|', $item, 3)}
- {$count ? $count . '×' : ''}
+ {$count ? $count . '×'}
{$message} in {Tracy\Helpers::editorLink($file, (int) $line)}
{/foreach}
diff --git a/src/Tracy/BlueScreen/BlueScreen.php b/src/Tracy/BlueScreen/BlueScreen.php
index 1ff43695c..f4c0fc226 100644
--- a/src/Tracy/BlueScreen/BlueScreen.php
+++ b/src/Tracy/BlueScreen/BlueScreen.php
@@ -370,7 +370,7 @@ public static function highlightFile(
? CodeHighlighter::highlightPhp($source, $line, $column)
: '' . CodeHighlighter::highlightLine(htmlspecialchars($source, ENT_IGNORE, 'UTF-8'), $line, $column) . '
';
- if ($editor = Helpers::editorUri($file, $line)) {
+ if ($editor = Helpers::editorUri($file, line: $line, column: $column)) {
$source = substr_replace($source, ' title="Ctrl-Click to open in editor" data-tracy-href="' . Helpers::escapeHtml($editor) . '"', 4, 0);
}
@@ -460,6 +460,8 @@ public function getAgentDumper(): \Closure
{
return fn($v, $k = null): string => Dumper::toText($v, [
Dumper::DEPTH => 3,
+ Dumper::TRUNCATE => $this->maxLength,
+ Dumper::ITEMS => $this->maxItems,
Dumper::SCRUBBER => $this->scrubber,
Dumper::KEYS_TO_HIDE => $this->keysToHide,
], $k);
diff --git a/src/Tracy/BlueScreen/assets/agent.latte b/src/Tracy/BlueScreen/assets/agent.latte
index 3e2a32bac..214b93919 100644
--- a/src/Tracy/BlueScreen/assets/agent.latte
+++ b/src/Tracy/BlueScreen/assets/agent.latte
@@ -45,7 +45,7 @@ This is an error page generated by Tracy (https://tracy.nette.org).
{foreach Helpers::getExceptionChain($exception) as $i => $ex}
{do $title = $blueScreen->getExceptionTitle($ex)}
- {do $code = $ex->getCode() ? ' #' . $ex->getCode() : ''}
+ {do $code = $ex->getCode() ? ' #' . $ex->getCode()}
{if $i === 0}
# {$title}: {$ex->getMessage()}{$code}
{else}
diff --git a/src/Tracy/BlueScreen/assets/bluescreen.css b/src/Tracy/BlueScreen/assets/bluescreen.css
index dd80a54a2..09a7db2fe 100644
--- a/src/Tracy/BlueScreen/assets/bluescreen.css
+++ b/src/Tracy/BlueScreen/assets/bluescreen.css
@@ -2,6 +2,13 @@
* This file is part of the Tracy (https://tracy.nette.org)
*/
+@layer components {
+
+/* shadow DOM host */
+:host {
+ display: contents;
+}
+
html.tracy-bs-visible,
html.tracy-bs-visible body {
display: block;
@@ -9,7 +16,7 @@ html.tracy-bs-visible body {
position: static;
}
-#tracy-bs {
+.tracy-bs {
font: 9pt/1.5 Verdana, sans-serif;
background: white;
color: #333;
@@ -19,404 +26,406 @@ html.tracy-bs-visible body {
top: 0;
width: 100%;
text-align: left;
-}
-#tracy-bs a {
- text-decoration: none;
- color: #328ADC;
- padding: 0 4px;
- margin: 0 -4px;
-}
+ a {
+ text-decoration: none;
+ color: #328ADC;
+ padding: 0 4px;
+ margin: 0 -4px;
+ }
-#tracy-bs a + a {
- margin-left: 0;
-}
+ a + a {
+ margin-left: 0;
+ }
-#tracy-bs a:hover,
-#tracy-bs a:focus {
- color: #085AA3;
-}
+ a:hover,
+ a:focus {
+ color: #085AA3;
+ }
-#tracy-bs-toggle {
- position: absolute;
- right: .5em;
- top: .5em;
- text-decoration: none;
- background: #CD1818;
- color: white !important;
- padding: 3px;
-}
+ .tracy-bs-toggle {
+ position: absolute;
+ right: .5em;
+ top: .5em;
+ text-decoration: none;
+ background: #CD1818;
+ color: white !important;
+ padding: 3px;
+ }
-#tracy-bs-toggle.tracy-collapsed {
- position: fixed;
-}
+ .tracy-bs-toggle.tracy-collapsed {
+ position: fixed;
+ }
-.tracy-bs-main {
- display: flex;
- flex-direction: column;
- padding-bottom: 80vh;
-}
+ .tracy-bs-main {
+ display: flex;
+ flex-direction: column;
+ padding-bottom: 80vh;
+ }
-.tracy-bs-main.tracy-collapsed {
- display: none;
-}
+ .tracy-bs-main.tracy-collapsed {
+ display: none;
+ }
-#tracy-bs :where(:is(
- h1, h2, h3, h4, h5, h6,
- p,
- ol, ul, dl,
- pre, table, hr,
- .tracy-section-panel,
- .tracy-pane
-):not(:first-child)) {
- margin-top: var(--tracy-space);
-}
+ :where(:is(
+ h1, h2, h3, h4, h5, h6,
+ p,
+ ol, ul, dl,
+ pre, table, hr,
+ .tracy-section-panel,
+ .tracy-pane
+ ):not(:first-child)) {
+ margin-top: var(--tracy-space);
+ }
-#tracy-bs h1 {
- font-size: 15pt;
- font-weight: normal;
- text-shadow: 1px 1px 2px rgba(0, 0, 0, .3);
-}
+ h1 {
+ font-size: 15pt;
+ font-weight: normal;
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, .3);
+ }
-#tracy-bs h1 span {
- white-space: pre-wrap;
-}
+ h1 span {
+ white-space: pre-wrap;
+ }
-#tracy-bs h2 {
- font-size: 14pt;
- font-weight: normal;
-}
+ h2 {
+ font-size: 14pt;
+ font-weight: normal;
+ }
-#tracy-bs h3 {
- font-size: 10pt;
- font-weight: bold;
-}
+ h3 {
+ font-size: 10pt;
+ font-weight: bold;
+ }
-#tracy-bs pre,
-#tracy-bs code,
-#tracy-bs table {
- font: 9pt/1.5 Consolas, monospace !important;
-}
+ pre,
+ code,
+ table {
+ font: 9pt/1.5 Consolas, monospace !important;
+ }
-#tracy-bs pre,
-#tracy-bs table {
- background: #FDF5CE;
- padding: .4em .7em;
- border: 2px solid #ffffffa6;
- box-shadow: 1px 2px 6px #00000005;
- overflow: auto;
-}
+ pre,
+ table {
+ background: #FDF5CE;
+ padding: .4em .7em;
+ border: 2px solid #ffffffa6;
+ box-shadow: 1px 2px 6px #00000005;
+ overflow: auto;
+ }
-#tracy-bs table pre {
- padding: 0;
- margin: 0;
- border: none;
- box-shadow: none;
-}
+ table pre {
+ padding: 0;
+ margin: 0;
+ border: none;
+ box-shadow: none;
+ }
-#tracy-bs table {
- border-collapse: collapse;
- width: 100%;
-}
+ table {
+ border-collapse: collapse;
+ width: 100%;
+ }
-#tracy-bs td,
-#tracy-bs th {
- vertical-align: top;
- text-align: left;
- padding: 2px 6px;
- border: 1px solid #e6dfbf;
-}
+ td,
+ th {
+ vertical-align: top;
+ text-align: left;
+ padding: 2px 6px;
+ border: 1px solid #e6dfbf;
+ }
-#tracy-bs th {
- font-weight: bold;
-}
+ th {
+ font-weight: bold;
+ }
-#tracy-bs tr > :first-child {
- width: 20%;
-}
+ tr > :first-child {
+ width: 20%;
+ }
-#tracy-bs tr:nth-child(2n),
-#tracy-bs tr:nth-child(2n) pre {
- background-color: #F7F0CB;
-}
+ tr:nth-child(2n),
+ tr:nth-child(2n) pre {
+ background-color: #F7F0CB;
+ }
-#tracy-bs .tracy-footer--sticky {
- position: fixed;
- width: 100%;
- bottom: 0;
-}
+ .tracy-footer--sticky {
+ position: fixed;
+ width: 100%;
+ bottom: 0;
+ }
-#tracy-bs footer ul {
- font-size: 7pt;
- padding: var(--tracy-space);
- margin: var(--tracy-space) 0 0;
- color: #777;
- background: #F6F5F3;
- border-top: 1px solid #DDD;
- list-style: none;
-}
+ footer ul {
+ font-size: 7pt;
+ padding: var(--tracy-space);
+ margin: var(--tracy-space) 0 0;
+ color: #777;
+ background: #F6F5F3;
+ border-top: 1px solid #DDD;
+ list-style: none;
+ }
-#tracy-bs .tracy-footer-logo {
- position: relative;
-}
+ .tracy-footer-logo {
+ position: relative;
+ }
-#tracy-bs .tracy-footer-logo a {
- position: absolute;
- bottom: 0;
- right: 0;
- width: 100px;
- height: 50px;
- background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAAAUBAMAAAD/1DctAAAAMFBMVEWupZzj39rEvbTy8O3X0sz9/PvGwLu8tavQysHq6OS0rKP5+Pbd2dT29fPMxbzPx8DKErMJAAAACXBIWXMAAAsTAAALEwEAmpwYAAACGUlEQVQoFX3TQWgTQRQA0MWLIJJDYehBTykhG5ERTx56K1u8eEhCYtomE7x5L4iLh0ViF7egewuFFqSIYE6hIHsIYQ6CQSg9CDKn4QsNCRlB59C74J/ZNHW1+An5+bOPyf6/s46oz2P+A0yIeZZ2ieEHi6TOnLKTxvWq+b52mxlVO3xnM1s7xLX1504XQH65OnW2dBqn7cCkYsFsfYsWpyY/2salmFTpEyzeR8zosYqMdiPDXdyU52K1wgEa/SjGpdEwUAxqvRfckQCDOyFearsEHe2grvkh/cFAHKvdtI3lcVceKQIOFpv+FOZaNPQBwJZLPp+hfrvT5JZXaUFsR8zqQc9qSgAharkfS5M/5F6nGJJAtXq/eLr3ucZpHccSxOOIPaQhtHohpCH2Xu6rLmQ0djnr4/+J3C6v+AW8/XWYxwYNdlhWj/P5fPSTQwVr0T9lGxdaBCqErNZaqYnEwbkjEB3NasGF3lPdrHa1nnxNOMgj0+neePUPjd2v/qVvUv29ifvc19huQ48qwXShy/9o8o3OSk0cs37mOFd0Ydgvsf/oZEnPVtggfd66lORn9mDyyzXU13SRtH2L6aR5T/snGAcZPfAXz5J1YlJWBEuxdMYqQecpBrlM49xAbmqyHA+xlA1FxBtqT2xmJoNXZlIt74ZBLeJ9ZGDqByNI7p543idzJ23vXEv7IgnsxiS+eNtwNbFdLq7+Bi4wQ0I4SVb9AAAAAElFTkSuQmCC') no-repeat;
- opacity: .6;
- padding: 0;
- margin: 0;
-}
+ .tracy-footer-logo a {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ width: 100px;
+ height: 50px;
+ background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAAAUBAMAAAD/1DctAAAAMFBMVEWupZzj39rEvbTy8O3X0sz9/PvGwLu8tavQysHq6OS0rKP5+Pbd2dT29fPMxbzPx8DKErMJAAAACXBIWXMAAAsTAAALEwEAmpwYAAACGUlEQVQoFX3TQWgTQRQA0MWLIJJDYehBTykhG5ERTx56K1u8eEhCYtomE7x5L4iLh0ViF7egewuFFqSIYE6hIHsIYQ6CQSg9CDKn4QsNCRlB59C74J/ZNHW1+An5+bOPyf6/s46oz2P+A0yIeZZ2ieEHi6TOnLKTxvWq+b52mxlVO3xnM1s7xLX1504XQH65OnW2dBqn7cCkYsFsfYsWpyY/2salmFTpEyzeR8zosYqMdiPDXdyU52K1wgEa/SjGpdEwUAxqvRfckQCDOyFearsEHe2grvkh/cFAHKvdtI3lcVceKQIOFpv+FOZaNPQBwJZLPp+hfrvT5JZXaUFsR8zqQc9qSgAharkfS5M/5F6nGJJAtXq/eLr3ucZpHccSxOOIPaQhtHohpCH2Xu6rLmQ0djnr4/+J3C6v+AW8/XWYxwYNdlhWj/P5fPSTQwVr0T9lGxdaBCqErNZaqYnEwbkjEB3NasGF3lPdrHa1nnxNOMgj0+neePUPjd2v/qVvUv29ifvc19huQ48qwXShy/9o8o3OSk0cs37mOFd0Ydgvsf/oZEnPVtggfd66lORn9mDyyzXU13SRtH2L6aR5T/snGAcZPfAXz5J1YlJWBEuxdMYqQecpBrlM49xAbmqyHA+xlA1FxBtqT2xmJoNXZlIt74ZBLeJ9ZGDqByNI7p543idzJ23vXEv7IgnsxiS+eNtwNbFdLq7+Bi4wQ0I4SVb9AAAAAElFTkSuQmCC') no-repeat;
+ opacity: .6;
+ padding: 0;
+ margin: 0;
+ }
-#tracy-bs .tracy-footer-logo a:hover,
-#tracy-bs .tracy-footer-logo a:focus {
- opacity: 1;
- transition: opacity 0.1s;
-}
+ .tracy-footer-logo a:hover,
+ .tracy-footer-logo a:focus {
+ opacity: 1;
+ transition: opacity 0.1s;
+ }
-#tracy-bs .tracy-section {
- padding: var(--tracy-space);
-}
+ .tracy-section {
+ padding: var(--tracy-space);
+ }
-#tracy-bs .tracy-section-panel {
- background: #5040200E;
- padding: var(--tracy-space);
- border-radius: 8px;
- box-shadow: inset 1px 1px 0px 0 #00000005;
- overflow: hidden;
-}
+ .tracy-section-panel {
+ background: #5040200E;
+ padding: var(--tracy-space);
+ border-radius: 8px;
+ box-shadow: inset 1px 1px 0px 0 #00000005;
+ overflow: hidden;
+ }
-#tracy-bs .outer, /* deprecated */
-#tracy-bs .tracy-pane {
- overflow: auto;
-}
+ .outer, /* deprecated */
+ .tracy-pane {
+ overflow: auto;
+ }
-#tracy-bs.tracy-mac .tracy-pane {
- padding-bottom: 12px;
-}
+ &.tracy-mac .tracy-pane {
+ padding-bottom: 12px;
+ }
-/* header */
-#tracy-bs .tracy-section--error {
- background: #CD1818;
- color: white;
-}
+ /* header */
+ .tracy-section--error {
+ background: #CD1818;
+ color: white;
+ }
-#tracy-bs .tracy-section--error p,
-#tracy-bs .tracy-section--error h1 {
- font-size: 13pt;
- color: white;
-}
+ .tracy-section--error p,
+ .tracy-section--error h1 {
+ font-size: 13pt;
+ color: white;
+ }
-#tracy-bs .tracy-section--error::selection,
-#tracy-bs .tracy-section--error ::selection {
- color: black !important;
- background: #FDF5CE !important;
-}
+ .tracy-section--error::selection,
+ .tracy-section--error ::selection {
+ color: black !important;
+ background: #FDF5CE !important;
+ }
-#tracy-bs .tracy-section--error h1 a {
- color: #ffefa1 !important;
-}
+ .tracy-section--error h1 a {
+ color: #ffefa1 !important;
+ }
-#tracy-bs .tracy-section--error span span {
- font-size: 80%;
- color: rgba(255, 255, 255, 0.5);
- text-shadow: none;
-}
+ .tracy-section--error span span {
+ font-size: 80%;
+ color: rgba(255, 255, 255, 0.5);
+ text-shadow: none;
+ }
-#tracy-bs .tracy-section--error a.tracy-action {
- color: white !important;
- opacity: 0;
- font-size: .7em;
- border-bottom: none !important;
-}
+ .tracy-section--error a.tracy-action {
+ color: white !important;
+ opacity: 0;
+ font-size: .7em;
+ border-bottom: none !important;
+ }
-#tracy-bs .tracy-section--error:hover a.tracy-action {
- opacity: .6;
-}
+ .tracy-section--error:hover a.tracy-action {
+ opacity: .6;
+ }
-#tracy-bs .tracy-section--error a.tracy-action:hover {
- opacity: 1;
-}
+ .tracy-section--error a.tracy-action:hover {
+ opacity: 1;
+ }
-#tracy-bs .tracy-section--error i {
- color: #ffefa1;
- font-style: normal;
-}
+ .tracy-section--error i {
+ color: #ffefa1;
+ font-style: normal;
+ }
-#tracy-bs .tracy-section--error:has(.tracy-caused) {
- border-radius: 0 0 0 8px;
- overflow: hidden;
-}
+ .tracy-section--error:has(.tracy-caused) {
+ border-radius: 0 0 0 8px;
+ overflow: hidden;
+ }
-#tracy-bs .tracy-caused {
- margin: var(--tracy-space) calc(-1 * var(--tracy-space)) calc(-1 * var(--tracy-space));
- padding: .3em var(--tracy-space);
- background: #df8075;
- white-space: nowrap;
-}
+ .tracy-caused {
+ margin: var(--tracy-space) calc(-1 * var(--tracy-space)) calc(-1 * var(--tracy-space));
+ padding: .3em var(--tracy-space);
+ background: #df8075;
+ white-space: nowrap;
+ }
-#tracy-bs .tracy-caused a {
- color: white;
-}
+ .tracy-caused a {
+ color: white;
+ }
-/* source code */
-#tracy-bs pre.tracy-code > div {
- min-width: fit-content;
- white-space: pre;
-}
+ /* source code */
+ pre.tracy-code > div {
+ min-width: fit-content;
+ white-space: pre;
+ }
-#tracy-bs .tracy-code-comment {
- color: rgba(0, 0, 0, 0.5);
- font-style: italic;
-}
+ .tracy-code-comment {
+ color: rgba(0, 0, 0, 0.5);
+ font-style: italic;
+ }
-#tracy-bs .tracy-code-keyword {
- color: #D24;
- font-weight: bold;
-}
+ .tracy-code-keyword {
+ color: #D24;
+ font-weight: bold;
+ }
-#tracy-bs .tracy-code-var {
- font-weight: bold;
-}
+ .tracy-code-var {
+ font-weight: bold;
+ }
-#tracy-bs .tracy-line-highlight {
- background: #CD1818;
- color: white;
- font-weight: bold;
- font-style: normal;
- display: block;
- padding: 0 1ch;
- margin: 0 -1ch -1lh;
-}
+ .tracy-line-highlight {
+ background: #CD1818;
+ color: white;
+ font-weight: bold;
+ font-style: normal;
+ display: block;
+ padding: 0 1ch;
+ margin: 0 -1ch -1lh;
+ }
-#tracy-bs .tracy-column-highlight {
- display: inline-block;
- backdrop-filter: grayscale(1);
- margin: 0 -1px;
- padding: 0 1px;
-}
+ .tracy-column-highlight {
+ display: inline-block;
+ backdrop-filter: grayscale(1);
+ margin: 0 -1px;
+ padding: 0 1px;
+ }
-#tracy-bs .tracy-line {
- color: #9F9C7F;
- font-weight: normal;
- font-style: normal;
-}
+ .tracy-line {
+ color: #9F9C7F;
+ font-weight: normal;
+ font-style: normal;
+ }
-#tracy-bs a.tracy-editor {
- color: inherit;
- border-bottom: 1px dotted rgba(0, 0, 0, .3);
- border-radius: 3px;
-}
+ a.tracy-editor {
+ color: inherit;
+ border-bottom: 1px dotted rgba(0, 0, 0, .3);
+ border-radius: 3px;
+ }
-#tracy-bs a.tracy-editor:hover {
- background: #0001;
-}
+ a.tracy-editor:hover {
+ background: #0001;
+ }
-#tracy-bs span[data-tracy-href] {
- border-bottom: 1px dotted rgba(0, 0, 0, .3);
-}
+ span[data-tracy-href] {
+ border-bottom: 1px dotted rgba(0, 0, 0, .3);
+ }
-#tracy-bs .tracy-dump-whitespace {
- color: #0003;
-}
+ .tracy-dump-whitespace {
+ color: #0003;
+ }
-#tracy-bs .tracy-callstack {
- display: grid;
- overflow: auto;
- grid-template-columns: max-content 1fr;
- row-gap: calc(.5 * var(--tracy-space));
-}
+ .tracy-callstack {
+ display: grid;
+ overflow: auto;
+ grid-template-columns: max-content 1fr;
+ row-gap: calc(.5 * var(--tracy-space));
+ }
-#tracy-bs .tracy-callstack-file {
- text-align: right;
- padding-right: var(--tracy-space);
- white-space: nowrap;
-}
+ .tracy-callstack-file {
+ text-align: right;
+ padding-right: var(--tracy-space);
+ white-space: nowrap;
+ }
-#tracy-bs .tracy-callstack-callee {
- white-space: nowrap;
-}
+ .tracy-callstack-callee {
+ white-space: nowrap;
+ }
-#tracy-bs .tracy-callstack-additional {
- grid-column-start: 1;
- grid-column-end: 3;
-}
+ .tracy-callstack-additional {
+ grid-column-start: 1;
+ grid-column-end: 3;
+ }
-#tracy-bs .tracy-callstack-args tr:first-child > * {
- position: relative;
-}
+ .tracy-callstack-args tr:first-child > * {
+ position: relative;
+ }
-#tracy-bs .tracy-callstack-args tr:first-child td:before {
- position: absolute;
- right: .3em;
- content: 'may not be true';
- opacity: .4;
-}
+ .tracy-callstack-args tr:first-child td:before {
+ position: absolute;
+ right: .3em;
+ content: 'may not be true';
+ opacity: .4;
+ }
-#tracy-bs .tracy-panel-fadein {
- animation: tracy-panel-fadein .12s ease;
-}
+ .tracy-panel-fadein {
+ animation: tracy-panel-fadein .12s ease;
+ }
-@keyframes tracy-panel-fadein {
- 0% {
- opacity: 0;
+ @keyframes tracy-panel-fadein {
+ 0% {
+ opacity: 0;
+ }
}
-}
-#tracy-bs .tracy-section--causedby {
- flex-direction: column;
- padding: 0;
-}
+ .tracy-section--causedby {
+ flex-direction: column;
+ padding: 0;
+ }
-#tracy-bs .tracy-section--causedby:not(.tracy-collapsed) {
- display: flex;
-}
+ .tracy-section--causedby:not(.tracy-collapsed) {
+ display: flex;
+ }
-#tracy-bs .tracy-section--causedby .tracy-section--error {
- background: #cd1818a6;
-}
+ .tracy-section--causedby .tracy-section--error {
+ background: #cd1818a6;
+ }
-#tracy-bs .tracy-section--error + .tracy-section--stack {
- margin-top: calc(1.5 * var(--tracy-space));
-}
+ .tracy-section--error + .tracy-section--stack {
+ margin-top: calc(1.5 * var(--tracy-space));
+ }
-/* tabs */
-#tracy-bs .tracy-tab-bar {
- display: flex;
- list-style: none;
- padding-left: 0;
- margin: 0;
- width: 100%;
- font-size: 110%;
- column-gap: var(--tracy-space);
-}
+ /* tabs */
+ .tracy-tab-bar {
+ display: flex;
+ list-style: none;
+ padding-left: 0;
+ margin: 0;
+ width: 100%;
+ font-size: 110%;
+ column-gap: var(--tracy-space);
+ }
-#tracy-bs .tracy-tab-bar a {
- display: block;
- padding: calc(.5 * var(--tracy-space)) var(--tracy-space);
- margin: 0;
- height: 100%;
- box-sizing: border-box;
- border-radius: 5px 5px 0 0;
- text-decoration: none;
- transition: all 0.1s;
-}
+ .tracy-tab-bar a {
+ display: block;
+ padding: calc(.5 * var(--tracy-space)) var(--tracy-space);
+ margin: 0;
+ height: 100%;
+ box-sizing: border-box;
+ border-radius: 5px 5px 0 0;
+ text-decoration: none;
+ transition: all 0.1s;
+ }
-#tracy-bs .tracy-tab-bar > .tracy-active a {
- background: white;
+ .tracy-tab-bar > .tracy-active a {
+ background: white;
+ }
+
+ .tracy-tab-panel {
+ border-top: 2px solid white;
+ padding-top: var(--tracy-space);
+ overflow: auto;
+ }
}
-#tracy-bs .tracy-tab-panel {
- border-top: 2px solid white;
- padding-top: var(--tracy-space);
- overflow: auto;
}
diff --git a/src/Tracy/BlueScreen/assets/bluescreen.js b/src/Tracy/BlueScreen/assets/bluescreen.js
index c8983b207..9af0ea746 100644
--- a/src/Tracy/BlueScreen/assets/bluescreen.js
+++ b/src/Tracy/BlueScreen/assets/bluescreen.js
@@ -6,7 +6,21 @@ class BlueScreen {
static init(ajax) {
BlueScreen.globalInit();
- let blueScreen = document.getElementById('tracy-bs');
+ let blueScreen = document.querySelector('.tracy-bs');
+
+ // Shadow DOM for CSS isolation
+ let host = document.createElement('tracy-bs');
+ let shadow = host.attachShadow({ mode: 'open' });
+ BlueScreen.shadow = shadow;
+ BlueScreen.host = host;
+
+ // Clone CSS into shadow root (exclude Bar CSS)
+ document.querySelectorAll('style.tracy-debug:not(.tracy-bar-css)').forEach((s) => {
+ shadow.appendChild(s.cloneNode(true));
+ });
+
+ shadow.appendChild(blueScreen);
+ document.body.appendChild(host);
document.documentElement.classList.add('tracy-bs-visible');
if (navigator.platform.indexOf('Mac') > -1) {
@@ -14,22 +28,23 @@ class BlueScreen {
}
blueScreen.addEventListener('tracy-toggle', (e) => {
- if (e.target.matches('#tracy-bs-toggle')) { // blue screen toggle
+ let target = e.composedPath()[0] || e.target;
+ if (target.matches('.tracy-bs-toggle')) { // blue screen toggle
document.documentElement.classList.toggle('tracy-bs-visible', !e.detail.collapsed);
- } else if (!e.target.matches('.tracy-dump *') && e.detail.originalEvent) { // panel toggle
+ } else if (!target.matches('.tracy-dump *') && e.detail.originalEvent) { // panel toggle
e.detail.relatedTarget.classList.toggle('tracy-panel-fadein', !e.detail.collapsed);
}
});
if (!ajax) {
- document.body.appendChild(blueScreen);
- let id = location.href + document.querySelector('.tracy-section--error').textContent;
+ let id = location.href + shadow.querySelector('.tracy-section--error').textContent;
Tracy.Toggle.persist(blueScreen, sessionStorage.getItem('tracy-toggles-bskey') === id);
sessionStorage.setItem('tracy-toggles-bskey', id);
}
- (new ResizeObserver(stickyFooter)).observe(blueScreen);
+ Tracy.Dumper.init(shadow);
+ (new ResizeObserver(() => stickyFooter(shadow))).observe(blueScreen);
if (document.documentElement.classList.contains('tracy-bs-visible')) {
blueScreen.scrollIntoView();
@@ -41,33 +56,37 @@ class BlueScreen {
// enables toggling via ESC
document.addEventListener('keyup', (e) => {
if (e.keyCode === 27 && !e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey) { // ESC
- Tracy.Toggle.toggle(document.getElementById('tracy-bs-toggle'));
+ let toggle = BlueScreen.shadow && BlueScreen.shadow.querySelector('.tracy-bs-toggle');
+ if (toggle) {
+ Tracy.Toggle.toggle(toggle);
+ }
}
});
Tracy.TableSort.init();
Tracy.Tabs.init();
- window.addEventListener('scroll', stickyFooter);
+ window.addEventListener('scroll', () => stickyFooter(BlueScreen.shadow));
BlueScreen.globalInit = function () {};
}
static loadAjax(content) {
- let ajaxBs = document.getElementById('tracy-bs');
- if (ajaxBs) {
- ajaxBs.remove();
+ let host = document.querySelector('tracy-bs');
+ if (host) {
+ host.remove();
}
document.body.insertAdjacentHTML('beforeend', content);
- ajaxBs = document.getElementById('tracy-bs');
- Tracy.Dumper.init(ajaxBs);
BlueScreen.init(true);
}
}
-function stickyFooter() {
- let footer = document.querySelector('#tracy-bs footer');
+function stickyFooter(root) {
+ let footer = root && root.querySelector('footer');
+ if (!footer) {
+ return;
+ }
footer.classList.toggle('tracy-footer--sticky', false); // to measure footer.offsetTop
footer.classList.toggle('tracy-footer--sticky', footer.offsetHeight + footer.offsetTop - window.innerHeight - document.documentElement.scrollTop < 0);
}
diff --git a/src/Tracy/BlueScreen/assets/content.latte b/src/Tracy/BlueScreen/assets/content.latte
index 6b37a21b7..b617acb96 100644
--- a/src/Tracy/BlueScreen/assets/content.latte
+++ b/src/Tracy/BlueScreen/assets/content.latte
@@ -19,8 +19,8 @@
{varType Fiber[] $fibers}
{* *}
-
-
+
+
{do $ex = $exception}
@@ -71,4 +71,4 @@
-
+
diff --git a/src/Tracy/BlueScreen/assets/page.latte b/src/Tracy/BlueScreen/assets/page.latte
index 0458a3597..7f80014b2 100644
--- a/src/Tracy/BlueScreen/assets/page.latte
+++ b/src/Tracy/BlueScreen/assets/page.latte
@@ -6,7 +6,7 @@
{varType string $js}
{varType string $source}
{do $title = $blueScreen->getExceptionTitle($exception)}
-{do $code = $exception->getCode() ? ' #' . $exception->getCode() : ''}
+{do $code = $exception->getCode() ? ' #' . $exception->getCode()}
{do $chain = Helpers::getExceptionChain($exception)}
{* *}
@@ -23,7 +23,7 @@
{if count($chain) > 1}
{/if}
diff --git a/src/Tracy/BlueScreen/assets/section-header.latte b/src/Tracy/BlueScreen/assets/section-header.latte
index b46182da4..98938cc96 100644
--- a/src/Tracy/BlueScreen/assets/section-header.latte
+++ b/src/Tracy/BlueScreen/assets/section-header.latte
@@ -5,7 +5,7 @@
{varType Tracy\BlueScreen $blueScreen}
{do $title = $blueScreen->getExceptionTitle($ex)}
-{do $code = $ex->getCode() ? ' #' . $ex->getCode() : ''}
+{do $code = $ex->getCode() ? ' #' . $ex->getCode()}
{$title}{$code}
diff --git a/src/Tracy/BlueScreen/dist/agent.phtml b/src/Tracy/BlueScreen/dist/agent.phtml
index fdaf58fb1..2a50d0b66 100644
--- a/src/Tracy/BlueScreen/dist/agent.phtml
+++ b/src/Tracy/BlueScreen/dist/agent.phtml
@@ -63,7 +63,7 @@ Mapped source: ';
echo "\n";
foreach (Helpers::getExceptionChain($exception) as $i => $ex) /* pos 46:1 */ {
$title = $blueScreen->getExceptionTitle($ex) /* pos 47:2 */;
- $code = $ex->getCode() ? ' #' . $ex->getCode() : '' /* pos 48:2 */;
+ $code = $ex->getCode() ? ' #' . $ex->getCode() : null /* pos 48:2 */;
if ($i === 0) /* pos 49:2 */ {
echo '# ';
echo Tracy\Helpers::escapeMd($title) /* pos 50:5 */;
@@ -119,7 +119,7 @@ in ';
$argParts = [] /* pos 84:5 */;
foreach ($row['args'] as $k => $v) /* pos 85:5 */ {
$name = isset($params[$k]) && !$params[$k]->isVariadic() ? '$' . $params[$k]->getName() : '#' . $k /* pos 86:6 */;
- $dump = rtrim($agentDump($v)) /* pos 87:6 */;
+ $dump = rtrim($agentDump($v, $name)) /* pos 87:6 */;
if (str_contains($dump, '
') || strlen($dump) > 50) /* pos 88:6 */ {
$allSimple = false /* pos 89:7 */;
diff --git a/src/Tracy/BlueScreen/dist/content.phtml b/src/Tracy/BlueScreen/dist/content.phtml
index 7d04ce72f..75f2fca22 100644
--- a/src/Tracy/BlueScreen/dist/content.phtml
+++ b/src/Tracy/BlueScreen/dist/content.phtml
@@ -19,8 +19,8 @@ use Tracy\Helpers;
/** @var mixed[][] $obStatus */
/** @var Generator[] $generators */
/** @var Fiber[] $fibers */
-echo '
-
+echo '
+
';
@@ -109,5 +109,5 @@ echo '
-
+
';
diff --git a/src/Tracy/BlueScreen/dist/page.phtml b/src/Tracy/BlueScreen/dist/page.phtml
index 587232dab..b6e2bb73b 100644
--- a/src/Tracy/BlueScreen/dist/page.phtml
+++ b/src/Tracy/BlueScreen/dist/page.phtml
@@ -8,7 +8,7 @@ use Tracy\Helpers;
/** @var string $js */
/** @var string $source */
$title = $blueScreen->getExceptionTitle($exception) /* pos 8:1 */;
-$code = $exception->getCode() ? ' #' . $exception->getCode() : '' /* pos 9:1 */;
+$code = $exception->getCode() ? ' #' . $exception->getCode() : null /* pos 9:1 */;
$chain = Helpers::getExceptionChain($exception) /* pos 10:1 */;
echo '
@@ -39,7 +39,7 @@ if (count($chain) > 1) /* pos 24:2 */ {
echo Tracy\Helpers::escapeHtml(get_debug_type($ex)) /* pos 26:12 */;
echo ': ';
echo Tracy\Helpers::escapeHtml($ex->getMessage()) /* pos 26:35 */;
- echo Tracy\Helpers::escapeHtml($ex->getCode() ? ' #' . $ex->getCode() : '') /* pos 26:54 */;
+ echo Tracy\Helpers::escapeHtml($ex->getCode() ? ' #' . $ex->getCode() : null) /* pos 26:54 */;
echo '
';
diff --git a/src/Tracy/BlueScreen/dist/section-header.phtml b/src/Tracy/BlueScreen/dist/section-header.phtml
index f6d912bc0..4cdd26a20 100644
--- a/src/Tracy/BlueScreen/dist/section-header.phtml
+++ b/src/Tracy/BlueScreen/dist/section-header.phtml
@@ -7,7 +7,7 @@ use Tracy\Helpers;
/** @var Tracy\BlueScreen $blueScreen */
echo "\n";
$title = $blueScreen->getExceptionTitle($ex) /* pos 7:1 */;
-$code = $ex->getCode() ? ' #' . $ex->getCode() : '' /* pos 8:1 */;
+$code = $ex->getCode() ? ' #' . $ex->getCode() : null /* pos 8:1 */;
echo '
';
diff --git a/src/Tracy/Debugger/Debugger.php b/src/Tracy/Debugger/Debugger.php
index 2b581311c..511f9d90c 100644
--- a/src/Tracy/Debugger/Debugger.php
+++ b/src/Tracy/Debugger/Debugger.php
@@ -17,7 +17,7 @@
*/
class Debugger
{
- public const Version = '2.12.0';
+ public const Version = '3.0-dev';
/** server modes for Debugger::enable() */
public const
@@ -27,19 +27,19 @@ class Debugger
public const CookieSecret = 'tracy-debug';
- /** @deprecated use Debugger::Version */
+ #[\Deprecated('use Debugger::Version')]
public const VERSION = self::Version;
- /** @deprecated use Debugger::Development */
+ #[\Deprecated('use Debugger::Development')]
public const DEVELOPMENT = self::Development;
- /** @deprecated use Debugger::Production */
+ #[\Deprecated('use Debugger::Production')]
public const PRODUCTION = self::Production;
- /** @deprecated use Debugger::Detect */
+ #[\Deprecated('use Debugger::Detect')]
public const DETECT = self::Detect;
- /** @deprecated use Debugger::CookieSecret */
+ #[\Deprecated('use Debugger::CookieSecret')]
public const COOKIE_SECRET = self::CookieSecret;
/** in production mode is suppressed any debugging output */
@@ -93,7 +93,7 @@ class Debugger
/** theme for dump() */
public static string $dumpTheme = 'light';
- /** @deprecated */
+ #[\Deprecated]
public static $maxLen;
/********************* logging ****************d*g**/
@@ -495,7 +495,7 @@ public static function dump(mixed $var, bool $return = false): mixed
} elseif (!self::$productionMode) {
$html = Helpers::isHtmlMode();
- echo $html ? '' : '';
+ echo $html ? '' : '';
Dumper::dump($var, [
Dumper::DEPTH => self::$maxDepth,
Dumper::TRUNCATE => self::$maxLength,
@@ -504,7 +504,7 @@ public static function dump(mixed $var, bool $return = false): mixed
Dumper::THEME => self::$dumpTheme,
Dumper::KEYS_TO_HIDE => self::$keysToHide,
]);
- echo $html ? ' ' : '';
+ echo $html ? '' : '';
if ($html && Helpers::isAgent()) {
Helpers::consoleLog(Dumper::toText($var, [
diff --git a/src/Tracy/Debugger/DeferredContent.php b/src/Tracy/Debugger/DeferredContent.php
index 2dfe9f688..9eef1d4dc 100644
--- a/src/Tracy/Debugger/DeferredContent.php
+++ b/src/Tracy/Debugger/DeferredContent.php
@@ -119,16 +119,16 @@ public function sendAssets(): bool
private function buildJsCss(): string
{
- $css = array_map(file_get_contents(...), array_merge([
+ $sharedCss = array_map(file_get_contents(...), array_merge([
__DIR__ . '/../assets/reset.css',
- __DIR__ . '/../Bar/assets/bar.css',
__DIR__ . '/../assets/toggle.css',
__DIR__ . '/../assets/table-sort.css',
__DIR__ . '/../assets/tabs.css',
__DIR__ . '/../Dumper/assets/dumper-light.css',
__DIR__ . '/../Dumper/assets/dumper-dark.css',
- __DIR__ . '/../BlueScreen/assets/bluescreen.css',
], Debugger::$customCssFiles));
+ $barCss = file_get_contents(__DIR__ . '/../Bar/assets/bar.css') ?: throw new \RuntimeException('Cannot read bar.css');
+ $bsCss = file_get_contents(__DIR__ . '/../BlueScreen/assets/bluescreen.css') ?: throw new \RuntimeException('Cannot read bluescreen.css');
$js1 = array_map(fn($file) => '(function() {' . file_get_contents($file) . '})();', [
__DIR__ . '/../Bar/assets/bar.js',
@@ -143,11 +143,17 @@ private function buildJsCss(): string
$str = "'use strict';
(function(){
- var el = document.createElement('style');
- el.setAttribute('nonce', document.currentScript.getAttribute('nonce') || document.currentScript.nonce);
- el.className='tracy-debug';
- el.textContent=" . Helpers::jsonEncode(Helpers::minifyCss(implode('', $css))) . ";
- document.head.appendChild(el);})
+ var n = document.currentScript.getAttribute('nonce') || document.currentScript.nonce;
+ function s(css, cls) {
+ var el = document.createElement('style');
+ el.setAttribute('nonce', n);
+ el.className = cls;
+ el.textContent = css;
+ document.head.appendChild(el);
+ }
+ s(" . json_encode(Helpers::minifyCss(implode('', $sharedCss))) . ",'tracy-debug');
+ s(" . json_encode(Helpers::minifyCss($barCss)) . ",'tracy-debug tracy-bar-css');
+ s(" . json_encode(Helpers::minifyCss($bsCss)) . ",'tracy-debug tracy-bs-css');})
();\n" . implode('', $js1) . implode('', $js2);
return $str;
diff --git a/src/Tracy/Debugger/ProductionStrategy.php b/src/Tracy/Debugger/ProductionStrategy.php
index ed760e00a..71410cf70 100644
--- a/src/Tracy/Debugger/ProductionStrategy.php
+++ b/src/Tracy/Debugger/ProductionStrategy.php
@@ -63,7 +63,7 @@ public function handleError(
$err = 'PHP ' . Helpers::errorTypeToString($severity) . ': ' . Helpers::improveError($message) . " in $file:$line";
}
- Debugger::tryLog($err, Debugger::ERROR);
+ Debugger::tryLog($err, Debugger::WARNING);
}
diff --git a/src/Tracy/Dumper/Dumper.php b/src/Tracy/Dumper/Dumper.php
index 5bee48b40..19e2a2945 100644
--- a/src/Tracy/Dumper/Dumper.php
+++ b/src/Tracy/Dumper/Dumper.php
@@ -179,7 +179,7 @@ public static function renderAssets(): void
. file_get_contents(__DIR__ . '/../assets/toggle.css')
. file_get_contents(__DIR__ . '/assets/dumper-light.css')
. file_get_contents(__DIR__ . '/assets/dumper-dark.css');
- echo "\n";
+ echo "\n";
if (!Debugger::isEnabled() || !Debugger::$showBar) {
$s = '(function(){' . file_get_contents(__DIR__ . '/../assets/toggle.js') . '})();'
diff --git a/src/Tracy/Dumper/assets/dumper-dark.css b/src/Tracy/Dumper/assets/dumper-dark.css
index 8346d7216..29a2e323e 100644
--- a/src/Tracy/Dumper/assets/dumper-dark.css
+++ b/src/Tracy/Dumper/assets/dumper-dark.css
@@ -2,6 +2,8 @@
* This file is part of the Tracy (https://tracy.nette.org)
*/
+@layer base {
+
.tracy-dump.tracy-dark {
text-align: left;
color: #f8f8f2;
@@ -143,3 +145,5 @@ span[data-tracy-href] {
background: #c0c0c033;
}
}
+
+}
diff --git a/src/Tracy/Dumper/assets/dumper-light.css b/src/Tracy/Dumper/assets/dumper-light.css
index fb865920d..36a408f84 100644
--- a/src/Tracy/Dumper/assets/dumper-light.css
+++ b/src/Tracy/Dumper/assets/dumper-light.css
@@ -2,6 +2,8 @@
* This file is part of the Tracy (https://tracy.nette.org)
*/
+@layer base {
+
.tracy-dump.tracy-light {
text-align: left;
color: #444;
@@ -143,3 +145,5 @@ span[data-tracy-href] {
background: #c0c0c033;
}
}
+
+}
diff --git a/src/Tracy/Dumper/assets/dumper.js b/src/Tracy/Dumper/assets/dumper.js
index 81417ad71..59e68b201 100644
--- a/src/Tracy/Dumper/assets/dumper.js
+++ b/src/Tracy/Dumper/assets/dumper.js
@@ -46,9 +46,9 @@ class Dumper {
Dumper.inited = true;
document.documentElement.addEventListener('click', (e) => {
- let el;
+ let el, target = e.composedPath()[0] || e.target;
// enables & ctrl or cmd key
- if ((e.ctrlKey || e.metaKey) && (el = e.target.closest('[data-tracy-href]'))) {
+ if ((e.ctrlKey || e.metaKey) && (el = target.closest('[data-tracy-href]'))) {
location.href = el.getAttribute('data-tracy-href');
return false;
}
@@ -56,9 +56,9 @@ class Dumper {
});
document.documentElement.addEventListener('tracy-beforetoggle', (e) => {
- let el;
+ let el, target = e.composedPath()[0] || e.target;
// initializes lazy inside
- if ((el = e.target.closest('[data-tracy-snapshot]'))) {
+ if ((el = target.closest('[data-tracy-snapshot]'))) {
let snapshot = JSON.parse(el.getAttribute('data-tracy-snapshot'));
el.removeAttribute('data-tracy-snapshot');
el.querySelectorAll('[data-tracy-dump]').forEach((toggler) => {
@@ -72,7 +72,8 @@ class Dumper {
});
document.documentElement.addEventListener('tracy-toggle', (e) => {
- if (!e.target.matches('.tracy-dump *')) {
+ let target = e.composedPath()[0] || e.target;
+ if (!target.matches('.tracy-dump *')) {
return;
}
@@ -98,21 +99,23 @@ class Dumper {
});
document.documentElement.addEventListener('animationend', (e) => {
+ let target = e.composedPath()[0] || e.target;
if (e.animationName === 'tracy-dump-flash') {
- e.target.classList.toggle('tracy-dump-flash', false);
+ target.classList.toggle('tracy-dump-flash', false);
}
});
document.addEventListener('mouseover', (e) => {
- if (!e.target.matches('.tracy-dump *')) {
+ let target = e.composedPath()[0] || e.target;
+ if (!target.matches('.tracy-dump *')) {
return;
}
let el;
- if (e.target.matches('.tracy-dump-hash') && (el = e.target.closest('tracy-div'))) {
+ if (target.matches('.tracy-dump-hash') && (el = target.getRootNode())) {
el.querySelectorAll('.tracy-dump-hash').forEach((el) => {
- if (el.textContent === e.target.textContent) {
+ if (el.textContent === target.textContent) {
el.classList.add('tracy-dump-highlight');
}
});
@@ -125,8 +128,9 @@ class Dumper {
});
document.addEventListener('mouseout', (e) => {
- if (e.target.matches('.tracy-dump-hash')) {
- document.querySelectorAll('.tracy-dump-hash.tracy-dump-highlight').forEach((el) => {
+ let target = e.composedPath()[0] || e.target;
+ if (target.matches('.tracy-dump-hash')) {
+ target.getRootNode().querySelectorAll('.tracy-dump-hash.tracy-dump-highlight').forEach((el) => {
el.classList.remove('tracy-dump-highlight');
});
}
@@ -381,6 +385,31 @@ function UnknownEntityException() {}
let Tracy = window.Tracy = window.Tracy || {};
Tracy.Dumper = Tracy.Dumper || Dumper;
+if (!customElements.get('tracy-dump')) {
+ customElements.define('tracy-dump', class extends HTMLElement {
+ connectedCallback() {
+ if (this.shadowRoot) {
+ return;
+ }
+ let shadow = this.attachShadow({ mode: 'open' });
+
+ // clone CSS from document into shadow root (exclude component-specific CSS)
+ document.querySelectorAll('style.tracy-debug:not(.tracy-bar-css):not(.tracy-bs-css), style.tracy-dump-style').forEach((s) => {
+ shadow.appendChild(s.cloneNode(true));
+ });
+
+ // move only non-style/script children into shadow root
+ Array.from(this.childNodes).forEach((child) => {
+ if (child.nodeType !== 1 || (child.tagName !== 'STYLE' && child.tagName !== 'SCRIPT')) {
+ shadow.appendChild(child);
+ }
+ });
+
+ Tracy.Dumper.init(shadow);
+ }
+ });
+}
+
function init() {
Tracy.Dumper.init();
}
diff --git a/src/Tracy/Helpers.php b/src/Tracy/Helpers.php
index e439f0787..028951533 100644
--- a/src/Tracy/Helpers.php
+++ b/src/Tracy/Helpers.php
@@ -55,6 +55,7 @@ public static function editorUri(
string $action = 'open',
string $search = '',
string $replace = '',
+ ?int $column = null,
): ?string
{
if (Debugger::$editor && $file && ($action === 'create' || @is_file($file))) { // @ - may trigger error
@@ -65,7 +66,7 @@ public static function editorUri(
return strtr(Debugger::$editor, [
'%action' => $action,
'%file' => rawurlencode($file),
- '%line' => $line ?: 1,
+ '%line' => ($line ?: 1) . ($column ? ':' . $column : ''),
'%search' => rawurlencode($search),
'%replace' => rawurlencode($replace),
]);
diff --git a/src/Tracy/Logger/Logger.php b/src/Tracy/Logger/Logger.php
index 62415339c..fa9fb0190 100644
--- a/src/Tracy/Logger/Logger.php
+++ b/src/Tracy/Logger/Logger.php
@@ -16,23 +16,22 @@
*/
class Logger implements ILogger
{
- /** @var ?string name of the directory where errors should be logged */
- public $directory;
+ /** name of the directory where errors should be logged */
+ public ?string $directory = null;
- /** @var string|string[]|null email or emails to which send error notifications */
- public $email;
+ /** @var string|string[]|null email or emails to which send error notifications */
+ public string|array|null $email = null;
- /** @var ?string sender of email notifications */
- public $fromEmail;
+ /** sender of email notifications */
+ public ?string $fromEmail = null;
- /** @var string|int interval for sending email is 2 days */
- public $emailSnooze = '2 days';
+ /** interval for sending email is 2 days */
+ public string|int $emailSnooze = '2 days';
- /** @var callable(mixed $message, string $email): void handler for sending emails */
- public $mailer;
+ /** @var \Closure(mixed $message, string $email): void handler for sending emails */
+ public ?\Closure $mailer = null;
- /** @var ?BlueScreen */
- private $blueScreen;
+ private ?BlueScreen $blueScreen = null;
/**
@@ -52,7 +51,7 @@ public function __construct(?string $directory, string|array|null $email = null,
* For levels ERROR, EXCEPTION and CRITICAL it sends email.
* @return ?string logged error filename
*/
- public function log(mixed $message, string $level = self::INFO)
+ public function log(mixed $message, string $level = self::INFO): ?string
{
if (!$this->directory) {
throw new \LogicException('Logging directory is not specified.');
@@ -82,10 +81,7 @@ public function log(mixed $message, string $level = self::INFO)
}
- /**
- * @param mixed $message
- */
- public static function formatMessage($message): string
+ public static function formatMessage(mixed $message): string
{
if ($message instanceof \Throwable) {
$tmp = [];
@@ -106,10 +102,7 @@ public static function formatMessage($message): string
}
- /**
- * @param mixed $message
- */
- public static function formatLogLine($message, ?string $exceptionFile = null): string
+ public static function formatLogLine(mixed $message, ?string $exceptionFile = null): string
{
return implode(' ', [
date('[Y-m-d H-i-s]'),
@@ -159,10 +152,7 @@ protected function logException(\Throwable $exception, ?string $file = null): st
}
- /**
- * @param mixed $message
- */
- protected function sendEmail($message): void
+ protected function sendEmail(mixed $message): void
{
$snooze = is_numeric($this->emailSnooze)
? $this->emailSnooze
@@ -179,12 +169,7 @@ protected function sendEmail($message): void
}
- /**
- * Default mailer.
- * @param mixed $message
- * @internal
- */
- public function defaultMailer($message, string $email): void
+ private function defaultMailer(mixed $message, string $email): void
{
$host = preg_replace('#[^\w.-]+#', '', $_SERVER['SERVER_NAME'] ?? php_uname('n'));
mail(
diff --git a/src/Tracy/assets/helpers.js b/src/Tracy/assets/helpers.js
index 83c71b8e7..c578878f7 100644
--- a/src/Tracy/assets/helpers.js
+++ b/src/Tracy/assets/helpers.js
@@ -5,7 +5,7 @@ function init() {
document.addEventListener(
'click',
(e) => {
- e.target.closest('a[href^="editor:"]')?.setAttribute('target', '_blank');
+ (e.composedPath()[0] || e.target).closest('a[href^="editor:"]')?.setAttribute('target', '_blank');
},
true,
);
diff --git a/src/Tracy/assets/reset.css b/src/Tracy/assets/reset.css
index b0a6a6fc8..4348889d5 100644
--- a/src/Tracy/assets/reset.css
+++ b/src/Tracy/assets/reset.css
@@ -2,385 +2,40 @@
* This file is part of the Tracy (https://tracy.nette.org)
*/
-:root {
+@layer base, components;
+
+@layer base {
+
+:root,
+:host {
--tracy-space: 16px;
}
@media (max-width: 600px) {
- :root {
+ :root,
+ :host {
--tracy-space: 8px;
}
}
-tracy-div:not(a b),
-tracy-div:not(a b) * {
- font: inherit;
- line-height: inherit;
- color: inherit;
- background: transparent;
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
margin: 0;
padding: 0;
- border: none;
- text-align: inherit;
- list-style: inherit;
- opacity: 1;
- border-radius: 0;
- box-shadow: none;
- text-shadow: none;
- box-sizing: border-box;
- text-decoration: none;
- text-transform: inherit;
- white-space: inherit;
- float: none;
- clear: none;
- max-width: initial;
- min-width: initial;
- max-height: initial;
- min-height: initial;
-}
-
-tracy-div:not(a b) *:hover {
- color: inherit;
- background: transparent;
-}
-
-tracy-div:not(a b) *:not(svg):not(img):not(table) {
- width: initial;
- height: initial;
-}
-
-tracy-div:not(a b):before,
-tracy-div:not(a b):after,
-tracy-div:not(a b) *:before,
-tracy-div:not(a b) *:after {
- all: unset;
-}
-
-tracy-div:not(a b) b,
-tracy-div:not(a b) strong {
- font-weight: bold;
-}
-
-tracy-div:not(a b) small {
- font-size: smaller;
-}
-
-tracy-div:not(a b) i,
-tracy-div:not(a b) em {
- font-style: italic;
-}
-
-tracy-div:not(a b) big {
- font-size: larger;
-}
-
-tracy-div:not(a b) small,
-tracy-div:not(a b) sub,
-tracy-div:not(a b) sup {
- font-size: smaller;
-}
-
-tracy-div:not(a b) ins {
- text-decoration: underline;
}
-tracy-div:not(a b) del {
- text-decoration: line-through;
-}
-
-tracy-div:not(a b) table {
+table {
border-collapse: collapse;
}
-tracy-div:not(a b) pre {
- font-family: monospace;
- white-space: pre;
-}
-
-tracy-div:not(a b) code,
-tracy-div:not(a b) kbd,
-tracy-div:not(a b) samp {
- font-family: monospace;
-}
-
-tracy-div:not(a b) input {
- background-color: white;
+input {
padding: 1px;
- border: 1px solid;
}
-tracy-div:not(a b) textarea {
- background-color: white;
- border: 1px solid;
+textarea {
padding: 2px;
- white-space: pre-wrap;
-}
-
-tracy-div:not(a b) select {
- border: 1px solid;
- white-space: pre;
-}
-
-tracy-div:not(a b) article,
-tracy-div:not(a b) aside,
-tracy-div:not(a b) details,
-tracy-div:not(a b) div,
-tracy-div:not(a b) figcaption,
-tracy-div:not(a b) footer,
-tracy-div:not(a b) form,
-tracy-div:not(a b) header,
-tracy-div:not(a b) hgroup,
-tracy-div:not(a b) main,
-tracy-div:not(a b) nav,
-tracy-div:not(a b) section,
-tracy-div:not(a b) summary,
-tracy-div:not(a b) pre,
-tracy-div:not(a b) p,
-tracy-div:not(a b) dl,
-tracy-div:not(a b) dd,
-tracy-div:not(a b) dt,
-tracy-div:not(a b) blockquote,
-tracy-div:not(a b) figure,
-tracy-div:not(a b) address,
-tracy-div:not(a b) h1,
-tracy-div:not(a b) h2,
-tracy-div:not(a b) h3,
-tracy-div:not(a b) h4,
-tracy-div:not(a b) h5,
-tracy-div:not(a b) h6,
-tracy-div:not(a b) ul,
-tracy-div:not(a b) ol,
-tracy-div:not(a b) li,
-tracy-div:not(a b) hr {
- display: block;
-}
-
-tracy-div:not(a b) a,
-tracy-div:not(a b) b,
-tracy-div:not(a b) big,
-tracy-div:not(a b) code,
-tracy-div:not(a b) em,
-tracy-div:not(a b) i,
-tracy-div:not(a b) small,
-tracy-div:not(a b) span,
-tracy-div:not(a b) strong {
- display: inline;
-}
-
-tracy-div:not(a b) table {
- display: table;
-}
-
-tracy-div:not(a b) tr {
- display: table-row;
-}
-
-tracy-div:not(a b) col {
- display: table-column;
-}
-
-tracy-div:not(a b) colgroup {
- display: table-column-group;
-}
-
-tracy-div:not(a b) tbody {
- display: table-row-group;
-}
-
-tracy-div:not(a b) thead {
- display: table-header-group;
-}
-
-tracy-div:not(a b) tfoot {
- display: table-footer-group;
-}
-
-tracy-div:not(a b) td {
- display: table-cell;
-}
-
-tracy-div:not(a b) th {
- display: table-cell;
-}
-
-
-
-/* TableSort */
-tracy-div:not(a b) .tracy-sortable > :first-child > tr:first-child > * {
- position: relative;
-}
-
-tracy-div:not(a b) .tracy-sortable > :first-child > tr:first-child > *:hover:before {
- position: absolute;
- right: .3em;
- content: "\21C5";
- opacity: .4;
- font-weight: normal;
-}
-
-
-/* dump */
-tracy-div:not(a b) .tracy-dump div {
- padding-left: 3ex;
-}
-
-tracy-div:not(a b) .tracy-dump div div {
- border-left: 1px solid rgba(0, 0, 0, .1);
- margin-left: .5ex;
-}
-
-tracy-div:not(a b) .tracy-dump div div:hover {
- border-left-color: rgba(0, 0, 0, .25);
-}
-
-tracy-div:not(a b) .tracy-dump {
- background: #FDF5CE;
- padding: .4em .7em;
- border: 1px dotted silver;
- overflow: auto;
-}
-
-tracy-div:not(a b) table .tracy-dump.tracy-dump { /* overwrite .tracy-dump.tracy-light etc. */
- padding: 0;
- margin: 0;
- border: none;
-}
-
-tracy-div:not(a b) .tracy-dump-location {
- color: gray;
- font-size: 80%;
- text-decoration: none;
- background: none;
- opacity: .5;
- float: right;
- cursor: pointer;
-}
-
-tracy-div:not(a b) .tracy-dump-location:hover,
-tracy-div:not(a b) .tracy-dump-location:focus {
- color: gray;
- background: none;
- opacity: 1;
-}
-
-tracy-div:not(a b) .tracy-dump-array,
-tracy-div:not(a b) .tracy-dump-object {
- color: #C22;
-}
-
-tracy-div:not(a b) .tracy-dump-string {
- color: #35D;
- white-space: break-spaces;
-}
-
-tracy-div:not(a b) div.tracy-dump-string {
- position: relative;
- padding-left: 3.5ex;
-}
-
-tracy-div:not(a b) .tracy-dump-lq {
- margin-left: calc(-1ex - 1px);
-}
-
-tracy-div:not(a b) div.tracy-dump-string:before {
- content: '';
- position: absolute;
- left: calc(3ex - 1px);
- top: 1.5em;
- bottom: 0;
- border-left: 1px solid rgba(0, 0, 0, .1);
-}
-
-tracy-div:not(a b) .tracy-dump-virtual span,
-tracy-div:not(a b) .tracy-dump-dynamic span,
-tracy-div:not(a b) .tracy-dump-string span {
- color: rgba(0, 0, 0, 0.5);
-}
-
-tracy-div:not(a b) .tracy-dump-virtual i,
-tracy-div:not(a b) .tracy-dump-dynamic i,
-tracy-div:not(a b) .tracy-dump-string i {
- font-size: 80%;
- font-style: normal;
- color: rgba(0, 0, 0, 0.5);
- user-select: none;
-}
-
-tracy-div:not(a b) .tracy-dump-number {
- color: #090;
-}
-
-tracy-div:not(a b) .tracy-dump-null,
-tracy-div:not(a b) .tracy-dump-bool {
- color: #850;
-}
-
-tracy-div:not(a b) .tracy-dump-virtual {
- font-style: italic;
-}
-
-tracy-div:not(a b) .tracy-dump-public::after {
- content: ' pub';
-}
-
-tracy-div:not(a b) .tracy-dump-protected::after {
- content: ' pro';
-}
-
-tracy-div:not(a b) .tracy-dump-private::after {
- content: ' pri';
-}
-
-tracy-div:not(a b) .tracy-dump-public::after,
-tracy-div:not(a b) .tracy-dump-protected::after,
-tracy-div:not(a b) .tracy-dump-private::after,
-tracy-div:not(a b) .tracy-dump-hash {
- font-size: 85%;
- color: rgba(0, 0, 0, 0.5);
-}
-
-tracy-div:not(a b) .tracy-dump-indent {
- display: none;
-}
-
-tracy-div:not(a b) .tracy-dump-highlight {
- background: #C22;
- color: white;
- border-radius: 2px;
- padding: 0 2px;
- margin: 0 -2px;
-}
-
-tracy-div:not(a b) span[data-tracy-href] {
- border-bottom: 1px dotted rgba(0, 0, 0, .2);
-}
-
-
-/* toggle */
-tracy-div:not(a b) .tracy-toggle:after {
- content: '';
- display: inline-block;
- vertical-align: middle;
- line-height: 0;
- border-top: .6ex solid;
- border-right: .6ex solid transparent;
- border-left: .6ex solid transparent;
- transform: scale(1, 1.5);
- margin: 0 .2ex 0 .7ex;
- transition: .1s transform;
- opacity: .5;
-}
-
-tracy-div:not(a b) .tracy-toggle.tracy-collapsed:after {
- transform: rotate(-90deg) scale(1, 1.5) translate(.1ex, 0);
-}
-
-
-/* tabs */
-tracy-div:not(a b) .tracy-tab-label {
- user-select: none;
}
-tracy-div:not(a b) .tracy-tab-panel:not(.tracy-active) {
- display: none;
}
diff --git a/src/Tracy/assets/table-sort.css b/src/Tracy/assets/table-sort.css
index 7967a642c..ae4a3e809 100644
--- a/src/Tracy/assets/table-sort.css
+++ b/src/Tracy/assets/table-sort.css
@@ -2,6 +2,8 @@
* This file is part of the Tracy (https://tracy.nette.org)
*/
+@layer base {
+
.tracy-sortable > :first-child > tr:first-child > * {
position: relative;
}
@@ -13,3 +15,5 @@
opacity: .4;
font-weight: normal;
}
+
+}
diff --git a/src/Tracy/assets/table-sort.js b/src/Tracy/assets/table-sort.js
index 39bae80f6..0f7e3e0e9 100644
--- a/src/Tracy/assets/table-sort.js
+++ b/src/Tracy/assets/table-sort.js
@@ -6,10 +6,11 @@
class TableSort {
static init() {
document.documentElement.addEventListener('click', (e) => {
+ let target = e.composedPath()[0] || e.target;
if ((window.getSelection().type !== 'Range')
- && e.target.matches('.tracy-sortable > :first-child > tr:first-child *')
+ && target.matches('.tracy-sortable > :first-child > tr:first-child *')
) {
- TableSort.sort(e.target.closest('td,th'));
+ TableSort.sort(target.closest('td,th'));
}
});
diff --git a/src/Tracy/assets/tabs.css b/src/Tracy/assets/tabs.css
index 9add853a1..7aac90201 100644
--- a/src/Tracy/assets/tabs.css
+++ b/src/Tracy/assets/tabs.css
@@ -2,6 +2,8 @@
* This file is part of the Tracy (https://tracy.nette.org)
*/
+@layer base {
+
.tracy-tab-label {
user-select: none;
}
@@ -9,3 +11,5 @@
.tracy-tab-panel:not(.tracy-active) {
display: none;
}
+
+}
diff --git a/src/Tracy/assets/tabs.js b/src/Tracy/assets/tabs.js
index 8ec2fe7b2..7b109d071 100644
--- a/src/Tracy/assets/tabs.js
+++ b/src/Tracy/assets/tabs.js
@@ -6,11 +6,11 @@
class Tabs {
static init() {
document.documentElement.addEventListener('click', (e) => {
- let label, context;
+ let label, context, target = e.composedPath()[0] || e.target;
if (
!e.shiftKey && !e.ctrlKey && !e.metaKey
- && (label = e.target.closest('.tracy-tab-label'))
- && (context = e.target.closest('.tracy-tabs'))
+ && (label = target.closest('.tracy-tab-label'))
+ && (context = target.closest('.tracy-tabs'))
) {
Tabs.toggle(context, label);
e.preventDefault();
diff --git a/src/Tracy/assets/toggle.css b/src/Tracy/assets/toggle.css
index acce8e4f8..aea228004 100644
--- a/src/Tracy/assets/toggle.css
+++ b/src/Tracy/assets/toggle.css
@@ -2,6 +2,8 @@
* This file is part of the Tracy (https://tracy.nette.org)
*/
+@layer base {
+
.tracy-collapsed {
display: none;
}
@@ -33,3 +35,5 @@
.tracy-toggle.tracy-collapsed:after {
transform: rotate(-90deg) scale(1, 1.5) translate(.1ex, 0);
}
+
+}
diff --git a/src/Tracy/assets/toggle.js b/src/Tracy/assets/toggle.js
index aabefb5f1..7d927affa 100644
--- a/src/Tracy/assets/toggle.js
+++ b/src/Tracy/assets/toggle.js
@@ -16,7 +16,7 @@ class Toggle {
let el;
if (
!e.shiftKey && !e.ctrlKey && !e.metaKey
- && (el = e.target.closest('.tracy-toggle'))
+ && (el = (e.composedPath()[0] || e.target).closest('.tracy-toggle'))
&& Math.pow(start[0] - e.clientX, 2) + Math.pow(start[1] - e.clientY, 2) < MOVE_THRESHOLD
) {
Toggle.toggle(el, undefined, e);
@@ -40,13 +40,14 @@ class Toggle {
el.dispatchEvent(new CustomEvent('tracy-beforetoggle', {
bubbles: true,
+ composed: true,
detail: { collapsed: !expand, originalEvent: e },
}));
if (!ref || ref === '#') {
ref = '+';
} else if (ref.substr(0, 1) === '#') {
- dest = document;
+ dest = el.getRootNode();
}
ref = ref.match(/(\^\s*([^+\s]*)\s*)?(\+\s*(\S*)\s*)?(.*)/);
dest = ref[1] ? dest.parentNode : dest;
@@ -59,6 +60,7 @@ class Toggle {
el.dispatchEvent(new CustomEvent('tracy-toggle', {
bubbles: true,
+ composed: true,
detail: { relatedTarget: dest, collapsed: !expand, originalEvent: e },
}));
}
diff --git a/tests/Dumper/Dumper.dump().html.phpt b/tests/Dumper/Dumper.dump().html.phpt
index 7e6293144..571ae312a 100644
--- a/tests/Dumper/Dumper.dump().html.phpt
+++ b/tests/Dumper/Dumper.dump().html.phpt
@@ -21,7 +21,7 @@ test('html mode', function () {
Assert::same(123, Dumper::dump(123));
Assert::match(
<<<'XX'
-
+
Dumper::dump(123)) 📍
+
dump(123) 📍123
-
+
XX,
ob_get_clean(),
);
@@ -39,10 +39,10 @@ test('dark theme', function () {
dump(123);
Assert::match(
<<<'XX'
- dump(123) 📍123
-
+
XX,
ob_get_clean(),
);
diff --git a/tests/Tracy/Bar.renderAgent().phpt b/tests/Tracy/Bar.renderAgent().phpt
index bad6f6e84..9a434206a 100644
--- a/tests/Tracy/Bar.renderAgent().phpt
+++ b/tests/Tracy/Bar.renderAgent().phpt
@@ -1,11 +1,9 @@
-maxItems = 3;
+ $blueScreen->maxLength = 10;
+ $fn = function (array $items, string $text) {
+ throw new RuntimeException('Large arguments');
+ };
+ try {
+ $fn(range(1, 10), str_repeat('x', 50));
+ } catch (RuntimeException $e) {
+ $output = $blueScreen->renderAgent($e);
+ }
+
+ Assert::contains('$items = array (10)', $output);
+ Assert::contains(' 0 => 1', $output);
+ Assert::contains(' 1 => 2', $output);
+ Assert::contains(' 2 => 3', $output);
+ Assert::contains(' ...', $output);
+ Assert::notContains(' 3 => 4', $output);
+ Assert::contains('$text = \'xxxxxxxxxx ... xxxxxxxxxx\'', $output);
+});
+
+
test('caused by section for chained exceptions', function () use ($blueScreen) {
$prev = new InvalidArgumentException('Root cause', 7);
$exception = new RuntimeException('Wrapper', 5, $prev);
diff --git a/tests/Tracy/Helpers.isAgent().phpt b/tests/Tracy/Helpers.isAgent().phpt
index cc05e28ca..d45b30689 100644
--- a/tests/Tracy/Helpers.isAgent().phpt
+++ b/tests/Tracy/Helpers.isAgent().phpt
@@ -1,11 +1,9 @@
-
-
-
+
+
@@ -251,7 +251,7 @@
-
+
diff --git a/tests/Tracy/expected/Debugger.exception.fiber.html.expect b/tests/Tracy/expected/Debugger.exception.fiber.html.expect
index e32104186..fa94f698e 100644
--- a/tests/Tracy/expected/Debugger.exception.fiber.html.expect
+++ b/tests/Tracy/expected/Debugger.exception.fiber.html.expect
@@ -16,8 +16,8 @@
-
-
+
+
diff --git a/tests/Tracy/expected/Debugger.exception.generator.html.expect b/tests/Tracy/expected/Debugger.exception.generator.html.expect
index bba16447c..09c2aea00 100644
--- a/tests/Tracy/expected/Debugger.exception.generator.html.expect
+++ b/tests/Tracy/expected/Debugger.exception.generator.html.expect
@@ -16,8 +16,8 @@
-
-
+
+
diff --git a/tests/Tracy/expected/Debugger.exception.html.expect b/tests/Tracy/expected/Debugger.exception.html.expect
index 493667edd..aee55b3e1 100644
--- a/tests/Tracy/expected/Debugger.exception.html.expect
+++ b/tests/Tracy/expected/Debugger.exception.html.expect
@@ -16,8 +16,8 @@
-
-
+
+
@@ -321,7 +321,7 @@
-
+
diff --git a/tests/Tracy/expected/Debugger.exception.in-generator.html.expect b/tests/Tracy/expected/Debugger.exception.in-generator.html.expect
index ddaebf9e7..67065fabf 100644
--- a/tests/Tracy/expected/Debugger.exception.in-generator.html.expect
+++ b/tests/Tracy/expected/Debugger.exception.in-generator.html.expect
@@ -16,8 +16,8 @@
-
-
+
+
diff --git a/tests/Tracy/expected/Debugger.strict.html.expect b/tests/Tracy/expected/Debugger.strict.html.expect
index 6198e27df..1eba3474c 100644
--- a/tests/Tracy/expected/Debugger.strict.html.expect
+++ b/tests/Tracy/expected/Debugger.strict.html.expect
@@ -16,8 +16,8 @@
-
-
+
+
@@ -260,7 +260,7 @@
-
+
');
+ Assert::contains('Tracy\Helpers::jsonEncode($data, true)', $r);
+});
+
+test('text mode disables escaping', function () {
+ $r = compileText('{$foo}');
+ Assert::notContains('escapeHtml', $r);
+ Assert::notContains('jsonEncode', $r);
+});
+
+test('regular expression is escaped', function () {
+ $r = compile('{$name}');
+ Assert::contains('Tracy\Helpers::escapeHtml($name)', $r);
+});
+
+
+// noEscape for known functions
+
+test('editorLink not escaped', function () {
+ $r = compile('{Helpers::editorLink($f, $l)}');
+ Assert::notContains('escapeHtml', $r);
+});
+
+test('highlightFile not escaped', function () {
+ $r = compile('{BlueScreen::highlightFile($f, $l)}');
+ Assert::notContains('escapeHtml', $r);
+});
+
+test('$dump() not escaped', function () {
+ $r = compile('{$dump($v)}');
+ Assert::notContains('escapeHtml', $r);
+});
+
+test('Dumper::toHtml not escaped', function () {
+ $r = compile('{Dumper::toHtml($v)}');
+ Assert::notContains('escapeHtml', $r);
+});
+
+test('formatMessage not escaped', function () {
+ $r = compile('{$blueScreen->formatMessage($ex)}');
+ Assert::notContains('escapeHtml', $r);
+});
+
+
+// |json filter
+
+test('|json in text context', function () {
+ $r = compile('{=[1,2,3]|json}');
+ Assert::contains('Tracy\Helpers::jsonEncode([1, 2, 3])', $r);
+ Assert::notContains('escapeHtml', $r);
+});
+
+test('|json in attribute uses single quotes', function () {
+ $r = compile('');
+ Assert::contains('jsonEncode', $r);
+ Assert::contains("foo=\\'", $r);
+});
+
+test('|json on data attribute', function () {
+ $r = compile('');
+ Assert::contains('Tracy\Helpers::jsonEncode($snapshot)', $r);
+ Assert::contains("data-snapshot=\\'", $r);
+ Assert::notContains('escapeHtml', $r);
+});
+
+
+// Attribute expressions
+
+test('regular attr={$var}', function () {
+ $r = compile('');
+ Assert::contains('escapeHtml', $r);
+ Assert::contains('attr="', $r);
+});
+
+test('boolean attr (hidden)', function () {
+ $r = compile('');
+ Assert::contains("' hidden'", $r);
+});
+
+test('class={[...]} list attribute', function () {
+ $r = compile('');
+ Assert::contains('array_filter', $r);
+ Assert::contains('implode', $r);
+});
+
+test('noEscape in attribute (formatSnapshotAttribute)', function () {
+ $r = compile('');
+ Assert::notContains('escapeHtml', $r);
+});
+
+
+// Tags
+
+test('{varType}', function () {
+ $r = compile('{varType string $name}');
+ Assert::contains('/** @var string $name */', $r);
+});
+
+test('{use}', function () {
+ $r = compile('{use Tracy\Helpers}');
+ Assert::contains('use Tracy\Helpers;', $r);
+});
+
+test('{var}', function () {
+ $r = compile('{var $x = 42}');
+ Assert::contains('$x = 42', $r);
+});
+
+test('{do}', function () {
+ $r = compile('{do $x = 1}');
+ Assert::contains('$x = 1', $r);
+});
+
+test('{if}/{else}/{/if}', function () {
+ $r = compile('{if $cond}A{else}B{/if}');
+ Assert::contains('if ($cond)', $r);
+ Assert::contains('else', $r);
+});
+
+test('{foreach}', function () {
+ $r = compile('{foreach $items as $item}{$item}{/foreach}');
+ Assert::contains('foreach ($items as $item)', $r);
+});
+
+test('{exitIf}', function () {
+ $r = compile('{exitIf $done}');
+ Assert::contains('if ($done)', $r);
+ Assert::contains('return;', $r);
+});
+
+test('{continueIf}', function () {
+ $r = compile('{foreach $a as $b}{continueIf $b}{/foreach}');
+ Assert::contains('continue;', $r);
+});
+
+test('{define}/{include} block', function () {
+ $r = compile('{define foo}hello{/define}{include foo}');
+ Assert::contains("\$_blocks['foo'] = function", $r);
+ Assert::contains("\$_blocks['foo']()", $r);
+});
+
+test('{include file}', function () {
+ $r = compile("{include 'section.phtml'}");
+ Assert::contains("require __DIR__ . '/section.phtml'", $r);
+});
+
+test('{try}/{rollback}', function () {
+ $r = compile('{try}{do $x = dangerous()}{rollback}{do $x = fallback()}{/try}');
+ Assert::contains('try {', $r);
+ Assert::contains('catch (\Throwable)', $r);
+});
+
+
+// Output format
+
+test('starts with setContentType($textMode ? Latte\ContentType::Text : Latte\ContentType::Html);
+ Assert::same(file_get_contents($phtmlFile), $engine->compile($latteFile));
+ });
+}
diff --git a/tools/latte-convert/tests/fixtures/kitchen-sink.latte b/tools/latte-convert/tests/fixtures/kitchen-sink.latte
new file mode 100644
index 000000000..95d39cd8e
--- /dev/null
+++ b/tools/latte-convert/tests/fixtures/kitchen-sink.latte
@@ -0,0 +1,72 @@
+{varType string $file}
+{varType int $line}
+{varType int $expanded}
+{use Tracy\Helpers}
+
+
+Queries: {$count}{$totalTime ? foo(' time ms ', " time ms ", '', "") : Anjb-d}, {$name . (string) $foo} {=\JSON_UNESCAPED_SLASHES}
+
+
+ {define foo}
+ hello
+ {exitIf true}
+ {/define}
+
+ {define bar $arg, $arg}
+ hello
+ {/define}
+
+ {include foo}
+ {include for $var1, var}
+ {include 'section-exception.phtml'}
+
+ {foreach $events as [$connection, $query, $trace, $time, $rows, $error, $command, $explain]}
+
+
+ {if $error}
+ ERROR
+ {elseif $time !== null}{sprintf('%0.3f', $time * 1000)}
+ {/if}
+
+ {if $explain}
explain{/if}
+ {if $trace}
trace{/if}
+
+
+
+ {Nette\Database\Helpers::dumpSql($query, $connection)|noescape}
+
+ {if $trace}
+ {substr_replace(Helpers::editorLink($trace[0][file], $trace[0][line]), ' class="nette-DbConnectionPanel-source"', 2, 0)|noescape}
+
+ {foreach $trace as $row}
+
+ {isset($row[file]) ? Tracy\Helpers::editorLink($row[file], $row[line])}
+ {$row[class] ?? ''}{$row[type] ?? ''}{$row[function]}()
+
+ {/foreach}
+
+ {/if}
+
+
+ {$rows}
+
+ {/foreach}
+
+ {if count($events) < $count}...and more
{/if}
+
+
+
+
+
+link
+
+
+{try}
+ {do $x = dangerous()}
+{rollback}
+ {do $x = fallback()}
+{/try}
diff --git a/tools/latte-convert/tests/fixtures/kitchen-sink.phtml b/tools/latte-convert/tests/fixtures/kitchen-sink.phtml
new file mode 100644
index 000000000..e0f2760c2
--- /dev/null
+++ b/tools/latte-convert/tests/fixtures/kitchen-sink.phtml
@@ -0,0 +1,140 @@
+
+ #tracy-debug td.nette-DbConnectionPanel-sql { background: white !important; overflow-x: auto; max-width: 0; }
+ #tracy-debug .nette-DbConnectionPanel-source { color: #BBB !important }
+ #tracy-debug .nette-DbConnectionPanel-explain td { white-space: pre }
+
+
+Queries: ';
+echo Tracy\Helpers::escapeHtml($count) /* pos 11:14 */;
+echo Tracy\Helpers::escapeHtml($totalTime ? foo(' time ms ', ' time ms ', '', '') : 'Anjb-d') /* pos 11:22 */;
+echo ', ';
+echo Tracy\Helpers::escapeHtml($name . (string) $foo) /* pos 11:85 */;
+echo ' ';
+echo Tracy\Helpers::escapeHtml(\JSON_UNESCAPED_SLASHES) /* pos 11:109 */;
+echo '
+
+
+';
+$_blocks['foo'] = function () use (&$_blocks) {
+ echo ' hello
+';
+ if (true) /* pos 16:3 */ return;
+};
+echo "\n";
+$_blocks['bar'] = function ($arg,$arg) use (&$_blocks) {
+ echo ' hello
+';
+};
+echo "\n";
+$_blocks['foo']();
+$_blocks['for']($var1,var);
+require __DIR__ . '/section-exception.phtml';
+echo "\n";
+foreach ($events as [$connection, $query, $trace, $time, $rows, $error, $command, $explain]) /* pos 27:2 */ {
+ echo '
+
+';
+ if ($error) /* pos 30:5 */ {
+ echo ' ERROR
+ ';
+ } elseif ($time !== null) /* pos 32:6 */ {
+ echo Tracy\Helpers::escapeHtml(sprintf('%0.3f', $time * 1000)) /* pos 32:29 */;
+ echo "\n";
+ }
+
+ echo "\n";
+ if ($explain) /* pos 35:5 */ {
+ echo '
explain';
+ }
+ echo "\n";
+ if ($trace) /* pos 36:5 */ {
+ echo '
trace';
+ }
+ echo '
+
+
+
+ ';
+ echo Nette\Database\Helpers::dumpSql($query, $connection) /* pos 40:5 */;
+ echo '
+
+';
+ if ($trace) /* pos 42:5 */ {
+ echo ' ';
+ echo substr_replace(Helpers::editorLink($trace[0]['file'], $trace[0]['line']), ' class="nette-DbConnectionPanel-source"', 2, 0) /* pos 43:6 */;
+ echo '
+
+';
+ foreach ($trace as $row) /* pos 45:7 */ {
+ echo '
+ ';
+ echo isset($row['file']) ? Tracy\Helpers::editorLink($row['file'], $row['line']) : null /* pos 47:13 */;
+ echo '
+ ';
+ echo Tracy\Helpers::escapeHtml($row['class'] ?? '') /* pos 48:13 */;
+ echo Tracy\Helpers::escapeHtml($row['type'] ?? '') /* pos 48:32 */;
+ echo Tracy\Helpers::escapeHtml($row['function']) /* pos 48:50 */;
+ echo '()
+
+';
+
+ }
+
+ echo '
+';
+ }
+ echo '
+
+ ';
+ echo Tracy\Helpers::escapeHtml($rows) /* pos 55:8 */;
+ echo '
+
+';
+
+}
+
+echo "\n";
+if (count($events) < $count) /* pos 59:2 */ {
+ echo ' ...and more
';
+}
+echo '
+
+
+
+
+
+link
+
+
+';
+try {
+ $x = dangerous() /* pos 69:2 */;
+} catch (\Throwable) {
+ $x = fallback() /* pos 71:2 */;
+}
diff --git a/tools/open-in-editor/linux/open-editor.sh b/tools/open-in-editor/linux/open-editor.sh
index 69e37be83..8cabf2789 100755
--- a/tools/open-in-editor/linux/open-editor.sh
+++ b/tools/open-in-editor/linux/open-editor.sh
@@ -6,7 +6,7 @@ declare -A mapping
#
# Visual Studio Code
-#editor='code --goto "$FILE":"$LINE"'
+#editor='code --goto "$FILE":"$LINE":"$COLUMN"'
# Emacs
#editor='emacs +$LINE "$FILE"'
# gVim
@@ -17,11 +17,9 @@ declare -A mapping
#editor='pluma +$LINE "$FILE"'
# PHPStorm
# To enable PHPStorm command-line interface, folow this guide: https://www.jetbrains.com/help/phpstorm/working-with-the-ide-features-from-command-line.html
-#editor='phpstorm --line $LINE "$FILE"'
+#editor='phpstorm --line $LINE --column $COLUMN "$FILE"'
# VS Codium
#editor='codium --goto "$FILE":"$LINE"'
-# Visual Studio Code
-#editor='code --goto "$FILE":"$LINE"'
#
# Optionally configure custom mapping here:
@@ -56,16 +54,21 @@ action=`echo $url | sed -r "s/$regex/\1/i"`
uri_params=`echo $url | sed -r "s/$regex/\2/i"`
file=`get_param $uri_params "file"`
-line=`get_param $uri_params "line"`
+line_param=`get_param $uri_params "line"`
search=`get_param $uri_params "search"`
replace=`get_param $uri_params "replace"`
+# Parse line and column from line parameter (format: "12:5" or just "12")
+IFS=':' read -r line column <<< "$line_param"
+column="${column:-1}"
+
# Debug?
#echo "action '$action'"
#echo "file '$file'"
#echo "line '$line'"
#echo "search '$search'"
#echo "replace '$replace'"
+#echo "column '$column'"
# Convert URI encoded codes to normal characters (e.g. '%2F' => '/').
printf -v file "${file//%/\\x}"
@@ -102,6 +105,7 @@ fi
# Format the command according to the selected editor.
command="${editor//\$FILE/$file}"
command="${command//\$LINE/$line}"
+command="${command//\$COLUMN/$column}"
# Debug?
#echo $command
diff --git a/tools/open-in-editor/windows/open-editor.js b/tools/open-in-editor/windows/open-editor.js
index ac1093e70..86f699483 100644
--- a/tools/open-in-editor/windows/open-editor.js
+++ b/tools/open-in-editor/windows/open-editor.js
@@ -1,7 +1,7 @@
var settings = {
// PhpStorm
- // editor: '"C:\\Program Files\\JetBrains\\PhpStorm 2018.1.2\\bin\\phpstorm64.exe" --line %line% "%file%"',
+ // editor: '"C:\\Program Files\\JetBrains\\PhpStorm 2018.1.2\\bin\\phpstorm64.exe" --line %line% --column %column% "%file%"',
// title: 'PhpStorm',
// NetBeans
@@ -14,7 +14,7 @@ var settings = {
// editor: '"C:\\Program Files\\SciTE\\scite.exe" "-open:%file%" -goto:%line%',
// EmEditor
- // editor: '"C:\\Program Files\\EmEditor\\EmEditor.exe" "%file%" /l %line%',
+ // editor: '"C:\\Program Files\\EmEditor\\EmEditor.exe" "%file%" /l %line% /cl %column%',
// PSPad Editor
// editor: '"C:\\Program Files\\PSPad editor\\PSPad.exe" -%line% "%file%"',
@@ -26,7 +26,7 @@ var settings = {
// editor: '"C:\\Program Files\\Sublime Text 2\\sublime_text.exe" "%file%:%line%"',
// Visual Studio Code / VSCodium
- // editor: '"C:\\Program Files\\Microsoft VS Code\\Code.exe" --goto "%file%:%line%"',
+ // editor: '"C:\\Program Files\\Microsoft VS Code\\Code.exe" --goto "%file%:%line%:%column%"',
mappings: {
// '/remotepath': '/localpath'
@@ -41,7 +41,7 @@ if (!settings.editor) {
}
var url = WScript.Arguments(0);
-var match = /^editor:\/\/(open|create|fix)\/?\?file=([^&]+)&line=(\d+)(?:&search=([^&]*)&replace=([^&]*))?/.exec(url);
+var match = /^editor:\/\/(open|create|fix)\/?\?file=([^&]+)&line=(\d+)(?::(\d+))?(?:&search=([^&]*)&replace=([^&]*))?/.exec(url);
if (!match) {
WScript.Echo('Unexpected URI ' + url);
WScript.Quit();
@@ -53,8 +53,9 @@ for (var i in match) {
var action = match[1];
var file = match[2];
var line = match[3];
-var search = match[4];
-var replace = match[5];
+var column = match[4] || 1;
+var search = match[5];
+var replace = match[6];
var shell = new ActiveXObject('WScript.Shell');
var fileSystem = new ActiveXObject('Scripting.FileSystemObject');
@@ -76,7 +77,7 @@ if (action === 'create' && !fileSystem.FileExists(file)) {
fileSystem.OpenTextFile(file, 2).Write(lines.join('\n'));
}
-var command = settings.editor.replace(/%line%/, line).replace(/%file%/, file);
+var command = settings.editor.replace(/%line%/, line).replace(/%column%/, column).replace(/%file%/, file);
shell.Exec(command);
if (settings.title) {