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

tstack / lnav / 20114321350-2741

10 Dec 2025 09:41PM UTC coverage: 68.836% (-0.07%) from 68.908%
20114321350-2741

push

github

tstack
[filters] add level filter

41 of 135 new or added lines in 10 files covered. (30.37%)

447 existing lines in 8 files now uncovered.

51534 of 74865 relevant lines covered (68.84%)

434761.7 hits per line

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

58.81
/src/plain_text_source.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 "plain_text_source.hh"
31

32
#include "base/itertools.hh"
33
#include "config.h"
34
#include "document.sections.hh"
35
#include "scn/scan.h"
36

37
static std::vector<plain_text_source::text_line>
38
to_text_line(const std::vector<attr_line_t>& lines)
×
39
{
40
    file_off_t off = 0;
×
41

42
    return lines | lnav::itertools::map([&off](const auto& elem) {
×
43
               auto retval = plain_text_source::text_line{
×
44
                   off,
45
                   elem,
46
               };
47

48
               off += elem.length() + 1;
×
49
               return retval;
×
50
           });
×
51
}
52

53
plain_text_source::plain_text_source(const string_fragment& text)
×
54
{
55
    size_t start = 0;
×
56

57
    for (const auto& line : text.split_lines()) {
×
58
        this->tds_lines.emplace_back(start, attr_line_t::from_ansi_frag(line));
×
59
        start += line.length();
×
60
    }
61
    this->tds_longest_line = this->compute_longest_line();
×
62
}
63

64
plain_text_source::plain_text_source(const std::vector<std::string>& text_lines)
×
65
{
66
    this->replace_with(text_lines);
×
67
}
68

69
plain_text_source::plain_text_source(const std::vector<attr_line_t>& text_lines)
×
70
    : tds_lines(to_text_line(text_lines))
×
71
{
72
    this->tds_longest_line = this->compute_longest_line();
×
73
}
74

75
plain_text_source&
76
plain_text_source::replace_with(const attr_line_t& text_lines)
73✔
77
{
78
    this->tds_lines.clear();
73✔
79
    this->tds_doc_sections = lnav::document::discover_metadata(text_lines);
73✔
80
    file_off_t off = 0;
73✔
81
    auto lines = text_lines.split_lines();
73✔
82
    while (!lines.empty() && lines.back().empty()) {
73✔
83
        lines.pop_back();
×
84
    }
85
    for (auto& line : lines) {
20,643✔
86
        auto line_len = line.al_string.size() + 1;
20,570✔
87
        this->tds_lines.emplace_back(off, std::move(line));
20,570✔
88
        off += line_len;
20,570✔
89
    }
90
    this->tds_longest_line = this->compute_longest_line();
73✔
91
    if (this->tss_view != nullptr) {
73✔
92
        if (this->tss_view->get_selection()) {
40✔
93
            this->tss_view->set_selection(0_vl);
38✔
94
        }
95
        this->tss_view->set_needs_update();
40✔
96
    }
97
    return *this;
73✔
98
}
73✔
99

100
plain_text_source&
101
plain_text_source::replace_with_mutable(attr_line_t& text_lines,
23✔
102
                                        text_format_t tf)
103
{
104
    this->tds_text_format = tf;
23✔
105
    this->tds_lines.clear();
23✔
106
    this->tds_doc_sections
107
        = lnav::document::discover(text_lines).with_text_format(tf).perform();
23✔
108
    file_off_t off = 0;
23✔
109
    auto lines = text_lines.split_lines();
23✔
110
    while (!lines.empty() && lines.back().empty()) {
23✔
UNCOV
111
        lines.pop_back();
×
112
    }
113
    for (auto& line : lines) {
8,498✔
114
        auto line_len = line.length() + 1;
8,475✔
115
        this->tds_lines.emplace_back(off, std::move(line));
8,475✔
116
        off += line_len;
8,475✔
117
    }
118
    this->tds_longest_line = this->compute_longest_line();
23✔
119
    if (this->tss_view != nullptr) {
23✔
120
        if (this->tss_view->get_selection()) {
4✔
121
            this->tss_view->set_selection(0_vl);
4✔
122
        }
123
        this->tss_view->set_needs_update();
4✔
124
    }
125
    return *this;
23✔
126
}
23✔
127

