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