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

tstack / lnav / 20196206901-2747

13 Dec 2025 06:36PM UTC coverage: 68.868% (+0.04%) from 68.832%
20196206901-2747

push

github

tstack
[keymap] change CTRL-F to toggle filtering

35 of 50 new or added lines in 7 files covered. (70.0%)

1 existing line in 1 file now uncovered.

51588 of 74909 relevant lines covered (68.87%)

434514.15 hits per line

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

83.28
/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()
216✔
82
{
83
    static ui_periodic_timer retval;
216✔
84

85
    return retval;
216✔
86
}
87

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

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

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

106
    return retval;
629✔
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)
100✔
131
        : uda_origin(utf_origin), uda_offset(offset)
100✔
132
    {
133
    }
100✔
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()
391✔
180
{
181
    bool retval = false;
391✔
182

183
    this->vc_needs_update = false;
391✔
184

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

189
    for (auto* child : this->vc_children) {
208✔
190
        retval = child->do_update() || retval;
41✔
191
    }
192
    return retval;
167✔
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()
9✔
249
{
250
    static const bool enabled = getenv("IN_SCRIPTY") != nullptr;
9✔
251
    static const char OSC_INPUT[] = "\x1b]999;send-input\a";
252

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

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

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

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

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

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

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

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

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

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

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

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

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

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

368
                auto exp_read_start = expanded_line.size();
7,000✔
369
                auto lpc_start = lpc;
7,000✔
370
                auto read_res
371
                    = ww898::utf::utf8::read([&line, &expanded_line, &lpc] {
×
372
                          auto ch = line[lpc++];
7,191✔
373
                          expanded_line.push_back(ch);
7,191✔
374
                          return ch;
7,191✔
375
                      });
7,000✔
376

377
                if (read_res.isErr()) {
7,000✔
378
                    log_trace(
×
379
                        "error:%d:%zu:%s", y, x + lpc, read_res.unwrapErr());
380
                    expanded_line.resize(exp_read_start);
×
381
                    expanded_line.push_back('?');
×
NEW
382
                    curr_ch_col_count = 1;
×
383
                    char_index += 1;
×
384
                    lpc = lpc_start + 1;
×
385
                } else {
386
                    auto wch = read_res.unwrap();
7,000✔
387
                    if (wch == L'\u200d') {
7,000✔
NEW
388
                        join_start_index = char_index - last_ch_col_count;
×
NEW
389
                        continue;
×
390
                    }
391
                    auto wcw_res = uc_width(wch, "UTF-8");
7,000✔
392
                    if (wcw_res < 0) {
7,000✔
NEW
393
                        log_trace("uc_width(%x) does not recognize character",
×
394
                                  wch);
UNCOV
395
                        wcw_res = 1;
×
396
                    }
397
                    if (lpc > (lpc_start + 1)) {
7,000✔
398
                        utf_adjustments.emplace_back(
95✔
399
                            lpc_start, wcw_res - (lpc - lpc_start));
95✔
400
                    }
401
                    curr_ch_col_count = wcw_res;
7,000✔
402
                    char_index += wcw_res;
7,000✔
403
                    if (lr_bytes.lr_end == -1 && char_index > lr_chars.lr_end) {
7,000✔
404
                        lr_bytes.lr_end = exp_start_index;
×
405
                        retval.mr_chars_out = char_index - wcw_res;
×
406
                    }
407
                }
408
                break;
7,000✔
409
            }
7,000✔
410
        }
411
        if (join_start_index) {
7,013✔
NEW
412
            curr_ch_col_count = std::max(last_ch_col_count, curr_ch_col_count);
×
NEW
413
            char_index = join_start_index.value() + curr_ch_col_count;
×
NEW
414
            if (char_index > lr_chars.lr_end) {
×
NEW
415
                retval.mr_chars_out = char_index;
×
416
            }
NEW
417
            join_start_index = std::nullopt;
×
418
        }
419
        last_ch_col_count = curr_ch_col_count;
7,013✔
420
    }
421
    if (lr_bytes.lr_start == -1) {
895✔
422
        lr_bytes.lr_start = expanded_line.size();
82✔
423
    }
424
    if (lr_bytes.lr_end == -1) {
895✔
425
        lr_bytes.lr_end = expanded_line.size();
885✔
426
    }
427
    if (retval.mr_chars_out == 0) {
895✔
428
        retval.mr_chars_out = char_index;
885✔
429
    }
430
    retval.mr_bytes_remaining = expanded_line.size() - lr_bytes.lr_end;
895✔
431
    expanded_line.resize(lr_bytes.lr_end);
895✔
432

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

453
    text_attrs resolved_line_attrs[line_width_chars + 1];
58,027✔
454

455
    std::stable_sort(sa.begin(), sa.end());
