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

tstack / lnav / 17589970077-2502

09 Sep 2025 05:00PM UTC coverage: 65.196% (-5.0%) from 70.225%
17589970077-2502

push

github

tstack
[format] add fields for source file/line

Knowing the source file/line context in a log
message can help find log messages when using
log2src.

56 of 70 new or added lines in 2 files covered. (80.0%)

13954 existing lines in 210 files now uncovered.

45516 of 69814 relevant lines covered (65.2%)

404154.37 hits per line

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

75.4
/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()
60✔
64
{
65
    struct sigaction sa;
66

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

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

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

85
    return retval;
135✔
86
}
87

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

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

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

106
    return retval;
570✔
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)
6✔
131
        : uda_origin(utf_origin), uda_offset(offset)
6✔
132
    {
133
    }
6✔
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
UNCOV
145
mouse_event::is_click_in(mouse_button_t button, int x_start, int x_end) const
×
146
{
147
    return this->me_button == button
×
UNCOV
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
×
UNCOV
175
        && lr.contains(this->me_x) && this->me_y == this->me_press_y;
×
176
}
177

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

183
    this->vc_needs_update = false;
40✔
184

185
    if (!this->vc_visible) {
40✔
UNCOV
186
        return retval;
×
187
    }
188

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

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

UNCOV
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;
×
UNCOV
205
        if ((me.me_state == mouse_button_state_t::BUTTON_STATE_DRAGGED
×
UNCOV
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
        {
UNCOV
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;
×
UNCOV
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) {
×
UNCOV
217
                this->vc_last_drag_child = child;
×
218
            }
UNCOV
219
            return child->handle_mouse(sub_me);
×
220
        }
221
    }
UNCOV
222
    return false;
×
223
}
224

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

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

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

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

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

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

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

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

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

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

293
        if (char_index == lr_chars.lr_start) {
1,464✔
294
            lr_bytes.lr_start = exp_start_index;
506✔
295
        } else if (char_index == lr_chars.lr_end) {
958✔
296
            lr_bytes.lr_end = exp_start_index;
×
297
            retval.mr_chars_out = char_index;
×
298
        }
299

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

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

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

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

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

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

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

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

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

428
    text_attrs resolved_line_attrs[line_width_chars + 1];
80,100✔
429

430
    std::stable_sort(sa.begin(), sa.end());
506✔
431
    for (auto iter = sa.cbegin(); iter != sa.cend(); ++iter) {
599✔
432
        auto attr_range = iter->sa_range;
93✔
433

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

437
        if (!(iter->sa_type == &VC_ROLE || iter->sa_type == &VC_ROLE_FG
186✔
438
              || iter->sa_type == &VC_STYLE || iter->sa_type == &VC_GRAPHIC
93✔
UNCOV
439
              || iter->sa_type == &SA_LEVEL || iter->sa_type == &VC_FOREGROUND
×
UNCOV
440
              || iter->sa_type == &VC_BACKGROUND
×
UNCOV
441
              || iter->sa_type == &VC_BLOCK_ELEM || iter->sa_type == &VC_ICON))
×
442
        {
UNCOV
443
            continue;
×
444
        }
445

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

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

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

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

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

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

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

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

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

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

543
            if (graphic || !attrs.empty()) {
93✔
544
                if (attrs.ta_fg_color.cu_value.is<styling::semantic>()) {
77✔
545
                    attrs.ta_fg_color
546
                        = vc.color_for_ident(al.to_string_fragment(iter));
×
547
                }
548
                if (attrs.ta_bg_color.cu_value.is<styling::semantic>()) {
77✔
549
                    attrs.ta_bg_color
UNCOV
550
                        = vc.color_for_ident(al.to_string_fragment(iter));
×
551
                }
552

553
                for (auto lpc = attr_range.lr_start; lpc < attr_range.lr_end;
256✔
554
                     ++lpc)
555
                {
556
                    auto clear_rev = attrs.has_style(text_attrs::style::reverse)
179✔
557
                        && resolved_line_attrs[lpc].has_style(
179✔
558
                            text_attrs::style::reverse);
179✔
559
                    resolved_line_attrs[lpc] = attrs | resolved_line_attrs[lpc];
179✔
560
                    if (clear_rev) {
179✔
UNCOV
561
                        resolved_line_attrs[lpc].clear_style(
×
562
                            text_attrs::style::reverse);
563
                    }
564
                }
565
            }
566
        }
93✔
567
    }
568

569
    for (int lpc = 0; lpc < line_width_chars; lpc++) {
39,544✔
570
        auto cell_attrs = resolved_line_attrs[lpc] | base_attrs;
39,038✔
571

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

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

623
        if (fg_color[lpc] == -1) {
624
            fg_color[lpc] = cur_fg;
625
        }
626
        if (bg_color[lpc] == -1) {
627
            bg_color[lpc] = cur_bg;
628
        }
629
#endif
630
    }
39,038✔
631

632
    return retval;
1,012✔
633
}
1,012✔
634

635
view_colors&
636
view_colors::singleton()
146,659✔
637
{
638
    static view_colors s_vc;
146,659✔
639

640
    return s_vc;
146,659✔
641
}
642

643
view_colors::view_colors()
1,091✔
UNCOV
644
    : vc_ansi_to_theme{
×
UNCOV
645
          styling::color_unit::from_palette({0}),
×
646
          styling::color_unit::from_palette({1}),
1,091✔
647
          styling::color_unit::from_palette({2}),
1,091✔
648
          styling::color_unit::from_palette({3}),
1,091✔
649
          styling::color_unit::from_palette({4}),
1,091✔
650
          styling::color_unit::from_palette({5}),
1,091✔
651
          styling::color_unit::from_palette({6}),
1,091✔
652
          styling::color_unit::from_palette({7}),
1,091✔
653
      }
117,828✔
654
{
655
    auto text_default = text_attrs{};
1,091✔
656
    text_default.ta_fg_color = styling::color_unit::from_palette(COLOR_WHITE);
1,091✔
657
    text_default.ta_bg_color = styling::color_unit::from_palette(COLOR_BLACK);
1,091✔
658
    this->vc_role_attrs[lnav::enums::to_underlying(role_t::VCR_TEXT)]
659
        = role_attrs{text_default, text_default};
1,091✔
660
}
1,091✔
661

662
block_elem_t
UNCOV
663
view_colors::wchar_for_icon(ui_icon_t ic) const
×
664
{
UNCOV
665
    return this->vc_icons[lnav::enums::to_underlying(ic)];
×
666
}
667

668
bool view_colors::initialized = false;
669

670
static const std::string COLOR_NAMES[] = {
671
    "black",
672
    "red",
673
    "green",
674
    "yellow",
675
    "blue",
676
    "magenta",
677
    "cyan",
678
    "white",
679
};
680

681
class ui_listener : public lnav_config_listener {
682
public:
683
    ui_listener() : lnav_config_listener(__FILE__) {}
1,091✔
684

685
    void reload_config(error_reporter& reporter) override
1,186✔
686
    {
687
        if (!view_colors::initialized) {
1,186✔
688
            view_colors::vc_active_palette = ansi_colors();
585✔
689
        }
690

691
        auto& vc = view_colors::singleton();
1,186✔
692

693
        for (const auto& pair : lnav_config.lc_ui_theme_defs) {
11,717✔
694
            vc.init_roles(pair.second, reporter);
10,531✔
695
        }
696

697
        const auto iter
698
            = lnav_config.lc_ui_theme_defs.find(lnav_config.lc_ui_theme);
1,186✔
699

700
        if (iter == lnav_config.lc_ui_theme_defs.end()) {
1,186✔
701
            auto theme_names
702
                = lnav_config.lc_ui_theme_defs | lnav::itertools::first();
18✔
703

704
            reporter(&lnav_config.lc_ui_theme,
18✔
UNCOV
705
                     lnav::console::user_message::error(
×
706
                         attr_line_t("unknown theme -- ")
36✔
707
                             .append_quoted(lnav_config.lc_ui_theme))
36✔
708
                         .with_help(attr_line_t("The available themes are: ")
36✔
709
                                        .join(theme_names, ", ")));
18✔
710

711
            vc.init_roles(lnav_config.lc_ui_theme_defs["default"], reporter);
18✔
712
            return;
18✔
713
        }
18✔
714

715
        if (view_colors::initialized) {
1,168✔
716
            vc.init_roles(iter->second, reporter);
584✔
717
        }
718
    }
719
};
720

