patch 8.2.4252: generating the normal command table at runtime is inefficient

Problem:    Generating the normal command table at runtime is inefficient.
Solution:   Generate the table with a Vim script and put it in a header file.
            (Yegappan Lakshmanan, closes #9648)
diff --git a/src/normal.c b/src/normal.c
index 412700a..2e8521c 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -19,7 +19,6 @@
 #ifdef FEAT_EVAL
 static void	set_vcount_ca(cmdarg_T *cap, int *set_prevcount);
 #endif
-static int	nv_compare(const void *s1, const void *s2);
 static void	unshift_special(cmdarg_T *cap);
 #ifdef FEAT_CMDL_INFO
 static void	del_from_showcmd(int);
@@ -128,6 +127,34 @@
 #endif
 static void	nv_cursorhold(cmdarg_T *cap);
 
+#ifdef FEAT_GUI
+#define NV_VER_SCROLLBAR	nv_ver_scrollbar
+#define NV_HOR_SCROLLBAR	nv_hor_scrollbar
+#else
+#define NV_VER_SCROLLBAR nv_error
+#define NV_HOR_SCROLLBAR nv_error
+#endif
+
+#ifdef FEAT_GUI_TABLINE
+#define NV_TABLINE	nv_tabline
+#define NV_TABMENU	nv_tabmenu
+#else
+#define NV_TABLINE	nv_error
+#define NV_TABMENU	nv_error
+#endif
+
+#ifdef FEAT_NETBEANS_INTG
+#define NV_NBCMD	nv_nbcmd
+#else
+#define NV_NBCMD	nv_error
+#endif
+
+#ifdef FEAT_DND
+#define NV_DROP		nv_drop
+#else
+#define NV_DROP		nv_error
+#endif
+
 /*
  * Function to be called for a Normal or Visual mode command.
  * The argument is a cmdarg_T.
@@ -159,8 +186,14 @@
 
 /*
  * This table contains one entry for every Normal or Visual mode command.
- * The order doesn't matter, init_normal_cmds() will create a sorted index.
+ * The order doesn't matter, this will be sorted by the create_nvcmdidx.vim
+ * script to generate the nv_cmd_idx[] lookup table.
  * It is faster when all keys from zero to '~' are present.
+ *
+ * After changing the "nv_cmds" table:
+ * 1. Build Vim with "make"
+ * 2. Run "make nvcmdidxs" to re-generate the nv_cmdidxs.h file.
+ * 3. Build Vim with "make" to use the newly generated index table.
  */
 static const struct nv_cmd
 {
@@ -193,8 +226,6 @@
     {Ctrl_T,	nv_tagpop,	NV_NCW,			0},
     {Ctrl_U,	nv_halfpage,	0,			0},
     {Ctrl_V,	nv_visual,	0,			FALSE},
-    {'V',	nv_visual,	0,			FALSE},
-    {'v',	nv_visual,	0,			FALSE},
     {Ctrl_W,	nv_window,	0,			0},
     {Ctrl_X,	nv_addsub,	0,			0},
     {Ctrl_Y,	nv_scroll_line,	0,			FALSE},
@@ -258,6 +289,7 @@
     {'S',	nv_subst,	NV_KEEPREG,		0},
     {'T',	nv_csearch,	NV_NCH_ALW|NV_LANG,	BACKWARD},
     {'U',	nv_Undo,	0,			0},
+    {'V',	nv_visual,	0,			FALSE},
     {'W',	nv_wordcmd,	0,			TRUE},
     {'X',	nv_abbrev,	NV_KEEPREG,		0},
     {'Y',	nv_abbrev,	NV_KEEPREG,		0},
@@ -289,6 +321,7 @@
     {'s',	nv_subst,	NV_KEEPREG,		0},
     {'t',	nv_csearch,	NV_NCH_ALW|NV_LANG,	FORWARD},
     {'u',	nv_undo,	0,			0},
+    {'v',	nv_visual,	0,			FALSE},
     {'w',	nv_wordcmd,	0,			FALSE},
     {'x',	nv_abbrev,	NV_KEEPREG,		0},
     {'y',	nv_operator,	0,			0},
@@ -356,20 +389,12 @@
     {K_F1,	nv_help,	NV_NCW,			0},
     {K_XF1,	nv_help,	NV_NCW,			0},
     {K_SELECT,	nv_select,	0,			0},
-#ifdef FEAT_GUI
-    {K_VER_SCROLLBAR, nv_ver_scrollbar, 0,		0},
-    {K_HOR_SCROLLBAR, nv_hor_scrollbar, 0,		0},
-#endif
-#ifdef FEAT_GUI_TABLINE
-    {K_TABLINE, nv_tabline,	0,			0},
-    {K_TABMENU, nv_tabmenu,	0,			0},
-#endif
-#ifdef FEAT_NETBEANS_INTG
-    {K_F21,	nv_nbcmd,	NV_NCH_ALW,		0},
-#endif
-#ifdef FEAT_DND
-    {K_DROP,	nv_drop,	NV_STS,			0},
-#endif
+    {K_VER_SCROLLBAR, NV_VER_SCROLLBAR, 0,		0},
+    {K_HOR_SCROLLBAR, NV_HOR_SCROLLBAR, 0,		0},
+    {K_TABLINE, NV_TABLINE,	0,			0},
+    {K_TABMENU, NV_TABMENU,	0,			0},
+    {K_F21,	NV_NBCMD,	NV_NCH_ALW,		0},
+    {K_DROP,	NV_DROP,	NV_STS,			0},
     {K_CURSORHOLD, nv_cursorhold, NV_KEEPREG,		0},
     {K_PS,	nv_edit,	0,			0},
     {K_COMMAND,	nv_colon,	0,			0},
@@ -379,55 +404,42 @@
 // Number of commands in nv_cmds[].
 #define NV_CMDS_SIZE ARRAY_LENGTH(nv_cmds)
 
-#ifndef PROTO  // cproto doesn't like this
-// Sorted index of commands in nv_cmds[].
-static short nv_cmd_idx[NV_CMDS_SIZE];
-#endif
+// Include the lookuptable generated by create_nvcmdidx.vim.
+#include "nv_cmdidxs.h"
 
-// The highest index for which
-// nv_cmds[idx].cmd_char == nv_cmd_idx[nv_cmds[idx].cmd_char]
-static int nv_max_linear;
-
+#if defined(FEAT_EVAL) || defined(PROTO)
 /*
- * Compare functions for qsort() below, that checks the command character
- * through the index in nv_cmd_idx[].
- */
-    static int
-nv_compare(const void *s1, const void *s2)
-{
-    int		c1, c2;
-
-    // The commands are sorted on absolute value.
-    c1 = nv_cmds[*(const short *)s1].cmd_char;
-    c2 = nv_cmds[*(const short *)s2].cmd_char;
-    if (c1 < 0)
-	c1 = -c1;
-    if (c2 < 0)
-	c2 = -c2;
-    return c1 - c2;
-}
-
-/*
- * Initialize the nv_cmd_idx[] table.
+ * Return the command character for the given command index. This function is
+ * used to auto-generate nv_cmd_idx[].
  */
     void
-init_normal_cmds(void)
+f_internal_get_nv_cmdchar(typval_T *argvars, typval_T *rettv)
 {
-    int		i;
+    int	idx;
+    int	cmd_char;
 
-    // Fill the index table with a one to one relation.
-    for (i = 0; i < (int)NV_CMDS_SIZE; ++i)
-	nv_cmd_idx[i] = i;
+    rettv->v_type = VAR_NUMBER;
+    rettv->vval.v_number = -1;
 
-    // Sort the commands by the command character.
-    qsort((void *)&nv_cmd_idx, (size_t)NV_CMDS_SIZE, sizeof(short), nv_compare);
+    if (check_for_number_arg(argvars, 0) == FAIL)
+	return;
 
-    // Find the first entry that can't be indexed by the command character.
-    for (i = 0; i < (int)NV_CMDS_SIZE; ++i)
-	if (i != nv_cmds[nv_cmd_idx[i]].cmd_char)
-	    break;
-    nv_max_linear = i - 1;
+    idx = tv_get_number(&argvars[0]);
+    if (idx < 0 || idx >= (int)NV_CMDS_SIZE)
+	return;
+
+    cmd_char = nv_cmds[idx].cmd_char;
+
+    // We use the absolute value of the character.  Special keys have a
+    // negative value, but are sorted on their absolute value.
+    if (cmd_char < 0)
+	cmd_char = -cmd_char;
+
+    rettv->vval.v_number = cmd_char;
+
+    return;
 }
+#endif
 
 /*
  * Search for a command in the commands table.