1 /* vifm
2  * Copyright (C) 2001 Ken Steen.
3  * Copyright (C) 2011 xaizek.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18  */
19 
20 #include "color_scheme.h"
21 
22 #include <curses.h>
23 #include <regex.h> /* regcomp() regexec() */
24 
25 #include <assert.h> /* assert() */
26 #include <limits.h> /* INT_MAX */
27 #include <stddef.h> /* NULL size_t */
28 #include <stdio.h> /* snprintf() */
29 #include <stdlib.h> /* free() */
30 #include <string.h> /* strcpy() strlen() */
31 
32 #include "../cfg/config.h"
33 #include "../compat/dtype.h"
34 #include "../compat/fs_limits.h"
35 #include "../compat/os.h"
36 #include "../compat/reallocarray.h"
37 #include "../engine/completion.h"
38 #include "../modes/dialogs/msg_dialog.h"
39 #include "../utils/fs.h"
40 #include "../utils/fsddata.h"
41 #include "../utils/macros.h"
42 #include "../utils/matchers.h"
43 #include "../utils/str.h"
44 #include "../utils/string_array.h"
45 #include "../utils/utils.h"
46 #include "../status.h"
47 #include "color_manager.h"
48 #include "statusbar.h"
49 #include "ui.h"
50 
51 char *HI_GROUPS[] = {
52 	[WIN_COLOR]          = "Win",
53 	[DIRECTORY_COLOR]    = "Directory",
54 	[LINK_COLOR]         = "Link",
55 	[BROKEN_LINK_COLOR]  = "BrokenLink",
56 	[HARD_LINK_COLOR]    = "HardLink",
57 	[SOCKET_COLOR]       = "Socket",
58 	[DEVICE_COLOR]       = "Device",
59 	[FIFO_COLOR]         = "Fifo",
60 	[EXECUTABLE_COLOR]   = "Executable",
61 	[SELECTED_COLOR]     = "Selected",
62 	[CURR_LINE_COLOR]    = "CurrLine",
63 	[TOP_LINE_COLOR]     = "TopLine",
64 	[TOP_LINE_SEL_COLOR] = "TopLineSel",
65 	[STATUS_LINE_COLOR]  = "StatusLine",
66 	[WILD_MENU_COLOR]    = "WildMenu",
67 	[CMD_LINE_COLOR]     = "CmdLine",
68 	[ERROR_MSG_COLOR]    = "ErrorMsg",
69 	[BORDER_COLOR]       = "Border",
70 	[OTHER_LINE_COLOR]   = "OtherLine",
71 	[JOB_LINE_COLOR]     = "JobLine",
72 	[SUGGEST_BOX_COLOR]  = "SuggestBox",
73 	[MISMATCH_COLOR]     = "CmpMismatch",
74 	[AUX_WIN_COLOR]      = "AuxWin",
75 	[TAB_LINE_COLOR]     = "TabLine",
76 	[TAB_LINE_SEL_COLOR] = "TabLineSel",
77 	[USER1_COLOR]        = "User1",
78 	[USER2_COLOR]        = "User2",
79 	[USER3_COLOR]        = "User3",
80 	[USER4_COLOR]        = "User4",
81 	[USER5_COLOR]        = "User5",
82 	[USER6_COLOR]        = "User6",
83 	[USER7_COLOR]        = "User7",
84 	[USER8_COLOR]        = "User8",
85 	[USER9_COLOR]        = "User9",
86 	[OTHER_WIN_COLOR]    = "OtherWin",
87 	[LINE_NUM_COLOR]     = "LineNr",
88 	[ODD_LINE_COLOR]     = "OddLine",
89 };
90 ARRAY_GUARD(HI_GROUPS, MAXNUM_COLOR);
91 
92 const char *HI_GROUPS_DESCR[] = {
93 	[WIN_COLOR]          = "base highlight of every window",
94 	[DIRECTORY_COLOR]    = "directories and links to directories",
95 	[LINK_COLOR]         = "symbolic links",
96 	[BROKEN_LINK_COLOR]  = "dangling symbolic links",
97 	[HARD_LINK_COLOR]    = "regular files with more than one hard link",
98 	[SOCKET_COLOR]       = "sockets",
99 	[DEVICE_COLOR]       = "device file",
100 	[FIFO_COLOR]         = "pipe",
101 	[EXECUTABLE_COLOR]   = "executable file",
102 	[SELECTED_COLOR]     = "selected item",
103 	[CURR_LINE_COLOR]    = "cursor in active pane",
104 	[TOP_LINE_COLOR]     = "base top line highlight",
105 	[TOP_LINE_SEL_COLOR] = "active window title",
106 	[STATUS_LINE_COLOR]  = "status line",
107 	[WILD_MENU_COLOR]    = "completion box",
108 	[CMD_LINE_COLOR]     = "status/command-line bar",
109 	[ERROR_MSG_COLOR]    = "error message",
110 	[BORDER_COLOR]       = "horizontal borders",
111 	[OTHER_LINE_COLOR]   = "cursor in inactive pane",
112 	[JOB_LINE_COLOR]     = "job bar",
113 	[SUGGEST_BOX_COLOR]  = "suggestion box",
114 	[MISMATCH_COLOR]     = "mismatched diff entries",
115 	[AUX_WIN_COLOR]      = "auxiliary part of window",
116 	[TAB_LINE_COLOR]     = "tab line",
117 	[TAB_LINE_SEL_COLOR] = "tip of selected tab",
118 	[USER1_COLOR]        = "user color #1",
119 	[USER2_COLOR]        = "user color #2",
120 	[USER3_COLOR]        = "user color #3",
121 	[USER4_COLOR]        = "user color #4",
122 	[USER5_COLOR]        = "user color #5",
123 	[USER6_COLOR]        = "user color #6",
124 	[USER7_COLOR]        = "user color #7",
125 	[USER8_COLOR]        = "user color #8",
126 	[USER9_COLOR]        = "user color #9",
127 	[OTHER_WIN_COLOR]    = "additional highlighting of inactive pane",
128 	[LINE_NUM_COLOR]     = "color of line number column in panes",
129 	[ODD_LINE_COLOR]     = "color of every second entry line in a pane",
130 };
131 ARRAY_GUARD(HI_GROUPS_DESCR, ARRAY_LEN(HI_GROUPS));
132 
133 char *LIGHT_COLOR_NAMES[8] = {
134 	[COLOR_BLACK]   = "lightblack",
135 	[COLOR_RED]     = "lightred",
136 	[COLOR_GREEN]   = "lightgreen",
137 	[COLOR_YELLOW]  = "lightyellow",
138 	[COLOR_BLUE]    = "lightblue",
139 	[COLOR_MAGENTA] = "lightmagenta",
140 	[COLOR_CYAN]    = "lightcyan",
141 	[COLOR_WHITE]   = "lightwhite",
142 };
143 
144 char *XTERM256_COLOR_NAMES[256] = {
145 	[COLOR_BLACK]   = "black",
146 	[COLOR_RED]     = "red",
147 	[COLOR_GREEN]   = "green",
148 	[COLOR_YELLOW]  = "yellow",
149 	[COLOR_BLUE]    = "blue",
150 	[COLOR_MAGENTA] = "magenta",
151 	[COLOR_CYAN]    = "cyan",
152 	[COLOR_WHITE]   = "white",
153 	[8]  = "lightblack",
154 	[9]  = "lightred",
155 	[10] = "lightgreen",
156 	[11] = "lightyellow",
157 	[12] = "lightblue",
158 	[13] = "lightmagenta",
159 	[14] = "lightcyan",
160 	[15] = "lightwhite",
161 	[16] = "Grey0",
162 	[17] = "NavyBlue",
163 	[18] = "DarkBlue",
164 	[19] = "Blue3",
165 	[20] = "Blue3_2",
166 	[21] = "Blue1",
167 	[22] = "DarkGreen",
168 	[23] = "DeepSkyBlue4",
169 	[24] = "DeepSkyBlue4_2",
170 	[25] = "DeepSkyBlue4_3",
171 	[26] = "DodgerBlue3",
172 	[27] = "DodgerBlue2",
173 	[28] = "Green4",
174 	[29] = "SpringGreen4",
175 	[30] = "Turquoise4",
176 	[31] = "DeepSkyBlue3",
177 	[32] = "DeepSkyBlue3_2",
178 	[33] = "DodgerBlue1",
179 	[34] = "Green3",
180 	[35] = "SpringGreen3",
181 	[36] = "DarkCyan",
182 	[37] = "LightSeaGreen",
183 	[38] = "DeepSkyBlue2",
184 	[39] = "DeepSkyBlue1",
185 	[40] = "Green3_2",
186 	[41] = "SpringGreen3_2",
187 	[42] = "SpringGreen2",
188 	[43] = "Cyan3",
189 	[44] = "DarkTurquoise",
190 	[45] = "Turquoise2",
191 	[46] = "Green1",
192 	[47] = "SpringGreen2_2",
193 	[48] = "SpringGreen1",
194 	[49] = "MediumSpringGreen",
195 	[50] = "Cyan2",
196 	[51] = "Cyan1",
197 	[52] = "DarkRed",
198 	[53] = "DeepPink4",
199 	[54] = "Purple4",
200 	[55] = "Purple4_2",
201 	[56] = "Purple3",
202 	[57] = "BlueViolet",
203 	[58] = "Orange4",
204 	[59] = "Grey37",
205 	[60] = "MediumPurple4",
206 	[61] = "SlateBlue3",
207 	[62] = "SlateBlue3_2",
208 	[63] = "RoyalBlue1",
209 	[64] = "Chartreuse4",
210 	[65] = "DarkSeaGreen4",
211 	[66] = "PaleTurquoise4",
212 	[67] = "SteelBlue",
213 	[68] = "SteelBlue3",
214 	[69] = "CornflowerBlue",
215 	[70] = "Chartreuse3",
216 	[71] = "DarkSeaGreen4_2",
217 	[72] = "CadetBlue",
218 	[73] = "CadetBlue_2",
219 	[74] = "SkyBlue3",
220 	[75] = "SteelBlue1",
221 	[76] = "Chartreuse3_2",
222 	[77] = "PaleGreen3",
223 	[78] = "SeaGreen3",
224 	[79] = "Aquamarine3",
225 	[80] = "MediumTurquoise",
226 	[81] = "SteelBlue1_2",
227 	[82] = "Chartreuse2",
228 	[83] = "SeaGreen2",
229 	[84] = "SeaGreen1",
230 	[85] = "SeaGreen1_2",
231 	[86] = "Aquamarine1",
232 	[87] = "DarkSlateGray2",
233 	[88] = "DarkRed_2",
234 	[89] = "DeepPink4_2",
235 	[90] = "DarkMagenta",
236 	[91] = "DarkMagenta_2",
237 	[92] = "DarkViolet",
238 	[93] = "Purple",
239 	[94] = "Orange4_2",
240 	[95] = "LightPink4",
241 	[96] = "Plum4",
242 	[97] = "MediumPurple3",
243 	[98] = "MediumPurple3_2",
244 	[99] = "SlateBlue1",
245 	[100] = "Yellow4",
246 	[101] = "Wheat4",
247 	[102] = "Grey53",
248 	[103] = "LightSlateGrey",
249 	[104] = "MediumPurple",
250 	[105] = "LightSlateBlue",
251 	[106] = "Yellow4_2",
252 	[107] = "DarkOliveGreen3",
253 	[108] = "DarkSeaGreen",
254 	[109] = "LightSkyBlue3",
255 	[110] = "LightSkyBlue3_2",
256 	[111] = "SkyBlue2",
257 	[112] = "Chartreuse2_2",
258 	[113] = "DarkOliveGreen3_2",
259 	[114] = "PaleGreen3_2",
260 	[115] = "DarkSeaGreen3",
261 	[116] = "DarkSlateGray3",
262 	[117] = "SkyBlue1",
263 	[118] = "Chartreuse1",
264 	[119] = "LightGreen_2",
265 	[120] = "LightGreen_3",
266 	[121] = "PaleGreen1",
267 	[122] = "Aquamarine1_2",
268 	[123] = "DarkSlateGray1",
269 	[124] = "Red3",
270 	[125] = "DeepPink4_3",
271 	[126] = "MediumVioletRed",
272 	[127] = "Magenta3",
273 	[128] = "DarkViolet_2",
274 	[129] = "Purple_2",
275 	[130] = "DarkOrange3",
276 	[131] = "IndianRed",
277 	[132] = "HotPink3",
278 	[133] = "MediumOrchid3",
279 	[134] = "MediumOrchid",
280 	[135] = "MediumPurple2",
281 	[136] = "DarkGoldenrod",
282 	[137] = "LightSalmon3",
283 	[138] = "RosyBrown",
284 	[139] = "Grey63",
285 	[140] = "MediumPurple2_2",
286 	[141] = "MediumPurple1",
287 	[142] = "Gold3",
288 	[143] = "DarkKhaki",
289 	[144] = "NavajoWhite3",
290 	[145] = "Grey69",
291 	[146] = "LightSteelBlue3",
292 	[147] = "LightSteelBlue",
293 	[148] = "Yellow3",
294 	[149] = "DarkOliveGreen3_3",
295 	[150] = "DarkSeaGreen3_2",
296 	[151] = "DarkSeaGreen2",
297 	[152] = "LightCyan3",
298 	[153] = "LightSkyBlue1",
299 	[154] = "GreenYellow",
300 	[155] = "DarkOliveGreen2",
301 	[156] = "PaleGreen1_2",
302 	[157] = "DarkSeaGreen2_2",
303 	[158] = "DarkSeaGreen1",
304 	[159] = "PaleTurquoise1",
305 	[160] = "Red3_2",
306 	[161] = "DeepPink3",
307 	[162] = "DeepPink3_2",
308 	[163] = "Magenta3_2",
309 	[164] = "Magenta3_3",
310 	[165] = "Magenta2",
311 	[166] = "DarkOrange3_2",
312 	[167] = "IndianRed_2",
313 	[168] = "HotPink3_2",
314 	[169] = "HotPink2",
315 	[170] = "Orchid",
316 	[171] = "MediumOrchid1",
317 	[172] = "Orange3",
318 	[173] = "LightSalmon3_2",
319 	[174] = "LightPink3",
320 	[175] = "Pink3",
321 	[176] = "Plum3",
322 	[177] = "Violet",
323 	[178] = "Gold3_2",
324 	[179] = "LightGoldenrod3",
325 	[180] = "Tan",
326 	[181] = "MistyRose3",
327 	[182] = "Thistle3",
328 	[183] = "Plum2",
329 	[184] = "Yellow3_2",
330 	[185] = "Khaki3",
331 	[186] = "LightGoldenrod2",
332 	[187] = "LightYellow3",
333 	[188] = "Grey84",
334 	[189] = "LightSteelBlue1",
335 	[190] = "Yellow2",
336 	[191] = "DarkOliveGreen1",
337 	[192] = "DarkOliveGreen1_2",
338 	[193] = "DarkSeaGreen1_2",
339 	[194] = "Honeydew2",
340 	[195] = "LightCyan1",
341 	[196] = "Red1",
342 	[197] = "DeepPink2",
343 	[198] = "DeepPink1",
344 	[199] = "DeepPink1_2",
345 	[200] = "Magenta2_2",
346 	[201] = "Magenta1",
347 	[202] = "OrangeRed1",
348 	[203] = "IndianRed1",
349 	[204] = "IndianRed1_2",
350 	[205] = "HotPink",
351 	[206] = "HotPink_2",
352 	[207] = "MediumOrchid1_2",
353 	[208] = "DarkOrange",
354 	[209] = "Salmon1",
355 	[210] = "LightCoral",
356 	[211] = "PaleVioletRed1",
357 	[212] = "Orchid2",
358 	[213] = "Orchid1",
359 	[214] = "Orange1",
360 	[215] = "SandyBrown",
361 	[216] = "LightSalmon1",
362 	[217] = "LightPink1",
363 	[218] = "Pink1",
364 	[219] = "Plum1",
365 	[220] = "Gold1",
366 	[221] = "LightGoldenrod2_2",
367 	[222] = "LightGoldenrod2_3",
368 	[223] = "NavajoWhite1",
369 	[224] = "MistyRose1",
370 	[225] = "Thistle1",
371 	[226] = "Yellow1",
372 	[227] = "LightGoldenrod1",
373 	[228] = "Khaki1",
374 	[229] = "Wheat1",
375 	[230] = "Cornsilk1",
376 	[231] = "Grey100",
377 	[232] = "Grey3",
378 	[233] = "Grey7",
379 	[234] = "Grey11",
380 	[235] = "Grey15",
381 	[236] = "Grey19",
382 	[237] = "Grey23",
383 	[238] = "Grey27",
384 	[239] = "Grey30",
385 	[240] = "Grey35",
386 	[241] = "Grey39",
387 	[242] = "Grey42",
388 	[243] = "Grey46",
389 	[244] = "Grey50",
390 	[245] = "Grey54",
391 	[246] = "Grey58",
392 	[247] = "Grey62",
393 	[248] = "Grey66",
394 	[249] = "Grey70",
395 	[250] = "Grey74",
396 	[251] = "Grey78",
397 	[252] = "Grey82",
398 	[253] = "Grey85",
399 	[254] = "Grey89",
400 	[255] = "Grey93",
401 };
402 
403 /* Default color scheme definition. */
404 static const col_attr_t default_cs[] = {
405 	                      /* fg             bg           attr */
406 	[WIN_COLOR]          = { COLOR_WHITE,   COLOR_BLACK, 0                       },
407 	[DIRECTORY_COLOR]    = { COLOR_CYAN,    -1,          A_BOLD                  },
408 	[LINK_COLOR]         = { COLOR_YELLOW,  -1,          A_BOLD                  },
409 	[BROKEN_LINK_COLOR]  = { COLOR_RED,     -1,          A_BOLD                  },
410 	[HARD_LINK_COLOR]    = { COLOR_YELLOW,  -1,          0                       },
411 	[SOCKET_COLOR]       = { COLOR_MAGENTA, -1,          A_BOLD                  },
412 	[DEVICE_COLOR]       = { COLOR_RED,     -1,          A_BOLD                  },
413 	[FIFO_COLOR]         = { COLOR_CYAN,    -1,          A_BOLD                  },
414 	[EXECUTABLE_COLOR]   = { COLOR_GREEN,   -1,          A_BOLD                  },
415 	[SELECTED_COLOR]     = { COLOR_MAGENTA, -1,          A_BOLD                  },
416 	[CURR_LINE_COLOR]    = { -1,            -1,          A_BOLD | A_REVERSE      },
417 	[TOP_LINE_COLOR]     = { COLOR_BLACK,   COLOR_WHITE, 0                       },
418 	[TOP_LINE_SEL_COLOR] = { COLOR_BLACK,   -1,          A_BOLD                  },
419 	[STATUS_LINE_COLOR]  = { COLOR_BLACK,   COLOR_WHITE, A_BOLD                  },
420 	[WILD_MENU_COLOR]    = { COLOR_WHITE,   COLOR_BLACK, A_UNDERLINE | A_REVERSE },
421 	[CMD_LINE_COLOR]     = { COLOR_WHITE,   COLOR_BLACK, 0                       },
422 	[ERROR_MSG_COLOR]    = { COLOR_RED,     COLOR_BLACK, 0                       },
423 	[BORDER_COLOR]       = { COLOR_BLACK,   COLOR_WHITE, 0                       },
424 	[OTHER_LINE_COLOR]   = { -1,            -1,          -1                      },
425 	[JOB_LINE_COLOR]     = { COLOR_BLACK,   COLOR_WHITE, A_BOLD | A_REVERSE      },
426 	[SUGGEST_BOX_COLOR]  = { -1,            -1,          A_BOLD                  },
427 	[MISMATCH_COLOR]     = { COLOR_WHITE,   COLOR_RED,   A_BOLD                  },
428 	[AUX_WIN_COLOR]      = { -1,            -1,          -1                      },
429 	[TAB_LINE_COLOR]     = { COLOR_WHITE,   COLOR_BLACK, 0                       },
430 	[TAB_LINE_SEL_COLOR] = { -1,            -1,          A_BOLD | A_REVERSE      },
431 	[USER1_COLOR]        = { -1,            -1,          -1                      },
432 	[USER2_COLOR]        = { -1,            -1,          -1                      },
433 	[USER3_COLOR]        = { -1,            -1,          -1                      },
434 	[USER4_COLOR]        = { -1,            -1,          -1                      },
435 	[USER5_COLOR]        = { -1,            -1,          -1                      },
436 	[USER6_COLOR]        = { -1,            -1,          -1                      },
437 	[USER7_COLOR]        = { -1,            -1,          -1                      },
438 	[USER8_COLOR]        = { -1,            -1,          -1                      },
439 	[USER9_COLOR]        = { -1,            -1,          -1                      },
440 	[OTHER_WIN_COLOR]    = { -1,            -1,          -1                      },
441 	[LINE_NUM_COLOR]     = { -1,            -1,          -1                      },
442 	[ODD_LINE_COLOR]     = { -1,            -1,          -1                      },
443 };
444 ARRAY_GUARD(default_cs, MAXNUM_COLOR);
445 
446 static char ** list_cs_files(int *len);
447 static void restore_primary_cs(const col_scheme_t *cs);
448 static void reset_to_default_cs(col_scheme_t *cs);
449 static void free_cs_highlights(col_scheme_t *cs);
450 static file_hi_t * clone_cs_highlights(const col_scheme_t *from);
451 static void reset_cs_colors(col_scheme_t *cs);
452 static int source_cs(const char name[]);
453 static void get_cs_path(const char name[], char buf[], size_t buf_size);
454 static const char * get_global_colors_dir(void);
455 static void check_cs(col_scheme_t *cs);
456 static void load_color_pairs(col_scheme_t *cs);
457 static void ensure_dir_map_exists(void);
458 
459 /* Mapping of color schemes associations onto file system tree. */
460 static fsddata_t *dir_map;
461 
462 int
cs_have_no_extensions(void)463 cs_have_no_extensions(void)
464 {
465 	int len;
466 	char **list = list_cs_files(&len);
467 	int i;
468 
469 	/* Check if extensions are in use. */
470 	for(i = 0; i < len; ++i)
471 	{
472 		if(ends_with(list[i], ".vifm"))
473 		{
474 			break;
475 		}
476 	}
477 
478 	free_string_array(list, len);
479 	return (len > 0 && i >= len);
480 }
481 
482 void
cs_rename_all(void)483 cs_rename_all(void)
484 {
485 	DIR *dir;
486 	struct dirent *d;
487 
488 	dir = os_opendir(cfg.colors_dir);
489 	if(dir == NULL)
490 	{
491 		return;
492 	}
493 
494 	while((d = os_readdir(dir)) != NULL)
495 	{
496 #ifndef _WIN32
497 		unsigned char type;
498 #endif
499 		char full_old_path[PATH_MAX + 32 + NAME_MAX];
500 		char full_new_path[PATH_MAX + 32 + NAME_MAX];
501 		snprintf(full_old_path, sizeof(full_old_path), "%s/%s", cfg.colors_dir,
502 				d->d_name);
503 		snprintf(full_new_path, sizeof(full_new_path), "%s/%s.vifm", cfg.colors_dir,
504 				d->d_name);
505 
506 #ifndef _WIN32
507 		type = get_dirent_type(d, full_old_path);
508 		if(type == DT_REG || (type == DT_UNKNOWN && is_regular_file(full_old_path)))
509 #else
510 		if(is_regular_file(full_old_path))
511 #endif
512 		{
513 			(void)os_rename(full_old_path, full_new_path);
514 		}
515 	}
516 	os_closedir(dir);
517 }
518 
519 char **
cs_list(int * len)520 cs_list(int *len)
521 {
522 	char **const list = list_cs_files(len);
523 	int i, j;
524 	int new_names;
525 
526 	/* Check if extensions are in use. */
527 	new_names = 0;
528 	for(i = 0; i < *len; ++i)
529 	{
530 		if(ends_with(list[i], ".vifm"))
531 		{
532 			new_names = 1;
533 		}
534 	}
535 
536 	/* Remove hidden files, maybe along with old style file names, maybe cut off
537 	 * extensions from new style file names. */
538 	j = 0;
539 	for(i = 0; i < *len; ++i)
540 	{
541 		if(list[i][0] == '.' || (new_names && !cut_suffix(list[i], ".vifm")))
542 		{
543 			free(list[i]);
544 		}
545 		else
546 		{
547 			list[j++] = list[i];
548 		}
549 	}
550 	*len = j;
551 
552 	return list;
553 }
554 
555 /* Lists names of all files in color schemes directories.  Allocates an array of
556  * strings, which should be freed by the caller.  Always sets *len.  Returns
557  * NULL on error. */
558 static char **
list_cs_files(int * len)559 list_cs_files(int *len)
560 {
561 	char **list = NULL;
562 	*len = 0;
563 
564 	list = list_regular_files(cfg.colors_dir, list, len);
565 	list = list_regular_files(get_global_colors_dir(), list, len);
566 
567 	return list;
568 }
569 
570 int
cs_exists(const char name[])571 cs_exists(const char name[])
572 {
573 	char cs_path[PATH_MAX + 32];
574 	get_cs_path(name, cs_path, sizeof(cs_path));
575 	return is_regular_file(cs_path);
576 }
577 
578 void
cs_write(void)579 cs_write(void)
580 {
581 	FILE *fp;
582 	char def_cs_path[PATH_MAX + 32];
583 	size_t i;
584 
585 	if(create_path(cfg.colors_dir, S_IRWXU) != 0)
586 	{
587 		/* Do nothing if local colors directory exists or we've failed to create
588 		 * it. */
589 		return;
590 	}
591 
592 	snprintf(def_cs_path, sizeof(def_cs_path), "%s/Default.vifm", cfg.colors_dir);
593 	fp = os_fopen(def_cs_path, "w");
594 	if(fp == NULL)
595 	{
596 		return;
597 	}
598 
599 	fprintf(fp, "\" You can edit this file by hand.\n");
600 	fprintf(fp, "\" The \" character at the beginning of a line comments out the line.\n");
601 	fprintf(fp, "\" Blank lines are ignored.\n\n");
602 
603 	fprintf(fp, "\" The Default color scheme is used for any directory that does not have\n");
604 	fprintf(fp, "\" a specified scheme and for parts of user interface like menus. A\n");
605 	fprintf(fp, "\" color scheme set for a base directory will also\n");
606 	fprintf(fp, "\" be used for the sub directories.\n\n");
607 
608 	fprintf(fp, "\" The standard ncurses colors are:\n");
609 	fprintf(fp, "\" Default = -1 = None, can be used for transparency or default color\n");
610 	fprintf(fp, "\" Black = 0\n");
611 	fprintf(fp, "\" Red = 1\n");
612 	fprintf(fp, "\" Green = 2\n");
613 	fprintf(fp, "\" Yellow = 3\n");
614 	fprintf(fp, "\" Blue = 4\n");
615 	fprintf(fp, "\" Magenta = 5\n");
616 	fprintf(fp, "\" Cyan = 6\n");
617 	fprintf(fp, "\" White = 7\n\n");
618 
619 	fprintf(fp, "\" Light versions of colors are also available (set bold attribute):\n");
620 	fprintf(fp, "\" LightBlack\n");
621 	fprintf(fp, "\" LightRed\n");
622 	fprintf(fp, "\" LightGreen\n");
623 	fprintf(fp, "\" LightYellow\n");
624 	fprintf(fp, "\" LightBlue\n");
625 	fprintf(fp, "\" LightMagenta\n");
626 	fprintf(fp, "\" LightCyan\n");
627 	fprintf(fp, "\" LightWhite\n\n");
628 
629 	fprintf(fp, "\" Available attributes (some of them can be combined):\n");
630 	fprintf(fp, "\" bold\n");
631 	fprintf(fp, "\" underline\n");
632 	fprintf(fp, "\" reverse or inverse\n");
633 	fprintf(fp, "\" standout\n");
634 	fprintf(fp, "\" italic (on unsupported systems becomes reverse)\n");
635 	fprintf(fp, "\" none\n\n");
636 
637 	fprintf(fp, "\" Vifm supports 256 colors you can use color numbers 0-255\n");
638 	fprintf(fp, "\" (requires properly set up terminal: set your TERM environment variable\n");
639 	fprintf(fp, "\" (directly or using resources) to some color terminal name (e.g.\n");
640 	fprintf(fp, "\" xterm-256color) from /usr/lib/terminfo/; you can check current number\n");
641 	fprintf(fp, "\" of colors in your terminal with tput colors command)\n\n");
642 
643 	fprintf(fp, "\" highlight group cterm=attrs ctermfg=foreground_color ctermbg=background_color\n\n");
644 
645 	fprintf(fp, "highlight clear\n\n");
646 
647 	for(i = 0U; i < ARRAY_LEN(default_cs); ++i)
648 	{
649 		char fg_buf[16], bg_buf[16];
650 		cs_color_to_str(default_cs[i].fg, sizeof(fg_buf), fg_buf);
651 		cs_color_to_str(default_cs[i].bg, sizeof(bg_buf), bg_buf);
652 
653 		if(default_cs[i].attr == -1)
654 		{
655 			fprintf(fp, "highlight %s ctermfg=%s ctermbg=%s\n", HI_GROUPS[i], fg_buf,
656 					bg_buf);
657 		}
658 		else
659 		{
660 			fprintf(fp, "highlight %s cterm=%s ctermfg=%s ctermbg=%s\n", HI_GROUPS[i],
661 					cs_attrs_to_str(default_cs[i].attr), fg_buf, bg_buf);
662 		}
663 	}
664 
665 	fclose(fp);
666 }
667 
668 void
cs_color_to_str(int color,size_t buf_len,char str_buf[])669 cs_color_to_str(int color, size_t buf_len, char str_buf[])
670 {
671 	if(color == -1)
672 	{
673 		copy_str(str_buf, buf_len, "default");
674 	}
675 	else if(color >= 0 && color < (int)ARRAY_LEN(XTERM256_COLOR_NAMES))
676 	{
677 		copy_str(str_buf, buf_len, XTERM256_COLOR_NAMES[color]);
678 	}
679 	else
680 	{
681 		snprintf(str_buf, buf_len, "%d", color);
682 	}
683 }
684 
685 void
cs_load_primary(const char name[])686 cs_load_primary(const char name[])
687 {
688 	col_scheme_t prev_cs = {};
689 
690 	if(!cs_exists(name))
691 	{
692 		show_error_msgf("Color Scheme", "No such color scheme: \"%s\"", name);
693 		return;
694 	}
695 
696 	cs_assign(&prev_cs, &cfg.cs);
697 	curr_stats.cs = &cfg.cs;
698 	cfg.cs.state = CSS_LOADING;
699 
700 	if(source_cs(name) != 0)
701 	{
702 		restore_primary_cs(&prev_cs);
703 		show_error_msgf("Color Scheme Sourcing",
704 				"An error occurred on sourcing color scheme: \"%s\"", name);
705 		return;
706 	}
707 
708 	check_cs(&cfg.cs);
709 	if(cfg.cs.state == CSS_DEFAULTED)
710 	{
711 		restore_primary_cs(&prev_cs);
712 		show_error_msgf("Color Scheme Error",
713 				"\"%s\" color scheme is not supported by the terminal, restored \"%s\"",
714 				name, prev_cs.name);
715 		return;
716 	}
717 
718 	copy_str(cfg.cs.name, sizeof(cfg.cs.name), name);
719 	update_attributes();
720 
721 	free_cs_highlights(&prev_cs);
722 	cfg.cs.state = CSS_NORMAL;
723 }
724 
725 void
cs_load_primary_list(char * names[],int count)726 cs_load_primary_list(char *names[], int count)
727 {
728 	col_scheme_t prev_cs = {};
729 	cs_assign(&prev_cs, &cfg.cs);
730 
731 	while(count-- > 0)
732 	{
733 		const char *name = *names++;
734 
735 		cs_assign(&cfg.cs, &prev_cs);
736 		curr_stats.cs = &cfg.cs;
737 		cfg.cs.state = CSS_LOADING;
738 
739 		if(!cs_exists(name))
740 		{
741 			continue;
742 		}
743 
744 		if(source_cs(name) != 0)
745 		{
746 			show_error_msgf("Color Scheme Sourcing",
747 					"An error occurred on sourcing color scheme: \"%s\"", name);
748 			continue;
749 		}
750 
751 		check_cs(&cfg.cs);
752 		if(cfg.cs.state == CSS_DEFAULTED)
753 		{
754 			continue;
755 		}
756 
757 		copy_str(cfg.cs.name, sizeof(cfg.cs.name), name);
758 		update_attributes();
759 
760 		free_cs_highlights(&prev_cs);
761 		cfg.cs.state = CSS_NORMAL;
762 
763 		return;
764 	}
765 
766 	restore_primary_cs(&prev_cs);
767 }
768 
769 /* Restore previous state of primary color scheme. */
770 static void
restore_primary_cs(const col_scheme_t * cs)771 restore_primary_cs(const col_scheme_t *cs)
772 {
773 	free_cs_highlights(&cfg.cs);
774 	cfg.cs = *cs;
775 	cs_load_pairs();
776 	update_screen(UT_FULL);
777 }
778 
779 void
cs_load_pairs(void)780 cs_load_pairs(void)
781 {
782 	ensure_dir_map_exists();
783 
784 	load_color_pairs(&cfg.cs);
785 	load_color_pairs(&lwin.cs);
786 	load_color_pairs(&rwin.cs);
787 }
788 
789 void
cs_load_defaults(void)790 cs_load_defaults(void)
791 {
792 	fsddata_free(dir_map);
793 	dir_map = NULL;
794 
795 	lwin.local_cs = 0;
796 	rwin.local_cs = 0;
797 
798 	reset_to_default_cs(&cfg.cs);
799 	reset_to_default_cs(&lwin.cs);
800 	reset_to_default_cs(&rwin.cs);
801 
802 	load_color_pairs(&cfg.cs);
803 	load_color_pairs(&lwin.cs);
804 	load_color_pairs(&rwin.cs);
805 }
806 
807 /* Completely resets the cs to builtin default color scheme.  Changes: colors,
808  * name, state. */
809 static void
reset_to_default_cs(col_scheme_t * cs)810 reset_to_default_cs(col_scheme_t *cs)
811 {
812 	reset_cs_colors(cs);
813 
814 	copy_str(cs->name, sizeof(cs->name), DEF_CS_NAME);
815 
816 	cs->state = CSS_NORMAL;
817 }
818 
819 void
cs_reset(col_scheme_t * cs)820 cs_reset(col_scheme_t *cs)
821 {
822 	reset_cs_colors(cs);
823 	free_cs_highlights(cs);
824 	load_color_pairs(cs);
825 }
826 
827 void
cs_assign(col_scheme_t * to,const col_scheme_t * from)828 cs_assign(col_scheme_t *to, const col_scheme_t *from)
829 {
830 	free_cs_highlights(to);
831 	*to = *from;
832 	to->file_hi = clone_cs_highlights(from);
833 }
834 
835 /* Resets color scheme to default builtin values. */
836 static void
reset_cs_colors(col_scheme_t * cs)837 reset_cs_colors(col_scheme_t *cs)
838 {
839 	size_t i;
840 	for(i = 0U; i < ARRAY_LEN(default_cs); ++i)
841 	{
842 		cs->color[i] = default_cs[i];
843 	}
844 }
845 
846 /* Frees data structures of the color scheme that are related to filename
847  * specific highlight. */
848 static void
free_cs_highlights(col_scheme_t * cs)849 free_cs_highlights(col_scheme_t *cs)
850 {
851 	int i;
852 
853 	for(i = 0; i < cs->file_hi_count; ++i)
854 	{
855 		matchers_free(cs->file_hi[i].matchers);
856 	}
857 
858 	free(cs->file_hi);
859 
860 	cs->file_hi = NULL;
861 	cs->file_hi_count = 0;
862 }
863 
864 /* Clones filename specific highlight array of the *from color scheme and
865  * returns it. */
866 static file_hi_t *
clone_cs_highlights(const col_scheme_t * from)867 clone_cs_highlights(const col_scheme_t *from)
868 {
869 	int i;
870 	file_hi_t *const file_hi = reallocarray(NULL, from->file_hi_count + 1,
871 			sizeof(*file_hi));
872 
873 	for(i = 0; i < from->file_hi_count; ++i)
874 	{
875 		const file_hi_t *const hi = &from->file_hi[i];
876 		file_hi[i].matchers = matchers_clone(hi->matchers);
877 		file_hi[i].hi = hi->hi;
878 	}
879 
880 	return file_hi;
881 }
882 
883 int
cs_load_local(int left,const char dir[])884 cs_load_local(int left, const char dir[])
885 {
886 	if(dir_map == NULL || curr_stats.cs != &cfg.cs)
887 	{
888 		/* Either have nothing to do, or we're already doing it (sourcing a file can
889 		 * trigger view redraws). */
890 		return 0;
891 	}
892 
893 	curr_stats.cs = left ? &lwin.cs : &rwin.cs;
894 	cs_assign(curr_stats.cs, &cfg.cs);
895 
896 	/* TODO: maybe use split_and_get() here as in io/iop:iop_mkdir(). */
897 	char *const dir_copy = strdup(dir);
898 	char *p = dir_copy;
899 	int altered = 0;
900 	char t;
901 	do
902 	{
903 		void *name;
904 
905 		t = *p;
906 		*p = '\0';
907 
908 		if(fsddata_get(dir_map, dir_copy, &name) == 0 && cs_exists(name))
909 		{
910 			(void)source_cs(name);
911 			altered = 1;
912 		}
913 
914 		*p = t;
915 		p = (t == '\0' ? NULL : until_first(p + 1, '/'));
916 	}
917 	while(t != '\0');
918 	free(dir_copy);
919 
920 	curr_stats.cs = &cfg.cs;
921 
922 	if(!altered)
923 	{
924 		return 0;
925 	}
926 
927 	check_cs(curr_stats.cs);
928 	load_color_pairs(curr_stats.cs);
929 
930 	return 1;
931 }
932 
933 /* Sources color scheme file.  Returns non-zero on error (e.g. file doesn't
934  * exist), and zero otherwise. */
935 static int
source_cs(const char name[])936 source_cs(const char name[])
937 {
938 	char cs_path[PATH_MAX + 1];
939 	get_cs_path(name, cs_path, sizeof(cs_path));
940 	return cfg_source_file(cs_path);
941 }
942 
943 /* Fill the buffer of specified size with path to color scheme, which might not
944  * exist. */
945 static void
get_cs_path(const char name[],char buf[],size_t buf_size)946 get_cs_path(const char name[], char buf[], size_t buf_size)
947 {
948 	snprintf(buf, buf_size, "%s/%s.vifm", cfg.colors_dir, name);
949 	if(is_regular_file(buf))
950 	{
951 		return;
952 	}
953 
954 	(void)cut_suffix(buf, ".vifm");
955 	if(is_regular_file(buf))
956 	{
957 		return;
958 	}
959 
960 	snprintf(buf, buf_size, "%s/%s.vifm", get_global_colors_dir(),
961 			name);
962 	if(is_regular_file(buf))
963 	{
964 		return;
965 	}
966 
967 	(void)cut_suffix(buf, ".vifm");
968 }
969 
970 /* Retrieves path to global directory containing color schemes.  Returns the
971  * path. */
972 static const char *
get_global_colors_dir(void)973 get_global_colors_dir(void)
974 {
975 #ifndef _WIN32
976 	return GLOBAL_COLORS_DIR;
977 #else
978 	static char dir_path[PATH_MAX + 1];
979 	if(dir_path[0] == '\0')
980 	{
981 		snprintf(dir_path, sizeof(dir_path), "%s/colors", get_installed_data_dir());
982 	}
983 	return dir_path;
984 #endif
985 }
986 
987 /* Checks whether colorscheme is in unusable state and resets it to normal
988  * state. */
989 static void
check_cs(col_scheme_t * cs)990 check_cs(col_scheme_t *cs)
991 {
992 	if(cs->state == CSS_BROKEN)
993 	{
994 		reset_cs_colors(cs);
995 		cs->state = CSS_DEFAULTED;
996 	}
997 }
998 
999 /* Loads color scheme settings into color pairs. */
1000 static void
load_color_pairs(col_scheme_t * cs)1001 load_color_pairs(col_scheme_t *cs)
1002 {
1003 	int i;
1004 	for(i = 0; i < MAXNUM_COLOR; ++i)
1005 	{
1006 		cs->pair[i] = colmgr_get_pair(cs->color[i].fg, cs->color[i].bg);
1007 	}
1008 }
1009 
1010 void
cs_complete(const char name[])1011 cs_complete(const char name[])
1012 {
1013 	int i;
1014 	size_t len;
1015 	int schemes_len;
1016 	char **schemes;
1017 
1018 	len = strlen(name);
1019 	schemes = cs_list(&schemes_len);
1020 
1021 	for(i = 0; i < schemes_len; ++i)
1022 	{
1023 		if(schemes[i][0] != '.' || name[0] == '.')
1024 		{
1025 			if(strnoscmp(name, schemes[i], len) == 0)
1026 			{
1027 				vle_compl_add_match(schemes[i], "");
1028 			}
1029 		}
1030 	}
1031 
1032 	free_string_array(schemes, schemes_len);
1033 
1034 	vle_compl_finish_group();
1035 	vle_compl_add_last_match(name);
1036 }
1037 
1038 const char *
cs_attrs_to_str(int attrs)1039 cs_attrs_to_str(int attrs)
1040 {
1041 	static char result[64];
1042 
1043 	if(attrs == 0 || attrs == -1)
1044 	{
1045 		return strcpy(result, "none");
1046 	}
1047 
1048 	result[0] = '\0';
1049 	if((attrs & A_BOLD) == A_BOLD)
1050 		strcat(result, "bold,");
1051 	if((attrs & A_UNDERLINE) == A_UNDERLINE)
1052 		strcat(result, "underline,");
1053 	if((attrs & A_REVERSE) == A_REVERSE)
1054 		strcat(result, "reverse,");
1055 	if((attrs & A_STANDOUT) == A_STANDOUT)
1056 		strcat(result, "standout,");
1057 #ifdef HAVE_A_ITALIC_DECL
1058 	if((attrs & A_ITALIC) == A_ITALIC)
1059 		strcat(result, "italic,");
1060 #endif
1061 	if(result[0] != '\0')
1062 		result[strlen(result) - 1] = '\0';
1063 	return result;
1064 }
1065 
1066 void
cs_assoc_dir(const char name[],const char dir[])1067 cs_assoc_dir(const char name[], const char dir[])
1068 {
1069 	char *const copy = strdup(name);
1070 
1071 	ensure_dir_map_exists();
1072 
1073 	if(fsddata_set(dir_map, dir, copy) != 0)
1074 	{
1075 		free(copy);
1076 	}
1077 }
1078 
1079 /* Makes sure that dir_map variable is initialized. */
1080 static void
ensure_dir_map_exists(void)1081 ensure_dir_map_exists(void)
1082 {
1083 	if(dir_map == NULL)
1084 	{
1085 		dir_map = fsddata_create(1, 1);
1086 	}
1087 }
1088 
1089 void
cs_mix_colors(col_attr_t * base,const col_attr_t * mixup)1090 cs_mix_colors(col_attr_t *base, const col_attr_t *mixup)
1091 {
1092 	if(mixup->fg != -1)
1093 	{
1094 		base->fg = mixup->fg;
1095 	}
1096 
1097 	if(mixup->bg != -1)
1098 	{
1099 		base->bg = mixup->bg;
1100 	}
1101 
1102 	if(mixup->attr != -1)
1103 	{
1104 		base->attr = mixup->attr;
1105 	}
1106 }
1107 
1108 void
cs_add_file_hi(struct matchers_t * matchers,const col_attr_t * hi)1109 cs_add_file_hi(struct matchers_t *matchers, const col_attr_t *hi)
1110 {
1111 	void *p;
1112 	file_hi_t *file_hi;
1113 	col_scheme_t *const cs = curr_stats.cs;
1114 
1115 	/* Try to find existing record that exactly matches the expression and update
1116 	 * the record. */
1117 	int i;
1118 	const char *expr = matchers_get_expr(matchers);
1119 	for(i = 0; i < cs->file_hi_count; ++i)
1120 	{
1121 		if(strcmp(matchers_get_expr(cs->file_hi[i].matchers), expr) == 0)
1122 		{
1123 			matchers_free(matchers);
1124 			cs->file_hi[i].hi = *hi;
1125 			return;
1126 		}
1127 	}
1128 
1129 	p = reallocarray(cs->file_hi, cs->file_hi_count + 1, sizeof(*cs->file_hi));
1130 	if(p == NULL)
1131 	{
1132 		matchers_free(matchers);
1133 		show_error_msg("Color Scheme File Highlight", "Not enough memory");
1134 		return;
1135 	}
1136 	cs->file_hi = p;
1137 
1138 	file_hi = &cs->file_hi[cs->file_hi_count];
1139 
1140 	file_hi->matchers = matchers;
1141 	file_hi->hi = *hi;
1142 
1143 	++cs->file_hi_count;
1144 }
1145 
1146 const col_attr_t *
cs_get_file_hi(const col_scheme_t * cs,const char fname[],int * hi_hint)1147 cs_get_file_hi(const col_scheme_t *cs, const char fname[], int *hi_hint)
1148 {
1149 	if(*hi_hint == INT_MAX)
1150 	{
1151 		return NULL;
1152 	}
1153 	if(*hi_hint != -1)
1154 	{
1155 		assert(*hi_hint >= 0 && "Wrong index.");
1156 		assert(*hi_hint < cs->file_hi_count && "Wrong index.");
1157 		return &cs->file_hi[*hi_hint].hi;
1158 	}
1159 
1160 	int i;
1161 	for(i = 0; i < cs->file_hi_count; ++i)
1162 	{
1163 		const file_hi_t *const file_hi = &cs->file_hi[i];
1164 		if(matchers_match(file_hi->matchers, fname))
1165 		{
1166 			*hi_hint = i;
1167 			return &file_hi->hi;
1168 		}
1169 	}
1170 
1171 	*hi_hint = INT_MAX;
1172 	return NULL;
1173 }
1174 
1175 int
cs_del_file_hi(const char matchers_expr[])1176 cs_del_file_hi(const char matchers_expr[])
1177 {
1178 	col_scheme_t *const cs = curr_stats.cs;
1179 
1180 	int i;
1181 	for(i = 0; i < cs->file_hi_count; ++i)
1182 	{
1183 		if(strcmp(matchers_get_expr(cs->file_hi[i].matchers), matchers_expr) == 0)
1184 		{
1185 			matchers_free(cs->file_hi[i].matchers);
1186 			memmove(&cs->file_hi[i], &cs->file_hi[i + 1],
1187 					sizeof(*cs->file_hi)*((cs->file_hi_count - 1) - i));
1188 			--cs->file_hi_count;
1189 			return 1;
1190 		}
1191 	}
1192 
1193 	return 0;
1194 }
1195 
1196 int
cs_is_color_set(const col_attr_t * color)1197 cs_is_color_set(const col_attr_t *color)
1198 {
1199 	return color->fg != -1 || color->bg != -1 || color->attr != -1;
1200 }
1201 
1202 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
1203 /* vim: set cinoptions+=t0 filetype=c : */
1204