895✔
456
    for (auto iter = sa.cbegin(); iter != sa.cend(); ++iter) {
2,487✔
457
        auto attr_range = iter->sa_range;
1,592✔
458

459
        require(attr_range.lr_start >= 0);
1,592✔
460
        require(attr_range.lr_end >= -1);
1,592✔
461

462
        if (!(iter->sa_type == &VC_ROLE || iter->sa_type == &VC_ROLE_FG
2,941✔
463
              || iter->sa_type == &VC_STYLE || iter->sa_type == &VC_GRAPHIC
1,349✔
464
              || iter->sa_type == &SA_LEVEL || iter->sa_type == &VC_FOREGROUND
832✔
465
              || iter->sa_type == &VC_BACKGROUND
704✔
466
              || iter->sa_type == &VC_BLOCK_ELEM || iter->sa_type == &VC_ICON))
704✔
467
        {
468
            continue;
705✔
469
        }
470

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

480
            if (attr_range.lr_end != -1) {
909✔
481
                for (const auto& adj : utf_adjustments) {
1,487✔
482
                    if (adj.uda_origin < iter->sa_range.lr_end) {
773✔
483
                        attr_range.lr_end += adj.uda_offset;
441✔
484
                    }
485
                }
486
            }
487
        }
488

489
        if (attr_range.lr_end == -1) {
909✔
490
            attr_range.lr_end = lr_chars.lr_start + line_width_chars;
195✔
491
        }
492
        if (attr_range.lr_end < lr_chars.lr_start) {
909✔
493
            continue;
×
494
        }
495
        attr_range.lr_start
496
            = std::max(0, attr_range.lr_start - lr_chars.lr_start);
909✔
497
        if (attr_range.lr_start > line_width_chars) {
909✔
498
            continue;
22✔
499
        }
500

501
        attr_range.lr_end
502
            = std::min(line_width_chars, attr_range.lr_end - lr_chars.lr_start);
887✔
503

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

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

522
        if (attr_range.lr_start < attr_range.lr_end) {
887✔
523
            auto attrs = text_attrs{};
879✔
524
            std::optional<const char*> graphic;
879✔
525

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

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

557
                if (role == role_t::VCR_SELECTED_TEXT) {
213✔
558
                    retval.mr_selected_text
559
                        = string_fragment::from_str(line).sub_range(
×
560
                            iter->sa_range.lr_start, iter->sa_range.lr_end);
×
561
                }
562
            } else if (iter->sa_type == &VC_ROLE_FG) {
×
563
                auto role_attrs
564
                    = vc.attrs_for_role(iter->sa_value.get<role_t>());
×
565
                attrs.ta_fg_color = role_attrs.ta_fg_color;
×
566
            }
567

568
            if (graphic || !attrs.empty()) {
879✔
569
                if (std::holds_alternative<styling::semantic>(
702✔
570
                        attrs.ta_fg_color.cu_value))
571
                {
572
                    attrs.ta_fg_color
573
                        = vc.color_for_ident(al.to_string_fragment(iter));
106✔
574
                }
575
                if (std::holds_alternative<styling::semantic>(
702✔
576
                        attrs.ta_bg_color.cu_value))
577
                {
578
                    attrs.ta_bg_color
579
                        = vc.color_for_ident(al.to_string_fragment(iter));
×
580
                }
581

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

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

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

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

652
        if (fg_color[lpc] == -1) {
653
            fg_color[lpc] = cur_fg;
654
        }
655
        if (bg_color[lpc] == -1) {
656
            bg_color[lpc] = cur_bg;
657
        }
658
#endif
659
    }
660

661
    return retval;
1,790✔
662
}
895✔
663

664
view_colors&
665
view_colors::singleton()
172,545✔
666
{
667
    static view_colors s_vc;
172,545✔
668

669
    return s_vc;
172,545✔
670
}
671

672
view_colors::view_colors()
1,168✔
673
    : vc_ansi_to_theme{
674
          styling::color_unit::from_palette({0}),
×
675
          styling::color_unit::from_palette({1}),
1,168✔
676
          styling::color_unit::from_palette({2}),
1,168✔
677
          styling::color_unit::from_palette({3}),
1,168✔
678
          styling::color_unit::from_palette({4}),
1,168✔
679
          styling::color_unit::from_palette({5}),
1,168✔
680
          styling::color_unit::from_palette({6}),
1,168✔
681
          styling::color_unit::from_palette({7}),
1,168✔
682
      }
134,320✔
683
{
684
    auto text_default = text_attrs{};
1,168✔
685
    text_default.ta_fg_color = styling::color_unit::from_palette(COLOR_WHITE);
1,168✔
686
    text_default.ta_bg_color = styling::color_unit::from_palette(COLOR_BLACK);
1,168✔
687
    this->vc_role_attrs[lnav::enums::to_underlying(role_t::VCR_TEXT)]
688
        = role_attrs{text_default, text_default};
1,168✔
689
}
1,168✔
690

691
block_elem_t
692
view_colors::wchar_for_icon(ui_icon_t ic) const
21✔
693
{
694
    return this->vc_icons[lnav::enums::to_underlying(ic)];
21✔
695
}
696

697
bool view_colors::initialized = false;
698

699
static const std::string COLOR_NAMES[] = {
700
    "black",
701
    "red",
702
    "green",
703
    "yellow",
704
    "blue",
705
    "magenta",
706
    "cyan",
707
    "white",
708
};
709

