xref: /inferno-os/libtk/textw.c (revision c9c0d12ef55c878b0e361f9f6936bbb4c67b40fb)
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 delete insert"},
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 +c"},
109 	{TkKey|CNTL('k'),	"%W delete insert {insert lineend}"},
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
2617 			tktextdelete(tk, "insert", nil);
2618 		tktextsee(tk, "insert", nil);
2619 	}
2620 	return nil;
2621 }
2622 
2623 static char*
2624 tktextdlineinfo(Tk *tk, char *arg, char **val)
2625 {
2626 	char *e;
2627 	TkTindex ix;
2628 	TkTline *l;
2629 	Point p;
2630 	int vh;
2631 	TkText *tkt = TKobj(TkText, tk);
2632 
2633 	e = tktindparse(tk, &arg, &ix);
2634 	if(e != nil)
2635 		return e;
2636 
2637 	l = ix.line;
2638 	vh = tk->act.height;
2639 
2640 	/* get p in V space */
2641 	p = subpt(l->orig, tkt->deltatv);
2642 	if(p.y+l->height < 0 || p.y >= vh)
2643 		return nil;
2644 
2645 	return tkvalue(val, "%d %d %d %d %d",
2646 		p.x, p.y, l->width, l->height, l->ascent);
2647 }
2648 
2649 static char*
2650 tktextdump(Tk *tk, char *arg, char **val)
2651 {
2652 	TkTline *l;
2653 	TkTitem *i;
2654 	Fmt fmt;
2655 	TkText *tkt;
2656 	TkDump tkdump;
2657 	TkOptab tko[2];
2658 	TkTtaginfo *ti;
2659 	TkName *names, *n;
2660 	char *e, *win, *p;
2661 	TkTindex ix1, ix2;
2662 	int r, j, numitems;
2663 	ulong fg, bg;
2664 
2665 	tkt = TKobj(TkText, tk);
2666 
2667 
2668 	tkdump.sgml = 0;
2669 	tkdump.metrics = 0;
2670 
2671 	tko[0].ptr = &tkdump;
2672 	tko[0].optab = dumpopts;
2673 	tko[1].ptr = nil;
2674 	names = nil;
2675 	e = tkparse(tk->env->top, arg, tko, &names);
2676 	if(e != nil)
2677 		return e;
2678 
2679 	if(names != nil) {			/* supplied indices */
2680 		p = names->name;
2681 		e = tktindparse(tk, &p, &ix1);
2682 		if(e != nil) {
2683 			tkfreename(names);
2684 			return e;
2685 		}
2686 		n = names->link;
2687 		if(n != nil) {
2688 			p = n->name;
2689 			e = tktindparse(tk, &p, &ix2);
2690 			if(e != nil) {
2691 				tkfreename(names);
2692 				return e;
2693 			}
2694 		}
2695 		else {
2696 			ix2 = ix1;
2697 			tktadjustind(tkt, TkTbychar, &ix2);
2698 		}
2699 		tkfreename(names);
2700 		if(!tktindbefore(&ix1, &ix2))
2701 			return nil;
2702 	}
2703 	else
2704 		return TkBadix;
2705 
2706 	if(tkdump.metrics != 0) {
2707 		fmtstrinit(&fmt);
2708 		if(fmtprint(&fmt, "%%Fonts\n") < 0)
2709 			return TkNomem;
2710 		for(ti=tkt->tags; ti != nil; ti=ti->next) {
2711 			if(ti->env == nil || ti->env->font == nil)
2712 				continue;
2713 			if(fmtprint(&fmt, "%d::%s\n", ti->id,ti->env->font->name) < 0)
2714 				return TkNomem;
2715 		}
2716 		if(fmtprint(&fmt, "-1::%s\n%%Colors\n", tk->env->font->name) < 0)
2717 			return TkNomem;
2718 		for(ti=tkt->tags; ti != nil; ti=ti->next) {
2719 			if(ti->env == nil)
2720 				continue;
2721 			bg = ti->env->colors[TkCbackgnd];
2722 			fg = ti->env->colors[TkCforegnd];
2723 			if(bg == tk->env->colors[TkCbackgnd] &&
2724 			   fg == ti->env->colors[TkCforegnd])
2725 				continue;
2726 			r = fmtprint(&fmt,"%d::#%.8lux\n", ti->id, bg);
2727 			if(r < 0)
2728 				return TkNomem;
2729 			r = fmtprint(&fmt,"%d::#%.8lux\n", ti->id, fg);
2730 			if(r < 0)
2731 				return TkNomem;
2732 		}
2733 		if(fmtprint(&fmt, "%%Lines\n") < 0)
2734 			return TkNomem;
2735 
2736 		/*
2737 		 * In 'metrics' format lines are recorded in the following way:
2738 		 *    xorig yorig wd ht as [data]
2739 		 * where data is of the form:
2740 		 *    CodeWidth{tags} data
2741 		 * For Example;
2742 		 *    A200{200000} Hello World!
2743 		 * denotes an A(scii) contiguous string of 200 pixels with
2744 		 * bit 20 set in its tags which corresponds to some font.
2745 		 *
2746 	 	*/
2747 		if(ix2.line->items != ix2.item)
2748 			ix2.line = ix2.line->next;
2749 		for(l = ix1.line; l != ix2.line; l = l->next) {
2750 			numitems = 0;
2751 			for(i = l->items; i != nil; i = i->next) {
2752 				if(i->kind != TkTmark)
2753 					numitems++;
2754 			}
2755 			r = fmtprint(&fmt, "%d %d %d %d %d %d ",
2756 				l->orig.x, l->orig.y, l->width, l->height, l->ascent,numitems);
2757 			if(r < 0)
2758 				return TkNomem;
2759 			for(i = l->items; i != nil; i = i->next) {
2760 				switch(i->kind) {
2761 				case TkTascii:
2762 				case TkTrune:
2763 					r = i->kind == TkTascii ? 'A' : 'R';
2764 					if(fmtprint(&fmt,"[%c%d{", r, i->width) < 0)
2765 						return TkNomem;
2766 					if(i->tags !=0 || i->tagextra !=0) {
2767 						if(fmtprint(&fmt,"%lux", i->tags[0]) < 0)
2768 							return TkNomem;
2769 						for(j=0; j < i->tagextra; j++)
2770 							if(fmtprint(&fmt,"::%lux", i->tags[j+1]) < 0)
2771 								return TkNomem;
2772 					}
2773 					/* XXX string should be quoted to avoid embedded ']'s */
2774 					if(fmtprint(&fmt,"}%s]", i->istring) < 0)
2775 						return TkNomem;
2776 					break;
2777 				case TkTnewline:
2778 				case TkTcontline:
2779 					r = i->kind == TkTnewline ? 'N' : 'C';
2780 					if(fmtprint(&fmt, "[%c]", r) < 0)
2781 						return TkNomem;
2782 					break;
2783 				case TkTtab:
2784 					if(fmtprint(&fmt,"[T%d]",i->width) < 0)
2785 						return TkNomem;
2786 					break;
2787 				case TkTwin:
2788 					win = "<null>";
2789 					if(i->iwin->sub != nil)
2790 						win = i->iwin->sub->name->name;
2791 					if(fmtprint(&fmt,"[W%d %s]",i->width, win) < 0)
2792 						return TkNomem;
2793 					break;
2794 				}
2795 				if(fmtprint(&fmt, " ") < 0)
2796 					return TkNomem;
2797 
2798 			}
2799 			if(fmtprint(&fmt, "\n") < 0)
2800 				return TkNomem;
2801 			*val = fmtstrflush(&fmt);
2802 			if(*val == nil)
2803 				return TkNomem;
2804 		}
2805 	}
2806 	else
2807 		return tktget(tkt, &ix1, &ix2, tkdump.sgml, val);
2808 
2809 	return nil;
2810 }
2811 
2812 
2813 static char*
2814 tktextget(Tk *tk, char *arg, char **val)
2815 {
2816 	char *e;
2817 	TkTindex ix1, ix2;
2818 	TkText *tkt = TKobj(TkText, tk);
2819 
2820 	e = tktindparse(tk, &arg, &ix1);
2821 	if(e != nil)
2822 		return e;
2823 
2824 	if(*arg != '\0') {
2825 		e = tktindparse(tk, &arg, &ix2);
2826 		if(e != nil)
2827 			return e;
2828 	}
2829 	else {
2830 		ix2 = ix1;
2831 		tktadjustind(tkt, TkTbychar, &ix2);
2832 	}
2833 	return tktget(tkt, &ix1, &ix2, 0, val);
2834 }
2835 
2836 static char*
2837 tktextindex(Tk *tk, char *arg, char **val)
2838 {
2839 	char *e;
2840 	TkTindex ix;
2841 	TkText *tkt = TKobj(TkText, tk);
2842 
2843 	e = tktindparse(tk, &arg, &ix);
2844 	if(e != nil)
2845 		return e;
2846 	return tkvalue(val, "%d.%d", tktlinenum(tkt, &ix), tktlinepos(tkt, &ix));
2847 }
2848 
2849 static char*
2850 tktextinsert(Tk *tk, char *arg, char **val)
2851 {
2852 	int n;
2853 	char *e, *p, *pe;
2854 	TkTindex ins, pins;
2855 	TkTtaginfo *ti;
2856 	TkText *tkt;
2857 	TkTline *lmin;
2858 	TkTop *top;
2859 	TkTitem *tagit;
2860 	char *tbuf, *buf;
2861 
2862 	USED(val);
2863 
2864 	tkt = TKobj(TkText, tk);
2865 	top = tk->env->top;
2866 
2867 	e = tktindparse(tk, &arg, &ins);
2868 	if(e != nil)
2869 		return e;
2870 
2871 	if(ins.item->kind == TkTmark) {
2872 		if(ins.item->imark->gravity == Tkleft) {
2873 			while(ins.item->kind == TkTmark && ins.item->imark->gravity == Tkleft)
2874 				if(!tktadjustind(tkt, TkTbyitem, &ins)) {
2875 					if(tktdbg)
2876 						print("tktextinsert botch\n");
2877 					break;
2878 				}
2879 		}
2880 		else {
2881 			for(;;) {
2882 				pins = ins;
2883 				if(!tktadjustind(tkt, TkTbyitemback, &pins))
2884 					break;
2885 				if(pins.item->kind == TkTmark && pins.item->imark->gravity == Tkright)
2886 					ins = pins;
2887 				else
2888 					break;
2889 			}
2890 		}
2891 	}
2892 
2893 	lmin = tktprevwrapline(tk, ins.line);
2894 
2895 	n = strlen(arg) + 1;
2896 	if(n < Tkmaxitem)
2897 		n = Tkmaxitem;
2898 	tbuf = malloc(n);
2899 	if(tbuf == nil)
2900 		return TkNomem;
2901 	buf = mallocz(Tkmaxitem, 0);
2902 	if(buf == nil) {
2903 		free(tbuf);
2904 		return TkNomem;
2905 	}
2906 
2907 	tagit = nil;
2908 
2909 	while(*arg != '\0') {
2910 		arg = tkword(top, arg, tbuf, tbuf+n, nil);
2911 		if(*arg != '\0') {
2912 			/* tag list spec -- add some slop to tagextra for added tags */
2913 			e = tktnewitem(TkTascii, (tkt->nexttag-1)/32 + 1, &tagit);
2914 			if(e != nil) {
2915 				free(tbuf);
2916 				free(buf);
2917 				return e;
2918 			}
2919 			arg = tkword(top, arg, buf, buf+Tkmaxitem, nil);
2920 			p = buf;
2921 			while(*p) {
2922 				while(*p == ' ') {
2923 					p++;
2924 				}
2925 				if(*p == '\0')
2926 					break;
2927 				pe = strchr(p, ' ');
2928 				if(pe != nil)
2929 					*pe = '\0';
2930 				ti = tktfindtag(tkt->tags, p);
2931 				if(ti == nil) {
2932 					e = tktaddtaginfo(tk, p, &ti);
2933 					if(e != nil) {
2934 						if(tagit != nil)
2935 							free(tagit);
2936 						free(tbuf);
2937 						free(buf);
2938 						return e;
2939 					}
2940 				}
2941 				tkttagbit(tagit, ti->id, 1);
2942 				if(pe == nil)
2943 					break;
2944 				else
2945 					p = pe+1;
2946 			}
2947 		}
2948 		e = tktinsert(tk, &ins, tbuf, tagit);
2949 		if(tagit != nil) {
2950 			free(tagit);
2951 			tagit = nil;
2952 		}
2953 		if(e != nil) {
2954 			free(tbuf);
2955 			free(buf);
2956 			return e;
2957 		}
2958 	}
2959 
2960 	tktfixgeom(tk, lmin, ins.line, 0);
2961 	tktextsize(tk, 1);
2962 
2963 	free(tbuf);
2964 	free(buf);
2965 
2966 	return nil;
2967 }
2968 
2969 static char*
2970 tktextinserti(Tk *tk, char *arg, char **val)
2971 {
2972 	int n;
2973 	TkTline *lmin;
2974 	TkTindex ix, is1, is2;
2975 	TkText *tkt = TKobj(TkText, tk);
2976 	char *tbuf, *buf;
2977 
2978 	USED(val);
2979 
2980 	if(tk->flag&Tkdisabled)
2981 		return nil;
2982 
2983 	buf = mallocz(Tkmaxitem, 0);
2984 	if(buf == nil)
2985 		return TkNomem;
2986 
2987 	tbuf = nil;
2988 	n = strlen(arg) + 1;
2989 	if(n < Tkmaxitem)
2990 		tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
2991 	else {
2992 		tbuf = malloc(n);
2993 		if(tbuf == nil) {
2994 			free(buf);
2995 			return TkNomem;
2996 		}
2997 		tkword(tk->env->top, arg, tbuf, buf+n, nil);
2998 	}
2999 	if(*buf == '\0')
3000 		goto Ret;
3001 	if(!tktmarkind(tk, "insert", &ix)) {
3002 		print("tktextinserti: botch\n");
3003 		goto Ret;
3004 	}
3005 	if(tktgetsel(tk, &is1, &is2)) {
3006 		if(tktindcompare(tkt, &is1, TkLte, &ix) &&
3007 		   tktindcompare(tkt, &is2, TkGte, &ix)) {
3008 			tktextdelete(tk, "sel.first sel.last", nil);
3009 			/* delete might have changed ix item */
3010 			tktmarkind(tk, "insert", &ix);
3011 		}
3012 	}
3013 
3014 	lmin = tktprevwrapline(tk, ix.line);
3015 	tktinsert(tk, &ix, tbuf==nil ? buf : tbuf, 0);
3016 	tktfixgeom(tk, lmin, ix.line, 0);
3017 	if(tktmarkind(tk, "insert", &ix))		/* index doesn't remain valid after fixgeom */
3018 		tktsee(tk, &ix, 0);
3019 	tktextsize(tk, 1);
3020 Ret:
3021 	if(tbuf != nil)
3022 		free(tbuf);
3023 	free(buf);
3024 	return nil;
3025 }
3026 
3027 static char*
3028 tktextmark(Tk *tk, char *arg, char **val)
3029 {
3030 	char *buf;
3031 	TkCmdtab *cmd;
3032 
3033 	buf = mallocz(Tkmaxitem, 0);
3034 	if(buf == nil)
3035 		return TkNomem;
3036 	arg = tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil);
3037 	for(cmd = tktmarkcmd; cmd->name != nil; cmd++) {
3038 		if(strcmp(cmd->name, buf) == 0) {
3039 			free(buf);
3040 			return cmd->fn(tk, arg, val);
3041 		}
3042 	}
3043 	free(buf);
3044 	return TkBadcm;
3045 }
3046 
3047 static char*
3048 tktextscan(Tk *tk, char *arg, char **val)
3049 {
3050 	char *e;
3051 	int mark, x, y, xmax, ymax, vh, vw;
3052 	Point p, odeltatv;
3053 	char buf[Tkmaxitem];
3054 	TkText *tkt = TKobj(TkText, tk);
3055 
3056 	USED(val);
3057 
3058 	arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
3059 
3060 	if(strcmp(buf, "mark") == 0)
3061 		mark = 1;
3062 	else
3063 	if(strcmp(buf, "dragto") == 0)
3064 		mark = 0;
3065 	else
3066 		return TkBadcm;
3067 
3068 	e = tkxyparse(tk, &arg, &p);
3069 	if(e != nil)
3070 		return e;
3071 
3072 	if(mark)
3073 		tkt->track = p;
3074 	else {
3075 		odeltatv = tkt->deltatv;
3076 		vw = tk->act.width - tk->ipad.x;
3077 		vh = tk->act.height - tk->ipad.y;
3078 		ymax = tkt->end.prev->orig.y + tkt->end.prev->height - vh;
3079 		y = tkt->deltatv.y -10*(p.y - tkt->track.y);
3080 		if(y > ymax)
3081 			y = ymax;
3082 		if(y < 0)
3083 			y = 0;
3084 		tkt->deltatv.y = y;
3085 		e = tktsetscroll(tk, Tkvertical);
3086 		if(e != nil)
3087 			return e;
3088 		if(tkt->opts[TkTwrap] == Tkwrapnone) {
3089 			xmax = tktmaxwid(tkt->start.next) - vw;
3090 			x = tkt->deltatv.x - 10*(p.x - tkt->track.x);
3091 			if(x > xmax)
3092 				x = xmax;
3093 			if(x < 0)
3094 				x = 0;
3095 			tkt->deltatv.x = x;
3096 			e = tktsetscroll(tk, Tkhorizontal);
3097 			if(e != nil)
3098 				return e;
3099 		}
3100 		tktfixscroll(tk, odeltatv);
3101 		tkt->track = p;
3102 	}
3103 
3104 	return nil;
3105 }
3106 
3107 static char*
3108 tktextscrollpages(Tk *tk, char *arg, char **val)
3109 {
3110 	TkText *tkt = TKobj(TkText, tk);
3111 
3112 	USED(tkt);
3113 	USED(arg);
3114 	USED(val);
3115 	return nil;
3116 }
3117 
3118 static char*
3119 tktextsearch(Tk *tk, char *arg, char **val)
3120 {
3121 	int i, n;
3122 	Rune r;
3123 	char *e, *s;
3124 	int wrap, fwd, nocase;
3125 	TkText *tkt;
3126 	TkTindex ix1, ix2, ixstart, ixend, tx;
3127 	char buf[Tkmaxitem];
3128 
3129 	tkt = TKobj(TkText, tk);
3130 
3131 	fwd = 1;
3132 	nocase = 0;
3133 
3134 	while(*arg != '\0') {
3135 		arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
3136 		if(*buf != '-')
3137 			break;
3138 		if(strcmp(buf, "-backwards") == 0)
3139 			fwd = 0;
3140 		else if(strcmp(buf, "-nocase") == 0)
3141 			nocase = 1;
3142 		else if(strcmp(buf, "--") == 0) {
3143 			arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
3144 			break;
3145 		}
3146 	}
3147 
3148 	tktstartind(tkt, &ixstart);
3149 	tktadjustind(tkt, TkTbycharstart, &ixstart);
3150 	tktendind(tkt, &ixend);
3151 
3152 	if(*arg == '\0')
3153 		return TkOparg;
3154 
3155 	e = tktindparse(tk, &arg, &ix1);
3156 	if(e != nil)
3157  		return e;
3158 	tktadjustind(tkt, fwd? TkTbycharstart : TkTbycharback, &ix1);
3159 
3160 	if(*arg != '\0') {
3161 		wrap = 0;
3162 		e = tktindparse(tk, &arg, &ix2);
3163 		if(e != nil)
3164 			return e;
3165 		if(!fwd)
3166 			tktadjustind(tkt, TkTbycharback, &ix2);
3167 	}
3168 	else {
3169 		wrap = 1;
3170 		if(fwd) {
3171 			if(tktindcompare(tkt, &ix1, TkEq, &ixstart))
3172 				ix2 = ixend;
3173 			else {
3174 				ix2 = ix1;
3175 				tktadjustind(tkt, TkTbycharback, &ix2);
3176 			}
3177 		}
3178 		else {
3179 			if(tktindcompare(tkt, &ix1, TkEq, &ixend))
3180 				ix2 = ixstart;
3181 			else {
3182 				ix2 = ix1;
3183 				tktadjustind(tkt, TkTbychar, &ix2);
3184 			}
3185 		}
3186 	}
3187 	tktadjustind(tkt, TkTbycharstart, &ix2);
3188 	if(tktindcompare(tkt, &ix1, TkEq, &ix2))
3189 		return nil;
3190 
3191 	if(*buf == '\0')
3192 		return tkvalue(val, "%d.%d", tktlinenum(tkt, &ix1), tktlinepos(tkt, &ix1));
3193 
3194 	while(!(ix1.item == ix2.item && ix1.pos == ix2.pos)) {
3195 		tx = ix1;
3196 		for(i = 0; buf[i] != '\0'; i++) {
3197 			switch(tx.item->kind) {
3198 			case TkTascii:
3199 				if(!tktcmatch(tx.item->istring[tx.pos], buf[i], nocase))
3200 					goto nomatch;
3201 				break;
3202 			case TkTrune:
3203 				s = tx.item->istring;
3204 				s += tktutfpos(s, tx.pos);
3205 				n = chartorune(&r, s);
3206 				if(strncmp(s, buf+i, n) != 0)
3207 					goto nomatch;
3208 				i += n-1;
3209 				break;
3210 			case TkTtab:
3211 				if(buf[i] != '\t')
3212 					goto nomatch;
3213 				break;
3214 			case TkTnewline:
3215 				if(buf[i] != '\n')
3216 					goto nomatch;
3217 				break;
3218 			default:
3219 				goto nomatch;
3220 			}
3221 			tktadjustind(tkt, TkTbychar, &tx);
3222 		}
3223 		return tkvalue(val, "%d.%d", tktlinenum(tkt, &ix1), tktlinepos(tkt, &ix1));
3224 	nomatch:
3225 		if(fwd) {
3226 			if(!tktadjustind(tkt, TkTbychar, &ix1)) {
3227 				if(!wrap)
3228 					break;
3229 				ix1 = ixstart;
3230 			}
3231 		}
3232 		else {
3233 			if(!tktadjustind(tkt, TkTbycharback, &ix1)) {
3234 				if(!wrap)
3235 					break;
3236 				ix1 = ixend;
3237 			}
3238 		}
3239 	}
3240 
3241 	return nil;
3242 }
3243 
3244 char*
3245 tktextselection(Tk *tk, char *arg, char **val)
3246 {
3247 	USED(val);
3248 	if (strcmp(arg, " clear") == 0) {
3249 		tktclearsel(tk);
3250 		return nil;
3251 	}
3252 	else
3253 		return TkBadcm;
3254 }
3255 
3256 static void
3257 doselectto(Tk *tk, Point p, int dbl)
3258 {
3259 	int halfway;
3260 	TkTindex cur, insert, first, last;
3261 	TkText *tkt = TKobj(TkText, tk);
3262 	tktclearsel(tk);
3263 
3264 	halfway = tktxyind(tk, p.x, p.y, &cur);
3265 
3266 	if(!dbl) {
3267 		if(!tktmarkind(tk, "insert", &insert))
3268 			insert = cur;
3269 
3270 		if(tktindcompare(tkt, &cur, TkLt, &insert)) {
3271 			first = cur;
3272 			last = insert;
3273 		}
3274 		else {
3275 			first = insert;
3276 			last = cur;
3277 			if(halfway)
3278 				tktadjustind(tkt, TkTbychar, &last);
3279 			if(last.line == &tkt->end)
3280 				tktadjustind(tkt, TkTbycharback, &last);
3281 			if(tktindcompare(tkt, &first, TkGte, &last))
3282 				return;
3283 			cur = last;
3284 		}
3285 		tktsee(tk, &cur, 0);
3286 	}
3287 	else {
3288 		first = cur;
3289 		last = cur;
3290 		tktdoubleclick(tkt, &first, &last);
3291 	}
3292 
3293 	tkttagchange(tk, TkTselid, &first, &last, 1);
3294 }
3295 
3296 static void
3297 autoselect(Tk *tk, void *v, int cancelled)
3298 {
3299 	TkText *tkt = TKobj(TkText, tk);
3300 	Rectangle hitr;
3301 	Point p;
3302 	USED(v);
3303 
3304 	if (cancelled)
3305 		return;
3306 
3307 	p = scr2local(tk, tkt->track);
3308 	if (tkvisiblerect(tk, &hitr) && ptinrect(p, hitr))
3309 		return;
3310 	doselectto(tk, p, 0);
3311 	tkdirty(tk);
3312 	tkupdate(tk->env->top);
3313 }
3314 
3315 static char*
3316 tktextselectto(Tk *tk, char *arg, char **val)
3317 {
3318 	int dbl;
3319 	char *e;
3320 	Point p;
3321 	Rectangle hitr;
3322 	TkText *tkt = TKobj(TkText, tk);
3323 
3324 	USED(val);
3325 
3326 	if(tkt->tflag & (TkTjustfoc|TkTnodrag))
3327 		return nil;
3328 
3329 	e = tkxyparse(tk, &arg, &p);
3330 	if(e != nil)
3331 		return e;
3332 	tkt->track = p;
3333 	p = scr2local(tk, p);
3334 
3335 	arg = tkskip(arg, " ");
3336 	if(*arg == 'd') {
3337 		tkcancelrepeat(tk);
3338 		dbl = 1;
3339 		tkt->tflag |= TkTnodrag;
3340 	} else {
3341 		dbl = 0;
3342 		if (!tkvisiblerect(tk, &hitr) || !ptinrect(p, hitr))
3343 			return nil;
3344 	}
3345 	doselectto(tk, p, dbl);
3346 	return nil;
3347 }
3348 
3349 static char tktleft1[] = "{[(<";
3350 static char tktright1[] = "}])>";
3351 static char tktleft2[] = "\n";
3352 static char tktleft3[] = "\'\"`";
3353 
3354 static char *tktleft[] = {tktleft1, tktleft2, tktleft3, nil};
3355 static char *tktright[] = {tktright1,  tktleft2, tktleft3, nil};
3356 
3357 static void
3358 tktdoubleclick(TkText *tkt, TkTindex *first, TkTindex *last)
3359 {
3360 	int c, i;
3361 	TkTindex ix, ix2;
3362 	char *r, *l, *p;
3363 
3364 	for(i = 0; tktleft[i] != nil; i++) {
3365 		ix = *first;
3366 		l = tktleft[i];
3367 		r = tktright[i];
3368 		/* try matching character to left, looking right */
3369 		ix2 = ix;
3370 		if(!tktadjustind(tkt, TkTbycharback, &ix2))
3371 			c = '\n';
3372 		else
3373 			c = tktindrune(&ix2);
3374 		p = strchr(l, c);
3375 		if(p != nil) {
3376 			if(tktclickmatch(tkt, c, r[p-l], 1, &ix)) {
3377 				*last = ix;
3378 				if(c != '\n')
3379 					tktadjustind(tkt, TkTbycharback, last);
3380 			}
3381 			return;
3382 		}
3383 		/* try matching character to right, looking left */
3384 		c = tktindrune(&ix);
3385 		p = strchr(r, c);
3386 		if(p != nil) {
3387 			if(tktclickmatch(tkt, c, l[p-r], -1, &ix)) {
3388 				*last = *first;
3389 				if(c == '\n')
3390 					tktadjustind(tkt, TkTbychar, last);
3391 				*first = ix;
3392 				if(!(c=='\n' && ix.line == tkt->start.next && ix.item == ix.line->items))
3393 					tktadjustind(tkt, TkTbychar, first);
3394 			}
3395 			return;
3396 		}
3397 	}
3398 	/* try filling out word to right */
3399 	while(tkiswordchar(tktindrune(last))) {
3400 		if(!tktadjustind(tkt, TkTbychar, last))
3401 			break;
3402 	}
3403 	/* try filling out word to left */
3404 	for(;;) {
3405 		ix = *first;
3406 		if(!tktadjustind(tkt, TkTbycharback, &ix))
3407 			break;
3408 		if(!tkiswordchar(tktindrune(&ix)))
3409 			break;
3410 		*first = ix;
3411 	}
3412 }
3413 
3414 static int
3415 tktclickmatch(TkText *tkt, int cl, int cr, int dir, TkTindex *ix)
3416 {
3417 	int c, nest, atend;
3418 
3419 	nest = 1;
3420 	atend = 0;
3421 	for(;;) {
3422 		if(dir > 0) {
3423 			if(atend)
3424 				break;
3425 			c = tktindrune(ix);
3426 			atend = !tktadjustind(tkt, TkTbychar, ix);
3427 		} else {
3428 			if(!tktadjustind(tkt, TkTbycharback, ix))
3429 				break;
3430 			c = tktindrune(ix);
3431 		}
3432 		if(c == cr){
3433 			if(--nest==0)
3434 				return 1;
3435 		}else if(c == cl)
3436 			nest++;
3437 	}
3438 	return cl=='\n' && nest==1;
3439 }
3440 
3441 /*
3442  * return the line before line l, unless word wrap is on,
3443  * (for the first word of line l), in which case return the last non-empty line before that.
3444  * tktgeom might then combine the end of that line with the start of the insertion
3445  * (unless there is a newline in the way).
3446  */
3447 TkTline*
3448 tktprevwrapline(Tk *tk, TkTline *l)
3449 {
3450 	TkTitem *i;
3451 	int *opts, wrapmode;
3452 	TkText *tkt = TKobj(TkText, tk);
3453 	TkEnv env;
3454 
3455 	if(l == nil)
3456 		return nil;
3457 	/* some spacing depends on tags of first non-mark on display line */
3458 	for(i = l->items; i != nil; i = i->next)
3459 		if(i->kind != TkTmark && i->kind != TkTcontline)
3460 			break;
3461 	if(i == nil || i->kind == TkTnewline)	/* can't use !tkanytags(i) because it doesn't check env */
3462 		return l->prev;
3463 	opts = mallocz(TkTnumopts*sizeof(int), 0);
3464 	if(opts == nil)
3465 		return l->prev;	/* in worst case gets word wrap wrong */
3466 	tkttagopts(tk, i, opts, &env, nil, 1);
3467 	wrapmode = opts[TkTwrap];
3468 	free(opts);
3469 	if(wrapmode != Tkwrapword)
3470 		return l->prev;
3471 	if(l->prev != &tkt->start)
3472 		l = l->prev;	/* having been processed by tktgeom, shouldn't have extraneous marks etc */
3473 	return l->prev;
3474 }
3475 
3476 static char*
3477 tktextsetcursor(Tk *tk, char *arg, char **val)
3478 {
3479 	char *e;
3480 	TkTindex ix;
3481 	TkTmarkinfo *mi;
3482 	TkText *tkt = TKobj(TkText, tk);
3483 
3484 	USED(val);
3485 
3486 	/* do clearsel here, because it can change indices */
3487 	tktclearsel(tk);
3488 
3489 	e = tktindparse(tk, &arg, &ix);
3490 	if(e != nil)
3491 		return e;
3492 
3493 	mi = tktfindmark(tkt->marks, "insert");
3494 	if(tktdbg && mi == nil) {
3495 		print("tktextsetcursor: botch\n");
3496 		return nil;
3497 	}
3498 	tktmarkmove(tk, mi, &ix);
3499 	tktsee(tk, &ix, 0);
3500 	return nil;
3501 }
3502 
3503 static char*
3504 tktexttag(Tk *tk, char *arg, char **val)
3505 {
3506 	char *buf;
3507 	TkCmdtab *cmd;
3508 
3509 	buf = mallocz(Tkmaxitem, 0);
3510 	if(buf == nil)
3511 		return TkNomem;
3512 	arg = tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil);
3513 	for(cmd = tkttagcmd; cmd->name != nil; cmd++) {
3514 		if(strcmp(cmd->name, buf) == 0) {
3515 			free(buf);
3516 			return cmd->fn(tk, arg, val);
3517 		}
3518 	}
3519 	free(buf);
3520 	return TkBadcm;
3521 }
3522 
3523 static char*
3524 tktextwindow(Tk *tk, char *arg, char **val)
3525 {
3526 	char buf[Tkmaxitem];
3527 	TkCmdtab *cmd;
3528 
3529 	arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
3530 	for(cmd = tktwincmd; cmd->name != nil; cmd++) {
3531 		if(strcmp(cmd->name, buf) == 0)
3532 			return cmd->fn(tk, arg, val);
3533 	}
3534 	return TkBadcm;
3535 }
3536 
3537 static char*
3538 tktextxview(Tk *tk, char *arg, char **val)
3539 {
3540 	int ntot, vw;
3541 	char *e;
3542 	Point odeltatv;
3543 	TkText *tkt = TKobj(TkText, tk);
3544 
3545 	odeltatv = tkt->deltatv;
3546 	vw = tk->act.width - tk->ipad.x;
3547 	ntot = tktmaxwid(tkt->start.next);
3548 	if(ntot < tkt->deltatv.x +vw)
3549 		ntot = tkt->deltatv.x + vw;
3550 	e = tktview(tk, arg, val, vw, &tkt->deltatv.x, ntot, Tkhorizontal);
3551 	if(e == nil) {
3552 		e = tktsetscroll(tk, Tkhorizontal);
3553 		if(e == nil)
3554 			tktfixscroll(tk, odeltatv);
3555 	}
3556 	return e;
3557 }
3558 
3559 static int
3560 istext(TkTline *l)
3561 {
3562 	TkTitem *i;
3563 
3564 	for(i = l->items; i != nil; i = i->next)
3565 		if(i->kind == TkTwin || i->kind == TkTmark)
3566 			return 0;
3567 	return 1;
3568 }
3569 
3570 static void
3571 tkadjpage(Tk *tk, int ody, int *dy)
3572 {
3573 	int y, a, b, d;
3574 	TkTindex ix;
3575 	TkTline *l;
3576 
3577 	d = *dy-ody;
3578 	y = d > 0 ? tk->act.height : 0;
3579 	tktxyind(tk, 0, y-d, &ix);
3580 	if((l = ix.line) != nil && istext(l)){
3581 		a = l->orig.y;
3582 		b = a+l->height;
3583 /* print("AP: %d %d %d (%d+%d)\n", a, ody+y, b, ody, y); */
3584 		if(a+2 < ody+y && ody+y < b-2){	/* partially obscured line */
3585 			if(d > 0)
3586 				*dy -= ody+y-a;
3587 			else
3588 				*dy += b-ody;
3589 		}
3590 	}
3591 }
3592 
3593 static char*
3594 tktextyview(Tk *tk, char *arg, char **val)
3595 {
3596 	int ntot, vh, d;
3597 	char *e;
3598 	TkTline *l;
3599 	Point odeltatv;
3600 	TkTindex ix;
3601 	TkText *tkt = TKobj(TkText, tk);
3602 	char buf[Tkmaxitem], *v;
3603 
3604 	if(*arg != '\0') {
3605 		v = tkitem(buf, arg);
3606 		if(strcmp(buf, "-pickplace") == 0)
3607 			return tktextsee(tk,v, val);
3608 		if(strcmp(buf, "moveto") != 0 && strcmp(buf, "scroll") != 0) {
3609 			e = tktindparse(tk, &arg, &ix);
3610 			if(e != nil)
3611 				return e;
3612 			tktsee(tk, &ix, 1);
3613 			return nil;
3614 		}
3615 	}
3616 	odeltatv = tkt->deltatv;
3617 	vh = tk->act.height;
3618 	l =  tkt->end.prev;
3619 	ntot = l->orig.y + l->height;
3620 //	if(ntot < tkt->deltatv.y + vh)
3621 //		ntot = tkt->deltatv.y + vh;
3622 	e = tktview(tk, arg, val, vh, &tkt->deltatv.y, ntot, Tkvertical);
3623 	d = tkt->deltatv.y-odeltatv.y;
3624 	if(d == vh || d == -vh)
3625 		tkadjpage(tk, odeltatv.y, &tkt->deltatv.y);
3626 	if(e == nil) {
3627 		e = tktsetscroll(tk, Tkvertical);
3628 		if(e == nil)
3629 			tktfixscroll(tk, odeltatv);
3630 	}
3631 	return e;
3632 }
3633 static void
3634 tktextfocusorder(Tk *tk)
3635 {
3636 	TkTindex ix;
3637 	TkText *t;
3638 	Tk *isub;
3639 
3640 	t = TKobj(TkText, tk);
3641 	tktstartind(t, &ix);
3642 	do {
3643 		if(ix.item->kind == TkTwin) {
3644 			isub = ix.item->iwin->sub;
3645 			if(isub != nil)
3646 				tkappendfocusorder(isub);
3647 		}
3648 	} while(tktadjustind(t, TkTbyitem, &ix));
3649 }
3650 
3651 TkCmdtab tktextcmd[] =
3652 {
3653 	"bbox",			tktextbbox,
3654 	"cget",			tktextcget,
3655 	"compare",		tktextcompare,
3656 	"configure",		tktextconfigure,
3657 	"debug",		tktextdebug,
3658 	"delete",		tktextdelete,
3659 	"dlineinfo",		tktextdlineinfo,
3660 	"dump",			tktextdump,
3661 	"get",			tktextget,
3662 	"index",		tktextindex,
3663 	"insert",		tktextinsert,
3664 	"mark",			tktextmark,
3665 	"scan",			tktextscan,
3666 	"search",		tktextsearch,
3667 	"see",			tktextsee,
3668 	"selection",		tktextselection,
3669 	"tag",			tktexttag,
3670 	"window",		tktextwindow,
3671 	"xview",		tktextxview,
3672 	"yview",		tktextyview,
3673 	"tkTextButton1",	tktextbutton1,
3674 	"tkTextButton1R",	tktextbutton1r,
3675 	"tkTextDelIns",		tktextdelins,
3676 	"tkTextInsert",		tktextinserti,
3677 	"tkTextSelectTo",	tktextselectto,
3678 	"tkTextSetCursor",	tktextsetcursor,
3679 	"tkTextScrollPages",	tktextscrollpages,
3680 	"tkTextCursor",		tktextcursor,
3681 	nil
3682 };
3683 
3684 TkMethod textmethod = {
3685 	"text",
3686 	tktextcmd,
3687 	tkfreetext,
3688 	tkdrawtext,
3689 	tktextgeom,
3690 	nil,
3691 	tktextfocusorder,
3692 	tktdirty,
3693 	tktrelpos,
3694 	tktextevent,
3695 	nil,				/* XXX need to implement textsee */
3696 	tktinwindow,
3697 	nil,
3698 	tktxtforgetsub,
3699 };
3700