/* define DIFF_DETAIL for a detailed trace of diff synthesis */ #include #include #include #include #include #include #include #include #include #define fpurge fflush /* not a full replacement, just good enough for here */ extern const char *__progname; #include "fatal.h" #include "subproc.h" #include "git-intf.h" #include "datastruct.h" #include "stdio-util.h" #include "ui.h" typedef struct tfhl TFHL; struct tfhl { TAG *t; FIL *f; HUNK *h; HUNKLINE *l; } ; static unsigned int tempserial = 0; static int cursed = 0; static TFHL top; static TFHL cur; static TFHL commit_cur; static int curline; static HUNK *merging; static int tagmode = 0; #define ICRINGSIZE 65536 static char icring[ICRINGSIZE]; static int icrh = 0; static int icrt = 0; void ui_abort(void) { if (cursed) { move(LINES-1,0); refresh(); endwin(); printf("\n"); cursed = 0; } } void ui_save(void) { ui_abort(); /* conceptually different, same implementation */ } static void ui_setup(void) { if (! cursed) { initscr(); noecho(); cbreak(); cursed = 1; } merging = 0; } void ui_restore(void) { ui_setup(); /* conceptually different, same implemenation */ } static int ich(void) { int c; c = getch(); icring[icrh] = c; if (icrh >= ICRINGSIZE-1) icrh = 0; else icrh ++; if (icrh == icrt) { if (icrt >= ICRINGSIZE-1) icrt = 0; else icrt ++; } return(c); } static void tag_line(FILE *to, void *tv) { TAG *t; t = tv; fprintf(to,"%c %s",(t->flags&TF_EXPANDED)?'+':'-',t->tag?:"(not tagged)"); } static void file_line(FILE *to, void *fv) { FIL *f; f = fv; if (tagmode) fprintf(to," "); fprintf(to,"%c %s",(f->flags&FF_ALLAPPLIED)?'A':' ',f->escname); } static void hunk_line(FILE *to, void *hv) { HUNK *h; h = hv; if (tagmode) fprintf(to," "); fprintf(to,"%c | ",(h==merging)?'m':(h->flags&HF__HIDDEN)?'*':(h->flags&HF_SELECTED)?'>':' '); if (! tagmode) { if (h->tag) { if (h->tag->tag) { fprintf(to,"[%s] ",h->tag->tag); } else { fprintf(to,"- "); } } } fprintf(to,"@@ -%d,%d +%d,%d",h->fl,h->fn,h->tl,h->tn); if (h->splitfrom) fprintf(to," (split @@ -%d,%d +%d,%d)",h->splitfrom->fl,h->splitfrom->fn,h->splitfrom->tl,h->splitfrom->tn); } static void line_line(FILE *to, void *lv) { HUNKLINE *l; int c; unsigned const char *t; l = lv; if (tagmode) fprintf(to," "); fprintf(to,"%c | | ",(l->h==merging)?'m':(l->h->flags&HF_SELECTED)?'>':' '); switch (l->type) { default: abort(); break; case HLT_COMMON: fprintf(to," "); break; case HLT_FROM: fprintf(to,"- "); break; case HLT_TO: fprintf(to,"+ "); break; } c = 0; t = (const void *) l->text; while ((c <= COLS) && *t) { if (*t == '\t') { do { putc(' ',to); c ++; } while (c & 7); } else if (*t < 32) { fprintf(to,"^%c",64^*t); c += 2; } else if (*t == 127) { fprintf(to,"^?"); c += 2; } else if ((*t >= 127) && (*t < 160)) { fprintf(to,"\\%03o",*t); c += 4; } else { putc(*t,to); c ++; } t ++; } } static void tfhl_down_t(TFHL *c) { if (! tagmode) abort(); c->t = c->t->flink; } static void tfhl_down_f(TFHL *c) { TAG *t; HUNK *h; if (tagmode) { t = c->t; while <"find"> (1) { c->f = c->f->flink; if (! c->f) break; if (c->f->flags & FF_ALLAPPLIED) continue; for (h=c->f->diff_h;h;h=h->fflink) { if (h->flags & HF__HIDDEN) continue; if (h->tag == t) break <"find">; } continue; } if (! c->f) tfhl_down_t(c); } else { do c->f = c->f->flink; while (c->f && (c->f->flags & FF_ALLAPPLIED)); } c->h = 0; } static void tfhl_down_h(TFHL *c) { TAG *t; t = c->t; do c->h = c->h->fflink; while (c->h && ((c->h->flags & HF__HIDDEN) || (tagmode && (c->h->tag != t)))); if (! c->h) tfhl_down_f(c); c->l = 0; } static void tfhl_down_l(TFHL *c) { c->l = c->l->flink; if (! c->l) tfhl_down_h(c); } static void tfhl_up_t(TFHL *c) { FIL *f; HUNK *h; if (! tagmode) abort(); c->t = c->t->blink; if (! c->t) return; if (c->t->flags & TF_EXPANDED) { for <"find"> (f=file_t;f;f=f->blink) { for (h=f->diff_t;h;h=h->fblink) { if (h->flags & HF__HIDDEN) continue; if (h->tag == c->t) break <"find">; } } if (f) { c->f = f; c->h = h; if (h->flags & HF_EXPANDED) c->l = h->line_t; } else { c->f = 0; c->h = 0; c->l = 0; } } } static void tfhl_up_f(TFHL *c) { TAG *t; HUNK *h; t = c->t; if (tagmode) { while <"find"> (1) { c->f = c->f->blink; if (! c->f) break; if (c->f->flags & FF_ALLAPPLIED) continue; for (h=c->f->diff_h;h;h=h->fflink) { if (h->flags & HF__HIDDEN) continue; if (h->tag == t) break <"find">; } continue; } } else { do c->f = c->f->blink; while (c->f && (c->f->flags & FF_ALLAPPLIED)); } if (c->f && (c->f->flags & FF_EXPANDED)) { c->h = c->f->diff_t; while (c->h && ((c->h->flags & HF__HIDDEN) || (tagmode && (c->h->tag != t)))) c->h = c->h->fblink; if (! c->h) abort(); if (c->h->flags & HF_EXPANDED) c->l = c->h->line_t; } } static void tfhl_up_h(TFHL *c) { TAG *t; t = c->t; do c->h = c->h->fblink; while (c->h && ((c->h->flags & HF__HIDDEN) || (tagmode && (c->h->tag != t)))); if (c->h && (c->h->flags & HF_EXPANDED)) c->l = c->h->line_t; } static void tfhl_up_l(TFHL *c) { c->l = c->l->blink; } static void tfhl_down(TFHL *c) { TAG *t; HUNK *h; t = c->t; if (tagmode && !t) abort(); if (!tagmode || (t->flags & TF_EXPANDED)) { if (c->f) { if (c->f->flags & FF_EXPANDED) { if (c->h) { if (c->h->flags & HF_EXPANDED) { if (c->l) { tfhl_down_l(c); } else { c->l = c->h->line_h; } } else { tfhl_down_h(c); } } else { c->h = c->f->diff_h; while ((c->h->flags & HF__HIDDEN) || (tagmode && (c->h->tag != t))) c->h = c->h->fflink; } } else { tfhl_down_f(c); } } else { if (! tagmode) abort(); c->f = file_h; while (c->f) { h = c->f->diff_h; while (h && ((h->flags & HF__HIDDEN) || (h->tag != t))) h = h->fflink; if (h) break; c->f = c->f->flink; } c->h = 0; if (tagmode && !c->f) { tfhl_down_t(c); } } } else { tfhl_down_t(c); } } static void tfhl_up(TFHL *c) { if (tagmode && !c->t) abort(); if (!tagmode || (c->t->flags & TF_EXPANDED)) { if (c->f) { if (c->f->flags & FF_EXPANDED) { if (c->h) { if (c->h->flags & HF_EXPANDED) { if (c->l) { tfhl_up_l(c); } else { tfhl_up_h(c); } } else { tfhl_up_h(c); } } else { tfhl_up_f(c); } } else { tfhl_up_f(c); } } else { if (! tagmode) abort(); tfhl_up_t(c); } } else { tfhl_up_t(c); } } static void tfhl_down_level(TFHL *c) { if (tagmode && !c->t) abort(); if (tagmode && (!c->f || !(c->t->flags & TF_EXPANDED))) { tfhl_down_t(c); } else if (! c->f) { abort(); } else if ((c->f->flags & FF_EXPANDED) && c->h) { tfhl_down_h(c); } else { tfhl_down_f(c); } } static void tfhl_up_level(TFHL *c) { if (tagmode ? !c->t : !c->f) abort(); if (!tagmode || ((c->t->flags & TF_EXPANDED) && c->f)) { if ((c->f->flags & FF_EXPANDED) && c->h) { if ((c->h->flags & HF_EXPANDED) && c->l) { c->l = 0; } else { tfhl_up_h(c); c->l = 0; } } else { tfhl_up_f(c); c->h = 0; c->l = 0; } } else { tfhl_up_t(c); c->f = 0; c->h = 0; c->l = 0; } } static void show_line(void (*fn)(FILE *, void *), int y, void *arg) { char *s; int l; FILE *f; f = fopen_accum(&s,&l); (*fn)(f,arg); fclose(f); if (l > COLS-2) { mvprintw(y,0,"%.*s...",COLS-4,s); } else { mvprintw(y,0,"%s",s); } clrtoeol(); free(s); } static void redraw_list_tagmode(void) { int y; TFHL c; void check(TFHL *t) { if (! t->t) abort(); if ((t->t->flags & TF_EXPANDED) && t->f && (t->f->flags & FF_EXPANDED) && t->h && (t->h->tag != t->t)) abort(); } check(&top); check(&cur); y = 0; c = top; while (c.t) { if (c.t->flags & TF_EXPANDED) { if (c.f) { if (c.f->flags & FF_EXPANDED) { if (c.h) { if (c.h->flags & HF_EXPANDED) { if (c.l) { if ((!tagmode || (c.t == cur.t)) && (c.f == cur.f) && (c.h == cur.h) && (c.l == cur.l)) curline = y; show_line(&line_line,y,c.l); } else { if ((!tagmode || (c.t == cur.t)) && (c.f == cur.f) && (c.h == cur.h) && !cur.l) curline = y; show_line(&hunk_line,y,c.h); } } else { if ((!tagmode || (c.t == cur.t)) && (c.f == cur.f) && (c.h == cur.h)) curline = y; show_line(&hunk_line,y,c.h); } } else { if ((!tagmode || (c.t == cur.t)) && (c.f == cur.f) && !cur.h) curline = y; show_line(&file_line,y,c.f); } } else { if ((!tagmode || (c.t == cur.t)) && (c.f == cur.f)) curline = y; show_line(&file_line,y,c.f); } } else { if ((c.t == cur.t) && !cur.f) curline = y; show_line(&tag_line,y,c.t); } } else { if (c.t == cur.t) curline = y; show_line(&tag_line,y,c.t); } y ++; if (y >= LINES) break; tfhl_down(&c); } clrtobot(); } static void redraw_list_normal(void) { int y; TFHL c; y = 0; c = top; while (c.f) { if (c.f->flags & FF_EXPANDED) { if (c.h) { if (c.h->flags & HF_EXPANDED) { if (c.l) { if ((c.f == cur.f) && (c.h == cur.h) && (c.l == cur.l)) curline = y; show_line(&line_line,y,c.l); } else { if ((c.f == cur.f) && (c.h == cur.h) && !cur.l) curline = y; show_line(&hunk_line,y,c.h); } } else { if ((c.f == cur.f) && (c.h == cur.h)) curline = y; show_line(&hunk_line,y,c.h); } } else { if ((c.f == cur.f) && !cur.h) curline = y; show_line(&file_line,y,c.f); } } else { if (c.f == cur.f) curline = y; show_line(&file_line,y,c.f); } y ++; if (y >= LINES) break; tfhl_down(&c); } clrtobot(); } static void redraw_list(void) { if (tagmode) { redraw_list_tagmode(); } else { redraw_list_normal(); } } static void recenter(void) { TFHL t; TFHL t2; int i; t = cur; for (i=(LINES-1)/2;i>0;i--) { t2 = t; tfhl_up(&t2); if (tagmode ? !t2.t : !t2.f) break; t = t2; } top = t; } static char *new_temp_name(const char *dname, const char *s) { char *name; asprintf(&name,"%s/.~%s~%x",dname,s,tempserial++); return(name); } static void open_temp_in_dir(const char *dname, const char *s, unsigned int mode, int *fdp, char **namep) { char *name; int fd; while (1) { name = new_temp_name(dname,s); fd = open(name,O_RDWR|O_CREAT|O_EXCL,mode); if (fd >= 0) { *fdp = fd; *namep = name; return; } if (errno == EEXIST) { free(name); continue; } fatal("can't open temp file %s: %s",name,strerror(errno)); } } static char hlt_char(HLT type) { switch (type) { case HLT_COMMON: return(' '); break; case HLT_FROM: return('-'); break; case HLT_TO: return('+'); break; } abort(); } static MEMSTREAM *gen_applied_file(FIL *f, FILE *lf) { typedef struct apply APPLY; struct apply { int il; HUNK *h; HUNKLINE *l; int start; } ; MEMSTREAM *ms; HUNK *h; APPLY *app; int napp; int i; APPLY *a; int fx; unsigned char *nl; int ll; int offset; int oaccum; void input_line(int to, const void *text, int textlen) { APPLY *a; HUNKLINE *l; if (to >= napp) { #ifdef APPLY_HUNK_DETAIL fprintf(lf,"input_line: %d - %.*s\n",to,textlen,(const char *)text); #endif memstream_append(ms,text,textlen); memstream_append(ms,"\n",1); return; } a = &app[to]; #ifdef APPLY_HUNK_DETAIL fprintf(lf,"input_line: %d %d %.*s\n",to,a->il,textlen,(const char *)text); #endif l = a->l; if ((a->il < a->start) || !l) { a->il ++; input_line(to+1,text,textlen); return; } switch (l->type) { case HLT_COMMON: case HLT_FROM: break; default: fatal("impossible next diff line"); break; } if ( (textlen != strlen(l->text)) || bcmp(text,l->text,textlen) ) { fatal("diff doesn't match input"); } if (l->type == HLT_COMMON) input_line(to+1,text,textlen); a->il ++; l = l->flink; while (l && (l->type == HLT_TO)) { input_line(to+1,l->text,strlen(l->text)); l = l->flink; } a->l = l; } fprintf(lf,"gen_applied_file entry\n"); ms = memstream_open_w(); napp = 0; for (h=f->diff_h;h;h=h->fflink) if (h->flags & (HF_APPLIED|HF_SELECTED)) napp ++; app = malloc(napp*sizeof(APPLY)); i = 0; for (h=f->diff_h;h;h=h->fflink) { if (h->flags & (HF_APPLIED|HF_SELECTED)) { if (i >= napp) fatal("apply array overrun"); app[i] = (APPLY){ .il = 1, .h = h, .l = h->line_h, .start = 0 }; i ++; } } if (i != napp) fatal("apply array size wrong"); offset = 0; oaccum = 0; for (i=0;istart = a->h->fl + offset; oaccum += a->h->tn - a->h->fn; if ( !a->h->splitfrom || ( (i+1 < napp) && (app[i+1].h->splitfrom != a->h->splitfrom) ) ) { offset = oaccum; } } for (i=0;ihdrline); for (l=app[i].h->line_h;l;l=l->flink) { fprintf(lf,"\t%c%s\n",hlt_char(l->type),l->text); } } for (i=napp-1;i>=0;i--) { a = &app[i]; if (a->h->fl == 1) { while (a->l && a->l->type == HLT_TO) { input_line(i+1,a->l->text,strlen(a->l->text)); a->l = a->l->flink; } } } fx = 0; while (1) { if (fx > f->contents_len) fatal("original file overrun"); if (fx == f->contents_len) break; nl = memchr(f->contents+fx,'\n',f->contents_len-fx); if (! nl) fatal("file %s missing final newline",f->escname); ll = nl - (f->contents + fx); input_line(0,f->contents+fx,ll); fx += ll + 1; } free(app); return(ms); } static FILE *fopen_info_file(char **namep) { int fd; char *name; open_temp_in_dir(".","info",0644,&fd,&name); *namep = name; return(fdopen(fd,"w")); } static int tfhl_equal(TFHL a, TFHL b) { if (tagmode) { if (a.t != b.t) return(0); if (!a.t || !(a.t->flags & FF_EXPANDED)) return(1); } if (a.f != b.f) return(0); if (!a.f || !(a.f->flags & FF_EXPANDED)) return(1); if (a.h != b.h) return(0); if (!a.h || !(a.h->flags & HF_EXPANDED)) return(1); if (a.l != b.l) return(0); return(1); } static void cmd_down(void) { TFHL ct; ct = cur; tfhl_down(&ct); if (tagmode ? !!ct.t : !!ct.f) cur = ct; } static void cmd_up(void) { TFHL ct; ct = cur; tfhl_up(&ct); if (tagmode ? !!ct.t : !!ct.f) cur = ct; } static void cmd_down_screen(void) { TFHL t; int i; int match; t = top; match = tfhl_equal(t,cur); for (i=(LINES*4)/5;i>0;i--) { tfhl_down(&t); if (tagmode ? !t.t : !t.f) break; top = t; if (match) cur = t; else if (tfhl_equal(t,cur)) match = 1; } } static void cmd_up_screen(void) { TFHL t; TFHL last; int i; t = top; for (i=(LINES*4)/5;i>0;i--) { tfhl_up(&t); if (tagmode ? !t.t : !t.f) break; top = t; } t = top; for (i=LINES;(tagmode?!!t.t:!!t.f)&&(i>0);i--) { if (tfhl_equal(t,cur)) return; last = t; tfhl_down(&t); } cur = last; } static void cmd_down_level(void) { TFHL ct; ct = cur; tfhl_down_level(&ct); if (ct.f) cur = ct; } static void cmd_up_level(void) { TFHL ct; ct = cur; tfhl_up_level(&ct); if (ct.f) cur = ct; } static void cmd_close(void) { if (!tagmode || (cur.t->flags & TF_EXPANDED)) { if (!tagmode || cur.f) { if (cur.f->flags & FF_EXPANDED) { if (cur.h) { if (cur.h->flags & HF_EXPANDED) { if (cur.l) { cur.l = 0; } else { cur.h->flags &= ~HF_EXPANDED; } } else { cur.h = 0; } } else { cur.f->flags &= ~FF_EXPANDED; } } else { if (tagmode) cur.f = 0; } } else { cur.t->flags &= ~TF_EXPANDED; } } } static void cmd_open(void) { HUNK *h; if (!tagmode || (cur.t->flags & TF_EXPANDED)) { if (!tagmode || cur.f) { if (cur.f->flags & FF_EXPANDED) { if (cur.h) { if (cur.h->flags & HF_EXPANDED) { if (! cur.l) cur.l = cur.h->line_h; } else { cur.h->flags |= HF_EXPANDED; cur.l = 0; } } else { cur.h = cur.f->diff_h; cur.l = 0; } } else { cur.f->flags |= FF_EXPANDED; cur.h = 0; } } else { cur.f = file_h; while <"find"> (cur.f) { for (h=cur.f->diff_h;h;h=h->fflink) { if (h->flags & HF__HIDDEN) continue; if (h->tag == cur.t) break <"find">; } cur.f = cur.f->flink; } cur.h = 0; cur.l = 0; } } else { cur.t->flags |= TF_EXPANDED; cur.f = 0; } } static void cmd_seltoggle(void) { HUNK *h; int na; int nna; if ( (!tagmode || (cur.t->flags & TF_EXPANDED)) && cur.f && (cur.f->flags & FF_EXPANDED) && cur.h ) { cur.h->flags ^= HF_SELECTED; } else if (tagmode && !cur.f) { if (cur.t->flags & TF_EXPANDED) { na = 0; nna = 0; for (h=hunk_h;h;h=h->aflink) { if (h->tag == cur.t) { if (h->flags & HF_SELECTED) na ++; else nna ++; } } if (nna) { for (h=hunk_h;h;h=h->aflink) { if (h->tag == cur.t) h->flags |= HF_SELECTED; } } else { for (h=hunk_h;h;h=h->aflink) { if (h->tag == cur.t) h->flags &= ~HF_SELECTED; } } } else { cur.t->flags |= TF_EXPANDED; } } else { beep(); } } static int commit_permitted(void) { FIL *f; HUNK *h; for (f=file_h;f;f=f->flink) { for (h=f->diff_h;h;h=h->fflink) { if ( h->splitfrom && h->fflink && (h->fflink->splitfrom == h->splitfrom) ) { if (!(h->flags & HF_APPLIED) && (h->fflink->flags & HF_APPLIED)) fatal("impossible split-up application"); if (!(h->flags & (HF_SELECTED|HF_APPLIED)) && (h->fflink->flags & HF_SELECTED)) return(0); } } } return(1); } static void cmd_commit(void) { FIL *f; HUNK *h; unsigned int fl; TFHL t; FILE *lf; char *infofn; GITFI *gfi; MEMSTREAM *ms; if (merging || !commit_permitted()) { beep(); return; } ui_save(); lf = fopen_info_file(&infofn); setbuf(lf,0); fprintf(lf,"starting git fast-import\n"); gfi = git_fast_import_start(); for (f=file_h;f;f=f->flink) { fprintf(lf,"generating patched %s...",f->escname); ms = gen_applied_file(f,lf); fprintf(lf,"feeding to git fast-import..."); git_fast_import_file(gfi,f->escname,f->mode,ms); fprintf(lf,"freeing...\n"); memstream_free(ms); fprintf(lf,"done\n"); } fprintf(lf,"finishing git fast-import\n"); git_fast_import_finish(gfi); fprintf(lf,"running git commit..."); if (git_commit()) { fprintf(lf,"success\n"); for (h=hunk_h;h;h=h->aflink) { if (h->flags & HF_SELECTED) h->flags = (h->flags & ~HF_SELECTED) | HF_APPLIED; } for (f=file_h;f;f=f->flink) { fl = HF_APPLIED; for (h=f->diff_h;h;h=h->fflink) fl &= h->flags; if (fl & HF_APPLIED) f->flags = (f->flags & ~FF_EXPANDED) | FF_ALLAPPLIED; } commit_cur = cur; if ( cur.f && ( (cur.f->flags & FF_ALLAPPLIED) || ( (cur.f->flags & FF_EXPANDED) && cur.h && (cur.h->flags & HF_APPLIED) ) ) ) { t = cur; if ( (t.f->flags & FF_EXPANDED) && t.h && (t.h->flags & HF_EXPANDED) ) t.l = t.h->line_t; tfhl_down(&t); if (tagmode ? !t.t : !t.f) { t = cur; t.l = 0; tfhl_up(&t); if (tagmode ? !t.t : !t.f) { unlink(infofn); printf("All hunks applied!\n"); exit(0); } } cur = t; } if ( top.f && ( (top.f->flags & FF_ALLAPPLIED) || ( (top.f->flags & FF_EXPANDED) && top.h && (top.h->flags & HF_APPLIED) ) ) ) { recenter(); } } else { fprintf(lf,"failed\n"); } fclose(lf); unlink(infofn); ui_restore(); } static int run_editor(const char *fn) { const char *editor; PROC *p; int status; editor = getenv("GIT_EDITOR"); if (! editor) editor = getenv("EDITOR"); if (! editor) editor = "vi"; p = proc_start(PROC_N,2,editor,fn,PROC_END); status = proc_await(p); return(WIFEXITED(status)&&(WEXITSTATUS(status)==0)); } static void free_hunk_and_lines(HUNK *h) { HUNKLINE *l; while ((l = h->line_h)) { h->line_h = l->flink; free(l->text); free(l); } free(h->hdrline); free(h); } static void gen_hdrline(HUNK *h) { asprintf(&h->hdrline,"@@ -%d,%d +%d,%d @@%s",h->fl,h->fn,h->tl,h->tn,h->splitfrom?" (split)":""); } static HUNK *read_hunk(FILE *f, HUNK *basis) { HUNK *h; HUNKLINE *hl; unsigned char *lb; int la; int ll; int c; int lwh; HLT lt; void l_append(int ch) { if (ll >= la) lb = realloc(lb,la=ll+8); lb[ll++] = ch; } h = new_hunk(); lwh = 0; lb = 0; la = 0; do <"done"> { do <"err"> { while <"lines"> (1) { ll = 0; while (1) { c = getc(f); if (c < 0) { if (ll == 0) break <"lines">; break; } if (c == '\n') break; l_append(c); } l_append('\0'); ll --; hl = malloc(sizeof(HUNKLINE)); switch (lb[0]) { default: fprintf(stderr,"%s: invalid line type character `%c' in edited diff\n",__progname,lb[0]); fprintf(stderr,"%s: line is: %s\n",__progname,lb); break <"err">; case '\0': fprintf(stderr,"%s: empty line in edited diff\n",__progname); break <"err">; case '#': break; case ' ': lt = HLT_COMMON; if (0) { case '-': lt = HLT_FROM; } if (0) { case '+': lt = HLT_TO; } hl = malloc(sizeof(HUNKLINE)); hl->flink = 0; hl->blink = h->line_t; h->line_t = hl; if (hl->blink) hl->blink->flink = hl; else h->line_h = hl; hl->h = h; hl->l = lwh++; hl->type = lt; hl->text = strdup(lb+1); break; } ll = 0; } break <"done">; } while (0); free_hunk_and_lines(h); h = 0; } while (0); free(lb); if (h) { h->f = basis->f; h->flags = basis->flags; h->fl = basis->fl; h->tl = basis->tl; for (hl=h->line_h;hl;hl=hl->flink) { switch (hl->type) { default: abort(); break; case HLT_COMMON: h->fn ++; h->tn ++; break; case HLT_FROM: h->fn ++; break; case HLT_TO: h->tn ++; break; } } gen_hdrline(h); } return(h); } static int hunks_match(HUNK *h1, HUNK *h2, HUNKLINE **lp1, HUNKLINE **lp2) { HUNKLINE *l1; HUNKLINE *l2; l1 = h1->line_h; l2 = h2->line_h; while (1) { while (l1 && (l1->type == HLT_TO)) l1 = l1->flink; while (l2 && (l2->type == HLT_TO)) l2 = l2->flink; if (!l1 && !l2) return(1); if (!l1 || !l2) break; if (strcmp(l1->text,l2->text)) break; l1 = l1->flink; l2 = l2->flink; } if (lp1) *lp1 = l1; if (lp2) *lp2 = l2; return(0); } static void load_hunk_lines(HUNKLINE *l, char **v, int vn) { int i; i = 0; for (;l;l=l->flink) { if (l->type == HLT_FROM) continue; if (i >= vn) abort(); v[i++] = l->text; } if (i != vn) abort(); } #ifdef DIFF_DETAIL #define f difftracef static FILE *f; #endif static void do_diff(char **al, int an, char *acv, char **bl, int bn, char *bcv) { static int **cv = 0; static int *cvdata = 0; int i; int search(int a, int b, int setc) { #ifdef DIFF_DETAIL static int depth = 0; fprintf(f,"%*ssearch %d %d %d\n",depth,"",a,b,setc); #endif if (a < 1) { if (b < 1) { #ifdef DIFF_DETAIL fprintf(f,"%*sreturn 0 [corner]\n",depth,""); #endif return(0); } else { if (setc) { #ifdef DIFF_DETAIL static int j; for (j=b-1;j>=0;j--) { fprintf(f,"bcv[%d] = 0\n",j); bcv[j] = 0; } #else bzero(bcv,b); #endif } #ifdef DIFF_DETAIL fprintf(f,"%*sreturn %d [edge]\n",depth,"",b); #endif return(b); } } else { if (b < 1) { if (setc) { #ifdef DIFF_DETAIL static int j; for (j=a-1;j>=0;j--) { fprintf(f,"acv[%d] = 0\n",j); acv[j] = 0; } #else bzero(acv,a); #endif } #ifdef DIFF_DETAIL fprintf(f,"%*sreturn %d [edge]\n",depth,"",a); #endif return(a); } else { int v; int v2; v = cv[a-1][b-1]; if ((v >= 0) && !setc) { #ifdef DIFF_DETAIL fprintf(f,"%*sreturn %d [memo]\n",depth,"",v); #endif return(v); } if (! strcmp(al[a-1],bl[b-1])) { #ifdef DIFF_DETAIL fprintf(f,"%*sequal <%s>\n",depth,"",al[a-1]); depth += 2; #endif v = search(a-1,b-1,setc); #ifdef DIFF_DETAIL depth -= 2; #endif if (setc) { acv[a-1] = 1; bcv[b-1] = 1; #ifdef DIFF_DETAIL fprintf(f,"acv[%d] = 1\nbcv[%d] = 1\n",a-1,b-1); #endif } } else { #ifdef DIFF_DETAIL fprintf(f,"%*sdiffer <%s> <%s>\n",depth,"",al[a-1],bl[b-1]); depth += 2; #endif v = search(a-1,b,0) + 1; #ifdef DIFF_DETAIL fprintf(f,"%*s----\n",depth-2,""); #endif v2 = search(a,b-1,0) + 1; #ifdef DIFF_DETAIL depth -= 2; fprintf(f,"%*svalues are %d %d\n",depth,"",v,v2); #endif if (v2 < v) { v = v2; if (setc) { #ifdef DIFF_DETAIL fprintf(f,"%*ssetc, using second choice\n",depth,""); depth += 2; #endif search(a,b-1,1); #ifdef DIFF_DETAIL depth -= 2; fprintf(f,"bcv[%d] = 0\n",b-1); #endif bcv[b-1] = 0; } } else { if (setc) { #ifdef DIFF_DETAIL fprintf(f,"%*ssetc, using first choice\n",depth,""); depth += 2; #endif search(a-1,b,1); #ifdef DIFF_DETAIL depth -= 2; fprintf(f,"acv[%d] = 0\n",a-1); #endif acv[a-1] = 0; } } } #ifdef DIFF_DETAIL fprintf(f,"%*sreturn %d [&save]\n",depth,"",v); #endif cv[a-1][b-1] = v; return(v); } } } free(cv); free(cvdata); cv = malloc(an*sizeof(*cv)); cvdata = malloc(an*bn*sizeof(*cvdata)); for (i=(an*bn)-1;i>=0;i--) cvdata[i] = -1; for (i=an-1;i>=0;i--) cv[i] = &cvdata[i*bn]; search(an,bn,1); } #ifdef DIFF_DETAIL static void dump_diffs(FILE *f, char **l, char *c, int n, const char *p) { int i; fprintf(f,"%scomm\t%slines\n",p,p); for (i=0;itn]; char *blines[b->tn]; char acomm[a->tn]; char bcomm[b->tn]; int i; int j; HUNKLINE *hl; HUNK *h; HLT lt; const char *txt; int lwh; #ifdef DIFF_DETAIL f = fopen("z.fnord","w"); #endif load_hunk_lines(a->line_h,&alines[0],a->tn); load_hunk_lines(b->line_h,&blines[0],b->tn); memset(&acomm[0],2,a->tn); memset(&bcomm[0],2,b->tn); do_diff(&alines[0],a->tn,&acomm[0],&blines[0],b->tn,&bcomm[0]); #ifdef DIFF_DETAIL dump_diffs(f,&alines[0],&acomm[0],a->tn,"a"); fprintf(f,"\n"); dump_diffs(f,&blines[0],&bcomm[0],b->tn,"b"); fclose(f); #endif if (memchr(&acomm[0],2,a->tn)) abort(); if (memchr(&bcomm[0],2,b->tn)) abort(); h = new_hunk(); i = 0; j = 0; if ((a->f != b->f) || (a->fl != b->fl) || (a->tl != b->tl)) abort(); h->f = a->f; h->flags = a->flags; h->fl = a->fl; h->tl = a->tl; lwh = 0; while ((i < a->tn) || (j < b->tn)) { if ((i < a->tn) && !acomm[i]) { lt = HLT_FROM; txt = alines[i]; h->fn ++; i ++; } else if ((j < b->tn) && !bcomm[j]) { lt = HLT_TO; txt = blines[j]; h->tn ++; j ++; } else { if ((i >= a->tn) || (j >= b->tn) || !acomm[i] || !bcomm[j]) abort(); if (strcmp(alines[i],blines[j])) abort(); lt = HLT_COMMON; txt = alines[i]; h->fn ++; h->tn ++; i ++; j ++; } hl = malloc(sizeof(HUNKLINE)); hl->h = h; hl->l = lwh++; hl->type = lt; hl->text = strdup(txt); hl->flink = 0; hl->blink = h->line_t; if (hl->blink) hl->blink->flink = hl; else h->line_h = hl; h->line_t = hl; } if ((h->fn != a->tn) || (h->tn != b->tn)) abort(); gen_hdrline(h); return(h); } #ifdef DIFF_DETAIL #undef f #endif static void split_hunk(void) { char *tempfn; int fd; FILE *fp; HUNKLINE *l; HUNKLINE *l2; HUNK *h; HUNK *h2; int iresp; if (merging || !(cur.f->flags & FF_EXPANDED) || !cur.h) { beep(); return; } ui_save(); if (cur.h->flags & HF__HIDDEN) abort(); open_temp_in_dir(".","split",0644,&fd,&tempfn); fp = fdopen(fd,"r+"); rewind(fp); for (l=cur.h->line_h;l;l=l->flink) { switch (l->type) { default: abort(); break; case HLT_COMMON: case HLT_FROM: case HLT_TO: putc(hlt_char(l->type),fp); break; } fprintf(fp,"%s\n",l->text); } fclose(fp); while <"edit"> (1) { if (run_editor(tempfn)) { fd = open(tempfn,O_RDONLY,0); if (fd < 0) { switch (errno) { case EEXIST: printf("Editor destroyed %s\n",tempfn); break; case EACCES: printf("Editor revoked read access to %s\n",tempfn); break; default: printf("Can't reopen %s: %s\n",tempfn,strerror(errno)); break; } exit(1); } do <"err"> { fp = fdopen(fd,"r"); h = read_hunk(fp,cur.h); if (! h) break <"err">; if (! hunks_match(h,cur.h,&l,&l2)) { printf("Old text doesn't match:\n"); if (l) { printf(" edited hunk: %c%s\n",hlt_char(l->type),l->text); } else { printf(" edited hunk ends\n"); } if (l2) { printf("original hunk: %c%s\n",hlt_char(l2->type),l2->text); } else { printf("original hunk ends\n"); } break <"err">; } h2 = intermediate_hunk(h,cur.h); h->aflink = h2; h2->aflink = cur.h->aflink; h->ablink = cur.h->ablink; h2->ablink = h; h->fflink = h2; h2->fflink = cur.h->fflink; h->fblink = cur.h->fblink; h2->fblink = h; h->tag = cur.h->tag; h2->tag = cur.h->tag; if (cur.h->ablink) cur.h->ablink->aflink = h; else hunk_h = h; if (cur.h->aflink) cur.h->aflink->ablink = h2; else hunk_t = h2; if (cur.h->fblink) cur.h->fblink->fflink = h; else cur.h->f->diff_h = h; if (cur.h->fflink) cur.h->fflink->fblink = h2; else cur.h->f->diff_t = h2; if (cur.h->splitfrom) { h->splitfrom = cur.h->splitfrom; h2->splitfrom = cur.h->splitfrom; free_hunk_and_lines(cur.h); } else { cur.h->flags = HF_SPLIT; /* drop other flags */ cur.h->ablink = h->ablink; cur.h->aflink = h; if (h->ablink) h->ablink->aflink = cur.h; else hunk_h = cur.h; h->ablink = cur.h; h->splitfrom = cur.h; h2->splitfrom = cur.h; } cur.h = h; cur.l = h->line_h; break <"edit">; } while (0); if (h) free_hunk_and_lines(h); while (1) { printf("e-edit or iscard edits? "); fpurge(stdin); iresp = getchar(); fpurge(stdin); switch (iresp) { case 'r': case 'R': continue <"edit">; break; case 'd': case 'D': case EOF: break <"edit">; } } } } unlink(tempfn); ui_restore(); } static HUNK *do_merge(HUNK *a, HUNK *b) { HUNK *new; HUNKLINE *al; HUNKLINE *bl; int lwh; void new_line(HLT t, HUNKLINE *src) { HUNKLINE *nl; nl = malloc(sizeof(HUNKLINE)); nl->flink = 0; nl->blink = new->line_t; new->line_t = nl; if (nl->blink) nl->blink->flink = nl; else new->line_h = nl; nl->h = new; nl->l = lwh++; nl->type = t; nl->text = strdup(src->text); switch (t) { case HLT_COMMON: new->fn ++; new->tn ++; break; case HLT_FROM: new->fn ++; break; case HLT_TO: new->tn ++; break; default: abort(); break; } } if ( (a->aflink != b) || (a != b->ablink) || (a->fflink != b) || (a != b->fblink) || (a->f != b->f) || !a->splitfrom || (a->splitfrom != b->splitfrom) ) abort(); new = new_hunk(); lwh = 0; al = a->line_h; bl = b->line_h; while (1) { while (al && (al->type == HLT_FROM)) { new_line(HLT_FROM,al); al = al->flink; } while (bl && (bl->type == HLT_TO)) { new_line(HLT_TO,bl); bl = bl->flink; } if (!al && !bl) break; if (!al || !bl || strcmp(al->text,bl->text)) { void dump_hunk(HUNK *h) { HUNKLINE *l; printf(" %s @@ -%d,%d +%d,%d\n",h->f->escname,h->fl,h->fn,h->tl,h->tn); for (l=h->line_h;l;l=l->flink) printf("\t%c%s\n",hlt_char(l->type),l->text); } ui_abort(); printf("do_merge: hunks don't match\nhunk 1:\n"); dump_hunk(a); printf("hunk 2:\n"); dump_hunk(b); exit(1); } switch (al->type) { case HLT_COMMON: switch (bl->type) { case HLT_COMMON: new_line(HLT_COMMON,al); break; case HLT_FROM: new_line(HLT_FROM,al); break; default: abort(); break; } break; case HLT_TO: switch (bl->type) { case HLT_COMMON: new_line(HLT_TO,al); break; case HLT_FROM: break; default: abort(); break; } break; default: abort(); break; } al = al->flink; bl = bl->flink; } new->fl = a->fl; new->tl = a->tl; gen_hdrline(new); new->f = a->f; new->flags = (a->flags | b->flags) & HF_EXPANDED; new->splitfrom = a->splitfrom; new->ablink = a->ablink; new->aflink = b->aflink; if (new->ablink) new->ablink->aflink = new; else hunk_h = new; if (new->aflink) new->aflink->ablink = new; else hunk_t = new; new->fblink = a->fblink; new->fflink = b->fflink; if (new->fblink) new->fblink->fflink = new; else a->f->diff_h = new; if (new->fflink) new->fflink->fblink = new; else a->f->diff_t = new; new->tag = a->tag; free_hunk_and_lines(a); free_hunk_and_lines(b); return(new); } static int hunks_identical(HUNK *a, HUNK *b) { HUNKLINE *al; HUNKLINE *bl; if ( (a->fl != b->fl) || (a->fn != b->fn) || (a->tl != b->tl) || (a->tn != b->tn) ) return(0); al = a->line_h; bl = b->line_h; while (1) { if (!al && !bl) break; if (!al || !bl) return(0); if (al->type != bl->type) return(0); if (strcmp(al->text,bl->text)) return(0); al = al->flink; bl = bl->flink; } return(1); } static void cmd_mergesplit(void) { if (!(cur.f->flags & FF_EXPANDED) || !cur.h) { beep(); return; } if (cur.h == merging) { merging = 0; return; } if (cur.h->flags & HF__HIDDEN) abort(); if (merging) { if (merging->flags & HF__HIDDEN) abort(); if ((cur.h->flags ^ merging->flags) & HF_SELECTED) { beep(); return; } if (cur.h->splitfrom != merging->splitfrom) { beep(); } else if (cur.h->fflink == merging) { cur.h = do_merge(cur.h,merging); cur.l = 0; merging = 0; } else if (merging->fflink == cur.h) { cur.h = do_merge(merging,cur.h); cur.l = 0; merging = 0; } else { beep(); } if ( !merging && (!cur.h->fblink || (cur.h->fblink->splitfrom != cur.h->splitfrom)) && (!cur.h->fflink || (cur.h->fflink->splitfrom != cur.h->splitfrom)) ) { HUNK *h; h = cur.h->splitfrom; if (! hunks_identical(cur.h,h)) abort(); if (h->aflink) h->aflink->ablink = h->ablink; else hunk_t = h->ablink; if (h->ablink) h->ablink->aflink = h->aflink; else hunk_h = h->aflink; free_hunk_and_lines(h); cur.h->splitfrom = 0; free(cur.h->hdrline); gen_hdrline(cur.h); } } else { if (cur.h->splitfrom) { merging = cur.h; } else { beep(); } } } static TAG *new_tag(void) { TAG *t; t = malloc(sizeof(TAG)); t->flink = 0; t->blink = 0; t->tag = 0; t->flags = 0; return(t); } static TAG *find_tag(const char *tag, int create) { TAG *t; for (t=tag_h;t;t=t->flink) if (tag ? t->tag&&!strcmp(t->tag,tag) : !t->tag) return(t); if (! create) return(0); t = new_tag(); t->tag = tag ? strdup(tag) : 0; t->flink = tag_h; t->blink = 0; if (tag_h) tag_h->blink = t; else tag_t = t; tag_h = t; return(t); } static void ensure_tags(void) { HUNK *h; TAG *t; t = find_tag(0,1); for (h=hunk_h;h;h=h->aflink) if (h->tag == 0) h->tag = t; } static void cmd_switch_tagmode(void) { if (tagmode) { tagmode = 0; if (! cur.f) { top.f = file_h; top.h = 0; top.l = 0; cur = top; } } else { tagmode = 1; ensure_tags(); if (! cur.f) abort(); if ((cur.f->flags & FF_EXPANDED) && cur.h) { cur.t = cur.h->tag; cur.t->flags |= TF_EXPANDED; recenter(); } else { cur.t = tag_h; cur.f = 0; top = cur; } } } HUNK *new_hunk(void) { HUNK *h; h = malloc(sizeof(HUNK)); h->aflink = 0; h->ablink = 0; h->fflink = 0; h->fblink = 0; h->f = 0; h->flags = 0; h->splitfrom = 0; h->hdrline = 0; h->fl = 0; h->fn = 0; h->tl = 0; h->tn = 0; h->line_h = 0; h->line_t = 0; h->tag = 0; return(h); } static void print_flags(FILE *, unsigned int, ...); static void print_flags(FILE *o, unsigned int flg, ...) { const char *sep; va_list ap; const char *fname; unsigned int fbit; if (flg == 0) { fprintf(o,"0"); return; } sep = ""; va_start(ap,flg); while (1) { fname = va_arg(ap,const char *); if (fname == 0) break; fbit = va_arg(ap,unsigned int); if (flg & fbit) { fprintf(o,"%s%s",sep,fname); sep = "|"; flg &= ~fbit; } } if (flg) fprintf(o,"%s%#x",sep,flg); } /* This is intended to be debugger-callable */ static void dump_state(void) __attribute__((__unused__)); static void dump_state(void) { FILE *o; TAG *t; FIL *f; HUNK *h; HUNKLINE *l; int nah; int nfh; o = fopen("z.dump","w"); if (o == 0) { beep(); return; } nah = 0; for (h=hunk_h;h;h=h->aflink) nah ++; nfh = 0; for (f=file_h;f;f=f->flink) { fprintf(o,"FIL %p is %s, flags ",(void *)f,f->escname); print_flags(o,f->flags,"ESCNAME",FF_ESCNAME,"ALLAPPLIED",FF_ALLAPPLIED,"EXPANDED",FF_EXPANDED,(char *)0); fprintf(o,", contents %d at %p, mode %#o\n",f->contents_len,(void *)f->contents,f->mode); for (h=f->diff_h;h;h=h->fflink) { nfh ++; fprintf(o," HUNK %p from %p is %d@%d -> %d@%d, flags ",(void *)h,h->f,h->fn,h->fl,h->tn,h->tl); print_flags(o,h->flags,"APPLIED",HF_APPLIED,"SELECTED",HF_SELECTED,"EXPANDED",HF_EXPANDED,"SPLIT",HF_SPLIT,(char *)0); fprintf(o," splitfrom %p",(void *)h->splitfrom); if (h->tag) { if (h->tag->tag) { fprintf(o," tag %p (%s)",(void *)h->tag,h->tag->tag); } else { fprintf(o," untagged %p",(void *)h->tag); } } else { fprintf(o," no tag"); } fprintf(o,": %s\n",h->hdrline); for (l=h->line_h;l;l=l->flink) { fprintf(o," "); if (l->h != h) fprintf(o,"[HUNK %p] ",l->h); fprintf(o,"%d ",l->l); switch (l->type) { case HLT_COMMON: fprintf(o," "); break; case HLT_FROM: fprintf(o,"-"); break; case HLT_TO: fprintf(o,"+"); break; default: fprintf(o,"(%d)",(int)l->type); break; } fprintf(o,"%s\n",l->text); } } } if (nah != nfh) { fprintf(o,"*** NOTE nah %d, nfh %d\n",nah,nfh); } for (t=tag_h;t;t=t->flink) { fprintf(o,"tag %p:",(void *)t); if (t->tag) fprintf(o," tag %s",t->tag); else fprintf(o," untagged"); fprintf(o," flags "); print_flags(o,t->flags,"EXPANDED",TF_EXPANDED,(char *)0); fprintf(o,"\n"); } fclose(o); } static char *get_edited(int, const char *, const char *, ...) __attribute__((__format__(__printf__,3,4))); static char *get_edited(int y, const char *init, const char *fmt, ...) { va_list ap; char *prompt; int promptlen; char *b; int ba; int bl; int ec; int l; int s; int s14; int s34; int doff; int curs; int i; int x; int c; void b_len(int n) { if (ba < n) b = realloc(b,ba=n+8); } va_start(ap,fmt); vasprintf(&prompt,fmt,ap); va_end(ap); promptlen = strlen(prompt); bl = strlen(init); ba = bl + 8; b = malloc(ba); bcopy(init,b,bl); curs = bl; doff = 0; while (1) { ec = COLS; if (ec < 16) ec = 16; move(y,0); s = (ec / 2) - 2; if (promptlen > s) { l = (s / 2) - 2; printw("%.*s...%s",l,prompt,prompt+(promptlen-(s-l-3))); x = s; } else { printw("%s",prompt); x = promptlen; } s = ec - 2 - x; s14 = s / 4; s34 = s - s14; if ((doff > 0) && (curs <= doff+s14)) { doff = curs - s34; if (doff < 0) doff = 0; } if (curs >= doff+((bl>doff+s)?s34:s)) { doff = curs - s14; } if (doff > 0) { printw("<"); i = 1; } else { i = 0; } if (bl >= doff+s) { printw("%.*s>",s-i-1,b+doff+i); } else { printw("%.*s",bl-(doff+i),b+doff+i); } clrtoeol(); move(y,x+curs-doff); refresh(); c = ich(); switch (c) { case 0x01: /* ^A */ curs = 0; break; case 0x02: /* ^B */ if (curs > 0) curs --; break; case 0x04: /* ^D */ if (curs < bl) { bl --; if (curs < bl) bcopy(b+curs+1,b+curs,bl-curs); } break; case 0x05: /* ^E */ curs = bl; break; case 0x06: /* ^F */ if (curs < bl) curs ++; break; case 0x07: /* ^G */ free(b); return(0); break; case 0x08: /* ^H */ case 0x7f: /* DEL */ if (curs > 0) { if (curs < bl) bcopy(b+curs,b+curs-1,bl-curs); bl --; curs --; } break; case 0x0a: /* ^J */ case 0x0d: /* ^M */ b_len(bl+1); b[bl] = '\0'; return(b); break; case 0x0b: /* ^K */ bl = curs; break; case 0x0c: /* ^L */ clearok(stdscr,TRUE); break; case 0x14: /* ^T */ if (curs >= 2) { char tc; tc = b[curs-1]; b[curs-1] = b[curs-2]; b[curs-2] = tc; } break; case 0x18: /* ^X */ curs = 0; bl = 0; break; case 0x20 ... 0x7e: case 0xc0 ... 0xff: b_len(bl+1); if (curs < bl) bcopy(b+curs,b+curs+1,bl-curs); b[curs++] = c; bl ++; break; default: beep(); break; } } } static void cmd_change_tag(void) { TAG *t; char *s; TAG *newt; HUNK *h; if (tagmode && cur.t && !cur.f) { t = cur.t; s = get_edited(curline,t->tag?:"","Rename tag%s%s%s",t->tag?" ":"",t->tag?:"",t->tag?" -> ":": "); if (s == 0) return; newt = find_tag(*s?s:0,0); if (newt) { for (h=hunk_h;h;h=h->aflink) if (h->tag == t) h->tag = newt; if (top.t == cur.t) top.t = newt; cur.t = newt; } else { free(t->tag); t->tag = *s ? strdup(s) : 0; } free(s); } else if (cur.h) { s = cur.h->tag ? cur.h->tag->tag : 0; s = get_edited(curline,s?:"","Tag%s%s%s: ",s?" [":"",s?:"",s?"]":""); if (s == 0) return; ensure_tags(); t = find_tag(*s?s:0,1); free(s); cur.h->tag = t; cur.t = t; t->flags |= TF_EXPANDED; if (tagmode) recenter(); } else { beep(); } } void ui_main_loop(void) { int c; ui_setup(); top.f = file_h; cur = top; while (1) { curline = -1; redraw_list(); if (curline < 0) { recenter(); continue; } else { move(curline,0); } refresh(); c = ich(); switch (c) { case 0x0c: /* ^L */ clearok(stdscr,TRUE); break; case 'j': cmd_down(); break; case 'k': cmd_up(); break; case 'J': cmd_down_level(); break; case 'K': cmd_up_level(); break; case 'h': cmd_close(); break; case 'l': cmd_open(); break; case 'm': cmd_mergesplit(); break; case 'a': cmd_seltoggle(); break; case 'C': cmd_commit(); break; case 'S': split_hunk(); break; case ' ': case 'f': cmd_down_screen(); break; case 'b': cmd_up_screen(); break; case 'T': cmd_switch_tagmode(); break; case 't': cmd_change_tag(); break; case '|': dump_state(); break; } } }