summaryrefslogtreecommitdiff
path: root/src/fe_utils/print.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/fe_utils/print.c')
-rw-r--r--src/fe_utils/print.c361
1 files changed, 277 insertions, 84 deletions
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 4af0f32f2fc..73847d3d6b3 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -33,6 +33,11 @@
#include "fe_utils/mbprint.h"
#include "fe_utils/print.h"
+/* Presently, count_table_lines() is only used within #ifdef TIOCGWINSZ */
+#ifdef TIOCGWINSZ
+#define NEED_COUNT_TABLE_LINES
+#endif
+
/*
* If the calling program doesn't have any mechanism for setting
* cancel_pressed, it will have no effect.
@@ -266,9 +271,20 @@ static const unicodeStyleFormat unicode_style = {
/* Local functions */
static int strlen_max_width(unsigned char *str, int *target_width, int encoding);
-static void IsPagerNeeded(const printTableContent *cont, int extra_lines, bool expanded,
+static FILE *PageOutputInternal(int lines, const printTableOpt *topt,
+ const printTableContent *cont,
+ const unsigned int *width_wrap,
+ bool vertical);
+static void IsPagerNeeded(const printTableContent *cont,
+ const unsigned int *width_wrap,
+ bool vertical,
FILE **fout, bool *is_pager);
-
+#ifdef NEED_COUNT_TABLE_LINES
+static int count_table_lines(const printTableContent *cont,
+ const unsigned int *width_wrap,
+ bool vertical,
+ int threshold);
+#endif
static void print_aligned_vertical(const printTableContent *cont,
FILE *fout, bool is_pager);
@@ -656,8 +672,6 @@ print_aligned_text(const printTableContent *cont, FILE *fout, bool is_pager)
unsigned char **format_buf;
unsigned int width_total;
unsigned int total_header_width;
- unsigned int extra_row_output_lines = 0;
- unsigned int extra_output_lines = 0;
const char *const *ptr;
@@ -722,17 +736,12 @@ print_aligned_text(const printTableContent *cont, FILE *fout, bool is_pager)
max_nl_lines[i] = nl_lines;
if (bytes_required > max_bytes[i])
max_bytes[i] = bytes_required;
- if (nl_lines > extra_row_output_lines)
- extra_row_output_lines = nl_lines;
width_header[i] = width;
}
- /* Add height of tallest header column */
- extra_output_lines += extra_row_output_lines;
- extra_row_output_lines = 0;
/* scan all cells, find maximum width, compute cell_count */
- for (i = 0, ptr = cont->cells; *ptr; ptr++, i++, cell_count++)
+ for (i = 0, ptr = cont->cells; *ptr; ptr++, cell_count++)
{
int width,
nl_lines,
@@ -741,14 +750,18 @@ print_aligned_text(const printTableContent *cont, FILE *fout, bool is_pager)
pg_wcssize((const unsigned char *) *ptr, strlen(*ptr), encoding,
&width, &nl_lines, &bytes_required);
- if (width > max_width[i % col_count])
- max_width[i % col_count] = width;
- if (nl_lines > max_nl_lines[i % col_count])
- max_nl_lines[i % col_count] = nl_lines;
- if (bytes_required > max_bytes[i % col_count])
- max_bytes[i % col_count] = bytes_required;
+ if (width > max_width[i])
+ max_width[i] = width;
+ if (nl_lines > max_nl_lines[i])
+ max_nl_lines[i] = nl_lines;
+ if (bytes_required > max_bytes[i])
+ max_bytes[i] = bytes_required;
+
+ width_average[i] += width;
- width_average[i % col_count] += width;
+ /* i is the current column number: increment with wrap */
+ if (++i >= col_count)
+ i = 0;
}
/* If we have rows, compute average */
@@ -889,43 +902,10 @@ print_aligned_text(const printTableContent *cont, FILE *fout, bool is_pager)
is_pager = is_local_pager = true;
}
- /* Check if newlines or our wrapping now need the pager */
- if (!is_pager && fout == stdout)
+ /* Check if there are enough lines to require the pager */
+ if (!is_pager)
{
- /* scan all cells, find maximum width, compute cell_count */
- for (i = 0, ptr = cont->cells; *ptr; ptr++, cell_count++)
- {
- int width,
- nl_lines,
- bytes_required;
-
- pg_wcssize((const unsigned char *) *ptr, strlen(*ptr), encoding,
- &width, &nl_lines, &bytes_required);
-
- /*
- * A row can have both wrapping and newlines that cause it to
- * display across multiple lines. We check for both cases below.
- */
- if (width > 0 && width_wrap[i])
- {
- unsigned int extra_lines;
-
- /* don't count the first line of nl_lines - it's not "extra" */
- extra_lines = ((width - 1) / width_wrap[i]) + nl_lines - 1;
- if (extra_lines > extra_row_output_lines)
- extra_row_output_lines = extra_lines;
- }
-
- /* i is the current column number: increment with wrap */
- if (++i >= col_count)
- {
- i = 0;
- /* At last column of each row, add tallest column height */
- extra_output_lines += extra_row_output_lines;
- extra_row_output_lines = 0;
- }
- }
- IsPagerNeeded(cont, extra_output_lines, false, &fout, &is_pager);
+ IsPagerNeeded(cont, width_wrap, false, &fout, &is_pager);
is_local_pager = is_pager;
}
@@ -1351,6 +1331,11 @@ print_aligned_vertical(const printTableContent *cont,
if (opt_border > 2)
opt_border = 2;
+ /*
+ * Kluge for totally empty table: use the default footer even though
+ * vertical modes normally don't. Otherwise we'd print nothing at all,
+ * which isn't terribly friendly. Assume pager will not be needed.
+ */
if (cont->cells[0] == NULL && cont->opt->start_table &&
cont->opt->stop_table)
{
@@ -1376,7 +1361,7 @@ print_aligned_vertical(const printTableContent *cont,
*/
if (!is_pager)
{
- IsPagerNeeded(cont, 0, true, &fout, &is_pager);
+ IsPagerNeeded(cont, NULL, true, &fout, &is_pager);
is_local_pager = is_pager;
}
@@ -3081,28 +3066,62 @@ set_sigpipe_trap_state(bool ignore)
/*
* PageOutput
*
- * Tests if pager is needed and returns appropriate FILE pointer.
+ * Tests if pager is needed and returns appropriate FILE pointer
+ * (either a pipe, or stdout if we don't need the pager).
+ *
+ * lines: number of lines that will be printed
+ * topt: print formatting options
*
* If the topt argument is NULL no pager is used.
*/
FILE *
PageOutput(int lines, const printTableOpt *topt)
{
+ return PageOutputInternal(lines, topt, NULL, NULL, false);
+}
+
+/*
+ * Private version that allows for line-counting to be avoided when
+ * not needed. If "cont" is not null then the input value of "lines"
+ * is ignored and we count lines based on cont + width_wrap + vertical
+ * (see count_table_lines).
+ */
+static FILE *
+PageOutputInternal(int lines, const printTableOpt *topt,
+ const printTableContent *cont,
+ const unsigned int *width_wrap,
+ bool vertical)
+{
/* check whether we need / can / are supposed to use pager */
if (topt && topt->pager && isatty(fileno(stdin)) && isatty(fileno(stdout)))
{
+ /* without TIOCGWINSZ, pager == 1 acts the same as pager > 1 */
#ifdef TIOCGWINSZ
unsigned short int pager = topt->pager;
int min_lines = topt->pager_min_lines;
- int result;
- struct winsize screen_size;
- result = ioctl(fileno(stdout), TIOCGWINSZ, &screen_size);
+ if (pager == 1)
+ {
+ int result;
+ struct winsize screen_size;
- /* >= accounts for a one-line prompt */
- if (result == -1
- || (lines >= screen_size.ws_row && lines >= min_lines)
- || pager > 1)
+ result = ioctl(fileno(stdout), TIOCGWINSZ, &screen_size);
+ if (result < 0)
+ pager = 2; /* force use of pager */
+ else
+ {
+ int threshold = Max(screen_size.ws_row, min_lines);
+
+ if (cont) /* caller wants us to calculate lines */
+ lines = count_table_lines(cont, width_wrap, vertical,
+ threshold);
+ /* >= accounts for a one-line prompt */
+ if (lines >= threshold)
+ pager = 2;
+ }
+ }
+
+ if (pager > 1)
#endif
{
const char *pagerprog;
@@ -3398,38 +3417,212 @@ printTableCleanup(printTableContent *const content)
* IsPagerNeeded
*
* Setup pager if required
+ *
+ * cont: table data to be printed
+ * width_wrap[]: per-column maximum width, or NULL if caller will not wrap
+ * vertical: vertical mode?
+ * fout: where to print to (in/out argument)
+ * is_pager: output argument
+ *
+ * If we decide pager is needed, *fout is modified and *is_pager is set true
*/
static void
-IsPagerNeeded(const printTableContent *cont, int extra_lines, bool expanded,
+IsPagerNeeded(const printTableContent *cont, const unsigned int *width_wrap,
+ bool vertical,
FILE **fout, bool *is_pager)
{
if (*fout == stdout)
{
- int lines;
+ *fout = PageOutputInternal(0, cont->opt, cont, width_wrap, vertical);
+ *is_pager = (*fout != stdout);
+ }
+ else
+ *is_pager = false;
+}
- if (expanded)
- lines = (cont->ncolumns + 1) * cont->nrows;
- else
- lines = cont->nrows + 1;
+/*
+ * Count the number of lines needed to print the given table.
+ *
+ * cont: table data to be printed
+ * width_wrap[]: per-column maximum width, or NULL if caller will not wrap
+ * vertical: vertical mode?
+ * threshold: we can stop counting once we pass this many lines
+ *
+ * The result is currently only fully accurate for ALIGNED/WRAPPED and
+ * UNALIGNED formats; otherwise it's an approximation.
+ *
+ * Note: while cont->opt will tell us most formatting details, we need the
+ * separate "vertical" flag because of the possibility of a dynamic switch
+ * from aligned_text to aligned_vertical format.
+ *
+ * The point of the threshold parameter is that when the table is very long,
+ * we'll typically be able to stop scanning after not many rows.
+ */
+#ifdef NEED_COUNT_TABLE_LINES
+static int
+count_table_lines(const printTableContent *cont,
+ const unsigned int *width_wrap,
+ bool vertical,
+ int threshold)
+{
+ int *header_height;
+ int lines = 0,
+ max_lines = 0,
+ nl_lines,
+ i;
+ int encoding = cont->opt->encoding;
+ const char *const *cell;
- if (!cont->opt->tuples_only)
- {
- printTableFooter *f;
+ /*
+ * Scan all column headers and determine their heights. Cache the values
+ * since vertical mode repeats the headers for every record.
+ */
+ header_height = (int *) pg_malloc(cont->ncolumns * sizeof(int));
+ for (i = 0; i < cont->ncolumns; i++)
+ {
+ pg_wcssize((const unsigned char *) cont->headers[i],
+ strlen(cont->headers[i]), encoding,
+ NULL, &header_height[i], NULL);
+ }
+
+ /*
+ * Account for separator lines (if used), as well as the trailing blank
+ * line that most formats emit.
+ */
+ switch (cont->opt->format)
+ {
+ case PRINT_ALIGNED:
+ case PRINT_WRAPPED:
/*
- * FIXME -- this is slightly bogus: it counts the number of
- * footers, not the number of lines in them.
+ * Vertical mode writes one separator line per record. Normal
+ * mode writes a single separator line between header and rows.
*/
- for (f = cont->footers; f; f = f->next)
- lines++;
+ lines = vertical ? cont->nrows : 1;
+ /* Both modes add a blank line at the end */
+ lines++;
+ break;
+ case PRINT_UNALIGNED:
+
+ /*
+ * Vertical mode writes a separator (here assumed to be a newline)
+ * between records. Normal mode writes nothing extra.
+ */
+ if (vertical)
+ lines = Max(cont->nrows - 1, 0);
+ break;
+ case PRINT_CSV:
+ /* Nothing extra is added */
+ break;
+ case PRINT_HTML:
+ case PRINT_ASCIIDOC:
+ case PRINT_LATEX:
+ case PRINT_LATEX_LONGTABLE:
+ case PRINT_TROFF_MS:
+
+ /*
+ * These formats aren't really meant for interactive consumption,
+ * so for now we won't work hard on them. Treat them like aligned
+ * mode.
+ */
+ lines = vertical ? cont->nrows : 1;
+ lines++;
+ break;
+ case PRINT_NOTHING:
+ /* Shouldn't get here... */
+ break;
+ }
+
+ /* Scan all cells to count their lines */
+ for (i = 0, cell = cont->cells; *cell; cell++)
+ {
+ int width;
+
+ /* Count the original line breaks */
+ pg_wcssize((const unsigned char *) *cell, strlen(*cell), encoding,
+ &width, &nl_lines, NULL);
+
+ /* Count extra lines due to wrapping */
+ if (width > 0 && width_wrap && width_wrap[i])
+ nl_lines += (width - 1) / width_wrap[i];
+
+ if (vertical)
+ {
+ /* 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 height in the current row */
+ if (nl_lines > max_lines)
+ max_lines = nl_lines;
}
- *fout = PageOutput(lines + extra_lines, cont->opt);
- *is_pager = (*fout != stdout);
+ /* i is the current column number: increment with wrap */
+ if (++i >= cont->ncolumns)
+ {
+ i = 0;
+ if (!vertical)
+ {
+ /* At last column of each row, add tallest column height */
+ lines += max_lines;
+ max_lines = 0;
+ }
+ /* Stop scanning table body once we pass threshold */
+ if (lines > threshold)
+ break;
+ }
}
- else
- *is_pager = false;
+
+ /* Account for header and footer decoration */
+ if (!cont->opt->tuples_only && lines <= threshold)
+ {
+ printTableFooter *f;
+
+ if (cont->title)
+ {
+ /* Add height of title */
+ pg_wcssize((const unsigned char *) cont->title, strlen(cont->title),
+ encoding, NULL, &nl_lines, NULL);
+ lines += nl_lines;
+ }
+
+ if (!vertical)
+ {
+ /* Add height of tallest header column */
+ max_lines = 0;
+ for (i = 0; i < cont->ncolumns; i++)
+ {
+ if (header_height[i] > max_lines)
+ max_lines = header_height[i];
+ }
+ lines += max_lines;
+ }
+
+ /*
+ * Add all footer lines. Vertical mode does not use the default
+ * footer, but we must include that in normal mode.
+ */
+ for (f = (vertical ? cont->footers : footers_with_default(cont));
+ f != NULL; f = f->next)
+ {
+ pg_wcssize((const unsigned char *) f->data, strlen(f->data),
+ encoding, NULL, &nl_lines, NULL);
+ lines += nl_lines;
+ /* Stop scanning footers once we pass threshold */
+ if (lines > threshold)
+ break;
+ }
+ }
+
+ free(header_height);
+
+ return lines;
}
+#endif /* NEED_COUNT_TABLE_LINES */
/*
* Use this to print any table in the supported formats.
@@ -3456,7 +3649,7 @@ printTable(const printTableContent *cont,
cont->opt->format != PRINT_ALIGNED &&
cont->opt->format != PRINT_WRAPPED)
{
- IsPagerNeeded(cont, 0, (cont->opt->expanded == 1), &fout, &is_pager);
+ IsPagerNeeded(cont, NULL, (cont->opt->expanded == 1), &fout, &is_pager);
is_local_pager = is_pager;
}
@@ -3464,10 +3657,6 @@ printTable(const printTableContent *cont,
clearerr(fout);
/* print the stuff */
-
- if (flog)
- print_aligned_text(cont, flog, false);
-
switch (cont->opt->format)
{
case PRINT_UNALIGNED:
@@ -3534,6 +3723,10 @@ printTable(const printTableContent *cont,
if (is_local_pager)
ClosePager(fout);
+
+ /* also produce log output if wanted */
+ if (flog)
+ print_aligned_text(cont, flog, false);
}
/*