From: Olivier Matz Date: Fri, 11 Mar 2011 19:40:27 +0000 (+0100) Subject: rdline: add a pager that can be used by user-defined commands X-Git-Url: http://git.droids-corp.org/?p=libcmdline.git;a=commitdiff_plain;h=3e6cc1d7b682f75a75034602f003c3e9f8a8d3ad rdline: add a pager that can be used by user-defined commands Signed-off-by: Olivier Matz --- diff --git a/src/lib/cmdline_rdline.c b/src/lib/cmdline_rdline.c index 7f34946..16fda43 100644 --- a/src/lib/cmdline_rdline.c +++ b/src/lib/cmdline_rdline.c @@ -77,6 +77,11 @@ static void rdline_remove_first_history_item(struct rdline *rdl); static unsigned int rdline_get_history_size(struct rdline *rdl); #endif /* !NO_RDLINE_HISTORY */ +#ifndef NO_PAGER +static int rdline_pager_next_page(struct rdline *rdl); +static void rdline_asyncpager_reset(struct rdline *rdl); +#endif /* !NO_PAGER */ + /* isblank() needs _XOPEN_SOURCE >= 600 || _ISOC99_SOURCE, so use our * own. */ @@ -206,6 +211,28 @@ rdline_parse_char(struct rdline *rdl, char c) if (cmd == VT100_NOT_COMPLETE) return RDLINE_RES_SUCCESS; +#ifndef NO_PAGER + /* display asynchrounous printf if any */ + if (rdl->pager_buf != NULL) { + if (cmd == VT100_STD_CHAR && c == 'q') { + rdline_asyncpager_reset(rdl); + // call cb() + if (rdl->pager_buf == NULL) + return RDLINE_RES_VALIDATED; + } + + if (rdline_pager_next_page(rdl) == 0) { + rdline_asyncpager_reset(rdl); + // call cb() + if (rdl->pager_buf == NULL) + return RDLINE_RES_VALIDATED; + } + /* async printf was called in cb() */ + return RDLINE_RES_SUCCESS; + } +#endif + + /* process control chars */ if (cmd != VT100_STD_CHAR) { switch (cmd) { case CMDLINE_KEY_CTRL_B: @@ -368,6 +395,12 @@ rdline_parse_char(struct rdline *rdl, char c) rdl->left_buf[CIRBUF_GET_LEN(&rdl->left)] = '\0'; rdline_printf(rdl, "\r\n"); rdl->help(rdl, rdl->left_buf, rdline_write, rdl); +#ifndef NO_PAGER + if (rdl->pager_buf != NULL) { + // XXX set cb + return RDLINE_RES_SUCCESS; + } +#endif rdline_redisplay(rdl); break; } @@ -393,6 +426,12 @@ rdline_parse_char(struct rdline *rdl, char c) rdl->left_buf[CIRBUF_GET_LEN(&rdl->left)] = '\0'; rdline_printf(rdl, "\r\n"); rdl->help(rdl, rdl->left_buf, rdline_write, rdl); +#ifndef NO_PAGER + if (rdl->pager_buf != NULL) { + // XXX set cb + return RDLINE_RES_SUCCESS; + } +#endif rdline_redisplay(rdl); break; } @@ -416,7 +455,6 @@ rdline_parse_char(struct rdline *rdl, char c) cirbuf_align_left(&rdl->left); rdl->left_buf[CIRBUF_GET_LEN(&rdl->left)] = '\n'; rdl->left_buf[CIRBUF_GET_LEN(&rdl->left) + 1] = '\0'; - rdl->status = RDLINE_INIT; rdline_printf(rdl, "\r\n"); #ifndef NO_RDLINE_HISTORY if (rdl->history_cur_line != -1) @@ -425,9 +463,21 @@ rdline_parse_char(struct rdline *rdl, char c) if (rdl->validate) rdl->validate(rdl, rdl->left_buf, CIRBUF_GET_LEN(&rdl->left)+2); +#ifndef NO_PAGER /* user may have stopped rdline */ + if (rdl->status == RDLINE_EXITED) { + rdline_asyncpager_reset(rdl); + return RDLINE_RES_EXITED; + } + if (rdl->pager_buf != NULL) { + // XXX set cb + return RDLINE_RES_SUCCESS; + } + rdl->status = RDLINE_INIT; +#else if (rdl->status == RDLINE_EXITED) return RDLINE_RES_EXITED; +#endif return RDLINE_RES_VALIDATED; #ifndef NO_RDLINE_HISTORY @@ -704,3 +754,142 @@ rdline_printf(const struct rdline *rdl, const char *fmt, ...) return ret; } + +#ifndef NO_PAGER +/* reset pager state */ +void +rdline_asyncpager_reset(struct rdline *rdl) +{ + if (rdl->pager_buf) { + free(rdl->pager_buf); + rdl->pager_buf = NULL; + } + rdl->pager_lines = 0; + rdl->pager_len = 0; + rdl->pager_off = 0; +} + +/* Return the offset of the i-th occurence of char c in string s. If + * there is less than i occurences, return -1 and fill i with the + * count. */ +static int +strnchr(const char *s, char c, int *i) +{ + int n = 0; + const char *orig = s; + + while (*s) { + if (*s == c) + n++; + if (*i == n) + return s - orig; + s++; + } + *i = n; + return -1; +} + +/* display a page of data from pager, return 0 if all is displayed */ +static int +rdline_pager_next_page(struct rdline *rdl) +{ + int lines = RDLINE_MAX_LINES; + int displen; + char *s; + + s = rdl->pager_buf; + if (s == NULL) + return 0; + + rdline_printf(rdl, vt100_home); + rdline_printf(rdl, vt100_clear_right); + + s += rdl->pager_off; + + /* we know that s is 0-terminated */ + displen = strnchr(s, '\n', &lines); + rdl->pager_lines = lines; + + /* we can display all the data */ + if (displen == -1) { + write(rdl->fd_out, s, rdl->pager_len); + free(rdl->pager_buf); + rdl->pager_buf = NULL; + return 0; + } + + displen = displen + 1; /* include \n */ + write(rdl->fd_out, s, displen); + rdl->pager_off += displen; + rdl->pager_len -= displen; + + rdline_printf(rdl, "--- press a key to continue ---"); + return -1; +} + +/* push data in pager */ +static int +rdline_pager_push(struct rdline *rdl, char *s, int len) +{ + /* display as many lines as we can */ + if (rdl->pager_lines < RDLINE_MAX_LINES) { + int lines = RDLINE_MAX_LINES - rdl->pager_lines; + int displen; + + /* we know that s is 0-terminated */ + displen = strnchr(s, '\n', &lines); + rdl->pager_lines += lines; + + /* we can display all the data */ + if (displen == -1) { + write(rdl->fd_out, s, len); + return 0; + } + displen = displen + 1; /* include \n */ + write(rdl->fd_out, s, displen); + s += displen; + len -= displen; + } + + if (rdl->pager_buf == NULL) { + rdline_printf(rdl, "--- press a key to continue ---"); + } + rdl->pager_buf = realloc(rdl->pager_buf, rdl->pager_len + len); + if (rdl->pager_buf == NULL) { + rdline_asyncpager_reset(rdl); + return -1; + } + + memcpy(rdl->pager_buf + rdl->pager_len, s, len); + rdl->pager_len += len; + return 0; +} + +/* XXX we should have a specific return value when displaying will be + * asynchronous, we may also have a callback */ +int +rdline_asyncpager_printf(struct rdline *rdl, const char *fmt, ...) +{ + int n; + char *buf; + va_list ap; + + if (rdl->fd_out < 0) + return -1; + + buf = malloc(BUFSIZ); + if (buf == NULL) + return -1; + + va_start(ap, fmt); + n = vsnprintf(buf, BUFSIZ, fmt, ap); + va_end(ap); + + if (n >= BUFSIZ) + n = BUFSIZ-1; + if (n > 0) + rdline_pager_push(rdl, buf, n); + free(buf); + return n; +} +#endif /* !NO_PAGER */ diff --git a/src/lib/cmdline_rdline.h b/src/lib/cmdline_rdline.h index cee438a..23b8cb6 100644 --- a/src/lib/cmdline_rdline.h +++ b/src/lib/cmdline_rdline.h @@ -147,6 +147,13 @@ struct rdline { /* opaque pointer */ void *opaque; + +#ifndef NO_PAGER + char *pager_buf; /* buffer used to store paged data */ + int pager_len; /* total len of buffer */ + int pager_off; /* offset of next data */ + int pager_lines; /* number of lines displayed */ +#endif }; /** @@ -351,4 +358,26 @@ void rdline_clear_history(struct rdline *rdl); */ char *rdline_get_history_item(struct rdline *rdl, unsigned int i); +#ifndef NO_PAGER +/** + * Print data asynchronously (using pager if needed) + * + * If there is enough place to print data on the current page, it is + * printed synchronously. Else, a temporary buffer is allocated and + * the data is stored in it. When the main rdline is called again, the + * pager is flushed before parsing any other commands. + * + * @param rdl + * The rdline descriptor + * @param fmt + * The format strings + * @return + * Upon successful return, these functions return the number of + * characters printed (not including the trailing '\0' used to end + * output to strings). On error, a negative value is returned. + */ +int rdline_asyncpager_printf(struct rdline *rdl, const char *fmt, ...); +#endif + + #endif /* _RDLINE_H_ */