2 * Copyright (c) 2021, 2024 Omar Polo <op@omarpolo.com>
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.
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.
39 #include "minibuffer.h"
45 #define nitems(x) (sizeof(x)/sizeof(x[0]))
47 static void *minibuffer_metadata(void);
48 static const char *minibuffer_compl_text(void);
49 static void minibuffer_hist_save_entry(void);
50 static void yornp_self_insert(void);
51 static void yornp_abort(void);
52 static void read_self_insert(void);
53 static void read_abort(void);
54 static void read_select(const char *);
55 static void handle_clear_echoarea(int, int, void *);
57 static unsigned long clechotimer;
58 static struct timeval clechotv = { 5, 0 };
60 static void (*yornp_cb)(int, void *);
61 static void *yornp_data;
63 static void (*read_cb)(const char*, struct tab *);
64 static struct tab *read_data;
66 struct hist *eecmd_history;
67 struct hist *ir_history;
68 struct hist *lu_history;
69 struct hist *read_history;
71 struct ministate ministate;
73 struct buffer minibufferwin;
78 codepoint_isgraph(uint32_t cp)
81 return isgraph((unsigned char)cp);
86 matches(char **words, size_t len, struct line *l)
91 for (i = 0; i < len; ++i) {
94 if (strcasestr(l->line, words[i]) != NULL)
97 strcasestr(l->alt, words[i]) != NULL)
108 * Recompute the visible completions. If add is 1, don't consider the
109 * ones already hidden.
112 recompute_completions(int add)
114 static char buf[GEMINI_URL_LEN];
116 char *input, **ap, *words[10];
122 if (in_minibuffer != MB_COMPREAD)
125 if (!ministate.editing)
126 text = hist_cur(ministate.hist);
128 text = ministate.buf;
130 strlcpy(buf, text, sizeof(buf));
133 /* tokenize the input */
134 for (ap = words; ap < words + nitems(words) &&
135 (*ap = strsep(&input, " ")) != NULL;) {
140 b = &ministate.compl.buffer;
141 TAILQ_FOREACH(l, &b->head, lines) {
142 l->type = LINE_COMPL;
143 if (add && l->flags & L_HIDDEN)
145 if (matches(words, len, l)) {
146 if (l->flags & L_HIDDEN)
148 l->flags &= ~L_HIDDEN;
150 if (!(l->flags & L_HIDDEN))
152 l->flags |= L_HIDDEN;
156 if (b->current_line == NULL)
157 b->current_line = TAILQ_FIRST(&b->vhead);
158 b->current_line = adjust_line(b->current_line, b);
159 vl = b->current_line;
160 if (ministate.compl.must_select && vl != NULL)
161 vl->parent->type = LINE_COMPL_CURRENT;
165 minibuffer_insert_current_candidate(void)
169 vl = ministate.compl.buffer.current_line;
170 if (vl == NULL || vl->parent->flags & L_HIDDEN)
173 minibuffer_taint_hist();
174 strlcpy(ministate.buf, vl->parent->line, sizeof(ministate.buf));
175 ministate.buffer.point_offset = strlen(ministate.buf);
176 ministate.vline.len = strlen(ministate.buf);
182 minibuffer_metadata(void)
186 vl = ministate.compl.buffer.current_line;
188 if (vl == NULL || vl->parent->flags & L_HIDDEN)
191 return vl->parent->data;
195 minibuffer_compl_text(void)
199 if (!ministate.editing)
200 return hist_cur(ministate.hist);
202 vl = ministate.compl.buffer.current_line;
203 if (vl == NULL || vl->parent->flags & L_HIDDEN ||
204 vl->parent->type == LINE_COMPL || vl->parent->line == NULL)
205 return ministate.buf;
206 return vl->parent->line;
210 minibuffer_hist_save_entry(void)
212 if (ministate.hist == NULL)
215 hist_append(ministate.hist, minibuffer_compl_text());
219 * taint the minibuffer cache: if we're currently showing a history
220 * element, copy that to the current buf and reset the "history
224 minibuffer_taint_hist(void)
226 if (ministate.editing)
229 ministate.editing = 1;
230 strlcpy(ministate.buf, hist_cur(ministate.hist),
231 sizeof(ministate.buf));
232 ministate.buffer.point_offset = 0;
233 ministate.buffer.current_line->parent->line = ministate.buf;
237 minibuffer_confirm(void)
239 if (!in_minibuffer || ministate.donefn == NULL)
242 minibuffer_taint_hist();
243 ministate.donefn(minibuffer_compl_text());
247 minibuffer_self_insert(void)
249 char *c, tmp[5] = {0};
252 minibuffer_taint_hist();
257 len = grapheme_encode_utf8(thiskey.cp, tmp, sizeof(tmp));
259 c = ministate.buffer.current_line->parent->line
260 + ministate.buffer.current_line->from
261 + ministate.buffer.point_offset;
262 if (c + len > ministate.buf + sizeof(ministate.buf) - 1)
265 memmove(c + len, c, strlen(c)+1);
267 ministate.buffer.point_offset += len;
268 ministate.vline.len += len;
270 recompute_completions(1);
274 sensible_self_insert(void)
277 (!codepoint_isgraph(thiskey.key) && thiskey.key != ' ')) {
278 global_key_unbound();
282 minibuffer_self_insert();
286 eecmd_select(const char *t)
290 for (cmd = cmds; cmd->cmd != NULL; ++cmd) {
291 if (!strcmp(cmd->cmd, t)) {
292 minibuffer_hist_save_entry();
294 cmd->fn(current_buffer());
303 ir_select_gemini(const char *text)
305 static struct iri iri;
307 struct tab *tab = current_tab;
309 minibuffer_hist_save_entry();
311 if (iri_parse(NULL, hist_cur(tab->hist), &iri) == -1)
313 if (iri_setquery(&iri, text) == -1)
315 if (iri_unparse(&iri, buf, sizeof(buf)) == -1)
319 load_url_in_tab(tab, buf, NULL, LU_MODE_NOCACHE);
323 message("Failed to select URL.");
327 ir_select_reply(const char *text)
329 static struct iri iri;
330 char buf[1025] = {0};
331 struct tab *tab = current_tab;
333 minibuffer_hist_save_entry();
335 /* a bit ugly but... */
336 iri_parse(NULL, tab->last_input_url, &iri);
337 iri_setquery(&iri, text);
338 iri_unparse(&iri, buf, sizeof(buf));
341 load_url_in_tab(tab, buf, NULL, LU_MODE_NOCACHE);
345 ir_select_gopher(const char *text)
347 minibuffer_hist_save_entry();
348 gopher_send_search_req(current_tab, text);
353 lu_select(const char *text)
355 char url[GEMINI_URL_LEN+1];
357 minibuffer_hist_save_entry();
358 humanify_url(text, hist_cur(current_tab->hist), url, sizeof(url));
361 load_url_in_tab(current_tab, url, NULL, LU_MODE_NOCACHE);
365 bp_select(const char *url)
368 if (bookmark_page(url) == -1)
369 message("failed to bookmark page: %s",
372 message("Bookmarked");
379 ts_select(const char *text)
383 if ((tab = minibuffer_metadata()) == NULL) {
384 message("No tab selected");
393 ls_select(const char *text)
397 if ((l = minibuffer_metadata()) == NULL) {
398 message("No link selected");
403 load_url_in_tab(current_tab, l->alt, NULL, LU_MODE_NOCACHE);
407 jump_to_line(struct line *l)
410 struct buffer *buffer;
412 buffer = current_buffer();
414 TAILQ_FOREACH(vl, &buffer->vhead, vlines) {
420 message("Ops, %s error! Please report to %s",
421 __func__, PACKAGE_BUGREPORT);
423 buffer->top_line = vl;
424 buffer->current_line = vl;
429 swiper_select(const char *text)
433 if ((l = minibuffer_metadata()) == NULL) {
434 message("No line selected");
443 toc_select(const char *text)
447 if ((l = minibuffer_metadata()) == NULL) {
448 message("No line selected");
457 save_cert_for_site_cb(int r, void *data)
459 struct tab *tab = data;
461 cert_save_for(tab->client_cert, &tab->iri, r);
465 uc_select(const char *name)
467 if ((current_tab->client_cert = ccert(name)) == NULL) {
468 message("Certificate %s not found", name);
474 yornp("Remember for future sessions too?", save_cert_for_site_cb,
479 search_select(const char *text)
481 static struct iri iri;
482 static char buf[1025];
484 /* a bit ugly but... */
485 if (iri_parse(NULL, default_search_engine, &iri) == -1) {
486 message("default-search-engine is a malformed IRI.");
490 iri_setquery(&iri, text);
491 iri_unparse(&iri, buf, sizeof(buf));
494 load_url_in_tab(current_tab, buf, NULL, LU_MODE_NOCACHE);
498 yornp_self_insert(void)
500 if (thiskey.key != 'y' && thiskey.key != 'n') {
501 message("Please answer y or n");
506 yornp_cb(thiskey.key == 'y', yornp_data);
513 yornp_cb(0, yornp_data);
517 read_self_insert(void)
519 if (thiskey.meta || !codepoint_isgraph(thiskey.cp)) {
520 global_key_unbound();
524 minibuffer_self_insert();
531 read_cb(NULL, read_data);
535 read_select(const char *text)
538 minibuffer_hist_save_entry();
539 read_cb(text, read_data);
543 * TODO: we should collect this asynchronously...
546 populate_compl_buffer(complfn *fn, void *data)
548 const char *s, *descr;
553 b = &ministate.compl.buffer;
557 while ((s = fn(&data, &linedata, &descr)) != NULL) {
558 l = xcalloc(1, sizeof(*l));
560 l->type = LINE_COMPL;
562 l->alt = (char*)descr;
563 l->line = xstrdup(s);
565 TAILQ_INSERT_TAIL(&b->head, l, lines);
571 if ((l = TAILQ_FIRST(&b->head)) != NULL &&
572 ministate.compl.must_select)
573 l->type = LINE_COMPL_CURRENT;
577 enter_minibuffer(struct minibuffer *minibuffer, const char *fmt, ...)
582 vsnprintf(ministate.prompt, sizeof(ministate.prompt), fmt, ap);
585 ministate.compl.must_select = minibuffer->must_select;
586 ministate.compl.fn = minibuffer->complfn;
587 ministate.compl.data = minibuffer->compldata;
589 in_minibuffer = minibuffer->complfn == NULL ? MB_READ : MB_COMPREAD;
590 if (in_minibuffer == MB_COMPREAD) {
591 populate_compl_buffer(minibuffer->complfn,
592 minibuffer->compldata);
593 ui_schedule_redraw();
596 base_map = &minibuffer_map;
597 current_map = &minibuffer_map;
599 base_map->unhandled_input = minibuffer->self_insert;
601 ministate.donefn = minibuffer->done;
602 ministate.abortfn = minibuffer->abort;
603 if (ministate.abortfn == NULL)
604 ministate.abortfn = exit_minibuffer;
606 if (minibuffer->input) {
607 strlcpy(ministate.buf, minibuffer->input,
608 sizeof(ministate.buf));
609 ministate.buffer.point_offset = strlen(ministate.buf);
610 ministate.vline.len = strlen(ministate.buf);
612 ministate.buf[0] = '\0';
613 ministate.buffer.point_offset = 0;
614 ministate.vline.len = 0;
617 ministate.buffer.current_line = &ministate.vline;
618 ministate.buffer.current_line->parent->line = ministate.buf;
620 ministate.editing = 1;
621 ministate.hist = minibuffer->history;
623 hist_seek_start(ministate.hist);
627 exit_minibuffer(void)
629 if (in_minibuffer == MB_COMPREAD) {
630 erase_buffer(&ministate.compl.buffer);
631 ui_schedule_redraw();
635 base_map = &global_map;
636 current_map = &global_map;
640 yornp(const char *prompt, void (*fn)(int, void*), void *data)
642 struct minibuffer m = {
643 .self_insert = yornp_self_insert,
644 .abort = yornp_abort,
654 enter_minibuffer(&m, "%s (y or n)", prompt);
658 minibuffer_read(const char *prompt, void (*fn)(const char *, struct tab *),
659 struct tab *data, const char *input)
661 struct minibuffer m = {
662 .self_insert = read_self_insert,
665 .history = read_history,
674 enter_minibuffer(&m, "%s: ", prompt);
678 handle_clear_echoarea(int fd, int ev, void *d)
680 free(ministate.curmesg);
681 ministate.curmesg = NULL;
683 ui_after_message_hook();
687 vmessage(const char *fmt, va_list ap)
689 ev_timer_cancel(clechotimer);
692 free(ministate.curmesg);
693 ministate.curmesg = NULL;
696 clechotimer = ev_timer(&clechotv, handle_clear_echoarea,
699 /* TODO: what to do if the allocation fails here? */
700 if (vasprintf(&ministate.curmesg, fmt, ap) == -1)
701 ministate.curmesg = NULL;
704 ui_after_message_hook();
708 message(const char *fmt, ...)
718 minibuffer_init(void)
720 if ((eecmd_history = hist_new(HIST_WRAP)) == NULL ||
721 (ir_history = hist_new(HIST_WRAP)) == NULL ||
722 (lu_history = hist_new(HIST_WRAP)) == NULL ||
723 (read_history = hist_new(HIST_WRAP)) == NULL)
726 TAILQ_INIT(&ministate.compl.buffer.head);
727 TAILQ_INIT(&ministate.compl.buffer.vhead);
729 ministate.line.type = LINE_TEXT;
730 ministate.vline.parent = &ministate.line;
731 ministate.buffer.mode = "*minibuffer*";
732 ministate.buffer.current_line = &ministate.vline;