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