• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tstack / lnav / 19833402571-2724

01 Dec 2025 06:33PM UTC coverage: 68.86% (-0.001%) from 68.861%
19833402571-2724

push

github

tstack
[breadcrumb] add thread ID to breadcrumb bar

173 of 219 new or added lines in 14 files covered. (79.0%)

4 existing lines in 3 files now uncovered.

51293 of 74489 relevant lines covered (68.86%)

435674.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.98
/src/view_curses.cc
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
25
 * ON 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 <chrono>
33
#include <cmath>
34
#include <iterator>
35
#include <string>
36

37
#include "view_curses.hh"
38

39
#include <zlib.h>
40

41
#include "base/ansi_scrubber.hh"
42
#include "base/attr_line.hh"
43
#include "base/from_trait.hh"
44
#include "base/injector.hh"
45
#include "base/itertools.enumerate.hh"
46
#include "base/itertools.hh"
47
#include "base/lnav_log.hh"
48
#include "config.h"
49
#include "lnav_config.hh"
50
#include "shlex.hh"
51
#include "terminfo-files.h"
52
#include "terminfo/terminfo.h"
53
#include "uniwidth.h"
54
#include "xterm_mouse.hh"
55

56
using namespace std::chrono_literals;
57

58
const struct itimerval ui_periodic_timer::INTERVAL = {
59
    {0, std::chrono::duration_cast<std::chrono::microseconds>(100ms).count()},
60
    {0, std::chrono::duration_cast<std::chrono::microseconds>(100ms).count()},
61
};
62

63
ui_periodic_timer::ui_periodic_timer()
68✔
64
{
65
    struct sigaction sa;
66

67
    if (getenv("lnav_test") != nullptr) {
68✔
68
        this->upt_deadline = std::chrono::steady_clock::now() + 5s;
68✔
69
    }
70

71
    sa.sa_handler = ui_periodic_timer::sigalrm;
68✔
72
    sa.sa_flags = SA_RESTART;
68✔
73
    sigemptyset(&sa.sa_mask);
68✔
74
    sigaction(SIGALRM, &sa, nullptr);
68✔
75
    if (setitimer(ITIMER_REAL, &INTERVAL, nullptr) == -1) {
68✔
76
        perror("setitimer");
×
77
    }
78
}
68✔
79

80
ui_periodic_timer&
81
ui_periodic_timer::singleton()
217✔
82
{
83
    static ui_periodic_timer retval;
217✔
84

85
    return retval;
217✔
86
}
87

88
void
89
ui_periodic_timer::sigalrm(int sig)
7✔
90
{
91
    auto& upt = singleton();
7✔
92

93
    if (upt.upt_deadline
7✔
94
        && std::chrono::steady_clock::now() > upt.upt_deadline.value())
7✔
95
    {
96
        abort();
×
97
    }
98
    upt.upt_counter += 1;
7✔
99
}
7✔
100

101
alerter&
102
alerter::singleton()
628✔
103
{
104
    static alerter retval;
105

106
    return retval;
628✔
107
}
108

109
bool
110
alerter::chime(std::string msg)
7✔
111
{
112
    if (!this->a_enabled) {
7✔
113
        return true;
2✔
114
    }
115

116
    bool retval = this->a_do_flash;
5✔
117
    if (this->a_do_flash) {
5✔
118
        static const auto BELL = "\a";
119
        log_warning("chime message: %s", msg.c_str());
4✔
120
        write(STDIN_FILENO, BELL, 1);
4✔
121
    }
122
    this->a_do_flash = false;
5✔
123
    return retval;
5✔
124
}
125

126
struct utf_to_display_adjustment {
127
    int uda_origin;
128
    int uda_offset;
129

130
    utf_to_display_adjustment(int utf_origin, int offset)
98✔
131
        : uda_origin(utf_origin), uda_offset(offset)
98✔
132
    {
133
    }
98✔
134
};
135

136
bool
137
mouse_event::is_click(mouse_button_t button) const
×
138
{
139
    return this->me_button == button
×
140
        && this->me_state == mouse_button_state_t::BUTTON_STATE_RELEASED
×
141
        && this->me_press_x == this->me_x && this->me_press_y == this->me_y;
×
142
}
143

144
bool
145
mouse_event::is_click_in(mouse_button_t button, int x_start, int x_end) const
×
146
{
147
    return this->me_button == button
×
148
        && this->me_state == mouse_button_state_t::BUTTON_STATE_RELEASED
×
149
        && (x_start <= this->me_x && this->me_x <= x_end)
×
150
        && (x_start <= this->me_press_x && this->me_press_x <= x_end)
×
151
        && this->me_y == this->me_press_y;
×
152
}
153

154
bool
155
mouse_event::is_press_in(mouse_button_t button, line_range lr) const
×
156
{
157
    return this->me_button == button
×
158
        && this->me_state == mouse_button_state_t::BUTTON_STATE_PRESSED
×
159
        && lr.contains(this->me_x);
×
160
}
161

162
bool
163
mouse_event::is_drag_in(mouse_button_t button, line_range lr) const
×
164
{
165
    return this->me_button == button
×
166
        && this->me_state == mouse_button_state_t::BUTTON_STATE_DRAGGED
×
167
        && lr.contains(this->me_press_x) && lr.contains(this->me_x);
×
168
}
169

170
bool
171
mouse_event::is_double_click_in(mouse_button_t button, line_range lr) const
×
172
{
173
    return this->me_button == button
×
174
        && this->me_state == mouse_button_state_t::BUTTON_STATE_DOUBLE_CLICK
×
175
        && lr.contains(this->me_x) && this->me_y == this->me_press_y;
×
176
}
177

178
bool
179
view_curses::do_update()
352✔
180
{
181
    bool retval = false;
352✔
182

183
    this->vc_needs_update = false;
352✔
184

185
    if (!this->vc_visible) {
352✔
186
        return retval;
194✔
187
    }
188

189
    for (auto* child : this->vc_children) {
198✔
190
        retval = child->do_update() || retval;
40✔
191
    }
192
    return retval;
158✔
193
}
194

195
bool
196
view_curses::handle_mouse(mouse_event& me)
×
197
{
198
    if (me.me_state != mouse_button_state_t::BUTTON_STATE_DRAGGED) {
×
199
        this->vc_last_drag_child = nullptr;
×
200
    }
201

202
    for (auto* child : this->vc_children) {
×
203
        auto x = this->vc_x + me.me_x;
×
204
        auto y = this->vc_y + me.me_y;
×
205
        if ((me.me_state == mouse_button_state_t::BUTTON_STATE_DRAGGED
×
206
             && child == this->vc_last_drag_child && child->vc_x <= x
×
207
             && x < (child->vc_x + child->vc_width))
×
208
            || child->contains(x, y))
×
209
        {
210
            auto sub_me = me;
×
211

212
            sub_me.me_x = x - child->vc_x;
×
213
            sub_me.me_y = y - child->vc_y;
×
214
            sub_me.me_press_x = this->vc_x + me.me_press_x - child->vc_x;
×
215
            sub_me.me_press_y = this->vc_y + me.me_press_y - child->vc_y;
×
216
            if (me.me_state == mouse_button_state_t::BUTTON_STATE_DRAGGED) {
×
217
                this->vc_last_drag_child = child;
×
218
            }
219
            return child->handle_mouse(sub_me);
×
220
        }
221
    }
222
    return false;
×
223
}
224

225
std::optional<view_curses*>
226
view_curses::contains(int x, int y)
×
227
{
228
    if (!this->vc_visible || !this->vc_enabled) {
×
229
        return std::nullopt;
×
230
    }
231

232
    for (auto* child : this->vc_children) {
×
233
        auto contains_res = child->contains(x, y);
×
234
        if (contains_res) {
×
235
            return contains_res;
×
236
        }
237
    }
238
    if (this->vc_x <= x
×
239
        && (this->vc_width < 0 || x <= this->vc_x + this->vc_width)
×
240
        && this->vc_y == y)
×
241
    {
242
        return this;
×
243
    }
244
    return std::nullopt;
×
245
}
246

247
void
248
view_curses::awaiting_user_input()
8✔
249
{
250
    static const bool enabled = getenv("IN_SCRIPTY") != nullptr;
8✔
251
    static const char OSC_INPUT[] = "\x1b]999;send-input\a";
252

253
    if (enabled) {
8✔
254
        write(STDOUT_FILENO, OSC_INPUT, sizeof(OSC_INPUT) - 1);
8✔
255
    }
256
}
8✔
257

258
view_curses::mvwattrline_result
259
view_curses::mvwattrline(ncplane* window,
833✔
260
                         int y,
261
                         const int x,
262
                         attr_line_t& al,
263
                         const struct line_range& lr_chars,
264
                         role_t base_role)
