• 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

87.72
/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 "config.h"
35
#include "fmt/color.h"
36
#include "itertools.hh"
37
#include "lnav.console.into.hh"
38
#include "lnav_log.hh"
39
#include "log_level_enum.hh"
40
#include "pcrepp/pcre2pp.hh"
41
#include "snippet_highlighters.hh"
42

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

45
namespace lnav::console {
46

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

61
    attr_line_t pointer;
4✔
62

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

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

83
    return retval;
8✔
84
}
4✔
85

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

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

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

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

106
user_message
107
user_message::info(const attr_line_t& al)
3,248✔
108
{
109
    user_message retval;
3,248✔
110

111
    retval.um_level = level::info;
3,248✔
112
    retval.um_message.append(al);
3,248✔
113
    return retval;
3,248✔
UNCOV
114
}
×
115

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

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

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

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

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

148
    return *this;
84✔
149
}
150

151
attr_line_t
152
user_message::to_attr_line(std::set<render_flags> flags) const
210✔
153
{
154
    auto indent = 1;
210✔
155
    attr_line_t retval;
210✔
156

157
    if (this->um_level == level::warning) {
210✔
158
        indent = 3;
13✔
159
    }
160

161
    if (flags.count(render_flags::prefix)) {
210✔
162
        switch (this->um_level) {
200✔
163
            case level::raw:
4✔
164
                break;
4✔
165
            case level::ok:
8✔
166
                retval.append("  ");
8✔
167
                retval.al_attrs.emplace_back(line_range{0, 1},
8✔
168
                                             VC_ICON.value(ui_icon_t::ok));
16✔
169
                break;
8✔
170
            case level::info:
2✔
171
                retval.append("  ").append("info"_info).append(": ");
2✔
172
                retval.al_attrs.emplace_back(line_range{0, 1},
2✔
173
                                             VC_ICON.value(ui_icon_t::info));
4✔
174
                break;
2✔
175
            case level::warning:
13✔
176
                retval.append("  ")
13✔
177
                    .append(lnav::roles::warning("warning"))
26✔
178
                    .append(": ");
13✔
179
                retval.al_attrs.emplace_back(line_range{0, 1},
13✔
180
                                             VC_ICON.value(ui_icon_t::warning));
26✔
181
                break;
13✔
182
            case level::error:
173✔
183
                retval.append("  ")
173✔
184
                    .append(lnav::roles::error("error"))
346✔
185
                    .append(": ");
173✔
186
                retval.al_attrs.emplace_back(line_range{0, 1},
173✔
187
                                             VC_ICON.value(ui_icon_t::error));
346✔
188
                break;
173✔
189
        }
190
    }
191

192
    retval.append(this->um_message).append("\n");
210✔
193
    if (!this->um_reason.empty()) {
210✔
194
        bool first_line = true;
143✔
195
        for (const auto& line : this->um_reason.split_lines()) {
319✔
196
            auto role = this->um_level == level::error ? role_t::VCR_ERROR
176✔
197
                                                       : role_t::VCR_WARNING;
198
            attr_line_t prefix;
176✔
199

200
            if (first_line) {
176✔
201
                prefix.append(indent, ' ')
143✔
202
                    .append("reason", VC_ROLE.value(role))
286✔
203
                    .append(": ");
143✔
204
                first_line = false;
143✔
205
            } else {
206
                prefix.append(" |      ", VC_ROLE.value(role))
66✔
207
                    .append(indent, ' ');
33✔
208
            }
209
            retval.append(prefix).append(line).append("\n");
176✔
210
        }
319✔
211
    }
212
    if (!this->um_snippets.empty()) {
210✔
213
        for (const auto& snip : this->um_snippets) {
382✔
214
            attr_line_t header;
201✔
215

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

229
                for (auto& line : snippet_lines) {
354✔
230
                    line.pad_to(longest_line_length);
193✔
231
                    retval.append(" | "_snippet_border)
193✔
232
                        .append(line)
193✔
233
                        .append("\n");
193✔
234
                }
235
            }
161✔
236
        }
201✔
237
    }
