nano

nano with my custom patches
git clone git://bsandro.tech/nano
Log | Files | Refs | README | LICENSE

rcfile.c (49085B)


      1 /**************************************************************************
      2  *   rcfile.c  --  This file is part of GNU nano.                         *
      3  *                                                                        *
      4  *   Copyright (C) 2001-2011, 2013-2025 Free Software Foundation, Inc.    *
      5  *   Copyright (C) 2014 Mike Frysinger                                    *
      6  *   Copyright (C) 2019 Brand Huntsman                                    *
      7  *   Copyright (C) 2014-2021 Benno Schulenberg                            *
      8  *                                                                        *
      9  *   GNU nano is free software: you can redistribute it and/or modify     *
     10  *   it under the terms of the GNU General Public License as published    *
     11  *   by the Free Software Foundation, either version 3 of the License,    *
     12  *   or (at your option) any later version.                               *
     13  *                                                                        *
     14  *   GNU nano is distributed in the hope that it will be useful,          *
     15  *   but WITHOUT ANY WARRANTY; without even the implied warranty          *
     16  *   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.              *
     17  *   See the GNU General Public License for more details.                 *
     18  *                                                                        *
     19  *   You should have received a copy of the GNU General Public License    *
     20  *   along with this program.  If not, see https://gnu.org/licenses/.     *
     21  *                                                                        *
     22  **************************************************************************/
     23 
     24 #include "prototypes.h"
     25 
     26 #ifdef ENABLE_NANORC
     27 
     28 #include <ctype.h>
     29 #include <errno.h>
     30 #include <glob.h>
     31 #include <string.h>
     32 #include <unistd.h>
     33 
     34 #ifndef RCFILE_NAME
     35 #define HOME_RC_NAME  ".nanorc"
     36 #define RCFILE_NAME  "nanorc"
     37 #else
     38 #define HOME_RC_NAME  RCFILE_NAME
     39 #endif
     40 
     41 static const rcoption rcopts[] = {
     42 	{"boldtext", BOLD_TEXT},
     43 #ifdef ENABLE_JUSTIFY
     44 	{"brackets", 0},
     45 #endif
     46 #ifdef ENABLE_WRAPPING
     47 	{"breaklonglines", BREAK_LONG_LINES},
     48 #endif
     49 	{"casesensitive", CASE_SENSITIVE},
     50 	{"constantshow", CONSTANT_SHOW},
     51 #ifdef ENABLED_WRAPORJUSTIFY
     52 	{"fill", 0},
     53 #endif
     54 #ifdef ENABLE_HISTORIES
     55 	{"historylog", HISTORYLOG},
     56 #endif
     57 #ifdef ENABLE_LINENUMBERS
     58 	{"linenumbers", LINE_NUMBERS},
     59 #endif
     60 #ifdef HAVE_LIBMAGIC
     61 	{"magic", USE_MAGIC},
     62 #endif
     63 #ifdef ENABLE_MOUSE
     64 	{"mouse", USE_MOUSE},
     65 #endif
     66 #ifdef ENABLE_MULTIBUFFER
     67 	{"multibuffer", MULTIBUFFER},
     68 #endif
     69 	{"nohelp", NO_HELP},
     70 	{"nonewlines", NO_NEWLINES},
     71 #ifdef ENABLE_WRAPPING
     72 	{"nowrap", NO_WRAP},  /* Deprecated; remove in 2027. */
     73 #endif
     74 #ifdef ENABLE_OPERATINGDIR
     75 	{"operatingdir", 0},
     76 #endif
     77 #ifdef ENABLE_HISTORIES
     78 	{"positionlog", POSITIONLOG},
     79 #endif
     80 	{"preserve", PRESERVE},
     81 #ifdef ENABLE_JUSTIFY
     82 	{"punct", 0},
     83 	{"quotestr", 0},
     84 #endif
     85 	{"quickblank", QUICK_BLANK},
     86 	{"rawsequences", RAW_SEQUENCES},
     87 	{"rebinddelete", REBIND_DELETE},
     88 	{"regexp", USE_REGEXP},
     89 	{"saveonexit", SAVE_ON_EXIT},
     90 #ifdef ENABLE_SPELLER
     91 	{"speller", 0},
     92 #endif
     93 #ifndef NANO_TINY
     94 	{"afterends", AFTER_ENDS},
     95 	{"allow_insecure_backup", INSECURE_BACKUP},
     96 	{"atblanks", AT_BLANKS},
     97 	{"autoindent", AUTOINDENT},
     98 	{"backup", MAKE_BACKUP},
     99 	{"backupdir", 0},
    100 	{"bookstyle", BOOKSTYLE},
    101 	{"colonparsing", COLON_PARSING},
    102 	{"cutfromcursor", CUT_FROM_CURSOR},
    103 	{"emptyline", EMPTY_LINE},
    104 	{"guidestripe", 0},
    105 	{"indicator", INDICATOR},
    106 	{"jumpyscrolling", JUMPY_SCROLLING},
    107 	{"locking", LOCKING},
    108 	{"matchbrackets", 0},
    109 	{"minibar", MINIBAR},
    110 	{"noconvert", NO_CONVERT},
    111 	{"showcursor", SHOW_CURSOR},
    112 	{"smarthome", SMART_HOME},
    113 	{"softwrap", SOFTWRAP},
    114 	{"stateflags", STATEFLAGS},
    115 	{"tabsize", 0},
    116 	{"tabstospaces", TABS_TO_SPACES},
    117 	{"trimblanks", TRIM_BLANKS},
    118 	{"unix", MAKE_IT_UNIX},
    119 	{"whitespace", 0},
    120 	{"whitespacedisplay", WHITESPACE_DISPLAY},
    121 	{"wordbounds", WORD_BOUNDS},
    122 	{"wordchars", 0},
    123 	{"zap", LET_THEM_ZAP},
    124 	{"zero", ZERO},
    125 #endif
    126 #ifdef ENABLE_COLOR
    127 	{"titlecolor", 0},
    128 	{"numbercolor", 0},
    129 	{"stripecolor", 0},
    130 	{"scrollercolor", 0},
    131 	{"selectedcolor", 0},
    132 	{"spotlightcolor", 0},
    133 	{"minicolor", 0},
    134 	{"promptcolor", 0},
    135 	{"statuscolor", 0},
    136 	{"errorcolor", 0},
    137 	{"keycolor", 0},
    138 	{"functioncolor", 0},
    139 #endif
    140 	{NULL, 0}
    141 };
    142 
    143 static size_t lineno = 0;
    144 		/* The line number of the last encountered error. */
    145 static char *nanorc = NULL;
    146 		/* The path to the rcfile we're parsing. */
    147 #ifdef ENABLE_COLOR
    148 static bool opensyntax = FALSE;
    149 		/* Whether we're allowed to add to the last syntax.  When a file ends,
    150 		 * or when a new syntax command is seen, this bool becomes FALSE. */
    151 static syntaxtype *live_syntax;
    152 		/* The syntax that is currently being parsed. */
    153 static bool seen_color_command = FALSE;
    154 		/* Whether a syntax definition contains any color commands. */
    155 static colortype *lastcolor = NULL;
    156 		/* The end of the color list for the current syntax. */
    157 #endif
    158 #endif /* ENABLE_NANORC */
    159 
    160 #if defined(ENABLE_NANORC) || defined(ENABLE_HISTORIES)
    161 static linestruct *errors_head = NULL;
    162 static linestruct *errors_tail = NULL;
    163 		/* Beginning and end of a list of errors in rcfiles, if any. */
    164 
    165 /* Send the gathered error messages (if any) to the terminal. */
    166 void display_rcfile_errors(void)
    167 {
    168 	for (linestruct *error = errors_head; error != NULL; error = error->next)
    169 		fprintf(stderr, "%s\n", error->data);
    170 }
    171 
    172 #define MAXSIZE  (PATH_MAX + 200)
    173 
    174 /* Store the given error message in a linked list, to be printed upon exit. */
    175 void jot_error(const char *msg, ...)
    176 {
    177 	linestruct *error = make_new_node(errors_tail);
    178 	char textbuf[MAXSIZE];
    179 	int length = 0;
    180 	va_list ap;
    181 
    182 	if (errors_head == NULL)
    183 		errors_head = error;
    184 	else
    185 		errors_tail->next = error;
    186 	errors_tail = error;
    187 
    188 	if (startup_problem == NULL) {
    189 #ifdef ENABLE_NANORC
    190 		if (nanorc != NULL) {
    191 			snprintf(textbuf, MAXSIZE, _("Mistakes in '%s'"), nanorc);
    192 			startup_problem = copy_of(textbuf);
    193 		} else
    194 #endif
    195 			startup_problem = copy_of(_("Problems with history file"));
    196 	}
    197 #ifdef ENABLE_NANORC
    198 	if (lineno > 0)
    199 		length = snprintf(textbuf, MAXSIZE, _("Error in %s on line %zu: "),
    200 											nanorc, lineno);
    201 #endif
    202 	va_start(ap, msg);
    203 	length += vsnprintf(textbuf + length, MAXSIZE - length, _(msg), ap);
    204 	va_end(ap);
    205 
    206 	error->data = nmalloc(length + 1);
    207 	sprintf(error->data, "%s", textbuf);
    208 }
    209 #endif /* ENABLE_NANORC || ENABLE_HISTORIES */
    210 
    211 #ifdef ENABLE_NANORC
    212 /* Interpret a function string given in the rc file, and return a
    213  * shortcut record with the corresponding function filled in. */
    214 keystruct *strtosc(const char *input)
    215 {
    216 	keystruct *s = nmalloc(sizeof(keystruct));
    217 
    218 #ifndef NANO_TINY
    219 	s->toggle = 0;
    220 #endif
    221 
    222 	if (!strcmp(input, "cancel"))
    223 		s->func = do_cancel;
    224 #ifdef ENABLE_HELP
    225 	else if (!strcmp(input, "help"))
    226 		s->func = do_help;
    227 #endif
    228 	else if (!strcmp(input, "exit"))
    229 		s->func = do_exit;
    230 	else if (!strcmp(input, "discardbuffer"))
    231 		s->func = discard_buffer;
    232 	else if (!strcmp(input, "writeout"))
    233 		s->func = do_writeout;
    234 	else if (!strcmp(input, "savefile"))
    235 		s->func = do_savefile;
    236 	else if (!strcmp(input, "insert"))
    237 		s->func = do_insertfile;
    238 	else if (!strcmp(input, "whereis"))
    239 		s->func = do_search_forward;
    240 	else if (!strcmp(input, "wherewas"))
    241 		s->func = do_search_backward;
    242 	else if (!strcmp(input, "findprevious"))
    243 		s->func = do_findprevious;
    244 	else if (!strcmp(input, "findnext"))
    245 		s->func = do_findnext;
    246 	else if (!strcmp(input, "replace"))
    247 		s->func = do_replace;
    248 	else if (!strcmp(input, "cut"))
    249 		s->func = cut_text;
    250 	else if (!strcmp(input, "copy"))
    251 		s->func = copy_text;
    252 	else if (!strcmp(input, "paste"))
    253 		s->func = paste_text;
    254 #ifndef NANO_TINY
    255 	else if (!strcmp(input, "execute"))
    256 		s->func = do_execute;
    257 	else if (!strcmp(input, "cutrestoffile"))
    258 		s->func = cut_till_eof;
    259 	else if (!strcmp(input, "zap"))
    260 		s->func = zap_text;
    261 	else if (!strcmp(input, "mark"))
    262 		s->func = do_mark;
    263 #endif
    264 #ifdef ENABLE_SPELLER
    265 	else if (!strcmp(input, "tospell") ||
    266 	         !strcmp(input, "speller"))
    267 		s->func = do_spell;
    268 #endif
    269 #ifdef ENABLE_LINTER
    270 	else if (!strcmp(input, "linter"))
    271 		s->func = do_linter;
    272 #endif
    273 #ifdef ENABLE_FORMATTER
    274 	else if (!strcmp(input, "formatter"))
    275 		s->func = do_formatter;
    276 #endif
    277 	else if (!strcmp(input, "location"))
    278 		s->func = report_cursor_position;
    279 	else if (!strcmp(input, "gotoline"))
    280 		s->func = do_gotolinecolumn;
    281 #ifdef ENABLE_JUSTIFY
    282 	else if (!strcmp(input, "justify"))
    283 		s->func = do_justify;
    284 	else if (!strcmp(input, "fulljustify"))
    285 		s->func = do_full_justify;
    286 	else if (!strcmp(input, "beginpara"))
    287 		s->func = to_para_begin;
    288 	else if (!strcmp(input, "endpara"))
    289 		s->func = to_para_end;
    290 #endif
    291 #ifdef ENABLE_COMMENT
    292 	else if (!strcmp(input, "comment"))
    293 		s->func = do_comment;
    294 #endif
    295 #ifdef ENABLE_WORDCOMPLETION
    296 	else if (!strcmp(input, "complete"))
    297 		s->func = complete_a_word;
    298 #endif
    299 #ifndef NANO_TINY
    300 	else if (!strcmp(input, "indent"))
    301 		s->func = do_indent;
    302 	else if (!strcmp(input, "unindent"))
    303 		s->func = do_unindent;
    304 	else if (!strcmp(input, "chopwordleft"))
    305 		s->func = chop_previous_word;
    306 	else if (!strcmp(input, "chopwordright"))
    307 		s->func = chop_next_word;
    308 	else if (!strcmp(input, "findbracket"))
    309 		s->func = do_find_bracket;
    310 	else if (!strcmp(input, "wordcount"))
    311 		s->func = count_lines_words_and_characters;
    312 	else if (!strcmp(input, "recordmacro"))
    313 		s->func = record_macro;
    314 	else if (!strcmp(input, "runmacro"))
    315 		s->func = run_macro;
    316 	else if (!strcmp(input, "anchor"))
    317 		s->func = put_or_lift_anchor;
    318 	else if (!strcmp(input, "prevanchor"))
    319 		s->func = to_prev_anchor;
    320 	else if (!strcmp(input, "nextanchor"))
    321 		s->func = to_next_anchor;
    322 	else if (!strcmp(input, "undo"))
    323 		s->func = do_undo;
    324 	else if (!strcmp(input, "redo"))
    325 		s->func = do_redo;
    326 	else if (!strcmp(input, "suspend"))
    327 		s->func = do_suspend;
    328 #endif
    329 	else if (!strcmp(input, "left") ||
    330 	         !strcmp(input, "back"))
    331 		s->func = do_left;
    332 	else if (!strcmp(input, "right") ||
    333 	         !strcmp(input, "forward"))
    334 		s->func = do_right;
    335 	else if (!strcmp(input, "up") ||
    336 	         !strcmp(input, "prevline"))
    337 		s->func = do_up;
    338 	else if (!strcmp(input, "down") ||
    339 	         !strcmp(input, "nextline"))
    340 		s->func = do_down;
    341 #if !defined(NANO_TINY) || defined(ENABLE_HELP)
    342 	else if (!strcmp(input, "scrollup"))
    343 		s->func = do_scroll_up;
    344 	else if (!strcmp(input, "scrolldown"))
    345 		s->func = do_scroll_down;
    346 #endif
    347 	else if (!strcmp(input, "prevword"))
    348 		s->func = to_prev_word;
    349 	else if (!strcmp(input, "nextword"))
    350 		s->func = to_next_word;
    351 	else if (!strcmp(input, "home"))
    352 		s->func = do_home;
    353 	else if (!strcmp(input, "end"))
    354 		s->func = do_end;
    355 	else if (!strcmp(input, "prevblock"))
    356 		s->func = to_prev_block;
    357 	else if (!strcmp(input, "nextblock"))
    358 		s->func = to_next_block;
    359 #ifndef NANO_TINY
    360 	else if (!strcmp(input, "toprow"))
    361 		s->func = to_top_row;
    362 	else if (!strcmp(input, "bottomrow"))
    363 		s->func = to_bottom_row;
    364 	else if (!strcmp(input, "center"))
    365 		s->func = do_center;
    366 	else if (!strcmp(input, "cycle"))
    367 		s->func = do_cycle;
    368 #endif
    369 	else if (!strcmp(input, "pageup") ||
    370 	         !strcmp(input, "prevpage"))
    371 		s->func = do_page_up;
    372 	else if (!strcmp(input, "pagedown") ||
    373 	         !strcmp(input, "nextpage"))
    374 		s->func = do_page_down;
    375 	else if (!strcmp(input, "firstline"))
    376 		s->func = to_first_line;
    377 	else if (!strcmp(input, "lastline"))
    378 		s->func = to_last_line;
    379 #ifdef ENABLE_MULTIBUFFER
    380 	else if (!strcmp(input, "prevbuf"))
    381 		s->func = switch_to_prev_buffer;
    382 	else if (!strcmp(input, "nextbuf"))
    383 		s->func = switch_to_next_buffer;
    384 #endif
    385 	else if (!strcmp(input, "verbatim"))
    386 		s->func = do_verbatim_input;
    387 	else if (!strcmp(input, "tab"))
    388 		s->func = do_tab;
    389 	else if (!strcmp(input, "enter"))
    390 		s->func = do_enter;
    391 	else if (!strcmp(input, "delete"))
    392 		s->func = do_delete;
    393 	else if (!strcmp(input, "backspace"))
    394 		s->func = do_backspace;
    395 	else if (!strcmp(input, "refresh"))
    396 		s->func = full_refresh;
    397 	else if (!strcmp(input, "casesens"))
    398 		s->func = case_sens_void;
    399 	else if (!strcmp(input, "regexp"))
    400 		s->func = regexp_void;
    401 	else if (!strcmp(input, "backwards"))
    402 		s->func = backwards_void;
    403 	else if (!strcmp(input, "flipreplace"))
    404 		s->func = flip_replace;
    405 	else if (!strcmp(input, "flipgoto"))
    406 		s->func = flip_goto;
    407 #ifdef ENABLE_HISTORIES
    408 	else if (!strcmp(input, "older"))
    409 		s->func = get_older_item;
    410 	else if (!strcmp(input, "newer"))
    411 		s->func = get_newer_item;
    412 #endif
    413 #ifndef NANO_TINY
    414 	else if (!strcmp(input, "dosformat"))
    415 		s->func = dos_format;
    416 	else if (!strcmp(input, "macformat"))
    417 		s->func = mac_format;
    418 	else if (!strcmp(input, "append"))
    419 		s->func = append_it;
    420 	else if (!strcmp(input, "prepend"))
    421 		s->func = prepend_it;
    422 	else if (!strcmp(input, "backup"))
    423 		s->func = back_it_up;
    424 	else if (!strcmp(input, "flipexecute"))
    425 		s->func = flip_execute;
    426 	else if (!strcmp(input, "flippipe"))
    427 		s->func = flip_pipe;
    428 	else if (!strcmp(input, "flipconvert"))
    429 		s->func = flip_convert;
    430 #endif
    431 #ifdef ENABLE_MULTIBUFFER
    432 	else if (!strcmp(input, "flipnewbuffer"))
    433 		s->func = flip_newbuffer;
    434 #endif
    435 #ifdef ENABLE_BROWSER
    436 	else if (!strcmp(input, "tofiles") ||
    437 	         !strcmp(input, "browser"))
    438 		s->func = to_files;
    439 	else if (!strcmp(input, "gotodir"))
    440 		s->func = goto_dir;
    441 	else if (!strcmp(input, "firstfile"))
    442 		s->func = to_first_file;
    443 	else if (!strcmp(input, "lastfile"))
    444 		s->func = to_last_file;
    445 #endif
    446 	else {
    447 #ifndef NANO_TINY
    448 		s->func = do_toggle;
    449 		if (!strcmp(input, "nohelp"))
    450 			s->toggle = NO_HELP;
    451 		else if (!strcmp(input, "zero"))
    452 			s->toggle = ZERO;
    453 		else if (!strcmp(input, "constantshow"))
    454 			s->toggle = CONSTANT_SHOW;
    455 		else if (!strcmp(input, "softwrap"))
    456 			s->toggle = SOFTWRAP;
    457 #ifdef ENABLE_LINENUMBERS
    458 		else if (!strcmp(input, "linenumbers"))
    459 			s->toggle = LINE_NUMBERS;
    460 #endif
    461 		else if (!strcmp(input, "whitespacedisplay"))
    462 			s->toggle = WHITESPACE_DISPLAY;
    463 #ifdef ENABLE_COLOR
    464 		else if (!strcmp(input, "nosyntax"))
    465 			s->toggle = NO_SYNTAX;
    466 #endif
    467 		else if (!strcmp(input, "smarthome"))
    468 			s->toggle = SMART_HOME;
    469 		else if (!strcmp(input, "autoindent"))
    470 			s->toggle = AUTOINDENT;
    471 		else if (!strcmp(input, "cutfromcursor"))
    472 			s->toggle = CUT_FROM_CURSOR;
    473 #ifdef ENABLE_WRAPPING
    474 		else if (!strcmp(input, "breaklonglines"))
    475 			s->toggle = BREAK_LONG_LINES;
    476 #endif
    477 		else if (!strcmp(input, "tabstospaces"))
    478 			s->toggle = TABS_TO_SPACES;
    479 #ifdef ENABLE_MOUSE
    480 		else if (!strcmp(input, "mouse"))
    481 			s->toggle = USE_MOUSE;
    482 #endif
    483 		else
    484 #endif /* !NANO_TINY */
    485 		{
    486 			free(s);
    487 			return NULL;
    488 		}
    489 	}
    490 	return s;
    491 }
    492 
    493 #define NUMBER_OF_MENUS  16
    494 char *menunames[NUMBER_OF_MENUS] = { "main", "search", "replace", "replacewith",
    495 									"yesno", "gotoline", "writeout", "insert",
    496 									"execute", "help", "spell", "linter",
    497 									"browser", "whereisfile", "gotodir",
    498 									"all" };
    499 int menusymbols[NUMBER_OF_MENUS] = { MMAIN, MWHEREIS, MREPLACE, MREPLACEWITH,
    500 									MYESNO, MGOTOLINE, MWRITEFILE, MINSERTFILE,
    501 									MEXECUTE, MHELP, MSPELL, MLINTER,
    502 									MBROWSER, MWHEREISFILE, MGOTODIR,
    503 									MMOST|MBROWSER|MHELP|MYESNO };
    504 
    505 /* Return the symbol that corresponds to the given menu name. */
    506 int name_to_menu(const char *name)
    507 {
    508 	int index = -1;
    509 
    510 	while (++index < NUMBER_OF_MENUS)
    511 		if (strcmp(name, menunames[index]) == 0)
    512 			return menusymbols[index];
    513 
    514 	return 0;
    515 }
    516 
    517 /* Return the name that corresponds to the given menu symbol. */
    518 char *menu_to_name(int menu)
    519 {
    520 	int index = -1;
    521 
    522 	while (++index < NUMBER_OF_MENUS)
    523 		if (menusymbols[index] == menu)
    524 			return menunames[index];
    525 
    526 	return "boooo";
    527 }
    528 
    529 /* Parse the next word from the string (if any), null-terminate it,
    530  * and return a pointer to the next word.  The returned pointer will
    531  * point to '\0' when end-of-line was reached. */
    532 char *parse_next_word(char *ptr)
    533 {
    534 	while (!isblank((unsigned char)*ptr) && *ptr != '\0')
    535 		ptr++;
    536 
    537 	if (*ptr == '\0')
    538 		return ptr;
    539 
    540 	/* Null-terminate and advance ptr. */
    541 	*ptr++ = '\0';
    542 
    543 	while (isblank((unsigned char)*ptr))
    544 		ptr++;
    545 
    546 	return ptr;
    547 }
    548 
    549 /* Parse an argument (optionally enveloped in double quotes).  When the
    550  * argument starts with a ", then the last " of the line indicates its
    551  * end -- meaning that an argument can contain "'s either way. */
    552 char *parse_argument(char *ptr)
    553 {
    554 	const char *ptr_save = ptr;
    555 	char *last_quote = NULL;
    556 
    557 	if (*ptr != '"')
    558 		return parse_next_word(ptr);
    559 
    560 	while (*ptr != '\0') {
    561 		if (*++ptr == '"')
    562 			last_quote = ptr;
    563 	}
    564 
    565 	if (last_quote == NULL) {
    566 		jot_error(N_("Argument '%s' has an unterminated \""), ptr_save);
    567 		return NULL;
    568 	}
    569 
    570 	*last_quote = '\0';
    571 	ptr = last_quote + 1;
    572 
    573 	while (isblank((unsigned char)*ptr))
    574 		ptr++;
    575 
    576 	return ptr;
    577 }
    578 
    579 #ifdef ENABLE_COLOR
    580 /* Advance over one regular expression in the line starting at ptr,
    581  * null-terminate it, and return a pointer to the succeeding text. */
    582 char *parse_next_regex(char *ptr)
    583 {
    584 	char *starting_point = ptr;
    585 
    586 	if (*(ptr - 1) != '"') {
    587 		jot_error(N_("Regex strings must begin and end with a \" character"));
    588 		return NULL;
    589 	}
    590 
    591 	/* Continue until the end of the line, or until a double quote followed
    592 	 * by end-of-line or a blank. */
    593 	while (*ptr != '\0' && (*ptr != '"' ||
    594 						(ptr[1] != '\0' && !isblank((unsigned char)ptr[1]))))
    595 		ptr++;
    596 
    597 	if (*ptr == '\0') {
    598 		jot_error(N_("Regex strings must begin and end with a \" character"));
    599 		return NULL;
    600 	}
    601 
    602 	if (ptr == starting_point) {
    603 		jot_error(N_("Empty regex string"));
    604 		return NULL;
    605 	}
    606 
    607 	/* Null-terminate the regex and skip until the next non-blank. */
    608 	*ptr++ = '\0';
    609 
    610 	while (isblank((unsigned char)*ptr))
    611 		ptr++;
    612 
    613 	return ptr;
    614 }
    615 
    616 /* Compile the given regular expression and store the result in packed (when
    617  * this pointer is not NULL).  Return TRUE when the expression is valid. */
    618 bool compile(const char *expression, int rex_flags, regex_t **packed)
    619 {
    620 	regex_t *compiled = nmalloc(sizeof(regex_t));
    621 	int outcome = regcomp(compiled, expression, rex_flags);
    622 
    623 	if (outcome != 0) {
    624 		size_t length = regerror(outcome, compiled, NULL, 0);
    625 		char *message = nmalloc(length);
    626 
    627 		regerror(outcome, compiled, message, length);
    628 		jot_error(N_("Bad regex \"%s\": %s"), expression, message);
    629 		free(message);
    630 
    631 		regfree(compiled);
    632 		free(compiled);
    633 	} else
    634 		*packed = compiled;
    635 
    636 	return (outcome == 0);
    637 }
    638 
    639 /* Parse the next syntax name and its possible extension regexes from the
    640  * line at ptr, and add it to the global linked list of color syntaxes. */
    641 void begin_new_syntax(char *ptr)
    642 {
    643 	char *nameptr = ptr;
    644 
    645 	/* Check that the syntax name is not empty. */
    646 	if (*ptr == '\0' || (*ptr == '"' &&
    647 						(*(ptr + 1) == '\0' || *(ptr + 1) == '"'))) {
    648 		jot_error(N_("Missing syntax name"));
    649 		return;
    650 	}
    651 
    652 	ptr = parse_next_word(ptr);
    653 
    654 	/* Check that quotes around the name are either paired or absent. */
    655 	if ((*nameptr == '\x22') ^ (nameptr[strlen(nameptr) - 1] == '\x22')) {
    656 		jot_error(N_("Unpaired quote in syntax name"));
    657 		return;
    658 	}
    659 
    660 	/* If the name is quoted, strip the quotes. */
    661 	if (*nameptr == '\x22') {
    662 		nameptr++;
    663 		nameptr[strlen(nameptr) - 1] = '\0';
    664 	}
    665 
    666 	/* Redefining the "none" syntax is not allowed. */
    667 	if (strcmp(nameptr, "none") == 0) {
    668 		jot_error(N_("The \"none\" syntax is reserved"));
    669 		return;
    670 	}
    671 
    672 	/* Initialize a new syntax struct. */
    673 	live_syntax = nmalloc(sizeof(syntaxtype));
    674 	live_syntax->name = copy_of(nameptr);
    675 	live_syntax->filename = copy_of(nanorc);
    676 	live_syntax->lineno = lineno;
    677 	live_syntax->augmentations = NULL;
    678 	live_syntax->extensions = NULL;
    679 	live_syntax->headers = NULL;
    680 	live_syntax->magics = NULL;
    681 	live_syntax->linter = NULL;
    682 	live_syntax->formatter = NULL;
    683 	live_syntax->tabstring = NULL;
    684 #ifdef ENABLE_COMMENT
    685 	live_syntax->comment = copy_of(GENERAL_COMMENT_CHARACTER);
    686 #endif
    687 	live_syntax->color = NULL;
    688 	live_syntax->multiscore = 0;
    689 
    690 	/* Hook the new syntax in at the top of the list. */
    691 	live_syntax->next = syntaxes;
    692 	syntaxes = live_syntax;
    693 
    694 	opensyntax = TRUE;
    695 	seen_color_command = FALSE;
    696 
    697 	/* The default syntax should have no associated extensions. */
    698 	if (strcmp(live_syntax->name, "default") == 0 && *ptr != '\0') {
    699 		jot_error(N_("The \"default\" syntax does not accept extensions"));
    700 		return;
    701 	}
    702 
    703 	/* If there seem to be extension regexes, pick them up. */
    704 	if (*ptr != '\0')
    705 		grab_and_store("extension", ptr, &live_syntax->extensions);
    706 }
    707 #endif /* ENABLE_COLOR */
    708 
    709 /* Verify that a syntax definition contains at least one color command. */
    710 void check_for_nonempty_syntax(void)
    711 {
    712 #ifdef ENABLE_COLOR
    713 	if (opensyntax && !seen_color_command) {
    714 		size_t current_lineno = lineno;
    715 
    716 		lineno = live_syntax->lineno;
    717 		jot_error(N_("Syntax \"%s\" has no color commands"), live_syntax->name);
    718 		lineno = current_lineno;
    719 	}
    720 
    721 	opensyntax = FALSE;
    722 #endif
    723 }
    724 
    725 /* Return TRUE when the given function is present in almost all menus. */
    726 bool is_universal(void (*func)(void))
    727 {
    728 	return (func == do_left || func == do_right ||
    729 		func == do_home || func == do_end ||
    730 #ifndef NANO_TINY
    731 		func == to_prev_word || func == to_next_word ||
    732 #endif
    733 		func == do_delete || func == do_backspace ||
    734 		func == cut_text || func == paste_text ||
    735 		func == do_tab || func == do_enter || func == do_verbatim_input);
    736 }
    737 
    738 /* Bind or unbind a key combo, to or from a function. */
    739 void parse_binding(char *ptr, bool dobind)
    740 {
    741 	char *keycopy, *keyptr, *menuptr;
    742 	keystruct *newsc = NULL;
    743 	char *funcptr = NULL;
    744 	int keycode, menu;
    745 	int mask = 0;
    746 
    747 	check_for_nonempty_syntax();
    748 
    749 	if (*ptr == '\0') {
    750 		jot_error(N_("Missing key name"));
    751 		return;
    752 	}
    753 
    754 	keyptr = ptr;
    755 	ptr = parse_next_word(ptr);
    756 	keycopy = copy_of(keyptr);
    757 
    758 	/* Uppercase either the second or the first character of the key name. */
    759 	if (keycopy[0] == '^') {
    760 		if ('a' <= keycopy[1] && keycopy[1] <= 'z')
    761 			keycopy[1] &= 0x5F;
    762 	} else if ('a' <= keycopy[0] && keycopy[0] <= 'z')
    763 		keycopy[0] &= 0x5F;
    764 
    765 	/* Verify that the key name is not too short. */
    766 	if (keycopy[1] == '\0' || (keycopy[0] == 'M' && keycopy[2] == '\0')) {
    767 		jot_error(N_("Key name %s is invalid"), keycopy);
    768 		goto free_things;
    769 	}
    770 
    771 	keycode = keycode_from_string(keycopy);
    772 
    773 	if (keycode < 0) {
    774 		jot_error(N_("Key name %s is invalid"), keycopy);
    775 		goto free_things;
    776 	}
    777 
    778 	if (dobind) {
    779 		funcptr = ptr;
    780 		ptr = parse_argument(ptr);
    781 
    782 		if (funcptr[0] == '\0') {
    783 			jot_error(N_("Must specify a function to bind the key to"));
    784 			goto free_things;
    785 		} else if (ptr == NULL)
    786 			goto free_things;
    787 	}
    788 
    789 	menuptr = ptr;
    790 	ptr = parse_next_word(ptr);
    791 
    792 	if (menuptr[0] == '\0') {
    793 		/* TRANSLATORS: Do not translate the word "all". */
    794 		jot_error(N_("Must specify a menu (or \"all\") "
    795 						"in which to bind/unbind the key"));
    796 		goto free_things;
    797 	}
    798 
    799 	menu = name_to_menu(menuptr);
    800 	if (menu == 0) {
    801 		jot_error(N_("Unknown menu: %s"), menuptr);
    802 		goto free_things;
    803 	}
    804 
    805 	if (dobind) {
    806 		/* If the thing to bind starts with a double quote, it is a string,
    807 		 * otherwise it is the name of a function. */
    808 		if (*funcptr == '"') {
    809 			newsc = nmalloc(sizeof(keystruct));
    810 			newsc->func = (functionptrtype)implant;
    811 			newsc->expansion = copy_of(funcptr + 1);
    812 #ifndef NANO_TINY
    813 			newsc->toggle = 0;
    814 #endif
    815 		} else
    816 			newsc = strtosc(funcptr);
    817 
    818 		if (newsc == NULL) {
    819 			jot_error(N_("Unknown function: %s"), funcptr);
    820 			goto free_things;
    821 		}
    822 	}
    823 
    824 	/* Wipe the given shortcut from the given menu. */
    825 	for (keystruct *s = sclist; s != NULL; s = s->next)
    826 		if ((s->menus & menu) && s->keycode == keycode)
    827 			s->menus &= ~menu;
    828 
    829 	/* When unbinding, we are done now. */
    830 	if (!dobind)
    831 		goto free_things;
    832 
    833 	/* Limit the given menu to those where the function exists;
    834 	 * first handle five special cases, then the general case. */
    835 	if (is_universal(newsc->func))
    836 		menu &= MMOST|MBROWSER;
    837 #ifndef NANO_TINY
    838 	else if (newsc->func == do_toggle && newsc->toggle == NO_HELP)
    839 		menu &= (MMOST|MBROWSER|MYESNO) & ~MFINDINHELP;
    840 	else if (newsc->func == do_toggle)
    841 		menu &= MMAIN;
    842 #endif
    843 	else if (newsc->func == full_refresh)
    844 		menu &= MMOST|MBROWSER|MHELP|MYESNO;
    845 	else if (newsc->func == (functionptrtype)implant)
    846 		menu &= MMOST|MBROWSER|MHELP;
    847 	else {
    848 		/* Tally up the menus where the function exists. */
    849 		for (funcstruct *f = allfuncs; f != NULL; f = f->next)
    850 			if (f->func == newsc->func)
    851 				mask |= f->menus;
    852 
    853 		menu &= mask;
    854 	}
    855 
    856 	if (!menu) {
    857 		if (!ISSET(RESTRICTED) && !ISSET(VIEW_MODE))
    858 			jot_error(N_("Function '%s' does not exist in menu '%s'"),
    859 								funcptr, menuptr);
    860 		goto free_things;
    861 	}
    862 
    863 	newsc->menus = menu;
    864 	newsc->keystr = keycopy;
    865 	newsc->keycode = keycode;
    866 
    867 	/* Disallow rebinding <Esc> (^[). */
    868 	if (newsc->keycode == ESC_CODE) {
    869 		jot_error(N_("Keystroke %s may not be rebound"), keycopy);
    870   free_things:
    871 		free(keycopy);
    872 		free(newsc);
    873 		return;
    874 	}
    875 
    876 #ifndef NANO_TINY
    877 	/* If this is a toggle, find and copy its sequence number. */
    878 	if (newsc->func == do_toggle) {
    879 		for (keystruct *s = sclist; s != NULL; s = s->next)
    880 			if (s->func == do_toggle && s->toggle == newsc->toggle)
    881 				newsc->ordinal = s->ordinal;
    882 	} else
    883 		newsc->ordinal = 0;
    884 #endif
    885 	/* Add the new shortcut at the start of the list. */
    886 	newsc->next = sclist;
    887 	sclist = newsc;
    888 }
    889 
    890 /* Verify that the given file exists, is not a folder nor a device. */
    891 bool is_good_file(char *file)
    892 {
    893 	struct stat rcinfo;
    894 
    895 	/* First check that the file exists and is readable. */
    896 	if (access(file, R_OK) != 0)
    897 		return FALSE;
    898 
    899 	/* If the thing exists, it may be neither a directory nor a device. */
    900 	if (stat(file, &rcinfo) != -1 && (S_ISDIR(rcinfo.st_mode) ||
    901 				S_ISCHR(rcinfo.st_mode) || S_ISBLK(rcinfo.st_mode))) {
    902 		jot_error(S_ISDIR(rcinfo.st_mode) ? N_("\"%s\" is a directory") :
    903 										N_("\"%s\" is a device file"), file);
    904 		return FALSE;
    905 	} else
    906 		return TRUE;
    907 }
    908 
    909 #ifdef ENABLE_COLOR
    910 /* Partially parse the syntaxes in the given file, or (when syntax
    911  * is not NULL) fully parse one specific syntax from the file. */
    912 void parse_one_include(char *file, syntaxtype *syntax)
    913 {
    914 	char *was_nanorc = nanorc;
    915 	size_t was_lineno = lineno;
    916 	augmentstruct *extra;
    917 	FILE *rcstream;
    918 
    919 	/* Don't open directories, character files, or block files. */
    920 	if (access(file, R_OK) == 0 && !is_good_file(file))
    921 		return;
    922 
    923 	rcstream = fopen(file, "rb");
    924 
    925 	if (rcstream == NULL) {
    926 		jot_error(N_("Error reading %s: %s"), file, strerror(errno));
    927 		return;
    928 	}
    929 
    930 	/* Use the name and line number position of the included syntax file
    931 	 * while parsing it, so we can know where any errors in it are. */
    932 	nanorc = file;
    933 	lineno = 0;
    934 
    935 	/* If this is the first pass, parse only the prologue. */
    936 	if (syntax == NULL) {
    937 		parse_rcfile(rcstream, TRUE, TRUE);
    938 		nanorc = was_nanorc;
    939 		lineno = was_lineno;
    940 		return;
    941 	}
    942 
    943 	live_syntax = syntax;
    944 	lastcolor = NULL;
    945 
    946 	/* Fully parse the given syntax (as it is about to be used). */
    947 	parse_rcfile(rcstream, TRUE, FALSE);
    948 
    949 	extra = syntax->augmentations;
    950 
    951 	/* Apply any stored extendsyntax commands. */
    952 	while (extra != NULL) {
    953 		char *keyword = extra->data;
    954 		char *therest = parse_next_word(extra->data);
    955 
    956 		nanorc = extra->filename;
    957 		lineno = extra->lineno;
    958 
    959 		if (!parse_syntax_commands(keyword, therest))
    960 			jot_error(N_("Command \"%s\" not understood"), keyword);
    961 
    962 		extra = extra->next;
    963 	}
    964 
    965 	free(syntax->filename);
    966 	syntax->filename = NULL;
    967 
    968 	nanorc = was_nanorc;
    969 	lineno = was_lineno;
    970 }
    971 
    972 /* Expand globs in the passed name, and parse the resultant files. */
    973 void parse_includes(char *ptr)
    974 {
    975 	char *pattern, *expanded;
    976 	glob_t files;
    977 	int result;
    978 
    979 	check_for_nonempty_syntax();
    980 
    981 	pattern = ptr;
    982 	if (*pattern == '"')
    983 		pattern++;
    984 	ptr = parse_argument(ptr);
    985 
    986 	if (strlen(pattern) > PATH_MAX) {
    987 		jot_error(N_("Path is too long"));
    988 		return;
    989 	}
    990 
    991 	/* Expand a tilde first, then try to match the globbing pattern. */
    992 	expanded = real_dir_from_tilde(pattern);
    993 	result = glob(expanded, GLOB_ERR|GLOB_NOCHECK, NULL, &files);
    994 
    995 	/* If there are matches, process each of them.  Otherwise, only
    996 	 * report an error if it's something other than zero matches. */
    997 	if (result == 0) {
    998 		for (size_t i = 0; i < files.gl_pathc; ++i)
    999 			parse_one_include(files.gl_pathv[i], NULL);
   1000 	} else if (result != GLOB_NOMATCH)
   1001 		jot_error(N_("Error expanding %s: %s"), pattern, strerror(errno));
   1002 
   1003 	globfree(&files);
   1004 	free(expanded);
   1005 }
   1006 
   1007 /* Return the index of the color that is closest to the given RGB levels,
   1008  * assuming that the terminal uses the 6x6x6 color cube of xterm-256color.
   1009  * When red == green == blue, return an index in the xterm gray scale. */
   1010 short closest_index_color(short red, short green, short blue)
   1011 {
   1012 	/* Translation table, from 16 intended color levels to 6 available levels. */
   1013 	static const short level[] = { 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5 };
   1014 
   1015 	/* Translation table, from 14 intended gray levels to 24 available levels. */
   1016 	static const short gray[] = { 1, 2, 3, 4, 5, 6, 7, 9, 11, 13, 15, 18, 21, 23 };
   1017 
   1018 	if (COLORS != 256)
   1019 		return THE_DEFAULT;
   1020 	else if (red == green && green == blue && 0 < red && red < 0xF)
   1021 		return 232 + gray[red - 1];
   1022 	else
   1023 		return (36 * level[red] + 6 * level[green] + level[blue] + 16);
   1024 }
   1025 
   1026 #define COLORCOUNT  34
   1027 
   1028 const char hues[COLORCOUNT][8] = { "red", "green", "blue",
   1029 								   "yellow", "cyan", "magenta",
   1030 								   "white", "black", "normal",
   1031 								   "pink", "purple", "mauve",
   1032 								   "lagoon", "mint", "lime",
   1033 								   "peach", "orange", "latte",
   1034 								   "rosy", "beet", "plum",
   1035 								   "sea", "sky", "slate",
   1036 								   "teal", "sage", "brown",
   1037 								   "ocher", "sand", "tawny",
   1038 								   "brick", "crimson",
   1039 								   "grey", "gray" };
   1040 
   1041 short indices[COLORCOUNT] = { COLOR_RED, COLOR_GREEN, COLOR_BLUE,
   1042 							  COLOR_YELLOW, COLOR_CYAN, COLOR_MAGENTA,
   1043 							  COLOR_WHITE, COLOR_BLACK, THE_DEFAULT,
   1044 							  204, 163, 134, 38, 48, 148, 215, 208, 137,
   1045 							  175, 127, 98, 32, 111, 66, 35, 107, 100,
   1046 							  142, 186, 136, 166, 161,
   1047 							  COLOR_BLACK + 8, COLOR_BLACK + 8 };
   1048 
   1049 /* Return the short value corresponding to the given color name, and set
   1050  * vivid to TRUE for a lighter color, and thick for a heavier typeface. */
   1051 short color_to_short(const char *colorname, bool *vivid, bool *thick)
   1052 {
   1053 	if (strncmp(colorname, "bright", 6) == 0 && colorname[6] != '\0') {
   1054 		/* Prefix "bright" is deprecated; remove in 2027. */
   1055 		*vivid = TRUE;
   1056 		*thick = TRUE;
   1057 		colorname += 6;
   1058 	} else if (strncmp(colorname, "light", 5) == 0 && colorname[5] != '\0') {
   1059 		*vivid = TRUE;
   1060 		*thick = FALSE;
   1061 		colorname += 5;
   1062 	} else {
   1063 		*vivid = FALSE;
   1064 		*thick = FALSE;
   1065 	}
   1066 
   1067 	if (colorname[0] == '#' && strlen(colorname) == 4) {
   1068 		unsigned short r, g, b;
   1069 
   1070 		if (*vivid) {
   1071 			jot_error(N_("Color '%s' takes no prefix"), colorname);
   1072 			return BAD_COLOR;
   1073 		}
   1074 
   1075 		if (sscanf(colorname, "#%1hX%1hX%1hX", &r, &g, &b) == 3)
   1076 			return closest_index_color(r, g, b);
   1077 	}
   1078 
   1079 	for (int index = 0; index < COLORCOUNT; index++)
   1080 		if (strcmp(colorname, hues[index]) == 0) {
   1081 			if (index > 7 && *vivid) {
   1082 				jot_error(N_("Color '%s' takes no prefix"), colorname);
   1083 				return BAD_COLOR;
   1084 			} else if (index > 8 && COLORS < 255)
   1085 				return THE_DEFAULT;
   1086 			else
   1087 				return indices[index];
   1088 		}
   1089 
   1090 	jot_error(N_("Color \"%s\" not understood"), colorname);
   1091 	return BAD_COLOR;
   1092 }
   1093 
   1094 /* Parse the color name (or pair of color names) in the given string.
   1095  * Return FALSE when any color name is invalid; otherwise return TRUE. */
   1096 bool parse_combination(char *combotext, short *fg, short *bg, int *attributes)
   1097 {
   1098 	bool vivid, thick;
   1099 	char *comma;
   1100 
   1101 	*attributes = A_NORMAL;
   1102 
   1103 	if (strncmp(combotext, "bold", 4) == 0) {
   1104 		*attributes |= A_BOLD;
   1105 		if (combotext[4] != ',') {
   1106 			jot_error(N_("An attribute requires a subsequent comma"));
   1107 			return FALSE;
   1108 		}
   1109 		combotext += 5;
   1110 	}
   1111 
   1112 	if (strncmp(combotext, "italic", 6) == 0) {
   1113 #ifdef A_ITALIC
   1114 		*attributes |= A_ITALIC;
   1115 #endif
   1116 		if (combotext[6] != ',') {
   1117 			jot_error(N_("An attribute requires a subsequent comma"));
   1118 			return FALSE;
   1119 		}
   1120 		combotext += 7;
   1121 	}
   1122 
   1123 	comma = strchr(combotext, ',');
   1124 
   1125 	if (comma)
   1126 		*comma = '\0';
   1127 
   1128 	if (!comma || comma > combotext) {
   1129 		*fg = color_to_short(combotext, &vivid, &thick);
   1130 		if (*fg == BAD_COLOR)
   1131 			return FALSE;
   1132 		if (vivid && !thick && COLORS > 8)
   1133 			*fg += 8;
   1134 		else if (vivid)
   1135 			*attributes |= A_BOLD;
   1136 	} else
   1137 		*fg = THE_DEFAULT;
   1138 
   1139 	if (comma) {
   1140 		*bg = color_to_short(comma + 1, &vivid, &thick);
   1141 		if (*bg == BAD_COLOR)
   1142 			return FALSE;
   1143 		if (vivid && COLORS > 8)
   1144 			*bg += 8;
   1145 	} else
   1146 		*bg = THE_DEFAULT;
   1147 
   1148 	return TRUE;
   1149 }
   1150 
   1151 /* Parse the color specification that starts at ptr, and then the one or more
   1152  * regexes that follow it.  For each valid regex (or start=/end= regex pair),
   1153  * add a rule to the current syntax. */
   1154 void parse_rule(char *ptr, int rex_flags)
   1155 {
   1156 	char *names, *regexstring;
   1157 	short fg, bg;
   1158 	int attributes;
   1159 
   1160 	if (*ptr == '\0') {
   1161 		jot_error(N_("Missing color name"));
   1162 		return;
   1163 	}
   1164 
   1165 	names = ptr;
   1166 	ptr = parse_next_word(ptr);
   1167 
   1168 	if (!parse_combination(names, &fg, &bg, &attributes))
   1169 		return;
   1170 
   1171 	if (*ptr == '\0') {
   1172 		jot_error(N_("Missing regex string after '%s' command"), "color");
   1173 		return;
   1174 	}
   1175 
   1176 	while (*ptr != '\0') {
   1177 		regex_t *start_rgx = NULL, *end_rgx = NULL;
   1178 			/* Intermediate storage for compiled regular expressions. */
   1179 		colortype *newcolor = NULL;
   1180 			/* Container for compiled regex (pair) and the color it paints. */
   1181 		bool expectend = FALSE;
   1182 			/* Whether it is a start=/end= regex pair. */
   1183 
   1184 		if (strncmp(ptr, "start=", 6) == 0) {
   1185 			ptr += 6;
   1186 			expectend = TRUE;
   1187 		}
   1188 
   1189 		regexstring = ++ptr;
   1190 		ptr = parse_next_regex(ptr);
   1191 
   1192 		/* When there is no regex, or it is invalid, skip this line. */
   1193 		if (ptr == NULL || !compile(regexstring, rex_flags, &start_rgx))
   1194 			return;
   1195 
   1196 		if (expectend) {
   1197 			if (strncmp(ptr, "end=", 4) != 0) {
   1198 				jot_error(N_("\"start=\" requires a corresponding \"end=\""));
   1199 				regfree(start_rgx);
   1200 				free(start_rgx);
   1201 				return;
   1202 			}
   1203 
   1204 			regexstring = ptr + 5;
   1205 			ptr = parse_next_regex(ptr + 5);
   1206 
   1207 			/* When there is no valid end= regex, abandon the rule. */
   1208 			if (ptr == NULL || !compile(regexstring, rex_flags, &end_rgx)) {
   1209 				regfree(start_rgx);
   1210 				free(start_rgx);
   1211 				return;
   1212 			}
   1213 		}
   1214 
   1215 		/* Allocate a rule, fill in the data, and link it into the list. */
   1216 		newcolor = nmalloc(sizeof(colortype));
   1217 
   1218 		newcolor->start = start_rgx;
   1219 		newcolor->end = end_rgx;
   1220 
   1221 		newcolor->fg = fg;
   1222 		newcolor->bg = bg;
   1223 		newcolor->attributes = attributes;
   1224 
   1225 		if (lastcolor == NULL)
   1226 			live_syntax->color = newcolor;
   1227 		else
   1228 			lastcolor->next = newcolor;
   1229 
   1230 		newcolor->next = NULL;
   1231 		lastcolor = newcolor;
   1232 
   1233 		/* For a multiline rule, give it a number and increase the count. */
   1234 		if (expectend) {
   1235 			newcolor->id = live_syntax->multiscore;
   1236 			live_syntax->multiscore++;
   1237 		}
   1238 	}
   1239 }
   1240 
   1241 /* Set the colors for the given interface element to the given combination. */
   1242 void set_interface_color(int element, char *combotext)
   1243 {
   1244 	colortype *trio = nmalloc(sizeof(colortype));
   1245 
   1246 	if (parse_combination(combotext, &trio->fg, &trio->bg, &trio->attributes)) {
   1247 		free(color_combo[element]);
   1248 		color_combo[element] = trio;
   1249 	} else
   1250 		free(trio);
   1251 }
   1252 
   1253 /* Read regex strings enclosed in double quotes from the line pointed at
   1254  * by ptr, and store them quoteless in the passed storage place. */
   1255 void grab_and_store(const char *kind, char *ptr, regexlisttype **storage)
   1256 {
   1257 	regexlisttype *lastthing, *newthing;
   1258 	const char *regexstring;
   1259 
   1260 	if (!opensyntax) {
   1261 		jot_error(N_("A '%s' command requires a preceding 'syntax' command"), kind);
   1262 		return;
   1263 	}
   1264 
   1265 	/* The default syntax doesn't take any file matching stuff. */
   1266 	if (strcmp(live_syntax->name, "default") == 0 && *ptr != '\0') {
   1267 		jot_error(N_("The \"default\" syntax does not accept '%s' regexes"), kind);
   1268 		return;
   1269 	}
   1270 
   1271 	if (*ptr == '\0') {
   1272 		jot_error(N_("Missing regex string after '%s' command"), kind);
   1273 		return;
   1274 	}
   1275 
   1276 	lastthing = *storage;
   1277 
   1278 	/* If there was an earlier command, go to the last of those regexes. */
   1279 	while (lastthing != NULL && lastthing->next != NULL)
   1280 		lastthing = lastthing->next;
   1281 
   1282 	/* Now gather any valid regexes and add them to the linked list. */
   1283 	while (*ptr != '\0') {
   1284 		regex_t *packed_rgx = NULL;
   1285 
   1286 		regexstring = ++ptr;
   1287 		ptr = parse_next_regex(ptr);
   1288 
   1289 		if (ptr == NULL)
   1290 			return;
   1291 
   1292 		/* If the regex string is malformed, skip it. */
   1293 		if (!compile(regexstring, NANO_REG_EXTENDED | REG_NOSUB, &packed_rgx))
   1294 			continue;
   1295 
   1296 		/* Copy the regex into a struct, and hook this in at the end. */
   1297 		newthing = nmalloc(sizeof(regexlisttype));
   1298 		newthing->one_rgx = packed_rgx;
   1299 		newthing->next = NULL;
   1300 
   1301 		if (lastthing == NULL)
   1302 			*storage = newthing;
   1303 		else
   1304 			lastthing->next = newthing;
   1305 
   1306 		lastthing = newthing;
   1307 	}
   1308 }
   1309 
   1310 /* Gather and store the string after a comment/linter/formatter/tabgives command. */
   1311 void pick_up_name(const char *kind, char *ptr, char **storage)
   1312 {
   1313 	if (*ptr == '\0') {
   1314 		jot_error(N_("Missing argument after '%s'"), kind);
   1315 		return;
   1316 	}
   1317 
   1318 	/* If the argument starts with a quote, find the terminating quote. */
   1319 	if (*ptr == '"') {
   1320 		char *look = ptr + strlen(ptr);
   1321 
   1322 		while (*look != '"') {
   1323 			if (--look == ptr) {
   1324 				jot_error(N_("Argument of '%s' lacks closing \""), kind);
   1325 				return;
   1326 			}
   1327 		}
   1328 
   1329 		*look = '\0';
   1330 		ptr++;
   1331 	}
   1332 
   1333 	*storage = mallocstrcpy(*storage, ptr);
   1334 }
   1335 
   1336 /* Handle the six syntax-only commands. */
   1337 bool parse_syntax_commands(char *keyword, char *ptr)
   1338 {
   1339 	if (strcmp(keyword, "color") == 0)
   1340 		parse_rule(ptr, NANO_REG_EXTENDED);
   1341 	else if (strcmp(keyword, "icolor") == 0)
   1342 		parse_rule(ptr, NANO_REG_EXTENDED | REG_ICASE);
   1343 	else if (strcmp(keyword, "comment") == 0) {
   1344 #ifdef ENABLE_COMMENT
   1345 		pick_up_name("comment", ptr, &live_syntax->comment);
   1346 #endif
   1347 	} else if (strcmp(keyword, "tabgives") == 0) {
   1348 		pick_up_name("tabgives", ptr, &live_syntax->tabstring);
   1349 	} else if (strcmp(keyword, "linter") == 0) {
   1350 		pick_up_name("linter", ptr, &live_syntax->linter);
   1351 		strip_leading_blanks_from(live_syntax->linter);
   1352 	} else if (strcmp(keyword, "formatter") == 0) {
   1353 		pick_up_name("formatter", ptr, &live_syntax->formatter);
   1354 		strip_leading_blanks_from(live_syntax->formatter);
   1355 	} else
   1356 		return FALSE;
   1357 
   1358 	return TRUE;
   1359 }
   1360 #endif /* ENABLE_COLOR */
   1361 
   1362 /* Verify that the user has not unmapped every shortcut for
   1363  * a function that we consider 'vital' (such as "Exit"). */
   1364 static void check_vitals_mapped(void)
   1365 {
   1366 #define VITALS  4
   1367 	void (*vitals[VITALS])(void) = { do_exit, do_exit, do_exit, do_cancel };
   1368 	int inmenus[VITALS] = { MMAIN, MBROWSER, MHELP, MYESNO };
   1369 
   1370 	for (int v = 0; v < VITALS; v++) {
   1371 		for (funcstruct *f = allfuncs; f != NULL; f = f->next) {
   1372 			if (f->func == vitals[v] && (f->menus & inmenus[v])) {
   1373 				if (first_sc_for(inmenus[v], f->func) == NULL) {
   1374 					jot_error(N_("No key is bound to function '%s' in menu '%s'. "
   1375 								" Exiting.\n"), f->tag, menu_to_name(inmenus[v]));
   1376 					die(_("If needed, use nano with the -I option "
   1377 								"to adjust your nanorc settings.\n"));
   1378 				} else
   1379 					break;
   1380 			}
   1381 		}
   1382 	}
   1383 }
   1384 
   1385 /* Parse the rcfile, once it has been opened successfully at rcstream,
   1386  * and close it afterwards.  If just_syntax is TRUE, allow the file to
   1387  * contain only color syntax commands. */
   1388 void parse_rcfile(FILE *rcstream, bool just_syntax, bool intros_only)
   1389 {
   1390 	char *buffer = NULL;
   1391 	size_t size = 0;
   1392 	ssize_t length;
   1393 
   1394 	while ((length = getline(&buffer, &size, rcstream)) > 0) {
   1395 		char *ptr, *keyword, *option, *argument;
   1396 #ifdef ENABLE_COLOR
   1397 		bool drop_open = FALSE;
   1398 #endif
   1399 		int set = 0;
   1400 		size_t i;
   1401 
   1402 		lineno++;
   1403 
   1404 #ifdef ENABLE_COLOR
   1405 		/* If doing a full parse, skip to after the 'syntax' command. */
   1406 		if (just_syntax && !intros_only && lineno <= live_syntax->lineno)
   1407 			continue;
   1408 #endif
   1409 		/* Strip the terminating newline and possibly a carriage return. */
   1410 		if (buffer[length - 1] == '\n')
   1411 			buffer[--length] = '\0';
   1412 		if (length > 0 && buffer[length - 1] == '\r')
   1413 			buffer[--length] = '\0';
   1414 
   1415 		ptr = buffer;
   1416 		while (isblank((unsigned char)*ptr))
   1417 			ptr++;
   1418 
   1419 		/* If the line is empty or a comment, skip to next line. */
   1420 		if (*ptr == '\0' || *ptr == '#')
   1421 			continue;
   1422 
   1423 		/* Otherwise, skip to the next space. */
   1424 		keyword = ptr;
   1425 		ptr = parse_next_word(ptr);
   1426 
   1427 #ifdef ENABLE_COLOR
   1428 		/* Handle extending first... */
   1429 		if (!just_syntax && strcmp(keyword, "extendsyntax") == 0) {
   1430 			augmentstruct *newitem, *extra;
   1431 			char *syntaxname = ptr;
   1432 			syntaxtype *sntx;
   1433 
   1434 			check_for_nonempty_syntax();
   1435 
   1436 			ptr = parse_next_word(ptr);
   1437 
   1438 			for (sntx = syntaxes; sntx != NULL; sntx = sntx->next)
   1439 				if (!strcmp(sntx->name, syntaxname))
   1440 					break;
   1441 
   1442 			if (sntx == NULL) {
   1443 				jot_error(N_("Could not find syntax \"%s\" to extend"), syntaxname);
   1444 				continue;
   1445 			}
   1446 
   1447 			keyword = ptr;
   1448 			argument = copy_of(ptr);
   1449 			ptr = parse_next_word(ptr);
   1450 
   1451 			/* File-matching commands need to be processed immediately;
   1452 			 * other commands are stored for possible later processing. */
   1453 			if (strcmp(keyword, "header") == 0 || strcmp(keyword, "magic") == 0) {
   1454 				free(argument);
   1455 				live_syntax = sntx;
   1456 				opensyntax = TRUE;
   1457 				drop_open = TRUE;
   1458 			} else {
   1459 				newitem = nmalloc(sizeof(augmentstruct));;
   1460 
   1461 				newitem->filename = copy_of(nanorc);
   1462 				newitem->lineno = lineno;
   1463 				newitem->data = argument;
   1464 				newitem->next = NULL;
   1465 
   1466 				if (sntx->augmentations != NULL) {
   1467 					extra = sntx->augmentations;
   1468 					while (extra->next != NULL)
   1469 						extra = extra->next;
   1470 					extra->next = newitem;
   1471 				} else
   1472 					sntx->augmentations = newitem;
   1473 
   1474 				continue;
   1475 			}
   1476 		}
   1477 
   1478 		/* Try to parse the keyword. */
   1479 		if (strcmp(keyword, "syntax") == 0) {
   1480 			if (intros_only) {
   1481 				check_for_nonempty_syntax();
   1482 				begin_new_syntax(ptr);
   1483 			} else
   1484 				break;
   1485 		} else if (strcmp(keyword, "header") == 0) {
   1486 			if (intros_only)
   1487 				grab_and_store("header", ptr, &live_syntax->headers);
   1488 		} else if (strcmp(keyword, "magic") == 0) {
   1489 #ifdef HAVE_LIBMAGIC
   1490 			if (intros_only)
   1491 				grab_and_store("magic", ptr, &live_syntax->magics);
   1492 #endif
   1493 		} else if (just_syntax && (strcmp(keyword, "set") == 0 ||
   1494 								strcmp(keyword, "unset") == 0 ||
   1495 								strcmp(keyword, "bind") == 0 ||
   1496 								strcmp(keyword, "unbind") == 0 ||
   1497 								strcmp(keyword, "include") == 0 ||
   1498 								strcmp(keyword, "extendsyntax") == 0)) {
   1499 			if (intros_only)
   1500 				jot_error(N_("Command \"%s\" not allowed in included file"),
   1501 									keyword);
   1502 			else
   1503 				break;
   1504 		} else if (intros_only && (strcmp(keyword, "color") == 0 ||
   1505 								strcmp(keyword, "icolor") == 0 ||
   1506 								strcmp(keyword, "comment") == 0 ||
   1507 								strcmp(keyword, "tabgives") == 0 ||
   1508 								strcmp(keyword, "linter") == 0 ||
   1509 								strcmp(keyword, "formatter") == 0)) {
   1510 			if (!opensyntax)
   1511 				jot_error(N_("A '%s' command requires a preceding "
   1512 									"'syntax' command"), keyword);
   1513 			if (strstr("icolor", keyword))
   1514 				seen_color_command = TRUE;
   1515 			continue;
   1516 		} else if (parse_syntax_commands(keyword, ptr))
   1517 			;
   1518 		else if (strcmp(keyword, "include") == 0)
   1519 			parse_includes(ptr);
   1520 		else
   1521 #endif /* ENABLE_COLOR */
   1522 		if (strcmp(keyword, "set") == 0)
   1523 			set = 1;
   1524 		else if (strcmp(keyword, "unset") == 0)
   1525 			set = -1;
   1526 		else if (strcmp(keyword, "bind") == 0)
   1527 			parse_binding(ptr, TRUE);
   1528 		else if (strcmp(keyword, "unbind") == 0)
   1529 			parse_binding(ptr, FALSE);
   1530 		else if (intros_only)
   1531 			jot_error(N_("Command \"%s\" not understood"), keyword);
   1532 
   1533 #ifdef ENABLE_COLOR
   1534 		if (drop_open)
   1535 			opensyntax = FALSE;
   1536 #endif
   1537 		if (set == 0)
   1538 			continue;
   1539 
   1540 		check_for_nonempty_syntax();
   1541 
   1542 		if (*ptr == '\0') {
   1543 			jot_error(N_("Missing option"));
   1544 			continue;
   1545 		}
   1546 
   1547 		option = ptr;
   1548 		ptr = parse_next_word(ptr);
   1549 
   1550 		/* Find the just parsed option name among the existing names. */
   1551 		for (i = 0; rcopts[i].name != NULL; i++) {
   1552 			if (strcmp(option, rcopts[i].name) == 0)
   1553 				break;
   1554 		}
   1555 
   1556 		if (rcopts[i].name == NULL) {
   1557 			jot_error(N_("Unknown option: %s"), option);
   1558 			continue;
   1559 		}
   1560 
   1561 		/* If the option has a flag, set it or unset it, as requested. */
   1562 		if (rcopts[i].flag) {
   1563 			if (set == 1)
   1564 				SET(rcopts[i].flag);
   1565 			else
   1566 				UNSET(rcopts[i].flag);
   1567 			continue;
   1568 		}
   1569 
   1570 		/* An option that takes an argument cannot be unset. */
   1571 		if (set == -1) {
   1572 			jot_error(N_("Cannot unset option \"%s\""), option);
   1573 			continue;
   1574 		}
   1575 
   1576 		if (*ptr == '\0') {
   1577 			jot_error(N_("Option \"%s\" requires an argument"), option);
   1578 			continue;
   1579 		}
   1580 
   1581 		argument = ptr;
   1582 		if (*argument == '"')
   1583 			argument++;
   1584 		ptr = parse_argument(ptr);
   1585 
   1586 #ifdef ENABLE_UTF8
   1587 		/* When in a UTF-8 locale, ignore arguments with invalid sequences. */
   1588 		if (using_utf8 && mbstowcs(NULL, argument, 0) == (size_t)-1) {
   1589 			jot_error(N_("Argument is not a valid multibyte string"));
   1590 			continue;
   1591 		}
   1592 #endif
   1593 #ifdef ENABLE_COLOR
   1594 		if (strcmp(option, "titlecolor") == 0)
   1595 			set_interface_color(TITLE_BAR, argument);
   1596 		else if (strcmp(option, "numbercolor") == 0)
   1597 			set_interface_color(LINE_NUMBER, argument);
   1598 		else if (strcmp(option, "stripecolor") == 0)
   1599 			set_interface_color(GUIDE_STRIPE, argument);
   1600 		else if (strcmp(option, "scrollercolor") == 0)
   1601 			set_interface_color(SCROLL_BAR, argument);
   1602 		else if (strcmp(option, "selectedcolor") == 0)
   1603 			set_interface_color(SELECTED_TEXT, argument);
   1604 		else if (strcmp(option, "spotlightcolor") == 0)
   1605 			set_interface_color(SPOTLIGHTED, argument);
   1606 		else if (strcmp(option, "minicolor") == 0)
   1607 			set_interface_color(MINI_INFOBAR, argument);
   1608 		else if (strcmp(option, "promptcolor") == 0)
   1609 			set_interface_color(PROMPT_BAR, argument);
   1610 		else if (strcmp(option, "statuscolor") == 0)
   1611 			set_interface_color(STATUS_BAR, argument);
   1612 		else if (strcmp(option, "errorcolor") == 0)
   1613 			set_interface_color(ERROR_MESSAGE, argument);
   1614 		else if (strcmp(option, "keycolor") == 0)
   1615 			set_interface_color(KEY_COMBO, argument);
   1616 		else if (strcmp(option, "functioncolor") == 0)
   1617 			set_interface_color(FUNCTION_TAG, argument);
   1618 		else
   1619 #endif
   1620 #ifdef ENABLE_OPERATINGDIR
   1621 		if (strcmp(option, "operatingdir") == 0)
   1622 			operating_dir = mallocstrcpy(operating_dir, argument);
   1623 		else
   1624 #endif
   1625 #ifdef ENABLED_WRAPORJUSTIFY
   1626 		if (strcmp(option, "fill") == 0) {
   1627 			if (!parse_num(argument, &fill)) {
   1628 				jot_error(N_("Requested fill size \"%s\" is invalid"), argument);
   1629 				fill = -COLUMNS_FROM_EOL;
   1630 			}
   1631 		} else
   1632 #endif
   1633 #ifndef NANO_TINY
   1634 		if (strcmp(option, "matchbrackets") == 0) {
   1635 			if (has_blank_char(argument))
   1636 				jot_error(N_("Non-blank characters required"));
   1637 			else if (mbstrlen(argument) % 2 != 0)
   1638 				jot_error(N_("Even number of characters required"));
   1639 			else
   1640 				matchbrackets = mallocstrcpy(matchbrackets, argument);
   1641 		} else if (strcmp(option, "whitespace") == 0) {
   1642 			if (mbstrlen(argument) != 2 || breadth(argument) != 2)
   1643 				jot_error(N_("Two single-column characters required"));
   1644 			else {
   1645 				whitespace = mallocstrcpy(whitespace, argument);
   1646 				whitelen[0] = char_length(whitespace);
   1647 				whitelen[1] = char_length(whitespace + whitelen[0]);
   1648 			}
   1649 		} else
   1650 #endif
   1651 #ifdef ENABLE_JUSTIFY
   1652 		if (strcmp(option, "punct") == 0) {
   1653 			if (has_blank_char(argument))
   1654 				jot_error(N_("Non-blank characters required"));
   1655 			else
   1656 				punct = mallocstrcpy(punct, argument);
   1657 		} else if (strcmp(option, "brackets") == 0) {
   1658 			if (has_blank_char(argument))
   1659 				jot_error(N_("Non-blank characters required"));
   1660 			else
   1661 				brackets = mallocstrcpy(brackets, argument);
   1662 		} else if (strcmp(option, "quotestr") == 0)
   1663 			quotestr = mallocstrcpy(quotestr, argument);
   1664 		else
   1665 #endif
   1666 #ifdef ENABLE_SPELLER
   1667 		if (strcmp(option, "speller") == 0)
   1668 			alt_speller = mallocstrcpy(alt_speller, argument);
   1669 		else
   1670 #endif
   1671 #ifndef NANO_TINY
   1672 		if (strcmp(option, "backupdir") == 0)
   1673 			backup_dir = mallocstrcpy(backup_dir, argument);
   1674 		else if (strcmp(option, "wordchars") == 0)
   1675 			word_chars = mallocstrcpy(word_chars, argument);
   1676 		else if (strcmp(option, "guidestripe") == 0) {
   1677 			if (!parse_num(argument, &stripe_column) || stripe_column <= 0) {
   1678 				jot_error(N_("Guide column \"%s\" is invalid"), argument);
   1679 				stripe_column = 0;
   1680 			}
   1681 		} else if (strcmp(option, "tabsize") == 0) {
   1682 			if (!parse_num(argument, &tabsize) || tabsize <= 0) {
   1683 				jot_error(N_("Requested tab size \"%s\" is invalid"), argument);
   1684 				tabsize = -1;
   1685 			}
   1686 		}
   1687 #else
   1688 		;  /* Properly terminate any earlier 'else'. */
   1689 #endif
   1690 	}
   1691 
   1692 	if (intros_only)
   1693 		check_for_nonempty_syntax();
   1694 
   1695 	fclose(rcstream);
   1696 	free(buffer);
   1697 	lineno = 0;
   1698 
   1699 	return;
   1700 }
   1701 
   1702 /* Read and interpret one of the two nanorc files. */
   1703 void parse_one_nanorc(void)
   1704 {
   1705 	FILE *rcstream = fopen(nanorc, "rb");
   1706 
   1707 	/* If opening the file succeeded, parse it.  Otherwise, only
   1708 	 * complain if the file actually exists. */
   1709 	if (rcstream != NULL)
   1710 		parse_rcfile(rcstream, FALSE, TRUE);
   1711 	else if (errno != ENOENT)
   1712 		jot_error(N_("Error reading %s: %s"), nanorc, strerror(errno));
   1713 }
   1714 
   1715 /* Return TRUE when path-plus-name denotes a readable, normal file. */
   1716 bool have_nanorc(const char *path, const char *name)
   1717 {
   1718 	if (path == NULL)
   1719 		return FALSE;
   1720 
   1721 	free(nanorc);
   1722 	nanorc = concatenate(path, name);
   1723 
   1724 	return is_good_file(nanorc);
   1725 }
   1726 
   1727 /* Process the nanorc file that was specified on the command line (if any),
   1728  * and otherwise the system-wide rcfile followed by the user's rcfile. */
   1729 void do_rcfiles(void)
   1730 {
   1731 	if (custom_nanorc) {
   1732 		nanorc = get_full_path(custom_nanorc);
   1733 		if (nanorc == NULL || access(nanorc, F_OK) != 0)
   1734 			die(_("Specified rcfile does not exist\n"));
   1735 	} else
   1736 		nanorc = mallocstrcpy(nanorc, SYSCONFDIR "/nanorc");
   1737 
   1738 	if (is_good_file(nanorc))
   1739 		parse_one_nanorc();
   1740 
   1741 	if (custom_nanorc == NULL) {
   1742 		const char *xdgconfdir = getenv("XDG_CONFIG_HOME");
   1743 
   1744 		get_homedir();
   1745 
   1746 		/* Now try to find a nanorc file in the user's home directory or in the
   1747 		 * XDG configuration directories, and process the first one found. */
   1748 		if (have_nanorc(homedir, "/" HOME_RC_NAME) ||
   1749 					have_nanorc(xdgconfdir, "/nano/" RCFILE_NAME) ||
   1750 					have_nanorc(homedir, "/.config/nano/" RCFILE_NAME))
   1751 			parse_one_nanorc();
   1752 		else if (homedir == NULL && xdgconfdir == NULL)
   1753 			jot_error(N_("I can't find my home directory!  Wah!"));
   1754 	}
   1755 
   1756 	check_vitals_mapped();
   1757 
   1758 	free(nanorc);
   1759 	nanorc = NULL;
   1760 }
   1761 
   1762 #endif /* ENABLE_NANORC */