265
{
266
    auto& sa = al.get_attrs();
833✔
267
    const auto& line = al.get_string();
833✔
268
    std::vector<utf_to_display_adjustment> utf_adjustments;
833✔
269

270
    require(lr_chars.lr_end >= 0);
833✔
271

272
    mvwattrline_result retval;
833✔
273
    auto line_width_chars = lr_chars.length();
833✔
274
    std::string expanded_line;
833✔
275
    line_range lr_bytes;
833✔
276
    int char_index = 0;
833✔
277

278
    {
279
        unsigned rows, cols;
280
        ncplane_dim_yx(window, &rows, &cols);
833✔
281

282
        if (y < 0 || y >= rows || x < 0 || x >= cols) {
833✔
283
            line_width_chars = 0;
10✔
284
        } else if ((x + line_width_chars) > cols) {
823✔
285
            line_width_chars = cols - x;
×
286
        }
287
    }
288

289
    for (size_t lpc = 0; lpc < line.size();) {
7,173✔
290
        int exp_start_index = expanded_line.size();
6,340✔
291
        auto ch = static_cast<unsigned char>(line[lpc]);
6,340✔
292

293
        if (char_index == lr_chars.lr_start) {
6,340✔
294
            lr_bytes.lr_start = exp_start_index;
765✔
295
        } else if (char_index == lr_chars.lr_end) {
5,575✔
296
            lr_bytes.lr_end = exp_start_index;
9✔
297
            retval.mr_chars_out = char_index;
9✔
298
        }
299

300
        switch (ch) {
6,340✔
301
            case '\t': {
27✔
302
                do {
303
                    expanded_line.push_back(' ');
27✔
304
                    char_index += 1;
27✔
305
                    if (char_index == lr_chars.lr_start) {
27✔
306
                        lr_bytes.lr_start = expanded_line.size();
×
307
                    }
308
                    if (char_index == lr_chars.lr_end) {
27✔
309
                        lr_bytes.lr_end = expanded_line.size();
×
310
                        retval.mr_chars_out = char_index;
×
311
                    }
312
                } while (expanded_line.size() % 8);
27✔
313
                utf_adjustments.emplace_back(
5✔
314
                    lpc, expanded_line.size() - exp_start_index - 1);
5✔
315
                lpc += 1;
5✔
316
                break;
5✔
317
            }
318

319
            case '\x1b':
×
320
                expanded_line.append("\u238b");
×
321
                utf_adjustments.emplace_back(lpc, -1);
×
322
                char_index += 1;
×
323
                lpc += 1;
×
324
                break;
×
325

326
            case '\b':
×
327
                expanded_line.append("\u232b");
×
328
                utf_adjustments.emplace_back(lpc, -1);
×
329
                char_index += 1;
×
330
                lpc += 1;
×
331
                break;
×
332

333
            case '\x07':
×
334
                expanded_line.append("\U0001F514");
×
335
                utf_adjustments.emplace_back(lpc, -1);
×
336
                char_index += 1;
×
337
                lpc += 1;
×
338
                break;
×
339

340
            case '\r':
8✔
341
            case '\n':
342
                expanded_line.push_back(' ');
8✔
343
                char_index += 1;
8✔
344
                lpc += 1;
8✔
345
                break;
8✔
346

347
            default: {
6,327✔
348
                if (ch <= 0x1f) {
6,327✔
349
                    expanded_line.push_back(0xe2);
×
350
                    expanded_line.push_back(0x90);
×
351
                    expanded_line.push_back(0x80 + ch);
×
352
                    char_index += 1;
×
353
                    lpc += 1;
×
354
                    break;
×
355
                }
356

357
                auto exp_read_start = expanded_line.size();
6,327✔
358
                auto lpc_start = lpc;
6,327✔
359
                auto read_res
360
                    = ww898::utf::utf8::read([&line, &expanded_line, &lpc] {
×
361
                          auto ch = line[lpc++];
6,521✔
362
                          expanded_line.push_back(ch);
6,521✔
363
                          return ch;
6,521✔
364
                      });
6,327✔
365

366
                if (read_res.isErr()) {
6,327✔
367
                    log_trace(
×
368
                        "error:%d:%zu:%s", y, x + lpc, read_res.unwrapErr());
369
                    expanded_line.resize(exp_read_start);
×
370
                    expanded_line.push_back('?');
×
371
                    char_index += 1;
×
372
                    lpc = lpc_start + 1;
×
373
                } else {
374
                    auto wch = read_res.unwrap();
6,327✔
375
                    auto wcw_res = uc_width(wch, "UTF-8");
6,327✔
376
                    if (wcw_res < 0) {
6,327✔
377
                        log_trace(
×
378
                            "uc_width(%x) does not recognize width character",
379
                            wch);
380
                        wcw_res = 1;
×
381
                    }
382
                    if (lpc > (lpc_start + 1)) {
6,327✔
383
                        utf_adjustments.emplace_back(
93✔
384
                            lpc_start, wcw_res - (lpc - lpc_start));
93✔
385
                    }
386
                    char_index += wcw_res;
6,327✔
387
                    if (lr_bytes.lr_end == -1 && char_index > lr_chars.lr_end) {
6,327✔
UNCOV
388
                        lr_bytes.lr_end = exp_start_index;
×
UNCOV
389
                        retval.mr_chars_out = char_index - wcw_res;
×
390
                    }
391
                }
392
                break;
6,327✔
393
            }
6,327✔
394
        }
395
    }
396
    if (lr_bytes.lr_start == -1) {
833✔
397
        lr_bytes.lr_start = expanded_line.size();
68✔
398
    }
399
    if (lr_bytes.lr_end == -1) {
833✔
400
        lr_bytes.lr_end = expanded_line.size();
824✔
401
    }
402
    if (retval.mr_chars_out == 0) {
833✔
403
        retval.mr_chars_out = char_index;
824✔
404
    }
405
    retval.mr_bytes_remaining = expanded_line.size() - lr_bytes.lr_end;
833✔
406
    expanded_line.resize(lr_bytes.lr_end);
833✔
407

408
    auto& vc = view_colors::singleton();
833✔
409
    auto base_attrs = vc.attrs_for_role(base_role);
833✔
410
    if (lr_chars.length() > 0) {
833✔
411
        ncplane_erase_region(window, y, x, 1, lr_chars.length());
823✔
412
        if (lr_bytes.lr_start < (int) expanded_line.size()) {
823✔
413
            ncplane_putstr_yx(
765✔
414
                window, y, x, &expanded_line.c_str()[lr_bytes.lr_start]);
765✔
415
        } else {
416
            // Need to move the cursor so the hline call below goes to the
417
            // right place
418
            ncplane_cursor_move_yx(window, y, x);
58✔
419
        }
420
        nccell clear_cell;
421
        nccell_init(&clear_cell);
823✔
422
        nccell_prime(
823✔
423
            window, &clear_cell, " ", 0, view_colors::to_channels(base_attrs));
424
        ncplane_hline(
823✔
425
            window, &clear_cell, lr_chars.length() - retval.mr_chars_out);
823✔
426
    }
427

428
    text_attrs resolved_line_attrs[line_width_chars + 1];
55,885✔
429

430
    std::stable_sort(sa.begin(), sa.end());
833✔
431
    for (auto iter = sa.cbegin(); iter != sa.cend(); ++iter) {
2,189✔
432
        auto attr_range = iter->sa_range;
1,356✔
433

434
        require(attr_range.lr_start >= 0);
1,356✔
435
        require(attr_range.lr_end >= -1);
1,356✔
436

437
        if (!(iter->sa_type == &VC_ROLE || iter->sa_type == &VC_ROLE_FG
2,501✔
438
              || iter->sa_type == &VC_STYLE || iter->sa_type == &VC_GRAPHIC
1,145✔
439
              || iter->sa_type == &SA_LEVEL || iter->sa_type == &VC_FOREGROUND
689✔
440
              || iter->sa_type == &VC_BACKGROUND
578✔
441
              || iter->sa_type == &VC_BLOCK_ELEM || iter->sa_type == &VC_ICON))
578✔
442
        {
443
            continue;
584✔
444
        }
445

446
        if (attr_range.lr_unit == line_range::unit::bytes) {
791✔
447
            for (const auto& adj : utf_adjustments) {
1,476✔
448
                // If the UTF adjustment is in the viewport, we need to adjust
449
                // this attribute.
450
                if (adj.uda_origin < iter->sa_range.lr_start) {
685✔
451
                    attr_range.lr_start += adj.uda_offset;
298✔
452
                }
453
            }
454

455
            if (attr_range.lr_end != -1) {
791✔
456
                for (const auto& adj : utf_adjustments) {
1,299✔
457
                    if (adj.uda_origin < iter->sa_range.lr_end) {
683✔
458
                        attr_range.lr_end += adj.uda_offset;
384✔
459
                    }
460
                }
461
            }
462
        }
463

464
        if (attr_range.lr_end == -1) {
791✔
465
            attr_range.lr_end = lr_chars.lr_start + line_width_chars;
175✔
466
        }
467
        if (attr_range.lr_end < lr_chars.lr_start) {
791✔
468
            continue;
×
469
        }
470
        attr_range.lr_start
471
            = std::max(0, attr_range.lr_start - lr_chars.lr_start);
791✔
472
        if (attr_range.lr_start > line_width_chars) {
791✔
473
            continue;
19✔
474
        }
475

476
        attr_range.lr_end
477
            = std::min(line_width_chars, attr_range.lr_end - lr_chars.lr_start);
772✔
478

479
        if (iter->sa_type == &VC_FOREGROUND) {
772✔
480
            auto attr_fg = iter->sa_value.get<styling::color_unit>();
×
481
            for (auto lpc = attr_range.lr_start; lpc < attr_range.lr_end; ++lpc)
×
482
            {
483
                resolved_line_attrs[lpc].ta_fg_color = attr_fg;
×
484
            }
485
            continue;
×
486
        }
487

488
        if (iter->sa_type == &VC_BACKGROUND) {
772✔
489
            auto attr_bg = iter->sa_value.get<styling::color_unit>();
×
490
            for (auto lpc = attr_range.lr_start; lpc < attr_range.lr_end; ++lpc)
×
491
            {
492
                resolved_line_attrs[lpc].ta_bg_color = attr_bg;
×
493
            }
494
            continue;
×
495
        }
496

497
        if (attr_range.lr_start < attr_range.lr_end) {
772✔
498
            auto attrs = text_attrs{};
765✔
499
            std::optional<const char*> graphic;
765✔
500

501
            if (iter->sa_type == &VC_GRAPHIC) {
765✔
502
                graphic = iter->sa_value.get<const char*>();
114✔
503
                attrs = text_attrs::with_altcharset();
114✔
504
                for (auto lpc = attr_range.lr_start; lpc < attr_range.lr_end;
228✔
505
                     ++lpc)
506
                {
507
                    ncplane_putstr_yx(window, y, x + lpc, graphic.value());
114✔
508
                }
509
            } else if (iter->sa_type == &VC_BLOCK_ELEM) {
651✔
510
                auto be = iter->sa_value.get<block_elem_t>();
×
511
                ncplane_pututf32_yx(
×
512
                    window, y, x + attr_range.lr_start, be.value);
×
513
                attrs = vc.attrs_for_role(be.role);
×
514
            } else if (iter->sa_type == &VC_ICON) {
651✔
515
                auto ic = iter->sa_value.get<ui_icon_t>();
13✔
516
                auto be = vc.wchar_for_icon(ic);
13✔
517

518
                ncplane_pututf32_yx(
13✔
519
                    window, y, x + attr_range.lr_start, be.value);
13✔
520
                attrs = vc.attrs_for_role(be.role);
13✔
521
                // clear the BG color, it interferes with the cursor BG
522
                attrs.ta_bg_color = styling::color_unit::EMPTY;
13✔
523
            } else if (iter->sa_type == &VC_STYLE) {
638✔
524
                attrs = iter->sa_value.get<text_attrs>();
342✔
525
            } else if (iter->sa_type == &SA_LEVEL) {
296✔
526
                attrs = vc.attrs_for_level(
111✔
527
                    (log_level_t) iter->sa_value.get<int64_t>());
111✔
528
            } else if (iter->sa_type == &VC_ROLE) {
185✔
529
                auto role = iter->sa_value.get<role_t>();
185✔
530
                attrs = vc.attrs_for_role(role);
185✔
531

532
                if (role == role_t::VCR_SELECTED_TEXT) {
185✔
533
                    retval.mr_selected_text
534
                        = string_fragment::from_str(line).sub_range(
×
535
                            iter->sa_range.lr_start, iter->sa_range.lr_end);
×
536
                }
537
            } else if (iter->sa_type == &VC_ROLE_FG) {
×
538
                auto role_attrs
539
                    = vc.attrs_for_role(iter->sa_value.get<role_t>());
×
540
                attrs.ta_fg_color = role_attrs.ta_fg_color;
×
541
            }
542

543
            if (graphic || !attrs.empty()) {
765✔
544
                if (std::holds_alternative<styling::semantic>(
611✔
545
                        attrs.ta_fg_color.cu_value))
546
                {
547
                    attrs.ta_fg_color
548
                        = vc.color_for_ident(al.to_string_fragment(iter));
92✔
549
                }
550
                if (std::holds_alternative<styling::semantic>(
611✔
551
                        attrs.ta_bg_color.cu_value))
552
                {
553
                    attrs.ta_bg_color
554
                        = vc.color_for_ident(al.to_string_fragment(iter));
×
555
                }
556

557
                for (auto lpc = attr_range.lr_start; lpc < attr_range.lr_end;
5,838✔
558
                     ++lpc)
559
                {
560
                    auto clear_rev = attrs.has_style(text_attrs::style::reverse)
5,227✔
561
                        && resolved_line_attrs[lpc].has_style(
5,227✔
562
                            text_attrs::style::reverse);
5,227✔
563
                    resolved_line_attrs[lpc] = attrs | resolved_line_attrs[lpc];
5,227✔
564
                    if (clear_rev) {
5,227✔
565
                        resolved_line_attrs[lpc].clear_style(
×
566
                            text_attrs::style::reverse);
567
                    }
568
                }
569
            }
570
        }
571
    }
572

573
    for (int lpc = 0; lpc < line_width_chars; lpc++) {
55,052✔
574
        auto cell_attrs = resolved_line_attrs[lpc] | base_attrs;
54,219✔
575

576
        cell_attrs.ta_fg_color = vc.ansi_to_theme_color(cell_attrs.ta_fg_color);
54,219✔
577
        cell_attrs.ta_bg_color = vc.ansi_to_theme_color(cell_attrs.ta_bg_color);
54,219✔
578
        auto chan = view_colors::to_channels(cell_attrs);
54,219✔
579
        ncplane_set_cell_yx(window, y, x + lpc, cell_attrs.ta_attrs, chan);
54,219✔
580
#if 0
581
        if (desired_fg == desired_bg) {
582
            if (desired_bg >= 0
583
                && desired_bg
584
                    < view_colors::vc_active_palette->tc_palette.size())
585
            {
586
                auto adjusted_color
587
                    = view_colors::vc_active_palette->tc_palette[desired_bg]
588
                          .xc_lab_color;
589
                if (adjusted_color.lc_l < 50.0) {
590
                    adjusted_color.lc_l += 50.0;
591
                } else {
592
                    adjusted_color.lc_l -= 50.0;
593
                }
594
                bg_color[lpc] = view_colors::vc_active_palette->match_color(
595
                    adjusted_color);
596
            }
597
        } else if (fg_color[lpc] >= 0
598
                   && fg_color[lpc]
599
                       < view_colors::vc_active_palette->tc_palette.size()
600
                   && bg_color[lpc] == -1
601
                   && base_attrs.ta_bg_color.value_or(0) >= 0
602
                   && base_attrs.ta_bg_color.value_or(0)
603
                       < view_colors::vc_active_palette->tc_palette.size())
604
        {
605
            const auto& fg_color_info
606
                = view_colors::vc_active_palette->tc_palette.at(fg_color[lpc]);
607
            const auto& bg_color_info
608
                = view_colors::vc_active_palette->tc_palette.at(
609
                    base_attrs.ta_bg_color.value_or(0));
610

611
            if (!fg_color_info.xc_lab_color.sufficient_contrast(
612
                    bg_color_info.xc_lab_color))
613
            {
614
                auto adjusted_color = bg_color_info.xc_lab_color;
615
                adjusted_color.lc_l = std::max(0.0, adjusted_color.lc_l - 40.0);
616
                auto new_bg = view_colors::vc_active_palette->match_color(
617
                    adjusted_color);
618
                for (int lpc2 = lpc; lpc2 < line_width_chars; lpc2++) {
619
                    if (fg_color[lpc2] == fg_color[lpc] && bg_color[lpc2] == -1)
620
                    {
621
                        bg_color[lpc2] = new_bg;
622
                    }
623
                }
624
            }
625
        }
626

627
        if (fg_color[lpc] == -1) {
628
            fg_color[lpc] = cur_fg;
629
        }
630
        if (bg_color[lpc] == -1) {
631
            bg_color[lpc] = cur_bg;
632
        }
633
#endif
634
    }
635

636
    return retval;
1,666✔
637
}
833✔
638

