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