Blob


1 /*
2 * Copyright (c) 2021, 2024 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 #include "compat.h"
19 #include <limits.h>
20 #include <signal.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <unistd.h>
25 #include <grapheme.h>
27 #include "certs.h"
28 #include "cmd.h"
29 #include "compl.h"
30 #include "defaults.h"
31 #include "ev.h"
32 #include "exec.h"
33 #include "hist.h"
34 #include "keymap.h"
35 #include "mcache.h"
36 #include "minibuffer.h"
37 #include "session.h"
38 #include "telescope.h"
39 #include "ui.h"
40 #include "utf8.h"
41 #include "utils.h"
43 #define GUARD_RECURSIVE_MINIBUFFER() \
44 do { \
45 if (in_minibuffer) { \
46 message("enable-recursive-minibuffers " \
47 "is not yet available."); \
48 return; \
49 } \
50 } while(0)
52 #define GUARD_READ_ONLY() \
53 do { \
54 if (!in_minibuffer) { \
55 message("text is read-only"); \
56 return; \
57 } \
58 } while(0)
60 /* return 1 if moved, 0 otherwise */
61 static inline int
62 forward_line(struct buffer *buffer, int n)
63 {
64 struct vline *vl;
65 char *text;
66 size_t left, off;
67 int did, target, col = 0; /* XXX breaks with tabs! */
69 if (buffer->current_line == NULL)
70 return 0;
71 vl = buffer->current_line;
73 text = vl->parent->line + vl->from;
74 left = MIN(vl->len, buffer->point_offset);
75 target = utf8_snwidth(text, left, 0); /* XXX breaks with tabs! */
77 did = 0;
78 while (n != 0) {
79 if (n > 0) {
80 vl = TAILQ_NEXT(vl, vlines);
81 if (vl == NULL)
82 break;
83 if (vl->parent->flags & L_HIDDEN)
84 continue;
85 buffer->current_line = vl;
86 n--;
87 } else {
88 vl = TAILQ_PREV(vl, vhead, vlines);
89 if (vl == NULL)
90 break;
91 if (vl->parent->flags & L_HIDDEN)
92 continue;
93 if (buffer->current_line == buffer->top_line) {
94 buffer->line_off--;
95 buffer->top_line = vl;
96 }
97 buffer->current_line = vl;
98 n++;
99 }
101 did = 1;
104 /* keep the cursor in the same column */
105 if (did) {
106 vl = buffer->current_line;
107 text = vl->parent->line + vl->from;
108 left = vl->len;
109 buffer->point_offset = 0;
110 while (left > 0 && col < target) {
111 off = grapheme_next_character_break_utf8(text, left);
112 col += utf8_snwidth(text, off, col);
113 text += off;
114 left -= off;
115 buffer->point_offset += off;
119 return did;
122 void
123 cmd_previous_line(struct buffer *buffer)
125 forward_line(buffer, -1);
128 void
129 cmd_next_line(struct buffer *buffer)
131 forward_line(buffer, +1);
134 void
135 cmd_backward_char(struct buffer *buffer)
137 struct vline *vl;
138 char *text;
139 size_t left, off, point = 0;
141 if ((vl = buffer->current_line) == NULL)
142 return;
144 text = vl->parent->line + vl->from;
145 left = vl->len;
147 for (;;) {
148 off = grapheme_next_character_break_utf8(text, left);
149 if (point + off >= buffer->point_offset)
150 break;
151 point += off;
152 text += off;
153 left -= off;
156 buffer->point_offset = point;
159 void
160 cmd_forward_char(struct buffer *buffer)
162 struct vline *vl;
163 char *text;
164 size_t left, off;
166 if ((vl = buffer->current_line) == NULL)
167 return;
169 text = vl->parent->line + vl->from;
170 left = vl->len;
172 text += buffer->point_offset;
173 left -= buffer->point_offset;
175 off = grapheme_next_character_break_utf8(text, left);
176 buffer->point_offset += off;
179 void
180 cmd_backward_paragraph(struct buffer *buffer)
182 do {
183 if (!forward_line(buffer, -1)) {
184 message("No previous paragraph");
185 return;
187 } while (buffer->current_line->len != 0 ||
188 buffer->current_line->parent->type != LINE_TEXT);
191 void
192 cmd_forward_paragraph(struct buffer *buffer)
194 do {
195 if (!forward_line(buffer, +1)) {
196 message("No next paragraph");
197 return;
199 } while (buffer->current_line->len != 0 ||
200 buffer->current_line->parent->type != LINE_TEXT);
203 void
204 cmd_move_beginning_of_line(struct buffer *buffer)
206 buffer->point_offset = 0;
209 void
210 cmd_move_end_of_line(struct buffer *buffer)
212 struct vline *vl;
214 vl = buffer->current_line;
215 if (vl == NULL)
216 return;
217 buffer->point_offset = vl->len;
220 void
221 cmd_redraw(struct buffer *buffer)
223 ui_schedule_redraw();
226 void
227 cmd_scroll_line_up(struct buffer *buffer)
229 struct vline *vl;
231 for (;;) {
232 if (buffer->top_line == NULL)
233 return;
235 if ((vl = TAILQ_PREV(buffer->top_line, vhead, vlines))
236 == NULL)
237 return;
239 buffer->top_line = vl;
241 if (vl->parent->flags & L_HIDDEN)
242 continue;
244 break;
247 buffer->line_off--;
249 forward_line(buffer, -1);
252 void
253 cmd_scroll_line_down(struct buffer *buffer)
255 if (!forward_line(buffer, +1))
256 return;
258 for (;;) {
259 if (buffer->top_line == NULL)
260 return;
262 buffer->top_line = TAILQ_NEXT(buffer->top_line, vlines);
263 if (buffer->top_line->parent->flags & L_HIDDEN)
264 continue;
265 break;
268 buffer->line_off++;
271 void
272 cmd_scroll_up(struct buffer *buffer)
274 struct vline *vl;
275 int i;
277 if (buffer->top_line == NULL)
278 return;
280 for (i = 0; i < body_lines; ++i) {
281 vl = TAILQ_PREV(buffer->top_line, vhead, vlines);
282 if (vl == NULL)
283 break;
284 buffer->line_off--;
285 buffer->top_line = vl;
286 forward_line(buffer, -1);
290 void
291 cmd_scroll_down(struct buffer *buffer)
293 int i;
295 if (buffer->top_line == NULL)
296 return;
298 for (i = 0; i < body_lines; ++i) {
299 if (!forward_line(buffer, +1))
300 break;
302 buffer->top_line = TAILQ_NEXT(buffer->top_line,
303 vlines);
304 buffer->line_off++;
308 void
309 cmd_beginning_of_buffer(struct buffer *buffer)
311 buffer->current_line = TAILQ_FIRST(&buffer->vhead);
312 buffer->point_offset = 0;
313 buffer->top_line = buffer->current_line;
314 buffer->line_off = 0;
317 void
318 cmd_end_of_buffer(struct buffer *buffer)
320 buffer->current_line = TAILQ_LAST(&buffer->vhead, vhead);
322 if (buffer->current_line == NULL)
323 return;
325 /* deal with invisible lines */
326 if (buffer->current_line->parent->flags & L_HIDDEN)
327 forward_line(buffer, -1);
329 cmd_move_end_of_line(buffer);
332 static void
333 kill_telescope_cb(int r, void *data)
335 if (r) {
336 save_session();
337 ev_break();
341 void
342 cmd_kill_telescope(struct buffer *buffer)
344 yornp("really quit?", kill_telescope_cb, NULL);
347 void
348 cmd_push_button(struct buffer *buffer)
350 struct vline *vl;
351 struct line *l;
353 vl = buffer->current_line;
355 if (vl == NULL)
356 return;
358 switch (vl->parent->type) {
359 case LINE_LINK:
360 load_url_in_tab(current_tab, vl->parent->alt, NULL,
361 LU_MODE_NOCACHE);
362 break;
363 case LINE_PRE_START:
364 l = TAILQ_NEXT(vl->parent, lines);
365 for (; l != NULL; l = TAILQ_NEXT(l, lines)) {
366 if (l->type == LINE_PRE_END)
367 break;
368 l->flags ^= L_HIDDEN;
369 if (l->flags & L_HIDDEN)
370 buffer->line_max--;
371 else
372 buffer->line_max++;
374 break;
375 default:
376 break;
380 void
381 cmd_push_button_new_tab(struct buffer *buffer)
383 struct vline *vl;
385 vl = buffer->current_line;
386 if (vl == NULL || vl->parent->type != LINE_LINK)
387 return;
389 new_tab(vl->parent->alt, hist_cur(current_tab->hist), current_tab);
392 void
393 cmd_previous_button(struct buffer *buffer)
395 struct excursion place;
397 save_excursion(&place, buffer);
399 do {
400 if (!forward_line(buffer, -1)) {
401 restore_excursion(&place, buffer);
402 message("No previous link");
403 return;
405 } while (buffer->current_line->parent->type != LINE_LINK);
408 void
409 cmd_next_button(struct buffer *buffer)
411 struct excursion place;
413 save_excursion(&place, buffer);
415 do {
416 if (!forward_line(buffer, +1)){
417 restore_excursion(&place, buffer);
418 message("No next link");
419 return;
421 } while (buffer->current_line->parent->type != LINE_LINK);
424 static inline int
425 is_heading(const struct line *l)
427 return l->type == LINE_TITLE_1 ||
428 l->type == LINE_TITLE_2 ||
429 l->type == LINE_TITLE_3;
432 void
433 cmd_previous_heading(struct buffer *buffer)
435 struct excursion place;
437 save_excursion(&place, buffer);
439 do {
440 if (!forward_line(buffer, -1)) {
441 restore_excursion(&place, buffer);
442 message("No previous heading");
443 return;
445 } while (!is_heading(buffer->current_line->parent));
448 void
449 cmd_next_heading(struct buffer *buffer)
451 struct excursion place;
453 save_excursion(&place, buffer);
455 do {
456 if (!forward_line(buffer, +1)) {
457 restore_excursion(&place, buffer);
458 message("No next heading");
459 return;
461 } while (!is_heading(buffer->current_line->parent));
464 void
465 cmd_previous_page(struct buffer *buffer)
467 if (!load_previous_page(current_tab))
468 message("No previous page");
471 void
472 cmd_next_page(struct buffer *buffer)
474 if (!load_next_page(current_tab))
475 message("No next page");
478 void
479 cmd_clear_minibuf(struct buffer *buffer)
481 message(NULL);
484 void
485 cmd_execute_extended_command(struct buffer *buffer)
487 struct minibuffer m = {
488 .self_insert = sensible_self_insert,
489 .done = eecmd_select,
490 .history = eecmd_history,
491 .complfn = compl_eecmd,
492 .must_select = 1,
493 };
495 GUARD_RECURSIVE_MINIBUFFER();
497 enter_minibuffer(&m, "%s%s%s", thiskey.meta ? "M-" : "",
498 ui_keyname(thiskey.key), thiskey.meta ? " " : "");
501 void
502 cmd_tab_close(struct buffer *buffer)
504 struct tab *tab, *t;
506 tab = current_tab;
508 if ((t = TAILQ_NEXT(tab, tabs)) != NULL ||
509 (t = TAILQ_PREV(tab, tabshead, tabs)) != NULL) {
510 switch_to_tab(t);
511 kill_tab(tab, 0);
512 } else
513 message("Can't close the only tab.");
517 void
518 cmd_tab_close_other(struct buffer *buffer)
520 struct tab *t, *i;
522 TAILQ_FOREACH_SAFE(t, &tabshead, tabs, i) {
523 if (t == current_tab)
524 continue;
526 kill_tab(t, 0);
530 void
531 cmd_tab_undo_close(struct buffer *buffer)
533 struct tab *t;
535 if ((t = unkill_tab()) == NULL) {
536 message("No recently-closed tabs");
537 return;
540 switch_to_tab(t);
543 void
544 cmd_tab_new(struct buffer *buffer)
546 const char *url;
548 if ((url = new_tab_url) == NULL)
549 url = NEW_TAB_URL;
551 new_tab(url, NULL, NULL);
554 void
555 cmd_tab_next(struct buffer *buffer)
557 struct tab *t;
559 if ((t = TAILQ_NEXT(current_tab, tabs)) == NULL)
560 t = TAILQ_FIRST(&tabshead);
561 switch_to_tab(t);
564 void
565 cmd_tab_previous(struct buffer *buffer)
567 struct tab *t;
569 if ((t = TAILQ_PREV(current_tab, tabshead, tabs)) == NULL)
570 t = TAILQ_LAST(&tabshead, tabshead);
571 switch_to_tab(t);
574 void
575 cmd_tab_move(struct buffer *buffer)
577 struct tab *t;
579 t = TAILQ_NEXT(current_tab, tabs);
580 TAILQ_REMOVE(&tabshead, current_tab, tabs);
582 if (t == NULL)
583 TAILQ_INSERT_HEAD(&tabshead, current_tab, tabs);
584 else
585 TAILQ_INSERT_AFTER(&tabshead, t, current_tab, tabs);
588 void
589 cmd_tab_move_to(struct buffer *buffer)
591 struct tab *t;
593 t = TAILQ_PREV(current_tab, tabshead, tabs);
594 TAILQ_REMOVE(&tabshead, current_tab, tabs);
596 if (t == NULL)
597 TAILQ_INSERT_TAIL(&tabshead, current_tab, tabs);
598 else
599 TAILQ_INSERT_BEFORE(t, current_tab, tabs);
602 void
603 cmd_tab_select(struct buffer *buffer)
605 struct minibuffer m = {
606 .self_insert = sensible_self_insert,
607 .done = ts_select,
608 .complfn = compl_ts,
609 .must_select = 1,
610 };
612 GUARD_RECURSIVE_MINIBUFFER();
614 enter_minibuffer(&m, "Select tab: ");
617 void
618 cmd_load_url(struct buffer *buffer)
620 struct minibuffer m = {
621 .self_insert = sensible_self_insert,
622 .done = lu_select,
623 .history = lu_history,
624 .complfn = compl_lu,
625 };
627 GUARD_RECURSIVE_MINIBUFFER();
629 enter_minibuffer(&m, "Load URL: ");
632 void
633 cmd_load_current_url(struct buffer *buffer)
635 struct minibuffer m = {
636 .self_insert = sensible_self_insert,
637 .done = lu_select,
638 .history = lu_history,
639 .complfn = compl_lu,
640 .input = hist_cur(current_tab->hist),
641 };
643 GUARD_RECURSIVE_MINIBUFFER();
645 enter_minibuffer(&m, "Load URL: ");
648 void
649 cmd_reload_page(struct buffer *buffer)
651 load_url_in_tab(current_tab, hist_cur(current_tab->hist), NULL,
652 LU_MODE_NOHIST|LU_MODE_NOCACHE);
655 void
656 cmd_bookmark_page(struct buffer *buffer)
658 struct minibuffer m = {
659 .self_insert = sensible_self_insert,
660 .done = bp_select,
661 .input = hist_cur(current_tab->hist),
662 };
664 GUARD_RECURSIVE_MINIBUFFER();
666 enter_minibuffer(&m, "Bookmark URL: ");
669 void
670 cmd_list_bookmarks(struct buffer *buffer)
672 load_url_in_tab(current_tab, "about:bookmarks", NULL, LU_MODE_NONE);
675 void
676 cmd_toggle_help(struct buffer *buffer)
678 ui_toggle_side_window(SIDE_WINDOW_LEFT);
681 void
682 cmd_link_select(struct buffer *buffer)
684 struct line *l;
685 struct minibuffer m = {
686 .self_insert = sensible_self_insert,
687 .done = ls_select,
688 .complfn = compl_ls,
689 .compldata = NULL,
690 .must_select = 1,
691 };
693 GUARD_RECURSIVE_MINIBUFFER();
695 l = TAILQ_FIRST(&buffer->head);
696 while (l != NULL && l->type != LINE_LINK)
697 l = TAILQ_NEXT(l, lines);
699 if (l == NULL) {
700 message("No links found");
701 return;
704 m.compldata = l;
705 enter_minibuffer(&m, "Select link: ");
708 void
709 cmd_swiper(struct buffer *buffer)
711 struct minibuffer m = {
712 .self_insert = sensible_self_insert,
713 .done = swiper_select,
714 .complfn = compl_swiper,
715 .compldata = TAILQ_FIRST(&buffer->head),
716 .must_select = 1,
717 };
719 GUARD_RECURSIVE_MINIBUFFER();
721 enter_minibuffer(&m, "Select line: ");
724 void
725 cmd_toc(struct buffer *buffer)
727 struct line *l;
728 struct minibuffer m = {
729 .self_insert = sensible_self_insert,
730 .done = toc_select,
731 .complfn = compl_toc,
732 .compldata = NULL,
733 .must_select = 1,
734 };
736 GUARD_RECURSIVE_MINIBUFFER();
738 l = TAILQ_FIRST(&buffer->head);
739 while (l != NULL &&
740 l->type != LINE_TITLE_1 &&
741 l->type != LINE_TITLE_2 &&
742 l->type != LINE_TITLE_3)
743 l = TAILQ_NEXT(l, lines);
745 if (l == NULL) {
746 message("No headings found");
747 return;
750 m.compldata = l;
751 enter_minibuffer(&m, "Select heading: ");
754 void
755 cmd_inc_fill_column(struct buffer *buffer)
757 if (fill_column == INT_MAX)
758 return;
760 fill_column += 2;
761 message("fill-column: %d", fill_column);
763 ui_schedule_redraw();
766 void
767 cmd_dec_fill_column(struct buffer *buffer)
769 if (fill_column == INT_MAX || fill_column < 8)
770 return;
772 fill_column -= 2;
773 message("fill-column: %d", fill_column);
775 ui_schedule_redraw();
778 void
779 cmd_olivetti_mode(struct buffer *buffer)
781 olivetti_mode = !olivetti_mode;
782 if (olivetti_mode)
783 message("olivetti-mode enabled");
784 else
785 message("olivetti-mode disabled");
787 ui_schedule_redraw();
790 void
791 cmd_mini_delete_char(struct buffer *buffer)
793 struct vline *vl;
794 char *text;
795 size_t old_point, gap, rest;
797 GUARD_READ_ONLY();
799 vl = buffer->current_line;
800 old_point = buffer->point_offset;
801 cmd_forward_char(buffer);
802 gap = buffer->point_offset - old_point;
803 if (gap == 0)
804 return;
806 minibuffer_taint_hist();
808 text = vl->parent->line + vl->from + old_point;
809 rest = vl->len - buffer->point_offset;
810 memmove(text, text + gap, rest + 1);
811 buffer->point_offset = old_point;
812 vl->len -= gap;
814 recompute_completions(0);
817 void
818 cmd_mini_delete_backward_char(struct buffer *buffer)
820 struct vline *vl;
821 char *text;
822 size_t old_point, gap, rest;
824 GUARD_READ_ONLY();
826 vl = buffer->current_line;
827 old_point = buffer->point_offset;
828 cmd_backward_char(buffer);
829 gap = old_point - buffer->point_offset;
830 if (gap == 0)
831 return;
833 minibuffer_taint_hist();
835 text = vl->parent->line + vl->from + buffer->point_offset;
836 rest = vl->len - old_point;
837 memmove(text, text + gap, rest + 1);
838 vl->len -= gap;
840 recompute_completions(0);
843 void
844 cmd_mini_kill_line(struct buffer *buffer)
846 char *line, *c;
848 GUARD_READ_ONLY();
850 minibuffer_taint_hist();
852 line = buffer->current_line->parent->line + buffer->current_line->from;
853 c = line + buffer->point_offset;
854 *c = '\0';
856 recompute_completions(0);
859 void
860 cmd_mini_kill_whole_line(struct buffer *buffer)
862 GUARD_READ_ONLY();
864 minibuffer_taint_hist();
865 *buffer->current_line->parent->line = '\0';
866 buffer->point_offset = 0;
867 buffer->current_line->len = 0;
869 recompute_completions(0);
872 void
873 cmd_mini_abort(struct buffer *buffer)
875 if (!in_minibuffer)
876 return;
878 ministate.abortfn();
881 void
882 cmd_mini_complete_and_exit(struct buffer *buffer)
884 struct vline *vl;
886 if (!in_minibuffer)
887 return;
889 if (ministate.compl.must_select && ministate.hist == NULL) {
890 vl = ministate.compl.buffer.current_line;
891 if (vl == NULL || vl->parent->flags & L_HIDDEN ||
892 vl->parent->type == LINE_COMPL) {
893 message("no match");
894 return;
898 minibuffer_confirm();
901 void
902 cmd_mini_previous_history_element(struct buffer *buffer)
904 char *text;
906 if (ministate.hist == NULL) {
907 message("No history");
908 return;
911 if (hist_prev(ministate.hist) == NULL) {
912 message("No prev history item");
913 return;
916 ministate.editing = 0;
918 /* XXX the minibuffer line is never modified so this is fine */
919 text = (char *)hist_cur(ministate.hist);
920 buffer->current_line->parent->line = text;
921 recompute_completions(0);
924 void
925 cmd_mini_next_history_element(struct buffer *buffer)
927 char *text;
929 if (ministate.hist == NULL) {
930 message("No history");
931 return;
934 if (hist_next(ministate.hist) == NULL) {
935 message("No next history item");
936 return;
939 ministate.editing = 0;
941 /* XXX the minibuffer line is never modified so this is fine */
942 text = (char *)hist_cur(ministate.hist);
943 buffer->current_line->parent->line = text;
944 recompute_completions(0);
947 void
948 cmd_previous_completion(struct buffer *buffer)
950 if (in_minibuffer != MB_COMPREAD)
951 return;
953 buffer = &ministate.compl.buffer;
954 if (buffer->current_line == NULL)
955 return;
957 buffer->current_line->parent->type = LINE_COMPL;
958 if (!forward_line(buffer, -1))
959 buffer->current_line->parent->type = LINE_COMPL;
960 else
961 buffer->current_line->parent->type = LINE_COMPL_CURRENT;
964 void
965 cmd_next_completion(struct buffer *buffer)
967 if (in_minibuffer != MB_COMPREAD)
968 return;
970 buffer = &ministate.compl.buffer;
971 if (buffer->current_line == NULL)
972 return;
974 if (buffer->current_line->parent->type == LINE_COMPL_CURRENT) {
975 buffer->current_line->parent->type = LINE_COMPL;
976 forward_line(buffer, +1);
979 if (buffer->current_line != NULL)
980 buffer->current_line->parent->type = LINE_COMPL_CURRENT;
983 void
984 cmd_insert_current_candidate(struct buffer *buffer)
986 if (in_minibuffer != MB_COMPREAD)
987 return;
989 minibuffer_insert_current_candidate();
992 void
993 cmd_suspend_telescope(struct buffer *buffer)
995 message("Zzz...");
996 ui_suspend();
997 kill(getpid(), SIGSTOP);
998 ui_resume();
1001 void
1002 cmd_toggle_pre_wrap(struct buffer *buffer)
1004 dont_wrap_pre = !dont_wrap_pre;
1006 if (dont_wrap_pre)
1007 message("Don't wrap preformatted blocks");
1008 else
1009 message("Wrap preformatted blocks");
1011 ui_schedule_redraw();
1014 void
1015 cmd_toggle_styling(struct buffer *buffer)
1017 dont_apply_styling = !dont_apply_styling;
1019 config_apply_style();
1021 /* Force a reload here, rather than calling ui_schedule_redraw() so
1022 * that any lines which are set as L_HIDDEN are redrawn appropriately.
1024 load_url_in_tab(current_tab, hist_cur(current_tab->hist), NULL,
1025 LU_MODE_NOHIST);
1028 void
1029 cmd_mini_goto_beginning(struct buffer *buffer)
1031 struct vline *vl;
1033 if (!in_minibuffer)
1034 return;
1036 buffer = &ministate.compl.buffer;
1038 if ((vl = buffer->current_line) != NULL)
1039 vl->parent->type = LINE_COMPL;
1041 vl = TAILQ_FIRST(&buffer->vhead);
1042 while (vl != NULL && vl->parent->flags & L_HIDDEN)
1043 vl = TAILQ_NEXT(vl, vlines);
1045 if (vl == NULL)
1046 return;
1048 vl->parent->type = LINE_COMPL_CURRENT;
1049 buffer->top_line = vl;
1050 buffer->current_line = vl;
1053 void
1054 cmd_mini_goto_end(struct buffer *buffer)
1056 struct vline *vl;
1058 if (!in_minibuffer)
1059 return;
1061 buffer = &ministate.compl.buffer;
1063 if ((vl = buffer->current_line) != NULL)
1064 vl->parent->type = LINE_COMPL;
1066 vl = TAILQ_LAST(&buffer->vhead, vhead);
1067 while (vl != NULL && vl->parent->flags & L_HIDDEN)
1068 vl = TAILQ_PREV(vl, vhead, vlines);
1070 if (vl == NULL)
1071 return;
1073 vl->parent->type = LINE_COMPL_CURRENT;
1074 buffer->current_line = vl;
1077 void
1078 cmd_other_window(struct buffer *buffer)
1080 ui_other_window();
1083 void
1084 cmd_mini_scroll_up(struct buffer *buffer)
1086 if (!in_minibuffer)
1087 return;
1089 buffer = &ministate.compl.buffer;
1090 if (buffer->current_line == NULL)
1091 return;
1093 buffer->current_line->parent->type = LINE_COMPL;
1094 cmd_scroll_up(buffer);
1095 buffer->current_line->parent->type = LINE_COMPL_CURRENT;
1098 void
1099 cmd_mini_scroll_down(struct buffer *buffer)
1101 if (!in_minibuffer)
1102 return;
1104 buffer = &ministate.compl.buffer;
1105 if (buffer->current_line == NULL)
1106 return;
1108 buffer->current_line->parent->type = LINE_COMPL;
1109 cmd_scroll_down(buffer);
1110 buffer->current_line->parent->type = LINE_COMPL_CURRENT;
1113 void
1114 cmd_toggle_downloads(struct buffer *buffer)
1116 ui_toggle_side_window(SIDE_WINDOW_BOTTOM);
1119 void
1120 cmd_cache_info(struct buffer *buffer)
1122 size_t npages, tot;
1123 char fmt[FMT_SCALED_STRSIZE];
1125 mcache_info(&npages, &tot);
1127 if (fmt_scaled(tot, fmt) == 0)
1128 message("pages: %zu, total: %s", npages, fmt);
1129 else
1130 message("pages: %zu, total: %zu", npages, tot);
1133 void
1134 cmd_reply_last_input(struct buffer *buffer)
1136 GUARD_RECURSIVE_MINIBUFFER();
1138 if (current_tab->last_input_url == NULL) {
1139 message("there was no previous input request in this tab");
1140 return;
1143 if (!strncmp(current_tab->last_input_url, "gopher", 6)) {
1144 load_url_in_tab(current_tab, current_tab->last_input_url,
1145 NULL, LU_MODE_NOCACHE);
1146 return;
1149 message("%s", current_tab->last_input_url);
1150 ui_require_input(current_tab, 0, ir_select_reply);
1153 void
1154 cmd_write_buffer(struct buffer *buffer)
1156 const char *f, *url;
1157 char path[PATH_MAX];
1159 GUARD_RECURSIVE_MINIBUFFER();
1161 if (safe_mode) {
1162 message("Can't write buffer in safe-mode.");
1163 return;
1166 url = hist_cur(current_tab->hist);
1168 if ((f = strrchr(url, '/')) != NULL)
1169 f++;
1170 if (f == NULL || *f == '\0') {
1171 /* guess a decent file name based on the protocol used */
1172 if (!strncmp(url, "gemini://", 9))
1173 f = "index.gmi";
1174 else
1175 f = "index.txt";
1178 strlcpy(path, download_path, sizeof(path));
1179 strlcat(path, f, sizeof(path));
1181 ui_read("Write file", write_buffer, current_tab, path);
1184 void
1185 cmd_home(struct buffer *buffer)
1187 char path[GEMINI_URL_LEN];
1188 char *tilde, *t;
1190 strlcpy(path, current_tab->iri.iri_path, sizeof(path));
1192 if ((tilde = strstr(path, "/~")) != NULL &&
1193 tilde[2] != '\0' && tilde[2] != '/') {
1194 if ((t = strchr(tilde + 2, '/')) != NULL)
1195 *++t = '\0';
1196 load_url_in_tab(current_tab, path, NULL, LU_MODE_NOCACHE);
1197 } else
1198 cmd_root(buffer);
1201 void
1202 cmd_root(struct buffer *buffer)
1204 load_url_in_tab(current_tab, "/", NULL, LU_MODE_NOCACHE);
1207 void
1208 cmd_up(struct buffer *buffer)
1210 load_url_in_tab(current_tab, "..", NULL, LU_MODE_NOCACHE);
1213 void
1214 cmd_use_certificate(struct buffer *buffer)
1216 struct minibuffer m = {
1217 .self_insert = sensible_self_insert,
1218 .done = uc_select,
1219 .complfn = compl_uc,
1220 .must_select = 1,
1223 GUARD_RECURSIVE_MINIBUFFER();
1225 enter_minibuffer(&m, "Select certificate: ");
1228 void
1229 cmd_client_certificate_info(struct buffer *buffer)
1231 if (current_tab->client_cert)
1232 message("Using certificate %s", current_tab->client_cert);
1233 else
1234 message("Not using any client certificate.");
1237 static void
1238 unload_certificate_cb(int r, void *data)
1240 struct tab *tab = data;
1242 message("Won't use %s for this site.", tab->client_cert);
1243 cert_delete_for(tab->client_cert, &tab->iri, r);
1246 void
1247 cmd_unload_certificate(struct buffer *buffer)
1249 struct tab *tab = current_tab;
1251 GUARD_RECURSIVE_MINIBUFFER();
1253 if (tab->client_cert == NULL) {
1254 message("No client certificate in use!");
1255 return;
1258 if (tab->client_cert_temp) {
1259 message("Won't use %s for this site.", tab->client_cert);
1260 cert_delete_for(tab->client_cert, &tab->iri, 0);
1261 return;
1264 yornp("Unload only for the current session?", unload_certificate_cb,
1265 current_tab);
1268 void
1269 cmd_search(struct buffer *buffer)
1271 struct minibuffer m = {
1272 .self_insert = sensible_self_insert,
1273 .done = search_select,
1276 GUARD_RECURSIVE_MINIBUFFER();
1278 if (!strncmp(default_search_engine, "gopher://", 9)) {
1279 load_url_in_tab(current_tab, default_search_engine, NULL,
1280 LU_MODE_NOCACHE);
1281 return;
1284 enter_minibuffer(&m, "Search: ");
1287 void
1288 cmd_mini_edit_external(struct buffer *buffer)
1290 FILE *fp;
1291 char buf[1024 + 1];
1292 size_t r, len = 0;
1294 GUARD_READ_ONLY();
1296 if (ministate.compl.must_select || ministate.donefn == NULL) {
1297 message("Can't use an external editor to complete");
1298 return;
1301 if ((fp = exec_editor(ministate.buf, strlen(ministate.buf))) == NULL)
1302 return;
1304 while (len < sizeof(buf) - 1) {
1305 r = fread(buf + len, 1, sizeof(buf) - 1 - len, fp);
1306 len += r;
1307 if (r == 0)
1308 break;
1310 buf[len] = '\0';
1311 while (len > 0 && buf[len-1] == '\n')
1312 buf[--len] = '\0';
1315 * XXX: do not use minibuffer_confirm() since the text could
1316 * have multiple lines and we are not prepared to render them
1317 * in the history navigation.
1319 ministate.donefn(buf);
1320 exit_minibuffer();