lineedit: remove pos_buf[] array (up to 16k!); fix compat bugs
pos_buf is a strange hack, easy to do without it.
This also allows lines >32k long to be handled.
Also simplified match prefix generations and made behavior more like bash.
function old new delta
remove_chunk - 43 +43
collapse_pos 79 - -79
build_match_prefix 804 579 -225
------------------------------------------------------------------------------
(add/remove: 1/1 grow/shrink: 0/1 up/down: 43/-304) Total: -261 bytes
Signed-off-by: Denys Vlasenko <dvlasenk@redhat.com>
diff --git a/libbb/lineedit.c b/libbb/lineedit.c
index e40917f..fff5c1a 100644
--- a/libbb/lineedit.c
+++ b/libbb/lineedit.c
@@ -99,7 +99,6 @@
enum {
- /* We use int16_t for positions, need to limit line len */
MAX_LINELEN = CONFIG_FEATURE_EDITING_MAX_LEN < 0x7ff0
? CONFIG_FEATURE_EDITING_MAX_LEN
: 0x7ff0
@@ -156,7 +155,6 @@
#if ENABLE_FEATURE_TAB_COMPLETION
char input_tab__matchBuf[MAX_LINELEN];
int16_t find_match__int_buf[MAX_LINELEN + 1]; /* need to have 9 bits at least */
- int16_t find_match__pos_buf[MAX_LINELEN + 1];
#endif
};
@@ -824,15 +822,16 @@
*/
#define QUOT (UCHAR_MAX+1)
#define int_buf (S.find_match__int_buf)
-#define pos_buf (S.find_match__pos_buf)
#define dbg_bmp 0
-static void collapse_pos(int beg, int end)
+static void remove_chunk(int beg, int end)
{
/* beg must be <= end */
if (beg == end)
return;
- memmove(int_buf+beg, int_buf+end, (MAX_LINELEN+1-end) * sizeof(int_buf[0]));
- memmove(pos_buf+beg, pos_buf+end, (MAX_LINELEN+1-end) * sizeof(pos_buf[0]));
+
+ while ((int_buf[beg] = int_buf[end]) != 0)
+ beg++, end++;
+
if (dbg_bmp) {
int i;
for (i = 0; int_buf[i]; i++)
@@ -846,42 +845,39 @@
int command_mode;
/* Were local, but it used too much stack */
/* int16_t int_buf[MAX_LINELEN + 1]; */
-/* int16_t pos_buf[MAX_LINELEN + 1]; */
if (dbg_bmp) printf("\n%s\n", matchBuf);
- for (i = 0;; i++) {
- int_buf[i] = (unsigned char)matchBuf[i];
- if (int_buf[i] == 0) {
- pos_buf[i] = -1; /* end-of-line indicator */
- break;
- }
- pos_buf[i] = i;
- }
+ i = 0;
+ while ((int_buf[i] = (unsigned char)matchBuf[i]) != '\0')
+ i++;
/* Mark every \c as "quoted c" */
for (i = j = 0; matchBuf[i]; i++, j++) {
if (matchBuf[i] == '\\') {
- collapse_pos(j, j + 1);
+ remove_chunk(j, j + 1);
int_buf[j] |= QUOT;
i++;
}
}
- /* Quote-mark "chars" and 'chars' */
+ /* Quote-mark "chars" and 'chars', drop delimiters */
{
int in_quote = 0;
- for (i = 0; int_buf[i]; i++) {
+ i = 0;
+ while (int_buf[i]) {
int cur = int_buf[i];
+ if (!cur)
+ break;
if (cur == '\'' || cur == '"') {
- if (!in_quote)
- in_quote = cur;
- else if (cur == in_quote)
- in_quote = 0;
- else
- int_buf[i] |= QUOT;
- } else if (in_quote && cur != '$') {
- int_buf[i] |= QUOT;
+ if (!in_quote || (cur == in_quote)) {
+ in_quote ^= cur;
+ remove_chunk(i, i + 1);
+ continue;
+ }
}
+ if (in_quote)
+ int_buf[i] = cur | QUOT;
+ i++;
}
}
@@ -898,53 +894,39 @@
} else if (cur == '|' && prev == '>') {
continue;
}
- collapse_pos(0, i + 1 + (cur == int_buf[i + 1]));
+ remove_chunk(0, i + 1 + (cur == int_buf[i + 1]));
i = -1; /* back to square 1 */
}
}
/* Remove all `cmd` */
-//BUG: `cmd` should count as a word: `cmd` c<tab> should search for files c*, not commands c*
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);
+ /* `cmd` should count as a word:
+ * `cmd` c<tab> should search for files c*,
+ * not commands c*. Therefore we don't drop
+ * `cmd` entirely, we replace it with single `.
+ */
+ remove_chunk(i, j);
goto next;
}
}
/* No closing ` - command mode, remove all up to ` */
- collapse_pos(0, i + 1);
+ remove_chunk(0, i + 1);
break;
- next:
- i--; /* hack increment */
+ next: ;
}
}
- /* Remove (command...(command...)...) and {command...{command...}...} */
- {
- int paren_lvl = 0;
- int curly_lvl = 0;
- for (i = 0; int_buf[i]; i++) {
- if (int_buf[i] == '(' || int_buf[i] == '{') {
- if (int_buf[i] == '(')
- paren_lvl++;
- else
- curly_lvl++;
- collapse_pos(0, i + 1);
- i = -1; /* hack increment */
- }
- }
- for (i = 0; pos_buf[i] >= 0 && (paren_lvl > 0 || curly_lvl > 0); i++) {
- if ((int_buf[i] == ')' && paren_lvl > 0)
- || (int_buf[i] == '}' && curly_lvl > 0)
- ) {
- if (int_buf[i] == ')')
- paren_lvl--;
- else
- curly_lvl--;
- collapse_pos(0, i + 1);
- i = -1; /* hack increment */
- }
+ /* Remove "cmd (" and "cmd {"
+ * Example: "if { c<tab>"
+ * In this example, c should be matched as command pfx.
+ */
+ for (i = 0; int_buf[i]; i++) {
+ if (int_buf[i] == '(' || int_buf[i] == '{') {
+ remove_chunk(0, i + 1);
+ i = -1; /* hack increment */
}
}
@@ -952,7 +934,7 @@
for (i = 0; int_buf[i]; i++)
if (int_buf[i] != ' ')
break;
- collapse_pos(0, i);
+ remove_chunk(0, i);
/* Determine completion mode */
command_mode = FIND_EXE_ONLY;
@@ -960,9 +942,9 @@
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'
-//BUG: must check "cd ", not "cd"
+ && (char)int_buf[0] == 'c'
+ && (char)int_buf[1] == 'd'
+ && i == 2 /* -> int_buf[2] == ' ' */
) {
command_mode = FIND_DIR_ONLY;
} else {
@@ -979,36 +961,20 @@
for (--i; i >= 0; i--) {
int cur = int_buf[i];
if (cur == ' ' || cur == '<' || cur == '>' || cur == '|' || cur == '&') {
- collapse_pos(0, i + 1);
+ remove_chunk(0, i + 1);
break;
}
}
- /* Skip all leading unquoted ' or " */
-//BUG: bash doesn't do this
- for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++)
- continue;
- /* Skip quoted or unquoted // or /~ */
-//BUG: bash doesn't do this
- while ((char)int_buf[i] == '/'
- && ((char)int_buf[i+1] == '/' || (char)int_buf[i+1] == '~')
- ) {
- i++;
- }
- /* set only match and destroy quotes */
- {
- int pos = 0;
- for (j = 0; pos_buf[i] >= 0; i++) {
- matchBuf[j++] = matchBuf[pos_buf[i]];
- pos = pos_buf[i] + 1;
- }
- matchBuf[j] = '\0';
- }
+ /* Store only match prefix */
+ i = 0;
+ while ((matchBuf[i] = int_buf[i]) != '\0')
+ i++;
+ if (dbg_bmp) printf("final matchBuf:'%s'\n", matchBuf);
return command_mode;
}
#undef int_buf
-#undef pos_buf
/*
* Display by column (original idea from ls applet,