| #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->b, 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<0 || 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; i<f->ntext; 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<p1; i+=n){ |
| n = p1 - i; |
| if(n > RBUFSIZE) |
| n = RBUFSIZE; |
| bufread(&f->b, 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->b.nc) |
| return -1; |
| bufread(&f->b, 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->b, 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->b, 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<u.n; i+=n){ |
| n = u.n - i; |
| if(n > RBUFSIZE) |
| n = RBUFSIZE; |
| bufread(delta, up+i, buf, n); |
| bufinsert(&f->b, 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->b); |
| 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; |
| } |