+
+ freeCompletions(&lc);
+ return c; /* Return last read character */
+}
+
+/* Register a callback function to be called for tab-completion.
+ Returns the prior callback so that the caller may (if needed)
+ restore it when done. */
+linenoiseCompletionCallback * linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn, void *userdata) {
+ linenoiseCompletionCallback * old = completionCallback;
+ completionCallback = fn;
+ completionUserdata = userdata;
+ return old;
+}
+
+void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) {
+ lc->cvec = (char **)realloc(lc->cvec,sizeof(char*)*(lc->len+1));
+ lc->cvec[lc->len++] = strdup(str);
+}
+
+void linenoiseSetHintsCallback(linenoiseHintsCallback *callback, void *userdata)
+{
+ hintsCallback = callback;
+ hintsUserdata = userdata;
+}
+
+void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *callback)
+{
+ freeHintsCallback = callback;
+}
+
+#endif
+
+
+static const char *reduceSingleBuf(const char *buf, int availcols, int *cursor_pos)
+{
+ /* We have availcols columns available.
+ * If necessary, strip chars off the front of buf until *cursor_pos
+ * fits within availcols
+ */
+ int needcols = 0;
+ int pos = 0;
+ int new_cursor_pos = *cursor_pos;
+ const char *pt = buf;
+
+ DRL("reduceSingleBuf: availcols=%d, cursor_pos=%d\n", availcols, *cursor_pos);
+
+ while (*pt) {
+ int ch;
+ int n = utf8_tounicode(pt, &ch);
+ pt += n;
+
+ needcols += char_display_width(ch);
+
+ /* If we need too many cols, strip
+ * chars off the front of buf to make it fit.
+ * We keep 3 extra cols to the right of the cursor.
+ * 2 for possible wide chars, 1 for the last column that
+ * can't be used.
+ */
+ while (needcols >= availcols - 3) {
+ n = utf8_tounicode(buf, &ch);
+ buf += n;
+ needcols -= char_display_width(ch);
+ DRL_CHAR(ch);
+
+ /* and adjust the apparent cursor position */
+ new_cursor_pos--;
+
+ if (buf == pt) {
+ /* can't remove more than this */
+ break;
+ }
+ }
+
+ if (pos++ == *cursor_pos) {
+ break;
+ }
+
+ }
+ DRL("<snip>");
+ DRL_STR(buf);
+ DRL("\nafter reduce, needcols=%d, new_cursor_pos=%d\n", needcols, new_cursor_pos);
+
+ /* Done, now new_cursor_pos contains the adjusted cursor position
+ * and buf points to he adjusted start
+ */
+ *cursor_pos = new_cursor_pos;
+ return buf;
+}
+
+static int mlmode = 0;
+
+void linenoiseSetMultiLine(int enableml)
+{
+ mlmode = enableml;
+}
+
+/* Helper of refreshSingleLine() and refreshMultiLine() to show hints
+ * 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) {
+ 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("<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);
+ }
+ /* Call the function to free the hint returned. */
+ if (freeHintsCallback) freeHintsCallback(hint, hintsUserdata);
+ }
+ }
+ }
+ return rc;
+}
+
+#ifdef USE_TERMIOS
+static void refreshStart(struct current *current)
+{
+ /* We accumulate all output here */
+ assert(current->output == NULL);
+ current->output = sb_alloc();
+}
+
+static void refreshEnd(struct current *current)
+{
+ /* Output everything at once */
+ IGNORE_RC(write(current->fd, sb_str(current->output), sb_len(current->output)));
+ sb_free(current->output);
+ current->output = NULL;
+}
+
+static void refreshStartChars(struct current *current)
+{
+}
+
+static void refreshNewline(struct current *current)
+{
+ DRL("<nl>");
+ outputChars(current, "\n", 1);
+}
+
+static void refreshEndChars(struct current *current)
+{
+}
+#endif
+
+static void refreshLineAlt(struct current *current, const char *prompt, const char *buf, int cursor_pos)
+{
+ int i;
+ const char *pt;
+ int displaycol;
+ int displayrow;
+ int visible;
+ int currentpos;
+ int notecursor;
+ int cursorcol = 0;
+ int cursorrow = 0;
+ int hint;
+ struct esc_parser parser;
+
+#ifdef DEBUG_REFRESHLINE
+ dfh = fopen("linenoise.debuglog", "a");
+#endif
+
+ /* Should intercept SIGWINCH. For now, just get the size every time */
+ getWindowSize(current);
+
+ refreshStart(current);
+
+ DRL("wincols=%d, cursor_pos=%d, nrows=%d, rpos=%d\n", current->cols, cursor_pos, current->nrows, current->rpos);
+
+ /* Here is the plan:
+ * (a) move the the bottom row, going down the appropriate number of lines
+ * (b) move to beginning of line and erase the current line
+ * (c) go up one line and do the same, until we have erased up to the first row
+ * (d) output the prompt, counting cols and rows, taking into account escape sequences
+ * (e) output the buffer, counting cols and rows
+ * (e') when we hit the current pos, save the cursor position
+ * (f) move the cursor to the saved cursor position
+ * (g) save the current cursor row and number of rows
+ */
+
+ /* (a) - The cursor is currently at row rpos */
+ cursorDown(current, current->nrows - current->rpos - 1);
+ DRL("<cud=%d>", current->nrows - current->rpos - 1);
+
+ /* (b), (c) - Erase lines upwards until we get to the first row */
+ for (i = 0; i < current->nrows; i++) {
+ if (i) {
+ DRL("<cup>");
+ cursorUp(current, 1);
+ }
+ DRL("<clearline>");
+ cursorToLeft(current);
+ eraseEol(current);
+ }
+ DRL("\n");
+
+ /* (d) First output the prompt. control sequences don't take up display space */
+ pt = prompt;
+ displaycol = 0; /* current display column */
+ displayrow = 0; /* current display row */
+ visible = 1;
+
+ refreshStartChars(current);
+
+ while (*pt) {
+ int width;
+ int ch;
+ int n = utf8_tounicode(pt, &ch);
+
+ if (visible && ch == CHAR_ESCAPE) {
+ /* The start of an escape sequence, so not visible */
+ visible = 0;
+ initParseEscapeSeq(&parser, 'm');
+ DRL("<esc-seq-start>");
+ }
+
+ if (ch == '\n' || ch == '\r') {
+ /* treat both CR and NL the same and force wrap */
+ refreshNewline(current);
+ displaycol = 0;
+ displayrow++;
+ }
+ else {
+ width = visible * utf8_width(ch);
+
+ displaycol += width;
+ if (displaycol >= current->cols) {
+ /* need to wrap to the next line because of newline or if it doesn't fit
+ * XXX this is a problem in single line mode
+ */
+ refreshNewline(current);
+ displaycol = width;
+ displayrow++;
+ }
+
+ DRL_CHAR(ch);
+#ifdef USE_WINCONSOLE
+ if (visible) {
+ outputChars(current, pt, n);
+ }
+#else
+ outputChars(current, pt, n);
+#endif
+ }
+ pt += n;
+
+ if (!visible) {
+ switch (parseEscapeSequence(&parser, ch)) {
+ case EP_END:
+ visible = 1;
+ setOutputHighlight(current, parser.props, parser.numprops);
+ DRL("<esc-seq-end,numprops=%d>", parser.numprops);
+ break;
+ case EP_ERROR:
+ DRL("<esc-seq-err>");
+ visible = 1;
+ break;
+ }
+ }
+ }
+
+ /* Now we are at the first line with all lines erased */
+ DRL("\nafter prompt: displaycol=%d, displayrow=%d\n", displaycol, displayrow);
+
+
+ /* (e) output the buffer, counting cols and rows */
+ if (mlmode == 0) {
+ /* In this mode we may need to trim chars from the start of the buffer until the
+ * cursor fits in the window.
+ */
+ pt = reduceSingleBuf(buf, current->cols - displaycol, &cursor_pos);
+ }
+ else {
+ pt = buf;
+ }
+
+ currentpos = 0;
+ notecursor = -1;
+
+ while (*pt) {
+ int ch;
+ int n = utf8_tounicode(pt, &ch);
+ int width = char_display_width(ch);
+
+ if (currentpos == cursor_pos) {
+ /* (e') wherever we output this character is where we want the cursor */
+ notecursor = 1;
+ }
+
+ if (displaycol + width >= current->cols) {
+ if (mlmode == 0) {
+ /* In single line mode stop once we print as much as we can on one line */
+ DRL("<slmode>");
+ break;
+ }
+ /* need to wrap to the next line since it doesn't fit */
+ refreshNewline(current);
+ displaycol = 0;
+ displayrow++;
+ }
+
+ if (notecursor == 1) {
+ /* (e') Save this position as the current cursor position */
+ cursorcol = displaycol;
+ cursorrow = displayrow;
+ notecursor = 0;
+ DRL("<cursor>");
+ }
+
+ displaycol += width;
+
+ if (ch < ' ') {
+ outputControlChar(current, ch + '@');
+ }
+ else {
+ outputChars(current, pt, n);
+ }
+ DRL_CHAR(ch);
+ if (width != 1) {
+ DRL("<w=%d>", width);
+ }
+
+ pt += n;
+ currentpos++;
+ }
+
+ /* If we didn't see the cursor, it is at the current location */
+ if (notecursor) {
+ DRL("<cursor>");
+ cursorcol = displaycol;
+ cursorrow = displayrow;
+ }
+
+ DRL("\nafter buf: displaycol=%d, displayrow=%d, cursorcol=%d, cursorrow=%d\n\n", displaycol, displayrow, cursorcol, cursorrow);
+
+ /* (f) show hints */
+ 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->availcols = current->cols - displaycol;
+ }
+ else {
+ /* Can't optimise */
+ current->availcols = 0;
+ }
+
+ refreshEndChars(current);
+
+ /* (g) move the cursor to the correct place */
+ cursorUp(current, displayrow - cursorrow);
+ setCursorPos(current, cursorcol);
+
+ /* (h) Update the number of rows if larger, but never reduce this */
+ if (displayrow >= current->nrows) {
+ current->nrows = displayrow + 1;
+ }
+ /* And remember the row that the cursor is on */
+ current->rpos = cursorrow;
+
+ refreshEnd(current);
+
+#ifdef DEBUG_REFRESHLINE
+ fclose(dfh);
+#endif
+}
+
+static void refreshLine(struct current *current)
+{
+ refreshLineAlt(current, current->prompt, sb_str(current->buf), current->pos);
+}
+
+static void set_current(struct current *current, const char *str)
+{
+ sb_clear(current->buf);
+ sb_append(current->buf, str);
+ current->pos = sb_chars(current->buf);
+}
+
+/**
+ * Removes the char at 'pos'.
+ *
+ * Returns 1 if the line needs to be refreshed, 2 if not
+ * and 0 if nothing was removed
+ */
+static int remove_char(struct current *current, int pos)
+{
+ 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
+ */
+ sb_delete(current->buf, offset, nbytes);
+
+ if (current->pos > pos) {
+ current->pos--;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ * Insert 'ch' at position 'pos'
+ *
+ * Returns 1 if the line needs to be refreshed, 2 if not
+ * and 0 if nothing was inserted (no room)
+ */
+static int insert_char(struct current *current, int pos, int ch)
+{
+ if (pos >= 0 && pos <= sb_chars(current->buf)) {
+ 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;
+
+ /* Now we try to optimise in the simple but very common case that:
+ * - we are inserting at EOL
+ * - there are enough columns available
+ * - no hints are being shown
+ */
+ if (pos == current->pos && pos == sb_chars(current->buf)) {
+ int width = char_display_width(ch);
+ if (current->availcols > width) {
+ /* Yes, can optimise */
+ current->availcols -= width;
+ rc = 2;
+ }
+ }
+ sb_insert(current->buf, offset, buf);
+ if (current->pos >= pos) {
+ current->pos++;
+ }
+ if (rc == 2) {
+ if (refreshShowHints(current, sb_str(current->buf), current->availcols, 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;