639
view_colors&
640
view_colors::singleton()
170,118✔
641
{
642
    static view_colors s_vc;
170,118✔
643

644
    return s_vc;
170,118✔
645
}
646

647
view_colors::view_colors()
1,167✔
648
    : vc_ansi_to_theme{
649
          styling::color_unit::from_palette({0}),
×
650
          styling::color_unit::from_palette({1}),
1,167✔
651
          styling::color_unit::from_palette({2}),
1,167✔
652
          styling::color_unit::from_palette({3}),
1,167✔
653
          styling::color_unit::from_palette({4}),
1,167✔
654
          styling::color_unit::from_palette({5}),
1,167✔
655
          styling::color_unit::from_palette({6}),
1,167✔
656
          styling::color_unit::from_palette({7}),
1,167✔
657
      }
134,205✔
658
{
659
    auto text_default = text_attrs{};
1,167✔
660
    text_default.ta_fg_color = styling::color_unit::from_palette(COLOR_WHITE);
1,167✔
661
    text_default.ta_bg_color = styling::color_unit::from_palette(COLOR_BLACK);
1,167✔
662
    this->vc_role_attrs[lnav::enums::to_underlying(role_t::VCR_TEXT)]
663
        = role_attrs{text_default, text_default};
1,167✔
664
}
1,167✔
665

666
block_elem_t
667
view_colors::wchar_for_icon(ui_icon_t ic) const
13✔
668
{
669
    return this->vc_icons[lnav::enums::to_underlying(ic)];
13✔
670
}
671

672
bool view_colors::initialized = false;
673

674
static const std::string COLOR_NAMES[] = {
675
    "black",
676
    "red",
677
    "green",
678
    "yellow",
679
    "blue",
680
    "magenta",
681
    "cyan",
682
    "white",
683
};
684

685
class ui_listener : public lnav_config_listener {
686
public:
687
    ui_listener() : lnav_config_listener(__FILE__) {}
1,167✔
688

689
    void reload_config(error_reporter& reporter) override
1,291✔
690
    {
691
        static auto op = lnav_operation{"reload_theme"};
1,291✔
692

693
        auto op_guard = lnav_opid_guard::internal(op);
1,291✔
694

695
        if (!view_colors::initialized) {
1,291✔
696
            view_colors::vc_active_palette = ansi_colors();
639✔
697
        }
698

699
        auto& vc = view_colors::singleton();
1,291✔
700

701
        for (const auto& pair : lnav_config.lc_ui_theme_defs) {
12,767✔
702
            vc.init_roles(pair.second, reporter);
11,476✔
703
        }
704

705
        const auto iter
706
            = lnav_config.lc_ui_theme_defs.find(lnav_config.lc_ui_theme);
1,291✔
707

708
        if (iter == lnav_config.lc_ui_theme_defs.end()) {
1,291✔
709
            auto theme_names
710
                = lnav_config.lc_ui_theme_defs | lnav::itertools::first();
18✔
711

712
            reporter(&lnav_config.lc_ui_theme,
18✔
713
                     lnav::console::user_message::error(
×
714
                         attr_line_t("unknown theme -- ")
36✔
715
                             .append_quoted(lnav_config.lc_ui_theme))
36✔
716
                         .with_help(attr_line_t("The available themes are: ")
36✔
717
                                        .join(theme_names, ", ")));
18✔
718

719
            vc.init_roles(lnav_config.lc_ui_theme_defs["default"], reporter);
18✔
720
            return;
18✔
721
        }
18✔
722

723
        if (view_colors::initialized) {
1,273✔
724
            vc.init_roles(iter->second, reporter);
635✔
725
        }
726
    }
1,291✔
727
};
728

729
std::optional<lab_color>
730
view_colors::to_lab_color(const styling::color_unit& color)
24,258✔
731
{
732
    auto* pal_color = std::get_if<palette_color>(&color.cu_value);
24,258✔
733
    if (pal_color != nullptr) {
24,258✔
734
        auto pal_index = *pal_color;
4,495✔
735
        if (pal_index < vc_active_palette->tc_palette.size()) {
4,495✔
736
            if (this->vc_notcurses != nullptr && pal_index == COLOR_BLACK) {
1,947✔
737
                // We use this as the default background, so try to get the
738
                // real default from the terminal.
739
                uint32_t chan = 0;
4✔
740
                notcurses_default_background(this->vc_notcurses, &chan);
4✔
741

742
                unsigned r = 0, g = 0, b = 0;
4✔
743

744
                ncchannel_rgb8(chan, &r, &g, &b);
4✔
745
                auto rgb = rgb_color{(short) r, (short) g, (short) b};
4✔
746
                return lab_color{rgb};
4✔
747
            }
748

749
            return vc_active_palette->tc_palette[pal_index].xc_lab_color;
1,943✔
750
        }
751
    } else {
752
        auto* col = std::get_if<rgb_color>(&color.cu_value);
19,763✔
753
        if (col != nullptr) {
19,763✔
754
            return lab_color{*col};
19,729✔
755
        }
756
    }
757

758
    return std::nullopt;
2,582✔
759
}
760

