color.c 13.3 KB
Newer Older
1
/**************************************************************************
2
 *   color.c  --  This file is part of GNU nano.                          *
3
 *                                                                        *
4
 *   Copyright (C) 2001-2011, 2013-2017 Free Software Foundation, Inc.    *
5
 *   Copyright (C) 2014, 2015, 2016, 2017 Benno Schulenberg               *
6
 *                                                                        *
7
8
9
10
 *   GNU nano 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 of the License,    *
 *   or (at your option) any later version.                               *
11
 *                                                                        *
12
13
14
15
 *   GNU nano 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.                 *
16
17
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
18
 *   along with this program.  If not, see http://www.gnu.org/licenses/.  *
19
20
21
 *                                                                        *
 **************************************************************************/

22
#include "proto.h"
23

24
25
26
27
#include <errno.h>
#ifdef HAVE_MAGIC_H
#include <magic.h>
#endif
28
29
#include <string.h>
#include <unistd.h>
30

31
#ifndef DISABLE_COLOR
Chris Allegretta's avatar
Chris Allegretta committed
32

33
34
/* Initialize the colors for nano's interface, and assign pair numbers
 * for the colors in each syntax. */
35
void set_colorpairs(void)
36
{
37
    const syntaxtype *sint;
38
39
    bool using_defaults = FALSE;
    short foreground, background;
40
41
    size_t i;

42
    /* Tell ncurses to enable colors. */
43
44
45
    start_color();

#ifdef HAVE_USE_DEFAULT_COLORS
46
47
    /* Allow using the default colors, if available. */
    using_defaults = (use_default_colors() != ERR);
48
49
#endif

50
    /* Initialize the color pairs for nano's interface elements. */
51
    for (i = 0; i < NUMBER_OF_ELEMENTS; i++) {
52
53
	bool bright = FALSE;

54
55
56
	if (specified_color_combo[i] != NULL &&
			parse_color_names(specified_color_combo[i],
				&foreground, &background, &bright)) {
57
58
59
60
61
	    if (foreground == -1 && !using_defaults)
		foreground = COLOR_WHITE;
	    if (background == -1 && !using_defaults)
		background = COLOR_BLACK;
	    init_pair(i + 1, foreground, background);
62
	    interface_color_pair[i] =
63
			COLOR_PAIR(i + 1) | (bright ? A_BOLD : A_NORMAL);
64
	} else {
65
	    if (i != FUNCTION_TAG)
66
		interface_color_pair[i] = hilite_attribute;
67
	    else
68
		interface_color_pair[i] = A_NORMAL;
69
70
	}

71
72
	free(specified_color_combo[i]);
	specified_color_combo[i] = NULL;
73
    }
74

75
76
    /* For each syntax, go through its list of colors and assign each
     * its pair number, giving identical color pairs the same number. */
77
78
    for (sint = syntaxes; sint != NULL; sint = sint->next) {
	colortype *ink;
79
	int new_number = NUMBER_OF_ELEMENTS + 1;
80

81
82
	for (ink = sint->color; ink != NULL; ink = ink->next) {
	    const colortype *beforenow = sint->color;
83

84
85
86
	    while (beforenow != ink && (beforenow->fg != ink->fg ||
					beforenow->bg != ink->bg ||
					beforenow->bright != ink->bright))
87
		beforenow = beforenow->next;
88

89
90
	    if (beforenow != ink)
		ink->pairnum = beforenow->pairnum;
91
92
	    else
		ink->pairnum = new_number++;
93

94
95
	    ink->attributes = COLOR_PAIR(ink->pairnum) |
				(ink->bright ? A_BOLD : A_NORMAL);
96
97
98
	}
    }
}
99

