text: /* linenoise.c -- guerrilla line editing library.
   *
   * See LICENSE for license stuff.
   *
   * EL (Erase Line)
   *    Sequence: ESC [ n K
   *    Effect: if n is 0 or missing, clear from cursor to end of line
   *    Effect: if n is 1, clear from beginning of line to cursor
   *    Effect: if n is 2, clear entire line
   *
   * CUF (CUrsor Forward)
   *    Sequence: ESC [ n C
   *    Effect: moves cursor forward n chars
   *
   * CUB (CUrsor Backward)
   *    Sequence: ESC [ n D
   *    Effect: moves cursor backward n chars
   *
   * The following is used to get the terminal width if getting
   * the width with the TIOCGWINSZ ioctl fails
   *
   * DSR (Device Status Report)
   *    Sequence: ESC [ 6 n
   *    Effect: reports the current cusor position as ESC [ n ; m R
   *            where n is the row and m is the column
   *
   * When multi line mode is enabled, we also use an additional escape
   * sequence. However multi line editing is disabled by default.
   *
   * CUU (Cursor Up)
   *    Sequence: ESC [ n A
   *    Effect: moves cursor up of n chars.
   *
   * CUD (Cursor Down)
   *    Sequence: ESC [ n B
   *    Effect: moves cursor down of n chars.
   */
  
  #include <termios.h>
  #include <unistd.h>
  #include <stdio.h>
  #include <string.h>
  
  #include "linenoise.h"
  
  static struct termios orig_termios; /* In order to restore at exit.*/
  static int rawmode = 0; /* For atexit() function to check if restore is needed*/
  static int history_pos = 0;
  static char history[LINENOISE_HISTORY_MAX_LEN][LINENOISE_MAX_LINE];
  
  /* The linenoiseState structure represents the state during line editing.
   * We pass this state to functions implementing specific editing
   * functionalities. */
  struct linenoiseState {
      int ifd;            /* Terminal stdin file descriptor. */
      int ofd;            /* Terminal stdout file descriptor. */
      char *buf;          /* Edited line buffer. */
      size_t buflen;      /* Edited line buffer size. */
      const char *prompt; /* Prompt to display. */
      size_t plen;        /* Prompt length. */
      size_t pos;         /* Current cursor position. */
      size_t oldpos;      /* Previous refresh cursor position. */
      size_t len;         /* Current edited line length. */
      size_t cols;        /* Number of columns in terminal. */
      int history_index;  /* The history index we are currently editing. */
  };
  
  enum KEY_ACTION{
  	KEY_NULL = 0,	    /* NULL */
  	CTRL_A = 1,         /* Ctrl+a */
  	CTRL_B = 2,         /* Ctrl-b */
  	CTRL_C = 3,         /* Ctrl-c */
  	CTRL_D = 4,         /* Ctrl-d */
  	CTRL_E = 5,         /* Ctrl-e */
  	CTRL_F = 6,         /* Ctrl-f */
  	CTRL_H = 8,         /* Ctrl-h */
  	TAB = 9,            /* Tab */
  	CTRL_K = 11,        /* Ctrl+k */
  	ENTER = 13,         /* Enter */
  	CTRL_N = 14,        /* Ctrl-n */
  	CTRL_P = 16,        /* Ctrl-p */
  	CTRL_T = 20,        /* Ctrl-t */
  	CTRL_U = 21,        /* Ctrl+u */
  	CTRL_W = 23,        /* Ctrl+w */
  	ESC = 27,           /* Escape */
  	BACKSPACE =  127    /* Backspace */
  };
  
  int linenoiseHistoryAdd(const char *line);
  static void refreshLine(struct linenoiseState *l);
  
  
  /* ======================= Low level terminal handling ====================== */
  
  /* Raw mode: 1960 magic shit. */
  static int enableRawMode(int fd) {
       struct termios raw;
  
      if (tcgetattr(fd,&orig_termios) == -1) goto fatal;
  
      raw = orig_termios;  /* modify the original mode */
      /* input modes: no break, no CR to NL, no parity check, no strip char,
       * no start/stop output control. */
      raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
      /* output modes - disable post processing */
      raw.c_oflag &= ~(OPOST);
      /* control modes - set 8 bit chars */
      raw.c_cflag |= (CS8);
      /* local modes - choing off, canonical off, no extended functions,
       * no signal chars (^Z,^C) */
      raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
      /* control chars - set return condition: min number of bytes and timer.
       * We want read to return every single byte, without timeout. */
      raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */
  
      /* put terminal in raw mode after flushing */
      if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal;
      rawmode = 1;
      return 0;
  
  fatal:
      return -2;
  }
  
  static void disableRawMode(int fd) {
      /* Don't even check the return value as it's too late. */
      if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1)
          rawmode = 0;
  }
  
  
  
  /* ====================== Line editing ============================ */
  
  
  /* Single line low level line refresh.
   *
   * Rewrite the currently edited line accordingly to the buffer content,
   * cursor position, and number of columns of the terminal. */
  static void refreshLine(struct linenoiseState *l) {
      char seq[64];
      size_t plen = strlen(l->prompt);
      int fd = l->ofd;
      char *buf = l->buf;
      size_t len = l->len;
      size_t pos = l->pos;
      char ab[LINENOISE_ABUF_MAX_LEN];
      ab[0] = '\0';
  
      while((plen+pos) >= l->cols) {
          buf++;
          len--;
          pos--;
      }
      while (plen+len > l->cols) {
          len--;
      }
  
      /* Cursor to left edge */
      snprintf(seq,64,"\r");
      strcat(ab, seq);
      /* Write the prompt and the current buffer content */
      strcat(ab, l->prompt);
      strcat(ab, buf);
      /* Erase to right */
      snprintf(seq,64,"\x1b[0K");
      strcat(ab, seq);
      /* Move cursor to original position. */
      snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen));
      strcat(ab, seq);
      if (write(fd,ab,strlen(ab)) == -1) {} /* Can't recover from write error. */
  }
  
  /* Insert the character 'c' at cursor current position.
   *
   * On error writing to the terminal -1 is returned, otherwise 0. */
  int linenoiseEditInsert(struct linenoiseState *l, char c) {
      if (l->len < l->buflen) {
          if (l->len == l->pos) {
              l->buf[l->pos] = c;
              l->pos++;
              l->len++;
              l->buf[l->len] = '\0';
              if ((l->plen+l->len < l->cols)) {
                  /* Avoid a full update of the line in the
                   * trivial case. */
                  if (write(l->ofd,&c,1) == -1) return -1;
              } else {
                  refreshLine(l);
              }
          } else {
              l->buf[l->pos] = c;
              l->len++;
              l->pos++;
              l->buf[l->len] = '\0';
              refreshLine(l);
          }
      }
      return 0;
  }
  
  /* Move cursor on the left. */
  void linenoiseEditMoveLeft(struct linenoiseState *l) {
      if (l->pos > 0) {
          l->pos--;
          refreshLine(l);
      }
  }
  
  /* Move cursor on the right. */
  void linenoiseEditMoveRight(struct linenoiseState *l) {
      if (l->pos != l->len) {
          l->pos++;
          refreshLine(l);
      }
  }
  
  /* Move cursor to the start of the line. */
  void linenoiseEditMoveHome(struct linenoiseState *l) {
      if (l->pos != 0) {
          l->pos = 0;
          refreshLine(l);
      }
  }
  
  /* Move cursor to the end of the line. */
  void linenoiseEditMoveEnd(struct linenoiseState *l) {
      if (l->pos != l->len) {
          l->pos = l->len;
          refreshLine(l);
      }
  }
  
  /* Substitute the currently edited line with the next or previous history
   * entry as specified by 'dir'. */
  #define LINENOISE_HISTORY_NEXT 0
  #define LINENOISE_HISTORY_PREV 1
  void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) {
      if (history_pos > 1) {
  	strncpy(history[history_pos - l->history_index],
  		l->buf,
  		LINENOISE_MAX_LINE);
          /* Show the new entry */
          l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1;
          if (l->history_index < 0) {
              l->history_index = 0;
              return;
          } else if (l->history_index >= history_pos) {
              l->history_index = history_pos-1;
              return;
          }
          strncpy(l->buf,history[history_pos - l->history_index],l->buflen);
          l->buf[l->buflen-1] = '\0';
          l->len = l->pos = strlen(l->buf);
          refreshLine(l);
      }
  }
  
  /* Delete the character at the right of the cursor without altering the cursor
   * position. Basically this is what happens with the "Delete" keyboard key. */
  void linenoiseEditDelete(struct linenoiseState *l) {
      if (l->len > 0 && l->pos < l->len) {
          l->len--;
          l->buf[l->len] = '\0';
          refreshLine(l);
      }
  }
  
  /* Backspace implementation. */
  void linenoiseEditBackspace(struct linenoiseState *l) {
      if (l->pos > 0 && l->len > 0) {
          l->pos--;
          l->len--;
          l->buf[l->len] = '\0';
          refreshLine(l);
      }
  }
  
  /* Delete the previous word, maintaining the cursor at the start of the
   * current word. */
  void linenoiseEditDeletePrevWord(struct linenoiseState *l) {
      size_t old_pos = l->pos;
      size_t diff;
  
      while (l->pos > 0 && l->buf[l->pos-1] == ' ')
          l->pos--;
      while (l->pos > 0 && l->buf[l->pos-1] != ' ')
          l->pos--;
      diff = old_pos - l->pos;
      l->len -= diff;
      refreshLine(l);
  }
  
  /* This function is the core of the line editing capability of linenoise.
   * It expects 'fd' to be already in "raw mode" so that every key pressed
   * will be returned ASAP to read().
   *
   * The resulting string is put into 'buf' when the user type enter, or
   * when ctrl+d is typed.
   *
   * The function returns the length of the current buffer. */
  static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt)
  {
      struct linenoiseState l;
  
      /* Populate the linenoise state that we pass to functions implementing
       * specific editing functionalities. */
      l.ifd = stdin_fd;
      l.ofd = stdout_fd;
      l.buf = buf;
      l.buflen = buflen;
      l.prompt = prompt;
      l.plen = strlen(prompt);
      l.oldpos = l.pos = 0;
      l.len = 0;
      l.cols = LINENOISE_MAX_COLS;
      l.history_index = 0;
  
      /* Buffer starts empty. */
      l.buf[0] = '\0';
      l.buflen--; /* Make sure there is always space for the nulterm */
  
      /* The latest history entry is always our current buffer, that
       * initially is just an empty string. */
      linenoiseHistoryAdd("");
  
      if (write(l.ofd,prompt,l.plen) == -1) return -1; /* OUT */
      while(1) {
          char c;
          int nread;
          char seq[3];
  
          nread = read(l.ifd,&c,1); /* IN */
          if (nread <= 0) return l.len;
  
          switch(c) {
          case ENTER:    /* enter */
              return (int)l.len;
          case CTRL_C:     /* ctrl-c */
              return -1;
          case BACKSPACE:   /* backspace */
          case 8:     /* ctrl-h */
              linenoiseEditBackspace(&l);
              break;
          case CTRL_D:     /* ctrl-d, remove char at right of cursor, or if the
                              line is empty, act as end-of-file. */
              if (l.len > 0) {
                  linenoiseEditDelete(&l);
              } else {
                  return -1;
              }
              break;
          case CTRL_T:    /* ctrl-t, swaps current character with previous. */
              if (l.pos > 0 && l.pos < l.len) {
                  int aux = buf[l.pos-1];
                  buf[l.pos-1] = buf[l.pos];
                  buf[l.pos] = aux;
                  if (l.pos != l.len-1) l.pos++;
                  refreshLine(&l);
              }
              break;
          case CTRL_B:     /* ctrl-b */
              linenoiseEditMoveLeft(&l);
              break;
          case CTRL_F:     /* ctrl-f */
              linenoiseEditMoveRight(&l);
              break;
          case CTRL_P:    /* ctrl-p */
              linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV);
              break;
          case CTRL_N:    /* ctrl-n */
              linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT);
              break;
          case ESC:    /* escape sequence */
              /* Read the next two bytes representing the escape sequence.
               * Use two calls to handle slow terminals returning the two
               * chars at different times. */
              if (read(l.ifd,seq,1) == -1) break;
              if (read(l.ifd,seq+1,1) == -1) break;
  
              /* ESC [ sequences. */
              if (seq[0] == '[') {
                  if (seq[1] >= '0' && seq[1] <= '9') {
                      /* Extended escape, read additional byte. */
                      if (read(l.ifd,seq+2,1) == -1) break; /* IN */
                      if (seq[2] == '~') {
                          switch(seq[1]) {
                          case '3': /* Delete key. */
                              linenoiseEditDelete(&l);
                              break;
                          }
                      }
                  } else {
                      switch(seq[1]) {
                      case 'A': /* Up */
                          linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV);
                          break;
                      case 'B': /* Down */
                          linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT);
                          break;
                      case 'C': /* Right */
                          linenoiseEditMoveRight(&l);
                          break;
                      case 'D': /* Left */
                          linenoiseEditMoveLeft(&l);
                          break;
                      case 'H': /* Home */
                          linenoiseEditMoveHome(&l);
                          break;
                      case 'F': /* End*/
                          linenoiseEditMoveEnd(&l);
                          break;
                      }
                  }
              }
              break;
          default:
              if (linenoiseEditInsert(&l,c)) return -1;
              break;
          case CTRL_U: /* Ctrl+u, delete the whole line. */
              buf[0] = '\0';
              l.pos = l.len = 0;
              refreshLine(&l);
              break;
          case CTRL_K: /* Ctrl+k, delete from current to end of line. */
              buf[l.pos] = '\0';
              l.len = l.pos;
              refreshLine(&l);
              break;
          case CTRL_A: /* Ctrl+a, go to the start of the line */
              linenoiseEditMoveHome(&l);
              break;
          case CTRL_E: /* ctrl+e, go to the end of the line */
              linenoiseEditMoveEnd(&l);
              break;
          case CTRL_W: /* ctrl+w, delete previous word */
              linenoiseEditDeletePrevWord(&l);
              break;
          }
      }
      return l.len;
  }
  
  
  /* This function calls the line editing function linenoiseEdit() using
   * the STDIN file descriptor set in raw mode. */
  static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) {
      int count;
  
      if (buflen == 0) {
          return -3;
      }
  
      if (enableRawMode(STDIN_FILENO) == -1) return -1;
      count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt);
      disableRawMode(STDIN_FILENO);
      printf("\n");
      return count;
  }
  
  
  /* The high level function that is the main API of the linenoise library.*/
  char *linenoise(const char *prompt, char* input) {
      int count;
      count = linenoiseRaw(input, LINENOISE_MAX_LINE, prompt);
      if (count == -1) return NULL;
      return input;
  }
  
  /* ================================ History ================================= */
  
  /* At exit we'll try to fix the terminal to the initial conditions. */
  void linenoiseAtExit(void) {
      disableRawMode(STDIN_FILENO);
  }
  
  /* This is the API call to add a new entry in the linenoise history.
   * It uses a fixed array of char pointers that are shifted (memmoved)
   * when the history max length is reached in order to remove the older
   * entry and make room for the new one, so it is not exactly suitable for huge
   * histories, but will work well for a few hundred of entries. */
  
  int linenoiseHistoryAdd(const char *line) {
      if (LINENOISE_HISTORY_MAX_LEN == 0) return 0;
  
      /* Don't add duplicated lines. */
      if (history_pos && !strcmp(history[history_pos], line)) return 0;
  
      if (history_pos == LINENOISE_HISTORY_MAX_LEN) {
          history_pos = 0; 	/* start populating from beginning */
      }
      strncpy(history[history_pos], line, LINENOISE_MAX_LINE);
      history_pos++;
      return 1;
  }
  