Commit dce92c6d authored by Tom Lane's avatar Tom Lane

Rewrite tab completion's previous-word fetching for more sanity.

Make it return empty strings when there are no more words to the left of
the current position, instead of sometimes returning NULL and other times
returning copies of the leftmost word.  Also, fetch the words in one scan,
rather than the previous wasteful approach of starting from scratch for
each word.  Make the code a bit harder to break when someone decides we
need more words of context, too.  (There was actually a memory leak here,
because whoever added prev6_wd neglected to free it.)
parent 8f3362d4
...@@ -668,7 +668,7 @@ static char **complete_from_variables(char *text, ...@@ -668,7 +668,7 @@ static char **complete_from_variables(char *text,
static PGresult *exec_query(const char *query); static PGresult *exec_query(const char *query);
static char *previous_word(int point, int skip); static void get_previous_words(int point, char **previous_words, int nwords);
#ifdef NOT_USED #ifdef NOT_USED
static char *quote_file_name(char *text, int match_type, char *quote_pointer); static char *quote_file_name(char *text, int match_type, char *quote_pointer);
...@@ -710,13 +710,16 @@ psql_completion(char *text, int start, int end) ...@@ -710,13 +710,16 @@ psql_completion(char *text, int start, int end)
/* This is the variable we'll return. */ /* This is the variable we'll return. */
char **matches = NULL; char **matches = NULL;
/* These are going to contain some scannage of the input line. */ /* This array will contain some scannage of the input line. */
char *prev_wd, char *previous_words[6];
*prev2_wd,
*prev3_wd, /* For compactness, we use these macros to reference previous_words[]. */
*prev4_wd, #define prev_wd (previous_words[0])
*prev5_wd, #define prev2_wd (previous_words[1])
*prev6_wd; #define prev3_wd (previous_words[2])
#define prev4_wd (previous_words[3])
#define prev5_wd (previous_words[4])
#define prev6_wd (previous_words[5])
static const char *const sql_commands[] = { static const char *const sql_commands[] = {
"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER", "ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
...@@ -755,16 +758,11 @@ psql_completion(char *text, int start, int end) ...@@ -755,16 +758,11 @@ psql_completion(char *text, int start, int end)
completion_info_charp2 = NULL; completion_info_charp2 = NULL;
/* /*
* Scan the input line before our current position for the last five * Scan the input line before our current position for the last few
* words. According to those we'll make some smart decisions on what the * words. According to those we'll make some smart decisions on what the
* user is probably intending to type. TODO: Use strtokx() to do this. * user is probably intending to type.
*/ */
prev_wd = previous_word(start, 0); get_previous_words(start, previous_words, lengthof(previous_words));
prev2_wd = previous_word(start, 1);
prev3_wd = previous_word(start, 2);
prev4_wd = previous_word(start, 3);
prev5_wd = previous_word(start, 4);
prev6_wd = previous_word(start, 5);
/* If a backslash command was started, continue */ /* If a backslash command was started, continue */
if (text[0] == '\\') if (text[0] == '\\')
...@@ -782,7 +780,7 @@ psql_completion(char *text, int start, int end) ...@@ -782,7 +780,7 @@ psql_completion(char *text, int start, int end)
} }
/* If no previous word, suggest one of the basic sql commands */ /* If no previous word, suggest one of the basic sql commands */
else if (!prev_wd) else if (prev_wd[0] == '\0')
COMPLETE_WITH_LIST(sql_commands); COMPLETE_WITH_LIST(sql_commands);
/* CREATE */ /* CREATE */
...@@ -790,10 +788,10 @@ psql_completion(char *text, int start, int end) ...@@ -790,10 +788,10 @@ psql_completion(char *text, int start, int end)
else if (pg_strcasecmp(prev_wd, "CREATE") == 0) else if (pg_strcasecmp(prev_wd, "CREATE") == 0)
matches = completion_matches(text, create_command_generator); matches = completion_matches(text, create_command_generator);
/* DROP, but watch out for DROP embedded in other commands */ /* DROP, but not DROP embedded in other commands */
/* complete with something you can drop */ /* complete with something you can drop */
else if (pg_strcasecmp(prev_wd, "DROP") == 0 && else if (pg_strcasecmp(prev_wd, "DROP") == 0 &&
pg_strcasecmp(prev2_wd, "DROP") == 0) prev2_wd[0] == '\0')
matches = completion_matches(text, drop_command_generator); matches = completion_matches(text, drop_command_generator);
/* ALTER */ /* ALTER */
...@@ -2918,11 +2916,12 @@ psql_completion(char *text, int start, int end) ...@@ -2918,11 +2916,12 @@ psql_completion(char *text, int start, int end)
} }
/* free storage */ /* free storage */
free(prev_wd); {
free(prev2_wd); int i;
free(prev3_wd);
free(prev4_wd); for (i = 0; i < lengthof(previous_words); i++)
free(prev5_wd); free(previous_words[i]);
}
/* Return our Grand List O' Matches */ /* Return our Grand List O' Matches */
return matches; return matches;
...@@ -3372,77 +3371,88 @@ exec_query(const char *query) ...@@ -3372,77 +3371,88 @@ exec_query(const char *query)
/* /*
* Return the word (space delimited) before point. Set skip > 0 to * Return the nwords word(s) before point. Words are returned right to left,
* skip that many words; e.g. skip=1 finds the word before the * that is, previous_words[0] gets the last word before point.
* previous one. Return value is NULL or a malloc'ed string. * If we run out of words, remaining array elements are set to empty strings.
* Each array element is filled with a malloc'd string.
*/ */
static char * static void
previous_word(int point, int skip) get_previous_words(int point, char **previous_words, int nwords)
{ {
int i,
start = 0,
end = -1,
inquotes = 0;
char *s;
const char *buf = rl_line_buffer; /* alias */ const char *buf = rl_line_buffer; /* alias */
int i;
/* first we look for a space or a parenthesis before the current word */ /* first we look for a non-word char before the current point */
for (i = point - 1; i >= 0; i--) for (i = point - 1; i >= 0; i--)
if (strchr(WORD_BREAKS, buf[i])) if (strchr(WORD_BREAKS, buf[i]))
break; break;
point = i; point = i;
while (skip-- >= 0) while (nwords-- > 0)
{ {
int parentheses = 0; int start,
end;
char *s;
/* now find the first non-space which then constitutes the end */ /* now find the first non-space which then constitutes the end */
end = -1;
for (i = point; i >= 0; i--) for (i = point; i >= 0; i--)
if (buf[i] != ' ') {
if (!isspace((unsigned char) buf[i]))
{ {
end = i; end = i;
break; break;
} }
}
/* /*
* If no end found we return null, because there is no word before the * If no end found we return an empty string, because there is no word
* point * before the point
*/
if (end == -1)
return NULL;
/*
* Otherwise we now look for the start. The start is either the last
* character before any space going backwards from the end, or it's
* simply character 0. We also handle open quotes and parentheses.
*/ */
for (start = end; start > 0; start--) if (end < 0)
{
point = end;
s = pg_strdup("");
}
else
{ {
if (buf[start] == '"') /*
inquotes = !inquotes; * Otherwise we now look for the start. The start is either the
if (inquotes == 0) * last character before any word-break character going backwards
* from the end, or it's simply character 0. We also handle open
* quotes and parentheses.
*/
bool inquotes = false;
int parentheses = 0;
for (start = end; start > 0; start--)
{ {
if (buf[start] == ')') if (buf[start] == '"')
parentheses++; inquotes = !inquotes;
else if (buf[start] == '(') else if (!inquotes)
{ {
if (--parentheses <= 0) if (buf[start] == ')')
parentheses++;
else if (buf[start] == '(')
{
if (--parentheses <= 0)
break;
}
else if (parentheses == 0 &&
strchr(WORD_BREAKS, buf[start - 1]))
break; break;
} }
else if (parentheses == 0 &&
strchr(WORD_BREAKS, buf[start - 1]))
break;
} }
}
point = start - 1; point = start - 1;
}
/* make a copy */ /* make a copy of chars from start to end inclusive */
s = pg_malloc(end - start + 2); s = pg_malloc(end - start + 2);
strlcpy(s, &buf[start], end - start + 2); strlcpy(s, &buf[start], end - start + 2);
}
return s; *previous_words++ = s;
}
} }
#ifdef NOT_USED #ifdef NOT_USED
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment