xref: /inferno-os/libtk/tindx.c (revision 7ef44d652ae9e5e1f5b3465d73684e4a54de73c0)
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 /* debugging */
12 extern int tktdbg;
13 extern void tktprinttext(TkText*);
14 extern void tktprintindex(TkTindex*);
15 extern void tktprintitem(TkTitem*);
16 extern void tktprintline(TkTline*);
17 
18 char*
19 tktindparse(Tk *tk, char **pspec, TkTindex *ans)
20 {
21 	int m, n, done, neg, modstart;
22 	char *s, *mod;
23 	TkTline *lend;
24 	TkText *tkt;
25 	char *buf;
26 
27 	buf = mallocz(Tkmaxitem, 0);
28 	if(buf == nil)
29 		return TkNomem;
30 
31 	tkt = TKobj(TkText, tk);
32 	lend = &tkt->end;
33 
34 	*pspec = tkword(tk->env->top, *pspec, buf, buf+Tkmaxitem, nil);
35 	modstart = 0;
36 	for(mod = buf; *mod != '\0'; mod++)
37 		if(*mod == ' ' || *mod == '-' || *mod == '+') {
38 			modstart = *mod;
39 			*mod = '\0';
40 			break;
41 		}
42 
43 	/*
44 	 * XXX there's a problem here - if either coordinate is negative
45 	 * which shouldn't be precluded, then the above scanning code
46 	 * will break up the coordinate pair, so @-23,45 for example
47 	 * yields a bad index, when it should probably return the index
48 	 * of the character at the start of the line containing y=45.
49 	 * i've seen this cause wm/sh to crash.
50 	 */
51 	if(strcmp(buf, "end") == 0)
52 		tktendind(tkt, ans);
53 	else
54 	if(*buf == '@') {
55 		/* by coordinates */
56 
57 		s = strchr(buf, ',');
58 		if(s == nil) {
59 			free(buf);
60 			return TkBadix;
61 		}
62 		*s = '\0';
63 		m = atoi(buf+1);
64 		n = atoi(s+1);
65 		tktxyind(tk, m, n, ans);
66 	}
67 	else
68 	if(*buf >= '0' && *buf <= '9') {
69 		/* line.char */
70 
71 		s = strchr(buf, '.');
72 		if(s == nil) {
73 			free(buf);
74 			return TkBadix;
75 		}
76 		*s = '\0';
77 		m = atoi(buf);
78 		n = atoi(s+1);
79 
80 		if(m < 1)
81 			m = 1;
82 
83 		tktstartind(tkt, ans);
84 
85 		while(--m > 0 && ans->line->next != lend)
86 			tktadjustind(tkt, TkTbyline, ans);
87 
88 		while(n-- > 0 && ans->item->kind != TkTnewline)
89 			tktadjustind(tkt, TkTbychar, ans);
90 	}
91 	else
92 	if(*buf == '.') {
93 		/* window */
94 
95 		tktstartind(tkt, ans);
96 
97 		while(ans->line != lend) {
98 			if(ans->item->kind == TkTwin &&
99 			   ans->item->iwin->sub != nil &&
100 			   ans->item->iwin->sub->name != nil &&
101 			   strcmp(ans->item->iwin->sub->name->name, buf) == 0)
102 				break;
103 			if(!tktadjustind(tkt, TkTbyitem, ans))
104 				ans->line = lend;
105 		}
106 		if(ans->line == lend) {
107 			free(buf);
108 			return TkBadix;
109 		}
110 	}
111 	else {
112 		s = strchr(buf, '.');
113 		if(s == nil) {
114 			if(tktmarkind(tk, buf, ans) == 0) {
115 				free(buf);
116 				return TkBadix;
117 			}
118 		}
119 		else {
120 			/* tag.first or tag.last */
121 
122 			*s = '\0';
123 			if(strcmp(s+1, "first") == 0) {
124 				if(tkttagind(tk, buf, 1, ans) == 0) {
125 					free(buf);
126 					return TkBadix;
127 				}
128 			}
129 			else
130 			if(strcmp(s+1, "last") == 0) {
131 				if(tkttagind(tk, buf, 0, ans) == 0) {
132 					free(buf);
133 					return TkBadix;
134 				}
135 			}
136 			else {
137 				free(buf);
138 				return TkBadix;
139 			}
140 		}
141 	}
142 
143 	if(modstart == 0) {
144 		free(buf);
145 		return nil;
146 	}
147 
148 	*mod = modstart;
149 	while(*mod == ' ')
150 		mod++;
151 
152 	while(*mod != '\0') {
153 		done = 0;
154 		switch(*mod) {
155 		case '+':
156 		case '-':
157 			neg = (*mod == '-');
158 			mod++;
159 			while(*mod == ' ')
160 				mod++;
161 			n = strtol(mod, &mod, 10);
162 			while(*mod == ' ')
163 				mod++;
164 			while(n-- > 0) {
165 				if(*mod == 'c')
166 					tktadjustind(tkt, neg? TkTbycharback : TkTbychar, ans);
167 				else
168 				if(*mod == 'l')
169 					tktadjustind(tkt, neg? TkTbylineback : TkTbyline, ans);
170 				else
171 					done = 1;
172 			}
173 			break;
174 		case 'l':
175 			if(strncmp(mod, "lines", 5) == 0)
176 				tktadjustind(tkt, TkTbylinestart, ans);
177 			else
178 			if(strncmp(mod, "linee", 5) == 0)
179 				tktadjustind(tkt, TkTbylineend, ans);
180 			else
181 				done = 1;
182 			break;
183 		case 'w':
184 			if(strncmp(mod, "words", 5) == 0)
185 				tktadjustind(tkt, TkTbywordstart, ans);
186 			else
187 			if(strncmp(mod, "worde", 5) == 0)
188 				tktadjustind(tkt, TkTbywordend, ans);
189 			else
190 				done = 1;
191 			break;
192 		default:
193 				done = 1;
194 		}
195 
196 		if(done)
197 			break;
198 
199 		while(tkiswordchar(*mod))
200 			mod++;
201 		while(*mod == ' ')
202 			mod++;
203 	}
204 
205 	free(buf);
206 	return nil;
207 }
208 
209 int
210 tktisbreak(int c)
211 {
212 	/* unicode rules suggest / as well but that would split dates, and URLs might as well char. wrap */
213 	return c == ' ' || c == '\t' || c == '\n' || c == '-' || c == ',';
214 	/* previously included . but would probably need more then to handle ." */
215 }
216 
217 /*
218  * Adjust the index p by units (one of TkTbyitem, etc.).
219  * The TkTbychar units mean that the final point should rest on a
220  * "character" (in text widget index space; i.e., a newline, a rune,
221  * and an embedded window are each 1 character, but marks and contlines are not).
222  *
223  * Indexes may not point in the tkt->start or tkt->end lines (which have
224  * no items); tktadjustind sticks at the beginning or end of the buffer.
225  *
226  * Return 1 if the index changes at all, 0 otherwise.
227  */
228 int
229 tktadjustind(TkText *tkt, int units, TkTindex *p)
230 {
231 	int n, opos, count, c;
232 	TkTitem *i, *it, *oit;
233 	TkTindex q;
234 
235 	oit = p->item;
236 	opos = p->pos;
237 	count = 1;
238 
239 	switch(units) {
240 	case TkTbyitemback:
241 		it = p->item;
242 		p->item = p->line->items;
243 		p->pos = 0;
244 		if(it == p->item) {
245 			if(p->line->prev != &tkt->start) {
246 				p->line = p->line->prev;
247 				p->item = tktlastitem(p->line->items);
248 			}
249 		}
250 		else {
251 			while(p->item->next != it) {
252 				p->item = p->item->next;
253 				if(tktdbg && p->item == nil) {
254 					print("tktadjustind: botch 1\n");
255 					break;
256 				}
257 			}
258 		}
259 		break;
260 
261 	case TkTbyitem:
262 		p->pos = 0;
263 		i = p->item->next;
264 		if(i == nil) {
265 			if(p->line->next != &tkt->end) {
266 				p->line = p->line->next;
267 				p->item = p->line->items;
268 			}
269 		}
270 		else
271 			p->item = i;
272 		break;
273 
274 	case TkTbytlineback:
275 		if(p->line->prev != &tkt->start)
276 			p->line = p->line->prev;
277 		p->item = p->line->items;
278 		p->pos = 0;
279 		break;
280 
281 	case TkTbytline:
282 		if(p->line->next != &tkt->end)
283 			p->line = p->line->next;
284 		p->item = p->line->items;
285 		p->pos = 0;
286 		break;
287 
288 	case TkTbycharstart:
289 		count = 0;
290 	case TkTbychar:
291 		while(count > 0) {
292 			i = p->item;
293 			n = tktposcount(i) - p->pos;
294 			if(count >= n) {
295 				if(tktadjustind(tkt, TkTbyitem, p))
296 					count -= n;
297 				else
298 					break;
299 			}
300 			else {
301 				p->pos += count;
302 				break;
303 			}
304 		}
305 		while(p->item->kind == TkTmark || p->item->kind == TkTcontline)
306 			if(!tktadjustind(tkt, TkTbyitem, p))
307 				break;
308 		break;
309 	case TkTbycharback:
310 		count = -1;
311 		while(count < 0) {
312 			if(p->pos + count >= 0) {
313 				p->pos += count;
314 				count = 0;
315 			}
316 			else {
317 				count += p->pos;
318 				if(!tktadjustind(tkt, TkTbyitemback, p))
319 					break;
320 				n = tktposcount(p->item);
321 				p->pos = n;
322 			}
323 		}
324 		break;
325 
326 	case TkTbylineback:
327 		count = -1;
328 		/* fall through */
329 	case TkTbyline:
330 		n = tktlinepos(tkt, p);
331 		while(count > 0) {
332 			if(p->line->next == &tkt->end) {
333 				count = 0;
334 				break;
335 			}
336 			if(p->line->flags&TkTlast)
337 				count--;
338 			p->line = p->line->next;
339 		}
340 		while(count < 0 && p->line->prev != &tkt->start) {
341 			if(p->line->flags&TkTfirst)
342 				count++;
343 			p->line = p->line->prev;
344 		}
345 		tktadjustind(tkt, TkTbylinestart, p);
346 		while(n > 0) {
347 			if(p->item->kind == TkTnewline)
348 				break;
349 			if(!tktadjustind(tkt, TkTbychar, p))
350 				break;
351 			n--;
352 		}
353 		break;
354 
355 	case TkTbylinestart:
356 		/* note: can call this with only p->line set correctly  in *p */
357 
358 		while(!(p->line->flags&TkTfirst))
359 			p->line = p->line->prev;
360 		p->item = p->line->items;
361 		p->pos = 0;
362 		break;
363 
364 	case TkTbylineend:
365 		while(p->item->kind != TkTnewline)
366 			if(!tktadjustind(tkt, TkTbychar, p))
367 				break;
368 		break;
369 
370 	case TkTbywordstart:
371 		tktadjustind(tkt, TkTbycharstart, p);
372 		q = *p;
373 		c = tktindrune(p);
374 		while(tkiswordchar(c)) {
375 			q = *p;
376 			if(!tktadjustind(tkt, TkTbycharback, p))
377 				break;
378 			c = tktindrune(p);
379 		}
380 		*p = q;
381 		break;
382 
383 	case TkTbywordend:
384 		tktadjustind(tkt, TkTbycharstart, p);
385 		if(p->item->kind == TkTascii || p->item->kind == TkTrune) {
386 			c = tktindrune(p);
387 			if(tkiswordchar(c)) {
388 				do {
389 					if(!tktadjustind(tkt, TkTbychar, p))
390 						break;
391 					c = tktindrune(p);
392 				} while(tkiswordchar(c));
393 			}
394 			else
395 				tktadjustind(tkt, TkTbychar, p);
396 		}
397 		else if(!(p->item->kind == TkTnewline && p->line->next == &tkt->end))
398 			tktadjustind(tkt, TkTbychar, p);
399 
400 		break;
401 
402 	case TkTbywrapstart:
403 		tktadjustind(tkt, TkTbycharstart, p);
404 		q = *p;
405 		c = tktindrune(p);
406 		while(!tktisbreak(c)) {
407 			q = *p;
408 			if(!tktadjustind(tkt, TkTbycharback, p))
409 				break;
410 			c = tktindrune(p);
411 		}
412 		*p = q;
413 		break;
414 
415 	case TkTbywrapend:
416 		tktadjustind(tkt, TkTbycharstart, p);
417 		if(p->item->kind == TkTascii || p->item->kind == TkTrune) {
418 			c = tktindrune(p);
419 			if(!tktisbreak(c)) {
420 				do {
421 					if(!tktadjustind(tkt, TkTbychar, p))
422 						break;
423 					c = tktindrune(p);
424 				} while(!tktisbreak(c) && (p->item->kind == TkTascii || p->item->kind == TkTrune));
425 				while(tktisbreak(c) && tktadjustind(tkt, TkTbychar, p))
426 					c = tktindrune(p);	/* could limit it */
427 			}
428 			else
429 				tktadjustind(tkt, TkTbychar, p);
430 		}
431 		else if(!(p->item->kind == TkTnewline && p->line->next == &tkt->end))
432 			tktadjustind(tkt, TkTbychar, p);
433 
434 		break;
435 	}
436 	return (p->item != oit || p->pos != opos);
437 }
438 
439 /* return 1 if advancing i1 by item eventually hits i2 */
440 int
441 tktindbefore(TkTindex *i1, TkTindex *i2)
442 {
443 	int ans;
444 	TkTitem *i;
445 	TkTline *l1, *l2;
446 
447 	ans = 0;
448 	l1 = i1->line;
449 	l2 = i2->line;
450 
451 	if(l1 == l2) {
452 		if(i1->item == i2->item)
453 			ans = (i1->pos < i2->pos);
454 		else {
455 			for(i = i1->item; i != nil; i = i->next)
456 				if(i->next == i2->item) {
457 					ans = 1;
458 					break;
459 				}
460 		}
461 	}
462 	else {
463 		if(l1->orig.y < l2->orig.y)
464 			ans = 1;
465 		else
466 		if(l1->orig.y == l2->orig.y) {
467 			for(; l1 != nil; l1 = l1->next) {
468 				if(l1->next == l2) {
469 					ans = 1;
470 					break;
471 				}
472 				if(l1->orig.y > l2->orig.y)
473 					break;
474 			}
475 		}
476 	}
477 
478 	return ans;
479 }
480 
481 /*
482  * This comparison only cares which characters the indices are before.
483  * So two marks should be called "equal" (and not "less" or "greater")
484  * if they are adjacent.
485  */
486 int
487 tktindcompare(TkText *tkt, TkTindex *i1, int op, TkTindex *i2)
488 {
489 	int eq, ans;
490 	TkTindex x1, x2;
491 
492 	x1 = *i1;
493 	x2 = *i2;
494 
495 	/* skip over any marks, contlines, to see if on same character */
496 	tktadjustind(tkt, TkTbycharstart, &x1);
497 	tktadjustind(tkt, TkTbycharstart, &x2);
498 	eq = (x1.item == x2.item && x1.pos == x2.pos);
499 
500 	switch(op) {
501 	case TkEq:
502 		ans = eq;
503 		break;
504 	case TkNeq:
505 		ans = !eq;
506 		break;
507 	case TkLte:
508 		ans = eq || tktindbefore(i1, i2);
509 		break;
510 	case TkLt:
511 		ans = !eq && tktindbefore(i1, i2);
512 		break;
513 	case TkGte:
514 		ans = eq || tktindbefore(i2, i1);
515 		break;
516 	case TkGt:
517 		ans = !eq && tktindbefore(i2, i1);
518 		break;
519 	default:
520 		ans = 0;	/* not reached */
521 		break;
522 	};
523 
524 	return ans;
525 }
526 
527 void
528 tktstartind(TkText *tkt, TkTindex *ans)
529 {
530 	ans->line = tkt->start.next;
531 	ans->item = ans->line->items;
532 	ans->pos = 0;
533 }
534 
535 void
536 tktendind(TkText *tkt, TkTindex *ans)
537 {
538 	ans->line = tkt->end.prev;
539 	ans->item = tktlastitem(ans->line->items);
540 	ans->pos = 0;
541 }
542 
543 void
544 tktitemind(TkTitem *it, TkTindex *ans)
545 {
546 	ans->item = it;
547 	ans->line = tktitemline(it);
548 	ans->pos = 0;
549 }
550 
551 /*
552  * Fill ans with the item that (x,y) (in V space) is over.
553  * Return 0 if it is over the first half of the width,
554  * and 1 if it is over the second half.
555  */
556 int
557 tktxyind(Tk *tk, int x, int y, TkTindex *ans)
558 {
559 	int n, w, secondhalf, k;
560 	Point p, q;
561 	TkTitem *i;
562 	TkText *tkt;
563 
564  	tkt = TKobj(TkText, tk);
565 	tktstartind(tkt, ans);
566 	secondhalf = 0;
567 
568 	/* (x,y), p, q in V space */
569 	p = subpt(ans->line->orig, tkt->deltatv);
570 	q = subpt(ans->line->next->orig, tkt->deltatv);
571 	while(ans->line->next != &tkt->end) {
572 		if(q.y > y)
573 			break;
574 		tktadjustind(tkt, TkTbytline, ans);
575 		p = q;
576 		q = subpt(ans->line->next->orig, tkt->deltatv);
577 	}
578 	if (ans->line->next == &tkt->end) {
579 		Point ep = subpt(tkt->end.orig, tkt->deltatv);
580 		if (ep.y < y)
581 			x = 1000000;
582 	}
583 
584 	while(ans->item->next != nil) {
585 		i = ans->item;
586 		w = i->width;
587 		if(p.x+w > x) {
588 			n = tktposcount(i);
589 			if(n > 1) {
590 				for(k = 0; k < n; k++) {
591 					/* probably wrong w.r.t tag tabs */
592 					w = tktdispwidth(tk, nil, i, nil, p.x, k, 1);
593 					if(p.x+w > x) {
594 						ans->pos = k;
595 						break;
596 					}
597 					p.x += w;
598 				}
599 			}
600 			secondhalf = (p.x + w/2 <= x);
601 			break;
602 		}
603 		p.x += w;
604 		if(!tktadjustind(tkt, TkTbyitem, ans))
605 			break;
606 	}
607 	tktadjustind(tkt, TkTbycharstart, ans);
608 	return secondhalf;
609 }
610 
611