From d8299a1231110908986159b6f0ea65484fb17c84 Mon Sep 17 00:00:00 2001 From: Přemysl Janouch Date: Sun, 15 Nov 2015 01:03:29 +0100 Subject: degesch: enable and use bracketed paste mode urxvt, xterm and maybe others support quoting text pasted by the user from clipboard, which prevents leading tabs from changing into highlights. The handling isn't perfect so far, just wrong in a different way, as we mishandle newlines. --- degesch.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 85 insertions(+), 7 deletions(-) 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; } -- cgit v1.2.3-70-g09d2