100
/* Initialize the color information. */
101
void color_init(void)
102
{
103
    const colortype *ink;
104
105
106
107
108
109
110
    bool using_defaults = FALSE;
    short foreground, background;

    /* If the terminal is not capable of colors, forget it. */
    if (!has_colors())
	return;

111
#ifdef HAVE_USE_DEFAULT_COLORS
112
113
    /* Allow using the default colors, if available. */
    using_defaults = (use_default_colors() != ERR);
114
#endif
115

116
    /* For each coloring expression, initialize the color pair. */
117
118
119
    for (ink = openfile->colorstrings; ink != NULL; ink = ink->next) {
	foreground = ink->fg;
	background = ink->bg;
120

121
122
	if (foreground == -1 && !using_defaults)
	    foreground = COLOR_WHITE;
123

124
125
	if (background == -1 && !using_defaults)
	    background = COLOR_BLACK;
126

127
	init_pair(ink->pairnum, foreground, background);
128
#ifdef DEBUG
129
	fprintf(stderr, "init_pair(): fg = %hd, bg = %hd\n", foreground, background);
130
#endif
131
    }
132
133

    have_palette = TRUE;
134
135
}

136
137
138
139
140
/* Try to match the given shibboleth string with one of the regexes in
 * the list starting at head.  Return TRUE upon success. */
bool found_in_list(regexlisttype *head, const char *shibboleth)
{
    regexlisttype *item;
141
    regex_t rgx;
142
143

    for (item = head; item != NULL; item = item->next) {
144
	regcomp(&rgx, fixbounds(item->full_regex), NANO_REG_EXTENDED);
145

146
147
	if (regexec(&rgx, shibboleth, 0, NULL, 0) == 0) {
	    regfree(&rgx);
148
	    return TRUE;
149
	}
150
151

	regfree(&rgx);
152
153
154
155
156
    }

    return FALSE;
}

157
/* Update the color information based on the current filename and content. */
158
void color_update(void)
159
{
160
    syntaxtype *sint = NULL;
161
    colortype *ink;
162

163
164
165
166
    /* If the rcfiles were not read, or contained no syntaxes, get out. */
    if (syntaxes == NULL)
	return;

167
    /* If we specified a syntax-override string, use it. */
168
    if (syntaxstr != NULL) {
169
	/* An override of "none" is like having no syntax at all. */
170
171
172
	if (strcmp(syntaxstr, "none") == 0)
	    return;

173
	for (sint = syntaxes; sint != NULL; sint = sint->next) {
174
	    if (strcmp(sint->name, syntaxstr) == 0)
175
		break;
176
	}
177

178
	if (sint == NULL)
179
	    statusline(ALERT, _("Unknown syntax name: %s"), syntaxstr);
180
    }
181

182
183
    /* If no syntax-override string was specified, or it didn't match,
     * try finding a syntax based on the filename (extension). */
184
    if (sint == NULL) {
185
186
	char *reserved = charalloc(PATH_MAX + 1);
	char *currentdir = getcwd(reserved, PATH_MAX + 1);
187
188
189
	char *joinednames = charalloc(PATH_MAX + 1);
	char *fullname = NULL;

190
191
192
	if (currentdir == NULL)
	    free(reserved);
	else {
193
194
195
	    /* Concatenate the current working directory with the
	     * specified filename, and canonicalize the result. */
	    sprintf(joinednames, "%s/%s", currentdir, openfile->filename);
196
	    fullname = get_full_path(joinednames);
197
198
199
200
201
202
	    free(currentdir);
	}

	if (fullname == NULL)
	    fullname = mallocstrcpy(fullname, openfile->filename);

203
	for (sint = syntaxes; sint != NULL; sint = sint->next) {
204
	    if (found_in_list(sint->extensions, fullname))
205
		break;
206
207
	}

208
209
	free(joinednames);
	free(fullname);
210
    }
211

212
213
    /* If the filename didn't match anything, try the first line. */
    if (sint == NULL) {
214
#ifdef DEBUG
215
	fprintf(stderr, "No result from file extension, trying headerline...\n");
216
#endif
217
218
219
	for (sint = syntaxes; sint != NULL; sint = sint->next) {
	    if (found_in_list(sint->headers, openfile->fileage->data))
		break;
220
	}
221
    }
222

223
#ifdef HAVE_LIBMAGIC
224
225
226
227
228
    /* If we still don't have an answer, try using magic. */
    if (sint == NULL) {
	struct stat fileinfo;
	magic_t cookie = NULL;
	const char *magicstring = NULL;
229
#ifdef DEBUG
230
	fprintf(stderr, "No result from headerline either, trying libmagic...\n");
231
#endif
232
233
234
	if (stat(openfile->filename, &fileinfo) == 0) {
	    /* Open the magic database and get a diagnosis of the file. */
	    cookie = magic_open(MAGIC_SYMLINK |
235
#ifdef DEBUG
236
				    MAGIC_DEBUG | MAGIC_CHECK |
237
#endif
238
				    MAGIC_ERROR);
239
	    if (cookie == NULL || magic_load(cookie, NULL) < 0)
240
		statusline(ALERT, _("magic_load() failed: %s"), strerror(errno));
241
242
243
	    else {
		magicstring = magic_file(cookie, openfile->filename);
		if (magicstring == NULL)
244
		    statusline(ALERT, _("magic_file(%s) failed: %s"),
245
				openfile->filename, magic_error(cookie));
246
#ifdef DEBUG
247
		fprintf(stderr, "Returned magic string is: %s\n", magicstring);
248
249
#endif
	    }
250
	}
251

252
253
254
	/* Now try and find a syntax that matches the magic string. */
	if (magicstring != NULL) {
	    for (sint = syntaxes; sint != NULL; sint = sint->next) {
255
		if (found_in_list(sint->magics, magicstring))
256
		    break;
257
258
	    }
	}
259
260
261

	if (stat(openfile->filename, &fileinfo) == 0)
	    magic_close(cookie);
262
    }
263
#endif /* HAVE_LIBMAGIC */
264

265
    /* If nothing at all matched, see if there is a default syntax. */
266
    if (sint == NULL) {
267
	for (sint = syntaxes; sint != NULL; sint = sint->next) {
268
	    if (strcmp(sint->name, "default") == 0)
269
		break;
270
	}
271
    }
272

273
274
275
    openfile->syntax = sint;
    openfile->colorstrings = (sint == NULL ? NULL : sint->color);

276
277
    /* If a syntax was found, compile its specified regexes (which have
     * already been checked for validity when they were read in). */
278
    for (ink = openfile->colorstrings; ink != NULL; ink = ink->next) {
279
280
	if (ink->start == NULL) {
	    ink->start = (regex_t *)nmalloc(sizeof(regex_t));
281
	    regcomp(ink->start, fixbounds(ink->start_regex), ink->rex_flags);
282
	}
283

284
285
	if (ink->end_regex != NULL && ink->end == NULL) {
	    ink->end = (regex_t *)nmalloc(sizeof(regex_t));
286
	    regcomp(ink->end, fixbounds(ink->end_regex), ink->rex_flags);
287
288
	}
    }
289
290
}

