aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--xC-proto73
-rw-r--r--xC.c205
-rw-r--r--xP/public/xP.css11
-rw-r--r--xP/public/xP.js107
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)]),