238
    if (!this->um_notes.empty()) {
210✔
239
        for (const auto& note : this->um_notes) {
63✔
240
            bool first_line = true;
33✔
241
            for (const auto& line : note.split_lines()) {
121✔
242
                attr_line_t prefix;
88✔
243

244
                if (first_line) {
88✔
245
                    prefix.append(" ="_snippet_border)
33✔
246
                        .append(indent, ' ')
33✔
247
                        .append("note"_snippet_border)
33✔
248
                        .append(": ");
33✔
249
                    first_line = false;
33✔
250
                } else {
251
                    prefix.append("        ").append(indent, ' ');
55✔
252
                }
253

254
                retval.append(prefix).append(line).append("\n");
88✔
255
            }
121✔
256
        }
257
    }
258
    if (!this->um_help.empty()) {
210✔
259
        bool first_line = true;
93✔
260
        for (const auto& line : this->um_help.split_lines()) {
365✔
261
            attr_line_t prefix;
272✔
262

263
            if (first_line) {
272✔
264
                prefix.append(" ="_snippet_border)
93✔
265
                    .append(indent, ' ')
93✔
266
                    .append("help"_snippet_border)
93✔
267
                    .append(": ");
93✔
268
                first_line = false;
93✔
269
            } else {
270
                prefix.append("         ");
179✔
271
            }
272

273
            retval.append(prefix).append(line).append("\n");
272✔
274
        }
365✔
275
    }
276

277
    return retval;
210✔
UNCOV
278
}
×
279

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

335
static bool
336
get_no_color()
434✔
337
{
338
    return getenv("NO_COLOR") != nullptr;
434✔
339
}
340

341
static bool
342
get_yes_color()
434✔
343
{
344
    return getenv("YES_COLOR") != nullptr;
434✔
345
}
346

347
static bool
348
get_fd_tty(int fd)
868✔
349
{
350
    return isatty(fd);
868✔
351
}
352

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

377
static void
378
role_to_style(const role_t role,
79,150✔
379
              fmt::text_style& default_bg_style,
380
              fmt::text_style& default_fg_style,
381
              fmt::text_style& line_style)
382
{
383
    switch (role) {
79,150✔
384
        case role_t::VCR_TEXT:
3,797✔
385
        case role_t::VCR_IDENTIFIER:
386
            break;
3,797✔
387
        case role_t::VCR_ALT_ROW:
21,585✔
388
            line_style |= fmt::emphasis::bold;
21,585✔
389
            break;
21,585✔
390
        case role_t::VCR_SEARCH:
8✔
391
            set_rev(line_style);
8✔
392
            break;
8✔
393
        case role_t::VCR_ERROR:
861✔
394
        case role_t::VCR_DIFF_DELETE:
395
            line_style
396
                |= fmt::fg(fmt::terminal_color::red) | fmt::emphasis::bold;
861✔
397
            break;
849✔
398
        case role_t::VCR_HIDDEN:
491✔
399
        case role_t::VCR_WARNING:
400
        case role_t::VCR_RE_REPEAT:
401
            line_style |= fmt::fg(fmt::terminal_color::yellow);
491✔
402
            break;
459✔
403
        case role_t::VCR_COMMENT:
2,791✔
404
        case role_t::VCR_DIFF_ADD:
405
            line_style |= fmt::fg(fmt::terminal_color::green);
2,791✔
406
            break;
2,189✔
407
        case role_t::VCR_SNIPPET_BORDER:
639✔
408
            line_style |= fmt::fg(fmt::terminal_color::cyan);
639✔
409
            break;
639✔
410
        case role_t::VCR_OK:
440✔
411
            line_style
412
                |= fmt::emphasis::bold | fmt::fg(fmt::terminal_color::green);
440✔
413
            break;
440✔
414
        case role_t::VCR_FOOTNOTE_BORDER:
202✔
415
            line_style |= fmt::fg(fmt::terminal_color::blue);
202✔
416
            break;
202✔
417
        case role_t::VCR_INFO:
13✔
418
        case role_t::VCR_STATUS:
419
            line_style
420
                |= fmt::emphasis::bold | fmt::fg(fmt::terminal_color::magenta);
13✔
421
            break;
13✔
422
        case role_t::VCR_KEYWORD:
1,143✔
423
        case role_t::VCR_RE_SPECIAL:
424
            line_style
425
                |= fmt::emphasis::bold | fmt::fg(fmt::terminal_color::cyan);
1,143✔
426
            break;
1,143✔
427
        case role_t::VCR_STRING:
800✔
428
            line_style |= fmt::fg(fmt::terminal_color::magenta);
800✔
429
            break;
800✔
430
        case role_t::VCR_VARIABLE:
995✔
431
            line_style |= fmt::emphasis::underline;
995✔
432
            break;
995✔
433
        case role_t::VCR_SYMBOL:
6,896✔
434
        case role_t::VCR_NUMBER:
435
        case role_t::VCR_FILE:
436
            line_style |= fmt::emphasis::bold;
6,896✔
437
            break;
6,896✔
438
        case role_t::VCR_H1:
26✔
439
            line_style
440
                |= fmt::emphasis::bold | fmt::fg(fmt::terminal_color::magenta);
26✔
441
            break;
26✔
442
        case role_t::VCR_H2:
163✔
443
            line_style |= fmt::emphasis::bold;
163✔
444
            break;
163✔
445
        case role_t::VCR_H3:
2,211✔
446
        case role_t::VCR_H4:
447
        case role_t::VCR_H5:
448
        case role_t::VCR_H6:
449
            line_style |= fmt::emphasis::underline;
2,211✔
450
            break;
2,211✔
451
        case role_t::VCR_LIST_GLYPH:
172✔
452
            line_style |= fmt::fg(fmt::terminal_color::yellow);
172✔
453
            break;
171✔
454
        case role_t::VCR_INLINE_CODE:
4,408✔
455
        case role_t::VCR_QUOTED_CODE:
456
            default_fg_style = fmt::fg(fmt::terminal_color::white);
4,408✔
457
            default_bg_style = fmt::bg(fmt::terminal_color::black);
4,408✔
458
            break;
4,408✔
459
        case role_t::VCR_LOW_THRESHOLD:
28✔
460
            line_style |= fmt::bg(fmt::terminal_color::green);
28✔
461
            break;
28✔
462
        case role_t::VCR_MED_THRESHOLD:
2✔
463
            line_style |= fmt::bg(fmt::terminal_color::yellow);
2✔
464
            break;
2✔
465
        case role_t::VCR_HIGH_THRESHOLD:
2✔
466
            line_style |= fmt::bg(fmt::terminal_color::red);
2✔
467
            break;
2✔
468
        default:
31,477✔
469
            // log_debug("missing role handler %d", (int) role);
470
            break;
31,477✔
471
    }
472
}
78,503✔
473

