rcfile.c 28.6 KB
Newer Older
1
2
/* $Id$ */
/**************************************************************************
3
 *   rcfile.c                                                             *
4
 *                                                                        *
5
6
 *   Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007               *
 *   Free Software Foundation, Inc.                                       *
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 3, or (at your option)  *
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.                             *
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.                                                     *
21
22
23
 *                                                                        *
 **************************************************************************/

24
#include "proto.h"
25

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

#ifdef ENABLE_NANORC

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
35
static const rcoption rcopts[] = {
36
    {"boldtext", BOLD_TEXT},
37
38
#ifndef DISABLE_JUSTIFY
    {"brackets", 0},
39
#endif
40
    {"const", CONST_UPDATE},
Chris Allegretta's avatar
Chris Allegretta committed
41
#ifndef DISABLE_WRAPJUSTIFY
42
    {"fill", 0},
Chris Allegretta's avatar
Chris Allegretta committed
43
#endif
44
#ifndef DISABLE_MOUSE
45
    {"mouse", USE_MOUSE},
46
47
48
49
#endif
#ifdef ENABLE_MULTIBUFFER
    {"multibuffer", MULTIBUFFER},
#endif
50
    {"morespace", MORE_SPACE},
51
    {"nofollow", NOFOLLOW_SYMLINKS},
52
    {"nohelp", NO_HELP},
53
    {"nonewlines", NO_NEWLINES},
Chris Allegretta's avatar
Chris Allegretta committed
54
#ifndef DISABLE_WRAPPING
55
    {"nowrap", NO_WRAP},
Chris Allegretta's avatar
Chris Allegretta committed
56
#endif
57
#ifndef DISABLE_OPERATINGDIR
58
    {"operatingdir", 0},
59
#endif
Chris Allegretta's avatar
Chris Allegretta committed
60
    {"preserve", PRESERVE},
61
#ifndef DISABLE_JUSTIFY
62
    {"punct", 0},
63
64
    {"quotestr", 0},
#endif
65
    {"rebinddelete", REBIND_DELETE},
66
    {"rebindkeypad", REBIND_KEYPAD},
67
68
69
#ifdef HAVE_REGEX_H
    {"regexp", USE_REGEXP},
#endif
70
#ifndef DISABLE_SPELLER
71
    {"speller", 0},
72
73
74
#endif
    {"suspend", SUSPEND},
    {"tabsize", 0},
75
    {"tempfile", TEMP_FILE},
76
    {"view", VIEW_MODE},
77
#ifndef NANO_TINY
78
79
80
81
82
83
84
    {"autoindent", AUTOINDENT},
    {"backup", BACKUP_FILE},
    {"backupdir", 0},
    {"backwards", BACKWARDS_SEARCH},
    {"casesensitive", CASE_SENSITIVE},
    {"cut", CUT_TO_END},
    {"historylog", HISTORYLOG},
85
    {"matchbrackets", 0},
86
    {"noconvert", NO_CONVERT},
87
    {"quiet", QUIET},
88
    {"quickblank", QUICK_BLANK},
89
90
91
    {"smarthome", SMART_HOME},
    {"smooth", SMOOTH_SCROLL},
    {"tabstospaces", TABS_TO_SPACES},
92
    {"undo", UNDOABLE},
93
    {"whitespace", 0},
94
    {"wordbounds", WORD_BOUNDS},
95
    {"softwrap", SOFTWRAP},
96
#endif
97
    {NULL, 0}
98
};
99

100
static bool errors = FALSE;
101
	/* Whether we got any errors while parsing an rcfile. */
102
static size_t lineno = 0;
103
	/* If we did, the line number where the last error occurred. */
104
static char *nanorc = NULL;
105
	/* The path to the rcfile we're parsing. */
106
107
108
#ifdef ENABLE_COLOR
static syntaxtype *endsyntax = NULL;
	/* The end of the list of syntaxes. */
109
110
static exttype *endheader = NULL;
	/* End of header list */
111
112
static colortype *endcolor = NULL;
	/* The end of the color list for the current syntax. */
113

114
#endif
115

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
116
117
118
/* We have an error in some part of the rcfile.  Print the error message
 * on stderr, and then make the user hit Enter to continue starting
 * nano. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
119
void rcfile_error(const char *msg, ...)
120
121
122
{
    va_list ap;

123
124
125
    if (ISSET(QUIET))
	return;

126
    fprintf(stderr, "\n");
127
128
    if (lineno > 0) {
	errors = TRUE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
129
	fprintf(stderr, _("Error in %s on line %lu: "), nanorc, (unsigned long)lineno);
130
    }
Chris Allegretta's avatar
Chris Allegretta committed
131

132
    va_start(ap, msg);
133
    vfprintf(stderr, _(msg), ap);
134
    va_end(ap);
135
136

    fprintf(stderr, "\n");
137
138
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
139
140
141
/* Parse the next word from the string, null-terminate it, and return
 * a pointer to the first character after the null terminator.  The
 * returned pointer will point to '\0' if we hit the end of the line. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
142
char *parse_next_word(char *ptr)
143
{
144
    while (!isblank(*ptr) && *ptr != '\0')
145
146
147
	ptr++;

    if (*ptr == '\0')
148
	return ptr;
149

150
151
    /* Null-terminate and advance ptr. */
    *ptr++ = '\0';