761
uint64_t
762
view_colors::to_channels(const text_attrs& ta)
56,413✔
763
{
764
    uint64_t retval = 0;
56,413✔
765
    std::visit(styling::overload{
×
766
                   [&retval](styling::transparent) {
40,281✔
767
                       ncchannels_set_fg_alpha(&retval, NCALPHA_TRANSPARENT);
40,281✔
768
                   },
40,281✔
769
                   [&retval](styling::semantic) {
×
770
                       ncchannels_set_fg_alpha(&retval, NCALPHA_TRANSPARENT);
×
771
                   },
×
772
                   [&retval](const palette_color& pc) {
16,062✔
773
                       if (pc == COLOR_WHITE) {
16,062✔
774
                           ncchannels_set_fg_default(&retval);
×
775
                       } else {
776
                           ncchannels_set_fg_palindex(&retval, pc);
16,062✔
777
                       }
778
                   },
16,062✔
779
                   [&retval](const rgb_color& rc) {
70✔
780
                       ncchannels_set_fg_rgb8(
70✔
781
                           &retval, rc.rc_r, rc.rc_g, rc.rc_b);
70✔
782
                   }},
70✔
783
               ta.ta_fg_color.cu_value);
56,413✔
784
    std::visit(styling::overload{
×
785
                   [&retval](styling::transparent) {
40,281✔
786
                       ncchannels_set_bg_alpha(&retval, NCALPHA_TRANSPARENT);
40,281✔
787
                   },
40,281✔
788
                   [&retval](styling::semantic) {
×
789
                       ncchannels_set_bg_alpha(&retval, NCALPHA_TRANSPARENT);
×
790
                   },
×
791
                   [&retval](const palette_color& pc) {
16,132✔
792
                       if (pc == COLOR_BLACK) {
16,132✔
793
                           ncchannels_set_bg_default(&retval);
7✔
794
                       } else {
795
                           ncchannels_set_bg_palindex(&retval, pc);
16,125✔
796
                       }
797
                   },
16,132✔
798
                   [&retval](const rgb_color& rc) {
×
799
                       ncchannels_set_bg_rgb8(
×
800
                           &retval, rc.rc_r, rc.rc_g, rc.rc_b);
×
801
                   },
×
802
               },
803
               ta.ta_bg_color.cu_value);
56,413✔
804

805
    return retval;
56,413✔
806
}
807

808
static ui_listener _UI_LISTENER;
809
term_color_palette* view_colors::vc_active_palette;
810

811
void
812
view_colors::init(notcurses* nc)
629✔
813
{
814
    vc_active_palette = ansi_colors();
629✔
815
    if (nc != nullptr) {
629✔
816
        const auto* caps = notcurses_capabilities(nc);
18✔
817
        if (caps->rgb) {
18✔
818
            log_info("terminal supports RGB colors");
×
819
        } else {
820
            log_info("terminal supports %d colors", caps->colors);
18✔
821
        }
822
        if (caps->colors > 8) {
18✔
823
            vc_active_palette = xterm_colors();
2✔
824
        }
825
    }
826

827
    singleton().vc_notcurses = nc;
629✔
828
    initialized = true;
629✔
829

830
    {
831
        auto reporter
832
            = [](const void*, const lnav::console::user_message& um) {};
16✔
833

834
        _UI_LISTENER.reload_config(reporter);
629✔
835
    }
836
}
629✔
837

838
styling::color_unit
839
view_colors::match_color(styling::color_unit cu) const
2,601,405✔
840
{
841
    if (this->vc_notcurses == nullptr) {
2,601,405✔
842
        return cu;
2,594,461✔
843
    }
844

845
    const auto* caps = notcurses_capabilities(this->vc_notcurses);
6,944✔
846

847
    if (caps->rgb) {
6,944✔
848
        return cu;
×
849
    }
850

851
    auto* rgb = std::get_if<rgb_color>(&cu.cu_value);
6,944✔
852
    if (rgb != nullptr) {
6,944✔
853
        auto lab = lab_color{*rgb};
2,308✔
854
        auto pal = vc_active_palette->match_color(lab);
2,308✔
855

856
        log_trace("mapped RGB (%d, %d, %d) to palette %d",
2,308✔
857
                  rgb->rc_r,
858
                  rgb->rc_g,
859
                  rgb->rc_b,
860
                  pal);
861
        return styling::color_unit::from_palette(palette_color{pal});
2,308✔
862
    }
863

864
    return cu;
4,636✔
865
}
866

867
view_colors::role_attrs
868
view_colors::to_attrs(const lnav_theme& lt,
1,154,471✔
869
                      const positioned_property<style_config>& pp_sc,
870
                      lnav_config_listener::error_reporter& reporter)
871
{
872
    const auto& sc = pp_sc.pp_value;
1,154,471✔
873
    std::string fg_color, bg_color;
1,154,471✔
874
    intern_string_t role_class;
1,154,471✔
875

876
    if (pp_sc.pp_path.empty()) {
1,154,471✔
877
#if 0
878
        // too slow to do this now
879
        reporter(&sc.sc_color, lnav::console::user_message::warning(""));
880
#endif
881
    } else if (!pp_sc.pp_path.empty()) {
1,011,679✔
882
        auto role_class_path = pp_sc.pp_path.to_string_fragment();
1,011,679✔
883
        auto inner
884
            = role_class_path.rsplit_pair(string_fragment::tag1{'/'}).value();
1,011,679✔
885
        auto outer
886
            = inner.first.rsplit_pair(string_fragment::tag1{'/'}).value();
1,011,679✔
887

888
        role_class = intern_string::lookup(
1,011,679✔
889
            fmt::format(FMT_STRING("-lnav_{}_{}"), outer.second, inner.second));
5,058,395✔
890
    }
891

892
    auto fg1 = sc.sc_color;
1,154,471✔
893
    auto bg1 = sc.sc_background_color;
1,154,471✔
894
    shlex(fg1).eval(fg_color, scoped_resolver{&lt.lt_vars});
1,154,471✔
895
    shlex(bg1).eval(bg_color, scoped_resolver{&lt.lt_vars});
1,154,471✔
896

897
    auto fg = styling::color_unit::from_str(fg_color).unwrapOrElse(
1,154,471✔
898
        [&](const auto& msg) {
3✔
899
            reporter(
6✔
900
                &sc.sc_color,
3✔
901
                lnav::console::user_message::error(
902
                    attr_line_t("invalid color -- ").append_quoted(sc.sc_color))
6✔
903
                    .with_reason(msg));
3✔
904
            return styling::color_unit::EMPTY;
3✔
905
        });
906
    auto bg = styling::color_unit::from_str(bg_color).unwrapOrElse(
1,154,471✔
907
        [&](const auto& msg) {
×
908
            reporter(&sc.sc_background_color,
×
909
                     lnav::console::user_message::error(
910
                         attr_line_t("invalid background color -- ")
×
911
                             .append_quoted(sc.sc_background_color))
×
912
                         .with_reason(msg));
×
913
            return styling::color_unit::EMPTY;
×
914
        });
915

916
    fg = this->match_color(fg);
1,154,471✔
917
    bg = this->match_color(bg);
1,154,471✔
918

919
    auto retval1 = text_attrs{0, fg, bg};
1,154,471✔
920
    text_attrs retval2;
1,154,471✔
921

922
    if (sc.sc_underline) {
1,154,471✔
923
        retval1 |= text_attrs::style::underline;
87,958✔
924
        retval2 |= text_attrs::style::underline;
87,958✔
925
    }
926
    if (sc.sc_bold) {
1,154,471✔
927
        retval1 |= text_attrs::style::bold;
251,812✔
928
        retval2 |= text_attrs::style::bold;
251,812✔
929
    }
930
    if (sc.sc_italic) {
1,154,471✔
931
        retval1 |= text_attrs::style::italic;
17,856✔
932
        retval2 |= text_attrs::style::italic;
17,856✔
933
    }
934
    if (sc.sc_strike) {
1,154,471✔
935
        retval1 |= text_attrs::style::struck;
×
936
        retval2 |= text_attrs::style::struck;
×
937
    }
938

939
    return {retval1, retval2, role_class};
1,154,471✔
940
}
1,154,471✔
941

942
void
943
view_colors::init_roles(const lnav_theme& lt,
12,129✔
944
                        lnav_config_listener::error_reporter& reporter)
