+static void clearScreen(struct current *current)
+{
+ fd_printf(current->fd, "\x1b[H\x1b[2J");
+}
+
+static void cursorToLeft(struct current *current)
+{
+ fd_printf(current->fd, "\x1b[1G");
+}
+
+static int outputChars(struct current *current, const char *buf, int len)
+{
+ return write(current->fd, buf, len);
+}
+
+static void outputControlChar(struct current *current, char ch)
+{
+ fd_printf(current->fd, "\033[7m^%c\033[0m", ch);
+}
+
+static void eraseEol(struct current *current)
+{
+ fd_printf(current->fd, "\x1b[0K");
+}
+
+static void setCursorPos(struct current *current, int x)
+{
+ fd_printf(current->fd, "\x1b[1G\x1b[%dC", x);
+}
+
+/**
+ * Reads a char from 'fd', waiting at most 'timeout' milliseconds.
+ *
+ * A timeout of -1 means to wait forever.
+ *
+ * Returns -1 if no char is received within the time or an error occurs.
+ */
+static int fd_read_char(int fd, int timeout)
+{
+ struct pollfd p;
+ unsigned char c;
+
+ p.fd = fd;
+ p.events = POLLIN;
+
+ if (poll(&p, 1, timeout) == 0) {
+ /* timeout */
+ return -1;
+ }
+ if (read(fd, &c, 1) != 1) {
+ return -1;
+ }
+ return c;
+}
+
+/**
+ * Reads a complete utf-8 character
+ * and returns the unicode value, or -1 on error.
+ */
+static int fd_read(struct current *current)
+{
+#ifdef USE_UTF8
+ char buf[4];
+ int n;
+ int i;
+ int c;
+
+ if (read(current->fd, &buf[0], 1) != 1) {
+ return -1;
+ }
+ n = utf8_charlen(buf[0]);
+ if (n < 1 || n > 3) {
+ return -1;
+ }
+ for (i = 1; i < n; i++) {
+ if (read(current->fd, &buf[i], 1) != 1) {
+ return -1;
+ }
+ }
+ buf[n] = 0;
+ /* decode and return the character */
+ utf8_tounicode(buf, &c);
+ return c;
+#else
+ return fd_read_char(current->fd, -1);
+#endif
+}
+
+static int getWindowSize(struct current *current)
+{
+ struct winsize ws;
+
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col != 0) {
+ current->cols = ws.ws_col;
+ return 0;
+ }
+
+ /* Failed to query the window size. Perhaps we are on a serial terminal.
+ * Try to query the width by sending the cursor as far to the right
+ * and reading back the cursor position.
+ * Note that this is only done once per call to linenoise rather than
+ * every time the line is refreshed for efficiency reasons.
+ */
+ if (current->cols == 0) {
+ current->cols = 80;
+
+ /* Move cursor far right and report cursor position */
+ fd_printf(current->fd, "\x1b[999G" "\x1b[6n");
+
+ /* Parse the response: ESC [ rows ; cols R */
+ if (fd_read_char(current->fd, 100) == 0x1b && fd_read_char(current->fd, 100) == '[') {
+ int n = 0;
+ while (1) {
+ int ch = fd_read_char(current->fd, 100);
+ if (ch == ';') {
+ /* Ignore rows */
+ n = 0;
+ }
+ else if (ch == 'R') {
+ /* Got cols */
+ if (n != 0 && n < 1000) {
+ current->cols = n;
+ }
+ break;
+ }
+ else if (ch >= 0 && ch <= '9') {
+ n = n * 10 + ch - '0';
+ }
+ else {
+ break;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+/**
+ * If escape (27) was received, reads subsequent
+ * chars to determine if this is a known special key.
+ *
+ * Returns SPECIAL_NONE if unrecognised, or -1 if EOF.
+ *
+ * If no additional char is received within a short time,
+ * 27 is returned.
+ */
+static int check_special(int fd)
+{
+ int c = fd_read_char(fd, 50);
+ int c2;
+
+ if (c < 0) {
+ return 27;
+ }
+
+ c2 = fd_read_char(fd, 50);
+ if (c2 < 0) {
+ return c2;
+ }
+ if (c == '[' || c == 'O') {
+ /* Potential arrow key */
+ switch (c2) {
+ case 'A':
+ return SPECIAL_UP;
+ case 'B':
+ return SPECIAL_DOWN;
+ case 'C':
+ return SPECIAL_RIGHT;
+ case 'D':
+ return SPECIAL_LEFT;
+ case 'F':
+ return SPECIAL_END;
+ case 'H':
+ return SPECIAL_HOME;
+ }
+ }
+ if (c == '[' && c2 >= '1' && c2 <= '6') {
+ /* extended escape */
+ int c3 = fd_read_char(fd, 50);
+ if (c2 == '3' && c3 == '~') {
+ /* delete char under cursor */
+ return SPECIAL_DELETE;
+ }
+ while (c3 != -1 && c3 != '~') {
+ /* .e.g \e[12~ or '\e[11;2~ discard the complete sequence */
+ c3 = fd_read_char(fd, 50);
+ }
+ }
+
+ return SPECIAL_NONE;
+}
+