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

tstack / lnav / 22507085525-2793

27 Feb 2026 10:49PM UTC coverage: 68.948% (-0.02%) from 68.966%
22507085525-2793

push

github

tstack
fix mixup of O_CLOEXEC with FD_CLOEXEC

52007 of 75429 relevant lines covered (68.95%)

440500.48 hits per line

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

83.43
/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 "unistr.h"
54
#include "uniwidth.h"
55
#include "xterm_mouse.hh"
56

57
using namespace std::chrono_literals;
58

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

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

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

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

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

86
    return retval;
217✔
87
}
88

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

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

102
alerter&
103
alerter::singleton()
640✔
104
{
105
    static alerter retval;
106

107
    return retval;
640✔
108
}
109

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

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

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

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

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

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

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

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

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

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

184
    this->vc_needs_update = false;
391✔
185

186
    if (!this->vc_visible) {
391✔
187
        return retval;
224✔
188
    }
189

190
    for (auto* child : this->vc_children) {
208✔
191
        retval = child->do_update() || retval;
41✔
192
    }
193
    return retval;
167✔
194
}
195

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

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

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

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

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

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

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

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

271
    require(lr_chars.lr_end >= 0);
895✔
272

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

279
    {
280
        unsigned rows, cols;
281
        ncplane_dim_yx(window, &rows, &cols);
895✔
282

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

290
    auto last_ch_col_count = 0;
895✔
291
    std::optional<int> join_start_index;
895✔
292
    for (size_t lpc = 0; lpc < line.size();) {
7,908✔
293
        int exp_start_index = expanded_line.size();
7,013✔
294
        auto ch = static_cast<unsigned char>(line[lpc]);
7,013✔
295
        auto curr_ch_col_count = 0;
7,013✔
296

297
        if (char_index == lr_chars.lr_start) {
7,013✔
298
            lr_bytes.lr_start = exp_start_index;
813✔
299
        } else if (char_index == lr_chars.lr_end) {
6,200✔
300
            lr_bytes.lr_end = exp_start_index;
10✔
301
            retval.mr_chars_out = char_index;
10✔
302
        }
303

304
        switch (ch) {
7,013✔
305
            case '\t': {
5✔
306
                auto tab_size = 0;
5✔
307
                do {
308
                    expanded_line.push_back(' ');
27✔
309
                    tab_size += 1;
27✔
310
                    char_index += 1;
27✔
311
                    if (char_index == lr_chars.lr_start) {
27✔
312
                        lr_bytes.lr_start = expanded_line.size();
×
313
                    }
314
                    if (char_index == lr_chars.lr_end) {
27✔
315
                        lr_bytes.lr_end = expanded_line.size();
×
316
                        retval.mr_chars_out = char_index;
×
317
                    }
318
                } while (expanded_line.size() % 8 > 0);
27✔
319
                curr_ch_col_count = tab_size;
5✔
320
                utf_adjustments.emplace_back(
5✔
321
                    lpc, expanded_line.size() - exp_start_index - 1);
5✔
322
                lpc += 1;
5✔
323
                break;
5✔
324
            }
325

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

334
            case '\b':
×
335
                expanded_line.append("\u232b");
×
336
                utf_adjustments.emplace_back(lpc, -1);
×
337
                curr_ch_col_count = 1;
×
338
                char_index += 1;
×
339
                lpc += 1;
×
340
                break;
×
341

342
            case '\x07':
×
343
                expanded_line.append("\U0001F514");
×
344
                utf_adjustments.emplace_back(lpc, -1);
×
345
                curr_ch_col_count = 1;
×
346
                char_index += 1;
×
347
                lpc += 1;
×
348
                break;
×
349

350
            case '\r':
8✔
351
            case '\n':
352
                expanded_line.push_back(' ');
8✔
353
                curr_ch_col_count = 1;
8✔
354
                char_index += 1;
8✔
355
                lpc += 1;
8✔
356
                break;
8✔
357

358
            default: {
7,000✔
359
                if (ch <= 0x1f) {
7,000✔
360
                    expanded_line.push_back(0xe2);
×
361
                    expanded_line.push_back(0x90);
×
362
                    expanded_line.push_back(0x80 + ch);
×
363
                    curr_ch_col_count = 1;
×
364
                    char_index += 1;
×
365
                    lpc += 1;
×
366
                    break;
7,000✔
367
                }
368

369
                if (ch <= 0x7f) {
7,000✔
370
                    expanded_line.push_back(ch);
6,905✔
371
                    curr_ch_col_count = 1;
6,905✔
372
                    char_index += 1;
6,905✔
373
                    lpc += 1;
6,905✔
374
                    break;
6,905✔
375
                }
376

377
                auto lpc_start = lpc;
95✔
378
                ucs4_t wch;
379
                auto read_res = u8_mbtoucr(
285✔
380
                    &wch, (uint8_t*) line.data() + lpc, line.size() - lpc);
95✔
381
                if (read_res <= 0) {
95✔
382
                    expanded_line.append("\ufffd");
×
383
                    sa.emplace_back(line_range{(int) lpc, (int) lpc + 1},
×
384
                                    VC_ROLE.value(role_t::VCR_NON_ASCII));
×
385
                    curr_ch_col_count = 1;
×
386
                    char_index += 1;
×
387
                    lpc += 1;
×
388
                } else {
389
                    for (size_t i = 0; i < read_res; i++) {
381✔
390
                        expanded_line.push_back(line[lpc++]);
286✔
391
                    }
392
                    if (wch == L'\u200d') {
95✔
393
                        join_start_index = char_index - last_ch_col_count;
×
394
                        continue;
×
395
                    }
396
                    auto wcw_res = uc_width(wch, "UTF-8");
95✔
397
                    if (wcw_res < 0) {
95✔
398
                        log_trace("uc_width(%x) does not recognize character",
×
399
                                  wch);
400
                        wcw_res = 1;
×
401
                    }
402
                    if (lpc > (lpc_start + 1)) {
95✔
403
                        utf_adjustments.emplace_back(
95✔
404
                            lpc_start, wcw_res - (lpc - lpc_start));
95✔
405
                    }
406
                    curr_ch_col_count = wcw_res;
95✔
407
                    char_index += wcw_res;
95✔
408
                    if (lr_bytes.lr_end == -1 && char_index > lr_chars.lr_end) {
95✔
409
                        lr_bytes.lr_end = exp_start_index;
×
410
                        retval.mr_chars_out = char_index - wcw_res;
×
411
                    }
412
                }
413
                break;
95✔
414
            }
415
        }
416
        if (join_start_index) {
7,013✔
417
            curr_ch_col_count = std::max(last_ch_col_count, curr_ch_col_count);
×
418
            char_index = join_start_index.value() + curr_ch_col_count;
×
419
            if (char_index > lr_chars.lr_end) {
×
420
                retval.mr_chars_out = char_index;
×
421
            }
422
            join_start_index = std::nullopt;
×
423
        }
424
        last_ch_col_count = curr_ch_col_count;
7,013✔
425
    }
426
    if (lr_bytes.lr_start == -1) {
895✔
427
        lr_bytes.lr_start = expanded_line.size();
82✔
428
    }
429
    if (lr_bytes.lr_end == -1) {
895✔
430
        lr_bytes.lr_end = expanded_line.size();
885✔
431
    }
432
    if (retval.mr_chars_out == 0) {
895✔
433
        retval.mr_chars_out = char_index;
885✔
434
    }
435
    retval.mr_bytes_remaining = expanded_line.size() - lr_bytes.lr_end;
895✔
436
    expanded_line.resize(lr_bytes.lr_end);
895✔
437

438
    auto& vc = view_colors::singleton();
895✔
439
    auto base_attrs = vc.attrs_for_role(base_role);
895✔
440
    if (lr_chars.length() > 0) {
895✔
441
        ncplane_erase_region(window, y, x, 1, lr_chars.length());
885✔
442
        if (lr_bytes.lr_start < (int) expanded_line.size()) {
885✔
443
            ncplane_putstr_yx(
813✔
444
                window, y, x, &expanded_line.c_str()[lr_bytes.lr_start]);
813✔
445
        } else {
446
            // Need to move the cursor so the hline call below goes to the
447
            // right place
448
            ncplane_cursor_move_yx(window, y, x);
72✔
449
        }
450
        nccell clear_cell;
451
        nccell_init(&clear_cell);
885✔
452
        nccell_prime(
885✔
453
            window, &clear_cell, " ", 0, view_colors::to_channels(base_attrs));
454
        ncplane_hline(
885✔
455
            window, &clear_cell, lr_chars.length() - retval.mr_chars_out);
885✔
456
    }
457

458
    text_attrs resolved_line_attrs[line_width_chars + 1];
58,027✔
459

460
    std::stable_sort(sa.begin(), sa.end());
895✔
461
    for (auto iter = sa.cbegin(); iter != sa.cend(); ++iter) {
2,487✔
462
        auto attr_range = iter->sa_range;
1,592✔
463

464
        require(attr_range.lr_start >= 0);
1,592✔
465
        require(attr_range.lr_end >= -1);
1,592✔
466

467
        if (!(iter->sa_type == &VC_ROLE || iter->sa_type == &VC_ROLE_FG
2,947✔
468
              || iter->sa_type == &VC_STYLE || iter->sa_type == &VC_GRAPHIC
1,355✔
469
              || iter->sa_type == &SA_LEVEL || iter->sa_type == &VC_FOREGROUND
832✔
470
              || iter->sa_type == &VC_BACKGROUND
704✔
471
              || iter->sa_type == &VC_BLOCK_ELEM || iter->sa_type == &VC_ICON))
704✔
472
        {
473
            continue;
705✔
474
        }
475

476
        if (attr_range.lr_unit == line_range::unit::bytes) {
909✔
477
            for (const auto& adj : utf_adjustments) {
1,684✔
478
                // If the UTF adjustment is in the viewport, we need to adjust
479
                // this attribute.
480
                if (adj.uda_origin < iter->sa_range.lr_start) {
775✔
481
                    attr_range.lr_start += adj.uda_offset;
346✔
482
                }
483
            }
484

485
            if (attr_range.lr_end != -1) {
909✔
486
                for (const auto& adj : utf_adjustments) {
1,487✔
487
                    if (adj.uda_origin < iter->sa_range.lr_end) {
773✔
488
                        attr_range.lr_end += adj.uda_offset;
441✔
489
                    }
490
                }
491
            }
492
        }
493

494
        if (attr_range.lr_end == -1) {
909✔
495
            attr_range.lr_end = lr_chars.lr_start + line_width_chars;
195✔
496
        }
497
        if (attr_range.lr_end < lr_chars.lr_start) {
909✔
498
            continue;
×
499
        }
500
        attr_range.lr_start
501
            = std::max(0, attr_range.lr_start - lr_chars.lr_start);
909✔
502
        if (attr_range.lr_start > line_width_chars) {
909✔
503
            continue;
22✔
504
        }
505

506
        attr_range.lr_end
507
            = std::min(line_width_chars, attr_range.lr_end - lr_chars.lr_start);
887✔
508

509
        if (iter->sa_type == &VC_FOREGROUND) {
887✔
510
            auto attr_fg = iter->sa_value.get<styling::color_unit>();
×
511
            for (auto lpc = attr_range.lr_start; lpc < attr_range.lr_end; ++lpc)
×
512
            {
513
                resolved_line_attrs[lpc].ta_fg_color = attr_fg;
×
514
            }
515
            continue;
×
516
        }
517

518
        if (iter->sa_type == &VC_BACKGROUND) {
887✔
519
            auto attr_bg = iter->sa_value.get<styling::color_unit>();
×
520
            for (auto lpc = attr_range.lr_start; lpc < attr_range.lr_end; ++lpc)
×
521
            {
522
                resolved_line_attrs[lpc].ta_bg_color = attr_bg;
×
523
            }
524
            continue;
×
525
        }
526

527
        if (attr_range.lr_start < attr_range.lr_end) {
887✔
528
            auto attrs = text_attrs{};
879✔
529
            std::optional<const char*> graphic;
879✔
530

531
            if (iter->sa_type == &VC_GRAPHIC) {
879✔
532
                graphic = iter->sa_value.get<const char*>();
131✔
533
                attrs = text_attrs::with_altcharset();
131✔
534
                for (auto lpc = attr_range.lr_start; lpc < attr_range.lr_end;
262✔
535
                     ++lpc)
536
                {
537
                    ncplane_putstr_yx(window, y, x + lpc, graphic.value());
131✔
538
                }
539
            } else if (iter->sa_type == &VC_BLOCK_ELEM) {
748✔
540
                auto be = iter->sa_value.get<block_elem_t>();
×
541
                ncplane_pututf32_yx(
×
542
                    window, y, x + attr_range.lr_start, be.value);
×
543
                attrs = vc.attrs_for_role(be.role);
×
544
            } else if (iter->sa_type == &VC_ICON) {
748✔
545
                auto ic = iter->sa_value.get<ui_icon_t>();
21✔
546
                auto be = vc.wchar_for_icon(ic);
21✔
547

548
                ncplane_pututf32_yx(
21✔
549
                    window, y, x + attr_range.lr_start, be.value);
21✔
550
                attrs = vc.attrs_for_role(be.role);
21✔
551
                // clear the BG color, it interferes with the cursor BG
552
                attrs.ta_bg_color = styling::color_unit::EMPTY;
21✔
553
            } else if (iter->sa_type == &VC_STYLE) {
727✔
554
                attrs = iter->sa_value.get<text_attrs>();
392✔
555
            } else if (iter->sa_type == &SA_LEVEL) {
335✔
556
                attrs = vc.attrs_for_level(
128✔
557
                    (log_level_t) iter->sa_value.get<int64_t>());
128✔
558
            } else if (iter->sa_type == &VC_ROLE) {
207✔
559
                auto role = iter->sa_value.get<role_t>();
207✔
560
                attrs = vc.attrs_for_role(role);
207✔
561

562
                if (role == role_t::VCR_SELECTED_TEXT) {
207✔
563
                    retval.mr_selected_text
564
                        = string_fragment::from_str(line).sub_range(
×
565
                            iter->sa_range.lr_start, iter->sa_range.lr_end);
×
566
                }
567
            } else if (iter->sa_type == &VC_ROLE_FG) {
×
568
                auto role_attrs
569
                    = vc.attrs_for_role(iter->sa_value.get<role_t>());
×
570
                attrs.ta_fg_color = role_attrs.ta_fg_color;
×
571
            }
572

573
            if (graphic || !attrs.empty()) {
879✔
574
                if (std::holds_alternative<styling::semantic>(
702✔
575
                        attrs.ta_fg_color.cu_value))
576
                {
577
                    attrs.ta_fg_color
578
                        = vc.color_for_ident(al.to_string_fragment(iter));
100✔
579
                }
580
                if (std::holds_alternative<styling::semantic>(
702✔
581
                        attrs.ta_bg_color.cu_value))
582
                {
583
                    attrs.ta_bg_color
584
                        = vc.color_for_ident(al.to_string_fragment(iter));
×
585
                }
586

587
                for (auto lpc = attr_range.lr_start; lpc < attr_range.lr_end;
6,378✔
588
                     ++lpc)
589
                {
590
                    auto clear_rev = attrs.has_style(text_attrs::style::reverse)
5,676✔
591
                        && resolved_line_attrs[lpc].has_style(
5,676✔
592
                            text_attrs::style::reverse);
5,676✔
593
                    resolved_line_attrs[lpc] = attrs | resolved_line_attrs[lpc];
5,676✔
594
                    if (clear_rev) {
5,676✔
595
                        resolved_line_attrs[lpc].clear_style(
×
596
                            text_attrs::style::reverse);
597
                    }
598
                }
599
            }
600
        }
601
    }
602

603
    for (int lpc = 0; lpc < line_width_chars; lpc++) {
57,132✔
604
        auto cell_attrs = resolved_line_attrs[lpc] | base_attrs;
56,237✔
605

606
        cell_attrs.ta_fg_color = vc.ansi_to_theme_color(cell_attrs.ta_fg_color);
56,237✔
607
        cell_attrs.ta_bg_color = vc.ansi_to_theme_color(cell_attrs.ta_bg_color);
56,237✔
608
        auto chan = view_colors::to_channels(cell_attrs);
56,237✔
609
        ncplane_set_cell_yx(window, y, x + lpc, cell_attrs.ta_attrs, chan);
56,237✔
610
#if 0
611
        if (desired_fg == desired_bg) {
612
            if (desired_bg >= 0
613
                && desired_bg
614
                    < view_colors::vc_active_palette->tc_palette.size())
615
            {
616
                auto adjusted_color
617
                    = view_colors::vc_active_palette->tc_palette[desired_bg]
618
                          .xc_lab_color;
619
                if (adjusted_color.lc_l < 50.0) {
620
                    adjusted_color.lc_l += 50.0;
621
                } else {
622
                    adjusted_color.lc_l -= 50.0;
623
                }
624
                bg_color[lpc] = view_colors::vc_active_palette->match_color(
625
                    adjusted_color);
626
            }
627
        } else if (fg_color[lpc] >= 0
628
                   && fg_color[lpc]
629
                       < view_colors::vc_active_palette->tc_palette.size()
630
                   && bg_color[lpc] == -1
631
                   && base_attrs.ta_bg_color.value_or(0) >= 0
632
                   && base_attrs.ta_bg_color.value_or(0)
633
                       < view_colors::vc_active_palette->tc_palette.size())
634
        {
635
            const auto& fg_color_info
636
                = view_colors::vc_active_palette->tc_palette.at(fg_color[lpc]);
637
            const auto& bg_color_info
638
                = view_colors::vc_active_palette->tc_palette.at(
639
                    base_attrs.ta_bg_color.value_or(0));
640

641
            if (!fg_color_info.xc_lab_color.sufficient_contrast(
642
                    bg_color_info.xc_lab_color))
643
            {
644
                auto adjusted_color = bg_color_info.xc_lab_color;
645
                adjusted_color.lc_l = std::max(0.0, adjusted_color.lc_l - 40.0);
646
                auto new_bg = view_colors::vc_active_palette->match_color(
647
                    adjusted_color);
648
                for (int lpc2 = lpc; lpc2 < line_width_chars; lpc2++) {
649
                    if (fg_color[lpc2] == fg_color[lpc] && bg_color[lpc2] == -1)
650
                    {
651
                        bg_color[lpc2] = new_bg;
652
                    }
653
                }
654
            }
655
        }
656

657
        if (fg_color[lpc] == -1) {
658
            fg_color[lpc] = cur_fg;
659
        }
660
        if (bg_color[lpc] == -1) {
661
            bg_color[lpc] = cur_bg;
662
        }
663
#endif
664
    }
665

666
    return retval;
1,790✔
667
}
895✔
668

