Refresh line with single write to avoid flickering. Escape sequences and actual content to write to the terminal is now accumulated into an heap allocated buffer that is flushed with a single write at the end. This avoids a flickering effect making linenoise more professional looking ;-)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
diff --git a/linenoise.c b/linenoise.c
index c690477..514ed89 100644
--- a/linenoise.c
+++ b/linenoise.c
@@ -318,6 +318,33 @@ void linenoiseAddCompletion(linenoiseCompletions *lc, char *str) {
/* =========================== Line editing ================================= */
+/* We define a very simple "append buffer" structure, that is an heap
+ * allocated string where we can append to. This is useful in order to
+ * write all the escape sequences in a buffer and flush them to the standard
+ * output in a single call, to avoid flickering effects. */
+struct abuf {
+ char *b;
+ int len;
+};
+
+static void abInit(struct abuf *ab) {
+ ab->b = NULL;
+ ab->len = 0;
+}
+
+static void abAppend(struct abuf *ab, const char *s, int len) {
+ char *new = realloc(ab->b,ab->len+len);
+
+ if (new == NULL) return;
+ memcpy(new+ab->len,s,len);
+ ab->b = new;
+ ab->len += len;
+}
+
+static void abFree(struct abuf *ab) {
+ free(ab->b);
+}
+
/* Single line low level line refresh.
*
* Rewrite the currently edited line accordingly to the buffer content,
@@ -329,6 +356,7 @@ static void refreshSingleLine(struct linenoiseState *l) {
char *buf = l->buf;
size_t len = l->len;
size_t pos = l->pos;
+ struct abuf ab;
while((plen+pos) >= l->cols) {
buf++;
@@ -339,18 +367,21 @@ static void refreshSingleLine(struct linenoiseState *l) {
len--;
}
+ abInit(&ab);
/* Cursor to left edge */
snprintf(seq,64,"\x1b[0G");
- if (write(fd,seq,strlen(seq)) == -1) return;
+ abAppend(&ab,seq,strlen(seq));
/* Write the prompt and the current buffer content */
- if (write(fd,l->prompt,strlen(l->prompt)) == -1) return;
- if (write(fd,buf,len) == -1) return;
+ abAppend(&ab,l->prompt,strlen(l->prompt));
+ abAppend(&ab,buf,len);
/* Erase to right */
snprintf(seq,64,"\x1b[0K");
- if (write(fd,seq,strlen(seq)) == -1) return;
+ abAppend(&ab,seq,strlen(seq));
/* Move cursor to original position. */
snprintf(seq,64,"\x1b[0G\x1b[%dC", (int)(pos+plen));
- if (write(fd,seq,strlen(seq)) == -1) return;
+ abAppend(&ab,seq,strlen(seq));
+ if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */
+ abFree(&ab);
}
/* Multi line low level line refresh.
@@ -365,6 +396,7 @@ static void refreshMultiLine(struct linenoiseState *l) {
int rpos2; /* rpos after refresh. */
int old_rows = l->maxrows;
int fd = l->fd, j;
+ struct abuf ab;
/* Update maxrows if needed. */
if (rows > (int)l->maxrows) l->maxrows = rows;
@@ -377,12 +409,13 @@ static void refreshMultiLine(struct linenoiseState *l) {
/* First step: clear all the lines used before. To do so start by
* going to the last row. */
+ abInit(&ab);
if (old_rows-rpos > 0) {
#ifdef LN_DEBUG
fprintf(fp,", go down %d", old_rows-rpos);
#endif
snprintf(seq,64,"\x1b[%dB", old_rows-rpos);
- if (write(fd,seq,strlen(seq)) == -1) return;
+ abAppend(&ab,seq,strlen(seq));
}
/* Now for every row clear it, go up. */
@@ -391,7 +424,7 @@ static void refreshMultiLine(struct linenoiseState *l) {
fprintf(fp,", clear+up");
#endif
snprintf(seq,64,"\x1b[0G\x1b[0K\x1b[1A");
- if (write(fd,seq,strlen(seq)) == -1) return;
+ abAppend(&ab,seq,strlen(seq));
}
/* Clean the top line. */
@@ -399,11 +432,11 @@ static void refreshMultiLine(struct linenoiseState *l) {
fprintf(fp,", clear");
#endif
snprintf(seq,64,"\x1b[0G\x1b[0K");
- if (write(fd,seq,strlen(seq)) == -1) return;
+ abAppend(&ab,seq,strlen(seq));
/* Write the prompt and the current buffer content */
- if (write(fd,l->prompt,strlen(l->prompt)) == -1) return;
- if (write(fd,l->buf,l->len) == -1) return;
+ abAppend(&ab,l->prompt,strlen(l->prompt));
+ abAppend(&ab,l->buf,l->len);
/* If we are at the very end of the screen with our prompt, we need to
* emit a newline and move the prompt to the first column. */
@@ -414,9 +447,9 @@ static void refreshMultiLine(struct linenoiseState *l) {
#ifdef LN_DEBUG
fprintf(fp,", <newline>");
#endif
- if (write(fd,"\n",1) == -1) return;
+ abAppend(&ab,"\n",1);
snprintf(seq,64,"\x1b[0G");
- if (write(fd,seq,strlen(seq)) == -1) return;
+ abAppend(&ab,seq,strlen(seq));
rows++;
if (rows > (int)l->maxrows) l->maxrows = rows;
}
@@ -432,14 +465,14 @@ static void refreshMultiLine(struct linenoiseState *l) {
fprintf(fp,", go-up %d", rows-rpos2);
#endif
snprintf(seq,64,"\x1b[%dA", rows-rpos2);
- if (write(fd,seq,strlen(seq)) == -1) return;
+ abAppend(&ab,seq,strlen(seq));
}
/* Set column. */
#ifdef LN_DEBUG
fprintf(fp,", set col %d", 1+((plen+(int)l->pos) % (int)l->cols));
#endif
snprintf(seq,64,"\x1b[%dG", 1+((plen+(int)l->pos) % (int)l->cols));
- if (write(fd,seq,strlen(seq)) == -1) return;
+ abAppend(&ab,seq,strlen(seq));
l->oldpos = l->pos;
@@ -447,6 +480,9 @@ static void refreshMultiLine(struct linenoiseState *l) {
fprintf(fp,"\n");
fclose(fp);
#endif
+
+ if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */
+ abFree(&ab);
}
/* Calls the two low level functions refreshSingleLine() or