ecmd.b 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350
  1. implement Editcmd;
  2. include "common.m";
  3. sys: Sys;
  4. utils: Utils;
  5. edit: Edit;
  6. editlog: Editlog;
  7. windowm: Windowm;
  8. look: Look;
  9. columnm: Columnm;
  10. bufferm: Bufferm;
  11. exec: Exec;
  12. dat: Dat;
  13. textm: Textm;
  14. regx: Regx;
  15. filem: Filem;
  16. rowm: Rowm;
  17. Dir: import Sys;
  18. Allwin, Filecheck, Tofile, Looper, Astring: import Dat;
  19. aNo, aDot, aAll: import Edit;
  20. C_nl, C_a, C_b, C_c, C_d, C_B, C_D, C_e, C_f, C_g, C_i, C_k, C_m, C_n, C_p, C_s, C_u, C_w, C_x, C_X, C_pipe, C_eq: import Edit;
  21. TRUE, FALSE: import Dat;
  22. Inactive, Inserting, Collecting: import Dat;
  23. BUFSIZE, Runestr: import Dat;
  24. Addr, Address, String, Cmd: import Edit;
  25. Window: import windowm;
  26. File: import filem;
  27. NRange, Range, Rangeset: import Dat;
  28. Text: import textm;
  29. Column: import columnm;
  30. Buffer: import bufferm;
  31. sprint: import sys;
  32. elogterm, elogclose, eloginsert, elogdelete, elogreplace, elogapply: import editlog;
  33. cmdtab, allocstring, freestring, Straddc, curtext, editing, newaddr, cmdlookup, editerror: import edit;
  34. error, stralloc, strfree, warning, skipbl, findbl: import utils;
  35. lookfile, cleanname, dirname: import look;
  36. undo, run: import exec;
  37. Ref, Lock, row, cedit: import dat;
  38. rxcompile, rxexecute, rxbexecute: import regx;
  39. allwindows: import rowm;
  40. init(mods : ref Dat->Mods)
  41. {
  42. sys = mods.sys;
  43. utils = mods.utils;
  44. edit = mods.edit;
  45. editlog = mods.editlog;
  46. windowm = mods.windowm;
  47. look = mods.look;
  48. columnm = mods.columnm;
  49. bufferm = mods.bufferm;
  50. exec = mods.exec;
  51. dat = mods.dat;
  52. textm = mods.textm;
  53. regx = mods.regx;
  54. filem = mods.filem;
  55. rowm = mods.rowm;
  56. none.r.q0 = none.r.q1 = 0;
  57. none.f = nil;
  58. }
  59. cmdtabexec(i: int, t: ref Text, cp: ref Cmd): int
  60. {
  61. case (cmdtab[i].fnc){
  62. C_nl => i = nl_cmd(t, cp);
  63. C_a => i = a_cmd(t, cp);
  64. C_b => i = b_cmd(t, cp);
  65. C_c => i = c_cmd(t, cp);
  66. C_d => i = d_cmd(t, cp);
  67. C_e => i = e_cmd(t, cp);
  68. C_f => i = f_cmd(t, cp);
  69. C_g => i = g_cmd(t, cp);
  70. C_i => i = i_cmd(t, cp);
  71. C_m => i = m_cmd(t, cp);
  72. C_p => i = p_cmd(t, cp);
  73. C_s => i = s_cmd(t, cp);
  74. C_u => i = u_cmd(t, cp);
  75. C_w => i = w_cmd(t, cp);
  76. C_x => i = x_cmd(t, cp);
  77. C_eq => i = eq_cmd(t, cp);
  78. C_B => i = B_cmd(t, cp);
  79. C_D => i = D_cmd(t, cp);
  80. C_X => i = X_cmd(t, cp);
  81. C_pipe => i = pipe_cmd(t, cp);
  82. * => error("bad case in cmdtabexec");
  83. }
  84. return i;
  85. }
  86. Glooping: int;
  87. nest: int;
  88. Enoname := "no file name given";
  89. addr: Address;
  90. menu: ref File;
  91. sel: Rangeset;
  92. collection: string;
  93. ncollection: int;
  94. clearcollection()
  95. {
  96. collection = nil;
  97. ncollection = 0;
  98. }
  99. resetxec()
  100. {
  101. Glooping = nest = 0;
  102. clearcollection();
  103. }
  104. mkaddr(f: ref File): Address
  105. {
  106. a: Address;
  107. a.r.q0 = f.curtext.q0;
  108. a.r.q1 = f.curtext.q1;
  109. a.f = f;
  110. return a;
  111. }
  112. none: Address;
  113. cmdexec(t: ref Text, cp: ref Cmd): int
  114. {
  115. i: int;
  116. ap: ref Addr;
  117. f: ref File;
  118. w: ref Window;
  119. dot: Address;
  120. if(t == nil)
  121. w = nil;
  122. else
  123. w = t.w;
  124. if(w==nil && (cp.addr==nil || cp.addr.typex!='"') &&
  125. utils->strchr("bBnqUXY!", cp.cmdc) < 0&&
  126. !(cp.cmdc=='D' && cp.text!=nil))
  127. editerror("no current window");
  128. i = cmdlookup(cp.cmdc); # will be -1 for '{'
  129. f = nil;
  130. if(t!=nil && t.w!=nil){
  131. t = t.w.body;
  132. f = t.file;
  133. f.curtext = t;
  134. }
  135. if(i>=0 && cmdtab[i].defaddr != aNo){
  136. if((ap=cp.addr)==nil && cp.cmdc!='\n'){
  137. cp.addr = ap = newaddr();
  138. ap.typex = '.';
  139. if(cmdtab[i].defaddr == aAll)
  140. ap.typex = '*';
  141. }else if(ap!=nil && ap.typex=='"' && ap.next==nil && cp.cmdc!='\n'){
  142. ap.next = newaddr();
  143. ap.next.typex = '.';
  144. if(cmdtab[i].defaddr == aAll)
  145. ap.next.typex = '*';
  146. }
  147. if(cp.addr!=nil){ # may be false for '\n' (only)
  148. if(f!=nil){
  149. dot = mkaddr(f);
  150. addr = cmdaddress(ap, dot, 0);
  151. }else # a "
  152. addr = cmdaddress(ap, none, 0);
  153. f = addr.f;
  154. t = f.curtext;
  155. }
  156. }
  157. case(cp.cmdc){
  158. '{' =>
  159. dot = mkaddr(f);
  160. if(cp.addr != nil)
  161. dot = cmdaddress(cp.addr, dot, 0);
  162. for(cp = cp.cmd; cp!=nil; cp = cp.next){
  163. t.q0 = dot.r.q0;
  164. t.q1 = dot.r.q1;
  165. cmdexec(t, cp);
  166. }
  167. break;
  168. * =>
  169. if(i < 0)
  170. editerror(sprint("unknown command %c in cmdexec", cp.cmdc));
  171. i = cmdtabexec(i, t, cp);
  172. return i;
  173. }
  174. return 1;
  175. }
  176. edittext(f: ref File, q: int, r: string, nr: int): string
  177. {
  178. case(editing){
  179. Inactive =>
  180. return "permission denied";
  181. Inserting =>
  182. eloginsert(f, q, r, nr);
  183. return nil;
  184. Collecting =>
  185. collection += r[0: nr];
  186. ncollection += nr;
  187. return nil;
  188. * =>
  189. return "unknown state in edittext";
  190. }
  191. }
  192. # string is known to be NUL-terminated
  193. filelist(t: ref Text, r: string, nr: int): string
  194. {
  195. if(nr == 0)
  196. return nil;
  197. (r, nr) = skipbl(r, nr);
  198. if(r[0] != '<')
  199. return r;
  200. # use < command to collect text
  201. clearcollection();
  202. runpipe(t, '<', r[1:], nr-1, Collecting);
  203. return collection;
  204. }
  205. a_cmd(t: ref Text, cp: ref Cmd): int
  206. {
  207. return append(t.file, cp, addr.r.q1);
  208. }
  209. b_cmd(nil: ref Text, cp: ref Cmd): int
  210. {
  211. f: ref File;
  212. f = tofile(cp.text);
  213. if(nest == 0)
  214. pfilename(f);
  215. curtext = f.curtext;
  216. return TRUE;
  217. }
  218. B_cmd(t: ref Text, cp: ref Cmd): int
  219. {
  220. listx, r, s: string;
  221. nr: int;
  222. listx = filelist(t, cp.text.r, cp.text.n);
  223. if(listx == nil)
  224. editerror(Enoname);
  225. r = listx;
  226. nr = len r;
  227. (r, nr) = skipbl(r, nr);
  228. if(nr == 0)
  229. look->new(t, t, nil, 0, 0, r, 0);
  230. else while(nr > 0){
  231. (s, nr) = findbl(r, nr);
  232. look->new(t, t, nil, 0, 0, r, len r);
  233. if(nr > 0)
  234. (r, nr) = skipbl(s[1:], nr-1);
  235. }
  236. clearcollection();
  237. return TRUE;
  238. }
  239. c_cmd(t: ref Text, cp: ref Cmd): int
  240. {
  241. elogreplace(t.file, addr.r.q0, addr.r.q1, cp.text.r, cp.text.n);
  242. return TRUE;
  243. }
  244. d_cmd(t: ref Text, nil: ref Cmd): int
  245. {
  246. if(addr.r.q1 > addr.r.q0)
  247. elogdelete(t.file, addr.r.q0, addr.r.q1);
  248. return TRUE;
  249. }
  250. D1(t: ref Text)
  251. {
  252. if(t.w.body.file.ntext>1 || t.w.clean(FALSE, FALSE))
  253. t.col.close(t.w, TRUE);
  254. }
  255. D_cmd(t: ref Text, cp: ref Cmd): int
  256. {
  257. listx, r, s, n: string;
  258. nr, nn: int;
  259. w: ref Window;
  260. dir, rs: Runestr;
  261. buf: string;
  262. listx = filelist(t, cp.text.r, cp.text.n);
  263. if(listx == nil){
  264. D1(t);
  265. return TRUE;
  266. }
  267. dir = dirname(t, nil, 0);
  268. r = listx;
  269. nr = len r;
  270. (r, nr) = skipbl(r, nr);
  271. do{
  272. (s, nr) = findbl(r, nr);
  273. # first time through, could be empty string, meaning delete file empty name
  274. nn = len r;
  275. if(r[0]=='/' || nn==0 || dir.nr==0){
  276. rs.r = r;
  277. rs.nr = nn;
  278. }else{
  279. n = dir.r + "/" + r;
  280. rs = cleanname(n, dir.nr+1+nn);
  281. }
  282. w = lookfile(rs.r, rs.nr);
  283. if(w == nil){
  284. buf = sprint("no such file %s", rs.r);
  285. rs.r = nil;
  286. editerror(buf);
  287. }
  288. rs.r = nil;
  289. D1(w.body);
  290. if(nr > 0)
  291. (r, nr) = skipbl(s[1:], nr-1);
  292. }while(nr > 0);
  293. clearcollection();
  294. dir.r = nil;
  295. return TRUE;
  296. }
  297. readloader(f: ref File, q0: int, r: string, nr: int): int
  298. {
  299. if(nr > 0)
  300. eloginsert(f, q0, r, nr);
  301. return 0;
  302. }
  303. e_cmd(t: ref Text , cp: ref Cmd): int
  304. {
  305. name: string;
  306. f: ref File;
  307. i, q0, q1, nulls, samename, allreplaced, ok: int;
  308. fd: ref Sys->FD;
  309. s, tmp: string;
  310. d: Dir;
  311. f = t.file;
  312. q0 = addr.r.q0;
  313. q1 = addr.r.q1;
  314. if(cp.cmdc == 'e'){
  315. if(t.w.clean(TRUE, FALSE)==FALSE)
  316. editerror(""); # winclean generated message already
  317. q0 = 0;
  318. q1 = f.buf.nc;
  319. }
  320. allreplaced = (q0==0 && q1==f.buf.nc);
  321. name = cmdname(f, cp.text, cp.cmdc=='e');
  322. if(name == nil)
  323. editerror(Enoname);
  324. i = len name;
  325. samename = name == t.file.name;
  326. s = name;
  327. name = nil;
  328. fd = sys->open(s, Sys->OREAD);
  329. if(fd == nil){
  330. tmp = sprint("can't open %s: %r", s);
  331. s = nil;
  332. editerror(tmp);
  333. }
  334. (ok, d) = sys->fstat(fd);
  335. if(ok >=0 && (d.mode&Sys->DMDIR)){
  336. fd = nil;
  337. tmp = sprint("%s is a directory", s);
  338. s = nil;
  339. editerror(tmp);
  340. }
  341. elogdelete(f, q0, q1);
  342. nulls = 0;
  343. bufferm->loadfile(fd, q1, Dat->READL, nil, f);
  344. s = nil;
  345. fd = nil;
  346. if(nulls)
  347. warning(nil, sprint("%s: NUL bytes elided\n", s));
  348. else if(allreplaced && samename)
  349. f.editclean = TRUE;
  350. return TRUE;
  351. }
  352. f_cmd(t: ref Text, cp: ref Cmd): int
  353. {
  354. name: string;
  355. name = cmdname(t.file, cp.text, TRUE);
  356. name = nil;
  357. pfilename(t.file);
  358. return TRUE;
  359. }
  360. g_cmd(t: ref Text, cp: ref Cmd): int
  361. {
  362. ok: int;
  363. if(t.file != addr.f){
  364. warning(nil, "internal error: g_cmd f!=addr.f\n");
  365. return FALSE;
  366. }
  367. if(rxcompile(cp.re.r) == FALSE)
  368. editerror("bad regexp in g command");
  369. (ok, sel) = rxexecute(t, nil, addr.r.q0, addr.r.q1);
  370. if(ok ^ cp.cmdc=='v'){
  371. t.q0 = addr.r.q0;
  372. t.q1 = addr.r.q1;
  373. return cmdexec(t, cp.cmd);
  374. }
  375. return TRUE;
  376. }
  377. i_cmd(t: ref Text, cp: ref Cmd): int
  378. {
  379. return append(t.file, cp, addr.r.q0);
  380. }
  381. # int
  382. # k_cmd(File *f, Cmd *cp)
  383. # {
  384. # USED(cp);
  385. # f->mark = addr.r;
  386. # return TRUE;
  387. # }
  388. copy(f: ref File, addr2: Address)
  389. {
  390. p: int;
  391. ni: int;
  392. buf: ref Astring;
  393. buf = stralloc(BUFSIZE);
  394. for(p=addr.r.q0; p<addr.r.q1; p+=ni){
  395. ni = addr.r.q1-p;
  396. if(ni > BUFSIZE)
  397. ni = BUFSIZE;
  398. f.buf.read(p, buf, 0, ni);
  399. eloginsert(addr2.f, addr2.r.q1, buf.s, ni);
  400. }
  401. strfree(buf);
  402. }
  403. move(f: ref File, addr2: Address)
  404. {
  405. if(addr.f!=addr2.f || addr.r.q1<=addr2.r.q0){
  406. elogdelete(f, addr.r.q0, addr.r.q1);
  407. copy(f, addr2);
  408. }else if(addr.r.q0 >= addr2.r.q1){
  409. copy(f, addr2);
  410. elogdelete(f, addr.r.q0, addr.r.q1);
  411. }else if(addr.r.q0==addr2.r.q0 && addr.r.q1==addr2.r.q1){
  412. ; # move to self; no-op
  413. }else
  414. editerror("move overlaps itself");
  415. }
  416. m_cmd(t: ref Text, cp: ref Cmd): int
  417. {
  418. dot, addr2: Address;
  419. dot = mkaddr(t.file);
  420. addr2 = cmdaddress(cp.mtaddr, dot, 0);
  421. if(cp.cmdc == 'm')
  422. move(t.file, addr2);
  423. else
  424. copy(t.file, addr2);
  425. return TRUE;
  426. }
  427. # int
  428. # n_cmd(File *f, Cmd *cp)
  429. # {
  430. # int i;
  431. # USED(f);
  432. # USED(cp);
  433. # for(i = 0; i<file.nused; i++){
  434. # if(file.filepptr[i] == cmd)
  435. # continue;
  436. # f = file.filepptr[i];
  437. # Strduplstr(&genstr, &f->name);
  438. # filename(f);
  439. # }
  440. # return TRUE;
  441. #}
  442. p_cmd(t: ref Text, nil: ref Cmd): int
  443. {
  444. return pdisplay(t.file);
  445. }
  446. s_cmd(t: ref Text, cp: ref Cmd): int
  447. {
  448. i, j, k, c, m, n, nrp, didsub, ok: int;
  449. p1, op, delta: int;
  450. buf: ref String;
  451. rp: array of Rangeset;
  452. err: string;
  453. rbuf: ref Astring;
  454. n = cp.num;
  455. op= -1;
  456. if(rxcompile(cp.re.r) == FALSE)
  457. editerror("bad regexp in s command");
  458. nrp = 0;
  459. rp = nil;
  460. delta = 0;
  461. didsub = FALSE;
  462. for(p1 = addr.r.q0; p1<=addr.r.q1; ){
  463. (ok, sel) = rxexecute(t, nil, p1, addr.r.q1);
  464. if(!ok)
  465. break;
  466. if(sel[0].q0 == sel[0].q1){ # empty match?
  467. if(sel[0].q0 == op){
  468. p1++;
  469. continue;
  470. }
  471. p1 = sel[0].q1+1;
  472. }else
  473. p1 = sel[0].q1;
  474. op = sel[0].q1;
  475. if(--n>0)
  476. continue;
  477. nrp++;
  478. orp := rp;
  479. rp = array[nrp] of Rangeset;
  480. rp[0: ] = orp[0:nrp-1];
  481. rp[nrp-1] = copysel(sel);
  482. orp = nil;
  483. }
  484. rbuf = stralloc(BUFSIZE);
  485. buf = allocstring(0);
  486. for(m=0; m<nrp; m++){
  487. buf.n = 0;
  488. buf.r = nil;
  489. sel = rp[m];
  490. for(i = 0; i<cp.text.n; i++)
  491. if((c = cp.text.r[i])=='\\' && i<cp.text.n-1){
  492. c = cp.text.r[++i];
  493. if('1'<=c && c<='9') {
  494. j = c-'0';
  495. if(sel[j].q1-sel[j].q0>BUFSIZE){
  496. err = "replacement string too long";
  497. rp = nil;
  498. freestring(buf);
  499. strfree(rbuf);
  500. editerror(err);
  501. return FALSE;
  502. }
  503. t.file.buf.read(sel[j].q0, rbuf, 0, sel[j].q1-sel[j].q0);
  504. for(k=0; k<sel[j].q1-sel[j].q0; k++)
  505. Straddc(buf, rbuf.s[k]);
  506. }else
  507. Straddc(buf, c);
  508. }else if(c!='&')
  509. Straddc(buf, c);
  510. else{
  511. if(sel[0].q1-sel[0].q0>BUFSIZE){
  512. err = "right hand side too long in substitution";
  513. rp = nil;
  514. freestring(buf);
  515. strfree(rbuf);
  516. editerror(err);
  517. return FALSE;
  518. }
  519. t.file.buf.read(sel[0].q0, rbuf, 0, sel[0].q1-sel[0].q0);
  520. for(k=0; k<sel[0].q1-sel[0].q0; k++)
  521. Straddc(buf, rbuf.s[k]);
  522. }
  523. elogreplace(t.file, sel[0].q0, sel[0].q1, buf.r, buf.n);
  524. delta -= sel[0].q1-sel[0].q0;
  525. delta += buf.n;
  526. didsub = 1;
  527. if(!cp.flag)
  528. break;
  529. }
  530. rp = nil;
  531. freestring(buf);
  532. strfree(rbuf);
  533. if(!didsub && nest==0)
  534. editerror("no substitution");
  535. t.q0 = addr.r.q0;
  536. t.q1 = addr.r.q1+delta;
  537. return TRUE;
  538. }
  539. u_cmd(t: ref Text, cp: ref Cmd): int
  540. {
  541. n, oseq, flag: int;
  542. n = cp.num;
  543. flag = TRUE;
  544. if(n < 0){
  545. n = -n;
  546. flag = FALSE;
  547. }
  548. oseq = -1;
  549. while(n-->0 && t.file.seq!=0 && t.file.seq!=oseq){
  550. oseq = t.file.seq;
  551. warning(nil, sprint("seq %d\n", t.file.seq));
  552. undo(t, flag);
  553. }
  554. return TRUE;
  555. }
  556. w_cmd(t: ref Text, cp: ref Cmd): int
  557. {
  558. r: string;
  559. f: ref File;
  560. f = t.file;
  561. if(f.seq == dat->seq)
  562. editerror("can't write file with pending modifications");
  563. r = cmdname(f, cp.text, FALSE);
  564. if(r == nil)
  565. editerror("no name specified for 'w' command");
  566. exec->putfile(f, addr.r.q0, addr.r.q1, r);
  567. # r is freed by putfile
  568. return TRUE;
  569. }
  570. x_cmd(t: ref Text, cp: ref Cmd): int
  571. {
  572. if(cp.re!=nil)
  573. looper(t.file, cp, cp.cmdc=='x');
  574. else
  575. linelooper(t.file, cp);
  576. return TRUE;
  577. }
  578. X_cmd(nil: ref Text, cp: ref Cmd): int
  579. {
  580. filelooper(cp, cp.cmdc=='X');
  581. return TRUE;
  582. }
  583. runpipe(t: ref Text, cmd: int, cr: string, ncr: int, state: int)
  584. {
  585. r, s: string;
  586. n: int;
  587. dir: Runestr;
  588. w: ref Window;
  589. (r, n) = skipbl(cr, ncr);
  590. if(n == 0)
  591. editerror("no command specified for >");
  592. w = nil;
  593. if(state == Inserting){
  594. w = t.w;
  595. t.q0 = addr.r.q0;
  596. t.q1 = addr.r.q1;
  597. if(cmd == '<' || cmd=='|')
  598. elogdelete(t.file, t.q0, t.q1);
  599. }
  600. tmps := "z";
  601. tmps[0] = cmd;
  602. s = tmps + r;
  603. n++;
  604. dir.r = nil;
  605. dir.nr = 0;
  606. if(t != nil)
  607. dir = dirname(t, nil, 0);
  608. if(dir.nr==1 && dir.r[0]=='.'){ # sigh
  609. dir.r = nil;
  610. dir.nr = 0;
  611. }
  612. editing = state;
  613. if(t!=nil && t.w!=nil)
  614. t.w.refx.inc(); # run will decref
  615. spawn run(w, s, dir.r, dir.nr, TRUE, nil, nil, TRUE);
  616. s = nil;
  617. if(t!=nil && t.w!=nil)
  618. t.w.unlock();
  619. row.qlock.unlock();
  620. <- cedit;
  621. row.qlock.lock();
  622. editing = Inactive;
  623. if(t!=nil && t.w!=nil)
  624. t.w.lock('M');
  625. }
  626. pipe_cmd(t: ref Text, cp: ref Cmd): int
  627. {
  628. runpipe(t, cp.cmdc, cp.text.r, cp.text.n, Inserting);
  629. return TRUE;
  630. }
  631. nlcount(t: ref Text, q0: int, q1: int): int
  632. {
  633. nl: int;
  634. buf: ref Astring;
  635. i, nbuf: int;
  636. buf = stralloc(BUFSIZE);
  637. nbuf = 0;
  638. i = nl = 0;
  639. while(q0 < q1){
  640. if(i == nbuf){
  641. nbuf = q1-q0;
  642. if(nbuf > BUFSIZE)
  643. nbuf = BUFSIZE;
  644. t.file.buf.read(q0, buf, 0, nbuf);
  645. i = 0;
  646. }
  647. if(buf.s[i++] == '\n')
  648. nl++;
  649. q0++;
  650. }
  651. strfree(buf);
  652. return nl;
  653. }
  654. printposn(t: ref Text, charsonly: int)
  655. {
  656. l1, l2: int;
  657. if(t != nil && t.file != nil && t.file.name != nil)
  658. warning(nil, t.file.name + ":");
  659. if(!charsonly){
  660. l1 = 1+nlcount(t, 0, addr.r.q0);
  661. l2 = l1+nlcount(t, addr.r.q0, addr.r.q1);
  662. # check if addr ends with '\n'
  663. if(addr.r.q1>0 && addr.r.q1>addr.r.q0 && t.readc(addr.r.q1-1)=='\n')
  664. --l2;
  665. warning(nil, sprint("%ud", l1));
  666. if(l2 != l1)
  667. warning(nil, sprint(",%ud", l2));
  668. warning(nil, "\n");
  669. # warning(nil, "; ");
  670. return;
  671. }
  672. warning(nil, sprint("#%d", addr.r.q0));
  673. if(addr.r.q1 != addr.r.q0)
  674. warning(nil, sprint(",#%d", addr.r.q1));
  675. warning(nil, "\n");
  676. }
  677. eq_cmd(t: ref Text, cp: ref Cmd): int
  678. {
  679. charsonly: int;
  680. case(cp.text.n){
  681. 0 =>
  682. charsonly = FALSE;
  683. break;
  684. 1 =>
  685. if(cp.text.r[0] == '#'){
  686. charsonly = TRUE;
  687. break;
  688. }
  689. * =>
  690. charsonly = TRUE;
  691. editerror("newline expected");
  692. }
  693. printposn(t, charsonly);
  694. return TRUE;
  695. }
  696. nl_cmd(t: ref Text, cp: ref Cmd): int
  697. {
  698. a: Address;
  699. f: ref File;
  700. f = t.file;
  701. if(cp.addr == nil){
  702. # First put it on newline boundaries
  703. a = mkaddr(f);
  704. addr = lineaddr(0, a, -1);
  705. a = lineaddr(0, a, 1);
  706. addr.r.q1 = a.r.q1;
  707. if(addr.r.q0==t.q0 && addr.r.q1==t.q1){
  708. a = mkaddr(f);
  709. addr = lineaddr(1, a, 1);
  710. }
  711. }
  712. t.show(addr.r.q0, addr.r.q1, TRUE);
  713. return TRUE;
  714. }
  715. append(f: ref File, cp: ref Cmd, p: int): int
  716. {
  717. if(cp.text.n > 0)
  718. eloginsert(f, p, cp.text.r, cp.text.n);
  719. return TRUE;
  720. }
  721. pdisplay(f: ref File): int
  722. {
  723. p1, p2: int;
  724. np: int;
  725. buf: ref Astring;
  726. p1 = addr.r.q0;
  727. p2 = addr.r.q1;
  728. if(p2 > f.buf.nc)
  729. p2 = f.buf.nc;
  730. buf = stralloc(BUFSIZE);
  731. while(p1 < p2){
  732. np = p2-p1;
  733. if(np>BUFSIZE-1)
  734. np = BUFSIZE-1;
  735. f.buf.read(p1, buf, 0, np);
  736. warning(nil, sprint("%s", buf.s[0:np]));
  737. p1 += np;
  738. }
  739. strfree(buf);
  740. f.curtext.q0 = addr.r.q0;
  741. f.curtext.q1 = addr.r.q1;
  742. return TRUE;
  743. }
  744. pfilename(f: ref File)
  745. {
  746. dirty: int;
  747. w: ref Window;
  748. w = f.curtext.w;
  749. # same check for dirty as in settag, but we know ncache==0
  750. dirty = !w.isdir && !w.isscratch && f.mod;
  751. warning(nil, sprint("%c%c%c %s\n", " '"[dirty],
  752. '+', " ."[curtext!=nil && curtext.file==f], f.name));
  753. }
  754. loopcmd(f: ref File, cp: ref Cmd, rp: array of Range, nrp: int)
  755. {
  756. i: int;
  757. for(i=0; i<nrp; i++){
  758. f.curtext.q0 = rp[i].q0;
  759. f.curtext.q1 = rp[i].q1;
  760. cmdexec(f.curtext, cp);
  761. }
  762. }
  763. looper(f: ref File, cp: ref Cmd, xy: int)
  764. {
  765. p, op, nrp, ok: int;
  766. r, tr: Range;
  767. rp: array of Range;
  768. r = addr.r;
  769. if(xy)
  770. op = -1;
  771. else
  772. op = r.q0;
  773. nest++;
  774. if(rxcompile(cp.re.r) == FALSE)
  775. editerror(sprint("bad regexp in %c command", cp.cmdc));
  776. nrp = 0;
  777. rp = nil;
  778. for(p = r.q0; p<=r.q1; ){
  779. (ok, sel) = rxexecute(f.curtext, nil, p, r.q1);
  780. if(!ok){ # no match, but y should still run
  781. if(xy || op>r.q1)
  782. break;
  783. tr.q0 = op;
  784. tr.q1 = r.q1;
  785. p = r.q1+1; # exit next loop
  786. }else{
  787. if(sel[0].q0==sel[0].q1){ # empty match?
  788. if(sel[0].q0==op){
  789. p++;
  790. continue;
  791. }
  792. p = sel[0].q1+1;
  793. }else
  794. p = sel[0].q1;
  795. if(xy)
  796. tr = sel[0];
  797. else{
  798. tr.q0 = op;
  799. tr.q1 = sel[0].q0;
  800. }
  801. }
  802. op = sel[0].q1;
  803. nrp++;
  804. orp := rp;
  805. rp = array[nrp] of Range;
  806. rp[0: ] = orp[0: nrp-1];
  807. rp[nrp-1] = tr;
  808. orp = nil;
  809. }
  810. loopcmd(f, cp.cmd, rp, nrp);
  811. rp = nil;
  812. --nest;
  813. }
  814. linelooper(f: ref File, cp: ref Cmd)
  815. {
  816. nrp, p: int;
  817. r, linesel: Range;
  818. a, a3: Address;
  819. rp: array of Range;
  820. nest++;
  821. nrp = 0;
  822. rp = nil;
  823. r = addr.r;
  824. a3.f = f;
  825. a3.r.q0 = a3.r.q1 = r.q0;
  826. a = lineaddr(0, a3, 1);
  827. linesel = a.r;
  828. for(p = r.q0; p<r.q1; p = a3.r.q1){
  829. a3.r.q0 = a3.r.q1;
  830. if(p!=r.q0 || linesel.q1==p){
  831. a = lineaddr(1, a3, 1);
  832. linesel = a.r;
  833. }
  834. if(linesel.q0 >= r.q1)
  835. break;
  836. if(linesel.q1 >= r.q1)
  837. linesel.q1 = r.q1;
  838. if(linesel.q1 > linesel.q0)
  839. if(linesel.q0>=a3.r.q1 && linesel.q1>a3.r.q1){
  840. a3.r = linesel;
  841. nrp++;
  842. orp := rp;
  843. rp = array[nrp] of Range;
  844. rp[0: ] = orp[0: nrp-1];
  845. rp[nrp-1] = linesel;
  846. orp = nil;
  847. continue;
  848. }
  849. break;
  850. }
  851. loopcmd(f, cp.cmd, rp, nrp);
  852. rp = nil;
  853. --nest;
  854. }
  855. loopstruct: ref Looper;
  856. alllooper(w: ref Window, lp: ref Looper)
  857. {
  858. t: ref Text;
  859. cp: ref Cmd;
  860. cp = lp.cp;
  861. # if(w.isscratch || w.isdir)
  862. # return;
  863. t = w.body;
  864. # only use this window if it's the current window for the file
  865. if(t.file.curtext != t)
  866. return;
  867. # if(w.nopen[QWevent] > 0)
  868. # return;
  869. # no auto-execute on files without names
  870. if(cp.re==nil && t.file.name==nil)
  871. return;
  872. if(cp.re==nil || filematch(t.file, cp.re)==lp.XY){
  873. olpw := lp.w;
  874. lp.w = array[lp.nw+1] of ref Window;
  875. lp.w[0: ] = olpw[0: lp.nw];
  876. lp.w[lp.nw++] = w;
  877. olpw = nil;
  878. }
  879. }
  880. filelooper(cp: ref Cmd, XY: int)
  881. {
  882. i: int;
  883. if(Glooping++)
  884. editerror(sprint("can't nest %c command", "YX"[XY]));
  885. nest++;
  886. if(loopstruct == nil)
  887. loopstruct = ref Looper;
  888. loopstruct.cp = cp;
  889. loopstruct.XY = XY;
  890. if(loopstruct.w != nil) # error'ed out last time
  891. loopstruct.w = nil;
  892. loopstruct.w = nil;
  893. loopstruct.nw = 0;
  894. aw := ref Allwin.LP(loopstruct);
  895. allwindows(Edit->ALLLOOPER, aw);
  896. aw = nil;
  897. for(i=0; i<loopstruct.nw; i++)
  898. cmdexec(loopstruct.w[i].body, cp.cmd);
  899. loopstruct.w = nil;
  900. --Glooping;
  901. --nest;
  902. }
  903. nextmatch(f: ref File, r: ref String, p: int, sign: int)
  904. {
  905. ok: int;
  906. if(rxcompile(r.r) == FALSE)
  907. editerror("bad regexp in command address");
  908. if(sign >= 0){
  909. (ok, sel) = rxexecute(f.curtext, nil, p, 16r7FFFFFFF);
  910. if(!ok)
  911. editerror("no match for regexp");
  912. if(sel[0].q0==sel[0].q1 && sel[0].q0==p){
  913. if(++p>f.buf.nc)
  914. p = 0;
  915. (ok, sel) = rxexecute(f.curtext, nil, p, 16r7FFFFFFF);
  916. if(!ok)
  917. editerror("address");
  918. }
  919. }else{
  920. (ok, sel) = rxbexecute(f.curtext, p);
  921. if(!ok)
  922. editerror("no match for regexp");
  923. if(sel[0].q0==sel[0].q1 && sel[0].q1==p){
  924. if(--p<0)
  925. p = f.buf.nc;
  926. (ok, sel) = rxbexecute(f.curtext, p);
  927. if(!ok)
  928. editerror("address");
  929. }
  930. }
  931. }
  932. cmdaddress(ap: ref Addr, a: Address, sign: int): Address
  933. {
  934. f := a.f;
  935. a1, a2: Address;
  936. do{
  937. case(ap.typex){
  938. 'l' or
  939. '#' =>
  940. if(ap.typex == '#')
  941. a = charaddr(ap.num, a, sign);
  942. else
  943. a = lineaddr(ap.num, a, sign);
  944. break;
  945. '.' =>
  946. a = mkaddr(f);
  947. break;
  948. '$' =>
  949. a.r.q0 = a.r.q1 = f.buf.nc;
  950. break;
  951. '\'' =>
  952. editerror("can't handle '");
  953. # a.r = f.mark;
  954. break;
  955. '?' =>
  956. sign = -sign;
  957. if(sign == 0)
  958. sign = -1;
  959. if(sign >= 0)
  960. v := a.r.q1;
  961. else
  962. v = a.r.q0;
  963. nextmatch(f, ap.re, v, sign);
  964. a.r = sel[0];
  965. break;
  966. '/' =>
  967. if(sign >= 0)
  968. v := a.r.q1;
  969. else
  970. v = a.r.q0;
  971. nextmatch(f, ap.re, v, sign);
  972. a.r = sel[0];
  973. break;
  974. '"' =>
  975. f = matchfile(ap.re);
  976. a = mkaddr(f);
  977. break;
  978. '*' =>
  979. a.r.q0 = 0;
  980. a.r.q1 = f.buf.nc;
  981. return a;
  982. ',' or
  983. ';' =>
  984. if(ap.left!=nil)
  985. a1 = cmdaddress(ap.left, a, 0);
  986. else{
  987. a1.f = a.f;
  988. a1.r.q0 = a1.r.q1 = 0;
  989. }
  990. if(ap.typex == ';'){
  991. f = a1.f;
  992. a = a1;
  993. f.curtext.q0 = a1.r.q0;
  994. f.curtext.q1 = a1.r.q1;
  995. }
  996. if(ap.next!=nil)
  997. a2 = cmdaddress(ap.next, a, 0);
  998. else{
  999. a2.f = a.f;
  1000. a2.r.q0 = a2.r.q1 = f.buf.nc;
  1001. }
  1002. if(a1.f != a2.f)
  1003. editerror("addresses in different files");
  1004. a.f = a1.f;
  1005. a.r.q0 = a1.r.q0;
  1006. a.r.q1 = a2.r.q1;
  1007. if(a.r.q1 < a.r.q0)
  1008. editerror("addresses out of order");
  1009. return a;
  1010. '+' or
  1011. '-' =>
  1012. sign = 1;
  1013. if(ap.typex == '-')
  1014. sign = -1;
  1015. if(ap.next==nil || ap.next.typex=='+' || ap.next.typex=='-')
  1016. a = lineaddr(1, a, sign);
  1017. break;
  1018. * =>
  1019. error("cmdaddress");
  1020. return a;
  1021. }
  1022. }while((ap = ap.next)!=nil); # assign =
  1023. return a;
  1024. }
  1025. alltofile(w: ref Window, tp: ref Tofile)
  1026. {
  1027. t: ref Text;
  1028. if(tp.f != nil)
  1029. return;
  1030. if(w.isscratch || w.isdir)
  1031. return;
  1032. t = w.body;
  1033. # only use this window if it's the current window for the file
  1034. if(t.file.curtext != t)
  1035. return;
  1036. # if(w.nopen[QWevent] > 0)
  1037. # return;
  1038. if(tp.r.r == t.file.name)
  1039. tp.f = t.file;
  1040. }
  1041. tofile(r: ref String): ref File
  1042. {
  1043. t: ref Tofile;
  1044. rr: String;
  1045. (rr.r, r.n) = skipbl(r.r, r.n);
  1046. t = ref Tofile;
  1047. t.f = nil;
  1048. t.r = ref String;
  1049. *t.r = rr;
  1050. aw := ref Allwin.FF(t);
  1051. allwindows(Edit->ALLTOFILE, aw);
  1052. aw = nil;
  1053. if(t.f == nil)
  1054. editerror(sprint("no such file\"%s\"", rr.r));
  1055. return t.f;
  1056. }
  1057. allmatchfile(w: ref Window, tp: ref Tofile)
  1058. {
  1059. t: ref Text;
  1060. if(w.isscratch || w.isdir)
  1061. return;
  1062. t = w.body;
  1063. # only use this window if it's the current window for the file
  1064. if(t.file.curtext != t)
  1065. return;
  1066. # if(w.nopen[QWevent] > 0)
  1067. # return;
  1068. if(filematch(w.body.file, tp.r)){
  1069. if(tp.f != nil)
  1070. editerror(sprint("too many files match \"%s\"", tp.r.r));
  1071. tp.f = w.body.file;
  1072. }
  1073. }
  1074. matchfile(r: ref String): ref File
  1075. {
  1076. tf: ref Tofile;
  1077. tf = ref Tofile;
  1078. tf.f = nil;
  1079. tf.r = r;
  1080. aw := ref Allwin.FF(tf);
  1081. allwindows(Edit->ALLMATCHFILE, aw);
  1082. aw = nil;
  1083. if(tf.f == nil)
  1084. editerror(sprint("no file matches \"%s\"", r.r));
  1085. return tf.f;
  1086. }
  1087. filematch(f: ref File, r: ref String): int
  1088. {
  1089. buf: string;
  1090. w: ref Window;
  1091. match, i, dirty: int;
  1092. s: Rangeset;
  1093. # compile expr first so if we get an error, we haven't allocated anything
  1094. if(rxcompile(r.r) == FALSE)
  1095. editerror("bad regexp in file match");
  1096. w = f.curtext.w;
  1097. # same check for dirty as in settag, but we know ncache==0
  1098. dirty = !w.isdir && !w.isscratch && f.mod;
  1099. buf = sprint("%c%c%c %s\n", " '"[dirty],
  1100. '+', " ."[curtext!=nil && curtext.file==f], f.name);
  1101. (match, s) = rxexecute(nil, buf, 0, i);
  1102. buf = nil;
  1103. return match;
  1104. }
  1105. charaddr(l: int, addr: Address, sign: int): Address
  1106. {
  1107. if(sign == 0)
  1108. addr.r.q0 = addr.r.q1 = l;
  1109. else if(sign < 0)
  1110. addr.r.q1 = addr.r.q0 -= l;
  1111. else if(sign > 0)
  1112. addr.r.q0 = addr.r.q1 += l;
  1113. if(addr.r.q0<0 || addr.r.q1>addr.f.buf.nc)
  1114. editerror("address out of range");
  1115. return addr;
  1116. }
  1117. lineaddr(l: int, addr: Address, sign: int): Address
  1118. {
  1119. n: int;
  1120. c: int;
  1121. f := addr.f;
  1122. a: Address;
  1123. p: int;
  1124. a.f = f;
  1125. if(sign >= 0){
  1126. if(l == 0){
  1127. if(sign==0 || addr.r.q1==0){
  1128. a.r.q0 = a.r.q1 = 0;
  1129. return a;
  1130. }
  1131. a.r.q0 = addr.r.q1;
  1132. p = addr.r.q1-1;
  1133. }else{
  1134. if(sign==0 || addr.r.q1==0){
  1135. p = 0;
  1136. n = 1;
  1137. }else{
  1138. p = addr.r.q1-1;
  1139. n = f.curtext.readc(p++)=='\n';
  1140. }
  1141. while(n < l){
  1142. if(p >= f.buf.nc)
  1143. editerror("address out of range");
  1144. if(f.curtext.readc(p++) == '\n')
  1145. n++;
  1146. }
  1147. a.r.q0 = p;
  1148. }
  1149. while(p < f.buf.nc && f.curtext.readc(p++)!='\n')
  1150. ;
  1151. a.r.q1 = p;
  1152. }else{
  1153. p = addr.r.q0;
  1154. if(l == 0)
  1155. a.r.q1 = addr.r.q0;
  1156. else{
  1157. for(n = 0; n<l; ){ # always runs once
  1158. if(p == 0){
  1159. if(++n != l)
  1160. editerror("address out of range");
  1161. }else{
  1162. c = f.curtext.readc(p-1);
  1163. if(c != '\n' || ++n != l)
  1164. p--;
  1165. }
  1166. }
  1167. a.r.q1 = p;
  1168. if(p > 0)
  1169. p--;
  1170. }
  1171. while(p > 0 && f.curtext.readc(p-1)!='\n') # lines start after a newline
  1172. p--;
  1173. a.r.q0 = p;
  1174. }
  1175. return a;
  1176. }
  1177. allfilecheck(w: ref Window, fp: ref Filecheck)
  1178. {
  1179. f: ref File;
  1180. f = w.body.file;
  1181. if(w.body.file == fp.f)
  1182. return;
  1183. if(fp.r == f.name)
  1184. warning(nil, sprint("warning: duplicate file name \"%s\"\n", fp.r));
  1185. }
  1186. cmdname(f: ref File, str: ref String , set: int): string
  1187. {
  1188. r, s: string;
  1189. n: int;
  1190. fc: ref Filecheck;
  1191. newname: Runestr;
  1192. r = nil;
  1193. n = str.n;
  1194. s = str.r;
  1195. if(n == 0){
  1196. # no name; use existing
  1197. if(f.name == nil)
  1198. return nil;
  1199. return f.name;
  1200. }
  1201. (s, n) = skipbl(s, n);
  1202. if(n == 0)
  1203. ;
  1204. else{
  1205. if(s[0] == '/'){
  1206. r = s;
  1207. }else{
  1208. newname = dirname(f.curtext, s, n);
  1209. r = newname.r;
  1210. n = newname.nr;
  1211. }
  1212. fc = ref Filecheck;
  1213. fc.f = f;
  1214. fc.r = r;
  1215. fc.nr = n;
  1216. aw := ref Allwin.FC(fc);
  1217. allwindows(Edit->ALLFILECHECK, aw);
  1218. aw = nil;
  1219. if(f.name == nil)
  1220. set = TRUE;
  1221. }
  1222. if(set && r[0: n] != f.name){
  1223. f.mark();
  1224. f.mod = TRUE;
  1225. f.curtext.w.dirty = TRUE;
  1226. f.curtext.w.setname(r, n);
  1227. }
  1228. return r;
  1229. }
  1230. copysel(rs: Rangeset): Rangeset
  1231. {
  1232. nrs := array[NRange] of Range;
  1233. for(i := 0; i < NRange; i++)
  1234. nrs[i] = rs[i];
  1235. return nrs;
  1236. }