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 <regexp.h> 11 #include <plumb.h> 12 #include "dat.h" 13 #include "fns.h" 14 15 Window* openfile(Text*, Expand*); 16 17 int nuntitled; 18 19 void 20 look3(Text *t, uint q0, uint q1, int external) 21 { 22 int n, c, f, expanded; 23 Text *ct; 24 Expand e; 25 Rune *r; 26 uint p; 27 Plumbmsg *m; 28 Runestr dir; 29 char buf[32]; 30 31 ct = seltext; 32 if(ct == nil) 33 seltext = t; 34 expanded = expand(t, q0, q1, &e); 35 if(!external && t->w!=nil && t->w->nopen[QWevent]>0){ 36 /* send alphanumeric expansion to external client */ 37 if(expanded == FALSE) 38 return; 39 f = 0; 40 if((e.at!=nil && t->w!=nil) || (e.nname>0 && lookfile(e.name, e.nname)!=nil)) 41 f = 1; /* acme can do it without loading a file */ 42 if(q0!=e.q0 || q1!=e.q1) 43 f |= 2; /* second (post-expand) message follows */ 44 if(e.nname) 45 f |= 4; /* it's a file name */ 46 c = 'l'; 47 if(t->what == Body) 48 c = 'L'; 49 n = q1-q0; 50 if(n <= EVENTSIZE){ 51 r = runemalloc(n); 52 bufread(t->file, q0, r, n); 53 winevent(t->w, "%c%d %d %d %d %.*S\n", c, q0, q1, f, n, n, r); 54 free(r); 55 }else 56 winevent(t->w, "%c%d %d %d 0 \n", c, q0, q1, f, n); 57 if(q0==e.q0 && q1==e.q1) 58 return; 59 if(e.nname){ 60 n = e.nname; 61 if(e.a1 > e.a0) 62 n += 1+(e.a1-e.a0); 63 r = runemalloc(n); 64 runemove(r, e.name, e.nname); 65 if(e.a1 > e.a0){ 66 r[e.nname] = ':'; 67 bufread(e.at->file, e.a0, r+e.nname+1, e.a1-e.a0); 68 } 69 }else{ 70 n = e.q1 - e.q0; 71 r = runemalloc(n); 72 bufread(t->file, e.q0, r, n); 73 } 74 f &= ~2; 75 if(n <= EVENTSIZE) 76 winevent(t->w, "%c%d %d %d %d %.*S\n", c, e.q0, e.q1, f, n, n, r); 77 else 78 winevent(t->w, "%c%d %d %d 0 \n", c, e.q0, e.q1, f, n); 79 free(r); 80 goto Return; 81 } 82 if(plumbsendfd >= 0){ 83 /* send whitespace-delimited word to plumber */ 84 m = emalloc(sizeof(Plumbmsg)); 85 m->src = estrdup("acme"); 86 m->dst = nil; 87 dir = dirname(t, nil, 0); 88 if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */ 89 free(dir.r); 90 dir.r = nil; 91 dir.nr = 0; 92 } 93 if(dir.nr == 0) 94 m->wdir = estrdup(wdir); 95 else 96 m->wdir = runetobyte(dir.r, dir.nr); 97 free(dir.r); 98 m->type = estrdup("text"); 99 m->attr = nil; 100 buf[0] = '\0'; 101 if(q1 == q0){ 102 if(t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){ 103 q0 = t->q0; 104 q1 = t->q1; 105 }else{ 106 p = q0; 107 while(q0>0 && (c=tgetc(t, q0-1))!=' ' && c!='\t' && c!='\n') 108 q0--; 109 while(q1<t->file->nc && (c=tgetc(t, q1))!=' ' && c!='\t' && c!='\n') 110 q1++; 111 if(q1 == q0){ 112 plumbfree(m); 113 goto Return; 114 } 115 sprint(buf, "click=%d", p-q0); 116 m->attr = plumbunpackattr(buf); 117 } 118 } 119 r = runemalloc(q1-q0); 120 bufread(t->file, q0, r, q1-q0); 121 m->data = runetobyte(r, q1-q0); 122 m->ndata = strlen(m->data); 123 free(r); 124 if(m->ndata<messagesize-1024 && plumbsend(plumbsendfd, m) >= 0){ 125 plumbfree(m); 126 goto Return; 127 } 128 plumbfree(m); 129 /* plumber failed to match; fall through */ 130 } 131 132 /* interpret alphanumeric string ourselves */ 133 if(expanded == FALSE) 134 return; 135 if(e.name || e.at) 136 openfile(t, &e); 137 else{ 138 if(t->w == nil) 139 return; 140 ct = &t->w->body; 141 if(t->w != ct->w) 142 winlock(ct->w, 'M'); 143 if(t == ct) 144 textsetselect(ct, e.q1, e.q1); 145 n = e.q1 - e.q0; 146 r = runemalloc(n); 147 bufread(t->file, e.q0, r, n); 148 if(search(ct, r, n) && e.jump) 149 moveto(mousectl, addpt(frptofchar(ct, ct->p0), Pt(4, ct->font->height-4))); 150 if(t->w != ct->w) 151 winunlock(ct->w); 152 free(r); 153 } 154 155 Return: 156 free(e.name); 157 free(e.bname); 158 } 159 160 int 161 plumbgetc(void *a, uint n) 162 { 163 Rune *r; 164 165 r = a; 166 if(n>runestrlen(r)) 167 return 0; 168 return r[n]; 169 } 170 171 void 172 plumblook(Plumbmsg *m) 173 { 174 Expand e; 175 char *addr; 176 177 if(m->ndata >= BUFSIZE){ 178 warning(nil, "insanely long file name (%d bytes) in plumb message (%.32s...)\n", m->ndata, m->data); 179 return; 180 } 181 e.q0 = 0; 182 e.q1 = 0; 183 if(m->data[0] == '\0') 184 return; 185 e.ar = nil; 186 e.bname = m->data; 187 e.name = bytetorune(e.bname, &e.nname); 188 e.jump = TRUE; 189 e.a0 = 0; 190 e.a1 = 0; 191 addr = plumblookup(m->attr, "addr"); 192 if(addr != nil){ 193 e.ar = bytetorune(addr, &e.a1); 194 e.agetc = plumbgetc; 195 } 196 openfile(nil, &e); 197 free(e.name); 198 free(e.at); 199 } 200 201 void 202 plumbshow(Plumbmsg *m) 203 { 204 Window *w; 205 Rune rb[256], *r; 206 int nb, nr; 207 Runestr rs; 208 char *name, *p, namebuf[16]; 209 210 w = makenewwindow(nil); 211 name = plumblookup(m->attr, "filename"); 212 if(name == nil){ 213 name = namebuf; 214 nuntitled++; 215 snprint(namebuf, sizeof namebuf, "Untitled-%d", nuntitled); 216 } 217 p = nil; 218 if(name[0]!='/' && m->wdir!=nil && m->wdir[0]!='\0'){ 219 nb = strlen(m->wdir) + 1 + strlen(name) + 1; 220 p = emalloc(nb); 221 snprint(p, nb, "%s/%s", m->wdir, name); 222 name = p; 223 } 224 cvttorunes(name, strlen(name), rb, &nb, &nr, nil); 225 free(p); 226 rs = cleanrname((Runestr){rb, nr}); 227 winsetname(w, rs.r, rs.nr); 228 r = runemalloc(m->ndata); 229 cvttorunes(m->data, m->ndata, r, &nb, &nr, nil); 230 textinsert(&w->body, 0, r, nr, TRUE); 231 free(r); 232 w->body.file->mod = FALSE; 233 w->dirty = FALSE; 234 winsettag(w); 235 textscrdraw(&w->body); 236 textsetselect(&w->tag, w->tag.file->nc, w->tag.file->nc); 237 } 238 239 int 240 search(Text *ct, Rune *r, uint n) 241 { 242 uint q, nb, maxn; 243 int around; 244 Rune *s, *b, *c; 245 246 if(n==0 || n>ct->file->nc) 247 return FALSE; 248 if(2*n > RBUFSIZE){ 249 warning(nil, "string too long\n"); 250 return FALSE; 251 } 252 maxn = max(2*n, RBUFSIZE); 253 s = fbufalloc(); 254 b = s; 255 nb = 0; 256 b[nb] = 0; 257 around = 0; 258 q = ct->q1; 259 for(;;){ 260 if(q >= ct->file->nc){ 261 q = 0; 262 around = 1; 263 nb = 0; 264 b[nb] = 0; 265 } 266 if(nb > 0){ 267 c = runestrchr(b, r[0]); 268 if(c == nil){ 269 q += nb; 270 nb = 0; 271 b[nb] = 0; 272 if(around && q>=ct->q1) 273 break; 274 continue; 275 } 276 q += (c-b); 277 nb -= (c-b); 278 b = c; 279 } 280 /* reload if buffer covers neither string nor rest of file */ 281 if(nb<n && nb!=ct->file->nc-q){ 282 nb = ct->file->nc-q; 283 if(nb >= maxn) 284 nb = maxn-1; 285 bufread(ct->file, q, s, nb); 286 b = s; 287 b[nb] = '\0'; 288 } 289 /* this runeeq is fishy but the null at b[nb] makes it safe */ 290 if(runeeq(b, n, r, n)==TRUE){ 291 if(ct->w){ 292 textshow(ct, q, q+n, 1); 293 winsettag(ct->w); 294 }else{ 295 ct->q0 = q; 296 ct->q1 = q+n; 297 } 298 seltext = ct; 299 fbuffree(s); 300 return TRUE; 301 } 302 if(around && q>=ct->q1) 303 break; 304 --nb; 305 b++; 306 q++; 307 } 308 fbuffree(s); 309 return FALSE; 310 } 311 312 int 313 isfilec(Rune r) 314 { 315 if(isalnum(r)) 316 return TRUE; 317 if(runestrchr(L".-+/:", r)) 318 return TRUE; 319 return FALSE; 320 } 321 322 /* Runestr wrapper for cleanname */ 323 Runestr 324 cleanrname(Runestr rs) 325 { 326 char *s; 327 int nb, nulls; 328 329 s = runetobyte(rs.r, rs.nr); 330 cleanname(s); 331 cvttorunes(s, strlen(s), rs.r, &nb, &rs.nr, &nulls); 332 free(s); 333 return rs; 334 } 335 336 Runestr 337 includefile(Rune *dir, Rune *file, int nfile) 338 { 339 int m, n; 340 char *a; 341 Rune *r; 342 343 m = runestrlen(dir); 344 a = emalloc((m+1+nfile)*UTFmax+1); 345 sprint(a, "%S/%.*S", dir, nfile, file); 346 n = access(a, 0); 347 free(a); 348 if(n < 0) 349 return (Runestr){nil, 0}; 350 r = runemalloc(m+1+nfile); 351 runemove(r, dir, m); 352 runemove(r+m, L"/", 1); 353 runemove(r+m+1, file, nfile); 354 free(file); 355 return cleanrname((Runestr){r, m+1+nfile}); 356 } 357 358 static Rune *objdir; 359 360 Runestr 361 includename(Text *t, Rune *r, int n) 362 { 363 Window *w; 364 char buf[128]; 365 Runestr file; 366 int i; 367 368 if(objdir==nil && objtype!=nil){ 369 sprint(buf, "/%s/include", objtype); 370 objdir = bytetorune(buf, &i); 371 objdir = runerealloc(objdir, i+1); 372 objdir[i] = '\0'; 373 } 374 375 w = t->w; 376 if(n==0 || r[0]=='/' || w==nil) 377 goto Rescue; 378 if(n>2 && r[0]=='.' && r[1]=='/') 379 goto Rescue; 380 file.r = nil; 381 file.nr = 0; 382 for(i=0; i<w->nincl && file.r==nil; i++) 383 file = includefile(w->incl[i], r, n); 384 385 if(file.r == nil) 386 file = includefile(L"/sys/include", r, n); 387 if(file.r==nil && objdir!=nil) 388 file = includefile(objdir, r, n); 389 if(file.r == nil) 390 goto Rescue; 391 return file; 392 393 Rescue: 394 return (Runestr){r, n}; 395 } 396 397 Runestr 398 dirname(Text *t, Rune *r, int n) 399 { 400 Rune *b, c; 401 uint m, nt; 402 int slash; 403 Runestr tmp; 404 405 b = nil; 406 if(t==nil || t->w==nil) 407 goto Rescue; 408 nt = t->w->tag.file->nc; 409 if(nt == 0) 410 goto Rescue; 411 if(n>=1 && r[0]=='/') 412 goto Rescue; 413 b = runemalloc(nt+n+1); 414 bufread(t->w->tag.file, 0, b, nt); 415 slash = -1; 416 for(m=0; m<nt; m++){ 417 c = b[m]; 418 if(c == '/') 419 slash = m; 420 if(c==' ' || c=='\t') 421 break; 422 } 423 if(slash < 0) 424 goto Rescue; 425 runemove(b+slash+1, r, n); 426 free(r); 427 return cleanrname((Runestr){b, slash+1+n}); 428 429 Rescue: 430 free(b); 431 tmp = (Runestr){r, n}; 432 if(r) 433 return cleanrname(tmp); 434 return tmp; 435 } 436 437 int 438 expandfile(Text *t, uint q0, uint q1, Expand *e) 439 { 440 int i, n, nname, colon, eval; 441 uint amin, amax; 442 Rune *r, c; 443 Window *w; 444 Runestr rs; 445 446 amax = q1; 447 if(q1 == q0){ 448 colon = -1; 449 while(q1<t->file->nc && isfilec(c=textreadc(t, q1))){ 450 if(c == ':'){ 451 colon = q1; 452 break; 453 } 454 q1++; 455 } 456 while(q0>0 && (isfilec(c=textreadc(t, q0-1)) || isaddrc(c) || isregexc(c))){ 457 q0--; 458 if(colon<0 && c==':') 459 colon = q0; 460 } 461 /* 462 * if it looks like it might begin file: , consume address chars after : 463 * otherwise terminate expansion at : 464 */ 465 if(colon >= 0){ 466 q1 = colon; 467 if(colon<t->file->nc-1 && isaddrc(textreadc(t, colon+1))){ 468 q1 = colon+1; 469 while(q1<t->file->nc && isaddrc(textreadc(t, q1))) 470 q1++; 471 } 472 } 473 if(q1 > q0) 474 if(colon >= 0){ /* stop at white space */ 475 for(amax=colon+1; amax<t->file->nc; amax++) 476 if((c=textreadc(t, amax))==' ' || c=='\t' || c=='\n') 477 break; 478 }else 479 amax = t->file->nc; 480 } 481 amin = amax; 482 e->q0 = q0; 483 e->q1 = q1; 484 n = q1-q0; 485 if(n == 0) 486 return FALSE; 487 /* see if it's a file name */ 488 r = runemalloc(n); 489 bufread(t->file, q0, r, n); 490 /* first, does it have bad chars? */ 491 nname = -1; 492 for(i=0; i<n; i++){ 493 c = r[i]; 494 if(c==':' && nname<0){ 495 if(q0+i+1<t->file->nc && (i==n-1 || isaddrc(textreadc(t, q0+i+1)))) 496 amin = q0+i; 497 else 498 goto Isntfile; 499 nname = i; 500 } 501 } 502 if(nname == -1) 503 nname = n; 504 for(i=0; i<nname; i++) 505 if(!isfilec(r[i])) 506 goto Isntfile; 507 /* 508 * See if it's a file name in <>, and turn that into an include 509 * file name if so. Should probably do it for "" too, but that's not 510 * restrictive enough syntax and checking for a #include earlier on the 511 * line would be silly. 512 */ 513 if(q0>0 && textreadc(t, q0-1)=='<' && q1<t->file->nc && textreadc(t, q1)=='>'){ 514 rs = includename(t, r, nname); 515 r = rs.r; 516 nname = rs.nr; 517 } 518 else if(amin == q0) 519 goto Isfile; 520 else{ 521 rs = dirname(t, r, nname); 522 r = rs.r; 523 nname = rs.nr; 524 } 525 e->bname = runetobyte(r, nname); 526 /* if it's already a window name, it's a file */ 527 w = lookfile(r, nname); 528 if(w != nil) 529 goto Isfile; 530 /* if it's the name of a file, it's a file */ 531 if(access(e->bname, 0) < 0){ 532 free(e->bname); 533 e->bname = nil; 534 goto Isntfile; 535 } 536 537 Isfile: 538 e->name = r; 539 e->nname = nname; 540 e->at = t; 541 e->a0 = amin+1; 542 eval = FALSE; 543 address(nil, nil, (Range){-1,-1}, (Range){0, 0}, t, e->a0, amax, tgetc, &eval, (uint*)&e->a1); 544 return TRUE; 545 546 Isntfile: 547 free(r); 548 return FALSE; 549 } 550 551 int 552 expand(Text *t, uint q0, uint q1, Expand *e) 553 { 554 memset(e, 0, sizeof *e); 555 e->agetc = tgetc; 556 /* if in selection, choose selection */ 557 e->jump = TRUE; 558 if(q1==q0 && t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){ 559 q0 = t->q0; 560 q1 = t->q1; 561 if(t->what == Tag) 562 e->jump = FALSE; 563 } 564 565 if(expandfile(t, q0, q1, e)) 566 return TRUE; 567 568 if(q0 == q1){ 569 while(q1<t->file->nc && isalnum(textreadc(t, q1))) 570 q1++; 571 while(q0>0 && isalnum(textreadc(t, q0-1))) 572 q0--; 573 } 574 e->q0 = q0; 575 e->q1 = q1; 576 return q1 > q0; 577 } 578 579 Window* 580 lookfile(Rune *s, int n) 581 { 582 int i, j, k; 583 Window *w; 584 Column *c; 585 Text *t; 586 587 /* avoid terminal slash on directories */ 588 if(n>1 && s[n-1] == '/') 589 --n; 590 for(j=0; j<row.ncol; j++){ 591 c = row.col[j]; 592 for(i=0; i<c->nw; i++){ 593 w = c->w[i]; 594 t = &w->body; 595 k = t->file->nname; 596 if(k>1 && t->file->name[k-1] == '/') 597 k--; 598 if(runeeq(t->file->name, k, s, n)){ 599 w = w->body.file->curtext->w; 600 if(w->col != nil) /* protect against race deleting w */ 601 return w; 602 } 603 } 604 } 605 return nil; 606 } 607 608 Window* 609 lookid(int id, int dump) 610 { 611 int i, j; 612 Window *w; 613 Column *c; 614 615 for(j=0; j<row.ncol; j++){ 616 c = row.col[j]; 617 for(i=0; i<c->nw; i++){ 618 w = c->w[i]; 619 if(dump && w->dumpid == id) 620 return w; 621 if(!dump && w->id == id) 622 return w; 623 } 624 } 625 return nil; 626 } 627 628 629 Window* 630 openfile(Text *t, Expand *e) 631 { 632 Range r; 633 Window *w, *ow; 634 int eval, i, n; 635 Rune *rp; 636 uint dummy; 637 638 if(e->nname == 0){ 639 w = t->w; 640 if(w == nil) 641 return nil; 642 }else 643 w = lookfile(e->name, e->nname); 644 if(w){ 645 t = &w->body; 646 if(!t->col->safe && t->maxlines==0) /* window is obscured by full-column window */ 647 colgrow(t->col, t->col->w[0], 1); 648 }else{ 649 ow = nil; 650 if(t) 651 ow = t->w; 652 w = makenewwindow(t); 653 t = &w->body; 654 winsetname(w, e->name, e->nname); 655 textload(t, 0, e->bname, 1); 656 t->file->mod = FALSE; 657 t->w->dirty = FALSE; 658 winsettag(t->w); 659 textsetselect(&t->w->tag, t->w->tag.file->nc, t->w->tag.file->nc); 660 if(ow != nil){ 661 for(i=ow->nincl; --i>=0; ){ 662 n = runestrlen(ow->incl[i]); 663 rp = runemalloc(n); 664 runemove(rp, ow->incl[i], n); 665 winaddincl(w, rp, n); 666 } 667 w->autoindent = ow->autoindent; 668 }else 669 w->autoindent = globalautoindent; 670 } 671 if(e->a1 == e->a0) 672 eval = FALSE; 673 else{ 674 eval = TRUE; 675 r = address(nil, t, (Range){-1, -1}, (Range){t->q0, t->q1}, e->at, e->a0, e->a1, e->agetc, &eval, &dummy); 676 if(eval == FALSE) 677 e->jump = FALSE; /* don't jump if invalid address */ 678 } 679 if(eval == FALSE){ 680 r.q0 = t->q0; 681 r.q1 = t->q1; 682 } 683 textshow(t, r.q0, r.q1, 1); 684 winsettag(t->w); 685 seltext = t; 686 if(e->jump) 687 moveto(mousectl, addpt(frptofchar(t, t->p0), Pt(4, font->height-4))); 688 return w; 689 } 690 691 void 692 new(Text *et, Text *t, Text *argt, int flag1, int flag2, Rune *arg, int narg) 693 { 694 int ndone; 695 Rune *a, *f; 696 int na, nf; 697 Expand e; 698 Runestr rs; 699 700 getarg(argt, FALSE, TRUE, &a, &na); 701 if(a){ 702 new(et, t, nil, flag1, flag2, a, na); 703 if(narg == 0) 704 return; 705 } 706 /* loop condition: *arg is not a blank */ 707 for(ndone=0; ; ndone++){ 708 a = findbl(arg, narg, &na); 709 if(a == arg){ 710 if(ndone==0 && et->col!=nil) 711 winsettag(coladd(et->col, nil, nil, -1)); 712 break; 713 } 714 nf = narg-na; 715 f = runemalloc(nf); 716 runemove(f, arg, nf); 717 rs = dirname(et, f, nf); 718 f = rs.r; 719 nf = rs.nr; 720 memset(&e, 0, sizeof e); 721 e.name = f; 722 e.nname = nf; 723 e.bname = runetobyte(f, nf); 724 e.jump = TRUE; 725 openfile(et, &e); 726 free(f); 727 free(e.bname); 728 arg = skipbl(a, na, &narg); 729 } 730 } 731