edit.b 12 KB

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