945
{
946
    const auto& default_theme = lnav_config.lc_ui_theme_defs["default"];
12,129✔
947
    std::string err;
12,129✔
948

949
    size_t icon_index = 0;
12,129✔
950
    for (const auto& ic : {
12,129✔
951
             lt.lt_icon_hidden,
12,129✔
952
             lt.lt_icon_ok,
12,129✔
953
             lt.lt_icon_info,
12,129✔
954
             lt.lt_icon_warning,
12,129✔
955
             lt.lt_icon_error,
12,129✔
956

957
             lt.lt_icon_log_level_trace,
12,129✔
958
             lt.lt_icon_log_level_debug,
12,129✔
959
             lt.lt_icon_log_level_info,
12,129✔
960
             lt.lt_icon_log_level_stats,
12,129✔
961
             lt.lt_icon_log_level_notice,
12,129✔
962
             lt.lt_icon_log_level_warning,
12,129✔
963
             lt.lt_icon_log_level_error,
12,129✔
964
             lt.lt_icon_log_level_critical,
12,129✔
965
             lt.lt_icon_log_level_fatal,
12,129✔
966

967
             lt.lt_icon_play,
12,129✔
968
             lt.lt_icon_edit,
12,129✔
969
         })
412,386✔
970
    {
971
        size_t index = 0;
194,064✔
972
        if (ic.pp_value.ic_value) {
194,064✔
973
            auto read_res = ww898::utf::utf8::read([&ic, &index]() {
×
974
                return ic.pp_value.ic_value.value()[index++];
108,813✔
975
            });
30,544✔
976
            if (read_res.isErr()) {
30,544✔
977
                reporter(&ic,
×
978
                         lnav::console::user_message::error(
×
979
                             "icon is not valid UTF-8"));
980
            } else if (read_res.unwrap() != 0) {
30,544✔
981
                role_t icon_role;
982
                switch (static_cast<ui_icon_t>(icon_index)) {
30,544✔
983
                    case ui_icon_t::hidden:
1,909✔
984
                        icon_role = role_t::VCR_HIDDEN;
1,909✔
985
                        break;
1,909✔
986
                    case ui_icon_t::ok:
1,909✔
987
                        icon_role = role_t::VCR_OK;
1,909✔
988
                        break;
1,909✔
989
                    case ui_icon_t::info:
1,909✔
990
                        icon_role = role_t::VCR_INFO;
1,909✔
991
                        break;
1,909✔
992
                    case ui_icon_t::warning:
3,818✔
993
                    case ui_icon_t::log_level_warning:
994
                        icon_role = role_t::VCR_WARNING;
3,818✔
995
                        break;
3,818✔
996
                    case ui_icon_t::error:
7,636✔
997
                    case ui_icon_t::log_level_error:
998
                    case ui_icon_t::log_level_fatal:
999
                    case ui_icon_t::log_level_critical:
1000
                        icon_role = role_t::VCR_ERROR;
7,636✔
1001
                        break;
7,636✔
1002
                    case ui_icon_t::play:
1,909✔
1003
                        icon_role = role_t::VCR_OK;
1,909✔
1004
                        break;
1,909✔
1005
                    default:
11,454✔
1006
                        icon_role = role_t::VCR_TEXT;
11,454✔
1007
                        break;
11,454✔
1008
                }
1009
                this->vc_icons[icon_index]
1010
                    = block_elem_t{(char32_t) read_res.unwrap(), icon_role};
30,544✔
1011
            }
1012
        }
30,544✔
1013
        icon_index += 1;
194,064✔
1014
    }
206,193✔
1015

1016
    /* Setup the mappings from roles to actual colors. */
1017
    this->get_role_attrs(role_t::VCR_TEXT)
12,129✔
1018
        = this->to_attrs(lt, lt.lt_style_text, reporter);
12,129✔
1019

1020
    for (int ansi_fg = 1; ansi_fg < 8; ansi_fg++) {
97,032✔
1021
        auto fg_iter = lt.lt_vars.find(COLOR_NAMES[ansi_fg]);
84,903✔
1022
        auto fg_str = fg_iter == lt.lt_vars.end()
84,903✔
1023
            ? ""
84,903✔
1024
            : fmt::to_string(fg_iter->second);
100,935✔
1025

1026
        auto rgb_fg = from<rgb_color>(string_fragment::from_str(fg_str))
169,806✔
1027
                          .unwrapOrElse([&](const auto& msg) {
84,903✔
1028
                              reporter(&fg_str,
×
1029
                                       lnav::console::user_message::error(
1030
                                           attr_line_t("invalid color -- ")
×
1031
                                               .append_quoted(fg_str))
×
1032
                                           .with_reason(msg));
×
1033
                              return rgb_color{};
×
1034
                          });
1035

1036
        auto fg = vc_active_palette->match_color(lab_color(rgb_fg));
84,903✔
1037

1038
        if (rgb_fg.empty()) {
84,903✔
1039
            fg = ansi_fg;
16,032✔
1040
        }
1041

1042
        this->vc_ansi_to_theme[ansi_fg] = palette_color{fg};
84,903✔
1043
    }
84,903✔
1044

1045
#if 0
1046
    if (lnav_config.lc_ui_dim_text) {
1047
        this->get_role_attrs(role_t::VCR_TEXT).ra_normal.ta_attrs |= A_DIM;
1048
        this->get_role_attrs(role_t::VCR_TEXT).ra_reverse.ta_attrs |= A_DIM;
1049
    }
1050
#endif
1051
    this->get_role_attrs(role_t::VCR_SEARCH)
12,129✔
1052
        = role_attrs{text_attrs::with_reverse(), text_attrs::with_reverse()};
12,129✔
1053
    this->get_role_attrs(role_t::VCR_SEARCH).ra_class_name
12,129✔
1054
        = intern_string::lookup("-lnav_styles_search");
24,258✔
1055
    this->get_role_attrs(role_t::VCR_IDENTIFIER)
12,129✔
1056
        = this->to_attrs(lt, lt.lt_style_identifier, reporter);
12,129✔
1057
    this->get_role_attrs(role_t::VCR_OK)
12,129✔
1058
        = this->to_attrs(lt, lt.lt_style_ok, reporter);
12,129✔
1059
    this->get_role_attrs(role_t::VCR_INFO)
12,129✔
1060
        = this->to_attrs(lt, lt.lt_style_info, reporter);
12,129✔
1061
    this->get_role_attrs(role_t::VCR_ERROR)
12,129✔
1062
        = this->to_attrs(lt, lt.lt_style_error, reporter);
12,129✔
1063
    this->get_role_attrs(role_t::VCR_WARNING)
12,129✔
1064
        = this->to_attrs(lt, lt.lt_style_warning, reporter);
12,129✔
1065
    this->get_role_attrs(role_t::VCR_ALT_ROW)
12,129✔
1066
        = this->to_attrs(lt, lt.lt_style_alt_text, reporter);
12,129✔
1067
    this->get_role_attrs(role_t::VCR_HIDDEN)
12,129✔
1068
        = this->to_attrs(lt, lt.lt_style_hidden, reporter);
12,129✔
1069
    this->get_role_attrs(role_t::VCR_CURSOR_LINE)
12,129✔
1070
        = this->to_attrs(lt, lt.lt_style_cursor_line, reporter);
12,129✔
1071
    if (this->get_role_attrs(role_t::VCR_CURSOR_LINE).ra_normal.empty()) {
12,129✔
1072
        this->get_role_attrs(role_t::VCR_CURSOR_LINE) = this->to_attrs(
1,926✔
1073
            default_theme, default_theme.lt_style_cursor_line, reporter);
1,926✔
1074
    }
1075
    this->get_role_attrs(role_t::VCR_DISABLED_CURSOR_LINE)
12,129✔
1076
        = this->to_attrs(lt, lt.lt_style_disabled_cursor_line, reporter);
12,129✔
1077
    if (this->get_role_attrs(role_t::VCR_DISABLED_CURSOR_LINE)
12,129✔
1078
            .ra_normal.empty())
12,129✔
1079
    {
1080
        this->get_role_attrs(role_t::VCR_DISABLED_CURSOR_LINE)
17✔
1081
            = this->to_attrs(default_theme,
17✔
1082
                             default_theme.lt_style_disabled_cursor_line,
17✔
1083
                             reporter);
1084
    }
1085
    this->get_role_attrs(role_t::VCR_ADJUSTED_TIME)
12,129✔
1086
        = this->to_attrs(lt, lt.lt_style_adjusted_time, reporter);
12,129✔
1087
    this->get_role_attrs(role_t::VCR_SKEWED_TIME)
12,129✔
1088
        = this->to_attrs(lt, lt.lt_style_skewed_time, reporter);
12,129✔
1089
    this->get_role_attrs(role_t::VCR_OFFSET_TIME)
12,129✔
1090
        = this->to_attrs(lt, lt.lt_style_offset_time, reporter);
12,129✔
1091
    this->get_role_attrs(role_t::VCR_TIME_COLUMN)
12,129✔
1092
        = this->to_attrs(lt, lt.lt_style_time_column, reporter);
12,129✔
1093
    this->get_role_attrs(role_t::VCR_POPUP)
12,129✔
1094
        = this->to_attrs(lt, lt.lt_style_popup, reporter);
12,129✔
1095
    this->get_role_attrs(role_t::VCR_POPUP_BORDER)
12,129✔
1096
        = this->to_attrs(lt, lt.lt_style_popup_border, reporter);
12,129✔
1097
    this->get_role_attrs(role_t::VCR_INLINE_CODE)
12,129✔
1098
        = this->to_attrs(lt, lt.lt_style_inline_code, reporter);
12,129✔
1099
    this->get_role_attrs(role_t::VCR_QUOTED_CODE)
12,129✔
1100
        = this->to_attrs(lt, lt.lt_style_quoted_code, reporter);
12,129✔
1101
    this->get_role_attrs(role_t::VCR_CODE_BORDER)
12,129✔
1102
        = this->to_attrs(lt, lt.lt_style_code_border, reporter);
12,129✔
1103

1104
    {
1105
        auto& time_to_text
1106
            = this->get_role_attrs(role_t::VCR_TIME_COLUMN_TO_TEXT);
12,129✔
1107
        auto time_attrs = this->attrs_for_role(role_t::VCR_TIME_COLUMN);
12,129✔
1108
        auto text_attrs = this->attrs_for_role(role_t::VCR_TEXT);
12,129✔
1109
        time_to_text.ra_class_name.clear();
12,129✔
1110

1111
        time_to_text.ra_normal.ta_fg_color = time_attrs.ta_bg_color;
12,129✔
1112
        time_to_text.ra_normal.ta_bg_color = text_attrs.ta_bg_color;
12,129✔
1113

1114
        auto fg_as_lab_opt = to_lab_color(time_attrs.ta_bg_color);
12,129✔
1115
        auto bg_as_lab_opt = to_lab_color(text_attrs.ta_bg_color);
12,129✔
1116
        if (fg_as_lab_opt && bg_as_lab_opt) {
12,129✔
1117
            auto fg_as_lab = fg_as_lab_opt.value();
10,838✔
1118
            auto bg_as_lab = bg_as_lab_opt.value();
10,838✔
1119
            auto diff = fg_as_lab.lc_l - bg_as_lab.lc_l;
10,838✔
1120
            fg_as_lab.lc_l -= diff / 4.0;
10,838✔
1121
            bg_as_lab.lc_l += diff / 4.0;
10,838✔
1122

1123
            time_to_text.ra_normal.ta_fg_color = this->match_color(
10,838✔
1124
                styling::color_unit::from_rgb(fg_as_lab.to_rgb()));
10,838✔
1125
            time_to_text.ra_normal.ta_bg_color = this->match_color(
10,838✔
1126
                styling::color_unit::from_rgb(bg_as_lab.to_rgb()));
21,676✔
1127
        }
1128

1129
        if (bg_as_lab_opt) {
12,129✔
1130
            auto bg_as_lab = bg_as_lab_opt.value();
10,838✔
1131
            std::vector<std::pair<double, size_t>> contrasting;
10,838✔
1132
            for (const auto& [index, tcolor] :
102,502✔
1133
                 lnav::itertools::enumerate(vc_active_palette->tc_palette))
113,340✔
1134
            {
1135
                if (index < 16) {
91,664✔
1136
                    continue;
86,864✔
1137
                }
1138
                if (bg_as_lab.sufficient_contrast(tcolor.xc_lab_color)) {
4,800✔
1139
                    contrasting.emplace_back(
2,132✔
1140
                        bg_as_lab.deltaE(tcolor.xc_lab_color), index);
2,132✔
1141
                }
1142
            }
1143

1144
            log_info("found %zu contrasting colors for highlights",
10,838✔
1145
                     contrasting.size());
1146
            if (contrasting.empty()) {
10,838✔
1147
                for (auto lpc = size_t{0}; lpc < HI_COLOR_COUNT; lpc++) {
594,990✔
1148
                    this->vc_highlight_colors[lpc] = 16;
584,172✔
1149
                }
1150
            } else {
1151
                std::stable_sort(
20✔
1152
                    contrasting.begin(), contrasting.end(), std::greater{});
1153
                for (auto lpc = size_t{0}; lpc < HI_COLOR_COUNT; lpc++) {
1,100✔
1154
                    this->vc_highlight_colors[lpc]
1155
                        = contrasting[lpc % contrasting.size()].second;
1,080✔
1156
                }
1157
            }
1158
            if (lt.lt_style_cursor_line.pp_value.sc_background_color.empty()) {
10,838✔
1159
                auto adjusted_cursor = bg_as_lab;
1,909✔
1160
                if (adjusted_cursor.lc_l < 50) {
1,909✔
1161
                    adjusted_cursor.lc_l += 18;
1,909✔
1162
                } else {
1163
                    adjusted_cursor.lc_l -= 15;
×
1164
                }
1165
                auto new_cursor_bg = this->match_color(
1,909✔
1166
                    styling::color_unit::from_rgb(adjusted_cursor.to_rgb()));
1,909✔
1167
                this->get_role_attrs(role_t::VCR_CURSOR_LINE)
1,909✔
1168
                    .ra_normal.ta_bg_color
1169
                    = new_cursor_bg;
1,909✔
1170
            }
1171
            if (lt.lt_style_popup.pp_value.sc_background_color.empty()) {
10,838✔
1172
                auto adjusted_cursor = bg_as_lab;
1,909✔
1173
                if (adjusted_cursor.lc_l < 50) {
1,909✔
1174
                    adjusted_cursor.lc_l += 30;
1,909✔
1175
                } else {
1176
                    adjusted_cursor.lc_l -= 30;
×
1177
                }
1178
                auto new_cursor_bg = this->match_color(
1,909✔
1179
                    styling::color_unit::from_rgb(adjusted_cursor.to_rgb()));
1,909✔
1180
                this->get_role_attrs(role_t::VCR_POPUP).ra_normal.ta_bg_color
1,909✔
1181
                    = new_cursor_bg;
1,909✔
1182
            }
1183
            if (lt.lt_style_inline_code.pp_value.sc_background_color.empty()) {
10,838✔
1184
                auto adjusted_cursor = bg_as_lab;
3,184✔
1185
                if (adjusted_cursor.lc_l < 50) {
3,184✔
1186
                    adjusted_cursor.lc_l = 10;
3,184✔
1187
                } else {
1188
                    adjusted_cursor.lc_l -= 25;
×
1189
                }
1190
                auto new_cursor_bg = this->match_color(
3,184✔
1191
                    styling::color_unit::from_rgb(adjusted_cursor.to_rgb()));
3,184✔
1192
                this->get_role_attrs(role_t::VCR_INLINE_CODE)
3,184✔
1193
                    .ra_normal.ta_bg_color
1194
                    = new_cursor_bg;
3,184✔
1195
            }
1196
            if (lt.lt_style_quoted_code.pp_value.sc_background_color.empty()) {
10,838✔
1197
                auto adjusted_cursor = bg_as_lab;
3,184✔
1198
                if (adjusted_cursor.lc_l < 50) {
3,184✔
1199
                    adjusted_cursor.lc_l = 10;
3,184✔
1200
                } else {
1201
                    adjusted_cursor.lc_l -= 25;
×
1202
                }
1203
                auto new_cursor_bg = this->match_color(
3,184✔
1204
                    styling::color_unit::from_rgb(adjusted_cursor.to_rgb()));
3,184✔
1205
                this->get_role_attrs(role_t::VCR_QUOTED_CODE)
3,184✔
1206
                    .ra_normal.ta_bg_color
1207
                    = new_cursor_bg;
3,184✔
1208
                this->get_role_attrs(role_t::VCR_CODE_BORDER)
3,184✔
1209
                    .ra_normal.ta_bg_color
1210
                    = new_cursor_bg;
3,184✔
1211
            }
1212
        }
10,838✔
1213
    }
1214
    this->get_role_attrs(role_t::VCR_FILE_OFFSET)
12,129✔
1215
        = this->to_attrs(lt, lt.lt_style_file_offset, reporter);
12,129✔
1216
    this->get_role_attrs(role_t::VCR_INVALID_MSG)
12,129✔
1217
        = this->to_attrs(lt, lt.lt_style_invalid_msg, reporter);
12,129✔
1218

1219
    this->get_role_attrs(role_t::VCR_STATUS)
12,129✔
1220
        = this->to_attrs(lt, lt.lt_style_status, reporter);
12,129✔
1221
    this->get_role_attrs(role_t::VCR_WARN_STATUS)
12,129✔
1222
        = this->to_attrs(lt, lt.lt_style_warn_status, reporter);
12,129✔
1223
    this->get_role_attrs(role_t::VCR_ALERT_STATUS)
12,129✔
1224
        = this->to_attrs(lt, lt.lt_style_alert_status, reporter);
12,129✔
1225
    this->get_role_attrs(role_t::VCR_ACTIVE_STATUS)
12,129✔
1226
        = this->to_attrs(lt, lt.lt_style_active_status, reporter);
12,129✔
1227
    this->get_role_attrs(role_t::VCR_ACTIVE_STATUS2) = role_attrs{
12,129✔
1228
        this->get_role_attrs(role_t::VCR_ACTIVE_STATUS).ra_normal,
12,129✔
1229
        this->get_role_attrs(role_t::VCR_ACTIVE_STATUS).ra_reverse,
12,129✔
1230
    };
1231
    this->get_role_attrs(role_t::VCR_ACTIVE_STATUS2).ra_normal
12,129✔
1232
        |= text_attrs::style::bold;
12,129✔
1233
    this->get_role_attrs(role_t::VCR_ACTIVE_STATUS2).ra_reverse
12,129✔
1234
        |= text_attrs::style::bold;
12,129✔
1235
    this->get_role_attrs(role_t::VCR_STATUS_TITLE)
12,129✔
1236
        = this->to_attrs(lt, lt.lt_style_status_title, reporter);
12,129✔
1237
    this->get_role_attrs(role_t::VCR_STATUS_SUBTITLE)
12,129✔
1238
        = this->to_attrs(lt, lt.lt_style_status_subtitle, reporter);
12,129✔
1239
    this->get_role_attrs(role_t::VCR_STATUS_INFO)
12,129✔
1240
        = this->to_attrs(lt, lt.lt_style_status_info, reporter);
12,129✔
1241

1242
    this->get_role_attrs(role_t::VCR_STATUS_HOTKEY)
12,129✔
1243
        = this->to_attrs(lt, lt.lt_style_status_hotkey, reporter);
12,129✔
1244
    this->get_role_attrs(role_t::VCR_STATUS_TITLE_HOTKEY)
12,129✔
1245
        = this->to_attrs(lt, lt.lt_style_status_title_hotkey, reporter);
12,129✔
1246
    this->get_role_attrs(role_t::VCR_STATUS_DISABLED_TITLE)
12,129✔
1247
        = this->to_attrs(lt, lt.lt_style_status_disabled_title, reporter);
12,129✔
1248

1249
    this->get_role_attrs(role_t::VCR_H1)
12,129✔
1250
        = this->to_attrs(lt, lt.lt_style_header[0], reporter);
12,129✔
1251
    this->get_role_attrs(role_t::VCR_H2)
12,129✔
1252
        = this->to_attrs(lt, lt.lt_style_header[1], reporter);
12,129✔
1253
    this->get_role_attrs(role_t::VCR_H3)
12,129✔
1254
        = this->to_attrs(lt, lt.lt_style_header[2], reporter);
12,129✔
1255
    this->get_role_attrs(role_t::VCR_H4)
12,129✔
1256
        = this->to_attrs(lt, lt.lt_style_header[3], reporter);
12,129✔
1257
    this->get_role_attrs(role_t::VCR_H5)
12,129✔
1258
        = this->to_attrs(lt, lt.lt_style_header[4], reporter);
12,129✔
1259
    this->get_role_attrs(role_t::VCR_H6)
12,129✔
1260
        = this->to_attrs(lt, lt.lt_style_header[5], reporter);
12,129✔
1261
    this->get_role_attrs(role_t::VCR_HR)
12,129✔
1262
        = this->to_attrs(lt, lt.lt_style_hr, reporter);
12,129✔
1263
    this->get_role_attrs(role_t::VCR_HYPERLINK)
12,129✔
1264
        = this->to_attrs(lt, lt.lt_style_hyperlink, reporter);
12,129✔
1265
    this->get_role_attrs(role_t::VCR_LIST_GLYPH)
12,129✔
1266
        = this->to_attrs(lt, lt.lt_style_list_glyph, reporter);
12,129✔
1267
    this->get_role_attrs(role_t::VCR_BREADCRUMB)
12,129✔
1268
        = this->to_attrs(lt, lt.lt_style_breadcrumb, reporter);
12,129✔
1269
    this->get_role_attrs(role_t::VCR_TABLE_BORDER)
12,129✔
1270
        = this->to_attrs(lt, lt.lt_style_table_border, reporter);
12,129✔
1271
    this->get_role_attrs(role_t::VCR_TABLE_HEADER)
12,129✔
1272
        = this->to_attrs(lt, lt.lt_style_table_header, reporter);
12,129✔
1273
    this->get_role_attrs(role_t::VCR_QUOTE_BORDER)
12,129✔
1274
        = this->to_attrs(lt, lt.lt_style_quote_border, reporter);
12,129✔
1275
    this->get_role_attrs(role_t::VCR_QUOTED_TEXT)
12,129✔
1276
        = this->to_attrs(lt, lt.lt_style_quoted_text, reporter);
12,129✔
1277
    this->get_role_attrs(role_t::VCR_FOOTNOTE_BORDER)
12,129✔
1278
        = this->to_attrs(lt, lt.lt_style_footnote_border, reporter);
12,129✔
1279
    this->get_role_attrs(role_t::VCR_FOOTNOTE_TEXT)
12,129✔
1280
        = this->to_attrs(lt, lt.lt_style_footnote_text, reporter);
12,129✔
1281
    this->get_role_attrs(role_t::VCR_SNIPPET_BORDER)
12,129✔
1282
        = this->to_attrs(lt, lt.lt_style_snippet_border, reporter);
12,129✔
1283
    this->get_role_attrs(role_t::VCR_INDENT_GUIDE)
12,129✔
1284
        = this->to_attrs(lt, lt.lt_style_indent_guide, reporter);
12,129✔
1285

1286
    {
1287
        positioned_property<style_config> stitch_sc;
12,129✔
1288

1289
        stitch_sc.pp_value.sc_color
1290
            = lt.lt_style_status_subtitle.pp_value.sc_background_color;
12,129✔
1291
        stitch_sc.pp_value.sc_background_color
1292
            = lt.lt_style_status_title.pp_value.sc_background_color;
12,129✔
1293
        this->get_role_attrs(role_t::VCR_STATUS_STITCH_TITLE_TO_SUB)
12,129✔
1294
            = this->to_attrs(lt, stitch_sc, reporter);
12,129✔
1295
    }
12,129✔
1296
    {
1297
        positioned_property<style_config> stitch_sc;
12,129✔
1298

1299
        stitch_sc.pp_value.sc_color
1300
            = lt.lt_style_status_title.pp_value.sc_background_color;
12,129✔
1301
        stitch_sc.pp_value.sc_background_color
1302
            = lt.lt_style_status_subtitle.pp_value.sc_background_color;
12,129✔
1303
        this->get_role_attrs(role_t::VCR_STATUS_STITCH_SUB_TO_TITLE)
12,129✔
1304
            = this->to_attrs(lt, stitch_sc, reporter);
12,129✔
1305
    }
12,129✔
1306

1307
    {
1308
        positioned_property<style_config> stitch_sc;
12,129✔
1309

1310
        stitch_sc.pp_value.sc_color
1311
            = lt.lt_style_status.pp_value.sc_background_color;
12,129✔
1312
        stitch_sc.pp_value.sc_background_color
1313
            = lt.lt_style_status_subtitle.pp_value.sc_background_color;
12,129✔
1314
        this->get_role_attrs(role_t::VCR_STATUS_STITCH_SUB_TO_NORMAL)
12,129✔
1315
            = this->to_attrs(lt, stitch_sc, reporter);
12,129✔
1316
    }
12,129✔
1317
    {
1318
        positioned_property<style_config> stitch_sc;
12,129✔
1319

1320
        stitch_sc.pp_value.sc_color
1321
            = lt.lt_style_status_subtitle.pp_value.sc_background_color;
12,129✔
1322
        stitch_sc.pp_value.sc_background_color
1323
            = lt.lt_style_status.pp_value.sc_background_color;
12,129✔
1324
        this->get_role_attrs(role_t::VCR_STATUS_STITCH_NORMAL_TO_SUB)
12,129✔
1325
            = this->to_attrs(lt, stitch_sc, reporter);
12,129✔
1326
    }
12,129✔
1327

1328
    {
1329
        positioned_property<style_config> stitch_sc;
12,129✔
1330

1331
        stitch_sc.pp_value.sc_color
1332
            = lt.lt_style_status.pp_value.sc_background_color;
12,129✔
1333
        stitch_sc.pp_value.sc_background_color
1334
            = lt.lt_style_status_title.pp_value.sc_background_color;
12,129✔
1335
        this->get_role_attrs(role_t::VCR_STATUS_STITCH_TITLE_TO_NORMAL)
12,129✔
1336
            = this->to_attrs(lt, stitch_sc, reporter);
12,129✔
1337
    }
12,129✔
1338
    {
1339
        positioned_property<style_config> stitch_sc;
12,129✔
1340

1341
        stitch_sc.pp_value.sc_color
1342
            = lt.lt_style_status_title.pp_value.sc_background_color;
12,129✔
1343
        stitch_sc.pp_value.sc_background_color
1344
            = lt.lt_style_status.pp_value.sc_background_color;
12,129✔
1345
        this->get_role_attrs(role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE)
12,129✔
1346
            = this->to_attrs(lt, stitch_sc, reporter);
12,129✔
1347
    }
12,129✔
1348

1349
    this->get_role_attrs(role_t::VCR_INACTIVE_STATUS)
12,129✔
1350
        = this->to_attrs(lt, lt.lt_style_inactive_status, reporter);
12,129✔
1351
    this->get_role_attrs(role_t::VCR_INACTIVE_ALERT_STATUS)
12,129✔
1352
        = this->to_attrs(lt, lt.lt_style_inactive_alert_status, reporter);
12,129✔
1353

1354
    this->get_role_attrs(role_t::VCR_FOCUSED)
12,129✔
1355
        = this->to_attrs(lt, lt.lt_style_focused, reporter);
12,129✔
1356
    this->get_role_attrs(role_t::VCR_DISABLED_FOCUSED)
12,129✔
1357
        = this->to_attrs(lt, lt.lt_style_disabled_focused, reporter);
12,129✔
1358
    this->get_role_attrs(role_t::VCR_SCROLLBAR)
12,129✔
1359
        = this->to_attrs(lt, lt.lt_style_scrollbar, reporter);
12,129✔
1360
    {
1361
        positioned_property<style_config> bar_sc;
12,129✔
1362

1363
        bar_sc.pp_value.sc_color = lt.lt_style_error.pp_value.sc_color;
12,129✔
1364
        bar_sc.pp_value.sc_background_color
1365
            = lt.lt_style_scrollbar.pp_value.sc_background_color;
12,129✔
1366
        this->get_role_attrs(role_t::VCR_SCROLLBAR_ERROR)
12,129✔
1367
            = this->to_attrs(lt, bar_sc, reporter);
12,129✔
1368
    }
12,129✔
1369
    {
1370
        positioned_property<style_config> bar_sc;
12,129✔
1371

1372
        bar_sc.pp_value.sc_color = lt.lt_style_warning.pp_value.sc_color;
12,129✔
1373
        bar_sc.pp_value.sc_background_color
1374
            = lt.lt_style_scrollbar.pp_value.sc_background_color;
12,129✔
1375
        this->get_role_attrs(role_t::VCR_SCROLLBAR_WARNING)
12,129✔
1376
            = this->to_attrs(lt, bar_sc, reporter);
12,129✔
1377
    }
12,129✔
1378

1379
    this->get_role_attrs(role_t::VCR_OBJECT_KEY)
12,129✔
1380
        = this->to_attrs(lt, lt.lt_style_object_key, reporter);
12,129✔
1381
    this->get_role_attrs(role_t::VCR_KEYWORD)
12,129✔
1382
        = this->to_attrs(lt, lt.lt_style_keyword, reporter);
12,129✔
1383
    this->get_role_attrs(role_t::VCR_STRING)
12,129✔
1384
        = this->to_attrs(lt, lt.lt_style_string, reporter);
12,129✔
1385
    this->get_role_attrs(role_t::VCR_COMMENT)
12,129✔
1386
        = this->to_attrs(lt, lt.lt_style_comment, reporter);
12,129✔
1387
    this->get_role_attrs(role_t::VCR_DOC_DIRECTIVE)
12,129✔
1388
        = this->to_attrs(lt, lt.lt_style_doc_directive, reporter);
12,129✔
1389
    this->get_role_attrs(role_t::VCR_VARIABLE)
12,129✔
1390
        = this->to_attrs(lt, lt.lt_style_variable, reporter);
12,129✔
1391
    this->get_role_attrs(role_t::VCR_SYMBOL)
12,129✔
1392
        = this->to_attrs(lt, lt.lt_style_symbol, reporter);
12,129✔
1393
    this->get_role_attrs(role_t::VCR_NULL)
12,129✔
1394
        = this->to_attrs(lt, lt.lt_style_null, reporter);
12,129✔
1395
    this->get_role_attrs(role_t::VCR_ASCII_CTRL)
12,129✔
1396
        = this->to_attrs(lt, lt.lt_style_ascii_ctrl, reporter);
12,129✔
1397
    this->get_role_attrs(role_t::VCR_NON_ASCII)
12,129✔
1398
        = this->to_attrs(lt, lt.lt_style_non_ascii, reporter);
12,129✔
1399
    this->get_role_attrs(role_t::VCR_NUMBER)
12,129✔
1400
        = this->to_attrs(lt, lt.lt_style_number, reporter);
12,129✔
1401
    this->get_role_attrs(role_t::VCR_FUNCTION)
12,129✔
1402
        = this->to_attrs(lt, lt.lt_style_function, reporter);
12,129✔
1403
    this->get_role_attrs(role_t::VCR_TYPE)
12,129✔
1404
        = this->to_attrs(lt, lt.lt_style_type, reporter);
12,129✔
1405
    this->get_role_attrs(role_t::VCR_SEP_REF_ACC)
12,129✔
1406
        = this->to_attrs(lt, lt.lt_style_sep_ref_acc, reporter);
12,129✔
1407
    this->get_role_attrs(role_t::VCR_SUGGESTION)
12,129✔
1408
        = this->to_attrs(lt, lt.lt_style_suggestion, reporter);
12,129✔
1409
    this->get_role_attrs(role_t::VCR_SELECTED_TEXT)
12,129✔
1410
        = this->to_attrs(lt, lt.lt_style_selected_text, reporter);
12,129✔
1411
    if (this->get_role_attrs(role_t::VCR_SELECTED_TEXT).ra_normal.empty()) {
12,129✔
1412
        this->get_role_attrs(role_t::VCR_SELECTED_TEXT) = this->to_attrs(
17✔
1413
            default_theme, default_theme.lt_style_selected_text, reporter);
17✔
1414
    }
1415
    this->get_role_attrs(role_t::VCR_FUZZY_MATCH)
12,129✔
1416
        = this->to_attrs(lt, lt.lt_style_fuzzy_match, reporter);
12,129✔
1417

1418
    this->get_role_attrs(role_t::VCR_RE_SPECIAL)
12,129✔
1419
        = this->to_attrs(lt, lt.lt_style_re_special, reporter);
12,129✔
1420
    this->get_role_attrs(role_t::VCR_RE_REPEAT)
12,129✔
1421
        = this->to_attrs(lt, lt.lt_style_re_repeat, reporter);
12,129✔
1422
    this->get_role_attrs(role_t::VCR_FILE)
12,129✔
1423
        = this->to_attrs(lt, lt.lt_style_file, reporter);
12,129✔
1424

1425
    this->get_role_attrs(role_t::VCR_DIFF_DELETE)
12,129✔
1426
        = this->to_attrs(lt, lt.lt_style_diff_delete, reporter);
12,129✔
1427
    this->get_role_attrs(role_t::VCR_DIFF_ADD)
12,129✔
1428
        = this->to_attrs(lt, lt.lt_style_diff_add, reporter);
12,129✔
1429
    this->get_role_attrs(role_t::VCR_DIFF_SECTION)
12,129✔
1430
        = this->to_attrs(lt, lt.lt_style_diff_section, reporter);
12,129✔
1431

1432
    this->get_role_attrs(role_t::VCR_LOW_THRESHOLD)
12,129✔
1433
        = this->to_attrs(lt, lt.lt_style_low_threshold, reporter);
12,129✔
1434
    this->get_role_attrs(role_t::VCR_MED_THRESHOLD)
12,129✔
1435
        = this->to_attrs(lt, lt.lt_style_med_threshold, reporter);
12,129✔
1436
    this->get_role_attrs(role_t::VCR_HIGH_THRESHOLD)
12,129✔
1437
        = this->to_attrs(lt, lt.lt_style_high_threshold, reporter);
12,129✔
1438

1439
    {
1440
        auto t0_attrs = this->to_attrs(lt, lt.lt_style_low_threshold, reporter);
12,129✔
1441
        auto t3_attrs = this->to_attrs(lt, lt.lt_style_med_threshold, reporter);
12,129✔
1442
        auto t6_attrs
1443
            = this->to_attrs(lt, lt.lt_style_high_threshold, reporter);
12,129✔
1444

1445
        auto t0_color = this->vc_active_palette->to_lab_color(
12,129✔
1446
            t0_attrs.ra_normal.ta_bg_color);
1447
        auto t1_attrs = t0_attrs;
12,129✔
1448
        auto t2_attrs = t3_attrs;
12,129✔
1449
        auto t3_color = this->vc_active_palette->to_lab_color(
12,129✔
1450
            t3_attrs.ra_normal.ta_bg_color);
1451
        auto t4_attrs = t3_attrs;
12,129✔
1452
        auto t5_attrs = t6_attrs;
12,129✔
1453
        auto t6_color = this->vc_active_palette->to_lab_color(
12,129✔
1454
            t6_attrs.ra_normal.ta_bg_color);
1455
        if (t0_color && t3_color && t6_color) {
12,129✔
1456
            auto low_mid = t0_color->avg(t3_color.value());
12,112✔
1457
            auto mid_high = t3_color->avg(t6_color.value());
12,112✔
1458
            t1_attrs.ra_normal.ta_bg_color = this->match_color(
12,112✔
1459
                styling::color_unit::from_rgb(t0_color->avg(low_mid).to_rgb()));
12,112✔
1460
            t2_attrs.ra_normal.ta_bg_color = this->match_color(
12,112✔
1461
                styling::color_unit::from_rgb(t3_color->avg(low_mid).to_rgb()));
12,112✔
1462
            t4_attrs.ra_normal.ta_bg_color
1463
                = this->match_color(styling::color_unit::from_rgb(
12,112✔
1464
                    t3_color->avg(mid_high).to_rgb()));
12,112✔
1465
            t5_attrs.ra_normal.ta_bg_color
1466
                = this->match_color(styling::color_unit::from_rgb(
12,112✔
1467
                    t6_color->avg(mid_high).to_rgb()));
24,224✔
1468
        }
1469
        this->get_role_attrs(role_t::VCR_SPECTRO_THRESHOLD0) = t0_attrs;
12,129✔
1470
        this->get_role_attrs(role_t::VCR_SPECTRO_THRESHOLD1) = t1_attrs;
12,129✔
1471
        this->get_role_attrs(role_t::VCR_SPECTRO_THRESHOLD2) = t2_attrs;
12,129✔
1472
        this->get_role_attrs(role_t::VCR_SPECTRO_THRESHOLD3) = t3_attrs;
12,129✔
1473
        this->get_role_attrs(role_t::VCR_SPECTRO_THRESHOLD4) = t4_attrs;
12,129✔
1474
        this->get_role_attrs(role_t::VCR_SPECTRO_THRESHOLD5) = t5_attrs;
12,129✔
1475
        this->get_role_attrs(role_t::VCR_SPECTRO_THRESHOLD6) = t6_attrs;
12,129✔
1476
    }
1477

1478
    for (auto level = static_cast<log_level_t>(LEVEL_UNKNOWN + 1);
12,129✔
1479
         level < LEVEL__MAX;
181,935✔
1480
         level = static_cast<log_level_t>(level + 1))
169,806✔
1481
    {
1482
        auto level_iter = lt.lt_level_styles.find(level);
169,806✔
1483

1484
        if (level_iter == lt.lt_level_styles.end()) {
169,806✔
1485
            this->vc_level_attrs[level]
121,034✔
1486
                = role_attrs{text_attrs{}, text_attrs{}};
121,034✔
1487
        } else {
1488
            this->vc_level_attrs[level]
48,772✔
1489
                = this->to_attrs(lt, level_iter->second, reporter);
48,772✔
1490
        }
1491
    }
1492
    this->vc_level_attrs[LEVEL_UNKNOWN] = this->vc_level_attrs[LEVEL_INFO];
12,129✔
1493

1494
    for (int32_t role_index = 0;
1,212,900✔
1495
         role_index < lnav::enums::to_underlying(role_t::VCR__MAX);
1,212,900✔
1496
         role_index++)
1497
    {
1498
        const auto& ra = this->vc_role_attrs[role_index];
1,200,771✔
1499
        if (ra.ra_class_name.empty()) {
1,200,771✔
1500
            continue;
176,963✔
1501
        }
1502

1503
        this->vc_class_to_role[ra.ra_class_name.to_string()]
2,047,616✔
1504
            = VC_ROLE.value(role_t(role_index));
3,071,424✔
1505
    }
1506
    for (int level_index = 0; level_index < LEVEL__MAX; level_index++) {
194,064✔
1507
        const auto& ra = this->vc_level_attrs[level_index];
181,935✔
1508
        if (ra.ra_class_name.empty()) {
181,935✔
1509
            continue;
133,487✔
1510
        }
1511

1512
        this->vc_class_to_role[ra.ra_class_name.to_string()]
96,896✔
1513
            = SA_LEVEL.value(level_index);
145,344✔
1514
    }
1515

1516
    if (this->vc_notcurses) {
12,129✔
1517
        auto& mouse_i = injector::get<xterm_mouse&>();
36✔
1518
        mouse_i.set_enabled(
36✔
1519
            this->vc_notcurses,
1520
            check_experimental("mouse")
36✔
1521
                || lnav_config.lc_mouse_mode == lnav_mouse_mode::enabled);
36✔
1522
    }
1523
}
12,129✔
1524

