+static char *add_quote_for_spec_chars(char *found)
+{
+ int l = 0;
+ char *s = xmalloc((strlen(found) + 1) * 2);
+
+ while (*found) {
+ if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
+ s[l++] = '\\';
+ s[l++] = *found++;
+ }
+ s[l] = 0;
+ return s;
+}
+
+static char **exe_n_cwd_tab_completion(char *command, int *num_matches,
+ int type)
+{
+
+ char **matches = 0;
+ DIR *dir;
+ struct dirent *next;
+ char dirbuf[BUFSIZ];
+ int nm = *num_matches;
+ struct stat st;
+ char *path1[1];
+ char **paths = path1;
+ int npaths;
+ int i;
+ char *found;
+ char *pfind = strrchr(command, '/');
+
+ path1[0] = ".";
+
+ if (pfind == NULL) {
+ /* no dir, if flags==EXE_ONLY - get paths, else "." */
+ npaths = path_parse(&paths, type);
+ pfind = command;
+ } else {
+ /* with dir */
+ /* save for change */
+ strcpy(dirbuf, command);
+ /* set dir only */
+ dirbuf[(pfind - command) + 1] = 0;
+#ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
+ if (dirbuf[0] == '~') /* ~/... or ~user/... */
+ username_tab_completion(dirbuf, 0);
+#endif
+ /* "strip" dirname in command */
+ pfind++;
+
+ paths[0] = dirbuf;
+ npaths = 1; /* only 1 dir */
+ }
+
+ for (i = 0; i < npaths; i++) {
+
+ dir = opendir(paths[i]);
+ if (!dir) /* Don't print an error */
+ continue;
+
+ while ((next = readdir(dir)) != NULL) {
+ char *str_found = next->d_name;
+
+ /* matched ? */
+ if (strncmp(str_found, pfind, strlen(pfind)))
+ continue;
+ /* not see .name without .match */
+ if (*str_found == '.' && *pfind == 0) {
+ if (*paths[i] == '/' && paths[i][1] == 0
+ && str_found[1] == 0) str_found = ""; /* only "/" */
+ else
+ continue;
+ }
+ found = concat_path_file(paths[i], str_found);
+ /* hmm, remover in progress? */
+ if (stat(found, &st) < 0)
+ goto cont;
+ /* find with dirs ? */
+ if (paths[i] != dirbuf)
+ strcpy(found, next->d_name); /* only name */
+ if (S_ISDIR(st.st_mode)) {
+ /* name is directory */
+ str_found = found;
+ found = concat_path_file(found, "");
+ free(str_found);
+ str_found = add_quote_for_spec_chars(found);
+ } else {
+ /* not put found file if search only dirs for cd */
+ if (type == FIND_DIR_ONLY)
+ goto cont;
+ str_found = add_quote_for_spec_chars(found);
+ if (type == FIND_FILE_ONLY ||
+ (type == FIND_EXE_ONLY && is_execute(&st)))
+ strcat(str_found, " ");
+ }
+ /* Add it to the list */
+ matches = xrealloc(matches, (nm + 1) * sizeof(char *));
+
+ matches[nm++] = str_found;
+cont:
+ free(found);
+ }
+ closedir(dir);
+ }
+ if (paths != path1) {
+ free(paths[0]); /* allocated memory only in first member */
+ free(paths);
+ }
+ *num_matches = nm;
+ return (matches);
+}
+
+static int match_compare(const void *a, const void *b)
+{
+ return strcmp(*(char **) a, *(char **) b);
+}
+
+
+
+#define QUOT (UCHAR_MAX+1)
+
+#define collapse_pos(is, in) { \
+ memcpy(int_buf+(is), int_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); \
+ memcpy(pos_buf+(is), pos_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); }
+
+static int find_match(char *matchBuf, int *len_with_quotes)
+{
+ int i, j;
+ int command_mode;
+ int c, c2;
+ int int_buf[BUFSIZ + 1];
+ int pos_buf[BUFSIZ + 1];
+
+ /* set to integer dimension characters and own positions */
+ for (i = 0;; i++) {
+ int_buf[i] = (int) ((unsigned char) matchBuf[i]);
+ if (int_buf[i] == 0) {
+ pos_buf[i] = -1; /* indicator end line */
+ break;
+ } else
+ pos_buf[i] = i;
+ }
+
+ /* mask \+symbol and convert '\t' to ' ' */
+ for (i = j = 0; matchBuf[i]; i++, j++)
+ if (matchBuf[i] == '\\') {
+ collapse_pos(j, j + 1);
+ int_buf[j] |= QUOT;
+ i++;
+#ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
+ if (matchBuf[i] == '\t') /* algorithm equivalent */
+ int_buf[j] = ' ' | QUOT;
+#endif
+ }
+#ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
+ else if (matchBuf[i] == '\t')
+ int_buf[j] = ' ';
+#endif
+
+ /* mask "symbols" or 'symbols' */
+ c2 = 0;
+ for (i = 0; int_buf[i]; i++) {
+ c = int_buf[i];
+ if (c == '\'' || c == '"') {
+ if (c2 == 0)
+ c2 = c;
+ else {
+ if (c == c2)
+ c2 = 0;
+ else
+ int_buf[i] |= QUOT;
+ }
+ } else if (c2 != 0 && c != '$')
+ int_buf[i] |= QUOT;
+ }
+
+ /* skip commands with arguments if line have commands delimiters */
+ /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
+ for (i = 0; int_buf[i]; i++) {
+ c = int_buf[i];
+ c2 = int_buf[i + 1];
+ j = i ? int_buf[i - 1] : -1;
+ command_mode = 0;
+ if (c == ';' || c == '&' || c == '|') {
+ command_mode = 1 + (c == c2);
+ if (c == '&') {
+ if (j == '>' || j == '<')
+ command_mode = 0;
+ } else if (c == '|' && j == '>')
+ command_mode = 0;
+ }
+ if (command_mode) {
+ collapse_pos(0, i + command_mode);
+ i = -1; /* hack incremet */
+ }
+ }
+ /* collapse `command...` */
+ for (i = 0; int_buf[i]; i++)
+ if (int_buf[i] == '`') {
+ for (j = i + 1; int_buf[j]; j++)
+ if (int_buf[j] == '`') {
+ collapse_pos(i, j + 1);
+ j = 0;
+ break;
+ }
+ if (j) {
+ /* not found close ` - command mode, collapse all previous */
+ collapse_pos(0, i + 1);
+ break;
+ } else
+ i--; /* hack incremet */
+ }
+
+ /* collapse (command...(command...)...) or {command...{command...}...} */
+ c = 0; /* "recursive" level */
+ c2 = 0;
+ for (i = 0; int_buf[i]; i++)
+ if (int_buf[i] == '(' || int_buf[i] == '{') {
+ if (int_buf[i] == '(')
+ c++;
+ else
+ c2++;
+ collapse_pos(0, i + 1);
+ i = -1; /* hack incremet */
+ }
+ for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
+ if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
+ if (int_buf[i] == ')')
+ c--;
+ else
+ c2--;
+ collapse_pos(0, i + 1);
+ i = -1; /* hack incremet */
+ }
+
+ /* skip first not quote space */
+ for (i = 0; int_buf[i]; i++)
+ if (int_buf[i] != ' ')
+ break;
+ if (i)
+ collapse_pos(0, i);
+
+ /* set find mode for completion */
+ command_mode = FIND_EXE_ONLY;
+ for (i = 0; int_buf[i]; i++)
+ if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
+ if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
+ && matchBuf[pos_buf[0]]=='c'
+ && matchBuf[pos_buf[1]]=='d' )
+ command_mode = FIND_DIR_ONLY;
+ else {
+ command_mode = FIND_FILE_ONLY;
+ break;
+ }
+ }
+ /* "strlen" */
+ for (i = 0; int_buf[i]; i++);
+ /* find last word */
+ for (--i; i >= 0; i--) {
+ c = int_buf[i];
+ if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
+ collapse_pos(0, i + 1);
+ break;
+ }
+ }
+ /* skip first not quoted '\'' or '"' */
+ for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
+ /* collapse quote or unquote // or /~ */
+ while ((int_buf[i] & ~QUOT) == '/' &&
+ ((int_buf[i + 1] & ~QUOT) == '/'
+ || (int_buf[i + 1] & ~QUOT) == '~')) {
+ i++;
+ }
+
+ /* set only match and destroy quotes */
+ j = 0;
+ for (c = 0; pos_buf[i] >= 0; i++) {
+ matchBuf[c++] = matchBuf[pos_buf[i]];
+ j = pos_buf[i] + 1;
+ }
+ matchBuf[c] = 0;
+ /* old lenght matchBuf with quotes symbols */
+ *len_with_quotes = j ? j - pos_buf[0] : 0;
+
+ return command_mode;
+}
+
+
+static void input_tab(int *lastWasTab)
+{
+ /* Do TAB completion */
+ static int num_matches;
+ static char **matches;
+
+ if (lastWasTab == 0) { /* free all memory */
+ if (matches) {
+ while (num_matches > 0)
+ free(matches[--num_matches]);
+ free(matches);
+ matches = (char **) NULL;
+ }
+ return;
+ }
+ if (! *lastWasTab) {
+
+ char *tmp;
+ int len_found;
+ char matchBuf[BUFSIZ];
+ int find_type;
+ int recalc_pos;
+
+ *lastWasTab = TRUE; /* flop trigger */
+
+ /* Make a local copy of the string -- up
+ * to the position of the cursor */
+ tmp = strncpy(matchBuf, command_ps, cursor);
+ tmp[cursor] = 0;
+
+ find_type = find_match(matchBuf, &recalc_pos);
+
+ /* Free up any memory already allocated */
+ input_tab(0);
+
+#ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
+ /* If the word starts with `~' and there is no slash in the word,
+ * then try completing this word as a username. */
+
+ if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
+ matches = username_tab_completion(matchBuf, &num_matches);
+#endif
+ /* Try to match any executable in our path and everything
+ * in the current working directory that matches. */
+ if (!matches)
+ matches =
+ exe_n_cwd_tab_completion(matchBuf,
+ &num_matches, find_type);
+ /* Remove duplicate found */
+ if(matches) {
+ int i, j;
+ /* bubble */
+ for(i=0; i<(num_matches-1); i++)
+ for(j=i+1; j<num_matches; j++)
+ if(matches[i]!=0 && matches[j]!=0 &&
+ strcmp(matches[i], matches[j])==0) {
+ free(matches[j]);
+ matches[j]=0;
+ }
+ j=num_matches;
+ num_matches = 0;
+ for(i=0; i<j; i++)
+ if(matches[i]) {
+ if(!strcmp(matches[i], "./"))
+ matches[i][1]=0;
+ else if(!strcmp(matches[i], "../"))
+ matches[i][2]=0;
+ matches[num_matches++]=matches[i];
+ }
+ }
+ /* Did we find exactly one match? */
+ if (!matches || num_matches > 1) {
+ char *tmp1;
+
+ beep();
+ if (!matches)
+ return; /* not found */
+ /* sort */
+ qsort(matches, num_matches, sizeof(char *), match_compare);
+
+ /* find minimal match */
+ tmp = xstrdup(matches[0]);
+ for (tmp1 = tmp; *tmp1; tmp1++)
+ for (len_found = 1; len_found < num_matches; len_found++)
+ if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
+ *tmp1 = 0;
+ break;
+ }
+ if (*tmp == 0) { /* have unique */
+ free(tmp);
+ return;
+ }
+ } else { /* one match */
+ tmp = matches[0];
+ /* for next completion current found */
+ *lastWasTab = FALSE;
+ }
+
+ len_found = strlen(tmp);
+ /* have space to placed match? */
+ if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
+
+ /* before word for match */
+ command_ps[cursor - recalc_pos] = 0;
+ /* save tail line */
+ strcpy(matchBuf, command_ps + cursor);
+ /* add match */
+ strcat(command_ps, tmp);
+ /* add tail */
+ strcat(command_ps, matchBuf);
+ /* back to begin word for match */
+ input_backward(recalc_pos);
+ /* new pos */
+ recalc_pos = cursor + len_found;
+ /* new len */
+ len = strlen(command_ps);
+ /* write out the matched command */
+ redraw(cmdedit_y, len - recalc_pos);
+ }
+ if (tmp != matches[0])
+ free(tmp);
+ } else {
+ /* Ok -- the last char was a TAB. Since they
+ * just hit TAB again, print a list of all the
+ * available choices... */
+ if (matches && num_matches > 0) {
+ int i, col, l;
+ int sav_cursor = cursor; /* change goto_new_line() */
+
+ /* Go to the next line */
+ goto_new_line();
+ for (i = 0, col = 0; i < num_matches; i++) {
+ l = strlen(matches[i]);
+ if (l < 14)
+ l = 14;
+ printf("%-14s ", matches[i]);
+ if ((l += 2) > 16)
+ while (l % 16) {
+ putchar(' ');
+ l++;
+ }
+ col += l;
+ col -= (col / cmdedit_termw) * cmdedit_termw;
+ if (col > 60 && matches[i + 1] != NULL) {
+ putchar('\n');
+ col = 0;
+ }
+ }
+ /* Go to the next line and rewrite */
+ putchar('\n');
+ redraw(0, len - sav_cursor);
+ }
+ }
+}
+#endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
+
+#if MAX_HISTORY >= 1
+static void get_previous_history(void)
+{
+ if(command_ps[0] != 0 || history[cur_history] == 0) {
+ free(history[cur_history]);
+ history[cur_history] = xstrdup(command_ps);
+ }
+ cur_history--;
+}
+
+static int get_next_history(void)
+{
+ int ch = cur_history;
+
+ if (ch < n_history) {
+ get_previous_history(); /* save the current history line */
+ return (cur_history = ch+1);
+ } else {
+ beep();
+ return 0;
+ }
+}
+#endif
+
+enum {
+ ESC = 27,
+ DEL = 127,
+};
+
+