edit.c (12156B)
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 "dat.h" 13 #include "edit.h" 14 #include "fns.h" 15 16 static char linex[]="\n"; 17 static char wordx[]=" \t\n"; 18 struct cmdtab cmdtab[]={ 19 /* cmdc text regexp addr defcmd defaddr count token fn */ 20 '\n', 0, 0, 0, 0, aDot, 0, 0, nl_cmd, 21 'a', 1, 0, 0, 0, aDot, 0, 0, a_cmd, 22 'b', 0, 0, 0, 0, aNo, 0, linex, b_cmd, 23 'c', 1, 0, 0, 0, aDot, 0, 0, c_cmd, 24 'd', 0, 0, 0, 0, aDot, 0, 0, d_cmd, 25 'e', 0, 0, 0, 0, aNo, 0, wordx, e_cmd, 26 'f', 0, 0, 0, 0, aNo, 0, wordx, f_cmd, 27 'g', 0, 1, 0, 'p', aDot, 0, 0, g_cmd, 28 'i', 1, 0, 0, 0, aDot, 0, 0, i_cmd, 29 'm', 0, 0, 1, 0, aDot, 0, 0, m_cmd, 30 'p', 0, 0, 0, 0, aDot, 0, 0, p_cmd, 31 'r', 0, 0, 0, 0, aDot, 0, wordx, e_cmd, 32 's', 0, 1, 0, 0, aDot, 1, 0, s_cmd, 33 't', 0, 0, 1, 0, aDot, 0, 0, m_cmd, 34 'u', 0, 0, 0, 0, aNo, 2, 0, u_cmd, 35 'v', 0, 1, 0, 'p', aDot, 0, 0, g_cmd, 36 'w', 0, 0, 0, 0, aAll, 0, wordx, w_cmd, 37 'x', 0, 1, 0, 'p', aDot, 0, 0, x_cmd, 38 'y', 0, 1, 0, 'p', aDot, 0, 0, x_cmd, 39 '=', 0, 0, 0, 0, aDot, 0, linex, eq_cmd, 40 'B', 0, 0, 0, 0, aNo, 0, linex, B_cmd, 41 'D', 0, 0, 0, 0, aNo, 0, linex, D_cmd, 42 'X', 0, 1, 0, 'f', aNo, 0, 0, X_cmd, 43 'Y', 0, 1, 0, 'f', aNo, 0, 0, X_cmd, 44 '<', 0, 0, 0, 0, aDot, 0, linex, pipe_cmd, 45 '|', 0, 0, 0, 0, aDot, 0, linex, pipe_cmd, 46 '>', 0, 0, 0, 0, aDot, 0, linex, pipe_cmd, 47 /* deliberately unimplemented: 48 'k', 0, 0, 0, 0, aDot, 0, 0, k_cmd, 49 'n', 0, 0, 0, 0, aNo, 0, 0, n_cmd, 50 'q', 0, 0, 0, 0, aNo, 0, 0, q_cmd, 51 '!', 0, 0, 0, 0, aNo, 0, linex, plan9_cmd, 52 */ 53 0, 0, 0, 0, 0, 0, 0, 0 54 }; 55 56 Cmd *parsecmd(int); 57 Addr *compoundaddr(void); 58 Addr *simpleaddr(void); 59 void freecmd(void); 60 void okdelim(int); 61 62 Rune *cmdstartp; 63 Rune *cmdendp; 64 Rune *cmdp; 65 Channel *editerrc; 66 67 String *lastpat; 68 int patset; 69 70 List cmdlist; 71 List addrlist; 72 List stringlist; 73 Text *curtext; 74 int editing = Inactive; 75 76 String* newstring(int); 77 78 void 79 editthread(void *v) 80 { 81 Cmd *cmdp; 82 83 USED(v); 84 threadsetname("editthread"); 85 while((cmdp=parsecmd(0)) != 0){ 86 if(cmdexec(curtext, cmdp) == 0) 87 break; 88 freecmd(); 89 } 90 sendp(editerrc, nil); 91 } 92 93 void 94 allelogterm(Window *w, void *x) 95 { 96 USED(x); 97 elogterm(w->body.file); 98 } 99 100 void 101 alleditinit(Window *w, void *x) 102 { 103 USED(x); 104 textcommit(&w->tag, TRUE); 105 textcommit(&w->body, TRUE); 106 w->body.file->editclean = FALSE; 107 } 108 109 void 110 allupdate(Window *w, void *x) 111 { 112 Text *t; 113 int i; 114 File *f; 115 116 USED(x); 117 t = &w->body; 118 f = t->file; 119 if(f->curtext != t) /* do curtext only */ 120 return; 121 if(f->elog.type == Null) 122 elogterm(f); 123 else if(f->elog.type != Empty){ 124 elogapply(f); 125 if(f->editclean){ 126 f->mod = FALSE; 127 for(i=0; i<f->ntext; i++) 128 f->text[i]->w->dirty = FALSE; 129 } 130 } 131 textsetselect(t, t->q0, t->q1); 132 textscrdraw(t); 133 winsettag(w); 134 } 135 136 void 137 editerror(char *fmt, ...) 138 { 139 va_list arg; 140 char *s; 141 142 va_start(arg, fmt); 143 s = vsmprint(fmt, arg); 144 va_end(arg); 145 freecmd(); 146 allwindows(allelogterm, nil); /* truncate the edit logs */ 147 sendp(editerrc, s); 148 threadexits(nil); 149 } 150 151 void 152 editcmd(Text *ct, Rune *r, uint n) 153 { 154 char *err; 155 156 if(n == 0) 157 return; 158 if(2*n > RBUFSIZE){ 159 warning(nil, "string too long\n"); 160 return; 161 } 162 163 allwindows(alleditinit, nil); 164 if(cmdstartp) 165 free(cmdstartp); 166 cmdstartp = runemalloc(n+2); 167 runemove(cmdstartp, r, n); 168 if(r[n-1] != '\n') 169 cmdstartp[n++] = '\n'; 170 cmdstartp[n] = '\0'; 171 cmdendp = cmdstartp+n; 172 cmdp = cmdstartp; 173 if(ct->w == nil) 174 curtext = nil; 175 else 176 curtext = &ct->w->body; 177 resetxec(); 178 if(editerrc == nil){ 179 editerrc = chancreate(sizeof(char*), 0); 180 chansetname(editerrc, "editerrc"); 181 lastpat = allocstring(0); 182 } 183 threadcreate(editthread, nil, STACK); 184 err = recvp(editerrc); 185 editing = Inactive; 186 if(err != nil){ 187 if(err[0] != '\0') 188 warning(nil, "Edit: %s\n", err); 189 free(err); 190 } 191 192 /* update everyone whose edit log has data */ 193 allwindows(allupdate, nil); 194 } 195 196 int 197 getch(void) 198 { 199 if(cmdp == cmdendp) 200 return -1; 201 return *cmdp++; 202 } 203 204 int 205 nextc(void) 206 { 207 if(cmdp == cmdendp) 208 return -1; 209 return *cmdp; 210 } 211 212 void 213 ungetch(void) 214 { 215 if(--cmdp < cmdstartp) 216 error("ungetch"); 217 } 218 219 long 220 getnum(int signok) 221 { 222 long n; 223 int c, sign; 224 225 n = 0; 226 sign = 1; 227 if(signok>1 && nextc()=='-'){ 228 sign = -1; 229 getch(); 230 } 231 if((c=nextc())<'0' || '9'<c) /* no number defaults to 1 */ 232 return sign; 233 while('0'<=(c=getch()) && c<='9') 234 n = n*10 + (c-'0'); 235 ungetch(); 236 return sign*n; 237 } 238 239 int 240 cmdskipbl(void) 241 { 242 int c; 243 do 244 c = getch(); 245 while(c==' ' || c=='\t'); 246 if(c >= 0) 247 ungetch(); 248 return c; 249 } 250 251 /* 252 * Check that list has room for one more element. 253 */ 254 void 255 growlist(List *l) 256 { 257 if(l->u.listptr==0 || l->nalloc==0){ 258 l->nalloc = INCR; 259 l->u.listptr = emalloc(INCR*sizeof(void*)); 260 l->nused = 0; 261 }else if(l->nused == l->nalloc){ 262 l->u.listptr = erealloc(l->u.listptr, (l->nalloc+INCR)*sizeof(void*)); 263 memset(l->u.ptr+l->nalloc, 0, INCR*sizeof(void*)); 264 l->nalloc += INCR; 265 } 266 } 267 268 /* 269 * Remove the ith element from the list 270 */ 271 void 272 dellist(List *l, int i) 273 { 274 memmove(&l->u.ptr[i], &l->u.ptr[i+1], (l->nused-(i+1))*sizeof(void*)); 275 l->nused--; 276 } 277 278 /* 279 * Add a new element, whose position is i, to the list 280 */ 281 void 282 inslist(List *l, int i, void *v) 283 { 284 growlist(l); 285 memmove(&l->u.ptr[i+1], &l->u.ptr[i], (l->nused-i)*sizeof(void*)); 286 l->u.ptr[i] = v; 287 l->nused++; 288 } 289 290 void 291 listfree(List *l) 292 { 293 free(l->u.listptr); 294 free(l); 295 } 296 297 String* 298 allocstring(int n) 299 { 300 String *s; 301 302 s = emalloc(sizeof(String)); 303 s->n = n; 304 s->nalloc = n+10; 305 s->r = emalloc(s->nalloc*sizeof(Rune)); 306 s->r[n] = '\0'; 307 return s; 308 } 309 310 void 311 freestring(String *s) 312 { 313 free(s->r); 314 free(s); 315 } 316 317 Cmd* 318 newcmd(void){ 319 Cmd *p; 320 321 p = emalloc(sizeof(Cmd)); 322 inslist(&cmdlist, cmdlist.nused, p); 323 return p; 324 } 325 326 String* 327 newstring(int n) 328 { 329 String *p; 330 331 p = allocstring(n); 332 inslist(&stringlist, stringlist.nused, p); 333 return p; 334 } 335 336 Addr* 337 newaddr(void) 338 { 339 Addr *p; 340 341 p = emalloc(sizeof(Addr)); 342 inslist(&addrlist, addrlist.nused, p); 343 return p; 344 } 345 346 void 347 freecmd(void) 348 { 349 int i; 350 351 while(cmdlist.nused > 0) 352 free(cmdlist.u.ucharptr[--cmdlist.nused]); 353 while(addrlist.nused > 0) 354 free(addrlist.u.ucharptr[--addrlist.nused]); 355 while(stringlist.nused>0){ 356 i = --stringlist.nused; 357 freestring(stringlist.u.stringptr[i]); 358 } 359 } 360 361 void 362 okdelim(int c) 363 { 364 if(c=='\\' || ('a'<=c && c<='z') 365 || ('A'<=c && c<='Z') || ('0'<=c && c<='9')) 366 editerror("bad delimiter %c\n", c); 367 } 368 369 void 370 atnl(void) 371 { 372 int c; 373 374 cmdskipbl(); 375 c = getch(); 376 if(c != '\n') 377 editerror("newline expected (saw %C)", c); 378 } 379 380 void 381 Straddc(String *s, int c) 382 { 383 if(s->n+1 >= s->nalloc){ 384 s->nalloc += 10; 385 s->r = erealloc(s->r, s->nalloc*sizeof(Rune)); 386 } 387 s->r[s->n++] = c; 388 s->r[s->n] = '\0'; 389 } 390 391 void 392 getrhs(String *s, int delim, int cmd) 393 { 394 int c; 395 396 while((c = getch())>0 && c!=delim && c!='\n'){ 397 if(c == '\\'){ 398 if((c=getch()) <= 0) 399 error("bad right hand side"); 400 if(c == '\n'){ 401 ungetch(); 402 c='\\'; 403 }else if(c == 'n') 404 c='\n'; 405 else if(c!=delim && (cmd=='s' || c!='\\')) /* s does its own */ 406 Straddc(s, '\\'); 407 } 408 Straddc(s, c); 409 } 410 ungetch(); /* let client read whether delimiter, '\n' or whatever */ 411 } 412 413 String * 414 collecttoken(char *end) 415 { 416 String *s = newstring(0); 417 int c; 418 419 while((c=nextc())==' ' || c=='\t') 420 Straddc(s, getch()); /* blanks significant for getname() */ 421 while((c=getch())>0 && utfrune(end, c)==0) 422 Straddc(s, c); 423 if(c != '\n') 424 atnl(); 425 return s; 426 } 427 428 String * 429 collecttext(void) 430 { 431 String *s; 432 int begline, i, c, delim; 433 434 s = newstring(0); 435 if(cmdskipbl()=='\n'){ 436 getch(); 437 i = 0; 438 do{ 439 begline = i; 440 while((c = getch())>0 && c!='\n') 441 i++, Straddc(s, c); 442 i++, Straddc(s, '\n'); 443 if(c < 0) 444 goto Return; 445 }while(s->r[begline]!='.' || s->r[begline+1]!='\n'); 446 s->r[s->n-2] = '\0'; 447 s->n -= 2; 448 }else{ 449 okdelim(delim = getch()); 450 getrhs(s, delim, 'a'); 451 if(nextc()==delim) 452 getch(); 453 atnl(); 454 } 455 Return: 456 return s; 457 } 458 459 int 460 cmdlookup(int c) 461 { 462 int i; 463 464 for(i=0; cmdtab[i].cmdc; i++) 465 if(cmdtab[i].cmdc == c) 466 return i; 467 return -1; 468 } 469 470 Cmd* 471 parsecmd(int nest) 472 { 473 int i, c; 474 struct cmdtab *ct; 475 Cmd *cp, *ncp; 476 Cmd cmd; 477 478 cmd.next = cmd.u.cmd = 0; 479 cmd.re = 0; 480 cmd.flag = cmd.num = 0; 481 cmd.addr = compoundaddr(); 482 if(cmdskipbl() == -1) 483 return 0; 484 if((c=getch())==-1) 485 return 0; 486 cmd.cmdc = c; 487 if(cmd.cmdc=='c' && nextc()=='d'){ /* sleazy two-character case */ 488 getch(); /* the 'd' */ 489 cmd.cmdc='c'|0x100; 490 } 491 i = cmdlookup(cmd.cmdc); 492 if(i >= 0){ 493 if(cmd.cmdc == '\n') 494 goto Return; /* let nl_cmd work it all out */ 495 ct = &cmdtab[i]; 496 if(ct->defaddr==aNo && cmd.addr) 497 editerror("command takes no address"); 498 if(ct->count) 499 cmd.num = getnum(ct->count); 500 if(ct->regexp){ 501 /* x without pattern -> .*\n, indicated by cmd.re==0 */ 502 /* X without pattern is all files */ 503 if((ct->cmdc!='x' && ct->cmdc!='X') || 504 ((c = nextc())!=' ' && c!='\t' && c!='\n')){ 505 cmdskipbl(); 506 if((c = getch())=='\n' || c<0) 507 editerror("no address"); 508 okdelim(c); 509 cmd.re = getregexp(c); 510 if(ct->cmdc == 's'){ 511 cmd.u.text = newstring(0); 512 getrhs(cmd.u.text, c, 's'); 513 if(nextc() == c){ 514 getch(); 515 if(nextc() == 'g') 516 cmd.flag = getch(); 517 } 518 519 } 520 } 521 } 522 if(ct->addr && (cmd.u.mtaddr=simpleaddr())==0) 523 editerror("bad address"); 524 if(ct->defcmd){ 525 if(cmdskipbl() == '\n'){ 526 getch(); 527 cmd.u.cmd = newcmd(); 528 cmd.u.cmd->cmdc = ct->defcmd; 529 }else if((cmd.u.cmd = parsecmd(nest))==0) 530 error("defcmd"); 531 }else if(ct->text) 532 cmd.u.text = collecttext(); 533 else if(ct->token) 534 cmd.u.text = collecttoken(ct->token); 535 else 536 atnl(); 537 }else 538 switch(cmd.cmdc){ 539 case '{': 540 cp = 0; 541 do{ 542 if(cmdskipbl()=='\n') 543 getch(); 544 ncp = parsecmd(nest+1); 545 if(cp) 546 cp->next = ncp; 547 else 548 cmd.u.cmd = ncp; 549 }while(cp = ncp); 550 break; 551 case '}': 552 atnl(); 553 if(nest==0) 554 editerror("right brace with no left brace"); 555 return 0; 556 default: 557 editerror("unknown command %c", cmd.cmdc); 558 } 559 Return: 560 cp = newcmd(); 561 *cp = cmd; 562 return cp; 563 } 564 565 String* 566 getregexp(int delim) 567 { 568 String *buf, *r; 569 int i, c; 570 571 buf = allocstring(0); 572 for(i=0; ; i++){ 573 if((c = getch())=='\\'){ 574 if(nextc()==delim) 575 c = getch(); 576 else if(nextc()=='\\'){ 577 Straddc(buf, c); 578 c = getch(); 579 } 580 }else if(c==delim || c=='\n') 581 break; 582 if(i >= RBUFSIZE) 583 editerror("regular expression too long"); 584 Straddc(buf, c); 585 } 586 if(c!=delim && c) 587 ungetch(); 588 if(buf->n > 0){ 589 patset = TRUE; 590 freestring(lastpat); 591 lastpat = buf; 592 }else 593 freestring(buf); 594 if(lastpat->n == 0) 595 editerror("no regular expression defined"); 596 r = newstring(lastpat->n); 597 runemove(r->r, lastpat->r, lastpat->n); /* newstring put \0 at end */ 598 return r; 599 } 600 601 Addr * 602 simpleaddr(void) 603 { 604 Addr addr; 605 Addr *ap, *nap; 606 607 addr.num = 0; 608 addr.next = 0; 609 addr.u.left = 0; 610 switch(cmdskipbl()){ 611 case '#': 612 addr.type = getch(); 613 addr.num = getnum(1); 614 break; 615 case '0': case '1': case '2': case '3': case '4': 616 case '5': case '6': case '7': case '8': case '9': 617 addr.num = getnum(1); 618 addr.type='l'; 619 break; 620 case '/': case '?': case '"': 621 addr.u.re = getregexp(addr.type = getch()); 622 break; 623 case '.': 624 case '$': 625 case '+': 626 case '-': 627 case '\'': 628 addr.type = getch(); 629 break; 630 default: 631 return 0; 632 } 633 if(addr.next = simpleaddr()) 634 switch(addr.next->type){ 635 case '.': 636 case '$': 637 case '\'': 638 if(addr.type=='"') 639 break; 640 /* fall through */ 641 case '"': 642 editerror("bad address syntax"); 643 break; 644 case 'l': 645 case '#': 646 if(addr.type=='"') 647 break; 648 /* fall through */ 649 case '/': 650 case '?': 651 if(addr.type!='+' && addr.type!='-'){ 652 /* insert the missing '+' */ 653 nap = newaddr(); 654 nap->type='+'; 655 nap->next = addr.next; 656 addr.next = nap; 657 } 658 break; 659 case '+': 660 case '-': 661 break; 662 default: 663 error("simpleaddr"); 664 } 665 ap = newaddr(); 666 *ap = addr; 667 return ap; 668 } 669 670 Addr * 671 compoundaddr(void) 672 { 673 Addr addr; 674 Addr *ap, *next; 675 676 addr.u.left = simpleaddr(); 677 if((addr.type = cmdskipbl())!=',' && addr.type!=';') 678 return addr.u.left; 679 getch(); 680 next = addr.next = compoundaddr(); 681 if(next && (next->type==',' || next->type==';') && next->u.left==0) 682 editerror("bad address syntax"); 683 ap = newaddr(); 684 *ap = addr; 685 return ap; 686 }