| #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>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; | 
 | } |