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 &lt, 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 &lt,
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