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  <[EMAIL PROTECTED]>        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;
  
  
-- 
Sent via pgsql-patches mailing list (pgsql-patches@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-patches

Reply via email to