128
plain_text_source&
129
plain_text_source::replace_with(const std::vector<std::string>& text_lines)
×
130
{
UNCOV
131
    file_off_t off = 0;
×
132
    for (const auto& str : text_lines) {
×
133
        this->tds_lines.emplace_back(off, attr_line_t::from_ansi_str(str));
×
134
        off += str.length() + 1;
×
135
    }
UNCOV
136
    this->tds_longest_line = this->compute_longest_line();
×
137
    if (this->tss_view != nullptr) {
×
UNCOV
138
        if (this->tss_view->get_selection()) {
×
UNCOV
139
            this->tss_view->set_selection(0_vl);
×
140
        }
UNCOV
141
        this->tss_view->set_needs_update();
×
142
    }
UNCOV
143
    return *this;
×
144
}
145

146
plain_text_source&
147
plain_text_source::replace_with(const std::vector<attr_line_t>& text_lines)
21✔
148
{
149
    file_off_t off = 0;
21✔
150
    this->tds_lines.clear();
21✔
151
    for (const auto& al : text_lines) {
454✔
152
        this->tds_lines.emplace_back(off, al);
433✔
153
        off += al.length() + 1;
433✔
154
    }
155
    this->tds_longest_line = this->compute_longest_line();
21✔
156
    if (this->tss_view != nullptr) {
21✔
157
        if (this->tss_view->get_selection()) {
21✔
158
            this->tss_view->set_selection(0_vl);
2✔
159
        }
160
        this->tss_view->set_needs_update();
21✔
161
    }
162
    return *this;
21✔
163
}
164

165
void
166
plain_text_source::clear()
456✔
167
{
168
    this->tds_lines.clear();
456✔
169
    this->tds_longest_line = 0;
456✔
170
    this->tds_text_format = text_format_t::TF_UNKNOWN;
456✔
171
    if (this->tss_view != nullptr) {
456✔
172
        this->tss_view->set_needs_update();
377✔
173
    }
174
}
456✔
175

176
plain_text_source&
177
plain_text_source::truncate_to(size_t max_lines)
×
178
{
UNCOV
179
    while (this->tds_lines.size() > max_lines) {
×
UNCOV
180
        this->tds_lines.pop_back();
×
181
    }
UNCOV
182
    if (this->tss_view != nullptr) {
×
UNCOV
183
        this->tss_view->set_needs_update();
×
184
    }
UNCOV
185
    return *this;
×
186
}
187

188
size_t
189
plain_text_source::text_line_width(textview_curses& curses)
5,334✔
190
{
191
    return this->tds_longest_line;
5,334✔
192
}
193

194
line_info
195
plain_text_source::text_value_for_line(textview_curses& tc,
7,525✔
196
                                       int row,
197
                                       std::string& value_out,
198
                                       text_sub_source::line_flags_t flags)
199
{
200
    value_out = this->tds_lines[row].tl_value.get_string();
7,525✔
201
    this->tds_line_indent_size = 0;
7,525✔
202
    for (const auto& ch : value_out) {
22,280✔
203
        if (ch == ' ') {
21,026✔
204
            this->tds_line_indent_size += 1;
14,755✔
205
        } else if (ch == '\t') {
6,271✔
206
            do {
UNCOV
207
                this->tds_line_indent_size += 1;
×
UNCOV
208
            } while (this->tds_line_indent_size % 8);
×
209
        } else {
210
            break;
6,271✔
211
        }
212
    }
213

214
    return {};
7,525✔
215
}
216

217
void
218
plain_text_source::text_attrs_for_line(textview_curses& tc,
7,525✔
219
                                       int line,
220
                                       string_attrs_t& value_out)
221
{
222
    value_out = this->tds_lines[line].tl_value.get_attrs();
7,525✔
UNCOV
223
    if (this->tds_reverse_selection && tc.is_selectable()
×
224
        && tc.get_selection() == line)
7,525✔
225
    {
UNCOV
226
        value_out.emplace_back(line_range{0, -1},
×
UNCOV
227
                               VC_STYLE.value(text_attrs::with_reverse()));
×
228
    }
229
    for (const auto& indent : this->tds_doc_sections.m_indents) {
10,100✔
230
        if (indent < this->tds_line_indent_size) {
2,575✔
231
            auto guide_lr = line_range{
232
                (int) indent,
465✔
233
                (int) (indent + 1),
465✔
234
                line_range::unit::codepoint,
235
            };
465✔
236
            value_out.emplace_back(guide_lr,
465✔
237
                                   VC_BLOCK_ELEM.value(block_elem_t{
930✔
238
                                       L'\u258f', role_t::VCR_INDENT_GUIDE}));
239
        }
240
    }
241
}
7,525✔
242

