1 /*
2 * wiggle - apply rejected patches
3 *
4 * Copyright (C) 2005 Neil Brown <neilb@cse.unsw.edu.au>
5 * Copyright (C) 2010-2013 Neil Brown <neilb@suse.de>
6 * Copyright (C) 2014-2020 Neil Brown <neil@brown.name>
7 *
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program.
21 *
22 * Author: Neil Brown
23 * Email: <neil@brown.name>
24 */
25
26 /*
27 * vpatch - visual front end for wiggle - aka Browse mode.
28 *
29 * "files" display, lists all files with statistics
30 * - can hide various lines including subdirectories
31 * and files without wiggles or conflicts
32 * "merge" display shows various views of merged file with different
33 * parts in different colours.
34 *
35 * The window can be split horizontally to show the original and result
36 * beside the diff, and each different branch can be shown alone.
37 *
38 */
39
40 #include "wiggle.h"
41 #include <curses.h>
42 #include <unistd.h>
43 #include <stdlib.h>
44 #include <signal.h>
45 #include <fcntl.h>
46 #include <ctype.h>
47 #include <sys/wait.h>
48
49 static void term_init(int raw);
50
51 static int intr_kills = 0;
52
53 /* global attributes */
54 static unsigned int a_delete, a_added, a_common, a_sep, a_void,
55 a_unmatched, a_extra, a_already;
56 static unsigned int a_has_conflicts, a_has_wiggles, a_no_wiggles, a_saved;
57
58 /******************************************************************
59 * Help window
60 * We display help in an insert, leaving 5 columns left and right,
61 * and 2 rows top and bottom, but at most 58x15 plus border
62 * In help mode:
63 * SPC or RTN moves down or to next page
64 * BKSPC goes backwards
65 * 'q' returns to origin screen
66 * '?' show help on help
67 * left and right scroll help view
68 *
69 * A help text is an array of lines of text
70 */
71
72 static char *help_help[] = {
73 " You are viewing the help page for the help viewer.",
74 "You normally get here by typing '?'",
75 "",
76 "The following keystrokes work in the help viewer:",
77 " ? display this help message",
78 " q return to previous view",
79 " SPC move forward through help document",
80 " RTN same as SPC",
81 " BKSP move backward through help document",
82 " RIGHT scroll help window so text on the right appears",
83 " LEFT scroll help window so text on the left appears",
84 NULL
85 };
86
87 static char *help_missing[] = {
88 "The file that this patch applies to appears",
89 "to be missing.",
90 "Please type 'q' to continue",
91 NULL
92 };
93
94 static char *help_corrupt[] = {
95 "This patch appears to be corrupt",
96 "Please type 'q' to continue",
97 NULL
98 };
99
100 /* We can give one or two pages to display in the help window.
101 * The first is specific to the current context. The second
102 * is optional and may provide help in a more broad context.
103 */
help_window(char * page1[],char * page2[],int query)104 static int help_window(char *page1[], char *page2[], int query)
105 {
106 int rows, cols;
107 int top, left;
108 int r, c;
109 int ch;
110 char **page = page1;
111 int line = 0;
112 int shift = 0;
113
114 getmaxyx(stdscr, rows, cols);
115
116 if (cols < 70) {
117 left = 6;
118 cols = cols-12;
119 } else {
120 left = (cols-58)/2 - 1;
121 cols = 58;
122 }
123
124 if (rows < 21) {
125 top = 3;
126 rows = rows - 6;
127 } else {
128 top = (rows-15)/2 - 1;
129 rows = 15;
130 }
131
132 /* Draw a border around the 'help' area */
133 (void)attrset(A_STANDOUT);
134 for (c = left; c < left+cols; c++) {
135 mvaddch(top-1, c, '-');
136 mvaddch(top+rows, c, '-');
137 }
138 for (r = top; r < top + rows ; r++) {
139 mvaddch(r, left-1, '|');
140 mvaddch(r, left+cols, '|');
141 }
142 mvaddch(top-1, left-1, '/');
143 mvaddch(top-1, left+cols, '\\');
144 mvaddch(top+rows, left-1, '\\');
145 mvaddch(top+rows, left+cols, '/');
146 if (query) {
147 mvaddstr(top-1, left + cols/2 - 4, "Question");
148 mvaddstr(top+rows, left + cols/2 - 9,
149 "Answer Y, N, or Q.");
150 } else {
151 mvaddstr(top-1, left + cols/2 - 9,
152 "HELP - 'q' to exit");
153 mvaddstr(top+rows, left+cols/2 - 17,
154 "Press SPACE for more, '?' for help");
155 }
156 (void)attrset(A_NORMAL);
157
158 while (1) {
159 char **lnp = page + line;
160
161 /* Draw as much of the page at the current offset
162 * as fits.
163 */
164 for (r = 0; r < rows; r++) {
165 char *ln = *lnp;
166 int sh = shift;
167 if (ln)
168 lnp++;
169 else
170 ln = "";
171
172 while (*ln && sh > 0) {
173 ln++;
174 sh--;
175 }
176 for (c = 0; c < cols; c++) {
177 int chr = *ln;
178 if (chr)
179 ln++;
180 else
181 chr = ' ';
182 mvaddch(top+r, left+c, chr);
183 }
184 }
185 move(top+rows-1, left);
186 ch = getch();
187
188 switch (ch) {
189 case 'C' - 64:
190 case 'Q':
191 case 'q':
192 return -1;
193 break;
194 case 'Y':
195 case 'y':
196 if (query)
197 return 1;
198 break;
199 case 'N':
200 case 'n':
201 if (query)
202 return 0;
203 break;
204
205 case '?':
206 if (page1 != help_help)
207 help_window(help_help, NULL, 0);
208 break;
209 case ' ':
210 case '\r': /* page-down */
211 for (r = 0; r < rows-2; r++)
212 if (page[line])
213 line++;
214 if (!page[line] && !query) {
215 line = 0;
216 if (page == page1)
217 page = page2;
218 else
219 page = NULL;
220 if (page == NULL)
221 return -1;
222 }
223 break;
224
225 case '\b': /* page up */
226 if (line > 0) {
227 line -= (rows-2);
228 if (line < 0)
229 line = 0;
230 } else {
231 if (page == page2)
232 page = page1;
233 else
234 page = page2;
235 if (page == NULL)
236 page = page1;
237 line = 0;
238 }
239 break;
240
241 case KEY_LEFT:
242 if (shift > 0)
243 shift--;
244 break;
245 case KEY_RIGHT:
246 shift++;
247 break;
248
249 case KEY_UP:
250 if (line > 0)
251 line--;
252 break;
253 case KEY_DOWN:
254 if (page[line])
255 line++;
256 break;
257 }
258 }
259 }
260
261 static char *typenames[] = {
262 [End] = "End",
263 [Unmatched] = "Unmatched",
264 [Unchanged] = "Unchanged",
265 [Extraneous] = "Extraneous",
266 [Changed] = "Changed",
267 [Conflict] = "Conflict",
268 [AlreadyApplied] = "AlreadyApplied",
269 };
270
271 /* When we merge the original and the diff together we need
272 * to keep track of where everything came from.
273 * When we display the different views, we need to be able to
274 * select certain portions of the whole document.
275 * These flags are used to identify what is present, and to
276 * request different parts be extracted. They also help
277 * guide choice of colour.
278 */
279 #define BEFORE 1
280 #define AFTER 2
281 #define ORIG 4
282 #define RESULT 8
283 #define CHANGES 16 /* A change is visible here,
284 * so 2 streams need to be shown */
285 #define WIGGLED 32 /* a conflict that was successfully resolved */
286 #define CONFLICTED 64 /* a conflict that was not successfully resolved */
287
288 /* Displaying a Merge.
289 * The first step is to linearise the merge. The merge in inherently
290 * parallel with before/after streams. However much of the whole document
291 * is linear as normally much of the original in unchanged.
292 * All parallelism comes from the patch. This normally produces two
293 * parallel stream, but in the case of a conflict can produce three.
294 * For browsing the merge we only ever show two alternates in-line.
295 * When there are three we use two panes with 1 or 2 alternates in each.
296 * So to linearise the two streams we find lines that are completely
297 * unchanged (same for all 3 streams, or missing in 2nd and 3rd) which bound
298 * a region where there are changes. We include everything between
299 * these twice, in two separate passes. The exact interpretation of the
300 * passes is handled at a higher level but will be one of:
301 * original and result
302 * before and after
303 * original and after (for a conflict)
304 * This is all encoded in the 'struct merge'. An array of these describes
305 * the whole document.
306 *
307 * At any position in the merge we can be in one of 3 states:
308 * 0: unchanged section
309 * 1: first pass
310 * 2: second pass
311 *
312 * So to walk a merge in display order we need a position in the merge,
313 * a current state, and when in a changed section, we need to know the
314 * bounds of that changed section.
315 * This is all encoded in 'struct mpos'.
316 *
317 * Each location may or may not be visible depending on certain
318 * display options.
319 *
320 * Also, some locations might be 'invalid' in that they don't need to be displayed.
321 * For example when the patch leaves a section of the original unchanged,
322 * we only need to see the original - the before/after sections are treated
323 * as invalid and are not displayed.
324 * The visibility of newlines is crucial and guides the display. One line
325 * of displayed text is all the visible sections between two visible newlines.
326 *
327 * Counting lines is a bit tricky. We only worry about line numbers in the
328 * original (stream 0) as these could compare with line numbers mentioned in
329 * patch chunks.
330 * We count 2 for every line: 1 for everything before the newline and 1 for the newline.
331 * That way we don't get a full counted line until we see the first char after the
332 * newline, so '+' lines are counted with the previous line.
333 *
334 */
335 struct mp {
336 int m; /* merger index */
337 int s; /* stream 0,1,2 for a,b,c */
338 int o; /* offset in that stream */
339 int lineno; /* Counts newlines in stream 0
340 * set lsb when see newline.
341 * add one when not newline and lsb set
342 */
343 };
344 struct mpos {
345 struct mp p, /* the current point (end of a line) */
346 lo, /* eol for start of the current group */
347 hi; /* eol for end of the current group */
348 int state; /*
349 * 0 if on an unchanged (lo/hi not meaningful)
350 * 1 if on the '-' of a diff,
351 * 2 if on the '+' of a diff
352 */
353 };
354
355 struct cursor {
356 struct mp pos; /* where in the document we are (an element) */
357 int offset; /* which char in that element */
358 int target; /* display column - or -1 if we are looking for 'pos' */
359 int col; /* where we found pos or target */
360 int width; /* Size of char, for moving to the right */
361 int alt; /* Cursor is in alternate window */
362 };
363
364 /* used for checking location during search */
same_mp(struct mp a,struct mp b)365 static int same_mp(struct mp a, struct mp b)
366 {
367 return a.m == b.m &&
368 a.s == b.s &&
369 a.o == b.o;
370 }
same_mpos(struct mpos a,struct mpos b)371 static int same_mpos(struct mpos a, struct mpos b)
372 {
373 return same_mp(a.p, b.p) &&
374 (a.state == b.state || a.state == 0 || b.state == 0);
375 }
376
377 /* Check if a particular stream is meaningful in a particular merge
378 * section. e.g. in an Unchanged section, only stream 0, the
379 * original, is meaningful. This is used to avoid walking down
380 * pointless paths.
381 */
stream_valid(int s,enum mergetype type)382 static int stream_valid(int s, enum mergetype type)
383 {
384 switch (type) {
385 case End:
386 return 1;
387 case Unmatched:
388 return s == 0;
389 case Unchanged:
390 return s == 0;
391 case Extraneous:
392 return s == 2;
393 case Changed:
394 return s != 1;
395 case Conflict:
396 return 1;
397 case AlreadyApplied:
398 return 1;
399 }
400 return 0;
401 }
402
403 /*
404 * Advance the 'pos' in the current mergepos returning the next
405 * element (word).
406 * This walks the merges in sequence, and the streams within
407 * each merge.
408 */
next_melmnt(struct mp * pos,struct file fm,struct file fb,struct file fa,struct merge * m)409 static struct elmnt next_melmnt(struct mp *pos,
410 struct file fm, struct file fb, struct file fa,
411 struct merge *m)
412 {
413 pos->o++;
414 while (pos->m < 0 || m[pos->m].type != End) {
415 int l = 0; /* Length remaining in current merge section */
416 if (pos->m >= 0)
417 switch (pos->s) {
418 case 0:
419 l = m[pos->m].al;
420 break;
421 case 1:
422 l = m[pos->m].bl;
423 break;
424 case 2:
425 l = m[pos->m].cl;
426 break;
427 }
428 if (pos->o >= l) {
429 /* Offset has reached length, choose new stream or
430 * new merge */
431 pos->o = 0;
432 do {
433 pos->s++;
434 if (pos->s > 2) {
435 pos->s = 0;
436 pos->m++;
437 }
438 } while (!stream_valid(pos->s, m[pos->m].oldtype));
439 } else
440 break;
441 }
442 if (pos->m == -1 || m[pos->m].type == End) {
443 struct elmnt e;
444 e.start = NULL; e.hash = 0; e.len = 0;
445 return e;
446 }
447 switch (pos->s) {
448 default: /* keep compiler happy */
449 case 0:
450 if (pos->lineno & 1)
451 pos->lineno++;
452 if (ends_line(fm.list[m[pos->m].a + pos->o]))
453 pos->lineno++;
454 return fm.list[m[pos->m].a + pos->o];
455 case 1: return fb.list[m[pos->m].b + pos->o];
456 case 2: return fa.list[m[pos->m].c + pos->o];
457 }
458 }
459
460 /* step current position.p backwards */
prev_melmnt(struct mp * pos,struct file fm,struct file fb,struct file fa,struct merge * m)461 static struct elmnt prev_melmnt(struct mp *pos,
462 struct file fm, struct file fb, struct file fa,
463 struct merge *m)
464 {
465 if (pos->s == 0) {
466 if (m[pos->m].a + pos->o < fm.elcnt &&
467 ends_line(fm.list[m[pos->m].a + pos->o]))
468 pos->lineno--;
469 if (pos->lineno & 1)
470 pos->lineno--;
471 }
472
473 pos->o--;
474 while (pos->m >= 0 && pos->o < 0) {
475 do {
476 pos->s--;
477 if (pos->s < 0) {
478 pos->s = 2;
479 pos->m--;
480 }
481 } while (pos->m >= 0 &&
482 !stream_valid(pos->s, m[pos->m].oldtype));
483 if (pos->m >= 0) {
484 switch (pos->s) {
485 case 0:
486 pos->o = m[pos->m].al-1;
487 break;
488 case 1:
489 pos->o = m[pos->m].bl-1;
490 break;
491 case 2:
492 pos->o = m[pos->m].cl-1;
493 break;
494 }
495 }
496 }
497 if (pos->m < 0 || m[pos->m].type == End) {
498 struct elmnt e;
499 e.start = NULL; e.hash = 0; e.len = 0;
500 return e;
501 }
502 switch (pos->s) {
503 default: /* keep compiler happy */
504 case 0: return fm.list[m[pos->m].a + pos->o];
505 case 1: return fb.list[m[pos->m].b + pos->o];
506 case 2: return fa.list[m[pos->m].c + pos->o];
507 }
508 }
509
510 /* 'visible' not only checks if this stream in this merge should be
511 * visible in this mode, but also chooses which colour/highlight to use
512 * to display it.
513 */
visible(int mode,struct merge * m,struct mpos * pos)514 static int visible(int mode, struct merge *m, struct mpos *pos)
515 {
516 enum mergetype type;
517 int stream = pos->p.s;
518
519 if (mode == 0)
520 return -1;
521 if (pos->p.m < 0)
522 type = End;
523 else if (mode & RESULT)
524 type = m[pos->p.m].type;
525 else
526 type = m[pos->p.m].oldtype;
527 /* mode can be any combination of ORIG RESULT BEFORE AFTER */
528 switch (type) {
529 case End: /* The END is always visible */
530 return A_NORMAL;
531 case Unmatched: /* Visible in ORIG and RESULT */
532 if (mode & (ORIG|RESULT))
533 return a_unmatched;
534 break;
535 case Unchanged: /* visible everywhere, but only show stream 0 */
536 if (stream == 0)
537 return a_common;
538 break;
539 case Extraneous: /* stream 2 is visible in BEFORE and AFTER */
540 if ((mode & (BEFORE|AFTER))
541 && stream == 2)
542 return a_extra;
543 break;
544 case Changed: /* stream zero visible ORIG and BEFORE, stream 2 elsewhere */
545 if (stream == 0 &&
546 (mode & (ORIG|BEFORE)))
547 return a_delete;
548 if (stream == 2 &&
549 (mode & (RESULT|AFTER)))
550 return a_added;
551 break;
552 case Conflict:
553 switch (stream) {
554 case 0:
555 if (mode & ORIG)
556 return a_unmatched | A_REVERSE;
557 break;
558 case 1:
559 if (mode & BEFORE)
560 return a_extra | A_UNDERLINE;
561 break;
562 case 2:
563 if (mode & (AFTER|RESULT))
564 return a_added | A_UNDERLINE;
565 break;
566 }
567 break;
568 case AlreadyApplied:
569 switch (stream) {
570 case 0:
571 if (mode & (ORIG|RESULT))
572 return a_already;
573 break;
574 case 1:
575 if (mode & BEFORE)
576 return a_delete | A_UNDERLINE;
577 break;
578 case 2:
579 if (mode & AFTER)
580 return a_added | A_UNDERLINE;
581 break;
582 }
583 break;
584 }
585 return -1;
586 }
587
588 /* checkline creates a summary of the sort of changes that
589 * are in a line, returning an "or" of
590 * CHANGES
591 * WIGGLED
592 * CONFLICTED
593 */
check_line(struct mpos pos,struct file fm,struct file fb,struct file fa,struct merge * m,int mode)594 static int check_line(struct mpos pos, struct file fm, struct file fb,
595 struct file fa,
596 struct merge *m, int mode)
597 {
598 int rv = 0;
599 struct elmnt e;
600 int unmatched = 0;
601
602 if (pos.p.m < 0)
603 return 0;
604 do {
605 int type = m[pos.p.m].oldtype;
606 if (mode & RESULT)
607 type = m[pos.p.m].type;
608 if (type == Changed)
609 rv |= CHANGES;
610 else if (type == Conflict) {
611 rv |= CONFLICTED | CHANGES;
612 } else if (type == AlreadyApplied) {
613 rv |= CONFLICTED;
614 if (mode & (BEFORE|AFTER))
615 rv |= CHANGES;
616 } else if (type == Extraneous) {
617 if (fb.list[m[pos.p.m].b].start[0] == '\0') {
618 /* hunk headers don't count as wiggles
619 * and nothing before a hunk header
620 * can possibly be part of this 'line' */
621 e.start = NULL;
622 break;
623 } else
624 rv |= WIGGLED;
625 } else if (type == Unmatched)
626 unmatched = 1;
627
628 if (m[pos.p.m].in_conflict > 1)
629 rv |= CONFLICTED | CHANGES;
630 if (m[pos.p.m].in_conflict == 1 &&
631 (pos.p.o < m[pos.p.m].lo ||
632 pos.p.o > m[pos.p.m].hi))
633 rv |= CONFLICTED | CHANGES;
634 e = prev_melmnt(&pos.p, fm, fb, fa, m);
635 } while (e.start != NULL &&
636 (!ends_line(e)
637 || visible(mode, m, &pos) == -1));
638 /* This is a bit of a hack... If the end-of-line just
639 * before this line was changed, then quite possibly this
640 * line is part of a change too. This is particularly important
641 * when --ignore-blanks is in effect as newlines are not separate
642 * from other words. It could be that this test needs to be
643 * strengthened when I have examined more cases.
644 */
645 if (e.start && m[pos.p.m].oldtype == Changed)
646 rv |= CHANGES;
647
648 if (unmatched && (rv & CHANGES))
649 rv |= WIGGLED;
650 return rv;
651 }
652
653 /* Find the next line in the merge which is visible.
654 * If we hit the end of a conflicted set during pass-1
655 * we rewind for pass-2.
656 * 'mode' tells which bits we want to see, possible one of
657 * the 4 parts (before/after/orig/result) or one of the pairs
658 * before+after or orig+result.
659 */
next_mline(struct mpos * pos,struct file fm,struct file fb,struct file fa,struct merge * m,int mode)660 static void next_mline(struct mpos *pos, struct file fm, struct file fb,
661 struct file fa,
662 struct merge *m, int mode)
663 {
664 int mask;
665 do {
666 struct mp prv;
667 int mode2;
668
669 prv = pos->p;
670 while (1) {
671 struct elmnt e = next_melmnt(&pos->p, fm, fb, fa, m);
672 if (e.start == NULL)
673 break;
674 if (ends_line(e) &&
675 visible(mode, m, pos) >= 0)
676 break;
677 }
678 mode2 = check_line(*pos, fm, fb, fa, m, mode);
679
680 if ((mode2 & CHANGES) && pos->state == 0) {
681 /* Just entered a diff-set */
682 pos->lo = pos->p;
683 pos->state = 1;
684 } else if (!(mode2 & CHANGES) && pos->state) {
685 /* Come to the end of a diff-set */
686 switch (pos->state) {
687 case 1:
688 /* Need to record the end */
689 pos->hi = prv;
690 /* time for another pass */
691 pos->p = pos->lo;
692 pos->state++;
693 break;
694 case 2:
695 /* finished final pass */
696 pos->state = 0;
697 break;
698 }
699 }
700 mask = ORIG|RESULT|BEFORE|AFTER;
701 switch (pos->state) {
702 case 1:
703 mask &= ~(RESULT|AFTER);
704 break;
705 case 2:
706 mask &= ~(ORIG|BEFORE);
707 break;
708 }
709 } while (visible(mode&mask, m, pos) < 0);
710
711 }
712
713 /* Move to previous line - simply the reverse of next_mline */
prev_mline(struct mpos * pos,struct file fm,struct file fb,struct file fa,struct merge * m,int mode)714 static void prev_mline(struct mpos *pos, struct file fm, struct file fb,
715 struct file fa,
716 struct merge *m, int mode)
717 {
718 int mask;
719 do {
720 struct mp prv;
721 int mode2;
722
723 prv = pos->p;
724 if (pos->p.m < 0)
725 return;
726 while (1) {
727 struct elmnt e = prev_melmnt(&pos->p, fm, fb, fa, m);
728 if (e.start == NULL)
729 break;
730 if (ends_line(e) &&
731 visible(mode, m, pos) >= 0)
732 break;
733 }
734 mode2 = check_line(*pos, fm, fb, fa, m, mode);
735
736 if ((mode2 & CHANGES) && pos->state == 0) {
737 /* Just entered a diff-set */
738 pos->hi = pos->p;
739 pos->state = 2;
740 } else if (!(mode2 & CHANGES) && pos->state) {
741 /* Come to the end (start) of a diff-set */
742 switch (pos->state) {
743 case 1:
744 /* finished final pass */
745 pos->state = 0;
746 break;
747 case 2:
748 /* Need to record the start */
749 pos->lo = prv;
750 /* time for another pass */
751 pos->p = pos->hi;
752 pos->state--;
753 break;
754 }
755 }
756 mask = ORIG|RESULT|BEFORE|AFTER;
757 switch (pos->state) {
758 case 1:
759 mask &= ~(RESULT|AFTER);
760 break;
761 case 2:
762 mask &= ~(ORIG|BEFORE);
763 break;
764 }
765 } while (visible(mode&mask, m, pos) < 0);
766 }
767
768 /* blank a whole row of display */
blank(int row,int start,int cols,unsigned int attr)769 static void blank(int row, int start, int cols, unsigned int attr)
770 {
771 (void)attrset(attr);
772 move(row, start);
773 while (cols-- > 0)
774 addch(' ');
775 }
776
777 /* search of a string on one display line. If found, update the
778 * cursor.
779 */
780
mcontains(struct mpos pos,struct file fm,struct file fb,struct file fa,struct merge * m,int mode,char * search,struct cursor * curs,int dir,int ignore_case)781 static int mcontains(struct mpos pos,
782 struct file fm, struct file fb, struct file fa,
783 struct merge *m,
784 int mode, char *search, struct cursor *curs,
785 int dir, int ignore_case)
786 {
787 /* See if any of the files, between start of this line and here,
788 * contain the search string.
789 * However this is modified by dir:
790 * -2: find last match *before* curs
791 * -1: find last match at-or-before curs
792 * 1: find first match at-or-after curs
793 * 2: find first match *after* curs
794 *
795 * We only test for equality with curs, so if it is on a different
796 * line it will not be found and everything is before/after.
797 * As we search from end-of-line to start we find the last
798 * match first.
799 * For a forward search, we stop when we find curs.
800 * For a backward search, we forget anything found when we find curs.
801 */
802 struct elmnt e;
803 int found = 0;
804 struct mp mp;
805 int o = 0;
806 int len = strlen(search);
807
808 do {
809 e = prev_melmnt(&pos.p, fm, fb, fa, m);
810 if (e.start && e.start[0]) {
811 int i;
812 int curs_i;
813 if (same_mp(pos.p, curs->pos))
814 curs_i = curs->offset;
815 else
816 curs_i = -1;
817 for (i = e.len-1; i >= 0; i--) {
818 if (i == curs_i && dir == -1)
819 /* next match is the one we want */
820 found = 0;
821 if (i == curs_i && dir == 2)
822 /* future matches not accepted */
823 goto break_while;
824 if ((!found || dir > 0) &&
825 (ignore_case ? strncasecmp : strncmp)
826 (e.start+i, search, len) == 0) {
827 mp = pos.p;
828 o = i;
829 found = 1;
830 }
831 if (i == curs_i && dir == -2)
832 /* next match is the one we want */
833 found = 0;
834 if (i == curs_i && dir == 1)
835 /* future matches not accepted */
836 goto break_while;
837 }
838 }
839 } while (e.start != NULL &&
840 (!ends_line(e)
841 || visible(mode, m, &pos) == -1));
842 break_while:
843 if (found) {
844 curs->pos = mp;
845 curs->offset = o;
846 }
847 return found;
848 }
849
850 /* Drawing the display window.
851 * There are 7 different ways we can display the data, each
852 * of which can be configured by a keystroke:
853 * o original - just show the original file with no changes, but still
854 * with highlights of what is changed or unmatched
855 * r result - show just the result of the merge. Conflicts just show
856 * the original, not the before/after options
857 * b before - show the 'before' stream of the patch
858 * a after - show the 'after' stream of the patch
859 * d diff - show just the patch, both before and after
860 * m merge - show the full merge with -+ sections for changes.
861 * If point is in a wiggled or conflicted section the
862 * window is split horizontally and the diff is shown
863 * in the bottom window
864 * | sidebyside - two panes, left and right. Left holds the merge,
865 * right holds the diff. In the case of a conflict,
866 * left holds orig/after, right holds before/after
867 *
868 * The horizontal split for 'merge' mode is managed as follows.
869 * - The window is split when we first visit a line that contains
870 * a wiggle or a conflict, and the second pane is removed when
871 * we next visit a line that contains no changes (is fully Unchanged).
872 * - to display the second pane, we find a visible end-of-line in the
873 * (BEFORE|AFTER) mode at-or-before the current end-of-line and
874 * the we centre that line.
875 * - We need to rewind to an unchanged section, and wind forward again
876 * to make sure that 'lo' and 'hi' are set properly.
877 * - every time we move, we redraw the second pane (see how that goes).
878 */
879
880 /* draw_mside draws one text line or, in the case of sidebyside, one side
881 * of a textline.
882 * The 'mode' tells us what to draw via the 'visible()' function.
883 * It is one of ORIG RESULT BEFORE AFTER or ORIG|RESULT or BEFORE|AFTER
884 * It may also have WIGGLED or CONFLICTED ored in to trigger extra highlights.
885 * The desired cursor position is given in 'target' the actual end
886 * cursor position (allowing e.g. for tabs) is returned in *colp.
887 */
draw_mside(int mode,int row,int offset,int start,int cols,struct file fm,struct file fb,struct file fa,struct merge * m,struct mpos pos,struct cursor * curs)888 static void draw_mside(int mode, int row, int offset, int start, int cols,
889 struct file fm, struct file fb, struct file fa,
890 struct merge *m,
891 struct mpos pos,
892 struct cursor *curs)
893 {
894 struct elmnt e;
895 int col = 0;
896 char tag;
897 unsigned int tag_attr;
898 int changed = 0;
899
900 switch (pos.state) {
901 default: /* keep compiler happy */
902 case 0: /* unchanged line */
903 tag = ' ';
904 tag_attr = A_NORMAL;
905 break;
906 case 1: /* 'before' text */
907 tag = '-';
908 tag_attr = a_delete;
909 if ((mode & ORIG) && (mode & CONFLICTED)) {
910 tag = '|';
911 tag_attr = a_delete | A_REVERSE;
912 }
913 mode &= (ORIG|BEFORE);
914 break;
915 case 2: /* the 'after' part */
916 tag = '+';
917 tag_attr = a_added;
918 mode &= (AFTER|RESULT);
919 break;
920 }
921
922 if (visible(mode, m, &pos) < 0) {
923 /* Not visible, just draw a blank */
924 blank(row, offset, cols, a_void);
925 if (curs) {
926 curs->width = -1;
927 curs->col = 0;
928 curs->pos = pos.p;
929 curs->offset = 0;
930 }
931 return;
932 }
933
934 (void)attrset(tag_attr);
935 mvaddch(row, offset, tag);
936 offset++;
937 cols--;
938 (void)attrset(A_NORMAL);
939
940 if (check_line(pos, fm, fb, fa, m, mode))
941 changed = 1;
942
943 /* find previous visible newline, or start of file */
944 do
945 e = prev_melmnt(&pos.p, fm, fb, fa, m);
946 while (e.start != NULL &&
947 (!ends_line(e) ||
948 visible(mode, m, &pos) == -1));
949
950 while (1) {
951 unsigned char *c;
952 unsigned int attr;
953 int highlight_space;
954 int l;
955 e = next_melmnt(&pos.p, fm, fb, fa, m);
956 if (!e.start)
957 break;
958
959 if (visible(mode, m, &pos) == -1)
960 continue;
961 if (e.start[0] == 0)
962 break;
963 c = (unsigned char *)e.start - e.prefix;
964 highlight_space = 0;
965 attr = visible(mode, m, &pos);
966 if ((attr == a_unmatched || attr == a_extra) &&
967 changed)
968 /* Only highlight spaces if there is a tab nearby */
969 for (l = 0; l < e.plen + e.prefix; l++)
970 if (c[l] == '\t')
971 highlight_space = 1;
972 if (!highlight_space && (c[0] == ' ' || c[0] == '\t')) {
973 /* always highlight space/tab at end-of-line */
974 struct mp nxt = pos.p;
975 struct elmnt nxte = next_melmnt(&nxt, fm, fb, fa, m);
976 if (nxte.start[0] == '\n')
977 highlight_space = 1;
978 }
979 for (l = 0; l < e.plen + e.prefix; l++) {
980 int scol = col;
981 if (*c == '\n')
982 break;
983 (void)attrset(attr);
984 if (*c >= ' ' && *c != 0x7f) {
985 if (highlight_space)
986 (void)attrset(attr|A_REVERSE);
987 if (col >= start && col < start+cols)
988 mvaddch(row, col-start+offset, *c);
989 col++;
990 } else if (*c == '\t') {
991 if (highlight_space)
992 (void)attrset(attr|A_UNDERLINE);
993 do {
994 if (col >= start && col < start+cols) {
995 mvaddch(row, col-start+offset, ' ');
996 } col++;
997 } while ((col&7) != 0);
998 } else {
999 if (col >= start && col < start+cols)
1000 mvaddch(row, col-start+offset, '?');
1001 col++;
1002 }
1003 if (curs) {
1004 if (curs->target >= 0) {
1005 if (curs->target < col) {
1006 /* Found target column */
1007 curs->pos = pos.p;
1008 curs->offset = l;
1009 curs->col = scol;
1010 if (scol >= start + cols)
1011 /* Didn't appear on screen */
1012 curs->width = 0;
1013 else
1014 curs->width = col - scol;
1015 curs = NULL;
1016 }
1017 } else if (l == curs->offset &&
1018 same_mp(pos.p, curs->pos)) {
1019 /* Found the pos */
1020 curs->target = scol;
1021 curs->col = scol;
1022 if (scol >= start + cols)
1023 /* Didn't appear on screen */
1024 curs->width = 0;
1025 else
1026 curs->width = col - scol;
1027 curs = NULL;
1028 }
1029 }
1030 c++;
1031 }
1032 if ((ends_line(e)
1033 && visible(mode, m, &pos) != -1))
1034 break;
1035 }
1036
1037 /* We have reached the end of visible line, or end of file */
1038 if (curs) {
1039 curs->col = col;
1040 if (col >= start + cols)
1041 curs->width = 0;
1042 else
1043 curs->width = -1; /* end of line */
1044 if (curs->target >= 0) {
1045 curs->pos = pos.p;
1046 curs->offset = 0;
1047 } else if (same_mp(pos.p, curs->pos))
1048 curs->target = col;
1049 }
1050 if (col < start)
1051 col = start;
1052 if (e.start && e.start[0] == 0) {
1053 char b[100];
1054 struct elmnt e1;
1055 int A, B, C, D, E, F;
1056 e1 = fb.list[m[pos.p.m].b + pos.p.o];
1057 sscanf(e1.start+1, "%d %d %d", &A, &B, &C);
1058 sscanf(e.start+1, "%d %d %d", &D, &E, &F);
1059 snprintf(b, sizeof(b), "@@ -%d,%d +%d,%d @@%s", B, C, E, F, e1.start+18);
1060 (void)attrset(a_sep);
1061
1062 mvaddstr(row, col-start+offset, b);
1063 col += strlen(b);
1064 }
1065 blank(row, col-start+offset, start+cols-col,
1066 e.start
1067 ? (unsigned)visible(mode, m, &pos)
1068 : A_NORMAL);
1069 }
1070
1071 /* Draw either 1 or 2 sides depending on the mode. */
1072
draw_mline(int mode,int row,int start,int cols,struct file fm,struct file fb,struct file fa,struct merge * m,struct mpos pos,struct cursor * curs)1073 static void draw_mline(int mode, int row, int start, int cols,
1074 struct file fm, struct file fb, struct file fa,
1075 struct merge *m,
1076 struct mpos pos,
1077 struct cursor *curs)
1078 {
1079 /*
1080 * Draw the left and right images of this line
1081 * One side might be a_blank depending on the
1082 * visibility of this newline
1083 */
1084 int lcols, rcols;
1085
1086 mode |= check_line(pos, fm, fb, fa, m, mode);
1087
1088 if ((mode & (BEFORE|AFTER)) &&
1089 (mode & (ORIG|RESULT))) {
1090
1091 lcols = (cols-1)/2;
1092 rcols = cols - lcols - 1;
1093
1094 (void)attrset(A_STANDOUT);
1095 mvaddch(row, lcols, '|');
1096
1097 draw_mside(mode&~(BEFORE|AFTER), row, 0, start, lcols,
1098 fm, fb, fa, m, pos, curs && !curs->alt ? curs : NULL);
1099
1100 draw_mside(mode&~(ORIG|RESULT), row, lcols+1, start, rcols,
1101 fm, fb, fa, m, pos, curs && curs->alt ? curs : NULL);
1102 } else
1103 draw_mside(mode, row, 0, start, cols,
1104 fm, fb, fa, m, pos, curs);
1105 }
1106
1107 static char *merge_help[] = {
1108 "This view shows the merge of the patch with the",
1109 "original file. It is like a full-context diff showing",
1110 "removed lines with a '-' prefix and added lines with a",
1111 "'+' prefix.",
1112 "In cases where a patch chunk could not be successfully",
1113 "applied, the original text is prefixed with a '|', and",
1114 "the text that the patch wanted to add is prefixed with",
1115 "a '+'.",
1116 "When the cursor is over such a conflict, or over a chunk",
1117 "which required wiggling to apply (i.e. there was unmatched",
1118 "text in the original, or extraneous unchanged text in",
1119 "the patch), the terminal is split and the bottom pane is",
1120 "use to display the part of the patch that applied to",
1121 "this section of the original. This allows you to confirm",
1122 "that a wiggled patch applied correctly, and to see",
1123 "why there was a conflict",
1124 NULL
1125 };
1126 static char *diff_help[] = {
1127 "This is the 'diff' or 'patch' view. It shows",
1128 "only the patch that is being applied without the",
1129 "original to which it is being applied.",
1130 "Underlined text indicates parts of the patch which",
1131 "resulted in a conflict when applied to the",
1132 "original.",
1133 NULL
1134 };
1135 static char *orig_help[] = {
1136 "This is the 'original' view which simply shows",
1137 "the original file before applying the patch.",
1138 "Sections of code that would be changed by the patch",
1139 "are highlighted in red.",
1140 NULL
1141 };
1142 static char *result_help[] = {
1143 "This is the 'result' view which shows just the",
1144 "result of applying the patch. When a conflict",
1145 "occurred this view does not show the full conflict",
1146 "but only the 'after' part of the patch. To see",
1147 "the full conflict, use the 'merge' or 'sidebyside'",
1148 "views.",
1149 NULL
1150 };
1151 static char *before_help[] = {
1152 "This view shows the 'before' section of a patch.",
1153 "It allows the expected match text to be seen uncluttered",
1154 "by text that is meant to replaced it.",
1155 "Red text is text that will be removed by the patch",
1156 NULL
1157 };
1158 static char *after_help[] = {
1159 "This view shows the 'after' section of a patch.",
1160 "It allows the intended result to be seen uncluttered",
1161 "by text that was meant to be matched and replaced.",
1162 "Green text is text that was added by the patch - it",
1163 "was not present in the 'before' part of the patch",
1164 NULL
1165 };
1166 static char *sidebyside_help[] = {
1167 "This is the Side By Side view of a patched file.",
1168 "The left side shows the original and the result.",
1169 "The right side shows the patch which was applied",
1170 "and lines up with the original/result as much as",
1171 "possible.",
1172 "",
1173 "Where one side has no line which matches the",
1174 "other side it is displayed as a solid colour in the",
1175 "yellow family (depending on your terminal window).",
1176 NULL
1177 };
1178 static char *merge_window_help[] = {
1179 " Highlight Colours and Keystroke commands",
1180 "",
1181 "In all different views of a merge, highlight colours",
1182 "are used to show which parts of lines were added,",
1183 "removed, already changed, unchanged or in conflict.",
1184 "Colours and their use are:",
1185 " normal unchanged text",
1186 " red text that was removed or changed",
1187 " green text that was added or the result",
1188 " of a change",
1189 " yellow background used in side-by-side for a line",
1190 " which has no match on the other",
1191 " side",
1192 " blue text in the original which did not",
1193 " match anything in the patch",
1194 " cyan text in the patch which did not",
1195 " match anything in the original",
1196 " cyan background already changed text: the result",
1197 " of the patch matches the original",
1198 " underline remove or added text can also be",
1199 " underlined indicating that it",
1200 " was involved in a conflict",
1201 "",
1202 "While viewing a merge various keystroke commands can",
1203 "be used to move around and change the view. Basic",
1204 "movement commands from both 'vi' and 'emacs' are",
1205 "available:",
1206 "",
1207 " p control-p k UP Move to previous line",
1208 " n control-n j DOWN Move to next line",
1209 " l LEFT Move one char to right",
1210 " h RIGHT Move one char to left",
1211 " / control-s Enter incremental search mode",
1212 " control-r Enter reverse-search mode",
1213 " control-g Search again",
1214 " ? Display help message",
1215 " ESC-< 0-G Go to start of file",
1216 " ESC-> G Go to end of file",
1217 " q Return to list of files or exit",
1218 " S Arrange for merge to be saved on exit",
1219 " control-C Disable auto-save-on-exit",
1220 " control-L recenter current line",
1221 " control-V SPACE page down",
1222 " ESC-v BACKSPC page up",
1223 " N go to next patch chunk",
1224 " P go to previous patch chunk",
1225 " C go to next conflicted chunk",
1226 " C-X-o O move cursor to alternate pane",
1227 " ^ control-A go to start of line",
1228 " $ control-E go to end of line",
1229 "",
1230 " a display 'after' view",
1231 " b display 'before' view",
1232 " o display 'original' view",
1233 " r display 'result' view",
1234 " d display 'diff' or 'patch' view",
1235 " m display 'merge' view",
1236 " | display side-by-side view",
1237 "",
1238 " I toggle whether spaces are ignored",
1239 " when matching text.",
1240 " x toggle ignoring of current Changed,",
1241 " Conflict, or Unmatched item",
1242 " c toggle accepting of result of conflict",
1243 " X Revert 'c' and 'x' changes on this line",
1244 " v Save the current merge and run the",
1245 " default editor on the file.",
1246 NULL
1247 };
1248 static char *save_query[] = {
1249 "",
1250 "You have modified the merge.",
1251 "Would you like to save it?",
1252 " Y = save the modified merge",
1253 " N = discard modifications, don't save",
1254 " Q = return to viewing modified merge",
1255 NULL
1256 };
1257
1258 static char *toggle_ignore[] = {
1259 "",
1260 "You have modified the merge.",
1261 "Toggling ignoring of spaces will discard changes.",
1262 "Do you want to proceed?",
1263 " Y = discard changes and toggle ignoring of spaces",
1264 " N = keep changes, don't toggle",
1265 NULL
1266 };
1267
do_edit(char * file,int line)1268 static void do_edit(char *file, int line)
1269 {
1270 char *ed = getenv("VISUAL");
1271 char linebuf[20];
1272 if (!ed)
1273 ed = getenv("EDITOR");
1274 if (!ed)
1275 ed = "/usr/bin/edit";
1276 snprintf(linebuf, sizeof(linebuf), "+%d", line);
1277 switch(fork()) {
1278 case 0:
1279 execlp(ed, ed, linebuf, file, NULL);
1280 exit(2);
1281 case -1:
1282 break;
1283 default:
1284 wait(NULL);
1285 }
1286 }
1287
memdup(void * a,int len)1288 static void *memdup(void *a, int len)
1289 {
1290 char *r = malloc(len);
1291 memcpy(r, a, len);
1292 return r;
1293 }
1294
1295
save_merge(struct file a,struct file b,struct file c,struct merge * merger,char * file,int backup)1296 static int save_merge(struct file a, struct file b, struct file c,
1297 struct merge *merger, char *file, int backup)
1298 {
1299 char *replacename = wiggle_xmalloc(strlen(file) + 20);
1300 char *orignew = wiggle_xmalloc(strlen(file) + 20);
1301 int fd;
1302 FILE *outfile;
1303 int err = 0;
1304 int lineno = 0;
1305 strcpy(replacename, file);
1306 strcat(replacename, "XXXXXX");
1307 strcpy(orignew, file);
1308 strcat(orignew, ".porig");
1309
1310 fd = mkstemp(replacename);
1311 if (fd < 0) {
1312 err = -1;
1313 goto out;
1314 }
1315 outfile = fdopen(fd, "w");
1316 lineno = wiggle_print_merge(outfile, &a, &b, &c, 0, merger,
1317 NULL, 0, 0);
1318 fclose(outfile);
1319 if (backup && rename(file, orignew) != 0)
1320 err = -2;
1321 else if (rename(replacename, file) != 0)
1322 err = -2;
1323
1324 out:
1325 free(replacename);
1326 free(orignew);
1327 return err < 0 ? err : lineno;
1328 }
1329
save_tmp_merge(struct file a,struct file b,struct file c,struct merge * merger,char ** filep,struct merge * mpos,int streampos,int offsetpos)1330 static int save_tmp_merge(struct file a, struct file b, struct file c,
1331 struct merge *merger, char **filep,
1332 struct merge *mpos, int streampos, int offsetpos)
1333 {
1334 int fd;
1335 FILE *outfile;
1336 char *dir, *fname;
1337 int lineno;
1338 int suffix = 0;
1339
1340 if (!*filep) {
1341 dir = getenv("TMPDIR");
1342 if (!dir)
1343 dir = "/tmp";
1344
1345 asprintf(&fname, "%s/wiggle-tmp-XXXXXX", dir);
1346 } else {
1347 char *base;
1348 dir = *filep;
1349 base = strrchr(dir, '/');
1350 if (base)
1351 base++;
1352 else
1353 base = dir;
1354 asprintf(&fname, "%.*stmp-XXXXXX-%s", (int)(base-dir), dir, base);
1355 suffix = strlen(base)+1;
1356 }
1357 fd = mkstemps(fname, suffix);
1358
1359 if (fd < 0) {
1360 free(fname);
1361 *filep = NULL;
1362 return -1;
1363 }
1364 outfile = fdopen(fd, "w");
1365 lineno = wiggle_print_merge(outfile, &a, &b, &c, 0, merger,
1366 mpos, streampos, offsetpos);
1367 fclose(outfile);
1368 *filep = fname;
1369 return lineno;
1370 }
1371
merge_window(struct plist * p,FILE * f,int reverse,int replace,int selftest,int ignore_blanks,int just_diff,int backup)1372 static int merge_window(struct plist *p, FILE *f, int reverse, int replace,
1373 int selftest, int ignore_blanks, int just_diff, int backup)
1374 {
1375 /* Display the merge window in one of the selectable modes,
1376 * starting with the 'merge' mode.
1377 *
1378 * Newlines are the key to display.
1379 * 'pos' is always a visible newline (or eof).
1380 * In sidebyside mode it might only be visible on one side,
1381 * in which case the other side will be blank.
1382 * Where the newline is visible, we rewind the previous visible
1383 * newline visible and display the stuff in between
1384 *
1385 * A 'position' is a struct mpos
1386 */
1387
1388 struct stream sm, sb, sa, sp; /* main, before, after, patch */
1389 struct file fm, fb, fa;
1390 struct csl *csl1, *csl2;
1391 struct ci ci;
1392 int ch; /* count of chunks */
1393 /* Always refresh the current line.
1394 * If refresh == 1, refresh all lines. If == 2, clear first
1395 */
1396 int refresh = 2;
1397 int rows = 0, cols = 0;
1398 int splitrow = -1; /* screen row for split - diff appears below */
1399 int lastrow = 0; /* end of screen, or just above 'splitrow' */
1400 int i, c, cswitch;
1401 MEVENT mevent;
1402 int mode = just_diff ? (BEFORE|AFTER) : (ORIG|RESULT);
1403 int mmode = mode; /* Mode for moving - used when in 'other' pane */
1404 char *modename = just_diff ? "diff" : "merge";
1405 char **modehelp = just_diff ? diff_help : merge_help;
1406 char *mesg = NULL;
1407
1408 int row, start = 0;
1409 int trow; /* screen-row while searching. If we cannot find,
1410 * we forget this number */
1411 struct cursor curs;
1412 struct mpos pos; /* current point */
1413 struct mpos tpos, /* temp point while drawing lines above and below pos */
1414 toppos, /* pos at top of screen - for page-up */
1415 botpos; /* pos at bottom of screen - for page-down */
1416 struct mpos vispos;
1417 int botrow = 0;
1418 int meta = 0, /* mode for multi-key commands- SEARCH or META */
1419 tmeta;
1420 int num = -1, /* numeric arg being typed. */
1421 tnum;
1422 int lineno;
1423 int changes = 0; /* If any edits have been made to the merge */
1424 int answer; /* answer to 'save changes?' question */
1425 char *tempname;
1426 struct elmnt e;
1427 char search[80]; /* string we are searching for */
1428 unsigned int searchlen = 0;
1429 int search_notfound = 0;
1430 int searchdir = 0;
1431 /* ignore_case:
1432 * 0 == no
1433 * 1 == no because there are upper-case chars
1434 * 2 == yes as there are no upper-case chars
1435 * 3 == yes
1436 */
1437 int ignore_case = 2;
1438 /* We record all the places we find so 'backspace'
1439 * can easily return to the previous one
1440 */
1441 struct search_anchor {
1442 struct search_anchor *next;
1443 struct mpos pos;
1444 struct cursor curs;
1445 int notfound;
1446 int row, start;
1447 unsigned int searchlen;
1448 } *anchor = NULL;
1449
1450 #define free_stuff(none) \
1451 do { \
1452 free(fm.list); \
1453 free(fb.list); \
1454 free(fa.list); \
1455 free(csl1); \
1456 free(csl2); \
1457 free(ci.merger); \
1458 } while(0)
1459
1460 #define find_line(ln) \
1461 do { \
1462 pos.p.m = 0; /* merge node */ \
1463 pos.p.s = 0; /* stream number */ \
1464 pos.p.o = -1; /* offset */ \
1465 pos.p.lineno = 1; \
1466 pos.state = 0; \
1467 memset(&curs, 0, sizeof(curs)); \
1468 do \
1469 next_mline(&pos, fm, fb, fa, ci.merger, mode); \
1470 while (pos.p.lineno < ln && ci.merger[pos.p.m].type != End); \
1471 } while(0)
1472
1473 #define prepare_merge(ch) \
1474 do { \
1475 /* FIXME check for errors in the stream */ \
1476 fm = wiggle_split_stream(sm, ByWord | ignore_blanks); \
1477 fb = wiggle_split_stream(sb, ByWord | ignore_blanks); \
1478 fa = wiggle_split_stream(sa, ByWord | ignore_blanks); \
1479 \
1480 if (ch && !just_diff) \
1481 csl1 = wiggle_pdiff(fm, fb, ch); \
1482 else \
1483 csl1 = wiggle_diff(fm, fb, 1); \
1484 csl2 = wiggle_diff_patch(fb, fa, 1); \
1485 \
1486 ci = wiggle_make_merger(fm, fb, fa, csl1, csl2, 0, 1, 0); \
1487 for (i = 0; ci.merger[i].type != End; i++) \
1488 ci.merger[i].oldtype = ci.merger[i].type; \
1489 } while(0)
1490
1491 if (selftest) {
1492 intr_kills = 1;
1493 selftest = 1;
1494 }
1495
1496 if (f == NULL) {
1497 if (!p->is_merge) {
1498 /* three separate files */
1499 sb = wiggle_load_file(p->before);
1500 sa = wiggle_load_file(p->after);
1501 if (just_diff)
1502 sm = sb;
1503 else
1504 sm = wiggle_load_file(p->file);
1505 } else {
1506 /* One merge file */
1507 sp = wiggle_load_file(p->file);
1508 if (reverse)
1509 wiggle_split_merge(sp, &sm, &sa, &sb);
1510 else
1511 wiggle_split_merge(sp, &sm, &sb, &sa);
1512 free(sp.body);
1513 }
1514 ch = 0;
1515 } else {
1516 sp = wiggle_load_segment(f, p->start, p->end);
1517 if (p->is_merge) {
1518 if (reverse)
1519 wiggle_split_merge(sp, &sm, &sa, &sb);
1520 else
1521 wiggle_split_merge(sp, &sm, &sb, &sa);
1522 ch = 0;
1523 } else {
1524 if (reverse)
1525 ch = wiggle_split_patch(sp, &sa, &sb);
1526 else
1527 ch = wiggle_split_patch(sp, &sb, &sa);
1528 if (just_diff)
1529 sm = sb;
1530 else
1531 sm = wiggle_load_file(p->file);
1532 }
1533 free(sp.body);
1534 }
1535 if (!sm.body || !sb.body || !sa.body) {
1536 if (!just_diff)
1537 free(sm.body);
1538 free(sb.body);
1539 free(sa.body);
1540 term_init(1);
1541 if (!sm.body)
1542 help_window(help_missing, NULL, 0);
1543 else
1544 help_window(help_corrupt, NULL, 0);
1545 endwin();
1546 return 0;
1547 }
1548 prepare_merge(ch);
1549 term_init(!selftest);
1550
1551 row = 1;
1552 find_line(1);
1553
1554 while (1) {
1555 unsigned int next;
1556 if (refresh >= 2) {
1557 clear();
1558 refresh = 1;
1559 }
1560 if (row < 1 || row >= lastrow)
1561 refresh = 1;
1562 if (curs.alt)
1563 refresh = 1;
1564
1565 if (mode == (ORIG|RESULT)) {
1566 int cmode = check_line(pos, fm, fb, fa, ci.merger, mode);
1567 if (cmode & (WIGGLED | CONFLICTED)) {
1568 if (splitrow < 0) {
1569 splitrow = (rows+1)/2;
1570 lastrow = splitrow - 1;
1571 refresh = 1;
1572 }
1573 } else if (!curs.alt && splitrow >= 0) {
1574 splitrow = -1;
1575 lastrow = rows-1;
1576 refresh = 1;
1577 }
1578 } else if (splitrow >= 0) {
1579 splitrow = -1;
1580 lastrow = rows-1;
1581 refresh = 1;
1582 }
1583
1584 if (refresh) {
1585 getmaxyx(stdscr, rows, cols);
1586 rows--; /* keep last row clear */
1587 if (splitrow >= 0) {
1588 splitrow = (rows+1)/2;
1589 lastrow = splitrow - 1;
1590 } else
1591 lastrow = rows - 1;
1592
1593 if (row < -3)
1594 row = lastrow/2+1;
1595 if (row < 1)
1596 row = 1;
1597 if (row > lastrow+3)
1598 row = lastrow/2+1;
1599 if (row >= lastrow)
1600 row = lastrow-1;
1601 }
1602
1603 /* Always refresh the line */
1604 while (start > curs.target) {
1605 start -= 8;
1606 refresh = 1;
1607 }
1608 if (start < 0)
1609 start = 0;
1610 vispos = pos; /* visible position - if cursor is in
1611 * alternate pane, pos might not be visible
1612 * in main pane. */
1613 if (check_line(vispos, fm, fb, fa, ci.merger, mode)
1614 & CHANGES) {
1615 if (vispos.state == 0) {
1616 vispos.state = 1;
1617 vispos.lo = vispos.p;
1618 vispos.hi = vispos.p;
1619 }
1620 } else {
1621 vispos.state = 0;
1622 }
1623
1624 if (visible(mode, ci.merger, &vispos) < 0)
1625 prev_mline(&vispos, fm, fb, fa, ci.merger, mode);
1626 if (!curs.alt)
1627 pos= vispos;
1628 retry:
1629 draw_mline(mode, row, start, cols, fm, fb, fa, ci.merger,
1630 vispos, (splitrow >= 0 && curs.alt) ? NULL : &curs);
1631 if (curs.width == 0 && start < curs.col) {
1632 /* width == 0 implies it appear after end-of-screen */
1633 start += 8;
1634 refresh = 1;
1635 goto retry;
1636 }
1637 if (curs.col < start) {
1638 start -= 8;
1639 refresh = 1;
1640 if (start < 0)
1641 start = 0;
1642 goto retry;
1643 }
1644 if (refresh) {
1645 refresh = 0;
1646
1647 tpos = vispos;
1648
1649 for (i = row-1; i >= 1 && tpos.p.m >= 0; ) {
1650 prev_mline(&tpos, fm, fb, fa, ci.merger, mode);
1651 draw_mline(mode, i--, start, cols,
1652 fm, fb, fa, ci.merger,
1653 tpos, NULL);
1654
1655 }
1656 if (i > 0) {
1657 row -= (i+1);
1658 refresh = 1;
1659 goto retry;
1660 }
1661 toppos = tpos;
1662 while (i >= 1)
1663 blank(i--, 0, cols, a_void);
1664 tpos = vispos;
1665 for (i = row; i <= lastrow && ci.merger[tpos.p.m].type != End; ) {
1666 draw_mline(mode, i++, start, cols,
1667 fm, fb, fa, ci.merger,
1668 tpos, NULL);
1669 next_mline(&tpos, fm, fb, fa, ci.merger, mode);
1670 }
1671 botpos = tpos; botrow = i;
1672 while (i <= lastrow)
1673 blank(i++, 0, cols, a_void);
1674 }
1675
1676 if (splitrow >= 0) {
1677 struct mpos spos = pos;
1678 int smode = BEFORE|AFTER;
1679 int srow = (rows + splitrow)/2;
1680 if (check_line(spos, fm, fb, fa, ci.merger, smode)
1681 & CHANGES) {
1682 if (spos.state == 0)
1683 spos.state = 1;
1684 } else {
1685 spos.state = 0;
1686 }
1687 if (visible(smode, ci.merger, &spos) < 0)
1688 prev_mline(&spos, fm, fb, fa, ci.merger, smode);
1689 /* Now hi/lo might be wrong, so lets fix it. */
1690 tpos = spos;
1691 if (spos.state)
1692 /* 'hi' might be wrong so we mustn't depend
1693 * on it while walking back. So set state
1694 * to 1 to avoid ever testing it.
1695 */
1696 spos.state = 1;
1697 while (spos.p.m >= 0 && spos.state != 0)
1698 prev_mline(&spos, fm, fb, fa, ci.merger, smode);
1699 while (!same_mpos(spos, tpos) &&
1700 spos.p.m >= 0 &&
1701 ci.merger[spos.p.m].type != End)
1702 next_mline(&spos, fm, fb, fa, ci.merger, smode);
1703
1704 (void)attrset(a_sep);
1705 for (i = 0; i < cols; i++)
1706 mvaddstr(splitrow, i, "-");
1707
1708 tpos = spos;
1709 for (i = srow-1; i > splitrow; i--) {
1710 prev_mline(&tpos, fm, fb, fa, ci.merger, smode);
1711 draw_mline(smode, i, start, cols, fm, fb, fa, ci.merger,
1712 tpos, NULL);
1713 }
1714 while (i > splitrow)
1715 blank(i--, 0, cols, a_void);
1716 tpos = spos;
1717 for (i = srow;
1718 i < rows && tpos.p.m >= 0 &&
1719 ci.merger[tpos.p.m].type != End;
1720 i++) {
1721 draw_mline(smode, i, start, cols, fm, fb, fa, ci.merger,
1722 tpos,
1723 (i == srow && curs.alt) ? &curs : NULL);
1724 next_mline(&tpos, fm, fb, fa, ci.merger, smode);
1725 }
1726 while (i < rows)
1727 blank(i++, 0, cols, a_void);
1728 }
1729 /* Now that curs is accurate, report the type */
1730 {
1731 int l = 0;
1732 char buf[100];
1733 if (p->file)
1734 l = snprintf(buf, 100, "File: %s%s ",
1735 p->file, reverse ? " - reversed" : "");
1736 snprintf(buf+l, 100-l, "Mode: %s", modename);
1737 (void)attrset(A_BOLD);
1738 mvaddstr(0, 0, buf);
1739 (void)attrset(A_NORMAL);
1740 if (ignore_blanks)
1741 addstr(" (ignoring blanks)");
1742 clrtoeol();
1743 (void)attrset(A_BOLD);
1744 if (ci.merger[curs.pos.m].type != ci.merger[curs.pos.m].oldtype)
1745 l = snprintf(buf, sizeof(buf), "%s->", typenames[ci.merger[curs.pos.m].oldtype]);
1746 snprintf(buf+l, sizeof(buf)-l, "%s ln:%d",
1747 typenames[ci.merger[curs.pos.m].type],
1748 (pos.p.lineno-1)/2);
1749 mvaddstr(0, cols - strlen(buf) - 1, buf);
1750 }
1751 #define META(c) ((c)|0x1000)
1752 #define SEARCH(c) ((c)|0x2000)
1753 #define CTRLX(c) ((c)|0x4000)
1754 move(rows, 0);
1755 (void)attrset(A_NORMAL);
1756 if (mesg) {
1757 attrset(A_REVERSE);
1758 addstr(mesg);
1759 mesg = NULL;
1760 attrset(A_NORMAL);
1761 }
1762 if (num >= 0) {
1763 char buf[12+1];
1764 snprintf(buf, sizeof(buf), "%d ", num);
1765 addstr(buf);
1766 }
1767 if (meta & META(0))
1768 addstr("ESC...");
1769 if (meta & CTRLX(0))
1770 addstr("C-x ");
1771 if (meta & SEARCH(0)) {
1772 if (searchdir < 0)
1773 addstr("Backwards ");
1774 addstr("Search: ");
1775 addstr(search);
1776 if (search_notfound)
1777 addstr(" - Not Found.");
1778 search_notfound = 0;
1779 }
1780 clrtoeol();
1781 /* '+1' to skip over the leading +/-/| char */
1782 if (curs.alt && splitrow > 0)
1783 move((rows + splitrow)/2, curs.col - start + 1);
1784 else if (curs.alt && ((mode & (BEFORE|AFTER)) &&
1785 (mode & (ORIG|RESULT))))
1786 move(row, curs.col-start + (cols-1)/2+2);
1787 else
1788 move(row, curs.col-start+1);
1789 switch (selftest) {
1790 case 0:
1791 c = getch(); break;
1792 case 1:
1793 c = 'n'; break;
1794 case 2:
1795 c = 'q'; break;
1796 }
1797 tmeta = meta; meta = 0;
1798 tnum = num; num = -1;
1799 cswitch = c | tmeta;
1800 /* Handle some ranges */
1801 /* case '0' ... '9': */
1802 if (cswitch >= '0' && cswitch <= '9')
1803 cswitch = '0';
1804 /* case SEARCH(' ') ... SEARCH('~'): */
1805 if (cswitch >= SEARCH(' ') && cswitch <= SEARCH('~'))
1806 cswitch = SEARCH(' ');
1807
1808 switch (cswitch) {
1809 case 27: /* escape */
1810 case META(27):
1811 meta = META(0);
1812 break;
1813
1814 case 'X'-64:
1815 case META('X'-64):
1816 meta = CTRLX(0);
1817 break;
1818
1819 case META('<'): /* start of file */
1820 start:
1821 tpos = pos; row++;
1822 do {
1823 pos = tpos; row--;
1824 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1825 } while (tpos.p.m >= 0);
1826 if (row <= 0)
1827 row = 0;
1828 break;
1829 case META('>'): /* end of file */
1830 case 'G':
1831 if (tnum >= 0)
1832 goto start;
1833 tpos = pos; row--;
1834 do {
1835 pos = tpos; row++;
1836 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
1837 } while (ci.merger[tpos.p.m].type != End);
1838 if (row >= lastrow)
1839 row = lastrow;
1840 break;
1841 case '0': /* actually '0'...'9' */
1842 if (tnum < 0)
1843 tnum = 0;
1844 num = tnum*10 + (c-'0');
1845 break;
1846 case 'C'-64:
1847 if (replace)
1848 mesg = "Autosave disabled";
1849 else
1850 mesg = "Use 'q' to quit";
1851 replace = 0;
1852 break;
1853 case 'S':
1854 mesg = "Will auto-save on exit, using Ctrl-C to cancel";
1855 replace = 1;
1856 break;
1857 case 'q':
1858 refresh = 2;
1859 answer = 0;
1860 if (replace)
1861 answer = 1;
1862 else if (changes)
1863 answer = help_window(save_query, NULL, 1);
1864 if (answer < 0)
1865 break;
1866 if (answer) {
1867 p->wiggles = 0;
1868 p->conflicts = wiggle_isolate_conflicts(
1869 fm, fb, fa, csl1, csl2, 0,
1870 ci.merger, 0, &p->wiggles);
1871 p->chunks = p->conflicts;
1872 save_merge(fm, fb, fa, ci.merger,
1873 p->outfile ? p->outfile : p->file,
1874 backup && (p->outfile ? 0 : !p->is_merge));
1875 }
1876 if (!just_diff)
1877 free(sm.body);
1878 free(sb.body);
1879 free(sa.body);
1880 free_stuff();
1881 endwin();
1882 return answer;
1883
1884 case 'I': /* Toggle ignoring of spaces */
1885 if (changes) {
1886 refresh = 2;
1887 answer = help_window(toggle_ignore, NULL, 1);
1888 if (answer <= 0)
1889 break;
1890 changes = 0;
1891 }
1892 free_stuff();
1893 ignore_blanks = ignore_blanks ? 0 : IgnoreBlanks;
1894 prepare_merge(ch);
1895 find_line(pos.p.lineno);
1896
1897 refresh = 2;
1898 break;
1899
1900 case 'v':
1901 if (!p->file || just_diff) {
1902 mesg = "Cannot run editor when diffing";
1903 break;
1904 }
1905 tempname = p->file;
1906 lineno = save_tmp_merge(fm, fb, fa, ci.merger,
1907 &tempname,
1908 ci.merger + pos.p.m,
1909 pos.p.s,
1910 pos.p.o);
1911 endwin();
1912 free_stuff();
1913 do_edit(tempname, lineno);
1914 sp = wiggle_load_file(tempname);
1915 unlink(tempname);
1916 wiggle_split_merge(sp, &sm, &sb, &sa);
1917 if (sp.len == sm.len &&
1918 memcmp(sp.body, sm.body, sm.len) == 0 &&
1919 !p->is_merge) {
1920 /* no conflicts left, so display diff */
1921 free(sm.body);
1922 sm = wiggle_load_file(p->file);
1923 free(sb.body);
1924 sb = sm;
1925 sb.body = memdup(sm.body, sm.len);
1926 }
1927 free(sp.body);
1928 ignore_blanks = 0;
1929 prepare_merge(0);
1930 refresh = 2;
1931 changes = 1;
1932
1933 find_line(pos.p.lineno);
1934
1935 doupdate();
1936 break;
1937
1938 case '/':
1939 case 'S'-64:
1940 /* incr search forward */
1941 meta = SEARCH(0);
1942 searchlen = 0;
1943 search[searchlen] = 0;
1944 searchdir = 1;
1945 break;
1946 case '\\':
1947 case 'R'-64:
1948 /* incr search backwards */
1949 meta = SEARCH(0);
1950 searchlen = 0;
1951 search[searchlen] = 0;
1952 searchdir = -1;
1953 break;
1954 case SEARCH('G'-64):
1955 case SEARCH('S'-64):
1956 case SEARCH('R'-64):
1957 /* search again */
1958 if ((c|tmeta) == SEARCH('R'-64))
1959 searchdir = -2;
1960 else
1961 searchdir = 2;
1962 meta = SEARCH(0);
1963 tpos = pos; trow = row;
1964 goto search_again;
1965
1966 case SEARCH('H'-64):
1967 case SEARCH(KEY_BACKSPACE):
1968 meta = SEARCH(0);
1969 if (anchor) {
1970 struct search_anchor *a;
1971 a = anchor;
1972 anchor = a->next;
1973 free(a);
1974 }
1975 if (anchor) {
1976 struct search_anchor *a;
1977 a = anchor;
1978 anchor = a->next;
1979 pos = a->pos;
1980 row = a->row;
1981 start = a->start;
1982 curs = a->curs;
1983 curs.target = -1;
1984 search_notfound = a->notfound;
1985 searchlen = a->searchlen;
1986 search[searchlen] = 0;
1987 free(a);
1988 refresh = 1;
1989 }
1990 break;
1991 case SEARCH(' '): /* actually ' '...'~' */
1992 case SEARCH('\t'):
1993 meta = SEARCH(0);
1994 if (searchlen < sizeof(search)-1)
1995 search[searchlen++] = c & (0x7f);
1996 search[searchlen] = 0;
1997 tpos = pos; trow = row;
1998 search_again:
1999 search_notfound = 1;
2000 if (ignore_case == 1 || ignore_case == 2) {
2001 unsigned int i;
2002 ignore_case = 2;
2003 for (i=0; i < searchlen; i++)
2004 if (isupper(search[i])) {
2005 ignore_case = 1;
2006 break;
2007 }
2008 }
2009 do {
2010 if (mcontains(tpos, fm, fb, fa, ci.merger,
2011 mmode, search, &curs, searchdir,
2012 ignore_case >= 2)) {
2013 curs.target = -1;
2014 pos = tpos;
2015 row = trow;
2016 search_notfound = 0;
2017 break;
2018 }
2019 if (searchdir < 0) {
2020 trow--;
2021 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2022 } else {
2023 trow++;
2024 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2025 }
2026 } while (tpos.p.m >= 0 && ci.merger[tpos.p.m].type != End);
2027 searchdir /= abs(searchdir);
2028
2029 break;
2030 case 'L'-64:
2031 refresh = 2;
2032 row = lastrow / 2;
2033 break;
2034
2035 case KEY_NPAGE:
2036 case ' ':
2037 case 'V'-64: /* page down */
2038 pos = botpos;
2039 if (botrow <= lastrow) {
2040 row = botrow;
2041 if (selftest == 1)
2042 selftest = 2;
2043 } else
2044 row = 2;
2045 refresh = 1;
2046 break;
2047 case KEY_PPAGE:
2048 case KEY_BACKSPACE:
2049 case META('v'): /* page up */
2050 pos = toppos;
2051 row = lastrow-1;
2052 refresh = 1;
2053 break;
2054
2055 case KEY_MOUSE:
2056 if (getmouse(&mevent) != OK)
2057 break;
2058 /* First see if this is on the 'other' pane */
2059 if (splitrow > 0) {
2060 /* merge mode, top and bottom */
2061 if ((curs.alt && mevent.y < splitrow) ||
2062 (!curs.alt && mevent.y > splitrow)) {
2063 goto other_pane;
2064 }
2065 } else if (mode == (ORIG|RESULT|BEFORE|AFTER)) {
2066 /* side-by-side mode */
2067 if ((curs.alt && mevent.x < cols/2) ||
2068 (!curs.alt && mevent.x > cols/2)) {
2069 goto other_pane;
2070 }
2071 }
2072 /* Now try to find the right line */
2073 if (splitrow < 0 || !curs.alt)
2074 trow = row;
2075 else
2076 trow = (rows + splitrow)/2;
2077 while (trow > mevent.y) {
2078 tpos = pos;
2079 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2080 if (tpos.p.m >= 0) {
2081 pos = tpos;
2082 trow--;
2083 } else
2084 break;
2085 }
2086 while (trow < mevent.y) {
2087 tpos = pos;
2088 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2089 if (ci.merger[tpos.p.m].type != End) {
2090 pos = tpos;
2091 trow++;
2092 } else
2093 break;
2094 }
2095 if (splitrow < 0 || !curs.alt)
2096 /* it is OK to change the row */
2097 row = trow;
2098
2099 /* Now set the target column */
2100 if (mode == (ORIG|RESULT|BEFORE|AFTER) &&
2101 curs.alt)
2102 curs.target = start + mevent.x - cols / 2 - 1;
2103 else
2104 curs.target = start + mevent.x - 1;
2105 break;
2106 case 'j':
2107 case 'n':
2108 case 'N'-64:
2109 case KEY_DOWN:
2110 if (tnum < 0)
2111 tnum = 1;
2112 for (; tnum > 0 ; tnum--) {
2113 tpos = pos;
2114 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2115 if (ci.merger[tpos.p.m].type != End) {
2116 pos = tpos;
2117 row++;
2118 } else {
2119 if (selftest == 1)
2120 selftest = 2;
2121 break;
2122 }
2123 }
2124 break;
2125 case 'N':
2126 /* Next diff */
2127 tpos = pos; row--;
2128 do {
2129 pos = tpos; row++;
2130 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2131 } while (!(pos.state == 0
2132 && (check_line(pos, fm, fb, fa, ci.merger, mmode)
2133 & (CONFLICTED|WIGGLED)) == 0)
2134 && ci.merger[tpos.p.m].type != End);
2135 tpos = pos; row--;
2136 do {
2137 pos = tpos; row++;
2138 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2139 } while (pos.state == 0
2140 && (check_line(pos, fm, fb, fa, ci.merger, mmode)
2141 & (CONFLICTED|WIGGLED)) == 0
2142 && ci.merger[tpos.p.m].type != End);
2143
2144 break;
2145 case 'C':
2146 /* Next conflict */
2147 tpos = pos; row--;
2148 do {
2149 pos = tpos; row++;
2150 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2151 } while ((check_line(pos, fm, fb, fa, ci.merger, mmode)
2152 & CONFLICTED) != 0
2153 && ci.merger[tpos.p.m].type != End);
2154 tpos = pos; row--;
2155 do {
2156 pos = tpos; row++;
2157 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2158 } while ((check_line(pos, fm, fb, fa, ci.merger, mmode)
2159 & CONFLICTED) == 0
2160 && ci.merger[tpos.p.m].type != End);
2161
2162 break;
2163
2164 case 'P':
2165 /* Previous diff */
2166 tpos = pos; row++;
2167 do {
2168 pos = tpos; row--;
2169 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2170 } while (tpos.state == 0
2171 && (check_line(tpos, fm, fb, fa, ci.merger, mmode)
2172 & (CONFLICTED|WIGGLED)) == 0
2173 && tpos.p.m >= 0);
2174 tpos = pos; row++;
2175 do {
2176 pos = tpos; row--;
2177 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2178 } while (!(tpos.state == 0
2179 && (check_line(tpos, fm, fb, fa, ci.merger, mmode)
2180 & (CONFLICTED|WIGGLED)) == 0)
2181 && tpos.p.m >= 0);
2182 break;
2183
2184 case 'k':
2185 case 'p':
2186 case 'P'-64:
2187 case KEY_UP:
2188 if (tnum < 0)
2189 tnum = 1;
2190 for (; tnum > 0 ; tnum--) {
2191 tpos = pos;
2192 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2193 if (tpos.p.m >= 0) {
2194 pos = tpos;
2195 row--;
2196 } else
2197 break;
2198 }
2199 break;
2200
2201 case KEY_LEFT:
2202 case 'h':
2203 /* left */
2204 curs.target = curs.col - 1;
2205 if (curs.target < 0) {
2206 /* Try to go to end of previous line */
2207 tpos = pos;
2208 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2209 if (tpos.p.m >= 0) {
2210 pos = tpos;
2211 row--;
2212 curs.pos = pos.p;
2213 curs.target = -1;
2214 } else
2215 curs.target = 0;
2216 }
2217 break;
2218 case KEY_RIGHT:
2219 case 'l':
2220 /* right */
2221 if (curs.width >= 0)
2222 curs.target = curs.col + curs.width;
2223 else {
2224 /* end of line, go to next */
2225 tpos = pos;
2226 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2227 if (ci.merger[tpos.p.m].type != End) {
2228 pos = tpos;
2229 curs.pos = pos.p;
2230 row++;
2231 curs.target = 0;
2232 }
2233 }
2234 break;
2235
2236 case '^':
2237 case 'A'-64:
2238 /* Start of line */
2239 curs.target = 0;
2240 break;
2241 case '$':
2242 case 'E'-64:
2243 /* End of line */
2244 curs.target = 1000;
2245 break;
2246
2247 case CTRLX('o'):
2248 case 'O':
2249 other_pane:
2250 curs.alt = !curs.alt;
2251 if (curs.alt && mode == (ORIG|RESULT))
2252 mmode = (BEFORE|AFTER);
2253 else
2254 mmode = mode;
2255 break;
2256
2257 case 'a': /* 'after' view in patch window */
2258 if (mode == AFTER)
2259 goto set_merge;
2260 mode = AFTER; modename = "after"; modehelp = after_help;
2261 mmode = mode; curs.alt = 0;
2262 refresh = 3;
2263 break;
2264 case 'b': /* 'before' view in patch window */
2265 if (mode == BEFORE)
2266 goto set_merge;
2267 mode = BEFORE; modename = "before"; modehelp = before_help;
2268 mmode = mode; curs.alt = 0;
2269 refresh = 3;
2270 break;
2271 case 'o': /* 'original' view in the merge window */
2272 if (mode == ORIG)
2273 goto set_merge;
2274 mode = ORIG; modename = "original"; modehelp = orig_help;
2275 mmode = mode; curs.alt = 0;
2276 refresh = 3;
2277 break;
2278 case 'r': /* the 'result' view in the merge window */
2279 if (mode == RESULT)
2280 goto set_merge;
2281 mode = RESULT; modename = "result"; modehelp = result_help;
2282 mmode = mode; curs.alt = 0;
2283 refresh = 3;
2284 break;
2285 case 'd':
2286 if (mode == (BEFORE|AFTER))
2287 goto set_merge;
2288 mode = BEFORE|AFTER; modename = "diff"; modehelp = diff_help;
2289 mmode = mode; curs.alt = 0;
2290 refresh = 3;
2291 break;
2292 case 'm':
2293 set_merge:
2294 mode = ORIG|RESULT; modename = "merge"; modehelp = merge_help;
2295 mmode = mode; curs.alt = 0;
2296 refresh = 3;
2297 break;
2298
2299 case '|':
2300 if (mode == (ORIG|RESULT|BEFORE|AFTER))
2301 goto set_merge;
2302 mode = ORIG|RESULT|BEFORE|AFTER; modename = "sidebyside"; modehelp = sidebyside_help;
2303 mmode = mode; curs.alt = 0;
2304 refresh = 3;
2305 break;
2306
2307 case 'H': /* scroll window to the right */
2308 if (start > 0)
2309 start--;
2310 curs.target = start + 1;
2311 refresh = 1;
2312 break;
2313 case 'L': /* scroll window to the left */
2314 if (start < cols)
2315 start++;
2316 curs.target = start + 1;
2317 refresh = 1;
2318 break;
2319
2320 case 'x': /* Toggle rejecting of conflict.
2321 * A 'Conflict' or 'Changed' becomes 'Unchanged'
2322 * 'Unmatched' becomes 'Changed'
2323 */
2324 if (ci.merger[curs.pos.m].oldtype == Conflict ||
2325 ci.merger[curs.pos.m].oldtype == Changed)
2326 next = Unchanged;
2327 else if (ci.merger[curs.pos.m].oldtype == Unmatched)
2328 next = Changed;
2329 else
2330 break;
2331
2332 if (ci.merger[curs.pos.m].type == next)
2333 ci.merger[curs.pos.m].type = ci.merger[curs.pos.m].oldtype;
2334 else
2335 ci.merger[curs.pos.m].type = next;
2336 p->conflicts = wiggle_isolate_conflicts(
2337 fm, fb, fa, csl1, csl2, 0,
2338 ci.merger, 0, &p->wiggles);
2339 refresh = 1;
2340 changes = 1;
2341 break;
2342
2343 case 'c': /* Toggle accepting of conflict.
2344 * A 'Conflict' or 'Extraneous' becomes 'Changed'
2345 */
2346 if (ci.merger[curs.pos.m].oldtype != Conflict &&
2347 ci.merger[curs.pos.m].oldtype != Extraneous)
2348 break;
2349
2350 if (ci.merger[curs.pos.m].type == Changed)
2351 ci.merger[curs.pos.m].type = ci.merger[curs.pos.m].oldtype;
2352 else
2353 ci.merger[curs.pos.m].type = Changed;
2354 p->conflicts = wiggle_isolate_conflicts(
2355 fm, fb, fa, csl1, csl2, 0,
2356 ci.merger, 0, &p->wiggles);
2357 refresh = 1;
2358 changes = 1;
2359 break;
2360
2361 case 'X': /* Reset all changes on the current line */
2362 tpos = pos;
2363 do {
2364 ci.merger[tpos.p.m].type =
2365 ci.merger[tpos.p.m].oldtype;
2366 e = prev_melmnt(&tpos.p, fm, fb, fa, ci.merger);
2367 if (tpos.p.m < 0)
2368 break;
2369 } while (!ends_line(e) ||
2370 visible(mode & (RESULT|AFTER), ci.merger, &tpos) < 0);
2371 p->conflicts = wiggle_isolate_conflicts(
2372 fm, fb, fa, csl1, csl2, 0,
2373 ci.merger, 0, &p->wiggles);
2374 refresh = 1;
2375 changes = 1;
2376 break;
2377
2378 case '?':
2379 help_window(modehelp, merge_window_help, 0);
2380 refresh = 2;
2381 break;
2382
2383 case KEY_RESIZE:
2384 refresh = 2;
2385 break;
2386 }
2387
2388 if (meta == SEARCH(0)) {
2389 if (anchor == NULL ||
2390 !same_mpos(anchor->pos, pos) ||
2391 anchor->searchlen != searchlen ||
2392 !same_mp(anchor->curs.pos, curs.pos)) {
2393 struct search_anchor *a = wiggle_xmalloc(sizeof(*a));
2394 a->pos = pos;
2395 a->row = row;
2396 a->start = start;
2397 a->curs = curs;
2398 a->searchlen = searchlen;
2399 a->notfound = search_notfound;
2400 a->next = anchor;
2401 anchor = a;
2402 }
2403 } else {
2404 while (anchor) {
2405 struct search_anchor *a = anchor;
2406 anchor = a->next;
2407 free(a);
2408 }
2409 }
2410 if (refresh == 3) {
2411 /* move backward and forward to make sure we
2412 * are on a visible line
2413 */
2414 tpos = pos;
2415 prev_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2416 if (tpos.p.m >= 0)
2417 pos = tpos;
2418 tpos = pos;
2419 next_mline(&tpos, fm, fb, fa, ci.merger, mmode);
2420 if (ci.merger[tpos.p.m].type != End)
2421 pos = tpos;
2422 }
2423 }
2424 }
2425
show_merge(char * origname,FILE * patch,int reverse,int is_merge,char * before,char * after,int replace,char * outfile,int selftest,int ignore_blanks,int just_diff,int backup)2426 static int show_merge(char *origname, FILE *patch, int reverse,
2427 int is_merge, char *before, char *after,
2428 int replace, char *outfile,
2429 int selftest, int ignore_blanks,
2430 int just_diff, int backup)
2431 {
2432 struct plist p = {0};
2433
2434 p.file = origname;
2435 p.outfile = replace ? outfile : NULL;
2436 if (patch) {
2437 p.start = 0;
2438 fseek(patch, 0, SEEK_END);
2439 p.end = ftell(patch);
2440 fseek(patch, 0, SEEK_SET);
2441 }
2442 p.calced = 0;
2443 p.is_merge = is_merge;
2444 p.before = before;
2445 p.after = after;
2446
2447 freopen("/dev/null","w",stderr);
2448 return merge_window(&p, patch, reverse, replace, selftest,
2449 ignore_blanks, just_diff, backup);
2450 }
2451
calc_one(struct plist * pl,FILE * f,int reverse,int ignore_blanks,int just_diff)2452 static void calc_one(struct plist *pl, FILE *f, int reverse,
2453 int ignore_blanks, int just_diff)
2454 {
2455 struct stream s1, s2;
2456 struct stream s = wiggle_load_segment(f, pl->start, pl->end);
2457 struct stream sf;
2458 if (pl->is_merge) {
2459 if (reverse)
2460 wiggle_split_merge(s, &sf, &s2, &s1);
2461 else
2462 wiggle_split_merge(s, &sf, &s1, &s2);
2463 pl->chunks = 0;
2464 } else {
2465 if (reverse)
2466 pl->chunks = wiggle_split_patch(s, &s2, &s1);
2467 else
2468 pl->chunks = wiggle_split_patch(s, &s1, &s2);
2469 if (just_diff)
2470 sf = s1;
2471 else
2472 sf = wiggle_load_file(pl->file);
2473 }
2474 if (sf.body == NULL || s1.body == NULL || s1.body == NULL) {
2475 pl->wiggles = pl->conflicts = -1;
2476 } else {
2477 struct file ff, fp1, fp2;
2478 struct csl *csl1, *csl2;
2479 struct ci ci;
2480 ff = wiggle_split_stream(sf, ByWord | ignore_blanks);
2481 fp1 = wiggle_split_stream(s1, ByWord | ignore_blanks);
2482 fp2 = wiggle_split_stream(s2, ByWord | ignore_blanks);
2483 if (pl->chunks && !just_diff)
2484 csl1 = wiggle_pdiff(ff, fp1, pl->chunks);
2485 else
2486 csl1 = wiggle_diff(ff, fp1, 1);
2487 csl2 = wiggle_diff_patch(fp1, fp2, 1);
2488 ci = wiggle_make_merger(ff, fp1, fp2, csl1, csl2, 0, 1, 0);
2489 pl->wiggles = ci.wiggles;
2490 pl->conflicts = ci.conflicts;
2491 free(ci.merger);
2492 free(csl1);
2493 free(csl2);
2494 free(ff.list);
2495 free(fp1.list);
2496 free(fp2.list);
2497 }
2498
2499 free(s1.body);
2500 free(s2.body);
2501 free(s.body);
2502 if (!just_diff)
2503 free(sf.body);
2504 pl->calced = 1;
2505 }
2506
get_prev(int pos,struct plist * pl,int n,int mode)2507 static int get_prev(int pos, struct plist *pl, int n, int mode)
2508 {
2509 int found = 0;
2510 if (pos == -1 || pl == NULL)
2511 return pos;
2512 do {
2513 if (pl[pos].prev == -1)
2514 return pl[pos].parent;
2515 pos = pl[pos].prev;
2516 while (pl[pos].open &&
2517 pl[pos].last >= 0)
2518 pos = pl[pos].last;
2519 if (pl[pos].last >= 0)
2520 /* always see directories */
2521 found = 1;
2522 else if (mode == 0)
2523 found = 1;
2524 else if (mode <= 1 && pl[pos].wiggles > 0)
2525 found = 1;
2526 else if (mode <= 2 && pl[pos].conflicts > 0)
2527 found = 1;
2528 } while (pos >= 0 && !found);
2529 return pos;
2530 }
2531
get_next(int pos,struct plist * pl,int n,int mode,FILE * f,int reverse,int ignore_blanks,int just_diff)2532 static int get_next(int pos, struct plist *pl, int n, int mode,
2533 FILE *f, int reverse, int ignore_blanks, int just_diff)
2534 {
2535 int found = 0;
2536 if (pos == -1)
2537 return pos;
2538 do {
2539 if (pl[pos].open) {
2540 if (pos + 1 < n)
2541 pos = pos+1;
2542 else
2543 return -1;
2544 } else {
2545 while (pos >= 0 && pl[pos].next == -1)
2546 pos = pl[pos].parent;
2547 if (pos >= 0)
2548 pos = pl[pos].next;
2549 }
2550 if (pos < 0)
2551 return -1;
2552 if (pl[pos].calced == 0 && pl[pos].end)
2553 calc_one(pl+pos, f, reverse, ignore_blanks, just_diff);
2554 if (pl[pos].last >= 0)
2555 /* always see directories */
2556 found = 1;
2557 else if (mode == 0)
2558 found = 1;
2559 else if (mode <= 1 && pl[pos].wiggles > 0)
2560 found = 1;
2561 else if (mode <= 2 && pl[pos].conflicts > 0)
2562 found = 1;
2563 } while (pos >= 0 && !found);
2564 return pos;
2565 }
2566
draw_one(int row,struct plist * pl,FILE * f,int reverse,int ignore_blanks,int just_diff)2567 static void draw_one(int row, struct plist *pl, FILE *f, int reverse,
2568 int ignore_blanks, int just_diff)
2569 {
2570 char hdr[2*12];
2571 hdr[0] = 0;
2572
2573 if (pl == NULL) {
2574 move(row, 0);
2575 clrtoeol();
2576 return;
2577 }
2578 if (pl->calced == 0 && pl->end)
2579 /* better load the patch and count the chunks */
2580 calc_one(pl, f, reverse, ignore_blanks, just_diff);
2581 if (pl->end == 0) {
2582 strcpy(hdr, " ");
2583 } else {
2584 if (pl->chunks > 99)
2585 strcpy(hdr, "XX");
2586 else
2587 sprintf(hdr, "%2d", pl->chunks);
2588 if (pl->wiggles > 99)
2589 strcpy(hdr+2, " XX");
2590 else
2591 sprintf(hdr+2, " %2d", pl->wiggles);
2592 if (pl->conflicts > 99)
2593 strcpy(hdr+5, " XX ");
2594 else
2595 sprintf(hdr+5, " %2d ", pl->conflicts);
2596 }
2597 if (pl->end)
2598 strcpy(hdr+9, "= ");
2599 else if (pl->open)
2600 strcpy(hdr+9, "+ ");
2601 else
2602 strcpy(hdr+9, "- ");
2603
2604 if (!pl->end)
2605 attrset(0);
2606 else if (pl->is_merge)
2607 attrset(a_saved);
2608 else if (pl->conflicts)
2609 attrset(a_has_conflicts);
2610 else if (pl->wiggles)
2611 attrset(a_has_wiggles);
2612 else
2613 attrset(a_no_wiggles);
2614
2615 mvaddstr(row, 0, hdr);
2616 mvaddstr(row, 11, pl->file);
2617 clrtoeol();
2618 }
2619
save_one(FILE * f,struct plist * pl,int reverse,int ignore_blanks,int backup)2620 static int save_one(FILE *f, struct plist *pl, int reverse,
2621 int ignore_blanks, int backup)
2622 {
2623 struct stream sp, sa, sb, sm;
2624 struct file fa, fb, fm;
2625 struct csl *csl1, *csl2;
2626 struct ci ci;
2627 int chunks;
2628 sp = wiggle_load_segment(f, pl->start,
2629 pl->end);
2630 if (reverse)
2631 chunks = wiggle_split_patch(sp, &sa, &sb);
2632 else
2633 chunks = wiggle_split_patch(sp, &sb, &sa);
2634 fb = wiggle_split_stream(sb, ByWord | ignore_blanks);
2635 fa = wiggle_split_stream(sa, ByWord | ignore_blanks);
2636 sm = wiggle_load_file(pl->file);
2637 fm = wiggle_split_stream(sm, ByWord | ignore_blanks);
2638 csl1 = wiggle_pdiff(fm, fb, chunks);
2639 csl2 = wiggle_diff_patch(fb, fa, 1);
2640 ci = wiggle_make_merger(fm, fb, fa, csl1, csl2, 0, 1, 0);
2641 return save_merge(fm, fb, fa, ci.merger,
2642 pl->file, backup);
2643 }
2644
2645 static char *main_help[] = {
2646 " You are using the \"browse\" mode of wiggle.",
2647 "This page shows a list of files in a patch together with",
2648 "the directories that contain them.",
2649 "A directory is indicated by a '+' if the contents are",
2650 "listed or a '-' if the contents are hidden. A file is",
2651 "indicated by an '='. Typing <space> or <return> will",
2652 "expose or hide a directory, and will visit a file.",
2653 "",
2654 "The three columns of numbers are:",
2655 " Ch The number of patch chunks which applied to",
2656 " this file",
2657 " Wi The number of chunks that needed to be wiggled",
2658 " in to place",
2659 " Co The number of chunks that created an unresolvable",
2660 " conflict",
2661 "",
2662 "Keystrokes recognised in this page are:",
2663 " ? Display this help",
2664 " SPC On a directory, toggle hiding of contents",
2665 " On file, visit the file",
2666 " RTN Same as SPC",
2667 " q Quit program",
2668 " control-C Disable auto-save-on-exit",
2669 " n,j,DOWN Go to next line",
2670 " p,k,UP Go to previous line",
2671 "",
2672 " A list All files",
2673 " W only list files with a wiggle or a conflict",
2674 " C only list files with a conflict",
2675 "",
2676 " S Save this file with changes applied. If",
2677 " some but not all files are saved, wiggle will",
2678 " prompt on exit to save the rest.",
2679 " R Revert the current saved file to its original",
2680 " content",
2681 " I toggle whether spaces are ignored",
2682 " when matching text.",
2683 NULL
2684 };
2685 static char *saveall_msg = " %d file%s (of %d) have not been saved.";
2686 static char saveall_buf[200];
2687 static char *saveall_query[] = {
2688 "",
2689 saveall_buf,
2690 " Would you like to save them?",
2691 " Y = yes, save them all",
2692 " N = no, exit without saving anything else",
2693 " Q = Don't quit just yet",
2694 NULL
2695 };
main_window(struct plist * pl,int np,FILE * f,int reverse,int replace,int ignore_blanks,int just_diff,int backup)2696 static void main_window(struct plist *pl, int np, FILE *f, int reverse,
2697 int replace, int ignore_blanks, int just_diff, int backup)
2698 {
2699 /* The main window lists all files together with summary information:
2700 * number of chunks, number of wiggles, number of conflicts.
2701 * The list is scrollable
2702 * When a entry is 'selected', we switch to the 'file' window
2703 * The list can be condensed by removing files with no conflict
2704 * or no wiggles, or removing subdirectories
2705 *
2706 * We record which file in the list is 'current', and which
2707 * screen line it is on. We try to keep things stable while
2708 * moving.
2709 *
2710 * Counts are printed before the name using at most 2 digits.
2711 * Numbers greater than 99 are XX
2712 * Ch Wi Co File
2713 * 27 5 1 drivers/md/md.c
2714 *
2715 * A directory show the sum in all children.
2716 *
2717 * Commands:
2718 * select: enter, space, mouseclick
2719 * on file, go to file window
2720 * on directory, toggle open
2721 * up: k, p, control-p uparrow
2722 * Move to previous open object
2723 * down: j, n, control-n, downarrow
2724 * Move to next open object
2725 *
2726 * A W C: select All Wiggles or Conflicts
2727 * mode
2728 *
2729 */
2730 char *mesg = NULL;
2731 char mesg_buf[1024];
2732 int last_mesg_len = 0;
2733 int pos = 0; /* position in file */
2734 int row = 1; /* position on screen */
2735 int rows = 0; /* size of screen in rows */
2736 int cols = 0;
2737 int tpos, i;
2738 int refresh = 2;
2739 int c = 0;
2740 int mode = 0; /* 0=all, 1= only wiggled, 2=only conflicted */
2741 int cnt; /* count of files that need saving */
2742 int any; /* count of files that have been save*/
2743 int ans;
2744 MEVENT mevent;
2745 char *debug = getenv("WIGGLE_DEBUG");
2746
2747 if (debug && !*debug)
2748 debug = NULL;
2749
2750 freopen("/dev/null","w",stderr);
2751 term_init(1);
2752
2753 while (1) {
2754 if (refresh == 2) {
2755 clear(); (void)attrset(0);
2756 attron(A_BOLD);
2757 mvaddstr(0, 0, "Ch Wi Co Patched Files");
2758 attroff(A_BOLD);
2759 if (ignore_blanks)
2760 addstr(" (ignoring blanks)");
2761 move(2, 0);
2762 refresh = 1;
2763 }
2764 if (row < 1 || row >= rows)
2765 refresh = 1;
2766 if (refresh) {
2767 refresh = 0;
2768 getmaxyx(stdscr, rows, cols);
2769
2770 if (row >= rows + 3)
2771 row = (rows+1)/2;
2772 if (row >= rows)
2773 row = rows-1;
2774 tpos = pos;
2775 for (i = row; i > 1; i--) {
2776 tpos = get_prev(tpos, pl, np, mode);
2777 if (tpos == -1) {
2778 row = row - i + 1;
2779 break;
2780 }
2781 }
2782 /* Ok, row and pos could be trustworthy now */
2783 tpos = pos;
2784 for (i = row; i >= 1; i--) {
2785 draw_one(i, &pl[tpos], f, reverse, ignore_blanks, just_diff);
2786 tpos = get_prev(tpos, pl, np, mode);
2787 }
2788 tpos = pos;
2789 for (i = row+1; i < rows; i++) {
2790 tpos = get_next(tpos, pl, np, mode, f, reverse,ignore_blanks, just_diff);
2791 if (tpos >= 0)
2792 draw_one(i, &pl[tpos], f, reverse, ignore_blanks, just_diff);
2793 else
2794 draw_one(i, NULL, f, reverse, ignore_blanks, just_diff);
2795 }
2796 }
2797 attrset(0);
2798 if (last_mesg_len) {
2799 move(0, cols - last_mesg_len);
2800 clrtoeol();
2801 last_mesg_len = 0;
2802 }
2803 if (mesg) {
2804 last_mesg_len = strlen(mesg);
2805 move(0, cols - last_mesg_len);
2806 addstr(mesg);
2807 mesg = NULL;
2808 } else if (debug) {
2809 /* debugging help: report last keystroke */
2810 char bb[30];
2811 sprintf(bb, "last-key = 0%o", c);
2812 attrset(0);
2813 last_mesg_len = strlen(bb);
2814 mvaddstr(0, cols - last_mesg_len, bb);
2815 }
2816 move(row, 9);
2817 c = getch();
2818 switch (c) {
2819 case 'j':
2820 case 'n':
2821 case 'N':
2822 case 'N'-64:
2823 case KEY_DOWN:
2824 tpos = get_next(pos, pl, np, mode, f, reverse, ignore_blanks, just_diff);
2825 if (tpos >= 0) {
2826 pos = tpos;
2827 row++;
2828 }
2829 break;
2830 case 'k':
2831 case 'p':
2832 case 'P':
2833 case 'P'-64:
2834 case KEY_UP:
2835 tpos = get_prev(pos, pl, np, mode);
2836 if (tpos >= 0) {
2837 pos = tpos;
2838 row--;
2839 }
2840 break;
2841
2842 case KEY_MOUSE:
2843 if (getmouse(&mevent) != OK)
2844 break;
2845 while (row < mevent.y &&
2846 (tpos = get_next(pos, pl, np, mode, f, reverse, ignore_blanks, just_diff))
2847 >= 0) {
2848 pos = tpos;
2849 row++;
2850 }
2851 while (row > mevent.y &&
2852 (tpos = get_prev(pos, pl, np, mode)) >= 0) {
2853 pos = tpos;
2854 row--;
2855 }
2856 if (row != mevent.y)
2857 /* couldn't find the line */
2858 break;
2859 /* FALL THROUGH */
2860 case ' ':
2861 case 13:
2862 if (pl[pos].end == 0) {
2863 pl[pos].open = !pl[pos].open;
2864 refresh = 1;
2865 if (pl[pos].open)
2866 mesg = "Opened folder";
2867 else
2868 mesg = "Closed folder";
2869 } else {
2870 int c;
2871 if (pl[pos].is_merge)
2872 c = merge_window(&pl[pos], NULL, reverse, 0, 0,
2873 ignore_blanks, just_diff, backup);
2874 else
2875 c = merge_window(&pl[pos], f, reverse, 0, 0,
2876 ignore_blanks, just_diff, backup);
2877 refresh = 2;
2878 if (c) {
2879 pl[pos].is_merge = 1;
2880 snprintf(mesg_buf, cols,
2881 "Saved file %s.",
2882 pl[pos].file);
2883 mesg = mesg_buf;
2884 }
2885 }
2886 break;
2887 case 27: /* escape */
2888 attrset(0);
2889 mvaddstr(0, cols-10, "ESC..."); clrtoeol();
2890 c = getch();
2891 switch (c) {
2892 }
2893 move(0, cols-10); clrtoeol();
2894 break;
2895 case 'C'-64:
2896 if (replace)
2897 mesg = "Save-on-exit disabled. Use 'q' to quit.";
2898 else
2899 mesg = "Use 'q' to quit.";
2900 replace = 0;
2901 break;
2902
2903 case 'q':
2904 cnt = 0;
2905 any = 0;
2906 for (i = 0; i < np; i++)
2907 if (pl[i].end && !pl[i].is_merge)
2908 cnt++;
2909 else if (pl[i].end)
2910 any++;
2911 if (!cnt) {
2912 endwin();
2913 return;
2914 }
2915 refresh = 2;
2916 if (replace)
2917 ans = 1;
2918 else if (any) {
2919 sprintf(saveall_buf, saveall_msg,
2920 cnt, cnt == 1 ? "" : "s", cnt+any);
2921 ans = help_window(saveall_query, NULL, 1);
2922 } else
2923 ans = 0;
2924 if (ans < 0)
2925 break;
2926 if (ans) {
2927 for (i = 0; i < np; i++) {
2928 if (pl[i].end
2929 && !pl[i].is_merge)
2930 save_one(f, &pl[i],
2931 reverse,
2932 ignore_blanks, backup);
2933 }
2934 } else
2935 cnt = 0;
2936 endwin();
2937 if (cnt)
2938 printf("%d file%s saved\n", cnt,
2939 cnt == 1 ? "" : "s");
2940 return;
2941
2942 case 'A':
2943 mode = 0; refresh = 1;
2944 mesg = "Showing ALL files";
2945 break;
2946 case 'W':
2947 mode = 1; refresh = 1;
2948 mesg = "Showing Wiggled files";
2949 break;
2950 case 'C':
2951 mode = 2; refresh = 1;
2952 mesg = "Showing Conflicted files";
2953 break;
2954
2955 case 'S': /* Save updated file */
2956 if (pl[pos].end == 0) {
2957 /* directory */
2958 mesg = "Cannot save a folder.";
2959 } else if (pl[pos].is_merge) {
2960 /* Already saved */
2961 mesg = "File is already saved.";
2962 } else {
2963 if (save_one(f, &pl[pos], reverse, ignore_blanks, backup) == 0) {
2964 pl[pos].is_merge = 1;
2965 snprintf(mesg_buf, cols,
2966 "Saved file %s.",
2967 pl[pos].file);
2968 pl[pos].chunks = pl[pos].conflicts;
2969 pl[pos].wiggles = 0;
2970 } else
2971 snprintf(mesg_buf, cols,
2972 "Failed to save file %s.",
2973 pl[pos].file);
2974 mesg = mesg_buf;
2975 refresh = 1;
2976 }
2977 break;
2978
2979 case 'R': /* Restore updated file */
2980 if (pl[pos].end == 0)
2981 mesg = "Cannot restore a folder.";
2982 else if (!pl[pos].is_merge)
2983 mesg = "File has not been saved, cannot restore.";
2984 else if (!backup)
2985 mesg = "Backups are disabled, nothing to restore!";
2986 else {
2987 /* rename foo.porig to foo, and clear is_merge */
2988 char *file = pl[pos].file;
2989 char *orignew = wiggle_xmalloc(strlen(file) + 20);
2990 strcpy(orignew, file);
2991 strcat(orignew, ".porig");
2992 if (rename(orignew, file) == 0) {
2993 mesg = "File has been restored.";
2994 pl[pos].is_merge = 0;
2995 refresh = 1;
2996 calc_one(&pl[pos], f, reverse, ignore_blanks, just_diff);
2997 } else
2998 mesg = "Could not restore file!";
2999 }
3000 break;
3001
3002 case 'I': /* Toggle ignoring blanks */
3003 ignore_blanks = ignore_blanks ? 0 : IgnoreBlanks;
3004 refresh = 2;
3005 for (i = 0; i < np; i++)
3006 pl[i].calced = 0;
3007 break;
3008
3009 case '?':
3010 help_window(main_help, NULL, 0);
3011 refresh = 2;
3012 break;
3013
3014 case KEY_RESIZE:
3015 refresh = 2;
3016 break;
3017 }
3018 }
3019 }
3020
catch(int sig)3021 static void catch(int sig)
3022 {
3023 if (sig == SIGINT && !intr_kills) {
3024 signal(sig, catch);
3025 return;
3026 }
3027 noraw();
3028 nl();
3029 endwin();
3030 printf("Died on signal %d\n", sig);
3031 fflush(stdout);
3032 if (sig != SIGBUS && sig != SIGSEGV)
3033 exit(2);
3034 else
3035 /* Otherwise return and wiggle_die */
3036 signal(sig, NULL);
3037 }
3038
term_init(int doraw)3039 static void term_init(int doraw)
3040 {
3041
3042 static int init_done = 0;
3043
3044 if (init_done)
3045 return;
3046 init_done = 1;
3047
3048 signal(SIGINT, catch);
3049 signal(SIGQUIT, catch);
3050 signal(SIGTERM, catch);
3051 signal(SIGBUS, catch);
3052 signal(SIGSEGV, catch);
3053
3054 initscr();
3055 if (doraw)
3056 raw();
3057 else
3058 cbreak();
3059 noecho();
3060 start_color();
3061 use_default_colors();
3062 if (!has_colors()) {
3063 a_delete = A_UNDERLINE;
3064 a_added = A_BOLD;
3065 a_common = A_NORMAL;
3066 a_sep = A_STANDOUT;
3067 a_already = A_STANDOUT;
3068 a_has_conflicts = A_UNDERLINE;
3069 a_has_wiggles = A_BOLD;
3070 a_no_wiggles = A_NORMAL;
3071 } else {
3072 init_pair(1, COLOR_RED, -1);
3073 a_delete = COLOR_PAIR(1);
3074 init_pair(2, COLOR_GREEN, -1);
3075 a_added = COLOR_PAIR(2);
3076 a_common = A_NORMAL;
3077 init_pair(3, COLOR_WHITE, COLOR_GREEN);
3078 a_sep = COLOR_PAIR(3); a_sep = A_STANDOUT;
3079 init_pair(4, -1, COLOR_YELLOW);
3080 a_void = COLOR_PAIR(4);
3081 init_pair(5, COLOR_BLUE, -1);
3082 a_unmatched = COLOR_PAIR(5);
3083 init_pair(6, COLOR_CYAN, -1);
3084 a_extra = COLOR_PAIR(6);
3085
3086 init_pair(7, COLOR_BLACK, COLOR_CYAN);
3087 a_already = COLOR_PAIR(7);
3088
3089 a_has_conflicts = a_delete;
3090 a_has_wiggles = a_added;
3091 a_no_wiggles = a_unmatched;
3092 a_saved = a_extra;
3093 }
3094 nonl(); intrflush(stdscr, FALSE); keypad(stdscr, TRUE);
3095 mousemask(ALL_MOUSE_EVENTS, NULL);
3096 }
3097
vpatch(int argc,char * argv[],int patch,int strip,int reverse,int replace,char * outfilename,int selftest,int ignore_blanks,int backup)3098 int vpatch(int argc, char *argv[], int patch, int strip,
3099 int reverse, int replace, char *outfilename,
3100 int selftest, int ignore_blanks, int backup)
3101 {
3102 /* NOTE argv[0] is first arg...
3103 * Behaviour depends on number of args and 'patch'.
3104 * If 'patch' is '1', assume a patch. if '2', assume a diff.
3105 * 0: A multi-file patch or diff is read from stdin.
3106 * A 'patch' is applies to relevant files. A 'diff' is just
3107 * displayed.
3108 * 1: if 'patch', parse it as a multi-file patch/diff and allow
3109 * the files to be browsed.
3110 * if filename ends '.rej', then treat it as a patch/diff again
3111 * a file with the same basename
3112 * Else treat the file as a merge (with conflicts) and view it.
3113 *
3114 * 2: First file is original, second is patch unless patch==2,
3115 * then two files need to be diffed.
3116 * 3: Files are: original previous new. The diff between 'previous' and
3117 * 'new' needs to be applied to 'original'.
3118 *
3119 * If a multi-file patch is being read, 'strip' tells how many
3120 * path components to strip. If it is -1, we guess based on
3121 * existing files.
3122 * If 'reverse' is given, when we invert any patch or diff
3123 * If 'replace' then we save the resulting merge.
3124 */
3125 FILE *in;
3126 FILE *f;
3127 struct plist *pl;
3128 int num_patches;
3129 int just_diff = (patch == 2);
3130
3131 switch (argc) {
3132 default:
3133 fprintf(stderr, "%s: too many file names given.\n", wiggle_Cmd);
3134 exit(1);
3135
3136 case 0: /* stdin is a patch or diff */
3137 if (lseek(fileno(stdin), 0L, 1) == -1) {
3138 /* cannot seek, so need to copy to a temp file */
3139 f = tmpfile();
3140 if (!f) {
3141 fprintf(stderr, "%s: Cannot create temp file\n", wiggle_Cmd);
3142 exit(1);
3143 }
3144 pl = wiggle_parse_patch(stdin, f, &num_patches);
3145 in = f;
3146 } else {
3147 pl = wiggle_parse_patch(stdin, NULL, &num_patches);
3148 in = fdopen(dup(0), "r");
3149 }
3150 /* use stderr for keyboard input */
3151 dup2(2, 0);
3152 if (!just_diff &&
3153 wiggle_set_prefix(pl, num_patches, strip) == 0) {
3154 fprintf(stderr, "%s: aborting\n", wiggle_Cmd);
3155 exit(2);
3156 }
3157 pl = wiggle_sort_patches(pl, &num_patches);
3158 main_window(pl, num_patches, in, reverse, replace, ignore_blanks,
3159 just_diff, backup);
3160 wiggle_plist_free(pl, num_patches);
3161 fclose(in);
3162 break;
3163
3164 case 1: /* a patch/diff, a .rej, or a merge file */
3165 f = fopen(argv[0], "r");
3166 if (!f) {
3167 fprintf(stderr, "%s: cannot open %s\n", wiggle_Cmd, argv[0]);
3168 exit(1);
3169 }
3170 wiggle_check_dir(argv[0], fileno(f));
3171 if (patch) {
3172 pl = wiggle_parse_patch(f, NULL, &num_patches);
3173 if (!just_diff && wiggle_set_prefix(pl, num_patches, strip) == 0) {
3174 fprintf(stderr, "%s: aborting\n", wiggle_Cmd);
3175 exit(2);
3176 }
3177 pl = wiggle_sort_patches(pl, &num_patches);
3178 main_window(pl, num_patches, f, reverse, replace,
3179 ignore_blanks, just_diff, backup);
3180 wiggle_plist_free(pl, num_patches);
3181 } else if (strlen(argv[0]) > 4 &&
3182 strcmp(argv[0]+strlen(argv[0])-4, ".rej") == 0) {
3183 char *origname = strdup(argv[0]);
3184 origname[strlen(origname) - 4] = '\0';
3185 show_merge(origname, f, reverse, 0, NULL, NULL,
3186 replace, outfilename,
3187 selftest, ignore_blanks, just_diff, backup);
3188 } else
3189 show_merge(argv[0], f, reverse, 1, NULL, NULL,
3190 replace, outfilename,
3191 selftest, ignore_blanks, just_diff, backup);
3192
3193 break;
3194 case 2: /* an orig and a diff/.rej or two files */
3195 if (just_diff) {
3196 show_merge(NULL, NULL, reverse, 0, argv[0], argv[1],
3197 replace, outfilename,
3198 selftest, ignore_blanks, just_diff, backup);
3199 break;
3200 }
3201 f = fopen(argv[1], "r");
3202 if (!f) {
3203 fprintf(stderr, "%s: cannot open %s\n", wiggle_Cmd, argv[0]);
3204 exit(1);
3205 }
3206 wiggle_check_dir(argv[1], fileno(f));
3207 show_merge(argv[0], f, reverse, 0, NULL, NULL,
3208 replace, outfilename,
3209 selftest, ignore_blanks, just_diff, backup);
3210 break;
3211 case 3: /* orig, before, after */
3212 show_merge(argv[0], NULL, reverse, 0, argv[1], argv[2],
3213 replace, outfilename,
3214 selftest, ignore_blanks, just_diff, backup);
3215 break;
3216 }
3217
3218 noraw();
3219 nl();
3220 endwin();
3221 exit(0);
3222 }
3223