669
view_colors&
670
view_colors::singleton()
158,035✔
671
{
672
    static view_colors s_vc;
158,035✔
673

674
    return s_vc;
158,035✔
675
}
676

677
view_colors::view_colors()
1,179✔
678
    : vc_ansi_to_theme{
679
          styling::color_unit::from_palette({0}),
×
680
          styling::color_unit::from_palette({1}),
1,179✔
681
          styling::color_unit::from_palette({2}),
1,179✔
682
          styling::color_unit::from_palette({3}),
1,179✔
683
          styling::color_unit::from_palette({4}),
1,179✔
684
          styling::color_unit::from_palette({5}),
1,179✔
685
          styling::color_unit::from_palette({6}),
1,179✔
686
          styling::color_unit::from_palette({7}),
1,179✔
687
      }
135,585✔
688
{
689
    auto text_default = text_attrs{};
1,179✔
690
    text_default.ta_fg_color = styling::color_unit::from_palette(COLOR_WHITE);
1,179✔
691
    text_default.ta_bg_color = styling::color_unit::from_palette(COLOR_BLACK);
1,179✔
692
    this->vc_role_attrs[lnav::enums::to_underlying(role_t::VCR_TEXT)]
693
        = role_attrs{text_default, text_default};
1,179✔
694
}
1,179✔
695

