Add popup menus for actions
This commit is contained in:
parent
3caca13e5c
commit
88abeaa896
@ -172,35 +172,29 @@ class Editor
|
|||||||
// }
|
// }
|
||||||
// break;
|
// break;
|
||||||
|
|
||||||
case 'i':
|
case 'I':
|
||||||
$coll = $this->list->findNearestCollection($this->currentRow);
|
$menu = new Menu($this->term, [
|
||||||
$node = $this->list->getNodeForIndex($coll);
|
'value' => "Insert Value",
|
||||||
if ($node instanceof ObjectNode) {
|
'object' => "Insert Object{}",
|
||||||
$key = $this->ask("\e[97mkey:\e[0m ");
|
'array' => "Insert Array[]",
|
||||||
if ($key === null) {
|
], 'Insert');
|
||||||
$this->redrawInfoBar();
|
$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;
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
|
|
||||||
$value = $this->ask("\e[97mvalue:\e[0m ");
|
case 'i':
|
||||||
if ($value !== null) {
|
$this->doInsertValue();
|
||||||
$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();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'Q':
|
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
|
public function ask(string $prompt, $value = ''): ?string
|
||||||
{
|
{
|
||||||
$plainPrompt = preg_replace('<\e\[.+?m>', '', $prompt);
|
$plainPrompt = preg_replace('<\e\[.+?m>', '', $prompt);
|
||||||
|
94
src/Editor/Menu.php
Normal file
94
src/Editor/Menu.php
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\JEdit\Editor;
|
||||||
|
|
||||||
|
use NoccyLabs\JEdit\Terminal\Terminal;
|
||||||
|
|
||||||
|
class Menu
|
||||||
|
{
|
||||||
|
|
||||||
|
const U_CORNER_TOPLEFT = "\u{250c}";
|
||||||
|
const U_CORNER_TOPRIGHT = "\u{2510}";
|
||||||
|
const U_CORNER_BOTTOMLEFT = "\u{2514}";
|
||||||
|
const U_CORNER_BOTTOMRIGHT = "\u{2518}";
|
||||||
|
|
||||||
|
const U_CORNER_ROUNDED_TOPLEFT = "\u{256d}";
|
||||||
|
const U_CORNER_ROUNDED_TOPRIGHT = "\u{256e}";
|
||||||
|
const U_CORNER_ROUNDED_BOTTOMLEFT = "\u{2570}";
|
||||||
|
const U_CORNER_ROUNDED_BOTTOMRIGHT = "\u{256f}";
|
||||||
|
|
||||||
|
const U_EDGE_HORIZONTAL = "\u{2500}";
|
||||||
|
|
||||||
|
const U_EDGE_VERTICAL = "\u{2502}";
|
||||||
|
const U_EDGE_VERTICAL_SCROLL = "\u{250a}";
|
||||||
|
const U_EDGE_VERTICAL_THUMB = "\u{2503}";
|
||||||
|
const U_EDGE_VERTICAL_LEFT = "\u{2524}";
|
||||||
|
const U_EDGE_VERTICAL_RIGHT = "\u{251c}";
|
||||||
|
|
||||||
|
private int $index = 0;
|
||||||
|
|
||||||
|
public function __construct(private Terminal $terminal, private array $items, private string $title = '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;
|
||||||
|
|
||||||
|
$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";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -31,8 +31,14 @@ class TreeList implements Countable
|
|||||||
|
|
||||||
private function parseNode(Node $node, array $path, string|int|null $key): void
|
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);
|
$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);
|
$entry = new Entry(depth: $level, key: $key, node: $node);
|
||||||
|
|
||||||
|
@ -73,11 +73,19 @@ class Terminal
|
|||||||
return [ $this->columns, $this->lines ];
|
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)
|
if ($row > 0 && $column > 0)
|
||||||
printf("\e[%d;%dH", $row, $column);
|
printf("\e[%d;%dH", $row, $column);
|
||||||
echo "\e[?25".($visible?"h":"l");
|
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 = '';
|
private string $inputBuffer = '';
|
||||||
|
Loading…
Reference in New Issue
Block a user