rdline: add a pager that can be used by user-defined commands
authorOlivier Matz <zer0@droids-corp.org>
Fri, 11 Mar 2011 19:40:27 +0000 (20:40 +0100)
committerOlivier Matz <zer0@droids-corp.org>
Sun, 13 Mar 2011 10:09:25 +0000 (11:09 +0100)
Signed-off-by: Olivier Matz <zer0@droids-corp.org>
src/lib/cmdline_rdline.c
src/lib/cmdline_rdline.h

index 7f34946..16fda43 100644 (file)
@@ -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 */
index cee438a..23b8cb6 100644 (file)
@@ -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_ */