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 * Text scrolling
21 * ==============
22 *
23 * ncurses allows you to scroll a window, but when a line goes out of
24 * the visible area it's forgotten. We keep a list of formatted lines
25 * (``visual lines'') that we know fits in the window, and draw them.
26 * This way is easy to scroll: just call wscrl and then render the
27 * first/last line!
28 *
29 * This means that on every resize we have to clear our list of lines
30 * and re-render everything. A clever approach would be to do this
31 * ``on-demand''.
32 *
33 * TODO: make the text formatting on-demand.
34 *
35 */
37 #include <telescope.h>
39 #include <ctype.h>
40 #include <curses.h>
41 #include <event.h>
42 #include <locale.h>
43 #include <signal.h>
44 #include <stdarg.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <unistd.h>
49 #define TAB_CURRENT 0x1
51 static struct event stdioev, winchev;
53 static void load_default_keys(void);
54 static void empty_vlist(struct tab*);
55 static void restore_cursor(struct tab *);
57 static void cmd_previous_line(struct tab*);
58 static void cmd_next_line(struct tab*);
59 static void cmd_forward_char(struct tab*);
60 static void cmd_backward_char(struct tab*);
61 static void cmd_move_beginning_of_line(struct tab*);
62 static void cmd_move_end_of_line(struct tab*);
63 static void cmd_redraw(struct tab*);
64 static void cmd_scroll_line_down(struct tab*);
65 static void cmd_scroll_line_up(struct tab*);
66 static void cmd_scroll_up(struct tab*);
67 static void cmd_scroll_down(struct tab*);
68 static void cmd_beginning_of_buffer(struct tab*);
69 static void cmd_end_of_buffer(struct tab*);
70 static void cmd_kill_telescope(struct tab*);
71 static void cmd_push_button(struct tab*);
72 static void cmd_push_button_new_tab(struct tab*);
73 static void cmd_previous_page(struct tab*);
74 static void cmd_next_page(struct tab*);
75 static void cmd_clear_minibuf(struct tab*);
76 static void cmd_execute_extended_command(struct tab*);
77 static void cmd_tab_close(struct tab*);
78 static void cmd_tab_new(struct tab*);
79 static void cmd_tab_next(struct tab*);
80 static void cmd_tab_previous(struct tab*);
81 static void cmd_load_url(struct tab*);
82 static void cmd_load_current_url(struct tab*);
84 static void global_key_unbound(void);
86 static void cmd_mini_delete_char(struct tab*);
87 static void cmd_mini_delete_backward_char(struct tab*);
88 static void cmd_mini_forward_char(struct tab*);
89 static void cmd_mini_backward_char(struct tab*);
90 static void cmd_mini_move_end_of_line(struct tab*);
91 static void cmd_mini_move_beginning_of_line(struct tab*);
92 static void cmd_mini_kill_line(struct tab*);
93 static void cmd_mini_abort(struct tab*);
94 static void cmd_mini_complete_and_exit(struct tab*);
95 static void cmd_mini_previous_history_element(struct tab*);
96 static void cmd_mini_next_history_element(struct tab*);
98 static void minibuffer_hist_save_entry(void);
99 static void minibuffer_taint_hist(void);
100 static void minibuffer_self_insert(void);
101 static void eecmd_self_insert(void);
102 static void eecmd_select(void);
103 static void ir_self_insert(void);
104 static void ir_select(void);
105 static void lu_self_insert(void);
106 static void lu_select(void);
108 static struct vline *nth_line(struct tab*, size_t);
109 static struct tab *current_tab(void);
110 static void dispatch_stdio(int, short, void*);
111 static void handle_clear_minibuf(int, short, void*);
112 static void handle_resize(int, short, void*);
113 static int wrap_page(struct tab*);
114 static void print_vline(struct vline*);
115 static void redraw_tabline(void);
116 static void redraw_body(struct tab*);
117 static void redraw_modeline(struct tab*);
118 static void redraw_minibuffer(void);
119 static void redraw_tab(struct tab*);
120 static void message(const char*, ...) __attribute__((format(printf, 1, 2)));
121 static void start_loading_anim(struct tab*);
122 static void update_loading_anim(int, short, void*);
123 static void stop_loading_anim(struct tab*);
124 static void load_url_in_tab(struct tab*, const char*);
125 static void enter_minibuffer(void(*)(void), void(*)(void), void(*)(void), struct histhead*);
126 static void exit_minibuffer(void);
127 static void switch_to_tab(struct tab*);
128 static struct tab *new_tab(void);
130 static struct { int meta, key; } thiskey;
132 static WINDOW *tabline, *body, *modeline, *minibuf;
133 static int body_lines, body_cols;
135 static struct event clminibufev;
136 static int clminibufev_set;
137 static struct timeval clminibufev_timer = { 5, 0 };
138 static struct timeval loadingev_timer = { 0, 250000 };
140 static uint32_t tab_counter;
142 static char keybuf[64];
144 struct kmap global_map,
145 minibuffer_map,
146 *current_map,
147 *base_map;
149 static struct histhead eecmd_history,
150 ir_history,
151 lu_history;
153 static int in_minibuffer;
155 static struct {
156 char *curmesg;
158 char buf[1025];
159 size_t off, len;
160 char prompt[32];
161 void (*donefn)(void);
162 void (*abortfn)(void);
164 struct histhead *history;
165 struct hist *hist_cur;
166 size_t hist_off;
167 } ministate;
169 struct lineprefix {
170 const char *prfx1;
171 const char *prfx2;
172 } line_prefixes[] = {
173 [LINE_TEXT] = { "", "" },
174 [LINE_LINK] = { "=> ", " " },
175 [LINE_TITLE_1] = { "# ", " " },
176 [LINE_TITLE_2] = { "## ", " " },
177 [LINE_TITLE_3] = { "### ", " " },
178 [LINE_ITEM] = { "* ", " " },
179 [LINE_QUOTE] = { "> ", "> " },
180 [LINE_PRE_START] = { "```", "```" },
181 [LINE_PRE_CONTENT] = { "", "" },
182 [LINE_PRE_END] = { "```", "```" },
183 };
185 struct line_face {
186 int prop;
187 } line_faces[] = {
188 [LINE_TEXT] = { 0 },
189 [LINE_LINK] = { A_UNDERLINE },
190 [LINE_TITLE_1] = { A_BOLD },
191 [LINE_TITLE_2] = { A_BOLD },
192 [LINE_TITLE_3] = { A_BOLD },
193 [LINE_ITEM] = { 0 },
194 [LINE_QUOTE] = { A_DIM },
195 [LINE_PRE_START] = { 0 },
196 [LINE_PRE_CONTENT] = { 0 },
197 [LINE_PRE_END] = { 0 },
198 };
200 static void
201 empty_vlist(struct tab *tab)
203 struct vline *vl, *t;
205 tab->s.line_max = 0;
207 TAILQ_FOREACH_SAFE(vl, &tab->s.head, vlines, t) {
208 TAILQ_REMOVE(&tab->s.head, vl, vlines);
209 free(vl->line);
210 free(vl);
214 static inline void
215 global_set_key(const char *key, void (*fn)(struct tab*))
217 if (!kmap_define_key(&global_map, key, fn))
218 _exit(1);
221 static inline void
222 minibuffer_set_key(const char *key, void (*fn)(struct tab*))
224 if (!kmap_define_key(&minibuffer_map, key, fn))
225 _exit(1);
228 static void
229 load_default_keys(void)
231 /* === global map === */
233 /* emacs */
234 global_set_key("C-p", cmd_previous_line);
235 global_set_key("C-n", cmd_next_line);
236 global_set_key("C-f", cmd_forward_char);
237 global_set_key("C-b", cmd_backward_char);
238 global_set_key("C-a", cmd_move_beginning_of_line);
239 global_set_key("C-e", cmd_move_end_of_line);
241 global_set_key("M-v", cmd_scroll_up);
242 global_set_key("C-v", cmd_scroll_down);
243 global_set_key("M-space", cmd_scroll_up);
244 global_set_key("space", cmd_scroll_down);
246 global_set_key("C-x C-c", cmd_kill_telescope);
248 global_set_key("C-g", cmd_clear_minibuf);
250 global_set_key("M-x", cmd_execute_extended_command);
251 global_set_key("C-x C-f", cmd_load_url);
252 global_set_key("C-x M-f", cmd_load_current_url);
254 global_set_key("C-x t 0", cmd_tab_close);
255 global_set_key("C-x t 2", cmd_tab_new);
256 global_set_key("C-x t o", cmd_tab_next);
257 global_set_key("C-x t O", cmd_tab_previous);
259 global_set_key("M-<", cmd_beginning_of_buffer);
260 global_set_key("M->", cmd_end_of_buffer);
262 global_set_key("C-M-b", cmd_previous_page);
263 global_set_key("C-M-f", cmd_next_page);
265 /* vi/vi-like */
266 global_set_key("k", cmd_previous_line);
267 global_set_key("j", cmd_next_line);
268 global_set_key("l", cmd_forward_char);
269 global_set_key("h", cmd_backward_char);
270 global_set_key("^", cmd_move_beginning_of_line);
271 global_set_key("$", cmd_move_end_of_line);
273 global_set_key("K", cmd_scroll_line_up);
274 global_set_key("J", cmd_scroll_line_down);
276 global_set_key("g g", cmd_beginning_of_buffer);
277 global_set_key("G", cmd_end_of_buffer);
279 global_set_key("H", cmd_previous_page);
280 global_set_key("L", cmd_next_page);
282 /* tmp */
283 global_set_key("q", cmd_kill_telescope);
285 global_set_key("esc", cmd_clear_minibuf);
287 global_set_key(":", cmd_execute_extended_command);
289 /* cua */
290 global_set_key("<up>", cmd_previous_line);
291 global_set_key("<down>", cmd_next_line);
292 global_set_key("<right>", cmd_forward_char);
293 global_set_key("<left>", cmd_backward_char);
294 global_set_key("<prior>", cmd_scroll_up);
295 global_set_key("<next>", cmd_scroll_down);
297 global_set_key("M-<left>", cmd_previous_page);
298 global_set_key("M-<right>", cmd_next_page);
300 /* "ncurses standard" */
301 global_set_key("C-l", cmd_redraw);
303 /* global */
304 global_set_key("C-m", cmd_push_button);
305 global_set_key("M-enter", cmd_push_button_new_tab);
307 /* === minibuffer map === */
308 minibuffer_set_key("ret", cmd_mini_complete_and_exit);
309 minibuffer_set_key("C-g", cmd_mini_abort);
310 minibuffer_set_key("esc", cmd_mini_abort);
311 minibuffer_set_key("C-d", cmd_mini_delete_char);
312 minibuffer_set_key("del", cmd_mini_delete_backward_char);
314 minibuffer_set_key("C-f", cmd_mini_forward_char);
315 minibuffer_set_key("C-b", cmd_mini_backward_char);
316 minibuffer_set_key("<right>", cmd_mini_forward_char);
317 minibuffer_set_key("<left>", cmd_mini_backward_char);
318 minibuffer_set_key("C-e", cmd_mini_move_end_of_line);
319 minibuffer_set_key("C-a", cmd_mini_move_beginning_of_line);
320 minibuffer_set_key("<end>", cmd_mini_move_end_of_line);
321 minibuffer_set_key("<home>", cmd_mini_move_beginning_of_line);
322 minibuffer_set_key("C-k", cmd_mini_kill_line);
324 minibuffer_set_key("M-p", cmd_mini_previous_history_element);
325 minibuffer_set_key("M-n", cmd_mini_next_history_element);
326 minibuffer_set_key("<up>", cmd_mini_previous_history_element);
327 minibuffer_set_key("<down>", cmd_mini_next_history_element);
330 static void
331 restore_cursor(struct tab *tab)
333 wmove(body, tab->s.curs_y, tab->s.curs_x);
336 static void
337 cmd_previous_line(struct tab *tab)
339 if (--tab->s.curs_y < 0) {
340 tab->s.curs_y = 0;
341 cmd_scroll_line_up(tab);
344 restore_cursor(tab);
347 static void
348 cmd_next_line(struct tab *tab)
350 if (tab->s.line_off + tab->s.curs_y >= tab->s.line_max)
351 return;
353 if (++tab->s.curs_y > body_lines-1) {
354 tab->s.curs_y = body_lines-1;
355 cmd_scroll_line_down(tab);
358 restore_cursor(tab);
361 static void
362 cmd_forward_char(struct tab *tab)
364 tab->s.curs_x = MIN(body_cols-1, tab->s.curs_x+1);
365 restore_cursor(tab);
368 static void
369 cmd_backward_char(struct tab *tab)
371 tab->s.curs_x = MAX(0, tab->s.curs_x-1);
372 restore_cursor(tab);
375 static void
376 cmd_move_beginning_of_line(struct tab *tab)
378 tab->s.curs_x = 0;
379 restore_cursor(tab);
382 static void
383 cmd_move_end_of_line(struct tab *tab)
385 struct vline *vl;
386 size_t off;
387 const char *prfx;
389 off = tab->s.line_off + tab->s.curs_y;
390 if (off >= tab->s.line_max) {
391 tab->s.curs_x = 0;
392 goto end;
395 vl = nth_line(tab, off);
396 if (vl->line != NULL)
397 tab->s.curs_x = strlen(vl->line);
398 else
399 tab->s.curs_x = 0;
401 prfx = line_prefixes[vl->parent->type].prfx1;
402 tab->s.curs_x += strlen(prfx);
404 end:
405 restore_cursor(tab);
408 static void
409 cmd_redraw(struct tab *tab)
411 handle_resize(0, 0, NULL);
414 static void
415 cmd_scroll_line_up(struct tab *tab)
417 struct vline *vl;
419 if (tab->s.line_off == 0)
420 return;
422 vl = nth_line(tab, --tab->s.line_off);
423 wscrl(body, -1);
424 wmove(body, 0, 0);
425 print_vline(vl);
428 static void
429 cmd_scroll_line_down(struct tab *tab)
431 struct vline *vl;
432 size_t n;
434 if (tab->s.line_max == 0 || tab->s.line_off == tab->s.line_max-1)
435 return;
437 tab->s.line_off++;
438 wscrl(body, 1);
440 if (tab->s.line_max - tab->s.line_off < body_lines)
441 return;
443 vl = nth_line(tab, tab->s.line_off + body_lines-1);
444 wmove(body, body_lines-1, 0);
445 print_vline(vl);
448 static void
449 cmd_scroll_up(struct tab *tab)
451 size_t off;
453 off = body_lines+1;
455 for (; off > 0; --off)
456 cmd_scroll_line_up(tab);
459 static void
460 cmd_scroll_down(struct tab *tab)
462 size_t off;
464 off = body_lines+1;
466 for (; off > 0; --off)
467 cmd_scroll_line_down(tab);
470 static void
471 cmd_beginning_of_buffer(struct tab *tab)
473 tab->s.line_off = 0;
474 tab->s.curs_y = 0;
475 redraw_body(tab);
478 static void
479 cmd_end_of_buffer(struct tab *tab)
481 ssize_t off;
483 off = tab->s.line_max - body_lines;
484 off = MAX(0, off);
486 tab->s.line_off = off;
487 tab->s.curs_y = MIN(body_lines, tab->s.line_max);
489 redraw_body(tab);
492 static void
493 cmd_kill_telescope(struct tab *tab)
495 event_loopbreak();
498 static void
499 cmd_push_button(struct tab *tab)
501 struct vline *vl;
502 size_t nth;
504 nth = tab->s.line_off + tab->s.curs_y;
505 if (nth >= tab->s.line_max)
506 return;
507 vl = nth_line(tab, nth);
508 if (vl->parent->type != LINE_LINK)
509 return;
511 load_url_in_tab(tab, vl->parent->alt);
514 static void
515 cmd_push_button_new_tab(struct tab *tab)
517 struct tab *t;
518 struct vline *vl;
519 size_t nth;
521 nth = tab->s.line_off + tab->s.curs_y;
522 if (nth > tab->s.line_max)
523 return;
524 vl = nth_line(tab, nth);
525 if (vl->parent->type != LINE_LINK)
526 return;
528 t = new_tab();
529 memcpy(&t->url, &tab->url, sizeof(tab->url));
530 load_url_in_tab(t, vl->parent->alt);
533 static void
534 cmd_previous_page(struct tab *tab)
536 if (!load_previous_page(tab))
537 message("No previous page");
538 else
539 start_loading_anim(tab);
542 static void
543 cmd_next_page(struct tab *tab)
545 if (!load_next_page(tab))
546 message("No next page");
547 else
548 start_loading_anim(tab);
551 static void
552 cmd_clear_minibuf(struct tab *tab)
554 handle_clear_minibuf(0, 0, NULL);
557 static void
558 cmd_execute_extended_command(struct tab *tab)
560 size_t len;
562 enter_minibuffer(eecmd_self_insert, eecmd_select, exit_minibuffer,
563 &eecmd_history);
565 len = sizeof(ministate.prompt);
566 strlcpy(ministate.prompt, "", len);
568 if (thiskey.meta)
569 strlcat(ministate.prompt, "M-", len);
571 strlcat(ministate.prompt, keyname(thiskey.key), len);
572 strlcat(ministate.prompt, " ", len);
575 static void
576 cmd_tab_close(struct tab *tab)
578 struct tab *t;
580 if (TAILQ_PREV(tab, tabshead, tabs) == NULL &&
581 TAILQ_NEXT(tab, tabs) == NULL) {
582 message("Can't close the only tab.");
583 return;
586 stop_tab(tab);
588 t = TAILQ_PREV(tab, tabshead, tabs);
589 t->flags |= TAB_CURRENT;
591 TAILQ_REMOVE(&tabshead, tab, tabs);
593 free(tab);
596 static void
597 cmd_tab_new(struct tab *tab)
599 new_tab();
602 static void
603 cmd_tab_next(struct tab *tab)
605 struct tab *t;
607 tab->flags &= ~TAB_CURRENT;
609 if ((t = TAILQ_NEXT(tab, tabs)) == NULL)
610 t = TAILQ_FIRST(&tabshead);
611 t->flags |= TAB_CURRENT;
614 static void
615 cmd_tab_previous(struct tab *tab)
617 struct tab *t;
619 tab->flags &= ~TAB_CURRENT;
621 if ((t = TAILQ_PREV(tab, tabshead, tabs)) == NULL)
622 t = TAILQ_LAST(&tabshead, tabshead);
623 t->flags |= TAB_CURRENT;
626 static void
627 cmd_load_url(struct tab *tab)
629 enter_minibuffer(lu_self_insert, lu_select, exit_minibuffer,
630 &lu_history);
631 strlcpy(ministate.prompt, "Load URL: ", sizeof(ministate.prompt));
634 static void
635 cmd_load_current_url(struct tab *tab)
637 enter_minibuffer(lu_self_insert, lu_select, exit_minibuffer,
638 &lu_history);
639 strlcpy(ministate.prompt, "Load URL: ", sizeof(ministate.prompt));
640 strlcpy(ministate.buf, tab->hist_cur->h, sizeof(ministate.buf));
641 ministate.off = strlen(tab->hist_cur->h);
642 ministate.len = ministate.off;
645 static void
646 global_key_unbound(void)
648 message("%s is undefined", keybuf);
651 static void
652 cmd_mini_delete_char(struct tab *tab)
654 minibuffer_taint_hist();
656 if (ministate.len == 0 || ministate.off == ministate.len)
657 return;
659 memmove(&ministate.buf[ministate.off],
660 &ministate.buf[ministate.off+1],
661 ministate.len - ministate.off + 1);
662 ministate.len--;
665 static void
666 cmd_mini_delete_backward_char(struct tab *tab)
668 minibuffer_taint_hist();
670 if (ministate.len == 0 || ministate.off == 0)
671 return;
673 memmove(&ministate.buf[ministate.off-1],
674 &ministate.buf[ministate.off],
675 ministate.len - ministate.off + 1);
676 ministate.off--;
677 ministate.len--;
680 static void
681 cmd_mini_forward_char(struct tab *tab)
683 if (ministate.off == ministate.len)
684 return;
685 ministate.off++;
688 static void
689 cmd_mini_backward_char(struct tab *tab)
691 if (ministate.off == 0)
692 return;
693 ministate.off--;
696 static void
697 cmd_mini_move_end_of_line(struct tab *tab)
699 ministate.off = ministate.len;
702 static void
703 cmd_mini_move_beginning_of_line(struct tab *tab)
705 ministate.off = 0;
708 static void
709 cmd_mini_kill_line(struct tab *tab)
711 minibuffer_taint_hist();
713 if (ministate.off == ministate.len)
714 return;
715 ministate.buf[ministate.off] = '\0';
716 ministate.len -= ministate.off;
719 static void
720 cmd_mini_abort(struct tab *tab)
722 ministate.abortfn();
725 static void
726 cmd_mini_complete_and_exit(struct tab *tab)
728 minibuffer_taint_hist();
729 ministate.donefn();
732 static void
733 cmd_mini_previous_history_element(struct tab *tab)
735 if (ministate.history == NULL) {
736 message("No history");
737 return;
740 if (ministate.hist_cur == NULL ||
741 (ministate.hist_cur = TAILQ_PREV(ministate.hist_cur, mhisthead, entries)) == NULL) {
742 ministate.hist_cur = TAILQ_LAST(&ministate.history->head, mhisthead);
743 ministate.hist_off = ministate.history->len - 1;
744 if (ministate.hist_cur == NULL)
745 message("No prev item");
746 } else {
747 ministate.hist_off--;
750 if (ministate.hist_cur != NULL) {
751 ministate.off = 0;
752 ministate.len = strlen(ministate.hist_cur->h);
756 static void
757 cmd_mini_next_history_element(struct tab *tab)
759 if (ministate.history == NULL) {
760 message("No history");
761 return;
764 if (ministate.hist_cur == NULL ||
765 (ministate.hist_cur = TAILQ_NEXT(ministate.hist_cur, entries)) == NULL) {
766 ministate.hist_cur = TAILQ_FIRST(&ministate.history->head);
767 ministate.hist_off = 0;
768 if (ministate.hist_cur == NULL)
769 message("No next item");
770 } else {
771 ministate.hist_off++;
774 if (ministate.hist_cur != NULL) {
775 ministate.off = 0;
776 ministate.len = strlen(ministate.hist_cur->h);
780 static void
781 minibuffer_hist_save_entry(void)
783 struct hist *hist;
785 if (ministate.history == NULL)
786 return;
788 if ((hist = calloc(1, sizeof(*hist))) == NULL)
789 abort();
791 strlcpy(hist->h, ministate.buf, sizeof(hist->h));
793 if (TAILQ_EMPTY(&ministate.history->head))
794 TAILQ_INSERT_HEAD(&ministate.history->head, hist, entries);
795 else
796 TAILQ_INSERT_TAIL(&ministate.history->head, hist, entries);
797 ministate.history->len++;
800 /*
801 * taint the minibuffer cache: if we're currently showing a history
802 * element, copy that to the current buf and reset the "history
803 * navigation" thing.
804 */
805 static void
806 minibuffer_taint_hist(void)
808 if (ministate.hist_cur == NULL)
809 return;
811 strlcpy(ministate.buf, ministate.hist_cur->h, sizeof(ministate.buf));
812 ministate.hist_cur = NULL;
815 static void
816 minibuffer_self_insert(void)
818 minibuffer_taint_hist();
820 if (ministate.len == sizeof(ministate.buf) -1)
821 return;
823 /* TODO: utf8 handling! */
825 memmove(&ministate.buf[ministate.off+1],
826 &ministate.buf[ministate.off],
827 ministate.len - ministate.off + 1);
828 ministate.buf[ministate.off] = thiskey.key;
829 ministate.off++;
830 ministate.len++;
833 static void
834 eecmd_self_insert(void)
836 if (thiskey.meta || isspace(thiskey.key) ||
837 !isgraph(thiskey.key)) {
838 global_key_unbound();
839 return;
842 minibuffer_self_insert();
845 static void
846 eecmd_select(void)
848 exit_minibuffer();
849 minibuffer_hist_save_entry();
850 message("TODO: try to execute %s", ministate.buf);
853 static void
854 ir_self_insert(void)
856 minibuffer_self_insert();
859 static void
860 ir_select(void)
862 char buf[1025] = {0};
863 struct url url;
864 struct tab *tab;
866 tab = current_tab();
868 exit_minibuffer();
869 minibuffer_hist_save_entry();
871 /* a bit ugly but... */
872 memcpy(&url, &tab->url, sizeof(tab->url));
873 url_set_query(&url, ministate.buf);
874 url_unparse(&url, buf, sizeof(buf));
875 load_url_in_tab(tab, buf);
878 static void
879 lu_self_insert(void)
881 if (thiskey.meta || isspace(thiskey.key) ||
882 !isgraph(thiskey.key)) {
883 global_key_unbound();
884 return;
887 minibuffer_self_insert();
890 static void
891 lu_select(void)
893 exit_minibuffer();
894 minibuffer_hist_save_entry();
895 load_url_in_tab(current_tab(), ministate.buf);
898 static struct vline *
899 nth_line(struct tab *tab, size_t n)
901 struct vline *vl;
902 size_t i;
904 i = 0;
905 TAILQ_FOREACH(vl, &tab->s.head, vlines) {
906 if (i == n)
907 return vl;
908 i++;
911 /* unreachable */
912 abort();
915 static struct tab *
916 current_tab(void)
918 struct tab *t;
920 TAILQ_FOREACH(t, &tabshead, tabs) {
921 if (t->flags & TAB_CURRENT)
922 return t;
925 /* unreachable */
926 abort();
929 static void
930 dispatch_stdio(int fd, short ev, void *d)
932 struct tab *tab;
933 struct keymap *k;
934 const char *keyname;
935 char tmp[2] = {0};
937 thiskey.key = wgetch(body);
938 if (thiskey.key == ERR)
939 return;
940 if (thiskey.key == 27) {
941 /* TODO: make escape-time customizable */
943 thiskey.meta = 1;
944 thiskey.key = wgetch(body);
945 if (thiskey.key == ERR || thiskey.key == 27) {
946 thiskey.meta = 0;
947 thiskey.key = 27;
949 } else
950 thiskey.meta = 0;
952 if (keybuf[0] != '\0')
953 strlcat(keybuf, " ", sizeof(keybuf));
954 if (thiskey.meta)
955 strlcat(keybuf, "M-", sizeof(keybuf));
956 if ((keyname = unkbd(thiskey.key)) != NULL)
957 strlcat(keybuf, keyname, sizeof(keybuf));
958 else {
959 tmp[0] = thiskey.key;
960 strlcat(keybuf, tmp, sizeof(keybuf));
963 TAILQ_FOREACH(k, &current_map->m, keymaps) {
964 if (k->meta == thiskey.meta &&
965 k->key == thiskey.key) {
966 if (k->fn == NULL)
967 current_map = &k->map;
968 else {
969 current_map = base_map;
970 strlcpy(keybuf, "", sizeof(keybuf));
971 k->fn(current_tab());
973 goto done;
977 if (current_map->unhandled_input != NULL)
978 current_map->unhandled_input();
979 else {
980 global_key_unbound();
983 strlcpy(keybuf, "", sizeof(keybuf));
984 current_map = base_map;
986 done:
987 redraw_tab(current_tab());
990 static void
991 handle_clear_minibuf(int fd, short ev, void *d)
993 clminibufev_set = 0;
995 free(ministate.curmesg);
996 ministate.curmesg = NULL;
998 redraw_minibuffer();
999 if (in_minibuffer) {
1000 wrefresh(body);
1001 wrefresh(minibuf);
1002 } else {
1003 wrefresh(minibuf);
1004 wrefresh(body);
1008 static void
1009 handle_resize(int sig, short ev, void *d)
1011 struct tab *tab;
1013 endwin();
1014 refresh();
1015 clear();
1017 /* move and resize the windows, in reverse order! */
1019 mvwin(minibuf, LINES-1, 0);
1020 wresize(minibuf, 1, COLS);
1022 mvwin(modeline, LINES-2, 0);
1023 wresize(modeline, 1, COLS);
1025 wresize(body, LINES-3, COLS);
1026 body_lines = LINES-3;
1027 body_cols = COLS;
1029 wresize(tabline, 1, COLS);
1031 tab = current_tab();
1033 wrap_page(tab);
1034 redraw_tab(tab);
1037 static int
1038 wrap_page(struct tab *tab)
1040 struct line *l;
1041 const char *prfx;
1043 empty_vlist(tab);
1045 TAILQ_FOREACH(l, &tab->page.head, lines) {
1046 prfx = line_prefixes[l->type].prfx1;
1047 switch (l->type) {
1048 case LINE_TEXT:
1049 case LINE_LINK:
1050 case LINE_TITLE_1:
1051 case LINE_TITLE_2:
1052 case LINE_TITLE_3:
1053 case LINE_ITEM:
1054 case LINE_QUOTE:
1055 case LINE_PRE_START:
1056 case LINE_PRE_END:
1057 wrap_text(tab, prfx, l, body_cols);
1058 /* push_line(tab, l, NULL, 0, 0); */
1059 break;
1060 case LINE_PRE_CONTENT:
1061 hardwrap_text(tab, l, body_cols);
1062 break;
1065 return 1;
1068 static inline void
1069 print_vline(struct vline *vl)
1071 const char *text = vl->line;
1072 const char *prfx;
1073 int face = line_faces[vl->parent->type].prop;
1075 if (!vl->flags)
1076 prfx = line_prefixes[vl->parent->type].prfx1;
1077 else
1078 prfx = line_prefixes[vl->parent->type].prfx2;
1080 if (text == NULL)
1081 text = "";
1083 if (face != 0)
1084 wattron(body, face);
1085 wprintw(body, "%s%s", prfx, text);
1086 if (face != 0)
1087 wattroff(body, face);
1090 static void
1091 redraw_tabline(void)
1093 struct tab *tab;
1094 int current;
1095 const char *title;
1097 werase(tabline);
1098 wbkgd(tabline, A_REVERSE);
1100 wprintw(tabline, " ");
1101 TAILQ_FOREACH(tab, &tabshead, tabs) {
1102 current = tab->flags & TAB_CURRENT;
1104 if (*(title = tab->page.title) == '\0')
1105 title = tab->hist_cur->h;
1107 if (current)
1108 wattron(tabline, A_UNDERLINE);
1110 wprintw(tabline, "%s%d: %s",
1111 current ? "*" : " ", tab->id, title);
1113 if (current)
1114 wattroff(tabline, A_UNDERLINE);
1118 static void
1119 redraw_modeline(struct tab *tab)
1121 double pct;
1122 int x, y, max_x, max_y;
1123 const char *mode = tab->page.name;
1124 const char *spin = "-\\|/";
1126 werase(modeline);
1127 wattron(modeline, A_REVERSE);
1128 wmove(modeline, 0, 0);
1130 wprintw(modeline, "-%c %s ",
1131 spin[tab->s.loading_anim_step], mode);
1133 pct = (tab->s.line_off + tab->s.curs_y) * 100.0 / tab->s.line_max;
1135 if (tab->s.line_max <= body_lines)
1136 wprintw(modeline, "All ");
1137 else if (tab->s.line_off == 0)
1138 wprintw(modeline, "Top ");
1139 else if (tab->s.line_off + body_lines >= tab->s.line_max)
1140 wprintw(modeline, "Bottom ");
1141 else
1142 wprintw(modeline, "%.0f%% ", pct);
1144 wprintw(modeline, "%d/%d %s ",
1145 tab->s.line_off + tab->s.curs_y,
1146 tab->s.line_max,
1147 tab->hist_cur->h);
1149 getyx(modeline, y, x);
1150 getmaxyx(modeline, max_y, max_x);
1152 (void)y;
1153 (void)max_y;
1155 for (; x < max_x; ++x)
1156 waddstr(modeline, "-");
1159 static void
1160 redraw_minibuffer(void)
1162 size_t skip = 0, off_x = 0, off_y = 0;
1164 werase(minibuf);
1165 if (in_minibuffer) {
1166 mvwprintw(minibuf, 0, 0, "%s", ministate.prompt);
1167 if (ministate.hist_cur != NULL)
1168 wprintw(minibuf, "(%zu/%zu) ",
1169 ministate.hist_off + 1,
1170 ministate.history->len);
1172 getyx(minibuf, off_y, off_x);
1174 while (ministate.off - skip > COLS / 2) {
1175 skip += MIN(ministate.off/4, 1);
1178 if (ministate.hist_cur != NULL)
1179 wprintw(minibuf, "%s", ministate.hist_cur->h + skip);
1180 else
1181 wprintw(minibuf, "%s", ministate.buf + skip);
1184 if (ministate.curmesg != NULL) {
1185 if (in_minibuffer)
1186 wprintw(minibuf, " [%s]", ministate.curmesg);
1187 else
1188 wprintw(minibuf, "%s", ministate.curmesg);
1191 if (!in_minibuffer && ministate.curmesg == NULL)
1192 wprintw(minibuf, "%s", keybuf);
1194 if (in_minibuffer)
1195 wmove(minibuf, 0, off_x + ministate.off - skip);
1198 static void
1199 redraw_tab(struct tab *tab)
1201 redraw_tabline();
1202 redraw_body(tab);
1203 redraw_modeline(tab);
1204 redraw_minibuffer();
1206 restore_cursor(tab);
1207 wrefresh(tabline);
1208 wrefresh(modeline);
1210 if (in_minibuffer) {
1211 wrefresh(body);
1212 wrefresh(minibuf);
1213 } else {
1214 wrefresh(minibuf);
1215 wrefresh(body);
1219 static void
1220 redraw_body(struct tab *tab)
1222 struct vline *vl;
1223 int line;
1225 werase(body);
1227 tab->s.line_off = MIN(tab->s.line_max-1, tab->s.line_off);
1228 if (TAILQ_EMPTY(&tab->s.head))
1229 return;
1231 line = 0;
1232 vl = nth_line(tab, tab->s.line_off);
1233 for (; vl != NULL; vl = TAILQ_NEXT(vl, vlines)) {
1234 wmove(body, line, 0);
1235 print_vline(vl);
1236 line++;
1237 if (line == body_lines)
1238 break;
1242 static void
1243 message(const char *fmt, ...)
1245 va_list ap;
1247 if (clminibufev_set)
1248 evtimer_del(&clminibufev);
1249 evtimer_set(&clminibufev, handle_clear_minibuf, NULL);
1250 evtimer_add(&clminibufev, &clminibufev_timer);
1251 clminibufev_set = 1;
1253 free(ministate.curmesg);
1255 va_start(ap, fmt);
1256 /* TODO: what to do if the allocation fails here? */
1257 if (vasprintf(&ministate.curmesg, fmt, ap) == -1)
1258 ministate.curmesg = NULL;
1259 va_end(ap);
1261 redraw_minibuffer();
1263 if (in_minibuffer) {
1264 wrefresh(body);
1265 wrefresh(minibuf);
1266 } else {
1267 wrefresh(minibuf);
1268 wrefresh(body);
1272 static void
1273 start_loading_anim(struct tab *tab)
1275 if (tab->s.loading_anim)
1276 return;
1277 tab->s.loading_anim = 1;
1278 evtimer_set(&tab->s.loadingev, update_loading_anim, tab);
1279 evtimer_add(&tab->s.loadingev, &loadingev_timer);
1282 static void
1283 update_loading_anim(int fd, short ev, void *d)
1285 struct tab *tab = d;
1287 tab->s.loading_anim_step = (tab->s.loading_anim_step+1)%4;
1289 redraw_modeline(tab);
1290 wrefresh(modeline);
1292 wrefresh(body);
1293 if (in_minibuffer)
1294 wrefresh(minibuf);
1296 evtimer_add(&tab->s.loadingev, &loadingev_timer);
1299 static void
1300 stop_loading_anim(struct tab *tab)
1302 if (!tab->s.loading_anim)
1303 return;
1304 evtimer_del(&tab->s.loadingev);
1305 tab->s.loading_anim = 0;
1306 tab->s.loading_anim_step = 0;
1308 redraw_modeline(tab);
1310 wrefresh(modeline);
1311 wrefresh(body);
1312 if (in_minibuffer)
1313 wrefresh(minibuf);
1316 static void
1317 load_url_in_tab(struct tab *tab, const char *url)
1319 empty_vlist(tab);
1320 message("Loading %s...", url);
1321 start_loading_anim(tab);
1322 load_url(tab, url);
1324 tab->s.curs_x = 0;
1325 tab->s.curs_y = 0;
1326 redraw_tab(tab);
1329 static void
1330 enter_minibuffer(void (*self_insert_fn)(void), void (*donefn)(void),
1331 void (*abortfn)(void), struct histhead *hist)
1333 in_minibuffer = 1;
1334 base_map = &minibuffer_map;
1335 current_map = &minibuffer_map;
1337 base_map->unhandled_input = self_insert_fn;
1339 ministate.donefn = donefn;
1340 ministate.abortfn = abortfn;
1341 memset(ministate.buf, 0, sizeof(ministate.buf));
1342 ministate.off = 0;
1343 ministate.len = 0;
1344 strlcpy(ministate.buf, "", sizeof(ministate.prompt));
1346 ministate.history = hist;
1347 ministate.hist_cur = NULL;
1348 ministate.hist_off = 0;
1351 static void
1352 exit_minibuffer(void)
1354 werase(minibuf);
1356 in_minibuffer = 0;
1357 base_map = &global_map;
1358 current_map = &global_map;
1361 static void
1362 switch_to_tab(struct tab *tab)
1364 struct tab *t;
1366 TAILQ_FOREACH(t, &tabshead, tabs) {
1367 t->flags &= ~TAB_CURRENT;
1370 tab->flags |= TAB_CURRENT;
1373 static struct tab *
1374 new_tab(void)
1376 struct tab *tab, *t;
1377 const char *url = "about:new";
1379 if ((tab = calloc(1, sizeof(*tab))) == NULL)
1380 goto err;
1382 TAILQ_INIT(&tab->hist.head);
1384 TAILQ_INIT(&tab->s.head);
1386 tab->id = tab_counter++;
1387 switch_to_tab(tab);
1389 if (TAILQ_EMPTY(&tabshead))
1390 TAILQ_INSERT_HEAD(&tabshead, tab, tabs);
1391 else
1392 TAILQ_INSERT_TAIL(&tabshead, tab, tabs);
1394 load_url_in_tab(tab, url);
1395 return tab;
1397 err:
1398 event_loopbreak();
1399 return NULL;
1402 int
1403 ui_init(void)
1405 setlocale(LC_ALL, "");
1407 TAILQ_INIT(&global_map.m);
1408 global_map.unhandled_input = global_key_unbound;
1410 TAILQ_INIT(&minibuffer_map.m);
1412 TAILQ_INIT(&eecmd_history.head);
1413 TAILQ_INIT(&ir_history.head);
1414 TAILQ_INIT(&lu_history.head);
1416 base_map = &global_map;
1417 current_map = &global_map;
1418 load_default_keys();
1420 initscr();
1421 raw();
1422 noecho();
1424 nonl();
1425 intrflush(stdscr, FALSE);
1427 if ((tabline = newwin(1, COLS, 0, 0)) == NULL)
1428 return 0;
1429 if ((body = newwin(LINES - 3, COLS, 1, 0)) == NULL)
1430 return 0;
1431 if ((modeline = newwin(1, COLS, LINES-2, 0)) == NULL)
1432 return 0;
1433 if ((minibuf = newwin(1, COLS, LINES-1, 0)) == NULL)
1434 return 0;
1436 body_lines = LINES-3;
1437 body_cols = COLS;
1439 keypad(body, TRUE);
1440 scrollok(body, TRUE);
1442 /* non-blocking input */
1443 wtimeout(body, 0);
1445 mvwprintw(body, 0, 0, "");
1447 event_set(&stdioev, 0, EV_READ | EV_PERSIST, dispatch_stdio, NULL);
1448 event_add(&stdioev, NULL);
1450 signal_set(&winchev, SIGWINCH, handle_resize, NULL);
1451 signal_add(&winchev, NULL);
1453 new_tab();
1455 return 1;
1458 void
1459 ui_on_tab_loaded(struct tab *tab)
1461 stop_loading_anim(tab);
1462 message("Loaded %s", tab->hist_cur->h);
1465 void
1466 ui_on_tab_refresh(struct tab *tab)
1468 if (!(tab->flags & TAB_CURRENT))
1469 return;
1471 wrap_page(tab);
1472 redraw_tab(tab);
1475 void
1476 ui_require_input(struct tab *tab, int hide)
1478 /* TODO: hard-switching to another tab is ugly */
1479 switch_to_tab(tab);
1481 enter_minibuffer(ir_self_insert, ir_select, exit_minibuffer,
1482 &ir_history);
1483 strlcpy(ministate.prompt, "Input required: ",
1484 sizeof(ministate.prompt));
1485 redraw_tab(tab);
1488 void
1489 ui_end(void)
1491 endwin();