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