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 <sys/time.h>
21 #include <ctype.h>
22 #include <errno.h>
23 #include <limits.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
28 #include "certs.h"
29 #include "cmd.h"
30 #include "defaults.h"
31 #include "ev.h"
32 #include "fs.h"
33 #include "hist.h"
34 #include "iri.h"
35 #include "keymap.h"
36 #include "minibuffer.h"
37 #include "session.h"
38 #include "ui.h"
39 #include "utf8.h"
40 #include "utils.h"
42 #define nitems(x) (sizeof(x)/sizeof(x[0]))
44 static void *minibuffer_metadata(void);
45 static const char *minibuffer_compl_text(void);
46 static void minibuffer_hist_save_entry(void);
47 static void yornp_self_insert(void);
48 static void yornp_abort(void);
49 static void read_self_insert(void);
50 static void read_abort(void);
51 static void read_select(void);
52 static void handle_clear_echoarea(int, int, void *);
54 static unsigned long clechotimer;
55 static struct timeval clechotv = { 5, 0 };
57 static void (*yornp_cb)(int, struct tab *);
58 static struct tab *yornp_data;
60 static void (*read_cb)(const char*, struct tab *);
61 static struct tab *read_data;
63 struct hist *eecmd_history;
64 struct hist *ir_history;
65 struct hist *lu_history;
66 struct hist *read_history;
68 struct ministate ministate;
70 struct buffer minibufferwin;
72 int in_minibuffer;
74 static int
75 codepoint_isgraph(uint32_t cp)
76 {
77 if (cp < INT8_MAX)
78 return isgraph((unsigned char)cp);
79 return 1;
80 }
82 static inline int
83 matches(char **words, size_t len, struct line *l)
84 {
85 size_t i;
86 int lm, am;
88 for (i = 0; i < len; ++i) {
89 lm = am = 0;
91 if (strcasestr(l->line, words[i]) != NULL)
92 lm = 1;
93 if (l->alt != NULL &&
94 strcasestr(l->alt, words[i]) != NULL)
95 am = 1;
97 if (!lm && !am)
98 return 0;
99 }
101 return 1;
104 /*
105 * Recompute the visible completions. If add is 1, don't consider the
106 * ones already hidden.
107 */
108 void
109 recompute_completions(int add)
111 static char buf[GEMINI_URL_LEN];
112 const char *text;
113 char *input, **ap, *words[10];
114 size_t len = 0;
115 struct line *l;
116 struct vline *vl;
117 struct buffer *b;
119 if (in_minibuffer != MB_COMPREAD)
120 return;
122 if (!ministate.editing)
123 text = hist_cur(ministate.hist);
124 else
125 text = ministate.buf;
127 strlcpy(buf, text, sizeof(buf));
128 input = buf;
130 /* tokenize the input */
131 for (ap = words; ap < words + nitems(words) &&
132 (*ap = strsep(&input, " ")) != NULL;) {
133 if (**ap != '\0')
134 ap++, len++;
137 b = &ministate.compl.buffer;
138 TAILQ_FOREACH(l, &b->page.head, lines) {
139 l->type = LINE_COMPL;
140 if (add && l->flags & L_HIDDEN)
141 continue;
142 if (matches(words, len, l)) {
143 if (l->flags & L_HIDDEN)
144 b->line_max++;
145 l->flags &= ~L_HIDDEN;
146 } else {
147 if (!(l->flags & L_HIDDEN))
148 b->line_max--;
149 l->flags |= L_HIDDEN;
153 if (b->current_line == NULL)
154 b->current_line = TAILQ_FIRST(&b->head);
155 b->current_line = adjust_line(b->current_line, b);
156 vl = b->current_line;
157 if (ministate.compl.must_select && vl != NULL)
158 vl->parent->type = LINE_COMPL_CURRENT;
161 int
162 minibuffer_insert_current_candidate(void)
164 struct vline *vl;
166 vl = ministate.compl.buffer.current_line;
167 if (vl == NULL || vl->parent->flags & L_HIDDEN)
168 return -1;
170 minibuffer_taint_hist();
171 strlcpy(ministate.buf, vl->parent->line, sizeof(ministate.buf));
172 ministate.buffer.cpoff = utf8_cplen(ministate.buf);
174 return 0;
177 static void *
178 minibuffer_metadata(void)
180 struct vline *vl;
182 vl = ministate.compl.buffer.current_line;
184 if (vl == NULL || vl->parent->flags & L_HIDDEN)
185 return NULL;
187 return vl->parent->data;
190 static const char *
191 minibuffer_compl_text(void)
193 struct vline *vl;
195 if (!ministate.editing)
196 return hist_cur(ministate.hist);
198 vl = ministate.compl.buffer.current_line;
199 if (vl == NULL || vl->parent->flags & L_HIDDEN ||
200 vl->parent->type == LINE_COMPL || vl->parent->line == NULL)
201 return ministate.buf;
202 return vl->parent->line;
205 static void
206 minibuffer_hist_save_entry(void)
208 if (ministate.hist == NULL)
209 return;
211 hist_append(ministate.hist, minibuffer_compl_text());
214 /*
215 * taint the minibuffer cache: if we're currently showing a history
216 * element, copy that to the current buf and reset the "history
217 * navigation" thing.
218 */
219 void
220 minibuffer_taint_hist(void)
222 if (ministate.editing)
223 return;
225 ministate.editing = 1;
226 strlcpy(ministate.buf, hist_cur(ministate.hist),
227 sizeof(ministate.buf));
228 ministate.buffer.current_line->parent->line = ministate.buf;
231 void
232 minibuffer_self_insert(void)
234 char *c, tmp[5] = {0};
235 size_t len;
237 minibuffer_taint_hist();
239 if (thiskey.cp == 0)
240 return;
242 len = utf8_encode(thiskey.cp, tmp);
243 c = utf8_nth(ministate.buffer.current_line->parent->line,
244 ministate.buffer.cpoff);
245 if (c + len > ministate.buf + sizeof(ministate.buf) - 1)
246 return;
248 memmove(c + len, c, strlen(c)+1);
249 memcpy(c, tmp, len);
250 ministate.buffer.cpoff++;
252 recompute_completions(1);
255 void
256 sensible_self_insert(void)
258 if (thiskey.meta ||
259 (!codepoint_isgraph(thiskey.key) && thiskey.key != ' ')) {
260 global_key_unbound();
261 return;
264 minibuffer_self_insert();
267 void
268 eecmd_select(void)
270 struct cmd *cmd;
271 const char *t;
273 t = minibuffer_compl_text();
274 for (cmd = cmds; cmd->cmd != NULL; ++cmd) {
275 if (!strcmp(cmd->cmd, t)) {
276 minibuffer_hist_save_entry();
277 exit_minibuffer();
278 cmd->fn(current_buffer());
279 return;
283 message("No match");
286 void
287 ir_select_gemini(void)
289 static struct iri iri;
290 char buf[1025];
291 struct tab *tab = current_tab;
293 minibuffer_hist_save_entry();
295 if (iri_parse(NULL, hist_cur(tab->hist), &iri) == -1)
296 goto err;
297 if (iri_setquery(&iri, minibuffer_compl_text()) == -1)
298 goto err;
299 if (iri_unparse(&iri, buf, sizeof(buf)) == -1)
300 goto err;
302 exit_minibuffer();
303 load_url_in_tab(tab, buf, NULL, LU_MODE_NOCACHE);
304 return;
306 err:
307 message("Failed to select URL.");
310 void
311 ir_select_reply(void)
313 static struct iri iri;
314 char buf[1025] = {0};
315 struct tab *tab = current_tab;
317 minibuffer_hist_save_entry();
319 /* a bit ugly but... */
320 iri_parse(NULL, tab->last_input_url, &iri);
321 iri_setquery(&iri, minibuffer_compl_text());
322 iri_unparse(&iri, buf, sizeof(buf));
324 exit_minibuffer();
325 load_url_in_tab(tab, buf, NULL, LU_MODE_NOCACHE);
328 void
329 ir_select_gopher(void)
331 minibuffer_hist_save_entry();
332 gopher_send_search_req(current_tab, minibuffer_compl_text());
333 exit_minibuffer();
336 void
337 lu_select(void)
339 char url[GEMINI_URL_LEN+1];
341 minibuffer_hist_save_entry();
342 humanify_url(minibuffer_compl_text(), hist_cur(current_tab->hist),
343 url, sizeof(url));
345 exit_minibuffer();
346 load_url_in_tab(current_tab, url, NULL, LU_MODE_NOCACHE);
349 void
350 bp_select(void)
352 const char *url;
354 url = minibuffer_compl_text();
355 if (*url != '\0') {
356 if (bookmark_page(url) == -1)
357 message("failed to bookmark page: %s",
358 strerror(errno));
359 else
360 message("Bookmarked");
361 } else
362 message("Abort.");
363 exit_minibuffer();
366 void
367 ts_select(void)
369 struct tab *tab;
371 if ((tab = minibuffer_metadata()) == NULL) {
372 message("No tab selected");
373 return;
376 exit_minibuffer();
377 switch_to_tab(tab);
380 void
381 ls_select(void)
383 struct line *l;
385 if ((l = minibuffer_metadata()) == NULL) {
386 message("No link selected");
387 return;
390 exit_minibuffer();
391 load_url_in_tab(current_tab, l->alt, NULL, LU_MODE_NOCACHE);
394 static inline void
395 jump_to_line(struct line *l)
397 struct vline *vl;
398 struct buffer *buffer;
400 buffer = current_buffer();
402 TAILQ_FOREACH(vl, &buffer->head, vlines) {
403 if (vl->parent == l)
404 break;
407 if (vl == NULL)
408 message("Ops, %s error! Please report to %s",
409 __func__, PACKAGE_BUGREPORT);
410 else {
411 buffer->top_line = vl;
412 buffer->current_line = vl;
416 void
417 swiper_select(void)
419 struct line *l;
421 if ((l = minibuffer_metadata()) == NULL) {
422 message("No line selected");
423 return;
426 exit_minibuffer();
427 jump_to_line(l);
430 void
431 toc_select(void)
433 struct line *l;
435 if ((l = minibuffer_metadata()) == NULL) {
436 message("No line selected");
437 return;
440 exit_minibuffer();
441 jump_to_line(l);
444 static void
445 save_cert_for_site_cb(int r, struct tab *tab)
447 cert_save_for(tab->client_cert, &tab->iri, r);
450 void
451 uc_select(void)
453 const char *name;
455 name = minibuffer_compl_text();
456 if ((current_tab->client_cert = ccert(name)) == NULL) {
457 message("Certificate %s not found", name);
458 return;
461 exit_minibuffer();
463 yornp("Remember for future sessions too?", save_cert_for_site_cb,
464 current_tab);
467 void
468 search_select(void)
470 static struct iri iri;
471 static char buf[1025];
473 /* a bit ugly but... */
474 if (iri_parse(NULL, default_search_engine, &iri) == -1) {
475 message("default-search-engine is a malformed IRI.");
476 exit_minibuffer();
477 return;
479 iri_setquery(&iri, minibuffer_compl_text());
480 iri_unparse(&iri, buf, sizeof(buf));
482 exit_minibuffer();
483 load_url_in_tab(current_tab, buf, NULL, LU_MODE_NOCACHE);
486 static void
487 yornp_self_insert(void)
489 if (thiskey.key != 'y' && thiskey.key != 'n') {
490 message("Please answer y or n");
491 return;
494 exit_minibuffer();
495 yornp_cb(thiskey.key == 'y', yornp_data);
498 static void
499 yornp_abort(void)
501 exit_minibuffer();
502 yornp_cb(0, yornp_data);
505 static void
506 read_self_insert(void)
508 if (thiskey.meta || !codepoint_isgraph(thiskey.cp)) {
509 global_key_unbound();
510 return;
513 minibuffer_self_insert();
516 static void
517 read_abort(void)
519 exit_minibuffer();
520 read_cb(NULL, read_data);
523 static void
524 read_select(void)
526 exit_minibuffer();
527 minibuffer_hist_save_entry();
528 read_cb(ministate.buf, read_data);
531 /*
532 * TODO: we should collect this asynchronously...
533 */
534 static inline void
535 populate_compl_buffer(complfn *fn, void *data)
537 const char *s, *descr;
538 struct line *l;
539 struct buffer *b;
540 struct parser *p;
541 void *linedata;
543 b = &ministate.compl.buffer;
544 p = &b->page;
546 linedata = NULL;
547 descr = NULL;
548 while ((s = fn(&data, &linedata, &descr)) != NULL) {
549 if ((l = calloc(1, sizeof(*l))) == NULL)
550 abort();
552 l->type = LINE_COMPL;
553 l->data = linedata;
554 l->alt = (char*)descr;
555 if ((l->line = strdup(s)) == NULL)
556 abort();
558 TAILQ_INSERT_TAIL(&p->head, l, lines);
560 linedata = NULL;
561 descr = NULL;
564 if ((l = TAILQ_FIRST(&p->head)) != NULL &&
565 ministate.compl.must_select)
566 l->type = LINE_COMPL_CURRENT;
569 void
570 enter_minibuffer(void (*self_insert_fn)(void), void (*donefn)(void),
571 void (*abortfn)(void), struct hist *hist,
572 complfn *complfn, void *compldata, int must_select)
574 ministate.compl.must_select = must_select;
575 ministate.compl.fn = complfn;
576 ministate.compl.data = compldata;
578 in_minibuffer = complfn == NULL ? MB_READ : MB_COMPREAD;
579 if (in_minibuffer == MB_COMPREAD) {
580 populate_compl_buffer(complfn, compldata);
581 ui_schedule_redraw();
584 base_map = &minibuffer_map;
585 current_map = &minibuffer_map;
587 base_map->unhandled_input = self_insert_fn;
589 ministate.donefn = donefn;
590 ministate.abortfn = abortfn;
591 memset(ministate.buf, 0, sizeof(ministate.buf));
592 ministate.buffer.current_line = &ministate.vline;
593 ministate.buffer.current_line->parent->line = ministate.buf;
594 ministate.buffer.cpoff = 0;
595 strlcpy(ministate.buf, "", sizeof(ministate.prompt));
597 ministate.editing = 1;
598 ministate.hist = hist;
599 if (ministate.hist)
600 hist_seek_start(ministate.hist);
603 void
604 exit_minibuffer(void)
606 if (in_minibuffer == MB_COMPREAD) {
607 erase_buffer(&ministate.compl.buffer);
608 ui_schedule_redraw();
611 in_minibuffer = 0;
612 base_map = &global_map;
613 current_map = &global_map;
616 void
617 yornp(const char *prompt, void (*fn)(int, struct tab*),
618 struct tab *data)
620 size_t len;
622 if (in_minibuffer) {
623 fn(0, data);
624 return;
627 yornp_cb = fn;
628 yornp_data = data;
629 enter_minibuffer(yornp_self_insert, yornp_self_insert,
630 yornp_abort, NULL, NULL, NULL, 0);
632 len = sizeof(ministate.prompt);
633 strlcpy(ministate.prompt, prompt, len);
634 strlcat(ministate.prompt, " (y or n) ", len);
637 void
638 minibuffer_read(const char *prompt, void (*fn)(const char *, struct tab *),
639 struct tab *data)
641 size_t len;
643 if (in_minibuffer)
644 return;
646 read_cb = fn;
647 read_data = data;
648 enter_minibuffer(read_self_insert, read_select, read_abort,
649 read_history, NULL, NULL, 0);
651 len = sizeof(ministate.prompt);
652 strlcpy(ministate.prompt, prompt, len);
653 strlcat(ministate.prompt, ": ", len);
656 static void
657 handle_clear_echoarea(int fd, int ev, void *d)
659 free(ministate.curmesg);
660 ministate.curmesg = NULL;
662 ui_after_message_hook();
665 void
666 vmessage(const char *fmt, va_list ap)
668 ev_timer_cancel(clechotimer);
669 clechotimer = 0;
671 free(ministate.curmesg);
672 ministate.curmesg = NULL;
674 if (fmt != NULL) {
675 clechotimer = ev_timer(&clechotv, handle_clear_echoarea,
676 NULL);
678 /* TODO: what to do if the allocation fails here? */
679 if (vasprintf(&ministate.curmesg, fmt, ap) == -1)
680 ministate.curmesg = NULL;
683 ui_after_message_hook();
686 void
687 message(const char *fmt, ...)
689 va_list ap;
691 va_start(ap, fmt);
692 vmessage(fmt, ap);
693 va_end(ap);
696 void
697 minibuffer_init(void)
699 if ((eecmd_history = hist_new(HIST_WRAP)) == NULL ||
700 (ir_history = hist_new(HIST_WRAP)) == NULL ||
701 (lu_history = hist_new(HIST_WRAP)) == NULL ||
702 (read_history = hist_new(HIST_WRAP)) == NULL)
703 err(1, "hist_new");
705 TAILQ_INIT(&ministate.compl.buffer.head);
706 TAILQ_INIT(&ministate.compl.buffer.page.head);
708 ministate.line.type = LINE_TEXT;
709 ministate.vline.parent = &ministate.line;
710 ministate.buffer.page.name = "*minibuffer*";
711 ministate.buffer.current_line = &ministate.vline;