243
size_t
UNCOV
244
plain_text_source::text_size_for_line(textview_curses& tc,
×
245
                                      int row,
246
                                      text_sub_source::line_flags_t flags)
247
{
UNCOV
248
    return this->tds_lines[row].tl_value.length();
×
249
}
250

251
text_format_t
252
plain_text_source::get_text_format() const
5,441✔
253
{
254
    return this->tds_text_format;
5,441✔
255
}
256

257
size_t
258
plain_text_source::compute_longest_line()
117✔
259
{
260
    size_t retval = 0;
117✔
261
    for (auto& iter : this->tds_lines) {
29,595✔
262
        retval = std::max(retval, (size_t) iter.tl_value.length());
29,478✔
263
    }
264
    return retval;
117✔
265
}
266

267
std::optional<vis_line_t>
268
plain_text_source::line_for_offset(file_off_t off) const
6✔
269
{
270
    struct cmper {
271
        bool operator()(const file_off_t& lhs, const text_line& rhs) const
272
        {
273
            return lhs < rhs.tl_offset;
274
        }
275

276
        bool operator()(const text_line& lhs, const file_off_t& rhs) const
57✔
277
        {
278
            return lhs.tl_offset < rhs;
57✔
279
        }
280
    };
281

282
    if (this->tds_lines.empty()) {
6✔
283
        return std::nullopt;
×
284
    }
285

286
    log_trace("line_for_offset(%lld)", off);
6✔
287
    auto iter = std::lower_bound(
6✔
288
        this->tds_lines.begin(), this->tds_lines.end(), off, cmper{});
289
    if (iter == this->tds_lines.end()) {
6✔
UNCOV
290
        if (this->tds_lines.back().contains_offset(off)) {
×
UNCOV
291
            return std::make_optional(
×
UNCOV
292
                vis_line_t(std::distance(this->tds_lines.end() - 1, iter)));
×
293
        }
UNCOV
294
        return std::nullopt;
×
295
    }
296

297
    if (!iter->contains_offset(off) && iter != this->tds_lines.begin()) {
6✔
298
        log_trace("  lower_bound (%lld) does not contain offset",
2✔
299
                  iter->tl_offset);
300
        --iter;
2✔
301
    }
302

303
    auto retval = vis_line_t(std::distance(this->tds_lines.begin(), iter));
12✔
304
    log_trace("  retval=%d", (int) retval);
6✔
305
    return retval;
6✔
306
}
307

308
void
309
plain_text_source::text_crumbs_for_line(int line,
2✔
310
                                        std::vector<breadcrumb::crumb>& crumbs)