152

153
    while (isblank(*ptr))
154
155
156
157
	ptr++;

    return ptr;
}
158

159
160
161
162
/* Parse an argument, with optional quotes, after a keyword that takes
 * one.  If the next word starts with a ", we say that it ends with the
 * last " of the line.  Otherwise, we interpret it as usual, so that the
 * arguments can contain "'s too. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
163
164
char *parse_argument(char *ptr)
{
165
    const char *ptr_save = ptr;
Chris Allegretta's avatar
Chris Allegretta committed
166
167
168
169
170
171
172
173
174
175
176
    char *last_quote = NULL;

    assert(ptr != NULL);

    if (*ptr != '"')
	return parse_next_word(ptr);

    do {
	ptr++;
	if (*ptr == '"')
	    last_quote = ptr;
177
    } while (*ptr != '\0');
Chris Allegretta's avatar
Chris Allegretta committed
178
179
180
181
182
183

    if (last_quote == NULL) {
	if (*ptr == '\0')
	    ptr = NULL;
	else
	    *ptr++ = '\0';
184
	rcfile_error(N_("Argument '%s' has an unterminated \""), ptr_save);
Chris Allegretta's avatar
Chris Allegretta committed
185
186
187
188
189
    } else {
	*last_quote = '\0';
	ptr = last_quote + 1;
    }
    if (ptr != NULL)
190
	while (isblank(*ptr))
Chris Allegretta's avatar
Chris Allegretta committed
191
192
193
194
195
	    ptr++;
    return ptr;
}

#ifdef ENABLE_COLOR
196
/* Parse the next regex string from the line at ptr, and return it. */
197
198
char *parse_next_regex(char *ptr)
{
199
200
201
202
    assert(ptr != NULL);

    /* Continue until the end of the line, or a " followed by a space, a
     * blank character, or \0. */
203
    while ((*ptr != '"' || (!isblank(*(ptr + 1)) &&
204
	*(ptr + 1) != '\0')) && *ptr != '\0')
205
206
	ptr++;

207
208
209
    assert(*ptr == '"' || *ptr == '\0');

    if (*ptr == '\0') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
210
211
	rcfile_error(
		N_("Regex strings must begin and end with a \" character"));
212
	return NULL;
213
    }
214

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
215
    /* Null-terminate and advance ptr. */
216
217
    *ptr++ = '\0';

218
    while (isblank(*ptr))
219
220
221
222
223
	ptr++;

    return ptr;
}

224
225
226
/* Compile the regular expression regex to see if it's valid.  Return
 * TRUE if it is, or FALSE otherwise. */
bool nregcomp(const char *regex, int eflags)
227
{
228
    regex_t preg;
229
230
    const char *r = fixbounds(regex);
    int rc = regcomp(&preg, r, REG_EXTENDED | eflags);
231
232

    if (rc != 0) {
233
	size_t len = regerror(rc, &preg, NULL, 0);
234
235
	char *str = charalloc(len);

236
	regerror(rc, &preg, str, len);
237
	rcfile_error(N_("Bad regex \"%s\": %s"), r, str);
238
239
	free(str);
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
240

241
    regfree(&preg);
242
    return (rc == 0);
243
244
}

245
246
/* Parse the next syntax string from the line at ptr, and add it to the
 * global list of color syntaxes. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
247
void parse_syntax(char *ptr)
248
{
Chris Allegretta's avatar
Chris Allegretta committed
249
    const char *fileregptr = NULL, *nameptr = NULL;
250
    syntaxtype *tmpsyntax;
251
252
    exttype *endext = NULL;
	/* The end of the extensions list for this syntax. */
253

254
    assert(ptr != NULL);
255

256
257
    if (*ptr == '\0') {
	rcfile_error(N_("Missing syntax name"));
258
	return;
259
    }
260
261

    if (*ptr != '"') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
262
263
	rcfile_error(
		N_("Regex strings must begin and end with a \" character"));
264
	return;
265
    }
266

267
268
269
270
271
    ptr++;

    nameptr = ptr;
    ptr = parse_next_regex(ptr);

272
    if (ptr == NULL)
273
	return;
274

275
276
    /* Search for a duplicate syntax name.  If we find one, free it, so
     * that we always use the last syntax with a given name. */
277
278
    for (tmpsyntax = syntaxes; tmpsyntax != NULL;
	tmpsyntax = tmpsyntax->next) {
279
	if (strcmp(nameptr, tmpsyntax->desc) == 0) {
280
281
282
283
284
	    syntaxtype *prev_syntax = tmpsyntax;

	    tmpsyntax = tmpsyntax->next;
	    free(prev_syntax);
	    break;
285
286
287
	}
    }

Chris Allegretta's avatar
Chris Allegretta committed
288
289
    if (syntaxes == NULL) {
	syntaxes = (syntaxtype *)nmalloc(sizeof(syntaxtype));
290
	endsyntax = syntaxes;
Chris Allegretta's avatar
Chris Allegretta committed
291
    } else {
292
293
	endsyntax->next = (syntaxtype *)nmalloc(sizeof(syntaxtype));
	endsyntax = endsyntax->next;
294
#ifdef DEBUG
295
	fprintf(stderr, "Adding new syntax after first one\n");
296
#endif
Chris Allegretta's avatar
Chris Allegretta committed
297
    }