1525
styling::color_unit
1526
view_colors::color_for_ident(const char* str, size_t len) const
5,226✔
1527
{
1528
    auto index = crc32(1, (const Bytef*) str, len);
5,226✔
1529

1530
    if (str[0] == '#' && (len == 4 || len == 7)) {
5,226✔
1531
        auto fg_res
1532
            = styling::color_unit::from_str(string_fragment(str, 0, len));
47✔
1533
        if (fg_res.isOk()) {
47✔
1534
            return fg_res.unwrap();
×
1535
        }
1536
    }
47✔
1537

1538
    const auto offset = index % HI_COLOR_COUNT;
5,226✔
1539
    if (this->vc_highlight_colors[offset] == 0) {
5,226✔
1540
        return styling::color_unit::EMPTY;
×
1541
    }
1542
    auto retval = styling::color_unit::from_palette(
5,226✔
1543
        palette_color{static_cast<uint8_t>(this->vc_highlight_colors[offset])});
5,226✔
1544

1545
    return retval;
5,226✔
1546
}
1547

1548
text_attrs
1549
view_colors::attrs_for_ident(const char* str, size_t len) const
5,150✔
1550
{
1551
    auto retval = this->attrs_for_role(role_t::VCR_IDENTIFIER);
5,150✔
1552

1553
    if (std::holds_alternative<styling::semantic>(retval.ta_fg_color.cu_value))
5,150✔
1554
    {
1555
        retval.ta_fg_color = this->color_for_ident(str, len);
5,134✔
1556
    }
1557
    if (std::holds_alternative<styling::semantic>(retval.ta_bg_color.cu_value))
5,150✔
1558
    {
1559
        retval.ta_bg_color = this->color_for_ident(str, len);
×
1560
    }
1561

1562
    return retval;
5,150✔
1563
}
1564

