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 */