1 #include <u.h> 2 #include <libc.h> 3 #include <draw.h> 4 #include <thread.h> 5 #include <cursor.h> 6 #include <mouse.h> 7 #include <keyboard.h> 8 #include <frame.h> 9 #include <fcall.h> 10 #include <plumb.h> 11 #include "dat.h" 12 #include "fns.h" 13 14 Image *tagcols[NCOL]; 15 Image *textcols[NCOL]; 16 17 enum{ 18 TABDIR = 3 /* width of tabs in directory windows */ 19 }; 20 21 void 22 textinit(Text *t, File *f, Rectangle r, Reffont *rf, Image *cols[NCOL]) 23 { 24 t->file = f; 25 t->all = r; 26 t->scrollr = r; 27 t->scrollr.max.x = r.min.x+Scrollwid; 28 t->lastsr = nullrect; 29 r.min.x += Scrollwid+Scrollgap; 30 t->eq0 = ~0; 31 t->ncache = 0; 32 t->reffont = rf; 33 t->tabstop = maxtab; 34 memmove(t->Frame.cols, cols, sizeof t->Frame.cols); 35 textredraw(t, r, rf->f, screen, -1); 36 } 37 38 void 39 textredraw(Text *t, Rectangle r, Font *f, Image *b, int odx) 40 { 41 int maxt; 42 Rectangle rr; 43 44 frinit(t, r, f, b, t->Frame.cols); 45 rr = t->r; 46 rr.min.x -= Scrollwid; /* back fill to scroll bar */ 47 draw(t->b, rr, t->cols[BACK], nil, ZP); 48 /* use no wider than 3-space tabs in a directory */ 49 maxt = maxtab; 50 if(t->what == Body){ 51 if(t->w->isdir) 52 maxt = min(TABDIR, maxtab); 53 else 54 maxt = t->tabstop; 55 } 56 t->maxtab = maxt*stringwidth(f, "0"); 57 if(t->what==Body && t->w->isdir && odx!=Dx(t->all)){ 58 if(t->maxlines > 0){ 59 textreset(t); 60 textcolumnate(t, t->w->dlp, t->w->ndl); 61 textshow(t, 0, 0, 1); 62 } 63 }else{ 64 textfill(t); 65 textsetselect(t, t->q0, t->q1); 66 } 67 } 68 69 int 70 textresize(Text *t, Rectangle r) 71 { 72 int odx; 73 74 if(Dy(r) > 0) 75 r.max.y -= Dy(r)%t->font->height; 76 else 77 r.max.y = r.min.y; 78 odx = Dx(t->all); 79 t->all = r; 80 t->scrollr = r; 81 t->scrollr.max.x = r.min.x+Scrollwid; 82 t->lastsr = nullrect; 83 r.min.x += Scrollwid+Scrollgap; 84 frclear(t, 0); 85 textredraw(t, r, t->font, t->b, odx); 86 return r.max.y; 87 } 88 89 void 90 textclose(Text *t) 91 { 92 free(t->cache); 93 frclear(t, 1); 94 filedeltext(t->file, t); 95 t->file = nil; 96 rfclose(t->reffont); 97 if(argtext == t) 98 argtext = nil; 99 if(typetext == t) 100 typetext = nil; 101 if(seltext == t) 102 seltext = nil; 103 if(mousetext == t) 104 mousetext = nil; 105 if(barttext == t) 106 barttext = nil; 107 } 108 109 int 110 dircmp(void *a, void *b) 111 { 112 Dirlist *da, *db; 113 int i, n; 114 115 da = *(Dirlist**)a; 116 db = *(Dirlist**)b; 117 n = min(da->nr, db->nr); 118 i = memcmp(da->r, db->r, n*sizeof(Rune)); 119 if(i) 120 return i; 121 return da->nr - db->nr; 122 } 123 124 void 125 textcolumnate(Text *t, Dirlist **dlp, int ndl) 126 { 127 int i, j, w, colw, mint, maxt, ncol, nrow; 128 Dirlist *dl; 129 uint q1; 130 131 if(t->file->ntext > 1) 132 return; 133 mint = stringwidth(t->font, "0"); 134 /* go for narrower tabs if set more than 3 wide */ 135 t->maxtab = min(maxtab, TABDIR)*mint; 136 maxt = t->maxtab; 137 colw = 0; 138 for(i=0; i<ndl; i++){ 139 dl = dlp[i]; 140 w = dl->wid; 141 if(maxt-w%maxt < mint || w%maxt==0) 142 w += mint; 143 if(w % maxt) 144 w += maxt-(w%maxt); 145 if(w > colw) 146 colw = w; 147 } 148 if(colw == 0) 149 ncol = 1; 150 else 151 ncol = max(1, Dx(t->r)/colw); 152 nrow = (ndl+ncol-1)/ncol; 153 154 q1 = 0; 155 for(i=0; i<nrow; i++){ 156 for(j=i; j<ndl; j+=nrow){ 157 dl = dlp[j]; 158 fileinsert(t->file, q1, dl->r, dl->nr); 159 q1 += dl->nr; 160 if(j+nrow >= ndl) 161 break; 162 w = dl->wid; 163 if(maxt-w%maxt < mint){ 164 fileinsert(t->file, q1, L"\t", 1); 165 q1++; 166 w += mint; 167 } 168 do{ 169 fileinsert(t->file, q1, L"\t", 1); 170 q1++; 171 w += maxt-(w%maxt); 172 }while(w < colw); 173 } 174 fileinsert(t->file, q1, L"\n", 1); 175 q1++; 176 } 177 } 178 179 uint 180 textload(Text *t, uint q0, char *file, int setqid) 181 { 182 Rune *rp; 183 Dirlist *dl, **dlp; 184 int fd, i, j, n, ndl, nulls; 185 uint q, q1; 186 Dir *d, *dbuf; 187 char *tmp; 188 Text *u; 189 190 if(t->ncache!=0 || t->file->nc || t->w==nil || t!=&t->w->body || (t->w->isdir && t->file->nname==0)) 191 error("text.load"); 192 fd = open(file, OREAD); 193 if(fd < 0){ 194 warning(nil, "can't open %s: %r\n", file); 195 return 0; 196 } 197 d = dirfstat(fd); 198 if(d == nil){ 199 warning(nil, "can't fstat %s: %r\n", file); 200 goto Rescue; 201 } 202 nulls = FALSE; 203 if(d->qid.type & QTDIR){ 204 /* this is checked in get() but it's possible the file changed underfoot */ 205 if(t->file->ntext > 1){ 206 warning(nil, "%s is a directory; can't read with multiple windows on it\n", file); 207 goto Rescue; 208 } 209 t->w->isdir = TRUE; 210 t->w->filemenu = FALSE; 211 if(t->file->name[t->file->nname-1] != '/'){ 212 rp = runemalloc(t->file->nname+1); 213 runemove(rp, t->file->name, t->file->nname); 214 rp[t->file->nname] = '/'; 215 winsetname(t->w, rp, t->file->nname+1); 216 free(rp); 217 } 218 dlp = nil; 219 ndl = 0; 220 dbuf = nil; 221 while((n=dirread(fd, &dbuf)) > 0){ 222 for(i=0; i<n; i++){ 223 dl = emalloc(sizeof(Dirlist)); 224 j = strlen(dbuf[i].name); 225 tmp = emalloc(j+1+1); 226 memmove(tmp, dbuf[i].name, j); 227 if(dbuf[i].qid.type & QTDIR) 228 tmp[j++] = '/'; 229 tmp[j] = '\0'; 230 dl->r = bytetorune(tmp, &dl->nr); 231 dl->wid = stringwidth(t->font, tmp); 232 free(tmp); 233 ndl++; 234 dlp = realloc(dlp, ndl*sizeof(Dirlist*)); 235 dlp[ndl-1] = dl; 236 } 237 free(dbuf); 238 } 239 qsort(dlp, ndl, sizeof(Dirlist*), dircmp); 240 t->w->dlp = dlp; 241 t->w->ndl = ndl; 242 textcolumnate(t, dlp, ndl); 243 q1 = t->file->nc; 244 }else{ 245 t->w->isdir = FALSE; 246 t->w->filemenu = TRUE; 247 q1 = q0 + fileload(t->file, q0, fd, &nulls); 248 } 249 if(setqid){ 250 t->file->dev = d->dev; 251 t->file->mtime = d->mtime; 252 t->file->qidpath = d->qid.path; 253 } 254 close(fd); 255 rp = fbufalloc(); 256 for(q=q0; q<q1; q+=n){ 257 n = q1-q; 258 if(n > RBUFSIZE) 259 n = RBUFSIZE; 260 bufread(t->file, q, rp, n); 261 if(q < t->org) 262 t->org += n; 263 else if(q <= t->org+t->nchars) 264 frinsert(t, rp, rp+n, q-t->org); 265 if(t->lastlinefull) 266 break; 267 } 268 fbuffree(rp); 269 for(i=0; i<t->file->ntext; i++){ 270 u = t->file->text[i]; 271 if(u != t){ 272 if(u->org > u->file->nc) /* will be 0 because of reset(), but safety first */ 273 u->org = 0; 274 textresize(u, u->all); 275 textbacknl(u, u->org, 0); /* go to beginning of line */ 276 } 277 textsetselect(u, q0, q0); 278 } 279 if(nulls) 280 warning(nil, "%s: NUL bytes elided\n", file); 281 free(d); 282 return q1-q0; 283 284 Rescue: 285 close(fd); 286 return 0; 287 } 288 289 uint 290 textbsinsert(Text *t, uint q0, Rune *r, uint n, int tofile, int *nrp) 291 { 292 Rune *bp, *tp, *up; 293 int i, initial; 294 295 if(t->what == Tag){ /* can't happen but safety first: mustn't backspace over file name */ 296 Err: 297 textinsert(t, q0, r, n, tofile); 298 *nrp = n; 299 return q0; 300 } 301 bp = r; 302 for(i=0; i<n; i++) 303 if(*bp++ == '\b'){ 304 --bp; 305 initial = 0; 306 tp = runemalloc(n); 307 runemove(tp, r, i); 308 up = tp+i; 309 for(; i<n; i++){ 310 *up = *bp++; 311 if(*up == '\b') 312 if(up == tp) 313 initial++; 314 else 315 --up; 316 else 317 up++; 318 } 319 if(initial){ 320 if(initial > q0) 321 initial = q0; 322 q0 -= initial; 323 textdelete(t, q0, q0+initial, tofile); 324 } 325 n = up-tp; 326 textinsert(t, q0, tp, n, tofile); 327 free(tp); 328 *nrp = n; 329 return q0; 330 } 331 goto Err; 332 } 333 334 void 335 textinsert(Text *t, uint q0, Rune *r, uint n, int tofile) 336 { 337 int c, i; 338 Text *u; 339 340 if(tofile && t->ncache != 0) 341 error("text.insert"); 342 if(n == 0) 343 return; 344 if(tofile){ 345 fileinsert(t->file, q0, r, n); 346 if(t->what == Body){ 347 t->w->dirty = TRUE; 348 t->w->utflastqid = -1; 349 } 350 if(t->file->ntext > 1) 351 for(i=0; i<t->file->ntext; i++){ 352 u = t->file->text[i]; 353 if(u != t){ 354 u->w->dirty = TRUE; /* always a body */ 355 textinsert(u, q0, r, n, FALSE); 356 textsetselect(u, u->q0, u->q1); 357 textscrdraw(u); 358 } 359 } 360 361 } 362 if(q0 < t->q1) 363 t->q1 += n; 364 if(q0 < t->q0) 365 t->q0 += n; 366 if(q0 < t->org) 367 t->org += n; 368 else if(q0 <= t->org+t->nchars) 369 frinsert(t, r, r+n, q0-t->org); 370 if(t->w){ 371 c = 'i'; 372 if(t->what == Body) 373 c = 'I'; 374 if(n <= EVENTSIZE) 375 winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q0+n, n, n, r); 376 else 377 winevent(t->w, "%c%d %d 0 0 \n", c, q0, q0+n, n); 378 } 379 } 380 381 382 void 383 textfill(Text *t) 384 { 385 Rune *rp; 386 int i, n, m, nl; 387 388 if(t->lastlinefull || t->nofill) 389 return; 390 if(t->ncache > 0){ 391 if(t->w != nil) 392 wincommit(t->w, t); 393 else 394 textcommit(t, TRUE); 395 } 396 rp = fbufalloc(); 397 do{ 398 n = t->file->nc-(t->org+t->nchars); 399 if(n == 0) 400 break; 401 if(n > 2000) /* educated guess at reasonable amount */ 402 n = 2000; 403 bufread(t->file, t->org+t->nchars, rp, n); 404 /* 405 * it's expensive to frinsert more than we need, so 406 * count newlines. 407 */ 408 nl = t->maxlines-t->nlines; 409 m = 0; 410 for(i=0; i<n; ){ 411 if(rp[i++] == '\n'){ 412 m++; 413 if(m >= nl) 414 break; 415 } 416 } 417 frinsert(t, rp, rp+i, t->nchars); 418 }while(t->lastlinefull == FALSE); 419 fbuffree(rp); 420 } 421 422 void 423 textdelete(Text *t, uint q0, uint q1, int tofile) 424 { 425 uint n, p0, p1; 426 int i, c; 427 Text *u; 428 429 if(tofile && t->ncache != 0) 430 error("text.delete"); 431 n = q1-q0; 432 if(n == 0) 433 return; 434 if(tofile){ 435 filedelete(t->file, q0, q1); 436 if(t->what == Body){ 437 t->w->dirty = TRUE; 438 t->w->utflastqid = -1; 439 } 440 if(t->file->ntext > 1) 441 for(i=0; i<t->file->ntext; i++){ 442 u = t->file->text[i]; 443 if(u != t){ 444 u->w->dirty = TRUE; /* always a body */ 445 textdelete(u, q0, q1, FALSE); 446 textsetselect(u, u->q0, u->q1); 447 textscrdraw(u); 448 } 449 } 450 } 451 if(q0 < t->q0) 452 t->q0 -= min(n, t->q0-q0); 453 if(q0 < t->q1) 454 t->q1 -= min(n, t->q1-q0); 455 if(q1 <= t->org) 456 t->org -= n; 457 else if(q0 < t->org+t->nchars){ 458 p1 = q1 - t->org; 459 if(p1 > t->nchars) 460 p1 = t->nchars; 461 if(q0 < t->org){ 462 t->org = q0; 463 p0 = 0; 464 }else 465 p0 = q0 - t->org; 466 frdelete(t, p0, p1); 467 textfill(t); 468 } 469 if(t->w){ 470 c = 'd'; 471 if(t->what == Body) 472 c = 'D'; 473 winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1); 474 } 475 } 476 477 Rune 478 textreadc(Text *t, uint q) 479 { 480 Rune r; 481 482 if(t->cq0<=q && q<t->cq0+t->ncache) 483 r = t->cache[q-t->cq0]; 484 else 485 bufread(t->file, q, &r, 1); 486 return r; 487 } 488 489 int 490 textbswidth(Text *t, Rune c) 491 { 492 uint q, eq; 493 Rune r; 494 int skipping; 495 496 /* there is known to be at least one character to erase */ 497 if(c == 0x08) /* ^H: erase character */ 498 return 1; 499 q = t->q0; 500 skipping = TRUE; 501 while(q > 0){ 502 r = textreadc(t, q-1); 503 if(r == '\n'){ /* eat at most one more character */ 504 if(q == t->q0) /* eat the newline */ 505 --q; 506 break; 507 } 508 if(c == 0x17){ 509 eq = isalnum(r); 510 if(eq && skipping) /* found one; stop skipping */ 511 skipping = FALSE; 512 else if(!eq && !skipping) 513 break; 514 } 515 --q; 516 } 517 return t->q0-q; 518 } 519 520 void 521 texttype(Text *t, Rune r) 522 { 523 uint q0, q1; 524 int nnb, nb, n, i; 525 Text *u; 526 527 if(t->what!=Body && r=='\n') 528 return; 529 switch(r){ 530 case Kdown: 531 case Kleft: 532 case Kright: 533 n = t->maxlines/2; 534 q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+n*t->font->height)); 535 textsetorigin(t, q0, FALSE); 536 return; 537 case Kup: 538 n = t->maxlines/2; 539 q0 = textbacknl(t, t->org, n); 540 textsetorigin(t, q0, FALSE); 541 return; 542 } 543 if(t->what == Body){ 544 seq++; 545 filemark(t->file); 546 } 547 if(t->q1 > t->q0){ 548 if(t->ncache != 0) 549 error("text.type"); 550 cut(t, t, nil, TRUE, TRUE, nil, 0); 551 t->eq0 = ~0; 552 } 553 textshow(t, t->q0, t->q0, 1); 554 switch(r){ 555 case 0x1B: 556 if(t->eq0 != ~0) 557 textsetselect(t, t->eq0, t->q0); 558 if(t->ncache > 0){ 559 if(t->w != nil) 560 wincommit(t->w, t); 561 else 562 textcommit(t, TRUE); 563 } 564 return; 565 case 0x08: /* ^H: erase character */ 566 case 0x15: /* ^U: erase line */ 567 case 0x17: /* ^W: erase word */ 568 if(t->q0 == 0) /* nothing to erase */ 569 return; 570 nnb = textbswidth(t, r); 571 q1 = t->q0; 572 q0 = q1-nnb; 573 /* if selection is at beginning of window, avoid deleting invisible text */ 574 if(q0 < t->org){ 575 q0 = t->org; 576 nnb = q1-q0; 577 } 578 if(nnb <= 0) 579 return; 580 for(i=0; i<t->file->ntext; i++){ 581 u = t->file->text[i]; 582 u->nofill = TRUE; 583 nb = nnb; 584 n = u->ncache; 585 if(n > 0){ 586 if(q1 != u->cq0+n) 587 error("text.type backspace"); 588 if(n > nb) 589 n = nb; 590 u->ncache -= n; 591 textdelete(u, q1-n, q1, FALSE); 592 nb -= n; 593 } 594 if(u->eq0==q1 || u->eq0==~0) 595 u->eq0 = q0; 596 if(nb && u==t) 597 textdelete(u, q0, q0+nb, TRUE); 598 if(u != t) 599 textsetselect(u, u->q0, u->q1); 600 else 601 textsetselect(t, q0, q0); 602 u->nofill = FALSE; 603 } 604 for(i=0; i<t->file->ntext; i++) 605 textfill(t->file->text[i]); 606 return; 607 } 608 /* otherwise ordinary character; just insert, typically in caches of all texts */ 609 for(i=0; i<t->file->ntext; i++){ 610 u = t->file->text[i]; 611 if(u->eq0 == ~0) 612 u->eq0 = t->q0; 613 if(u->ncache == 0) 614 u->cq0 = t->q0; 615 else if(t->q0 != u->cq0+u->ncache) 616 error("text.type cq1"); 617 textinsert(u, t->q0, &r, 1, FALSE); 618 if(u != t) 619 textsetselect(u, u->q0, u->q1); 620 if(u->ncache == u->ncachealloc){ 621 u->ncachealloc += 10; 622 u->cache = runerealloc(u->cache, u->ncachealloc); 623 } 624 u->cache[u->ncache++] = r; 625 } 626 textsetselect(t, t->q0+1, t->q0+1); 627 if(r=='\n' && t->w!=nil) 628 wincommit(t->w, t); 629 } 630 631 void 632 textcommit(Text *t, int tofile) 633 { 634 if(t->ncache == 0) 635 return; 636 if(tofile) 637 fileinsert(t->file, t->cq0, t->cache, t->ncache); 638 if(t->what == Body){ 639 t->w->dirty = TRUE; 640 t->w->utflastqid = -1; 641 } 642 t->ncache = 0; 643 } 644 645 static Text *clicktext; 646 static uint clickmsec; 647 static Text *selecttext; 648 static uint selectq; 649 650 /* 651 * called from frame library 652 */ 653 void 654 framescroll(Frame *f, int dl) 655 { 656 if(f != &selecttext->Frame) 657 error("frameselect not right frame"); 658 textframescroll(selecttext, dl); 659 } 660 661 void 662 textframescroll(Text *t, int dl) 663 { 664 uint q0; 665 666 if(dl == 0){ 667 scrsleep(100); 668 return; 669 } 670 if(dl < 0){ 671 q0 = textbacknl(t, t->org, -dl); 672 if(selectq > t->org+t->p0) 673 textsetselect(t, t->org+t->p0, selectq); 674 else 675 textsetselect(t, selectq, t->org+t->p0); 676 }else{ 677 if(t->org+t->nchars == t->file->nc) 678 return; 679 q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+dl*t->font->height)); 680 if(selectq > t->org+t->p1) 681 textsetselect(t, t->org+t->p1, selectq); 682 else 683 textsetselect(t, selectq, t->org+t->p1); 684 } 685 textsetorigin(t, q0, TRUE); 686 } 687 688 689 void 690 textselect(Text *t) 691 { 692 uint q0, q1; 693 int b, x, y; 694 int state; 695 696 selecttext = t; 697 /* 698 * To have double-clicking and chording, we double-click 699 * immediately if it might make sense. 700 */ 701 b = mouse->buttons; 702 q0 = t->q0; 703 q1 = t->q1; 704 selectq = t->org+frcharofpt(t, mouse->xy); 705 if(clicktext==t && mouse->msec-clickmsec<500) 706 if(q0==q1 && selectq==q0){ 707 textdoubleclick(t, &q0, &q1); 708 textsetselect(t, q0, q1); 709 flushimage(display, 1); 710 x = mouse->xy.x; 711 y = mouse->xy.y; 712 /* stay here until something interesting happens */ 713 do 714 readmouse(mousectl); 715 while(mouse->buttons==b && abs(mouse->xy.x-x)<3 && abs(mouse->xy.y-y)<3); 716 mouse->xy.x = x; /* in case we're calling frselect */ 717 mouse->xy.y = y; 718 q0 = t->q0; /* may have changed */ 719 q1 = t->q1; 720 selectq = q0; 721 } 722 if(mouse->buttons == b){ 723 t->Frame.scroll = framescroll; 724 frselect(t, mousectl); 725 /* horrible botch: while asleep, may have lost selection altogether */ 726 if(selectq > t->file->nc) 727 selectq = t->org + t->p0; 728 t->Frame.scroll = nil; 729 if(selectq < t->org) 730 q0 = selectq; 731 else 732 q0 = t->org + t->p0; 733 if(selectq > t->org+t->nchars) 734 q1 = selectq; 735 else 736 q1 = t->org+t->p1; 737 } 738 if(q0 == q1){ 739 if(q0==t->q0 && clicktext==t && mouse->msec-clickmsec<500){ 740 textdoubleclick(t, &q0, &q1); 741 clicktext = nil; 742 }else{ 743 clicktext = t; 744 clickmsec = mouse->msec; 745 } 746 }else 747 clicktext = nil; 748 textsetselect(t, q0, q1); 749 flushimage(display, 1); 750 state = 0; /* undo when possible; +1 for cut, -1 for paste */ 751 while(mouse->buttons){ 752 mouse->msec = 0; 753 b = mouse->buttons; 754 if(b & 6){ 755 if(state==0 && t->what==Body){ 756 seq++; 757 filemark(t->w->body.file); 758 } 759 if(b & 2){ 760 if(state==-1 && t->what==Body){ 761 winundo(t->w, TRUE); 762 textsetselect(t, q0, t->q0); 763 state = 0; 764 }else if(state != 1){ 765 cut(t, t, nil, TRUE, TRUE, nil, 0); 766 state = 1; 767 } 768 }else{ 769 if(state==1 && t->what==Body){ 770 winundo(t->w, TRUE); 771 textsetselect(t, q0, t->q1); 772 state = 0; 773 }else if(state != -1){ 774 paste(t, t, nil, TRUE, FALSE, nil, 0); 775 state = -1; 776 } 777 } 778 textscrdraw(t); 779 clearmouse(); 780 } 781 flushimage(display, 1); 782 while(mouse->buttons == b) 783 readmouse(mousectl); 784 clicktext = nil; 785 } 786 } 787 788 void 789 textshow(Text *t, uint q0, uint q1, int doselect) 790 { 791 int qe; 792 int nl; 793 uint q; 794 795 if(t->what != Body) 796 return; 797 if(t->w!=nil && t->maxlines==0) 798 colgrow(t->col, t->w, 1); 799 if(doselect) 800 textsetselect(t, q0, q1); 801 qe = t->org+t->nchars; 802 if(t->org<=q0 && (q0<qe || (q0==qe && qe==t->file->nc+t->ncache))) 803 textscrdraw(t); 804 else{ 805 if(t->w->nopen[QWevent] > 0) 806 nl = 3*t->maxlines/4; 807 else 808 nl = t->maxlines/4; 809 q = textbacknl(t, q0, nl); 810 /* avoid going backwards if trying to go forwards - long lines! */ 811 if(!(q0>t->org && q<t->org)) 812 textsetorigin(t, q, TRUE); 813 while(q0 > t->org+t->nchars) 814 textsetorigin(t, t->org+1, FALSE); 815 } 816 } 817 818 static 819 int 820 region(int a, int b) 821 { 822 if(a < b) 823 return -1; 824 if(a == b) 825 return 0; 826 return 1; 827 } 828 829 void 830 selrestore(Frame *f, Point pt0, uint p0, uint p1) 831 { 832 if(p1<=f->p0 || p0>=f->p1){ 833 /* no overlap */ 834 frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]); 835 return; 836 } 837 if(p0>=f->p0 && p1<=f->p1){ 838 /* entirely inside */ 839 frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]); 840 return; 841 } 842 843 /* they now are known to overlap */ 844 845 /* before selection */ 846 if(p0 < f->p0){ 847 frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]); 848 p0 = f->p0; 849 pt0 = frptofchar(f, p0); 850 } 851 /* after selection */ 852 if(p1 > f->p1){ 853 frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]); 854 p1 = f->p1; 855 } 856 /* inside selection */ 857 frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]); 858 } 859 860 void 861 textsetselect(Text *t, uint q0, uint q1) 862 { 863 int p0, p1; 864 865 /* t->p0 and t->p1 are always right; t->q0 and t->q1 may be off */ 866 t->q0 = q0; 867 t->q1 = q1; 868 /* compute desired p0,p1 from q0,q1 */ 869 p0 = q0-t->org; 870 p1 = q1-t->org; 871 if(p0 < 0) 872 p0 = 0; 873 if(p1 < 0) 874 p1 = 0; 875 if(p0 > t->nchars) 876 p0 = t->nchars; 877 if(p1 > t->nchars) 878 p1 = t->nchars; 879 if(p0==t->p0 && p1==t->p1) 880 return; 881 /* screen disagrees with desired selection */ 882 if(t->p1<=p0 || p1<=t->p0 || p0==p1 || t->p1==t->p0){ 883 /* no overlap or too easy to bother trying */ 884 frdrawsel(t, frptofchar(t, t->p0), t->p0, t->p1, 0); 885 frdrawsel(t, frptofchar(t, p0), p0, p1, 1); 886 goto Return; 887 } 888 /* overlap; avoid unnecessary painting */ 889 if(p0 < t->p0){ 890 /* extend selection backwards */ 891 frdrawsel(t, frptofchar(t, p0), p0, t->p0, 1); 892 }else if(p0 > t->p0){ 893 /* trim first part of selection */ 894 frdrawsel(t, frptofchar(t, t->p0), t->p0, p0, 0); 895 } 896 if(p1 > t->p1){ 897 /* extend selection forwards */ 898 frdrawsel(t, frptofchar(t, t->p1), t->p1, p1, 1); 899 }else if(p1 < t->p1){ 900 /* trim last part of selection */ 901 frdrawsel(t, frptofchar(t, p1), p1, t->p1, 0); 902 } 903 904 Return: 905 t->p0 = p0; 906 t->p1 = p1; 907 } 908 909 /* 910 * Release the button in less than DELAY ms and it's considered a null selection 911 * if the mouse hardly moved, regardless of whether it crossed a char boundary. 912 */ 913 enum { 914 DELAY = 2, 915 MINMOVE = 4, 916 }; 917 918 uint 919 xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p) /* when called, button is down */ 920 { 921 uint p0, p1, q, tmp; 922 ulong msec; 923 Point mp, pt0, pt1, qt; 924 int reg, b; 925 926 mp = mc->xy; 927 b = mc->buttons; 928 msec = mc->msec; 929 930 /* remove tick */ 931 if(f->p0 == f->p1) 932 frtick(f, frptofchar(f, f->p0), 0); 933 p0 = p1 = frcharofpt(f, mp); 934 pt0 = frptofchar(f, p0); 935 pt1 = frptofchar(f, p1); 936 reg = 0; 937 frtick(f, pt0, 1); 938 do{ 939 q = frcharofpt(f, mc->xy); 940 if(p1 != q){ 941 if(p0 == p1) 942 frtick(f, pt0, 0); 943 if(reg != region(q, p0)){ /* crossed starting point; reset */ 944 if(reg > 0) 945 selrestore(f, pt0, p0, p1); 946 else if(reg < 0) 947 selrestore(f, pt1, p1, p0); 948 p1 = p0; 949 pt1 = pt0; 950 reg = region(q, p0); 951 if(reg == 0) 952 frdrawsel0(f, pt0, p0, p1, col, display->white); 953 } 954 qt = frptofchar(f, q); 955 if(reg > 0){ 956 if(q > p1) 957 frdrawsel0(f, pt1, p1, q, col, display->white); 958 959 else if(q < p1) 960 selrestore(f, qt, q, p1); 961 }else if(reg < 0){ 962 if(q > p1) 963 selrestore(f, pt1, p1, q); 964 else 965 frdrawsel0(f, qt, q, p1, col, display->white); 966 } 967 p1 = q; 968 pt1 = qt; 969 } 970 if(p0 == p1) 971 frtick(f, pt0, 1); 972 flushimage(f->display, 1); 973 readmouse(mc); 974 }while(mc->buttons == b); 975 if(mc->msec-msec < DELAY && p0!=p1 976 && abs(mp.x-mc->xy.x)<MINMOVE 977 && abs(mp.y-mc->xy.y)<MINMOVE) { 978 if(reg > 0) 979 selrestore(f, pt0, p0, p1); 980 else if(reg < 0) 981 selrestore(f, pt1, p1, p0); 982 p1 = p0; 983 } 984 if(p1 < p0){ 985 tmp = p0; 986 p0 = p1; 987 p1 = tmp; 988 } 989 pt0 = frptofchar(f, p0); 990 if(p0 == p1) 991 frtick(f, pt0, 0); 992 selrestore(f, pt0, p0, p1); 993 /* restore tick */ 994 if(f->p0 == f->p1) 995 frtick(f, frptofchar(f, f->p0), 1); 996 flushimage(f->display, 1); 997 *p1p = p1; 998 return p0; 999 } 1000 1001 int 1002 textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask) 1003 { 1004 uint p0, p1; 1005 int buts; 1006 1007 p0 = xselect(t, mousectl, high, &p1); 1008 buts = mousectl->buttons; 1009 if((buts & mask) == 0){ 1010 *q0 = p0+t->org; 1011 *q1 = p1+t->org; 1012 } 1013 1014 while(mousectl->buttons) 1015 readmouse(mousectl); 1016 return buts; 1017 } 1018 1019 int 1020 textselect2(Text *t, uint *q0, uint *q1, Text **tp) 1021 { 1022 int buts; 1023 1024 *tp = nil; 1025 buts = textselect23(t, q0, q1, but2col, 4); 1026 if(buts & 4) 1027 return 0; 1028 if(buts & 1){ /* pick up argument */ 1029 *tp = argtext; 1030 return 1; 1031 } 1032 return 1; 1033 } 1034 1035 int 1036 textselect3(Text *t, uint *q0, uint *q1) 1037 { 1038 int h; 1039 1040 h = (textselect23(t, q0, q1, but3col, 1|2) == 0); 1041 return h; 1042 } 1043 1044 static Rune left1[] = { L'{', L'[', L'(', L'<', L'«', 0 }; 1045 static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 }; 1046 static Rune left2[] = { L'\n', 0 }; 1047 static Rune left3[] = { L'\'', L'"', L'`', 0 }; 1048 1049 static 1050 Rune *left[] = { 1051 left1, 1052 left2, 1053 left3, 1054 nil 1055 }; 1056 static 1057 Rune *right[] = { 1058 right1, 1059 left2, 1060 left3, 1061 nil 1062 }; 1063 1064 void 1065 textdoubleclick(Text *t, uint *q0, uint *q1) 1066 { 1067 int c, i; 1068 Rune *r, *l, *p; 1069 uint q; 1070 1071 for(i=0; left[i]!=nil; i++){ 1072 q = *q0; 1073 l = left[i]; 1074 r = right[i]; 1075 /* try matching character to left, looking right */ 1076 if(q == 0) 1077 c = '\n'; 1078 else 1079 c = textreadc(t, q-1); 1080 p = runestrchr(l, c); 1081 if(p != nil){ 1082 if(textclickmatch(t, c, r[p-l], 1, &q)) 1083 *q1 = q-(c!='\n'); 1084 return; 1085 } 1086 /* try matching character to right, looking left */ 1087 if(q == t->file->nc) 1088 c = '\n'; 1089 else 1090 c = textreadc(t, q); 1091 p = runestrchr(r, c); 1092 if(p != nil){ 1093 if(textclickmatch(t, c, l[p-r], -1, &q)){ 1094 *q1 = *q0+(*q0<t->file->nc && c=='\n'); 1095 *q0 = q; 1096 if(c!='\n' || q!=0 || textreadc(t, 0)=='\n') 1097 (*q0)++; 1098 } 1099 return; 1100 } 1101 } 1102 /* try filling out word to right */ 1103 while(*q1<t->file->nc && isalnum(textreadc(t, *q1))) 1104 (*q1)++; 1105 /* try filling out word to left */ 1106 while(*q0>0 && isalnum(textreadc(t, *q0-1))) 1107 (*q0)--; 1108 } 1109 1110 int 1111 textclickmatch(Text *t, int cl, int cr, int dir, uint *q) 1112 { 1113 Rune c; 1114 int nest; 1115 1116 nest = 1; 1117 for(;;){ 1118 if(dir > 0){ 1119 if(*q == t->file->nc) 1120 break; 1121 c = textreadc(t, *q); 1122 (*q)++; 1123 }else{ 1124 if(*q == 0) 1125 break; 1126 (*q)--; 1127 c = textreadc(t, *q); 1128 } 1129 if(c == cr){ 1130 if(--nest==0) 1131 return 1; 1132 }else if(c == cl) 1133 nest++; 1134 } 1135 return cl=='\n' && nest==1; 1136 } 1137 1138 uint 1139 textbacknl(Text *t, uint p, uint n) 1140 { 1141 int i, j; 1142 1143 /* look for start of this line if n==0 */ 1144 if(n==0 && p>0 && textreadc(t, p-1)!='\n') 1145 n = 1; 1146 i = n; 1147 while(i-->0 && p>0){ 1148 --p; /* it's at a newline now; back over it */ 1149 if(p == 0) 1150 break; 1151 /* at 128 chars, call it a line anyway */ 1152 for(j=128; --j>0 && p>0; p--) 1153 if(textreadc(t, p-1)=='\n') 1154 break; 1155 } 1156 return p; 1157 } 1158 1159 void 1160 textsetorigin(Text *t, uint org, int exact) 1161 { 1162 int i, a, fixup; 1163 Rune *r; 1164 uint n; 1165 1166 if(org>0 && !exact){ 1167 /* org is an estimate of the char posn; find a newline */ 1168 /* don't try harder than 256 chars */ 1169 for(i=0; i<256 && org<t->file->nc; i++){ 1170 if(textreadc(t, org) == '\n'){ 1171 org++; 1172 break; 1173 } 1174 org++; 1175 } 1176 } 1177 a = org-t->org; 1178 fixup = 0; 1179 if(a>=0 && a<t->nchars){ 1180 frdelete(t, 0, a); 1181 fixup = 1; /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */ 1182 } 1183 else if(a<0 && -a<t->nchars){ 1184 n = t->org - org; 1185 r = runemalloc(n); 1186 bufread(t->file, org, r, n); 1187 frinsert(t, r, r+n, 0); 1188 free(r); 1189 }else 1190 frdelete(t, 0, t->nchars); 1191 t->org = org; 1192 textfill(t); 1193 textscrdraw(t); 1194 textsetselect(t, t->q0, t->q1); 1195 if(fixup && t->p1 > t->p0) 1196 frdrawsel(t, frptofchar(t, t->p1-1), t->p1-1, t->p1, 1); 1197 } 1198 1199 void 1200 textreset(Text *t) 1201 { 1202 t->file->seq = 0; 1203 t->eq0 = ~0; 1204 /* do t->delete(0, t->nc, TRUE) without building backup stuff */ 1205 textsetselect(t, t->org, t->org); 1206 frdelete(t, 0, t->nchars); 1207 t->org = 0; 1208 t->q0 = 0; 1209 t->q1 = 0; 1210 filereset(t->file); 1211 bufreset(t->file); 1212 } 1213