From 443b5133fdddb755acf16fa50098f90de5f0138a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Eric=20Janouch?= Date: Mon, 29 Aug 2022 14:10:23 +0200 Subject: WIP: xC: force libedit into asynchronicity --- xC.c | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/xC.c b/xC.c index dd4dc0c..7bc1f5e 100644 --- a/xC.c +++ b/xC.c @@ -14717,9 +14717,121 @@ on_editline_return (EditLine *editline, int key) return CC_REFRESH; } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +#ifdef EDITLINE_ASYNC + +// This is an adjusted version of EL_BUILTIN_GETCFN. +static int +app_editline_wgetc (struct app_context *ctx, wchar_t *wch, bool *readable) +{ + char buf[MB_LEN_MAX] = {}; + size_t buf_len = 0; + mbstate_t mbs; + + *wch = L'\0'; + while (ctx->polling) + { + *readable = false; + poller_run (&ctx->poller); + if (!*readable) + continue; + + ssize_t n_read; + if ((n_read = read (STDIN_FILENO, buf + buf_len++, 1)) <= 0) + return n_read; + +retry: + // XXX: This will only really work for non-shifting encodings. + memset (&mbs, 0, sizeof mbs); + switch (mbrtowc (wch, buf, buf_len, &mbs)) { + case (size_t) -1: + // Invalid sequence, discard all bytes except any last one. + // Not exactly sure what this is supposed to help. + if (!--buf_len) + continue; + + buf[0] = buf[buf_len]; + buf_len = 1; + goto retry; + case (size_t) -2: + if (buf_len < sizeof buf) + continue; + + errno = EILSEQ; + *wch = L'\0'; + return -1; + default: + return 1; + } + } + return 0; +} + +static void +on_editline_tty_readable (const struct pollfd *fd, bool *readable) +{ + (void) fd; + *readable = true; +} + +static int +on_editline_wgetc (EditLine *editline, wchar_t *wch) +{ + struct app_context *ctx = g_ctx; + struct input_el *self = NULL; + el_get (editline, EL_CLIENTDATA, &self); + + // We must not touch ourselves at any cost, Editline isn't that reentrant. + // TODO: Also defer SIGWINCH, since we keep the foreground process group. + self->active = false; + ctx->terminal_suspended++; + poller_fd_reset (&ctx->tty_event); + + bool readable = false; + struct poller_fd tty_event_redirect = + poller_fd_make (&ctx->poller, ctx->tty_event.fd); + tty_event_redirect.dispatcher = (poller_fd_fn) on_editline_tty_readable; + tty_event_redirect.user_data = &readable; + + poller_fd_set (&tty_event_redirect, POLLIN); + int result = app_editline_wgetc (ctx, wch, &readable); + poller_fd_reset (&tty_event_redirect); + + poller_fd_set (&ctx->tty_event, POLLIN); + ctx->terminal_suspended--; + self->active = true; + + // TODO: If needed, at the end of input_el_on_tty_readable(): + // - buffer_print_backlog (ctx, ctx->current_buffer); + // - update Editline's prompt. + // What's hard is detecting when it's needed, because it's a corner case: + // - in general, any time buffer_print_backlog() is called, + // - when log_formatter() doesn't display something for current_buffer, + // - ... + + // TODO: It seems like _buffer_switch() should be deferred, + // at least in case of Editline--it can't wreak much async havoc, + // seeing as it returns right after any command is done. + // + // What's worse is that input buffers may need to be removed, + // which would require _buffer_destroy() to take ownership of ::current, + // since we can't destroy /or change/ Editline's current history pointer. + return result; +} + +#endif // EDITLINE_ASYNC + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + static void app_editline_init (struct input_el *self) { +#ifdef EDITLINE_ASYNC + // Avoid timeouts while reading in sequences + el_wset (self->editline, EL_GETCFN, on_editline_wgetc); +#endif // EDITLINE_ASYNC + // el_set() leaks memory in 20150325 and other versions, we need wchar_t el_wset (self->editline, EL_ADDFN, L"send-line", L"Send line", on_editline_return); -- cgit v1.2.3