├── LICENCE
├── README.md
├── Sage.php
├── composer.json
├── decorators
├── SageDecoratorsInterface.php
├── SageDecoratorsPlain.php
└── SageDecoratorsRich.php
├── inc
├── SageHelper.php
├── SageParser.php
├── SageTraceStep.php
├── SageVariableData.php
├── eloquentListener.inc.php
└── shorthands.inc.php
├── parsers
├── SageParserInterface.php
├── SageParsersBlacklist.php
├── SageParsersClassName.php
├── SageParsersClassStatics.php
├── SageParsersClosure.php
├── SageParsersColor.php
├── SageParsersDateTime.php
├── SageParsersEloquent.php
├── SageParsersFilePath.php
├── SageParsersJson.php
├── SageParsersMicrotime.php
├── SageParsersObjectIterateable.php
├── SageParsersSmarty.php
├── SageParsersSplFileInfo.php
├── SageParsersSplObjectStorage.php
├── SageParsersTimestamp.php
└── SageParsersXml.php
└── resources
├── compiled
├── aante-light.css
├── original-light.css
├── original.css
├── sage.js
├── solarized-dark.css
└── solarized.css
├── css
├── base.scss
└── themes
│ ├── aante-light.scss
│ ├── original-light.scss
│ ├── original.scss
│ ├── solarized-dark.scss
│ └── solarized.scss
└── js
└── base.js
/LICENCE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 Rokas Šleinius (raveren@gmail.com)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sage - Insightful PHP debugging assistant ☯
2 |
3 | At first glance **Sage** is just a drop-in replacement
4 | for **[var_dump()](http://php.net/manual/en/function.var-dump.php)**
5 | and **[debug_backtrace()](http://php.net/manual/en/function.debug-backtrace.php)**.
6 |
7 | However, it's much, *much* more.
8 |
9 | ---
10 |
11 | 
12 |
13 | Sage is designed to exceed expectations, it intelligently handles everything that you throw at it and displays in the best possible way.
14 |
15 | If you can think of how it can be better, please create an issue!
16 |
17 | For an overview of Sage's outstanding features jump to the [F.A.Q.](#faq)
18 |
19 | ## Installation
20 |
21 | ```bash
22 | composer require php-sage/sage --dev
23 | ```
24 |
25 | #### Or if you prefer, [download the phar](https://github.com/php-sage/sage/raw/main/sage.phar) and simply
26 |
27 | ```php
28 | 10 years. It is used by a lot of real people in a lot of
80 | real-world scenarios, it is battle tested and has a lot of advanced features no other tool has.
81 |
82 | ## More cool stuff
83 |
84 | Sage displays the passed variable name and supports prefixes to the dump function call. Use it for some common
85 | on-the-fly adjustments to the dump output.
86 |
87 | ```php
88 | ~ss($var); // outputs plain text
89 | $output = @ss($var); // returns output instead of displaying it
90 | ! sage($var); // ignores depth limit for large objects
91 | + sage($var); // auto-expands the view
92 | print sd($var); // saves output into "sage.html" in the current directory
93 | print ! sd($var); // saves output into "sage.html" while also ignoring the output depth limit!
94 | ```
95 |
96 | Sage tokenizes & introspects the calling code to get all this information. All that can be done with prefixes can also
97 | be achieved with standard, verbose syntax:
98 |
99 | ```php
100 | ~ss($var);
101 | // is equivalent to:
102 | Sage::enabled(Sage::MODE_TEXT_ONLY);
103 | Sage::dump($var);
104 | ```
105 |
106 | See [Advanced section](#-advanced-tips--tricks) below if you want more tips & tricks.
107 |
108 | ### Verbose versions
109 |
110 | If you want to use Sage as part of permanent code (e.g. part of a test helper/logger/exception reporter etc), you can
111 | use the verbose way to invoke Sage:
112 |
113 | ```php
114 | // instead of sage()
115 | Sage::dump('this looks way less hacky (yeah right:)');
116 |
117 | // equivalent to sage(1);
118 | Sage::trace();
119 |
120 | // a real-life test helper:
121 | class TestHelper{
122 | public function getVarDump(mixed $providedContext): string
123 | {
124 | if (! $providedContext) {
125 | return '';
126 | }
127 |
128 | $state = Sage::saveState();
129 | Sage::enabled(Sage::MODE_TEXT_ONLY);
130 | Sage::$aliases[] = __CLASS__ . '::' . __FUNCTION__;
131 | Sage::$returnOutput = true;
132 | Sage::$displayCalledFrom = false;
133 | $debugOutput = Sage::dump($providedContext);
134 |
135 | Sage::saveState($state); // now reset settings to presumed defaults
136 |
137 | return PHP_EOL . $debugOutput;
138 | }
139 | }
140 | ```
141 |
142 | ----
143 |
144 | # Customization
145 |
146 | The main goal of Sage is to be **zero-setup**. There are also several customization options for advanced uses.
147 |
148 | ### Where to store the configuration?
149 |
150 | 1. Add this entry to the `autoload.files` configuration in `composer.json`:
151 |
152 | ```js
153 | "autoload": {
154 | "files": [
155 | "config/sage.php" /* <--------------- create this file with your settings! */
156 | ]
157 | }, ...
158 | ```
159 |
160 | 2. Put settings inside of `php.ini`:
161 |
162 | ```ini
163 | ; change sage theme:
164 | sage.theme = solarized-dark
165 | ; always display all dump levels, almost always crashes the browser:
166 | sage.maxLevels = 0
167 | ; set your IDE links
168 | sage.editor = vscode
169 | ; disable Sage unless explicitly enabled
170 | sage.enabled = 0
171 | ```
172 |
173 | 3. Include the desired settings in your bootstrap process anywhere™.
174 |
175 | ```php
176 | require 'sage.phar';
177 | Sage::$theme = Sage::THEME_LIGHT;
178 | ```
179 |
180 |
181 | # All available options
182 |
183 | ```php
184 | Sage::$theme = Sage::THEME_ORIGINAL;
185 | ```
186 |
187 | Currently available themes are:
188 |
189 | * `Sage::THEME_ORIGINAL`
190 | * `Sage::THEME_LIGHT`
191 | * `Sage::THEME_SOLARIZED`
192 | * `Sage::THEME_SOLARIZED_DARK`
193 |
194 | ---
195 |
196 | ```php
197 | Sage::$editor = ini_get('xdebug.file_link_format');
198 | ```
199 |
200 | Make visible source file paths clickable to open your editor. Available options are:
201 |
202 | > `'phpstorm-remote'` - default (requires [IDE Remote Control](https://plugins.jetbrains.com/plugin/19991-ide-remote-control) plugin), `'sublime'`, `'textmate'`, `'emacs'`, `'macvim'`, `'phpstorm'`, `'idea'`, `'vscode'`, `'vscode-insiders'`, `'vscode-remote'`, `'vscode-insiders-remote'`, `'vscodium'`, `'atom'`, `'nova'`, `'netbeans'`, `'xdebug'`
203 |
204 | Or pass a custom string where %file should be replaced with full file path, %line with line number to create a custom
205 | link. Set to null to disable linking.
206 |
207 | ---
208 |
209 | ```php
210 | Sage::$displayCalledFrom = true;
211 | ```
212 |
213 | Whether to display where Sage was called from
214 |
215 | ---
216 |
217 | ```php
218 | Sage::$maxLevels = 7;
219 | ```
220 |
221 | Max array/object levels to go deep, set to zero/false to disable
222 |
223 | ---
224 |
225 | ```php
226 | Sage::$expandedByDefault = false;
227 | ```
228 |
229 | Draw rich output already expanded without having to click
230 |
231 | ---
232 |
233 | ```php
234 | Sage::$cliDetection = true;
235 | ```
236 |
237 | Enable detection when running in command line and adjust output format accordingly.
238 |
239 | ---
240 |
241 | ```php
242 | Sage::$cliColors = true;
243 | ```
244 |
245 | In addition to above setting, enable detection when Sage is run in *UNIX* command line. Attempts to add coloring, but if
246 | opened as plain text, the color information is visible as gibberish.
247 |
248 | ---
249 |
250 | ```php
251 | Sage::$charEncodings = [ 'UTF-8', 'Windows-1252', 'euc-jp' ]
252 | ```
253 |
254 | Possible alternative char encodings in order of probability.
255 |
256 | ---
257 |
258 | ```php
259 | Sage::$returnOutput = false;
260 | ```
261 |
262 | Sage returns output instead of printing it.
263 |
264 | ---
265 |
266 | ```php
267 | Sage::$aliases;
268 | ```
269 |
270 | Add new custom Sage wrapper names. Optional, but needed for backtraces, variable name detection and modifiers to work
271 | properly. Accepts array or comma separated string. Use notation `Class::method` for methods.
272 |
273 | ---
274 |
275 | # 🧙 Advanced Tips & Tricks
276 |
277 | > this section is under construction :)
278 |
279 | ```php
280 | // we already saw:
281 | sage($GLOBALS, $_SERVER);
282 | // you can also go shorter for the same result:
283 | s($GLOBALS, $_SERVER);
284 | // or you can go the verbose way, it's all equivalent:
285 | Sage::dump($GLOBALS, $_SERVER);
286 |
287 |
288 | // ss() will display a more basic, javascript-free display (but with colors)
289 | ss($GLOBALS, $_SERVER);
290 | // to recap: s() or sage() - dumps. Add "d" to die afterwards: sd(), saged()
291 | // preppend "s" to simplify output: ss(), ssage().
292 | // works in combination, too: ssd() and ssagedd() will dump in "simple mode" and die!
293 |
294 | // prepending a tilde will make the output *even more basic* (rich->basic and basic->plain text)
295 | ~d($GLOBALS, $_SERVER); // more on modifiers below
296 |
297 | // show a trace
298 | Sage::trace();
299 | s(1); // shorthand works too!
300 | s(2); // trace - but just the paths
301 | Sage::dump( debug_backtrace() ); // you can even pass a custom result from debug_trace and it will be recognized
302 |
303 | // dump and die debugging
304 | sd($GLOBALS, $_SERVER); // dd() might be taken by your framework
305 | saged($GLOBALS, $_SERVER); // so this is an equivalent alternative
306 | ssd($GLOBALS, $_SERVER); // available for plain display too!
307 |
308 | // this will disable Sage completely
309 | Sage::enabled(false);
310 | sd('Get off my lawn!'); // no effect
311 | ```
312 |
313 | * Sage supports keyboard shortcuts! Just press d when viewing output and the rest is self-explanatory, try it
314 | out! (p.s.
315 | vim-style `hjkl` works as well);
316 | * Call `Sage::enabled(Sage::MODE_PLAIN);` to switch to a simpler, js-free output.
317 | * Call `Sage::enabled(Sage::MODE_TEXT_ONLY);` for pure-plain text output which you can save or pass around by first
318 | setting `Sage::$returnOutput = true;`
319 | * Sage can provide a plain-text version of its output and does so automatically when invoked via PHP running in command
320 | line mode.
321 |
322 | 
323 |
324 | * Double clicking the `[+]` sign in the output will expand/collapse ALL nodes; **triple clicking** a big block of text
325 | will select it all.
326 | * Clicking the tiny arrow on the **right** of the output will open it in a separate window where you can keep it for
327 | comparison.
328 | * Sage supports themes:
329 |
330 | 
331 |
332 | For customization instructions read the section below.
333 | * If a variable is an object, its classname can be clicked to open the class in your IDE.
334 | * There are several real-time prefix modifiers you can use (combinations possible):
335 |
336 | | Prefix | | Example |
337 | |--------|----------------------------------------------|--------------|
338 | | print | Puts output into current DIR as sage.html | print sage() |
339 | | ! | Dump ignoring depth limits for large objects | ! sage() |
340 | | ~ | Simplifies sage output (rich->html->plain) | ~ sage() |
341 | | - | Clean up any output before dumping | - sage() |
342 | | + | Expand all nodes (in rich view) | + sage() |
343 | | @ | Return output instead of displaying it | @ sage() |
344 |
345 | * Sage also includes a naïve profiler you may find handy. It's for determining relatively which code blocks take longer
346 | than others:
347 | * Sage runs perfectly fine on PHP 5.1 (couldn't find a way to test it on 5.0).
348 |
349 | ```php
350 | Sage::dump( microtime() ); // just pass microtime() - also works if you pass NOTHING: s();
351 | sleep( 1 );
352 | Sage::dump( microtime(), 'after sleep(1)' );
353 | sleep( 2 );
354 | sd( microtime(), 'final call, after sleep(2)' );
355 | ```
356 |
357 | 
358 |
359 | ---
360 |
361 | ## F.A.Q.
362 |
363 | ### 💬 How is it different or better than [symfony/var-dumper](https://symfony.com/doc/current/components/var_dumper.html)?
364 |
365 | * Visible **Variable name**
366 | * Keyboard shortcuts. Type d and the rest is just self-explanatory (use arrows, space, tab, enter, you'll get
367 | it!).
368 | * **Debug backtraces** with full insight of arguments, callee objects and more.
369 | * Custom display for a lot of recognized types:
370 | 
371 | * Has text-only, plain and rich views, has several visual themes - created by a professional designer.
372 | * A huge number of usability enhancements - like the (clickable) **call trace** in the footer of each output.
373 | * Supports convenience modifiers, for example `@sage($var);` will return instead of outputting, `-sage($var);`
374 | will `ob_clean` all output to be the only thing on page (see advanced section above for more).
375 | * Compatibility! Fully works on **PHP 5.1 - 8.1+**!
376 | * Code is way less complex - to read and contribute to.
377 | * Sage came **first** - developed
378 | since [pre-2012](https://github.com/php-sage/sage/commit/3c49968cb912fb627c6650c4bfd4673bb1b44277). It inspired the
379 | now
380 | ubiquitous [dd](https://github.com/php-sage/sage/commit/fa6c8074ea1870bb5c6a080e94f7130e9a0f2fda#diff-2cdf3c423d47e373c75638c910674ec68c5aa434e11d4074037c91a543d9cb58R549)
381 | shorthand, pioneered a lot of the concepts in the tool niche.
382 |
383 | ### 💬 What are the other dumpers out there
384 |
385 | * [Symfony/var-dumper](https://symfony.com/doc/current/components/var_dumper.html)
386 | * [yii\helpers\VarDumper](https://www.yiiframework.com/doc/api/2.0/yii-helpers-vardumper)
387 | * [Tracy](https://tracy.nette.org/)
388 | * [PHP Debug Bar](https://github.com/maximebf/php-debugbar)
389 | * [Kint](https://kint-php.github.io/kint/) - sage supersedes Kint.
390 |
391 | ### 💬 Why does Sage look so much like Kint? A.K.A. Why does this repo have so few stars?
392 |
393 | Because it is Kint, and I am its author, however the project was
394 | [**forcibly taken over**](https://github.com/kint-php/kint/commit/1ea81f3add81b586756515673f8364f60feb86a3) by a
395 | malicious contributor!
396 |
397 | Instead of fighting windmills I chose to fork and rename the last good version and continue under a new name!
398 |
399 | You can use Sage as a drop-in replacement for Kint. Simple.
400 |
401 | ### 💬 How is `var_dump` - style debugging still relevant when we have Xdebug?
402 |
403 | 1. In practice, Xdebug is quite often very difficult and time-consuming to install and configure.
404 | 2. There's many use-cases where dump&die is just faster to bring up.
405 | 3. There is no way you can visualise a timeline of changed data with XDebug. For example, all values dumped from within
406 | a loop.
407 | 4. And there's more subtle use-cases, eg. if you stepped over something there's no way to go back, but with var-dumping
408 | the values are still there...
409 |
410 | I use xdebug almost daily, by the way. Side by side with Sage.
411 |
412 | ### Known issues
413 |
414 | 1. Since PHP 8.2 variable name detection for *multiline* Sage calls is broken. Simple matter of Backtrace format
415 | changing the reported line, fix is comming.
416 |
417 | ---
418 |
419 | ### Contributing
420 |
421 | #### 🎲 Prerequisites
422 |
423 | * Install [Docker Compose](https://docs.docker.com/compose/install/#install-compose').
424 | * If you're on **Windows** 10+ you need to use WSL2:
425 | 1. Setup: `wsl --install`
426 | 2. Set Ubuntu as your default wsl shell: `wsl --set-version Ubuntu 2`.
427 | 3. All commands listed below must be run from inside wsl shell: `wsl`
428 |
429 | Do your changes but before committing run
430 |
431 | ```bash
432 | docker compose run php composer build
433 | # OR (see Makefile):
434 | make build
435 | ```
436 |
437 | To compile resources and build the phar file.
438 |
439 | ## Author
440 |
441 | **Rokas Šleinius** ([Raveren](https://github.com/raveren))
442 |
443 | Contributors: https://github.com/php-sage/sage/graphs/contributors
444 |
445 | ### License
446 |
447 | Licensed under the MIT License
448 |
449 | ---
450 |
451 | Hope you'll love using Sage as much as I love creating it!
452 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "php-sage/sage",
3 | "description": "☯ Insightful PHP debugging assistant.",
4 | "keywords": [
5 | "sage",
6 | "php",
7 | "dumper",
8 | "debug",
9 | "dump",
10 | "var_dump",
11 | "debug_backtrace",
12 | "json_decode"
13 | ],
14 | "type": "library",
15 | "homepage": "https://github.com/php-sage/sage",
16 | "license": "MIT",
17 | "authors": [
18 | {
19 | "name": "Rokas Šleinius",
20 | "homepage": "https://github.com/raveren"
21 | },
22 | {
23 | "name": "Contributors",
24 | "homepage": "https://github.com/php-sage/sage/contributors"
25 | }
26 | ],
27 | "require": {
28 | "php": ">=5.1.0"
29 | },
30 | "require-dev": {
31 | "pestphp/pest": "^1.0",
32 | "seld/phar-utils": "^1.0",
33 | "spatie/pest-plugin-snapshots": "^1.0",
34 | "symfony/finder": "^3.0 || ^4.0 || ^5.0",
35 | "symfony/var-dumper": "^6.1"
36 | },
37 | "autoload": {
38 | "files": [
39 | "Sage.php"
40 | ]
41 | },
42 | "config": {
43 | "sort-packages": true,
44 | "allow-plugins": {
45 | "pestphp/pest-plugin": true
46 | }
47 | },
48 | "scripts": {
49 | "post-update-cmd": "npm ci",
50 | "post-install-cmd": "@post-update-cmd",
51 | "build": [
52 | "@post-update-cmd",
53 | "@build:resources",
54 | "@build:php"
55 | ],
56 | "build:php": "php .github/build/build.php",
57 | "build:resources": "npm run build"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/decorators/SageDecoratorsInterface.php:
--------------------------------------------------------------------------------
1 | name ? $varData->name : '';
29 | $varData->name = null;
30 |
31 | $output .= $this->title($name);
32 | }
33 |
34 | // make each level different-color
35 | self::$levelColors = array_slice(self::$levelColors, 0, $level);
36 | $s = ' ';
37 | $space = '';
38 | if (Sage::enabled() === Sage::MODE_CLI) {
39 | for ($i = 0; $i < $level; $i++) {
40 | if (! array_key_exists($i, self::$levelColors)) {
41 | self::$levelColors[$i] = rand(1, 231);
42 | }
43 | $color = self::$levelColors[$i];
44 | $space .= "\x1b[38;5;{$color}m┆\x1b[0m ";
45 | }
46 | } else {
47 | $space = str_repeat($s, $level);
48 | }
49 |
50 | $output .= $space . $this->drawHeader($varData);
51 |
52 | if (isset($varData->extendedValue)) {
53 | $output .= ' ' . ($varData->type === 'array' ? '[' : '(') . PHP_EOL;
54 |
55 | if (is_array($varData->extendedValue)) {
56 | foreach ($varData->extendedValue as $k => $v) {
57 | if (is_string($v)) {
58 | $output .= $space . $s
59 | . $this->colorize($k, 'key', false) . ': '
60 | . $this->colorize($v, 'value');
61 | } else {
62 | $output .= $this->decorate($v, $level + 1);
63 | }
64 | }
65 | } elseif (is_string($varData->extendedValue)) {
66 | $output .= $space . $s . $this->colorize($varData->extendedValue, 'value');
67 | } else {
68 | // throw new RuntimeException();
69 | // $output .= self::decorate($varData->extendedValue, $level + 1); // it's SageVariableData
70 | }
71 | $output .= $space . ($varData->type === 'array' ? ']' : ')');
72 | }
73 |
74 | $output .= PHP_EOL;
75 |
76 | return $output;
77 | }
78 |
79 | /** @param SageTraceStep[] $traceData */
80 | public function decorateTrace(array $traceData, $pathsOnly = false)
81 | {
82 | $lastStepNumber = count($traceData);
83 | $output = $this->title($pathsOnly ? 'QUICK TRACE' : 'TRACE');
84 |
85 | $blacklistedStepsInARow = 0;
86 | foreach ($traceData as $stepNumber => $step) {
87 | if (
88 | $stepNumber >= Sage::$minimumTraceStepsToShowFull
89 | && $step->isBlackListed
90 | ) {
91 | $blacklistedStepsInARow++;
92 | continue;
93 | }
94 |
95 | if ($blacklistedStepsInARow) {
96 | if ($blacklistedStepsInARow <= 5) {
97 | for ($j = $blacklistedStepsInARow; $j > 0; $j--) {
98 | $output .= $this->drawTraceStep(
99 | $stepNumber - $j,
100 | $traceData[$stepNumber - $j],
101 | $pathsOnly,
102 | $lastStepNumber
103 | );
104 | }
105 | } else {
106 | $output .= "...\n[{$blacklistedStepsInARow} steps skipped]\n...\n";
107 | }
108 |
109 | $blacklistedStepsInARow = 0;
110 | }
111 | $output .= $this->drawTraceStep($stepNumber, $step, $pathsOnly, $lastStepNumber);
112 | }
113 |
114 | if ($blacklistedStepsInARow > 1) {
115 | $output .= "...\n[{$blacklistedStepsInARow} steps skipped]\n";
116 | }
117 |
118 | return $output;
119 | }
120 |
121 | private function colorize($text, $type, $nlAfter = true)
122 | {
123 | $nl = $nlAfter ? PHP_EOL : '';
124 |
125 | switch (Sage::enabled()) {
126 | case Sage::MODE_PLAIN:
127 | if (! self::$_enableColors) {
128 | return $text . $nl;
129 | }
130 |
131 | switch ($type) {
132 | case 'key':
133 | $text = "{$text} ";
134 | break;
135 | case 'access':
136 | $text = "{$text} ";
137 | break;
138 | case 'value':
139 | $text = "{$text} ";
140 | break;
141 | case 'type':
142 | $text = "{$text} ";
143 | break;
144 | case 'header':
145 | $text = "
{$text} ";
146 | break;
147 | }
148 |
149 | return $text . $nl;
150 | case Sage::MODE_CLI:
151 | if (! self::$_enableColors) {
152 | return $text . $nl;
153 | }
154 |
155 | /*
156 | * Black 0;30 Dark Gray 1;30
157 | * Red 0;31 Light Red 1;31
158 | * Green 0;32 Light Green 1;32
159 | * Brown 0;33 Yellow 1;33
160 | * Blue 0;34 Light Blue 1;34
161 | * Purple 0;35 Light Purple 1;35
162 | * Cyan 0;36 Light Cyan 1;36
163 | * Light Gray 0;37 White 1;37
164 | *
165 | * Format:
166 | * \x1b[[light];[color];[font]m
167 | * light: 1/0
168 | * color: 30-37
169 | * font: 1 - bold, 3 - italic, 4 - underline, 7 - invert, 9 - strikethrough
170 | *
171 | * https://misc.flogisoft.com/bash/tip_colors_and_formatting
172 | */
173 |
174 | $optionsMap = array(
175 | 'key' => "\x1b[32m",
176 | 'access' => "\x1b[3m",
177 | 'header' => "\x1b[38;5;75m",
178 | 'type' => "\x1b[1m",
179 | 'value' => "\x1b[31m",
180 | );
181 |
182 | return $optionsMap[$type] . $text . "\x1b[0m" . $nl;
183 | case Sage::MODE_TEXT_ONLY:
184 | default:
185 | return $text . $nl;
186 | }
187 | }
188 |
189 | private function title($text)
190 | {
191 | $escaped = SageHelper::esc($text);
192 | $lengthDifference = strlen($escaped) - strlen($text);
193 |
194 | $ret = '┌──────────────────────────────────────────────────────────────────────────────┐' . PHP_EOL;
195 | if ($text) {
196 | $ret .= '│' . str_pad($escaped, 78 + $lengthDifference, ' ', STR_PAD_BOTH) . '│' . PHP_EOL;
197 | }
198 | $ret .= '└──────────────────────────────────────────────────────────────────────────────┘';
199 |
200 | return $this->colorize($ret, 'header');
201 | }
202 |
203 | public function wrapStart()
204 | {
205 | if (Sage::enabled() === Sage::MODE_PLAIN) {
206 | return '';
207 | }
208 |
209 | return '';
210 | }
211 |
212 | public function wrapEnd($callee, $miniTrace, $prevCaller)
213 | {
214 | $lastLine = '════════════════════════════════════════════════════════════════════════════════';
215 | $lastChar = Sage::enabled() === Sage::MODE_PLAIN ? ' ' : '';
216 | $traceDisplay = '';
217 |
218 | if (! Sage::$displayCalledFrom) {
219 | return $this->colorize($lastLine . $lastChar, 'header');
220 | }
221 |
222 | if (! empty($miniTrace)) {
223 | $traceDisplay = PHP_EOL;
224 | $i = 0;
225 | foreach ($miniTrace as $step) {
226 | $traceDisplay .= ' ' . ($i + 2) . '. ';
227 | $traceDisplay .= SageHelper::ideLink($step['file'], $step['line']);
228 | $traceDisplay .= PHP_EOL;
229 | if ($i++ > 2) {
230 | break;
231 | }
232 | }
233 | $traceDisplay .= '';
234 | }
235 |
236 | return $this->colorize(
237 | $lastLine . PHP_EOL
238 | . 'Call stack ' . SageHelper::ideLink($callee['file'], $callee['line'])
239 | . $traceDisplay,
240 | 'header'
241 | )
242 | . $lastChar;
243 | }
244 |
245 | private function drawHeader(SageVariableData $varData)
246 | {
247 | $output = '';
248 |
249 | if ($varData->access) {
250 | $output .= ' ' . $this->colorize(SageHelper::esc($varData->access), 'access', false);
251 | }
252 |
253 | if ($varData->name !== null && $varData->name !== '') {
254 | $output .= ' ' . $this->colorize(SageHelper::esc($varData->name), 'key', false);
255 | }
256 |
257 | if ($varData->operator) {
258 | $output .= ' ' . $varData->operator;
259 | }
260 |
261 | $type = $varData->type;
262 | if ($varData->size !== null) {
263 | $type .= ' (' . $varData->size . ')';
264 | }
265 |
266 | $output .= ' ' . $this->colorize($type, 'type', false);
267 |
268 | if ($varData->value !== null && $varData->value !== '') {
269 | $output .= ' ' . $this->colorize($varData->value, 'value', false);
270 | }
271 |
272 | return ltrim($output);
273 | }
274 |
275 | public function init()
276 | {
277 | if (! Sage::$cliColors) {
278 | self::$_enableColors = false;
279 | } elseif (isset($_SERVER['NO_COLOR']) || getenv('NO_COLOR') !== false) {
280 | self::$_enableColors = false;
281 | } elseif (getenv('TERM_PROGRAM') === 'Hyper') {
282 | self::$_enableColors = true;
283 | } elseif (DIRECTORY_SEPARATOR === '\\') {
284 | self::$_enableColors =
285 | function_exists('sapi_windows_vt100_support')
286 | || getenv('ANSICON') !== false
287 | || getenv('ConEmuANSI') === 'ON'
288 | || getenv('TERM') === 'xterm';
289 | } else {
290 | self::$_enableColors = true;
291 | }
292 |
293 | if (Sage::enabled() !== Sage::MODE_PLAIN) {
294 | return '';
295 | }
296 |
297 | return ''
298 | . '';
299 | }
300 |
301 | private function drawTraceStep($stepNumber, $step, $pathsOnly, $lastStepNumber)
302 | {
303 | $output = '';
304 |
305 | // ASCII art 🎨
306 | $_________________ = '────────────────────────────────────────────────────────────────────────────────';
307 | $____Arguments____ = ' ┌────────────────────────── Arguments ─────────────────────────────────┐';
308 | $__Callee_Object__ = ' ┌───────────────────────── Callee Object ──────────────────────────────┐';
309 | $L________________ = ' └──────────────────────────────────────────────────────────────────────┘';
310 | $_________________ = $this->colorize($_________________, 'header');
311 | $____Arguments____ = $this->colorize($____Arguments____, 'header');
312 | $__Callee_Object__ = $this->colorize($__Callee_Object__, 'header');
313 | $L________________ = $this->colorize($L________________, 'header');
314 |
315 | $output .= str_pad($stepNumber++ . ': ', 4, ' ');
316 | $output .= $this->colorize($step->fileLine, 'header');
317 |
318 | if ($step->functionName) {
319 | $output .= ' ' . $step->functionName;
320 | $output .= PHP_EOL;
321 | }
322 |
323 | if (! $pathsOnly && $step->arguments) {
324 | $output .= $____Arguments____;
325 |
326 | foreach ($step->arguments as $argument) {
327 | $output .= $this->decorate($argument, 2);
328 | }
329 |
330 | $output .= $L________________;
331 | }
332 |
333 | if (! $pathsOnly && $step->object) {
334 | $output .= $__Callee_Object__;
335 |
336 | $output .= $this->decorate($step->object, 2);
337 |
338 | $output .= $L________________;
339 | }
340 |
341 | if ($stepNumber !== $lastStepNumber) {
342 | $output .= $_________________;
343 | }
344 |
345 | return $output;
346 | }
347 | }
348 |
--------------------------------------------------------------------------------
/decorators/SageDecoratorsRich.php:
--------------------------------------------------------------------------------
1 | ';
23 |
24 | $allRepresentations = $varData->getAllRepresentations();
25 | $extendedPresent = ! empty($allRepresentations);
26 |
27 | if ($extendedPresent) {
28 | $class = '_sage-parent';
29 | if (Sage::$expandedByDefault) {
30 | $class .= ' _sage-show';
31 | }
32 | $output .= '';
33 | } else {
34 | $output .= '';
35 | }
36 |
37 | if ($extendedPresent) {
38 | $output .= ' ';
39 | }
40 |
41 | $output .= $this->_drawHeader($varData) . $varData->value . " ";
42 |
43 | if ($extendedPresent) {
44 | $output .= ' ';
45 | }
46 |
47 | if (count($allRepresentations) === 1 && ! empty($varData->extendedValue)) {
48 | $extendedValue = reset($allRepresentations);
49 | $output .= $this->decorateAlternativeView($extendedValue);
50 | } elseif ($extendedPresent) {
51 | $output .= "";
52 |
53 | $isFirst = true;
54 | foreach ($allRepresentations as $tabName => $_) {
55 | $active = $isFirst ? ' class="_sage-active-tab"' : '';
56 | $isFirst = false;
57 | $output .= "" . SageHelper::esc($tabName) . ' ';
58 | }
59 |
60 | $output .= ' ';
61 |
62 | foreach ($allRepresentations as $alternative) {
63 | $output .= '';
64 | $output .= $this->decorateAlternativeView($alternative);
65 | $output .= ' ';
66 | }
67 |
68 | $output .= ' ';
69 | }
70 | if ($extendedPresent) {
71 | $output .= ' ';
72 | }
73 |
74 | $output .= "\n";
75 |
76 | return $output;
77 | }
78 |
79 | /** @param SageTraceStep[] $traceData */
80 | public function decorateTrace(array $traceData, $pathsOnly = false)
81 | {
82 | $output = '';
83 |
84 | $blacklistedStepsInARow = 0;
85 | foreach ($traceData as $stepNumber => $step) {
86 | if (
87 | $stepNumber >= Sage::$minimumTraceStepsToShowFull
88 | && $step->isBlackListed
89 | ) {
90 | $blacklistedStepsInARow++;
91 | continue;
92 | }
93 |
94 | if ($blacklistedStepsInARow) {
95 | if ($blacklistedStepsInARow <= 5) {
96 | for ($j = $blacklistedStepsInARow; $j > 0; $j--) {
97 | $output .= $this->drawTraceStep($stepNumber - $j, $traceData[$stepNumber - $j], $pathsOnly);
98 | }
99 | } else {
100 | $output .= " [{$blacklistedStepsInARow} steps skipped] ";
101 | }
102 |
103 | $blacklistedStepsInARow = 0;
104 | }
105 |
106 | $output .= $this->drawTraceStep($stepNumber, $step, $pathsOnly);
107 | }
108 |
109 | if ($blacklistedStepsInARow > 1) {
110 | $output .= " [{$blacklistedStepsInARow} steps skipped] ";
111 | }
112 |
113 | $output .= ' ';
114 |
115 | return $output;
116 | }
117 |
118 | private function drawTraceStep($i, $step, $pathsOnly)
119 | {
120 | $isChildless = ! $step->sourceSnippet && ! $step->arguments && ! $step->object;
121 |
122 | $class = '';
123 |
124 | if ($step->isBlackListed) {
125 | $class .= ' _sage-blacklisted';
126 | } elseif ($isChildless) {
127 | $class .= ' _sage-childless';
128 | } else {
129 | $class .= '_sage-parent';
130 |
131 | if (Sage::$expandedByDefault) {
132 | $class .= ' _sage-show';
133 | }
134 | }
135 |
136 | $output = $class ? '' : '';
137 | $output .= '' . ($i + 1) . ' ';
138 | if (! $isChildless) {
139 | $output .= ' ';
140 | }
141 | $output .= '' . $step->fileLine . ' ';
142 | $output .= $step->functionName;
143 | $output .= ' ';
144 |
145 | if ($isChildless) {
146 | return $output;
147 | }
148 |
149 | $output .= ' ';
150 | $firstTabClass = ' class="_sage-active-tab"';
151 |
152 | if ($step->sourceSnippet) {
153 | $output .= "Source ";
154 | $firstTabClass = '';
155 | }
156 |
157 | if (! $pathsOnly && $step->arguments) {
158 | $output .= "Arguments ";
159 | $firstTabClass = '';
160 | }
161 |
162 | if (! $pathsOnly && $step->object) {
163 | $output .= "Callee object [{$step->object->type}] ";
164 | }
165 |
166 | $output .= ' ';
167 |
168 | if ($step->sourceSnippet) {
169 | $output .= "{$step->sourceSnippet} ";
170 | }
171 |
172 | if (! $pathsOnly && $step->arguments) {
173 | $output .= '';
174 | foreach ($step->arguments as $argument) {
175 | $output .= $this->decorate($argument);
176 | }
177 | $output .= ' ';
178 | }
179 |
180 | if (! $pathsOnly && $step->object) {
181 | $output .= '' . $this->decorate($step->object) . ' ';
182 | }
183 |
184 | $output .= ' ';
185 |
186 | return $output;
187 | }
188 |
189 | /**
190 | * called for each dump, opens the html tag
191 | *
192 | * @return string
193 | */
194 | public function wrapStart()
195 | {
196 | return "";
197 | }
198 |
199 | /**
200 | * closes Sage::_wrapStart() started html tags and displays callee information
201 | *
202 | * @param array $callee caller information taken from debug backtrace
203 | * @param array $miniTrace full path to Sage call
204 | * @param array $prevCaller previous caller information taken from debug backtrace
205 | *
206 | * @return string
207 | */
208 | public function wrapEnd($callee, $miniTrace, $prevCaller)
209 | {
210 | if (! Sage::$displayCalledFrom) {
211 | return '
';
212 | }
213 |
214 | $callingFunction = '';
215 | $calleeInfo = '';
216 | $traceDisplay = '';
217 | if (isset($prevCaller['class'])) {
218 | $callingFunction = $prevCaller['class'];
219 | }
220 | if (isset($prevCaller['type'])) {
221 | $callingFunction .= $prevCaller['type'];
222 | }
223 | if (isset($prevCaller['function'])
224 | && ! in_array($prevCaller['function'], array('include', 'include_once', 'require', 'require_once'))
225 | ) {
226 | $callingFunction .= $prevCaller['function'] . '()';
227 | }
228 | $callingFunction and $callingFunction = " [{$callingFunction}]";
229 |
230 | if (isset($callee['file'])) {
231 | $calleeInfo .= 'Called from ' . SageHelper::ideLink($callee['file'], $callee['line']);
232 | }
233 |
234 | if (! empty($miniTrace)) {
235 | $traceDisplay = '';
236 | foreach ($miniTrace as $step) {
237 | $traceDisplay .= '' . SageHelper::ideLink($step['file'], $step['line']); // closing tag not required
238 | if (isset($step['function'])
239 | && ! in_array($step['function'], array('include', 'include_once', 'require', 'require_once'))
240 | ) {
241 | $classString = ' [';
242 | if (isset($step['class'])) {
243 | $classString .= $step['class'];
244 | }
245 | if (isset($step['type'])) {
246 | $classString .= $step['type'];
247 | }
248 | $classString .= $step['function'] . '()]';
249 | $traceDisplay .= $classString;
250 | }
251 | }
252 | $traceDisplay .= ' ';
253 |
254 | $calleeInfo = ' ' . $calleeInfo;
255 | }
256 |
257 | $callingFunction .= ' @ ' . date('Y-m-d H:i:s');
258 |
259 | return ''
260 | . ' '
261 | . "{$calleeInfo}{$callingFunction}{$traceDisplay}"
262 | . ' ';
263 | }
264 |
265 | private function _drawHeader(SageVariableData $varData)
266 | {
267 | $output = '';
268 | if ($varData->access !== null) {
269 | $output .= "{$varData->access} ";
270 | }
271 |
272 | if ($varData->name !== null && $varData->name !== '') {
273 | $output .= '' . SageHelper::esc($varData->name) . ' ';
274 | }
275 |
276 | if ($varData->operator !== null) {
277 | $output .= $varData->operator . ' ';
278 | }
279 |
280 | if ($varData->type !== null) {
281 | // tyoe output is unescaped as it is set internally and contains links to user class
282 | $output .= "{$varData->type} ";
283 | }
284 |
285 | if ($varData->size !== null) {
286 | $output .= '(' . $varData->size . ') ';
287 | }
288 |
289 | return $output;
290 | }
291 |
292 | /**
293 | * produces css and js required for display. May be called multiple times, will only produce output once per
294 | * pageload or until `-` or `@` modifier is used
295 | *
296 | * @return string
297 | */
298 | public function init()
299 | {
300 | $baseDir = SAGE_DIR . 'resources/compiled/';
301 |
302 | if (! is_readable($cssFile = $baseDir . Sage::$theme . '.css')) {
303 | $cssFile = $baseDir . 'original.css';
304 | }
305 |
306 | return
307 | ''
308 | . '\n";
309 | }
310 |
311 | private function decorateAlternativeView($alternative)
312 | {
313 | if (empty($alternative)) {
314 | return '';
315 | }
316 |
317 | $output = '';
318 | if (is_array($alternative)) {
319 | // we either get a prepared array of SageVariableData or a raw array of anything
320 | $parse = reset($alternative) instanceof SageVariableData
321 | ? $alternative
322 | : SageParser::process($alternative)->extendedValue; // convert into SageVariableData[]
323 |
324 | foreach ($parse as $v) {
325 | $output .= $this->decorate($v);
326 | }
327 | } elseif (is_string($alternative)) {
328 | // the browser does not render leading new line in
329 | if ($alternative[0] === "\n" || $alternative[0] === "\r") {
330 | $alternative = "\n" . $alternative;
331 | }
332 | $output .= "{$alternative} ";
333 | }
334 |
335 | return $output;
336 | }
337 |
338 | }
339 |
--------------------------------------------------------------------------------
/inc/SageHelper.php:
--------------------------------------------------------------------------------
1 | 'subl://open?url=file://%file&line=%line',
14 | 'textmate' => 'txmt://open?url=file://%file&line=%line',
15 | 'emacs' => 'emacs://open?url=file://%file&line=%line',
16 | 'macvim' => 'mvim://open/?url=file://%file&line=%line',
17 | 'phpstorm' => 'phpstorm://open?file=%file&line=%line',
18 | 'phpstorm-remote' => 'http://localhost:63342/api/file/%file:%line',
19 | 'idea' => 'idea://open?file=%file&line=%line',
20 | 'vscode' => 'vscode://file/%file:%line',
21 | 'vscode-insiders' => 'vscode-insiders://file/%file:%line',
22 | 'vscode-remote' => 'vscode://vscode-remote/%file:%line',
23 | 'vscode-insiders-remote' => 'vscode-insiders://vscode-remote/%file:%line',
24 | 'vscodium' => 'vscodium://file/%file:%line',
25 | 'atom' => 'atom://core/open/file?filename=%file&line=%line',
26 | 'nova' => 'nova://core/open/file?filename=%file&line=%line',
27 | 'netbeans' => 'netbeans://open/?f=%file:%line',
28 | 'xdebug' => 'xdebug://%file@%line'
29 | );
30 |
31 | private static $aliasesRaw;
32 | private static $projectRootDir;
33 |
34 | public static function php53orLater()
35 | {
36 | if (! isset(self::$_php53)) {
37 | self::$_php53 = version_compare(PHP_VERSION, '5.3.0') > 0;
38 | }
39 |
40 | return self::$_php53;
41 | }
42 |
43 | public static function isRichMode()
44 | {
45 | return Sage::enabled() === Sage::MODE_RICH;
46 | }
47 |
48 | public static function isHtmlMode()
49 | {
50 | $enabledMode = Sage::enabled();
51 |
52 | return $enabledMode === Sage::MODE_RICH || $enabledMode === Sage::MODE_PLAIN;
53 | }
54 |
55 | /**
56 | * generic path display callback, can be configured in the settings; purpose is to show relevant path info and hide
57 | * as much of the path as possible.
58 | *
59 | * @param string $file
60 | *
61 | * @return string
62 | */
63 | public static function shortenPath($file)
64 | {
65 | $file = str_replace('\\', '/', $file);
66 |
67 | if (self::$projectRootDir && strpos($file, self::$projectRootDir) === 0) {
68 | return substr($file, strlen(self::$projectRootDir));
69 | }
70 |
71 | return $file;
72 | }
73 |
74 | public static function buildAliases()
75 | {
76 | self::$aliasesRaw = array(
77 | 'methods' => array(
78 | array('sage', 'dump'),
79 | array('sage', 'doDump'),
80 | array('sage', 'trace')
81 | ),
82 | 'functions' => array()
83 | );
84 |
85 | foreach (Sage::$aliases as $alias) {
86 | $alias = strtolower($alias);
87 |
88 | if (strpos($alias, '::') !== false) {
89 | self::$aliasesRaw['methods'][] = explode('::', $alias);
90 | } else {
91 | self::$aliasesRaw['functions'][] = $alias;
92 | }
93 | }
94 | }
95 |
96 | public static function detectProjectRoot($calledFromFile)
97 | {
98 | // Find common path with Sage dir
99 | self::$projectRootDir = '';
100 |
101 | $sagePathParts = explode('/', str_replace('\\', '/', SAGE_DIR));
102 | $filePathParts = explode('/', $calledFromFile);
103 | foreach ($filePathParts as $i => $filePart) {
104 | if (! isset($sagePathParts[$i]) || $sagePathParts[$i] !== $filePart) {
105 | break;
106 | }
107 |
108 | self::$projectRootDir .= $filePart . '/';
109 | }
110 | }
111 |
112 | /**
113 | * returns whether current trace step belongs to Sage or its wrappers
114 | *
115 | * @param $step
116 | *
117 | * @return bool
118 | */
119 | public static function stepIsInternal($step)
120 | {
121 | if (isset($step['class'])) {
122 | foreach (self::$aliasesRaw['methods'] as $alias) {
123 | if ($alias[0] === strtolower($step['class']) && $alias[1] === strtolower($step['function'])) {
124 | return true;
125 | }
126 | }
127 |
128 | return false;
129 | }
130 |
131 | return in_array(strtolower($step['function']), self::$aliasesRaw['functions'], true);
132 | }
133 |
134 | public static function isKeyBlacklisted($key)
135 | {
136 | return in_array(preg_replace('/\W/', '', $key), Sage::$keysBlacklist, true);
137 | }
138 |
139 | public static function substr($string, $start, $end, $encoding = null)
140 | {
141 | if (! isset($string)) {
142 | return '';
143 | }
144 |
145 | if (function_exists('mb_substr')) {
146 | $encoding or $encoding = self::detectEncoding($string);
147 |
148 | return mb_substr($string, $start, $end, $encoding);
149 | }
150 |
151 | return substr($string, $start, $end);
152 | }
153 |
154 | /**
155 | * returns whether the array:
156 | * 1) is numeric and
157 | * 2) in sequence starting from zero
158 | *
159 | * @param array $array
160 | *
161 | * @return bool
162 | */
163 | public static function isArraySequential(array $array)
164 | {
165 | $keys = array_keys($array);
166 |
167 | return array_keys($keys) === $keys;
168 | }
169 |
170 | public static function detectEncoding($value)
171 | {
172 | if (function_exists('mb_detect_encoding')) {
173 | $mbDetected = mb_detect_encoding($value);
174 | if ($mbDetected === 'ASCII') {
175 | return 'UTF-8';
176 | }
177 | }
178 |
179 | if (! function_exists('iconv')) {
180 | return ! empty($mbDetected) ? $mbDetected : 'UTF-8';
181 | }
182 |
183 | $md5 = md5($value);
184 | foreach (Sage::$charEncodings as $encoding) {
185 | // f*#! knows why, //IGNORE and //TRANSLIT still throw notice
186 | if (md5(@iconv($encoding, $encoding, $value)) === $md5) {
187 | return $encoding;
188 | }
189 | }
190 |
191 | return 'UTF-8';
192 | }
193 |
194 | public static function strlen($string, $encoding = null)
195 | {
196 | if (function_exists('mb_strlen')) {
197 | $encoding or $encoding = self::detectEncoding($string);
198 |
199 | return mb_strlen($string, $encoding);
200 | }
201 |
202 | return strlen($string);
203 | }
204 |
205 | public static function ideLink($file, $line, $linkText = null)
206 | {
207 | $enabledMode = Sage::enabled();
208 | $file = self::shortenPath($file);
209 |
210 | $fileLine = $file;
211 | // in some cases (like called from inside template) we don't know the $line
212 | // it's then passed here as null, in that case don't display it in the link text, but keep :0 in the
213 | // url so that the IDE protocols don't break.
214 | if ($line) {
215 | $fileLine .= ':' . $line;
216 | } else {
217 | $line = 0;
218 | }
219 |
220 | if (! self::isHtmlMode()) {
221 | return $fileLine;
222 | }
223 |
224 | $linkText = $linkText
225 | ? $linkText
226 | : $fileLine;
227 | $linkText = self::esc($linkText);
228 |
229 | if (! Sage::$editor) {
230 | return $linkText;
231 | }
232 |
233 | $ideLink = str_replace(
234 | array('%file', '%line', Sage::$fileLinkServerPath),
235 | array($file, $line, Sage::$fileLinkLocalPath),
236 | isset(self::$editors[Sage::$editor]) ? self::$editors[Sage::$editor] : Sage::$editor
237 | );
238 |
239 | if ($enabledMode === Sage::MODE_RICH) {
240 | $class = (strpos($ideLink, 'http://') === 0) ? ' class="_sage-ide-link" ' : ' ';
241 |
242 | return "{$linkText} ";
243 | }
244 |
245 | // MODE_PLAIN
246 | if (strpos($ideLink, 'http://') === 0) {
247 | return <<{$linkText}
249 | HTML;
250 | }
251 |
252 | return "{$linkText} ";
253 | }
254 |
255 | public static function esc($value, $decode = true)
256 | {
257 | $value = self::isHtmlMode()
258 | ? htmlspecialchars($value, ENT_NOQUOTES, 'UTF-8')
259 | : $value;
260 |
261 | if ($decode) {
262 | $value = self::decodeStr($value);
263 | }
264 |
265 | return $value;
266 | }
267 |
268 | /**
269 | * Make all invisible characters visible. HTML-escape if needed.
270 | */
271 | private static function decodeStr($value)
272 | {
273 | if (is_int($value)) {
274 | return (string)$value;
275 | }
276 |
277 | if ($value === '') {
278 | return '';
279 | }
280 |
281 | if (self::isHtmlMode()) {
282 | if (htmlspecialchars($value, ENT_NOQUOTES, 'UTF-8') === '') {
283 | return '‹binary data›';
284 | }
285 |
286 | $controlCharsMap = array(
287 | "\v" => '\v ',
288 | "\f" => '\f ',
289 | "\033" => '\e ',
290 | "\t" => "\t\\t ",
291 | "\r\n" => "\\r\\n \n",
292 | "\n" => "\\n \n",
293 | "\r" => "\\r "
294 | );
295 | $replaceTemplate = '‹0x%d› ';
296 | } else {
297 | $controlCharsMap = array(
298 | "\v" => '\v',
299 | "\f" => '\f',
300 | "\033" => '\e',
301 | );
302 | $replaceTemplate = '\x%02X';
303 | }
304 |
305 | $out = '';
306 | $i = 0;
307 | do {
308 | $character = $value[$i];
309 | $ord = ord($character);
310 | // escape all invisible characters except \t, \n and \r - ORD 9, 10 and 13 respectively
311 | if ($ord < 32 && $ord !== 9 && $ord !== 10 && $ord !== 13) {
312 | if (isset($controlCharsMap[$character])) {
313 | $out .= $controlCharsMap[$character];
314 | } else {
315 | $out .= sprintf($replaceTemplate, $ord);
316 | }
317 | } else {
318 | $out .= $character;
319 | }
320 | } while (isset($value[++$i]));
321 |
322 | return $out;
323 | }
324 | }
325 |
--------------------------------------------------------------------------------
/inc/SageParser.php:
--------------------------------------------------------------------------------
1 | */
10 | private static $_objects;
11 | /** @var string */
12 | private static $_marker;
13 |
14 | private static $parsingAlternative = array();
15 |
16 | private static $_placeFullStringInValue = false;
17 |
18 | public static function reset()
19 | {
20 | self::$_level = 0;
21 | self::$_objects = self::$_marker = null;
22 | }
23 |
24 | final public static function process(&$variable, $name = null)
25 | {
26 | // save internal data to revert after dumping to properly handle recursions etc
27 | $revert = array(
28 | 'level' => self::$_level,
29 | 'objects' => self::$_objects,
30 | );
31 |
32 | self::$_level++;
33 |
34 | // set name
35 | $varData = new SageVariableData();
36 | if (isset($name)) {
37 | $varData->name = $name;
38 |
39 | if (strlen($varData->name) > 60) {
40 | $varData->name =
41 | SageHelper::substr($varData->name, 0, 27)
42 | . '...'
43 | . SageHelper::substr($varData->name, -28, null);
44 | }
45 | }
46 |
47 | // first go through alternative parsers (eg.: json detection)
48 | foreach (Sage::$enabledParsers as $parserClass => $enabled) {
49 | if (! $enabled || array_key_exists($parserClass, self::$parsingAlternative)) {
50 | continue;
51 | }
52 | self::$parsingAlternative[$parserClass] = true;
53 | $parser = new $parserClass();
54 | $parseResult = $parser->parse($variable, $varData);
55 |
56 | // if var was parsed by "can only be one"-parser - return here
57 | if ($parseResult !== false && $parser->replacesAllOtherParsers()) {
58 | unset(self::$parsingAlternative[$parserClass]);
59 | self::$_level = $revert['level'];
60 | self::$_objects = $revert['objects'];
61 |
62 | return $varData;
63 | }
64 | unset(self::$parsingAlternative[$parserClass]);
65 | }
66 |
67 | // parse the variable based on its type
68 | $varType = gettype($variable);
69 | $varType === 'unknown type' and $varType = 'unknown'; // PHP 5.4 inconsistency
70 | $methodName = '_parse_' . $varType;
71 | if (! method_exists(__CLASS__, $methodName)) {
72 | $varData->type = $varType; // resource (closed) for example
73 |
74 | return $varData;
75 | }
76 | // base type parser returning false means "stop processing further": e.g. recursion
77 | if (self::$methodName($variable, $varData) === false) {
78 | self::$_level--;
79 |
80 | return $varData;
81 | }
82 |
83 | self::$_level = $revert['level'];
84 | self::$_objects = $revert['objects'];
85 |
86 | return $varData;
87 | }
88 |
89 | private static function isDepthLimit()
90 | {
91 | return Sage::$maxLevels && self::$_level >= Sage::$maxLevels;
92 | }
93 |
94 | /**
95 | * @return array|false return ALL keys from all rows if array is tabular, false otherwise
96 | */
97 | private static function _isArrayTabular(array $variable)
98 | {
99 | if (Sage::enabled() !== Sage::MODE_RICH) {
100 | return false;
101 | }
102 |
103 | $arrayKeys = array();
104 | $keys = null;
105 | $closeEnough = false;
106 | foreach ($variable as $k => $row) {
107 | if (isset(self::$_marker) && $k === self::$_marker) {
108 | continue;
109 | }
110 |
111 | if (! is_array($row) || empty($row)) {
112 | return false;
113 | }
114 |
115 | foreach ($row as $col) {
116 | if (! empty($col) && ! is_scalar($col)) {
117 | return false;
118 | } // todo add tabular "tolerance"
119 | }
120 |
121 | if (isset($keys) && ! $closeEnough) {
122 | // let's just see if the first two rows have same keys, that's faster and has the
123 | // positive side effect of easily spotting missing keys in later rows
124 | if ($keys !== array_keys($row)) {
125 | return false;
126 | }
127 |
128 | $closeEnough = true;
129 | } else {
130 | $keys = array_keys($row);
131 | }
132 |
133 | $arrayKeys = array_unique(array_merge($arrayKeys, $keys));
134 | }
135 |
136 | return $arrayKeys;
137 | }
138 |
139 | private static function _decorateCell(SageVariableData $varData)
140 | {
141 | if ($varData->extendedValue !== null) {
142 | return '' . SageDecoratorsRich::decorate($varData) . ' ';
143 | }
144 |
145 | $output = 'value !== null) {
148 | $output .= ' title="' . $varData->type;
149 |
150 | if ($varData->size !== null) {
151 | $output .= ' (' . $varData->size . ')';
152 | }
153 |
154 | $output .= '">' . $varData->value;
155 | } else {
156 | $output .= '>';
157 |
158 | if ($varData->type !== 'NULL') {
159 | $output .= '' . $varData->type;
160 |
161 | if ($varData->size !== null) {
162 | $output .= '(' . $varData->size . ')';
163 | }
164 |
165 | $output .= ' ';
166 | } else {
167 | $output .= 'NULL ';
168 | }
169 | }
170 |
171 | return $output . ' ';
172 | }
173 |
174 | private static $_dealingWithGlobals = false;
175 |
176 | private static function _parse_array(&$variable, SageVariableData $variableData)
177 | {
178 | isset(self::$_marker) or self::$_marker = "\x00" . uniqid();
179 |
180 | // naturally, $GLOBALS variable is an intertwined recursion nightmare, use black magic
181 | $globalsDetector = false;
182 | if (array_key_exists('GLOBALS', $variable) && is_array($variable['GLOBALS'])) {
183 | $globalsDetector = "\x01" . uniqid();
184 |
185 | $variable['GLOBALS'][$globalsDetector] = true;
186 | if (isset($variable[$globalsDetector])) {
187 | unset($variable[$globalsDetector]);
188 | self::$_dealingWithGlobals = true;
189 | } else {
190 | unset($variable['GLOBALS'][$globalsDetector]);
191 | $globalsDetector = false;
192 | }
193 | }
194 |
195 | $variableData->type = 'array';
196 | $variableData->size = count($variable);
197 |
198 | if ($variableData->size === 0) {
199 | return;
200 | }
201 | if (isset($variable[self::$_marker])) { // recursion; todo mayhaps show from where
202 | if (self::$_dealingWithGlobals) {
203 | $variableData->value = '*RECURSION*';
204 | } else {
205 | unset($variable[self::$_marker]);
206 | $variableData->value = self::$_marker;
207 | }
208 |
209 | return false;
210 | }
211 | if (self::isDepthLimit()) {
212 | $variableData->extendedValue = '*DEPTH TOO GREAT*';
213 |
214 | return false;
215 | }
216 |
217 | $isSequential = SageHelper::isArraySequential($variable);
218 | $variable[self::$_marker] = true;
219 |
220 | if ($variableData->size > 1 && ($arrayKeys = self::_isArrayTabular($variable)) !== false) {
221 | // tabular array parse
222 | $firstRow = true;
223 | $extendedValue = ' ';
224 |
225 | foreach ($variable as $rowIndex => & $row) {
226 | // display strings in their full length
227 | self::$_placeFullStringInValue = true;
228 |
229 | if ($rowIndex === self::$_marker) {
230 | continue;
231 | }
232 |
233 | if (isset($row[self::$_marker])) {
234 | $variableData->value = '*RECURSION*';
235 |
236 | return false;
237 | }
238 |
239 | $extendedValue .= '';
240 | if ($isSequential) {
241 | $output = '' . (((int)$rowIndex) + 1) . ' ';
242 | } else {
243 | $output = self::_decorateCell(self::process($rowIndex));
244 | }
245 | if ($firstRow) {
246 | $extendedValue .= ' ';
247 | }
248 |
249 | // we iterate the known full set of keys from all rows in case some appeared at later rows,
250 | // as we only check the first two to assume
251 | foreach ($arrayKeys as $key) {
252 | if ($firstRow) {
253 | $extendedValue .= '' . SageHelper::esc($key) . ' ';
254 | }
255 |
256 | if (SageHelper::isKeyBlacklisted($key)) {
257 | $output .= '*REDACTED* ';
258 | continue;
259 | }
260 |
261 | if (! array_key_exists($key, $row)) {
262 | $output .= ' ';
263 | continue;
264 | }
265 |
266 | $var = self::process($row[$key]);
267 |
268 | if ($var->value === self::$_marker) {
269 | $variableData->value = '*RECURSION*';
270 |
271 | return false;
272 | } elseif ($var->value === '*RECURSION*') {
273 | $output .= '*RECURSION* ';
274 | } else {
275 | $output .= self::_decorateCell($var);
276 | }
277 | unset($var);
278 | }
279 |
280 | if ($firstRow) {
281 | $extendedValue .= ' ';
282 | $firstRow = false;
283 | }
284 |
285 | $extendedValue .= $output . ' ';
286 | }
287 | self::$_placeFullStringInValue = false;
288 |
289 | $variableData->extendedValue = $extendedValue . '
';
290 | } else {
291 | $extendedValue = array();
292 |
293 | foreach ($variable as $key => & $val) {
294 | if ($key === self::$_marker) {
295 | continue;
296 | }
297 |
298 | if (in_array($key, Sage::$keysBlacklist, true)) {
299 | $val = '*REDACTED*';
300 | }
301 |
302 | $output = self::process($val);
303 | if ($output->value === self::$_marker) {
304 | // recursion occurred on a higher level, thus $variableData is recursion
305 | $variableData->value = '*RECURSION*';
306 |
307 | return false;
308 | }
309 | if ($isSequential) {
310 | $output->name = null;
311 | } else {
312 | $output->operator = '=>';
313 | $output->name = is_int($key)
314 | ? $key
315 | : "'" . $key . "'";
316 | }
317 | $extendedValue[] = $output;
318 | }
319 | $variableData->extendedValue = $extendedValue;
320 | }
321 |
322 | if ($globalsDetector) {
323 | self::$_dealingWithGlobals = false;
324 | }
325 |
326 | unset($variable[self::$_marker]);
327 | }
328 |
329 | private static function _parse_object(&$variable, SageVariableData $variableData)
330 | {
331 | $hash = self::getObjectHash($variable);
332 |
333 | $castedArray = (array)$variable;
334 | $className = get_class($variable);
335 | $variableData->type = $className;
336 | $variableData->size = count($castedArray);
337 |
338 | if (isset(self::$_objects[$hash])) {
339 | $variableData->value = '*RECURSION*';
340 |
341 | return false;
342 | }
343 | if (self::isDepthLimit()) {
344 | $variableData->extendedValue = '*DEPTH TOO GREAT*';
345 |
346 | return false;
347 | }
348 |
349 | // ArrayObject (and maybe ArrayIterator, did not try yet) unsurprisingly consist of mainly dark magic.
350 | // What bothers me most, var_dump sees no problem with it, and ArrayObject also uses a custom,
351 | // undocumented serialize function, so you can see the properties in internal functions, but
352 | // can never iterate some of them if the flags are not STD_PROP_LIST. Fun stuff.
353 | if ($variableData->type === 'ArrayObject' || is_subclass_of($variable, 'ArrayObject')) {
354 | $arrayObjectFlags = $variable->getFlags();
355 | $variable->setFlags(ArrayObject::STD_PROP_LIST);
356 | }
357 |
358 | if (strpos($variableData->type, "@anonymous\0") !== false) {
359 | $variableData->type = 'Anonymous class';
360 | }
361 |
362 | self::$_objects[$hash] = true;
363 | $variableReflection = new ReflectionObject($variable);
364 |
365 | // add link to definition of userland objects
366 | if (SageHelper::isHtmlMode() && $variableReflection->isUserDefined()) {
367 | $variableData->type = SageHelper::ideLink(
368 | $variableReflection->getFileName(),
369 | $variableReflection->getStartLine(),
370 | $variableData->type
371 | );
372 | }
373 | $variableData->size = 0;
374 |
375 | $extendedValue = array();
376 | static $publicProperties = array();
377 | if (! isset($publicProperties[$className])) {
378 | $reflectionClass = new ReflectionClass($className);
379 | foreach ($reflectionClass->getProperties(ReflectionProperty::IS_PUBLIC) as $prop) {
380 | $publicProperties[$className][$prop->name] = true;
381 | }
382 | }
383 |
384 | // copy the object as an array as it provides more info than Reflection (depends)
385 | foreach ($castedArray as $key => $value) {
386 | /* casting object to array:
387 | * integer properties are inaccessible;
388 | * private variables have the class name prepended to the variable name;
389 | * protected variables have a '*' prepended to the variable name.
390 | * These prepended values have null bytes on either side.
391 | * http://www.php.net/manual/en/language.types.array.php#language.types.array.casting
392 | */
393 | if (is_string($key) && $key[0] === "\x00") {
394 | $access = $key[1] === '*' ? 'protected' : 'private';
395 |
396 | // Remove the access level from the variable name
397 | $key = substr($key, strrpos($key, "\x00") + 1);
398 | } else {
399 | $access = 'public';
400 |
401 | if ($variableData->type !== 'stdClass' && ! isset($publicProperties[$className][$key])) {
402 | $access .= ' (dynamically added)';
403 | }
404 | }
405 |
406 | if (SageHelper::isKeyBlacklisted($key)) {
407 | $value = '*REDACTED*';
408 | }
409 |
410 | $output = self::process($value);
411 | $output->name = SageHelper::esc($key);
412 | $output->access = $access;
413 | $output->operator = '->';
414 |
415 | $extendedValue[$key] = $output;
416 |
417 | $variableData->size++;
418 | }
419 |
420 | if ($variable instanceof __PHP_Incomplete_Class) {
421 | $variableData->extendedValue = $extendedValue;
422 |
423 | return $castedArray;
424 | }
425 |
426 | foreach ($variableReflection->getProperties() as $property) {
427 | if ($property->isStatic()) {
428 | continue;
429 | }
430 |
431 | $name = $property->getName();
432 | if (isset($extendedValue[$name])) {
433 | if (method_exists($property, 'isReadOnly') && $property->isReadOnly()) {
434 | $extendedValue[$name]->access .= ' readonly';
435 | }
436 |
437 | continue;
438 | }
439 |
440 | if ($property->isProtected()) {
441 | $property->setAccessible(true);
442 | $access = 'protected';
443 | } elseif ($property->isPrivate()) {
444 | $property->setAccessible(true);
445 | $access = 'private';
446 | } else {
447 | $access = 'public';
448 | }
449 |
450 | if (method_exists($property, 'isInitialized')
451 | && ! $property->isInitialized($variable)) {
452 | $value = null;
453 | $access .= ' [uninitialized]';
454 | } else {
455 | $value = $property->getValue($variable);
456 | }
457 |
458 | $output = self::process($value, SageHelper::esc($name));
459 |
460 | $output->access = $access;
461 | $output->operator = '->';
462 | $extendedValue[] = $output;
463 | $variableData->size++;
464 | }
465 |
466 | if (isset($arrayObjectFlags)) {
467 | $variable->setFlags($arrayObjectFlags);
468 | }
469 |
470 | if (method_exists($variableReflection, 'isEnum') && $variableReflection->isEnum()) {
471 | $variableData->size = 'enum';
472 | $variableData->value = '"' . $variable->name . '"';
473 | }
474 |
475 | if ($variableData->size) {
476 | $variableData->extendedValue = $extendedValue;
477 | }
478 | }
479 |
480 | private static function _parse_boolean(&$variable, SageVariableData $variableData)
481 | {
482 | $variableData->type = 'bool';
483 | $variableData->value = $variable ? 'TRUE' : 'FALSE';
484 | }
485 |
486 | private static function _parse_double(&$variable, SageVariableData $variableData)
487 | {
488 | $variableData->type = 'float';
489 | $variableData->value = $variable;
490 | }
491 |
492 | private static function _parse_integer(&$variable, SageVariableData $variableData)
493 | {
494 | $variableData->type = 'integer';
495 | $variableData->value = $variable;
496 | }
497 |
498 | private static function _parse_null(&$variable, SageVariableData $variableData)
499 | {
500 | $variableData->type = 'NULL';
501 | }
502 |
503 | private static function _parse_resource(&$variable, SageVariableData $variableData)
504 | {
505 | $resourceType = get_resource_type($variable);
506 | $variableData->type = "resource ({$resourceType})";
507 |
508 | if ($resourceType === 'stream' && $meta = stream_get_meta_data($variable)) {
509 | if (isset($meta['uri'])) {
510 | $file = $meta['uri'];
511 |
512 | if (function_exists('stream_is_local')) {
513 | // Only exists on PHP >= 5.2.4
514 | if (stream_is_local($file)) {
515 | $file = SageHelper::shortenPath($file);
516 | }
517 | }
518 |
519 | $variableData->value = $file;
520 | }
521 | }
522 | }
523 |
524 | private static function _parse_string(&$variable, SageVariableData $variableData)
525 | {
526 | if (preg_match('//u', $variable)) {
527 | $variableData->type = 'string';
528 | } else {
529 | $variableData->type .= 'binary string';
530 | }
531 |
532 | $encoding = SageHelper::detectEncoding($variable);
533 | if ($encoding !== 'UTF-8') {
534 | $variableData->type .= ' ' . $encoding;
535 | }
536 |
537 | $variableData->size = SageHelper::strlen($variable, $encoding);
538 |
539 | if (self::$_placeFullStringInValue) { // in tabular view
540 | $variableData->value = SageHelper::esc($variable);
541 | } elseif (! SageHelper::isRichMode()) {
542 | $variableData->value = '"' . SageHelper::esc($variable) . '"';
543 | } else {
544 | $decoded = SageHelper::esc($variable);
545 |
546 | // trim inline value if too long
547 | if ($variableData->size > (SageHelper::MAX_STR_LENGTH + 8)) {
548 | $variableData->value =
549 | '"'
550 | . SageHelper::esc(
551 | SageHelper::substr($variable, 0, SageHelper::MAX_STR_LENGTH, $encoding),
552 | false
553 | )
554 | . '…"';
555 | } else {
556 | $variableData->value = '"' . SageHelper::esc($variable, false) . '"';
557 | }
558 |
559 | // detect invisible characters
560 | if ($variable !== preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x80-\x9F]/u', '', $variable)) {
561 | $variableData->extendedValue = SageHelper::esc($variable, false);
562 | $variableData->addTabToView($variable, 'Hidden characters escaped', $decoded);
563 | } elseif ($variableData->size > (SageHelper::MAX_STR_LENGTH + 8)
564 | || $variable !== preg_replace(
565 | '/\s+/',
566 | ' ',
567 | $variable
568 | )) {
569 | $variableData->extendedValue = $decoded;
570 | }
571 | }
572 | }
573 |
574 | private static function _parse_unknown(&$variable, SageVariableData $variableData)
575 | {
576 | $type = gettype($variable);
577 | $variableData->type = 'UNKNOWN' . (! empty($type) ? " ({$type})" : '');
578 | $variableData->value = var_export($variable, true);
579 | }
580 |
581 | private static function getObjectHash($variable)
582 | {
583 | if (function_exists('spl_object_hash')) { // since PHP 5.2
584 | return spl_object_hash($variable);
585 | }
586 |
587 | ob_start();
588 | var_dump($variable);
589 | preg_match('[#(\d+)]', ob_get_clean(), $match);
590 |
591 | return $match[1];
592 | }
593 |
594 | public static function alternativesParse($originalVar, $alternativesArray)
595 | {
596 | $varData = new SageVariableData();
597 |
598 | // if the alternatives contain references to the object itself it's an ∞ loop
599 | if (is_object($originalVar)) {
600 | self::$_objects[self::getObjectHash($originalVar)] = true;
601 | } elseif (is_array($originalVar)) {
602 | isset(self::$_marker) or self::$_marker = "\x00" . uniqid();
603 |
604 | $originalVar[self::$_marker] = true;
605 | }
606 |
607 | if (is_array($alternativesArray)) {
608 | self::_parse_array($alternativesArray, $varData);
609 | } else {
610 | self::_parse_object($alternativesArray, $varData);
611 | }
612 |
613 | return $varData->extendedValue;
614 | }
615 | }
616 |
--------------------------------------------------------------------------------
/inc/SageTraceStep.php:
--------------------------------------------------------------------------------
1 | fileLine = $this->getFileAndLine($step);
20 | $this->argumentNames = $this->getStepArgumentNames($step);
21 | $this->functionName = $this->getStepFunctionName($step, $this->argumentNames);
22 |
23 | if ($this->isStepBlacklisted($step, $stepNumber)) {
24 | $this->isBlackListed = true;
25 |
26 | return;
27 | }
28 |
29 | // todo it's possible to parse the object name out from the source!!!
30 | $this->object = $this->getObject($step);
31 | $this->sourceSnippet = $this->getSourceSnippet($step);
32 | $this->arguments = $this->getArguments($step, $this->argumentNames);
33 | }
34 |
35 | private function isStepBlacklisted($step, $stepNumber)
36 | {
37 | if (! Sage::$maxLevels) {
38 | return false;
39 | }
40 |
41 | if (! isset($step['file'])) {
42 | return false;
43 | }
44 |
45 | foreach (Sage::$traceBlacklist as $blacklistedPath) {
46 | if (preg_match($blacklistedPath, $step['file'])) {
47 | return true;
48 | }
49 | }
50 |
51 | return false;
52 | }
53 |
54 | private function getFileAndLine($step)
55 | {
56 | if (! isset($step['file'])) {
57 | return 'PHP internal call';
58 | }
59 |
60 | return SageHelper::ideLink($step['file'], $step['line']);
61 | }
62 |
63 | private function getStepArgumentNames($step)
64 | {
65 | if (empty($step['args']) || empty($step['function'])) {
66 | return array();
67 | }
68 |
69 | $function = $step['function'];
70 | if (in_array($function, array('include', 'include_once', 'require', 'require_once'))) {
71 | return array('');
72 | }
73 |
74 | $reflection = null;
75 |
76 | if (isset($step['class'])) {
77 | if (method_exists($step['class'], $function)) {
78 | $reflection = new ReflectionMethod($step['class'], $function);
79 | }
80 | } elseif (function_exists($function)) {
81 | $reflection = new ReflectionFunction($function);
82 | }
83 |
84 | $params = $reflection ? $reflection->getParameters() : null;
85 |
86 | $names = array();
87 | foreach ($step['args'] as $i => $arg) {
88 | if (isset($params[$i])) {
89 | $names[] = '$' . $params[$i]->name;
90 | } else {
91 | $names[] = '#' . ($i + 1);
92 | }
93 | }
94 |
95 | return $names;
96 | }
97 |
98 | private function getStepFunctionName($step, $functionNames)
99 | {
100 | if (empty($step['function'])) {
101 | return '';
102 | }
103 |
104 | $function = $step['function'];
105 | if ($function && isset($step['class'])) {
106 | $function = $step['class'] . $step['type'] . $function;
107 | }
108 |
109 | return $function . '(' . implode(', ', $functionNames) . ')';
110 | }
111 |
112 | private function getObject($step)
113 | {
114 | if (! isset($step['object'])) {
115 | return null;
116 | }
117 |
118 | return SageParser::process($step['object']);
119 | }
120 |
121 | private function getSourceSnippet($step)
122 | {
123 | if (
124 | empty($step['file'])
125 | || ! isset($step['line'])
126 | || Sage::enabled() !== Sage::MODE_RICH
127 | || ! is_readable($step['file'])
128 | ) {
129 | return null;
130 | }
131 |
132 | // open the file and set the line position
133 | $file = fopen($step['file'], 'r');
134 | $line = $step['line'];
135 | $readingLine = 0;
136 |
137 | // Set the reading range
138 | $range = array(
139 | 'start' => $line - 7,
140 | 'end' => $line + 7,
141 | );
142 |
143 | // set the zero-padding amount for line numbers
144 | $format = '% ' . strlen($range['end']) . 'd';
145 |
146 | $source = '';
147 | while (($row = fgets($file)) !== false) {
148 | // increment the line number
149 | if (++$readingLine > $range['end']) {
150 | break;
151 | }
152 |
153 | if ($readingLine >= $range['start']) {
154 | $row = SageHelper::esc($row);
155 |
156 | $row = '' . sprintf($format, $readingLine) . ' ' . $row;
157 |
158 | if ($readingLine === (int)$line) {
159 | // apply highlighting to this row
160 | $row = '' . $row . '
';
161 | } else {
162 | $row = '' . $row . '
';
163 | }
164 |
165 | $source .= $row;
166 | }
167 | }
168 |
169 | fclose($file);
170 |
171 | return $source;
172 | }
173 |
174 | private function getArguments($step, $argumentNames)
175 | {
176 | $result = array();
177 | foreach ($this->getRawArguments($step) as $k => $variable) {
178 | $name = isset($argumentNames[$k]) ? $argumentNames[$k] : '';
179 | if (SageHelper::isKeyBlacklisted($name)) {
180 | $variable = '*REDACTED*';
181 | }
182 |
183 | $parsed = SageParser::process($variable, $argumentNames[$k]);
184 | $parsed->operator = substr($name, 0, 1) === '$' ? '=' : ':';
185 | $result[] = $parsed;
186 | }
187 |
188 | return $result;
189 | }
190 |
191 | private function getRawArguments($step)
192 | {
193 | if (
194 | ! empty($step['args'])
195 | && in_array($step['function'], array('include', 'include_once', 'require', 'require_once'), true)
196 | ) {
197 | // sanitize the included file path
198 | return array(SageHelper::shortenPath($step['args'][0]));
199 | }
200 |
201 | return isset($step['args']) ? $step['args'] : array();
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/inc/SageVariableData.php:
--------------------------------------------------------------------------------
1 | alternativeRepresentations[$tabName] = $value;
48 | }
49 |
50 | public function getAllRepresentations()
51 | {
52 | # if alternative displays exist, push extendedValue to their front and display it as one of alternatives
53 | $prepared = array();
54 |
55 | if (! empty($this->extendedValue)) {
56 | $prepared['Contents'] = $this->extendedValue;
57 | }
58 | if (! empty($this->alternativeRepresentations)) {
59 | $prepared = array_merge($prepared, $this->alternativeRepresentations);
60 | }
61 |
62 | return $prepared;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/inc/eloquentListener.inc.php:
--------------------------------------------------------------------------------
1 | $queryNumber++, 'sql' => $query->sql, 'bindings' => $query->bindings);
10 | Sage::dump($EloquentQuery);
11 |
12 | Sage::saveState($state);
13 | });
14 |
--------------------------------------------------------------------------------
/inc/shorthands.inc.php:
--------------------------------------------------------------------------------
1 | html->plain) |
35 | * | - | Clean up any output before dumping |
36 | * | + | Expand all nodes (in rich view) |
37 | * | @ | Return output instead of displaying it |
38 | *
39 | */
40 |
41 | if (! function_exists('sage')) {
42 | /**
43 | * Alias of Sage::dump()
44 | *
45 | * @return string|int
46 | *
47 | * @see Sage::dump()
48 | */
49 | function sage()
50 | {
51 | if (! Sage::enabled()) {
52 | return 5463;
53 | }
54 |
55 | Sage::$aliases[] = __FUNCTION__;
56 |
57 | $params = func_get_args();
58 |
59 | return call_user_func_array(array('Sage', 'dump'), $params);
60 | }
61 | }
62 |
63 | if (! function_exists('s')) {
64 | /**
65 | * Alias of Sage::dump()
66 | *
67 | * @return string|int
68 | *
69 | * @see Sage::dump()
70 | */
71 | function s()
72 | {
73 | if (! Sage::enabled()) {
74 | return 5463;
75 | }
76 |
77 | Sage::$aliases[] = __FUNCTION__;
78 |
79 | $params = func_get_args();
80 |
81 | return call_user_func_array(array('Sage', 'dump'), $params);
82 | }
83 | }
84 |
85 | if (! function_exists('saged')) {
86 | /**
87 | * Alias of Sage::dump(); die;
88 | *
89 | * @return never [!!!] IMPORTANT: execution will halt after call to this function
90 | */
91 | function saged()
92 | {
93 | if (! Sage::enabled()) {
94 | return 5463;
95 | }
96 |
97 | Sage::$aliases[] = __FUNCTION__;
98 |
99 | $params = func_get_args();
100 | call_user_func_array(array('Sage', 'dump'), $params);
101 | die;
102 | }
103 | }
104 |
105 | if (! function_exists('sd')) {
106 | /**
107 | * Alias of Sage::dump(); die;
108 | *
109 | * [!!!] IMPORTANT: execution will halt after call to this function
110 | *
111 | * @return string|int @see Sage::dump()
112 | */
113 | function sd()
114 | {
115 | if (! Sage::enabled()) {
116 | return 5463;
117 | }
118 |
119 | Sage::$aliases[] = __FUNCTION__;
120 |
121 | $params = func_get_args();
122 | call_user_func_array(array('Sage', 'dump'), $params);
123 | die;
124 | }
125 | }
126 |
127 | if (! function_exists('ssage')) {
128 | /**
129 | * Alias of Sage::dump(), however the output is in plain htmlescaped text and some minor visibility enhancements
130 | * added. If run in CLI mode, output is pure whitespace.
131 | *
132 | * To force rendering mode without autodetecting anything:
133 | *
134 | * Sage::enabled( Sage::MODE_PLAIN );
135 | * Sage::dump( $variable );
136 | *
137 | * @return string|int @see Sage::dump()
138 | */
139 | function ssage()
140 | {
141 | if (! Sage::enabled()) {
142 | return 5463;
143 | }
144 |
145 | $simplify = Sage::$simplifyDisplay;
146 | Sage::$simplifyDisplay = true;
147 | Sage::$aliases[] = __FUNCTION__;
148 |
149 | $params = func_get_args();
150 | $dump = call_user_func_array(array('Sage', 'dump'), $params);
151 | Sage::$simplifyDisplay = $simplify;
152 |
153 | return $dump;
154 | }
155 | }
156 |
157 | if (! function_exists('ss')) {
158 | /**
159 | * Alias of Sage::dump(), however the output is in plain htmlescaped text and some minor visibility enhancements
160 | * added. If run in CLI mode, output is pure whitespace.
161 | *
162 | * To force rendering mode without autodetecting anything:
163 | *
164 | * Sage::enabled( Sage::MODE_PLAIN );
165 | * Sage::dump( $variable );
166 | *
167 | * @return string|int @see Sage::dump()
168 | */
169 | function ss()
170 | {
171 | if (! Sage::enabled()) {
172 | return 5463;
173 | }
174 |
175 | $simplify = Sage::$simplifyDisplay;
176 | Sage::$simplifyDisplay = true;
177 | Sage::$aliases[] = __FUNCTION__;
178 |
179 | $params = func_get_args();
180 | $dump = call_user_func_array(array('Sage', 'dump'), $params);
181 | Sage::$simplifyDisplay = $simplify;
182 |
183 | return $dump;
184 | }
185 | }
186 |
187 | if (! function_exists('ssaged')) {
188 | /**
189 | * @return string|int @see Sage::dump
190 | * @return never [!!!] IMPORTANT: execution will halt after call to this function
191 | * @see s()
192 | */
193 | function ssaged()
194 | {
195 | if (! Sage::enabled()) {
196 | return 5463;
197 | }
198 |
199 | Sage::$simplifyDisplay = true;
200 | Sage::$aliases[] = __FUNCTION__;
201 | $params = func_get_args();
202 | call_user_func_array(array('Sage', 'dump'), $params);
203 | die;
204 | }
205 | }
206 |
207 | if (! function_exists('ssd')) {
208 | /**
209 | * @return string|int @see Sage::dump
210 | * @return never [!!!] IMPORTANT: execution will halt after call to this function
211 | * @see s()
212 | */
213 | function ssd()
214 | {
215 | if (! Sage::enabled()) {
216 | return 5463;
217 | }
218 |
219 | Sage::$simplifyDisplay = true;
220 | Sage::$aliases[] = __FUNCTION__;
221 | $params = func_get_args();
222 | call_user_func_array(array('Sage', 'dump'), $params);
223 | die;
224 | }
225 | }
226 |
227 | if (! function_exists('d')) {
228 | /**
229 | * Alias of Sage::dump()
230 | *
231 | * Same as sage(), here just to allow drop-in replacement for Kint.
232 | *
233 | * @return string|int @see Sage::dump()
234 | */
235 | function d()
236 | {
237 | if (! Sage::enabled()) {
238 | return 5463;
239 | }
240 |
241 | Sage::$aliases[] = __FUNCTION__;
242 |
243 | $params = func_get_args();
244 |
245 | return call_user_func_array(array('Sage', 'dump'), $params);
246 | }
247 | }
248 |
249 | if (! function_exists('sagetrace')) {
250 | /**
251 | * Alias of Sage::dump()
252 | *
253 | * Same as sage(), here just to allow drop-in replacement for Kint.
254 | *
255 | * @return string|int @see Sage::dump()
256 | */
257 | function sagetrace()
258 | {
259 | if (! Sage::enabled()) {
260 | return 5463;
261 | }
262 |
263 | Sage::$aliases[] = __FUNCTION__;
264 |
265 | return Sage::trace();
266 | }
267 | }
268 |
--------------------------------------------------------------------------------
/parsers/SageParserInterface.php:
--------------------------------------------------------------------------------
1 | type = get_class($variable) . ' [skipped, dump object in top level to see contents]';
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/parsers/SageParsersClassName.php:
--------------------------------------------------------------------------------
1 | isUserDefined()) {
37 | return false;
38 | }
39 |
40 | if (SageHelper::isRichMode()) {
41 | $varData->addTabToView(
42 | $variable,
43 | 'Existing class',
44 | SageHelper::ideLink(
45 | $reflector->getFileName(),
46 | $reflector->getStartLine(),
47 | $reflector->getShortName()
48 | )
49 | );
50 | } else {
51 | if (SageHelper::isHtmlMode()) {
52 | $varData->extendedValue =
53 | array(
54 | 'Existing class' => SageHelper::ideLink(
55 | $reflector->getFileName(),
56 | $reflector->getStartLine(),
57 | $reflector->getShortName()
58 | )
59 | );
60 | } else {
61 | $varData->extendedValue =
62 | array('Existing class' => $reflector->getFileName() . ':' . $reflector->getStartLine());
63 | }
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/parsers/SageParsersClassStatics.php:
--------------------------------------------------------------------------------
1 | getProperties(ReflectionProperty::IS_STATIC) as $property) {
25 | if ($property->isProtected()) {
26 | $property->setAccessible(true);
27 | $access = 'protected';
28 | } elseif ($property->isPrivate()) {
29 | $property->setAccessible(true);
30 | $access = 'private';
31 | } else {
32 | $access = 'public';
33 | }
34 |
35 | if (method_exists($property, 'isInitialized') && ! $property->isInitialized($variable)) {
36 | $value = null;
37 | $access .= ' [uninitialized]';
38 | } else {
39 | $value = $property->getValue($variable);
40 | }
41 |
42 | $name = '$' . $property->getName();
43 | $output = SageParser::process($value, SageHelper::esc($name));
44 |
45 | $output->access = $access;
46 | $output->operator = '::';
47 | $statics[] = $output;
48 | }
49 |
50 | if (empty($statics)) {
51 | return false;
52 | }
53 |
54 | $varData->addTabToView(
55 | $variable,
56 | 'Static class properties (' . count($statics) . ')',
57 | $statics
58 | );
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/parsers/SageParsersClosure.php:
--------------------------------------------------------------------------------
1 | type = 'Closure';
21 | $reflection = new ReflectionFunction($variable);
22 |
23 | $parameters = array();
24 | foreach ($reflection->getParameters() as $parameter) {
25 | $parameters = $parameter->name;
26 | }
27 | if (! empty($parameters)) {
28 | $varData->addTabToView($variable, 'Closure Parameters', $parameters);
29 | }
30 |
31 | $uses = array();
32 | if ($val = $reflection->getStaticVariables()) {
33 | $uses = $val;
34 | }
35 | if (method_exists($reflection, 'getClousureThis') && $val = $reflection->getClosureThis()) {
36 | $uses[] = SageParser::process($val, 'Closure $this');
37 | }
38 | if (! empty($uses)) {
39 | $varData->addTabToView($variable, 'Closure Parameters', $uses);
40 | }
41 |
42 | if ($reflection->getFileName()) {
43 | $varData->value = SageHelper::ideLink($reflection->getFileName(), $reflection->getStartLine());
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/parsers/SageParsersColor.php:
--------------------------------------------------------------------------------
1 | '#f0f8ff',
10 | 'antiquewhite' => '#faebd7',
11 | 'aqua' => '#00ffff',
12 | 'aquamarine' => '#7fffd4',
13 | 'azure' => '#f0ffff',
14 | 'beige' => '#f5f5dc',
15 | 'bisque' => '#ffe4c4',
16 | 'black' => '#000000',
17 | 'blanchedalmond' => '#ffebcd',
18 | 'blue' => '#0000ff',
19 | 'blueviolet' => '#8a2be2',
20 | 'brown' => '#a52a2a',
21 | 'burlywood' => '#deb887',
22 | 'cadetblue' => '#5f9ea0',
23 | 'chartreuse' => '#7fff00',
24 | 'chocolate' => '#d2691e',
25 | 'coral' => '#ff7f50',
26 | 'cornflowerblue' => '#6495ed',
27 | 'cornsilk' => '#fff8dc',
28 | 'crimson' => '#dc143c',
29 | 'cyan' => '#00ffff',
30 | 'darkblue' => '#00008b',
31 | 'darkcyan' => '#008b8b',
32 | 'darkgoldenrod' => '#b8860b',
33 | 'darkgray' => '#a9a9a9',
34 | 'darkgrey' => '#a9a9a9',
35 | 'darkgreen' => '#006400',
36 | 'darkkhaki' => '#bdb76b',
37 | 'darkmagenta' => '#8b008b',
38 | 'darkolivegreen' => '#556b2f',
39 | 'darkorange' => '#ff8c00',
40 | 'darkorchid' => '#9932cc',
41 | 'darkred' => '#8b0000',
42 | 'darksalmon' => '#e9967a',
43 | 'darkseagreen' => '#8fbc8f',
44 | 'darkslateblue' => '#483d8b',
45 | 'darkslategray' => '#2f4f4f',
46 | 'darkslategrey' => '#2f4f4f',
47 | 'darkturquoise' => '#00ced1',
48 | 'darkviolet' => '#9400d3',
49 | 'deeppink' => '#ff1493',
50 | 'deepskyblue' => '#00bfff',
51 | 'dimgray' => '#696969',
52 | 'dimgrey' => '#696969',
53 | 'dodgerblue' => '#1e90ff',
54 | 'firebrick' => '#b22222',
55 | 'floralwhite' => '#fffaf0',
56 | 'forestgreen' => '#228b22',
57 | 'fuchsia' => '#ff00ff',
58 | 'gainsboro' => '#dcdcdc',
59 | 'ghostwhite' => '#f8f8ff',
60 | 'gold' => '#ffd700',
61 | 'goldenrod' => '#daa520',
62 | 'gray' => '#808080',
63 | 'grey' => '#808080',
64 | 'green' => '#008000',
65 | 'greenyellow' => '#adff2f',
66 | 'honeydew' => '#f0fff0',
67 | 'hotpink' => '#ff69b4',
68 | 'indianred' => '#cd5c5c',
69 | 'indigo' => '#4b0082',
70 | 'ivory' => '#fffff0',
71 | 'khaki' => '#f0e68c',
72 | 'lavender' => '#e6e6fa',
73 | 'lavenderblush' => '#fff0f5',
74 | 'lawngreen' => '#7cfc00',
75 | 'lemonchiffon' => '#fffacd',
76 | 'lightblue' => '#add8e6',
77 | 'lightcoral' => '#f08080',
78 | 'lightcyan' => '#e0ffff',
79 | 'lightgoldenrodyellow' => '#fafad2',
80 | 'lightgray' => '#d3d3d3',
81 | 'lightgrey' => '#d3d3d3',
82 | 'lightgreen' => '#90ee90',
83 | 'lightpink' => '#ffb6c1',
84 | 'lightsalmon' => '#ffa07a',
85 | 'lightseagreen' => '#20b2aa',
86 | 'lightskyblue' => '#87cefa',
87 | 'lightslategray' => '#778899',
88 | 'lightslategrey' => '#778899',
89 | 'lightsteelblue' => '#b0c4de',
90 | 'lightyellow' => '#ffffe0',
91 | 'lime' => '#00ff00',
92 | 'limegreen' => '#32cd32',
93 | 'linen' => '#faf0e6',
94 | 'magenta' => '#ff00ff',
95 | 'maroon' => '#800000',
96 | 'mediumaquamarine' => '#66cdaa',
97 | 'mediumblue' => '#0000cd',
98 | 'mediumorchid' => '#ba55d3',
99 | 'mediumpurple' => '#9370d8',
100 | 'mediumseagreen' => '#3cb371',
101 | 'mediumslateblue' => '#7b68ee',
102 | 'mediumspringgreen' => '#00fa9a',
103 | 'mediumturquoise' => '#48d1cc',
104 | 'mediumvioletred' => '#c71585',
105 | 'midnightblue' => '#191970',
106 | 'mintcream' => '#f5fffa',
107 | 'mistyrose' => '#ffe4e1',
108 | 'moccasin' => '#ffe4b5',
109 | 'navajowhite' => '#ffdead',
110 | 'navy' => '#000080',
111 | 'oldlace' => '#fdf5e6',
112 | 'olive' => '#808000',
113 | 'olivedrab' => '#6b8e23',
114 | 'orange' => '#ffa500',
115 | 'orangered' => '#ff4500',
116 | 'orchid' => '#da70d6',
117 | 'palegoldenrod' => '#eee8aa',
118 | 'palegreen' => '#98fb98',
119 | 'paleturquoise' => '#afeeee',
120 | 'palevioletred' => '#d87093',
121 | 'papayawhip' => '#ffefd5',
122 | 'peachpuff' => '#ffdab9',
123 | 'peru' => '#cd853f',
124 | 'pink' => '#ffc0cb',
125 | 'plum' => '#dda0dd',
126 | 'powderblue' => '#b0e0e6',
127 | 'purple' => '#800080',
128 | 'red' => '#ff0000',
129 | 'rosybrown' => '#bc8f8f',
130 | 'royalblue' => '#4169e1',
131 | 'saddlebrown' => '#8b4513',
132 | 'salmon' => '#fa8072',
133 | 'sandybrown' => '#f4a460',
134 | 'seagreen' => '#2e8b57',
135 | 'seashell' => '#fff5ee',
136 | 'sienna' => '#a0522d',
137 | 'silver' => '#c0c0c0',
138 | 'skyblue' => '#87ceeb',
139 | 'slateblue' => '#6a5acd',
140 | 'slategray' => '#708090',
141 | 'slategrey' => '#708090',
142 | 'snow' => '#fffafa',
143 | 'springgreen' => '#00ff7f',
144 | 'steelblue' => '#4682b4',
145 | 'tan' => '#d2b48c',
146 | 'teal' => '#008080',
147 | 'thistle' => '#d8bfd8',
148 | 'tomato' => '#ff6347',
149 | 'turquoise' => '#40e0d0',
150 | 'violet' => '#ee82ee',
151 | 'wheat' => '#f5deb3',
152 | 'white' => '#ffffff',
153 | 'whitesmoke' => '#f5f5f5',
154 | 'yellow' => '#ffff00',
155 | 'yellowgreen' => '#9acd32'
156 | );
157 |
158 | public function replacesAllOtherParsers()
159 | {
160 | return false;
161 | }
162 |
163 | public function parse(&$variable, $varData)
164 | {
165 | if (! $this->_fits($variable)) {
166 | return false;
167 | }
168 |
169 | // todo after we migrate from less:
170 | // $originalVarData->name .= "{$variable}
";
171 |
172 | $variants = $this->_convert($variable);
173 | $value = <<{$variable}hex: {$variants['hex']}
175 | rgb: {$variants['rgb']}
176 | hsl: {$variants['hsl']}
177 | HTML;
178 | if (array_key_exists('name', $variants)) {
179 | $value .= PHP_EOL . "css: {$variants['name']}";
180 | }
181 |
182 | $varData->addTabToView($variable, 'CSS color', $value);
183 | }
184 |
185 | private function _fits($variable)
186 | {
187 | if (! SageHelper::isRichMode()) {
188 | return false;
189 | }
190 |
191 | if (! is_string($variable)) {
192 | return false;
193 | }
194 |
195 | if (strlen($variable) > 32) {
196 | return false;
197 | }
198 |
199 | $var = strtolower(trim($variable));
200 |
201 | return isset(self::$colorNames[$var])
202 | || preg_match(
203 | '/^(?:#[0-9A-Fa-f]{3}|#[0-9A-Fa-f]{6}|(?:rgb|hsl)a?\s*\((?:\s*[0-9.%]+\s*,?){3,4}\))$/',
204 | $var
205 | );
206 | }
207 |
208 | private function _convert($color)
209 | {
210 | $color = strtolower($color);
211 | $decimalColors = array();
212 | $variants = array(
213 | 'hex' => null,
214 | 'rgb' => null,
215 | 'name' => null,
216 | 'hsl' => null,
217 | );
218 |
219 | if (isset(self::$colorNames[$color])) {
220 | $variants['name'] = $color;
221 | $color = self::$colorNames[$color];
222 | }
223 |
224 | if ($color[0] === '#') {
225 | $variants['hex'] = $color;
226 | $color = substr($color, 1);
227 | if (strlen($color) === 6) {
228 | $colors = str_split($color, 2);
229 | } else {
230 | $colors = array(
231 | $color[0] . $color[0],
232 | $color[1] . $color[1],
233 | $color[2] . $color[2],
234 | );
235 | }
236 |
237 | $decimalColors = array_map('hexdec', $colors);
238 | } elseif (substr($color, 0, 3) === 'rgb') {
239 | $variants['rgb'] = $color;
240 | preg_match_all('#([0-9.%]+)#', $color, $matches);
241 | $decimalColors = $matches[1];
242 | foreach ($decimalColors as &$color) {
243 | if (strpos($color, '%') !== false) {
244 | $color = str_replace('%', '', $color) * 2.55;
245 | }
246 | }
247 | } elseif (substr($color, 0, 3) === 'hsl') {
248 | $variants['hsl'] = $color;
249 | preg_match_all('#([0-9.%]+)#', $color, $matches);
250 |
251 | $colors = $matches[1];
252 | $colors[0] /= 360;
253 | $colors[1] = str_replace('%', '', $colors[1]) / 100;
254 | $colors[2] = str_replace('%', '', $colors[2]) / 100;
255 |
256 | $decimalColors = $this->_HSLtoRGB($colors);
257 | if (isset($colors[3])) {
258 | $decimalColors[] = $colors[3];
259 | }
260 | }
261 |
262 | if (isset($decimalColors[3])) {
263 | $alpha = $decimalColors[3];
264 | unset($decimalColors[3]);
265 | } else {
266 | $alpha = null;
267 | }
268 | foreach ($variants as $type => &$variant) {
269 | if (isset($variant)) {
270 | continue;
271 | }
272 |
273 | switch ($type) {
274 | case 'hex':
275 | $variant = '#';
276 | foreach ($decimalColors as &$color) {
277 | $variant .= str_pad(dechex($color), 2, '0', STR_PAD_LEFT);
278 | }
279 | $variant .= isset($alpha) ? ' (alpha omitted)' : '';
280 | break;
281 | case 'rgb':
282 | $rgb = $decimalColors;
283 | if (isset($alpha)) {
284 | $rgb[] = $alpha;
285 | $a = 'a';
286 | } else {
287 | $a = '';
288 | }
289 | $variant = "rgb{$a}( " . implode(', ', $rgb) . ' )';
290 | break;
291 | case 'hsl':
292 | $rgb = $this->_RGBtoHSL($decimalColors);
293 | if ($rgb === null) {
294 | unset($variants[$type]);
295 | break;
296 | }
297 | if (isset($alpha)) {
298 | $rgb[] = $alpha;
299 | $a = 'a';
300 | } else {
301 | $a = '';
302 | }
303 |
304 | $variant = "hsl{$a}( " . implode(', ', $rgb) . ' )';
305 | break;
306 | case 'name':
307 | // [!] name in initial variants array must go after hex
308 | if (($key = array_search($variants['hex'], self::$colorNames, true)) !== false) {
309 | $variant = $key;
310 | } else {
311 | unset($variants[$type]);
312 | }
313 | break;
314 | }
315 | }
316 |
317 | return $variants;
318 | }
319 |
320 | private function _HSLtoRGB(array $hsl)
321 | {
322 | list($h, $s, $l) = $hsl;
323 | $m2 = ($l <= 0.5) ? $l * ($s + 1) : $l + $s - $l * $s;
324 | $m1 = $l * 2 - $m2;
325 |
326 | return array(
327 | round($this->_hue2rgb($m1, $m2, $h + 0.33333) * 255),
328 | round($this->_hue2rgb($m1, $m2, $h) * 255),
329 | round($this->_hue2rgb($m1, $m2, $h - 0.33333) * 255),
330 | );
331 | }
332 |
333 | /**
334 | * Helper function for _color_hsl2rgb().
335 | */
336 | private function _hue2rgb($m1, $m2, $h)
337 | {
338 | $h = ($h < 0) ? $h + 1 : (($h > 1) ? $h - 1 : $h);
339 | if ($h * 6 < 1) {
340 | return $m1 + ($m2 - $m1) * $h * 6;
341 | }
342 | if ($h * 2 < 1) {
343 | return $m2;
344 | }
345 | if ($h * 3 < 2) {
346 | return $m1 + ($m2 - $m1) * (0.66666 - $h) * 6;
347 | }
348 |
349 | return $m1;
350 | }
351 |
352 | private function _RGBtoHSL(array $rgb)
353 | {
354 | list($clrR, $clrG, $clrB) = $rgb;
355 |
356 | $clrMin = min($clrR, $clrG, $clrB);
357 | $clrMax = max($clrR, $clrG, $clrB);
358 | $deltaMax = $clrMax - $clrMin;
359 |
360 | $L = ($clrMax + $clrMin) / 510;
361 |
362 | if (0 == $deltaMax) {
363 | $H = 0;
364 | $S = 0;
365 | } else {
366 | if (0.5 > $L) {
367 | $S = $deltaMax / ($clrMax + $clrMin);
368 | } else {
369 | $S = $deltaMax / (510 - $clrMax - $clrMin);
370 | }
371 |
372 | if ($clrMax == $clrR) {
373 | $H = ($clrG - $clrB) / (6.0 * $deltaMax);
374 | } elseif ($clrMax == $clrG) {
375 | $H = 1 / 3 + ($clrB - $clrR) / (6.0 * $deltaMax);
376 | } else {
377 | $H = 2 / 3 + ($clrR - $clrG) / (6.0 * $deltaMax);
378 | }
379 |
380 | if (0 > $H) {
381 | $H += 1;
382 | }
383 | if (1 < $H) {
384 | $H -= 1;
385 | }
386 | }
387 |
388 | return array(
389 | round($H * 360),
390 | round($S * 100) . '%',
391 | round($L * 100) . '%'
392 | );
393 | }
394 | }
395 |
--------------------------------------------------------------------------------
/parsers/SageParsersDateTime.php:
--------------------------------------------------------------------------------
1 | format('u');
22 | if (rtrim($ms, '0')) {
23 | $format .= '.' . $ms;
24 | } else {
25 | $format .= '.0';
26 | }
27 |
28 | if ($variable->getTimezone()->getLocation()) {
29 | $format .= ' e';
30 | }
31 | $format .= ' (P)';
32 |
33 | $varData->value = $variable->format($format);
34 | $varData->type = get_class($variable);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/parsers/SageParsersEloquent.php:
--------------------------------------------------------------------------------
1 | getProperty('attributes');
22 | $attrReflecion->setAccessible(true);
23 | $attributes = $attrReflecion->getValue($variable);
24 |
25 | $reference = '`' . $variable->getConnection()->getDatabaseName() . '`.`' . $variable->getTable() . '`';
26 |
27 | $varData->size = count($attributes);
28 | if (SageHelper::isRichMode()) {
29 | $varData->type = $reflection->getName();
30 | $varData->addTabToView($variable, 'data from ' . $reference, $attributes);
31 | } else {
32 | $varData->type = $reflection->getName() . '; ' . $reference . ' row data:';
33 | $varData->extendedValue = SageParser::alternativesParse($variable, $attributes);
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/parsers/SageParsersFilePath.php:
--------------------------------------------------------------------------------
1 | 2048
19 | || $strlen < 3
20 | || ! preg_match('#[\\\\/]#', $variable)
21 | || preg_match('/[?<>"*|]/', $variable)
22 | || ! @is_readable($variable) // PHP and its random warnings
23 | ) {
24 | return false;
25 | }
26 |
27 | return $this->run($variable, $varData, new SplFileInfo($variable));
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/parsers/SageParsersJson.php:
--------------------------------------------------------------------------------
1 | addTabToView($variable, 'Json', $val);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/parsers/SageParsersMicrotime.php:
--------------------------------------------------------------------------------
1 | 0) {
34 | $lap = $time - end(self::$times);
35 | self::$laps[] = $lap;
36 |
37 | $sinceLast = round($lap, 4) . 's.';
38 | if ($numberOfCalls > 1) {
39 | $sinceStart = round($time - self::$times[0], 4) . 's.';
40 | $averageDuration = round(array_sum(self::$laps) / $numberOfCalls, 4) . 's.';
41 | } else {
42 | $sinceStart = null;
43 | $averageDuration = null;
44 | }
45 |
46 | if (SageHelper::isRichMode()) {
47 | $tabContents = "SINCE LAST SUCH CALL: " . round($lap, 4) . ' s.';
48 | if ($numberOfCalls > 1) {
49 | $tabContents .= "\nSINCE START: {$sinceStart}";
50 | $tabContents .= "\nAVERAGE DURATION: {$averageDuration}";
51 | }
52 | $tabContents .= "\nPHP MEMORY USAGE: {$memoryUsage}";
53 |
54 | $varData->addTabToView($variable, 'Benchmark', $tabContents);
55 | } else {
56 | $varData->extendedValue = array(
57 | 'Since last such call' => $sinceLast
58 | );
59 |
60 | if ($sinceStart !== null) {
61 | $varData->extendedValue['Since start'] = $sinceStart;
62 | $varData->extendedValue['Average duration'] = $averageDuration;
63 | }
64 |
65 | $varData->extendedValue['Memory usage'] = $memoryUsage;
66 | }
67 | } else {
68 | $varData->extendedValue = array(
69 | 'Time (from microtime)' => @date('Y-m-d H:i:s', (int)$sec) . substr($usec, 1),
70 | 'PHP MEMORY USAGE' => $memoryUsage
71 | );
72 | }
73 |
74 | self::$times[] = $time;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/parsers/SageParsersObjectIterateable.php:
--------------------------------------------------------------------------------
1 | addTabToView($variable, "Iterator contents ({$size})", $arrayCopy);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/parsers/SageParsersSmarty.php:
--------------------------------------------------------------------------------
1 | name = 'Smarty v' . Smarty::SMARTY_VERSION;
22 |
23 | $assigned = $globalAssigns = array();
24 | foreach ($variable->tpl_vars as $name => $var) {
25 | $assigned[$name] = $var->value;
26 | }
27 | foreach (Smarty::$global_tpl_vars as $name => $var) {
28 | if ($name === 'SCRIPT_NAME') {
29 | continue;
30 | }
31 |
32 | $globalAssigns[$name] = $var->value;
33 | }
34 |
35 | $varData->addTabToView($variable, 'Assigned to view', $assigned);
36 | $varData->addTabToView($variable, 'Assigned globally', $globalAssigns);
37 | $varData->addTabToView($variable, 'Configuration', array(
38 | 'Compiled files stored in' => isset($variable->compile_dir)
39 | ? $variable->compile_dir
40 | : $variable->getCompileDir(),
41 | )
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/parsers/SageParsersSplFileInfo.php:
--------------------------------------------------------------------------------
1 | run($variable, $varData, $variable);
23 | }
24 |
25 | /**
26 | * @param mixed $variable
27 | * @param SageVariableData $varData
28 | * @param SplFileInfo $fileInfo
29 | *
30 | * @return bool
31 | */
32 | protected function run(&$variable, $varData, $fileInfo)
33 | {
34 | $varData->value = '"' . SageHelper::esc($fileInfo->getPathname()) . '"';
35 | $varData->type = get_class($fileInfo);
36 |
37 | if (! $fileInfo->getPathname() || ! $fileInfo->getRealPath()) {
38 | $varData->size = 'invalid path';
39 |
40 | return true;
41 | }
42 |
43 | try {
44 | $flags = array();
45 | $perms = $fileInfo->getPerms();
46 |
47 | if (($perms & 0xC000) === 0xC000) {
48 | $type = 'File socket';
49 | $flags[] = 's';
50 | } elseif (($perms & 0xA000) === 0xA000) {
51 | $type = 'File symlink';
52 | $flags[] = 'l';
53 | } elseif (($perms & 0x8000) === 0x8000) {
54 | $type = 'File';
55 | $flags[] = '-';
56 | } elseif (($perms & 0x6000) === 0x6000) {
57 | $type = 'Block special file';
58 | $flags[] = 'b';
59 | } elseif (($perms & 0x4000) === 0x4000) {
60 | $type = 'Directory';
61 | $flags[] = 'd';
62 | } elseif (($perms & 0x2000) === 0x2000) {
63 | $type = 'Character special file';
64 | $flags[] = 'c';
65 | } elseif (($perms & 0x1000) === 0x1000) {
66 | $type = 'FIFO pipe file';
67 | $flags[] = 'p';
68 | } else {
69 | $type = 'Unknown file';
70 | $flags[] = 'u';
71 | }
72 |
73 | // owner
74 | $flags[] = (($perms & 0x0100) ? 'r' : '-');
75 | $flags[] = (($perms & 0x0080) ? 'w' : '-');
76 | $flags[] = (($perms & 0x0040) ? (($perms & 0x0800) ? 's' : 'x') : (($perms & 0x0800) ? 'S' : '-'));
77 |
78 | // group
79 | $flags[] = (($perms & 0x0020) ? 'r' : '-');
80 | $flags[] = (($perms & 0x0010) ? 'w' : '-');
81 | $flags[] = (($perms & 0x0008) ? (($perms & 0x0400) ? 's' : 'x') : (($perms & 0x0400) ? 'S' : '-'));
82 |
83 | // world
84 | $flags[] = (($perms & 0x0004) ? 'r' : '-');
85 | $flags[] = (($perms & 0x0002) ? 'w' : '-');
86 | $flags[] = (($perms & 0x0001) ? (($perms & 0x0200) ? 't' : 'x') : (($perms & 0x0200) ? 'T' : '-'));
87 |
88 | $varData->type = get_class($fileInfo);
89 |
90 | if ($type === 'Directory') {
91 | $name = 'Existing Directory';
92 | $size = iterator_count(
93 | new FilesystemIterator($fileInfo->getRealPath(), FilesystemIterator::SKIP_DOTS)
94 | ) . ' item(s)';
95 | } else {
96 | $name = "Existing {$type}";
97 | $size = $this->humanFilesize($fileInfo->getSize());
98 | }
99 |
100 | $extra = array();
101 |
102 | if ($fileInfo->getRealPath() !== $fileInfo->getPathname()) {
103 | $extra['realPath'] = $fileInfo->getRealPath();
104 | }
105 |
106 | if (SageHelper::isRichMode()) {
107 | $extra['flags'] = implode($flags);
108 |
109 | if ($fileInfo->getGroup() || $fileInfo->getOwner()) {
110 | $extra['group&owner'] = $fileInfo->getGroup() . ':' . $fileInfo->getOwner();
111 | }
112 |
113 | $extra['created'] = date('Y-m-d H:i:s', $fileInfo->getCTime());
114 | $extra['modified'] = date('Y-m-d H:i:s', $fileInfo->getMTime());
115 | $extra['accessed'] = date('Y-m-d H:i:s', $fileInfo->getATime());
116 |
117 | if ($fileInfo->isLink()) {
118 | $extra['link'] = 'true';
119 | $extra['linkTarget'] = $fileInfo->getLinkTarget();
120 | }
121 |
122 | $varData->addTabToView($variable, $name . " [{$size}]", $extra);
123 | } else {
124 | if ($type === 'Directory') {
125 | $extended = array('Existing Directory' => $fileInfo->getFilename());
126 | } else {
127 | $extended = array("Existing {$type}" => $this->humanFilesize($fileInfo->getSize()));
128 | }
129 |
130 | $varData->extendedValue = array($name => $size) + $extra;
131 | }
132 | } catch (Exception $e) {
133 | return false;
134 | }
135 |
136 | return true;
137 | }
138 |
139 | private function humanFilesize($bytes)
140 | {
141 | $sizeInBytes = $bytes;
142 | if ($bytes < 10240) {
143 | return "{$bytes} bytes";
144 | }
145 |
146 | $units = array('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
147 | $precisionByUnit = array(0, 1, 1, 2, 2, 3, 3, 4, 4);
148 | for ($order = 0; ($bytes / 1024) >= 0.9 && $order < count($units); $order++) {
149 | $bytes /= 1024;
150 | }
151 |
152 | return $sizeInBytes . ' bytes (' . round($bytes, $precisionByUnit[$order]) . $units[$order] . ')';
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/parsers/SageParsersSplObjectStorage.php:
--------------------------------------------------------------------------------
1 | count();
20 | if ($count === 0) {
21 | return false;
22 | }
23 |
24 | $variable->rewind();
25 | $arrayCopy = array();
26 | while ($variable->valid()) {
27 | $arrayCopy[] = $variable->current();
28 | $variable->next();
29 | }
30 |
31 | $varData->addTabToView($variable, "Storage contents ({$count})", $arrayCopy);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/parsers/SageParsersTimestamp.php:
--------------------------------------------------------------------------------
1 | _fits($variable)) {
16 | return false;
17 | }
18 |
19 | $var = strlen($variable) === 13 ? substr($variable, 0, -3) : $variable;
20 |
21 | // avoid dreaded "Timezone must be set" error
22 | $varData->addTabToView($variable, 'Timestamp', @date('Y-m-d H:i:s', $var));
23 | }
24 |
25 | private function _fits($variable)
26 | {
27 | if (! SageHelper::isRichMode()) {
28 | return false;
29 | }
30 |
31 | if (! is_string($variable) && ! is_int($variable)) {
32 | return false;
33 | }
34 |
35 | $len = strlen((int)$variable);
36 |
37 | return
38 | (
39 | $len === 9 || $len === 10 // a little naive
40 | || ($len === 13 && substr($variable, -3) === '000') // also handles javascript micro timestamps
41 | )
42 | && ((string)(int)$variable == $variable);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/parsers/SageParsersXml.php:
--------------------------------------------------------------------------------
1 | addTabToView($variable, 'XML', SageParser::alternativesParse($variable, $xml));
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/resources/compiled/aante-light.css:
--------------------------------------------------------------------------------
1 | ._sage::selection{background:#aaa;color:#1d1e1e}._sage,._sage::before,._sage::after,._sage *,._sage *::before,._sage *::after{box-sizing:border-box;border-radius:0;color:#1d1e1e;float:none !important;font-family:ui-monospace,"Cascadia Code","Source Code Pro",Menlo,Consolas,"DejaVu Sans Mono",monospace;line-height:15px;margin:0;padding:0;text-align:left}._sage{font-size:13px;margin:8px 0;overflow-x:auto;white-space:nowrap}._sage u{color:#ff8c00}._sage dt{background:#f8f8f8;border:1px solid #d7d7d7;color:#1d1e1e;display:block;font-weight:bold;list-style:none outside none;overflow:auto;padding:4px}._sage dt:hover{border-color:#aaa}._sage>dl dl{padding:0 0 0 12px}._sage nav{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAA8CAYAAAC5OOBJAAAAvklEQVR42mNgGMFAS0uLLSQk5HxoaOh/XDg4OLgdpwEBAQEGQAN+YtMIFH/i4ODAg9cFQNMbsWkOCgrKJMv5ID5Qipko/6M7PzAw0ImkAIQ5H0hvISv0gZpP+fn5qZAVfcBAkmEYBaNZcjRLjmbJUUCvqAIlDlAiASUWkjWDkiU0eTaSpBGUEZBy1E9QRiFWLzO2LEmU80GZHkcRhN/5oGIGVNzgKIbwOx9UwOErAIl2/igYzZKjWXI0S9IHAAASJijo0Ypj8AAAAABJRU5ErkJggg==") no-repeat scroll 0 0 rgba(0,0,0,0);cursor:pointer;display:inline-block;height:15px;width:15px;margin-right:3px;vertical-align:middle}._sage dt._sage-parent{cursor:pointer}._sage dt._sage-parent:hover nav{background-position:0 -15px}._sage dt._sage-parent._sage-show:hover>nav{background-position:0 -45px}._sage dt._sage-show>nav{background-position:0 -30px}._sage dt._sage-parent+dd{display:none;border-left:1px dashed #d7d7d7}._sage dt._sage-parent._sage-show+dd{display:block}._sage var,._sage var a{color:#06f;font-style:normal}._sage dt:hover var,._sage dt:hover var a{color:red}._sage dfn{font-style:normal;font-weight:normal;color:#1d1e1e}._sage pre{color:#1d1e1e;margin:0 0 0 12px;padding:5px;overflow-y:hidden;border-top:0;border:1px solid #d7d7d7;background:#f8f8f8;display:block;word-break:normal}._sage ._sage-popup-trigger{float:right !important;cursor:pointer;color:#aaa}._sage ._sage-popup-trigger:hover{color:#d7d7d7}._sage dt._sage-parent>._sage-popup-trigger{font-size:13px}._sage footer{padding:0 3px 3px;font-size:9px;color:#999;text-shadow:0 0 1px #ccc}._sage footer>._sage-popup-trigger{font-size:12px}._sage footer nav{background-size:10px;height:10px;width:10px}._sage footer nav:hover{background-position:0 -10px}._sage footer>ol{display:none;margin-left:32px}._sage footer li{color:#999}._sage footer._sage-show>ol{display:block}._sage footer._sage-show nav{background-position:0 -20px}._sage footer._sage-show nav:hover{background-position:0 -30px}._sage a{color:#1d1e1e;text-shadow:none}._sage a:hover{color:#1d1e1e;border-bottom:1px dotted #1d1e1e}._sage ul{list-style:none;padding-left:12px}._sage ul:not(._sage-tabs) li{border-left:1px dashed #d7d7d7}._sage ul:not(._sage-tabs) li>dl{border-left:none}._sage ul._sage-tabs{margin:0 0 0 12px;padding-left:0;background:#f8f8f8;border:1px solid #d7d7d7;border-top:0}._sage ul._sage-tabs li{background:#f8f8f8;border:1px solid #d7d7d7;cursor:pointer;display:inline-block;height:24px;margin:2px;padding:0 12px;vertical-align:top}._sage ul._sage-tabs li:hover,._sage ul._sage-tabs li._sage-active-tab:hover{border-color:#aaa;color:red}._sage ul._sage-tabs li._sage-active-tab{background:#f8f8f8;border-top:0;margin-top:-2px;height:27px;line-height:24px}._sage ul._sage-tabs li:not(._sage-active-tab){line-height:20px}._sage ul._sage-tabs li+li{margin-left:0}._sage ul:not(._sage-tabs)>li:not(:first-child){display:none}._sage dt:hover+dd>ul>li._sage-active-tab{border-color:#aaa;color:red}._sage-report{border-collapse:collapse;empty-cells:show;border-spacing:0}._sage-report *{font-size:12px}._sage-report dt{background:none;padding:2px}._sage-report dt ._sage-parent{min-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}._sage-report td,._sage-report th{border:1px solid #d7d7d7;padding:2px;vertical-align:center}._sage-report th{cursor:alias}._sage-report td:first-child,._sage-report th{font-weight:bold;background:#f8f8f8;color:#1d1e1e}._sage-report td{background:#f8f8f8;white-space:pre}._sage-report td>dl{padding:0}._sage-report pre{border-top:0;border-right:0}._sage-report th:first-child{background:none;border:0}._sage-report td._sage-empty{background:#d33682 !important}._sage-report tr:hover>td{box-shadow:0 0 1px 0 #aaa inset}._sage-report tr:hover var{color:red}._sage-report ul._sage-tabs li._sage-active-tab{height:20px;line-height:17px}._sage-trace ._sage-source{line-height:14px}._sage-trace ._sage-source span{padding-right:1px;border-right:3px inset #06f}._sage-trace ._sage-source ._sage-highlight{background:#f8f8f8}._sage-trace b{min-width:18px;display:inline-block;text-align:right;margin-right:6px;color:#1d1e1e}._sage-trace ._sage-blacklisted>b,._sage-trace ._sage-childless>b{margin-right:22px}._sage-trace ._sage-blacklisted{filter:brightness(120%)}._sage-trace ._sage-parent>var>a{color:#06f}._sage-focused{box-shadow:0 0 3px 2px red}._sage-microtime,._sage-color-preview{box-shadow:0 0 2px 0 #b6cedb;height:16px;text-align:center;text-shadow:-1px 0 #839496,0 1px #839496,1px 0 #839496,0 -1px #839496;width:230px;color:#fdf6e3}._sage dt{font-weight:normal;margin-top:4px}._sage>dl{background:linear-gradient(90deg, rgba(255, 255, 255, 0) 0, #fff 15px)}._sage dl dl{margin-top:4px;padding-left:25px;border-left:none}._sage>dl>dt{background:#f8f8f8}._sage ul{margin:0;padding-left:0}._sage ul:not(._sage-tabs)>li{border-left:0}._sage ul._sage-tabs{background:#f8f8f8;border:1px solid #d7d7d7;border-width:0 1px 1px 1px;padding:4px 0 0 12px;margin-left:-1px;margin-top:-1px}._sage ul._sage-tabs li,._sage ul._sage-tabs li+li{margin:0 0 0 4px}._sage ul._sage-tabs li{border-bottom-width:0;height:25px}._sage ul._sage-tabs li:first-child{margin-left:0}._sage ul._sage-tabs li._sage-active-tab{border-top:1px solid #d7d7d7;background:#fff;font-weight:bold;padding-top:0;border-bottom:1px solid #fff !important;margin-bottom:-1px}._sage ul._sage-tabs li._sage-active-tab:hover{border-bottom:1px solid #fff}._sage ul>li>pre{border:1px solid #d7d7d7}._sage dt:hover+dd>ul{border-color:#aaa}._sage pre{background:#fff;margin-top:4px;margin-left:25px}._sage ._sage-popup-trigger:hover{color:red}._sage ._sage-source ._sage-highlight{background:#cfc}._sage ._sage-source span{border-right:3px inset #268bd2}._sage-report td{background:#fff}._sage-report td>dl{padding:0;margin:0}._sage-report td>dl>dt._sage-parent{margin:0}._sage-report td:first-child,._sage-report td,._sage-report th{padding:2px 4px}._sage-report td._sage-empty{background:#d7d7d7 !important}._sage-report dd,._sage-report dt{background:#fff}._sage-report tr:hover>td{box-shadow:none;background:#cfc}
2 |
--------------------------------------------------------------------------------
/resources/compiled/original-light.css:
--------------------------------------------------------------------------------
1 | ._sage::selection{background:#0092db;color:#1d1e1e}._sage,._sage::before,._sage::after,._sage *,._sage *::before,._sage *::after{box-sizing:border-box;border-radius:0;color:#1d1e1e;float:none !important;font-family:ui-monospace,"Cascadia Code","Source Code Pro",Menlo,Consolas,"DejaVu Sans Mono",monospace;line-height:15px;margin:0;padding:0;text-align:left}._sage{font-size:13px;margin:8px 0;overflow-x:auto;white-space:nowrap}._sage u{color:#ff8c00}._sage dt{background:#f1f5f7;border:1px solid #b6cedb;color:#1d1e1e;display:block;font-weight:bold;list-style:none outside none;overflow:auto;padding:4px}._sage dt:hover{border-color:#0092db}._sage>dl dl{padding:0 0 0 12px}._sage nav{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAA8CAMAAACO5hB7AAAACVBMVEX///82b4tctzBls0NMAAAAQklEQVR42rWRIQ4AAAgCwf8/2qgER2BeuwJsgAqIzekMfMryPL9XQSkobE6vwKcsz/N7FfPvh09lnPf/b+7673+f0uHuAZ/EdkNQAAAAAElFTkSuQmCC") no-repeat scroll 0 0 rgba(0,0,0,0);cursor:pointer;display:inline-block;height:15px;width:15px;margin-right:3px;vertical-align:middle}._sage dt._sage-parent{cursor:pointer}._sage dt._sage-parent:hover nav{background-position:0 -15px}._sage dt._sage-parent._sage-show:hover>nav{background-position:0 -45px}._sage dt._sage-show>nav{background-position:0 -30px}._sage dt._sage-parent+dd{display:none;border-left:1px dashed #b6cedb}._sage dt._sage-parent._sage-show+dd{display:block}._sage var,._sage var a{color:#0092db;font-style:normal}._sage dt:hover var,._sage dt:hover var a{color:#5cb730}._sage dfn{font-style:normal;font-weight:normal;color:#1d1e1e}._sage pre{color:#1d1e1e;margin:0 0 0 12px;padding:5px;overflow-y:hidden;border-top:0;border:1px solid #b6cedb;background:#f1f5f7;display:block;word-break:normal}._sage ._sage-popup-trigger{float:right !important;cursor:pointer;color:#0092db}._sage ._sage-popup-trigger:hover{color:#b6cedb}._sage dt._sage-parent>._sage-popup-trigger{font-size:13px}._sage footer{padding:0 3px 3px;font-size:9px;color:#999;text-shadow:0 0 1px #ccc}._sage footer>._sage-popup-trigger{font-size:12px}._sage footer nav{background-size:10px;height:10px;width:10px}._sage footer nav:hover{background-position:0 -10px}._sage footer>ol{display:none;margin-left:32px}._sage footer li{color:#999}._sage footer._sage-show>ol{display:block}._sage footer._sage-show nav{background-position:0 -20px}._sage footer._sage-show nav:hover{background-position:0 -30px}._sage a{color:#1d1e1e;text-shadow:none}._sage a:hover{color:#1d1e1e;border-bottom:1px dotted #1d1e1e}._sage ul{list-style:none;padding-left:12px}._sage ul:not(._sage-tabs) li{border-left:1px dashed #b6cedb}._sage ul:not(._sage-tabs) li>dl{border-left:none}._sage ul._sage-tabs{margin:0 0 0 12px;padding-left:0;background:#f1f5f7;border:1px solid #b6cedb;border-top:0}._sage ul._sage-tabs li{background:#e3ecf0;border:1px solid #b6cedb;cursor:pointer;display:inline-block;height:24px;margin:2px;padding:0 12px;vertical-align:top}._sage ul._sage-tabs li:hover,._sage ul._sage-tabs li._sage-active-tab:hover{border-color:#0092db;color:#5cb730}._sage ul._sage-tabs li._sage-active-tab{background:#f1f5f7;border-top:0;margin-top:-2px;height:27px;line-height:24px}._sage ul._sage-tabs li:not(._sage-active-tab){line-height:20px}._sage ul._sage-tabs li+li{margin-left:0}._sage ul:not(._sage-tabs)>li:not(:first-child){display:none}._sage dt:hover+dd>ul>li._sage-active-tab{border-color:#0092db;color:#5cb730}._sage-report{border-collapse:collapse;empty-cells:show;border-spacing:0}._sage-report *{font-size:12px}._sage-report dt{background:none;padding:2px}._sage-report dt ._sage-parent{min-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}._sage-report td,._sage-report th{border:1px solid #b6cedb;padding:2px;vertical-align:center}._sage-report th{cursor:alias}._sage-report td:first-child,._sage-report th{font-weight:bold;background:#e3ecf0;color:#1d1e1e}._sage-report td{background:#f1f5f7;white-space:pre}._sage-report td>dl{padding:0}._sage-report pre{border-top:0;border-right:0}._sage-report th:first-child{background:none;border:0}._sage-report td._sage-empty{background:#d33682 !important}._sage-report tr:hover>td{box-shadow:0 0 1px 0 #0092db inset}._sage-report tr:hover var{color:#5cb730}._sage-report ul._sage-tabs li._sage-active-tab{height:20px;line-height:17px}._sage-trace ._sage-source{line-height:14px}._sage-trace ._sage-source span{padding-right:1px;border-right:3px inset #0092db}._sage-trace ._sage-source ._sage-highlight{background:#e3ecf0}._sage-trace b{min-width:18px;display:inline-block;text-align:right;margin-right:6px;color:#1d1e1e}._sage-trace ._sage-blacklisted>b,._sage-trace ._sage-childless>b{margin-right:22px}._sage-trace ._sage-blacklisted{filter:brightness(120%)}._sage-trace ._sage-parent>var>a{color:#0092db}._sage-focused{box-shadow:0 0 3px 2px #5cb730}._sage-microtime,._sage-color-preview{box-shadow:0 0 2px 0 #b6cedb;height:16px;text-align:center;text-shadow:-1px 0 #839496,0 1px #839496,1px 0 #839496,0 -1px #839496;width:230px;color:#fdf6e3}._sage>dl>dt,._sage ul._sage-tabs{background:linear-gradient(to bottom, #fff 0, #f1f5f7 100%)}._sage dl:not(:last-of-type) dt:not(._sage-show){border-bottom:0}._sage ul._sage-tabs li{background:#e3ecf0}._sage>dl._sage-trace>dt{background:linear-gradient(to bottom, #f1f5f7 0, #fff 100%)}._sage>dl._sage-trace dt:not(:last-of-type):not(._sage-show){border-bottom:0}._sage>dl._sage-trace>dd>ul._sage-tabs>li._sage-active-tab{background:#fff}._sage ._sage-source ._sage-highlight{background:#f0eb96}._sage ._sage-source>div{margin-top:-1px}
2 |
--------------------------------------------------------------------------------
/resources/compiled/original.css:
--------------------------------------------------------------------------------
1 | ._sage::selection{background:#0092db;color:#1d1e1e}._sage,._sage::before,._sage::after,._sage *,._sage *::before,._sage *::after{box-sizing:border-box;border-radius:0;color:#1d1e1e;float:none !important;font-family:ui-monospace,"Cascadia Code","Source Code Pro",Menlo,Consolas,"DejaVu Sans Mono",monospace;line-height:15px;margin:0;padding:0;text-align:left}._sage{font-size:13px;margin:8px 0;overflow-x:auto;white-space:nowrap}._sage u{color:#ff8c00}._sage dt{background:#e0eaef;border:1px solid #b6cedb;color:#1d1e1e;display:block;font-weight:bold;list-style:none outside none;overflow:auto;padding:4px}._sage dt:hover{border-color:#0092db}._sage>dl dl{padding:0 0 0 12px}._sage nav{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAA8CAMAAACO5hB7AAAACVBMVEX///82b4tctzBls0NMAAAAQklEQVR42rWRIQ4AAAgCwf8/2qgER2BeuwJsgAqIzekMfMryPL9XQSkobE6vwKcsz/N7FfPvh09lnPf/b+7673+f0uHuAZ/EdkNQAAAAAElFTkSuQmCC") no-repeat scroll 0 0 rgba(0,0,0,0);cursor:pointer;display:inline-block;height:15px;width:15px;margin-right:3px;vertical-align:middle}._sage dt._sage-parent{cursor:pointer}._sage dt._sage-parent:hover nav{background-position:0 -15px}._sage dt._sage-parent._sage-show:hover>nav{background-position:0 -45px}._sage dt._sage-show>nav{background-position:0 -30px}._sage dt._sage-parent+dd{display:none;border-left:1px dashed #b6cedb}._sage dt._sage-parent._sage-show+dd{display:block}._sage var,._sage var a{color:#0092db;font-style:normal}._sage dt:hover var,._sage dt:hover var a{color:#5cb730}._sage dfn{font-style:normal;font-weight:normal;color:#1d1e1e}._sage pre{color:#1d1e1e;margin:0 0 0 12px;padding:5px;overflow-y:hidden;border-top:0;border:1px solid #b6cedb;background:#e0eaef;display:block;word-break:normal}._sage ._sage-popup-trigger{float:right !important;cursor:pointer;color:#0092db}._sage ._sage-popup-trigger:hover{color:#b6cedb}._sage dt._sage-parent>._sage-popup-trigger{font-size:13px}._sage footer{padding:0 3px 3px;font-size:9px;color:#999;text-shadow:0 0 1px #ccc}._sage footer>._sage-popup-trigger{font-size:12px}._sage footer nav{background-size:10px;height:10px;width:10px}._sage footer nav:hover{background-position:0 -10px}._sage footer>ol{display:none;margin-left:32px}._sage footer li{color:#999}._sage footer._sage-show>ol{display:block}._sage footer._sage-show nav{background-position:0 -20px}._sage footer._sage-show nav:hover{background-position:0 -30px}._sage a{color:#1d1e1e;text-shadow:none}._sage a:hover{color:#1d1e1e;border-bottom:1px dotted #1d1e1e}._sage ul{list-style:none;padding-left:12px}._sage ul:not(._sage-tabs) li{border-left:1px dashed #b6cedb}._sage ul:not(._sage-tabs) li>dl{border-left:none}._sage ul._sage-tabs{margin:0 0 0 12px;padding-left:0;background:#e0eaef;border:1px solid #b6cedb;border-top:0}._sage ul._sage-tabs li{background:#c1d4df;border:1px solid #b6cedb;cursor:pointer;display:inline-block;height:24px;margin:2px;padding:0 12px;vertical-align:top}._sage ul._sage-tabs li:hover,._sage ul._sage-tabs li._sage-active-tab:hover{border-color:#0092db;color:#5cb730}._sage ul._sage-tabs li._sage-active-tab{background:#e0eaef;border-top:0;margin-top:-2px;height:27px;line-height:24px}._sage ul._sage-tabs li:not(._sage-active-tab){line-height:20px}._sage ul._sage-tabs li+li{margin-left:0}._sage ul:not(._sage-tabs)>li:not(:first-child){display:none}._sage dt:hover+dd>ul>li._sage-active-tab{border-color:#0092db;color:#5cb730}._sage-report{border-collapse:collapse;empty-cells:show;border-spacing:0}._sage-report *{font-size:12px}._sage-report dt{background:none;padding:2px}._sage-report dt ._sage-parent{min-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}._sage-report td,._sage-report th{border:1px solid #b6cedb;padding:2px;vertical-align:center}._sage-report th{cursor:alias}._sage-report td:first-child,._sage-report th{font-weight:bold;background:#c1d4df;color:#1d1e1e}._sage-report td{background:#e0eaef;white-space:pre}._sage-report td>dl{padding:0}._sage-report pre{border-top:0;border-right:0}._sage-report th:first-child{background:none;border:0}._sage-report td._sage-empty{background:#d33682 !important}._sage-report tr:hover>td{box-shadow:0 0 1px 0 #0092db inset}._sage-report tr:hover var{color:#5cb730}._sage-report ul._sage-tabs li._sage-active-tab{height:20px;line-height:17px}._sage-trace ._sage-source{line-height:14px}._sage-trace ._sage-source span{padding-right:1px;border-right:3px inset #0092db}._sage-trace ._sage-source ._sage-highlight{background:#c1d4df}._sage-trace b{min-width:18px;display:inline-block;text-align:right;margin-right:6px;color:#1d1e1e}._sage-trace ._sage-blacklisted>b,._sage-trace ._sage-childless>b{margin-right:22px}._sage-trace ._sage-blacklisted{filter:brightness(120%)}._sage-trace ._sage-parent>var>a{color:#0092db}._sage-focused{box-shadow:0 0 3px 2px #5cb730}._sage-microtime,._sage-color-preview{box-shadow:0 0 2px 0 #b6cedb;height:16px;text-align:center;text-shadow:-1px 0 #839496,0 1px #839496,1px 0 #839496,0 -1px #839496;width:230px;color:#fdf6e3}._sage>dl>dt{background:linear-gradient(to bottom, #e3ecf0 0, #c0d4df 100%)}._sage ul._sage-tabs{background:linear-gradient(to bottom, #9dbed0 0px, #b2ccda 100%)}._sage>dl:not(._sage-trace)>dd>ul._sage-tabs li{background:#e0eaef}._sage>dl:not(._sage-trace)>dd>ul._sage-tabs li._sage-active-tab{background:#c1d4df}._sage>dl._sage-trace>dt{background:linear-gradient(to bottom, #c0d4df 0px, #e3ecf0 100%)}._sage ._sage-source ._sage-highlight{background:#f0eb96}
2 |
--------------------------------------------------------------------------------
/resources/compiled/sage.js:
--------------------------------------------------------------------------------
1 | if("undefined"==typeof _sageInitialized){const a={t:[],i:-(_sageInitialized=1),o:[],u:(n,t=100)=>{let i;return(...e)=>{clearTimeout(i),i=setTimeout(()=>n(...e),t)}},l:function(){let t=a.o.length;for(;t--;){let e=a.o[t],n=e.g;(window.scrollY<=n._||window.scrollY>=n.v)&&(a.o.splice(t,1),n.remove(),e.g=null)}var e=document.querySelectorAll("._sage");for(t=e.length;t--;){const n=e[t];if(!a.p(n))return;let o=0;n.querySelectorAll("._sage-clone").forEach(function(e){o+=e.offsetHeight}),n.querySelectorAll("dt._sage-parent._sage-show:not(._sage-clone)").forEach(function(e){if(!a.o.includes(e)){const t=e.nextElementSibling;if(a.p(t)){const i=e.cloneNode(!0);var n=t.getBoundingClientRect();i._=n.top+window.scrollY,i.v=n.bottom+window.scrollY,i.style.width=n.width+"px",i.style.top=o+"px",i.style.position="fixed",i.style.opacity=.9,i.classList.add("_sage-clone"),o+=e.offsetHeight,e.g=i,a.o.push(e),e.after(i)}}});break}},p:function(e){e=e.getBoundingClientRect();return 0!==e.height&&(!(e.top>=window.innerHeight)&&(!(0dd>._sage-tabs>._sage-active-tab").forEach(function(e){e=e.nextSibling;e&&a.C(e)})},C:function(e){let n,t=e,i=0;for(a.A(e.parentNode.getElementsByClassName("_sage-active-tab")[0],"_sage-active-tab"),a.k(e,"_sage-active-tab");t=t.previousSibling;)1===t.nodeType&&i++;n=e.parentNode.nextSibling.childNodes;for(let e=0;eli:not(._sage-active-tab)",function(e){0===e.offsetWidth&&0===e.offsetHeight||a.t.push(e)})},tag:function(e){return"<"+e+">"},O:function(e){let n;(n=window.open())&&(n.document.open(),n.document.write(a.tag("html")+a.tag("head")+"Sage ☯ ("+(new Date).toISOString()+") "+a.tag('meta charset="utf-8"')+document.getElementsByClassName("_sage-js")[0].outerHTML+document.getElementsByClassName("_sage-css")[0].outerHTML+a.tag("/head")+a.tag("body")+''+e.parentNode.outerHTML+"
"+a.tag("/body")),n.document.close())},R:function(e,t,n){const i=e.tBodies[0],o=new Intl.Collator(void 0,{numeric:!0,sensitivity:"base"}),a=void 0===n.V?1:n.V;n.V=-1*a,[].slice.call(e.tBodies[0].rows).sort(function(e,n){return a*o.compare(e.cells[t].textContent,n.cells[t].textContent)}).forEach(function(e){i.appendChild(e)})},S:{L:function(e){var n="_sage-focused",t=document.querySelector("."+n);if(t&&a.A(t,n),-1!==e){t=a.t[e];a.k(t,n);const i=function(e){return e.offsetTop+(e.offsetParent?i(e.offsetParent):0)};n=i(t)-window.innerHeight/2;window.scrollTo(0,n)}a.i=e},j:function(e,n){return e?--n<0&&(n=a.t.length-1):++n>=a.t.length&&(n=0),a.S.L(n),!1}}};window.addEventListener("click",function(e){let n=e.target,t=n.tagName;if(a.D(n)){if("DFN"===t)a.m(n),n=n.parentNode;else if("VAR"===t)n=n.parentNode,t=n.tagName;else if("TH"===t)return e.ctrlKey||a.R(n.parentNode.parentNode.parentNode,n.cellIndex,n),!1;if("LI"===t&&a.T(n.parentNode,"_sage-tabs"))return a.T(n,"_sage-active-tab")||(a.C(n),-1!==a.i&&a.F()),!1;if("NAV"===t)return"FOOTER"===n.parentNode.tagName?(n=n.parentNode,a.toggle(n)):setTimeout(function(){0e&&(t=e),idl dl{padding:0 0 0 12px}._sage nav{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAA8CAMAAACO5hB7AAAA7VBMVEVYbnWToaH///9YbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnWToaGToaGToaGToaGToaGToaGToaGToaGToaFYbnWToaFYbnVYbnWToaGToaFYbnWToaFYbnWToaFYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnWToaGToaGToaGToaGToaGToaGToaGToaGToaGToaGToaGToaGToaGToaFYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnWToaGToaGToaGToaGToaGToaGToaGToaGToaFYbnWToaEeRtj1AAAATXRSTlMAAACLab0B7iP2R7CL9iPuaUewAb36F0/QnPoX0JxPYQaurepi6QV8FhV7MzFhM6586RYFMa3qYnsVBpNQ+ZGOGJ3PkM9QkZAY+Y6TnV7gnYsAAAEcSURBVHhebdHVbgMxEEBRu8sUZmqapBBoSuGUmWb+/3NayxtrLOe+Hck745XZgd6/eb3CVcKt7HmR+gKxlCGOETG5vFbOoahR6NxI5zHt6nuq+2dqnqfzzH3mfcz70vb8H6MJt5u6a96hS30E4PjEFgAEp2fKNojKUfVEOoS044ex7u3YPE/nmfvM+5j3pe17T9oe/77O41w+n4vnrbrwZxbTshVhvtx5Kb8vliRLRWmeSQSTjJq/El5x5fUXYmNN9hcQC5y4g/hGvVksNtT8451rns2IScb7mn567ll2GNpWr9YWfvQgzWsKs8HOA/m960g6rjTzA8HAV/NHwiOmPLwDKA/J/gggYsRVgFvqbr/fpWYv90zzZKKs9Qfx00Jx65oxLAAAAABJRU5ErkJggg==") no-repeat scroll 0 0 rgba(0,0,0,0);cursor:pointer;display:inline-block;height:15px;width:15px;margin-right:3px;vertical-align:middle}._sage dt._sage-parent{cursor:pointer}._sage dt._sage-parent:hover nav{background-position:0 -15px}._sage dt._sage-parent._sage-show:hover>nav{background-position:0 -45px}._sage dt._sage-show>nav{background-position:0 -30px}._sage dt._sage-parent+dd{display:none;border-left:1px dashed #586e75}._sage dt._sage-parent._sage-show+dd{display:block}._sage var,._sage var a{color:#268bd2;font-style:normal}._sage dt:hover var,._sage dt:hover var a{color:#2aa198}._sage dfn{font-style:normal;font-weight:normal;color:#93a1a1}._sage pre{color:#839496;margin:0 0 0 12px;padding:5px;overflow-y:hidden;border-top:0;border:1px solid #586e75;background:#002b36;display:block;word-break:normal}._sage ._sage-popup-trigger{float:right !important;cursor:pointer;color:#268bd2}._sage ._sage-popup-trigger:hover{color:#586e75}._sage dt._sage-parent>._sage-popup-trigger{font-size:13px}._sage footer{padding:0 3px 3px;font-size:9px;color:#999;text-shadow:0 0 1px #ccc}._sage footer>._sage-popup-trigger{font-size:12px}._sage footer nav{background-size:10px;height:10px;width:10px}._sage footer nav:hover{background-position:0 -10px}._sage footer>ol{display:none;margin-left:32px}._sage footer li{color:#999}._sage footer._sage-show>ol{display:block}._sage footer._sage-show nav{background-position:0 -20px}._sage footer._sage-show nav:hover{background-position:0 -30px}._sage a{color:#839496;text-shadow:none}._sage a:hover{color:#93a1a1;border-bottom:1px dotted #93a1a1}._sage ul{list-style:none;padding-left:12px}._sage ul:not(._sage-tabs) li{border-left:1px dashed #586e75}._sage ul:not(._sage-tabs) li>dl{border-left:none}._sage ul._sage-tabs{margin:0 0 0 12px;padding-left:0;background:#002b36;border:1px solid #586e75;border-top:0}._sage ul._sage-tabs li{background:#073642;border:1px solid #586e75;cursor:pointer;display:inline-block;height:24px;margin:2px;padding:0 12px;vertical-align:top}._sage ul._sage-tabs li:hover,._sage ul._sage-tabs li._sage-active-tab:hover{border-color:#268bd2;color:#2aa198}._sage ul._sage-tabs li._sage-active-tab{background:#002b36;border-top:0;margin-top:-2px;height:27px;line-height:24px}._sage ul._sage-tabs li:not(._sage-active-tab){line-height:20px}._sage ul._sage-tabs li+li{margin-left:0}._sage ul:not(._sage-tabs)>li:not(:first-child){display:none}._sage dt:hover+dd>ul>li._sage-active-tab{border-color:#268bd2;color:#2aa198}._sage-report{border-collapse:collapse;empty-cells:show;border-spacing:0}._sage-report *{font-size:12px}._sage-report dt{background:none;padding:2px}._sage-report dt ._sage-parent{min-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}._sage-report td,._sage-report th{border:1px solid #586e75;padding:2px;vertical-align:center}._sage-report th{cursor:alias}._sage-report td:first-child,._sage-report th{font-weight:bold;background:#073642;color:#93a1a1}._sage-report td{background:#002b36;white-space:pre}._sage-report td>dl{padding:0}._sage-report pre{border-top:0;border-right:0}._sage-report th:first-child{background:none;border:0}._sage-report td._sage-empty{background:#d33682 !important}._sage-report tr:hover>td{box-shadow:0 0 1px 0 #268bd2 inset}._sage-report tr:hover var{color:#2aa198}._sage-report ul._sage-tabs li._sage-active-tab{height:20px;line-height:17px}._sage-trace ._sage-source{line-height:14px}._sage-trace ._sage-source span{padding-right:1px;border-right:3px inset #268bd2}._sage-trace ._sage-source ._sage-highlight{background:#073642}._sage-trace b{min-width:18px;display:inline-block;text-align:right;margin-right:6px;color:#93a1a1}._sage-trace ._sage-blacklisted>b,._sage-trace ._sage-childless>b{margin-right:22px}._sage-trace ._sage-blacklisted{filter:brightness(120%)}._sage-trace ._sage-parent>var>a{color:#268bd2}._sage-focused{box-shadow:0 0 3px 2px #859900 inset;border-radius:7px}._sage-microtime,._sage-color-preview{box-shadow:0 0 2px 0 #b6cedb;height:16px;text-align:center;text-shadow:-1px 0 #839496,0 1px #839496,1px 0 #839496,0 -1px #839496;width:230px;color:#fdf6e3}body{background:#073642;color:#fff}._sage{background:#073642;box-shadow:0 0 5px 3px #073642}._sage>dl>dt,._sage ul._sage-tabs{box-shadow:4px 0 2px -3px #268bd2 inset}._sage ul._sage-tabs li._sage-active-tab{padding-top:7px;height:34px}
2 |
--------------------------------------------------------------------------------
/resources/compiled/solarized.css:
--------------------------------------------------------------------------------
1 | ._sage::selection{background:#268bd2;color:#657b83}._sage,._sage::before,._sage::after,._sage *,._sage *::before,._sage *::after{box-sizing:border-box;border-radius:0;color:#657b83;float:none !important;font-family:ui-monospace,"Cascadia Code","Source Code Pro",Menlo,Consolas,"DejaVu Sans Mono",monospace;line-height:15px;margin:0;padding:0;text-align:left}._sage{font-size:13px;margin:8px 0;overflow-x:auto;white-space:nowrap}._sage u{color:#ff8c00}._sage dt{background:#fdf6e3;border:1px solid #93a1a1;color:#657b83;display:block;font-weight:bold;list-style:none outside none;overflow:auto;padding:4px}._sage dt:hover{border-color:#268bd2}._sage>dl dl{padding:0 0 0 12px}._sage nav{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAA8CAMAAACO5hB7AAAA7VBMVEVYbnWToaH///9YbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnWToaGToaGToaGToaGToaGToaGToaGToaGToaFYbnWToaFYbnVYbnWToaGToaFYbnWToaFYbnWToaFYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnWToaGToaGToaGToaGToaGToaGToaGToaGToaGToaGToaGToaGToaGToaFYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnWToaGToaGToaGToaGToaGToaGToaGToaGToaFYbnWToaEeRtj1AAAATXRSTlMAAACLab0B7iP2R7CL9iPuaUewAb36F0/QnPoX0JxPYQaurepi6QV8FhV7MzFhM6586RYFMa3qYnsVBpNQ+ZGOGJ3PkM9QkZAY+Y6TnV7gnYsAAAEcSURBVHhebdHVbgMxEEBRu8sUZmqapBBoSuGUmWb+/3NayxtrLOe+Hck745XZgd6/eb3CVcKt7HmR+gKxlCGOETG5vFbOoahR6NxI5zHt6nuq+2dqnqfzzH3mfcz70vb8H6MJt5u6a96hS30E4PjEFgAEp2fKNojKUfVEOoS044ex7u3YPE/nmfvM+5j3pe17T9oe/77O41w+n4vnrbrwZxbTshVhvtx5Kb8vliRLRWmeSQSTjJq/El5x5fUXYmNN9hcQC5y4g/hGvVksNtT8451rns2IScb7mn567ll2GNpWr9YWfvQgzWsKs8HOA/m960g6rjTzA8HAV/NHwiOmPLwDKA/J/gggYsRVgFvqbr/fpWYv90zzZKKs9Qfx00Jx65oxLAAAAABJRU5ErkJggg==") no-repeat scroll 0 0 rgba(0,0,0,0);cursor:pointer;display:inline-block;height:15px;width:15px;margin-right:3px;vertical-align:middle}._sage dt._sage-parent{cursor:pointer}._sage dt._sage-parent:hover nav{background-position:0 -15px}._sage dt._sage-parent._sage-show:hover>nav{background-position:0 -45px}._sage dt._sage-show>nav{background-position:0 -30px}._sage dt._sage-parent+dd{display:none;border-left:1px dashed #93a1a1}._sage dt._sage-parent._sage-show+dd{display:block}._sage var,._sage var a{color:#268bd2;font-style:normal}._sage dt:hover var,._sage dt:hover var a{color:#2aa198}._sage dfn{font-style:normal;font-weight:normal;color:#586e75}._sage pre{color:#657b83;margin:0 0 0 12px;padding:5px;overflow-y:hidden;border-top:0;border:1px solid #93a1a1;background:#fdf6e3;display:block;word-break:normal}._sage ._sage-popup-trigger{float:right !important;cursor:pointer;color:#268bd2}._sage ._sage-popup-trigger:hover{color:#93a1a1}._sage dt._sage-parent>._sage-popup-trigger{font-size:13px}._sage footer{padding:0 3px 3px;font-size:9px;color:#999;text-shadow:0 0 1px #ccc}._sage footer>._sage-popup-trigger{font-size:12px}._sage footer nav{background-size:10px;height:10px;width:10px}._sage footer nav:hover{background-position:0 -10px}._sage footer>ol{display:none;margin-left:32px}._sage footer li{color:#999}._sage footer._sage-show>ol{display:block}._sage footer._sage-show nav{background-position:0 -20px}._sage footer._sage-show nav:hover{background-position:0 -30px}._sage a{color:#657b83;text-shadow:none}._sage a:hover{color:#586e75;border-bottom:1px dotted #586e75}._sage ul{list-style:none;padding-left:12px}._sage ul:not(._sage-tabs) li{border-left:1px dashed #93a1a1}._sage ul:not(._sage-tabs) li>dl{border-left:none}._sage ul._sage-tabs{margin:0 0 0 12px;padding-left:0;background:#fdf6e3;border:1px solid #93a1a1;border-top:0}._sage ul._sage-tabs li{background:#eee8d5;border:1px solid #93a1a1;cursor:pointer;display:inline-block;height:24px;margin:2px;padding:0 12px;vertical-align:top}._sage ul._sage-tabs li:hover,._sage ul._sage-tabs li._sage-active-tab:hover{border-color:#268bd2;color:#2aa198}._sage ul._sage-tabs li._sage-active-tab{background:#fdf6e3;border-top:0;margin-top:-2px;height:27px;line-height:24px}._sage ul._sage-tabs li:not(._sage-active-tab){line-height:20px}._sage ul._sage-tabs li+li{margin-left:0}._sage ul:not(._sage-tabs)>li:not(:first-child){display:none}._sage dt:hover+dd>ul>li._sage-active-tab{border-color:#268bd2;color:#2aa198}._sage-report{border-collapse:collapse;empty-cells:show;border-spacing:0}._sage-report *{font-size:12px}._sage-report dt{background:none;padding:2px}._sage-report dt ._sage-parent{min-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}._sage-report td,._sage-report th{border:1px solid #93a1a1;padding:2px;vertical-align:center}._sage-report th{cursor:alias}._sage-report td:first-child,._sage-report th{font-weight:bold;background:#eee8d5;color:#586e75}._sage-report td{background:#fdf6e3;white-space:pre}._sage-report td>dl{padding:0}._sage-report pre{border-top:0;border-right:0}._sage-report th:first-child{background:none;border:0}._sage-report td._sage-empty{background:#d33682 !important}._sage-report tr:hover>td{box-shadow:0 0 1px 0 #268bd2 inset}._sage-report tr:hover var{color:#2aa198}._sage-report ul._sage-tabs li._sage-active-tab{height:20px;line-height:17px}._sage-trace ._sage-source{line-height:14px}._sage-trace ._sage-source span{padding-right:1px;border-right:3px inset #268bd2}._sage-trace ._sage-source ._sage-highlight{background:#eee8d5}._sage-trace b{min-width:18px;display:inline-block;text-align:right;margin-right:6px;color:#586e75}._sage-trace ._sage-blacklisted>b,._sage-trace ._sage-childless>b{margin-right:22px}._sage-trace ._sage-blacklisted{filter:brightness(120%)}._sage-trace ._sage-parent>var>a{color:#268bd2}._sage-focused{box-shadow:0 0 3px 2px #859900 inset;border-radius:7px}._sage-microtime,._sage-color-preview{box-shadow:0 0 2px 0 #b6cedb;height:16px;text-align:center;text-shadow:-1px 0 #839496,0 1px #839496,1px 0 #839496,0 -1px #839496;width:230px;color:#fdf6e3}._sage>dl>dt,._sage ul._sage-tabs{box-shadow:4px 0 2px -3px #268bd2 inset}._sage ul._sage-tabs li._sage-active-tab{padding-top:7px;height:34px}
2 |
--------------------------------------------------------------------------------
/resources/css/base.scss:
--------------------------------------------------------------------------------
1 | //
2 | // VARIABLES FOR THEMES TO OVERRIDE
3 | // --------------------------------------------------
4 |
5 | $spacing: 4;
6 |
7 | //
8 | // SET UP HELPER VARIABLES
9 | // --------------------------------------------------
10 |
11 | $border: 1px solid $border-color;
12 |
13 | @mixin selection() {
14 | background: $border-color-hover;
15 | color: $text-color;
16 | }
17 |
18 | //
19 | // BASE STYLES
20 | // --------------------------------------------------
21 |
22 | ._sage::selection {
23 | @include selection;
24 | }
25 |
26 |
27 | ._sage,
28 | ._sage::before,
29 | ._sage::after,
30 | ._sage *,
31 | ._sage *::before,
32 | ._sage *::after {
33 | box-sizing: border-box;
34 | border-radius: 0;
35 | color: $text-color;
36 | float: none !important;
37 | font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
38 | line-height: 15px;
39 | margin: 0;
40 | padding: 0;
41 | text-align: left;
42 | }
43 |
44 | ._sage {
45 | font-size: 13px;
46 | margin: $spacing * 2px 0;
47 | overflow-x: auto;
48 | white-space: nowrap;
49 |
50 | // invisible symbols
51 | u {
52 | color: #ff8c00;
53 | }
54 |
55 | dt {
56 | background: $main-background;
57 | border: $border;
58 | color: $text-color;
59 | display: block;
60 | font-weight: bold;
61 | list-style: none outside none;
62 | overflow: auto;
63 | padding: $spacing * 1px;
64 |
65 | &:hover {
66 | border-color: $border-color-hover;
67 | }
68 |
69 |
70 | }
71 |
72 | > dl dl {
73 | padding: 0 0 0 $spacing * 3px;
74 | }
75 |
76 | //
77 | // DROPDOWN CARET
78 | // --------------------------------------------------
79 |
80 | nav {
81 | background: $caret-image no-repeat scroll 0 0 transparent;
82 | cursor: pointer;
83 | display: inline-block;
84 | height: 15px;
85 | width: 15px;
86 | margin-right: 3px;
87 | vertical-align: middle;
88 | }
89 |
90 | dt._sage-parent {
91 | cursor: pointer;
92 | }
93 |
94 | dt._sage-parent:hover nav {
95 | background-position: 0 -15px;
96 | }
97 |
98 | dt._sage-parent._sage-show:hover > nav {
99 | background-position: 0 -45px;
100 | }
101 |
102 | dt._sage-show > nav {
103 | background-position: 0 -30px;
104 | }
105 |
106 | dt._sage-parent + dd {
107 | display: none;
108 | border-left: 1px dashed $border-color;
109 | }
110 |
111 | dt._sage-parent._sage-show + dd {
112 | display: block;
113 | }
114 |
115 | //
116 | // INDIVIDUAL ITEMS
117 | // --------------------------------------------------
118 |
119 | var,
120 | var a {
121 | color: $variable-type-color;
122 | font-style: normal;
123 | }
124 |
125 | dt:hover var,
126 | dt:hover var a {
127 | color: $variable-type-color-hover;
128 | }
129 |
130 | dfn {
131 | font-style: normal;
132 | font-weight: normal;
133 | color: $variable-name-color;
134 | }
135 |
136 | pre {
137 | color: $text-color;
138 | margin: 0 0 0 $spacing * 3px;
139 | padding: 5px;
140 | overflow-y: hidden;
141 | border-top: 0;
142 | border: $border;
143 | background: $main-background;
144 | display: block;
145 | word-break: normal;
146 | }
147 |
148 | ._sage-popup-trigger {
149 | float: right !important;
150 | cursor: pointer;
151 | color: $border-color-hover;
152 |
153 | &:hover {
154 | color: $border-color;
155 | }
156 | }
157 |
158 | dt._sage-parent > ._sage-popup-trigger {
159 | font-size: 13px;
160 | }
161 |
162 | footer {
163 | padding: 0 3px 3px;
164 | font-size: 9px;
165 | color: #999;
166 | text-shadow: 0 0 1px #ccc;
167 |
168 | > ._sage-popup-trigger {
169 | font-size: 12px;
170 | }
171 |
172 | nav {
173 | background-size: 10px;
174 | height: 10px;
175 | width: 10px;
176 |
177 | &:hover {
178 | background-position: 0 -10px;
179 | }
180 | }
181 |
182 | > ol {
183 | display: none;
184 | margin-left: 32px;
185 | }
186 |
187 | li {
188 | color: #999;
189 | }
190 |
191 | &._sage-show {
192 | > ol {
193 | display: block;
194 | }
195 |
196 | nav {
197 | background-position: 0 -20px;
198 |
199 | &:hover {
200 | background-position: 0 -30px;
201 | }
202 | }
203 | }
204 | }
205 |
206 | a {
207 | color: $text-color;
208 | text-shadow: none;
209 |
210 | &:hover {
211 | color: $variable-name-color;
212 | border-bottom: 1px dotted $variable-name-color;
213 | }
214 | }
215 |
216 | //
217 | // TABS
218 | // --------------------------------------------------
219 |
220 | ul {
221 | list-style: none;
222 | padding-left: $spacing * 3px;
223 |
224 | &:not(._sage-tabs) {
225 | li {
226 | border-left: 1px dashed $border-color;
227 |
228 | > dl {
229 | border-left: none;
230 | }
231 | }
232 | }
233 |
234 | &._sage-tabs {
235 | margin: 0 0 0 $spacing * 3px;
236 | padding-left: 0;
237 | background: $main-background;
238 | border: $border;
239 | border-top: 0;
240 |
241 | li {
242 | background: $secondary-background;
243 | border: $border;
244 | cursor: pointer;
245 | display: inline-block;
246 | height: $spacing * 6px;
247 | margin: calc($spacing / 2) * 1px;
248 | padding: 0 2px + round($spacing * 2.5px);
249 | vertical-align: top;
250 |
251 | &:hover,
252 | &._sage-active-tab:hover {
253 | border-color: $border-color-hover;
254 | color: $variable-type-color-hover;
255 | }
256 |
257 | &._sage-active-tab {
258 | background: $main-background;
259 | border-top: 0;
260 | margin-top: -2px;
261 | height: 27px;
262 | line-height: 24px;
263 | }
264 |
265 | &:not(._sage-active-tab) {
266 | line-height: $spacing * 5px;
267 | }
268 | }
269 |
270 | li + li {
271 | margin-left: 0
272 | }
273 | }
274 |
275 | &:not(._sage-tabs) > li:not(:first-child) {
276 | display: none;
277 | }
278 | }
279 |
280 | dt:hover + dd > ul > li._sage-active-tab {
281 | border-color: $border-color-hover;
282 | color: $variable-type-color-hover;
283 | }
284 | }
285 |
286 | //
287 | // REPORT
288 | // --------------------------------------------------
289 |
290 | ._sage-report {
291 | border-collapse: collapse;
292 | empty-cells: show;
293 | border-spacing: 0;
294 |
295 | * {
296 | font-size: 12px;
297 | }
298 |
299 | dt {
300 | background: none;
301 | padding: calc($spacing/2) * 1px;
302 |
303 | ._sage-parent {
304 | min-width: 100%;
305 | overflow: hidden;
306 | text-overflow: ellipsis;
307 | white-space: nowrap;
308 | }
309 | }
310 |
311 | td,
312 | th {
313 | border: $border;
314 | padding: calc($spacing/2) * 1px;
315 | vertical-align: center;
316 | }
317 |
318 | th {
319 | cursor: alias;
320 | }
321 |
322 | td:first-child,
323 | th {
324 | font-weight: bold;
325 | background: $secondary-background;
326 | color: $variable-name-color;
327 | }
328 |
329 | td {
330 | background: $main-background;
331 | white-space: pre;
332 |
333 | > dl {
334 | padding: 0;
335 | }
336 | }
337 |
338 | pre {
339 | border-top: 0;
340 | border-right: 0;
341 | }
342 |
343 | th:first-child {
344 | background: none;
345 | border: 0;
346 | }
347 |
348 | td._sage-empty {
349 | background: #d33682 !important;
350 | }
351 |
352 | tr:hover {
353 | > td {
354 | box-shadow: 0 0 1px 0 $border-color-hover inset;
355 | }
356 |
357 | var {
358 | color: $variable-type-color-hover;
359 | }
360 | }
361 |
362 | ul._sage-tabs li._sage-active-tab {
363 | height: 20px;
364 | line-height: 17px;
365 | }
366 | }
367 |
368 | //
369 | // TRACE
370 | // --------------------------------------------------
371 | ._sage-trace {
372 | ._sage-source {
373 | line-height: round($spacing * 3.5) * 1px;
374 |
375 | span {
376 | padding-right: 1px;
377 | border-right: 3px inset $variable-type-color;
378 | }
379 |
380 | ._sage-highlight {
381 | background: $secondary-background;
382 | }
383 | }
384 |
385 | b { // line number
386 | min-width: 18px;
387 | display: inline-block;
388 | text-align: right;
389 | margin-right: 6px;
390 | color: $variable-name-color;
391 | }
392 |
393 | ._sage-blacklisted,
394 | ._sage-childless {
395 | > b { // line number
396 | margin-right: 22px;
397 | }
398 | }
399 |
400 | ._sage-blacklisted {
401 | filter: brightness(120%);
402 | }
403 |
404 | ._sage-parent {
405 | > var {
406 | > a {
407 | color: $variable-type-color;
408 | }
409 | }
410 | }
411 | }
412 |
413 | //
414 | // MISC
415 | // --------------------------------------------------
416 |
417 | // keyboard navigation caret
418 | ._sage-focused {
419 | @include keyboard-caret;
420 | }
421 |
422 | ._sage-microtime,
423 | ._sage-color-preview {
424 | box-shadow: 0 0 2px 0 #b6cedb;
425 | height: 16px;
426 | text-align: center;
427 | text-shadow: -1px 0 #839496, 0 1px #839496, 1px 0 #839496, 0 -1px #839496;
428 | width: 230px;
429 | color: #fdf6e3;
430 | }
431 |
--------------------------------------------------------------------------------
/resources/css/themes/aante-light.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Ante Aljinovic https://github.com/aljinovic
3 | */
4 |
5 | $main-background: #f8f8f8;
6 | $secondary-background: #f8f8f8;
7 |
8 | $text-color: #1d1e1e;
9 | $variable-name-color: #1d1e1e;
10 | $variable-type-color: #06f;
11 | $variable-type-color-hover: #f00;
12 |
13 | $border-color: #d7d7d7;
14 | $border-color-hover: #aaa;
15 |
16 | $caret-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAA8CAYAAAC5OOBJAAAAvklEQVR42mNgGMFAS0uLLSQk5HxoaOh/XDg4OLgdpwEBAQEGQAN+YtMIFH/i4ODAg9cFQNMbsWkOCgrKJMv5ID5Qipko/6M7PzAw0ImkAIQ5H0hvISv0gZpP+fn5qZAVfcBAkmEYBaNZcjRLjmbJUUCvqAIlDlAiASUWkjWDkiU0eTaSpBGUEZBy1E9QRiFWLzO2LEmU80GZHkcRhN/5oGIGVNzgKIbwOx9UwOErAIl2/igYzZKjWXI0S9IHAAASJijo0Ypj8AAAAABJRU5ErkJggg==");
17 |
18 | @mixin keyboard-caret() {
19 | box-shadow: 0 0 3px 2px $variable-type-color-hover;
20 | }
21 |
22 | @import "../base";
23 |
24 | ._sage {
25 | dt {
26 | font-weight: normal;
27 | margin-top: 4px;
28 | }
29 |
30 | > dl {
31 | background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0, #fff 15px);
32 | }
33 |
34 | dl dl {
35 | margin-top: 4px;
36 | padding-left: 25px;
37 | border-left: none;
38 | }
39 |
40 | > dl > dt {
41 | background: $secondary-background;
42 | }
43 |
44 | //
45 | // TABS
46 | // --------------------------------------------------
47 |
48 | ul {
49 | margin: 0;
50 | padding-left: 0;
51 |
52 | &:not(._sage-tabs) > li {
53 | border-left: 0;
54 | }
55 |
56 | &._sage-tabs {
57 | background: $secondary-background;
58 | border: $border;
59 | border-width: 0 1px 1px 1px;
60 | padding: 4px 0 0 12px;
61 | margin-left: -1px;
62 | margin-top: -1px;
63 |
64 | li,
65 | li + li {
66 | margin: 0 0 0 4px;
67 | }
68 |
69 | li {
70 | border-bottom-width: 0;
71 | height: $spacing * 6px + 1px;
72 |
73 |
74 | &:first-child {
75 | margin-left: 0;
76 | }
77 |
78 | &._sage-active-tab {
79 | border-top: $border;
80 | background: #fff;
81 | font-weight: bold;
82 | padding-top: 0;
83 | border-bottom: 1px solid #fff !important;
84 | margin-bottom: -1px;
85 | }
86 |
87 | &._sage-active-tab:hover {
88 | border-bottom: 1px solid #fff;
89 | }
90 | }
91 | }
92 |
93 | > li > pre {
94 | border: $border;
95 | }
96 | }
97 |
98 | dt:hover + dd > ul {
99 | border-color: $border-color-hover;
100 | }
101 |
102 | pre {
103 | background: #fff;
104 | margin-top: 4px;
105 | margin-left: 25px;
106 | }
107 |
108 | ._sage-popup-trigger:hover {
109 | color: $variable-type-color-hover;
110 | }
111 |
112 | ._sage-source ._sage-highlight {
113 | background: #cfc;
114 | }
115 |
116 | ._sage-source span {
117 | border-right: 3px inset #268bd2;
118 | }
119 | }
120 |
121 | //
122 | // REPORT
123 | // --------------------------------------------------
124 |
125 | ._sage-report {
126 | td {
127 | background: #fff;
128 |
129 | > dl {
130 | padding: 0;
131 | margin: 0;
132 |
133 | > dt._sage-parent {
134 | margin: 0;
135 | }
136 | }
137 | }
138 |
139 | td:first-child,
140 | td,
141 | th {
142 | padding: 2px 4px;
143 | }
144 |
145 | td._sage-empty {
146 | background: $border-color !important;
147 | }
148 |
149 | dd, dt {
150 | background: #fff;
151 | }
152 |
153 | tr:hover > td {
154 | box-shadow: none;
155 | background: #cfc;
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/resources/css/themes/original-light.scss:
--------------------------------------------------------------------------------
1 | $main-background: #f1f5f7;
2 | $secondary-background: #e3ecf0;
3 |
4 | $text-color: #1d1e1e;
5 | $variable-name-color: #1d1e1e;
6 | $variable-type-color: #0092db;
7 | $variable-type-color-hover: #5cb730;
8 |
9 | $border-color: #b6cedb;
10 | $border-color-hover: #0092db;
11 |
12 | $caret-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAA8CAMAAACO5hB7AAAACVBMVEX///82b4tctzBls0NMAAAAQklEQVR42rWRIQ4AAAgCwf8/2qgER2BeuwJsgAqIzekMfMryPL9XQSkobE6vwKcsz/N7FfPvh09lnPf/b+7673+f0uHuAZ/EdkNQAAAAAElFTkSuQmCC");
13 |
14 |
15 | @mixin keyboard-caret() {
16 | box-shadow: 0 0 3px 2px #5cb730;
17 | }
18 |
19 | @import "../base";
20 |
21 | ._sage {
22 | > dl > dt,
23 | ul._sage-tabs {
24 | background: linear-gradient(to bottom, #fff 0, $main-background 100%);
25 | }
26 |
27 | dl:not(:last-of-type) dt:not(._sage-show) {
28 | border-bottom: 0;
29 | }
30 |
31 | //._sage dl._sage-trace dd ul._sage-tabs li._sage-active-tab
32 | & ul._sage-tabs li {
33 | background: $secondary-background;
34 | }
35 |
36 | & > dl._sage-trace {
37 | > dt {
38 | background: linear-gradient(to bottom, $main-background 0, #fff 100%);
39 | }
40 |
41 | dt:not(:last-of-type):not(._sage-show) {
42 | border-bottom: 0;
43 | }
44 |
45 | > dd > ul._sage-tabs > li._sage-active-tab {
46 | background: #fff;
47 | }
48 | }
49 |
50 | ._sage-source ._sage-highlight {
51 | background: #f0eb96;
52 | }
53 |
54 | ._sage-source > div {
55 | margin-top: -1px;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/resources/css/themes/original.scss:
--------------------------------------------------------------------------------
1 | $main-background: #e0eaef;
2 | $secondary-background: #c1d4df;
3 |
4 | $text-color: #1d1e1e;
5 | $variable-name-color: #1d1e1e;
6 | $variable-type-color: #0092db;
7 | $variable-type-color-hover: #5cb730;
8 |
9 | $border-color: #b6cedb;
10 | $border-color-hover: #0092db;
11 |
12 | $caret-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAA8CAMAAACO5hB7AAAACVBMVEX///82b4tctzBls0NMAAAAQklEQVR42rWRIQ4AAAgCwf8/2qgER2BeuwJsgAqIzekMfMryPL9XQSkobE6vwKcsz/N7FfPvh09lnPf/b+7673+f0uHuAZ/EdkNQAAAAAElFTkSuQmCC");
13 |
14 |
15 | @mixin keyboard-caret() {
16 | box-shadow: 0 0 3px 2px #5cb730;
17 | }
18 |
19 | @import "../base";
20 |
21 | ._sage {
22 | > dl > dt {
23 | background: linear-gradient(to bottom, #e3ecf0 0, #c0d4df 100%);
24 | }
25 |
26 | ul._sage-tabs {
27 | background: linear-gradient(to bottom, #9dbed0 0px, #b2ccda 100%);
28 | }
29 |
30 | & > dl:not(._sage-trace) > dd > ul._sage-tabs li {
31 | background: $main-background;
32 |
33 | &._sage-active-tab {
34 | background: $secondary-background;
35 | }
36 | }
37 |
38 | & > dl._sage-trace > dt {
39 | background: linear-gradient(to bottom, #c0d4df 0px, #e3ecf0 100%);
40 | }
41 |
42 | ._sage-source ._sage-highlight {
43 | background: #f0eb96;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/resources/css/themes/solarized-dark.scss:
--------------------------------------------------------------------------------
1 | $spacing: 5;
2 |
3 | $main-background: #002b36; // base 03
4 | $secondary-background: #073642; // base 02
5 |
6 | $text-color: #839496; // base 0
7 | $variable-name-color: #93a1a1; // base 1
8 | $variable-type-color: #268bd2; // blue
9 | $variable-type-color-hover: #2aa198; // cyan
10 |
11 | $border-color: #586e75; // base 01
12 | $border-color-hover: #268bd2; // blue
13 |
14 |
15 | $caret-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAA8CAMAAACO5hB7AAAA7VBMVEVYbnWToaH///9YbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnWToaGToaGToaGToaGToaGToaGToaGToaGToaFYbnWToaFYbnVYbnWToaGToaFYbnWToaFYbnWToaFYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnWToaGToaGToaGToaGToaGToaGToaGToaGToaGToaGToaGToaGToaGToaFYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnWToaGToaGToaGToaGToaGToaGToaGToaGToaFYbnWToaEeRtj1AAAATXRSTlMAAACLab0B7iP2R7CL9iPuaUewAb36F0/QnPoX0JxPYQaurepi6QV8FhV7MzFhM6586RYFMa3qYnsVBpNQ+ZGOGJ3PkM9QkZAY+Y6TnV7gnYsAAAEcSURBVHhebdHVbgMxEEBRu8sUZmqapBBoSuGUmWb+/3NayxtrLOe+Hck745XZgd6/eb3CVcKt7HmR+gKxlCGOETG5vFbOoahR6NxI5zHt6nuq+2dqnqfzzH3mfcz70vb8H6MJt5u6a96hS30E4PjEFgAEp2fKNojKUfVEOoS044ex7u3YPE/nmfvM+5j3pe17T9oe/77O41w+n4vnrbrwZxbTshVhvtx5Kb8vliRLRWmeSQSTjJq/El5x5fUXYmNN9hcQC5y4g/hGvVksNtT8451rns2IScb7mn567ll2GNpWr9YWfvQgzWsKs8HOA/m960g6rjTzA8HAV/NHwiOmPLwDKA/J/gggYsRVgFvqbr/fpWYv90zzZKKs9Qfx00Jx65oxLAAAAABJRU5ErkJggg==");
16 |
17 | @mixin keyboard-caret() {
18 | box-shadow: 0 0 3px 2px #859900 inset; // green
19 | border-radius: 7px;
20 | }
21 |
22 | @import "../base";
23 |
24 | body {
25 | background: $secondary-background;
26 | color: #fff; // for non-_sage elements to remain at least semi-readable
27 | }
28 |
29 | ._sage {
30 | background: $secondary-background;
31 | box-shadow: 0 0 5px 3px $secondary-background;
32 |
33 | > dl > dt,
34 | ul._sage-tabs {
35 | box-shadow: 4px 0 2px -3px $variable-type-color inset;
36 | }
37 | }
38 |
39 | ._sage ul._sage-tabs li._sage-active-tab {
40 | padding-top: 7px;
41 | height: 34px;
42 | }
43 |
--------------------------------------------------------------------------------
/resources/css/themes/solarized.scss:
--------------------------------------------------------------------------------
1 | $spacing: 5;
2 |
3 | $main-background: #fdf6e3; // base 3
4 | $secondary-background: #eee8d5; // base 2
5 |
6 | $text-color: #657b83; // base 00
7 | $variable-name-color: #586e75; // base 01
8 | $variable-type-color: #268bd2; // blue
9 | $variable-type-color-hover: #2aa198; // cyan
10 |
11 | $border-color: #93a1a1; // base 1
12 | $border-color-hover: #268bd2; // blue
13 |
14 |
15 | $caret-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAA8CAMAAACO5hB7AAAA7VBMVEVYbnWToaH///9YbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnWToaGToaGToaGToaGToaGToaGToaGToaGToaFYbnWToaFYbnVYbnWToaGToaFYbnWToaFYbnWToaFYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnWToaGToaGToaGToaGToaGToaGToaGToaGToaGToaGToaGToaGToaGToaFYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnVYbnWToaGToaGToaGToaGToaGToaGToaGToaGToaFYbnWToaEeRtj1AAAATXRSTlMAAACLab0B7iP2R7CL9iPuaUewAb36F0/QnPoX0JxPYQaurepi6QV8FhV7MzFhM6586RYFMa3qYnsVBpNQ+ZGOGJ3PkM9QkZAY+Y6TnV7gnYsAAAEcSURBVHhebdHVbgMxEEBRu8sUZmqapBBoSuGUmWb+/3NayxtrLOe+Hck745XZgd6/eb3CVcKt7HmR+gKxlCGOETG5vFbOoahR6NxI5zHt6nuq+2dqnqfzzH3mfcz70vb8H6MJt5u6a96hS30E4PjEFgAEp2fKNojKUfVEOoS044ex7u3YPE/nmfvM+5j3pe17T9oe/77O41w+n4vnrbrwZxbTshVhvtx5Kb8vliRLRWmeSQSTjJq/El5x5fUXYmNN9hcQC5y4g/hGvVksNtT8451rns2IScb7mn567ll2GNpWr9YWfvQgzWsKs8HOA/m960g6rjTzA8HAV/NHwiOmPLwDKA/J/gggYsRVgFvqbr/fpWYv90zzZKKs9Qfx00Jx65oxLAAAAABJRU5ErkJggg==");
16 |
17 | @mixin keyboard-caret() {
18 | box-shadow: 0 0 3px 2px #859900 inset; // green
19 | border-radius: 7px;
20 | }
21 |
22 | @import "../base";
23 |
24 | ._sage > dl > dt,
25 | ._sage ul._sage-tabs {
26 | box-shadow: 4px 0 2px -3px $variable-type-color inset;
27 | }
28 |
29 | ._sage ul._sage-tabs li._sage-active-tab {
30 | padding-top: 7px;
31 | height: 34px;
32 | }
33 |
--------------------------------------------------------------------------------
/resources/js/base.js:
--------------------------------------------------------------------------------
1 | if (typeof _sageInitialized === 'undefined') {
2 | _sageInitialized = 1;
3 | const _sage = {
4 | visiblePluses: [], // all visible toggle carets
5 | currentPlus: -1, // currently selected caret
6 | pinnedParents: [], // fixed to top parents
7 |
8 | debounce: (callback, wait = 100) => {
9 | let timeoutId;
10 | return (...args) => {
11 | clearTimeout(timeoutId);
12 | timeoutId = setTimeout(() => callback(...args), wait)
13 | };
14 | },
15 |
16 | processScroll: function () {
17 | let i = _sage.pinnedParents.length;
18 | while (i--) {
19 | let el = _sage.pinnedParents[i]
20 | let pinnedClone = el.sageClone
21 | if (window.scrollY <= pinnedClone.sageMinY || window.scrollY >= pinnedClone.sageMaxY) {
22 | _sage.pinnedParents.splice(i, 1)
23 | pinnedClone.remove()
24 | el.sageClone = null
25 | }
26 | }
27 |
28 | let all = document.querySelectorAll('._sage')
29 | i = all.length;
30 | while (i--) {
31 | const sage = all[i]
32 |
33 | if (!_sage.isPinningNeeded(sage)) {
34 | return;
35 | }
36 |
37 | let totalPinnedHeight = 0
38 | sage.querySelectorAll('._sage-clone').forEach(function (clone) {
39 | totalPinnedHeight += clone.offsetHeight
40 | })
41 | sage.querySelectorAll('dt._sage-parent._sage-show:not(._sage-clone)').forEach(function (parent) {
42 | if (_sage.pinnedParents.includes(parent)) {
43 | return;
44 | }
45 |
46 | const children = parent.nextElementSibling; //
47 |
48 | if (!_sage.isPinningNeeded(children)) {
49 | return;
50 | }
51 |
52 | // we create a pinned clone to float - for many technical reasons :)
53 | const clone = parent.cloneNode(true);
54 | const rect = children.getBoundingClientRect();
55 |
56 | clone.sageMinY = rect.top + window.scrollY;
57 | clone.sageMaxY = rect.bottom + window.scrollY;
58 | clone.style.width = rect.width + 'px'
59 | clone.style.top = totalPinnedHeight + 'px'
60 | clone.style.position = 'fixed'
61 | clone.style.opacity = 0.9
62 | clone.classList.add('_sage-clone')
63 |
64 | totalPinnedHeight += parent.offsetHeight
65 | parent.sageClone = clone
66 | _sage.pinnedParents.push(parent)
67 | parent.after(clone)
68 | })
69 |
70 | // my brain & time management restrictions do not support side by side sage outputs
71 | break;
72 | }
73 | },
74 |
75 | isPinningNeeded: function (el) {
76 | const rect = el.getBoundingClientRect()
77 |
78 | if (rect.height === 0) {
79 | return false;
80 | }
81 |
82 |
83 | // if top is lower than the viewport
84 | if (rect.top >= window.innerHeight) {
85 | return false
86 | }
87 |
88 | // if top is visible we don't need to scroll it to view
89 | if (rect.top > 0) {
90 | return false
91 | }
92 |
93 | // if bottom is above view port, we scrolled passed it and it's invisible now
94 | if (rect.bottom < 25) {
95 | return false
96 | }
97 |
98 | return true
99 | },
100 |
101 | selectText: function (element) {
102 | const selection = window.getSelection(),
103 | range = document.createRange();
104 |
105 | range.selectNodeContents(element);
106 | selection.removeAllRanges();
107 | selection.addRange(range);
108 | },
109 |
110 | each: function (selector, callback) {
111 | Array.prototype.slice.call(document.querySelectorAll(selector), 0).forEach(callback)
112 | },
113 |
114 | hasClass: function (target, className = '_sage-show') {
115 | if (!target.classList) {
116 | return false;
117 | }
118 |
119 | return target.classList.contains(className);
120 | },
121 |
122 | addClass: function (target, className = '_sage-show') {
123 | target.classList.add(className);
124 | },
125 |
126 | removeClass: function (target, className = '_sage-show') {
127 | target.classList.remove(className);
128 | return target;
129 | },
130 |
131 | next: function (element) {
132 | do {
133 | element = element.nextElementSibling;
134 | } while (element && element.tagName !== 'DD');
135 |
136 | return element;
137 | },
138 |
139 | toggle: function (element, hide) {
140 | if (typeof hide === 'undefined') {
141 | hide = _sage.hasClass(element);
142 | }
143 |
144 | if (hide) {
145 | _sage.removeClass(element);
146 | } else {
147 | _sage.addClass(element);
148 | }
149 |
150 | // also open up child element if there's only one
151 | let parent = _sage.next(element);
152 | if (parent && parent.childElementCount === 1) {
153 | parent = parent.children[0]; // reuse variable cause I can
154 |
155 | // parent is checked in case of empty when array("\n") is dumped
156 | if (parent && _sage.hasClass(parent, '_sage-parent')) {
157 | _sage.toggle(parent, hide)
158 | }
159 | }
160 | },
161 |
162 | toggleChildren: function (element, hide) {
163 | const parent = _sage.next(element)
164 | , nodes = parent.getElementsByClassName('_sage-parent');
165 | let i = nodes.length;
166 |
167 | if (typeof hide === 'undefined') {
168 | hide = _sage.hasClass(element);
169 | }
170 |
171 | while (i--) {
172 | _sage.toggle(nodes[i], hide);
173 | }
174 | _sage.toggle(element, hide);
175 | },
176 |
177 | toggleAll: function (show) {
178 | const elements = document.getElementsByClassName('_sage-parent')
179 | let i = elements.length
180 |
181 | while (i--) {
182 | if (show) {
183 | _sage.addClass(elements[i]);
184 | } else {
185 | _sage.removeClass(elements[i]);
186 | }
187 | }
188 | },
189 |
190 | switchAllToNextTraceTab: function () {
191 | document.querySelectorAll('._sage-trace>dd>._sage-tabs>._sage-active-tab').forEach(function (element) {
192 | const nextTab = element.nextSibling;
193 | if (nextTab) {
194 | _sage.switchTab(nextTab);
195 | }
196 | })
197 | },
198 |
199 | switchTab: function (target) {
200 | let lis, el = target, index = 0;
201 |
202 | _sage.removeClass(
203 | target.parentNode.getElementsByClassName('_sage-active-tab')[0],
204 | '_sage-active-tab'
205 | );
206 | _sage.addClass(target, '_sage-active-tab');
207 |
208 | // take the index of clicked title tab and make the same n-th content tab visible
209 | while (el = el.previousSibling) {
210 | el.nodeType === 1 && index++;
211 | }
212 |
213 | lis = target.parentNode.nextSibling.childNodes;
214 | for (let i = 0; i < lis.length; i++) {
215 | lis[i].style.display = i === index ? 'block' : 'none';
216 | }
217 | },
218 |
219 | isInsideSage: function (el) {
220 | for (; ;) {
221 | // if it's a pinned clone scroll to original element on click
222 | if (_sage.hasClass(el, '_sage-clone')) {
223 | let scrollTo = el.offsetHeight;
224 | if (el.style.top !== '0px') {
225 | scrollTo *= 2;
226 | }
227 | window.scroll(0, el.sageMinY - scrollTo);
228 | return false;
229 | }
230 |
231 | el = el.parentNode;
232 | if (!el || _sage.hasClass(el, '_sage')) {
233 | break;
234 | }
235 | }
236 |
237 | return !!el;
238 | },
239 |
240 | fetchVisiblePluses: function () {
241 | _sage.visiblePluses = [];
242 | _sage.each('._sage nav, ._sage-tabs>li:not(._sage-active-tab)', function (el) {
243 | if (el.offsetWidth !== 0 || el.offsetHeight !== 0) {
244 | _sage.visiblePluses.push(el)
245 | }
246 | });
247 | },
248 |
249 | // some custom implementations screw up the JS when they see or
250 | // this method survives minification
251 | tag: function (contents) {
252 | return '<' + contents + '>';
253 | },
254 |
255 | openInNewWindow: function (_sageContainer) {
256 | let newWindow;
257 |
258 | if (newWindow = window.open()) {
259 | newWindow.document.open();
260 | newWindow.document.write(
261 | _sage.tag('html')
262 | + _sage.tag('head')
263 | + 'Sage ☯ (' + new Date().toISOString() + ') '
264 | + _sage.tag('meta charset="utf-8"')
265 | + document.getElementsByClassName('_sage-js')[0].outerHTML
266 | + document.getElementsByClassName('_sage-css')[0].outerHTML
267 | + _sage.tag('/head')
268 | + _sage.tag('body')
269 | + ' '
270 | + ''
271 | + _sageContainer.parentNode.outerHTML
272 | + '
'
273 | + _sage.tag('/body')
274 | );
275 | newWindow.document.close();
276 | }
277 | },
278 |
279 | sortTable: function (table, column, header) {
280 | const tbody = table.tBodies[0];
281 |
282 | const collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'});
283 |
284 | const direction = (typeof header.sage_direction === 'undefined') ? 1 : header.sage_direction
285 | header.sage_direction = -1 * direction;
286 |
287 | [].slice.call(table.tBodies[0].rows)
288 | .sort(function (a, b) {
289 | return direction * collator.compare(a.cells[column].textContent, b.cells[column].textContent)
290 | })
291 | .forEach(function (el) {
292 | tbody.appendChild(el);
293 | });
294 | },
295 |
296 | keyCallBacks: {
297 | cleanup: function (i) {
298 | const focusedClass = '_sage-focused';
299 | const prevElement = document.querySelector('.' + focusedClass);
300 | prevElement && _sage.removeClass(prevElement, focusedClass);
301 |
302 | if (i !== -1) {
303 | const el = _sage.visiblePluses[i];
304 | _sage.addClass(el, focusedClass);
305 |
306 | const offsetTop = function (el) {
307 | return el.offsetTop + (el.offsetParent ? offsetTop(el.offsetParent) : 0);
308 | };
309 |
310 | const top = offsetTop(el) - (window.innerHeight / 2);
311 | window.scrollTo(0, top);
312 | }
313 |
314 | _sage.currentPlus = i;
315 | },
316 |
317 | moveCursor: function (up, i) {
318 | if (up) {
319 | if (--i < 0) {
320 | i = _sage.visiblePluses.length - 1;
321 | }
322 | } else {
323 | if (++i >= _sage.visiblePluses.length) {
324 | i = 0;
325 | }
326 | }
327 |
328 | _sage.keyCallBacks.cleanup(i);
329 | return false;
330 | }
331 | }
332 | };
333 |
334 | window.addEventListener('click', function (e) {
335 | let target = e.target
336 | , tagName = target.tagName;
337 |
338 | if (!_sage.isInsideSage(target)) {
339 | return;
340 | }
341 |
342 | // auto-select name of variable
343 | if (tagName === 'DFN') {
344 | _sage.selectText(target);
345 | target = target.parentNode;
346 | } else if (tagName === 'VAR') { // stupid workaround for misc elements
347 | target = target.parentNode; // to not stop event from further propagating
348 | tagName = target.tagName;
349 | } else if (tagName === 'TH') {
350 | if (!e.ctrlKey) {
351 | _sage.sortTable(target.parentNode.parentNode.parentNode, target.cellIndex, target)
352 | }
353 | return false;
354 | }
355 |
356 | // switch tabs
357 | if (tagName === 'LI' && _sage.hasClass(target.parentNode, '_sage-tabs')) {
358 | if (!_sage.hasClass(target, '_sage-active-tab')) {
359 | _sage.switchTab(target);
360 | if (_sage.currentPlus !== -1) {
361 | _sage.fetchVisiblePluses();
362 | }
363 | }
364 | return false;
365 | }
366 |
367 | // handle clicks on the navigation caret
368 | if (tagName === 'NAV') {
369 | // special case for nav in footer
370 | if (target.parentNode.tagName === 'FOOTER') {
371 | target = target.parentNode;
372 | _sage.toggle(target)
373 | } else {
374 | // ensure doubleclick has different behaviour, see below
375 | setTimeout(function () {
376 | const timer = parseInt(target._sageTimer, 10);
377 | if (timer > 0) {
378 | target._sageTimer--;
379 | } else {
380 | _sage.toggleChildren(target.parentNode); //
381 | if (_sage.currentPlus !== -1) {
382 | _sage.fetchVisiblePluses();
383 | }
384 | }
385 | }, 300);
386 | }
387 |
388 | e.stopPropagation();
389 | return false;
390 | } else if (_sage.hasClass(target, '_sage-parent')) {
391 | _sage.toggle(target);
392 | if (_sage.currentPlus !== -1) {
393 | _sage.fetchVisiblePluses();
394 | }
395 | return false;
396 | } else if (_sage.hasClass(target, '_sage-ide-link')) {
397 | e.preventDefault()
398 | fetch(target.href);
399 | return false;
400 | } else if (_sage.hasClass(target, '_sage-popup-trigger')) {
401 | let _sageContainer = target.parentNode;
402 | if (_sageContainer.tagName === 'FOOTER') {
403 | _sageContainer = _sageContainer.previousSibling;
404 | } else {
405 | while (_sageContainer && !_sage.hasClass(_sageContainer, '_sage-parent')) {
406 | _sageContainer = _sageContainer.parentNode;
407 | }
408 | }
409 |
410 | _sage.openInNewWindow(_sageContainer);
411 | } else if (tagName === 'PRE' && e.detail === 3) { // triple click pre to select it all
412 | _sage.selectText(target);
413 | }
414 | }, false);
415 |
416 | window.addEventListener('dblclick', function (e) {
417 | const target = e.target;
418 | if (!_sage.isInsideSage(target)) {
419 | return;
420 | }
421 |
422 | if (target.tagName === 'NAV') {
423 | target._sageTimer = 2;
424 | _sage.toggleAll(_sage.hasClass(target));
425 | if (_sage.currentPlus !== -1) {
426 | _sage.fetchVisiblePluses();
427 | }
428 | e.stopPropagation();
429 | }
430 | }, false);
431 |
432 | // keyboard navigation
433 | window.onkeydown = function (e) { // direct assignment is used to have priority over ex FAYT
434 | // todo use e.key https://www.toptal.com/developers/keycode
435 | const keyCode = e.keyCode;
436 | let currentPlus = _sage.currentPlus;
437 |
438 | // user pressed ctrl+f
439 | if (keyCode === 70 && e.ctrlKey) {
440 | _sage.toggleAll(true);
441 | // we are probably more interested in the Arguments, or Callee object tab in traces, whichever exists
442 | _sage.switchAllToNextTraceTab(true);
443 | return;
444 | }
445 |
446 | // do nothing if alt/ctrl key is pressed or if we're actually typing somewhere
447 | if (['INPUT', 'TEXTAREA'].includes(e.target.tagName) || e.altKey || e.ctrlKey) {
448 | return;
449 | }
450 |
451 | if (keyCode === 9) { // TAB jumps out of navigation
452 | _sage.keyCallBacks.cleanup(-1);
453 |
454 | return;
455 | // todo 's' too
456 | } else if (keyCode === 68) { // 'd' : toggles navigation on/off
457 | if (currentPlus === -1) {
458 | _sage.fetchVisiblePluses();
459 | return _sage.keyCallBacks.moveCursor(false, currentPlus);
460 | } else {
461 | _sage.keyCallBacks.cleanup(-1);
462 | return false;
463 | }
464 | } else {
465 | if (currentPlus === -1) {
466 | return;
467 | }
468 |
469 | if (keyCode === 38) { // ARROW UP : moves up
470 | return _sage.keyCallBacks.moveCursor(true, currentPlus);
471 | } else if (keyCode === 40) { // ARROW DOWN : down
472 | return _sage.keyCallBacks.moveCursor(false, currentPlus);
473 | }
474 | }
475 |
476 |
477 | let currentNav = _sage.visiblePluses[currentPlus];
478 | if (currentNav.tagName === 'LI') { // we're on a trace tab
479 | if (keyCode === 32 || keyCode === 13) { // SPACE/ENTER
480 | _sage.switchTab(currentNav);
481 | _sage.fetchVisiblePluses();
482 | return _sage.keyCallBacks.moveCursor(true, currentPlus);
483 | } else if (keyCode === 39) { // arrows
484 | return _sage.keyCallBacks.moveCursor(false, currentPlus);
485 | } else if (keyCode === 37) {
486 | return _sage.keyCallBacks.moveCursor(true, currentPlus);
487 | }
488 | }
489 |
490 | // we are on a regular/footer [+]
491 |
492 | currentNav = currentNav.parentNode; // simple dump
493 |
494 | if (currentNav.tagName === 'FOOTER') {
495 | if (keyCode === 32 || keyCode === 13) { // SPACE/ENTER : toggles
496 | _sage.toggle(currentNav);
497 |
498 | return false;
499 | }
500 | }
501 |
502 | if (keyCode === 32 || keyCode === 13) { // SPACE/ENTER : toggles
503 | _sage.toggle(currentNav);
504 | _sage.fetchVisiblePluses();
505 | return false;
506 | } else if (keyCode === 39 || keyCode === 37) { // ARROW LEFT/RIGHT : respectively hides/shows and traverses
507 | const visible = _sage.hasClass(currentNav);
508 | const hide = keyCode === 37;
509 |
510 | if (visible) {
511 | _sage.toggleChildren(currentNav, hide); // expand/collapse all children if immediate ones are showing
512 | } else {
513 | if (hide) { // LEFT
514 | // traverse to parent and THEN hide
515 | do {
516 | currentNav = currentNav.parentNode
517 | } while (currentNav && currentNav.tagName !== 'DD');
518 |
519 | if (currentNav) {
520 | currentNav = currentNav.previousElementSibling;
521 |
522 | currentPlus = -1;
523 | const parentPlus = currentNav.querySelector('nav');
524 | while (parentPlus !== _sage.visiblePluses[++currentPlus]) {
525 | }
526 | _sage.keyCallBacks.cleanup(currentPlus)
527 | } else { // we are at root
528 | currentNav = _sage.visiblePluses[currentPlus].parentNode;
529 | }
530 | }
531 | _sage.toggle(currentNav, hide);
532 | }
533 | _sage.fetchVisiblePluses();
534 | return false;
535 | }
536 | };
537 |
538 | window.addEventListener('load', function () { // colorize microtime results relative to others
539 | const elements = Array.prototype.slice.call(document.querySelectorAll('._sage-microtime'), 0);
540 | let min = Infinity
541 | , max = -Infinity;
542 |
543 | elements.forEach(function (el) {
544 | const val = parseFloat(el.innerHTML);
545 |
546 | if (min > val) {
547 | min = val;
548 | }
549 | if (max < val) {
550 | max = val;
551 | }
552 | });
553 |
554 | elements.forEach(function (el) {
555 | const val = parseFloat(el.innerHTML);
556 | const ratio = 1 - (val - min) / (max - min);
557 |
558 | el.style.background = 'hsl(' + Math.round(ratio * 120) + ',60%,70%)';
559 | });
560 | });
561 |
562 | window.addEventListener('scroll', _sage.debounce(_sage.processScroll));
563 | }
564 |
565 | // debug purposes only, removed in minified source
566 | function clg(i) {
567 | if (!window.console) {
568 | return;
569 | }
570 | const l = arguments.length;
571 | let o = 0;
572 | while (o < l) console.log(arguments[o++])
573 | }
574 |
--------------------------------------------------------------------------------