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 <curses.h>
40 #include <event.h>
41 #include <locale.h>
42 #include <signal.h>
43 #include <stdarg.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <unistd.h>
48 #define TAB_CURRENT 0x1
50 #define NEW_TAB_URL "about:new"
52 static struct event stdioev, winchev;
54 static void load_default_keys(void);
55 static void empty_vlist(struct window*);
56 static void restore_cursor(struct window*);
58 #define CMD(fnname) static void fnname(struct window *)
60 CMD(cmd_previous_line);
61 CMD(cmd_next_line);
62 CMD(cmd_backward_char);
63 CMD(cmd_forward_char);
64 CMD(cmd_backward_paragraph);
65 CMD(cmd_forward_paragraph);
66 CMD(cmd_move_beginning_of_line);
67 CMD(cmd_move_end_of_line);
68 CMD(cmd_redraw);
69 CMD(cmd_scroll_line_down);
70 CMD(cmd_scroll_line_up);
71 CMD(cmd_scroll_up);
72 CMD(cmd_scroll_down);
73 CMD(cmd_beginning_of_buffer);
74 CMD(cmd_end_of_buffer);
75 CMD(cmd_kill_telescope);
76 CMD(cmd_push_button);
77 CMD(cmd_push_button_new_tab);
78 CMD(cmd_previous_button);
79 CMD(cmd_next_button);
80 CMD(cmd_previous_page);
81 CMD(cmd_next_page);
82 CMD(cmd_clear_minibuf);
83 CMD(cmd_execute_extended_command);
84 CMD(cmd_tab_close);
85 CMD(cmd_tab_close_other);
86 CMD(cmd_tab_new);
87 CMD(cmd_tab_next);
88 CMD(cmd_tab_previous);
89 CMD(cmd_tab_move);
90 CMD(cmd_tab_move_to);
91 CMD(cmd_load_url);
92 CMD(cmd_load_current_url);
93 CMD(cmd_bookmark_page);
94 CMD(cmd_goto_bookmarks);
96 CMD(cmd_mini_delete_char);
97 CMD(cmd_mini_delete_backward_char);
98 CMD(cmd_mini_kill_line);
99 CMD(cmd_mini_abort);
100 CMD(cmd_mini_complete_and_exit);
101 CMD(cmd_mini_previous_history_element);
102 CMD(cmd_mini_next_history_element);
104 #include "cmd.gen.h"
106 static void global_key_unbound(void);
107 static void minibuffer_hist_save_entry(void);
108 static void minibuffer_taint_hist(void);
109 static void minibuffer_self_insert(void);
110 static void eecmd_self_insert(void);
111 static void eecmd_select(void);
112 static void ir_self_insert(void);
113 static void ir_select(void);
114 static void lu_self_insert(void);
115 static void lu_select(void);
116 static void bp_select(void);
117 static void yornp_self_insert(void);
118 static void yornp_abort(void);
120 static struct vline *nth_line(struct window*, size_t);
121 static struct tab *current_tab(void);
122 static struct window *current_window(void);
123 static int readkey(void);
124 static void dispatch_stdio(int, short, void*);
125 static void handle_clear_minibuf(int, short, void*);
126 static void handle_resize(int, short, void*);
127 static int wrap_page(struct window*);
128 static void print_vline(struct vline*);
129 static void redraw_tabline(void);
130 static void redraw_body(struct tab*);
131 static void redraw_modeline(struct tab*);
132 static void redraw_minibuffer(void);
133 static void redraw_tab(struct tab*);
134 static void vmessage(const char*, va_list);
135 static void message(const char*, ...) __attribute__((format(printf, 1, 2)));
136 static void start_loading_anim(struct tab*);
137 static void update_loading_anim(int, short, void*);
138 static void stop_loading_anim(struct tab*);
139 static void load_url_in_tab(struct tab*, const char*);
140 static void enter_minibuffer(void(*)(void), void(*)(void), void(*)(void), struct histhead*);
141 static void exit_minibuffer(void);
142 static void switch_to_tab(struct tab*);
143 static struct tab *new_tab(const char*);
144 static void usage(void);
146 static struct { short meta; int key; uint32_t cp; } thiskey;
148 static WINDOW *tabline, *body, *modeline, *minibuf;
149 static int body_lines, body_cols;
151 static struct event clminibufev;
152 static struct timeval clminibufev_timer = { 5, 0 };
153 static struct timeval loadingev_timer = { 0, 250000 };
155 static uint32_t tab_counter;
157 static char keybuf[64];
159 static void (*yornp_cb)(int, unsigned int);
161 struct kmap global_map,
162 minibuffer_map,
163 *current_map,
164 *base_map;
166 static struct histhead eecmd_history,
167 ir_history,
168 lu_history;
170 static int in_minibuffer;
172 static struct {
173 char *curmesg;
175 char prompt[64];
176 void (*donefn)(void);
177 void (*abortfn)(void);
179 char buf[1025];
180 struct line line;
181 struct vline vline;
182 struct window window;
184 struct histhead *history;
185 struct hist *hist_cur;
186 size_t hist_off;
187 } ministate;
189 struct lineprefix {
190 const char *prfx1;
191 const char *prfx2;
192 } line_prefixes[] = {
193 [LINE_TEXT] = { "", "" },
194 [LINE_LINK] = { "=> ", " " },
195 [LINE_TITLE_1] = { "# ", " " },
196 [LINE_TITLE_2] = { "## ", " " },
197 [LINE_TITLE_3] = { "### ", " " },
198 [LINE_ITEM] = { "* ", " " },
199 [LINE_QUOTE] = { "> ", " " },
200 [LINE_PRE_START] = { "```", " " },
201 [LINE_PRE_CONTENT] = { "", "" },
202 [LINE_PRE_END] = { "```", "```" },
203 };
205 static struct line_face {
206 int prefix_prop;
207 int text_prop;
208 } line_faces[] = {
209 [LINE_TEXT] = { 0, 0 },
210 [LINE_LINK] = { 0, A_UNDERLINE },
211 [LINE_TITLE_1] = { A_BOLD, A_BOLD },
212 [LINE_TITLE_2] = { A_BOLD, A_BOLD },
213 [LINE_TITLE_3] = { A_BOLD, A_BOLD },
214 [LINE_ITEM] = { 0, 0 },
215 [LINE_QUOTE] = { 0, A_DIM },
216 [LINE_PRE_START] = { 0, 0 },
217 [LINE_PRE_CONTENT] = { 0, 0 },
218 [LINE_PRE_END] = { 0, 0 },
219 };
221 static struct tab_face {
222 int background, tab, current_tab;
223 } tab_face = {
224 A_REVERSE, A_REVERSE, A_NORMAL
225 };
227 static void
228 empty_vlist(struct window *window)
230 struct vline *vl, *t;
232 window->current_line = NULL;
233 window->line_max = 0;
235 TAILQ_FOREACH_SAFE(vl, &window->head, vlines, t) {
236 TAILQ_REMOVE(&window->head, vl, vlines);
237 free(vl->line);
238 free(vl);
242 static inline void
243 global_set_key(const char *key, void (*fn)(struct window*))
245 if (!kmap_define_key(&global_map, key, fn))
246 _exit(1);
249 static inline void
250 minibuffer_set_key(const char *key, void (*fn)(struct window*))
252 if (!kmap_define_key(&minibuffer_map, key, fn))
253 _exit(1);
256 static void
257 load_default_keys(void)
259 /* === global map === */
261 /* emacs */
262 global_set_key("C-p", cmd_previous_line);
263 global_set_key("C-n", cmd_next_line);
264 global_set_key("C-f", cmd_forward_char);
265 global_set_key("C-b", cmd_backward_char);
266 global_set_key("M-{", cmd_backward_paragraph);
267 global_set_key("M-}", cmd_forward_paragraph);
268 global_set_key("C-a", cmd_move_beginning_of_line);
269 global_set_key("C-e", cmd_move_end_of_line);
271 global_set_key("M-v", cmd_scroll_up);
272 global_set_key("C-v", cmd_scroll_down);
273 global_set_key("M-space", cmd_scroll_up);
274 global_set_key("space", cmd_scroll_down);
276 global_set_key("M-<", cmd_beginning_of_buffer);
277 global_set_key("M->", cmd_end_of_buffer);
279 global_set_key("C-x C-c", cmd_kill_telescope);
281 global_set_key("C-g", cmd_clear_minibuf);
283 global_set_key("M-x", cmd_execute_extended_command);
284 global_set_key("C-x C-f", cmd_load_url);
285 global_set_key("C-x M-f", cmd_load_current_url);
287 global_set_key("C-x t 0", cmd_tab_close);
288 global_set_key("C-x t 1", cmd_tab_close_other);
289 global_set_key("C-x t 2", cmd_tab_new);
290 global_set_key("C-x t o", cmd_tab_next);
291 global_set_key("C-x t O", cmd_tab_previous);
292 global_set_key("C-x t m", cmd_tab_move);
293 global_set_key("C-x t M", cmd_tab_move_to);
295 global_set_key("C-M-b", cmd_previous_page);
296 global_set_key("C-M-f", cmd_next_page);
298 global_set_key("<f7> a", cmd_bookmark_page);
299 global_set_key("<f7> <f7>", cmd_goto_bookmarks);
301 /* vi/vi-like */
302 global_set_key("k", cmd_previous_line);
303 global_set_key("j", cmd_next_line);
304 global_set_key("l", cmd_forward_char);
305 global_set_key("h", cmd_backward_char);
306 global_set_key("{", cmd_backward_paragraph);
307 global_set_key("}", cmd_forward_paragraph);
308 global_set_key("^", cmd_move_beginning_of_line);
309 global_set_key("$", cmd_move_end_of_line);
311 global_set_key("K", cmd_scroll_line_up);
312 global_set_key("J", cmd_scroll_line_down);
314 global_set_key("g g", cmd_beginning_of_buffer);
315 global_set_key("G", cmd_end_of_buffer);
317 global_set_key("g D", cmd_tab_close);
318 global_set_key("g N", cmd_tab_new);
319 global_set_key("g t", cmd_tab_next);
320 global_set_key("g T", cmd_tab_previous);
321 global_set_key("g M-t", cmd_tab_move);
322 global_set_key("g M-T", cmd_tab_move_to);
324 global_set_key("H", cmd_previous_page);
325 global_set_key("L", cmd_next_page);
327 /* tmp */
328 global_set_key("q", cmd_kill_telescope);
330 global_set_key("esc", cmd_clear_minibuf);
332 global_set_key(":", cmd_execute_extended_command);
334 /* cua */
335 global_set_key("<up>", cmd_previous_line);
336 global_set_key("<down>", cmd_next_line);
337 global_set_key("<right>", cmd_forward_char);
338 global_set_key("<left>", cmd_backward_char);
339 global_set_key("<prior>", cmd_scroll_up);
340 global_set_key("<next>", cmd_scroll_down);
342 global_set_key("M-<left>", cmd_previous_page);
343 global_set_key("M-<right>", cmd_next_page);
345 /* "ncurses standard" */
346 global_set_key("C-l", cmd_redraw);
348 /* global */
349 global_set_key("C-m", cmd_push_button);
350 global_set_key("M-enter", cmd_push_button_new_tab);
351 global_set_key("M-tab", cmd_previous_button);
352 global_set_key("tab", cmd_next_button);
354 /* === minibuffer map === */
355 minibuffer_set_key("ret", cmd_mini_complete_and_exit);
356 minibuffer_set_key("C-g", cmd_mini_abort);
357 minibuffer_set_key("esc", cmd_mini_abort);
358 minibuffer_set_key("C-d", cmd_mini_delete_char);
359 minibuffer_set_key("del", cmd_mini_delete_backward_char);
360 minibuffer_set_key("backspace", cmd_mini_delete_backward_char);
361 minibuffer_set_key("C-h", cmd_mini_delete_backward_char);
363 minibuffer_set_key("C-b", cmd_backward_char);
364 minibuffer_set_key("C-f", cmd_forward_char);
365 minibuffer_set_key("<left>", cmd_backward_char);
366 minibuffer_set_key("<right>", cmd_forward_char);
367 minibuffer_set_key("C-e", cmd_move_end_of_line);
368 minibuffer_set_key("C-a", cmd_move_beginning_of_line);
369 minibuffer_set_key("<end>", cmd_move_end_of_line);
370 minibuffer_set_key("<home>", cmd_move_beginning_of_line);
371 minibuffer_set_key("C-k", cmd_mini_kill_line);
373 minibuffer_set_key("M-p", cmd_mini_previous_history_element);
374 minibuffer_set_key("M-n", cmd_mini_next_history_element);
375 minibuffer_set_key("<up>", cmd_mini_previous_history_element);
376 minibuffer_set_key("<down>", cmd_mini_next_history_element);
379 static void
380 restore_cursor(struct window *window)
382 struct vline *vl;
383 const char *prfx;
385 vl = window->current_line;
386 if (vl == NULL || vl->line == NULL)
387 window->curs_x = window->cpoff = 0;
388 else
389 window->curs_x = utf8_snwidth(vl->line, window->cpoff);
391 if (vl != NULL) {
392 prfx = line_prefixes[vl->parent->type].prfx1;
393 window->curs_x += utf8_swidth(prfx);
397 static void
398 cmd_previous_line(struct window *window)
400 struct vline *vl;
402 if (window->current_line == NULL
403 || (vl = TAILQ_PREV(window->current_line, vhead, vlines)) == NULL)
404 return;
406 if (--window->curs_y < 0) {
407 window->curs_y = 0;
408 cmd_scroll_line_up(window);
409 return;
412 window->current_line = vl;
413 restore_cursor(window);
416 static void
417 cmd_next_line(struct window *window)
419 struct vline *vl;
421 if (window->current_line == NULL
422 || (vl = TAILQ_NEXT(window->current_line, vlines)) == NULL)
423 return;
425 if (++window->curs_y > body_lines-1) {
426 window->curs_y = body_lines-1;
427 cmd_scroll_line_down(window);
428 return;
431 window->current_line = vl;
432 restore_cursor(window);
435 static void
436 cmd_backward_char(struct window *window)
438 if (window->cpoff != 0)
439 window->cpoff--;
440 restore_cursor(window);
443 static void
444 cmd_forward_char(struct window *window)
446 window->cpoff++;
447 restore_cursor(window);
450 static void
451 cmd_backward_paragraph(struct window *window)
453 do {
454 if (window->current_line == NULL ||
455 window->current_line == TAILQ_FIRST(&window->head)) {
456 message("No previous paragraph");
457 return;
459 cmd_previous_line(window);
460 } while (window->current_line->line != NULL ||
461 window->current_line->parent->type != LINE_TEXT);
464 static void
465 cmd_forward_paragraph(struct window *window)
467 do {
468 if (window->current_line == NULL ||
469 window->current_line == TAILQ_LAST(&window->head, vhead)) {
470 message("No next paragraph");
471 return;
473 cmd_next_line(window);
474 } while (window->current_line->line != NULL ||
475 window->current_line->parent->type != LINE_TEXT);
478 static void
479 cmd_move_beginning_of_line(struct window *window)
481 window->cpoff = 0;
482 restore_cursor(window);
485 static void
486 cmd_move_end_of_line(struct window *window)
488 struct vline *vl;
490 vl = window->current_line;
491 if (vl->line == NULL)
492 return;
493 window->cpoff = utf8_cplen(vl->line);
494 restore_cursor(window);
497 static void
498 cmd_redraw(struct window *window)
500 handle_resize(0, 0, NULL);
503 static void
504 cmd_scroll_line_up(struct window *window)
506 struct vline *vl;
508 if (window->line_off == 0)
509 return;
511 vl = nth_line(window, --window->line_off);
512 wscrl(body, -1);
513 wmove(body, 0, 0);
514 print_vline(vl);
516 window->current_line = TAILQ_PREV(window->current_line, vhead, vlines);
517 restore_cursor(window);
520 static void
521 cmd_scroll_line_down(struct window *window)
523 struct vline *vl;
525 vl = window->current_line;
526 if ((vl = TAILQ_NEXT(vl, vlines)) == NULL)
527 return;
528 window->current_line = vl;
530 window->line_off++;
531 wscrl(body, 1);
533 if (window->line_max - window->line_off < (size_t)body_lines)
534 return;
536 vl = nth_line(window, window->line_off + body_lines-1);
537 wmove(body, body_lines-1, 0);
538 print_vline(vl);
540 restore_cursor(window);
543 static void
544 cmd_scroll_up(struct window *window)
546 size_t off;
548 off = body_lines+1;
550 for (; off > 0; --off)
551 cmd_scroll_line_up(window);
554 static void
555 cmd_scroll_down(struct window *window)
557 size_t off;
559 off = body_lines+1;
561 for (; off > 0; --off)
562 cmd_scroll_line_down(window);
565 static void
566 cmd_beginning_of_buffer(struct window *window)
568 window->current_line = TAILQ_FIRST(&window->head);
569 window->line_off = 0;
570 window->curs_y = 0;
571 window->cpoff = 0;
572 restore_cursor(window);
575 static void
576 cmd_end_of_buffer(struct window *window)
578 ssize_t off;
580 off = window->line_max - body_lines;
581 off = MAX(0, off);
583 window->line_off = off;
584 window->curs_y = MIN((size_t)body_lines, window->line_max-1);
586 window->current_line = TAILQ_LAST(&window->head, vhead);
587 window->cpoff = body_cols;
588 restore_cursor(window);
591 static void
592 cmd_kill_telescope(struct window *window)
594 event_loopbreak();
597 static void
598 cmd_push_button(struct window *window)
600 struct vline *vl;
601 size_t nth;
603 nth = window->line_off + window->curs_y;
604 if (nth >= window->line_max)
605 return;
606 vl = nth_line(window, nth);
607 if (vl->parent->type != LINE_LINK)
608 return;
610 load_url_in_tab(current_tab(), vl->parent->alt);
613 static void
614 cmd_push_button_new_tab(struct window *window)
616 struct vline *vl;
617 size_t nth;
619 nth = window->line_off + window->curs_y;
620 if (nth > window->line_max)
621 return;
622 vl = nth_line(window, nth);
623 if (vl->parent->type != LINE_LINK)
624 return;
626 new_tab(vl->parent->alt);
629 static void
630 cmd_previous_button(struct window *window)
632 do {
633 if (window->current_line == NULL ||
634 window->current_line == TAILQ_FIRST(&window->head)) {
635 message("No previous link");
636 return;
638 cmd_previous_line(window);
639 } while (window->current_line->parent->type != LINE_LINK);
642 static void
643 cmd_next_button(struct window *window)
645 do {
646 if (window->current_line == NULL ||
647 window->current_line == TAILQ_LAST(&window->head, vhead)) {
648 message("No next link");
649 return;
651 cmd_next_line(window);
652 } while (window->current_line->parent->type != LINE_LINK);
655 static void
656 cmd_previous_page(struct window *window)
658 struct tab *tab = current_tab();
660 if (!load_previous_page(tab))
661 message("No previous page");
662 else
663 start_loading_anim(tab);
666 static void
667 cmd_next_page(struct window *window)
669 struct tab *tab = current_tab();
671 if (!load_next_page(tab))
672 message("No next page");
673 else
674 start_loading_anim(tab);
677 static void
678 cmd_clear_minibuf(struct window *window)
680 handle_clear_minibuf(0, 0, NULL);
683 static void
684 cmd_execute_extended_command(struct window *window)
686 size_t len;
688 if (in_minibuffer) {
689 message("We don't have enable-recursive-minibuffers");
690 return;
693 enter_minibuffer(eecmd_self_insert, eecmd_select, exit_minibuffer,
694 &eecmd_history);
696 len = sizeof(ministate.prompt);
697 strlcpy(ministate.prompt, "", len);
699 if (thiskey.meta)
700 strlcat(ministate.prompt, "M-", len);
702 strlcat(ministate.prompt, keyname(thiskey.key), len);
703 strlcat(ministate.prompt, " ", len);
706 static void
707 cmd_tab_close(struct window *window)
709 struct tab *tab, *t;
711 tab = current_tab();
712 if (TAILQ_PREV(tab, tabshead, tabs) == NULL &&
713 TAILQ_NEXT(tab, tabs) == NULL) {
714 message("Can't close the only tab.");
715 return;
718 if (evtimer_pending(&tab->loadingev, NULL))
719 evtimer_del(&tab->loadingev);
721 stop_tab(tab);
723 if ((t = TAILQ_PREV(tab, tabshead, tabs)) == NULL)
724 t = TAILQ_NEXT(tab, tabs);
725 TAILQ_REMOVE(&tabshead, tab, tabs);
726 free(tab);
728 switch_to_tab(t);
731 static void
732 cmd_tab_close_other(struct window *window)
734 struct tab *t, *i;
736 TAILQ_FOREACH_SAFE(t, &tabshead, tabs, i) {
737 if (t->flags & TAB_CURRENT)
738 continue;
740 stop_tab(t);
741 TAILQ_REMOVE(&tabshead, t, tabs);
742 free(t);
746 static void
747 cmd_tab_new(struct window *window)
749 new_tab(NEW_TAB_URL);
752 static void
753 cmd_tab_next(struct window *window)
755 struct tab *tab, *t;
757 tab = current_tab();
758 tab->flags &= ~TAB_CURRENT;
760 if ((t = TAILQ_NEXT(tab, tabs)) == NULL)
761 t = TAILQ_FIRST(&tabshead);
762 t->flags |= TAB_CURRENT;
765 static void
766 cmd_tab_previous(struct window *window)
768 struct tab *tab, *t;
770 tab = current_tab();
771 tab->flags &= ~TAB_CURRENT;
773 if ((t = TAILQ_PREV(tab, tabshead, tabs)) == NULL)
774 t = TAILQ_LAST(&tabshead, tabshead);
775 t->flags |= TAB_CURRENT;
778 static void
779 cmd_tab_move(struct window *window)
781 struct tab *tab, *t;
783 tab = current_tab();
784 t = TAILQ_NEXT(tab, tabs);
785 TAILQ_REMOVE(&tabshead, tab, tabs);
787 if (t == NULL)
788 TAILQ_INSERT_HEAD(&tabshead, tab, tabs);
789 else
790 TAILQ_INSERT_AFTER(&tabshead, t, tab, tabs);
793 static void
794 cmd_tab_move_to(struct window *window)
796 struct tab *tab, *t;
798 tab = current_tab();
799 t = TAILQ_PREV(tab, tabshead, tabs);
800 TAILQ_REMOVE(&tabshead, tab, tabs);
802 if (t == NULL) {
803 if (TAILQ_EMPTY(&tabshead))
804 TAILQ_INSERT_HEAD(&tabshead, tab, tabs);
805 else
806 TAILQ_INSERT_TAIL(&tabshead, tab, tabs);
807 } else
808 TAILQ_INSERT_BEFORE(t, tab, tabs);
811 static void
812 cmd_load_url(struct window *window)
814 if (in_minibuffer) {
815 message("We don't have enable-recursive-minibuffers");
816 return;
819 enter_minibuffer(lu_self_insert, lu_select, exit_minibuffer,
820 &lu_history);
821 strlcpy(ministate.prompt, "Load URL: ", sizeof(ministate.prompt));
824 static void
825 cmd_load_current_url(struct window *window)
827 struct tab *tab = current_tab();
829 if (in_minibuffer) {
830 message("We don't have enable-recursive-minibuffers");
831 return;
834 enter_minibuffer(lu_self_insert, lu_select, exit_minibuffer,
835 &lu_history);
836 strlcpy(ministate.prompt, "Load URL: ", sizeof(ministate.prompt));
837 strlcpy(ministate.buf, tab->hist_cur->h, sizeof(ministate.buf));
838 ministate.window.cpoff = utf8_cplen(ministate.buf);
841 static void
842 cmd_bookmark_page(struct window *window)
844 struct tab *tab = current_tab();
846 enter_minibuffer(lu_self_insert, bp_select, exit_minibuffer, NULL);
847 strlcpy(ministate.prompt, "Bookmark URL: ", sizeof(ministate.prompt));
848 strlcpy(ministate.buf, tab->hist_cur->h, sizeof(ministate.buf));
849 ministate.window.cpoff = utf8_cplen(ministate.buf);
852 static void
853 cmd_goto_bookmarks(struct window *window)
855 load_url_in_tab(current_tab(), "about:bookmarks");
858 static void
859 cmd_mini_delete_char(struct window *window)
861 char *c, *n;
863 if (!in_minibuffer) {
864 message("text is read-only");
865 return;
868 minibuffer_taint_hist();
870 c = utf8_nth(window->current_line->line, window->cpoff);
871 if (*c == '\0')
872 return;
873 n = utf8_next_cp(c);
875 memmove(c, n, strlen(n)+1);
878 static void
879 cmd_mini_delete_backward_char(struct window *window)
881 char *c, *p, *start;
883 if (!in_minibuffer) {
884 message("text is read-only");
885 return;
888 minibuffer_taint_hist();
890 c = utf8_nth(window->current_line->line, window->cpoff);
891 start = window->current_line->line;
892 if (c == start)
893 return;
894 p = utf8_prev_cp(c-1, start);
896 memmove(p, c, strlen(c)+1);
897 window->cpoff--;
900 static void
901 cmd_mini_kill_line(struct window *window)
903 char *c;
905 if (!in_minibuffer) {
906 message("text is read-only");
907 return;
910 minibuffer_taint_hist();
911 c = utf8_nth(window->current_line->line, window->cpoff);
912 *c = '\0';
915 static void
916 cmd_mini_abort(struct window *window)
918 if (!in_minibuffer)
919 return;
921 ministate.abortfn();
924 static void
925 cmd_mini_complete_and_exit(struct window *window)
927 if (!in_minibuffer)
928 return;
930 minibuffer_taint_hist();
931 ministate.donefn();
934 static void
935 cmd_mini_previous_history_element(struct window *window)
937 if (ministate.history == NULL) {
938 message("No history");
939 return;
942 if (ministate.hist_cur == NULL ||
943 (ministate.hist_cur = TAILQ_PREV(ministate.hist_cur, mhisthead, entries)) == NULL) {
944 ministate.hist_cur = TAILQ_LAST(&ministate.history->head, mhisthead);
945 ministate.hist_off = ministate.history->len - 1;
946 if (ministate.hist_cur == NULL)
947 message("No prev item");
948 } else {
949 ministate.hist_off--;
952 if (ministate.hist_cur != NULL)
953 window->current_line->line = ministate.hist_cur->h;
956 static void
957 cmd_mini_next_history_element(struct window *window)
959 if (ministate.history == NULL) {
960 message("No history");
961 return;
964 if (ministate.hist_cur == NULL ||
965 (ministate.hist_cur = TAILQ_NEXT(ministate.hist_cur, entries)) == NULL) {
966 ministate.hist_cur = TAILQ_FIRST(&ministate.history->head);
967 ministate.hist_off = 0;
968 if (ministate.hist_cur == NULL)
969 message("No next item");
970 } else {
971 ministate.hist_off++;
974 if (ministate.hist_cur != NULL)
975 window->current_line->line = ministate.hist_cur->h;
978 static void
979 global_key_unbound(void)
981 message("%s is undefined", keybuf);
984 static void
985 minibuffer_hist_save_entry(void)
987 struct hist *hist;
989 if (ministate.history == NULL)
990 return;
992 if ((hist = calloc(1, sizeof(*hist))) == NULL)
993 abort();
995 strlcpy(hist->h, ministate.buf, sizeof(hist->h));
997 if (TAILQ_EMPTY(&ministate.history->head))
998 TAILQ_INSERT_HEAD(&ministate.history->head, hist, entries);
999 else
1000 TAILQ_INSERT_TAIL(&ministate.history->head, hist, entries);
1001 ministate.history->len++;
1005 * taint the minibuffer cache: if we're currently showing a history
1006 * element, copy that to the current buf and reset the "history
1007 * navigation" thing.
1009 static void
1010 minibuffer_taint_hist(void)
1012 if (ministate.hist_cur == NULL)
1013 return;
1015 strlcpy(ministate.buf, ministate.hist_cur->h, sizeof(ministate.buf));
1016 ministate.hist_cur = NULL;
1019 static void
1020 minibuffer_self_insert(void)
1022 char *c, tmp[5] = {0};
1023 size_t len;
1025 minibuffer_taint_hist();
1027 if (thiskey.cp == 0)
1028 return;
1030 len = utf8_encode(thiskey.cp, tmp);
1031 c = utf8_nth(ministate.window.current_line->line, ministate.window.cpoff);
1032 if (c + len > ministate.buf + sizeof(ministate.buf) - 1)
1033 return;
1035 memmove(c + len, c, strlen(c)+1);
1036 memcpy(c, tmp, len);
1037 ministate.window.cpoff++;
1040 static void
1041 eecmd_self_insert(void)
1043 if (thiskey.meta || unicode_isspace(thiskey.cp) ||
1044 !unicode_isgraph(thiskey.cp)) {
1045 global_key_unbound();
1046 return;
1049 minibuffer_self_insert();
1052 static void
1053 eecmd_select(void)
1055 struct cmds *cmd;
1057 for (cmd = cmds; cmd->cmd != NULL; ++cmd) {
1058 if (!strcmp(cmd->cmd, ministate.buf)) {
1059 exit_minibuffer();
1060 minibuffer_hist_save_entry();
1061 cmd->fn(current_window());
1062 return;
1066 message("No match");
1069 static void
1070 ir_self_insert(void)
1072 minibuffer_self_insert();
1075 static void
1076 ir_select(void)
1078 char buf[1025] = {0};
1079 struct url url;
1080 struct tab *tab;
1082 tab = current_tab();
1084 exit_minibuffer();
1085 minibuffer_hist_save_entry();
1087 /* a bit ugly but... */
1088 memcpy(&url, &tab->url, sizeof(tab->url));
1089 url_set_query(&url, ministate.buf);
1090 url_unparse(&url, buf, sizeof(buf));
1091 load_url_in_tab(tab, buf);
1094 static void
1095 lu_self_insert(void)
1097 if (thiskey.meta || unicode_isspace(thiskey.key) ||
1098 !unicode_isgraph(thiskey.key)) {
1099 global_key_unbound();
1100 return;
1103 minibuffer_self_insert();
1106 static void
1107 lu_select(void)
1109 exit_minibuffer();
1110 minibuffer_hist_save_entry();
1111 load_url_in_tab(current_tab(), ministate.buf);
1114 static void
1115 bp_select(void)
1117 exit_minibuffer();
1118 if (*ministate.buf != '\0')
1119 add_to_bookmarks(ministate.buf);
1120 else
1121 message("Abort.");
1124 static void
1125 yornp_self_insert(void)
1127 if (thiskey.key != 'y' && thiskey.key != 'n') {
1128 message("Please answer y or n");
1129 return;
1132 yornp_cb(thiskey.key == 'y', current_tab()->id);
1133 exit_minibuffer();
1136 static void
1137 yornp_abort(void)
1139 yornp_cb(0, current_tab()->id);
1140 exit_minibuffer();
1143 static struct vline *
1144 nth_line(struct window *window, size_t n)
1146 struct vline *vl;
1147 size_t i;
1149 i = 0;
1150 TAILQ_FOREACH(vl, &window->head, vlines) {
1151 if (i == n)
1152 return vl;
1153 i++;
1156 /* unreachable */
1157 abort();
1160 static struct tab *
1161 current_tab(void)
1163 struct tab *t;
1165 TAILQ_FOREACH(t, &tabshead, tabs) {
1166 if (t->flags & TAB_CURRENT)
1167 return t;
1170 /* unreachable */
1171 abort();
1174 static struct window *
1175 current_window(void)
1177 if (in_minibuffer)
1178 return &ministate.window;
1179 return &current_tab()->window;
1182 static int
1183 readkey(void)
1185 uint32_t state = 0;
1187 if ((thiskey.key = wgetch(body)) == ERR)
1188 return 0;
1190 thiskey.meta = thiskey.key == 27;
1191 if (thiskey.meta) {
1192 thiskey.key = wgetch(body);
1193 if (thiskey.key == ERR || thiskey.key == 27) {
1194 thiskey.meta = 0;
1195 thiskey.key = 27;
1199 thiskey.cp = 0;
1200 if ((unsigned int)thiskey.key < UINT8_MAX) {
1201 while (1) {
1202 if (!utf8_decode(&state, &thiskey.cp, (uint8_t)thiskey.key))
1203 break;
1204 if ((thiskey.key = wgetch(body)) == ERR) {
1205 message("Error decoding user input");
1206 return 0;
1211 return 1;
1214 static void
1215 dispatch_stdio(int fd, short ev, void *d)
1217 struct keymap *k;
1218 const char *keyname;
1219 char tmp[5] = {0};
1221 if (!readkey())
1222 return;
1224 if (keybuf[0] != '\0')
1225 strlcat(keybuf, " ", sizeof(keybuf));
1226 if (thiskey.meta)
1227 strlcat(keybuf, "M-", sizeof(keybuf));
1228 if (thiskey.cp != 0) {
1229 utf8_encode(thiskey.cp, tmp);
1230 strlcat(keybuf, tmp, sizeof(keybuf));
1231 } else {
1232 if ((keyname = unkbd(thiskey.key)) != NULL)
1233 strlcat(keybuf, keyname, sizeof(keybuf));
1234 else {
1235 tmp[0] = thiskey.key;
1236 strlcat(keybuf, tmp, sizeof(keybuf));
1240 TAILQ_FOREACH(k, &current_map->m, keymaps) {
1241 if (k->meta == thiskey.meta &&
1242 k->key == thiskey.key) {
1243 if (k->fn == NULL)
1244 current_map = &k->map;
1245 else {
1246 current_map = base_map;
1247 strlcpy(keybuf, "", sizeof(keybuf));
1248 k->fn(current_window());
1250 goto done;
1254 if (current_map->unhandled_input != NULL)
1255 current_map->unhandled_input();
1256 else {
1257 global_key_unbound();
1260 strlcpy(keybuf, "", sizeof(keybuf));
1261 current_map = base_map;
1263 done:
1264 redraw_tab(current_tab());
1267 static void
1268 handle_clear_minibuf(int fd, short ev, void *d)
1270 free(ministate.curmesg);
1271 ministate.curmesg = NULL;
1273 redraw_minibuffer();
1274 if (in_minibuffer) {
1275 wrefresh(body);
1276 wrefresh(minibuf);
1277 } else {
1278 wrefresh(minibuf);
1279 wrefresh(body);
1283 static void
1284 handle_resize(int sig, short ev, void *d)
1286 struct tab *tab;
1288 endwin();
1289 refresh();
1290 clear();
1292 /* move and resize the windows, in reverse order! */
1294 mvwin(minibuf, LINES-1, 0);
1295 wresize(minibuf, 1, COLS);
1297 mvwin(modeline, LINES-2, 0);
1298 wresize(modeline, 1, COLS);
1300 wresize(body, LINES-3, COLS);
1301 body_lines = LINES-3;
1302 body_cols = COLS;
1304 wresize(tabline, 1, COLS);
1306 tab = current_tab();
1308 wrap_page(&tab->window);
1309 redraw_tab(tab);
1312 static int
1313 wrap_page(struct window *window)
1315 struct line *l;
1316 const struct line *orig;
1317 struct vline *vl;
1318 const char *prfx;
1320 orig = window->current_line == NULL
1321 ? NULL
1322 : window->current_line->parent;
1323 window->current_line = NULL;
1325 window->curs_y = 0;
1326 window->line_off = 0;
1328 empty_vlist(window);
1330 TAILQ_FOREACH(l, &window->page.head, lines) {
1331 prfx = line_prefixes[l->type].prfx1;
1332 switch (l->type) {
1333 case LINE_TEXT:
1334 case LINE_LINK:
1335 case LINE_TITLE_1:
1336 case LINE_TITLE_2:
1337 case LINE_TITLE_3:
1338 case LINE_ITEM:
1339 case LINE_QUOTE:
1340 case LINE_PRE_START:
1341 case LINE_PRE_END:
1342 wrap_text(window, prfx, l, body_cols);
1343 break;
1344 case LINE_PRE_CONTENT:
1345 hardwrap_text(window, l, body_cols);
1346 break;
1349 if (orig == l && window->current_line == NULL) {
1350 window->line_off = window->line_max-1;
1351 window->current_line = TAILQ_LAST(&window->head, vhead);
1353 while (1) {
1354 vl = TAILQ_PREV(window->current_line, vhead, vlines);
1355 if (vl == NULL || vl->parent != orig)
1356 break;
1357 window->current_line = vl;
1358 window->line_off--;
1363 if (window->current_line == NULL)
1364 window->current_line = TAILQ_FIRST(&window->head);
1366 return 1;
1369 static void
1370 print_vline(struct vline *vl)
1372 const char *text = vl->line;
1373 const char *prfx;
1374 int prefix_face = line_faces[vl->parent->type].prefix_prop;
1375 int text_face = line_faces[vl->parent->type].text_prop;
1377 if (!vl->flags)
1378 prfx = line_prefixes[vl->parent->type].prfx1;
1379 else
1380 prfx = line_prefixes[vl->parent->type].prfx2;
1382 if (text == NULL)
1383 text = "";
1385 wattron(body, prefix_face);
1386 wprintw(body, "%s", prfx);
1387 wattroff(body, prefix_face);
1389 wattron(body, text_face);
1390 wprintw(body, "%s", text);
1391 wattroff(body, text_face);
1394 static void
1395 redraw_tabline(void)
1397 struct tab *tab;
1398 size_t toskip;
1399 int current, x, y, truncated;
1400 const char *title;
1401 char buf[25];
1403 toskip = 0;
1404 x = 1;
1405 TAILQ_FOREACH(tab, &tabshead, tabs) {
1406 x += sizeof(buf) + 1;
1407 toskip++;
1408 if (tab->flags & TAB_CURRENT)
1409 break;
1411 if (x < COLS-2)
1412 toskip = 0;
1413 else
1414 toskip--;
1416 werase(tabline);
1417 wattron(tabline, tab_face.background);
1418 wprintw(tabline, toskip == 0 ? " " : "<");
1419 wattroff(tabline, tab_face.background);
1421 truncated = 0;
1422 TAILQ_FOREACH(tab, &tabshead, tabs) {
1423 if (truncated)
1424 break;
1425 if (toskip != 0) {
1426 toskip--;
1427 continue;
1430 getyx(tabline, y, x);
1431 if (x + sizeof(buf)+2 >= (size_t)COLS)
1432 truncated = 1;
1434 current = tab->flags & TAB_CURRENT;
1436 if (*(title = tab->window.page.title) == '\0')
1437 title = tab->hist_cur->h;
1439 strlcpy(buf, " ", sizeof(buf));
1440 if (strlcat(buf, title, sizeof(buf)) >= sizeof(buf)) {
1441 /* truncation happens */
1442 strlcpy(&buf[sizeof(buf)-4], "...", 4);
1443 } else {
1444 /* pad with spaces */
1445 while (strlcat(buf, " ", sizeof(buf)) < sizeof(buf))
1446 /* nop */ ;
1449 if (current)
1450 wattron(tabline, tab_face.current_tab);
1451 else
1452 wattron(tabline, tab_face.tab);
1454 wprintw(tabline, "%s", buf);
1455 if (TAILQ_NEXT(tab, tabs) != NULL)
1456 wprintw(tabline, " ");
1458 if (current)
1459 wattroff(tabline, tab_face.current_tab);
1460 else
1461 wattroff(tabline, tab_face.tab);
1464 wattron(tabline, tab_face.background);
1465 for (; x < COLS; ++x)
1466 waddch(tabline, ' ');
1467 if (truncated)
1468 mvwprintw(tabline, 0, COLS-1, ">");
1471 static inline char
1472 trust_status_char(enum trust_state ts)
1474 switch (ts) {
1475 case TS_UNKNOWN: return 'u';
1476 case TS_UNTRUSTED: return '!';
1477 case TS_TRUSTED: return 'v';
1478 case TS_VERIFIED: return 'V';
1482 static void
1483 redraw_modeline(struct tab *tab)
1485 double pct;
1486 int x, y, max_x, max_y;
1487 const char *mode = tab->window.page.name;
1488 const char *spin = "-\\|/";
1490 werase(modeline);
1491 wattron(modeline, A_REVERSE);
1492 wmove(modeline, 0, 0);
1494 wprintw(modeline, "-%c%c %s ",
1495 spin[tab->loading_anim_step],
1496 trust_status_char(tab->trust),
1497 mode == NULL ? "(none)" : mode);
1499 pct = (tab->window.line_off + tab->window.curs_y) * 100.0 / tab->window.line_max;
1501 if (tab->window.line_max <= (size_t)body_lines)
1502 wprintw(modeline, "All ");
1503 else if (tab->window.line_off == 0)
1504 wprintw(modeline, "Top ");
1505 else if (tab->window.line_off + body_lines >= tab->window.line_max)
1506 wprintw(modeline, "Bottom ");
1507 else
1508 wprintw(modeline, "%.0f%% ", pct);
1510 wprintw(modeline, "%d/%d %s ",
1511 tab->window.line_off + tab->window.curs_y,
1512 tab->window.line_max,
1513 tab->hist_cur->h);
1515 getyx(modeline, y, x);
1516 getmaxyx(modeline, max_y, max_x);
1518 (void)y;
1519 (void)max_y;
1521 for (; x < max_x; ++x)
1522 waddstr(modeline, "-");
1525 static void
1526 redraw_minibuffer(void)
1528 struct tab *tab;
1529 size_t off_y, off_x = 0;
1530 char *start, *c;
1532 werase(minibuf);
1534 if (in_minibuffer) {
1535 mvwprintw(minibuf, 0, 0, "%s", ministate.prompt);
1536 if (ministate.hist_cur != NULL)
1537 wprintw(minibuf, "(%zu/%zu) ",
1538 ministate.hist_off + 1,
1539 ministate.history->len);
1541 getyx(minibuf, off_y, off_x);
1543 start = ministate.hist_cur != NULL
1544 ? ministate.hist_cur->h
1545 : ministate.buf;
1546 c = utf8_nth(ministate.window.current_line->line,
1547 ministate.window.cpoff);
1548 while (utf8_swidth_between(start, c) > (size_t)COLS/2) {
1549 start = utf8_next_cp(start);
1552 waddstr(minibuf, start);
1555 if (ministate.curmesg != NULL)
1556 wprintw(minibuf, in_minibuffer ? " [%s]" : "%s",
1557 ministate.curmesg);
1559 if (!in_minibuffer && ministate.curmesg == NULL)
1560 waddstr(minibuf, keybuf);
1562 /* If nothing else, show the URL at point */
1563 if (!in_minibuffer && ministate.curmesg == NULL && *keybuf == '\0') {
1564 tab = current_tab();
1565 if (tab->window.current_line != NULL &&
1566 tab->window.current_line->parent->type == LINE_LINK)
1567 waddstr(minibuf, tab->window.current_line->parent->alt);
1570 if (in_minibuffer)
1571 wmove(minibuf, 0, off_x + utf8_swidth_between(start, c));
1574 static void
1575 redraw_tab(struct tab *tab)
1577 redraw_tabline();
1578 redraw_body(tab);
1579 redraw_modeline(tab);
1580 redraw_minibuffer();
1582 wrefresh(tabline);
1583 wrefresh(modeline);
1585 if (in_minibuffer) {
1586 wrefresh(body);
1587 wrefresh(minibuf);
1588 } else {
1589 wrefresh(minibuf);
1590 wrefresh(body);
1594 static void
1595 redraw_body(struct tab *tab)
1597 struct vline *vl;
1598 int line;
1600 werase(body);
1602 tab->window.line_off = MIN(tab->window.line_max-1, tab->window.line_off);
1603 if (TAILQ_EMPTY(&tab->window.head))
1604 return;
1606 line = 0;
1607 vl = nth_line(&tab->window, tab->window.line_off);
1608 for (; vl != NULL; vl = TAILQ_NEXT(vl, vlines)) {
1609 wmove(body, line, 0);
1610 print_vline(vl);
1611 line++;
1612 if (line == body_lines)
1613 break;
1616 wmove(body, tab->window.curs_y, tab->window.curs_x);
1619 static void
1620 vmessage(const char *fmt, va_list ap)
1622 if (evtimer_pending(&clminibufev, NULL))
1623 evtimer_del(&clminibufev);
1624 evtimer_set(&clminibufev, handle_clear_minibuf, NULL);
1625 evtimer_add(&clminibufev, &clminibufev_timer);
1627 free(ministate.curmesg);
1629 /* TODO: what to do if the allocation fails here? */
1630 if (vasprintf(&ministate.curmesg, fmt, ap) == -1)
1631 ministate.curmesg = NULL;
1633 redraw_minibuffer();
1634 if (in_minibuffer) {
1635 wrefresh(body);
1636 wrefresh(minibuf);
1637 } else {
1638 wrefresh(minibuf);
1639 wrefresh(body);
1643 static void
1644 message(const char *fmt, ...)
1646 va_list ap;
1648 va_start(ap, fmt);
1649 vmessage(fmt, ap);
1650 va_end(ap);
1653 static void
1654 start_loading_anim(struct tab *tab)
1656 if (tab->loading_anim)
1657 return;
1658 tab->loading_anim = 1;
1659 evtimer_set(&tab->loadingev, update_loading_anim, tab);
1660 evtimer_add(&tab->loadingev, &loadingev_timer);
1663 static void
1664 update_loading_anim(int fd, short ev, void *d)
1666 struct tab *tab = d;
1668 tab->loading_anim_step = (tab->loading_anim_step+1)%4;
1670 if (tab->flags & TAB_CURRENT) {
1671 redraw_modeline(tab);
1672 wrefresh(modeline);
1673 wrefresh(body);
1674 if (in_minibuffer)
1675 wrefresh(minibuf);
1678 evtimer_add(&tab->loadingev, &loadingev_timer);
1681 static void
1682 stop_loading_anim(struct tab *tab)
1684 if (!tab->loading_anim)
1685 return;
1686 evtimer_del(&tab->loadingev);
1687 tab->loading_anim = 0;
1688 tab->loading_anim_step = 0;
1690 if (!(tab->flags & TAB_CURRENT))
1691 return;
1693 redraw_modeline(tab);
1695 wrefresh(modeline);
1696 wrefresh(body);
1697 if (in_minibuffer)
1698 wrefresh(minibuf);
1701 static void
1702 load_url_in_tab(struct tab *tab, const char *url)
1704 empty_vlist(&tab->window);
1705 message("Loading %s...", url);
1706 start_loading_anim(tab);
1707 load_url(tab, url);
1709 tab->window.curs_x = 0;
1710 tab->window.curs_y = 0;
1711 redraw_tab(tab);
1714 static void
1715 enter_minibuffer(void (*self_insert_fn)(void), void (*donefn)(void),
1716 void (*abortfn)(void), struct histhead *hist)
1718 in_minibuffer = 1;
1719 base_map = &minibuffer_map;
1720 current_map = &minibuffer_map;
1722 base_map->unhandled_input = self_insert_fn;
1724 ministate.donefn = donefn;
1725 ministate.abortfn = abortfn;
1726 memset(ministate.buf, 0, sizeof(ministate.buf));
1727 ministate.window.current_line = &ministate.vline;
1728 ministate.window.current_line->line = ministate.buf;
1729 ministate.window.cpoff = 0;
1730 strlcpy(ministate.buf, "", sizeof(ministate.prompt));
1732 ministate.history = hist;
1733 ministate.hist_cur = NULL;
1734 ministate.hist_off = 0;
1737 static void
1738 exit_minibuffer(void)
1740 werase(minibuf);
1742 in_minibuffer = 0;
1743 base_map = &global_map;
1744 current_map = &global_map;
1747 static void
1748 switch_to_tab(struct tab *tab)
1750 struct tab *t;
1752 TAILQ_FOREACH(t, &tabshead, tabs) {
1753 t->flags &= ~TAB_CURRENT;
1756 tab->flags |= TAB_CURRENT;
1759 static struct tab *
1760 new_tab(const char *url)
1762 struct tab *tab;
1764 if ((tab = calloc(1, sizeof(*tab))) == NULL) {
1765 event_loopbreak();
1766 return NULL;
1769 TAILQ_INIT(&tab->hist.head);
1771 TAILQ_INIT(&tab->window.head);
1773 tab->id = tab_counter++;
1774 switch_to_tab(tab);
1776 if (TAILQ_EMPTY(&tabshead))
1777 TAILQ_INSERT_HEAD(&tabshead, tab, tabs);
1778 else
1779 TAILQ_INSERT_TAIL(&tabshead, tab, tabs);
1781 load_url_in_tab(tab, url);
1782 return tab;
1785 static void
1786 usage(void)
1788 fprintf(stderr, "USAGE: %s [url]\n", getprogname());
1791 int
1792 ui_init(int argc, char * const *argv)
1794 const char *url = NEW_TAB_URL;
1795 int ch;
1797 while ((ch = getopt(argc, argv, "")) != -1) {
1798 switch (ch) {
1799 default:
1800 usage();
1801 return 0;
1804 argc -= optind;
1805 argv += optind;
1807 if (argc != 0)
1808 url = argv[0];
1810 setlocale(LC_ALL, "");
1812 TAILQ_INIT(&global_map.m);
1813 global_map.unhandled_input = global_key_unbound;
1815 TAILQ_INIT(&minibuffer_map.m);
1817 TAILQ_INIT(&eecmd_history.head);
1818 TAILQ_INIT(&ir_history.head);
1819 TAILQ_INIT(&lu_history.head);
1821 ministate.line.type = LINE_TEXT;
1822 ministate.vline.parent = &ministate.line;
1823 ministate.window.current_line = &ministate.vline;
1825 base_map = &global_map;
1826 current_map = &global_map;
1827 load_default_keys();
1829 initscr();
1830 raw();
1831 noecho();
1833 nonl();
1834 intrflush(stdscr, FALSE);
1836 if ((tabline = newwin(1, COLS, 0, 0)) == NULL)
1837 return 0;
1838 if ((body = newwin(LINES - 3, COLS, 1, 0)) == NULL)
1839 return 0;
1840 if ((modeline = newwin(1, COLS, LINES-2, 0)) == NULL)
1841 return 0;
1842 if ((minibuf = newwin(1, COLS, LINES-1, 0)) == NULL)
1843 return 0;
1845 body_lines = LINES-3;
1846 body_cols = COLS;
1848 keypad(body, TRUE);
1849 scrollok(body, TRUE);
1851 /* non-blocking input */
1852 wtimeout(body, 0);
1854 mvwprintw(body, 0, 0, "");
1856 event_set(&stdioev, 0, EV_READ | EV_PERSIST, dispatch_stdio, NULL);
1857 event_add(&stdioev, NULL);
1859 signal_set(&winchev, SIGWINCH, handle_resize, NULL);
1860 signal_add(&winchev, NULL);
1862 new_tab(url);
1864 return 1;
1867 void
1868 ui_on_tab_loaded(struct tab *tab)
1870 stop_loading_anim(tab);
1871 message("Loaded %s", tab->hist_cur->h);
1873 redraw_tabline();
1874 wrefresh(tabline);
1875 if (in_minibuffer)
1876 wrefresh(minibuf);
1877 else
1878 wrefresh(body);
1881 void
1882 ui_on_tab_refresh(struct tab *tab)
1884 wrap_page(&tab->window);
1885 if (tab->flags & TAB_CURRENT) {
1886 restore_cursor(&tab->window);
1887 redraw_tab(tab);
1891 void
1892 ui_require_input(struct tab *tab, int hide)
1894 /* TODO: hard-switching to another tab is ugly */
1895 switch_to_tab(tab);
1897 enter_minibuffer(ir_self_insert, ir_select, exit_minibuffer,
1898 &ir_history);
1899 strlcpy(ministate.prompt, "Input required: ",
1900 sizeof(ministate.prompt));
1901 redraw_tab(tab);
1904 void
1905 ui_yornp(const char *prompt, void (*fn)(int, unsigned int))
1907 size_t len;
1909 yornp_cb = fn;
1910 enter_minibuffer(yornp_self_insert, yornp_self_insert,
1911 yornp_abort, NULL);
1913 len = sizeof(ministate.prompt);
1914 strlcpy(ministate.prompt, prompt, len);
1915 strlcat(ministate.prompt, " (y or n) ", len);
1916 redraw_tab(current_tab());
1919 void
1920 ui_notify(const char *fmt, ...)
1922 va_list ap;
1924 va_start(ap, fmt);
1925 vmessage(fmt, ap);
1926 va_end(ap);
1929 void
1930 ui_end(void)
1932 endwin();