xC/xP: relay and render channel topics

This commit is contained in:
Přemysl Eric Janouch 2022-09-21 12:13:30 +02:00
parent 414859d309
commit 919b12510b
Signed by: p
GPG Key ID: A0420B94F92B9493
4 changed files with 223 additions and 173 deletions

View File

@ -53,12 +53,12 @@ struct EventMessage {
u32 event_seq; u32 event_seq;
union EventData switch (enum Event { union EventData switch (enum Event {
PING, PING,
BUFFER_LINE,
BUFFER_UPDATE, BUFFER_UPDATE,
BUFFER_STATS, BUFFER_STATS,
BUFFER_RENAME, BUFFER_RENAME,
BUFFER_REMOVE, BUFFER_REMOVE,
BUFFER_ACTIVATE, BUFFER_ACTIVATE,
BUFFER_LINE,
BUFFER_CLEAR, BUFFER_CLEAR,
SERVER_UPDATE, SERVER_UPDATE,
SERVER_RENAME, SERVER_RENAME,
@ -69,41 +69,6 @@ struct EventMessage {
case PING: case PING:
void; 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: case BUFFER_LINE:
string buffer_name; string buffer_name;
// Whether the line should also be displayed in the active buffer. // Whether the line should also be displayed in the active buffer.
@ -150,6 +115,42 @@ struct EventMessage {
case FLIP_MONOSPACE: case FLIP_MONOSPACE:
void; void;
} items<>; } 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: case BUFFER_CLEAR:
string buffer_name; string buffer_name;

205
xC.c
View File

@ -2883,84 +2883,9 @@ relay_prepare_ping (struct app_context *ctx)
relay_prepare (ctx)->data.event = RELAY_EVENT_PING; 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 * static union relay_item_data *
relay_translate_formatter (struct app_context *ctx, union relay_item_data *p, 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. // XXX: See attr_printer_decode_color(), this is a footgun.
int16_t c16 = i->color; int16_t c16 = i->color;
@ -3016,6 +2941,23 @@ relay_translate_formatter (struct app_context *ctx, union relay_item_data *p,
return 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 static void
relay_prepare_buffer_line (struct app_context *ctx, struct buffer *buffer, relay_prepare_buffer_line (struct app_context *ctx, struct buffer *buffer,
struct buffer_line *line, bool leak_to_active) 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->rendition = 1 + line->r;
e->when = line->when * 1000; e->when = line->when * 1000;
e->leak_to_active = leak_to_active; e->leak_to_active = leak_to_active;
e->items = relay_items (ctx, line->items, &e->items_len);
}
size_t len = 0; // TODO: Consider pushing this whole block of code much further down.
for (size_t i = 0; line->items[i].type; i++) static void formatter_add (struct formatter *self, const char *format, ...);
len++;
// Beware of the upper bound, currently dominated by FORMATTER_ITEM_ATTR. static void
union relay_item_data *p = e->items = xcalloc (len * 9, sizeof *e->items); relay_prepare_buffer_update (struct app_context *ctx, struct buffer *buffer)
for (struct formatter_item *i = line->items; len--; i++) {
p = relay_translate_formatter (ctx, p, i); 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;
e->items_len = p - e->items; 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;
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 static void
@ -5291,6 +5310,20 @@ irc_make_channel (struct server *s, char *name)
return channel; 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 * static struct channel_user *
irc_channel_get_user (struct channel *channel, struct user *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 // It would be weird for this to be false
if (channel) if (channel)
cstr_set (&channel->topic, xstrdup (topic)); irc_channel_set_topic (channel, topic);
if (buffer) if (buffer)
{ {
@ -8486,7 +8519,7 @@ irc_handle_rpl_topic (struct server *s, const struct irc_message *msg)
hard_assert (channel || !buffer); hard_assert (channel || !buffer);
if (channel) if (channel)
cstr_set (&channel->topic, xstrdup (topic)); irc_channel_set_topic (channel, topic);
if (buffer) if (buffer)
log_server_status (s, buffer, "The topic is: #m", topic); log_server_status (s, buffer, "The topic is: #m", topic);

View File

@ -30,11 +30,16 @@ body {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: baseline; align-items: baseline;
column-gap: .3em;
position: relative; position: relative;
border-top: 3px solid #ccc; border-top: 3px solid #ccc;
border-bottom: 2px solid #888; border-bottom: 2px solid #888;
} }
.title {
/* To approximate right-aligned space-between. */
flex-direction: row-reverse;
}
.title:before, .status:before { .title:before, .status:before {
content: " "; content: " ";
position: absolute; position: absolute;
@ -57,7 +62,7 @@ body {
.toolbar { .toolbar {
display: flex; display: flex;
align-items: baseline; align-items: baseline;
gap: .3em; column-gap: .3em;
} }
button { button {
font: inherit; font: inherit;
@ -129,11 +134,13 @@ button:hover:active {
overflow-y: auto; overflow-y: auto;
} }
.log, .content { .log, .content {
padding: .1em .3em;
/* Note: https://bugs.chromium.org/p/chromium/issues/detail?id=1261435 */ /* Note: https://bugs.chromium.org/p/chromium/issues/detail?id=1261435 */
white-space: break-spaces; white-space: break-spaces;
overflow-wrap: break-word; overflow-wrap: break-word;
} }
.log, .buffer .content {
padding: .1em .3em;
}
.leaked { .leaked {
opacity: 50%; opacity: 50%;

View File

@ -254,12 +254,58 @@ rpc.addEventListener('event', event => {
} }
}) })
rpcEventHandlers['Ping'] = e => { rpcEventHandlers.set(Relay.Event.Ping, e => {
rpc.send({command: 'PingResponse', eventSeq: e.eventSeq}) rpc.send({command: 'PingResponse', eventSeq: e.eventSeq})
} })
// ~~~ Buffer events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~ 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 => { rpcEventHandlers.set(Relay.Event.BufferUpdate, e => {
let b = buffers.get(e.bufferName) let b = buffers.get(e.bufferName)
if (b === undefined) { if (b === undefined) {
@ -274,6 +320,7 @@ rpcEventHandlers.set(Relay.Event.BufferUpdate, e => {
b.hideUnimportant = e.hideUnimportant b.hideUnimportant = e.hideUnimportant
b.kind = e.context.kind b.kind = e.context.kind
b.server = servers.get(e.context.serverName) b.server = servers.get(e.context.serverName)
b.topic = e.context.topic
}) })
rpcEventHandlers.set(Relay.Event.BufferStats, e => { 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 => { rpcEventHandlers.set(Relay.Event.BufferClear, e => {
let b = buffers.get(e.bufferName) let b = buffers.get(e.bufferName)
if (b !== undefined) 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 = { let Buffer = {
controller: new AbortController(), controller: new AbortController(),
@ -945,7 +954,7 @@ let Main = {
return m('.xP', {}, [ return m('.xP', {}, [
overlay, overlay,
m('.title', {}, `xP`), m('.title', {}, [m('b', {}, `xP`), m(Topic)]),
m('.middle', {}, [m(BufferList), m(BufferContainer)]), m('.middle', {}, [m(BufferList), m(BufferContainer)]),
m(Status), m(Status),
m('.input', {}, [m(Prompt), m(Input)]), m('.input', {}, [m(Prompt), m(Input)]),