nano

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

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