shithub: purgatorio

ref: a5cb451b299b03f44154fac5780b6a57ca130ce0
dir: /libtk/textu.c/

View raw version
#include "lib9.h"
#include "draw.h"
#include "tk.h"
#include "textw.h"

#define istring u.string
#define iwin u.win
#define imark u.mark
#define iline u.line

/* debugging */
int tktdbg;
extern void tktprinttext(TkText*);
extern void tktprintindex(TkTindex*);
extern void tktprintitem(TkTitem*);
extern void tktprintline(TkTline*);
extern void tktcheck(TkText*, char*);
int tktutfpos(char*, int);

char*
tktnewitem(int kind, int tagextra,TkTitem **ret)
{
	int n;
	TkTitem *i;

	n = sizeof(TkTitem) + tagextra * sizeof(ulong);
	i = malloc(n);
	if(i == nil)
		return TkNomem;

	memset(i, 0, n);
	i->kind = kind;
	i->tagextra = tagextra;
	*ret = i;
	return nil;
}

char*
tktnewline(int flags, TkTitem *items, TkTline *prev, TkTline *next, TkTline **ret)
{
	TkTline *l;
	TkTitem *i;

	l = malloc(sizeof(TkTline));
	if(l == nil)
		return TkNomem;

	memset(l, 0, sizeof(TkTline));
	l->flags = flags;
	l->items = items;
	l->prev = prev;
	l->next = next;
	next->prev = l;
	prev->next = l;

	for(i = items; i->next != nil;)
		i = i->next;
	if(tktdbg && !(i->kind == TkTnewline || i->kind == TkTcontline))
		print("text:tktnewline botch\n");
	i->iline = l;

	*ret = l;
	return nil;
}

/*
 * free items; freewins is 0 when the subwindows will be
 * freed anyway as the main text widget is being destroyed.
 */
void
tktfreeitems(TkText *tkt, TkTitem *i, int freewins)
{
	TkTitem *n;
	Tk *tk;

	while(i != nil) {
		n = i->next;
		if(tkt->mouse == i)
			tkt->mouse = nil;
		switch(i->kind) {
		case TkTascii:
		case TkTrune:
			if(i->istring != nil)
				free(i->istring);
			break;
		case TkTwin:
			if (i->iwin != nil) {
				tk = i->iwin->sub;
				if (tk != nil) {
					tk->geom = nil;
					tk->destroyed = nil;
					if (i->iwin->owned && freewins) {
						if (tk->name != nil)
							tkdestroy(tk->env->top, tk->name->name, nil);
					} else {
						tk->parent = nil;
						tk->geom = nil;
						tk->destroyed = nil;
					}
				}
				if(i->iwin->create != nil)
					free(i->iwin->create);
				free(i->iwin);
			}
			break;
		case TkTmark:
			break;
		}
		free(i);
		i = n;
	}
}

void
tktfreelines(TkText *tkt, TkTline *l, int freewins)
{
	TkTline *n;

	while(l != nil) {
		n = l->next;
		tktfreeitems(tkt, l->items, freewins);
		free(l);
		l = n;
	}
}

void
tktfreetabs(TkTtabstop *t)
{
	TkTtabstop *n;

	while(t != nil) {
		n = t->next;
		free(t);
		t = n;
	}
}

void
tkfreetext(Tk *tk)
{
	TkText *tkt = TKobj(TkText, tk);

	if(tkt->start.next != nil && tkt->start.next != &(tkt->end)) {
		tkt->end.prev->next = nil;
		tktfreelines(tkt, tkt->start.next, 0);
	}
	tktfreeitems(tkt, tkt->start.items, 0);
	tktfreeitems(tkt, tkt->end.items, 0);
	tktfreetabs(tkt->tabs);
	if(tkt->tagshare == nil)
		tktfreetags(tkt->tags);
	else
		tk->binds = nil;
	tktfreemarks(tkt->marks);
	if(tkt->xscroll != nil)
		free(tkt->xscroll);
	if(tkt->yscroll != nil)
		free(tkt->yscroll);
	/* don't free image because it belongs to window */
}

