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 * :C-r - enter incremental search, looking back
17 * :C-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 * :C-r - sets prev line as search start and repeats search
25 * :C-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
50 struct lookup_cmd handle;
53 static struct map *history_map;
54 DEF_LOOKUP_CMD(history_handle, history_map);
56 static void free_si(struct si **sip safe)
60 while ((i = *sip) != NULL) {
62 if (i->prev == NULL || i->prev->line != i->line)
68 DEF_CMD(history_close)
70 struct history_info *hi = ci->home->data;
74 pane_close(hi->history);
80 struct history_info *hi = ci->home->data;
86 /* handle was in 'hi' */
87 ci->home->handle = NULL;
91 DEF_CMD(history_notify_close)
93 struct history_info *hi = ci->home->data;
95 if (ci->focus == hi->history) {
96 /* The history document is going away!!! */
103 DEF_CMD(history_save)
105 struct history_info *hi = ci->home->data;
107 const char *line = ci->str;
110 if (!hi->history || !ci->str)
111 /* history document was destroyed */
113 /* Must never include a newline in a history entry! */
114 eol = strchr(ci->str, '\n');
116 line = strnsave(ci->home, ci->str, eol - ci->str);
118 prev = call_ret(strsave, "history:get-last", ci->focus);
119 if (prev && line && strcmp(prev, line) == 0)
122 call("doc:file", hi->history, 1);
123 call("Replace", hi->history, 1, NULL, line);
124 call("Replace", hi->history, 1, NULL, "\n", 1);
128 DEF_CMD(history_done)
130 history_save_func(ci);
134 DEF_CMD(history_notify_replace)
136 struct history_info *hi = ci->home->data;
143 static void recall_line(struct pane *p safe, struct pane *focus safe, int fore)
145 struct history_info *hi = p->data;
151 m = mark_at_point(hi->history, NULL, MARK_UNGROUPED);
152 call("doc:EOL", hi->history, 1, m, NULL, 1);
153 l = call_ret(str, "doc:get-str", hi->history, 0, NULL, NULL, 0, m);
156 /* No more history */
168 call("doc:EOL", focus, -1);
169 m = mark_at_point(focus, NULL, MARK_UNGROUPED);
170 call("doc:EOL", focus, 1, m);
174 hi->saved = call_ret(str, "doc:get-str", focus,
178 call("Replace", focus, 1, m, l);
186 DEF_CMD(history_move)
188 struct history_info *hi = ci->home->data;
189 const char *suffix = ksuffix(ci, "K:A-");
194 call("doc:EOL", hi->history, -2);
196 call("doc:EOL", hi->history, 1, NULL, NULL, 1);
198 recall_line(ci->home, ci->focus, *suffix == 'n');
202 DEF_CMD(history_attach)
204 struct history_info *hi;
207 if (!ci->str || !ci->str2)
211 hi->done_map = key_alloc();
212 hi->handle = history_handle;
213 hi->handle.m = &hi->done_map;
214 key_add_chain(hi->done_map, history_map);
215 key_add(hi->done_map, ci->str2, &history_done);
216 p = call_ret(pane, "docs:byname", ci->focus, 0, NULL, ci->str);
218 p = call_ret(pane, "doc:from-text", ci->focus, 0, NULL, ci->str);
224 hi->history = call_ret(pane, "doc:attach-view", p, -1, NULL, "invisible");
227 call("doc:file", hi->history, 1);
228 buf_init(&hi->search);
229 buf_concat(&hi->search, "?0"); /* remaining chars are searched verbatim */
230 p = pane_register(ci->focus, 0, &hi->handle.c, hi);
233 pane_add_notify(p, hi->history, "Notify:Close");
234 call("doc:request:doc:replaced", p);
235 return comm_call(ci->comm2, "callback:attach", p);
238 DEF_CMD(history_hlast)
240 struct history_info *hi = ci->home->data;
241 struct pane *doc = hi->history;
251 call("doc:set-ref", doc, 0, m);
252 call("doc:set", doc, 0, m, NULL, 1);
255 while (doc_prior(doc, m) != '\n')
256 if (doc_prev(doc,m) == WEOF)
258 rv = call_comm("doc:get-str", doc, ci->comm2, 0, m, NULL, 0, m2);
264 static bool has_name(struct pane *doc safe, struct mark *m safe,
265 const char *name safe)
269 a = call_ret(strsave, "doc:get-attr", doc, 0, m, "history:name");
270 return a && strcmp(a, name) == 0;
273 DEF_CMD(history_last)
275 /* Get last line from the given history document
276 * If ci->num > 1 get nth last line
277 * else if ci->str, get the line with given name
278 * If both set, assign str to the nth last line
279 * Names are assign with attribute "history:name"
284 const char *name = ci->str2;
287 doc = call_ret(pane, "docs:byname", ci->focus, 0, NULL, ci->str);
293 call("doc:set-ref", doc, 0, m);
294 call("doc:set", doc, 0, m, NULL, 1);
298 while (doc_prior(doc, m) != '\n')
299 if (doc_prev(doc,m) == WEOF)
301 } while (!mark_same(m, m2) && num > 1 &&
302 (name == NULL || has_name(doc, m, name)));
303 if (mark_same(m, m2) || num > 1)
306 if (num == 1 && name)
307 call("doc:set-attr", doc, 0, m, "history:name",
309 rv = call_comm("doc:get-str", doc, ci->comm2,
317 DEF_CMD(history_search)
319 struct history_info *hi = ci->home->data;
320 char *prompt, *prefix;
324 call("Mode:set-mode", ci->focus, 0, NULL, ":History-search");
325 buf_reinit(&hi->search);
326 buf_concat(&hi->search, "?0");
328 prompt = pane_attr_get(ci->focus, "prompt");
332 hi->prompt = strdup(prompt);
333 prefix = strconcat(ci->focus, prompt, " (): ");
334 attr_set_str(&ci->focus->attrs, "prefix", prefix);
335 call("view:changed", ci->focus);
337 hi->search_back = (ci->key[4] == 'R');
341 static void update_search(struct pane *p safe, struct pane *focus safe,
344 struct history_info *hi = p->data;
355 i->line = mark_at_point(hi->history, NULL, MARK_UNGROUPED);
359 prefix = strconcat(focus, hi->prompt?:"?",
360 " (", buf_final(&hi->search)+2, "): ");
361 attr_set_str(&focus->attrs, "prefix", prefix);
362 call("view:changed", focus);
363 call("Mode:set-mode", focus, 0, NULL, ":History-search");
364 m = mark_at_point(hi->history, NULL, MARK_UNGROUPED);
365 ret = call("text-search", hi->history, 1, m, buf_final(&hi->search),
372 call("doc:EOL", hi->history, -1, m);
373 call("Move-to", hi->history, 0, m);
375 recall_line(p, focus, 0);
378 DEF_CMD(history_search_again)
380 struct history_info *hi = ci->home->data;
383 k = ksuffix(ci, "K:History-search-");
385 int l = hi->search.len;
386 buf_concat(&hi->search, k);
387 update_search(ci->home, ci->focus, l);
392 DEF_CMD(history_search_retry);
394 DEF_CMD(history_search_bs)
396 struct history_info *hi = ci->home->data;
397 struct si *i = hi->prev;
399 if (!i || !hi->history) {
400 history_search_retry_func(ci);
404 call("Mode:set-mode", ci->focus, 0, NULL, ":History-search");
406 hi->search.len = i->i;
407 call("Move:to", hi->history, 0, i->line);
408 if (!i->prev || i->line != i->prev->line)
412 update_search(ci->home, ci->focus, -1);
416 DEF_CMD(history_search_repeat)
418 struct history_info *hi = ci->home->data;
419 const char *suffix = ksuffix(ci, "K:History-search:C-");
423 hi->search_back = *suffix == 'R';
425 call("doc:EOL", hi->history, -2);
427 call("doc:EOL", hi->history, 1, NULL, NULL, 1);
429 update_search(ci->home, ci->focus, hi->search.len);
433 DEF_CMD(history_search_cancel)
435 struct history_info *hi = ci->home->data;
438 prefix = strconcat(ci->focus, hi->prompt?:"?", ": ");
439 attr_set_str(&ci->focus->attrs, "prefix", prefix);
440 call("view:changed", ci->focus);
444 REDEF_CMD(history_search_retry)
446 struct history_info *hi = ci->home->data;
448 char *k = strconcat(ci->home, "K", ksuffix(ci, "K:History-search"));
450 prefix = strconcat(ci->focus, hi->prompt?:"?", ": ");
451 attr_set_str(&ci->focus->attrs, "prefix", prefix);
452 call("view:changed", ci->focus);
453 return call(k, ci->focus, ci->num, ci->mark, ci->str,
454 ci->num2, ci->mark2, ci->str2);
459 const char *docname = ci->str;
460 const char *line = ci->str2;
463 if (!docname || !line || strchr(line, '\n'))
465 doc = call_ret(pane, "docs:byname", ci->focus, 0, NULL, ci->str);
467 doc = call_ret(pane, "doc:from-text", ci->focus,
470 call("global-multicall-doc:appeared-", doc);
474 call("doc:replace", doc, 1, NULL, line, 1);
475 call("doc:replace", doc, 1, NULL, "\n", 1);
479 void edlib_init(struct pane *ed safe)
481 call_comm("global-set-command", ed, &history_attach, 0, NULL, "attach-history");
482 call_comm("global-set-command", ed, &history_last, 0, NULL, "history:get-last");
483 call_comm("global-set-command", ed, &history_add, 0, NULL, "history:add");
488 history_map = key_alloc();
489 key_add(history_map, "Close", &history_close);
490 key_add(history_map, "Free", &history_free);
491 key_add(history_map, "Notify:Close", &history_notify_close);
492 key_add(history_map, "doc:replaced", &history_notify_replace);
493 key_add(history_map, "K:A-p", &history_move);
494 key_add(history_map, "K:A-n", &history_move);
495 key_add(history_map, "K:C-R", &history_search);
496 key_add(history_map, "K:C-S", &history_search);
497 key_add_prefix(history_map, "K:History-search-", &history_search_again);
498 key_add_prefix(history_map, "K:History-search:",
499 &history_search_retry);
500 key_add(history_map, "K:History-search:Backspace",
502 key_add(history_map, "K:History-search:C-R",
503 &history_search_repeat);
504 key_add(history_map, "K:History-search:C-S",
505 &history_search_repeat);
506 key_add(history_map, "K:History-search:Enter",
507 &history_search_cancel);
508 key_add(history_map, "K:History-search:ESC",
509 &history_search_cancel);
510 key_add(history_map, "history:save", &history_save);
511 key_add(history_map, "history:get-last", &history_hlast);