From 215d2c6512ebf9aa410744b6b21e3faa85f62a73 Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Tue, 21 Jun 2022 22:29:19 +0100 Subject: [PATCH] heditor: edit and save the front buffer (not the whole source) --- src/heditor.c | 93 ++++++++++++++++++++++++++++++---- src/heditor.h | 9 ++++ src/main.c | 137 +++++++++++++++++++++++++++++++------------------- src/source.c | 23 +++++++-- src/source.h | 13 ++++- 5 files changed, 208 insertions(+), 67 deletions(-) diff --git a/src/heditor.c b/src/heditor.c index 0c67174..d93c90c 100644 --- a/src/heditor.c +++ b/src/heditor.c @@ -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; } diff --git a/src/heditor.h b/src/heditor.h index 6feacca..6c03f3b 100644 --- a/src/heditor.h +++ b/src/heditor.h @@ -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); diff --git a/src/main.c b/src/main.c index 128526d..af6b696 100644 --- a/src/main.c +++ b/src/main.c @@ -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 : "", 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(); } } diff --git a/src/source.c b/src/source.c index de30c3b..fe2d804 100644 --- a/src/source.c +++ b/src/source.c @@ -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) diff --git a/src/source.h b/src/source.h index 2ee8ccf..8677cd0 100644 --- a/src/source.h +++ b/src/source.h @@ -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);