/*
 * Remove the item at ix, joining previous and next items.
 * If item is at end of line, remove next line and join
 * its items to this one (except at end).
 * On return, ix is adjusted to point to the next item.
 */
void
tktremitem(TkText *tkt, TkTindex *ix)
{
	TkTline *l, *lnext;
	TkTindex prev, nx;
	TkTitem *i, *ilast;

	l = ix->line;
	i = ix->item;

	if(i->next == nil) {
		if(tktdbg && !(i->kind == TkTnewline || i->kind == TkTcontline)) {
			print("tktremitem: botch 1\n");
			return;
		}
		lnext = l->next;
		if(lnext == &tkt->end)
			/* not supposed to remove final newline */
			return;
		if(i->kind == TkTnewline)
			tkt->nlines--;
		ilast = tktlastitem(lnext->items);
		ilast->iline = l;
		i->next = lnext->items;
		l->flags = (l->flags & ~TkTlast) | (lnext->flags & TkTlast);
		l->next = lnext->next;
		lnext->next->prev = l;
		free(lnext);
	}
	if(l->items == i)
		l->items = i->next;
	else {
		prev = *ix;
		if(!tktadjustind(tkt, TkTbyitemback, &prev) && tktdbg) {
			print("tktremitem: botch 2\n");
			return;
		}
		prev.item->next = i->next;
	}
	ix->item = i->next;
	ix->pos = 0;
	i->next = nil;
	nx = *ix;
	tktadjustind(tkt, TkTbycharstart, &nx);

	/* check against cached items */
	if(tkt->selfirst == i)
		tkt->selfirst = nx.item;
	if(tkt->sellast == i)
		tkt->sellast = nx.item;
	if(tkt->selfirst == tkt->sellast) {
		tkt->selfirst = nil;
		tkt->sellast = nil;
	}

	tktfreeitems(tkt, i, 1);
}

int
tktdispwidth(Tk *tk, TkTtabstop *tb, TkTitem *i, Font *f, int x, int pos, int nchars)
{
	int w, del, locked;
	TkTtabstop *tbprev;
	Display *d;
	TkText *tkt;
	TkEnv env;

	tkt = TKobj(TkText, tk);
	d = tk->env->top->display;
	if (tb == nil)
		tb = tkt->tabs;

	switch(i->kind) {
	case TkTrune:
		pos = tktutfpos(i->istring, pos);
		/* FALLTHRU */
	case TkTascii:
		if(f == nil) {
			if(!tktanytags(i))
				f = tk->env->font;
			else {
				tkttagopts(tk, i, nil, &env, nil, 1);
				f = env.font;
			}
		}
		locked = 0;
		if(!(tkt->tflag&TkTdlocked))
			locked = lockdisplay(d);
		if(nchars >= 0)
			w = stringnwidth(f, i->istring+pos, nchars);
		else
			w = stringwidth(f, i->istring+pos);
		if(locked)
			unlockdisplay(d);
		break;
	case TkTtab:
		if(tb == nil)
			w = 0;
		else {
			tbprev = nil;
			while(tb->pos <= x && tb->next != nil) {
					tbprev = tb;
					tb = tb->next;
				}
			w = tb->pos - x;
			if(w <= 0) {
				del = tb->pos;
				if(tbprev != nil)
					del -= tbprev->pos;
				while(w <= 0)
					w += del;
			}
			/* todo: other kinds of justification */
		}
		break;
	case TkTwin:
		if(i->iwin->sub == 0)
			w = 0;
		else
			w = i->iwin->sub->act.width + 2*i->iwin->padx + 2*i->iwin->sub->borderwidth;
		break;
	default:
		w = 0;
	}
	return w;
}

int
tktindrune(TkTindex *ix)
{
	int ans;
	Rune r;

	switch(ix->item->kind) {
		case TkTascii:
			ans = ix->item->istring[ix->pos];
			break;
		case TkTrune:
			chartorune(&r, ix->item->istring + tktutfpos(ix->item->istring, ix->pos));
			ans = r;
			break;
		case TkTtab:
			ans = '\t';
			break;
		case TkTnewline:
			ans = '\n';
			break;
		default:
			/* only care that it isn't a word char */
			ans = 0x80;
	}
	return ans;
}

