xref: /inferno-os/libtk/textw.c (revision ce8e0d607a2bec33fcaac7237d0b5535e5b152a1)
1 #include "lib9.h"
2 #include "draw.h"
3 #include "keyboard.h"
4 #include "tk.h"
5 #include "textw.h"
6 
7 /*
8  * useful text widget info to be found at:
9  * :/coordinate.systems		- what coord. systems are in use
10  * textu.c:/assumed.invariants	- some invariants that must be preserved
11  */
12 
13 #define istring u.string
14 #define iwin u.win
15 #define imark u.mark
16 #define iline u.line
17 
18 #define FLUSH() flushimage(tk->env->top->display, 1)
19 
20 #define	O(t, e)		((long)(&((t*)0)->e))
21 
22 /* Layout constants */
23 enum {
24 	Textpadx	= 2,
25 	Textpady	= 0,
26 };
27 
28 typedef struct Interval {
29 	int	lo;
30 	int	hi;
31 } Interval;
32 
33 typedef struct Mprint Mprint;
34 struct Mprint
35 {
36 	char*	buf;
37 	int	ptr;
38 	int	len;
39 };
40 
41 typedef struct TkDump TkDump;
42 struct TkDump
43 {
44 	int	sgml;
45 	int	metrics;
46 };
47 
48 static
49 TkOption dumpopts[] =
50 {
51 	"sgml",		OPTbool,	O(TkDump, sgml),	nil,
52 	"metrics",	OPTbool,	O(TkDump, metrics),	nil,
53 	nil
54 };
55 
56 static
57 TkStab tkcompare[] =
58 {
59 	"<",		TkLt,
60 	"<=",		TkLte,
61 	"==",		TkEq,
62 	">=",		TkGte,
63 	">",		TkGt,
64 	"!=",		TkNeq,
65 	nil
66 };
67 
68 static
69 TkOption textopts[] =
70 {
71 	"wrap",			OPTstab, O(TkText, opts[TkTwrap]),	tkwrap,
72 	"spacing1",		OPTnndist, O(TkText, opts[TkTspacing1]),	(void *)O(Tk, env),
73 	"spacing2",		OPTnndist, O(TkText, opts[TkTspacing2]),	(void *)O(Tk, env),
74 	"spacing3",		OPTnndist, O(TkText, opts[TkTspacing3]),	(void *)O(Tk, env),
75 	"tabs",			OPTtabs, O(TkText, tabs), 		(void *)O(Tk, env),
76 	"xscrollcommand",	OPTtext, O(TkText, xscroll),		nil,
77 	"yscrollcommand",	OPTtext, O(TkText, yscroll),		nil,
78 	"insertwidth",		OPTnndist, O(TkText, inswidth),		nil,
79 	"tagshare",		OPTwinp, O(TkText, tagshare),		nil,
80 	"propagate",		OPTstab, O(TkText, propagate),	tkbool,
81 	"selectborderwidth",	OPTnndist, O(TkText, sborderwidth), nil,
82 	nil
83 };
84 
85 #define CNTL(c) ((c)&0x1f)
86 #define DEL 0x7f
87 
88 static TkEbind tktbinds[] = {
89 	{TkButton1P,		"%W tkTextButton1 %X %Y"},
90 	{TkButton1P|TkMotion,	"%W tkTextSelectTo %X %Y"},
91 	{TkButton1P|TkDouble,	"%W tkTextSelectTo %X %Y double"},
92 	{TkButton1R,		"%W tkTextButton1R"},
93 	{TkButton2P,		"%W scan mark %x %y"},
94 	{TkButton2P|TkMotion,	"%W scan dragto %x %y"},
95 	{TkKey,			"%W tkTextInsert {%A}"},
96 	{TkKey|CNTL('a'),	"%W tkTextSetCursor {insert linestart}"},
97 	{TkKey|Home,		"%W tkTextSetCursor {insert linestart}"},
98 	{TkKey|CNTL('<'),	"%W tkTextSetCursor {insert linestart}"},
99 	{TkKey|CNTL('b'),	"%W tkTextSetCursor insert-1c"},
100 	{TkKey|Left,		"%W tkTextSetCursor insert-1c"},
101 	{TkKey|CNTL('d'),	"%W delete insert"},
102 	{TkKey|CNTL('e'),	"%W tkTextSetCursor {insert lineend}"},
103 	{TkKey|End,		"%W tkTextSetCursor {insert lineend}"},
104 	{TkKey|CNTL('>'),	"%W tkTextSetCursor {insert lineend}"},
105 	{TkKey|CNTL('f'),	"%W tkTextSetCursor insert+1c"},
106 	{TkKey|Right,		"%W tkTextSetCursor insert+1c"},
107 	{TkKey|CNTL('h'),	"%W tkTextDelIns -c"},
108 	{TkKey|DEL,		"%W tkTextDelIns +c"},
109 	{TkKey|CNTL('k'),	"%W delete insert {insert lineend}"},
110 	{TkKey|CNTL('n'),	"%W tkTextSetCursor {insert+1l}"},
111 	{TkKey|Down,		"%W tkTextSetCursor {insert+1l}"},
112 	{TkKey|CNTL('o'),       "%W tkTextInsert {\n}; %W mark set insert insert-1c"},
113 	{TkKey|CNTL('p'),	"%W tkTextSetCursor {insert-1l}"},
114 	{TkKey|Up,		"%W tkTextSetCursor {insert-1l}"},
115 	{TkKey|CNTL('u'),	"%W tkTextDelIns -l"},
116 	{TkKey|CNTL('v'),	"%W yview scroll 0.75 page"},
117 	{TkKey|Pgdown,	"%W yview scroll 0.75 page"},
118 	{TkKey|CNTL('w'),	"%W tkTextDelIns -w"},
119 	{TkKey|Pgup,	"%W yview scroll -0.75 page"},
120 	{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 = 1;
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 		e = tkfracword(tk->env->top, &arg, &amount, nil);
1803 		if(e != nil)
1804 			return e;
1805 		arg = tkskip(arg, " \t");
1806 		if(*arg == 'p')		/* Pages */
1807 			amount *= nl;
1808 		else				/* Lines or Characters */
1809 		if(orient == Tkvertical) {
1810 			/* XXX needs improvement */
1811 			amount *= tk->env->font->height;
1812 		}
1813 		else
1814 			amount *= tk->env->wzero;
1815 		amount = TKF2I(amount);
1816 		n = *posn + amount;
1817 		if(n < 0)
1818 			n = 0;
1819 		if(n > max)
1820 			n = max;
1821 		*posn = n;
1822 	}
1823 	else
1824 		return TkBadcm;
1825 
1826 	bot = max - (nl * 3 / 4);
1827 	if(*posn > bot)
1828 		*posn = bot;
1829 	if(*posn < 0)
1830 		*posn = 0;
1831 
1832 	return nil;
1833 }
1834 
1835 static void
1836 tktclearsel(Tk *tk)
1837 {
1838 	TkTindex ibeg, iend;
1839 	TkText *tkt = TKobj(TkText, tk);
1840 
1841 	if(tkt->selfirst == nil)
1842 		return;
1843 	tktitemind(tkt->selfirst, &ibeg);
1844 	tktitemind(tkt->sellast, &iend);
1845 
1846 	tkttagchange(tk, TkTselid, &ibeg, &iend, 0);
1847 }
1848 
1849 static int
1850 tktgetsel(Tk *tk, TkTindex *i1, TkTindex *i2)
1851 {
1852 	TkText *tkt =TKobj(TkText, tk);
1853 
1854 	if(tkt->selfirst == nil)
1855 		return 0;
1856 	tktitemind(tkt->selfirst, i1);
1857 	tktitemind(tkt->sellast, i2);
1858 	return 1;
1859 }
1860 
1861 /*
1862  * Adjust tkt->deltatv so that indexed character is visible.
1863  *	- if seetop is true, make indexed char be at top of window
1864  *	- if it is already visible, do nothing.
1865  *	- if it is > 1/2 screenful off edge of screen, center it
1866  *	   else put it at bottom or top (whichever is nearer)
1867  *	- if first line is visible, put it at top
1868  *	- if last line is visible, allow one blank line at bottom
1869  *
1870  * BUG: should handle x visibility too
1871  */
1872 static void
1873 tktsee(Tk *tk, TkTindex *ixp, int seetop)
1874 {
1875 	int ycur, ynext, deltatvy, adjy, h;
1876 	Point p, odeltatv;
1877 	Rectangle bbox;
1878 	TkTline *l, *el;
1879 	TkText *tkt = TKobj(TkText, tk);
1880 	TkTindex ix;
1881 
1882 	ix = *ixp;
1883 	deltatvy = tkt->deltatv.y;
1884 	odeltatv = tkt->deltatv;
1885 	h = tk->act.height;
1886 
1887 	/* find p (in T space): top left of indexed line */
1888 	l = ix.line;
1889 	p = l->orig;
1890 
1891 	/* ycur, ynext in V space */
1892 	ycur = p.y - deltatvy;
1893 	ynext = ycur + l->height;
1894 	adjy = 0;
1895 
1896 	/* quantize h to line boundaries (works if single font) */
1897 	if ( l->height )
1898 		h -= h%l->height;
1899 
1900 	if(seetop) {
1901 		deltatvy = p.y;
1902 		adjy = 1;
1903 	}
1904 	else
1905 	if(ycur < 0 || ynext >= h) {
1906 		adjy = 1;
1907 
1908 		if(ycur < -h/2 || ycur > 3*h/2)
1909 			deltatvy = p.y - h/2;
1910 		else if(ycur < 0)
1911 			deltatvy = p.y;
1912 		else
1913 			deltatvy = p.y - h + l->height;
1914 
1915 		el = tkt->end.prev;
1916 		if(el != nil && el->orig.y - deltatvy < h)
1917 			deltatvy = tkt->end.orig.y - (h * 3 / 4);
1918 
1919 		if(p.y - deltatvy < 0)
1920 			deltatvy = p.y;
1921 		if(deltatvy < 0)
1922 			deltatvy = 0;
1923 	}
1924 	if(adjy) {
1925 		tkt->deltatv.y = deltatvy;
1926 		tktsetscroll(tk, Tkvertical);	/* XXX - Tad: err ignored */
1927 		tktfixscroll(tk, odeltatv);
1928 	}
1929 	while (ix.item->kind == TkTmark)
1930 		ix.item = ix.item->next;
1931 	bbox = tktbbox(tk, &ix);
1932 	/* make sure that cursor at the end gets shown */
1933 	tksee(tk, bbox, Pt(bbox.min.x, (bbox.min.y + bbox.max.y) / 2));
1934 }
1935 
1936 static int
1937 tktcmatch(int c1, int c2, int nocase)
1938 {
1939 	if(nocase) {
1940 		if(c1 >= 'a' && c1 <= 'z')
1941 			c1 -= 'a' - 'A';
1942 		if(c2 >= 'a' && c2 <= 'z')
1943 			c2 -= 'a' - 'A';
1944 	}
1945 	return (c1 == c2);
1946 }
1947 
1948 /*
1949  * Return 1 if tag with id m1 ends before tag with id m2,
1950  * starting at the item after that indexed in ix (but don't
1951  * modify ix).
1952  */
1953 static int
1954 tagendsbefore(TkText *tkt, TkTindex *ix, int m1, int m2)
1955 {
1956 	int s1, s2;
1957 	TkTindex ix1;
1958 	TkTitem *i;
1959 
1960 	ix1 = *ix;
1961 	while(tktadjustind(tkt, TkTbyitem, &ix1)) {
1962 		i = ix1.item;
1963 		if(i->kind == TkTwin || i->kind == TkTcontline || i->kind == TkTmark)
1964 			continue;
1965 		s1 = tkttagset(i, m1);
1966 		s2 = tkttagset(i, m2);
1967 		if(!s1)
1968 			return s2;
1969 		else if(!s2)
1970 			return 0;
1971 	}
1972 	return 0;
1973 }
1974 
1975 static int
1976 tktsgmltags(TkText *tkt, Fmt *fmt, TkTitem *iprev, TkTitem *i, TkTindex *ix, int *stack, int *pnstack, int *tmpstack)
1977 {
1978 	int nprev, n, m, r, k, j, ii, onstack, nt;
1979 
1980 	nprev = 0;
1981 	if(iprev != nil && (iprev->tags[0] != 0 || iprev->tagextra > 0))
1982 		nprev = 32*(iprev->tagextra + 1);
1983 	n = 0;
1984 	if(i != nil && (i->tags[0] != 0 || i->tagextra > 0))
1985 		n = 32*(i->tagextra + 1);
1986 	nt = 0;
1987 	if(n > 0) {
1988 		/* find tags which open here */
1989 		for(m = 0; m < n; m++)
1990 			if(tkttagset(i, m) && (iprev == nil || !tkttagset(iprev, m)))
1991 				tmpstack[nt++] = m;
1992 	}
1993 	if(nprev > 0) {
1994 		/*
1995 		 * Find lowest tag in stack that ends before any tag beginning here.
1996 		 * We have to emit end tags all the way down to there, then add
1997 		 * back the ones that haven't actually ended here, together with ones
1998 		 * that start here, and sort all of the added ones so that tags that
1999 		 * end later are lower in the stack.
2000 		 */
2001 		ii = *pnstack;
2002 		for(k = *pnstack - 1; k >=0; k--) {
2003 			m = stack[k];
2004 			if(i == nil || !tkttagset(i, m))
2005 				ii = k;
2006 			else
2007 				for(j = 0; j < nt; j++)
2008 					if(tagendsbefore(tkt, ix, m, tmpstack[j]))
2009 						ii = k;
2010 		}
2011 		for(k = *pnstack - 1; k >= ii; k--) {
2012 			m = stack[k];
2013 			r = fmtprint(fmt, "</%s>", tkttagname(tkt, m));
2014 			if(r < 0)
2015 				return r;
2016 			/* add m back to starting tags if m didn't actually end here */
2017 			if(i != nil && tkttagset(i, m))
2018 				tmpstack[nt++] = m;
2019 		}
2020 		*pnstack = ii;
2021 	}
2022 	if(nt > 0) {
2023 		/* add tags which open  or reopen here */
2024 		onstack = *pnstack;
2025 		k = onstack;
2026 		for(j = 0; j < nt; j++)
2027 			stack[k++] = tmpstack[j];
2028 		*pnstack = k;
2029 		if(k - onstack > 1) {
2030 			/* sort new stack entries so tags that end later are lower in stack */
2031 			for(ii = k-2; ii>= onstack; ii--) {
2032 				m = stack[ii];
2033 				for(j = ii+1; j < k && tagendsbefore(tkt, ix, m, stack[j]); j++) {
2034 					stack[j-1] = stack[j];
2035 				}
2036 				stack[j-1] = m;
2037 			}
2038 		}
2039 		for(j = onstack; j < k; j++) {
2040 			r = fmtprint(fmt, "<%s>", tkttagname(tkt, stack[j]));
2041 			if(r < 0)
2042 				return r;
2043 		}
2044 	}
2045 	return 0;
2046 }
2047 
2048 /*
2049  * In 'sgml' format, just print text (no special treatment of
2050  * special characters, except that < turns into &lt;)
2051  * interspersed with things like <Bold> and </Bold>
2052  * (where Bold is a tag name).
2053  * Make sure that the tag pairs nest properly.
2054 */
2055 static char*
2056 tktget(TkText *tkt, TkTindex *ix1, TkTindex *ix2, int sgml, char **val)
2057 {
2058 	int n, m, i, bychar, nstack;
2059 	int *stack, *tmpstack;
2060 	char *s;
2061 	TkTitem *iprev;
2062 	Tk *sub;
2063 	Fmt fmt;
2064 	char *buf;
2065 
2066 	if(!tktindbefore(ix1, ix2))
2067 		return nil;
2068 
2069 	stack = nil;
2070 	tmpstack = nil;
2071 
2072 	iprev = nil;
2073 	fmtstrinit(&fmt);
2074 	buf = mallocz(100, 0);
2075 	if(buf == nil)
2076 		return TkNomem;
2077 	if(sgml) {
2078 		stack = malloc((tkt->nexttag+1)*sizeof(int));
2079 		tmpstack = malloc((tkt->nexttag+1)*sizeof(int));
2080 		if(stack == nil || tmpstack == nil)
2081 			goto nomemret;
2082 		nstack = 0;
2083 	}
2084 	for(;;) {
2085 		if(ix1->item == ix2->item && ix1->pos == ix2->pos)
2086 			break;
2087 		s = nil;
2088 		bychar = 0;
2089 		m = 1;
2090 		switch(ix1->item->kind) {
2091 		case TkTrune:
2092 			s = ix1->item->istring;
2093 			s += tktutfpos(s, ix1->pos);
2094 			if(ix1->item == ix2->item) {
2095 				m = ix2->pos - ix1->pos;
2096 				bychar = 1;
2097 			}
2098 			break;
2099 		case TkTascii:
2100 			s = ix1->item->istring + ix1->pos;
2101 			if(ix1->item == ix2->item) {
2102 				m = ix2->pos - ix1->pos;
2103 				bychar = 1;
2104 			}
2105 			else {
2106 				m = strlen(s);
2107 				if(sgml && memchr(s, '<', m) != nil)
2108 					bychar = 1;
2109 			}
2110 			break;
2111 		case TkTtab:
2112 			s = "\t";
2113 			break;
2114 		case TkTnewline:
2115 			s = "\n";
2116 			break;
2117 		case TkTwin:
2118 			sub = ix1->item->iwin->sub;
2119 			if(sgml &&  sub != nil && sub->name != nil) {
2120 				snprint(buf, 100, "<Window %s>", sub->name->name);
2121 				s = buf;
2122 			}
2123 		}
2124 		if(s != nil) {
2125 			if(sgml) {
2126 				n = tktsgmltags(tkt, &fmt, iprev, ix1->item, ix1, stack, &nstack, tmpstack);
2127 				if(n < 0)
2128 					goto nomemret;
2129 			}
2130 			if(bychar) {
2131 				if (ix1->item->kind == TkTrune)
2132 					n = fmtprint(&fmt, "%.*s", m, s);
2133 				else {
2134 					n = 0;
2135 					for(i = 0; i < m && n >= 0; i++) {
2136 						if(s[i] == '<')
2137 							n = fmtprint(&fmt, "&lt;");
2138 						else
2139 							n = fmtprint(&fmt, "%c", s[i]);
2140 					}
2141 				}
2142 			}
2143 			else
2144 				n = fmtprint(&fmt, "%s", s);
2145 			if(n < 0)
2146 				goto nomemret;
2147 			iprev = ix1->item;
2148 		}
2149 		if(ix1->item == ix2->item)
2150 			break;
2151 		if(!tktadjustind(tkt, TkTbyitem, ix1)) {
2152 			if(tktdbg)
2153 				print("tktextget botch\n");
2154 			break;
2155 		}
2156 	}
2157 	if(sgml) {
2158 		n = tktsgmltags(tkt, &fmt, iprev, nil, nil, stack, &nstack, tmpstack);
2159 		if(n < 0)
2160 			goto nomemret;
2161 	}
2162 
2163 	*val = fmtstrflush(&fmt);
2164 	free(buf);
2165 	return nil;
2166 
2167 nomemret:
2168 	free(buf);
2169 	if(stack != nil)
2170 		free(stack);
2171 	if(tmpstack != nil)
2172 		free(tmpstack);
2173 	return TkNomem;
2174 }
2175 
2176 /* Widget Commands (+ means implemented)
2177 	+bbox
2178 	+cget
2179 	+compare
2180 	+configure
2181 	+debug
2182 	+delete
2183 	+dlineinfo
2184 	+dump
2185 	+get
2186 	+index
2187 	+insert
2188 	+mark
2189 	+scan
2190 	+search
2191 	+see
2192 	+tag
2193 	+window
2194 	+xview
2195 	+yview
2196 */
2197 
2198 static int
2199 tktviewrectclip(Rectangle *r, Rectangle b);
2200 
2201 static char*
2202 tktextbbox(Tk *tk, char *arg, char **val)
2203 {
2204 	char *e;
2205 	int noclip, w, h;
2206 	Rectangle r, rview;
2207 	TkTindex ix;
2208 	TkText *tkt;
2209 	char buf[Tkmaxitem];
2210 
2211 	e = tktindparse(tk, &arg, &ix);
2212 	if(e != nil)
2213 		return e;
2214 
2215 	noclip = 0;
2216 	if(*arg != '\0') {
2217 		/* extension to tk4.0:
2218 		 * "noclip" means don't clip to viewable area
2219 		 * "all" means give unclipped bbox of entire contents
2220 		 */
2221 		arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
2222 		if(strcmp(buf, "noclip") == 0)
2223 			noclip = 1;
2224 		else
2225 		if(strcmp(buf, "all") == 0) {
2226 			tkt = TKobj(TkText, tk);
2227 			w = tktmaxwid(tkt->start.next);
2228 			h = tkt->end.orig.y;
2229 			return tkvalue(val, "0 0 %d %d", w, h);
2230 		}
2231 	}
2232 
2233 	/*
2234 	 * skip marks; bbox applies to characters only.
2235 	 * it's not defined what happens when bbox is applied to a newline char,
2236 	 * so we'll just let the default case sort that out.
2237 	 */
2238 	while (ix.item->kind == TkTmark)
2239 		ix.item = ix.item->next;
2240 	r = tktbbox(tk, &ix);
2241 
2242 	rview.min.x = 0;
2243 	rview.min.y = 0;
2244 	rview.max.x = tk->act.width - tk->ipad.x;
2245 	rview.max.y = tk->act.height - tk->ipad.y;
2246 	if(noclip || tktviewrectclip(&r, rview))
2247 		return tkvalue(val, "%d %d %d %d", r.min.x, r.min.y,
2248 			r.max.x-r.min.x, r.max.y-r.min.y);
2249 	return nil;
2250 }
2251 
2252 /*
2253  * a supplemented rectclip, as ((0, 1), (0,1)) does not intersect ((0, 0), (5, 5))
2254  * but for our purposes, we want it to. it's a hack.
2255  */
2256 static int
2257 tktviewrectclip(Rectangle *rp, Rectangle b)
2258 {
2259 	Rectangle *bp = &b;
2260 	if((rp->min.x<bp->max.x &&
2261 		(bp->min.x<rp->max.x || (rp->max.x  == b.min.x
2262 				&& rp->min.x == b.min.x)) &&
2263 			rp->min.y<bp->max.y && bp->min.y<rp->max.y)==0)
2264 		return 0;
2265 	/* They must overlap */
2266 	if(rp->min.x < bp->min.x)
2267 		rp->min.x = bp->min.x;
2268 	if(rp->min.y < bp->min.y)
2269 		rp->min.y = bp->min.y;
2270 	if(rp->max.x > bp->max.x)
2271 		rp->max.x = bp->max.x;
2272 	if(rp->max.y > bp->max.y)
2273 		rp->max.y = bp->max.y;
2274 	return 1;
2275 }
2276 
2277 static Point
2278 scr2local(Tk *tk, Point p)
2279 {
2280 	p = subpt(p, tkposn(tk));
2281 	p.x -= tk->borderwidth;
2282 	p.y -= tk->borderwidth;
2283 	return p;
2284 }
2285 
2286 static char*
2287 tktextbutton1(Tk *tk, char *arg, char **val)
2288 {
2289 	char *e;
2290 	Point p;
2291 	TkCtxt *c;
2292 	TkTindex ix;
2293 	TkTmarkinfo *mi;
2294 	TkText *tkt = TKobj(TkText, tk);
2295 
2296 	USED(val);
2297 
2298 	e = tkxyparse(tk, &arg, &p);
2299 	if(e != nil)
2300 		return e;
2301 	tkt->track = p;
2302 	p = scr2local(tk, p);
2303 
2304 	tktxyind(tk, p.x, p.y, &ix);
2305 	tkt->tflag &= ~TkTjustfoc;
2306 	c = tk->env->top->ctxt;
2307 	if(!(tk->flag&Tkdisabled) && c->tkkeygrab != tk
2308                       && (tk->name != nil) && ix.item->kind != TkTwin) {
2309 		tkfocus(tk->env->top, tk->name->name, nil);
2310 		tkt->tflag |= TkTjustfoc;
2311 		return nil;
2312 	}
2313 
2314 	mi = tktfindmark(tkt->marks, "insert");
2315 	if(tktdbg && !mi) {
2316 		print("tktextbutton1: botch\n");
2317 		return nil;
2318 	}
2319 	tktmarkmove(tk, mi, &ix);
2320 
2321 	tktclearsel(tk);
2322 	tkrepeat(tk, autoselect, nil, TkRptpause, TkRptinterval);
2323 	return nil;
2324 }
2325 
2326 static char*
2327 tktextbutton1r(Tk *tk, char *arg, char **val)
2328 {
2329 	TkText *tkt;
2330 
2331 	USED(arg);
2332 	USED(val);
2333 
2334 	tkt = TKobj(TkText, tk);
2335 	tkt->tflag &= ~TkTnodrag;
2336 	tkcancelrepeat(tk);
2337 	return nil;
2338 }
2339 
2340 static char*
2341 tktextcget(Tk *tk, char *arg, char **val)
2342 {
2343 	TkText *tkt;
2344 	TkOptab tko[3];
2345 
2346 	tkt = TKobj(TkText, tk);
2347 	tko[0].ptr = tk;
2348 	tko[0].optab = tkgeneric;
2349 	tko[1].ptr = tkt;
2350 	tko[1].optab = textopts;
2351 	tko[2].ptr = nil;
2352 
2353 	return tkgencget(tko, arg, val, tk->env->top);
2354 }
2355 
2356 static char*
2357 tktextcompare(Tk *tk, char *arg, char **val)
2358 {
2359 	int op;
2360 	char *e;
2361 	TkTindex i1, i2;
2362 	TkText *tkt;
2363 	TkStab *s;
2364 	char *buf;
2365 
2366 	tkt = TKobj(TkText, tk);
2367 
2368 	e = tktindparse(tk, &arg, &i1);
2369 	if(e != nil)
2370 		return e;
2371 
2372 	if(*arg == '\0')
2373 		return TkBadcm;
2374 
2375 	buf = mallocz(Tkmaxitem, 0);
2376 	if(buf == nil)
2377 		return TkNomem;
2378 
2379 	arg = tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil);
2380 
2381 	op = -1;
2382 	for(s = tkcompare; s->val; s++)
2383 		if(strcmp(s->val, buf) == 0) {
2384 			op = s->con;
2385 			break;
2386 		}
2387 	if(op == -1) {
2388 		free(buf);
2389 		return TkBadcm;
2390 	}
2391 
2392 	e = tktindparse(tk, &arg, &i2);
2393 	if(e != nil) {
2394 		free(buf);
2395 		return e;
2396 	}
2397 
2398 	e = tkvalue(val, tktindcompare(tkt, &i1, op, &i2)? "1" : "0");
2399 	free(buf);
2400 	return e;
2401 }
2402 
2403 static char*
2404 tktextconfigure(Tk *tk, char *arg, char **val)
2405 {
2406 	char *e;
2407 	TkGeom g;
2408 	int bd;
2409 	TkText *tkt;
2410 	TkOptab tko[3];
2411 	tkt = TKobj(TkText, tk);
2412 	tko[0].ptr = tk;
2413 	tko[0].optab = tkgeneric;
2414 	tko[1].ptr = tkt;
2415 	tko[1].optab = textopts;
2416 	tko[2].ptr = nil;
2417 
2418 	if(*arg == '\0')
2419 		return tkconflist(tko, val);
2420 
2421 	g = tk->req;
2422 	bd = tk->borderwidth;
2423 
2424 	e = tkparse(tk->env->top, arg, tko, nil);
2425 	tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd));
2426 	if (tkt->propagate != BoolT) {
2427 		if ((tk->flag & Tksetwidth) == 0)
2428 			tk->req.width = tk->env->wzero*Textwidth;
2429 		if ((tk->flag & Tksetheight) == 0)
2430 			tk->req.height = tk->env->font->height*Textheight;
2431 	}
2432 	/* note: tkgeomchg() may also call tktfixgeom() via tktextgeom() */
2433 	tktfixgeom(tk, &tkt->start, tkt->end.prev, 0);
2434 	tktextsize(tk, 0);
2435 	tkgeomchg(tk, &g, bd);
2436 	tktnotdrawn(tk, 0, tkt->end.orig.y, 1);
2437 
2438 	return e;
2439 }
2440 
2441 static char*
2442 tktextdebug(Tk *tk, char *arg, char **val)
2443 {
2444 	char buf[Tkmaxitem];
2445 
2446 	tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
2447 	if(*buf == '\0')
2448 		return tkvalue(val, "%s", tktdbg? "on" : "off");
2449 	else {
2450 		tktdbg = (strcmp(buf, "1") == 0 || strcmp(buf, "yes") == 0);
2451 		if(tktdbg) {
2452 			tktprinttext(TKobj(TkText, tk));
2453 		}
2454 		return nil;
2455 	}
2456 }
2457 
2458 static char*
2459 tktextdelete(Tk *tk, char *arg, char **val)
2460 {
2461 	int sameit;
2462 	char *e;
2463 	TkTindex i1, i2, ip, isee;
2464 	TkTline *lmin;
2465 	TkText *tkt = TKobj(TkText, tk);
2466 	char buf[20], *p;
2467 
2468 	USED(val);
2469 
2470 	e = tktindparse(tk, &arg, &i1);
2471 	if(e != nil)
2472 		return e;
2473 	tktadjustind(tkt, TkTbycharstart, &i1);
2474 
2475 	e = tktsplititem(&i1);
2476 	if(e != nil)
2477 		return e;
2478 
2479 	if(*arg != '\0') {
2480 		e = tktindparse(tk, &arg, &i2);
2481 		if(e != nil)
2482 			return e;
2483 	}
2484 	else {
2485 		i2 = i1;
2486 		tktadjustind(tkt, TkTbychar, &i2);
2487 	}
2488 	if(tktindcompare(tkt, &i1, TkGte, &i2))
2489 		return nil;
2490 
2491 	sameit = (i1.item == i2.item);
2492 
2493 	/* save possible fixup see place */
2494 	isee.line = nil;
2495 	if(i2.line->orig.y + i2.line->height < tkt->deltatv.y) {
2496 		/* delete completely precedes view */
2497 		tktxyind(tk, 0, 0, &isee);
2498 	}
2499 
2500 	e = tktsplititem(&i2);
2501 	if(e != nil)
2502 		return e;
2503 
2504 	if(sameit) {
2505 		/* after split, i1 should be in previous item to i2 */
2506 		ip = i2;
2507 		tktadjustind(tkt, TkTbyitemback, &ip);
2508 		i1.item = ip.item;
2509 	}
2510 
2511 	lmin = tktprevwrapline(tk, i1.line);
2512 	while(i1.item != i2.item) {
2513 		if(i1.item->kind != TkTmark)
2514 			tktremitem(tkt, &i1);
2515 			/* tktremitem moves i1 to next item */
2516 		else {
2517 			if(!tktadjustind(tkt, TkTbyitem, &i1)) {
2518 				if(tktdbg)
2519 					print("tktextdelete botch\n");
2520 				break;
2521 			}
2522 		}
2523 	}
2524 
2525 	/*
2526 	 * guard against invalidation of index by tktfixgeom
2527 	 */
2528 	if (isee.line != nil)
2529 		snprint(buf, sizeof(buf), "%d.%d", tktlinenum(tkt, &isee), tktlinepos(tkt, &isee));
2530 
2531 	tktfixgeom(tk, lmin, i1.line, 0);
2532 	tktextsize(tk, 1);
2533 	if(isee.line != nil) {
2534 		p = buf;
2535 		tktindparse(tk, &p, &isee);
2536 		tktsee(tk, &isee, 1);
2537 	}
2538 	return nil;
2539 }
2540 
2541 static char*
2542 tktextsee(Tk *tk, char *arg, char **val)
2543 {
2544 	char *e;
2545 	TkTindex ix;
2546 
2547 	USED(val);
2548 
2549 	e = tktindparse(tk, &arg, &ix);
2550 	if(e != nil)
2551 		return e;
2552 
2553 	tktsee(tk, &ix, 0);
2554 	return nil;
2555 }
2556 
2557 static char*
2558 tktextdelins(Tk *tk, char *arg, char **val)
2559 {
2560 	int m, c, skipping, wordc, n;
2561 	TkTindex ix, ix2;
2562 	TkText *tkt = TKobj(TkText, tk);
2563 	char buf[30];
2564 
2565 	USED(val);
2566 
2567 	if(tk->flag&Tkdisabled)
2568 		return nil;
2569 
2570 	if(tktgetsel(tk, &ix, &ix2))
2571 		tktextdelete(tk, "sel.first sel.last", nil);
2572 	else {
2573 		while(*arg == ' ')
2574 			arg++;
2575 		if(*arg == '-') {
2576 			m = arg[1];
2577 			if(m == 'c')
2578 				n = 1;
2579 			else {
2580 				/* delete prev word (m=='w') or prev line (m=='l') */
2581 				if(!tktmarkind(tk, "insert", &ix))
2582 					return nil;
2583 				if(!tktadjustind(tkt, TkTbycharback, &ix))
2584 					return nil;
2585 				n = 1;
2586 				/* ^W skips back over nonwordchars, then takes maximal seq of wordchars */
2587 				skipping = 1;
2588 				for(;;) {
2589 					c = tktindrune(&ix);
2590 					if(c == '\n') {
2591 						/* special case: always delete at least one char */
2592 						if(n > 1)
2593 							n--;
2594 						break;
2595 					}
2596 					if(m == 'w') {
2597 						wordc = tkiswordchar(c);
2598 						if(wordc && skipping)
2599 							skipping = 0;
2600 						else if(!wordc && !skipping) {
2601 							n--;
2602 							break;
2603 						}
2604 					}
2605 					if(tktadjustind(tkt, TkTbycharback, &ix))
2606 						n++;
2607 					else
2608 						break;
2609 				}
2610 			}
2611 			sprint(buf, "insert-%dc insert", n);
2612 			tktextdelete(tk, buf, nil);
2613 		}
2614 		else
2615 			tktextdelete(tk, "insert", nil);
2616 		tktextsee(tk, "insert", nil);
2617 	}
2618 	return nil;
2619 }
2620 
2621 static char*
2622 tktextdlineinfo(Tk *tk, char *arg, char **val)
2623 {
2624 	char *e;
2625 	TkTindex ix;
2626 	TkTline *l;
2627 	Point p;
2628 	int vh;
2629 	TkText *tkt = TKobj(TkText, tk);
2630 
2631 	e = tktindparse(tk, &arg, &ix);
2632 	if(e != nil)
2633 		return e;
2634 
2635 	l = ix.line;
2636 	vh = tk->act.height;
2637 
2638 	/* get p in V space */
2639 	p = subpt(l->orig, tkt->deltatv);
2640 	if(p.y+l->height < 0 || p.y >= vh)
2641 		return nil;
2642 
2643 	return tkvalue(val, "%d %d %d %d %d",
2644 		p.x, p.y, l->width, l->height, l->ascent);
2645 }
2646 
2647 static char*
2648 tktextdump(Tk *tk, char *arg, char **val)
2649 {
2650 	TkTline *l;
2651 	TkTitem *i;
2652 	Fmt fmt;
2653 	TkText *tkt;
2654 	TkDump tkdump;
2655 	TkOptab tko[2];
2656 	TkTtaginfo *ti;
2657 	TkName *names, *n;
2658 	char *e, *win, *p;
2659 	TkTindex ix1, ix2;
2660 	int r, j, numitems;
2661 	ulong fg, bg;
2662 
2663 	tkt = TKobj(TkText, tk);
2664 
2665 
2666 	tkdump.sgml = 0;
2667 	tkdump.metrics = 0;
2668 
2669 	tko[0].ptr = &tkdump;
2670 	tko[0].optab = dumpopts;
2671 	tko[1].ptr = nil;
2672 	names = nil;
2673 	e = tkparse(tk->env->top, arg, tko, &names);
2674 	if(e != nil)
2675 		return e;
2676 
2677 	if(names != nil) {			/* supplied indices */
2678 		p = names->name;
2679 		e = tktindparse(tk, &p, &ix1);
2680 		if(e != nil) {
2681 			tkfreename(names);
2682 			return e;
2683 		}
2684 		n = names->link;
2685 		if(n != nil) {
2686 			p = n->name;
2687 			e = tktindparse(tk, &p, &ix2);
2688 			if(e != nil) {
2689 				tkfreename(names);
2690 				return e;
2691 			}
2692 		}
2693 		else {
2694 			ix2 = ix1;
2695 			tktadjustind(tkt, TkTbychar, &ix2);
2696 		}
2697 		tkfreename(names);
2698 		if(!tktindbefore(&ix1, &ix2))
2699 			return nil;
2700 	}
2701 	else
2702 		return TkBadix;
2703 
2704 	if(tkdump.metrics != 0) {
2705 		fmtstrinit(&fmt);
2706 		if(fmtprint(&fmt, "%%Fonts\n") < 0)
2707 			return TkNomem;
2708 		for(ti=tkt->tags; ti != nil; ti=ti->next) {
2709 			if(ti->env == nil || ti->env->font == nil)
2710 				continue;
2711 			if(fmtprint(&fmt, "%d::%s\n", ti->id,ti->env->font->name) < 0)
2712 				return TkNomem;
2713 		}
2714 		if(fmtprint(&fmt, "-1::%s\n%%Colors\n", tk->env->font->name) < 0)
2715 			return TkNomem;
2716 		for(ti=tkt->tags; ti != nil; ti=ti->next) {
2717 			if(ti->env == nil)
2718 				continue;
2719 			bg = ti->env->colors[TkCbackgnd];
2720 			fg = ti->env->colors[TkCforegnd];
2721 			if(bg == tk->env->colors[TkCbackgnd] &&
2722 			   fg == ti->env->colors[TkCforegnd])
2723 				continue;
2724 			r = fmtprint(&fmt,"%d::#%.8lux\n", ti->id, bg);
2725 			if(r < 0)
2726 				return TkNomem;
2727 			r = fmtprint(&fmt,"%d::#%.8lux\n", ti->id, fg);
2728 			if(r < 0)
2729 				return TkNomem;
2730 		}
2731 		if(fmtprint(&fmt, "%%Lines\n") < 0)
2732 			return TkNomem;
2733 
2734 		/*
2735 		 * In 'metrics' format lines are recorded in the following way:
2736 		 *    xorig yorig wd ht as [data]
2737 		 * where data is of the form:
2738 		 *    CodeWidth{tags} data
2739 		 * For Example;
2740 		 *    A200{200000} Hello World!
2741 		 * denotes an A(scii) contiguous string of 200 pixels with
2742 		 * bit 20 set in its tags which corresponds to some font.
2743 		 *
2744 	 	*/
2745 		if(ix2.line->items != ix2.item)
2746 			ix2.line = ix2.line->next;
2747 		for(l = ix1.line; l != ix2.line; l = l->next) {
2748 			numitems = 0;
2749 			for(i = l->items; i != nil; i = i->next) {
2750 				if(i->kind != TkTmark)
2751 					numitems++;
2752 			}
2753 			r = fmtprint(&fmt, "%d %d %d %d %d %d ",
2754 				l->orig.x, l->orig.y, l->width, l->height, l->ascent,numitems);
2755 			if(r < 0)
2756 				return TkNomem;
2757 			for(i = l->items; i != nil; i = i->next) {
2758 				switch(i->kind) {
2759 				case TkTascii:
2760 				case TkTrune:
2761 					r = i->kind == TkTascii ? 'A' : 'R';
2762 					if(fmtprint(&fmt,"[%c%d{", r, i->width) < 0)
2763 						return TkNomem;
2764 					if(i->tags !=0 || i->tagextra !=0) {
2765 						if(fmtprint(&fmt,"%lux", i->tags[0]) < 0)
2766 							return TkNomem;
2767 						for(j=0; j < i->tagextra; j++)
2768 							if(fmtprint(&fmt,"::%lux", i->tags[j+1]) < 0)
2769 								return TkNomem;
2770 					}
2771 					/* XXX string should be quoted to avoid embedded ']'s */
2772 					if(fmtprint(&fmt,"}%s]", i->istring) < 0)
2773 						return TkNomem;
2774 					break;
2775 				case TkTnewline:
2776 				case TkTcontline:
2777 					r = i->kind == TkTnewline ? 'N' : 'C';
2778 					if(fmtprint(&fmt, "[%c]", r) < 0)
2779 						return TkNomem;
2780 					break;
2781 				case TkTtab:
2782 					if(fmtprint(&fmt,"[T%d]",i->width) < 0)
2783 						return TkNomem;
2784 					break;
2785 				case TkTwin:
2786 					win = "<null>";
2787 					if(i->iwin->sub != nil)
2788 						win = i->iwin->sub->name->name;
2789 					if(fmtprint(&fmt,"[W%d %s]",i->width, win) < 0)
2790 						return TkNomem;
2791 					break;
2792 				}
2793 				if(fmtprint(&fmt, " ") < 0)
2794 					return TkNomem;
2795 
2796 			}
2797 			if(fmtprint(&fmt, "\n") < 0)
2798 				return TkNomem;
2799 			*val = fmtstrflush(&fmt);
2800 			if(*val == nil)
2801 				return TkNomem;
2802 		}
2803 	}
2804 	else
2805 		return tktget(tkt, &ix1, &ix2, tkdump.sgml, val);
2806 
2807 	return nil;
2808 }
2809 
2810 
2811 static char*
2812 tktextget(Tk *tk, char *arg, char **val)
2813 {
2814 	char *e;
2815 	TkTindex ix1, ix2;
2816 	TkText *tkt = TKobj(TkText, tk);
2817 
2818 	e = tktindparse(tk, &arg, &ix1);
2819 	if(e != nil)
2820 		return e;
2821 
2822 	if(*arg != '\0') {
2823 		e = tktindparse(tk, &arg, &ix2);
2824 		if(e != nil)
2825 			return e;
2826 	}
2827 	else {
2828 		ix2 = ix1;
2829 		tktadjustind(tkt, TkTbychar, &ix2);
2830 	}
2831 	return tktget(tkt, &ix1, &ix2, 0, val);
2832 }
2833 
2834 static char*
2835 tktextindex(Tk *tk, char *arg, char **val)
2836 {
2837 	char *e;
2838 	TkTindex ix;
2839 	TkText *tkt = TKobj(TkText, tk);
2840 
2841 	e = tktindparse(tk, &arg, &ix);
2842 	if(e != nil)
2843 		return e;
2844 	return tkvalue(val, "%d.%d", tktlinenum(tkt, &ix), tktlinepos(tkt, &ix));
2845 }
2846 
2847 static char*
2848 tktextinsert(Tk *tk, char *arg, char **val)
2849 {
2850 	int n;
2851 	char *e, *p, *pe;
2852 	TkTindex ins, pins;
2853 	TkTtaginfo *ti;
2854 	TkText *tkt;
2855 	TkTline *lmin;
2856 	TkTop *top;
2857 	TkTitem *tagit;
2858 	char *tbuf, *buf;
2859 
2860 	USED(val);
2861 
2862 	tkt = TKobj(TkText, tk);
2863 	top = tk->env->top;
2864 
2865 	e = tktindparse(tk, &arg, &ins);
2866 	if(e != nil)
2867 		return e;
2868 
2869 	if(ins.item->kind == TkTmark) {
2870 		if(ins.item->imark->gravity == Tkleft) {
2871 			while(ins.item->kind == TkTmark && ins.item->imark->gravity == Tkleft)
2872 				if(!tktadjustind(tkt, TkTbyitem, &ins)) {
2873 					if(tktdbg)
2874 						print("tktextinsert botch\n");
2875 					break;
2876 				}
2877 		}
2878 		else {
2879 			for(;;) {
2880 				pins = ins;
2881 				if(!tktadjustind(tkt, TkTbyitemback, &pins))
2882 					break;
2883 				if(pins.item->kind == TkTmark && pins.item->imark->gravity == Tkright)
2884 					ins = pins;
2885 				else
2886 					break;
2887 			}
2888 		}
2889 	}
2890 
2891 	lmin = tktprevwrapline(tk, ins.line);
2892 
2893 	n = strlen(arg) + 1;
2894 	if(n < Tkmaxitem)
2895 		n = Tkmaxitem;
2896 	tbuf = malloc(n);
2897 	if(tbuf == nil)
2898 		return TkNomem;
2899 	buf = mallocz(Tkmaxitem, 0);
2900 	if(buf == nil) {
2901 		free(tbuf);
2902 		return TkNomem;
2903 	}
2904 
2905 	tagit = nil;
2906 
2907 	while(*arg != '\0') {
2908 		arg = tkword(top, arg, tbuf, tbuf+n, nil);
2909 		if(*arg != '\0') {
2910 			/* tag list spec -- add some slop to tagextra for added tags */
2911 			e = tktnewitem(TkTascii, (tkt->nexttag-1)/32 + 1, &tagit);
2912 			if(e != nil) {
2913 				free(tbuf);
2914 				free(buf);
2915 				return e;
2916 			}
2917 			arg = tkword(top, arg, buf, buf+Tkmaxitem, nil);
2918 			p = buf;
2919 			while(*p) {
2920 				while(*p == ' ') {
2921 					p++;
2922 				}
2923 				if(*p == '\0')
2924 					break;
2925 				pe = strchr(p, ' ');
2926 				if(pe != nil)
2927 					*pe = '\0';
2928 				ti = tktfindtag(tkt->tags, p);
2929 				if(ti == nil) {
2930 					e = tktaddtaginfo(tk, p, &ti);
2931 					if(e != nil) {
2932 						if(tagit != nil)
2933 							free(tagit);
2934 						free(tbuf);
2935 						free(buf);
2936 						return e;
2937 					}
2938 				}
2939 				tkttagbit(tagit, ti->id, 1);
2940 				if(pe == nil)
2941 					break;
2942 				else
2943 					p = pe+1;
2944 			}
2945 		}
2946 		e = tktinsert(tk, &ins, tbuf, tagit);
2947 		if(tagit != nil) {
2948 			free(tagit);
2949 			tagit = nil;
2950 		}
2951 		if(e != nil) {
2952 			free(tbuf);
2953 			free(buf);
2954 			return e;
2955 		}
2956 	}
2957 
2958 	tktfixgeom(tk, lmin, ins.line, 0);
2959 	tktextsize(tk, 1);
2960 
2961 	free(tbuf);
2962 	free(buf);
2963 
2964 	return nil;
2965 }
2966 
2967 static char*
2968 tktextinserti(Tk *tk, char *arg, char **val)
2969 {
2970 	int n;
2971 	TkTline *lmin;
2972 	TkTindex ix, is1, is2;
2973 	TkText *tkt = TKobj(TkText, tk);
2974 	char *tbuf, *buf;
2975 
2976 	USED(val);
2977 
2978 	if(tk->flag&Tkdisabled)
2979 		return nil;
2980 
2981 	buf = mallocz(Tkmaxitem, 0);
2982 	if(buf == nil)
2983 		return TkNomem;
2984 
2985 	tbuf = nil;
2986 	n = strlen(arg) + 1;
2987 	if(n < Tkmaxitem)
2988 		tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
2989 	else {
2990 		tbuf = malloc(n);
2991 		if(tbuf == nil) {
2992 			free(buf);
2993 			return TkNomem;
2994 		}
2995 		tkword(tk->env->top, arg, tbuf, buf+n, nil);
2996 	}
2997 	if(*buf == '\0')
2998 		goto Ret;
2999 	if(!tktmarkind(tk, "insert", &ix)) {
3000 		print("tktextinserti: botch\n");
3001 		goto Ret;
3002 	}
3003 	if(tktgetsel(tk, &is1, &is2)) {
3004 		if(tktindcompare(tkt, &is1, TkLte, &ix) &&
3005 		   tktindcompare(tkt, &is2, TkGte, &ix)) {
3006 			tktextdelete(tk, "sel.first sel.last", nil);
3007 			/* delete might have changed ix item */
3008 			tktmarkind(tk, "insert", &ix);
3009 		}
3010 	}
3011 
3012 	lmin = tktprevwrapline(tk, ix.line);
3013 	tktinsert(tk, &ix, tbuf==nil ? buf : tbuf, 0);
3014 	tktfixgeom(tk, lmin, ix.line, 0);
3015 	if(tktmarkind(tk, "insert", &ix))		/* index doesn't remain valid after fixgeom */
3016 		tktsee(tk, &ix, 0);
3017 	tktextsize(tk, 1);
3018 Ret:
3019 	if(tbuf != nil)
3020 		free(tbuf);
3021 	free(buf);
3022 	return nil;
3023 }
3024 
3025 static char*
3026 tktextmark(Tk *tk, char *arg, char **val)
3027 {
3028 	char *buf;
3029 	TkCmdtab *cmd;
3030 
3031 	buf = mallocz(Tkmaxitem, 0);
3032 	if(buf == nil)
3033 		return TkNomem;
3034 	arg = tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil);
3035 	for(cmd = tktmarkcmd; cmd->name != nil; cmd++) {
3036 		if(strcmp(cmd->name, buf) == 0) {
3037 			free(buf);
3038 			return cmd->fn(tk, arg, val);
3039 		}
3040 	}
3041 	free(buf);
3042 	return TkBadcm;
3043 }
3044 
3045 static char*
3046 tktextscan(Tk *tk, char *arg, char **val)
3047 {
3048 	char *e;
3049 	int mark, x, y, xmax, ymax, vh, vw;
3050 	Point p, odeltatv;
3051 	char buf[Tkmaxitem];
3052 	TkText *tkt = TKobj(TkText, tk);
3053 
3054 	USED(val);
3055 
3056 	arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
3057 
3058 	if(strcmp(buf, "mark") == 0)
3059 		mark = 1;
3060 	else
3061 	if(strcmp(buf, "dragto") == 0)
3062 		mark = 0;
3063 	else
3064 		return TkBadcm;
3065 
3066 	e = tkxyparse(tk, &arg, &p);
3067 	if(e != nil)
3068 		return e;
3069 
3070 	if(mark)
3071 		tkt->track = p;
3072 	else {
3073 		odeltatv = tkt->deltatv;
3074 		vw = tk->act.width - tk->ipad.x;
3075 		vh = tk->act.height - tk->ipad.y;
3076 		ymax = tkt->end.prev->orig.y + tkt->end.prev->height - vh;
3077 		y = tkt->deltatv.y -10*(p.y - tkt->track.y);
3078 		if(y > ymax)
3079 			y = ymax;
3080 		if(y < 0)
3081 			y = 0;
3082 		tkt->deltatv.y = y;
3083 		e = tktsetscroll(tk, Tkvertical);
3084 		if(e != nil)
3085 			return e;
3086 		if(tkt->opts[TkTwrap] == Tkwrapnone) {
3087 			xmax = tktmaxwid(tkt->start.next) - vw;
3088 			x = tkt->deltatv.x - 10*(p.x - tkt->track.x);
3089 			if(x > xmax)
3090 				x = xmax;
3091 			if(x < 0)
3092 				x = 0;
3093 			tkt->deltatv.x = x;
3094 			e = tktsetscroll(tk, Tkhorizontal);
3095 			if(e != nil)
3096 				return e;
3097 		}
3098 		tktfixscroll(tk, odeltatv);
3099 		tkt->track = p;
3100 	}
3101 
3102 	return nil;
3103 }
3104 
3105 static char*
3106 tktextscrollpages(Tk *tk, char *arg, char **val)
3107 {
3108 	TkText *tkt = TKobj(TkText, tk);
3109 
3110 	USED(tkt);
3111 	USED(arg);
3112 	USED(val);
3113 	return nil;
3114 }
3115 
3116 static char*
3117 tktextsearch(Tk *tk, char *arg, char **val)
3118 {
3119 	int i, n;
3120 	Rune r;
3121 	char *e, *s;
3122 	int wrap, fwd, nocase;
3123 	TkText *tkt;
3124 	TkTindex ix1, ix2, ixstart, ixend, tx;
3125 	char buf[Tkmaxitem];
3126 
3127 	tkt = TKobj(TkText, tk);
3128 
3129 	fwd = 1;
3130 	nocase = 0;
3131 
3132 	while(*arg != '\0') {
3133 		arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
3134 		if(*buf != '-')
3135 			break;
3136 		if(strcmp(buf, "-backwards") == 0)
3137 			fwd = 0;
3138 		else if(strcmp(buf, "-nocase") == 0)
3139 			nocase = 1;
3140 		else if(strcmp(buf, "--") == 0) {
3141 			arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
3142 			break;
3143 		}
3144 	}
3145 
3146 	tktstartind(tkt, &ixstart);
3147 	tktadjustind(tkt, TkTbycharstart, &ixstart);
3148 	tktendind(tkt, &ixend);
3149 
3150 	if(*arg == '\0')
3151 		return TkOparg;
3152 
3153 	e = tktindparse(tk, &arg, &ix1);
3154 	if(e != nil)
3155  		return e;
3156 	tktadjustind(tkt, fwd? TkTbycharstart : TkTbycharback, &ix1);
3157 
3158 	if(*arg != '\0') {
3159 		wrap = 0;
3160 		e = tktindparse(tk, &arg, &ix2);
3161 		if(e != nil)
3162 			return e;
3163 		if(!fwd)
3164 			tktadjustind(tkt, TkTbycharback, &ix2);
3165 	}
3166 	else {
3167 		wrap = 1;
3168 		if(fwd) {
3169 			if(tktindcompare(tkt, &ix1, TkEq, &ixstart))
3170 				ix2 = ixend;
3171 			else {
3172 				ix2 = ix1;
3173 				tktadjustind(tkt, TkTbycharback, &ix2);
3174 			}
3175 		}
3176 		else {
3177 			if(tktindcompare(tkt, &ix1, TkEq, &ixend))
3178 				ix2 = ixstart;
3179 			else {
3180 				ix2 = ix1;
3181 				tktadjustind(tkt, TkTbychar, &ix2);
3182 			}
3183 		}
3184 	}
3185 	tktadjustind(tkt, TkTbycharstart, &ix2);
3186 	if(tktindcompare(tkt, &ix1, TkEq, &ix2))
3187 		return nil;
3188 
3189 	if(*buf == '\0')
3190 		return tkvalue(val, "%d.%d", tktlinenum(tkt, &ix1), tktlinepos(tkt, &ix1));
3191 
3192 	while(!(ix1.item == ix2.item && ix1.pos == ix2.pos)) {
3193 		tx = ix1;
3194 		for(i = 0; buf[i] != '\0'; i++) {
3195 			switch(tx.item->kind) {
3196 			case TkTascii:
3197 				if(!tktcmatch(tx.item->istring[tx.pos], buf[i], nocase))
3198 					goto nomatch;
3199 				break;
3200 			case TkTrune:
3201 				s = tx.item->istring;
3202 				s += tktutfpos(s, tx.pos);
3203 				n = chartorune(&r, s);
3204 				if(strncmp(s, buf+i, n) != 0)
3205 					goto nomatch;
3206 				i += n-1;
3207 				break;
3208 			case TkTtab:
3209 				if(buf[i] != '\t')
3210 					goto nomatch;
3211 				break;
3212 			case TkTnewline:
3213 				if(buf[i] != '\n')
3214 					goto nomatch;
3215 				break;
3216 			default:
3217 				goto nomatch;
3218 			}
3219 			tktadjustind(tkt, TkTbychar, &tx);
3220 		}
3221 		return tkvalue(val, "%d.%d", tktlinenum(tkt, &ix1), tktlinepos(tkt, &ix1));
3222 	nomatch:
3223 		if(fwd) {
3224 			if(!tktadjustind(tkt, TkTbychar, &ix1)) {
3225 				if(!wrap)
3226 					break;
3227 				ix1 = ixstart;
3228 			}
3229 		}
3230 		else {
3231 			if(!tktadjustind(tkt, TkTbycharback, &ix1)) {
3232 				if(!wrap)
3233 					break;
3234 				ix1 = ixend;
3235 			}
3236 		}
3237 	}
3238 
3239 	return nil;
3240 }
3241 
3242 char*
3243 tktextselection(Tk *tk, char *arg, char **val)
3244 {
3245 	USED(val);
3246 	if (strcmp(arg, " clear") == 0) {
3247 		tktclearsel(tk);
3248 		return nil;
3249 	}
3250 	else
3251 		return TkBadcm;
3252 }
3253 
3254 static void
3255 doselectto(Tk *tk, Point p, int dbl)
3256 {
3257 	int halfway;
3258 	TkTindex cur, insert, first, last;
3259 	TkText *tkt = TKobj(TkText, tk);
3260 	tktclearsel(tk);
3261 
3262 	halfway = tktxyind(tk, p.x, p.y, &cur);
3263 
3264 	if(!dbl) {
3265 		if(!tktmarkind(tk, "insert", &insert))
3266 			insert = cur;
3267 
3268 		if(tktindcompare(tkt, &cur, TkLt, &insert)) {
3269 			first = cur;
3270 			last = insert;
3271 		}
3272 		else {
3273 			first = insert;
3274 			last = cur;
3275 			if(halfway)
3276 				tktadjustind(tkt, TkTbychar, &last);
3277 			if(last.line == &tkt->end)
3278 				tktadjustind(tkt, TkTbycharback, &last);
3279 			if(tktindcompare(tkt, &first, TkGte, &last))
3280 				return;
3281 			cur = last;
3282 		}
3283 		tktsee(tk, &cur, 0);
3284 	}
3285 	else {
3286 		first = cur;
3287 		last = cur;
3288 		tktdoubleclick(tkt, &first, &last);
3289 	}
3290 
3291 	tkttagchange(tk, TkTselid, &first, &last, 1);
3292 }
3293 
3294 static void
3295 autoselect(Tk *tk, void *v, int cancelled)
3296 {
3297 	TkText *tkt = TKobj(TkText, tk);
3298 	Rectangle hitr;
3299 	Point p;
3300 	USED(v);
3301 
3302 	if (cancelled)
3303 		return;
3304 
3305 	p = scr2local(tk, tkt->track);
3306 	if (tkvisiblerect(tk, &hitr) && ptinrect(p, hitr))
3307 		return;
3308 	doselectto(tk, p, 0);
3309 	tkdirty(tk);
3310 	tkupdate(tk->env->top);
3311 }
3312 
3313 static char*
3314 tktextselectto(Tk *tk, char *arg, char **val)
3315 {
3316 	int dbl;
3317 	char *e;
3318 	Point p;
3319 	Rectangle hitr;
3320 	TkText *tkt = TKobj(TkText, tk);
3321 
3322 	USED(val);
3323 
3324 	if(tkt->tflag & (TkTjustfoc|TkTnodrag))
3325 		return nil;
3326 
3327 	e = tkxyparse(tk, &arg, &p);
3328 	if(e != nil)
3329 		return e;
3330 	tkt->track = p;
3331 	p = scr2local(tk, p);
3332 
3333 	arg = tkskip(arg, " ");
3334 	if(*arg == 'd') {
3335 		tkcancelrepeat(tk);
3336 		dbl = 1;
3337 		tkt->tflag |= TkTnodrag;
3338 	} else {
3339 		dbl = 0;
3340 		if (!tkvisiblerect(tk, &hitr) || !ptinrect(p, hitr))
3341 			return nil;
3342 	}
3343 	doselectto(tk, p, dbl);
3344 	return nil;
3345 }
3346 
3347 static char tktleft1[] = "{[(<";
3348 static char tktright1[] = "}])>";
3349 static char tktleft2[] = "\n";
3350 static char tktleft3[] = "\'\"`";
3351 
3352 static char *tktleft[] = {tktleft1, tktleft2, tktleft3, nil};
3353 static char *tktright[] = {tktright1,  tktleft2, tktleft3, nil};
3354 
3355 static void
3356 tktdoubleclick(TkText *tkt, TkTindex *first, TkTindex *last)
3357 {
3358 	int c, i;
3359 	TkTindex ix, ix2;
3360 	char *r, *l, *p;
3361 
3362 	for(i = 0; tktleft[i] != nil; i++) {
3363 		ix = *first;
3364 		l = tktleft[i];
3365 		r = tktright[i];
3366 		/* try matching character to left, looking right */
3367 		ix2 = ix;
3368 		if(!tktadjustind(tkt, TkTbycharback, &ix2))
3369 			c = '\n';
3370 		else
3371 			c = tktindrune(&ix2);
3372 		p = strchr(l, c);
3373 		if(p != nil) {
3374 			if(tktclickmatch(tkt, c, r[p-l], 1, &ix)) {
3375 				*last = ix;
3376 				if(c != '\n')
3377 					tktadjustind(tkt, TkTbycharback, last);
3378 			}
3379 			return;
3380 		}
3381 		/* try matching character to right, looking left */
3382 		c = tktindrune(&ix);
3383 		p = strchr(r, c);
3384 		if(p != nil) {
3385 			if(tktclickmatch(tkt, c, l[p-r], -1, &ix)) {
3386 				*last = *first;
3387 				if(c == '\n')
3388 					tktadjustind(tkt, TkTbychar, last);
3389 				*first = ix;
3390 				if(!(c=='\n' && ix.line == tkt->start.next && ix.item == ix.line->items))
3391 					tktadjustind(tkt, TkTbychar, first);
3392 			}
3393 			return;
3394 		}
3395 	}
3396 	/* try filling out word to right */
3397 	while(tkiswordchar(tktindrune(last))) {
3398 		if(!tktadjustind(tkt, TkTbychar, last))
3399 			break;
3400 	}
3401 	/* try filling out word to left */
3402 	for(;;) {
3403 		ix = *first;
3404 		if(!tktadjustind(tkt, TkTbycharback, &ix))
3405 			break;
3406 		if(!tkiswordchar(tktindrune(&ix)))
3407 			break;
3408 		*first = ix;
3409 	}
3410 }
3411 
3412 static int
3413 tktclickmatch(TkText *tkt, int cl, int cr, int dir, TkTindex *ix)
3414 {
3415 	int c, nest, atend;
3416 
3417 	nest = 1;
3418 	atend = 0;
3419 	for(;;) {
3420 		if(dir > 0) {
3421 			if(atend)
3422 				break;
3423 			c = tktindrune(ix);
3424 			atend = !tktadjustind(tkt, TkTbychar, ix);
3425 		} else {
3426 			if(!tktadjustind(tkt, TkTbycharback, ix))
3427 				break;
3428 			c = tktindrune(ix);
3429 		}
3430 		if(c == cr){
3431 			if(--nest==0)
3432 				return 1;
3433 		}else if(c == cl)
3434 			nest++;
3435 	}
3436 	return cl=='\n' && nest==1;
3437 }
3438 
3439 /*
3440  * return the line before line l, unless word wrap is on,
3441  * (for the first word of line l), in which case return the last non-empty line before that.
3442  * tktgeom might then combine the end of that line with the start of the insertion
3443  * (unless there is a newline in the way).
3444  */
3445 TkTline*
3446 tktprevwrapline(Tk *tk, TkTline *l)
3447 {
3448 	TkTitem *i;
3449 	int *opts, wrapmode;
3450 	TkText *tkt = TKobj(TkText, tk);
3451 	TkEnv env;
3452 
3453 	if(l == nil)
3454 		return nil;
3455 	/* some spacing depends on tags of first non-mark on display line */
3456 	for(i = l->items; i != nil; i = i->next)
3457 		if(i->kind != TkTmark && i->kind != TkTcontline)
3458 			break;
3459 	if(i == nil || i->kind == TkTnewline)	/* can't use !tkanytags(i) because it doesn't check env */
3460 		return l->prev;
3461 	opts = mallocz(TkTnumopts*sizeof(int), 0);
3462 	if(opts == nil)
3463 		return l->prev;	/* in worst case gets word wrap wrong */
3464 	tkttagopts(tk, i, opts, &env, nil, 1);
3465 	wrapmode = opts[TkTwrap];
3466 	free(opts);
3467 	if(wrapmode != Tkwrapword)
3468 		return l->prev;
3469 	if(l->prev != &tkt->start)
3470 		l = l->prev;	/* having been processed by tktgeom, shouldn't have extraneous marks etc */
3471 	return l->prev;
3472 }
3473 
3474 static char*
3475 tktextsetcursor(Tk *tk, char *arg, char **val)
3476 {
3477 	char *e;
3478 	TkTindex ix;
3479 	TkTmarkinfo *mi;
3480 	TkText *tkt = TKobj(TkText, tk);
3481 
3482 	USED(val);
3483 
3484 	/* do clearsel here, because it can change indices */
3485 	tktclearsel(tk);
3486 
3487 	e = tktindparse(tk, &arg, &ix);
3488 	if(e != nil)
3489 		return e;
3490 
3491 	mi = tktfindmark(tkt->marks, "insert");
3492 	if(tktdbg && mi == nil) {
3493 		print("tktextsetcursor: botch\n");
3494 		return nil;
3495 	}
3496 	tktmarkmove(tk, mi, &ix);
3497 	tktsee(tk, &ix, 0);
3498 	return nil;
3499 }
3500 
3501 static char*
3502 tktexttag(Tk *tk, char *arg, char **val)
3503 {
3504 	char *buf;
3505 	TkCmdtab *cmd;
3506 
3507 	buf = mallocz(Tkmaxitem, 0);
3508 	if(buf == nil)
3509 		return TkNomem;
3510 	arg = tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil);
3511 	for(cmd = tkttagcmd; cmd->name != nil; cmd++) {
3512 		if(strcmp(cmd->name, buf) == 0) {
3513 			free(buf);
3514 			return cmd->fn(tk, arg, val);
3515 		}
3516 	}
3517 	free(buf);
3518 	return TkBadcm;
3519 }
3520 
3521 static char*
3522 tktextwindow(Tk *tk, char *arg, char **val)
3523 {
3524 	char buf[Tkmaxitem];
3525 	TkCmdtab *cmd;
3526 
3527 	arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
3528 	for(cmd = tktwincmd; cmd->name != nil; cmd++) {
3529 		if(strcmp(cmd->name, buf) == 0)
3530 			return cmd->fn(tk, arg, val);
3531 	}
3532 	return TkBadcm;
3533 }
3534 
3535 static char*
3536 tktextxview(Tk *tk, char *arg, char **val)
3537 {
3538 	int ntot, vw;
3539 	char *e;
3540 	Point odeltatv;
3541 	TkText *tkt = TKobj(TkText, tk);
3542 
3543 	odeltatv = tkt->deltatv;
3544 	vw = tk->act.width - tk->ipad.x;
3545 	ntot = tktmaxwid(tkt->start.next);
3546 	if(ntot < tkt->deltatv.x +vw)
3547 		ntot = tkt->deltatv.x + vw;
3548 	e = tktview(tk, arg, val, vw, &tkt->deltatv.x, ntot, Tkhorizontal);
3549 	if(e == nil) {
3550 		e = tktsetscroll(tk, Tkhorizontal);
3551 		if(e == nil)
3552 			tktfixscroll(tk, odeltatv);
3553 	}
3554 	return e;
3555 }
3556 
3557 static int
3558 istext(TkTline *l)
3559 {
3560 	TkTitem *i;
3561 
3562 	for(i = l->items; i != nil; i = i->next)
3563 		if(i->kind == TkTwin || i->kind == TkTmark)
3564 			return 0;
3565 	return 1;
3566 }
3567 
3568 static void
3569 tkadjpage(Tk *tk, int ody, int *dy)
3570 {
3571 	int y, a, b, d;
3572 	TkTindex ix;
3573 	TkTline *l;
3574 
3575 	d = *dy-ody;
3576 	y = d > 0 ? tk->act.height : 0;
3577 	tktxyind(tk, 0, y-d, &ix);
3578 	if((l = ix.line) != nil && istext(l)){
3579 		a = l->orig.y;
3580 		b = a+l->height;
3581 /* print("AP: %d %d %d (%d+%d)\n", a, ody+y, b, ody, y); */
3582 		if(a+2 < ody+y && ody+y < b-2){	/* partially obscured line */
3583 			if(d > 0)
3584 				*dy -= ody+y-a;
3585 			else
3586 				*dy += b-ody;
3587 		}
3588 	}
3589 }
3590 
3591 static char*
3592 tktextyview(Tk *tk, char *arg, char **val)
3593 {
3594 	int ntot, vh, d;
3595 	char *e;
3596 	TkTline *l;
3597 	Point odeltatv;
3598 	TkTindex ix;
3599 	TkText *tkt = TKobj(TkText, tk);
3600 	char buf[Tkmaxitem], *v;
3601 
3602 	if(*arg != '\0') {
3603 		v = tkitem(buf, arg);
3604 		if(strcmp(buf, "-pickplace") == 0)
3605 			return tktextsee(tk,v, val);
3606 		if(strcmp(buf, "moveto") != 0 && strcmp(buf, "scroll") != 0) {
3607 			e = tktindparse(tk, &arg, &ix);
3608 			if(e != nil)
3609 				return e;
3610 			tktsee(tk, &ix, 1);
3611 			return nil;
3612 		}
3613 	}
3614 	odeltatv = tkt->deltatv;
3615 	vh = tk->act.height;
3616 	l =  tkt->end.prev;
3617 	ntot = l->orig.y + l->height;
3618 //	if(ntot < tkt->deltatv.y + vh)
3619 //		ntot = tkt->deltatv.y + vh;
3620 	e = tktview(tk, arg, val, vh, &tkt->deltatv.y, ntot, Tkvertical);
3621 	d = tkt->deltatv.y-odeltatv.y;
3622 	if(d == vh || d == -vh)
3623 		tkadjpage(tk, odeltatv.y, &tkt->deltatv.y);
3624 	if(e == nil) {
3625 		e = tktsetscroll(tk, Tkvertical);
3626 		if(e == nil)
3627 			tktfixscroll(tk, odeltatv);
3628 	}
3629 	return e;
3630 }
3631 static void
3632 tktextfocusorder(Tk *tk)
3633 {
3634 	TkTindex ix;
3635 	TkText *t;
3636 	Tk *isub;
3637 
3638 	t = TKobj(TkText, tk);
3639 	tktstartind(t, &ix);
3640 	do {
3641 		if(ix.item->kind == TkTwin) {
3642 			isub = ix.item->iwin->sub;
3643 			if(isub != nil)
3644 				tkappendfocusorder(isub);
3645 		}
3646 	} while(tktadjustind(t, TkTbyitem, &ix));
3647 }
3648 
3649 TkCmdtab tktextcmd[] =
3650 {
3651 	"bbox",			tktextbbox,
3652 	"cget",			tktextcget,
3653 	"compare",		tktextcompare,
3654 	"configure",		tktextconfigure,
3655 	"debug",		tktextdebug,
3656 	"delete",		tktextdelete,
3657 	"dlineinfo",		tktextdlineinfo,
3658 	"dump",			tktextdump,
3659 	"get",			tktextget,
3660 	"index",		tktextindex,
3661 	"insert",		tktextinsert,
3662 	"mark",			tktextmark,
3663 	"scan",			tktextscan,
3664 	"search",		tktextsearch,
3665 	"see",			tktextsee,
3666 	"selection",		tktextselection,
3667 	"tag",			tktexttag,
3668 	"window",		tktextwindow,
3669 	"xview",		tktextxview,
3670 	"yview",		tktextyview,
3671 	"tkTextButton1",	tktextbutton1,
3672 	"tkTextButton1R",	tktextbutton1r,
3673 	"tkTextDelIns",		tktextdelins,
3674 	"tkTextInsert",		tktextinserti,
3675 	"tkTextSelectTo",	tktextselectto,
3676 	"tkTextSetCursor",	tktextsetcursor,
3677 	"tkTextScrollPages",	tktextscrollpages,
3678 	"tkTextCursor",		tktextcursor,
3679 	nil
3680 };
3681 
3682 TkMethod textmethod = {
3683 	"text",
3684 	tktextcmd,
3685 	tkfreetext,
3686 	tkdrawtext,
3687 	tktextgeom,
3688 	nil,
3689 	tktextfocusorder,
3690 	tktdirty,
3691 	tktrelpos,
3692 	tktextevent,
3693 	nil,				/* XXX need to implement textsee */
3694 	tktinwindow
3695 };
3696