From cf14cb8122bc73f697643748aadd2993535be827 Mon Sep 17 00:00:00 2001 From: Přemysl Eric Janouch Date: Tue, 6 Sep 2022 16:27:22 +0200 Subject: xC: implement buffer completion in the relay And actually support completion with non-UTF-8 locales. We used to ignore the encoding conversion result. --- xC.c | 164 ++++++++++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 98 insertions(+), 66 deletions(-) diff --git a/xC.c b/xC.c index af2adbd..6d37ece 100644 --- a/xC.c +++ b/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. // 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 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 }; } -static void -completion_parse (struct completion *self, const char *line, size_t len) +static struct completion +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 - const char *s = self->line; + const char *s = self.line; while (true) { 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; 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) break; } + return self; } static void @@ -13486,14 +13481,13 @@ complete_set (struct app_context *ctx, struct completion *data, } 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) { (void) data; // 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 - struct buffer *buffer = ctx->current_buffer; if (buffer->type != BUFFER_CHANNEL) return; @@ -13509,10 +13503,9 @@ complete_topic (struct app_context *ctx, struct completion *data, } 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) { - struct buffer *buffer = ctx->current_buffer; if (buffer->type == BUFFER_SERVER) { struct user *self_user = buffer->server->irc_user; @@ -13534,9 +13527,9 @@ complete_nicknames (struct app_context *ctx, struct completion *data, } } -static char ** -complete_word (struct app_context *ctx, struct completion *data, - const char *word) +static struct strv +complete_word (struct app_context *ctx, struct buffer *buffer, + struct completion *data, const char *word) { 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")) { - complete_topic (ctx, data, word, &words); - complete_nicknames (ctx, data, word, &words); + complete_topic (buffer, data, word, &words); + complete_nicknames (buffer, data, word, &words); } else - complete_nicknames (ctx, data, word, &words); + complete_nicknames (buffer, data, word, &words); cstr_set (&initial, NULL); 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); } - if (words.len == 1) - { - // Nothing matched - strv_free (&words); - return NULL; - } - - if (words.len == 2) + if (words.len <= 2) { + // When nothing matches, this copies the sentinel value words.vector[0] = words.vector[1]; words.vector[1] = NULL; + words.len--; } else { @@ -13589,7 +13577,7 @@ complete_word (struct app_context *ctx, struct completion *data, else 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); } -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, /// returns a vector of matches in locale-specific encoding. 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 }; 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); - struct completion c; - completion_init (&c); - 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); - + struct strv completions = + make_completions (ctx, ctx->current_buffer, line_utf8, start, end); 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 -------------------------------------------- @@ -14381,7 +14372,7 @@ app_readline_completion (const char *text, int start, int end) // Don't iterate over filenames and stuff 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 @@ -14425,8 +14416,6 @@ static unsigned char on_editline_complete (EditLine *editline, int key) { (void) key; - (void) editline; - struct app_context *ctx = g_ctx; // 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])) 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 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 client_process_buffer_log (struct client *c, uint32_t seq, struct buffer *buffer) { - struct relay_event_message *m = relay_prepare (c->ctx); - struct relay_event_data_response *e = &m->data.response; + 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_LOG; @@ -15254,9 +15287,8 @@ client_process_message (struct client *c, reset_autoaway (c->ctx); break; case RELAY_COMMAND_BUFFER_COMPLETE: - // TODO: Run the completion machinery. - relay_prepare_error (c->ctx, m->command_seq, "Not implemented"); - relay_send (c); + client_process_buffer_complete (c, m->command_seq, buffer, + &m->data.buffer_complete); break; case RELAY_COMMAND_BUFFER_INPUT: (void) process_input_utf8 (c->ctx, -- cgit v1.2.3-70-g09d2