From 6fb82cb4e9521d1c6086c4848cf1733c4009c0cc Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sat, 13 Jul 2024 12:11:21 -0400
Subject: [PATCH v1 2/5] Split out most of psql_completion into a separate
 function.

This is in preparation for converting the giant else-if chain
into a switch statement with a loop around it.  If we keep the
code where it is, it'll need several additional tab stops of
indentation; but as a separate function there'll be at most
one more tab stop.

The first and last couple of bits of psql_completion are not
based on HeadMatches/TailMatches/Matches tests, so they stay
where they are; they won't become part of the switch.
---
 src/bin/psql/tab-complete.c | 163 +++++++++++++++++++++---------------
 1 file changed, 97 insertions(+), 66 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 2878506c30..b628cc3999 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1195,6 +1195,20 @@ static const VersionedQuery Query_for_list_of_subscriptions[] = {
 	{0, NULL}
 };
 
+ /* Known command-starting keywords. */
+static const char *const sql_commands[] = {
+	"ABORT", "ALTER", "ANALYZE", "BEGIN", "CALL", "CHECKPOINT", "CLOSE", "CLUSTER",
+	"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
+	"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN",
+	"FETCH", "GRANT", "IMPORT FOREIGN SCHEMA", "INSERT INTO", "LISTEN", "LOAD", "LOCK",
+	"MERGE INTO", "MOVE", "NOTIFY", "PREPARE",
+	"REASSIGN", "REFRESH MATERIALIZED VIEW", "REINDEX", "RELEASE",
+	"RESET", "REVOKE", "ROLLBACK",
+	"SAVEPOINT", "SECURITY LABEL", "SELECT", "SET", "SHOW", "START",
+	"TABLE", "TRUNCATE", "UNLISTEN", "UPDATE", "VACUUM", "VALUES", "WITH",
+	NULL
+};
+
 /*
  * This is a list of all "things" in Pgsql, which can show up after CREATE or
  * DROP; and there is also a query to get a list of them.
@@ -1340,6 +1354,9 @@ static const char *const view_optional_parameters[] = {
 
 /* Forward declaration of functions */
 static char **psql_completion(const char *text, int start, int end);
+static char **match_previous_words(const char *text, int start, int end,
+								   char **previous_words,
+								   int previous_words_count);
 static char *create_command_generator(const char *text, int state);
 static char *drop_command_generator(const char *text, int state);
 static char *alter_command_generator(const char *text, int state);
