diff --git a/src/Editor/Editor.php b/src/Editor/Editor.php index 20bc6d4..3a80a3b 100644 --- a/src/Editor/Editor.php +++ b/src/Editor/Editor.php @@ -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"; + } + } diff --git a/src/List/TreeList.php b/src/List/TreeList.php index 1045d15..fa5b6d9 100644 --- a/src/List/TreeList.php +++ b/src/List/TreeList.php @@ -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 */ 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 => "", diff --git a/src/Settings.php b/src/Settings.php index 9ac4448..3383cc9 100644 --- a/src/Settings.php +++ b/src/Settings.php @@ -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)) { diff --git a/src/Terminal/Terminal.php b/src/Terminal/Terminal.php index 3224399..b68f50d 100644 --- a/src/Terminal/Terminal.php +++ b/src/Terminal/Terminal.php @@ -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}";