diff options
author | Kaz Kylheku <kaz@kylheku.com> | 2015-09-20 11:49:29 -0700 |
---|---|---|
committer | Kaz Kylheku <kaz@kylheku.com> | 2015-09-20 11:49:29 -0700 |
commit | 3df312a334ad3b2dbd146e5fb8b2bd2f9e086165 (patch) | |
tree | ac46bc4ed4a01828b0ada53be1f5aaa0b24df2e0 | |
parent | 24193f782a02939d6f4c7a197b44beb97202e1a0 (diff) | |
download | txr-3df312a334ad3b2dbd146e5fb8b2bd2f9e086165.tar.gz txr-3df312a334ad3b2dbd146e5fb8b2bd2f9e086165.tar.bz2 txr-3df312a334ad3b2dbd146e5fb8b2bd2f9e086165.zip |
linenoise: visual select and clipboard copy/paste.
* linenoise/linenoise.c (struc lino_state): New member,
clip, sel, end, dsel, dend, need_refresh, selmode.
(sync_data_to_buf): Update the sel and end members
of the structure based on dsel and dend, the way
pos is being updated from dpos.
(refresh_singleline, refresh_multiline): If visual selection
mode is in effect, show the selected region in inverse video.
(update_sel, clear_sel, yank_sel, delete_sel):
New static function.
(edit_insert): Delete the selection before inserting, so that
the character appears to replace the selection.
Set need_refresh flag instead of calling refresh_line.
(edit_insert_str): New static function. Inserts string,
replacing existing selection, if any.
(paren_jump, edit_move_left, edit_move_right, edit_move_home,
edit_move_end, edit_history_next): Set new
need_refresh flag instead of calling refresh_line directly.
(edit_delete): If selection is in effect, just delete the
selection and return. Set need_refresh flag instead
of calling refresh_line.
(edit_backspace): If selection is in effect, and selection
is not inverted (cursor is to the right of selection)
then just delete the selection. Otherwise delete the
selection, and perform the backspace. Set need_refresh flag
instead of calling refresh_line.
(edit_delete_prev_word): Delete the selection and the word
before the selection. Set need_refresh flag
instead of calling refresh_line.
(edit_in_editor): Set need_refresh_flag instead of
calling refresh_line, and cancel visual selection mode.
(edit): Clear selection mode on entry. Update the selection
variables on each loop iteration. Honor the need_refresh
flag. New commands implemented: Ctrl-S, Ctrl-Q, Ctrl-X Ctrl-Q.
Some commands need to set need_refresh flag.
Some need to cancel selection mode.
(lino_copy): Set the clip member of the cloned structure
to null, otherwise there will be a double free of the
clipboard buffer.
(lino_cleanup): Free the clipboard and null out the pointer.
* txr.1: Documented visual select.
-rw-r--r-- | linenoise/linenoise.c | 270 | ||||
-rw-r--r-- | txr.1 | 101 |
2 files changed, 333 insertions, 38 deletions
diff --git a/linenoise/linenoise.c b/linenoise/linenoise.c index baaf4c6e..47b867f8 100644 --- a/linenoise/linenoise.c +++ b/linenoise/linenoise.c @@ -83,6 +83,7 @@ struct lino_state { int history_max_len; int history_len; char **history; + char *clip; /* Selection */ int ifd; /* Terminal stdin file descriptor. */ int ofd; /* Terminal stdout file descriptor. */ @@ -93,14 +94,20 @@ struct lino_state { const char *suffix; /* Suffix when creating temp file. */ size_t plen; /* Prompt length. */ size_t pos; /* Current cursor position. */ + size_t sel; /* Selection start in terms of display. */ + size_t end; /* Selection end in terms of display. */ size_t len; /* Current edited line display length. */ size_t dlen; /* True underlying length. */ size_t dpos; /* True underlying position. */ + size_t dsel; /* Start of selection */ + size_t dend; /* End of selection */ size_t cols; /* Number of columns in terminal. */ size_t oldrow; /* Row of previous cursor position (multiline mode) */ size_t maxrows; /* Maximum num of rows used so far (multiline mode) */ int history_index; /* The history index we are currently editing. */ int need_resize; /* Need resize flag. */ + int need_refresh; /* Need refresh. */ + int selmode; /* Visual selection being made. */ lino_error_t error; /* Most recent error. */ }; @@ -603,8 +610,15 @@ static void sync_data_to_buf(lino_t *l) l->prompt); while (bptr - l->buf < sizeof l->buf - 1) { - if (dptr - l->data == (ptrdiff_t) l->dpos) - l->pos = bptr - l->buf; + size_t dpos = dptr - l->data; + size_t pos = bptr - l->buf; + + if (l->dpos == dpos) + l->pos = pos; + if (l->dsel == dpos) + l->sel = pos; + if (l->dend == dpos) + l->end = pos; if (*dptr) { char ch = *dptr++; @@ -649,15 +663,31 @@ static void refresh_singleline(lino_t *l) { size_t len = l->len; size_t pos = l->pos; struct abuf ab; + size_t sel = l->sel; + size_t end = l->end; + + if (sel > end) { + size_t tmp = end; + end = sel; + sel = tmp; + } while((plen+pos) >= l->cols) { buf++; len--; pos--; + if (end > 0) + end--; + if (sel > 0) + sel--; } while (plen+len > l->cols) { len--; } + if (end > len) + end = len; + if (sel > len) + sel = len; ab_init(&ab); /* Cursor to left edge */ @@ -665,7 +695,20 @@ static void refresh_singleline(lino_t *l) { ab_append(&ab,seq,strlen(seq)); /* Write the prompt and the current buffer content */ ab_append(&ab,l->prompt,strlen(l->prompt)); - ab_append(&ab,buf,len); + if (!l->selmode) { + ab_append(&ab, buf, len); + } else { + if (sel > 0) + ab_append(&ab, buf, sel); + if (end - sel > 0) { + ab_append(&ab, "\x1b[7m", 4); + ab_append(&ab, buf + sel, end - sel); + ab_append(&ab, "\x1b[m", 3); + } + if (len - end > 0) + ab_append(&ab, buf + end, len - end); + } + /* Erase to right */ snprintf(seq,64,"\x1b[0K"); ab_append(&ab,seq,strlen(seq)); @@ -750,7 +793,29 @@ static void refresh_multiline(lino_t *l) { ab_append(&ab,seq,strlen(seq)); /* Write the current buffer content which includes the prompt */ - ab_append(&ab,l->buf,l->len); + if (!l->selmode) { + ab_append(&ab,l->buf,l->len); + } else { + size_t sel = l->sel; + size_t end = l->end; + size_t len = l->len; + + if (sel > end) { + size_t tmp = end; + end = sel; + sel = tmp; + } + + if (sel > 0) + ab_append(&ab, l->buf, sel); + if (end - sel > 0) { + ab_append(&ab, "\x1b[7m", 4); + ab_append(&ab, l->buf + sel, end - sel); + ab_append(&ab, "\x1b[m", 3); + } + if (len - end > 0) + ab_append(&ab, l->buf + end, len - end); + } /* If we are at the very end of the screen with our prompt, we need to * emit a newline and move the cursor to the first column. */ @@ -876,27 +941,83 @@ static void paren_jump(lino_t *l) refresh_line(l); usec_delay(l, LINENOISE_PAREN_DELAY); l->dpos = dp; - refresh_line(l); + l->need_refresh = 1; + } +} + +static void update_sel(lino_t *l) +{ + if (l->selmode) { + size_t oend = l->dend; + l->dend = l->dpos; + l->need_refresh |= (oend != l->dend); } } +static void clear_sel(lino_t *l) +{ + if (l->selmode) { + l->selmode = 0; + l->dsel = l->dend = 0; + l->need_refresh = 1; + } +} + +static void yank_sel(lino_t *l) +{ + if (l->selmode) { + int notrev = l->dsel <= l->dend; + size_t sel = notrev ? l->dsel : l->dend; + size_t end = notrev ? l->dend : l->dsel; + + if (end - sel > 0) { + free(l->clip); + l->clip = (char *) chk_malloc(end - sel + 1); + memcpy(l->clip, l->data + sel, end - sel); + l->clip[end - sel] = 0; + l->dpos = sel; + } + } +} + +static void delete_sel(lino_t *l) +{ + if (l->selmode) { + int notrev = l->dsel <= l->dend; + size_t sel = notrev ? l->dsel : l->dend; + size_t end = notrev ? l->dend : l->dsel; + size_t len = l->dlen; + + if (len - end > 0) + memmove(l->data + sel, l->data + end, len - end); + + len -= (end - sel); + l->data[len] = 0; + l->dlen = len; + l->dpos = sel; + + clear_sel(l); + } +} + + /* Insert the character 'c' at cursor current position. * * On error writing to the terminal -1 is returned, otherwise 0. */ static int edit_insert(lino_t *l, char c) { if (l->dlen < sizeof l->data - 1) { - if (l->len == l->dpos) { + delete_sel(l); + if (l->dpos == l->dlen) { l->data[l->dpos] = c; l->dpos++; l->dlen++; l->data[l->dlen] = '\0'; - sync_data_to_buf(l); if ((!l->mlmode && l->len == l->dlen && l->plen+l->len < l->cols) /* || mlmode */) { /* Avoid a full update of the line in the * trivial case. */ if (write(l->ofd,&c,1) == -1) return -1; } else { - refresh_line(l); + l->need_refresh = 1; } } else { memmove(l->data + l->dpos+1, l->data + l->dpos, l->dlen-l->dpos); @@ -904,17 +1025,34 @@ static int edit_insert(lino_t *l, char c) { l->dlen++; l->dpos++; l->data[l->dlen] = '\0'; - refresh_line(l); + l->need_refresh = 1; } } return 0; } +static int edit_insert_str(lino_t *l, const char *s, size_t nchar) +{ + if (l->dlen < sizeof l->data - nchar) { + delete_sel(l); + + if (l->dpos < l->dlen) + memmove(l->data + l->dpos + nchar, l->data + l->dpos, l->dlen - l->dpos); + memcpy(l->data + l->dpos, s, nchar); + l->dpos += nchar; + l->dlen += nchar; + l->data[l->dlen] = 0; + l->need_refresh = 1; + clear_sel(l); + } + return 0; +} + /* Move cursor on the left. */ static void edit_move_left(lino_t *l) { if (l->dpos > 0) { l->dpos--; - refresh_line(l); + l->need_refresh = 1; } } @@ -922,7 +1060,7 @@ static void edit_move_left(lino_t *l) { static void edit_move_right(lino_t *l) { if (l->dpos != l->dlen) { l->dpos++; - refresh_line(l); + l->need_refresh = 1; } } @@ -930,7 +1068,7 @@ static void edit_move_right(lino_t *l) { static void edit_move_home(lino_t *l) { if (l->dpos != 0) { l->dpos = 0; - refresh_line(l); + l->need_refresh = 1; } } @@ -938,7 +1076,7 @@ static void edit_move_home(lino_t *l) { static void edit_move_end(lino_t *l) { if (l->dpos != l->dlen) { l->dpos = l->dlen; - refresh_line(l); + l->need_refresh = 1; } } @@ -947,6 +1085,7 @@ static void edit_move_end(lino_t *l) { #define LINENOISE_HISTORY_NEXT 0 #define LINENOISE_HISTORY_PREV 1 static void edit_history_next(lino_t *l, int dir) { + clear_sel(l); if (l->history_len > 1) { /* Update the current history entry before to * overwrite it with the next one. */ @@ -964,47 +1103,63 @@ static void edit_history_next(lino_t *l, int dir) { strncpy(l->data,l->history[l->history_len - 1 - l->history_index], sizeof l->data); l->data[sizeof l->data - 1] = 0; l->dpos = l->dlen = strlen(l->data); - refresh_line(l); + l->need_refresh = 1; } } /* Delete the character at the right of the cursor without altering the cursor * position. Basically this is what happens with the "Delete" keyboard key. */ static void edit_delete(lino_t *l) { + if (l->selmode) { + delete_sel(l); + return; + } + if (l->dlen > 0 && l->dpos < l->dlen) { memmove(l->data + l->dpos, l->data + l->dpos + 1, l->dlen - l->dpos - 1); l->dlen--; l->data[l->dlen] = '\0'; - refresh_line(l); + l->need_refresh = 1; } } /* Backspace implementation. */ static void edit_backspace(lino_t *l) { + if (l->selmode && l->dend > l->dsel) { + delete_sel(l); + return; + } + + delete_sel(l); + if (l->dpos > 0 && l->dlen > 0) { memmove(l->data + l->dpos - 1, l->data + l->dpos, l->dlen - l->dpos); l->dpos--; l->dlen--; l->data[l->dlen] = '\0'; - refresh_line(l); + l->need_refresh = 1; } } /* Delete all characters to left of cursor. */ static void edit_delete_prev_all(lino_t *l) { + delete_sel(l); + memmove(l->data, l->data + l->dpos, l->dlen - l->dpos + 1); l->dlen -= l->dpos; l->dpos = 0; - refresh_line(l); + l->need_refresh = 1; } /* Delete the previosu word, maintaining the cursor at the start of the * current word. */ static void edit_delete_prev_word(lino_t *l) { - size_t odpos = l->dpos; - size_t diff; + size_t odpos, diff; + delete_sel(l); + + odpos = l->dpos; while (l->dpos > 0 && strchr(SPACE, l->data[l->dpos - 1])) l->dpos--; while (l->dpos > 0 && strchr(SPACE, l->data[l->dpos - 1]) == 0) @@ -1012,7 +1167,7 @@ static void edit_delete_prev_word(lino_t *l) { diff = odpos - l->dpos; memmove(l->data + l->dpos, l->data + odpos, l->dlen - odpos + 1); l->dlen -= diff; - refresh_line(l); + l->need_refresh = 1; } static void tr(char *s, int find, int rep) @@ -1072,12 +1227,13 @@ static void edit_in_editor(lino_t *l) { l->data[--nread] = 0; l->dpos = l->dlen = nread; tr(l->data, '\n', '\r'); - refresh_line(l); + l->need_refresh = 1; } } fclose(fo); remove(path); + clear_sel(l); } } @@ -1102,6 +1258,7 @@ static int edit(lino_t *l, const char *prompt) l->cols = get_columns(l->ifd, l->ofd); l->oldrow = l->maxrows = 0; l->history_index = 0; + clear_sel(l); /* Buffer starts empty. */ l->data[0] = '\0'; @@ -1120,6 +1277,13 @@ static int edit(lino_t *l, const char *prompt) int nread; char seq[3]; + update_sel(l); + + if (l->need_refresh) { + l->need_refresh = 0; + refresh_line(l); + } + nread = read(l->ifd,&byte,1); if (nread < 0 && errno == EINTR) { @@ -1181,12 +1345,10 @@ static int edit(lino_t *l, const char *prompt) break; } - while (word_start < word_end) - if (edit_insert(l, *word_start++)) { - l->error = lino_ioerr; - return -1; - } - + if (edit_insert_str(l, word_start, word_end - word_start)) { + l->error = lino_ioerr; + return -1; + } } break; case CTL('A'): case 'a': @@ -1198,14 +1360,10 @@ static int edit(lino_t *l, const char *prompt) char *prev_line = l->history[l->history_len - 2]; char *word = l->atom_callback(l, prev_line, extend_num, l->ca_ctx); - const char *p = word; int res = 0; if (word != 0) { - while (*p) - if ((res = edit_insert(l, *p++)) != 0) - break; - + res = edit_insert_str(l, word, strlen(word)); free(word); } @@ -1215,6 +1373,19 @@ static int edit(lino_t *l, const char *prompt) } } break; + case CTL('Q'): + extended = 0; + { + char *clip = l->clip; + l->clip = 0; + yank_sel(l); + if (clip != 0) { + edit_insert_str(l, clip, strlen(clip)); + free(clip); + } + clear_sel(l); + } + break; default: if (isdigit((unsigned char) c)) { if (extend_num < 0) @@ -1237,10 +1408,13 @@ static int edit(lino_t *l, const char *prompt) * character that should be handled next. */ switch (c) { case TAB: - if (l->completion_callback != NULL) + if (l->completion_callback != NULL) { + clear_sel(l); c = complete_line(l); + } break; case CTL('R'): + clear_sel(l); c = history_search(l); break; } @@ -1265,6 +1439,8 @@ static int edit(lino_t *l, const char *prompt) l->history[l->history_len] = 0; } if (l->mlmode) edit_move_end(l); + if (l->need_refresh) + refresh_line(l); return (int)l->len; case CTL('C'): l->error = lino_intr; @@ -1276,6 +1452,7 @@ static int edit(lino_t *l, const char *prompt) case CTL('D'): /* remove char at right of cursor, or if the line is empty, act as end-of-file. */ if (l->dlen > 0) { + yank_sel(l); edit_delete(l); } else { if (l->history_len > 0) { @@ -1293,7 +1470,7 @@ static int edit(lino_t *l, const char *prompt) l->data[l->dpos-1] = l->data[l->dpos]; l->data[l->dpos] = aux; if (l->dpos != l->dlen - 1) l->dpos++; - refresh_line(l); + l->need_refresh = 1; } break; case CTL('B'): @@ -1388,10 +1565,24 @@ static int edit(lino_t *l, const char *prompt) extended = 1; extend_num = -1; continue; + case CTL('S'): + l->selmode ^= 1; + if (l->selmode) + l->dsel = l->dend = l->dpos; + l->need_refresh = 1; + break; + case CTL('Y'): + yank_sel(l); + clear_sel(l); + break; + case CTL('Q'): + if (l->clip != 0) + edit_insert_str(l, l->clip, strlen(l->clip)); + break; case CTL('K'): /* delete from current to end of line. */ l->data[l->dpos] = '\0'; l->dlen = l->dpos; - refresh_line(l); + l->need_refresh = 1; break; case CTL('A'): edit_move_home(l); @@ -1401,7 +1592,7 @@ static int edit(lino_t *l, const char *prompt) break; case CTL('L'): lino_clear_screen(l); - refresh_line(l); + l->need_refresh = 1; break; case CTL('W'): edit_delete_prev_word(l); @@ -1414,13 +1605,13 @@ static int edit(lino_t *l, const char *prompt) } l->mlmode ^= 1; - refresh_line(l); + l->need_refresh = 1; break; case CTL('Z'): disable_raw_mode(l); raise(SIGTSTP); enable_raw_mode(l); - refresh_line(l); + l->need_refresh = 1; break; } } @@ -1547,6 +1738,7 @@ lino_t *lino_copy(lino_t *le) ls->history_len = 0; ls->history = 0; ls->rawmode = 0; + ls->clip = 0; link_into_list(&lino_list, ls); } @@ -1561,6 +1753,8 @@ static void lino_cleanup(lino_t *ls) { disable_raw_mode(ls); free_hist(ls); + free(ls->clip); + ls->clip = 0; } void lino_free(lino_t *ls) @@ -33821,6 +33821,107 @@ when written out to the file. Conversely, when the edited file is read back, its newlines are converted to carriage returns, so that multi-line content is handled properly. (See the following section, Multi-Line Mode). +.SS* Visual Selection Mode + +The interactive listener supports visual copy and paste operation. +Text may be visually selected for copying into a clipboard (copy) +or deletion. In visual selection mode, the actions of some editing +commands are modified so that they act upon the selection instead +of their usual target, or upon both the target and the selection. + +.NP* Making a Selection + +The Ctrl-S command enters into visual selection mode and marks the +starting point of the selection, which is considered the position +immediately to the left of the current character. + +While in visual selection mode, it is possible to move around using +the usual movement commands. The ending point of the selection +tracks the movement. The ending point of the selection is also +the position immediately to the left of the current character. +Thus the selection excludes the rightmost character. The selection +consists of the text between these two positions, whether or not +they are reversed. The selected text is displayed in reverse video. + +Typing Ctrl-S again while in visual selection mode cancels +the mode. + +Tab completion, history navigation, history search and editing in an external +editor all cancel visual selection mode. + +.NP* Visual Copy + +The Ctrl-Y command ("yank") copies the selected text into a clipboard buffer. +The previous contents of the clipboard buffer, if any, are discarded. + +Unlike the history, the clipboard buffer is not persisted. +If \*(TX terminates, it is lost. + +.NP* Visual Cut + +If the Ctrl-D command is invoked while a selection is in effect, then +instead of deleting the character under the cursor, it deletes the +selection, and copies it to the clipboard. + +.NP* Clipboard Paste + +The Ctrl-Q command ("quote the clipboard") inserts text from the clipboard +at the current cursor position. The cursor position is updated to +be immediately after the inserted text. The clipboard text remains available +for further pasting. + +If nothing has been yet been copied to the clipboard in the current +session, then this command has no effect. + +.NP* Clipboard Swap Paste + +The Ctrl-X, Ctrl-Q command sequence ("exchange quote") exchanges the +selected text with the contents of the clipboard. The selection is +copied into the clipboard as if by Ctrl-Y and replaced by the +previous contents of the clipboard. + +If the clipboard has not yet been used in the current session, + +If nothing has been yet been copied to the clipboard in the current +session, then this command behaves like Ctrl-Y: +text is yanked into the clipboard, but not deleted. + +.NP* Visual Replace + +In visual selection mode, an editing commands may be used which insert new +text, or a character may be typed in order to insert it. When this happens, the +selection is first deleted and visual mode is canceled. Then the insertion +takes place and visual mode is canceled. The effect is that the newly inserted +text replaces the selected text. + +This applies to the Clipboard Paste (Ctrl-Q) command also. If a +selection is effect when Ctrl-Q is invoked, the selected text +is replaced with the clipboard buffer contents. + +When a selection is replaced, nothing is copied to the clipboard. + +.NP* Delete in Selection Mode + +In visual mode, it is possible to issue commands which delete text. +Ctrl-D has special behavior, Visual Cut, described above. + +The Backspace key and Ctrl-H also have a special behavior in select mode. If +the cursor is at the rightmost endpoint of the selection, then these commands +delete the selection and nothing else. If the cursor is at the leftmost +endpoint of the selection, then these commands delete the selection, and take +their usual effect of deleting a character also. In both cases, selection mode +is canceled. + +The Ctrl-W command to delete the previous word, wen used in visual +selection mode, deletes the selection and cancels selection mode, +and then deletes the word before the selection. + +All other deletion commands such as Ctrl-K simply cancel visual +selection mode and take their usual effect. + +Nothing is copied to the clipboard when deletion commands are used while a +selection is in effect. + .SS* Multi-Line Mode The listener operates in one of two modes: line mode and multi-line mode. |