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:
Přemysl Eric Janouch 2022-09-06 16:27:22 +02:00
parent 31e9c6d2d5
commit cf14cb8122
Signed by: p
GPG Key ID: A0420B94F92B9493
1 changed files with 99 additions and 67 deletions

166
xC.c
View File

@ -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,