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"
50 #include "output/cairo.h"
51 #include "output/chart-item-provider.h"
52 #include "output/driver-provider.h"
53 #include "output/message-item.h"
54 #include "output/options.h"
55 #include "output/render.h"
56 #include "output/table-item.h"
57 #include "output/text-item.h"
59 #include "gl/minmax.h"
60 #include "gl/xalloc.h"
64 #define _(msgid) gettext (msgid)
66 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
78 #define N_BOX (ASCII_N_LINES * ASCII_N_LINES \
79 * ASCII_N_LINES * ASCII_N_LINES)
81 static const ucs4_t ascii_box_chars[N_BOX] =
112 static const ucs4_t unicode_box_chars[N_BOX] =
114 0x0020, 0x2575, 0x2551,
115 0x2574, 0x256f, 0x255c,
116 0x2550, 0x255b, 0x255d,
117 0x2577, 0x2502, 0x2551,
118 0x256e, 0x2524, 0x2562,
119 0x2555, 0x2561, 0x2563,
120 0x2551, 0x2551, 0x2551,
121 0x2556, 0x2562, 0x2562,
122 0x2557, 0x2563, 0x2563,
123 0x2576, 0x2570, 0x2559,
124 0x2500, 0x2534, 0x2568,
125 0x2550, 0x2567, 0x2569,
126 0x256d, 0x251c, 0x255f,
127 0x252c, 0x253c, 0x256a,
128 0x2564, 0x256a, 0x256c,
129 0x2553, 0x255f, 0x255f,
130 0x2565, 0x256b, 0x256b,
131 0x2566, 0x256c, 0x256c,
132 0x2550, 0x2558, 0x255a,
133 0x2550, 0x2567, 0x2569,
134 0x2550, 0x2567, 0x2569,
135 0x2552, 0x255e, 0x2560,
136 0x2564, 0x256a, 0x256c,
137 0x2564, 0x256a, 0x256c,
138 0x2554, 0x2560, 0x2560,
139 0x2560, 0x256c, 0x256c,
140 0x2566, 0x256c, 0x256c,
144 ascii_line_from_render_line (int render_line)
148 case RENDER_LINE_NONE:
149 return ASCII_LINE_NONE;
151 case RENDER_LINE_SINGLE:
152 case RENDER_LINE_DASHED:
153 case RENDER_LINE_THICK:
154 case RENDER_LINE_THIN:
155 return ASCII_LINE_SINGLE;
157 case RENDER_LINE_DOUBLE:
158 return ASCII_LINE_DOUBLE;
161 return ASCII_LINE_NONE;
167 make_box_index (int left_, int right_, int top_, int bottom_)
169 bool rtl = render_direction_rtl ();
170 int left = ascii_line_from_render_line (rtl ? right_ : left_);
171 int right = ascii_line_from_render_line (rtl ? left_ : right_);
172 int top = ascii_line_from_render_line (top_);
173 int bottom = ascii_line_from_render_line (bottom_);
176 idx = idx * ASCII_N_LINES + bottom;
177 idx = idx * ASCII_N_LINES + left;
178 idx = idx * ASCII_N_LINES + top;
182 /* ASCII output driver. */
185 struct output_driver driver;
187 /* User parameters. */
188 bool append; /* Append if output file already exists? */
189 bool emphasis; /* Enable bold and underline in output? */
190 char *chart_file_name; /* Name of files used for charts. */
193 /* Colours for charts */
194 struct cell_color fg;
195 struct cell_color bg;
198 /* How the page width is determined: */
200 FIXED_WIDTH, /* Specified by configuration. */
201 VIEW_WIDTH, /* From SET WIDTH. */
202 TERMINAL_WIDTH /* From the terminal's width. */
204 int width; /* Page width. */
206 int min_hbreak; /* Min cell size to break across pages. */
208 const ucs4_t *box; /* Line & box drawing characters. */
210 /* Internal state. */
211 struct file_handle *handle;
212 FILE *file; /* Output file. */
213 bool error; /* Output error? */
214 struct u8_line *lines; /* Page content. */
215 int allocated_lines; /* Number of lines allocated. */
216 int chart_cnt; /* Number of charts so far. */
217 int object_cnt; /* Number of objects so far. */
218 struct render_params params;
221 static const struct output_driver_class ascii_driver_class;
223 static void ascii_submit (struct output_driver *, const struct output_item *);
225 static int get_terminal_width (void);
227 static bool update_page_size (struct ascii_driver *, bool issue_error);
228 static int parse_page_size (struct driver_option *);
230 static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2],
231 enum render_line_style styles[TABLE_N_AXES][2],
232 struct cell_color colors[TABLE_N_AXES][2]);
233 static void ascii_measure_cell_width (void *, const struct table_cell *,
235 static int ascii_measure_cell_height (void *, const struct table_cell *,
237 static void ascii_draw_cell (void *, const struct table_cell *, int color_idx,
238 int bb[TABLE_N_AXES][2],
239 int spill[TABLE_N_AXES][2],
240 int clip[TABLE_N_AXES][2]);
242 static struct ascii_driver *
243 ascii_driver_cast (struct output_driver *driver)
245 assert (driver->class == &ascii_driver_class);
246 return UP_CAST (driver, struct ascii_driver, driver);
249 static struct driver_option *
250 opt (struct output_driver *d, struct string_map *options, const char *key,
251 const char *default_value)
253 return driver_option_get (d, options, key, default_value);
256 /* Return true iff the terminal appears to be an xterm with
257 UTF-8 capabilities */
259 term_is_utf8_xterm (void)
261 const char *term = getenv ("TERM");
262 const char *xterm_locale = getenv ("XTERM_LOCAL");
263 return (term && xterm_locale
264 && !strcmp (term, "xterm")
265 && (strcasestr (xterm_locale, "utf8")
266 || strcasestr (xterm_locale, "utf-8")));
269 static struct output_driver *
270 ascii_create (struct file_handle *fh, enum settings_output_devices device_type,
271 struct string_map *o)
273 enum { BOX_ASCII, BOX_UNICODE } box;
274 struct output_driver *d;
275 struct ascii_driver *a;
277 a = xzalloc (sizeof *a);
279 output_driver_init (&a->driver, &ascii_driver_class, fh_get_file_name (fh), device_type);
280 a->append = parse_boolean (opt (d, o, "append", "false"));
281 a->emphasis = parse_boolean (opt (d, o, "emphasis", "false"));
283 a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", fh_get_file_name (fh)));
287 bool terminal = !strcmp (fh_get_file_name (fh), "-") && isatty (1);
288 a->width = parse_page_size (opt (d, o, "width", "-1"));
289 a->width_mode = (a->width > 0 ? FIXED_WIDTH
290 : terminal ? TERMINAL_WIDTH
292 a->min_hbreak = parse_int (opt (d, o, "min-hbreak", "-1"), -1, INT_MAX);
295 parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &a->bg);
296 parse_color (d, o, "foreground-color", "#000000000000", &a->fg);
299 const char *default_box = (terminal && (!strcmp (locale_charset (), "UTF-8")
300 || term_is_utf8_xterm ())
301 ? "unicode" : "ascii");
302 box = parse_enum (opt (d, o, "box", default_box),
304 "unicode", BOX_UNICODE,
306 a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
311 a->allocated_lines = 0;
315 a->params.draw_line = ascii_draw_line;
316 a->params.measure_cell_width = ascii_measure_cell_width;
317 a->params.measure_cell_height = ascii_measure_cell_height;
318 a->params.adjust_break = NULL;
319 a->params.draw_cell = ascii_draw_cell;
321 a->params.size[H] = a->width;
322 a->params.size[V] = INT_MAX;
323 a->params.font_size[H] = 1;
324 a->params.font_size[V] = 1;
325 for (int i = 0; i < RENDER_N_LINES; i++)
327 int width = i == RENDER_LINE_NONE ? 0 : 1;
328 a->params.line_widths[H][i] = width;
329 a->params.line_widths[V][i] = width;
331 a->params.supports_margins = false;
332 a->params.rtl = render_direction_rtl ();
334 if (!update_page_size (a, true))
337 a->file = fn_open (a->handle, a->append ? "a" : "w");
340 msg_error (errno, _("ascii: opening output file `%s'"),
341 fh_get_file_name (a->handle));
348 output_driver_destroy (d);
353 parse_page_size (struct driver_option *option)
355 int dim = atol (option->default_value);
357 if (option->value != NULL)
359 if (!strcmp (option->value, "auto"))
367 value = strtol (option->value, &tail, 0);
368 if (value >= 1 && errno != ERANGE && *tail == '\0')
371 msg (MW, _("%s: %s must be positive integer or `auto'"),
372 option->driver_name, option->name);
376 driver_option_destroy (option);
381 /* Re-calculates the page width based on settings, margins, and, if "auto" is
382 set, the size of the user's terminal window or GUI output window. */
384 update_page_size (struct ascii_driver *a, bool issue_error)
386 enum { MIN_WIDTH = 6 };
388 int want_width = (a->width_mode == VIEW_WIDTH ? settings_get_viewwidth ()
389 : a->width_mode == TERMINAL_WIDTH ? get_terminal_width ()
391 bool ok = want_width >= MIN_WIDTH;
392 if (!ok && issue_error)
393 msg (ME, _("ascii: page must be at least %d characters wide, but "
394 "as configured is only %d characters"),
395 MIN_WIDTH, want_width);
397 a->width = ok ? want_width : MIN_WIDTH;
398 a->params.size[H] = a->width;
399 a->params.min_break[H] = a->min_hbreak >= 0 ? a->min_hbreak : a->width / 2;
405 ascii_destroy (struct output_driver *driver)
407 struct ascii_driver *a = ascii_driver_cast (driver);
411 fn_close (a->handle, a->file);
412 fh_unref (a->handle);
413 free (a->chart_file_name);
414 for (i = 0; i < a->allocated_lines; i++)
415 u8_line_destroy (&a->lines[i]);
421 ascii_flush (struct output_driver *driver)
423 struct ascii_driver *a = ascii_driver_cast (driver);
429 ascii_output_lines (struct ascii_driver *a, size_t n_lines)
431 for (size_t y = 0; y < n_lines; y++)
433 if (y < a->allocated_lines)
435 struct u8_line *line = &a->lines[y];
437 while (ds_chomp_byte (&line->s, ' '))
439 fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
440 u8_line_clear (&a->lines[y]);
442 putc ('\n', a->file);
447 ascii_output_table_item (struct ascii_driver *a,
448 const struct table_item *table_item)
450 struct render_pager *p;
452 update_page_size (a, false);
455 putc ('\n', a->file);
457 p = render_pager_create (&a->params, table_item);
458 for (int i = 0; render_pager_has_next (p); i++)
461 putc ('\n', a->file);
462 ascii_output_lines (a, render_pager_draw_next (p, INT_MAX));
464 render_pager_destroy (p);
468 ascii_output_table_item_unref (struct ascii_driver *a,
469 struct table_item *table_item)
471 ascii_output_table_item (a, table_item);
472 table_item_unref (table_item);
476 ascii_output_text (struct ascii_driver *a, const char *text)
478 ascii_output_table_item_unref (
479 a, table_item_create (table_from_string (text), NULL, NULL));
483 ascii_submit (struct output_driver *driver,
484 const struct output_item *output_item)
486 struct ascii_driver *a = ascii_driver_cast (driver);
491 if (is_table_item (output_item))
492 ascii_output_table_item (a, to_table_item (output_item));
494 else if (is_chart_item (output_item) && a->chart_file_name != NULL)
496 struct chart_item *chart_item = to_chart_item (output_item);
499 file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
503 if (file_name != NULL)
505 struct text_item *text_item;
507 text_item = text_item_create_format (
508 TEXT_ITEM_LOG, _("See %s for a chart."), file_name);
510 ascii_submit (driver, &text_item->output_item);
511 text_item_unref (text_item);
515 #endif /* HAVE_CAIRO */
516 else if (is_text_item (output_item))
518 const struct text_item *text_item = to_text_item (output_item);
519 enum text_item_type type = text_item_get_type (text_item);
521 if (type != TEXT_ITEM_PAGE_TITLE && type != TEXT_ITEM_EJECT_PAGE)
522 ascii_output_table_item_unref (
523 a, text_item_to_table_item (text_item_ref (text_item)));
525 else if (is_message_item (output_item))
527 const struct message_item *message_item = to_message_item (output_item);
528 char *s = msg_to_string (message_item_get_msg (message_item));
529 ascii_output_text (a, s);
534 const struct output_driver_factory txt_driver_factory =
535 { "txt", "-", ascii_create };
536 const struct output_driver_factory list_driver_factory =
537 { "list", "-", ascii_create };
539 static const struct output_driver_class ascii_driver_class =
547 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
549 static void ascii_layout_cell (struct ascii_driver *,
550 const struct table_cell *,
551 int bb[TABLE_N_AXES][2],
552 int clip[TABLE_N_AXES][2],
553 int *width, int *height);
556 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
557 enum render_line_style styles[TABLE_N_AXES][2],
558 struct cell_color colors[TABLE_N_AXES][2] UNUSED)
560 struct ascii_driver *a = a_;
567 /* Clip to the page. */
568 x0 = MAX (bb[H][0], 0);
569 y0 = MAX (bb[V][0], 0);
570 x1 = MIN (bb[H][1], a->width);
572 if (x1 <= 0 || y1 <= 0 || x0 >= a->width)
576 uc = a->box[make_box_index (styles[V][0], styles[V][1],
577 styles[H][0], styles[H][1])];
578 mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
579 for (y = y0; y < y1; y++)
581 char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
582 for (x = x0; x < x1; x++)
584 memcpy (p, mbchar, mblen);
591 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
592 int *min_width, int *max_width)
594 struct ascii_driver *a = a_;
595 int bb[TABLE_N_AXES][2];
596 int clip[TABLE_N_AXES][2];
603 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
604 ascii_layout_cell (a, cell, bb, clip, max_width, &h);
606 if (cell->n_footnotes || strchr (cell->text, ' ')
607 || cell->n_subscripts || cell->superscript)
610 ascii_layout_cell (a, cell, bb, clip, min_width, &h);
613 *min_width = *max_width;
617 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
619 struct ascii_driver *a = a_;
620 int bb[TABLE_N_AXES][2];
621 int clip[TABLE_N_AXES][2];
628 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
629 ascii_layout_cell (a, cell, bb, clip, &w, &h);
634 ascii_draw_cell (void *a_, const struct table_cell *cell, int color_idx UNUSED,
635 int bb[TABLE_N_AXES][2],
636 int spill[TABLE_N_AXES][2] UNUSED,
637 int clip[TABLE_N_AXES][2])
639 struct ascii_driver *a = a_;
642 ascii_layout_cell (a, cell, bb, clip, &w, &h);
646 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
648 if (y >= a->allocated_lines)
650 size_t new_alloc = MAX (25, a->allocated_lines);
651 while (new_alloc <= y)
652 new_alloc = xtimes (new_alloc, 2);
653 a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
654 for (size_t i = a->allocated_lines; i < new_alloc; i++)
655 u8_line_init (&a->lines[i]);
656 a->allocated_lines = new_alloc;
658 return u8_line_reserve (&a->lines[y], x0, x1, n);
662 text_draw (struct ascii_driver *a, enum table_halign halign, int options,
663 bool bold, bool underline,
664 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
665 int y, const uint8_t *string, int n, size_t width)
667 int x0 = MAX (0, clip[H][0]);
668 int y0 = MAX (0, clip[V][0]);
669 int x1 = MIN (a->width, clip[H][1]);
673 if (y < y0 || y >= y1)
676 switch (table_halign_interpret (halign, options & TAB_NUMERIC))
678 case TABLE_HALIGN_LEFT:
681 case TABLE_HALIGN_CENTER:
682 x = (bb[H][0] + bb[H][1] - width + 1) / 2;
684 case TABLE_HALIGN_RIGHT:
685 case TABLE_HALIGN_DECIMAL:
686 x = bb[H][1] - width;
702 mblen = u8_mbtouc (&uc, string, n);
707 w = uc_width (uc, "UTF-8");
722 for (ofs = 0; ofs < n;)
728 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
730 w = uc_width (uc, "UTF-8");
733 if (width + w > x1 - x)
744 if (!a->emphasis || (!bold && !underline))
745 memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
753 /* First figure out how many bytes need to be inserted. */
755 for (ofs = 0; ofs < n; ofs += mblen)
760 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
761 w = uc_width (uc, "UTF-8");
772 /* Then insert them. */
773 out = ascii_reserve (a, y, x, x + width, n_out);
774 for (ofs = 0; ofs < n; ofs += mblen)
779 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
780 w = uc_width (uc, "UTF-8");
786 out = mempcpy (out, string + ofs, mblen);
795 out = mempcpy (out, string + ofs, mblen);
801 add_markers (const char *text, const struct table_cell *cell)
803 struct string s = DS_EMPTY_INITIALIZER;
804 ds_put_cstr (&s, text);
805 for (size_t i = 0; i < cell->n_subscripts; i++)
806 ds_put_format (&s, "%c%s", i ? ',' : '_', cell->subscripts[i]);
807 if (cell->superscript)
808 ds_put_format (&s, "^%s", cell->superscript);
809 for (size_t i = 0; i < cell->n_footnotes; i++)
810 ds_put_format (&s, "[%s]", cell->footnotes[i]->marker);
811 return ds_steal_cstr (&s);
815 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
816 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
817 int *widthp, int *heightp)
822 /* Get the basic textual contents. */
823 const char *plain_text = (cell->options & TAB_MARKUP
824 ? output_get_text_from_markup (cell->text)
827 /* Append footnotes, subscripts, superscript if any. */
829 if (cell->n_footnotes || cell->n_subscripts || cell->superscript)
831 text = add_markers (plain_text, cell);
832 if (plain_text != cell->text)
833 free (CONST_CAST (char *, plain_text));
838 /* Calculate length; if it's zero, then there's nothing to do. */
839 size_t length = strlen (text);
842 if (text != cell->text)
843 free (CONST_CAST (char *, text));
847 char *breaks = xmalloc (length + 1);
848 u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
850 breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
851 ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
854 int bb_width = bb[H][1] - bb[H][0];
855 for (int y = bb[V][0]; y < bb[V][1] && pos < length; y++)
857 const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
858 const char *b = breaks + pos;
859 size_t n = length - pos;
861 size_t last_break_ofs = 0;
862 int last_break_width = 0;
867 for (ofs = 0; ofs < n;)
873 mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
874 if (b[ofs] == UC_BREAK_MANDATORY)
876 else if (b[ofs] == UC_BREAK_POSSIBLE)
878 last_break_ofs = ofs;
879 last_break_width = width;
882 w = uc_width (uc, "UTF-8");
885 if (width + w > bb_width)
887 if (isspace (line[ofs]))
889 else if (last_break_ofs != 0)
891 ofs = last_break_ofs;
892 width = last_break_width;
901 /* Trim any trailing spaces off the end of the text to be drawn. */
902 for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
903 if (!isspace (line[graph_ofs - 1]))
905 width -= ofs - graph_ofs;
908 text_draw (a, cell->style->cell_style.halign, cell->options,
909 cell->style->font_style.bold,
910 cell->style->font_style.underline,
911 bb, clip, y, line, graph_ofs, width);
913 /* If a new-line ended the line, just skip the new-line. Otherwise, skip
914 past any spaces past the end of the line (but not past a new-line). */
915 if (b[ofs] == UC_BREAK_MANDATORY)
918 while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
928 if (text != cell->text)
929 free (CONST_CAST (char *, text));
933 ascii_test_write (struct output_driver *driver,
934 const char *s, int x, int y, bool bold, bool underline)
936 struct ascii_driver *a = ascii_driver_cast (driver);
937 int bb[TABLE_N_AXES][2];
943 struct area_style style = {
944 .cell_style.halign = TABLE_HALIGN_LEFT,
945 .font_style.bold = bold,
946 .font_style.underline = underline,
948 struct table_cell cell = {
949 .text = CONST_CAST (char *, s),
953 bb[TABLE_HORZ][0] = x;
954 bb[TABLE_HORZ][1] = a->width;
955 bb[TABLE_VERT][0] = y;
956 bb[TABLE_VERT][1] = INT_MAX;
958 ascii_layout_cell (a, &cell, bb, bb, &width, &height);
962 ascii_test_set_length (struct output_driver *driver, int y, int length)
964 struct ascii_driver *a = ascii_driver_cast (driver);
971 u8_line_set_length (&a->lines[y], length);
975 ascii_test_flush (struct output_driver *driver)
977 struct ascii_driver *a = ascii_driver_cast (driver);
979 for (size_t i = a->allocated_lines; i-- > 0;)
980 if (a->lines[i].width)
982 ascii_output_lines (a, i + 1);
987 static sig_atomic_t terminal_changed = true;
988 static int terminal_width;
990 #if HAVE_DECL_SIGWINCH
992 winch_handler (int signum UNUSED)
994 terminal_changed = true;
999 get_terminal_width (void)
1001 #if HAVE_DECL_SIGWINCH
1002 static bool setup_signal;
1005 setup_signal = true;
1007 struct sigaction action = { .sa_handler = winch_handler };
1008 sigemptyset (&action.sa_mask);
1009 sigaction (SIGWINCH, &action, NULL);
1013 if (terminal_changed)
1015 terminal_changed = false;
1017 #ifdef HAVE_TERMIOS_H
1019 if (!ioctl (0, TIOCGWINSZ, &ws))
1020 terminal_width = ws.ws_col;
1024 if (getenv ("COLUMNS"))
1025 terminal_width = atoi (getenv ("COLUMNS"));
1028 if (terminal_width <= 0 || terminal_width > 1024)
1029 terminal_width = 79;
1032 return terminal_width;