From 919b12510b49b7b32850b1153d04bb40506bf0a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Eric=20Janouch?= Date: Wed, 21 Sep 2022 12:13:30 +0200 Subject: xC/xP: relay and render channel topics --- xC-proto | 73 ++++++++++---------- xC.c | 205 ++++++++++++++++++++++++++++++++----------------------- xP/public/xP.css | 11 ++- xP/public/xP.js | 107 ++++++++++++++++------------- 4 files changed, 223 insertions(+), 173 deletions(-) diff --git a/xC-proto b/xC-proto index ea39b86..25d48e8 100644 --- a/xC-proto +++ b/xC-proto @@ -53,12 +53,12 @@ struct EventMessage { u32 event_seq; union EventData switch (enum Event { PING, + BUFFER_LINE, BUFFER_UPDATE, BUFFER_STATS, BUFFER_RENAME, BUFFER_REMOVE, BUFFER_ACTIVATE, - BUFFER_LINE, BUFFER_CLEAR, SERVER_UPDATE, SERVER_RENAME, @@ -69,41 +69,6 @@ struct EventMessage { case PING: void; - case BUFFER_UPDATE: - string buffer_name; - bool hide_unimportant; - union BufferContext switch (enum BufferKind { - GLOBAL, - SERVER, - CHANNEL, - PRIVATE_MESSAGE, - } kind) { - case GLOBAL: - void; - case SERVER: - string server_name; - case CHANNEL: - string server_name; - case PRIVATE_MESSAGE: - string server_name; - } context; - case BUFFER_STATS: - string buffer_name; - // These are cumulative, even for lines flushed out from buffers. - // Updates to these values aren't broadcasted, thus handle: - // - BUFFER_LINE by bumping/setting them as appropriate, - // - BUFFER_ACTIVATE by clearing them for the previous buffer - // (this way, they can be used to mark unread messages). - u32 new_messages; - u32 new_unimportant_messages; - bool highlighted; - case BUFFER_RENAME: - string buffer_name; - string new; - case BUFFER_REMOVE: - string buffer_name; - case BUFFER_ACTIVATE: - string buffer_name; case BUFFER_LINE: string buffer_name; // Whether the line should also be displayed in the active buffer. @@ -150,6 +115,42 @@ struct EventMessage { case FLIP_MONOSPACE: void; } items<>; + case BUFFER_UPDATE: + string buffer_name; + bool hide_unimportant; + union BufferContext switch (enum BufferKind { + GLOBAL, + SERVER, + CHANNEL, + PRIVATE_MESSAGE, + } kind) { + case GLOBAL: + void; + case SERVER: + string server_name; + case CHANNEL: + string server_name; + ItemData topic<>; + case PRIVATE_MESSAGE: + string server_name; + } context; + case BUFFER_STATS: + string buffer_name; + // These are cumulative, even for lines flushed out from buffers. + // Updates to these values aren't broadcasted, thus handle: + // - BUFFER_LINE by bumping/setting them as appropriate, + // - BUFFER_ACTIVATE by clearing them for the previous buffer + // (this way, they can be used to mark unread messages). + u32 new_messages; + u32 new_unimportant_messages; + bool highlighted; + case BUFFER_RENAME: + string buffer_name; + string new; + case BUFFER_REMOVE: + string buffer_name; + case BUFFER_ACTIVATE: + string buffer_name; case BUFFER_CLEAR: string buffer_name; diff --git a/xC.c b/xC.c index 2027a6a..38f2c00 100644 --- a/xC.c +++ b/xC.c @@ -2883,84 +2883,9 @@ relay_prepare_ping (struct app_context *ctx) relay_prepare (ctx)->data.event = RELAY_EVENT_PING; } -static void -relay_prepare_buffer_update (struct app_context *ctx, struct buffer *buffer) -{ - struct relay_event_message *m = relay_prepare (ctx); - struct relay_event_data_buffer_update *e = &m->data.buffer_update; - e->event = RELAY_EVENT_BUFFER_UPDATE; - e->buffer_name = str_from_cstr (buffer->name); - e->hide_unimportant = buffer->hide_unimportant; - - struct str *server_name = NULL; - switch (buffer->type) - { - case BUFFER_GLOBAL: - e->context.kind = RELAY_BUFFER_KIND_GLOBAL; - break; - case BUFFER_SERVER: - e->context.kind = RELAY_BUFFER_KIND_SERVER; - server_name = &e->context.server.server_name; - break; - case BUFFER_CHANNEL: - e->context.kind = RELAY_BUFFER_KIND_CHANNEL; - server_name = &e->context.channel.server_name; - break; - case BUFFER_PM: - e->context.kind = RELAY_BUFFER_KIND_PRIVATE_MESSAGE; - server_name = &e->context.private_message.server_name; - break; - } - if (server_name) - *server_name = str_from_cstr (buffer->server->name); -} - -static void -relay_prepare_buffer_stats (struct app_context *ctx, struct buffer *buffer) -{ - struct relay_event_message *m = relay_prepare (ctx); - struct relay_event_data_buffer_stats *e = &m->data.buffer_stats; - e->event = RELAY_EVENT_BUFFER_STATS; - e->buffer_name = str_from_cstr (buffer->name); - e->new_messages = MIN (UINT32_MAX, - buffer->new_messages_count - buffer->new_unimportant_count); - e->new_unimportant_messages = MIN (UINT32_MAX, - buffer->new_unimportant_count); - e->highlighted = buffer->highlighted; -} - -static void -relay_prepare_buffer_rename (struct app_context *ctx, struct buffer *buffer, - const char *new_name) -{ - struct relay_event_message *m = relay_prepare (ctx); - struct relay_event_data_buffer_rename *e = &m->data.buffer_rename; - e->event = RELAY_EVENT_BUFFER_RENAME; - e->buffer_name = str_from_cstr (buffer->name); - e->new = str_from_cstr (new_name); -} - -static void -relay_prepare_buffer_remove (struct app_context *ctx, struct buffer *buffer) -{ - struct relay_event_message *m = relay_prepare (ctx); - struct relay_event_data_buffer_remove *e = &m->data.buffer_remove; - e->event = RELAY_EVENT_BUFFER_REMOVE; - e->buffer_name = str_from_cstr (buffer->name); -} - -static void -relay_prepare_buffer_activate (struct app_context *ctx, struct buffer *buffer) -{ - struct relay_event_message *m = relay_prepare (ctx); - struct relay_event_data_buffer_activate *e = &m->data.buffer_activate; - e->event = RELAY_EVENT_BUFFER_ACTIVATE; - e->buffer_name = str_from_cstr (buffer->name); -} - static union relay_item_data * relay_translate_formatter (struct app_context *ctx, union relay_item_data *p, - struct formatter_item *i) + const struct formatter_item *i) { // XXX: See attr_printer_decode_color(), this is a footgun. int16_t c16 = i->color; @@ -3016,6 +2941,23 @@ relay_translate_formatter (struct app_context *ctx, union relay_item_data *p, return p; } +static union relay_item_data * +relay_items (struct app_context *ctx, const struct formatter_item *items, + uint32_t *len) +{ + size_t items_len = 0; + for (size_t i = 0; items[i].type; i++) + items_len++; + + // Beware of the upper bound, currently dominated by FORMATTER_ITEM_ATTR. + union relay_item_data *a = xcalloc (items_len * 9, sizeof *a), *p = a; + for (const struct formatter_item *i = items; items_len--; i++) + p = relay_translate_formatter (ctx, p, i); + + *len = p - a; + return a; +} + static void relay_prepare_buffer_line (struct app_context *ctx, struct buffer *buffer, struct buffer_line *line, bool leak_to_active) @@ -3029,17 +2971,94 @@ relay_prepare_buffer_line (struct app_context *ctx, struct buffer *buffer, e->rendition = 1 + line->r; e->when = line->when * 1000; e->leak_to_active = leak_to_active; + e->items = relay_items (ctx, line->items, &e->items_len); +} - size_t len = 0; - for (size_t i = 0; line->items[i].type; i++) - len++; +// TODO: Consider pushing this whole block of code much further down. +static void formatter_add (struct formatter *self, const char *format, ...); - // Beware of the upper bound, currently dominated by FORMATTER_ITEM_ATTR. - union relay_item_data *p = e->items = xcalloc (len * 9, sizeof *e->items); - for (struct formatter_item *i = line->items; len--; i++) - p = relay_translate_formatter (ctx, p, i); +static void +relay_prepare_buffer_update (struct app_context *ctx, struct buffer *buffer) +{ + struct relay_event_message *m = relay_prepare (ctx); + struct relay_event_data_buffer_update *e = &m->data.buffer_update; + e->event = RELAY_EVENT_BUFFER_UPDATE; + e->buffer_name = str_from_cstr (buffer->name); + e->hide_unimportant = buffer->hide_unimportant; + + struct str *server_name = NULL; + switch (buffer->type) + { + case BUFFER_GLOBAL: + e->context.kind = RELAY_BUFFER_KIND_GLOBAL; + break; + case BUFFER_SERVER: + e->context.kind = RELAY_BUFFER_KIND_SERVER; + server_name = &e->context.server.server_name; + break; + case BUFFER_CHANNEL: + { + e->context.kind = RELAY_BUFFER_KIND_CHANNEL; + server_name = &e->context.channel.server_name; - e->items_len = p - e->items; + struct formatter f = formatter_make (ctx, buffer->server); + if (buffer->channel->topic) + formatter_add (&f, "#m", buffer->channel->topic); + e->context.channel.topic = + relay_items (ctx, f.items, &e->context.channel.topic_len); + formatter_free (&f); + break; + } + case BUFFER_PM: + e->context.kind = RELAY_BUFFER_KIND_PRIVATE_MESSAGE; + server_name = &e->context.private_message.server_name; + break; + } + if (server_name) + *server_name = str_from_cstr (buffer->server->name); +} + +static void +relay_prepare_buffer_stats (struct app_context *ctx, struct buffer *buffer) +{ + struct relay_event_message *m = relay_prepare (ctx); + struct relay_event_data_buffer_stats *e = &m->data.buffer_stats; + e->event = RELAY_EVENT_BUFFER_STATS; + e->buffer_name = str_from_cstr (buffer->name); + e->new_messages = MIN (UINT32_MAX, + buffer->new_messages_count - buffer->new_unimportant_count); + e->new_unimportant_messages = MIN (UINT32_MAX, + buffer->new_unimportant_count); + e->highlighted = buffer->highlighted; +} + +static void +relay_prepare_buffer_rename (struct app_context *ctx, struct buffer *buffer, + const char *new_name) +{ + struct relay_event_message *m = relay_prepare (ctx); + struct relay_event_data_buffer_rename *e = &m->data.buffer_rename; + e->event = RELAY_EVENT_BUFFER_RENAME; + e->buffer_name = str_from_cstr (buffer->name); + e->new = str_from_cstr (new_name); +} + +static void +relay_prepare_buffer_remove (struct app_context *ctx, struct buffer *buffer) +{ + struct relay_event_message *m = relay_prepare (ctx); + struct relay_event_data_buffer_remove *e = &m->data.buffer_remove; + e->event = RELAY_EVENT_BUFFER_REMOVE; + e->buffer_name = str_from_cstr (buffer->name); +} + +static void +relay_prepare_buffer_activate (struct app_context *ctx, struct buffer *buffer) +{ + struct relay_event_message *m = relay_prepare (ctx); + struct relay_event_data_buffer_activate *e = &m->data.buffer_activate; + e->event = RELAY_EVENT_BUFFER_ACTIVATE; + e->buffer_name = str_from_cstr (buffer->name); } static void @@ -5291,6 +5310,20 @@ irc_make_channel (struct server *s, char *name) return channel; } +static void +irc_channel_set_topic (struct channel *channel, const char *topic) +{ + cstr_set (&channel->topic, xstrdup (topic)); + + struct server *s = channel->s; + struct buffer *buffer = str_map_find (&s->irc_buffer_map, channel->name); + if (buffer) + { + relay_prepare_buffer_update (s->ctx, buffer); + relay_broadcast (s->ctx); + } +} + static struct channel_user * irc_channel_get_user (struct channel *channel, struct user *user) { @@ -8074,7 +8107,7 @@ irc_handle_topic (struct server *s, const struct irc_message *msg) // It would be weird for this to be false if (channel) - cstr_set (&channel->topic, xstrdup (topic)); + irc_channel_set_topic (channel, topic); if (buffer) { @@ -8486,7 +8519,7 @@ irc_handle_rpl_topic (struct server *s, const struct irc_message *msg) hard_assert (channel || !buffer); if (channel) - cstr_set (&channel->topic, xstrdup (topic)); + irc_channel_set_topic (channel, topic); if (buffer) log_server_status (s, buffer, "The topic is: #m", topic); diff --git a/xP/public/xP.css b/xP/public/xP.css index 5c02d7e..87cfec2 100644 --- a/xP/public/xP.css +++ b/xP/public/xP.css @@ -30,11 +30,16 @@ body { display: flex; justify-content: space-between; align-items: baseline; + column-gap: .3em; position: relative; border-top: 3px solid #ccc; border-bottom: 2px solid #888; } +.title { + /* To approximate right-aligned space-between. */ + flex-direction: row-reverse; +} .title:before, .status:before { content: " "; position: absolute; @@ -57,7 +62,7 @@ body { .toolbar { display: flex; align-items: baseline; - gap: .3em; + column-gap: .3em; } button { font: inherit; @@ -129,11 +134,13 @@ button:hover:active { overflow-y: auto; } .log, .content { - padding: .1em .3em; /* Note: https://bugs.chromium.org/p/chromium/issues/detail?id=1261435 */ white-space: break-spaces; overflow-wrap: break-word; } +.log, .buffer .content { + padding: .1em .3em; +} .leaked { opacity: 50%; diff --git a/xP/public/xP.js b/xP/public/xP.js index 8820251..c8ceed0 100644 --- a/xP/public/xP.js +++ b/xP/public/xP.js @@ -254,12 +254,58 @@ rpc.addEventListener('event', event => { } }) -rpcEventHandlers['Ping'] = e => { +rpcEventHandlers.set(Relay.Event.Ping, e => { rpc.send({command: 'PingResponse', eventSeq: e.eventSeq}) -} +}) // ~~~ Buffer events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +rpcEventHandlers.set(Relay.Event.BufferLine, e => { + let b = buffers.get(e.bufferName), line = {...e} + delete line.event + delete line.eventSeq + delete line.leakToActive + if (b === undefined) + return + + // Initial sync: skip all other processing, let highlights be. + if (bufferCurrent === undefined) { + b.lines.push(line) + return + } + + let visible = document.visibilityState !== 'hidden' && + bufferLog === undefined && + bufferAutoscroll && + (e.bufferName == bufferCurrent || e.leakToActive) + b.lines.push({...line}) + if (!(visible || e.leakToActive) || + b.newMessages || b.newUnimportantMessages) { + if (line.isUnimportant) + b.newUnimportantMessages++ + else + b.newMessages++ + } + + if (e.leakToActive) { + let bc = buffers.get(bufferCurrent) + bc.lines.push({...line, leaked: true}) + if (!visible || bc.newMessages || bc.newUnimportantMessages) { + if (line.isUnimportant) + bc.newUnimportantMessages++ + else + bc.newMessages++ + } + } + + if (line.isHighlight || (!visible && !line.isUnimportant && + b.kind === Relay.BufferKind.PrivateMessage)) { + beep() + if (!visible) + b.highlighted = true + } +}) + rpcEventHandlers.set(Relay.Event.BufferUpdate, e => { let b = buffers.get(e.bufferName) if (b === undefined) { @@ -274,6 +320,7 @@ rpcEventHandlers.set(Relay.Event.BufferUpdate, e => { b.hideUnimportant = e.hideUnimportant b.kind = e.context.kind b.server = servers.get(e.context.serverName) + b.topic = e.context.topic }) rpcEventHandlers.set(Relay.Event.BufferStats, e => { @@ -332,52 +379,6 @@ rpcEventHandlers.set(Relay.Event.BufferActivate, e => { } }) -rpcEventHandlers.set(Relay.Event.BufferLine, e => { - let b = buffers.get(e.bufferName), line = {...e} - delete line.event - delete line.eventSeq - delete line.leakToActive - if (b === undefined) - return - - // Initial sync: skip all other processing, let highlights be. - if (bufferCurrent === undefined) { - b.lines.push(line) - return - } - - let visible = document.visibilityState !== 'hidden' && - bufferLog === undefined && - bufferAutoscroll && - (e.bufferName == bufferCurrent || e.leakToActive) - b.lines.push({...line}) - if (!(visible || e.leakToActive) || - b.newMessages || b.newUnimportantMessages) { - if (line.isUnimportant) - b.newUnimportantMessages++ - else - b.newMessages++ - } - - if (e.leakToActive) { - let bc = buffers.get(bufferCurrent) - bc.lines.push({...line, leaked: true}) - if (!visible || bc.newMessages || bc.newUnimportantMessages) { - if (line.isUnimportant) - bc.newUnimportantMessages++ - else - bc.newMessages++ - } - } - - if (line.isHighlight || (!visible && !line.isUnimportant && - b.kind === Relay.BufferKind.PrivateMessage)) { - beep() - if (!visible) - b.highlighted = true - } -}) - rpcEventHandlers.set(Relay.Event.BufferClear, e => { let b = buffers.get(e.bufferName) if (b !== undefined) @@ -548,6 +549,14 @@ let Content = { }, } +let Topic = { + view: vnode => { + let b = buffers.get(bufferCurrent) + if (b !== undefined && b.topic !== undefined) + return m(Content, {}, {items: b.topic}) + }, +} + let Buffer = { controller: new AbortController(), @@ -945,7 +954,7 @@ let Main = { return m('.xP', {}, [ overlay, - m('.title', {}, `xP`), + m('.title', {}, [m('b', {}, `xP`), m(Topic)]), m('.middle', {}, [m(BufferList), m(BufferContainer)]), m(Status), m('.input', {}, [m(Prompt), m(Input)]), -- cgit v1.2.3