710
class ui_listener : public lnav_config_listener {
711
public:
712
    ui_listener() : lnav_config_listener(__FILE__) {}
1,168✔
713

714
    void reload_config(error_reporter& reporter) override
1,293✔
715
    {
716
        static auto op = lnav_operation{"reload_theme"};
1,293✔
717

718
        auto op_guard = lnav_opid_guard::internal(op);
1,293✔
719

720
        if (!view_colors::initialized) {
1,293✔
721
            view_colors::vc_active_palette = ansi_colors();
640✔
722
        }
723

724
        auto& vc = view_colors::singleton();
1,293✔
725

726
        for (const auto& pair : lnav_config.lc_ui_theme_defs) {
12,787✔
727
            vc.init_roles(pair.second, reporter);
11,494✔
728
        }
729

730
        const auto iter
731
            = lnav_config.lc_ui_theme_defs.find(lnav_config.lc_ui_theme);
1,293✔
732

733
        if (iter == lnav_config.lc_ui_theme_defs.end()) {
1,293✔
734
            auto theme_names
735
                = lnav_config.lc_ui_theme_defs | lnav::itertools::first();
18✔
736

737
            reporter(&lnav_config.lc_ui_theme,
18✔
738
                     lnav::console::user_message::error(
×
739
                         attr_line_t("unknown theme -- ")
36✔
740
                             .append_quoted(lnav_config.lc_ui_theme))
36✔
741
                         .with_help(attr_line_t("The available themes are: ")
36✔
742
                                        .join(theme_names, ", ")));
18✔
743

744
            vc.init_roles(lnav_config.lc_ui_theme_defs["default"], reporter);
18✔
745
            return;
18✔
746
        }
18✔
747

748
        if (view_colors::initialized) {
1,275✔
749
            vc.init_roles(iter->second, reporter);
636✔
750
        }
751
    }
1,293✔
752
};
753

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

767
                unsigned r = 0, g = 0, b = 0;
4✔
768

769
                ncchannel_rgb8(chan, &r, &g, &b);
4✔
770
                auto rgb = rgb_color{(short) r, (short) g, (short) b};
4✔
771
                return lab_color{rgb};
4✔
772
            }
773

774
            return vc_active_palette->tc_palette[pal_index].xc_lab_color;
1,946✔
775
        }
776
    } else {
777
        auto* col = std::get_if<rgb_color>(&color.cu_value);
19,794✔
778
        if (col != nullptr) {
19,794✔
779
            return lab_color{*col};
19,760✔
780
        }
781
    }
782

783
    return std::nullopt;
2,586✔
784
}
785

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

830
    return retval;
58,523✔
831
}
832

833
static ui_listener _UI_LISTENER;
834
term_color_palette* view_colors::vc_active_palette;
835

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

852
    singleton().vc_notcurses = nc;
630✔
853
    initialized = true;
630✔
854

855
    {
856
        auto reporter
857
            = [](const void*, const lnav::console::user_message& um) {};
16✔
858

859
        _UI_LISTENER.reload_config(reporter);
630✔
860
    }
861
}
630✔
862

863
styling::color_unit
864
view_colors::match_color(styling::color_unit cu) const
2,605,467✔
865
{
866
    if (this->vc_notcurses == nullptr) {
2,605,467✔
867
        return cu;
2,598,523✔
868
    }
869

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

872
    if (caps->rgb) {
6,944✔
873
        return cu;
×
874
    }
875

876
    auto* rgb = std::get_if<rgb_color>(&cu.cu_value);
6,944✔
877
    if (rgb != nullptr) {
6,944✔
878
        auto lab = lab_color{*rgb};
2,308✔
879
        auto pal = vc_active_palette->match_color(lab);
2,308✔
880

881
        log_trace("mapped RGB (%d, %d, %d) to palette %d",
2,308✔
882
                  rgb->rc_r,
883
                  rgb->rc_g,
884
                  rgb->rc_b,
885
                  pal);
886
        return styling::color_unit::from_palette(palette_color{pal});
2,308✔
887
    }
888

889
    return cu;
4,636✔
890
}
891

892
view_colors::role_attrs
893
view_colors::to_attrs(const lnav_theme& lt,
1,156,279✔
894
                      const positioned_property<style_config>& pp_sc,
895
                      lnav_config_listener::error_reporter& reporter)
