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*
tktindparse(Tk * tk,char ** pspec,TkTindex * ans)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
tktisbreak(int c)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
tktadjustind(TkText * tkt,int units,TkTindex * p)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
tktindbefore(TkTindex * i1,TkTindex * i2)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
tktindcompare(TkText * tkt,TkTindex * i1,int op,TkTindex * i2)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
tktstartind(TkText * tkt,TkTindex * ans)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
tktendind(TkText * tkt,TkTindex * ans)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
tktitemind(TkTitem * it,TkTindex * ans)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
tktxyind(Tk * tk,int x,int y,TkTindex * ans)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