nconfig: add search support

Remove the old hotkeys feature, and replace it by an interactive string
search.
From nconfig help:

Searching: pressing '/' triggers interactive search mode.
           nconfig performs a case insensitive search for the string
           in the menu prompts (no regex support).
           Pressing the up/down keys highlights the previous/next
           matching item. Backspace removes one character from the
           match string. Pressing either '/' again or ESC exits
           search mode. All other keys behave normally.

Miscellaneous other changes (including Rundy's and Justin's input).

Signed-off-by: Nir Tzachar <nir.tzachar@gmail.com>
Acked-by: Sam Ravnborg <sam@ravnborg.org>
Signed-off-by: Michal Marek <mmarek@suse.cz>
diff --git a/scripts/kconfig/nconf.c b/scripts/kconfig/nconf.c
index 2ba71bc..18a215d 100644
--- a/scripts/kconfig/nconf.c
+++ b/scripts/kconfig/nconf.c
@@ -5,9 +5,12 @@
  * Derived from menuconfig.
  *
  */
+#define _GNU_SOURCE
+#include <string.h>
 #define LKC_DIRECT_LINK
 #include "lkc.h"
 #include "nconf.h"
+#include <ctype.h>
 
 static const char nconf_readme[] = N_(
 "Overview\n"
@@ -23,7 +26,7 @@
 "  < > can be built in, modularized or removed\n"
 "  { } can be built in or modularized (selected by other feature)\n"
 "  - - are selected by other feature,\n"
-"  XXX cannot be selected. use Symbol Info to find out why,\n"
+"  XXX cannot be selected. Use Symbol Info to find out why,\n"
 "while *, M or whitespace inside braces means to build in, build as\n"
 "a module or to exclude the feature respectively.\n"
 "\n"
@@ -41,9 +44,13 @@
 "   pressing <Enter> of <right-arrow>. Use <Esc> or <left-arrow> to go back.\n"
 "   Submenus are designated by \"--->\".\n"
 "\n"
-"   Shortcut: Press the option's highlighted letter (hotkey).\n"
-"             Pressing a hotkey more than once will sequence\n"
-"             through all visible items which use that hotkey.\n"
+"   Searching: pressing '/' triggers interactive search mode.\n"
+"              nconfig performs a case insensitive search for the string\n"
+"              in the menu prompts (no regex support).\n"
+"              Pressing the up/down keys highlights the previous/next\n"
+"              matching item. Backspace removes one character from the\n"
+"              match string. Pressing either '/' again or ESC exits\n"
+"              search mode. All other keys behave normally.\n"
 "\n"
 "   You may also use the <PAGE UP> and <PAGE DOWN> keys to scroll\n"
 "   unseen options into view.\n"
@@ -141,21 +148,21 @@
 " <Enter> or <right-arrow> selects submenus --->.\n"
 " Capital Letters are hotkeys.\n"
 " Pressing <Y> includes, <N> excludes, <M> modularizes features.\n"
-" Pressing SpaceBar toggles between the above options\n"
-" Press <Esc> or <left-arrow> to go back one menu, \n"
+" Pressing SpaceBar toggles between the above options.\n"
+" Press <Esc> or <left-arrow> to go back one menu,\n"
 " <?> or <h> for Help, </> for Search.\n"
-" <1> is interchangable with <F1>, <2> with <F2>, etc.\n"
+" <1> is interchangeable with <F1>, <2> with <F2>, etc.\n"
 " Legend: [*] built-in  [ ] excluded  <M> module  < > module capable.\n"
-" <Esc> always leaves the current window\n"),
+" <Esc> always leaves the current window.\n"),
 menu_instructions[] = N_(
 " Arrow keys navigate the menu.\n"
 " <Enter> or <right-arrow> selects submenus --->.\n"
 " Capital Letters are hotkeys.\n"
 " Pressing <Y> includes, <N> excludes, <M> modularizes features.\n"
 " Pressing SpaceBar toggles between the above options\n"
-" Press <Esc>, <F3> or <left-arrow> to go back one menu, \n"
+" Press <Esc>, <F5> or <left-arrow> to go back one menu,\n"
 " <?>, <F1> or <h> for Help, </> for Search.\n"
-" <1> is interchangable with <F1>, <2> with <F2>, etc.\n"
+" <1> is interchangeable with <F1>, <2> with <F2>, etc.\n"
 " Legend: [*] built-in  [ ] excluded  <M> module  < > module capable.\n"
 " <Esc> always leaves the current window\n"),
 radiolist_instructions[] = N_(
@@ -252,7 +259,6 @@
 	char str[256];
 	char tag;
 	void *usrptr;
-	int is_hot;
 	int is_visible;
 };
 