TkTitem*
tktlastitem(TkTitem *i)
{
	while(i->next != nil)
		i = i->next;
	if(tktdbg && !(i->kind == TkTnewline || i->kind == TkTcontline))
		print("text:tktlastitem botch\n");

	return i;
}

TkTline*
tktitemline(TkTitem *i)
{
	i = tktlastitem(i);
	return i->iline;
}

int
tktlinenum(TkText *tkt, TkTindex *p)
{
	int n;
	TkTline *l;

	if(p->line->orig.y <= tkt->end.orig.y / 2) {
		/* line seems closer to beginning */
		n = 1;
		for(l = tkt->start.next; l != p->line; l = l->next) {
			if(tktdbg && l->next == nil) {
				print("text: tktlinenum botch\n");
				break;
			}
			if(l->flags & TkTlast)
				n++;
		}
	}
	else {
		n = tkt->nlines;
		for(l = tkt->end.prev; l != p->line; l = l->prev) {
			if(tktdbg && l->prev == nil) {
				print("text: tktlinenum botch\n");
				break;
			}
			if(l->flags & TkTfirst)
				n--;
		}
	}
	return n;
}

int
tktlinepos(TkText *tkt, TkTindex *p)
{
	int n;
	TkTindex ix;
	TkTitem *i;

	n = 0;
	ix = *p;
	i = ix.item;
	tktadjustind(tkt, TkTbylinestart, &ix);
	while(ix.item != i) {
		if(tktdbg && ix.item->next == nil && (ix.line->flags&TkTlast)) {
			print("text: tktlinepos botch\n");
			break;
		}
		n += tktposcount(ix.item);
		if(!tktadjustind(tkt, TkTbyitem, &ix)) {
			if(tktdbg)
				print("tktlinepos botch\n");
			break;
		}
	}
	return (n+p->pos);
}

int
tktposcount(TkTitem *i)
{
	int n;

	if(i->kind == TkTascii)
		n = strlen(i->istring);
	else
	if(i->kind == TkTrune)
		n = utflen(i->istring);
	else
	if(i->kind == TkTmark || i->kind == TkTcontline)
		n = 0;
	else
		n = 1;
	return n;
}

/*
 * Insert item i before position ins.
 * If i is a newline or a contline, make a new line to contain the items up to
 * and including the new newline, and make the original line
 * contain the items from ins on.
 * Adjust ins so that it points just after inserted item.
 */
char*
tktiteminsert(TkText *tkt, TkTindex *ins, TkTitem *i)
{
	int hasprev, flags;
	char *e;
	TkTindex prev;
	TkTline *l;
	TkTitem *items;

	prev = *ins;
	hasprev = tktadjustind(tkt, TkTbyitemback, &prev);

	if(i->kind == TkTnewline || i->kind == TkTcontline) {
		i->next = nil;
		if(hasprev && prev.line == ins->line) {
			items = ins->line->items;
			prev.item->next = i;
		}
		else
			items = i;

		flags = ins->line->flags&TkTfirst;
		if(i->kind == TkTnewline)
			flags |= TkTlast;
		e = tktnewline(flags, items, ins->line->prev, ins->line, &l);
		if(e != nil) {
			if(hasprev && prev.line == ins->line)
				prev.item->next = ins->item;
			return e;
		}

		if(i->kind == TkTnewline)
			ins->line->flags |= TkTfirst;

		if(i->kind == TkTcontline)
			ins->line->flags &= ~TkTfirst;
		ins->line->items = ins->item;
		ins->pos = 0;
	}
	else {
		if(hasprev && prev.line == ins->line)
			prev.item->next = i;
		else
			ins->line->items = i;
		i->next = ins->item;
	}

	return nil;
}

/*
 * If index p doesn't point at the beginning of an item,
 * split the item at p.  Adjust p to point to the beginning of
 * the item after the split (same character it used to point at).
 * If there is a split, the old item gets the characters before
 * the split, and a new item gets the characters after it.
 */
