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