Add messagebox, bugfixes
This commit is contained in:
parent
24f569070f
commit
f23c378020
@ -3,6 +3,7 @@
|
|||||||
namespace NoccyLabs\JEdit\Editor;
|
namespace NoccyLabs\JEdit\Editor;
|
||||||
|
|
||||||
use NoccyLabs\JEdit\List\TreeList;
|
use NoccyLabs\JEdit\List\TreeList;
|
||||||
|
use NoccyLabs\JEdit\Settings;
|
||||||
use NoccyLabs\JEdit\Terminal\Terminal;
|
use NoccyLabs\JEdit\Terminal\Terminal;
|
||||||
use NoccyLabs\JEdit\Tree\ArrayNode;
|
use NoccyLabs\JEdit\Tree\ArrayNode;
|
||||||
use NoccyLabs\JEdit\Tree\ObjectNode;
|
use NoccyLabs\JEdit\Tree\ObjectNode;
|
||||||
@ -27,6 +28,8 @@ class Editor
|
|||||||
|
|
||||||
private bool $running = true;
|
private bool $running = true;
|
||||||
|
|
||||||
|
private bool $modified = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
@ -137,10 +140,12 @@ class Editor
|
|||||||
$collNode->removeIndex($deleteKey);
|
$collNode->removeIndex($deleteKey);
|
||||||
$this->list->parseTree();
|
$this->list->parseTree();
|
||||||
$this->redrawEditor();
|
$this->redrawEditor();
|
||||||
|
$this->modified = true;
|
||||||
} elseif ($collNode instanceof ObjectNode) {
|
} elseif ($collNode instanceof ObjectNode) {
|
||||||
$collNode->unset($deleteKey);
|
$collNode->unset($deleteKey);
|
||||||
$this->list->parseTree();
|
$this->list->parseTree();
|
||||||
$this->redrawEditor();
|
$this->redrawEditor();
|
||||||
|
$this->modified = true;
|
||||||
} else {
|
} else {
|
||||||
$this->term->setCursor(1, $h);
|
$this->term->setCursor(1, $h);
|
||||||
echo "\e[97;41mCan only delete from object, array\e[K\e[0m";
|
echo "\e[97;41mCan only delete from object, array\e[K\e[0m";
|
||||||
@ -175,18 +180,58 @@ class Editor
|
|||||||
switch ($sel) {
|
switch ($sel) {
|
||||||
case 'value':
|
case 'value':
|
||||||
$this->doInsertValue();
|
$this->doInsertValue();
|
||||||
|
$this->modified = true;
|
||||||
break;
|
break;
|
||||||
case 'array':
|
case 'array':
|
||||||
$this->doInsertValue('[]');
|
$this->doInsertValue('[]');
|
||||||
|
$this->modified = true;
|
||||||
break;
|
break;
|
||||||
case 'object':
|
case 'object':
|
||||||
$this->doInsertValue('{}');
|
$this->doInsertValue('{}');
|
||||||
|
$this->modified = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'h':
|
||||||
|
$text = <<<EOT
|
||||||
|
Welcome to JSONEdit! The editor you have missed all this time without even knowing it!
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
↑↓ Navigate values in document
|
||||||
|
i Insert a new value
|
||||||
|
I Insert value, array or object
|
||||||
|
e Edit selected value
|
||||||
|
E Edit selected key
|
||||||
|
D Delete selected key
|
||||||
|
^W Write to file
|
||||||
|
^R Read from file
|
||||||
|
^N New document with empty object
|
||||||
|
^C Cancel/Exit
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Go to https://dev.noccylabs.info/noccy/jsonedit to find the source code, issue tracker, and learn more about the project!
|
||||||
|
EOT;
|
||||||
|
$msg = new MessageBox($this->term, $text, "Help (press ctrl-C to close)");
|
||||||
|
$msg->display(5, 3, 70, 20);
|
||||||
|
$this->redrawEditor();
|
||||||
|
break;
|
||||||
|
|
||||||
case 'i':
|
case 'i':
|
||||||
$this->doInsertValue();
|
$this->doInsertValue();
|
||||||
|
$this->modified = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'Q':
|
case 'Q':
|
||||||
@ -194,6 +239,20 @@ class Editor
|
|||||||
$this->running = false;
|
$this->running = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "q":
|
||||||
|
Settings::$editorQuotedKeys = !Settings::$editorQuotedKeys;
|
||||||
|
$this->redrawEditor();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "\x0e": // ctrl-n
|
||||||
|
$this->document->load((object)[]);
|
||||||
|
$this->list->parseTree();
|
||||||
|
$this->filename = "untitled.json";
|
||||||
|
$this->shortfilename = "untitled.json";
|
||||||
|
$this->modified = false;
|
||||||
|
$this->redrawEditor();
|
||||||
|
break;
|
||||||
|
|
||||||
case "\x12": // ctrl-r
|
case "\x12": // ctrl-r
|
||||||
$this->doReadFile();
|
$this->doReadFile();
|
||||||
break;
|
break;
|
||||||
@ -211,6 +270,8 @@ class Editor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Settings::save(SETTINGS_FILE);
|
||||||
|
|
||||||
// for ($n = 0; $n < 20; $n++) {
|
// for ($n = 0; $n < 20; $n++) {
|
||||||
// $this->currentRow = $n;
|
// $this->currentRow = $n;
|
||||||
// $this->redrawEditor();
|
// $this->redrawEditor();
|
||||||
@ -278,7 +339,6 @@ class Editor
|
|||||||
|
|
||||||
$doc = $this->document->save();
|
$doc = $this->document->save();
|
||||||
|
|
||||||
$this->term->setCursor(1, $h);
|
|
||||||
|
|
||||||
$ext = strtolower(pathinfo($saveTo, PATHINFO_EXTENSION));
|
$ext = strtolower(pathinfo($saveTo, PATHINFO_EXTENSION));
|
||||||
switch ($ext) {
|
switch ($ext) {
|
||||||
@ -291,11 +351,19 @@ class Editor
|
|||||||
file_put_contents($saveTo, Yaml::dump($doc));
|
file_put_contents($saveTo, Yaml::dump($doc));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
$this->term->setCursor(1, $h);
|
||||||
echo "\e[97;41mUnable to write format: {$ext}\e[K\e[0m";
|
echo "\e[97;41mUnable to write format: {$ext}\e[K\e[0m";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->filename = $saveTo;
|
||||||
|
$this->shortfilename = basename($saveTo);
|
||||||
|
$this->modified = false;
|
||||||
|
$this->redrawEditor();
|
||||||
|
|
||||||
|
$this->term->setCursor(1, $h);
|
||||||
echo "\e[97;42mWrote to {$saveTo}\e[K\e[0m";
|
echo "\e[97;42mWrote to {$saveTo}\e[K\e[0m";
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -316,7 +384,9 @@ class Editor
|
|||||||
if ($parent->node instanceof ObjectNode) {
|
if ($parent->node instanceof ObjectNode) {
|
||||||
$newVal = $this->ask("\e[0;33mnew key:\e[0m ", $entry->key);
|
$newVal = $this->ask("\e[0;33mnew key:\e[0m ", $entry->key);
|
||||||
if ($newVal !== null) {
|
if ($newVal !== null) {
|
||||||
|
$parent->node->rename($entry->key, $newVal);
|
||||||
$entry->key = $newVal;
|
$entry->key = $newVal;
|
||||||
|
$this->list->parseTree();
|
||||||
$this->redrawEditor();
|
$this->redrawEditor();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -347,6 +417,7 @@ class Editor
|
|||||||
$val = $newVal;
|
$val = $newVal;
|
||||||
}
|
}
|
||||||
$node->value = $val;
|
$node->value = $val;
|
||||||
|
$this->modified = true;
|
||||||
$this->redrawEditor();
|
$this->redrawEditor();
|
||||||
} else {
|
} else {
|
||||||
$this->redrawInfoBar();
|
$this->redrawInfoBar();
|
||||||
@ -395,6 +466,7 @@ class Editor
|
|||||||
} elseif ($node instanceof ObjectNode) {
|
} elseif ($node instanceof ObjectNode) {
|
||||||
$node->set($key, $valueNode);
|
$node->set($key, $valueNode);
|
||||||
}
|
}
|
||||||
|
$this->modified = true;
|
||||||
$this->list->parseTree();
|
$this->list->parseTree();
|
||||||
$this->redrawEditor();
|
$this->redrawEditor();
|
||||||
} else {
|
} else {
|
||||||
@ -477,8 +549,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);
|
||||||
|
|
||||||
|
$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\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";
|
||||||
|
|
||||||
//$this->term->setCursor(1, $h);
|
//$this->term->setCursor(1, $h);
|
||||||
echo " = ";
|
echo " = ";
|
||||||
|
110
src/Editor/MessageBox.php
Normal file
110
src/Editor/MessageBox.php
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\JEdit\Editor;
|
||||||
|
|
||||||
|
use NoccyLabs\JEdit\Terminal\Terminal;
|
||||||
|
|
||||||
|
class MessageBox
|
||||||
|
{
|
||||||
|
|
||||||
|
const U_CORNER_TOPLEFT = "\u{250c}";
|
||||||
|
const U_CORNER_TOPRIGHT = "\u{2510}";
|
||||||
|
const U_CORNER_BOTTOMLEFT = "\u{2514}";
|
||||||
|
const U_CORNER_BOTTOMRIGHT = "\u{2518}";
|
||||||
|
|
||||||
|
const U_CORNER_ROUNDED_TOPLEFT = "\u{256d}";
|
||||||
|
const U_CORNER_ROUNDED_TOPRIGHT = "\u{256e}";
|
||||||
|
const U_CORNER_ROUNDED_BOTTOMLEFT = "\u{2570}";
|
||||||
|
const U_CORNER_ROUNDED_BOTTOMRIGHT = "\u{256f}";
|
||||||
|
|
||||||
|
const U_EDGE_HORIZONTAL = "\u{2500}";
|
||||||
|
|
||||||
|
const U_EDGE_VERTICAL = "\u{2502}";
|
||||||
|
const U_EDGE_VERTICAL_SCROLL = "\u{2591}";
|
||||||
|
const U_EDGE_VERTICAL_THUMB = "\u{2593}";
|
||||||
|
const U_EDGE_VERTICAL_LEFT = "\u{2524}";
|
||||||
|
const U_EDGE_VERTICAL_RIGHT = "\u{251c}";
|
||||||
|
|
||||||
|
private int $scroll = 0;
|
||||||
|
|
||||||
|
public function __construct(private Terminal $terminal, private string $text, private string $title = 'Message')
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function display(int $left, int $top, int $width, int $height = 0): void
|
||||||
|
{
|
||||||
|
$wrapped = explode("\n", wordwrap($this->text, $width - 4, cut_long_words:true));
|
||||||
|
|
||||||
|
if ($height == 0) $height = count($wrapped) + 4;
|
||||||
|
|
||||||
|
$maxScroll = (count($wrapped) > $height) ? count($wrapped) - $height + 4 : 0;
|
||||||
|
|
||||||
|
$this->redraw($left, $top, $width, $height, $wrapped, $this->title, $maxScroll);
|
||||||
|
|
||||||
|
$showing = true;
|
||||||
|
while ($showing) {
|
||||||
|
while (null === ($ch = $this->terminal->readKey())) {
|
||||||
|
usleep(10000);
|
||||||
|
}
|
||||||
|
if (mb_strlen($ch) == 1) {
|
||||||
|
if ($ch == "\x03") {
|
||||||
|
$showing = false;
|
||||||
|
}
|
||||||
|
if ($ch == "\r") {
|
||||||
|
$showing = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($ch == "k{UP}") {
|
||||||
|
$this->scroll--;
|
||||||
|
if ($this->scroll < 0) $this->scroll = 0;
|
||||||
|
$this->redraw($left, $top, $width, $height, $wrapped, $this->title, $maxScroll);
|
||||||
|
} elseif ($ch == "k{DOWN}") {
|
||||||
|
$this->scroll++;
|
||||||
|
if ($this->scroll > $maxScroll) $this->scroll = $maxScroll;
|
||||||
|
$this->redraw($left, $top, $width, $height, $wrapped, $this->title, $maxScroll);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function redraw(int $left, int $top, int $width, int $height, array $wrapped, string $title, int $maxScroll) {
|
||||||
|
$visibleItems = $height - 4;
|
||||||
|
|
||||||
|
// calculate scrollbar thumb positions
|
||||||
|
$scrollTop = ($this->scroll / ($maxScroll + $visibleItems));
|
||||||
|
$scrollVisible = $visibleItems / count($wrapped); // / $visibleItems;
|
||||||
|
$scrollBottom = $scrollTop + $scrollVisible;
|
||||||
|
$thumbTop = round($scrollTop * $visibleItems);
|
||||||
|
$thumbBottom = round($visibleItems * $scrollBottom);
|
||||||
|
|
||||||
|
echo "\e[40;37m";
|
||||||
|
$this->terminal
|
||||||
|
->writeAt($left, $top + 0, self::U_CORNER_ROUNDED_TOPLEFT.str_repeat(self::U_EDGE_HORIZONTAL,$width - 2).self::U_CORNER_ROUNDED_TOPRIGHT)
|
||||||
|
->writeAt($left, $top + 1, self::U_EDGE_VERTICAL.str_repeat(" ",$width - 2).self::U_EDGE_VERTICAL)
|
||||||
|
->writeAt($left + 2, $top + 1, "\e[1m" . $this->title . "\e[22m")
|
||||||
|
->writeAt($left, $top + 2, self::U_EDGE_VERTICAL_RIGHT.str_repeat(self::U_EDGE_HORIZONTAL,$width - 2).self::U_EDGE_VERTICAL_LEFT)
|
||||||
|
->writeAt($left, $top + $height - 1, self::U_CORNER_BOTTOMLEFT.str_repeat(self::U_EDGE_HORIZONTAL,$width - 2).self::U_CORNER_BOTTOMRIGHT)
|
||||||
|
;
|
||||||
|
for ($n = 0; $n < $visibleItems; $n++) {
|
||||||
|
if (isset($wrapped[$n+$this->scroll])) {
|
||||||
|
$item = " " . $wrapped[$n+$this->scroll]??null;
|
||||||
|
$item = $item . str_repeat(" ", $width - 2 - mb_strlen($item)) . "\e[40;37m";
|
||||||
|
} else {
|
||||||
|
$item = str_repeat(" ", $width - 2)."\e[40;37m";
|
||||||
|
}
|
||||||
|
if ($n >= $thumbTop && $n <= $thumbBottom) {
|
||||||
|
$scrollbar = "\e[97;1m".self::U_EDGE_VERTICAL_THUMB."\e[40;37;22m";
|
||||||
|
} else {
|
||||||
|
$scrollbar = "\e[37;2m".self::U_EDGE_VERTICAL_SCROLL."\e[40;37;22m";
|
||||||
|
}
|
||||||
|
$this->terminal
|
||||||
|
->writeAt($left, $top + 3 + $n, self::U_EDGE_VERTICAL.$item.$scrollbar);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\e[0m";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
namespace NoccyLabs\JEdit\List;
|
namespace NoccyLabs\JEdit\List;
|
||||||
|
|
||||||
use Countable;
|
use Countable;
|
||||||
|
use NoccyLabs\JEdit\Settings;
|
||||||
use NoccyLabs\JEdit\Tree\ArrayNode;
|
use NoccyLabs\JEdit\Tree\ArrayNode;
|
||||||
use NoccyLabs\JEdit\Tree\Tree;
|
use NoccyLabs\JEdit\Tree\Tree;
|
||||||
use NoccyLabs\JEdit\Tree\Node;
|
use NoccyLabs\JEdit\Tree\Node;
|
||||||
@ -159,8 +160,11 @@ class TreeList implements Countable
|
|||||||
|
|
||||||
if (!is_null($entry->key)) {
|
if (!is_null($entry->key)) {
|
||||||
echo (is_int($entry->key)
|
echo (is_int($entry->key)
|
||||||
?"\e[36;2m\u{e0b6}\e[7m#{$entry->key}\e[27m\u{e0b4}\e[22m "
|
?"\e[36;2m\u{e0b6}\e[7m#{$entry->key}\e[27m\u{e0b4}\e[22;37m "
|
||||||
:"\e[36m{$entry->key}:\e[37m ");
|
:(Settings::$editorQuotedKeys
|
||||||
|
? "\e[36m\"{$entry->key}\":\e[37m "
|
||||||
|
: "\e[36m{$entry->key}:\e[37m "
|
||||||
|
));
|
||||||
}
|
}
|
||||||
if ($entry->node instanceof ArrayNode) {
|
if ($entry->node instanceof ArrayNode) {
|
||||||
echo "[";
|
echo "[";
|
||||||
|
32
src/Settings.php
Normal file
32
src/Settings.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\JEdit;
|
||||||
|
|
||||||
|
class Settings
|
||||||
|
{
|
||||||
|
public static bool $editorQuotedKeys = false;
|
||||||
|
|
||||||
|
public static function load(string $filename): void
|
||||||
|
{
|
||||||
|
if (file_exists($filename) && is_readable($filename)) {
|
||||||
|
$data = json_decode(file_get_contents($filename));
|
||||||
|
self::$editorQuotedKeys = $data->editorQuotedKeys ?? false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function save(string $filename): void
|
||||||
|
{
|
||||||
|
$data = json_encode([
|
||||||
|
'editorQuotedKeys' => self::$editorQuotedKeys
|
||||||
|
], JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT)."\n";
|
||||||
|
@file_put_contents($filename, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function set(string $key, mixed $value): void
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'editorQuotedKeys':
|
||||||
|
self::$editorQuotedKeys = (bool)$value; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,15 @@ class ObjectNode extends Node implements CollapsibleNode
|
|||||||
$this->properties[$key] = $value;
|
$this->properties[$key] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function rename(string $key, string $newKey)
|
||||||
|
{
|
||||||
|
$renamed = [];
|
||||||
|
foreach ($this->properties as $k=>$v) {
|
||||||
|
$renamed[($k==$key)?$newKey:$k] = $v;
|
||||||
|
}
|
||||||
|
$this->properties = $renamed;
|
||||||
|
}
|
||||||
|
|
||||||
public function unset(string $key)
|
public function unset(string $key)
|
||||||
{
|
{
|
||||||
unset($this->properties[$key]);
|
unset($this->properties[$key]);
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use NoccyLabs\JEdit\Settings;
|
||||||
|
|
||||||
require_once __DIR__."/../vendor/autoload.php";
|
require_once __DIR__."/../vendor/autoload.php";
|
||||||
|
|
||||||
$filename = $argv[1]??null; // ?(__DIR__."/../composer.json");
|
define("SETTINGS_FILE", getenv("HOME")."/.config/jsonedit/config.json");
|
||||||
|
|
||||||
|
$filename = $argv[1]??null;
|
||||||
|
|
||||||
$terminal = new NoccyLabs\JEdit\Terminal\Terminal();
|
$terminal = new NoccyLabs\JEdit\Terminal\Terminal();
|
||||||
|
|
||||||
|
Settings::load(SETTINGS_FILE);
|
||||||
|
|
||||||
$editor = new NoccyLabs\JEdit\Editor\Editor($terminal);
|
$editor = new NoccyLabs\JEdit\Editor\Editor($terminal);
|
||||||
if ($filename) {
|
if ($filename) {
|
||||||
$editor->loadFile($filename);
|
$editor->loadFile($filename);
|
||||||
|
Loading…
Reference in New Issue
Block a user