diff --git a/src/Editor/Editor.php b/src/Editor/Editor.php index eda6f66..78ddff9 100644 --- a/src/Editor/Editor.php +++ b/src/Editor/Editor.php @@ -172,35 +172,29 @@ class Editor // } // break; - case 'i': - $coll = $this->list->findNearestCollection($this->currentRow); - $node = $this->list->getNodeForIndex($coll); - if ($node instanceof ObjectNode) { - $key = $this->ask("\e[97mkey:\e[0m "); - if ($key === null) { - $this->redrawInfoBar(); + case 'I': + $menu = new Menu($this->term, [ + 'value' => "Insert Value", + 'object' => "Insert Object{}", + 'array' => "Insert Array[]", + ], 'Insert'); + $sel = $menu->display(5, 2, 30, 0, "value"); + $this->redrawEditor(); + switch ($sel) { + case 'value': + $this->doInsertValue(); + break; + case 'array': + $this->doInsertValue('[]'); + break; + case 'object': + $this->doInsertValue('{}'); break; - } } + break; - $value = $this->ask("\e[97mvalue:\e[0m "); - if ($value !== null) { - $value = json_decode($value); - $valueNode = match (true) { - is_array($value) => new ArrayNode([]), - is_object($value) => new ObjectNode([]), - default => new ValueNode($value) - }; - if ($node instanceof ArrayNode) { - $node->append($valueNode); - } elseif ($node instanceof ObjectNode) { - $node->set($key, $valueNode); - } - $this->list->parseTree(); - $this->redrawEditor(); - } else { - $this->redrawInfoBar(); - } + case 'i': + $this->doInsertValue(); break; case 'Q': @@ -288,6 +282,39 @@ class Editor } + private function doInsertValue(mixed $value = null): void + { + $coll = $this->list->findNearestCollection($this->currentRow); + $node = $this->list->getNodeForIndex($coll); + if ($node instanceof ObjectNode) { + $key = $this->ask("\e[97mkey:\e[0m "); + if ($key === null) { + $this->redrawInfoBar(); + return; + } + } + + if ($value === null) + $value = $this->ask("\e[97mvalue:\e[0m "); + if ($value !== null) { + $value = json_decode($value); + $valueNode = match (true) { + is_array($value) => new ArrayNode([]), + is_object($value) => new ObjectNode([]), + default => new ValueNode($value) + }; + if ($node instanceof ArrayNode) { + $node->append($valueNode); + } elseif ($node instanceof ObjectNode) { + $node->set($key, $valueNode); + } + $this->list->parseTree(); + $this->redrawEditor(); + } else { + $this->redrawInfoBar(); + } + } + public function ask(string $prompt, $value = ''): ?string { $plainPrompt = preg_replace('<\e\[.+?m>', '', $prompt); diff --git a/src/Editor/Menu.php b/src/Editor/Menu.php new file mode 100644 index 0000000..dc1eafa --- /dev/null +++ b/src/Editor/Menu.php @@ -0,0 +1,94 @@ +items) + 4; + + $this->redraw($left, $top, $width, $height); + + $showing = true; + while ($showing) { + while (null === ($ch = $this->terminal->readKey())) { + usleep(10000); + } + if (mb_strlen($ch) == 1) { + if ($ch == "\x03") { + $showing = false; + } + if ($ch == "\r") { + return array_keys($this->items)[$this->index]; + } + } else { + if ($ch == "k{UP}") { + $this->index--; + if ($this->index < 0) $this->index = count($this->items) - 1; + $this->redraw($left, $top, $width, $height); + } elseif ($ch == "k{DOWN}") { + $this->index++; + if ($this->index >= count($this->items)) $this->index = 0; + $this->redraw($left, $top, $width, $height); + } + } + } + + + return null; + } + + private function redraw(int $left, int $top, int $width, int $height) { + $visibleItems = $height - 4; + // draw head + echo "\e[40;37m"; + $this->terminal + ->writeAt($left, $top + 0, self::U_CORNER_ROUNDED_TOPLEFT.str_repeat(self::U_EDGE_HORIZONTAL,$width - 2).self::U_CORNER_ROUNDED_TOPRIGHT) + ->writeAt($left, $top + 1, self::U_EDGE_VERTICAL.str_repeat(" ",$width - 2).self::U_EDGE_VERTICAL) + ->writeAt($left + 2, $top + 1, "\e[1m" . $this->title . "\e[22m") + ->writeAt($left, $top + 2, self::U_EDGE_VERTICAL_RIGHT.str_repeat(self::U_EDGE_HORIZONTAL,$width - 2).self::U_EDGE_VERTICAL_LEFT) + ->writeAt($left, $top + $height - 1, self::U_CORNER_BOTTOMLEFT.str_repeat(self::U_EDGE_HORIZONTAL,$width - 2).self::U_CORNER_BOTTOMRIGHT) + ; + $keys = array_keys($this->items); + 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 = (($n == $this->index)?"\e[37;44m":"\e[40;37m") . $item; + $this->terminal + ->writeAt($left, $top + 3 + $n, self::U_EDGE_VERTICAL.$item.self::U_EDGE_VERTICAL_SCROLL); + } + echo "\e[0m"; + } + +} \ No newline at end of file diff --git a/src/List/TreeList.php b/src/List/TreeList.php index c47ed71..86c6be8 100644 --- a/src/List/TreeList.php +++ b/src/List/TreeList.php @@ -31,8 +31,14 @@ class TreeList implements Countable 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", $key))); + $entryKey = join("/", $path) . (is_null($key) ? "/" : (is_int($key) ? sprintf("/%d", $key) : sprintf("/%s", $ekey))); $entry = new Entry(depth: $level, key: $key, node: $node); diff --git a/src/Terminal/Terminal.php b/src/Terminal/Terminal.php index e44249f..9e7cf1d 100644 --- a/src/Terminal/Terminal.php +++ b/src/Terminal/Terminal.php @@ -73,11 +73,19 @@ class Terminal return [ $this->columns, $this->lines ]; } - public function setCursor(int $column, int $row, bool $visible = false): void + public function setCursor(int $column, int $row, bool $visible = false): self { if ($row > 0 && $column > 0) printf("\e[%d;%dH", $row, $column); echo "\e[?25".($visible?"h":"l"); + return $this; + } + + public function writeAt(int $column, int $row, string $text, bool $visible = false): self + { + $this->setCursor($column, $row, $visible); + echo $text; + return $this; } private string $inputBuffer = '';