value;
$this->snapshot = $model->snapshot;
if ($this->lazy === \false) {
// no lazy-loading
$html = $this->renderVar($value);
$json = $snapshot = null;
} elseif ($this->lazy && (\is_array($value) && $value || \is_object($value))) {
// full lazy-loading
$html = '';
$snapshot = $this->collectingMode ? null : $this->snapshot;
$json = $value;
} else {
// lazy-loading of collapsed parts
$html = $this->renderVar($value);
$snapshot = $this->snapshotSelection;
$json = null;
}
} finally {
$this->parents = $this->snapshot = $this->above = [];
$this->snapshotSelection = null;
}
$location = null;
if ($model->location && $this->sourceLocation) {
[$file, $line, $code] = $model->location;
$uri = Helpers::editorUri($file, $line);
$location = Helpers::formatHtml('', $uri ?? '#', $file, $line, $uri ? "\nClick to open in editor" : '') . Helpers::encodeString($code, 50) . " 📍";
}
return '
100 ? "\n" : '') . '>' . $location . $html . "
\n";
}
public function renderAsText(\stdClass $model, array $colors = []) : string
{
try {
$this->snapshot = $model->snapshot;
$this->lazy = \false;
$s = $this->renderVar($model->value);
} finally {
$this->parents = $this->snapshot = $this->above = [];
}
$s = $colors ? self::htmlToAnsi($s, $colors) : $s;
$s = \htmlspecialchars_decode(\strip_tags($s), \ENT_QUOTES | \ENT_HTML5);
$s = \str_replace('…', '...', $s);
$s .= \substr($s, -1) === "\n" ? '' : "\n";
if ($this->sourceLocation && ([$file, $line] = $model->location)) {
$s .= "in {$file}:{$line}\n";
}
return $s;
}
/**
* @param mixed $value
* @param string|int|null $keyType
*/
private function renderVar($value, int $depth = 0, $keyType = null) : string
{
switch (\true) {
case $value === null:
return 'null';
case \is_bool($value):
return '' . ($value ? 'true' : 'false') . '';
case \is_int($value):
return '' . $value . '';
case \is_float($value):
return '' . self::jsonEncode($value) . '';
case \is_string($value):
return $this->renderString($value, $depth, $keyType);
case \is_array($value):
case $value->type === Value::TypeArray:
return $this->renderArray($value, $depth);
case $value->type === Value::TypeRef:
return $this->renderVar($this->snapshot[$value->value], $depth, $keyType);
case $value->type === Value::TypeObject:
return $this->renderObject($value, $depth);
case $value->type === Value::TypeNumber:
return '' . Helpers::escapeHtml($value->value) . '';
case $value->type === Value::TypeText:
return '' . Helpers::escapeHtml($value->value) . '';
case $value->type === Value::TypeStringHtml:
case $value->type === Value::TypeBinaryHtml:
return $this->renderString($value, $depth, $keyType);
case $value->type === Value::TypeResource:
return $this->renderResource($value, $depth);
default:
throw new \Exception('Unknown type');
}
}
/**
* @param string|Value $str
* @param string|int|null $keyType
*/
private function renderString($str, int $depth, $keyType) : string
{
if ($keyType === self::TypeArrayKey) {
$indent = ' ' . \str_repeat('| ', $depth - 1) . ' ';
return '' . "'" . (\is_string($str) ? Helpers::escapeHtml($str) : \str_replace("\n", "\n" . $indent, $str->value)) . "'" . '';
} elseif ($keyType !== null) {
$classes = [Value::PropertyPublic => 'tracy-dump-public', Value::PropertyProtected => 'tracy-dump-protected', Value::PropertyDynamic => 'tracy-dump-dynamic', Value::PropertyVirtual => 'tracy-dump-virtual'];
$indent = ' ' . \str_repeat('| ', $depth - 1) . ' ';
$title = \is_string($keyType) ? ' title="declared in ' . Helpers::escapeHtml($keyType) . '"' : null;
return '' . (\is_string($str) ? Helpers::escapeHtml($str) : "'" . \str_replace("\n", "\n" . $indent, $str->value) . "'") . '';
} elseif (\is_string($str)) {
$len = Helpers::utf8Length($str);
return ' 1 ? ' title="' . $len . ' characters"' : '') . '>' . "'" . Helpers::escapeHtml($str) . "'" . '';
} else {
$unit = $str->type === Value::TypeStringHtml ? 'characters' : 'bytes';
$count = \substr_count($str->value, "\n");
if ($count) {
$collapsed = $indent1 = $toggle = null;
$indent = ' ';
if ($depth) {
$collapsed = $count >= $this->collapseSub;
$indent1 = ' ' . \str_repeat('| ', $depth) . '';
$indent = ' ' . \str_repeat('| ', $depth) . ' ';
$toggle = 'string' . "\n";
}
return $toggle . '' . $indent1 . ''" . \str_replace("\n", "\n" . $indent, $str->value) . "'" . ($depth ? "\n" : '') . '
';
}
return 'length > 1 ? " title=\"{$str->length} {$unit}\"" : '') . '>' . "'" . $str->value . "'" . '';
}
}
/**
* @param array|Value $array
*/
private function renderArray($array, int $depth) : string
{
$out = 'array (';
if (\is_array($array)) {
$items = $array;
$count = \count($items);
$out .= $count . ')';
} elseif ($array->items === null) {
return $out . $array->length . ') …';
} else {
$items = $array->items;
$count = $array->length ?? \count($items);
$out .= $count . ')';
if ($array->id && isset($this->parents[$array->id])) {
return $out . ' RECURSION';
} elseif ($array->id && ($array->depth < $depth || isset($this->above[$array->id]))) {
if ($this->lazy !== \false) {
$ref = new Value(Value::TypeRef, $array->id);
$this->copySnapshot($ref);
return '" . $out . '';
} elseif ($this->hash) {
return $out . (isset($this->above[$array->id]) ? ' see above' : ' see below');
}
}
}
if (!$count) {
return $out;
}
$collapsed = $depth ? $this->lazy === \false || $depth === 1 ? $count >= $this->collapseSub : \true : (\is_int($this->collapseTop) ? $count >= $this->collapseTop : $this->collapseTop);
$span = 'lazy !== \false) {
$array = isset($array->id) ? new Value(Value::TypeRef, $array->id) : $array;
$this->copySnapshot($array);
return $span . " data-tracy-dump='" . self::jsonEncode($array) . "'>" . $out . '';
}
$out = $span . '>' . $out . "\n" . '';
$indent = ' ' . \str_repeat('| ', $depth) . '';
$this->parents[$array->id ?? null] = $this->above[$array->id ?? null] = \true;
foreach ($items as $info) {
[$k, $v, $ref] = $info + [2 => null];
$out .= $indent . $this->renderVar($k, $depth + 1, self::TypeArrayKey) . ' => ' . ($ref && $this->hash ? '&' . $ref . ' ' : '') . ($tmp = $this->renderVar($v, $depth + 1)) . (\substr($tmp, -6) === '
' ? '' : "\n");
}
if ($count > \count($items)) {
$out .= $indent . "…\n";
}
unset($this->parents[$array->id ?? null]);
return $out . '';
}
private function renderObject(Value $object, int $depth) : string
{
$editorAttributes = '';
if ($this->classLocation && $object->editor) {
$editorAttributes = Helpers::formatHtml(' title="Declared in file % on line %%%" data-tracy-href="%"', $object->editor->file, $object->editor->line, $object->editor->url ? "\nCtrl-Click to open in editor" : '', "\nAlt-Click to expand/collapse all child nodes", $object->editor->url);
}
$pos = \strrpos($object->value, '\\');
$out = '' . ($pos ? Helpers::escapeHtml(\substr($object->value, 0, $pos + 1)) . '' . Helpers::escapeHtml(\substr($object->value, $pos + 1)) . '' : Helpers::escapeHtml($object->value)) . '' . ($object->id && $this->hash ? ' #' . $object->id . '' : '');
if ($object->items === null) {
return $out . ' …';
} elseif (!$object->items) {
return $out;
} elseif ($object->id && isset($this->parents[$object->id])) {
return $out . ' RECURSION';
} elseif ($object->id && ($object->depth < $depth || isset($this->above[$object->id]))) {
if ($this->lazy !== \false) {
$ref = new Value(Value::TypeRef, $object->id);
$this->copySnapshot($ref);
return '" . $out . '';
} elseif ($this->hash) {
return $out . (isset($this->above[$object->id]) ? ' see above' : ' see below');
}
}
$collapsed = $object->collapsed ?? ($depth ? $this->lazy === \false || $depth === 1 ? \count($object->items) >= $this->collapseSub : \true : (\is_int($this->collapseTop) ? \count($object->items) >= $this->collapseTop : $this->collapseTop));
$span = 'lazy !== \false) {
$value = $object->id ? new Value(Value::TypeRef, $object->id) : $object;
$this->copySnapshot($value);
return $span . " data-tracy-dump='" . self::jsonEncode($value) . "'>" . $out . '';
}
$out = $span . '>' . $out . "\n" . '';
$indent = ' ' . \str_repeat('| ', $depth) . '';
$this->parents[$object->id] = $this->above[$object->id] = \true;
foreach ($object->items as $info) {
[$k, $v, $type, $ref] = $info + [2 => Value::PropertyVirtual, null];
$out .= $indent . $this->renderVar($k, $depth + 1, $type) . ': ' . ($ref && $this->hash ? '&' . $ref . ' ' : '') . ($tmp = $this->renderVar($v, $depth + 1)) . (\substr($tmp, -6) === '
' ? '' : "\n");
}
if ($object->length > \count($object->items)) {
$out .= $indent . "…\n";
}
unset($this->parents[$object->id]);
return $out . '';
}
private function renderResource(Value $resource, int $depth) : string
{
$out = '' . Helpers::escapeHtml($resource->value) . ' ' . ($this->hash ? '@' . \substr($resource->id, 1) . '' : '');
if (!$resource->items) {
return $out;
} elseif (isset($this->above[$resource->id])) {
if ($this->lazy !== \false) {
$ref = new Value(Value::TypeRef, $resource->id);
$this->copySnapshot($ref);
return '" . $out . '';
}
return $out . ' see above';
} else {
$this->above[$resource->id] = \true;
$out = "{$out}\n";
foreach ($resource->items as [$k, $v]) {
$out .= ' ' . \str_repeat('| ', $depth) . '' . $this->renderVar($k, $depth + 1, Value::PropertyVirtual) . ': ' . ($tmp = $this->renderVar($v, $depth + 1)) . (\substr($tmp, -6) === '
' ? '' : "\n");
}
return $out . '';
}
}
private function copySnapshot($value) : void
{
if ($this->collectingMode) {
return;
}
if ($this->snapshotSelection === null) {
$this->snapshotSelection = [];
}
if (\is_array($value)) {
foreach ($value as [, $v]) {
$this->copySnapshot($v);
}
} elseif ($value instanceof Value && $value->type === Value::TypeRef) {
if (!isset($this->snapshotSelection[$value->value])) {
$ref = $this->snapshotSelection[$value->value] = $this->snapshot[$value->value];
$this->copySnapshot($ref);
}
} elseif ($value instanceof Value && $value->items) {
foreach ($value->items as [, $v]) {
$this->copySnapshot($v);
}
}
}
public static function jsonEncode($snapshot) : string
{
$old = @\ini_set('serialize_precision', '-1');
// @ may be disabled
try {
return \json_encode($snapshot, \JSON_HEX_APOS | \JSON_HEX_AMP | \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES);
} finally {
if ($old !== \false) {
\ini_set('serialize_precision', $old);
}
}
}
private static function htmlToAnsi(string $s, array $colors) : string
{
$stack = ['0'];
$s = \preg_replace_callback('#<\\w+(?: class="tracy-dump-(\\w+)")?[^>]*>|\\w+>#', function ($m) use($colors, &$stack) : string {
if ($m[0][1] === '/') {
\array_pop($stack);
} else {
$stack[] = isset($m[1], $colors[$m[1]]) ? $colors[$m[1]] : '0';
}
return "\x1b[" . \end($stack) . 'm';
}, $s);
$s = \preg_replace('/\\e\\[0m(\\n*)(?=\\e)/', '$1', $s);
return $s;
}
}