311
{
312
    const auto initial_size = crumbs.size();
2✔
313
    const auto& tl = this->tds_lines[line];
2✔
314

315
    this->tds_doc_sections.m_sections_tree.visit_overlapping(
4✔
316
        tl.tl_offset,
2✔
317
        tl.tl_offset + tl.tl_value.length(),
2✔
318
        [&crumbs, initial_size, meta = &this->tds_doc_sections, this](
2✔
319
            const auto& iv) {
320
            auto path = crumbs | lnav::itertools::skip(initial_size)
12✔
321
                | lnav::itertools::map(&breadcrumb::crumb::c_key)
8✔
322
                | lnav::itertools::append(iv.value);
4✔
323
            crumbs.emplace_back(
8✔
324
                iv.value,
4✔
325
                [meta, path]() { return meta->possibility_provider(path); },
12✔
326
                [this, meta, path](const auto& key) {
8✔
327
                    auto curr_node = lnav::document::hier_node::lookup_path(
×
UNCOV
328
                        meta->m_sections_root.get(), path);
×
329
                    if (!curr_node) {
×
330
                        return;
×
331
                    }
332
                    auto* parent_node = curr_node.value()->hn_parent;
×
333

334
                    if (parent_node == nullptr) {
×
335
                        return;
×
336
                    }
337
                    key.match(
×
338
                        [this, parent_node](const std::string& str) {
×
339
                            auto sib_iter
340
                                = parent_node->hn_named_children.find(str);
×
341
                            if (sib_iter
×
UNCOV
342
                                == parent_node->hn_named_children.end()) {
×
UNCOV
343
                                return;
×
344
                            }
UNCOV
345
                            this->line_for_offset(sib_iter->second->hn_start) |
×
346
                                [this](const auto new_top) {
×
347
                                    this->tss_view->set_selection(new_top);
×
348
                                    if (this->tss_view->is_selectable()) {
×
UNCOV
349
                                        this->tss_view->set_top(new_top - 2_vl,
×
350
                                                                false);
351
                                    }
352
                                };
353
                        },
354
                        [this, parent_node](size_t index) {
×
355
                            if (index >= parent_node->hn_children.size()) {
×
UNCOV
356
                                return;
×
357
                            }
UNCOV
358
                            auto sib = parent_node->hn_children[index].get();
×
UNCOV
359
                            this->line_for_offset(sib->hn_start) |
×
UNCOV
360
                                [this](const auto new_top) {
×
UNCOV
361
                                    this->tss_view->set_selection(new_top);
×
UNCOV
362
                                    if (this->tss_view->is_selectable()) {
×
UNCOV
363
                                        this->tss_view->set_top(new_top - 2_vl,
×
364
                                                                false);
365
                                    }
366
                                };
367
                        });
368
                });
369
        });
4✔
370

371
    auto path = crumbs | lnav::itertools::skip(initial_size)
4✔
372
        | lnav::itertools::map(&breadcrumb::crumb::c_key);
4✔
373
    auto node = lnav::document::hier_node::lookup_path(
2✔
374
        this->tds_doc_sections.m_sections_root.get(), path);
2✔
375

376
    if (node && !node.value()->hn_children.empty()) {
2✔
377
        auto poss_provider = [curr_node = node.value()]() {
1✔
378
            std::vector<breadcrumb::possibility> retval;
1✔
379
            for (const auto& child : curr_node->hn_named_children) {
2✔
380
                retval.emplace_back(child.first);
1✔
381
            }
382
            return retval;
1✔
383
        };
384
        auto path_performer = [this, curr_node = node.value()](
2✔
385
                                  const breadcrumb::crumb::key_t& value) {
UNCOV
386
            value.match(
×
387
                [this, curr_node](const std::string& str) {
×
388
                    auto child_iter = curr_node->hn_named_children.find(str);
×
389
                    if (child_iter != curr_node->hn_named_children.end()) {
×
390
                        this->line_for_offset(child_iter->second->hn_start) |
×
391
                            [this](const auto new_top) {
×
392
                                this->tss_view->set_selection(new_top);
×
393
                            };
394
                    }
UNCOV
395
                },
×
UNCOV
396
                [this, curr_node](size_t index) {
×
UNCOV
397
                    auto* child = curr_node->hn_children[index].get();
×
UNCOV
398
                    this->line_for_offset(child->hn_start) |
×
UNCOV
399
                        [this](const auto new_top) {
×
UNCOV
400
                            this->tss_view->set_selection(new_top);
×
401
                        };
UNCOV
402
                });
×
403
        };
1✔
404
        crumbs.emplace_back(
1✔
405
            "", "\u22ef", std::move(poss_provider), std::move(path_performer));
1✔
406
        crumbs.back().c_expected_input = node.value()->hn_named_children.empty()
2✔
407
            ? breadcrumb::crumb::expected_input_t::index
1✔
408
            : breadcrumb::crumb::expected_input_t::index_or_exact;
409
    }
410
}
2✔
411