291
292
/* Determine whether the matches of multiline regexes are still the same,
 * and if not, schedule a screen refresh, so things will be repainted. */
293
void check_the_multis(filestruct *line)
294
{
295
    const colortype *ink;
296
    bool astart, anend;
297
    regmatch_t startmatch, endmatch;
298

299
300
    /* If there is no syntax or no multiline regex, there is nothing to do. */
    if (openfile->syntax == NULL || openfile->syntax->nmultis == 0)
301
302
	return;

303
    for (ink = openfile->colorstrings; ink != NULL; ink = ink->next) {
304
	/* If it's not a multiline regex, skip. */
305
	if (ink->end == NULL)
306
307
	    continue;

308
	alloc_multidata_if_needed(line);
309

310
311
	astart = (regexec(ink->start, line->data, 1, &startmatch, 0) == 0);
	anend = (regexec(ink->end, line->data, 1, &endmatch, 0) == 0);
312

313
314
	/* Check whether the multidata still matches the current situation. */
	if (line->multidata[ink->id] == CNONE ||
315
			line->multidata[ink->id] == CWHOLELINE) {
316
317
318
319
320
321
322
323
324
325
326
327
	    if (!astart && !anend)
		continue;
	} else if (line->multidata[ink->id] == CSTARTENDHERE) {
	    if (astart && anend && startmatch.rm_so < endmatch.rm_so)
		continue;
	} else if (line->multidata[ink->id] == CBEGINBEFORE) {
	    if (!astart && anend)
		continue;
	} else if (line->multidata[ink->id] == CENDAFTER) {
	    if (astart && !anend)
		continue;
	}
328

329
	/* There is a mismatch, so something changed: repaint. */
330
331
	refresh_needed = TRUE;
	return;
332
333
    }
}
334