696
block_elem_t
697
view_colors::wchar_for_icon(ui_icon_t ic) const
21✔
698
{
699
    return this->vc_icons[lnav::enums::to_underlying(ic)];
21✔
700
}
701

702
bool view_colors::initialized = false;
703

704
static const std::string COLOR_NAMES[] = {
705
    "black",
706
    "red",
707
    "green",
708
    "yellow",
709
    "blue",
710
    "magenta",
711
    "cyan",
712
    "white",
713
};
714

715
class ui_listener : public lnav_config_listener {
716
public:
717
    ui_listener() : lnav_config_listener(__FILE__) {}
1,179✔
718

719
    void reload_config(error_reporter& reporter) override
1,315✔
720
    {
721
        static auto op = lnav_operation{"reload_theme"};
1,315✔
722

723
        auto op_guard = lnav_opid_guard::internal(op);
1,315✔
724

725
        if (!view_colors::initialized) {
1,315✔
726
            view_colors::vc_active_palette = ansi_colors();
651✔
727
        }
728

729
        auto& vc = view_colors::singleton();
1,315✔
730

731
        for (const auto& pair : lnav_config.lc_ui_theme_defs) {
13,007✔
732
            vc.init_roles(pair.second, reporter);
11,692✔
733
        }
734

735
        const auto iter
736
            = lnav_config.lc_ui_theme_defs.find(lnav_config.lc_ui_theme);
1,315✔
737

738
        if (iter == lnav_config.lc_ui_theme_defs.end()) {
1,315✔
739
            auto theme_names
740
                = lnav_config.lc_ui_theme_defs | lnav::itertools::first();
18✔
741

742
            reporter(&lnav_config.lc_ui_theme,
18✔
743
                     lnav::console::user_message::error(
×
744
                         attr_line_t("unknown theme -- ")
36✔
745
                             .append_quoted(lnav_config.lc_ui_theme))
36✔
746
                         .with_help(attr_line_t("The available themes are: ")
36✔
747
                                        .join(theme_names, ", ")));
18✔
748

749
            vc.init_roles(lnav_config.lc_ui_theme_defs["default"], reporter);
18✔
750
            return;
18✔
751
        }
18✔
752

753
        if (view_colors::initialized) {
1,297✔
754
            vc.init_roles(iter->second, reporter);
647✔
755
        }
756
    }
1,315✔
757
};
758

759
std::optional<lab_color>
760
view_colors::to_lab_color(const styling::color_unit& color)
24,714✔
761
{
762
    auto* pal_color = std::get_if<palette_color>(&color.cu_value);
24,714✔
763
    if (pal_color != nullptr) {
24,714✔
764
        auto pal_index = *pal_color;
4,579✔
765
        if (pal_index < vc_active_palette->tc_palette.size()) {
4,579✔
766
            if (this->vc_notcurses != nullptr && pal_index == COLOR_BLACK) {
1,983✔
767
                // We use this as the default background, so try to get the
768
                // real default from the terminal.
769
                uint32_t chan = 0;
4✔
770
                notcurses_default_background(this->vc_notcurses, &chan);
4✔
771

772
                unsigned r = 0, g = 0, b = 0;
4✔
773

774
                ncchannel_rgb8(chan, &r, &g, &b);
4✔
775
                auto rgb = rgb_color{(short) r, (short) g, (short) b};
4✔
776
                return lab_color{rgb};
4✔
777
            }
778

779
            return vc_active_palette->tc_palette[pal_index].xc_lab_color;
1,979✔
780
        }
781
    } else {
782
        auto* col = std::get_if<rgb_color>(&color.cu_value);
20,135✔
783
        if (col != nullptr) {
20,135✔
784
            return lab_color{*col};
20,101✔
785
        }
786
    }
787

788
    return std::nullopt;
2,630✔
789
}
790

791
uint64_t
792
view_colors::to_channels(const text_attrs& ta)
58,523✔
793
{
794
    uint64_t retval = 0;
58,523✔
795
    std::visit(styling::overload{
×
796
                   [&retval](styling::transparent) {
40,281✔
797
                       ncchannels_set_fg_alpha(&retval, NCALPHA_TRANSPARENT);
40,281✔
798
                   },
40,281✔
799
                   [&retval](styling::semantic) {
×
800
                       ncchannels_set_fg_alpha(&retval, NCALPHA_TRANSPARENT);
×
801
                   },
×
802
                   [&retval](const palette_color& pc) {
18,166✔
803
                       if (pc == COLOR_WHITE) {
18,166✔
804
                           ncchannels_set_fg_default(&retval);
×
805
                       } else {
806
                           ncchannels_set_fg_palindex(&retval, pc);
18,166✔
807
                       }
808
                   },
18,166✔
809
                   [&retval](const rgb_color& rc) {
76✔
810
                       ncchannels_set_fg_rgb8(
76✔
811
                           &retval, rc.rc_r, rc.rc_g, rc.rc_b);
76✔
812
                   }},
76✔
813
               ta.ta_fg_color.cu_value);
58,523✔
814
    std::visit(styling::overload{
×
815
                   [&retval](styling::transparent) {
40,281✔
816
                       ncchannels_set_bg_alpha(&retval, NCALPHA_TRANSPARENT);
40,281✔
817
                   },
40,281✔
818
                   [&retval](styling::semantic) {
×
819
                       ncchannels_set_bg_alpha(&retval, NCALPHA_TRANSPARENT);
×
820
                   },
×
821
                   [&retval](const palette_color& pc) {
18,242✔
822
                       if (pc == COLOR_BLACK) {
18,242✔
823
                           ncchannels_set_bg_default(&retval);
7✔
824
                       } else {
825
                           ncchannels_set_bg_palindex(&retval, pc);
18,235✔
826
                       }
827
                   },
18,242✔
828
                   [&retval](const rgb_color& rc) {
×
829
                       ncchannels_set_bg_rgb8(
×
830
                           &retval, rc.rc_r, rc.rc_g, rc.rc_b);
×
831
                   },
×
832
               },
833
               ta.ta_bg_color.cu_value);
58,523✔
834

835
    return retval;
58,523✔
836
}
837

838
static ui_listener _UI_LISTENER;
839
term_color_palette* view_colors::vc_active_palette;
840

841
void
842
view_colors::init(notcurses* nc)
641✔
843
{
844
    vc_active_palette = ansi_colors();
641✔
845
    if (nc != nullptr) {
641✔
846
        const auto* caps = notcurses_capabilities(nc);
18✔
847
        if (caps->rgb) {
18✔
848
            log_info("terminal supports RGB colors");
×
849
        } else {
850
            log_info("terminal supports %d colors", caps->colors);
18✔
851
        }
852
        if (caps->colors > 8) {
18✔
853
            vc_active_palette = xterm_colors();
2✔
854
        }
855
    }
856

857
    singleton().vc_notcurses = nc;
641✔
858
    initialized = true;
641✔
859

860
    {
861
        auto reporter
862
            = [](const void*, const lnav::console::user_message& um) {};
16✔
863

864
        _UI_LISTENER.reload_config(reporter);
641✔
865
    }
866
}
641✔
867

868
styling::color_unit
869
view_colors::match_color(styling::color_unit cu) const
2,650,248✔
870
{
871
    if (this->vc_notcurses == nullptr) {
2,650,248✔
872
        return cu;
2,643,304✔
873
    }
874

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

877
    if (caps->rgb) {
6,944✔
878
        return cu;
×
879
    }
880

881
    auto* rgb = std::get_if<rgb_color>(&cu.cu_value);
6,944✔
882
    if (rgb != nullptr) {
6,944✔
883
        auto lab = lab_color{*rgb};
2,308✔
884
        auto pal = vc_active_palette->match_color(lab);
2,308✔
885

886
        log_trace("mapped RGB (%d, %d, %d) to palette %d",
2,308✔
887
                  rgb->rc_r,
888
                  rgb->rc_g,
889
                  rgb->rc_b,
890
                  pal);
891
        return styling::color_unit::from_palette(palette_color{pal});
2,308✔
892
    }
893

894
    return cu;
4,636✔
895
}
896

897
view_colors::role_attrs
898
view_colors::to_attrs(const lnav_theme& lt,
1,176,167✔
899
                      const positioned_property<style_config>& pp_sc,
900
                      lnav_config_listener::error_reporter& reporter)
