diff options
Diffstat (limited to 'src/fe-text/gui-readline.c')
-rw-r--r-- | src/fe-text/gui-readline.c | 289 |
1 files changed, 239 insertions, 50 deletions
diff --git a/src/fe-text/gui-readline.c b/src/fe-text/gui-readline.c index 476a798b..2c2eac21 100644 --- a/src/fe-text/gui-readline.c +++ b/src/fe-text/gui-readline.c @@ -37,8 +37,12 @@ #include "gui-windows.h" #include "utf8.h" +#include <string.h> #include <signal.h> +/* After LINE_SPLIT_LIMIT characters, the message will be split into multiple lines */ +#define LINE_SPLIT_LIMIT 400 + typedef void (*ENTRY_REDIRECT_KEY_FUNC) (int key, void *data, SERVER_REC *server, WI_ITEM_REC *item); typedef void (*ENTRY_REDIRECT_ENTRY_FUNC) (const char *line, void *data, SERVER_REC *server, WI_ITEM_REC *item); @@ -60,11 +64,22 @@ static int paste_detect_time, paste_verify_line_count; static char *paste_entry; static int paste_entry_pos; static GArray *paste_buffer; +static GArray *paste_buffer_rest; static char *paste_old_prompt; static int paste_prompt, paste_line_count; static int paste_join_multiline; static int paste_timeout_id; +static int paste_use_bracketed_mode; +static int paste_bracketed_mode; +static int paste_was_bracketed_mode; +static int previous_yank_preceded; + +/* Terminal sequences that surround the input when the terminal has the + * bracketed paste mode active. Fror more details see + * https://cirw.in/blog/bracketed-paste */ +static const unichar bp_start[] = { 0x1b, '[', '2', '0', '0', '~' }; +static const unichar bp_end[] = { 0x1b, '[', '2', '0', '1', '~' }; static void sig_input(void); @@ -147,7 +162,6 @@ static void window_next_page(void) static void paste_buffer_join_lines(GArray *buf) { -#define IS_WHITE(c) ((c) == ' ' || (c) == '\t') unsigned int i, count, indent, line_len; unichar *arr, *dest, *last_lf_pos; int last_lf; @@ -177,15 +191,15 @@ static void paste_buffer_join_lines(GArray *buf) if (buf->len == 0) return; - arr = (unichar *) paste_buffer->data; + arr = (unichar *)buf->data; /* first line */ - if (IS_WHITE(arr[0])) + if (isblank(arr[0])) return; /* find the first beginning of indented line */ for (i = 1; i < buf->len; i++) { - if (arr[i-1] == '\n' && IS_WHITE(arr[i])) + if (arr[i-1] == '\n' && isblank(arr[i])) break; } if (i == buf->len) @@ -193,7 +207,7 @@ static void paste_buffer_join_lines(GArray *buf) /* get how much indentation we have.. */ for (indent = 0; i < buf->len; i++, indent++) { - if (!IS_WHITE(arr[i])) + if (!isblank(arr[i])) break; } if (i == buf->len) @@ -203,7 +217,7 @@ static void paste_buffer_join_lines(GArray *buf) count = indent; last_lf = TRUE; for (; i < buf->len; i++) { if (last_lf) { - if (IS_WHITE(arr[i])) + if (isblank(arr[i])) count++; else { last_lf = FALSE; @@ -217,14 +231,14 @@ static void paste_buffer_join_lines(GArray *buf) } /* all looks fine - now remove the whitespace, but don't let lines - get longer than 400 chars */ + get longer than LINE_SPLIT_LIMIT chars */ dest = arr; last_lf = TRUE; last_lf_pos = NULL; line_len = 0; for (i = 0; i < buf->len; i++) { - if (last_lf && IS_WHITE(arr[i])) { + if (last_lf && isblank(arr[i])) { /* whitespace, ignore */ } else if (arr[i] == '\n') { if (!last_lf && i+1 != buf->len && - IS_WHITE(arr[i+1])) { + isblank(arr[i+1])) { last_lf_pos = dest; *dest++ = ' '; } else { @@ -235,9 +249,9 @@ static void paste_buffer_join_lines(GArray *buf) last_lf = TRUE; } else { last_lf = FALSE; - if (++line_len >= 400 && last_lf_pos != NULL) { + if (++line_len >= LINE_SPLIT_LIMIT && last_lf_pos != NULL) { memmove(last_lf_pos+1, last_lf_pos, - dest - last_lf_pos); + (dest - last_lf_pos) * sizeof(unichar)); *last_lf_pos = '\n'; last_lf_pos = NULL; line_len = 0; dest++; @@ -248,9 +262,16 @@ static void paste_buffer_join_lines(GArray *buf) g_array_set_size(buf, dest - arr); } +static void paste_send_line(char *text) +{ + /* we need to get the current history every time because it might change between calls */ + command_history_add(command_history_current(active_win), text); + + signal_emit("send command", 3, text, active_win->active_server, active_win->active); +} + static void paste_send(void) { - HISTORY_REC *history; unichar *arr; GString *str; char out[10], *text; @@ -275,11 +296,7 @@ static void paste_send(void) } text = gui_entry_get_text(active_entry); - history = command_history_current(active_win); - command_history_add(history, text); - - signal_emit("send command", 3, text, - active_win->active_server, active_win->active); + paste_send_line(text); g_free(text); } @@ -287,12 +304,7 @@ static void paste_send(void) str = g_string_new(NULL); for (; i < paste_buffer->len; i++) { if (arr[i] == '\r' || arr[i] == '\n') { - history = command_history_current(active_win); - command_history_add(history, str->str); - - signal_emit("send command", 3, str->str, - active_win->active_server, - active_win->active); + paste_send_line(str->str); g_string_truncate(str, 0); } else if (active_entry->utf8) { out[g_unichar_to_utf8(arr[i], out)] = '\0'; @@ -306,7 +318,14 @@ static void paste_send(void) } } - gui_entry_set_text(active_entry, str->str); + if (paste_was_bracketed_mode) { + /* the text before the bracket end should be sent along with the rest */ + paste_send_line(str->str); + gui_entry_set_text(active_entry, ""); + } else { + gui_entry_set_text(active_entry, str->str); + } + g_string_free(str, TRUE); } @@ -322,6 +341,12 @@ static void paste_flush(int send) paste_send(); g_array_set_size(paste_buffer, 0); + /* re-add anything that may have been after the bracketed paste end */ + if (paste_buffer_rest->len > 0) { + g_array_append_vals(paste_buffer, paste_buffer_rest->data, paste_buffer_rest->len); + g_array_set_size(paste_buffer_rest, 0); + } + gui_entry_set_prompt(active_entry, paste_old_prompt == NULL ? "" : paste_old_prompt); g_free(paste_old_prompt); paste_old_prompt = NULL; @@ -336,11 +361,24 @@ static void insert_paste_prompt(void) { char *str; + /* The actual number of lines that will show up post-line-split */ + int actual_line_count = paste_line_count; + int split_lines = paste_buffer->len / LINE_SPLIT_LIMIT; + + /* in case this prompt is happening due to line-splitting, calculate the + number of lines obtained from this. The number isn't entirely accurate; + we just choose the greater of the two since the exact value isn't + important */ + if (split_lines > paste_verify_line_count && + split_lines > paste_line_count) { + actual_line_count = split_lines; + } + paste_prompt = TRUE; paste_old_prompt = g_strdup(active_entry->prompt); printformat_window(active_win, MSGLEVEL_CLIENTNOTICE, TXT_PASTE_WARNING, - paste_line_count, + actual_line_count, active_win->active == NULL ? "window" : active_win->active->visible_name); @@ -392,9 +430,9 @@ static void sig_gui_key_pressed(gpointer keyp) str[g_unichar_to_utf8(key, str)] = '\0'; } - if (strcmp(str, "^") == 0) { - /* change it as ^^ */ - str[1] = '^'; + if (g_strcmp0(str, "^") == 0) { + /* change it as ^-, that is an invalid control char */ + str[1] = '-'; str[2] = '\0'; } @@ -403,11 +441,20 @@ static void sig_gui_key_pressed(gpointer keyp) gui_entry_insert_char(active_entry, key); ret = 1; } else { + previous_yank_preceded = active_entry->yank_preceded; + active_entry->yank_preceded = FALSE; + active_entry->previous_append_next_kill = active_entry->append_next_kill; + active_entry->append_next_kill = FALSE; ret = key_pressed(keyboard, str); if (ret < 0) { /* key wasn't used for anything, print it */ gui_entry_insert_char(active_entry, key); } + if (ret == 0) { + /* combo not complete, ignore append_next_kill and yank_preceded */ + active_entry->append_next_kill = active_entry->previous_append_next_kill; + active_entry->yank_preceded = previous_yank_preceded; + } } /* ret = 0 : some key create multiple characters - we're in the middle @@ -435,22 +482,21 @@ static void key_send_line(void) add_history = *str != '\0'; history = command_history_current(active_win); + if (redir != NULL && redir->flags & ENTRY_REDIRECT_FLAG_HIDDEN) + add_history = 0; + + if (add_history && history != NULL) { + command_history_add(history, str); + } + if (redir == NULL) { signal_emit("send command", 3, str, active_win->active_server, active_win->active); } else { - if (redir->flags & ENTRY_REDIRECT_FLAG_HIDDEN) - add_history = 0; handle_entry_redirect(str); } - if (add_history) { - history = command_history_find(history); - if (history != NULL) - command_history_add(history, str); - } - if (active_entry != NULL) gui_entry_set_text(active_entry, ""); command_history_clear_pos(active_win); @@ -527,7 +573,7 @@ static void key_forward_to_space(void) static void key_erase_line(void) { gui_entry_set_pos(active_entry, active_entry->text_len); - gui_entry_erase(active_entry, active_entry->text_len, TRUE); + gui_entry_erase(active_entry, active_entry->text_len, CUTBUFFER_UPDATE_REPLACE); } static void key_erase_to_beg_of_line(void) @@ -535,7 +581,7 @@ static void key_erase_to_beg_of_line(void) int pos; pos = gui_entry_get_pos(active_entry); - gui_entry_erase(active_entry, pos, TRUE); + gui_entry_erase(active_entry, pos, CUTBUFFER_UPDATE_PREPEND); } static void key_erase_to_end_of_line(void) @@ -544,7 +590,7 @@ static void key_erase_to_end_of_line(void) pos = gui_entry_get_pos(active_entry); gui_entry_set_pos(active_entry, active_entry->text_len); - gui_entry_erase(active_entry, active_entry->text_len - pos, TRUE); + gui_entry_erase(active_entry, active_entry->text_len - pos, CUTBUFFER_UPDATE_APPEND); } static void key_yank_from_cutbuffer(void) @@ -554,7 +600,32 @@ static void key_yank_from_cutbuffer(void) cutbuffer = gui_entry_get_cutbuffer(active_entry); if (cutbuffer != NULL) { gui_entry_insert_text(active_entry, cutbuffer); - g_free(cutbuffer); + active_entry->yank_preceded = TRUE; + g_free(cutbuffer); + } +} + +static void key_yank_next_cutbuffer(void) +{ + GUI_ENTRY_CUTBUFFER_REC *rec; + guint length = 0; + char *cutbuffer; + + if (!previous_yank_preceded) + return; + + if (active_entry->kill_ring == NULL) + return; + + rec = active_entry->kill_ring->data; + if (rec != NULL) length = rec->cutbuffer_len; + + cutbuffer = gui_entry_get_next_cutbuffer(active_entry); + if (cutbuffer != NULL) { + gui_entry_erase(active_entry, length, CUTBUFFER_UPDATE_NOOP); + gui_entry_insert_text(active_entry, cutbuffer); + active_entry->yank_preceded = TRUE; + g_free(cutbuffer); } } @@ -591,32 +662,44 @@ static void key_delete_character(void) static void key_backspace(void) { - gui_entry_erase(active_entry, 1, FALSE); + gui_entry_erase(active_entry, 1, CUTBUFFER_UPDATE_NOOP); } static void key_delete_previous_word(void) { - gui_entry_erase_word(active_entry, FALSE); + gui_entry_erase_word(active_entry, FALSE, CUTBUFFER_UPDATE_PREPEND); } static void key_delete_next_word(void) { - gui_entry_erase_next_word(active_entry, FALSE); + gui_entry_erase_next_word(active_entry, FALSE, CUTBUFFER_UPDATE_APPEND); } static void key_delete_to_previous_space(void) { - gui_entry_erase_word(active_entry, TRUE); + gui_entry_erase_word(active_entry, TRUE, CUTBUFFER_UPDATE_PREPEND); } static void key_delete_to_next_space(void) { - gui_entry_erase_next_word(active_entry, TRUE); + gui_entry_erase_next_word(active_entry, TRUE, CUTBUFFER_UPDATE_APPEND); +} + +static void key_append_next_kill(void) +{ + active_entry->append_next_kill = TRUE; } static gboolean paste_timeout(gpointer data) { - if (paste_line_count == 0) { + int split_lines; + paste_was_bracketed_mode = paste_bracketed_mode; + + /* number of lines after splitting extra-long messages */ + split_lines = paste_buffer->len / LINE_SPLIT_LIMIT; + + /* Take into account the fact that a line may be split every LINE_SPLIT_LIMIT characters */ + if (paste_line_count == 0 && split_lines <= paste_verify_line_count) { int i; for (i = 0; i < paste_buffer->len; i++) { @@ -625,8 +708,9 @@ static gboolean paste_timeout(gpointer data) } g_array_set_size(paste_buffer, 0); } else if (paste_verify_line_count > 0 && - paste_line_count >= paste_verify_line_count && - active_win->active != NULL) + (paste_line_count >= paste_verify_line_count || + split_lines > paste_verify_line_count) && + active_win->active != NULL) insert_paste_prompt(); else paste_flush(TRUE); @@ -634,6 +718,70 @@ static gboolean paste_timeout(gpointer data) return FALSE; } +static void paste_bracketed_end(int i, gboolean rest) +{ + unichar last_char; + + /* if there's stuff after the end bracket, save it for later */ + if (rest) { + unichar *start = ((unichar *) paste_buffer->data) + i + G_N_ELEMENTS(bp_end); + int len = paste_buffer->len - i - G_N_ELEMENTS(bp_end); + + g_array_set_size(paste_buffer_rest, 0); + g_array_append_vals(paste_buffer_rest, start, len); + } + + /* remove the rest, including the trailing sequence chars */ + g_array_set_size(paste_buffer, i); + + last_char = g_array_index(paste_buffer, unichar, i - 1); + + if (paste_line_count > 0 && last_char != '\n' && last_char != '\r') { + /* there are newlines, but there's also stuff after the newline + * adjust line count to reflect this */ + paste_line_count++; + } + + /* decide what to do with the buffer */ + paste_timeout(NULL); + + paste_bracketed_mode = FALSE; +} + +static void paste_bracketed_middle() +{ + int i; + int marklen = G_N_ELEMENTS(bp_end); + int len = paste_buffer->len - marklen; + unichar *ptr = (unichar *) paste_buffer->data; + + if (len < 0) { + return; + } + + for (i = 0; i <= len; i++, ptr++) { + if (ptr[0] == bp_end[0] && memcmp(ptr, bp_end, sizeof(bp_end)) == 0) { + + /* if there are at least 6 bytes after the end, + * check for another start marker right afterwards */ + if (i <= (len - marklen) && + memcmp(ptr + marklen, bp_start, sizeof(bp_start)) == 0) { + + /* remove both markers*/ + g_array_remove_range(paste_buffer, i, marklen * 2); + len -= marklen * 2; + + /* go one step back */ + i--; + ptr--; + continue; + } + paste_bracketed_end(i, i != len); + break; + } + } +} + static void sig_input(void) { if (!active_entry) { @@ -647,21 +795,37 @@ static void sig_input(void) unichar key; term_gets(buffer, &line_count); key = g_array_index(buffer, unichar, 0); + /* Either Ctrl-k or Ctrl-c is pressed */ if (key == 11 || key == 3) paste_flush(key == 11); g_array_free(buffer, TRUE); } else { term_gets(paste_buffer, &paste_line_count); - if (paste_detect_time > 0 && paste_buffer->len >= 3) { + + /* use the bracketed paste mode to detect when the user pastes + * some text into the entry */ + if (paste_bracketed_mode) { + paste_bracketed_middle(); + + } else if (!paste_use_bracketed_mode && paste_detect_time > 0 && paste_buffer->len >= 3) { if (paste_timeout_id != -1) g_source_remove(paste_timeout_id); paste_timeout_id = g_timeout_add(paste_detect_time, paste_timeout, NULL); - } else { + } else if (!paste_bracketed_mode) { int i; for (i = 0; i < paste_buffer->len; i++) { unichar key = g_array_index(paste_buffer, unichar, i); signal_emit("gui key pressed", 1, GINT_TO_POINTER(key)); + + if (paste_bracketed_mode) { + /* just enabled by the signal, remove what was processed so far */ + g_array_remove_range(paste_buffer, 0, i + 1); + + /* handle single-line / small pastes here */ + paste_bracketed_middle(); + return; + } } g_array_set_size(paste_buffer, 0); paste_line_count = 0; @@ -669,6 +833,11 @@ static void sig_input(void) } } +static void key_paste_start(void) +{ + paste_bracketed_mode = TRUE; +} + time_t get_idle_time(void) { return last_keypress.tv_sec; @@ -930,6 +1099,11 @@ static void setup_changed(void) paste_verify_line_count = settings_get_int("paste_verify_line_count"); paste_join_multiline = settings_get_bool("paste_join_multiline"); + paste_use_bracketed_mode = settings_get_bool("paste_use_bracketed_mode"); + + term_set_appkey_mode(settings_get_bool("term_appkey_mode")); + /* Enable the bracketed paste mode on demand */ + term_set_bracketed_paste_mode(paste_use_bracketed_mode); } void gui_readline_init(void) @@ -943,13 +1117,17 @@ void gui_readline_init(void) paste_entry = NULL; paste_entry_pos = 0; paste_buffer = g_array_new(FALSE, FALSE, sizeof(unichar)); + paste_buffer_rest = g_array_new(FALSE, FALSE, sizeof(unichar)); paste_old_prompt = NULL; paste_timeout_id = -1; + paste_bracketed_mode = FALSE; g_get_current_time(&last_keypress); input_listen_init(STDIN_FILENO); + settings_add_bool("lookandfeel", "term_appkey_mode", TRUE); settings_add_str("history", "scroll_page_count", "/2"); settings_add_time("misc", "paste_detect_time", "5msecs"); + settings_add_bool("misc", "paste_use_bracketed_mode", FALSE); /* NOTE: function keys can generate at least 5 characters long keycodes. this must be larger to allow them to work. */ settings_add_int("misc", "paste_verify_line_count", 5); @@ -1020,6 +1198,10 @@ void gui_readline_init(void) key_bind("key", NULL, "meta2-5F", "cend", (SIGNAL_FUNC) key_combo); key_bind("key", NULL, "meta2-1;5F", "cend", (SIGNAL_FUNC) key_combo); + key_bind("key", NULL, "meta-O-M", "return", (SIGNAL_FUNC) key_combo); + + key_bind("paste_start", "Bracketed paste start", "meta2-200~", "paste_start", (SIGNAL_FUNC) key_paste_start); + /* cursor movement */ key_bind("backward_character", "Move the cursor a character backward", "left", NULL, (SIGNAL_FUNC) key_backward_character); key_bind("forward_character", "Move the cursor a character forward", "right", NULL, (SIGNAL_FUNC) key_forward_character); @@ -1050,6 +1232,8 @@ void gui_readline_init(void) key_bind("erase_to_beg_of_line", "Erase everything before the cursor", NULL, NULL, (SIGNAL_FUNC) key_erase_to_beg_of_line); key_bind("erase_to_end_of_line", "Erase everything after the cursor", "^K", NULL, (SIGNAL_FUNC) key_erase_to_end_of_line); key_bind("yank_from_cutbuffer", "\"Undelete\", paste the last deleted text", "^Y", NULL, (SIGNAL_FUNC) key_yank_from_cutbuffer); + key_bind("yank_next_cutbuffer", "Revert to the previous last deleted text", NULL, NULL, (SIGNAL_FUNC) key_yank_next_cutbuffer); + key_bind("append_next_kill", "Append next deletion", NULL, NULL, (SIGNAL_FUNC) key_append_next_kill); key_bind("transpose_characters", "Swap current and previous character", "^T", NULL, (SIGNAL_FUNC) key_transpose_characters); key_bind("transpose_words", "Swap current and previous word", NULL, NULL, (SIGNAL_FUNC) key_transpose_words); key_bind("capitalize_word", "Capitalize the current word", NULL, NULL, (SIGNAL_FUNC) key_capitalize_word); @@ -1115,6 +1299,8 @@ void gui_readline_deinit(void) key_configure_freeze(); + key_unbind("paste_start", (SIGNAL_FUNC) key_paste_start); + key_unbind("backward_character", (SIGNAL_FUNC) key_backward_character); key_unbind("forward_character", (SIGNAL_FUNC) key_forward_character); key_unbind("backward_word", (SIGNAL_FUNC) key_backward_word); @@ -1137,6 +1323,8 @@ void gui_readline_deinit(void) key_unbind("erase_to_beg_of_line", (SIGNAL_FUNC) key_erase_to_beg_of_line); key_unbind("erase_to_end_of_line", (SIGNAL_FUNC) key_erase_to_end_of_line); key_unbind("yank_from_cutbuffer", (SIGNAL_FUNC) key_yank_from_cutbuffer); + key_unbind("yank_next_cutbuffer", (SIGNAL_FUNC) key_yank_next_cutbuffer); + key_unbind("append_next_kill", (SIGNAL_FUNC) key_append_next_kill); key_unbind("transpose_characters", (SIGNAL_FUNC) key_transpose_characters); key_unbind("transpose_words", (SIGNAL_FUNC) key_transpose_words); @@ -1172,6 +1360,7 @@ void gui_readline_deinit(void) key_unbind("stop_irc", (SIGNAL_FUNC) key_sig_stop); keyboard_destroy(keyboard); g_array_free(paste_buffer, TRUE); + g_array_free(paste_buffer_rest, TRUE); key_configure_thaw(); |