721
std::optional<lab_color>
722
view_colors::to_lab_color(const styling::color_unit& color)
22,266✔
723
{
724
    if (color.cu_value.is<palette_color>()) {
22,266✔
725
        auto pal_index = color.cu_value.get<palette_color>();
4,096✔
726
        if (pal_index < vc_active_palette->tc_palette.size()) {
4,096✔
727
            if (this->vc_notcurses != nullptr && pal_index == COLOR_BLACK) {
1,756✔
728
                // We use this as the default background, so try to get the
729
                // real default from the terminal.
UNCOV
730
                uint32_t chan = 0;
×
UNCOV
731
                notcurses_default_background(this->vc_notcurses, &chan);
×
732

UNCOV
733
                unsigned r = 0, g = 0, b = 0;
×
734

UNCOV
735
                ncchannel_rgb8(chan, &r, &g, &b);
×
UNCOV
736
                auto rgb = rgb_color{(short) r, (short) g, (short) b};
×
UNCOV
737
                return lab_color{rgb};
×
738
            }
739

740
            return vc_active_palette->tc_palette[pal_index].xc_lab_color;
1,756✔
741
        }
742
    } else if (color.cu_value.is<rgb_color>()) {
18,170✔
743
        auto rgb = color.cu_value.get<rgb_color>();
18,136✔
744

745
        return lab_color{rgb};
18,136✔
746
    }
747

748
    return std::nullopt;
2,374✔
749
}
750

751
uint64_t
752
view_colors::to_channels(const text_attrs& ta)
40,358✔
753
{
754
    uint64_t retval = 0;
40,358✔
755
    ta.ta_fg_color.cu_value.match(
40,358✔
UNCOV
756
        [&retval](styling::transparent) {
×
757
            ncchannels_set_fg_alpha(&retval, NCALPHA_TRANSPARENT);
40,281✔
758
        },
40,281✔
759
        [&retval](styling::semantic) {
×
UNCOV
760
            ncchannels_set_fg_alpha(&retval, NCALPHA_TRANSPARENT);
×
UNCOV
761
        },
×
UNCOV
762
        [&retval](const palette_color& pc) {
×
763
            if (pc == COLOR_WHITE) {
7✔
UNCOV
764
                ncchannels_set_fg_default(&retval);
×
765
            } else {
766
                ncchannels_set_fg_palindex(&retval, pc);
7✔
767
            }
768
        },
7✔
769
        [&retval](const rgb_color& rc) {
40,358✔
770
            ncchannels_set_fg_rgb8(&retval, rc.rc_r, rc.rc_g, rc.rc_b);
70✔
771
        });
70✔
772
    ta.ta_bg_color.cu_value.match(
40,358✔
UNCOV
773
        [&retval](styling::transparent) {
×
774
            ncchannels_set_bg_alpha(&retval, NCALPHA_TRANSPARENT);
40,281✔
775
        },
40,281✔
UNCOV
776
        [&retval](styling::semantic) {
×
UNCOV
777
            ncchannels_set_bg_alpha(&retval, NCALPHA_TRANSPARENT);
×
UNCOV
778
        },
×
UNCOV
779
        [&retval](const palette_color& pc) {
×
780
            if (pc == COLOR_BLACK) {
77✔
781
                ncchannels_set_bg_default(&retval);
7✔
782
            } else {
783
                ncchannels_set_bg_palindex(&retval, pc);
70✔
784
            }
785
        },
77✔
786
        [&retval](const rgb_color& rc) {
40,358✔
UNCOV
787
            ncchannels_set_bg_rgb8(&retval, rc.rc_r, rc.rc_g, rc.rc_b);
×
UNCOV
788
        });
×
789

790
    return retval;
40,358✔
791
}
792

793
static ui_listener _UI_LISTENER;
794
term_color_palette* view_colors::vc_active_palette;
795

796
void
797
view_colors::init(notcurses* nc)
579✔
798
{
799
    vc_active_palette = ansi_colors();
579✔
800
    if (nc != nullptr) {
579✔
801
        const auto* caps = notcurses_capabilities(nc);
16✔
802
        if (caps->rgb) {
16✔
UNCOV
803
            log_info("terminal supports RGB colors");
×
804
        } else {
805
            log_info("terminal supports %d colors", caps->colors);
16✔
806
        }
807
        if (caps->colors > 8) {
16✔
UNCOV
808
            vc_active_palette = xterm_colors();
×
809
        }
810
    }
811

812
    singleton().vc_notcurses = nc;
579✔
813
    initialized = true;
579✔
814

815
    {
816
        auto reporter
817
            = [](const void*, const lnav::console::user_message& um) {};
16✔
818

819
        _UI_LISTENER.reload_config(reporter);
579✔
820
    }
821
}
579✔
822

823
styling::color_unit
824
view_colors::match_color(styling::color_unit cu) const
2,267,163✔
825
{
826
    if (this->vc_notcurses == nullptr) {
2,267,163✔
827
        return cu;
2,264,251✔
828
    }
829

830
    const auto* caps = notcurses_capabilities(this->vc_notcurses);
2,912✔
831

832
    if (caps->rgb) {
2,912✔
UNCOV
833
        return cu;
×
834
    }
835

836
    if (cu.cu_value.is<rgb_color>()) {
2,912✔
UNCOV
837
        auto rgb = cu.cu_value.get<rgb_color>();
×
UNCOV
838
        auto lab = lab_color{rgb};
×
UNCOV
839
        auto pal = vc_active_palette->match_color(lab);
×
840

UNCOV
841
        log_trace("mapped RGB (%d, %d, %d) to palette %d",
×
842
                  rgb.rc_r,
843
                  rgb.rc_g,
844
                  rgb.rc_b,
845
                  pal);
UNCOV
846
        return styling::color_unit::from_palette(palette_color{pal});
×
847
    }
848

849
    return cu;
2,912✔
850
}
851

852
view_colors::role_attrs
853
view_colors::to_attrs(const lnav_theme& lt,
1,026,299✔
854
                      const positioned_property<style_config>& pp_sc,
855
                      lnav_config_listener::error_reporter& reporter)