896
{
897
    const auto& sc = pp_sc.pp_value;
1,156,279✔
898
    std::string fg_color, bg_color;
1,156,279✔
899
    intern_string_t role_class;
1,156,279✔
900

901
    if (pp_sc.pp_path.empty()) {
1,156,279✔
902
#if 0
903
        // too slow to do this now
904
        reporter(&sc.sc_color, lnav::console::user_message::warning(""));
905
#endif
906
    } else if (!pp_sc.pp_path.empty()) {
1,013,266✔
907
        auto role_class_path = pp_sc.pp_path.to_string_fragment();
1,013,266✔
908
        auto inner
909
            = role_class_path.rsplit_pair(string_fragment::tag1{'/'}).value();
1,013,266✔
910
        auto outer
911
            = inner.first.rsplit_pair(string_fragment::tag1{'/'}).value();
1,013,266✔
912

913
        role_class = intern_string::lookup(
1,013,266✔
914
            fmt::format(FMT_STRING("-lnav_{}_{}"), outer.second, inner.second));
5,066,330✔
915
    }
916

917
    auto fg1 = sc.sc_color;
1,156,279✔
918
    auto bg1 = sc.sc_background_color;
1,156,279✔
919
    shlex(fg1).eval(fg_color, scoped_resolver{&lt.lt_vars});
1,156,279✔
920
    shlex(bg1).eval(bg_color, scoped_resolver{&lt.lt_vars});
1,156,279✔
921

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

941
    fg = this->match_color(fg);
1,156,279✔
942
    bg = this->match_color(bg);
1,156,279✔
943

944
    auto retval1 = text_attrs{0, fg, bg};
1,156,279✔
945
    text_attrs retval2;
1,156,279✔
946

947
    if (sc.sc_underline) {
1,156,279✔
948
        retval1 |= text_attrs::style::underline;
88,096✔
949
        retval2 |= text_attrs::style::underline;
88,096✔
950
    }
951
    if (sc.sc_bold) {
1,156,279✔
952
        retval1 |= text_attrs::style::bold;
252,207✔
953
        retval2 |= text_attrs::style::bold;
252,207✔
954
    }
955
    if (sc.sc_italic) {
1,156,279✔
956
        retval1 |= text_attrs::style::italic;
17,884✔
957
        retval2 |= text_attrs::style::italic;
17,884✔
958
    }
959
    if (sc.sc_strike) {
1,156,279✔
960
        retval1 |= text_attrs::style::struck;
×
961
        retval2 |= text_attrs::style::struck;
×
962
    }
963

964
    return {retval1, retval2, role_class};
1,156,279✔
965
}
1,156,279✔
966

967
void
968
view_colors::init_roles(const lnav_theme& lt,
12,148✔
969
                        lnav_config_listener::error_reporter& reporter)
