Editor fixes, code cleanup, bool colors, open dialog

This commit is contained in:
Chris 2024-10-05 14:57:11 +02:00
parent 9819b22019
commit 62c78fa14c
3 changed files with 160 additions and 65 deletions

View File

@ -2,6 +2,11 @@
This is an editor for JSON files, that also supports YAML. This is an editor for JSON files, that also supports YAML.
> [!WARNING]
> This software is still very much beta. That means there will be bugs. Please
> report them, and don't go doing stupid stuff like editing your live configuration
> files in place.
## Features ## Features
- Interactive terminal TUI application - Interactive terminal TUI application
@ -35,7 +40,8 @@ $ mv bin/jsonedit.phar ~/bin/jsonedit
## Using ## Using
JSONEdit is controlled using hotkeys. The essential keys are always displayed JSONEdit is controlled using hotkeys. The essential keys are always displayed
at the bottom of the screen. at the bottom of the screen. Press **h** for the help, which lists all available
keys.
### Creating a document from scratch ### Creating a document from scratch

View File

@ -10,11 +10,14 @@ use NoccyLabs\JsonEdit\Tree\CollapsibleNode;
use NoccyLabs\JsonEdit\Tree\ObjectNode; use NoccyLabs\JsonEdit\Tree\ObjectNode;
use NoccyLabs\JsonEdit\Tree\Tree; use NoccyLabs\JsonEdit\Tree\Tree;
use NoccyLabs\JsonEdit\Tree\ValueNode; use NoccyLabs\JsonEdit\Tree\ValueNode;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
class Editor class Editor
{ {
public static string $helpText;
private Tree $document; private Tree $document;
private TreeList $list; private TreeList $list;
@ -201,7 +204,6 @@ class Editor
break; break;
case "\x18": // ctrl-x case "\x18": // ctrl-x
if ($this->modified) { if ($this->modified) {
$menu = new Menu($this->term, [ $menu = new Menu($this->term, [
'cancel' => "Return to editor", 'cancel' => "Return to editor",
@ -321,12 +323,28 @@ class Editor
$this->showMessage("Toggle compact groups (c)"); $this->showMessage("Toggle compact groups (c)");
break; break;
case "\x0d": // enter
$node = $this->list->getNodeForIndex($this->currentRow);
if ($node instanceof ValueNode) {
if ($node->value === true) {
$node->value = false;
$this->modified = true;
$this->redrawEditor();
} elseif ($node->value === false) {
$node->value = true;
$this->modified = true;
$this->redrawEditor();
} else {
$this->doEditValue();
}
}
break;
case "\x03": // ctrl-c case "\x03": // ctrl-c
$this->showMessage("\e[30;43mPress Ctrl-X to exit."); $this->showMessage("\e[30;43mPress Ctrl-X to exit.");
break; break;
case "\x0e": // ctrl-n case "\x0e": // ctrl-n
if ($this->modified) { if ($this->modified) {
$menu = new Menu($this->term, [ $menu = new Menu($this->term, [
'cancel' => "Return to editor", 'cancel' => "Return to editor",
@ -349,6 +367,8 @@ class Editor
case 'discard': case 'discard':
break; break;
} }
$this->modified = false;
} }
$this->document->load((object)[]); $this->document->load((object)[]);
@ -359,6 +379,10 @@ class Editor
$this->redrawEditor(); $this->redrawEditor();
break; break;
case "\x0F": // ctrl-o
$this->doOpenFile();
break;
case "\x12": // ctrl-r case "\x12": // ctrl-r
$this->doReadFile(); $this->doReadFile();
break; break;
@ -369,6 +393,7 @@ class Editor
case null: case null:
break; break;
default: default:
$this->term->setCursor(1, $h, true); $this->term->setCursor(1, $h, true);
echo sprintf("%s %02x %03d", ctype_print($read)?$read:'.', ord($read), ord($read)); echo sprintf("%s %02x %03d", ctype_print($read)?$read:'.', ord($read), ord($read));
@ -476,75 +501,68 @@ class Editor
} }
private function doOpenFile()
{
$wd = getcwd();
while (true) {
$items = [
dirname($wd) => sprintf(
"%-30s %10s %20s",
"Parent Directory",
"<DIR>",
"-"
)
];
$files = glob($wd."/*");
foreach ($files as $file) {
$items[$file] = sprintf(
"%-30s %10s %20s",
is_dir($file) ? ("<".basename($file).">") : (basename($file)),
is_dir($file) ? "<DIR>" : filesize($file),
date("Y-m-d H:i:s", filemtime($file))
);
}
$menu = new Menu($this->term, $items, $wd);
$sel = $menu->display(0, 0, 70, 20, null);
if ($sel === null) {
$this->redrawEditor();
return;
}
if (is_dir($sel)) {
$wd = $sel;
}
if (is_file($sel)) {
$body = file_get_contents($sel);
$json = json_decode($body);
if ($json !== null) {
$this->loadDocument($json);
break;
}
try {
$yaml = Yaml::parse($body);
$this->loadDocument($yaml);
break;
} catch (ParseException $e) {
$this->showMessage("\e[41;93mUnsupported file format");
}
}
}
$this->filename = $sel;
$this->shortfilename = basename($sel);
$this->redrawEditor();
$this->showMessage("\e[42;97mOpened {$this->shortfilename}");
}
private function doShowHelp(): void private function doShowHelp(): void
{ {
[$w,$h] = $this->term->getSize(); [$w,$h] = $this->term->getSize();
$text = <<<EOT
Welcome to JSONEdit! The editor you have missed all this time without even knowing it!
# QuickStart
To get started, press I (shift-i) and add something to the document. Use the arrow keys to get around. To cancel a prompt, close a menu or close a dialog, press ctrl-C. When you are happy with your work, press ctrl-W and enter a filename to write. You can also press ctrl-R and read in a new file, overwriting your masterpiece.
## Useful keys
↑↓ Navigate values in document
h Show this help
H Show information about the app and license
i Insert a new value
I Insert value, array or object
e Edit selected value
E Edit selected key
D Delete selected key
c Toggle compact list view
q Toggle quoted keys in list view
^W Write to file
^R Read from file
^N New document with empty object
^C Cancel/Exit
## Doing stuff
### Adding keys or values
To add a key or a value, navigate to a value in an array or object, or to a specific array or object, and press "i". You will be prompted for the value, and for objects the key.
You can also press "I" to add arrays and objects. Just select what you want to add in the menu and press enter.
### Editing keys
You can edit keys on objects. For this, press "E".
### Editing values
To edit a value, press "e". The value is verbatim JSON, so strings should be quoted and all that. Anything that is unparsable JSON will be used as is, resulting in a string.
### Loading and Saving files
To load a file, press ^R and enter the filename to read. To write to a file, press ^W and enter the filename to write to.
### YAML or JSON?
There is no need to select YAML or JSON mode. All operations work the same, and the format is determined on load or save.
# Disclaimer
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:
* 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.
* There are crashes, and lock-ups. Data corruption is a possibility.
# Support
Go to https://dev.noccylabs.info/noccy/jsonedit to find the source code, issue tracker, and learn more about the project!
EOT;
$width = min(90, $w - 10); $width = min(90, $w - 10);
$height = min(40, $h - 6); $height = min(40, $h - 6);
$left = round(($w / 2) - ($width / 2)); $left = round(($w / 2) - ($width / 2));
$top = round(($h / 2) - ($height / 2)); $top = round(($h / 2) - ($height / 2));
$msg = new MessageBox($this->term, $text, "Help"); $msg = new MessageBox($this->term, self::$helpText, "Help");
$this->redrawInfoBar([ '↑/↓' => 'Scroll', '^C' => 'Close' ]); $this->redrawInfoBar([ '↑/↓' => 'Scroll', '^C' => 'Close' ]);
$msg->display($left, $top, $width, $height); $msg->display($left, $top, $width, $height);
$this->redrawEditor(); $this->redrawEditor();
@ -622,7 +640,14 @@ class Editor
if ($node instanceof ValueNode) { if ($node instanceof ValueNode) {
$val = json_encode($node->value, JSON_UNESCAPED_SLASHES); $val = json_encode($node->value, JSON_UNESCAPED_SLASHES);
$newVal = $this->ask("\e[33;1mnew value:\e[0m ", $val); $newVal = $this->ask("\e[33;1mnew value:\e[0m ", $val);
// We get null on ctrl-C, so non-null is input
if ($newVal !== null) { if ($newVal !== null) {
// Break out if the value has not changed
if ($newVal === $val) {
$this->redrawInfoBar();
return;
}
// Attempt to decode the value
$val = json_decode($newVal); $val = json_decode($newVal);
// If the string decodes to null, but isn't 'null', treat it as a string // If the string decodes to null, but isn't 'null', treat it as a string
if ($val === null && $newVal !== 'null') { if ($val === null && $newVal !== 'null') {
@ -816,6 +841,8 @@ class Editor
$path = $this->list->getPathForIndex($this->currentRow); $path = $this->list->getPathForIndex($this->currentRow);
$node = $this->list->getNodeForIndex($this->currentRow); $node = $this->list->getNodeForIndex($this->currentRow);
ob_start();
$modified = $this->modified ? "\e[31m*\e[37m" : ""; $modified = $this->modified ? "\e[31m*\e[37m" : "";
$this->term->setCursor(1, $h-1); $this->term->setCursor(1, $h-1);
echo "\e[40;37m\e[K{$modified}\e[1m{$this->shortfilename}\e[22m#\e[3m{$path}\e[37;23m"; echo "\e[40;37m\e[K{$modified}\e[1m{$this->shortfilename}\e[22m#\e[3m{$path}\e[37;23m";
@ -841,6 +868,8 @@ class Editor
} }
// $this->term->setCursor(1, 1); // $this->term->setCursor(1, 1);
// echo "\e[90;3m«Empty»\e[0m"; // echo "\e[90;3m«Empty»\e[0m";
ob_end_flush();
} }
/** /**
@ -902,3 +931,63 @@ class Editor
} }
} }
Editor::$helpText = <<<EOT
Welcome to JSONEdit! The editor you never knew you were missing!
# QuickStart
To get started, press I (shift-i) and add something to the document. Use the arrow keys to get around. To cancel a prompt, close a menu or close a dialog, press ctrl-C. When you are happy with your work, press ctrl-W and enter a filename to write. You can also press ctrl-R and read in a new file, overwriting your masterpiece.
# Useful keys
### Main Window
↑↓ Navigate values in document
h Show this help
H Show information about the app and license
i Insert a new value
I Insert value, array or object
e Edit selected value
E Edit selected key
Enter Edit string/number, toggle booleans
D Delete selected key
c Toggle compact list view
q Toggle quoted keys in list view
^W Write to file
^R Read from file
^O Open file browser
^N New document with empty object
^X Exit
### Dialogs/Menus
↑↓ Select/Scroll
Enter Select value
^C Cancel
# Doing stuff
### Adding keys or values
To add a key or a value, navigate to a value in an array or object, or to a specific array or object, and press "i". You will be prompted for the value, and for objects the key.
You can also press "I" to add arrays and objects. Just select what you want to add in the menu and press enter.
### Editing keys
You can edit keys on objects. For this, press "E".
### Editing values
To edit a value, press "e". The value is verbatim JSON, so strings should be quoted and all that. Anything that is unparsable JSON will be used as is, resulting in a string.
### Loading and Saving files
To load a file, press ^R and enter the filename to read. To write to a file, press ^W and enter the filename to write to.
### YAML or JSON?
There is no need to select YAML or JSON mode. All operations work the same, and the format is determined on load or save.
# Disclaimer
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:
# Support
Go to https://dev.noccylabs.info/noccy/jsonedit to find the source code, issue tracker, and learn more about the project!
EOT;

View File

@ -241,7 +241,7 @@ class TreeList implements Countable, IteratorAggregate
'string' => "\e[33m", 'string' => "\e[33m",
'integer' => "\e[94m", 'integer' => "\e[94m",
'double' => "\e[96m", 'double' => "\e[96m",
'boolean' => "\e[35m", 'boolean' => ($value?"\e[92m":"\e[32m"),
'NULL' => "\e[31m", 'NULL' => "\e[31m",
default => "", default => "",
}; };