856
{
857
    const auto& sc = pp_sc.pp_value;
1,026,299✔
858
    std::string fg_color, bg_color;
1,026,299✔
859
    intern_string_t role_class;
1,026,299✔
860

861
    if (pp_sc.pp_path.empty()) {
1,026,299✔
862
#if 0
863
        // too slow to do this now
864
        reporter(&sc.sc_color, lnav::console::user_message::warning(""));
865
#endif
866
    } else if (!pp_sc.pp_path.empty()) {
891,625✔
867
        auto role_class_path = pp_sc.pp_path.to_string_fragment();
891,625✔
868
        auto inner
869
            = role_class_path.rsplit_pair(string_fragment::tag1{'/'}).value();
891,625✔
870
        auto outer
871
            = inner.first.rsplit_pair(string_fragment::tag1{'/'}).value();
891,625✔
872

873
        role_class = intern_string::lookup(
891,625✔
874
            fmt::format(FMT_STRING("-lnav_{}_{}"), outer.second, inner.second));
4,458,125✔
875
    }
876

877
    auto fg1 = sc.sc_color;
1,026,299✔
878
    auto bg1 = sc.sc_background_color;
1,026,299✔
879
    shlex(fg1).eval(fg_color, scoped_resolver{&lt.lt_vars});
1,026,299✔
880
    shlex(bg1).eval(bg_color, scoped_resolver{&lt.lt_vars});
1,026,299✔
881

882
    auto fg = styling::color_unit::from_str(fg_color).unwrapOrElse(
2,052,598✔
883
        [&](const auto& msg) {
3✔
884
            reporter(
6✔
885
                &sc.sc_color,
3✔
886
                lnav::console::user_message::error(
887
                    attr_line_t("invalid color -- ").append_quoted(sc.sc_color))
6✔
888
                    .with_reason(msg));
3✔
889
            return styling::color_unit::EMPTY;
3✔
890
        });
1,026,299✔
891
    auto bg = styling::color_unit::from_str(bg_color).unwrapOrElse(
2,052,598✔
UNCOV
892
        [&](const auto& msg) {
×
UNCOV
893
            reporter(&sc.sc_background_color,
×
894
                     lnav::console::user_message::error(
UNCOV
895
                         attr_line_t("invalid background color -- ")
×
UNCOV
896
                             .append_quoted(sc.sc_background_color))
×
UNCOV
897
                         .with_reason(msg));
×
UNCOV
898
            return styling::color_unit::EMPTY;
×
899
        });
1,026,299✔
900

901
    fg = this->match_color(fg);
1,026,299✔
902
    bg = this->match_color(bg);
1,026,299✔
903

904
    auto retval1 = text_attrs{0, fg, bg};
1,026,299✔
905
    text_attrs retval2;
1,026,299✔
906

907
    if (sc.sc_underline) {
1,026,299✔
908
        retval1 |= text_attrs::style::underline;
80,740✔
909
        retval2 |= text_attrs::style::underline;
80,740✔
910
    }
911
    if (sc.sc_bold) {
1,026,299✔
912
        retval1 |= text_attrs::style::bold;
194,236✔
913
        retval2 |= text_attrs::style::bold;
194,236✔
914
    }
915
    if (sc.sc_italic) {
1,026,299✔
916
        retval1 |= text_attrs::style::italic;
16,380✔
917
        retval2 |= text_attrs::style::italic;
16,380✔
918
    }
919
    if (sc.sc_strike) {
1,026,299✔
UNCOV
920
        retval1 |= text_attrs::style::struck;
×
UNCOV
921
        retval2 |= text_attrs::style::struck;
×
922
    }
923

924
    return {retval1, retval2, role_class};
1,026,299✔
925
}
1,026,299✔
926

927
void
928
view_colors::init_roles(const lnav_theme& lt,
11,133✔
929
                        lnav_config_listener::error_reporter& reporter)