412
std::optional<vis_line_t>
413
plain_text_source::row_for_anchor(const std::string& id)
5✔
414
{
415
    std::optional<vis_line_t> retval;
5✔
416

417
    if (this->tds_doc_sections.m_sections_root == nullptr) {
5✔
418
        return retval;
×
419
    }
420

421
    const auto& meta = this->tds_doc_sections;
5✔
422

423
    auto is_ptr = startswith(id, "#/");
5✔
424
    if (is_ptr) {
5✔
425
        auto hier_sf = string_fragment::from_str(id).consume_n(2).value();
×
UNCOV
426
        std::vector<lnav::document::section_key_t> path;
×
427

428
        while (!hier_sf.empty()) {
×
429
            auto comp_pair = hier_sf.split_when(string_fragment::tag1{'/'});
×
430
            auto scan_res
431
                = scn::scan_value<int64_t>(comp_pair.first.to_string_view());
×
UNCOV
432
            if (scan_res && scan_res->range().empty()) {
×
UNCOV
433
                path.emplace_back(scan_res->value());
×
434
            } else {
435
                stack_buf allocator;
×
436
                path.emplace_back(
×
437
                    json_ptr::decode(comp_pair.first, allocator).to_string());
×
438
            }
UNCOV
439
            hier_sf = comp_pair.second;
×
440
        }
441

UNCOV
442
        auto lookup_res = lnav::document::hier_node::lookup_path(
×
UNCOV
443
            meta.m_sections_root.get(), path);
×
UNCOV
444
        if (lookup_res) {
×
UNCOV
445
            retval = this->line_for_offset(lookup_res.value()->hn_start);
×
446
        }
447

UNCOV
448
        return retval;
×
449
    }
450

451
    lnav::document::hier_node::depth_first(
5✔
452
        meta.m_sections_root.get(),
453
        [this, &id, &retval](const lnav::document::hier_node* node) {
67✔
454
            for (const auto& child_pair : node->hn_named_children) {
114✔
455
                const auto& child_anchor = to_anchor_string(child_pair.first);
51✔
456

457
                if (child_anchor != id) {
51✔
458
                    continue;
47✔
459
                }
460

461
                retval = this->line_for_offset(child_pair.second->hn_start);
4✔
462
                break;
4✔
463
            }
51✔
464
        });
67✔
465

466
    return retval;
5✔
467
}
468

469
std::unordered_set<std::string>
470
plain_text_source::get_anchors()
×
471
{
472
    std::unordered_set<std::string> retval;
×
473

474
    lnav::document::hier_node::depth_first(
×
475
        this->tds_doc_sections.m_sections_root.get(),
UNCOV
476
        [&retval](const lnav::document::hier_node* node) {
×
UNCOV
477
            for (const auto& child_pair : node->hn_named_children) {
×
UNCOV
478
                retval.emplace(to_anchor_string(child_pair.first));
×
479
            }
UNCOV
480
        });
×
481

UNCOV
482
    return retval;
×
UNCOV
483
}
×
484

485
std::optional<std::string>
486
plain_text_source::anchor_for_row(vis_line_t vl)
4✔
487
{
488
    std::optional<std::string> retval;
4✔
489

490
    if (vl > (ssize_t) this->tds_lines.size()
4✔
491
        || this->tds_doc_sections.m_sections_root == nullptr)
4✔
492
    {
UNCOV
493
        return retval;
×
494
    }
495

496
    const auto& tl = this->tds_lines[vl];
4✔
497
    auto& md = this->tds_doc_sections;
4✔
498
    auto path_for_line = md.path_for_range(
499
        tl.tl_offset, tl.tl_offset + tl.tl_value.al_string.length());
4✔
500

501
    if (path_for_line.empty()) {
4✔
502
        return std::nullopt;
1✔
503
    }
504

505
    if ((path_for_line.size() == 1
3✔
506
         || this->tds_text_format == text_format_t::TF_MARKDOWN)
2✔
507
        && path_for_line.back().is<std::string>())
5✔
508
    {
509
        return to_anchor_string(path_for_line.back().get<std::string>());
2✔
510
    }
511

512
    auto comps
513
        = path_for_line | lnav::itertools::map([](const auto& elem) {
2✔
514
              return elem.match(
UNCOV
515
                  [](const std::string& str) {
×
516
                      stack_buf allocator;
1✔
517
                      return json_ptr::encode(str, allocator).to_string();
2✔
518
                  },
1✔
519
                  [](size_t index) { return fmt::to_string(index); });
5✔
520
          });
1✔
521

522
    return fmt::format(FMT_STRING("#/{}"),
4✔
523
                       fmt::join(comps.begin(), comps.end(), "/"));
2✔
524
}
4✔
525