901
{
902
    const auto& sc = pp_sc.pp_value;
1,176,167✔
903
    std::string fg_color, bg_color;
1,176,167✔
904
    intern_string_t role_class;
1,176,167✔
905

906
    if (pp_sc.pp_path.empty()) {
1,176,167✔
907
#if 0
908
        // too slow to do this now
909
        reporter(&sc.sc_color, lnav::console::user_message::warning(""));
910
#endif
911
    } else if (!pp_sc.pp_path.empty()) {
1,030,723✔
912
        auto role_class_path = pp_sc.pp_path.to_string_fragment();
1,030,723✔
913
        auto inner
914
            = role_class_path.rsplit_pair(string_fragment::tag1{'/'}).value();
1,030,723✔
915
        auto outer
916
            = inner.first.rsplit_pair(string_fragment::tag1{'/'}).value();
1,030,723✔
917

918
        role_class = intern_string::lookup(
1,030,723✔
919
            fmt::format(FMT_STRING("-lnav_{}_{}"), outer.second, inner.second));
5,153,615✔
920
    }
921

922
    auto fg1 = sc.sc_color;
1,176,167✔
923
    auto bg1 = sc.sc_background_color;
1,176,167✔
924
    shlex(fg1).eval(fg_color, scoped_resolver{&lt.lt_vars});
1,176,167✔
925
    shlex(bg1).eval(bg_color, scoped_resolver{&lt.lt_vars});
1,176,167✔
926

927
    auto fg = styling::color_unit::from_str(fg_color).unwrapOrElse(
1,176,167✔
928
        [&](const auto& msg) {
3✔
929
            reporter(
6✔
930
                &sc.sc_color,
3✔
931
                lnav::console::user_message::error(
932
                    attr_line_t("invalid color -- ").append_quoted(sc.sc_color))
6✔
933
                    .with_reason(msg));
3✔
934
            return styling::color_unit::EMPTY;
3✔
935
        });
936
    auto bg = styling::color_unit::from_str(bg_color).unwrapOrElse(
1,176,167✔
937
        [&](const auto& msg) {
×
938
            reporter(&sc.sc_background_color,
×
939
                     lnav::console::user_message::error(
940
                         attr_line_t("invalid background color -- ")
×
941
                             .append_quoted(sc.sc_background_color))
×
942
                         .with_reason(msg));
×
943
            return styling::color_unit::EMPTY;
×
944
        });
945

946
    fg = this->match_color(fg);
1,176,167✔
947
    bg = this->match_color(bg);
1,176,167✔
948

949
    auto retval1 = text_attrs{0, fg, bg};
1,176,167✔
950
    text_attrs retval2;
1,176,167✔
951

952
    if (sc.sc_underline) {
1,176,167✔
953
        retval1 |= text_attrs::style::underline;
89,614✔
954
        retval2 |= text_attrs::style::underline;
89,614✔
955
    }
956
    if (sc.sc_bold) {
1,176,167✔
957
        retval1 |= text_attrs::style::bold;
256,552✔
958
        retval2 |= text_attrs::style::bold;
256,552✔
959
    }
960
    if (sc.sc_italic) {
1,176,167✔
961
        retval1 |= text_attrs::style::italic;
18,192✔
962
        retval2 |= text_attrs::style::italic;
18,192✔
963
    }
964
    if (sc.sc_strike) {
1,176,167✔
965
        retval1 |= text_attrs::style::struck;
×
966
        retval2 |= text_attrs::style::struck;
×
967
    }
968

969
    return {retval1, retval2, role_class};
1,176,167✔
970
}
1,176,167✔
971

972
void
973
view_colors::init_roles(const lnav_theme& lt,
12,357✔
974
                        lnav_config_listener::error_reporter& reporter)
