New release: v1.3
[wiggle/upstream.git] / demo / vpatch.c
blobfae871496e7ffd9d2b15c4b396db46608eb8a5e8
2 /*
3 * vpatch - visual front end for wiggle
5 * "files" display, lists all files with Statistics
6 * - can hide various lines including subdirectories
7 * and files without wiggles or conflicts
8 * "diff" display shows merged file with different parts
9 * in different colours
10 * - untouched are pale A_DIM
11 * - matched/remaining are regular A_NORMAL
12 * - matched/removed are red/
14 * underlined A_UNDERLINE
15 * - unmatched in file are A_STANDOUT
16 * - unmatched in patch are A_STANDOUT|A_UNDERLINE ???
17 * - inserted are inverse/green ?? A_REVERSE
19 * The window can be split horizontally or vertically and two different
20 * views displayed. They will have different parts missing
22 * So a display of NORMAL, underline, standout|underline reverse
23 * should show a normal patch.
27 #include "wiggle.h"
28 #include <malloc.h>
29 #include <string.h>
30 #include <curses.h>
31 #include <unistd.h>
32 #include <stdlib.h>
33 #include <signal.h>
35 #define assert(x) do { if (!(x)) abort(); } while (0)
37 struct plist {
38 char *file;
39 unsigned int start, end;
40 int parent;
41 int next, prev, last;
42 int open;
43 int chunks, wiggles, conflicts;
46 struct plist *patch_add_file(struct plist *pl, int *np, char *file,
47 unsigned int start, unsigned int end)
49 /* size of pl is 0, 16, n^2 */
50 int n = *np;
51 int asize;
53 /* printf("adding %s at %d: %u %u\n", file, n, start, end); */
54 if (n==0) asize = 0;
55 else if (n<=16) asize = 16;
56 else if ((n&(n-1))==0) asize = n;
57 else asize = n+1; /* not accurate, but not too large */
58 if (asize <= n) {
59 /* need to extend array */
60 struct plist *npl;
61 if (asize < 16) asize = 16;
62 else asize += asize;
63 npl = realloc(pl, asize * sizeof(struct plist));
64 if (!npl) {
65 fprintf(stderr, "malloc failed - skipping %s\n", file);
66 return pl;
68 pl = npl;
70 pl[n].file = file;
71 pl[n].start = start;
72 pl[n].end = end;
73 pl[n].last = pl[n].next = pl[n].prev = pl[n].parent = -1;
74 pl[n].chunks = pl[n].wiggles = pl[n].conflicts = 0;
75 pl[n].open = 1;
76 *np = n+1;
77 return pl;
82 struct plist *parse_patch(FILE *f, FILE *of, int *np)
84 /* read a multi-file patch from 'f' and record relevant
85 * details in a plist.
86 * if 'of' >= 0, fd might not be seekable so we write
87 * to 'of' and use lseek on 'of' to determine position
89 struct plist *plist = NULL;
91 while (!feof(f)) {
92 /* first, find the start of a patch: "\n+++ "
93 * grab the file name and scan to the end of a line
95 char *target="\n+++ ";
96 char *target2="\n--- ";
97 char *pos = target;
98 int c;
99 char name[1024];
100 unsigned start, end;
102 while (*pos && (c=fgetc(f)) != EOF ) {
103 if (of) fputc(c, of);
104 if (c == *pos)
105 pos++;
106 else pos = target;
108 if (c == EOF)
109 break;
110 assert(c == ' ');
111 /* now read a file name */
112 pos = name;
113 while ((c=fgetc(f)) != EOF && c != '\t' && c != '\n' && c != ' ' &&
114 pos - name < 1023) {
115 *pos++ = c;
116 if (of) fputc(c, of);
118 *pos = 0;
119 if (c == EOF)
120 break;
121 if (of) fputc(c, of);
122 while (c != '\n' && (c=fgetc(f)) != EOF) {
123 if (of) fputc(c, of);
125 start = of ? ftell(of) : ftell(f);
127 if (c == EOF) break;
129 /* now skip to end - "\n--- " */
130 pos = target2+1;
132 while (*pos && (c=fgetc(f)) != EOF) {
133 if (of) fputc(c, of);
134 if (c == *pos)
135 pos++;
136 else pos = target2;
138 if (pos > target2) {
139 end = of ? ftell(of) : ftell(f);
140 end -= (pos - target2) - 1;
141 plist = patch_add_file(plist, np,
142 strdup(name), start, end);
145 return plist;
147 void die()
149 fprintf(stderr,"vpatch: fatal error\n");
150 abort();
151 exit(3);
155 static struct stream load_segment(FILE *f,
156 unsigned int start, unsigned int end)
158 struct stream s;
159 s.len = end - start;
160 s.body = malloc(s.len);
161 if (s.body) {
162 fseek(f, start, 0);
163 if (fread(s.body, 1, s.len, f) != s.len) {
164 free(s.body);
165 s.body = NULL;
167 } else
168 die();
169 return s;
173 void catch(int sig)
175 if (sig == SIGINT) {
176 signal(sig, catch);
177 return;
179 nocbreak();nl();endwin();
180 printf("Died on signal %d\n", sig);
181 exit(2);
184 int pl_cmp(const void *av, const void *bv)
186 const struct plist *a = av;
187 const struct plist *b = bv;
188 return strcmp(a->file, b->file);
191 int common_depth(char *a, char *b)
193 /* find number of patch segments that these two have
194 * in common
196 int depth = 0;
197 while(1) {
198 char *c;
199 int al, bl;
200 c = strchr(a, '/');
201 if (c) al = c-a; else al = strlen(a);
202 c = strchr(b, '/');
203 if (c) bl = c-b; else bl = strlen(b);
204 if (al == 0 || al != bl || strncmp(a,b,al) != 0)
205 return depth;
206 a+= al;
207 while (*a=='/') a++;
208 b+= bl;
209 while(*b=='/') b++;
211 depth++;
215 struct plist *add_dir(struct plist *pl, int *np, char *file, char *curr)
217 /* any parent of file that is not a parent of curr
218 * needs to be added to pl
220 int d = common_depth(file, curr);
221 char *buf = curr;
222 while (d) {
223 char *c = strchr(file, '/');
224 int l;
225 if (c) l = c-file; else l = strlen(file);
226 file += l;
227 curr += l;
228 while (*file == '/') file++;
229 while (*curr == '/') curr++;
230 d--;
232 while (*file) {
233 if (curr > buf && curr[-1] != '/')
234 *curr++ = '/';
235 while (*file && *file != '/')
236 *curr++ = *file++;
237 while (*file == '/') *file++;
238 *curr = '\0';
239 if (*file)
240 pl = patch_add_file(pl, np, strdup(buf),
241 0, 0);
243 return pl;
246 struct plist *sort_patches(struct plist *pl, int *np)
248 /* sort the patches, add directory names, and re-sort */
249 char curr[1024];
250 char *prev;
251 int parents[100];
252 int prevnode[100];
253 int i, n;
254 qsort(pl, *np, sizeof(struct plist), pl_cmp);
255 curr[0] = 0;
256 n = *np;
257 for (i=0; i<n; i++)
258 pl = add_dir(pl, np, pl[i].file, curr);
260 qsort(pl, *np, sizeof(struct plist), pl_cmp);
262 /* array is now stable, so set up parent pointers */
263 n = *np;
264 curr[0] = 0;
265 prevnode[0] = -1;
266 prev = "";
267 for (i=0; i<n; i++) {
268 int d = common_depth(prev, pl[i].file);
269 if (d == 0)
270 pl[i].parent = -1;
271 else {
272 pl[i].parent = parents[d-1];
273 pl[pl[i].parent].last = i;
275 pl[i].prev = prevnode[d];
276 if (pl[i].prev > -1)
277 pl[pl[i].prev].next = i;
278 prev = pl[i].file;
279 parents[d] = i;
280 prevnode[d] = i;
281 prevnode[d+1] = -1;
283 return pl;
286 int get_prev(int pos, struct plist *pl, int n)
288 if (pos == -1) return pos;
289 if (pl[pos].prev == -1)
290 return pl[pos].parent;
291 pos = pl[pos].prev;
292 while (pl[pos].open &&
293 pl[pos].last >= 0)
294 pos = pl[pos].last;
295 return pos;
298 int get_next(int pos, struct plist *pl, int n)
300 if (pos == -1) return pos;
301 if (pl[pos].open) {
302 if (pos +1 < n)
303 return pos+1;
304 else
305 return -1;
307 while (pos >= 0 && pl[pos].next == -1)
308 pos = pl[pos].parent;
309 if (pos >= 0)
310 pos = pl[pos].next;
311 return pos;
314 void draw_one(int row, struct plist *pl)
316 char hdr[10];
317 hdr[0] = 0;
319 if (pl == NULL) {
320 move(row,0);
321 clrtoeol();
322 return;
324 if (pl->chunks > 99)
325 strcpy(hdr, "XX");
326 else sprintf(hdr, "%02d", pl->chunks);
327 if (pl->wiggles > 99)
328 strcpy(hdr, " XX");
329 else sprintf(hdr+2, " %02d", pl->wiggles);
330 if (pl->conflicts > 99)
331 strcpy(hdr, " XX");
332 else sprintf(hdr+5, " %02d ", pl->conflicts);
333 if (pl->end)
334 strcpy(hdr+9, "= ");
335 else if (pl->open)
336 strcpy(hdr+9, "+ ");
337 else strcpy(hdr+9, "- ");
339 mvaddstr(row, 0, hdr);
340 mvaddstr(row, 11, pl->file);
341 clrtoeol();
344 void addword(struct elmnt e)
346 addnstr(e.start, e.len);
349 void diff_window(struct plist *p, FILE *f)
352 * I wonder what to display here ....
354 struct stream s;
355 struct stream s1, s2;
356 struct file f1, f2;
357 struct csl *csl;
358 char buf[100];
359 int ch;
360 s = load_segment(f, p->start, p->end);
361 ch = split_patch(s, &s1, &s2);
363 clear();
364 sprintf(buf, "Chunk count: %d\n", ch);
365 mvaddstr(1,1,buf); clrtoeol();
368 f1 = split_stream(s1, ByWord, 0);
369 f2 = split_stream(s2, ByWord, 0);
371 csl = diff(f1, f2);
373 /* now try to display the diff highlighted */
374 int sol = 1;
375 int a=0, b=0;
377 while(a<f1.elcnt || b < f2.elcnt) {
378 if (a < csl->a) {
379 if (sol) {
380 int a1;
381 /* if we remove a whole line, output +line,
382 * else clear sol and retry
384 sol = 0;
385 for (a1=a; a1<csl->a; a1++)
386 if (f1.list[a1].start[0] == '\n') {
387 sol = 1;
388 break;
390 if (sol) {
391 addch('-');
392 attron(A_UNDERLINE);
393 for (; a<csl->a; a++) {
394 addword(f1.list[a]);
395 if (f1.list[a].start[0] == '\n') {
396 a++;
397 break;
400 attroff(A_UNDERLINE);
401 } else addch('|');
403 if (!sol) {
404 attron(A_UNDERLINE);
405 do {
406 if (sol) {
407 attroff(A_UNDERLINE);
408 addch('|');
409 attron(A_UNDERLINE);
411 addword(f1.list[a]);
412 sol = (f1.list[a].start[0] == '\n');
413 a++;
414 } while (a < csl->a);
415 attroff(A_UNDERLINE);
416 if (sol) addch('|');
417 sol = 0;
419 } else if (b < csl->b) {
420 if (sol) {
421 int b1;
422 sol = 0;
423 for (b1=b; b1<csl->b; b1++)
424 if (f2.list[b1].start[0] == '\n') {
425 sol = 1;
426 break;
428 if (sol) {
429 addch('+');
430 attron(A_BOLD);
431 for (; b<csl->b; b++) {
432 addword(f2.list[b]);
433 if (f2.list[b].start[0] == '\n') {
434 b++;
435 break;
438 attroff(A_BOLD);
439 } else addch('|');
441 if (!sol) {
442 attron(A_BOLD);
443 do {
444 if (sol) {
445 attroff(A_BOLD);
446 addch('|');
447 attron(A_BOLD);
449 addword(f2.list[b]);
450 sol = (f2.list[b].start[0] == '\n');
451 b++;
452 } while (b < csl->b);
453 attroff(A_BOLD);
454 if (sol) addch('|');
455 sol = 0;
457 } else {
458 if (sol) {
459 int a1;
460 sol = 0;
461 for (a1=a; a1<csl->a+csl->len; a1++)
462 if (f1.list[a1].start[0] == '\n')
463 sol = 1;
464 if (sol) {
465 if (f1.list[a].start[0]) {
466 addch(' ');
467 for (; a< csl->a+csl->len; a++,b++) {
468 addword(f1.list[a]);
469 if (f1.list[a].start[0]=='\n') {
470 a++,b++;
471 break;
474 } else {
475 addstr("SEP\n");
476 a++; b++;
478 } else addch('|');
480 if (!sol) {
481 addword(f1.list[a]);
482 if (f1.list[a].start[0] == '\n')
483 sol = 1;
484 a++;
485 b++;
487 if (a >= csl->a+csl->len)
488 csl++;
493 getch();
495 free(s1.body);
496 free(s2.body);
497 free(f1.list);
498 free(f2.list);
501 void main_window(struct plist *pl, int n, FILE *f)
503 /* The main window lists all files together with summary information:
504 * number of chunks, number of wiggles, number of conflicts.
505 * The list is scrollable
506 * When a entry is 'selected', we switch to the 'file' window
507 * The list can be condensed by removing files with no conflict
508 * or no wiggles, or removing subdirectories
510 * We record which file in the list is 'current', and which
511 * screen line it is on. We try to keep things stable while
512 * moving.
514 * Counts are printed before the name using at most 2 digits.
515 * Numbers greater than 99 are XX
516 * Ch Wi Co File
517 * 27 5 1 drivers/md/md.c
519 * A directory show the sum in all children.
521 * Commands:
522 * select: enter, space, mouseclick
523 * on file, go to file window
524 * on directory, toggle open
525 * up: k, p, control-p uparrow
526 * Move to previous open object
527 * down: j, n, control-n, downarrow
528 * Move to next open object
531 int pos=0; /* position in file */
532 int row=1; /* position on screen */
533 int rows; /* size of screen in rows */
534 int cols;
535 int tpos, i;
536 int refresh = 2;
537 int c;
539 while(1) {
540 if (refresh == 2) {
541 clear();
542 attron(A_BOLD);
543 mvaddstr(0,0,"Ch Wi Co Patched Files");
544 move(2,0);
545 attroff(A_BOLD);
546 refresh = 1;
548 if (row <1 || row >= rows)
549 refresh = 1;
550 if (refresh) {
551 refresh = 0;
552 getmaxyx(stdscr, rows, cols);
553 if (row >= rows +3)
554 row = (rows+1)/2;
555 if (row >= rows)
556 row = rows-1;
557 tpos = pos;
558 for (i=row; i>1; i--) {
559 tpos = get_prev(tpos, pl, n);
560 if (tpos == -1) {
561 row = row - i + 1;
562 break;
565 /* Ok, row and pos could be trustworthy now */
566 tpos = pos;
567 for (i=row; i>=1; i--) {
568 draw_one(i, &pl[tpos]);
569 tpos = get_prev(tpos, pl, n);
571 tpos = pos;
572 for (i=row+1; i<rows; i++) {
573 tpos = get_next(tpos, pl, n);
574 if (tpos >= 0)
575 draw_one(i, &pl[tpos]);
576 else
577 draw_one(i, NULL);
580 move(row, 9);
581 c = getch();
582 switch(c) {
583 case 'j':
584 case 'n':
585 case 'N':
586 case 'N'-64:
587 case KEY_DOWN:
588 tpos = get_next(pos, pl, n);
589 if (tpos >= 0) {
590 pos = tpos;
591 row++;
593 break;
594 case 'k':
595 case 'p':
596 case 'P':
597 case 'P'-64:
598 case KEY_UP:
599 tpos = get_prev(pos, pl, n);
600 if (tpos >= 0) {
601 pos = tpos;
602 row--;
604 break;
606 case ' ':
607 case 13:
608 if (pl[pos].end == 0) {
609 pl[pos].open = ! pl[pos].open;
610 refresh = 1;
611 } else {
612 diff_window(&pl[pos], f);
613 refresh = 2;
615 break;
616 case 27: /* escape */
617 case 'q':
618 return;
624 int main(int argc, char *argv[])
626 int n = 0;
627 FILE *f = NULL;
628 FILE *in = stdin;
629 struct plist *pl;
631 if (argc == 3)
632 f = fopen(argv[argc-1], "w+");
633 if (argc >=2)
634 in = fopen(argv[1], "r");
635 else {
636 printf("no arg...\n");
637 exit(2);
640 pl = parse_patch(in, f, &n);
641 pl = sort_patches(pl, &n);
643 if (f) {
644 fclose(in);
645 in = f;
647 #if 0
648 int i;
649 for (i=0; i<n ; i++) {
650 printf("%3d: %3d %2d/%2d %s\n", i, pl[i].parent, pl[i].prev, pl[i].next, pl[i].file);
652 exit(0);
653 #endif
654 signal(SIGINT, catch);
655 signal(SIGQUIT, catch);
656 signal(SIGTERM, catch);
657 signal(SIGBUS, catch);
658 signal(SIGSEGV, catch);
660 initscr(); cbreak(); noecho();
661 nonl(); intrflush(stdscr, FALSE); keypad(stdscr, TRUE);
662 mousemask(ALL_MOUSE_EVENTS, NULL);
664 main_window(pl, n, in);
666 nocbreak();nl();endwin();
667 return 0;