/* * Copyright (c) 2018 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include "line.h" /* Common */ int dst_line_getter(DstArgs args) { DST_FIXARITY(args, 2); DST_CHECK(args, 0, DST_STRING); DST_CHECK(args, 1, DST_BUFFER); dst_line_get( dst_unwrap_string(args.v[0]), dst_unwrap_buffer(args.v[1])); DST_RETURN(args, args.v[0]); } static void simpleline(DstBuffer *buffer) { buffer->count = 0; char c; for (;;) { c = fgetc(stdin); if (feof(stdin) || c < 0) { break; } dst_buffer_push_u8(buffer, (uint8_t) c); if (c == '\n') break; } } /* Windows */ #ifdef DST_WINDOWS void dst_line_init() { ; } void dst_line_deinit() { ; } void dst_line_get(const uint8_t *p, DstBuffer *buffer) { fputs((const char *)p, stdout); simpleline(buffer); } /* Posix */ #else /* https://github.com/antirez/linenoise/blob/master/linenoise.c */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* static state */ #define DST_LINE_MAX 1024 #define DST_HISTORY_MAX 100 static int israwmode = 0; static const char *prompt = "> "; static int plen = 2; static char buf[DST_LINE_MAX]; static int len = 0; static int pos = 0; static int cols = 80; static char **history = NULL; static int historyi = 0; static struct termios termios_start; /* Unsupported terminal list from linenoise */ static const char *badterms[] = { "cons25", "dumb", "emacs", NULL }; static char *sdup(const char *s) { size_t len = strlen(s) + 1; char *mem = malloc(len); if (!mem) { return NULL; } return memcpy(mem, s, len); } /* Ansi terminal raw mode */ static int rawmode() { struct termios t; if (!isatty(STDIN_FILENO)) goto fatal; if (tcgetattr(STDIN_FILENO, &termios_start) == -1) goto fatal; t = termios_start; t.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); t.c_oflag &= ~(OPOST); t.c_cflag |= (CS8); t.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); t.c_cc[VMIN] = 1; t.c_cc[VTIME] = 0; if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &t) < 0) goto fatal; israwmode = 1; return 0; fatal: errno = ENOTTY; return -1; } /* Disable raw mode */ static void norawmode() { if (israwmode && tcsetattr(STDIN_FILENO, TCSAFLUSH, &termios_start) != -1) israwmode = 0; } static int curpos() { char buf[32]; int cols, rows; unsigned int i = 0; if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) return -1; while (i < sizeof(buf)-1) { if (read(STDIN_FILENO, buf+i, 1) != 1) break; if (buf[i] == 'R') break; i++; } buf[i] = '\0'; if (buf[0] != 27 || buf[1] != '[') return -1; if (sscanf(buf + 2, "%d;%d", &rows, &cols) != 2) return -1; return cols; } static int getcols() { struct winsize ws; if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { int start, cols; start = curpos(); if (start == -1) goto failed; if (write(STDOUT_FILENO, "\x1b[999C", 6) != 6) goto failed; cols = curpos(); if (cols == -1) goto failed; if (cols > start) { char seq[32]; snprintf(seq, 32, "\x1b[%dD", cols-start); if (write(STDOUT_FILENO, seq, strlen(seq)) == -1) {} } return cols; } else { return ws.ws_col; } failed: return 80; } static void clear() { if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) {} } static void refresh() { char seq[64]; DstBuffer b; /* Keep cursor position on screen */ char *_buf = buf; int _len = len; int _pos = pos; while ((plen + _pos) >= cols) { _buf++; _len--; _pos--; } while ((plen + _len) > cols) { _len--; } dst_buffer_init(&b, 0); /* Cursor to left edge, prompt and buffer */ dst_buffer_push_u8(&b, '\r'); dst_buffer_push_cstring(&b, prompt); dst_buffer_push_bytes(&b, (uint8_t *) _buf, _len); /* Erase to right */ dst_buffer_push_cstring(&b, "\x1b[0K"); /* Move cursor to original position. */ snprintf(seq, 64,"\r\x1b[%dC", (int)(_pos + plen)); dst_buffer_push_cstring(&b, seq); if (write(STDOUT_FILENO, b.data, b.count) == -1) {} dst_buffer_deinit(&b); } static int insert(char c) { if (len < DST_LINE_MAX - 1) { if (len == pos) { buf[pos++] = c; buf[++len] = '\0'; if (plen + len < cols) { /* Avoid a full update of the line in the * trivial case. */ if (write(STDOUT_FILENO, &c, 1) == -1) return -1; } else { refresh(); } } else { memmove(buf + pos + 1, buf + pos, len - pos); buf[pos++] = c; buf[++len] = '\0'; refresh(); } } return 0; } static void historymove(int delta) { if (dst_v_count(history) > 1) { free(history[historyi]); history[historyi] = sdup(buf); historyi += delta; if (historyi < 0) { historyi = 0; return; } else if (historyi >= dst_v_count(history)) { historyi = dst_v_count(history) - 1; return; } strncpy(buf, history[historyi], DST_LINE_MAX); pos = len = strlen(buf); buf[len] = '\0'; refresh(); } } static void addhistory() { int i, len; char *newline = sdup(buf); if (!newline) return; len = dst_v_count(history); if (len < DST_HISTORY_MAX) { dst_v_push(history, newline); len++; } else { free(history[DST_HISTORY_MAX - 1]); } for (i = len - 1; i > 0; i--) { history[i] = history[i - 1]; } history[0] = newline; } static void replacehistory() { char *newline = sdup(buf); if (!newline) return; free(history[0]); history[0] = newline; } static void kleft() { if (pos > 0) { pos--; refresh(); } } static void kright() { if (pos != len) { pos++; refresh(); } } static void kbackspace() { if (pos > 0) { memmove(buf + pos - 1, buf + pos, len - pos); pos--; buf[--len] = '\0'; refresh(); } } static int line() { cols = getcols(); plen = 0; len = 0; pos = 0; while (prompt[plen]) plen++; buf[0] = '\0'; addhistory(); if (write(STDOUT_FILENO, prompt, plen) == -1) return -1; for (;;) { char c; int nread; char seq[3]; nread = read(STDIN_FILENO, &c, 1); if (nread <= 0) return -1; switch(c) { default: if (insert(c)) return -1; break; case 13: /* enter */ return 0; case 3: /* ctrl-c */ errno = EAGAIN; return -1; case 127: /* backspace */ case 8: /* ctrl-h */ kbackspace(); break; case 4: /* ctrl-d, eof */ return -1; case 2: /* ctrl-b */ kleft(); break; case 6: /* ctrl-f */ kright(); break; case 21: buf[0] = '\0'; pos = len = 0; refresh(); break; case 12: clear(); refresh(); break; case 27: /* 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(STDIN_FILENO, seq, 1) == -1) break; if (read(STDIN_FILENO, seq + 1, 1) == -1) break; if (seq[0] == '[') { if (seq[1] >= '0' && seq[1] <= '9') { /* Extended escape, read additional byte. */ if (read(STDIN_FILENO, seq + 2, 1) == -1) break; if (seq[2] == '~') { switch(seq[1]) { default: break; } } } else { switch (seq[1]) { default: break; case 'A': historymove(1); break; case 'B': historymove(-1); break; case 'C': /* Right */ kright(); break; case 'D': /* Left */ kleft(); break; case 'H': pos = 0; refresh(); break; case 'F': pos = len; refresh(); break; } } } else if (seq[0] == 'O') { switch (seq[1]) { default: break; case 'H': pos = 0; refresh(); break; case 'F': pos = len; refresh(); break; } } break; } } return 0; } void dst_line_init() { ; } void dst_line_deinit() { int i; norawmode(); for (i = 0; i < dst_v_count(history); i++) free(history[i]); dst_v_free(history); historyi = 0; } static int checktermsupport() { const char *t = getenv("TERM"); int i; if (!t) return 1; for (i = 0; badterms[i]; i++) if (!strcmp(t, badterms[i])) return 0; return 1; } void dst_line_get(const uint8_t *p, DstBuffer *buffer) { prompt = (const char *)p; buffer->count = 0; historyi = 0; if (!isatty(STDIN_FILENO) || !checktermsupport()) { simpleline(buffer); return; } if (rawmode()) { simpleline(buffer); return; } if (line()) { norawmode(); fputc('\n', stdout); return; } norawmode(); fputc('\n', stdout); dst_buffer_ensure(buffer, len + 1); memcpy(buffer->data, buf, len); buffer->data[len] = '\n'; buffer->count = len + 1; replacehistory(); } #endif