298

299
300
301
    endsyntax->desc = mallocstrcpy(NULL, nameptr);
    endsyntax->color = NULL;
    endcolor = NULL;
302
    endheader = NULL;
303
    endsyntax->extensions = NULL;
304
    endsyntax->headers = NULL;
305
    endsyntax->next = NULL;
306
    endsyntax->nmultis = 0;
307

308
#ifdef DEBUG
309
    fprintf(stderr, "Starting a new syntax type: \"%s\"\n", nameptr);
310
311
#endif

312
313
314
315
316
317
318
    /* The "none" syntax is the same as not having a syntax at all, so
     * we can't assign any extensions or colors to it. */
    if (strcmp(endsyntax->desc, "none") == 0) {
	rcfile_error(N_("The \"none\" syntax is reserved"));
	return;
    }

319
    /* The default syntax should have no associated extensions. */
320
    if (strcmp(endsyntax->desc, "default") == 0 && *ptr != '\0') {
321
322
	rcfile_error(
		N_("The \"default\" syntax must take no extensions"));
323
324
325
	return;
    }

326
327
    /* Now load the extensions into their part of the struct. */
    while (*ptr != '\0') {
328
329
330
	exttype *newext;
	    /* The new extension structure. */

331
	while (*ptr != '"' && *ptr != '\0')
332
333
	    ptr++;

334
	if (*ptr == '\0')
335
	    return;
336

337
338
339
340
	ptr++;

	fileregptr = ptr;
	ptr = parse_next_regex(ptr);
341
342
	if (ptr == NULL)
	    break;
343

344
	newext = (exttype *)nmalloc(sizeof(exttype));
345
346
347
348
349
350

	/* Save the extension regex if it's valid. */
	if (nregcomp(fileregptr, REG_NOSUB)) {
	    newext->ext_regex = mallocstrcpy(NULL, fileregptr);
	    newext->ext = NULL;

351
	    if (endext == NULL)
352
		endsyntax->extensions = newext;
353
354
355
356
	    else
		endext->next = newext;
	    endext = newext;
	    endext->next = NULL;
357
358
	} else
	    free(newext);
359
    }
360
361
}

362
363
364
365
366
367
368
369
370
371
372
373
374
375
int check_bad_binding(sc *s)
{
#define BADLISTLEN 1
    int badtypes[BADLISTLEN] = {META};
    int badseqs[BADLISTLEN] = { 91 };
    int i;

    for (i = 0; i < BADLISTLEN; i++)
        if (s->type == badtypes[i] && s->seq == badseqs[i])
	    return 1;

    return 0;
}

376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
void parse_keybinding(char *ptr)
{
    char *keyptr = NULL, *keycopy = NULL, *funcptr = NULL, *menuptr = NULL;
    sc *s, *newsc;
    int i, menu;

    assert(ptr != NULL);

    if (*ptr == '\0') {
	rcfile_error(N_("Missing key name"));
	return;
    }

    keyptr = ptr;
    ptr = parse_next_word(ptr);
    keycopy = mallocstrcpy(NULL, keyptr);
    for (i = 0; i < strlen(keycopy); i++)
	keycopy[i] = toupper(keycopy[i]);

395
    if (keycopy[0] != 'M' && keycopy[0] != '^' && keycopy[0] != 'F' && keycopy[0] != 'K') {
396
	rcfile_error(
397
		N_("keybindings must begin with \"^\", \"M\", or \"F\""));
398
399
400
401
402
403
	return;
    }

    funcptr = ptr;
    ptr = parse_next_word(ptr);

404
    if (!strcmp(funcptr, "")) {
405
	rcfile_error(
406
		N_("Must specify function to bind key to"));
407
408
409
410
411
412
	return;
    }

    menuptr = ptr;
    ptr = parse_next_word(ptr);

413
    if (!strcmp(menuptr, "")) {
414
415
416
	rcfile_error(
		/* Note to translators, do not translate the word "all"
		   in the sentence below, everything else is fine */
417
		N_("Must specify menu to bind key to (or \"all\")"));
418
419
420
	return;
    }

421
    menu = strtomenu(menuptr);
422
423
    newsc = strtosc(menu, funcptr);
    if (newsc == NULL) {
424
	rcfile_error(
425
		N_("Could not map name \"%s\" to a function"), funcptr);
426
427
428
	return;
    }

429
    if (menu < 1) {
430
	rcfile_error(
431
		N_("Could not map name \"%s\" to a menu"), menuptr);
432
433
	return;
    }
434
435


436
437
#ifdef DEBUG
    fprintf(stderr, "newsc now address %d, menu func assigned = %d, menu = %d\n",
438
	&newsc, newsc->scfunc, menu);
439
440
441
#endif


442
443
444
445
446
447
448
449
450
    newsc->keystr = keycopy;
    newsc->menu = menu;
    newsc->type = strtokeytype(newsc->keystr);
    assign_keyinfo(newsc);
#ifdef DEBUG
    fprintf(stderr, "s->keystr = \"%s\"\n", newsc->keystr);
    fprintf(stderr, "s->seq = \"%d\"\n", newsc->seq);
#endif

451
452
    if (check_bad_binding(newsc)) {
	rcfile_error(
453
		N_("Sorry, keystr \"%s\" is an illegal binding"), newsc->keystr);
454
455
456
457
	return;
    }


458
459
460
461
    /* now let's have some fun.  Try and delete the other entries
       we found for the same menu, then make this new new
       beginning */
    for (s = sclist; s != NULL; s = s->next) {
Chris Allegretta's avatar
Chris Allegretta committed
462
        if (((s->menu & newsc->menu)) && s->seq == newsc->seq) {
463
464
465
466
467
468
469
470
471
472
473
	    s->menu &= ~newsc->menu;
#ifdef DEBUG
	    fprintf(stderr, "replaced menu entry %d\n", s->menu);
#endif
	}
    }
    newsc->next = sclist;
    sclist = newsc;
}


