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