2 * Copyright Neil Brown ©2016-2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
7 * A history pane supports selection of lines from a separate
8 * document. The underlying document is assumed to be one line
9 * and this line can be replaced by various lines from the history document.
10 * When a line is replaced, if it had been modified, it is saved first so it
11 * can be revisited when "down" movement gets back to the end.
12 * When a selection is committed (:Enter), it is added to end of history.
13 * :A-p - replace current line with previous line from history, if there is one
14 * :A-n - replace current line with next line from history. If none, restore
16 * :A-r - enter incremental search, looking back
17 * :A-s - enter incremental search, looking forward
19 * In incremental search mode the current search string appears in the
21 * -glyph appends to the search string and repeats search from start
22 * in current direction
23 * :Backspace strips a glyph and repeats search
24 * :A-r - sets prev line as search start and repeats search
25 * :A-s - sets next line as search start and repeats.
26 * :Enter - drops out of search mode
27 * Anything else drops out of search mode and repeats the command as normal
29 * For each history document a number of "favourites" can be registered.
30 * These are accessed by moving "down" from the start point rather than "up"
31 * for previous history items.
40 #define PANE_DATA_TYPE struct history_info
58 #include "core-pane.h"
60 static struct map *history_map;
61 DEF_LOOKUP_CMD(history_handle, history_map);
63 static void free_si(struct si **sip safe)
67 while ((i = *sip) != NULL) {
69 if (i->prev == NULL || i->prev->line != i->line)
75 DEF_CMD_CLOSED(history_close)
77 struct history_info *hi = ci->home->data;
81 pane_close(hi->history);
88 DEF_CMD(history_notify_close)
90 struct history_info *hi = ci->home->data;
92 if (ci->focus == hi->history) {
93 /* The history document is going away!!! */
100 DEF_CMD(history_save)
102 struct history_info *hi = ci->home->data;
104 const char *line = ci->str;
107 if (!hi->history || !ci->str)
108 /* history document was destroyed */
110 /* Must never include a newline in a history entry! */
111 eol = strchr(ci->str, '\n');
113 line = strnsave(ci->home, ci->str, eol - ci->str);
115 prev = call_ret(strsave, "history:get-last", ci->focus);
116 if (prev && line && strcmp(prev, line) == 0)
119 call("doc:file", hi->history, 1);
120 call("Replace", hi->history, 1, NULL, line);
121 call("Replace", hi->history, 1, NULL, "\n", 1);
125 DEF_CMD(history_done)
127 history_save_func(ci);
131 DEF_CMD(history_notify_replace)
133 struct history_info *hi = ci->home->data;
140 static void recall_line(struct pane *p safe, struct pane *focus safe, int fore)
142 struct history_info *hi = p->data;
148 m = mark_at_point(hi->history, NULL, MARK_UNGROUPED);
149 call("doc:EOL", hi->history, 1, m, NULL, 1);
150 l = call_ret(str, "doc:get-str", hi->history, 0, NULL, NULL, 0, m);
153 /* No more history */
165 call("doc:EOL", focus, -1);
166 m = mark_at_point(focus, NULL, MARK_UNGROUPED);
167 call("doc:EOL", focus, 1, m);
171 hi->saved = call_ret(str, "doc:get-str", focus,
175 call("Replace", focus, 1, m, l);
183 DEF_CMD(history_move)
185 struct history_info *hi = ci->home->data;
186 const char *suffix = ksuffix(ci, "K:A-");
187 char attr[sizeof("doc:favourite-") + 12];
191 if (*suffix == 'p') {
192 if (hi->favourite > 0)
195 call("doc:EOL", hi->history, -2);
197 if (hi->favourite > 0)
199 else if (call("doc:EOL", hi->history, 1, NULL, NULL, 1) < 0)
202 while (hi->favourite > 0) {
205 snprintf(attr, sizeof(attr)-1, "doc:favourite-%d",
207 f = pane_attr_get(hi->history, attr);
212 call("doc:EOL", ci->focus, -1);
213 m = mark_at_point(ci->focus, NULL, MARK_UNGROUPED);
214 call("doc:EOL", ci->focus, 1, m);
215 call("Replace", ci->focus, 1, m, f);
219 recall_line(ci->home, ci->focus, *suffix == 'n');
223 DEF_CMD(history_add_favourite)
225 struct history_info *hi = ci->home->data;
226 char attr[sizeof("doc:favourite-") + 10];
232 l = call_ret(strsave, "doc:get-str", ci->focus);
235 for (f = 1; f < 100; f++) {
236 snprintf(attr, sizeof(attr)-1, "doc:favourite-%d", f);
237 if (pane_attr_get(hi->history, attr))
239 call("doc:set:", hi->history, 0, NULL, l, 0, NULL, attr);
240 call("Message:modal", ci->focus, 0, NULL, "Added as favourite");
246 DEF_CMD(history_attach)
248 struct history_info *hi;
249 struct pane *p, *history;
254 p = call_ret(pane, "docs:byname", ci->focus, 0, NULL, ci->str);
256 p = call_ret(pane, "doc:from-text", ci->focus, 0, NULL, ci->str);
260 history = call_ret(pane, "doc:attach-view", p, -1, NULL, "invisible");
263 call("doc:file", history, 1);
264 p = pane_register(ci->focus, 0, &history_handle.c);
266 pane_free(history); // FIXME should I send a close message?
270 hi->history = history;
271 buf_init(&hi->search);
272 buf_concat(&hi->search, "?0"); /* remaining chars are searched verbatim */
273 pane_add_notify(p, hi->history, "Notify:Close");
274 call("doc:request:doc:replaced", p);
275 return comm_call(ci->comm2, "callback:attach", p);
278 DEF_CMD(history_hlast)
280 struct history_info *hi = ci->home->data;
281 struct pane *doc = hi->history;
291 call("doc:set-ref", doc, 0, m);
292 call("doc:set", doc, 0, m, NULL, 1);
295 while (doc_prior(doc, m) != '\n')
296 if (doc_prev(doc,m) == WEOF)
298 rv = call_comm("doc:get-str", doc, ci->comm2, 0, m, NULL, 0, m2);
304 static bool has_name(struct pane *doc safe, struct mark *m safe,
305 const char *name safe)
309 a = call_ret(strsave, "doc:get-attr", doc, 0, m, "history:name");
310 return a && strcmp(a, name) == 0;
313 DEF_CMD(history_last)
315 /* Get last line from the given history document
316 * If ci->num > 1 get nth last line
317 * else if ci->str, get the line with given name
318 * If both set, assign str to the nth last line
319 * Names are assign with attribute "history:name"
324 const char *name = ci->str2;
327 doc = call_ret(pane, "docs:byname", ci->focus, 0, NULL, ci->str);
333 call("doc:set-ref", doc, 0, m);
334 call("doc:set", doc, 0, m, NULL, 1);
338 while (doc_prior(doc, m) != '\n')
339 if (doc_prev(doc,m) == WEOF)
341 } while (!mark_same(m, m2) && num > 1 &&
342 (name == NULL || has_name(doc, m, name)));
343 if (mark_same(m, m2) || num > 1)
346 if (num == 1 && name)
347 call("doc:set-attr", doc, 0, m, "history:name",
349 rv = call_comm("doc:get-str", doc, ci->comm2,
357 DEF_CMD(history_search)
359 struct history_info *hi = ci->home->data;
360 char *prompt, *prefix;
364 call("Mode:set-mode", ci->focus, 0, NULL, ":History-search");
365 buf_reinit(&hi->search);
366 buf_concat(&hi->search, "?0");
368 prompt = pane_attr_get(ci->focus, "prompt");
372 hi->prompt = strdup(prompt);
373 prefix = strconcat(ci->focus, prompt, " (): ");
374 attr_set_str(&ci->focus->attrs, "prefix", prefix);
375 call("view:changed", ci->focus);
377 hi->search_back = (toupper(ci->key[4]) == 'R');
381 static void update_search(struct pane *p safe, struct pane *focus safe,
384 struct history_info *hi = p->data;
395 i->line = mark_at_point(hi->history, NULL, MARK_UNGROUPED);
399 prefix = strconcat(focus, hi->prompt?:"?",
400 " (", buf_final(&hi->search)+2, "): ");
401 attr_set_str(&focus->attrs, "prefix", prefix);
402 call("view:changed", focus);
403 call("Mode:set-mode", focus, 0, NULL, ":History-search");
404 m = mark_at_point(hi->history, NULL, MARK_UNGROUPED);
405 /* Alway search backwards from the end-of-line of last match */
406 call("doc:EOL", hi->history, 1, m);
407 ret = call("text-search", hi->history, 1, m, buf_final(&hi->search),
414 /* Leave point at start-of-line */
415 call("doc:EOL", hi->history, -1, m);
416 call("Move-to", hi->history, 0, m);
418 recall_line(p, focus, 0);
421 DEF_CMD(history_search_again)
423 struct history_info *hi = ci->home->data;
426 k = ksuffix(ci, "K:History-search-");
428 int l = hi->search.len;
429 buf_concat(&hi->search, k);
430 update_search(ci->home, ci->focus, l);
435 DEF_CMD(history_search_retry);
437 DEF_CMD(history_search_bs)
439 struct history_info *hi = ci->home->data;
440 struct si *i = hi->prev;
442 if (!i || !hi->history) {
443 history_search_retry_func(ci);
447 call("Mode:set-mode", ci->focus, 0, NULL, ":History-search");
449 hi->search.len = i->i;
450 call("Move:to", hi->history, 0, i->line);
451 if (!i->prev || i->line != i->prev->line)
455 update_search(ci->home, ci->focus, -1);
459 DEF_CMD(history_search_repeat)
461 struct history_info *hi = ci->home->data;
462 const char *suffix = ksuffix(ci, "K:History-search:C-");
466 hi->search_back = toupper(*suffix) == 'R';
468 call("doc:EOL", hi->history, -2);
470 call("doc:EOL", hi->history, 1, NULL, NULL, 1);
472 update_search(ci->home, ci->focus, hi->search.len);
476 DEF_CMD(history_search_cancel)
478 struct history_info *hi = ci->home->data;
481 prefix = strconcat(ci->focus, hi->prompt?:"?", ": ");
482 attr_set_str(&ci->focus->attrs, "prefix", prefix);
483 call("view:changed", ci->focus);
487 REDEF_CMD(history_search_retry)
489 struct history_info *hi = ci->home->data;
491 char *k = strconcat(ci->home, "K", ksuffix(ci, "K:History-search"));
493 prefix = strconcat(ci->focus, hi->prompt?:"?", ": ");
494 attr_set_str(&ci->focus->attrs, "prefix", prefix);
495 call("view:changed", ci->focus);
496 return call(k, ci->focus, ci->num, ci->mark, ci->str,
497 ci->num2, ci->mark2, ci->str2);
502 const char *docname = ci->str;
503 const char *line = ci->str2;
506 if (!docname || !line || strchr(line, '\n'))
508 doc = call_ret(pane, "docs:byname", ci->focus, 0, NULL, ci->str);
510 doc = call_ret(pane, "doc:from-text", ci->focus,
513 call("global-multicall-doc:appeared-", doc);
517 call("doc:replace", doc, 1, NULL, line, 1);
518 call("doc:replace", doc, 1, NULL, "\n", 1);
522 void edlib_init(struct pane *ed safe)
524 call_comm("global-set-command", ed, &history_attach, 0, NULL, "attach-history");
525 call_comm("global-set-command", ed, &history_last, 0, NULL, "history:get-last");
526 call_comm("global-set-command", ed, &history_add, 0, NULL, "history:add");
531 history_map = key_alloc();
532 key_add(history_map, "Close", &history_close);
533 key_add(history_map, "Notify:Close", &history_notify_close);
534 key_add(history_map, "doc:replaced", &history_notify_replace);
535 key_add(history_map, "K:A-p", &history_move);
536 key_add(history_map, "K:A-n", &history_move);
537 key_add(history_map, "K:A-r", &history_search);
538 key_add(history_map, "K:A-s", &history_search);
539 key_add(history_map, "K:A-*", &history_add_favourite);
540 key_add_prefix(history_map, "K:History-search-", &history_search_again);
541 key_add_prefix(history_map, "K:History-search:",
542 &history_search_retry);
543 key_add(history_map, "K:History-search:Backspace",
545 key_add(history_map, "K:History-search:A-r",
546 &history_search_repeat);
547 key_add(history_map, "K:History-search:A-s",
548 &history_search_repeat);
549 key_add(history_map, "K:History-search:Enter",
550 &history_search_cancel);
551 key_add(history_map, "K:History-search:ESC",
552 &history_search_cancel);
553 key_add(history_map, "history:save", &history_save);
554 key_add(history_map, "history:get-last", &history_hlast);
555 key_add(history_map, "popup:close", &history_done);