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

tstack / lnav / 22507085525-2793

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

push

github

tstack
fix mixup of O_CLOEXEC with FD_CLOEXEC

52007 of 75429 relevant lines covered (68.95%)

440500.48 hits per line

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

88.24
/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
#include <set>
32

33
#include "lnav.console.hh"
34

35
#include <stdio.h>
36
#include <stdlib.h>
37

38
#include "color_spaces.hh"
39
#include "config.h"
40
#include "fmt/color.h"
41
#include "fmt/format.h"
42
#include "itertools.hh"
43
#include "lnav.console.into.hh"
44
#include "lnav_log.hh"
45
#include "log_level_enum.hh"
46
#include "pcrepp/pcre2pp.hh"
47
#include "snippet_highlighters.hh"
48

49
using namespace lnav::roles::literals;
50

51
namespace lnav::console {
52

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

67
    attr_line_t pointer;
4✔
68

69
    pointer.append(erroff_in_line, ' ')
4✔
70
        .append("^ "_snippet_border)
4✔
71
        .append(lnav::roles::error(errmsg))
8✔
72
        .with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
4✔
73

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

89
    return retval;
8✔
90
}
4✔
91

92
user_message
93
user_message::raw(const attr_line_t& al)
4✔
94
{
95
    user_message retval;
4✔
96

97
    retval.um_level = level::raw;
4✔
98
    retval.um_message.append(al);
4✔
99
    return retval;
4✔
100
}
×
101

102
user_message
103
user_message::error(const attr_line_t& al)
243✔
104
{
105
    user_message retval;
243✔
106

107
    retval.um_level = level::error;
243✔
108
    retval.um_message.append(al);
243✔
109
    return retval;
243✔
110
}
×
111

112
user_message
113
user_message::info(const attr_line_t& al)
47,630✔
114
{
115
    user_message retval;
47,630✔
116

117
    retval.um_level = level::info;
47,630✔
118
    retval.um_message.append(al);
47,630✔
119
    return retval;
47,630✔
120
}
×
121

122
user_message
123
user_message::ok(const attr_line_t& al)
599✔
124
{
125
    user_message retval;
599✔
126

127
    retval.um_level = level::ok;
599✔
128
    retval.um_message.append(al);
599✔
129
    return retval;
599✔
130
}
×
131

132
user_message
133
user_message::warning(const attr_line_t& al)
18✔
134
{
135
    user_message retval;
18✔
136

137
    retval.um_level = level::warning;
18✔
138
    retval.um_message.append(al);
18✔
139
    return retval;
18✔
140
}
×
141

142
user_message&
143
user_message::remove_internal_snippets()
90✔
144
{
145
    auto new_end = std::remove_if(
90✔
146
        this->um_snippets.begin(),
147
        this->um_snippets.end(),
148
        [](const snippet& snip) {
99✔
149
            return snip.s_location.sl_source.to_string_fragment().startswith(
198✔
150
                "__");
198✔
151
        });
152
    this->um_snippets.erase(new_end, this->um_snippets.end());
90✔
153

154
    return *this;
90✔
155
}
156

