]> granicus.if.org Git - postgresql/commitdiff
Add psql '\pset format wrapped' mode to wrap output to screen width, or
authorBruce Momjian <bruce@momjian.us>
Thu, 8 May 2008 17:04:26 +0000 (17:04 +0000)
committerBruce Momjian <bruce@momjian.us>
Thu, 8 May 2008 17:04:26 +0000 (17:04 +0000)
file/pipe output too if \pset columns' is set.

Bryce Nesbitt

doc/src/sgml/ref/psql-ref.sgml
src/bin/psql/command.c
src/bin/psql/mbprint.c
src/bin/psql/print.c
src/bin/psql/print.h
src/bin/psql/startup.c

index 72bae112701375755f2f6ac9e13eaec4c10d84bc..5a0ecb6ea8f05601787ce474455750981e6f361e 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/psql-ref.sgml,v 1.202 2008/05/08 00:27:57 momjian Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/psql-ref.sgml,v 1.203 2008/05/08 17:04:26 momjian Exp $
 PostgreSQL documentation
 -->
 
@@ -1514,7 +1514,8 @@ lo_import 152801
           <listitem>
           <para>
           Sets the output format to one of <literal>unaligned</literal>,
-          <literal>aligned</literal>, <literal>html</literal>,
+          <literal>aligned</literal>, <literal>wrapped</literal>, 
+          <literal>html</literal>,
           <literal>latex</literal>, or <literal>troff-ms</literal>.
           Unique abbreviations are allowed.  (That would mean one letter
           is enough.)
@@ -1526,8 +1527,21 @@ lo_import 152801
           is intended to create output that might be intended to be read
           in by other programs (tab-separated, comma-separated).
           <quote>Aligned</quote> mode is the standard, human-readable,
-          nicely formatted text output that is default. The
-          <quote><acronym>HTML</acronym></quote> and
+          nicely formatted text output that is default.
+          </para>
+
+          <para>
+          <quote>Wrapped</quote> is like <literal>aligned</> but wraps
+          output to the specified width.  If <literal>\pset columns</> is
+          zero (the default), <literal>wrapped</> mode only affects screen
+          output and wrapped width is controlled by the environment
+          variable <envar>COLUMNS</> or the detected screen width.  If
+          <literal>\pset columns</> is set to a non-zero value, all output
+          is wrapped, including file and pipe output.
+          </para>
+
+          <para>
+          The <quote><acronym>HTML</acronym></quote> and
           <quote>LaTeX</quote> modes put out tables that are intended to
           be included in documents using the respective mark-up
           language. They are not complete documents! (This might not be
@@ -1537,6 +1551,17 @@ lo_import 152801
           </listitem>
           </varlistentry>
 
+          <varlistentry>
+          <term><literal>columns</literal></term>
+          <listitem>
+          <para>
+          Controls the target width for the <literal>wrapped</> format.
+          Zero (the default) causes the <literal>wrapped</> format to
+          affect only screen output.
+          </para>
+          </listitem>
+          </varlistentry>
+
           <varlistentry>
           <term><literal>border</literal></term>
           <listitem>
@@ -2706,6 +2731,18 @@ $endif
   <title>Environment</title>
 
   <variablelist>
+
+   <varlistentry>
+    <term><envar>COLUMNS</envar></term>
+
+    <listitem>
+     <para>
+      Used for the <literal>wrapped</> output format if 
+      <literal>\pset columns</> is zero.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><envar>PAGER</envar></term>
 
index 28a14c7d0aef5fc7844271e142310f3232127dc9..f74ce95140970eaeab11cbcf934bc1ab86b57f75 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2008, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.187 2008/05/02 09:27:50 petere Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.188 2008/05/08 17:04:26 momjian Exp $
  */
 #include "postgres_fe.h"
 #include "command.h"
@@ -1502,6 +1502,9 @@ _align2string(enum printFormat in)
                case PRINT_ALIGNED:
                        return "aligned";
                        break;
+               case PRINT_WRAPPED:
+                       return "wrapped";
+                       break;
                case PRINT_HTML:
                        return "html";
                        break;
@@ -1535,6 +1538,8 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
                        popt->topt.format = PRINT_UNALIGNED;
                else if (pg_strncasecmp("aligned", value, vallen) == 0)
                        popt->topt.format = PRINT_ALIGNED;
+               else if (pg_strncasecmp("wrapped", value, vallen) == 0)
+                       popt->topt.format = PRINT_WRAPPED;
                else if (pg_strncasecmp("html", value, vallen) == 0)
                        popt->topt.format = PRINT_HTML;
                else if (pg_strncasecmp("latex", value, vallen) == 0)
@@ -1543,7 +1548,7 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
                        popt->topt.format = PRINT_TROFF_MS;
                else
                {
-                       psql_error("\\pset: allowed formats are unaligned, aligned, html, latex, troff-ms\n");
+                       psql_error("\\pset: allowed formats are unaligned, aligned, wrapped, html, latex, troff-ms\n");
                        return false;
                }
 
@@ -1724,6 +1729,16 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
                }
        }
 
