Improve tail line, fix edit box
* Edit box now supports the usual keys; left/right plus home/end, and does the appropriate scrolling. * Tail line is now a top bar. * Added option to toggle tail bar, but not bound to any key.
This commit is contained in:
		@@ -49,6 +49,8 @@ class Editor
 | 
			
		||||
        // ]);
 | 
			
		||||
 | 
			
		||||
        $this->list = new TreeList($this->document);
 | 
			
		||||
 | 
			
		||||
        $this->setWindowTitle($this->shortfilename." - JSONEdit");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -73,6 +75,7 @@ class Editor
 | 
			
		||||
            default:
 | 
			
		||||
                throw new \RuntimeException("Unable to read file of type {$ext}");
 | 
			
		||||
        }
 | 
			
		||||
        $this->setWindowTitle($this->shortfilename." - JSONEdit");
 | 
			
		||||
        $this->document->load($doc);
 | 
			
		||||
        $this->list->parseTree();
 | 
			
		||||
    }
 | 
			
		||||
@@ -108,7 +111,7 @@ class Editor
 | 
			
		||||
                    $this->redrawEditor();
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'k{DOWN}':
 | 
			
		||||
                    $this->currentRow = min(count($this->list) - 1, $this->currentRow + 1);
 | 
			
		||||
                    $this->currentRow = min(count($this->list), $this->currentRow + 1);
 | 
			
		||||
                    $this->redrawEditor();
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'k{PGUP}':
 | 
			
		||||
