master xplshn/aruu / shared / libredline / redline.c
   1/* see license file for copyright and license details */
   2
   3#include "redline.h"
   4#include "../paths.h"
   5
   6#include <ctype.h>
   7#include <dirent.h>
   8#include <errno.h>
   9#include <fcntl.h>
  10#include <signal.h>
  11#include <stdint.h>
  12#include <stdio.h>
  13#include <stdlib.h>
  14#include <string.h>
  15#include <sys/ioctl.h>
  16#include <sys/stat.h>
  17#include <sys/types.h>
  18#include <termios.h>
  19#include <unistd.h>
  20
  21#define REDLINE_DEFAULT_HISTORY_MAX_LEN 100
  22#define REDLINE_MAX_LINE                (1024 * 1024)
  23#define REDLINE_INITIAL_BUFLEN          4096
  24
  25#define ENTER     13
  26#define CTRL_A    1
  27#define CTRL_B    2
  28#define CTRL_C    3
  29#define CTRL_D    4
  30#define CTRL_E    5
  31#define CTRL_F    6
  32#define CTRL_H    8
  33#define CTRL_K    11
  34#define CTRL_L    12
  35#define CTRL_N    14
  36#define CTRL_P    16
  37#define CTRL_T    20
  38#define CTRL_U    21
  39#define CTRL_W    23
  40#define CTRL_Y    25
  41#define CTRL_Z    26
  42#define CTRL_QUIT 28
  43#define BACKSPACE 127
  44#define ESC       27
  45
  46struct redlineState {
  47  int         in_completion;
  48  int         ifd;
  49  int         ofd;
  50  char       *buf;
  51  size_t      buflen;
  52  const char *prompt;
  53  size_t      plen;
  54  size_t      pos;
  55  size_t      oldpos;
  56  size_t      len;
  57  size_t      cols;
  58  size_t      oldrows;
  59  int         oldrpos;
  60  int         history_index;
  61};
  62
  63struct abuf {
  64  char *b;
  65  int   len;
  66};
  67
  68static struct termios        orig_termios;
  69static int                   rawmode         = 0;
  70static int                   mlmode          = 0;
  71static int                   history_max_len = REDLINE_DEFAULT_HISTORY_MAX_LEN;
  72static int                   history_len     = 0;
  73static char                **history         = NULL;
  74static char                 *kill_buffer     = NULL;
  75static volatile sig_atomic_t winch_received  = 0;
  76static struct sigaction      orig_sigwinch;
  77
  78static void redlineEditStop(struct redlineState *l);
  79
  80static void (*completionCallback)(const char *, struct redlineCompletions *) = NULL;
  81
  82/* return the number of bytes that compose the utf-8 character starting at c */
  83static int
  84utf8ByteLen(char c)
  85{
  86  unsigned char uc = (unsigned char)c;
  87  if ((uc & 0x80) == 0)
  88    return 1;
  89  if ((uc & 0xE0) == 0xC0)
  90    return 2;
  91  if ((uc & 0xF0) == 0xE0)
  92    return 3;
  93  if ((uc & 0xF8) == 0xF0)
  94    return 4;
  95  return 1;
  96}
  97
  98/* decode character starting at s */
  99static uint32_t
 100utf8DecodeChar(const char *s, size_t *len)
 101{
 102  uint32_t cp = 0;
 103  int      l  = utf8ByteLen(*s);
 104  *len        = l;
 105  if (l == 1) {
 106    cp = ((unsigned char)*s);
 107  } else if (l == 2) {
 108    cp = ((unsigned char)*s & 0x1F) << 6;
 109    cp |= ((unsigned char)*(s + 1) & 0x3F);
 110  } else if (l == 3) {
 111    cp = ((unsigned char)*s & 0x0F) << 12;
 112    cp |= ((unsigned char)*(s + 1) & 0x3F) << 6;
 113    cp |= ((unsigned char)*(s + 2) & 0x3F);
 114  } else if (l == 4) {
 115    cp = ((unsigned char)*s & 0x07) << 18;
 116    cp |= ((unsigned char)*(s + 1) & 0x3F) << 12;
 117    cp |= ((unsigned char)*(s + 2) & 0x3F) << 6;
 118    cp |= ((unsigned char)*(s + 3) & 0x3F);
 119  }
 120  return cp;
 121}
 122
 123static int
 124isZWJ(uint32_t cp)
 125{
 126  return cp == 0x200D;
 127}
 128
 129static int
 130isCombiningMark(uint32_t cp)
 131{
 132  return (cp >= 0x0300 && cp <= 0x036F) || (cp >= 0x1DC0 && cp <= 0x1DFF)
 133         || (cp >= 0x20D0 && cp <= 0x20FF) || (cp >= 0xFE20 && cp <= 0xFE2F);
 134}
 135
 136static int
 137isVariationSelector(uint32_t cp)
 138{
 139  return (cp >= 0xFE00 && cp <= 0xFE0F) || (cp >= 0xE0100 && cp <= 0xE01EF);
 140}
 141
 142static int
 143isSkinToneModifier(uint32_t cp)
 144{
 145  return cp >= 0x1F3FB && cp <= 0x1F3FF;
 146}
 147
 148static int
 149isGraphemeExtend(uint32_t cp)
 150{
 151  return isCombiningMark(cp) || isVariationSelector(cp) || isSkinToneModifier(cp);
 152}
 153
 154/* decode character going backward from pos */
 155static uint32_t
 156utf8DecodePrev(const char *buf, size_t pos, size_t *cplen)
 157{
 158  size_t i = 1;
 159  while (pos >= i && i <= 4) {
 160    unsigned char uc = (unsigned char)buf[pos - i];
 161    if ((uc & 0x80) == 0) {
 162      if (i == 1) {
 163        *cplen = 1;
 164        return uc;
 165      }
 166      break;
 167    }
 168    if ((uc & 0xC0) == 0xC0) {
 169      int l = utf8ByteLen(buf[pos - i]);
 170      if ((size_t)l == i) {
 171        *cplen = i;
 172        return utf8DecodeChar(buf + pos - i, cplen);
 173      }
 174      break;
 175    }
 176    i++;
 177  }
 178  *cplen = 1;
 179  return (unsigned char)buf[pos - 1];
 180}
 181
 182/* calculate width of utf-8 char pos */
 183static size_t
 184utf8PrevCharLen(const char *buf, size_t pos)
 185{
 186  size_t   len      = 0;
 187  size_t   next_len = 0;
 188  uint32_t cp;
 189  if (pos == 0)
 190    return 0;
 191  cp = utf8DecodePrev(buf, pos, &len);
 192  pos -= len;
 193  while (pos > 0 && isGraphemeExtend(cp)) {
 194    cp = utf8DecodePrev(buf, pos, &next_len);
 195    len += next_len;
 196    pos -= next_len;
 197  }
 198  if (pos > 0 && isZWJ(cp)) {
 199    size_t j = utf8PrevCharLen(buf, pos);
 200    if (j > 0)
 201      len += j;
 202  }
 203  return len;
 204}
 205
 206/* calculate width of next utf-8 char */
 207static size_t
 208utf8NextCharLen(const char *buf, size_t pos, size_t len)
 209{
 210  size_t   clen   = 0;
 211  size_t   offset = 0;
 212  uint32_t cp;
 213  if (pos >= len)
 214    return 0;
 215  cp     = utf8DecodeChar(buf + pos, &clen);
 216  offset = clen;
 217  while (pos + offset < len) {
 218    size_t   next_len = 0;
 219    uint32_t next_cp  = utf8DecodeChar(buf + pos + offset, &next_len);
 220    if (isGraphemeExtend(next_cp)) {
 221      offset += next_len;
 222    } else if (isZWJ(cp)) {
 223      offset += next_len;
 224      cp = next_cp;
 225    } else {
 226      break;
 227    }
 228  }
 229  return offset;
 230}
 231
 232/* get columns needed to display char */
 233static int
 234utf8CharWidth(uint32_t cp)
 235{
 236  if (cp == 0)
 237    return 0;
 238  if (cp < 0x20 || (cp >= 0x7f && cp < 0xa0))
 239    return 0;
 240  if ((cp >= 0x1100 && cp <= 0x115f) || (cp >= 0x2e80 && cp <= 0xa4cf && cp != 0x303f)
 241      || (cp >= 0xac00 && cp <= 0xd7a3) || (cp >= 0xf900 && cp <= 0xfaff)
 242      || (cp >= 0xfe10 && cp <= 0xfe19) || (cp >= 0xfe30 && cp <= 0xfe6f)
 243      || (cp >= 0xff00 && cp <= 0xff60) || (cp >= 0xffe0 && cp <= 0xffe6)
 244      || (cp >= 0x20000 && cp <= 0x2fffd) || (cp >= 0x30000 && cp <= 0x3fffd)) {
 245    return 2;
 246  }
 247  return 1;
 248}
 249
 250/* get ansi escape sequence length */
 251static size_t
 252ansiEscapeLen(const char *s, size_t len)
 253{
 254  size_t i = 0;
 255  if (len < 2 || s[0] != '\x1b' || s[1] != '[')
 256    return 0;
 257  i = 2;
 258  while (i < len) {
 259    char c = s[i];
 260    if ((c >= '0' && c <= '9') || c == ';' || c == '?' || c == '"') {
 261      i++;
 262    } else if (c >= 'A' && c <= 'Z') {
 263      return i + 1;
 264    } else if (c >= 'a' && c <= 'z') {
 265      return i + 1;
 266    } else {
 267      break;
 268    }
 269  }
 270  return 0;
 271}
 272
 273/* calculate width of string */
 274static size_t
 275utf8StrWidth(const char *s, size_t len)
 276{
 277  size_t width = 0;
 278  size_t i     = 0;
 279  while (i < len) {
 280    size_t elen = ansiEscapeLen(s + i, len - i);
 281    if (elen > 0) {
 282      i += elen;
 283      continue;
 284    }
 285    size_t   clen = 0;
 286    uint32_t cp   = utf8DecodeChar(s + i, &clen);
 287    width += utf8CharWidth(cp);
 288    i += clen;
 289  }
 290  return width;
 291}
 292
 293/* get single character width */
 294static int
 295utf8SingleCharWidth(const char *s, size_t len)
 296{
 297  size_t   clen = 0;
 298  uint32_t cp   = utf8DecodeChar(s, &clen);
 299  (void)len;
 300  return utf8CharWidth(cp);
 301}
 302
 303static int
 304isUnsupportedTerm(void)
 305{
 306  char        *term = getenv("TERM");
 307  int          i;
 308  static char *unsupported[] = {"dumb", "cons25", "emacs", NULL};
 309  if (term == NULL)
 310    return 0;
 311  for (i = 0; unsupported[i]; i++) {
 312    if (strcasecmp(term, unsupported[i]) == 0)
 313      return 1;
 314  }
 315  return 0;
 316}
 317
 318static void
 319sigwinchHandler(int sig)
 320{
 321  (void)sig;
 322  winch_received = 1;
 323}
 324
 325static int
 326enableRawMode(int fd)
 327{
 328  struct termios   raw;
 329  struct sigaction sa;
 330
 331  if (!isatty(STDIN_FILENO))
 332    return -1;
 333  if (tcgetattr(fd, &orig_termios) == -1)
 334    return -1;
 335
 336  raw = orig_termios;
 337  raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP);
 338  raw.c_oflag &= ~(OPOST);
 339  raw.c_cflag |= (CS8);
 340  raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
 341  raw.c_cc[VMIN]  = 1;
 342  raw.c_cc[VTIME] = 0;
 343
 344  if (tcsetattr(fd, TCSAFLUSH, &raw) < 0)
 345    return -1;
 346
 347  rawmode = 1;
 348
 349  /* register sigwinch handler */
 350  sa.sa_handler = sigwinchHandler;
 351  sigemptyset(&sa.sa_mask);
 352  sa.sa_flags = 0;
 353  sigaction(SIGWINCH, &sa, &orig_sigwinch);
 354
 355  return 0;
 356}
 357
 358static void
 359disableRawMode(int fd)
 360{
 361  if (rawmode) {
 362    tcsetattr(fd, TCSAFLUSH, &orig_termios);
 363    sigaction(SIGWINCH, &orig_sigwinch, NULL);
 364    rawmode = 0;
 365  }
 366}
 367
 368static int
 369getCursorPosition(int ifd, int ofd)
 370{
 371  char         buf[32];
 372  int          cols, rows;
 373  unsigned int i = 0;
 374
 375  if (write(ofd, "\x1b[6n", 4) != 4)
 376    return -1;
 377
 378  while (i < sizeof(buf) - 1) {
 379    if (read(ifd, buf + i, 1) != 1)
 380      break;
 381    if (buf[i] == 'R')
 382      break;
 383    i++;
 384  }
 385  buf[i] = '\0';
 386
 387  if (buf[0] != 27 || buf[1] != '[')
 388    return -1;
 389  if (sscanf(buf + 2, "%d;%d", &rows, &cols) != 2)
 390    return -1;
 391  return cols;
 392}
 393
 394static int
 395getColumns(int ifd, int ofd)
 396{
 397  struct winsize ws;
 398  char          *cols_env;
 399  int            tty_fd;
 400  int            cols = 0;
 401
 402  if (ioctl(ofd, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 20)
 403    return ws.ws_col;
 404  if (ioctl(ifd, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 20)
 405    return ws.ws_col;
 406  if (ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 20)
 407    return ws.ws_col;
 408  if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 20)
 409    return ws.ws_col;
 410  if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 20)
 411    return ws.ws_col;
 412
 413  tty_fd = open(ARUU_PATH_DEVTTY, O_RDWR | O_NOCTTY);
 414  if (tty_fd >= 0) {
 415    if (ioctl(tty_fd, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 20) {
 416      cols = ws.ws_col;
 417    }
 418    close(tty_fd);
 419    if (cols >= 20)
 420      return cols;
 421  }
 422
 423  cols_env = getenv("COLUMNS");
 424  if (cols_env) {
 425    cols = atoi(cols_env);
 426    if (cols >= 20)
 427      return cols;
 428  }
 429
 430  /* fallback to cursor position query */
 431  int start;
 432
 433  if (!isatty(ifd) || !isatty(ofd))
 434    return 80;
 435
 436  start = getCursorPosition(ifd, ofd);
 437  if (start == -1)
 438    return 80;
 439
 440  if (write(ofd, "\x1b[999C", 6) != 6)
 441    return 80;
 442  cols = getCursorPosition(ifd, ofd);
 443  if (cols == -1)
 444    return 80;
 445
 446  if (cols > start) {
 447    char seq[32];
 448    snprintf(seq, sizeof(seq), "\x1b[%dD", cols - start);
 449    if (write(ofd, seq, strlen(seq)) == -1) {}
 450  }
 451  if (cols < 20)
 452    return 80;
 453  return cols;
 454}
 455
 456static void
 457redlineBeep(void)
 458{
 459  fprintf(stderr, "\x7");
 460  fflush(stderr);
 461}
 462
 463static void
 464freeCompletions(struct redlineCompletions *lc)
 465{
 466  size_t i;
 467  if (lc->cvec) {
 468    for (i = 0; i < lc->len; i++) {
 469      free(lc->cvec[i]);
 470    }
 471    free(lc->cvec);
 472  }
 473}
 474
 475static size_t
 476longestCommonPrefix(struct redlineCompletions *lc)
 477{
 478  size_t i, j;
 479  if (lc->len == 0)
 480    return 0;
 481  for (i = 0;; i++) {
 482    char c = lc->cvec[0][i];
 483    if (c == '\0')
 484      return i;
 485    for (j = 1; j < lc->len; j++) {
 486      if (lc->cvec[j][i] != c) {
 487        return i;
 488      }
 489    }
 490  }
 491}
 492
 493static void
 494printCompletions(struct redlineState *ls, struct redlineCompletions *lc)
 495{
 496  size_t      max_len = 0;
 497  size_t      i, j, k;
 498  size_t      col_width, num_cols, num_rows;
 499  size_t      sp, len, idx;
 500  const char *comp;
 501  const char *name;
 502
 503  for (i = 0; i < lc->len; i++) {
 504    comp = lc->cvec[i];
 505    sp   = strlen(comp);
 506    if (sp > 0 && comp[sp - 1] == ' ') {
 507      sp--;
 508    }
 509    while (sp > 0 && comp[sp - 1] != ' ') {
 510      sp--;
 511    }
 512    len = strlen(comp + sp);
 513    if (len > 0 && (comp + sp)[len - 1] == ' ') {
 514      len--;
 515    }
 516    if (len > max_len) {
 517      max_len = len;
 518    }
 519  }
 520
 521  col_width = max_len + 2;
 522  num_cols  = ls->cols / col_width;
 523  if (num_cols == 0)
 524    num_cols = 1;
 525  num_rows = (lc->len + num_cols - 1) / num_cols;
 526
 527  if (write(ls->ofd, "\r\n", 2) == -1) {}
 528  for (i = 0; i < num_rows; i++) {
 529    for (j = 0; j < num_cols; j++) {
 530      idx = j * num_rows + i;
 531      if (idx < lc->len) {
 532        comp = lc->cvec[idx];
 533        sp   = strlen(comp);
 534        if (sp > 0 && comp[sp - 1] == ' ') {
 535          sp--;
 536        }
 537        while (sp > 0 && comp[sp - 1] != ' ') {
 538          sp--;
 539        }
 540        name = comp + sp;
 541        len  = strlen(name);
 542        if (len > 0 && name[len - 1] == ' ') {
 543          len--;
 544        }
 545        if (write(ls->ofd, name, len) == -1) {}
 546        if (j < num_cols - 1) {
 547          for (k = len; k < col_width; k++) {
 548            if (write(ls->ofd, " ", 1) == -1) {}
 549          }
 550        }
 551      }
 552    }
 553    if (write(ls->ofd, "\r\n", 2) == -1) {}
 554  }
 555}
 556
 557static void
 558abInit(struct abuf *ab)
 559{
 560  ab->b   = NULL;
 561  ab->len = 0;
 562}
 563
 564static void
 565abAppend(struct abuf *ab, const char *s, int len)
 566{
 567  char *new = realloc(ab->b, ab->len + len);
 568  if (new == NULL)
 569    return;
 570  memcpy(new + ab->len, s, len);
 571  ab->b = new;
 572  ab->len += len;
 573}
 574
 575static void
 576abFree(struct abuf *ab)
 577{
 578  free(ab->b);
 579}
 580
 581static void
 582refreshSingleLine(struct redlineState *l, int flags)
 583{
 584  char        seq[64];
 585  size_t      pwidth = utf8StrWidth(l->prompt, l->plen);
 586  int         fd     = l->ofd;
 587  char       *buf    = l->buf;
 588  size_t      len    = l->len;
 589  size_t      pos    = l->pos;
 590  size_t      poscol;
 591  size_t      lencol;
 592  struct abuf ab;
 593
 594  poscol = utf8StrWidth(buf, pos);
 595  lencol = utf8StrWidth(buf, len);
 596
 597  while (pwidth + poscol >= l->cols) {
 598    size_t clen   = utf8NextCharLen(buf, 0, len);
 599    int    cwidth = utf8SingleCharWidth(buf, clen);
 600    buf += clen;
 601    len -= clen;
 602    pos -= clen;
 603    poscol -= cwidth;
 604    lencol -= cwidth;
 605  }
 606
 607  while (pwidth + lencol > l->cols) {
 608    size_t clen   = utf8PrevCharLen(buf, len);
 609    int    cwidth = utf8SingleCharWidth(buf + len - clen, clen);
 610    len -= clen;
 611    lencol -= cwidth;
 612  }
 613
 614  abInit(&ab);
 615  snprintf(seq, sizeof(seq), "\r");
 616  abAppend(&ab, seq, strlen(seq));
 617
 618  if (flags & 1) {
 619    abAppend(&ab, l->prompt, l->plen);
 620    abAppend(&ab, buf, len);
 621  }
 622
 623  snprintf(seq, sizeof(seq), "\x1b[0K");
 624  abAppend(&ab, seq, strlen(seq));
 625
 626  if (flags & 1) {
 627    snprintf(seq, sizeof(seq), "\r\x1b[%dC", (int)(poscol + pwidth));
 628    abAppend(&ab, seq, strlen(seq));
 629  }
 630
 631  if (write(fd, ab.b, ab.len) == -1) {}
 632  abFree(&ab);
 633}
 634
 635static void
 636refreshMultiLine(struct redlineState *l, int flags)
 637{
 638  char        seq[64];
 639  size_t      pwidth = utf8StrWidth(l->prompt, l->plen);
 640  size_t      bufwidth;
 641  size_t      poswidth;
 642  int         rows;
 643  int         rpos2;
 644  int         col;
 645  int         old_rows = l->oldrows;
 646  int         rpos     = l->oldrpos;
 647  int         fd       = l->ofd, j;
 648  struct abuf ab;
 649
 650  (void)flags;
 651
 652  bufwidth   = utf8StrWidth(l->buf, l->len);
 653  poswidth   = utf8StrWidth(l->buf, l->pos);
 654  rows       = (pwidth + bufwidth + l->cols - 1) / l->cols;
 655  l->oldrows = rows;
 656
 657  abInit(&ab);
 658
 659  /* move cursor up to the first row, column 0 of the input area */
 660  if (rpos > 1) {
 661    snprintf(seq, 64, "\r\x1b[%dA", rpos - 1);
 662    abAppend(&ab, seq, strlen(seq));
 663  } else {
 664    abAppend(&ab, "\r", 1);
 665  }
 666
 667  /* clear all old rows */
 668  for (j = 0; j < old_rows; j++) {
 669    abAppend(&ab, "\x1b[0K", 4);
 670    if (j < old_rows - 1) {
 671      abAppend(&ab, "\n\r", 2);
 672    }
 673  }
 674
 675  /* move cursor back to the first row, column 0 */
 676  if (old_rows > 1) {
 677    snprintf(seq, 64, "\r\x1b[%dA", old_rows - 1);
 678    abAppend(&ab, seq, strlen(seq));
 679  } else {
 680    abAppend(&ab, "\r", 1);
 681  }
 682
 683  /* print prompt and new buffer */
 684  abAppend(&ab, l->prompt, l->plen);
 685  abAppend(&ab, l->buf, l->len);
 686
 687  /* if cursor is at the end of the line and wraps, print a newline */
 688  if (l->pos && l->pos == l->len && (poswidth + pwidth) % l->cols == 0) {
 689    abAppend(&ab, "\n\r", 2);
 690    rows++;
 691    if (rows > (int)l->oldrows)
 692      l->oldrows = rows;
 693  }
 694
 695  /* calculate cursor row and column */
 696  rpos2 = (pwidth + poswidth + l->cols) / l->cols;
 697  col   = (pwidth + poswidth) % l->cols;
 698
 699  /* move cursor to the correct row and column */
 700  if (rows - rpos2 > 0) {
 701    snprintf(seq, 64, "\x1b[%dA", rows - rpos2);
 702    abAppend(&ab, seq, strlen(seq));
 703  }
 704  if (col) {
 705    snprintf(seq, 64, "\r\x1b[%dC", col);
 706    abAppend(&ab, seq, strlen(seq));
 707  } else {
 708    abAppend(&ab, "\r", 1);
 709  }
 710
 711  l->oldpos  = l->pos;
 712  l->oldrpos = rpos2;
 713
 714  if (write(fd, ab.b, ab.len) == -1) {}
 715  abFree(&ab);
 716}
 717
 718static void
 719refreshLineWithFlags(struct redlineState *l, int flags)
 720{
 721  if (mlmode)
 722    refreshMultiLine(l, flags);
 723  else
 724    refreshSingleLine(l, flags);
 725}
 726
 727static void
 728refreshLine(struct redlineState *l)
 729{
 730  refreshLineWithFlags(l, 1);
 731}
 732
 733static int
 734completeLine(struct redlineState *ls, int keypressed)
 735{
 736  struct redlineCompletions lc = {0, NULL};
 737  size_t                    lcp_len;
 738  int                       c       = keypressed;
 739  int                       proceed = 1;
 740  char                      query[128];
 741  char                      answer = 0;
 742
 743  if (c != 9) {
 744    ls->in_completion = 0;
 745    return c;
 746  }
 747
 748  completionCallback(ls->buf, &lc);
 749  if (lc.len == 0) {
 750    redlineBeep();
 751    ls->in_completion = 0;
 752    c                 = 0;
 753  } else if (lc.len == 1) {
 754    size_t nwritten = snprintf(ls->buf, ls->buflen, "%s", lc.cvec[0]);
 755    ls->len = ls->pos = nwritten;
 756    refreshLine(ls);
 757    ls->in_completion = 0;
 758    c                 = 0;
 759  } else {
 760    lcp_len = longestCommonPrefix(&lc);
 761    if (lcp_len > ls->len) {
 762      size_t nwritten = snprintf(ls->buf, ls->buflen, "%.*s", (int)lcp_len, lc.cvec[0]);
 763      ls->len = ls->pos = nwritten;
 764      refreshLine(ls);
 765      ls->in_completion = 1;
 766      c                 = 0;
 767    } else {
 768      /* prefix cannot be expanded further */
 769      if (ls->in_completion == 0) {
 770        /* first tab: beep and wait for the second tab
 771         */
 772        redlineBeep();
 773        ls->in_completion = 1;
 774        c                 = 0;
 775      } else {
 776        /* second tab: display possibilities */
 777        if (lc.len > 100) {
 778          snprintf(
 779              query,
 780              sizeof(query),
 781              "\r\nDisplay all %d "
 782              "possibilities? (y or n) ",
 783              (int)lc.len
 784          );
 785          if (write(ls->ofd, query, strlen(query)) == -1) {}
 786          while (1) {
 787            if (read(ls->ifd, &answer, 1) != 1) {
 788              proceed = 0;
 789              break;
 790            }
 791            if (answer == 'y' || answer == 'Y' || answer == ' ' || answer == '\t') {
 792              proceed = 1;
 793              break;
 794            }
 795            if (answer == 'n' || answer == 'N' || answer == 27 || answer == 3 || answer == 4) {
 796              proceed = 0;
 797              break;
 798            }
 799            redlineBeep();
 800          }
 801        }
 802        if (proceed) {
 803          printCompletions(ls, &lc);
 804        } else {
 805          if (write(ls->ofd, "\r\n", 2) == -1) {}
 806        }
 807        ls->oldrows = 0;
 808        refreshLine(ls);
 809        ls->in_completion = 0;
 810        c                 = 0;
 811      }
 812    }
 813  }
 814
 815  freeCompletions(&lc);
 816  return c;
 817}
 818
 819static int
 820redlineEditInsert(struct redlineState *l, const char *c, int clen)
 821{
 822  if (l->len + clen >= l->buflen) {
 823    return 0;
 824  }
 825  if (l->len == l->pos) {
 826    memcpy(l->buf + l->pos, c, clen);
 827    l->pos += clen;
 828    l->len += clen;
 829    l->buf[l->len] = '\0';
 830    refreshLine(l);
 831  } else {
 832    memmove(l->buf + l->pos + clen, l->buf + l->pos, l->len - l->pos);
 833    memcpy(l->buf + l->pos, c, clen);
 834    l->pos += clen;
 835    l->len += clen;
 836    l->buf[l->len] = '\0';
 837    refreshLine(l);
 838  }
 839  return 1;
 840}
 841
 842static void
 843redlineEditBackspace(struct redlineState *l)
 844{
 845  if (l->pos > 0 && l->len > 0) {
 846    size_t clen = utf8PrevCharLen(l->buf, l->pos);
 847    memmove(l->buf + l->pos - clen, l->buf + l->pos, l->len - l->pos);
 848    l->pos -= clen;
 849    l->len -= clen;
 850    l->buf[l->len] = '\0';
 851    refreshLine(l);
 852  }
 853}
 854
 855static void
 856redlineEditDelete(struct redlineState *l)
 857{
 858  if (l->len > 0 && l->pos < l->len) {
 859    size_t clen = utf8NextCharLen(l->buf, l->pos, l->len);
 860    memmove(l->buf + l->pos, l->buf + l->pos + clen, l->len - l->pos - clen);
 861    l->len -= clen;
 862    l->buf[l->len] = '\0';
 863    refreshLine(l);
 864  }
 865}
 866
 867static void
 868redlineEditMoveLeft(struct redlineState *l)
 869{
 870  if (l->pos > 0) {
 871    l->pos -= utf8PrevCharLen(l->buf, l->pos);
 872    refreshLine(l);
 873  }
 874}
 875
 876static void
 877redlineEditMoveRight(struct redlineState *l)
 878{
 879  if (l->pos != l->len) {
 880    l->pos += utf8NextCharLen(l->buf, l->pos, l->len);
 881    refreshLine(l);
 882  }
 883}
 884
 885static void
 886redlineEditMoveHome(struct redlineState *l)
 887{
 888  if (l->pos != 0) {
 889    l->pos = 0;
 890    refreshLine(l);
 891  }
 892}
 893
 894static void
 895redlineEditMoveEnd(struct redlineState *l)
 896{
 897  if (l->pos != l->len) {
 898    l->pos = l->len;
 899    refreshLine(l);
 900  }
 901}
 902
 903static void
 904redlineEditMoveWordLeft(struct redlineState *l)
 905{
 906  if (l->pos > 0) {
 907    while (l->pos > 0 && l->buf[l->pos - 1] == ' ')
 908      l->pos -= utf8PrevCharLen(l->buf, l->pos);
 909    while (l->pos > 0 && l->buf[l->pos - 1] != ' ')
 910      l->pos -= utf8PrevCharLen(l->buf, l->pos);
 911    refreshLine(l);
 912  }
 913}
 914
 915static void
 916redlineEditMoveWordRight(struct redlineState *l)
 917{
 918  if (l->pos < l->len) {
 919    while (l->pos < l->len && l->buf[l->pos] == ' ')
 920      l->pos += utf8NextCharLen(l->buf, l->pos, l->len);
 921    while (l->pos < l->len && l->buf[l->pos] != ' ')
 922      l->pos += utf8NextCharLen(l->buf, l->pos, l->len);
 923    refreshLine(l);
 924  }
 925}
 926
 927static void
 928killBufferSave(const char *text, size_t len)
 929{
 930  free(kill_buffer);
 931  kill_buffer = malloc(len + 1);
 932  if (kill_buffer) {
 933    memcpy(kill_buffer, text, len);
 934    kill_buffer[len] = '\0';
 935  }
 936}
 937
 938static void
 939redlineEditDeleteWordRight(struct redlineState *l)
 940{
 941  size_t old_pos = l->pos;
 942  size_t diff;
 943  if (l->pos < l->len) {
 944    while (l->pos < l->len && l->buf[l->pos] == ' ')
 945      l->pos += utf8NextCharLen(l->buf, l->pos, l->len);
 946    while (l->pos < l->len && l->buf[l->pos] != ' ')
 947      l->pos += utf8NextCharLen(l->buf, l->pos, l->len);
 948    diff   = l->pos - old_pos;
 949    l->pos = old_pos;
 950    killBufferSave(l->buf + l->pos, diff);
 951    memmove(l->buf + l->pos, l->buf + l->pos + diff, l->len - l->pos - diff + 1);
 952    l->len -= diff;
 953    refreshLine(l);
 954  }
 955}
 956
 957static void
 958redlineEditDeletePrevWord(struct redlineState *l)
 959{
 960  size_t old_pos = l->pos;
 961  size_t diff;
 962  if (l->pos > 0) {
 963    while (l->pos > 0 && l->buf[l->pos - 1] == ' ')
 964      l->pos -= utf8PrevCharLen(l->buf, l->pos);
 965    while (l->pos > 0 && l->buf[l->pos - 1] != ' ')
 966      l->pos -= utf8PrevCharLen(l->buf, l->pos);
 967    diff = old_pos - l->pos;
 968    killBufferSave(l->buf + l->pos, diff);
 969    memmove(l->buf + l->pos, l->buf + old_pos, l->len - old_pos + 1);
 970    l->len -= diff;
 971    refreshLine(l);
 972  }
 973}
 974
 975void
 976redlineHistoryAdd(const char *line)
 977{
 978  char *linecopy;
 979  if (history_max_len == 0)
 980    return;
 981  if (history == NULL) {
 982    history = malloc(sizeof(char *) * history_max_len);
 983    if (history == NULL)
 984      return;
 985    memset(history, 0, sizeof(char *) * history_max_len);
 986  }
 987  if (history_len && strcmp(history[history_len - 1], line) == 0)
 988    return;
 989  linecopy = strdup(line);
 990  if (!linecopy)
 991    return;
 992  if (history_len == history_max_len) {
 993    free(history[0]);
 994    memmove(history, history + 1, sizeof(char *) * (history_max_len - 1));
 995    history_len--;
 996  }
 997  history[history_len] = linecopy;
 998  history_len++;
 999}
1000
1001void
1002redlineHistorySetMaxLen(int len)
1003{
1004  char **new;
1005  if (len < 1)
1006    return;
1007  if (history) {
1008    int tocopy = history_len;
1009    new        = malloc(sizeof(char *) * len);
1010    if (new == NULL)
1011      return;
1012    if (len < tocopy) {
1013      int j;
1014      for (j = 0; j < tocopy - len; j++)
1015        free(history[j]);
1016      tocopy = len;
1017    }
1018    memset(new, 0, sizeof(char *) * len);
1019    memcpy(new, history + (history_len - tocopy), sizeof(char *) * tocopy);
1020    free(history);
1021    history = new;
1022  }
1023  history_max_len = len;
1024  if (history_len > history_max_len)
1025    history_len = history_max_len;
1026}
1027
1028int
1029redlineHistorySave(const char *filename)
1030{
1031  mode_t old_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
1032  FILE  *fp;
1033  int    j;
1034
1035  fp = fopen(filename, "w");
1036  umask(old_umask);
1037  if (fp == NULL)
1038    return -1;
1039  chmod(filename, S_IRUSR | S_IWUSR);
1040  for (j = 0; j < history_len; j++) {
1041    fprintf(fp, "%s\n", history[j]);
1042  }
1043  fclose(fp);
1044  return 0;
1045}
1046
1047int
1048redlineHistoryLoad(const char *filename)
1049{
1050  FILE *fp = fopen(filename, "r");
1051  char  buf[REDLINE_INITIAL_BUFLEN];
1052  if (fp == NULL)
1053    return -1;
1054  while (fgets(buf, sizeof(buf), fp) != NULL) {
1055    char *p = strchr(buf, '\r');
1056    if (!p)
1057      p = strchr(buf, '\n');
1058    if (p)
1059      *p = '\0';
1060    redlineHistoryAdd(buf);
1061  }
1062  fclose(fp);
1063  return 0;
1064}
1065
1066char *
1067redlineHistoryGet(int idx)
1068{
1069  if (idx >= 0 && idx < history_len)
1070    return history[idx];
1071  return NULL;
1072}
1073
1074int
1075redlineHistoryLen(void)
1076{
1077  return history_len;
1078}
1079
1080static void
1081redlineEditHistoryNext(struct redlineState *l, int dir)
1082{
1083  if (history_len > 1) {
1084    const char *src;
1085    size_t      len;
1086    free(history[history_len - 1 - l->history_index]);
1087    history[history_len - 1 - l->history_index] = strdup(l->buf);
1088    l->history_index += (dir == 1) ? 1 : -1;
1089    if (l->history_index < 0) {
1090      l->history_index = 0;
1091      return;
1092    } else if (l->history_index >= history_len) {
1093      l->history_index = history_len - 1;
1094      return;
1095    }
1096    src = history[history_len - 1 - l->history_index];
1097    len = strlen(src);
1098    if (len >= l->buflen)
1099      len = l->buflen - 1;
1100    memcpy(l->buf, src, len);
1101    l->buf[len] = '\0';
1102    l->len = l->pos = len;
1103    refreshLine(l);
1104  }
1105}
1106
1107static char *
1108redlineReadLine(FILE *fp)
1109{
1110  char  *line = NULL;
1111  size_t len = 0, cap = 0;
1112  while (1) {
1113    if (len + 1 >= cap) {
1114      size_t newcap = cap ? cap * 2 : 16;
1115      char *new     = realloc(line, newcap);
1116      if (!new) {
1117        free(line);
1118        return NULL;
1119      }
1120      line = new;
1121      cap  = newcap;
1122    }
1123    int c = fgetc(fp);
1124    if (c == EOF || c == '\n') {
1125      if (c == EOF && len == 0) {
1126        free(line);
1127        return NULL;
1128      }
1129      line[len] = '\0';
1130      return line;
1131    }
1132    line[len++] = c;
1133  }
1134}
1135
1136static char *
1137redlineNoTTY(void)
1138{
1139  return redlineReadLine(stdin);
1140}
1141
1142void
1143redlineClearScreen(void)
1144{
1145  if (write(STDOUT_FILENO, "\x1b[H\x1b[2J", 7) == -1) {}
1146}
1147
1148static char *
1149redlineEditFeed(struct redlineState *l)
1150{
1151  char   c;
1152  int    nread;
1153  char   seq[3];
1154  char   param[8];
1155  size_t plen;
1156  char   final;
1157  char   p;
1158  int    is_word_jump;
1159  char   tmp[32];
1160  size_t prevlen;
1161  size_t currlen;
1162  size_t prevstart;
1163  char   utf8[4];
1164  int    utf8len;
1165  int    i;
1166
1167  if (!isatty(l->ifd) && !getenv("REDLINE_ASSUME_TTY"))
1168    return redlineNoTTY();
1169
1170  while (1) {
1171    nread = read(l->ifd, &c, 1);
1172    if (nread < 0) {
1173      if (errno == EINTR) {
1174        if (winch_received) {
1175          winch_received = 0;
1176          l->cols        = getColumns(l->ifd, l->ofd);
1177          refreshLine(l);
1178        }
1179        continue;
1180      }
1181      return (errno == EAGAIN || errno == EWOULDBLOCK) ? "more" : NULL;
1182    } else if (nread == 0) {
1183      return NULL;
1184    }
1185    break;
1186  }
1187
1188  if ((l->in_completion || c == 9) && completionCallback != NULL) {
1189    int retval = completeLine(l, c);
1190    if (retval == 0)
1191      return "more";
1192    c = retval;
1193  }
1194
1195  switch (c) {
1196    case 10:
1197    case ENTER:
1198      if (mlmode)
1199        redlineEditMoveEnd(l);
1200      return strdup(l->buf);
1201    case CTRL_C:
1202      if (write(l->ofd, "^C", 2) == -1) {}
1203      errno = EAGAIN;
1204      return NULL;
1205    case CTRL_Z:
1206      break;
1207    case CTRL_QUIT:
1208      if (write(l->ofd, "^\\", 2) == -1) {}
1209      redlineEditStop(l);
1210      fflush(stdout);
1211      {
1212        struct sigaction sa, osa;
1213        sa.sa_handler = SIG_DFL;
1214        sigemptyset(&sa.sa_mask);
1215        sa.sa_flags = 0;
1216        sigaction(SIGQUIT, &sa, &osa);
1217        kill(getpid(), SIGQUIT);
1218        sigaction(SIGQUIT, &osa, NULL);
1219      }
1220      enableRawMode(l->ifd);
1221      refreshLine(l);
1222      break;
1223    case BACKSPACE:
1224    case 8:
1225      redlineEditBackspace(l);
1226      break;
1227    case CTRL_D:
1228      if (l->len > 0) {
1229        redlineEditDelete(l);
1230      } else {
1231        errno = ENOENT;
1232        return NULL;
1233      }
1234      break;
1235    case CTRL_T:
1236      if (l->pos > 0 && l->pos < l->len) {
1237        prevlen   = utf8PrevCharLen(l->buf, l->pos);
1238        currlen   = utf8NextCharLen(l->buf, l->pos, l->len);
1239        prevstart = l->pos - prevlen;
1240        if (prevlen > sizeof(tmp) || currlen > sizeof(tmp))
1241          break;
1242        memcpy(tmp, l->buf + l->pos, currlen);
1243        memmove(l->buf + prevstart + currlen, l->buf + prevstart, prevlen);
1244        memcpy(l->buf + prevstart, tmp, currlen);
1245        if (l->pos + currlen <= l->len)
1246          l->pos += currlen;
1247        refreshLine(l);
1248      }
1249      break;
1250    case CTRL_B:
1251      redlineEditMoveLeft(l);
1252      break;
1253    case CTRL_F:
1254      redlineEditMoveRight(l);
1255      break;
1256    case CTRL_P:
1257      redlineEditHistoryNext(l, 1);
1258      break;
1259    case CTRL_N:
1260      redlineEditHistoryNext(l, 0);
1261      break;
1262    case ESC:
1263      if (read(l->ifd, seq, 1) == -1)
1264        break;
1265      if (seq[0] == '[' || seq[0] == 'O') {
1266        if (read(l->ifd, seq + 1, 1) == -1)
1267          break;
1268        if (seq[0] == '[') {
1269          if (seq[1] >= '0' && seq[1] <= '9') {
1270            plen     = 1;
1271            final    = 0;
1272            param[0] = seq[1];
1273            while (plen < sizeof(param)) {
1274              if (read(l->ifd, &p, 1) != 1)
1275                break;
1276              if ((p >= '0' && p <= '9') || p == ';') {
1277                param[plen++] = p;
1278              } else {
1279                final = p;
1280                break;
1281              }
1282            }
1283            if (final == '~') {
1284              if (plen == 1 && param[0] == '3') {
1285                redlineEditDelete(l);
1286              }
1287            } else if (final == 'D' || final == 'C') {
1288              is_word_jump = 0;
1289              if (plen == 3 && param[0] == '1' && param[1] == ';'
1290                  && (param[2] == '5' || param[2] == '3')) {
1291                is_word_jump = 1;
1292              } else if (plen == 1 && (param[0] == '5' || param[0] == '3')) {
1293                is_word_jump = 1;
1294              }
1295              if (is_word_jump) {
1296                if (final == 'D') {
1297                  redlineEditMoveWordLeft(l);
1298                } else {
1299                  redlineEditMoveWordRight(l);
1300                }
1301              }
1302            }
1303          } else {
1304            switch (seq[1]) {
1305              case 'A':
1306                redlineEditHistoryNext(l, 1);
1307                break;
1308              case 'B':
1309                redlineEditHistoryNext(l, 0);
1310                break;
1311              case 'C':
1312                redlineEditMoveRight(l);
1313                break;
1314              case 'D':
1315                redlineEditMoveLeft(l);
1316                break;
1317              case 'H':
1318                redlineEditMoveHome(l);
1319                break;
1320              case 'F':
1321                redlineEditMoveEnd(l);
1322                break;
1323            }
1324          }
1325        } else if (seq[0] == 'O') {
1326          switch (seq[1]) {
1327            case 'H':
1328              redlineEditMoveHome(l);
1329              break;
1330            case 'F':
1331              redlineEditMoveEnd(l);
1332              break;
1333          }
1334        }
1335      } else {
1336        if (seq[0] == 'b' || seq[0] == 'B') {
1337          redlineEditMoveWordLeft(l);
1338        } else if (seq[0] == 'f' || seq[0] == 'F') {
1339          redlineEditMoveWordRight(l);
1340        } else if (seq[0] == 'd' || seq[0] == 'D') {
1341          redlineEditDeleteWordRight(l);
1342        } else if (seq[0] == 127 || seq[0] == 8) {
1343          redlineEditDeletePrevWord(l);
1344        }
1345      }
1346      break;
1347    default:
1348      if (c < 32)
1349        break;
1350      utf8len = utf8ByteLen(c);
1351      utf8[0] = c;
1352      if (utf8len > 1) {
1353        for (i = 1; i < utf8len; i++) {
1354          if (read(l->ifd, utf8 + i, 1) != 1)
1355            break;
1356        }
1357      }
1358      if (redlineEditInsert(l, utf8, utf8len) == 0)
1359        return NULL;
1360      break;
1361    case CTRL_U:
1362      killBufferSave(l->buf, l->pos);
1363      memmove(l->buf, l->buf + l->pos, l->len - l->pos + 1);
1364      l->len -= l->pos;
1365      l->pos = 0;
1366      refreshLine(l);
1367      break;
1368    case CTRL_K:
1369      killBufferSave(l->buf + l->pos, l->len - l->pos);
1370      l->buf[l->pos] = '\0';
1371      l->len         = l->pos;
1372      refreshLine(l);
1373      break;
1374    case CTRL_A:
1375      redlineEditMoveHome(l);
1376      break;
1377    case CTRL_E:
1378      redlineEditMoveEnd(l);
1379      break;
1380    case CTRL_L:
1381      redlineClearScreen();
1382      refreshLine(l);
1383      break;
1384    case CTRL_W:
1385      redlineEditDeletePrevWord(l);
1386      break;
1387    case CTRL_Y:
1388      if (kill_buffer) {
1389        redlineEditInsert(l, kill_buffer, strlen(kill_buffer));
1390      }
1391      break;
1392  }
1393  return "more";
1394}
1395
1396static int
1397redlineEditStart(
1398    struct redlineState *l,
1399    int                  stdin_fd,
1400    int                  stdout_fd,
1401    char                *buf,
1402    size_t               buflen,
1403    const char          *prompt
1404)
1405{
1406  l->in_completion = 0;
1407  l->ifd           = stdin_fd;
1408  l->ofd           = stdout_fd;
1409  l->buf           = buf;
1410  l->buflen        = buflen;
1411  l->prompt        = prompt;
1412  l->plen          = strlen(prompt);
1413  l->pos           = 0;
1414  l->oldpos        = 0;
1415  l->len           = 0;
1416  l->cols          = getColumns(stdin_fd, stdout_fd);
1417  l->oldrows       = 0;
1418  l->oldrpos       = 0;
1419  l->history_index = 0;
1420  l->buf[0]        = '\0';
1421
1422  if (enableRawMode(l->ifd) == -1)
1423    return -1;
1424  refreshLine(l);
1425  return 0;
1426}
1427
1428static void
1429redlineEditStop(struct redlineState *l)
1430{
1431  if (!isatty(l->ifd) && !getenv("REDLINE_ASSUME_TTY"))
1432    return;
1433  disableRawMode(l->ifd);
1434  printf("\n");
1435}
1436
1437char *
1438redline(const char *prompt)
1439{
1440  struct redlineState l;
1441  char               *buf;
1442  char               *res;
1443
1444  if (!isatty(STDIN_FILENO) || isUnsupportedTerm()) {
1445    if (write(STDOUT_FILENO, prompt, strlen(prompt)) == -1) {}
1446    return redlineNoTTY();
1447  }
1448
1449  buf = malloc(REDLINE_INITIAL_BUFLEN);
1450  if (buf == NULL)
1451    return NULL;
1452  if (redlineEditStart(&l, STDIN_FILENO, STDOUT_FILENO, buf, REDLINE_INITIAL_BUFLEN, prompt)
1453      == -1) {
1454    free(buf);
1455    return NULL;
1456  }
1457  redlineHistoryAdd("");
1458  while (1) {
1459    res = redlineEditFeed(&l);
1460    if (res == NULL || strcmp(res, "more") != 0) {
1461      break;
1462    }
1463  }
1464  redlineEditStop(&l);
1465  if (history_len > 0) {
1466    history_len--;
1467    free(history[history_len]);
1468  }
1469  free(l.buf);
1470  if (res == NULL && errno == EAGAIN) {
1471    kill(getpid(), SIGINT);
1472  }
1473  return res;
1474}
1475
1476void
1477redlineSetCompletionCallback(void (*cb)(const char *, struct redlineCompletions *))
1478{
1479  completionCallback = cb;
1480}
1481
1482void
1483redlineAddCompletion(struct redlineCompletions *lc, const char *str)
1484{
1485  size_t len = strlen(str);
1486  char  *copy, **cvec;
1487
1488  copy = malloc(len + 1);
1489  if (copy == NULL)
1490    return;
1491  memcpy(copy, str, len + 1);
1492  cvec = realloc(lc->cvec, sizeof(char *) * (lc->len + 1));
1493  if (cvec == NULL) {
1494    free(copy);
1495    return;
1496  }
1497  lc->cvec            = cvec;
1498  lc->cvec[lc->len++] = copy;
1499}
1500
1501void
1502redlineSetMultiLine(int ml)
1503{
1504  mlmode = ml;
1505}