char*
tktsplititem(TkTindex *p)
{
	int l1, l2;
	char *s1, *s2, *e;
	TkTitem *i, *i2;

	i = p->item;

	if(p->pos != 0) {
		/*
		 * Must be TkTascii or TkTrune
		 *
		 * Make new item i2, to be inserted after i,
		 * with portion of string from p->pos on
		 */

		if (i->kind == TkTascii)
			l1 = p->pos;
		else
			l1 = tktutfpos(i->istring, p->pos);
		l2 = strlen(i->istring) - l1;
		if (l2 == 0)
			print("tktsplititem botch\n");
		s1 = malloc(l1+1);
		if(s1 == nil)
			return TkNomem;
		s2 = malloc(l2+1);
		if(s2 == nil) {
			free(s1);
			return TkNomem;
		}

		memmove(s1, i->istring, l1);
		s1[l1] = '\0';
		memmove(s2, i->istring + l1, l2);
		s2[l2] = '\0';

		e = tktnewitem(i->kind, i->tagextra, &i2);
		if(e != nil) {
			free(s1);
			free(s2);
			return e;
		}

		free(i->istring);

		tkttagcomb(i2, i, 1);
		i2->next = i->next;
		i->next = i2;
		i->istring = s1;
		i2->istring = s2;

		p->item = i2;
		p->pos = 0;
	}

	return nil;
}

int
tktmaxwid(TkTline *l)
{
	int w, maxw;

	maxw = 0;
	while(l != nil) {
		w = l->width;
		if(w > maxw)
			maxw = w;
		l = l->next;
	}
	return maxw;
}

Rectangle
tktbbox(Tk *tk, TkTindex *ix)
{
	Rectangle r;
	int d, w;
	TkTitem *i;
	TkTline *l;
	TkEnv env;
	TkTtabstop *tb = nil;
	Tk *sub;
	TkText *tkt = TKobj(TkText, tk);
 	int opts[TkTnumopts];

	l = ix->line;

	/* r in V space */
	r.min = subpt(l->orig, tkt->deltatv);
	r.min.y += l->ascent;
	r.max = r.min;

	/* tabs dependon tags of first non-mark on display line */
	for(i = l->items; i->kind == TkTmark; )
		i = i->next;
	tkttagopts(tk, i, opts, &env, &tb, 1);

	for(i = l->items; i != nil; i = i->next) {
		if(i == ix->item) {
			tkttagopts(tk, i, opts, &env, nil, 1);
			r.min.y -= opts[TkToffset];
			switch(i->kind) {
			case TkTascii:
			case TkTrune:
				d = tktdispwidth(tk, tb, i, nil, r.min.x, 0, ix->pos);
				w = tktdispwidth(tk, tb, i, nil, r.min.x, ix->pos, 1);
				r.min.x += d;
				r.min.y -= env.font->ascent;
				r.max.x = r.min.x + w;
				r.max.y = r.min.y + env.font->height;
				break;
			case TkTwin:
				sub = i->iwin->sub;
				if(sub == nil)
					break;
				r.min.x += sub->act.x;
				r.min.y += sub->act.y;
				r.max.x = r.min.x + sub->act.width + 2*sub->borderwidth;
				r.max.y = r.min.y + sub->act.height + 2*sub->borderwidth;
				break;
			case TkTnewline:
				r.max.x = r.min.x;
				r.min.y -= l->ascent;
				r.max.y = r.min.y + l->height;
				break;
			default:
				d = tktdispwidth(tk, tb, i, nil, r.min.x, 0, -1);
				r.max.x = r.min.x + d;
				r.max.y = r.min.y;
				break;
			}
			return r;
		}
		r.min.x += tktdispwidth(tk, tb, i, nil, r.min.x, 0, -1);
	}
	r.min.x = 0;
	r.min.y = 0;
	r.max.x = 0;
	r.max.y = 0;
	return r;
}

/* Return left-at-baseline position of given item, in V coords */
static Point
tktitempos(Tk *tk, TkTindex *ix)
{
	Point p;
	TkTitem *i;
	TkTline *l;
	TkText *tkt = TKobj(TkText, tk);
 
	l = ix->line;

	/* p in V space */
	p = subpt(l->orig, tkt->deltatv);
	p.y += l->ascent;

	for(i = l->items; i != nil && i != ix->item; i = i->next)
		p.x += i->width;
	return p;
}

