]> granicus.if.org Git - postgresql/commitdiff
Teach psql to do tab completion for names of psql variables.
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 10 Oct 2010 22:42:35 +0000 (18:42 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 10 Oct 2010 22:42:35 +0000 (18:42 -0400)
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

src/bin/psql/tab-complete.c

index 51010e16217701037d697cb447e2dc54b6a82ee4..00c97d374d3b4bed368631250ad5ec0b3ed9ede4 100644 (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_query(int is_schema_query,
                                         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_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 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 *dequote_file_name(char *text, char quote_char);
 #endif
 
 
-/* Initialize the readline library for our purposes. */
+/*
+ * Initialize the readline library for our purposes.
+ */
 void
 initialize_readline(void)
 {
@@ -607,11 +611,14 @@ 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
-   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.
-*/
+/*
+ * The completion function.
+ *
+ * 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 **
 psql_completion(char *text, int start, int end)
 {
@@ -1943,7 +1950,7 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev_wd, "WRAPPER") == 0)
                COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
 
-/* GRANT && REVOKE*/
+/* GRANT && REVOKE */
        /* Complete GRANT/REVOKE with a list of privileges */
        else if (pg_strcasecmp(prev_wd, "GRANT") == 0 ||
                         pg_strcasecmp(prev_wd, "REVOKE") == 0)
@@ -2512,7 +2519,6 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev3_wd, "\\copy") != 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv, NULL);
 
-
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
        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);
        }
+       else if (strcmp(prev_wd, "\\set") == 0)
+       {
+               matches = complete_from_variables(text, "", "");
+       }
        else if (strcmp(prev_wd, "\\sf") == 0 || strcmp(prev_wd, "\\sf+") == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
        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);
 
+/* 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
@@ -2643,23 +2663,24 @@ psql_completion(char *text, int start, int end)
 }
 
 
+/*
+ * GENERATOR FUNCTIONS
+ *
+ * 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 far with the same text.
+ * If you read the above carefully, you'll see that these don't get called
+ * directly but through the readline interface.
+ * 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
+ * will be free()'d by readline, so you must run it through strdup() or
+ * something of that sort.
+ */
 
-/* GENERATOR FUNCTIONS
-
-   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
-   far with the same text.
-   If you read the above carefully, you'll see that these don't get called
-   directly but through the readline interface.
-   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
-   will be free()'d by readline, so you must run it through strdup() or
-   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 *
 create_command_generator(const char *text, int state)
 {
@@ -2677,7 +2698,8 @@ create_command_generator(const char *text, int state)
        /* find something that matches */
        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);
        }
        /* 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.
-   The query can be one of two kinds:
-   - A simple query which must contain a %d and a %s, which will be replaced
-   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
-   value of completion_info_charp, the next two by the value of
-   completion_info_charp2.
-        or:
-   - A schema query used for completion of both schema and relation names;
-   these are more complex and must contain in the following order:
-        %d %s %d %s %d %s %s %d %s
-   where %d is the string length of the text and %s the text itself.
-
-   It is assumed that strings should be escaped to become SQL literals
-   (that is, what is in the query is actually ... '%s' ...)
-
-   See top of file for examples of both kinds of query.
-*/
-
+/*
+ * This creates a list of matching things, according to a query pointed to
+ * by completion_charp.
+ * The query can be one of two kinds:
+ *
+ * 1. A simple query which must contain a %d and a %s, which will be replaced
+ * 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
+ * value of completion_info_charp, the next two by the value of
+ * completion_info_charp2.
+ *
+ * 2. A schema query used for completion of both schema and relation names.
+ * These are more complex and must contain in the following order:
+ * %d %s %d %s %d %s %s %d %s
+ * where %d is the string length of the text and %s the text itself.
+ *
+ * It is assumed that strings should be escaped to become SQL literals
+ * (that is, what is in the query is actually ... '%s' ...)
+ *
+ * See top of file for examples of both kinds of query.
+ */
 static char *
 _complete_from_query(int is_schema_query, const char *text, int state)
 {
@@ -2950,10 +2973,11 @@ _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
-   SQL words that can appear at certain spot.
-*/
+/*
+ * 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
+ * SQL words that can appear at certain spot.
+ */
 static char *
 complete_from_list(const char *text, int state)
 {
@@ -3006,12 +3030,13 @@ 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
-   is only one possibility that can appear at a certain spot, so misspellings
-   will be overwritten.
-   The string to be passed must be in completion_charp.
-*/
+/*
+ * 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 is only one possibility that can appear at a certain spot, so
+ * misspellings will be overwritten.  The string to be passed must be in
+ * completion_charp.
+ */
 static char *
 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 */
 
@@ -3046,7 +3120,7 @@ exec_query(const char *query)
 
        if (PQresultStatus(result) != PGRES_TUPLES_OK)
        {
-#if 0
+#ifdef NOT_USED
                psql_error("tab completion query failed: %s\nQuery was:\n%s\n",
                                   PQerrorMessage(pset.db), query);
 #endif
@@ -3058,7 +3132,6 @@ exec_query(const char *query)
 }
 
 
-
 /*
  * Return the word (space delimited) before point. Set skip > 0 to
  * 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;
 }
 
-#if 0
+#ifdef NOT_USED
 
 /*
  * 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;
 }
 
-
-
 static char *
 dequote_file_name(char *text, char quote_char)
 {
@@ -3175,6 +3246,6 @@ dequote_file_name(char *text, char quote_char)
 
        return s;
 }
-#endif   /* 0 */
+#endif   /* NOT_USED */
 
 #endif   /* USE_READLINE */