aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPřemysl Janouch <p.janouch@gmail.com>2017-01-17 14:12:19 +0100
committerPřemysl Janouch <p.janouch@gmail.com>2017-01-17 14:13:57 +0100
commit80e511f3cb1a28872ed56d323a9ffd5694a85484 (patch)
treecd154ea421a0d29dbab016556e4cce6414aba03a
parentd2aed617839fe03df68039fd6fb31767be1e8cae (diff)
downloadhex-80e511f3cb1a28872ed56d323a9ffd5694a85484.tar.gz
hex-80e511f3cb1a28872ed56d323a9ffd5694a85484.tar.xz
hex-80e511f3cb1a28872ed56d323a9ffd5694a85484.zip
Add infrastructure for field highlighting
-rw-r--r--hex.c225
1 files changed, 213 insertions, 12 deletions
diff --git a/hex.c b/hex.c
index a5c08cf..cf719e9 100644
--- a/hex.c
+++ b/hex.c
@@ -23,18 +23,23 @@
// We "need" to have an enum for attributes before including liberty.
// Avoiding colours in the defaults here in order to support dumb terminals.
#define ATTRIBUTE_TABLE(XX) \
- XX( FOOTER, "footer", -1, -1, 0 ) \
- XX( FOOTER_HL, "footer_hl", -1, -1, A_BOLD ) \
- /* Bar */ \
- XX( BAR, "bar", -1, -1, A_REVERSE ) \
- XX( BAR_HL, "bar_hl", -1, -1, A_REVERSE | A_BOLD ) \
- /* View */ \
- XX( EVEN, "even", -1, -1, 0 ) \
- XX( ODD, "odd", -1, -1, 0 ) \
- XX( SELECTION, "selection", -1, -1, A_REVERSE ) \
- /* These are for debugging only */ \
- XX( WARNING, "warning", 3, -1, 0 ) \
- XX( ERROR, "error", 1, -1, 0 )
+ XX( FOOTER, "footer", -1, -1, 0 ) \
+ XX( FOOTER_HL, "footer_hl", -1, -1, A_BOLD ) \
+ /* Bar */ \
+ XX( BAR, "bar", -1, -1, A_REVERSE ) \
+ XX( BAR_HL, "bar_hl", -1, -1, A_REVERSE | A_BOLD ) \
+ /* View */ \
+ XX( EVEN, "even", -1, -1, 0 ) \
+ XX( ODD, "odd", -1, -1, 0 ) \
+ XX( SELECTION, "selection", -1, -1, A_REVERSE ) \
+ /* Field highlights */ \
+ XX( C1, "c1", 22, 194, 0 ) \
+ XX( C2, "c2", 88, 224, 0 ) \
+ XX( C3, "c3", 58, 229, 0 ) \
+ XX( C4, "c4", 20, 189, 0 ) \
+ /* These are for debugging only */ \
+ XX( WARNING, "warning", 3, -1, 0 ) \
+ XX( ERROR, "error", 1, -1, 0 )
enum
{
@@ -88,6 +93,26 @@ update_curses_terminal_size (void)
#endif // HAVE_RESIZETERM && TIOCGWINSZ
}
+// --- Simple array support ----------------------------------------------------
+
+// Primitives for arrays
+
+#define ARRAY(type, name) type *name; size_t name ## _len, name ## _size;
+#define ARRAY_INIT(a) \
+ BLOCK_START \
+ a = xcalloc (sizeof *a, (a ## _size = 16)); \
+ a ## _len = 0; \
+ BLOCK_END
+#define ARRAY_RESERVE(a, n) \
+ array_reserve ((void **) &a, sizeof *a, a ## _len + n, &a ## _size)
+
+static void
+array_reserve (void **array, size_t element_size, size_t len, size_t *size)
+{
+ while (len > *size)
+ *array = xreallocarray (*array, element_size, *size <<= 1);
+}
+
// --- Application -------------------------------------------------------------
enum
@@ -101,6 +126,26 @@ enum endianity
ENDIANITY_BE ///< Big endian
};
+struct mark
+{
+ int64_t offset; ///< Offset of the mark
+ int64_t len; ///< Length of the mark
+ size_t description; ///< Textual description string offset
+};
+
+// XXX: can we avoid constructing the marks_by_offset lookup array?
+// How much memory is it even going to consume in reality?
+
+/// This is the final result suitable for display, including unmarked areas.
+/// We might infer `color` from the index of this entry but then unmarked areas
+/// would skip a color, which is undesired.
+struct marks_by_offset
+{
+ int64_t offset; ///< Offset in the file
+ struct mark **marks; ///< Sentinelled array of mark pointers
+ int color; ///< Color of the area until next offset
+};
+
static struct app_context
{
// Event loop:
@@ -124,6 +169,13 @@ static struct app_context
int64_t data_len; ///< Length of the data
int64_t data_offset; ///< Offset of the data within the file
+ // Field marking:
+
+ ARRAY (struct mark, marks) ///< Marks
+ struct str mark_strings; ///< Storage for mark descriptions
+
+ ARRAY (struct marks_by_offset, marks_by_offset)
+
// View:
int64_t view_top; ///< Offset of the top of the screen
@@ -239,6 +291,10 @@ app_init_context (void)
poller_init (&g_ctx.poller);
config_init (&g_ctx.config);
+ ARRAY_INIT (g_ctx.marks);
+ str_init (&g_ctx.mark_strings);
+ ARRAY_INIT (g_ctx.marks_by_offset);
+
// This is also approximately what libunistring does internally,
// since the locale name is canonicalized by locale_charset().
// Note that non-Unicode locales are handled pretty inefficiently.
@@ -268,6 +324,8 @@ app_init_terminal (void)
if (g_ctx.attrs[a].fg >= COLORS || g_ctx.attrs[a].fg < -1
|| g_ctx.attrs[a].bg >= COLORS || g_ctx.attrs[a].bg < -1)
{
+ // FIXME: we need a 256color default palette that fails gracefully
+ // to something like underlined fields
app_init_attributes ();
return;
}
@@ -283,6 +341,13 @@ app_free_context (void)
config_free (&g_ctx.config);
poller_free (&g_ctx.poller);
+ free (g_ctx.marks);
+ str_free (&g_ctx.mark_strings);
+
+ for (size_t i = 0; i < g_ctx.marks_by_offset_len; i++)
+ free (g_ctx.marks_by_offset[i].marks);
+ free (g_ctx.marks_by_offset);
+
free (g_ctx.message);
free (g_ctx.filename);
@@ -317,6 +382,106 @@ app_is_character_in_locale (ucs4_t ch)
return true;
}
+// --- Field marking -----------------------------------------------------------
+
+/// Find the "marks_by_offset" object covering the offset (if any)
+static ssize_t
+app_find_marks (int64_t offset)
+{
+ ssize_t min = 0, end = g_ctx.marks_by_offset_len;
+ while (min < end)
+ {
+ ssize_t mid = min + (end - min) / 2;
+ if (offset >= g_ctx.marks_by_offset[mid].offset)
+ min = mid + 1;
+ else
+ end = mid;
+ }
+ return min - 1;
+}
+
+static struct marks_by_offset *
+app_marks_at_offset (int64_t offset)
+{
+ ssize_t i = app_find_marks (offset);
+ if (i < 0 || (size_t) i >= g_ctx.marks_by_offset_len)
+ return NULL;
+
+ struct marks_by_offset *marks = &g_ctx.marks_by_offset[i];
+ if (marks->offset > offset)
+ return NULL;
+ return marks;
+}
+
+static int
+app_mark_cmp (const void *first, const void *second)
+{
+ const struct mark *a = first, *b = second;
+ // This ordering is pretty much arbitrary, seemed to make sense
+ if (a->offset < b->offset) return -1;
+ if (a->offset > b->offset) return 1;
+ if (a->len < b->len) return 1;
+ if (a->len > b->len) return -1;
+ return 0;
+}
+
+static void
+app_flatten_marks (void)
+{
+ qsort (g_ctx.marks, g_ctx.marks_len, sizeof *g_ctx.marks, app_mark_cmp);
+ if (!g_ctx.marks_len)
+ return;
+
+ ARRAY (struct mark *, current)
+ ARRAY_INIT (current);
+ int current_color = 0;
+
+ struct mark *next = g_ctx.marks;
+ struct mark *end = next + g_ctx.marks_len;
+ while (current_len || next < end)
+ {
+ // Find the closest offset at which marks change
+ int64_t closest = g_ctx.data_offset + g_ctx.data_len;
+ if (next < end)
+ closest = next->offset;
+ for (size_t i = 0; i < current_len; i++)
+ closest = MIN (closest, current[i]->offset + current[i]->len);
+
+ // Remove from "current" marks that have ended
+ for (size_t i = 0; i < current_len; i++)
+ {
+ if (closest == current[i]->offset + current[i]->len)
+ memmove (current + i, current + i + 1,
+ (--current_len - i) * sizeof *current);
+ }
+
+ // Add any new marks at "closest"
+ while (next < end && next->offset == closest)
+ {
+ ARRAY_RESERVE (current, 1);
+ current[current_len++] = next++;
+ }
+
+ // Save marks at that offset to be used by rendering
+ struct mark **marks = NULL;
+ int color = -1;
+
+ if (current_len)
+ {
+ marks = memcpy (xcalloc (sizeof *marks, current_len + 1),
+ current, sizeof *marks * current_len);
+
+ color = ATTRIBUTE_C1 + current_color++;
+ current_color %= 4;
+ }
+
+ ARRAY_RESERVE (g_ctx.marks_by_offset, 1);
+ g_ctx.marks_by_offset[g_ctx.marks_by_offset_len++] =
+ (struct marks_by_offset) { closest, marks, color };
+ }
+ free (current);
+}
+
// --- Rendering ---------------------------------------------------------------
static void
@@ -379,6 +544,10 @@ app_make_row (struct row_buffer *buf, int64_t addr, int attrs)
else
{
int cell_attrs = attrs;
+ struct marks_by_offset *marks = app_marks_at_offset (cell_addr);
+ if (marks && marks->color >= 0)
+ cell_attrs = g_ctx.attrs[marks->color].attrs;
+
if (cell_addr >= g_ctx.view_cursor
&& cell_addr < g_ctx.view_cursor + 8)
cell_attrs |= A_UNDERLINE;
@@ -418,6 +587,33 @@ app_draw_view (void)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+static void
+app_draw_info (void)
+{
+ const struct marks_by_offset *marks;
+ if (!(marks = app_marks_at_offset (g_ctx.view_cursor)))
+ return;
+
+ int x_offset = 70;
+ struct mark **iter = marks->marks;
+ for (int y = 0; y < app_visible_rows (); y++)
+ {
+ struct mark *mark;
+ if (!iter || !(mark = *iter++))
+ break;
+
+ struct row_buffer buf;
+ row_buffer_init (&buf);
+ row_buffer_append (&buf,
+ g_ctx.mark_strings.str + mark->description, 0);
+
+ move (y, x_offset);
+ app_flush_buffer (&buf, COLS - x_offset, 0);
+ }
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
static uint64_t
app_decode (const uint8_t *p, size_t len, enum endianity endianity)
{
@@ -564,6 +760,7 @@ app_on_refresh (void *user_data)
erase ();
app_draw_view ();
+ app_draw_info ();
app_draw_footer ();
int64_t diff = g_ctx.view_cursor - g_ctx.view_top;
@@ -1060,6 +1257,8 @@ app_init_poller_events (void)
g_ctx.refresh_event.dispatcher = app_on_refresh;
}
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
/// Decode size arguments according to similar rules to those that dd(1) uses;
/// we support octal and hexadecimal numbers but they clash with suffixes
static bool
@@ -1200,6 +1399,8 @@ main (int argc, char *argv[])
print_warning ("failed to set the locale");
app_init_context ();
+ app_flatten_marks ();
+
app_load_configuration ();
app_init_terminal ();
signals_setup_handlers ();