/* $OpenBSD: extend.c,v 1.80 2023/04/17 10:11:30 op Exp $ */ /* This file is in the public domain. */ /* * Extended (M-x) commands, rebinding, and startup file processing. */ #include #include #include #include #include #include #include #include #include #include "chrdef.h" #include "def.h" #include "funmap.h" #include "kbd.h" #include "key.h" #include "macro.h" static int remap(KEYMAP *, int, PF, KEYMAP *); static KEYMAP *reallocmap(KEYMAP *); static void fixmap(KEYMAP *, KEYMAP *, KEYMAP *); static int dobind(KEYMAP *, const char *, int); static char *parsetoken(char *); static int bindkey(KEYMAP **, const char *, KCHAR *, int); /* * Insert a string, mainly for use from macros (created by selfinsert). */ int insert(int f, int n) { char buf[BUFSIZE], *bufp, *cp; int count, c; if (inmacro) { while (--n >= 0) { for (count = 0; count < maclcur->l_used; count++) { if ((((c = maclcur->l_text[count]) == *curbp->b_nlchr) ? lnewline() : linsert(1, c)) != TRUE) return (FALSE); } } maclcur = maclcur->l_fp; return (TRUE); } if (n == 1) /* CFINS means selfinsert can tack on the end */ thisflag |= CFINS; if ((bufp = eread("Insert: ", buf, sizeof(buf), EFNEW)) == NULL) return (ABORT); else if (bufp[0] == '\0') return (FALSE); while (--n >= 0) { cp = buf; while (*cp) { if (((*cp == *curbp->b_nlchr) ? lnewline() : linsert(1, *cp)) != TRUE) return (FALSE); cp++; } } return (TRUE); } /* * Bind a key to a function. Cases range from the trivial (replacing an * existing binding) to the extremely complex (creating a new prefix in a * map_element that already has one, so the map_element must be split, * but the keymap doesn't have enough room for another map_element, so * the keymap is reallocated). No attempt is made to reclaim space no * longer used, if this is a problem flags must be added to indicate * malloced versus static storage in both keymaps and map_elements. * Structure assignments would come in real handy, but K&R based compilers * don't have them. Care is taken so running out of memory will leave * the keymap in a usable state. * Parameters are: * curmap: pointer to the map being changed * c: character being changed * funct: function being changed to * pref_map: if funct==NULL, map to bind to or NULL for new */ static int remap(KEYMAP *curmap, int c, PF funct, KEYMAP *pref_map) { int i, n1, n2, nold; KEYMAP *mp, *newmap; PF *pfp; struct map_element *mep; if (ele >= &curmap->map_element[curmap->map_num] || c < ele->k_base) { if (ele > &curmap->map_element[0] && (funct != NULL || (ele - 1)->k_prefmap == NULL)) n1 = c - (ele - 1)->k_num; else n1 = HUGE; if (ele < &curmap->map_element[curmap->map_num] && (funct != NULL || ele->k_prefmap == NULL)) n2 = ele->k_base - c; else n2 = HUGE; if (n1 <= MAPELEDEF && n1 <= n2) { ele--; if ((pfp = calloc(c - ele->k_base + 1, sizeof(PF))) == NULL) return (dobeep_msg("Out of memory")); nold = ele->k_num - ele->k_base + 1; for (i = 0; i < nold; i++) pfp[i] = ele->k_funcp[i]; while (--n1) pfp[i++] = curmap->map_default; pfp[i] = funct; ele->k_num = c; ele->k_funcp = pfp; } else if (n2 <= MAPELEDEF) { if ((pfp = calloc(ele->k_num - c + 1, sizeof(PF))) == NULL) return (dobeep_msg("Out of memory")); nold = ele->k_num - ele->k_base + 1; for (i = 0; i < nold; i++) pfp[i + n2] = ele->k_funcp[i]; while (--n2) pfp[n2] = curmap->map_default; pfp[0] = funct; ele->k_base = c; ele->k_funcp = pfp; } else { if (curmap->map_num >= curmap->map_max) { if ((newmap = reallocmap(curmap)) == NULL) return (FALSE); curmap = newmap; } if ((pfp = malloc(sizeof(PF))) == NULL) return (dobeep_msg("Out of memory")); pfp[0] = funct; for (mep = &curmap->map_element[curmap->map_num]; mep > ele; mep--) { mep->k_base = (mep - 1)->k_base; mep->k_num = (mep - 1)->k_num; mep->k_funcp = (mep - 1)->k_funcp; mep->k_prefmap = (mep - 1)->k_prefmap; } ele->k_base = c; ele->k_num = c; ele->k_funcp = pfp; ele->k_prefmap = NULL; curmap->map_num++; } if (funct == NULL) { if (pref_map != NULL) ele->k_prefmap = pref_map; else { if ((mp = malloc(sizeof(KEYMAP) + (MAPINIT - 1) * sizeof(struct map_element))) == NULL) { (void)dobeep_msg("Out of memory"); ele->k_funcp[c - ele->k_base] = curmap->map_default; return (FALSE); } mp->map_num = 0; mp->map_max = MAPINIT; mp->map_default = rescan; ele->k_prefmap = mp; } } } else { n1 = c - ele->k_base; if (ele->k_funcp[n1] == funct && (funct != NULL || pref_map == NULL || pref_map == ele->k_prefmap)) /* no change */ return (TRUE); if (funct != NULL || ele->k_prefmap == NULL) { if (ele->k_funcp[n1] == NULL) ele->k_prefmap = NULL; /* easy case */ ele->k_funcp[n1] = funct; if (funct == NULL) { if (pref_map != NULL) ele->k_prefmap = pref_map; else { if ((mp = malloc(sizeof(KEYMAP) + (MAPINIT - 1) * sizeof(struct map_element))) == NULL) { (void)dobeep_msg("Out of memory"); ele->k_funcp[c - ele->k_base] = curmap->map_default; return (FALSE); } mp->map_num = 0; mp->map_max = MAPINIT; mp->map_default = rescan; ele->k_prefmap = mp; } } } else { /* * This case is the splits. * Determine which side of the break c goes on * 0 = after break; 1 = before break */ n2 = 1; for (i = 0; n2 && i < n1; i++) n2 &= ele->k_funcp[i] != NULL; if (curmap->map_num >= curmap->map_max) { if ((newmap = reallocmap(curmap)) == NULL) return (FALSE); curmap = newmap; } if ((pfp = calloc(ele->k_num - c + !n2, sizeof(PF))) == NULL) return (dobeep_msg("Out of memory")); ele->k_funcp[n1] = NULL; for (i = n1 + n2; i <= ele->k_num - ele->k_base; i++) pfp[i - n1 - n2] = ele->k_funcp[i]; for (mep = &curmap->map_element[curmap->map_num]; mep > ele; mep--) { mep->k_base = (mep - 1)->k_base; mep->k_num = (mep - 1)->k_num; mep->k_funcp = (mep - 1)->k_funcp; mep->k_prefmap = (mep - 1)->k_prefmap; } ele->k_num = c - !n2; (ele + 1)->k_base = c + n2; (ele + 1)->k_funcp = pfp; ele += !n2; ele->k_prefmap = NULL; curmap->map_num++; if (pref_map == NULL) { if ((mp = malloc(sizeof(KEYMAP) + (MAPINIT - 1) * sizeof(struct map_element))) == NULL) { (void)dobeep_msg("Out of memory"); ele->k_funcp[c - ele->k_base] = curmap->map_default; return (FALSE); } mp->map_num = 0; mp->map_max = MAPINIT; mp->map_default = rescan; ele->k_prefmap = mp; } else ele->k_prefmap = pref_map; } } return (TRUE); } /* * Reallocate a keymap. Returns NULL (without trashing the current map) * on failure. */ static KEYMAP * reallocmap(KEYMAP *curmap) { struct maps_s *mps; KEYMAP *mp; int i; if (curmap->map_max > SHRT_MAX - MAPGROW) { (void)dobeep_msg("keymap too large"); return (NULL); } if ((mp = malloc(sizeof(KEYMAP) + (curmap->map_max + (MAPGROW - 1)) * sizeof(struct map_element))) == NULL) { (void)dobeep_msg("Out of memory"); return (NULL); } mp->map_num = curmap->map_num; mp->map_max = curmap->map_max + MAPGROW; mp->map_default = curmap->map_default; for (i = curmap->map_num; i--;) { mp->map_element[i].k_base = curmap->map_element[i].k_base; mp->map_element[i].k_num = curmap->map_element[i].k_num; mp->map_element[i].k_funcp = curmap->map_element[i].k_funcp; mp->map_element[i].k_prefmap = curmap->map_element[i].k_prefmap; } for (mps = maps; mps != NULL; mps = mps->p_next) { if (mps->p_map == curmap) mps->p_map = mp; else fixmap(curmap, mp, mps->p_map); } ele = &mp->map_element[ele - &curmap->map_element[0]]; return (mp); } /* * Fix references to a reallocated keymap (recursive). */ static void fixmap(KEYMAP *curmap, KEYMAP *mp, KEYMAP *mt) { int i; for (i = mt->map_num; i--;) { if (mt->map_element[i].k_prefmap != NULL) { if (mt->map_element[i].k_prefmap == curmap) mt->map_element[i].k_prefmap = mp; else fixmap(curmap, mp, mt->map_element[i].k_prefmap); } } } /* * Do the input for local-set-key, global-set-key and define-key * then call remap to do the work. */ static int dobind(KEYMAP *curmap, const char *p, int unbind) { KEYMAP *pref_map = NULL; PF funct; char bprompt[80], *bufp, *pep; int c, s, n; if (macrodef) { /* * Keystrokes aren't collected. Not hard, but pretty useless. * Would not work for function keys in any case. */ return (dobeep_msg("Can't rebind key in macro")); } if (inmacro) { for (s = 0; s < maclcur->l_used - 1; s++) { if (doscan(curmap, c = CHARMASK(maclcur->l_text[s]), &curmap) != NULL) { if (remap(curmap, c, NULL, NULL) != TRUE) return (FALSE); } } (void)doscan(curmap, c = maclcur->l_text[s], NULL); maclcur = maclcur->l_fp; } else { n = strlcpy(bprompt, p, sizeof(bprompt)); if (n >= sizeof(bprompt)) n = sizeof(bprompt) - 1; pep = bprompt + n; for (;;) { ewprintf("%s", bprompt); pep[-1] = ' '; pep = getkeyname(pep, sizeof(bprompt) - (pep - bprompt), c = getkey(FALSE)); if (doscan(curmap, c, &curmap) != NULL) break; *pep++ = '-'; *pep = '\0'; } } if (unbind) funct = rescan; else { if ((bufp = eread("%s to command: ", bprompt, sizeof(bprompt), EFFUNC | EFNEW, bprompt)) == NULL) return (ABORT); else if (bufp[0] == '\0') return (FALSE); if (((funct = name_function(bprompt)) == NULL) ? (pref_map = name_map(bprompt)) == NULL : funct == NULL) return (dobeep_msg("[No match]")); } return (remap(curmap, c, funct, pref_map)); } /* * bindkey: bind key sequence to a function in the specified map. Used by * excline so it can bind function keys. To close to release to change * calling sequence, should just pass KEYMAP *curmap rather than * KEYMAP **mapp. */ static int bindkey(KEYMAP **mapp, const char *fname, KCHAR *keys, int kcount) { KEYMAP *curmap = *mapp; KEYMAP *pref_map = NULL; PF funct; int c; if (fname == NULL) funct = rescan; else if (((funct = name_function(fname)) == NULL) ? (pref_map = name_map(fname)) == NULL : funct == NULL) { dobeep(); ewprintf("[No match: %s]", fname); return (FALSE); } while (--kcount) { if (doscan(curmap, c = *keys++, &curmap) != NULL) { if (remap(curmap, c, NULL, NULL) != TRUE) return (FALSE); /* * XXX - Bizzarreness. remap creates an empty KEYMAP * that the last key is supposed to point to. */ curmap = ele->k_prefmap; } } (void)doscan(curmap, c = *keys, NULL); return (remap(curmap, c, funct, pref_map)); } /* * Wrapper for bindkey() that converts escapes. */ int dobindkey(KEYMAP *map, const char *func, const char *str) { int i; for (i = 0; *str && i < MAXKEY; i++) { /* XXX - convert numbers w/ strol()? */ if (*str == '^' && *(str + 1) != '\0') { key.k_chars[i] = CCHR(toupper((unsigned char)*++str)); } else if (*str == '\\' && *(str + 1) != '\0') { switch (*++str) { case '^': key.k_chars[i] = '^'; break; case 't': case 'T': key.k_chars[i] = '\t'; break; case 'n': case 'N': key.k_chars[i] = *curbp->b_nlchr; break; case 'r': case 'R': key.k_chars[i] = '\r'; break; case 'e': case 'E': key.k_chars[i] = CCHR('['); break; case '\\': key.k_chars[i] = '\\'; break; } } else key.k_chars[i] = *str; str++; } key.k_count = i; return (bindkey(&map, func, key.k_chars, key.k_count)); } /* * This function modifies the fundamental keyboard map. */ int bindtokey(int f, int n) { return (dobind(fundamental_map, "Global set key: ", FALSE)); } /* * This function modifies the current mode's keyboard map. */ int localbind(int f, int n) { return (dobind(curbp->b_modes[curbp->b_nmodes]->p_map, "Local set key: ", FALSE)); } /* * This function redefines a key in any keymap. */ int redefine_key(int f, int n) { static char buf[48]; char tmp[32], *bufp; KEYMAP *mp; (void)strlcpy(buf, "Define key map: ", sizeof(buf)); if ((bufp = eread("%s", tmp, sizeof(tmp), EFNEW, buf)) == NULL) return (ABORT); else if (bufp[0] == '\0') return (FALSE); (void)strlcat(buf, tmp, sizeof(buf)); if ((mp = name_map(tmp)) == NULL) return (dobeep_msgs("Unknown map", tmp)); if (strlcat(buf, "key: ", sizeof(buf)) >= sizeof(buf)) return (FALSE); return (dobind(mp, buf, FALSE)); } int unbindtokey(int f, int n) { return (dobind(fundamental_map, "Global unset key: ", TRUE)); } int localunbind(int f, int n) { return (dobind(curbp->b_modes[curbp->b_nmodes]->p_map, "Local unset key: ", TRUE)); } /* * Extended command. Call the message line routine to read in the command * name and apply autocompletion to it. When it comes back, look the name * up in the symbol table and run the command if it is found. Print an * error if there is anything wrong. */ int extend(int f, int n) { PF funct; char xname[NXNAME], *bufp; if (!(f & FFARG)) bufp = eread("M-x ", xname, NXNAME, EFNEW | EFFUNC); else bufp = eread("%d M-x ", xname, NXNAME, EFNEW | EFFUNC, n); if (bufp == NULL) return (ABORT); else if (bufp[0] == '\0') return (FALSE); if ((funct = name_function(bufp)) != NULL) { if (macrodef) { struct line *lp = maclcur; macro[macrocount - 1].m_funct = funct; maclcur = lp->l_bp; maclcur->l_fp = lp->l_fp; free(lp); } return ((*funct)(f, n)); } return (dobeep_msg("[No match]")); } /* * Define the commands needed to do startup-file processing. * This code is mostly a kludge just so we can get startup-file processing. * * If you're serious about having this code, you should rewrite it. * To wit: * It has lots of funny things in it to make the startup-file look * like a GNU startup file; mostly dealing with parens and semicolons. * This should all vanish. * * We define eval-expression because it's easy. It can make * *-set-key or define-key set an arbitrary key sequence, so it isn't * useless. */ /* * evalexpr - get one line from the user, and run it. * Use strlen for length of line, assume user is not typing in a '\0' in the * modeline. llen only used for foundparen() so old-school will be ok. */ int evalexpr(int f, int n) { char exbuf[BUFSIZE], *bufp; int llen; if ((bufp = eread("Eval: ", exbuf, sizeof(exbuf), EFNEW | EFCR)) == NULL) return (ABORT); else if (bufp[0] == '\0') return (FALSE); llen = strlen(bufp); return (excline(exbuf, llen, 1)); } /* * evalbuffer - evaluate the current buffer as line commands. Useful for * testing startup files. */ int evalbuffer(int f, int n) { struct line *lp; struct buffer *bp = curbp; int s, llen, lnum = 0; static char excbuf[BUFSIZE]; for (lp = bfirstlp(bp); lp != bp->b_headp; lp = lforw(lp)) { lnum++; llen = llength(lp); if (llen >= BUFSIZE) return (FALSE); (void)strncpy(excbuf, ltext(lp), llen); /* make sure the line is terminated */ excbuf[llen] = '\0'; if ((s = excline(excbuf, llen, lnum)) != TRUE) { cleanup(); return (s); } } cleanup(); return (TRUE); } /* * evalfile - go get a file and evaluate it as line commands. You can * go get your own startup file if need be. */ int evalfile(int f, int n) { FILE *ffp; char fname[NFILEN], *bufp; int ret; if ((bufp = eread("Load file: ", fname, NFILEN, EFNEW | EFCR)) == NULL) return (ABORT); if (bufp[0] == '\0') return (FALSE); if ((bufp = adjustname(fname, TRUE)) == NULL) return (FALSE); ret = ffropen(&ffp, bufp, NULL); if (ret == FIODIR) (void)ffclose(ffp, NULL); if (ret != FIOSUC) return (FALSE); ret = load(ffp, bufp); (void)ffclose(ffp, NULL); return (ret); } /* * load - go load the file name we got passed. */ int load(FILE *ffp, const char *fname) { int s = TRUE, line; int nbytes = 0; char excbuf[BUFSIZE]; line = 0; while ((s = ffgetline(ffp, excbuf, sizeof(excbuf) - 1, &nbytes)) == FIOSUC) { line++; excbuf[nbytes] = '\0'; if (excline(excbuf, nbytes, line) != TRUE) { s = FIOERR; dobeep(); ewprintf("Error loading file %s at line %d", fname, line); break; } } excbuf[nbytes] = '\0'; if (s != FIOEOF || (nbytes && excline(excbuf, nbytes, ++line) != TRUE)) return (FALSE); return (TRUE); } /* * excline - run a line from a load file or eval-expression. */ int excline(char *line, int llen, int lnum) { PF fp; struct line *lp, *np; int status, c, f, n; char *funcp, *tmp; char *argp = NULL; long nl; int bind; KEYMAP *curmap; #define BINDARG 0 /* this arg is key to bind (local/global set key) */ #define BINDNO 1 /* not binding or non-quoted BINDARG */ #define BINDNEXT 2 /* next arg " (define-key) */ #define BINDDO 3 /* already found key to bind */ #define BINDEXT 1 /* space for trailing \0 */ lp = NULL; if (macrodef || inmacro) return (dobeep_msg("Not now!")); f = 0; n = 1; funcp = skipwhite(line); if (*funcp == '\0') return (TRUE); /* No error on blank lines */ if (*funcp == '(') return (foundparen(funcp, llen, lnum)); line = parsetoken(funcp); if (*line != '\0') { *line++ = '\0'; line = skipwhite(line); if (ISDIGIT(*line) || *line == '-') { argp = line; line = parsetoken(line); } } if (argp != NULL) { f = FFARG; nl = strtol(argp, &tmp, 10); if (*tmp != '\0') return (FALSE); if (nl >= INT_MAX || nl <= INT_MIN) return (FALSE); n = (int)nl; } if ((fp = name_function(funcp)) == NULL) return (dobeep_msgs("Unknown function:", funcp)); if (fp == bindtokey || fp == unbindtokey) { bind = BINDARG; curmap = fundamental_map; } else if (fp == localbind || fp == localunbind) { bind = BINDARG; curmap = curbp->b_modes[curbp->b_nmodes]->p_map; } else if (fp == redefine_key) bind = BINDNEXT; else bind = BINDNO; /* Pack away all the args now... */ if ((np = lalloc(0)) == FALSE) return (FALSE); np->l_fp = np->l_bp = maclcur = np; while (*line != '\0') { argp = skipwhite(line); if (*argp == '\0') break; line = parsetoken(argp); if (*argp != '"') { if (*argp == '\'') ++argp; if ((lp = lalloc((int) (line - argp) + BINDEXT)) == NULL) { status = FALSE; goto cleanup; } bcopy(argp, ltext(lp), (int)(line - argp)); /* don't count BINDEXT */ lp->l_used--; if (bind == BINDARG) bind = BINDNO; } else { /* quoted strings are special */ ++argp; if (bind != BINDARG) { lp = lalloc((int)(line - argp) + BINDEXT); if (lp == NULL) { status = FALSE; goto cleanup; } lp->l_used = 0; } else key.k_count = 0; while (*argp != '"' && *argp != '\0') { if (*argp != '\\') c = *argp++; else { switch (*++argp) { case 't': case 'T': c = CCHR('I'); break; case 'n': case 'N': c = CCHR('J'); break; case 'r': case 'R': c = CCHR('M'); break; case 'e': case 'E': c = CCHR('['); break; case '^': /* * split into two statements * due to bug in OSK cpp */ c = CHARMASK(*++argp); c = ISLOWER(c) ? CCHR(TOUPPER(c)) : CCHR(c); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': c = *argp - '0'; if (argp[1] <= '7' && argp[1] >= '0') { c <<= 3; c += *++argp - '0'; if (argp[1] <= '7' && argp[1] >= '0') { c <<= 3; c += *++argp - '0'; } } break; case 'f': case 'F': c = *++argp - '0'; if (ISDIGIT(argp[1])) { c *= 10; c += *++argp - '0'; } c += KFIRST; break; default: c = CHARMASK(*argp); break; } argp++; } if (bind == BINDARG) key.k_chars[key.k_count++] = c; else lp->l_text[lp->l_used++] = c; } if (*line) line++; } switch (bind) { case BINDARG: bind = BINDDO; break; case BINDNEXT: lp->l_text[lp->l_used] = '\0'; if ((curmap = name_map(lp->l_text)) == NULL) { (void)dobeep_msgs("No such mode:", lp->l_text); status = FALSE; free(lp); goto cleanup; } free(lp); bind = BINDARG; break; default: lp->l_fp = np->l_fp; lp->l_bp = np; np->l_fp = lp; np = lp; } } switch (bind) { default: (void)dobeep_msg("Bad args to set key"); status = FALSE; break; case BINDDO: if (fp != unbindtokey && fp != localunbind) { lp->l_text[lp->l_used] = '\0'; status = bindkey(&curmap, lp->l_text, key.k_chars, key.k_count); } else status = bindkey(&curmap, NULL, key.k_chars, key.k_count); break; case BINDNO: inmacro = TRUE; maclcur = maclcur->l_fp; status = (*fp)(f, n); inmacro = FALSE; } cleanup: lp = maclcur->l_fp; while (lp != maclcur) { np = lp->l_fp; free(lp); lp = np; } free(lp); maclhead = NULL; macrodef = FALSE; return (status); } /* * a pair of utility functions for the above */ char * skipwhite(char *s) { while (*s == ' ' || *s == '\t') s++; if ((*s == ';') || (*s == '#')) *s = '\0'; return (s); } static char * parsetoken(char *s) { if (*s != '"') { while (*s && *s != ' ' && *s != '\t' && *s != ')' && *s != '(') s++; if (*s == ';') *s = '\0'; } else do { /* * Strings get special treatment. * Beware: You can \ out the end of the string! */ if (*s == '\\') ++s; } while (*++s != '"' && *s != '\0'); return (s); }