930
{
931
    const auto& default_theme = lnav_config.lc_ui_theme_defs["default"];
11,133✔
932
    std::string err;
11,133✔
933

934
    size_t icon_index = 0;
11,133✔
935
    for (const auto& ic : {
11,133✔
936
             lt.lt_icon_hidden,
11,133✔
937
             lt.lt_icon_ok,
11,133✔
938
             lt.lt_icon_info,
11,133✔
939
             lt.lt_icon_warning,
11,133✔
940
             lt.lt_icon_error,
11,133✔
941

942
             lt.lt_icon_log_level_trace,
11,133✔
943
             lt.lt_icon_log_level_debug,
11,133✔
944
             lt.lt_icon_log_level_info,
11,133✔
945
             lt.lt_icon_log_level_stats,
11,133✔
946
             lt.lt_icon_log_level_notice,
11,133✔
947
             lt.lt_icon_log_level_warning,
11,133✔
948
             lt.lt_icon_log_level_error,
11,133✔
949
             lt.lt_icon_log_level_critical,
11,133✔
950
             lt.lt_icon_log_level_fatal,
11,133✔
951

952
             lt.lt_icon_play,
11,133✔
953
             lt.lt_icon_edit,
11,133✔
954
         })
378,522✔
955
    {
956
        size_t index = 0;
178,128✔
957
        if (ic.pp_value.ic_value) {
178,128✔
UNCOV
958
            auto read_res = ww898::utf::utf8::read([&ic, &index]() {
×
959
                return ic.pp_value.ic_value.value()[index++];
100,092✔
960
            });
28,096✔
961
            if (read_res.isErr()) {
28,096✔
UNCOV
962
                reporter(&ic,
×
UNCOV
963
                         lnav::console::user_message::error(
×
964
                             "icon is not valid UTF-8"));
965
            } else if (read_res.unwrap() != 0) {
28,096✔
966
                role_t icon_role;
967
                switch (static_cast<ui_icon_t>(icon_index)) {
28,096✔
968
                    case ui_icon_t::hidden:
1,756✔
969
                        icon_role = role_t::VCR_HIDDEN;
1,756✔
970
                        break;
1,756✔
971
                    case ui_icon_t::ok:
1,756✔
972
                        icon_role = role_t::VCR_OK;
1,756✔
973
                        break;
1,756✔
974
                    case ui_icon_t::info:
1,756✔
975
                        icon_role = role_t::VCR_INFO;
1,756✔
976
                        break;
1,756✔
977
                    case ui_icon_t::warning:
3,512✔
978
                    case ui_icon_t::log_level_warning:
979
                        icon_role = role_t::VCR_WARNING;
3,512✔
980
                        break;
3,512✔
981
                    case ui_icon_t::error:
7,024✔
982
                    case ui_icon_t::log_level_error:
983
                    case ui_icon_t::log_level_fatal:
984
                    case ui_icon_t::log_level_critical:
985
                        icon_role = role_t::VCR_ERROR;
7,024✔
986
                        break;
7,024✔
987
                    case ui_icon_t::play:
1,756✔
988
                        icon_role = role_t::VCR_OK;
1,756✔
989
                        break;
1,756✔
990
                    default:
10,536✔
991
                        icon_role = role_t::VCR_TEXT;
10,536✔
992
                        break;
10,536✔
993
                }
994
                this->vc_icons[icon_index]
995
                    = block_elem_t{(char32_t) read_res.unwrap(), icon_role};
28,096✔
996
            }
997
        }
28,096✔
998
        icon_index += 1;
178,128✔
999
    }
189,261✔
1000

1001
    /* Setup the mappings from roles to actual colors. */
1002
    this->get_role_attrs(role_t::VCR_TEXT)
11,133✔
1003
        = this->to_attrs(lt, lt.lt_style_text, reporter);
22,266✔
1004

1005
    for (int ansi_fg = 1; ansi_fg < 8; ansi_fg++) {
89,064✔
1006
        auto fg_iter = lt.lt_vars.find(COLOR_NAMES[ansi_fg]);
77,931✔
1007
        auto fg_str = fg_iter == lt.lt_vars.end()
77,931✔
1008
            ? ""
77,931✔
1009
            : fmt::to_string(fg_iter->second);
92,682✔
1010

1011
        auto rgb_fg = from<rgb_color>(string_fragment::from_str(fg_str))
155,862✔
1012
                          .unwrapOrElse([&](const auto& msg) {
77,931✔
UNCOV
1013
                              reporter(&fg_str,
×
1014
                                       lnav::console::user_message::error(
UNCOV
1015
                                           attr_line_t("invalid color -- ")
×
UNCOV
1016
                                               .append_quoted(fg_str))
×
UNCOV
1017
                                           .with_reason(msg));
×
UNCOV
1018
                              return rgb_color{};
×
1019
                          });
1020

1021
        auto fg = vc_active_palette->match_color(lab_color(rgb_fg));
77,931✔
1022

1023
        if (rgb_fg.empty()) {
77,931✔
1024
            fg = ansi_fg;
14,751✔
1025
        }
1026

1027
        this->vc_ansi_to_theme[ansi_fg] = palette_color{fg};
77,931✔
1028
    }
77,931✔
1029

1030
#if 0
1031
    if (lnav_config.lc_ui_dim_text) {
1032
        this->get_role_attrs(role_t::VCR_TEXT).ra_normal.ta_attrs |= A_DIM;
1033
        this->get_role_attrs(role_t::VCR_TEXT).ra_reverse.ta_attrs |= A_DIM;
1034
    }
1035
#endif
1036
    this->get_role_attrs(role_t::VCR_SEARCH)
11,133✔
1037
        = role_attrs{text_attrs::with_reverse(), text_attrs::with_reverse()};
11,133✔
1038
    this->get_role_attrs(role_t::VCR_SEARCH).ra_class_name
11,133✔
1039
        = intern_string::lookup("-lnav_styles_search");
22,266✔
1040
    this->get_role_attrs(role_t::VCR_IDENTIFIER)
11,133✔
1041
        = this->to_attrs(lt, lt.lt_style_identifier, reporter);
22,266✔
1042
    this->get_role_attrs(role_t::VCR_OK)
11,133✔
1043
        = this->to_attrs(lt, lt.lt_style_ok, reporter);
22,266✔
1044
    this->get_role_attrs(role_t::VCR_INFO)
11,133✔
1045
        = this->to_attrs(lt, lt.lt_style_info, reporter);
22,266✔
1046
    this->get_role_attrs(role_t::VCR_ERROR)
11,133✔
1047
        = this->to_attrs(lt, lt.lt_style_error, reporter);
22,266✔
1048
    this->get_role_attrs(role_t::VCR_WARNING)
11,133✔
1049
        = this->to_attrs(lt, lt.lt_style_warning, reporter);
22,266✔
1050
    this->get_role_attrs(role_t::VCR_ALT_ROW)
11,133✔
1051
        = this->to_attrs(lt, lt.lt_style_alt_text, reporter);
22,266✔
1052
    this->get_role_attrs(role_t::VCR_HIDDEN)
11,133✔
1053
        = this->to_attrs(lt, lt.lt_style_hidden, reporter);
22,266✔
1054
    this->get_role_attrs(role_t::VCR_CURSOR_LINE)
11,133✔
1055
        = this->to_attrs(lt, lt.lt_style_cursor_line, reporter);
22,266✔
1056
    if (this->get_role_attrs(role_t::VCR_CURSOR_LINE).ra_normal.empty()) {
11,133✔
1057
        this->get_role_attrs(role_t::VCR_CURSOR_LINE) = this->to_attrs(
3,546✔
1058
            default_theme, default_theme.lt_style_cursor_line, reporter);
3,546✔
1059
    }
1060
    this->get_role_attrs(role_t::VCR_DISABLED_CURSOR_LINE)
11,133✔
1061
        = this->to_attrs(lt, lt.lt_style_disabled_cursor_line, reporter);
22,266✔
1062
    if (this->get_role_attrs(role_t::VCR_DISABLED_CURSOR_LINE)
11,133✔
1063
            .ra_normal.empty())
11,133✔
1064
    {
1065
        this->get_role_attrs(role_t::VCR_DISABLED_CURSOR_LINE)
17✔
1066
            = this->to_attrs(default_theme,
17✔
1067
                             default_theme.lt_style_disabled_cursor_line,
17✔
1068
                             reporter);
17✔
1069
    }
1070
    this->get_role_attrs(role_t::VCR_ADJUSTED_TIME)
11,133✔
1071
        = this->to_attrs(lt, lt.lt_style_adjusted_time, reporter);
22,266✔
1072
    this->get_role_attrs(role_t::VCR_SKEWED_TIME)
11,133✔
1073
        = this->to_attrs(lt, lt.lt_style_skewed_time, reporter);
22,266✔
1074
    this->get_role_attrs(role_t::VCR_OFFSET_TIME)
11,133✔
1075
        = this->to_attrs(lt, lt.lt_style_offset_time, reporter);
22,266✔
1076
    this->get_role_attrs(role_t::VCR_TIME_COLUMN)
11,133✔
1077
        = this->to_attrs(lt, lt.lt_style_time_column, reporter);
22,266✔
1078
    this->get_role_attrs(role_t::VCR_POPUP)
11,133✔
1079
        = this->to_attrs(lt, lt.lt_style_popup, reporter);
22,266✔
1080
    this->get_role_attrs(role_t::VCR_POPUP_BORDER)
11,133✔
1081
        = this->to_attrs(lt, lt.lt_style_popup_border, reporter);
22,266✔
1082
    this->get_role_attrs(role_t::VCR_INLINE_CODE)
11,133✔
1083
        = this->to_attrs(lt, lt.lt_style_inline_code, reporter);
22,266✔
1084
    this->get_role_attrs(role_t::VCR_QUOTED_CODE)
11,133✔
1085
        = this->to_attrs(lt, lt.lt_style_quoted_code, reporter);
22,266✔
1086
    this->get_role_attrs(role_t::VCR_CODE_BORDER)
11,133✔
1087
        = this->to_attrs(lt, lt.lt_style_code_border, reporter);
22,266✔
1088

1089
    {
1090
        auto& time_to_text
1091
            = this->get_role_attrs(role_t::VCR_TIME_COLUMN_TO_TEXT);
11,133✔
1092
        auto time_attrs = this->attrs_for_role(role_t::VCR_TIME_COLUMN);
11,133✔
1093
        auto text_attrs = this->attrs_for_role(role_t::VCR_TEXT);
11,133✔
1094
        time_to_text.ra_class_name.clear();
11,133✔
1095

1096
        time_to_text.ra_normal.ta_fg_color = time_attrs.ta_bg_color;
11,133✔
1097
        time_to_text.ra_normal.ta_bg_color = text_attrs.ta_bg_color;
11,133✔
1098

1099
        auto fg_as_lab_opt = to_lab_color(time_attrs.ta_bg_color);
11,133✔
1100
        auto bg_as_lab_opt = to_lab_color(text_attrs.ta_bg_color);
11,133✔
1101
        if (fg_as_lab_opt && bg_as_lab_opt) {
11,133✔
1102
            auto fg_as_lab = fg_as_lab_opt.value();
9,946✔
1103
            auto bg_as_lab = bg_as_lab_opt.value();
9,946✔
1104
            auto diff = fg_as_lab.lc_l - bg_as_lab.lc_l;
9,946✔
1105
            fg_as_lab.lc_l -= diff / 4.0;
9,946✔
1106
            bg_as_lab.lc_l += diff / 4.0;
9,946✔
1107

1108
            time_to_text.ra_normal.ta_fg_color = this->match_color(
19,892✔
1109
                styling::color_unit::from_rgb(fg_as_lab.to_rgb()));
29,838✔
1110
            time_to_text.ra_normal.ta_bg_color = this->match_color(
19,892✔
1111
                styling::color_unit::from_rgb(bg_as_lab.to_rgb()));
29,838✔
1112
        }
1113

1114
        if (bg_as_lab_opt) {
11,133✔
1115
            auto bg_as_lab = bg_as_lab_opt.value();
9,946✔
1116
            std::vector<std::pair<double, size_t>> contrasting;
9,946✔
1117
            for (const auto& [index, tcolor] :
89,514✔
1118
                 lnav::itertools::enumerate(vc_active_palette->tc_palette))
99,460✔
1119
            {
1120
                if (index < 16) {
79,568✔
1121
                    continue;
79,568✔
1122
                }
UNCOV
1123
                if (bg_as_lab.sufficient_contrast(tcolor.xc_lab_color)) {
×
UNCOV
1124
                    contrasting.emplace_back(
×
UNCOV
1125
                        bg_as_lab.deltaE(tcolor.xc_lab_color), index);
×
1126
                }
1127
            }
1128

1129
            log_info("found %zu contrasting colors for highlights",
9,946✔
1130
                     contrasting.size());
1131
            if (contrasting.empty()) {
9,946✔
1132
                for (auto lpc = size_t{0}; lpc < HI_COLOR_COUNT; lpc++) {
547,030✔
1133
                    this->vc_highlight_colors[lpc] = 16;
537,084✔
1134
                }
1135
            } else {
UNCOV
1136
                std::stable_sort(
×
1137
                    contrasting.begin(), contrasting.end(), std::greater{});
UNCOV
1138
                for (auto lpc = size_t{0}; lpc < HI_COLOR_COUNT; lpc++) {
×
1139
                    this->vc_highlight_colors[lpc]
UNCOV
1140
                        = contrasting[lpc % contrasting.size()].second;
×
1141
                }
1142
            }
1143
            if (lt.lt_style_cursor_line.pp_value.sc_background_color.empty()) {
9,946✔
1144
                auto adjusted_cursor = bg_as_lab;
1,756✔
1145
                if (adjusted_cursor.lc_l < 50) {
1,756✔
1146
                    adjusted_cursor.lc_l += 18;
1,756✔
1147
                } else {
UNCOV
1148
                    adjusted_cursor.lc_l -= 15;
×
1149
                }
1150
                auto new_cursor_bg = this->match_color(
1151
                    styling::color_unit::from_rgb(adjusted_cursor.to_rgb()));
1,756✔
1152
                this->get_role_attrs(role_t::VCR_CURSOR_LINE)
1,756✔
1153
                    .ra_normal.ta_bg_color
1154
                    = new_cursor_bg;
1,756✔
1155
            }
1,756✔
1156
            if (lt.lt_style_popup.pp_value.sc_background_color.empty()) {
9,946✔
1157
                auto adjusted_cursor = bg_as_lab;
1,756✔
1158
                if (adjusted_cursor.lc_l < 50) {
1,756✔
1159
                    adjusted_cursor.lc_l += 30;
1,756✔
1160
                } else {
UNCOV
1161
                    adjusted_cursor.lc_l -= 30;
×
1162
                }
1163
                auto new_cursor_bg = this->match_color(
1164
                    styling::color_unit::from_rgb(adjusted_cursor.to_rgb()));
1,756✔
1165
                this->get_role_attrs(role_t::VCR_POPUP).ra_normal.ta_bg_color
1,756✔
1166
                    = new_cursor_bg;
1,756✔
1167
            }
1,756✔
1168
            if (lt.lt_style_inline_code.pp_value.sc_background_color.empty()) {
9,946✔
1169
                auto adjusted_cursor = bg_as_lab;
2,926✔
1170
                if (adjusted_cursor.lc_l < 50) {
2,926✔
1171
                    adjusted_cursor.lc_l = 10;
2,926✔
1172
                } else {
UNCOV
1173
                    adjusted_cursor.lc_l -= 25;
×
1174
                }
1175
                auto new_cursor_bg = this->match_color(
1176
                    styling::color_unit::from_rgb(adjusted_cursor.to_rgb()));
2,926✔
1177
                this->get_role_attrs(role_t::VCR_INLINE_CODE)
2,926✔
1178
                    .ra_normal.ta_bg_color
1179
                    = new_cursor_bg;
2,926✔
1180
            }
2,926✔
1181
            if (lt.lt_style_quoted_code.pp_value.sc_background_color.empty()) {
9,946✔
1182
                auto adjusted_cursor = bg_as_lab;
2,926✔
1183
                if (adjusted_cursor.lc_l < 50) {
2,926✔
1184
                    adjusted_cursor.lc_l = 10;
2,926✔
1185
                } else {
UNCOV
1186
                    adjusted_cursor.lc_l -= 25;
×
1187
                }
1188
                auto new_cursor_bg = this->match_color(
1189
                    styling::color_unit::from_rgb(adjusted_cursor.to_rgb()));
2,926✔
1190
                this->get_role_attrs(role_t::VCR_QUOTED_CODE)
2,926✔
1191
                    .ra_normal.ta_bg_color
1192
                    = new_cursor_bg;
2,926✔
1193
                this->get_role_attrs(role_t::VCR_CODE_BORDER)
2,926✔
1194
                    .ra_normal.ta_bg_color
1195
                    = new_cursor_bg;
2,926✔
1196
            }
2,926✔
1197
        }
9,946✔
1198
    }
11,133✔
1199
    this->get_role_attrs(role_t::VCR_FILE_OFFSET)
11,133✔
1200
        = this->to_attrs(lt, lt.lt_style_file_offset, reporter);
22,266✔
1201
    this->get_role_attrs(role_t::VCR_INVALID_MSG)
11,133✔
1202
        = this->to_attrs(lt, lt.lt_style_invalid_msg, reporter);
22,266✔
1203

1204
    this->get_role_attrs(role_t::VCR_STATUS)
11,133✔
1205
        = this->to_attrs(lt, lt.lt_style_status, reporter);
22,266✔
1206
    this->get_role_attrs(role_t::VCR_WARN_STATUS)
11,133✔
1207
        = this->to_attrs(lt, lt.lt_style_warn_status, reporter);
22,266✔
1208
    this->get_role_attrs(role_t::VCR_ALERT_STATUS)
11,133✔
1209
        = this->to_attrs(lt, lt.lt_style_alert_status, reporter);
22,266✔
1210
    this->get_role_attrs(role_t::VCR_ACTIVE_STATUS)
11,133✔
1211
        = this->to_attrs(lt, lt.lt_style_active_status, reporter);
22,266✔
1212
    this->get_role_attrs(role_t::VCR_ACTIVE_STATUS2) = role_attrs{
22,266✔
1213
        this->get_role_attrs(role_t::VCR_ACTIVE_STATUS).ra_normal,
11,133✔
1214
        this->get_role_attrs(role_t::VCR_ACTIVE_STATUS).ra_reverse,
11,133✔
1215
    };
11,133✔
1216
    this->get_role_attrs(role_t::VCR_ACTIVE_STATUS2).ra_normal
11,133✔
1217
        |= text_attrs::style::bold;
11,133✔
1218
    this->get_role_attrs(role_t::VCR_ACTIVE_STATUS2).ra_reverse
11,133✔
1219
        |= text_attrs::style::bold;
11,133✔
1220
    this->get_role_attrs(role_t::VCR_STATUS_TITLE)
11,133✔
1221
        = this->to_attrs(lt, lt.lt_style_status_title, reporter);
22,266✔
1222
    this->get_role_attrs(role_t::VCR_STATUS_SUBTITLE)
11,133✔
1223
        = this->to_attrs(lt, lt.lt_style_status_subtitle, reporter);
22,266✔
1224
    this->get_role_attrs(role_t::VCR_STATUS_INFO)
11,133✔
1225
        = this->to_attrs(lt, lt.lt_style_status_info, reporter);
22,266✔
1226

1227
    this->get_role_attrs(role_t::VCR_STATUS_HOTKEY)
11,133✔
1228
        = this->to_attrs(lt, lt.lt_style_status_hotkey, reporter);
22,266✔
1229
    this->get_role_attrs(role_t::VCR_STATUS_TITLE_HOTKEY)
11,133✔
1230
        = this->to_attrs(lt, lt.lt_style_status_title_hotkey, reporter);
22,266✔
1231
    this->get_role_attrs(role_t::VCR_STATUS_DISABLED_TITLE)
11,133✔
1232
        = this->to_attrs(lt, lt.lt_style_status_disabled_title, reporter);
22,266✔
1233

1234
    this->get_role_attrs(role_t::VCR_H1)
11,133✔
1235
        = this->to_attrs(lt, lt.lt_style_header[0], reporter);
22,266✔
1236
    this->get_role_attrs(role_t::VCR_H2)
11,133✔
1237
        = this->to_attrs(lt, lt.lt_style_header[1], reporter);
22,266✔
1238
    this->get_role_attrs(role_t::VCR_H3)
11,133✔
1239
        = this->to_attrs(lt, lt.lt_style_header[2], reporter);
22,266✔
1240
    this->get_role_attrs(role_t::VCR_H4)
11,133✔
1241
        = this->to_attrs(lt, lt.lt_style_header[3], reporter);
22,266✔
1242
    this->get_role_attrs(role_t::VCR_H5)
11,133✔
1243
        = this->to_attrs(lt, lt.lt_style_header[4], reporter);
22,266✔
1244
    this->get_role_attrs(role_t::VCR_H6)
11,133✔
1245
        = this->to_attrs(lt, lt.lt_style_header[5], reporter);
22,266✔
1246
    this->get_role_attrs(role_t::VCR_HR)
11,133✔
1247
        = this->to_attrs(lt, lt.lt_style_hr, reporter);
22,266✔
1248
    this->get_role_attrs(role_t::VCR_HYPERLINK)
11,133✔
1249
        = this->to_attrs(lt, lt.lt_style_hyperlink, reporter);
22,266✔
1250
    this->get_role_attrs(role_t::VCR_LIST_GLYPH)
11,133✔
1251
        = this->to_attrs(lt, lt.lt_style_list_glyph, reporter);
22,266✔
1252
    this->get_role_attrs(role_t::VCR_BREADCRUMB)
11,133✔
1253
        = this->to_attrs(lt, lt.lt_style_breadcrumb, reporter);
22,266✔
1254
    this->get_role_attrs(role_t::VCR_TABLE_BORDER)
11,133✔
1255
        = this->to_attrs(lt, lt.lt_style_table_border, reporter);
22,266✔
1256
    this->get_role_attrs(role_t::VCR_TABLE_HEADER)
11,133✔
1257
        = this->to_attrs(lt, lt.lt_style_table_header, reporter);
22,266✔
1258
    this->get_role_attrs(role_t::VCR_QUOTE_BORDER)
11,133✔
1259
        = this->to_attrs(lt, lt.lt_style_quote_border, reporter);
22,266✔
1260
    this->get_role_attrs(role_t::VCR_QUOTED_TEXT)
11,133✔
1261
        = this->to_attrs(lt, lt.lt_style_quoted_text, reporter);
22,266✔
1262
    this->get_role_attrs(role_t::VCR_FOOTNOTE_BORDER)
11,133✔
1263
        = this->to_attrs(lt, lt.lt_style_footnote_border, reporter);
22,266✔
1264
    this->get_role_attrs(role_t::VCR_FOOTNOTE_TEXT)
11,133✔
1265
        = this->to_attrs(lt, lt.lt_style_footnote_text, reporter);
22,266✔
1266
    this->get_role_attrs(role_t::VCR_SNIPPET_BORDER)
11,133✔
1267
        = this->to_attrs(lt, lt.lt_style_snippet_border, reporter);
22,266✔
1268
    this->get_role_attrs(role_t::VCR_INDENT_GUIDE)
11,133✔
1269
        = this->to_attrs(lt, lt.lt_style_indent_guide, reporter);
22,266✔
1270

1271
    {
1272
        positioned_property<style_config> stitch_sc;
11,133✔
1273

1274
        stitch_sc.pp_value.sc_color
1275
            = lt.lt_style_status_subtitle.pp_value.sc_background_color;
11,133✔
1276
        stitch_sc.pp_value.sc_background_color
1277
            = lt.lt_style_status_title.pp_value.sc_background_color;
11,133✔
1278
        this->get_role_attrs(role_t::VCR_STATUS_STITCH_TITLE_TO_SUB)
11,133✔
1279
            = this->to_attrs(lt, stitch_sc, reporter);
22,266✔
1280
    }
11,133✔
1281
    {
1282
        positioned_property<style_config> stitch_sc;
11,133✔
1283

1284
        stitch_sc.pp_value.sc_color
1285
            = lt.lt_style_status_title.pp_value.sc_background_color;
11,133✔
1286
        stitch_sc.pp_value.sc_background_color
1287
            = lt.lt_style_status_subtitle.pp_value.sc_background_color;
11,133✔
1288
        this->get_role_attrs(role_t::VCR_STATUS_STITCH_SUB_TO_TITLE)
11,133✔
1289
            = this->to_attrs(lt, stitch_sc, reporter);
22,266✔
1290
    }
11,133✔
1291

1292
    {
1293
        positioned_property<style_config> stitch_sc;
11,133✔
1294

1295
        stitch_sc.pp_value.sc_color
1296
            = lt.lt_style_status.pp_value.sc_background_color;
11,133✔
1297
        stitch_sc.pp_value.sc_background_color
1298
            = lt.lt_style_status_subtitle.pp_value.sc_background_color;
11,133✔
1299
        this->get_role_attrs(role_t::VCR_STATUS_STITCH_SUB_TO_NORMAL)
11,133✔
1300
            = this->to_attrs(lt, stitch_sc, reporter);
22,266✔
1301
    }
11,133✔
1302
    {
1303
        positioned_property<style_config> stitch_sc;
11,133✔
1304

1305
        stitch_sc.pp_value.sc_color
1306
            = lt.lt_style_status_subtitle.pp_value.sc_background_color;
11,133✔
1307
        stitch_sc.pp_value.sc_background_color
1308
            = lt.lt_style_status.pp_value.sc_background_color;
11,133✔
1309
        this->get_role_attrs(role_t::VCR_STATUS_STITCH_NORMAL_TO_SUB)
11,133✔
1310
            = this->to_attrs(lt, stitch_sc, reporter);
22,266✔
1311
    }
11,133✔
1312

1313
    {
1314
        positioned_property<style_config> stitch_sc;
11,133✔
1315

1316
        stitch_sc.pp_value.sc_color
1317
            = lt.lt_style_status.pp_value.sc_background_color;
11,133✔
1318
        stitch_sc.pp_value.sc_background_color
1319
            = lt.lt_style_status_title.pp_value.sc_background_color;
11,133✔
1320
        this->get_role_attrs(role_t::VCR_STATUS_STITCH_TITLE_TO_NORMAL)
11,133✔
1321
            = this->to_attrs(lt, stitch_sc, reporter);
22,266✔
1322
    }
11,133✔
1323
    {
1324
        positioned_property<style_config> stitch_sc;
11,133✔
1325

1326
        stitch_sc.pp_value.sc_color
1327
            = lt.lt_style_status_title.pp_value.sc_background_color;
11,133✔
1328
        stitch_sc.pp_value.sc_background_color
1329
            = lt.lt_style_status.pp_value.sc_background_color;
11,133✔
1330
        this->get_role_attrs(role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE)
11,133✔
1331
            = this->to_attrs(lt, stitch_sc, reporter);
22,266✔
1332
    }
11,133✔
1333

1334
    this->get_role_attrs(role_t::VCR_INACTIVE_STATUS)
11,133✔
1335
        = this->to_attrs(lt, lt.lt_style_inactive_status, reporter);
22,266✔
1336
    this->get_role_attrs(role_t::VCR_INACTIVE_ALERT_STATUS)
11,133✔
1337
        = this->to_attrs(lt, lt.lt_style_inactive_alert_status, reporter);
22,266✔
1338

1339
    this->get_role_attrs(role_t::VCR_FOCUSED)
11,133✔
1340
        = this->to_attrs(lt, lt.lt_style_focused, reporter);
22,266✔
1341
    this->get_role_attrs(role_t::VCR_DISABLED_FOCUSED)
11,133✔
1342
        = this->to_attrs(lt, lt.lt_style_disabled_focused, reporter);
22,266✔
1343
    this->get_role_attrs(role_t::VCR_SCROLLBAR)
11,133✔
1344
        = this->to_attrs(lt, lt.lt_style_scrollbar, reporter);
22,266✔
1345
    {
1346
        positioned_property<style_config> bar_sc;
11,133✔
1347

1348
        bar_sc.pp_value.sc_color = lt.lt_style_error.pp_value.sc_color;
11,133✔
1349
        bar_sc.pp_value.sc_background_color
1350
            = lt.lt_style_scrollbar.pp_value.sc_background_color;
11,133✔
1351
        this->get_role_attrs(role_t::VCR_SCROLLBAR_ERROR)
11,133✔
1352
            = this->to_attrs(lt, bar_sc, reporter);
22,266✔
1353
    }
11,133✔
1354
    {
1355
        positioned_property<style_config> bar_sc;
11,133✔
1356

1357
        bar_sc.pp_value.sc_color = lt.lt_style_warning.pp_value.sc_color;
11,133✔
1358
        bar_sc.pp_value.sc_background_color
1359
            = lt.lt_style_scrollbar.pp_value.sc_background_color;
11,133✔
1360
        this->get_role_attrs(role_t::VCR_SCROLLBAR_WARNING)
11,133✔
1361
            = this->to_attrs(lt, bar_sc, reporter);
22,266✔
1362
    }
11,133✔
1363

1364
    this->get_role_attrs(role_t::VCR_OBJECT_KEY)
11,133✔
1365
        = this->to_attrs(lt, lt.lt_style_object_key, reporter);
22,266✔
1366
    this->get_role_attrs(role_t::VCR_KEYWORD)
11,133✔
1367
        = this->to_attrs(lt, lt.lt_style_keyword, reporter);
22,266✔
1368
    this->get_role_attrs(role_t::VCR_STRING)
11,133✔
1369
        = this->to_attrs(lt, lt.lt_style_string, reporter);
22,266✔
1370
    this->get_role_attrs(role_t::VCR_COMMENT)
11,133✔
1371
        = this->to_attrs(lt, lt.lt_style_comment, reporter);
22,266✔
1372
    this->get_role_attrs(role_t::VCR_DOC_DIRECTIVE)
11,133✔
1373
        = this->to_attrs(lt, lt.lt_style_doc_directive, reporter);
22,266✔
1374
    this->get_role_attrs(role_t::VCR_VARIABLE)
11,133✔
1375
        = this->to_attrs(lt, lt.lt_style_variable, reporter);
22,266✔
1376
    this->get_role_attrs(role_t::VCR_SYMBOL)
11,133✔
1377
        = this->to_attrs(lt, lt.lt_style_symbol, reporter);
22,266✔
1378
    this->get_role_attrs(role_t::VCR_NULL)
11,133✔
1379
        = this->to_attrs(lt, lt.lt_style_null, reporter);
22,266✔
1380
    this->get_role_attrs(role_t::VCR_ASCII_CTRL)
11,133✔
1381
        = this->to_attrs(lt, lt.lt_style_ascii_ctrl, reporter);
22,266✔
1382
    this->get_role_attrs(role_t::VCR_NON_ASCII)
11,133✔
1383
        = this->to_attrs(lt, lt.lt_style_non_ascii, reporter);
22,266✔
1384
    this->get_role_attrs(role_t::VCR_NUMBER)
11,133✔
1385
        = this->to_attrs(lt, lt.lt_style_number, reporter);
22,266✔
1386
    this->get_role_attrs(role_t::VCR_FUNCTION)
11,133✔
1387
        = this->to_attrs(lt, lt.lt_style_function, reporter);
22,266✔
1388
    this->get_role_attrs(role_t::VCR_TYPE)
11,133✔
1389
        = this->to_attrs(lt, lt.lt_style_type, reporter);
22,266✔
1390
    this->get_role_attrs(role_t::VCR_SEP_REF_ACC)
11,133✔
1391
        = this->to_attrs(lt, lt.lt_style_sep_ref_acc, reporter);
22,266✔
1392
    this->get_role_attrs(role_t::VCR_SUGGESTION)
11,133✔
1393
        = this->to_attrs(lt, lt.lt_style_suggestion, reporter);
22,266✔
1394
    this->get_role_attrs(role_t::VCR_SELECTED_TEXT)
11,133✔
1395
        = this->to_attrs(lt, lt.lt_style_selected_text, reporter);
22,266✔
1396
    if (this->get_role_attrs(role_t::VCR_SELECTED_TEXT).ra_normal.empty()) {
11,133✔
1397
        this->get_role_attrs(role_t::VCR_SELECTED_TEXT) = this->to_attrs(
34✔
1398
            default_theme, default_theme.lt_style_selected_text, reporter);
34✔
1399
    }
1400
    this->get_role_attrs(role_t::VCR_FUZZY_MATCH)
11,133✔
1401
        = this->to_attrs(lt, lt.lt_style_fuzzy_match, reporter);
22,266✔
1402

1403
    this->get_role_attrs(role_t::VCR_RE_SPECIAL)
11,133✔
1404
        = this->to_attrs(lt, lt.lt_style_re_special, reporter);
22,266✔
1405
    this->get_role_attrs(role_t::VCR_RE_REPEAT)
11,133✔
1406
        = this->to_attrs(lt, lt.lt_style_re_repeat, reporter);
22,266✔
1407
    this->get_role_attrs(role_t::VCR_FILE)
11,133✔
1408
        = this->to_attrs(lt, lt.lt_style_file, reporter);
22,266✔
1409

1410
    this->get_role_attrs(role_t::VCR_DIFF_DELETE)
11,133✔
1411
        = this->to_attrs(lt, lt.lt_style_diff_delete, reporter);
22,266✔
1412
    this->get_role_attrs(role_t::VCR_DIFF_ADD)
11,133✔
1413
        = this->to_attrs(lt, lt.lt_style_diff_add, reporter);
22,266✔
1414
    this->get_role_attrs(role_t::VCR_DIFF_SECTION)
11,133✔
1415
        = this->to_attrs(lt, lt.lt_style_diff_section, reporter);
22,266✔
1416

1417
    this->get_role_attrs(role_t::VCR_LOW_THRESHOLD)
11,133✔
1418
        = this->to_attrs(lt, lt.lt_style_low_threshold, reporter);
22,266✔
1419
    this->get_role_attrs(role_t::VCR_MED_THRESHOLD)
11,133✔
1420
        = this->to_attrs(lt, lt.lt_style_med_threshold, reporter);
22,266✔
1421
    this->get_role_attrs(role_t::VCR_HIGH_THRESHOLD)
11,133✔
1422
        = this->to_attrs(lt, lt.lt_style_high_threshold, reporter);
22,266✔
1423

1424
    for (auto level = static_cast<log_level_t>(LEVEL_UNKNOWN + 1);
11,133✔
1425
         level < LEVEL__MAX;
166,995✔
1426
         level = static_cast<log_level_t>(level + 1))
155,862✔
1427
    {
1428
        auto level_iter = lt.lt_level_styles.find(level);
155,862✔
1429

1430
        if (level_iter == lt.lt_level_styles.end()) {
155,862✔
1431
            this->vc_level_attrs[level]
111,074✔
1432
                = role_attrs{text_attrs{}, text_attrs{}};
111,074✔
1433
        } else {
1434
            this->vc_level_attrs[level]
44,788✔
1435
                = this->to_attrs(lt, level_iter->second, reporter);
44,788✔
1436
        }
1437
    }
1438
    this->vc_level_attrs[LEVEL_UNKNOWN] = this->vc_level_attrs[LEVEL_INFO];
11,133✔
1439

1440
    for (int32_t role_index = 0;
1,035,369✔
1441
         role_index < lnav::enums::to_underlying(role_t::VCR__MAX);
1,035,369✔
1442
         role_index++)
1443
    {
1444
        const auto& ra = this->vc_role_attrs[role_index];
1,024,236✔
1445
        if (ra.ra_class_name.empty()) {
1,024,236✔
1446
            continue;
165,942✔
1447
        }
1448

1449
        this->vc_class_to_role[ra.ra_class_name.to_string()]
1,716,588✔
1450
            = VC_ROLE.value(role_t(role_index));
2,574,882✔
1451
    }
1452
    for (int level_index = 0; level_index < LEVEL__MAX; level_index++) {
178,128✔
1453
        const auto& ra = this->vc_level_attrs[level_index];
166,995✔
1454
        if (ra.ra_class_name.empty()) {
166,995✔
1455
            continue;
122,531✔
1456
        }
1457

1458
        this->vc_class_to_role[ra.ra_class_name.to_string()]
88,928✔
1459
            = SA_LEVEL.value(level_index);
133,392✔
1460
    }
1461

1462
    if (this->vc_notcurses) {
11,133✔
1463
        auto& mouse_i = injector::get<xterm_mouse&>();
16✔
1464
        mouse_i.set_enabled(
16✔
1465
            this->vc_notcurses,
1466
            check_experimental("mouse")
16✔
1467
                || lnav_config.lc_mouse_mode == lnav_mouse_mode::enabled);
16✔
1468
    }
1469
}
11,133✔
1470

