Re: [HACKERS] Proposed patch - psql wraps at window width - Mailing list pgsql-patches
From | Bruce Momjian |
---|---|
Subject | Re: [HACKERS] Proposed patch - psql wraps at window width |
Date | |
Msg-id | 200804290211.m3T2BGI04844@momjian.us Whole thread Raw |
Responses |
Re: [HACKERS] Proposed patch - psql wraps at window width
|
List | pgsql-patches |
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;
pgsql-patches by date: