From 8c7e25e4a2d939a32751bd9a0d487c510ec66191 Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Wed, 21 Sep 2022 13:58:50 +0900 Subject: [PATCH 2/2] psql: Add PartialMatches() macro for better tab-completion. --- src/bin/psql/tab-complete.c | 152 +++++++++++++++++++++++++++--------- 1 file changed, 113 insertions(+), 39 deletions(-) diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 820f47d23a..0b8c252615 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -1582,6 +1582,65 @@ HeadMatchesImpl(bool case_sensitive, return true; } +/* + * Implementation of PartialMatches and PartialMatchesCS macros: do parts of + * the words in previous_words match the variadic arguments? + */ +static bool +PartialMatchesImpl(bool case_sensitive, + int previous_words_count, char **previous_words, + int narg,...) +{ + va_list args; + char *firstarg = NULL; + + if (previous_words_count < narg) + return false; + + for (int startpos = 0; startpos < previous_words_count; startpos++) + { + int argno; + + if (firstarg == NULL) + { + va_start(args, narg); + firstarg = va_arg(args, char *); + } + + if (!word_matches(firstarg, + previous_words[previous_words_count - startpos - 1], + case_sensitive)) + continue; + + if (previous_words_count - startpos < narg) + { + va_end(args); + return false; + } + + for (argno = 1; argno < narg; argno++) + { + const char *arg = va_arg(args, const char *); + + if (!word_matches(arg, + previous_words[previous_words_count - argno - startpos - 1], + case_sensitive)) + break; + } + + va_end(args); + firstarg = NULL; + + if (argno == narg) + return true; + } + + if (firstarg != NULL) + va_end(args); + + return false; +} + /* * Check if the final character of 's' is 'c'. */ @@ -1663,6 +1722,16 @@ psql_completion(const char *text, int start, int end) HeadMatchesImpl(true, previous_words_count, previous_words, \ VA_ARGS_NARGS(__VA_ARGS__), __VA_ARGS__) + /* Match N words on the line partially, case-insensitively. */ +#define PartialMatches(...) \ + PartialMatchesImpl(false, previous_words_count, previous_words, \ + VA_ARGS_NARGS(__VA_ARGS__), __VA_ARGS__) + + /* Match N words on the line partially, case-sensitively. */ +#define PartialMatchesCS(...) \ + PartialMatchesImpl(true, previous_words_count, previous_words, \ + VA_ARGS_NARGS(__VA_ARGS__), __VA_ARGS__) + /* Known command-starting keywords. */ static const char *const sql_commands[] = { "ABORT", "ALTER", "ANALYZE", "BEGIN", "CALL", "CHECKPOINT", "CLOSE", "CLUSTER", @@ -4108,46 +4177,51 @@ psql_completion(const char *text, int start, int end) TailMatches("MERGE", "INTO", MatchAny, MatchAny, "USING", MatchAny, MatchAnyExcept("ON|AS"))) COMPLETE_WITH("ON"); - /* Complete MERGE INTO ... ON with target table attributes */ - else if (TailMatches("INTO", MatchAny, "USING", MatchAny, "ON")) - COMPLETE_WITH_ATTR(prev4_wd); - else if (TailMatches("INTO", MatchAny, "AS", MatchAny, "USING", MatchAny, "AS", MatchAny, "ON")) - COMPLETE_WITH_ATTR(prev8_wd); - else if (TailMatches("INTO", MatchAny, MatchAny, "USING", MatchAny, MatchAny, "ON")) - COMPLETE_WITH_ATTR(prev6_wd); + else if (PartialMatches("MERGE", "INTO", MatchAny, "USING") || + PartialMatches("MERGE", "INTO", MatchAny, "AS", MatchAny, "USING") || + PartialMatches("MERGE", "INTO", MatchAny, MatchAny, "USING")) + { + /* Complete MERGE INTO ... ON with target table attributes */ + if (TailMatches("INTO", MatchAny, "USING", MatchAny, "ON")) + COMPLETE_WITH_ATTR(prev4_wd); + else if (TailMatches("INTO", MatchAny, "AS", MatchAny, "USING", MatchAny, "AS", MatchAny, "ON")) + COMPLETE_WITH_ATTR(prev8_wd); + else if (TailMatches("INTO", MatchAny, MatchAny, "USING", MatchAny, MatchAny, "ON")) + COMPLETE_WITH_ATTR(prev6_wd); - /* - * Complete ... USING [[AS] alias] ON join condition - * (consisting of one or three words typically used) with WHEN [NOT] - * MATCHED - */ - else if (TailMatches("USING", MatchAny, "ON", MatchAny) || - TailMatches("USING", MatchAny, "AS", MatchAny, "ON", MatchAny) || - TailMatches("USING", MatchAny, MatchAny, "ON", MatchAny) || - TailMatches("USING", MatchAny, "ON", MatchAny, MatchAnyExcept("WHEN"), MatchAnyExcept("WHEN")) || - TailMatches("USING", MatchAny, "AS", MatchAny, "ON", MatchAny, MatchAnyExcept("WHEN"), MatchAnyExcept("WHEN")) || - TailMatches("USING", MatchAny, MatchAny, "ON", MatchAny, MatchAnyExcept("WHEN"), MatchAnyExcept("WHEN"))) - COMPLETE_WITH("WHEN MATCHED", "WHEN NOT MATCHED"); - else if (TailMatches("USING", MatchAny, "ON", MatchAny, "WHEN") || - TailMatches("USING", MatchAny, "AS", MatchAny, "ON", MatchAny, "WHEN") || - TailMatches("USING", MatchAny, MatchAny, "ON", MatchAny, "WHEN") || - TailMatches("USING", MatchAny, "ON", MatchAny, MatchAny, MatchAny, "WHEN") || - TailMatches("USING", MatchAny, "AS", MatchAny, "ON", MatchAny, MatchAny, MatchAny, "WHEN") || - TailMatches("USING", MatchAny, MatchAny, "ON", MatchAny, MatchAny, MatchAny, "WHEN")) - COMPLETE_WITH("MATCHED", "NOT MATCHED"); - - /* Complete ... WHEN [NOT] MATCHED with THEN/AND */ - else if (TailMatches("WHEN", "MATCHED") || - TailMatches("WHEN", "NOT", "MATCHED")) - COMPLETE_WITH("THEN", "AND"); - - /* Complete ... WHEN MATCHED THEN with UPDATE SET/DELETE/DO NOTHING */ - else if (TailMatches("WHEN", "MATCHED", "THEN")) - COMPLETE_WITH("UPDATE SET", "DELETE", "DO NOTHING"); - - /* Complete ... WHEN NOT MATCHED THEN with INSERT/DO NOTHING */ - else if (TailMatches("WHEN", "NOT", "MATCHED", "THEN")) - COMPLETE_WITH("INSERT", "DO NOTHING"); + /* + * Complete ... USING [[AS] alias] ON join condition + * (consisting of one or three words typically used) with WHEN [NOT] + * MATCHED + */ + else if (TailMatches("USING", MatchAny, "ON", MatchAny) || + TailMatches("USING", MatchAny, "AS", MatchAny, "ON", MatchAny) || + TailMatches("USING", MatchAny, MatchAny, "ON", MatchAny) || + TailMatches("USING", MatchAny, "ON", MatchAny, MatchAnyExcept("WHEN"), MatchAnyExcept("WHEN")) || + TailMatches("USING", MatchAny, "AS", MatchAny, "ON", MatchAny, MatchAnyExcept("WHEN"), MatchAnyExcept("WHEN")) || + TailMatches("USING", MatchAny, MatchAny, "ON", MatchAny, MatchAnyExcept("WHEN"), MatchAnyExcept("WHEN"))) + COMPLETE_WITH("WHEN MATCHED", "WHEN NOT MATCHED"); + else if (TailMatches("USING", MatchAny, "ON", MatchAny, "WHEN") || + TailMatches("USING", MatchAny, "AS", MatchAny, "ON", MatchAny, "WHEN") || + TailMatches("USING", MatchAny, MatchAny, "ON", MatchAny, "WHEN") || + TailMatches("USING", MatchAny, "ON", MatchAny, MatchAny, MatchAny, "WHEN") || + TailMatches("USING", MatchAny, "AS", MatchAny, "ON", MatchAny, MatchAny, MatchAny, "WHEN") || + TailMatches("USING", MatchAny, MatchAny, "ON", MatchAny, MatchAny, MatchAny, "WHEN")) + COMPLETE_WITH("MATCHED", "NOT MATCHED"); + + /* Complete ... WHEN [NOT] MATCHED with THEN/AND */ + else if (TailMatches("WHEN", "MATCHED") || + TailMatches("WHEN", "NOT", "MATCHED")) + COMPLETE_WITH("THEN", "AND"); + + /* Complete ... WHEN MATCHED THEN with UPDATE SET/DELETE/DO NOTHING */ + else if (TailMatches("WHEN", "MATCHED", "THEN")) + COMPLETE_WITH("UPDATE SET", "DELETE", "DO NOTHING"); + + /* Complete ... WHEN NOT MATCHED THEN with INSERT/DO NOTHING */ + else if (TailMatches("WHEN", "NOT", "MATCHED", "THEN")) + COMPLETE_WITH("INSERT", "DO NOTHING"); + } /* NOTIFY --- can be inside EXPLAIN, RULE, etc */ else if (TailMatches("NOTIFY")) -- 2.37.1