335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
/* Allocate (for one line) the cache space for multiline color regexes. */
void alloc_multidata_if_needed(filestruct *fileptr)
{
    int i;

    if (fileptr->multidata == NULL) {
	fileptr->multidata = (short *)nmalloc(openfile->syntax->nmultis * sizeof(short));

	for (i = 0; i < openfile->syntax->nmultis; i++)
	    fileptr->multidata[i] = -1;
    }
}

/* Precalculate the multi-line start and end regex info so we can
 * speed up rendering (with any hope at all...). */
void precalc_multicolorinfo(void)
{
352
    const colortype *ink;
353
    regmatch_t startmatch, endmatch;
354
    filestruct *line, *tailline;
355
356
357
358
359

    if (openfile->colorstrings == NULL || ISSET(NO_COLOR_SYNTAX))
	return;

#ifdef DEBUG
360
    fprintf(stderr, "Precalculating the multiline color info...\n");
361
362
#endif

363
    for (ink = openfile->colorstrings; ink != NULL; ink = ink->next) {
364
	/* If this is not a multi-line regex, skip it. */
365
	if (ink->end == NULL)
366
367
	    continue;

368
	for (line = openfile->fileage; line != NULL; line = line->next) {
369
	    int index = 0;
370

371
	    alloc_multidata_if_needed(line);
372
	    /* Assume nothing applies until proven otherwise below. */
373
	    line->multidata[ink->id] = CNONE;
374

375
376
377
378
379
380
	    /* For an unpaired start match, mark all remaining lines. */
	    if (line->prev && line->prev->multidata[ink->id] == CWOULDBE) {
		line->multidata[ink->id] = CWOULDBE;
		continue;
	    }

381
382
	    /* When the line contains a start match, look for an end, and if
	     * found, mark all the lines that are affected. */
383
	    while (regexec(ink->start, line->data + index, 1,
384
			&startmatch, (index == 0) ? 0 : REG_NOTBOL) == 0) {
385
		/* Begin looking for an end match after the start match. */
386
		index += startmatch.rm_eo;
387

388
389
		/* If there is an end match on this line, mark the line, but
		 * continue looking for other starts after it. */
390
		if (regexec(ink->end, line->data + index, 1,
391
			&endmatch, (index == 0) ? 0 : REG_NOTBOL) == 0) {
392
		    line->multidata[ink->id] = CSTARTENDHERE;
393
		    index += endmatch.rm_eo;
394
		    /* If both start and end are mere anchors, step ahead. */
395
		    if (startmatch.rm_so == startmatch.rm_eo &&
396
397
398
399
400
401
				endmatch.rm_so == endmatch.rm_eo) {
			/* When at end-of-line, we're done. */
			if (line->data[index] == '\0')
			    break;
			index = move_mbright(line->data, index);
		    }
402
403
404
		    continue;
		}

405
		/* Look for an end match on later lines. */
406
		tailline = line->next;
407

408
409
		while (tailline != NULL) {
		    if (regexec(ink->end, tailline->data, 1, &endmatch, 0) == 0)
410
			break;
411
		    tailline = tailline->next;
412
413
		}

414
415
		if (tailline == NULL) {
		    line->multidata[ink->id] = CWOULDBE;
416
		    break;
417
		}
418

419
420
		/* We found it, we found it, la la la la la.  Mark all
		 * the lines in between and the end properly. */
421
		line->multidata[ink->id] = CENDAFTER;
422

423
		for (line = line->next; line != tailline; line = line->next) {
424
425
		    alloc_multidata_if_needed(line);
		    line->multidata[ink->id] = CWHOLELINE;
426
427
		}

428
429
		alloc_multidata_if_needed(tailline);
		tailline->multidata[ink->id] = CBEGINBEFORE;
430

431
		/* Begin looking for a new start after the end match. */
432
		index = endmatch.rm_eo;
433
434
435
436
437
	    }
	}
    }
}

438
#endif /* !DISABLE_COLOR */