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_format (
510 TEXT_ITEM_LOG, _("See %s for a chart."), file_name);
512 ascii_submit (driver, &text_item->output_item);
513 text_item_unref (text_item);
517 #endif /* HAVE_CAIRO */
518 else if (is_text_item (output_item))
520 const struct text_item *text_item = to_text_item (output_item);
521 enum text_item_type type = text_item_get_type (text_item);
523 if (type != TEXT_ITEM_PAGE_TITLE)
524 ascii_output_table_item_unref (
525 a, text_item_to_table_item (text_item_ref (text_item)));
527 else if (is_message_item (output_item))
528 ascii_output_table_item_unref (
529 a, text_item_to_table_item (
530 message_item_to_text_item (
532 output_item_ref (output_item)))));
535 const struct output_driver_factory txt_driver_factory =
536 { "txt", "-", ascii_create };
537 const struct output_driver_factory list_driver_factory =
538 { "list", "-", ascii_create };
540 static const struct output_driver_class ascii_driver_class =
548 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
550 static void ascii_layout_cell (struct ascii_driver *,
551 const struct table_cell *,
552 int bb[TABLE_N_AXES][2],
553 int clip[TABLE_N_AXES][2],
554 int *width, int *height);
557 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
558 enum render_line_style styles[TABLE_N_AXES][2],
559 struct cell_color colors[TABLE_N_AXES][2] UNUSED)
561 struct ascii_driver *a = a_;
568 /* Clip to the page. */
569 x0 = MAX (bb[H][0], 0);
570 y0 = MAX (bb[V][0], 0);
571 x1 = MIN (bb[H][1], a->width);
573 if (x1 <= 0 || y1 <= 0 || x0 >= a->width)
577 uc = a->box[make_box_index (styles[V][0], styles[V][1],
578 styles[H][0], styles[H][1])];
579 mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
580 for (y = y0; y < y1; y++)
582 char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
583 for (x = x0; x < x1; x++)
585 memcpy (p, mbchar, mblen);
592 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
593 int *min_width, int *max_width)
595 struct ascii_driver *a = a_;
596 int bb[TABLE_N_AXES][2];
597 int clip[TABLE_N_AXES][2];
604 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
605 ascii_layout_cell (a, cell, bb, clip, max_width, &h);
607 if (cell->n_footnotes || strchr (cell->text, ' ')
608 || cell->n_subscripts || cell->superscript)
611 ascii_layout_cell (a, cell, bb, clip, min_width, &h);
614 *min_width = *max_width;
618 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
620 struct ascii_driver *a = a_;
621 int bb[TABLE_N_AXES][2];
622 int clip[TABLE_N_AXES][2];
629 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
630 ascii_layout_cell (a, cell, bb, clip, &w, &h);
635 ascii_draw_cell (void *a_, const struct table_cell *cell, int color_idx UNUSED,
636 int bb[TABLE_N_AXES][2], int valign_offset,
637 int spill[TABLE_N_AXES][2] UNUSED,
638 int clip[TABLE_N_AXES][2])
640 struct ascii_driver *a = a_;
643 bb[V][0] += valign_offset;
644 ascii_layout_cell (a, cell, bb, clip, &w, &h);
648 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
650 if (y >= a->allocated_lines)
652 size_t new_alloc = MAX (25, a->allocated_lines);
653 while (new_alloc <= y)
654 new_alloc = xtimes (new_alloc, 2);
655 a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
656 for (size_t i = a->allocated_lines; i < new_alloc; i++)
657 u8_line_init (&a->lines[i]);
658 a->allocated_lines = new_alloc;
660 return u8_line_reserve (&a->lines[y], x0, x1, n);
664 text_draw (struct ascii_driver *a, enum table_halign halign, int options,
665 bool bold, bool underline,
666 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
667 int y, const uint8_t *string, int n, size_t width)
669 int x0 = MAX (0, clip[H][0]);
670 int y0 = MAX (0, clip[V][0]);
671 int x1 = MIN (a->width, clip[H][1]);
675 if (y < y0 || y >= y1)
678 switch (table_halign_interpret (halign, options & TAB_NUMERIC))
680 case TABLE_HALIGN_LEFT:
683 case TABLE_HALIGN_CENTER:
684 x = (bb[H][0] + bb[H][1] - width + 1) / 2;
686 case TABLE_HALIGN_RIGHT:
687 case TABLE_HALIGN_DECIMAL:
688 x = bb[H][1] - width;
704 mblen = u8_mbtouc (&uc, string, n);
709 w = uc_width (uc, "UTF-8");
724 for (ofs = 0; ofs < n;)
730 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
732 w = uc_width (uc, "UTF-8");
735 if (width + w > x1 - x)
746 if (!a->emphasis || (!bold && !underline))
747 memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
755 /* First figure out how many bytes need to be inserted. */
757 for (ofs = 0; ofs < n; ofs += mblen)
762 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
763 w = uc_width (uc, "UTF-8");
774 /* Then insert them. */
775 out = ascii_reserve (a, y, x, x + width, n_out);
776 for (ofs = 0; ofs < n; ofs += mblen)
781 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
782 w = uc_width (uc, "UTF-8");
788 out = mempcpy (out, string + ofs, mblen);
797 out = mempcpy (out, string + ofs, mblen);
803 add_markers (const char *text, const struct table_cell *cell)
805 struct string s = DS_EMPTY_INITIALIZER;
806 ds_put_cstr (&s, text);
807 for (size_t i = 0; i < cell->n_subscripts; i++)
808 ds_put_format (&s, "%c%s", i ? ',' : '_', cell->subscripts[i]);
809 if (cell->superscript)
810 ds_put_format (&s, "^%s", cell->superscript);
811 for (size_t i = 0; i < cell->n_footnotes; i++)
812 ds_put_format (&s, "[%s]", cell->footnotes[i]->marker);
813 return ds_steal_cstr (&s);
817 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
818 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
819 int *widthp, int *heightp)
824 /* Get the basic textual contents. */
825 const char *plain_text = (cell->options & TAB_MARKUP
826 ? output_get_text_from_markup (cell->text)
829 /* Append footnotes, subscripts, superscript if any. */
831 if (cell->n_footnotes || cell->n_subscripts || cell->superscript)
833 text = add_markers (plain_text, cell);
834 if (plain_text != cell->text)
835 free (CONST_CAST (char *, plain_text));
840 /* Calculate length; if it's zero, then there's nothing to do. */
841 size_t length = strlen (text);
844 if (text != cell->text)
845 free (CONST_CAST (char *, text));
849 char *breaks = xmalloc (length + 1);
850 u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
852 breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
853 ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
856 int bb_width = bb[H][1] - bb[H][0];
857 for (int y = bb[V][0]; y < bb[V][1] && pos < length; y++)
859 const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
860 const char *b = breaks + pos;
861 size_t n = length - pos;
863 size_t last_break_ofs = 0;
864 int last_break_width = 0;
869 for (ofs = 0; ofs < n;)
875 mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
876 if (b[ofs] == UC_BREAK_MANDATORY)
878 else if (b[ofs] == UC_BREAK_POSSIBLE)
880 last_break_ofs = ofs;
881 last_break_width = width;
884 w = uc_width (uc, "UTF-8");
887 if (width + w > bb_width)
889 if (isspace (line[ofs]))
891 else if (last_break_ofs != 0)
893 ofs = last_break_ofs;
894 width = last_break_width;
903 /* Trim any trailing spaces off the end of the text to be drawn. */
904 for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
905 if (!isspace (line[graph_ofs - 1]))
907 width -= ofs - graph_ofs;
910 text_draw (a, cell->style->cell_style.halign, cell->options,
911 cell->style->font_style.bold,
912 cell->style->font_style.underline,
913 bb, clip, y, line, graph_ofs, width);
915 /* If a new-line ended the line, just skip the new-line. Otherwise, skip
916 past any spaces past the end of the line (but not past a new-line). */
917 if (b[ofs] == UC_BREAK_MANDATORY)
920 while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
930 if (text != cell->text)
931 free (CONST_CAST (char *, text));
935 ascii_test_write (struct output_driver *driver,
936 const char *s, int x, int y, bool bold, bool underline)
938 struct ascii_driver *a = ascii_driver_cast (driver);
939 int bb[TABLE_N_AXES][2];
945 struct table_area_style style = {
946 .cell_style.halign = TABLE_HALIGN_LEFT,
947 .font_style.bold = bold,
948 .font_style.underline = underline,
950 struct table_cell cell = {
951 .text = CONST_CAST (char *, s),
955 bb[TABLE_HORZ][0] = x;
956 bb[TABLE_HORZ][1] = a->width;
957 bb[TABLE_VERT][0] = y;
958 bb[TABLE_VERT][1] = INT_MAX;
960 ascii_layout_cell (a, &cell, bb, bb, &width, &height);
964 ascii_test_set_length (struct output_driver *driver, int y, int length)
966 struct ascii_driver *a = ascii_driver_cast (driver);
973 u8_line_set_length (&a->lines[y], length);
977 ascii_test_flush (struct output_driver *driver)
979 struct ascii_driver *a = ascii_driver_cast (driver);
981 for (size_t i = a->allocated_lines; i-- > 0;)
982 if (a->lines[i].width)
984 ascii_output_lines (a, i + 1);
989 static sig_atomic_t terminal_changed = true;
990 static int terminal_width;
992 #if HAVE_DECL_SIGWINCH
994 winch_handler (int signum UNUSED)
996 terminal_changed = true;
1001 get_terminal_width (void)
1003 #if HAVE_DECL_SIGWINCH
1004 static bool setup_signal;
1007 setup_signal = true;
1009 struct sigaction action = { .sa_handler = winch_handler };
1010 sigemptyset (&action.sa_mask);
1011 sigaction (SIGWINCH, &action, NULL);
1015 if (terminal_changed)
1017 terminal_changed = false;
1019 #ifdef HAVE_TERMIOS_H
1021 if (!ioctl (0, TIOCGWINSZ, &ws))
1022 terminal_width = ws.ws_col;
1026 if (getenv ("COLUMNS"))
1027 terminal_width = atoi (getenv ("COLUMNS"));
1030 if (terminal_width <= 0 || terminal_width > 1024)
1031 terminal_width = 79;
1034 return terminal_width;