| /* $Id$ */ |
| /************************************************************************** |
| * rcfile.c * |
| * * |
| * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 * |
| * Free Software Foundation, Inc. * |
| * This program is free software; you can redistribute it and/or modify * |
| * it under the terms of the GNU General Public License as published by * |
| * the Free Software Foundation; either version 3, or (at your option) * |
| * any later version. * |
| * * |
| * This program is distributed in the hope that it will be useful, but * |
| * WITHOUT ANY WARRANTY; without even the implied warranty of * |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * |
| * General Public License for more details. * |
| * * |
| * You should have received a copy of the GNU General Public License * |
| * along with this program; if not, write to the Free Software * |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * |
| * 02110-1301, USA. * |
| * * |
| **************************************************************************/ |
| |
| #include "proto.h" |
| |
| #include <stdarg.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <errno.h> |
| #include <unistd.h> |
| #include <ctype.h> |
| |
| #ifdef ENABLE_NANORC |
| |
| static const rcoption rcopts[] = { |
| {"boldtext", BOLD_TEXT}, |
| #ifndef DISABLE_JUSTIFY |
| {"brackets", 0}, |
| #endif |
| {"const", CONST_UPDATE}, |
| #ifndef DISABLE_WRAPJUSTIFY |
| {"fill", 0}, |
| #endif |
| #ifndef NANO_TINY |
| {"locking", LOCKING}, |
| #endif |
| #ifndef DISABLE_MOUSE |
| {"mouse", USE_MOUSE}, |
| #endif |
| #ifdef ENABLE_MULTIBUFFER |
| {"multibuffer", MULTIBUFFER}, |
| #endif |
| {"morespace", MORE_SPACE}, |
| {"nofollow", NOFOLLOW_SYMLINKS}, |
| {"nohelp", NO_HELP}, |
| {"nonewlines", NO_NEWLINES}, |
| #ifndef DISABLE_WRAPPING |
| {"nowrap", NO_WRAP}, |
| #endif |
| #ifndef DISABLE_OPERATINGDIR |
| {"operatingdir", 0}, |
| #endif |
| {"preserve", PRESERVE}, |
| #ifndef DISABLE_JUSTIFY |
| {"punct", 0}, |
| {"quotestr", 0}, |
| #endif |
| {"rebinddelete", REBIND_DELETE}, |
| {"rebindkeypad", REBIND_KEYPAD}, |
| #ifdef HAVE_REGEX_H |
| {"regexp", USE_REGEXP}, |
| #endif |
| #ifndef DISABLE_SPELLER |
| {"speller", 0}, |
| #endif |
| {"suspend", SUSPEND}, |
| {"tabsize", 0}, |
| {"tempfile", TEMP_FILE}, |
| {"view", VIEW_MODE}, |
| #ifndef NANO_TINY |
| {"autoindent", AUTOINDENT}, |
| {"backup", BACKUP_FILE}, |
| {"allow_insecure_backup", INSECURE_BACKUP}, |
| {"backupdir", 0}, |
| {"backwards", BACKWARDS_SEARCH}, |
| {"casesensitive", CASE_SENSITIVE}, |
| {"cut", CUT_TO_END}, |
| {"historylog", HISTORYLOG}, |
| {"matchbrackets", 0}, |
| {"noconvert", NO_CONVERT}, |
| {"poslog", POS_HISTORY}, |
| {"quiet", QUIET}, |
| {"quickblank", QUICK_BLANK}, |
| {"smarthome", SMART_HOME}, |
| {"smooth", SMOOTH_SCROLL}, |
| {"tabstospaces", TABS_TO_SPACES}, |
| {"undo", UNDOABLE}, |
| {"whitespace", 0}, |
| {"wordbounds", WORD_BOUNDS}, |
| {"softwrap", SOFTWRAP}, |
| #endif |
| {NULL, 0} |
| }; |
| |
| static bool errors = FALSE; |
| /* Whether we got any errors while parsing an rcfile. */ |
| static size_t lineno = 0; |
| /* If we did, the line number where the last error occurred. */ |
| static char *nanorc = NULL; |
| /* The path to the rcfile we're parsing. */ |
| #ifdef ENABLE_COLOR |
| static syntaxtype *endsyntax = NULL; |
| /* The end of the list of syntaxes. */ |
| static exttype *endheader = NULL; |
| /* End of header list */ |
| static colortype *endcolor = NULL; |
| /* The end of the color list for the current syntax. */ |
| |
| #endif |
| |
| /* We have an error in some part of the rcfile. Print the error message |
| * on stderr, and then make the user hit Enter to continue starting |
| * nano. */ |
| void rcfile_error(const char *msg, ...) |
| { |
| va_list ap; |
| |
| if (ISSET(QUIET)) |
| return; |
| |
| fprintf(stderr, "\n"); |
| if (lineno > 0) { |
| errors = TRUE; |
| fprintf(stderr, _("Error in %s on line %lu: "), nanorc, (unsigned long)lineno); |
| } |
| |
| va_start(ap, msg); |
| vfprintf(stderr, _(msg), ap); |
| va_end(ap); |
| |
| fprintf(stderr, "\n"); |
| } |
| |
| /* Parse the next word from the string, null-terminate it, and return |
| * a pointer to the first character after the null terminator. The |
| * returned pointer will point to '\0' if we hit the end of the line. */ |
| char *parse_next_word(char *ptr) |
| { |
| while (!isblank(*ptr) && *ptr != '\0') |
| ptr++; |
| |
| if (*ptr == '\0') |
| return ptr; |
| |
| /* Null-terminate and advance ptr. */ |
| *ptr++ = '\0'; |
| |
| while (isblank(*ptr)) |
| ptr++; |
| |
| return ptr; |
| } |
| |
| /* Parse an argument, with optional quotes, after a keyword that takes |
| * one. If the next word starts with a ", we say that it ends with the |
| * last " of the line. Otherwise, we interpret it as usual, so that the |
| * arguments can contain "'s too. */ |
| char *parse_argument(char *ptr) |
| { |
| const char *ptr_save = ptr; |
| char *last_quote = NULL; |
| |
| assert(ptr != NULL); |
| |
| if (*ptr != '"') |
| return parse_next_word(ptr); |
| |
| do { |
| ptr++; |
| if (*ptr == '"') |
| last_quote = ptr; |
| } while (*ptr != '\0'); |
| |
| if (last_quote == NULL) { |
| if (*ptr == '\0') |
| ptr = NULL; |
| else |
| *ptr++ = '\0'; |
| rcfile_error(N_("Argument '%s' has an unterminated \""), ptr_save); |
| } else { |
| *last_quote = '\0'; |
| ptr = last_quote + 1; |
| } |
| if (ptr != NULL) |
| while (isblank(*ptr)) |
| ptr++; |
| return ptr; |
| } |
| |
| #ifdef ENABLE_COLOR |
| /* Parse the next regex string from the line at ptr, and return it. */ |
| char *parse_next_regex(char *ptr) |
| { |
| assert(ptr != NULL); |
| |
| /* Continue until the end of the line, or a " followed by a space, a |
| * blank character, or \0. */ |
| while ((*ptr != '"' || (!isblank(*(ptr + 1)) && |
| *(ptr + 1) != '\0')) && *ptr != '\0') |
| ptr++; |
| |
| assert(*ptr == '"' || *ptr == '\0'); |
| |
| if (*ptr == '\0') { |
| rcfile_error( |
| N_("Regex strings must begin and end with a \" character")); |
| return NULL; |
| } |
| |
| /* Null-terminate and advance ptr. */ |
| *ptr++ = '\0'; |
| |
| while (isblank(*ptr)) |
| ptr++; |
| |
| return ptr; |
| } |
| |
| /* Compile the regular expression regex to see if it's valid. Return |
| * TRUE if it is, or FALSE otherwise. */ |
| bool nregcomp(const char *regex, int eflags) |
| { |
| regex_t preg; |
| const char *r = fixbounds(regex); |
| int rc = regcomp(&preg, r, REG_EXTENDED | eflags); |
| |
| if (rc != 0) { |
| size_t len = regerror(rc, &preg, NULL, 0); |
| char *str = charalloc(len); |
| |
| regerror(rc, &preg, str, len); |
| rcfile_error(N_("Bad regex \"%s\": %s"), r, str); |
| free(str); |
| } |
| |
| regfree(&preg); |
| return (rc == 0); |
| } |
| |
| /* Parse the next syntax string from the line at ptr, and add it to the |
| * global list of color syntaxes. */ |
| void parse_syntax(char *ptr) |
| { |
| const char *fileregptr = NULL, *nameptr = NULL; |
| syntaxtype *tmpsyntax, *prev_syntax; |
| exttype *endext = NULL; |
| /* The end of the extensions list for this syntax. */ |
| |
| assert(ptr != NULL); |
| |
| if (*ptr == '\0') { |
| rcfile_error(N_("Missing syntax name")); |
| return; |
| } |
| |
| if (*ptr != '"') { |
| rcfile_error( |
| N_("Regex strings must begin and end with a \" character")); |
| return; |
| } |
| |
| ptr++; |
| |
| nameptr = ptr; |
| ptr = parse_next_regex(ptr); |
| |
| if (ptr == NULL) |
| return; |
| |
| /* Search for a duplicate syntax name. If we find one, free it, so |
| * that we always use the last syntax with a given name. */ |
| prev_syntax = NULL; |
| for (tmpsyntax = syntaxes; tmpsyntax != NULL; |
| tmpsyntax = tmpsyntax->next) { |
| if (strcmp(nameptr, tmpsyntax->desc) == 0) { |
| syntaxtype *old_syntax = tmpsyntax; |
| |
| if (endsyntax == tmpsyntax) |
| endsyntax = prev_syntax; |
| |
| tmpsyntax = tmpsyntax->next; |
| if (prev_syntax != NULL) |
| prev_syntax->next = tmpsyntax; |
| else |
| syntaxes = tmpsyntax; |
| |
| free(old_syntax->desc); |
| free(old_syntax); |
| break; |
| } |
| prev_syntax = tmpsyntax; |
| } |
| |
| if (syntaxes == NULL) { |
| syntaxes = (syntaxtype *)nmalloc(sizeof(syntaxtype)); |
| endsyntax = syntaxes; |
| } else { |
| endsyntax->next = (syntaxtype *)nmalloc(sizeof(syntaxtype)); |
| endsyntax = endsyntax->next; |
| #ifdef DEBUG |
| fprintf(stderr, "Adding new syntax after first one\n"); |
| #endif |
| } |
| |
| endsyntax->desc = mallocstrcpy(NULL, nameptr); |
| endsyntax->color = NULL; |
| endcolor = NULL; |
| endheader = NULL; |
| endsyntax->extensions = NULL; |
| endsyntax->headers = NULL; |
| endsyntax->magics = NULL; |
| endsyntax->next = NULL; |
| endsyntax->nmultis = 0; |
| endsyntax->linter = NULL; |
| |
| #ifdef DEBUG |
| fprintf(stderr, "Starting a new syntax type: \"%s\"\n", nameptr); |
| #endif |
| |
| /* The "none" syntax is the same as not having a syntax at all, so |
| * we can't assign any extensions or colors to it. */ |
| if (strcmp(endsyntax->desc, "none") == 0) { |
| rcfile_error(N_("The \"none\" syntax is reserved")); |
| return; |
| } |
| |
| /* The default syntax should have no associated extensions. */ |
| if (strcmp(endsyntax->desc, "default") == 0 && *ptr != '\0') { |
| rcfile_error( |
| N_("The \"default\" syntax must take no extensions")); |
| return; |
| } |
| |
| /* Now load the extensions into their part of the struct. */ |
| while (*ptr != '\0') { |
| exttype *newext; |
| /* The new extension structure. */ |
| |
| while (*ptr != '"' && *ptr != '\0') |
| ptr++; |
| |
| if (*ptr == '\0') |
| return; |
| |
| ptr++; |
| |
| fileregptr = ptr; |
| ptr = parse_next_regex(ptr); |
| if (ptr == NULL) |
| break; |
| |
| newext = (exttype *)nmalloc(sizeof(exttype)); |
| |
| /* Save the extension regex if it's valid. */ |
| if (nregcomp(fileregptr, REG_NOSUB)) { |
| newext->ext_regex = mallocstrcpy(NULL, fileregptr); |
| newext->ext = NULL; |
| |
| if (endext == NULL) |
| endsyntax->extensions = newext; |
| else |
| endext->next = newext; |
| endext = newext; |
| endext->next = NULL; |
| } else |
| free(newext); |
| } |
| |
| } |
| |
| |
| /* Parse the next syntax string from the line at ptr, and add it to the |
| * global list of color syntaxes. */ |
| void parse_magictype(char *ptr) |
| { |
| #ifdef HAVE_LIBMAGIC |
| const char *fileregptr = NULL; |
| exttype *endext = NULL; |
| |
| assert(ptr != NULL); |
| |
| if (syntaxes == NULL) { |
| rcfile_error( |
| N_("Cannot add a magic string regex without a syntax command")); |
| return; |
| } |
| |
| if (*ptr == '\0') { |
| rcfile_error(N_("Missing magic string name")); |
| return; |
| } |
| |
| if (*ptr != '"') { |
| rcfile_error( |
| N_("Regex strings must begin and end with a \" character")); |
| return; |
| } |
| |
| #ifdef DEBUG |
| fprintf(stderr, "Starting a magic type: \"%s\"\n", ptr); |
| #endif |
| |
| /* Now load the extensions into their part of the struct. */ |
| while (*ptr != '\0') { |
| exttype *newext; |
| /* The new extension structure. */ |
| |
| while (*ptr != '"' && *ptr != '\0') |
| ptr++; |
| |
| if (*ptr == '\0') |
| return; |
| |
| ptr++; |
| |
| fileregptr = ptr; |
| ptr = parse_next_regex(ptr); |
| if (ptr == NULL) |
| break; |
| |
| newext = (exttype *)nmalloc(sizeof(exttype)); |
| |
| /* Save the regex if it's valid. */ |
| if (nregcomp(fileregptr, REG_NOSUB)) { |
| newext->ext_regex = mallocstrcpy(NULL, fileregptr); |
| newext->ext = NULL; |
| |
| if (endext == NULL) |
| endsyntax->magics = newext; |
| else |
| endext->next = newext; |
| endext = newext; |
| endext->next = NULL; |
| } else |
| free(newext); |
| } |
| #endif /* HAVE_LIBMAGIC */ |
| } |
| |
| int check_bad_binding(sc *s) |
| { |
| #define BADLISTLEN 1 |
| int badtypes[BADLISTLEN] = {META}; |
| int badseqs[BADLISTLEN] = { 91 }; |
| int i; |
| |
| for (i = 0; i < BADLISTLEN; i++) |
| if (s->type == badtypes[i] && s->seq == badseqs[i]) |
| return 1; |
| |
| return 0; |
| } |
| |
| void parse_keybinding(char *ptr) |
| { |
| char *keyptr = NULL, *keycopy = NULL, *funcptr = NULL, *menuptr = NULL; |
| sc *s, *newsc; |
| int i, menu; |
| |
| assert(ptr != NULL); |
| |
| if (*ptr == '\0') { |
| rcfile_error(N_("Missing key name")); |
| return; |
| } |
| |
| keyptr = ptr; |
| ptr = parse_next_word(ptr); |
| keycopy = mallocstrcpy(NULL, keyptr); |
| for (i = 0; i < strlen(keycopy); i++) |
| keycopy[i] = toupper(keycopy[i]); |
| |
| if (keycopy[0] != 'M' && keycopy[0] != '^' && keycopy[0] != 'F' && keycopy[0] != 'K') { |
| rcfile_error( |
| N_("keybindings must begin with \"^\", \"M\", or \"F\"")); |
| return; |
| } |
| |
| funcptr = ptr; |
| ptr = parse_next_word(ptr); |
| |
| if (!strcmp(funcptr, "")) { |
| rcfile_error( |
| N_("Must specify function to bind key to")); |
| return; |
| } |
| |
| menuptr = ptr; |
| ptr = parse_next_word(ptr); |
| |
| if (!strcmp(menuptr, "")) { |
| rcfile_error( |
| /* Note to translators, do not translate the word "all" |
| in the sentence below, everything else is fine */ |
| N_("Must specify menu to bind key to (or \"all\")")); |
| return; |
| } |
| |
| menu = strtomenu(menuptr); |
| newsc = strtosc(menu, funcptr); |
| if (newsc == NULL) { |
| rcfile_error( |
| N_("Could not map name \"%s\" to a function"), funcptr); |
| return; |
| } |
| |
| if (menu < 1) { |
| rcfile_error( |
| N_("Could not map name \"%s\" to a menu"), menuptr); |
| return; |
| } |
| |
| |
| #ifdef DEBUG |
| fprintf(stderr, "newsc now address %d, menu func assigned = %d, menu = %d\n", |
| &newsc, newsc->scfunc, menu); |
| #endif |
| |
| |
| newsc->keystr = keycopy; |
| newsc->menu = menu; |
| newsc->type = strtokeytype(newsc->keystr); |
| assign_keyinfo(newsc); |
| #ifdef DEBUG |
| fprintf(stderr, "s->keystr = \"%s\"\n", newsc->keystr); |
| fprintf(stderr, "s->seq = \"%d\"\n", newsc->seq); |
| #endif |
| |
| if (check_bad_binding(newsc)) { |
| rcfile_error( |
| N_("Sorry, keystr \"%s\" is an illegal binding"), newsc->keystr); |
| return; |
| } |
| |
| /* now let's have some fun. Try and delete the other entries |
| we found for the same menu, then make this new new |
| beginning */ |
| for (s = sclist; s != NULL; s = s->next) { |
| if (((s->menu & newsc->menu)) && s->seq == newsc->seq) { |
| s->menu &= ~newsc->menu; |
| #ifdef DEBUG |
| fprintf(stderr, "replaced menu entry %d\n", s->menu); |
| #endif |
| } |
| } |
| newsc->next = sclist; |
| sclist = newsc; |
| } |
| |
| /* Let user unbind a sequence from a given (or all) menus */ |
| void parse_unbinding(char *ptr) |
| { |
| char *keyptr = NULL, *keycopy = NULL, *menuptr = NULL; |
| sc *s; |
| int i, menu; |
| |
| assert(ptr != NULL); |
| |
| if (*ptr == '\0') { |
| rcfile_error(N_("Missing key name")); |
| return; |
| } |
| |
| keyptr = ptr; |
| ptr = parse_next_word(ptr); |
| keycopy = mallocstrcpy(NULL, keyptr); |
| for (i = 0; i < strlen(keycopy); i++) |
| keycopy[i] = toupper(keycopy[i]); |
| |
| #ifdef DEBUG |
| fprintf(stderr, "Starting unbinding code"); |
| #endif |
| |
| if (keycopy[0] != 'M' && keycopy[0] != '^' && keycopy[0] != 'F' && keycopy[0] != 'K') { |
| rcfile_error( |
| N_("keybindings must begin with \"^\", \"M\", or \"F\"")); |
| return; |
| } |
| |
| menuptr = ptr; |
| ptr = parse_next_word(ptr); |
| |
| if (!strcmp(menuptr, "")) { |
| rcfile_error( |
| /* Note to translators, do not translate the word "all" |
| in the sentence below, everything else is fine */ |
| N_("Must specify menu to bind key to (or \"all\")")); |
| return; |
| } |
| |
| menu = strtomenu(menuptr); |
| if (menu < 1) { |
| rcfile_error( |
| N_("Could not map name \"%s\" to a menu"), menuptr); |
| return; |
| } |
| |
| |
| #ifdef DEBUG |
| fprintf(stderr, "unbinding \"%s\" from menu = %d\n", keycopy, menu); |
| #endif |
| |
| /* Now find the apropriate entries in the menu to delete */ |
| for (s = sclist; s != NULL; s = s->next) { |
| if (((s->menu & menu)) && !strcmp(s->keystr,keycopy)) { |
| s->menu &= ~menu; |
| #ifdef DEBUG |
| fprintf(stderr, "deleted menu entry %d\n", s->menu); |
| #endif |
| } |
| } |
| } |
| |
| |
| /* Read and parse additional syntax files. */ |
| void parse_include(char *ptr) |
| { |
| struct stat rcinfo; |
| FILE *rcstream; |
| char *option, *nanorc_save = nanorc, *expanded; |
| size_t lineno_save = lineno; |
| |
| option = ptr; |
| if (*option == '"') |
| option++; |
| ptr = parse_argument(ptr); |
| |
| /* Can't get the specified file's full path cause it may screw up |
| our cwd depending on the parent dirs' permissions, (see Savannah bug 25297) */ |
| |
| /* Don't open directories, character files, or block files. */ |
| if (stat(option, &rcinfo) != -1) { |
| if (S_ISDIR(rcinfo.st_mode) || S_ISCHR(rcinfo.st_mode) || |
| S_ISBLK(rcinfo.st_mode)) { |
| rcfile_error(S_ISDIR(rcinfo.st_mode) ? |
| _("\"%s\" is a directory") : |
| _("\"%s\" is a device file"), option); |
| } |
| } |
| |
| expanded = real_dir_from_tilde(option); |
| |
| /* Open the new syntax file. */ |
| if ((rcstream = fopen(expanded, "rb")) == NULL) { |
| rcfile_error(_("Error reading %s: %s"), expanded, |
| strerror(errno)); |
| return; |
| } |
| |
| /* Use the name and line number position of the new syntax file |
| * while parsing it, so we can know where any errors in it are. */ |
| nanorc = expanded; |
| lineno = 0; |
| |
| #ifdef DEBUG |
| fprintf(stderr, "Parsing file \"%s\" (expanded from \"%s\")\n", expanded, option); |
| #endif |
| |
| parse_rcfile(rcstream |
| #ifdef ENABLE_COLOR |
| , TRUE |
| #endif |
| ); |
| |
| /* We're done with the new syntax file. Restore the original |
| * filename and line number position. */ |
| nanorc = nanorc_save; |
| lineno = lineno_save; |
| |
| } |
| |
| /* Return the short value corresponding to the color named in colorname, |
| * and set bright to TRUE if that color is bright. */ |
| short color_to_short(const char *colorname, bool *bright) |
| { |
| short mcolor = -1; |
| |
| assert(colorname != NULL && bright != NULL); |
| |
| if (strncasecmp(colorname, "bright", 6) == 0) { |
| *bright = TRUE; |
| colorname += 6; |
| } |
| |
| if (strcasecmp(colorname, "green") == 0) |
| mcolor = COLOR_GREEN; |
| else if (strcasecmp(colorname, "red") == 0) |
| mcolor = COLOR_RED; |
| else if (strcasecmp(colorname, "blue") == 0) |
| mcolor = COLOR_BLUE; |
| else if (strcasecmp(colorname, "white") == 0) |
| mcolor = COLOR_WHITE; |
| else if (strcasecmp(colorname, "yellow") == 0) |
| mcolor = COLOR_YELLOW; |
| else if (strcasecmp(colorname, "cyan") == 0) |
| mcolor = COLOR_CYAN; |
| else if (strcasecmp(colorname, "magenta") == 0) |
| mcolor = COLOR_MAGENTA; |
| else if (strcasecmp(colorname, "black") == 0) |
| mcolor = COLOR_BLACK; |
| else |
| rcfile_error(N_("Color \"%s\" not understood.\n" |
| "Valid colors are \"green\", \"red\", \"blue\",\n" |
| "\"white\", \"yellow\", \"cyan\", \"magenta\" and\n" |
| "\"black\", with the optional prefix \"bright\"\n" |
| "for foreground colors."), colorname); |
| |
| return mcolor; |
| } |
| |
| /* Parse the color string in the line at ptr, and add it to the current |
| * file's associated colors. If icase is TRUE, treat the color string |
| * as case insensitive. */ |
| void parse_colors(char *ptr, bool icase) |
| { |
| short fg, bg; |
| bool bright = FALSE, no_fgcolor = FALSE; |
| char *fgstr; |
| |
| assert(ptr != NULL); |
| |
| if (syntaxes == NULL) { |
| rcfile_error( |
| N_("Cannot add a color command without a syntax command")); |
| return; |
| } |
| |
| if (*ptr == '\0') { |
| rcfile_error(N_("Missing color name")); |
| return; |
| } |
| |
| fgstr = ptr; |
| ptr = parse_next_word(ptr); |
| |
| if (strchr(fgstr, ',') != NULL) { |
| char *bgcolorname; |
| |
| strtok(fgstr, ","); |
| bgcolorname = strtok(NULL, ","); |
| if (bgcolorname == NULL) { |
| /* If we have a background color without a foreground color, |
| * parse it properly. */ |
| bgcolorname = fgstr + 1; |
| no_fgcolor = TRUE; |
| } |
| if (strncasecmp(bgcolorname, "bright", 6) == 0) { |
| rcfile_error( |
| N_("Background color \"%s\" cannot be bright"), |
| bgcolorname); |
| return; |
| } |
| bg = color_to_short(bgcolorname, &bright); |
| } else |
| bg = -1; |
| |
| if (!no_fgcolor) { |
| fg = color_to_short(fgstr, &bright); |
| |
| /* Don't try to parse screwed-up foreground colors. */ |
| if (fg == -1) |
| return; |
| } else |
| fg = -1; |
| |
| if (*ptr == '\0') { |
| rcfile_error(N_("Missing regex string")); |
| return; |
| } |
| |
| /* Now for the fun part. Start adding regexes to individual strings |
| * in the colorstrings array, woo! */ |
| while (ptr != NULL && *ptr != '\0') { |
| colortype *newcolor; |
| /* The new color structure. */ |
| bool cancelled = FALSE; |
| /* The start expression was bad. */ |
| bool expectend = FALSE; |
| /* Do we expect an end= line? */ |
| |
| if (strncasecmp(ptr, "start=", 6) == 0) { |
| ptr += 6; |
| expectend = TRUE; |
| } |
| |
| if (*ptr != '"') { |
| rcfile_error( |
| N_("Regex strings must begin and end with a \" character")); |
| ptr = parse_next_regex(ptr); |
| continue; |
| } |
| |
| ptr++; |
| |
| fgstr = ptr; |
| ptr = parse_next_regex(ptr); |
| if (ptr == NULL) |
| break; |
| |
| newcolor = (colortype *)nmalloc(sizeof(colortype)); |
| |
| /* Save the starting regex string if it's valid, and set up the |
| * color information. */ |
| if (nregcomp(fgstr, icase ? REG_ICASE : 0)) { |
| newcolor->fg = fg; |
| newcolor->bg = bg; |
| newcolor->bright = bright; |
| newcolor->icase = icase; |
| |
| newcolor->start_regex = mallocstrcpy(NULL, fgstr); |
| newcolor->start = NULL; |
| |
| newcolor->end_regex = NULL; |
| newcolor->end = NULL; |
| |
| newcolor->next = NULL; |
| |
| if (endcolor == NULL) { |
| endsyntax->color = newcolor; |
| #ifdef DEBUG |
| fprintf(stderr, "Starting a new colorstring for fg %hd, bg %hd\n", fg, bg); |
| #endif |
| } else { |
| #ifdef DEBUG |
| fprintf(stderr, "Adding new entry for fg %hd, bg %hd\n", fg, bg); |
| #endif |
| /* Need to recompute endcolor now so we can extend colors to syntaxes */ |
| for (endcolor = endsyntax->color; endcolor->next != NULL; endcolor = endcolor->next) |
| ; |
| endcolor->next = newcolor; |
| } |
| |
| endcolor = newcolor; |
| } else { |
| free(newcolor); |
| cancelled = TRUE; |
| } |
| |
| if (expectend) { |
| if (ptr == NULL || strncasecmp(ptr, "end=", 4) != 0) { |
| rcfile_error( |
| N_("\"start=\" requires a corresponding \"end=\"")); |
| return; |
| } |
| ptr += 4; |
| if (*ptr != '"') { |
| rcfile_error( |
| N_("Regex strings must begin and end with a \" character")); |
| continue; |
| } |
| |
| ptr++; |
| |
| fgstr = ptr; |
| ptr = parse_next_regex(ptr); |
| if (ptr == NULL) |
| break; |
| |
| /* If the start regex was invalid, skip past the end regex to |
| * stay in sync. */ |
| if (cancelled) |
| continue; |
| |
| /* Save the ending regex string if it's valid. */ |
| newcolor->end_regex = (nregcomp(fgstr, icase ? REG_ICASE : |
| 0)) ? mallocstrcpy(NULL, fgstr) : NULL; |
| |
| /* Lame way to skip another static counter */ |
| newcolor->id = endsyntax->nmultis; |
| endsyntax->nmultis++; |
| } |
| } |
| } |
| |
| /* Parse the headers (1st line) of the file which may influence the regex used. */ |
| void parse_headers(char *ptr) |
| { |
| char *regstr; |
| |
| assert(ptr != NULL); |
| |
| if (syntaxes == NULL) { |
| rcfile_error( |
| N_("Cannot add a header regex without a syntax command")); |
| return; |
| } |
| |
| if (*ptr == '\0') { |
| rcfile_error(N_("Missing regex string")); |
| return; |
| } |
| |
| /* Now for the fun part. Start adding regexes to individual strings |
| * in the colorstrings array, woo! */ |
| while (ptr != NULL && *ptr != '\0') { |
| exttype *newheader; |
| /* The new color structure. */ |
| |
| if (*ptr != '"') { |
| rcfile_error( |
| N_("Regex strings must begin and end with a \" character")); |
| ptr = parse_next_regex(ptr); |
| continue; |
| } |
| |
| ptr++; |
| |
| regstr = ptr; |
| ptr = parse_next_regex(ptr); |
| if (ptr == NULL) |
| break; |
| |
| newheader = (exttype *)nmalloc(sizeof(exttype)); |
| |
| /* Save the regex string if it's valid */ |
| if (nregcomp(regstr, 0)) { |
| newheader->ext_regex = mallocstrcpy(NULL, regstr); |
| newheader->ext = NULL; |
| newheader->next = NULL; |
| |
| #ifdef DEBUG |
| fprintf(stderr, "Starting a new header entry: %s\n", newheader->ext_regex); |
| #endif |
| |
| if (endheader == NULL) { |
| endsyntax->headers = newheader; |
| } else { |
| endheader->next = newheader; |
| } |
| |
| endheader = newheader; |
| } else |
| free(newheader); |
| |
| } |
| } |
| |
| |
| /* Parse the linter requested for this syntax. Simple? */ |
| void parse_linter(char *ptr) |
| { |
| assert(ptr != NULL); |
| |
| if (syntaxes == NULL) { |
| rcfile_error( |
| N_("Cannot add a linter without a syntax command")); |
| return; |
| } |
| |
| if (*ptr == '\0') { |
| rcfile_error(N_("Missing linter command")); |
| return; |
| } |
| |
| if (endsyntax->linter != NULL) |
| free(endsyntax->linter); |
| |
| /* Let them unset the linter by using "" */ |
| if (!strcmp(ptr, "\"\"")) |
| endsyntax->linter = NULL; |
| else |
| endsyntax->linter = mallocstrcpy(syntaxes->linter, ptr); |
| } |
| #endif /* ENABLE_COLOR */ |
| |
| |
| |
| /* Check whether the user has unmapped every shortcut for a |
| sequence we consider 'vital', like the exit function */ |
| static void check_vitals_mapped(void) |
| { |
| subnfunc *f; |
| int v; |
| #define VITALS 5 |
| void (*vitals[VITALS])(void) = { do_exit, do_exit, do_cancel, do_cancel, do_cancel }; |
| int inmenus[VITALS] = { MMAIN, MHELP, MWHEREIS, MREPLACE, MGOTOLINE }; |
| |
| for (v = 0; v < VITALS; v++) { |
| for (f = allfuncs; f != NULL; f = f->next) { |
| if (f->scfunc == vitals[v] && f->menus & inmenus[v]) { |
| const sc *s = first_sc_for(inmenus[v], f->scfunc); |
| if (!s) { |
| rcfile_error(N_("Fatal error: no keys mapped for function \"%s\""), |
| f->desc); |
| fprintf(stderr, _("Exiting. If needed, use nano with the -I option " |
| "to adjust your nanorc settings.\n")); |
| exit(1); |
| } |
| break; |
| } |
| } |
| } |
| } |
| |
| /* Parse the rcfile, once it has been opened successfully at rcstream, |
| * and close it afterwards. If syntax_only is TRUE, only allow the file |
| * to contain color syntax commands: syntax, color, and icolor. */ |
| void parse_rcfile(FILE *rcstream |
| #ifdef ENABLE_COLOR |
| , bool syntax_only |
| #endif |
| ) |
| { |
| char *buf = NULL; |
| ssize_t len; |
| size_t n = 0; |
| #ifdef ENABLE_COLOR |
| syntaxtype *end_syn_save = NULL; |
| #endif |
| |
| while ((len = getline(&buf, &n, rcstream)) > 0) { |
| char *ptr, *keyword, *option; |
| int set = 0; |
| size_t i; |
| |
| /* Ignore the newline. */ |
| if (buf[len - 1] == '\n') |
| buf[len - 1] = '\0'; |
| |
| lineno++; |
| ptr = buf; |
| while (isblank(*ptr)) |
| ptr++; |
| |
| /* If we have a blank line or a comment, skip to the next |
| * line. */ |
| if (*ptr == '\0' || *ptr == '#') |
| continue; |
| |
| /* Otherwise, skip to the next space. */ |
| keyword = ptr; |
| ptr = parse_next_word(ptr); |
| |
| |
| /* Handle extending first... */ |
| if (strcasecmp(keyword, "extendsyntax") == 0) { |
| char *syntaxname = ptr; |
| syntaxtype *ts = NULL; |
| |
| ptr = parse_next_word(ptr); |
| for (ts = syntaxes; ts != NULL; ts = ts->next) |
| if (!strcmp(ts->desc, syntaxname)) |
| break; |
| |
| if (ts == NULL) { |
| rcfile_error(N_("Could not find syntax \"%s\" to extend"), syntaxname); |
| continue; |
| } else { |
| end_syn_save = endsyntax; |
| endsyntax = ts; |
| keyword = ptr; |
| ptr = parse_next_word(ptr); |
| } |
| } |
| |
| /* Try to parse the keyword. */ |
| if (strcasecmp(keyword, "set") == 0) { |
| #ifdef ENABLE_COLOR |
| if (syntax_only) |
| rcfile_error( |
| N_("Command \"%s\" not allowed in included file"), |
| keyword); |
| else |
| #endif |
| set = 1; |
| } else if (strcasecmp(keyword, "unset") == 0) { |
| #ifdef ENABLE_COLOR |
| if (syntax_only) |
| rcfile_error( |
| N_("Command \"%s\" not allowed in included file"), |
| keyword); |
| else |
| #endif |
| set = -1; |
| } |
| #ifdef ENABLE_COLOR |
| else if (strcasecmp(keyword, "include") == 0) { |
| if (syntax_only) |
| rcfile_error( |
| N_("Command \"%s\" not allowed in included file"), |
| keyword); |
| else |
| parse_include(ptr); |
| } else if (strcasecmp(keyword, "syntax") == 0) { |
| if (endsyntax != NULL && endcolor == NULL) |
| rcfile_error(N_("Syntax \"%s\" has no color commands"), |
| endsyntax->desc); |
| parse_syntax(ptr); |
| } |
| else if (strcasecmp(keyword, "magic") == 0) { |
| parse_magictype(ptr); |
| } else if (strcasecmp(keyword, "header") == 0) |
| parse_headers(ptr); |
| else if (strcasecmp(keyword, "color") == 0) |
| parse_colors(ptr, FALSE); |
| else if (strcasecmp(keyword, "icolor") == 0) |
| parse_colors(ptr, TRUE); |
| else if (strcasecmp(keyword, "bind") == 0) |
| parse_keybinding(ptr); |
| else if (strcasecmp(keyword, "unbind") == 0) |
| parse_unbinding(ptr); |
| else if (strcasecmp(keyword, "linter") == 0) { |
| parse_linter(ptr); |
| } |
| #endif /* ENABLE_COLOR */ |
| else |
| rcfile_error(N_("Command \"%s\" not understood"), keyword); |
| |
| #ifdef ENABLE_COLOR |
| /* If we temporarily reset emdsyntax to allow extending, reset |
| the value here */ |
| if (end_syn_save != NULL) { |
| endsyntax = end_syn_save; |
| end_syn_save = NULL; |
| } |
| #endif |
| |
| if (set == 0) |
| continue; |
| |
| if (*ptr == '\0') { |
| rcfile_error(N_("Missing flag")); |
| continue; |
| } |
| |
| option = ptr; |
| ptr = parse_next_word(ptr); |
| |
| for (i = 0; rcopts[i].name != NULL; i++) { |
| if (strcasecmp(option, rcopts[i].name) == 0) { |
| #ifdef DEBUG |
| fprintf(stderr, "parse_rcfile(): name = \"%s\"\n", rcopts[i].name); |
| #endif |
| if (set == 1) { |
| if (rcopts[i].flag != 0) |
| /* This option has a flag, so it doesn't take an |
| * argument. */ |
| SET(rcopts[i].flag); |
| else { |
| /* This option doesn't have a flag, so it takes |
| * an argument. */ |
| if (*ptr == '\0') { |
| rcfile_error( |
| N_("Option \"%s\" requires an argument"), |
| rcopts[i].name); |
| break; |
| } |
| option = ptr; |
| if (*option == '"') |
| option++; |
| ptr = parse_argument(ptr); |
| |
| option = mallocstrcpy(NULL, option); |
| #ifdef DEBUG |
| fprintf(stderr, "option = \"%s\"\n", option); |
| #endif |
| |
| /* Make sure option is a valid multibyte |
| * string. */ |
| if (!is_valid_mbstring(option)) { |
| rcfile_error( |
| N_("Option is not a valid multibyte string")); |
| break; |
| } |
| |
| #ifndef DISABLE_OPERATINGDIR |
| if (strcasecmp(rcopts[i].name, "operatingdir") == 0) |
| operating_dir = option; |
| else |
| #endif |
| #ifndef DISABLE_WRAPJUSTIFY |
| if (strcasecmp(rcopts[i].name, "fill") == 0) { |
| if (!parse_num(option, &wrap_at)) { |
| rcfile_error( |
| N_("Requested fill size \"%s\" is invalid"), |
| option); |
| wrap_at = -CHARS_FROM_EOL; |
| } else |
| free(option); |
| } else |
| #endif |
| #ifndef NANO_TINY |
| if (strcasecmp(rcopts[i].name, |
| "matchbrackets") == 0) { |
| matchbrackets = option; |
| if (has_blank_mbchars(matchbrackets)) { |
| rcfile_error( |
| N_("Non-blank characters required")); |
| free(matchbrackets); |
| matchbrackets = NULL; |
| } |
| } else if (strcasecmp(rcopts[i].name, |
| "whitespace") == 0) { |
| whitespace = option; |
| if (mbstrlen(whitespace) != 2 || |
| strlenpt(whitespace) != 2) { |
| rcfile_error( |
| N_("Two single-column characters required")); |
| free(whitespace); |
| whitespace = NULL; |
| } else { |
| whitespace_len[0] = |
| parse_mbchar(whitespace, NULL, |
| NULL); |
| whitespace_len[1] = |
| parse_mbchar(whitespace + |
| whitespace_len[0], NULL, NULL); |
| } |
| } else |
| #endif |
| #ifndef DISABLE_JUSTIFY |
| if (strcasecmp(rcopts[i].name, "punct") == 0) { |
| punct = option; |
| if (has_blank_mbchars(punct)) { |
| rcfile_error( |
| N_("Non-blank characters required")); |
| free(punct); |
| punct = NULL; |
| } |
| } else if (strcasecmp(rcopts[i].name, |
| "brackets") == 0) { |
| brackets = option; |
| if (has_blank_mbchars(brackets)) { |
| rcfile_error( |
| N_("Non-blank characters required")); |
| free(brackets); |
| brackets = NULL; |
| } |
| } else if (strcasecmp(rcopts[i].name, |
| "quotestr") == 0) |
| quotestr = option; |
| else |
| #endif |
| #ifndef NANO_TINY |
| if (strcasecmp(rcopts[i].name, |
| "backupdir") == 0) |
| backup_dir = option; |
| else |
| #endif |
| #ifndef DISABLE_SPELLER |
| if (strcasecmp(rcopts[i].name, "speller") == 0) |
| alt_speller = option; |
| else |
| #endif |
| if (strcasecmp(rcopts[i].name, |
| "tabsize") == 0) { |
| if (!parse_num(option, &tabsize) || |
| tabsize <= 0) { |
| rcfile_error( |
| N_("Requested tab size \"%s\" is invalid"), |
| option); |
| tabsize = -1; |
| } else |
| free(option); |
| } else |
| assert(FALSE); |
| } |
| #ifdef DEBUG |
| fprintf(stderr, "flag = %ld\n", rcopts[i].flag); |
| #endif |
| } else if (rcopts[i].flag != 0) |
| UNSET(rcopts[i].flag); |
| else |
| rcfile_error(N_("Cannot unset flag \"%s\""), |
| rcopts[i].name); |
| /* Looks like we still need this specific hack for undo */ |
| if (strcasecmp(rcopts[i].name, "undo") == 0) |
| shortcut_init(0); |
| break; |
| } |
| } |
| if (rcopts[i].name == NULL) |
| rcfile_error(N_("Unknown flag \"%s\""), option); |
| } |
| |
| #ifdef ENABLE_COLOR |
| if (endsyntax != NULL && endcolor == NULL) |
| rcfile_error(N_("Syntax \"%s\" has no color commands"), |
| endsyntax->desc); |
| #endif |
| |
| free(buf); |
| fclose(rcstream); |
| lineno = 0; |
| |
| check_vitals_mapped(); |
| return; |
| } |
| |
| /* The main rcfile function. It tries to open the system-wide rcfile, |
| * followed by the current user's rcfile. */ |
| void do_rcfile(void) |
| { |
| struct stat rcinfo; |
| FILE *rcstream; |
| |
| nanorc = mallocstrcpy(nanorc, SYSCONFDIR "/nanorc"); |
| |
| /* Don't open directories, character files, or block files. */ |
| if (stat(nanorc, &rcinfo) != -1) { |
| if (S_ISDIR(rcinfo.st_mode) || S_ISCHR(rcinfo.st_mode) || |
| S_ISBLK(rcinfo.st_mode)) |
| rcfile_error(S_ISDIR(rcinfo.st_mode) ? |
| _("\"%s\" is a directory") : |
| _("\"%s\" is a device file"), nanorc); |
| } |
| |
| #ifdef DEBUG |
| fprintf(stderr, "Parsing file \"%s\"\n", nanorc); |
| #endif |
| |
| /* Try to open the system-wide nanorc. */ |
| rcstream = fopen(nanorc, "rb"); |
| if (rcstream != NULL) |
| parse_rcfile(rcstream |
| #ifdef ENABLE_COLOR |
| , FALSE |
| #endif |
| ); |
| |
| #ifdef DISABLE_ROOTWRAPPING |
| /* We've already read SYSCONFDIR/nanorc, if it's there. If we're |
| * root, and --disable-wrapping-as-root is used, turn wrapping off |
| * now. */ |
| if (geteuid() == NANO_ROOT_UID) |
| SET(NO_WRAP); |
| #endif |
| |
| get_homedir(); |
| |
| if (homedir == NULL) |
| rcfile_error(N_("I can't find my home directory! Wah!")); |
| else { |
| #ifndef RCFILE_NAME |
| #define RCFILE_NAME ".nanorc" |
| #endif |
| nanorc = charealloc(nanorc, strlen(homedir) + strlen(RCFILE_NAME) + 2); |
| sprintf(nanorc, "%s/%s", homedir, RCFILE_NAME); |
| |
| /* Don't open directories, character files, or block files. */ |
| if (stat(nanorc, &rcinfo) != -1) { |
| if (S_ISDIR(rcinfo.st_mode) || S_ISCHR(rcinfo.st_mode) || |
| S_ISBLK(rcinfo.st_mode)) |
| rcfile_error(S_ISDIR(rcinfo.st_mode) ? |
| _("\"%s\" is a directory") : |
| _("\"%s\" is a device file"), nanorc); |
| } |
| |
| /* Try to open the current user's nanorc. */ |
| rcstream = fopen(nanorc, "rb"); |
| if (rcstream == NULL) { |
| /* Don't complain about the file's not existing. */ |
| if (errno != ENOENT) |
| rcfile_error(N_("Error reading %s: %s"), nanorc, |
| strerror(errno)); |
| } else |
| parse_rcfile(rcstream |
| #ifdef ENABLE_COLOR |
| , FALSE |
| #endif |
| ); |
| } |
| |
| free(nanorc); |
| nanorc = NULL; |
| |
| if (errors && !ISSET(QUIET)) { |
| errors = FALSE; |
| fprintf(stderr, |
| _("\nPress Enter to continue starting nano.\n")); |
| while (getchar() != '\n') |
| ; |
| } |
| |
| #ifdef ENABLE_COLOR |
| set_colorpairs(); |
| #endif |
| } |
| |
| #endif /* ENABLE_NANORC */ |