975
{
976
    const auto& default_theme = lnav_config.lc_ui_theme_defs["default"];
12,357✔
977
    std::string err;
12,357✔
978

979
    size_t icon_index = 0;
12,357✔
980
    for (const auto& ic :
12,357✔
981
         {
982
             lt.lt_icon_hidden,
12,357✔
983
             lt.lt_icon_ok,
12,357✔
984
             lt.lt_icon_info,
12,357✔
985
             lt.lt_icon_warning,
12,357✔
986
             lt.lt_icon_error,
12,357✔
987

988
             lt.lt_icon_log_level_trace,
12,357✔
989
             lt.lt_icon_log_level_debug,
12,357✔
990
             lt.lt_icon_log_level_info,
12,357✔
991
             lt.lt_icon_log_level_stats,
12,357✔
992
             lt.lt_icon_log_level_notice,
12,357✔
993
             lt.lt_icon_log_level_warning,
12,357✔
994
             lt.lt_icon_log_level_error,
12,357✔
995
             lt.lt_icon_log_level_critical,
12,357✔
996
             lt.lt_icon_log_level_fatal,
12,357✔
997

998
             lt.lt_icon_play,
12,357✔
999
             lt.lt_icon_edit,
12,357✔
1000
             lt.lt_icon_file,
12,357✔
1001
             lt.lt_icon_thread,
12,357✔
1002
             lt.lt_icon_busy,
12,357✔
1003
         })
494,280✔
1004
    {
1005
        size_t index = 0;
234,783✔
1006
        if (ic.pp_value.ic_value) {
234,783✔
1007
            auto read_res = ww898::utf::utf8::read([&ic, &index]() {
×
1008
                return ic.pp_value.ic_value.value()[index++];
132,260✔
1009
            });
36,955✔
1010
            if (read_res.isErr()) {
36,955✔
1011
                reporter(&ic,
×
1012
                         lnav::console::user_message::error(
×
1013
                             "icon is not valid UTF-8"));
1014
            } else if (read_res.unwrap() != 0) {
36,955✔
1015
                role_t icon_role;
1016
                switch (static_cast<ui_icon_t>(icon_index)) {
36,955✔
1017
                    case ui_icon_t::hidden:
1,945✔
1018
                        icon_role = role_t::VCR_HIDDEN;
1,945✔
1019
                        break;
1,945✔
1020
                    case ui_icon_t::ok:
1,945✔
1021
                        icon_role = role_t::VCR_OK;
1,945✔
1022
                        break;
1,945✔
1023
                    case ui_icon_t::info:
1,945✔
1024
                        icon_role = role_t::VCR_INFO;
1,945✔
1025
                        break;
1,945✔
1026
                    case ui_icon_t::warning:
3,890✔
1027
                    case ui_icon_t::log_level_warning:
1028
                        icon_role = role_t::VCR_WARNING;
3,890✔
1029
                        break;
3,890✔
1030
                    case ui_icon_t::error:
7,780✔
1031
                    case ui_icon_t::log_level_error:
1032
                    case ui_icon_t::log_level_fatal:
1033
                    case ui_icon_t::log_level_critical:
1034
                        icon_role = role_t::VCR_ERROR;
7,780✔
1035
                        break;
7,780✔
1036
                    case ui_icon_t::play:
1,945✔
1037
                        icon_role = role_t::VCR_OK;
1,945✔
1038
                        break;
1,945✔
1039
                    default:
17,505✔
1040
                        icon_role = role_t::VCR_TEXT;
17,505✔
1041
                        break;
17,505✔
1042
                }
1043
                this->vc_icons[icon_index]
1044
                    = block_elem_t{(char32_t) read_res.unwrap(), icon_role};
36,955✔
1045
            }
1046
        }
36,955✔
1047
        icon_index += 1;
234,783✔
1048
    }
247,140✔
1049

1050
    /* Setup the mappings from roles to actual colors. */
1051
    this->get_role_attrs(role_t::VCR_TEXT)
12,357✔
1052
        = this->to_attrs(lt, lt.lt_style_text, reporter);
12,357✔
1053

1054
    for (int ansi_fg = 1; ansi_fg < 8; ansi_fg++) {
98,856✔
1055
        auto fg_iter = lt.lt_vars.find(COLOR_NAMES[ansi_fg]);
86,499✔
1056
        auto fg_str = fg_iter == lt.lt_vars.end()
86,499✔
1057
            ? ""
86,499✔
1058
            : fmt::to_string(fg_iter->second);
102,831✔
1059

1060
        auto rgb_fg = from<rgb_color>(string_fragment::from_str(fg_str))
172,998✔
1061
                          .unwrapOrElse([&](const auto& msg) {
86,499✔
1062
                              reporter(&fg_str,
×
1063
                                       lnav::console::user_message::error(
1064
                                           attr_line_t("invalid color -- ")
×
1065
                                               .append_quoted(fg_str))
×
1066
                                           .with_reason(msg));
×
1067
                              return rgb_color{};
×
1068
                          });
1069

1070
        auto fg = vc_active_palette->match_color(lab_color(rgb_fg));
86,499✔
1071

1072
        if (rgb_fg.empty()) {
86,499✔
1073
            fg = ansi_fg;
16,332✔
1074
        }
1075

1076
        this->vc_ansi_to_theme[ansi_fg] = palette_color{fg};
86,499✔
1077
    }
86,499✔
1078

1079
#if 0
1080
    if (lnav_config.lc_ui_dim_text) {
1081
        this->get_role_attrs(role_t::VCR_TEXT).ra_normal.ta_attrs |= A_DIM;
1082
        this->get_role_attrs(role_t::VCR_TEXT).ra_reverse.ta_attrs |= A_DIM;
1083
    }
1084
#endif
1085
    this->get_role_attrs(role_t::VCR_SEARCH)
12,357✔
1086
        = role_attrs{text_attrs::with_reverse(), text_attrs::with_reverse()};
12,357✔
1087
    this->get_role_attrs(role_t::VCR_SEARCH).ra_class_name
12,357✔
1088
        = intern_string::lookup("-lnav_styles_search");
24,714✔
1089
    this->get_role_attrs(role_t::VCR_IDENTIFIER)
12,357✔
1090
        = this->to_attrs(lt, lt.lt_style_identifier, reporter);
12,357✔
1091
    this->get_role_attrs(role_t::VCR_OK)
12,357✔
1092
        = this->to_attrs(lt, lt.lt_style_ok, reporter);
12,357✔
1093
    this->get_role_attrs(role_t::VCR_INFO)
12,357✔
1094
        = this->to_attrs(lt, lt.lt_style_info, reporter);
12,357✔
1095
    this->get_role_attrs(role_t::VCR_ERROR)
12,357✔
1096
        = this->to_attrs(lt, lt.lt_style_error, reporter);
12,357✔
1097
    this->get_role_attrs(role_t::VCR_WARNING)
12,357✔
1098
        = this->to_attrs(lt, lt.lt_style_warning, reporter);
12,357✔
1099
    this->get_role_attrs(role_t::VCR_ALT_ROW)
12,357✔
1100
        = this->to_attrs(lt, lt.lt_style_alt_text, reporter);
12,357✔
1101
    this->get_role_attrs(role_t::VCR_HIDDEN)
12,357✔
1102
        = this->to_attrs(lt, lt.lt_style_hidden, reporter);
12,357✔
1103
    this->get_role_attrs(role_t::VCR_CURSOR_LINE)
12,357✔
1104
        = this->to_attrs(lt, lt.lt_style_cursor_line, reporter);
12,357✔
1105
    if (this->get_role_attrs(role_t::VCR_CURSOR_LINE).ra_normal.empty()) {
12,357✔
1106
        this->get_role_attrs(role_t::VCR_CURSOR_LINE) = this->to_attrs(
1,962✔
1107
            default_theme, default_theme.lt_style_cursor_line, reporter);
1,962✔
1108
    }
1109
    this->get_role_attrs(role_t::VCR_DISABLED_CURSOR_LINE)
12,357✔
1110
        = this->to_attrs(lt, lt.lt_style_disabled_cursor_line, reporter);
12,357✔
1111
    if (this->get_role_attrs(role_t::VCR_DISABLED_CURSOR_LINE)
12,357✔
1112
            .ra_normal.empty())
12,357✔
1113
    {
1114
        this->get_role_attrs(role_t::VCR_DISABLED_CURSOR_LINE)
17✔
1115
            = this->to_attrs(default_theme,
17✔
1116
                             default_theme.lt_style_disabled_cursor_line,
17✔
1117
                             reporter);
1118
    }
1119
    this->get_role_attrs(role_t::VCR_ADJUSTED_TIME)
12,357✔
1120
        = this->to_attrs(lt, lt.lt_style_adjusted_time, reporter);
12,357✔
1121
    this->get_role_attrs(role_t::VCR_SKEWED_TIME)
12,357✔
1122
        = this->to_attrs(lt, lt.lt_style_skewed_time, reporter);
12,357✔
1123
    this->get_role_attrs(role_t::VCR_OFFSET_TIME)
12,357✔
1124
        = this->to_attrs(lt, lt.lt_style_offset_time, reporter);
12,357✔
1125
    this->get_role_attrs(role_t::VCR_TIME_COLUMN)
12,357✔
1126
        = this->to_attrs(lt, lt.lt_style_time_column, reporter);
12,357✔
1127
    this->get_role_attrs(role_t::VCR_POPUP)
12,357✔
1128
        = this->to_attrs(lt, lt.lt_style_popup, reporter);
12,357✔
1129
    this->get_role_attrs(role_t::VCR_POPUP_BORDER)
12,357✔
1130
        = this->to_attrs(lt, lt.lt_style_popup_border, reporter);
12,357✔
1131
    this->get_role_attrs(role_t::VCR_INLINE_CODE)
12,357✔
1132
        = this->to_attrs(lt, lt.lt_style_inline_code, reporter);
12,357✔
1133
    this->get_role_attrs(role_t::VCR_QUOTED_CODE)
12,357✔
1134
        = this->to_attrs(lt, lt.lt_style_quoted_code, reporter);
12,357✔
1135
    this->get_role_attrs(role_t::VCR_CODE_BORDER)
12,357✔
1136
        = this->to_attrs(lt, lt.lt_style_code_border, reporter);
12,357✔
1137

1138
    {
1139
        auto& time_to_text
1140
            = this->get_role_attrs(role_t::VCR_TIME_COLUMN_TO_TEXT);
12,357✔
1141
        auto time_attrs = this->attrs_for_role(role_t::VCR_TIME_COLUMN);
12,357✔
1142
        auto text_attrs = this->attrs_for_role(role_t::VCR_TEXT);
12,357✔
1143
        time_to_text.ra_class_name.clear();
12,357✔
1144

1145
        time_to_text.ra_normal.ta_fg_color = time_attrs.ta_bg_color;
12,357✔
1146
        time_to_text.ra_normal.ta_bg_color = text_attrs.ta_bg_color;
12,357✔
1147

1148
        auto fg_as_lab_opt = to_lab_color(time_attrs.ta_bg_color);
12,357✔
1149
        auto bg_as_lab_opt = to_lab_color(text_attrs.ta_bg_color);
12,357✔
1150
        if (fg_as_lab_opt && bg_as_lab_opt) {
12,357✔
1151
            auto fg_as_lab = fg_as_lab_opt.value();
11,042✔
1152
            auto bg_as_lab = bg_as_lab_opt.value();
11,042✔
1153
            auto diff = fg_as_lab.lc_l - bg_as_lab.lc_l;
11,042✔
1154
            fg_as_lab.lc_l -= diff / 4.0;
11,042✔
1155
            bg_as_lab.lc_l += diff / 4.0;
11,042✔
1156

1157
            time_to_text.ra_normal.ta_fg_color = this->match_color(
11,042✔
1158
                styling::color_unit::from_rgb(fg_as_lab.to_rgb()));
11,042✔
1159
            time_to_text.ra_normal.ta_bg_color = this->match_color(
11,042✔
1160
                styling::color_unit::from_rgb(bg_as_lab.to_rgb()));
22,084✔
1161
        }
1162

1163
        if (bg_as_lab_opt) {
12,357✔
1164
            auto bg_as_lab = bg_as_lab_opt.value();
11,042✔
1165
            std::vector<std::pair<double, size_t>> contrasting;
11,042✔
1166
            for (const auto& [index, tcolor] :
104,338✔
1167
                 lnav::itertools::enumerate(vc_active_palette->tc_palette))
115,380✔
1168
            {
1169
                if (index < 16) {
93,296✔
1170
                    continue;
88,496✔
1171
                }
1172
                if (bg_as_lab.sufficient_contrast(tcolor.xc_lab_color)) {
4,800✔
1173
                    contrasting.emplace_back(
2,132✔
1174
                        bg_as_lab.deltaE(tcolor.xc_lab_color), index);
2,132✔
1175
                }
1176
            }
1177

1178
            log_info("found %zu contrasting colors for highlights",
11,042✔
1179
                     contrasting.size());
1180
            if (contrasting.empty()) {
11,042✔
1181
                for (auto lpc = size_t{0}; lpc < HI_COLOR_COUNT; lpc++) {
606,210✔
1182
                    this->vc_highlight_colors[lpc] = 16;
595,188✔
1183
                }
1184
            } else {
1185
                std::stable_sort(
20✔
1186
                    contrasting.begin(), contrasting.end(), std::greater{});
1187
                for (auto lpc = size_t{0}; lpc < HI_COLOR_COUNT; lpc++) {
1,100✔
1188
                    this->vc_highlight_colors[lpc]
1189
                        = contrasting[lpc % contrasting.size()].second;
1,080✔
1190
                }
1191
            }
1192
            if (lt.lt_style_cursor_line.pp_value.sc_background_color.empty()) {
11,042✔
1193
                auto adjusted_cursor = bg_as_lab;
1,945✔
1194
                if (adjusted_cursor.lc_l < 50) {
1,945✔
1195
                    adjusted_cursor.lc_l += 18;
1,945✔
1196
                } else {
1197
                    adjusted_cursor.lc_l -= 15;
×
1198
                }
1199
                auto new_cursor_bg = this->match_color(
1,945✔
1200
                    styling::color_unit::from_rgb(adjusted_cursor.to_rgb()));
1,945✔
1201
                this->get_role_attrs(role_t::VCR_CURSOR_LINE)
1,945✔
1202
                    .ra_normal.ta_bg_color = new_cursor_bg;
1,945✔
1203
            }
1204
            if (lt.lt_style_popup.pp_value.sc_background_color.empty()) {
11,042✔
1205
                auto adjusted_cursor = bg_as_lab;
1,945✔
1206
                if (adjusted_cursor.lc_l < 50) {
1,945✔
1207
                    adjusted_cursor.lc_l += 30;
1,945✔
1208
                } else {
1209
                    adjusted_cursor.lc_l -= 30;
×
1210
                }
1211
                auto new_cursor_bg = this->match_color(
1,945✔
1212
                    styling::color_unit::from_rgb(adjusted_cursor.to_rgb()));
1,945✔
1213
                this->get_role_attrs(role_t::VCR_POPUP).ra_normal.ta_bg_color
1,945✔
1214
                    = new_cursor_bg;
1,945✔
1215
            }
1216
            if (lt.lt_style_inline_code.pp_value.sc_background_color.empty()) {
11,042✔
1217
                auto adjusted_cursor = bg_as_lab;
3,244✔
1218
                if (adjusted_cursor.lc_l < 50) {
3,244✔
1219
                    adjusted_cursor.lc_l = 10;
3,244✔
1220
                } else {
1221
                    adjusted_cursor.lc_l -= 25;
×
1222
                }
1223
                auto new_cursor_bg = this->match_color(
3,244✔
1224
                    styling::color_unit::from_rgb(adjusted_cursor.to_rgb()));
3,244✔
1225
                this->get_role_attrs(role_t::VCR_INLINE_CODE)
3,244✔
1226
                    .ra_normal.ta_bg_color = new_cursor_bg;
3,244✔
1227
            }
1228
            if (lt.lt_style_quoted_code.pp_value.sc_background_color.empty()) {
11,042✔
1229
                auto adjusted_cursor = bg_as_lab;
3,244✔
1230
                if (adjusted_cursor.lc_l < 50) {
3,244✔
1231
                    adjusted_cursor.lc_l = 10;
3,244✔
1232
                } else {
1233
                    adjusted_cursor.lc_l -= 25;
×
1234
                }
1235
                auto new_cursor_bg = this->match_color(
3,244✔
1236
                    styling::color_unit::from_rgb(adjusted_cursor.to_rgb()));
3,244✔
1237
                this->get_role_attrs(role_t::VCR_QUOTED_CODE)
3,244✔
1238
                    .ra_normal.ta_bg_color = new_cursor_bg;
3,244✔
1239
                this->get_role_attrs(role_t::VCR_CODE_BORDER)
3,244✔
1240
                    .ra_normal.ta_bg_color = new_cursor_bg;
3,244✔
1241
            }
1242
        }
11,042✔
1243
    }
1244
    this->get_role_attrs(role_t::VCR_FILE_OFFSET)
12,357✔
1245
        = this->to_attrs(lt, lt.lt_style_file_offset, reporter);
12,357✔
1246
    this->get_role_attrs(role_t::VCR_INVALID_MSG)
12,357✔
1247
        = this->to_attrs(lt, lt.lt_style_invalid_msg, reporter);
12,357✔
1248

1249
    this->get_role_attrs(role_t::VCR_STATUS)
12,357✔
1250
        = this->to_attrs(lt, lt.lt_style_status, reporter);
12,357✔
1251
    this->get_role_attrs(role_t::VCR_WARN_STATUS)
12,357✔
1252
        = this->to_attrs(lt, lt.lt_style_warn_status, reporter);
12,357✔
1253
    this->get_role_attrs(role_t::VCR_ALERT_STATUS)
12,357✔
1254
        = this->to_attrs(lt, lt.lt_style_alert_status, reporter);
12,357✔
1255
    this->get_role_attrs(role_t::VCR_ACTIVE_STATUS)
12,357✔
1256
        = this->to_attrs(lt, lt.lt_style_active_status, reporter);
12,357✔
1257
    this->get_role_attrs(role_t::VCR_ACTIVE_STATUS2) = role_attrs{
12,357✔
1258
        this->get_role_attrs(role_t::VCR_ACTIVE_STATUS).ra_normal,
12,357✔
1259
        this->get_role_attrs(role_t::VCR_ACTIVE_STATUS).ra_reverse,
12,357✔
1260
    };
1261
    this->get_role_attrs(role_t::VCR_ACTIVE_STATUS2).ra_normal
12,357✔
1262
        |= text_attrs::style::bold;
12,357✔
1263
    this->get_role_attrs(role_t::VCR_ACTIVE_STATUS2).ra_reverse
12,357✔
1264
        |= text_attrs::style::bold;
12,357✔
1265
    this->get_role_attrs(role_t::VCR_STATUS_TITLE)
12,357✔
1266
        = this->to_attrs(lt, lt.lt_style_status_title, reporter);
12,357✔
1267
    this->get_role_attrs(role_t::VCR_STATUS_SUBTITLE)
12,357✔
1268
        = this->to_attrs(lt, lt.lt_style_status_subtitle, reporter);
12,357✔
1269
    this->get_role_attrs(role_t::VCR_STATUS_INFO)
12,357✔
1270
        = this->to_attrs(lt, lt.lt_style_status_info, reporter);
12,357✔
1271

1272
    this->get_role_attrs(role_t::VCR_STATUS_HOTKEY)
12,357✔
1273
        = this->to_attrs(lt, lt.lt_style_status_hotkey, reporter);
12,357✔
1274
    this->get_role_attrs(role_t::VCR_STATUS_TITLE_HOTKEY)
12,357✔
1275
        = this->to_attrs(lt, lt.lt_style_status_title_hotkey, reporter);
12,357✔
1276
    this->get_role_attrs(role_t::VCR_STATUS_DISABLED_TITLE)
12,357✔
1277
        = this->to_attrs(lt, lt.lt_style_status_disabled_title, reporter);
12,357✔
1278

1279
    this->get_role_attrs(role_t::VCR_H1)
12,357✔
1280
        = this->to_attrs(lt, lt.lt_style_header[0], reporter);
12,357✔
1281
    this->get_role_attrs(role_t::VCR_H2)
12,357✔
1282
        = this->to_attrs(lt, lt.lt_style_header[1], reporter);
12,357✔
1283
    this->get_role_attrs(role_t::VCR_H3)
12,357✔
1284
        = this->to_attrs(lt, lt.lt_style_header[2], reporter);
12,357✔
1285
    this->get_role_attrs(role_t::VCR_H4)
12,357✔
1286
        = this->to_attrs(lt, lt.lt_style_header[3], reporter);
12,357✔
1287
    this->get_role_attrs(role_t::VCR_H5)
12,357✔
1288
        = this->to_attrs(lt, lt.lt_style_header[4], reporter);
12,357✔
1289
    this->get_role_attrs(role_t::VCR_H6)
12,357✔
1290
        = this->to_attrs(lt, lt.lt_style_header[5], reporter);
12,357✔
1291
    this->get_role_attrs(role_t::VCR_HR)
12,357✔
1292
        = this->to_attrs(lt, lt.lt_style_hr, reporter);
12,357✔
1293
    this->get_role_attrs(role_t::VCR_HYPERLINK)
12,357✔
1294
        = this->to_attrs(lt, lt.lt_style_hyperlink, reporter);
12,357✔
1295
    this->get_role_attrs(role_t::VCR_LIST_GLYPH)
12,357✔
1296
        = this->to_attrs(lt, lt.lt_style_list_glyph, reporter);
12,357✔
1297
    this->get_role_attrs(role_t::VCR_BREADCRUMB)
12,357✔
1298
        = this->to_attrs(lt, lt.lt_style_breadcrumb, reporter);
12,357✔
1299
    this->get_role_attrs(role_t::VCR_TABLE_BORDER)
12,357✔
1300
        = this->to_attrs(lt, lt.lt_style_table_border, reporter);
12,357✔
1301
    this->get_role_attrs(role_t::VCR_TABLE_HEADER)
12,357✔
1302
        = this->to_attrs(lt, lt.lt_style_table_header, reporter);
12,357✔
1303
    this->get_role_attrs(role_t::VCR_QUOTE_BORDER)
12,357✔
1304
        = this->to_attrs(lt, lt.lt_style_quote_border, reporter);
12,357✔
1305
    this->get_role_attrs(role_t::VCR_QUOTED_TEXT)
12,357✔
1306
        = this->to_attrs(lt, lt.lt_style_quoted_text, reporter);
12,357✔
1307
    this->get_role_attrs(role_t::VCR_FOOTNOTE_BORDER)
12,357✔
1308
        = this->to_attrs(lt, lt.lt_style_footnote_border, reporter);
12,357✔
1309
    this->get_role_attrs(role_t::VCR_FOOTNOTE_TEXT)
12,357✔
1310
        = this->to_attrs(lt, lt.lt_style_footnote_text, reporter);
12,357✔
1311
    this->get_role_attrs(role_t::VCR_SNIPPET_BORDER)
12,357✔
1312
        = this->to_attrs(lt, lt.lt_style_snippet_border, reporter);
12,357✔
1313
    this->get_role_attrs(role_t::VCR_INDENT_GUIDE)
12,357✔
1314
        = this->to_attrs(lt, lt.lt_style_indent_guide, reporter);
12,357✔
1315

1316
    {
1317
        positioned_property<style_config> stitch_sc;
12,357✔
1318

1319
        stitch_sc.pp_value.sc_color
1320
            = lt.lt_style_status_subtitle.pp_value.sc_background_color;
12,357✔
1321
        stitch_sc.pp_value.sc_background_color
1322
            = lt.lt_style_status_title.pp_value.sc_background_color;
12,357✔
1323
        this->get_role_attrs(role_t::VCR_STATUS_STITCH_TITLE_TO_SUB)
12,357✔
1324
            = this->to_attrs(lt, stitch_sc, reporter);
12,357✔
1325
    }
12,357✔
1326
    {
1327
        positioned_property<style_config> stitch_sc;
12,357✔
1328

1329
        stitch_sc.pp_value.sc_color
1330
            = lt.lt_style_status_title.pp_value.sc_background_color;
12,357✔
1331
        stitch_sc.pp_value.sc_background_color
1332
            = lt.lt_style_status_subtitle.pp_value.sc_background_color;
12,357✔
1333
        this->get_role_attrs(role_t::VCR_STATUS_STITCH_SUB_TO_TITLE)
12,357✔
1334
            = this->to_attrs(lt, stitch_sc, reporter);
12,357✔
1335
    }
12,357✔
1336

1337
    {
1338
        positioned_property<style_config> stitch_sc;
12,357✔
1339

1340
        stitch_sc.pp_value.sc_color
1341
            = lt.lt_style_status.pp_value.sc_background_color;
12,357✔
1342
        stitch_sc.pp_value.sc_background_color
1343
            = lt.lt_style_status_subtitle.pp_value.sc_background_color;
12,357✔
1344
        this->get_role_attrs(role_t::VCR_STATUS_STITCH_SUB_TO_NORMAL)
12,357✔
1345
            = this->to_attrs(lt, stitch_sc, reporter);
12,357✔
1346
    }
12,357✔
1347
    {
1348
        positioned_property<style_config> stitch_sc;
12,357✔
1349

1350
        stitch_sc.pp_value.sc_color
1351
            = lt.lt_style_status_subtitle.pp_value.sc_background_color;
12,357✔
1352
        stitch_sc.pp_value.sc_background_color
1353
            = lt.lt_style_status.pp_value.sc_background_color;
12,357✔
1354
        this->get_role_attrs(role_t::VCR_STATUS_STITCH_NORMAL_TO_SUB)
12,357✔
1355
            = this->to_attrs(lt, stitch_sc, reporter);
12,357✔
1356
    }
12,357✔
1357

1358
    {
1359
        positioned_property<style_config> stitch_sc;
12,357✔
1360

1361
        stitch_sc.pp_value.sc_color
1362
            = lt.lt_style_status.pp_value.sc_background_color;
12,357✔
1363
        stitch_sc.pp_value.sc_background_color
1364
            = lt.lt_style_status_title.pp_value.sc_background_color;
12,357✔
1365
        this->get_role_attrs(role_t::VCR_STATUS_STITCH_TITLE_TO_NORMAL)
12,357✔
1366
            = this->to_attrs(lt, stitch_sc, reporter);
12,357✔
1367
    }
12,357✔
1368
    {
1369
        positioned_property<style_config> stitch_sc;
12,357✔
1370

1371
        stitch_sc.pp_value.sc_color
1372
            = lt.lt_style_status_title.pp_value.sc_background_color;
12,357✔
1373
        stitch_sc.pp_value.sc_background_color
1374
            = lt.lt_style_status.pp_value.sc_background_color;
12,357✔
1375
        this->get_role_attrs(role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE)
12,357✔
1376
            = this->to_attrs(lt, stitch_sc, reporter);
12,357✔
1377
    }
12,357✔
1378

1379
    this->get_role_attrs(role_t::VCR_INACTIVE_STATUS)
12,357✔
1380
        = this->to_attrs(lt, lt.lt_style_inactive_status, reporter);
12,357✔
1381
    this->get_role_attrs(role_t::VCR_INACTIVE_ALERT_STATUS)
12,357✔
1382
        = this->to_attrs(lt, lt.lt_style_inactive_alert_status, reporter);
12,357✔
1383

1384
    this->get_role_attrs(role_t::VCR_FOCUSED)
12,357✔
1385
        = this->to_attrs(lt, lt.lt_style_focused, reporter);
12,357✔
1386
    this->get_role_attrs(role_t::VCR_DISABLED_FOCUSED)
12,357✔
1387
        = this->to_attrs(lt, lt.lt_style_disabled_focused, reporter);
12,357✔
1388
    this->get_role_attrs(role_t::VCR_SCROLLBAR)
12,357✔
1389
        = this->to_attrs(lt, lt.lt_style_scrollbar, reporter);
12,357✔
1390
    {
1391
        positioned_property<style_config> bar_sc;
12,357✔
1392

1393
        bar_sc.pp_value.sc_color = lt.lt_style_error.pp_value.sc_color;
12,357✔
1394
        bar_sc.pp_value.sc_background_color
1395
            = lt.lt_style_scrollbar.pp_value.sc_background_color;
12,357✔
1396
        this->get_role_attrs(role_t::VCR_SCROLLBAR_ERROR)
12,357✔
1397
            = this->to_attrs(lt, bar_sc, reporter);
12,357✔
1398
    }
12,357✔
1399
    {
1400
        positioned_property<style_config> bar_sc;
12,357✔
1401

1402
        bar_sc.pp_value.sc_color = lt.lt_style_warning.pp_value.sc_color;
12,357✔
1403
        bar_sc.pp_value.sc_background_color
1404
            = lt.lt_style_scrollbar.pp_value.sc_background_color;
12,357✔
1405
        this->get_role_attrs(role_t::VCR_SCROLLBAR_WARNING)
12,357✔
1406
            = this->to_attrs(lt, bar_sc, reporter);
12,357✔
1407
    }
12,357✔
1408

1409
    this->get_role_attrs(role_t::VCR_OBJECT_KEY)
12,357✔
1410
        = this->to_attrs(lt, lt.lt_style_object_key, reporter);
12,357✔
1411
    this->get_role_attrs(role_t::VCR_KEYWORD)
12,357✔
1412
        = this->to_attrs(lt, lt.lt_style_keyword, reporter);
12,357✔
1413
    this->get_role_attrs(role_t::VCR_STRING)
12,357✔
1414
        = this->to_attrs(lt, lt.lt_style_string, reporter);
12,357✔
1415
    this->get_role_attrs(role_t::VCR_COMMENT)
12,357✔
1416
        = this->to_attrs(lt, lt.lt_style_comment, reporter);
12,357✔
1417
    this->get_role_attrs(role_t::VCR_DOC_DIRECTIVE)
12,357✔
1418
        = this->to_attrs(lt, lt.lt_style_doc_directive, reporter);
12,357✔
1419
    this->get_role_attrs(role_t::VCR_VARIABLE)
12,357✔
1420
        = this->to_attrs(lt, lt.lt_style_variable, reporter);
12,357✔
1421
    this->get_role_attrs(role_t::VCR_SYMBOL)
12,357✔
1422
        = this->to_attrs(lt, lt.lt_style_symbol, reporter);
12,357✔
1423
    this->get_role_attrs(role_t::VCR_NULL)
12,357✔
1424
        = this->to_attrs(lt, lt.lt_style_null, reporter);
12,357✔
1425
    this->get_role_attrs(role_t::VCR_ASCII_CTRL)
12,357✔
1426
        = this->to_attrs(lt, lt.lt_style_ascii_ctrl, reporter);
12,357✔
1427
    this->get_role_attrs(role_t::VCR_NON_ASCII)
12,357✔
1428
        = this->to_attrs(lt, lt.lt_style_non_ascii, reporter);
12,357✔
1429
    this->get_role_attrs(role_t::VCR_NUMBER)
12,357✔
1430
        = this->to_attrs(lt, lt.lt_style_number, reporter);
12,357✔
1431
    this->get_role_attrs(role_t::VCR_FUNCTION)
12,357✔
1432
        = this->to_attrs(lt, lt.lt_style_function, reporter);
12,357✔
1433
    this->get_role_attrs(role_t::VCR_TYPE)
12,357✔
1434
        = this->to_attrs(lt, lt.lt_style_type, reporter);
12,357✔
1435
    this->get_role_attrs(role_t::VCR_SEP_REF_ACC)
12,357✔
1436
        = this->to_attrs(lt, lt.lt_style_sep_ref_acc, reporter);
12,357✔
1437
    this->get_role_attrs(role_t::VCR_SUGGESTION)
12,357✔
1438
        = this->to_attrs(lt, lt.lt_style_suggestion, reporter);
12,357✔
1439
    this->get_role_attrs(role_t::VCR_SELECTED_TEXT)
12,357✔
1440
        = this->to_attrs(lt, lt.lt_style_selected_text, reporter);
12,357✔
1441
    if (this->get_role_attrs(role_t::VCR_SELECTED_TEXT).ra_normal.empty()) {
12,357✔
1442
        this->get_role_attrs(role_t::VCR_SELECTED_TEXT) = this->to_attrs(
17✔
1443
            default_theme, default_theme.lt_style_selected_text, reporter);
17✔
1444
    }
1445
    this->get_role_attrs(role_t::VCR_FUZZY_MATCH)
12,357✔
1446
        = this->to_attrs(lt, lt.lt_style_fuzzy_match, reporter);
12,357✔
1447

1448
    this->get_role_attrs(role_t::VCR_RE_SPECIAL)
12,357✔
1449
        = this->to_attrs(lt, lt.lt_style_re_special, reporter);
12,357✔
1450
    this->get_role_attrs(role_t::VCR_RE_REPEAT)
12,357✔
1451
        = this->to_attrs(lt, lt.lt_style_re_repeat, reporter);
12,357✔
1452
    this->get_role_attrs(role_t::VCR_FILE)
12,357✔
1453
        = this->to_attrs(lt, lt.lt_style_file, reporter);
12,357✔
1454

1455
    this->get_role_attrs(role_t::VCR_DIFF_DELETE)
12,357✔
1456
        = this->to_attrs(lt, lt.lt_style_diff_delete, reporter);
12,357✔
1457
    this->get_role_attrs(role_t::VCR_DIFF_ADD)
12,357✔
1458
        = this->to_attrs(lt, lt.lt_style_diff_add, reporter);
12,357✔
1459
    this->get_role_attrs(role_t::VCR_DIFF_SECTION)
12,357✔
1460
        = this->to_attrs(lt, lt.lt_style_diff_section, reporter);
12,357✔
1461

1462
    this->get_role_attrs(role_t::VCR_LOW_THRESHOLD)
12,357✔
1463
        = this->to_attrs(lt, lt.lt_style_low_threshold, reporter);
12,357✔
1464
    this->get_role_attrs(role_t::VCR_MED_THRESHOLD)
12,357✔
1465
        = this->to_attrs(lt, lt.lt_style_med_threshold, reporter);
12,357✔
1466
    this->get_role_attrs(role_t::VCR_HIGH_THRESHOLD)
12,357✔
1467
        = this->to_attrs(lt, lt.lt_style_high_threshold, reporter);
12,357✔
1468

1469
    {
1470
        auto t0_attrs = this->to_attrs(lt, lt.lt_style_low_threshold, reporter);
12,357✔
1471
        auto t3_attrs = this->to_attrs(lt, lt.lt_style_med_threshold, reporter);
12,357✔
1472
        auto t6_attrs
1473
            = this->to_attrs(lt, lt.lt_style_high_threshold, reporter);
12,357✔
1474

1475
        auto t0_color = this->vc_active_palette->to_lab_color(
12,357✔
1476
            t0_attrs.ra_normal.ta_bg_color);
1477
        auto t1_attrs = t0_attrs;
12,357✔
1478
        auto t2_attrs = t3_attrs;
12,357✔
1479
        auto t3_color = this->vc_active_palette->to_lab_color(
12,357✔
1480
            t3_attrs.ra_normal.ta_bg_color);
1481
        auto t4_attrs = t3_attrs;
12,357✔
1482
        auto t5_attrs = t6_attrs;
12,357✔
1483
        auto t6_color = this->vc_active_palette->to_lab_color(
12,357✔
1484
            t6_attrs.ra_normal.ta_bg_color);
1485
        if (t0_color && t3_color && t6_color) {
12,357✔
1486
            auto low_mid = t0_color->avg(t3_color.value());
12,340✔
1487
            auto mid_high = t3_color->avg(t6_color.value());
12,340✔
1488
            t1_attrs.ra_normal.ta_bg_color = this->match_color(
12,340✔
1489
                styling::color_unit::from_rgb(t0_color->avg(low_mid).to_rgb()));
12,340✔
1490
            t2_attrs.ra_normal.ta_bg_color = this->match_color(
12,340✔
1491
                styling::color_unit::from_rgb(t3_color->avg(low_mid).to_rgb()));
12,340✔
1492
            t4_attrs.ra_normal.ta_bg_color
1493
                = this->match_color(styling::color_unit::from_rgb(
12,340✔
1494
                    t3_color->avg(mid_high).to_rgb()));
12,340✔
1495
            t5_attrs.ra_normal.ta_bg_color
1496
                = this->match_color(styling::color_unit::from_rgb(
12,340✔
1497
                    t6_color->avg(mid_high).to_rgb()));
24,680✔
1498
        }
1499
        this->get_role_attrs(role_t::VCR_SPECTRO_THRESHOLD0) = t0_attrs;
12,357✔
1500
        this->get_role_attrs(role_t::VCR_SPECTRO_THRESHOLD1) = t1_attrs;
12,357✔
1501
        this->get_role_attrs(role_t::VCR_SPECTRO_THRESHOLD2) = t2_attrs;
12,357✔
1502
        this->get_role_attrs(role_t::VCR_SPECTRO_THRESHOLD3) = t3_attrs;
12,357✔
1503
        this->get_role_attrs(role_t::VCR_SPECTRO_THRESHOLD4) = t4_attrs;
12,357✔
1504
        this->get_role_attrs(role_t::VCR_SPECTRO_THRESHOLD5) = t5_attrs;
12,357✔
1505
        this->get_role_attrs(role_t::VCR_SPECTRO_THRESHOLD6) = t6_attrs;
12,357✔
1506
    }
1507

1508
    for (auto level = static_cast<log_level_t>(LEVEL_UNKNOWN + 1);
12,357✔
1509
         level < LEVEL__MAX;
185,355✔
1510
         level = static_cast<log_level_t>(level + 1))
172,998✔
1511
    {
1512
        auto level_iter = lt.lt_level_styles.find(level);
172,998✔
1513

1514
        if (level_iter == lt.lt_level_styles.end()) {
172,998✔
1515
            this->vc_level_attrs[level]
123,314✔
1516
                = role_attrs{text_attrs{}, text_attrs{}};
123,314✔
1517
        } else {
1518
            this->vc_level_attrs[level]
49,684✔
1519
                = this->to_attrs(lt, level_iter->second, reporter);
49,684✔
1520
        }
1521
    }
1522
    this->vc_level_attrs[LEVEL_UNKNOWN] = this->vc_level_attrs[LEVEL_INFO];
12,357✔
1523

1524
    for (int32_t role_index = 0;
1,235,700✔
1525
         role_index < lnav::enums::to_underlying(role_t::VCR__MAX);
1,235,700✔
1526
         role_index++)
1527
    {
1528
        const auto& ra = this->vc_role_attrs[role_index];
1,223,343✔
1529
        if (ra.ra_class_name.empty()) {
1,223,343✔
1530
            continue;
180,263✔
1531
        }
1532

1533
        this->vc_class_to_role[ra.ra_class_name.to_string()]
2,086,160✔
1534
            = VC_ROLE.value(role_t(role_index));
3,129,240✔
1535
    }
1536
    for (int level_index = 0; level_index < LEVEL__MAX; level_index++) {
197,712✔
1537
        const auto& ra = this->vc_level_attrs[level_index];
185,355✔
1538
        if (ra.ra_class_name.empty()) {
185,355✔
1539
            continue;
135,995✔
1540
        }
1541

1542
        this->vc_class_to_role[ra.ra_class_name.to_string()]
98,720✔
1543
            = SA_LEVEL.value(level_index);
148,080✔
1544
    }
1545

1546
    if (this->vc_notcurses) {
12,357✔
1547
        auto& mouse_i = injector::get<xterm_mouse&>();
36✔
1548
        mouse_i.set_enabled(
36✔
1549
            this->vc_notcurses,
1550
            check_experimental("mouse")
36✔
1551
                || lnav_config.lc_mouse_mode == lnav_mouse_mode::enabled);
36✔
1552
    }
1553
}
12,357✔
1554