474
475
476
477
478
/* Read and parse additional syntax files. */
void parse_include(char *ptr)
{
    struct stat rcinfo;
    FILE *rcstream;
479
    char *option, *nanorc_save = nanorc, *expanded;
480
481
482
483
484
485
486
    size_t lineno_save = lineno;

    option = ptr;
    if (*option == '"')
	option++;
    ptr = parse_argument(ptr);

487
488
    /* Can't get the specified file's full path cause it may screw up
	our cwd depending on the parent dirs' permissions, (see Savannah bug 25297) */
489
490

    /* Don't open directories, character files, or block files. */
491
    if (stat(option, &rcinfo) != -1) {
492
493
494
495
	if (S_ISDIR(rcinfo.st_mode) || S_ISCHR(rcinfo.st_mode) ||
		S_ISBLK(rcinfo.st_mode)) {
	    rcfile_error(S_ISDIR(rcinfo.st_mode) ?
		_("\"%s\" is a directory") :
496
		_("\"%s\" is a device file"), option);
497
498
499
	}
    }

500
501
    expanded = real_dir_from_tilde(option);

502
    /* Open the new syntax file. */
503
504
    if ((rcstream = fopen(expanded, "rb")) == NULL) {
	rcfile_error(_("Error reading %s: %s"), expanded,
505
		strerror(errno));
506
	return;
507
508
509
510
    }

    /* Use the name and line number position of the new syntax file
     * while parsing it, so we can know where any errors in it are. */
511
    nanorc = expanded;
512
513
    lineno = 0;

514
#ifdef DEBUG
515
    fprintf(stderr, "Parsing file \"%s\" (expanded from \"%s\")\n", expanded, option);
516
517
#endif

518
519
520
521
522
523
524
525
526
527
528
529
530
    parse_rcfile(rcstream
#ifdef ENABLE_COLOR
	, TRUE
#endif
	);

    /* We're done with the new syntax file.  Restore the original
     * filename and line number position. */
    nanorc = nanorc_save;
    lineno = lineno_save;

}

531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
/* Return the short value corresponding to the color named in colorname,
 * and set bright to TRUE if that color is bright. */
short color_to_short(const char *colorname, bool *bright)
{
    short mcolor = -1;

    assert(colorname != NULL && bright != NULL);

    if (strncasecmp(colorname, "bright", 6) == 0) {
	*bright = TRUE;
	colorname += 6;
    }

    if (strcasecmp(colorname, "green") == 0)
	mcolor = COLOR_GREEN;
    else if (strcasecmp(colorname, "red") == 0)
	mcolor = COLOR_RED;
    else if (strcasecmp(colorname, "blue") == 0)
	mcolor = COLOR_BLUE;
    else if (strcasecmp(colorname, "white") == 0)
	mcolor = COLOR_WHITE;
    else if (strcasecmp(colorname, "yellow") == 0)
	mcolor = COLOR_YELLOW;
    else if (strcasecmp(colorname, "cyan") == 0)
	mcolor = COLOR_CYAN;
    else if (strcasecmp(colorname, "magenta") == 0)
	mcolor = COLOR_MAGENTA;
    else if (strcasecmp(colorname, "black") == 0)
	mcolor = COLOR_BLACK;
    else
	rcfile_error(N_("Color \"%s\" not understood.\n"
		"Valid colors are \"green\", \"red\", \"blue\",\n"
		"\"white\", \"yellow\", \"cyan\", \"magenta\" and\n"
		"\"black\", with the optional prefix \"bright\"\n"
		"for foreground colors."), colorname);

    return mcolor;
}

570
571
572
/* Parse the color string in the line at ptr, and add it to the current
 * file's associated colors.  If icase is TRUE, treat the color string
 * as case insensitive. */
