commiting:
authorPaul Fox <pgf@brightstareng.com>
Thu, 4 Aug 2005 19:04:46 +0000 (19:04 -0000)
committerPaul Fox <pgf@brightstareng.com>
Thu, 4 Aug 2005 19:04:46 +0000 (19:04 -0000)
    0000025: vi-editing mode for ash

AUTHORS
shell/Config.in
shell/ash.c
shell/cmdedit.c
shell/cmdedit.h

diff --git a/AUTHORS b/AUTHORS
index 24501f1..3582e0b 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -157,3 +157,5 @@ Tito Ragusa <farmatito@tiscali.it>
     devfsd and size optimizations in strings, openvt, chvt, deallocvt, hdparm,
     fdformat, lsattr, chattr, id and eject.
 
+Paul Fox <pgf@foxharp.boston.ma.us>
+    vi editing mode for ash, various other patches/fixes
index 1a3c388..0d39e5b 100644 (file)
@@ -201,6 +201,14 @@ config CONFIG_FEATURE_COMMAND_EDITING
        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 7832049..7f77594 100644 (file)
@@ -1949,19 +1949,21 @@ struct shparam {
 #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",
@@ -1976,6 +1978,7 @@ static const char *const optletters_optnames[NOPTS] = {
        "b"   "notify",
        "u"   "nounset",
        "q"   "quietprofile",
+       "\0"  "vi",
 #ifdef DEBUG
        "\0"  "nolog",
        "\0"  "debug",
@@ -1985,6 +1988,7 @@ static const char *const optletters_optnames[NOPTS] = {
 #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];
 
@@ -8862,6 +8866,7 @@ optschanged(void)
 #endif
        setinteractive(iflag);
        setjobctl(mflag);
+       setvimode(viflag);
 }
 
 static inline void
index 3380dff..c67283f 100644 (file)
@@ -441,27 +441,61 @@ static void redraw(int y, int back_cursor)
        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 @@ static void input_forward(void)
                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,147 @@ enum {
  * 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 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 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 +1427,38 @@ int cmdedit_read_input(char *prompt, char command[BUFSIZ])
                        /* 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 +1471,7 @@ int cmdedit_read_input(char *prompt, char command[BUFSIZ])
                        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 +1488,17 @@ prepare_to_die:
                                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 +1512,29 @@ prepare_to_die:
                        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 +1544,167 @@ prepare_to_die:
                        }
                        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'):
+                       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:{
+#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 +1736,17 @@ prepare_to_die:
                        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 +1759,7 @@ rewrite_line:
                                break;
                        case '3':
                                /* Delete */
-                               input_delete();
+                               input_delete(0);
                                break;
                        case '1':
                        case 'H':
@@ -1450,7 +1781,7 @@ rewrite_line:
                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 +1790,14 @@ rewrite_line:
                                }
                        } 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 4c0c09d..f8482f4 100644 (file)
@@ -12,4 +12,8 @@ void    load_history ( const char *fromfile );
 void    save_history ( const char *tofile );
 #endif
 
+#if CONFIG_FEATURE_COMMAND_EDITING_VI
+void   setvimode ( int viflag );
+#endif
+
 #endif /* CMDEDIT_H */