static Tk*
tktdeliver(Tk *tk, TkTitem *i, TkTitem *tagit, int event, void *data, Point deltasv)
{
	Tk *ftk, *dest;
	TkTwind *w;
	TkText *tkt;
	TkTtaginfo *t;
	TkTline *l;
	TkMouse m;
	Point mp, p;
	TkTindex ix;
	int bd;

	dest = nil;
	if(i != nil) {
		tkt = TKobj(TkText, tk);

		if(i->kind == TkTwin) {
			w = i->iwin;
			if(w->sub != nil) {
				if(!(event & TkKey) && (event & TkEmouse)) {
					m = *(TkMouse*)data;
					mp.x = m.x;
					mp.y = m.y;
					ix.item = i;
					ix.pos = 0;
					ix.line = tktitemline(i);
					p = tktitempos(tk, &ix);
					bd = w->sub->borderwidth;
					mp.x = m.x - (deltasv.x + p.x + w->sub->act.x + bd);
					mp.y = m.y - (deltasv.y + p.y + w->sub->act.y + bd);
					ftk = tkinwindow(w->sub, mp, 0);
					if(ftk != w->focus) {
						tkdeliver(w->focus, TkLeave, data);
						tkdeliver(ftk, TkEnter, data);

						w->focus = ftk;
					}
					if(ftk != nil)
						dest = tkdeliver(ftk, event, &m);
				}
				else {
					if ((event & TkLeave) && (w->focus != w->sub)) {
						tkdeliver(w->focus, TkLeave, data);
						w->focus = nil;
						event &= ~TkLeave;
					}
					if (event)
						tkdeliver(w->sub, event, data);
				}
				if(Dx(w->sub->dirty) > 0) {
					l = tktitemline(i);
					tktfixgeom(tk, tktprevwrapline(tk, l), l, 0);
				}
				if(event & TkKey)
					return dest;
			}
		}

		if(tagit != 0) {
			for(t = tkt->tags; t != nil; t = t->next) {
				if(t->binds != nil && tkttagset(tagit, t->id)) {
					if(tksubdeliver(tk, t->binds, event, data, 0) == TkDbreak) {
						return dest;
					}
				}
			}
		}
	}
	return dest;
}

Tk*
tktinwindow(Tk *tk, Point *p)
{
	TkTindex ix;
	Point q;
	Tk *sub;

	tktxyind(tk, p->x, p->y, &ix);
	if (ix.item == nil || ix.item->kind != TkTwin || ix.item->iwin->sub == nil)
		return tk;
	sub = ix.item->iwin->sub;
	q = tktitempos(tk, &ix);
	p->x -= q.x + sub->borderwidth + sub->act.x;
	p->y -= q.y + sub->borderwidth + sub->act.y;
	return sub;
}

