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 void 478 textconstrain(Text *t, uint q0, uint q1, uint *p0, uint *p1) 479 { 480 *p0 = min(q0, t->file->nc); 481 *p1 = min(q1, t->file->nc); 482 } 483 484 Rune 485 textreadc(Text *t, uint q) 486 { 487 Rune r; 488 489 if(t->cq0<=q && q<t->cq0+t->ncache) 490 r = t->cache[q-t->cq0]; 491 else 492 bufread(t->file, q, &r, 1); 493 return r; 494 } 495 496 int 497 textbswidth(Text *t, Rune c) 498 { 499 uint q, eq; 500 Rune r; 501 int skipping; 502 503 /* there is known to be at least one character to erase */ 504 if(c == 0x08) /* ^H: erase character */ 505 return 1; 506 q = t->q0; 507 skipping = TRUE; 508 while(q > 0){ 509 r = textreadc(t, q-1); 510 if(r == '\n'){ /* eat at most one more character */ 511 if(q == t->q0) /* eat the newline */ 512 --q; 513 break; 514 } 515 if(c == 0x17){ 516 eq = isalnum(r); 517 if(eq && skipping) /* found one; stop skipping */ 518 skipping = FALSE; 519 else if(!eq && !skipping) 520 break; 521 } 522 --q; 523 } 524 return t->q0-q; 525 } 526 527 void 528 texttype(Text *t, Rune r) 529 { 530 uint q0, q1; 531 int nnb, nb, n, i; 532 Text *u; 533 534 if(t->what!=Body && r=='\n') 535 return; 536 switch(r){ 537 case Kdown: 538 case Kleft: 539 case Kright: 540 n = t->maxlines/2; 541 q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+n*t->font->height)); 542 textsetorigin(t, q0, FALSE); 543 return; 544 case Kup: 545 n = t->maxlines/2; 546 q0 = textbacknl(t, t->org, n); 547 textsetorigin(t, q0, FALSE); 548 return; 549 } 550 if(t->what == Body){ 551 seq++; 552 filemark(t->file); 553 } 554 if(t->q1 > t->q0){ 555 if(t->ncache != 0) 556 error("text.type"); 557 cut(t, t, nil, TRUE, TRUE, nil, 0); 558 t->eq0 = ~0; 559 } 560 textshow(t, t->q0, t->q0, 1); 561 switch(r){ 562 case 0x1B: 563 if(t->eq0 != ~0) 564 textsetselect(t, t->eq0, t->q0); 565 if(t->ncache > 0){ 566 if(t->w != nil) 567 wincommit(t->w, t); 568 else 569 textcommit(t, TRUE); 570 } 571 return; 572 case 0x08: /* ^H: erase character */ 573 case 0x15: /* ^U: erase line */ 574 case 0x17: /* ^W: erase word */ 575 if(t->q0 == 0) /* nothing to erase */ 576 return; 577 nnb = textbswidth(t, r); 578 q1 = t->q0; 579 q0 = q1-nnb; 580 /* if selection is at beginning of window, avoid deleting invisible text */ 581 if(q0 < t->org){ 582 q0 = t->org; 583 nnb = q1-q0; 584 } 585 if(nnb <= 0) 586 return; 587 for(i=0; i<t->file->ntext; i++){ 588 u = t->file->text[i]; 589 u->nofill = TRUE; 590 nb = nnb; 591 n = u->ncache; 592 if(n > 0){ 593 if(q1 != u->cq0+n) 594 error("text.type backspace"); 595 if(n > nb) 596 n = nb; 597 u->ncache -= n; 598 textdelete(u, q1-n, q1, FALSE); 599 nb -= n; 600 } 601 if(u->eq0==q1 || u->eq0==~0) 602 u->eq0 = q0; 603 if(nb && u==t) 604 textdelete(u, q0, q0+nb, TRUE); 605 if(u != t) 606 textsetselect(u, u->q0, u->q1); 607 else 608 textsetselect(t, q0, q0); 609 u->nofill = FALSE; 610 } 611 for(i=0; i<t->file->ntext; i++) 612 textfill(t->file->text[i]); 613 return; 614 } 615 /* otherwise ordinary character; just insert, typically in caches of all texts */ 616 for(i=0; i<t->file->ntext; i++){ 617 u = t->file->text[i]; 618 if(u->eq0 == ~0) 619 u->eq0 = t->q0; 620 if(u->ncache == 0) 621 u->cq0 = t->q0; 622 else if(t->q0 != u->cq0+u->ncache) 623 error("text.type cq1"); 624 textinsert(u, t->q0, &r, 1, FALSE); 625 if(u != t) 626 textsetselect(u, u->q0, u->q1); 627 if(u->ncache == u->ncachealloc){ 628 u->ncachealloc += 10; 629 u->cache = runerealloc(u->cache, u->ncachealloc); 630 } 631 u->cache[u->ncache++] = r; 632 } 633 textsetselect(t, t->q0+1, t->q0+1); 634 if(r=='\n' && t->w!=nil) 635 wincommit(t->w, t); 636 } 637 638 void 639 textcommit(Text *t, int tofile) 640 { 641 if(t->ncache == 0) 642 return; 643 if(tofile) 644 fileinsert(t->file, t->cq0, t->cache, t->ncache); 645 if(t->what == Body){ 646 t->w->dirty = TRUE; 647 t->w->utflastqid = -1; 648 } 649 t->ncache = 0; 650 } 651 652 static Text *clicktext; 653 static uint clickmsec; 654 static Text *selecttext; 655 static uint selectq; 656 657 /* 658 * called from frame library 659 */ 660 void 661 framescroll(Frame *f, int dl) 662 { 663 if(f != &selecttext->Frame) 664 error("frameselect not right frame"); 665 textframescroll(selecttext, dl); 666 } 667 668 void 669 textframescroll(Text *t, int dl) 670 { 671 uint q0; 672 673 if(dl == 0){ 674 scrsleep(100); 675 return; 676 } 677 if(dl < 0){ 678 q0 = textbacknl(t, t->org, -dl); 679 if(selectq > t->org+t->p0) 680 textsetselect(t, t->org+t->p0, selectq); 681 else 682 textsetselect(t, selectq, t->org+t->p0); 683 }else{ 684 if(t->org+t->nchars == t->file->nc) 685 return; 686 q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+dl*t->font->height)); 687 if(selectq > t->org+t->p1) 688 textsetselect(t, t->org+t->p1, selectq); 689 else 690 textsetselect(t, selectq, t->org+t->p1); 691 } 692 textsetorigin(t, q0, TRUE); 693 } 694 695 696 void 697 textselect(Text *t) 698 { 699 uint q0, q1; 700 int b, x, y; 701 int state; 702 703 selecttext = t; 704 /* 705 * To have double-clicking and chording, we double-click 706 * immediately if it might make sense. 707 */ 708 b = mouse->buttons; 709 q0 = t->q0; 710 q1 = t->q1; 711 selectq = t->org+frcharofpt(t, mouse->xy); 712 if(clicktext==t && mouse->msec-clickmsec<500) 713 if(q0==q1 && selectq==q0){ 714 textdoubleclick(t, &q0, &q1); 715 textsetselect(t, q0, q1); 716 flushimage(display, 1); 717 x = mouse->xy.x; 718 y = mouse->xy.y; 719 /* stay here until something interesting happens */ 720 do 721 readmouse(mousectl); 722 while(mouse->buttons==b && abs(mouse->xy.x-x)<3 && abs(mouse->xy.y-y)<3); 723 mouse->xy.x = x; /* in case we're calling frselect */ 724 mouse->xy.y = y; 725 q0 = t->q0; /* may have changed */ 726 q1 = t->q1; 727 selectq = q0; 728 } 729 if(mouse->buttons == b){ 730 t->Frame.scroll = framescroll; 731 frselect(t, mousectl); 732 /* horrible botch: while asleep, may have lost selection altogether */ 733 if(selectq > t->file->nc) 734 selectq = t->org + t->p0; 735 t->Frame.scroll = nil; 736 if(selectq < t->org) 737 q0 = selectq; 738 else 739 q0 = t->org + t->p0; 740 if(selectq > t->org+t->nchars) 741 q1 = selectq; 742 else 743 q1 = t->org+t->p1; 744 } 745 if(q0 == q1){ 746 if(q0==t->q0 && clicktext==t && mouse->msec-clickmsec<500){ 747 textdoubleclick(t, &q0, &q1); 748 clicktext = nil; 749 }else{ 750 clicktext = t; 751 clickmsec = mouse->msec; 752 } 753 }else 754 clicktext = nil; 755 textsetselect(t, q0, q1); 756 flushimage(display, 1); 757 state = 0; /* undo when possible; +1 for cut, -1 for paste */ 758 while(mouse->buttons){ 759 mouse->msec = 0; 760 b = mouse->buttons; 761 if(b & 6){ 762 if(state==0 && t->what==Body){ 763 seq++; 764 filemark(t->w->body.file); 765 } 766 if(b & 2){ 767 if(state==-1 && t->what==Body){ 768 winundo(t->w, TRUE); 769 textsetselect(t, q0, t->q0); 770 state = 0; 771 }else if(state != 1){ 772 cut(t, t, nil, TRUE, TRUE, nil, 0); 773 state = 1; 774 } 775 }else{ 776 if(state==1 && t->what==Body){ 777 winundo(t->w, TRUE); 778 textsetselect(t, q0, t->q1); 779 state = 0; 780 }else if(state != -1){ 781 paste(t, t, nil, TRUE, FALSE, nil, 0); 782 state = -1; 783 } 784 } 785 textscrdraw(t); 786 clearmouse(); 787 } 788 flushimage(display, 1); 789 while(mouse->buttons == b) 790 readmouse(mousectl); 791 clicktext = nil; 792 } 793 } 794 795 void 796 textshow(Text *t, uint q0, uint q1, int doselect) 797 { 798 int qe; 799 int nl; 800 uint q; 801 802 if(t->what != Body) 803 return; 804 if(t->w!=nil && t->maxlines==0) 805 colgrow(t->col, t->w, 1); 806 if(doselect) 807 textsetselect(t, q0, q1); 808 qe = t->org+t->nchars; 809 if(t->org<=q0 && (q0<qe || (q0==qe && qe==t->file->nc+t->ncache))) 810 textscrdraw(t); 811 else{ 812 if(t->w->nopen[QWevent] > 0) 813 nl = 3*t->maxlines/4; 814 else 815 nl = t->maxlines/4; 816 q = textbacknl(t, q0, nl); 817 /* avoid going backwards if trying to go forwards - long lines! */ 818 if(!(q0>t->org && q<t->org)) 819 textsetorigin(t, q, TRUE); 820 while(q0 > t->org+t->nchars) 821 textsetorigin(t, t->org+1, FALSE); 822 } 823 } 824 825 static 826 int 827 region(int a, int b) 828 { 829 if(a < b) 830 return -1; 831 if(a == b) 832 return 0; 833 return 1; 834 } 835 836 void 837 selrestore(Frame *f, Point pt0, uint p0, uint p1) 838 { 839 if(p1<=f->p0 || p0>=f->p1){ 840 /* no overlap */ 841 frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]); 842 return; 843 } 844 if(p0>=f->p0 && p1<=f->p1){ 845 /* entirely inside */ 846 frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]); 847 return; 848 } 849 850 /* they now are known to overlap */ 851 852 /* before selection */ 853 if(p0 < f->p0){ 854 frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]); 855 p0 = f->p0; 856 pt0 = frptofchar(f, p0); 857 } 858 /* after selection */ 859 if(p1 > f->p1){ 860 frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]); 861 p1 = f->p1; 862 } 863 /* inside selection */ 864 frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]); 865 } 866 867 void 868 textsetselect(Text *t, uint q0, uint q1) 869 { 870 int p0, p1; 871 872 /* t->p0 and t->p1 are always right; t->q0 and t->q1 may be off */ 873 t->q0 = q0; 874 t->q1 = q1; 875 /* compute desired p0,p1 from q0,q1 */ 876 p0 = q0-t->org; 877 p1 = q1-t->org; 878 if(p0 < 0) 879 p0 = 0; 880 if(p1 < 0) 881 p1 = 0; 882 if(p0 > t->nchars) 883 p0 = t->nchars; 884 if(p1 > t->nchars) 885 p1 = t->nchars; 886 if(p0==t->p0 && p1==t->p1) 887 return; 888 /* screen disagrees with desired selection */ 889 if(t->p1<=p0 || p1<=t->p0 || p0==p1 || t->p1==t->p0){ 890 /* no overlap or too easy to bother trying */ 891 frdrawsel(t, frptofchar(t, t->p0), t->p0, t->p1, 0); 892 frdrawsel(t, frptofchar(t, p0), p0, p1, 1); 893 goto Return; 894 } 895 /* overlap; avoid unnecessary painting */ 896 if(p0 < t->p0){ 897 /* extend selection backwards */ 898 frdrawsel(t, frptofchar(t, p0), p0, t->p0, 1); 899 }else if(p0 > t->p0){ 900 /* trim first part of selection */ 901 frdrawsel(t, frptofchar(t, t->p0), t->p0, p0, 0); 902 } 903 if(p1 > t->p1){ 904 /* extend selection forwards */ 905 frdrawsel(t, frptofchar(t, t->p1), t->p1, p1, 1); 906 }else if(p1 < t->p1){ 907 /* trim last part of selection */ 908 frdrawsel(t, frptofchar(t, p1), p1, t->p1, 0); 909 } 910 911 Return: 912 t->p0 = p0; 913 t->p1 = p1; 914 } 915 916 /* 917 * Release the button in less than DELAY ms and it's considered a null selection 918 * if the mouse hardly moved, regardless of whether it crossed a char boundary. 919 */ 920 enum { 921 DELAY = 2, 922 MINMOVE = 4, 923 }; 924 925 uint 926 xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p) /* when called, button is down */ 927 { 928 uint p0, p1, q, tmp; 929 ulong msec; 930 Point mp, pt0, pt1, qt; 931 int reg, b; 932 933 mp = mc->xy; 934 b = mc->buttons; 935 msec = mc->msec; 936 937 /* remove tick */ 938 if(f->p0 == f->p1) 939 frtick(f, frptofchar(f, f->p0), 0); 940 p0 = p1 = frcharofpt(f, mp); 941 pt0 = frptofchar(f, p0); 942 pt1 = frptofchar(f, p1); 943 reg = 0; 944 frtick(f, pt0, 1); 945 do{ 946 q = frcharofpt(f, mc->xy); 947 if(p1 != q){ 948 if(p0 == p1) 949 frtick(f, pt0, 0); 950 if(reg != region(q, p0)){ /* crossed starting point; reset */ 951 if(reg > 0) 952 selrestore(f, pt0, p0, p1); 953 else if(reg < 0) 954 selrestore(f, pt1, p1, p0); 955 p1 = p0; 956 pt1 = pt0; 957 reg = region(q, p0); 958 if(reg == 0) 959 frdrawsel0(f, pt0, p0, p1, col, display->white); 960 } 961 qt = frptofchar(f, q); 962 if(reg > 0){ 963 if(q > p1) 964 frdrawsel0(f, pt1, p1, q, col, display->white); 965 966 else if(q < p1) 967 selrestore(f, qt, q, p1); 968 }else if(reg < 0){ 969 if(q > p1) 970 selrestore(f, pt1, p1, q); 971 else 972 frdrawsel0(f, qt, q, p1, col, display->white); 973 } 974 p1 = q; 975 pt1 = qt; 976 } 977 if(p0 == p1) 978 frtick(f, pt0, 1); 979 flushimage(f->display, 1); 980 readmouse(mc); 981 }while(mc->buttons == b); 982 if(mc->msec-msec < DELAY && p0!=p1 983 && abs(mp.x-mc->xy.x)<MINMOVE 984 && abs(mp.y-mc->xy.y)<MINMOVE) { 985 if(reg > 0) 986 selrestore(f, pt0, p0, p1); 987 else if(reg < 0) 988 selrestore(f, pt1, p1, p0); 989 p1 = p0; 990 } 991 if(p1 < p0){ 992 tmp = p0; 993 p0 = p1; 994 p1 = tmp; 995 } 996 pt0 = frptofchar(f, p0); 997 if(p0 == p1) 998 frtick(f, pt0, 0); 999 selrestore(f, pt0, p0, p1); 1000 /* restore tick */ 1001 if(f->p0 == f->p1) 1002 frtick(f, frptofchar(f, f->p0), 1); 1003 flushimage(f->display, 1); 1004 *p1p = p1; 1005 return p0; 1006 } 1007 1008 int 1009 textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask) 1010 { 1011 uint p0, p1; 1012 int buts; 1013 1014 p0 = xselect(t, mousectl, high, &p1); 1015 buts = mousectl->buttons; 1016 if((buts & mask) == 0){ 1017 *q0 = p0+t->org; 1018 *q1 = p1+t->org; 1019 } 1020 1021 while(mousectl->buttons) 1022 readmouse(mousectl); 1023 return buts; 1024 } 1025 1026 int 1027 textselect2(Text *t, uint *q0, uint *q1, Text **tp) 1028 { 1029 int buts; 1030 1031 *tp = nil; 1032 buts = textselect23(t, q0, q1, but2col, 4); 1033 if(buts & 4) 1034 return 0; 1035 if(buts & 1){ /* pick up argument */ 1036 *tp = argtext; 1037 return 1; 1038 } 1039 return 1; 1040 } 1041 1042 int 1043 textselect3(Text *t, uint *q0, uint *q1) 1044 { 1045 int h; 1046 1047 h = (textselect23(t, q0, q1, but3col, 1|2) == 0); 1048 return h; 1049 } 1050 1051 static Rune left1[] = { L'{', L'[', L'(', L'<', L'«', 0 }; 1052 static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 }; 1053 static Rune left2[] = { L'\n', 0 }; 1054 static Rune left3[] = { L'\'', L'"', L'`', 0 }; 1055 1056 static 1057 Rune *left[] = { 1058 left1, 1059 left2, 1060 left3, 1061 nil 1062 }; 1063 static 1064 Rune *right[] = { 1065 right1, 1066 left2, 1067 left3, 1068 nil 1069 }; 1070 1071 void 1072 textdoubleclick(Text *t, uint *q0, uint *q1) 1073 { 1074 int c, i; 1075 Rune *r, *l, *p; 1076 uint q; 1077 1078 for(i=0; left[i]!=nil; i++){ 1079 q = *q0; 1080 l = left[i]; 1081 r = right[i]; 1082 /* try matching character to left, looking right */ 1083 if(q == 0) 1084 c = '\n'; 1085 else 1086 c = textreadc(t, q-1); 1087 p = runestrchr(l, c); 1088 if(p != nil){ 1089 if(textclickmatch(t, c, r[p-l], 1, &q)) 1090 *q1 = q-(c!='\n'); 1091 return; 1092 } 1093 /* try matching character to right, looking left */ 1094 if(q == t->file->nc) 1095 c = '\n'; 1096 else 1097 c = textreadc(t, q); 1098 p = runestrchr(r, c); 1099 if(p != nil){ 1100 if(textclickmatch(t, c, l[p-r], -1, &q)){ 1101 *q1 = *q0+(*q0<t->file->nc && c=='\n'); 1102 *q0 = q; 1103 if(c!='\n' || q!=0 || textreadc(t, 0)=='\n') 1104 (*q0)++; 1105 } 1106 return; 1107 } 1108 } 1109 /* try filling out word to right */ 1110 while(*q1<t->file->nc && isalnum(textreadc(t, *q1))) 1111 (*q1)++; 1112 /* try filling out word to left */ 1113 while(*q0>0 && isalnum(textreadc(t, *q0-1))) 1114 (*q0)--; 1115 } 1116 1117 int 1118 textclickmatch(Text *t, int cl, int cr, int dir, uint *q) 1119 { 1120 Rune c; 1121 int nest; 1122 1123 nest = 1; 1124 for(;;){ 1125 if(dir > 0){ 1126 if(*q == t->file->nc) 1127 break; 1128 c = textreadc(t, *q); 1129 (*q)++; 1130 }else{ 1131 if(*q == 0) 1132 break; 1133 (*q)--; 1134 c = textreadc(t, *q); 1135 } 1136 if(c == cr){ 1137 if(--nest==0) 1138 return 1; 1139 }else if(c == cl) 1140 nest++; 1141 } 1142 return cl=='\n' && nest==1; 1143 } 1144 1145 uint 1146 textbacknl(Text *t, uint p, uint n) 1147 { 1148 int i, j; 1149 1150 /* look for start of this line if n==0 */ 1151 if(n==0 && p>0 && textreadc(t, p-1)!='\n') 1152 n = 1; 1153 i = n; 1154 while(i-->0 && p>0){ 1155 --p; /* it's at a newline now; back over it */ 1156 if(p == 0) 1157 break; 1158 /* at 128 chars, call it a line anyway */ 1159 for(j=128; --j>0 && p>0; p--) 1160 if(textreadc(t, p-1)=='\n') 1161 break; 1162 } 1163 return p; 1164 } 1165 1166 void 1167 textsetorigin(Text *t, uint org, int exact) 1168 { 1169 int i, a, fixup; 1170 Rune *r; 1171 uint n; 1172 1173 if(org>0 && !exact){ 1174 /* org is an estimate of the char posn; find a newline */ 1175 /* don't try harder than 256 chars */ 1176 for(i=0; i<256 && org<t->file->nc; i++){ 1177 if(textreadc(t, org) == '\n'){ 1178 org++; 1179 break; 1180 } 1181 org++; 1182 } 1183 } 1184 a = org-t->org; 1185 fixup = 0; 1186 if(a>=0 && a<t->nchars){ 1187 frdelete(t, 0, a); 1188 fixup = 1; /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */ 1189 } 1190 else if(a<0 && -a<t->nchars){ 1191 n = t->org - org; 1192 r = runemalloc(n); 1193 bufread(t->file, org, r, n); 1194 frinsert(t, r, r+n, 0); 1195 free(r); 1196 }else 1197 frdelete(t, 0, t->nchars); 1198 t->org = org; 1199 textfill(t); 1200 textscrdraw(t); 1201 textsetselect(t, t->q0, t->q1); 1202 if(fixup && t->p1 > t->p0) 1203 frdrawsel(t, frptofchar(t, t->p1-1), t->p1-1, t->p1, 1); 1204 } 1205 1206 void 1207 textreset(Text *t) 1208 { 1209 t->file->seq = 0; 1210 t->eq0 = ~0; 1211 /* do t->delete(0, t->nc, TRUE) without building backup stuff */ 1212 textsetselect(t, t->org, t->org); 1213 frdelete(t, 0, t->nchars); 1214 t->org = 0; 1215 t->q0 = 0; 1216 t->q1 = 0; 1217 filereset(t->file); 1218 bufreset(t->file); 1219 } 1220