Teach psql to do tab completion for names of psql variables.

Completion is supported in the context of \set and when interpolating
a variable value using :foo etc.

In passing, fix some places in tab-complete.c that weren't following
project style for comment formatting.

Pavel Stehule, reviewed by Itagaki Takahiro
This commit is contained in:
Tom Lane 2010-10-10 18:42:35 -04:00
parent 2ec993a7cb
commit b48b9cb3a4
1 changed files with 134 additions and 63 deletions

View File

@ -576,20 +576,24 @@ static char *complete_from_query(const char *text, int state);
static char *complete_from_schema_query(const char *text, int state); static char *complete_from_schema_query(const char *text, int state);
static char *_complete_from_query(int is_schema_query, static char *_complete_from_query(int is_schema_query,
const char *text, int state); const char *text, int state);
static char *complete_from_const(const char *text, int state);
static char *complete_from_list(const char *text, int state); static char *complete_from_list(const char *text, int state);
static char *complete_from_const(const char *text, int state);
static char **complete_from_variables(char *text,
const char *prefix, const char *suffix);
static PGresult *exec_query(const char *query); static PGresult *exec_query(const char *query);
static char *previous_word(int point, int skip); static char *previous_word(int point, int skip);
#if 0 #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);
static char *dequote_file_name(char *text, char quote_char); static char *dequote_file_name(char *text, char quote_char);
#endif #endif
/* Initialize the readline library for our purposes. */ /*
* Initialize the readline library for our purposes.
*/
void void
initialize_readline(void) initialize_readline(void)
{ {
@ -607,10 +611,13 @@ initialize_readline(void)
} }
/* The completion function. Acc. to readline spec this gets passed the text /*
entered to far and its start and end in the readline buffer. The return value * The completion function.
is some partially obscure list format that can be generated by the readline *
libraries completion_matches() function, so we don't have to worry about it. * According to readline spec this gets passed the text entered so far and its
* start and end positions in the readline buffer. The return value is some
* partially obscure list format that can be generated by readline's
* completion_matches() function, so we don't have to worry about it.
*/ */
static char ** static char **
psql_completion(char *text, int start, int end) psql_completion(char *text, int start, int end)
@ -2512,7 +2519,6 @@ psql_completion(char *text, int start, int end)
pg_strcasecmp(prev3_wd, "\\copy") != 0) pg_strcasecmp(prev3_wd, "\\copy") != 0)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv, NULL); COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv, NULL);
/* Backslash commands */ /* Backslash commands */
/* TODO: \dc \dd \dl */ /* TODO: \dc \dd \dl */
else if (strcmp(prev_wd, "\\connect") == 0 || strcmp(prev_wd, "\\c") == 0) else if (strcmp(prev_wd, "\\connect") == 0 || strcmp(prev_wd, "\\c") == 0)
@ -2582,6 +2588,10 @@ psql_completion(char *text, int start, int end)
COMPLETE_WITH_LIST(my_list); COMPLETE_WITH_LIST(my_list);
} }
else if (strcmp(prev_wd, "\\set") == 0)
{
matches = complete_from_variables(text, "", "");
}
else if (strcmp(prev_wd, "\\sf") == 0 || strcmp(prev_wd, "\\sf+") == 0) else if (strcmp(prev_wd, "\\sf") == 0 || strcmp(prev_wd, "\\sf+") == 0)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL); COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
else if (strcmp(prev_wd, "\\cd") == 0 || else if (strcmp(prev_wd, "\\cd") == 0 ||
@ -2594,6 +2604,16 @@ psql_completion(char *text, int start, int end)
) )
matches = completion_matches(text, filename_completion_function); matches = completion_matches(text, filename_completion_function);
/* Variable interpolation */
else if (text[0] == ':' && text[1] != ':')
{
if (text[1] == '\'')
matches = complete_from_variables(text, ":'", "'");
else if (text[1] == '"')
matches = complete_from_variables(text, ":\"", "\"");
else
matches = complete_from_variables(text, ":", "");
}
/* /*
* Finally, we look through the list of "things", such as TABLE, INDEX and * Finally, we look through the list of "things", such as TABLE, INDEX and
@ -2643,22 +2663,23 @@ psql_completion(char *text, int start, int end)
} }
/*
/* GENERATOR FUNCTIONS * GENERATOR FUNCTIONS
*
These functions do all the actual work of completing the input. They get * These functions do all the actual work of completing the input. They get
passed the text so far and the count how many times they have been called so * passed the text so far and the count how many times they have been called
far with the same text. * so far with the same text.
If you read the above carefully, you'll see that these don't get called * If you read the above carefully, you'll see that these don't get called
directly but through the readline interface. * directly but through the readline interface.
The return value is expected to be the full completion of the text, going * The return value is expected to be the full completion of the text, going
through a list each time, or NULL if there are no more matches. The string * through a list each time, or NULL if there are no more matches. The string
will be free()'d by readline, so you must run it through strdup() or * will be free()'d by readline, so you must run it through strdup() or
something of that sort. * something of that sort.
*/ */
/* This one gives you one from a list of things you can put after CREATE /*
as defined above. * This one gives you one from a list of things you can put after CREATE
* as defined above.
*/ */
static char * static char *
create_command_generator(const char *text, int state) create_command_generator(const char *text, int state)
@ -2677,7 +2698,8 @@ create_command_generator(const char *text, int state)
/* find something that matches */ /* find something that matches */
while ((name = words_after_create[list_index++].name)) while ((name = words_after_create[list_index++].name))
{ {
if ((pg_strncasecmp(name, text, string_length) == 0) && !words_after_create[list_index - 1].noshow) if ((pg_strncasecmp(name, text, string_length) == 0) &&
!words_after_create[list_index - 1].noshow)
return pg_strdup(name); return pg_strdup(name);
} }
/* if nothing matches, return NULL */ /* if nothing matches, return NULL */
@ -2745,26 +2767,27 @@ complete_from_schema_query(const char *text, int state)
} }
/* This creates a list of matching things, according to a query pointed to /*
by completion_charp. * This creates a list of matching things, according to a query pointed to
The query can be one of two kinds: * by completion_charp.
- A simple query which must contain a %d and a %s, which will be replaced * The query can be one of two kinds:
by the string length of the text and the text itself. The query may also *
have up to four more %s in it; the first two such will be replaced by the * 1. A simple query which must contain a %d and a %s, which will be replaced
value of completion_info_charp, the next two by the value of * by the string length of the text and the text itself. The query may also
completion_info_charp2. * have up to four more %s in it; the first two such will be replaced by the
or: * value of completion_info_charp, the next two by the value of
- A schema query used for completion of both schema and relation names; * completion_info_charp2.
these are more complex and must contain in the following order: *
%d %s %d %s %d %s %s %d %s * 2. A schema query used for completion of both schema and relation names.
where %d is the string length of the text and %s the text itself. * These are more complex and must contain in the following order:
* %d %s %d %s %d %s %s %d %s
It is assumed that strings should be escaped to become SQL literals * where %d is the string length of the text and %s the text itself.
(that is, what is in the query is actually ... '%s' ...) *
* It is assumed that strings should be escaped to become SQL literals
See top of file for examples of both kinds of query. * (that is, what is in the query is actually ... '%s' ...)
*
* See top of file for examples of both kinds of query.
*/ */
static char * static char *
_complete_from_query(int is_schema_query, const char *text, int state) _complete_from_query(int is_schema_query, const char *text, int state)
{ {
@ -2950,9 +2973,10 @@ _complete_from_query(int is_schema_query, const char *text, int state)
} }
/* This function returns in order one of a fixed, NULL pointer terminated list /*
of strings (if matching). This can be used if there are only a fixed number * This function returns in order one of a fixed, NULL pointer terminated list
SQL words that can appear at certain spot. * of strings (if matching). This can be used if there are only a fixed number
* SQL words that can appear at certain spot.
*/ */
static char * static char *
complete_from_list(const char *text, int state) complete_from_list(const char *text, int state)
@ -3006,11 +3030,12 @@ complete_from_list(const char *text, int state)
} }
/* This function returns one fixed string the first time even if it doesn't /*
match what's there, and nothing the second time. This should be used if there * This function returns one fixed string the first time even if it doesn't
is only one possibility that can appear at a certain spot, so misspellings * match what's there, and nothing the second time. This should be used if
will be overwritten. * there is only one possibility that can appear at a certain spot, so
The string to be passed must be in completion_charp. * misspellings will be overwritten. The string to be passed must be in
* completion_charp.
*/ */
static char * static char *
complete_from_const(const char *text, int state) complete_from_const(const char *text, int state)
@ -3026,6 +3051,55 @@ complete_from_const(const char *text, int state)
} }
/*
* This function supports completion with the name of a psql variable.
* The variable names can be prefixed and suffixed with additional text
* to support quoting usages.
*/
static char **
complete_from_variables(char *text, const char *prefix, const char *suffix)
{
char **matches;
int overhead = strlen(prefix) + strlen(suffix) + 1;
const char **varnames;
int nvars = 0;
int maxvars = 100;
int i;
struct _variable *ptr;
varnames = (const char **) pg_malloc((maxvars + 1) * sizeof(char *));
for (ptr = pset.vars->next; ptr; ptr = ptr->next)
{
char *buffer;
if (nvars >= maxvars)
{
maxvars *= 2;
varnames = (const char **) realloc(varnames,
(maxvars + 1) * sizeof(char *));
if (!varnames)
{
psql_error("out of memory\n");
exit(EXIT_FAILURE);
}
}
buffer = (char *) pg_malloc(strlen(ptr->name) + overhead);
sprintf(buffer, "%s%s%s", prefix, ptr->name, suffix);
varnames[nvars++] = buffer;
}
varnames[nvars] = NULL;
COMPLETE_WITH_LIST(varnames);
for (i = 0; i < nvars; i++)
free((void *) varnames[i]);
free(varnames);
return matches;
}
/* HELPER FUNCTIONS */ /* HELPER FUNCTIONS */
@ -3046,7 +3120,7 @@ exec_query(const char *query)
if (PQresultStatus(result) != PGRES_TUPLES_OK) if (PQresultStatus(result) != PGRES_TUPLES_OK)
{ {
#if 0 #ifdef NOT_USED
psql_error("tab completion query failed: %s\nQuery was:\n%s\n", psql_error("tab completion query failed: %s\nQuery was:\n%s\n",
PQerrorMessage(pset.db), query); PQerrorMessage(pset.db), query);
#endif #endif
@ -3058,7 +3132,6 @@ exec_query(const char *query)
} }
/* /*
* Return the word (space delimited) before point. Set skip > 0 to * Return the word (space delimited) before point. Set skip > 0 to
* skip that many words; e.g. skip=1 finds the word before the * skip that many words; e.g. skip=1 finds the word before the
@ -3133,7 +3206,7 @@ previous_word(int point, int skip)
return s; return s;
} }
#if 0 #ifdef NOT_USED
/* /*
* Surround a string with single quotes. This works for both SQL and * Surround a string with single quotes. This works for both SQL and
@ -3158,8 +3231,6 @@ quote_file_name(char *text, int match_type, char *quote_pointer)
return s; return s;
} }
static char * static char *
dequote_file_name(char *text, char quote_char) dequote_file_name(char *text, char quote_char)
{ {
@ -3175,6 +3246,6 @@ dequote_file_name(char *text, char quote_char)
return s; return s;
} }
#endif /* 0 */ #endif /* NOT_USED */
#endif /* USE_READLINE */ #endif /* USE_READLINE */