573
void parse_colors(char *ptr, bool icase)
574
{
575
    short fg, bg;
576
    bool bright = FALSE, no_fgcolor = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
577
    char *fgstr;
578

579
    assert(ptr != NULL);
580

581
582
    if (syntaxes == NULL) {
	rcfile_error(
583
		N_("Cannot add a color command without a syntax command"));
584
585
586
	return;
    }

587
    if (*ptr == '\0') {
588
	rcfile_error(N_("Missing color name"));
589
	return;
590
591
    }

592
593
594
595
    fgstr = ptr;
    ptr = parse_next_word(ptr);

    if (strchr(fgstr, ',') != NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
596
	char *bgcolorname;
597

598
	strtok(fgstr, ",");
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
599
	bgcolorname = strtok(NULL, ",");
600
601
602
603
604
605
	if (bgcolorname == NULL) {
	    /* If we have a background color without a foreground color,
	     * parse it properly. */
	    bgcolorname = fgstr + 1;
	    no_fgcolor = TRUE;
	}
606
	if (strncasecmp(bgcolorname, "bright", 6) == 0) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
607
	    rcfile_error(
608
		N_("Background color \"%s\" cannot be bright"),
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
609
		bgcolorname);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
610
611
	    return;
	}
612
	bg = color_to_short(bgcolorname, &bright);
613
    } else
Chris Allegretta's avatar
Chris Allegretta committed
614
	bg = -1;
615

616
    if (!no_fgcolor) {
617
	fg = color_to_short(fgstr, &bright);
618

619
620
621
622
623
	/* Don't try to parse screwed-up foreground colors. */
	if (fg == -1)
	    return;
    } else
	fg = -1;
624

625
    if (*ptr == '\0') {
626
	rcfile_error(N_("Missing regex string"));
627
628
629
	return;
    }

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
630
    /* Now for the fun part.  Start adding regexes to individual strings
631
632
     * in the colorstrings array, woo! */
    while (ptr != NULL && *ptr != '\0') {
633
634
	colortype *newcolor;
	    /* The new color structure. */
635
	bool cancelled = FALSE;
636
	    /* The start expression was bad. */
637
638
	bool expectend = FALSE;
	    /* Do we expect an end= line? */
639

640
	if (strncasecmp(ptr, "start=", 6) == 0) {
641
	    ptr += 6;
642
	    expectend = TRUE;
643
644
645
	}

	if (*ptr != '"') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
646
647
	    rcfile_error(
		N_("Regex strings must begin and end with a \" character"));
648
	    ptr = parse_next_regex(ptr);
649
650
	    continue;
	}
651

652
	ptr++;
653

654
655
	fgstr = ptr;
	ptr = parse_next_regex(ptr);
656
657
658
	if (ptr == NULL)
	    break;

659
	newcolor = (colortype *)nmalloc(sizeof(colortype));
660

661
662
663
	/* Save the starting regex string if it's valid, and set up the
	 * color information. */
	if (nregcomp(fgstr, icase ? REG_ICASE : 0)) {
664
665
666
	    newcolor->fg = fg;
	    newcolor->bg = bg;
	    newcolor->bright = bright;
667
	    newcolor->icase = icase;
668
669
670
671

	    newcolor->start_regex = mallocstrcpy(NULL, fgstr);
	    newcolor->start = NULL;

672
	    newcolor->end_regex = NULL;
673
	    newcolor->end = NULL;
674

675
	    newcolor->next = NULL;
676

677
678
	    if (endcolor == NULL) {
		endsyntax->color = newcolor;
679
#ifdef DEBUG
680
		fprintf(stderr, "Starting a new colorstring for fg %hd, bg %hd\n", fg, bg);
681
#endif
682
	    } else {
Chris Allegretta's avatar
Chris Allegretta committed
683
#ifdef DEBUG
684
		fprintf(stderr, "Adding new entry for fg %hd, bg %hd\n", fg, bg);
Chris Allegretta's avatar
Chris Allegretta committed
685
#endif
686
		endcolor->next = newcolor;
687
	    }
688

689
	    endcolor = newcolor;
690
691
692
	} else {
	    free(newcolor);
	    cancelled = TRUE;
693
	}
Chris Allegretta's avatar
Chris Allegretta committed
694

695
	if (expectend) {
696
	    if (ptr == NULL || strncasecmp(ptr, "end=", 4) != 0) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
697
698
		rcfile_error(
			N_("\"start=\" requires a corresponding \"end=\""));
699
700
701
702
		return;
	    }
	    ptr += 4;
	    if (*ptr != '"') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
703
704
		rcfile_error(
			N_("Regex strings must begin and end with a \" character"));
705
		continue;
706
	    }
707

708
709
	    ptr++;

710
	    fgstr = ptr;
711
	    ptr = parse_next_regex(ptr);
712
713
	    if (ptr == NULL)
		break;
714
715
716
717
718

	    /* If the start regex was invalid, skip past the end regex to
	     * stay in sync. */
	    if (cancelled)
		continue;
719

720
721
722
	    /* Save the ending regex string if it's valid. */
	    newcolor->end_regex = (nregcomp(fgstr, icase ? REG_ICASE :
		0)) ? mallocstrcpy(NULL, fgstr) : NULL;
723
724
725
726

	    /* Lame way to skip another static counter */
            newcolor->id = endsyntax->nmultis;
            endsyntax->nmultis++;
Chris Allegretta's avatar
Chris Allegretta committed
727
	}
728
    }
729
}
730
731
732
733

