a34b05d2e6
This opens an OpenBSD-mirabile (aka MirBSD) repository. ### MirBSD is: # Copyright (c) 1982-2003 by Thorsten "mirabile" Glaser <x86@ePost.de> # Copyright © 1968-2003 The authors of And contributors to UNIX®, the # C Language, BSD/Berkeley Unix; 386BSD, NetBSD 1.1 and OpenBSD. # # Anyone who obtained a copy of this work is hereby permitted to freely use, # distribute, modify, merge, sublicence, give away or sell it as long as the # authors are given due credit and the following notice is retained: # # This work is provided "as is", with no explicit or implicit warranty what- # soever. Use it only at your own risk. In no event may an author or contri- # butor be held liable for any damage, directly or indirectly, that origina- # ted through or is caused by creation or modification of this work. MirBSD is my private tree. MirBSD does not differ very much from OpenBSD and intentionally tracks OpenBSD. That's why it _is_ OpenBSD, just not the official one. It's like with DarrenBSD. At time of this writing, no advertising for MirBSD must be done, because the advertising clause has not yet been sorted out. http://templeofhate.com/tglaser/MirBSD/index.php
2200 lines
41 KiB
C
2200 lines
41 KiB
C
/* $OpenBSD: emacs.c,v 1.18 2003/02/28 09:45:09 jmc 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 "config.h"
|
|
#ifdef EMACS
|
|
|
|
#include "sh.h"
|
|
#include "ksh_stat.h"
|
|
#include "ksh_dir.h"
|
|
#include <ctype.h>
|
|
#include <locale.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) (x_usemeta && ((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(c) || c == '_' || c == '$')) /* Separator for motion */
|
|
|
|
#ifdef OS2
|
|
/* Deal with 8 bit chars & an extra prefix for function key (these two
|
|
* changes increase memory usage from 9,216 bytes to 24,416 bytes...)
|
|
*/
|
|
# define CHARMASK 0xFF /* 8-bit ASCII character mask */
|
|
# define X_NTABS 4 /* normal, meta1, meta2, meta3 */
|
|
static int x_prefix3 = 0xE0;
|
|
#else /* OS2 */
|
|
# define CHARMASK 0xFF /* 8-bit character mask */
|
|
# define X_NTABS 3 /* normal, meta1, meta2 */
|
|
#endif /* OS2 */
|
|
#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 x_usemeta; /* no 8-bit ascii, meta = ESC */
|
|
|
|
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_skip;
|
|
|
|
static int x_ins ARGS((char *cp));
|
|
static void x_delete ARGS((int nc, int force_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));
|
|
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_capitialize, "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 },
|
|
#endif
|
|
#ifdef OS2
|
|
{ x_meta3, "prefix-3", XF_PREFIX },
|
|
#else
|
|
{ 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_capitialize, 1, 'C' },
|
|
{ XFUNC_fold_capitialize, 1, 'c' },
|
|
#ifdef OS2
|
|
{ XFUNC_meta3, 0, 0xE0 },
|
|
{ XFUNC_mv_back, 3, 'K' },
|
|
{ XFUNC_mv_forw, 3, 'M' },
|
|
{ XFUNC_next_com, 3, 'P' },
|
|
{ XFUNC_prev_com, 3, 'H' },
|
|
#endif /* OS2 */
|
|
/* 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;
|
|
x_adj_ok = 1;
|
|
x_displen = xx_cols - 2 - x_col;
|
|
x_adj_done = 0;
|
|
|
|
pprompt(prompt, 0);
|
|
|
|
if (x_nextcmd >= 0) {
|
|
int off = source->line - x_nextcmd;
|
|
if (histptr - history >= 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(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, force_push)
|
|
int nc;
|
|
int force_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 (nc > 1 || force_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 = xx_cols - 2 - x_col) > 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(), FALSE);
|
|
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(), FALSE);
|
|
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(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(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(history); 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(history);
|
|
else
|
|
x_load_hist(histptr + x_arg - source->line);
|
|
return KSTD;
|
|
}
|
|
|
|
static void
|
|
x_load_hist(hp)
|
|
register char **hp;
|
|
{
|
|
int oldsize;
|
|
|
|
if (hp < history || hp > histptr) {
|
|
x_e_putc(BEL);
|
|
return;
|
|
}
|
|
x_histp = hp;
|
|
oldsize = x_size_str(xbuf);
|
|
(void)strcpy(xbuf, *hp);
|
|
xbp = xbuf;
|
|
xep = xcp = xbuf + strlen(*hp);
|
|
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 >= history; --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 mdoe.
|
|
* 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;
|
|
}
|
|
|
|
#ifdef OS2
|
|
static int
|
|
x_meta3(c)
|
|
int c;
|
|
{
|
|
x_curprefix = 3;
|
|
return KSTD;
|
|
}
|
|
#endif /* OS2 */
|
|
|
|
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) {
|
|
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_t savmode = x_mode(FALSE);
|
|
|
|
(void)ioctl(TTY, TIOCSTI, &ch);
|
|
(void)x_mode(savmode);
|
|
x_redraw(-1);
|
|
#endif
|
|
return KSTD;
|
|
}
|
|
|
|
static char *
|
|
x_mapin(cp)
|
|
const char *cp;
|
|
{
|
|
char *new, *op;
|
|
|
|
op = new = str_save(cp, ATEMP);
|
|
while (*cp) {
|
|
/* XXX -- should handle \^ escape? */
|
|
if (*cp == '^') {
|
|
cp++;
|
|
#ifdef OS2
|
|
if (*cp == '0') /* To define function keys */
|
|
*op++ = 0xE0;
|
|
else
|
|
#endif /* OS2 */
|
|
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;
|
|
|
|
#ifdef OS2
|
|
if (c == 0xE0) {
|
|
*p++ = '^';
|
|
*p++ = '0';
|
|
} else
|
|
#endif /* OS2 */
|
|
if (iscntrl(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));
|
|
#ifdef OS2
|
|
if (prefix == 3)
|
|
shprintf("%s", x_mapout(x_prefix3));
|
|
#endif /* OS2 */
|
|
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;
|
|
}
|
|
|
|
m1 = x_mapin(a1);
|
|
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;
|
|
#ifdef OS2
|
|
else if (x_tab[prefix][key] == XFUNC_meta3)
|
|
prefix = 3;
|
|
#endif /* OS2 */
|
|
else
|
|
break;
|
|
}
|
|
|
|
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;
|
|
m2 = x_mapin(a2);
|
|
sp = str_save(m2, 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()
|
|
{
|
|
register int i, 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"))
|
|
x_usemeta = 1;
|
|
}
|
|
|
|
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 = (char *) ksh_version + 4;
|
|
xend = xep = (char *) 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; i++)
|
|
if (x_ins(words[i]) < 0 || (i < nwords - 1 && x_ins(space) < 0))
|
|
{
|
|
x_e_putc(BEL);
|
|
return KSTD;
|
|
}
|
|
|
|
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 (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 = *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 c;
|
|
|
|
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++;
|
|
c = *rcp;
|
|
*rcp = '\0';
|
|
x_ins(cp);
|
|
*rcp = c;
|
|
}
|
|
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_capitialize(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--) {
|
|
/*
|
|
* fisrt 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(*cp))
|
|
*cp = tolower(*cp);
|
|
} else { /* uppercase, capitialize */
|
|
if (islower(*cp))
|
|
*cp = toupper(*cp);
|
|
}
|
|
cp++;
|
|
}
|
|
/*
|
|
* now for the rest of the word
|
|
*/
|
|
while (cp != xep && !is_mfs(*cp)) {
|
|
if (c == 'U') { /* uppercase */
|
|
if (islower(*cp))
|
|
*cp = toupper(*cp);
|
|
} else { /* lowercase, capitialize */
|
|
if (isupper(*cp))
|
|
*cp = tolower(*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 */
|