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