diff --git a/degesch.c b/degesch.c index fb95019..4c27283 100644 --- a/degesch.c +++ b/degesch.c @@ -1367,9 +1367,13 @@ struct app_context size_t nick_palette_len; ///< Number of entries in nick_palette bool awaiting_mirc_escape; ///< Awaiting a mIRC attribute escape + // TODO: try to get rid of this in favor of "paste_buffer" -> "input_buffer" char char_buf[MB_LEN_MAX + 1]; ///< Buffered multibyte char size_t char_buf_len; ///< How much of an MB char is buffered + bool in_bracketed_paste; ///< User is pasting some content + struct str paste_buffer; ///< Buffered pasted content + bool running_backlog_helper; ///< Running a backlog helper } *g_ctx; @@ -1433,6 +1437,7 @@ app_context_init (struct app_context *self) free (encoding); input_init (&self->input); + str_init (&self->paste_buffer); self->nick_palette = filter_color_cube_for_acceptable_nick_colors (&self->nick_palette_len); @@ -1466,6 +1471,7 @@ app_context_free (struct app_context *self) iconv_close (self->term_to_utf8); input_free (&self->input); + str_free (&self->paste_buffer); } static void refresh_prompt (struct app_context *ctx); @@ -8602,6 +8608,7 @@ process_input (struct app_context *ctx, char *user_input) if (!(input = iconv_xstrdup (ctx->term_to_utf8, user_input, -1, NULL))) print_error ("character conversion failed for `%s'", "user input"); else + // TODO: split at newlines? (void) process_input_utf8 (ctx, ctx->current_buffer, input, 0); free (input); } @@ -8973,6 +8980,13 @@ make_completions (struct app_context *ctx, char *line, int start, int end) // --- Common code for user actions -------------------------------------------- +static void +toggle_bracketed_paste (bool enable) +{ + fprintf (stdout, "\x1b[?2004%c", "lh"[enable]); + fflush (stdout); +} + static void suspend_terminal (struct app_context *ctx) { @@ -8982,6 +8996,7 @@ suspend_terminal (struct app_context *ctx) el_set (ctx->input.editline, EL_PREP_TERM, 0); #endif + toggle_bracketed_paste (false); input_hide (&ctx->input); poller_fd_reset (&ctx->tty_event); // TODO: also disable the date change timer @@ -8996,6 +9011,7 @@ resume_terminal (struct app_context *ctx) el_set (ctx->input.editline, EL_PREP_TERM, 1); #endif + toggle_bracketed_paste (true); // In theory we could just print all unseen messages but this is safer buffer_print_backlog (ctx, ctx->current_buffer); // Now it's safe to process any user input @@ -9154,6 +9170,8 @@ bind_common_keys (struct app_context *ctx) if (clear_screen) input_bind_control (self, 'l', "redraw-screen"); + + input_bind (self, "\x1b[200~", "start-paste-mode"); } // --- GNU Readline user actions ----------------------------------------------- @@ -9236,6 +9254,17 @@ on_readline_insert_attribute (int count, int key) return 0; } +static int +on_readline_start_paste_mode (int count, int key) +{ + (void) count; + (void) key; + + struct app_context *ctx = g_ctx; + ctx->in_bracketed_paste = true; + return 0; +} + static int on_readline_return (int count, int key) { @@ -9309,6 +9338,7 @@ app_readline_init (void) rl_add_defun ("display-full-log", on_readline_display_full_log, -1); rl_add_defun ("redraw-screen", on_readline_redraw_screen, -1); rl_add_defun ("insert-attribute", on_readline_insert_attribute, -1); + rl_add_defun ("start-paste-mode", on_readline_start_paste_mode, -1); rl_add_defun ("send-line", on_readline_return, -1); bind_common_keys (ctx); @@ -9407,6 +9437,16 @@ on_editline_insert_attribute (EditLine *editline, int key) return CC_NORM; } +static unsigned char +on_editline_start_paste_mode (EditLine *editline, int key) +{ + (void) editline; + (void) key; + + g_ctx->in_bracketed_paste = true; + return CC_NORM; +} + static unsigned char on_editline_complete (EditLine *editline, int key) { @@ -9506,6 +9546,7 @@ app_editline_init (struct input *self) { "display-full-log", "Show full log", on_editline_display_full_log }, { "redraw-screen", "Redraw screen", on_editline_redraw_screen }, { "insert-attribute", "mIRC formatting", on_editline_insert_attribute }, + { "start-paste-mode", "Bracketed paste", on_editline_start_paste_mode }, { "send-line", "Send line", on_editline_return }, { "complete", "Complete word", on_editline_complete }, }; @@ -9886,6 +9927,43 @@ done: ctx->awaiting_mirc_escape = false; } +#define BRACKETED_PASTE_LIMIT 102400 ///< How much text can be pasted + +static void +process_bracketed_paste (const struct pollfd *fd, struct app_context *ctx) +{ + struct str *buf = &ctx->paste_buffer; + str_ensure_space (buf, 1); + if (read (fd->fd, buf->str + buf->len, 1) != 1) + goto error; + buf->str[++buf->len] = '\0'; + + static const char stop_mark[] = "\x1b[201~"; + static const size_t stop_mark_len = sizeof stop_mark - 1; + if (buf->len < stop_mark_len) + return; + + size_t text_len = buf->len - stop_mark_len; + if (memcmp (buf->str + text_len, stop_mark, stop_mark_len)) + return; + + // Avoid endless flooding of the buffer + if (text_len > BRACKETED_PASTE_LIMIT) + log_global_error (ctx, "Paste trimmed to %zu bytes", + (text_len = BRACKETED_PASTE_LIMIT)); + + buf->str[text_len] = '\0'; + if (input_insert (&ctx->input, buf->str)) + goto done; + +error: + input_ding (&ctx->input); + log_global_error (ctx, "Paste failed"); +done: + str_reset (buf); + ctx->in_bracketed_paste = false; +} + static void on_tty_readable (const struct pollfd *fd, struct app_context *ctx) { @@ -9895,14 +9973,12 @@ on_tty_readable (const struct pollfd *fd, struct app_context *ctx) print_debug ("fd %d: unexpected revents: %d", fd->fd, fd->revents); if (ctx->awaiting_mirc_escape) - { process_mirc_escape (fd, ctx); - return; - } - - // XXX: this may loop for a bit: stop the event or eat the input? - // (This prevents a segfault when the input has been stopped.) - if (ctx->input.active) + else if (ctx->in_bracketed_paste) + process_bracketed_paste (fd, ctx); + else if (ctx->input.active) + // XXX: this may loop for a bit: stop the event or eat the input? + // (This prevents a segfault when the input has been stopped.) input_on_readable (&ctx->input); } @@ -10053,6 +10129,7 @@ main (int argc, char *argv[]) // Initialize input so that we can switch to new buffers refresh_prompt (&ctx); input_start (&ctx.input, argv[0]); + toggle_bracketed_paste (true); // Finally, we juice the configuration for some servers to create load_servers (&ctx); @@ -10065,6 +10142,7 @@ main (int argc, char *argv[]) save_configuration (&ctx); app_context_free (&ctx); + toggle_bracketed_paste (false); free_terminal (); return EXIT_SUCCESS; }