utils.c 17.4 KB
Newer Older
Chris Allegretta's avatar
Chris Allegretta committed
1
/* $Id$ */
Chris Allegretta's avatar
Chris Allegretta committed
2
3
4
/**************************************************************************
 *   utils.c                                                              *
 *                                                                        *
5
 *   Copyright (C) 1999-2004 Chris Allegretta                             *
6
 *   Copyright (C) 2005-2006 David Lawrence Ramsey                        *
Chris Allegretta's avatar
Chris Allegretta committed
7
8
 *   This program is free software; you can redistribute it and/or modify *
 *   it under the terms of the GNU General Public License as published by *
9
 *   the Free Software Foundation; either version 2, or (at your option)  *
Chris Allegretta's avatar
Chris Allegretta committed
10
11
 *   any later version.                                                   *
 *                                                                        *
12
13
14
15
 *   This program is distributed in the hope that it will be useful, but  *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of           *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU    *
 *   General Public License for more details.                             *
Chris Allegretta's avatar
Chris Allegretta committed
16
17
18
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
 *   along with this program; if not, write to the Free Software          *
19
20
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA            *
 *   02110-1301, USA.                                                     *
Chris Allegretta's avatar
Chris Allegretta committed
21
22
23
 *                                                                        *
 **************************************************************************/

24
#include "proto.h"
25

Chris Allegretta's avatar
Chris Allegretta committed
26
#include <string.h>
27
28
#include <stdio.h>
#include <unistd.h>
29
#include <pwd.h>
Chris Allegretta's avatar
Chris Allegretta committed
30
#include <ctype.h>
31
#include <errno.h>
Chris Allegretta's avatar
Chris Allegretta committed
32

33
/* Return the number of decimal digits in n. */
34
int digits(size_t n)
35
{
36
    int i;
37

38
39
    for (i = 1; n >= 10; n /= 10, i++)
	;
40
41
42
43

    return i;
}

44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/* Return the user's home directory.  We use $HOME, and if that fails,
 * we fall back on getpwuid(). */
