#include <sys/types.h>
#include "linenoise.h"
+#ifndef STRINGBUF_H
#include "stringbuf.h"
+#endif
+#ifndef UTF8_UTIL_H
#include "utf8.h"
+#endif
#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
int cols; /* Size of the window, in chars */
int nrows; /* How many rows are being used in multiline mode (>= 1) */
int rpos; /* The current row containing the cursor - multiline mode only */
+ int colsright; /* refreshLine() cached cols for insert_char() optimisation */
+ int colsleft; /* refreshLine() cached cols for remove_char() optimisation */
const char *prompt;
stringbuf *capture; /* capture buffer, or NULL for none. Always null terminated */
stringbuf *output; /* used only during refreshLine() - output accumulator */
}
}
+typedef enum {
+ EP_START, /* looking for ESC */
+ EP_ESC, /* looking for [ */
+ EP_DIGITS, /* parsing digits */
+ EP_PROPS, /* parsing digits or semicolons */
+ EP_END, /* ok */
+ EP_ERROR, /* error */
+} ep_state_t;
+
struct esc_parser {
- enum {
- EP_START, /* looking for ESC */
- EP_ESC, /* looking for [ */
- EP_DIGITS, /* parsing digits */
- EP_PROPS, /* parsing digits or semicolons */
- EP_END, /* ok */
- EP_ERROR, /* error */
- } state;
+ ep_state_t state;
int props[5]; /* properties are stored here */
int maxprops; /* size of the props[] array */
int numprops; /* number of properties found */
}
}
#else
-#define DRL(ARGS...)
+#define DRL(...)
#define DRL_CHAR(ch)
#define DRL_STR(str)
#endif
void linenoiseClearScreen(void)
{
- write(STDOUT_FILENO, "\x1b[H\x1b[2J", 7);
+ IGNORE_RC(write(STDOUT_FILENO, "\x1b[H\x1b[2J", 7));
}
/**
}
/* Helper of refreshSingleLine() and refreshMultiLine() to show hints
- * to the right of the prompt. */
-static void refreshShowHints(struct current *current, const char *buf, int availcols) {
+ * to the right of the prompt.
+ * Returns 1 if a hint was shown, or 0 if not
+ * If 'display' is 0, does no output. Just returns the appropriate return code.
+ */
+static int refreshShowHints(struct current *current, const char *buf, int availcols, int display)
+{
+ int rc = 0;
if (showhints && hintsCallback && availcols > 0) {
int bold = 0;
int color = -1;
char *hint = hintsCallback(buf, &color, &bold, hintsUserdata);
if (hint) {
- const char *pt;
- if (bold == 1 && color == -1) color = 37;
- if (bold || color > 0) {
- int props[3] = { bold, color, 49 }; /* bold, color, fgnormal */
- setOutputHighlight(current, props, 3);
- }
- DRL("<hint bold=%d,color=%d>", bold, color);
- pt = hint;
- while (*pt) {
- int ch;
- int n = utf8_tounicode(pt, &ch);
- int width = char_display_width(ch);
-
- if (width >= availcols) {
- DRL("<hinteol>");
- break;
+ rc = 1;
+ if (display) {
+ const char *pt;
+ if (bold == 1 && color == -1) color = 37;
+ if (bold || color > 0) {
+ int props[3] = { bold, color, 49 }; /* bold, color, fgnormal */
+ setOutputHighlight(current, props, 3);
}
- DRL_CHAR(ch);
+ DRL("<hint bold=%d,color=%d>", bold, color);
+ pt = hint;
+ while (*pt) {
+ int ch;
+ int n = utf8_tounicode(pt, &ch);
+ int width = char_display_width(ch);
+
+ if (width >= availcols) {
+ DRL("<hinteol>");
+ break;
+ }
+ DRL_CHAR(ch);
- availcols -= width;
- outputChars(current, pt, n);
- pt += n;
- }
- if (bold || color > 0) {
- clearOutputHighlight(current);
+ availcols -= width;
+ outputChars(current, pt, n);
+ pt += n;
+ }
+ if (bold || color > 0) {
+ clearOutputHighlight(current);
+ }
+ /* Call the function to free the hint returned. */
+ if (freeHintsCallback) freeHintsCallback(hint, hintsUserdata);
}
- /* Call the function to free the hint returned. */
- if (freeHintsCallback) freeHintsCallback(hint, hintsUserdata);
}
}
+ return rc;
}
#ifdef USE_TERMIOS
static void refreshStartChars(struct current *current)
{
+ (void)current;
}
static void refreshNewline(struct current *current)
static void refreshEndChars(struct current *current)
{
+ (void)current;
}
#endif
int notecursor;
int cursorcol = 0;
int cursorrow = 0;
+ int hint;
struct esc_parser parser;
#ifdef DEBUG_REFRESHLINE
cursorrow = displayrow;
}
- DRL("\nafter buf: displaycol=%d, displayrow=%d, cursorcol=%d, cursorrow=%d\n\n", displaycol, displayrow, cursorcol, cursorrow);
+ DRL("\nafter buf: displaycol=%d, displayrow=%d, cursorcol=%d, cursorrow=%d\n", displaycol, displayrow, cursorcol, cursorrow);
/* (f) show hints */
- refreshShowHints(current, buf, current->cols - displaycol);
+ hint = refreshShowHints(current, buf, current->cols - displaycol, 1);
+
+ /* Remember how many many cols are available for insert optimisation */
+ if (prompt == current->prompt && hint == 0) {
+ current->colsright = current->cols - displaycol;
+ current->colsleft = displaycol;
+ }
+ else {
+ /* Can't optimise */
+ current->colsright = 0;
+ current->colsleft = 0;
+ }
+ DRL("\nafter hints: colsleft=%d, colsright=%d\n\n", current->colsleft, current->colsright);
refreshEndChars(current);
if (pos >= 0 && pos < sb_chars(current->buf)) {
int offset = utf8_index(sb_str(current->buf), pos);
int nbytes = utf8_index(sb_str(current->buf) + offset, 1);
-
- /* Note that we no longer try to optimise the remove-at-end case
- * since control characters and wide characters mess
- * up the simple count
+ int rc = 1;
+
+ /* Now we try to optimise in the simple but very common case that:
+ * - outputChars() can be used directly (not win32)
+ * - we are removing the char at EOL
+ * - the buffer is not empty
+ * - there are columns available to the left
+ * - the char being deleted is not a wide or utf-8 character
+ * - no hints are being shown
*/
+ if (current->output && current->pos == pos + 1 && current->pos == sb_chars(current->buf) && pos > 0) {
+#ifdef USE_UTF8
+ /* Could implement utf8_prev_len() but simplest just to not optimise this case */
+ char last = sb_str(current->buf)[offset];
+#else
+ char last = 0;
+#endif
+ if (current->colsleft > 0 && (last & 0x80) == 0) {
+ /* Have cols on the left and not a UTF-8 char or continuation */
+ /* Yes, can optimise */
+ current->colsleft--;
+ current->colsright++;
+ rc = 2;
+ }
+ }
+
sb_delete(current->buf, offset, nbytes);
if (current->pos > pos) {
current->pos--;
}
+ if (rc == 2) {
+ if (refreshShowHints(current, sb_str(current->buf), current->colsright, 0)) {
+ /* A hint needs to be shown, so can't optimise after all */
+ rc = 1;
+ }
+ else {
+ /* optimised output */
+ outputChars(current, "\b \b", 3);
+ }
+ }
+ return rc;
return 1;
}
return 0;
char buf[MAX_UTF8_LEN + 1];
int offset = utf8_index(sb_str(current->buf), pos);
int n = utf8_getchars(buf, ch);
+ int rc = 1;
/* null terminate since sb_insert() requires it */
buf[n] = 0;
- /* Optimisation removed - see reason in remove_char() */
-
+ /* Now we try to optimise in the simple but very common case that:
+ * - outputChars() can be used directly (not win32)
+ * - we are inserting at EOL
+ * - there are enough columns available
+ * - no hints are being shown
+ */
+ if (current->output && pos == current->pos && pos == sb_chars(current->buf)) {
+ int width = char_display_width(ch);
+ if (current->colsright > width) {
+ /* Yes, can optimise */
+ current->colsright -= width;
+ current->colsleft -= width;
+ rc = 2;
+ }
+ }
sb_insert(current->buf, offset, buf);
if (current->pos >= pos) {
current->pos++;
}
- return 1;
+ if (rc == 2) {
+ if (refreshShowHints(current, sb_str(current->buf), current->colsright, 0)) {
+ /* A hint needs to be shown, so can't optimise after all */
+ rc = 1;
+ }
+ else {
+ /* optimised output */
+ outputChars(current, buf, n);
+ }
+ }
+ return rc;
}
return 0;
}
int offset = utf8_index(sb_str(current->buf), pos);
int nbytes = utf8_index(sb_str(current->buf) + offset, nchars);
- if (nbytes) {
+ if (nbytes > 0) {
if (current->capture) {
sb_clear(current->capture);
}
c = fd_read(current);
if (c == ctrl('H') || c == CHAR_DELETE) {
if (rchars) {
- int p = utf8_index(rbuf, --rchars);
- rbuf[p] = 0;
+ int p_ind = utf8_index(rbuf, --rchars);
+ rbuf[p_ind] = 0;
rlen = strlen(rbuf);
}
continue;
switch(c) {
case SPECIAL_NONE:
break;
- case '\r': /* enter */
+ case '\r': /* enter/CR */
+ case '\n': /* LF */
history_len--;
free(history[history_len]);
current->pos = sb_chars(current->buf);
int linenoiseColumns(void)
{
struct current current;
+ current.output = NULL;
enableRawMode (¤t);
getWindowSize (¤t);
disableRawMode (¤t);