Tk*
tktextevent(Tk *tk, int event, void *data)
{
	char *e;
	TkMouse m, vm;
	TkTitem *f, *tagit;
	TkText *tkt;
	TkTindex ix;
	Tk *dest;
	Point deltasv;

	tkt = TKobj(TkText, tk);
	deltasv = tkposn(tk);
	deltasv.x += tk->borderwidth + tk->ipad.x/2;
	deltasv.y += tk->borderwidth + tk->ipad.y/2;

	dest = nil;
	if(event == TkLeave && tkt->mouse != nil) {
		vm.x = 0;
		vm.y = 0;
		tktdeliver(tk, tkt->mouse, tkt->mouse, TkLeave, data, deltasv);
		tkt->mouse = nil;
	}
	else if((event & TkKey) == 0 && (event & TkEmouse)) {
		/* m in S space, tm in V space */
		m = *(TkMouse*)data;
		vm = m;
		vm.x -= deltasv.x;
		vm.y -= deltasv.y;
		if((event & TkMotion) == 0 || m.b == 0) {
			tkt->current.x = vm.x;
			tkt->current.y = vm.y;
		}
		tktxyind(tk, vm.x, vm.y, &ix);
		f = ix.item;
		if(tkt->mouse != f) {
			tagit = nil;
			if(tkt->mouse != nil) {
				if(tktanytags(tkt->mouse)) {
					e = tktnewitem(TkTascii, tkt->mouse->tagextra, &tagit);
					if(e != nil)
						return dest;	/* XXX propagate error? */
					tkttagcomb(tagit, tkt->mouse, 1);
					tkttagcomb(tagit, f, -1);
				}
				tktdeliver(tk, tkt->mouse, tagit, TkLeave, data, deltasv);
				if(tagit)
					free(tagit);
				tagit = nil;
			}
			if(tktanytags(f)) {
				e = tktnewitem(TkTascii, f->tagextra, &tagit);
				if(e != nil)
					return dest;		/* XXX propagate error? */
				tkttagcomb(tagit, f, 1);
				if(tkt->mouse)
					tkttagcomb(tagit, tkt->mouse, -1);
			}
			tktdeliver(tk, f, tagit, TkEnter, data, deltasv);
			tkt->mouse = f;
			if(tagit)
				free(tagit);
		}
		if(tkt->mouse != nil)
			dest = tktdeliver(tk, tkt->mouse, tkt->mouse, event, &m, deltasv);
	}
	else if(event == TkFocusin) 
		tktextcursor(tk, " insert", (char **) nil);
	/* pass all "real" events on to parent text widget - DBK */
	tksubdeliver(tk, tk->binds, event, data, 0);
	return dest;
}

/* Debugging */
void
tktprintitem(TkTitem *i)
{
	int j;

	print("%p:", i);
	switch(i->kind){
	case TkTascii:
		print("\"%s\"", i->istring);
		break;
	case TkTrune:
		print("<rune:%s>", i->istring);
		break;
	case TkTnewline:
		print("<nl:%p>", i->iline);
		break;
	case TkTcontline:
		print("<cont:%p>", i->iline);
		break;
	case TkTtab:
		print("<tab>");
		break;
	case TkTmark:
		print("<mk:%s>", i->imark->name);
		break;
	case TkTwin:
	        if (i->iwin->sub->name != nil)
		  print("<win:%s>", i->iwin->sub? i->iwin->sub->name->name : "<null>");
	}
	print("[%d]", i->width);
	if(i->tags !=0 || i->tagextra !=0) {
		print("{%lux", i->tags[0]);
		for(j=0; j < i->tagextra; j++)
			print(" %lux", i->tags[j+1]);
		print("}");
	}
	print(" ");
}

void
tktprintline(TkTline *l)
{
	TkTitem *i;

	print("line %p: orig=(%d,%d), w=%d, h=%d, a=%d, f=%x\n\t",
		l, l->orig.x, l->orig.y, l->width, l->height, l->ascent, l->flags);
	for(i = l->items; i != nil; i = i->next)
		tktprintitem(i);
	print("\n");
}

void
tktprintindex(TkTindex *ix)
{
	print("line=%p,item=%p,pos=%d\n", ix->line, ix->item, ix->pos);
}

void
tktprinttext(TkText *tkt)
{
	TkTline *l;
	TkTtaginfo *ti;
	TkTmarkinfo *mi;

	for(ti=tkt->tags; ti != nil; ti=ti->next)
		print("%s{%d} ", ti->name, ti->id);
	print("\n");
	for(mi = tkt->marks; mi != nil; mi=mi->next)
		print("%s{%p} ", mi->name? mi->name : "nil", mi->cur);
	print("\n");
	print("selfirst=%p sellast=%p\n", tkt->selfirst, tkt->sellast);

	for(l = &tkt->start; l != nil; l = l->next)
		tktprintline(l);
}

/*
 * Check that assumed invariants are true.
 *
 * - start line and end line have no items
 * - all other lines have at least one item
 * - start line leads to end line via next pointers
 * - prev pointers point to previous lines
 * - each line ends in either a TkTnewline or a TkTcontline
 *    whose iline pointer points to the line itself
 * - TkTcontline and TkTmark items have no tags
 *    (this is so they don't get realloc'd because of tag combination)
 * - all cur fields of marks point to nil or a TkTmark
 * - selfirst and sellast correctly define select region
 * - nlines counts the number of lines
 */
