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

tstack / lnav / 20281835752-2752

16 Dec 2025 08:31PM UTC coverage: 68.903% (+0.03%) from 68.87%
20281835752-2752

push

github

tstack
[tests] update test data

51677 of 75000 relevant lines covered (68.9%)

434192.37 hits per line

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

88.19
/src/base/lnav.console.cc
1
/**
2
 * Copyright (c) 2022, 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

30
#include <algorithm>
31

32
#include "lnav.console.hh"
33

34
#include "color_spaces.hh"
35
#include "config.h"
36
#include "fmt/color.h"
37
#include "itertools.hh"
38
#include "lnav.console.into.hh"
39
#include "lnav_log.hh"
40
#include "log_level_enum.hh"
41
#include "pcrepp/pcre2pp.hh"
42
#include "snippet_highlighters.hh"
43

44
using namespace lnav::roles::literals;
45

46
namespace lnav::console {
47

48
snippet
49
snippet::from_content_with_offset(intern_string_t src,
4✔
50
                                  const attr_line_t& content,
51
                                  size_t offset,
52
                                  const std::string& errmsg)
53
{
54
    const auto content_sf = string_fragment::from_str(content.get_string());
4✔
55
    const auto line_with_error = content_sf.find_boundaries_around(
4✔
56
        offset, string_fragment::tag1{'\n'});
×
57
    const auto line_with_context = content_sf.find_boundaries_around(
4✔
58
        offset, string_fragment::tag1{'\n'}, 3);
×
59
    const auto line_number = content_sf.sub_range(0, offset).count('\n');
4✔
60
    const auto erroff_in_line = offset - line_with_error.sf_begin;
4✔
61

62
    attr_line_t pointer;
4✔
63

64
    pointer.append(erroff_in_line, ' ')
4✔
65
        .append("^ "_snippet_border)
4✔
66
        .append(lnav::roles::error(errmsg))
8✔
67
        .with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
4✔
68

69
    snippet retval;
4✔
70
    retval.s_content
71
        = content.subline(line_with_context.sf_begin,
4✔
72
                          line_with_error.sf_end - line_with_context.sf_begin);
4✔
73
    if (line_with_error.sf_end >= (int) retval.s_content.get_string().size()) {
4✔
74
        retval.s_content.append("\n");
4✔
75
    }
76
    retval.s_content.append(pointer).append(
8✔
77
        content.subline(line_with_error.sf_end,
4✔
78
                        line_with_context.sf_end - line_with_error.sf_end));
4✔
79
    retval.s_location = source_location{
8✔
80
        src,
81
        static_cast<int32_t>(1 + line_number),
4✔
82
    };
83

84
    return retval;
8✔
85
}
4✔
86

87
user_message
88
user_message::raw(const attr_line_t& al)
4✔
89
{
90
    user_message retval;
4✔
91

92
    retval.um_level = level::raw;
4✔
93
    retval.um_message.append(al);
4✔
94
    return retval;
4✔
95
}
×
96

97
user_message
98
user_message::error(const attr_line_t& al)
232✔
99
{
100
    user_message retval;
232✔
101

102
    retval.um_level = level::error;
232✔
103
    retval.um_message.append(al);
232✔
104
    return retval;
232✔
105
}
×
106

107
user_message
108
user_message::info(const attr_line_t& al)
46,788✔
109
{
110
    user_message retval;
46,788✔
111

112
    retval.um_level = level::info;
46,788✔
113
    retval.um_message.append(al);
46,788✔
114
    return retval;
46,788✔
115
}
×
116

117
user_message
118
user_message::ok(const attr_line_t& al)
589✔
119
{
120
    user_message retval;
589✔
121

122
    retval.um_level = level::ok;
589✔
123
    retval.um_message.append(al);
589✔
124
    return retval;
589✔
125
}
×
126

127
user_message
128
user_message::warning(const attr_line_t& al)
18✔
129
{
130
    user_message retval;
18✔
131

132
    retval.um_level = level::warning;
18✔
133
    retval.um_message.append(al);
18✔
134
    return retval;
18✔
135
}
×
136

137
user_message&
138
user_message::remove_internal_snippets()
89✔
139
{
140
    auto new_end = std::remove_if(
89✔
141
        this->um_snippets.begin(),
142
        this->um_snippets.end(),
143
        [](const snippet& snip) {
98✔
144
            return snip.s_location.sl_source.to_string_fragment().startswith(
196✔
145
                "__");
196✔
146
        });
147
    this->um_snippets.erase(new_end, this->um_snippets.end());
89✔
148

149
    return *this;
89✔
150
}
151

152
attr_line_t
153
user_message::to_attr_line(render_flags flags) const
43,645✔
154
{
155
    auto indent = 1;
43,645✔
156
    attr_line_t retval;
43,645✔
157

158
    if (this->um_level == level::warning) {
43,645✔
159
        indent = 3;
13✔
160
    }
161

162
    if (flags == render_flags::prefix) {
43,645✔
163
        switch (this->um_level) {
43,635✔
164
            case level::raw:
4✔
165
                break;
4✔
166
            case level::ok:
15✔
167
                retval.append("  ");
15✔
168
                retval.al_attrs.emplace_back(line_range{0, 1},
15✔
169
                                             VC_ICON.value(ui_icon_t::ok));
30✔
170
                break;
15✔
171
            case level::info:
43,409✔
172
                retval.append("  ").append("info"_info).append(": ");
43,409✔
173
                retval.al_attrs.emplace_back(line_range{0, 1},
43,409✔
174
                                             VC_ICON.value(ui_icon_t::info));
86,818✔
175
                break;
43,409✔
176
            case level::warning:
13✔
177
                retval.append("  ").append("warning"_warning).append(": ");
13✔
178
                retval.al_attrs.emplace_back(line_range{0, 1},
13✔
179
                                             VC_ICON.value(ui_icon_t::warning));
26✔
180
                break;
13✔
181
            case level::error:
194✔
182
                retval.append("  ").append("error"_error).append(": ");
194✔
183
                retval.al_attrs.emplace_back(line_range{0, 1},
194✔
184
                                             VC_ICON.value(ui_icon_t::error));
388✔
185
                break;
194✔
186
        }
187
    }
188

189
    retval.append(this->um_message).append("\n");
43,645✔
190
    if (!this->um_reason.empty()) {
43,645✔
191
        bool first_line = true;
161✔
192
        for (const auto& line : this->um_reason.split_lines()) {
355✔
193
            auto role = this->um_level == level::error ? role_t::VCR_ERROR
194✔
194
                                                       : role_t::VCR_WARNING;
195
            attr_line_t prefix;
194✔
196

197
            if (first_line) {
194✔
198
                prefix.append(indent, ' ')
161✔
199
                    .append("reason", VC_ROLE.value(role))
322✔
200
                    .append(": ");
161✔
201
                first_line = false;
161✔
202
            } else {
203
                prefix.append(" |      ", VC_ROLE.value(role))
66✔
204
                    .append(indent, ' ');
33✔
205
            }
206
            retval.append(prefix).append(line).append("\n");
194✔
207
        }
355✔
208
    }
209
    if (!this->um_snippets.empty()) {
43,645✔
210
        for (const auto& snip : this->um_snippets) {
400✔
211
            attr_line_t header;
210✔
212

213
            header.append(" --> "_snippet_border)
210✔
214
                .append(lnav::roles::file(snip.s_location.sl_source.get()));
210✔
215
            if (snip.s_location.sl_line_number > 0) {
210✔
216
                header.append(":").appendf(FMT_STRING("{}"),
756✔
217
                                           snip.s_location.sl_line_number);
189✔
218
            }
219
            retval.append(header).append("\n");
210✔
220
            if (!snip.s_content.blank()) {
210✔
221
                auto snippet_lines = snip.s_content.split_lines();
164✔
222
                auto longest_line_length = snippet_lines
223
                    | lnav::itertools::map(&attr_line_t::utf8_length_or_length)
328✔
224
                    | lnav::itertools::max(40);
328✔
225

226
                for (auto& line : snippet_lines) {
359✔
227
                    line.pad_to(longest_line_length);
195✔
228
                    retval.append(" | "_snippet_border)
195✔
229
                        .append(line)
195✔
230
                        .append("\n");
195✔
231
                }
232
            }
164✔
233
        }
210✔
234
    }
235
    if (!this->um_notes.empty()) {
43,645✔
236
        for (const auto& note : this->um_notes) {
228✔
237
            bool first_line = true;
116✔
238
            for (const auto& line : note.split_lines()) {
431✔
239
                attr_line_t prefix;
315✔
240

241
                if (first_line) {
315✔
242
                    prefix.append(" ="_snippet_border)
116✔
243
                        .append(indent, ' ')
116✔
244
                        .append("note"_snippet_border)
116✔
245
                        .append(": ");
116✔
246
                    first_line = false;
116✔
247
                } else {
248
                    prefix.append("        ").append(indent, ' ');
199✔
249
                }
250

251
                retval.append(prefix).append(line).append("\n");
315✔
252
            }
431✔
253
        }
254
    }
255
    if (!this->um_help.empty()) {
43,645✔
256
        bool first_line = true;
104✔
257
        for (const auto& line : this->um_help.split_lines()) {
398✔
258
            attr_line_t prefix;
294✔
259

260
            if (first_line) {
294✔
261
                prefix.append(" ="_snippet_border)
104✔
262
                    .append(indent, ' ')
104✔
263
                    .append("help"_snippet_border)
104✔
264
                    .append(": ");
104✔
265
                first_line = false;
104✔
266
            } else {
267
                prefix.append("         ");
190✔
268
            }
269

270
            retval.append(prefix).append(line).append("\n");
294✔
271
        }
398✔
272
    }
273

274
    return retval;
43,645✔
275
}
×
276

277
static std::optional<fmt::terminal_color>
278
color_to_terminal_color(const styling::color_unit& curses_color)
1,671✔
279
{
280
    return std::visit(
1,671✔
281
        styling::overload{
1,671✔
282
            [](const styling::semantic& s)
507✔
283
                -> std::optional<fmt::terminal_color> { return std::nullopt; },
507✔
284
            [](const styling::transparent& s)
×
285
                -> std::optional<fmt::terminal_color> { return std::nullopt; },
×
286
            [](const palette_color& pc) -> std::optional<fmt::terminal_color> {
1,157✔
287
                switch (pc) {
1,157✔
288
                    case COLOR_BLACK:
10✔
289
                        return fmt::terminal_color::black;
10✔
290
                    case COLOR_RED:
74✔
291
                        return fmt::terminal_color::red;
74✔
292
                    case COLOR_GREEN:
36✔
293
                        return fmt::terminal_color::green;
36✔
294
                    case COLOR_YELLOW:
31✔
295
                        return fmt::terminal_color::yellow;
31✔
296
                    case COLOR_BLUE:
44✔
297
                        return fmt::terminal_color::blue;
44✔
298
                    case COLOR_MAGENTA:
×
299
                        return fmt::terminal_color::magenta;
×
300
                    case COLOR_CYAN:
3✔
301
                        return fmt::terminal_color::cyan;
3✔
302
                    case COLOR_WHITE:
10✔
303
                        return fmt::terminal_color::white;
10✔
304
                    default:
949✔
305
                        return std::nullopt;
949✔
306
                }
307
            },
308
            [](const rgb_color& rgb) -> std::optional<fmt::terminal_color> {
7✔
309
                switch (to_ansi_color(rgb)) {
7✔
310
                    case ansi_color::black:
×
311
                        return fmt::terminal_color::black;
×
312
                    case ansi_color::cyan:
1✔
313
                        return fmt::terminal_color::cyan;
1✔
314
                    case ansi_color::white:
×
315
                        return fmt::terminal_color::white;
×
316
                    case ansi_color::magenta:
×
317
                        return fmt::terminal_color::magenta;
×
318
                    case ansi_color::blue:
×
319
                        return fmt::terminal_color::blue;
×
320
                    case ansi_color::yellow:
×
321
                        return fmt::terminal_color::yellow;
×
322
                    case ansi_color::green:
2✔
323
                        return fmt::terminal_color::green;
2✔
324
                    case ansi_color::red:
4✔
325
                        return fmt::terminal_color::red;
4✔
326
                    default:
×
327
                        return std::nullopt;
×
328
                }
329
            }},
330
        curses_color.cu_value);
3,342✔
331
}
332

333
static bool
334
get_no_color()
481✔
335
{
336
    return getenv("NO_COLOR") != nullptr;
481✔
337
}
338

339
static bool
340
get_yes_color()
481✔
341
{
342
    return getenv("YES_COLOR") != nullptr;
481✔
343
}
344

345
static bool
346
get_fd_tty(int fd)
962✔
347
{
348
    return isatty(fd);
962✔
349
}
350

351
static void
352
set_rev(fmt::text_style& line_style)
1,530✔
353
{
354
    if (line_style.has_emphasis()
1,530✔
355
        && lnav::enums::to_underlying(line_style.get_emphasis())
1,978✔
356
            & lnav::enums::to_underlying(fmt::emphasis::reverse))
448✔
357
    {
358
        auto old_style = line_style;
1✔
359
        auto old_emph = fmt::emphasis(
360
            lnav::enums::to_underlying(old_style.get_emphasis())
1✔
361
            & ~lnav::enums::to_underlying(fmt::emphasis::reverse));
1✔
362
        line_style = fmt::text_style{};
1✔
363
        if (old_style.has_foreground()) {
1✔
364
            line_style |= fmt::fg(old_style.get_foreground());
×
365
        }
366
        if (old_style.has_background()) {
1✔
367
            line_style |= fmt::bg(old_style.get_background());
×
368
        }
369
        line_style |= old_emph;
1✔
370
    } else {
371
        line_style |= fmt::emphasis::reverse;
1,529✔
372
    }
373
}
1,530✔
374

375
static void
376
role_to_style(const role_t role,
41,872✔
377
              fmt::text_style& default_bg_style,
378
              fmt::text_style& default_fg_style,
379
              fmt::text_style& line_style)
380
{
381
    switch (role) {
41,872✔
382
        case role_t::VCR_TEXT:
8,677✔
383
        case role_t::VCR_IDENTIFIER:
384
            break;
8,677✔
385
        case role_t::VCR_ALT_ROW:
4,574✔
386
            line_style |= fmt::emphasis::bold;
4,574✔
387
            break;
4,574✔
388
        case role_t::VCR_SEARCH:
8✔
389
            set_rev(line_style);
8✔
390
            break;
8✔
391
        case role_t::VCR_ERROR:
1,017✔
392
        case role_t::VCR_DIFF_DELETE:
393
            line_style
394
                |= fmt::fg(fmt::terminal_color::red) | fmt::emphasis::bold;
1,017✔
395
            break;
1,005✔
396
        case role_t::VCR_HIDDEN:
638✔
397
        case role_t::VCR_WARNING:
398
        case role_t::VCR_RE_REPEAT:
399
        case role_t::VCR_NON_ASCII:
400
            line_style |= fmt::fg(fmt::terminal_color::yellow);
638✔
401
            break;
604✔
402
        case role_t::VCR_ASCII_CTRL:
4,383✔
403
        case role_t::VCR_COMMENT:
404
        case role_t::VCR_DIFF_ADD:
405
            line_style |= fmt::fg(fmt::terminal_color::green);
4,383✔
406
            break;
3,585✔
407
        case role_t::VCR_SNIPPET_BORDER:
680✔
408
            line_style |= fmt::fg(fmt::terminal_color::cyan);
680✔
409
            break;
680✔
410
        case role_t::VCR_OK:
579✔
411
            line_style
412
                |= fmt::emphasis::bold | fmt::fg(fmt::terminal_color::green);
579✔
413
            break;
579✔
414
        case role_t::VCR_FOOTNOTE_BORDER:
243✔
415
            line_style |= fmt::fg(fmt::terminal_color::blue);
243✔
416
            break;
243✔
417
        case role_t::VCR_INFO:
10✔
418
        case role_t::VCR_STATUS:
419
            line_style
420
                |= fmt::emphasis::bold | fmt::fg(fmt::terminal_color::magenta);
10✔
421
            break;
10✔
422
        case role_t::VCR_NULL:
2,040✔
423
        case role_t::VCR_KEYWORD:
424
        case role_t::VCR_RE_SPECIAL:
425
            line_style
426
                |= fmt::emphasis::bold | fmt::fg(fmt::terminal_color::cyan);
2,040✔
427
            break;
1,928✔
428
        case role_t::VCR_STRING:
905✔
429
            line_style |= fmt::fg(fmt::terminal_color::magenta);
905✔
430
            break;
850✔
431
        case role_t::VCR_VARIABLE:
1,051✔
432
            line_style |= fmt::emphasis::underline;
1,051✔
433
            break;
1,051✔
434
        case role_t::VCR_SYMBOL:
7,244✔
435
        case role_t::VCR_NUMBER:
436
        case role_t::VCR_FILE:
437
            line_style |= fmt::emphasis::bold;
7,244✔
438
            break;
7,244✔
439
        case role_t::VCR_H1:
57✔
440
            line_style
441
                |= fmt::emphasis::bold | fmt::fg(fmt::terminal_color::magenta);
57✔
442
            break;
57✔
443
        case role_t::VCR_H2:
190✔
444
            line_style |= fmt::emphasis::bold;
190✔
445
            break;
190✔
446
        case role_t::VCR_H3:
2,329✔
447
        case role_t::VCR_H4:
448
        case role_t::VCR_H5:
449
        case role_t::VCR_H6:
450
            line_style |= fmt::emphasis::underline;
2,329✔
451
            break;
2,329✔
452
        case role_t::VCR_LIST_GLYPH:
207✔
453
            line_style |= fmt::fg(fmt::terminal_color::yellow);
207✔
454
            break;
206✔
455
        case role_t::VCR_INLINE_CODE:
4,496✔
456
        case role_t::VCR_QUOTED_CODE:
457
            default_fg_style = fmt::fg(fmt::terminal_color::white);
4,496✔
458
            default_bg_style = fmt::bg(fmt::terminal_color::black);
4,496✔
459
            break;
4,496✔
460
        case role_t::VCR_LOW_THRESHOLD:
24✔
461
        case role_t::VCR_SPECTRO_THRESHOLD0:
462
        case role_t::VCR_SPECTRO_THRESHOLD1:
463
            line_style |= fmt::bg(fmt::terminal_color::green);
24✔
464
            break;
24✔
465
        case role_t::VCR_MED_THRESHOLD:
8✔
466
        case role_t::VCR_SPECTRO_THRESHOLD2:
467
        case role_t::VCR_SPECTRO_THRESHOLD3:
468
        case role_t::VCR_SPECTRO_THRESHOLD4:
469
            line_style |= fmt::bg(fmt::terminal_color::yellow);
8✔
470
            break;
8✔
471
        case role_t::VCR_HIGH_THRESHOLD:
5✔
472
        case role_t::VCR_SPECTRO_THRESHOLD5:
473
        case role_t::VCR_SPECTRO_THRESHOLD6:
474
            line_style |= fmt::bg(fmt::terminal_color::red);
5✔
475
            break;
5✔
476
        default:
2,507✔
477
            // log_debug("missing role handler %d", (int) role);
478
            break;
2,507✔
479
    }
480
}
40,860✔
481

482
static block_elem_t
483
wchar_for_icon(ui_icon_t ic)
348✔
484
{
485
    switch (ic) {
348✔
486
        case ui_icon_t::hidden:
114✔
487
            return {U'\u22ee', role_t::VCR_HIDDEN};
114✔
488
        case ui_icon_t::ok:
8✔
489
            return {U'\u2714', role_t::VCR_OK};
8✔
490
        case ui_icon_t::info:
2✔
491
            return {U'\u24d8', role_t::VCR_INFO};
2✔
492
        case ui_icon_t::warning:
13✔
493
            return {U'\u26a0', role_t::VCR_WARNING};
13✔
494
        case ui_icon_t::error:
172✔
495
            return {U'\u2718', role_t::VCR_ERROR};
172✔
496

497
        case ui_icon_t::log_level_trace:
×
498
            return {U'\U0001F143', role_t::VCR_TEXT};
×
499
        case ui_icon_t::log_level_debug:
×
500
            return {U'\U0001F133', role_t::VCR_TEXT};
×
501
        case ui_icon_t::log_level_info:
×
502
            return {U'\U0001F138', role_t::VCR_TEXT};
×
503
        case ui_icon_t::log_level_stats:
×
504
            return {U'\U0001F142', role_t::VCR_TEXT};
×
505
        case ui_icon_t::log_level_notice:
×
506
            return {U'\U0001F13d', role_t::VCR_TEXT};
×
507
        case ui_icon_t::log_level_warning:
×
508
            return {U'\U0001F146', role_t::VCR_WARNING};
×
509
        case ui_icon_t::log_level_error:
×
510
            return {U'\U0001F134', role_t::VCR_ERROR};
×
511
        case ui_icon_t::log_level_critical:
×
512
            return {U'\U0001F132', role_t::VCR_ERROR};
×
513
        case ui_icon_t::log_level_fatal:
×
514
            return {U'\U0001F135', role_t::VCR_ERROR};
×
515

516
        case ui_icon_t::play:
×
517
            return {U'\u25b6', role_t::VCR_TEXT};
×
518
        case ui_icon_t::edit:
×
519
            return {U'\u270f', role_t::VCR_TEXT};
×
520
        case ui_icon_t::file:
16✔
521
            return {U'\U0001f4c4', role_t::VCR_TEXT};
16✔
522
        case ui_icon_t::thread:
23✔
523
            return {U'\U0001F9F5', role_t::VCR_TEXT};
23✔
524
    }
525

526
    ensure(false);
×
527
}
528

529
void
530
println(FILE* file, const attr_line_t& al)
13,948✔
531
{
532
    static const auto IS_NO_COLOR = get_no_color();
13,948✔
533
    static const auto IS_YES_COLOR = get_yes_color();
13,948✔
534
    static const auto IS_STDOUT_TTY = get_fd_tty(STDOUT_FILENO);
13,948✔
535
    static const auto IS_STDERR_TTY = get_fd_tty(STDERR_FILENO);
13,948✔
536

537
    const auto& str = al.get_string();
13,948✔
538

539
    if (IS_NO_COLOR || (file != stdout && file != stderr)
13,948✔
540
        || (((file == stdout && !IS_STDOUT_TTY)
13,944✔
541
             || (file == stderr && !IS_STDERR_TTY))
184✔
542
            && !IS_YES_COLOR))
13,944✔
543
    {
544
        fmt::print(file, "{}\n", str);
×
545
        return;
562✔
546
    }
547

548
    auto points = std::set<size_t>{0, static_cast<size_t>(al.length())};
26,772✔
549

550
    for (const auto& attr : al.get_attrs()) {
75,843✔
551
        if (!attr.sa_range.is_valid()) {
62,457✔
552
            continue;
×
553
        }
554
        points.insert(attr.sa_range.lr_start);
62,457✔
555
        if (attr.sa_range.lr_end > 0) {
62,457✔
556
            points.insert(attr.sa_range.lr_end);
56,469✔
557
        }
558
    }
559

560
    std::optional<size_t> last_point;
13,386✔
561
    for (const auto& point : points) {
94,801✔
562
        if (!last_point) {
81,416✔
563
            last_point = point;
13,386✔
564
            continue;
13,386✔
565
        }
566
        auto default_fg_style = fmt::text_style{};
68,030✔
567
        auto default_bg_style = fmt::text_style{};
68,030✔
568
        auto line_style = fmt::text_style{};
68,030✔
569
        auto fg_style = fmt::text_style{};
68,030✔
570
        auto start = last_point.value();
68,030✔
571
        std::optional<std::string> href;
68,030✔
572
        auto replaced = false;
68,030✔
573

574
        for (const auto& attr : al.get_attrs()) {
1,285,573✔
575
            if (!attr.sa_range.contains(start)
1,217,543✔
576
                && !attr.sa_range.contains(point - 1))
1,217,543✔
577
            {
578
                continue;
1,056,114✔
579
            }
580

581
            try {
582
                if (attr.sa_type == &VC_ICON) {
161,429✔
583
                    auto ic = attr.sa_value.get<ui_icon_t>();
348✔
584
                    auto be = wchar_for_icon(ic);
348✔
585
                    auto icon_fg_style = default_fg_style;
348✔
586
                    auto icon_bg_style = default_bg_style;
348✔
587
                    auto icon_style = line_style;
348✔
588
                    std::string utf8_out;
348✔
589

590
                    role_to_style(
348✔
591
                        be.role, icon_bg_style, icon_fg_style, icon_style);
592
                    ww898::utf::utf8::write(
323✔
593
                        be.value,
323✔
594
                        [&utf8_out](const char ch) { utf8_out.push_back(ch); });
1,331✔
595
                    fmt::print(file, icon_style, FMT_STRING("{}"), utf8_out);
969✔
596
                    replaced = true;
323✔
597
                } else if (attr.sa_type == &VC_HYPERLINK) {
161,429✔
598
                    auto saw = string_attr_wrapper<std::string>(&attr);
151✔
599
                    href = saw.get();
151✔
600
                } else if (attr.sa_type == &VC_BACKGROUND) {
160,930✔
601
                    auto saw = string_attr_wrapper<styling::color_unit>(&attr);
×
602
                    auto color_opt = color_to_terminal_color(saw.get());
×
603

604
                    if (color_opt) {
×
605
                        line_style |= fmt::bg(color_opt.value());
×
606
                    }
607
                } else if (attr.sa_type == &VC_FOREGROUND) {
160,930✔
608
                    auto saw = string_attr_wrapper<styling::color_unit>(&attr);
×
609
                    auto color_opt = color_to_terminal_color(saw.get());
×
610

611
                    if (color_opt) {
×
612
                        fg_style = fmt::fg(color_opt.value());
×
613
                    }
614
                } else if (attr.sa_type == &VC_STYLE) {
160,930✔
615
                    auto saw = string_attr_wrapper<text_attrs>(&attr);
5,041✔
616
                    auto style = saw.get();
5,041✔
617

618
                    if (style.has_style(text_attrs::style::reverse)) {
5,041✔
619
                        set_rev(line_style);
1,522✔
620
                    }
621
                    if (style.has_style(text_attrs::style::bold)) {
5,041✔
622
                        line_style |= fmt::emphasis::bold;
1,408✔
623
                    }
624
                    if (style.has_style(text_attrs::style::underline)) {
5,041✔
625
                        line_style |= fmt::emphasis::underline;
2,455✔
626
                    }
627
                    if (style.has_style(text_attrs::style::italic)) {
5,041✔
628
                        line_style |= fmt::emphasis::italic;
2✔
629
                    }
630
                    if (style.has_style(text_attrs::style::struck)) {
5,041✔
631
                        line_style |= fmt::emphasis::strikethrough;
1✔
632
                    }
633
                    if (!style.ta_fg_color.empty()) {
5,041✔
634
                        auto color_opt
635
                            = color_to_terminal_color(style.ta_fg_color);
1,649✔
636

637
                        if (color_opt) {
1,649✔
638
                            fg_style = fmt::fg(color_opt.value());
195✔
639
                        }
640
                    }
641
                    if (!style.ta_bg_color.empty()) {
5,041✔
642
                        auto color_opt
643
                            = color_to_terminal_color(style.ta_bg_color);
22✔
644

645
                        if (color_opt) {
22✔
646
                            line_style |= fmt::bg(color_opt.value());
20✔
647
                        }
648
                    }
649
                } else if (attr.sa_type == &SA_LEVEL) {
155,889✔
650
                    auto level = static_cast<log_level_t>(
651
                        attr.sa_value.get<int64_t>());
26,867✔
652

653
                    switch (level) {
26,867✔
654
                        case LEVEL_FATAL:
11,143✔
655
                        case LEVEL_CRITICAL:
656
                        case LEVEL_ERROR:
657
                            line_style |= fmt::fg(fmt::terminal_color::red);
11,143✔
658
                            break;
11,140✔
659
                        case LEVEL_WARNING:
249✔
660
                            line_style |= fmt::fg(fmt::terminal_color::yellow);
249✔
661
                            break;
246✔
662
                        default:
15,475✔
663
                            break;
15,475✔
664
                    }
665
                } else if (attr.sa_type == &VC_ROLE
129,022✔
666
                           || attr.sa_type == &VC_ROLE_FG)
87,522✔
667
                {
668
                    auto saw = string_attr_wrapper<role_t>(&attr);
41,524✔
669
                    auto role = saw.get();
41,524✔
670

671
                    role_to_style(
41,524✔
672
                        role, default_bg_style, default_fg_style, line_style);
673
                }
674
            } catch (const fmt::format_error& e) {
1,018✔
675
                log_error("style error: %s", e.what());
1,018✔
676
            }
1,018✔
677
        }
678

679
        if (!line_style.has_foreground() && fg_style.has_foreground()) {
68,030✔
680
            line_style |= fg_style;
195✔
681
        }
682
        if (!line_style.has_foreground() && default_fg_style.has_foreground()) {
68,030✔
683
            line_style |= default_fg_style;
3,330✔
684
        }
685
        if (!line_style.has_background() && default_bg_style.has_background()) {
68,030✔
686
            line_style |= default_bg_style;
4,496✔
687
        }
688

689
        if (line_style.has_foreground() && line_style.has_background()
92,404✔
690
            && !line_style.get_foreground().is_rgb
4,506✔
691
            && !line_style.get_background().is_rgb
4,506✔
692
            && line_style.get_foreground().value.term_color
96,910✔
693
                == line_style.get_background().value.term_color)
4,506✔
694
        {
695
            auto new_style = fmt::text_style{};
2✔
696

697
            if (line_style.has_emphasis()) {
2✔
698
                new_style |= line_style.get_emphasis();
×
699
            }
700
            new_style |= fmt::fg(line_style.get_foreground());
2✔
701
            if (line_style.get_background().value.term_color
2✔
702
                == lnav::enums::to_underlying(fmt::terminal_color::black))
2✔
703
            {
704
                new_style |= fmt::bg(fmt::terminal_color::white);
1✔
705
            } else {
706
                new_style |= fmt::bg(fmt::terminal_color::black);
1✔
707
            }
708
            line_style = new_style;
2✔
709
        }
710

711
        if (href) {
68,030✔
712
            fmt::print(file, FMT_STRING("\x1b]8;;{}\x1b\\"), href.value());
596✔
713
        }
714
        if (!replaced && start < str.size()) {
68,030✔
715
            auto actual_end = std::min(str.size(), static_cast<size_t>(point));
67,658✔
716
            auto sub = std::string{};
67,658✔
717

718
            for (auto lpc = start; lpc < actual_end;) {
1,376,699✔
719
                auto cp_start = lpc;
1,309,041✔
720
                auto read_res = ww898::utf::utf8::read(
721
                    [&str, &lpc] { return str[lpc++]; });
2,665,252✔
722

723
                if (read_res.isErr()) {
1,309,041✔
724
                    fmt::print(file, line_style, FMT_STRING("{}"), sub);
18✔
725
                    sub.clear();
6✔
726
                    fmt::print(
6✔
727
                        file,
728
                        fmt::fg(fmt::terminal_color::yellow),
6✔
729
                        FMT_STRING("{:?}"),
18✔
730
                        fmt::string_view{&str[cp_start], lpc - cp_start});
6✔
731
                    continue;
6✔
732
                }
6✔
733

734
                auto ch = read_res.unwrap();
1,309,035✔
735
                switch (ch) {
1,309,035✔
736
                    case '\b':
4✔
737
                        sub.append("\u232b");
4✔
738
                        break;
4✔
739
                    case '\x1b':
2✔
740
                        sub.append("\u238b");
2✔
741
                        break;
2✔
742
                    case '\x07':
2✔
743
                        sub.append("\U0001F514");
2✔
744
                        break;
2✔
745
                    case '\t':
16,506✔
746
                    case '\n':
747
                        sub.push_back(ch);
16,506✔
748
                        break;
16,506✔
749

750
                    default:
1,292,521✔
751
                        if (ch <= 0x1f) {
1,292,521✔
752
                            sub.push_back(0xe2);
25✔
753
                            sub.push_back(0x90);
25✔
754
                            sub.push_back(0x80 + ch);
25✔
755
                        } else {
756
                            sub.append(&str[cp_start], lpc - cp_start);
1,292,496✔
757
                        }
758
                        break;
1,292,521✔
759
                }
760
            }
1,309,041✔
761

762
            fmt::print(file, line_style, FMT_STRING("{}"), sub);
202,974✔
763
        }
67,658✔
764
        if (href) {
68,029✔
765
            fmt::print(file, FMT_STRING("\x1b]8;;\x1b\\"));
596✔
766
        }
767
        last_point = point;
68,029✔
768
    }
68,030✔
769
    fmt::print(file, "\n");
×
770
}
13,386✔
771

772
void
773
print(FILE* file, const user_message& um)
196✔
774
{
775
    auto al = um.to_attr_line();
196✔
776

777
    if (endswith(al.get_string(), "\n")) {
196✔
778
        al.erase(al.length() - 1);
196✔
779
    }
780
    println(file, al);
196✔
781
}
196✔
782

783
user_message
784
to_user_message(intern_string_t src, const lnav::pcre2pp::compile_error& ce)
13✔
785
{
786
    attr_line_t pcre_error_content{ce.ce_pattern};
13✔
787

788
    lnav::snippets::regex_highlighter(pcre_error_content,
13✔
789
                                      pcre_error_content.length(),
13✔
790
                                      line_range{
791
                                          0,
792
                                          (int) pcre_error_content.length(),
13✔
793
                                      });
794
    pcre_error_content.append("\n")
13✔
795
        .append(ce.ce_offset, ' ')
13✔
796
        .append("^ "_error)
13✔
797
        .append(lnav::roles::error(ce.get_message()))
26✔
798
        .with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
13✔
799

800
    return user_message::error(
×
801
               attr_line_t()
13✔
802
                   .append_quoted(ce.ce_pattern)
26✔
803
                   .append(" is not a valid regular expression"))
13✔
804
        .with_reason(ce.get_message())
26✔
805
        .with_snippet(lnav::console::snippet::from(src, pcre_error_content));
26✔
806
}
13✔
807

808
}  // namespace lnav::console
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