1471
styling::color_unit
1472
view_colors::color_for_ident(const char* str, size_t len) const
3,856✔
1473
{
1474
    auto index = crc32(1, (const Bytef*) str, len);
3,856✔
1475

1476
    if (str[0] == '#' && (len == 4 || len == 7)) {
3,856✔
1477
        auto fg_res
1478
            = styling::color_unit::from_str(string_fragment(str, 0, len));
65✔
1479
        if (fg_res.isOk()) {
65✔
UNCOV
1480
            return fg_res.unwrap();
×
1481
        }
1482
    }
65✔
1483

1484
    const auto offset = index % HI_COLOR_COUNT;
3,856✔
1485
    if (this->vc_highlight_colors[offset] == 0) {
3,856✔
UNCOV
1486
        return styling::color_unit::EMPTY;
×
1487
    }
1488
    auto retval = styling::color_unit::from_palette(
1489
        palette_color{static_cast<uint8_t>(this->vc_highlight_colors[offset])});
3,856✔
1490

1491
    return retval;
3,856✔
1492
}
3,856✔
1493

1494
text_attrs
1495
view_colors::attrs_for_ident(const char* str, size_t len) const
3,872✔
1496
{
1497
    auto retval = this->attrs_for_role(role_t::VCR_IDENTIFIER);
3,872✔
1498

1499
    if (retval.ta_fg_color.cu_value.is<styling::semantic>()) {
3,872✔
1500
        retval.ta_fg_color = this->color_for_ident(str, len);
3,856✔
1501
    }
1502
    if (retval.ta_bg_color.cu_value.is<styling::semantic>()) {
3,872✔
UNCOV
1503
        retval.ta_bg_color = this->color_for_ident(str, len);
×
1504
    }
1505

1506
    return retval;
3,872✔
UNCOV
1507
}
×
1508

