xC: implement buffer completion in the relay
And actually support completion with non-UTF-8 locales. We used to ignore the encoding conversion result.
This commit is contained in:
		
							
								
								
									
										166
									
								
								xC.c
									
									
									
									
									
								
							
							
						
						
									
										166
									
								
								xC.c
									
									
									
									
									
								
							@@ -13271,12 +13271,6 @@ process_input (struct app_context *ctx, char *user_input)
 | 
				
			|||||||
// The amount of crap that goes into this is truly insane.
 | 
					// The amount of crap that goes into this is truly insane.
 | 
				
			||||||
// It's mostly because of Editline's total ignorance of this task.
 | 
					// It's mostly because of Editline's total ignorance of this task.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void
 | 
					 | 
				
			||||||
completion_init (struct completion *self)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	memset (self, 0, sizeof *self);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
completion_free (struct completion *self)
 | 
					completion_free (struct completion *self)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -13295,13 +13289,13 @@ completion_add_word (struct completion *self, size_t start, size_t end)
 | 
				
			|||||||
	self->words[self->words_len++] = (struct completion_word) { start, end };
 | 
						self->words[self->words_len++] = (struct completion_word) { start, end };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void
 | 
					static struct completion
 | 
				
			||||||
completion_parse (struct completion *self, const char *line, size_t len)
 | 
					completion_make (const char *line, size_t len)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	self->line = xstrndup (line, len);
 | 
						struct completion self = { .line = xstrndup (line, len) };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// The first and the last word may be empty
 | 
						// The first and the last word may be empty
 | 
				
			||||||
	const char *s = self->line;
 | 
						const char *s = self.line;
 | 
				
			||||||
	while (true)
 | 
						while (true)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		const char *start = s;
 | 
							const char *start = s;
 | 
				
			||||||
