xref: /inferno-os/libtk/ttags.c (revision d0e1d143ef6f03c75c008c7ec648859dd260cbab)
1 #include "lib9.h"
2 #include "draw.h"
3 #include "tk.h"
4 #include "textw.h"
5 
6 #define istring u.string
7 #define iwin u.win
8 #define imark u.mark
9 #define iline u.line
10 
11 static char* tkttagadd(Tk*, char*, char**);
12 static char* tkttagbind(Tk*, char*, char**);
13 static char* tkttagcget(Tk*, char*, char**);
14 static char* tkttagconfigure(Tk*, char*, char**);
15 static char* tkttagdelete(Tk*, char*, char**);
16 static char* tkttaglower(Tk*, char*, char**);
17 static char* tkttagnames(Tk*, char*, char**);
18 static char* tkttagnextrange(Tk*, char*, char**);
19 static char* tkttagprevrange(Tk*, char*, char**);
20 static char* tkttagraise(Tk*, char*, char**);
21 static char* tkttagranges(Tk*, char*, char**);
22 static char* tkttagremove(Tk*, char*, char**);
23 
24 #define	O(t, e)		((long)(&((t*)0)->e))
25 
26 #define TKTEO		(O(TkTtaginfo, env))
27 static
28 TkOption tagopts[] =
29 {
30 	"borderwidth",
31 		OPTnndist, O(TkTtaginfo, opts[TkTborderwidth]),	nil,
32 	"justify",
33 		OPTstab, O(TkTtaginfo, opts[TkTjustify]),	tkjustify,
34 	"lineheight",
35 		OPTnndist, O(TkTtaginfo, opts[TkTlineheight]),	IAUX(TKTEO),
36 	"lmargin1",
37 		OPTdist, O(TkTtaginfo, opts[TkTlmargin1]),	IAUX(TKTEO),
38 	"lmargin2",
39 		OPTdist, O(TkTtaginfo, opts[TkTlmargin2]),	IAUX(TKTEO),
40 	"lmargin3",
41 		OPTdist, O(TkTtaginfo, opts[TkTlmargin3]),	IAUX(TKTEO),
42 	"rmargin",
43 		OPTdist, O(TkTtaginfo, opts[TkTrmargin]),	IAUX(TKTEO),
44 	"spacing1",
45 		OPTnndist, O(TkTtaginfo, opts[TkTspacing1]),	IAUX(TKTEO),
46 	"spacing2",
47 		OPTnndist, O(TkTtaginfo, opts[TkTspacing2]),	IAUX(TKTEO),
48 	"spacing3",
49 		OPTnndist, O(TkTtaginfo, opts[TkTspacing3]),	IAUX(TKTEO),
50 	"offset",
51 		OPTdist, O(TkTtaginfo, opts[TkToffset]),	IAUX(TKTEO),
52 	"underline",
53 		OPTstab, O(TkTtaginfo, opts[TkTunderline]),	tkbool,
54 	"overstrike",
55 		OPTstab, O(TkTtaginfo, opts[TkToverstrike]),	tkbool,
56 	"relief",
57 		OPTstab, O(TkTtaginfo, opts[TkTrelief]),	tkrelief,
58 	"tabs",
59 		OPTtabs, O(TkTtaginfo, tabs),			IAUX(TKTEO),
60 	"wrap",
61 		OPTstab, O(TkTtaginfo, opts[TkTwrap]),		tkwrap,
62 	nil,
63 };
64 
65 static
66 TkOption tagenvopts[] =
67 {
68 	"foreground",	OPTcolr,	O(TkTtaginfo, env),	IAUX(TkCforegnd),
69 	"background",	OPTcolr,	O(TkTtaginfo, env),	IAUX(TkCbackgnd),
70 	"fg",		OPTcolr,	O(TkTtaginfo, env),	IAUX(TkCforegnd),
71 	"bg",		OPTcolr,	O(TkTtaginfo, env),	IAUX(TkCbackgnd),
72 	"font",		OPTfont,	O(TkTtaginfo, env),	nil,
73 	nil
74 };
75 
76 TkCmdtab
77 tkttagcmd[] =
78 {
79 	"add",		tkttagadd,
80 	"bind",		tkttagbind,
81 	"cget",		tkttagcget,
82 	"configure",	tkttagconfigure,
83 	"delete",	tkttagdelete,
84 	"lower",	tkttaglower,
85 	"names",	tkttagnames,
86 	"nextrange",	tkttagnextrange,
87 	"prevrange",	tkttagprevrange,
88 	"raise",	tkttagraise,
89 	"ranges",	tkttagranges,
90 	"remove",	tkttagremove,
91 	nil
92 };
93 
94 int
95 tktanytags(TkTitem *it)
96 {
97 	int i;
98 
99 	if(it->tagextra == 0)
100 		return (it->tags[0] != 0);
101 	for(i = 0; i <= it->tagextra; i++)
102 		if(it->tags[i] != 0)
103 			return 1;
104 	return 0;
105 }
106 
107 int
108 tktsametags(TkTitem *i1, TkTitem *i2)
109 {
110 	int i, j;
111 
112 	for(i = 0; i <= i1->tagextra && i <= i2->tagextra; i++)
113 		if(i1->tags[i] != i2->tags[i])
114 			return 0;
115 	for(j = i; j <= i1->tagextra; j++)
116 		if(i1->tags[j] != 0)
117 			return 0;
118 	for(j = i; j <= i2->tagextra; j++)
119 		if(i2->tags[j] != 0)
120 			return 0;
121 	return 1;
122 }
123 
124 int
125 tkttagset(TkTitem *it, int id)
126 {
127 	int i;
128 
129 	if(it->tagextra == 0 && it->tags[0] == 0)
130 		return 0;
131 	for(i = 0; i <= it->tagextra; i++) {
132 		if(id < 32)
133 			return ((it->tags[i] & (1<<id)) != 0);
134 		id -= 32;
135 	}
136 	return 0;
137 }
138 
139 char *
140 tkttagname(TkText *tkt, int id)
141 {
142 	TkTtaginfo *t;
143 
144 	for(t = tkt->tags; t != nil; t = t->next) {
145 		if(t->id == id)
146 			return t->name;
147 	}
148 	return "";
149 }
150 
151 /* return 1 if this actually changes the value */
152 int
153 tkttagbit(TkTitem *it, int id, int val)
154 {
155 	int i, changed;
156 	ulong z, b;
157 
158 	changed = 0;
159 	for(i = 0; i <= it->tagextra; i++) {
160 		if(id < 32) {
161 			b = (1<<id);
162 			z = it->tags[i];
163 			if(val == 0) {
164 				if(z & b) {
165 					changed = 1;
166 					it->tags[i] = z & (~b);
167 				}
168 			}
169 			else {
170 				if((z & b) == 0) {
171 					changed = 1;
172 					it->tags[i] = z | b;
173 				}
174 			}
175 			break;
176 		}
177 		id -= 32;
178 	}
179 	return changed;
180 }
181 
182 void
183 tkttagcomb(TkTitem *i1, TkTitem *i2, int add)
184 {
185 	int i;
186 
187 	for(i = 0; i <= i1->tagextra && i <= i2->tagextra; i++) {
188 		if(add == 1)
189 			i1->tags[i] |= i2->tags[i];
190 		else if(add == 0)
191 			/* intersect */
192 			i1->tags[i] &= i2->tags[i];
193 		else
194 			/* subtract */
195 			i1->tags[i] &= ~i2->tags[i];
196 	}
197 }
198 
199 char*
200 tktaddtaginfo(Tk *tk, char *name, TkTtaginfo **ret)
201 {
202 	int i, *ntagp;
203 	TkTtaginfo *ti;
204 	TkText *tkt, *tktshare;
205 
206 	tkt = TKobj(TkText, tk);
207 	ti = malloc(sizeof(TkTtaginfo));
208 	if(ti == nil)
209 		return TkNomem;
210 
211 	ntagp = &tkt->nexttag;
212 	if(tkt->tagshare != nil) {
213 		tktshare = TKobj(TkText, tkt->tagshare);
214 		ntagp = &tktshare->nexttag;
215 	}
216 	ti->id = *ntagp;
217 	ti->name = strdup(name);
218 	if(ti->name == nil) {
219 		free(ti);
220 		return TkNomem;
221 	}
222 	ti->env = tknewenv(tk->env->top);
223 	if(ti->env == nil) {
224 		free(ti->name);
225 		free(ti);
226 		return TkNomem;
227 	}
228 
229 	ti->tabs = nil;
230 	for(i = 0; i < TkTnumopts; i++)
231 		ti->opts[i] = TkTunset;
232 	ti->next = tkt->tags;
233 	tkt->tags = ti;
234 
235 	(*ntagp)++;
236 	if(tkt->tagshare)
237 		tkt->nexttag = *ntagp;
238 
239 	*ret = ti;
240 	return nil;
241 }
242 
243 TkTtaginfo *
244 tktfindtag(TkTtaginfo *t, char *name)
245 {
246 	while(t != nil) {
247 		if(strcmp(t->name, name) == 0)
248 			return t;
249 		t = t->next;
250 	}
251 	return nil;
252 }
253 
254 void
255 tktfreetags(TkTtaginfo *t)
256 {
257 	TkTtaginfo *n;
258 
259 	while(t != nil) {
260 		n = t->next;
261 		free(t->name);
262 		tktfreetabs(t->tabs);
263 		tkputenv(t->env);
264 		tkfreebind(t->binds);
265 		free(t);
266 		t = n;
267 	}
268 }
269 
270 int
271 tkttagind(Tk *tk, char *name, int first, TkTindex *ans)
272 {
273 	int id;
274 	TkTtaginfo *t;
275 	TkText *tkt;
276 
277 	tkt = TKobj(TkText, tk);
278 
279 	if(strcmp(name, "sel") == 0) {
280 		if(tkt->selfirst == nil)
281 			return 0;
282 		if(first)
283 			tktitemind(tkt->selfirst, ans);
284 		else
285 			tktitemind(tkt->sellast, ans);
286 		return 1;
287 	}
288 
289 	t = tktfindtag(tkt->tags, name);
290 	if(t == nil)
291 		return 0;
292 	id = t->id;
293 
294 	if(first) {
295 		tktstartind(tkt, ans);
296 		while(!tkttagset(ans->item, id))
297 			if(!tktadjustind(tkt, TkTbyitem, ans))
298 				return 0;
299 	}
300 	else {
301 		tktendind(tkt, ans);
302 		while(!tkttagset(ans->item, id))
303 			if(!tktadjustind(tkt, TkTbyitemback, ans))
304 				return 0;
305 		tktadjustind(tkt, TkTbyitem, ans);
306 	}
307 
308 	return 1;
309 }
310 
311 /*
312  * Fill in opts and e, based on info from tags set in it,
313  * using tags order for priority.
314  * If dflt != 0, options not set are filled from tk,
315  * otherwise iInteger options not set by any tag are left 'TkTunset'
316  * and environment values not set are left nil.
317  */
318 void
319 tkttagopts(Tk *tk, TkTitem *it, int *opts, TkEnv *e, TkTtabstop **tb, int dflt)
320 {
321 	int i;
322 	int colset;
323 	TkEnv *te;
324 	TkTtaginfo *tags;
325 	TkText *tkt = TKobj(TkText, tk);
326 
327 	if (tb != nil)
328 		*tb = tkt->tabs;
329 
330 	tags = tkt->tags;
331 
332 	if(opts != nil)
333 		for(i = 0; i < TkTnumopts; i++)
334 			opts[i] = TkTunset;
335 
336 	memset(e, 0, sizeof(TkEnv));
337 	e->top = tk->env->top;
338 	colset = 0;
339 	while(tags != nil) {
340 		if(tkttagset(it, tags->id)) {
341 			if(opts != nil) {
342 				for(i = 0; i < TkTnumopts; i++) {
343 					if(opts[i] == TkTunset && tags->opts[i] != TkTunset)
344 						opts[i] = tags->opts[i];
345 				}
346 			}
347 
348 			te = tags->env;
349 			for(i = 0; i < TkNcolor; i++)
350 				if(!(colset & (1<<i)) && te->set & (1<<i)) {
351 					e->colors[i] = te->colors[i];
352 					colset |= 1<<i;
353 				}
354 
355 			if(e->font == nil && te->font != nil)
356 				e->font = te->font;
357 
358 			if (tb != nil && tags->tabs != nil)
359 				*tb = tags->tabs;
360 		}
361 		tags = tags->next;
362 	}
363 	e->set |= colset;
364 	if(dflt) {
365 		if(opts != nil) {
366 			for(i = 0; i < TkTnumopts; i++)
367 				if(opts[i] == TkTunset)
368 					opts[i] = tkt->opts[i];
369 		}
370 		te = tk->env;
371 		for(i = 0; i < TkNcolor; i++)
372 			if(!(e->set & (1<<i))) {
373 				e->colors[i] = te->colors[i];
374 				e->set |= 1<<i;
375 			}
376 		if(e->font == nil)
377 			e->font = te->font;
378 	}
379 }
380 
381 char*
382 tkttagparse(Tk *tk, char **parg, TkTtaginfo **ret)
383 {
384 	char *e, *buf;
385 	TkText *tkt = TKobj(TkText, tk);
386 
387 	buf = mallocz(Tkmaxitem, 0);
388 	if(buf == nil)
389 		return TkNomem;
390 	*parg = tkword(tk->env->top, *parg, buf, buf+Tkmaxitem, nil);
391 	if(*buf == '\0') {
392 		free(buf);
393 		return TkOparg;
394 	}
395 	if(buf[0] >= '0' && buf[0] <= '9'){
396 		free(buf);
397 		return TkBadtg;
398 	}
399 
400 	*ret = tktfindtag(tkt->tags, buf);
401 	if(*ret == nil) {
402 		e = tktaddtaginfo(tk, buf, ret);
403 		if(e != nil) {
404 			free(buf);
405 			return e;
406 		}
407 	}
408 	free(buf);
409 
410 	return nil;
411 }
412 
413 int
414 tkttagnrange(TkText *tkt, int tid, TkTindex *i1, TkTindex *i2,
415 			TkTindex *istart, TkTindex *iend)
416 {
417 	int found;
418 
419 	found = 0;
420 	while(i1->line != &tkt->end) {
421 		if(i1->item == i2->item && i2->pos == 0)
422 			break;
423 		if(tkttagset(i1->item, tid)) {
424 			if(!found) {
425 				found = 1;
426 				*istart = *i1;
427 			}
428 			if(i1->item == i2->item) {
429 				/* i2->pos > 0 */
430 				*iend = *i2;
431 				return 1;
432 			}
433 		}
434 		else
435 		if(i1->item == i2->item || (found && i1->item->kind != TkTmark && i1->item->kind != TkTcontline))
436 			break;
437 		tktadjustind(tkt, TkTbyitem, i1);
438 	}
439 	if(found)
440 		*iend = *i1;
441 
442 	return found;
443 }
444 
445 static int
446 tkttagprange(TkText *tkt, int tid, TkTindex *i1, TkTindex *i2,
447 			TkTindex *istart, TkTindex *iend)
448 {
449 	int found;
450 
451 	found = 0;
452 	while(i1->line != &tkt->start && i1->item != i2->item) {
453 		tktadjustind(tkt, TkTbyitemback, i1);
454 		if(tkttagset(i1->item, tid)) {
455 			if(!found) {
456 				found = 1;
457 				*iend = *i1;
458 			}
459 		}
460 		else
461 		if(found && i1->item->kind != TkTmark && i1->item->kind != TkTcontline)
462 			break;
463 	}
464 	if(found) {
465 		tktadjustind(tkt, TkTbyitem, i1);
466 		*istart = *i1;
467 		if(i1->item == i2->item)
468 			istart->pos = i2->pos;
469 	}
470 
471 	return found;
472 }
473 
474 /* XXX - Tad: potential memory leak on memory allocation failure */
475 char *
476 tkttagchange(Tk *tk, int tid, TkTindex *i1, TkTindex *i2, int add)
477 {
478 	char *e;
479 	int samei, nextra, j, changed;
480 	TkTline *lmin, *lmax;
481 	TkTindex ixprev;
482 	TkTitem *nit;
483 	TkText *tkt = TKobj(TkText, tk);
484 
485 	if(!tktindbefore(i1, i2))
486 		return nil;
487 
488 	nextra = tid/32;
489 	lmin = nil;
490 	lmax = nil;
491 	tktadjustind(tkt, TkTbycharstart, i1);
492 	tktadjustind(tkt, TkTbycharstart, i2);
493 	samei = (i1->item == i2->item);
494 	if(i2->pos != 0) {
495 		e = tktsplititem(i2);
496 		if(e != nil)
497 			return e;
498 		if(samei) {
499 			/* split means i1 should now point to previous item */
500 			ixprev = *i2;
501 			tktadjustind(tkt, TkTbyitemback, &ixprev);
502 			i1->item = ixprev.item;
503 		}
504 	}
505 	if(i1->pos != 0) {
506 		e = tktsplititem(i1);
507 		if(e != nil)
508 			return e;
509 	}
510 	/* now i1 and i2 both point to beginning of non-mark/contline items */
511 	if(tid == TkTselid) {
512 		/*
513 		 * Cache location of selection.
514 		 * Note: there can be only one selection range in widget
515 		 */
516 		if(add) {
517 			if(tkt->selfirst != nil)
518 				return TkBadsl;
519 			tkt->selfirst = i1->item;
520 			tkt->sellast = i2->item;
521 		}
522 		else {
523 			tkt->selfirst = nil;
524 			tkt->sellast = nil;
525 		}
526 	}
527 	while(i1->item != i2->item) {
528 		if(i1->item->kind != TkTmark && i1->item->kind != TkTcontline) {
529 			if(tid >= 32 && i1->item->tagextra < nextra) {
530 				nit = realloc(i1->item, sizeof(TkTitem) + nextra * sizeof(long));
531 				if(nit == nil)
532 					return TkNomem;
533 				for(j = nit->tagextra+1; j <= nextra; j++)
534 					nit->tags[j] = 0;
535 				nit->tagextra = nextra;
536 				if(i1->line->items == i1->item)
537 					i1->line->items = nit;
538 				else {
539 					ixprev = *i1;
540 					tktadjustind(tkt, TkTbyitemback, &ixprev);
541 					ixprev.item->next = nit;
542 				}
543 				/* check nit against cached items */
544 				if(tkt->selfirst == i1->item)
545 					tkt->selfirst = nit;
546 				if(tkt->sellast == i1->item)
547 					tkt->sellast = nit;
548 				i1->item = nit;
549 			}
550 			changed = tkttagbit(i1->item, tid, add);
551 			if(lmin == nil) {
552 				if(changed) {
553 					lmin = i1->line;
554 					lmax = lmin;
555 				}
556 			}
557 			else {
558 				if(changed)
559 					lmax = i1->line;
560 			}
561 		}
562 		if(!tktadjustind(tkt, TkTbyitem, i1))
563 			break;
564 	}
565 	if(lmin != nil) {
566 		tktfixgeom(tk, tktprevwrapline(tk, lmin), lmax, 0);
567 		tktextsize(tk, 1);
568 	}
569 	return nil;
570 }
571 
572 static char*
573 tkttagaddrem(Tk *tk, char *arg, int add)
574 {
575 	char *e;
576 	TkText *tkt;
577 	TkTtaginfo *ti;
578 	TkTindex ix1, ix2;
579 
580 	tkt = TKobj(TkText, tk);
581 
582 	e = tkttagparse(tk, &arg, &ti);
583 	if(e != nil)
584 		return e;
585 
586 	while(*arg != '\0') {
587 		e = tktindparse(tk, &arg, &ix1);
588 		if(e != nil)
589 			return e;
590 		if(*arg != '\0') {
591 			e = tktindparse(tk, &arg, &ix2);
592 			if(e != nil)
593 				return e;
594 		}
595 		else {
596 			ix2 = ix1;
597 			tktadjustind(tkt, TkTbychar, &ix2);
598 		}
599 		if(!tktindbefore(&ix1, &ix2))
600 			continue;
601 
602 		e = tkttagchange(tk, ti->id, &ix1, &ix2, add);
603 		if(e != nil)
604 			return e;
605 	}
606 
607 	return nil;
608 }
609 
610 
611 /* Text Tag Command (+ means implemented)
612 	+add
613 	+bind
614 	+cget
615 	+configure
616 	+delete
617 	+lower
618 	+names
619 	+nextrange
620 	+prevrange
621 	+raise
622 	+ranges
623 	+remove
624 */
625 
626 static char*
627 tkttagadd(Tk *tk, char *arg, char **val)
628 {
629 	USED(val);
630 
631 	return tkttagaddrem(tk, arg, 1);
632 }
633 
634 static char*
635 tkttagbind(Tk *tk, char *arg, char **val)
636 {
637 	char *e;
638 	Rune r;
639 	TkTtaginfo *ti;
640 	TkAction *a;
641 	int event, mode;
642 	char *cmd, buf[Tkmaxitem];
643 
644 
645 	e = tkttagparse(tk, &arg, &ti);
646 	if(e != nil)
647 		return e;
648 
649 	arg = tkskip(arg, " \t");
650 	if (arg[0] == '\0')
651 		return TkBadsq;
652 	arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
653 	if(buf[0] == '<') {
654 		event = tkseqparse(buf+1);
655 		if(event == -1)
656 			return TkBadsq;
657 	}
658 	else {
659 		chartorune(&r, buf);
660 		event = TkKey | r;
661 	}
662 	if(event == 0)
663 		return TkBadsq;
664 
665 	arg = tkskip(arg, " \t");
666 	if(*arg == '\0') {
667 		for(a = ti->binds; a; a = a->link)
668 				if(event == a->event)
669 					return tkvalue(val, "%s", a->arg);
670 		return nil;
671 	}
672 
673 	mode = TkArepl;
674 	if(*arg == '+') {
675 		mode = TkAadd;
676 		arg++;
677 	}
678 	else if(*arg == '-'){
679 		mode = TkAsub;
680 		arg++;
681 	}
682 
683 	tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
684 	cmd = strdup(buf);
685 	if(cmd == nil)
686 		return TkNomem;
687 	return tkaction(&ti->binds, event, TkDynamic, cmd, mode);
688 }
689 
690 static char*
691 tkttagcget(Tk *tk, char *arg, char **val)
692 {
693 	char *e;
694 	TkTtaginfo *ti;
695 	TkOptab tko[3];
696 
697 	e = tkttagparse(tk, &arg, &ti);
698 	if(e != nil)
699 		return e;
700 
701 	tko[0].ptr = ti;
702 	tko[0].optab = tagopts;
703 	tko[1].ptr = ti;
704 	tko[1].optab = tagenvopts;
705 	tko[2].ptr = nil;
706 
707 	return tkgencget(tko, arg, val, tk->env->top);
708 }
709 
710 static char*
711 tkttagconfigure(Tk *tk, char *arg, char **val)
712 {
713 	char *e;
714 	TkOptab tko[3];
715 	TkTtaginfo *ti;
716 	TkTindex ix;
717 	TkText *tkt = TKobj(TkText, tk);
718 
719 	USED(val);
720 
721 	e = tkttagparse(tk, &arg, &ti);
722 	if(e != nil)
723 		return e;
724 
725 	tko[0].ptr = ti;
726 	tko[0].optab = tagopts;
727 	tko[1].ptr = ti;
728 	tko[1].optab = tagenvopts;
729 	tko[2].ptr = nil;
730 
731 	e = tkparse(tk->env->top, arg, tko, nil);
732 	if(e != nil)
733 		return e;
734 
735 	if(tkttagind(tk, ti->name, 1, &ix)) {
736 		tktfixgeom(tk, tktprevwrapline(tk, ix.line), tkt->end.prev, 0);
737 		tktextsize(tk, 1);
738 	}
739 
740 	return nil;
741 }
742 
743 static void
744 tktunlinktag(TkText *tkt, TkTtaginfo *t)
745 {
746 	TkTtaginfo *f, **l;
747 
748 	l = &tkt->tags;
749 	for(f = *l; f != nil; f = f->next) {
750 		if(f == t) {
751 			*l = t->next;
752 			return;
753 		}
754 		l = &f->next;
755 	}
756 }
757 
758 static char*
759 tkttagdelete(Tk *tk, char *arg, char **val)
760 {
761 	TkText *tkt;
762 	TkTtaginfo *t;
763 	TkTindex ix;
764 	char *e;
765 	int found;
766 
767 	USED(val);
768 
769 	tkt = TKobj(TkText, tk);
770 
771 	e = tkttagparse(tk, &arg, &t);
772 	if(e != nil)
773 		return e;
774 
775 	found = 0;
776 	while(t != nil) {
777 		if(t->id == TkTselid)
778 			return TkBadvl;
779 
780 		while(tkttagind(tk, t->name, 1, &ix)) {
781 			found = 1;
782 			tkttagbit(ix.item, t->id, 0);
783 		}
784 
785 		tktunlinktag(tkt, t);
786 		t->next = nil;
787 		tktfreetags(t);
788 
789 		if(*arg != '\0') {
790 			e = tkttagparse(tk, &arg, &t);
791 			if(e != nil)
792 				return e;
793 		}
794 		else
795 			t = nil;
796 	}
797 	if (found) {
798 		tktfixgeom(tk, &tkt->start, tkt->end.prev, 0);
799 		tktextsize(tk, 1);
800 	}
801 
802 	return nil;
803 }
804 
805 static char*
806 tkttaglower(Tk *tk, char *arg, char **val)
807 {
808 	TkText *tkt;
809 	TkTindex ix;
810 	TkTtaginfo *t, *tbelow, *f, **l;
811 	char *e;
812 
813 	USED(val);
814 
815 	tkt = TKobj(TkText, tk);
816 
817 	e = tkttagparse(tk, &arg, &t);
818 	if(e != nil)
819 		return e;
820 
821 	if(*arg != '\0') {
822 		e = tkttagparse(tk, &arg, &tbelow);
823 		if(e != nil)
824 			return e;
825 	}
826 	else
827 		tbelow = nil;
828 
829 	tktunlinktag(tkt, t);
830 
831 	if(tbelow != nil) {
832 		t->next = tbelow->next;
833 		tbelow->next = t;
834 	}
835 	else {
836 		l = &tkt->tags;
837 		for(f = *l; f != nil; f = f->next)
838 			l = &f->next;
839 		*l = t;
840 		t->next = nil;
841 	}
842 	if(tkttagind(tk, t->name, 1, &ix)) {
843 		tktfixgeom(tk, tktprevwrapline(tk, ix.line), tkt->end.prev, 0);
844 		tktextsize(tk, 1);
845 	}
846 
847 	return nil;
848 }
849 
850 
851 static char*
852 tkttagnames(Tk *tk, char *arg, char **val)
853 {
854 	char *e, *r, *fmt;
855 	TkTtaginfo *t;
856 	TkTindex i;
857 	TkText *tkt = TKobj(TkText, tk);
858 	TkTitem *tagit;
859 
860 	if(*arg != '\0') {
861 		e = tktindparse(tk, &arg, &i);
862 		if(e != nil)
863 			return e;
864 		/* make sure we're actually on a character */
865 		tktadjustind(tkt, TkTbycharstart, &i);
866 		tagit = i.item;
867 	}
868 	else
869 		tagit = nil;
870 
871 	/* generate in order highest-to-lowest priority (contrary to spec) */
872 	fmt = "%s";
873 	for(t = tkt->tags; t != nil; t = t->next) {
874 		if(tagit == nil || tkttagset(tagit, t->id)) {
875 			r = tkvalue(val, fmt, t->name);
876 			if(r != nil)
877 				return r;
878 			fmt = " %s";
879 		}
880 	}
881 	return nil;
882 }
883 
884 static char*
885 tkttagnextrange(Tk *tk, char *arg, char **val)
886 {
887 	char *e;
888 	TkTtaginfo *t;
889 	TkTindex i1, i2, istart, iend;
890 	TkText *tkt = TKobj(TkText, tk);
891 
892 	e = tkttagparse(tk, &arg, &t);
893 	if(e != nil)
894 		return e;
895 	e = tktindparse(tk, &arg, &i1);
896 	if(e != nil)
897 		return e;
898 	if(*arg != '\0') {
899 		e = tktindparse(tk, &arg, &i2);
900 		if(e != nil)
901 			return e;
902 	}
903 	else
904 		tktendind(tkt, &i2);
905 
906 	if(tkttagnrange(tkt, t->id, &i1, &i2, &istart, &iend))
907 		return tkvalue(val, "%d.%d %d.%d",
908 			tktlinenum(tkt, &istart), tktlinepos(tkt, &istart),
909 			tktlinenum(tkt, &iend), tktlinepos(tkt, &iend));
910 
911 	return nil;
912 }
913 
914 static char*
915 tkttagprevrange(Tk *tk, char *arg, char **val)
916 {
917 	char *e;
918 	TkTtaginfo *t;
919 	TkTindex i1, i2, istart, iend;
920 	TkText *tkt = TKobj(TkText, tk);
921 
922 	e = tkttagparse(tk, &arg, &t);
923 	if(e != nil)
924 		return e;
925 	e = tktindparse(tk, &arg, &i1);
926 	if(e != nil)
927 		return e;
928 	if(*arg != '\0') {
929 		e = tktindparse(tk, &arg, &i2);
930 		if(e != nil)
931 			return e;
932 	}
933 	else
934 		tktstartind(tkt, &i2);
935 
936 	if(tkttagprange(tkt, t->id, &i1, &i2, &istart, &iend))
937 		return tkvalue(val, "%d.%d %d.%d",
938 			tktlinenum(tkt, &istart), tktlinepos(tkt, &istart),
939 			tktlinenum(tkt, &iend), tktlinepos(tkt, &iend));
940 
941 	return nil;
942 }
943 
944 static char*
945 tkttagraise(Tk *tk, char *arg, char **val)
946 {
947 	TkText *tkt;
948 	TkTindex ix;
949 	TkTtaginfo *t, *tabove, *f, **l;
950 	char *e;
951 
952 	USED(val);
953 
954 	tkt = TKobj(TkText, tk);
955 
956 	e = tkttagparse(tk, &arg, &t);
957 	if(e != nil)
958 		return e;
959 
960 	if(*arg != '\0') {
961 		e = tkttagparse(tk, &arg, &tabove);
962 		if(e != nil)
963 			return e;
964 	}
965 	else
966 		tabove = nil;
967 
968 	tktunlinktag(tkt, t);
969 
970 	if(tabove != nil) {
971 		l = &tkt->tags;
972 		for(f = *l; f != nil; f = f->next) {
973 			if(f == tabove) {
974 				*l = t;
975 				t->next = tabove;
976 				break;
977 			}
978 			l = &f->next;
979 		}
980 	}
981 	else {
982 		t->next = tkt->tags;
983 		tkt->tags = t;
984 	}
985 
986 	if(tkttagind(tk, t->name, 1, &ix)) {
987 		tktfixgeom(tk, tktprevwrapline(tk, ix.line), tkt->end.prev, 0);
988 		tktextsize(tk, 1);
989 	}
990 	return nil;
991 }
992 
993 static char*
994 tkttagranges(Tk *tk, char *arg, char **val)
995 {
996 	char *e, *fmt;
997 	TkTtaginfo *t;
998 	TkTindex i1, i2, istart, iend;
999 	TkText *tkt = TKobj(TkText, tk);
1000 
1001 	e = tkttagparse(tk, &arg, &t);
1002 	if(e != nil)
1003 		return e;
1004 
1005 	tktstartind(tkt, &i1);
1006 	tktendind(tkt, &i2);
1007 
1008 	fmt = "%d.%d %d.%d";
1009 	while(tkttagnrange(tkt, t->id, &i1, &i2, &istart, &iend)) {
1010 		e = tkvalue(val, fmt,
1011 			tktlinenum(tkt, &istart), tktlinepos(tkt, &istart),
1012 			tktlinenum(tkt, &iend), tktlinepos(tkt, &iend));
1013 		if(e != nil)
1014 			return e;
1015 
1016 		fmt = " %d.%d %d.%d";
1017 		i1 = iend;
1018 	}
1019 
1020 	return nil;
1021 }
1022 
1023 static char*
1024 tkttagremove(Tk *tk, char *arg, char **val)
1025 {
1026 	USED(val);
1027 
1028 	return tkttagaddrem(tk, arg, 0);
1029 }
1030