1509
styling::color_unit
1510
view_colors::ansi_to_theme_color(styling::color_unit ansi_fg) const
78,076✔
1511
{
1512
    if (ansi_fg.cu_value.is<palette_color>()) {
78,076✔
1513
        auto pal
1514
            = static_cast<ansi_color>(ansi_fg.cu_value.get<palette_color>());
84✔
1515

1516
        if (pal >= ansi_color::black && pal <= ansi_color::white) {
84✔
1517
            return this->vc_ansi_to_theme[lnav::enums::to_underlying(pal)];
84✔
1518
        }
1519
    }
1520

1521
    return ansi_fg;
77,992✔
1522
}
1523

1524
Result<screen_curses, std::string>
1525
screen_curses::create(const notcurses_options& options)
16✔
1526
{
1527
    auto* nc = notcurses_core_init(&options, stdout);
16✔
1528
    if (nc == nullptr) {
16✔
UNCOV
1529
        return Err(fmt::format(FMT_STRING("unable to initialize notcurses {}"),
×
UNCOV
1530
                               strerror(errno)));
×
1531
    }
1532

1533
    auto& mouse_i = injector::get<xterm_mouse&>();
16✔
1534
    mouse_i.set_enabled(
16✔
1535
        nc,
1536
        check_experimental("mouse")
16✔
1537
            || lnav_config.lc_mouse_mode == lnav_mouse_mode::enabled);
16✔
1538

1539
    auto_mem<char> term_name;
16✔
1540
    term_name = notcurses_detected_terminal(nc);
16✔
1541
    log_info("notcurses detected terminal: %s", term_name.in());
16✔
1542

1543
    auto retval = screen_curses(nc);
16✔
1544

1545
    tcgetattr(STDIN_FILENO, &retval.sc_termios);
16✔
1546
    retval.sc_termios.c_cc[VSTART] = 0;
16✔
1547
    retval.sc_termios.c_cc[VSTOP] = 0;
16✔
1548
#ifdef VDISCARD
1549
    retval.sc_termios.c_cc[VDISCARD] = 0;
16✔
1550
#endif
1551
#ifdef VDSUSP
1552
    retval.sc_termios.c_cc[VDSUSP] = 0;
1553
#endif
1554
    tcsetattr(STDIN_FILENO, TCSANOW, &retval.sc_termios);
16✔
1555

1556
    return Ok(std::move(retval));
16✔
1557
}
16✔
1558

