1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 1997-9, 2000, 2007, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>. */
34 #ifdef GWINSZ_IN_SYS_IOCTL
35 # include <sys/ioctl.h>
38 #include "data/file-name.h"
39 #include "data/file-handle-def.h"
40 #include "data/settings.h"
41 #include "libpspp/assertion.h"
42 #include "libpspp/cast.h"
43 #include "libpspp/compiler.h"
44 #include "libpspp/message.h"
45 #include "libpspp/start-date.h"
46 #include "libpspp/string-map.h"
47 #include "libpspp/u8-line.h"
48 #include "libpspp/version.h"
49 #include "output/ascii.h"
51 #include "output/cairo-chart.h"
53 #include "output/chart-item-provider.h"
54 #include "output/driver-provider.h"
55 #include "output/message-item.h"
56 #include "output/options.h"
57 #include "output/render.h"
58 #include "output/table-item.h"
59 #include "output/text-item.h"
61 #include "gl/minmax.h"
62 #include "gl/xalloc.h"
66 #define _(msgid) gettext (msgid)
68 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
80 #define N_BOX (ASCII_N_LINES * ASCII_N_LINES \
81 * ASCII_N_LINES * ASCII_N_LINES)
83 static const ucs4_t ascii_box_chars[N_BOX] =
114 static const ucs4_t unicode_box_chars[N_BOX] =
116 0x0020, 0x2575, 0x2551,
117 0x2574, 0x256f, 0x255c,
118 0x2550, 0x255b, 0x255d,
119 0x2577, 0x2502, 0x2551,
120 0x256e, 0x2524, 0x2562,
121 0x2555, 0x2561, 0x2563,
122 0x2551, 0x2551, 0x2551,
123 0x2556, 0x2562, 0x2562,
124 0x2557, 0x2563, 0x2563,
125 0x2576, 0x2570, 0x2559,
126 0x2500, 0x2534, 0x2568,
127 0x2550, 0x2567, 0x2569,
128 0x256d, 0x251c, 0x255f,
129 0x252c, 0x253c, 0x256a,
130 0x2564, 0x256a, 0x256c,
131 0x2553, 0x255f, 0x255f,
132 0x2565, 0x256b, 0x256b,
133 0x2566, 0x256c, 0x256c,
134 0x2550, 0x2558, 0x255a,
135 0x2550, 0x2567, 0x2569,
136 0x2550, 0x2567, 0x2569,
137 0x2552, 0x255e, 0x2560,
138 0x2564, 0x256a, 0x256c,
139 0x2564, 0x256a, 0x256c,
140 0x2554, 0x2560, 0x2560,
141 0x2560, 0x256c, 0x256c,
142 0x2566, 0x256c, 0x256c,
146 ascii_line_from_render_line (int render_line)
150 case RENDER_LINE_NONE:
151 return ASCII_LINE_NONE;
153 case RENDER_LINE_SINGLE:
154 case RENDER_LINE_DASHED:
155 case RENDER_LINE_THICK:
156 case RENDER_LINE_THIN:
157 return ASCII_LINE_SINGLE;
159 case RENDER_LINE_DOUBLE:
160 return ASCII_LINE_DOUBLE;
163 return ASCII_LINE_NONE;
169 make_box_index (int left_, int right_, int top_, int bottom_)
171 bool rtl = render_direction_rtl ();
172 int left = ascii_line_from_render_line (rtl ? right_ : left_);
173 int right = ascii_line_from_render_line (rtl ? left_ : right_);
174 int top = ascii_line_from_render_line (top_);
175 int bottom = ascii_line_from_render_line (bottom_);
178 idx = idx * ASCII_N_LINES + bottom;
179 idx = idx * ASCII_N_LINES + left;
180 idx = idx * ASCII_N_LINES + top;
184 /* ASCII output driver. */
187 struct output_driver driver;
189 /* User parameters. */
190 bool append; /* Append if output file already exists? */
191 bool emphasis; /* Enable bold and underline in output? */
192 char *chart_file_name; /* Name of files used for charts. */
195 /* Colours for charts */
196 struct cell_color fg;
197 struct cell_color bg;
200 /* How the page width is determined: */
202 FIXED_WIDTH, /* Specified by configuration. */
203 VIEW_WIDTH, /* From SET WIDTH. */
204 TERMINAL_WIDTH /* From the terminal's width. */
206 int width; /* Page width. */
208 int min_hbreak; /* Min cell size to break across pages. */
210 const ucs4_t *box; /* Line & box drawing characters. */
212 /* Internal state. */
213 struct file_handle *handle;
214 FILE *file; /* Output file. */
215 bool error; /* Output error? */
216 struct u8_line *lines; /* Page content. */
217 int allocated_lines; /* Number of lines allocated. */
218 int chart_cnt; /* Number of charts so far. */
219 int object_cnt; /* Number of objects so far. */
220 struct render_params params;
223 static const struct output_driver_class ascii_driver_class;
225 static void ascii_submit (struct output_driver *, const struct output_item *);
227 static int get_terminal_width (void);
229 static bool update_page_size (struct ascii_driver *, bool issue_error);
230 static int parse_page_size (struct driver_option *);
232 static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2],
233 enum render_line_style styles[TABLE_N_AXES][2],
234 struct cell_color colors[TABLE_N_AXES][2]);
235 static void ascii_measure_cell_width (void *, const struct table_cell *,
237 static int ascii_measure_cell_height (void *, const struct table_cell *,
239 static void ascii_draw_cell (void *, const struct table_cell *, int color_idx,
240 int bb[TABLE_N_AXES][2], int valign_offset,
241 int spill[TABLE_N_AXES][2],
242 int clip[TABLE_N_AXES][2]);
244 static struct ascii_driver *
245 ascii_driver_cast (struct output_driver *driver)
247 assert (driver->class == &ascii_driver_class);
248 return UP_CAST (driver, struct ascii_driver, driver);
251 static struct driver_option *
252 opt (struct output_driver *d, struct string_map *options, const char *key,
253 const char *default_value)
255 return driver_option_get (d, options, key, default_value);
258 /* Return true iff the terminal appears to be an xterm with
259 UTF-8 capabilities */
261 term_is_utf8_xterm (void)
263 const char *term = getenv ("TERM");
264 const char *xterm_locale = getenv ("XTERM_LOCAL");
265 return (term && xterm_locale
266 && !strcmp (term, "xterm")
267 && (strcasestr (xterm_locale, "utf8")
268 || strcasestr (xterm_locale, "utf-8")));
271 static struct output_driver *
272 ascii_create (struct file_handle *fh, enum settings_output_devices device_type,
273 struct string_map *o)
275 enum { BOX_ASCII, BOX_UNICODE } box;
276 struct output_driver *d;
277 struct ascii_driver *a;
279 a = xzalloc (sizeof *a);
281 output_driver_init (&a->driver, &ascii_driver_class, fh_get_file_name (fh), device_type);
282 a->append = parse_boolean (opt (d, o, "append", "false"));
283 a->emphasis = parse_boolean (opt (d, o, "emphasis", "false"));
285 a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", fh_get_file_name (fh)));
289 bool terminal = !strcmp (fh_get_file_name (fh), "-") && isatty (1);
290 a->width = parse_page_size (opt (d, o, "width", "-1"));
291 a->width_mode = (a->width > 0 ? FIXED_WIDTH
292 : terminal ? TERMINAL_WIDTH
294 a->min_hbreak = parse_int (opt (d, o, "min-hbreak", "-1"), -1, INT_MAX);
297 a->bg = parse_color (opt (d, o, "background-color", "#FFFFFFFFFFFF"));
298 a->fg = parse_color (opt (d, o, "foreground-color", "#000000000000"));
301 const char *default_box = (terminal && (!strcmp (locale_charset (), "UTF-8")
302 || term_is_utf8_xterm ())
303 ? "unicode" : "ascii");
304 box = parse_enum (opt (d, o, "box", default_box),
306 "unicode", BOX_UNICODE,
308 a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
313 a->allocated_lines = 0;
317 static const struct render_ops ascii_render_ops = {
318 .draw_line = ascii_draw_line,
319 .measure_cell_width = ascii_measure_cell_width,
320 .measure_cell_height = ascii_measure_cell_height,
321 .adjust_break = NULL,
322 .draw_cell = ascii_draw_cell,
324 a->params.ops = &ascii_render_ops;
326 a->params.size[H] = a->width;
327 a->params.size[V] = INT_MAX;
328 a->params.font_size[H] = 1;
329 a->params.font_size[V] = 1;
331 static const int ascii_line_widths[RENDER_N_LINES] = {
332 [RENDER_LINE_NONE] = 0,
333 [RENDER_LINE_SINGLE] = 1,
334 [RENDER_LINE_DASHED] = 1,
335 [RENDER_LINE_THICK] = 1,
336 [RENDER_LINE_THIN] = 1,
337 [RENDER_LINE_DOUBLE] = 1,
339 a->params.line_widths = ascii_line_widths;
340 a->params.supports_margins = false;
341 a->params.rtl = render_direction_rtl ();
343 if (!update_page_size (a, true))
346 a->file = fn_open (a->handle, a->append ? "a" : "w");
349 msg_error (errno, _("ascii: opening output file `%s'"),
350 fh_get_file_name (a->handle));
357 output_driver_destroy (d);
362 parse_page_size (struct driver_option *option)
364 int dim = atol (option->default_value);
366 if (option->value != NULL)
368 if (!strcmp (option->value, "auto"))
376 value = strtol (option->value, &tail, 0);
377 if (value >= 1 && errno != ERANGE && *tail == '\0')
380 msg (MW, _("%s: %s must be positive integer or `auto'"),
381 option->driver_name, option->name);
385 driver_option_destroy (option);
390 /* Re-calculates the page width based on settings, margins, and, if "auto" is
391 set, the size of the user's terminal window or GUI output window. */
393 update_page_size (struct ascii_driver *a, bool issue_error)
395 enum { MIN_WIDTH = 6 };
397 int want_width = (a->width_mode == VIEW_WIDTH ? settings_get_viewwidth ()
398 : a->width_mode == TERMINAL_WIDTH ? get_terminal_width ()
400 bool ok = want_width >= MIN_WIDTH;
401 if (!ok && issue_error)
402 msg (ME, _("ascii: page must be at least %d characters wide, but "
403 "as configured is only %d characters"),
404 MIN_WIDTH, want_width);
406 a->width = ok ? want_width : MIN_WIDTH;
407 a->params.size[H] = a->width;
408 a->params.min_break[H] = a->min_hbreak >= 0 ? a->min_hbreak : a->width / 2;
414 ascii_destroy (struct output_driver *driver)
416 struct ascii_driver *a = ascii_driver_cast (driver);
420 fn_close (a->handle, a->file);
421 fh_unref (a->handle);
422 free (a->chart_file_name);
423 for (i = 0; i < a->allocated_lines; i++)
424 u8_line_destroy (&a->lines[i]);
430 ascii_flush (struct output_driver *driver)
432 struct ascii_driver *a = ascii_driver_cast (driver);
438 ascii_output_lines (struct ascii_driver *a, size_t n_lines)
440 for (size_t y = 0; y < n_lines; y++)
442 if (y < a->allocated_lines)
444 struct u8_line *line = &a->lines[y];
446 while (ds_chomp_byte (&line->s, ' '))
448 fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
449 u8_line_clear (&a->lines[y]);
451 putc ('\n', a->file);
456 ascii_output_table_item (struct ascii_driver *a,
457 const struct table_item *table_item)
459 struct render_pager *p;
461 update_page_size (a, false);
464 putc ('\n', a->file);
466 p = render_pager_create (&a->params, table_item);
467 for (int i = 0; render_pager_has_next (p); i++)
470 putc ('\n', a->file);
471 ascii_output_lines (a, render_pager_draw_next (p, INT_MAX));
473 render_pager_destroy (p);
477 ascii_output_table_item_unref (struct ascii_driver *a,
478 struct table_item *table_item)
480 ascii_output_table_item (a, table_item);
481 table_item_unref (table_item);
485 ascii_submit (struct output_driver *driver,
486 const struct output_item *output_item)
488 struct ascii_driver *a = ascii_driver_cast (driver);
493 if (is_table_item (output_item))
494 ascii_output_table_item (a, to_table_item (output_item));
496 else if (is_chart_item (output_item) && a->chart_file_name != NULL)
498 struct chart_item *chart_item = to_chart_item (output_item);
501 file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
505 if (file_name != NULL)
507 struct text_item *text_item;
509 text_item = text_item_create_nocopy (
511 xasprintf (_("See %s for a chart."), file_name),
514 ascii_submit (driver, &text_item->output_item);
515 text_item_unref (text_item);
519 #endif /* HAVE_CAIRO */
520 else if (is_text_item (output_item))
522 const struct text_item *text_item = to_text_item (output_item);
523 enum text_item_type type = text_item_get_type (text_item);
525 if (type != TEXT_ITEM_PAGE_TITLE)
526 ascii_output_table_item_unref (
527 a, text_item_to_table_item (text_item_ref (text_item)));
529 else if (is_message_item (output_item))
530 ascii_output_table_item_unref (
531 a, text_item_to_table_item (
532 message_item_to_text_item (
534 output_item_ref (output_item)))));
537 const struct output_driver_factory txt_driver_factory =
538 { "txt", "-", ascii_create };
539 const struct output_driver_factory list_driver_factory =
540 { "list", "-", ascii_create };
542 static const struct output_driver_class ascii_driver_class =
550 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
552 static void ascii_layout_cell (struct ascii_driver *,
553 const struct table_cell *,
554 int bb[TABLE_N_AXES][2],
555 int clip[TABLE_N_AXES][2],
556 int *width, int *height);
559 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
560 enum render_line_style styles[TABLE_N_AXES][2],
561 struct cell_color colors[TABLE_N_AXES][2] UNUSED)
563 struct ascii_driver *a = a_;
570 /* Clip to the page. */
571 x0 = MAX (bb[H][0], 0);
572 y0 = MAX (bb[V][0], 0);
573 x1 = MIN (bb[H][1], a->width);
575 if (x1 <= 0 || y1 <= 0 || x0 >= a->width)
579 uc = a->box[make_box_index (styles[V][0], styles[V][1],
580 styles[H][0], styles[H][1])];
581 mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
582 for (y = y0; y < y1; y++)
584 char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
585 for (x = x0; x < x1; x++)
587 memcpy (p, mbchar, mblen);
594 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
595 int *min_width, int *max_width)
597 struct ascii_driver *a = a_;
598 int bb[TABLE_N_AXES][2];
599 int clip[TABLE_N_AXES][2];
606 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
607 ascii_layout_cell (a, cell, bb, clip, max_width, &h);
609 if (cell->n_footnotes || strchr (cell->text, ' ')
610 || cell->n_subscripts || cell->superscript)
613 ascii_layout_cell (a, cell, bb, clip, min_width, &h);
616 *min_width = *max_width;
620 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
622 struct ascii_driver *a = a_;
623 int bb[TABLE_N_AXES][2];
624 int clip[TABLE_N_AXES][2];
631 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
632 ascii_layout_cell (a, cell, bb, clip, &w, &h);
637 ascii_draw_cell (void *a_, const struct table_cell *cell, int color_idx UNUSED,
638 int bb[TABLE_N_AXES][2], int valign_offset,
639 int spill[TABLE_N_AXES][2] UNUSED,
640 int clip[TABLE_N_AXES][2])
642 struct ascii_driver *a = a_;
645 bb[V][0] += valign_offset;
646 ascii_layout_cell (a, cell, bb, clip, &w, &h);
650 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
652 if (y >= a->allocated_lines)
654 size_t new_alloc = MAX (25, a->allocated_lines);
655 while (new_alloc <= y)
656 new_alloc = xtimes (new_alloc, 2);
657 a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
658 for (size_t i = a->allocated_lines; i < new_alloc; i++)
659 u8_line_init (&a->lines[i]);
660 a->allocated_lines = new_alloc;
662 return u8_line_reserve (&a->lines[y], x0, x1, n);
666 text_draw (struct ascii_driver *a, enum table_halign halign, int options,
667 bool bold, bool underline,
668 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
669 int y, const uint8_t *string, int n, size_t width)
671 int x0 = MAX (0, clip[H][0]);
672 int y0 = MAX (0, clip[V][0]);
673 int x1 = MIN (a->width, clip[H][1]);
677 if (y < y0 || y >= y1)
680 switch (table_halign_interpret (halign, options & TAB_NUMERIC))
682 case TABLE_HALIGN_LEFT:
685 case TABLE_HALIGN_CENTER:
686 x = (bb[H][0] + bb[H][1] - width + 1) / 2;
688 case TABLE_HALIGN_RIGHT:
689 case TABLE_HALIGN_DECIMAL:
690 x = bb[H][1] - width;
706 mblen = u8_mbtouc (&uc, string, n);
711 w = uc_width (uc, "UTF-8");
726 for (ofs = 0; ofs < n;)
732 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
734 w = uc_width (uc, "UTF-8");
737 if (width + w > x1 - x)
748 if (!a->emphasis || (!bold && !underline))
749 memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
757 /* First figure out how many bytes need to be inserted. */
759 for (ofs = 0; ofs < n; ofs += mblen)
764 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
765 w = uc_width (uc, "UTF-8");
776 /* Then insert them. */
777 out = ascii_reserve (a, y, x, x + width, n_out);
778 for (ofs = 0; ofs < n; ofs += mblen)
783 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
784 w = uc_width (uc, "UTF-8");
790 out = mempcpy (out, string + ofs, mblen);
799 out = mempcpy (out, string + ofs, mblen);
805 add_markers (const char *text, const struct table_cell *cell)
807 struct string s = DS_EMPTY_INITIALIZER;
808 ds_put_cstr (&s, text);
809 for (size_t i = 0; i < cell->n_subscripts; i++)
810 ds_put_format (&s, "%c%s", i ? ',' : '_', cell->subscripts[i]);
811 if (cell->superscript)
812 ds_put_format (&s, "^%s", cell->superscript);
813 for (size_t i = 0; i < cell->n_footnotes; i++)
814 ds_put_format (&s, "[%s]", cell->footnotes[i]->marker);
815 return ds_steal_cstr (&s);
819 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
820 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
821 int *widthp, int *heightp)
826 /* Get the basic textual contents. */
827 const char *plain_text = (cell->options & TAB_MARKUP
828 ? output_get_text_from_markup (cell->text)
831 /* Append footnotes, subscripts, superscript if any. */
833 if (cell->n_footnotes || cell->n_subscripts || cell->superscript)
835 text = add_markers (plain_text, cell);
836 if (plain_text != cell->text)
837 free (CONST_CAST (char *, plain_text));
842 /* Calculate length; if it's zero, then there's nothing to do. */
843 size_t length = strlen (text);
846 if (text != cell->text)
847 free (CONST_CAST (char *, text));
851 char *breaks = xmalloc (length + 1);
852 u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
854 breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
855 ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
858 int bb_width = bb[H][1] - bb[H][0];
859 for (int y = bb[V][0]; y < bb[V][1] && pos < length; y++)
861 const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
862 const char *b = breaks + pos;
863 size_t n = length - pos;
865 size_t last_break_ofs = 0;
866 int last_break_width = 0;
871 for (ofs = 0; ofs < n;)
877 mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
878 if (b[ofs] == UC_BREAK_MANDATORY)
880 else if (b[ofs] == UC_BREAK_POSSIBLE)
882 last_break_ofs = ofs;
883 last_break_width = width;
886 w = uc_width (uc, "UTF-8");
889 if (width + w > bb_width)
891 if (isspace (line[ofs]))
893 else if (last_break_ofs != 0)
895 ofs = last_break_ofs;
896 width = last_break_width;
905 /* Trim any trailing spaces off the end of the text to be drawn. */
906 for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
907 if (!isspace (line[graph_ofs - 1]))
909 width -= ofs - graph_ofs;
912 text_draw (a, cell->style->cell_style.halign, cell->options,
913 cell->style->font_style.bold,
914 cell->style->font_style.underline,
915 bb, clip, y, line, graph_ofs, width);
917 /* If a new-line ended the line, just skip the new-line. Otherwise, skip
918 past any spaces past the end of the line (but not past a new-line). */
919 if (b[ofs] == UC_BREAK_MANDATORY)
922 while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
932 if (text != cell->text)
933 free (CONST_CAST (char *, text));
937 ascii_test_write (struct output_driver *driver,
938 const char *s, int x, int y, bool bold, bool underline)
940 struct ascii_driver *a = ascii_driver_cast (driver);
941 int bb[TABLE_N_AXES][2];
947 struct table_area_style style = {
948 .cell_style.halign = TABLE_HALIGN_LEFT,
949 .font_style.bold = bold,
950 .font_style.underline = underline,
952 struct table_cell cell = {
953 .text = CONST_CAST (char *, s),
957 bb[TABLE_HORZ][0] = x;
958 bb[TABLE_HORZ][1] = a->width;
959 bb[TABLE_VERT][0] = y;
960 bb[TABLE_VERT][1] = INT_MAX;
962 ascii_layout_cell (a, &cell, bb, bb, &width, &height);
966 ascii_test_set_length (struct output_driver *driver, int y, int length)
968 struct ascii_driver *a = ascii_driver_cast (driver);
975 u8_line_set_length (&a->lines[y], length);
979 ascii_test_flush (struct output_driver *driver)
981 struct ascii_driver *a = ascii_driver_cast (driver);
983 for (size_t i = a->allocated_lines; i-- > 0;)
984 if (a->lines[i].width)
986 ascii_output_lines (a, i + 1);
991 static sig_atomic_t terminal_changed = true;
992 static int terminal_width;
994 #if HAVE_DECL_SIGWINCH
996 winch_handler (int signum UNUSED)
998 terminal_changed = true;
1003 get_terminal_width (void)
1005 #if HAVE_DECL_SIGWINCH
1006 static bool setup_signal;
1009 setup_signal = true;
1011 struct sigaction action = { .sa_handler = winch_handler };
1012 sigemptyset (&action.sa_mask);
1013 sigaction (SIGWINCH, &action, NULL);
1017 if (terminal_changed)
1019 terminal_changed = false;
1021 #ifdef HAVE_TERMIOS_H
1023 if (!ioctl (0, TIOCGWINSZ, &ws))
1024 terminal_width = ws.ws_col;
1028 if (getenv ("COLUMNS"))
1029 terminal_width = atoi (getenv ("COLUMNS"));
1032 if (terminal_width <= 0 || terminal_width > 1024)
1033 terminal_width = 79;
1036 return terminal_width;