+       /* set border style/width */
+       else if (strcmp(param, "columns") == 0)
+       {
+               if (value)
+                       popt->topt.columns = atoi(value);
+
+               if (!quiet)
+                       printf(_("Target width for \"wrapped\" format is %d.\n"), popt->topt.columns);
+       }
+
        else
        {
                psql_error("\\pset: unknown option: %s\n", param);
index 4032951277cde11b3c1fcff0cf05461312445b42..a753dca2da168e21c08d29019992f3efc70d7b18 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2008, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/mbprint.c,v 1.30 2008/04/16 18:18:00 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/mbprint.c,v 1.31 2008/05/08 17:04:26 momjian Exp $
  *
  * XXX this file does not really belong in psql/.  Perhaps move to libpq?
  * It also seems that the mbvalidate function is redundant with existing
@@ -204,8 +204,8 @@ pg_wcswidth(const unsigned char *pwcs, size_t len, int encoding)
 /*
  * pg_wcssize takes the given string in the given encoding and returns three
  * values:
- *       result_width: Width in display character of longest line in string
- *       result_height: Number of lines in display output
+ *       result_width: Width in display characters of the longest line in string
+ *       result_height: Number of newlines in display output
  *       result_format_size: Number of bytes required to store formatted representation of string
  */
 int
@@ -279,9 +279,14 @@ pg_wcssize(unsigned char *pwcs, size_t len, int encoding, int *result_width,
        return width;
 }
 
+/*
+ *  Filter out unprintable characters, companion to wcs_size.
+ *  Break input into lines based on \n.  lineptr[i].ptr == NULL
+ *     indicates the end of the array.
+ */
 void
 pg_wcsformat(unsigned char *pwcs, size_t len, int encoding,
-                        struct lineptr * lines, int count)
+                        struct lineptr *lines, int count)
 {
        int                     w,
                                chlen = 0;
@@ -307,6 +312,7 @@ pg_wcsformat(unsigned char *pwcs, size_t len, int encoding,
                                if (count == 0)
                                        exit(1);        /* Screwup */
 
+                               /* make next line point to remaining memory */
                                lines->ptr = ptr;
                        }
                        else if (*pwcs == '\r')         /* Linefeed */
@@ -353,12 +359,13 @@ pg_wcsformat(unsigned char *pwcs, size_t len, int encoding,
                }
                len -= chlen;
        }
-       *ptr++ = '\0';
        lines->width = linewidth;
-       lines++;
-       count--;
-       if (count > 0)
-               lines->ptr = NULL;
+       *ptr++ = '\0';                  /* Terminate formatted string */
+
+       if (count == 0)
+               exit(1);        /* Screwup */
+
+       (lines+1)->ptr = NULL;  /* terminate line array */
 }
 
 unsigned char *
index 411c66b1a01e1065ced2207508673935e187a290..0e615d8cb8fdfbc3d8415c887919abab95873a13 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2008, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/print.c,v 1.97 2008/03/27 03:57:34 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/print.c,v 1.98 2008/05/08 17:04:26 momjian Exp $
  */
 #include "postgres_fe.h"
 
@@ -28,6 +28,8 @@
 
 #include "mbprint.h"
 
