Editor fixes, code cleanup, bool colors, open dialog
This commit is contained in:
parent
9819b22019
commit
62c78fa14c
@ -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
|
||||||
|
|
||||||
|
@ -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') {
|
||||||
@ -815,7 +840,9 @@ 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;
|
||||||
|
@ -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 => "",
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user