]> git.neil.brown.name Git - edlib.git/blob - emacs-search.c
TODO: clean out done items.
[edlib.git] / emacs-search.c
1 /*
2  * Copyright Neil Brown ©2015-2023 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * Minor mode for emacs incremental search.
6  *
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.
13  * We capture:
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)
22  *
23  */
24
25 #include <stdlib.h>
26 #include <string.h>
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 *
30 #include "core.h"
31 #include "rexel.h"
32
33 struct es_info {
34         struct stk {
35                 struct stk *next;
36                 struct mark *m safe; /* Start of search */
37                 unsigned int len; /* current length of search string */
38                 short wrapped;
39                 short case_sensitive;
40                 short backwards;
41         } *s;
42         struct mark *start safe; /* where searching starts */
43         struct mark *end safe; /* where last success ended */
44         struct pane *target;
45         struct pane *replace_pane;
46         short matched;
47         short wrapped;
48         short backwards;
49         short case_sensitive;
50         short replaced;
51 };
52
53 struct highlight_info {
54         int view, replace_view;
55         char *patn;
56         int ci;
57         struct mark *start, *end, *match, *rpos, *oldpoint;
58         struct pane *popup, *replace_popup;
59 };
60 #include "core-pane.h"
61
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[] = ".|*+?{()?^$\\[";
66
67 DEF_CMD(search_forward)
68 {
69         struct es_info *esi = ci->home->data;
70         struct stk *s;
71         char *str;
72         struct mark *newstart;
73         const char *suffix;
74
75         if (!esi->target)
76                 return Efail;
77
78         suffix = ksuffix(ci, "K:C-");
79         if (suffix[0])
80                 esi->backwards = suffix[0] == 'R';
81
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 */
86                         return 1;
87                 esi->s->case_sensitive = esi->case_sensitive;
88                 esi->s->backwards = esi->backwards;
89         }
90         str = call_ret(str, "doc:get-str", ci->focus);
91         if (!str || !*str) {
92                 char *ss;
93                 ss = call_ret(strsave, "history:get-last", ci->focus);
94                 if (ss) {
95                         call("Replace", ci->home, 1, NULL, ss);
96                         return 1;
97                 }
98                 if (!str)
99                         return Einval;
100         }
101         s = calloc(1, sizeof(*s));
102         s->m = esi->start;
103         s->len = strlen(str);
104         s->wrapped = esi->wrapped;
105         s->case_sensitive = esi->case_sensitive;
106         s->backwards = -1;
107         free(str);
108         s->next = esi->s;
109         esi->s = s;
110         newstart = NULL;
111         if (esi->matched) {
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) {
117                                 mark_free(newstart);
118                                 newstart = NULL;
119                         }
120         }
121         if (!newstart) {
122                 newstart = mark_dup(s->m);
123                 esi->wrapped = 1;
124                 call("doc:file", esi->target, esi->backwards ? 1 : -1,
125                      newstart);
126         }
127         esi->start = newstart;
128         /* Trigger notification so isearch watcher searches again */
129         call("Replace", ci->home, 1, NULL, "");
130
131         if (!esi->matched && strcmp(ci->key, "search:again") == 0)
132                 return Efail;
133         return 1;
134 }
135
136 DEF_CMD(search_retreat)
137 {
138         struct es_info *esi = ci->home->data;
139         char *str;
140         struct stk *s;
141         char *attr;
142         struct mark *mk;
143
144         if (esi->s == NULL)
145                 goto just_delete;
146         str = call_ret(str, "doc:get-str", ci->focus);
147         if (!str)
148                 return Einval;
149         if (strlen(str) > esi->s->len) {
150                 free(str);
151                 goto just_delete;
152         }
153         free(str);
154         s = esi->s;
155         esi->s = s->next;
156         mark_free(esi->start);
157         esi->start = s->m;
158         esi->wrapped = s->wrapped;
159         free(s);
160         /* Trigger notification so isearch watcher searches again */
161         call("Replace", ci->home, 1, NULL, "");
162         return 1;
163
164 just_delete:
165         if (doc_following(ci->focus, NULL) != WEOF)
166                 /* Not at end-of-buffer, just delete one char */
167                 return Efallthrough;
168
169         mk = call_ret(mark, "doc:point", ci->focus);
170         if (!mk)
171                 return Efallthrough;
172         mk = mark_dup(mk);
173         do {
174                 if (doc_prev(ci->focus, mk) == WEOF)
175                         break;
176                 attr = call_ret(strsave, "doc:get-attr", ci->focus, 0, mk, "auto");
177         } while (attr && strcmp(attr, "1") == 0);
178
179         call("Replace", ci->focus, 1, mk);
180         mark_free(mk);
181         return 1;
182 }
183
184 DEF_CMD(search_add)
185 {
186         struct es_info *esi = ci->home->data;
187         wint_t wch;
188         char b[5];
189         struct mark *m;
190         int limit = 1000;
191         char *attr = NULL;
192         struct mark *addpos = mark_dup(esi->end);
193         char *str = call_ret(strsave, "doc:get-str", ci->home);
194         int first = 1;
195
196         if (!str)
197                 return 1;
198         if (!esi->target)
199                 return Efail;
200
201         if (esi->backwards)
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);
208         else
209                 call("Move-Char", esi->target, 1, m);
210
211         /* Move cursor to end of search string */
212         call("doc:file", ci->focus, 1);
213         while (esi->matched
214                && mark_ordered_not_same(addpos, m)) {
215                 int slash = 0;
216                 if (limit-- <= 0)
217                         break;
218                 wch = doc_next(esi->target, addpos);
219                 if (wch == WEOF)
220                         break;
221                 put_utf8(b, wch);
222                 if (wch == '\n') {
223                         slash = 1;
224                         strcpy(b, "n");
225                 } else if (strchr(must_quote, wch)) {
226                         slash = 1;
227                 }
228                 if (slash) {
229                         call("Replace", ci->focus, 1, NULL, "\\",
230                              !first, NULL, attr);
231                         attr = ",auto=1";
232                         first = 0;
233                 }
234                 call("Replace", ci->focus, 1, NULL, b,
235                      !first, NULL, attr);
236                 first = 0;
237                 attr = ",auto=1";
238         }
239         return 1;
240 }
241
242 DEF_CMD(search_insert_quoted)
243 {
244         const char *suffix = ksuffix(ci, "doc:char-");
245         char *patn;
246         if (strchr(must_quote, suffix[0]) == NULL)
247                 return Efallthrough;
248         patn = call_ret(strsave, "doc:get-str", ci->focus);
249         if (patn) {
250                 char *open = strrchr(patn, '[');
251                 if (open &&
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
255                          * quote anything.
256                          */
257                         return Efallthrough;
258         }
259         call("Replace", ci->focus, 1, NULL, "\\");
260         call("Replace", ci->focus, 1, NULL, suffix,
261              1, NULL, ",auto=1");
262         return 1;
263 }
264
265 #include <stdio.h>
266 DEF_CMD(search_insert_meta)
267 {
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
271          */
272         char *bracket;
273         const char *brackets = "{}()[]";
274         const char *k = ksuffix(ci, "K:A-");
275
276         if (strchr(must_quote, *k) == NULL || !ci->mark)
277                 return Efallthrough;
278         bracket = strchr(brackets, *k);
279         if (!bracket) {
280                 call("Replace", ci->focus, 1, NULL, k);
281         } else if ((bracket - brackets) % 2) {
282                 /* Close bracket */
283                 if (doc_following(ci->focus, ci->mark) == (wint_t)k[0])
284                         call("Move-Char", ci->focus, 1);
285                 else
286                         call("Replace", ci->focus, 1, NULL, k);
287         } else {
288                 /* Open bracket */
289                 char b[3];
290                 strncpy(b, bracket, 2);
291                 b[2] = 0;
292                 call("Replace", ci->focus, 1, NULL, b);
293                 call("Move-Char", ci->focus, -1);
294         }
295         return 1;
296
297 }
298
299 DEF_CMD_CLOSED(search_close)
300 {
301         struct es_info *esi = ci->home->data;
302
303         if (esi->target)
304                 call("search:highlight", esi->target);
305         mark_free(esi->end);
306         esi->end = safe_cast NULL;
307         mark_free(esi->start);
308         while (esi->s) {
309                 struct stk *n = esi->s;
310                 esi->s = n->next;
311                 mark_free(n->m);
312                 free(n);
313         }
314         return 1;
315 }
316
317 DEF_CMD(search_again)
318 {
319         /* document has changed, retry search */
320         struct es_info *esi = ci->home->data;
321         struct pane *p;
322         char *a, *pfx;
323         int ret;
324         struct mark *m;
325         char *str;
326
327         if (!esi->target)
328                 return Efail;
329
330         call("search:highlight", esi->target);
331         esi->matched = 0;
332         esi->replaced = 0;
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 */
337                 ret = 1;
338         else if (esi->backwards && doc_prev(esi->target, m) == WEOF)
339                 ret = Efail;
340         else {
341                 ret = call("text-search", esi->target,
342                            !esi->case_sensitive, m, str, esi->backwards);
343         }
344         if (ret == 0)
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): ";
354         } else {
355                 int len = --ret;
356                 mark_to_mark(esi->end, m);
357                 if (esi->backwards) {
358                         while (ret > 0 && doc_next(esi->target, m) != WEOF)
359                                 ret -= 1;
360                         call("search:highlight", esi->target, len, esi->end, str,
361                              !esi->case_sensitive, m);
362                 } else {
363                         while (ret > 0 && doc_prev(esi->target, m) != WEOF)
364                                 ret -= 1;
365                         call("search:highlight", esi->target, len, m, str,
366                              !esi->case_sensitive, esi->end);
367                 }
368                 esi->matched = len + 1;
369                 pfx = esi->backwards ? "Reverse Search: ":"Search: ";
370                 if (esi->wrapped)
371                         pfx = esi->backwards ? "Wrapped Reverse Search: ":"Wrapped Search: ";
372         }
373         /* HACK */
374         for (p = ci->home; p != p->parent; p = p->parent) {
375                 a = attr_find(p->attrs, "prefix");
376                 if (!a)
377                         continue;
378                 if (strcmp(a, pfx) != 0)
379                         attr_set_str(&p->attrs, "prefix", pfx);
380         }
381         if (m)
382                 mark_free(m);
383         free(str);
384         return 1;
385 }
386
387 DEF_CMD(search_done)
388 {
389         /* need to advance the target view to 'start', leaving
390          * mark at point
391          */
392         struct es_info *esi = ci->home->data;
393         char *str;
394         struct mark *mk;
395
396         if (!esi->target)
397                 return Efail;
398
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);
402                 return 1;
403         }
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);
408         if (mk)
409                 attr_set_int(&mk->attrs, "selection:active", 0);
410         call("Move-to", esi->target, 0, esi->end, NULL, 1);
411
412         call("popup:close", safe_cast ci->focus->parent, 0, NULL, str);
413         free(str);
414         return 1;
415 }
416
417 DEF_CMD(search_escape)
418 {
419         return call("search:done", ci->focus);
420 }
421
422 DEF_CMD(search_clip)
423 {
424         struct es_info *esi = ci->home->data;
425         struct stk *s;
426
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);
431         return Efallthrough;
432 }
433
434 DEF_CMD(search_recentre)
435 {
436         /* Send this command through to target, at current location */
437         struct es_info *esi = ci->home->data;
438
439         if (!esi->target)
440                 return Efail;
441         return call(ci->key, esi->target, ci->num, esi->end, NULL,
442                     ci->num2);
443 }
444
445 DEF_CMD(search_toggle_ci)
446 {
447         struct es_info *esi = ci->home->data;
448
449         /* If not at end of doc, fall through */
450         if (ci->mark && doc_following(ci->focus, ci->mark) != WEOF)
451                 return Efallthrough;
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 ");
457         return 1;
458 }
459
460 DEF_CMD(search_replace)
461 {
462         struct pane *p;
463         struct es_info *esi = ci->home->data;
464
465         if (esi->replace_pane) {
466                 pane_take_focus(esi->replace_pane);
467                 return 1;
468         }
469
470         if (!esi->target)
471                 return Efail;
472
473         p = call_ret(pane, "PopupTile", ci->focus, 0, NULL, "P", 0, NULL,
474                      "");
475         if (!p)
476                 return Efail;
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");
480
481         p = pane_register_3(p, 0, &replace_handle.c, ci->focus);
482         if (!p)
483                 return Efail;
484         p = call_ret(pane, "attach-history", p, 0, NULL, "*Replace History*");
485         esi->replace_pane = p;
486         if (p) {
487                 pane_add_notify(ci->home, p, "Notify:Close");
488                 home_call(esi->target, "highlight:set-popup", p, 1);
489         }
490         if (strcmp(ci->key, "K:A-%") == 0)
491                 pane_take_focus(ci->focus);
492         else
493                 pane_take_focus(p);
494         return 1;
495 }
496
497 DEF_CMD(search_notify_close)
498 {
499         struct es_info *esi = ci->home->data;
500
501         if (ci->focus == esi->replace_pane)
502                 esi->replace_pane = NULL;
503         if (ci->focus == esi->target) {
504                 esi->target = NULL;
505                 //pane_close(ci->home);
506         }
507         return 1;
508 }
509
510 DEF_CMD(do_replace)
511 {
512         struct es_info *esi = ci->home->data;
513         const char *new = ci->str;
514         struct mark *m;
515         int len = esi->matched - 1;
516
517         if (!new)
518                 return Enoarg;
519         if (len < 0)
520                 return Efail;
521         if (esi->replaced)
522                 return 1;
523         if (!esi->target)
524                 return Efail;
525         esi->replaced = 1;
526         m = mark_dup(esi->end);
527         if (esi->backwards) {
528                 while (len > 0 && doc_next(esi->target, m) != WEOF)
529                         len -= 1;
530                 mark_step(m, 0);
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);
534                         return 1;
535                 }
536         } else {
537                 while (len > 0 && doc_prev(esi->target, m) != WEOF)
538                         len -= 1;
539                 mark_step(m, 1);
540                 if (strchr(new, '\\')) {
541                         char *Pattern = call_ret(strsave, "doc:get-str", ci->home);
542                         struct command *ptn = call_ret(comm, "make-search",
543                                                        ci->home,
544                                                        RXLF_ANCHORED | RXLF_BACKTRACK,
545                                                        NULL, Pattern);
546                         if (ptn) {
547                                 char *new2;
548                                 call_comm("doc:content", esi->target, ptn, 0, m);
549                                 new2 = comm_call_ret(strsave, ptn, "interp",
550                                                      esi->target, 0, NULL, new);
551                                 if (new2)
552                                         new = new2;
553                                 command_put(ptn);
554                         }
555                 }
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);
559                         return 1;
560                 }
561         }
562         return Efail;
563 }
564
565 DEF_CMD(replace_request_next)
566 {
567         struct pane *sp = ci->home->data3;
568         char *new;
569
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);
574         } else {
575                 call("search:done", sp);
576         }
577         return 1;
578 }
579
580 DEF_CMD(replace_request)
581 {
582         struct pane *sp = ci->home->data3;
583         char *new;
584
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);
588         free(new);
589         return 1;
590 }
591
592 DEF_CMD(replace_all)
593 {
594         struct pane *sp = ci->home->data3;
595         char *new;
596         int replaced = 0;
597
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))
603                 replaced = 1;
604         if (replaced)
605                 call("history:save", ci->focus, 0, NULL, new);
606         free(new);
607
608         return 1;
609 }
610
611 DEF_CMD(replace_to_search)
612 {
613         struct pane *sp = ci->home->data3;
614
615         pane_take_focus(sp);
616         return 1;
617 }
618
619 DEF_CMD(replace_forward)
620 {
621         struct pane *sp = ci->home->data3;
622
623         call(ci->key, sp);
624
625         return 1;
626 }
627
628 DEF_CMD(replace_undo)
629 {
630         return Efallthrough;
631 }
632
633 DEF_CMD(replace_escape)
634 {
635         struct pane *sp = ci->home->data3;
636
637         return call("search:done", sp);
638 }
639
640 DEF_CMD(replace_prev)
641 {
642         struct pane *home = ci->home->data3;
643         struct es_info *esi = home->data;
644
645         if (esi->target)
646                 call("search:step-replace", esi->target, -1);
647         return 1;
648 }
649
650 DEF_CMD(replace_next)
651 {
652         struct pane *home = ci->home->data3;
653         struct es_info *esi = home->data;
654
655         if (esi->target)
656                 call("search:step-replace", esi->target, 1);
657         return 1;
658 }
659
660 static void emacs_search_init_map(void)
661 {
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);
683
684         key_add(es_map, "search:replace", &do_replace);
685         key_add(es_map, "Notify:Close", &search_notify_close);
686
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);
700 }
701
702 DEF_CMD(emacs_search)
703 {
704         struct pane *p, *target;
705         struct es_info *esi;
706         struct mark *m;
707
708         if (!es_map)
709                 emacs_search_init_map();
710         target = call_ret(pane, "popup:get-target", ci->focus);
711         if (!target)
712                 return Efail;
713
714         m = mark_at_point(target, NULL, MARK_POINT);
715         if (!m)
716                 return Efail;
717
718         p = pane_register(ci->focus, 0, &search_handle.c);
719         if (!p)
720                 return Efail;
721         esi = p->data;
722         esi->target = target;
723         esi->end = m;
724
725         esi->start = mark_dup(m);
726         esi->s = NULL;
727         esi->matched = 1;
728         esi->wrapped = 0;
729         esi->replaced = 0;
730         esi->backwards = ci->num & 1;
731
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");
736
737         if (ci->num & 2)
738                 call("K:A-%", p);
739
740         return 1;
741 }
742
743 static void do_searches(struct pane *p safe,
744                         struct pane *owner safe, int view, char *patn,
745                         int ci,
746                         struct mark *m, struct mark *end)
747 {
748         int ret;
749         struct highlight_info *hi = owner->data2;
750         struct mark *start;
751
752         if (!m)
753                 return;
754         m = mark_dup(m);
755         start = mark_dup(m);
756         while ((ret = call("text-search", p, ci, m, patn, 0, end)) >= 1) {
757                 struct mark *m2, *m3;
758                 int len = ret - 1;
759                 m2 = vmark_new(p, view, owner);
760                 if (!m2)
761                         break;
762                 mark_to_mark(m2, m);
763                 while (ret > 1 && doc_prev(p, m2) != WEOF)
764                         ret -= 1;
765                 m3 = vmark_matching(m2);
766                 if (m3) {
767                         mark_free(m2);
768                         m2 = m3;
769                 }
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",
774                                      len);
775                         call("view:changed", p, 0, m2, NULL, 0, m);
776                         m2 = vmark_new(p, view, owner);
777                         if (m2) {
778                                 mark_to_mark(m2, m);
779                                 attr_set_int(&m2->attrs,
780                                              match ? "render:search-end"
781                                              : "render:search2-end",
782                                              0);
783                         }
784                 }
785
786                 if (len == 0 || mark_ordered_or_same(m, start))
787                         /* Need to move forward, or we'll just match here again*/
788                         doc_next(p, m);
789                 mark_free(start);
790                 start = mark_dup(m);
791         }
792         mark_free(start);
793         mark_free(m);
794 }
795
796 static void queue_highlight_refresh(struct pane *p safe);
797
798 DEF_CMD(emacs_search_highlight)
799 {
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.
805          */
806         struct mark *m, *start;
807         struct highlight_info *hi = ci->home->data2;
808
809         if (hi->view < 0)
810                 return Efail;
811
812         while ((start = vmark_first(ci->focus, hi->view, ci->home)) != NULL)
813                 mark_free(start);
814
815         free(hi->patn);
816         if (ci->str)
817                 hi->patn = strdup(ci->str);
818         else
819                 hi->patn = NULL;
820         hi->ci = ci->num2;
821         mark_free(hi->match);
822         hi->match = NULL;
823
824         if (hi->oldpoint) {
825                 call("Move-to", ci->focus, 0, hi->oldpoint);
826                 mark_free(hi->rpos);
827                 mark_free(hi->oldpoint);
828                 hi->rpos = NULL;
829                 hi->oldpoint = NULL;
830         }
831
832         if (ci->mark && ci->num >= 0 && ci->str) {
833                 m = vmark_new(ci->focus, hi->view, ci->home);
834                 if (!m)
835                         return Efail;
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);
840                 if (ci->mark2 &&
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);
844                 }
845         } else if (ci->str) {
846                 /* No destination to move to, so just refresh whatever
847                  * is visible
848                  */
849                 queue_highlight_refresh(ci->focus);
850         }
851         call("view:changed", ci->focus);
852         call("render:request:reposition", ci->focus);
853         return 1;
854 }
855
856 DEF_CMD(emacs_replace_highlight)
857 {
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
862          * be added.
863          */
864         struct mark *m;
865         struct highlight_info *hi = ci->home->data2;
866
867         if (hi->replace_view < 0 || !hi->replace_popup)
868                 return Efail;
869
870         if (!ci->mark || !ci->mark2)
871                 return Enoarg;
872
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))
877                 mark_free(m);
878         m = vmark_new(ci->focus, hi->replace_view, ci->home);
879         if (m) {
880                 mark_to_mark(m, ci->mark);
881                 attr_set_int(&m->attrs, "render:replacement", ci->num);
882         }
883         m = vmark_new(ci->focus, hi->replace_view, ci->home);
884         if (m) {
885                 mark_to_mark(m, ci->mark2);
886                 attr_set_int(&m->attrs, "render:replacement-end", 0);
887         }
888         call("view:changed", ci->focus);
889         return 1;
890 }
891
892 DEF_CMD(emacs_hl_attrs)
893 {
894         struct highlight_info *hi = ci->home->data2;
895
896         if (!ci->str)
897                 return Efallthrough;
898         if (!hi->popup)
899                 return Efallthrough;
900
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);
907                 }
908         }
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);
915                 }
916         }
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);
924                 }
925         }
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))
929                         m = NULL;
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);
936         }
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);
941         }
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);
946         }
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);
951         }
952         return Efallthrough;
953 }
954
955 DEF_CMD(highlight_draw)
956 {
957         struct highlight_info *hi = ci->home->data2;
958         struct pane *pp = hi->popup;
959         struct pane *pp2 = hi->replace_popup;
960         struct xy xy;
961
962         if (!ci->str2 || !strstr(ci->str2, ",focus") || !pp)
963                 return Efallthrough;
964
965         /* here is where the user will be looking, make sure
966          * the popup doesn't obscure it.
967          */
968
969         xy = pane_mapxy(ci->focus, ci->home, ci->x, ci->y, False);
970         while (pp->parent != pp && pp->z == 0)
971                 pp = pp->parent;
972         while (pp2 && pp2->parent != pp2 && pp2->z == 0)
973                 pp2 = pp2->parent;
974         if (pp->x == 0) {
975                 /* currently TL, should we move it back */
976                 if (xy.x < pp->w ||
977                     (xy.y > pp->h &&
978                      (!pp2 || xy.y > pp2->y + pp2->h)))
979                         call("popup:style", hi->popup, 0, NULL, "TR2");
980         } else {
981                 /* currently TR, should we move it out of way */
982                 if (xy.x >= pp->x &&
983                     (xy.y <= pp->h ||
984                      (pp2 && xy.y <= pp2->y + pp2->h)))
985                         call("popup:style", hi->popup, 0, NULL, "TL2");
986         }
987         return Efallthrough;
988 }
989
990 DEF_CMD(emacs_search_reposition_delayed)
991 {
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;
997
998         if (!start || !end)
999                 return Efalse;
1000
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);
1007                 if (vend)
1008                         do_searches(ci->focus, ci->home, hi->view, patn, hi->ci,
1009                                     vend, end);
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,
1013                             vend, end);
1014         }
1015         return Efalse;
1016 }
1017
1018 static void queue_highlight_refresh(struct pane *p safe)
1019 {
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);
1023 }
1024
1025 DEF_CMD(emacs_search_reposition)
1026 {
1027         /*
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
1033          * reposition.
1034          */
1035         struct highlight_info *hi = ci->home->data2;
1036         struct mark *start = ci->mark;
1037         struct mark *end = ci->mark2;
1038         struct mark *m;
1039
1040         if (hi->view < 0 || hi->patn == NULL || !start || !end || !hi->popup)
1041                 return Efallthrough;
1042
1043         while ((m = vmark_first(ci->focus, hi->view, ci->home)) != NULL &&
1044                mark_ordered_not_same(m, start))
1045                 mark_free(m);
1046
1047         while ((m = vmark_last(ci->focus, hi->view, ci->home)) != NULL &&
1048                mark_ordered_not_same(end, m))
1049                 mark_free(m);
1050
1051         mark_free(hi->start);
1052         mark_free(hi->end);
1053         hi->start = mark_dup(start);
1054         hi->end = mark_dup(end);
1055
1056         queue_highlight_refresh(ci->home);
1057         return Efallthrough;
1058 }
1059
1060 DEF_CMD_CLOSED(emacs_highlight_close)
1061 {
1062         /* ci->focus is being closed */
1063         struct highlight_info *hi = ci->home->data2;
1064
1065         free(hi->patn);
1066         mark_free(hi->start);
1067         mark_free(hi->end);
1068         mark_free(hi->match);
1069         mark_free(hi->rpos);
1070         mark_free(hi->oldpoint);
1071         hi->start = NULL;
1072         hi->end = NULL;
1073         hi->match = NULL;
1074         hi->rpos = NULL;
1075         hi->oldpoint = NULL;
1076         return 1;
1077 }
1078
1079 static void free_marks(struct pane *home safe)
1080 {
1081         struct highlight_info *hi = home->data2;
1082         struct mark *m;
1083
1084         while ((m = vmark_first(home, hi->view, home)) != NULL)
1085                 mark_free(m);
1086         while ((m = vmark_first(home, hi->replace_view, home)) != NULL)
1087                 mark_free(m);
1088 }
1089
1090 DEF_CMD(emacs_search_done)
1091 {
1092         struct highlight_info *hi = ci->home->data2;
1093
1094         if (ci->str && ci->str[0])
1095                 call("history:save", ci->focus, 0, NULL, ci->str);
1096
1097         if (hi->oldpoint) {
1098                 call("Move-to", ci->focus, 0, hi->oldpoint);
1099                 mark_free(hi->rpos);
1100                 mark_free(hi->oldpoint);
1101                 hi->rpos = NULL;
1102                 hi->oldpoint = NULL;
1103         }
1104
1105         hi->popup = NULL;
1106         hi->replace_popup = NULL;
1107         free_marks(ci->home);
1108         return 1;
1109 }
1110
1111 DEF_CMD(emacs_highlight_abort)
1112 {
1113         struct highlight_info *hi = ci->home->data2;
1114         struct pane *p;
1115
1116         p = hi->replace_popup;
1117         hi->replace_popup = NULL;
1118         if (p)
1119                 call("popup:close", p, 0, NULL, "");
1120         p = hi->popup;
1121         hi->popup = NULL;
1122         if (p)
1123                 call("popup:close", p, 0, NULL, "");
1124         free_marks(ci->home);
1125
1126         return Efallthrough;
1127 }
1128
1129 DEF_CMD(emacs_highlight_clip)
1130 {
1131         struct highlight_info *hi = ci->home->data2;
1132
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;
1138 }
1139
1140 DEF_CMD(emacs_highlight_set_popup)
1141 {
1142         struct highlight_info *hi = ci->home->data2;
1143
1144         if (ci->num)
1145                 hi->replace_popup = ci->focus;
1146         else
1147                 hi->popup = ci->focus;
1148         pane_add_notify(ci->home, ci->focus, "Notify:Close");
1149         return 1;
1150 }
1151
1152 DEF_CMD(emacs_highlight_close_notify)
1153 {
1154         struct highlight_info *hi = ci->home->data2;
1155
1156         if (ci->focus == hi->replace_popup)
1157                 hi->replace_popup = NULL;
1158         if (ci->focus == hi->popup)
1159                 hi->popup = NULL;
1160         return 1;
1161 }
1162
1163 DEF_CMD(emacs_highlight_reattach)
1164 {
1165         comm_call(ci->comm2, "cb", ci->home);
1166         return 1;
1167 }
1168
1169 DEF_CMD(emacs_step_replace)
1170 {
1171         struct highlight_info *hi = ci->home->data2;
1172         struct mark *m;
1173
1174         if (!hi->replace_view || !hi->match)
1175                 return Einval;
1176         if (ci->num > 0 && !hi->rpos)
1177                 return 1;
1178         if (!hi->rpos) {
1179                 if (!hi->oldpoint) {
1180                         m = call_ret(mark, "doc:point", ci->home);
1181                         if (m)
1182                                 hi->oldpoint = mark_dup(m);
1183                 }
1184                 hi->rpos = mark_dup(hi->match);
1185         }
1186
1187         if (ci->num > 0) {
1188                 m = vmark_at_or_before(ci->home, hi->rpos, hi->replace_view, ci->home);
1189                 if (m)
1190                         m = vmark_next(m);
1191                 while (m && attr_find_int(m->attrs, "render:replacement") < 0)
1192                         m = vmark_next(m);
1193         } else {
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)
1197                         m = vmark_prev(m);
1198         }
1199         if (m) {
1200                 mark_to_mark(hi->rpos, m);
1201                 call("Move-View-Pos", ci->home, 0, m);
1202                 call("Move-to", ci->home, 0, m);
1203         }
1204         return 1;
1205 }
1206
1207 static struct map *hl_map;
1208 DEF_LOOKUP_CMD(highlight_handle, hl_map);
1209
1210 static void emacs_highlight_init_map(void)
1211 {
1212         struct map *m;
1213
1214         m = key_alloc();
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);
1228         hl_map = m;
1229 }
1230
1231 DEF_CMD(emacs_search_attach_highlight)
1232 {
1233         struct highlight_info *hi;
1234         struct pane *p;
1235
1236         if (!hl_map)
1237                 emacs_highlight_init_map();
1238
1239         p = pane_register_2(ci->focus, 0, &highlight_handle.c);
1240         if (!p)
1241                 return Efail;
1242         hi = p->data2;
1243
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);
1247
1248         return 1;
1249 }
1250
1251 void edlib_init(struct pane *ed safe)
1252 {
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");
1257 }