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