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->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}':
@ -261,12 +264,44 @@ class Editor
}
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";
}
}

View File

@ -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 => "",

View File

@ -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)) {

View File

@ -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}";