I have updated the documentation for this patch. I consider it ready to
apply. I think it is as close to a middle ground as we are going to
get. Further adjustment will have to happen when we have more reports
from the field.
---------------------------------------------------------------------------
Bruce Momjian wrote:
> I have moved this discussion to hackers in hopes of getting more
> feedback, and moved the patch to a static URL:
>
> ftp://momjian.us/pub/postgresql/mypatches/wrap
>
> This patch adds a new '\pset format wrapped' mode that wraps long values
> to fit the table on the user's screen, or in '\pset columns' columns in
> an output to file or pipe. The documentation additions are at the top
> of the patch.
>
> Sample:
>
> \pset format wrapped
> Output format is wrapped.
>
> \pset columns 70
> Target column width for "wrap" format is 70.
>
> SELECT 1, 2, repeat('a', 80), repeat('b', 80), E'a\nb\nc', 1
> FROM generate_series(1,2)\g
>
> ?column? | ?column? | repeat | repeat | ?column? | ?column?
> ----------+----------+------------+-------------+----------+----------
> 1 | 2 | aaaaaaaaaa | bbbbbbbbbbb | a | 1
> ; aaaaaaaaaa ; bbbbbbbbbbb : b
> ; aaaaaaaaaa ; bbbbbbbbbbb : c
> ; aaaaaaaaaa ; bbbbbbbbbbb
> ; aaaaaaaaaa ; bbbbbbbbbbb
> ; aaaaaaaaaa ; bbbbbbbbbbb
> ; aaaaaaaaaa ; bbbbbbbbbbb
> ; aaaaaaaaaa ; bbb
> 1 | 2 | aaaaaaaaaa | bbbbbbbbbbb | a | 1
> ; aaaaaaaaaa ; bbbbbbbbbbb : b
> ; aaaaaaaaaa ; bbbbbbbbbbb : c
> ; aaaaaaaaaa ; bbbbbbbbbbb
> ; aaaaaaaaaa ; bbbbbbbbbbb
> ; aaaaaaaaaa ; bbbbbbbbbbb
> ; aaaaaaaaaa ; bbbbbbbbbbb
> ; aaaaaaaaaa ; bbb
> (2 rows)
>
> You will notice:
>
> o I have pulled up newline values to appear in the same rows
> as the wrapped text
> o Colons are used on the left for newline values
> o Semicolons are used on the left for wrapped values
> o There are no vertical bars for values that don't extend
> to the wrapped or newline rows. This is how our
> newline display has always worked so it was copied
> by the wrapped code
> o The left column has no indicator of wrapping or newlines
> because there is no left border
>
> We could use dashes to indicated wrapped values, but we don't. It would
> be nice if someone could do some multi-byte testing of this,
> particularly for characters that have a display width greater than one.
>
> I think this patch is ready for application.
>
> Should 'wrapped' be the default for certain operations, like \df?
> 'wrapped' mode is really good for a table that would fit the screen
> width except for a few wide values.
--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com
+ If your life is a hard drive, Christ can be your backup. +
Index: doc/src/sgml/ref/psql-ref.sgml
===================================================================
RCS file: /cvsroot/pgsql/doc/src/sgml/ref/psql-ref.sgml,v
retrieving revision 1.199
diff -c -c -r1.199 psql-ref.sgml
*** doc/src/sgml/ref/psql-ref.sgml 30 Mar 2008 18:10:20 -0000 1.199
--- doc/src/sgml/ref/psql-ref.sgml 29 Apr 2008 01:24:44 -0000
***************
*** 1513,1519 ****
<listitem>
<para>
Sets the output format to one of <literal>unaligned</literal>,
! <literal>aligned</literal>, <literal>html</literal>,
<literal>latex</literal>, or <literal>troff-ms</literal>.
Unique abbreviations are allowed. (That would mean one letter
is enough.)
--- 1513,1520 ----
<listitem>
<para>
Sets the output format to one of <literal>unaligned</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.)
***************
*** 1525,1531 ****
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
<quote>LaTeX</quote> modes put out tables that are intended to
be included in documents using the respective mark-up
--- 1526,1535 ----
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.
! <quote>Wrapped</quote> is like <literal>aligned</> but wraps
! to a target width of <literal>\pset columns</> or the
! width of the screen. 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
***************
*** 1537,1542 ****
--- 1541,1557 ----
</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>
<para>
***************
*** 2707,2712 ****
--- 2722,2739 ----
<title>Environment</title>
<variablelist>
+
+ <varlistentry>
+ <term><envar>COLUMNS</envar></term>
+
+ <listitem>
+ <para>
+ Used for the <literal>wrapped</> format when screen width
+ detection fails.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><envar>PAGER</envar></term>
Index: src/bin/psql/command.c
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/command.c,v
retrieving revision 1.186
diff -c -c -r1.186 command.c
*** src/bin/psql/command.c 1 Jan 2008 19:45:55 -0000 1.186
--- src/bin/psql/command.c 29 Apr 2008 01:24:45 -0000
***************
*** 1526,1531 ****
--- 1526,1534 ----
case PRINT_ALIGNED:
return "aligned";
break;
+ case PRINT_WRAPPED:
+ return "wrapped";
+ break;
case PRINT_HTML:
return "html";
break;
***************
*** 1559,1564 ****
--- 1562,1569 ----
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)
***************
*** 1567,1573 ****
popt->topt.format = PRINT_TROFF_MS;
else
{
! psql_error("\\pset: allowed formats are unaligned, aligned, html, latex, troff-ms\n");
return false;
}
--- 1572,1578 ----
popt->topt.format = PRINT_TROFF_MS;
else
{
! psql_error("\\pset: allowed formats are unaligned, aligned, wrapped, html, latex, troff-ms\n");
return false;
}
***************
*** 1748,1753 ****
--- 1753,1768 ----
}
}
+ /* 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: src/bin/psql/mbprint.c
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/mbprint.c,v
retrieving revision 1.30
diff -c -c -r1.30 mbprint.c
*** src/bin/psql/mbprint.c 16 Apr 2008 18:18:00 -0000 1.30
--- src/bin/psql/mbprint.c 29 Apr 2008 01:24:47 -0000
***************
*** 204,211 ****
/*
* 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_format_size: Number of bytes required to store formatted representation of string
*/
int
--- 204,211 ----
/*
* pg_wcssize takes the given string in the given encoding and returns three
* values:
! * 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,287 ****
return width;
}
void
pg_wcsformat(unsigned char *pwcs, size_t len, int encoding,
! struct lineptr * lines, int count)
{
int w,
chlen = 0;
--- 279,292 ----
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)
{
int w,
chlen = 0;
***************
*** 307,312 ****
--- 312,318 ----
if (count == 0)
exit(1); /* Screwup */
+ /* make next line point to remaining memory */
lines->ptr = ptr;
}
else if (*pwcs == '\r') /* Linefeed */
***************
*** 353,364 ****
}
len -= chlen;
}
- *ptr++ = '\0';
lines->width = linewidth;
! lines++;
! count--;
! if (count > 0)
! lines->ptr = NULL;
}
unsigned char *
--- 359,371 ----
}
len -= chlen;
}
lines->width = linewidth;
! *ptr++ = '\0'; /* Terminate formatted string */
!
! if (count == 0)
! exit(1); /* Screwup */
!
! (lines+1)->ptr = NULL; /* terminate line array */
}
unsigned char *
Index: src/bin/psql/print.c
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/print.c,v
retrieving revision 1.97
diff -c -c -r1.97 print.c
*** src/bin/psql/print.c 27 Mar 2008 03:57:34 -0000 1.97
--- src/bin/psql/print.c 29 Apr 2008 01:24:48 -0000
***************
*** 28,33 ****
--- 28,35 ----
#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,48 ****
--- 45,51 ----
static char *grouping;
static char *thousands_sep;
+
static void *
pg_local_malloc(size_t size)
{
***************
*** 396,401 ****
--- 399,407 ----
}
+ /*
+ * 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,
***************
*** 404,429 ****
{
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 char **format_buf;
! const char *const * ptr;
! struct lineptr **col_lineptrs; /* pointers to line pointer for each
! * column */
! struct lineptr *lineptr_list; /* complete list of linepointers */
! int *complete; /* Array remembering which columns have
! * completed output */
if (cancel_pressed)
return;
--- 410,441 ----
{
bool opt_tuples_only = opt->tuples_only;
bool opt_numeric_locale = opt->numericLocale;
int encoding = opt->encoding;
! 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;
! struct lineptr **col_lineptrs; /* pointers to line pointer per column */
! 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,711 ****
if (col_count > 0)
{
! widths = pg_local_calloc(col_count, sizeof(*widths));
! heights = pg_local_calloc(col_count, sizeof(*heights));
col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs));
! format_space = pg_local_calloc(col_count, sizeof(*format_space));
format_buf = pg_local_calloc(col_count, sizeof(*format_buf));
! complete = pg_local_calloc(col_count, sizeof(*complete));
}
else
{
! widths = NULL;
! heights = NULL;
col_lineptrs = NULL;
! format_space = NULL;
format_buf = NULL;
! complete = NULL;
}
! /* count cells (rows * cols) */
! for (ptr = cells; *ptr; ptr++)
! cell_count++;
!
! /* calc column widths */
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;
}
! for (i = 0, ptr = cells; *ptr; ptr++, i++)
{
! int numeric_locale_len;
! int height,
! space;
! 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 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;
}
if (opt_border == 0)
! total_w = col_count - 1;
else if (opt_border == 1)
! total_w = col_count * 3 - 1;
else
! total_w = col_count * 3 + 1;
for (i = 0; i < col_count; i++)
! total_w += widths[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
*/
! if (col_count > 0)
{
! int heights_total = 0;
! struct lineptr *lineptr;
! for (i = 0; i < col_count; i++)
! heights_total += heights[i];
! lineptr = lineptr_list = pg_local_calloc(heights_total, sizeof(*lineptr_list));
! for (i = 0; i < col_count; i++)
{
! col_lineptrs[i] = lineptr;
! lineptr += heights[i];
! format_buf[i] = pg_local_malloc(format_space[i]);
! col_lineptrs[i]->ptr = format_buf[i];
}
}
- else
- lineptr_list = NULL;
if (opt->start_table)
{
/* print title */
if (title && !opt_tuples_only)
{
! /* Get width & height */
! int height;
! pg_wcssize((unsigned char *) title, strlen(title), encoding, &tmp, &height, NULL);
! if (tmp >= total_w)
! fprintf(fout, "%s\n", title);
else
! fprintf(fout, "%-*s%s\n", (total_w - tmp) / 2, "", title);
}
/* print headers */
if (!opt_tuples_only)
{
! int cols_todo;
! int line_count;
if (opt_border == 2)
! _print_horizontal_line(col_count, widths, 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]);
! cols_todo = col_count;
! line_count = 0;
! memset(complete, 0, col_count * sizeof(int));
! while (cols_todo)
{
if (opt_border == 2)
! fprintf(fout, "|%c", line_count ? '+' : ' ');
else if (opt_border == 1)
! fputc(line_count ? '+' : ' ', fout);
for (i = 0; i < col_count; i++)
{
unsigned int nbspace;
! struct lineptr *this_line = col_lineptrs[i] + line_count;
! if (!complete[i])
{
! nbspace = widths[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)
{
! cols_todo--;
! complete[i] = 1;
}
}
else
! fprintf(fout, "%*s", widths[i], "");
if (i < col_count - 1)
{
if (opt_border == 0)
! fputc(line_count ? '+' : ' ', fout);
else
! fprintf(fout, " |%c", line_count ? '+' : ' ');
}
}
! line_count++;
if (opt_border == 2)
fputs(" |", fout);
else if (opt_border == 1)
! fputc(' ', fout);;
fputc('\n', fout);
}
! _print_horizontal_line(col_count, widths, opt_border, fout);
}
}
! /* print cells */
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 */
if (cancel_pressed)
break;
for (j = 0; j < col_count; j++)
! pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], heights[j]);
! line_count = 0;
! memset(complete, 0, col_count * sizeof(int));
! while (cols_todo)
{
! /* beginning of line */
if (opt_border == 2)
fputs("| ", fout);
else if (opt_border == 1)
fputc(' ', fout);
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);
!
! if (complete[j]) /* Just print spaces... */
! {
! if (finalspaces)
! fprintf(fout, "%*s", widths[j], "");
! }
else
{
! /* content */
! if (opt_align[j] == 'r')
{
! 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);
}
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--;
}
}
! /* divider */
if ((j + 1) % col_count)
{
if (opt_border == 0)
fputc(' ', fout);
! else if (line_count == 0)
! fputs(" | ", fout);
else
! fprintf(fout, " %c ", complete[j + 1] ? ' ' : ':');
}
}
if (opt_border == 2)
fputs(" |", fout);
fputc('\n', fout);
! line_count++;
! }
}
if (opt->stop_table)
{
if (opt_border == 2 && !cancel_pressed)
! _print_horizontal_line(col_count, widths, opt_border, fout);
/* print footers */
if (footers && !opt_tuples_only && !cancel_pressed)
--- 449,856 ----
if (col_count > 0)
{
! 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));
! max_bytes = pg_local_calloc(col_count, sizeof(*max_bytes));
format_buf = pg_local_calloc(col_count, sizeof(*format_buf));
! header_done = pg_local_calloc(col_count, sizeof(*header_done));
! bytes_output = pg_local_calloc(col_count, sizeof(*bytes_output));
}
else
{
! width_header = NULL;
! width_average = NULL;
! max_width = NULL;
! width_wrap = NULL;
! max_nl_lines = NULL;
! curr_nl_line = NULL;
col_lineptrs = NULL;
! max_bytes = NULL;
format_buf = NULL;
! header_done = NULL;
! bytes_output = NULL;
}
! /* scan all column headers, find maximum width and max max_nl_lines */
for (i = 0; i < col_count; i++)
{
! 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;
}
! /* scan all cells, find maximum width, compute cell_count */
! for (i = 0, ptr = cells; *ptr; ptr++, i++, cell_count++)
{
! int width,
! nl_lines,
! bytes_required;
! /* 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;
! 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)
! width_total = col_count - 1;
else if (opt_border == 1)
! width_total = col_count * 3 - 1;
else
! width_total = col_count * 3 + 1;
! total_header_width = width_total;
for (i = 0; i < col_count; i++)
! {
! width_total += max_width[i];
! total_header_width += width_header[i];
! }
/*
! * 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.
*/
! for (i = 0; i < col_count; i++)
{
! /* Add entry for ptr == NULL array termination */
! col_lineptrs[i] = pg_local_calloc(max_nl_lines[i] + 1,
! sizeof(**col_lineptrs));
! format_buf[i] = pg_local_malloc(max_bytes[i] + 1);
! col_lineptrs[i]->ptr = format_buf[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];
!
! /*
! * 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 (opt->format == PRINT_WRAPPED)
! {
! /* Get terminal width */
! if (opt->columns > 0)
! output_columns = opt->columns;
! else if (fout == stdout && isatty(fileno(stdout)))
! {
! #ifdef TIOCGWINSZ
! struct winsize screen_size;
!
! if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) != -1)
! output_columns = screen_size.ws_col;
! else
! #endif
! {
! const char *columns_env = getenv("COLUMNS");
!
! if (columns_env)
! output_columns = atoi(columns_env);
! }
! }
!
! /*
! * 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;
!
! if (ratio > max_ratio)
! {
! max_ratio = ratio;
! worst_col = i;
! }
! }
! }
! /* Exit loop if we can't squeeze any more. */
! if (worst_col == -1)
! break;
!
! /* Decrease width of target column by one. */
! width_wrap[worst_col]--;
! width_total--;
! }
}
}
+ /* time to output */
if (opt->start_table)
{
/* print title */
if (title && !opt_tuples_only)
{
! int width, height;
! 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", (width_total - width) / 2, "", title); /* Centered */
}
/* print headers */
if (!opt_tuples_only)
{
! int more_col_wrapping;
! int curr_nl_line;
if (opt_border == 2)
! _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], max_nl_lines[i]);
! 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", curr_nl_line ? '+' : ' ');
else if (opt_border == 1)
! fputc(curr_nl_line ? '+' : ' ', fout);
for (i = 0; i < col_count; i++)
{
unsigned int nbspace;
! struct lineptr *this_line = col_lineptrs[i] + curr_nl_line;
! if (!header_done[i])
{
! nbspace = width_wrap[i] - this_line->width;
/* centered */
fprintf(fout, "%-*s%s%-*s",
nbspace / 2, "", this_line->ptr, (nbspace + 1) / 2, "");
! if (!(this_line + 1)->ptr)
{
! more_col_wrapping--;
! header_done[i] = 1;
}
}
else
! fprintf(fout, "%*s", width_wrap[i], "");
if (i < col_count - 1)
{
if (opt_border == 0)
! fputc(curr_nl_line ? '+' : ' ', fout);
else
! fprintf(fout, " |%c", curr_nl_line ? '+' : ' ');
}
}
! curr_nl_line++;
if (opt_border == 2)
fputs(" |", fout);
else if (opt_border == 1)
! fputc(' ', fout);
fputc('\n', fout);
}
! _print_horizontal_line(col_count, width_wrap, opt_border, fout);
}
}
! /* print cells, one loop per row */
for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count)
{
! 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], max_nl_lines[j]);
! curr_nl_line[j] = 0;
! 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
{
! 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++)
{
! /* 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];
!
! /* Past newline lines so pad for other columns */
! if (!this_line->ptr)
! fprintf(fout, "%*s", width_wrap[j], "");
else
{
! /* 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 */
{
! /* 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
{
! /* 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;
}
}
! /* print a divider, middle columns only */
if ((j + 1) % col_count)
{
if (opt_border == 0)
fputc(' ', 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
! /* Ordinary line */
! fputs(" | ", fout);
}
+
}
+
+ /* end of row border */
if (opt_border == 2)
fputs(" |", fout);
fputc('\n', fout);
!
! } while (more_lines);
}
if (opt->stop_table)
{
if (opt_border == 2 && !cancel_pressed)
! _print_horizontal_line(col_count, width_wrap, opt_border, fout);
/* print footers */
if (footers && !opt_tuples_only && !cancel_pressed)
***************
*** 722,733 ****
}
/* clean up */
! free(widths);
! free(heights);
free(col_lineptrs);
! free(format_space);
! free(complete);
! free(lineptr_list);
for (i = 0; i < col_count; i++)
free(format_buf[i]);
free(format_buf);
--- 867,882 ----
}
/* clean up */
! free(width_header);
! free(width_average);
! free(max_width);
! free(width_wrap);
! free(max_nl_lines);
! free(curr_nl_line);
free(col_lineptrs);
! free(max_bytes);
! free(header_done);
! free(bytes_output);
for (i = 0; i < col_count; i++)
free(format_buf[i]);
free(format_buf);
***************
*** 754,760 ****
dheight = 1,
hformatsize = 0,
dformatsize = 0;
- int tmp = 0;
char *divider;
unsigned int cell_count = 0;
struct lineptr *hlineptr,
--- 903,908 ----
***************
*** 779,790 ****
/* Find the maximum dimensions for the headers */
for (i = 0; i < col_count; i++)
{
! int height,
fs;
! pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &tmp, &height, &fs);
! if (tmp > hwidth)
! hwidth = tmp;
if (height > hheight)
hheight = height;
if (fs > hformatsize)
--- 927,939 ----
/* Find the maximum dimensions for the headers */
for (i = 0; i < col_count; i++)
{
! int width,
! height,
fs;
! 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,805 ****
for (i = 0, ptr = cells; *ptr; ptr++, i++)
{
int numeric_locale_len;
! int height,
fs;
if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
--- 948,955 ----
for (i = 0, ptr = cells; *ptr; ptr++, i++)
{
int numeric_locale_len;
! int width,
! height,
fs;
if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
***************
*** 807,816 ****
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;
if (height > dheight)
dheight = height;
if (fs > dformatsize)
--- 957,966 ----
else
numeric_locale_len = 0;
! 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,828 ****
* 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->ptr = pg_local_malloc(dformatsize);
hlineptr->ptr = pg_local_malloc(hformatsize);
--- 971,978 ----
* We now have all the information we need to setup the formatting
* structures
*/
! 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,916 ****
fprintf(fout, "%-s%*s", hlineptr[line_count].ptr,
hwidth - hlineptr[line_count].width, "");
! if (line_count == (hheight - 1) || !hlineptr[line_count + 1].ptr)
hcomplete = 1;
}
else
--- 1060,1066 ----
fprintf(fout, "%-s%*s", hlineptr[line_count].ptr,
hwidth - hlineptr[line_count].width, "");
! if (!hlineptr[line_count + 1].ptr)
hcomplete = 1;
}
else
***************
*** 943,949 ****
dwidth - dlineptr[line_count].width, "");
}
! if (line_count == dheight - 1 || !dlineptr[line_count + 1].ptr)
dcomplete = 1;
}
else
--- 1093,1099 ----
dwidth - dlineptr[line_count].width, "");
}
! if (!dlineptr[line_count + 1].ptr)
dcomplete = 1;
}
else
***************
*** 1879,1884 ****
--- 2029,2035 ----
opt, output);
break;
case PRINT_ALIGNED:
+ case PRINT_WRAPPED:
if (opt->expanded)
print_aligned_vertical(title, headers, cells, footers, align,
opt, output);
***************
*** 2066,2068 ****
--- 2217,2254 ----
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: src/bin/psql/print.h
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/print.h,v
retrieving revision 1.35
diff -c -c -r1.35 print.h
*** src/bin/psql/print.h 1 Jan 2008 19:45:56 -0000 1.35
--- src/bin/psql/print.h 29 Apr 2008 01:24:48 -0000
***************
*** 21,26 ****
--- 21,27 ----
PRINT_NOTHING = 0, /* to make sure someone initializes this */
PRINT_UNALIGNED,
PRINT_ALIGNED,
+ PRINT_WRAPPED,
PRINT_HTML,
PRINT_LATEX,
PRINT_TROFF_MS
***************
*** 47,52 ****
--- 48,54 ----
* decimal marker */
char *tableAttr; /* attributes for HTML <table ...> */
int encoding; /* character encoding */
+ int columns; /* target width for wrapped format */
} printTableOpt;