]> git.neil.brown.name Git - edlib.git/blob - lib-wiggle.c
Send warning message when nested notification is prohibited.
[edlib.git] / lib-wiggle.c
1 /*
2  * Copyright Neil Brown ©2020-2023 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * wiggle - mark word-wise differences and merges
6  *
7  * ranges currently have to be in same file.
8  * We use code from wiggle which requires that a 'stream'
9  * (a char[] with length) be split as words into a 'file', then two
10  * such 'file's passed to 'diff()' to produce a 'csl' (common subsequence list).
11  * The a/b/len of each element in the csl are index to the respective
12  * files, which have indexes into the streams.
13  */
14
15 #include <wctype.h>
16 #include "core.h"
17 #include "misc.h"
18 #include "wiggle/wiggle.h"
19
20 static bool has_nonspace(const char *s, int len)
21 {
22         wint_t ch;
23         const char *end = s+len;
24
25         while ((ch = get_utf8(&s, end)) < WERR &&
26                iswspace(ch))
27                 ;
28         return ch != WEOF;
29 }
30
31 static bool only_spaces(struct file f, struct csl *csl safe, int which)
32 {
33         int fpos = 0;
34
35         if (!f.list)
36                 return True;
37
38         for (; csl->len; csl += 1) {
39                 int o = which ? csl->b : csl->a;
40
41                 for (; fpos < o; fpos += 1) {
42                         struct elmnt e = f.list[fpos];
43
44                         if (has_nonspace(e.start, e.len))
45                                 return False;
46                 }
47                 fpos = o + csl->len;
48         }
49         for (; fpos < f.elcnt; fpos += 1) {
50                 struct elmnt e = f.list[fpos];
51
52                 if (has_nonspace(e.start, e.len))
53                         return False;
54         }
55         return True;
56 }
57
58 static void doskip(struct pane *p safe,
59                    struct mark *m safe, struct mark *end,
60                    int skip, int choose)
61 {
62         /* If skip > 0, then the first 'skip' chars on each line
63          * are skipped over.
64          * If 'choose' is also > 0 then the whole line is skipped
65          * unless:
66          *  choose <= skip and the "choose"th char is not '+'
67          *  choose > skip and none of the skip chars are '-'
68          */
69         int toskip = skip;
70         bool chosen = choose == 0 || choose > skip;
71
72         while ((!end || mark_ordered_not_same(m, end)) &&
73                (toskip || !chosen)) {
74                 /* Don't want this char */
75                 wint_t wch = doc_next(p, m);
76                 if (wch == WEOF)
77                         break;
78                 if (is_eol(wch)) {
79                         toskip = skip;
80                         chosen = choose == 0 || choose > skip;
81                 } else if (toskip) {
82                         toskip -= 1;
83                         if (choose > skip && wch == '-')
84                                 chosen = False;
85                         if (skip - toskip == choose && wch != '+')
86                                 chosen = True;
87                 }
88         }
89 }
90
91 static bool collect(struct pane *p, struct mark *start, struct mark *end,
92                     int skip, int choose, struct stream *s safe)
93 {
94         struct mark *m;
95         wint_t wch = '\n';
96         struct buf b;
97
98         if (!p || !start || !end)
99                 return False;
100
101         buf_init(&b);
102         m = mark_dup(start);
103         while (mark_ordered_not_same(m, end)) {
104                 if (is_eol(wch)) {
105                         doskip(p, m, end, skip, choose);
106                         if (!mark_ordered_not_same(m, end))
107                                 break;
108                 }
109                 wch = doc_next(p, m);
110                 if (wch == WEOF)
111                         break;
112                 buf_append(&b, wch);
113         }
114         s->body = buf_final(&b);
115         s->len = b.len;
116         mark_free(m);
117
118         return True;
119 }
120
121 static void add_markup(struct pane *p, struct mark *start,
122                        int skip, int choose,
123                        struct stream astream, struct file afile,
124                        struct csl *csl safe, const char *attr safe, int which)
125 {
126         /* Each range of characters that is mentioned in csl gets an attribute
127          * named 'attr' with value 'len' from csl.
128          * If a range crosses a newline, the first (non-skipped) character
129          * also gets the attribute with the remaining length.
130          */
131         const char *pos = astream.body;
132         struct mark *m;
133         wint_t ch = '\n';
134
135         if (!p || !start || !afile.list || !pos)
136                 return;
137         m = mark_dup(start);
138         while (csl->len) {
139                 int st = which ? csl->b : csl->a;
140                 const char *startp = afile.list[st].start - afile.list[st].prefix;
141                 const char *endp =      afile.list[st + csl->len - 1].start +
142                                         afile.list[st + csl->len - 1].len;
143                 char buf[20];
144                 int len;
145
146                 if (is_eol(ch))
147                         doskip(p, m, NULL, skip, choose);
148                 while (pos < startp) {
149                         if (get_utf8(&pos, NULL) >= WERR)
150                                 pos += 1;
151                         ch = doc_next(p, m);
152                         if (is_eol(ch))
153                                 doskip(p, m, NULL, skip, choose);
154                 }
155                 /* Convert csl->len in bytes to len in codepoints. */
156                 len = 0;
157                 while (pos < endp) {
158                         if (get_utf8(&pos, NULL) >= WERR)
159                                 pos += 1;
160                         len += 1;
161                 }
162                 pos = startp;
163                 snprintf(buf, sizeof(buf), "%d %d", len, which);
164                 call("doc:set-attr", p, 0, m, attr, 0, NULL, buf);
165                 ch = ' ';
166                 while (pos < endp) {
167                         if (get_utf8(&pos, NULL) >= WERR)
168                                 pos += 1;
169                         if (is_eol(ch)) {
170                                 doskip(p, m, NULL, skip, choose);
171                                 snprintf(buf, sizeof(buf), "%d %d", len, which);
172                                 call("doc:set-attr", p, 0, m, attr,
173                                      0, NULL, buf);
174                         }
175                         len -= 1;
176                         ch = doc_next(p, m);
177                 }
178                 csl += 1;
179         }
180         mark_free(m);
181 }
182
183 /* We provide a command that handles wiggling across multiple panes.  It
184  * is paired with a private pane which can get notifications when those
185  * panes are closed.
186  */
187 struct wiggle_data {
188         struct pane *private safe;
189         struct wtxt {
190                 struct pane *text;
191                 struct mark *start, *end;
192                 short skip;     /* prefix chars to skip */
193                 short choose;   /* if !=0, only chose lines with non-space
194                                  * in this position 1..skip
195                                  */
196         } texts[3];
197         struct command c;
198         /* After set-wiggle is called, these are set */
199         int space_conflicts, conflicts, wiggles;
200         char *wiggle;
201 };
202
203 DEF_CMD(notify_close)
204 {
205         /* Private pane received a "close" notification. */
206         struct wiggle_data *wd = ci->home->data;
207         int i;
208
209         for (i = 0; i < 3; i++)
210                 if (ci->focus == wd->texts[i].text ||
211                     ci->focus == wd->private) {
212                         mark_free(wd->texts[i].start);
213                         wd->texts[i].start = NULL;
214                         mark_free(wd->texts[i].end);
215                         wd->texts[i].end = NULL;
216                         wd->texts[i].text = NULL;
217                 }
218         return 1;
219 }
220
221 DEF_CMD(wiggle_close)
222 {
223         struct wiggle_data *wd = ci->home->data;
224         int i;
225
226         for (i = 0; i < 3 ; i++) {
227                 mark_free(wd->texts[i].start);
228                 wd->texts[i].start = NULL;
229                 mark_free(wd->texts[i].end);
230                 wd->texts[i].end = NULL;
231                 wd->texts[i].text = NULL;
232         }
233         free(wd->wiggle);
234         return 1;
235 }
236
237 static void wiggle_free(struct command *c safe)
238 {
239         struct wiggle_data *wd = container_of(c, struct wiggle_data, c);
240
241         pane_close(wd->private);
242 }
243
244 DEF_CB(do_wiggle)
245 {
246         struct wiggle_data *wd = container_of(ci->comm, struct wiggle_data, c);
247
248         return home_call(wd->private, ci->key, ci->focus,
249                          ci->num, ci->mark, ci->str,
250                          ci->num2, ci->mark2, ci->str2,
251                          ci->x, ci->y, ci->comm2);
252 }
253
254 static void forward_lines(struct pane *p safe, struct mark *m safe,
255                           int skip, int choose, int lines)
256 {
257         while (lines > 0) {
258                 doskip(p, m, NULL, skip, choose);
259                 call("doc:EOL", p, 1, m, NULL, 1);
260                 lines -= 1;
261         }
262 }
263
264 DEF_CMD(wiggle_text)
265 {
266         /* remember pane, mark1, mark2, num,  num2 */
267         struct wiggle_data *wd = ci->home->data;
268         struct mark *m2;
269         char k0 = ci->key[0];
270         int which = k0 == 'b' ? 1 : k0 == 'a' ? 2 : 0;
271
272         /* Always clean out, even it not given enough args */
273         mark_free(wd->texts[which].start);
274         wd->texts[which].start = NULL;
275         mark_free(wd->texts[which].end);
276         wd->texts[which].end = NULL;
277         /* It isn't possible to drop individual notification links.
278          * We will lose them all on close, and ignore any before that.
279          */
280         wd->texts[which].text = NULL;
281
282         if (!ci->mark || (!ci->mark2 && !ci->str))
283                 return Enoarg;
284         if (ci->num < 0 || ci->num2 < 0 || ci->num2 > ci->num+1)
285                 return Einval;
286         if (!ci->mark2) {
287                 int lines = atoi(ci->str ?: "1");
288                 m2 = mark_dup(ci->mark);
289                 forward_lines(ci->focus, m2, ci->num, ci->num2, lines);
290         } else
291                 m2 = mark_dup(ci->mark2);
292
293         wd->texts[which].text = ci->focus;
294         pane_add_notify(ci->home, ci->focus, "Notify:Close");
295         wd->texts[which].start = mark_dup(ci->mark);
296         wd->texts[which].end = m2;
297         wd->texts[which].skip = ci->num;
298         wd->texts[which].choose = ci->num2;
299
300         return 1;
301 }
302
303 DEF_CMD(wiggle_extract)
304 {
305         struct wiggle_data *wd = ci->home->data;
306         struct wtxt *wt;
307         struct stream str;
308
309         if (!ci->str || !ci->comm2)
310                 return Enoarg;
311         if (strcmp(ci->str, "orig") == 0)
312                 wt = &wd->texts[0];
313         else if (strcmp(ci->str, "before") == 0)
314                 wt = &wd->texts[1];
315         else if (strcmp(ci->str, "after") == 0)
316                 wt = &wd->texts[2];
317         else
318                 return Einval;
319         if (!collect(wt->text, wt->start, wt->end, wt->skip, wt->choose,
320                      &str))
321                 return Enoarg;
322
323         comm_call(ci->comm2, "cb", ci->focus, 0, NULL, str.body);
324         free(str.body);
325         return 1;
326 }
327
328 DEF_CMD(wiggle_set_common)
329 {
330         /* Set the attribute 'str' on all common ranges in
331          * 'before' and 'after'
332          */
333         struct wiggle_data *wd = ci->home->data;
334         const char *attr = ci->str ?: "render:common";
335         struct stream before, after;
336         struct file bfile, afile;
337         struct csl *csl;
338         int ret = Efail;
339         int ignore_blanks = 0 /* IgnoreBlanks */;
340
341         if (!collect(wd->texts[1].text, wd->texts[1].start, wd->texts[1].end,
342                      wd->texts[1].skip, wd->texts[1].choose, &before))
343                 return Enoarg;
344         if (!collect(wd->texts[2].text, wd->texts[2].start, wd->texts[2].end,
345                      wd->texts[2].skip, wd->texts[2].choose, &after)) {
346                 free(before.body);
347                 return Enoarg;
348         }
349
350         bfile = wiggle_split_stream(before, ByWord | ignore_blanks);
351         afile = wiggle_split_stream(after, ByWord | ignore_blanks);
352         csl = wiggle_diff(bfile, afile, 1);
353         if (csl) {
354                 add_markup(wd->texts[1].text, wd->texts[1].start,
355                            wd->texts[1].skip, wd->texts[1].choose,
356                            before, bfile, csl, attr, 0);
357                 add_markup(wd->texts[2].text, wd->texts[2].start,
358                            wd->texts[2].skip, wd->texts[2].choose,
359                            after, afile, csl, attr, 1);
360
361                 ret = 3;
362                 if (only_spaces(bfile, csl, 0) &&
363                     only_spaces(afile, csl, 1))
364                         ret = 2; /* only space differences */
365         }
366
367         free(bfile.list);
368         free(afile.list);
369         free(csl);
370         free(before.body);
371         free(after.body);
372
373         return ret;
374 }
375
376 static const char *typenames[] = {
377         [End] = "End",
378         [Unmatched] = "Unmatched",
379         [Unchanged] = "Unchanged",
380         [Extraneous] = "Extraneous",
381         [Changed] = "Changed",
382         [Conflict] = "Conflict",
383         [AlreadyApplied] = "AlreadyApplied",
384 };
385
386 static bool merge_has_nonspace(struct file f, int pos, int len)
387 {
388         char *cp;
389         char *endcp;
390
391         if (len == 0)
392                 return False;
393         if (!f.list)
394                 return True;
395         endcp = f.list[pos+len-1].start + f.list[pos+len-1].len;
396         cp = f.list[pos].start;
397         return has_nonspace(cp, endcp-cp);
398 }
399
400 static int count_space_conflicts(struct merge *merge safe,
401                                  struct file a, struct file b, struct file c)
402 {
403         int cnt = 0;
404         struct merge *m;
405
406         for (m = merge; m->type != End; m++) {
407
408                 if (m->type != Conflict)
409                         continue;
410                 if (!merge_has_nonspace(a, m->a, m->al) &&
411                     !merge_has_nonspace(b, m->b, m->bl) &&
412                     !merge_has_nonspace(c, m->c, m->cl))
413                         cnt += 1;
414         }
415         return cnt;
416 }
417
418 static void add_merge_markup(struct pane *p safe,
419                              struct mark *st,
420                              int skip, int choose,
421                              struct file f, struct merge *merge safe,
422                              const char *attr safe, int which,
423                              int ignore_blanks)
424 {
425         struct merge *m;
426         int mergenum;
427         int pos = 0;
428
429         if (!f.list || !st)
430                 return;
431         st = mark_dup(st);
432
433 #if 0
434         LOG("which=%d", which);
435         for (m = merge, mergenum=0; m->type != End; m++, mergenum++) {
436                 pos2 = 0;
437                 switch(which) {
438                 case 0: pos2 = m->a; break;
439                 case 1: pos2 = m->b; break;
440                 case 2: pos2 = m->c; break;
441                 }
442                 LOG("M:%d %-10s %d  %d  %d (%d+%d)", mergenum, typenames[m->type], m->al, m->bl, m->cl,
443                     f.list[pos2].prefix, f.list[pos2].len);
444         }
445 #endif
446         doskip(p, st, NULL, skip, choose);
447         for (m = merge, mergenum=0; m->type != End; m++, mergenum++) {
448                 int len;
449                 const char *cp, *endcp;
450                 wint_t wch;
451                 bool non_space;
452                 int chars;
453                 char *suffix = "";
454                 char buf[128];
455
456                 switch (which) {
457                 case 0: /* orig - no Extraneous */
458                         if (m->type == Extraneous)
459                                 continue;
460                         if (pos != m->a) abort();
461                         len = m->al;
462                         break;
463                 case 1: /* before - no Unmatched */
464                         if (m->type == Unmatched)
465                                 continue;
466                         if (pos != m->b) abort();
467                         len = m->bl;
468                         break;
469                 case 2: /* after - no Unmatched */
470                         if (m->type == Unmatched)
471                                 continue;
472                         if (pos != m->c) abort();
473                         len = m->cl;
474                         break;
475                 }
476                 /* From here for 'len' element in f are 'm->type' */
477                 if (!len)
478                         continue;
479                 cp = f.list[pos].start - f.list[pos].prefix;
480                 endcp = f.list[pos+len-1].start + f.list[pos+len-1].len;
481                 if (ignore_blanks && endcp) {
482                         while (*endcp == ' ' || *endcp == '\t')
483                                 endcp++;
484                         if (*endcp == '\n')
485                                 endcp++;
486                 }
487                 pos += len;
488                 chars = 0;
489                 non_space = False;
490                 while ((wch = get_utf8(&cp, endcp)) < WERR) {
491                         chars += 1;
492                         if (!iswspace(wch))
493                                 non_space = True;
494                 }
495
496                 if (m->type == Conflict && !non_space)
497                         suffix = " spaces";
498                 snprintf(buf, sizeof(buf), "M %d %s %d%s",
499                          chars, typenames[m->type], mergenum, suffix);
500                 call("doc:set-attr", p, 0, st, attr, 0, NULL, buf);
501                 while (chars > 0) {
502                         wint_t ch = doc_next(p, st);
503
504                         if (ch == WEOF)
505                                 break;
506                         if (is_eol(ch))
507                                 doskip(p, st, NULL, skip, choose);
508                         chars -= 1;
509                         if (is_eol(ch) && chars > 0) {
510                                 snprintf(buf, sizeof(buf), "L %d %s %d%s", chars,
511                                          typenames[m->type], mergenum, suffix);
512                                 call("doc:set-attr", p, 0, st, attr,
513                                      0, NULL, buf);
514                         }
515                 }
516         }
517         mark_free(st);
518 }
519
520 static int copy_words(char *str, struct file *f safe, int start, int len)
521 {
522         int ret = 0;
523         if (!f->list)
524                 return 0;
525         while (len > 0 && start < f->elcnt) {
526                 struct elmnt e = f->list[start];
527                 if (str) {
528                         memcpy(str, e.start - e.prefix, e.plen + e.prefix);
529                         str += e.plen + e.prefix;
530                 }
531                 ret += e.plen + e.prefix;
532                 start += 1;
533                 len -= 1;
534         }
535         return ret;
536 }
537
538 static char *collect_merge(struct merge *merge safe,
539                            struct file of, struct file bf, struct file af)
540 {
541         struct merge *m;
542         char *str;
543         int l;
544
545         if (!of.list || !bf.list || !af.list)
546                 return NULL;
547         /* First determine size */
548         l = 0;
549         for (m = merge; m->type != End; m++) {
550                 if (m->type == Unmatched ||
551                     m->type == AlreadyApplied ||
552                     m->type == Conflict ||
553                     m->type == Unchanged)
554                         l += copy_words(NULL, &of, m->a, m->al);
555                 else if (m->type == Changed)
556                         l += copy_words(NULL, &af, m->c, m->cl);
557         }
558         str = malloc(l+1);
559         /* Now copy content in */
560         l = 0;
561         for (m = merge; m->type != End; m++) {
562                 if (m->type == Unmatched ||
563                     m->type == AlreadyApplied ||
564                     m->type == Conflict ||
565                     m->type == Unchanged)
566                         l += copy_words(str+l, &of, m->a, m->al);
567                 else if (m->type == Changed)
568                         l += copy_words(str+l, &af, m->c, m->cl);
569         }
570         str[l] = 0;
571         return str;
572 }
573
574 DEF_CMD(wiggle_set_wiggle)
575 {
576         struct wiggle_data *wd = ci->home->data;
577         struct stream ostr, astr, bstr;
578         struct file of, af, bf;
579         struct csl *csl1, *csl2;
580         struct ci info;
581         const char *attr = ci->str ?: "render:wiggle";
582         int ignore_blanks = 0 /*IgnoreBlanks*/;
583
584         if (!collect(wd->texts[0].text, wd->texts[0].start, wd->texts[0].end,
585                      wd->texts[0].skip, wd->texts[0].choose, &ostr))
586                 return Enoarg;
587         if (!collect(wd->texts[1].text, wd->texts[1].start, wd->texts[1].end,
588                      wd->texts[1].skip, wd->texts[1].choose, &bstr)) {
589                 free(ostr.body);
590                 return Enoarg;
591         }
592         if (!collect(wd->texts[2].text, wd->texts[2].start, wd->texts[2].end,
593                      wd->texts[2].skip, wd->texts[2].choose, &astr)) {
594                 free(ostr.body);
595                 free(bstr.body);
596                 return Enoarg;
597         }
598
599         of = wiggle_split_stream(ostr, ByWord | ignore_blanks);
600         bf = wiggle_split_stream(bstr, ByWord | ignore_blanks);
601         af = wiggle_split_stream(astr, ByWord | ignore_blanks);
602
603         csl1 = wiggle_diff(of, bf, 1);
604         csl2 = wiggle_diff(bf, af, 1);
605         info = wiggle_make_merger(of, bf, af, csl1, csl2, 1, 1, 0);
606         if (info.merger) {
607                 free(wd->wiggle);
608                 wd->wiggle = NULL;
609                 wd->conflicts = info.conflicts;
610                 wd->wiggles = info.wiggles;
611                 wd->space_conflicts = count_space_conflicts(info.merger,
612                                                             of, bf, af);
613                 if (info.conflicts == wd->space_conflicts)
614                         wd->wiggle = collect_merge(info.merger, of, bf, af);
615                 if (*attr) {
616                         add_merge_markup(ci->focus,
617                                          wd->texts[0].start,
618                                          wd->texts[0].skip, wd->texts[0].choose,
619                                          of, info.merger, attr, 0, ignore_blanks);
620                         add_merge_markup(ci->focus,
621                                          wd->texts[1].start,
622                                          wd->texts[1].skip, wd->texts[1].choose,
623                                          bf, info.merger, attr, 1, ignore_blanks);
624                         add_merge_markup(ci->focus,
625                                          wd->texts[2].start,
626                                          wd->texts[2].skip, wd->texts[2].choose,
627                                          af, info.merger, attr, 2, ignore_blanks);
628                 }
629         }
630
631         free(csl1);
632         free(csl2);
633         free(of.list);
634         free(bf.list);
635         free(af.list);
636         free(ostr.body);
637         free(bstr.body);
638         free(astr.body);
639
640         return info.conflicts + 1;
641 }
642
643 DEF_CMD(wiggle_find)
644 {
645         /* Find orig, before or after in 'focus' near 'mark'
646          * str in "orig", "before" or "after"
647          * num is max number of lines to stripe (fuzz)
648          * num2 is max number of lines, defaults to searching whole file.
649          * Returns number of fuzz lines, plus 1
650          */
651         struct wiggle_data *wd = ci->home->data;
652         int lines = ci->num2;
653         struct pane *p = ci->focus;
654         struct stream str;
655         char *match, *end;
656         struct wtxt *wt;
657         int ret = Efail;
658         int fuzz = 0;
659
660         if (!ci->mark || !ci->str)
661                 return Enoarg;
662         if (strcmp(ci->str, "orig") == 0)
663                 wt = &wd->texts[0];
664         else if (strcmp(ci->str, "before") == 0)
665                 wt = &wd->texts[1];
666         else if (strcmp(ci->str, "after") == 0)
667                 wt = &wd->texts[2];
668         else
669                 return Einval;
670         if (!collect(wt->text, wt->start, wt->end, wt->skip, wt->choose,
671                      &str))
672                 return Enoarg;
673
674         match = str.body;
675         if (!match)
676                 return Enoarg;
677         do {
678                 struct mark *early, *late;
679
680                 early = mark_dup(ci->mark);
681                 call("doc:EOL", p, -1, early);
682                 late = mark_dup(ci->mark);
683                 call("doc:EOL", p, 1, late, NULL, 1);
684                 if (doc_following(p, late) == WEOF) {
685                         mark_free(late);
686                         late = NULL;
687                 }
688
689                 while (early || late) {
690                         if (early) {
691                                 ret = call("text-equals", p, 0, early, match);
692                                 if (ret > 0) {
693                                         mark_to_mark(ci->mark, early);
694                                         break;
695                                 }
696                                 if (ret != Efalse || doc_prior(p, early) == WEOF) {
697                                         mark_free(early);
698                                         early = NULL;
699                                 } else
700                                         call("doc:EOL", p, -2, early);
701                         }
702                         if (late) {
703                                 ret = call("text-equals", p, 0, late, match);
704                                 if (ret > 0) {
705                                         mark_to_mark(ci->mark, late);
706                                         break;
707                                 }
708                                 if (ret != Efalse || doc_following(p, late) == WEOF) {
709                                         mark_free(late);
710                                         late = NULL;
711                                 } else {
712                                         call("doc:EOL", p, 1, late, NULL, 1);
713                                 }
714                         }
715                         if (lines > 0) {
716                                 lines -= 1;
717                                 if (lines == 0)
718                                         break;
719                         }
720                 }
721                 mark_free(early);
722                 mark_free(late);
723
724                 if (ret > 0)
725                         break;
726                 fuzz += 1;
727                 match = strchr(match, '\n');
728                 if (!match)
729                         break;
730                 match += 1;
731                 end = strrchr(match, '\n');
732                 if (end)
733                         *end = 0;
734                 end = strrchr(match, '\n');
735                 if (end)
736                         end[1] = 0;
737                 else
738                         break;
739         } while (fuzz < ci->num);
740
741         free(str.body);
742         return ret > 0 ? fuzz + 1 : Efail;
743 }
744
745 DEF_CMD(wiggle_get)
746 {
747         struct wiggle_data *wd = ci->home->data;
748
749         if (wd->conflicts < 0)
750                 return Einval;
751         if (!ci->str)
752                 return Enoarg;
753         if (strcmp(ci->str, "wiggle") == 0) {
754                 if (wd->wiggle)
755                         return comm_call(ci->comm2, "cb", ci->focus, 0, NULL,
756                                          wd->wiggle);
757                 else
758                         return Efalse;
759         }
760         if (strcmp(ci->str, "space-conflicts") == 0)
761                 return wd->space_conflicts + 1;
762         if (strcmp(ci->str, "conflicts") == 0)
763                 return wd->conflicts + 1;
764         if (strcmp(ci->str, "wiggles") == 0)
765                 return wd->wiggles + 1;
766         return Einval;
767 }
768
769 DEF_CMD(wiggle_find_best) { return 0; }
770
771 static struct map *wiggle_map;
772 DEF_LOOKUP_CMD(wiggle_pane, wiggle_map);
773
774 DEF_CMD(make_wiggle)
775 {
776         struct wiggle_data *wd;
777         struct pane *p;
778
779         if (!wiggle_map) {
780                 wiggle_map = key_alloc();
781                 key_add(wiggle_map, "Notify:Close", &notify_close);
782                 key_add(wiggle_map, "Close", &wiggle_close);
783                 key_add(wiggle_map, "orig", &wiggle_text);
784                 key_add(wiggle_map, "before", &wiggle_text);
785                 key_add(wiggle_map, "after", &wiggle_text);
786                 key_add(wiggle_map, "extract", &wiggle_extract);
787                 key_add(wiggle_map, "set-common", &wiggle_set_common);
788                 key_add(wiggle_map, "set-wiggle", &wiggle_set_wiggle);
789                 key_add(wiggle_map, "find", &wiggle_find);
790                 key_add(wiggle_map, "find-best", &wiggle_find_best);
791                 key_add(wiggle_map, "get-result", &wiggle_get);
792         }
793
794         alloc(wd, pane);
795         wd->c = do_wiggle;
796         wd->c.free = wiggle_free;
797         wd->conflicts = -1;
798         p = pane_register(pane_root(ci->focus), 0,
799                           &wiggle_pane.c, wd);
800         if (!p) {
801                 unalloc(wd, pane);
802                 return Efail;
803         }
804         command_get(&wd->c);
805         wd->private = p;
806         comm_call(ci->comm2, "cb", ci->focus,
807                   0, NULL, NULL,
808                   0, NULL, NULL, 0,0, &wd->c);
809         command_put(&wd->c);
810         return 1;
811 }
812
813 void edlib_init(struct pane *ed safe)
814 {
815         call_comm("global-set-command", ed, &make_wiggle,
816                   0, NULL, "MakeWiggle");
817 }