474
static block_elem_t
475
wchar_for_icon(ui_icon_t ic)
258✔
476
{
477
    switch (ic) {
258✔
478
        case ui_icon_t::hidden:
82✔
479
            return {U'\u22ee', role_t::VCR_HIDDEN};
82✔
480
        case ui_icon_t::ok:
8✔
481
            return {U'\u2714', role_t::VCR_OK};
8✔
482
        case ui_icon_t::info:
2✔
483
            return {U'\u24d8', role_t::VCR_INFO};
2✔
484
        case ui_icon_t::warning:
13✔
485
            return {U'\u26a0', role_t::VCR_WARNING};
13✔
486
        case ui_icon_t::error:
153✔
487
            return {U'\u2718', role_t::VCR_ERROR};
153✔
488

UNCOV
489
        case ui_icon_t::log_level_trace:
×
UNCOV
490
            return {U'\U0001F143', role_t::VCR_TEXT};
×
UNCOV
491
        case ui_icon_t::log_level_debug:
×
UNCOV
492
            return {U'\U0001F133', role_t::VCR_TEXT};
×
UNCOV
493
        case ui_icon_t::log_level_info:
×
UNCOV
494
            return {U'\U0001F138', role_t::VCR_TEXT};
×
UNCOV
495
        case ui_icon_t::log_level_stats:
×
UNCOV
496
            return {U'\U0001F142', role_t::VCR_TEXT};
×
UNCOV
497
        case ui_icon_t::log_level_notice:
×
UNCOV
498
            return {U'\U0001F13d', role_t::VCR_TEXT};
×
UNCOV
499
        case ui_icon_t::log_level_warning:
×
UNCOV
500
            return {U'\U0001F146', role_t::VCR_WARNING};
×
UNCOV
501
        case ui_icon_t::log_level_error:
×
UNCOV
502
            return {U'\U0001F134', role_t::VCR_ERROR};
×
UNCOV
503
        case ui_icon_t::log_level_critical:
×
UNCOV
504
            return {U'\U0001F132', role_t::VCR_ERROR};
×
UNCOV
505
        case ui_icon_t::log_level_fatal:
×
UNCOV
506
            return {U'\U0001F135', role_t::VCR_ERROR};
×
507

UNCOV
508
        case ui_icon_t::play:
×
UNCOV
509
            return {U'\u25b6', role_t::VCR_TEXT};
×
UNCOV
510
        case ui_icon_t::edit:
×
UNCOV
511
            return {U'\u270f', role_t::VCR_TEXT};
×
512
    }
513

UNCOV
514
    ensure(false);
×
515
}
516

