*/ public array $list = []; private const TREE_FOLD_OPEN = "\u{f0fe}"; private const TREE_FOLD_CLOSE = "\u{f146}"; private const TREE_INDENT_GUIDE = "\u{258f} "; private const TREE_INDENT = " "; public function __construct(private Tree $tree) { } public function count(): int { return count($this->list); } public function parseTree(): void { $this->list = []; $this->parseNode($this->tree->root, [], null); } private function parseNode(Node $node, array $path, string|int|null $key): void { if (str_contains($key, '/')) { $ekey = urlencode($key); } else { $ekey = $key; } $level = count($path); $entryKey = join("/", $path) . (is_null($key) ? "/" : (is_int($key) ? sprintf("/%d", $key) : sprintf("/%s", $ekey))); $entry = new Entry(depth: $level, key: $key, node: $node); $this->list[$entryKey] = $entry; if ($node instanceof ArrayNode) { $index = 0; if ($node->isCollapsed()) return; foreach ($node->items as $item) { $this->parseNode($item, [ ...$path, $key ], $index++); } if (!Settings::$compactGroups) $this->list[$entryKey.'$'] = new Entry(depth: $level, key: $key, node: $node, closer: true); } elseif ($node instanceof ObjectNode) { if ($node->isCollapsed()) return; foreach ($node->properties as $nodekey=>$item) { $this->parseNode($item, [ ...$path, $key ], $nodekey); } if (!Settings::$compactGroups) $this->list[$entryKey.'$'] = new Entry(depth: $level, key: $key, node: $node, closer: true); } } public function getPathForIndex(int $index): ?string { return array_keys($this->list)[$index]??null; } public function getIndexForPath(string $path): ?int { return array_search($path, array_keys($this->list)); } public function getNodeForPath(string $path): ?Node { $entry = $this->list[$path]??null; return $entry ? $entry->node : null; } public function getNodeForIndex(int $index): ?Node { $key = array_keys($this->list)[$index]??null; $entry = $this->list[$key]??null; if (!$entry) return null; return $entry->node; } public function getEntryForIndex(int $index): ?Entry { $key = array_keys($this->list)[$index]??null; $entry = $this->list[$key]??null; if (!$entry) return null; return $entry; } public function findNearestCollection(int $index, bool $ignoreSelf = false): ?int { $path = $this->getPathForIndex($index); if ($ignoreSelf) { $path = dirname($path); } while (strlen($path) > 0) { $entry = $this->list[$path]; $node = $entry->node; if ($node instanceof ArrayNode || $node instanceof ObjectNode) { return $this->getIndexForPath($path); } $path = dirname($path); } // $depth = $this->getEntryForIndex($index)->depth; // if ($ignoreSelf) $depth = max(0, $depth - 1); // echo "{start={$depth}}"; sleep(1); // while ($index >= 0) { // $entry = $this->getEntryForIndex($index); // if ($entry->depth < $depth) { // $node = $entry->node; // if ($node instanceof ArrayNode || $node instanceof ObjectNode) { // return $index; // } // } // $index--; // } return null; } public function drawEntry(int $screenRow, int $entryRow, int $columns, bool $selected): void { $keys = array_keys($this->list); echo "\e[{$screenRow};1H"; if ($entryRow >= count($keys)) { echo ($selected?"\e[44;97m":"\e[0;90m")."\e[K"; if ($entryRow == count($keys)) echo str_repeat("\u{2574}",$columns); echo "\e[0m"; //else echo "\e[90m\u{2805}\e[0m"; return; } $key = $keys[$entryRow]; if (!isset($this->list[$key])) { echo "\e[0m\e[K\e[31mE_NO_KEY_IN_LIST\e[0m"; return; } $entry = $this->list[$key]; if (!$entry) { echo "\e[0m\e[K\e[31mE_NO_ENTRY_IN_LIST\e[0m"; return; } echo "\e[{$screenRow};1H\e[0m"; echo ($selected?"\e[44;97m":"\e[0;37m")."\e[K"; echo "\e[90m".str_repeat( Settings::$indentationGuides ? self::TREE_INDENT_GUIDE : self::TREE_INDENT, $entry->depth )."\e[37m"; if ($entry->closer) { if (!Settings::$compactGroups) { if ($entry->node instanceof ArrayNode) { echo "]"; } elseif ($entry->node instanceof ObjectNode) { echo "}"; } } return; } if (Settings::$collapseBefore) { if ($entry->node instanceof CollapsibleNode) { if ($entry->node->isCollapsed()) { //echo "\e[90m\u{25ba} \e[37m"; echo "\e[90m".self::TREE_FOLD_OPEN." \e[37m"; } else { //echo "\e[90m\u{25bc} \e[37m"; echo "\e[90m".self::TREE_FOLD_CLOSE." \e[37m"; } } } if (!is_null($entry->key)) { echo (is_int($entry->key) ?"\e[36;2m\u{e0b6}\e[7m#{$entry->key}\e[27m\u{e0b4}\e[22;37m " :(Settings::$editorQuotedKeys ? "\e[36m\"{$entry->key}\":\e[37m " : "\e[36m{$entry->key}:\e[37m " )); } if ($entry->node instanceof ArrayNode) { echo "[" . (Settings::$compactGroups ? "…]":""); if ($entry->node->isCollapsed()) { if (!Settings::$collapseBefore) echo " \e[90m\u{25ba}"; echo " \e[90;2m[".count($entry->node->items)."]\e[22m"; if (!Settings::$compactGroups) echo "\e[37m ]"; } else { if (!Settings::$collapseBefore) echo " \e[90m\u{25bc}"; } } elseif ($entry->node instanceof ObjectNode) { echo "{" . (Settings::$compactGroups ? "…}":""); if ($entry->node->isCollapsed()) { if (!Settings::$collapseBefore) echo " \e[90m\u{25ba}"; echo " \e[90;2;3m".join(", ",array_keys($entry->node->properties))."\e[22;23m"; if (!Settings::$compactGroups) echo "\e[37m }"; } else { if (!Settings::$collapseBefore) echo " \e[90m\u{25bc}"; } } elseif ($entry->node instanceof ValueNode) { $value = $entry->node->value; echo match (gettype($value)) { 'string' => "\e[33m", 'integer' => "\e[34m", 'double' => "\e[32m", 'boolean' => "\e[35m", 'NULL' => "\e[31m", default => "", }; echo json_encode($entry->node->value, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); } } }