Blob


1 /*
2 * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
17 /*
18 * Ncurses UI for telescope.
19 *
20 *
21 * Text wrapping
22 * =============
23 *
24 * There's a simple text wrapping algorithm.
25 *
26 * 1. if it's a line in a pre-formatted block:
27 * a. hard wrap.
28 * b. repeat
29 * 2. there is enough room for the next word?
30 * a. yes: render it
31 * b. no: break the current line.
32 * i. while there isn't enough space to draw the current
33 * word, hard-wrap it
34 * ii. draw the remainder of the current word (can be the
35 * the entirely)
36 * 3. render the spaces after the word
37 * a. but if there is not enough room break the line and
38 * forget them
39 * 4. repeat
40 *
41 *
42 * Text scrolling
43 * ==============
44 *
45 * ncurses allows you to scroll a window, but when a line goes out of
46 * the visible area it's forgotten. We keep a list of formatted lines
47 * (``visual lines'') that we know fits in the window, and draw them.
48 * This way is easy to scroll: just call wscrl and then render the
49 * first/last line!
50 *
51 * This means that on every resize we have to clear our list of lines
52 * and re-render everything. A clever approach would be to do this
53 * ``on-demand''.
54 *
55 * TODO: make the text formatting on-demand.
56 *
57 */
59 #include <telescope.h>
61 #include <ctype.h>
62 #include <curses.h>
63 #include <event.h>
64 #include <locale.h>
65 #include <signal.h>
66 #include <stdarg.h>
67 #include <stdlib.h>
68 #include <string.h>
69 #include <unistd.h>
71 #define TAB_CURRENT 0x1
73 static struct event stdioev, winchev;
75 static void load_default_keys(void);
76 static int push_line(struct tab*, const struct line*, const char*, size_t, int);
77 static void empty_vlist(struct tab*);
78 static void restore_cursor(struct tab *);
80 static void cmd_previous_line(struct tab*);
81 static void cmd_next_line(struct tab*);
82 static void cmd_forward_char(struct tab*);
83 static void cmd_backward_char(struct tab*);
84 static void cmd_move_beginning_of_line(struct tab*);
85 static void cmd_move_end_of_line(struct tab*);
86 static void cmd_redraw(struct tab*);
87 static void cmd_scroll_line_down(struct tab*);
88 static void cmd_scroll_line_up(struct tab*);
89 static void cmd_scroll_up(struct tab*);
90 static void cmd_scroll_down(struct tab*);
91 static void cmd_beginning_of_buffer(struct tab*);
92 static void cmd_end_of_buffer(struct tab*);
93 static void cmd_kill_telescope(struct tab*);
94 static void cmd_push_button(struct tab*);
95 static void cmd_push_button_new_tab(struct tab*);
96 static void cmd_previous_page(struct tab*);
97 static void cmd_next_page(struct tab*);
98 static void cmd_clear_minibuf(struct tab*);
99 static void cmd_execute_extended_command(struct tab*);
100 static void cmd_tab_close(struct tab*);
101 static void cmd_tab_new(struct tab*);
102 static void cmd_tab_next(struct tab*);
103 static void cmd_tab_previous(struct tab*);
104 static void cmd_load_url(struct tab*);
105 static void cmd_load_current_url(struct tab*);
107 static void global_key_unbound(void);
109 static void cmd_mini_delete_char(struct tab*);
110 static void cmd_mini_delete_backward_char(struct tab*);
111 static void cmd_mini_forward_char(struct tab*);
112 static void cmd_mini_backward_char(struct tab*);
113 static void cmd_mini_move_end_of_line(struct tab*);
114 static void cmd_mini_move_beginning_of_line(struct tab*);
115 static void cmd_mini_kill_line(struct tab*);
116 static void cmd_mini_abort(struct tab*);
117 static void cmd_mini_complete_and_exit(struct tab*);
118 static void cmd_mini_previous_history_element(struct tab*);
119 static void cmd_mini_next_history_element(struct tab*);
121 static void minibuffer_hist_save_entry(void);
122 static void minibuffer_taint_hist(void);
123 static void minibuffer_self_insert(void);
124 static void eecmd_self_insert(void);
125 static void eecmd_select(void);
126 static void ir_self_insert(void);
127 static void ir_select(void);
128 static void lu_self_insert(void);
129 static void lu_select(void);
131 static struct line *nth_line(struct tab*, size_t);
132 static struct tab *current_tab(void);
133 static void dispatch_stdio(int, short, void*);
134 static void handle_clear_minibuf(int, short, void*);
135 static void handle_resize(int, short, void*);
136 static int word_bourdaries(const char*, const char*, const char**, const char**);
137 static void wrap_text(struct tab*, const char*, struct line*);
138 static int hardwrap_text(struct tab*, struct line*);
139 static int wrap_page(struct tab*);
140 static void print_line(struct line*);
141 static void redraw_tabline(void);
142 static void redraw_body(struct tab*);
143 static void redraw_modeline(struct tab*);
144 static void redraw_minibuffer(void);
145 static void redraw_tab(struct tab*);
146 static void message(const char*, ...) __attribute__((format(printf, 1, 2)));
147 static void start_loading_anim(struct tab*);
148 static void update_loading_anim(int, short, void*);
149 static void stop_loading_anim(struct tab*);
150 static void load_url_in_tab(struct tab*, const char*);
151 static void enter_minibuffer(void(*)(void), void(*)(void), void(*)(void), struct histhead*);
152 static void exit_minibuffer(void);
153 static void switch_to_tab(struct tab*);
154 static struct tab *new_tab(void);
156 static struct { int meta, key; } thiskey;
158 static WINDOW *tabline, *body, *modeline, *minibuf;
159 static int body_lines, body_cols;
161 static struct event clminibufev;
162 static int clminibufev_set;
163 static struct timeval clminibufev_timer = { 5, 0 };
164 static struct timeval loadingev_timer = { 0, 250000 };
166 static uint32_t tab_counter;
168 struct ui_state {
169 int curs_x;
170 int curs_y;
171 size_t line_off;
172 size_t line_max;
174 short loading_anim;
175 short loading_anim_step;
176 struct event loadingev;
178 TAILQ_HEAD(, line) head;
179 };
181 static char keybuf[64];
183 struct kmap global_map,
184 minibuffer_map,
185 *current_map,
186 *base_map;
188 static struct histhead eecmd_history,
189 ir_history,
190 lu_history;
192 static int in_minibuffer;
194 static struct {
195 char *curmesg;
197 char buf[1025];
198 size_t off, len;
199 char prompt[32];
200 void (*donefn)(void);
201 void (*abortfn)(void);
203 struct histhead *history;
204 struct hist *hist_cur;
205 size_t hist_off;
206 } ministate;
208 struct lineprefix {
209 const char *prfx1;
210 const char *prfx2;
211 } line_prefixes[] = {
212 [LINE_TEXT] = { "", "" },
213 [LINE_LINK] = { "=> ", " " },
214 [LINE_TITLE_1] = { "# ", " " },
215 [LINE_TITLE_2] = { "## ", " " },
216 [LINE_TITLE_3] = { "### ", " " },
217 [LINE_ITEM] = { "* ", " " },
218 [LINE_QUOTE] = { "> ", "> " },
219 [LINE_PRE_START] = { "```", "```" },
220 [LINE_PRE_CONTENT] = { "", "" },
221 [LINE_PRE_END] = { "```", "```" },
222 };
224 struct line_face {
225 int prop;
226 } line_faces[] = {
227 [LINE_TEXT] = { 0 },
228 [LINE_LINK] = { A_UNDERLINE },
229 [LINE_TITLE_1] = { A_BOLD },
230 [LINE_TITLE_2] = { A_BOLD },
231 [LINE_TITLE_3] = { A_BOLD },
232 [LINE_ITEM] = { 0 },
233 [LINE_QUOTE] = { A_DIM },
234 [LINE_PRE_START] = { 0 },
235 [LINE_PRE_CONTENT] = { 0 },
236 [LINE_PRE_END] = { 0 },
237 };
239 static inline void
240 global_set_key(const char *key, void (*fn)(struct tab*))
242 if (!kmap_define_key(&global_map, key, fn))
243 _exit(1);
246 static inline void
247 minibuffer_set_key(const char *key, void (*fn)(struct tab*))
249 if (!kmap_define_key(&minibuffer_map, key, fn))
250 _exit(1);
253 static void
254 load_default_keys(void)
256 /* === global map === */
258 /* emacs */
259 global_set_key("C-p", cmd_previous_line);
260 global_set_key("C-n", cmd_next_line);
261 global_set_key("C-f", cmd_forward_char);
262 global_set_key("C-b", cmd_backward_char);
263 global_set_key("C-a", cmd_move_beginning_of_line);
264 global_set_key("C-e", cmd_move_end_of_line);
266 global_set_key("M-v", cmd_scroll_up);
267 global_set_key("C-v", cmd_scroll_down);
268 global_set_key("M-space", cmd_scroll_up);
269 global_set_key("space", cmd_scroll_down);
271 global_set_key("C-x C-c", cmd_kill_telescope);
273 global_set_key("C-g", cmd_clear_minibuf);
275 global_set_key("M-x", cmd_execute_extended_command);
276 global_set_key("C-x C-f", cmd_load_url);
277 global_set_key("C-x M-f", cmd_load_current_url);
279 global_set_key("C-x t 0", cmd_tab_close);
280 global_set_key("C-x t 2", cmd_tab_new);
281 global_set_key("C-x t o", cmd_tab_next);
282 global_set_key("C-x t O", cmd_tab_previous);
284 global_set_key("M-<", cmd_beginning_of_buffer);
285 global_set_key("M->", cmd_end_of_buffer);
287 global_set_key("C-M-b", cmd_previous_page);
288 global_set_key("C-M-f", cmd_next_page);
290 /* vi/vi-like */
291 global_set_key("k", cmd_previous_line);
292 global_set_key("j", cmd_next_line);
293 global_set_key("l", cmd_forward_char);
294 global_set_key("h", cmd_backward_char);
295 global_set_key("^", cmd_move_beginning_of_line);
296 global_set_key("$", cmd_move_end_of_line);
298 global_set_key("K", cmd_scroll_line_up);
299 global_set_key("J", cmd_scroll_line_down);
301 global_set_key("g g", cmd_beginning_of_buffer);
302 global_set_key("G", cmd_end_of_buffer);
304 global_set_key("H", cmd_previous_page);
305 global_set_key("L", cmd_next_page);
307 /* tmp */
308 global_set_key("q", cmd_kill_telescope);
310 global_set_key("esc", cmd_clear_minibuf);
312 global_set_key(":", cmd_execute_extended_command);
314 /* cua */
315 global_set_key("<up>", cmd_previous_line);
316 global_set_key("<down>", cmd_next_line);
317 global_set_key("<right>", cmd_forward_char);
318 global_set_key("<left>", cmd_backward_char);
319 global_set_key("<prior>", cmd_scroll_up);
320 global_set_key("<next>", cmd_scroll_down);
322 global_set_key("M-<left>", cmd_previous_page);
323 global_set_key("M-<right>", cmd_next_page);
325 /* "ncurses standard" */
326 global_set_key("C-l", cmd_redraw);
328 /* global */
329 global_set_key("C-m", cmd_push_button);
330 global_set_key("M-enter", cmd_push_button_new_tab);
332 /* === minibuffer map === */
333 minibuffer_set_key("ret", cmd_mini_complete_and_exit);
334 minibuffer_set_key("C-g", cmd_mini_abort);
335 minibuffer_set_key("esc", cmd_mini_abort);
336 minibuffer_set_key("C-d", cmd_mini_delete_char);
337 minibuffer_set_key("del", cmd_mini_delete_backward_char);
339 minibuffer_set_key("C-f", cmd_mini_forward_char);
340 minibuffer_set_key("C-b", cmd_mini_backward_char);
341 minibuffer_set_key("<right>", cmd_mini_forward_char);
342 minibuffer_set_key("<left>", cmd_mini_backward_char);
343 minibuffer_set_key("C-e", cmd_mini_move_end_of_line);
344 minibuffer_set_key("C-a", cmd_mini_move_beginning_of_line);
345 minibuffer_set_key("<end>", cmd_mini_move_end_of_line);
346 minibuffer_set_key("<home>", cmd_mini_move_beginning_of_line);
347 minibuffer_set_key("C-k", cmd_mini_kill_line);
349 minibuffer_set_key("M-p", cmd_mini_previous_history_element);
350 minibuffer_set_key("M-n", cmd_mini_next_history_element);
351 minibuffer_set_key("<up>", cmd_mini_previous_history_element);
352 minibuffer_set_key("<down>", cmd_mini_next_history_element);
355 static int
356 push_line(struct tab *tab, const struct line *l, const char *buf, size_t len, int cont)
358 struct line *vl;
360 tab->s->line_max++;
362 if ((vl = calloc(1, sizeof(*vl))) == NULL)
363 return 0;
365 if (len != 0 && (vl->line = calloc(1, len+1)) == NULL) {
366 free(vl);
367 return 0;
370 vl->type = l->type;
371 if (len != 0)
372 memcpy(vl->line, buf, len);
373 vl->alt = l->alt;
374 vl->flags = cont;
376 if (TAILQ_EMPTY(&tab->s->head))
377 TAILQ_INSERT_HEAD(&tab->s->head, vl, lines);
378 else
379 TAILQ_INSERT_TAIL(&tab->s->head, vl, lines);
380 return 1;
383 static void
384 empty_vlist(struct tab *tab)
386 struct line *l, *t;
388 tab->s->line_max = 0;
390 TAILQ_FOREACH_SAFE(l, &tab->s->head, lines, t) {
391 TAILQ_REMOVE(&tab->s->head, l, lines);
392 free(l->line);
393 /* l->alt references the original line! */
394 free(l);
398 static void
399 restore_cursor(struct tab *tab)
401 wmove(body, tab->s->curs_y, tab->s->curs_x);
404 static void
405 cmd_previous_line(struct tab *tab)
407 if (--tab->s->curs_y < 0) {
408 tab->s->curs_y = 0;
409 cmd_scroll_line_up(tab);
412 restore_cursor(tab);
415 static void
416 cmd_next_line(struct tab *tab)
418 if (tab->s->line_off + tab->s->curs_y >= tab->s->line_max)
419 return;
421 if (++tab->s->curs_y > body_lines-1) {
422 tab->s->curs_y = body_lines-1;
423 cmd_scroll_line_down(tab);
426 restore_cursor(tab);
429 static void
430 cmd_forward_char(struct tab *tab)
432 tab->s->curs_x = MIN(body_cols-1, tab->s->curs_x+1);
433 restore_cursor(tab);
436 static void
437 cmd_backward_char(struct tab *tab)
439 tab->s->curs_x = MAX(0, tab->s->curs_x-1);
440 restore_cursor(tab);
443 static void
444 cmd_move_beginning_of_line(struct tab *tab)
446 tab->s->curs_x = 0;
447 restore_cursor(tab);
450 static void
451 cmd_move_end_of_line(struct tab *tab)
453 struct line *line;
454 size_t off;
455 const char *prfx;
457 off = tab->s->line_off + tab->s->curs_y;
458 if (off >= tab->s->line_max) {
459 tab->s->curs_x = 0;
460 goto end;
463 line = nth_line(tab, off);
464 if (line->line != NULL)
465 tab->s->curs_x = strlen(line->line);
466 else
467 tab->s->curs_x = 0;
469 prfx = line_prefixes[line->type].prfx1;
470 tab->s->curs_x += strlen(prfx);
472 end:
473 restore_cursor(tab);
476 static void
477 cmd_redraw(struct tab *tab)
479 handle_resize(0, 0, NULL);
482 static void
483 cmd_scroll_line_up(struct tab *tab)
485 struct line *l;
487 if (tab->s->line_off == 0)
488 return;
490 l = nth_line(tab, --tab->s->line_off);
491 wscrl(body, -1);
492 wmove(body, 0, 0);
493 print_line(l);
496 static void
497 cmd_scroll_line_down(struct tab *tab)
499 struct line *l;
500 size_t n;
502 if (tab->s->line_max == 0 || tab->s->line_off == tab->s->line_max-1)
503 return;
505 tab->s->line_off++;
506 wscrl(body, 1);
508 if (tab->s->line_max - tab->s->line_off < body_lines)
509 return;
511 l = nth_line(tab, tab->s->line_off + body_lines-1);
512 wmove(body, body_lines-1, 0);
513 print_line(l);
516 static void
517 cmd_scroll_up(struct tab *tab)
519 size_t off;
521 off = body_lines+1;
523 for (; off > 0; --off)
524 cmd_scroll_line_up(tab);
527 static void
528 cmd_scroll_down(struct tab *tab)
530 size_t off;
532 off = body_lines+1;
534 for (; off > 0; --off)
535 cmd_scroll_line_down(tab);
538 static void
539 cmd_beginning_of_buffer(struct tab *tab)
541 tab->s->line_off = 0;
542 tab->s->curs_y = 0;
543 redraw_body(tab);
546 static void
547 cmd_end_of_buffer(struct tab *tab)
549 ssize_t off;
551 off = tab->s->line_max - body_lines;
552 off = MAX(0, off);
554 tab->s->line_off = off;
555 tab->s->curs_y = MIN(body_lines, tab->s->line_max);
557 redraw_body(tab);
560 static void
561 cmd_kill_telescope(struct tab *tab)
563 event_loopbreak();
566 static void
567 cmd_push_button(struct tab *tab)
569 struct line *l;
570 size_t nth;
572 nth = tab->s->line_off + tab->s->curs_y;
573 if (nth >= tab->s->line_max)
574 return;
575 l = nth_line(tab, nth);
576 if (l->type != LINE_LINK)
577 return;
579 load_url_in_tab(tab, l->alt);
582 static void
583 cmd_push_button_new_tab(struct tab *tab)
585 struct tab *t;
586 struct line *l;
587 size_t nth;
589 nth = tab->s->line_off + tab->s->curs_y;
590 if (nth > tab->s->line_max)
591 return;
592 l = nth_line(tab, nth);
593 if (l->type != LINE_LINK)
594 return;
596 t = new_tab();
597 memcpy(&t->url, &tab->url, sizeof(tab->url));
598 load_url_in_tab(t, l->alt);
601 static void
602 cmd_previous_page(struct tab *tab)
604 if (!load_previous_page(tab))
605 message("No previous page");
608 static void
609 cmd_next_page(struct tab *tab)
611 if (!load_next_page(tab))
612 message("No next page");
615 static void
616 cmd_clear_minibuf(struct tab *tab)
618 handle_clear_minibuf(0, 0, NULL);
621 static void
622 cmd_execute_extended_command(struct tab *tab)
624 size_t len;
626 enter_minibuffer(eecmd_self_insert, eecmd_select, exit_minibuffer,
627 &eecmd_history);
629 len = sizeof(ministate.prompt);
630 strlcpy(ministate.prompt, "", len);
632 if (thiskey.meta)
633 strlcat(ministate.prompt, "M-", len);
635 strlcat(ministate.prompt, keyname(thiskey.key), len);
636 strlcat(ministate.prompt, " ", len);
639 static void
640 cmd_tab_close(struct tab *tab)
642 struct tab *t;
644 if (TAILQ_PREV(tab, tabshead, tabs) == NULL &&
645 TAILQ_NEXT(tab, tabs) == NULL) {
646 message("Can't close the only tab.");
647 return;
650 stop_tab(tab);
652 t = TAILQ_PREV(tab, tabshead, tabs);
653 t->flags |= TAB_CURRENT;
655 TAILQ_REMOVE(&tabshead, tab, tabs);
657 free(tab->s);
658 free(tab);
661 static void
662 cmd_tab_new(struct tab *tab)
664 new_tab();
667 static void
668 cmd_tab_next(struct tab *tab)
670 struct tab *t;
672 tab->flags &= ~TAB_CURRENT;
674 if ((t = TAILQ_NEXT(tab, tabs)) == NULL)
675 t = TAILQ_FIRST(&tabshead);
676 t->flags |= TAB_CURRENT;
679 static void
680 cmd_tab_previous(struct tab *tab)
682 struct tab *t;
684 tab->flags &= ~TAB_CURRENT;
686 if ((t = TAILQ_PREV(tab, tabshead, tabs)) == NULL)
687 t = TAILQ_LAST(&tabshead, tabshead);
688 t->flags |= TAB_CURRENT;
691 static void
692 cmd_load_url(struct tab *tab)
694 enter_minibuffer(lu_self_insert, lu_select, exit_minibuffer,
695 &lu_history);
696 strlcpy(ministate.prompt, "Load URL: ", sizeof(ministate.prompt));
699 static void
700 cmd_load_current_url(struct tab *tab)
702 enter_minibuffer(lu_self_insert, lu_select, exit_minibuffer,
703 &lu_history);
704 strlcpy(ministate.prompt, "Load URL: ", sizeof(ministate.prompt));
705 strlcpy(ministate.buf, tab->hist_cur->h, sizeof(ministate.buf));
706 ministate.off = strlen(tab->hist_cur->h);
707 ministate.len = ministate.off;
710 static void
711 global_key_unbound(void)
713 message("%s is undefined", keybuf);
716 static void
717 cmd_mini_delete_char(struct tab *tab)
719 minibuffer_taint_hist();
721 if (ministate.len == 0 || ministate.off == ministate.len)
722 return;
724 memmove(&ministate.buf[ministate.off],
725 &ministate.buf[ministate.off+1],
726 ministate.len - ministate.off + 1);
727 ministate.len--;
730 static void
731 cmd_mini_delete_backward_char(struct tab *tab)
733 minibuffer_taint_hist();
735 if (ministate.len == 0 || ministate.off == 0)
736 return;
738 memmove(&ministate.buf[ministate.off-1],
739 &ministate.buf[ministate.off],
740 ministate.len - ministate.off + 1);
741 ministate.off--;
742 ministate.len--;
745 static void
746 cmd_mini_forward_char(struct tab *tab)
748 if (ministate.off == ministate.len)
749 return;
750 ministate.off++;
753 static void
754 cmd_mini_backward_char(struct tab *tab)
756 if (ministate.off == 0)
757 return;
758 ministate.off--;
761 static void
762 cmd_mini_move_end_of_line(struct tab *tab)
764 ministate.off = ministate.len;
767 static void
768 cmd_mini_move_beginning_of_line(struct tab *tab)
770 ministate.off = 0;
773 static void
774 cmd_mini_kill_line(struct tab *tab)
776 minibuffer_taint_hist();
778 if (ministate.off == ministate.len)
779 return;
780 ministate.buf[ministate.off] = '\0';
781 ministate.len -= ministate.off;
784 static void
785 cmd_mini_abort(struct tab *tab)
787 ministate.abortfn();
790 static void
791 cmd_mini_complete_and_exit(struct tab *tab)
793 minibuffer_taint_hist();
794 ministate.donefn();
797 static void
798 cmd_mini_previous_history_element(struct tab *tab)
800 if (ministate.history == NULL) {
801 message("No history");
802 return;
805 if (ministate.hist_cur == NULL ||
806 (ministate.hist_cur = TAILQ_PREV(ministate.hist_cur, mhisthead, entries)) == NULL) {
807 ministate.hist_cur = TAILQ_LAST(&ministate.history->head, mhisthead);
808 ministate.hist_off = ministate.history->len - 1;
809 if (ministate.hist_cur == NULL)
810 message("No prev item");
811 } else {
812 ministate.hist_off--;
815 if (ministate.hist_cur != NULL) {
816 ministate.off = 0;
817 ministate.len = strlen(ministate.hist_cur->h);
821 static void
822 cmd_mini_next_history_element(struct tab *tab)
824 if (ministate.history == NULL) {
825 message("No history");
826 return;
829 if (ministate.hist_cur == NULL ||
830 (ministate.hist_cur = TAILQ_NEXT(ministate.hist_cur, entries)) == NULL) {
831 ministate.hist_cur = TAILQ_FIRST(&ministate.history->head);
832 ministate.hist_off = 0;
833 if (ministate.hist_cur == NULL)
834 message("No next item");
835 } else {
836 ministate.hist_off++;
839 if (ministate.hist_cur != NULL) {
840 ministate.off = 0;
841 ministate.len = strlen(ministate.hist_cur->h);
845 static void
846 minibuffer_hist_save_entry(void)
848 struct hist *hist;
850 if (ministate.history == NULL)
851 return;
853 if ((hist = calloc(1, sizeof(*hist))) == NULL)
854 abort();
856 strlcpy(hist->h, ministate.buf, sizeof(hist->h));
858 if (TAILQ_EMPTY(&ministate.history->head))
859 TAILQ_INSERT_HEAD(&ministate.history->head, hist, entries);
860 else
861 TAILQ_INSERT_TAIL(&ministate.history->head, hist, entries);
862 ministate.history->len++;
865 /*
866 * taint the minibuffer cache: if we're currently showing a history
867 * element, copy that to the current buf and reset the "history
868 * navigation" thing.
869 */
870 static void
871 minibuffer_taint_hist(void)
873 if (ministate.hist_cur == NULL)
874 return;
876 strlcpy(ministate.buf, ministate.hist_cur->h, sizeof(ministate.buf));
877 ministate.hist_cur = NULL;
880 static void
881 minibuffer_self_insert(void)
883 minibuffer_taint_hist();
885 if (ministate.len == sizeof(ministate.buf) -1)
886 return;
888 /* TODO: utf8 handling! */
890 memmove(&ministate.buf[ministate.off+1],
891 &ministate.buf[ministate.off],
892 ministate.len - ministate.off + 1);
893 ministate.buf[ministate.off] = thiskey.key;
894 ministate.off++;
895 ministate.len++;
898 static void
899 eecmd_self_insert(void)
901 if (thiskey.meta || isspace(thiskey.key) ||
902 !isgraph(thiskey.key)) {
903 global_key_unbound();
904 return;
907 minibuffer_self_insert();
910 static void
911 eecmd_select(void)
913 exit_minibuffer();
914 minibuffer_hist_save_entry();
915 message("TODO: try to execute %s", ministate.buf);
918 static void
919 ir_self_insert(void)
921 minibuffer_self_insert();
924 static void
925 ir_select(void)
927 char buf[1025] = {0};
928 struct url url;
929 struct tab *tab;
931 tab = current_tab();
933 exit_minibuffer();
934 minibuffer_hist_save_entry();
936 /* a bit ugly but... */
937 memcpy(&url, &tab->url, sizeof(tab->url));
938 url_set_query(&url, ministate.buf);
939 url_unparse(&url, buf, sizeof(buf));
940 load_url_in_tab(tab, buf);
943 static void
944 lu_self_insert(void)
946 if (thiskey.meta || isspace(thiskey.key) ||
947 !isgraph(thiskey.key)) {
948 global_key_unbound();
949 return;
952 minibuffer_self_insert();
955 static void
956 lu_select(void)
958 exit_minibuffer();
959 minibuffer_hist_save_entry();
960 load_url_in_tab(current_tab(), ministate.buf);
963 static struct line *
964 nth_line(struct tab *tab, size_t n)
966 struct line *l;
967 size_t i;
969 i = 0;
970 TAILQ_FOREACH(l, &tab->s->head, lines) {
971 if (i == n)
972 return l;
973 i++;
976 /* unreachable */
977 abort();
980 static struct tab *
981 current_tab(void)
983 struct tab *t;
985 TAILQ_FOREACH(t, &tabshead, tabs) {
986 if (t->flags & TAB_CURRENT)
987 return t;
990 /* unreachable */
991 abort();
994 static void
995 dispatch_stdio(int fd, short ev, void *d)
997 struct tab *tab;
998 struct keymap *k;
999 const char *keyname;
1000 char tmp[2] = {0};
1002 thiskey.key = wgetch(body);
1003 if (thiskey.key == ERR)
1004 return;
1005 if (thiskey.key == 27) {
1006 /* TODO: make escape-time customizable */
1008 thiskey.meta = 1;
1009 thiskey.key = wgetch(body);
1010 if (thiskey.key == ERR || thiskey.key == 27) {
1011 thiskey.meta = 0;
1012 thiskey.key = 27;
1014 } else
1015 thiskey.meta = 0;
1017 if (keybuf[0] != '\0')
1018 strlcat(keybuf, " ", sizeof(keybuf));
1019 if (thiskey.meta)
1020 strlcat(keybuf, "M-", sizeof(keybuf));
1021 if ((keyname = unkbd(thiskey.key)) != NULL)
1022 strlcat(keybuf, keyname, sizeof(keybuf));
1023 else {
1024 tmp[0] = thiskey.key;
1025 strlcat(keybuf, tmp, sizeof(keybuf));
1028 TAILQ_FOREACH(k, &current_map->m, keymaps) {
1029 if (k->meta == thiskey.meta &&
1030 k->key == thiskey.key) {
1031 if (k->fn == NULL)
1032 current_map = &k->map;
1033 else {
1034 current_map = base_map;
1035 strlcpy(keybuf, "", sizeof(keybuf));
1036 k->fn(current_tab());
1038 goto done;
1042 if (current_map->unhandled_input != NULL)
1043 current_map->unhandled_input();
1044 else {
1045 global_key_unbound();
1048 strlcpy(keybuf, "", sizeof(keybuf));
1049 current_map = base_map;
1051 done:
1052 redraw_tab(current_tab());
1055 static void
1056 handle_clear_minibuf(int fd, short ev, void *d)
1058 clminibufev_set = 0;
1060 free(ministate.curmesg);
1061 ministate.curmesg = NULL;
1063 redraw_minibuffer();
1064 if (in_minibuffer) {
1065 wrefresh(body);
1066 wrefresh(minibuf);
1067 } else {
1068 wrefresh(minibuf);
1069 wrefresh(body);
1073 static void
1074 handle_resize(int sig, short ev, void *d)
1076 struct tab *tab;
1078 endwin();
1079 refresh();
1080 clear();
1082 /* move and resize the windows, in reverse order! */
1084 mvwin(minibuf, LINES-1, 0);
1085 wresize(minibuf, 1, COLS);
1087 mvwin(modeline, LINES-2, 0);
1088 wresize(modeline, 1, COLS);
1090 wresize(body, LINES-3, COLS);
1091 body_lines = LINES-3;
1092 body_cols = COLS;
1094 wresize(tabline, 1, COLS);
1096 tab = current_tab();
1098 wrap_page(tab);
1099 redraw_tab(tab);
1103 * Helper function for wrap_text. Find the end of the current word
1104 * and the end of the separator after the word.
1106 static int
1107 word_boundaries(const char *s, const char *sep, const char **endword, const char **endspc)
1109 *endword = s;
1110 *endword = s;
1112 if (*s == '\0')
1113 return 0;
1115 /* find the end of the current world */
1116 for (; *s != '\0'; ++s) {
1117 if (strchr(sep, *s) != NULL)
1118 break;
1121 *endword = s;
1123 /* find the end of the separator */
1124 for (; *s != '\0'; ++s) {
1125 if (strchr(sep, *s) == NULL)
1126 break;
1129 *endspc = s;
1131 return 1;
1134 static inline int
1135 emitline(struct tab *tab, size_t zero, size_t *off, const struct line *l,
1136 const char **line, int *cont)
1138 if (!push_line(tab, l, *line, *off - zero, *cont))
1139 return 0;
1140 if (!*cont)
1141 *cont = 1;
1142 *line += *off - zero;
1143 *off = zero;
1144 return 1;
1147 static inline void
1148 emitstr(const char **s, size_t len, size_t *off)
1150 size_t i;
1152 /* printw("%*s", ...) doesn't seem to respect the precision, so... */
1153 for (i = 0; i < len; ++i)
1154 addch((*s)[i]);
1155 *off += len;
1156 *s += len;
1160 * Build a list of visual line by wrapping the given line, assuming
1161 * that when printed will have a leading prefix prfx.
1163 * TODO: it considers each byte one cell on the screen!
1165 static void
1166 wrap_text(struct tab *tab, const char *prfx, struct line *l)
1168 size_t zero, off, len, split;
1169 int cont = 0;
1170 const char *endword, *endspc, *line, *linestart;
1172 zero = strlen(prfx);
1173 off = zero;
1174 line = l->line;
1175 linestart = l->line;
1177 while (word_boundaries(line, " \t-", &endword, &endspc)) {
1178 len = endword - line;
1179 if (off + len >= body_cols) {
1180 emitline(tab, zero, &off, l, &linestart, &cont);
1181 while (len >= body_cols) {
1182 /* hard wrap */
1183 emitline(tab, zero, &off, l, &linestart, &cont);
1184 len -= body_cols-1;
1185 line += body_cols-1;
1188 if (len != 0)
1189 off += len;
1190 } else
1191 off += len;
1193 /* print the spaces iff not at bol */
1194 len = endspc - endword;
1195 /* line = endspc; */
1196 if (off != zero) {
1197 if (off + len >= body_cols) {
1198 emitline(tab, zero, &off, l, &linestart, &cont);
1199 linestart = endspc;
1200 } else
1201 off += len;
1204 line = endspc;
1207 emitline(tab, zero, &off, l, &linestart, &cont);
1210 static int
1211 hardwrap_text(struct tab *tab, struct line *l)
1213 size_t off, len;
1214 int cont;
1215 const char *linestart;
1217 if (l->line == NULL)
1218 return emitline(tab, 0, &off, l, &linestart, &cont);
1220 len = strlen(l->line);
1221 off = 0;
1222 linestart = l->line;
1224 while (len >= COLS) {
1225 len -= COLS-1;
1226 off = COLS-1;
1227 if (!emitline(tab, 0, &off, l, &linestart, &cont))
1228 return 0;
1231 if (len != 0)
1232 return emitline(tab, 0, &len, l, &linestart, &cont);
1234 return 1;
1237 static int
1238 wrap_page(struct tab *tab)
1240 struct line *l;
1241 const char *prfx;
1243 empty_vlist(tab);
1245 TAILQ_FOREACH(l, &tab->page.head, lines) {
1246 prfx = line_prefixes[l->type].prfx1;
1247 switch (l->type) {
1248 case LINE_TEXT:
1249 case LINE_LINK:
1250 case LINE_TITLE_1:
1251 case LINE_TITLE_2:
1252 case LINE_TITLE_3:
1253 case LINE_ITEM:
1254 case LINE_QUOTE:
1255 wrap_text(tab, prfx, l);
1256 break;
1257 case LINE_PRE_START:
1258 case LINE_PRE_END:
1259 push_line(tab, l, NULL, 0, 0);
1260 break;
1261 case LINE_PRE_CONTENT:
1262 hardwrap_text(tab, l);
1263 break;
1266 return 1;
1269 static inline void
1270 print_line(struct line *l)
1272 const char *text = l->line;
1273 const char *prfx;
1274 int face = line_faces[l->type].prop;
1276 if (!l->flags)
1277 prfx = line_prefixes[l->type].prfx1;
1278 else
1279 prfx = line_prefixes[l->type].prfx2;
1281 if (text == NULL)
1282 text = "";
1284 if (face != 0)
1285 wattron(body, face);
1286 wprintw(body, "%s%s", prfx, text);
1287 if (face != 0)
1288 wattroff(body, face);
1291 static void
1292 redraw_tabline(void)
1294 struct tab *tab;
1295 int current;
1296 const char *title;
1298 werase(tabline);
1299 wbkgd(tabline, A_REVERSE);
1301 wprintw(tabline, " ");
1302 TAILQ_FOREACH(tab, &tabshead, tabs) {
1303 current = tab->flags & TAB_CURRENT;
1305 if (*(title = tab->page.title) == '\0')
1306 title = tab->hist_cur->h;
1308 if (current)
1309 wattron(tabline, A_UNDERLINE);
1311 wprintw(tabline, "%s%d: %s",
1312 current ? "*" : " ", tab->id, title);
1314 if (current)
1315 wattroff(tabline, A_UNDERLINE);
1319 static void
1320 redraw_modeline(struct tab *tab)
1322 double pct;
1323 int x, y, max_x, max_y;
1324 const char *mode = tab->page.name;
1325 const char *spin = "-\\|/";
1327 werase(modeline);
1328 wattron(modeline, A_REVERSE);
1329 wmove(modeline, 0, 0);
1331 wprintw(modeline, "-%c %s-mode ",
1332 spin[tab->s->loading_anim_step], mode);
1334 pct = (tab->s->line_off + tab->s->curs_y) * 100.0 / tab->s->line_max;
1336 if (tab->s->line_max <= body_lines)
1337 wprintw(modeline, "All ");
1338 else if (tab->s->line_off == 0)
1339 wprintw(modeline, "Top ");
1340 else if (tab->s->line_off + body_lines >= tab->s->line_max)
1341 wprintw(modeline, "Bottom ");
1342 else
1343 wprintw(modeline, "%.0f%% ", pct);
1345 wprintw(modeline, "%d/%d %s ",
1346 tab->s->line_off + tab->s->curs_y,
1347 tab->s->line_max,
1348 tab->hist_cur->h);
1350 getyx(modeline, y, x);
1351 getmaxyx(modeline, max_y, max_x);
1353 (void)y;
1354 (void)max_y;
1356 for (; x < max_x; ++x)
1357 waddstr(modeline, "-");
1360 static void
1361 redraw_minibuffer(void)
1363 size_t skip = 0, off_x = 0, off_y = 0;
1365 werase(minibuf);
1366 if (in_minibuffer) {
1367 mvwprintw(minibuf, 0, 0, "%s", ministate.prompt);
1368 if (ministate.hist_cur != NULL)
1369 wprintw(minibuf, "(%zu/%zu) ",
1370 ministate.hist_off + 1,
1371 ministate.history->len);
1373 getyx(minibuf, off_y, off_x);
1375 while (ministate.off - skip > COLS / 2) {
1376 skip += MIN(ministate.off/4, 1);
1379 if (ministate.hist_cur != NULL)
1380 wprintw(minibuf, "%s", ministate.hist_cur->h + skip);
1381 else
1382 wprintw(minibuf, "%s", ministate.buf + skip);
1385 if (ministate.curmesg != NULL) {
1386 if (in_minibuffer)
1387 wprintw(minibuf, " [%s]", ministate.curmesg);
1388 else
1389 wprintw(minibuf, "%s", ministate.curmesg);
1392 if (!in_minibuffer && ministate.curmesg == NULL)
1393 wprintw(minibuf, "%s", keybuf);
1395 if (in_minibuffer)
1396 wmove(minibuf, 0, off_x + ministate.off - skip);
1399 static void
1400 redraw_tab(struct tab *tab)
1402 redraw_tabline();
1403 redraw_body(tab);
1404 redraw_modeline(tab);
1405 redraw_minibuffer();
1407 restore_cursor(tab);
1408 wrefresh(tabline);
1409 wrefresh(modeline);
1411 if (in_minibuffer) {
1412 wrefresh(body);
1413 wrefresh(minibuf);
1414 } else {
1415 wrefresh(minibuf);
1416 wrefresh(body);
1420 static void
1421 redraw_body(struct tab *tab)
1423 struct line *l;
1424 int line;
1426 werase(body);
1428 tab->s->line_off = MIN(tab->s->line_max, tab->s->line_off);
1429 if (TAILQ_EMPTY(&tab->s->head))
1430 return;
1432 line = 0;
1433 l = nth_line(tab, tab->s->line_off);
1434 for (; l != NULL; l = TAILQ_NEXT(l, lines)) {
1435 wmove(body, line, 0);
1436 print_line(l);
1437 line++;
1438 if (line == body_lines)
1439 break;
1443 static void
1444 message(const char *fmt, ...)
1446 va_list ap;
1448 if (clminibufev_set)
1449 evtimer_del(&clminibufev);
1450 evtimer_set(&clminibufev, handle_clear_minibuf, NULL);
1451 evtimer_add(&clminibufev, &clminibufev_timer);
1452 clminibufev_set = 1;
1454 free(ministate.curmesg);
1456 va_start(ap, fmt);
1457 /* TODO: what to do if the allocation fails here? */
1458 if (vasprintf(&ministate.curmesg, fmt, ap) == -1)
1459 ministate.curmesg = NULL;
1460 va_end(ap);
1462 redraw_minibuffer();
1464 if (in_minibuffer) {
1465 wrefresh(body);
1466 wrefresh(minibuf);
1467 } else {
1468 wrefresh(minibuf);
1469 wrefresh(body);
1473 static void
1474 start_loading_anim(struct tab *tab)
1476 if (tab->s->loading_anim)
1477 return;
1478 tab->s->loading_anim = 1;
1479 evtimer_set(&tab->s->loadingev, update_loading_anim, tab);
1480 evtimer_add(&tab->s->loadingev, &loadingev_timer);
1483 static void
1484 update_loading_anim(int fd, short ev, void *d)
1486 struct tab *tab = d;
1488 tab->s->loading_anim_step = (tab->s->loading_anim_step+1)%4;
1490 redraw_modeline(tab);
1491 wrefresh(modeline);
1493 wrefresh(body);
1494 if (in_minibuffer)
1495 wrefresh(minibuf);
1497 evtimer_add(&tab->s->loadingev, &loadingev_timer);
1500 static void
1501 stop_loading_anim(struct tab *tab)
1503 if (!tab->s->loading_anim)
1504 return;
1505 evtimer_del(&tab->s->loadingev);
1506 tab->s->loading_anim = 0;
1507 tab->s->loading_anim_step = 0;
1509 redraw_modeline(tab);
1511 wrefresh(modeline);
1512 wrefresh(body);
1513 if (in_minibuffer)
1514 wrefresh(minibuf);
1517 static void
1518 load_url_in_tab(struct tab *tab, const char *url)
1520 empty_vlist(tab);
1521 message("Loading %s...", url);
1522 start_loading_anim(tab);
1523 load_url(tab, url);
1525 tab->s->curs_x = 0;
1526 tab->s->curs_y = 0;
1527 redraw_tab(tab);
1530 static void
1531 enter_minibuffer(void (*self_insert_fn)(void), void (*donefn)(void),
1532 void (*abortfn)(void), struct histhead *hist)
1534 in_minibuffer = 1;
1535 base_map = &minibuffer_map;
1536 current_map = &minibuffer_map;
1538 base_map->unhandled_input = self_insert_fn;
1540 ministate.donefn = donefn;
1541 ministate.abortfn = abortfn;
1542 memset(ministate.buf, 0, sizeof(ministate.buf));
1543 ministate.off = 0;
1544 ministate.len = 0;
1545 strlcpy(ministate.buf, "", sizeof(ministate.prompt));
1547 ministate.history = hist;
1548 ministate.hist_cur = NULL;
1549 ministate.hist_off = 0;
1552 static void
1553 exit_minibuffer(void)
1555 werase(minibuf);
1557 in_minibuffer = 0;
1558 base_map = &global_map;
1559 current_map = &global_map;
1562 static void
1563 switch_to_tab(struct tab *tab)
1565 struct tab *t;
1567 TAILQ_FOREACH(t, &tabshead, tabs) {
1568 t->flags &= ~TAB_CURRENT;
1571 tab->flags |= TAB_CURRENT;
1574 static struct tab *
1575 new_tab(void)
1577 struct tab *tab, *t;
1578 const char *url = "about:new";
1580 if ((tab = calloc(1, sizeof(*tab))) == NULL)
1581 goto err;
1583 TAILQ_INIT(&tab->hist.head);
1585 if ((tab->s = calloc(1, sizeof(*t->s))) == NULL)
1586 goto err;
1588 TAILQ_INIT(&tab->s->head);
1590 tab->id = tab_counter++;
1591 switch_to_tab(tab);
1593 if (TAILQ_EMPTY(&tabshead))
1594 TAILQ_INSERT_HEAD(&tabshead, tab, tabs);
1595 else
1596 TAILQ_INSERT_TAIL(&tabshead, tab, tabs);
1598 load_url_in_tab(tab, url);
1599 return tab;
1601 err:
1602 event_loopbreak();
1603 return NULL;
1606 int
1607 ui_init(void)
1609 setlocale(LC_ALL, "");
1611 TAILQ_INIT(&global_map.m);
1612 global_map.unhandled_input = global_key_unbound;
1614 TAILQ_INIT(&minibuffer_map.m);
1616 TAILQ_INIT(&eecmd_history.head);
1617 TAILQ_INIT(&ir_history.head);
1618 TAILQ_INIT(&lu_history.head);
1620 base_map = &global_map;
1621 current_map = &global_map;
1622 load_default_keys();
1624 initscr();
1625 raw();
1626 noecho();
1628 nonl();
1629 intrflush(stdscr, FALSE);
1631 if ((tabline = newwin(1, COLS, 0, 0)) == NULL)
1632 return 0;
1633 if ((body = newwin(LINES - 3, COLS, 1, 0)) == NULL)
1634 return 0;
1635 if ((modeline = newwin(1, COLS, LINES-2, 0)) == NULL)
1636 return 0;
1637 if ((minibuf = newwin(1, COLS, LINES-1, 0)) == NULL)
1638 return 0;
1640 body_lines = LINES-3;
1641 body_cols = COLS;
1643 keypad(body, TRUE);
1644 scrollok(body, TRUE);
1646 /* non-blocking input */
1647 wtimeout(body, 0);
1649 mvwprintw(body, 0, 0, "");
1651 event_set(&stdioev, 0, EV_READ | EV_PERSIST, dispatch_stdio, NULL);
1652 event_add(&stdioev, NULL);
1654 signal_set(&winchev, SIGWINCH, handle_resize, NULL);
1655 signal_add(&winchev, NULL);
1657 new_tab();
1659 return 1;
1662 void
1663 ui_on_tab_loaded(struct tab *tab)
1665 stop_loading_anim(tab);
1666 message("Loaded %s", tab->hist_cur->h);
1669 void
1670 ui_on_tab_refresh(struct tab *tab)
1672 if (!(tab->flags & TAB_CURRENT))
1673 return;
1675 wrap_page(tab);
1676 redraw_tab(tab);
1679 void
1680 ui_require_input(struct tab *tab, int hide)
1682 /* TODO: hard-switching to another tab is ugly */
1683 switch_to_tab(tab);
1685 enter_minibuffer(ir_self_insert, ir_select, exit_minibuffer,
1686 &ir_history);
1687 strlcpy(ministate.prompt, "Input required: ",
1688 sizeof(ministate.prompt));
1689 redraw_tab(tab);
1692 void
1693 ui_end(void)
1695 endwin();