#include "sam.h" /* * Structure of Undo list: * The Undo structure follows any associated data, so the list * can be read backwards: read the structure, then read whatever * data is associated (insert string, file name) and precedes it. * The structure includes the previous value of the modify bit * and a sequence number; successive Undo structures with the * same sequence number represent simultaneous changes. */ typedef struct Undo Undo; typedef struct Merge Merge; struct Undo { short type; /* Delete, Insert, Filename, Dot, Mark */ short mod; /* modify bit */ uint seq; /* sequence number */ uint p0; /* location of change (unused in f) */ uint n; /* # runes in string or file name */ }; struct Merge { File *f; uint seq; /* of logged change */ uint p0; /* location of change (unused in f) */ uint n; /* # runes to delete */ uint nbuf; /* # runes to insert */ Rune buf[RBUFSIZE]; }; enum { Maxmerge = 50, Undosize = sizeof(Undo)/sizeof(Rune), }; static Merge merge; File* fileopen(void) { File *f; f = emalloc(sizeof(File)); f->dot.f = f; f->ndot.f = f; f->seq = 0; f->mod = FALSE; f->unread = TRUE; Strinit0(&f->name); return f; } int fileisdirty(File *f) { return f->seq != f->cleanseq; } static void wrinsert(Buffer *delta, int seq, int mod, uint p0, Rune *s, uint ns) { Undo u; u.type = Insert; u.mod = mod; u.seq = seq; u.p0 = p0; u.n = ns; bufinsert(delta, delta->nc, s, ns); bufinsert(delta, delta->nc, (Rune*)&u, Undosize); } static void wrdelete(Buffer *delta, int seq, int mod, uint p0, uint p1) { Undo u; u.type = Delete; u.mod = mod; u.seq = seq; u.p0 = p0; u.n = p1 - p0; bufinsert(delta, delta->nc, (Rune*)&u, Undosize); } void flushmerge(void) { File *f; f = merge.f; if(f == nil) return; if(merge.seq != f->seq) panic("flushmerge seq mismatch"); if(merge.n != 0) wrdelete(&f->epsilon, f->seq, TRUE, merge.p0, merge.p0+merge.n); if(merge.nbuf != 0) wrinsert(&f->epsilon, f->seq, TRUE, merge.p0+merge.n, merge.buf, merge.nbuf); merge.f = nil; merge.n = 0; merge.nbuf = 0; } void mergeextend(File *f, uint p0) { uint mp0n; mp0n = merge.p0+merge.n; if(mp0n != p0){ bufread(f, mp0n, merge.buf+merge.nbuf, p0-mp0n); merge.nbuf += p0-mp0n; merge.n = p0-merge.p0; } } /* * like fileundelete, but get the data from arguments */ void loginsert(File *f, uint p0, Rune *s, uint ns) { if(f->rescuing) return; if(ns == 0) return; if(ns>STRSIZE) panic("loginsert"); if(f->seq < seq) filemark(f); if(p0 < f->hiposn) error(Esequence); if(merge.f != f || p0-(merge.p0+merge.n)>Maxmerge /* too far */ || merge.nbuf+((p0+ns)-(merge.p0+merge.n))>=RBUFSIZE) /* too long */ flushmerge(); if(ns>=RBUFSIZE){ if(!(merge.n == 0 && merge.nbuf == 0 && merge.f == nil)) panic("loginsert bad merge state"); wrinsert(&f->epsilon, f->seq, TRUE, p0, s, ns); }else{ if(merge.f != f){ merge.f = f; merge.p0 = p0; merge.seq = f->seq; } mergeextend(f, p0); /* append string to merge */ runemove(merge.buf+merge.nbuf, s, ns); merge.nbuf += ns; } f->hiposn = p0; if(!f->unread && !f->mod) state(f, Dirty); } void logdelete(File *f, uint p0, uint p1) { if(f->rescuing) return; if(p0 == p1) return; if(f->seq < seq) filemark(f); if(p0 < f->hiposn) error(Esequence); if(merge.f != f || p0-(merge.p0+merge.n)>Maxmerge /* too far */ || merge.nbuf+(p0-(merge.p0+merge.n))>=RBUFSIZE){ /* too long */ flushmerge(); merge.f = f; merge.p0 = p0; merge.seq = f->seq; } mergeextend(f, p0); /* add to deletion */ merge.n = p1-merge.p0; f->hiposn = p1; if(!f->unread && !f->mod) state(f, Dirty); } /* * like fileunsetname, but get the data from arguments */ void logsetname(File *f, String *s) { Undo u; Buffer *delta; if(f->rescuing) return; if(f->unread){ /* This is setting initial file name */ filesetname(f, s); return; } if(f->seq < seq) filemark(f); /* undo a file name change by restoring old name */ delta = &f->epsilon; u.type = Filename; u.mod = TRUE; u.seq = f->seq; u.p0 = 0; /* unused */ u.n = s->n; if(s->n) bufinsert(delta, delta->nc, s->s, s->n); bufinsert(delta, delta->nc, (Rune*)&u, Undosize); if(!f->unread && !f->mod) state(f, Dirty); } #ifdef NOTEXT File* fileaddtext(File *f, Text *t) { if(f == nil){ f = emalloc(sizeof(File)); f->unread = TRUE; } f->text = realloc(f->text, (f->ntext+1)*sizeof(Text*)); f->text[f->ntext++] = t; f->curtext = t; return f; } void filedeltext(File *f, Text *t) { int i; for(i=0; intext; i++) if(f->text[i] == t) goto Found; panic("can't find text in filedeltext"); Found: f->ntext--; if(f->ntext == 0){ fileclose(f); return; } memmove(f->text+i, f->text+i+1, (f->ntext-i)*sizeof(Text*)); if(f->curtext == t) f->curtext = f->text[0]; } #endif void fileuninsert(File *f, Buffer *delta, uint p0, uint ns) { Undo u; /* undo an insertion by deleting */ u.type = Delete; u.mod = f->mod; u.seq = f->seq; u.p0 = p0; u.n = ns; bufinsert(delta, delta->nc, (Rune*)&u, Undosize); } void fileundelete(File *f, Buffer *delta, uint p0, uint p1) { Undo u; Rune *buf; uint i, n; /* undo a deletion by inserting */ u.type = Insert; u.mod = f->mod; u.seq = f->seq; u.p0 = p0; u.n = p1-p0; buf = fbufalloc(); for(i=p0; i RBUFSIZE) n = RBUFSIZE; bufread(f, i, buf, n); bufinsert(delta, delta->nc, buf, n); } fbuffree(buf); bufinsert(delta, delta->nc, (Rune*)&u, Undosize); } int filereadc(File *f, uint q) { Rune r; if(q >= f->nc) return -1; bufread(f, q, &r, 1); return r; } void filesetname(File *f, String *s) { if(!f->unread) /* This is setting initial file name */ fileunsetname(f, &f->delta); Strduplstr(&f->name, s); sortname(f); f->unread = TRUE; } void fileunsetname(File *f, Buffer *delta) { String s; Undo u; /* undo a file name change by restoring old name */ u.type = Filename; u.mod = f->mod; u.seq = f->seq; u.p0 = 0; /* unused */ Strinit(&s); Strduplstr(&s, &f->name); fullname(&s); u.n = s.n; if(s.n) bufinsert(delta, delta->nc, s.s, s.n); bufinsert(delta, delta->nc, (Rune*)&u, Undosize); Strclose(&s); } void fileunsetdot(File *f, Buffer *delta, Range dot) { Undo u; u.type = Dot; u.mod = f->mod; u.seq = f->seq; u.p0 = dot.p1; u.n = dot.p2 - dot.p1; bufinsert(delta, delta->nc, (Rune*)&u, Undosize); } void fileunsetmark(File *f, Buffer *delta, Range mark) { Undo u; u.type = Mark; u.mod = f->mod; u.seq = f->seq; u.p0 = mark.p1; u.n = mark.p2 - mark.p1; bufinsert(delta, delta->nc, (Rune*)&u, Undosize); } uint fileload(File *f, uint p0, int fd, int *nulls) { if(f->seq > 0) panic("undo in file.load unimplemented"); return bufload(f, p0, fd, nulls); } int fileupdate(File *f, int notrans, int toterm) { uint p1, p2; int mod; if(f->rescuing) return FALSE; flushmerge(); /* * fix the modification bit * subtle point: don't save it away in the log. * * if another change is made, the correct f->mod * state is saved in the undo log by filemark * when setting the dot and mark. * * if the change is undone, the correct state is * saved from f in the fileun... routines. */ mod = f->mod; f->mod = f->prevmod; if(f == cmd) notrans = TRUE; else{ fileunsetdot(f, &f->delta, f->prevdot); fileunsetmark(f, &f->delta, f->prevmark); } f->dot = f->ndot; fileundo(f, FALSE, !notrans, &p1, &p2, toterm); f->mod = mod; if(f->delta.nc == 0) f->seq = 0; if(f == cmd) return FALSE; if(f->mod){ f->closeok = 0; quitok = 0; }else f->closeok = 1; return TRUE; } long prevseq(Buffer *b) { Undo u; uint up; up = b->nc; if(up == 0) return 0; up -= Undosize; bufread(b, up, (Rune*)&u, Undosize); return u.seq; } long undoseq(File *f, int isundo) { if(isundo) return f->seq; return prevseq(&f->epsilon); } void fileundo(File *f, int isundo, int canredo, uint *q0p, uint *q1p, int flag) { Undo u; Rune *buf; uint i, n, up; uint stop; Buffer *delta, *epsilon; if(isundo){ /* undo; reverse delta onto epsilon, seq decreases */ delta = &f->delta; epsilon = &f->epsilon; stop = f->seq; }else{ /* redo; reverse epsilon onto delta, seq increases */ delta = &f->epsilon; epsilon = &f->delta; stop = 0; /* don't know yet */ } raspstart(f); while(delta->nc > 0){ /* rasp and buffer are in sync; sync with wire if needed */ if(needoutflush()) raspflush(f); up = delta->nc-Undosize; bufread(delta, up, (Rune*)&u, Undosize); if(isundo){ if(u.seq < stop){ f->seq = u.seq; raspdone(f, flag); return; } }else{ if(stop == 0) stop = u.seq; if(u.seq > stop){ raspdone(f, flag); return; } } switch(u.type){ default: panic("undo unknown u.type"); break; case Delete: f->seq = u.seq; if(canredo) fileundelete(f, epsilon, u.p0, u.p0+u.n); f->mod = u.mod; bufdelete(f, u.p0, u.p0+u.n); raspdelete(f, u.p0, u.p0+u.n, flag); *q0p = u.p0; *q1p = u.p0; break; case Insert: f->seq = u.seq; if(canredo) fileuninsert(f, epsilon, u.p0, u.n); f->mod = u.mod; up -= u.n; buf = fbufalloc(); for(i=0; i RBUFSIZE) n = RBUFSIZE; bufread(delta, up+i, buf, n); bufinsert(f, u.p0+i, buf, n); raspinsert(f, u.p0+i, buf, n, flag); } fbuffree(buf); *q0p = u.p0; *q1p = u.p0+u.n; break; case Filename: f->seq = u.seq; if(canredo) fileunsetname(f, epsilon); f->mod = u.mod; up -= u.n; Strinsure(&f->name, u.n+1); bufread(delta, up, f->name.s, u.n); f->name.s[u.n] = 0; f->name.n = u.n; fixname(&f->name); sortname(f); break; case Dot: f->seq = u.seq; if(canredo) fileunsetdot(f, epsilon, f->dot.r); f->mod = u.mod; f->dot.r.p1 = u.p0; f->dot.r.p2 = u.p0 + u.n; break; case Mark: f->seq = u.seq; if(canredo) fileunsetmark(f, epsilon, f->mark); f->mod = u.mod; f->mark.p1 = u.p0; f->mark.p2 = u.p0 + u.n; break; } bufdelete(delta, up, delta->nc); } if(isundo) f->seq = 0; raspdone(f, flag); } void filereset(File *f) { bufreset(&f->delta); bufreset(&f->epsilon); f->seq = 0; } void fileclose(File *f) { Strclose(&f->name); bufclose(f); bufclose(&f->delta); bufclose(&f->epsilon); if(f->rasp) listfree(f->rasp); free(f); } void filemark(File *f) { if(f->unread) return; if(f->epsilon.nc) bufdelete(&f->epsilon, 0, f->epsilon.nc); if(f != cmd){ f->prevdot = f->dot.r; f->prevmark = f->mark; f->prevseq = f->seq; f->prevmod = f->mod; } f->ndot = f->dot; f->seq = seq; f->hiposn = 0; }