/* Parse the headers (1st line) of the file which may influence the regex used. */
void parse_headers(char *ptr)
{
734
    char *regstr;
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792

    assert(ptr != NULL);

    if (syntaxes == NULL) {
	rcfile_error(
		N_("Cannot add a header regex without a syntax command"));
	return;
    }

    if (*ptr == '\0') {
	rcfile_error(N_("Missing regex string"));
	return;
    }

    /* Now for the fun part.  Start adding regexes to individual strings
     * in the colorstrings array, woo! */
    while (ptr != NULL && *ptr != '\0') {
	exttype *newheader;
	    /* The new color structure. */

	if (*ptr != '"') {
	    rcfile_error(
		N_("Regex strings must begin and end with a \" character"));
	    ptr = parse_next_regex(ptr);
	    continue;
	}

	ptr++;

	regstr = ptr;
	ptr = parse_next_regex(ptr);
	if (ptr == NULL)
	    break;

	newheader = (exttype *)nmalloc(sizeof(exttype));

	/* Save the regex string if it's valid */
	if (nregcomp(regstr, 0)) {
	    newheader->ext_regex = mallocstrcpy(NULL, regstr);
	    newheader->ext = NULL;
	    newheader->next = NULL;

#ifdef DEBUG
	     fprintf(stderr, "Starting a new header entry: %s\n", newheader->ext_regex);
#endif

	    if (endheader == NULL) {
		endsyntax->headers = newheader;
	    } else {
		endheader->next = newheader;
	    }

	    endheader = newheader;
	} else
	    free(newheader);

    }
}
793
#endif /* ENABLE_COLOR */
794

