heditor: edit and save the front buffer (not the whole source)

This commit is contained in:
Lephenixnoir 2022-06-21 22:29:19 +01:00
parent a08e7fa89d
commit 215d2c6512
Signed by untrusted user: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
5 changed files with 208 additions and 67 deletions

View File

@ -32,6 +32,8 @@ heditor *heditor_create(jwidget *parent)
e->bytes_per_line = 8;
e->line_spacing = 3;
e->font = dfont_default();
e->half_byte = false;
e->front_buffer_dirty = false;
return e;
}
@ -57,6 +59,11 @@ static void shake(heditor *e)
heditor_move_to(e, e->cursor);
}
static void emit_changed(heditor *e)
{
jwidget_emit(e, (jevent){ .type = HEDITOR_CHANGED });
}
bool heditor_move_to(heditor *e, int target_cursor)
{
bool changed = false;
@ -77,8 +84,10 @@ bool heditor_move_to(heditor *e, int target_cursor)
target_cursor = max(0, min(target_cursor, max_cursor));
if(target_cursor != e->cursor)
if(target_cursor != e->cursor) {
changed = true;
e->half_byte = false;
}
e->cursor = target_cursor;
/* Determine the scroll offset for the target. If the target falls within
@ -108,16 +117,32 @@ bool heditor_move_to(heditor *e, int target_cursor)
int view_size = e->visible_lines * bpl;
view_size = min(view_size, max(source_size(e->source) - e->scroll, 0));
if(source_ensure_loaded(e->source, e->scroll, view_size))
changed = true;
if(source_requires_load(e->source, e->scroll, view_size)) {
/* Don't lose the contents of the front buffer if it fails to save */
if(!e->front_buffer_dirty || source_save_front_buffer(e->source)) {
source_ensure_loaded(e->source, e->scroll, view_size);
e->front_buffer_dirty = false;
changed = true;
}
}
if(changed) {
e->widget.update = true;
jwidget_emit(e, (jevent){ .type = HEDITOR_CHANGED });
emit_changed(e);
}
return changed;
}
bool heditor_save_front_buffer(heditor *e)
{
if(e && source_save_front_buffer(e->source)) {
e->front_buffer_dirty = false;
emit_changed(e);
return true;
}
return false;
}
void heditor_set_insert_mode(heditor *e, bool insert)
{
e->insert = false;
@ -132,7 +157,7 @@ void heditor_set_source(heditor *e, source_t *source)
/* Force a change event */
if(!heditor_move_to(e, 0)) {
e->widget.update = true;
jwidget_emit(e, (jevent){ .type = HEDITOR_CHANGED });
emit_changed(e);
}
}
@ -166,7 +191,6 @@ static void heditor_poly_csize(void *e0)
heditor *e = e0;
jwidget *w = &e->widget;
// TODO: heditor: Subtler content size estimation
w->w = 128;
w->h = 64;
}
@ -207,7 +231,10 @@ static void heditor_poly_render(void *e0, int x, int y)
if(o >= s->buf_offset && o < front_buffer_max) {
int c = *(uint8_t *)(s->buf->mem + o - s->buf_offset);
sprintf(byte, "%02X", c);
if(e->cursor == o && e->half_byte)
sprintf(byte, "%01X_", c >> 4);
else
sprintf(byte, "%02X", c);
dprint(ascii_x, line_y, C_BLACK, "%c",
(c >= 0x20 && c < 0x7f) ? c : '.');
}
@ -237,14 +264,26 @@ static void heditor_poly_render(void *e0, int x, int y)
}
}
static int get_hexdigit(int key)
{
int digit = keycode_digit(key);
if(digit >= 0)
return digit;
if(key >= KEY_XOT && key <= KEY_TAN)
return key - KEY_XOT + 10;
return -1;
}
static bool heditor_poly_event(void *e0, jevent ev)
{
heditor *e = e0;
if(!e->source)
return false;
if(ev.type != JSCENE_KEY || ev.key.type == KEYEV_UP)
source_t *s = e->source;
if(!s || ev.type != JSCENE_KEY || ev.key.type == KEYEV_UP)
return false;
// Movement //
int key = ev.key.key;
int move_speed = (ev.key.alpha ? e->visible_lines : 1);
@ -275,6 +314,40 @@ static bool heditor_poly_event(void *e0, jevent ev)
return true;
}
// Edition //
if(!(e->source->intf->cap & SOURCE_WRITE))
return false;
if(e->insert && !(e->source->intf->cap & SOURCE_EXPAND))
return false;
int offset = e->cursor - s->buf_offset;
if(offset < 0 || (size_t)offset >= s->buf->data_size)
return false;
uint8_t *pointed_byte = s->buf->mem + (e->cursor - s->buf_offset);
int hexdigit = get_hexdigit(key);
if(hexdigit >= 0) {
if(e->half_byte) {
*pointed_byte = (*pointed_byte & 0xf0) | hexdigit;
e->half_byte = false;
e->front_buffer_dirty = true;
e->widget.update = true;
if(!heditor_move_to(e, e->cursor + 1))
emit_changed(e);
return true;
}
else {
*pointed_byte = (hexdigit << 4);
e->half_byte = true;
e->front_buffer_dirty = true;
e->widget.update = true;
emit_changed(e);
return true;
}
}
return false;
}

