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

tstack / lnav / 18921488161-2616

29 Oct 2025 08:39PM UTC coverage: 68.075% (-0.8%) from 68.921%
18921488161-2616

push

github

tstack
[files-panel] show file load progress

18 of 23 new or added lines in 6 files covered. (78.26%)

607 existing lines in 26 files now uncovered.

49621 of 72892 relevant lines covered (68.07%)

427861.28 hits per line

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

90.86
/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✔
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
            }
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
        }
73
        retval += 1;
×
74
    }
75

76
    return std::nullopt;
×
77
}
78

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

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

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✔
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 {
118
        const auto* prev_hn = this->hn_children[index_opt.value() - 1].get();
×
119

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

128
            if (parent_neighbors_opt) {
×
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
137
                = this->hn_parent->child_neighbors(this, offset);
×
138

139
            if (parent_neighbors_opt) {
×
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✔
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✔
182
            retval.cnr_next = child.get();
×
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,
53✔
193
                       const std::vector<section_key_t>& path)
194
{
195
    auto retval = make_optional_from_nullable(root);
53✔
196

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

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

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

209
    return retval;
53✔
210
}
211

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

217
    this->m_sections_tree.visit_overlapping(
17✔
218
        start, stop, [&retval](const lnav::document::section_interval_t& iv) {
17✔
219
            retval.emplace_back(iv.value);
20✔
220
        });
20✔
221
    return retval;
17✔
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() &&
237✔
233
    {
234
        return {
235
            std::move(this->mb_intervals),
237✔
236
            std::move(this->mb_root_node),
237✔
237
            std::move(this->mb_type_intervals),
237✔
238
            std::move(this->mb_indents),
237✔
239
            this->mb_text_format,
237✔
240
            this->mb_words,
237✔
241
        };
237✔
242
    }
237✔
243
};
244

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

255
                       if (attr.sa_type == &VC_ANCHOR) {
55,459✔
256
                           return true;
4✔
257
                       }
258
                       if (attr.sa_type != &VC_ROLE) {
55,455✔
259
                           return false;
16,499✔
260
                       }
261

262
                       const auto role = attr.sa_value.get<role_t>();
38,956✔
263
                       switch (role) {
38,956✔
264
                           case role_t::VCR_H1:
3,320✔
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,320✔
271
                           default:
35,636✔
272
                               return false;
35,636✔
273
                       }
274
                   })
275
        | lnav::itertools::sort_by(&string_attr::sa_range);
474✔
276

277
    // Remove headers from quoted text
278
    for (const auto& orig_attr : orig_attrs) {
55,696✔
279
        if (orig_attr.sa_type == &VC_ROLE
110,918✔
280
            && orig_attr.sa_value.get<role_t>() == role_t::VCR_QUOTED_TEXT)
55,459✔
281
        {
282
            remove_string_attr(headers, orig_attr.sa_range);
81✔
283
        }
284
    }
285

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

