[BusyBox] [PATCH] vi-editing mode for ash
Paul Fox
pgf at brightstareng.com
Sat Nov 13 21:48:22 UTC 2004
hi --
as a long time vi user, and an editor curmudgeon in general, i
missed having a vi-mode in ash when doing development on or when
using a busybox-based system, so i finally implemented it.
it's not perfect, of course: no undo, no searching, the 'w' and
'b' commands are actually 'W' and 'B' (vi users will know what i
mean), but it does have 'p', so you can put back what you've
deleted (which was necessary to be able to enter "xp", something
my typing demands that i do pretty often :-).
it builds on top of emacs-mode -- so most (all?) of the emacs
commands (e.g. ^A, ^F, ^B, ^N, ^P) still work while you're
"inserting" in vi-mode. this is either a bug or a feature, but
mostly it's just a fallout of the implementation.
turning on CONFIG_FEATURE_COMMAND_EDITING_VI adds another couple
of kbytes on an x86 system (which is roughly 25% more than
emacs-mode by itself).
with luck, i'm not the only person to think this is useful or
worthwhile, and hopefully someone else can try it and offer a
comment.
anyway, hope it can be included... just trying to give back to
an excellent project!
(the patch is against current CVS)
paul, pgf at brightstareng.com, pgf at foxharp.boston.ma.us
------------------------------
Index: shell/Config.in
===================================================================
RCS file: /var/cvs/busybox/shell/Config.in,v
retrieving revision 1.18
diff -u -r1.18 Config.in
--- shell/Config.in 24 Sep 2004 09:09:44 -0000 1.18
+++ shell/Config.in 13 Nov 2004 18:56:51 -0000
@@ -190,6 +190,14 @@
help
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
int "history size"
default 15
Index: shell/ash.c
===================================================================
RCS file: /var/cvs/busybox/shell/ash.c,v
retrieving revision 1.108
diff -u -r1.108 ash.c
--- shell/ash.c 8 Oct 2004 09:43:34 -0000 1.108
+++ shell/ash.c 13 Nov 2004 18:56:53 -0000
@@ -1948,19 +1948,21 @@
#define bflag optlist[11]
#define uflag optlist[12]
#define qflag optlist[13]
+#define viflag optlist[14]
#ifdef DEBUG
-#define nolog optlist[14]
-#define debug optlist[15]
-#define NOPTS 16
-#else
-#define NOPTS 14
+#define nolog optlist[15]
+#define debug optlist[16]
+#endif
+
+#ifndef CONFIG_FEATURE_COMMAND_EDITING_VI
+#define setvimode(on) viflag = 0 /* forcibly keep the option off */
#endif
/* $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",
"f" "noglob",
"I" "ignoreeof",
@@ -1975,6 +1977,7 @@
"b" "notify",
"u" "nounset",
"q" "quietprofile",
+ "\0" "vi",
#ifdef DEBUG
"\0" "nolog",
"\0" "debug",
@@ -1984,6 +1987,7 @@
#define optletters(n) optletters_optnames[(n)][0]
#define optnames(n) (&optletters_optnames[(n)][1])
+#define NOPTS (sizeof(optletters_optnames)/sizeof(optletters_optnames[0]))
static char optlist[NOPTS];
@@ -8856,6 +8860,7 @@
#endif
setinteractive(iflag);
setjobctl(mflag);
+ setvimode(viflag);
}
static inline void
Index: shell/cmdedit.c
===================================================================
RCS file: /var/cvs/busybox/shell/cmdedit.c,v
retrieving revision 1.93
diff -u -r1.93 cmdedit.c
--- shell/cmdedit.c 19 Aug 2004 18:22:13 -0000 1.93
+++ shell/cmdedit.c 13 Nov 2004 18:56:54 -0000
@@ -441,27 +441,61 @@
input_backward(back_cursor);
}
-/* Delete the char in front of the cursor */
-static void input_delete(void)
+#ifdef CONFIG_FEATURE_COMMAND_EDITING_VI
+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;
if (j == len)
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);
len--;
- input_end(); /* rewtite new line */
+ input_end(); /* rewrite new line */
cmdedit_set_out_char(0); /* destroy end char */
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 */
static void input_backspace(void)
{
if (cursor > 0) {
input_backward(1);
- input_delete();
+ input_delete(0);
}
}
@@ -473,7 +507,6 @@
cmdedit_set_out_char(command_ps[cursor + 1]);
}
-
static void cmdedit_setwidth(int w, int redraw_flg)
{
cmdedit_termw = cmdedit_prmt_len + 2;
@@ -1217,18 +1250,54 @@
* ESC-h -- Delete forward one word
* 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 by Paul Fox <pgf at foxharp.boston.ma.us>
*
*/
+#ifdef CONFIG_FEATURE_COMMAND_EDITING_VI
+static int vi_mode;
+
+void setvimode ( int viflag )
+{
+ vi_mode = viflag;
+}
+
+#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 break_out = 0;
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 */
cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
len = 0;
@@ -1265,22 +1334,38 @@
/* if we can't read input then exit */
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 '\r':
+ case VICMD('\n'):
+ case VICMD('\r'):
/* Enter */
goto_new_line();
break_out = 1;
break;
- case 1:
+ case CNTRL('A'):
+ case VICMD('0'):
/* Control-a -- Beginning of line */
input_backward(cursor);
break;
- case 2:
+ case CNTRL('B'):
+ case VICMD('h'):
+ case VICMD('\b'):
+ case VICMD(DEL):
/* Control-b -- Move back one character */
input_backward(1);
break;
- case 3:
+ case CNTRL('C'):
+ case VICMD(CNTRL('C')):
/* Control-c -- stop gathering input */
goto_new_line();
#ifndef CONFIG_ASH
@@ -1293,7 +1378,7 @@
break_out = -1; /* to control traps */
#endif
break;
- case 4:
+ case CNTRL('D'):
/* Control-d -- Delete one character, or exit
* if the len=0 and no chars to delete */
if (len == 0) {
@@ -1310,14 +1395,17 @@
break;
#endif
} else {
- input_delete();
+ input_delete(0);
}
break;
- case 5:
+ case CNTRL('E'):
+ case VICMD('$'):
/* Control-e -- End of line */
input_end();
break;
- case 6:
+ case CNTRL('F'):
+ case VICMD('l'):
+ case VICMD(' '):
/* Control-f -- Move forward one character */
input_forward();
break;
@@ -1331,24 +1419,29 @@
input_tab(&lastWasTab);
#endif
break;
- case 11:
+ case CNTRL('K'):
/* Control-k -- clear to end of line */
*(command + cursor) = 0;
len = cursor;
printf("\033[J");
break;
- case 12:
+ case CNTRL('L'):
+ case VICMD(CNTRL('L')):
/* Control-l -- clear screen */
printf("\033[H");
redraw(0, len-cursor);
break;
#if MAX_HISTORY >= 1
- case 14:
+ case CNTRL('N'):
+ case VICMD(CNTRL('N')):
+ case VICMD('j'):
/* Control-n -- Get next command in history */
if (get_next_history())
goto rewrite_line;
break;
- case 16:
+ case CNTRL('P'):
+ case VICMD(CNTRL('P')):
+ case VICMD('k'):
/* Control-p -- Get previous command from history */
if (cur_history > 0) {
get_previous_history();
@@ -1358,26 +1451,160 @@
}
break;
#endif
- case 21:
+ case CNTRL('U'):
+ case VICMD(CNTRL('U')):
/* Control-U -- Clear line before cursor */
if (cursor) {
strcpy(command, command + cursor);
redraw(cmdedit_y, len -= cursor);
}
break;
- case 23:
+ case CNTRL('W'):
+ case VICMD(CNTRL('W')):
/* Control-W -- Remove the last word */
while (cursor > 0 && isspace(command[cursor-1]))
input_backspace();
while (cursor > 0 &&!isspace(command[cursor-1]))
input_backspace();
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'): /* implemented as W for now */
+ case VICMD('W'):
+ while (cursor < len && !isspace(command[cursor]))
+ input_forward();
+ while (cursor < len && isspace(command[cursor]))
+ input_forward();
+ break;
+ case VICMD('e'): /* implemented as E for now */
+ case VICMD('E'):
+ input_forward();
+ while (cursor < len && isspace(command[cursor]))
+ input_forward();
+ while (cursor < len-1 && !isspace(command[cursor+1]))
+ input_forward();
+ break;
+ case VICMD('b'): /* implemented as B for now */
+ case VICMD('B'):
+ while (cursor > 0 && isspace(command[cursor-1]))
+ input_backward(1);
+ while (cursor > 0 && !isspace(command[cursor-1]))
+ input_backward(1);
+ 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'):
+ 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': /* "dw", "cw" */
+ case 'W': /* implemented as W */
+ while (cursor < len && !isspace(command[cursor]))
+ input_delete(1);
+ while (cursor < len && isspace(command[cursor]))
+ input_delete(1);
+ break;
+ case 'e': /* "de", "ce" */
+ case 'E': /* implemented as E */
+ input_delete(1);
+ while (cursor < len && isspace(command[cursor]))
+ input_delete(1);
+ while (cursor < len-1 && !isspace(command[cursor+1]))
+ input_delete(1);
+ input_delete(1);
+ break;
+ case 'b': /* "db", "cb" */
+ case 'B': /* implemented as B */
+ { int sc = cursor;
+ while (cursor > 0 && isspace(command[cursor-1]))
+ input_backward(1);
+ while (cursor > 0 && !isspace(command[cursor-1]))
+ input_backward(1);
+ 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:{
+#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 */
if (safe_read(0, &c, 1) < 1)
goto prepare_to_die;
/* different vt100 emulations */
if (c == '[' || c == 'O') {
+ case VICMD('['):
+ case VICMD('O'):
if (safe_read(0, &c, 1) < 1)
goto prepare_to_die;
}
@@ -1409,13 +1636,17 @@
case 'B':
/* Down Arrow -- Get next command in history */
if (!get_next_history())
- break;
+ break;
/* Rewrite the line with the selected history item */
rewrite_line:
/* change command */
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);
+#endif
break;
#endif
case 'C':
@@ -1428,7 +1659,7 @@
break;
case '3':
/* Delete */
- input_delete();
+ input_delete(0);
break;
case '1':
case 'H':
@@ -1450,7 +1681,7 @@
default: /* If it's regular input, do the normal thing */
#ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
/* Control-V -- Add non-printable symbol */
- if (c == 22) {
+ if (c == CNTRL('V')) {
if (safe_read(0, &c, 1) < 1)
goto prepare_to_die;
if (c == 0) {
@@ -1459,8 +1690,14 @@
}
} else
#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 */
break;
Index: shell/cmdedit.h
===================================================================
RCS file: /var/cvs/busybox/shell/cmdedit.h,v
retrieving revision 1.15
diff -u -r1.15 cmdedit.h
--- shell/cmdedit.h 14 Jan 2004 09:34:51 -0000 1.15
+++ shell/cmdedit.h 13 Nov 2004 18:56:54 -0000
@@ -12,4 +12,8 @@
void save_history ( const char *tofile );
#endif
+#if CONFIG_FEATURE_COMMAND_EDITING_VI
+void setvimode ( int viflag );
+#endif
+
#endif /* CMDEDIT_H */
-----------end of patch--------
=---------------------
paul fox, pgf at foxharp.boston.ma.us
More information about the busybox
mailing list