Compare commits
No commits in common. "0bc2c12eecfb5b035c498272556f8fc6a39059a9" and "8c8e06b0157c97ee1771f848ed363c6a0ed398be" have entirely different histories.
0bc2c12eec
...
8c8e06b015
@ -110,11 +110,11 @@ function codegen_enum(name, cg, ctype) {
|
|||||||
|
|
||||||
# XXX: This should also check if it isn't out-of-range for any reason,
|
# XXX: This should also check if it isn't out-of-range for any reason,
|
||||||
# but our usage of sprintf() stands in the way a bit.
|
# but our usage of sprintf() stands in the way a bit.
|
||||||
CodegenSerialize[name] = "\tstr_pack_i8(w, %s);\n"
|
CodegenSerialize[name] = "\tstr_pack_i32(w, %s);\n"
|
||||||
CodegenDeserialize[name] = \
|
CodegenDeserialize[name] = \
|
||||||
"\t{\n" \
|
"\t{\n" \
|
||||||
"\t\tint8_t v = 0;\n" \
|
"\t\tint32_t v = 0;\n" \
|
||||||
"\t\tif (!msg_unpacker_i8(r, &v) || !v)\n" \
|
"\t\tif (!msg_unpacker_i32(r, &v) || !v)\n" \
|
||||||
"\t\t\treturn false;\n" \
|
"\t\t\treturn false;\n" \
|
||||||
"\t\t%s = v;\n" \
|
"\t\t%s = v;\n" \
|
||||||
"\t}\n"
|
"\t}\n"
|
||||||
|
@ -161,7 +161,7 @@ function codegen_begin() {
|
|||||||
print "\tvar n int64"
|
print "\tvar n int64"
|
||||||
print "\tif err := json.Unmarshal(data, &n); err != nil {"
|
print "\tif err := json.Unmarshal(data, &n); err != nil {"
|
||||||
print "\t\treturn 0, err"
|
print "\t\treturn 0, err"
|
||||||
print "\t} else if n > math.MaxInt8 || n < math.MinInt8 {"
|
print "\t} else if n > math.MaxInt32 || n < math.MinInt32 {"
|
||||||
print "\t\treturn 0, errors.New(`integer out of range`)"
|
print "\t\treturn 0, errors.New(`integer out of range`)"
|
||||||
print "\t} else {"
|
print "\t} else {"
|
||||||
print "\t\treturn n, nil"
|
print "\t\treturn n, nil"
|
||||||
@ -191,7 +191,7 @@ function codegen_enum_value(name, subname, value, cg, goname) {
|
|||||||
|
|
||||||
function codegen_enum(name, cg, gotype, fields) {
|
function codegen_enum(name, cg, gotype, fields) {
|
||||||
gotype = PrefixCamel name
|
gotype = PrefixCamel name
|
||||||
print "type " gotype " int8"
|
print "type " gotype " int"
|
||||||
print ""
|
print ""
|
||||||
|
|
||||||
print "const ("
|
print "const ("
|
||||||
@ -239,10 +239,12 @@ function codegen_enum(name, cg, gotype, fields) {
|
|||||||
|
|
||||||
# XXX: This should also check if it isn't out-of-range for any reason,
|
# XXX: This should also check if it isn't out-of-range for any reason,
|
||||||
# but our usage of sprintf() stands in the way a bit.
|
# but our usage of sprintf() stands in the way a bit.
|
||||||
CodegenSerialize[name] = "\tdata = append(data, uint8(%s))\n"
|
CodegenSerialize[name] = \
|
||||||
|
"\tdata = binary.BigEndian.AppendUint32(data, uint32(%s))\n"
|
||||||
CodegenDeserialize[name] = \
|
CodegenDeserialize[name] = \
|
||||||
"\tif len(data) >= 1 {\n" \
|
"\tif len(data) >= 4 {\n" \
|
||||||
"\t\t%s, data = " gotype "(data[0]), data[1:]\n" \
|
"\t\t%s = " gotype "(int32(binary.BigEndian.Uint32(data)))\n" \
|
||||||
|
"\t\tdata = data[4:]\n" \
|
||||||
"\t} else {\n" \
|
"\t} else {\n" \
|
||||||
"\t\treturn nil, false\n" \
|
"\t\treturn nil, false\n" \
|
||||||
"\t}\n"
|
"\t}\n"
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
# Booleans are one byte each.
|
# Booleans are one byte each.
|
||||||
# Strings must be valid UTF-8, use u8<> to lift that restriction.
|
# Strings must be valid UTF-8, use u8<> to lift that restriction.
|
||||||
# String and array lengths are encoded as u32.
|
# String and array lengths are encoded as u32.
|
||||||
# Enumeration values automatically start at 1, and are encoded as i8.
|
# Enumeration values automatically start at 1, and are encoded as i32.
|
||||||
# Any struct or union field may be a variable-length array.
|
# Any struct or union field may be a variable-length array.
|
||||||
#
|
#
|
||||||
# Message framing is done externally, but also happens to prefix u32 lengths.
|
# Message framing is done externally, but also happens to prefix u32 lengths.
|
||||||
@ -189,8 +189,6 @@ function defenum( name, ident, value, cg) {
|
|||||||
value = readnumber()
|
value = readnumber()
|
||||||
if (!value)
|
if (!value)
|
||||||
fatal("enumeration values cannot be zero")
|
fatal("enumeration values cannot be zero")
|
||||||
if (value < -128 || value > 127)
|
|
||||||
fatal("enumeration value out of range")
|
|
||||||
expect(accept(","))
|
expect(accept(","))
|
||||||
append(EnumValues, name, SUBSEP ident)
|
append(EnumValues, name, SUBSEP ident)
|
||||||
if (EnumValues[name, ident]++)
|
if (EnumValues[name, ident]++)
|
||||||
|
7
xC-proto
7
xC-proto
@ -19,7 +19,7 @@ struct CommandMessage {
|
|||||||
case HELLO:
|
case HELLO:
|
||||||
u32 version;
|
u32 version;
|
||||||
// If the version check succeeds, the client will receive
|
// If the version check succeeds, the client will receive
|
||||||
// an initial stream of BUFFER_UPDATE, BUFFER_STATS, BUFFER_LINE,
|
// an initial stream of BUFFER_UPDATE, BUFFER_LINE,
|
||||||
// and finally a BUFFER_ACTIVATE message.
|
// and finally a BUFFER_ACTIVATE message.
|
||||||
case ACTIVE:
|
case ACTIVE:
|
||||||
void;
|
void;
|
||||||
@ -50,7 +50,6 @@ struct EventMessage {
|
|||||||
union EventData switch (enum Event {
|
union EventData switch (enum Event {
|
||||||
PING,
|
PING,
|
||||||
BUFFER_UPDATE,
|
BUFFER_UPDATE,
|
||||||
BUFFER_STATS,
|
|
||||||
BUFFER_RENAME,
|
BUFFER_RENAME,
|
||||||
BUFFER_REMOVE,
|
BUFFER_REMOVE,
|
||||||
BUFFER_ACTIVATE,
|
BUFFER_ACTIVATE,
|
||||||
@ -62,15 +61,13 @@ struct EventMessage {
|
|||||||
case PING:
|
case PING:
|
||||||
void;
|
void;
|
||||||
case BUFFER_UPDATE:
|
case BUFFER_UPDATE:
|
||||||
string buffer_name;
|
|
||||||
bool hide_unimportant;
|
|
||||||
case BUFFER_STATS:
|
|
||||||
string buffer_name;
|
string buffer_name;
|
||||||
// These are cumulative, even for lines flushed out from buffers.
|
// These are cumulative, even for lines flushed out from buffers.
|
||||||
// Updates to these values aren't broadcasted, thus handle:
|
// Updates to these values aren't broadcasted, thus handle:
|
||||||
// - BUFFER_LINE by bumping/setting them as appropriate,
|
// - BUFFER_LINE by bumping/setting them as appropriate,
|
||||||
// - BUFFER_ACTIVATE by clearing them for the previous buffer
|
// - BUFFER_ACTIVATE by clearing them for the previous buffer
|
||||||
// (this way, they can be used to mark unread messages).
|
// (this way, they can be used to mark unread messages).
|
||||||
|
// Any updates received after the initial sync should be ignored.
|
||||||
u32 new_messages;
|
u32 new_messages;
|
||||||
u32 new_unimportant_messages;
|
u32 new_unimportant_messages;
|
||||||
bool highlighted;
|
bool highlighted;
|
||||||
|
83
xC.c
83
xC.c
@ -3068,16 +3068,6 @@ relay_prepare_buffer_update (struct app_context *ctx, struct buffer *buffer)
|
|||||||
struct relay_event_data_buffer_update *e = &m->data.buffer_update;
|
struct relay_event_data_buffer_update *e = &m->data.buffer_update;
|
||||||
e->event = RELAY_EVENT_BUFFER_UPDATE;
|
e->event = RELAY_EVENT_BUFFER_UPDATE;
|
||||||
e->buffer_name = str_from_cstr (buffer->name);
|
e->buffer_name = str_from_cstr (buffer->name);
|
||||||
e->hide_unimportant = buffer->hide_unimportant;
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
e->new_messages = MIN (UINT32_MAX,
|
||||||
buffer->new_messages_count - buffer->new_unimportant_count);
|
buffer->new_messages_count - buffer->new_unimportant_count);
|
||||||
e->new_unimportant_messages = MIN (UINT32_MAX,
|
e->new_unimportant_messages = MIN (UINT32_MAX,
|
||||||
@ -8181,7 +8171,7 @@ irc_try_parse_welcome_for_userhost (struct server *s, const char *m)
|
|||||||
strv_free (&v);
|
strv_free (&v);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool process_input_line
|
static bool process_input_utf8
|
||||||
(struct app_context *, struct buffer *, const char *, int);
|
(struct app_context *, struct buffer *, const char *, int);
|
||||||
static void on_autoaway_timer (struct app_context *ctx);
|
static void on_autoaway_timer (struct app_context *ctx);
|
||||||
|
|
||||||
@ -8210,7 +8200,7 @@ irc_on_registered (struct server *s, const char *nickname)
|
|||||||
if (command)
|
if (command)
|
||||||
{
|
{
|
||||||
log_server_debug (s, "Executing \"#s\"", command);
|
log_server_debug (s, "Executing \"#s\"", command);
|
||||||
(void) process_input_line (s->ctx, s->buffer, command, 0);
|
(void) process_input_utf8 (s->ctx, s->buffer, command, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t command_delay = get_config_integer (s->config, "command_delay");
|
int64_t command_delay = get_config_integer (s->config, "command_delay");
|
||||||
@ -9076,21 +9066,12 @@ irc_autosplit_message (struct server *s, const char *message,
|
|||||||
- 1 - (int) strlen (s->irc_user_host)
|
- 1 - (int) strlen (s->irc_user_host)
|
||||||
- 1 - fixed_part;
|
- 1 - fixed_part;
|
||||||
|
|
||||||
// Multiline messages can be triggered through hooks and plugins.
|
// However we don't always have the full info for message splitting
|
||||||
struct strv lines = strv_make ();
|
|
||||||
cstr_split (message, "\r\n", false, &lines);
|
|
||||||
bool success = true;
|
|
||||||
for (size_t i = 0; i < lines.len; i++)
|
|
||||||
{
|
|
||||||
// We don't always have the full info for message splitting.
|
|
||||||
if (!space_in_one_message)
|
if (!space_in_one_message)
|
||||||
strv_append (output, lines.vector[i]);
|
strv_append (output, message);
|
||||||
else if (!(success =
|
else if (!wrap_message (message, space_in_one_message, output, e))
|
||||||
wrap_message (lines.vector[i], space_in_one_message, output, e)))
|
return false;
|
||||||
break;
|
return true;
|
||||||
}
|
|
||||||
strv_free (&lines);
|
|
||||||
return success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -9099,11 +9080,12 @@ send_autosplit_message (struct server *s,
|
|||||||
const char *prefix, const char *suffix)
|
const char *prefix, const char *suffix)
|
||||||
{
|
{
|
||||||
struct buffer *buffer = str_map_find (&s->irc_buffer_map, target);
|
struct buffer *buffer = str_map_find (&s->irc_buffer_map, target);
|
||||||
|
|
||||||
// "COMMAND target * :prefix*suffix"
|
|
||||||
int fixed_part = strlen (command) + 1 + strlen (target) + 1 + 1
|
int fixed_part = strlen (command) + 1 + strlen (target) + 1 + 1
|
||||||
+ strlen (prefix) + strlen (suffix);
|
+ strlen (prefix) + strlen (suffix);
|
||||||
|
|
||||||
|
// We might also want to preserve attributes across splits but
|
||||||
|
// that would make this code a lot more complicated
|
||||||
|
|
||||||
struct strv lines = strv_make ();
|
struct strv lines = strv_make ();
|
||||||
struct error *e = NULL;
|
struct error *e = NULL;
|
||||||
if (!irc_autosplit_message (s, message, fixed_part, &lines, &e))
|
if (!irc_autosplit_message (s, message, fixed_part, &lines, &e))
|
||||||
@ -9849,7 +9831,7 @@ lua_buffer_execute (lua_State *L)
|
|||||||
struct lua_weak *wrapper = lua_weak_deref (L, &lua_buffer_info);
|
struct lua_weak *wrapper = lua_weak_deref (L, &lua_buffer_info);
|
||||||
struct buffer *buffer = wrapper->object;
|
struct buffer *buffer = wrapper->object;
|
||||||
const char *line = lua_plugin_check_utf8 (L, 2);
|
const char *line = lua_plugin_check_utf8 (L, 2);
|
||||||
(void) process_input_line (wrapper->plugin->ctx, buffer, line, 0);
|
(void) process_input_utf8 (wrapper->plugin->ctx, buffer, line, 0);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -13279,13 +13261,13 @@ process_alias (struct app_context *ctx, struct buffer *buffer,
|
|||||||
log_global_debug (ctx, "Alias expanded to: ###d: \"#s\"",
|
log_global_debug (ctx, "Alias expanded to: ###d: \"#s\"",
|
||||||
(int) i, commands->vector[i]);
|
(int) i, commands->vector[i]);
|
||||||
for (size_t i = 0; i < commands->len; i++)
|
for (size_t i = 0; i < commands->len; i++)
|
||||||
if (!process_input_line (ctx, buffer, commands->vector[i], ++level))
|
if (!process_input_utf8 (ctx, buffer, commands->vector[i], ++level))
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
process_input_line_posthook (struct app_context *ctx, struct buffer *buffer,
|
process_input_utf8_posthook (struct app_context *ctx, struct buffer *buffer,
|
||||||
char *input, int alias_level)
|
char *input, int alias_level)
|
||||||
{
|
{
|
||||||
if (*input != '/' || *++input == '/')
|
if (*input != '/' || *++input == '/')
|
||||||
@ -13334,27 +13316,35 @@ process_input_hooks (struct app_context *ctx, struct buffer *buffer,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
process_input_line (struct app_context *ctx, struct buffer *buffer,
|
process_input_utf8 (struct app_context *ctx, struct buffer *buffer,
|
||||||
const char *input, int alias_level)
|
const char *input, int alias_level)
|
||||||
{
|
{
|
||||||
// Note that this also gets called on expanded aliases,
|
// Note that this also gets called on expanded aliases,
|
||||||
// which might or might not be desirable (we can forward "alias_level")
|
// which might or might not be desirable (we can forward "alias_level")
|
||||||
char *processed = process_input_hooks (ctx, buffer, xstrdup (input));
|
char *processed = process_input_hooks (ctx, buffer, xstrdup (input));
|
||||||
bool result = !processed
|
bool result = !processed
|
||||||
|| process_input_line_posthook (ctx, buffer, processed, alias_level);
|
|| process_input_utf8_posthook (ctx, buffer, processed, alias_level);
|
||||||
free (processed);
|
free (processed);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
process_input (struct app_context *ctx, struct buffer *buffer,
|
process_input (struct app_context *ctx, char *user_input)
|
||||||
const char *input)
|
|
||||||
{
|
{
|
||||||
|
char *input;
|
||||||
|
if (!(input = iconv_xstrdup (ctx->term_to_utf8, user_input, -1, NULL)))
|
||||||
|
print_error ("character conversion failed for: %s", "user input");
|
||||||
|
else
|
||||||
|
{
|
||||||
struct strv lines = strv_make ();
|
struct strv lines = strv_make ();
|
||||||
cstr_split (input, "\r\n", false, &lines);
|
cstr_split (input, "\r\n", false, &lines);
|
||||||
for (size_t i = 0; i < lines.len; i++)
|
for (size_t i = 0; i < lines.len; i++)
|
||||||
(void) process_input_line (ctx, buffer, lines.vector[i], 0);
|
(void) process_input_utf8 (ctx,
|
||||||
|
ctx->current_buffer, lines.vector[i], 0);
|
||||||
|
|
||||||
strv_free (&lines);
|
strv_free (&lines);
|
||||||
|
}
|
||||||
|
free (input);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Word completion ---------------------------------------------------------
|
// --- Word completion ---------------------------------------------------------
|
||||||
@ -14203,10 +14193,6 @@ on_toggle_unimportant (int count, int key, void *user_data)
|
|||||||
(void) key;
|
(void) key;
|
||||||
struct app_context *ctx = user_data;
|
struct app_context *ctx = user_data;
|
||||||
ctx->current_buffer->hide_unimportant ^= true;
|
ctx->current_buffer->hide_unimportant ^= true;
|
||||||
|
|
||||||
relay_prepare_buffer_update (ctx, ctx->current_buffer);
|
|
||||||
relay_broadcast (ctx);
|
|
||||||
|
|
||||||
buffer_print_backlog (ctx, ctx->current_buffer);
|
buffer_print_backlog (ctx, ctx->current_buffer);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -14594,7 +14580,7 @@ on_editline_return (EditLine *editline, int key)
|
|||||||
}
|
}
|
||||||
free (line);
|
free (line);
|
||||||
|
|
||||||
// on_pending_input() expects a multibyte string
|
// process_input() expects a multibyte string
|
||||||
const LineInfo *info_mb = el_line (editline);
|
const LineInfo *info_mb = el_line (editline);
|
||||||
strv_append_owned (&ctx->pending_input,
|
strv_append_owned (&ctx->pending_input,
|
||||||
xstrndup (info_mb->buffer, info_mb->lastchar - info_mb->buffer));
|
xstrndup (info_mb->buffer, info_mb->lastchar - info_mb->buffer));
|
||||||
@ -15184,15 +15170,7 @@ on_pending_input (struct app_context *ctx)
|
|||||||
{
|
{
|
||||||
poller_idle_reset (&ctx->input_event);
|
poller_idle_reset (&ctx->input_event);
|
||||||
for (size_t i = 0; i < ctx->pending_input.len; i++)
|
for (size_t i = 0; i < ctx->pending_input.len; i++)
|
||||||
{
|
process_input (ctx, ctx->pending_input.vector[i]);
|
||||||
char *input = iconv_xstrdup
|
|
||||||
(ctx->term_to_utf8, ctx->pending_input.vector[i], -1, NULL);
|
|
||||||
if (input)
|
|
||||||
process_input (ctx, ctx->current_buffer, input);
|
|
||||||
else
|
|
||||||
print_error ("character conversion failed for: %s", "user input");
|
|
||||||
free (input);
|
|
||||||
}
|
|
||||||
strv_reset (&ctx->pending_input);
|
strv_reset (&ctx->pending_input);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -15245,8 +15223,6 @@ client_resync (struct client *c)
|
|||||||
{
|
{
|
||||||
relay_prepare_buffer_update (c->ctx, buffer);
|
relay_prepare_buffer_update (c->ctx, buffer);
|
||||||
relay_send (c);
|
relay_send (c);
|
||||||
relay_prepare_buffer_stats (c->ctx, buffer);
|
|
||||||
relay_send (c);
|
|
||||||
|
|
||||||
LIST_FOR_EACH (struct buffer_line, line, buffer->lines)
|
LIST_FOR_EACH (struct buffer_line, line, buffer->lines)
|
||||||
{
|
{
|
||||||
@ -15396,7 +15372,8 @@ client_process_message (struct client *c,
|
|||||||
&m->data.buffer_complete);
|
&m->data.buffer_complete);
|
||||||
break;
|
break;
|
||||||
case RELAY_COMMAND_BUFFER_INPUT:
|
case RELAY_COMMAND_BUFFER_INPUT:
|
||||||
process_input (c->ctx, buffer, m->data.buffer_input.text.str);
|
(void) process_input_utf8 (c->ctx,
|
||||||
|
buffer, m->data.buffer_input.text.str, 0);
|
||||||
break;
|
break;
|
||||||
case RELAY_COMMAND_BUFFER_ACTIVATE:
|
case RELAY_COMMAND_BUFFER_ACTIVATE:
|
||||||
buffer_activate (c->ctx, buffer);
|
buffer_activate (c->ctx, buffer);
|
||||||
|
@ -89,18 +89,14 @@ body {
|
|||||||
}
|
}
|
||||||
.buffer {
|
.buffer {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: max-content minmax(0, 1fr);
|
grid-template-columns: max-content auto;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
.log {
|
.log {
|
||||||
font-family: monospace;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
.log, .content {
|
|
||||||
padding: .1em .3em;
|
padding: .1em .3em;
|
||||||
/* Note: https://bugs.chromium.org/p/chromium/issues/detail?id=1261435 */
|
font-family: monospace;
|
||||||
white-space: break-spaces;
|
white-space: pre-wrap;
|
||||||
overflow-wrap: break-word;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaked {
|
.leaked {
|
||||||
@ -140,6 +136,10 @@ body {
|
|||||||
.mark.action {
|
.mark.action {
|
||||||
color: darkred;
|
color: darkred;
|
||||||
}
|
}
|
||||||
|
.content {
|
||||||
|
padding: .1em .3em;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
.content .b {
|
.content .b {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
@ -162,7 +162,6 @@ textarea {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
border: 2px inset #eee;
|
border: 2px inset #eee;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
resize: vertical;
|
|
||||||
}
|
}
|
||||||
textarea:focus {
|
textarea:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
149
xP/public/xP.js
149
xP/public/xP.js
@ -110,9 +110,8 @@ class RelayRpc extends EventTarget {
|
|||||||
if (typeof params !== 'object')
|
if (typeof params !== 'object')
|
||||||
throw "Method parameters must be an object"
|
throw "Method parameters must be an object"
|
||||||
|
|
||||||
// Left shifts in Javascript convert to a 32-bit signed number.
|
|
||||||
let seq = ++this.commandSeq
|
let seq = ++this.commandSeq
|
||||||
if ((seq << 0) != seq)
|
if (seq >= 1 << 32)
|
||||||
seq = this.commandSeq = 0
|
seq = this.commandSeq = 0
|
||||||
|
|
||||||
this.ws.send(JSON.stringify({commandSeq: seq, data: params}))
|
this.ws.send(JSON.stringify({commandSeq: seq, data: params}))
|
||||||
@ -132,25 +131,13 @@ class RelayRpc extends EventTarget {
|
|||||||
let rpc = new RelayRpc(proxy)
|
let rpc = new RelayRpc(proxy)
|
||||||
|
|
||||||
let buffers = new Map()
|
let buffers = new Map()
|
||||||
let bufferLast = undefined
|
|
||||||
let bufferCurrent = undefined
|
let bufferCurrent = undefined
|
||||||
let bufferLog = undefined
|
let bufferLog = undefined
|
||||||
let bufferAutoscroll = true
|
let bufferAutoscroll = true
|
||||||
|
|
||||||
function resetBufferStats(b) {
|
|
||||||
b.newMessages = 0
|
|
||||||
b.newUnimportantMessages = 0
|
|
||||||
b.highlighted = false
|
|
||||||
}
|
|
||||||
|
|
||||||
function bufferActivate(name) {
|
|
||||||
rpc.send({command: 'BufferActivate', bufferName: name})
|
|
||||||
}
|
|
||||||
|
|
||||||
let connecting = true
|
let connecting = true
|
||||||
rpc.connect().then(result => {
|
rpc.connect().then(result => {
|
||||||
buffers.clear()
|
buffers.clear()
|
||||||
bufferLast = undefined
|
|
||||||
bufferCurrent = undefined
|
bufferCurrent = undefined
|
||||||
bufferLog = undefined
|
bufferLog = undefined
|
||||||
bufferAutoscroll = true
|
bufferAutoscroll = true
|
||||||
@ -174,20 +161,13 @@ rpc.addEventListener('Ping', event => {
|
|||||||
rpc.addEventListener('BufferUpdate', event => {
|
rpc.addEventListener('BufferUpdate', event => {
|
||||||
let e = event.detail, b = buffers.get(e.bufferName)
|
let e = event.detail, b = buffers.get(e.bufferName)
|
||||||
if (b === undefined) {
|
if (b === undefined) {
|
||||||
buffers.set(e.bufferName, (b = {lines: []}))
|
buffers.set(e.bufferName, {
|
||||||
resetBufferStats(b)
|
lines: [],
|
||||||
|
newMessages: e.newMessages,
|
||||||
|
newUnimportantMessages: e.newUnimportantMessages,
|
||||||
|
highlighted: e.highlighted,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
b.hideUnimportant = e.hideUnimportant
|
|
||||||
})
|
|
||||||
|
|
||||||
rpc.addEventListener('BufferStats', event => {
|
|
||||||
let e = event.detail, b = buffers.get(e.bufferName)
|
|
||||||
if (b === undefined)
|
|
||||||
return
|
|
||||||
|
|
||||||
b.newMessages = e.newMessages,
|
|
||||||
b.newUnimportantMessages = e.newUnimportantMessages
|
|
||||||
b.highlighted = e.highlighted
|
|
||||||
})
|
})
|
||||||
|
|
||||||
rpc.addEventListener('BufferRename', event => {
|
rpc.addEventListener('BufferRename', event => {
|
||||||
@ -199,16 +179,16 @@ rpc.addEventListener('BufferRename', event => {
|
|||||||
rpc.addEventListener('BufferRemove', event => {
|
rpc.addEventListener('BufferRemove', event => {
|
||||||
let e = event.detail
|
let e = event.detail
|
||||||
buffers.delete(e.bufferName)
|
buffers.delete(e.bufferName)
|
||||||
if (e.bufferName === bufferLast)
|
|
||||||
bufferLast = undefined
|
|
||||||
})
|
})
|
||||||
|
|
||||||
rpc.addEventListener('BufferActivate', event => {
|
rpc.addEventListener('BufferActivate', event => {
|
||||||
let old = buffers.get(bufferCurrent)
|
let old = buffers.get(bufferCurrent)
|
||||||
if (old !== undefined)
|
if (old !== undefined) {
|
||||||
resetBufferStats(old)
|
old.newMessages = 0
|
||||||
|
old.newUnimportantMessages = 0
|
||||||
|
old.highlighted = false
|
||||||
|
}
|
||||||
|
|
||||||
bufferLast = bufferCurrent
|
|
||||||
let e = event.detail, b = buffers.get(e.bufferName)
|
let e = event.detail, b = buffers.get(e.bufferName)
|
||||||
bufferCurrent = e.bufferName
|
bufferCurrent = e.bufferName
|
||||||
bufferLog = undefined
|
bufferLog = undefined
|
||||||
@ -241,21 +221,18 @@ rpc.addEventListener('BufferLine', event => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let visible = !document.hidden && bufferLog === undefined &&
|
let visible = e.bufferName == bufferCurrent || e.leakToActive
|
||||||
(e.bufferName == bufferCurrent || e.leakToActive)
|
|
||||||
b.lines.push({...line})
|
b.lines.push({...line})
|
||||||
if (!(visible || e.leakToActive) ||
|
if (!visible || b.newMessages || b.newUnimportantMessages) {
|
||||||
b.newMessages || b.newUnimportantMessages) {
|
|
||||||
if (line.isUnimportant)
|
if (line.isUnimportant)
|
||||||
b.newUnimportantMessages++
|
b.newUnimportantMessages++
|
||||||
else
|
else
|
||||||
b.newMessages++
|
b.newMessages++
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.leakToActive) {
|
if (e.leakToActive) {
|
||||||
let bc = buffers.get(bufferCurrent)
|
let bc = buffers.get(bufferCurrent)
|
||||||
bc.lines.push({...line, leaked: true})
|
bc.lines.push({...line, leaked: true})
|
||||||
if (!visible || bc.newMessages || bc.newUnimportantMessages) {
|
if (bc.newMessages || bc.newUnimportantMessages) {
|
||||||
if (line.isUnimportant)
|
if (line.isUnimportant)
|
||||||
bc.newUnimportantMessages++
|
bc.newUnimportantMessages++
|
||||||
else
|
else
|
||||||
@ -296,12 +273,6 @@ for (let i = 0; i < 24; i++) {
|
|||||||
|
|
||||||
// ---- UI ---------------------------------------------------------------------
|
// ---- UI ---------------------------------------------------------------------
|
||||||
|
|
||||||
let linkRE = [
|
|
||||||
/https?:\/\//,
|
|
||||||
/([^\[\](){}<>"'\s]|\([^\[\](){}<>"'\s]*\))+/,
|
|
||||||
/[^\[\](){}<>"'\s,.:]/,
|
|
||||||
].map(r => r.source).join('')
|
|
||||||
|
|
||||||
let Toolbar = {
|
let Toolbar = {
|
||||||
toggleAutoscroll: () => {
|
toggleAutoscroll: () => {
|
||||||
bufferAutoscroll = !bufferAutoscroll
|
bufferAutoscroll = !bufferAutoscroll
|
||||||
@ -333,6 +304,10 @@ let Toolbar = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let BufferList = {
|
let BufferList = {
|
||||||
|
activate: name => {
|
||||||
|
rpc.send({command: 'BufferActivate', bufferName: name})
|
||||||
|
},
|
||||||
|
|
||||||
view: vnode => {
|
view: vnode => {
|
||||||
let items = Array.from(buffers, ([name, b]) => {
|
let items = Array.from(buffers, ([name, b]) => {
|
||||||
let classes = [], displayName = name
|
let classes = [], displayName = name
|
||||||
@ -347,7 +322,7 @@ let BufferList = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return m('.item', {
|
return m('.item', {
|
||||||
onclick: event => bufferActivate(name),
|
onclick: event => BufferList.activate(name),
|
||||||
class: classes.join(' '),
|
class: classes.join(' '),
|
||||||
}, displayName)
|
}, displayName)
|
||||||
})
|
})
|
||||||
@ -370,11 +345,17 @@ let Content = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
linkify: (text, attrs) => {
|
linkify: (text, attrs) => {
|
||||||
let re = new RegExp(linkRE, 'g'), a = [], end = 0, match
|
let re = new RegExp([
|
||||||
|
/https?:\/\//,
|
||||||
|
/([^\[\](){}<>"'\s]|\([^\[\](){}<>"'\s]*\))+/,
|
||||||
|
/[^\[\](){}<>"'\s,.:]/,
|
||||||
|
].map(r => r.source).join(''), 'g')
|
||||||
|
|
||||||
|
let a = [], end = 0, match
|
||||||
while ((match = re.exec(text)) !== null) {
|
while ((match = re.exec(text)) !== null) {
|
||||||
if (end < match.index)
|
if (end < match.index)
|
||||||
a.push(m('span', attrs, text.substring(end, match.index)))
|
a.push(m('span', attrs, text.substring(end, match.index)))
|
||||||
a.push(m('a[target=_blank]', {href: match[0], ...attrs}, match[0]))
|
a.push(m('a', {href: match[0], ...attrs}, match[0]))
|
||||||
end = re.lastIndex
|
end = re.lastIndex
|
||||||
}
|
}
|
||||||
if (end < text.length)
|
if (end < text.length)
|
||||||
@ -444,38 +425,25 @@ let Content = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let Buffer = {
|
let Buffer = {
|
||||||
controller: new AbortController(),
|
oncreate: vnode => {
|
||||||
|
if (vnode.dom !== undefined && bufferAutoscroll)
|
||||||
onbeforeremove: vnode => {
|
|
||||||
Buffer.controller.abort()
|
|
||||||
},
|
|
||||||
|
|
||||||
onupdate: vnode => {
|
|
||||||
if (bufferAutoscroll)
|
|
||||||
vnode.dom.scrollTop = vnode.dom.scrollHeight
|
vnode.dom.scrollTop = vnode.dom.scrollHeight
|
||||||
},
|
},
|
||||||
|
|
||||||
oncreate: vnode => {
|
onupdate: vnode => {
|
||||||
Buffer.onupdate(vnode)
|
Buffer.oncreate(vnode)
|
||||||
window.addEventListener('resize', event => Buffer.onupdate(vnode),
|
|
||||||
{signal: Buffer.controller.signal})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
view: vnode => {
|
view: vnode => {
|
||||||
let lines = []
|
let lines = []
|
||||||
let b = buffers.get(bufferCurrent)
|
let b = buffers.get(bufferCurrent)
|
||||||
if (b === undefined)
|
if (b === undefined)
|
||||||
return m('.buffer')
|
return
|
||||||
|
|
||||||
let lastDateMark = undefined
|
let lastDateMark = undefined
|
||||||
let markBefore = b.lines.length
|
let markBefore = b.lines.length
|
||||||
- b.newMessages - b.newUnimportantMessages
|
- b.newMessages - b.newUnimportantMessages
|
||||||
b.lines.forEach((line, i) => {
|
b.lines.forEach((line, i) => {
|
||||||
if (i == markBefore)
|
|
||||||
lines.push(m('.unread'))
|
|
||||||
if (line.isUnimportant && b.hideUnimportant)
|
|
||||||
return
|
|
||||||
|
|
||||||
let date = new Date(line.when)
|
let date = new Date(line.when)
|
||||||
let dateMark = date.toLocaleDateString()
|
let dateMark = date.toLocaleDateString()
|
||||||
if (dateMark !== lastDateMark) {
|
if (dateMark !== lastDateMark) {
|
||||||
@ -483,10 +451,14 @@ let Buffer = {
|
|||||||
lastDateMark = dateMark
|
lastDateMark = dateMark
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (i == markBefore)
|
||||||
|
lines.push(m('.unread'))
|
||||||
|
|
||||||
let attrs = {}
|
let attrs = {}
|
||||||
if (line.leaked)
|
if (line.leaked)
|
||||||
attrs.class = 'leaked'
|
attrs.class = 'leaked'
|
||||||
|
|
||||||
|
// TODO: Make use of isUnimportant.
|
||||||
lines.push(m('.time', {...attrs}, date.toLocaleTimeString()))
|
lines.push(m('.time', {...attrs}, date.toLocaleTimeString()))
|
||||||
lines.push(m(Content, {...attrs}, line))
|
lines.push(m(Content, {...attrs}, line))
|
||||||
})
|
})
|
||||||
@ -504,21 +476,8 @@ let Log = {
|
|||||||
vnode.dom.scrollTop = vnode.dom.scrollHeight
|
vnode.dom.scrollTop = vnode.dom.scrollHeight
|
||||||
},
|
},
|
||||||
|
|
||||||
linkify: text => {
|
|
||||||
let re = new RegExp(linkRE, 'g'), a = [], end = 0, match
|
|
||||||
while ((match = re.exec(text)) !== null) {
|
|
||||||
if (end < match.index)
|
|
||||||
a.push(text.substring(end, match.index))
|
|
||||||
a.push(m('a[target=_blank]', {href: match[0]}, match[0]))
|
|
||||||
end = re.lastIndex
|
|
||||||
}
|
|
||||||
if (end < text.length)
|
|
||||||
a.push(text.substring(end))
|
|
||||||
return a
|
|
||||||
},
|
|
||||||
|
|
||||||
view: vnode => {
|
view: vnode => {
|
||||||
return m(".log", {}, Log.linkify(bufferLog))
|
return m(".log", {}, bufferLog)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -583,13 +542,11 @@ let Input = {
|
|||||||
let handled = false
|
let handled = false
|
||||||
switch (event.keyCode) {
|
switch (event.keyCode) {
|
||||||
case 9:
|
case 9:
|
||||||
if (!event.ctrlKey && !event.metaKey && !event.altKey &&
|
if (!event.shiftKey)
|
||||||
!event.shiftKey)
|
|
||||||
handled = Input.complete(textarea)
|
handled = Input.complete(textarea)
|
||||||
break
|
break
|
||||||
case 13:
|
case 13:
|
||||||
if (!event.ctrlKey && !event.metaKey && !event.altKey &&
|
if (!event.shiftKey)
|
||||||
!event.shiftKey)
|
|
||||||
handled = Input.submit(textarea)
|
handled = Input.submit(textarea)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -613,7 +570,6 @@ let Main = {
|
|||||||
return m('.xP', {}, [
|
return m('.xP', {}, [
|
||||||
m('.title', {}, [`xP (${state})`, m(Toolbar)]),
|
m('.title', {}, [`xP (${state})`, m(Toolbar)]),
|
||||||
m('.middle', {}, [m(BufferList), m(BufferContainer)]),
|
m('.middle', {}, [m(BufferList), m(BufferContainer)]),
|
||||||
// TODO: Indicate hideUnimportant.
|
|
||||||
m('.status', {}, bufferCurrent),
|
m('.status', {}, bufferCurrent),
|
||||||
m(Input),
|
m(Input),
|
||||||
])
|
])
|
||||||
@ -621,28 +577,3 @@ let Main = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('load', () => m.mount(document.body, Main))
|
window.addEventListener('load', () => m.mount(document.body, Main))
|
||||||
|
|
||||||
document.addEventListener('keydown', event => {
|
|
||||||
if (rpc.ws == undefined || event.ctrlKey || event.metaKey)
|
|
||||||
return
|
|
||||||
|
|
||||||
if (event.altKey && event.key == 'Tab') {
|
|
||||||
if (bufferLast !== undefined)
|
|
||||||
bufferActivate(bufferLast)
|
|
||||||
} else if (event.altKey && event.key == 'a') {
|
|
||||||
for (const [name, b] of buffers)
|
|
||||||
if (name !== bufferCurrent && b.newMessages) {
|
|
||||||
bufferActivate(name)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else if (event.altKey && event.key == '!') {
|
|
||||||
for (const [name, b] of buffers)
|
|
||||||
if (name !== bufferCurrent && b.highlighted) {
|
|
||||||
bufferActivate(name)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
return
|
|
||||||
|
|
||||||
event.preventDefault()
|
|
||||||
})
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user