Add infrastructure for field highlighting

This commit is contained in:
Přemysl Eric Janouch 2017-01-17 14:12:19 +01:00
parent d2aed61783
commit 80e511f3cb
Signed by: p
GPG Key ID: B715679E3A361BE6
1 changed files with 213 additions and 12 deletions

225
hex.c
View File

@ -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 ();