2 * Copyright Neil Brown ©2015-2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
5 * Minor mode for emacs incremental search.
7 * emacs search attaches emacs-search-highlight to the document stack,
8 * then adds a popup search box and attaches emacs-search over it.
9 * We send a popup-get-target message to collect the target pane.
10 * We have a stack of "string,pos" for repeated search requests.
11 * We capture "Replace" to repeat search.
12 * We send "Move-View-Pos" to target to get it to refresh to a new location.
14 * :C-S - if we have a match, save end of match as new start
15 * :Backspace - is search string is same as saved start, pop
16 * otherwise remove whatever was last entered, which
17 * must be multiple chars if :Cw was used.
18 * :C-W - collect word from target and add to search string
19 * :C-C - collect char from target and add to search string.
20 * :C-R - search backwards.. tricky.
21 * :A-c - toggle case sensitivity (currently invisible)
27 #define PANE_DATA_TYPE struct es_info
28 #define PANE_DATA_TYPE_2 struct highlight_info
29 #define PANE_DATA_PTR_TYPE_3 struct pane *
36 struct mark *m safe; /* Start of search */
37 unsigned int len; /* current length of search string */
42 struct mark *start safe; /* where searching starts */
43 struct mark *end safe; /* where last success ended */
45 struct pane *replace_pane;
53 struct highlight_info {
54 int view, replace_view;
57 struct mark *start, *end, *match, *rpos, *oldpoint;
58 struct pane *popup, *replace_popup;
60 #include "core-pane.h"
62 static struct map *es_map, *er_map;
63 DEF_LOOKUP_CMD(search_handle, es_map);
64 DEF_LOOKUP_CMD(replace_handle, er_map);
65 static const char must_quote[] = ".|*+?{()?^$\\[";
67 DEF_CMD(search_forward)
69 struct es_info *esi = ci->home->data;
72 struct mark *newstart;
78 suffix = ksuffix(ci, "K:C-");
80 esi->backwards = suffix[0] == 'R';
82 if (esi->s && mark_same(esi->s->m, esi->end)) {
83 if (esi->s->case_sensitive == esi->case_sensitive &&
84 esi->s->backwards == esi->backwards)
85 /* already pushed and didn't find anything new */
87 esi->s->case_sensitive = esi->case_sensitive;
88 esi->s->backwards = esi->backwards;
90 str = call_ret(str, "doc:get-str", ci->focus);
93 ss = call_ret(strsave, "history:get-last", ci->focus);
95 call("Replace", ci->home, 1, NULL, ss);
101 s = calloc(1, sizeof(*s));
103 s->len = strlen(str);
104 s->wrapped = esi->wrapped;
105 s->case_sensitive = esi->case_sensitive;
112 newstart = mark_dup(esi->end);
113 if (esi->matched == 1)
114 /* zero length match */
115 if (doc_move(esi->target, newstart,
116 esi->backwards ? -1 : 1) == WEOF) {
122 newstart = mark_dup(s->m);
124 call("doc:file", esi->target, esi->backwards ? 1 : -1,
127 esi->start = newstart;
128 /* Trigger notification so isearch watcher searches again */
129 call("Replace", ci->home, 1, NULL, "");
131 if (!esi->matched && strcmp(ci->key, "search:again") == 0)
136 DEF_CMD(search_retreat)
138 struct es_info *esi = ci->home->data;
146 str = call_ret(str, "doc:get-str", ci->focus);
149 if (strlen(str) > esi->s->len) {
156 mark_free(esi->start);
158 esi->wrapped = s->wrapped;
160 /* Trigger notification so isearch watcher searches again */
161 call("Replace", ci->home, 1, NULL, "");
165 if (doc_following(ci->focus, NULL) != WEOF)
166 /* Not at end-of-buffer, just delete one char */
169 mk = call_ret(mark, "doc:point", ci->focus);
174 if (doc_prev(ci->focus, mk) == WEOF)
176 attr = call_ret(strsave, "doc:get-attr", ci->focus, 0, mk, "auto");
177 } while (attr && strcmp(attr, "1") == 0);
179 call("Replace", ci->focus, 1, mk);
186 struct es_info *esi = ci->home->data;
192 struct mark *addpos = mark_dup(esi->end);
193 char *str = call_ret(strsave, "doc:get-str", ci->home);
202 /* Move to end of match */
203 call("text-search", esi->target,
204 !esi->case_sensitive, addpos, str);
205 m = mark_dup(addpos);
206 if (strcmp(ci->key, "K:C-W")==0)
207 call("doc:word", esi->target, 1, m);
209 call("Move-Char", esi->target, 1, m);
211 /* Move cursor to end of search string */
212 call("doc:file", ci->focus, 1);
214 && mark_ordered_not_same(addpos, m)) {
218 wch = doc_next(esi->target, addpos);
225 } else if (strchr(must_quote, wch)) {
229 call("Replace", ci->focus, 1, NULL, "\\",
234 call("Replace", ci->focus, 1, NULL, b,
242 DEF_CMD(search_insert_quoted)
244 const char *suffix = ksuffix(ci, "doc:char-");
246 if (strchr(must_quote, suffix[0]) == NULL)
248 patn = call_ret(strsave, "doc:get-str", ci->focus);
250 char *open = strrchr(patn, '[');
252 (open == patn || open[-1] != '\\') &&
253 (open[1] == 0 || strchr(open+2, ']') == NULL))
254 /* There is an '[' that hasn't been closed, so don't
259 call("Replace", ci->focus, 1, NULL, "\\");
260 call("Replace", ci->focus, 1, NULL, suffix,
266 DEF_CMD(search_insert_meta)
268 /* Insert a regexp meta char.
269 * If it is 'open', insert the 'close' too.
270 * If it is 'close', skip over a close instead if possible
273 const char *brackets = "{}()[]";
274 const char *k = ksuffix(ci, "K:A-");
276 if (strchr(must_quote, *k) == NULL || !ci->mark)
278 bracket = strchr(brackets, *k);
280 call("Replace", ci->focus, 1, NULL, k);
281 } else if ((bracket - brackets) % 2) {
283 if (doc_following(ci->focus, ci->mark) == (wint_t)k[0])
284 call("Move-Char", ci->focus, 1);
286 call("Replace", ci->focus, 1, NULL, k);
290 strncpy(b, bracket, 2);
292 call("Replace", ci->focus, 1, NULL, b);
293 call("Move-Char", ci->focus, -1);
299 DEF_CMD_CLOSED(search_close)
301 struct es_info *esi = ci->home->data;
304 call("search:highlight", esi->target);
306 esi->end = safe_cast NULL;
307 mark_free(esi->start);
309 struct stk *n = esi->s;
317 DEF_CMD(search_again)
319 /* document has changed, retry search */
320 struct es_info *esi = ci->home->data;
330 call("search:highlight", esi->target);
333 m = mark_dup(esi->start);
334 str = call_ret(str, "doc:get-str", ci->home);
335 if (str == NULL || strlen(str) == 0)
336 /* empty string always matches */
338 else if (esi->backwards && doc_prev(esi->target, m) == WEOF)
341 ret = call("text-search", esi->target,
342 !esi->case_sensitive, m, str, esi->backwards);
345 pfx = "Search (unavailable): ";
346 else if (ret == Efail) {
347 call("search:highlight", esi->target, 0,NULL, str,
348 !esi->case_sensitive);
349 pfx = "Failed Search: ";
350 } else if (ret == Einval) {
351 pfx = "Search (incomplete): ";
352 } else if (ret < 0) {
353 pfx = "Search (sys-error): ";
356 mark_to_mark(esi->end, m);
357 if (esi->backwards) {
358 while (ret > 0 && doc_next(esi->target, m) != WEOF)
360 call("search:highlight", esi->target, len, esi->end, str,
361 !esi->case_sensitive, m);
363 while (ret > 0 && doc_prev(esi->target, m) != WEOF)
365 call("search:highlight", esi->target, len, m, str,
366 !esi->case_sensitive, esi->end);
368 esi->matched = len + 1;
369 pfx = esi->backwards ? "Reverse Search: ":"Search: ";
371 pfx = esi->backwards ? "Wrapped Reverse Search: ":"Wrapped Search: ";
374 for (p = ci->home; p != p->parent; p = p->parent) {
375 a = attr_find(p->attrs, "prefix");
378 if (strcmp(a, pfx) != 0)
379 attr_set_str(&p->attrs, "prefix", pfx);
389 /* need to advance the target view to 'start', leaving
392 struct es_info *esi = ci->home->data;
399 if (esi->replace_pane && strcmp(ci->key, "K:Enter") == 0) {
400 /* if there is a replace pane, switch to it instead of closing */
401 pane_take_focus(esi->replace_pane);
404 str = call_ret(str, "doc:get-str", ci->focus);
405 /* Move "mark" to last location, found */
406 call("Move-to", esi->target, 1);
407 mk = call_ret(mark2, "doc:point", esi->target);
409 attr_set_int(&mk->attrs, "selection:active", 0);
410 call("Move-to", esi->target, 0, esi->end, NULL, 1);
412 call("popup:close", safe_cast ci->focus->parent, 0, NULL, str);
417 DEF_CMD(search_escape)
419 return call("search:done", ci->focus);
424 struct es_info *esi = ci->home->data;
427 mark_clip(esi->start, ci->mark, ci->mark2, !!ci->num);
428 mark_clip(esi->end, ci->mark, ci->mark2, !!ci->num);
429 for (s = esi->s; s; s = s->next)
430 mark_clip(s->m, ci->mark, ci->mark2, !!ci->num);
434 DEF_CMD(search_recentre)
436 /* Send this command through to target, at current location */
437 struct es_info *esi = ci->home->data;
441 return call(ci->key, esi->target, ci->num, esi->end, NULL,
445 DEF_CMD(search_toggle_ci)
447 struct es_info *esi = ci->home->data;
449 /* If not at end of doc, fall through */
450 if (ci->mark && doc_following(ci->focus, ci->mark) != WEOF)
452 esi->case_sensitive = !esi->case_sensitive;
453 call("doc:notify:doc:replaced", ci->focus);
454 attr_set_str(&ci->home->attrs, "status-line",
455 esi->case_sensitive ? " Search: case sensitive " :
456 " Search: case insensitive ");
460 DEF_CMD(search_replace)
463 struct es_info *esi = ci->home->data;
465 if (esi->replace_pane) {
466 pane_take_focus(esi->replace_pane);
473 p = call_ret(pane, "PopupTile", ci->focus, 0, NULL, "P", 0, NULL,
477 attr_set_str(&p->attrs, "prompt", "Replacement");
478 attr_set_str(&p->attrs, "status-line", " Replacement ");
479 call("doc:set-name", p, 0, NULL, "Replacement");
481 p = pane_register_3(p, 0, &replace_handle.c, ci->focus);
484 p = call_ret(pane, "attach-history", p, 0, NULL, "*Replace History*");
485 esi->replace_pane = p;
487 pane_add_notify(ci->home, p, "Notify:Close");
488 home_call(esi->target, "highlight:set-popup", p, 1);
490 if (strcmp(ci->key, "K:A-%") == 0)
491 pane_take_focus(ci->focus);
497 DEF_CMD(search_notify_close)
499 struct es_info *esi = ci->home->data;
501 if (ci->focus == esi->replace_pane)
502 esi->replace_pane = NULL;
503 if (ci->focus == esi->target) {
505 //pane_close(ci->home);
512 struct es_info *esi = ci->home->data;
513 const char *new = ci->str;
515 int len = esi->matched - 1;
526 m = mark_dup(esi->end);
527 if (esi->backwards) {
528 while (len > 0 && doc_next(esi->target, m) != WEOF)
531 if (call("doc:replace", esi->target, 0, esi->end, new, 0, m) > 0) {
532 call("search:highlight-replace", esi->target,
533 strlen(new), esi->end, NULL, 0, m);
537 while (len > 0 && doc_prev(esi->target, m) != WEOF)
540 if (strchr(new, '\\')) {
541 char *Pattern = call_ret(strsave, "doc:get-str", ci->home);
542 struct command *ptn = call_ret(comm, "make-search",
544 RXLF_ANCHORED | RXLF_BACKTRACK,
548 call_comm("doc:content", esi->target, ptn, 0, m);
549 new2 = comm_call_ret(strsave, ptn, "interp",
550 esi->target, 0, NULL, new);
556 if (call("doc:replace", esi->target, 0, m, new, 0, esi->end) > 0) {
557 call("search:highlight-replace", esi->target,
558 strlen(new), m, NULL, 0, esi->end);
565 DEF_CMD(replace_request_next)
567 struct pane *sp = ci->home->data3;
570 new = call_ret(str, "doc:get-str", ci->focus);
571 if (call("search:replace", sp, 0, NULL, new) > 0) {
572 call("history:save", ci->focus, 0, NULL, new);
573 call("search:again", sp);
575 call("search:done", sp);
580 DEF_CMD(replace_request)
582 struct pane *sp = ci->home->data3;
585 new = call_ret(str, "doc:get-str", ci->focus);
586 if (call("search:replace", sp, 0, NULL, new) > 0)
587 call("history:save", ci->focus, 0, NULL, new);
594 struct pane *sp = ci->home->data3;
598 new = call_ret(str, "doc:get-str", ci->focus);
599 pane_set_time(ci->home);
600 while (call("search:replace", sp, 0, NULL, new) > 0 &&
601 call("search:again", sp) > 0 &&
602 !pane_too_long(ci->home, 2000))
605 call("history:save", ci->focus, 0, NULL, new);
611 DEF_CMD(replace_to_search)
613 struct pane *sp = ci->home->data3;
619 DEF_CMD(replace_forward)
621 struct pane *sp = ci->home->data3;
628 DEF_CMD(replace_undo)
633 DEF_CMD(replace_escape)
635 struct pane *sp = ci->home->data3;
637 return call("search:done", sp);
640 DEF_CMD(replace_prev)
642 struct pane *home = ci->home->data3;
643 struct es_info *esi = home->data;
646 call("search:step-replace", esi->target, -1);
650 DEF_CMD(replace_next)
652 struct pane *home = ci->home->data3;
653 struct es_info *esi = home->data;
656 call("search:step-replace", esi->target, 1);
660 static void emacs_search_init_map(void)
662 /* Keys for the 'search' pane */
663 es_map = key_alloc();
664 key_add(es_map, "K:C-S", &search_forward);
665 key_add(es_map, "search:again", &search_forward);
666 key_add(es_map, "K:Backspace", &search_retreat);
667 key_add(es_map, "K:C-W", &search_add);
668 key_add(es_map, "K:C-C", &search_add);
669 key_add(es_map, "K:C-R", &search_forward);
670 key_add(es_map, "Close", &search_close);
671 key_add(es_map, "K:Enter", &search_done);
672 key_add(es_map, "search:done", &search_done);
673 key_add(es_map, "doc:replaced", &search_again);
674 key_add(es_map, "Notify:clip", &search_clip);
675 key_add(es_map, "K:C-L", &search_recentre);
676 key_add_range(es_map, "doc:char- ", "doc:char-~", &search_insert_quoted);
677 key_add_range(es_map, "K:A- ", "K:A-~", &search_insert_meta);
678 key_add(es_map, "K:A-c", &search_toggle_ci);
679 key_add(es_map, "K:A-r", &search_replace);
680 key_add(es_map, "K:S:Tab", &search_replace);
681 key_add(es_map, "K:A-%", &search_replace);
682 key_add(es_map, "Cancel", &search_escape);
684 key_add(es_map, "search:replace", &do_replace);
685 key_add(es_map, "Notify:Close", &search_notify_close);
687 /* keys for the 'replace' pane */
688 er_map = key_alloc();
689 key_add(er_map, "K:Enter", &replace_request_next);
690 key_add(er_map, "K:A:Enter", &replace_request);
691 key_add(er_map, "K:S:Tab", &replace_to_search);
692 key_add(er_map, "K:A-!", &replace_all);
693 key_add(er_map, "K:C-S", &replace_forward);
694 key_add(er_map, "K:C-R", &replace_forward);
695 key_add(er_map, "K:C-L", &replace_forward);
696 key_add(er_map, "Cancel", &replace_escape);
697 key_add(er_map, "K:Up", &replace_prev);
698 key_add(er_map, "K:Down", &replace_next);
699 key_add(er_map, "doc:reundo", &replace_undo);
702 DEF_CMD(emacs_search)
704 struct pane *p, *target;
709 emacs_search_init_map();
710 target = call_ret(pane, "popup:get-target", ci->focus);
714 m = mark_at_point(target, NULL, MARK_POINT);
718 p = pane_register(ci->focus, 0, &search_handle.c);
722 esi->target = target;
725 esi->start = mark_dup(m);
730 esi->backwards = ci->num & 1;
732 call("doc:request:doc:replaced", p);
733 attr_set_str(&p->attrs, "status-line", " Search: case insensitive ");
734 comm_call(ci->comm2, "callback:attach", p);
735 pane_add_notify(p, esi->target, "Notify:Close");
743 static void do_searches(struct pane *p safe,
744 struct pane *owner safe, int view, char *patn,
746 struct mark *m, struct mark *end)
749 struct highlight_info *hi = owner->data2;
756 while ((ret = call("text-search", p, ci, m, patn, 0, end)) >= 1) {
757 struct mark *m2, *m3;
759 m2 = vmark_new(p, view, owner);
763 while (ret > 1 && doc_prev(p, m2) != WEOF)
765 m3 = vmark_matching(m2);
770 if (attr_find(m2->attrs, "render:search") == NULL) {
771 bool match = hi->match && mark_same(hi->match, m2);
772 attr_set_int(&m2->attrs,
773 match ? "render:search" : "render:search2",
775 call("view:changed", p, 0, m2, NULL, 0, m);
776 m2 = vmark_new(p, view, owner);
779 attr_set_int(&m2->attrs,
780 match ? "render:search-end"
781 : "render:search2-end",
786 if (len == 0 || mark_ordered_or_same(m, start))
787 /* Need to move forward, or we'll just match here again*/
796 static void queue_highlight_refresh(struct pane *p safe);
798 DEF_CMD(emacs_search_highlight)
800 /* from 'mark' for 'num' chars to 'mark2' there is a match for 'str',
801 * or else there are no matches (num==0).
802 * Here we remove any existing highlighting and highlight
803 * just the match. A subsequent call to emacs_search_reposition
804 * will highlight other near-by matches.
806 struct mark *m, *start;
807 struct highlight_info *hi = ci->home->data2;
812 while ((start = vmark_first(ci->focus, hi->view, ci->home)) != NULL)
817 hi->patn = strdup(ci->str);
821 mark_free(hi->match);
825 call("Move-to", ci->focus, 0, hi->oldpoint);
827 mark_free(hi->oldpoint);
832 if (ci->mark && ci->num >= 0 && ci->str) {
833 m = vmark_new(ci->focus, hi->view, ci->home);
836 mark_to_mark(m, ci->mark);
837 attr_set_int(&m->attrs, "render:search", ci->num);
838 call("Move-View-Pos", ci->focus, 0, m);
839 hi->match = mark_dup(ci->mark);
841 (m = vmark_new(ci->focus, hi->view, ci->home)) != NULL) {
842 mark_to_mark(m, ci->mark2);
843 attr_set_int(&m->attrs, "render:search-end", 0);
845 } else if (ci->str) {
846 /* No destination to move to, so just refresh whatever
849 queue_highlight_refresh(ci->focus);
851 call("view:changed", ci->focus);
852 call("render:request:reposition", ci->focus);
856 DEF_CMD(emacs_replace_highlight)
858 /* from 'mark' for 'num' chars to 'mark2' there is a recent
859 * replacement in a search/replace.
860 * The existing render:search{-end} marks which are near mark2
861 * need to be discarded, and new "render:replacement" need to
865 struct highlight_info *hi = ci->home->data2;
867 if (hi->replace_view < 0 || !hi->replace_popup)
870 if (!ci->mark || !ci->mark2)
873 while ((m = vmark_at_or_before(ci->focus, ci->mark2,
874 hi->view, ci->home)) != NULL &&
875 (attr_find_int(m->attrs, "render:search") >= 0 ||
876 attr_find_int(m->attrs, "render:search-end") >= 0))
878 m = vmark_new(ci->focus, hi->replace_view, ci->home);
880 mark_to_mark(m, ci->mark);
881 attr_set_int(&m->attrs, "render:replacement", ci->num);
883 m = vmark_new(ci->focus, hi->replace_view, ci->home);
885 mark_to_mark(m, ci->mark2);
886 attr_set_int(&m->attrs, "render:replacement-end", 0);
888 call("view:changed", ci->focus);
892 DEF_CMD(emacs_hl_attrs)
894 struct highlight_info *hi = ci->home->data2;
901 if (strcmp(ci->str, "render:search") == 0) {
902 /* Current search match - "220" is a priority */
903 if (hi->view >= 0 && ci->mark && ci->mark->viewnum == hi->view) {
904 int len = atoi(ci->str2) ?: 1;
905 return comm_call(ci->comm2, "attr:callback", ci->focus, len,
906 ci->mark, "fg:red,inverse,focus,vis-nl", 220);
909 if (strcmp(ci->str, "render:search2") == 0) {
910 /* alternate matches in current view */
911 if (hi->view >= 0 && ci->mark && ci->mark->viewnum == hi->view) {
912 int len = atoi(ci->str2) ?: 1;
913 return comm_call(ci->comm2, "attr:callback", ci->focus, len,
914 ci->mark, "fg:blue,inverse,vis-nl", 220);
917 if (strcmp(ci->str, "render:replacement") == 0) {
918 /* Replacement - "220" is a priority */
919 if (hi->replace_view >= 0 && ci->mark &&
920 ci->mark->viewnum == hi->replace_view) {
921 int len = atoi(ci->str2) ?: 1;
922 return comm_call(ci->comm2, "attr:callback", ci->focus, len,
923 ci->mark, "fg:green-40,inverse,vis-nl", 220);
926 if (strcmp(ci->str, "start-of-line") == 0 && ci->mark && hi->view >= 0) {
927 struct mark *m = vmark_at_or_before(ci->focus, ci->mark, hi->view, ci->home);
928 if (m && mark_same(m, ci->mark))
930 if (m && attr_find_int(m->attrs, "render:search") > 0)
931 return comm_call(ci->comm2, "attr:callback", ci->focus, 0,
932 ci->mark, "fg:red,inverse,vis-nl", 220);
933 if (m && attr_find_int(m->attrs, "render:search2") > 0)
934 return comm_call(ci->comm2, "attr:callback", ci->focus, 0,
935 ci->mark, "fg:blue,inverse,vis-nl", 220);
937 if (strcmp(ci->str, "render:search-end") ==0) {
938 /* Here endeth the match */
939 return comm_call(ci->comm2, "attr:callback", ci->focus, -1,
940 ci->mark, "fg:red,inverse,vis-nl", 220);
942 if (strcmp(ci->str, "render:search2-end") ==0) {
943 /* Here endeth the match */
944 return comm_call(ci->comm2, "attr:callback", ci->focus, -1,
945 ci->mark, "fg:blue,inverse,vis-nl", 220);
947 if (strcmp(ci->str, "render:replacement-end") ==0) {
948 /* Here endeth the replacement */
949 return comm_call(ci->comm2, "attr:callback", ci->focus, -1,
950 ci->mark, "fg:green-40,inverse,vis-nl", 220);
955 DEF_CMD(highlight_draw)
957 struct highlight_info *hi = ci->home->data2;
958 struct pane *pp = hi->popup;
959 struct pane *pp2 = hi->replace_popup;
962 if (!ci->str2 || !strstr(ci->str2, ",focus") || !pp)
965 /* here is where the user will be looking, make sure
966 * the popup doesn't obscure it.
969 xy = pane_mapxy(ci->focus, ci->home, ci->x, ci->y, False);
970 while (pp->parent != pp && pp->z == 0)
972 while (pp2 && pp2->parent != pp2 && pp2->z == 0)
975 /* currently TL, should we move it back */
978 (!pp2 || xy.y > pp2->y + pp2->h)))
979 call("popup:style", hi->popup, 0, NULL, "TR2");
981 /* currently TR, should we move it out of way */
984 (pp2 && xy.y <= pp2->y + pp2->h)))
985 call("popup:style", hi->popup, 0, NULL, "TL2");
990 DEF_CMD(emacs_search_reposition_delayed)
992 struct highlight_info *hi = ci->home->data2;
993 struct mark *start = hi->start;
994 struct mark *end = hi->end;
995 struct mark *vstart, *vend;
996 char *patn = hi->patn;
1001 vstart = vmark_first(ci->focus, hi->view, ci->home);
1002 vend = vmark_last(ci->focus, hi->view, ci->home);
1003 if (vstart == NULL || start->seq < vstart->seq) {
1004 /* search from 'start' to first match or 'end' */
1005 do_searches(ci->focus, ci->home, hi->view, patn, hi->ci,
1006 start, vstart ?: end);
1008 do_searches(ci->focus, ci->home, hi->view, patn, hi->ci,
1010 } else if (vend && end->seq > vend->seq) {
1011 /* search from last match to end */
1012 do_searches(ci->focus, ci->home, hi->view, patn, hi->ci,
1018 static void queue_highlight_refresh(struct pane *p safe)
1020 call_comm("event:free", p, &emacs_search_reposition_delayed);
1021 call_comm("event:timer", p, &emacs_search_reposition_delayed,
1022 edlib_testing(p) ? 50 : 500);
1025 DEF_CMD(emacs_search_reposition)
1028 * Delete any matches that are no longer visible.
1029 * Then record new end-points and schedule an update shortly
1030 * to find any matches in the new range. If there are multiple
1031 * calls to this in quick successes (e.g. when scrolling), the
1032 * delayed update won't happen until a suitable time after the last
1035 struct highlight_info *hi = ci->home->data2;
1036 struct mark *start = ci->mark;
1037 struct mark *end = ci->mark2;
1040 if (hi->view < 0 || hi->patn == NULL || !start || !end || !hi->popup)
1041 return Efallthrough;
1043 while ((m = vmark_first(ci->focus, hi->view, ci->home)) != NULL &&
1044 mark_ordered_not_same(m, start))
1047 while ((m = vmark_last(ci->focus, hi->view, ci->home)) != NULL &&
1048 mark_ordered_not_same(end, m))
1051 mark_free(hi->start);
1053 hi->start = mark_dup(start);
1054 hi->end = mark_dup(end);
1056 queue_highlight_refresh(ci->home);
1057 return Efallthrough;
1060 DEF_CMD_CLOSED(emacs_highlight_close)
1062 /* ci->focus is being closed */
1063 struct highlight_info *hi = ci->home->data2;
1066 mark_free(hi->start);
1068 mark_free(hi->match);
1069 mark_free(hi->rpos);
1070 mark_free(hi->oldpoint);
1075 hi->oldpoint = NULL;
1079 static void free_marks(struct pane *home safe)
1081 struct highlight_info *hi = home->data2;
1084 while ((m = vmark_first(home, hi->view, home)) != NULL)
1086 while ((m = vmark_first(home, hi->replace_view, home)) != NULL)
1090 DEF_CMD(emacs_search_done)
1092 struct highlight_info *hi = ci->home->data2;
1094 if (ci->str && ci->str[0])
1095 call("history:save", ci->focus, 0, NULL, ci->str);
1098 call("Move-to", ci->focus, 0, hi->oldpoint);
1099 mark_free(hi->rpos);
1100 mark_free(hi->oldpoint);
1102 hi->oldpoint = NULL;
1106 hi->replace_popup = NULL;
1107 free_marks(ci->home);
1111 DEF_CMD(emacs_highlight_abort)
1113 struct highlight_info *hi = ci->home->data2;
1116 p = hi->replace_popup;
1117 hi->replace_popup = NULL;
1119 call("popup:close", p, 0, NULL, "");
1123 call("popup:close", p, 0, NULL, "");
1124 free_marks(ci->home);
1126 return Efallthrough;
1129 DEF_CMD(emacs_highlight_clip)
1131 struct highlight_info *hi = ci->home->data2;
1133 marks_clip(ci->home, ci->mark, ci->mark2,
1134 hi->view, ci->home, !!ci->num);
1135 marks_clip(ci->home, ci->mark, ci->mark2,
1136 hi->replace_view, ci->home, !!ci->num);
1137 return Efallthrough;
1140 DEF_CMD(emacs_highlight_set_popup)
1142 struct highlight_info *hi = ci->home->data2;
1145 hi->replace_popup = ci->focus;
1147 hi->popup = ci->focus;
1148 pane_add_notify(ci->home, ci->focus, "Notify:Close");
1152 DEF_CMD(emacs_highlight_close_notify)
1154 struct highlight_info *hi = ci->home->data2;
1156 if (ci->focus == hi->replace_popup)
1157 hi->replace_popup = NULL;
1158 if (ci->focus == hi->popup)
1163 DEF_CMD(emacs_highlight_reattach)
1165 comm_call(ci->comm2, "cb", ci->home);
1169 DEF_CMD(emacs_step_replace)
1171 struct highlight_info *hi = ci->home->data2;
1174 if (!hi->replace_view || !hi->match)
1176 if (ci->num > 0 && !hi->rpos)
1179 if (!hi->oldpoint) {
1180 m = call_ret(mark, "doc:point", ci->home);
1182 hi->oldpoint = mark_dup(m);
1184 hi->rpos = mark_dup(hi->match);
1188 m = vmark_at_or_before(ci->home, hi->rpos, hi->replace_view, ci->home);
1191 while (m && attr_find_int(m->attrs, "render:replacement") < 0)
1194 doc_prev(ci->home, hi->rpos);
1195 m = vmark_at_or_before(ci->home, hi->rpos, hi->replace_view, ci->home);
1196 while (m && attr_find_int(m->attrs, "render:replacement") < 0)
1200 mark_to_mark(hi->rpos, m);
1201 call("Move-View-Pos", ci->home, 0, m);
1202 call("Move-to", ci->home, 0, m);
1207 static struct map *hl_map;
1208 DEF_LOOKUP_CMD(highlight_handle, hl_map);
1210 static void emacs_highlight_init_map(void)
1215 key_add(m, "Search String", &emacs_search_done);
1216 key_add(m, "render:reposition", &emacs_search_reposition);
1217 key_add(m, "search:highlight", &emacs_search_highlight);
1218 key_add(m, "search:highlight-replace", &emacs_replace_highlight);
1219 key_add(m, "search:step-replace", &emacs_step_replace);
1220 key_add(m, "map-attr", &emacs_hl_attrs);
1221 key_add(m, "Draw:text", &highlight_draw);
1222 key_add(m, "Close", &emacs_highlight_close);
1223 key_add(m, "Abort", &emacs_highlight_abort);
1224 key_add(m, "Notify:clip", &emacs_highlight_clip);
1225 key_add(m, "highlight:set-popup", &emacs_highlight_set_popup);
1226 key_add(m, "attach-emacs-search-highlight", &emacs_highlight_reattach);
1227 key_add(m, "Notify:Close", &emacs_highlight_close_notify);
1231 DEF_CMD(emacs_search_attach_highlight)
1233 struct highlight_info *hi;
1237 emacs_highlight_init_map();
1239 p = pane_register_2(ci->focus, 0, &highlight_handle.c);
1244 hi->view = home_call(ci->focus, "doc:add-view", p) - 1;
1245 hi->replace_view = home_call(ci->focus, "doc:add-view", p) - 1;
1246 comm_call(ci->comm2, "callback:attach", p);
1251 void edlib_init(struct pane *ed safe)
1253 call_comm("global-set-command", ed, &emacs_search,
1254 0, NULL, "attach-emacs-search");
1255 call_comm("global-set-command", ed, &emacs_search_attach_highlight,
1256 0, NULL, "attach-emacs-search-highlight");