void
tktcheck(TkText *tkt, char *fun)
{
	int nl, insel, selfound;
	TkTline *l;
	TkTitem *i;
	TkTmarkinfo *mi;
	TkTindex ix;
	char *prob;

	prob = nil;
	nl = 0;

	if(tkt->start.items != nil || tkt->end.items != nil)
		prob = "start/end has items";
	for(l = tkt->start.next; l != &tkt->end; l = l->next) {
		if(l->prev->next != l) {
			prob = "prev mismatch";
			break;
		}
		if(l->next->prev != l) {
			prob = "next mismatch";
			break;
		}
		i = l->items;
		if(i == nil) {
			prob = "empty line";
			break;
		}
		while(i->next != nil) {
			if(i->kind == TkTnewline || i->kind == TkTcontline) {
				prob = "premature end of line";
				break;
			}
			if(i->kind == TkTmark && (i->tags[0] != 0 || i->tagextra != 0)) {
				prob = "mark has tags";
				break;
			}
			i = i->next;
		}
		if(i->kind == TkTnewline)
			nl++;
		if(!(i->kind == TkTnewline || i->kind == TkTcontline)) {
			prob = "bad end of line";
			break;
		}
		if(i->kind == TkTcontline && (i->tags[0] != 0 || i->tagextra != 0)) {
			prob = "contline has tags";
			break;
		}
		if(i->iline != l) {
			prob = "bad end-of-line pointer";
			break;
		}
	}
	for(mi = tkt->marks; mi != nil; mi=mi->next) {
		if(mi->cur != nil) {
			tktstartind(tkt, &ix);
			do {
				if(ix.item->kind == TkTmark && ix.item == mi->cur)
					goto foundmark;
			} while(tktadjustind(tkt, TkTbyitem, &ix));
			prob = "bad mark cur";
			break;
		    foundmark: ;
		}
	}
	insel = 0;
	selfound = 0;
	tktstartind(tkt, &ix);
	do {
		i = ix.item;
		if(i == tkt->selfirst) {
			if(i->kind == TkTmark || i->kind == TkTcontline) {
				prob = "selfirst not on character";
				break;
			}
			if(i == tkt->sellast) {
				prob = "selfirst==sellast, not nil";
				break;
			}
			insel = 1;
			selfound = 1;
		}
		if(i == tkt->sellast) {
			if(i->kind == TkTmark || i->kind == TkTcontline) {
				prob = "sellast not on character";
				break;
			}
			insel = 0;
		}
		if(i->kind != TkTmark && i->kind != TkTcontline) {
			if(i->tags[0] & (1<<TkTselid)) {
				if(!insel) {
					prob = "sel set outside selfirst..sellast";
					break;
				}
			}
			else {
				if(insel) {
					prob = "sel not set inside selfirst..sellast";
					break;
				}
			}
		}
	} while(tktadjustind(tkt, TkTbyitem, &ix));
	if(tkt->selfirst != nil && !selfound)
		prob = "selfirst not found";

	if(prob != nil) {
		print("tktcheck problem: %s: %s\n", fun, prob);
		tktprinttext(tkt);
abort();
	}
}

int
tktutfpos(char *s, int pos)
{
	char *s1;
	int c;
	Rune r;

	for (s1 = s; pos > 0; pos--) {
		c = *(uchar *)s1;
		if (c < Runeself) {
			if (c == '\0')
				break;
			s1++;
		}
		else
			s1 += chartorune(&r, s1);
	}
	return s1 - s;
}

/*
struct timerec {
	char *name;
	ulong ms;
};

static struct timerec tt[100];
static int ntt = 1;

int
tktalloctime(char *name)
{
	if(ntt >= 100)
		abort();
	tt[ntt].name = strdup(name);
	tt[ntt].ms = 0;
	return ntt++;
}

void
tktstarttime(int ind)
{
return;
	tt[ind].ms -= osmillisec();
}

void
tktendtime(int ind)
{
return;
	tt[ind].ms += osmillisec();
}

void
tktdumptime(void)
{
	int i;

	for(i = 1; i < ntt; i++)
		print("%s: %d\n", tt[i].name, tt[i].ms);
}
*/