xF: render formatting
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success

This commit is contained in:
2026-07-30 21:40:47 +02:00
parent 70fa559752
commit 8827d1d473
+309 -64
View File
@@ -34,6 +34,27 @@
// ~~~ Buffer lines ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
enum buffer_line_item_style
{
BUFFER_LINE_ITEM_BOLD = 1 << 0,
BUFFER_LINE_ITEM_ITALIC = 1 << 1,
BUFFER_LINE_ITEM_UNDERLINE = 1 << 2,
BUFFER_LINE_ITEM_INVERSE = 1 << 3,
BUFFER_LINE_ITEM_CROSSED_OUT = 1 << 4,
BUFFER_LINE_ITEM_MONOSPACE = 1 << 5,
};
// See: union relay_item_data
struct buffer_line_item
{
LIST_HEADER (struct buffer_line_item)
int16_t fg;
int16_t bg;
unsigned style;
char text[];
};
// See: struct relay_event_data_buffer_line
struct buffer_line
{
@@ -46,29 +67,50 @@ struct buffer_line
bool is_highlight;
enum relay_rendition rendition;
uint64_t when; ///< Unix timestamp in milliseconds
char text[];
struct buffer_line_item *items;
struct buffer_line_item *items_tail;
};
static struct buffer_line *
buffer_line_new_text (const char *text, enum relay_rendition rendition,
uint64_t when)
static void
buffer_line_item_add (struct buffer_line_item **head,
struct buffer_line_item **tail, const char *text, int16_t fg, int16_t bg,
unsigned style)
{
size_t len = strlen (text);
struct buffer_line *self = xcalloc (1, sizeof *self + len + 1);
memcpy (self->text, text, len + 1);
self->rendition = rendition;
self->when = when;
return self;
struct buffer_line_item *item = xcalloc (1, sizeof *item + len + 1);
item->fg = fg;
item->bg = bg;
item->style = style;
memcpy (item->text, text, len + 1);
LIST_APPEND_WITH_TAIL (*head, *tail, item);
}
static void
buffer_line_items_free (struct buffer_line_item *items)
{
LIST_FOR_EACH (struct buffer_line_item, item, items)
free (item);
}
static void
buffer_line_free (struct buffer_line *self)
{
buffer_line_items_free (self->items);
free (self);
}
static struct buffer_line *
buffer_line_clone (const struct buffer_line *line)
{
struct buffer_line *self =
buffer_line_new_text (line->text, line->rendition, line->when);
struct buffer_line *self = xcalloc (1, sizeof *self);
self->rendition = line->rendition;
self->when = line->when;
self->leaked = line->leaked;
self->is_unimportant = line->is_unimportant;
self->is_highlight = line->is_highlight;
LIST_FOR_EACH (struct buffer_line_item, item, line->items)
buffer_line_item_add (&self->items, &self->items_tail,
item->text, item->fg, item->bg, item->style);
return self;
}
@@ -82,7 +124,8 @@ struct buffer
char *buffer_name;
enum relay_buffer_kind kind;
char *server_name;
char *topic;
struct buffer_line_item *topic;
struct buffer_line_item *topic_tail;
char *modes;
bool hide_unimportant;
@@ -132,7 +175,7 @@ static void
buffer_clear (struct buffer *self)
{
LIST_FOR_EACH (struct buffer_line, iter, self->lines)
free (iter);
buffer_line_free (iter);
self->lines = self->lines_tail = NULL;
}
@@ -151,7 +194,7 @@ buffer_pop_excess_lines (struct buffer *self)
{
struct buffer_line *line = self->lines;
LIST_UNLINK_WITH_TAIL (self->lines, self->lines_tail, line);
free (line);
buffer_line_free (line);
}
}
@@ -160,7 +203,7 @@ buffer_destroy (struct buffer *self)
{
free (self->buffer_name);
free (self->server_name);
free (self->topic);
buffer_line_items_free (self->topic);
free (self->modes);
free (self->input);
strv_free (&self->history);
@@ -269,7 +312,23 @@ struct binding
XX (ACTION, 1, -1, 0) \
XX (LEAKED, 248, -1, 0) \
XX (PROMPT, -1, 255, A_BOLD) \
XX (SCROLLBAR, 250, -1, 0)
XX (SCROLLBAR, 250, -1, 0) \
XX (IRC_0, 16, -1, 0) \
XX (IRC_1, 124, -1, 0) \
XX (IRC_2, 34, -1, 0) \
XX (IRC_3, 142, -1, 0) \
XX (IRC_4, 19, -1, 0) \
XX (IRC_5, 127, -1, 0) \
XX (IRC_6, 37, -1, 0) \
XX (IRC_7, 252, -1, 0) \
XX (IRC_8, 245, -1, 0) \
XX (IRC_9, 196, -1, 0) \
XX (IRC_10, 46, -1, 0) \
XX (IRC_11, 226, -1, 0) \
XX (IRC_12, 21, -1, 0) \
XX (IRC_13, 201, -1, 0) \
XX (IRC_14, 51, -1, 0) \
XX (IRC_15, 231, -1, 0)
enum app_attribute
{
@@ -508,7 +567,11 @@ app_queue_local_line (enum relay_rendition rendition, const char *text)
struct timespec tp = {};
hard_assert (clock_gettime (CLOCK_REALTIME, &tp) != -1);
uint64_t when = (uint64_t) tp.tv_sec * 1000 + tp.tv_nsec / 1000000;
struct buffer_line *line = buffer_line_new_text (text, rendition, when);
struct buffer_line *line = xcalloc (1, sizeof *line);
line->rendition = rendition;
line->when = when;
buffer_line_item_add (&line->items, &line->items_tail, text, -1, -1, 0);
LIST_APPEND_WITH_TAIL (g.local_lines, g.local_lines_tail, line);
app_flush_local_lines ();
}
@@ -528,9 +591,7 @@ app_log_handler (void *user_data, const char *quote, const char *fmt,
str_append_vprintf (&message, fmt, ap);
enum relay_rendition rendition = RELAY_RENDITION_BARE;
if (!strcmp (quote, "-- "))
rendition = RELAY_RENDITION_STATUS;
else if (!strcmp (quote, "warning: ")
if (!strcmp (quote, "warning: ")
|| !strcmp (quote, "error: ")
|| !strcmp (quote, "fatal: "))
rendition = RELAY_RENDITION_ERROR;
@@ -888,26 +949,66 @@ buffer_find (const char *name)
return NULL;
}
static char *
relay_items_text (const union relay_item_data *items, uint32_t items_len)
static void
buffer_line_items_convert (const union relay_item_data *items, uint32_t len,
struct buffer_line_item **head, struct buffer_line_item **tail)
{
struct str s = str_make ();
for (uint32_t i = 0; i < items_len; i++)
if (items[i].kind == RELAY_ITEM_TEXT)
str_append_str (&s, &items[i].text.text);
return str_steal (&s);
int16_t fg = -1, bg = -1;
unsigned style = 0;
for (uint32_t i = 0; i < len; i++)
{
const union relay_item_data *item = &items[i];
switch (item->kind)
{
case RELAY_ITEM_TEXT:
buffer_line_item_add (head, tail,
item->text.text.str, fg, bg, style);
break;
case RELAY_ITEM_RESET:
fg = bg = -1;
style = 0;
break;
case RELAY_ITEM_FG_COLOR:
fg = item->fg_color.color;
break;
case RELAY_ITEM_BG_COLOR:
bg = item->bg_color.color;
break;
case RELAY_ITEM_FLIP_BOLD:
style ^= BUFFER_LINE_ITEM_BOLD;
break;
case RELAY_ITEM_FLIP_ITALIC:
style ^= BUFFER_LINE_ITEM_ITALIC;
break;
case RELAY_ITEM_FLIP_UNDERLINE:
style ^= BUFFER_LINE_ITEM_UNDERLINE;
break;
case RELAY_ITEM_FLIP_INVERSE:
style ^= BUFFER_LINE_ITEM_INVERSE;
break;
case RELAY_ITEM_FLIP_CROSSED_OUT:
style ^= BUFFER_LINE_ITEM_CROSSED_OUT;
break;
case RELAY_ITEM_FLIP_MONOSPACE:
style ^= BUFFER_LINE_ITEM_MONOSPACE;
break;
default:
break;
}
}
}
static struct buffer_line *
buffer_line_new (const struct relay_event_data_buffer_line *m)
{
char *text = relay_items_text (m->items, m->items_len);
struct buffer_line *self =
buffer_line_new_text (text, m->rendition, m->when);
free (text);
struct buffer_line *self = xcalloc (1, sizeof *self);
self->rendition = m->rendition;
self->when = m->when;
self->is_unimportant = m->is_unimportant;
self->is_highlight = m->is_highlight;
buffer_line_items_convert (m->items, m->items_len,
&self->items, &self->items_tail);
return self;
}
@@ -1004,13 +1105,15 @@ relay_process_buffer_update (struct buffer *b,
if (server_name)
b->server_name = xstrdup (server_name);
cstr_set (&b->topic, NULL);
buffer_line_items_free (b->topic);
b->topic = b->topic_tail = NULL;
cstr_set (&b->modes, NULL);
if (b->kind == RELAY_BUFFER_KIND_CHANNEL)
{
const struct relay_buffer_context_channel *channel =
&e->context.channel;
b->topic = relay_items_text (channel->topic, channel->topic_len);
buffer_line_items_convert (channel->topic, channel->topic_len,
&b->topic, &b->topic_tail);
b->modes = xstrdup (channel->modes.str);
}
}
@@ -1748,15 +1851,21 @@ app_push (struct layout *l, struct widget *w)
}
static struct widget *
app_label (enum app_attribute attribute, const char *text, bool fill)
app_label_attrs (chtype attrs, unsigned extended, const char *text, bool fill)
{
struct widget *widget =
g_xui.ui->label (g_attrs[attribute].attrs, 0, text ? text : "");
g_xui.ui->label (attrs, extended, text ? text : "");
if (fill)
widget->width = -1;
return widget;
}
static struct widget *
app_label (enum app_attribute attribute, const char *text, bool fill)
{
return app_label_attrs (g_attrs[attribute].attrs, 0, text, fill);
}
static struct widget *
app_button (enum app_attribute attribute, const char *text, enum action action)
{
@@ -1777,6 +1886,112 @@ app_padding (enum app_attribute attribute, float width)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void
app_line_item_color_rgb (int color, int *r, int *g, int *b)
{
static const uint16_t base[] =
{
0x000, 0x800, 0x080, 0x880, 0x008, 0x808, 0x088, 0xccc,
0x888, 0xf00, 0x0f0, 0xff0, 0x00f, 0xf0f, 0x0ff, 0xfff,
};
if (color < 16)
{
*r = (0xf & (base[color] >> 8)) * 0x11;
*g = (0xf & (base[color] >> 4)) * 0x11;
*b = (0xf & base[color]) * 0x11;
}
else if (color >= 232)
*r = *g = *b = 8 + (color - 232) * 10;
else
{
int i = color - 16;
*r = i / 36;
*g = i / 6 % 6;
*b = i % 6;
*r = !*r ? 0 : 55 + 40 * *r;
*g = !*g ? 0 : 55 + 40 * *g;
*b = !*b ? 0 : 55 + 40 * *b;
}
}
static int
app_line_item_color (int16_t color)
{
// These are the closest 256-colour palette matches for the base 16,
// using the same RGB estimates as xC and XUI.
if (color < 0 || color >= 256)
return -1;
int r = 0, g = 0, b = 0;
app_line_item_color_rgb (color, &r, &g, &b);
int best = 0, best_distance = INT_MAX;
for (int i = 0; i < 16; i++)
{
int rr = 0, gg = 0, bb = 0;
app_line_item_color_rgb (g_attrs[ATTRIBUTE_IRC_0 + i].fg,
&rr, &gg, &bb);
int distance =
(r - rr) * (r - rr) +
(g - gg) * (g - gg) +
(b - bb) * (b - bb);
if (distance < best_distance)
{
best = i;
best_distance = distance;
}
}
return best;
}
static chtype
app_line_item_attrs (enum app_attribute attribute, bool leaked,
const struct buffer_line_item *item)
{
chtype attrs = g_attrs[attribute].attrs;
if (item->style & BUFFER_LINE_ITEM_BOLD)
attrs |= A_BOLD;
#ifdef A_ITALIC
if (item->style & BUFFER_LINE_ITEM_ITALIC)
attrs |= A_ITALIC;
#endif // A_ITALIC
if (item->style & BUFFER_LINE_ITEM_UNDERLINE)
attrs |= A_UNDERLINE;
// Match xW: leaked lines retain font styling, but not their colours.
if (leaked)
return attrs;
int16_t fg = item->fg, bg = item->bg;
if (item->style & BUFFER_LINE_ITEM_INVERSE)
{
int16_t tmp = fg;
fg = bg;
bg = tmp;
}
// XUI attributes use a small, preallocated colour-pair table.
// Preserve its common 16-colour palette, approximating extended colours,
// and preferring the foreground when both colours are set.
fg = app_line_item_color (fg);
bg = app_line_item_color (bg);
if (fg >= 0 && bg < 0)
{
attrs &= ~A_COLOR;
attrs |= g_attrs[ATTRIBUTE_IRC_0 + fg].attrs & A_COLOR;
}
else if (bg >= 0 && fg < 0)
{
attrs &= ~A_COLOR;
attrs |= g_attrs[ATTRIBUTE_IRC_0 + bg].attrs & A_COLOR;
attrs ^= A_REVERSE;
}
else if (item->style & BUFFER_LINE_ITEM_INVERSE)
attrs ^= A_REVERSE;
return attrs;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static struct widget *
app_make_activity_indicator (void)
{
@@ -1795,8 +2010,19 @@ app_make_title (void)
{
struct layout row = {};
app_push (&row, app_padding (ATTRIBUTE_CHROME, 0.25));
app_push (&row, app_label (ATTRIBUTE_CHROME,
g.buffer_current ? g.buffer_current->topic : NULL, true));
if (g.buffer_current && g.buffer_current->topic)
LIST_FOR_EACH (struct buffer_line_item, item, g.buffer_current->topic)
{
unsigned extended = item->style & BUFFER_LINE_ITEM_MONOSPACE
? XUI_ATTR_MONOSPACE
: 0;
chtype attrs = app_line_item_attrs (ATTRIBUTE_CHROME, false, item);
app_push (&row, app_label_attrs (attrs, extended, item->text,
item == g.buffer_current->topic_tail));
}
else
app_push (&row, app_label (ATTRIBUTE_CHROME, "", true));
app_push (&row, app_padding (ATTRIBUTE_CHROME, 0.5));
app_push (&row, app_make_activity_indicator ());
app_push (&row, app_padding (ATTRIBUTE_CHROME, 0.5));
@@ -1847,29 +2073,11 @@ app_make_buffer_list (void)
return list;
}
static const char *
app_line_mark (enum relay_rendition rendition)
static enum app_attribute
app_mark_attribute (enum relay_rendition rendition)
{
switch (rendition)
{
case RELAY_RENDITION_INDENT: return " ";
case RELAY_RENDITION_STATUS: return " ";
case RELAY_RENDITION_ERROR: return "";
case RELAY_RENDITION_JOIN: return "";
case RELAY_RENDITION_PART: return "";
case RELAY_RENDITION_ACTION: return "";
default: return "";
}
}
static enum app_attribute
app_line_attribute (const struct buffer_line *line)
{
if (line->leaked)
return ATTRIBUTE_LEAKED;
switch (line->rendition)
{
case RELAY_RENDITION_ERROR: return ATTRIBUTE_ERROR;
case RELAY_RENDITION_JOIN: return ATTRIBUTE_JOIN;
case RELAY_RENDITION_PART: return ATTRIBUTE_PART;
@@ -1878,6 +2086,31 @@ app_line_attribute (const struct buffer_line *line)
}
}
static const char *
app_line_mark (enum relay_rendition rendition)
{
switch (rendition)
{
case RELAY_RENDITION_INDENT: return " ";
case RELAY_RENDITION_STATUS: return " ";
case RELAY_RENDITION_ERROR: return "";
case RELAY_RENDITION_JOIN: return "";
case RELAY_RENDITION_PART: return "";
case RELAY_RENDITION_ACTION: return "";
default: return " ";
}
}
static struct widget *
app_make_mark (const struct buffer_line *line)
{
chtype attrs = line->leaked
? ATTRIBUTE_LEAKED
: g_attrs[app_mark_attribute (line->rendition)].attrs;
return app_label_attrs (attrs, XUI_ATTR_MONOSPACE,
app_line_mark (line->rendition), false);
}
static struct widget *
app_make_line (const struct buffer_line *line)
{
@@ -1887,15 +2120,27 @@ app_make_line (const struct buffer_line *line)
if (localtime_r (&when, &tm))
strftime (timestamp, sizeof timestamp, "%H:%M:%S", &tm);
enum app_attribute attribute = app_line_attribute (line);
enum app_attribute attribute = ATTRIBUTE_NORMAL;
if (line->leaked)
attribute = ATTRIBUTE_LEAKED;
struct layout row = {};
app_push (&row, app_padding (ATTRIBUTE_TIME, 0.25));
app_push (&row, app_label (ATTRIBUTE_TIME, timestamp, false));
app_push (&row, app_padding (ATTRIBUTE_TIME, 0.25));
app_push (&row, app_padding (ATTRIBUTE_NORMAL, 0.5));
app_push (&row, app_label (attribute,
app_line_mark (line->rendition), false));
app_push (&row, app_label (attribute, line->text, true));
app_push (&row, app_make_mark (line->rendition));
LIST_FOR_EACH (struct buffer_line_item, item, line->items)
{
unsigned extended = item->style & BUFFER_LINE_ITEM_MONOSPACE
? XUI_ATTR_MONOSPACE
: 0;
chtype attrs = app_line_item_attrs (attribute, line->leaked, item);
app_push (&row, app_label_attrs (attrs, extended, item->text,
item == line->items_tail));
}
if (!line->items)
app_push (&row, app_label (attribute, "", true));
app_push (&row, app_padding (ATTRIBUTE_NORMAL, 0.25));
return xui_hbox (row.head);
}
@@ -2946,7 +3191,7 @@ app_free_context (void)
{
app_reset_relay_state ();
LIST_FOR_EACH (struct buffer_line, iter, g.local_lines)
free (iter);
buffer_line_free (iter);
g.local_lines = g.local_lines_tail = NULL;
log_view_free (&g.log);