void get_homedir(void)
{
    if (homedir == NULL) {
	const char *homenv = getenv("HOME");

	if (homenv == NULL) {
	    const struct passwd *userage = getpwuid(geteuid());

	    if (userage != NULL)
		homenv = userage->pw_dir;
	}
	homedir = mallocstrcpy(NULL, homenv);
    }
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
61
62
63
/* Read a ssize_t from str, and store it in *val (if val is not NULL).
 * On error, we return FALSE and don't change *val.  Otherwise, we
 * return TRUE. */
64
bool parse_num(const char *str, ssize_t *val)
65
66
67
68
69
{
    char *first_error;
    ssize_t j;

    assert(str != NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
70

71
    j = (ssize_t)strtol(str, &first_error, 10);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
72

73
    if (errno == ERANGE || *str == '\0' || *first_error != '\0')
74
	return FALSE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
75

76
77
    if (val != NULL)
	*val = j;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
78

79
    return TRUE;
80
81
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
82
83
84
/* Read two ssize_t's, separated by a comma, from str, and store them in
 * *line and *column (if they're not both NULL).  Return FALSE on error,
 * or TRUE otherwise. */
85
bool parse_line_column(const char *str, ssize_t *line, ssize_t *column)
86
{
87
    bool retval = TRUE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
88
    const char *comma;
89
90
91
92
93

    assert(str != NULL);

    comma = strchr(str, ',');

94
    if (comma != NULL && column != NULL) {
95
	if (!parse_num(comma + 1, column))
96
97
98
99
100
	    retval = FALSE;
    }

    if (line != NULL) {
	if (comma != NULL) {
101
102
	    char *str_line = mallocstrncpy(NULL, str, comma - str + 1);
	    str_line[comma - str] = '\0';
103

104
	    if (str_line[0] != '\0' && !parse_num(str_line, line))
105
106
107
108
109
110
		retval = FALSE;

	    free(str_line);
	} else if (!parse_num(str, line))
	    retval = FALSE;
    }
111

112
    return retval;
113
114
}

Chris Allegretta's avatar
Chris Allegretta committed
115
/* Fix the memory allocation for a string. */
116
void align(char **str)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
117
{
118
    assert(str != NULL);
119

120
121
    if (*str != NULL)
	*str = charealloc(*str, strlen(*str) + 1);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
122
123
}

Chris Allegretta's avatar
Chris Allegretta committed
124
125
/* Null a string at a certain index and align it. */
void null_at(char **data, size_t index)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
126
{
Chris Allegretta's avatar
Chris Allegretta committed
127
    assert(data != NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
128

Chris Allegretta's avatar
Chris Allegretta committed
129
    *data = charealloc(*data, index + 1);
Chris Allegretta's avatar
Chris Allegretta committed
130
    (*data)[index] = '\0';
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
131
132
}

Chris Allegretta's avatar
Chris Allegretta committed
133
134
135
/* For non-null-terminated lines.  A line, by definition, shouldn't
 * normally have newlines in it, so encode its nulls as newlines. */
void unsunder(char *str, size_t true_len)
Chris Allegretta's avatar
Chris Allegretta committed
136
{
Chris Allegretta's avatar
Chris Allegretta committed
137
    assert(str != NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
138

139
    for (; true_len > 0; true_len--, str++) {
Chris Allegretta's avatar
Chris Allegretta committed
140
141
	if (*str == '\0')
	    *str = '\n';
142
    }
Chris Allegretta's avatar
Chris Allegretta committed
143
}
Chris Allegretta's avatar
Chris Allegretta committed
144

Chris Allegretta's avatar
Chris Allegretta committed
145
146
147
148
149
/* For non-null-terminated lines.  A line, by definition, shouldn't
 * normally have newlines in it, so decode its newlines into nulls. */
void sunder(char *str)
{
    assert(str != NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
150

151
    for (; *str != '\0'; str++) {
Chris Allegretta's avatar
Chris Allegretta committed
152
153
	if (*str == '\n')
	    *str = '\0';
154
    }
Chris Allegretta's avatar
Chris Allegretta committed
155
156
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
157
158
159
/* These functions, ngetline() (originally getline()) and ngetdelim()
 * (originally getdelim()), were adapted from GNU mailutils 0.5
 * (mailbox/getline.c).  Here is the notice from that file, after
160
161
 * converting to the GPL via LGPL clause 3, and with the Free Software
 * Foundation's address updated:
162
163
164
165
166
 *
 * GNU Mailutils -- a suite of utilities for electronic mail
 * Copyright (C) 1999, 2000, 2001, 2002 Free Software Foundation, Inc.
 *
 * This library is free software; you can redistribute it and/or
167
168
169
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
170
171
172
173
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
174
 * General Public License for more details.
175
 *
176
177
 * You should have received a copy of the GNU General Public License
 * along with this library; if not, write to the Free Software
178
179
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301, USA. */
180

181
#if !defined(NANO_TINY) && defined(ENABLE_NANORC)
182
#ifndef HAVE_GETLINE
183
/* This function is equivalent to getline(). */
184
185
186
187
188
189
190
ssize_t ngetline(char **lineptr, size_t *n, FILE *stream)
{
    return getdelim(lineptr, n, '\n', stream);
}
#endif

#ifndef HAVE_GETDELIM
191
/* This function is equivalent to getdelim(). */
192
193
194
195
196
197
ssize_t ngetdelim(char **lineptr, size_t *n, int delim, FILE *stream)
{
    size_t indx = 0;
    int c;

    /* Sanity checks. */
198
199
    if (lineptr == NULL || n == NULL || stream == NULL ||
	fileno(stream) == -1) {
200
201
202
	errno = EINVAL;
	return -1;
    }
203
204
205

    /* Allocate the line the first time. */
    if (*lineptr == NULL) {
206
207
	*lineptr = charalloc(MAX_BUF_SIZE);
	*n = MAX_BUF_SIZE;
208
209
210
211
212
    }

    while ((c = getc(stream)) != EOF) {
	/* Check if more memory is needed. */
	if (indx >= *n) {
213
214
	    *lineptr = charealloc(*lineptr, *n + MAX_BUF_SIZE);
	    *n += MAX_BUF_SIZE;
215
216
	}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
217
	/* Put the result in the line. */
218
219
220
221
222
223
224
225
226
	(*lineptr)[indx++] = (char)c;

	/* Bail out. */
	if (c == delim)
	    break;
    }

    /* Make room for the null character. */
    if (indx >= *n) {
227
228
	*lineptr = charealloc(*lineptr, *n + MAX_BUF_SIZE);
	*n += MAX_BUF_SIZE;
229
230
231
    }

    /* Null terminate the buffer. */
232
    null_at(lineptr, indx++);
233
    *n = indx;
234

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
235
236
    /* The last line may not have the delimiter.  We have to return what
     * we got, and the error will be seen on the next iteration. */
237
238
239
    return (c == EOF && (indx - 1) == 0) ? -1 : indx - 1;
}
#endif
240
#endif /* !NANO_TINY && ENABLE_NANORC */
241

242
#ifdef HAVE_REGEX_H
243
244
245
/* Do the compiled regex in preg and the regex in string match the
 * beginning or end of a line? */
bool regexp_bol_or_eol(const regex_t *preg, const char *string)
246
247
248
249
250
{
    return (regexec(preg, string, 0, NULL, 0) == 0 &&
	regexec(preg, string, 0, NULL, REG_NOTBOL | REG_NOTEOL) ==
	REG_NOMATCH);
}
251
#endif
252

253
#ifndef DISABLE_SPELLER
254
255
256
257
258
259
260
261
262
/* Is the word starting at position pos in buf a whole word? */
bool is_whole_word(size_t pos, const char *buf, const char *word)
{
    char *p = charalloc(mb_cur_max()), *r = charalloc(mb_cur_max());
    size_t word_end = pos + strlen(word);
    bool retval;

    assert(buf != NULL && pos <= strlen(buf) && word != NULL);

263
264
    parse_mbchar(buf + move_mbleft(buf, pos), p, NULL);
    parse_mbchar(buf + word_end, r, NULL);
265
266
267
268
269
270
271
272
273
274
275
276
277

    /* If we're at the beginning of the line or the character before the
     * word isn't a non-punctuation "word" character, and if we're at
     * the end of the line or the character after the word isn't a
     * non-punctuation "word" character, we have a whole word. */
    retval = (pos == 0 || !is_word_mbchar(p, FALSE)) &&
	(word_end == strlen(buf) || !is_word_mbchar(r, FALSE));

    free(p);
    free(r);

    return retval;
}
278
#endif /* !DISABLE_SPELLER */
279

280
281
282
283
284
285
/* If we are searching backwards, we will find the last match that
 * starts no later than start.  Otherwise we find the first match
 * starting no earlier than start.  If we are doing a regexp search, we
 * fill in the global variable regmatches with at most 9 subexpression
 * matches.  Also, all .rm_so elements are relative to the start of the
 * whole match, so regmatches[0].rm_so == 0. */
Chris Allegretta's avatar
Chris Allegretta committed
286
const char *strstrwrapper(const char *haystack, const char *needle,
287
	const char *start)
Chris Allegretta's avatar
Chris Allegretta committed
288
{
289
    /* start can be 1 character before the start or after the end of the
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
290
     * line.  In either case, we just say no match was found. */
291
292
    if ((start > haystack && *(start - 1) == '\0') || start < haystack)
	return NULL;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
293

294
    assert(haystack != NULL && needle != NULL && start != NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
295

296
#ifdef HAVE_REGEX_H
297
    if (ISSET(USE_REGEXP)) {
298
#ifndef NANO_TINY
299
	if (ISSET(BACKWARDS_SEARCH)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
300
301
	    if (regexec(&search_regexp, haystack, 1, regmatches,
		0) == 0 && haystack + regmatches[0].rm_so <= start) {
302
303
		const char *retval = haystack + regmatches[0].rm_so;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
304
		/* Search forward until there are no more matches. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
305
306
307
308
		while (regexec(&search_regexp, retval + 1, 1,
			regmatches, REG_NOTBOL) == 0 &&
			retval + regmatches[0].rm_so + 1 <= start)
		    retval += regmatches[0].rm_so + 1;
309
310
311
312
313
		/* Finally, put the subexpression matches in global
		 * variable regmatches.  The REG_NOTBOL flag doesn't
		 * matter now. */
		regexec(&search_regexp, retval, 10, regmatches, 0);
		return retval;
Chris Allegretta's avatar
Chris Allegretta committed
314
	    }
315
	} else
316
#endif /* !NANO_TINY */
317
	if (regexec(&search_regexp, start, 10, regmatches,
318
		(start > haystack) ? REG_NOTBOL : 0) == 0) {
319
	    const char *retval = start + regmatches[0].rm_so;
320
321
322

	    regexec(&search_regexp, retval, 10, regmatches, 0);
	    return retval;
323
	}
324
	return NULL;
325
    }
326
#endif /* HAVE_REGEX_H */
327
#if !defined(NANO_TINY) || !defined(DISABLE_SPELLER)
328
    if (ISSET(CASE_SENSITIVE)) {
329
#ifndef NANO_TINY
330
	if (ISSET(BACKWARDS_SEARCH))
331
	    return revstrstr(haystack, needle, start);
332
	else
333
#endif
334
	    return strstr(start, needle);
335
    }
336
337
#endif /* !DISABLE_SPELLER || !NANO_TINY */
#ifndef NANO_TINY
338
    else if (ISSET(BACKWARDS_SEARCH))
339
	return mbrevstrcasestr(haystack, needle, start);
340
#endif
341
    return mbstrcasestr(start, needle);
Chris Allegretta's avatar
Chris Allegretta committed
342
}
Chris Allegretta's avatar
Chris Allegretta committed
343

344
345
346
347
/* This is a wrapper for the perror() function.  The wrapper temporarily
 * leaves curses mode, calls perror() (which writes to stderr), and then
 * reenters curses mode, updating the screen in the process.  Note that
 * nperror() causes the window to flicker once. */
348
349
void nperror(const char *s)
{
350
351
352
    endwin();
    perror(s);
    doupdate();
353
354
}

355
356
357
/* This is a wrapper for the malloc() function that properly handles
 * things when we run out of memory.  Thanks, BG, many people have been
 * asking for this... */
Chris Allegretta's avatar
Chris Allegretta committed
358
void *nmalloc(size_t howmuch)
Chris Allegretta's avatar
Chris Allegretta committed
359
{
Chris Allegretta's avatar
Chris Allegretta committed
360
    void *r = malloc(howmuch);
Chris Allegretta's avatar
Chris Allegretta committed
361

Chris Allegretta's avatar
Chris Allegretta committed
362
363
    if (r == NULL && howmuch != 0)
	die(_("nano is out of memory!"));
364

Chris Allegretta's avatar
Chris Allegretta committed
365
    return r;
366
367
}

368
369
/* This is a wrapper for the realloc() function that properly handles
 * things when we run out of memory. */
Chris Allegretta's avatar
Chris Allegretta committed
370
void *nrealloc(void *ptr, size_t howmuch)
Chris Allegretta's avatar
Chris Allegretta committed
371
{
Chris Allegretta's avatar
Chris Allegretta committed
372
    void *r = realloc(ptr, howmuch);
Chris Allegretta's avatar
Chris Allegretta committed
373

Chris Allegretta's avatar
Chris Allegretta committed
374
375
    if (r == NULL && howmuch != 0)
	die(_("nano is out of memory!"));
Chris Allegretta's avatar
Chris Allegretta committed
376
377
378

    return r;
}
Robert Siemborski's avatar
Robert Siemborski committed
379

380
381
382
383
/* Copy the first n characters of one malloc()ed string to another
 * pointer.  Should be used as: "dest = mallocstrncpy(dest, src,
 * n);". */
char *mallocstrncpy(char *dest, const char *src, size_t n)
384
{
385
386
    if (src == NULL)
	src = "";
387

388
    if (src != dest)
389
390
	free(dest);

391
    dest = charalloc(n);
392
    strncpy(dest, src, n);
393
394
395
396

    return dest;
}

397
398
399
400
/* Copy one malloc()ed string to another pointer.  Should be used as:
 * "dest = mallocstrcpy(dest, src);". */
char *mallocstrcpy(char *dest, const char *src)
{
401
402
    return mallocstrncpy(dest, src, (src == NULL) ? 1 :
	strlen(src) + 1);
403
404
}

405
406
407
408
409
410
411
412
413
/* Free the malloc()ed string at dest and return the malloc()ed string
 * at src.  Should be used as: "answer = mallocstrassn(answer,
 * real_dir_from_tilde(answer));". */
char *mallocstrassn(char *dest, char *src)
{
    free(dest);
    return src;
}

414
415
416
417
418
419
420
421
/* nano scrolls horizontally within a line in chunks.  Return the column
 * number of the first character displayed in the edit window when the
 * cursor is at the given column.  Note that (0 <= column -
 * get_page_start(column) < COLS). */
size_t get_page_start(size_t column)
{
    if (column == 0 || column < COLS - 1)
	return 0;
422
    else if (COLS > 8)
423
424
425
426
427
	return column - 7 - (column - 7) % (COLS - 8);
    else
	return column - (COLS - 2);
}

428
/* Return the placewewant associated with current_x, i.e. the zero-based
429
430
431
432
433
434
435
 * column position of the cursor.  The value will be no smaller than
 * current_x. */
size_t xplustabs(void)
{
    return strnlenpt(openfile->current->data, openfile->current_x);
}

436
/* Return the index in s of the character displayed at the given column,
437
 * i.e. the largest value such that strnlenpt(s, actual_x(s, column)) <=
438
 * column. */
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
size_t actual_x(const char *s, size_t column)
{
    size_t i = 0;
	/* The position in s, returned. */
    size_t len = 0;
	/* The screen display width to s[i]. */

    assert(s != NULL);

    while (*s != '\0') {
	int s_len = parse_mbchar(s, NULL, &len);

	if (len > column)
	    break;

	i += s_len;
	s += s_len;
    }

    return i;
}

/* A strnlen() with tabs and multicolumn characters factored in, similar
 * to xplustabs().  How many columns wide are the first maxlen characters
 * of s? */
size_t strnlenpt(const char *s, size_t maxlen)
{
    size_t len = 0;
	/* The screen display width to s[i]. */

    if (maxlen == 0)
	return 0;

    assert(s != NULL);

    while (*s != '\0') {
	int s_len = parse_mbchar(s, NULL, &len);

	s += s_len;

	if (maxlen <= s_len)
	    break;

	maxlen -= s_len;
    }

    return len;
}

/* A strlen() with tabs and multicolumn characters factored in, similar
 * to xplustabs().  How many columns wide is s? */
size_t strlenpt(const char *s)
{
    return strnlenpt(s, (size_t)-1);
}

495
/* Append a new magicline to filebot. */
496
497
void new_magicline(void)
{
498
499
500
501
502
503
504
    openfile->filebot->next = (filestruct *)nmalloc(sizeof(filestruct));
    openfile->filebot->next->data = mallocstrcpy(NULL, "");
    openfile->filebot->next->prev = openfile->filebot;
    openfile->filebot->next->next = NULL;
    openfile->filebot->next->lineno = openfile->filebot->lineno + 1;
    openfile->filebot = openfile->filebot->next;
    openfile->totsize++;
Robert Siemborski's avatar
Robert Siemborski committed
505
}
Chris Allegretta's avatar
Chris Allegretta committed
506

507
#ifndef NANO_TINY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
508
/* Remove the magicline from filebot, if there is one and it isn't the
509
510
 * only line in the file.  Assume that edittop and current are not at
 * filebot. */
511
512
void remove_magicline(void)
{
513
    if (openfile->filebot->data[0] == '\0' &&
514
	openfile->filebot != openfile->fileage) {
515
516
	assert(openfile->filebot != openfile->edittop && openfile->filebot != openfile->current);

517
518
519
520
	openfile->filebot = openfile->filebot->prev;
	free_filestruct(openfile->filebot->next);
	openfile->filebot->next = NULL;
	openfile->totsize--;
521
522
523
    }
}

524
525
526
/* Set top_x and bot_x to the top and bottom x-coordinates of the mark,
 * respectively, based on the locations of top and bot.  If
 * right_side_up isn't NULL, set it to TRUE If the mark begins with
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
527
 * (mark_begin, mark_begin_x) and ends with (current, current_x), or
528
529
530
531
532
533
 * FALSE otherwise. */
void mark_order(const filestruct **top, size_t *top_x, const filestruct
	**bot, size_t *bot_x, bool *right_side_up)
{
    assert(top != NULL && top_x != NULL && bot != NULL && bot_x != NULL);

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
534
535
536
537
538
    if ((openfile->current->lineno == openfile->mark_begin->lineno &&
	openfile->current_x > openfile->mark_begin_x) ||
	openfile->current->lineno > openfile->mark_begin->lineno) {
	*top = openfile->mark_begin;
	*top_x = openfile->mark_begin_x;
539
540
	*bot = openfile->current;
	*bot_x = openfile->current_x;
541
542
543
	if (right_side_up != NULL)
	    *right_side_up = TRUE;
    } else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
544
545
	*bot = openfile->mark_begin;
	*bot_x = openfile->mark_begin_x;
546
547
	*top = openfile->current;
	*top_x = openfile->current_x;
548
549
550
551
552
553
	if (right_side_up != NULL)
	    *right_side_up = FALSE;
    }
}
#endif

554
555
556
/* Calculate the number of characters between begin and end, and return
 * it. */
size_t get_totsize(const filestruct *begin, const filestruct *end)
557
{
558
    size_t totsize = 0;
559
560
561
    const filestruct *f;

    /* Go through the lines from begin to end->prev, if we can. */
562
    for (f = begin; f != end && f != NULL; f = f->next) {
563
	/* Count the number of characters on this line. */
564
	totsize += mbstrlen(f->data);
565

566
567
568
	/* Count the newline if we have one. */
	if (f->next != NULL)
	    totsize++;
569
570
571
572
573
    }

    /* Go through the line at end, if we can. */
    if (f != NULL) {
	/* Count the number of characters on this line. */
574
	totsize += mbstrlen(f->data);
575

576
577
578
	/* Count the newline if we have one. */
	if (f->next != NULL)
	    totsize++;
579
    }
580
581

    return totsize;
582
}
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612

#ifdef DEBUG
/* Dump the filestruct inptr to stderr. */
void dump_filestruct(const filestruct *inptr)
{
    if (inptr == openfile->fileage)
	fprintf(stderr, "Dumping file buffer to stderr...\n");
    else if (inptr == cutbuffer)
	fprintf(stderr, "Dumping cutbuffer to stderr...\n");
    else
	fprintf(stderr, "Dumping a buffer to stderr...\n");

    while (inptr != NULL) {
	fprintf(stderr, "(%ld) %s\n", (long)inptr->lineno, inptr->data);
	inptr = inptr->next;
    }
}

/* Dump the current buffer's filestruct to stderr in reverse. */
void dump_filestruct_reverse(void)
{
    const filestruct *fileptr = openfile->filebot;

    while (fileptr != NULL) {
	fprintf(stderr, "(%ld) %s\n", (long)fileptr->lineno,
		fileptr->data);
	fileptr = fileptr->prev;
    }
}
#endif /* DEBUG */