(lwupdate):
[kopensolaris-gnu/glibc.git] / stdio / linewrap.c
1 /* Word-wrapping and line-truncating streams.
2 Copyright (C) 1996 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public License as
7 published by the Free Software Foundation; either version 2 of the
8 License, or (at your option) any later version.
9
10 The GNU C Library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public
16 License along with the GNU C Library; see the file COPYING.LIB.  If
17 not, write to the Free Software Foundation, Inc., 675 Mass Ave,
18 Cambridge, MA 02139, USA.  */
19
20 #include <stdio.h>
21 #include <ctype.h>
22 #include <string.h>
23 #include <stdlib.h>
24
25 #include <linewrap.h>
26 \f
27 void __line_wrap_output (FILE *, int);
28
29 /* Install our hooks into a stream.  */
30 static inline void
31 wrap_stream (FILE *stream, struct line_wrap_data *d)
32 {
33   static __io_close_fn lwclose;
34   static __io_fileno_fn lwfileno;
35
36   stream->__cookie = d;
37   stream->__room_funcs.__output = &__line_wrap_output;
38   stream->__io_funcs.__close = &lwclose;
39   stream->__io_funcs.__fileno = &lwfileno;
40   stream->__io_funcs.__seek = NULL; /* Cannot seek.  */
41 }
42
43 /* Restore a stream to its original state.  */
44 static inline void
45 unwrap_stream (FILE *stream, struct line_wrap_data *d)
46 {
47   stream->__cookie = d->cookie;
48   stream->__room_funcs.__output = d->output;
49   stream->__io_funcs.__close = d->close;
50   stream->__io_funcs.__fileno = d->fileno;
51   stream->__io_funcs.__seek = d->seek;
52 }
53
54 /* If WRAPPER_COOKIE points to a 0 pointer, then STREAM is assumed to be
55    wrapped, and will be unwrapped, storing the wrapper cookie into
56    WRAPPER_COOKIE.  Otherwise, nothing is done.  */
57 static inline void
58 ensure_unwrapped (FILE *stream, struct line_wrap_data **wrapper_cookie)
59 {
60   if (*wrapper_cookie == 0)
61     {
62       *wrapper_cookie = stream->__cookie;
63       unwrap_stream (stream, *wrapper_cookie);
64     }
65 }
66
67 /* If WRAPPER_COOKIE points to a non-0 pointer, then STREAM is assumed to
68    *have been unwrapped with ensure_unwrapped, will be wrapped with
69    *WRAPPER_COOKIE, and *WRAPPER_COOKIE zeroed.  Otherwise, nothing is done. */
70 static inline void
71 ensure_wrapped (FILE *stream, struct line_wrap_data **wrapper_cookie)
72 {
73   if (*wrapper_cookie)
74     {
75       wrap_stream (stream, *wrapper_cookie);
76       *wrapper_cookie = 0;
77     }
78 }
79 \f
80 /* Cookie io functions that might get called on a wrapped stream.
81    Must pass the original cookie to the original functions.  */
82
83 static int
84 lwclose (void *cookie)
85 {
86   struct line_wrap_data *d = cookie;
87   return (*d->close) (d->cookie);
88 }
89
90 static int
91 lwfileno (void *cookie)
92 {
93   struct line_wrap_data *d = cookie;
94   return (*d->fileno) (d->cookie);
95 }
96 \f
97 /* Process STREAM's buffer so that line wrapping is done from POINT_OFFS to
98    the end of its buffer.  If WRAPPER_COOKIE is 0, and it's necessary to
99    flush some data, STREAM is unwrapped, and the line wrap stdio cookie
100    stored in WRAPPER_COOKIE; otherwise, stream is assumed to already be
101    unwrapped, and WRAPPER_COOKIE to point to the line wrap data.  Returns C
102    or EOF if C was output.  */
103 static inline int
104 lwupdate (FILE *stream, int c, struct line_wrap_data **wrapper_cookie)
105 {
106   char *buf, *nl;
107   size_t len;
108   struct line_wrap_data *d = *wrapper_cookie ?: stream->__cookie;
109
110   /* Scan the buffer for newlines.  */
111   buf = stream->__buffer + d->point_offs;
112   while ((buf < stream->__bufp || (c != EOF && c != '\n')) && !stream->__error)
113     {
114       size_t r;
115
116       if (d->point_col == 0 && d->lmargin != 0)
117         {
118           /* We are starting a new line.  Print spaces to the left margin.  */
119           const size_t pad = d->lmargin;
120           if (stream->__bufp + pad < stream->__put_limit)
121             {
122               /* We can fit in them in the buffer by moving the
123                  buffer text up and filling in the beginning.  */
124               memmove (buf + pad, buf, stream->__bufp - buf);
125               stream->__bufp += pad; /* Compensate for bigger buffer. */
126               memset (buf, ' ', pad); /* Fill in the spaces.  */
127               buf += pad; /* Don't bother searching them.  */
128             }
129           else
130             {
131               /* No buffer space for spaces.  Must flush.  */
132               size_t i;
133               char *olimit;
134
135               ensure_unwrapped (stream, wrapper_cookie);
136
137               len = stream->__bufp - buf;
138               olimit = stream->__put_limit;
139               stream->__bufp = stream->__put_limit = buf;
140               for (i = 0; i < pad; ++i)
141                 (*d->output) (stream, ' ');
142               stream->__put_limit = olimit;
143               memmove (stream->__bufp, buf, len);
144               stream->__bufp += len;
145             }
146           d->point_col = pad;
147         }
148
149       len = stream->__bufp - buf;
150       nl = memchr (buf, '\n', len);
151
152       if (!nl)
153         {
154           /* The buffer ends in a partial line.  */
155
156           if (d->point_col + len + (c != EOF && c != '\n') <= d->rmargin)
157             {
158               /* The remaining buffer text is a partial line and fits
159                  within the maximum line width.  Advance point for the
160                  characters to be written and stop scanning.  */
161               d->point_col += len;
162               break;
163             }
164           else
165             /* Set the end-of-line pointer for the code below to
166                the end of the buffer.  */
167             nl = stream->__bufp;
168         }
169       else if (d->point_col + (nl - buf) <= d->rmargin)
170         {
171           /* The buffer contains a full line that fits within the maximum
172              line width.  Reset point and scan the next line.  */
173           d->point_col = 0;
174           buf = nl + 1;
175           continue;
176         }
177
178       /* This line is too long.  */
179       r = d->rmargin;
180
181       if (d->wmargin < 0)
182         {
183           /* Truncate the line by overwriting the excess with the
184              newline and anything after it in the buffer.  */
185           if (nl < stream->__bufp)
186             {
187               memmove (buf + (r - d->point_col), nl, stream->__bufp - nl);
188               stream->__bufp -= buf + (r - d->point_col) - nl;
189               /* Reset point for the next line and start scanning it.  */
190               d->point_col = 0;
191               buf += r + 1; /* Skip full line plus \n. */
192             }
193           else
194             {
195               /* The buffer ends with a partial line that is beyond the
196                  maximum line width.  Advance point for the characters
197                  written, and discard those past the max from the buffer.  */
198               d->point_col += len;
199               stream->__bufp -= d->point_col - r;
200               if (c != '\n')
201                 /* Swallow the extra character too.  */
202                 c = EOF;
203               break;
204             }
205         }
206       else
207         {
208           /* Do word wrap.  Go to the column just past the maximum line
209              width and scan back for the beginning of the word there.
210              Then insert a line break.  */
211
212           char *p, *nextline;
213           int i;
214
215           p = buf + (r + 1 - d->point_col);
216           while (p >= buf && !isblank (*p))
217             --p;
218           nextline = p + 1;     /* This will begin the next line.  */
219
220           if (nextline > buf)
221             {
222               /* Swallow separating blanks.  */
223               do
224                 --p;
225               while (isblank (*p));
226               nl = p + 1;       /* The newline will replace the first blank. */
227             }
228           else
229             {
230               /* A single word that is greater than the maximum line width.
231                  Oh well.  Put it on an overlong line by itself.  */
232               p = buf + (r + 1 - d->point_col);
233               /* Find the end of the long word.  */
234               do
235                 ++p;
236               while (p < nl && !isblank (*p));
237               if (p == nl)
238                 {
239                   /* It already ends a line.  No fussing required.  */
240                   d->point_col = 0;
241                   buf = nl + 1;
242                   continue;
243                 }
244               /* We will move the newline to replace the first blank.  */
245               nl = p;
246               /* Swallow separating blanks.  */
247               do
248                 ++p;
249               while (isblank (*p));
250               /* The next line will start here.  */
251               nextline = p;
252             }
253
254           /* Temporarily reset bufp to include just the first line.  */
255           stream->__bufp = nl;
256           if (nextline - (nl + 1) < d->wmargin)
257             /* The margin needs more blanks than we removed.
258                Output the first line so we can use the space.  */
259             {
260               ensure_unwrapped (stream, wrapper_cookie);
261               (*d->output) (stream, '\n');
262             }
263           else
264             /* We can fit the newline and blanks in before
265                the next word.  */
266             *stream->__bufp++ = '\n';
267
268           /* Reset the counter of what has been output this line.  */
269           d->point_col = 0;
270
271           /* Add blanks up to the wrap margin column.  */
272           for (i = 0; i < d->wmargin; ++i)
273             *stream->__bufp++ = ' ';
274
275           /* Copy the tail of the original buffer into the current buffer
276              position.  */
277           if (stream->__bufp != nextline)
278             memmove (stream->__bufp, nextline, buf + len - nextline);
279           len -= nextline - buf;
280
281           /* Continue the scan on the remaining lines in the buffer.  */
282           buf = stream->__bufp;
283
284           /* Restore bufp to include all the remaining text.  */
285           stream->__bufp += len;
286         }
287     }
288
289   /* Remember that we've scanned as far as the end of the buffer.  */
290   d->point_offs = stream->__bufp - stream->__buffer;
291
292   return c;
293 }
294 \f
295 /* This function is called when STREAM must be flushed.
296    C is EOF or a character to be appended to the buffer contents.  */
297 void
298 __line_wrap_output (FILE *stream, int c)
299 {
300   struct line_wrap_data *d = 0;
301
302   c = lwupdate (stream, c, &d);
303
304   if (!stream->__error)
305     {
306       ensure_unwrapped (stream, &d);
307       (*d->output) (stream, c);
308       d->point_offs = 0;        /* The buffer now holds nothing.  */
309       if (c == '\n')
310         d->point_col = 0;
311       else if (c != EOF)
312         ++d->point_col;
313     }
314
315   ensure_wrapped (stream, &d);
316 }
317 \f
318 /* Modify STREAM so that it prefixes lines written on it with LMARGIN spaces
319    and limits them to RMARGIN columns total.  If WMARGIN >= 0, words that
320    extend past RMARGIN are wrapped by replacing the whitespace before them
321    with a newline and WMARGIN spaces.  Otherwise, chars beyond RMARGIN are
322    simply dropped until a newline.  Returns STREAM after modifying it, or
323    NULL if there was an error.  */
324 FILE *
325 line_wrap_stream (FILE *stream, size_t lmargin, size_t rmargin, size_t wmargin)
326 {
327   struct line_wrap_data *d = malloc (sizeof *d);
328
329   if (!d)
330     return NULL;
331
332   /* Ensure full setup before we start tweaking.  */
333   fflush (stream);
334
335   /* Initialize our wrapping state.  */
336   d->point_col = 0;
337   d->point_offs = 0;
338
339   /* Save the original cookie and output and close hooks.  */
340   d->cookie = stream->__cookie;
341   d->output = stream->__room_funcs.__output;
342   d->close = stream->__io_funcs.__close;
343   d->fileno = stream->__io_funcs.__fileno;
344
345   /* Take over the stream.  */
346   wrap_stream (stream, d);
347
348   /* Line-wrapping streams are normally line-buffered.  This is not
349      required, just assumed desired.  The wrapping feature should continue
350      to work if the stream is switched to full or no buffering.  */
351   stream->__linebuf = 1;
352
353   d->lmargin = lmargin;
354   d->rmargin = rmargin;
355   d->wmargin = wmargin;
356
357   return stream;
358 }
359
360 /* Remove the hooks placed in STREAM by `line_wrap_stream'.  */
361 void
362 line_unwrap_stream (FILE *stream)
363 {
364   struct line_wrap_data *d = stream->__cookie;
365   unwrap_stream (stream, d);
366   free (d);
367 }
368 \f
369 /* Functions on wrapped streams.  */
370
371 /* Returns true if STREAM is line wrapped.  */
372 inline int
373 line_wrapped (FILE *stream)
374 {
375   return (stream->__room_funcs.__output == &__line_wrap_output);
376 }
377
378 /* If STREAM is not line-wrapped, return 0.  Otherwise all pending text
379    buffered text in STREAM so that the POINT_OFFS field refers to the last
380    position in the stdio buffer, and return the line wrap state object for
381    STREAM.  Since all text has been processed, this means that (1) the
382    POINT_COL field refers to the column at which any new text would be added,
383    and (2) any changes to the margin parameters will only affect new text.  */
384 struct line_wrap_data *
385 __line_wrap_update (FILE *stream)
386 {
387   if (line_wrapped (stream))
388     {
389       struct line_wrap_data *d = stream->__cookie, *wc = 0;
390       lwupdate (stream, EOF, &wc);
391       ensure_wrapped (stream, &wc);
392       return d;
393     }
394   else
395     return 0;
396 }
397 \f
398 /* If STREAM is not line-wrapped return -1, else return its left margin.  */
399 inline size_t
400 line_wrap_lmargin (FILE *stream)
401 {
402   if (! line_wrapped (stream))
403     return -1;
404   return ((struct line_wrap_data *)stream->__cookie)->lmargin;
405 }
406
407 /* If STREAM is not line-wrapped return -1, else set its left margin to
408    LMARGIN and return the old value.  */
409 inline size_t
410 line_wrap_set_lmargin (FILE *stream, size_t lmargin)
411 {
412   struct line_wrap_data *d = __line_wrap_update (stream);
413   if (d)
414     {
415       size_t old = d->lmargin;
416       d->lmargin = lmargin;
417       return old;
418     }
419   else
420     return -1;
421 }
422
423 /* If STREAM is not line-wrapped return -1, else return its left margin.  */
424 inline size_t
425 line_wrap_rmargin (FILE *stream)
426 {
427   if (! line_wrapped (stream))
428     return -1;
429   return ((struct line_wrap_data *)stream->__cookie)->rmargin;
430 }
431
432 /* If STREAM is not line-wrapped return -1, else set its right margin to
433    RMARGIN and return the old value.  */
434 inline size_t
435 line_wrap_set_rmargin (FILE *stream, size_t rmargin)
436 {
437   struct line_wrap_data *d = __line_wrap_update (stream);
438   if (d)
439     {
440       size_t old = d->rmargin;
441       d->rmargin = rmargin;
442       return old;
443     }
444   else
445     return -1;
446 }
447
448 /* If STREAM is not line-wrapped return -1, else return its wrap margin.  */
449 inline size_t
450 line_wrap_wmargin (FILE *stream)
451 {
452   if (! line_wrapped (stream))
453     return -1;
454   return ((struct line_wrap_data *)stream->__cookie)->wmargin;
455 }
456
457 /* If STREAM is not line-wrapped return -1, else set its left margin to
458    WMARGIN and return the old value.  */
459 inline size_t
460 line_wrap_set_wmargin (FILE *stream, size_t wmargin)
461 {
462   struct line_wrap_data *d = __line_wrap_update (stream);
463   if (d)
464     {
465       size_t old = d->wmargin;
466       d->wmargin = wmargin;
467       return old;
468     }
469   else
470     return -1;
471 }
472
473 /* If STREAM is not line-wrapped return -1, else return the column number of
474    the current output point.  */
475 inline size_t
476 line_wrap_point (FILE *stream)
477 {
478   struct line_wrap_data *d = __line_wrap_update (stream);
479   return d ? d->point_col : -1;
480 }
481 \f
482 #ifdef TEST
483 int
484 main (int argc, char **argv)
485 {
486   int c;
487   puts ("stopme");
488   line_wrap_stream (stdout, atoi (argv[1]), atoi (argv[2] ?: "-1"));
489   while ((c = getchar()) != EOF) putchar (c);
490   return 0;
491 }
492 #endif