@@ -13309,10 +13303,11 @@ completion_parse (struct completion *self, const char *line, size_t len)
 | 
				
			|||||||
		const char *end = start + word_len;
 | 
							const char *end = start + word_len;
 | 
				
			||||||
		s = end + strspn (end, WORD_BREAKING_CHARS);
 | 
							s = end + strspn (end, WORD_BREAKING_CHARS);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		completion_add_word (self, start - self->line, end - self->line);
 | 
							completion_add_word (&self, start - self.line, end - self.line);
 | 
				
			||||||
		if (s == end)
 | 
							if (s == end)
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						return self;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
@@ -13486,14 +13481,13 @@ complete_set (struct app_context *ctx, struct completion *data,
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
complete_topic (struct app_context *ctx, struct completion *data,
 | 
					complete_topic (struct buffer *buffer, struct completion *data,
 | 
				
			||||||
	const char *word, struct strv *output)
 | 
						const char *word, struct strv *output)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	(void) data;
 | 
						(void) data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// TODO: make it work in other server-related buffers, too, i.e. when we're
 | 
						// TODO: make it work in other server-related buffers, too, i.e. when we're
 | 
				
			||||||
	//   completing the third word and the second word is a known channel name
 | 
						//   completing the third word and the second word is a known channel name
 | 
				
			||||||
	struct buffer *buffer = ctx->current_buffer;
 | 
					 | 
				
			||||||
	if (buffer->type != BUFFER_CHANNEL)
 | 
						if (buffer->type != BUFFER_CHANNEL)
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -13509,10 +13503,9 @@ complete_topic (struct app_context *ctx, struct completion *data,
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
complete_nicknames (struct app_context *ctx, struct completion *data,
 | 
					complete_nicknames (struct buffer *buffer, struct completion *data,
 | 
				
			||||||
	const char *word, struct strv *output)
 | 
						const char *word, struct strv *output)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct buffer *buffer = ctx->current_buffer;
 | 
					 | 
				
			||||||
	if (buffer->type == BUFFER_SERVER)
 | 
						if (buffer->type == BUFFER_SERVER)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		struct user *self_user = buffer->server->irc_user;
 | 
							struct user *self_user = buffer->server->irc_user;
 | 
				
			||||||
@@ -13534,9 +13527,9 @@ complete_nicknames (struct app_context *ctx, struct completion *data,
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static char **
 | 
					static struct strv
 | 
				
			||||||
complete_word (struct app_context *ctx, struct completion *data,
 | 
					complete_word (struct app_context *ctx, struct buffer *buffer,
 | 
				
			||||||
	const char *word)
 | 
						struct completion *data, const char *word)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	char *initial = completion_word (data, 0);
 | 
						char *initial = completion_word (data, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -13555,11 +13548,11 @@ complete_word (struct app_context *ctx, struct completion *data,
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	else if (data->location == 1 && !strcmp (initial, "/topic"))
 | 
						else if (data->location == 1 && !strcmp (initial, "/topic"))
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		complete_topic     (ctx, data, word, &words);
 | 
							complete_topic     (buffer, data, word, &words);
 | 
				
			||||||
		complete_nicknames (ctx, data, word, &words);
 | 
							complete_nicknames (buffer, data, word, &words);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	else
 | 
						else
 | 
				
			||||||
		complete_nicknames (ctx, data, word, &words);
 | 
							complete_nicknames (buffer, data, word, &words);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cstr_set (&initial, NULL);
 | 
						cstr_set (&initial, NULL);
 | 
				
			||||||
	LIST_FOR_EACH (struct hook, iter, ctx->completion_hooks)
 | 
						LIST_FOR_EACH (struct hook, iter, ctx->completion_hooks)
 | 
				
			||||||
@@ -13568,17 +13561,12 @@ complete_word (struct app_context *ctx, struct completion *data,
 | 
				
			|||||||
		hook->complete (hook, data, word, &words);
 | 
							hook->complete (hook, data, word, &words);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (words.len == 1)
 | 
						if (words.len <= 2)
 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		// Nothing matched
 | 
					 | 
				
			||||||
		strv_free (&words);
 | 
					 | 
				
			||||||
		return NULL;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (words.len == 2)
 | 
					 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
 | 
							// When nothing matches, this copies the sentinel value
 | 
				
			||||||
		words.vector[0] = words.vector[1];
 | 
							words.vector[0] = words.vector[1];
 | 
				
			||||||
		words.vector[1] = NULL;
 | 
							words.vector[1] = NULL;
 | 
				
			||||||
 | 
							words.len--;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	else
 | 
						else
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
@@ -13589,7 +13577,7 @@ complete_word (struct app_context *ctx, struct completion *data,
 | 
				
			|||||||
		else
 | 
							else
 | 
				
			||||||
			words.vector[0] = xstrndup (words.vector[1], prefix);
 | 
								words.vector[0] = xstrndup (words.vector[1], prefix);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return words.vector;
 | 
						return words;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
					// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
				
			||||||
@@ -13655,26 +13643,26 @@ locale_to_utf8 (struct app_context *ctx, const char *locale,
 | 
				
			|||||||
	return str_steal (&utf8);
 | 
						return str_steal (&utf8);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void
 | 
					 | 
				
			||||||
utf8_vector_to_locale (struct app_context *ctx, char **vector)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	for (; *vector; vector++)
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		char *converted = iconv_xstrdup
 | 
					 | 
				
			||||||
			(ctx->term_from_utf8, *vector, -1, NULL);
 | 
					 | 
				
			||||||
		if (!soft_assert (converted))
 | 
					 | 
				
			||||||
			converted = xstrdup ("");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		cstr_set (vector, converted);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
					// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct strv
 | 
				
			||||||
 | 
					make_completions (struct app_context *ctx, struct buffer *buffer,
 | 
				
			||||||
 | 
						const char *line_utf8, size_t start, size_t end)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct completion comp = completion_make (line_utf8, strlen (line_utf8));
 | 
				
			||||||
 | 
						completion_locate (&comp, start);
 | 
				
			||||||
 | 
						char *word = xstrndup (line_utf8 + start, end - start);
 | 
				
			||||||
 | 
						struct strv completions = complete_word (ctx, buffer, &comp, word);
 | 
				
			||||||
 | 
						free (word);
 | 
				
			||||||
 | 
						completion_free (&comp);
 | 
				
			||||||
 | 
						return completions;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Takes a line in locale-specific encoding and position of a word to complete,
 | 
					/// Takes a line in locale-specific encoding and position of a word to complete,
 | 
				
			||||||
/// returns a vector of matches in locale-specific encoding.
 | 
					/// returns a vector of matches in locale-specific encoding.
 | 
				
			||||||
static char **
 | 
					static char **
 | 
				
			||||||
make_completions (struct app_context *ctx, char *line, int start, int end)
 | 
					make_input_completions
 | 
				
			||||||
 | 
						(struct app_context *ctx, const char *line, int start, int end)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	int *fixes[] = { &start, &end };
 | 
						int *fixes[] = { &start, &end };
 | 
				
			||||||
	char *line_utf8 = locale_to_utf8 (ctx, line, fixes, N_ELEMENTS (fixes));
 | 
						char *line_utf8 = locale_to_utf8 (ctx, line, fixes, N_ELEMENTS (fixes));
 | 
				
			||||||
@@ -13683,20 +13671,23 @@ make_completions (struct app_context *ctx, char *line, int start, int end)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	hard_assert (start >= 0 && end >= 0 && start <= end);
 | 
						hard_assert (start >= 0 && end >= 0 && start <= end);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	struct completion c;
 | 
						struct strv completions =
 | 
				
			||||||
	completion_init (&c);
 | 
							make_completions (ctx, ctx->current_buffer, line_utf8, start, end);
 | 
				
			||||||
	completion_parse (&c, line, strlen (line));
 | 
					 | 
				
			||||||
	completion_locate (&c, start);
 | 
					 | 
				
			||||||
	char *word = xstrndup (line + start, end - start);
 | 
					 | 
				
			||||||
	char **completions = complete_word (ctx, &c, word);
 | 
					 | 
				
			||||||
	free (word);
 | 
					 | 
				
			||||||
	completion_free (&c);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (completions)
 | 
					 | 
				
			||||||
		utf8_vector_to_locale (ctx, completions);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	free (line_utf8);
 | 
						free (line_utf8);
 | 
				
			||||||
	return completions;
 | 
						if (!completions.len)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							strv_free (&completions);
 | 
				
			||||||
 | 
							return NULL;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for (size_t i = 0; i < completions.len; i++)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							char *converted = iconv_xstrdup
 | 
				
			||||||
 | 
								(ctx->term_from_utf8, completions.vector[i], -1, NULL);
 | 
				
			||||||
 | 
							if (!soft_assert (converted))
 | 
				
			||||||
 | 
								converted = xstrdup ("?");
 | 
				
			||||||
 | 
							cstr_set (&completions.vector[i], converted);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return completions.vector;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// --- Common code for user actions --------------------------------------------
 | 
					// --- Common code for user actions --------------------------------------------
 | 
				
			||||||
@@ -14381,7 +14372,7 @@ app_readline_completion (const char *text, int start, int end)
 | 
				
			|||||||
	// Don't iterate over filenames and stuff
 | 
						// Don't iterate over filenames and stuff
 | 
				
			||||||
	rl_attempted_completion_over = true;
 | 
						rl_attempted_completion_over = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return make_completions (g_ctx, rl_line_buffer, start, end);
 | 
						return make_input_completions (g_ctx, rl_line_buffer, start, end);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int
 | 
					static int
 | 
				
			||||||
@@ -14425,8 +14416,6 @@ static unsigned char
 | 
				
			|||||||
on_editline_complete (EditLine *editline, int key)
 | 
					on_editline_complete (EditLine *editline, int key)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	(void) key;
 | 
						(void) key;
 | 
				
			||||||
	(void) editline;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	struct app_context *ctx = g_ctx;
 | 
						struct app_context *ctx = g_ctx;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// First prepare what Readline would have normally done for us...
 | 
						// First prepare what Readline would have normally done for us...
 | 
				
			||||||
@@ -14440,7 +14429,7 @@ on_editline_complete (EditLine *editline, int key)
 | 
				
			|||||||
	while (el_start && !strchr (WORD_BREAKING_CHARS, copy[el_start - 1]))
 | 
						while (el_start && !strchr (WORD_BREAKING_CHARS, copy[el_start - 1]))
 | 
				
			||||||
		el_start--;
 | 
							el_start--;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	char **completions = make_completions (ctx, copy, el_start, el_end);
 | 
						char **completions = make_input_completions (ctx, copy, el_start, el_end);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// XXX: possibly incorrect wrt. shift state encodings
 | 
						// XXX: possibly incorrect wrt. shift state encodings
 | 
				
			||||||
	copy[el_end] = '\0';
 | 
						copy[el_end] = '\0';
 | 
				
			||||||
@@ -15178,12 +15167,56 @@ client_message_buffer_name (const struct relay_command_message *m)
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					client_process_buffer_complete (struct client *c, uint32_t seq,
 | 
				
			||||||
 | 
						struct buffer *buffer, struct relay_command_data_buffer_complete *req)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct str *line = &req->text;
 | 
				
			||||||
 | 
						uint32_t end = req->position;
 | 
				
			||||||
 | 
						if (line->len < end || line->len != strlen (line->str))
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							relay_prepare_error (c->ctx, seq, "Invalid arguments");
 | 
				
			||||||
 | 
							goto out;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						uint32_t start = end;
 | 
				
			||||||
 | 
						while (start && !strchr (WORD_BREAKING_CHARS, line->str[start - 1]))
 | 
				
			||||||
 | 
							start--;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct strv completions =
 | 
				
			||||||
 | 
							make_completions (c->ctx, buffer, line->str, start, end);
 | 
				
			||||||
 | 
						if (completions.len > UINT32_MAX)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							relay_prepare_error (c->ctx, seq, "Internal error");
 | 
				
			||||||
 | 
							goto out_internal;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct relay_event_data_response *e =
 | 
				
			||||||
 | 
							&relay_prepare (c->ctx)->data.response;
 | 
				
			||||||
 | 
						e->event = RELAY_EVENT_RESPONSE;
 | 
				
			||||||
 | 
						e->command_seq = seq;
 | 
				
			||||||
 | 
						e->data.command = RELAY_COMMAND_BUFFER_COMPLETE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct relay_response_data_buffer_complete *resp =
 | 
				
			||||||
 | 
							&e->data.buffer_complete;
 | 
				
			||||||
 | 
						resp->start = start;
 | 
				
			||||||
 | 
						resp->completions_len = completions.len;
 | 
				
			||||||
 | 
						resp->completions = xcalloc (completions.len, sizeof *resp->completions);
 | 
				
			||||||
 | 
						for (size_t i = 0; i < completions.len; i++)
 | 
				
			||||||
 | 
							resp->completions[i] = str_from_cstr (completions.vector[i]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					out_internal:
 | 
				
			||||||
 | 
						strv_free (&completions);
 | 
				
			||||||
 | 
					out:
 | 
				
			||||||
 | 
						relay_send (c);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
client_process_buffer_log
 | 
					client_process_buffer_log
 | 
				
			||||||
	(struct client *c, uint32_t seq, struct buffer *buffer)
 | 
						(struct client *c, uint32_t seq, struct buffer *buffer)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct relay_event_message *m = relay_prepare (c->ctx);
 | 
						struct relay_event_data_response *e =
 | 
				
			||||||
	struct relay_event_data_response *e = &m->data.response;
 | 
							&relay_prepare (c->ctx)->data.response;
 | 
				
			||||||
	e->event = RELAY_EVENT_RESPONSE;
 | 
						e->event = RELAY_EVENT_RESPONSE;
 | 
				
			||||||
	e->command_seq = seq;
 | 
						e->command_seq = seq;
 | 
				
			||||||
	e->data.command = RELAY_COMMAND_BUFFER_LOG;
 | 
						e->data.command = RELAY_COMMAND_BUFFER_LOG;
 | 
				
			||||||
@@ -15254,9 +15287,8 @@ client_process_message (struct client *c,
 | 
				
			|||||||
		reset_autoaway (c->ctx);
 | 
							reset_autoaway (c->ctx);
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
	case RELAY_COMMAND_BUFFER_COMPLETE:
 | 
						case RELAY_COMMAND_BUFFER_COMPLETE:
 | 
				
			||||||
		// TODO: Run the completion machinery.
 | 
							client_process_buffer_complete (c, m->command_seq, buffer,
 | 
				
			||||||
		relay_prepare_error (c->ctx, m->command_seq, "Not implemented");
 | 
								&m->data.buffer_complete);
 | 
				
			||||||
		relay_send (c);
 | 
					 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
	case RELAY_COMMAND_BUFFER_INPUT:
 | 
						case RELAY_COMMAND_BUFFER_INPUT:
 | 
				
			||||||
		(void) process_input_utf8 (c->ctx,
 | 
							(void) process_input_utf8 (c->ctx,
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user