1555
styling::color_unit
1556
view_colors::color_for_ident(const char* str, size_t len) const
5,674✔
1557
{
1558
    auto index = crc32(1, (const Bytef*) str, len);
5,674✔
1559

1560
    if (str[0] == '#' && (len == 4 || len == 7)) {
5,674✔
1561
        auto fg_res
1562
            = styling::color_unit::from_str(string_fragment(str, 0, len));
47✔
1563
        if (fg_res.isOk()) {
47✔
1564
            return fg_res.unwrap();
×
1565
        }
1566
    }
47✔
1567

1568
    const auto offset = index % HI_COLOR_COUNT;
5,674✔
1569
    if (this->vc_highlight_colors[offset] == 0) {
5,674✔
1570
        return styling::color_unit::EMPTY;
×
1571
    }
1572
    auto retval = styling::color_unit::from_palette(
5,674✔
1573
        palette_color{static_cast<uint8_t>(this->vc_highlight_colors[offset])});
5,674✔
1574

1575
    return retval;
5,674✔
1576
}
1577

1578
text_attrs
1579
view_colors::attrs_for_ident(const char* str, size_t len) const
5,590✔
1580
{
1581
    auto retval = this->attrs_for_role(role_t::VCR_IDENTIFIER);
5,590✔
1582

1583
    if (std::holds_alternative<styling::semantic>(retval.ta_fg_color.cu_value))
5,590✔
1584
    {
1585
        retval.ta_fg_color = this->color_for_ident(str, len);
5,574✔
1586
    }
1587
    if (std::holds_alternative<styling::semantic>(retval.ta_bg_color.cu_value))
5,590✔
1588
    {
1589
        retval.ta_bg_color = this->color_for_ident(str, len);
×
1590
    }
1591

1592
    return retval;
5,590✔
1593
}
1594