157
attr_line_t
158
user_message::to_attr_line(render_flags flags) const
44,438✔
159
{
160
    auto indent = 1;
44,438✔
161
    attr_line_t retval;
44,438✔
162

163
    if (this->um_level == level::warning) {
44,438✔
164
        indent = 3;
13✔
165
    }
166

167
    if (flags == render_flags::prefix) {
44,438✔
168
        switch (this->um_level) {
44,428✔
169
            case level::raw:
4✔
170
                break;
4✔
171
            case level::ok:
15✔
172
                retval.append("  ");
15✔
173
                retval.al_attrs.emplace_back(line_range{0, 1},
15✔
174
                                             VC_ICON.value(ui_icon_t::ok));
30✔
175
                break;
15✔
176
            case level::info:
44,200✔
177
                retval.append("  ").append("info"_info).append(": ");
44,200✔
178
                retval.al_attrs.emplace_back(line_range{0, 1},
44,200✔
179
                                             VC_ICON.value(ui_icon_t::info));
88,400✔
180
                break;
44,200✔
181
            case level::warning:
13✔
182
                retval.append("  ").append("warning"_warning).append(": ");
13✔
183
                retval.al_attrs.emplace_back(line_range{0, 1},
13✔
184
                                             VC_ICON.value(ui_icon_t::warning));
26✔
185
                break;
13✔
186
            case level::error:
196✔
187
                retval.append("  ").append("error"_error).append(": ");
196✔
188
                retval.al_attrs.emplace_back(line_range{0, 1},
196✔
189
                                             VC_ICON.value(ui_icon_t::error));
392✔
190
                break;
196✔
191
        }
192
    }
193

194
    retval.append(this->um_message).append("\n");
44,438✔
195
    if (!this->um_reason.empty()) {
44,438✔
196
        bool first_line = true;
163✔
197
        for (const auto& line : this->um_reason.split_lines()) {
359✔
198
            auto role = this->um_level == level::error ? role_t::VCR_ERROR
196✔
199
                                                       : role_t::VCR_WARNING;
200
            attr_line_t prefix;
196✔
201

202
            if (first_line) {
196✔
203
                prefix.append(indent, ' ')
163✔
204
                    .append("reason", VC_ROLE.value(role))
326✔
205
                    .append(": ");
163✔
206
                first_line = false;
163✔
207
            } else {
208
                prefix.append(" |      ", VC_ROLE.value(role))
66✔
209
                    .append(indent, ' ');
33✔
210
            }
211
            retval.append(prefix).append(line).append("\n");
196✔
212
        }
359✔
213
    }
214
    if (!this->um_snippets.empty()) {
44,438✔
215
        for (const auto& snip : this->um_snippets) {
404✔
216
            attr_line_t header;
212✔
217

218
            header.append(" --> "_snippet_border)
212✔
219
                .append(lnav::roles::file(snip.s_location.sl_source.get()));
212✔
220
            if (snip.s_location.sl_line_number > 0) {
212✔
221
                header.append(":").appendf(FMT_STRING("{}"),
760✔
222
                                           snip.s_location.sl_line_number);
190✔
223
            }
224
            retval.append(header).append("\n");
212✔
225
            if (!snip.s_content.blank()) {
212✔
226
                auto snippet_lines = snip.s_content.split_lines();
166✔
227
                auto longest_line_length = snippet_lines
228
                    | lnav::itertools::map(&attr_line_t::utf8_length_or_length)
332✔
229
                    | lnav::itertools::max(40);
332✔
230

231
                for (auto& line : snippet_lines) {
364✔
232
                    line.pad_to(longest_line_length);
198✔
233
                    retval.append(" | "_snippet_border)
198✔
234
                        .append(line)
198✔
235
                        .append("\n");
198✔
236
                }
237
            }
166✔
238
        }
212✔
239
    }
240
    if (!this->um_notes.empty()) {
44,438✔
241
        for (const auto& note : this->um_notes) {
228✔
242
            bool first_line = true;
116✔
243
            for (const auto& line : note.split_lines()) {
431✔
244
                attr_line_t prefix;
315✔
245

246
                if (first_line) {
315✔
247
                    prefix.append(" ="_snippet_border)
116✔
248
                        .append(indent, ' ')
116✔
249
                        .append("note"_snippet_border)
116✔
250
                        .append(": ");
116✔
251
                    first_line = false;
116✔
252
                } else {
253
                    prefix.append("        ").append(indent, ' ');
199✔
254
                }
255

256
                retval.append(prefix).append(line).append("\n");
315✔
257
            }
431✔
258
        }
259
    }
260
    if (!this->um_help.empty()) {
44,438✔
261
        bool first_line = true;
106✔
262
        for (const auto& line : this->um_help.split_lines()) {
407✔
263
            attr_line_t prefix;
301✔
264

265
            if (first_line) {
301✔
266
                prefix.append(" ="_snippet_border)
106✔
267
                    .append(indent, ' ')
106✔
268
                    .append("help"_snippet_border)
106✔
269
                    .append(": ");
106✔
270
                first_line = false;
106✔
271
            } else {
272
                prefix.append("         ");
195✔
273
            }
274

275
            retval.append(prefix).append(line).append("\n");
301✔
276
        }
407✔
277
    }
278

279
    return retval;
44,438✔
280
}
×
281

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

