xref: /inferno-os/libtk/textw.c (revision 7ef44d652ae9e5e1f5b3465d73684e4a54de73c0)
1 #include "lib9.h"
2 #include "draw.h"
3 #include "keyboard.h"
4 #include "tk.h"
5 #include "textw.h"
6 
7 /*
8  * useful text widget info to be found at:
9  * :/coordinate.systems		- what coord. systems are in use
10  * textu.c:/assumed.invariants	- some invariants that must be preserved
11  */
12 
13 #define istring u.string
14 #define iwin u.win
15 #define imark u.mark
16 #define iline u.line
17 
18 #define FLUSH() flushimage(tk->env->top->display, 1)
19 
20 #define	O(t, e)		((long)(&((t*)0)->e))
21 
22 /* Layout constants */
23 enum {
24 	Textpadx	= 2,
25 	Textpady	= 0,
26 };
27 
28 typedef struct Interval {
29 	int	lo;
30 	int	hi;
31 } Interval;
32 
33 typedef struct Mprint Mprint;
34 struct Mprint
35 {
36 	char*	buf;
37 	int	ptr;
38 	int	len;
39 };
40 
41 typedef struct TkDump TkDump;
42 struct TkDump
43 {
44 	int	sgml;
45 	int	metrics;
46 };
47 
48 static
49 TkOption dumpopts[] =
50 {
51 	"sgml",		OPTbool,	O(TkDump, sgml),	nil,
52 	"metrics",	OPTbool,	O(TkDump, metrics),	nil,
53 	nil
54 };
55 
56 static
57 TkStab tkcompare[] =
58 {
59 	"<",		TkLt,
60 	"<=",		TkLte,
61 	"==",		TkEq,
62 	">=",		TkGte,
63 	">",		TkGt,
64 	"!=",		TkNeq,
65 	nil
66 };
67 
68 static
69 TkOption textopts[] =
70 {
71 	"wrap",			OPTstab, O(TkText, opts[TkTwrap]),	tkwrap,
72 	"spacing1",		OPTnndist, O(TkText, opts[TkTspacing1]),	(void *)O(Tk, env),
73 	"spacing2",		OPTnndist, O(TkText, opts[TkTspacing2]),	(void *)O(Tk, env),
74 	"spacing3",		OPTnndist, O(TkText, opts[TkTspacing3]),	(void *)O(Tk, env),
75 	"tabs",			OPTtabs, O(TkText, tabs), 		(void *)O(Tk, env),
76 	"xscrollcommand",	OPTtext, O(TkText, xscroll),		nil,
77 	"yscrollcommand",	OPTtext, O(TkText, yscroll),		nil,
78 	"insertwidth",		OPTnndist, O(TkText, inswidth),		nil,
79 	"tagshare",		OPTwinp, O(TkText, tagshare),		nil,
80 	"propagate",		OPTstab, O(TkText, propagate),	tkbool,
81 	"selectborderwidth",	OPTnndist, O(TkText, sborderwidth), nil,
82 	nil
83 };
84 
85 #define CNTL(c) ((c)&0x1f)
86 #define DEL 0x7f
87 
88 static TkEbind tktbinds[] = {
89 	{TkButton1P,		"%W tkTextButton1 %X %Y"},
90 	{TkButton1P|TkMotion,	"%W tkTextSelectTo %X %Y"},
91 	{TkButton1P|TkDouble,	"%W tkTextSelectTo %X %Y double"},
92 	{TkButton1R,		"%W tkTextButton1R"},
93 	{TkButton2P,		"%W scan mark %x %y"},
94 	{TkButton2P|TkMotion,	"%W scan dragto %x %y"},
95 	{TkKey,			"%W tkTextInsert {%A}"},
96 	{TkKey|CNTL('a'),	"%W tkTextSetCursor {insert linestart}"},
97 	{TkKey|Home,		"%W tkTextSetCursor {insert linestart}"},
98 	{TkKey|CNTL('<'),	"%W tkTextSetCursor {insert linestart}"},
99 	{TkKey|CNTL('b'),	"%W tkTextSetCursor insert-1c"},
100 	{TkKey|Left,		"%W tkTextSetCursor insert-1c"},
101 	{TkKey|CNTL('d'),	"%W tkTextDelIns"},
102 	{TkKey|CNTL('e'),	"%W tkTextSetCursor {insert lineend}"},
103 	{TkKey|End,		"%W tkTextSetCursor {insert lineend}"},
104 	{TkKey|CNTL('>'),	"%W tkTextSetCursor {insert lineend}"},
105 	{TkKey|CNTL('f'),	"%W tkTextSetCursor insert+1c"},
106 	{TkKey|Right,		"%W tkTextSetCursor insert+1c"},
107 	{TkKey|CNTL('h'),	"%W tkTextDelIns -c"},
108 	{TkKey|DEL,		"%W tkTextDelIns"},
109 	{TkKey|CNTL('k'),	"%W tkTextDelIns +l"},
110 	{TkKey|CNTL('n'),	"%W tkTextSetCursor {insert+1l}"},
111 	{TkKey|Down,		"%W tkTextSetCursor {insert+1l}"},
112 	{TkKey|CNTL('o'),       "%W tkTextInsert {\n}; %W mark set insert insert-1c"},
113 	{TkKey|CNTL('p'),	"%W tkTextSetCursor {insert-1l}"},
114 	{TkKey|Up,		"%W tkTextSetCursor {insert-1l}"},
115 	{TkKey|CNTL('u'),	"%W tkTextDelIns -l"},
116 	{TkKey|CNTL('v'),	"%W yview scroll 0.75 page"},
117 	{TkKey|Pgdown,	"%W yview scroll 0.75 page"},
118 	{TkKey|CNTL('w'),	"%W tkTextDelIns -w"},
119 	{TkKey|Pgup,	"%W yview scroll -0.75 page"},
120 	{TkButton4P,	"%W yview scroll -0.2 page"},
121 	{TkButton5P,	"%W yview scroll 0.2 page"},
122 	{TkFocusout,            "%W tkTextCursor delete"},
123 	{TkKey|APP|'\t',	""},
124 	{TkKey|BackTab,		""},
125 };
126 
127 static int	tktclickmatch(TkText *, int, int, int, TkTindex *);
128 static void	tktdoubleclick(TkText *, TkTindex *, TkTindex *);
129 static char* 	tktdrawline(Image*, Tk*, TkTline*, Point);
130 static void	tktextcursordraw(Tk *, int);
131 static char* 	tktsetscroll(Tk*, int);
132 static void	tktsetclip(Tk *);
133 static char* 	tktview(Tk*, char*, char**, int, int*, int, int);
134 static Interval tkttranslate(Tk*, Interval, int);
135 static void 	tktfixscroll(Tk*, Point);
136 static void 	tktnotdrawn(Tk*, int, int, int);
137 static void	tktdrawbg(Tk*, int, int, int);
138 static int	tktwidbetween(Tk*, int, TkTindex*, TkTindex*);
139 static int	tktpostspace(Tk*, TkTline*);
140 static int	tktprespace(Tk*, TkTline*);
141 static void	tktsee(Tk*, TkTindex*, int);
142 static Point	tktrelpos(Tk*);
143 static void	autoselect(Tk*, void*, int);
144 static void	blinkreset(Tk*);
145 
146 /* debugging */
147 extern int tktdbg;
148 extern void tktprinttext(TkText*);
149 extern void tktprintindex(TkTindex*);
150 extern void tktprintitem(TkTitem*);
151 extern void tktprintline(TkTline*);
152 extern void tktcheck(TkText*, char*);
153 extern int tktutfpos(char *, int);
154 
155 char*
156 tktext(TkTop *t, char* arg, char **ret)
157 {
158 	Tk *tk;
159 	char *e;
160 	TkEnv *ev;
161 	TkTline *l;
162 	TkTitem *it = nil;
163 	TkName *names = nil;
164 	TkTtaginfo *ti = nil;
165 	TkOptab tko[3];
166 	TkTmarkinfo *mi = nil;
167 	TkText *tkt, *tktshare;
168 
169 	tk = tknewobj(t, TKtext, sizeof(Tk)+sizeof(TkText));
170 	if(tk == nil)
171 		return TkNomem;
172 
173 	tkt = TKobj(TkText, tk);
174 
175 	tk->relief = TKsunken;
176 	tk->borderwidth = 1;
177 	tk->ipad.x = Textpadx * 2;
178 	tk->ipad.y = Textpady * 2;
179 	tk->flag |= Tktakefocus;
180 	tkt->sborderwidth = 0;
181 	tkt->inswidth = 2;
182 	tkt->cur_flag = 0;	/* text cursor doesn't show up initially */
183 	tkt->opts[TkTwrap] = Tkwrapchar;
184 	tkt->opts[TkTrelief] = TKflat;
185 	tkt->opts[TkTjustify] = Tkleft;
186 	tkt->propagate = BoolX;
187 
188 	tko[0].ptr = tk;
189 	tko[0].optab = tkgeneric;
190 	tko[1].ptr = tkt;
191 	tko[1].optab = textopts;
192 	tko[2].ptr = nil;
193 
194 	tk->req.width = tk->env->wzero*Textwidth;
195 	tk->req.height = tk->env->font->height*Textheight;
196 
197 	names = nil;
198 	e = tkparse(t, arg, tko, &names);
199 	if(e != nil)
200 		goto err;
201 	tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd));
202 	if(names == nil) {
203 		/* tkerr(t, arg); XXX */
204 		e = TkBadwp;
205 		goto err;
206 	}
207 
208 	if(tkt->tagshare != nil) {
209 		tkputenv(tk->env);
210 		tk->env = tkt->tagshare->env;
211 		tk->env->ref++;
212 	}
213 
214 	if(tk->flag&Tkdisabled)
215 		tkt->inswidth = 0;
216 
217 	if(tkt->tabs == nil) {
218 		tkt->tabs = malloc(sizeof(TkTtabstop));
219 		if(tkt->tabs == nil)
220 			goto err;
221 		tkt->tabs->pos = 8*tk->env->wzero;
222 		tkt->tabs->justify = Tkleft;
223 		tkt->tabs->next = nil;
224 	}
225 
226 	if(tkt->tagshare != nil) {
227 		tktshare = TKobj(TkText, tkt->tagshare);
228 		tkt->tags = tktshare->tags;
229 		tkt->nexttag = tktshare->nexttag;
230 	}
231 	else {
232 		/* Note: sel should have id == TkTselid == 0 */
233 		e = tktaddtaginfo(tk, "sel", &ti);
234 		if(e != nil)
235 			goto err;
236 
237 		tkputenv(ti->env);
238 		ti->env = tknewenv(t);
239 		if(ti->env == nil)
240 			goto err;
241 
242 		ev = ti->env;
243 		ev->colors[TkCbackgnd] = tk->env->colors[TkCselectbgnd];
244 		ev->colors[TkCbackgndlght] = tk->env->colors[TkCselectbgndlght];
245 		ev->colors[TkCbackgnddark] = tk->env->colors[TkCselectbgnddark];
246 		ev->colors[TkCforegnd] = tk->env->colors[TkCselectfgnd];
247 		ev->set = (1<<TkCbackgnd)|(1<<TkCbackgndlght)|
248 			  (1<<TkCbackgnddark)|(1<<TkCforegnd);
249 
250 		ti->opts[TkTborderwidth] = tkt->sborderwidth;
251 		if(tkt->sborderwidth > 0)
252 			ti->opts[TkTrelief] = TKraised;
253 	}
254 
255 	e = tktaddmarkinfo(tkt, "current", &mi);
256 	if(e != nil)
257 		goto err;
258 
259 	e = tktaddmarkinfo(tkt, "insert", &mi);
260 	if(e != nil)
261 		goto err;
262 
263 	tkt->start.flags = TkTfirst|TkTlast;
264 	tkt->end.flags = TkTlast;
265 
266 	e = tktnewitem(TkTnewline, 0, &it);
267 
268 	if(e != nil)
269 		goto err;
270 
271 	e = tktnewline(TkTfirst|TkTlast, it, &tkt->start, &tkt->end, &l);
272 	if(e != nil)
273 		goto err;
274 
275 	e = tktnewitem(TkTmark, 0, &it);
276 	if(e != nil)
277 		goto err;
278 
279 	it->next = l->items;
280 	l->items = it;
281 	it->imark = mi;
282 	mi->cur = it;
283 	tkt->nlines = 1;
284 	tkt->scrolltop[Tkvertical] = -1;
285 	tkt->scrolltop[Tkhorizontal] = -1;
286 	tkt->scrollbot[Tkvertical] = -1;
287 	tkt->scrollbot[Tkhorizontal] = -1;
288 
289 	if(tkt->tagshare != nil)
290 		tk->binds = tkt->tagshare->binds;
291 	else {
292 		e = tkbindings(t, tk, tktbinds, nelem(tktbinds));
293 
294 		if(e != nil)
295 			goto err;
296 	}
297 	if (tkt->propagate == BoolT) {
298 		if ((tk->flag & Tksetwidth) == 0)
299 			tk->req.width = tktmaxwid(tkt->start.next);
300 		if ((tk->flag & Tksetheight) == 0)
301 			tk->req.height = tkt->end.orig.y;
302 	}
303 
304 	e = tkaddchild(t, tk, &names);
305 	tkfreename(names);
306 	if(e != nil)
307 		goto err;
308 	tk->name->link = nil;
309 
310 	return tkvalue(ret, "%s", tk->name->name);
311 
312 err:
313 	/* XXX it's possible there's a memory leak here */
314 	tkfreeobj(tk);
315 	return e;
316 }
317 
318 /*
319  * There are four coordinate systems of interest:
320  *	S - screen coordinate system (i.e. top left corner of
321  *		inferno screen is (0,0) in S space.)
322  *	I - image coordinate system (i.e. top left corner of
323  *		tkimageof(this widget) is (0,0) in I space.)
324  *	T - text coordinate system (i.e., top left of first line
325  *		is at (0,0) in T space.)
326  *	V - view coordinate system (i.e., top left of visible
327  *		portion of widget is at (0,0) in V space.)
328  *
329  *	A point P in the four systems (Ps, Pi, Pt, Pv) satisfies:
330  *		Pt = Ps - deltast
331  *		Pv = Ps - deltasv
332  *		Pv = Pi - deltaiv
333  *	(where deltast is vector from S origin to T origin;
334  *	     deltasv is vector from S origin to V origin;
335  *	     deltaiv is vector from I origin to V origin)
336  *
337  *	We keep deltatv, deltasv, and deltaiv in tkt.
338  *	Deltatv is updated by scrolling.
339  *	Deltasv is updated by geom changes:
340  *		tkposn(tk)+ipad/2
341  *	Deltaiv is affected by geom changes and the call to the draw function:
342  *		tk->act+orig+ipad/2+(bw,bw) (orig is the parameter to tkdrawtext),
343  *
344  *	We can derive
345  *		Ps = Pt + deltast
346  *		   = Pt +  deltasv - deltatv
347  *
348  *		Pv = Pt - deltatv
349  *
350  * Here are various coordinates in the text widget according
351  * to which coordinate system they use:
352  *
353  *	S - Mouse coordinates (coming in to tktextevent);
354  *		the deltasv parameter to tkdrawtext;
355  *		coords in tkt->image, where drawing is done to
356  *		(to get same bit-alignment as screen, for fast transfer)
357  *	T - orig in TkTlines
358  *	V - %x,%y delivered via binds to TkText or its tags
359 
360  * Note deltasv changes underneath us, so is calculated on the fly
361  * when it needs to be (in tktextevent).
362  *
363  */
364 static void
365 tktsetdeltas(Tk *tk, Point orig)
366 {
367 	TkText *tkt = TKobj(TkText, tk);
368 
369 	tkt->deltaiv.x = orig.x + tk->act.x + tk->ipad.x/2 + tk->borderwidth;
370 	tkt->deltaiv.y = orig.y + tk->act.y + tk->ipad.y/2 + tk->borderwidth;
371 }
372 
373 static Point
374 tktrelpos(Tk *sub)
375 {
376 	Tk *tk;
377 	TkTindex ix;
378 	Rectangle r;
379 	Point ans;
380 
381 	tk = sub->parent;
382 	if(tk == nil)
383 		return ZP;
384 
385 	if(tktfindsubitem(sub, &ix)) {
386 		r = tktbbox(tk, &ix);
387 		ans.x = r.min.x;
388 		ans.y = r.min.y;
389 		return r.min;
390 	}
391 	return ZP;
392 }
393 
394 static void
395 tktreplclipr(Image *dst, Rectangle r)
396 {
397 	int locked;
398 
399 	locked = lockdisplay(dst->display);
400 	replclipr(dst, 0, r);
401 	if(locked)
402 		unlockdisplay(dst->display);
403 }
404 
405 char*
406 tkdrawtext(Tk *tk, Point orig)
407 {
408 	int vh;
409 	Image *dst;
410 	TkText *tkt;
411 	TkTline *l, *lend;
412 	Point p, deltait;
413 	Rectangle oclipr;
414 	int reldone = 1;
415 	char *e;
416 	tkt = TKobj(TkText, tk);
417 	dst = tkimageof(tk);
418 	if (dst == nil)
419 		return nil;
420 	tkt->image = dst;
421 	tktsetdeltas(tk, orig);
422 	tkt->tflag |= TkTdrawn|TkTdlocked;
423 	oclipr = dst->clipr;
424 	tktsetclip(tk);
425 
426 	if(tk->flag&Tkrefresh) {
427 		reldone = 0;
428 		tktnotdrawn(tk, 0, tkt->end.orig.y, 1);
429 	}
430 	tk->flag &= ~Tkrefresh;
431 
432 	deltait = subpt(tkt->deltaiv, tkt->deltatv);
433 	vh = tk->act.height - tk->ipad.y/2;
434 	lend = &tkt->end;
435 	for(l = tkt->start.next; l != lend; l = l->next) {
436 		if(l->orig.y+l->height < tkt->deltatv.y)
437 			continue;
438 		if(l->orig.y > tkt->deltatv.y + vh)
439 			break;
440 		if(!(l->flags&TkTdrawn)) {
441 			e = tktdrawline(dst, tk, l, deltait);
442 			if(e != nil)
443 				return e;
444 		}
445 	}
446 
447 	tktreplclipr(dst, oclipr);
448 	if(!reldone) {
449 		p.x = orig.x + tk->act.x;
450 		p.y = orig.y + tk->act.y;
451 		tkdrawrelief(dst, tk, p, TkCbackgnd, tk->relief);
452 	}
453 	tkt->tflag &= ~TkTdlocked;
454 
455 	return nil;
456 }
457 
458 /*
459  * Set the clipping rectangle of the destination image to the
460  * intersection of the current clipping rectangle and the area inside
461  * the text widget that needs to be redrawn.
462  * The caller should save the old one and restore it later.
463  */
464 static void
465 tktsetclip(Tk *tk)
466 {
467 	Rectangle r;
468 	Image *dst;
469 	TkText *tkt = TKobj(TkText, tk);
470 
471 	dst = tkt->image;
472 	r.min = tkt->deltaiv;
473 	r.max.x = r.min.x + tk->act.width - tk->ipad.x / 2;
474 	r.max.y = r.min.y + tk->act.height - tk->ipad.y / 2;
475 
476 	if(!rectclip(&r, dst->clipr))
477 		r.max = r.min;
478 	tktreplclipr(dst, r);
479 }
480 
481 static char*
482 tktdrawline(Image *i, Tk *tk, TkTline *l, Point deltait)
483 {
484 	Tk *sub;
485 	Font *f;
486 	Image *bg;
487 	Point p, q;
488 	Rectangle r;
489 	TkText *tkt;
490 	TkTitem *it, *z;
491 	int bevtop, bevbot;
492 	TkEnv *e, *et, *env;
493 	int *opts;
494 	int o, bd, ul, ov, h, w, la, lh, cursorx, join;
495 	char *err;
496 
497 	env = mallocz(sizeof(TkEnv), 0);
498 	if(env == nil)
499 		return TkNomem;
500 	opts = mallocz(TkTnumopts*sizeof(int), 0);
501 	if(opts == nil) {
502 		free(env);
503 		return TkNomem;
504 	}
505 	tkt = TKobj(TkText, tk);
506 	e = tk->env;
507 	et = env;
508 	et->top = e->top;
509 	f = e->font;
510 
511 	/* l->orig is in T space, p is in I space */
512 	la = l->ascent;
513 	lh = l->height;
514 	p = addpt(l->orig, deltait);
515 	p.y += la;
516 /* if(tktdbg){print("drawline, p=(%d,%d), f->a=%d, f->h=%d\n", p.x, p.y, f->ascent, f->height); tktprintline(l);} */
517 	cursorx = -1000;
518 	join = 0;
519 	for(it = l->items; it != nil; it = it->next) {
520 		bg = tkgc(e, TkCbackgnd);
521 		if(tktanytags(it)) {
522 			tkttagopts(tk, it, opts, env, nil, 1);
523 			if(e->colors[TkCbackgnd] != et->colors[TkCbackgnd]) {
524 				bg = tkgc(et, TkCbackgnd);
525 				r.min = p;
526 				r.min.y -= la;
527 				r.max.x = r.min.x + it->width;
528 				r.max.y = r.min.y + lh;
529 				draw(i, r, bg, nil, ZP);
530 			}
531 			o = opts[TkTrelief];
532 			bd = opts[TkTborderwidth];
533 			if((o == TKsunken || o == TKraised) && bd > 0) {
534 				/* fit relief inside item bounding box */
535 
536 				q.x = p.x;
537 				q.y = p.y - la;
538 				if(it->width < 2*bd)
539 					bd = it->width / 2;
540 				if(lh < 2*bd)
541 					bd = lh / 2;
542 				w = it->width - 2*bd;
543 				h = lh - 2*bd;
544 				if(o == TKraised) {
545 					bevtop = TkLightshade;
546 					bevbot = TkDarkshade;
547 				}
548 				else {
549 					bevtop = TkDarkshade;
550 					bevbot = TkLightshade;
551 				}
552 
553 				tkbevel(i, q, w, h, bd,
554 					tkgc(et, TkCbackgnd+bevtop), tkgc(et, TkCbackgnd+bevbot));
555 
556 				/* join relief between adjacent items if tags match */
557 				if(join) {
558 					r.min.x = q.x;
559 					r.max.x = q.x + bd;
560 					r.min.y = q.y + bd;
561 					r.max.y = r.min.y + h;
562 					draw(i, r, bg, nil, ZP);
563 					r.min.y = r.max.y;
564 					r.max.y = r.min.y + bd;
565 					draw(i, r, tkgc(et, TkCbackgnd+bevbot), nil, ZP);
566 				}
567 				for(z = it->next; z != nil && z->kind == TkTmark; )
568 					z = z->next;
569 				if(z != nil && tktsametags(z, it)) {
570 					r.min.x = q.x + bd + w;
571 					r.max.x = r.min.x + bd;
572 					r.min.y = q.y;
573 					r.max.y = q.y + bd;
574 					draw(i, r, tkgc(et, TkCbackgnd+bevtop), nil, ZP);
575 					r.min.y = r.max.y;
576 					r.max.y = r.min.y + h;
577 					draw(i, r, bg, nil, ZP);
578 					join = 1;
579 				}
580 				else
581 					join = 0;
582 			}
583 			o = opts[TkToffset];
584 			ul = opts[TkTunderline];
585 			ov = opts[TkToverstrike];
586 		}
587 		else {
588 			et->font = f;
589 			et->colors[TkCforegnd] = e->colors[TkCforegnd];
590 			o = 0;
591 			ul = 0;
592 			ov = 0;
593 		}
594 
595 		switch(it->kind) {
596 		case TkTascii:
597 		case TkTrune:
598 			q.x = p.x;
599 			q.y = p.y - env->font->ascent - o;
600 /*if(tktdbg)print("q=(%d,%d)\n", q.x, q.y);*/
601 			string(i, q, tkgc(et, TkCforegnd), q, env->font, it->istring);
602 			if(ov == BoolT) {
603 				r.min.x = q.x;
604 				r.max.x = r.min.x + it->width;
605 				r.min.y = q.y + 2*env->font->ascent/3;
606 				r.max.y = r.min.y + 2;
607 				draw(i, r, tkgc(et, TkCforegnd), nil, ZP);
608 			}
609 			if(ul == BoolT) {
610 				r.min.x = q.x;
611 				r.max.x = r.min.x + it->width;
612 				r.max.y = p.y - la + lh;
613 				r.min.y = r.max.y - 2;
614 				draw(i, r, tkgc(et, TkCforegnd), nil, ZP);
615 			}
616 			break;
617 		case TkTmark:
618 			if((it->imark != nil)
619                            && strcmp(it->imark->name, "insert") == 0) {
620 				cursorx = p.x - 1;
621 			}
622 			break;
623 		case TkTwin:
624 			sub = it->iwin->sub;
625 			if(sub != nil) {
626 				int dirty;
627 				sub->flag |= Tkrefresh;
628 				sub->dirty = tkrect(sub, 1);
629 				err = tkdrawslaves(sub, p, &dirty);
630 				if(err != nil) {
631 					free(opts);
632 					free(env);
633 					return err;
634 				}
635 			}
636 			break;
637 		}
638 		p.x += it->width;
639 	}
640 	l->flags |= TkTdrawn;
641 
642 	/* do cursor last, so not overwritten by later items */
643 	if(cursorx != -1000 && tkt->inswidth > 0) {
644 		r.min.x = cursorx;
645 		r.min.y = p.y - la;
646 		r.max.x = r.min.x + tkt->inswidth;
647 		r.max.y = r.min.y + lh;
648 		r = rectsubpt(r, deltait);
649 		if (!eqrect(tkt->cur_rec, r))
650 			blinkreset(tk);
651 		tkt->cur_rec = r;
652 		if(tkt->cur_flag)
653 			tktextcursordraw(tk, TkCforegnd);
654 	}
655 
656 	free(opts);
657 	free(env);
658 	return nil;
659 }
660 
661 static void
662 tktextcursordraw(Tk *tk, int color)
663 {
664 	Rectangle r;
665 	TkText *tkt;
666 	Image *i;
667 
668 	tkt = TKobj(TkText, tk);
669 
670 	r = rectaddpt(tkt->cur_rec, subpt(tkt->deltaiv, tkt->deltatv));
671 
672 	/* check the cursor with widget boundary */
673 	/* do nothing if entire cursor outside widget boundary */
674 	if( ! (	r.max.x < tkt->deltaiv.x ||
675 		r.min.x > tkt->deltaiv.x + tk->act.width ||
676 		r.max.y < tkt->deltaiv.y ||
677 		r.min.y > tkt->deltaiv.y + tk->act.height)) {
678 
679 		/* clip rectangle if extends beyond widget boundary */
680 		if (r.min.x < tkt->deltaiv.x)
681 			r.min.x = tkt->deltaiv.x;
682 		if (r.max.x > tkt->deltaiv.x + tk->act.width)
683 			r.max.x = tkt->deltaiv.x + tk->act.width;
684 		if (r.min.y < tkt->deltaiv.y)
685 			r.min.y = tkt->deltaiv.y;
686 		if (r.max.y > tkt->deltaiv.y + tk->act.height)
687 			r.max.y = tkt->deltaiv.y + tk->act.height;
688 		i = tkimageof(tk);
689 		if (i != nil)
690 			draw(i, r, tkgc(tk->env, color), nil, ZP);
691 	}
692 }
693 
694 static void
695 blinkreset(Tk *tk)
696 {
697 	TkText *tkt = TKobj(TkText, tk);
698 	if (!tkhaskeyfocus(tk) || tk->flag&Tkdisabled)
699 		return;
700 	tkt->cur_flag = 1;
701 	tkblinkreset(tk);
702 }
703 
704 static void
705 showcaret(Tk *tk, int on)
706 {
707 	TkText *tkt = TKobj(TkText, tk);
708 	TkTline *l, *lend;
709 	TkTitem *it;
710 
711 	tkt->cur_flag = on;
712 	lend = &tkt->end;
713 	for(l = tkt->start.next; l != lend; l = l->next) {
714 		for (it = l->items; it != nil; it = it->next) {
715 			if (it->kind == TkTmark && it->imark != nil &&
716 				    strcmp(it->imark->name, "insert") == 0) {
717 				if (on) {
718 					tktextcursordraw(tk, TkCforegnd);
719 					tk->dirty = tkrect(tk, 1);
720 				} else
721 					tktnotdrawn(tk, l->orig.y, l->orig.y+l->height, 0);
722 				tkdirty(tk);
723 				return;
724 			}
725 		}
726 	}
727 }
728 
729 char*
730 tktextcursor(Tk *tk, char* arg, char **ret)
731 {
732 	int on = 0;
733 	USED(ret);
734 
735 	if (tk->flag&Tkdisabled)
736 		return nil;
737 
738 	if(strcmp(arg, " insert") == 0) {
739 		tkblink(tk, showcaret);
740 		on = 1;
741 	}
742 	else
743 		tkblink(nil, nil);
744 
745 	showcaret(tk, on);
746 	return nil;
747 }
748 
749 /*
750  * Insert string s just before ins, but don't worry about geometry values.
751  * Don't worry about doing wrapping correctly, but break long strings
752  * into pieces to avoid bad behavior in the wrapping code of tktfixgeom.
753  * If tagit != 0, use its tags, else use the intersection of tags of
754  * non cont or mark elements just before and just after insertion point.
755  * (At beginning and end of widget, just use the tags of one adjacent item).
756  * Keep *ins up-to-date.
757  */
758 char*
759 tktinsert(Tk *tk, TkTindex *ins, char *s, TkTitem *tagit)
760 {
761 	int c, n, nextra, nmax, atend, atbeg;
762 	char *e, *p;
763 	Rune r;
764 	TkTindex iprev, inext;
765 	TkTitem *i, *utagit;
766 	TkText *tkt = TKobj(TkText, tk);
767 
768 	e = tktsplititem(ins);
769 	if(e != nil)
770 		return e;
771 
772 	/* if no tags give, use intersection of previous and next char tags */
773 
774 	nextra = 0;
775 	n = tk->env->wzero;
776 	if(n <= 0)
777 		n = 8;
778 	nmax = tk->act.width - tk->ipad.x;
779 	if(nmax <= 0) {
780 		if (tkt->propagate != BoolT || (tk->flag & Tksetwidth))
781 			nmax = tk->req.width;
782 		if(nmax <= 0)
783 			nmax = 60*n;
784 	}
785 	nmax = (nmax + n - 1) / n;
786 	utagit = nil;
787 	if(tagit == nil) {
788 		inext = *ins;
789 		tktadjustind(tkt, TkTbycharstart, &inext);
790 		atend = (inext.item->next == nil && inext.line->next == &tkt->end);
791 		if(atend || tktanytags(inext.item)) {
792 			iprev = *ins;
793 			tktadjustind(tkt, TkTbycharback, &iprev);
794 			atbeg = (iprev.line->prev == &tkt->start && iprev.line->items == iprev.item);
795 			if(atbeg || tktanytags(iprev.item)) {
796 				nextra = 0;
797 				if(!atend)
798 					nextra = inext.item->tagextra;
799 				if(!atbeg && iprev.item->tagextra > nextra)
800 					nextra = iprev.item->tagextra;
801 				e = tktnewitem(TkTascii, nextra, &utagit);
802 				if(e != nil)
803 					return e;
804 				if(!atend) {
805 					tkttagcomb(utagit, inext.item, 1);
806 					if(!atbeg)
807 						tkttagcomb(utagit, iprev.item, 0);
808 				}
809 				else if(!atbeg)
810 					tkttagcomb(utagit, iprev.item, 1);
811 				tagit = utagit;
812 			}
813 		}
814 	}
815 	else
816 		nextra = tagit->tagextra;
817 
818 	while((c = *s) != '\0') {
819 		e = tktnewitem(TkTascii, nextra, &i);
820 		if(e != nil) {
821 			if(utagit != nil)
822 				free(utagit);
823 			return e;
824 		}
825 
826 		if(tagit != nil)
827 			tkttagcomb(i, tagit, 1);
828 
829 		if(c == '\n') {
830 			i->kind = TkTnewline;
831 			tkt->nlines++;
832 			s++;
833 		}
834 		else
835 		if(c == '\t') {
836 			i->kind = TkTtab;
837 			s++;
838 		}
839 		else {
840 			p = s;
841 			n = 0;
842 			i->kind = TkTascii;
843 			while(c != '\0' && c != '\n' && c != '\t' && n < nmax){
844 				s += chartorune(&r, s);
845 				c = *s;
846 				n++;
847 			}
848 			/*
849 			 * if more bytes than runes, then it's not all ascii, so create a TkTrune item
850 			 */
851 			if(s - p > n)
852 				i->kind = TkTrune;
853 			n = s - p;
854 			i->istring = malloc(n+1);
855 			if(i->istring == nil) {
856 				tktfreeitems(tkt, i, 1);
857 				if(utagit != nil)
858 					free(utagit);
859 				return TkNomem;
860 			}
861 			memmove(i->istring, p, n);
862 			i->istring[n] = '\0';
863 		}
864 		e = tktiteminsert(tkt, ins, i);
865 		if(e != nil) {
866 			if(utagit != nil)
867 				free(utagit);
868 			tktfreeitems(tkt, i, 1);
869 			return e;
870 		}
871 	}
872 
873 	if(utagit != nil)
874 		free(utagit);
875 	return nil;
876 }
877 
878 void
879 tktextsize(Tk *tk, int dogeom)
880 {
881 	TkText *tkt;
882 	TkGeom g;
883 	tkt = TKobj(TkText, tk);
884 	if (tkt->propagate == BoolT) {
885 		g = tk->req;
886 		if ((tk->flag & Tksetwidth) == 0)
887 			tk->req.width = tktmaxwid(tkt->start.next);
888 		if ((tk->flag & Tksetheight) == 0)
889 			tk->req.height = tkt->end.orig.y;
890 		if (dogeom)
891 			tkgeomchg(tk, &g, tk->borderwidth);
892 	}
893 }
894 
895 static int
896 maximum(int a, int b)
897 {
898 	if (a > b)
899 		return a;
900 	return b;
901 }
902 
903 /*
904  * For lines l1->next, ..., l2, fix up the geometry
905  * elements of constituent TkTlines and TkTitems.
906  * This involves doing proper line wrapping, and calculating item
907  * widths and positions.
908  * Also, merge any adjacent TkTascii/TkTrune items with the same tags.
909  * Finally, bump the y component of lines l2->next, ... end.
910  * l2 should not be tkt->end.
911  *
912  * if finalwidth is 0, we're trying to work out what the
913  * width and height should be. if propagation is off,
914  * it's irrelevant; otherwise it must assume that
915  * its desired width will be fulfilled, as the packer
916  * doesn't iterate...
917  *
918  * N.B. this function rearranges lines, merges and splits items.
919  * this means that in general the item and line pointed to
920  * by any index might have been freed after tktfixgeom
921  * has been called.
922  */
923 char*
924 tktfixgeom(Tk *tk, TkTline *l1, TkTline *l2, int finalwidth)
925 {
926 	int x, y, a, wa, h, w, o, n, j, sp3, xleft, xright, winw, oa, oh, lh;
927 	int wrapmode, just, needsplit;
928 	char *e, *s;
929 	TkText *tkt;
930 	Tk *sub;
931 	TkTitem *i, *it, *ilast, *iprev;
932 	TkTindex ix, ixprev, ixw;
933 	TkTline *l, *lafter;
934 	Interval oldi, hole, rest, newrest;
935 	TkEnv *env;
936 	Font *f;
937 	int *opts;
938 	TkTtabstop *tb;
939 
940 	tkt = TKobj(TkText, tk);
941 
942 	if(tktdbg)
943 		tktcheck(tkt, "tktfixgeom");
944 
945 	if (!finalwidth && tkt->propagate == BoolT) {
946 		if ((tk->flag & Tksetwidth) == 0)
947 			winw = 1000000;
948 		else
949 			winw = tk->req.width;
950 	} else {
951 		winw = tk->act.width - tk->ipad.x;
952 		if(winw <= 0)
953 			winw = tk->req.width;
954 	}
955 	if(winw < 0)
956 		return nil;
957 
958 	/*
959 	 * Make lafter be the first line after l2 that comes after a newline
960 	 * (so that wrap correction cannot affect it)
961 	 */
962 	lafter = l2->next;
963 	if(tktdbg && lafter == nil) {
964 		print("tktfixgeom: botch 1\n");
965 		return nil;
966 	}
967 	while((lafter->flags & TkTfirst) == 0 && lafter != &tkt->end)
968 		lafter = lafter->next;
969 
970 
971 	y = l1->orig.y + l1->height + tktpostspace(tk, l1);
972 
973 	oldi.lo = y;
974 	oldi.hi = lafter->orig.y;
975 	rest.lo = oldi.hi;
976 	rest.hi = rest.lo + 1000; /* get background after end, too */
977 
978 	opts = mallocz(TkTnumopts*sizeof(int), 0);
979 	if(opts == nil)
980 		return TkNomem;
981 	env = mallocz(sizeof(TkEnv), 0);
982 	if(env == nil) {
983 		free(opts);
984 		return TkNomem;
985 	}
986 
987 	for(l = l1->next; l != lafter; l = l->next) {
988 		if(tktdbg && l == nil) {
989 			print("tktfixgeom: botch 2\n");
990 			free(opts);
991 			free(env);
992 			return nil;
993 		}
994 
995 		l->flags &= ~TkTdrawn;
996 
997 		/* some spacing depends on tags of first non-mark on display line */
998 		iprev = nil;
999 		for(i = l->items; i->kind == TkTmark; ) {
1000 			iprev = i;
1001 			i = i->next;
1002 		}
1003 		tkttagopts(tk, i, opts, env, &tb, 1);
1004 
1005 		if(l->flags&TkTfirst) {
1006 			xleft = opts[TkTlmargin1];
1007 			y += opts[TkTspacing1];
1008 		}
1009 		else {
1010 			xleft = opts[TkTlmargin2];
1011 			y += opts[TkTspacing2];
1012 		}
1013 		sp3 = opts[TkTspacing3];
1014 		just = opts[TkTjustify];
1015 
1016 		wrapmode = opts[TkTwrap];
1017 		f = env->font;
1018 		h = f->height;
1019 		lh = opts[TkTlineheight];
1020 		a = f->ascent;
1021 		x = xleft;
1022 		xright = winw - opts[TkTrmargin];
1023 		if(xright < xleft)
1024 			xright = xleft;
1025 
1026 		/*
1027 		 * perform line wrapping and calculate h (height) and a (ascent)
1028 		 * for the current line
1029 		 */
1030 		for(; i != nil; iprev = i, i = i->next) {
1031 		    again:
1032 			if(i->kind == TkTmark)
1033 				continue;
1034 			if(i->kind == TkTnewline)
1035 				break;
1036 			if(i->kind == TkTcontline) {
1037 				/*
1038 				 * See if some of following line fits on this one.
1039 				 * First, ensure that following line isn't empty.
1040 				 */
1041 				it = l->next->items;
1042 				while(it->kind == TkTmark)
1043 					it = it->next;
1044 
1045 				if(it->kind == TkTnewline || it->kind == TkTcontline) {
1046 					/* next line is empty; join it to this one by removing i */
1047 					ix.item = i;
1048 					ix.line = l;
1049 					ix.pos = 0;
1050 					tktremitem(tkt, &ix);
1051 					it = l->next->items;
1052 					if(iprev == nil)
1053 						i = l->items;
1054 					else
1055 						i = iprev->next;
1056 					goto again;
1057 				}
1058 
1059 				n = xright - x;
1060 				if(n <= 0)
1061 					break;
1062 				ixprev.line = l;
1063 				ixprev.item = i;
1064 				ixprev.pos = 0;
1065 				ix = ixprev;
1066 				tktadjustind(tkt, TkTbychar, &ix);
1067 				if(wrapmode == Tkwrapword)
1068 					tktadjustind(tkt, TkTbywrapend, &ix);
1069 				if(wrapmode != Tkwrapnone && tktwidbetween(tk, x, &ixprev, &ix) > n)
1070 					break;
1071 				/* move one item up from next line and try again */
1072 				it = l->next->items;
1073 				if(tktdbg && (it == nil || it->kind == TkTnewline || it->kind == TkTcontline)) {
1074 					print("tktfixgeom: botch 3\n");
1075 					free(opts);
1076 					free(env);
1077 					return nil;
1078 				}
1079 				if(iprev == nil)
1080 					l->items = it;
1081 				else
1082 					iprev->next = it;
1083 				l->next->items = it->next;
1084 				it->next = i;
1085 				i = it;
1086 				goto again;
1087 			}
1088 
1089 			oa = a;
1090 			oh = h;
1091 			if(!tktanytags(i)) {
1092 				env->font = tk->env->font;
1093 				o = 0;
1094 			}
1095 			else {
1096 				tkttagopts(tk, i, opts, env, nil, 1);
1097 				o = opts[TkToffset];
1098 			}
1099 			if((o != 0 || env->font != f) && i->kind != TkTwin) {
1100 				/* check ascent of current item */
1101 				n = o+env->font->ascent;
1102 				if(n > a) {
1103 					a = n;
1104 					h += (a - oa);
1105 				}
1106 				/* check descent of current item */
1107 				n = (env->font->height - env->font->ascent) - o;
1108 				if(n > h-a)
1109 					h = a + n;
1110 			}
1111 			if(i->kind == TkTwin && i->iwin->sub != nil) {
1112 				sub = i->iwin->sub;
1113 				n = 2 * i->iwin->pady + sub->act.height +
1114 					2 * sub->borderwidth;
1115 				switch(i->iwin->align) {
1116 				case Tktop:
1117 				case Tkbottom:
1118 					if(n > h)
1119 						h = n;
1120 					break;
1121 				case Tkcenter:
1122 					if(n/2 > a)
1123 						a = n/2;
1124 					if(n/2 > h-a)
1125 						h = a + n/2;
1126 					break;
1127 				case Tkbaseline:
1128 					wa = i->iwin->ascent;
1129 					if (wa == -1)
1130 						wa = n;
1131 					h = maximum(a, wa) + maximum(h - a, n - wa);
1132 					a = maximum(a, wa);
1133 					break;
1134 				}
1135 			}
1136 
1137 			w = tktdispwidth(tk, tb, i, env->font, x, 0, -1);
1138 			n = x + w - xright;
1139 			if(n > 0 && wrapmode != Tkwrapnone) {
1140 				/* find shortest suffix that can be removed to fit item */
1141 				j = tktposcount(i) - 1;
1142 				while(j > 0 && tktdispwidth(tk, tb, i, env->font, x, j, -1) < n)
1143 					j--;
1144 				/* put at least one item on a line before splitting */
1145 				if(j == 0 && x == xleft) {
1146 					if(tktposcount(i) == 1)
1147 						goto Nosplit;
1148 					j = 1;
1149 				}
1150 				ix.line = l;
1151 				ix.item = i;
1152 				ix.pos = j;
1153 				if(wrapmode == Tkwrapword) {
1154 					/* trim the item at the first word at or before the shortest suffix */
1155 					/* TO DO: convert any resulting trailing white space to zero width */
1156 					ixw = ix;
1157 					if(tktisbreak(tktindrune(&ixw))) {
1158 						/* at break character, find end of word preceding it */
1159 						while(tktisbreak(tktindrune(&ixw))){
1160 							if(!tktadjustind(tkt, TkTbycharback, &ixw) ||
1161 							   ixw.line != l || ixw.item == l->items && ixw.pos == 0)
1162 								goto Wrapchar;		/* no suitable point, degrade to char wrap */
1163 						}
1164 						ix = ixw;
1165 					}
1166 					/* now find start of word */
1167 					tktadjustind(tkt, TkTbywrapstart, &ixw);
1168 					if(ixw.line == l && (ixw.item != l->items || ixw.pos > 0)){
1169 						/* it will leave something on the line, so reasonable to split here */
1170 						ix = ixw;
1171 					}
1172 					/* otherwise degrade to char wrap */
1173 				}
1174 			   Wrapchar:
1175 				if(ix.pos > 0) {
1176 					needsplit = 1;
1177 					e = tktsplititem(&ix);
1178 					if(e != nil) {
1179 						free(opts);
1180 						free(env);
1181 						return e;
1182 					}
1183 				}
1184 				else
1185 					needsplit = 0;
1186 
1187 				e = tktnewitem(TkTcontline, 0, &it);
1188 				if(e != nil) {
1189 					free(opts);
1190 					free(env);
1191 					return e;
1192 				}
1193 				e = tktiteminsert(tkt, &ix, it);
1194 				if(e != nil) {
1195 					tktfreeitems(tkt, it, 1);
1196 					free(opts);
1197 					free(env);
1198 					return e;
1199 				}
1200 
1201 				l = l->prev;	/* work on part of line up to split */
1202 
1203 				if(needsplit) {
1204 					/* have to calculate width of pre-split part */
1205 					ixprev = ix;
1206 					if(tktadjustind(tkt, TkTbyitemback, &ixprev) &&
1207 					   tktadjustind(tkt, TkTbyitemback, &ixprev)) {
1208 						w = tktdispwidth(tk, tb, ixprev.item, nil, x, 0, -1);
1209 						ixprev.item->width = w;
1210 						x += w;
1211 					}
1212 				}
1213 				else {
1214 					h = oh;
1215 					a = oa;
1216 				}
1217 				break;
1218 			}
1219 			else {
1220 			    Nosplit:
1221 				i->width =w;
1222 				x += w;
1223 			}
1224 		}
1225 		if (a > h)
1226 			h = a;
1227 		if (lh == 0)
1228 			lh = f->height;
1229 		if (lh > h) {
1230 			a += (lh - h) / 2;
1231 			h = lh;
1232 		}
1233 
1234 		/*
1235 		 * Now line l is broken correctly and has correct item widths/line height/ascent.
1236 		 * Merge adjacent TkTascii/TkTrune items with same tags.
1237 		 * Also, set act{x,y} of embedded widgets to offset from
1238 		 * left of item box at baseline.
1239 		 */
1240 		for(i = l->items; i->next != nil; i = i->next) {
1241 			it = i->next;
1242 			if( (i->kind == TkTascii || i->kind == TkTrune)
1243 			      &&
1244 			     i->kind == it->kind
1245 			      &&
1246 			     tktsametags(i, it)) {
1247 				n = strlen(i->istring);
1248 				j = strlen(it->istring);
1249 				s = realloc(i->istring, n + j + 1);
1250 				if(s == nil) {
1251 					free(opts);
1252 					free(env);
1253 					return TkNomem;
1254 				}
1255 				i->istring = s;
1256 				memmove(i->istring+n, it->istring, j+1);
1257 				i->width += it->width;
1258 				i->next = it->next;
1259 				it->next = nil;
1260 				tktfreeitems(tkt, it, 1);
1261 			}
1262 			else if(i->kind == TkTwin && i->iwin->sub != nil) {
1263 				sub = i->iwin->sub;
1264 				n = sub->act.height + 2 * sub->borderwidth;
1265 				o = i->iwin->pady;
1266 				sub->act.x = i->iwin->padx;
1267 				/*
1268 				 * sub->act.y is y-origin of widget relative to baseline.
1269 				 */
1270 				switch(i->iwin->align) {
1271 				case Tktop:
1272 					sub->act.y = o - a;
1273 					break;
1274 				case Tkbottom:
1275 					sub->act.y = h - (o + n) - a;
1276 					break;
1277 				case Tkcenter:
1278 					sub->act.y = (h - n) / 2 - a;
1279 					break;
1280 				case Tkbaseline:
1281 					wa = i->iwin->ascent;
1282 					if (wa == -1)
1283 						wa = n;
1284 					sub->act.y = -wa;
1285 					break;
1286 				}
1287 			}
1288 		}
1289 
1290 		l->width = x - xleft;
1291 
1292 		/* justification bug: wrong if line has tabs */
1293 		l->orig.x = xleft;
1294 		n = xright - x;
1295 		if(n > 0) {
1296 			if(just == Tkright)
1297 				l->orig.x += n;
1298 			else
1299 			if(just == Tkcenter)
1300 				l->orig.x += n/2;
1301 		}
1302 
1303 		/* give newline or contline width up to right margin */
1304 		ilast = tktlastitem(l->items);
1305 		ilast->width = xright - l->width;
1306 		if(ilast->width < 0)
1307 			ilast->width = 0;
1308 
1309 		l->orig.y = y;
1310 		l->height = h;
1311 		l->ascent = a;
1312 		y += h;
1313 		if(l->flags&TkTlast)
1314 			y += sp3;
1315 	}
1316 	free(opts);
1317 	free(env);
1318 
1319 	tktdrawbg(tk, oldi.lo, oldi.hi, 0);
1320 
1321 	y += tktprespace(tk, l);
1322 	newrest.lo = y;
1323 	newrest.hi = y + rest.hi - rest.lo;
1324 
1325 	hole = tkttranslate(tk, newrest, rest.lo);
1326 
1327 	tktdrawbg(tk, hole.lo, hole.hi, 0);
1328 
1329 	if(l != &tkt->end) {
1330 		while(l != &tkt->end) {
1331 			oh = l->next->orig.y - l->orig.y;
1332 			l->orig.y = y;
1333 			if(y + oh > hole.lo && y < hole.hi) {
1334 				l->flags &= ~TkTdrawn;
1335 			}
1336 			y += oh;
1337 			l = l->next;
1338 		}
1339 	}
1340 	tkt->end.orig.y = tkt->end.prev->orig.y + tkt->end.prev->height;
1341 
1342 	if(tkt->deltatv.y > tkt->end.orig.y)
1343 		tkt->deltatv.y = tkt->end.prev->orig.y;
1344 
1345 
1346 	e = tktsetscroll(tk, Tkvertical);
1347 	if(e != nil)
1348 		return e;
1349 	e = tktsetscroll(tk, Tkhorizontal);
1350 	if(e != nil)
1351 		return e;
1352 
1353 	tk->dirty = tkrect(tk, 1);
1354 	if(tktdbg)
1355 		tktcheck(tkt, "tktfixgeom end");
1356 	return nil;
1357 }
1358 
1359 static int
1360 tktpostspace(Tk *tk, TkTline *l)
1361 {
1362 	int ans;
1363 	TkTitem *i;
1364 	TkEnv env;
1365 	int *opts;
1366 
1367 	opts = mallocz(TkTnumopts*sizeof(int), 0);
1368 	if(opts == nil)
1369 		return 0;
1370 	ans = 0;
1371 	if(l->items != nil && (l->flags&TkTlast)) {
1372 		for(i = l->items; i->kind == TkTmark; )
1373 			i = i->next;
1374 		tkttagopts(tk, i, opts, &env, nil, 1);
1375 		ans = opts[TkTspacing3];
1376 	}
1377 	free(opts);
1378 	return ans;
1379 }
1380 
1381 static int
1382 tktprespace(Tk *tk, TkTline *l)
1383 {
1384 	int ans;
1385 	TkTitem *i;
1386 	TkEnv env;
1387 	int *opts;
1388 
1389 	opts = mallocz(TkTnumopts*sizeof(int), 0);
1390 	if(opts == nil)
1391 		return 0;
1392 
1393 	ans = 0;
1394 	if(l->items != nil) {
1395 		for(i = l->items; i->kind == TkTmark; )
1396 			i = i->next;
1397 		tkttagopts(tk, i, opts, &env, nil, 1);
1398 		if(l->flags&TkTfirst)
1399 			ans = opts[TkTspacing1];
1400 		else
1401 			ans = opts[TkTspacing2];
1402 	}
1403 	free(opts);
1404 	return ans;
1405 }
1406 
1407 static int
1408 tktwidbetween(Tk *tk, int x, TkTindex *i1, TkTindex *i2)
1409 {
1410 	int d, w, n;
1411 	TkTindex ix;
1412 	TkText *tkt = TKobj(TkText, tk);
1413 
1414 	w = 0;
1415 	ix = *i1;
1416 	while(ix.item != i2->item) {
1417 		/* probably wrong w.r.t tag tabs */
1418 		d = tktdispwidth(tk, nil, ix.item, nil, x, ix.pos, -1);
1419 		w += d;
1420 		x += d;
1421 		if(!tktadjustind(tkt, TkTbyitem, &ix)) {
1422 			if(tktdbg)
1423 				print("tktwidbetween botch\n");
1424 			break;
1425 		}
1426 	}
1427 	n = i2->pos - ix.pos;
1428 	if(n > 0)
1429 		/* probably wrong w.r.t tag tabs */
1430 		w += tktdispwidth(tk, nil, ix.item, nil, x, ix.pos, i2->pos-ix.pos);
1431 	return w;
1432 }
1433 
1434 static Interval
1435 tktvclip(Interval i, int vh)
1436 {
1437 	if(i.lo < 0)
1438 		i.lo = 0;
1439 	if(i.hi > vh)
1440 		i.hi = vh;
1441 	return i;
1442 }
1443 
1444 /*
1445  * Do translation of any part of interval that appears on screen
1446  * starting at srcy to its new position, dsti.
1447  * Return y-range of the hole left in the image (either because
1448  * the src bits were out of the V window, or because the src bits
1449  * vacated an area of the V window).
1450  * The coordinates passed in and out are in T space.
1451  */
1452 static Interval
1453 tkttranslate(Tk *tk, Interval dsti, int srcy)
1454 {
1455 	int vh, vw, dvty, locked;
1456 	TkText *tkt;
1457 	Image *i;
1458 	Interval hole, vdst, vsrc;
1459 	Point src;
1460 	Rectangle dst;
1461 	Display *d;
1462 
1463 	hole.hi = 0;
1464 	hole.lo = 0;
1465 
1466 
1467 	/*
1468 	 * If we are embedded in a text widget, we need to come in through
1469 	 * the tkdrawtext routine, to ensure our clipr is set properly, so we
1470 	 * just punt in that case.
1471 	 * XXX is just checking parent good enough. what if we're in
1472 	 * a frame in a text widget?
1473 	 * BUG!
1474 
1475 	* if(tk->parent != nil && tk->parent->type == TKtext) {
1476 	*	tk->flag |= Tkrefresh;
1477 	*	return hole;
1478 	* }
1479 	*/
1480 	tkt = TKobj(TkText, tk);
1481 	dvty = tkt->deltatv.y;
1482 	i = tkt->image;
1483 
1484 	vw = tk->act.width - tk->ipad.x;
1485 	vh = tk->act.height - tk->ipad.y;
1486 
1487 	/* convert to V space */
1488 	vdst.lo = dsti.lo - dvty;
1489 	vdst.hi = dsti.hi - dvty;
1490 	vsrc.lo = srcy - dvty;
1491 	vsrc.hi = vsrc.lo + dsti.hi - dsti.lo;
1492 	if(vsrc.lo == vsrc.hi || vsrc.lo == vdst.lo)
1493 		return hole;
1494 	else if(vsrc.hi <= 0 || vsrc.lo >= vh)
1495 		hole = tktvclip(vdst, vh);
1496 	else if(vdst.hi <= 0 || vdst.lo >= vh)
1497 		hole = tktvclip(vsrc, vh);
1498 	else if(i != nil) {
1499 		src.x = 0;
1500 		src.y = vsrc.lo;
1501 		if(vdst.lo > vsrc.lo) {  /* see earlier text lines */
1502 			if(vsrc.lo < 0) {
1503 				src.y = 0;
1504 				vdst.lo -= vsrc.lo;
1505 			}
1506 			if(vdst.hi > vh)
1507 				vdst.hi = vh;
1508 			hole.lo = src.y;
1509 			hole.hi = vdst.lo;
1510 		}
1511 		else {  /* see later text lines */
1512 			if(vsrc.hi > vh)
1513 				vdst.hi -= (vsrc.hi - vh);
1514 			if(vdst.lo < 0){
1515 				src.y -= vdst.lo;
1516 				vdst.lo = 0;
1517 			}
1518 			hole.lo = vdst.hi;
1519 			hole.hi = src.y + (vdst.hi - vdst.lo);
1520 		}
1521 		if(vdst.hi > vdst.lo && (tkt->tflag&TkTdrawn)) {
1522 			src = addpt(src, tkt->deltaiv);
1523 			dst = rectaddpt(Rect(0, vdst.lo, vw, vdst.hi), tkt->deltaiv);
1524 			d = tk->env->top->display;
1525 			locked = 0;
1526 			if(!(tkt->tflag&TkTdlocked))
1527 				locked = lockdisplay(d);
1528 			i = tkimageof(tk);
1529 			tkt->image = i;
1530 			if(i != nil)
1531 				draw(i, dst, i, nil, src);
1532 			if(locked)
1533 				unlockdisplay(d);
1534 		}
1535 	}
1536 	hole.lo += dvty;
1537 	hole.hi += dvty;
1538 	return hole;
1539 }
1540 
1541 /*
1542  * mark lines from firsty to lasty as not drawn.
1543  * firsty and lasty are in T space
1544  */
1545 static void
1546 tktnotdrawn(Tk *tk, int firsty, int lasty, int all)
1547 {
1548 	TkTline *lend, *l;
1549 	TkText *tkt = TKobj(TkText, tk);
1550 	if(firsty >= lasty && !all)
1551 		return;
1552 	lend = &tkt->end;
1553 	for(l = tkt->start.next; l != lend; l = l->next) {
1554 		if(l->orig.y+l->height <= firsty)
1555 			continue;
1556 		if(l->orig.y >= lasty)
1557 			break;
1558 		l->flags &= ~TkTdrawn;
1559 		if (firsty > l->orig.y)
1560 			firsty = l->orig.y;
1561 		if (lasty < l->orig.y+l->height)
1562 			lasty = l->orig.y+l->height;
1563 	}
1564 	tktdrawbg(tk, firsty, lasty, all);
1565 	tk->dirty = tkrect(tk, 1);
1566 }
1567 
1568 /*
1569  * firsty and lasty are in T space
1570  */
1571 static void
1572 tktdrawbg(Tk *tk, int firsty, int lasty, int all)
1573 {
1574 	int vw, vh, locked;
1575 	Rectangle r;
1576 	Image *i;
1577 	Display *d;
1578 	TkText *tkt = TKobj(TkText, tk);
1579 
1580 	if(tk->env->top->root->flag & Tksuspended){
1581 		tk->flag |= Tkrefresh;
1582 		return;
1583 	}
1584 	/*
1585 	 * If we are embedded in a text widget, we need to come in through
1586 	 * the tkdrawtext routine, to ensure our clipr is set properly, so we
1587 	 * just punt in that case.
1588 	 * BUG!
1589 	 * if(tk->parent != nil && tk->parent->type == TKtext) {
1590 	 * 	tk->flag |= Tkrefresh;
1591 	 * 	return;
1592 	 * }
1593 	 */
1594 	vw = tk->act.width - tk->ipad.x;
1595 	vh = tk->act.height - tk->ipad.y;
1596 	if(all) {
1597 		/* whole background is to be drawn, not just until last line */
1598 		firsty = 0;
1599 		lasty = 100000;
1600 	}
1601 	if(firsty >= lasty)
1602 		return;
1603 	firsty -= tkt->deltatv.y;
1604 	lasty -= tkt->deltatv.y;
1605 	if(firsty < 0)
1606 		firsty = 0;
1607 	if(lasty > vh)
1608 		lasty = vh;
1609 	r = rectaddpt(Rect(0, firsty, vw, lasty), tkt->deltaiv);
1610 	if(r.min.y < r.max.y && (tkt->tflag&TkTdrawn)) {
1611 		d = tk->env->top->display;
1612 		locked = 0;
1613 		if(!(tkt->tflag&TkTdlocked))
1614 			locked = lockdisplay(d);
1615 		i = tkimageof(tk);
1616 		tkt->image = i;
1617 		if(i != nil)
1618 			draw(i, r, tkgc(tk->env, TkCbackgnd), nil, ZP);
1619 		if(locked)
1620 			unlockdisplay(d);
1621 	}
1622 }
1623 
1624 static void
1625 tktfixscroll(Tk *tk, Point odeltatv)
1626 {
1627 	int lasty;
1628 	Interval oi, hole;
1629 	Rectangle oclipr;
1630 	Image *dst;
1631 	Point ndeltatv;
1632 	TkText *tkt = TKobj(TkText, tk);
1633 
1634 	ndeltatv = tkt->deltatv;
1635 
1636 	if(eqpt(odeltatv, ndeltatv))
1637 		return;
1638 
1639 	/* set clipr to avoid spilling outside (in case didn't come in through draw) */
1640 	dst = tkimageof(tk);
1641 	if(dst != nil) {
1642 		tkt->image = dst;
1643 		oclipr = dst->clipr;
1644 		tktsetclip(tk);
1645 	}
1646 
1647 	lasty = tkt->end.orig.y;
1648 	if(odeltatv.x != ndeltatv.x)
1649 		tktnotdrawn(tk, ndeltatv.y, lasty, 0);
1650 	else {
1651 		oi.lo = odeltatv.y;
1652 		oi.hi = lasty;
1653 		hole = tkttranslate(tk, oi, ndeltatv.y);
1654 		tktnotdrawn(tk, hole.lo, hole.hi, 0);
1655 	}
1656 	if(dst != nil)
1657 		tktreplclipr(dst, oclipr);
1658 }
1659 
1660 void
1661 tktextgeom(Tk *tk)
1662 {
1663 	TkTindex ix;
1664 	Rectangle oclipr;
1665 	Image *dst;
1666 	TkText *tkt = TKobj(TkText, tk);
1667 	char buf[20], *p;
1668 
1669 	tkt->tflag &= ~TkTdrawn;
1670 	tktsetdeltas(tk, ZP);
1671 	/* find index of current top-left, so can see it again */
1672 	tktxyind(tk, 0, 0, &ix);
1673 	/* make sure scroll bar is redrawn */
1674 	tkt->scrolltop[Tkvertical] = -1;
1675 	tkt->scrolltop[Tkhorizontal] = -1;
1676 	tkt->scrollbot[Tkvertical] = -1;
1677 	tkt->scrollbot[Tkhorizontal] = -1;
1678 
1679 	/* set clipr to avoid spilling outside (didn't come in through draw) */
1680 	dst = tkimageof(tk);
1681 	if(dst != nil) {
1682 		tkt->image = dst;
1683 		oclipr = dst->clipr;
1684 		tktsetclip(tk);
1685 	}
1686 
1687 	/*
1688 	 * have to save index in a reusable format, as
1689 	 * tktfixgeom can free everything that ix points to.
1690 	 */
1691 	snprint(buf, sizeof(buf), "%d.%d", tktlinenum(tkt, &ix), tktlinepos(tkt, &ix));
1692 	tktfixgeom(tk, &tkt->start, tkt->end.prev, 1);
1693 	p = buf;
1694 	tktindparse(tk, &p, &ix);		/* restore index to something close to original value */
1695 	tktsee(tk, &ix, 1);
1696 
1697 	if(dst != nil)
1698 		tktreplclipr(dst, oclipr);
1699 }
1700 
1701 static char*
1702 tktsetscroll(Tk *tk, int orient)
1703 {
1704 	TkText *tkt;
1705 	TkTline *l;
1706 	int ntot, nmin, nmax, top, bot, vw, vh;
1707 	char *val, *cmd, *v, *e, *s;
1708 
1709 	tkt = TKobj(TkText, tk);
1710 
1711 	s = (orient == Tkvertical)? tkt->yscroll : tkt->xscroll;
1712 	if(s == nil)
1713 		return nil;
1714 
1715 	vw = tk->act.width - tk->ipad.x;
1716 	vh = tk->act.height - tk->ipad.y;
1717 
1718 	if(orient == Tkvertical) {
1719 		l = tkt->end.prev;
1720 		ntot = l->orig.y + l->height;
1721 		nmin = tkt->deltatv.y;
1722 		if(vh <= 0)
1723 			nmax = nmin;
1724 		else
1725 			nmax = nmin + vh;
1726 	}
1727 	else {
1728 		ntot = tktmaxwid(tkt->start.next);
1729 		nmin = tkt->deltatv.x;
1730 		if(vw <= 0)
1731 			nmax = nmin;
1732 		else
1733 			nmax = nmin + vw;
1734 	}
1735 
1736 	if(ntot == 0) {
1737 		top = 0;
1738 		bot = TKI2F(1);
1739 	}
1740 	else {
1741 		if(ntot < nmax)
1742 			ntot = nmax;
1743 		top = TKI2F(nmin)/ntot;
1744 		bot = TKI2F(nmax)/ntot;
1745 	}
1746 
1747 	if(tkt->scrolltop[orient] == top && tkt->scrollbot[orient] == bot)
1748 		return nil;
1749 
1750 	tkt->scrolltop[orient] = top;
1751 	tkt->scrollbot[orient] = bot;
1752 
1753 	val = mallocz(Tkminitem, 0);
1754 	if(val == nil)
1755 		return TkNomem;
1756 	cmd = mallocz(Tkmaxitem, 0);
1757 	if(cmd == nil) {
1758 		free(val);
1759 		return TkNomem;
1760 	}
1761 
1762 	v = tkfprint(val, top);
1763 	*v++ = ' ';
1764 	tkfprint(v, bot);
1765 	snprint(cmd, Tkmaxitem, "%s %s", s, val);
1766 	e = tkexec(tk->env->top, cmd, nil);
1767 	free(cmd);
1768 	free(val);
1769 	return e;
1770 }
1771 
1772 static char*
1773 tktview(Tk *tk, char *arg, char **val, int nl, int *posn, int max, int orient)
1774 {
1775 	int top, bot, amount, n;
1776 	char buf[Tkminitem], *v, *e;
1777 
1778 	if(*arg == '\0') {
1779 		if ( max == 0 ) {
1780 			top = 0;
1781 			bot = TKI2F(1);
1782 		}
1783 		else {
1784 			top = TKI2F(*posn)/max;
1785 			bot = TKI2F(*posn+nl)/max;
1786 			if (bot > TKI2F(1))
1787 				bot = TKI2F(1);
1788 		}
1789 		v = tkfprint(buf, top);
1790 		*v++ = ' ';
1791 		tkfprint(v, bot);
1792 		return tkvalue(val, "%s", buf);
1793 	}
1794 
1795 	arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
1796 	if(strcmp(buf, "moveto") == 0) {
1797 		e = tkfracword(tk->env->top, &arg, &top, nil);
1798 		if (e != nil)
1799 			return e;
1800 		*posn = TKF2I(top*max);
1801 	}
1802 	else
1803 	if(strcmp(buf, "scroll") == 0) {
1804 		e = tkfracword(tk->env->top, &arg, &amount, nil);
1805 		if(e != nil)
1806 			return e;
1807 		arg = tkskip(arg, " \t");
1808 		if(*arg == 'p')		/* Pages */
1809 			amount *= nl;
1810 		else				/* Lines or Characters */
1811 		if(orient == Tkvertical) {
1812 			/* XXX needs improvement */
1813 			amount *= tk->env->font->height;
1814 		}
1815 		else
1816 			amount *= tk->env->wzero;
1817 		amount = TKF2I(amount);
1818 		n = *posn + amount;
1819 		if(n < 0)
1820 			n = 0;
1821 		if(n > max)
1822 			n = max;
1823 		*posn = n;
1824 	}
1825 	else
1826 		return TkBadcm;
1827 
1828 	bot = max - (nl * 3 / 4);
1829 	if(*posn > bot)
1830 		*posn = bot;
1831 	if(*posn < 0)
1832 		*posn = 0;
1833 
1834 	return nil;
1835 }
1836 
1837 static void
1838 tktclearsel(Tk *tk)
1839 {
1840 	TkTindex ibeg, iend;
1841 	TkText *tkt = TKobj(TkText, tk);
1842 
1843 	if(tkt->selfirst == nil)
1844 		return;
1845 	tktitemind(tkt->selfirst, &ibeg);
1846 	tktitemind(tkt->sellast, &iend);
1847 
1848 	tkttagchange(tk, TkTselid, &ibeg, &iend, 0);
1849 }
1850 
1851 static int
1852 tktgetsel(Tk *tk, TkTindex *i1, TkTindex *i2)
1853 {
1854 	TkText *tkt =TKobj(TkText, tk);
1855 
1856 	if(tkt->selfirst == nil)
1857 		return 0;
1858 	tktitemind(tkt->selfirst, i1);
1859 	tktitemind(tkt->sellast, i2);
1860 	return 1;
1861 }
1862 
1863 /*
1864  * Adjust tkt->deltatv so that indexed character is visible.
1865  *	- if seetop is true, make indexed char be at top of window
1866  *	- if it is already visible, do nothing.
1867  *	- if it is > 1/2 screenful off edge of screen, center it
1868  *	   else put it at bottom or top (whichever is nearer)
1869  *	- if first line is visible, put it at top
1870  *	- if last line is visible, allow one blank line at bottom
1871  *
1872  * BUG: should handle x visibility too
1873  */
1874 static void
1875 tktsee(Tk *tk, TkTindex *ixp, int seetop)
1876 {
1877 	int ycur, ynext, deltatvy, adjy, h;
1878 	Point p, odeltatv;
1879 	Rectangle bbox;
1880 	TkTline *l, *el;
1881 	TkText *tkt = TKobj(TkText, tk);
1882 	TkTindex ix;
1883 
1884 	ix = *ixp;
1885 	deltatvy = tkt->deltatv.y;
1886 	odeltatv = tkt->deltatv;
1887 	h = tk->act.height;
1888 
1889 	/* find p (in T space): top left of indexed line */
1890 	l = ix.line;
1891 	p = l->orig;
1892 
1893 	/* ycur, ynext in V space */
1894 	ycur = p.y - deltatvy;
1895 	ynext = ycur + l->height;
1896 	adjy = 0;
1897 
1898 	/* quantize h to line boundaries (works if single font) */
1899 	if ( l->height )
1900 		h -= h%l->height;
1901 
1902 	if(seetop) {
1903 		deltatvy = p.y;
1904 		adjy = 1;
1905 	}
1906 	else
1907 	if(ycur < 0 || ynext >= h) {
1908 		adjy = 1;
1909 
1910 		if(ycur < -h/2 || ycur > 3*h/2)
1911 			deltatvy = p.y - h/2;
1912 		else if(ycur < 0)
1913 			deltatvy = p.y;
1914 		else
1915 			deltatvy = p.y - h + l->height;
1916 
1917 		el = tkt->end.prev;
1918 		if(el != nil && el->orig.y - deltatvy < h)
1919 			deltatvy = tkt->end.orig.y - (h * 3 / 4);
1920 
1921 		if(p.y - deltatvy < 0)
1922 			deltatvy = p.y;
1923 		if(deltatvy < 0)
1924 			deltatvy = 0;
1925 	}
1926 	if(adjy) {
1927 		tkt->deltatv.y = deltatvy;
1928 		tktsetscroll(tk, Tkvertical);	/* XXX - Tad: err ignored */
1929 		tktfixscroll(tk, odeltatv);
1930 	}
1931 	while (ix.item->kind == TkTmark)
1932 		ix.item = ix.item->next;
1933 	bbox = tktbbox(tk, &ix);
1934 	/* make sure that cursor at the end gets shown */
1935 	tksee(tk, bbox, Pt(bbox.min.x, (bbox.min.y + bbox.max.y) / 2));
1936 }
1937 
1938 static int
1939 tktcmatch(int c1, int c2, int nocase)
1940 {
1941 	if(nocase) {
1942 		if(c1 >= 'a' && c1 <= 'z')
1943 			c1 -= 'a' - 'A';
1944 		if(c2 >= 'a' && c2 <= 'z')
1945 			c2 -= 'a' - 'A';
1946 	}
1947 	return (c1 == c2);
1948 }
1949 
1950 /*
1951  * Return 1 if tag with id m1 ends before tag with id m2,
1952  * starting at the item after that indexed in ix (but don't
1953  * modify ix).
1954  */
1955 static int
1956 tagendsbefore(TkText *tkt, TkTindex *ix, int m1, int m2)
1957 {
1958 	int s1, s2;
1959 	TkTindex ix1;
1960 	TkTitem *i;
1961 
1962 	ix1 = *ix;
1963 	while(tktadjustind(tkt, TkTbyitem, &ix1)) {
1964 		i = ix1.item;
1965 		if(i->kind == TkTwin || i->kind == TkTcontline || i->kind == TkTmark)
1966 			continue;
1967 		s1 = tkttagset(i, m1);
1968 		s2 = tkttagset(i, m2);
1969 		if(!s1)
1970 			return s2;
1971 		else if(!s2)
1972 			return 0;
1973 	}
1974 	return 0;
1975 }
1976 
1977 static int
1978 tktsgmltags(TkText *tkt, Fmt *fmt, TkTitem *iprev, TkTitem *i, TkTindex *ix, int *stack, int *pnstack, int *tmpstack)
1979 {
1980 	int nprev, n, m, r, k, j, ii, onstack, nt;
1981 
1982 	nprev = 0;
1983 	if(iprev != nil && (iprev->tags[0] != 0 || iprev->tagextra > 0))
1984 		nprev = 32*(iprev->tagextra + 1);
1985 	n = 0;
1986 	if(i != nil && (i->tags[0] != 0 || i->tagextra > 0))
1987 		n = 32*(i->tagextra + 1);
1988 	nt = 0;
1989 	if(n > 0) {
1990 		/* find tags which open here */
1991 		for(m = 0; m < n; m++)
1992 			if(tkttagset(i, m) && (iprev == nil || !tkttagset(iprev, m)))
1993 				tmpstack[nt++] = m;
1994 	}
1995 	if(nprev > 0) {
1996 		/*
1997 		 * Find lowest tag in stack that ends before any tag beginning here.
1998 		 * We have to emit end tags all the way down to there, then add
1999 		 * back the ones that haven't actually ended here, together with ones
2000 		 * that start here, and sort all of the added ones so that tags that
2001 		 * end later are lower in the stack.
2002 		 */
2003 		ii = *pnstack;
2004 		for(k = *pnstack - 1; k >=0; k--) {
2005 			m = stack[k];
2006 			if(i == nil || !tkttagset(i, m))
2007 				ii = k;
2008 			else
2009 				for(j = 0; j < nt; j++)
2010 					if(tagendsbefore(tkt, ix, m, tmpstack[j]))
2011 						ii = k;
2012 		}
2013 		for(k = *pnstack - 1; k >= ii; k--) {
2014 			m = stack[k];
2015 			r = fmtprint(fmt, "</%s>", tkttagname(tkt, m));
2016 			if(r < 0)
2017 				return r;
2018 			/* add m back to starting tags if m didn't actually end here */
2019 			if(i != nil && tkttagset(i, m))
2020 				tmpstack[nt++] = m;
2021 		}
2022 		*pnstack = ii;
2023 	}
2024 	if(nt > 0) {
2025 		/* add tags which open  or reopen here */
2026 		onstack = *pnstack;
2027 		k = onstack;
2028 		for(j = 0; j < nt; j++)
2029 			stack[k++] = tmpstack[j];
2030 		*pnstack = k;
2031 		if(k - onstack > 1) {
2032 			/* sort new stack entries so tags that end later are lower in stack */
2033 			for(ii = k-2; ii>= onstack; ii--) {
2034 				m = stack[ii];
2035 				for(j = ii+1; j < k && tagendsbefore(tkt, ix, m, stack[j]); j++) {
2036 					stack[j-1] = stack[j];
2037 				}
2038 				stack[j-1] = m;
2039 			}
2040 		}
2041 		for(j = onstack; j < k; j++) {
2042 			r = fmtprint(fmt, "<%s>", tkttagname(tkt, stack[j]));
2043 			if(r < 0)
2044 				return r;
2045 		}
2046 	}
2047 	return 0;
2048 }
2049 
2050 /*
2051  * In 'sgml' format, just print text (no special treatment of
2052  * special characters, except that < turns into &lt;)
2053  * interspersed with things like <Bold> and </Bold>
2054  * (where Bold is a tag name).
2055  * Make sure that the tag pairs nest properly.
2056 */
2057 static char*
2058 tktget(TkText *tkt, TkTindex *ix1, TkTindex *ix2, int sgml, char **val)
2059 {
2060 	int n, m, i, bychar, nstack;
2061 	int *stack, *tmpstack;
2062 	char *s;
2063 	TkTitem *iprev;
2064 	Tk *sub;
2065 	Fmt fmt;
2066 	char *buf;
2067 
2068 	if(!tktindbefore(ix1, ix2))
2069 		return nil;
2070 
2071 	stack = nil;
2072 	tmpstack = nil;
2073 
2074 	iprev = nil;
2075 	fmtstrinit(&fmt);
2076 	buf = mallocz(100, 0);
2077 	if(buf == nil)
2078 		return TkNomem;
2079 	if(sgml) {
2080 		stack = malloc((tkt->nexttag+1)*sizeof(int));
2081 		tmpstack = malloc((tkt->nexttag+1)*sizeof(int));
2082 		if(stack == nil || tmpstack == nil)
2083 			goto nomemret;
2084 		nstack = 0;
2085 	}
2086 	for(;;) {
2087 		if(ix1->item == ix2->item && ix1->pos == ix2->pos)
2088 			break;
2089 		s = nil;
2090 		bychar = 0;
2091 		m = 1;
2092 		switch(ix1->item->kind) {
2093 		case TkTrune:
2094 			s = ix1->item->istring;
2095 			s += tktutfpos(s, ix1->pos);
2096 			if(ix1->item == ix2->item) {
2097 				m = ix2->pos - ix1->pos;
2098 				bychar = 1;
2099 			}
2100 			break;
2101 		case TkTascii:
2102 			s = ix1->item->istring + ix1->pos;
2103 			if(ix1->item == ix2->item) {
2104 				m = ix2->pos - ix1->pos;
2105 				bychar = 1;
2106 			}
2107 			else {
2108 				m = strlen(s);
2109 				if(sgml && memchr(s, '<', m) != nil)
2110 					bychar = 1;
2111 			}
2112 			break;
2113 		case TkTtab:
2114 			s = "\t";
2115 			break;
2116 		case TkTnewline:
2117 			s = "\n";
2118 			break;
2119 		case TkTwin:
2120 			sub = ix1->item->iwin->sub;
2121 			if(sgml &&  sub != nil && sub->name != nil) {
2122 				snprint(buf, 100, "<Window %s>", sub->name->name);
2123 				s = buf;
2124 			}
2125 		}
2126 		if(s != nil) {
2127 			if(sgml) {
2128 				n = tktsgmltags(tkt, &fmt, iprev, ix1->item, ix1, stack, &nstack, tmpstack);
2129 				if(n < 0)
2130 					goto nomemret;
2131 			}
2132 			if(bychar) {
2133 				if (ix1->item->kind == TkTrune)
2134 					n = fmtprint(&fmt, "%.*s", m, s);
2135 				else {
2136 					n = 0;
2137 					for(i = 0; i < m && n >= 0; i++) {
2138 						if(s[i] == '<')
2139 							n = fmtprint(&fmt, "&lt;");
2140 						else
2141 							n = fmtprint(&fmt, "%c", s[i]);
2142 					}
2143 				}
2144 			}
2145 			else
2146 				n = fmtprint(&fmt, "%s", s);
2147 			if(n < 0)
2148 				goto nomemret;
2149 			iprev = ix1->item;
2150 		}
2151 		if(ix1->item == ix2->item)
2152 			break;
2153 		if(!tktadjustind(tkt, TkTbyitem, ix1)) {
2154 			if(tktdbg)
2155 				print("tktextget botch\n");
2156 			break;
2157 		}
2158 	}
2159 	if(sgml) {
2160 		n = tktsgmltags(tkt, &fmt, iprev, nil, nil, stack, &nstack, tmpstack);
2161 		if(n < 0)
2162 			goto nomemret;
2163 	}
2164 
2165 	*val = fmtstrflush(&fmt);
2166 	free(buf);
2167 	return nil;
2168 
2169 nomemret:
2170 	free(buf);
2171 	if(stack != nil)
2172 		free(stack);
2173 	if(tmpstack != nil)
2174 		free(tmpstack);
2175 	return TkNomem;
2176 }
2177 
2178 /* Widget Commands (+ means implemented)
2179 	+bbox
2180 	+cget
2181 	+compare
2182 	+configure
2183 	+debug
2184 	+delete
2185 	+dlineinfo
2186 	+dump
2187 	+get
2188 	+index
2189 	+insert
2190 	+mark
2191 	+scan
2192 	+search
2193 	+see
2194 	+tag
2195 	+window
2196 	+xview
2197 	+yview
2198 */
2199 
2200 static int
2201 tktviewrectclip(Rectangle *r, Rectangle b);
2202 
2203 static char*
2204 tktextbbox(Tk *tk, char *arg, char **val)
2205 {
2206 	char *e;
2207 	int noclip, w, h;
2208 	Rectangle r, rview;
2209 	TkTindex ix;
2210 	TkText *tkt;
2211 	char buf[Tkmaxitem];
2212 
2213 	e = tktindparse(tk, &arg, &ix);
2214 	if(e != nil)
2215 		return e;
2216 
2217 	noclip = 0;
2218 	if(*arg != '\0') {
2219 		/* extension to tk4.0:
2220 		 * "noclip" means don't clip to viewable area
2221 		 * "all" means give unclipped bbox of entire contents
2222 		 */
2223 		arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
2224 		if(strcmp(buf, "noclip") == 0)
2225 			noclip = 1;
2226 		else
2227 		if(strcmp(buf, "all") == 0) {
2228 			tkt = TKobj(TkText, tk);
2229 			w = tktmaxwid(tkt->start.next);
2230 			h = tkt->end.orig.y;
2231 			return tkvalue(val, "0 0 %d %d", w, h);
2232 		}
2233 	}
2234 
2235 	/*
2236 	 * skip marks; bbox applies to characters only.
2237 	 * it's not defined what happens when bbox is applied to a newline char,
2238 	 * so we'll just let the default case sort that out.
2239 	 */
2240 	while (ix.item->kind == TkTmark)
2241 		ix.item = ix.item->next;
2242 	r = tktbbox(tk, &ix);
2243 
2244 	rview.min.x = 0;
2245 	rview.min.y = 0;
2246 	rview.max.x = tk->act.width - tk->ipad.x;
2247 	rview.max.y = tk->act.height - tk->ipad.y;
2248 	if(noclip || tktviewrectclip(&r, rview))
2249 		return tkvalue(val, "%d %d %d %d", r.min.x, r.min.y,
2250 			r.max.x-r.min.x, r.max.y-r.min.y);
2251 	return nil;
2252 }
2253 
2254 /*
2255  * a supplemented rectclip, as ((0, 1), (0,1)) does not intersect ((0, 0), (5, 5))
2256  * but for our purposes, we want it to. it's a hack.
2257  */
2258 static int
2259 tktviewrectclip(Rectangle *rp, Rectangle b)
2260 {
2261 	Rectangle *bp = &b;
2262 	if((rp->min.x<bp->max.x &&
2263 		(bp->min.x<rp->max.x || (rp->max.x  == b.min.x
2264 				&& rp->min.x == b.min.x)) &&
2265 			rp->min.y<bp->max.y && bp->min.y<rp->max.y)==0)
2266 		return 0;
2267 	/* They must overlap */
2268 	if(rp->min.x < bp->min.x)
2269 		rp->min.x = bp->min.x;
2270 	if(rp->min.y < bp->min.y)
2271 		rp->min.y = bp->min.y;
2272 	if(rp->max.x > bp->max.x)
2273 		rp->max.x = bp->max.x;
2274 	if(rp->max.y > bp->max.y)
2275 		rp->max.y = bp->max.y;
2276 	return 1;
2277 }
2278 
2279 static Point
2280 scr2local(Tk *tk, Point p)
2281 {
2282 	p = subpt(p, tkposn(tk));
2283 	p.x -= tk->borderwidth;
2284 	p.y -= tk->borderwidth;
2285 	return p;
2286 }
2287 
2288 static char*
2289 tktextbutton1(Tk *tk, char *arg, char **val)
2290 {
2291 	char *e;
2292 	Point p;
2293 	TkCtxt *c;
2294 	TkTindex ix;
2295 	TkTmarkinfo *mi;
2296 	TkText *tkt = TKobj(TkText, tk);
2297 
2298 	USED(val);
2299 
2300 	e = tkxyparse(tk, &arg, &p);
2301 	if(e != nil)
2302 		return e;
2303 	tkt->track = p;
2304 	p = scr2local(tk, p);
2305 
2306 	tktxyind(tk, p.x, p.y, &ix);
2307 	tkt->tflag &= ~TkTjustfoc;
2308 	c = tk->env->top->ctxt;
2309 	if(!(tk->flag&Tkdisabled) && c->tkkeygrab != tk
2310                       && (tk->name != nil) && ix.item->kind != TkTwin) {
2311 		tkfocus(tk->env->top, tk->name->name, nil);
2312 		tkt->tflag |= TkTjustfoc;
2313 		return nil;
2314 	}
2315 
2316 	mi = tktfindmark(tkt->marks, "insert");
2317 	if(tktdbg && !mi) {
2318 		print("tktextbutton1: botch\n");
2319 		return nil;
2320 	}
2321 	tktmarkmove(tk, mi, &ix);
2322 
2323 	tktclearsel(tk);
2324 	tkrepeat(tk, autoselect, nil, TkRptpause, TkRptinterval);
2325 	return nil;
2326 }
2327 
2328 static char*
2329 tktextbutton1r(Tk *tk, char *arg, char **val)
2330 {
2331 	TkText *tkt;
2332 
2333 	USED(arg);
2334 	USED(val);
2335 
2336 	tkt = TKobj(TkText, tk);
2337 	tkt->tflag &= ~TkTnodrag;
2338 	tkcancelrepeat(tk);
2339 	return nil;
2340 }
2341 
2342 static char*
2343 tktextcget(Tk *tk, char *arg, char **val)
2344 {
2345 	TkText *tkt;
2346 	TkOptab tko[3];
2347 
2348 	tkt = TKobj(TkText, tk);
2349 	tko[0].ptr = tk;
2350 	tko[0].optab = tkgeneric;
2351 	tko[1].ptr = tkt;
2352 	tko[1].optab = textopts;
2353 	tko[2].ptr = nil;
2354 
2355 	return tkgencget(tko, arg, val, tk->env->top);
2356 }
2357 
2358 static char*
2359 tktextcompare(Tk *tk, char *arg, char **val)
2360 {
2361 	int op;
2362 	char *e;
2363 	TkTindex i1, i2;
2364 	TkText *tkt;
2365 	TkStab *s;
2366 	char *buf;
2367 
2368 	tkt = TKobj(TkText, tk);
2369 
2370 	e = tktindparse(tk, &arg, &i1);
2371 	if(e != nil)
2372 		return e;
2373 
2374 	if(*arg == '\0')
2375 		return TkBadcm;
2376 
2377 	buf = mallocz(Tkmaxitem, 0);
2378 	if(buf == nil)
2379 		return TkNomem;
2380 
2381 	arg = tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil);
2382 
2383 	op = -1;
2384 	for(s = tkcompare; s->val; s++)
2385 		if(strcmp(s->val, buf) == 0) {
2386 			op = s->con;
2387 			break;
2388 		}
2389 	if(op == -1) {
2390 		free(buf);
2391 		return TkBadcm;
2392 	}
2393 
2394 	e = tktindparse(tk, &arg, &i2);
2395 	if(e != nil) {
2396 		free(buf);
2397 		return e;
2398 	}
2399 
2400 	e = tkvalue(val, tktindcompare(tkt, &i1, op, &i2)? "1" : "0");
2401 	free(buf);
2402 	return e;
2403 }
2404 
2405 static char*
2406 tktextconfigure(Tk *tk, char *arg, char **val)
2407 {
2408 	char *e;
2409 	TkGeom g;
2410 	int bd;
2411 	TkText *tkt;
2412 	TkOptab tko[3];
2413 	tkt = TKobj(TkText, tk);
2414 	tko[0].ptr = tk;
2415 	tko[0].optab = tkgeneric;
2416 	tko[1].ptr = tkt;
2417 	tko[1].optab = textopts;
2418 	tko[2].ptr = nil;
2419 
2420 	if(*arg == '\0')
2421 		return tkconflist(tko, val);
2422 
2423 	g = tk->req;
2424 	bd = tk->borderwidth;
2425 
2426 	e = tkparse(tk->env->top, arg, tko, nil);
2427 	tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd));
2428 	if (tkt->propagate != BoolT) {
2429 		if ((tk->flag & Tksetwidth) == 0)
2430 			tk->req.width = tk->env->wzero*Textwidth;
2431 		if ((tk->flag & Tksetheight) == 0)
2432 			tk->req.height = tk->env->font->height*Textheight;
2433 	}
2434 	/* note: tkgeomchg() may also call tktfixgeom() via tktextgeom() */
2435 	tktfixgeom(tk, &tkt->start, tkt->end.prev, 0);
2436 	tktextsize(tk, 0);
2437 	tkgeomchg(tk, &g, bd);
2438 	tktnotdrawn(tk, 0, tkt->end.orig.y, 1);
2439 
2440 	return e;
2441 }
2442 
2443 static char*
2444 tktextdebug(Tk *tk, char *arg, char **val)
2445 {
2446 	char buf[Tkmaxitem];
2447 
2448 	tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
2449 	if(*buf == '\0')
2450 		return tkvalue(val, "%s", tktdbg? "on" : "off");
2451 	else {
2452 		tktdbg = (strcmp(buf, "1") == 0 || strcmp(buf, "yes") == 0);
2453 		if(tktdbg) {
2454 			tktprinttext(TKobj(TkText, tk));
2455 		}
2456 		return nil;
2457 	}
2458 }
2459 
2460 static char*
2461 tktextdelete(Tk *tk, char *arg, char **val)
2462 {
2463 	int sameit;
2464 	char *e;
2465 	TkTindex i1, i2, ip, isee;
2466 	TkTline *lmin;
2467 	TkText *tkt = TKobj(TkText, tk);
2468 	char buf[20], *p;
2469 
2470 	USED(val);
2471 
2472 	e = tktindparse(tk, &arg, &i1);
2473 	if(e != nil)
2474 		return e;
2475 	tktadjustind(tkt, TkTbycharstart, &i1);
2476 
2477 	e = tktsplititem(&i1);
2478 	if(e != nil)
2479 		return e;
2480 
2481 	if(*arg != '\0') {
2482 		e = tktindparse(tk, &arg, &i2);
2483 		if(e != nil)
2484 			return e;
2485 	}
2486 	else {
2487 		i2 = i1;
2488 		tktadjustind(tkt, TkTbychar, &i2);
2489 	}
2490 	if(tktindcompare(tkt, &i1, TkGte, &i2))
2491 		return nil;
2492 
2493 	sameit = (i1.item == i2.item);
2494 
2495 	/* save possible fixup see place */
2496 	isee.line = nil;
2497 	if(i2.line->orig.y + i2.line->height < tkt->deltatv.y) {
2498 		/* delete completely precedes view */
2499 		tktxyind(tk, 0, 0, &isee);
2500 	}
2501 
2502 	e = tktsplititem(&i2);
2503 	if(e != nil)
2504 		return e;
2505 
2506 	if(sameit) {
2507 		/* after split, i1 should be in previous item to i2 */
2508 		ip = i2;
2509 		tktadjustind(tkt, TkTbyitemback, &ip);
2510 		i1.item = ip.item;
2511 	}
2512 
2513 	lmin = tktprevwrapline(tk, i1.line);
2514 	while(i1.item != i2.item) {
2515 		if(i1.item->kind != TkTmark)
2516 			tktremitem(tkt, &i1);
2517 			/* tktremitem moves i1 to next item */
2518 		else {
2519 			if(!tktadjustind(tkt, TkTbyitem, &i1)) {
2520 				if(tktdbg)
2521 					print("tktextdelete botch\n");
2522 				break;
2523 			}
2524 		}
2525 	}
2526 
2527 	/*
2528 	 * guard against invalidation of index by tktfixgeom
2529 	 */
2530 	if (isee.line != nil)
2531 		snprint(buf, sizeof(buf), "%d.%d", tktlinenum(tkt, &isee), tktlinepos(tkt, &isee));
2532 
2533 	tktfixgeom(tk, lmin, i1.line, 0);
2534 	tktextsize(tk, 1);
2535 	if(isee.line != nil) {
2536 		p = buf;
2537 		tktindparse(tk, &p, &isee);
2538 		tktsee(tk, &isee, 1);
2539 	}
2540 	return nil;
2541 }
2542 
2543 static char*
2544 tktextsee(Tk *tk, char *arg, char **val)
2545 {
2546 	char *e;
2547 	TkTindex ix;
2548 
2549 	USED(val);
2550 
2551 	e = tktindparse(tk, &arg, &ix);
2552 	if(e != nil)
2553 		return e;
2554 
2555 	tktsee(tk, &ix, 0);
2556 	return nil;
2557 }
2558 
2559 static char*
2560 tktextdelins(Tk *tk, char *arg, char **val)
2561 {
2562 	int m, c, skipping, wordc, n;
2563 	TkTindex ix, ix2;
2564 	TkText *tkt = TKobj(TkText, tk);
2565 	char buf[30];
2566 
2567 	USED(val);
2568 
2569 	if(tk->flag&Tkdisabled)
2570 		return nil;
2571 
2572 	if(tktgetsel(tk, &ix, &ix2))
2573 		tktextdelete(tk, "sel.first sel.last", nil);
2574 	else {
2575 		while(*arg == ' ')
2576 			arg++;
2577 		if(*arg == '-') {
2578 			m = arg[1];
2579 			if(m == 'c')
2580 				n = 1;
2581 			else {
2582 				/* delete prev word (m=='w') or prev line (m=='l') */
2583 				if(!tktmarkind(tk, "insert", &ix))
2584 					return nil;
2585 				if(!tktadjustind(tkt, TkTbycharback, &ix))
2586 					return nil;
2587 				n = 1;
2588 				/* ^W skips back over nonwordchars, then takes maximal seq of wordchars */
2589 				skipping = 1;
2590 				for(;;) {
2591 					c = tktindrune(&ix);
2592 					if(c == '\n') {
2593 						/* special case: always delete at least one char */
2594 						if(n > 1)
2595 							n--;
2596 						break;
2597 					}
2598 					if(m == 'w') {
2599 						wordc = tkiswordchar(c);
2600 						if(wordc && skipping)
2601 							skipping = 0;
2602 						else if(!wordc && !skipping) {
2603 							n--;
2604 							break;
2605 						}
2606 					}
2607 					if(tktadjustind(tkt, TkTbycharback, &ix))
2608 						n++;
2609 					else
2610 						break;
2611 				}
2612 			}
2613 			sprint(buf, "insert-%dc insert", n);
2614 			tktextdelete(tk, buf, nil);
2615 		}
2616 		else if(arg[0] == '+' && arg[1] == 'l')
2617 			tktextdelete(tk, "insert {insert lineend}", nil);
2618 		else
2619 			tktextdelete(tk, "insert", nil);
2620 		tktextsee(tk, "insert", nil);
2621 	}
2622 	return nil;
2623 }
2624 
2625 static char*
2626 tktextdlineinfo(Tk *tk, char *arg, char **val)
2627 {
2628 	char *e;
2629 	TkTindex ix;
2630 	TkTline *l;
2631 	Point p;
2632 	int vh;
2633 	TkText *tkt = TKobj(TkText, tk);
2634 
2635 	e = tktindparse(tk, &arg, &ix);
2636 	if(e != nil)
2637 		return e;
2638 
2639 	l = ix.line;
2640 	vh = tk->act.height;
2641 
2642 	/* get p in V space */
2643 	p = subpt(l->orig, tkt->deltatv);
2644 	if(p.y+l->height < 0 || p.y >= vh)
2645 		return nil;
2646 
2647 	return tkvalue(val, "%d %d %d %d %d",
2648 		p.x, p.y, l->width, l->height, l->ascent);
2649 }
2650 
2651 static char*
2652 tktextdump(Tk *tk, char *arg, char **val)
2653 {
2654 	TkTline *l;
2655 	TkTitem *i;
2656 	Fmt fmt;
2657 	TkText *tkt;
2658 	TkDump tkdump;
2659 	TkOptab tko[2];
2660 	TkTtaginfo *ti;
2661 	TkName *names, *n;
2662 	char *e, *win, *p;
2663 	TkTindex ix1, ix2;
2664 	int r, j, numitems;
2665 	ulong fg, bg;
2666 
2667 	tkt = TKobj(TkText, tk);
2668 
2669 
2670 	tkdump.sgml = 0;
2671 	tkdump.metrics = 0;
2672 
2673 	tko[0].ptr = &tkdump;
2674 	tko[0].optab = dumpopts;
2675 	tko[1].ptr = nil;
2676 	names = nil;
2677 	e = tkparse(tk->env->top, arg, tko, &names);
2678 	if(e != nil)
2679 		return e;
2680 
2681 	if(names != nil) {			/* supplied indices */
2682 		p = names->name;
2683 		e = tktindparse(tk, &p, &ix1);
2684 		if(e != nil) {
2685 			tkfreename(names);
2686 			return e;
2687 		}
2688 		n = names->link;
2689 		if(n != nil) {
2690 			p = n->name;
2691 			e = tktindparse(tk, &p, &ix2);
2692 			if(e != nil) {
2693 				tkfreename(names);
2694 				return e;
2695 			}
2696 		}
2697 		else {
2698 			ix2 = ix1;
2699 			tktadjustind(tkt, TkTbychar, &ix2);
2700 		}
2701 		tkfreename(names);
2702 		if(!tktindbefore(&ix1, &ix2))
2703 			return nil;
2704 	}
2705 	else
2706 		return TkBadix;
2707 
2708 	if(tkdump.metrics != 0) {
2709 		fmtstrinit(&fmt);
2710 		if(fmtprint(&fmt, "%%Fonts\n") < 0)
2711 			return TkNomem;
2712 		for(ti=tkt->tags; ti != nil; ti=ti->next) {
2713 			if(ti->env == nil || ti->env->font == nil)
2714 				continue;
2715 			if(fmtprint(&fmt, "%d::%s\n", ti->id,ti->env->font->name) < 0)
2716 				return TkNomem;
2717 		}
2718 		if(fmtprint(&fmt, "-1::%s\n%%Colors\n", tk->env->font->name) < 0)
2719 			return TkNomem;
2720 		for(ti=tkt->tags; ti != nil; ti=ti->next) {
2721 			if(ti->env == nil)
2722 				continue;
2723 			bg = ti->env->colors[TkCbackgnd];
2724 			fg = ti->env->colors[TkCforegnd];
2725 			if(bg == tk->env->colors[TkCbackgnd] &&
2726 			   fg == ti->env->colors[TkCforegnd])
2727 				continue;
2728 			r = fmtprint(&fmt,"%d::#%.8lux\n", ti->id, bg);
2729 			if(r < 0)
2730 				return TkNomem;
2731 			r = fmtprint(&fmt,"%d::#%.8lux\n", ti->id, fg);
2732 			if(r < 0)
2733 				return TkNomem;
2734 		}
2735 		if(fmtprint(&fmt, "%%Lines\n") < 0)
2736 			return TkNomem;
2737 
2738 		/*
2739 		 * In 'metrics' format lines are recorded in the following way:
2740 		 *    xorig yorig wd ht as [data]
2741 		 * where data is of the form:
2742 		 *    CodeWidth{tags} data
2743 		 * For Example;
2744 		 *    A200{200000} Hello World!
2745 		 * denotes an A(scii) contiguous string of 200 pixels with
2746 		 * bit 20 set in its tags which corresponds to some font.
2747 		 *
2748 	 	*/
2749 		if(ix2.line->items != ix2.item)
2750 			ix2.line = ix2.line->next;
2751 		for(l = ix1.line; l != ix2.line; l = l->next) {
2752 			numitems = 0;
2753 			for(i = l->items; i != nil; i = i->next) {
2754 				if(i->kind != TkTmark)
2755 					numitems++;
2756 			}
2757 			r = fmtprint(&fmt, "%d %d %d %d %d %d ",
2758 				l->orig.x, l->orig.y, l->width, l->height, l->ascent,numitems);
2759 			if(r < 0)
2760 				return TkNomem;
2761 			for(i = l->items; i != nil; i = i->next) {
2762 				switch(i->kind) {
2763 				case TkTascii:
2764 				case TkTrune:
2765 					r = i->kind == TkTascii ? 'A' : 'R';
2766 					if(fmtprint(&fmt,"[%c%d{", r, i->width) < 0)
2767 						return TkNomem;
2768 					if(i->tags !=0 || i->tagextra !=0) {
2769 						if(fmtprint(&fmt,"%lux", i->tags[0]) < 0)
2770 							return TkNomem;
2771 						for(j=0; j < i->tagextra; j++)
2772 							if(fmtprint(&fmt,"::%lux", i->tags[j+1]) < 0)
2773 								return TkNomem;
2774 					}
2775 					/* XXX string should be quoted to avoid embedded ']'s */
2776 					if(fmtprint(&fmt,"}%s]", i->istring) < 0)
2777 						return TkNomem;
2778 					break;
2779 				case TkTnewline:
2780 				case TkTcontline:
2781 					r = i->kind == TkTnewline ? 'N' : 'C';
2782 					if(fmtprint(&fmt, "[%c]", r) < 0)
2783 						return TkNomem;
2784 					break;
2785 				case TkTtab:
2786 					if(fmtprint(&fmt,"[T%d]",i->width) < 0)
2787 						return TkNomem;
2788 					break;
2789 				case TkTwin:
2790 					win = "<null>";
2791 					if(i->iwin->sub != nil)
2792 						win = i->iwin->sub->name->name;
2793 					if(fmtprint(&fmt,"[W%d %s]",i->width, win) < 0)
2794 						return TkNomem;
2795 					break;
2796 				}
2797 				if(fmtprint(&fmt, " ") < 0)
2798 					return TkNomem;
2799 
2800 			}
2801 			if(fmtprint(&fmt, "\n") < 0)
2802 				return TkNomem;
2803 			*val = fmtstrflush(&fmt);
2804 			if(*val == nil)
2805 				return TkNomem;
2806 		}
2807 	}
2808 	else
2809 		return tktget(tkt, &ix1, &ix2, tkdump.sgml, val);
2810 
2811 	return nil;
2812 }
2813 
2814 
2815 static char*
2816 tktextget(Tk *tk, char *arg, char **val)
2817 {
2818 	char *e;
2819 	TkTindex ix1, ix2;
2820 	TkText *tkt = TKobj(TkText, tk);
2821 
2822 	e = tktindparse(tk, &arg, &ix1);
2823 	if(e != nil)
2824 		return e;
2825 
2826 	if(*arg != '\0') {
2827 		e = tktindparse(tk, &arg, &ix2);
2828 		if(e != nil)
2829 			return e;
2830 	}
2831 	else {
2832 		ix2 = ix1;
2833 		tktadjustind(tkt, TkTbychar, &ix2);
2834 	}
2835 	return tktget(tkt, &ix1, &ix2, 0, val);
2836 }
2837 
2838 static char*
2839 tktextindex(Tk *tk, char *arg, char **val)
2840 {
2841 	char *e;
2842 	TkTindex ix;
2843 	TkText *tkt = TKobj(TkText, tk);
2844 
2845 	e = tktindparse(tk, &arg, &ix);
2846 	if(e != nil)
2847 		return e;
2848 	return tkvalue(val, "%d.%d", tktlinenum(tkt, &ix), tktlinepos(tkt, &ix));
2849 }
2850 
2851 static char*
2852 tktextinsert(Tk *tk, char *arg, char **val)
2853 {
2854 	int n;
2855 	char *e, *p, *pe;
2856 	TkTindex ins, pins;
2857 	TkTtaginfo *ti;
2858 	TkText *tkt;
2859 	TkTline *lmin;
2860 	TkTop *top;
2861 	TkTitem *tagit;
2862 	char *tbuf, *buf;
2863 
2864 	USED(val);
2865 
2866 	tkt = TKobj(TkText, tk);
2867 	top = tk->env->top;
2868 
2869 	e = tktindparse(tk, &arg, &ins);
2870 	if(e != nil)
2871 		return e;
2872 
2873 	if(ins.item->kind == TkTmark) {
2874 		if(ins.item->imark->gravity == Tkleft) {
2875 			while(ins.item->kind == TkTmark && ins.item->imark->gravity == Tkleft)
2876 				if(!tktadjustind(tkt, TkTbyitem, &ins)) {
2877 					if(tktdbg)
2878 						print("tktextinsert botch\n");
2879 					break;
2880 				}
2881 		}
2882 		else {
2883 			for(;;) {
2884 				pins = ins;
2885 				if(!tktadjustind(tkt, TkTbyitemback, &pins))
2886 					break;
2887 				if(pins.item->kind == TkTmark && pins.item->imark->gravity == Tkright)
2888 					ins = pins;
2889 				else
2890 					break;
2891 			}
2892 		}
2893 	}
2894 
2895 	lmin = tktprevwrapline(tk, ins.line);
2896 
2897 	n = strlen(arg) + 1;
2898 	if(n < Tkmaxitem)
2899 		n = Tkmaxitem;
2900 	tbuf = malloc(n);
2901 	if(tbuf == nil)
2902 		return TkNomem;
2903 	buf = mallocz(Tkmaxitem, 0);
2904 	if(buf == nil) {
2905 		free(tbuf);
2906 		return TkNomem;
2907 	}
2908 
2909 	tagit = nil;
2910 
2911 	while(*arg != '\0') {
2912 		arg = tkword(top, arg, tbuf, tbuf+n, nil);
2913 		if(*arg != '\0') {
2914 			/* tag list spec -- add some slop to tagextra for added tags */
2915 			e = tktnewitem(TkTascii, (tkt->nexttag-1)/32 + 1, &tagit);
2916 			if(e != nil) {
2917 				free(tbuf);
2918 				free(buf);
2919 				return e;
2920 			}
2921 			arg = tkword(top, arg, buf, buf+Tkmaxitem, nil);
2922 			p = buf;
2923 			while(*p) {
2924 				while(*p == ' ') {
2925 					p++;
2926 				}
2927 				if(*p == '\0')
2928 					break;
2929 				pe = strchr(p, ' ');
2930 				if(pe != nil)
2931 					*pe = '\0';
2932 				ti = tktfindtag(tkt->tags, p);
2933 				if(ti == nil) {
2934 					e = tktaddtaginfo(tk, p, &ti);
2935 					if(e != nil) {
2936 						if(tagit != nil)
2937 							free(tagit);
2938 						free(tbuf);
2939 						free(buf);
2940 						return e;
2941 					}
2942 				}
2943 				tkttagbit(tagit, ti->id, 1);
2944 				if(pe == nil)
2945 					break;
2946 				else
2947 					p = pe+1;
2948 			}
2949 		}
2950 		e = tktinsert(tk, &ins, tbuf, tagit);
2951 		if(tagit != nil) {
2952 			free(tagit);
2953 			tagit = nil;
2954 		}
2955 		if(e != nil) {
2956 			free(tbuf);
2957 			free(buf);
2958 			return e;
2959 		}
2960 	}
2961 
2962 	tktfixgeom(tk, lmin, ins.line, 0);
2963 	tktextsize(tk, 1);
2964 
2965 	free(tbuf);
2966 	free(buf);
2967 
2968 	return nil;
2969 }
2970 
2971 static char*
2972 tktextinserti(Tk *tk, char *arg, char **val)
2973 {
2974 	int n;
2975 	TkTline *lmin;
2976 	TkTindex ix, is1, is2;
2977 	TkText *tkt = TKobj(TkText, tk);
2978 	char *tbuf, *buf;
2979 
2980 	USED(val);
2981 
2982 	if(tk->flag&Tkdisabled)
2983 		return nil;
2984 
2985 	buf = mallocz(Tkmaxitem, 0);
2986 	if(buf == nil)
2987 		return TkNomem;
2988 
2989 	tbuf = nil;
2990 	n = strlen(arg) + 1;
2991 	if(n < Tkmaxitem)
2992 		tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
2993 	else {
2994 		tbuf = malloc(n);
2995 		if(tbuf == nil) {
2996 			free(buf);
2997 			return TkNomem;
2998 		}
2999 		tkword(tk->env->top, arg, tbuf, buf+n, nil);
3000 	}
3001 	if(*buf == '\0')
3002 		goto Ret;
3003 	if(!tktmarkind(tk, "insert", &ix)) {
3004 		print("tktextinserti: botch\n");
3005 		goto Ret;
3006 	}
3007 	if(tktgetsel(tk, &is1, &is2)) {
3008 		if(tktindcompare(tkt, &is1, TkLte, &ix) &&
3009 		   tktindcompare(tkt, &is2, TkGte, &ix)) {
3010 			tktextdelete(tk, "sel.first sel.last", nil);
3011 			/* delete might have changed ix item */
3012 			tktmarkind(tk, "insert", &ix);
3013 		}
3014 	}
3015 
3016 	lmin = tktprevwrapline(tk, ix.line);
3017 	tktinsert(tk, &ix, tbuf==nil ? buf : tbuf, 0);
3018 	tktfixgeom(tk, lmin, ix.line, 0);
3019 	if(tktmarkind(tk, "insert", &ix))		/* index doesn't remain valid after fixgeom */
3020 		tktsee(tk, &ix, 0);
3021 	tktextsize(tk, 1);
3022 Ret:
3023 	if(tbuf != nil)
3024 		free(tbuf);
3025 	free(buf);
3026 	return nil;
3027 }
3028 
3029 static char*
3030 tktextmark(Tk *tk, char *arg, char **val)
3031 {
3032 	char *buf;
3033 	TkCmdtab *cmd;
3034 
3035 	buf = mallocz(Tkmaxitem, 0);
3036 	if(buf == nil)
3037 		return TkNomem;
3038 	arg = tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil);
3039 	for(cmd = tktmarkcmd; cmd->name != nil; cmd++) {
3040 		if(strcmp(cmd->name, buf) == 0) {
3041 			free(buf);
3042 			return cmd->fn(tk, arg, val);
3043 		}
3044 	}
3045 	free(buf);
3046 	return TkBadcm;
3047 }
3048 
3049 static char*
3050 tktextscan(Tk *tk, char *arg, char **val)
3051 {
3052 	char *e;
3053 	int mark, x, y, xmax, ymax, vh, vw;
3054 	Point p, odeltatv;
3055 	char buf[Tkmaxitem];
3056 	TkText *tkt = TKobj(TkText, tk);
3057 
3058 	USED(val);
3059 
3060 	arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
3061 
3062 	if(strcmp(buf, "mark") == 0)
3063 		mark = 1;
3064 	else
3065 	if(strcmp(buf, "dragto") == 0)
3066 		mark = 0;
3067 	else
3068 		return TkBadcm;
3069 
3070 	e = tkxyparse(tk, &arg, &p);
3071 	if(e != nil)
3072 		return e;
3073 
3074 	if(mark)
3075 		tkt->track = p;
3076 	else {
3077 		odeltatv = tkt->deltatv;
3078 		vw = tk->act.width - tk->ipad.x;
3079 		vh = tk->act.height - tk->ipad.y;
3080 		ymax = tkt->end.prev->orig.y + tkt->end.prev->height - vh;
3081 		y = tkt->deltatv.y -10*(p.y - tkt->track.y);
3082 		if(y > ymax)
3083 			y = ymax;
3084 		if(y < 0)
3085 			y = 0;
3086 		tkt->deltatv.y = y;
3087 		e = tktsetscroll(tk, Tkvertical);
3088 		if(e != nil)
3089 			return e;
3090 		if(tkt->opts[TkTwrap] == Tkwrapnone) {
3091 			xmax = tktmaxwid(tkt->start.next) - vw;
3092 			x = tkt->deltatv.x - 10*(p.x - tkt->track.x);
3093 			if(x > xmax)
3094 				x = xmax;
3095 			if(x < 0)
3096 				x = 0;
3097 			tkt->deltatv.x = x;
3098 			e = tktsetscroll(tk, Tkhorizontal);
3099 			if(e != nil)
3100 				return e;
3101 		}
3102 		tktfixscroll(tk, odeltatv);
3103 		tkt->track = p;
3104 	}
3105 
3106 	return nil;
3107 }
3108 
3109 static char*
3110 tktextscrollpages(Tk *tk, char *arg, char **val)
3111 {
3112 	TkText *tkt = TKobj(TkText, tk);
3113 
3114 	USED(tkt);
3115 	USED(arg);
3116 	USED(val);
3117 	return nil;
3118 }
3119 
3120 static char*
3121 tktextsearch(Tk *tk, char *arg, char **val)
3122 {
3123 	int i, n;
3124 	Rune r;
3125 	char *e, *s;
3126 	int wrap, fwd, nocase;
3127 	TkText *tkt;
3128 	TkTindex ix1, ix2, ixstart, ixend, tx;
3129 	char buf[Tkmaxitem];
3130 
3131 	tkt = TKobj(TkText, tk);
3132 
3133 	fwd = 1;
3134 	nocase = 0;
3135 
3136 	while(*arg != '\0') {
3137 		arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
3138 		if(*buf != '-')
3139 			break;
3140 		if(strcmp(buf, "-backwards") == 0)
3141 			fwd = 0;
3142 		else if(strcmp(buf, "-nocase") == 0)
3143 			nocase = 1;
3144 		else if(strcmp(buf, "--") == 0) {
3145 			arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
3146 			break;
3147 		}
3148 	}
3149 
3150 	tktstartind(tkt, &ixstart);
3151 	tktadjustind(tkt, TkTbycharstart, &ixstart);
3152 	tktendind(tkt, &ixend);
3153 
3154 	if(*arg == '\0')
3155 		return TkOparg;
3156 
3157 	e = tktindparse(tk, &arg, &ix1);
3158 	if(e != nil)
3159  		return e;
3160 	tktadjustind(tkt, fwd? TkTbycharstart : TkTbycharback, &ix1);
3161 
3162 	if(*arg != '\0') {
3163 		wrap = 0;
3164 		e = tktindparse(tk, &arg, &ix2);
3165 		if(e != nil)
3166 			return e;
3167 		if(!fwd)
3168 			tktadjustind(tkt, TkTbycharback, &ix2);
3169 	}
3170 	else {
3171 		wrap = 1;
3172 		if(fwd) {
3173 			if(tktindcompare(tkt, &ix1, TkEq, &ixstart))
3174 				ix2 = ixend;
3175 			else {
3176 				ix2 = ix1;
3177 				tktadjustind(tkt, TkTbycharback, &ix2);
3178 			}
3179 		}
3180 		else {
3181 			if(tktindcompare(tkt, &ix1, TkEq, &ixend))
3182 				ix2 = ixstart;
3183 			else {
3184 				ix2 = ix1;
3185 				tktadjustind(tkt, TkTbychar, &ix2);
3186 			}
3187 		}
3188 	}
3189 	tktadjustind(tkt, TkTbycharstart, &ix2);
3190 	if(tktindcompare(tkt, &ix1, TkEq, &ix2))
3191 		return nil;
3192 
3193 	if(*buf == '\0')
3194 		return tkvalue(val, "%d.%d", tktlinenum(tkt, &ix1), tktlinepos(tkt, &ix1));
3195 
3196 	while(!(ix1.item == ix2.item && ix1.pos == ix2.pos)) {
3197 		tx = ix1;
3198 		for(i = 0; buf[i] != '\0'; i++) {
3199 			switch(tx.item->kind) {
3200 			case TkTascii:
3201 				if(!tktcmatch(tx.item->istring[tx.pos], buf[i], nocase))
3202 					goto nomatch;
3203 				break;
3204 			case TkTrune:
3205 				s = tx.item->istring;
3206 				s += tktutfpos(s, tx.pos);
3207 				n = chartorune(&r, s);
3208 				if(strncmp(s, buf+i, n) != 0)
3209 					goto nomatch;
3210 				i += n-1;
3211 				break;
3212 			case TkTtab:
3213 				if(buf[i] != '\t')
3214 					goto nomatch;
3215 				break;
3216 			case TkTnewline:
3217 				if(buf[i] != '\n')
3218 					goto nomatch;
3219 				break;
3220 			default:
3221 				goto nomatch;
3222 			}
3223 			tktadjustind(tkt, TkTbychar, &tx);
3224 		}
3225 		return tkvalue(val, "%d.%d", tktlinenum(tkt, &ix1), tktlinepos(tkt, &ix1));
3226 	nomatch:
3227 		if(fwd) {
3228 			if(!tktadjustind(tkt, TkTbychar, &ix1)) {
3229 				if(!wrap)
3230 					break;
3231 				ix1 = ixstart;
3232 			}
3233 		}
3234 		else {
3235 			if(!tktadjustind(tkt, TkTbycharback, &ix1)) {
3236 				if(!wrap)
3237 					break;
3238 				ix1 = ixend;
3239 			}
3240 		}
3241 	}
3242 
3243 	return nil;
3244 }
3245 
3246 char*
3247 tktextselection(Tk *tk, char *arg, char **val)
3248 {
3249 	USED(val);
3250 	if (strcmp(arg, " clear") == 0) {
3251 		tktclearsel(tk);
3252 		return nil;
3253 	}
3254 	else
3255 		return TkBadcm;
3256 }
3257 
3258 static void
3259 doselectto(Tk *tk, Point p, int dbl)
3260 {
3261 	int halfway;
3262 	TkTindex cur, insert, first, last;
3263 	TkText *tkt = TKobj(TkText, tk);
3264 	tktclearsel(tk);
3265 
3266 	halfway = tktxyind(tk, p.x, p.y, &cur);
3267 
3268 	if(!dbl) {
3269 		if(!tktmarkind(tk, "insert", &insert))
3270 			insert = cur;
3271 
3272 		if(tktindcompare(tkt, &cur, TkLt, &insert)) {
3273 			first = cur;
3274 			last = insert;
3275 		}
3276 		else {
3277 			first = insert;
3278 			last = cur;
3279 			if(halfway)
3280 				tktadjustind(tkt, TkTbychar, &last);
3281 			if(last.line == &tkt->end)
3282 				tktadjustind(tkt, TkTbycharback, &last);
3283 			if(tktindcompare(tkt, &first, TkGte, &last))
3284 				return;
3285 			cur = last;
3286 		}
3287 		tktsee(tk, &cur, 0);
3288 	}
3289 	else {
3290 		first = cur;
3291 		last = cur;
3292 		tktdoubleclick(tkt, &first, &last);
3293 	}
3294 
3295 	tkttagchange(tk, TkTselid, &first, &last, 1);
3296 }
3297 
3298 static void
3299 autoselect(Tk *tk, void *v, int cancelled)
3300 {
3301 	TkText *tkt = TKobj(TkText, tk);
3302 	Rectangle hitr;
3303 	Point p;
3304 	USED(v);
3305 
3306 	if (cancelled)
3307 		return;
3308 
3309 	p = scr2local(tk, tkt->track);
3310 	if (tkvisiblerect(tk, &hitr) && ptinrect(p, hitr))
3311 		return;
3312 	doselectto(tk, p, 0);
3313 	tkdirty(tk);
3314 	tkupdate(tk->env->top);
3315 }
3316 
3317 static char*
3318 tktextselectto(Tk *tk, char *arg, char **val)
3319 {
3320 	int dbl;
3321 	char *e;
3322 	Point p;
3323 	Rectangle hitr;
3324 	TkText *tkt = TKobj(TkText, tk);
3325 
3326 	USED(val);
3327 
3328 	if(tkt->tflag & (TkTjustfoc|TkTnodrag))
3329 		return nil;
3330 
3331 	e = tkxyparse(tk, &arg, &p);
3332 	if(e != nil)
3333 		return e;
3334 	tkt->track = p;
3335 	p = scr2local(tk, p);
3336 
3337 	arg = tkskip(arg, " ");
3338 	if(*arg == 'd') {
3339 		tkcancelrepeat(tk);
3340 		dbl = 1;
3341 		tkt->tflag |= TkTnodrag;
3342 	} else {
3343 		dbl = 0;
3344 		if (!tkvisiblerect(tk, &hitr) || !ptinrect(p, hitr))
3345 			return nil;
3346 	}
3347 	doselectto(tk, p, dbl);
3348 	return nil;
3349 }
3350 
3351 static char tktleft1[] = "{[(<";
3352 static char tktright1[] = "}])>";
3353 static char tktleft2[] = "\n";
3354 static char tktleft3[] = "\'\"`";
3355 
3356 static char *tktleft[] = {tktleft1, tktleft2, tktleft3, nil};
3357 static char *tktright[] = {tktright1,  tktleft2, tktleft3, nil};
3358 
3359 static void
3360 tktdoubleclick(TkText *tkt, TkTindex *first, TkTindex *last)
3361 {
3362 	int c, i;
3363 	TkTindex ix, ix2;
3364 	char *r, *l, *p;
3365 
3366 	for(i = 0; tktleft[i] != nil; i++) {
3367 		ix = *first;
3368 		l = tktleft[i];
3369 		r = tktright[i];
3370 		/* try matching character to left, looking right */
3371 		ix2 = ix;
3372 		if(!tktadjustind(tkt, TkTbycharback, &ix2))
3373 			c = '\n';
3374 		else
3375 			c = tktindrune(&ix2);
3376 		p = strchr(l, c);
3377 		if(p != nil) {
3378 			if(tktclickmatch(tkt, c, r[p-l], 1, &ix)) {
3379 				*last = ix;
3380 				if(c != '\n')
3381 					tktadjustind(tkt, TkTbycharback, last);
3382 			}
3383 			return;
3384 		}
3385 		/* try matching character to right, looking left */
3386 		c = tktindrune(&ix);
3387 		p = strchr(r, c);
3388 		if(p != nil) {
3389 			if(tktclickmatch(tkt, c, l[p-r], -1, &ix)) {
3390 				*last = *first;
3391 				if(c == '\n')
3392 					tktadjustind(tkt, TkTbychar, last);
3393 				*first = ix;
3394 				if(!(c=='\n' && ix.line == tkt->start.next && ix.item == ix.line->items))
3395 					tktadjustind(tkt, TkTbychar, first);
3396 			}
3397 			return;
3398 		}
3399 	}
3400 	/* try filling out word to right */
3401 	while(tkiswordchar(tktindrune(last))) {
3402 		if(!tktadjustind(tkt, TkTbychar, last))
3403 			break;
3404 	}
3405 	/* try filling out word to left */
3406 	for(;;) {
3407 		ix = *first;
3408 		if(!tktadjustind(tkt, TkTbycharback, &ix))
3409 			break;
3410 		if(!tkiswordchar(tktindrune(&ix)))
3411 			break;
3412 		*first = ix;
3413 	}
3414 }
3415 
3416 static int
3417 tktclickmatch(TkText *tkt, int cl, int cr, int dir, TkTindex *ix)
3418 {
3419 	int c, nest, atend;
3420 
3421 	nest = 1;
3422 	atend = 0;
3423 	for(;;) {
3424 		if(dir > 0) {
3425 			if(atend)
3426 				break;
3427 			c = tktindrune(ix);
3428 			atend = !tktadjustind(tkt, TkTbychar, ix);
3429 		} else {
3430 			if(!tktadjustind(tkt, TkTbycharback, ix))
3431 				break;
3432 			c = tktindrune(ix);
3433 		}
3434 		if(c == cr){
3435 			if(--nest==0)
3436 				return 1;
3437 		}else if(c == cl)
3438 			nest++;
3439 	}
3440 	return cl=='\n' && nest==1;
3441 }
3442 
3443 /*
3444  * return the line before line l, unless word wrap is on,
3445  * (for the first word of line l), in which case return the last non-empty line before that.
3446  * tktgeom might then combine the end of that line with the start of the insertion
3447  * (unless there is a newline in the way).
3448  */
3449 TkTline*
3450 tktprevwrapline(Tk *tk, TkTline *l)
3451 {
3452 	TkTitem *i;
3453 	int *opts, wrapmode;
3454 	TkText *tkt = TKobj(TkText, tk);
3455 	TkEnv env;
3456 
3457 	if(l == nil)
3458 		return nil;
3459 	/* some spacing depends on tags of first non-mark on display line */
3460 	for(i = l->items; i != nil; i = i->next)
3461 		if(i->kind != TkTmark && i->kind != TkTcontline)
3462 			break;
3463 	if(i == nil || i->kind == TkTnewline)	/* can't use !tkanytags(i) because it doesn't check env */
3464 		return l->prev;
3465 	opts = mallocz(TkTnumopts*sizeof(int), 0);
3466 	if(opts == nil)
3467 		return l->prev;	/* in worst case gets word wrap wrong */
3468 	tkttagopts(tk, i, opts, &env, nil, 1);
3469 	wrapmode = opts[TkTwrap];
3470 	free(opts);
3471 	if(wrapmode != Tkwrapword)
3472 		return l->prev;
3473 	if(l->prev != &tkt->start)
3474 		l = l->prev;	/* having been processed by tktgeom, shouldn't have extraneous marks etc */
3475 	return l->prev;
3476 }
3477 
3478 static char*
3479 tktextsetcursor(Tk *tk, char *arg, char **val)
3480 {
3481 	char *e;
3482 	TkTindex ix;
3483 	TkTmarkinfo *mi;
3484 	TkText *tkt = TKobj(TkText, tk);
3485 
3486 	USED(val);
3487 
3488 	/* do clearsel here, because it can change indices */
3489 	tktclearsel(tk);
3490 
3491 	e = tktindparse(tk, &arg, &ix);
3492 	if(e != nil)
3493 		return e;
3494 
3495 	mi = tktfindmark(tkt->marks, "insert");
3496 	if(tktdbg && mi == nil) {
3497 		print("tktextsetcursor: botch\n");
3498 		return nil;
3499 	}
3500 	tktmarkmove(tk, mi, &ix);
3501 	tktsee(tk, &ix, 0);
3502 	return nil;
3503 }
3504 
3505 static char*
3506 tktexttag(Tk *tk, char *arg, char **val)
3507 {
3508 	char *buf;
3509 	TkCmdtab *cmd;
3510 
3511 	buf = mallocz(Tkmaxitem, 0);
3512 	if(buf == nil)
3513 		return TkNomem;
3514 	arg = tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil);
3515 	for(cmd = tkttagcmd; cmd->name != nil; cmd++) {
3516 		if(strcmp(cmd->name, buf) == 0) {
3517 			free(buf);
3518 			return cmd->fn(tk, arg, val);
3519 		}
3520 	}
3521 	free(buf);
3522 	return TkBadcm;
3523 }
3524 
3525 static char*
3526 tktextwindow(Tk *tk, char *arg, char **val)
3527 {
3528 	char buf[Tkmaxitem];
3529 	TkCmdtab *cmd;
3530 
3531 	arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
3532 	for(cmd = tktwincmd; cmd->name != nil; cmd++) {
3533 		if(strcmp(cmd->name, buf) == 0)
3534 			return cmd->fn(tk, arg, val);
3535 	}
3536 	return TkBadcm;
3537 }
3538 
3539 static char*
3540 tktextxview(Tk *tk, char *arg, char **val)
3541 {
3542 	int ntot, vw;
3543 	char *e;
3544 	Point odeltatv;
3545 	TkText *tkt = TKobj(TkText, tk);
3546 
3547 	odeltatv = tkt->deltatv;
3548 	vw = tk->act.width - tk->ipad.x;
3549 	ntot = tktmaxwid(tkt->start.next);
3550 	if(ntot < tkt->deltatv.x +vw)
3551 		ntot = tkt->deltatv.x + vw;
3552 	e = tktview(tk, arg, val, vw, &tkt->deltatv.x, ntot, Tkhorizontal);
3553 	if(e == nil) {
3554 		e = tktsetscroll(tk, Tkhorizontal);
3555 		if(e == nil)
3556 			tktfixscroll(tk, odeltatv);
3557 	}
3558 	return e;
3559 }
3560 
3561 static int
3562 istext(TkTline *l)
3563 {
3564 	TkTitem *i;
3565 
3566 	for(i = l->items; i != nil; i = i->next)
3567 		if(i->kind == TkTwin || i->kind == TkTmark)
3568 			return 0;
3569 	return 1;
3570 }
3571 
3572 static void
3573 tkadjpage(Tk *tk, int ody, int *dy)
3574 {
3575 	int y, a, b, d;
3576 	TkTindex ix;
3577 	TkTline *l;
3578 
3579 	d = *dy-ody;
3580 	y = d > 0 ? tk->act.height : 0;
3581 	tktxyind(tk, 0, y-d, &ix);
3582 	if((l = ix.line) != nil && istext(l)){
3583 		a = l->orig.y;
3584 		b = a+l->height;
3585 /* print("AP: %d %d %d (%d+%d)\n", a, ody+y, b, ody, y); */
3586 		if(a+2 < ody+y && ody+y < b-2){	/* partially obscured line */
3587 			if(d > 0)
3588 				*dy -= ody+y-a;
3589 			else
3590 				*dy += b-ody;
3591 		}
3592 	}
3593 }
3594 
3595 static char*
3596 tktextyview(Tk *tk, char *arg, char **val)
3597 {
3598 	int ntot, vh, d;
3599 	char *e;
3600 	TkTline *l;
3601 	Point odeltatv;
3602 	TkTindex ix;
3603 	TkText *tkt = TKobj(TkText, tk);
3604 	char buf[Tkmaxitem], *v;
3605 
3606 	if(*arg != '\0') {
3607 		v = tkitem(buf, arg);
3608 		if(strcmp(buf, "-pickplace") == 0)
3609 			return tktextsee(tk,v, val);
3610 		if(strcmp(buf, "moveto") != 0 && strcmp(buf, "scroll") != 0) {
3611 			e = tktindparse(tk, &arg, &ix);
3612 			if(e != nil)
3613 				return e;
3614 			tktsee(tk, &ix, 1);
3615 			return nil;
3616 		}
3617 	}
3618 	odeltatv = tkt->deltatv;
3619 	vh = tk->act.height;
3620 	l =  tkt->end.prev;
3621 	ntot = l->orig.y + l->height;
3622 //	if(ntot < tkt->deltatv.y + vh)
3623 //		ntot = tkt->deltatv.y + vh;
3624 	e = tktview(tk, arg, val, vh, &tkt->deltatv.y, ntot, Tkvertical);
3625 	d = tkt->deltatv.y-odeltatv.y;
3626 	if(d == vh || d == -vh)
3627 		tkadjpage(tk, odeltatv.y, &tkt->deltatv.y);
3628 	if(e == nil) {
3629 		e = tktsetscroll(tk, Tkvertical);
3630 		if(e == nil)
3631 			tktfixscroll(tk, odeltatv);
3632 	}
3633 	return e;
3634 }
3635 static void
3636 tktextfocusorder(Tk *tk)
3637 {
3638 	TkTindex ix;
3639 	TkText *t;
3640 	Tk *isub;
3641 
3642 	t = TKobj(TkText, tk);
3643 	tktstartind(t, &ix);
3644 	do {
3645 		if(ix.item->kind == TkTwin) {
3646 			isub = ix.item->iwin->sub;
3647 			if(isub != nil)
3648 				tkappendfocusorder(isub);
3649 		}
3650 	} while(tktadjustind(t, TkTbyitem, &ix));
3651 }
3652 
3653 TkCmdtab tktextcmd[] =
3654 {
3655 	"bbox",			tktextbbox,
3656 	"cget",			tktextcget,
3657 	"compare",		tktextcompare,
3658 	"configure",		tktextconfigure,
3659 	"debug",		tktextdebug,
3660 	"delete",		tktextdelete,
3661 	"dlineinfo",		tktextdlineinfo,
3662 	"dump",			tktextdump,
3663 	"get",			tktextget,
3664 	"index",		tktextindex,
3665 	"insert",		tktextinsert,
3666 	"mark",			tktextmark,
3667 	"scan",			tktextscan,
3668 	"search",		tktextsearch,
3669 	"see",			tktextsee,
3670 	"selection",		tktextselection,
3671 	"tag",			tktexttag,
3672 	"window",		tktextwindow,
3673 	"xview",		tktextxview,
3674 	"yview",		tktextyview,
3675 	"tkTextButton1",	tktextbutton1,
3676 	"tkTextButton1R",	tktextbutton1r,
3677 	"tkTextDelIns",		tktextdelins,
3678 	"tkTextInsert",		tktextinserti,
3679 	"tkTextSelectTo",	tktextselectto,
3680 	"tkTextSetCursor",	tktextsetcursor,
3681 	"tkTextScrollPages",	tktextscrollpages,
3682 	"tkTextCursor",		tktextcursor,
3683 	nil
3684 };
3685 
3686 TkMethod textmethod = {
3687 	"text",
3688 	tktextcmd,
3689 	tkfreetext,
3690 	tkdrawtext,
3691 	tktextgeom,
3692 	nil,
3693 	tktextfocusorder,
3694 	tktdirty,
3695 	tktrelpos,
3696 	tktextevent,
3697 	nil,				/* XXX need to implement textsee */
3698 	tktinwindow,
3699 	nil,
3700 	tktxtforgetsub,
3701 };
3702