From c6a7d3bab493e8d589f24d239db00ce5c2934178 Mon Sep 17 00:00:00 2001 From: Masahiko Sawada Date: Mon, 22 Dec 2025 14:28:12 -0800 Subject: [PATCH] psql: Improve tab completion for COPY option lists. Previously, only the first option in a parenthesized option list was suggested by tab completion. This commit enhances tab completion for both COPY TO and COPY FROM commands to suggest options after each comma. Also add completion for HEADER and FREEZE option value candidates. Author: Yugo Nagata Reviewed-by: Masahiko Sawada Discussion: https://postgr.es/m/20250605100835.b396f9d656df1018f65a4556@sraoss.co.jp --- src/bin/psql/tab-complete.in.c | 60 ++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c index b1ff6f6cd94..ab2712216b5 100644 --- a/src/bin/psql/tab-complete.in.c +++ b/src/bin/psql/tab-complete.in.c @@ -3378,30 +3378,50 @@ match_previous_words(int pattern_id, Matches("COPY|\\copy", MatchAny, "FROM", "PROGRAM", MatchAny)) COMPLETE_WITH("WITH (", "WHERE"); - /* Complete COPY FROM [PROGRAM] filename WITH ( */ - else if (Matches("COPY|\\copy", MatchAny, "FROM", MatchAnyExcept("PROGRAM"), "WITH", "(") || - Matches("COPY|\\copy", MatchAny, "FROM", "PROGRAM", MatchAny, "WITH", "(")) - COMPLETE_WITH(Copy_from_options); + /* Complete COPY FROM|TO [PROGRAM] filename WITH ( */ + else if (HeadMatches("COPY|\\copy", MatchAny, "FROM|TO", MatchAnyExcept("PROGRAM"), "WITH", "(*") || + HeadMatches("COPY|\\copy", MatchAny, "FROM|TO", "PROGRAM", MatchAny, "WITH", "(*")) + { + if (!HeadMatches("COPY|\\copy", MatchAny, "FROM|TO", MatchAnyExcept("PROGRAM"), "WITH", "(*)") && + !HeadMatches("COPY|\\copy", MatchAny, "FROM|TO", "PROGRAM", MatchAny, "WITH", "(*)")) + { + /* + * This fires if we're in an unfinished parenthesized option list. + * get_previous_words treats a completed parenthesized option list + * as one word, so the above tests are correct. + */ - /* Complete COPY TO [PROGRAM] filename WITH ( */ - else if (Matches("COPY|\\copy", MatchAny, "TO", MatchAnyExcept("PROGRAM"), "WITH", "(") || - Matches("COPY|\\copy", MatchAny, "TO", "PROGRAM", MatchAny, "WITH", "(")) - COMPLETE_WITH(Copy_to_options); + if (ends_with(prev_wd, '(') || ends_with(prev_wd, ',')) + { + if (HeadMatches("COPY|\\copy", MatchAny, "FROM")) + COMPLETE_WITH(Copy_from_options); + else + COMPLETE_WITH(Copy_to_options); + } - /* Complete COPY FROM|TO [PROGRAM] WITH (FORMAT */ - else if (Matches("COPY|\\copy", MatchAny, "FROM|TO", MatchAnyExcept("PROGRAM"), "WITH", "(", "FORMAT") || - Matches("COPY|\\copy", MatchAny, "FROM|TO", "PROGRAM", MatchAny, "WITH", "(", "FORMAT")) - COMPLETE_WITH("binary", "csv", "text"); + /* Complete COPY FROM|TO filename WITH (FORMAT */ + else if (TailMatches("FORMAT")) + COMPLETE_WITH("binary", "csv", "text"); - /* Complete COPY FROM [PROGRAM] filename WITH (ON_ERROR */ - else if (Matches("COPY|\\copy", MatchAny, "FROM", MatchAnyExcept("PROGRAM"), "WITH", "(", "ON_ERROR") || - Matches("COPY|\\copy", MatchAny, "FROM", "PROGRAM", MatchAny, "WITH", "(", "ON_ERROR")) - COMPLETE_WITH("stop", "ignore"); + /* Complete COPY FROM|TO filename WITH (FREEZE */ + else if (TailMatches("FREEZE")) + COMPLETE_WITH("true", "false"); - /* Complete COPY FROM [PROGRAM] filename WITH (LOG_VERBOSITY */ - else if (Matches("COPY|\\copy", MatchAny, "FROM", MatchAnyExcept("PROGRAM"), "WITH", "(", "LOG_VERBOSITY") || - Matches("COPY|\\copy", MatchAny, "FROM", "PROGRAM", MatchAny, "WITH", "(", "LOG_VERBOSITY")) - COMPLETE_WITH("silent", "default", "verbose"); + /* Complete COPY FROM|TO filename WITH (HEADER */ + else if (TailMatches("HEADER")) + COMPLETE_WITH("true", "false", "MATCH"); + + /* Complete COPY FROM filename WITH (ON_ERROR */ + else if (TailMatches("ON_ERROR")) + COMPLETE_WITH("stop", "ignore"); + + /* Complete COPY FROM filename WITH (LOG_VERBOSITY */ + else if (TailMatches("LOG_VERBOSITY")) + COMPLETE_WITH("silent", "default", "verbose"); + } + + /* A completed parenthesized option list should be caught below */ + } /* Complete COPY FROM [PROGRAM] WITH () */ else if (Matches("COPY|\\copy", MatchAny, "FROM", MatchAnyExcept("PROGRAM"), "WITH", MatchAny) ||