Add popup menus for actions
This commit is contained in:
		@@ -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 = '';
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user