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