970
{
971
    const auto& default_theme = lnav_config.lc_ui_theme_defs["default"];
12,148✔
972
    std::string err;
12,148✔
973

974
    size_t icon_index = 0;
12,148✔
975
    for (const auto& ic :
12,148✔
976
         {
977
             lt.lt_icon_hidden,
12,148✔
978
             lt.lt_icon_ok,
12,148✔
979
             lt.lt_icon_info,
12,148✔
980
             lt.lt_icon_warning,
12,148✔
981
             lt.lt_icon_error,
12,148✔
982

983
             lt.lt_icon_log_level_trace,
12,148✔
984
             lt.lt_icon_log_level_debug,
12,148✔
985
             lt.lt_icon_log_level_info,
12,148✔
986
             lt.lt_icon_log_level_stats,
12,148✔
987
             lt.lt_icon_log_level_notice,
12,148✔
988
             lt.lt_icon_log_level_warning,
12,148✔
989
             lt.lt_icon_log_level_error,
12,148✔
990
             lt.lt_icon_log_level_critical,
12,148✔
991
             lt.lt_icon_log_level_fatal,
12,148✔
992

993
             lt.lt_icon_play,
12,148✔
994
             lt.lt_icon_edit,
12,148✔
995
             lt.lt_icon_file,
12,148✔
996
             lt.lt_icon_thread,
12,148✔
997
         })
461,624✔
998
    {
999
        size_t index = 0;
218,664✔
1000
        if (ic.pp_value.ic_value) {
218,664✔
1001
            auto read_res = ww898::utf::utf8::read([&ic, &index]() {
×
1002
                return ic.pp_value.ic_value.value()[index++];
124,280✔
1003
            });
34,416✔
1004
            if (read_res.isErr()) {
34,416✔
1005
                reporter(&ic,
×
1006
                         lnav::console::user_message::error(
×
1007
                             "icon is not valid UTF-8"));
1008
            } else if (read_res.unwrap() != 0) {
34,416✔
1009
                role_t icon_role;
1010
                switch (static_cast<ui_icon_t>(icon_index)) {
34,416✔
1011
                    case ui_icon_t::hidden:
1,912✔
1012
                        icon_role = role_t::VCR_HIDDEN;
1,912✔
1013
                        break;
1,912✔
1014
                    case ui_icon_t::ok:
1,912✔
1015
                        icon_role = role_t::VCR_OK;
1,912✔
1016
                        break;
1,912✔
1017
                    case ui_icon_t::info:
1,912✔
1018
                        icon_role = role_t::VCR_INFO;
1,912✔
1019
                        break;
1,912✔
1020
                    case ui_icon_t::warning:
3,824✔
1021
                    case ui_icon_t::log_level_warning:
1022
                        icon_role = role_t::VCR_WARNING;
3,824✔
1023
                        break;
3,824✔
1024
                    case ui_icon_t::error:
7,648✔
1025
                    case ui_icon_t::log_level_error:
1026
                    case ui_icon_t::log_level_fatal:
1027
                    case ui_icon_t::log_level_critical:
1028
                        icon_role = role_t::VCR_ERROR;
7,648✔
1029
                        break;
7,648✔
1030
                    case ui_icon_t::play:
1,912✔
1031
                        icon_role = role_t::VCR_OK;
1,912✔
1032
                        break;
1,912✔
1033
                    default:
15,296✔
1034
                        icon_role = role_t::VCR_TEXT;
15,296✔
1035
                        break;
15,296✔
1036
                }
1037
                this->vc_icons[icon_index]
1038
                    = block_elem_t{(char32_t) read_res.unwrap(), icon_role};
34,416✔
1039
            }
1040
        }
34,416✔
1041
        icon_index += 1;
218,664✔
1042
    }
230,812✔
1043

1044
    /* Setup the mappings from roles to actual colors. */
1045
    this->get_role_attrs(role_t::VCR_TEXT)
12,148✔
1046
        = this->to_attrs(lt, lt.lt_style_text, reporter);
12,148✔
1047

1048
    for (int ansi_fg = 1; ansi_fg < 8; ansi_fg++) {
97,184✔
1049
        auto fg_iter = lt.lt_vars.find(COLOR_NAMES[ansi_fg]);
85,036✔
1050
        auto fg_str = fg_iter == lt.lt_vars.end()
85,036✔
1051
            ? ""
85,036✔
1052
            : fmt::to_string(fg_iter->second);
101,093✔
1053

1054
        auto rgb_fg = from<rgb_color>(string_fragment::from_str(fg_str))
170,072✔
1055
                          .unwrapOrElse([&](const auto& msg) {
85,036✔
1056
                              reporter(&fg_str,
×
1057
                                       lnav::console::user_message::error(
1058
                                           attr_line_t("invalid color -- ")
×
1059
                                               .append_quoted(fg_str))
×
1060
                                           .with_reason(msg));
×
1061
                              return rgb_color{};
×
1062
                          });
1063

1064
        auto fg = vc_active_palette->match_color(lab_color(rgb_fg));
85,036✔
1065

1066
        if (rgb_fg.empty()) {
85,036✔
1067
            fg = ansi_fg;
16,057✔
1068
        }
1069

1070
        this->vc_ansi_to_theme[ansi_fg] = palette_color{fg};
85,036✔
1071
    }
85,036✔
1072

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

1132
    {
1133
        auto& time_to_text
1134
            = this->get_role_attrs(role_t::VCR_TIME_COLUMN_TO_TEXT);
12,148✔
1135
        auto time_attrs = this->attrs_for_role(role_t::VCR_TIME_COLUMN);
12,148✔
1136
        auto text_attrs = this->attrs_for_role(role_t::VCR_TEXT);
12,148✔
1137
        time_to_text.ra_class_name.clear();
12,148✔
1138

1139
        time_to_text.ra_normal.ta_fg_color = time_attrs.ta_bg_color;
12,148✔
1140
        time_to_text.ra_normal.ta_bg_color = text_attrs.ta_bg_color;
12,148✔
1141

1142
        auto fg_as_lab_opt = to_lab_color(time_attrs.ta_bg_color);
12,148✔
1143
        auto bg_as_lab_opt = to_lab_color(text_attrs.ta_bg_color);
12,148✔
1144
        if (fg_as_lab_opt && bg_as_lab_opt) {
12,148✔
1145
            auto fg_as_lab = fg_as_lab_opt.value();
10,855✔
1146
            auto bg_as_lab = bg_as_lab_opt.value();
10,855✔
1147
            auto diff = fg_as_lab.lc_l - bg_as_lab.lc_l;
10,855✔
1148
            fg_as_lab.lc_l -= diff / 4.0;
10,855✔
1149
            bg_as_lab.lc_l += diff / 4.0;
10,855✔
1150

1151
            time_to_text.ra_normal.ta_fg_color = this->match_color(
10,855✔
1152
                styling::color_unit::from_rgb(fg_as_lab.to_rgb()));
10,855✔
1153
            time_to_text.ra_normal.ta_bg_color = this->match_color(
10,855✔
1154
                styling::color_unit::from_rgb(bg_as_lab.to_rgb()));
21,710✔
1155
        }
1156

1157
        if (bg_as_lab_opt) {
12,148✔
1158
            auto bg_as_lab = bg_as_lab_opt.value();
10,855✔
1159
            std::vector<std::pair<double, size_t>> contrasting;
10,855✔
1160
            for (const auto& [index, tcolor] :
102,655✔
1161
                 lnav::itertools::enumerate(vc_active_palette->tc_palette))
113,510✔
1162
            {
1163
                if (index < 16) {
91,800✔
1164
                    continue;
87,000✔
1165
                }
1166
                if (bg_as_lab.sufficient_contrast(tcolor.xc_lab_color)) {
4,800✔
1167
                    contrasting.emplace_back(
2,132✔
1168
                        bg_as_lab.deltaE(tcolor.xc_lab_color), index);
2,132✔
1169
                }
1170
            }
1171

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

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

1266
    this->get_role_attrs(role_t::VCR_STATUS_HOTKEY)
12,148✔
1267
        = this->to_attrs(lt, lt.lt_style_status_hotkey, reporter);
12,148✔
1268
    this->get_role_attrs(role_t::VCR_STATUS_TITLE_HOTKEY)
12,148✔
1269
        = this->to_attrs(lt, lt.lt_style_status_title_hotkey, reporter);
12,148✔
1270
    this->get_role_attrs(role_t::VCR_STATUS_DISABLED_TITLE)
12,148✔
1271
        = this->to_attrs(lt, lt.lt_style_status_disabled_title, reporter);
12,148✔
1272

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

1310
    {
1311
        positioned_property<style_config> stitch_sc;
12,148✔
1312

1313
        stitch_sc.pp_value.sc_color
1314
            = lt.lt_style_status_subtitle.pp_value.sc_background_color;
12,148✔
1315
        stitch_sc.pp_value.sc_background_color
1316
            = lt.lt_style_status_title.pp_value.sc_background_color;
12,148✔
1317
        this->get_role_attrs(role_t::VCR_STATUS_STITCH_TITLE_TO_SUB)
12,148✔
1318
            = this->to_attrs(lt, stitch_sc, reporter);
12,148✔
1319
    }
12,148✔
1320
    {
1321
        positioned_property<style_config> stitch_sc;
12,148✔
1322

1323
        stitch_sc.pp_value.sc_color
1324
            = lt.lt_style_status_title.pp_value.sc_background_color;
12,148✔
1325
        stitch_sc.pp_value.sc_background_color
1326
            = lt.lt_style_status_subtitle.pp_value.sc_background_color;
12,148✔
1327
        this->get_role_attrs(role_t::VCR_STATUS_STITCH_SUB_TO_TITLE)
12,148✔
1328
            = this->to_attrs(lt, stitch_sc, reporter);
12,148✔
1329
    }
12,148✔
1330

1331
    {
1332
        positioned_property<style_config> stitch_sc;
12,148✔
1333

1334
        stitch_sc.pp_value.sc_color
1335
            = lt.lt_style_status.pp_value.sc_background_color;
12,148✔
1336
        stitch_sc.pp_value.sc_background_color
1337
            = lt.lt_style_status_subtitle.pp_value.sc_background_color;
12,148✔
1338
        this->get_role_attrs(role_t::VCR_STATUS_STITCH_SUB_TO_NORMAL)
12,148✔
1339
            = this->to_attrs(lt, stitch_sc, reporter);
12,148✔
1340
    }
12,148✔
1341
    {
1342
        positioned_property<style_config> stitch_sc;
12,148✔
1343

1344
        stitch_sc.pp_value.sc_color
1345
            = lt.lt_style_status_subtitle.pp_value.sc_background_color;
12,148✔
1346
        stitch_sc.pp_value.sc_background_color
1347
            = lt.lt_style_status.pp_value.sc_background_color;
12,148✔
1348
        this->get_role_attrs(role_t::VCR_STATUS_STITCH_NORMAL_TO_SUB)
12,148✔
1349
            = this->to_attrs(lt, stitch_sc, reporter);
12,148✔
1350
    }
12,148✔
1351

1352
    {
1353
        positioned_property<style_config> stitch_sc;
12,148✔
1354

1355
        stitch_sc.pp_value.sc_color
1356
            = lt.lt_style_status.pp_value.sc_background_color;
12,148✔
1357
        stitch_sc.pp_value.sc_background_color
1358
            = lt.lt_style_status_title.pp_value.sc_background_color;
12,148✔
1359
        this->get_role_attrs(role_t::VCR_STATUS_STITCH_TITLE_TO_NORMAL)
12,148✔
1360
            = this->to_attrs(lt, stitch_sc, reporter);
12,148✔
1361
    }
12,148✔
1362
    {
1363
        positioned_property<style_config> stitch_sc;
12,148✔
1364

1365
        stitch_sc.pp_value.sc_color
1366
            = lt.lt_style_status_title.pp_value.sc_background_color;
12,148✔
1367
        stitch_sc.pp_value.sc_background_color
1368
            = lt.lt_style_status.pp_value.sc_background_color;
12,148✔
1369
        this->get_role_attrs(role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE)
12,148✔
1370
            = this->to_attrs(lt, stitch_sc, reporter);
12,148✔
1371
    }
12,148✔
1372

1373
    this->get_role_attrs(role_t::VCR_INACTIVE_STATUS)
12,148✔
1374
        = this->to_attrs(lt, lt.lt_style_inactive_status, reporter);
12,148✔
1375
    this->get_role_attrs(role_t::VCR_INACTIVE_ALERT_STATUS)
12,148✔
1376
        = this->to_attrs(lt, lt.lt_style_inactive_alert_status, reporter);
12,148✔
1377

1378
    this->get_role_attrs(role_t::VCR_FOCUSED)
12,148✔
1379
        = this->to_attrs(lt, lt.lt_style_focused, reporter);
12,148✔
1380
    this->get_role_attrs(role_t::VCR_DISABLED_FOCUSED)
12,148✔
1381
        = this->to_attrs(lt, lt.lt_style_disabled_focused, reporter);
12,148✔
1382
    this->get_role_attrs(role_t::VCR_SCROLLBAR)
12,148✔
1383
        = this->to_attrs(lt, lt.lt_style_scrollbar, reporter);
12,148✔
1384
    {
1385
        positioned_property<style_config> bar_sc;
12,148✔
1386

1387
        bar_sc.pp_value.sc_color = lt.lt_style_error.pp_value.sc_color;
12,148✔
1388
        bar_sc.pp_value.sc_background_color
1389
            = lt.lt_style_scrollbar.pp_value.sc_background_color;
12,148✔
1390
        this->get_role_attrs(role_t::VCR_SCROLLBAR_ERROR)
12,148✔
1391
            = this->to_attrs(lt, bar_sc, reporter);
12,148✔
1392
    }
12,148✔
1393
    {
1394
        positioned_property<style_config> bar_sc;
12,148✔
1395

1396
        bar_sc.pp_value.sc_color = lt.lt_style_warning.pp_value.sc_color;
12,148✔
1397
        bar_sc.pp_value.sc_background_color
1398
            = lt.lt_style_scrollbar.pp_value.sc_background_color;
12,148✔
1399
        this->get_role_attrs(role_t::VCR_SCROLLBAR_WARNING)
12,148✔
1400
            = this->to_attrs(lt, bar_sc, reporter);
12,148✔
1401
    }
12,148✔
1402

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

1442
    this->get_role_attrs(role_t::VCR_RE_SPECIAL)
12,148✔
1443
        = this->to_attrs(lt, lt.lt_style_re_special, reporter);
12,148✔
1444
    this->get_role_attrs(role_t::VCR_RE_REPEAT)
12,148✔
1445
        = this->to_attrs(lt, lt.lt_style_re_repeat, reporter);
12,148✔
1446
    this->get_role_attrs(role_t::VCR_FILE)
12,148✔
1447
        = this->to_attrs(lt, lt.lt_style_file, reporter);
12,148✔
1448

1449
    this->get_role_attrs(role_t::VCR_DIFF_DELETE)
12,148✔
1450
        = this->to_attrs(lt, lt.lt_style_diff_delete, reporter);
12,148✔
1451
    this->get_role_attrs(role_t::VCR_DIFF_ADD)
12,148✔
1452
        = this->to_attrs(lt, lt.lt_style_diff_add, reporter);
12,148✔
1453
    this->get_role_attrs(role_t::VCR_DIFF_SECTION)
12,148✔
1454
        = this->to_attrs(lt, lt.lt_style_diff_section, reporter);
12,148✔
1455

1456
    this->get_role_attrs(role_t::VCR_LOW_THRESHOLD)
12,148✔
1457
        = this->to_attrs(lt, lt.lt_style_low_threshold, reporter);
12,148✔
1458
    this->get_role_attrs(role_t::VCR_MED_THRESHOLD)
12,148✔
1459
        = this->to_attrs(lt, lt.lt_style_med_threshold, reporter);
12,148✔
1460
    this->get_role_attrs(role_t::VCR_HIGH_THRESHOLD)
12,148✔
1461
        = this->to_attrs(lt, lt.lt_style_high_threshold, reporter);
12,148✔
1462

1463
    {
1464
        auto t0_attrs = this->to_attrs(lt, lt.lt_style_low_threshold, reporter);
12,148✔
1465
        auto t3_attrs = this->to_attrs(lt, lt.lt_style_med_threshold, reporter);
12,148✔
1466
        auto t6_attrs
1467
            = this->to_attrs(lt, lt.lt_style_high_threshold, reporter);
12,148✔
1468

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

1502
    for (auto level = static_cast<log_level_t>(LEVEL_UNKNOWN + 1);
12,148✔
1503
         level < LEVEL__MAX;
182,220✔
1504
         level = static_cast<log_level_t>(level + 1))
170,072✔
1505
    {
1506
        auto level_iter = lt.lt_level_styles.find(level);
170,072✔
1507

1508
        if (level_iter == lt.lt_level_styles.end()) {
170,072✔
1509
            this->vc_level_attrs[level]
121,224✔
1510
                = role_attrs{text_attrs{}, text_attrs{}};
121,224✔
1511
        } else {
1512
            this->vc_level_attrs[level]
48,848✔
1513
                = this->to_attrs(lt, level_iter->second, reporter);
48,848✔
1514
        }
1515
    }
1516
    this->vc_level_attrs[LEVEL_UNKNOWN] = this->vc_level_attrs[LEVEL_INFO];
12,148✔
1517

1518
    for (int32_t role_index = 0;
1,214,800✔
1519
         role_index < lnav::enums::to_underlying(role_t::VCR__MAX);
1,214,800✔
1520
         role_index++)
1521
    {
1522
        const auto& ra = this->vc_role_attrs[role_index];
1,202,652✔
1523
        if (ra.ra_class_name.empty()) {
1,202,652✔
1524
            continue;
177,238✔
1525
        }
1526

1527
        this->vc_class_to_role[ra.ra_class_name.to_string()]
2,050,828✔
1528
            = VC_ROLE.value(role_t(role_index));
3,076,242✔
1529
    }
1530
    for (int level_index = 0; level_index < LEVEL__MAX; level_index++) {
194,368✔
1531
        const auto& ra = this->vc_level_attrs[level_index];
182,220✔
1532
        if (ra.ra_class_name.empty()) {
182,220✔
1533
            continue;
133,696✔
1534
        }
1535

1536
        this->vc_class_to_role[ra.ra_class_name.to_string()]
97,048✔
1537
            = SA_LEVEL.value(level_index);
145,572✔
1538
    }
1539

1540
    if (this->vc_notcurses) {
12,148✔
1541
        auto& mouse_i = injector::get<xterm_mouse&>();
36✔
1542
        mouse_i.set_enabled(
36✔
1543
            this->vc_notcurses,
1544
            check_experimental("mouse")
36✔
1545
                || lnav_config.lc_mouse_mode == lnav_mouse_mode::enabled);
36✔
1546
    }
1547
}
12,148✔
1548

1549
styling::color_unit
1550
view_colors::color_for_ident(const char* str, size_t len) const
5,648✔
1551
{
1552
    auto index = crc32(1, (const Bytef*) str, len);
5,648✔
1553

1554
    if (str[0] == '#' && (len == 4 || len == 7)) {
5,648✔
1555
        auto fg_res
1556
            = styling::color_unit::from_str(string_fragment(str, 0, len));
47✔
1557
        if (fg_res.isOk()) {
47✔
1558
            return fg_res.unwrap();
×
1559
        }
1560
    }
47✔
1561

1562
    const auto offset = index % HI_COLOR_COUNT;
5,648✔
1563
    if (this->vc_highlight_colors[offset] == 0) {
5,648✔
1564
        return styling::color_unit::EMPTY;
×
1565
    }
1566
    auto retval = styling::color_unit::from_palette(
5,648✔
1567
        palette_color{static_cast<uint8_t>(this->vc_highlight_colors[offset])});
5,648✔
1568

1569
    return retval;
5,648✔
1570
}
1571

1572
text_attrs
1573
view_colors::attrs_for_ident(const char* str, size_t len) const
5,558✔
1574
{
1575
    auto retval = this->attrs_for_role(role_t::VCR_IDENTIFIER);
5,558✔
1576

1577
    if (std::holds_alternative<styling::semantic>(retval.ta_fg_color.cu_value))
5,558✔
1578
    {
1579
        retval.ta_fg_color = this->color_for_ident(str, len);
5,542✔
1580
    }
1581
    if (std::holds_alternative<styling::semantic>(retval.ta_bg_color.cu_value))
5,558✔
1582
    {
1583
        retval.ta_bg_color = this->color_for_ident(str, len);
×
1584
    }
1585

1586
    return retval;
5,558✔
1587
}
1588

1589
styling::color_unit
1590
view_colors::ansi_to_theme_color(styling::color_unit ansi_fg) const
112,474✔
1591
{
1592
    auto* palp = std::get_if<palette_color>(&ansi_fg.cu_value);
112,474✔
1593
    if (palp != nullptr) {
112,474✔
1594
        auto pal = static_cast<ansi_color>(*palp);
34,482✔
1595

1596
        if (pal >= ansi_color::black && pal <= ansi_color::white) {
34,482✔
1597
            return this->vc_ansi_to_theme[lnav::enums::to_underlying(pal)];
84✔
1598
        }
1599
    }
1600

1601
    return ansi_fg;
112,390✔
1602
}
1603

1604
Result<screen_curses, std::string>
1605
screen_curses::create(const notcurses_options& options)
18✔
1606
{
1607
    auto* nc = notcurses_core_init(&options, stdout);
18✔
1608
    if (nc == nullptr) {
18✔
1609
        return Err(fmt::format(FMT_STRING("unable to initialize notcurses {}"),
×
1610
                               lnav::from_errno()));
×
1611
    }
1612

1613
    auto& mouse_i = injector::get<xterm_mouse&>();
18✔
1614
    mouse_i.set_enabled(
18✔
1615
        nc,
1616
        check_experimental("mouse")
18✔
1617
            || lnav_config.lc_mouse_mode == lnav_mouse_mode::enabled);
18✔
1618

1619
    auto_mem<char> term_name;
18✔
1620
    term_name = notcurses_detected_terminal(nc);
18✔
1621
    log_info("notcurses detected terminal: %s", term_name.in());
18✔
1622

1623
    auto retval = screen_curses(nc);
18✔
1624

1625
    tcgetattr(STDIN_FILENO, &retval.sc_termios);
18✔
1626
    retval.sc_termios.c_cc[VSTART] = 0;
18✔
1627
    retval.sc_termios.c_cc[VSTOP] = 0;
18✔
1628
#ifdef VDISCARD
1629
    retval.sc_termios.c_cc[VDISCARD] = 0;
18✔
1630
#endif
1631
#ifdef VDSUSP
1632
    retval.sc_termios.c_cc[VDSUSP] = 0;
1633
#endif
1634
    tcsetattr(STDIN_FILENO, TCSANOW, &retval.sc_termios);
18✔
1635

1636
    return Ok(std::move(retval));
18✔
1637
}
18✔
1638

1639
screen_curses::screen_curses(screen_curses&& other) noexcept
72✔
1640
    : sc_termios(other.sc_termios),
72✔
1641
      sc_notcurses(std::exchange(other.sc_notcurses, nullptr))
72✔
1642
{
1643
}
72✔
1644

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

1666
    return nullptr;
×
1667
}
1668
}
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