1559
screen_curses::screen_curses(screen_curses&& other) noexcept
64✔
1560
    : sc_termios(other.sc_termios),
64✔
1561
      sc_notcurses(std::exchange(other.sc_notcurses, nullptr))
64✔
1562
{
1563
}
64✔
1564

1565
extern "C"
1566
{
1567
Terminfo*
UNCOV
1568
terminfo_load_from_internal(const char* term_name)
×
1569
{
UNCOV
1570
    log_debug("checking for internal terminfo for: %s", term_name);
×
UNCOV
1571
    for (const auto& tf : lnav_terminfo_files) {
×
UNCOV
1572
        if (strcmp(tf.get_name(), term_name) != 0) {
×
UNCOV
1573
            continue;
×
1574
        }
UNCOV
1575
        log_info("  found internal terminfo!");
×
UNCOV
1576
        auto sfp = tf.to_string_fragment_producer();
×
UNCOV
1577
        auto content = sfp->to_string();
×
UNCOV
1578
        auto* retval = terminfo_parse(content.c_str(), content.size());
×
UNCOV
1579
        if (retval != nullptr) {
×
UNCOV
1580
            return retval;
×
1581
        }
UNCOV
1582
        log_error("  failed to load internal terminfo");
×
1583
    }
1584

UNCOV
1585
    return nullptr;
×
1586
}
1587
}
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