1595
styling::color_unit
1596
view_colors::ansi_to_theme_color(styling::color_unit ansi_fg) const
112,474✔
1597
{
1598
    auto* palp = std::get_if<palette_color>(&ansi_fg.cu_value);
112,474✔
1599
    if (palp != nullptr) {
112,474✔
1600
        auto pal = static_cast<ansi_color>(*palp);
34,476✔
1601

1602
        if (pal >= ansi_color::black && pal <= ansi_color::white) {
34,476✔
1603
            return this->vc_ansi_to_theme[lnav::enums::to_underlying(pal)];
84✔
1604
        }
1605
    }
1606

1607
    return ansi_fg;
112,390✔
1608
}
1609

1610
Result<screen_curses, std::string>
1611
screen_curses::create(const notcurses_options& options)
18✔
1612
{
1613
    auto* nc = notcurses_core_init(&options, stdout);
18✔
1614
    if (nc == nullptr) {
18✔
1615
        return Err(fmt::format(FMT_STRING("unable to initialize notcurses {}"),
×
1616
                               lnav::from_errno()));
×
1617
    }
1618

1619
    auto& mouse_i = injector::get<xterm_mouse&>();
18✔
1620
    mouse_i.set_enabled(
18✔
1621
        nc,
1622
        check_experimental("mouse")
18✔
1623
            || lnav_config.lc_mouse_mode == lnav_mouse_mode::enabled);
18✔
1624

1625
    auto_mem<char> term_name;
18✔
1626
    term_name = notcurses_detected_terminal(nc);
18✔
1627
    log_info("notcurses detected terminal: %s", term_name.in());
18✔
1628

1629
    auto retval = screen_curses(nc);
18✔
1630

1631
    tcgetattr(STDIN_FILENO, &retval.sc_termios);
18✔
1632
    retval.sc_termios.c_cc[VSTART] = 0;
18✔
1633
    retval.sc_termios.c_cc[VSTOP] = 0;
18✔
1634
#ifdef VDISCARD
1635
    retval.sc_termios.c_cc[VDISCARD] = 0;
18✔
1636
#endif
1637
#ifdef VDSUSP
1638
    retval.sc_termios.c_cc[VDSUSP] = 0;
1639
#endif
1640
    tcsetattr(STDIN_FILENO, TCSANOW, &retval.sc_termios);
18✔
1641

1642
    return Ok(std::move(retval));
18✔
1643
}
18✔
1644