@@ -275,14 +281,6 @@
 static int global_exit;
 /* the currently selected button */
 const char *current_instructions = menu_instructions;
-/* this array is used to implement hot keys. it is updated in item_make and
- * resetted in clean_items. It would be better to use a hash, but lets keep it
- * simple... */
-#define MAX_SAME_KEY MAX_MENU_ITEMS
-struct {
-	int count;
-	int ptrs[MAX_MENU_ITEMS];
-} hotkeys[1<<(sizeof(char)*8)];
 
 static void conf(struct menu *menu);
 static void conf_choice(struct menu *menu);
@@ -292,6 +290,7 @@
 static void show_help(struct menu *menu);
 static int do_exit(void);
 static void setup_windows(void);
+static void search_conf(void);
 
 typedef void (*function_key_handler_t)(int *key, struct menu *menu);
 static void handle_f1(int *key, struct menu *current_item);
@@ -302,6 +301,7 @@
 static void handle_f6(int *key, struct menu *current_item);
 static void handle_f7(int *key, struct menu *current_item);
 static void handle_f8(int *key, struct menu *current_item);
+static void handle_f9(int *key, struct menu *current_item);
 
 struct function_keys {
 	const char *key_str;
@@ -310,7 +310,7 @@
 	function_key_handler_t handler;
 };
 
-static const int function_keys_num = 8;
+static const int function_keys_num = 9;
 struct function_keys function_keys[] = {
 	{
 		.key_str = "F1",
@@ -320,13 +320,13 @@
 	},
 	{
 		.key_str = "F2",
-		.func = "Symbol Info",
+		.func = "Sym Info",
 		.key = F_SYMBOL,
 		.handler = handle_f2,
 	},
 	{
 		.key_str = "F3",
-		.func = "Instructions",
+		.func = "Insts",
 		.key = F_INSTS,
 		.handler = handle_f3,
 	},
@@ -356,9 +356,15 @@
 	},
 	{
 		.key_str = "F8",
+		.func = "Sym Search",
+		.key = F_SEARCH,
+		.handler = handle_f8,
+	},
+	{
+		.key_str = "F9",
 		.func = "Exit",
 		.key = F_EXIT,
-		.handler = handle_f8,
+		.handler = handle_f9,
 	},
 };
 
@@ -444,9 +450,16 @@
 	return;
 }
 
-/* exit */
+/* search */
 static void handle_f8(int *key, struct menu *current_item)
 {
+	search_conf();
+	return;
+}
+
+/* exit */
+static void handle_f9(int *key, struct menu *current_item)
+{
 	do_exit();
 	return;
 }
@@ -479,110 +492,44 @@
 		free_item(curses_menu_items[i]);
 	bzero(curses_menu_items, sizeof(curses_menu_items));
 	bzero(k_menu_items, sizeof(k_menu_items));
-	bzero(hotkeys, sizeof(hotkeys));
 	items_num = 0;
 }
 