@@ -260,13 +263,45 @@ class Editor
 | 
			
		||||
                        $this->redrawEditor();
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
                case "-":
 | 
			
		||||
                    foreach ($this->list as $path => $entry) {
 | 
			
		||||
                        $node = $entry->node;
 | 
			
		||||
                        if ($path == "/") {
 | 
			
		||||
                            if ($node instanceof CollapsibleNode) {
 | 
			
		||||
                                $node->collapse(false);
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            if ($node instanceof CollapsibleNode) {
 | 
			
		||||
                                $node->collapse(true);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    $this->currentRow = 0;
 | 
			
		||||
                    $this->list->parseTree();
 | 
			
		||||
                    $this->redrawEditor();
 | 
			
		||||
                    break;
 | 
			
		||||
                        
 | 
			
		||||
                case "k{LEFT}":
 | 
			
		||||
                    $node = $this->list->getNodeForIndex($this->currentRow);
 | 
			
		||||
                    if ($node instanceof CollapsibleNode) {
 | 
			
		||||
                        if ($node->isCollapsed()) {
 | 
			
		||||
                            $path = $this->list->getPathForIndex($this->currentRow);
 | 
			
		||||
                            $parent = $this->list->getIndexForPath(dirname($path));
 | 
			
		||||
                            if ($parent) {
 | 
			
		||||
                                $this->currentRow = $parent;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        $node->collapse(true);
 | 
			
		||||
                        $this->list->parseTree();
 | 
			
		||||
                        $this->redrawEditor();
 | 
			
		||||
                    } else {
 | 
			
		||||
                        $path = $this->list->getPathForIndex($this->currentRow);
 | 
			
		||||
                        $parent = $this->list->getIndexForPath(dirname($path));
 | 
			
		||||
                        if ($parent) {
 | 
			
		||||
                            $this->currentRow = $parent;
 | 
			
		||||
                        }
 | 
			
		||||
                        $this->redrawEditor();
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
    
 | 
			
		||||
@@ -373,6 +408,8 @@ class Editor
 | 
			
		||||
        $this->list->parseTree();
 | 
			
		||||
        $this->redrawEditor();
 | 
			
		||||
 | 
			
		||||
        $this->setWindowTitle($this->shortfilename." - JSONEdit");
 | 
			
		||||
 | 
			
		||||
        $this->showMessage("\e[97;42mLoaded {$readFrom}");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -413,6 +450,8 @@ class Editor
 | 
			
		||||
        $this->modified = false;
 | 
			
		||||
        $this->redrawEditor();
 | 
			
		||||
 | 
			
		||||
        $this->setWindowTitle($this->shortfilename." - JSONEdit");
 | 
			
		||||
 | 
			
		||||
        $this->showMessage("\e[97;42mWrote to {$saveTo}");
 | 
			
		||||
        return true;
 | 
			
		||||
 | 
			
		||||
@@ -469,14 +508,11 @@ class Editor
 | 
			
		||||
        This is beta software, if not alpha. It kinda works, but there will be issues. Feel free to help out with a patch, or by filing bug reports.
 | 
			
		||||
        Known issues include:
 | 
			
		||||
 | 
			
		||||
        * Editing long lines will blow up. Don't try to edit anything longer than the terminal is wide.
 | 
			
		||||
        * There is no fullscreen editing, so verbatim blocks in twig will probably not work well either.
 | 
			
		||||
        * Comments are not preserved.
 | 
			
		||||
        * Files are overwritten without confirmation.
 | 
			
		||||
        * There is no command mode, no search.
 | 
			
		||||
        * Some things just don't work yet.
 | 
			
		||||
        * Unhandled keys will appear in the bottom left of the screen with a delay.
 | 
			
		||||
        * Folding is not yet implemented.
 | 
			
		||||
        * There are crashes, and lock-ups. Data corruption is a possibility.
 | 
			
		||||
 | 
			
		||||
        # Support
 | 
			
		||||
@@ -643,12 +679,22 @@ class Editor
 | 
			
		||||
        $promptLen = mb_strlen($plainPrompt);
 | 
			
		||||
        [$w,$h] = $this->term->getSize();
 | 
			
		||||
 | 
			
		||||
        $available = $w - $promptLen - 1;
 | 
			
		||||
 | 
			
		||||
        $prompting = true;
 | 
			
		||||
        $cursorPos = mb_strlen($value);
 | 
			
		||||
        $scrollPos = max(0, $cursorPos - $available);
 | 
			
		||||
        while ($prompting) {
 | 
			
		||||
            while (($cursorPos - $scrollPos) > $available) {
 | 
			
		||||
                $scrollPos++;
 | 
			
		||||
            }
 | 
			
		||||
            while (($cursorPos - $scrollPos) < 0) {
 | 
			
		||||
                $scrollPos--;
 | 
			
		||||
            }
 | 
			
		||||
            $cursorOffs = $cursorPos - $scrollPos;
 | 
			
		||||
            $this->term->setCursor(1, $h);
 | 
			
		||||
            echo $prompt."\e[0m\e[K".$value;
 | 
			
		||||
            $this->term->setCursor($promptLen+$cursorPos+1, $h, true);
 | 
			
		||||
            echo $prompt."\e[0m\e[K".mb_substr($value, $scrollPos, $available);
 | 
			
		||||
            $this->term->setCursor($promptLen+$cursorOffs+1, $h, true);
 | 
			
		||||
            while (null === ($ch = $this->term->readKey())) {
 | 
			
		||||
                usleep(10000);
 | 
			
		||||
            }
 | 
			
		||||
@@ -684,6 +730,12 @@ class Editor
 | 
			
		||||
                        if ($cursorPos < mb_strlen($value))
 | 
			
		||||
                            $cursorPos++;
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "k{HOME}":
 | 
			
		||||
                        $cursorPos = 0;
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "k{END}":
 | 
			
		||||
                        $cursorPos = mb_strlen($value);
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -699,9 +751,16 @@ class Editor
 | 
			
		||||
    {
 | 
			
		||||
        [$w,$h] = $this->term->getSize();
 | 
			
		||||
 | 
			
		||||
        // Jump to the tail line if the cursor is past the end of the entry list
 | 
			
		||||
        if ($this->currentRow > count($this->list)) $this->currentRow = count($this->list);
 | 
			
		||||
 | 
			
		||||
        // Make sure the selection is in view
 | 
			
		||||
        while ($this->currentRow < $this->scrollOffset) $this->scrollOffset--;
 | 
			
		||||
        while ($this->currentRow > $h + $this->scrollOffset - 3) $this->scrollOffset++;
 | 
			
		||||
 | 
			
		||||
        // Nudge back so the tail line is visible but not selected
 | 
			
		||||
        if ($this->currentRow == count($this->list)) $this->currentRow--;
 | 
			
		||||
 | 
			
		||||
        $path = $this->list->getPathForIndex($this->currentRow);
 | 
			
		||||
        $node = $this->list->getNodeForIndex($this->currentRow);
 | 
			
		||||
        
 | 
			
		||||
@@ -770,9 +829,10 @@ class Editor
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->term->setCursor(1, $h);
 | 
			
		||||
        echo "\e[0;40m\e[K";
 | 
			
		||||
        echo "\e[37;40m\e[K";
 | 
			
		||||
        foreach ($keys as $key=>$info)
 | 
			
		||||
            echo "\e[37;40;2m\u{e0b6}\e[7;37m{$key} \e[22m {$info} \e[27m\u{e0b4}\e[0m";
 | 
			
		||||
            echo "\e[2m\u{f104}\e[22;97;1m{$key}\e[22;37;2m\u{f105} \e[22;36m{$info}\e[37m ";
 | 
			
		||||
            //echo "\e[37;40;2m\u{e0b6}\e[7;37m{$key} \e[22m {$info} \e[27m\u{e0b4}\e[0m";
 | 
			
		||||
            //echo " \e[1m{$key}\e[2m \e[3m{$info}\e[0m \e[90m\u{2502}\e[0m";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -784,4 +844,9 @@ class Editor
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function setWindowTitle(string $title): void
 | 
			
		||||
    {
 | 
			
		||||
        echo "\e]2;{$title}\x07";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,9 @@
 | 
			
		||||
 | 
			
		||||
namespace NoccyLabs\JsonEdit\List;
 | 
			
		||||
 | 
			
		||||
use ArrayIterator;
 | 
			
		||||
use Countable;
 | 
			
		||||
use IteratorAggregate;
 | 
			
		||||
use NoccyLabs\JsonEdit\Settings;
 | 
			
		||||
use NoccyLabs\JsonEdit\Tree\ArrayNode;
 | 
			
		||||
use NoccyLabs\JsonEdit\Tree\CollapsibleNode;
 | 
			
		||||
@@ -10,8 +12,9 @@ use NoccyLabs\JsonEdit\Tree\Tree;
 | 
			
		||||
use NoccyLabs\JsonEdit\Tree\Node;
 | 
			
		||||
use NoccyLabs\JsonEdit\Tree\ObjectNode;
 | 
			
		||||
use NoccyLabs\JsonEdit\Tree\ValueNode;
 | 
			
		||||
use Traversable;
 | 
			
		||||
 | 
			
		||||
class TreeList implements Countable
 | 
			
		||||
class TreeList implements Countable, IteratorAggregate
 | 
			
		||||
{
 | 
			
		||||
    /** @var array<string,Entry> */
 | 
			
		||||
    public array $list = [];
 | 
			
		||||
@@ -31,6 +34,11 @@ class TreeList implements Countable
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getIterator(): Traversable
 | 
			
		||||
    {
 | 
			
		||||
        return new ArrayIterator($this->list);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function count(): int
 | 
			
		||||
    {
 | 
			
		||||
        return count($this->list);
 | 
			
		||||
@@ -146,7 +154,7 @@ class TreeList implements Countable
 | 
			
		||||
        echo "\e[{$screenRow};1H";
 | 
			
		||||
        if ($entryRow >= count($keys)) {
 | 
			
		||||
            echo ($selected?"\e[44;97m":"\e[0;90m")."\e[K";
 | 
			
		||||
            if ($entryRow == count($keys)) echo str_repeat("\u{2574}",$columns);
 | 
			
		||||
            if ($entryRow == count($keys) && Settings::$tailLine) echo str_repeat("\u{2594}",$columns);
 | 
			
		||||
            echo "\e[0m";
 | 
			
		||||
            //else echo "\e[90m\u{2805}\e[0m";
 | 
			
		||||
            return;
 | 
			
		||||
@@ -183,10 +191,10 @@ class TreeList implements Countable
 | 
			
		||||
            if ($entry->node instanceof CollapsibleNode) {
 | 
			
		||||
                if ($entry->node->isCollapsed()) {
 | 
			
		||||
                    //echo "\e[90m\u{25ba} \e[37m";
 | 
			
		||||
                    echo "\e[90m".self::TREE_FOLD_OPEN." \e[37m";
 | 
			
		||||
                    echo "\e[37m".self::TREE_FOLD_OPEN." \e[37m";
 | 
			
		||||
                } else {
 | 
			
		||||
                    //echo "\e[90m\u{25bc} \e[37m";
 | 
			
		||||
                    echo "\e[90m".self::TREE_FOLD_CLOSE." \e[37m";
 | 
			
		||||
                    echo "\e[37m".self::TREE_FOLD_CLOSE." \e[37m";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -231,8 +239,8 @@ class TreeList implements Countable
 | 
			
		||||
            $value = $entry->node->value;
 | 
			
		||||
            echo match (gettype($value)) {
 | 
			
		||||
                'string' => "\e[33m",
 | 
			
		||||
                'integer' => "\e[34m",
 | 
			
		||||
                'double' => "\e[32m",
 | 
			
		||||
                'integer' => "\e[94m",
 | 
			
		||||
                'double' => "\e[96m",
 | 
			
		||||
                'boolean' => "\e[35m",
 | 
			
		||||
                'NULL' => "\e[31m",
 | 
			
		||||
                default => "",
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,8 @@ class Settings
 | 
			
		||||
 | 
			
		||||
    public static bool $highlightRow = true;
 | 
			
		||||
 | 
			
		||||
    public static bool $tailLine = true;
 | 
			
		||||
 | 
			
		||||
    public static function load(string $filename): void
 | 
			
		||||
    {
 | 
			
		||||
        if (file_exists($filename) && is_readable($filename)) {
 | 
			
		||||
 
 | 
			
		||||
@@ -115,6 +115,12 @@ class Terminal
 | 
			
		||||
            } elseif (strncmp($this->inputBuffer, "\e[D", 3) === 0) {
 | 
			
		||||
                $this->inputBuffer = mb_substr($this->inputBuffer, 3);
 | 
			
		||||
                return "k{LEFT}";
 | 
			
		||||
            } elseif (strncmp($this->inputBuffer, "\e[H", 3) === 0) {
 | 
			
		||||
                $this->inputBuffer = mb_substr($this->inputBuffer, 3);
 | 
			
		||||
                return "k{HOME}";
 | 
			
		||||
            } elseif (strncmp($this->inputBuffer, "\e[F", 3) === 0) {
 | 
			
		||||
                $this->inputBuffer = mb_substr($this->inputBuffer, 3);
 | 
			
		||||
                return "k{END}";
 | 
			
		||||
            } elseif (strncmp($this->inputBuffer, "\e[5~", 4) === 0) {
 | 
			
		||||
                $this->inputBuffer = mb_substr($this->inputBuffer, 4);
 | 
			
		||||
                return "k{PGUP}";
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user