we’ll need to distinguish these for sarge/etch as well
[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.11 2017/12/02 04:32:40 tg Exp $");
12
13 #include <string.h>
14 #ifdef HAVE_STDLIB_H
15 #include <stdlib.h>
16 #endif
17
18 #include "b.h"
19 #include "cmd.h"
20 #include "main.h"
21 #include "pw.h"
22 #include "qw.h"
23 #include "tty.h"
24 #include "ublock.h"
25 #include "uedit.h"
26 #include "umath.h"
27 #include "undo.h"
28 #include "utils.h"
29 #include "vs.h"
30 #include "charmap.h"
31 #include "w.h"
32
33 MACRO *freemacros = NULL;
34
35 /* Create a macro */
36
37 MACRO *mkmacro(int k, int arg, int n, CMD *cmd)
38 {
39         MACRO *macro;
40
41         if (!freemacros) {
42                 int x;
43
44                 macro = (MACRO *) joe_malloc(sizeof(MACRO) * 64);
45                 for (x = 0; x != 64; ++x) {     /* FIXME: why limit to 64? */
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                         joe_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 = (MACRO **) joe_realloc(macro->steps, (macro->size += 8) * sizeof(MACRO *));
85                 else
86                         macro->steps = (MACRO **) joe_malloc((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 = (MACRO **) joe_malloc((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                                         c = 0;
172                                         if (buf[x + 1] >= '0' && buf[x + 1] <= '9')
173                                                 c = c * 16 + buf[++x] - '0';
174                                         else if ((buf[x + 1] >= 'a' && buf[x + 1] <= 'f') || (buf[x + 1] >= 'A' && buf[x + 1] <= 'F'))
175                                                 c = c * 16 + (buf[++x] & 0xF) + 9;
176                                         if (buf[x + 1] >= '0' && buf[x + 1] <= '9')
177                                                 c = c * 16 + buf[++x] - '0';
178                                         else if ((buf[x + 1] >= 'a' && buf[x + 1] <= 'f') || (buf[x + 1] >= 'A' && buf[x + 1] <= 'F'))
179                                                 c = c * 16 + (buf[++x] & 0xF) + 9;
180                                         buf[x] = c;
181                                         break;
182                                 case '0':
183                                 case '1':
184                                 case '2':
185                                 case '3':
186                                 case '4':
187                                 case '5':
188                                 case '6':
189                                 case '7':
190                                 case '8':
191                                 case '9':
192                                         c = buf[x] - '0';
193                                         if (buf[x + 1] >= '0' && buf[x + 1] <= '7')
194                                                 c = c * 8 + buf[++x] - '0';
195                                         if (buf[x + 1] >= '0' && buf[x + 1] <= '7')
196                                                 c = c * 8 + buf[++x] - '0';
197                                         buf[x] = c;
198                                         break;
199                                 }
200                         }
201                         if (m) {
202                                 if (!m->steps) {
203                                         MACRO *macro = m;
204
205                                         m = mkmacro(-1, 1, 0, NULL);
206                                         addmacro(m, macro);
207                                 }
208                         } else
209                                 m = mkmacro(-1, 1, 0, NULL);
210                         addmacro(m, mkmacro(buf[x], 1, 0, findcmd(US "type")));
211                         ++x;
212                 }
213                 if (buf[x] == '\"')
214                         ++x;
215         }
216
217         /* Do we have a command? */
218         else {
219                 for (y = x; buf[y] && buf[y] != ',' && buf[y] != ' ' && buf[y] != '\t' && buf[y] != '\n' && buf[x] != '\r'; ++y) ;
220                 if (y != x) {
221                         CMD *cmd;
222
223                         c = buf[y];
224                         buf[y] = 0;
225                         cmd = findcmd(buf + x);
226                         if (!cmd) {
227                                 *sta = -1;
228                                 return NULL;
229                         } else if (m) {
230                                 if (!m->steps) {
231                                         MACRO *macro = m;
232
233                                         m = mkmacro(-1, 1, 0, NULL);
234                                         addmacro(m, macro);
235                                 }
236                                 addmacro(m, mkmacro(-1, 1, 0, cmd));
237                         } else
238                                 m = mkmacro(-1, 1, 0, cmd);
239                         buf[x = y] = c;
240                 }
241         }
242
243         /* Skip whitespace */
244         while (joe_isblank(locale_map,buf[x]))
245                 ++x;
246
247         /* Do we have a comma? */
248         if (buf[x] == ',') {
249                 ++x;
250                 while (joe_isblank(locale_map,buf[x]))
251                         ++x;
252                 if (buf[x] && buf[x] != '\r' && buf[x] != '\n')
253                         goto macroloop;
254                 *sta = -2;
255                 return m;
256         }
257
258         /* Done */
259         *sta = x;
260         return m;
261 }
262
263 /* Convert macro to text */
264
265 static unsigned char *ptr;
266 static int first;
267 static int instr;
268
269 static unsigned char *unescape(unsigned char *uptr, int c)
270 {
271         if (c == '"') {
272                 *uptr++ = '\\';
273                 *uptr++ = '"';
274         } else if (c == '\\') {
275                 *uptr++ = '\\';
276                 *uptr++ = '\\';
277         } else if (c == '\'') {
278                 *uptr++ = '\\';
279                 *uptr++ = '\'';
280         } else if (c < 32 || c > 126) {
281                 /* FIXME: what if c > 256 or c < 0 ? */
282                 *uptr++ = '\\';
283                 *uptr++ = 'x';
284                 *uptr++ = "0123456789ABCDEF"[c >> 4];
285                 *uptr++ = "0123456789ABCDEF"[c & 15];
286         } else
287                 *uptr++ = c;
288         return uptr;
289 }
290
291 static void domtext(MACRO *m)
292 {
293         int x;
294
295         if (!m)
296                 return;
297         if (m->steps)
298                 for (x = 0; x != m->n; ++x)
299                         domtext(m->steps[x]);
300         else {
301                 if (instr && strcmp(m->cmd->name, "type")) {
302                         *ptr++ = '\"';
303                         instr = 0;
304                 }
305                 if (first)
306                         first = 0;
307                 else if (!instr)
308                         *ptr++ = ',';
309                 if (!strcmp(m->cmd->name, "type")) {
310                         if (!instr) {
311                                 *ptr++ = '\"';
312                                 instr = 1;
313                         }
314                         ptr = unescape(ptr, m->k);
315                 } else {
316                         for (x = 0; m->cmd->name[x]; ++x)
317                                 *ptr++ = m->cmd->name[x];
318                         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")) {
319                                 *ptr++ = ',';
320                                 *ptr++ = '"';
321                                 ptr = unescape(ptr, m->k);
322                                 *ptr++ = '"';
323                         }
324                 }
325         }
326 }
327
328 unsigned char *mtext(unsigned char *s, MACRO *m)
329 {
330         ptr = s;
331         first = 1;
332         instr = 0;
333         domtext(m);
334         if (instr)
335                 *ptr++ = '\"';
336         *ptr = 0;
337         return s;
338 }
339
340 /* Keyboard macro recorder */
341
342 static MACRO *kbdmacro[10];
343 static int playmode[10];
344
345 struct recmac *recmac = NULL;
346
347 static void unmac(void)
348 {
349         if (recmac)
350                 rmmacro(recmac->m->steps[--recmac->m->n]);
351 }
352
353 void chmac(void)
354 {
355         if (recmac && recmac->m->n)
356                 recmac->m->steps[recmac->m->n - 1]->k = 3;
357 }
358
359 static void record(MACRO *m)
360 {
361         if (recmac)
362                 addmacro(recmac->m, dupmacro(m));
363 }
364
365 /* Query for user input */
366
367 int uquery(BW *bw)
368 {
369         int ret;
370         struct recmac *tmp = recmac;
371
372         recmac = NULL;
373         ret = edloop(1);
374         recmac = tmp;
375         return ret;
376 }
377
378 /* Macro execution */
379
380 MACRO *curmacro = NULL;         /* Set if we're in a macro */
381 static int macroptr;
382 static int arg = 0;             /* Repeat argument */
383 static int argset = 0;          /* Set if 'arg' is set */
384
385 int exmacro(MACRO *m, int u)
386 {
387         int larg;
388         /*XXX why is this local here and global below? */
389         int negarg = 0;
390         int flg = 0;
391         CMD *cmd = NULL;
392         int ret = 0;
393
394         if (argset) {
395                 larg = arg;
396                 arg = 0;
397                 argset = 0;
398                 if (larg < 0) {
399                         negarg = 1;
400                         larg = -larg;
401                 }
402                 if (m->steps) {
403                         ; /* dead store: negarg = 0; */
404                 } else {
405                         cmd = m->cmd;
406                         if (!cmd->arg)
407                                 larg = 0;
408                         else if (negarg) {
409                                 if (cmd->negarg)
410                                         cmd = findcmd(cmd->negarg);
411                                 else
412                                         larg = 0;
413                         }
414                 }
415         } else {
416                 cmd = m->cmd;
417                 larg = 1;
418         }
419
420         if (m->steps || larg != 1 || !(cmd->flag & EMINOR)
421             || maint->curwin->watom->what == TYPEQW     /* Undo work right for s & r */
422             )
423                 flg = 1;
424
425         if (flg && u)
426                 umclear();
427         while (larg-- && !leave && !ret)
428                 if (m->steps) {
429                         MACRO *tmpmac = curmacro;
430                         int tmpptr = macroptr;
431                         int x = 0;
432                         int stk = nstack;
433
434                         while (m && x != m->n && !leave && !ret) {
435                                 MACRO *d;
436
437                                 d = m->steps[x++];
438                                 curmacro = m;
439                                 macroptr = x;
440                                 ret = exmacro(d, 0);
441                                 m = curmacro;
442                                 x = macroptr;
443                         }
444                         curmacro = tmpmac;
445                         macroptr = tmpptr;
446                         while (nstack > stk)
447                                 upop(NULL);
448                 } else
449                         ret = execmd(cmd, m->k);
450         if (leave)
451                 return ret;
452         if (flg && u)
453                 umclear();
454
455         if (u)
456                 undomark();
457
458         return ret;
459 }
460
461 /* Execute a macro */
462
463 int exemac(MACRO *m)
464 {
465         record(m);
466         return exmacro(m, 1);
467 }
468
469 /* Keyboard macro user routines */
470
471 static int dorecord(BW *bw, int c, void *object, int *notify)
472 {
473         int n;
474         struct recmac *r;
475
476         if (notify)
477                 *notify = 1;
478         if (c > '9' || c < '0') {
479                 nungetc(c);
480                 return -1;
481         }
482         for (n = 0; n != 10; ++n)
483                 if (playmode[n])
484                         return -1;
485         r = (struct recmac *) joe_malloc(sizeof(struct recmac));
486
487         r->m = mkmacro(0, 1, 0, NULL);
488         r->next = recmac;
489         r->n = c - '0';
490         recmac = r;
491         return 0;
492 }
493
494 int urecord(BW *bw, int c)
495 {
496         if (c >= '0' && c <= '9')
497                 return dorecord(bw, c, NULL, NULL);
498         else if (mkqw(bw->parent, sc("Macro to record (0-9 or ^C to abort): "), dorecord, NULL, NULL, NULL))
499                 return 0;
500         else
501                 return -1;
502 }
503
504 extern volatile int dostaupd;
505
506 int ustop(void)
507 {
508         unmac();
509         if (recmac) {
510                 struct recmac *r = recmac;
511                 MACRO *m;
512
513                 dostaupd = 1;
514                 recmac = r->next;
515                 if (kbdmacro[r->n])
516                         rmmacro(kbdmacro[r->n]);
517                 kbdmacro[r->n] = r->m;
518                 if (recmac)
519                         record(m = mkmacro(r->n + '0', 1, 0, findcmd(US "play"))), rmmacro(m);
520                 joe_free(r);
521         }
522         return 0;
523 }
524
525 static int doplay(BW *bw, int c, void *object, int *notify)
526 {
527         if (notify)
528                 *notify = 1;
529         if (c >= '0' && c <= '9') {
530                 int ret;
531
532                 c -= '0';
533                 if (playmode[c] || !kbdmacro[c])
534                         return -1;
535                 playmode[c] = 1;
536                 ret = exmacro(kbdmacro[c], 0);
537                 playmode[c] = 0;
538                 return ret;
539         } else {
540                 nungetc(c);
541                 return -1;
542         }
543 }
544
545 int umacros(BW *bw)
546 {
547         int x;
548         unsigned char buf[1024];
549
550         p_goto_eol(bw->cursor);
551         for (x = 0; x != 10; ++x)
552                 if (kbdmacro[x]) {
553                         mtext(buf, kbdmacro[x]);
554                         binss(bw->cursor, buf);
555                         p_goto_eol(bw->cursor);
556                         joe_snprintf_2((char *)buf, JOE_MSGBUFSIZE, "\t^K %c\tMacro %d", x + '0', x);
557                         binss(bw->cursor, buf);
558                         p_goto_eol(bw->cursor);
559                         binsc(bw->cursor, '\n');
560                         pgetc(bw->cursor);
561                 }
562         return 0;
563 }
564
565 int uplay(BW *bw, int c)
566 {
567         if (c >= '0' && c <= '9')
568                 return doplay(bw, c, NULL, NULL);
569         else if (mkqwna(bw->parent, sc("Play-"), doplay, NULL, NULL, NULL))
570                 return 0;
571         else
572                 return -1;
573 }
574
575 /* Repeat-count setting */
576
577 static int doarg(BW *bw, unsigned char *s, void *object, int *notify)
578 {
579         long num;
580
581         if (notify)
582                 *notify = 1;
583         num = calcl(bw, s);
584         if (merrf) {
585                 msgnw(bw->parent, merrt);
586                 return -1;
587         }
588         arg = num;
589         argset = 1;
590         vsrm(s);
591         return 0;
592 }
593
594 int uarg(BW *bw)
595 {
596         if (wmkpw(bw->parent, US "No. times to repeat next command (^C to abort): ", NULL, doarg, NULL, NULL, utypebw, NULL, NULL, locale_map))
597                 return 0;
598         else
599                 return -1;
600 }
601
602 int unaarg;
603 int negarg;
604
605 static int douarg(BW *bw, int c, void *object, int *notify)
606 {
607         if (c == '-')
608                 negarg = !negarg;
609         else if (c >= '0' && c <= '9')
610                 unaarg = unaarg * 10 + c - '0';
611         else if (c == 'U' - '@')
612                 if (unaarg)
613                         unaarg *= 4;
614                 else
615                         unaarg = 16;
616         else if (c == 7 || c == 3 || c == 32) {
617                 if (notify)
618                         *notify = 1;
619                 return -1;
620         } else {
621                 nungetc(c);
622                 if (unaarg)
623                         arg = unaarg;
624                 else if (negarg)
625                         arg = 1;
626                 else
627                         arg = 4;
628                 if (negarg)
629                         arg = -arg;
630                 argset = 1;
631                 if (notify)
632                         *notify = 1;
633                 return 0;
634         }
635         joe_snprintf_2((char *)msgbuf, JOE_MSGBUFSIZE, "Repeat %s%d", negarg ? "-" : "", unaarg);
636         if (mkqwna(bw->parent, sz(msgbuf), douarg, NULL, NULL, notify))
637                 return 0;
638         else
639                 return -1;
640 }
641
642 int uuarg(BW *bw, int c)
643 {
644         unaarg = 0;
645         negarg = 0;
646         if ((c >= '0' && c <= '9') || c == '-')
647                 return douarg(bw, c, NULL, NULL);
648         else if (mkqwna(bw->parent, sc("Repeat"), douarg, NULL, NULL, NULL))
649                 return 0;
650         else
651                 return -1;
652 }