View File

@ -25,6 +25,7 @@ typedef struct {
int cursor;
/* Current insertion mode (false=overwrite, true=insert) */
bool insert;
/* Number of visible lines */
int8_t visible_lines;
/* Number of bytes printed per line */
@ -34,6 +35,11 @@ typedef struct {
/* Rendering font */
font_t const *font;
/* Whether we are currently writing into a half-finished byte */
bool half_byte;
/* Whether the front buffer is dirty */
bool front_buffer_dirty;
} heditor;
/* Type IDs */
@ -49,6 +55,9 @@ void heditor_set_source(heditor *e, source_t *source);
/* Scroll to the requested cursor position. */
bool heditor_move_to(heditor *e, int offset);
/* Save the front buffer. */
bool heditor_save_front_buffer(heditor *e);
/* Change the insertion mode; true for insertion, false for overwrite. If the
source doesn't support insertion, this function is a no-op. */
void heditor_set_insert_mode(heditor *e, bool insert);

View File

@ -22,8 +22,12 @@ struct {
jlabel *title;
jlabel *status;
jfkeys *fkeys;
/* Main editor. */
heditor *editor;
/* Source info widget. */
jlabel *source_info;
/* Source stats widget. */
jlabel *source_stats;
/* Current data source. */
source_t *source;
@ -45,6 +49,8 @@ enum {
FK_EDITOR,
FK_EDITOR_SOURCE,
FK_EDITOR_OPEN,
FK_INFO,
FK_STATS,
FK_VIEW,
};
@ -73,11 +79,17 @@ static void update_fkeys(void)
}
}
else if(app.fkmenu == FK_EDITOR_SOURCE) {
jfkeys_set(app.fkeys, "@SAVE;@SAVE AS;;;;");
jfkeys_set(app.fkeys, "@SAVE;@SAVE AS;@FRONT;;;");
}
else if(app.fkmenu == FK_EDITOR_OPEN) {
jfkeys_set(app.fkeys, "@FILE;@LAZY;@MEMORY;@NEW");
}
else if(app.fkmenu == FK_INFO) {
jfkeys_set(app.fkeys, ";;;;;#VIEW");
}
else if(app.fkmenu == FK_STATS) {
jfkeys_set(app.fkeys, ";;;;;#VIEW");
}
else if(app.fkmenu == FK_VIEW) {
fkeys_sprintf(app.fkeys, ";;;%sEDITOR;%sINFO;%sSTATS",
app.view == VIEW_EDITOR ? "#" : "@",
@ -88,7 +100,6 @@ static void update_fkeys(void)
static void update_status(void)
{
/* TODO: (*) and (**) markers on title/status bar */
source_t *s = app.source;
if(!s) {
@ -100,8 +111,16 @@ static void update_status(void)
size_t size = source_size(s);
jwidget_set_visible(app.status, true);
if(s->origin)
jlabel_asprintf(app.title, "%s (%d B) - Hex Editor", s->origin, size);
if(s->origin) {
char const *dirty = "";
if(app.editor->front_buffer_dirty)
dirty = " **";
/* TODO: (*) marker on title bar */
else if(0)
dirty = " *";
jlabel_asprintf(app.title, "%s%s (%d B) - Hex Editor",
s->origin, dirty, size);
}
else
jlabel_set_text(app.title, "(nil) - Hex Editor");
@ -132,35 +151,15 @@ static void update_source_info(void)
"%s\n"
"Size: %d bytes\n"
"Front buffer: %p, size %d/%d (x%d)\n"
"Front buffer maps to: @%d, size TODO\n"
"Front buffer maps to: @%d, size %d\n"
"Capabilities: %s",
s->origin ? s->origin : "<unnamed source>",
source_size(s),
s->buf->mem, s->buf->data_size, s->buf->mem_size, s->buf->block_size,
s->buf_offset, /* s->buf_size, */
s->buf_offset, s->buf_segment_size,
caps + 1);
}
static jwidget *make_popup(void *parent0, int w, int h)
{
J_CAST(parent)
jwidget *popup = jwidget_create(parent);
if(!popup)
return NULL;
jwidget_set_floating(popup, true);
jwidget_set_border(popup, J_BORDER_SOLID, 2, C_BLACK);
jwidget_set_padding(popup, 4, 4, 4, 4);
jwidget_set_background(popup, C_WHITE);
jwidget_set_visible(popup, false);
jwidget_set_fixed_size(popup, w, h);
popup->x = (DWIDTH - w) / 2;
popup->y = (DHEIGHT - h) / 2;
return popup;
}
void hex_view(void)
{
app.source = NULL;
@ -192,10 +191,11 @@ void hex_view(void)
// Main tab //
jwidget *tab = jwidget_create(NULL);
heditor *mem = heditor_create(tab); /* previously 321x153 */
jinput *goto_input = jinput_create("Goto: ", 12, tab);
jlabel *status = jlabel_create("", tab);
jwidget *tab_editor = jwidget_create(NULL);
heditor *mem = heditor_create(tab_editor); /* previously 321x153 */
jinput *goto_input = jinput_create("Goto: ", 12, tab_editor);
jlabel *status = jlabel_create("", tab_editor);
app.editor = mem;
app.status = status;
jwidget_set_margin(mem, 2, 0, 0, 0);
@ -208,9 +208,9 @@ void hex_view(void)
jwidget_set_stretch(goto_input, 1, 0, false);
jwidget_set_visible(goto_input, false);
jlayout_set_vbox(tab)->spacing = 3;
jwidget_add_child(stack, tab);
jwidget_set_stretch(tab, 1, 1, false);
jlayout_set_vbox(tab_editor)->spacing = 3;
jwidget_add_child(stack, tab_editor);
jwidget_set_stretch(tab_editor, 1, 1, false);
// File selection tab //
@ -218,16 +218,27 @@ void hex_view(void)
jfileselect_set_show_file_size(fileselect, true);
jwidget_set_stretch(fileselect, 1, 1, false);
// Source info popup //
// Info tab //
jwidget *popup_source_info = make_popup(scene, DWIDTH*3/4, DHEIGHT/2);
jlayout_set_vbox(popup_source_info)->spacing = 3;
jwidget *tab_info = jwidget_create(stack);
jwidget_set_stretch(tab_info, 1, 1, false);
jlayout_set_vbox(tab_info)->spacing = 3;
jlabel *source_info = jlabel_create("", popup_source_info);
jlabel *source_info = jlabel_create("", tab_info);
jlabel_set_wrap_mode(source_info, J_WRAP_WORD);
jlabel_set_line_spacing(source_info, 2);
jlabel_set_line_spacing(source_info, 4);
jlabel_set_block_alignment(source_info, J_ALIGN_LEFT, J_ALIGN_TOP);
jwidget_set_stretch(source_info, 1, 1, false);
app.source_info = source_info;
// Stats tab //
jwidget *tab_stats = jwidget_create(stack);
jwidget_set_stretch(tab_stats, 1, 1, false);
jlayout_set_vbox(tab_stats)->spacing = 3;
jlabel *source_stats = jlabel_create("TODO", tab_stats);
app.source_stats = source_stats;
// Initial state //
@ -325,13 +336,21 @@ void hex_view(void)
else if(key == KEY_F2 && app.source) {
/* TODO: Source save as */
}
else if(key == KEY_F3 && app.source) {
heditor_save_front_buffer(mem);
}
else if(key == KEY_EXIT) {
app.fkmenu = FK_EDITOR;
update_fkeys();
}
}
else if(app.fkmenu == FK_EDITOR_OPEN) {
if(key == KEY_F1 || key == KEY_F2) {
if(key == KEY_F1) {
/* TODO: Check if current source is unsaved? */
jfileselect_browse(fileselect, "/");
jscene_show_and_focus(scene, fileselect);
}
else if(key == KEY_F2) {
/* TODO: Check if current source is unsaved? */
jfileselect_browse(fileselect, "/");
jscene_show_and_focus(scene, fileselect);
@ -347,31 +366,45 @@ void hex_view(void)
update_fkeys();
}
}
else if(app.fkmenu == FK_INFO) {
if(key == KEY_F6 && app.source) {
app.fkmenu = FK_VIEW;
update_fkeys();
}
}
else if(app.fkmenu == FK_STATS) {
if(key == KEY_F6 && app.source) {
app.fkmenu = FK_VIEW;
update_fkeys();
}
}
else if(app.fkmenu == FK_VIEW) {
if(key == KEY_F4) {
jscene_show_and_focus(scene, mem);
app.fkmenu = FK_EDITOR;
app.view = VIEW_EDITOR;
update_fkeys();
}
else if(key == KEY_F5) {
/* TODO: Show info panel */
// jscene_show_and_focus(scene, tab_info);
// app.fkmenu = FK_INFO;
// update_fkeys();
jscene_show_and_focus(scene, source_info);
app.fkmenu = FK_INFO;
app.view = VIEW_INFO;
update_source_info();
update_fkeys();
}
else if(key == KEY_F6) {
/* TODO: Show statistics panel */
// jscene_show_and_focus(scene, tab_stats);
// app.fkmenu = FK_STATS;
// update_fkeys();
jscene_show_and_focus(scene, source_stats);
app.fkmenu = FK_STATS;
app.view = VIEW_STATS;
update_fkeys();
}
else if(key == KEY_EXIT) {
// if(current_tab == tab_editor)
if(app.view == VIEW_EDITOR)
app.fkmenu = FK_EDITOR;
// else if(current_tab == tab_info)
// app.fkmenu = FK_INFO;
// else if(current_tab == tab_stats)
// app.fkmenu = FK_STATS;
else if(app.view == VIEW_INFO)
app.fkmenu = FK_INFO;
else if(app.view == VIEW_STATS)
app.fkmenu = FK_STATS;
update_fkeys();
}
}

View File

@ -33,24 +33,31 @@ ssize_t source_load(source_t *s, off_t offset, size_t segment_size)
if(!s || !buffer_resize(s->buf, segment_size))
return -1;
s->buf_offset = offset;
ssize_t rc = s->intf->read(s->cookie, s->buf->mem, offset, segment_size);
if(rc < 0)
return -1;
s->buf_offset = offset;
s->buf_segment_size = rc;
s->buf->data_size = rc;
return rc;
}
bool source_ensure_loaded(source_t *s, off_t offset, int n)
bool source_requires_load(source_t *s, off_t offset, int n)
{
if(!s)
return false;
if(offset >= s->buf_offset &&
offset + n <= (int)(s->buf_offset + s->buf->data_size))
return false;
return true;
}
void source_ensure_loaded(source_t *s, off_t offset, int n)
{
if(!source_requires_load(s, offset, n))
return;
int s_size = source_size(s);
int max_offset = max(s_size - n, 0);
@ -61,7 +68,15 @@ bool source_ensure_loaded(source_t *s, off_t offset, int n)
/* Use 2 blocks of buffer by default */
source_load(s, new_s_offset, 2 * s->buf->block_size);
return true;
}
bool source_save_front_buffer(source_t *s)
{
if(!s || !s->intf->write)
return false;
return s->intf->write(s->cookie, s->buf->mem, s->buf->data_size,
s->buf_offset, s->buf_segment_size);
}
void source_free(source_t *s)

View File

@ -52,6 +52,10 @@ typedef struct {
buffer_t *buf;
/* Address of the front buffer in the source */
off_t buf_offset;
/* Length of the front buffer in the source */
int buf_segment_size;
/* TODO: Whether the source is dirty, ie. has unsaved data other than in
the front buffer */
} source_t;
/* Open a data source; this is a low-level function used by source-specific
@ -65,10 +69,17 @@ size_t source_size(source_t *s);
/* Replace the source's buffer's contents with data read from the source. */
ssize_t source_load(source_t *s, off_t offset, size_t segment_size);
/* Determine whether the `n` bytes following `offset` are currently loaded in
the front buffer. */
bool source_requires_load(source_t *s, off_t offset, int n);
/* Make sure that the `n` bytes following `offset` are loaded in the front
buffer. If not, load the region around there. Returns true if the region
changed, otherwise false. */
bool source_ensure_loaded(source_t *s, off_t offset, int n);
void source_ensure_loaded(source_t *s, off_t offset, int n);
/* Push the front buffer into the data source. */
bool source_save_front_buffer(source_t *s);
/* Free a source and its data. */
void source_free(source_t *s);