+static int strlen_max_width(unsigned char *str, int *target_width, int encoding);
+
 /*
  * We define the cancel_pressed flag in this file, rather than common.c where
  * it naturally belongs, because this file is also used by non-psql programs
@@ -43,6 +45,7 @@ static char *decimal_point;
 static char *grouping;
 static char *thousands_sep;
 
+
 static void *
 pg_local_malloc(size_t size)
 {
@@ -396,34 +399,43 @@ _print_horizontal_line(const unsigned int col_count, const unsigned int *widths,
 }
 
 
+/*
+ *     Prety pretty boxes around cells.
+ */
 static void
 print_aligned_text(const char *title, const char *const * headers,
                                   const char *const * cells, const char *const * footers,
                                   const char *opt_align, const printTableOpt *opt,
-                                  FILE *fout)
+                                  bool is_pager, FILE *fout)
 {
        bool            opt_tuples_only = opt->tuples_only;
        bool            opt_numeric_locale = opt->numericLocale;
-       unsigned short int opt_border = opt->border;
        int                     encoding = opt->encoding;
-       unsigned int col_count = 0;
-       unsigned int cell_count = 0;
-       unsigned int i;
-       int                     tmp;
-       unsigned int *widths,
-                               total_w;
-       unsigned int *heights;
-       unsigned int *format_space;
+       unsigned short int opt_border = opt->border;
+
+       unsigned int col_count = 0, cell_count = 0;
+
+       unsigned int i,
+                               j;
+
+       unsigned int *width_header,
+                          *max_width,
+                          *width_wrap,
+                          *width_average;
+       unsigned int *max_nl_lines,     /* value split by newlines */
+                               *curr_nl_line,
+                               *max_bytes;
        unsigned char **format_buf;
+       unsigned int width_total;
+       unsigned int total_header_width;
 
-       const char *const * ptr;
+       const char * const *ptr;
 
-       struct lineptr **col_lineptrs;          /* pointers to line pointer for each
-                                                                                * column */
-       struct lineptr *lineptr_list;           /* complete list of linepointers */
+       struct lineptr **col_lineptrs;          /* pointers to line pointer per column */
 
-       int                *complete;           /* Array remembering which columns have
-                                                                * completed output */
+       bool       *header_done;        /* Have all header lines been output? */
+       int                *bytes_output;       /* Bytes output for column value */
+       int                     output_columns = 0;     /* Width of interactive console */
 
        if (cancel_pressed)
                return;
@@ -437,275 +449,404 @@ print_aligned_text(const char *title, const char *const * headers,
 
        if (col_count > 0)
        {
-               widths = pg_local_calloc(col_count, sizeof(*widths));
-               heights = pg_local_calloc(col_count, sizeof(*heights));
+               width_header = pg_local_calloc(col_count, sizeof(*width_header));
+               width_average = pg_local_calloc(col_count, sizeof(*width_average));
+               max_width = pg_local_calloc(col_count, sizeof(*max_width));
+               width_wrap = pg_local_calloc(col_count, sizeof(*width_wrap));
+               max_nl_lines = pg_local_calloc(col_count, sizeof(*max_nl_lines));
+               curr_nl_line = pg_local_calloc(col_count, sizeof(*curr_nl_line));
                col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs));
-               format_space = pg_local_calloc(col_count, sizeof(*format_space));
+               max_bytes = pg_local_calloc(col_count, sizeof(*max_bytes));
                format_buf = pg_local_calloc(col_count, sizeof(*format_buf));
-               complete = pg_local_calloc(col_count, sizeof(*complete));
+               header_done = pg_local_calloc(col_count, sizeof(*header_done));
+               bytes_output = pg_local_calloc(col_count, sizeof(*bytes_output));
        }
        else
        {
-               widths = NULL;
-               heights = NULL;
+               width_header = NULL;
+               width_average = NULL;
+               max_width = NULL;
+               width_wrap = NULL;
+               max_nl_lines = NULL;
+               curr_nl_line = NULL;
                col_lineptrs = NULL;
-               format_space = NULL;
+               max_bytes = NULL;
                format_buf = NULL;
-               complete = NULL;
+               header_done = NULL;
+               bytes_output = NULL;
        }
 
-       /* count cells (rows * cols) */
-       for (ptr = cells; *ptr; ptr++)
-               cell_count++;
-
-       /* calc column widths */
+       /* scan all column headers, find maximum width and max max_nl_lines */
        for (i = 0; i < col_count; i++)
        {
-               /* Get width & height */
-               int                     height,
-                                       space;
-
-               pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &tmp, &height, &space);
-               if (tmp > widths[i])
-                       widths[i] = tmp;
-               if (height > heights[i])
-                       heights[i] = height;
-               if (space > format_space[i])
-                       format_space[i] = space;
+               int                     width,
+                                       nl_lines,
+                                       bytes_required;
+
+               pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &width, &nl_lines, &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_header[i] = width;
        }
 
-       for (i = 0, ptr = cells; *ptr; ptr++, i++)
+       /* scan all cells, find maximum width, compute cell_count */
+       for (i = 0, ptr = cells; *ptr; ptr++, i++, cell_count++)
        {
-               int                     numeric_locale_len;
-               int                     height,
-                                       space;
+               int                     width,
+                                       nl_lines,
+                                       bytes_required;
 
-               if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
-                       numeric_locale_len = additional_numeric_locale_len(*ptr);
-               else
-                       numeric_locale_len = 0;
+               /* Get width, ignore nl_lines */
+               pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &width, &nl_lines, &bytes_required);
+               if (opt_numeric_locale && opt_align[i % col_count] == 'r')
+               {
+                       width += additional_numeric_locale_len(*ptr);
+                       bytes_required += additional_numeric_locale_len(*ptr);
+               }
+
+               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;
 
-               /* Get width, ignore height */
-               pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &tmp, &height, &space);
-               tmp += numeric_locale_len;
-               if (tmp > widths[i % col_count])
-                       widths[i % col_count] = tmp;
-               if (height > heights[i % col_count])
-                       heights[i % col_count] = height;
-               if (space > format_space[i % col_count])
-                       format_space[i % col_count] = space;
+               width_average[i % col_count] += width;
        }
 