-/* return the index of the next hot item, or -1 if no such item exists */
-static int get_next_hot(int c)
+typedef enum {MATCH_TINKER_PATTERN_UP, MATCH_TINKER_PATTERN_DOWN,
+	FIND_NEXT_MATCH_DOWN, FIND_NEXT_MATCH_UP} match_f;
+
+/* return the index of the matched item, or -1 if no such item exists */
+static int get_mext_match(const char *match_str, match_f flag)
 {
-	static int hot_index;
-	static int hot_char;
+	int match_start = item_index(current_item(curses_menu));
+	int index;
 
-	if (c < 0 || c > 255 || hotkeys[c].count <= 0)
-		return -1;
+	if (flag == FIND_NEXT_MATCH_DOWN)
+		++match_start;
+	else if (flag == FIND_NEXT_MATCH_UP)
+		--match_start;
 
-	if (hot_char == c) {
-		hot_index = (hot_index+1)%hotkeys[c].count;
-		return hotkeys[c].ptrs[hot_index];
-	} else {
-		hot_char = c;
-		hot_index = 0;
-		return hotkeys[c].ptrs[0];
+	index = match_start;
+	index = (index + items_num) % items_num;
+	while (true) {
+		char *str = k_menu_items[index].str;
+		if (strcasestr(str, match_str) != 0)
+			return index;
+		if (flag == FIND_NEXT_MATCH_UP ||
+		    flag == MATCH_TINKER_PATTERN_UP)
+			--index;
+		else
+			++index;
+		index = (index + items_num) % items_num;
+		if (index == match_start)
+			return -1;
 	}
 }
 
-/* can the char c be a hot key? no, if c is a common shortcut used elsewhere */
-static int canbhot(char c)
-{
-	c = tolower(c);
-	return isalnum(c) && c != 'y' && c != 'm' && c != 'h' &&
-		c != 'n' && c != '?';
-}
-
-/* check if str already contains a hot key. */
-static int is_hot(int index)
-{
-	return k_menu_items[index].is_hot;
-}
-
-/* find the first possible hot key, and mark it.
- * index is the index of the item in the menu
- * return 0 on success*/
-static int make_hot(char *dest, int len, const char *org, int index)
-{
-	int position = -1;
-	int i;
-	int tmp;
-	int c;
-	int org_len = strlen(org);
-
-	if (org == NULL || is_hot(index))
-		return 1;
-
-	/* make sure not to make hot keys out of markers.
-	 * find where to start looking for a hot key
-	 */
-	i = 0;
-	/* skip white space */
-	while (i < org_len && org[i] == ' ')
-		i++;
-	if (i == org_len)
-		return -1;
-	/* if encountering '(' or '<' or '[', find the match and look from there
-	 **/
-	if (org[i] == '[' || org[i] == '<' || org[i] == '(') {
-		i++;
-		for (; i < org_len; i++)
-			if (org[i] == ']' || org[i] == '>' || org[i] == ')')
-				break;
-	}
-	if (i == org_len)
-		return -1;
-	for (; i < org_len; i++) {
-		if (canbhot(org[i]) && org[i-1] != '<' && org[i-1] != '(') {
-			position = i;
-			break;
-		}
-	}
-	if (position == -1)
-		return 1;
-
-	/* ok, char at org[position] should be a hot key to this item */
-	c = tolower(org[position]);
-	tmp = hotkeys[c].count;
-	hotkeys[c].ptrs[tmp] = index;
-	hotkeys[c].count++;
-	/*
-	   snprintf(dest, len, "%.*s(%c)%s", position, org, org[position],
-	   &org[position+1]);
-	   */
-	/* make org[position] uppercase, and all leading letter small case */
-	strncpy(dest, org, len);
-	for (i = 0; i < position; i++)
-		dest[i] = tolower(dest[i]);
-	dest[position] = toupper(dest[position]);
-	k_menu_items[index].is_hot = 1;
-	return 0;
-}
-
-/* Make a new item. Add a hotkey mark in the first possible letter.
- * As ncurses does not allow any attributes inside menue item, we mark the
- * hot key as the first capitalized letter in the string */
+/* Make a new item. */
 static void item_make(struct menu *menu, char tag, const char *fmt, ...)
 {
 	va_list ap;
-	char tmp_str[256];
 
 	if (items_num > MAX_MENU_ITEMS-1)
 		return;
@@ -597,16 +544,13 @@
 		k_menu_items[items_num].is_visible = 1;
 
 	va_start(ap, fmt);
-	vsnprintf(tmp_str, sizeof(tmp_str), fmt, ap);
-	if (!k_menu_items[items_num].is_visible)
-		memcpy(tmp_str, "XXX", 3);
+	vsnprintf(k_menu_items[items_num].str,
+		  sizeof(k_menu_items[items_num].str),
+		  fmt, ap);
 	va_end(ap);
-	if (make_hot(
-		k_menu_items[items_num].str,
-		sizeof(k_menu_items[items_num].str), tmp_str, items_num) != 0)
-		strncpy(k_menu_items[items_num].str,
-			tmp_str,
-			sizeof(k_menu_items[items_num].str));
+
+	if (!k_menu_items[items_num].is_visible)
+		memcpy(k_menu_items[items_num].str, "XXX", 3);
 
 	curses_menu_items[items_num] = new_item(
 			k_menu_items[items_num].str,
@@ -638,11 +582,9 @@
 	va_end(ap);
 	snprintf(tmp_str, sizeof(tmp_str), "%s%s",
 			k_menu_items[index].str, new_str);
-	if (make_hot(k_menu_items[index].str,
-			sizeof(k_menu_items[index].str), tmp_str, index) != 0)
-		strncpy(k_menu_items[index].str,
-			tmp_str,
-			sizeof(k_menu_items[index].str));
+	strncpy(k_menu_items[index].str,
+		tmp_str,
+		sizeof(k_menu_items[index].str));
 
 	free_item(curses_menu_items[index]);
 	curses_menu_items[index] = new_item(
@@ -1027,23 +969,18 @@
 static void center_item(int selected_index, int *last_top_row)
 {
 	int toprow;
-	int maxy, maxx;
 
-	scale_menu(curses_menu, &maxy, &maxx);
 	set_top_row(curses_menu, *last_top_row);
 	toprow = top_row(curses_menu);
-	if (selected_index >= toprow && selected_index < toprow+maxy) {
-		/* we can only move the selected item. no need to scroll */
-		set_current_item(curses_menu,
-				curses_menu_items[selected_index]);
-	} else {
-		toprow = max(selected_index-maxy/2, 0);
-		if (toprow >= item_count(curses_menu)-maxy)
+	if (selected_index < toprow ||
+	    selected_index >= toprow+mwin_max_lines) {
+		toprow = max(selected_index-mwin_max_lines/2, 0);
+		if (toprow >= item_count(curses_menu)-mwin_max_lines)
 			toprow = item_count(curses_menu)-mwin_max_lines;
 		set_top_row(curses_menu, toprow);
-		set_current_item(curses_menu,
-				curses_menu_items[selected_index]);
 	}
+	set_current_item(curses_menu,
+			curses_menu_items[selected_index]);
 	*last_top_row = toprow;
 	post_menu(curses_menu);
 	refresh_all_windows(main_window);
@@ -1075,7 +1012,7 @@
 	/* position the menu at the middle of the screen */
 	scale_menu(curses_menu, &maxy, &maxx);
 	maxx = min(maxx, mwin_max_cols-2);
-	maxy = mwin_max_lines-2;
+	maxy = mwin_max_lines;
 	menu_window = derwin(main_window,
 			maxy,
 			maxx,
@@ -1099,10 +1036,77 @@
 	refresh_all_windows(main_window);
 }
 
+static void adj_match_dir(match_f *match_direction)
+{
+	if (*match_direction == FIND_NEXT_MATCH_DOWN)
+		*match_direction =
+			MATCH_TINKER_PATTERN_DOWN;
+	else if (*match_direction == FIND_NEXT_MATCH_UP)
+		*match_direction =
+			MATCH_TINKER_PATTERN_UP;
+	/* else, do no change.. */
+}
+
+struct match_state
+{
+	int in_search;
+	match_f match_direction;
+	char pattern[256];
+};
+
+/* Return 0 means I have handled the key. In such a case, ans should hold the
+ * item to center, or -1 otherwise.
+ * Else return -1 .
+ */
+static int do_match(int key, struct match_state *state, int *ans)
+{
+	char c = (char) key;
+	int terminate_search = 0;
+	*ans = -1;
+	if (key == '/' || (state->in_search && key == 27)) {
+		move(0, 0);
+		refresh();
+		clrtoeol();
+		state->in_search = 1-state->in_search;
+		bzero(state->pattern, sizeof(state->pattern));
+		state->match_direction = MATCH_TINKER_PATTERN_DOWN;
+		return 0;
+	} else if (!state->in_search)
+		return 1;
+
+	if (isalnum(c) || isgraph(c) || c == ' ') {
+		state->pattern[strlen(state->pattern)] = c;
+		state->pattern[strlen(state->pattern)] = '\0';
+		adj_match_dir(&state->match_direction);
+		*ans = get_mext_match(state->pattern,
+				state->match_direction);
+	} else if (key == KEY_DOWN) {
+		state->match_direction = FIND_NEXT_MATCH_DOWN;
+		*ans = get_mext_match(state->pattern,
+				state->match_direction);
+	} else if (key == KEY_UP) {
+		state->match_direction = FIND_NEXT_MATCH_UP;
+		*ans = get_mext_match(state->pattern,
+				state->match_direction);
+	} else if (key == KEY_BACKSPACE || key == 127) {
+		state->pattern[strlen(state->pattern)-1] = '\0';
+		adj_match_dir(&state->match_direction);
+	} else
+		terminate_search = 1;
+
+	if (terminate_search) {
+		state->in_search = 0;
+		bzero(state->pattern, sizeof(state->pattern));
+		move(0, 0);
+		refresh();
+		clrtoeol();
+		return -1;
+	}
+	return 0;
+}
 
 static void conf(struct menu *menu)
 {
-	char pattern[256];
 	struct menu *submenu = 0;
 	const char *prompt = menu_get_prompt(menu);
 	struct symbol *sym;
@@ -1110,8 +1114,11 @@
 	int res;
 	int current_index = 0;
 	int last_top_row = 0;
-
-	bzero(pattern, sizeof(pattern));
+	struct match_state match_state = {
+		.in_search = 0,
+		.match_direction = MATCH_TINKER_PATTERN_DOWN,
+		.pattern = "",
+	};
 
 	while (!global_exit) {
 		reset_menu();
@@ -1124,7 +1131,22 @@
 				_(menu_instructions),
 				current_index, &last_top_row);
 		keypad((menu_win(curses_menu)), TRUE);
-		while (!global_exit && (res = wgetch(menu_win(curses_menu)))) {
+		while (!global_exit) {
+			if (match_state.in_search) {
+				mvprintw(0, 0,
+					"searching: %s", match_state.pattern);
+				clrtoeol();
+			}
+			refresh_all_windows(main_window);
+			res = wgetch(menu_win(curses_menu));
+			if (!res)
+				break;
+			if (do_match(res, &match_state, &current_index) == 0) {
+				if (current_index != -1)
+					center_item(current_index,
+						    &last_top_row);
+				continue;
+			}
 			if (process_special_keys(&res,
 						(struct menu *) item_data()))
 				break;
@@ -1155,19 +1177,13 @@
 			if (res == 10 || res == 27 ||
 				res == 32 || res == 'n' || res == 'y' ||
 				res == KEY_LEFT || res == KEY_RIGHT ||
-				res == 'm' || res == '/')
+				res == 'm')
 				break;
-			else if (canbhot(res)) {
-				/* check for hot keys: */
-				int tmp = get_next_hot(res);
-				if (tmp != -1)
-					center_item(tmp, &last_top_row);
-			}
 			refresh_all_windows(main_window);
 		}
 
 		refresh_all_windows(main_window);
-		/* if ESC  or left*/
+		/* if ESC or left*/
 		if (res == 27 || (menu != &rootmenu && res == KEY_LEFT))
 			break;
 
@@ -1235,9 +1251,6 @@
 			if (item_is_tag('t'))
 				sym_set_tristate_value(sym, mod);
 			break;
-		case '/':
-			search_conf();
-			break;
 		}
 	}
 }
@@ -1268,6 +1281,11 @@
 	int selected_index = 0;
 	int last_top_row = 0;
 	int res, i = 0;
+	struct match_state match_state = {
+		.in_search = 0,
+		.match_direction = MATCH_TINKER_PATTERN_DOWN,
+		.pattern = "",
+	};
 
 	active = sym_get_choice_value(menu->sym);
 	/* this is mostly duplicated from the conf() function. */
@@ -1294,7 +1312,22 @@
 				_(radiolist_instructions),
 				selected_index,
 				&last_top_row);
-		while (!global_exit && (res = wgetch(menu_win(curses_menu)))) {
+		while (!global_exit) {
+			if (match_state.in_search) {
+				mvprintw(0, 0, "searching: %s",
+					 match_state.pattern);
+				clrtoeol();
+			}
+			refresh_all_windows(main_window);
+			res = wgetch(menu_win(curses_menu));
+			if (!res)
+				break;
+			if (do_match(res, &match_state, &selected_index) == 0) {
+				if (selected_index != -1)
+					center_item(selected_index,
+						    &last_top_row);
+				continue;
+			}
 			if (process_special_keys(
 						&res,
 						(struct menu *) item_data()))
@@ -1324,13 +1357,8 @@
 				break;
 			}
 			if (res == 10 || res == 27 || res == ' ' ||
-				res == KEY_LEFT)
+					res == KEY_LEFT){
 				break;
-			else if (canbhot(res)) {
-				/* check for hot keys: */
-				int tmp = get_next_hot(res);
-				if (tmp != -1)
-					center_item(tmp, &last_top_row);
 			}
 			refresh_all_windows(main_window);
 		}
@@ -1485,7 +1513,7 @@
 	/* set up the menu and menu window */
 	main_window = newwin(LINES-2, COLS-2, 2, 1);
 	keypad(main_window, TRUE);
-	mwin_max_lines = LINES-6;
+	mwin_max_lines = LINES-7;
 	mwin_max_cols = COLS-6;
 
 	/* panels order is from bottom to top */
@@ -1532,9 +1560,10 @@
 	/* set btns menu */
 	curses_menu = new_menu(curses_menu_items);
 	menu_opts_off(curses_menu, O_SHOWDESC);
-	menu_opts_off(curses_menu, O_SHOWMATCH);
+	menu_opts_on(curses_menu, O_SHOWMATCH);
 	menu_opts_on(curses_menu, O_ONEVALUE);
 	menu_opts_on(curses_menu, O_NONCYCLIC);
+	menu_opts_on(curses_menu, O_IGNORECASE);
 	set_menu_mark(curses_menu, " ");
 	set_menu_fore(curses_menu, attributes[MAIN_MENU_FORE]);
 	set_menu_back(curses_menu, attributes[MAIN_MENU_BACK]);
@@ -1550,8 +1579,6 @@
 				_(menu_no_f_instructions));
 	}
 
-
-
 	/* do the work */
 	while (!global_exit) {
 		conf(&rootmenu);