commiting:

0000025: vi-editing mode for ash
This commit is contained in:
Paul Fox 2005-08-04 19:04:46 +00:00
parent 8eeb655661
commit 3f11b1bf63
5 changed files with 390 additions and 34 deletions

View File

@ -157,3 +157,5 @@ Tito Ragusa <farmatito@tiscali.it>
devfsd and size optimizations in strings, openvt, chvt, deallocvt, hdparm, devfsd and size optimizations in strings, openvt, chvt, deallocvt, hdparm,
fdformat, lsattr, chattr, id and eject. fdformat, lsattr, chattr, id and eject.
Paul Fox <pgf@foxharp.boston.ma.us>
vi editing mode for ash, various other patches/fixes

View File

@ -201,6 +201,14 @@ config CONFIG_FEATURE_COMMAND_EDITING
help help
Enable command editing in shell. Enable command editing in shell.
config CONFIG_FEATURE_COMMAND_EDITING_VI
bool "vi-style line editing commands"
default n
depends on CONFIG_FEATURE_COMMAND_EDITING
help
Enable vi-style line editing in the shell. This mode can be
turned on and off with "set -o vi" and "set +o vi".
config CONFIG_FEATURE_COMMAND_HISTORY config CONFIG_FEATURE_COMMAND_HISTORY
int "history size" int "history size"
default 15 default 15

View File

