Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit dce92c6

Browse files
committed
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.)
1 parent 8f3362d commit dce92c6

File tree

1 file changed

+76
-66
lines changed

1 file changed

+76
-66
lines changed

src/bin/psql/tab-complete.c

+76-66
Original file line numberDiff line numberDiff line change
@@ -668,7 +668,7 @@ static char **complete_from_variables(char *text,
668668

669669
static PGresult *exec_query(const char *query);
670670

671-
static char *previous_word(int point, int skip);
671+
static void get_previous_words(int point, char **previous_words, int nwords);
672672

673673
#ifdef NOT_USED
674674
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)
710710
/* This is the variable we'll return. */
711711
char **matches = NULL;
712712

713-
/* These are going to contain some scannage of the input line. */
714-
char *prev_wd,
715-
*prev2_wd,
716-
*prev3_wd,
717-
*prev4_wd,
718-
*prev5_wd,
719-
*prev6_wd;
713+
/* This array will contain some scannage of the input line. */
714+
char *previous_words[6];
715+
716+
/* For compactness, we use these macros to reference previous_words[]. */
717+
#define prev_wd (previous_words[0])
718+
#define prev2_wd (previous_words[1])
719+
#define prev3_wd (previous_words[2])
720+
#define prev4_wd (previous_words[3])
721+
#define prev5_wd (previous_words[4])
722+
#define prev6_wd (previous_words[5])
720723

721724
static const char *const sql_commands[] = {
722725
"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
@@ -755,16 +758,11 @@ psql_completion(char *text, int start, int end)
755758
completion_info_charp2 = NULL;
756759

757760
/*
758-
* Scan the input line before our current position for the last five
761+
* Scan the input line before our current position for the last few
759762
* words. According to those we'll make some smart decisions on what the
760-
* user is probably intending to type. TODO: Use strtokx() to do this.
763+
* user is probably intending to type.
761764
*/
762-
prev_wd = previous_word(start, 0);
763-
prev2_wd = previous_word(start, 1);
764-
prev3_wd = previous_word(start, 2);
765-
prev4_wd = previous_word(start, 3);
766-
prev5_wd = previous_word(start, 4);
767-
prev6_wd = previous_word(start, 5);
765+
get_previous_words(start, previous_words, lengthof(previous_words));
768766

769767
/* If a backslash command was started, continue */
770768
if (text[0] == '\\')
@@ -782,18 +780,18 @@ psql_completion(char *text, int start, int end)
782780
}
783781

784782
/* If no previous word, suggest one of the basic sql commands */
785-
else if (!prev_wd)
783+
else if (prev_wd[0] == '\0')
786784
COMPLETE_WITH_LIST(sql_commands);
787785

788786
/* CREATE */
789787
/* complete with something you can create */
790788
else if (pg_strcasecmp(prev_wd, "CREATE") == 0)
791789
matches = completion_matches(text, create_command_generator);
792790

793-
/* DROP, but watch out for DROP embedded in other commands */
791+
/* DROP, but not DROP embedded in other commands */
794792
/* complete with something you can drop */
795793
else if (pg_strcasecmp(prev_wd, "DROP") == 0 &&
796-
pg_strcasecmp(prev2_wd, "DROP") == 0)
794+
prev2_wd[0] == '\0')
797795
matches = completion_matches(text, drop_command_generator);
798796

799797
/* ALTER */
@@ -2918,11 +2916,12 @@ psql_completion(char *text, int start, int end)
29182916
}
29192917

29202918
/* free storage */
2921-
free(prev_wd);
2922-
free(prev2_wd);
2923-
free(prev3_wd);
2924-
free(prev4_wd);
2925-
free(prev5_wd);
2919+
{
2920+
int i;
2921+
2922+
for (i = 0; i < lengthof(previous_words); i++)
2923+
free(previous_words[i]);
2924+
}
29262925

29272926
/* Return our Grand List O' Matches */
29282927
return matches;
@@ -3372,77 +3371,88 @@ exec_query(const char *query)
33723371

33733372

