• 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

90.0
/src/document.sections.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 <utility>
32
#include <vector>
33

34
#include "document.sections.hh"
35

36
#include "base/enum_util.hh"
37
#include "base/itertools.hh"
38
#include "base/lnav_log.hh"
39
#include "base/opt_util.hh"
40
#include "data_scanner.hh"
41

42
namespace lnav::document {
43

44
std::optional<hier_node*>
45
hier_node::lookup_child(section_key_t key) const
88✔
46
{
47
    return make_optional_from_nullable(key.match(
88✔
UNCOV
48
        [this](const std::string& str) -> hier_node* {
×
49
            auto iter = this->hn_named_children.find(str);
72✔
50
            if (iter != this->hn_named_children.end()) {
72✔
51
                return iter->second;
68✔
52
            }
53

54
            return nullptr;
4✔
55
        },
56
        [this](size_t index) -> hier_node* {
176✔
57
            if (index < this->hn_children.size()) {
16✔
58
                return this->hn_children[index].get();
16✔
59
            }
UNCOV
60
            return nullptr;
×
61
        }));
176✔
62
}
63

64
std::optional<size_t>
65
hier_node::child_index(const hier_node* hn) const
11✔
66
{
67
    size_t retval = 0;
11✔
68

69
    for (const auto& child : this->hn_children) {
11✔
70
        if (child.get() == hn) {
11✔
71
            return retval;
11✔
72
        }
UNCOV
73
        retval += 1;
×
74
    }
75

UNCOV
76
    return std::nullopt;
×
77
}
78

79
std::optional<section_key_t>
UNCOV
80
hier_node::child_key(const hier_node* hn) const
×
81
{
UNCOV
82
    for (const auto& named_pair : this->hn_named_children) {
×
UNCOV
83
        if (named_pair.second == hn) {
×
UNCOV
84
            return named_pair.first;
×
85
        }
86
    }
87

UNCOV
88
    auto index_opt = this->child_index(hn);
×
UNCOV
89
    if (index_opt) {
×
UNCOV
90
        return section_key_t{index_opt.value()};
×
91
    }
92

UNCOV
93
    return std::nullopt;
×
94
}
95

96
std::optional<hier_node::child_neighbors_result>
97
hier_node::child_neighbors(const hier_node* hn, file_off_t offset) const
11✔
98
{
99
    auto index_opt = this->child_index(hn);
11✔
100
    if (!index_opt) {
11✔
UNCOV
101
        return std::nullopt;
×
102
    }
103

104
    child_neighbors_result retval;
11✔
105

106
    if (index_opt.value() == 0) {
11✔
107
        if (this->hn_parent != nullptr) {
11✔
108
            auto parent_neighbors_opt
109
                = this->hn_parent->child_neighbors(this, offset);
6✔
110

111
            if (parent_neighbors_opt) {
6✔
112
                retval.cnr_previous = parent_neighbors_opt->cnr_previous;
6✔
113
            }
114
        } else {
115
            retval.cnr_previous = hn;
5✔
116
        }
117
    } else {
UNCOV
118
        const auto* prev_hn = this->hn_children[index_opt.value() - 1].get();
×
119

UNCOV
120
        if (hn->hn_line_number == 0
×
UNCOV
121
            || (hn->hn_line_number - prev_hn->hn_line_number) > 1)
×
122
        {
UNCOV
123
            retval.cnr_previous = prev_hn;
×
124
        } else if (this->hn_parent != nullptr) {
×
125
            auto parent_neighbors_opt
UNCOV
126
                = this->hn_parent->child_neighbors(this, offset);
×
127

UNCOV
128
            if (parent_neighbors_opt) {
×
UNCOV
129
                retval.cnr_previous = parent_neighbors_opt->cnr_previous;
×
130
            }
131
        }
132
    }
133

134
    if (index_opt.value() == this->hn_children.size() - 1) {
11✔
135
        if (this->hn_parent != nullptr) {
5✔
136
            auto parent_neighbors_opt
UNCOV
137
                = this->hn_parent->child_neighbors(this, offset);
×
138

UNCOV
139
            if (parent_neighbors_opt) {
×
UNCOV
140
                retval.cnr_next = parent_neighbors_opt->cnr_next;
×
141
            }
142
        } else if (!hn->hn_children.empty()) {
5✔
143
            for (const auto& child : hn->hn_children) {
9✔
144
                if (child->hn_start > offset) {
9✔
145
                    retval.cnr_next = child.get();
5✔
146
                    break;
5✔
147
                }
148
            }
149
        }
150
    } else {
151
        const auto* next_hn = this->hn_children[index_opt.value() + 1].get();
6✔
152

153
        if (next_hn->hn_start > offset
6✔
154
            && (hn->hn_line_number == 0
6✔
155
                || (next_hn->hn_line_number - hn->hn_line_number) > 1))
6✔
156
        {
157
            retval.cnr_next = next_hn;
4✔
158
        } else if (this->hn_parent != nullptr) {
2✔
159
            auto parent_neighbors_opt
160
                = this->hn_parent->child_neighbors(this, offset);
2✔
161

162
            if (parent_neighbors_opt) {
2✔
163
                retval.cnr_next = parent_neighbors_opt->cnr_next;
2✔
164
            }
165
        }
166
    }
167

168
    return retval;
11✔
169
}
170

171
std::optional<hier_node::child_neighbors_result>
172
hier_node::line_neighbors(size_t ln) const
1✔
173
{
174
    if (this->hn_children.empty()) {
1✔
UNCOV
175
        return std::nullopt;
×
176
    }
177

178
    child_neighbors_result retval;
1✔
179

180
    for (const auto& child : this->hn_children) {
2✔
181
        if (child->hn_line_number > ln) {
1✔
UNCOV
182
            retval.cnr_next = child.get();
×
UNCOV
183
            break;
×
184
        }
185
        retval.cnr_previous = child.get();
1✔
186
    }
187

188
    return retval;
1✔
189
}
190

191
std::optional<const hier_node*>
192
hier_node::lookup_path(const hier_node* root,
44✔
193
                       const std::vector<section_key_t>& path)
194
{
195
    auto retval = make_optional_from_nullable(root);
44✔
196

197
    for (const auto& comp : path) {
123✔
198
        if (!retval) {
79✔
UNCOV
199
            break;
×
200
        }
201

202
        retval = retval.value()->lookup_child(comp);
79✔
203
    }
204

205
    if (!retval) {
44✔
UNCOV
206
        return std::nullopt;
×
207
    }
208

209
    return retval;
44✔
210
}
211

212
std::vector<section_key_t>
213
metadata::path_for_range(size_t start, size_t stop)
16✔
214
{
215
    std::vector<section_key_t> retval;
16✔
216

217
    this->m_sections_tree.visit_overlapping(
16✔
218
        start, stop, [&retval](const lnav::document::section_interval_t& iv) {
16✔
219
            retval.emplace_back(iv.value);
20✔
220
        });
20✔
221
    return retval;
16✔
UNCOV
222
}
×
223

224
struct metadata_builder {
225
    std::vector<section_interval_t> mb_intervals;
226
    std::vector<section_type_interval_t> mb_type_intervals;
227
    std::unique_ptr<hier_node> mb_root_node;
228
    std::set<size_t> mb_indents;
229
    text_format_t mb_text_format{text_format_t::TF_UNKNOWN};
230
    std::set<std::string> mb_words;
231

232
    metadata to_metadata() &&
200✔
233
    {
234
        return {
235
            std::move(this->mb_intervals),
200✔
236
            std::move(this->mb_root_node),
200✔
237
            std::move(this->mb_type_intervals),
200✔
238
            std::move(this->mb_indents),
200✔
239
            this->mb_text_format,
200✔
240
            this->mb_words,
200✔
241
        };
200✔
242
    }
200✔
243
};
244

245
static void
246
discover_metadata_int(const attr_line_t& al, metadata_builder& mb)
200✔
247
{
248
    const auto& orig_attrs = al.get_attrs();
200✔
249
    auto headers = orig_attrs
250
        | lnav::itertools::filter_in([](const string_attr& attr) {
400✔
251
                       if (!attr.sa_range.is_valid()) {
49,908✔
UNCOV
252
                           return false;
×
253
                       }
254

255
                       if (attr.sa_type == &VC_ANCHOR) {
49,908✔
256
                           return true;
4✔
257
                       }
258
                       if (attr.sa_type != &VC_ROLE) {
49,904✔
259
                           return false;
15,283✔
260
                       }
261

262
                       const auto role = attr.sa_value.get<role_t>();
34,621✔
263
                       switch (role) {
34,621✔
264
                           case role_t::VCR_H1:
3,087✔
265
                           case role_t::VCR_H2:
266
                           case role_t::VCR_H3:
267
                           case role_t::VCR_H4:
268
                           case role_t::VCR_H5:
269
                           case role_t::VCR_H6:
270
                               return true;
3,087✔
271
                           default:
31,534✔
272
                               return false;
31,534✔
273
                       }
274
                   })
275
        | lnav::itertools::sort_by(&string_attr::sa_range);
400✔
276

277
    // Remove headers from quoted text
278
    for (const auto& orig_attr : orig_attrs) {
50,108✔
279
        if (orig_attr.sa_type == &VC_ROLE
99,816✔
280
            && orig_attr.sa_value.get<role_t>() == role_t::VCR_QUOTED_TEXT)
49,908✔
281
        {
282
            remove_string_attr(headers, orig_attr.sa_range);
81✔
283
        }
284
    }
285

286
    auto& intervals = mb.mb_intervals;
200✔
287

288
    struct open_interval_t {
289
        open_interval_t(uint32_t level, file_off_t start, section_key_t id)
3,071✔
290
            : oi_level(level), oi_start(start), oi_id(std::move(id))
3,071✔
291
        {
292
        }
3,071✔
293

294
        int32_t oi_level;
295
        file_off_t oi_start;
296
        section_key_t oi_id;
297
        std::unique_ptr<hier_node> oi_node{std::make_unique<hier_node>()};
298
    };
299
    std::vector<open_interval_t> open_intervals;
200✔
300
    auto root_node = std::make_unique<hier_node>();
200✔
301
    const auto sf = string_fragment::from_str(al.get_string());
200✔
302

303
    for (const auto& hdr_attr : headers) {
3,291✔
304
        auto role_num = 0;
3,091✔
305
        if (hdr_attr.sa_type == &VC_ROLE) {
3,091✔
306
            const auto role = hdr_attr.sa_value.get<role_t>();
3,087✔
307
            role_num = lnav::enums::to_underlying(role)
3,087✔
308
                - lnav::enums::to_underlying(role_t::VCR_H1);
3,087✔
309
        }
310
        std::vector<open_interval_t> new_open_intervals;
3,091✔
311

312
        for (auto& oi : open_intervals) {
13,526✔
313
            if (oi.oi_level >= role_num) {
10,435✔
314
                // close out this section
315
                auto left_sf = sf.find_left_boundary(
2,970✔
316
                    hdr_attr.sa_range.lr_start, string_fragment::tag1{'\n'});
2,970✔
317
                if (left_sf.sf_begin > 0) {
2,970✔
318
                    left_sf.sf_begin -= 1;
2,970✔
319
                }
320
                intervals.emplace_back(oi.oi_start, left_sf.sf_begin, oi.oi_id);
2,970✔
321
                auto* node_ptr = oi.oi_node.get();
2,970✔
322
                auto* parent_node = oi.oi_node->hn_parent;
2,970✔
323
                if (parent_node != nullptr) {
2,970✔
324
                    parent_node->hn_children.emplace_back(
2,970✔
325
                        std::move(oi.oi_node));
2,970✔
326
                    parent_node->hn_named_children.insert({
2,970✔
327
                        oi.oi_id.get<std::string>(),
328
                        node_ptr,
329
                    });
330
                }
331
            } else {
332
                new_open_intervals.emplace_back(std::move(oi));
7,465✔
333
            }
334
        }
335
        if (hdr_attr.sa_type == &VC_ANCHOR || !hdr_attr.sa_range.empty()) {
3,091✔
336
            auto* parent_node = new_open_intervals.empty()
3,071✔
337
                ? root_node.get()
3,071✔
338
                : new_open_intervals.back().oi_node.get();
2,902✔
339
            auto left_sf = sf.find_left_boundary(hdr_attr.sa_range.lr_start,
3,071✔
UNCOV
340
                                                 string_fragment::tag1{'\n'});
×
341
            auto key = section_key_t{hdr_attr.sa_type == &VC_ANCHOR
3,071✔
342
                                         ? hdr_attr.sa_value.get<std::string>()
6,142✔
343
                                         : al.get_substring(hdr_attr.sa_range)};
3,071✔
344
            new_open_intervals.emplace_back(role_num, left_sf.sf_begin, key);
3,071✔
345
            new_open_intervals.back().oi_node->hn_parent = parent_node;
3,071✔
346
            new_open_intervals.back().oi_node->hn_start = left_sf.sf_begin;
3,071✔
347
        }
3,071✔
348
        open_intervals = std::move(new_open_intervals);
3,091✔
349
    }
3,091✔
350

351
    for (auto& oi : open_intervals) {
301✔
352
        // close out this section
353
        intervals.emplace_back(oi.oi_start, al.length(), oi.oi_id);
101✔
354
        auto* node_ptr = oi.oi_node.get();
101✔
355
        auto* parent_node = oi.oi_node->hn_parent;
101✔
356
        if (parent_node == nullptr) {
101✔
357
            root_node = std::move(oi.oi_node);
×
358
        } else {
359
            parent_node->hn_children.emplace_back(std::move(oi.oi_node));
101✔
360
            parent_node->hn_named_children.insert({
101✔
361
                oi.oi_id.get<std::string>(),
362
                node_ptr,
363
            });
364
        }
365
    }
366

367
    for (auto& interval : intervals) {
4,403✔
368
        auto start_off_iter = find_string_attr_containing(
4,203✔
369
            orig_attrs, &SA_ORIGIN_OFFSET, interval.start);
370
        if (start_off_iter != orig_attrs.end()) {
4,203✔
371
            interval.start += start_off_iter->sa_value.get<int64_t>();
54✔
372
        }
373
        auto stop_off_iter = find_string_attr_containing(
4,203✔
374
            orig_attrs, &SA_ORIGIN_OFFSET, interval.stop - 1);
4,203✔
375
        if (stop_off_iter != orig_attrs.end()) {
4,203✔
376
            interval.stop += stop_off_iter->sa_value.get<int64_t>();
51✔
377
        }
378
    }
379
    for (auto& interval : mb.mb_type_intervals) {
2,706✔
380
        auto start_off_iter = find_string_attr_containing(
2,506✔
381
            orig_attrs, &SA_ORIGIN_OFFSET, interval.start);
382
        if (start_off_iter != orig_attrs.end()) {
2,506✔
UNCOV
383
            interval.start += start_off_iter->sa_value.get<int64_t>();
×
384
        }
385
        auto stop_off_iter = find_string_attr_containing(
2,506✔
386
            orig_attrs, &SA_ORIGIN_OFFSET, interval.stop - 1);
2,506✔
387
        if (stop_off_iter != orig_attrs.end()) {
2,506✔
UNCOV
388
            interval.stop += stop_off_iter->sa_value.get<int64_t>();
×
389
        }
390
    }
391

392
    hier_node::depth_first(root_node.get(), [&orig_attrs](hier_node* node) {
200✔
393
        auto off_opt
394
            = get_string_attr(orig_attrs, &SA_ORIGIN_OFFSET, node->hn_start);
3,271✔
395

396
        if (off_opt) {
3,271✔
397
            node->hn_start += off_opt.value()->sa_value.get<int64_t>();
1,534✔
398
        }
399
    });
3,271✔
400

401
    hier_node::depth_first(
200✔
402
        mb.mb_root_node.get(), [&orig_attrs](hier_node* node) {
1,245✔
403
            auto off_opt = get_string_attr(
1,245✔
404
                orig_attrs, &SA_ORIGIN_OFFSET, node->hn_start);
1,245✔
405

406
            if (off_opt) {
1,245✔
407
                node->hn_start += off_opt.value()->sa_value.get<int64_t>();
32✔
408
            }
409
        });
1,245✔
410

411
    if (!root_node->hn_children.empty()
200✔
412
        || !root_node->hn_named_children.empty())
200✔
413
    {
414
        mb.mb_root_node = std::move(root_node);
51✔
415
    }
416
}
200✔
417

418
metadata
419
discover_metadata(const attr_line_t& al)
42✔
420
{
421
    metadata_builder mb;
42✔
422

423
    discover_metadata_int(al, mb);
42✔
424

425
    return std::move(mb).to_metadata();
84✔
426
}
42✔
427

428
class structure_walker {
429
public:
430
    explicit structure_walker(discover_builder& db)
158✔
431
        : sw_discover_builder(db), sw_line(db.db_line),
158✔
432
          sw_scanner(string_fragment::from_str_range(db.db_line.get_string(),
158✔
433
                                                     db.db_range.lr_start,
158✔
434
                                                     db.db_range.lr_end))
316✔
435
    {
436
        this->sw_interval_state.resize(1);
158✔
437
        this->sw_hier_nodes.push_back(std::make_unique<hier_node>());
158✔
438
    }
158✔
439

440
    bool is_structured_text() const
6,311✔
441
    {
442
        switch (this->sw_discover_builder.db_text_format) {
6,311✔
443
            case text_format_t::TF_JSON:
908✔
444
            case text_format_t::TF_YAML:
445
            case text_format_t::TF_TOML:
446
            case text_format_t::TF_INI:
447
            case text_format_t::TF_LOG:
448
            case text_format_t::TF_UNKNOWN:
449
                return true;
908✔
450
            default:
5,403✔
451
                return false;
5,403✔
452
        }
453
    }
454

455
    metadata walk()
158✔
456
    {
457
        metadata_builder mb;
158✔
458

459
        mb.mb_text_format = this->sw_discover_builder.db_text_format;
158✔
460
        while (true) {
461
            require(this->sw_depth == this->sw_container_tokens.size());
64,608✔
462

463
            auto tokenize_res = this->sw_scanner.tokenize2(
64,608✔
464
                this->sw_discover_builder.db_text_format);
64,608✔
465
            if (!tokenize_res) {
64,608✔
466
                break;
158✔
467
            }
468

469
            auto dt = tokenize_res->tr_token;
64,450✔
470

471
            element el(dt, tokenize_res->tr_capture);
64,450✔
472
            const auto& inner_cap = tokenize_res->tr_inner_capture;
64,450✔
473

474
#if 0
475
            printf("tok %s %s\n",
476
                   data_scanner::token2name(dt),
477
                   tokenize_res->to_string().c_str());
478
#endif
479
            if (dt != DT_WHITE) {
64,450✔
480
                this->sw_at_start = false;
40,374✔
481
            }
482
            switch (dt) {
64,450✔
483
                case DT_XML_DECL_TAG:
27✔
484
                case DT_XML_EMPTY_TAG:
485
                    this->sw_values.emplace_back(el);
27✔
486
                    break;
27✔
487
                case DT_COMMENT:
2,418✔
488
                    this->sw_type_intervals.emplace_back(
2,418✔
489
                        el.e_capture.c_begin,
490
                        el.e_capture.c_end,
491
                        section_types_t::comment);
2,418✔
492
                    this->sw_line.get_attrs().emplace_back(
4,836✔
493
                        line_range{
2,418✔
494
                            el.e_capture.c_begin,
495
                            el.e_capture.c_end,
496
                        },
497
                        VC_ROLE.value(role_t::VCR_COMMENT));
4,836✔
498
                    break;
2,418✔
499
                case DT_XML_OPEN_TAG:
487✔
500
                    this->flush_values();
487✔
501
                    this->sw_interval_state.back().is_start
487✔
502
                        = el.e_capture.c_begin;
487✔
503
                    this->sw_interval_state.back().is_line_number
487✔
504
                        = this->sw_line_number;
487✔
505
                    this->sw_interval_state.back().is_name
487✔
506
                        = tokenize_res->to_string_fragment()
487✔
507
                              .to_unquoted_string();
974✔
508
                    this->sw_depth += 1;
487✔
509
                    this->sw_interval_state.resize(this->sw_depth + 1);
487✔
510
                    this->sw_hier_nodes.push_back(
487✔
511
                        std::make_unique<hier_node>());
974✔
512
                    this->sw_container_tokens.push_back(to_closer(dt));
487✔
513
                    break;
487✔
514
                case DT_XML_CLOSE_TAG: {
466✔
515
                    auto term = this->flush_values();
466✔
516
                    if (this->sw_depth > 0
932✔
517
                        && !this->sw_container_tokens.empty())
466✔
518
                    {
519
                        auto found = false;
466✔
520
                        do {
521
                            if (this->sw_container_tokens.back() == dt) {
468✔
522
                                found = true;
464✔
523
                            }
524
                            if (term) {
468✔
525
                                this->append_child_node(term);
372✔
526
                                term = std::nullopt;
372✔
527
                            }
528
                            this->sw_interval_state.pop_back();
468✔
529
                            if (!found && this->sw_depth > 0) {
468✔
530
                                this->sw_depth -= 1;
4✔
531
                            }
532
                            this->sw_hier_stage
533
                                = std::move(this->sw_hier_nodes.back());
468✔
534
                            this->sw_hier_nodes.pop_back();
468✔
535
                            this->sw_container_tokens.pop_back();
468✔
536
                        } while (!found && !this->sw_container_tokens.empty());
468✔
537
                    }
538
                    this->append_child_node(el.e_capture);
466✔
539
                    if (this->sw_depth > 0) {
466✔
540
                        this->sw_depth -= 1;
464✔
541
                    }
542
                    this->flush_values();
466✔
543
                    break;
466✔
544
                }
545
                case DT_H1: {
124✔
546
                    this->sw_line.get_attrs().emplace_back(
248✔
547
                        line_range{
×
548
                            inner_cap.c_begin,
124✔
549
                            inner_cap.c_end,
124✔
550
                        },
551
                        VC_ROLE.value(role_t::VCR_H1));
248✔
552
                    this->sw_line_number += 1;
124✔
553
                    break;
124✔
554
                }
555
                case DT_DIFF_FILE_HEADER: {
7✔
556
                    this->drop_open_children();
7✔
557

558
                    auto sf = this->sw_scanner.to_string_fragment(inner_cap);
7✔
559
                    auto split_res = sf.split_pair(string_fragment::tag1{'\n'});
7✔
560
                    auto file1 = split_res->first.consume_n(4).value();
7✔
561
                    auto file2 = split_res->second.consume_n(4).value();
7✔
562
                    if ((file1 == "/dev/null" || file1.startswith("a/"))
13✔
563
                        && file2.startswith("b/"))
13✔
564
                    {
565
                        if (file1 != "/dev/null") {
7✔
566
                            file1 = file1.consume_n(2).value();
6✔
567
                        }
568
                        file2 = file2.consume_n(2).value();
7✔
569
                    }
570
                    this->sw_line.get_attrs().emplace_back(
14✔
571
                        line_range{
7✔
572
                            tokenize_res->tr_capture.c_begin,
7✔
573
                            tokenize_res->tr_capture.c_begin,
7✔
574
                        },
575
                        VC_ROLE.value(role_t::VCR_H1));
14✔
576
                    if (file1 == "/dev/null" || file1 == file2) {
7✔
577
                        this->sw_line.get_attrs().emplace_back(
14✔
578
                            line_range{
7✔
579
                                file2.sf_begin,
580
                                file2.sf_end,
581
                            },
582
                            VC_ROLE.value(role_t::VCR_H1));
14✔
583
                    } else {
UNCOV
584
                        this->sw_line.get_attrs().emplace_back(
×
UNCOV
585
                            line_range{
×
UNCOV
586
                                inner_cap.c_begin,
×
UNCOV
587
                                inner_cap.c_end,
×
588
                            },
UNCOV
589
                            VC_ROLE.value(role_t::VCR_H1));
×
590
                    }
591
                    this->sw_line_number += 2;
7✔
592
                    break;
7✔
593
                }
594
                case DT_DIFF_HUNK_HEADING: {
5✔
595
                    this->drop_open_children();
5✔
596
                    this->sw_line.get_attrs().emplace_back(
10✔
597
                        line_range{
5✔
598
                            tokenize_res->tr_capture.c_begin,
5✔
599
                            tokenize_res->tr_capture.c_begin,
5✔
600
                        },
601
                        VC_ROLE.value(role_t::VCR_H2));
10✔
602
                    this->sw_line.get_attrs().emplace_back(
10✔
UNCOV
603
                        line_range{
×
604
                            inner_cap.c_begin,
5✔
605
                            inner_cap.c_end,
5✔
606
                        },
607
                        VC_ROLE.value(role_t::VCR_H2));
10✔
608
                    this->sw_line_number += 1;
5✔
609
                    break;
5✔
610
                }
611
                case DT_LCURLY:
1,269✔
612
                case DT_LSQUARE:
613
                case DT_LPAREN: {
614
                    if (this->is_structured_text()) {
1,269✔
615
                        this->flush_values();
293✔
616
                        // this->append_child_node(term);
617
                        this->sw_depth += 1;
293✔
618
                        this->sw_interval_state.back().is_start
293✔
619
                            = el.e_capture.c_begin;
293✔
620
                        this->sw_interval_state.back().is_line_number
293✔
621
                            = this->sw_line_number;
293✔
622
                        this->sw_interval_state.resize(this->sw_depth + 1);
293✔
623
                        this->sw_hier_nodes.push_back(
293✔
624
                            std::make_unique<hier_node>());
586✔
625
                        this->sw_container_tokens.push_back(to_closer(dt));
293✔
626
                    } else {
627
                        this->sw_values.emplace_back(el);
976✔
628
                    }
629
                    break;
1,269✔
630
                }
631
                case DT_RCURLY:
1,347✔
632
                case DT_RSQUARE:
633
                case DT_RPAREN:
634
                    if (this->is_structured_text()
1,347✔
635
                        && !this->sw_container_tokens.empty()
286✔
636
                        && std::find(this->sw_container_tokens.begin(),
1,919✔
637
                                     this->sw_container_tokens.end(),
638
                                     dt)
639
                            != this->sw_container_tokens.end())
1,919✔
640
                    {
641
                        auto term = this->flush_values();
284✔
642
                        if (this->sw_depth > 0) {
284✔
643
                            auto found = false;
284✔
644
                            do {
645
                                if (this->sw_container_tokens.back() == dt) {
309✔
646
                                    found = true;
284✔
647
                                }
648
                                this->append_child_node(term);
309✔
649
                                term = std::nullopt;
309✔
650
                                this->sw_depth -= 1;
309✔
651
                                this->sw_interval_state.pop_back();
309✔
652
                                this->sw_hier_stage
653
                                    = std::move(this->sw_hier_nodes.back());
309✔
654
                                this->sw_hier_nodes.pop_back();
309✔
655
                                if (this->sw_interval_state.back().is_start) {
309✔
656
                                    data_scanner::capture_t obj_cap = {
657
                                        static_cast<int>(
658
                                            this->sw_interval_state.back()
309✔
659
                                                .is_start.value()),
309✔
660
                                        el.e_capture.c_end,
661
                                    };
309✔
662

663
                                    auto sf
664
                                        = this->sw_scanner.to_string_fragment(
309✔
665
                                            obj_cap);
666
                                    if (!sf.find('\n')) {
309✔
667
                                        this->sw_hier_stage->hn_named_children
203✔
668
                                            .clear();
203✔
669
                                        this->sw_hier_stage->hn_children
203✔
670
                                            .clear();
203✔
671
                                        while (
203✔
672
                                            !this->sw_intervals.empty()
376✔
673
                                            && this->sw_intervals.back().start
686✔
674
                                                > obj_cap.c_begin)
310✔
675
                                        {
676
                                            this->sw_intervals.pop_back();
173✔
677
                                        }
678
                                    }
679
                                }
680
                                this->sw_container_tokens.pop_back();
309✔
681
                            } while (!found);
309✔
682
                        }
683
                    }
684
                    this->sw_values.emplace_back(el);
1,347✔
685
                    break;
1,347✔
686
                case DT_COMMA:
3,695✔
687
                    if (this->is_structured_text()) {
3,695✔
688
                        if (this->sw_depth > 0) {
329✔
689
                            auto term = this->flush_values();
285✔
690
                            this->append_child_node(term);
285✔
691
                        }
692
                    } else {
693
                        this->sw_values.emplace_back(el);
3,366✔
694
                    }
695
                    break;
3,695✔
696
                case DT_LINE:
4,627✔
697
                    this->sw_line_number += 1;
4,627✔
698
                    this->sw_at_start = true;
4,627✔
699
                    break;
4,627✔
700
                case DT_WHITE:
24,076✔
701
                    if (this->sw_at_start) {
24,076✔
702
                        size_t indent_size = 0;
2,049✔
703

704
                        for (auto ch : tokenize_res->to_string_fragment()) {
13,243✔
705
                            if (ch == '\t') {
11,194✔
706
                                do {
707
                                    indent_size += 1;
29✔
708
                                } while (indent_size % 8);
29✔
709
                            } else {
710
                                indent_size += 1;
11,190✔
711
                            }
712
                        }
713
                        this->sw_indents.insert(indent_size);
2,049✔
714
                        this->sw_at_start = false;
2,049✔
715
                    }
716
                    break;
24,076✔
UNCOV
717
                case DT_ZERO_WIDTH_SPACE:
×
UNCOV
718
                    break;
×
719
                default: {
25,902✔
720
                    if (dt == DT_QUOTED_STRING || dt == DT_CODE_BLOCK) {
25,902✔
721
                        auto quoted_sf = tokenize_res->to_string_fragment();
1,427✔
722

723
                        if (quoted_sf.find('\n')) {
1,427✔
724
                            this->sw_type_intervals.emplace_back(
88✔
725
                                el.e_capture.c_begin,
726
                                el.e_capture.c_end,
727
                                section_types_t::multiline_string);
88✔
728
                            this->sw_line.get_attrs().emplace_back(
176✔
729
                                line_range{
88✔
730
                                    el.e_capture.c_begin,
731
                                    el.e_capture.c_end,
732
                                },
733
                                VC_ROLE.value(role_t::VCR_STRING));
176✔
734
                        }
735
                    }
736
                    if (this->sw_discover_builder.db_save_words
25,902✔
UNCOV
737
                        && (dt == DT_CONSTANT || dt == DT_ID || dt == DT_WORD
×
UNCOV
738
                            || dt == DT_SYMBOL))
×
739
                    {
UNCOV
740
                        mb.mb_words.insert(
×
UNCOV
741
                            tokenize_res->to_string_fragment().to_string());
×
742
                    }
743
                    this->sw_values.emplace_back(el);
25,902✔
744
                    break;
25,902✔
745
                }
746
            }
747

748
            ensure(this->sw_depth == this->sw_container_tokens.size());
64,450✔
749
        }
64,450✔
750
        this->flush_values();
158✔
751

752
        if (this->sw_hier_stage != nullptr) {
158✔
753
            this->sw_hier_stage->hn_parent = this->sw_hier_nodes.back().get();
43✔
754
            this->sw_hier_nodes.back()->hn_children.push_back(
86✔
755
                std::move(this->sw_hier_stage));
43✔
756
        }
757
        this->sw_hier_stage = std::move(this->sw_hier_nodes.back());
158✔
758
        this->sw_hier_nodes.pop_back();
158✔
759
        if (this->sw_hier_stage->hn_children.size() == 1
158✔
760
            && this->sw_hier_stage->hn_named_children.empty())
158✔
761
        {
762
            this->sw_hier_stage
763
                = std::move(this->sw_hier_stage->hn_children.front());
42✔
764
            this->sw_hier_stage->hn_parent = nullptr;
42✔
765
        }
766

767
        if (!this->sw_indents.empty()) {
158✔
768
            auto low_indent_iter = this->sw_indents.begin();
70✔
769

770
            if (*low_indent_iter == 1) {
70✔
771
                // adding guides for small indents is noisy, drop for now
772
                this->sw_indents.clear();
14✔
773
            } else {
774
                auto lcm = *low_indent_iter;
56✔
775

776
                for (auto indent_iter = this->sw_indents.begin();
56✔
777
                     indent_iter != this->sw_indents.end();)
172✔
778
                {
779
                    if ((*indent_iter % lcm) == 0) {
116✔
780
                        ++indent_iter;
109✔
781
                    } else {
782
                        indent_iter = this->sw_indents.erase(indent_iter);
7✔
783
                    }
784
                }
785
            }
786
        }
787

788
        mb.mb_root_node = std::move(this->sw_hier_stage);
158✔
789
        mb.mb_intervals = std::move(this->sw_intervals);
158✔
790
        mb.mb_type_intervals = std::move(this->sw_type_intervals);
158✔
791
        mb.mb_indents = std::move(this->sw_indents);
158✔
792

793
        discover_metadata_int(this->sw_line, mb);
158✔
794

795
        return std::move(mb).to_metadata();
316✔
796
    }
158✔
797

798
private:
799
    struct element {
800
        element(data_token_t token, data_scanner::capture_t& cap)
64,450✔
801
            : e_token(token), e_capture(cap)
64,450✔
802
        {
803
        }
64,450✔
804

805
        data_token_t e_token;
806
        data_scanner::capture_t e_capture;
807
    };
808

809
    struct interval_state {
810
        std::optional<file_off_t> is_start;
811
        size_t is_line_number{0};
812
        std::string is_name;
813
    };
814

815
    void drop_open_children()
12✔
816
    {
817
        while (this->sw_depth > 0) {
12✔
UNCOV
818
            this->sw_depth -= 1;
×
UNCOV
819
            this->sw_interval_state.pop_back();
×
UNCOV
820
            this->sw_hier_nodes.pop_back();
×
UNCOV
821
            this->sw_container_tokens.pop_back();
×
822
        }
823
    }
12✔
824

825
    std::optional<data_scanner::capture_t> flush_values()
2,439✔
826
    {
827
        std::optional<data_scanner::capture_t> last_key;
2,439✔
828
        std::optional<data_scanner::capture_t> retval;
2,439✔
829

830
        if (!this->sw_values.empty()) {
2,439✔
831
            if (!this->sw_interval_state.back().is_start) {
1,313✔
832
                this->sw_interval_state.back().is_start
1,029✔
833
                    = this->sw_values.front().e_capture.c_begin;
1,029✔
834
                this->sw_interval_state.back().is_line_number
1,029✔
835
                    = this->sw_line_number;
1,029✔
836
            }
837
            retval = this->sw_values.back().e_capture;
1,313✔
838
        }
839
        for (const auto& el : this->sw_values) {
34,057✔
840
            switch (el.e_token) {
31,618✔
841
                case DT_SYMBOL:
20,631✔
842
                case DT_CONSTANT:
843
                case DT_WORD:
844
                case DT_QUOTED_STRING:
845
                    last_key = el.e_capture;
20,631✔
846
                    break;
20,631✔
847
                case DT_COLON:
659✔
848
                case DT_EQUALS:
849
                    if (last_key) {
659✔
850
                        this->sw_interval_state.back().is_name
576✔
851
                            = this->sw_scanner
852
                                  .to_string_fragment(last_key.value())
576✔
853
                                  .to_unquoted_string();
1,152✔
854
                        if (!this->sw_interval_state.back().is_name.empty()) {
576✔
855
                            this->sw_interval_state.back().is_start
576✔
UNCOV
856
                                = static_cast<ssize_t>(
×
857
                                    last_key.value().c_begin);
576✔
858
                            this->sw_interval_state.back().is_line_number
576✔
859
                                = this->sw_line_number;
576✔
860
                        }
861
                        last_key = std::nullopt;
576✔
862
                    }
863
                    break;
659✔
864
                default:
10,328✔
865
                    break;
10,328✔
866
            }
867
        }
868

869
        this->sw_values.clear();
2,439✔
870

871
        return retval;
2,439✔
872
    }
873

874
    void append_child_node(std::optional<data_scanner::capture_t> terminator)
1,432✔
875
    {
876
        auto& ivstate = this->sw_interval_state.back();
1,432✔
877
        if (!ivstate.is_start || !terminator || this->sw_depth == 0) {
1,432✔
878
            ivstate.is_start = std::nullopt;
31✔
879
            ivstate.is_line_number = 0;
31✔
880
            ivstate.is_name.clear();
31✔
881
            return;
31✔
882
        }
883

884
        auto new_node = this->sw_hier_stage != nullptr
1,401✔
885
            ? std::move(this->sw_hier_stage)
1,401✔
886
            : std::make_unique<lnav::document::hier_node>();
1,401✔
887
        auto iv_start = ivstate.is_start.value();
1,401✔
888
        auto iv_stop = static_cast<ssize_t>(terminator.value().c_end);
1,401✔
889
        auto* top_node = this->sw_hier_nodes.back().get();
1,401✔
890
        auto new_key = ivstate.is_name.empty()
1,401✔
891
            ? lnav::document::section_key_t{top_node->hn_children.size()}
1,401✔
892
            : lnav::document::section_key_t{ivstate.is_name};
1,401✔
893
        auto* retval = new_node.get();
1,401✔
894
        new_node->hn_parent = top_node;
1,401✔
895
        new_node->hn_start = iv_start;
1,401✔
896
        new_node->hn_line_number = ivstate.is_line_number;
1,401✔
897
        if (this->sw_depth == 1
2,802✔
898
            || new_node->hn_line_number != top_node->hn_line_number)
1,401✔
899
        {
900
            this->sw_intervals.emplace_back(iv_start, iv_stop, new_key);
1,305✔
901
            if (!ivstate.is_name.empty()) {
1,305✔
902
                top_node->hn_named_children.insert({
690✔
903
                    ivstate.is_name,
690✔
904
                    retval,
905
                });
906
            }
907
            top_node->hn_children.emplace_back(std::move(new_node));
1,305✔
908
        }
909
        ivstate.is_start = std::nullopt;
1,401✔
910
        ivstate.is_line_number = 0;
1,401✔
911
        ivstate.is_name.clear();
1,401✔
912
    }
1,401✔
913

914
    discover_builder& sw_discover_builder;
915
    attr_line_t& sw_line;
916
    data_scanner sw_scanner;
917
    int sw_depth{0};
918
    size_t sw_line_number{0};
919
    bool sw_at_start{true};
920
    std::set<size_t> sw_indents;
921
    std::vector<element> sw_values{};
922
    std::vector<data_token_t> sw_container_tokens;
923
    std::vector<interval_state> sw_interval_state;
924
    std::vector<section_interval_t> sw_intervals;
925
    std::vector<section_type_interval_t> sw_type_intervals;
926
    std::vector<std::unique_ptr<hier_node>> sw_hier_nodes;
927
    std::unique_ptr<hier_node> sw_hier_stage;
928
};
929

930
metadata
931
discover_builder::perform()
158✔
932
{
933
    return structure_walker(*this).walk();
158✔
934
}
935

936
std::vector<breadcrumb::possibility>
937
metadata::possibility_provider(const std::vector<section_key_t>& path)
14✔
938
{
939
    std::vector<breadcrumb::possibility> retval;
14✔
940
    auto curr_node = hier_node::lookup_path(this->m_sections_root.get(), path);
14✔
941
    if (curr_node) {
14✔
942
        auto* parent_node = curr_node.value()->hn_parent;
14✔
943

944
        if (parent_node != nullptr) {
14✔
945
            for (const auto& sibling : parent_node->hn_named_children) {
67✔
946
                retval.emplace_back(sibling.first);
53✔
947
            }
948
        }
949
    }
950
    return retval;
28✔
UNCOV
951
}
×
952

953
}  // namespace lnav::document
954

955
namespace fmt {
956
auto
957
formatter<lnav::document::section_key_t>::format(
6✔
958
    const lnav::document::section_key_t& key, fmt::format_context& ctx)
959
    -> decltype(ctx.out()) const
960
{
961
    return key.match(
6✔
UNCOV
962
        [this, &ctx](const std::string& str) {
×
963
            return formatter<string_view>::format(str, ctx);
8✔
964
        },
965
        [&ctx](size_t index) {
12✔
966
            return format_to(ctx.out(), FMT_STRING("{}"), index);
10✔
967
        });
12✔
968
}
969

970
auto
971
formatter<std::vector<lnav::document::section_key_t>>::format(
2✔
972
    const std::vector<lnav::document::section_key_t>& path,
973
    fmt::format_context& ctx) -> decltype(ctx.out()) const
974
{
975
    for (const auto& part : path) {
8✔
976
        format_to(ctx.out(), FMT_STRING("\uff1a"));
24✔
977
        format_to(ctx.out(), FMT_STRING("{}"), part);
30✔
978
    }
979
    return ctx.out();
2✔
980
}
981
}  // namespace fmt
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