+       /* If we have rows, compute average */
+       if (col_count != 0 && cell_count != 0)
+       {
+               int rows = cell_count / col_count;
+               
+               for (i = 0; i < col_count; i++)
+                       width_average[i % col_count] /= rows;
+       }
+
+       /* adjust the total display width based on border style */
        if (opt_border == 0)
-               total_w = col_count - 1;
+               width_total = col_count - 1;
        else if (opt_border == 1)
-               total_w = col_count * 3 - 1;
+               width_total = col_count * 3 - 1;
        else
-               total_w = col_count * 3 + 1;
+               width_total = col_count * 3 + 1;
+       total_header_width = width_total;
 
        for (i = 0; i < col_count; i++)
-               total_w += widths[i];
+       {
+               width_total += max_width[i];
+               total_header_width += width_header[i];
+       }
 
        /*
-        * At this point: widths contains the max width of each column heights
-        * contains the max height of a cell of each column format_space contains
-        * maximum space required to store formatted string so we prepare the
-        * formatting structures
+        * At this point: max_width[] contains the max width of each column,
+        * max_nl_lines[] contains the max number of lines in each column,
+        * max_bytes[] contains the maximum storage space for formatting
+        * strings, width_total contains the giant width sum.  Now we allocate
+        * some memory for line pointers.
         */
-       if (col_count > 0)
+       for (i = 0; i < col_count; i++)
        {
-               int                     heights_total = 0;
-               struct lineptr *lineptr;
+               /* Add entry for ptr == NULL array termination */
+               col_lineptrs[i] = pg_local_calloc(max_nl_lines[i] + 1,
+                                                                                       sizeof(**col_lineptrs));
 
-               for (i = 0; i < col_count; i++)
-                       heights_total += heights[i];
+               format_buf[i] = pg_local_malloc(max_bytes[i] + 1);
 
-               lineptr = lineptr_list = pg_local_calloc(heights_total, sizeof(*lineptr_list));
+               col_lineptrs[i]->ptr = format_buf[i];
+       }
 
-               for (i = 0; i < col_count; i++)
+       /* Default word wrap to the full width, i.e. no word wrap */
+       for (i = 0; i < col_count; i++)
+               width_wrap[i] = max_width[i];
+
+       if (opt->format == PRINT_WRAPPED)
+       {
+               /* Get terminal width */
+               if (opt->columns > 0)
+                       output_columns = opt->columns;
+               else if ((fout == stdout && isatty(fileno(stdout))) || is_pager)
                {
-                       col_lineptrs[i] = lineptr;
-                       lineptr += heights[i];
+                       if (opt->env_columns)
+                               output_columns = opt->env_columns;
+#ifdef TIOCGWINSZ
+                       else
+                       {
+                               struct winsize screen_size;
+       
+                               if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) != -1)
+                                       output_columns = screen_size.ws_col;
+                       }
+#endif
+               }
+
+               /*
+                * Optional optimized word wrap. Shrink columns with a high max/avg ratio.
+                * Slighly bias against wider columns. (Increases chance a narrow column
+                * will fit in its cell.)
+                * If available columns is positive...
+                * and greater than the width of the unshrinkable column headers
+                */
+               if (output_columns > 0 && output_columns >= total_header_width)
+               {
+                       /* While there is still excess width... */
+                       while (width_total > output_columns)
+                       {
+                               double          max_ratio = 0;
+                               int                     worst_col = -1;
+
+                               /*
+                                *      Find column that has the highest ratio of its maximum
+                                *      width compared to its average width.  This tells us which
+                                *      column will produce the fewest wrapped values if shortened.
+                                *      width_wrap starts as equal to max_width.
+                                */
+                               for (i = 0; i < col_count; i++)
+                               {
+                                       if (width_average[i] && width_wrap[i] > width_header[i])
+                                       {
+                                               /* Penalize wide columns by +1% of their width (0.01) */
+                                               double ratio = (double)width_wrap[i] / width_average[i] +
+                                                                       max_width[i] * 0.01;
 
-                       format_buf[i] = pg_local_malloc(format_space[i]);
+                                               if (ratio > max_ratio)
+                                               {
+                                                       max_ratio = ratio;
+                                                       worst_col = i;
+                                               }
+                                       }
+                               }
+
+                               /* Exit loop if we can't squeeze any more. */
+                               if (worst_col == -1)
+                                       break;
 
-                       col_lineptrs[i]->ptr = format_buf[i];
+                               /* Decrease width of target column by one. */
+                               width_wrap[worst_col]--;
+                               width_total--;
+                       }
                }
        }
-       else
-               lineptr_list = NULL;
 
