summaryrefslogtreecommitdiffstats
path: root/linenoise/linenoise.c
diff options
context:
space:
mode:
authorKaz Kylheku <kaz@kylheku.com>2015-09-20 11:49:29 -0700
committerKaz Kylheku <kaz@kylheku.com>2015-09-20 11:49:29 -0700
commit3df312a334ad3b2dbd146e5fb8b2bd2f9e086165 (patch)
treeac46bc4ed4a01828b0ada53be1f5aaa0b24df2e0 /linenoise/linenoise.c
parent24193f782a02939d6f4c7a197b44beb97202e1a0 (diff)
downloadtxr-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.
Diffstat (limited to 'linenoise/linenoise.c')
-rw-r--r--linenoise/linenoise.c270
1 files changed, 232 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)