526
std::optional<vis_line_t>
527
plain_text_source::adjacent_anchor(vis_line_t vl, direction dir)
2✔
528
{
529
    if (vl > (ssize_t) this->tds_lines.size()
2✔
530
        || this->tds_doc_sections.m_sections_root == nullptr)
2✔
531
    {
UNCOV
532
        return std::nullopt;
×
533
    }
534

535
    const auto& tl = this->tds_lines[vl];
2✔
536
    auto path_for_line = this->tds_doc_sections.path_for_range(
537
        tl.tl_offset, tl.tl_offset + tl.tl_value.al_string.length());
2✔
538

539
    log_trace("adjacent_anchor: curr path = %s",
2✔
540
              fmt::format(FMT_STRING("{}"), path_for_line).c_str());
541
    const auto& md = this->tds_doc_sections;
2✔
542
    if (path_for_line.empty()) {
2✔
543
        auto neighbors_res = md.m_sections_root->line_neighbors(vl);
1✔
544
        if (!neighbors_res) {
1✔
UNCOV
545
            return std::nullopt;
×
546
        }
547

548
        switch (dir) {
1✔
UNCOV
549
            case direction::prev: {
×
550
                if (neighbors_res->cnr_previous) {
×
551
                    return this->line_for_offset(
×
UNCOV
552
                        neighbors_res->cnr_previous.value()->hn_start);
×
553
                }
UNCOV
554
                break;
×
555
            }
556
            case direction::next: {
1✔
557
                if (neighbors_res->cnr_next) {
1✔
UNCOV
558
                    return this->line_for_offset(
×
UNCOV
559
                        neighbors_res->cnr_next.value()->hn_start);
×
560
                }
561
                if (!md.m_sections_root->hn_children.empty()) {
1✔
562
                    return this->line_for_offset(
1✔
563
                        md.m_sections_root->hn_children[0]->hn_start);
2✔
564
                }
UNCOV
565
                break;
×
566
            }
567
        }
UNCOV
568
        return std::nullopt;
×
569
    }
570

571
    auto last_key = path_for_line.back();
1✔
572
    path_for_line.pop_back();
1✔
573

574
    auto parent_opt = lnav::document::hier_node::lookup_path(
1✔
575
        md.m_sections_root.get(), path_for_line);
1✔
576
    if (!parent_opt) {
1✔
577
        log_trace("  no parent");
×
UNCOV
578
        return std::nullopt;
×
579
    }
580
    const auto* parent = parent_opt.value();
1✔
581

582
    auto child_hn = parent->lookup_child(last_key);
1✔
583
    if (!child_hn) {
1✔
584
        // XXX "should not happen"
UNCOV
585
        return std::nullopt;
×
586
    }
587

588
    auto neighbors_res = parent->child_neighbors(
3✔
589
        child_hn.value(), tl.tl_offset + tl.tl_value.al_string.length() + 1);
1✔
590
    if (!neighbors_res) {
1✔
591
        log_trace("no neighbors");
×
592
        return std::nullopt;
×
593
    }
594

595
    if (neighbors_res->cnr_previous && last_key.is<std::string>()) {
1✔
596
        auto neighbor_sub
597
            = neighbors_res->cnr_previous.value()->lookup_child(last_key);
1✔
598
        if (neighbor_sub) {
1✔
UNCOV
599
            log_trace("  loading previous child");
×
600
            neighbors_res->cnr_previous = neighbor_sub;
×
601
        }
602
    }
603

604
    if (neighbors_res->cnr_next && last_key.is<std::string>()) {
1✔
605
        auto neighbor_sub
606
            = neighbors_res->cnr_next.value()->lookup_child(last_key);
1✔
607
        if (neighbor_sub) {
1✔
608
            log_trace("  loading next child");
×
609
            neighbors_res->cnr_next = neighbor_sub;
×
610
        }
611
    }
612

613
    switch (dir) {
1✔
UNCOV
614
        case direction::prev: {
×
UNCOV
615
            if (neighbors_res->cnr_previous) {
×
UNCOV
616
                return this->line_for_offset(
×
UNCOV
617
                    neighbors_res->cnr_previous.value()->hn_start);
×
618
            }
UNCOV
619
            break;
×
620
        }
621
        case direction::next: {
1✔
622
            if (neighbors_res->cnr_next) {
1✔
623
                log_trace("  next offset %lld",
1✔
624
                          neighbors_res->cnr_next.value()->hn_start);
625
                return this->line_for_offset(
1✔
626
                    neighbors_res->cnr_next.value()->hn_start);
1✔
627
            }
UNCOV
628
            break;
×
629
        }
630
    }
631

UNCOV
632
    return std::nullopt;
×
633
}
2✔
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