1565
styling::color_unit
1566
view_colors::ansi_to_theme_color(styling::color_unit ansi_fg) const
108,438✔
1567
{
1568
    auto* palp = std::get_if<palette_color>(&ansi_fg.cu_value);
108,438✔
1569
    if (palp != nullptr) {
108,438✔
1570
        auto pal = static_cast<ansi_color>(*palp);
30,446✔
1571

1572
        if (pal >= ansi_color::black && pal <= ansi_color::white) {
30,446✔
1573
            return this->vc_ansi_to_theme[lnav::enums::to_underlying(pal)];
84✔
1574
        }
1575
    }
1576

1577
    return ansi_fg;
108,354✔
1578
}
1579

1580
Result<screen_curses, std::string>
1581
screen_curses::create(const notcurses_options& options)
18✔
1582
{
1583
    auto* nc = notcurses_core_init(&options, stdout);
18✔
1584
    if (nc == nullptr) {
18✔
1585
        return Err(fmt::format(FMT_STRING("unable to initialize notcurses {}"),
×
1586
                               lnav::from_errno()));
×
1587
    }
1588

1589
    auto& mouse_i = injector::get<xterm_mouse&>();
18✔
1590
    mouse_i.set_enabled(
18✔
1591
        nc,
1592
        check_experimental("mouse")
18✔
1593
            || lnav_config.lc_mouse_mode == lnav_mouse_mode::enabled);
18✔
1594

1595
    auto_mem<char> term_name;
18✔
1596
    term_name = notcurses_detected_terminal(nc);
18✔
1597
    log_info("notcurses detected terminal: %s", term_name.in());
18✔
1598

1599
    auto retval = screen_curses(nc);
18✔
1600

1601
    tcgetattr(STDIN_FILENO, &retval.sc_termios);
18✔
1602
    retval.sc_termios.c_cc[VSTART] = 0;
18✔
1603
    retval.sc_termios.c_cc[VSTOP] = 0;
18✔
1604
#ifdef VDISCARD
1605
    retval.sc_termios.c_cc[VDISCARD] = 0;
18✔
1606
#endif
1607
#ifdef VDSUSP
1608
    retval.sc_termios.c_cc[VDSUSP] = 0;
1609
#endif
1610
    tcsetattr(STDIN_FILENO, TCSANOW, &retval.sc_termios);
18✔
1611

1612
    return Ok(std::move(retval));
18✔
1613
}
18✔
1614