@ -1949,19 +1949,21 @@ struct shparam {
#define bflag optlist[11] #define bflag optlist[11]
#define uflag optlist[12] #define uflag optlist[12]
#define qflag optlist[13] #define qflag optlist[13]
#define viflag optlist[14]
#ifdef DEBUG #ifdef DEBUG
#define nolog optlist[14] #define nolog optlist[15]
#define debug optlist[15] #define debug optlist[16]
#define NOPTS 16 #endif
#else
#define NOPTS 14 #ifndef CONFIG_FEATURE_COMMAND_EDITING_VI
#define setvimode(on) viflag = 0 /* forcibly keep the option off */
#endif #endif
/* $NetBSD: options.c,v 1.33 2003/01/22 20:36:04 dsl Exp $ */ /* $NetBSD: options.c,v 1.33 2003/01/22 20:36:04 dsl Exp $ */
static const char *const optletters_optnames[NOPTS] = { static const char *const optletters_optnames[] = {
"e" "errexit", "e" "errexit",
"f" "noglob", "f" "noglob",
"I" "ignoreeof", "I" "ignoreeof",
@ -1976,6 +1978,7 @@ static const char *const optletters_optnames[NOPTS] = {
"b" "notify", "b" "notify",
"u" "nounset", "u" "nounset",
"q" "quietprofile", "q" "quietprofile",
"\0" "vi",
#ifdef DEBUG #ifdef DEBUG
"\0" "nolog", "\0" "nolog",
"\0" "debug", "\0" "debug",
@ -1985,6 +1988,7 @@ static const char *const optletters_optnames[NOPTS] = {
#define optletters(n) optletters_optnames[(n)][0] #define optletters(n) optletters_optnames[(n)][0]
#define optnames(n) (&optletters_optnames[(n)][1]) #define optnames(n) (&optletters_optnames[(n)][1])
#define NOPTS (sizeof(optletters_optnames)/sizeof(optletters_optnames[0]))
static char optlist[NOPTS]; static char optlist[NOPTS];
@ -8862,6 +8866,7 @@ optschanged(void)
#endif #endif
setinteractive(iflag); setinteractive(iflag);
setjobctl(mflag); setjobctl(mflag);
setvimode(viflag);
} }
static inline void static inline void

View File

@ -441,27 +441,61 @@ static void redraw(int y, int back_cursor)
input_backward(back_cursor); input_backward(back_cursor);
} }
/* Delete the char in front of the cursor */ #ifdef CONFIG_FEATURE_COMMAND_EDITING_VI
static void input_delete(void) static char delbuf[BUFSIZ]; /* a place to store deleted characters */
static char *delp = delbuf;
static int newdelflag; /* whether delbuf should be reused yet */
#endif
/* Delete the char in front of the cursor, optionally saving it
* for later putback */
static void input_delete(int save)
{ {
int j = cursor; int j = cursor;
if (j == len) if (j == len)
return; return;
#ifdef CONFIG_FEATURE_COMMAND_EDITING_VI
if (save) {
if (newdelflag) {
delp = delbuf;
newdelflag = 0;
}
if (delp - delbuf < BUFSIZ)
*delp++ = command_ps[j];
}
#endif
strcpy(command_ps + j, command_ps + j + 1); strcpy(command_ps + j, command_ps + j + 1);
len--; len--;
input_end(); /* rewtite new line */ input_end(); /* rewrite new line */
cmdedit_set_out_char(0); /* destroy end char */ cmdedit_set_out_char(0); /* destroy end char */
input_backward(cursor - j); /* back to old pos cursor */ input_backward(cursor - j); /* back to old pos cursor */
} }
#ifdef CONFIG_FEATURE_COMMAND_EDITING_VI
static void put(void)
{
int ocursor, j = delp - delbuf;
if (j == 0)
return;
ocursor = cursor;
/* open hole and then fill it */
memmove(command_ps + cursor + j, command_ps + cursor, len - cursor + 1);
strncpy(command_ps + cursor, delbuf, j);
len += j;
input_end(); /* rewrite new line */
input_backward(cursor-ocursor-j+1); /* at end of new text */
}
#endif
/* Delete the char in back of the cursor */ /* Delete the char in back of the cursor */
static void input_backspace(void) static void input_backspace(void)
{ {
if (cursor > 0) { if (cursor > 0) {
input_backward(1); input_backward(1);
input_delete(); input_delete(0);
} }
} }
@ -473,7 +507,6 @@ static void input_forward(void)
cmdedit_set_out_char(command_ps[cursor + 1]); cmdedit_set_out_char(command_ps[cursor + 1]);
} }
static void cmdedit_setwidth(int w, int redraw_flg) static void cmdedit_setwidth(int w, int redraw_flg)
{ {
cmdedit_termw = cmdedit_prmt_len + 2; cmdedit_termw = cmdedit_prmt_len + 2;
@ -1217,18 +1250,147 @@ enum {
* ESC-h -- Delete forward one word * ESC-h -- Delete forward one word
* CTL-t -- Transpose two characters * CTL-t -- Transpose two characters
* *
* Furthermore, the "vi" command editing keys are not implemented. * Minimalist vi-style command line editing available if configured.
* vi mode implemented 2005 by Paul Fox <pgf@foxharp.boston.ma.us>
* *
*/ */
#ifdef CONFIG_FEATURE_COMMAND_EDITING_VI
static int vi_mode;
void setvimode ( int viflag )
{
vi_mode = viflag;
}
void
vi_Word_motion(char *command, int eat)
{
while (cursor < len && !isspace(command[cursor]))
input_forward();
if (eat) while (cursor < len && isspace(command[cursor]))
input_forward();
}
void
vi_word_motion(char *command, int eat)
{
if (isalnum(command[cursor]) || command[cursor] == '_') {
while (cursor < len &&
(isalnum(command[cursor+1]) ||
command[cursor+1] == '_'))
input_forward();
} else if (ispunct(command[cursor])) {
while (cursor < len &&
(ispunct(command[cursor+1])))
input_forward();
}
if (cursor < len)
input_forward();
if (eat && cursor < len && isspace(command[cursor]))
while (cursor < len && isspace(command[cursor]))
input_forward();
}
void
vi_End_motion(char *command)
{
input_forward();
while (cursor < len && isspace(command[cursor]))
input_forward();
while (cursor < len-1 && !isspace(command[cursor+1]))
input_forward();
}
void
vi_end_motion(char *command)
{
if (cursor >= len-1)
return;
input_forward();
while (cursor < len-1 && isspace(command[cursor]))
input_forward();
if (cursor >= len-1)
return;
if (isalnum(command[cursor]) || command[cursor] == '_') {
while (cursor < len-1 &&
(isalnum(command[cursor+1]) ||
command[cursor+1] == '_'))
input_forward();
} else if (ispunct(command[cursor])) {
while (cursor < len-1 &&
(ispunct(command[cursor+1])))
input_forward();
}
}
void
vi_Back_motion(char *command)
{
while (cursor > 0 && isspace(command[cursor-1]))
input_backward(1);
while (cursor > 0 && !isspace(command[cursor-1]))
input_backward(1);
}
void
vi_back_motion(char *command)
{
if (cursor <= 0)
return;
input_backward(1);
while (cursor > 0 && isspace(command[cursor]))
input_backward(1);
if (cursor <= 0)
return;
if (isalnum(command[cursor]) || command[cursor] == '_') {
while (cursor > 0 &&
(isalnum(command[cursor-1]) ||
command[cursor-1] == '_'))
input_backward(1);
} else if (ispunct(command[cursor])) {
while (cursor > 0 &&
(ispunct(command[cursor-1])))
input_backward(1);
}
}
#endif
/*
* the normal emacs mode and vi's insert mode are the same.
* commands entered when in vi command mode ("escape mode") get
* an extra bit added to distinguish them. this lets them share
* much of the code in the big switch and while loop. i
* experimented with an ugly macro to make the case labels for
* these cases go away entirely when vi mode isn't configured, in
* hopes of letting the jump tables get smaller:
* #define vcase(caselabel) caselabel
* and then
* case CNTRL('A'):
* case vcase(VICMD('0'):)
* but it didn't seem to make any difference in code size,
* and the macro-ized code was too ugly.
*/
#define VI_cmdbit 0x100
#define VICMD(somecmd) ((somecmd)|VI_cmdbit)
/* convert uppercase ascii to equivalent control char, for readability */
#define CNTRL(uc_char) ((uc_char) - 0x40)
int cmdedit_read_input(char *prompt, char command[BUFSIZ]) int cmdedit_read_input(char *prompt, char command[BUFSIZ])
{ {
int break_out = 0; int break_out = 0;
int lastWasTab = FALSE; int lastWasTab = FALSE;
unsigned char c = 0; unsigned char c;
#ifdef CONFIG_FEATURE_COMMAND_EDITING_VI
unsigned int ic, prevc;
int vi_cmdmode = 0;
#endif
/* prepare before init handlers */ /* prepare before init handlers */
cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */ cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
len = 0; len = 0;
@ -1265,22 +1427,38 @@ int cmdedit_read_input(char *prompt, char command[BUFSIZ])
/* if we can't read input then exit */ /* if we can't read input then exit */
goto prepare_to_die; goto prepare_to_die;
switch (c) { #ifdef CONFIG_FEATURE_COMMAND_EDITING_VI
newdelflag = 1;
ic = c;
if (vi_cmdmode)
ic |= VI_cmdbit;
switch (ic)
#else
switch (c)
#endif
{
case '\n': case '\n':
case '\r': case '\r':
case VICMD('\n'):
case VICMD('\r'):
/* Enter */ /* Enter */
goto_new_line(); goto_new_line();
break_out = 1; break_out = 1;
break; break;
case 1: case CNTRL('A'):
case VICMD('0'):
/* Control-a -- Beginning of line */ /* Control-a -- Beginning of line */
input_backward(cursor); input_backward(cursor);
break; break;
case 2: case CNTRL('B'):
case VICMD('h'):
case VICMD('\b'):
case VICMD(DEL):
/* Control-b -- Move back one character */ /* Control-b -- Move back one character */
input_backward(1); input_backward(1);
break; break;
case 3: case CNTRL('C'):
case VICMD(CNTRL('C')):
/* Control-c -- stop gathering input */ /* Control-c -- stop gathering input */
goto_new_line(); goto_new_line();
#ifndef CONFIG_ASH #ifndef CONFIG_ASH
@ -1293,7 +1471,7 @@ int cmdedit_read_input(char *prompt, char command[BUFSIZ])
break_out = -1; /* to control traps */ break_out = -1; /* to control traps */
#endif #endif
break; break;
case 4: case CNTRL('D'):
/* Control-d -- Delete one character, or exit /* Control-d -- Delete one character, or exit
* if the len=0 and no chars to delete */ * if the len=0 and no chars to delete */
if (len == 0) { if (len == 0) {
@ -1310,14 +1488,17 @@ prepare_to_die:
break; break;
#endif #endif
} else { } else {
input_delete(); input_delete(0);
} }
break; break;
case 5: case CNTRL('E'):
case VICMD('$'):
/* Control-e -- End of line */ /* Control-e -- End of line */
input_end(); input_end();
break; break;
case 6: case CNTRL('F'):
case VICMD('l'):
case VICMD(' '):
/* Control-f -- Move forward one character */ /* Control-f -- Move forward one character */
input_forward(); input_forward();
break; break;
@ -1331,24 +1512,29 @@ prepare_to_die:
input_tab(&lastWasTab); input_tab(&lastWasTab);
#endif #endif
break; break;
case 11: case CNTRL('K'):
/* Control-k -- clear to end of line */ /* Control-k -- clear to end of line */
*(command + cursor) = 0; *(command + cursor) = 0;
len = cursor; len = cursor;
printf("\033[J"); printf("\033[J");
break; break;
case 12: case CNTRL('L'):
case VICMD(CNTRL('L')):
/* Control-l -- clear screen */ /* Control-l -- clear screen */
printf("\033[H"); printf("\033[H");
redraw(0, len-cursor); redraw(0, len-cursor);
break; break;
#if MAX_HISTORY >= 1 #if MAX_HISTORY >= 1
case 14: case CNTRL('N'):
case VICMD(CNTRL('N')):
case VICMD('j'):
/* Control-n -- Get next command in history */ /* Control-n -- Get next command in history */
if (get_next_history()) if (get_next_history())
goto rewrite_line; goto rewrite_line;
break; break;
case 16: case CNTRL('P'):
case VICMD(CNTRL('P')):
case VICMD('k'):
/* Control-p -- Get previous command from history */ /* Control-p -- Get previous command from history */
if (cur_history > 0) { if (cur_history > 0) {
get_previous_history(); get_previous_history();
@ -1358,26 +1544,167 @@ prepare_to_die:
} }
break; break;
#endif #endif
case 21: case CNTRL('U'):
case VICMD(CNTRL('U')):
/* Control-U -- Clear line before cursor */ /* Control-U -- Clear line before cursor */
if (cursor) { if (cursor) {
strcpy(command, command + cursor); strcpy(command, command + cursor);
redraw(cmdedit_y, len -= cursor); redraw(cmdedit_y, len -= cursor);
} }
break; break;
case 23: case CNTRL('W'):
case VICMD(CNTRL('W')):
/* Control-W -- Remove the last word */ /* Control-W -- Remove the last word */
while (cursor > 0 && isspace(command[cursor-1])) while (cursor > 0 && isspace(command[cursor-1]))
input_backspace(); input_backspace();
while (cursor > 0 &&!isspace(command[cursor-1])) while (cursor > 0 &&!isspace(command[cursor-1]))
input_backspace(); input_backspace();
break; break;
#if CONFIG_FEATURE_COMMAND_EDITING_VI
case VICMD('i'):
vi_cmdmode = 0;
break;
case VICMD('I'):
input_backward(cursor);
vi_cmdmode = 0;
break;
case VICMD('a'):
input_forward();
vi_cmdmode = 0;
break;
case VICMD('A'):
input_end();
vi_cmdmode = 0;
break;
case VICMD('x'):
input_delete(1);
break;
case VICMD('X'):
if (cursor > 0) {
input_backward(1);
input_delete(1);
}
break;
case VICMD('W'):
vi_Word_motion(command, 1);
break;
case VICMD('w'):
vi_word_motion(command, 1);
break;
case VICMD('E'):
vi_End_motion(command);
break;
case VICMD('e'):
vi_end_motion(command);
break;
case VICMD('B'):
vi_Back_motion(command);
break;
case VICMD('b'):
vi_back_motion(command);
break;
case VICMD('C'):
vi_cmdmode = 0;
/* fall through */
case VICMD('D'):
goto clear_to_eol;
case VICMD('c'):
vi_cmdmode = 0;
/* fall through */
case VICMD('d'):
{
int nc, sc;
sc = cursor;
prevc = ic;
if (safe_read(0, &c, 1) < 1)
goto prepare_to_die;
if (c == (prevc & 0xff)) {
/* "cc", "dd" */
input_backward(cursor);
goto clear_to_eol;
break;
}
switch(c) {
case 'w':
case 'W':
case 'e':
case 'E':
switch (c) {
case 'w': /* "dw", "cw" */
vi_word_motion(command, vi_cmdmode);
break;
case 'W': /* 'dW', 'cW' */
vi_Word_motion(command, vi_cmdmode);
break;
case 'e': /* 'de', 'ce' */
vi_end_motion(command);
input_forward();
break;
case 'E': /* 'dE', 'cE' */
vi_End_motion(command);
input_forward();
break;
}
nc = cursor;
input_backward(cursor - sc);
while (nc-- > cursor)
input_delete(1);
break;
case 'b': /* "db", "cb" */
case 'B': /* implemented as B */
if (c == 'b')
vi_back_motion(command);
else
vi_Back_motion(command);
while (sc-- > cursor)
input_delete(1);
break;
case ' ': /* "d ", "c " */
input_delete(1);
break;
case '$': /* "d$", "c$" */
clear_to_eol:
while (cursor < len)
input_delete(1);
break;
}
}
break;
case VICMD('p'):
input_forward();
/* fallthrough */
case VICMD('P'):
put();
break;
case VICMD('r'):
if (safe_read(0, &c, 1) < 1)
goto prepare_to_die;
if (c == 0)
beep();
else {
*(command + cursor) = c;
putchar(c);
putchar('\b');
}
break;
#endif /* CONFIG_FEATURE_COMMAND_EDITING_VI */
case ESC:{ case ESC:{
#if CONFIG_FEATURE_COMMAND_EDITING_VI
if (vi_mode) {
/* ESC: insert mode --> command mode */
vi_cmdmode = 1;
input_backward(1);
break;
}
#endif
/* escape sequence follows */ /* escape sequence follows */
if (safe_read(0, &c, 1) < 1) if (safe_read(0, &c, 1) < 1)
goto prepare_to_die; goto prepare_to_die;
/* different vt100 emulations */ /* different vt100 emulations */
if (c == '[' || c == 'O') { if (c == '[' || c == 'O') {
case VICMD('['):
case VICMD('O'):
if (safe_read(0, &c, 1) < 1) if (safe_read(0, &c, 1) < 1)
goto prepare_to_die; goto prepare_to_die;
} }
@ -1409,13 +1736,17 @@ prepare_to_die:
case 'B': case 'B':
/* Down Arrow -- Get next command in history */ /* Down Arrow -- Get next command in history */
if (!get_next_history()) if (!get_next_history())
break; break;
/* Rewrite the line with the selected history item */ /* Rewrite the line with the selected history item */
rewrite_line: rewrite_line:
/* change command */ /* change command */
len = strlen(strcpy(command, history[cur_history])); len = strlen(strcpy(command, history[cur_history]));
/* redraw and go to end line */ /* redraw and go to eol (bol, in vi */
#if CONFIG_FEATURE_COMMAND_EDITING_VI
redraw(cmdedit_y, vi_mode ? 9999:0);
#else
redraw(cmdedit_y, 0); redraw(cmdedit_y, 0);
#endif
break; break;
#endif #endif
case 'C': case 'C':
@ -1428,7 +1759,7 @@ rewrite_line:
break; break;
case '3': case '3':
/* Delete */ /* Delete */
input_delete(); input_delete(0);
break; break;
case '1': case '1':
case 'H': case 'H':
@ -1450,7 +1781,7 @@ rewrite_line:
default: /* If it's regular input, do the normal thing */ default: /* If it's regular input, do the normal thing */
#ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
/* Control-V -- Add non-printable symbol */ /* Control-V -- Add non-printable symbol */
if (c == 22) { if (c == CNTRL('V')) {
if (safe_read(0, &c, 1) < 1) if (safe_read(0, &c, 1) < 1)
goto prepare_to_die; goto prepare_to_die;
if (c == 0) { if (c == 0) {
@ -1459,8 +1790,14 @@ rewrite_line:
} }
} else } else
#endif #endif
if (!Isprint(c)) /* Skip non-printable characters */ {
break; #if CONFIG_FEATURE_COMMAND_EDITING_VI
if (vi_cmdmode) /* don't self-insert */
break;
#endif
if (!Isprint(c)) /* Skip non-printable characters */
break;
}
if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */ if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
break; break;

View File

@ -12,4 +12,8 @@ void load_history ( const char *fromfile );
void save_history ( const char *tofile ); void save_history ( const char *tofile );
#endif #endif
#if CONFIG_FEATURE_COMMAND_EDITING_VI
void setvimode ( int viflag );
#endif
#endif /* CMDEDIT_H */ #endif /* CMDEDIT_H */