338
static bool
339
get_no_color()
491✔
340
{
341
    return getenv("NO_COLOR") != nullptr;
491✔
342
}
343

344
static bool
345
get_yes_color()
491✔
346
{
347
    return getenv("YES_COLOR") != nullptr;
491✔
348
}
349

350
static bool
351
get_fd_tty(int fd)
982✔
352
{
353
    return isatty(fd);
982✔
354
}
355

356
static void
357
set_rev(fmt::text_style& line_style)
1,518✔
358
{
359
    if (line_style.has_emphasis()
1,518✔
360
        && lnav::enums::to_underlying(line_style.get_emphasis())
1,967✔
361
            & lnav::enums::to_underlying(fmt::emphasis::reverse))
449✔
362
    {
363
        auto old_style = line_style;
1✔
364
        auto old_emph = fmt::emphasis(
365
            lnav::enums::to_underlying(old_style.get_emphasis())
1✔
366
            & ~lnav::enums::to_underlying(fmt::emphasis::reverse));
1✔
367
        line_style = fmt::text_style{};
1✔
368
        if (old_style.has_foreground()) {
1✔
369
            line_style |= fmt::fg(old_style.get_foreground());
×
370
        }
371
        if (old_style.has_background()) {
1✔
372
            line_style |= fmt::bg(old_style.get_background());
×
373
        }
374
        line_style |= old_emph;
1✔
375
    } else {
376
        line_style |= fmt::emphasis::reverse;
1,517✔
377
    }
378
}
1,518✔
379

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

487
static block_elem_t
488
wchar_for_icon(ui_icon_t ic)
346✔
489
{
490
    switch (ic) {
346✔
491
        case ui_icon_t::hidden:
113✔
492
            return {U'\u22ee', role_t::VCR_HIDDEN};
113✔
493
        case ui_icon_t::ok:
8✔
494
            return {U'\u2714', role_t::VCR_OK};
8✔
495
        case ui_icon_t::info:
2✔
496
            return {U'\u24d8', role_t::VCR_INFO};
2✔
497
        case ui_icon_t::warning:
13✔
498
            return {U'\u26a0', role_t::VCR_WARNING};
13✔
499
        case ui_icon_t::error:
174✔
500
            return {U'\u2718', role_t::VCR_ERROR};
174✔
501

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

521
        case ui_icon_t::play:
×
522
            return {U'\u25b6', role_t::VCR_TEXT};
×
523
        case ui_icon_t::edit:
×
524
            return {U'\u270f', role_t::VCR_TEXT};
×
525
        case ui_icon_t::file:
13✔
526
            return {U'\U0001f4c4', role_t::VCR_TEXT};
13✔
527
        case ui_icon_t::thread:
23✔
528
            return {U'\U0001F9F5', role_t::VCR_TEXT};
23✔
529
        case ui_icon_t::busy:
×
530
            return {U'\u23f3', role_t::VCR_TEXT};
×
531
    }
532

533
    ensure(false);
×
534
}
535

