another update from CVS HEAD, for QA
[alioth/jupp.git] / macro.c
1 /*
2  *      Keyboard macros
3  *      Copyright
4  *              (C) 1992 Joseph H. Allen
5  *
6  *      This file is part of JOE (Joe's Own Editor)
7  */
8 #include "config.h"
9 #include "types.h"
10
11 __RCSID("$MirOS: contrib/code/jupp/macro.c,v 1.19 2017/12/08 02:28:05 tg Exp $");
12
13 #include <string.h>
14 #include <stdlib.h>
15
16 #include "b.h"
17 #include "cmd.h"
18 #include "macro.h"
19 #include "main.h"
20 #include "pw.h"
21 #include "qw.h"
22 #include "tty.h"
23 #include "ublock.h"
24 #include "uedit.h"
25 #include "umath.h"
26 #include "undo.h"
27 #include "utils.h"
28 #include "vs.h"
29 #include "charmap.h"
30 #include "w.h"
31
32 MACRO *freemacros = NULL;
33
34 /* Create a macro */
35
36 MACRO *mkmacro(int k, int arg, int n, CMD *cmd)
37 {
38         MACRO *macro;
39
40         if (!freemacros) {
41                 int x;
42
43                 /* FIXME: why limit to 64? */
44                 macro = calloc(64, sizeof(MACRO));
45                 for (x = 0; x != 64; ++x) {
46                         macro[x].steps = (MACRO **) freemacros;
47                         freemacros = macro + x;
48                 }
49         }
50         macro = freemacros;
51         freemacros = (MACRO *) macro->steps;
52         macro->steps = NULL;
53         macro->size = 0;
54         macro->arg = arg;
55         macro->n = n;
56         macro->cmd = cmd;
57         macro->k = k;
58         return macro;
59 }
60
61 /* Eliminate a macro */
62
63 void rmmacro(MACRO *macro)
64 {
65         if (macro) {
66                 if (macro->steps) {
67                         int x;
68
69                         for (x = 0; x != macro->n; ++x)
70                                 rmmacro(macro->steps[x]);
71                         free(macro->steps);
72                 }
73                 macro->steps = (MACRO **) freemacros;
74                 freemacros = macro;
75         }
76 }
77
78 /* Add a step to block macro */
79
80 void addmacro(MACRO *macro, MACRO *m)
81 {
82         if (macro->n == macro->size) {
83                 if (macro->steps)
84                         macro->steps = realloc(macro->steps, (macro->size += 8) * sizeof(MACRO *));
85                 else
86                         macro->steps = calloc((macro->size = 8), sizeof(MACRO *));
87         }
88         macro->steps[macro->n++] = m;
89 }
90
91 /* Duplicate a macro */
92
93 MACRO *dupmacro(MACRO *mac)
94 {
95         MACRO *m = mkmacro(mac->k, mac->arg, mac->n, mac->cmd);
96
97         if (mac->steps) {
98                 int x;
99
100                 m->steps = calloc((m->size = mac->n), sizeof(MACRO *));
101                 for (x = 0; x != m->n; ++x)
102                         m->steps[x] = dupmacro(mac->steps[x]);
103         }
104         return m;
105 }
106
107 /* Set key part of macro */
108
109 MACRO *macstk(MACRO *m, int k)
110 {
111         m->k = k;
112         return m;
113 }
114
115 /* Set arg part of macro */
116
117 MACRO *macsta(MACRO *m, int a)
118 {
119         m->arg = a;
120         return m;
121 }
122
123 /* Parse text into a macro
124  * sta is set to:  ending position in buffer for no error.
125  *                 -1 for syntax error
126  *                 -2 for need more input
127  */
128
129 MACRO *mparse(MACRO *m, unsigned char *buf, int *sta)
130 {
131         int y, c, x = 0;
132
133  macroloop:
134
135         /* Skip whitespace */
136         while (joe_isblank(locale_map,buf[x]))
137                 ++x;
138
139         /* If the buffer is only whitespace then treat as unknown command */
140         if (!buf[x]) {
141                 *sta = -1;
142                 return NULL;
143         }
144
145         /* Do we have a string? */
146         if (buf[x] == '\"') {
147                 ++x;
148                 while (buf[x] && buf[x] != '\"') {
149                         if (buf[x] == '\\' && buf[x + 1]) {
150                                 ++x;
151                                 switch (buf[x]) {
152                                 case 'n':
153                                         buf[x] = 10;
154                                         break;
155                                 case 'r':
156                                         buf[x] = 13;
157                                         break;
158                                 case 'b':
159                                         buf[x] = 8;
160                                         break;
161                                 case 'f':
162                                         buf[x] = 12;
163                                         break;
164                                 case 'a':
165                                         buf[x] = 7;
166                                         break;
167                                 case 't':
168                                         buf[x] = 9;
169                                         break;
170                                 case 'x':
171                                         x += 1 + ustoc_hex(buf + x + 1, &c, USTOC_MAX);
172                                         break;
173                                 case '0':
174                                 case '1':
175                                 case '2':
176                                 case '3':
177                                 case '4':
178                                 case '5':
179                                 case '6':
180                                 case '7':
181                                 case '8':
182                                 case '9':
183                                         x += ustoc_oct(buf + x, &c, USTOC_MAX);
184                                         break;
185                                 }
186                         }
187                         if (m) {
188                                 if (!m->steps) {
189                                         MACRO *macro = m;
190
191                                         m = mkmacro(-1, 1, 0, NULL);
192                                         addmacro(m, macro);
193                                 }
194                         } else
195                                 m = mkmacro(-1, 1, 0, NULL);
196                         addmacro(m, mkmacro(buf[x], 1, 0, findcmd(UC "type")));
197                         ++x;
198                 }
199                 if (buf[x] == '\"')
200                         ++x;
201         }
202
203         /* Do we have a command? */
204         else {
205                 for (y = x; buf[y] && buf[y] != ',' && buf[y] != ' ' && buf[y] != '\t' && buf[y] != '\n' && buf[x] != '\r'; ++y) ;
206                 if (y != x) {
207                         CMD *cmd;
208
209                         c = buf[y];
210                         buf[y] = 0;
211                         cmd = findcmd(buf + x);
212                         if (!cmd) {
213                                 *sta = -1;
214                                 return NULL;
215                         } else if (m) {
216                                 if (!m->steps) {
217                                         MACRO *macro = m;
218
219                                         m = mkmacro(-1, 1, 0, NULL);
220                                         addmacro(m, macro);
221                                 }
222                                 addmacro(m, mkmacro(-1, 1, 0, cmd));
223                         } else
224                                 m = mkmacro(-1, 1, 0, cmd);
225                         buf[x = y] = c;
226                 }
227         }
228
229         /* Skip whitespace */
230         while (joe_isblank(locale_map,buf[x]))
231                 ++x;
232
233         /* Do we have a comma? */
234         if (buf[x] == ',') {
235                 ++x;
236                 while (joe_isblank(locale_map,buf[x]))
237                         ++x;
238                 if (buf[x] && buf[x] != '\r' && buf[x] != '\n')
239                         goto macroloop;
240                 *sta = -2;
241                 return m;
242         }
243
244         /* Done */
245         *sta = x;
246         return m;
247 }
248
249 /* Convert macro to text */
250
251 static unsigned char *ptr;
252 static int first;
253 static int instr;
254
255 static unsigned char *unescape(unsigned char *uptr, int c)
256 {
257         if (c == '"') {
258                 *uptr++ = '\\';
259                 *uptr++ = '"';
260         } else if (c == '\\') {
261                 *uptr++ = '\\';
262                 *uptr++ = '\\';
263         } else if (c == '\'') {
264                 *uptr++ = '\\';
265                 *uptr++ = '\'';
266         } else if (c < 32 || c > 126) {
267                 /* FIXME: what if c > 256 or c < 0 ? */
268                 *uptr++ = '\\';
269                 *uptr++ = 'x';
270                 *uptr++ = "0123456789ABCDEF"[c >> 4];
271                 *uptr++ = "0123456789ABCDEF"[c & 15];
272         } else
273                 *uptr++ = c;
274         return uptr;
275 }
276
277 static void domtext(MACRO *m)
278 {
279         int x;
280
281         if (!m)
282                 return;
283         if (m->steps)
284                 for (x = 0; x != m->n; ++x)
285                         domtext(m->steps[x]);
286         else {
287                 if (instr && strcmp(m->cmd->name, "type")) {
288                         *ptr++ = '\"';
289                         instr = 0;
290                 }
291                 if (first)
292                         first = 0;
293                 else if (!instr)
294                         *ptr++ = ',';
295                 if (!strcmp(m->cmd->name, "type")) {
296                         if (!instr) {
297                                 *ptr++ = '\"';
298                                 instr = 1;
299                         }
300                         ptr = unescape(ptr, m->k);
301                 } else {
302                         for (x = 0; m->cmd->name[x]; ++x)
303                                 *ptr++ = m->cmd->name[x];
304                         if (!strcmp(m->cmd->name, "play") || !strcmp(m->cmd->name, "gomark") || !strcmp(m->cmd->name, "setmark") || !strcmp(m->cmd->name, "record") || !strcmp(m->cmd->name, "uarg")) {
305                                 *ptr++ = ',';
306                                 *ptr++ = '"';
307                                 ptr = unescape(ptr, m->k);
308                                 *ptr++ = '"';
309                         }
310                 }
311         }
312 }
313
314 unsigned char *mtext(unsigned char *s, MACRO *m)
315 {
316         ptr = s;
317         first = 1;
318         instr = 0;
319         domtext(m);
320         if (instr)
321                 *ptr++ = '\"';
322         *ptr = 0;
323         return s;
324 }
325
326 /* Keyboard macro recorder */
327
328 static MACRO *kbdmacro[10];
329 static int playmode[10];
330
331 struct recmac *recmac = NULL;
332
333 static void unmac(void)
334 {
335         if (recmac)
336                 rmmacro(recmac->m->steps[--recmac->m->n]);
337 }
338
339 void chmac(void)
340 {
341         if (recmac && recmac->m->n)
342                 recmac->m->steps[recmac->m->n - 1]->k = 3;
343 }
344
345 static void record(MACRO *m)
346 {
347         if (recmac)
348                 addmacro(recmac->m, dupmacro(m));
349 }
350
351 /* Query for user input */
352
353 int uquery(BW *bw)
354 {
355         int ret;
356         struct recmac *tmp = recmac;
357
358         recmac = NULL;
359         ret = edloop(1);
360         recmac = tmp;
361         return ret;
362 }
363
364 /* Macro execution */
365
366 MACRO *curmacro = NULL;         /* Set if we're in a macro */
367 static int macroptr;
368 static int arg = 0;             /* Repeat argument */
369 static int argset = 0;          /* Set if 'arg' is set */
370
371 int exmacro(MACRO *m, int u)
372 {
373         int larg;
374         /*XXX why is this local here and global below? */
375         int negarg = 0;
376         int flg = 0;
377         CMD *cmd = NULL;
378         int ret = 0;
379
380         if (argset) {
381                 larg = arg;
382                 arg = 0;
383                 argset = 0;
384                 if (larg < 0) {
385                         negarg = 1;
386                         larg = -larg;
387                 }
388                 if (m->steps) {
389                         ; /* dead store: negarg = 0; */
390                 } else {
391                         cmd = m->cmd;
392                         if (!cmd->arg)
393                                 larg = 0;
394                         else if (negarg) {
395                                 if (cmd->negarg)
396                                         cmd = findcmd(cmd->negarg);
397                                 else
398                                         larg = 0;
399                         }
400                 }
401         } else {
402                 cmd = m->cmd;
403                 larg = 1;
404         }
405
406         if (m->steps || larg != 1 || !(cmd->flag & EMINOR)
407             || maint->curwin->watom->what == TYPEQW     /* Undo work right for s & r */
408             )
409                 flg = 1;
410
411         if (flg && u)
412                 umclear();
413         while (larg-- && !leave && !ret)
414                 if (m->steps) {
415                         MACRO *tmpmac = curmacro;
416                         int tmpptr = macroptr;
417                         int x = 0;
418                         int stk = nstack;
419
420                         while (m && x != m->n && !leave && !ret) {
421                                 MACRO *d;
422
423                                 d = m->steps[x++];
424                                 curmacro = m;
425                                 macroptr = x;
426                                 ret = exmacro(d, 0);
427                                 m = curmacro;
428                                 x = macroptr;
429                         }
430                         curmacro = tmpmac;
431                         macroptr = tmpptr;
432                         while (nstack > stk)
433                                 upop(NULL);
434                 } else
435                         ret = execmd(cmd, m->k);
436         if (leave)
437                 return ret;
438         if (flg && u)
439                 umclear();
440
441         if (u)
442                 undomark();
443
444         return ret;
445 }
446
447 /* Execute a macro */
448
449 int exemac(MACRO *m)
450 {
451         record(m);
452         return exmacro(m, 1);
453 }
454
455 /* Keyboard macro user routines */
456
457 static int dorecord(BW *bw, int c, void *object, int *notify)
458 {
459         int n;
460         struct recmac *r;
461
462         if (notify)
463                 *notify = 1;
464         if (c > '9' || c < '0') {
465                 nungetc(c);
466                 return -1;
467         }
468         for (n = 0; n != 10; ++n)
469                 if (playmode[n])
470                         return -1;
471         r = malloc(sizeof(struct recmac));
472
473         r->m = mkmacro(0, 1, 0, NULL);
474         r->next = recmac;
475         r->n = c - '0';
476         recmac = r;
477         return 0;
478 }
479
480 int urecord(BW *bw, int c)
481 {
482         if (c >= '0' && c <= '9')
483                 return dorecord(bw, c, NULL, NULL);
484         else if (mkqw(bw->parent, sc("Macro to record (0-9 or ^C to abort): "), dorecord, NULL, NULL, NULL))
485                 return 0;
486         else
487                 return -1;
488 }
489
490 extern volatile int dostaupd;
491
492 int ustop(void)
493 {
494         unmac();
495         if (recmac) {
496                 struct recmac *r = recmac;
497                 MACRO *m;
498
499                 dostaupd = 1;
500                 recmac = r->next;
501                 if (kbdmacro[r->n])
502                         rmmacro(kbdmacro[r->n]);
503                 kbdmacro[r->n] = r->m;
504                 if (recmac)
505                         record(m = mkmacro(r->n + '0', 1, 0, findcmd(UC "play"))), rmmacro(m);
506                 free(r);
507         }
508         return 0;
509 }
510
511 static int doplay(BW *bw, int c, void *object, int *notify)
512 {
513         if (notify)
514                 *notify = 1;
515         if (c >= '0' && c <= '9') {
516                 int ret;
517
518                 c -= '0';
519                 if (playmode[c] || !kbdmacro[c])
520                         return -1;
521                 playmode[c] = 1;
522                 ret = exmacro(kbdmacro[c], 0);
523                 playmode[c] = 0;
524                 return ret;
525         } else {
526                 nungetc(c);
527                 return -1;
528         }
529 }
530
531 int umacros(BW *bw)
532 {
533         int x;
534         unsigned char buf[1024];
535
536         p_goto_eol(bw->cursor);
537         for (x = 0; x != 10; ++x)
538                 if (kbdmacro[x]) {
539                         mtext(buf, kbdmacro[x]);
540                         binss(bw->cursor, buf);
541                         p_goto_eol(bw->cursor);
542                         joe_snprintf_2((char *)buf, JOE_MSGBUFSIZE, "\t^K %c\tMacro %d", x + '0', x);
543                         binss(bw->cursor, buf);
544                         p_goto_eol(bw->cursor);
545                         binsc(bw->cursor, '\n');
546                         pgetc(bw->cursor);
547                 }
548         return 0;
549 }
550
551 int uplay(BW *bw, int c)
552 {
553         if (c >= '0' && c <= '9')
554                 return doplay(bw, c, NULL, NULL);
555         else if (mkqwna(bw->parent, sc("Play-"), doplay, NULL, NULL, NULL))
556                 return 0;
557         else
558                 return -1;
559 }
560
561 /* Repeat-count setting */
562
563 static int doarg(BW *bw, unsigned char *s, void *object, int *notify)
564 {
565         long num;
566
567         if (notify)
568                 *notify = 1;
569         num = calcl(bw, s);
570         if (merrf) {
571                 vsrm(s);
572                 msgnw(bw->parent, merrt);
573                 return -1;
574         }
575         arg = num;
576         argset = 1;
577         vsrm(s);
578         return 0;
579 }
580
581 int uarg(BW *bw)
582 {
583         if (wmkpw(bw->parent, UC "No. times to repeat next command (^C to abort): ", NULL, doarg, NULL, NULL, utypebw, NULL, NULL, locale_map))
584                 return 0;
585         else
586                 return -1;
587 }
588
589 int unaarg;
590 int negarg;
591
592 static int douarg(BW *bw, int c, void *object, int *notify)
593 {
594         if (c == '-')
595                 negarg = !negarg;
596         else if (c >= '0' && c <= '9')
597                 unaarg = unaarg * 10 + c - '0';
598         else if (c == 'U' - '@')
599                 if (unaarg)
600                         unaarg *= 4;
601                 else
602                         unaarg = 16;
603         else if (c == 7 || c == 3 || c == 32) {
604                 if (notify)
605                         *notify = 1;
606                 return -1;
607         } else {
608                 nungetc(c);
609                 if (unaarg)
610                         arg = unaarg;
611                 else if (negarg)
612                         arg = 1;
613                 else
614                         arg = 4;
615                 if (negarg)
616                         arg = -arg;
617                 argset = 1;
618                 if (notify)
619                         *notify = 1;
620                 return 0;
621         }
622         joe_snprintf_2((char *)msgbuf, JOE_MSGBUFSIZE, "Repeat %s%d", negarg ? "-" : "", unaarg);
623         if (mkqwna(bw->parent, sz(msgbuf), douarg, NULL, NULL, notify))
624                 return 0;
625         else
626                 return -1;
627 }
628
629 int uuarg(BW *bw, int c)
630 {
631         unaarg = 0;
632         negarg = 0;
633         if ((c >= '0' && c <= '9') || c == '-')
634                 return douarg(bw, c, NULL, NULL);
635         else if (mkqwna(bw->parent, sc("Repeat"), douarg, NULL, NULL, NULL))
636                 return 0;
637         else
638                 return -1;
639 }