1 /* lister.c -- Listing data in various formats
2 Copyright (C) 1998-1999 Free Software Foundation, Inc.
3
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 2, or (at your option)
7 any later version.
8
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.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software Foundation,
16 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
17
18 /*
19
20 This file still needs quite a lot of work. In particular, its
21 interaction with tterm, and the fact that the user might want to use
22 another stream than the one given at initialization.
23
24 */
25
26
27 #ifdef HAVE_CONFIG_H
28 # include <config.h>
29 #endif
30
31 #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 8)
32 # define PARAM_UNUSED __attribute__ ((unused))
33 #else
34 # define PARAM_UNUSED
35 #endif
36
37 #include <stdio.h>
38
39 #if HAVE_STRING_H
40 # if !STDC_HEADERS && HAVE_MEMORY_H
41 # include <memory.h>
42 # endif
43 # include <string.h>
44 #else
45 # if HAVE_STRINGS_H
46 # include <strings.h>
47 # endif
48 #endif
49 #if HAVE_STDLIB_H
50 # include <stdlib.h>
51 #endif
52
53 #if HAVE_SYS_TYPES_H
54 # include <sys/types.h>
55 #endif
56
57 #if HAVE_INTTYPES_H
58 # include <inttypes.h>
59 #endif
60
61 #if !HAVE_DECL_FPUTS
62 extern void fputs ();
63 #endif
64
65 #if !HAVE_DECL_STRLEN
66 extern int strlen ();
67 #endif
68
69 #include "xalloc.h"
70 #include "tterm.h"
71 #include "lister.h"
72
73 /* Information about filling a column. */
74 struct world
75 {
76 size_t *widths; /* Array of the widths of the columns */
77 size_t width; /* Sum of the widths of the columns */
78 size_t valid_len; /* This world respects the width of the line */
79 };
80
81 /* All the needed information for multicolumn listings */
82
83 struct multicol
84 {
85 /* Number of spaces between columns. */
86 size_t between;
87
88 /* Justification of the items. */
89 size_t justify;
90
91 /* Array with information about column filledness. Indexed by the
92 number of columns minus one (used for hypothetical reasonning.
93 Only one is actually selected to print.) */
94 struct world *worlds;
95 };
96
97 /* List with separators. See lister_print_separated for more
98 information. */
99
100 struct separated
101 {
102 /* String between items. */
103 const char *separator;
104
105 /* Spaces after the separator. */
106 size_t between;
107
108 /* String after the last item. */
109 const char *final;
110 };
111
112
113 struct lister
114 {
115 /* The tinyterm under which displays are done. */
116 struct tterm *tterm;
117
118 /* Default output stream. */
119 FILE *stream;
120
121 /* Default width function to be applied on items. */
122 lister_width_t width_fn;
123
124 /* Default print function to be applied on items. */
125 lister_print_t print_fn;
126
127 /* Number of spaces to leave at the beginning of the line. */
128 size_t before;
129
130 /* Number of spaces to leave before the end of the line. */
131 size_t after;
132
133 /* All the needed information for multicolumn listings. */
134 struct multicol multicol;
135
136 /* All the needed information for separated listings. */
137 struct separated separated;
138 };
139
140 /* Default setting */
141 static struct lister lister_default =
142 {
143 /* tterm. Lister's default tterm, is the default tterm */
144 NULL,
145
146 /* stream. We cannot initialize statically to stdout, EGCS won't
147 let us do. */
148 NULL,
149
150 /* width_fn, print_fn */
151 (lister_width_t) strlen, (lister_print_t) fputs,
152
153 /* Before, after */
154 0, 0,
155
156 /* multicol structure. */
157 {
158 /* between, justify, worlds */
159 2, lister_left, NULL
160 },
161
162 /* separated structure. */
163 {
164 /* separator, between, final. */
165 ",", 2, "."
166 }
167 };
168
169 /* Maximum number of columns ever possible for this display. */
170 static size_t max_idx;
171
172 /* The minimum width of a colum is 1 character for the name. */
173 #define MIN_COLUMN_WIDTH 1
174
175
176 /* Initialize the LISTER for the STREAM. */
177
178 void
lister_initialize(struct lister * lister,FILE * stream)179 lister_initialize (struct lister *lister, FILE *stream)
180 {
181 struct lister * l = lister ? lister : &lister_default;
182 tterm_initialize (NULL, stream);
183 l->stream = stream;
184 }
185
186 /* Assuming cursor is at position FROM, indent up to position TO.
187 Use a TAB character instead of two or more spaces whenever possible. */
188
189 #define INDENT(_from_,_to_) \
190 do { \
191 size_t from = _from_; \
192 size_t to = _to_; \
193 while (from < to) \
194 { \
195 if (tabsize > 0 \
196 && to / tabsize > (from + 1) / tabsize) \
197 { \
198 putc ('\t', stream); \
199 from += tabsize - from % tabsize; \
200 } \
201 else \
202 { \
203 putc (' ', stream); \
204 from++; \
205 } \
206 } \
207 } while (0)
208
209 /* FIXME: comment */
210 static void
init_worlds(struct lister * l)211 init_worlds (struct lister * l)
212 {
213 size_t i;
214 size_t line_width = tterm_width (l->tterm);
215
216 /* Prepare the structure to the maximum number of columns, hence do
217 not depend on BEFORE, BETWEEN and AFTER which can change while
218 running, but only on LINE_WIDTH which is a constant. */
219 if (l->multicol.worlds == NULL)
220 {
221 l->multicol.worlds = XMALLOC (struct world, line_width);
222 for (i = 0; i < line_width; ++i)
223 l->multicol.worlds[i].widths = XMALLOC (size_t, i + 1);
224 }
225
226 max_idx = ((line_width
227 - l->before - l->after - l->multicol.between)
228 / (MIN_COLUMN_WIDTH + l->multicol.between));
229 if (max_idx == 0)
230 max_idx = 1;
231
232 for (i = 0; i < max_idx; ++i)
233 {
234 size_t j;
235
236 l->multicol.worlds[i].valid_len = 1;
237 l->multicol.worlds[i].width = (i + 1) * MIN_COLUMN_WIDTH;
238
239 for (j = 0; j <= i; ++j)
240 l->multicol.worlds[i].widths[j] = MIN_COLUMN_WIDTH;
241 }
242 }
243
244 /* Set the tiny term of LISTER to TTERM. Returns the previous value. */
245
246 struct tterm *
lister_tterm_set(struct lister * lister,struct tterm * tterm)247 lister_tterm_set (struct lister * lister, struct tterm *tterm)
248 {
249 struct lister * l = lister ? lister : &lister_default;
250 struct tterm *old = l->tterm;
251 l->tterm = tterm;
252 return old;
253 }
254
255 /* Set the width of the white prefix in LISTER to SIZE. Returns the
256 previous value. */
257
258 size_t
lister_before_set(struct lister * lister,size_t size)259 lister_before_set (struct lister * lister, size_t size)
260 {
261 struct lister * l = lister ? lister : &lister_default;
262 size_t old = l->before;
263 l->before = size;
264 return old;
265 }
266
267 /* Set the width of the white suffix in LISTER to SIZE. Returns the
268 previous value. */
269
270 size_t
lister_after_set(struct lister * lister,size_t size)271 lister_after_set (struct lister * lister, size_t size)
272 {
273 struct lister * l = lister ? lister : &lister_default;
274 size_t old = l->after;
275 l->after = size;
276 return old;
277 }
278
279
280 static size_t
lister_vertical_format(struct lister * l,void ** items,size_t item_number,lister_width_t item_width_fn,struct world ** line_fmt)281 lister_vertical_format (struct lister * l,
282 void **items, size_t item_number,
283 lister_width_t item_width_fn,
284 struct world ** line_fmt)
285 {
286 size_t max_cols; /* Maximum number of columns usable */
287 size_t cols;
288 size_t itemno;
289 size_t item_width;
290 struct multicol * m = &l->multicol;
291 size_t available_width = tterm_width (l->tterm) - l->after - l->before;
292 struct world * worlds = m->worlds;
293
294 /* Normally the maximum number of columns is determined by the
295 screen width. But if few files are available this might limit it
296 as well. */
297 max_cols = max_idx > item_number ? item_number : max_idx;
298
299 /* Compute the maximum number of possible columns. */
300 for (itemno = 0; itemno < item_number; ++itemno)
301 {
302 size_t i;
303
304 item_width = item_width_fn (items[itemno]);
305
306 for (i = 0; i < max_cols; ++i)
307 {
308 if (worlds[i].valid_len)
309 {
310 size_t effective_width = available_width - i * m->between;
311 size_t idx = itemno / ((item_number + i) / (i + 1));
312 size_t real_width = item_width;
313
314 if (real_width > worlds[i].widths[idx])
315 {
316 worlds[i].width += (real_width - worlds[i].widths[idx]);
317 worlds[i].widths[idx] = real_width;
318 worlds[i].valid_len = worlds[i].width <= effective_width;
319 }
320 }
321 }
322 }
323
324 /* Find maximum allowed columns. */
325 for (cols = max_cols; cols > 1; --cols)
326 {
327 if (worlds[cols - 1].valid_len)
328 break;
329 }
330
331 *line_fmt = &worlds[cols - 1];
332 return cols;
333 }
334
335 void
lister_fprint_vertical(struct lister * lister,PARAM_UNUSED FILE * unused,void ** items,size_t item_number,lister_width_t item_width_fn,lister_print_t item_print_fn)336 lister_fprint_vertical (struct lister * lister, PARAM_UNUSED FILE *unused,
337 void **items, size_t item_number,
338 lister_width_t item_width_fn,
339 lister_print_t item_print_fn)
340 {
341 struct world *line_fmt;
342 size_t itemno; /* Index size_to files. */
343 size_t row; /* Current row. */
344 size_t col_width; /* Width of longest file name + frills. */
345 size_t item_width; /* Width of each file name + frills. */
346 size_t pos; /* Current character column. */
347 size_t cols; /* Number of files across. */
348 size_t rows; /* Maximum number of files down. */
349 struct lister * l = lister ? lister : &lister_default;
350 struct multicol * m = &l->multicol;
351 size_t tabsize = tterm_tabsize (l->tterm);
352 FILE *stream = l->stream;
353
354 init_worlds (l);
355
356 /* If not known, compute the actually number of elements.
357 FIXME: there are probably more intelligent scheme to get item_number
358 on the fly, but currently it is need in lister_vertical_format
359 real soon, so just compute it right now. */
360 if (item_number == (size_t) -1)
361 {
362 for (item_number = 0 ; items[item_number] ; item_number++)
363 /* nothing */ ;
364 }
365
366 cols = lister_vertical_format (l, items, item_number,
367 item_width_fn, &line_fmt);
368
369 /* Calculate the number of rows that will be in each column except possibly
370 for a short column on the right. */
371 rows = item_number / cols + (item_number % cols != 0);
372
373 for (row = 0; row < rows; row++)
374 {
375 size_t col = 0;
376 size_t nextpos, nextcolpos;
377 itemno = row;
378
379 /* Print the next row. */
380 pos = 0;
381 nextcolpos = nextpos = l->before;
382 while (1)
383 {
384 col_width = line_fmt->widths[col++];
385 item_width = (*item_width_fn) (items[itemno]);
386 nextpos += (col_width - item_width) * m->justify /2 ;
387 nextcolpos += col_width + m->between;
388
389 INDENT (pos, nextpos);
390 (*item_print_fn) (items[itemno], stream);
391
392 itemno += rows;
393 if (itemno >= item_number)
394 break;
395
396 pos = nextpos + item_width;
397 nextpos = nextcolpos;
398 }
399 putc ('\n', stream);
400 }
401 }
402
403 /* Same as LISTER_FPRINT_VERTICAL, but using LISTER's default value
404 for width_fn and print_fn. */
405
406 void
lister_print_vertical(struct lister * lister,void ** items,size_t item_number)407 lister_print_vertical (struct lister *lister,
408 void **items, size_t item_number)
409 {
410 struct lister *l = lister ? lister : &lister_default;
411 lister_fprint_vertical (lister, NULL,
412 items, item_number,
413 l->width_fn, l->print_fn);
414 }
415
416
417 /* Listing in horizontal format. Columns are built to minimize the
418 number of needed rows. For instance:
419
420 +---------------------------+
421 | a bbbbb c dddddddd |
422 | ee ff g h |
423 | i j k lllllllllll |
424 +---------------------------+
425
426 */
427
428 static size_t
lister_horizontal_format(struct lister * l,void ** items,size_t item_number,lister_width_t item_width_fn,struct world ** line_fmt)429 lister_horizontal_format (struct lister * l,
430 void **items, size_t item_number,
431 lister_width_t item_width_fn,
432 struct world **line_fmt)
433 {
434 size_t max_cols; /* Maximum number of columns usable */
435 size_t cols;
436 size_t itemno;
437 size_t item_width;
438 struct multicol * m = &l->multicol;
439 size_t available_width = tterm_width (l->tterm) - l->after - l->before;
440 struct world * worlds = m->worlds;
441
442 /* Normally the maximum number of columns is determined by the
443 screen width. But if few files are available this might limit it
444 as well. */
445 max_cols = max_idx > item_number ? item_number : max_idx;
446
447 /* Compute the maximum file name width. */
448 for (itemno = 0; itemno < item_number; ++itemno)
449 {
450 size_t i;
451
452 item_width = (*item_width_fn) (items[itemno]);
453
454 for (i = 0; i < max_cols; ++i)
455 {
456 if (worlds[i].valid_len)
457 {
458 size_t effective_width = available_width - i * m->between;
459 size_t idx = itemno % (i + 1);
460 size_t real_width = item_width;
461
462 if (real_width > worlds[i].widths[idx])
463 {
464 worlds[i].width += (real_width - worlds[i].widths[idx]);
465 worlds[i].widths[idx] = real_width;
466 worlds[i].valid_len = worlds[i].width <= effective_width;
467 }
468 }
469 }
470 }
471
472 /* Find maximum allowed columns. */
473 for (cols = max_cols; cols > 1; --cols)
474 {
475 if (worlds[cols - 1].valid_len)
476 break;
477 }
478
479 *line_fmt = &worlds[cols - 1];
480 return cols;
481 }
482
483 /* FIXME: document */
484
485 void
lister_fprint_horizontal(struct lister * lister,PARAM_UNUSED FILE * unused,void ** items,size_t item_number,lister_width_t item_width_fn,lister_print_t item_print_fn)486 lister_fprint_horizontal (struct lister * lister, PARAM_UNUSED FILE *unused,
487 void **items, size_t item_number,
488 lister_width_t item_width_fn,
489 lister_print_t item_print_fn)
490 {
491 struct world *line_fmt;
492 size_t itemno;
493 size_t col_width;
494 size_t item_width;
495 size_t cols;
496 size_t pos;
497 size_t nextpos, nextcolpos;
498 struct lister * l = lister ? lister : &lister_default;
499 struct multicol * m = &l->multicol;
500 size_t tabsize = tterm_tabsize (l->tterm);
501 FILE *stream = l->stream;
502
503 init_worlds (l);
504
505 /* If not known, compute the actually number of elements.
506 FIXME: there are probably more intelligent scheme to get item_number
507 on the fly, but currently it is need in lister_vertical_format
508 real soon, so just compute it right now. */
509 if (item_number == (size_t) -1)
510 {
511 for (item_number = 0 ; items[item_number] ; item_number++)
512 /* nothing */ ;
513 }
514
515 cols = lister_horizontal_format (l, items, item_number,
516 item_width_fn, &line_fmt);
517
518 /* Print first entry. */
519 pos = 0;
520 nextcolpos = nextpos = l->before;
521
522 /* Now the rest. */
523 for (itemno = 0; itemno < item_number; ++itemno)
524 {
525 size_t col = itemno % cols;
526
527 item_width = strlen (items[itemno]);
528 col_width = line_fmt->widths[col];
529
530 if (col == 0 && itemno != 0)
531 {
532 putc ('\n', stream);
533 pos = 0;
534 nextcolpos = nextpos = l->before;
535 }
536
537 nextpos += (col_width - item_width) * m->justify / 2;
538 INDENT (pos, nextpos);
539 (*item_print_fn) (items [itemno], stream);
540 pos = nextpos + item_width;
541 nextcolpos += col_width + m->between;
542 nextpos = nextcolpos;
543 }
544 putc ('\n', stream);
545 }
546
547
548 /* Same as LISTER_FPRINT_HORIZONTAL, but using LISTER's default value
549 for width_fn and print_fn. */
550
551 void
lister_print_horizontal(struct lister * lister,void ** items,size_t item_number)552 lister_print_horizontal (struct lister *lister,
553 void **items, size_t item_number)
554 {
555 struct lister *l = lister ? lister : &lister_default;
556 lister_fprint_horizontal (lister, NULL,
557 items, item_number,
558 l->width_fn, l->print_fn);
559 }
560
561 /*
562 Listing thing separated by spaces and strings. For instance:
563
564 +------------------------------+
565 | one, two, three, four, five,|
566 | six, seven, eight, nine, |
567 | ten, eleven. |
568 +------------------------------+
569
570 Note that in the first line, `,' is written though there is not
571 enough room for the ` ' behind. Note also that `ten' is not
572 written on the second line, because there is no room enough for the
573 `,'.
574
575 This corresponds to the settings: before = 2, after = 0,
576 between_width = 1, between_string = `,' after_string = `.'.
577 */
578
579 void
lister_fprint_separated(struct lister * lister,PARAM_UNUSED FILE * unused,void ** items,size_t item_number,lister_width_t item_width_fn,lister_print_t item_print_fn)580 lister_fprint_separated (struct lister * lister,PARAM_UNUSED FILE *unused,
581 void **items, size_t item_number,
582 lister_width_t item_width_fn,
583 lister_print_t item_print_fn)
584 {
585 size_t itemno;
586 size_t pos;
587 struct lister * l = lister ? lister : &lister_default;
588 struct separated * s = &l->separated;
589 size_t final_width = strlen (s->final);
590 size_t separator_width = strlen (s->separator);
591 size_t tabsize = tterm_tabsize (l->tterm);
592 FILE *stream = l->stream;
593
594 /* The BEFORE prefix must not be `smartly' hidden in line_width,
595 since INDENT needs the absolute position on the screen in order
596 to synchronize correctly the tabulations. */
597 size_t line_width = tterm_width (l->tterm) - l->after;
598 size_t old_pos;
599
600 pos = l->before;
601 INDENT (0, pos);
602
603 for (itemno = 0;
604 (item_number != (size_t) -1
605 ? (itemno < item_number)
606 : items[itemno] != NULL);
607 itemno++)
608 {
609 old_pos = pos;
610
611 pos += (*item_width_fn) (items[itemno]);
612 pos += (itemno + 1 < item_number) ? separator_width : final_width;
613
614 if (itemno)
615 {
616 if (pos + s->between > line_width)
617 {
618 putc ('\n', stream);
619 INDENT (0, l->before);
620 pos = pos - old_pos + l->before;
621 }
622 else
623 {
624 INDENT (old_pos, old_pos + s->between);
625 pos += s->between;
626 }
627 }
628 (*item_print_fn) (items[itemno], stream);
629 fputs ((itemno + 1 < item_number) ? s->separator : s->final, stream);
630 }
631 putc ('\n', stream);
632 }
633
634 /* Same as LISTER_FPRINT_SEPARATED, but using LISTER's default value
635 for width_fn and print_fn. */
636
637 void
lister_print_separated(struct lister * lister,void ** items,size_t item_number)638 lister_print_separated (struct lister *lister,
639 void **items, size_t item_number)
640 {
641 struct lister *l = lister ? lister : &lister_default;
642 lister_fprint_separated (lister, NULL,
643 items, item_number,
644 l->width_fn, l->print_fn);
645 }
646
647
648 #ifdef TEST
649
650 const char * program_name = "lister";
651
652 int
main(int argc,char ** argv)653 main (int argc, char **argv)
654 {
655 static const char * liste[] =
656 {
657 "1", "22", "333", "4444", "55555", "666666", "7777777",
658 "88888888", "999999999"
659 };
660 #if 0
661 {
662 "68000.ssh", "a2ps.ssh", "a2psrc.ssh", "ada.ssh", "c.ssh",
663 "caml.ssh", "card.ssh", "chlog.ssh", "claire.ssh", "clisp.ssh",
664 "coqv.ssh", "cpp.ssh", "csh.ssh", "eiffel.ssh", "elisp.ssh",
665 "eps.ssh", "fortran.ssh", "gnuc.ssh", "html.ssh", "idl.ssh",
666 "initora.ssh", "is5rul.ssh", "java.ssh", "lace.ssh", "lex.ssh",
667 "mail.ssh", "make.ssh", "matlab4.ssh", "mib.ssh", "modula2.ssh",
668 "modula3.ssh", "o2c.ssh", "oberon.ssh", "objc.ssh", "octave.ssh",
669 "oldperl.ssh", "oracle.ssh", "pascal.ssh", "perl.ssh", "plsql.ssh",
670 "ppd.ssh", "pre.ssh", "pretex.ssh", "prolog.ssh", "promela.ssh",
671 "ps.ssh", "python.ssh", "sather.ssh", "scheme.ssh", "sdl88.ssh",
672 "sh.ssh", "sql.ssh", "sql92.ssh", "ssh.ssh", "symbols.ssh",
673 "tcl.ssh", "tcsh.ssh", "tex.ssh", "texinfo.ssh", "texscript.ssh",
674 "tk.ssh", "udiff.ssh", "unity.ssh", "verilog.ssh", "vhdl.ssh",
675 "vrml.ssh", "wdiff.ssh", "yacc.ssh", "zsh.ssh",
676 "1111111111", "22",
677 "1111111111", "22",
678 "1111111111", "22",
679 "1111111111", "22",
680 "1111111111", "22",
681 "1111111111", "22",
682 "1111111111", "22",
683 "1111111111", "22",
684 "1111111111", "22"
685 } ;
686 #endif
687 /* lister_default.line_width = 67;*/
688 lister_init (stdout);
689
690 lister_fprint_separated (NULL,
691 liste, sizeof (liste) / sizeof (*liste),
692 strlen, fputs);
693 return 0;
694 }
695 #endif
696