From 7a0978071e0992b6e3ded4cf094c5e7574b8f780 Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Mon, 20 Jun 2022 01:02:52 +0100 Subject: [PATCH] heditor: navigation by cursor rather than row --- src/heditor.c | 150 +++++++++++++++++++++++++++++++------------------- src/heditor.h | 6 +- src/main.c | 5 +- 3 files changed, 98 insertions(+), 63 deletions(-) diff --git a/src/heditor.c b/src/heditor.c index 889a878..dc9186f 100644 --- a/src/heditor.c +++ b/src/heditor.c @@ -26,6 +26,7 @@ heditor *heditor_create(jwidget *parent) e->source = NULL; e->scroll = 0; + e->cursor = 0; e->visible_lines = 0; e->bytes_per_line = 8; e->line_spacing = 3; @@ -34,25 +35,32 @@ heditor *heditor_create(jwidget *parent) return e; } -static int compute_max_scroll(heditor *e) +static void compute_maxima(heditor *e, int *max_scroll, int *max_cursor) { if(!e || !e->source) - return 0; + return; int bpl = e->bytes_per_line; - int rows = (source_size(e->source) + bpl - 1) / bpl; - return max(rows - e->visible_lines, 0) * bpl; + int size = source_size(e->source); + int rows = (size + bpl - 1) / bpl; + + if(max_scroll) + *max_scroll = max(rows - e->visible_lines, 0) * bpl; + if(max_cursor) + *max_cursor = size - 1; } /* Adjust positioning to make sure we stay on legal values */ static void shake(heditor *e) { - heditor_scroll_to(e, e->scroll); + heditor_move_to(e, e->cursor); } -bool heditor_scroll_to(heditor *e, int target_scroll) +bool heditor_move_to(heditor *e, int target_cursor) { bool changed = false; + int max_scroll=0, max_cursor=0; + compute_maxima(e, &max_scroll, &max_cursor); /* Recompute the number of visible lines */ @@ -64,11 +72,32 @@ bool heditor_scroll_to(heditor *e, int target_scroll) changed = true; e->visible_lines = visible_lines; - /* Clamp the scroll to allowed limits */ + /* Clamp the cursor to allowed limits */ - int max_scroll = compute_max_scroll(e); - int adjusted_scroll = target_scroll - (target_scroll % e->bytes_per_line); - int new_scroll = max(0, min(adjusted_scroll, max_scroll)); + target_cursor = max(0, min(target_cursor, max_cursor)); + + if(target_cursor != e->cursor) + changed = true; + e->cursor = target_cursor; + + /* Determine the scroll offset for the target. If the target falls within + the center of the current view (ie. everything but the first and last + lines), do nothing. Otherwise, scroll to make it the first/last line. */ + int bpl = e->bytes_per_line; + int target_row = target_cursor - (target_cursor % bpl); + + int target_scroll = + // Minimum scroll where target is in the center + max(target_row - (visible_lines - 2) * bpl, + // Default value + min(e->scroll, + // Maximum scroll where target is in the center + target_row - bpl)); + + /* Clamp the newly-decided scroll to allowed limits */ + + int new_scroll = target_scroll - (target_scroll % bpl); + new_scroll = max(0, min(new_scroll, max_scroll)); if(new_scroll != e->scroll) changed = true; @@ -76,7 +105,7 @@ bool heditor_scroll_to(heditor *e, int target_scroll) /* Keep the source's front buffer aligned with the view */ - int view_size = e->visible_lines * e->bytes_per_line; + 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; @@ -92,7 +121,7 @@ void heditor_set_source(heditor *e, source_t *source) { e->source = source; /* Force a change event */ - if(!heditor_scroll_to(e, 0)) { + if(!heditor_move_to(e, 0)) { e->widget.update = true; jwidget_emit(e, (jevent){ .type = HEDITOR_CHANGED }); } @@ -139,60 +168,57 @@ static void heditor_poly_layout(void *e0) shake(e); } -/* Generate hexdump strings from raw data */ -static int hexdump(source_t *s, off_t offset, char *header, char *bytes, - char *ascii, int n) -{ - if(!s) return 1; - - sprintf(header, "%08X:", offset); - int bytes_i = 0; - - for(int k = 0; k < n; k++) { - off_t o = offset + k; - - if(o >= s->buf_offset && o < (int)(s->buf_offset+s->buf->data_size)) { - int c = *(uint8_t *)(s->buf->mem + o - s->buf_offset); - ascii[k] = (c >= 0x20 && c < 0x7f) ? c : '.'; - bytes_i += sprintf(bytes + bytes_i, "%02X%s", c, - (k & 1) ? " " : ""); - } - else { - ascii[k] = 0x7f; - bytes_i += sprintf(bytes + bytes_i, "--%s", (k & 1) ? " " : ""); - } - } - - ascii[n] = 0; - return 0; -} - static void heditor_poly_render(void *e0, int x, int y) { heditor *e = e0; - char header[12], bytes[48], ascii[24]; + source_t *s = e->source; + char byte[16]; - int offset = e->scroll; - int line_height = e->font->line_height + e->line_spacing; - - if(!e->source) { + if(!s) { dtext(x, y, C_BLACK, "No source opened"); return; } + int offset = e->scroll; + int line_height = e->font->line_height + e->line_spacing; + int front_buffer_max = s->buf_offset + s->buf->data_size; + int n = e->bytes_per_line; + for(int i = 0; i < e->visible_lines; i++) { - int rc = hexdump(e->source, offset, header, bytes, ascii, - e->bytes_per_line); + int line_y = y + line_height * i; + dprint(x, line_y, C_BLACK, "%08X:", offset); - dtext(x, y + line_height*i, C_BLACK, header); - dtext(x+85, y + line_height*i, rc ? C_RED : C_BLACK, bytes); + int bytes_x = x + 85; + int ascii_x = x + 250; - for(int k = 7; k >= 0; k--) { - ascii[k+1] = 0; - dtext(x + 250 + 9*k, y + 12*i, C_BLACK, ascii+k); + for(int k = 0; k < n; k++) { + off_t o = offset + k; + strcpy(byte, "--"); + + if(o >= s->buf_offset && o < front_buffer_max) { + int c = *(uint8_t *)(s->buf->mem + o - s->buf_offset); + + sprintf(byte, "%02X", c); + dprint(ascii_x, line_y, C_BLACK, "%c", + (c >= 0x20 && c < 0x7f) ? c : '.'); + } + + int text_w; + dsize(byte, NULL, &text_w, NULL); + + int fg = C_BLACK; + if(e->cursor == o) { + drect(bytes_x - 1, line_y - 1, bytes_x + text_w, + line_y + e->font->line_height, C_BLACK); + fg = C_WHITE; + } + dtext(bytes_x, line_y, fg, byte); + + bytes_x += text_w + ((k & 1) ? 7 : 2); + ascii_x += 9; } - offset += e->bytes_per_line; + offset += n; } } @@ -204,25 +230,33 @@ static bool heditor_poly_event(void *e0, jevent ev) if(ev.type != JSCENE_KEY || ev.key.type == KEYEV_UP) return false; - int max_scroll = compute_max_scroll(e); int key = ev.key.key; int move_speed = (ev.key.alpha ? e->visible_lines : 1); if(key == KEY_UP && ev.key.shift) { - heditor_scroll_to(e, 0); + heditor_move_to(e, 0); return true; } else if(key == KEY_UP) { - heditor_scroll_to(e, e->scroll - move_speed * e->bytes_per_line); + heditor_move_to(e, e->cursor - move_speed * e->bytes_per_line); return true; } if(key == KEY_DOWN && ev.key.shift) { - heditor_scroll_to(e, max_scroll); + heditor_move_to(e, source_size(e->source) - 1); return true; } else if(key == KEY_DOWN) { - heditor_scroll_to(e, e->scroll + move_speed * e->bytes_per_line); + heditor_move_to(e, e->cursor + move_speed * e->bytes_per_line); + return true; + } + + if(key == KEY_LEFT) { + heditor_move_to(e, e->cursor - 1); + return true; + } + if(key == KEY_RIGHT) { + heditor_move_to(e, e->cursor + 1); return true; } diff --git a/src/heditor.h b/src/heditor.h index 5821864..7605d82 100644 --- a/src/heditor.h +++ b/src/heditor.h @@ -21,6 +21,8 @@ typedef struct { source_t *source; /* Current scroll offset, in bytes */ int scroll; + /* Current cursor position in source, in bytes */ + int cursor; /* Number of visible lines */ int8_t visible_lines; /* Number of bytes printed per line */ @@ -42,8 +44,8 @@ heditor *heditor_create(jwidget *parent); /* Switch the source. The old source is not freed by the editor. */ void heditor_set_source(heditor *e, source_t *source); -/* Scroll to the requested offset. */ -bool heditor_scroll_to(heditor *e, int offset); +/* Scroll to the requested cursor position. */ +bool heditor_move_to(heditor *e, int offset); /* Trivial properties */ void heditor_set_font(heditor *e, font_t const *font); diff --git a/src/main.c b/src/main.c index e1c7b8c..3f770d4 100644 --- a/src/main.c +++ b/src/main.c @@ -101,16 +101,15 @@ void hex_view(void) } if(e.type == JINPUT_VALIDATED) { /* Parse string into hexa */ - uint32_t target = 0; char const *str = jinput_value(input); - + uint32_t target = 0; for(int k = 0; k < str[k]; k++) { target <<= 4; if(str[k] <= '9') target += (str[k] - '0'); else target += ((str[k]|0x20) - 'a' + 10); } - heditor_scroll_to(mem, target); + heditor_move_to(mem, target); } if(e.type == JINPUT_VALIDATED || e.type == JINPUT_CANCELED) { jwidget_set_visible(input, false);