795
796
797
798
799
800
801
/* Check whether the user has unmapped every shortcut for a
sequence we consider 'vital', like the exit function */
static void check_vitals_mapped(void)
{
    subnfunc *f;
    int v;
#define VITALS 5
802
    short vitals[VITALS] = { DO_EXIT, DO_EXIT, CANCEL_MSG, CANCEL_MSG, CANCEL_MSG };
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
    int inmenus[VITALS] = { MMAIN, MHELP, MWHEREIS, MREPLACE, MGOTOLINE };

    for  (v = 0; v < VITALS; v++) {
       for (f = allfuncs; f != NULL; f = f->next) {
           if (f->scfunc == vitals[v] && f->menus & inmenus[v]) {
               const sc *s = first_sc_for(inmenus[v], f->scfunc);
               if (!s) {
                   rcfile_error(N_("Fatal error: no keys mapped for function \"%s\""),
                       f->desc);
                   fprintf(stderr, N_("Exiting.  Please use nano with the -I option if needed to adjust your nanorc settings\n"));
                   exit(1);
               }
           break;
           }
       }
    }
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
821
822
823
/* Parse the rcfile, once it has been opened successfully at rcstream,
 * and close it afterwards.  If syntax_only is TRUE, only allow the file
 * to contain color syntax commands: syntax, color, and icolor. */
824
825
826
827
828
void parse_rcfile(FILE *rcstream
#ifdef ENABLE_COLOR
	, bool syntax_only
#endif
	)
829
{
830
831
832
833
834
835
    char *buf = NULL;
    ssize_t len;
    size_t n;

    while ((len = getline(&buf, &n, rcstream)) > 0) {
	char *ptr, *keyword, *option;
836
837
	int set = 0;
	size_t i;
838

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
839
	/* Ignore the newline. */
840
841
	if (buf[len - 1] == '\n')
	    buf[len - 1] = '\0';
842
843
844

	lineno++;
	ptr = buf;
845
	while (isblank(*ptr))
846
847
	    ptr++;

848
849
850
	/* If we have a blank line or a comment, skip to the next
	 * line. */
	if (*ptr == '\0' || *ptr == '#')
851
852
	    continue;

853
	/* Otherwise, skip to the next space. */
854
855
856
	keyword = ptr;
	ptr = parse_next_word(ptr);

857
	/* Try to parse the keyword. */
858
	if (strcasecmp(keyword, "set") == 0) {
859
#ifdef ENABLE_COLOR
860
861
	    if (syntax_only)
		rcfile_error(
862
			N_("Command \"%s\" not allowed in included file"),
863
864
865
866
867
868
869
870
			keyword);
	    else
#endif
		set = 1;
	} else if (strcasecmp(keyword, "unset") == 0) {
#ifdef ENABLE_COLOR
	    if (syntax_only)
		rcfile_error(
871
			N_("Command \"%s\" not allowed in included file"),
872
873
874
875
876
877
878
879
880
			keyword);
	    else
#endif
		set = -1;
	}
#ifdef ENABLE_COLOR
	else if (strcasecmp(keyword, "include") == 0) {
	    if (syntax_only)
		rcfile_error(
881
			N_("Command \"%s\" not allowed in included file"),
882
883
884
			keyword);
	    else
		parse_include(ptr);
885
886
	} else if (strcasecmp(keyword, "syntax") == 0) {
	    if (endsyntax != NULL && endcolor == NULL)
887
		rcfile_error(N_("Syntax \"%s\" has no color commands"),
888
			endsyntax->desc);
Chris Allegretta's avatar
Chris Allegretta committed
889
	    parse_syntax(ptr);
890
891
892
	} else if (strcasecmp(keyword, "header") == 0)
	    parse_headers(ptr);
	else if (strcasecmp(keyword, "color") == 0)
893
894
895
	    parse_colors(ptr, FALSE);
	else if (strcasecmp(keyword, "icolor") == 0)
	    parse_colors(ptr, TRUE);
896
897
	else if (strcasecmp(keyword, "bind") == 0)
	    parse_keybinding(ptr);
898
899
#endif /* ENABLE_COLOR */
	else
900
	    rcfile_error(N_("Command \"%s\" not understood"), keyword);
901
902
903
904
905
906

	if (set == 0)
	    continue;

	if (*ptr == '\0') {
	    rcfile_error(N_("Missing flag"));
907
908
909
910
911
912
	    continue;
	}

	option = ptr;
	ptr = parse_next_word(ptr);

913
914
	for (i = 0; rcopts[i].name != NULL; i++) {
	    if (strcasecmp(option, rcopts[i].name) == 0) {
915
#ifdef DEBUG
916
917
918
919
920
921
922
923
924
		fprintf(stderr, "parse_rcfile(): name = \"%s\"\n", rcopts[i].name);
#endif
		if (set == 1) {
		    if (rcopts[i].flag != 0)
			/* This option has a flag, so it doesn't take an
			 * argument. */
			SET(rcopts[i].flag);
		    else {
			/* This option doesn't have a flag, so it takes
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
925
			 * an argument. */
926
			if (*ptr == '\0') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
927
			    rcfile_error(
928
				N_("Option \"%s\" requires an argument"),
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
929
				rcopts[i].name);
930
931
932
933
934
935
			    break;
			}
			option = ptr;
			if (*option == '"')
			    option++;
			ptr = parse_argument(ptr);
936

937
			option = mallocstrcpy(NULL, option);
Chris Allegretta's avatar
Chris Allegretta committed
938
#ifdef DEBUG
939
			fprintf(stderr, "option = \"%s\"\n", option);
Chris Allegretta's avatar
Chris Allegretta committed
940
#endif
941
942
943
944
945
946
947
948
949

			/* Make sure option is a valid multibyte
			 * string. */
			if (!is_valid_mbstring(option)) {
			    rcfile_error(
				N_("Option is not a valid multibyte string"));
			    break;
			}

Chris Allegretta's avatar
Chris Allegretta committed
950
#ifndef DISABLE_OPERATINGDIR
951
			if (strcasecmp(rcopts[i].name, "operatingdir") == 0)
952
			    operating_dir = option;
953
			else
Chris Allegretta's avatar
Chris Allegretta committed
954
#endif
955
#ifndef DISABLE_WRAPJUSTIFY
956
957
			if (strcasecmp(rcopts[i].name, "fill") == 0) {
			    if (!parse_num(option, &wrap_at)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
958
				rcfile_error(
959
					N_("Requested fill size \"%s\" is invalid"),
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
960
					option);
961
				wrap_at = -CHARS_FROM_EOL;
962
963
			    } else
				free(option);
964
			} else
Chris Allegretta's avatar
Chris Allegretta committed
965
#endif
966
#ifndef NANO_TINY
967
968
969
970
971
972
973
974
975
976
977
			if (strcasecmp(rcopts[i].name,
				"matchbrackets") == 0) {
			    matchbrackets = option;
			    if (has_blank_mbchars(matchbrackets)) {
				rcfile_error(
					N_("Non-blank characters required"));
				free(matchbrackets);
				matchbrackets = NULL;
			    }
			} else if (strcasecmp(rcopts[i].name,
				"whitespace") == 0) {
978
			    whitespace = option;
979
980
			    if (mbstrlen(whitespace) != 2 ||
				strlenpt(whitespace) != 2) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
981
982
				rcfile_error(
					N_("Two single-column characters required"));
983
984
				free(whitespace);
				whitespace = NULL;
985
986
987
			    } else {
				whitespace_len[0] =
					parse_mbchar(whitespace, NULL,
988
					NULL);
989
990
				whitespace_len[1] =
					parse_mbchar(whitespace +
991
					whitespace_len[0], NULL, NULL);
992
993
			    }
			} else
994
#endif
995
#ifndef DISABLE_JUSTIFY
996
			if (strcasecmp(rcopts[i].name, "punct") == 0) {
997
			    punct = option;
998
			    if (has_blank_mbchars(punct)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
999
				rcfile_error(
1000
					N_("Non-blank characters required"));
1001
1002
1003
				free(punct);
				punct = NULL;
			    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1004
1005
			} else if (strcasecmp(rcopts[i].name,
				"brackets") == 0) {
1006
			    brackets = option;
1007
			    if (has_blank_mbchars(brackets)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1008
				rcfile_error(
1009
					N_("Non-blank characters required"));
1010
1011
1012
				free(brackets);
				brackets = NULL;
			    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1013
1014
			} else if (strcasecmp(rcopts[i].name,
				"quotestr") == 0)
1015
			    quotestr = option;
1016
			else
1017
#endif
1018
#ifndef NANO_TINY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1019
1020
			if (strcasecmp(rcopts[i].name,
				"backupdir") == 0)
1021
			    backup_dir = option;
1022
			else
1023
#endif
1024
#ifndef DISABLE_SPELLER
1025
			if (strcasecmp(rcopts[i].name, "speller") == 0)
1026
			    alt_speller = option;
1027
1028
			else
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1029
1030
1031
1032
1033
			if (strcasecmp(rcopts[i].name,
				"tabsize") == 0) {
			    if (!parse_num(option, &tabsize) ||
				tabsize <= 0) {
				rcfile_error(
1034
					N_("Requested tab size \"%s\" is invalid"),
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1035
					option);
1036
				tabsize = -1;
1037
1038
			    } else
				free(option);
1039
			} else
1040
1041
			    assert(FALSE);
		    }
1042
#ifdef DEBUG
1043
		    fprintf(stderr, "flag = %ld\n", rcopts[i].flag);
1044
#endif
1045
1046
1047
		} else if (rcopts[i].flag != 0)
		    UNSET(rcopts[i].flag);
		else
1048
		    rcfile_error(N_("Cannot unset flag \"%s\""),
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1049
			rcopts[i].name);
1050
1051
1052
		/* Looks like we still need this specific hack for undo */
		if (strcasecmp(rcopts[i].name, "undo") == 0)
		    shortcut_init(0);
1053
		break;
1054
1055
	    }
	}
1056
	if (rcopts[i].name == NULL)
1057
	    rcfile_error(N_("Unknown flag \"%s\""), option);
1058
    }
1059

1060
#ifdef ENABLE_COLOR
1061
    if (endsyntax != NULL && endcolor == NULL)
1062
	rcfile_error(N_("Syntax \"%s\" has no color commands"),
1063
		endsyntax->desc);
1064
#endif
1065

Chris Allegretta's avatar
Chris Allegretta committed
1066
    free(buf);
1067
1068
    fclose(rcstream);
    lineno = 0;
1069

Chris Allegretta's avatar
Chris Allegretta committed
1070
    check_vitals_mapped();
1071
1072
1073
    return;
}

1074
/* The main rcfile function.  It tries to open the system-wide rcfile,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1075
 * followed by the current user's rcfile. */
1076
1077
void do_rcfile(void)
{
1078
    struct stat rcinfo;
1079
1080
    FILE *rcstream;

1081
    nanorc = mallocstrcpy(nanorc, SYSCONFDIR "/nanorc");
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091

    /* Don't open directories, character files, or block files. */
    if (stat(nanorc, &rcinfo) != -1) {
	if (S_ISDIR(rcinfo.st_mode) || S_ISCHR(rcinfo.st_mode) ||
		S_ISBLK(rcinfo.st_mode))
	    rcfile_error(S_ISDIR(rcinfo.st_mode) ?
		_("\"%s\" is a directory") :
		_("\"%s\" is a device file"), nanorc);
    }

1092
1093
1094
1095
#ifdef DEBUG
    fprintf(stderr, "Parsing file \"%s\"\n", nanorc);
#endif

1096
    /* Try to open the system-wide nanorc. */
1097
    rcstream = fopen(nanorc, "rb");
1098
    if (rcstream != NULL)
1099
1100
1101
1102
1103
	parse_rcfile(rcstream
#ifdef ENABLE_COLOR
		, FALSE
#endif
		);
1104

1105
#ifdef DISABLE_ROOTWRAPPING
1106
    /* We've already read SYSCONFDIR/nanorc, if it's there.  If we're
1107
1108
     * root, and --disable-wrapping-as-root is used, turn wrapping off
     * now. */
1109
1110
1111
    if (geteuid() == NANO_ROOT_UID)
	SET(NO_WRAP);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1112

1113
    get_homedir();
1114

1115
    if (homedir == NULL)
1116
	rcfile_error(N_("I can't find my home directory!  Wah!"));
1117
    else {
1118
1119
1120
1121
1122
#ifndef RCFILE_NAME
#define RCFILE_NAME ".nanorc"
#endif
	nanorc = charealloc(nanorc, strlen(homedir) + strlen(RCFILE_NAME) + 2);
	sprintf(nanorc, "%s/%s", homedir, RCFILE_NAME);
1123

1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
	/* Don't open directories, character files, or block files. */
	if (stat(nanorc, &rcinfo) != -1) {
	    if (S_ISDIR(rcinfo.st_mode) || S_ISCHR(rcinfo.st_mode) ||
		S_ISBLK(rcinfo.st_mode))
		rcfile_error(S_ISDIR(rcinfo.st_mode) ?
			_("\"%s\" is a directory") :
			_("\"%s\" is a device file"), nanorc);
	}

	/* Try to open the current user's nanorc. */
	rcstream = fopen(nanorc, "rb");
1135
	if (rcstream == NULL) {
1136
1137
	    /* Don't complain about the file's not existing. */
	    if (errno != ENOENT)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1138
1139
		rcfile_error(N_("Error reading %s: %s"), nanorc,
			strerror(errno));
1140
	} else
1141
1142
1143
1144
1145
	    parse_rcfile(rcstream
#ifdef ENABLE_COLOR
		, FALSE
#endif
		);
1146
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1147

1148
1149
    free(nanorc);
    nanorc = NULL;
1150

1151
1152
1153
1154
1155
1156
1157
1158
    if (errors && !ISSET(QUIET)) {
	errors = FALSE;
	fprintf(stderr,
		_("\nPress Enter to continue starting nano.\n"));
	while (getchar() != '\n')
	    ;
    }

1159
1160
1161
#ifdef ENABLE_COLOR
    set_colorpairs();
#endif
1162
1163
}

1164
#endif /* ENABLE_NANORC */