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; | ||||||
|  | |||||||
							
								
								
									
										93
									
								
								xC.c
									
									
									
									
									
								
							
							
						
						
									
										93
									
								
								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 (); | 	if (!space_in_one_message) | ||||||
| 	cstr_split (message, "\r\n", false, &lines); | 		strv_append (output, message); | ||||||
| 	bool success = true; | 	else if (!wrap_message (message, space_in_one_message, output, e)) | ||||||
| 	for (size_t i = 0; i < lines.len; i++) | 		return false; | ||||||
| 	{ | 	return true; | ||||||
| 		// We don't always have the full info for message splitting.
 |  | ||||||
| 		if (!space_in_one_message) |  | ||||||
| 			strv_append (output, lines.vector[i]); |  | ||||||
| 		else if (!(success = |  | ||||||
| 			wrap_message (lines.vector[i], space_in_one_message, output, e))) |  | ||||||
| 			break; |  | ||||||
| 	} |  | ||||||
| 	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) |  | ||||||
| { | { | ||||||
| 	struct strv lines = strv_make (); | 	char *input; | ||||||
| 	cstr_split (input, "\r\n", false, &lines); | 	if (!(input = iconv_xstrdup (ctx->term_to_utf8, user_input, -1, NULL))) | ||||||
| 	for (size_t i = 0; i < lines.len; i++) | 		print_error ("character conversion failed for: %s", "user input"); | ||||||
| 		(void) process_input_line (ctx, buffer, lines.vector[i], 0); | 	else | ||||||
| 	strv_free (&lines); | 	{ | ||||||
|  | 		struct strv lines = strv_make (); | ||||||
|  | 		cstr_split (input, "\r\n", false, &lines); | ||||||
|  | 		for (size_t i = 0; i < lines.len; i++) | ||||||
|  | 			(void) process_input_utf8 (ctx, | ||||||
|  | 				ctx->current_buffer, lines.vector[i], 0); | ||||||
|  | 
 | ||||||
|  | 		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