summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMasahiko Sawada <msawada@postgresql.org>2025-11-04 10:40:58 -0800
committerMasahiko Sawada <msawada@postgresql.org>2025-11-04 10:40:58 -0800
commit02fd47dbfade9b86ae4c34b5b01e10abb6dc45dc (patch)
tree801053f454c7875e26b80161247102decc1a8b09
parentbe9efd4929b0f4843cdde38866421c4d486b45e3 (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.c84
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