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:
Chris 2024-10-03 17:57:41 +02:00
parent 466406733d
commit 19ba6a9bee
4 changed files with 96 additions and 15 deletions

View File

@ -49,6 +49,8 @@ class Editor
// ]); // ]);
$this->list = new TreeList($this->document); $this->list = new TreeList($this->document);
$this->setWindowTitle($this->shortfilename." - JSONEdit");
} }
/** /**
@ -73,6 +75,7 @@ class Editor
default: default:
throw new \RuntimeException("Unable to read file of type {$ext}"); throw new \RuntimeException("Unable to read file of type {$ext}");
} }
$this->setWindowTitle($this->shortfilename." - JSONEdit");
$this->document->load($doc); $this->document->load($doc);
$this->list->parseTree(); $this->list->parseTree();
} }
@ -108,7 +111,7 @@ class Editor
$this->redrawEditor(); $this->redrawEditor();
break; break;
case 'k{DOWN}': case 'k{DOWN}':
$this->currentRow = min(count($this->list) - 1, $this->currentRow + 1); $this->currentRow = min(count($this->list), $this->currentRow + 1);
$this->redrawEditor(); $this->redrawEditor();
break; break;
case 'k{PGUP}': case 'k{PGUP}':
@ -261,12 +264,44 @@ class Editor
} }
break; 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}": case "k{LEFT}":
$node = $this->list->getNodeForIndex($this->currentRow); $node = $this->list->getNodeForIndex($this->currentRow);
if ($node instanceof CollapsibleNode) { 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); $node->collapse(true);
$this->list->parseTree(); $this->list->parseTree();
$this->redrawEditor(); $this->redrawEditor();
} else {
$path = $this->list->getPathForIndex($this->currentRow);
$parent = $this->list->getIndexForPath(dirname($path));
if ($parent) {
$this->currentRow = $parent;
}
$this->redrawEditor();
} }
break; break;
@ -373,6 +408,8 @@ class Editor
$this->list->parseTree(); $this->list->parseTree();
$this->redrawEditor(); $this->redrawEditor();
$this->setWindowTitle($this->shortfilename." - JSONEdit");
$this->showMessage("\e[97;42mLoaded {$readFrom}"); $this->showMessage("\e[97;42mLoaded {$readFrom}");
} }
@ -413,6 +450,8 @@ class Editor
$this->modified = false; $this->modified = false;
$this->redrawEditor(); $this->redrawEditor();
$this->setWindowTitle($this->shortfilename." - JSONEdit");
$this->showMessage("\e[97;42mWrote to {$saveTo}"); $this->showMessage("\e[97;42mWrote to {$saveTo}");
return true; 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. 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: 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. * Comments are not preserved.
* Files are overwritten without confirmation. * Files are overwritten without confirmation.
* There is no command mode, no search. * There is no command mode, no search.
* Some things just don't work yet. * Some things just don't work yet.
* Unhandled keys will appear in the bottom left of the screen with a delay. * 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. * There are crashes, and lock-ups. Data corruption is a possibility.
# Support # Support
@ -643,12 +679,22 @@ class Editor
$promptLen = mb_strlen($plainPrompt); $promptLen = mb_strlen($plainPrompt);
[$w,$h] = $this->term->getSize(); [$w,$h] = $this->term->getSize();
$available = $w - $promptLen - 1;
$prompting = true; $prompting = true;
$cursorPos = mb_strlen($value); $cursorPos = mb_strlen($value);
$scrollPos = max(0, $cursorPos - $available);
while ($prompting) { while ($prompting) {
while (($cursorPos - $scrollPos) > $available) {
$scrollPos++;
}
while (($cursorPos - $scrollPos) < 0) {
$scrollPos--;
}
$cursorOffs = $cursorPos - $scrollPos;
$this->term->setCursor(1, $h); $this->term->setCursor(1, $h);
echo $prompt."\e[0m\e[K".$value; echo $prompt."\e[0m\e[K".mb_substr($value, $scrollPos, $available);
$this->term->setCursor($promptLen+$cursorPos+1, $h, true); $this->term->setCursor($promptLen+$cursorOffs+1, $h, true);
while (null === ($ch = $this->term->readKey())) { while (null === ($ch = $this->term->readKey())) {
usleep(10000); usleep(10000);
} }
@ -684,6 +730,12 @@ class Editor
if ($cursorPos < mb_strlen($value)) if ($cursorPos < mb_strlen($value))
$cursorPos++; $cursorPos++;
break; 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(); [$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 < $this->scrollOffset) $this->scrollOffset--;
while ($this->currentRow > $h + $this->scrollOffset - 3) $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); $path = $this->list->getPathForIndex($this->currentRow);
$node = $this->list->getNodeForIndex($this->currentRow); $node = $this->list->getNodeForIndex($this->currentRow);
@ -770,9 +829,10 @@ class Editor
} }
$this->term->setCursor(1, $h); $this->term->setCursor(1, $h);
echo "\e[0;40m\e[K"; echo "\e[37;40m\e[K";
foreach ($keys as $key=>$info) 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"; //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";
}
} }

View File

@ -2,7 +2,9 @@
namespace NoccyLabs\JsonEdit\List; namespace NoccyLabs\JsonEdit\List;
use ArrayIterator;
use Countable; use Countable;
use IteratorAggregate;
use NoccyLabs\JsonEdit\Settings; use NoccyLabs\JsonEdit\Settings;
use NoccyLabs\JsonEdit\Tree\ArrayNode; use NoccyLabs\JsonEdit\Tree\ArrayNode;
use NoccyLabs\JsonEdit\Tree\CollapsibleNode; use NoccyLabs\JsonEdit\Tree\CollapsibleNode;
@ -10,8 +12,9 @@ use NoccyLabs\JsonEdit\Tree\Tree;
use NoccyLabs\JsonEdit\Tree\Node; use NoccyLabs\JsonEdit\Tree\Node;
use NoccyLabs\JsonEdit\Tree\ObjectNode; use NoccyLabs\JsonEdit\Tree\ObjectNode;
use NoccyLabs\JsonEdit\Tree\ValueNode; use NoccyLabs\JsonEdit\Tree\ValueNode;
use Traversable;
class TreeList implements Countable class TreeList implements Countable, IteratorAggregate
{ {
/** @var array<string,Entry> */ /** @var array<string,Entry> */
public array $list = []; public array $list = [];
@ -31,6 +34,11 @@ class TreeList implements Countable
{ {
} }
public function getIterator(): Traversable
{
return new ArrayIterator($this->list);
}
public function count(): int public function count(): int
{ {
return count($this->list); return count($this->list);
@ -146,7 +154,7 @@ class TreeList implements Countable
echo "\e[{$screenRow};1H"; echo "\e[{$screenRow};1H";
if ($entryRow >= count($keys)) { if ($entryRow >= count($keys)) {
echo ($selected?"\e[44;97m":"\e[0;90m")."\e[K"; 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"; echo "\e[0m";
//else echo "\e[90m\u{2805}\e[0m"; //else echo "\e[90m\u{2805}\e[0m";
return; return;
@ -183,10 +191,10 @@ class TreeList implements Countable
if ($entry->node instanceof CollapsibleNode) { if ($entry->node instanceof CollapsibleNode) {
if ($entry->node->isCollapsed()) { if ($entry->node->isCollapsed()) {
//echo "\e[90m\u{25ba} \e[37m"; //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 { } else {
//echo "\e[90m\u{25bc} \e[37m"; //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; $value = $entry->node->value;
echo match (gettype($value)) { echo match (gettype($value)) {
'string' => "\e[33m", 'string' => "\e[33m",
'integer' => "\e[34m", 'integer' => "\e[94m",
'double' => "\e[32m", 'double' => "\e[96m",
'boolean' => "\e[35m", 'boolean' => "\e[35m",
'NULL' => "\e[31m", 'NULL' => "\e[31m",
default => "", default => "",

View File

@ -14,6 +14,8 @@ class Settings
public static bool $highlightRow = true; public static bool $highlightRow = true;
public static bool $tailLine = true;
public static function load(string $filename): void public static function load(string $filename): void
{ {
if (file_exists($filename) && is_readable($filename)) { if (file_exists($filename) && is_readable($filename)) {

View File

@ -115,6 +115,12 @@ class Terminal
} elseif (strncmp($this->inputBuffer, "\e[D", 3) === 0) { } elseif (strncmp($this->inputBuffer, "\e[D", 3) === 0) {
$this->inputBuffer = mb_substr($this->inputBuffer, 3); $this->inputBuffer = mb_substr($this->inputBuffer, 3);
return "k{LEFT}"; 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) { } elseif (strncmp($this->inputBuffer, "\e[5~", 4) === 0) {
$this->inputBuffer = mb_substr($this->inputBuffer, 4); $this->inputBuffer = mb_substr($this->inputBuffer, 4);
return "k{PGUP}"; return "k{PGUP}";