.
[glibc/history.git] / stdio / linewrap.c
blobe22640da0f7e73da7d6be57d66b057cb7da5b8ce
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.
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.
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.
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. */
20 #include <stdio.h>
21 #include <ctype.h>
22 #include <string.h>
23 #include <stdlib.h>
25 #include <linewrap.h>
27 void __line_wrap_output (FILE *, int);
29 /* Install our hooks into a stream. */
30 static inline void
31 wrap_stream (FILE *stream, struct line_wrap_data *d)
33 static __io_close_fn lwclose;
34 static __io_fileno_fn lwfileno;
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. */
43 /* Restore a stream to its original state. */
44 static inline void
45 unwrap_stream (FILE *stream, struct line_wrap_data *d)
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;
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)
60 if (*wrapper_cookie == 0)
62 *wrapper_cookie = stream->__cookie;
63 unwrap_stream (stream, *wrapper_cookie);
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)
73 if (*wrapper_cookie)
75 wrap_stream (stream, *wrapper_cookie);
76 *wrapper_cookie = 0;
80 /* Cookie io functions that might get called on a wrapped stream.
81 Must pass the original cookie to the original functions. */
83 static int
84 lwclose (void *cookie)
86 struct line_wrap_data *d = cookie;
87 return (*d->close) (d->cookie);
90 static int
91 lwfileno (void *cookie)
93 struct line_wrap_data *d = cookie;
94 return (*d->fileno) (d->cookie);
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)
106 char *buf, *nl;
107 size_t len;
108 struct line_wrap_data *d = *wrapper_cookie ?: stream->__cookie;
110 /* Scan the buffer for newlines. */
111 buf = stream->__buffer + d->point_offs;
112 while ((buf < stream->__bufp || (c != EOF && c != '\n')) && !stream->__error)
114 size_t r;
116 if (d->point_col == 0 && d->lmargin != 0)
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)
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. */
129 else
131 /* No buffer space for spaces. Must flush. */
132 size_t i;
133 char *olimit;
135 ensure_unwrapped (stream, wrapper_cookie);
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;
146 d->point_col = pad;
149 len = stream->__bufp - buf;
150 nl = memchr (buf, '\n', len);
152 if (!nl)
154 /* The buffer ends in a partial line. */
156 if (d->point_col + len + (c != EOF && c != '\n') <= d->rmargin)
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;
164 else
165 /* Set the end-of-line pointer for the code below to
166 the end of the buffer. */
167 nl = stream->__bufp;
169 else if (d->point_col + (nl - buf) <= d->rmargin)
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;
178 /* This line is too long. */
179 r = d->rmargin;
181 if (d->wmargin < 0)
183 /* Truncate the line by overwriting the excess with the
184 newline and anything after it in the buffer. */
185 if (nl < stream->__bufp)
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. */
193 else
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;
206 else
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. */
212 char *p, *nextline;
213 int i;
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. */
220 if (nextline > buf)
222 /* Swallow separating blanks. */
224 --p;
225 while (isblank (*p));
226 nl = p + 1; /* The newline will replace the first blank. */
228 else
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. */
235 ++p;
236 while (p < nl && !isblank (*p));
237 if (p == nl)
239 /* It already ends a line. No fussing required. */
240 d->point_col = 0;
241 buf = nl + 1;
242 continue;
244 /* We will move the newline to replace the first blank. */
245 nl = p;
246 /* Swallow separating blanks. */
248 ++p;
249 while (isblank (*p));
250 /* The next line will start here. */
251 nextline = p;
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. */
260 ensure_unwrapped (stream, wrapper_cookie);
261 (*d->output) (stream, '\n');
263 else
264 /* We can fit the newline and blanks in before
265 the next word. */
266 *stream->__bufp++ = '\n';
268 /* Reset the counter of what has been output this line. */
269 d->point_col = 0;
271 /* Add blanks up to the wrap margin column. */
272 for (i = 0; i < d->wmargin; ++i)
273 *stream->__bufp++ = ' ';
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;
281 /* Continue the scan on the remaining lines in the buffer. */
282 buf = stream->__bufp;
284 /* Restore bufp to include all the remaining text. */
285 stream->__bufp += len;
289 /* Remember that we've scanned as far as the end of the buffer. */
290 d->point_offs = stream->__bufp - stream->__buffer;
292 return c;
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)
300 struct line_wrap_data *d = 0;
302 c = lwupdate (stream, c, &d);
304 if (!stream->__error)
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;
315 ensure_wrapped (stream, &d);
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)
327 struct line_wrap_data *d = malloc (sizeof *d);
329 if (!d)
330 return NULL;
332 /* Ensure full setup before we start tweaking. */
333 fflush (stream);
335 /* Initialize our wrapping state. */
336 d->point_col = 0;
337 d->point_offs = 0;
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;
345 /* Take over the stream. */
346 wrap_stream (stream, d);
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;
353 d->lmargin = lmargin;
354 d->rmargin = rmargin;
355 d->wmargin = wmargin;
357 return stream;
360 /* Remove the hooks placed in STREAM by `line_wrap_stream'. */
361 void
362 line_unwrap_stream (FILE *stream)
364 struct line_wrap_data *d = stream->__cookie;
365 unwrap_stream (stream, d);
366 free (d);
369 /* Functions on wrapped streams. */
371 /* Returns true if STREAM is line wrapped. */
372 inline int
373 line_wrapped (FILE *stream)
375 return (stream->__room_funcs.__output == &__line_wrap_output);
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)
387 if (line_wrapped (stream))
389 struct line_wrap_data *d = stream->__cookie, *wc = 0;
390 lwupdate (stream, EOF, &wc);
391 ensure_wrapped (stream, &wc);
392 return d;
394 else
395 return 0;
398 /* If STREAM is not line-wrapped return -1, else return its left margin. */
399 inline size_t
400 line_wrap_lmargin (FILE *stream)
402 if (! line_wrapped (stream))
403 return -1;
404 return ((struct line_wrap_data *)stream->__cookie)->lmargin;
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)
412 struct line_wrap_data *d = __line_wrap_update (stream);
413 if (d)
415 size_t old = d->lmargin;
416 d->lmargin = lmargin;
417 return old;
419 else
420 return -1;
423 /* If STREAM is not line-wrapped return -1, else return its left margin. */
424 inline size_t
425 line_wrap_rmargin (FILE *stream)
427 if (! line_wrapped (stream))
428 return -1;
429 return ((struct line_wrap_data *)stream->__cookie)->rmargin;
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)
437 struct line_wrap_data *d = __line_wrap_update (stream);
438 if (d)
440 size_t old = d->rmargin;
441 d->rmargin = rmargin;
442 return old;
444 else
445 return -1;
448 /* If STREAM is not line-wrapped return -1, else return its wrap margin. */
449 inline size_t
450 line_wrap_wmargin (FILE *stream)
452 if (! line_wrapped (stream))
453 return -1;
454 return ((struct line_wrap_data *)stream->__cookie)->wmargin;
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)
462 struct line_wrap_data *d = __line_wrap_update (stream);
463 if (d)
465 size_t old = d->wmargin;
466 d->wmargin = wmargin;
467 return old;
469 else
470 return -1;
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)
478 struct line_wrap_data *d = __line_wrap_update (stream);
479 return d ? d->point_col : -1;
482 #ifdef TEST
484 main (int argc, char **argv)
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;
492 #endif