radare2/libr/cons/dietline.c
Riccardo Schirone 72ffe1bd0f cons/dietline: show the same number of items per line
It fixes a bug when showing options for autocompletion, where the first
row has less items than the others. With this patch every line, except
the last, should have the same number of elements.
2016-02-13 09:23:17 +01:00

1365 lines
34 KiB
C

/* radare - LGPL - Copyright 2007-2016 - pancake */
/* dietline is a lightweight and portable library similar to GNU readline */
#include <r_cons.h>
#include <r_core.h>
#include <string.h>
#include <stdlib.h>
#if __WINDOWS__ && !__CYGWIN__
#include <windows.h>
#define USE_UTF8 0
#else
#include <sys/ioctl.h>
#include <termios.h>
#include <signal.h>
#define USE_UTF8 1
#endif
static char *r_line_nullstr = "";
#define ONLY_VALID_CHARS 1
#if ONLY_VALID_CHARS
static inline int is_valid_char (unsigned char ch) {
if (ch>=32 && ch<=127) return true;
switch (ch) {
//case 0: // wat
case 1: // ^a
case 2: // ^b -> emacs left
case 4: // ^d
case 5: // ^e
case 6: // ^f -> emacs right
case 8: // backspace
case 9: // tab
case 10: // newline
case 13: // carriage return
case 23: // ^w
case 27: // arrow
return true;
}
return false;
}
#endif
static int inithist() {
ZERO_FILL (I.history);
if ((I.history.size + 1024) * sizeof(char *) < I.history.size)
return false;
I.history.data = (char **)calloc ((I.history.size + 1024), sizeof(char *));
if (!I.history.data)
return false;
I.history.size = R_LINE_HISTSIZE;
return true;
}
/* initialize history stuff */
R_API int r_line_dietline_init() {
ZERO_FILL (I.completion);
if (!inithist ())
return false;
I.echo = true;
return true;
}
#if USE_UTF8
/* read utf8 char into 's', return the length in bytes */
static int r_line_readchar_utf8(unsigned char *s, int slen) {
// TODO: add support for w32
int ret, len;
for (len = 0; len+2<slen; len++) {
s[len] = 0;
ret = read (0, s+len, 1);
if (ret!=1)
return 0;
s[len] = r_cons_controlz (s[len]);
if (!s[len]) return 1; // ^z
if (s[len] < 28)
return s[0]?1:0;
if (is_valid_char (s[len]))
return s[0]?1:0;
if ((s[len] & 0xc0) != 0x80) continue;
if (len>0) break;
}
len++;
s[len] = 0;
return len;
}
#endif
#if __WINDOWS__ && !__CYGWIN__
static int r_line_readchar_win(int * vch) { // this function handle the input in console mode
INPUT_RECORD irInBuf[128];
BOOL ret, bCtrl = FALSE;
DWORD mode, out;
ut8 buf[2];
HANDLE h;
int i;
if (I.zerosep) {
*vch = 0;
buf[0] = 0;
read (0, buf, 1);
return buf[0];
}
*buf = '\0';
do_it_again:
h = GetStdHandle (STD_INPUT_HANDLE);
GetConsoleMode (h, &mode);
SetConsoleMode (h, 0); // RAW
*vch = 0;
ret = ReadConsoleInput (h, irInBuf, 128, &out);
if (ret<1) {
return 0;
}
for (i = 0; i < out; i++) {
if (irInBuf[i].EventType!=KEY_EVENT)
continue;
if (!irInBuf[i].Event.KeyEvent.bKeyDown)
continue;
*buf = irInBuf[i].Event.KeyEvent.uChar.AsciiChar;
bCtrl = irInBuf[i].Event.KeyEvent.dwControlKeyState & 8;
if (irInBuf[i].Event.KeyEvent.uChar.AsciiChar) {
continue;
}
switch (irInBuf[i].Event.KeyEvent.wVirtualKeyCode) {
case VK_DOWN: *vch = bCtrl? 140: 40; break;
case VK_UP: *vch = bCtrl? 138: 38; break;
case VK_RIGHT: *vch = bCtrl? 139: 39; break;
case VK_LEFT: *vch = bCtrl? 137:37; break;
case 46: *vch = bCtrl? 146: 46; break; // SUPR KEY
case VK_PRIOR: *vch = bCtrl? 136:36; break; // HOME KEY
case VK_NEXT: *vch = bCtrl? 135: 35; break; // END KEY
default: *vch = *buf = 0; break;
}
}
SetConsoleMode (h, mode);
if (buf[0]==0 && *vch==0)
goto do_it_again;
return buf[0];
}
#endif
static int r_line_readchar() {
ut8 buf[2];
*buf = '\0';
#if __WINDOWS__ && !__CYGWIN__
#if 1 // new implementation for read input at windows by skuater. If something fail set this to 0
int dummy=0;
return r_line_readchar_win(&dummy);
#endif
BOOL ret;
DWORD mode, out;
HANDLE h;
#else
int ret;
#endif
do_it_again:
#if __WINDOWS__ && !__CYGWIN__
h = GetStdHandle (STD_INPUT_HANDLE);
GetConsoleMode (h, &mode);
SetConsoleMode (h, 0); // RAW
ret = ReadConsole (h, buf, 1, &out, NULL);
// wine hack-around
if (!ret && read (0, buf, 1) != 1)
return -1;
SetConsoleMode (h, mode);
#else
do {
buf[0] = 0;
ret = read (0, buf, 1);
buf[0] = r_cons_controlz (buf[0]);
// VTE HOME/END support
if (buf[0]==79) {
if (read (0, buf, 1) != 1)
return -1;
if (buf[0]==70) {
return 5;
} else if (buf[0]==72) {
return 1;
}
return 0;
}
if (ret == -1) return 0; // read no char
if (!buf[0] || ret == 0) return -1; // eof
// TODO: add support for other invalid chars
if (*buf==0xc2 || *buf==0xc3) {
read (0, buf+1, 1);
*buf = '\0';
}
} while (*buf == '\0');
#endif
#if ONLY_VALID_CHARS
if (!is_valid_char (buf[0]))
goto do_it_again;
#endif
return buf[0];
}
R_API int r_line_hist_add(const char *line) {
if (!I.history.data)
inithist ();
if (I.history.top >= I.history.size)
I.history.top = I.history.index = 0; // workaround
/* ignore dup */
if (I.history.index > 0) {
const char *data = I.history.data[I.history.index - 1];
if (data && !strcmp (line, data))
return false;
}
if (line && *line) { // && I.history.index < I.history.size) {
I.history.data[I.history.top++] = strdup (line);
I.history.index = I.history.top;
return true;
}
return false;
}
static int r_line_hist_up() {
if (I.hist_up)
return I.hist_up (I.user);
if (!I.history.data)
inithist ();
if (I.history.index>0) {
strncpy (I.buffer.data, I.history.data[--I.history.index], R_LINE_BUFSIZE-1);
I.buffer.index = I.buffer.length = strlen (I.buffer.data);
return true;
}
return false;
}
static int r_line_hist_down() {
if (I.hist_down)
return I.hist_down (I.user);
I.buffer.index = 0;
if (!I.history.data)
inithist ();
if (I.history.index<I.history.size
&& I.history.data[I.history.index]) {
I.history.index++;
if (I.history.data[I.history.index] == NULL) {
I.buffer.data[0]='\0';
I.buffer.index = I.buffer.length = 0;
return 0;
}
if (I.history.data[I.history.index]) {
strncpy (I.buffer.data, I.history.data[I.history.index], R_LINE_BUFSIZE-1);
I.buffer.index = I.buffer.length = strlen (I.buffer.data);
}
return true;
}
return false;
}
R_API const char *r_line_hist_get(int n) {
int i = 0;
if (!I.history.data)
inithist ();
if (I.history.data != NULL)
for (i=0; i<I.history.size && I.history.data[i]; i++)
if (n==i) return I.history.data[i];
return NULL;
}
R_API int r_line_hist_list() {
int i = 0;
if (!I.history.data)
inithist ();
if (I.history.data != NULL)
for (i=0; i<I.history.size && I.history.data[i]; i++)
printf (" !%d # %s\n", i, I.history.data[i]);
return i;
}
R_API void r_line_hist_free() {
int i;
if (I.history.data != NULL)
for (i=0; i<I.history.size; i++) {
free (I.history.data[i]);
I.history.data[i] = NULL;
}
R_FREE (I.history.data);
I.history.index = 0;
}
/* load history from file. TODO: if file == NULL load from ~/.<prg>.history or so */
R_API int r_line_hist_load(const char *file) {
FILE *fd;
char buf[R_LINE_BUFSIZE],
*path = r_str_home (file);
if (path == NULL)
return false;
if (!(fd = fopen (path, "r"))) {
free (path);
return false;
}
while (fgets (buf, sizeof (buf), fd) != NULL) {
buf[strlen (buf)-1] = 0;
r_line_hist_add (buf);
}
fclose (fd);
free (path);
return true;
}
R_API int r_line_hist_save(const char *file) {
FILE *fd;
int i, ret = false;
char *p, *path = r_str_home (file);
if (path != NULL) {
p = (char*)r_str_lastbut (path, R_SYS_DIR[0], NULL); // TODO: use fs
if (p) {
*p = 0;
r_sys_mkdirp (path);
*p = R_SYS_DIR[0];
}
fd = fopen (path, "w");
if (fd != NULL) {
if (I.history.data) {
for (i=0; i<I.history.index; i++) {
fputs (I.history.data[i], fd);
fputs ("\n", fd);
}
fclose (fd);
ret = true;
} else fclose (fd);
}
}
free (path);
return ret;
}
R_API int r_line_hist_chop(const char *file, int limit) {
/* TODO */
return 0;
}
R_API void r_line_autocomplete() {
int argc = 0;
char *p;
const char **argv = NULL;
int i, j, opt = 0, plen, len = 0;
int cols = r_cons_get_size (NULL)*0.82;
/* prepare argc and argv */
if (I.completion.run != NULL) {
I.completion.run (&I);
opt = argc = I.completion.argc;
argv = I.completion.argv;
}
p = (char *)r_sub_str_lchr (I.buffer.data, 0, I.buffer.index, ' ');
if (!p) {
p = (char *)r_sub_str_lchr (I.buffer.data, 0, I.buffer.index, '@'); // HACK FOR r2
}
if (p) {
p++;
plen = sizeof (I.buffer.data)-(int)(size_t)(p-I.buffer.data);
} else {
p = I.buffer.data; // XXX: removes current buffer
plen = sizeof (I.buffer.data);
}
/* autocomplete */
if (argc == 1) {
const char *end_word = r_sub_str_rchr(I.buffer.data,
I.buffer.index, strlen(I.buffer.data), ' ');
const char *t = end_word != NULL ?
end_word : I.buffer.data + I.buffer.index;
int largv0 = strlen (argv[0]? argv[0]: "");
size_t len_t = strlen (t);
if ((p - I.buffer.data) + largv0 + 1 + len_t < plen) {
if (len_t > 0) {
int tt = largv0;
if (*t != ' ') p[tt++] = ' ';
memmove (p + tt, t, len_t);
}
memcpy (p, argv[0], largv0);
p[largv0] = ' ';
if (len_t == 0) p[largv0 + 1] = '\0';
I.buffer.length = strlen (I.buffer.data);
I.buffer.index = (p - I.buffer.data) + largv0 + 1;
}
} else if (argc > 0) {
if (*p) {
// TODO: avoid overflow
const char *t = I.buffer.data + I.buffer.index;
const char *root = argv[0];
int min_common_len = strlen(root);
size_t len_t = strlen (t);
// try to autocomplete argument
for (i = 0; i < argc; i++) {
j = 0;
if (!argv[i]) break;
while (argv[i][j] == root[j] && root[j] != '\0') j++;
if (j < min_common_len) {
min_common_len = j;
}
root = argv[i];
}
if (len_t > 0) {
int tt = min_common_len;
memmove (p + tt, t, len_t);
p[tt + len_t] = '\0';
}
memmove (p, root, min_common_len);
if (len_t == 0) p[min_common_len] = '\0';
I.buffer.length = strlen (I.buffer.data);
I.buffer.index = (p - I.buffer.data) + min_common_len;
}
}
/* show options */
if (opt > 1 && I.echo) {
const int sep = 3;
int slen, col = 10;
printf ("%s%s\n", I.prompt, I.buffer.data);
for (i = 0; i < argc && argv[i]; i++) {
int l = strlen (argv[i]);
if (sep + l > col) {
col = sep + l;
}
if (col > (cols >> 1)) {
col = (cols >> 1);
break;
}
}
for (len = i = 0; i < argc && argv[i]; i++) {
if (len + col > cols) {
printf ("\n");
len = 0;
}
printf ("%-*s ", col - sep, argv[i]);
slen = strlen (argv[i]);
len += (slen > col) ? (slen + sep) : (col + sep);
}
printf ("\n");
}
fflush (stdout);
}
R_API const char *r_line_readline() {
return r_line_readline_cb (NULL, NULL);
}
#if __WINDOWS__ && !__CYGWIN__
R_API const char *r_line_readline_cb_win(RLineReadCallback cb, void *user) {
int columns = r_cons_get_size (NULL)-2;
const char *gcomp_line = "";
static int gcomp_idx = 0;
static int gcomp = 0;
signed char buf[10];
int ch, i=0; /* grep completion */
int vch=0;
char *tmp_ed_cmd, prev = 0;
HANDLE hClipBoard;
char *clipText;
I.buffer.index = I.buffer.length = 0;
I.buffer.data[0] = '\0';
if (I.contents) {
memmove (I.buffer.data, I.contents,
R_MIN (strlen (I.contents)+1, R_LINE_BUFSIZE-1));
I.buffer.data[R_LINE_BUFSIZE-1] = '\0';
I.buffer.index = I.buffer.length = strlen (I.contents);
}
if (I.disable) {
if (!fgets (I.buffer.data, R_LINE_BUFSIZE-1, stdin))
return NULL;
I.buffer.data[strlen (I.buffer.data)] = '\0';
return (*I.buffer.data)? I.buffer.data : r_line_nullstr;
}
memset (&buf, 0, sizeof buf);
r_cons_set_raw (1);
if (I.echo) {
r_cons_clear_line (0);
printf ("\x1b[0K\r%s%s", I.prompt, I.buffer.data);
fflush (stdout);
}
r_cons_singleton()->breaked = false;
for (;;) {
#if 0
if (I.echo) {
printf(" (");
for(i=1;i<argc;i++) {
if (I.buffer.length==0||!strncmp(argv[i], I.buffer.data, I.buffer.length)) {
len+=strlen(argv[i])+1;
if (len+I.buffer.length+4 >= columns) break;
printf("%s ", argv[i]);
}
}
printf(")");
fflush(stdout);
}
#endif
I.buffer.data[I.buffer.length]='\0';
if (cb && !cb (user, I.buffer.data)) {
I.buffer.data[0] = 0;
I.buffer.length = 0;
}
ch = r_line_readchar_win (&vch);
//eprintf("des ch=%c(%d) vch=%d\n",ch,vch);
if (ch == -1) return NULL;
buf[0] = ch;
if (I.echo)
r_cons_clear_line (0);
columns = r_cons_get_size (NULL)-2;
if (columns<1)
columns = 40;
if (I.echo)
printf ("\r%*c\r", columns, ' ');
if (I.echo)
printf ("\r\x1b[2K\r"); //%*c\r", columns, ' ');
/* process special at vch codes first*/
switch (vch) {
case 37: // left arrow
I.buffer.index = I.buffer.index? I.buffer.index-1: 0;
break;
case 38: // up arrow
if (gcomp) gcomp_idx++;
else if (r_line_hist_up ()==-1)
return NULL;
break;
case 39: // right arrow
I.buffer.index = I.buffer.index<I.buffer.length?I.buffer.index+1: I.buffer.length;
break;
case 40: // down arrow
if (gcomp) {
if (gcomp_idx>0)
gcomp_idx--;
} else if (r_line_hist_down ()==-1)
return NULL;
break;
/* ctrl+arrows */
case 137:// ctrl+left arrow
// previous word
for (i=I.buffer.index; i>0; i--) {
if (I.buffer.data[i] == ' ') {
I.buffer.index = i-1;
break;
}
}
if (I.buffer.data[i] != ' ')
I.buffer.index = 0;
break;
case 138:// ctrl+up arrow
//first
I.buffer.index = 0;
break;
case 139:// ctrl+right arrow
// next word
for (i=I.buffer.index; i<I.buffer.length; i++) {
if (I.buffer.data[i] == ' ') {
I.buffer.index = i+1;
break;
}
}
if (I.buffer.data[i] != ' ')
I.buffer.index = I.buffer.length;
break;
case 140:// ctrl+down arrow
//end
I.buffer.index = I.buffer.length;
break;
case 36: // HOME
I.buffer.index = 0;
break;
case 35: // END
I.buffer.index = I.buffer.length;
break;
/*case 0x37: // HOME xrvt-unicode
//r_cons_readchar ();
case 0x38: // END xrvt-unicode
//r_cons_readchar ();*/
case 46: // supr
if (I.buffer.index<I.buffer.length)
memmove (I.buffer.data+I.buffer.index,
I.buffer.data+I.buffer.index+1,
strlen (I.buffer.data+I.buffer.index+1)+1);
if (buf[1] == -1)
return NULL;
break;
default:
break;
}
vch=0;
switch (*buf) {
//case -1: // ^D
// return NULL;
case 0: // no key must by handle by the code up
// ignore
break;
case 1: // ^A
I.buffer.index = 0;
break;
case 2: // ^b // emacs left
I.buffer.index = I.buffer.index? I.buffer.index-1: 0;
break;
case 5: // ^E
if (prev == 24) { // ^X = 0x18
I.buffer.data[I.buffer.length] = 0; // probably unnecessary
tmp_ed_cmd = I.editor_cb (I.user, I.buffer.data);
if (tmp_ed_cmd) {
/* copied from yank (case 25) */
I.buffer.length = strlen (tmp_ed_cmd);
if (I.buffer.length < R_LINE_BUFSIZE) {
I.buffer.index = I.buffer.length;
strncpy (I.buffer.data, tmp_ed_cmd, R_LINE_BUFSIZE-1);
I.buffer.data[R_LINE_BUFSIZE-1] = '\0';
} else I.buffer.length -= strlen (tmp_ed_cmd);
free (tmp_ed_cmd);
}
} else I.buffer.index = I.buffer.length;
break;
case 3: // ^C
if (I.echo) eprintf ("^C\n");
I.buffer.index = I.buffer.length = 0;
*I.buffer.data = '\0';
r_cons_singleton()->breaked = true;
goto _end;
case 4: // ^D
if (!I.buffer.data[0]) { /* eof */
if (I.echo) printf ("^D\n");
r_cons_set_raw (false);
return NULL;
}
if (I.buffer.index<I.buffer.length)
memmove (I.buffer.data+I.buffer.index,
I.buffer.data+I.buffer.index+1,
strlen (I.buffer.data+I.buffer.index+1)+1);
break;
case 10: // ^J -- ignore
return I.buffer.data;
case 11: // ^K -- ignore
break;
case 6: // ^f // emacs right
I.buffer.index = I.buffer.index<I.buffer.length?
I.buffer.index+1: I.buffer.length;
break;
case 12: // ^L -- right
I.buffer.index = (I.buffer.index<I.buffer.length)?
I.buffer.index+1 : I.buffer.length;
if (I.echo)
eprintf ("\x1b[2J\x1b[0;0H");
fflush (stdout);
break;
case 18: // ^R -- autocompletion
gcomp = 1;
break;
case 19: // ^S -- backspace
if (gcomp) gcomp--;
else I.buffer.index = I.buffer.index? I.buffer.index-1: 0;
break;
case 21: // ^U - cut
free (I.clipboard);
I.clipboard = strdup (I.buffer.data);
I.buffer.data[0] = '\0';
I.buffer.length = 0;
I.buffer.index = 0;
break;
case 22: // ^V - Paste from windows clipboard
if (OpenClipboard (NULL)) {
hClipBoard = GetClipboardData(CF_TEXT);
if (hClipBoard) {
clipText = GlobalLock(hClipBoard);
if (clipText) {
I.buffer.length += strlen(clipText);
if (I.buffer.length < R_LINE_BUFSIZE) {
I.buffer.index = I.buffer.length;
strcat (I.buffer.data, clipText);
} else I.buffer.length -= strlen (I.clipboard);
}
GlobalUnlock(hClipBoard);
}
CloseClipboard();
}
break;
case 23: // ^W ^w
if (I.buffer.index>0) {
for (i=I.buffer.index-1; i>0&&I.buffer.data[i]==' '; i--);
for (; i&&I.buffer.data[i]!=' '; i--);
if (!i) for (; i>0&&I.buffer.data[i]==' '; i--);
if (i>0) i++; else if (i<0) i=0;
if (I.buffer.index>I.buffer.length)
I.buffer.length = I.buffer.index;
memmove (I.buffer.data+i,
I.buffer.data+I.buffer.index,
I.buffer.length-I.buffer.index+1);
I.buffer.length = strlen (I.buffer.data);
I.buffer.index = i;
}
break;
case 24: // ^X -- do nothing but store in prev = *buf
break;
case 25: // ^Y - paste
if (I.clipboard != NULL) {
I.buffer.length += strlen(I.clipboard);
// TODO: support endless strings
if (I.buffer.length < R_LINE_BUFSIZE) {
I.buffer.index = I.buffer.length;
strcat (I.buffer.data, I.clipboard);
} else I.buffer.length -= strlen (I.clipboard);
}
break;
case 14: // ^n
if (gcomp) {
if (gcomp_idx>0)
gcomp_idx--;
} else r_line_hist_down ();
break;
case 16: // ^p
if (gcomp) {
gcomp_idx++;
} else r_line_hist_up ();
break;
case 8:
case 127:
if (I.buffer.index < I.buffer.length) {
if (I.buffer.index>0) {
int len = 0;
// TODO: WIP
len = 1;
I.buffer.index--;
memmove (I.buffer.data+I.buffer.index,
I.buffer.data+I.buffer.index+len,
strlen (I.buffer.data+I.buffer.index));
I.buffer.length -= len;
I.buffer.data[I.buffer.length] = 0;
}
} else {
// OK
I.buffer.index = --I.buffer.length;
if (I.buffer.length<0) I.buffer.length=0;
I.buffer.data[I.buffer.length]='\0';
}
if (I.buffer.index<0)
I.buffer.index = 0;
break;
/* tab */
case 9: // tab
r_line_autocomplete ();
break;
/* enter */
case 13:
if (gcomp && I.buffer.length>0) {
strncpy (I.buffer.data, gcomp_line, R_LINE_BUFSIZE-1);
I.buffer.data[R_LINE_BUFSIZE-1] = '\0';
I.buffer.length = strlen (gcomp_line);
}
gcomp_idx = gcomp = 0;
goto _end;
#if 0
// force command fit
for(i=1;i<argc;i++) {
if (I.buffer.length==0 || !strncmp(argv[i], I.buffer.data, I.buffer.length)) {
printf("%*c", columns, ' ');
printf("\r");
printf("\n\n(%s)\n\n", I.buffer.data);
r_cons_set_raw(0);
return I.buffer.data;
}
}
#endif
default:
if (gcomp)
gcomp++;
if (I.buffer.index<I.buffer.length) {
for (i = ++I.buffer.length; i>I.buffer.index; i--)
I.buffer.data[i] = I.buffer.data[i-1];
I.buffer.data[I.buffer.index] = buf[0];
} else {
I.buffer.data[I.buffer.length]=buf[0];
I.buffer.length++;
if (I.buffer.length>(R_LINE_BUFSIZE-1))
I.buffer.length--;
I.buffer.data[I.buffer.length]='\0';
}
I.buffer.index++;
break;
}
prev = buf[0];
if (I.echo) {
if (gcomp) {
gcomp_line = "";
//if (I.buffer.length == 0)
// gcomp = 0;
if (I.history.data != NULL)
for (i=0; i<I.history.size; i++) {
if (I.history.data[i] == NULL)
break;
if (strstr (I.history.data[i], I.buffer.data)) {
gcomp_line = I.history.data[i];
if (!gcomp_idx--)
break;
}
}
printf ("\r (reverse-i-search (%s)): %s\r", I.buffer.data, gcomp_line);
} else {
int chars = R_MAX (1, strlen (I.buffer.data)); // wtf?
int len, cols = R_MAX (1, columns - r_str_ansi_len (I.prompt)-2);
/* print line */
printf ("\r%s", I.prompt);
fwrite (I.buffer.data, 1, R_MIN (cols, chars), stdout);
/* place cursor */
printf ("\r%s", I.prompt);
if (I.buffer.index>cols) {
printf ("< ");
i = I.buffer.index-cols;
if (i>sizeof (I.buffer.data)) {
i = sizeof (I.buffer.data)-1;
}
} else i = 0;
len = I.buffer.index-i;
if (len>0 && (i+len)<=I.buffer.length)
fwrite (I.buffer.data+i, 1, len, stdout);
}
fflush (stdout);
}
}
_end:
r_cons_set_raw (0);
if (I.echo) {
printf ("\r%s%s\n", I.prompt, I.buffer.data);
fflush (stdout);
}
// should be here or not?
if (!memcmp (I.buffer.data, "!history", 8)) {
//if (I.buffer.data[0]=='!' && I.buffer.data[1]=='\0') {
r_line_hist_list ();
return r_line_nullstr;
}
return I.buffer.data[0] != '\0'? I.buffer.data : r_line_nullstr;
}
#endif
R_API const char *r_line_readline_cb(RLineReadCallback cb, void *user) {
#if __WINDOWS__ && !__CYGWIN__
#if 1 // new implementation for read input at windows by skuater. If something fail set this to 0
return r_line_readline_cb_win(cb,user);
#endif
#endif
int columns = r_cons_get_size (NULL)-2;
const char *gcomp_line = "";
static int gcomp_idx = 0;
static int gcomp = 0;
signed char buf[10];
#if USE_UTF8
int utflen;
#endif
int ch, i=0; /* grep completion */
char *tmp_ed_cmd, prev = 0;
I.buffer.index = I.buffer.length = 0;
I.buffer.data[0] = '\0';
if (I.contents) {
memmove (I.buffer.data, I.contents,
R_MIN (strlen (I.contents)+1, R_LINE_BUFSIZE-1));
I.buffer.data[R_LINE_BUFSIZE-1] = '\0';
I.buffer.index = I.buffer.length = strlen (I.contents);
}
if (I.disable) {
if (!fgets (I.buffer.data, R_LINE_BUFSIZE-1, stdin))
return NULL;
I.buffer.data[strlen (I.buffer.data)] = '\0';
return (*I.buffer.data)? I.buffer.data : r_line_nullstr;
}
memset (&buf, 0, sizeof buf);
r_cons_set_raw (1);
if (I.echo) {
r_cons_clear_line (0);
printf ("\x1b[0K\r%s%s", I.prompt, I.buffer.data);
fflush (stdout);
}
r_cons_singleton()->breaked = false;
for (;;) {
I.buffer.data[I.buffer.length] = '\0';
if (cb && !cb (user, I.buffer.data)) {
I.buffer.data[0] = 0;
I.buffer.length = 0;
}
#if USE_UTF8
utflen = r_line_readchar_utf8 (
(ut8*)buf, sizeof (buf));
if (utflen <1) {
return NULL;
}
buf[utflen] = 0;
#else
ch = r_line_readchar ();
if (ch == -1) return NULL;
buf[0] = ch;
#endif
if (I.echo) {
r_cons_clear_line (0);
}
if (columns < 1) {
columns = 40;
}
#if __WINDOWS__ && !__CYGWIN__
if (I.echo) {
printf ("\r%*c\r", columns, ' ');
}
#else
if (I.echo) {
printf ("\r\x1b[2K\r"); //%*c\r", columns, ' ');
}
#endif
switch (*buf) {
case 0: // control-space
/* ignore atm */
break;
case 1: // ^A
I.buffer.index = 0;
break;
case 2: // ^b // emacs left
#if USE_UTF8
{
char *s = I.buffer.data+I.buffer.index-1;
utflen = 1;
while (s>I.buffer.data && (*s & 0xc0) == 0x80) {
utflen++;
s--;
}
}
I.buffer.index = I.buffer.index? I.buffer.index-utflen: 0;
#else
I.buffer.index = I.buffer.index? I.buffer.index-1: 0;
#endif
break;
case 5: // ^E
if (prev == 24) { // ^X = 0x18
I.buffer.data[I.buffer.length] = 0; // probably unnecessary
tmp_ed_cmd = I.editor_cb (I.user, I.buffer.data);
if (tmp_ed_cmd) {
/* copied from yank (case 25) */
I.buffer.length = strlen (tmp_ed_cmd);
if (I.buffer.length < R_LINE_BUFSIZE) {
I.buffer.index = I.buffer.length;
strncpy (I.buffer.data, tmp_ed_cmd, R_LINE_BUFSIZE-1);
I.buffer.data[R_LINE_BUFSIZE-1] = '\0';
} else I.buffer.length -= strlen (tmp_ed_cmd);
free (tmp_ed_cmd);
}
} else I.buffer.index = I.buffer.length;
break;
case 3: // ^C
if (I.echo)
eprintf ("^C\n");
I.buffer.index = I.buffer.length = 0;
*I.buffer.data = '\0';
r_cons_singleton()->breaked = true;
goto _end;
case 4: // ^D
if (!I.buffer.data[0]) { /* eof */
if (I.echo)
printf ("^D\n");
r_cons_set_raw (false);
return NULL;
}
if (I.buffer.index<I.buffer.length)
memmove (I.buffer.data+I.buffer.index,
I.buffer.data+I.buffer.index+1,
strlen (I.buffer.data+I.buffer.index+1)+1);
break;
case 10: // ^J -- ignore
return I.buffer.data;
case 11: // ^K
I.buffer.data[I.buffer.index] = '\0';
I.buffer.length = I.buffer.index;
break;
case 6: // ^f // emacs right
#if USE_UTF8
{
char *s = I.buffer.data+I.buffer.index+1;
utflen = 1;
while ((*s & 0xc0) == 0x80) {
utflen++;
s++;
}
I.buffer.index = I.buffer.index<I.buffer.length?
I.buffer.index+utflen: I.buffer.length;
}
#else
I.buffer.index = I.buffer.index<I.buffer.length?
I.buffer.index+1: I.buffer.length;
#endif
break;
case 12: // ^L -- right
I.buffer.index = (I.buffer.index<I.buffer.length)?
I.buffer.index+1 : I.buffer.length;
if (I.echo)
eprintf ("\x1b[2J\x1b[0;0H");
fflush (stdout);
break;
case 18: // ^R -- autocompletion
gcomp = 1;
break;
case 19: // ^S -- backspace
if (gcomp) gcomp--;
else {
#if USE_UTF8
if (I.buffer.index>0) {
char *s;
do {
I.buffer.index--;
s = I.buffer.data+I.buffer.index;
} while ((*s & 0xc0) == 0x80);
}
#else
I.buffer.index = I.buffer.index? I.buffer.index-1: 0;
#endif
}
break;
case 21: // ^U - cut
free (I.clipboard);
I.clipboard = strdup (I.buffer.data);
I.buffer.data[0] = '\0';
I.buffer.length = 0;
I.buffer.index = 0;
break;
case 23: // ^W ^w
if (I.buffer.index>0) {
for (i=I.buffer.index-1; i>0&&I.buffer.data[i]==' '; i--);
for (; i&&I.buffer.data[i]!=' '; i--);
if (!i) for (; i>0&&I.buffer.data[i]==' '; i--);
if (i>0) i++; else if (i<0) i=0;
if (I.buffer.index>I.buffer.length)
I.buffer.length = I.buffer.index;
memmove (I.buffer.data+i,
I.buffer.data+I.buffer.index,
I.buffer.length-I.buffer.index+1);
I.buffer.length = strlen (I.buffer.data);
I.buffer.index = i;
}
break;
case 24: // ^X -- do nothing but store in prev = *buf
break;
case 25: // ^Y - paste
if (I.clipboard != NULL) {
I.buffer.length += strlen(I.clipboard);
// TODO: support endless strings
if (I.buffer.length < R_LINE_BUFSIZE) {
I.buffer.index = I.buffer.length;
strcat (I.buffer.data, I.clipboard);
} else I.buffer.length -= strlen (I.clipboard);
}
break;
case 14: // ^n
if (gcomp) {
if (gcomp_idx>0)
gcomp_idx--;
} else r_line_hist_down ();
break;
case 16: // ^p
if (gcomp) {
gcomp_idx++;
} else r_line_hist_up ();
break;
case 27: //esc-5b-41-00-00
buf[0] = r_line_readchar();
switch (buf[0]) {
case -1: return NULL;
case 1: // begin
I.buffer.index = 0;
break;
case 5: // end
I.buffer.index = I.buffer.length;
break;
case 'B':
case 'b':
// previous word
for (i = I.buffer.index - 2; i >= 0; i--) {
if (I.buffer.data[i] == ' ' && I.buffer.data[i + 1] != ' ') {
I.buffer.index = i + 1;
break;
}
}
if (i < 0)
I.buffer.index = 0;
break;
case 'F':
case 'f':
// next word
for (i = I.buffer.index + 1; i < I.buffer.length; i++) {
if (I.buffer.data[i] != ' ' && I.buffer.data[i - 1] == ' ') {
I.buffer.index = i;
break;
}
}
if (i >= I.buffer.length)
I.buffer.index = I.buffer.length;
break;
default:
buf[1] = r_line_readchar();
if (buf[1] == -1)
return NULL;
if (buf[0]==0x5b) { // [
switch (buf[1]) {
case 0x33: // supr
if (I.buffer.index<I.buffer.length)
memmove (I.buffer.data+I.buffer.index,
I.buffer.data+I.buffer.index+1,
strlen (I.buffer.data+I.buffer.index+1)+1);
buf[1] = r_line_readchar ();
if (buf[1] == -1)
return NULL;
break;
/* arrows */
case 0x41:
if (gcomp) gcomp_idx++;
else if (r_line_hist_up ()==-1)
return NULL;
break;
case 0x42:
if (gcomp) {
if (gcomp_idx>0)
gcomp_idx--;
} else if (r_line_hist_down ()==-1)
return NULL;
break;
case 0x43: // C --> right arrow
#if USE_UTF8
{
char *s = I.buffer.data+I.buffer.index+1;
utflen = 1;
while ((*s & 0xc0) == 0x80) {
utflen++;
s++;
}
I.buffer.index = I.buffer.index<I.buffer.length?
I.buffer.index+utflen: I.buffer.length;
}
#else
I.buffer.index = I.buffer.index<I.buffer.length?
I.buffer.index+1: I.buffer.length;
#endif
break;
case 0x44: // D --> left arrow
#if USE_UTF8
{
char *s = I.buffer.data+I.buffer.index-1;
utflen = 1;
while (s>I.buffer.data && (*s & 0xc0) == 0x80) {
utflen++;
s--;
}
}
I.buffer.index = I.buffer.index? I.buffer.index-utflen: 0;
#else
I.buffer.index = I.buffer.index? I.buffer.index-1: 0;
#endif
break;
case 0x31: // control + arrow
ch = r_cons_readchar ();
if (ch == 0x7e) { // HOME in screen/tmux
// corresponding END is 0x34 below (the 0x7e is ignored there)
I.buffer.index = 0;
break;
}
r_cons_readchar ();
ch = r_cons_readchar ();
switch (ch) {
case 0x41:
//first
I.buffer.index = 0;
break;
case 0x44:
// previous word
for (i=I.buffer.index; i>0; i--) {
if (I.buffer.data[i] == ' ') {
I.buffer.index = i-1;
break;
}
}
if (I.buffer.data[i] != ' ')
I.buffer.index = 0;
break;
case 0x42:
//end
I.buffer.index = I.buffer.length;
break;
case 0x43:
// next word
for (i=I.buffer.index; i<I.buffer.length; i++) {
if (I.buffer.data[i] == ' ') {
I.buffer.index = i+1;
break;
}
}
if (I.buffer.data[i] != ' ')
I.buffer.index = I.buffer.length;
break;
}
r_cons_set_raw (1);
break;
case 0x37: // HOME xrvt-unicode
r_cons_readchar ();
case 0x48: // HOME
I.buffer.index = 0;
break;
case 0x34: // END
case 0x38: // END xrvt-unicode
r_cons_readchar ();
case 0x46: // END
I.buffer.index = I.buffer.length;
break;
}
}
}
break;
case 8:
case 127:
if (I.buffer.index < I.buffer.length) {
if (I.buffer.index > 0) {
int len = 0;
// TODO: WIP
#if USE_UTF8
char *s;
do {
I.buffer.index--;
s = I.buffer.data + I.buffer.index;
len++;
} while ((*s & 0xc0) == 0x80);
#else
len = 1;
I.buffer.index--;
#endif
memmove (I.buffer.data + I.buffer.index,
I.buffer.data + I.buffer.index + len,
strlen (I.buffer.data + I.buffer.index));
I.buffer.length -= len;
I.buffer.data[I.buffer.length] = 0;
}
} else {
// OK
#if USE_UTF8
char *s;
// utf8 backward size
do {
I.buffer.length--;
s = I.buffer.data+I.buffer.length;
i++;
} while ((*s & 0xc0) == 0x80);
I.buffer.index = I.buffer.length;
#else
I.buffer.index = --I.buffer.length;
#endif
if (I.buffer.length < 0) I.buffer.length = 0;
I.buffer.data[I.buffer.length] = '\0';
}
if (I.buffer.index < 0) {
I.buffer.index = 0;
}
break;
case 9: // tab
r_line_autocomplete ();
break;
case 13:
if (gcomp && I.buffer.length>0) {
strncpy (I.buffer.data, gcomp_line, R_LINE_BUFSIZE-1);
I.buffer.data[R_LINE_BUFSIZE-1] = '\0';
I.buffer.length = strlen (gcomp_line);
}
gcomp_idx = gcomp = 0;
goto _end;
default:
if (gcomp)
gcomp++;
if (I.buffer.index<I.buffer.length) {
#if USE_UTF8
if ((I.buffer.length + utflen) < sizeof (I.buffer.data)) {
I.buffer.length += utflen;
for (i = I.buffer.length; i > I.buffer.index; i--) {
I.buffer.data[i] = I.buffer.data[i - utflen];
}
memcpy (I.buffer.data + I.buffer.index, buf, utflen);
}
#else
for (i = ++I.buffer.length; i > I.buffer.index; i--) {
I.buffer.data[i] = I.buffer.data[i - 1];
}
I.buffer.data[I.buffer.index] = buf[0];
#endif
} else {
#if USE_UTF8
if ((I.buffer.length + utflen) < sizeof (I.buffer.data)) {
memcpy (I.buffer.data + I.buffer.length, buf, utflen);
I.buffer.length += utflen;
}
I.buffer.data[I.buffer.length] = '\0';
#else
I.buffer.data[I.buffer.length] = buf[0];
I.buffer.length++;
if (I.buffer.length > (R_LINE_BUFSIZE - 1)) {
I.buffer.length--;
}
I.buffer.data[I.buffer.length] = '\0';
#endif
}
#if USE_UTF8
I.buffer.index += utflen;
#else
I.buffer.index++;
#endif
break;
}
prev = buf[0];
if (I.echo) {
if (gcomp) {
gcomp_line = "";
if (I.history.data != NULL)
for (i=0; i<I.history.size; i++) {
if (I.history.data[i] == NULL)
break;
if (strstr (I.history.data[i], I.buffer.data)) {
gcomp_line = I.history.data[i];
if (!gcomp_idx--)
break;
}
}
printf ("\r (reverse-i-search (%s)): %s\r", I.buffer.data, gcomp_line);
} else {
int chars = R_MAX (1, strlen (I.buffer.data)); // wtf?
int len, cols = R_MAX (1, columns - r_str_ansi_len (I.prompt)-2);
/* print line */
printf ("\r%s", I.prompt);
fwrite (I.buffer.data, 1, R_MIN (cols, chars), stdout);
/* place cursor */
printf ("\r%s", I.prompt);
if (I.buffer.index>cols) {
printf ("< ");
i = I.buffer.index-cols;
if (i>sizeof (I.buffer.data)) {
i = sizeof (I.buffer.data)-1;
}
} else i = 0;
len = I.buffer.index-i;
if (len>0 && (i+len)<=I.buffer.length)
fwrite (I.buffer.data+i, 1, len, stdout);
}
fflush (stdout);
}
}
_end:
r_cons_set_raw (0);
if (I.echo) {
printf ("\r%s%s\n", I.prompt, I.buffer.data);
fflush (stdout);
}
// should be here or not?
if (!memcmp (I.buffer.data, "!history", 8)) {
//if (I.buffer.data[0]=='!' && I.buffer.data[1]=='\0') {
r_line_hist_list ();
return r_line_nullstr;
}
return I.buffer.data[0] != '\0'? I.buffer.data : r_line_nullstr;
}