NetBSD/bin/ksh/emacs.c

2175 lines
41 KiB
C

/* $NetBSD: emacs.c,v 1.37 2017/06/30 04:41:19 kamil Exp $ */
/*
* Emacs-like command line editing and history
*
* created by Ron Natalie at BRL
* modified by Doug Kingston, Doug Gwyn, and Lou Salkind
* adapted to PD ksh by Eric Gisin
*/
#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: emacs.c,v 1.37 2017/06/30 04:41:19 kamil Exp $");
#endif
#include "config.h"
#ifdef EMACS
#include <sys/stat.h>
#include <ctype.h>
#include <locale.h>
#include <stdbool.h>
#include "sh.h"
#include "ksh_dir.h"
#include "edit.h"
static Area aedit;
#define AEDIT &aedit /* area for kill ring and macro defns */
#undef CTRL /* _BSD brain damage */
#define CTRL(x) ((x) == '?' ? 0x7F : (x) & 0x1F) /* ASCII */
#define UNCTRL(x) ((x) == 0x7F ? '?' : (x) | 0x40) /* ASCII */
#define META(x) ((x) & 0x7f)
#define ISMETA(x) (Flag(FEMACSUSEMETA) && ((x) & 0x80))
/* values returned by keyboard functions */
#define KSTD 0
#define KEOL 1 /* ^M, ^J */
#define KINTR 2 /* ^G, ^C */
struct x_ftab {
int (*xf_func) ARGS((int c));
const char *xf_name;
short xf_flags;
};
/* index into struct x_ftab x_ftab[] - small is good */
typedef unsigned char Findex;
struct x_defbindings {
Findex xdb_func; /* XFUNC_* */
unsigned char xdb_tab;
unsigned char xdb_char;
};
#define XF_ARG 1 /* command takes number prefix */
#define XF_NOBIND 2 /* not allowed to bind to function */
#define XF_PREFIX 4 /* function sets prefix */
/* Separator for completion */
#define is_cfs(c) (c == ' ' || c == '\t' || c == '"' || c == '\'')
#define is_mfs(c) (!(isalnum((unsigned char)c) || c == '_' || c == '$')) /* Separator for motion */
# define CHARMASK 0xFF /* 8-bit character mask */
# define X_NTABS 3 /* normal, meta1, meta2 */
#define X_TABSZ (CHARMASK+1) /* size of keydef tables etc */
/* Arguments for do_complete()
* 0 = enumerate M-= complete as much as possible and then list
* 1 = complete M-Esc
* 2 = list M-?
*/
typedef enum { CT_LIST, /* list the possible completions */
CT_COMPLETE, /* complete to longest prefix */
CT_COMPLIST /* complete and then list (if non-exact) */
} Comp_type;
/* { from 4.9 edit.h */
/*
* The following are used for my horizontal scrolling stuff
*/
static char *xbuf; /* beg input buffer */
static char *xend; /* end input buffer */
static char *xcp; /* current position */
static char *xep; /* current end */
static char *xbp; /* start of visible portion of input buffer */
static char *xlp; /* last char visible on screen */
static int x_adj_ok;
/*
* we use x_adj_done so that functions can tell
* whether x_adjust() has been called while they are active.
*/
static int x_adj_done;
static int xx_cols;
static int x_col;
static int x_displen;
static int x_arg; /* general purpose arg */
static int x_arg_defaulted;/* x_arg not explicitly set; defaulted to 1 */
static int xlp_valid;
/* end from 4.9 edit.h } */
static int x_prefix1 = CTRL('['), x_prefix2 = CTRL('X');
static char **x_histp; /* history position */
static int x_nextcmd; /* for newline-and-next */
static char *xmp; /* mark pointer */
static Findex x_last_command;
static Findex (*x_tab)[X_TABSZ]; /* key definition */
static char *(*x_atab)[X_TABSZ]; /* macro definitions */
static unsigned char x_bound[(X_TABSZ * X_NTABS + 7) / 8];
#define KILLSIZE 20
static char *killstack[KILLSIZE];
static int killsp, killtp;
static int x_curprefix;
static char *macroptr;
static int prompt_trunc;
static int prompt_skip;
static int x_ins ARGS((char *cp));
static void x_delete ARGS((int nc, int push));
static int x_bword ARGS((void));
static int x_fword ARGS((void));
static void x_goto ARGS((char *cp));
static void x_bs ARGS((int c));
static int x_size_str ARGS((char *cp));
static int x_size ARGS((int c));
static void x_zots ARGS((char *str));
static void x_zotc ARGS((int c));
static void x_load_hist ARGS((char **hp));
static int x_search ARGS((char *pat, int sameline, int offset));
static int x_match ARGS((char *str, char *pat));
static void x_redraw ARGS((int limit));
static void x_push ARGS((int nchars));
static char * x_mapin ARGS((const char *cp, Area *area));
static char * x_mapout ARGS((int c));
static void x_print ARGS((int prefix, int key));
static void x_adjust ARGS((void));
static void x_e_ungetc ARGS((int c));
static int x_e_getc ARGS((void));
static void x_e_putc ARGS((int c));
static void x_e_puts ARGS((const char *s));
static int x_comment ARGS((int c));
static int x_fold_case ARGS((int c));
static char *x_lastcp ARGS((void));
static void do_complete ARGS((int flags, Comp_type type));
static int x_emacs_putbuf ARGS((const char *s, size_t len));
/* The lines between START-FUNC-TAB .. END-FUNC-TAB are run through a
* script (emacs-gen.sh) that generates emacs.out which contains:
* - function declarations for x_* functions
* - defines of the form XFUNC_<name> where <name> is function
* name, sans leading x_.
* Note that the script treats #ifdef and { 0, 0, 0} specially - use with
* caution.
*/
#include "emacs.out"
static const struct x_ftab x_ftab[] = {
/* @START-FUNC-TAB@ */
{ x_abort, "abort", 0 },
{ x_beg_hist, "beginning-of-history", 0 },
{ x_comp_comm, "complete-command", 0 },
{ x_comp_file, "complete-file", 0 },
{ x_complete, "complete", 0 },
{ x_del_back, "delete-char-backward", XF_ARG },
{ x_del_bword, "delete-word-backward", XF_ARG },
{ x_del_char, "delete-char-forward", XF_ARG },
{ x_del_fword, "delete-word-forward", XF_ARG },
{ x_del_line, "kill-line", 0 },
{ x_draw_line, "redraw", 0 },
{ x_end_hist, "end-of-history", 0 },
{ x_end_of_text, "eot", 0 },
{ x_enumerate, "list", 0 },
{ x_eot_del, "eot-or-delete", XF_ARG },
{ x_error, "error", 0 },
{ x_goto_hist, "goto-history", XF_ARG },
{ x_ins_string, "macro-string", XF_NOBIND },
{ x_insert, "auto-insert", XF_ARG },
{ x_kill, "kill-to-eol", XF_ARG },
{ x_kill_region, "kill-region", 0 },
{ x_list_comm, "list-command", 0 },
{ x_list_file, "list-file", 0 },
{ x_literal, "quote", 0 },
{ x_meta1, "prefix-1", XF_PREFIX },
{ x_meta2, "prefix-2", XF_PREFIX },
{ x_meta_yank, "yank-pop", 0 },
{ x_mv_back, "backward-char", XF_ARG },
{ x_mv_begin, "beginning-of-line", 0 },
{ x_mv_bword, "backward-word", XF_ARG },
{ x_mv_end, "end-of-line", 0 },
{ x_mv_forw, "forward-char", XF_ARG },
{ x_mv_fword, "forward-word", XF_ARG },
{ x_newline, "newline", 0 },
{ x_next_com, "down-history", XF_ARG },
{ x_nl_next_com, "newline-and-next", 0 },
{ x_noop, "no-op", 0 },
{ x_prev_com, "up-history", XF_ARG },
{ x_prev_histword, "prev-hist-word", XF_ARG },
{ x_search_char_forw, "search-character-forward", XF_ARG },
{ x_search_char_back, "search-character-backward", XF_ARG },
{ x_search_hist, "search-history", 0 },
{ x_set_mark, "set-mark-command", 0 },
{ x_stuff, "stuff", 0 },
{ x_stuffreset, "stuff-reset", 0 },
{ x_transpose, "transpose-chars", 0 },
{ x_version, "version", 0 },
{ x_xchg_point_mark, "exchange-point-and-mark", 0 },
{ x_yank, "yank", 0 },
{ x_comp_list, "complete-list", 0 },
{ x_expand, "expand-file", 0 },
{ x_fold_capitalize, "capitalize-word", XF_ARG },
{ x_fold_lower, "downcase-word", XF_ARG },
{ x_fold_upper, "upcase-word", XF_ARG },
{ x_set_arg, "set-arg", XF_NOBIND },
{ x_comment, "comment", 0 },
#ifdef SILLY
{ x_game_of_life, "play-game-of-life", 0 },
#else
{ 0, 0, 0 },
#endif
#ifdef DEBUG
{ x_debug_info, "debug-info", 0 },
#else
{ 0, 0, 0 },
{ 0, 0, 0 },
#endif
/* @END-FUNC-TAB@ */
};
static struct x_defbindings const x_defbindings[] = {
{ XFUNC_del_back, 0, CTRL('?') },
{ XFUNC_del_bword, 1, CTRL('?') },
{ XFUNC_eot_del, 0, CTRL('D') },
{ XFUNC_del_back, 0, CTRL('H') },
{ XFUNC_del_bword, 1, CTRL('H') },
{ XFUNC_del_bword, 1, 'h' },
{ XFUNC_mv_bword, 1, 'b' },
{ XFUNC_mv_fword, 1, 'f' },
{ XFUNC_del_fword, 1, 'd' },
{ XFUNC_mv_back, 0, CTRL('B') },
{ XFUNC_mv_forw, 0, CTRL('F') },
{ XFUNC_search_char_forw, 0, CTRL(']') },
{ XFUNC_search_char_back, 1, CTRL(']') },
{ XFUNC_newline, 0, CTRL('M') },
{ XFUNC_newline, 0, CTRL('J') },
{ XFUNC_end_of_text, 0, CTRL('_') },
{ XFUNC_abort, 0, CTRL('G') },
{ XFUNC_prev_com, 0, CTRL('P') },
{ XFUNC_next_com, 0, CTRL('N') },
{ XFUNC_nl_next_com, 0, CTRL('O') },
{ XFUNC_search_hist, 0, CTRL('R') },
{ XFUNC_beg_hist, 1, '<' },
{ XFUNC_end_hist, 1, '>' },
{ XFUNC_goto_hist, 1, 'g' },
{ XFUNC_mv_end, 0, CTRL('E') },
{ XFUNC_mv_begin, 0, CTRL('A') },
{ XFUNC_draw_line, 0, CTRL('L') },
{ XFUNC_meta1, 0, CTRL('[') },
{ XFUNC_meta2, 0, CTRL('X') },
{ XFUNC_kill, 0, CTRL('K') },
{ XFUNC_yank, 0, CTRL('Y') },
{ XFUNC_meta_yank, 1, 'y' },
{ XFUNC_literal, 0, CTRL('^') },
{ XFUNC_comment, 1, '#' },
#if defined(BRL) && defined(TIOCSTI)
{ XFUNC_stuff, 0, CTRL('T') },
#else
{ XFUNC_transpose, 0, CTRL('T') },
#endif
{ XFUNC_complete, 1, CTRL('[') },
{ XFUNC_comp_list, 0, CTRL('I') },
{ XFUNC_comp_list, 1, '=' },
{ XFUNC_enumerate, 1, '?' },
{ XFUNC_expand, 1, '*' },
{ XFUNC_comp_file, 1, CTRL('X') },
{ XFUNC_comp_comm, 2, CTRL('[') },
{ XFUNC_list_comm, 2, '?' },
{ XFUNC_list_file, 2, CTRL('Y') },
{ XFUNC_set_mark, 1, ' ' },
{ XFUNC_kill_region, 0, CTRL('W') },
{ XFUNC_xchg_point_mark, 2, CTRL('X') },
{ XFUNC_version, 0, CTRL('V') },
#ifdef DEBUG
{ XFUNC_debug_info, 1, CTRL('H') },
#endif
{ XFUNC_prev_histword, 1, '.' },
{ XFUNC_prev_histword, 1, '_' },
{ XFUNC_set_arg, 1, '0' },
{ XFUNC_set_arg, 1, '1' },
{ XFUNC_set_arg, 1, '2' },
{ XFUNC_set_arg, 1, '3' },
{ XFUNC_set_arg, 1, '4' },
{ XFUNC_set_arg, 1, '5' },
{ XFUNC_set_arg, 1, '6' },
{ XFUNC_set_arg, 1, '7' },
{ XFUNC_set_arg, 1, '8' },
{ XFUNC_set_arg, 1, '9' },
{ XFUNC_fold_upper, 1, 'U' },
{ XFUNC_fold_upper, 1, 'u' },
{ XFUNC_fold_lower, 1, 'L' },
{ XFUNC_fold_lower, 1, 'l' },
{ XFUNC_fold_capitalize, 1, 'C' },
{ XFUNC_fold_capitalize, 1, 'c' },
/* These for ansi arrow keys: arguablely shouldn't be here by
* default, but its simpler/faster/smaller than using termcap
* entries.
*/
{ XFUNC_meta2, 1, '[' },
{ XFUNC_meta2, 1, 'O' },
{ XFUNC_prev_com, 2, 'A' },
{ XFUNC_next_com, 2, 'B' },
{ XFUNC_mv_forw, 2, 'C' },
{ XFUNC_mv_back, 2, 'D' },
};
int
x_emacs(buf, len)
char *buf;
size_t len;
{
int c;
const char *p;
int i;
Findex f;
xbp = xbuf = buf; xend = buf + len;
xlp = xcp = xep = buf;
*xcp = 0;
xlp_valid = true;
xmp = NULL;
x_curprefix = 0;
macroptr = (char *) 0;
x_histp = histptr + 1;
x_last_command = XFUNC_error;
xx_cols = x_cols;
x_col = promptlen(prompt, &p);
prompt_skip = p - prompt;
prompt_trunc = x_col - (x_cols - 3 - MIN_EDIT_SPACE);
if (prompt_trunc > 0)
x_col -= prompt_trunc;
else
prompt_trunc = 0;
x_adj_ok = 1;
x_displen = xx_cols - 2 - x_col;
x_adj_done = 0;
pprompt(prompt, prompt_trunc);
if (x_nextcmd >= 0) {
int off = source->line - x_nextcmd;
if (histptr - histlist >= off)
x_load_hist(histptr - off);
x_nextcmd = -1;
}
while (1) {
x_flush();
if ((c = x_e_getc()) < 0)
return 0;
if (ISMETA(c)) {
c = META(c);
x_curprefix = 1;
}
f = x_curprefix == -1 ? XFUNC_insert
: x_tab[x_curprefix][c&CHARMASK];
if (!(x_ftab[f].xf_flags & XF_PREFIX)
&& x_last_command != XFUNC_set_arg)
{
x_arg = 1;
x_arg_defaulted = 1;
}
i = c | (x_curprefix << 8);
x_curprefix = 0;
switch (i = (*x_ftab[f].xf_func)(i)) {
case KSTD:
if (!(x_ftab[f].xf_flags & XF_PREFIX))
x_last_command = f;
break;
case KEOL:
i = xep - xbuf;
return i;
case KINTR: /* special case for interrupt */
trapsig(SIGINT);
x_mode(false);
unwind(LSHELL);
}
}
}
static int
x_insert(c)
int c;
{
char str[2];
/*
* Should allow tab and control chars.
*/
if (c == 0) {
x_e_putc(BEL);
return KSTD;
}
str[0] = c;
str[1] = '\0';
while (x_arg--)
x_ins(str);
return KSTD;
}
static int
x_ins_string(c)
int c;
{
if (macroptr) {
x_e_putc(BEL);
return KSTD;
}
macroptr = x_atab[c>>8][c & CHARMASK];
if (macroptr && !*macroptr) {
/* XXX bell? */
macroptr = (char *) 0;
}
return KSTD;
}
static int x_do_ins(const char *cp, int len);
static int
x_do_ins(cp, len)
const char *cp;
int len;
{
if (xep+len >= xend) {
x_e_putc(BEL);
return -1;
}
memmove(xcp+len, xcp, xep - xcp + 1);
memmove(xcp, cp, len);
xcp += len;
xep += len;
return 0;
}
static int
x_ins(s)
char *s;
{
char *cp = xcp;
register int adj = x_adj_done;
if (x_do_ins(s, strlen(s)) < 0)
return -1;
/*
* x_zots() may result in a call to x_adjust()
* we want xcp to reflect the new position.
*/
xlp_valid = false;
x_lastcp();
x_adj_ok = (xcp >= xlp);
x_zots(cp);
if (adj == x_adj_done) /* has x_adjust() been called? */
{
/* no */
for (cp = xlp; cp > xcp; )
x_bs(*--cp);
}
x_adj_ok = 1;
return 0;
}
/*
* this is used for x_escape() in do_complete()
*/
static int
x_emacs_putbuf(s, len)
const char *s;
size_t len;
{
int rval;
if ((rval = x_do_ins(s, len)) != 0)
return (rval);
return (rval);
}
static int
x_del_back(c)
int c;
{
int col = xcp - xbuf;
if (col == 0) {
x_e_putc(BEL);
return KSTD;
}
if (x_arg > col)
x_arg = col;
x_goto(xcp - x_arg);
x_delete(x_arg, false);
return KSTD;
}
static int
x_del_char(c)
int c;
{
int nleft = xep - xcp;
if (!nleft) {
x_e_putc(BEL);
return KSTD;
}
if (x_arg > nleft)
x_arg = nleft;
x_delete(x_arg, false);
return KSTD;
}
/* Delete nc chars to the right of the cursor (including cursor position) */
static void
x_delete(nc, push)
int nc;
int push;
{
int i,j;
char *cp;
if (nc == 0)
return;
if (xmp != NULL && xmp > xcp) {
if (xcp + nc > xmp)
xmp = xcp;
else
xmp -= nc;
}
/*
* This lets us yank a word we have deleted.
*/
if (push)
x_push(nc);
xep -= nc;
cp = xcp;
j = 0;
i = nc;
while (i--) {
j += x_size(*cp++);
}
memmove(xcp, xcp+nc, xep - xcp + 1); /* Copies the null */
x_adj_ok = 0; /* don't redraw */
x_zots(xcp);
/*
* if we are already filling the line,
* there is no need to ' ','\b'.
* But if we must, make sure we do the minimum.
*/
if ((i = x_displen) > 0)
{
j = (j < i) ? j : i;
i = j;
while (i--)
x_e_putc(' ');
i = j;
while (i--)
x_e_putc('\b');
}
/*x_goto(xcp);*/
x_adj_ok = 1;
xlp_valid = false;
for (cp = x_lastcp(); cp > xcp; )
x_bs(*--cp);
return;
}
static int
x_del_bword(c)
int c;
{
x_delete(x_bword(), true);
return KSTD;
}
static int
x_mv_bword(c)
int c;
{
(void)x_bword();
return KSTD;
}
static int
x_mv_fword(c)
int c;
{
x_goto(xcp + x_fword());
return KSTD;
}
static int
x_del_fword(c)
int c;
{
x_delete(x_fword(), true);
return KSTD;
}
static int
x_bword()
{
int nc = 0;
register char *cp = xcp;
if (cp == xbuf) {
x_e_putc(BEL);
return 0;
}
while (x_arg--)
{
while (cp != xbuf && is_mfs(cp[-1]))
{
cp--;
nc++;
}
while (cp != xbuf && !is_mfs(cp[-1]))
{
cp--;
nc++;
}
}
x_goto(cp);
return nc;
}
static int
x_fword()
{
int nc = 0;
register char *cp = xcp;
if (cp == xep) {
x_e_putc(BEL);
return 0;
}
while (x_arg--)
{
while (cp != xep && is_mfs(*cp))
{
cp++;
nc++;
}
while (cp != xep && !is_mfs(*cp))
{
cp++;
nc++;
}
}
return nc;
}
static void
x_goto(cp)
register char *cp;
{
if (cp < xbp || cp >= (xbp + x_displen))
{
/* we are heading off screen */
xcp = cp;
x_adjust();
}
else
{
if (cp < xcp) /* move back */
{
while (cp < xcp)
x_bs(*--xcp);
}
else
{
if (cp > xcp) /* move forward */
{
while (cp > xcp)
x_zotc(*xcp++);
}
}
}
}
static void
x_bs(c)
int c;
{
register int i;
i = x_size(c);
while (i--)
x_e_putc('\b');
}
static int
x_size_str(cp)
register char *cp;
{
register int size = 0;
while (*cp)
size += x_size(*cp++);
return size;
}
static int
x_size(c)
int c;
{
if (c=='\t')
return 4; /* Kludge, tabs are always four spaces. */
if (iscntrl((unsigned char)c)) /* control char */
return 2;
return 1;
}
static void
x_zots(str)
register char *str;
{
register int adj = x_adj_done;
x_lastcp();
while (*str && str < xlp && adj == x_adj_done)
x_zotc(*str++);
}
static void
x_zotc(c)
int c;
{
if (c == '\t') {
/* Kludge, tabs are always four spaces. */
x_e_puts(" ");
} else if (iscntrl((unsigned char)c)) {
x_e_putc('^');
x_e_putc(UNCTRL(c));
} else
x_e_putc(c);
}
static int
x_mv_back(c)
int c;
{
int col = xcp - xbuf;
if (col == 0) {
x_e_putc(BEL);
return KSTD;
}
if (x_arg > col)
x_arg = col;
x_goto(xcp - x_arg);
return KSTD;
}
static int
x_mv_forw(c)
int c;
{
int nleft = xep - xcp;
if (!nleft) {
x_e_putc(BEL);
return KSTD;
}
if (x_arg > nleft)
x_arg = nleft;
x_goto(xcp + x_arg);
return KSTD;
}
static int
x_search_char_forw(c)
int c;
{
char *cp = xcp;
*xep = '\0';
c = x_e_getc();
while (x_arg--) {
if (c < 0
|| ((cp = (cp == xep) ? NULL : strchr(cp + 1, c)) == NULL
&& (cp = strchr(xbuf, c)) == NULL))
{
x_e_putc(BEL);
return KSTD;
}
}
x_goto(cp);
return KSTD;
}
static int
x_search_char_back(c)
int c;
{
char *cp = xcp, *p;
c = x_e_getc();
for (; x_arg--; cp = p)
for (p = cp; ; ) {
if (p-- == xbuf)
p = xep;
if (c < 0 || p == cp) {
x_e_putc(BEL);
return KSTD;
}
if (*p == c)
break;
}
x_goto(cp);
return KSTD;
}
static int
x_newline(c)
int c;
{
x_e_putc('\r');
x_e_putc('\n');
x_flush();
*xep++ = '\n';
return KEOL;
}
static int
x_end_of_text(c)
int c;
{
return KEOL;
}
static int x_beg_hist(c) int c; { x_load_hist(histlist); return KSTD;}
static int x_end_hist(c) int c; { x_load_hist(histptr); return KSTD;}
static int x_prev_com(c) int c; { x_load_hist(x_histp - x_arg); return KSTD;}
static int x_next_com(c) int c; { x_load_hist(x_histp + x_arg); return KSTD;}
/* Goto a particular history number obtained from argument.
* If no argument is given history 1 is probably not what you
* want so we'll simply go to the oldest one.
*/
static int
x_goto_hist(c)
int c;
{
if (x_arg_defaulted)
x_load_hist(histlist);
else
x_load_hist(histptr + x_arg - source->line);
return KSTD;
}
static void
x_load_hist(hp)
register char **hp;
{
int oldsize;
if (hp < histlist || hp > histptr) {
x_e_putc(BEL);
return;
}
x_histp = hp;
oldsize = x_size_str(xbuf);
strlcpy(xbuf, *hp, xend - xbuf);
xbp = xbuf;
xep = xcp = xbuf + strlen(xbuf);
xlp_valid = false;
if (xep > x_lastcp())
x_goto(xep);
else
x_redraw(oldsize);
}
static int
x_nl_next_com(c)
int c;
{
x_nextcmd = source->line - (histptr - x_histp) + 1;
return (x_newline(c));
}
static int
x_eot_del(c)
int c;
{
if (xep == xbuf && x_arg_defaulted)
return (x_end_of_text(c));
else
return (x_del_char(c));
}
/* reverse incremental history search */
static int
x_search_hist(c)
int c;
{
int offset = -1; /* offset of match in xbuf, else -1 */
char pat [256+1]; /* pattern buffer */
register char *p = pat;
Findex f;
*p = '\0';
while (1) {
if (offset < 0) {
x_e_puts("\nI-search: ");
x_e_puts(pat);
}
x_flush();
if ((c = x_e_getc()) < 0)
return KSTD;
f = x_tab[0][c&CHARMASK];
if (c == CTRL('['))
break;
else if (f == XFUNC_search_hist)
offset = x_search(pat, 0, offset);
else if (f == XFUNC_del_back) {
if (p == pat) {
offset = -1;
break;
}
if (p > pat)
*--p = '\0';
if (p == pat)
offset = -1;
else
offset = x_search(pat, 1, offset);
continue;
} else if (f == XFUNC_insert) {
/* add char to pattern */
/* overflow check... */
if (p >= &pat[sizeof(pat) - 1]) {
x_e_putc(BEL);
continue;
}
*p++ = c, *p = '\0';
if (offset >= 0) {
/* already have partial match */
offset = x_match(xbuf, pat);
if (offset >= 0) {
x_goto(xbuf + offset + (p - pat) - (*pat == '^'));
continue;
}
}
offset = x_search(pat, 0, offset);
} else { /* other command */
x_e_ungetc(c);
break;
}
}
if (offset < 0)
x_redraw(-1);
return KSTD;
}
/* search backward from current line */
static int
x_search(pat, sameline, offset)
char *pat;
int sameline;
int offset;
{
register char **hp;
int i;
for (hp = x_histp - (sameline ? 0 : 1) ; hp >= histlist; --hp) {
i = x_match(*hp, pat);
if (i >= 0) {
if (offset < 0)
x_e_putc('\n');
x_load_hist(hp);
x_goto(xbuf + i + strlen(pat) - (*pat == '^'));
return i;
}
}
x_e_putc(BEL);
x_histp = histptr;
return -1;
}
/* return position of first match of pattern in string, else -1 */
static int
x_match(str, pat)
char *str, *pat;
{
if (*pat == '^') {
return (strncmp(str, pat+1, strlen(pat+1)) == 0) ? 0 : -1;
} else {
char *q = strstr(str, pat);
return (q == NULL) ? -1 : q - str;
}
}
static int
x_del_line(c)
int c;
{
int i, j;
*xep = 0;
i = xep - xbuf;
j = x_size_str(xbuf);
xcp = xbuf;
x_push(i);
xlp = xbp = xep = xbuf;
xlp_valid = true;
*xcp = 0;
xmp = NULL;
x_redraw(j);
return KSTD;
}
static int
x_mv_end(c)
int c;
{
x_goto(xep);
return KSTD;
}
static int
x_mv_begin(c)
int c;
{
x_goto(xbuf);
return KSTD;
}
static int
x_draw_line(c)
int c;
{
x_redraw(-1);
return KSTD;
}
/* Redraw (part of) the line. If limit is < 0, the everything is redrawn
* on a NEW line, otherwise limit is the screen column up to which needs
* redrawing.
*/
static void
x_redraw(limit)
int limit;
{
int i, j;
char *cp;
x_adj_ok = 0;
if (limit == -1)
x_e_putc('\n');
else
x_e_putc('\r');
x_flush();
if (xbp == xbuf)
{
pprompt(prompt + prompt_skip, 0);
x_col = promptlen(prompt, (const char **) 0);
}
x_displen = xx_cols - 2 - x_col;
xlp_valid = false;
cp = x_lastcp();
x_zots(xbp);
if (xbp != xbuf || xep > xlp)
limit = xx_cols;
if (limit >= 0)
{
if (xep > xlp)
i = 0; /* we fill the line */
else
i = limit - (xlp - xbp);
for (j = 0; j < i && x_col < (xx_cols - 2); j++)
x_e_putc(' ');
i = ' ';
if (xep > xlp) /* more off screen */
{
if (xbp > xbuf)
i = '*';
else
i = '>';
}
else
if (xbp > xbuf)
i = '<';
x_e_putc(i);
j++;
while (j--)
x_e_putc('\b');
}
for (cp = xlp; cp > xcp; )
x_bs(*--cp);
x_adj_ok = 1;
D__(x_flush();)
return;
}
static int
x_transpose(c)
int c;
{
char tmp;
/* What transpose is meant to do seems to be up for debate. This
* is a general summary of the options; the text is abcd with the
* upper case character or underscore indicating the cursor position:
* Who Before After Before After
* at&t ksh in emacs mode: abCd abdC abcd_ (bell)
* at&t ksh in gmacs mode: abCd baCd abcd_ abdc_
* gnu emacs: abCd acbD abcd_ abdc_
* Pdksh currently goes with GNU behavior since I believe this is the
* most common version of emacs, unless in gmacs mode, in which case
* it does the at&t ksh gmacs mode.
* This should really be broken up into 3 functions so users can bind
* to the one they want.
*/
if (xcp == xbuf) {
x_e_putc(BEL);
return KSTD;
} else if (xcp == xep || Flag(FGMACS)) {
if (xcp - xbuf == 1) {
x_e_putc(BEL);
return KSTD;
}
/* Gosling/Unipress emacs style: Swap two characters before the
* cursor, do not change cursor position
*/
x_bs(xcp[-1]);
x_bs(xcp[-2]);
x_zotc(xcp[-1]);
x_zotc(xcp[-2]);
tmp = xcp[-1];
xcp[-1] = xcp[-2];
xcp[-2] = tmp;
} else {
/* GNU emacs style: Swap the characters before and under the
* cursor, move cursor position along one.
*/
x_bs(xcp[-1]);
x_zotc(xcp[0]);
x_zotc(xcp[-1]);
tmp = xcp[-1];
xcp[-1] = xcp[0];
xcp[0] = tmp;
x_bs(xcp[0]);
x_goto(xcp + 1);
}
return KSTD;
}
static int
x_literal(c)
int c;
{
x_curprefix = -1;
return KSTD;
}
static int
x_meta1(c)
int c;
{
x_curprefix = 1;
return KSTD;
}
static int
x_meta2(c)
int c;
{
x_curprefix = 2;
return KSTD;
}
static int
x_kill(c)
int c;
{
int col = xcp - xbuf;
int lastcol = xep - xbuf;
int ndel;
if (x_arg_defaulted)
x_arg = lastcol;
else if (x_arg > lastcol)
x_arg = lastcol;
ndel = x_arg - col;
if (ndel < 0) {
x_goto(xbuf + x_arg);
ndel = -ndel;
}
x_delete(ndel, true);
return KSTD;
}
static void
x_push(nchars)
int nchars;
{
char *cp = str_nsave(xcp, nchars, AEDIT);
if (killstack[killsp])
afree((void *)killstack[killsp], AEDIT);
killstack[killsp] = cp;
killsp = (killsp + 1) % KILLSIZE;
}
static int
x_yank(c)
int c;
{
if (killsp == 0)
killtp = KILLSIZE;
else
killtp = killsp;
killtp--;
if (killstack[killtp] == 0) {
x_e_puts("\nnothing to yank");
x_redraw(-1);
return KSTD;
}
xmp = xcp;
x_ins(killstack[killtp]);
return KSTD;
}
static int
x_meta_yank(c)
int c;
{
int len;
if ((x_last_command != XFUNC_yank && x_last_command != XFUNC_meta_yank)
|| killstack[killtp] == 0) {
killtp = killsp;
x_e_puts("\nyank something first");
x_redraw(-1);
return KSTD;
}
len = strlen(killstack[killtp]);
x_goto(xcp - len);
x_delete(len, false);
do {
if (killtp == 0)
killtp = KILLSIZE - 1;
else
killtp--;
} while (killstack[killtp] == 0);
x_ins(killstack[killtp]);
return KSTD;
}
static int
x_abort(c)
int c;
{
/* x_zotc(c); */
xlp = xep = xcp = xbp = xbuf;
xlp_valid = true;
*xcp = 0;
return KINTR;
}
static int
x_error(c)
int c;
{
x_e_putc(BEL);
return KSTD;
}
static int
x_stuffreset(c)
int c;
{
#ifdef TIOCSTI
(void)x_stuff(c);
return KINTR;
#else
x_zotc(c);
xlp = xcp = xep = xbp = xbuf;
xlp_valid = true;
*xcp = 0;
x_redraw(-1);
return KSTD;
#endif
}
static int
x_stuff(c)
int c;
{
#if 0 || defined TIOCSTI
char ch = c;
bool savmode = x_mode(false);
(void)ioctl(TTY, TIOCSTI, &ch);
(void)x_mode(savmode);
x_redraw(-1);
#endif
return KSTD;
}
static char *
x_mapin(cp, area)
const char *cp;
Area *area;
{
char *new, *op;
op = new = str_save(cp, area);
while (*cp) {
/* XXX -- should handle \^ escape? */
if (*cp == '^') {
cp++;
if (*cp >= '?') /* includes '?'; ASCII */
*op++ = CTRL(*cp);
else {
*op++ = '^';
cp--;
}
} else
*op++ = *cp;
cp++;
}
*op = '\0';
return new;
}
static char *
x_mapout(c)
int c;
{
static char buf[8];
register char *p = buf;
if (iscntrl((unsigned char)c)) {
*p++ = '^';
*p++ = UNCTRL(c);
} else
*p++ = c;
*p = 0;
return buf;
}
static void
x_print(prefix, key)
int prefix, key;
{
if (prefix == 1)
shprintf("%s", x_mapout(x_prefix1));
if (prefix == 2)
shprintf("%s", x_mapout(x_prefix2));
shprintf("%s = ", x_mapout(key));
if (x_tab[prefix][key] != XFUNC_ins_string)
shprintf("%s\n", x_ftab[x_tab[prefix][key]].xf_name);
else
shprintf("'%s'\n", x_atab[prefix][key]);
}
int
x_bind(a1, a2, macro, list)
const char *a1, *a2;
int macro; /* bind -m */
int list; /* bind -l */
{
Findex f;
int prefix, key;
char *sp = NULL;
char *m1, *m2;
if (x_tab == NULL) {
bi_errorf("cannot bind, not a tty");
return 1;
}
/* List function names */
if (list) {
for (f = 0; f < NELEM(x_ftab); f++)
if (x_ftab[f].xf_name
&& !(x_ftab[f].xf_flags & XF_NOBIND))
shprintf("%s\n", x_ftab[f].xf_name);
return 0;
}
if (a1 == NULL) {
for (prefix = 0; prefix < X_NTABS; prefix++)
for (key = 0; key < X_TABSZ; key++) {
f = x_tab[prefix][key];
if (f == XFUNC_insert || f == XFUNC_error
|| (macro && f != XFUNC_ins_string))
continue;
x_print(prefix, key);
}
return 0;
}
m2 = m1 = x_mapin(a1, ATEMP);
prefix = key = 0;
for (;; m1++) {
key = *m1 & CHARMASK;
if (x_tab[prefix][key] == XFUNC_meta1)
prefix = 1;
else if (x_tab[prefix][key] == XFUNC_meta2)
prefix = 2;
else
break;
}
afree(m2, ATEMP);
if (a2 == NULL) {
x_print(prefix, key);
return 0;
}
if (*a2 == 0)
f = XFUNC_insert;
else if (!macro) {
for (f = 0; f < NELEM(x_ftab); f++)
if (x_ftab[f].xf_name
&& strcmp(x_ftab[f].xf_name, a2) == 0)
break;
if (f == NELEM(x_ftab) || x_ftab[f].xf_flags & XF_NOBIND) {
bi_errorf("%s: no such function", a2);
return 1;
}
#if 0 /* This breaks the bind commands that map arrow keys */
if (f == XFUNC_meta1)
x_prefix1 = key;
if (f == XFUNC_meta2)
x_prefix2 = key;
#endif /* 0 */
} else {
f = XFUNC_ins_string;
sp = x_mapin(a2, AEDIT);
}
if (x_tab[prefix][key] == XFUNC_ins_string && x_atab[prefix][key])
afree((void *)x_atab[prefix][key], AEDIT);
x_tab[prefix][key] = f;
x_atab[prefix][key] = sp;
/* Track what the user has bound so x_emacs_keys() won't toast things */
if (f == XFUNC_insert)
x_bound[(prefix * X_TABSZ + key) / 8] &=
~(1 << ((prefix * X_TABSZ + key) % 8));
else
x_bound[(prefix * X_TABSZ + key) / 8] |=
(1 << ((prefix * X_TABSZ + key) % 8));
return 0;
}
void
x_init_emacs()
{
size_t i;
register int j;
char *locale;
ainit(AEDIT);
x_nextcmd = -1;
x_tab = (Findex (*)[X_TABSZ]) alloc(sizeofN(*x_tab, X_NTABS), AEDIT);
for (j = 0; j < X_TABSZ; j++)
x_tab[0][j] = XFUNC_insert;
for (i = 1; i < X_NTABS; i++)
for (j = 0; j < X_TABSZ; j++)
x_tab[i][j] = XFUNC_error;
for (i = 0; i < NELEM(x_defbindings); i++)
x_tab[(unsigned char)x_defbindings[i].xdb_tab][x_defbindings[i].xdb_char]
= x_defbindings[i].xdb_func;
x_atab = (char *(*)[X_TABSZ]) alloc(sizeofN(*x_atab, X_NTABS), AEDIT);
for (i = 1; i < X_NTABS; i++)
for (j = 0; j < X_TABSZ; j++)
x_atab[i][j] = NULL;
/* Determine if we can translate meta key or use 8-bit AscII
* XXX - It would be nice if there was a locale attribute to
* determine if the locale is 7-bit or not.
*/
locale = setlocale(LC_CTYPE, NULL);
if (locale == NULL || !strcmp(locale, "C") || !strcmp(locale, "POSIX"))
Flag(FEMACSUSEMETA) = 0;
}
static void bind_if_not_bound(int p, int k, int func);
static void
bind_if_not_bound(p, k, func)
int p, k;
int func;
{
/* Has user already bound this key? If so, don't override it */
if (x_bound[((p) * X_TABSZ + (k)) / 8]
& (1 << (((p) * X_TABSZ + (k)) % 8)))
return;
x_tab[p][k] = func;
}
void
x_emacs_keys(ec)
X_chars *ec;
{
if (ec->erase >= 0) {
bind_if_not_bound(0, ec->erase, XFUNC_del_back);
bind_if_not_bound(1, ec->erase, XFUNC_del_bword);
}
if (ec->kill >= 0)
bind_if_not_bound(0, ec->kill, XFUNC_del_line);
if (ec->werase >= 0)
bind_if_not_bound(0, ec->werase, XFUNC_del_bword);
if (ec->intr >= 0)
bind_if_not_bound(0, ec->intr, XFUNC_abort);
if (ec->quit >= 0)
bind_if_not_bound(0, ec->quit, XFUNC_noop);
}
static int
x_set_mark(c)
int c;
{
xmp = xcp;
return KSTD;
}
static int
x_kill_region(c)
int c;
{
int rsize;
char *xr;
if (xmp == NULL) {
x_e_putc(BEL);
return KSTD;
}
if (xmp > xcp) {
rsize = xmp - xcp;
xr = xcp;
} else {
rsize = xcp - xmp;
xr = xmp;
}
x_goto(xr);
x_delete(rsize, true);
xmp = xr;
return KSTD;
}
static int
x_xchg_point_mark(c)
int c;
{
char *tmp;
if (xmp == NULL) {
x_e_putc(BEL);
return KSTD;
}
tmp = xmp;
xmp = xcp;
x_goto( tmp );
return KSTD;
}
static int
x_version(c)
int c;
{
char *o_xbuf = xbuf, *o_xend = xend;
char *o_xbp = xbp, *o_xep = xep, *o_xcp = xcp;
int lim = x_lastcp() - xbp;
xbuf = xbp = xcp = ksh_version + 4;
xend = xep = ksh_version + 4 + strlen(ksh_version + 4);
x_redraw(lim);
x_flush();
c = x_e_getc();
xbuf = o_xbuf;
xend = o_xend;
xbp = o_xbp;
xep = o_xep;
xcp = o_xcp;
x_redraw(strlen(ksh_version));
if (c < 0)
return KSTD;
/* This is what at&t ksh seems to do... Very bizarre */
if (c != ' ')
x_e_ungetc(c);
return KSTD;
}
static int
x_noop(c)
int c;
{
return KSTD;
}
#ifdef SILLY
static int
x_game_of_life(c)
int c;
{
char newbuf [256+1];
register char *ip, *op;
int i, len;
i = xep - xbuf;
*xep = 0;
len = x_size_str(xbuf);
xcp = xbp = xbuf;
memmove(newbuf+1, xbuf, i);
newbuf[0] = 'A';
newbuf[i] = 'A';
for (ip = newbuf+1, op = xbuf; --i >= 0; ip++, op++) {
/* Empty space */
if (*ip < '@' || *ip == '_' || *ip == 0x7F) {
/* Two adults, make whoopee */
if (ip[-1] < '_' && ip[1] < '_') {
/* Make kid look like parents. */
*op = '`' + ((ip[-1] + ip[1])/2)%32;
if (*op == 0x7F) /* Birth defect */
*op = '`';
}
else
*op = ' '; /* nothing happens */
continue;
}
/* Child */
if (*ip > '`') {
/* All alone, dies */
if (ip[-1] == ' ' && ip[1] == ' ')
*op = ' ';
else /* Gets older */
*op = *ip-'`'+'@';
continue;
}
/* Adult */
/* Overcrowded, dies */
if (ip[-1] >= '@' && ip[1] >= '@') {
*op = ' ';
continue;
}
*op = *ip;
}
*op = 0;
x_redraw(len);
return KSTD;
}
#endif
/*
* File/command name completion routines
*/
static int
x_comp_comm(c)
int c;
{
do_complete(XCF_COMMAND, CT_COMPLETE);
return KSTD;
}
static int
x_list_comm(c)
int c;
{
do_complete(XCF_COMMAND, CT_LIST);
return KSTD;
}
static int
x_complete(c)
int c;
{
do_complete(XCF_COMMAND_FILE, CT_COMPLETE);
return KSTD;
}
static int
x_enumerate(c)
int c;
{
do_complete(XCF_COMMAND_FILE, CT_LIST);
return KSTD;
}
static int
x_comp_file(c)
int c;
{
do_complete(XCF_FILE, CT_COMPLETE);
return KSTD;
}
static int
x_list_file(c)
int c;
{
do_complete(XCF_FILE, CT_LIST);
return KSTD;
}
static int
x_comp_list(c)
int c;
{
do_complete(XCF_COMMAND_FILE, CT_COMPLIST);
return KSTD;
}
static int
x_expand(c)
int c;
{
char **words;
int nwords = 0;
int start, end;
int is_command;
int i;
nwords = x_cf_glob(XCF_FILE,
xbuf, xep - xbuf, xcp - xbuf,
&start, &end, &words, &is_command);
if (nwords == 0) {
x_e_putc(BEL);
return KSTD;
}
x_goto(xbuf + start);
x_delete(end - start, false);
for (i = 0; i < nwords;) {
if (x_escape(words[i], strlen(words[i]), x_emacs_putbuf) < 0 ||
(++i < nwords && x_ins(space) < 0))
{
x_e_putc(BEL);
return KSTD;
}
}
x_adjust();
return KSTD;
}
/* type == 0 for list, 1 for complete and 2 for complete-list */
static void
do_complete(flags, type)
int flags; /* XCF_{COMMAND,FILE,COMMAND_FILE} */
Comp_type type;
{
char **words;
int nwords;
int start, end, nlen, olen;
int is_command;
int completed = 0;
nwords = x_cf_glob(flags, xbuf, xep - xbuf, xcp - xbuf,
&start, &end, &words, &is_command);
/* no match */
if (nwords == 0) {
x_e_putc(BEL);
return;
}
if (type == CT_LIST) {
x_print_expansions(nwords, words, is_command);
x_redraw(0);
x_free_words(nwords, words);
return;
}
olen = end - start;
nlen = x_longest_prefix(nwords, words);
/* complete */
if (nwords == 1 || nlen > olen) {
x_goto(xbuf + start);
x_delete(olen, false);
x_escape(words[0], nlen, x_emacs_putbuf);
x_adjust();
completed = 1;
}
/* add space if single non-dir match */
if ((nwords == 1) && (!ISDIRSEP(words[0][nlen - 1]))) {
x_ins(space);
completed = 1;
}
if (type == CT_COMPLIST && !completed) {
x_print_expansions(nwords, words, is_command);
completed = 1;
}
if (completed)
x_redraw(0);
x_free_words(nwords, words);
}
/* NAME:
* x_adjust - redraw the line adjusting starting point etc.
*
* DESCRIPTION:
* This function is called when we have exceeded the bounds
* of the edit window. It increments x_adj_done so that
* functions like x_ins and x_delete know that we have been
* called and can skip the x_bs() stuff which has already
* been done by x_redraw.
*
* RETURN VALUE:
* None
*/
static void
x_adjust()
{
x_adj_done++; /* flag the fact that we were called. */
/*
* we had a problem if the prompt length > xx_cols / 2
*/
if ((xbp = xcp - (x_displen / 2)) < xbuf)
xbp = xbuf;
xlp_valid = false;
x_redraw(xx_cols);
x_flush();
}
static int unget_char = -1;
static void
x_e_ungetc(c)
int c;
{
unget_char = c;
}
static int
x_e_getc()
{
int c;
if (unget_char >= 0) {
c = unget_char;
unget_char = -1;
} else {
if (macroptr) {
c = (unsigned char) *macroptr++;
if (!*macroptr)
macroptr = (char *) 0;
} else
c = x_getc();
}
return c <= CHARMASK ? c : (c & CHARMASK);
}
static void
x_e_putc(c)
int c;
{
if (c == '\r' || c == '\n')
x_col = 0;
if (x_col < xx_cols)
{
x_putc(c);
switch(c)
{
case BEL:
break;
case '\r':
case '\n':
break;
case '\b':
x_col--;
break;
default:
x_col++;
break;
}
}
if (x_adj_ok && (x_col < 0 || x_col >= (xx_cols - 2)))
{
x_adjust();
}
}
#ifdef DEBUG
static int
x_debug_info(c)
int c;
{
x_flush();
shellf("\nksh debug:\n");
shellf("\tx_col == %d,\t\tx_cols == %d,\tx_displen == %d\n",
x_col, xx_cols, x_displen);
shellf("\txcp == 0x%lx,\txep == 0x%lx\n", (long) xcp, (long) xep);
shellf("\txbp == 0x%lx,\txbuf == 0x%lx\n", (long) xbp, (long) xbuf);
shellf("\txlp == 0x%lx\n", (long) xlp);
shellf("\txlp == 0x%lx\n", (long) x_lastcp());
shellf(newline);
x_redraw(-1);
return 0;
}
#endif
static void
x_e_puts(s)
const char *s;
{
register int adj = x_adj_done;
while (*s && adj == x_adj_done)
x_e_putc(*s++);
}
/* NAME:
* x_set_arg - set an arg value for next function
*
* DESCRIPTION:
* This is a simple implementation of M-[0-9].
*
* RETURN VALUE:
* KSTD
*/
static int
x_set_arg(c)
int c;
{
int n = 0;
int first = 1;
c &= CHARMASK; /* strip command prefix */
for (; c >= 0 && isdigit(c); c = x_e_getc(), first = 0)
n = n * 10 + (c - '0');
if (c < 0 || first) {
x_e_putc(BEL);
x_arg = 1;
x_arg_defaulted = 1;
} else {
x_e_ungetc(c);
x_arg = n;
x_arg_defaulted = 0;
}
return KSTD;
}
/* Comment or uncomment the current line. */
static int
x_comment(c)
int c;
{
int oldsize = x_size_str(xbuf);
int len = xep - xbuf;
int ret = x_do_comment(xbuf, xend - xbuf, &len);
if (ret < 0)
x_e_putc(BEL);
else {
xep = xbuf + len;
*xep = '\0';
xcp = xbp = xbuf;
x_redraw(oldsize);
if (ret > 0)
return x_newline('\n');
}
return KSTD;
}
/* NAME:
* x_prev_histword - recover word from prev command
*
* DESCRIPTION:
* This function recovers the last word from the previous
* command and inserts it into the current edit line. If a
* numeric arg is supplied then the n'th word from the
* start of the previous command is used.
*
* Bound to M-.
*
* RETURN VALUE:
* KSTD
*/
static int
x_prev_histword(c)
int c;
{
register char *rcp;
char *cp;
cp = *histptr;
if (!cp)
x_e_putc(BEL);
else if (x_arg_defaulted) {
rcp = &cp[strlen(cp) - 1];
/*
* ignore white-space after the last word
*/
while (rcp > cp && is_cfs(*rcp))
rcp--;
while (rcp > cp && !is_cfs(*rcp))
rcp--;
if (is_cfs(*rcp))
rcp++;
x_ins(rcp);
} else {
int i;
rcp = cp;
/*
* ignore white-space at start of line
*/
while (*rcp && is_cfs(*rcp))
rcp++;
while (x_arg-- > 1)
{
while (*rcp && !is_cfs(*rcp))
rcp++;
while (*rcp && is_cfs(*rcp))
rcp++;
}
cp = rcp;
while (*rcp && !is_cfs(*rcp))
rcp++;
i = *rcp;
*rcp = '\0';
x_ins(cp);
*rcp = i;
}
return KSTD;
}
/* Uppercase N(1) words */
static int
x_fold_upper(c)
int c;
{
return x_fold_case('U');
}
/* Lowercase N(1) words */
static int
x_fold_lower(c)
int c;
{
return x_fold_case('L');
}
/* Lowercase N(1) words */
static int
x_fold_capitalize(c)
int c;
{
return x_fold_case('C');
}
/* NAME:
* x_fold_case - convert word to UPPER/lower/Capital case
*
* DESCRIPTION:
* This function is used to implement M-U,M-u,M-L,M-l,M-C and M-c
* to UPPER case, lower case or Capitalize words.
*
* RETURN VALUE:
* None
*/
static int
x_fold_case(c)
int c;
{
char *cp = xcp;
if (cp == xep) {
x_e_putc(BEL);
return KSTD;
}
while (x_arg--) {
/*
* first skip over any white-space
*/
while (cp != xep && is_mfs(*cp))
cp++;
/*
* do the first char on its own since it may be
* a different action than for the rest.
*/
if (cp != xep) {
if (c == 'L') { /* lowercase */
if (isupper((unsigned char)*cp))
*cp = tolower((unsigned char)*cp);
} else { /* uppercase, capitialize */
if (islower((unsigned char)*cp))
*cp = toupper((unsigned char)*cp);
}
cp++;
}
/*
* now for the rest of the word
*/
while (cp != xep && !is_mfs((unsigned char)*cp)) {
if (c == 'U') { /* uppercase */
if (islower((unsigned char)*cp))
*cp = toupper((unsigned char)*cp);
} else { /* lowercase, capitialize */
if (isupper((unsigned char)*cp))
*cp = tolower((unsigned char)*cp);
}
cp++;
}
}
x_goto(cp);
return KSTD;
}
/* NAME:
* x_lastcp - last visible char
*
* SYNOPSIS:
* x_lastcp()
*
* DESCRIPTION:
* This function returns a pointer to that char in the
* edit buffer that will be the last displayed on the
* screen. The sequence:
*
* for (cp = x_lastcp(); cp > xcp; cp)
* x_bs(*--cp);
*
* Will position the cursor correctly on the screen.
*
* RETURN VALUE:
* cp or NULL
*/
static char *
x_lastcp()
{
register char *rcp;
register int i;
if (!xlp_valid)
{
for (i = 0, rcp = xbp; rcp < xep && i < x_displen; rcp++)
i += x_size(*rcp);
xlp = rcp;
}
xlp_valid = true;
return (xlp);
}
#endif /* EDIT */