536
void
537
println(FILE* file, const attr_line_t& al)
13,680✔
538
{
539
    static const auto IS_NO_COLOR = get_no_color();
13,680✔
540
    static const auto IS_YES_COLOR = get_yes_color();
13,680✔
541
    static const auto IS_STDOUT_TTY = get_fd_tty(STDOUT_FILENO);
13,680✔
542
    static const auto IS_STDERR_TTY = get_fd_tty(STDERR_FILENO);
13,680✔
543

544
    const auto& str = al.get_string();
13,680✔
545

546
    if (IS_NO_COLOR || (file != stdout && file != stderr)
13,680✔
547
        || (((file == stdout && !IS_STDOUT_TTY)
13,676✔
548
             || (file == stderr && !IS_STDERR_TTY))
186✔
549
            && !IS_YES_COLOR))
13,676✔
550
    {
551
        fmt::print(file, "{}\n", str);
×
552
        return;
572✔
553
    }
554

555
    auto points = std::set<size_t>{0, static_cast<size_t>(al.length())};
26,216✔
556

557
    for (const auto& attr : al.get_attrs()) {
75,752✔
558
        if (!attr.sa_range.is_valid()) {
62,644✔
559
            continue;
×
560
        }
561
        points.insert(attr.sa_range.lr_start);
62,644✔
562
        if (attr.sa_range.lr_end > 0) {
62,644✔
563
            points.insert(attr.sa_range.lr_end);
56,649✔
564
        }
565
    }
566

567
    std::optional<size_t> last_point;
13,108✔
568
    for (const auto& point : points) {
94,233✔
569
        if (!last_point) {
81,126✔
570
            last_point = point;
13,108✔
571
            continue;
13,108✔
572
        }
573
        auto default_fg_style = fmt::text_style{};
68,018✔
574
        auto default_bg_style = fmt::text_style{};
68,018✔
575
        auto line_style = fmt::text_style{};
68,018✔
576
        auto fg_style = fmt::text_style{};
68,018✔
577
        auto start = last_point.value();
68,018✔
578
        std::optional<std::string> href;
68,018✔
579
        auto replaced = false;
68,018✔
580

581
        for (const auto& attr : al.get_attrs()) {
1,292,577✔
582
            if (!attr.sa_range.contains(start)
1,224,559✔
583
                && !attr.sa_range.contains(point - 1))
1,224,559✔
584
            {
585
                continue;
1,061,661✔
586
            }
587

588
            try {
589
                if (attr.sa_type == &VC_ICON) {
162,898✔
590
                    auto ic = attr.sa_value.get<ui_icon_t>();
346✔
591
                    auto be = wchar_for_icon(ic);
346✔
592
                    auto icon_fg_style = default_fg_style;
346✔
593
                    auto icon_bg_style = default_bg_style;
346✔
594
                    auto icon_style = line_style;
346✔
595
                    std::string utf8_out;
346✔
596

597
                    role_to_style(
346✔
598
                        be.role, icon_bg_style, icon_fg_style, icon_style);
599
                    ww898::utf::utf8::write(
322✔
600
                        be.value,
322✔
601
                        [&utf8_out](const char ch) { utf8_out.push_back(ch); });
1,324✔
602
                    fmt::print(file, icon_style, FMT_STRING("{}"), utf8_out);
966✔
603
                    replaced = true;
322✔
604
                } else if (attr.sa_type == &VC_HYPERLINK) {
162,898✔
605
                    auto saw = string_attr_wrapper<std::string>(&attr);
151✔
606
                    href = saw.get();
151✔
607
                } else if (attr.sa_type == &VC_BACKGROUND) {
162,401✔
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
                        line_style |= fmt::bg(color_opt.value());
×
613
                    }
614
                } else if (attr.sa_type == &VC_FOREGROUND) {
162,401✔
615
                    auto saw = string_attr_wrapper<styling::color_unit>(&attr);
×
616
                    auto color_opt = color_to_terminal_color(saw.get());
×
617

618
                    if (color_opt) {
×
619
                        fg_style = fmt::fg(color_opt.value());
×
620
                    }
621
                } else if (attr.sa_type == &VC_STYLE) {
162,401✔
622
                    auto saw = string_attr_wrapper<text_attrs>(&attr);
5,044✔
623
                    auto style = saw.get();
5,044✔
624

625
                    if (style.has_style(text_attrs::style::reverse)) {
5,044✔
626
                        set_rev(line_style);
1,510✔
627
                    }
628
                    if (style.has_style(text_attrs::style::bold)) {
5,044✔
629
                        line_style |= fmt::emphasis::bold;
1,409✔
630
                    }
631
                    if (style.has_style(text_attrs::style::underline)) {
5,044✔
632
                        line_style |= fmt::emphasis::underline;
2,482✔
633
                    }
634
                    if (style.has_style(text_attrs::style::italic)) {
5,044✔
635
                        line_style |= fmt::emphasis::italic;
2✔
636
                    }
637
                    if (style.has_style(text_attrs::style::struck)) {
5,044✔
638
                        line_style |= fmt::emphasis::strikethrough;
1✔
639
                    }
640
                    if (!style.ta_fg_color.empty()) {
5,044✔
641
                        auto color_opt
642
                            = color_to_terminal_color(style.ta_fg_color);
1,635✔
643

644
                        if (color_opt) {
1,635✔
645
                            fg_style = fmt::fg(color_opt.value());
201✔
646
                        }
647
                    }
648
                    if (!style.ta_bg_color.empty()) {
5,044✔
649
                        auto color_opt
650
                            = color_to_terminal_color(style.ta_bg_color);
24✔
651

652
                        if (color_opt) {
24✔
653
                            line_style |= fmt::bg(color_opt.value());
22✔
654
                        }
655
                    }
656
                } else if (attr.sa_type == &SA_LEVEL) {
157,357✔
657
                    auto level = static_cast<log_level_t>(
658
                        attr.sa_value.get<int64_t>());
27,139✔
659

660
                    switch (level) {
27,139✔
661
                        case LEVEL_FATAL:
11,211✔
662
                        case LEVEL_CRITICAL:
663
                        case LEVEL_ERROR:
664
                            line_style |= fmt::fg(fmt::terminal_color::red);
11,211✔
665
                            break;
11,208✔
666
                        case LEVEL_WARNING:
249✔
667
                            line_style |= fmt::fg(fmt::terminal_color::yellow);
249✔
668
                            break;
246✔
669
                        default:
15,679✔
670
                            break;
15,679✔
671
                    }
672
                } else if (attr.sa_type == &VC_ROLE
130,218✔
673
                           || attr.sa_type == &VC_ROLE_FG)
88,456✔
674
                {
675
                    auto saw = string_attr_wrapper<role_t>(&attr);
41,786✔
676
                    auto role = saw.get();
41,786✔
677

678
                    role_to_style(
41,786✔
679
                        role, default_bg_style, default_fg_style, line_style);
680
                }
681
            } catch (const fmt::format_error& e) {
1,026✔
682
                log_error("style error: %s", e.what());
1,026✔
683
            }
1,026✔
684
        }
685

686
        if (!line_style.has_foreground() && fg_style.has_foreground()) {
68,018✔
687
            line_style |= fg_style;
201✔
688
        }
689
        if (!line_style.has_foreground() && default_fg_style.has_foreground()) {
68,018✔
690
            line_style |= default_fg_style;
3,342✔
691
        }
692
        if (!line_style.has_background() && default_bg_style.has_background()) {
68,018✔
693
            line_style |= default_bg_style;
4,521✔
694
        }
695

696
        if (line_style.has_foreground() && line_style.has_background()
92,359✔
697
            && !line_style.get_foreground().is_rgb
4,531✔
698
            && !line_style.get_background().is_rgb
4,531✔
699
            && line_style.get_foreground().value.term_color
96,890✔
700
                == line_style.get_background().value.term_color)
4,531✔
701
        {
702
            auto new_style = fmt::text_style{};
2✔
703

704
            if (line_style.has_emphasis()) {
2✔
705
                new_style |= line_style.get_emphasis();
×
706
            }
707
            new_style |= fmt::fg(line_style.get_foreground());
2✔
708
            if (line_style.get_background().value.term_color
2✔
709
                == lnav::enums::to_underlying(fmt::terminal_color::black))
2✔
710
            {
711
                new_style |= fmt::bg(fmt::terminal_color::white);
1✔
712
            } else {
713
                new_style |= fmt::bg(fmt::terminal_color::black);
1✔
714
            }
715
            line_style = new_style;
2✔
716
        }
717

718
        if (href) {
68,018✔
719
            fmt::print(file, FMT_STRING("\x1b]8;;{}\x1b\\"), href.value());
596✔
720
        }
721
        if (!replaced && start < str.size()) {
68,018✔
722
            auto actual_end = std::min(str.size(), static_cast<size_t>(point));
67,644✔
723
            auto sub = std::string{};
67,644✔
724

725
            for (auto lpc = start; lpc < actual_end;) {
1,355,793✔
726
                auto cp_start = lpc;
1,288,149✔
727
                auto read_res = ww898::utf::utf8::read(
728
                    [&str, &lpc] { return str[lpc++]; });
2,624,046✔
729

730
                if (read_res.isErr()) {
1,288,149✔
731
                    fmt::print(file, line_style, FMT_STRING("{}"), sub);
18✔
732
                    sub.clear();
6✔
733
                    fmt::print(
6✔
734
                        file,
735
                        fmt::fg(fmt::terminal_color::yellow),
6✔
736
                        FMT_STRING("{:?}"),
18✔
737
                        fmt::string_view{&str[cp_start], lpc - cp_start});
6✔
738
                    continue;
6✔
739
                }
6✔
740

741
                auto ch = read_res.unwrap();
1,288,143✔
742
                switch (ch) {
1,288,143✔
743
                    case '\b':
4✔
744
                        sub.append("\u232b");
4✔
745
                        break;
4✔
746
                    case '\x1b':
2✔
747
                        sub.append("\u238b");
2✔
748
                        break;
2✔
749
                    case '\x07':
2✔
750
                        sub.append("\U0001F514");
2✔
751
                        break;
2✔
752
                    case '\t':
16,548✔
753
                    case '\n':
754
                        sub.push_back(ch);
16,548✔
755
                        break;
16,548✔
756

757
                    default:
1,271,587✔
758
                        if (ch <= 0x1f) {
1,271,587✔
759
                            sub.push_back(0xe2);
25✔
760
                            sub.push_back(0x90);
25✔
761
                            sub.push_back(0x80 + ch);
25✔
762
                        } else {
763
                            sub.append(&str[cp_start], lpc - cp_start);
1,271,562✔
764
                        }
765
                        break;
1,271,587✔
766
                }
767
            }
1,288,149✔
768

769
            fmt::print(file, line_style, FMT_STRING("{}"), sub);
202,932✔
770
        }
67,644✔
771
        if (href) {
68,017✔
772
            fmt::print(file, FMT_STRING("\x1b]8;;\x1b\\"));
596✔
773
        }
774
        last_point = point;
68,017✔
775
    }