288
    struct open_interval_t {
289
        open_interval_t(uint32_t level, file_off_t start, section_key_t id)
3,291✔
290
            : oi_level(level), oi_start(start), oi_id(std::move(id))
3,291✔
291
        {
292
        }
3,291✔
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;
237✔
300
    auto root_node = std::make_unique<hier_node>();
237✔
301
    const auto sf = string_fragment::from_str(al.get_string());
237✔
302

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

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

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

367
    for (auto& interval : intervals) {
4,660✔
368
        auto start_off_iter = find_string_attr_containing(
4,423✔
369
            orig_attrs, &SA_ORIGIN_OFFSET, interval.start);
370
        if (start_off_iter != orig_attrs.end()) {
4,423✔
371
            interval.start += start_off_iter->sa_value.get<int64_t>();
57✔
372
        }
373
        auto stop_off_iter = find_string_attr_containing(
4,423✔
374
            orig_attrs, &SA_ORIGIN_OFFSET, interval.stop - 1);
4,423✔
375
        if (stop_off_iter != orig_attrs.end()) {
4,423✔
376
            interval.stop += stop_off_iter->sa_value.get<int64_t>();
57✔
377
        }
378
    }
379
    for (auto& interval : mb.mb_type_intervals) {
5,216✔
380
        auto start_off_iter = find_string_attr_containing(
4,979✔
381
            orig_attrs, &SA_ORIGIN_OFFSET, interval.start);
382
        if (start_off_iter != orig_attrs.end()) {
4,979✔
383
            interval.start += start_off_iter->sa_value.get<int64_t>();
×
384
        }
385
        auto stop_off_iter = find_string_attr_containing(
4,979✔
386
            orig_attrs, &SA_ORIGIN_OFFSET, interval.stop - 1);
4,979✔
387
        if (stop_off_iter != orig_attrs.end()) {
4,979✔
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) {
237✔
393
        auto off_opt
394
            = get_string_attr(orig_attrs, &SA_ORIGIN_OFFSET, node->hn_start);
3,528✔
395

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

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

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

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

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

423
    discover_metadata_int(al, mb);
69✔
424

425
    return std::move(mb).to_metadata();
138✔
426
}
69✔
427

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

440
    bool is_structured_text() const
10,061✔
441
    {
442
        switch (this->sw_discover_builder.db_text_format) {
10,061✔
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:
9,153✔
451
                return false;
9,153✔
452
        }
453
    }
454

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

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

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

469
            auto dt = tokenize_res->tr_token;
99,422✔
470

471
            element el(dt, tokenize_res->tr_capture);
99,422✔
472
            const auto& inner_cap = tokenize_res->tr_inner_capture;
99,422✔
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) {
99,422✔
480
                this->sw_at_start = false;
60,693✔
481
            }
482
            switch (dt) {
99,422✔
483
                case DT_XML_DECL_TAG:
28✔
484
                case DT_XML_EMPTY_TAG:
485
                    this->sw_values.emplace_back(el);
28✔
486
                    break;
28✔
487
                case DT_COMMENT:
4,882✔
488
                    this->sw_type_intervals.emplace_back(
4,882✔
489
                        el.e_capture.c_begin,
490
                        el.e_capture.c_end,
491
                        section_types_t::comment);
4,882✔
492
                    this->sw_line.get_attrs().emplace_back(
9,764✔
493
                        line_range{
4,882✔
494
                            el.e_capture.c_begin,
495
                            el.e_capture.c_end,
496
                        },
497
                        VC_ROLE.value(role_t::VCR_COMMENT));
9,764✔
498
                    break;
4,882✔
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: {
228✔
546
                    this->sw_line.get_attrs().emplace_back(
456✔
547
                        line_range{
×
548
                            inner_cap.c_begin,
228✔
549
                            inner_cap.c_end,
228✔
550
                        },
551
                        VC_ROLE.value(role_t::VCR_H1));
456✔
552
                    this->sw_line_number += 1;
228✔
553
                    break;
228✔
554
                }
555
                case DT_DIFF_FILE_HEADER: {
13✔
556
                    this->drop_open_children();
13✔
557

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

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

702
                        for (auto ch : tokenize_res->to_string_fragment()) {
16,086✔
703
                            if (ch == '\t') {
13,205✔
704
                                do {
705
                                    indent_size += 1;
29✔
706
                                } while (indent_size % 8);
29✔
707
                            } else {
708
                                indent_size += 1;
13,201✔
709
                            }
710
                        }
711
                        this->sw_indents.insert(indent_size);
2,881✔
712
                        this->sw_at_start = false;
2,881✔
713
                    }
714
                    break;
38,729✔
715
                case DT_ZERO_WIDTH_SPACE:
×
716
                    break;
×
717
                default: {
38,307✔
718
                    if (dt == DT_QUOTED_STRING || dt == DT_CODE_BLOCK) {
38,307✔
719
                        auto quoted_sf = tokenize_res->to_string_fragment();
2,273✔
720

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

746
            ensure(this->sw_depth == this->sw_container_tokens.size());
99,422✔
747
        }
99,422✔
748
        this->flush_values();
168✔
749

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

765
        if (!this->sw_indents.empty()) {
168✔
766
            auto low_indent_iter = this->sw_indents.begin();
73✔
767

768
            if (*low_indent_iter == 1) {
73✔
769
                // adding guides for small indents is noisy, drop for now
770
                this->sw_indents.clear();
15✔
771
            } else {
772
                auto lcm = *low_indent_iter;
58✔
773

774
                for (auto indent_iter = this->sw_indents.begin();
58✔
775
                     indent_iter != this->sw_indents.end();)
181✔
776
                {
777
                    if ((*indent_iter % lcm) == 0) {
123✔
778
                        ++indent_iter;
114✔
779
                    } else {
780
                        indent_iter = this->sw_indents.erase(indent_iter);
9✔
781
                    }
782
                }
783
            }
784
        }
785

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

791
        discover_metadata_int(this->sw_line, mb);
168✔
792

793
        return std::move(mb).to_metadata();
336✔
794
    }
168✔
795

796
private:
797
    struct element {
798
        element(data_token_t token, data_scanner::capture_t& cap)
99,422✔
799
            : e_token(token), e_capture(cap)
99,422✔
800
        {
801
        }
99,422✔
802

803
        data_token_t e_token;
804
        data_scanner::capture_t e_capture;
805
    };
806

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

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

823
    std::optional<data_scanner::capture_t> flush_values()
2,449✔
824
    {
825
        std::optional<data_scanner::capture_t> last_key;
2,449✔
826
        std::optional<data_scanner::capture_t> retval;
2,449✔
827

828
        if (!this->sw_values.empty()) {
2,449✔
829
            if (!this->sw_interval_state.back().is_start) {
1,320✔
830
                this->sw_interval_state.back().is_start
1,036✔
831
                    = this->sw_values.front().e_capture.c_begin;
1,036✔
832
                this->sw_interval_state.back().is_line_number
1,036✔
833
                    = this->sw_line_number;
1,036✔
834
            }
835
            retval = this->sw_values.back().e_capture;
1,320✔
836
        }
837
        for (const auto& el : this->sw_values) {
50,223✔
838
            switch (el.e_token) {
47,774✔
839
                case DT_SYMBOL:
32,321✔
840
                case DT_CONSTANT:
841
                case DT_WORD:
842
                case DT_QUOTED_STRING:
843
                    last_key = el.e_capture;
32,321✔
844
                    break;
32,321✔
845
                case DT_COLON:
694✔
846
                case DT_EQUALS:
847
                    if (last_key) {
694✔
848
                        this->sw_interval_state.back().is_name
609✔
849
                            = this->sw_scanner
850
                                  .to_string_fragment(last_key.value())
609✔
851
                                  .to_unquoted_string();
1,218✔
852
                        if (!this->sw_interval_state.back().is_name.empty()) {
609✔
853
                            this->sw_interval_state.back().is_start
609✔
854
                                = static_cast<ssize_t>(
×
855
                                    last_key.value().c_begin);
609✔
856
                            this->sw_interval_state.back().is_line_number
609✔
857
                                = this->sw_line_number;
609✔
858
                        }
859
                        last_key = std::nullopt;
609✔
860
                    }
861
                    break;
694✔
862
                default:
14,759✔
863
                    break;
14,759✔
864
            }
865
        }
866

867
        this->sw_values.clear();
2,449✔
868

869
        return retval;
2,449✔
870
    }
871

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

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

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

928
metadata
929
discover_builder::perform()
168✔
930
{
931
    return structure_walker(*this).walk();
168✔
932
}
933

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

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

951
}  // namespace lnav::document
952

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

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