diff options
| author | Masahiko Sawada <msawada@postgresql.org> | 2025-11-04 10:40:58 -0800 |
|---|---|---|
| committer | Masahiko Sawada <msawada@postgresql.org> | 2025-11-04 10:40:58 -0800 |
| commit | 02fd47dbfade9b86ae4c34b5b01e10abb6dc45dc (patch) | |
| tree | 801053f454c7875e26b80161247102decc1a8b09 | |
| parent | be9efd4929b0f4843cdde38866421c4d486b45e3 (diff) | |
psql: Improve tab completion for COPY ... STDIN/STDOUT.
This commit enhances tab completion for both COPY FROM and COPY TO
commands to suggest STDIN and STDOUT, respectively.
To make suggesting both file names and keywords easier, it introduces
a new COMPLETE_WITH_FILES_PLUS() macro.
Author: Yugo Nagata <nagata@sraoss.co.jp>
Reviewed-by: Masahiko Sawada <sawada.mshk@gmail.com>
Discussion: https://postgr.es/m/20250605100835.b396f9d656df1018f65a4556@sraoss.co.jp
| -rw-r--r-- | src/bin/psql/tab-complete.in.c | 84 |
1 files changed, 77 insertions, 7 deletions
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c index 0b7b3aead7c..5f59564b1e3 100644 --- a/src/bin/psql/tab-complete.in.c +++ b/src/bin/psql/tab-complete.in.c @@ -443,13 +443,23 @@ do { \ matches = rl_completion_matches(text, complete_from_schema_query); \ } while (0) -#define COMPLETE_WITH_FILES(escape, force_quote) \ +#define COMPLETE_WITH_FILES_LIST(escape, force_quote, list) \ do { \ completion_charp = escape; \ + completion_charpp = list; \ completion_force_quote = force_quote; \ matches = rl_completion_matches(text, complete_from_files); \ } while (0) +#define COMPLETE_WITH_FILES(escape, force_quote) \ + COMPLETE_WITH_FILES_LIST(escape, force_quote, NULL) + +#define COMPLETE_WITH_FILES_PLUS(escape, force_quote, ...) \ +do { \ + static const char *const list[] = { __VA_ARGS__, NULL }; \ + COMPLETE_WITH_FILES_LIST(escape, force_quote, list); \ +} while (0) + #define COMPLETE_WITH_GENERATOR(generator) \ matches = rl_completion_matches(text, generator) @@ -1485,6 +1495,7 @@ static void append_variable_names(char ***varnames, int *nvars, static char **complete_from_variables(const char *text, const char *prefix, const char *suffix, bool need_value); static char *complete_from_files(const char *text, int state); +static char *_complete_from_files(const char *text, int state); static char *pg_strdup_keyword_case(const char *s, const char *ref); static char *escape_string(const char *text); @@ -3325,11 +3336,17 @@ match_previous_words(int pattern_id, /* Complete COPY <sth> */ else if (Matches("COPY|\\copy", MatchAny)) COMPLETE_WITH("FROM", "TO"); - /* Complete COPY <sth> FROM|TO with filename */ - else if (Matches("COPY", MatchAny, "FROM|TO")) - COMPLETE_WITH_FILES("", true); /* COPY requires quoted filename */ - else if (Matches("\\copy", MatchAny, "FROM|TO")) - COMPLETE_WITH_FILES("", false); + /* Complete COPY|\copy <sth> FROM|TO with filename or STDIN/STDOUT */ + else if (Matches("COPY|\\copy", MatchAny, "FROM|TO")) + { + /* COPY requires quoted filename */ + bool force_quote = HeadMatches("COPY"); + + if (TailMatches("FROM")) + COMPLETE_WITH_FILES_PLUS("", force_quote, "STDIN"); + else + COMPLETE_WITH_FILES_PLUS("", force_quote, "STDOUT"); + } /* Complete COPY <sth> TO <sth> */ else if (Matches("COPY|\\copy", MatchAny, "TO", MatchAny)) @@ -6251,6 +6268,59 @@ complete_from_variables(const char *text, const char *prefix, const char *suffix /* + * This function returns in order one of a fixed, NULL pointer terminated list + * of string that matches file names or optionally specified list of keywords. + * + * If completion_charpp is set to a null-terminated array of literal keywords, + * those keywords are added to the completion results alongside filenames if + * they case-insensitively match the current input. + */ +static char * +complete_from_files(const char *text, int state) +{ + static int list_index; + static bool files_done; + const char *item; + + /* Initialization */ + if (state == 0) + { + list_index = 0; + files_done = false; + } + + if (!files_done) + { + char *result = _complete_from_files(text, state); + + /* Return a filename that matches */ + if (result) + return result; + + /* There are no more matching files */ + files_done = true; + } + + if (!completion_charpp) + return NULL; + + /* + * Check for hard-wired keywords. These will only be returned if they + * match the input-so-far, ignoring case. + */ + while ((item = completion_charpp[list_index++])) + { + if (pg_strncasecmp(text, item, strlen(text)) == 0) + { + completion_force_quote = false; + return pg_strdup_keyword_case(item, text); + } + } + + return NULL; +} + +/* * This function wraps rl_filename_completion_function() to strip quotes from * the input before searching for matches and to quote any matches for which * the consuming command will require it. @@ -6264,7 +6334,7 @@ complete_from_variables(const char *text, const char *prefix, const char *suffix * quotes around the result. (The SQL COPY command requires that.) */ static char * -complete_from_files(const char *text, int state) +_complete_from_files(const char *text, int state) { #ifdef USE_FILENAME_QUOTING_FUNCTIONS |