+       /* time to output */
        if (opt->start_table)
        {
                /* print title */
                if (title && !opt_tuples_only)
                {
-                       /* Get width & height */
-                       int                     height;
+                       int                     width, height;
 
-                       pg_wcssize((unsigned char *) title, strlen(title), encoding, &tmp, &height, NULL);
-                       if (tmp >= total_w)
-                               fprintf(fout, "%s\n", title);
+                       pg_wcssize((unsigned char *) title, strlen(title), encoding, &width, &height, NULL);
+                       if (width >= width_total)
+                               fprintf(fout, "%s\n", title);   /* Aligned */
                        else
-                               fprintf(fout, "%-*s%s\n", (total_w - tmp) / 2, "", title);
+                               fprintf(fout, "%-*s%s\n", (width_total - width) / 2, "", title);                /* Centered */
                }
 
                /* print headers */
                if (!opt_tuples_only)
                {
-                       int                     cols_todo;
-                       int                     line_count;
+                       int                     more_col_wrapping;
+                       int                     curr_nl_line;
 
                        if (opt_border == 2)
-                               _print_horizontal_line(col_count, widths, opt_border, fout);
+                               _print_horizontal_line(col_count, width_wrap, opt_border, fout);
 
                        for (i = 0; i < col_count; i++)
-                               pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]), encoding, col_lineptrs[i], heights[i]);
+                               pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]),
+                                                        encoding, col_lineptrs[i], max_nl_lines[i]);
 
-                       cols_todo = col_count;
-                       line_count = 0;
-                       memset(complete, 0, col_count * sizeof(int));
-                       while (cols_todo)
+                       more_col_wrapping = col_count;
+                       curr_nl_line = 0;
+                       memset(header_done, false, col_count * sizeof(bool));
+                       while (more_col_wrapping)
                        {
                                if (opt_border == 2)
-                                       fprintf(fout, "|%c", line_count ? '+' : ' ');
+                                       fprintf(fout, "|%c", curr_nl_line ? '+' : ' ');
                                else if (opt_border == 1)
-                                       fputc(line_count ? '+' : ' ', fout);
+                                       fputc(curr_nl_line ? '+' : ' ', fout);
 
                                for (i = 0; i < col_count; i++)
                                {
                                        unsigned int nbspace;
 
-                                       struct lineptr *this_line = col_lineptrs[i] + line_count;
+                                       struct lineptr *this_line = col_lineptrs[i] + curr_nl_line;
 
-                                       if (!complete[i])
+                                       if (!header_done[i])
                                        {
-                                               nbspace = widths[i] - this_line->width;
+                                               nbspace = width_wrap[i] - this_line->width;
 
                                                /* centered */
                                                fprintf(fout, "%-*s%s%-*s",
                                                                nbspace / 2, "", this_line->ptr, (nbspace + 1) / 2, "");
 
-                                               if (line_count == (heights[i] - 1) || !(this_line + 1)->ptr)
+                                               if (!(this_line + 1)->ptr)
                                                {
-                                                       cols_todo--;
-                                                       complete[i] = 1;
+                                                       more_col_wrapping--;
+                                                       header_done[i] = 1;
                                                }
                                        }
                                        else
-                                               fprintf(fout, "%*s", widths[i], "");
+                                               fprintf(fout, "%*s", width_wrap[i], "");
                                        if (i < col_count - 1)
                                        {
                                                if (opt_border == 0)
-                                                       fputc(line_count ? '+' : ' ', fout);
+                                                       fputc(curr_nl_line ? '+' : ' ', fout);
                                                else
-                                                       fprintf(fout, " |%c", line_count ? '+' : ' ');
+                                                       fprintf(fout, " |%c", curr_nl_line ? '+' : ' ');
                                        }
                                }
-                               line_count++;
+                               curr_nl_line++;
 
                                if (opt_border == 2)
                                        fputs(" |", fout);
                                else if (opt_border == 1)
-                                       fputc(' ', fout);;
+                                       fputc(' ', fout);
                                fputc('\n', fout);
                        }
 
-                       _print_horizontal_line(col_count, widths, opt_border, fout);
+                       _print_horizontal_line(col_count, width_wrap, opt_border, fout);
                }
        }
 
-       /* print cells */
+       /* print cells, one loop per row */
        for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count)
        {
-               int                     j;
-               int                     cols_todo = col_count;
-               int                     line_count; /* Number of lines output so far in row */
+               bool            more_lines;
 
                if (cancel_pressed)
                        break;
 
+               /*
+                * Format each cell.  Format again, it is a numeric formatting locale
+                * (e.g. 123,456 vs. 123456)
+                */
                for (j = 0; j < col_count; j++)
-                       pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], heights[j]);
+               {
+                       pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], max_nl_lines[j]);
+                       curr_nl_line[j] = 0;
 