1615
screen_curses::screen_curses(screen_curses&& other) noexcept
72✔
1616
    : sc_termios(other.sc_termios),
72✔
1617
      sc_notcurses(std::exchange(other.sc_notcurses, nullptr))
72✔
1618
{
1619
}
72✔
1620

1621
extern "C"
1622
{
1623
Terminfo*
1624
terminfo_load_from_internal(const char* term_name)
18✔
1625
{
1626
    log_debug("checking for internal terminfo for: %s", term_name);
18✔
1627
    auto term_name_sf = string_fragment::from_c_str(term_name);
18✔
1628
    for (const auto& tf : lnav_terminfo_files) {
182✔
1629
        if (tf.get_name() != term_name_sf) {
182✔
1630
            continue;
164✔
1631
        }
1632
        log_info("  found internal terminfo!");
18✔
1633
        auto sfp = tf.to_string_fragment_producer();
18✔
1634
        auto content = sfp->to_string();
18✔
1635
        auto* retval = terminfo_parse(content.c_str(), content.size());
18✔
1636
        if (retval != nullptr) {
18✔
1637
            return retval;
18✔
1638
        }
1639
        log_error("  failed to load internal terminfo");
×
1640
    }
36✔
1641

1642
    return nullptr;
×
1643
}
1644
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc