commit - 15e1b108be37e22089affc9e1165a3834c9259b7
commit + 1d08c280e4fb59f78de88a09aa7932e3013e1ce7
blob - 8fd6b69aad5ec4b5c2f16a83d35000248847ade6
blob + 455231379643b68424898cad35d772cfd869d525
--- configure.ac
+++ configure.ac
PKG_PROG_PKG_CONFIG
-dnl AX_WITH_CURSES
-dnl if test "x$ax_cv_ncursesw" != xyes && test "x$ax_cv_ncurses" != xyes; then
-dnl AC_MSG_ERROR([requires either NcursesW or Ncurses library])
-dnl fi
+AC_SEARCH_LIBS([initscr], [ncursesw ncurses], [], [
+ AC_MSG_ERROR([requires either ncursesw or ncurses library])
+])
-AC_CHECK_LIB(ncursesw, initscr, [],
- AC_CHECK_LIB(ncurses, initscr, [],
- AC_MSG_ERROR([requires either ncursesw or ncurses library])))
+AC_CHECK_LIB(tls, tls_init, [], [
+ AC_MSG_ERROR([requires libtls])
+])
-AC_CHECK_LIB(tls, tls_init, [],
- AC_MSG_ERROR([requires libtls]))
+AC_CHECK_LIB(event, event_init, [], [
+ AC_MSG_ERROR([requires libevent])
+])
-AC_CHECK_LIB(event, event_init, [],
- AC_MSG_ERROR([requires libevent]))
-
IMSG_LDFLAGS=-lutil
AC_SUBST([IMSG_LDFLAGS])
blob - 7a68d17396de5e737c5f63fe556fe0730022f43b
blob + 3ff19e7ad790dfb9328cd07a0a6c07b5c34c2e6f
--- telescope.c
+++ telescope.c
new_tab(void)
{
struct tab *tab;
- const char *url = "about:new";
- /* const char *url = "gemini://localhost.it/"; */
+ /* const char *url = "about:new"; */
+ /* const char *url = "gemini://localhost.it/scroll.txt"; */
+ const char *url = "gemini://localhost.it/test.gmi";
if ((tab = calloc(1, sizeof(*tab))) == NULL)
die();
blob - 62c3fb3d9c04d0e9a9f20ab61f4ff9c1a0d92f87
blob + 88b28838e6b20443ee507a5789451fbb439d7d35
--- telescope.h
+++ telescope.h
TAILQ_HEAD(, line) head;
};
+struct ui_state;
+
extern TAILQ_HEAD(tabshead, tab) tabshead;
struct tab {
struct parser page;
int code;
char meta[GEMINI_URL_LEN];
int redirect_count;
+
+ struct ui_state *s;
};
extern struct event imsgev;
/* ui.c */
int ui_init(void);
-void ui_on_new_tab(struct tab*);
+int ui_on_new_tab(struct tab*);
void ui_on_tab_refresh(struct tab*);
void ui_end(void);
blob - 0a00d3b8723a3b9f23ea154ddf335499633adcec
blob + a32b231dcdacc9ff63d4d65cc5b19decb370585f
--- ui.c
+++ ui.c
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+/*
+ * Ncurses UI for telescope.
+ *
+ *
+ * Text wrapping
+ * =============
+ *
+ * There's a simple text wrapping algorithm.
+ *
+ * 1. if it's a line in a pre-formatted block:
+ * a. hard wrap.
+ * b. repeat
+ * 2. there is enough room for the next word?
+ * a. yes: render it
+ * b. no: break the current line.
+ * i. while there isn't enough space to draw the current
+ * word, hard-wrap it
+ * ii. draw the remainder of the current word (can be the
+ * the entirely)
+ * 3. render the spaces after the word
+ * a. but if there is not enough room break the line and
+ * forget them
+ * 4. repeat
+ *
+ *
+ * Text scrolling
+ * ==============
+ *
+ * ncurses allows you to scroll a window, but when a line goes out of
+ * the visible area it's forgotten. We keep a list of formatted lines
+ * (``visual lines'') that we know fits in the window, and draw them.
+ * This way is easy to scroll: just call wscrl and then render the
+ * first/last line!
+ *
+ * This means that on every resize we have to clear our list of lines
+ * and re-render everything. A clever approach would be to do this
+ * ``on-demand''.
+ *
+ * TODO: make the text formatting on-demand.
+ *
+ */
+
#include <telescope.h>
#include <curses.h>
#define CTRL(x) ((x)&0x1F)
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
static struct event stdioev, winchev;
+static int push_line(struct tab*, const struct line*, const char*, size_t);
+static void empty_vlist(struct tab*);
static struct tab *current_tab(void);
static void dispatch_stdio(int, short, void*);
static void handle_resize(int, short, void*);
static int word_bourdaries(const char*, const char*, const char**, const char**);
-static void wrap_text(const char*, const char*, const char*, const char*);
+static void wrap_text(struct tab*, const char*, struct line*);
+static int hardwrap_text(struct tab*, struct line*);
+static int wrap_page(struct tab*);
static void redraw_tab(struct tab*);
+typedef void (*interactivefn)(int);
+
+static void cmd_previous_line(int);
+static void cmd_next_line(int);
+static void cmd_forward_char(int);
+static void cmd_backward_char(int);
+static void cmd_redraw(int);
+static void cmd_scroll_down(int);
+static void cmd_scroll_up(int);
+static void cmd_kill_telescope(int);
+
+static void cmd_unbound(int);
+
+struct ui_state {
+ int curs_x;
+ int curs_y;
+ int line_off;
+
+ TAILQ_HEAD(, line) head;
+};
+
+static int
+push_line(struct tab *tab, const struct line *l, const char *buf, size_t len)
+{
+ struct line *vl;
+
+ if ((vl = calloc(1, sizeof(*vl))) == NULL)
+ return 0;
+
+ if (len != 0 && (vl->line = calloc(1, len+1)) == NULL) {
+ free(vl);
+ return 0;
+ }
+
+ vl->type = l->type;
+ if (len != 0)
+ memcpy(vl->line, buf, len);
+ vl->alt = l->alt;
+
+ if (TAILQ_EMPTY(&tab->s->head))
+ TAILQ_INSERT_HEAD(&tab->s->head, vl, lines);
+ else
+ TAILQ_INSERT_TAIL(&tab->s->head, vl, lines);
+ return 1;
+}
+
+static void
+empty_vlist(struct tab *tab)
+{
+ struct line *l, *t;
+
+ TAILQ_FOREACH_SAFE(l, &tab->s->head, lines, t) {
+ TAILQ_REMOVE(&tab->s->head, l, lines);
+ free(l->line);
+ /* l->alt references the original line! */
+ free(l);
+ }
+}
+
+struct binding {
+ int key;
+ interactivefn fn;
+} bindings[] = {
+ { CTRL('p'), cmd_previous_line, },
+ { CTRL('n'), cmd_next_line, },
+ { CTRL('f'), cmd_forward_char, },
+ { CTRL('b'), cmd_backward_char, },
+
+ { CTRL('L'), cmd_redraw, },
+
+ { 'J', cmd_scroll_down, },
+ { 'K', cmd_scroll_up, },
+
+ { 'q', cmd_kill_telescope, },
+
+ { 0, NULL, },
+};
+
+static void
+cmd_previous_line(int k)
+{
+ struct tab *tab;
+
+ tab = current_tab();
+ tab->s->curs_y = MAX(0, tab->s->curs_y-1);
+ move(tab->s->curs_y, tab->s->curs_x);
+}
+
+static void
+cmd_next_line(int k)
+{
+ struct tab *tab;
+
+ tab = current_tab();
+ tab->s->curs_y = MIN(LINES, tab->s->curs_y-1);
+ move(tab->s->curs_y, tab->s->curs_x);
+}
+
+static void
+cmd_forward_char(int k)
+{
+}
+
+static void
+cmd_backward_char(int k)
+{
+}
+
+static void
+cmd_redraw(int k)
+{
+ clear();
+ redraw_tab(current_tab());
+}
+
+static void
+cmd_scroll_down(int k)
+{
+ wscrl(stdscr, -1);
+}
+
+static void
+cmd_scroll_up(int k)
+{
+ wscrl(stdscr, 1);
+}
+
+static void
+cmd_kill_telescope(int k)
+{
+ event_loopbreak();
+}
+
+static void
+cmd_unbound(int k)
+{
+ /* TODO: flash a message */
+}
+
static struct tab *
current_tab(void)
{
static void
dispatch_stdio(int fd, short ev, void *d)
{
- int c;
+ struct binding *b;
+ int k;
- c = getch();
+ k = getch();
- if (c == ERR)
+ if (k == ERR)
return;
- if (c == 'q') {
- event_loopbreak();
- return;
- }
-
- if (c == CTRL('L')) {
- clear();
- redraw_tab(current_tab());
- return;
+ for (b = bindings; b->fn != NULL; ++b) {
+ if (k == b->key) {
+ b->fn(k);
+ goto done;
+ }
}
- printw("You typed %c\n", c);
+ cmd_unbound(k);
+
+done:
refresh();
}
static void
handle_resize(int sig, short ev, void *d)
{
+ struct tab *tab;
+
endwin();
refresh();
clear();
- redraw_tab(current_tab());
+ tab = current_tab();
+
+ wrap_page(tab);
+ redraw_tab(tab);
}
/*
return 1;
}
-static inline void
-emitline(const char *prfx, size_t zero, size_t *off)
+static inline int
+emitline(struct tab *tab, size_t zero, size_t *off, const struct line *l,
+ const char **line)
{
- printw("\n%s", prfx);
+ if (!push_line(tab, l, *line, *off - zero))
+ return 0;
+ *line += *off - zero;
*off = zero;
+ return 1;
}
static inline void
}
/*
- * Wrap the text, prefixing the first line with prfx1 and the
- * following with prfx2, and breaking on characters in the breakon set.
- * The idea is pretty simple: if there is enough space, write the next
- * word; if we are at the start of a line and there's not enough
- * space, hard-split it.
+ * Build a list of visual line by wrapping the given line, assuming
+ * that when printed will have a leading prefix prfx.
*
* TODO: it considers each byte one cell on the screen!
- * TODO: assume strlen(prfx1) == strlen(prfx2)
*/
static void
-wrap_text(const char *prfx1, const char *prfx2, const char *line, const char *breakon)
+wrap_text(struct tab *tab, const char *prfx, struct line *l)
{
size_t zero, off, len, split;
- const char *endword, *endspc;
+ const char *endword, *endspc, *line, *linestart;
- printw("%s", prfx1);
- zero = strlen(prfx1);
+ zero = strlen(prfx);
off = zero;
+ line = l->line;
+ linestart = l->line;
- while (word_boundaries(line, breakon, &endword, &endspc)) {
+ while (word_boundaries(line, " \t-", &endword, &endspc)) {
len = endword - line;
- if (off + len < COLS) {
- emitstr(&line, len, &off);
- } else {
- emitline(prfx2, zero, &off);
+ if (off + len >= COLS) {
+ emitline(tab, zero, &off, l, &linestart);
while (len >= COLS) {
/* hard wrap */
- printw("%*s", COLS-1, line);
- emitline(prfx2, zero, &off);
+ emitline(tab, zero, &off, l, &linestart);
len -= COLS-1;
line += COLS-1;
}
if (len != 0)
- emitstr(&line, len, &off);
- }
+ off += len;
+ } else
+ off += len;
/* print the spaces iff not at bol */
len = endspc - endword;
/* line = endspc; */
if (off != zero) {
- if (off + len >= COLS)
- emitline(prfx2, zero, &off);
- else
- emitstr(&line, len, &off);
+ if (off + len >= COLS) {
+ emitline(tab, zero, &off, l, &linestart);
+ linestart = endspc;
+ } else
+ off += len;
}
line = endspc;
}
- printw("\n");
+ emitline(tab, zero, &off, l, &linestart);
}
-static void
-redraw_tab(struct tab *tab)
+static int
+hardwrap_text(struct tab *tab, struct line *l)
{
- struct line *l;
- const char *sep = " \t";
+ size_t off, len;
+ const char *linestart;
- erase();
+ len = strlen(l->line);
+ off = 0;
+ linestart = l->line;
+
+ while (len >= COLS) {
+ len -= COLS-1;
+ off = COLS-1;
+ if (!emitline(tab, 0, &off, l, &linestart))
+ return 0;
+ }
+
+ return 1;
+}
+static int
+wrap_page(struct tab *tab)
+{
+ struct line *l;
+
+ empty_vlist(tab);
+
TAILQ_FOREACH(l, &tab->page.head, lines) {
switch (l->type) {
case LINE_TEXT:
- wrap_text("", "", l->line, sep);
+ wrap_text(tab, "", l);
break;
case LINE_LINK:
- wrap_text("=> ", " ", l->line, sep);
+ wrap_text(tab, "=> ", l);
break;
case LINE_TITLE_1:
- wrap_text("# ", " ", l->line, sep);
+ wrap_text(tab, "# ", l);
break;
case LINE_TITLE_2:
- wrap_text("## ", " ", l->line, sep);
+ wrap_text(tab, "## ", l);
break;
case LINE_TITLE_3:
- wrap_text("### ", " ", l->line, sep);
+ wrap_text(tab, "### ", l);
break;
case LINE_ITEM:
- wrap_text("* ", " ", l->line, sep);
+ wrap_text(tab, "* ", l);
break;
case LINE_QUOTE:
- wrap_text("> ", "> ", l->line, sep);
+ wrap_text(tab, "> ", l);
break;
case LINE_PRE_START:
case LINE_PRE_END:
- printw("```\n");
+ push_line(tab, l, NULL, 0);
break;
case LINE_PRE_CONTENT:
- printw("%s\n", l->line);
+ hardwrap_text(tab, l);
break;
}
}
+ return 1;
+}
+static inline void
+print_line(struct line *l)
+{
+ switch (l->type) {
+ case LINE_TEXT:
+ break;
+ case LINE_LINK:
+ printw("=> ");
+ break;
+ case LINE_TITLE_1:
+ printw("# ");
+ break;
+ case LINE_TITLE_2:
+ printw("## ");
+ break;
+ case LINE_TITLE_3:
+ printw("### ");
+ break;
+ case LINE_ITEM:
+ printw("* ");
+ break;
+ case LINE_QUOTE:
+ printw("> ");
+ break;
+ case LINE_PRE_START:
+ case LINE_PRE_END:
+ printw("```");
+ break;
+ case LINE_PRE_CONTENT:
+ break;
+ }
+
+ if (l->line != NULL)
+ printw("%s", l->line);
+ printw("\n");
+}
+
+static void
+redraw_tab(struct tab *tab)
+{
+ struct line *l;
+
+ erase();
+
+ TAILQ_FOREACH(l, &tab->s->head, lines) {
+ print_line(l);
+ }
+
+ move(tab->s->curs_y, tab->s->curs_x);
refresh();
}
intrflush(stdscr, FALSE);
keypad(stdscr, TRUE);
+ scrollok(stdscr, TRUE);
+
/* non-blocking input */
timeout(0);
return 1;
}
-void
+int
ui_on_new_tab(struct tab *tab)
{
struct tab *t;
+ if ((tab->s = calloc(1, sizeof(*t->s))) == NULL)
+ return 0;
+
+ TAILQ_INIT(&tab->s->head);
+
TAILQ_FOREACH(t, &tabshead, tabs) {
t->flags &= ~TAB_CURRENT;
}
-
tab->flags = TAB_CURRENT;
/* TODO: redraw the tab list */
+ /* TODO: switch to the new tab */
+
+ move(0, 0);
+
+ return 1;
}
void
if (!(tab->flags & TAB_CURRENT))
return;
+ wrap_page(tab);
redraw_tab(tab);
}