color.c (12353B)
1 /************************************************************************** 2 * color.c -- This file is part of GNU nano. * 3 * * 4 * Copyright (C) 2001-2011, 2013-2025 Free Software Foundation, Inc. * 5 * Copyright (C) 2014-2017, 2020, 2021 Benno Schulenberg * 6 * * 7 * GNU nano is free software: you can redistribute it and/or modify * 8 * it under the terms of the GNU General Public License as published * 9 * by the Free Software Foundation, either version 3 of the License, * 10 * or (at your option) any later version. * 11 * * 12 * GNU nano is distributed in the hope that it will be useful, * 13 * but WITHOUT ANY WARRANTY; without even the implied warranty * 14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * 15 * See the GNU General Public License for more details. * 16 * * 17 * You should have received a copy of the GNU General Public License * 18 * along with this program. If not, see https://gnu.org/licenses/. * 19 * * 20 **************************************************************************/ 21 22 #include "prototypes.h" 23 24 #ifdef ENABLE_COLOR 25 26 #include <errno.h> 27 #ifdef HAVE_MAGIC_H 28 #include <magic.h> 29 #endif 30 #include <string.h> 31 32 static bool defaults_allowed = FALSE; 33 /* Whether ncurses accepts -1 to mean "default color". */ 34 35 /* Initialize the color pairs for nano's interface. */ 36 void set_interface_colorpairs(void) 37 { 38 #ifdef HAVE_USE_DEFAULT_COLORS 39 /* Ask ncurses to allow -1 to mean "default color". */ 40 defaults_allowed = (use_default_colors() == OK); 41 #endif 42 43 /* Initialize the color pairs for nano's interface elements. */ 44 for (size_t index = 0; index < NUMBER_OF_ELEMENTS; index++) { 45 colortype *combo = color_combo[index]; 46 47 if (combo != NULL) { 48 if (!defaults_allowed) { 49 if (combo->fg == THE_DEFAULT) 50 combo->fg = COLOR_WHITE; 51 if (combo->bg == THE_DEFAULT) 52 combo->bg = COLOR_BLACK; 53 } 54 init_pair(index + 1, combo->fg, combo->bg); 55 interface_color_pair[index] = COLOR_PAIR(index + 1) | combo->attributes; 56 rescind_colors = FALSE; 57 } else { 58 if (index == FUNCTION_TAG || index == SCROLL_BAR) 59 interface_color_pair[index] = A_NORMAL; 60 else if (index == GUIDE_STRIPE) 61 interface_color_pair[index] = A_REVERSE; 62 else if (index == SPOTLIGHTED) { 63 init_pair(index + 1, COLOR_BLACK, COLOR_YELLOW + (COLORS > 15 ? 8 : 0)); 64 interface_color_pair[index] = COLOR_PAIR(index + 1); 65 } else if (index == MINI_INFOBAR || index == PROMPT_BAR) 66 interface_color_pair[index] = interface_color_pair[TITLE_BAR]; 67 else if (index == ERROR_MESSAGE) { 68 init_pair(index + 1, COLOR_WHITE, COLOR_RED); 69 interface_color_pair[index] = COLOR_PAIR(index + 1) | A_BOLD; 70 } else 71 interface_color_pair[index] = hilite_attribute; 72 } 73 74 free(color_combo[index]); 75 } 76 77 if (rescind_colors) { 78 interface_color_pair[SPOTLIGHTED] = A_REVERSE; 79 interface_color_pair[ERROR_MESSAGE] = A_REVERSE; 80 } 81 } 82 83 /* Assign a pair number to each of the foreground/background color combinations 84 * in the given syntax, giving identical combinations the same number. */ 85 void set_syntax_colorpairs(syntaxtype *sntx) 86 { 87 short number = NUMBER_OF_ELEMENTS; 88 colortype *older; 89 90 for (colortype *ink = sntx->color; ink != NULL; ink = ink->next) { 91 if (!defaults_allowed) { 92 if (ink->fg == THE_DEFAULT) 93 ink->fg = COLOR_WHITE; 94 if (ink->bg == THE_DEFAULT) 95 ink->bg = COLOR_BLACK; 96 } 97 98 older = sntx->color; 99 100 while (older != ink && (older->fg != ink->fg || older->bg != ink->bg)) 101 older = older->next; 102 103 ink->pairnum = (older != ink) ? older->pairnum : ++number; 104 105 ink->attributes |= COLOR_PAIR(ink->pairnum); 106 } 107 } 108 109 /* Initialize the color pairs for the current syntax. */ 110 void prepare_palette(void) 111 { 112 short number = NUMBER_OF_ELEMENTS; 113 114 /* For each unique pair number, tell ncurses the combination of colors. */ 115 for (colortype *ink = openfile->syntax->color; ink != NULL; ink = ink->next) 116 if (ink->pairnum > number) { 117 init_pair(ink->pairnum, ink->fg, ink->bg); 118 number = ink->pairnum; 119 } 120 121 have_palette = TRUE; 122 } 123 124 /* Try to match the given shibboleth string with one of the regexes in 125 * the list starting at head. Return TRUE upon success. */ 126 bool found_in_list(regexlisttype *head, const char *shibboleth) 127 { 128 for (regexlisttype *item = head; item != NULL; item = item->next) 129 if (regexec(item->one_rgx, shibboleth, 0, NULL, 0) == 0) 130 return TRUE; 131 132 return FALSE; 133 } 134 135 /* Find a syntax that applies to the current buffer, based upon filename 136 * or buffer content, and load and prime this syntax when needed. */ 137 void find_and_prime_applicable_syntax(void) 138 { 139 syntaxtype *sntx = NULL; 140 141 /* If the rcfiles were not read, or contained no syntaxes, get out. */ 142 if (syntaxes == NULL) 143 return; 144 145 /* If we specified a syntax-override string, use it. */ 146 if (syntaxstr != NULL) { 147 /* An override of "none" is like having no syntax at all. */ 148 if (strcmp(syntaxstr, "none") == 0) 149 return; 150 151 for (sntx = syntaxes; sntx != NULL; sntx = sntx->next) 152 if (strcmp(sntx->name, syntaxstr) == 0) 153 break; 154 155 if (sntx == NULL && !inhelp) 156 statusline(ALERT, _("Unknown syntax name: %s"), syntaxstr); 157 } 158 159 /* If no syntax-override string was specified, or it didn't match, 160 * try finding a syntax based on the filename (extension). */ 161 if (sntx == NULL && !inhelp) { 162 char *fullname = get_full_path(openfile->filename); 163 164 if (fullname == NULL) 165 fullname = mallocstrcpy(fullname, openfile->filename); 166 167 for (sntx = syntaxes; sntx != NULL; sntx = sntx->next) 168 if (found_in_list(sntx->extensions, fullname)) 169 break; 170 171 free(fullname); 172 } 173 174 /* If the filename didn't match anything, try the first line. */ 175 if (sntx == NULL && !inhelp) { 176 for (sntx = syntaxes; sntx != NULL; sntx = sntx->next) 177 if (found_in_list(sntx->headers, openfile->filetop->data)) 178 break; 179 } 180 181 #ifdef HAVE_LIBMAGIC 182 /* If we still don't have an answer, try using magic (when requested). */ 183 if (sntx == NULL && !inhelp && ISSET(USE_MAGIC)) { 184 struct stat fileinfo; 185 magic_t cookie = NULL; 186 const char *magicstring = NULL; 187 188 if (stat(openfile->filename, &fileinfo) == 0) { 189 /* Open the magic database and get a diagnosis of the file. */ 190 cookie = magic_open(MAGIC_SYMLINK | 191 #ifdef DEBUG 192 MAGIC_DEBUG | MAGIC_CHECK | 193 #endif 194 MAGIC_ERROR); 195 if (cookie == NULL || magic_load(cookie, NULL) < 0) 196 statusline(ALERT, _("magic_load() failed: %s"), strerror(errno)); 197 else { 198 magicstring = magic_file(cookie, openfile->filename); 199 if (magicstring == NULL) 200 statusline(ALERT, _("magic_file(%s) failed: %s"), 201 openfile->filename, magic_error(cookie)); 202 } 203 } 204 205 /* Now try and find a syntax that matches the magic string. */ 206 if (magicstring != NULL) { 207 for (sntx = syntaxes; sntx != NULL; sntx = sntx->next) 208 if (found_in_list(sntx->magics, magicstring)) 209 break; 210 } 211 212 if (stat(openfile->filename, &fileinfo) == 0) 213 magic_close(cookie); 214 } 215 #endif /* HAVE_LIBMAGIC */ 216 217 /* If nothing at all matched, see if there is a default syntax. */ 218 if (sntx == NULL && !inhelp) { 219 for (sntx = syntaxes; sntx != NULL; sntx = sntx->next) 220 if (strcmp(sntx->name, "default") == 0) 221 break; 222 } 223 224 /* When the syntax isn't loaded yet, parse it and initialize its colors. */ 225 if (sntx != NULL && sntx->filename != NULL) { 226 parse_one_include(sntx->filename, sntx); 227 set_syntax_colorpairs(sntx); 228 } 229 230 openfile->syntax = sntx; 231 } 232 233 /* Determine whether the matches of multiline regexes are still the same, 234 * and if not, schedule a screen refresh, so things will be repainted. */ 235 void check_the_multis(linestruct *line) 236 { 237 const colortype *ink; 238 bool astart, anend; 239 regmatch_t startmatch, endmatch; 240 char *afterstart; 241 242 /* If there is no syntax or no multiline regex, there is nothing to do. */ 243 if (!openfile->syntax || openfile->syntax->multiscore == 0) 244 return; 245 246 if (line->multidata == NULL) { 247 refresh_needed = TRUE; 248 return; 249 } 250 251 for (ink = openfile->syntax->color; ink != NULL; ink = ink->next) { 252 /* If it's not a multiline regex, skip. */ 253 if (ink->end == NULL) 254 continue; 255 256 astart = (regexec(ink->start, line->data, 1, &startmatch, 0) == 0); 257 afterstart = line->data + (astart ? startmatch.rm_eo : 0); 258 anend = (regexec(ink->end, afterstart, 1, &endmatch, 0) == 0); 259 260 /* Check whether the multidata still matches the current situation. */ 261 if (line->multidata[ink->id] == NOTHING) { 262 if (!astart) 263 continue; 264 } else if (line->multidata[ink->id] == WHOLELINE) { 265 /* Ensure that a detected start match is not actually an end match. */ 266 if (!anend && (!astart || regexec(ink->end, line->data, 1, 267 &endmatch, 0) != 0)) 268 continue; 269 } else if (line->multidata[ink->id] == JUSTONTHIS) { 270 if (astart && anend && regexec(ink->start, line->data + startmatch.rm_eo + 271 endmatch.rm_eo, 1, &startmatch, 0) != 0) 272 continue; 273 } else if (line->multidata[ink->id] == STARTSHERE) { 274 if (astart && !anend) 275 continue; 276 } else if (line->multidata[ink->id] == ENDSHERE) { 277 if (!astart && anend) 278 continue; 279 } 280 281 /* There is a mismatch, so something changed: repaint. */ 282 refresh_needed = TRUE; 283 perturbed = TRUE; 284 return; 285 } 286 } 287 288 /* Precalculate the multi-line start and end regex info so we can 289 * speed up rendering (with any hope at all...). */ 290 void precalc_multicolorinfo(void) 291 { 292 const colortype *ink; 293 regmatch_t startmatch, endmatch; 294 linestruct *line, *tailline; 295 296 if (!openfile->syntax || openfile->syntax->multiscore == 0 || ISSET(NO_SYNTAX)) 297 return; 298 299 //#define TIMEPRECALC 123 300 #ifdef TIMEPRECALC 301 #include <time.h> 302 clock_t start = clock(); 303 #endif 304 305 /* For each line, allocate cache space for the multiline-regex info. */ 306 for (line = openfile->filetop; line != NULL; line = line->next) 307 if (!line->multidata) 308 line->multidata = nmalloc(openfile->syntax->multiscore * sizeof(short)); 309 310 for (ink = openfile->syntax->color; ink != NULL; ink = ink->next) { 311 /* If this is not a multi-line regex, skip it. */ 312 if (ink->end == NULL) 313 continue; 314 315 for (line = openfile->filetop; line != NULL; line = line->next) { 316 int index = 0; 317 318 /* Assume nothing applies until proven otherwise below. */ 319 line->multidata[ink->id] = NOTHING; 320 321 /* When the line contains a start match, look for an end, 322 * and if found, mark all the lines that are affected. */ 323 while (regexec(ink->start, line->data + index, 1, &startmatch, 324 (index == 0) ? 0 : REG_NOTBOL) == 0) { 325 /* Begin looking for an end match after the start match. */ 326 index += startmatch.rm_eo; 327 328 /* If there is an end match on this same line, mark the line, 329 * but continue looking for other starts after it. */ 330 if (regexec(ink->end, line->data + index, 1, &endmatch, 331 (index == 0) ? 0 : REG_NOTBOL) == 0) { 332 line->multidata[ink->id] = JUSTONTHIS; 333 334 index += endmatch.rm_eo; 335 336 /* If the total match has zero length, force an advance. */ 337 if (startmatch.rm_eo - startmatch.rm_so + endmatch.rm_eo == 0) { 338 /* When at end-of-line, there is no other start. */ 339 if (line->data[index] == '\0') 340 break; 341 index = step_right(line->data, index); 342 } 343 344 continue; 345 } 346 347 /* Look for an end match on later lines. */ 348 tailline = line->next; 349 350 while (tailline && regexec(ink->end, tailline->data, 351 1, &endmatch, 0) != 0) 352 tailline = tailline->next; 353 354 line->multidata[ink->id] = STARTSHERE; 355 356 // Note that this also advances the line in the main loop. 357 for (line = line->next; line != tailline; line = line->next) 358 line->multidata[ink->id] = WHOLELINE; 359 360 if (tailline == NULL) { 361 line = openfile->filebot; 362 break; 363 } 364 365 tailline->multidata[ink->id] = ENDSHERE; 366 367 /* Look for a possible new start after the end match. */ 368 index = endmatch.rm_eo; 369 } 370 } 371 } 372 373 #ifdef TIMEPRECALC 374 statusline(INFO, "Precalculation: %.1f ms", 1000 * (double)(clock() - start) / CLOCKS_PER_SEC); 375 napms(1200); 376 #endif 377 } 378 379 #endif /* ENABLE_COLOR */