68,018✔
776
    fmt::print(file, "\n");
×
777
}
13,108✔
778

779
void
780
print(FILE* file, const user_message& um)
198✔
781
{
782
    auto al = um.to_attr_line();
198✔
783

784
    if (endswith(al.get_string(), "\n")) {
198✔
785
        al.erase(al.length() - 1);
198✔
786
    }
787
    println(file, al);
198✔
788
}
198✔
789

790
user_message
791
to_user_message(intern_string_t src, const lnav::pcre2pp::compile_error& ce)
14✔
792
{
793
    attr_line_t pcre_error_content{ce.ce_pattern};
14✔
794

795
    lnav::snippets::regex_highlighter(pcre_error_content,
14✔
796
                                      pcre_error_content.length(),
14✔
797
                                      line_range{
798
                                          0,
799
                                          (int) pcre_error_content.length(),
14✔
800
                                      });
801
    pcre_error_content.append("\n")
14✔
802
        .append(ce.ce_offset, ' ')
14✔
803
        .append("^ "_error)
14✔
804
        .append(lnav::roles::error(ce.get_message()))
28✔
805
        .with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
14✔
806

807
    return user_message::error(
×
808
               attr_line_t()
14✔
809
                   .append_quoted(ce.ce_pattern)
28✔
810
                   .append(" is not a valid regular expression"))
14✔
811
        .with_reason(ce.get_message())
28✔
812
        .with_snippet(lnav::console::snippet::from(src, pcre_error_content));
28✔
813
}
14✔
814

815
}  // 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