diff --git a/src/Editor/Editor.php b/src/Editor/Editor.php index 0c02857..3bf69a3 100644 --- a/src/Editor/Editor.php +++ b/src/Editor/Editor.php @@ -6,6 +6,7 @@ use NoccyLabs\JsonEdit\List\TreeList; use NoccyLabs\JsonEdit\Settings; use NoccyLabs\JsonEdit\Terminal\Terminal; use NoccyLabs\JsonEdit\Tree\ArrayNode; +use NoccyLabs\JsonEdit\Tree\CollapsibleNode; use NoccyLabs\JsonEdit\Tree\ObjectNode; use NoccyLabs\JsonEdit\Tree\Tree; use NoccyLabs\JsonEdit\Tree\ValueNode; @@ -79,7 +80,7 @@ class Editor /** * Load a document from array/object * - * @param mixed $document +< * @param mixed $document * @return void */ public function loadDocument(mixed $document): void @@ -147,8 +148,7 @@ class Editor $this->redrawEditor(); $this->modified = true; } else { - $this->term->setCursor(1, $h); - echo "\e[97;41mCan only delete from object, array\e[K\e[0m"; + $this->showMessage("\e[97;41mCan only delete from object, array"); } break; @@ -176,7 +176,7 @@ class Editor 'array' => "Insert Array[]", ], 'Insert'); $this->redrawInfoBar([ '^C' => 'Cancel', '↑/↓' => 'Select option', 'Enter' => 'Accept' ]); - $sel = $menu->display(5, 2, 30, 0, "value"); + $sel = $menu->display(0, 0, 30, 0, "value"); $this->redrawEditor(); switch ($sel) { case 'value': @@ -211,6 +211,15 @@ class Editor $this->running = false; break; + case "+": + $node = $this->list->getNodeForIndex($this->currentRow); + if ($node instanceof CollapsibleNode) { + $node->collapse(!$node->isCollapsed()); + $this->list->parseTree(); + $this->redrawEditor(); + } + break; + case "q": Settings::$editorQuotedKeys = !Settings::$editorQuotedKeys; $this->redrawEditor(); @@ -269,13 +278,12 @@ class Editor $readFrom = $this->ask("\e[33mRead from:\e[0m ", ""); - $this->term->setCursor(1, $h); if (!file_exists($readFrom)) { - echo "\e[97;41mFile does not exist\e[K\e[0m"; + $this->showMessage("\e[97;41mFile does not exist"); return; } if (!is_readable($readFrom)) { - echo "\e[97;41mFile not readable\e[K\e[0m"; + $this->showMessage("\e[97;41mFile not readable"); return; } $ext = strtolower(pathinfo($readFrom, PATHINFO_EXTENSION)); @@ -288,7 +296,7 @@ class Editor $doc = Yaml::parseFile($readFrom); break; default: - echo "\e[97;41mUnable to read format: {$ext}\e[K\e[0m"; + $this->showMessage("\e[97;41mUnable to read format: {$ext}"); return; } @@ -300,8 +308,7 @@ class Editor $this->list->parseTree(); $this->redrawEditor(); - $this->term->setCursor(1, $h); - echo "\e[97;42mLoaded {$readFrom}\e[K\e[0m"; + $this->showMessage("\e[97;42mLoaded {$readFrom}"); } /** @@ -329,8 +336,7 @@ class Editor file_put_contents($saveTo, Yaml::dump($doc)); break; default: - $this->term->setCursor(1, $h); - echo "\e[97;41mUnable to write format: {$ext}\e[K\e[0m"; + $this->showMessage("\e[97;41mUnable to write format: {$ext}"); } $this->filename = $saveTo; @@ -338,8 +344,7 @@ class Editor $this->modified = false; $this->redrawEditor(); - $this->term->setCursor(1, $h); - echo "\e[97;42mWrote to {$saveTo}\e[K\e[0m"; + $this->showMessage("\e[97;42mWrote to {$saveTo}"); } @@ -375,20 +380,20 @@ class Editor ## Doing stuff ### Adding keys or values - To add a key or a value, navigate to a value in an array or object, or to a specific array or object, and press "i". You will be prompted for the value, and for objects the key. - You can also press "I" to add arrays and objects. Just select what you want to add in the menu and press enter. ### Editing keys - You can edit keys on objects. For this, press "E". ### Editing values - To edit a value, press "e". The value is verbatim JSON, so strings should be quoted and all that. Anything that is unparsable JSON will be used as is, resulting in a string. ### Loading and Saving files + To load a file, press ^R and enter the filename to read. To write to a file, press ^W and enter the filename to write to. + + ### YAML or JSON? + There is no need to select YAML or JSON mode. All operations work the same, and the format is determined on load or save. # Disclaimer @@ -472,8 +477,9 @@ class Editor $this->redrawEditor(); } } else { - $this->term->setCursor(1, $h); - echo "\e[97;41mCan only edit keys on objects\e[K\e[0m"; + // $this->term->setCursor(1, $h); + // echo "\e[97;41mCan only edit keys on objects\e[K\e[0m"; + $this->showMessage("\e[97;41mCan only edit keys on objects"); } } @@ -505,8 +511,7 @@ class Editor $this->redrawInfoBar(); } } else { - $this->term->setCursor(1, $h); - echo "\e[97;41mCan not edit array/object\e[K\e[0m"; + $this->showMessage("\e[97;41mCan not edit array/object"); } } @@ -702,4 +707,12 @@ class Editor //echo " \e[1m{$key}\e[2m \e[3m{$info}\e[0m \e[90m\u{2502}\e[0m"; } + private function showMessage(string $message): void + { + [$w,$h] = $this->term->getSize(); + $this->term->setCursor(1, $h); + echo $message."\e[K\e[0m"; + + } + } diff --git a/src/Editor/Menu.php b/src/Editor/Menu.php index f23f321..294e230 100644 --- a/src/Editor/Menu.php +++ b/src/Editor/Menu.php @@ -27,6 +27,8 @@ class Menu private int $index = 0; + private int $scroll = 0; + public function __construct(private Terminal $terminal, private array $items, private string $title = 'Menu') { @@ -35,7 +37,16 @@ class Menu public function display(int $left, int $top, int $width, int $height = 0, string|int|null $value): mixed { - if ($height == 0) $height = count($this->items) + 4; + [$w,$h] = $this->terminal->getSize(); + if ($height == 0) { + $height = min($h - 5, count($this->items) + 4); + } + if ($left == 0) { + $left = round(($w - $width) / 2); + } + if ($top == 0) { + $top = round(($h - $height) / 2); + } $this->redraw($left, $top, $width, $height); @@ -69,7 +80,15 @@ class Menu } private function redraw(int $left, int $top, int $width, int $height) { + $visibleItems = $height - 4; + + $scrollTop = $this->scroll; + $scrollVisible = $visibleItems / count($this->items); // / $visibleItems; + $scrollBottom = $scrollTop + $scrollVisible; + $thumbTop = round($scrollTop * $visibleItems); + $thumbBottom = round($visibleItems * $scrollBottom); + // draw head echo "\e[40;37m"; $this->terminal @@ -83,12 +102,23 @@ class Menu for ($n = 0; $n < $visibleItems; $n++) { $key = $keys[$n]??null; $item = " " . ($key ? ($this->items[$key]) : null); - $item = $item . str_repeat(" ", $width - 2 - mb_strlen($item)) . "\e[40;37m"; + $item = $item . str_repeat(" ", $width - 2 - $this->itemlen($item)) . "\e[40;37m"; $item = (($n == $this->index)?"\e[37;44m":"\e[40;37m") . $item; + if ($n >= $thumbTop && $n <= $thumbBottom) { + $scrollbar = "\e[97;1m".self::U_EDGE_VERTICAL_THUMB."\e[40;37;22m"; + } else { + $scrollbar = "\e[37;2m".self::U_EDGE_VERTICAL_SCROLL."\e[40;37;22m"; + } $this->terminal - ->writeAt($left, $top + 3 + $n, self::U_EDGE_VERTICAL.$item.self::U_EDGE_VERTICAL_THUMB); + ->writeAt($left, $top + 3 + $n, self::U_EDGE_VERTICAL.$item.$scrollbar); } echo "\e[0m"; } + private function itemlen(string $item): int + { + $item = preg_replace('<\e\[.+?m>', '', $item); + return mb_strlen($item); + } + } \ No newline at end of file diff --git a/src/Editor/MessageBox.php b/src/Editor/MessageBox.php index addcdc0..c0c2e76 100644 --- a/src/Editor/MessageBox.php +++ b/src/Editor/MessageBox.php @@ -35,7 +35,16 @@ class MessageBox { $wrapped = explode("\n", wordwrap($this->text, $width - 4, cut_long_words:true)); - if ($height == 0) $height = count($wrapped) + 4; + [$w,$h] = $this->terminal->getSize(); + if ($height == 0) { + $height = min($h - 5, count($wrapped) + 4); + } + if ($left == 0) { + $left = round(($w - $width) / 2); + } + if ($top == 0) { + $top = round(($h - $height) / 2); + } $maxScroll = (count($wrapped) > $height) ? count($wrapped) - $height + 4 : 0; diff --git a/src/List/TreeList.php b/src/List/TreeList.php index 604dc02..51f75e1 100644 --- a/src/List/TreeList.php +++ b/src/List/TreeList.php @@ -47,12 +47,14 @@ class TreeList implements Countable 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); } @@ -172,8 +174,18 @@ class TreeList implements Countable } if ($entry->node instanceof ArrayNode) { echo "[" . (Settings::$compactGroups ? "…]":""); + if ($entry->node->isCollapsed()) { + echo " \e[90m\u{25ba} \e[2m".count($entry->node->items)."\e[22m"; + } else { + echo " \e[90m\u{25bc}"; + } } elseif ($entry->node instanceof ObjectNode) { echo "{" . (Settings::$compactGroups ? "…}":""); + if ($entry->node->isCollapsed()) { + echo " \e[90m\u{25ba} \e[2m".count($entry->node->properties)."\e[22m"; + } else { + echo " \e[90m\u{25bc}"; + } } elseif ($entry->node instanceof ValueNode) { $value = $entry->node->value; echo match (gettype($value)) {