1645
screen_curses::screen_curses(screen_curses&& other) noexcept
72✔
1646
    : sc_termios(other.sc_termios),
72✔
1647
      sc_notcurses(std::exchange(other.sc_notcurses, nullptr))
72✔
1648
{
1649
}
72✔
1650

1651
extern "C"
1652
{
1653
Terminfo*
1654
terminfo_load_from_internal(const char* term_name)
18✔
1655
{
1656
    log_debug("checking for internal terminfo for: %s", term_name);
18✔
1657
    auto term_name_sf = string_fragment::from_c_str(term_name);
18✔
1658
    for (const auto& tf : lnav_terminfo_files) {
182✔
1659
        if (tf.get_name() != term_name_sf) {
182✔
1660
            continue;
164✔
1661
        }
1662
        log_info("  found internal terminfo!");
18✔
1663
        auto sfp = tf.to_string_fragment_producer();
18✔
1664
        auto content = sfp->to_string();
18✔
1665
        auto* retval = terminfo_parse(content.c_str(), content.size());
18✔
1666
        if (retval != nullptr) {
18✔
1667
            return retval;
18✔
1668
        }
1669
        log_error("  failed to load internal terminfo");
×
1670
    }
36✔
1671

1672
    return nullptr;
×
1673
}
1674
}
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