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'', but it's still missing.
32 *
33 */
35 #include "telescope.h"
37 #include <assert.h>
38 #include <curses.h>
39 #include <event.h>
40 #include <locale.h>
41 #include <signal.h>
42 #include <stdarg.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
47 #define TAB_CURRENT 0x1
49 #define NEW_TAB_URL "about:new"
51 static struct event stdioev, winchev;
53 static void load_default_keys(void);
54 static void restore_cursor(struct window*);
56 #define CMD(fnname) static void fnname(struct window *)
57 #define DEFALIAS(s, d) /* nothing */
59 CMD(cmd_previous_line);
60 CMD(cmd_next_line);
61 CMD(cmd_backward_char);
62 CMD(cmd_forward_char);
63 CMD(cmd_backward_paragraph);
64 CMD(cmd_forward_paragraph);
65 CMD(cmd_move_beginning_of_line);
66 CMD(cmd_move_end_of_line);
67 CMD(cmd_redraw);
68 CMD(cmd_scroll_line_down);
69 CMD(cmd_scroll_line_up);
70 CMD(cmd_scroll_up);
71 CMD(cmd_scroll_down);
72 CMD(cmd_beginning_of_buffer);
73 CMD(cmd_end_of_buffer);
75 CMD(cmd_kill_telescope);
76 DEFALIAS(q, cmd_kill_telescope)
77 DEFALIAS(wq, cmd_kill_telescope)
79 CMD(cmd_push_button);
80 CMD(cmd_push_button_new_tab);
81 CMD(cmd_previous_button);
82 CMD(cmd_next_button);
83 CMD(cmd_previous_page);
84 CMD(cmd_next_page);
85 CMD(cmd_clear_minibuf);
86 CMD(cmd_execute_extended_command);
87 CMD(cmd_tab_close);
88 CMD(cmd_tab_close_other);
90 CMD(cmd_tab_new);
91 DEFALIAS(tabnew, cmd_tab_new)
93 CMD(cmd_tab_next);
94 DEFALIAS(tabn, cmd_tab_next)
96 CMD(cmd_tab_previous);
97 DEFALIAS(tabp, cmd_tab_previous)
99 CMD(cmd_tab_move);
100 CMD(cmd_tab_move_to);
101 CMD(cmd_load_url);
102 CMD(cmd_load_current_url);
103 CMD(cmd_bookmark_page);
104 CMD(cmd_list_bookmarks);
105 CMD(cmd_toggle_help);
107 CMD(cmd_mini_delete_char);
108 CMD(cmd_mini_delete_backward_char);
109 CMD(cmd_mini_kill_line);
110 CMD(cmd_mini_abort);
111 CMD(cmd_mini_complete_and_exit);
112 CMD(cmd_mini_previous_history_element);
113 CMD(cmd_mini_next_history_element);
115 #include "cmd.gen.h"
117 static void global_key_unbound(void);
118 static void minibuffer_hist_save_entry(void);
119 static void minibuffer_taint_hist(void);
120 static void minibuffer_self_insert(void);
121 static void eecmd_self_insert(void);
122 static void eecmd_select(void);
123 static void ir_self_insert(void);
124 static void ir_select(void);
125 static void lu_self_insert(void);
126 static void lu_select(void);
127 static void bp_select(void);
128 static void yornp_self_insert(void);
129 static void yornp_abort(void);
131 static struct vline *nth_line(struct window*, size_t);
132 static struct tab *current_tab(void);
133 static struct window *current_window(void);
134 static int readkey(void);
135 static void dispatch_stdio(int, short, void*);
136 static void handle_clear_minibuf(int, short, void*);
137 static void handle_resize(int, short, void*);
138 static void handle_resize_timeout(int, short, void*);
139 static int wrap_page(struct window*, int);
140 static void print_vline(WINDOW*, struct vline*);
141 static void redraw_tabline(void);
142 static void redraw_window(WINDOW*, int, struct window*);
143 static void redraw_help(void);
144 static void redraw_body(struct tab*);
145 static void redraw_modeline(struct tab*);
146 static void redraw_minibuffer(void);
147 static void redraw_tab(struct tab*);
148 static void emit_help_item(char*, void*);
149 static void rec_compute_help(struct kmap*, char*, size_t);
150 static void recompute_help(void);
151 static void vmessage(const char*, va_list);
152 static void message(const char*, ...) __attribute__((format(printf, 1, 2)));
153 static void start_loading_anim(struct tab*);
154 static void update_loading_anim(int, short, void*);
155 static void stop_loading_anim(struct tab*);
156 static void load_url_in_tab(struct tab*, const char*);
157 static void enter_minibuffer(void(*)(void), void(*)(void), void(*)(void), struct histhead*);
158 static void exit_minibuffer(void);
159 static void switch_to_tab(struct tab*);
160 static struct tab *new_tab(const char*);
161 static void session_new_tab_cb(const char*);
162 static void usage(void);
164 static struct { short meta; int key; uint32_t cp; } thiskey;
166 static struct event resizeev;
167 static struct timeval resize_timer = { 0, 250000 };
169 static WINDOW *tabline, *body, *modeline, *minibuf;
170 static int body_lines, body_cols;
172 static WINDOW *help;
173 static struct window helpwin;
174 static int help_lines, help_cols;
176 static int side_window;
178 static struct event clminibufev;
179 static struct timeval clminibufev_timer = { 5, 0 };
180 static struct timeval loadingev_timer = { 0, 250000 };
182 static uint32_t tab_counter;
184 static char keybuf[64];
186 static void (*yornp_cb)(int, unsigned int);
187 static unsigned int yornp_data;
189 struct kmap global_map,
190 minibuffer_map,
191 *current_map,
192 *base_map;
194 static struct histhead eecmd_history,
195 ir_history,
196 lu_history;
198 static int in_minibuffer;
200 static struct {
201 char *curmesg;
203 char prompt[64];
204 void (*donefn)(void);
205 void (*abortfn)(void);
207 char buf[1025];
208 struct line line;
209 struct vline vline;
210 struct window window;
212 struct histhead *history;
213 struct hist *hist_cur;
214 size_t hist_off;
215 } ministate;
217 struct lineprefix {
218 const char *prfx1;
219 const char *prfx2;
220 } line_prefixes[] = {
221 [LINE_TEXT] = { "", "" },
222 [LINE_LINK] = { "=> ", " " },
223 [LINE_TITLE_1] = { "# ", " " },
224 [LINE_TITLE_2] = { "## ", " " },
225 [LINE_TITLE_3] = { "### ", " " },
226 [LINE_ITEM] = { "* ", " " },
227 [LINE_QUOTE] = { "> ", " " },
228 [LINE_PRE_START] = { "```", " " },
229 [LINE_PRE_CONTENT] = { "", "" },
230 [LINE_PRE_END] = { "```", "```" },
231 };
233 static struct line_face {
234 int prefix_prop;
235 int text_prop;
236 } line_faces[] = {
237 [LINE_TEXT] = { 0, 0 },
238 [LINE_LINK] = { 0, A_UNDERLINE },
239 [LINE_TITLE_1] = { A_BOLD, A_BOLD },
240 [LINE_TITLE_2] = { A_BOLD, A_BOLD },
241 [LINE_TITLE_3] = { A_BOLD, A_BOLD },
242 [LINE_ITEM] = { 0, 0 },
243 [LINE_QUOTE] = { 0, A_DIM },
244 [LINE_PRE_START] = { 0, 0 },
245 [LINE_PRE_CONTENT] = { 0, 0 },
246 [LINE_PRE_END] = { 0, 0 },
247 };
249 static struct tab_face {
250 int background, tab, current_tab;
251 } tab_face = {
252 A_REVERSE, A_REVERSE, A_NORMAL
253 };
255 static inline void
256 global_set_key(const char *key, void (*fn)(struct window*))
258 if (!kmap_define_key(&global_map, key, fn))
259 _exit(1);
262 static inline void
263 minibuffer_set_key(const char *key, void (*fn)(struct window*))
265 if (!kmap_define_key(&minibuffer_map, key, fn))
266 _exit(1);
269 static void
270 load_default_keys(void)
272 /* === global map === */
274 /* emacs */
275 global_set_key("C-p", cmd_previous_line);
276 global_set_key("C-n", cmd_next_line);
277 global_set_key("C-f", cmd_forward_char);
278 global_set_key("C-b", cmd_backward_char);
279 global_set_key("M-{", cmd_backward_paragraph);
280 global_set_key("M-}", cmd_forward_paragraph);
281 global_set_key("C-a", cmd_move_beginning_of_line);
282 global_set_key("C-e", cmd_move_end_of_line);
284 global_set_key("M-v", cmd_scroll_up);
285 global_set_key("C-v", cmd_scroll_down);
286 global_set_key("M-space", cmd_scroll_up);
287 global_set_key("space", cmd_scroll_down);
289 global_set_key("M-<", cmd_beginning_of_buffer);
290 global_set_key("M->", cmd_end_of_buffer);
292 global_set_key("C-x C-c", cmd_kill_telescope);
294 global_set_key("C-g", cmd_clear_minibuf);
296 global_set_key("M-x", cmd_execute_extended_command);
297 global_set_key("C-x C-f", cmd_load_url);
298 global_set_key("C-x M-f", cmd_load_current_url);
300 global_set_key("C-x t 0", cmd_tab_close);
301 global_set_key("C-x t 1", cmd_tab_close_other);
302 global_set_key("C-x t 2", cmd_tab_new);
303 global_set_key("C-x t o", cmd_tab_next);
304 global_set_key("C-x t O", cmd_tab_previous);
305 global_set_key("C-x t m", cmd_tab_move);
306 global_set_key("C-x t M", cmd_tab_move_to);
308 global_set_key("C-M-b", cmd_previous_page);
309 global_set_key("C-M-f", cmd_next_page);
311 global_set_key("<f7> a", cmd_bookmark_page);
312 global_set_key("<f7> <f7>", cmd_list_bookmarks);
314 /* vi/vi-like */
315 global_set_key("k", cmd_previous_line);
316 global_set_key("j", cmd_next_line);
317 global_set_key("l", cmd_forward_char);
318 global_set_key("h", cmd_backward_char);
319 global_set_key("{", cmd_backward_paragraph);
320 global_set_key("}", cmd_forward_paragraph);
321 global_set_key("^", cmd_move_beginning_of_line);
322 global_set_key("$", cmd_move_end_of_line);
324 global_set_key("K", cmd_scroll_line_up);
325 global_set_key("J", cmd_scroll_line_down);
327 global_set_key("g g", cmd_beginning_of_buffer);
328 global_set_key("G", cmd_end_of_buffer);
330 global_set_key("g D", cmd_tab_close);
331 global_set_key("g N", cmd_tab_new);
332 global_set_key("g t", cmd_tab_next);
333 global_set_key("g T", cmd_tab_previous);
334 global_set_key("g M-t", cmd_tab_move);
335 global_set_key("g M-T", cmd_tab_move_to);
337 global_set_key("H", cmd_previous_page);
338 global_set_key("L", cmd_next_page);
340 /* tmp */
341 global_set_key("q", cmd_kill_telescope);
343 global_set_key("esc", cmd_clear_minibuf);
345 global_set_key(":", cmd_execute_extended_command);
347 /* cua */
348 global_set_key("<up>", cmd_previous_line);
349 global_set_key("<down>", cmd_next_line);
350 global_set_key("<right>", cmd_forward_char);
351 global_set_key("<left>", cmd_backward_char);
352 global_set_key("<prior>", cmd_scroll_up);
353 global_set_key("<next>", cmd_scroll_down);
355 global_set_key("M-<left>", cmd_previous_page);
356 global_set_key("M-<right>", cmd_next_page);
358 /* "ncurses standard" */
359 global_set_key("C-l", cmd_redraw);
361 /* global */
362 global_set_key("<f1>", cmd_toggle_help);
363 global_set_key("C-m", cmd_push_button);
364 global_set_key("M-enter", cmd_push_button_new_tab);
365 global_set_key("M-tab", cmd_previous_button);
366 global_set_key("tab", cmd_next_button);
368 /* === minibuffer map === */
369 minibuffer_set_key("ret", cmd_mini_complete_and_exit);
370 minibuffer_set_key("C-g", cmd_mini_abort);
371 minibuffer_set_key("esc", cmd_mini_abort);
372 minibuffer_set_key("C-d", cmd_mini_delete_char);
373 minibuffer_set_key("del", cmd_mini_delete_backward_char);
374 minibuffer_set_key("backspace", cmd_mini_delete_backward_char);
375 minibuffer_set_key("C-h", cmd_mini_delete_backward_char);
377 minibuffer_set_key("C-b", cmd_backward_char);
378 minibuffer_set_key("C-f", cmd_forward_char);
379 minibuffer_set_key("<left>", cmd_backward_char);
380 minibuffer_set_key("<right>", cmd_forward_char);
381 minibuffer_set_key("C-e", cmd_move_end_of_line);
382 minibuffer_set_key("C-a", cmd_move_beginning_of_line);
383 minibuffer_set_key("<end>", cmd_move_end_of_line);
384 minibuffer_set_key("<home>", cmd_move_beginning_of_line);
385 minibuffer_set_key("C-k", cmd_mini_kill_line);
387 minibuffer_set_key("M-p", cmd_mini_previous_history_element);
388 minibuffer_set_key("M-n", cmd_mini_next_history_element);
389 minibuffer_set_key("<up>", cmd_mini_previous_history_element);
390 minibuffer_set_key("<down>", cmd_mini_next_history_element);
393 static void
394 restore_cursor(struct window *window)
396 struct vline *vl;
397 const char *prfx;
399 vl = window->current_line;
400 if (vl == NULL || vl->line == NULL)
401 window->curs_x = window->cpoff = 0;
402 else
403 window->curs_x = utf8_snwidth(vl->line, window->cpoff);
405 if (vl != NULL) {
406 prfx = line_prefixes[vl->parent->type].prfx1;
407 window->curs_x += utf8_swidth(prfx);
411 static void
412 cmd_previous_line(struct window *window)
414 struct vline *vl;
416 if (window->current_line == NULL
417 || (vl = TAILQ_PREV(window->current_line, vhead, vlines)) == NULL)
418 return;
420 if (--window->curs_y < 0) {
421 window->curs_y = 0;
422 cmd_scroll_line_up(window);
423 return;
426 window->current_line = vl;
427 restore_cursor(window);
430 static void
431 cmd_next_line(struct window *window)
433 struct vline *vl;
435 if (window->current_line == NULL
436 || (vl = TAILQ_NEXT(window->current_line, vlines)) == NULL)
437 return;
439 if (++window->curs_y > body_lines-1) {
440 window->curs_y = body_lines-1;
441 cmd_scroll_line_down(window);
442 return;
445 window->current_line = vl;
446 restore_cursor(window);
449 static void
450 cmd_backward_char(struct window *window)
452 if (window->cpoff != 0)
453 window->cpoff--;
454 restore_cursor(window);
457 static void
458 cmd_forward_char(struct window *window)
460 size_t len = 0;
462 if (window->current_line->line != NULL)
463 len = utf8_cplen(window->current_line->line);
464 if (++window->cpoff > len)
465 window->cpoff = len;
466 restore_cursor(window);
469 static void
470 cmd_backward_paragraph(struct window *window)
472 do {
473 if (window->current_line == NULL ||
474 window->current_line == TAILQ_FIRST(&window->head)) {
475 message("No previous paragraph");
476 return;
478 cmd_previous_line(window);
479 } while (window->current_line->line != NULL ||
480 window->current_line->parent->type != LINE_TEXT);
483 static void
484 cmd_forward_paragraph(struct window *window)
486 do {
487 if (window->current_line == NULL ||
488 window->current_line == TAILQ_LAST(&window->head, vhead)) {
489 message("No next paragraph");
490 return;
492 cmd_next_line(window);
493 } while (window->current_line->line != NULL ||
494 window->current_line->parent->type != LINE_TEXT);
497 static void
498 cmd_move_beginning_of_line(struct window *window)
500 window->cpoff = 0;
501 restore_cursor(window);
504 static void
505 cmd_move_end_of_line(struct window *window)
507 struct vline *vl;
509 vl = window->current_line;
510 if (vl->line == NULL)
511 return;
512 window->cpoff = utf8_cplen(vl->line);
513 restore_cursor(window);
516 static void
517 cmd_redraw(struct window *window)
519 handle_resize(0, 0, NULL);
522 static void
523 cmd_scroll_line_up(struct window *window)
525 struct vline *vl;
527 if (window->line_off == 0)
528 return;
530 vl = nth_line(window, --window->line_off);
531 wscrl(body, -1);
532 wmove(body, 0, 0);
533 print_vline(body, vl);
535 window->current_line = TAILQ_PREV(window->current_line, vhead, vlines);
536 restore_cursor(window);
539 static void
540 cmd_scroll_line_down(struct window *window)
542 struct vline *vl;
544 vl = window->current_line;
545 if ((vl = TAILQ_NEXT(vl, vlines)) == NULL)
546 return;
547 window->current_line = vl;
549 window->line_off++;
550 wscrl(body, 1);
552 if (window->line_max - window->line_off < (size_t)body_lines)
553 return;
555 vl = nth_line(window, window->line_off + body_lines-1);
556 wmove(body, body_lines-1, 0);
557 print_vline(body, vl);
559 restore_cursor(window);
562 static void
563 cmd_scroll_up(struct window *window)
565 size_t off;
567 off = body_lines-1;
569 for (; off > 0; --off)
570 cmd_scroll_line_up(window);
573 static void
574 cmd_scroll_down(struct window *window)
576 size_t off;
578 off = body_lines-1;
580 for (; off > 0; --off)
581 cmd_scroll_line_down(window);
584 static void
585 cmd_beginning_of_buffer(struct window *window)
587 window->current_line = TAILQ_FIRST(&window->head);
588 window->line_off = 0;
589 window->curs_y = 0;
590 window->cpoff = 0;
591 restore_cursor(window);
594 static void
595 cmd_end_of_buffer(struct window *window)
597 ssize_t off;
599 off = window->line_max - body_lines;
600 off = MAX(0, off);
602 window->line_off = off;
603 window->curs_y = MIN((size_t)body_lines, window->line_max-1);
605 window->current_line = TAILQ_LAST(&window->head, vhead);
606 window->cpoff = body_cols;
607 restore_cursor(window);
610 static void
611 cmd_kill_telescope(struct window *window)
613 save_session();
614 event_loopbreak();
617 static void
618 cmd_push_button(struct window *window)
620 struct vline *vl;
621 size_t nth;
623 nth = window->line_off + window->curs_y;
624 if (nth >= window->line_max)
625 return;
626 vl = nth_line(window, nth);
627 if (vl->parent->type != LINE_LINK)
628 return;
630 load_url_in_tab(current_tab(), vl->parent->alt);
633 static void
634 cmd_push_button_new_tab(struct window *window)
636 struct vline *vl;
637 size_t nth;
639 nth = window->line_off + window->curs_y;
640 if (nth > window->line_max)
641 return;
642 vl = nth_line(window, nth);
643 if (vl->parent->type != LINE_LINK)
644 return;
646 new_tab(vl->parent->alt);
649 static void
650 cmd_previous_button(struct window *window)
652 do {
653 if (window->current_line == NULL ||
654 window->current_line == TAILQ_FIRST(&window->head)) {
655 message("No previous link");
656 return;
658 cmd_previous_line(window);
659 } while (window->current_line->parent->type != LINE_LINK);
662 static void
663 cmd_next_button(struct window *window)
665 do {
666 if (window->current_line == NULL ||
667 window->current_line == TAILQ_LAST(&window->head, vhead)) {
668 message("No next link");
669 return;
671 cmd_next_line(window);
672 } while (window->current_line->parent->type != LINE_LINK);
675 static void
676 cmd_previous_page(struct window *window)
678 struct tab *tab = current_tab();
680 if (!load_previous_page(tab))
681 message("No previous page");
682 else
683 start_loading_anim(tab);
686 static void
687 cmd_next_page(struct window *window)
689 struct tab *tab = current_tab();
691 if (!load_next_page(tab))
692 message("No next page");
693 else
694 start_loading_anim(tab);
697 static void
698 cmd_clear_minibuf(struct window *window)
700 handle_clear_minibuf(0, 0, NULL);
703 static void
704 cmd_execute_extended_command(struct window *window)
706 size_t len;
708 if (in_minibuffer) {
709 message("We don't have enable-recursive-minibuffers");
710 return;
713 enter_minibuffer(eecmd_self_insert, eecmd_select, exit_minibuffer,
714 &eecmd_history);
716 len = sizeof(ministate.prompt);
717 strlcpy(ministate.prompt, "", len);
719 if (thiskey.meta)
720 strlcat(ministate.prompt, "M-", len);
722 strlcat(ministate.prompt, keyname(thiskey.key), len);
724 if (thiskey.meta)
725 strlcat(ministate.prompt, " ", len);
728 static void
729 cmd_tab_close(struct window *window)
731 struct tab *tab, *t;
733 tab = current_tab();
734 if (TAILQ_PREV(tab, tabshead, tabs) == NULL &&
735 TAILQ_NEXT(tab, tabs) == NULL) {
736 message("Can't close the only tab.");
737 return;
740 if (evtimer_pending(&tab->loadingev, NULL))
741 evtimer_del(&tab->loadingev);
743 stop_tab(tab);
745 if ((t = TAILQ_PREV(tab, tabshead, tabs)) == NULL)
746 t = TAILQ_NEXT(tab, tabs);
747 TAILQ_REMOVE(&tabshead, tab, tabs);
748 free(tab);
750 switch_to_tab(t);
753 static void
754 cmd_tab_close_other(struct window *window)
756 struct tab *t, *i;
758 TAILQ_FOREACH_SAFE(t, &tabshead, tabs, i) {
759 if (t->flags & TAB_CURRENT)
760 continue;
762 stop_tab(t);
763 TAILQ_REMOVE(&tabshead, t, tabs);
764 free(t);
768 static void
769 cmd_tab_new(struct window *window)
771 new_tab(NEW_TAB_URL);
774 static void
775 cmd_tab_next(struct window *window)
777 struct tab *tab, *t;
779 tab = current_tab();
780 tab->flags &= ~TAB_CURRENT;
782 if ((t = TAILQ_NEXT(tab, tabs)) == NULL)
783 t = TAILQ_FIRST(&tabshead);
784 t->flags |= TAB_CURRENT;
787 static void
788 cmd_tab_previous(struct window *window)
790 struct tab *tab, *t;
792 tab = current_tab();
793 tab->flags &= ~TAB_CURRENT;
795 if ((t = TAILQ_PREV(tab, tabshead, tabs)) == NULL)
796 t = TAILQ_LAST(&tabshead, tabshead);
797 t->flags |= TAB_CURRENT;
800 static void
801 cmd_tab_move(struct window *window)
803 struct tab *tab, *t;
805 tab = current_tab();
806 t = TAILQ_NEXT(tab, tabs);
807 TAILQ_REMOVE(&tabshead, tab, tabs);
809 if (t == NULL)
810 TAILQ_INSERT_HEAD(&tabshead, tab, tabs);
811 else
812 TAILQ_INSERT_AFTER(&tabshead, t, tab, tabs);
815 static void
816 cmd_tab_move_to(struct window *window)
818 struct tab *tab, *t;
820 tab = current_tab();
821 t = TAILQ_PREV(tab, tabshead, tabs);
822 TAILQ_REMOVE(&tabshead, tab, tabs);
824 if (t == NULL) {
825 if (TAILQ_EMPTY(&tabshead))
826 TAILQ_INSERT_HEAD(&tabshead, tab, tabs);
827 else
828 TAILQ_INSERT_TAIL(&tabshead, tab, tabs);
829 } else
830 TAILQ_INSERT_BEFORE(t, tab, tabs);
833 static void
834 cmd_load_url(struct window *window)
836 if (in_minibuffer) {
837 message("We don't have enable-recursive-minibuffers");
838 return;
841 enter_minibuffer(lu_self_insert, lu_select, exit_minibuffer,
842 &lu_history);
843 strlcpy(ministate.prompt, "Load URL: ", sizeof(ministate.prompt));
844 strlcpy(ministate.buf, "gemini://", sizeof(ministate.buf));
845 cmd_move_end_of_line(&ministate.window);
848 static void
849 cmd_load_current_url(struct window *window)
851 struct tab *tab = current_tab();
853 if (in_minibuffer) {
854 message("We don't have enable-recursive-minibuffers");
855 return;
858 enter_minibuffer(lu_self_insert, lu_select, exit_minibuffer,
859 &lu_history);
860 strlcpy(ministate.prompt, "Load URL: ", sizeof(ministate.prompt));
861 strlcpy(ministate.buf, tab->hist_cur->h, sizeof(ministate.buf));
862 ministate.window.cpoff = utf8_cplen(ministate.buf);
865 static void
866 cmd_bookmark_page(struct window *window)
868 struct tab *tab = current_tab();
870 enter_minibuffer(lu_self_insert, bp_select, exit_minibuffer, NULL);
871 strlcpy(ministate.prompt, "Bookmark URL: ", sizeof(ministate.prompt));
872 strlcpy(ministate.buf, tab->hist_cur->h, sizeof(ministate.buf));
873 ministate.window.cpoff = utf8_cplen(ministate.buf);
876 static void
877 cmd_list_bookmarks(struct window *window)
879 load_url_in_tab(current_tab(), "about:bookmarks");
882 static void
883 cmd_toggle_help(struct window *window)
885 side_window = !side_window;
886 if (side_window)
887 recompute_help();
889 /*
890 * ugly hack, but otherwise the window doesn't get updated
891 * until I call handle_resize a second time (i.e. C-l). I
892 * will be happy to know why something like this is needed.
893 */
894 handle_resize(0, 0, NULL);
895 handle_resize(0, 0, NULL);
898 static void
899 cmd_mini_delete_char(struct window *window)
901 char *c, *n;
903 if (!in_minibuffer) {
904 message("text is read-only");
905 return;
908 minibuffer_taint_hist();
910 c = utf8_nth(window->current_line->line, window->cpoff);
911 if (*c == '\0')
912 return;
913 n = utf8_next_cp(c);
915 memmove(c, n, strlen(n)+1);
918 static void
919 cmd_mini_delete_backward_char(struct window *window)
921 char *c, *p, *start;
923 if (!in_minibuffer) {
924 message("text is read-only");
925 return;
928 minibuffer_taint_hist();
930 c = utf8_nth(window->current_line->line, window->cpoff);
931 start = window->current_line->line;
932 if (c == start)
933 return;
934 p = utf8_prev_cp(c-1, start);
936 memmove(p, c, strlen(c)+1);
937 window->cpoff--;
940 static void
941 cmd_mini_kill_line(struct window *window)
943 char *c;
945 if (!in_minibuffer) {
946 message("text is read-only");
947 return;
950 minibuffer_taint_hist();
951 c = utf8_nth(window->current_line->line, window->cpoff);
952 *c = '\0';
955 static void
956 cmd_mini_abort(struct window *window)
958 if (!in_minibuffer)
959 return;
961 ministate.abortfn();
964 static void
965 cmd_mini_complete_and_exit(struct window *window)
967 if (!in_minibuffer)
968 return;
970 minibuffer_taint_hist();
971 ministate.donefn();
974 static void
975 cmd_mini_previous_history_element(struct window *window)
977 if (ministate.history == NULL) {
978 message("No history");
979 return;
982 if (ministate.hist_cur == NULL ||
983 (ministate.hist_cur = TAILQ_PREV(ministate.hist_cur, mhisthead, entries)) == NULL) {
984 ministate.hist_cur = TAILQ_LAST(&ministate.history->head, mhisthead);
985 ministate.hist_off = ministate.history->len - 1;
986 if (ministate.hist_cur == NULL)
987 message("No prev item");
988 } else {
989 ministate.hist_off--;
992 if (ministate.hist_cur != NULL)
993 window->current_line->line = ministate.hist_cur->h;
996 static void
997 cmd_mini_next_history_element(struct window *window)
999 if (ministate.history == NULL) {
1000 message("No history");
1001 return;
1004 if (ministate.hist_cur == NULL ||
1005 (ministate.hist_cur = TAILQ_NEXT(ministate.hist_cur, entries)) == NULL) {
1006 ministate.hist_cur = TAILQ_FIRST(&ministate.history->head);
1007 ministate.hist_off = 0;
1008 if (ministate.hist_cur == NULL)
1009 message("No next item");
1010 } else {
1011 ministate.hist_off++;
1014 if (ministate.hist_cur != NULL)
1015 window->current_line->line = ministate.hist_cur->h;
1018 static void
1019 global_key_unbound(void)
1021 message("%s is undefined", keybuf);
1024 static void
1025 minibuffer_hist_save_entry(void)
1027 struct hist *hist;
1029 if (ministate.history == NULL)
1030 return;
1032 if ((hist = calloc(1, sizeof(*hist))) == NULL)
1033 abort();
1035 strlcpy(hist->h, ministate.buf, sizeof(hist->h));
1037 if (TAILQ_EMPTY(&ministate.history->head))
1038 TAILQ_INSERT_HEAD(&ministate.history->head, hist, entries);
1039 else
1040 TAILQ_INSERT_TAIL(&ministate.history->head, hist, entries);
1041 ministate.history->len++;
1045 * taint the minibuffer cache: if we're currently showing a history
1046 * element, copy that to the current buf and reset the "history
1047 * navigation" thing.
1049 static void
1050 minibuffer_taint_hist(void)
1052 if (ministate.hist_cur == NULL)
1053 return;
1055 strlcpy(ministate.buf, ministate.hist_cur->h, sizeof(ministate.buf));
1056 ministate.hist_cur = NULL;
1059 static void
1060 minibuffer_self_insert(void)
1062 char *c, tmp[5] = {0};
1063 size_t len;
1065 minibuffer_taint_hist();
1067 if (thiskey.cp == 0)
1068 return;
1070 len = utf8_encode(thiskey.cp, tmp);
1071 c = utf8_nth(ministate.window.current_line->line, ministate.window.cpoff);
1072 if (c + len > ministate.buf + sizeof(ministate.buf) - 1)
1073 return;
1075 memmove(c + len, c, strlen(c)+1);
1076 memcpy(c, tmp, len);
1077 ministate.window.cpoff++;
1080 static void
1081 eecmd_self_insert(void)
1083 if (thiskey.meta || unicode_isspace(thiskey.cp) ||
1084 !unicode_isgraph(thiskey.cp)) {
1085 global_key_unbound();
1086 return;
1089 minibuffer_self_insert();
1092 static void
1093 eecmd_select(void)
1095 struct cmds *cmd;
1097 for (cmd = cmds; cmd->cmd != NULL; ++cmd) {
1098 if (!strcmp(cmd->cmd, ministate.buf)) {
1099 exit_minibuffer();
1100 minibuffer_hist_save_entry();
1101 cmd->fn(current_window());
1102 return;
1106 message("No match");
1109 static void
1110 ir_self_insert(void)
1112 minibuffer_self_insert();
1115 static void
1116 ir_select(void)
1118 char buf[1025] = {0};
1119 struct phos_uri uri;
1120 struct tab *tab;
1122 tab = current_tab();
1124 exit_minibuffer();
1125 minibuffer_hist_save_entry();
1127 /* a bit ugly but... */
1128 memcpy(&uri, &tab->uri, sizeof(tab->uri));
1129 phos_uri_set_query(&uri, ministate.buf);
1130 phos_serialize_uri(&uri, buf, sizeof(buf));
1131 load_url_in_tab(tab, buf);
1134 static void
1135 lu_self_insert(void)
1137 if (thiskey.meta || unicode_isspace(thiskey.key) ||
1138 !unicode_isgraph(thiskey.key)) {
1139 global_key_unbound();
1140 return;
1143 minibuffer_self_insert();
1146 static void
1147 lu_select(void)
1149 exit_minibuffer();
1150 minibuffer_hist_save_entry();
1151 load_url_in_tab(current_tab(), ministate.buf);
1154 static void
1155 bp_select(void)
1157 exit_minibuffer();
1158 if (*ministate.buf != '\0')
1159 add_to_bookmarks(ministate.buf);
1160 else
1161 message("Abort.");
1164 static void
1165 yornp_self_insert(void)
1167 if (thiskey.key != 'y' && thiskey.key != 'n') {
1168 message("Please answer y or n");
1169 return;
1172 exit_minibuffer();
1173 yornp_cb(thiskey.key == 'y', yornp_data);
1176 static void
1177 yornp_abort(void)
1179 exit_minibuffer();
1180 yornp_cb(0, yornp_data);
1183 static struct vline *
1184 nth_line(struct window *window, size_t n)
1186 struct vline *vl;
1187 size_t i;
1189 i = 0;
1190 TAILQ_FOREACH(vl, &window->head, vlines) {
1191 if (i == n)
1192 return vl;
1193 i++;
1196 /* unreachable */
1197 abort();
1200 static struct tab *
1201 current_tab(void)
1203 struct tab *t;
1205 TAILQ_FOREACH(t, &tabshead, tabs) {
1206 if (t->flags & TAB_CURRENT)
1207 return t;
1210 /* unreachable */
1211 abort();
1214 static struct window *
1215 current_window(void)
1217 if (in_minibuffer)
1218 return &ministate.window;
1219 return &current_tab()->window;
1222 static int
1223 readkey(void)
1225 uint32_t state = 0;
1227 if ((thiskey.key = wgetch(body)) == ERR)
1228 return 0;
1230 thiskey.meta = thiskey.key == 27;
1231 if (thiskey.meta) {
1232 thiskey.key = wgetch(body);
1233 if (thiskey.key == ERR || thiskey.key == 27) {
1234 thiskey.meta = 0;
1235 thiskey.key = 27;
1239 thiskey.cp = 0;
1240 if ((unsigned int)thiskey.key < UINT8_MAX) {
1241 while (1) {
1242 if (!utf8_decode(&state, &thiskey.cp, (uint8_t)thiskey.key))
1243 break;
1244 if ((thiskey.key = wgetch(body)) == ERR) {
1245 message("Error decoding user input");
1246 return 0;
1251 return 1;
1254 static void
1255 dispatch_stdio(int fd, short ev, void *d)
1257 struct keymap *k;
1258 const char *keyname;
1259 char tmp[5] = {0};
1261 if (!readkey())
1262 return;
1264 if (keybuf[0] != '\0')
1265 strlcat(keybuf, " ", sizeof(keybuf));
1266 if (thiskey.meta)
1267 strlcat(keybuf, "M-", sizeof(keybuf));
1268 if (thiskey.cp != 0) {
1269 utf8_encode(thiskey.cp, tmp);
1270 strlcat(keybuf, tmp, sizeof(keybuf));
1271 } else {
1272 if ((keyname = unkbd(thiskey.key)) != NULL)
1273 strlcat(keybuf, keyname, sizeof(keybuf));
1274 else {
1275 tmp[0] = thiskey.key;
1276 strlcat(keybuf, tmp, sizeof(keybuf));
1280 TAILQ_FOREACH(k, &current_map->m, keymaps) {
1281 if (k->meta == thiskey.meta &&
1282 k->key == thiskey.key) {
1283 if (k->fn == NULL)
1284 current_map = &k->map;
1285 else {
1286 current_map = base_map;
1287 strlcpy(keybuf, "", sizeof(keybuf));
1288 k->fn(current_window());
1290 goto done;
1294 if (current_map->unhandled_input != NULL)
1295 current_map->unhandled_input();
1296 else {
1297 global_key_unbound();
1300 strlcpy(keybuf, "", sizeof(keybuf));
1301 current_map = base_map;
1303 done:
1304 if (side_window)
1305 recompute_help();
1307 redraw_tab(current_tab());
1310 static void
1311 handle_clear_minibuf(int fd, short ev, void *d)
1313 free(ministate.curmesg);
1314 ministate.curmesg = NULL;
1316 redraw_minibuffer();
1317 if (in_minibuffer) {
1318 wrefresh(body);
1319 wrefresh(minibuf);
1320 } else {
1321 wrefresh(minibuf);
1322 wrefresh(body);
1326 static void
1327 handle_resize(int sig, short ev, void *d)
1329 if (event_pending(&resizeev, EV_TIMEOUT, NULL)) {
1330 event_del(&resizeev);
1332 evtimer_set(&resizeev, handle_resize_timeout, NULL);
1333 evtimer_add(&resizeev, &resize_timer);
1336 static void
1337 handle_resize_timeout(int s, short ev, void *d)
1339 struct tab *tab;
1341 endwin();
1342 refresh();
1343 clear();
1345 /* move and resize the windows, in reverse order! */
1347 mvwin(minibuf, LINES-1, 0);
1348 wresize(minibuf, 1, COLS);
1350 mvwin(modeline, LINES-2, 0);
1351 wresize(modeline, 1, COLS);
1353 body_lines = LINES-3;
1354 body_cols = COLS;
1356 if (side_window) {
1357 help_cols = 0.3 * COLS;
1358 help_lines = LINES-3;
1359 mvwin(help, 1, 0);
1360 wresize(help, help_lines, help_cols);
1362 wrap_page(&helpwin, help_cols);
1364 body_cols = COLS - help_cols - 1;
1365 mvwin(body, 1, help_cols);
1366 } else
1367 mvwin(body, 1, 0);
1369 wresize(body, body_lines, body_cols);
1371 wresize(tabline, 1, COLS);
1373 tab = current_tab();
1375 wrap_page(&tab->window, body_cols);
1376 redraw_tab(tab);
1379 static int
1380 wrap_page(struct window *window, int width)
1382 struct line *l;
1383 const struct line *orig;
1384 struct vline *vl;
1385 const char *prfx;
1387 orig = window->current_line == NULL
1388 ? NULL
1389 : window->current_line->parent;
1390 window->current_line = NULL;
1392 window->curs_y = 0;
1393 window->line_off = 0;
1395 empty_vlist(window);
1397 TAILQ_FOREACH(l, &window->page.head, lines) {
1398 prfx = line_prefixes[l->type].prfx1;
1399 switch (l->type) {
1400 case LINE_TEXT:
1401 case LINE_LINK:
1402 case LINE_TITLE_1:
1403 case LINE_TITLE_2:
1404 case LINE_TITLE_3:
1405 case LINE_ITEM:
1406 case LINE_QUOTE:
1407 case LINE_PRE_START:
1408 case LINE_PRE_END:
1409 wrap_text(window, prfx, l, width);
1410 break;
1411 case LINE_PRE_CONTENT:
1412 hardwrap_text(window, l, width);
1413 break;
1416 if (orig == l && window->current_line == NULL) {
1417 window->line_off = window->line_max-1;
1418 window->current_line = TAILQ_LAST(&window->head, vhead);
1420 while (1) {
1421 vl = TAILQ_PREV(window->current_line, vhead, vlines);
1422 if (vl == NULL || vl->parent != orig)
1423 break;
1424 window->current_line = vl;
1425 window->line_off--;
1430 if (window->current_line == NULL)
1431 window->current_line = TAILQ_FIRST(&window->head);
1433 return 1;
1436 static void
1437 print_vline(WINDOW *window, struct vline *vl)
1439 const char *text = vl->line;
1440 const char *prfx;
1441 int prefix_face = line_faces[vl->parent->type].prefix_prop;
1442 int text_face = line_faces[vl->parent->type].text_prop;
1444 if (!vl->flags)
1445 prfx = line_prefixes[vl->parent->type].prfx1;
1446 else
1447 prfx = line_prefixes[vl->parent->type].prfx2;
1449 if (text == NULL)
1450 text = "";
1452 wattron(window, prefix_face);
1453 wprintw(window, "%s", prfx);
1454 wattroff(window, prefix_face);
1456 wattron(window, text_face);
1457 wprintw(window, "%s", text);
1458 wattroff(window, text_face);
1461 static void
1462 redraw_tabline(void)
1464 struct tab *tab;
1465 size_t toskip, ots, tabwidth, space, x;
1466 int current, y, truncated;
1467 const char *title;
1468 char buf[25];
1470 tabwidth = sizeof(buf)+1;
1471 space = COLS-2;
1473 toskip = 0;
1474 TAILQ_FOREACH(tab, &tabshead, tabs) {
1475 toskip++;
1476 if (tab->flags & TAB_CURRENT)
1477 break;
1480 if (toskip * tabwidth < space)
1481 toskip = 0;
1482 else {
1483 ots = toskip;
1484 toskip--;
1485 while (toskip != 0 &&
1486 (ots - toskip+1) * tabwidth < space)
1487 toskip--;
1490 werase(tabline);
1491 wattron(tabline, tab_face.background);
1492 wprintw(tabline, toskip == 0 ? " " : "<");
1493 wattroff(tabline, tab_face.background);
1495 truncated = 0;
1496 TAILQ_FOREACH(tab, &tabshead, tabs) {
1497 if (truncated)
1498 break;
1499 if (toskip != 0) {
1500 toskip--;
1501 continue;
1504 getyx(tabline, y, x);
1505 if (x + sizeof(buf)+2 >= (size_t)COLS)
1506 truncated = 1;
1508 current = tab->flags & TAB_CURRENT;
1510 if (*(title = tab->window.page.title) == '\0')
1511 title = tab->hist_cur->h;
1513 strlcpy(buf, " ", sizeof(buf));
1514 if (strlcat(buf, title, sizeof(buf)) >= sizeof(buf)) {
1515 /* truncation happens */
1516 strlcpy(&buf[sizeof(buf)-4], "...", 4);
1517 } else {
1518 /* pad with spaces */
1519 while (strlcat(buf, " ", sizeof(buf)) < sizeof(buf))
1520 /* nop */ ;
1523 if (current)
1524 wattron(tabline, tab_face.current_tab);
1525 else
1526 wattron(tabline, tab_face.tab);
1528 wprintw(tabline, "%s", buf);
1529 if (TAILQ_NEXT(tab, tabs) != NULL)
1530 wprintw(tabline, " ");
1532 if (current)
1533 wattroff(tabline, tab_face.current_tab);
1534 else
1535 wattroff(tabline, tab_face.tab);
1538 wattron(tabline, tab_face.background);
1539 for (; x < (size_t)COLS; ++x)
1540 waddch(tabline, ' ');
1541 if (truncated)
1542 mvwprintw(tabline, 0, COLS-1, ">");
1545 static void
1546 redraw_window(WINDOW *win, int height, struct window *window)
1548 struct vline *vl;
1549 int l;
1551 werase(win);
1553 window->line_off = MIN(window->line_max-1, window->line_off);
1554 if (TAILQ_EMPTY(&window->head))
1555 return;
1557 l = 0;
1558 vl = nth_line(window, window->line_off);
1559 for (; vl != NULL; vl = TAILQ_NEXT(vl, vlines)) {
1560 wmove(win, l, 0);
1561 print_vline(win, vl);
1562 l++;
1563 if (l == height)
1564 break;
1567 wmove(win, window->curs_y, window->curs_x);
1570 static void
1571 redraw_help(void)
1573 redraw_window(help, help_lines, &helpwin);
1576 static void
1577 redraw_body(struct tab *tab)
1579 redraw_window(body, body_lines, &tab->window);
1582 static inline char
1583 trust_status_char(enum trust_state ts)
1585 switch (ts) {
1586 case TS_UNKNOWN: return 'u';
1587 case TS_UNTRUSTED: return '!';
1588 case TS_TRUSTED: return 'v';
1589 case TS_VERIFIED: return 'V';
1593 static void
1594 redraw_modeline(struct tab *tab)
1596 double pct;
1597 int x, y, max_x, max_y;
1598 const char *mode = tab->window.page.name;
1599 const char *spin = "-\\|/";
1601 werase(modeline);
1602 wattron(modeline, A_REVERSE);
1603 wmove(modeline, 0, 0);
1605 wprintw(modeline, "-%c%c %s ",
1606 spin[tab->loading_anim_step],
1607 trust_status_char(tab->trust),
1608 mode == NULL ? "(none)" : mode);
1610 pct = (tab->window.line_off + tab->window.curs_y) * 100.0 / tab->window.line_max;
1612 if (tab->window.line_max <= (size_t)body_lines)
1613 wprintw(modeline, "All ");
1614 else if (tab->window.line_off == 0)
1615 wprintw(modeline, "Top ");
1616 else if (tab->window.line_off + body_lines >= tab->window.line_max)
1617 wprintw(modeline, "Bottom ");
1618 else
1619 wprintw(modeline, "%.0f%% ", pct);
1621 wprintw(modeline, "%d/%d %s ",
1622 tab->window.line_off + tab->window.curs_y,
1623 tab->window.line_max,
1624 tab->hist_cur->h);
1626 getyx(modeline, y, x);
1627 getmaxyx(modeline, max_y, max_x);
1629 (void)y;
1630 (void)max_y;
1632 for (; x < max_x; ++x)
1633 waddstr(modeline, "-");
1636 static void
1637 redraw_minibuffer(void)
1639 struct tab *tab;
1640 size_t off_y, off_x = 0;
1641 char *start, *c;
1643 werase(minibuf);
1645 if (in_minibuffer) {
1646 mvwprintw(minibuf, 0, 0, "%s", ministate.prompt);
1647 if (ministate.hist_cur != NULL)
1648 wprintw(minibuf, "(%zu/%zu) ",
1649 ministate.hist_off + 1,
1650 ministate.history->len);
1652 getyx(minibuf, off_y, off_x);
1654 start = ministate.hist_cur != NULL
1655 ? ministate.hist_cur->h
1656 : ministate.buf;
1657 c = utf8_nth(ministate.window.current_line->line,
1658 ministate.window.cpoff);
1659 while (utf8_swidth_between(start, c) > (size_t)COLS/2) {
1660 start = utf8_next_cp(start);
1663 waddstr(minibuf, start);
1666 if (ministate.curmesg != NULL)
1667 wprintw(minibuf, in_minibuffer ? " [%s]" : "%s",
1668 ministate.curmesg);
1670 if (!in_minibuffer && ministate.curmesg == NULL)
1671 waddstr(minibuf, keybuf);
1673 /* If nothing else, show the URL at point */
1674 if (!in_minibuffer && ministate.curmesg == NULL && *keybuf == '\0') {
1675 tab = current_tab();
1676 if (tab->window.current_line != NULL &&
1677 tab->window.current_line->parent->type == LINE_LINK)
1678 waddstr(minibuf, tab->window.current_line->parent->alt);
1681 if (in_minibuffer)
1682 wmove(minibuf, 0, off_x + utf8_swidth_between(start, c));
1685 static void
1686 redraw_tab(struct tab *tab)
1688 if (side_window) {
1689 redraw_help();
1690 wnoutrefresh(help);
1693 redraw_tabline();
1694 redraw_body(tab);
1695 redraw_modeline(tab);
1696 redraw_minibuffer();
1698 wnoutrefresh(tabline);
1699 wnoutrefresh(modeline);
1701 if (in_minibuffer) {
1702 wnoutrefresh(body);
1703 wnoutrefresh(minibuf);
1704 } else {
1705 wnoutrefresh(minibuf);
1706 wnoutrefresh(body);
1709 doupdate();
1712 static void
1713 emit_help_item(char *prfx, void *fn)
1715 struct line *l;
1716 struct cmds *cmd;
1718 for (cmd = cmds; cmd->cmd != NULL; ++cmd) {
1719 if (fn == cmd->fn)
1720 break;
1722 assert(cmd != NULL);
1724 if ((l = calloc(1, sizeof(*l))) == NULL)
1725 abort();
1727 l->type = LINE_TEXT;
1728 l->alt = NULL;
1730 asprintf(&l->line, "%s %s", prfx, cmd->cmd);
1732 if (TAILQ_EMPTY(&helpwin.page.head))
1733 TAILQ_INSERT_HEAD(&helpwin.page.head, l, lines);
1734 else
1735 TAILQ_INSERT_TAIL(&helpwin.page.head, l, lines);
1738 static void
1739 rec_compute_help(struct kmap *keymap, char *prfx, size_t len)
1741 struct keymap *k;
1742 char p[32];
1743 const char *kn;
1745 TAILQ_FOREACH(k, &keymap->m, keymaps) {
1746 strlcpy(p, prfx, sizeof(p));
1747 if (*p != '\0')
1748 strlcat(p, " ", sizeof(p));
1749 if (k->meta)
1750 strlcat(p, "M-", sizeof(p));
1751 if ((kn = unkbd(k->key)) != NULL)
1752 strlcat(p, kn, sizeof(p));
1753 else
1754 strlcat(p, keyname(k->key), sizeof(p));
1756 if (k->fn == NULL)
1757 rec_compute_help(&k->map, p, sizeof(p));
1758 else
1759 emit_help_item(p, k->fn);
1763 static void
1764 recompute_help(void)
1766 char p[32] = { 0 };
1768 empty_vlist(&helpwin);
1769 empty_linelist(&helpwin);
1770 rec_compute_help(current_map, p, sizeof(p));
1771 wrap_page(&helpwin, help_cols);
1774 static void
1775 vmessage(const char *fmt, va_list ap)
1777 if (evtimer_pending(&clminibufev, NULL))
1778 evtimer_del(&clminibufev);
1779 evtimer_set(&clminibufev, handle_clear_minibuf, NULL);
1780 evtimer_add(&clminibufev, &clminibufev_timer);
1782 free(ministate.curmesg);
1784 /* TODO: what to do if the allocation fails here? */
1785 if (vasprintf(&ministate.curmesg, fmt, ap) == -1)
1786 ministate.curmesg = NULL;
1788 redraw_minibuffer();
1789 if (in_minibuffer) {
1790 wrefresh(body);
1791 wrefresh(minibuf);
1792 } else {
1793 wrefresh(minibuf);
1794 wrefresh(body);
1798 static void
1799 message(const char *fmt, ...)
1801 va_list ap;
1803 va_start(ap, fmt);
1804 vmessage(fmt, ap);
1805 va_end(ap);
1808 static void
1809 start_loading_anim(struct tab *tab)
1811 if (tab->loading_anim)
1812 return;
1813 tab->loading_anim = 1;
1814 evtimer_set(&tab->loadingev, update_loading_anim, tab);
1815 evtimer_add(&tab->loadingev, &loadingev_timer);
1818 static void
1819 update_loading_anim(int fd, short ev, void *d)
1821 struct tab *tab = d;
1823 tab->loading_anim_step = (tab->loading_anim_step+1)%4;
1825 if (tab->flags & TAB_CURRENT) {
1826 redraw_modeline(tab);
1827 wrefresh(modeline);
1828 wrefresh(body);
1829 if (in_minibuffer)
1830 wrefresh(minibuf);
1833 evtimer_add(&tab->loadingev, &loadingev_timer);
1836 static void
1837 stop_loading_anim(struct tab *tab)
1839 if (!tab->loading_anim)
1840 return;
1841 evtimer_del(&tab->loadingev);
1842 tab->loading_anim = 0;
1843 tab->loading_anim_step = 0;
1845 if (!(tab->flags & TAB_CURRENT))
1846 return;
1848 redraw_modeline(tab);
1850 wrefresh(modeline);
1851 wrefresh(body);
1852 if (in_minibuffer)
1853 wrefresh(minibuf);
1856 static void
1857 load_url_in_tab(struct tab *tab, const char *url)
1859 message("Loading %s...", url);
1860 start_loading_anim(tab);
1861 load_url(tab, url);
1863 tab->window.curs_x = 0;
1864 tab->window.curs_y = 0;
1865 redraw_tab(tab);
1868 static void
1869 enter_minibuffer(void (*self_insert_fn)(void), void (*donefn)(void),
1870 void (*abortfn)(void), struct histhead *hist)
1872 in_minibuffer = 1;
1873 base_map = &minibuffer_map;
1874 current_map = &minibuffer_map;
1876 base_map->unhandled_input = self_insert_fn;
1878 ministate.donefn = donefn;
1879 ministate.abortfn = abortfn;
1880 memset(ministate.buf, 0, sizeof(ministate.buf));
1881 ministate.window.current_line = &ministate.vline;
1882 ministate.window.current_line->line = ministate.buf;
1883 ministate.window.cpoff = 0;
1884 strlcpy(ministate.buf, "", sizeof(ministate.prompt));
1886 ministate.history = hist;
1887 ministate.hist_cur = NULL;
1888 ministate.hist_off = 0;
1891 static void
1892 exit_minibuffer(void)
1894 werase(minibuf);
1896 in_minibuffer = 0;
1897 base_map = &global_map;
1898 current_map = &global_map;
1901 static void
1902 switch_to_tab(struct tab *tab)
1904 struct tab *t;
1906 TAILQ_FOREACH(t, &tabshead, tabs) {
1907 t->flags &= ~TAB_CURRENT;
1910 tab->flags |= TAB_CURRENT;
1913 unsigned int
1914 tab_new_id(void)
1916 return tab_counter++;
1919 static struct tab *
1920 new_tab(const char *url)
1922 struct tab *tab;
1924 if ((tab = calloc(1, sizeof(*tab))) == NULL) {
1925 event_loopbreak();
1926 return NULL;
1929 TAILQ_INIT(&tab->hist.head);
1931 TAILQ_INIT(&tab->window.head);
1933 tab->id = tab_new_id();
1934 switch_to_tab(tab);
1936 if (TAILQ_EMPTY(&tabshead))
1937 TAILQ_INSERT_HEAD(&tabshead, tab, tabs);
1938 else
1939 TAILQ_INSERT_TAIL(&tabshead, tab, tabs);
1941 load_url_in_tab(tab, url);
1942 return tab;
1945 static void
1946 session_new_tab_cb(const char *url)
1948 new_tab(url);
1951 static void
1952 usage(void)
1954 fprintf(stderr, "USAGE: %s [url]\n", getprogname());
1957 int
1958 ui_init(int argc, char * const *argv)
1960 const char *url = NEW_TAB_URL;
1961 int ch;
1963 while ((ch = getopt(argc, argv, "")) != -1) {
1964 switch (ch) {
1965 default:
1966 usage();
1967 return 0;
1970 argc -= optind;
1971 argv += optind;
1973 if (argc != 0)
1974 url = argv[0];
1976 setlocale(LC_ALL, "");
1978 TAILQ_INIT(&global_map.m);
1979 global_map.unhandled_input = global_key_unbound;
1981 TAILQ_INIT(&minibuffer_map.m);
1983 TAILQ_INIT(&eecmd_history.head);
1984 TAILQ_INIT(&ir_history.head);
1985 TAILQ_INIT(&lu_history.head);
1987 ministate.line.type = LINE_TEXT;
1988 ministate.vline.parent = &ministate.line;
1989 ministate.window.current_line = &ministate.vline;
1991 /* initialize help window */
1992 TAILQ_INIT(&helpwin.head);
1994 base_map = &global_map;
1995 current_map = &global_map;
1996 load_default_keys();
1998 initscr();
1999 raw();
2000 noecho();
2002 nonl();
2003 intrflush(stdscr, FALSE);
2005 if ((tabline = newwin(1, COLS, 0, 0)) == NULL)
2006 return 0;
2007 if ((body = newwin(LINES - 3, COLS, 1, 0)) == NULL)
2008 return 0;
2009 if ((modeline = newwin(1, COLS, LINES-2, 0)) == NULL)
2010 return 0;
2011 if ((minibuf = newwin(1, COLS, LINES-1, 0)) == NULL)
2012 return 0;
2013 if ((help = newwin(1, 1, 1, 0)) == NULL)
2014 return 0;
2016 body_lines = LINES-3;
2017 body_cols = COLS;
2019 keypad(body, TRUE);
2020 scrollok(body, TRUE);
2022 /* non-blocking input */
2023 wtimeout(body, 0);
2025 mvwprintw(body, 0, 0, "");
2027 event_set(&stdioev, 0, EV_READ | EV_PERSIST, dispatch_stdio, NULL);
2028 event_add(&stdioev, NULL);
2030 signal_set(&winchev, SIGWINCH, handle_resize, NULL);
2031 signal_add(&winchev, NULL);
2033 load_last_session(session_new_tab_cb);
2034 if (strcmp(url, NEW_TAB_URL) || TAILQ_EMPTY(&tabshead))
2035 new_tab(url);
2037 return 1;
2040 void
2041 ui_on_tab_loaded(struct tab *tab)
2043 stop_loading_anim(tab);
2044 message("Loaded %s", tab->hist_cur->h);
2046 redraw_tabline();
2047 wrefresh(tabline);
2048 if (in_minibuffer)
2049 wrefresh(minibuf);
2050 else
2051 wrefresh(body);
2054 void
2055 ui_on_tab_refresh(struct tab *tab)
2057 wrap_page(&tab->window, body_cols);
2058 if (tab->flags & TAB_CURRENT) {
2059 restore_cursor(&tab->window);
2060 redraw_tab(tab);
2064 void
2065 ui_require_input(struct tab *tab, int hide)
2067 /* TODO: hard-switching to another tab is ugly */
2068 switch_to_tab(tab);
2070 enter_minibuffer(ir_self_insert, ir_select, exit_minibuffer,
2071 &ir_history);
2072 strlcpy(ministate.prompt, "Input required: ",
2073 sizeof(ministate.prompt));
2074 redraw_tab(tab);
2077 void
2078 ui_yornp(const char *prompt, void (*fn)(int, unsigned int),
2079 unsigned int data)
2081 size_t len;
2083 if (in_minibuffer) {
2084 fn(0, data);
2085 return;
2088 yornp_cb = fn;
2089 yornp_data = data;
2090 enter_minibuffer(yornp_self_insert, yornp_self_insert,
2091 yornp_abort, NULL);
2093 len = sizeof(ministate.prompt);
2094 strlcpy(ministate.prompt, prompt, len);
2095 strlcat(ministate.prompt, " (y or n) ", len);
2096 redraw_tab(current_tab());
2099 void
2100 ui_notify(const char *fmt, ...)
2102 va_list ap;
2104 va_start(ap, fmt);
2105 vmessage(fmt, ap);
2106 va_end(ap);
2109 void
2110 ui_end(void)
2112 endwin();