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