@@ -1787,20 +1804,6 @@ psql_completion(const char *text, int start, int end)
 	HeadMatchesImpl(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",
-		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
-		"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN",
-		"FETCH", "GRANT", "IMPORT FOREIGN SCHEMA", "INSERT INTO", "LISTEN", "LOAD", "LOCK",
-		"MERGE INTO", "MOVE", "NOTIFY", "PREPARE",
-		"REASSIGN", "REFRESH MATERIALIZED VIEW", "REINDEX", "RELEASE",
-		"RESET", "REVOKE", "ROLLBACK",
-		"SAVEPOINT", "SECURITY LABEL", "SELECT", "SET", "SHOW", "START",
-		"TABLE", "TRUNCATE", "UNLISTEN", "UPDATE", "VACUUM", "VALUES", "WITH",
-		NULL
-	};
-
 	/* psql's backslash commands. */
 	static const char *const backslash_commands[] = {
 		"\\a",
@@ -1887,6 +1890,86 @@ psql_completion(const char *text, int start, int end)
 	else if (previous_words_count == 0)
 		COMPLETE_WITH_LIST(sql_commands);
 
+	/* Else try completions based on matching patterns of previous words */
+	else if ((matches = match_previous_words(text, start, end,
+											 previous_words,
+											 previous_words_count)) != NULL)
+		 /* skip */ ;
+
+	/*
+	 * Finally, we look through the list of "things", such as TABLE, INDEX and
+	 * check if that was the previous word. If so, execute the query to get a
+	 * list of them.
+	 */
+	else
+	{
+		const pgsql_thing_t *wac;
+
+		for (wac = words_after_create; wac->name != NULL; wac++)
+		{
+			if (pg_strcasecmp(prev_wd, wac->name) == 0)
+			{
+				if (wac->query)
+					COMPLETE_WITH_QUERY_LIST(wac->query,
+											 wac->keywords);
+				else if (wac->vquery)
+					COMPLETE_WITH_VERSIONED_QUERY_LIST(wac->vquery,
+													   wac->keywords);
+				else if (wac->squery)
+					COMPLETE_WITH_VERSIONED_SCHEMA_QUERY_LIST(wac->squery,
+															  wac->keywords);
+				break;
+			}
+		}
+	}
+
+	/*
+	 * If we still don't have anything to match we have to fabricate some sort
+	 * of default list. If we were to just return NULL, readline automatically
+	 * attempts filename completion, and that's usually no good.
+	 */
+	if (matches == NULL)
+	{
+		COMPLETE_WITH_CONST(true, "");
+		/* Also, prevent Readline from appending stuff to the non-match */
+		rl_completion_append_character = '\0';
+#ifdef HAVE_RL_COMPLETION_SUPPRESS_QUOTE
+		rl_completion_suppress_quote = 1;
+#endif
+	}
+
+	/* free storage */
+	free(previous_words);
+	free(words_buffer);
+	free(text_copy);
+	free(completion_ref_object);
+	completion_ref_object = NULL;
+	free(completion_ref_schema);
+	completion_ref_schema = NULL;
+
+	/* Return our Grand List O' Matches */
+	return matches;
+}
+
+/*
+ * Subroutine to try matches based on previous_words.
+ *
+ * This is split out of psql_completion() simply to reduce code
+ * indentation level.
+ *
+ * Returns a matches list, or NULL if no match.
+ */
+static char **
+match_previous_words(const char *text, int start, int end,
+					 char **previous_words, int previous_words_count)
+{
+	/* This is the variable we'll return. */
+	char	  **matches = NULL;
+
+	/* Dummy, to be replaced by switch */
+	if (0)
+		;
+
 /* CREATE */
 	/* complete with something you can create */
 	else if (TailMatches("CREATE"))
@@ -5140,58 +5223,6 @@ psql_completion(const char *text, int start, int end)
 		matches = rl_completion_matches(text, complete_from_files);
 	}
 
-	/*
-	 * Finally, we look through the list of "things", such as TABLE, INDEX and
-	 * check if that was the previous word. If so, execute the query to get a
-	 * list of them.
-	 */
-	else
-	{
-		const pgsql_thing_t *wac;
-
-		for (wac = words_after_create; wac->name != NULL; wac++)
-		{
-			if (pg_strcasecmp(prev_wd, wac->name) == 0)
-			{
-				if (wac->query)
-					COMPLETE_WITH_QUERY_LIST(wac->query,
-											 wac->keywords);
-				else if (wac->vquery)
-					COMPLETE_WITH_VERSIONED_QUERY_LIST(wac->vquery,
-													   wac->keywords);
-				else if (wac->squery)
-					COMPLETE_WITH_VERSIONED_SCHEMA_QUERY_LIST(wac->squery,
-															  wac->keywords);
-				break;
-			}
-		}
-	}
-
-	/*
-	 * If we still don't have anything to match we have to fabricate some sort
-	 * of default list. If we were to just return NULL, readline automatically
-	 * attempts filename completion, and that's usually no good.
-	 */
-	if (matches == NULL)
-	{
-		COMPLETE_WITH_CONST(true, "");
-		/* Also, prevent Readline from appending stuff to the non-match */
-		rl_completion_append_character = '\0';
-#ifdef HAVE_RL_COMPLETION_SUPPRESS_QUOTE
-		rl_completion_suppress_quote = 1;
-#endif
-	}
-
-	/* free storage */
-	free(previous_words);
-	free(words_buffer);
-	free(text_copy);
-	free(completion_ref_object);
-	completion_ref_object = NULL;
-	free(completion_ref_schema);
-	completion_ref_schema = NULL;
-
-	/* Return our Grand List O' Matches */
 	return matches;
 }
 
-- 
2.43.5