517
void
518
println(FILE* file, const attr_line_t& al)
12,750✔
519
{
520
    static const auto IS_NO_COLOR = get_no_color();
12,750✔
521
    static const auto IS_YES_COLOR = get_yes_color();
12,750✔
522
    static const auto IS_STDOUT_TTY = get_fd_tty(STDOUT_FILENO);
12,750✔
523
    static const auto IS_STDERR_TTY = get_fd_tty(STDERR_FILENO);
12,750✔
524

525
    const auto& str = al.get_string();
12,750✔
526

527
    if (IS_NO_COLOR || (file != stdout && file != stderr)
12,750✔
528
        || (((file == stdout && !IS_STDOUT_TTY)
12,746✔
529
             || (file == stderr && !IS_STDERR_TTY))
166✔
530
            && !IS_YES_COLOR))
12,746✔
531
    {
UNCOV
532
        fmt::print(file, "{}\n", str);
×
533
        return;
169✔
534
    }
535

536
    auto points = std::set<size_t>{0, static_cast<size_t>(al.length())};
25,162✔
537

538
    for (const auto& attr : al.get_attrs()) {
93,514✔
539
        if (!attr.sa_range.is_valid()) {
80,933✔
UNCOV
540
            continue;
×
541
        }
542
        points.insert(attr.sa_range.lr_start);
80,933✔
543
        if (attr.sa_range.lr_end > 0) {
80,933✔
544
            points.insert(attr.sa_range.lr_end);
73,592✔
545
        }
546
    }
547

548
    std::optional<size_t> last_point;
12,581✔
549
    for (const auto& point : points) {
112,915✔
550
        if (!last_point) {
100,335✔
551
            last_point = point;
12,581✔
552
            continue;
12,581✔
553
        }
554
        auto default_fg_style = fmt::text_style{};
87,754✔
555
        auto default_bg_style = fmt::text_style{};
87,754✔
556
        auto line_style = fmt::text_style{};
87,754✔
557
        auto fg_style = fmt::text_style{};
87,754✔
558
        auto start = last_point.value();
87,754✔
559
        std::optional<std::string> href;
87,754✔
560
        auto replaced = false;
87,754✔
561

562
        for (const auto& attr : al.get_attrs()) {
2,227,133✔
563
            if (!attr.sa_range.contains(start)
2,139,379✔
564
                && !attr.sa_range.contains(point - 1))
2,139,379✔
565
            {
566
                continue;
1,951,560✔
567
            }
568

569
            try {
570
                if (attr.sa_type == &VC_ICON) {
187,819✔
571
                    auto ic = attr.sa_value.get<ui_icon_t>();
258✔
572
                    auto be = wchar_for_icon(ic);
258✔
573
                    auto icon_fg_style = default_fg_style;
258✔
574
                    auto icon_bg_style = default_bg_style;
258✔
575
                    auto icon_style = line_style;
258✔
576
                    std::string utf8_out;
258✔
577

578
                    role_to_style(
258✔
579
                        be.role, icon_bg_style, icon_fg_style, icon_style);
580
                    ww898::utf::utf8::write(
234✔
581
                        be.value,
234✔
582
                        [&utf8_out](const char ch) { utf8_out.push_back(ch); });
936✔
583
                    fmt::print(file, icon_style, FMT_STRING("{}"), utf8_out);
702✔
584
                    replaced = true;
234✔
585
                } else if (attr.sa_type == &VC_HYPERLINK) {
187,819✔
586
                    auto saw = string_attr_wrapper<std::string>(&attr);
100✔
587
                    href = saw.get();
100✔
588
                } else if (attr.sa_type == &VC_BACKGROUND) {
187,461✔
UNCOV
589
                    auto saw = string_attr_wrapper<styling::color_unit>(&attr);
×
UNCOV
590
                    auto color_opt = color_to_terminal_color(saw.get());
×
591

UNCOV
592
                    if (color_opt) {
×
UNCOV
593
                        line_style |= fmt::bg(color_opt.value());
×
594
                    }
595
                } else if (attr.sa_type == &VC_FOREGROUND) {
187,461✔
UNCOV
596
                    auto saw = string_attr_wrapper<styling::color_unit>(&attr);
×
UNCOV
597
                    auto color_opt = color_to_terminal_color(saw.get());
×
598

UNCOV
599
                    if (color_opt) {
×
UNCOV
600
                        fg_style = fmt::fg(color_opt.value());
×
601
                    }
602
                } else if (attr.sa_type == &VC_STYLE) {
187,461✔
603
                    auto saw = string_attr_wrapper<text_attrs>(&attr);
3,930✔
604
                    auto style = saw.get();
3,930✔
605

606
                    if (style.has_style(text_attrs::style::reverse)) {
3,930✔
607
                        set_rev(line_style);
1,117✔
608
                    }
609
                    if (style.has_style(text_attrs::style::bold)) {
3,930✔
610
                        line_style |= fmt::emphasis::bold;
1,292✔
611
                    }
612
                    if (style.has_style(text_attrs::style::underline)) {
3,930✔
613
                        line_style |= fmt::emphasis::underline;
2,000✔
614
                    }
615
                    if (style.has_style(text_attrs::style::italic)) {
3,930✔
616
                        line_style |= fmt::emphasis::italic;
2✔
617
                    }
618
                    if (style.has_style(text_attrs::style::struck)) {
3,930✔
619
                        line_style |= fmt::emphasis::strikethrough;
1✔
620
                    }
621
                    if (!style.ta_fg_color.empty()) {
3,930✔
622
                        auto color_opt
623
                            = color_to_terminal_color(style.ta_fg_color);
1,369✔
624

625
                        if (color_opt) {
1,369✔
626
                            fg_style = fmt::fg(color_opt.value());
195✔
627
                        }
628
                    }
629
                    if (!style.ta_bg_color.empty()) {
3,930✔
630
                        auto color_opt
631
                            = color_to_terminal_color(style.ta_bg_color);
22✔
632

633
                        if (color_opt) {
22✔
634
                            line_style |= fmt::bg(color_opt.value());
20✔
635
                        }
636
                    }
637
                } else if (attr.sa_type == &SA_LEVEL) {
187,461✔
638
                    auto level = static_cast<log_level_t>(
639
                        attr.sa_value.get<int64_t>());
16,324✔
640

641
                    switch (level) {
16,324✔
642
                        case LEVEL_FATAL:
6,073✔
643
                        case LEVEL_CRITICAL:
644
                        case LEVEL_ERROR:
645
                            line_style |= fmt::fg(fmt::terminal_color::red);
6,073✔
646
                            break;
6,070✔
647
                        case LEVEL_WARNING:
232✔
648
                            line_style |= fmt::fg(fmt::terminal_color::yellow);
232✔
649
                            break;
229✔
650
                        default:
10,019✔
651
                            break;
10,019✔
652
                    }
653
                } else if (attr.sa_type == &VC_ROLE
167,207✔
654
                           || attr.sa_type == &VC_ROLE_FG)
88,339✔
655
                {
656
                    auto saw = string_attr_wrapper<role_t>(&attr);
78,892✔
657
                    auto role = saw.get();
78,892✔
658

659
                    role_to_style(
78,892✔
660
                        role, default_bg_style, default_fg_style, line_style);
661
                }
662
            } catch (const fmt::format_error& e) {
653✔
663
                log_error("style error: %s", e.what());
653✔
664
            }
653✔
665
        }
666

667
        if (!line_style.has_foreground() && fg_style.has_foreground()) {
87,754✔
668
            line_style |= fg_style;
195✔
669
        }
670
        if (!line_style.has_foreground() && default_fg_style.has_foreground()) {
87,754✔
671
            line_style |= default_fg_style;
3,255✔
672
        }
673
        if (!line_style.has_background() && default_bg_style.has_background()) {
87,754✔
674
            line_style |= default_bg_style;
4,408✔
675
        }
676

677
        if (line_style.has_foreground() && line_style.has_background()
104,200✔
678
            && !line_style.get_foreground().is_rgb
4,418✔
679
            && !line_style.get_background().is_rgb
4,418✔
680
            && line_style.get_foreground().value.term_color
108,618✔
681
                == line_style.get_background().value.term_color)
4,418✔
682
        {
683
            auto new_style = fmt::text_style{};
2✔
684

685
            if (line_style.has_emphasis()) {
2✔
UNCOV
686
                new_style |= line_style.get_emphasis();
×
687
            }
688
            new_style |= fmt::fg(line_style.get_foreground());
2✔
689
            if (line_style.get_background().value.term_color
2✔
690
                == lnav::enums::to_underlying(fmt::terminal_color::black))
2✔
691
            {
692
                new_style |= fmt::bg(fmt::terminal_color::white);
1✔
693
            } else {
694
                new_style |= fmt::bg(fmt::terminal_color::black);
1✔
695
            }
696
            line_style = new_style;
2✔
697
        }
698

699
        if (href) {
87,754✔
700
            fmt::print(file, FMT_STRING("\x1b]8;;{}\x1b\\"), href.value());
396✔
701
        }
702
        if (!replaced && start < str.size()) {
87,754✔
703
            auto actual_end = std::min(str.size(), static_cast<size_t>(point));
87,500✔
704
            auto sub = std::string{};
87,500✔
705

706
            for (auto lpc = start; lpc < actual_end;) {
1,254,362✔
707
                auto cp_start = lpc;
1,166,862✔
708
                auto read_res = ww898::utf::utf8::read(
709
                    [&str, &lpc]() { return str[lpc++]; });
2,379,184✔
710

711
                if (read_res.isErr()) {
1,166,862✔
712
                    sub.append(fmt::format(
3✔
713
                        FMT_STRING("{:?}"),
9✔
714
                        fmt::string_view{&str[cp_start], lpc - cp_start}));
3✔
715
                    continue;
3✔
716
                }
3✔
717

718
                auto ch = read_res.unwrap();
1,166,859✔
719
                switch (ch) {
1,166,859✔
720
                    case '\b':
4✔
721
                        sub.append("\u232b");
4✔
722
                        break;
4✔
723
                    case '\x1b':
2✔
724
                        sub.append("\u238b");
2✔
725
                        break;
2✔
726
                    case '\x07':
2✔
727
                        sub.append("\U0001F514");
2✔
728
                        break;
2✔
729
                    case '\t':
10,813✔
730
                    case '\n':
731
                        sub.push_back(ch);
10,813✔
732
                        break;
10,813✔
733

734
                    default:
1,156,038✔
735
                        if (ch <= 0x1f) {
1,156,038✔
736
                            sub.push_back(0xe2);
25✔
737
                            sub.push_back(0x90);
25✔
738
                            sub.push_back(0x80 + ch);
25✔
739
                        } else {
740
                            sub.append(&str[cp_start], lpc - cp_start);
1,156,013✔
741
                        }
742
                        break;
1,156,038✔
743
                }
744
            }
1,166,862✔
745

746
            fmt::print(file, line_style, FMT_STRING("{}"), sub);
262,500✔
747
        }
87,500✔
748
        if (href) {
87,753✔
749
            fmt::print(file, FMT_STRING("\x1b]8;;\x1b\\"));
396✔
750
        }
751
        last_point = point;
87,753✔
752
    }
87,754✔
UNCOV
753
    fmt::print(file, "\n");
×
754
}
12,581✔
755