33743373
/*
3375-
* Return the word (space delimited) before point. Set skip > 0 to
3376-
* skip that many words; e.g. skip=1 finds the word before the
3377-
* previous one. Return value is NULL or a malloc'ed string.
3374+
* Return the nwords word(s) before point. Words are returned right to left,
3375+
* that is, previous_words[0] gets the last word before point.
3376+
* If we run out of words, remaining array elements are set to empty strings.
3377+
* Each array element is filled with a malloc'd string.
33783378
*/
3379-
static char *
3380-
previous_word(int point, int skip)
3379+
static void
3380+
get_previous_words(int point, char **previous_words, int nwords)
33813381
{
3382-
int i,
3383-
start = 0,
3384-
end = -1,
3385-
inquotes = 0;
3386-
char *s;
33873382
const char *buf = rl_line_buffer; /* alias */
3383+
int i;
33883384

3389-
/* first we look for a space or a parenthesis before the current word */
3385+
/* first we look for a non-word char before the current point */
33903386
for (i = point - 1; i >= 0; i--)
33913387
if (strchr(WORD_BREAKS, buf[i]))
33923388
break;
33933389
point = i;
33943390

3395-
while (skip-- >= 0)
3391+
while (nwords-- > 0)
33963392
{
3397-
int parentheses = 0;
3393+
int start,
3394+
end;
3395+
char *s;
33983396

33993397
/* now find the first non-space which then constitutes the end */
3398+
end = -1;
34003399
for (i = point; i >= 0; i--)
3401-
if (buf[i] != ' ')
3400+
{
3401+
if (!isspace((unsigned char) buf[i]))
34023402
{
34033403
end = i;
34043404
break;
34053405
}
3406+
}
34063407

34073408
/*
3408-
* If no end found we return null, because there is no word before the
3409-
* point
3410-
*/
3411-
if (end == -1)
3412-
return NULL;
3413-
3414-
/*
3415-
* Otherwise we now look for the start. The start is either the last
3416-
* character before any space going backwards from the end, or it's
3417-
* simply character 0. We also handle open quotes and parentheses.
3409+
* If no end found we return an empty string, because there is no word
3410+
* before the point
34183411
*/
3419-
for (start = end; start > 0; start--)
3412+
if (end < 0)
3413+
{
3414+
point = end;
3415+
s = pg_strdup("");
3416+
}
3417+
else
34203418
{
3421-
if (buf[start] == '"')
3422-
inquotes = !inquotes;
3423-
if (inquotes == 0)
3419+
/*
3420+
* Otherwise we now look for the start. The start is either the
3421+
* last character before any word-break character going backwards
3422+
* from the end, or it's simply character 0. We also handle open
3423+
* quotes and parentheses.
3424+
*/
3425+
bool inquotes = false;
3426+
int parentheses = 0;
3427+
3428+
for (start = end; start > 0; start--)
34243429
{
3425-
if (buf[start] == ')')
3426-
parentheses++;
3427-
else if (buf[start] == '(')
3430+
if (buf[start] == '"')
3431+
inquotes = !inquotes;
3432+
else if (!inquotes)
34283433
{
3429-
if (--parentheses <= 0)
3434+
if (buf[start] == ')')
3435+
parentheses++;
3436+
else if (buf[start] == '(')
3437+
{
3438+
if (--parentheses <= 0)
3439+
break;
3440+
}
3441+
else if (parentheses == 0 &&
3442+
strchr(WORD_BREAKS, buf[start - 1]))
34303443
break;
34313444
}
3432-
else if (parentheses == 0 &&
3433-
strchr(WORD_BREAKS, buf[start - 1]))
3434-
break;
34353445
}
3436-
}
34373446

3438-
point = start - 1;
3439-
}
3447+
point = start - 1;
34403448

3441-
/* make a copy */
3442-
s = pg_malloc(end - start + 2);
3443-
strlcpy(s, &buf[start], end - start + 2);
3449+
/* make a copy of chars from start to end inclusive */
3450+
s = pg_malloc(end - start + 2);
3451+
strlcpy(s, &buf[start], end - start + 2);
3452+
}
34443453

3445-
return s;
3454+
*previous_words++ = s;
3455+
}
34463456
}
34473457

34483458
#ifdef NOT_USED

0 commit comments

Comments
 (0)