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