-               line_count = 0;
-               memset(complete, 0, col_count * sizeof(int));
-               while (cols_todo)
+                       if (opt_numeric_locale && opt_align[j % col_count] == 'r')
+                       {
+                               char       *my_cell;
+
+                               my_cell = format_numeric_locale((char *) col_lineptrs[j]->ptr);
+                               strcpy((char *) col_lineptrs[j]->ptr, my_cell); /* Buffer IS large
+                                                                                                                                * enough... now */
+                               free(my_cell);
+                       }
+               }
+
+               memset(bytes_output, 0, col_count * sizeof(int));
+
+               /*
+                *      Each time through this loop, one display line is output.
+                *      It can either be a full value or a partial value if embedded
+                *      newlines exist or if 'format=wrapping' mode is enabled.
+                */
+               do
                {
-                       /* beginning of line */
+                       more_lines = false;
+
+                       /* left border */
                        if (opt_border == 2)
                                fputs("| ", fout);
                        else if (opt_border == 1)
                                fputc(' ', fout);
 
+                       /* for each column */
                        for (j = 0; j < col_count; j++)
                        {
-                               struct lineptr *this_line = col_lineptrs[j] + line_count;
-                               bool            finalspaces = (opt_border == 2 || j != col_count - 1);
+                               /* We have a valid array element, so index it */
+                               struct lineptr *this_line = &col_lineptrs[j][curr_nl_line[j]];
+                               int             bytes_to_output,  chars_to_output = width_wrap[j];
 
-                               if (complete[j])        /* Just print spaces... */
-                               {
-                                       if (finalspaces)
-                                               fprintf(fout, "%*s", widths[j], "");
-                               }
+                               /* Past newline lines so pad for other columns */
+                               if (!this_line->ptr)
+                                       fprintf(fout, "%*s", width_wrap[j], "");
                                else
                                {
-                                       /* content */
-                                       if (opt_align[j] == 'r')
+                                       /* Get strlen() of the width_wrap character */
+                                       bytes_to_output = strlen_max_width(this_line->ptr +
+                                                                       bytes_output[j], &chars_to_output, encoding);
+
+                                       /*
+                                        *      If we exceeded width_wrap, it means the display width
+                                        *      of a single character was wider than our target width.
+                                        *      In that case, we have to pretend we are only printing
+                                        *      the target display width and make the best of it.
+                                        */
+                                       if (chars_to_output > width_wrap[j])
+                                               chars_to_output = width_wrap[j];
+
+                                       if (opt_align[j] == 'r')                /* Right aligned cell */
                                        {
-                                               if (opt_numeric_locale)
-                                               {
-                                                       /*
-                                                        * Assumption: This code used only on strings
-                                                        * without multibyte characters, otherwise
-                                                        * this_line->width < strlen(this_ptr) and we get
-                                                        * an overflow
-                                                        */
-                                                       char       *my_cell = format_numeric_locale((char *) this_line->ptr);
-
-                                                       fprintf(fout, "%*s%s",
-                                                                       (int) (widths[i % col_count] - strlen(my_cell)), "",
-                                                                       my_cell);
-                                                       free(my_cell);
-                                               }
-                                               else
-                                                       fprintf(fout, "%*s%s",
-                                                                       widths[j] - this_line->width, "",
-                                                                       this_line->ptr);
+                                               /* spaces first */
+                                               fprintf(fout, "%*s", width_wrap[j] - chars_to_output, "");
+                                               fprintf(fout, "%.*s", bytes_to_output,
+                                                               this_line->ptr + bytes_output[j]);
                                        }
+                                       else    /* Left aligned cell */
+                                       {
+                                               /* spaces second */
+                                               fprintf(fout, "%.*s", bytes_to_output,
+                                                               this_line->ptr + bytes_output[j]);
+                                               fprintf(fout, "%*s", width_wrap[j] - chars_to_output, "");
+                                       }
+
+                                       bytes_output[j] += bytes_to_output;
+
+                                       /* Do we have more text to wrap? */
+                                       if (*(this_line->ptr + bytes_output[j]) != 0)
+                                               more_lines = true;
                                        else
-                                               fprintf(fout, "%-s%*s", this_line->ptr,
-                                               finalspaces ? (widths[j] - this_line->width) : 0, "");
-                                       /* If at the right height, done this col */
-                                       if (line_count == heights[j] - 1 || !this_line[1].ptr)
                                        {
-                                               complete[j] = 1;
-                                               cols_todo--;
+                                               /* Advance to next newline line */
+                                               curr_nl_line[j]++;
+                                               if (col_lineptrs[j][curr_nl_line[j]].ptr != NULL)
+                                                       more_lines = true;
+                                               bytes_output[j] = 0;
                                        }
                                }
 
-                               /* divider */
+                               /* print a divider, middle columns only */
                                if ((j + 1) % col_count)
                                {
                                        if (opt_border == 0)
                                                fputc(' ', fout);
-                                       else if (line_count == 0)
-                                               fputs(" | ", fout);
+                                       /* Next value is beyond past newlines? */
+                                       else if (col_lineptrs[j+1][curr_nl_line[j+1]].ptr == NULL)
+                                               fputs("   ", fout);
+                                       /* In wrapping of value? */
+                                       else if (bytes_output[j+1] != 0)
+                                               fputs(" ; ", fout);
+                                       /* After first newline value */
+                                       else if (curr_nl_line[j+1] != 0)
+                                               fputs(" : ", fout);
                                        else
-                                               fprintf(fout, " %c ", complete[j + 1] ? ' ' : ':');
+                                       /* Ordinary line */
+                                               fputs(" | ", fout);
                                }
+
                        }
+
+                       /* end of row border */
                        if (opt_border == 2)
                                fputs(" |", fout);
                        fputc('\n', fout);
-                       line_count++;
-               }
+
+               } while (more_lines);
        }
 
        if (opt->stop_table)
        {
                if (opt_border == 2 && !cancel_pressed)
-                       _print_horizontal_line(col_count, widths, opt_border, fout);
+                       _print_horizontal_line(col_count, width_wrap, opt_border, fout);
 
                /* print footers */
                if (footers && !opt_tuples_only && !cancel_pressed)
@@ -722,12 +863,16 @@ print_aligned_text(const char *title, const char *const * headers,
        }
 
        /* clean up */
-       free(widths);
-       free(heights);
+       free(width_header);
+       free(width_average);
+       free(max_width);
+       free(width_wrap);
+       free(max_nl_lines);
+       free(curr_nl_line);
        free(col_lineptrs);
-       free(format_space);
-       free(complete);
-       free(lineptr_list);
+       free(max_bytes);
+       free(header_done);
+       free(bytes_output);
        for (i = 0; i < col_count; i++)
                free(format_buf[i]);
        free(format_buf);
@@ -754,7 +899,6 @@ print_aligned_vertical(const char *title, const char *const * headers,
                                dheight = 1,
                                hformatsize = 0,
                                dformatsize = 0;
-       int                     tmp = 0;
        char       *divider;
        unsigned int cell_count = 0;
        struct lineptr *hlineptr,
@@ -779,12 +923,13 @@ print_aligned_vertical(const char *title, const char *const * headers,
        /* Find the maximum dimensions for the headers */
        for (i = 0; i < col_count; i++)
        {
-               int                     height,
+               int                     width,
+                                       height,
                                        fs;
 
-               pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &tmp, &height, &fs);
-               if (tmp > hwidth)
-                       hwidth = tmp;
+               pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &width, &height, &fs);
+               if (width > hwidth)
+                       hwidth = width;
                if (height > hheight)
                        hheight = height;
                if (fs > hformatsize)
@@ -799,7 +944,8 @@ print_aligned_vertical(const char *title, const char *const * headers,
        for (i = 0, ptr = cells; *ptr; ptr++, i++)
        {
                int                     numeric_locale_len;
-               int                     height,
+               int                     width,
+                                       height,
                                        fs;
 
                if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
@@ -807,10 +953,10 @@ print_aligned_vertical(const char *title, const char *const * headers,
                else
                        numeric_locale_len = 0;
 
-               pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &tmp, &height, &fs);
-               tmp += numeric_locale_len;
-               if (tmp > dwidth)
-                       dwidth = tmp;
+               pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &width, &height, &fs);
+               width += numeric_locale_len;
+               if (width > dwidth)
+                       dwidth = width;
                if (height > dheight)
                        dheight = height;
                if (fs > dformatsize)
@@ -821,8 +967,8 @@ print_aligned_vertical(const char *title, const char *const * headers,
         * We now have all the information we need to setup the formatting
         * structures
         */
-       dlineptr = pg_local_malloc(sizeof(*dlineptr) * dheight);
-       hlineptr = pg_local_malloc(sizeof(*hlineptr) * hheight);
+       dlineptr = pg_local_malloc((sizeof(*dlineptr) + 1) * dheight);
+       hlineptr = pg_local_malloc((sizeof(*hlineptr) + 1) * hheight);
 
        dlineptr->ptr = pg_local_malloc(dformatsize);
        hlineptr->ptr = pg_local_malloc(hformatsize);
@@ -910,7 +1056,7 @@ print_aligned_vertical(const char *title, const char *const * headers,
                                fprintf(fout, "%-s%*s", hlineptr[line_count].ptr,
                                                hwidth - hlineptr[line_count].width, "");
 
-                               if (line_count == (hheight - 1) || !hlineptr[line_count + 1].ptr)
+                               if (!hlineptr[line_count + 1].ptr)
                                        hcomplete = 1;
                        }
                        else
@@ -943,7 +1089,7 @@ print_aligned_vertical(const char *title, const char *const * headers,
                                                                dwidth - dlineptr[line_count].width, "");
                                }
 
-                               if (line_count == dheight - 1 || !dlineptr[line_count + 1].ptr)
+                               if (!dlineptr[line_count + 1].ptr)
                                        dcomplete = 1;
                        }
                        else
@@ -1823,7 +1969,8 @@ printTable(const char *title,
 {
        static const char *default_footer[] = {NULL};
        FILE       *output;
-
+       bool            is_pager = false;
+       
        if (cancel_pressed)
                return;
 
@@ -1858,6 +2005,7 @@ printTable(const char *title,
                        for (ptr = footers; *ptr; ptr++)
                                lines++;
                output = PageOutput(lines, opt->pager);
+               is_pager = (output != fout);
        }
        else
                output = fout;
@@ -1866,7 +2014,7 @@ printTable(const char *title,
 
        if (flog)
                print_aligned_text(title, headers, cells, footers, align,
-                                                  opt, flog);
+                                                  opt, is_pager, flog);
 
        switch (opt->format)
        {
@@ -1879,12 +2027,13 @@ printTable(const char *title,
                                                                         opt, output);
                        break;
                case PRINT_ALIGNED:
+               case PRINT_WRAPPED:
                        if (opt->expanded)
                                print_aligned_vertical(title, headers, cells, footers, align,
                                                                           opt, output);
                        else
                                print_aligned_text(title, headers, cells, footers, align,
-                                                                  opt, output);
+                                                                  opt, is_pager, output);
                        break;
                case PRINT_HTML:
                        if (opt->expanded)
@@ -1915,8 +2064,7 @@ printTable(const char *title,
                        exit(EXIT_FAILURE);
        }
 
-       /* Only close if we used the pager */
-       if (output != fout)
+       if (is_pager)
                ClosePager(output);
 }
 
@@ -2066,3 +2214,38 @@ setDecimalLocale(void)
        else
                thousands_sep = ".";
 }
+
+/*
+ *     Returns the byte length to the end of the specified character
+ *  and number of display characters processed (useful if the string
+ *     is shorter then dpylen).
+ */
+static int
+strlen_max_width(unsigned char *str, int *target_width, int encoding)
+{
+       unsigned char *start = str;
+       int curr_width = 0;
+
+       while (*str && curr_width < *target_width)
+       {
+               int char_width = PQdsplen((char *) str, encoding);
+
+               /*
+                *      If the display width of the new character causes
+                *      the string to exceed its target width, skip it
+                *      and return.  However, if this is the first character
+                *      of the string (*width == 0), we have to accept it.
+                */
+               if (*target_width - curr_width < char_width && curr_width != 0)
+                       break;
+                       
+               str += PQmblen((char *)str, encoding);
+
+               curr_width += char_width;
+       }
+
+       *target_width = curr_width;
+       
+       /* last byte */
+       return str - start;
+}
index 984652d0fd9ebd41924607aa8baae0a6a0fc9655..da84343df9627b12d6b7467fb19ef10507909bcb 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2008, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/print.h,v 1.35 2008/01/01 19:45:56 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/print.h,v 1.36 2008/05/08 17:04:26 momjian Exp $
  */
 #ifndef PRINT_H
 #define PRINT_H
@@ -21,6 +21,7 @@ enum printFormat
        PRINT_NOTHING = 0,                      /* to make sure someone initializes this */
        PRINT_UNALIGNED,
        PRINT_ALIGNED,
+       PRINT_WRAPPED,
        PRINT_HTML,
        PRINT_LATEX,
        PRINT_TROFF_MS
@@ -47,6 +48,8 @@ typedef struct _printTableOpt
                                                                 * decimal marker */
        char       *tableAttr;          /* attributes for HTML <table ...> */
        int                     encoding;               /* character encoding */
+       int                     env_columns;    /* $COLUMNS on psql start, 0 is unset */
+       int                     columns;                /* target width for wrapped format */
 } printTableOpt;
 
 
index 40359754b8bb5d1e2c2a71f8d612c0c1c2da176e..89c3ef71a50534fbd74d79b870a7742617725552 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2008, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/startup.c,v 1.146 2008/01/01 19:45:56 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/startup.c,v 1.147 2008/05/08 17:04:26 momjian Exp $
  */
 #include "postgres_fe.h"
 
@@ -147,6 +147,8 @@ main(int argc, char *argv[])
        pset.popt.topt.start_table = true;
        pset.popt.topt.stop_table = true;
        pset.popt.default_footer = true;
+       /* We must get COLUMNS here before readline() sets it */
+       pset.popt.topt.env_columns = getenv("COLUMNS") ? atoi(getenv("COLUMNS")) : 0;
 
        pset.notty = (!isatty(fileno(stdin)) || !isatty(fileno(stdout)));