756
void
757
print(FILE* file, const user_message& um)
178✔
758
{
759
    auto al = um.to_attr_line();
356✔
760

761
    if (endswith(al.get_string(), "\n")) {
178✔
762
        al.erase(al.length() - 1);
178✔
763
    }
764
    println(file, al);
178✔
765
}
178✔
766

767
user_message
768
to_user_message(intern_string_t src, const lnav::pcre2pp::compile_error& ce)
13✔
769
{
770
    attr_line_t pcre_error_content{ce.ce_pattern};
13✔
771

772
    lnav::snippets::regex_highlighter(pcre_error_content,
13✔
773
                                      pcre_error_content.length(),
13✔
774
                                      line_range{
775
                                          0,
776
                                          (int) pcre_error_content.length(),
13✔
777
                                      });
778
    pcre_error_content.append("\n")
13✔
779
        .append(ce.ce_offset, ' ')
13✔
780
        .append(lnav::roles::error("^ "))
13✔
781
        .append(lnav::roles::error(ce.get_message()))
26✔
782
        .with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
13✔
783

UNCOV
784
    return user_message::error(
×
785
               attr_line_t()
13✔
786
                   .append_quoted(ce.ce_pattern)
26✔
787
                   .append(" is not a valid regular expression"))
13✔
788
        .with_reason(ce.get_message())
26✔
789
        .with_snippet(lnav::console::snippet::from(src, pcre_error_content));
26✔
790
}
13✔
791

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