From 0ecf9d642f92e41fa82829d49ac889d37fed1d05 Mon Sep 17 00:00:00 2001
From: Erik Wienhold <ewie@ewie.name>
Date: Wed, 10 Sep 2025 02:58:56 +0200
Subject: [PATCH v3 2/3] psql: Fix counting of cell lines in expanded mode

In expanded mode we repeat the table header in every record by printing
header and cell side by side.  But the pager setup only counts the lines
in the cells.  The total line count is off if headers have more line
breaks than some of the respective cells.  Fix this by distinguishing
between expanded and normal mode in the loop that counts the cell lines.
While at it, change that loop to count all lines of each cell instead of
just the extra lines to make the logic easier to follow.  The first line
of each cell was accounted for in the initial value of the total line
count which now only accounts for the separator lines, depending on the
output mode.
---
 src/fe_utils/print.c | 60 ++++++++++++++++++++++++++++----------------
 1 file changed, 39 insertions(+), 21 deletions(-)

diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 44d5af7577e..82f4fc9af20 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3366,6 +3366,7 @@ IsPagerNeeded(const printTableContent *cont, unsigned int *width_wrap,
 	int			lines,
 				max_lines = 0,
 				nl_lines,
+			   *header_height = NULL,
 				i;
 	const char *const *cell = NULL;
 
@@ -3375,18 +3376,25 @@ IsPagerNeeded(const printTableContent *cont, unsigned int *width_wrap,
 		return;
 	}
 
-	if (expanded)
-		lines = (cont->ncolumns + 1) * cont->nrows;
-	else
-		lines = cont->nrows + 1;
+	/*
+	 * Count one line per record separator in expanded mode.  In normal mode,
+	 * we write a single separator line between header and rows.
+	 */
+	lines = expanded ? cont->nrows : 1;
 
-	/* Scan all column headers to find maximum height */
+	/*
+	 * Scan all column headers and cache their line count since expanded
+	 * output repeats the header for every record.
+	 */
+	header_height = pg_malloc0(cont->ncolumns * sizeof(header_height));
 	for (i = 0; i < cont->ncolumns; i++)
 	{
 		pg_wcssize((const unsigned char *) cont->headers[i],
 				   strlen(cont->headers[i]), cont->opt->encoding,
 				   NULL, &nl_lines, NULL);
 
+		header_height[i] = nl_lines;
+
 		if (nl_lines > max_lines)
 			max_lines = nl_lines;
 	}
@@ -3395,37 +3403,45 @@ IsPagerNeeded(const printTableContent *cont, unsigned int *width_wrap,
 	lines += max_lines;
 	max_lines = 0;
 
-	/* Scan all cells to count extra lines */
+	/* Scan all cells to count their lines */
 	for (i = 0, cell = cont->cells; *cell; cell++)
 	{
 		int			width;
-		unsigned int extra_lines;
 
+		/* Count the original line breaks */
 		pg_wcssize((const unsigned char *) *cell, strlen(*cell),
 				   cont->opt->encoding, &width, &nl_lines, NULL);
 
-		/*
-		 * A cell can have both wrapping and newlines that cause it to display
-		 * across multiple lines.  We check for both cases below.
-		 */
-
-		/* Don't count the first line of nl_lines -- it's not "extra" */
-		extra_lines = nl_lines - 1;
-
 		/* Count extra lines due to wrapping */
 		if (width > 0 && width_wrap && width_wrap[i])
-			extra_lines += (width - 1) / width_wrap[i];
+			nl_lines += (width - 1) / width_wrap[i];
 
-		if (extra_lines > max_lines)
-			max_lines = extra_lines;
+		if (expanded)
+		{
+			/* Pick the height of the header or cell, whichever is taller */
+			if (nl_lines > header_height[i])
+				lines += nl_lines;
+			else
+				lines += header_height[i];
+		}
+		else
+		{
+			/* Remember max line count in the current row */
+			if (nl_lines > max_lines)
+				max_lines = nl_lines;
+		}
 
 		/* i is the current column number: increment with wrap */
 		if (++i >= cont->ncolumns)
 		{
 			i = 0;
-			/* At last column of each row, add tallest column height */
-			lines += max_lines;
-			max_lines = 0;
+
+			if (!expanded)
+			{
+				/* At last column of each row, add tallest column height */
+				lines += max_lines;
+				max_lines = 0;
+			}
 		}
 	}
 
@@ -3443,6 +3459,8 @@ IsPagerNeeded(const printTableContent *cont, unsigned int *width_wrap,
 
 	*fout = PageOutput(lines, cont->opt);
 	*is_pager = (*fout != stdout);
+
+	free(header_height);
 }
 
 /*
-- 
2.51.0

