1 /**
2 * Copyright (c) 2007-2012, Timothy Stack
3 *
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * * Redistributions of source code must retain the above copyright notice, this
10 * list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 * * Neither the name of Timothy Stack nor the names of its contributors
15 * may be used to endorse or promote products derived from this software
16 * without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 *
29 * @file view_curses.cc
30 */
31
32 #include "config.h"
33
34 #include <cmath>
35 #include <string>
36 #include <chrono>
37
38 #include "auto_mem.hh"
39 #include "base/lnav_log.hh"
40 #include "view_curses.hh"
41 #include "ansi_scrubber.hh"
42 #include "lnav_config.hh"
43 #include "attr_line.hh"
44 #include "shlex.hh"
45
46 using namespace std::chrono_literals;
47
48 string_attr_type view_curses::VC_ROLE("role");
49 string_attr_type view_curses::VC_ROLE_FG("role-fg");
50 string_attr_type view_curses::VC_STYLE("style");
51 string_attr_type view_curses::VC_GRAPHIC("graphic");
52 string_attr_type view_curses::VC_SELECTED("selected");
53 string_attr_type view_curses::VC_FOREGROUND("foreground");
54 string_attr_type view_curses::VC_BACKGROUND("background");
55
56 const struct itimerval ui_periodic_timer::INTERVAL = {
57 { 0, std::chrono::duration_cast<std::chrono::microseconds>(350ms).count() },
58 { 0, std::chrono::duration_cast<std::chrono::microseconds>(350ms).count() }
59 };
60
ui_periodic_timer()61 ui_periodic_timer::ui_periodic_timer()
62 : upt_counter(0)
63 {
64 struct sigaction sa;
65
66 sa.sa_handler = ui_periodic_timer::sigalrm;
67 sa.sa_flags = SA_RESTART;
68 sigemptyset(&sa.sa_mask);
69 sigaction(SIGALRM, &sa, nullptr);
70 if (setitimer(ITIMER_REAL, &INTERVAL, nullptr) == -1) {
71 perror("setitimer");
72 }
73 }
74
singleton()75 ui_periodic_timer &ui_periodic_timer::singleton()
76 {
77 static ui_periodic_timer retval;
78
79 return retval;
80 }
81
sigalrm(int sig)82 void ui_periodic_timer::sigalrm(int sig)
83 {
84 singleton().upt_counter += 1;
85 }
86
singleton()87 alerter &alerter::singleton() {
88 static alerter retval;
89
90 return retval;
91 }
92
93 struct utf_to_display_adjustment {
94 int uda_origin;
95 int uda_offset;
96
utf_to_display_adjustmentutf_to_display_adjustment97 utf_to_display_adjustment(int utf_origin, int offset)
98 : uda_origin(utf_origin), uda_offset(offset) {
99
100 };
101 };
102
awaiting_user_input()103 void view_curses::awaiting_user_input()
104 {
105 static const bool enabled = getenv("lnav_test") != nullptr;
106 static const char OSC_INPUT[] = "\x1b]999;send-input\a";
107
108 if (enabled) {
109 write(STDOUT_FILENO, OSC_INPUT, sizeof(OSC_INPUT) - 1);
110 }
111 }
112
mvwattrline(WINDOW * window,int y,int x,attr_line_t & al,const struct line_range & lr_chars,view_colors::role_t base_role)113 void view_curses::mvwattrline(WINDOW *window,
114 int y,
115 int x,
116 attr_line_t &al,
117 const struct line_range &lr_chars,
118 view_colors::role_t base_role)
119 {
120 attr_t text_attrs, attrs;
121 int line_width_chars;
122 string_attrs_t & sa = al.get_attrs();
123 std::string & line = al.get_string();
124 string_attrs_t::const_iterator iter;
125 std::vector<utf_to_display_adjustment> utf_adjustments;
126 int tab_count = 0;
127 char *expanded_line;
128 int exp_index = 0;
129 int exp_offset = 0;
130 std::string full_line;
131
132 require(lr_chars.lr_end >= 0);
133
134 line_width_chars = lr_chars.length();
135 tab_count = count(line.begin(), line.end(), '\t');
136 expanded_line = (char *)alloca(line.size() + tab_count * 8 + 1);
137
138 short *fg_color = (short *) alloca(line_width_chars * sizeof(short));
139 bool has_fg = false;
140 short *bg_color = (short *) alloca(line_width_chars * sizeof(short));
141 bool has_bg = false;
142 line_range lr_bytes{lr_chars.lr_start, lr_chars.lr_end};
143 int char_index = 0;
144
145 for (size_t lpc = 0; lpc < line.size(); lpc++) {
146 int exp_start_index = exp_index;
147 unsigned char ch = static_cast<unsigned char>(line[lpc]);
148
149 switch (ch) {
150 case '\t':
151 do {
152 expanded_line[exp_index] = ' ';
153 exp_index += 1;
154 char_index += 1;
155 } while (exp_index % 8);
156 utf_adjustments.emplace_back(lpc,
157 exp_index - exp_start_index - 1);
158 break;
159
160 case '\r':
161 case '\n':
162 expanded_line[exp_index] = ' ';
163 exp_index += 1;
164 char_index += 1;
165 break;
166
167 default: {
168 auto size_result = ww898::utf::utf8::char_size([&line, lpc]() {
169 return std::make_pair(line[lpc], line.length() - lpc - 1);
170 });
171
172 if (size_result.isErr()) {
173 expanded_line[exp_index] = '?';
174 exp_index += 1;
175 } else {
176 auto offset = 1 - (int) size_result.unwrap();
177
178 expanded_line[exp_index] = line[lpc];
179 exp_index += 1;
180 if (offset) {
181 if (char_index < lr_chars.lr_start) {
182 lr_bytes.lr_start += abs(offset);
183 }
184 if (char_index < lr_chars.lr_end) {
185 lr_bytes.lr_end += abs(offset);
186 }
187 exp_offset += offset;
188 utf_adjustments.emplace_back(lpc, offset);
189 for (; offset &&
190 (lpc + 1) < line.size(); lpc++, offset++) {
191 expanded_line[exp_index] = line[lpc + 1];
192 exp_index += 1;
193 }
194 }
195 }
196 char_index += 1;
197 break;
198 }
199 }
200 }
201
202 expanded_line[exp_index] = '\0';
203 full_line = std::string(expanded_line);
204
205 view_colors &vc = view_colors::singleton();
206 text_attrs = vc.attrs_for_role(base_role);
207 attrs = text_attrs;
208 wmove(window, y, x);
209 wattron(window, attrs);
210 if (lr_bytes.lr_start < (int)full_line.size()) {
211 waddnstr(window, &full_line.c_str()[lr_bytes.lr_start], lr_bytes.length());
212 }
213 if (lr_bytes.lr_end > (int)full_line.size()) {
214 whline(window, ' ', lr_bytes.lr_end - (full_line.size() + exp_offset));
215 }
216 wattroff(window, attrs);
217
218 stable_sort(sa.begin(), sa.end());
219 for (iter = sa.begin(); iter != sa.end(); ++iter) {
220 struct line_range attr_range = iter->sa_range;
221
222 require(attr_range.lr_start >= 0);
223 require(attr_range.lr_end >= -1);
224
225 if (!(iter->sa_type == &VC_ROLE ||
226 iter->sa_type == &VC_ROLE_FG ||
227 iter->sa_type == &VC_STYLE ||
228 iter->sa_type == &VC_GRAPHIC ||
229 iter->sa_type == &VC_FOREGROUND ||
230 iter->sa_type == &VC_BACKGROUND)) {
231 continue;
232 }
233
234 for (const auto &adj : utf_adjustments) {
235 // If the UTF adjustment is in the viewport, we need to adjust this
236 // attribute.
237 if (adj.uda_origin < iter->sa_range.lr_start) {
238 attr_range.lr_start += adj.uda_offset;
239 }
240 }
241
242 if (attr_range.lr_end != -1) {
243 for (const auto &adj : utf_adjustments) {
244 if (adj.uda_origin < iter->sa_range.lr_end) {
245 attr_range.lr_end += adj.uda_offset;
246 }
247 }
248 }
249
250 if (attr_range.lr_end == -1) {
251 attr_range.lr_end = lr_chars.lr_start + line_width_chars;
252 }
253 if (attr_range.lr_end < lr_chars.lr_start) {
254 continue;
255 }
256 attr_range.lr_start = std::max(0, attr_range.lr_start - lr_chars.lr_start);
257 if (attr_range.lr_start > line_width_chars) {
258 continue;
259 }
260
261 attr_range.lr_end = std::min(line_width_chars, attr_range.lr_end - lr_chars.lr_start);
262
263 if (iter->sa_type == &VC_FOREGROUND) {
264 if (!has_fg) {
265 memset(fg_color, -1, line_width_chars * sizeof(short));
266 }
267 short attr_fg = iter->sa_value.sav_int;
268 if (attr_fg == view_colors::MATCH_COLOR_SEMANTIC) {
269 attr_fg = vc.color_for_ident(
270 &line[iter->sa_range.lr_start],
271 iter->sa_range.length());
272 }
273 std::fill(&fg_color[attr_range.lr_start], &fg_color[attr_range.lr_end], attr_fg);
274 has_fg = true;
275 continue;
276 }
277
278 if (iter->sa_type == &VC_BACKGROUND) {
279 if (!has_bg) {
280 memset(bg_color, -1, line_width_chars * sizeof(short));
281 }
282 short attr_bg = iter->sa_value.sav_int;
283 if (attr_bg == view_colors::MATCH_COLOR_SEMANTIC) {
284 attr_bg = vc.color_for_ident(
285 &line[iter->sa_range.lr_start],
286 iter->sa_range.length());
287 }
288 std::fill(bg_color + attr_range.lr_start, bg_color + attr_range.lr_end, attr_bg);
289 has_bg = true;
290 continue;
291 }
292
293 if (attr_range.lr_end > attr_range.lr_start) {
294 int awidth = attr_range.length();
295 nonstd::optional<char> graphic;
296 short color_pair = 0;
297
298 if (iter->sa_type == &VC_GRAPHIC) {
299 graphic = iter->sa_value.sav_int;
300 } else if (iter->sa_type == &VC_STYLE) {
301 attrs = iter->sa_value.sav_int & ~A_COLOR;
302 color_pair = PAIR_NUMBER(iter->sa_value.sav_int);
303 } else if (iter->sa_type == &VC_ROLE) {
304 attrs = vc.attrs_for_role((view_colors::role_t) iter->sa_value.sav_int);
305 color_pair = PAIR_NUMBER(attrs);
306 attrs = attrs & ~A_COLOR;
307 } else if (iter->sa_type == &VC_ROLE_FG) {
308 short role_fg, role_bg;
309 attrs = vc.attrs_for_role((view_colors::role_t) iter->sa_value.sav_int);
310 color_pair = PAIR_NUMBER(attrs);
311 pair_content(color_pair, &role_fg, &role_bg);
312 attrs = attrs & ~A_COLOR;
313 if (!has_fg) {
314 memset(fg_color, -1, line_width_chars * sizeof(short));
315 }
316 std::fill(&fg_color[attr_range.lr_start], &fg_color[attr_range.lr_end], (short) role_fg);
317 has_fg = true;
318 color_pair = 0;
319 }
320
321 if (graphic || attrs || color_pair > 0) {
322 int x_pos = x + attr_range.lr_start;
323 int ch_width = std::min(awidth, (line_width_chars - attr_range.lr_start));
324 cchar_t row_ch[ch_width + 1];
325
326 if (attrs & (A_LEFT|A_RIGHT)) {
327 short pair_fg, pair_bg;
328
329 pair_content(color_pair, &pair_fg, &pair_bg);
330 if (attrs & A_LEFT) {
331 pair_fg = vc.color_for_ident(
332 &line[iter->sa_range.lr_start],
333 iter->sa_range.length());
334 }
335 if (attrs & A_RIGHT) {
336 pair_bg = vc.color_for_ident(
337 &line[iter->sa_range.lr_start],
338 iter->sa_range.length());
339 }
340 color_pair = vc.ensure_color_pair(pair_fg, pair_bg);
341
342 attrs &= ~(A_LEFT|A_RIGHT);
343 }
344
345 mvwin_wchnstr(window, y, x_pos, row_ch, ch_width);
346 for (int lpc = 0; lpc < ch_width; lpc++) {
347 bool clear_rev = false;
348
349 if (graphic) {
350 row_ch[lpc].chars[0] = graphic.value();
351 row_ch[lpc].attr |= A_ALTCHARSET;
352 }
353 if (row_ch[lpc].attr & A_REVERSE && attrs & A_REVERSE) {
354 clear_rev = true;
355 }
356 if (color_pair > 0) {
357 row_ch[lpc].attr =
358 attrs | (row_ch[lpc].attr & ~A_COLOR);
359 #ifdef NCURSES_EXT_COLORS
360 row_ch[lpc].ext_color = color_pair;
361 #else
362 row_ch[lpc].attr |= COLOR_PAIR(color_pair);
363 #endif
364 } else {
365 row_ch[lpc].attr |= attrs;
366 }
367 if (clear_rev) {
368 row_ch[lpc].attr &= ~A_REVERSE;
369 }
370 }
371 mvwadd_wchnstr(window, y, x_pos, row_ch, ch_width);
372 }
373 }
374 }
375
376 #if 1
377 if (has_fg || has_bg) {
378 if (!has_fg) {
379 memset(fg_color, -1, line_width_chars * sizeof(short));
380 }
381 if (!has_bg) {
382 memset(bg_color, -1, line_width_chars * sizeof(short));
383 }
384
385 int ch_width = lr_chars.length();
386 cchar_t row_ch[ch_width + 1];
387
388 mvwin_wchnstr(window, y, x, row_ch, ch_width);
389 for (int lpc = 0; lpc < ch_width; lpc++) {
390 if (fg_color[lpc] == -1 && bg_color[lpc] == -1) {
391 continue;
392 }
393
394 auto cur_pair = PAIR_NUMBER(row_ch[lpc].attr);
395 short cur_fg, cur_bg;
396 pair_content(cur_pair, &cur_fg, &cur_bg);
397 if (fg_color[lpc] == -1) {
398 fg_color[lpc] = cur_fg;
399 }
400 if (bg_color[lpc] == -1) {
401 bg_color[lpc] = cur_bg;
402 }
403
404 int color_pair = vc.ensure_color_pair(fg_color[lpc], bg_color[lpc]);
405
406 row_ch[lpc].attr = row_ch[lpc].attr & ~A_COLOR;
407 #ifdef NCURSES_EXT_COLORS
408 row_ch[lpc].ext_color = color_pair;
409 #else
410 row_ch[lpc].attr |= COLOR_PAIR(color_pair);
411 #endif
412 }
413 mvwadd_wchnstr(window, y, x, row_ch, ch_width);
414 }
415 #endif
416 }
417
singleton()418 view_colors &view_colors::singleton()
419 {
420 static view_colors s_vc;
421
422 return s_vc;
423 }
424
view_colors()425 view_colors::view_colors()
426 : vc_dyn_pairs(0)
427 {
428 size_t color_index = 0;
429 for (int z = 0; z < 6; z++) {
430 for (int x = 1; x < 6; x += 2) {
431 for (int y = 1; y < 6; y += 2) {
432 short fg = 16 + x + (y * 6) + (z * 6 * 6);
433
434 this->vc_highlight_colors[color_index++] = fg;
435 }
436 }
437 }
438 }
439
440 bool view_colors::initialized = false;
441
442 static std::string COLOR_NAMES[] = {
443 "black",
444 "red",
445 "green",
446 "yellow",
447 "blue",
448 "magenta",
449 "cyan",
450 "white",
451 };
452
453 class color_listener : public lnav_config_listener {
454 public:
reload_config(error_reporter & reporter)455 void reload_config(error_reporter &reporter) override {
456 if (!view_colors::initialized) {
457 return;
458 }
459
460 auto &vc = view_colors::singleton();
461
462 for (const auto &pair : lnav_config.lc_ui_theme_defs) {
463 vc.init_roles(pair.second, reporter);
464 }
465
466 auto iter = lnav_config.lc_ui_theme_defs.find(lnav_config.lc_ui_theme);
467
468 if (iter == lnav_config.lc_ui_theme_defs.end()) {
469 reporter(&lnav_config.lc_ui_theme,
470 "unknown theme -- " + lnav_config.lc_ui_theme);
471
472 vc.init_roles(lnav_config.lc_ui_theme_defs["default"], reporter);
473 return;
474 }
475
476 if (view_colors::initialized) {
477 vc.init_roles(iter->second, reporter);
478 }
479 }
480 };
481
482 static color_listener _COLOR_LISTENER;
483 term_color_palette *view_colors::vc_active_palette;
484
init()485 void view_colors::init()
486 {
487 vc_active_palette = ansi_colors();
488 if (has_colors()) {
489 static const int ansi_colors_to_curses[] = {
490 COLOR_BLACK,
491 COLOR_RED,
492 COLOR_GREEN,
493 COLOR_YELLOW,
494 COLOR_BLUE,
495 COLOR_MAGENTA,
496 COLOR_CYAN,
497 COLOR_WHITE,
498 };
499
500 start_color();
501
502 if (lnav_config.lc_ui_default_colors) {
503 use_default_colors();
504 }
505 for (int fg = 0; fg < 8; fg++) {
506 for (int bg = 0; bg < 8; bg++) {
507 if (fg == 0 && bg == 0)
508 continue;
509
510 if (lnav_config.lc_ui_default_colors &&
511 ansi_colors_to_curses[fg] == COLOR_WHITE &&
512 ansi_colors_to_curses[bg] == COLOR_BLACK) {
513 init_pair(ansi_color_pair_index(fg, bg), -1, -1);
514 } else {
515 auto curs_bg = ansi_colors_to_curses[bg];
516
517 if (lnav_config.lc_ui_default_colors &&
518 curs_bg == COLOR_BLACK) {
519 curs_bg = -1;
520 }
521 init_pair(ansi_color_pair_index(fg, bg),
522 ansi_colors_to_curses[fg],
523 curs_bg);
524 }
525 }
526 }
527 if (COLORS >= 256) {
528 vc_active_palette = xterm_colors();
529 }
530 }
531
532 log_debug("COLOR_PAIRS = %d", COLOR_PAIRS);
533 singleton().vc_dyn_pairs.set_max_size(COLOR_PAIRS);
534
535 initialized = true;
536
537 {
538 auto reporter = [](const void *, const std::string &) {
539
540 };
541
542 _COLOR_LISTENER.reload_config(reporter);
543 }
544 }
545
attr_for_colors(int & pair_base,short fg,short bg)546 inline attr_t attr_for_colors(int &pair_base, short fg, short bg)
547 {
548 if (fg == -1) {
549 fg = COLOR_WHITE;
550 }
551 if (bg == -1) {
552 bg = COLOR_BLACK;
553 }
554 if (COLOR_PAIRS <= 64) {
555 return view_colors::ansi_color_pair(fg, bg);
556 } else {
557 if (lnav_config.lc_ui_default_colors) {
558 if (fg == COLOR_WHITE) {
559 fg = -1;
560 }
561 if (bg == COLOR_BLACK) {
562 bg = -1;
563 }
564 }
565 }
566
567 require(pair_base < COLOR_PAIRS);
568
569 int pair = pair_base;
570 pair_base += 1;
571
572 if (view_colors::initialized) {
573 init_pair(pair, fg, bg);
574 }
575
576 auto retval = COLOR_PAIR(pair);
577
578 if (fg == view_colors::MATCH_COLOR_SEMANTIC) {
579 retval |= A_LEFT;
580 }
581 if (bg == view_colors::MATCH_COLOR_SEMANTIC) {
582 retval |= A_RIGHT;
583 }
584
585 return retval;
586 }
587
to_attrs(int & pair_base,const lnav_theme & lt,const style_config & sc,const style_config & fallback_sc,lnav_config_listener::error_reporter & reporter)588 std::pair<attr_t, attr_t> view_colors::to_attrs(
589 int &pair_base,
590 const lnav_theme <, const style_config &sc, const style_config &fallback_sc,
591 lnav_config_listener::error_reporter &reporter)
592 {
593 std::string fg1, bg1, fg_color, bg_color;
594
595 fg1 = sc.sc_color;
596 if (fg1.empty()) {
597 fg1 = fallback_sc.sc_color;
598 }
599 bg1 = sc.sc_background_color;
600 if (bg1.empty()) {
601 bg1 = fallback_sc.sc_background_color;
602 }
603 shlex(fg1).eval(fg_color, lt.lt_vars);
604 shlex(bg1).eval(bg_color, lt.lt_vars);
605
606 auto fg = styling::color_unit::from_str(fg_color).unwrapOrElse([&](const auto& msg) {
607 reporter(&sc.sc_color, msg);
608 return styling::color_unit::make_empty();
609 });
610 auto bg = styling::color_unit::from_str(bg_color).unwrapOrElse([&](const auto& msg) {
611 reporter(&sc.sc_background_color, msg);
612 return styling::color_unit::make_empty();
613 });
614
615 attr_t retval1 = attr_for_colors(pair_base,
616 this->match_color(fg),
617 this->match_color(bg));
618 attr_t retval2 = 0;
619
620 if (sc.sc_underline) {
621 retval1 |= A_UNDERLINE;
622 retval2 |= A_UNDERLINE;
623 }
624 if (sc.sc_bold) {
625 retval1 |= A_BOLD;
626 retval2 |= A_BOLD;
627 }
628
629 return {retval1, retval2};
630 }
631
init_roles(const lnav_theme & lt,lnav_config_listener::error_reporter & reporter)632 void view_colors::init_roles(const lnav_theme <,
633 lnav_config_listener::error_reporter &reporter)
634 {
635 int color_pair_base = VC_ANSI_END + 1;
636 rgb_color fg, bg;
637 std::string err;
638
639 if (COLORS == 256) {
640 const auto &ident_sc = lt.lt_style_identifier;
641 int ident_bg = (lnav_config.lc_ui_default_colors ? -1 : COLOR_BLACK);
642
643 if (!ident_sc.sc_background_color.empty()) {
644 std::string bg_color;
645
646 shlex(ident_sc.sc_background_color).eval(bg_color, lt.lt_vars);
647 auto rgb_bg = rgb_color::from_str(bg_color)
648 .unwrapOrElse([&](const auto& msg) {
649 reporter(&ident_sc.sc_background_color, msg);
650 return rgb_color{};
651 });
652 ident_bg = vc_active_palette->match_color(lab_color(rgb_bg));
653 }
654 } else {
655 color_pair_base = VC_ANSI_END + HI_COLOR_COUNT;
656 }
657
658 /* Setup the mappings from roles to actual colors. */
659 this->vc_role_colors[VCR_TEXT] = this->to_attrs(color_pair_base,
660 lt, lt.lt_style_text, lt.lt_style_text, reporter);
661
662 {
663 int pnum = PAIR_NUMBER(this->vc_role_colors[VCR_TEXT].first);
664 short text_fg, text_bg;
665
666 pair_content(pnum, &text_fg, &text_bg);
667 for (int ansi_fg = 0; ansi_fg < 8; ansi_fg++) {
668 for (int ansi_bg = 0; ansi_bg < 8; ansi_bg++) {
669 if (ansi_fg == 0 && ansi_bg == 0) {
670 continue;
671 }
672
673 auto fg_iter = lt.lt_vars.find(COLOR_NAMES[ansi_fg]);
674 auto bg_iter = lt.lt_vars.find(COLOR_NAMES[ansi_bg]);
675 auto fg_str = fg_iter == lt.lt_vars.end() ? "" : fg_iter->second;
676 auto bg_str = bg_iter == lt.lt_vars.end() ? "" : bg_iter->second;
677
678 auto rgb_fg = rgb_color::from_str(fg_str)
679 .unwrapOrElse([&](const auto& msg) {
680 reporter(&fg_str, msg);
681 return rgb_color{};
682 });
683 auto rgb_bg = rgb_color::from_str(bg_str)
684 .unwrapOrElse([&](const auto& msg) {
685 reporter(&fg_str, msg);
686 return rgb_color{};
687 });
688
689 short fg = vc_active_palette->match_color(lab_color(rgb_fg));
690 short bg = vc_active_palette->match_color(lab_color(rgb_bg));
691
692 if (rgb_fg.empty()) {
693 fg = ansi_fg;
694 }
695 if (rgb_bg.empty()) {
696 bg = ansi_bg;
697 }
698
699 this->vc_ansi_to_theme[ansi_fg] = fg;
700 if (lnav_config.lc_ui_default_colors && bg == COLOR_BLACK) {
701 bg = -1;
702 if (fg == COLOR_WHITE) {
703 fg = -1;
704 }
705 }
706 init_pair(ansi_color_pair_index(ansi_fg, ansi_bg), fg, bg);
707 }
708 }
709 }
710 if (lnav_config.lc_ui_dim_text) {
711 this->vc_role_colors[VCR_TEXT].first |= A_DIM;
712 this->vc_role_colors[VCR_TEXT].second |= A_DIM;
713 }
714 this->vc_role_colors[VCR_SEARCH] = std::make_pair(A_REVERSE, A_REVERSE);
715 this->vc_role_colors[VCR_IDENTIFIER] = this->to_attrs(
716 color_pair_base, lt, lt.lt_style_identifier, lt.lt_style_text, reporter);
717 this->vc_role_colors[VCR_OK] = this->to_attrs(color_pair_base,
718 lt, lt.lt_style_ok,
719 lt.lt_style_text,
720 reporter);
721 this->vc_role_colors[VCR_ERROR] = this->to_attrs(color_pair_base,
722 lt, lt.lt_style_error,
723 lt.lt_style_text,
724 reporter);
725 this->vc_role_colors[VCR_WARNING] = this->to_attrs(color_pair_base,
726 lt, lt.lt_style_warning,
727 lt.lt_style_text,
728 reporter);
729 this->vc_role_colors[VCR_ALT_ROW] = this->to_attrs(color_pair_base,
730 lt, lt.lt_style_alt_text,
731 lt.lt_style_text,
732 reporter);
733 this->vc_role_colors[VCR_HIDDEN] = this->to_attrs(color_pair_base,
734 lt, lt.lt_style_hidden,
735 lt.lt_style_text,
736 reporter);
737 this->vc_role_colors[VCR_ADJUSTED_TIME] = this->to_attrs(
738 color_pair_base, lt, lt.lt_style_adjusted_time, lt.lt_style_text, reporter);
739 this->vc_role_colors[VCR_SKEWED_TIME] = this->to_attrs(
740 color_pair_base, lt, lt.lt_style_skewed_time, lt.lt_style_text, reporter);
741 this->vc_role_colors[VCR_OFFSET_TIME] = this->to_attrs(
742 color_pair_base, lt, lt.lt_style_offset_time, lt.lt_style_text, reporter);
743 this->vc_role_colors[VCR_INVALID_MSG] = this->to_attrs(
744 color_pair_base, lt, lt.lt_style_invalid_msg, lt.lt_style_text, reporter);
745
746 this->vc_role_colors[VCR_STATUS] = this->to_attrs(color_pair_base,
747 lt, lt.lt_style_status, lt.lt_style_status, reporter);
748 this->vc_role_colors[VCR_WARN_STATUS] = this->to_attrs(color_pair_base,
749 lt, lt.lt_style_warn_status, lt.lt_style_status, reporter);
750 this->vc_role_colors[VCR_ALERT_STATUS] = this->to_attrs(color_pair_base,
751 lt, lt.lt_style_alert_status, lt.lt_style_status, reporter);
752 this->vc_role_colors[VCR_ACTIVE_STATUS] = this->to_attrs(color_pair_base,
753 lt, lt.lt_style_active_status, lt.lt_style_status, reporter);
754 this->vc_role_colors[VCR_ACTIVE_STATUS2] =
755 std::make_pair(this->vc_role_colors[VCR_ACTIVE_STATUS].first | A_BOLD,
756 this->vc_role_colors[VCR_ACTIVE_STATUS].second | A_BOLD);
757 this->vc_role_colors[VCR_STATUS_TITLE] = this->to_attrs(
758 color_pair_base, lt, lt.lt_style_status_title, lt.lt_style_status, reporter);
759 this->vc_role_colors[VCR_STATUS_SUBTITLE] = this->to_attrs(
760 color_pair_base, lt, lt.lt_style_status_subtitle, lt.lt_style_status, reporter);
761
762 this->vc_role_colors[VCR_STATUS_HOTKEY] = this->to_attrs(
763 color_pair_base, lt, lt.lt_style_status_hotkey, lt.lt_style_status,
764 reporter);
765 this->vc_role_colors[VCR_STATUS_TITLE_HOTKEY] = this->to_attrs(
766 color_pair_base, lt, lt.lt_style_status_title_hotkey, lt.lt_style_status,
767 reporter);
768 this->vc_role_colors[VCR_STATUS_DISABLED_TITLE] = this->to_attrs(
769 color_pair_base, lt, lt.lt_style_status_disabled_title, lt.lt_style_status,
770 reporter);
771
772 {
773 style_config stitch_sc;
774
775 stitch_sc.sc_color = lt.lt_style_status_subtitle.sc_background_color;
776 stitch_sc.sc_background_color =
777 lt.lt_style_status_title.sc_background_color;
778 this->vc_role_colors[VCR_STATUS_STITCH_TITLE_TO_SUB] =
779 this->to_attrs(color_pair_base, lt, stitch_sc, lt.lt_style_status, reporter);
780 }
781 {
782 style_config stitch_sc;
783
784 stitch_sc.sc_color = lt.lt_style_status_title.sc_background_color;
785 stitch_sc.sc_background_color =
786 lt.lt_style_status_subtitle.sc_background_color;
787 this->vc_role_colors[VCR_STATUS_STITCH_SUB_TO_TITLE] =
788 this->to_attrs(color_pair_base, lt, stitch_sc, lt.lt_style_status, reporter);
789 }
790
791 {
792 style_config stitch_sc;
793
794 stitch_sc.sc_color = lt.lt_style_status.sc_background_color;
795 stitch_sc.sc_background_color =
796 lt.lt_style_status_subtitle.sc_background_color;
797 this->vc_role_colors[VCR_STATUS_STITCH_SUB_TO_NORMAL] =
798 this->to_attrs(color_pair_base, lt, stitch_sc, lt.lt_style_status, reporter);
799 }
800 {
801 style_config stitch_sc;
802
803 stitch_sc.sc_color = lt.lt_style_status_subtitle.sc_background_color;
804 stitch_sc.sc_background_color =
805 lt.lt_style_status.sc_background_color;
806 this->vc_role_colors[VCR_STATUS_STITCH_NORMAL_TO_SUB] =
807 this->to_attrs(color_pair_base, lt, stitch_sc, lt.lt_style_status, reporter);
808 }
809
810 {
811 style_config stitch_sc;
812
813 stitch_sc.sc_color = lt.lt_style_status.sc_background_color;
814 stitch_sc.sc_background_color =
815 lt.lt_style_status_title.sc_background_color;
816 this->vc_role_colors[VCR_STATUS_STITCH_TITLE_TO_NORMAL] =
817 this->to_attrs(color_pair_base, lt, stitch_sc, lt.lt_style_status, reporter);
818 }
819 {
820 style_config stitch_sc;
821
822 stitch_sc.sc_color = lt.lt_style_status_title.sc_background_color;
823 stitch_sc.sc_background_color =
824 lt.lt_style_status.sc_background_color;
825 this->vc_role_colors[VCR_STATUS_STITCH_NORMAL_TO_TITLE] =
826 this->to_attrs(color_pair_base, lt, stitch_sc, lt.lt_style_status, reporter);
827 }
828
829 this->vc_role_colors[VCR_INACTIVE_STATUS] = this->to_attrs(color_pair_base,
830 lt, lt.lt_style_inactive_status, lt.lt_style_status, reporter);
831 this->vc_role_colors[VCR_INACTIVE_ALERT_STATUS] = this->to_attrs(
832 color_pair_base, lt, lt.lt_style_inactive_alert_status, lt.lt_style_alert_status, reporter);
833
834 this->vc_role_colors[VCR_POPUP] = this->to_attrs(
835 color_pair_base, lt, lt.lt_style_popup, lt.lt_style_text, reporter);
836 this->vc_role_colors[VCR_FOCUSED] = this->to_attrs(
837 color_pair_base, lt, lt.lt_style_focused, lt.lt_style_focused, reporter);
838 this->vc_role_colors[VCR_DISABLED_FOCUSED] = this->to_attrs(
839 color_pair_base, lt, lt.lt_style_disabled_focused, lt.lt_style_disabled_focused, reporter);
840 this->vc_role_colors[VCR_COLOR_HINT] = std::make_pair(
841 COLOR_PAIR(color_pair_base), COLOR_PAIR(color_pair_base + 1));
842 color_pair_base += 2;
843
844 this->vc_role_colors[VCR_SCROLLBAR] = this->to_attrs(
845 color_pair_base, lt, lt.lt_style_scrollbar, lt.lt_style_status, reporter);
846 {
847 style_config bar_sc;
848
849 bar_sc.sc_color = lt.lt_style_error.sc_color;
850 bar_sc.sc_background_color = lt.lt_style_scrollbar.sc_background_color;
851 this->vc_role_colors[VCR_SCROLLBAR_ERROR] = this->to_attrs(
852 color_pair_base, lt, bar_sc, lt.lt_style_alert_status, reporter);
853 }
854 {
855 style_config bar_sc;
856
857 bar_sc.sc_color = lt.lt_style_warning.sc_color;
858 bar_sc.sc_background_color = lt.lt_style_scrollbar.sc_background_color;
859 this->vc_role_colors[VCR_SCROLLBAR_WARNING] = this->to_attrs(
860 color_pair_base, lt, bar_sc, lt.lt_style_warn_status, reporter);
861 }
862
863 this->vc_role_colors[VCR_KEYWORD] = this->to_attrs(color_pair_base,
864 lt, lt.lt_style_keyword, lt.lt_style_text, reporter);
865 this->vc_role_colors[VCR_STRING] = this->to_attrs(color_pair_base,
866 lt, lt.lt_style_string, lt.lt_style_text, reporter);
867 this->vc_role_colors[VCR_COMMENT] = this->to_attrs(color_pair_base,
868 lt, lt.lt_style_comment, lt.lt_style_text, reporter);
869 this->vc_role_colors[VCR_DOC_DIRECTIVE] = this->to_attrs(color_pair_base,
870 lt, lt.lt_style_doc_directive, lt.lt_style_text, reporter);
871 this->vc_role_colors[VCR_VARIABLE] = this->to_attrs(color_pair_base,
872 lt, lt.lt_style_variable, lt.lt_style_text, reporter);
873 this->vc_role_colors[VCR_SYMBOL] = this->to_attrs(color_pair_base,
874 lt, lt.lt_style_symbol, lt.lt_style_text, reporter);
875 this->vc_role_colors[VCR_NUMBER] = this->to_attrs(
876 color_pair_base, lt, lt.lt_style_number, lt.lt_style_text, reporter);
877
878 this->vc_role_colors[VCR_RE_SPECIAL] = this->to_attrs(
879 color_pair_base, lt, lt.lt_style_re_special, lt.lt_style_text, reporter);
880 this->vc_role_colors[VCR_RE_REPEAT] = this->to_attrs(
881 color_pair_base, lt, lt.lt_style_re_repeat, lt.lt_style_text, reporter);
882 this->vc_role_colors[VCR_FILE] = this->to_attrs(color_pair_base,
883 lt, lt.lt_style_file, lt.lt_style_text, reporter);
884
885 this->vc_role_colors[VCR_DIFF_DELETE] = this->to_attrs(
886 color_pair_base, lt, lt.lt_style_diff_delete, lt.lt_style_text, reporter);
887 this->vc_role_colors[VCR_DIFF_ADD] = this->to_attrs(
888 color_pair_base, lt, lt.lt_style_diff_add, lt.lt_style_text, reporter);
889 this->vc_role_colors[VCR_DIFF_SECTION] = this->to_attrs(
890 color_pair_base, lt, lt.lt_style_diff_section, lt.lt_style_text, reporter);
891
892 this->vc_role_colors[VCR_LOW_THRESHOLD] = this->to_attrs(
893 color_pair_base, lt, lt.lt_style_low_threshold, lt.lt_style_text, reporter);
894 this->vc_role_colors[VCR_MED_THRESHOLD] = this->to_attrs(
895 color_pair_base, lt, lt.lt_style_med_threshold, lt.lt_style_text, reporter);
896 this->vc_role_colors[VCR_HIGH_THRESHOLD] = this->to_attrs(
897 color_pair_base, lt, lt.lt_style_high_threshold, lt.lt_style_text, reporter);
898
899 for (auto level = static_cast<log_level_t>(LEVEL_UNKNOWN + 1);
900 level < LEVEL__MAX;
901 level = static_cast<log_level_t>(level + 1)) {
902 auto level_iter = lt.lt_level_styles.find(level);
903
904 if (level_iter == lt.lt_level_styles.end()) {
905 this->vc_level_attrs[level] = this->to_attrs(
906 color_pair_base, lt, lt.lt_style_text, lt.lt_style_text, reporter);
907 } else {
908 this->vc_level_attrs[level] = this->to_attrs(
909 color_pair_base, lt, level_iter->second, lt.lt_style_text, reporter);
910 }
911 }
912
913 if (initialized && this->vc_color_pair_end == 0) {
914 this->vc_color_pair_end = color_pair_base + 1;
915 }
916 this->vc_dyn_pairs.clear();
917 }
918
ensure_color_pair(short fg,short bg)919 int view_colors::ensure_color_pair(short fg, short bg)
920 {
921 require(fg >= -100);
922 require(bg >= -100);
923
924 auto index_pair = std::make_pair(fg, bg);
925 auto existing = this->vc_dyn_pairs.get(index_pair);
926
927 if (existing) {
928 auto retval = existing.value().dp_color_pair;
929
930 return retval;
931 }
932
933 short def_pair = PAIR_NUMBER(this->attrs_for_role(VCR_TEXT));
934 short def_fg, def_bg;
935
936 pair_content(def_pair, &def_fg, &def_bg);
937
938 int new_pair = this->vc_color_pair_end + this->vc_dyn_pairs.size();
939 auto retval = PAIR_NUMBER(attr_for_colors(new_pair,
940 fg == -1 ? def_fg : fg,
941 bg == -1 ? def_bg : bg));
942
943 if (initialized) {
944 struct dyn_pair dp = { (int) retval };
945
946 this->vc_dyn_pairs.put(index_pair, dp);
947 }
948
949 return retval;
950 }
951
ensure_color_pair(const styling::color_unit & rgb_fg,const styling::color_unit & rgb_bg)952 int view_colors::ensure_color_pair(const styling::color_unit &rgb_fg,
953 const styling::color_unit &rgb_bg)
954 {
955 auto fg = this->match_color(rgb_fg);
956 auto bg = this->match_color(rgb_bg);
957
958 return this->ensure_color_pair(fg, bg);
959 }
960
match_color(const styling::color_unit & color) const961 short view_colors::match_color(const styling::color_unit &color) const
962 {
963 return color.cu_value.match(
964 [](styling::semantic) {
965 return MATCH_COLOR_SEMANTIC;
966 },
967 [](const rgb_color& color) {
968 if (color.empty()) {
969 return MATCH_COLOR_DEFAULT;
970 }
971
972 return vc_active_palette->match_color(lab_color(color));
973 }
974 );
975 }
976
color_for_ident(const char * str,size_t len) const977 int view_colors::color_for_ident(const char *str, size_t len) const
978 {
979 unsigned long index = crc32(1, (const Bytef*)str, len);
980 int retval;
981
982 if (COLORS >= 256) {
983 if (str[0] == '#' && (len == 4 || len == 7)) {
984 auto fg_res = styling::color_unit::from_str(string_fragment(str, 0, len));
985 if (fg_res.isOk()) {
986 return this->match_color(fg_res.unwrap());
987 }
988 }
989
990 unsigned long offset = index % HI_COLOR_COUNT;
991 retval = this->vc_highlight_colors[offset];
992 }
993 else {
994 retval = -1;
995 }
996
997 return retval;
998 }
999
attrs_for_ident(const char * str,size_t len)1000 attr_t view_colors::attrs_for_ident(const char *str, size_t len)
1001 {
1002 auto retval = this->attrs_for_role(VCR_IDENTIFIER);
1003
1004 if (retval & (A_LEFT|A_RIGHT)) {
1005 auto color_pair = PAIR_NUMBER(retval);
1006 short pair_fg, pair_bg;
1007
1008 pair_content(color_pair, &pair_fg, &pair_bg);
1009 if (retval & A_LEFT) {
1010 pair_fg = this->color_for_ident(str, len);
1011 }
1012 if (retval & A_RIGHT) {
1013 pair_bg = this->color_for_ident(str, len);
1014 }
1015 color_pair = this->ensure_color_pair(pair_fg, pair_bg);
1016 retval &= ~(A_COLOR|A_LEFT|A_RIGHT);
1017 retval |= COLOR_PAIR(color_pair);
1018 }
1019
1020 return retval;
1021 }
1022
lab_color(const rgb_color & rgb)1023 lab_color::lab_color(const rgb_color &rgb)
1024 {
1025 double r = rgb.rc_r / 255.0,
1026 g = rgb.rc_g / 255.0,
1027 b = rgb.rc_b / 255.0,
1028 x, y, z;
1029
1030 r = (r > 0.04045) ? pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
1031 g = (g > 0.04045) ? pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
1032 b = (b > 0.04045) ? pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
1033
1034 x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047;
1035 y = (r * 0.2126 + g * 0.7152 + b * 0.0722) / 1.00000;
1036 z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883;
1037
1038 x = (x > 0.008856) ? pow(x, 1.0/3.0) : (7.787 * x) + 16.0/116.0;
1039 y = (y > 0.008856) ? pow(y, 1.0/3.0) : (7.787 * y) + 16.0/116.0;
1040 z = (z > 0.008856) ? pow(z, 1.0/3.0) : (7.787 * z) + 16.0/116.0;
1041
1042 this->lc_l = (116.0 * y) - 16;
1043 this->lc_a = 500.0 * (x - y);
1044 this->lc_b = 200.0 * (y - z);
1045 }
1046
deltaE(const lab_color & other) const1047 double lab_color::deltaE(const lab_color &other) const
1048 {
1049 double deltaL = this->lc_l - other.lc_l;
1050 double deltaA = this->lc_a - other.lc_a;
1051 double deltaB = this->lc_b - other.lc_b;
1052 double c1 = sqrt(this->lc_a * this->lc_a + this->lc_b * this->lc_b);
1053 double c2 = sqrt(other.lc_a * other.lc_a + other.lc_b * other.lc_b);
1054 double deltaC = c1 - c2;
1055 double deltaH = deltaA * deltaA + deltaB * deltaB - deltaC * deltaC;
1056 deltaH = deltaH < 0.0 ? 0.0 : sqrt(deltaH);
1057 double sc = 1.0 + 0.045 * c1;
1058 double sh = 1.0 + 0.015 * c1;
1059 double deltaLKlsl = deltaL / (1.0);
1060 double deltaCkcsc = deltaC / (sc);
1061 double deltaHkhsh = deltaH / (sh);
1062 double i = deltaLKlsl * deltaLKlsl + deltaCkcsc * deltaCkcsc + deltaHkhsh * deltaHkhsh;
1063 return i < 0.0 ? 0.0 : sqrt(i);
1064 }
1065
operator <(const lab_color & rhs) const1066 bool lab_color::operator<(const lab_color &rhs) const
1067 {
1068 if (lc_l < rhs.lc_l)
1069 return true;
1070 if (rhs.lc_l < lc_l)
1071 return false;
1072 if (lc_a < rhs.lc_a)
1073 return true;
1074 if (rhs.lc_a < lc_a)
1075 return false;
1076 return lc_b < rhs.lc_b;
1077 }
1078
operator >(const lab_color & rhs) const1079 bool lab_color::operator>(const lab_color &rhs) const
1080 {
1081 return rhs < *this;
1082 }
1083
operator <=(const lab_color & rhs) const1084 bool lab_color::operator<=(const lab_color &rhs) const
1085 {
1086 return !(rhs < *this);
1087 }
1088
operator >=(const lab_color & rhs) const1089 bool lab_color::operator>=(const lab_color &rhs) const
1090 {
1091 return !(*this < rhs);
1092 }
1093
operator ==(const lab_color & rhs) const1094 bool lab_color::operator==(const lab_color &rhs) const
1095 {
1096 return lc_l == rhs.lc_l &&
1097 lc_a == rhs.lc_a &&
1098 lc_b == rhs.lc_b;
1099 }
1100
operator !=(const lab_color & rhs) const1101 bool lab_color::operator!=(const lab_color &rhs) const
1102 {
1103 return !(rhs == *this);
1104 }
1105