Implement folding (use '+' key), clean up code

This commit is contained in:
Chris 2024-10-03 01:44:10 +02:00
parent 989cc89e4b
commit 1cccfc0187
4 changed files with 90 additions and 26 deletions

View File

@ -6,6 +6,7 @@ use NoccyLabs\JsonEdit\List\TreeList;
use NoccyLabs\JsonEdit\Settings; use NoccyLabs\JsonEdit\Settings;
use NoccyLabs\JsonEdit\Terminal\Terminal; use NoccyLabs\JsonEdit\Terminal\Terminal;
use NoccyLabs\JsonEdit\Tree\ArrayNode; use NoccyLabs\JsonEdit\Tree\ArrayNode;
use NoccyLabs\JsonEdit\Tree\CollapsibleNode;
use NoccyLabs\JsonEdit\Tree\ObjectNode; use NoccyLabs\JsonEdit\Tree\ObjectNode;
use NoccyLabs\JsonEdit\Tree\Tree; use NoccyLabs\JsonEdit\Tree\Tree;
use NoccyLabs\JsonEdit\Tree\ValueNode; use NoccyLabs\JsonEdit\Tree\ValueNode;
@ -79,7 +80,7 @@ class Editor
/** /**
* Load a document from array/object * Load a document from array/object
* *
* @param mixed $document < * @param mixed $document
* @return void * @return void
*/ */
public function loadDocument(mixed $document): void public function loadDocument(mixed $document): void
@ -147,8 +148,7 @@ class Editor
$this->redrawEditor(); $this->redrawEditor();
$this->modified = true; $this->modified = true;
} else { } else {
$this->term->setCursor(1, $h); $this->showMessage("\e[97;41mCan only delete from object, array");
echo "\e[97;41mCan only delete from object, array\e[K\e[0m";
} }
break; break;
@ -176,7 +176,7 @@ class Editor
'array' => "Insert Array[]", 'array' => "Insert Array[]",
], 'Insert'); ], 'Insert');
$this->redrawInfoBar([ '^C' => 'Cancel', '↑/↓' => 'Select option', 'Enter' => 'Accept' ]); $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(); $this->redrawEditor();
switch ($sel) { switch ($sel) {
case 'value': case 'value':
@ -211,6 +211,15 @@ class Editor
$this->running = false; $this->running = false;
break; break;
case "+":
$node = $this->list->getNodeForIndex($this->currentRow);
if ($node instanceof CollapsibleNode) {
$node->collapse(!$node->isCollapsed());
$this->list->parseTree();
$this->redrawEditor();
}
break;
case "q": case "q":
Settings::$editorQuotedKeys = !Settings::$editorQuotedKeys; Settings::$editorQuotedKeys = !Settings::$editorQuotedKeys;
$this->redrawEditor(); $this->redrawEditor();
@ -269,13 +278,12 @@ class Editor
$readFrom = $this->ask("\e[33mRead from:\e[0m ", ""); $readFrom = $this->ask("\e[33mRead from:\e[0m ", "");
$this->term->setCursor(1, $h);
if (!file_exists($readFrom)) { if (!file_exists($readFrom)) {
echo "\e[97;41mFile does not exist\e[K\e[0m"; $this->showMessage("\e[97;41mFile does not exist");
return; return;
} }
if (!is_readable($readFrom)) { if (!is_readable($readFrom)) {
echo "\e[97;41mFile not readable\e[K\e[0m"; $this->showMessage("\e[97;41mFile not readable");
return; return;
} }
$ext = strtolower(pathinfo($readFrom, PATHINFO_EXTENSION)); $ext = strtolower(pathinfo($readFrom, PATHINFO_EXTENSION));
@ -288,7 +296,7 @@ class Editor
$doc = Yaml::parseFile($readFrom); $doc = Yaml::parseFile($readFrom);
break; break;
default: default:
echo "\e[97;41mUnable to read format: {$ext}\e[K\e[0m"; $this->showMessage("\e[97;41mUnable to read format: {$ext}");
return; return;
} }
@ -300,8 +308,7 @@ class Editor
$this->list->parseTree(); $this->list->parseTree();
$this->redrawEditor(); $this->redrawEditor();
$this->term->setCursor(1, $h); $this->showMessage("\e[97;42mLoaded {$readFrom}");
echo "\e[97;42mLoaded {$readFrom}\e[K\e[0m";
} }
/** /**
@ -329,8 +336,7 @@ class Editor
file_put_contents($saveTo, Yaml::dump($doc)); file_put_contents($saveTo, Yaml::dump($doc));
break; break;
default: default:
$this->term->setCursor(1, $h); $this->showMessage("\e[97;41mUnable to write format: {$ext}");
echo "\e[97;41mUnable to write format: {$ext}\e[K\e[0m";
} }
$this->filename = $saveTo; $this->filename = $saveTo;
@ -338,8 +344,7 @@ class Editor
$this->modified = false; $this->modified = false;
$this->redrawEditor(); $this->redrawEditor();
$this->term->setCursor(1, $h); $this->showMessage("\e[97;42mWrote to {$saveTo}");
echo "\e[97;42mWrote to {$saveTo}\e[K\e[0m";
} }
@ -375,20 +380,20 @@ class Editor
## Doing stuff ## Doing stuff
### Adding keys or values ### 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. 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. 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 ### Editing keys
You can edit keys on objects. For this, press "E". You can edit keys on objects. For this, press "E".
### Editing values ### 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. 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 ### 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 # Disclaimer
@ -472,8 +477,9 @@ class Editor
$this->redrawEditor(); $this->redrawEditor();
} }
} else { } else {
$this->term->setCursor(1, $h); // $this->term->setCursor(1, $h);
echo "\e[97;41mCan only edit keys on objects\e[K\e[0m"; // 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(); $this->redrawInfoBar();
} }
} else { } else {
$this->term->setCursor(1, $h); $this->showMessage("\e[97;41mCan not edit array/object");
echo "\e[97;41mCan not edit array/object\e[K\e[0m";
} }
} }
@ -702,4 +707,12 @@ class Editor
//echo " \e[1m{$key}\e[2m \e[3m{$info}\e[0m \e[90m\u{2502}\e[0m"; //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";
}
} }

View File

@ -27,6 +27,8 @@ class Menu
private int $index = 0; private int $index = 0;
private int $scroll = 0;
public function __construct(private Terminal $terminal, private array $items, private string $title = 'Menu') 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 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); $this->redraw($left, $top, $width, $height);
@ -69,7 +80,15 @@ class Menu
} }
private function redraw(int $left, int $top, int $width, int $height) { private function redraw(int $left, int $top, int $width, int $height) {
$visibleItems = $height - 4; $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 // draw head
echo "\e[40;37m"; echo "\e[40;37m";
$this->terminal $this->terminal
@ -83,12 +102,23 @@ class Menu
for ($n = 0; $n < $visibleItems; $n++) { for ($n = 0; $n < $visibleItems; $n++) {
$key = $keys[$n]??null; $key = $keys[$n]??null;
$item = " " . ($key ? ($this->items[$key]) : 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; $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 $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"; echo "\e[0m";
} }
private function itemlen(string $item): int
{
$item = preg_replace('<\e\[.+?m>', '', $item);
return mb_strlen($item);
}
} }

View File

@ -35,7 +35,16 @@ class MessageBox
{ {
$wrapped = explode("\n", wordwrap($this->text, $width - 4, cut_long_words:true)); $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; $maxScroll = (count($wrapped) > $height) ? count($wrapped) - $height + 4 : 0;

View File

@ -47,12 +47,14 @@ class TreeList implements Countable
if ($node instanceof ArrayNode) { if ($node instanceof ArrayNode) {
$index = 0; $index = 0;
if ($node->isCollapsed()) return;
foreach ($node->items as $item) { foreach ($node->items as $item) {
$this->parseNode($item, [ ...$path, $key ], $index++); $this->parseNode($item, [ ...$path, $key ], $index++);
} }
if (!Settings::$compactGroups) if (!Settings::$compactGroups)
$this->list[$entryKey.'$'] = new Entry(depth: $level, key: $key, node: $node, closer: true); $this->list[$entryKey.'$'] = new Entry(depth: $level, key: $key, node: $node, closer: true);
} elseif ($node instanceof ObjectNode) { } elseif ($node instanceof ObjectNode) {
if ($node->isCollapsed()) return;
foreach ($node->properties as $nodekey=>$item) { foreach ($node->properties as $nodekey=>$item) {
$this->parseNode($item, [ ...$path, $key ], $nodekey); $this->parseNode($item, [ ...$path, $key ], $nodekey);
} }
@ -172,8 +174,18 @@ class TreeList implements Countable
} }
if ($entry->node instanceof ArrayNode) { if ($entry->node instanceof ArrayNode) {
echo "[" . (Settings::$compactGroups ? "…]":""); 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) { } elseif ($entry->node instanceof ObjectNode) {
echo "{" . (Settings::$compactGroups ? "…}":""); 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) { } elseif ($entry->node instanceof ValueNode) {
$value = $entry->node->value; $value = $entry->node->value;
echo match (gettype($value)) { echo match (gettype($value)) {