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

tstack / lnav / 25348852825-3024

04 May 2026 11:18PM UTC coverage: 69.963% (+0.7%) from 69.226%
25348852825-3024

push

github

tstack
[ui] horizontal scroll should work on columns

Related to #1685

7 of 141 new or added lines in 5 files covered. (4.96%)

7760 existing lines in 84 files now uncovered.

57014 of 81492 relevant lines covered (69.96%)

622491.44 hits per line

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

58.28
/src/db_sub_source.cc
1
/**
2
 * Copyright (c) 2014, 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 <iterator>
31
#include <map>
32

33
#include "db_sub_source.hh"
34

35
#include "base/ansi_scrubber.hh"
36
#include "base/date_time_scanner.hh"
37
#include "base/func_util.hh"
38
#include "base/humanize.hh"
39
#include "base/itertools.enumerate.hh"
40
#include "base/itertools.hh"
41
#include "base/map_util.hh"
42
#include "base/math_util.hh"
43
#include "base/time_util.hh"
44
#include "base/types.hh"
45
#include "command_executor.hh"
46
#include "config.h"
47
#include "hist_source_T.hh"
48
#include "lnav_util.hh"
49
#include "log_level.hh"
50
#include "scn/scan.h"
51
#include "yajlpp/json_ptr.hh"
52
#include "yajlpp/yajlpp_def.hh"
53

54
using namespace lnav::roles::literals;
55

56
const unsigned char db_label_source::NULL_STR[] = "<NULL>";
57

58
constexpr ssize_t MAX_JSON_WIDTH = 16 * 1024;
59

60
struct user_row_style {
61
    lnav::map::small<std::string, style_config> urs_column_config;
62
};
63

64
static const typed_json_path_container<user_row_style>&
65
get_row_style_handlers()
9✔
66
{
67
    static const json_path_container col_style_handlers = {
68
        yajlpp::pattern_property_handler("(?<column_name>[^/]+)")
3✔
69
            .for_field(&user_row_style::urs_column_config)
3✔
70
            .with_children(style_config_handlers),
3✔
71
    };
18✔
72

73
    static const typed_json_path_container<user_row_style> retval
74
        = typed_json_path_container<user_row_style>{
9✔
75
            yajlpp::property_handler("columns")
9✔
76
                .with_children(col_style_handlers),
3✔
77
    }.with_schema_id2("row-style"_frag);
18✔
78

79
    return retval;
9✔
80
}
12✔
81

82
line_info
83
db_label_source::text_value_for_line(textview_curses& tc,
1,073✔
84
                                     int row,
85
                                     std::string& label_out,
86
                                     line_flags_t flags)
87
{
88
    /*
89
     * start_value is the result rowid, each bucket type is a column value
90
     * label_out should be the raw text output.
91
     */
92

93
    label_out.clear();
1,073✔
94
    this->dls_ansi_attrs.clear();
1,073✔
95
    label_out.reserve(this->dls_max_column_width * this->dls_headers.size());
1,073✔
96
    if (row < 0_vl || row >= (int) this->dls_row_cursors.size()) {
1,073✔
UNCOV
97
        return {};
×
98
    }
99
    std::optional<log_level_t> row_level;
1,073✔
100
    auto cell_cursor = this->dls_row_cursors[row].sync();
1,073✔
101
    for (size_t lpc = 0; lpc < this->dls_headers.size();
5,844✔
102
         lpc++, cell_cursor = cell_cursor->next())
4,771✔
103
    {
104
        if (lpc == this->dls_row_style_column
4,771✔
105
            && !this->dls_row_styles_have_errors)
9✔
106
        {
107
            continue;
9✔
108
        }
109
        const auto& hm = this->dls_headers[lpc];
4,765✔
110
        if (hm.hm_hidden) {
4,765✔
111
            continue;
3✔
112
        }
113
        ssize_t actual_col_size
114
            = std::min(this->dls_max_column_width, hm.hm_column_size);
4,762✔
115
        auto align = hm.hm_align;
4,762✔
116

117
        if (row < (int) this->dls_row_styles.size()) {
4,762✔
118
            auto style_opt
119
                = this->dls_row_styles[row].rs_column_config.value_for(lpc);
18✔
120
            if (style_opt && style_opt.value()->ta_align.has_value()) {
18✔
121
                align = style_opt.value()->ta_align.value();
3✔
122
            }
123
        }
124

125
        auto sf = cell_cursor->to_string_fragment(this->dls_cell_allocator);
4,762✔
126
        auto al = attr_line_t::from_table_cell_content(
127
            sf, this->dls_max_column_width);
4,762✔
128

129
        if (this->tss_view != nullptr
9,524✔
130
            && cell_cursor->get_type() == lnav::cell_type::CT_TEXT)
4,762✔
131
        {
132
            this->tss_view->apply_highlights(
1,108✔
133
                al, line_range::empty_at(0), line_range::empty_at(0));
2,216✔
134
        }
135
        if (this->dls_level_column && lpc == this->dls_level_column.value()) {
4,762✔
136
            row_level = string2level(sf.data(), sf.length());
125✔
137
        }
138

139
        auto cell_length = al.utf8_length_or_length();
4,762✔
140
        if (actual_col_size < cell_length) {
4,762✔
UNCOV
141
            log_warning(
×
142
                "invalid column size: actual_col_size=%zd < cell_length=%zd",
143
                actual_col_size,
144
                cell_length);
UNCOV
145
            cell_length = actual_col_size;
×
146
        }
147
        const auto padding = actual_col_size - cell_length;
4,762✔
148
        auto lpadding = 0;
4,762✔
149
        auto rpadding = padding;
4,762✔
150
        switch (align) {
4,762✔
151
            case text_align_t::start:
2,142✔
152
                break;
2,142✔
153
            case text_align_t::center: {
1✔
154
                lpadding = padding / 2;
1✔
155
                rpadding = padding - lpadding;
1✔
156
                break;
1✔
157
            }
158
            case text_align_t::end:
2,619✔
159
                lpadding = padding;
2,619✔
160
                rpadding = 0;
2,619✔
161
                break;
2,619✔
162
        }
163
        this->dls_cell_width[lpc] = al.al_string.length() + padding;
4,762✔
164
        label_out.append(lpadding, ' ');
4,762✔
165
        shift_string_attrs(al.al_attrs, 0, label_out.size());
4,762✔
166
        label_out.append(std::move(al.al_string));
4,762✔
167
        label_out.append(rpadding, ' ');
4,762✔
168
        label_out.push_back(' ');
4,762✔
169

170
        this->dls_ansi_attrs.insert(
9,524✔
171
            this->dls_ansi_attrs.end(),
4,762✔
172
            std::make_move_iterator(al.al_attrs.begin()),
173
            std::make_move_iterator(al.al_attrs.end()));
174
    }
4,762✔
175
    if (row_level.has_value()) {
1,073✔
176
        this->dls_ansi_attrs.emplace_back(line_range{0, -1},
125✔
177
                                          SA_LEVEL.value(row_level.value()));
250✔
178
    }
179
    this->dls_ansi_attrs.reserve(this->dls_ansi_attrs.size()
1,073✔
180
                                 + 3 * this->dls_headers.size());
1,073✔
181
    this->dls_cell_allocator.reset();
1,073✔
182

183
    return {};
1,073✔
184
}
185

186
void
187
db_label_source::text_attrs_for_line(textview_curses& tc,
1,073✔
188
                                     int row,
189
                                     string_attrs_t& sa)
190
{
191
    static const auto NUM_ATTR = VC_ROLE.value(role_t::VCR_NUMBER);
1,073✔
192
    static const auto VLINE_ATTR = VC_GRAPHIC.value(NCACS_VLINE);
1,073✔
193

194
    line_range lr(0, 0);
1,073✔
195
    const line_range lr2(0, -1);
1,073✔
196

197
    if (row < 0_vl || row >= (int) this->dls_row_cursors.size()) {
1,073✔
UNCOV
198
        return;
×
199
    }
200
    sa = std::move(this->dls_ansi_attrs);
1,073✔
201
    auto alt_row_index = row % 4;
1,073✔
202
    if (alt_row_index == 2 || alt_row_index == 3) {
1,073✔
203
        sa.emplace_back(lr2, VC_ROLE.value(role_t::VCR_ALT_ROW));
402✔
204
    }
205
    sa.emplace_back(line_range{0, 0}, SA_ORIGINAL_LINE.value());
1,073✔
206
    sa.emplace_back(line_range{0, 0}, SA_BODY.value());
1,073✔
207
    for (size_t lpc = 0; lpc < this->dls_headers.size() - 1; lpc++) {
4,771✔
208
        if (lpc == this->dls_row_style_column
3,698✔
UNCOV
209
            && !this->dls_row_styles_have_errors)
×
210
        {
UNCOV
211
            continue;
×
212
        }
213

214
        const auto& hm = this->dls_headers[lpc];
3,698✔
215
        if (hm.hm_hidden) {
3,698✔
216
            continue;
3✔
217
        }
218

219
        if (hm.is_graphable()) {
3,695✔
220
            lr.lr_end += this->dls_cell_width[lpc];
748✔
221
            sa.emplace_back(lr, NUM_ATTR);
748✔
222
        }
223
        lr.lr_start += this->dls_cell_width[lpc];
3,695✔
224
        lr.lr_end = lr.lr_start + 1;
3,695✔
225
        sa.emplace_back(lr, VLINE_ATTR);
3,695✔
226
        lr.lr_start += 1;
3,695✔
227
    }
228

229
    for (const auto& attr : sa) {
8,378✔
230
        require_ge(attr.sa_range.lr_start, 0);
7,305✔
231
    }
232
    int cell_start = 0;
1,073✔
233
    auto cursor = this->dls_row_cursors[row].sync();
1,073✔
234
    for (size_t lpc = 0; lpc < this->dls_headers.size();
5,844✔
235
         lpc++, cursor = cursor->next())
4,771✔
236
    {
237
        std::optional<text_attrs> user_attrs;
4,771✔
238

239
        if (lpc == this->dls_row_style_column) {
4,771✔
240
            if (!this->dls_row_styles_have_errors) {
9✔
241
                continue;
9✔
242
            }
243
        }
244

245
        const auto& hm = this->dls_headers[lpc];
4,765✔
246
        if (hm.hm_hidden) {
4,765✔
247
            continue;
3✔
248
        }
249
        if (row < (ssize_t) this->dls_row_styles.size()) {
4,762✔
250
            auto style_opt
251
                = this->dls_row_styles[row].rs_column_config.value_for(lpc);
18✔
252
            if (style_opt) {
18✔
253
                user_attrs = *style_opt.value();
6✔
254
            }
255
        }
256

257
        int left = cell_start;
4,762✔
258
        auto stlr = line_range{
259
            cell_start,
260
            (int) (cell_start + this->dls_cell_width[lpc]),
9,524✔
261
        };
4,762✔
262
        if (hm.is_graphable()) {
4,762✔
263
            std::optional<double> get_res;
1,135✔
264
            if (cursor->get_type() == lnav::cell_type::CT_INTEGER) {
1,135✔
265
                get_res = cursor->get_int();
816✔
266
            } else if (cursor->get_type() == lnav::cell_type::CT_FLOAT) {
319✔
267
                get_res = cursor->get_float();
315✔
268
            }
269
            if (get_res.has_value()) {
1,135✔
270
                hm.hm_chart.chart_attrs_for_value(tc,
1,131✔
271
                                                  left,
272
                                                  this->dls_cell_width[lpc],
1,131✔
273
                                                  hm.hm_name,
1,131✔
274
                                                  get_res.value(),
1,131✔
275
                                                  sa,
276
                                                  user_attrs);
277

278
                for (const auto& attr : sa) {
20,205✔
279
                    require_ge(attr.sa_range.lr_start, 0);
19,074✔
280
                }
281
            }
282
        } else if (user_attrs.has_value()) {
3,627✔
283
            sa.emplace_back(stlr, VC_STYLE.value(user_attrs.value()));
3✔
284
        }
285
        auto cell_sf = string_fragment::invalid();
4,762✔
286
        if (cursor->get_type() == lnav::cell_type::CT_TEXT) {
4,762✔
287
            cell_sf = cursor->get_text();
1,802✔
288
        } else if (cursor->get_type() == lnav::cell_type::CT_NULL) {
2,960✔
289
            sa.emplace_back(stlr, VC_ROLE.value(role_t::VCR_NULL));
1,051✔
290
        }
291
        if (lpc == this->dls_row_style_column) {
4,762✔
292
            sa.emplace_back(stlr, VC_ROLE.value(role_t::VCR_ERROR));
3✔
293
        } else if (cell_sf.is_valid() && cell_sf.length() > 2
6,558✔
294
                   && cell_sf.length() < MAX_JSON_WIDTH
1,534✔
295
                   && ((cell_sf.front() == '{' && cell_sf.back() == '}')
8,015✔
296
                       || (cell_sf.front() == '[' && cell_sf.back() == ']')))
1,457✔
297
        {
298
            auto cb = [&](const std::string& ptr, const scoped_value_t& sv) {
215✔
299
                auto val_opt = to_double(sv);
215✔
300
                if (val_opt) {
215✔
301
                    hm.hm_chart.chart_attrs_for_value(tc,
162✔
302
                                                      left,
303
                                                      this->dls_cell_width[lpc],
81✔
304
                                                      ptr,
305
                                                      val_opt.value(),
81✔
306
                                                      sa);
307
                    for (const auto& attr : sa) {
707✔
308
                        require_ge(attr.sa_range.lr_start, 0);
626✔
309
                    }
310
                }
311
            };
215✔
312
            json_ptr_walk jpw(cb);
133✔
313

314
            jpw.parse_fully(cell_sf);
133✔
315
        }
133✔
316
        cell_start += this->dls_cell_width[lpc] + 1;
4,762✔
317
    }
318

319
    for (const auto& attr : sa) {
10,322✔
320
        require_ge(attr.sa_range.lr_start, 0);
9,249✔
321
    }
322
}
323

324
void
NEW
325
db_label_source::text_horiz_columns(textview_curses& tc,
×
326
                                    vis_line_t start_row,
327
                                    vis_line_t end_row,
328
                                    std::set<int>& columns_out)
329
{
NEW
330
    int cell_start = 0;
×
NEW
331
    for (size_t lpc = 0; lpc < this->dls_headers.size(); lpc++) {
×
NEW
332
        if (lpc == this->dls_row_style_column
×
NEW
333
            && !this->dls_row_styles_have_errors)
×
334
        {
NEW
335
            continue;
×
336
        }
NEW
337
        if (this->dls_headers[lpc].hm_hidden) {
×
NEW
338
            continue;
×
339
        }
NEW
340
        columns_out.insert(cell_start);
×
NEW
341
        cell_start += this->dls_cell_width[lpc] + 1;
×
342
    }
343
}
344

345
void
346
db_label_source::set_col_as_graphable(int lpc)
850✔
347
{
348
    static auto& vc = view_colors::singleton();
850✔
349

350
    auto& hm = this->dls_headers[lpc];
850✔
351
    auto name_for_ident_attrs = hm.hm_name;
850✔
352
    auto attrs = vc.attrs_for_ident(name_for_ident_attrs);
850✔
353
    for (size_t attempt = 0; hm.hm_chart.attrs_in_use(attrs) && attempt < 3;
850✔
354
         attempt++)
355
    {
UNCOV
356
        name_for_ident_attrs += " ";
×
UNCOV
357
        attrs = vc.attrs_for_ident(name_for_ident_attrs);
×
358
    }
359
    hm.hm_graphable = true;
850✔
360
    hm.hm_chart.with_attrs_for_ident(hm.hm_name, attrs);
850✔
361
    hm.hm_title_attrs = attrs | text_attrs::with_reverse();
850✔
362
    hm.hm_column_size = std::max(hm.hm_column_size, size_t{10});
850✔
363
}
850✔
364

365
void
366
db_label_source::push_header(const std::string& colstr, int type)
3,504✔
367
{
368
    this->dls_headers.emplace_back(colstr);
3,504✔
369
    this->dls_cell_width.push_back(0);
3,504✔
370
    // The sparse cursor index needs to know how many cells make up a
371
    // row so random access can walk forward from the nearest block
372
    // start.  Headers are pushed once per query, before any rows, so
373
    // keeping this in sync per header is safe.
374
    this->dls_row_cursors.set_columns(this->dls_headers.size());
3,504✔
375

376
    auto& hm = this->dls_headers.back();
3,504✔
377

378
    hm.hm_column_size = utf8_string_length(colstr).unwrapOr(colstr.length());
3,504✔
379
    hm.hm_column_type = type;
3,504✔
380
    if (colstr == "log_time" || colstr == "min(log_time)"
6,931✔
381
        || colstr == "log_time_msecs")
6,931✔
382
    {
383
        this->dls_time_column_index = this->dls_headers.size() - 1;
84✔
384
    }
385
    if (colstr == "__lnav_style__") {
3,504✔
386
        this->dls_row_style_column = this->dls_headers.size() - 1;
3✔
387
    }
388
    if (colstr == "log_level") {
3,504✔
389
        this->dls_level_column = this->dls_headers.size() - 1;
59✔
390
    }
391
    hm.hm_chart.with_show_state(stacked_bar_chart_base::show_all{});
3,504✔
392
}
3,504✔
393

394
void
395
db_label_source::update_time_column(std::chrono::microseconds us)
361✔
396
{
397
    if (!this->dls_time_column.empty() && us < this->dls_time_column.back()) {
361✔
398
        this->dls_time_column_invalidated_at = this->dls_time_column.size();
1✔
399
        this->dls_time_column_index = SIZE_MAX;
1✔
400
        this->dls_time_column.clear();
1✔
401
    } else {
402
        this->dls_time_column.push_back(us);
360✔
403
    }
404
}
361✔
405

406
void
407
db_label_source::update_time_column(const string_fragment& sf)
336✔
408
{
409
    date_time_scanner dts;
336✔
410
    timeval tv;
411

412
    if (!dts.convert_to_timeval(sf.data(), sf.length(), nullptr, tv)) {
336✔
UNCOV
413
        tv.tv_sec = -1;
×
UNCOV
414
        tv.tv_usec = -1;
×
415
    }
416
    this->update_time_column(to_us(tv));
336✔
417
}
336✔
418

419
void
420
db_label_source::push_column(const column_value_t& sv)
10,418✔
421
{
422
    auto row_index = this->dls_row_cursors.size() - 1;
10,418✔
423
    auto& vc = view_colors::singleton();
10,418✔
424
    auto col = this->dls_push_column++;
10,418✔
425
    auto& hm = this->dls_headers[col];
10,418✔
426
    size_t width = 1;
10,418✔
427
    auto cv_sf = string_fragment::invalid();
10,418✔
428

429
    sv.match(
10,418✔
UNCOV
430
        [this, &col, &width, &cv_sf, &hm, &row_index](
×
431
            const string_fragment& sf) {
432
            if (this->dls_row_style_column == col) {
4,710✔
433
                return;
9✔
434
            }
435
            if (col == this->dls_time_column_index) {
4,701✔
436
                this->update_time_column(sf);
336✔
437
            } else if (this->dls_level_column
8,730✔
438
                       && this->dls_level_column.value() == col
5,766✔
439
                       && this->tss_view != nullptr)
5,766✔
440
            {
441
                auto& bm = this->tss_view->get_bookmarks();
234✔
442
                auto lev = string2level(sf.data(), sf.length());
234✔
443
                switch (lev) {
234✔
444
                    case log_level_t::LEVEL_FATAL:
38✔
445
                    case log_level_t::LEVEL_CRITICAL:
446
                    case log_level_t::LEVEL_ERROR:
447
                        bm[&textview_curses::BM_ERRORS].insert_once(
76✔
448
                            vis_line_t(row_index));
38✔
449
                        break;
38✔
450
                    case log_level_t::LEVEL_WARNING:
8✔
451
                        bm[&textview_curses::BM_WARNINGS].insert_once(
16✔
452
                            vis_line_t(row_index));
8✔
453
                        break;
8✔
454
                    default:
188✔
455
                        break;
188✔
456
                }
457
            }
458
            width = utf8_string_length(sf.data(), sf.length())
4,701✔
459
                        .unwrapOr(sf.length());
4,701✔
460
            if (hm.is_graphable()
4,701✔
461
                && sf.length() < lnav::cell_container::SHORT_TEXT_LENGTH)
4,701✔
462
            {
463
                auto from_res = humanize::try_from<double>(sf);
459✔
464
                if (from_res.has_value()) {
459✔
465
                    // First humanized cell fixes the column's unit so
466
                    // downstream renderers (chart, stats) can format
467
                    // values against the same base unit.
468
                    if (hm.hm_unit_suffix.empty()) {
453✔
469
                        hm.hm_unit_suffix
95✔
470
                            = from_res->unit_suffix.to_string();
95✔
471
                    }
472
                    this->dls_cell_container.push_float_with_units_cell(
906✔
473
                        from_res.value().value, sf);
453✔
474
                } else {
475
                    this->dls_cell_container.push_text_cell(sf);
6✔
476
                }
477
            } else {
478
                this->dls_cell_container.push_text_cell(sf);
4,242✔
479
            }
480
            cv_sf = sf;
4,701✔
481
        },
UNCOV
482
        [this, col, &width](int64_t i) {
×
483
            width = count_digits(i);
3,061✔
484
            this->dls_cell_container.push_int_cell(i);
3,061✔
485
            if (col == this->dls_time_column_index) {
3,061✔
486
                auto ms = std::chrono::milliseconds{i};
25✔
487
                auto us
488
                    = std::chrono::duration_cast<std::chrono::microseconds>(ms);
25✔
489

490
                this->update_time_column(us);
25✔
491
            }
492
        },
3,061✔
UNCOV
493
        [this, &width](double d) {
×
494
            char buffer[1];
495
            auto fmt_res = fmt::format_to_n(buffer, 0, FMT_STRING("{}"), d);
921✔
496
            width = fmt_res.size;
307✔
497
            this->dls_cell_container.push_float_cell(d);
307✔
498
        },
307✔
499
        [this, &width](null_value_t) {
10,418✔
500
            width = 6;
2,340✔
501
            this->dls_cell_container.push_null_cell();
2,340✔
502
        });
2,340✔
503

504
    if (col == this->dls_row_style_column) {
10,418✔
505
        auto col_sf = string_fragment::invalid();
9✔
506
        if (sv.is<null_value_t>()) {
9✔
UNCOV
507
            this->dls_row_styles.emplace_back(row_style{});
×
508
        } else if (sv.is<string_fragment>()) {
9✔
509
            static const intern_string_t SRC
510
                = intern_string::lookup("__lnav_style__");
15✔
511
            auto frag = sv.get<string_fragment>();
9✔
512
            if (frag.empty()) {
9✔
513
                this->dls_row_styles.emplace_back(row_style{});
×
514
            } else {
515
                auto parse_res
516
                    = get_row_style_handlers().parser_for(SRC).of(frag);
9✔
517
                if (parse_res.isErr()) {
9✔
UNCOV
518
                    log_error("DB row %zu JSON is invalid:", row_index);
×
UNCOV
519
                    auto errors = parse_res.unwrapErr();
×
UNCOV
520
                    for (const auto& err : errors) {
×
UNCOV
521
                        log_error("  %s", err.to_attr_line().al_string.c_str());
×
522
                    }
UNCOV
523
                    col_sf = string_fragment::from_str(
×
UNCOV
524
                                 errors[0].to_attr_line().al_string)
×
UNCOV
525
                                 .to_owned(this->dls_cell_allocator);
×
UNCOV
526
                    this->dls_row_styles_have_errors = true;
×
UNCOV
527
                } else {
×
528
                    auto urs = parse_res.unwrap();
9✔
529
                    auto rs = row_style{};
9✔
530
                    for (const auto& [col_name, col_style] :
18✔
531
                         urs.urs_column_config)
27✔
532
                    {
533
                        auto col_index_opt
534
                            = this->column_name_to_index(col_name);
9✔
535
                        if (!col_index_opt) {
9✔
536
                            log_error("DB row %zu column name '%s' not found",
3✔
537
                                      row_index,
538
                                      col_name.c_str());
UNCOV
539
                            col_sf = string_fragment::from_str(
×
540
                                         fmt::format(
3✔
541
                                             FMT_STRING(
9✔
542
                                                 "column name '{}' not found"),
543
                                             col_name))
544
                                         .to_owned(this->dls_cell_allocator);
3✔
545
                            this->dls_row_styles_have_errors = true;
3✔
546
                        } else {
547
                            text_attrs ta;
6✔
548

549
                            auto fg_res = styling::color_unit::from_str(
550
                                col_style.sc_color.pp_value);
6✔
551
                            if (fg_res.isErr()) {
6✔
UNCOV
552
                                log_error("DB row %zu color is invalid: %s",
×
553
                                          row_index,
554
                                          fg_res.unwrapErr().c_str());
555
                                col_sf
UNCOV
556
                                    = string_fragment::from_str(
×
UNCOV
557
                                          fmt::format(
×
UNCOV
558
                                              FMT_STRING("invalid color: {}"),
×
559
                                              fg_res.unwrapErr()))
×
UNCOV
560
                                          .to_owned(this->dls_cell_allocator);
×
UNCOV
561
                                this->dls_row_styles_have_errors = true;
×
562
                            } else {
563
                                ta.ta_fg_color
564
                                    = vc.match_color(fg_res.unwrap());
6✔
565
                            }
566
                            auto bg_res = styling::color_unit::from_str(
567
                                col_style.sc_background_color.pp_value);
6✔
568
                            if (bg_res.isErr()) {
6✔
UNCOV
569
                                log_error(
×
570
                                    "DB row %zu background-color is invalid: "
571
                                    "%s",
572
                                    row_index,
573
                                    bg_res.unwrapErr().c_str());
574
                                col_sf
UNCOV
575
                                    = string_fragment::from_str(
×
UNCOV
576
                                          fmt::format(
×
UNCOV
577
                                              FMT_STRING(
×
578
                                                  "invalid "
579
                                                  "background-color: {}"),
UNCOV
580
                                              bg_res.unwrapErr()))
×
UNCOV
581
                                          .to_owned(this->dls_cell_allocator);
×
582
                                this->dls_row_styles_have_errors = true;
×
583
                            } else {
584
                                ta.ta_bg_color
585
                                    = vc.match_color(bg_res.unwrap());
6✔
586
                            }
587
                            ta.ta_align = col_style.sc_text_align;
6✔
588
                            if (col_style.sc_underline) {
6✔
UNCOV
589
                                ta |= text_attrs::style::underline;
×
590
                            }
591
                            if (col_style.sc_bold) {
6✔
UNCOV
592
                                ta |= text_attrs::style::bold;
×
593
                            }
594
                            if (col_style.sc_italic) {
6✔
UNCOV
595
                                ta |= text_attrs::style::italic;
×
596
                            }
597
                            if (col_style.sc_strike) {
6✔
UNCOV
598
                                ta |= text_attrs::style::struck;
×
599
                            }
600
                            if (this->dls_headers[col_index_opt.value()]
6✔
601
                                    .is_graphable())
6✔
602
                            {
603
                                this->dls_headers[col_index_opt.value()]
3✔
604
                                    .hm_title_attrs
605
                                    = text_attrs::with_underline();
6✔
606
                            }
607
                            rs.rs_column_config[col_index_opt.value()] = ta;
6✔
608
                        }
6✔
609
                    }
610
                    this->dls_row_styles.emplace_back(std::move(rs));
9✔
611
                }
9✔
612
            }
9✔
613
        } else {
UNCOV
614
            log_error("DB row %zu is not a string -- %s",
×
615
                      row_index,
616
                      mapbox::util::apply_visitor(type_visitor(), sv));
617

618
            col_sf
UNCOV
619
                = string_fragment::from_str("expecting a JSON object for style")
×
UNCOV
620
                      .to_owned(this->dls_cell_allocator);
×
UNCOV
621
            this->dls_row_styles_have_errors = true;
×
622
        }
623

624
        if (col_sf.empty()) {
9✔
625
            this->dls_cell_container.push_null_cell();
6✔
626
        } else {
627
            this->dls_cell_container.push_text_cell(col_sf);
3✔
628
            width = utf8_string_length(col_sf.data(), col_sf.length())
3✔
629
                        .unwrapOr(col_sf.length());
3✔
630
            this->dls_cell_allocator.reset();
3✔
631
        }
632
    }
633

634
    hm.hm_column_size = std::max(this->dls_headers[col].hm_column_size, width);
10,418✔
635
    if (hm.is_graphable()) {
10,418✔
636
        if (sv.is<int64_t>()) {
2,345✔
637
            hm.hm_chart.add_value(hm.hm_name, sv.get<int64_t>());
1,582✔
638
            hm.hm_tdigest.insert(sv.get<int64_t>());
1,582✔
639
        } else if (sv.is<double>()) {
763✔
640
            hm.hm_chart.add_value(hm.hm_name, sv.get<double>());
295✔
641
            hm.hm_tdigest.insert(sv.get<double>());
295✔
642
        } else if (sv.is<string_fragment>()) {
468✔
643
            auto sf = sv.get<string_fragment>();
459✔
644
            auto num_from_res = humanize::try_from<double>(sf);
459✔
645
            if (num_from_res) {
459✔
646
                hm.hm_chart.add_value(hm.hm_name, num_from_res->value);
453✔
647
                hm.hm_tdigest.insert(num_from_res->value);
453✔
648
            }
649
        }
650
    } else if (cv_sf.is_valid() && cv_sf.length() > 2
12,315✔
651
               && ((cv_sf.startswith("{") && cv_sf.endswith("}"))
15,801✔
652
                   || (cv_sf.startswith("[") && cv_sf.endswith("]"))))
3,486✔
653
    {
654
        auto cb = [this, &hm, &vc](const std::string& ptr,
1,222✔
655
                                   const scoped_value_t& sv) {
656
            auto ptr_sf = string_fragment::from_str(ptr);
1,222✔
657
            auto iter = hm.hm_json_columns.find(ptr_sf);
1,222✔
658
            if (iter == hm.hm_json_columns.end()) {
1,222✔
659
                auto owned_ptr_sf = ptr_sf.to_owned(this->dls_header_allocator);
1,015✔
660
                iter = hm.hm_json_columns
2,030✔
661
                           .emplace(owned_ptr_sf, hm.hm_json_columns.size())
1,015✔
662
                           .first;
663
            }
664
            if (sv.is<int64_t>()) {
1,222✔
665
                auto val = sv.get<int64_t>();
404✔
666
                auto& ci = hm.hm_chart.add_value(ptr, val);
404✔
667
                if (ci.ci_attrs.empty()) {
404✔
668
                    ci.ci_attrs = vc.attrs_for_ident(ptr);
373✔
669
                }
670
            } else if (sv.is<double>()) {
818✔
671
                auto val = sv.get<double>();
42✔
672
                auto& ci = hm.hm_chart.add_value(ptr, val);
42✔
673
                if (ci.ci_attrs.empty()) {
42✔
674
                    ci.ci_attrs = vc.attrs_for_ident(ptr);
42✔
675
                }
676
            }
677
        };
1,222✔
678
        json_ptr_walk jpw(cb);
456✔
679

680
        jpw.parse_fully(cv_sf);
456✔
681
    }
456✔
682
    hm.hm_chart.next_row();
10,418✔
683
}
10,418✔
684

685
void
686
db_label_source::clear()
3,163✔
687
{
688
    this->dls_generation += 1;
3,163✔
689
    this->dls_user_query.clear();
3,163✔
690
    this->dls_user_query_src_loc = std::nullopt;
3,163✔
691
    this->dls_user_query_vars.clear();
3,163✔
692
    this->dls_query_start = std::nullopt;
3,163✔
693
    this->dls_query_end = std::nullopt;
3,163✔
694
    this->dls_query_touches_log_data = false;
3,163✔
695
    this->dls_log_gen_at_query = 0;
3,163✔
696
    this->dls_log_line_count_at_query = 0;
3,163✔
697
    this->dls_headers.clear();
3,163✔
698
    this->dls_row_cursors.clear();
3,163✔
699
    this->dls_cell_container.reset();
3,163✔
700
    this->dls_time_column.clear();
3,163✔
701
    this->dls_time_column_index = SIZE_MAX;
3,163✔
702
    this->dls_cell_width.clear();
3,163✔
703
    this->dls_row_styles.clear();
3,163✔
704
    this->dls_row_styles_have_errors = false;
3,163✔
705
    this->dls_row_style_column = SIZE_MAX;
3,163✔
706
    this->dls_level_column = std::nullopt;
3,163✔
707
    this->dls_cell_allocator.reset();
3,163✔
708
    this->dls_header_allocator.reset();
3,163✔
709
    this->dls_step_rc = SQLITE_OK;
3,163✔
710
    if (this->tss_view != nullptr) {
3,163✔
711
        this->tss_view->get_bookmarks().clear();
387✔
712
    }
713
}
3,163✔
714

715
std::optional<size_t>
716
db_label_source::column_name_to_index(const std::string& name) const
15✔
717
{
718
    return this->dls_headers | lnav::itertools::find(name);
15✔
719
}
720

721
std::optional<vis_line_t>
722
db_label_source::row_for_time(timeval time_bucket)
5✔
723
{
724
    const auto target = to_us(time_bucket);
5✔
725
    const auto iter = std::lower_bound(this->dls_time_column.begin(),
5✔
726
                                       this->dls_time_column.end(),
727
                                       target);
728
    if (iter != this->dls_time_column.end()) {
5✔
729
        return vis_line_t(std::distance(this->dls_time_column.begin(), iter));
8✔
730
    }
731
    return std::nullopt;
1✔
732
}
733

734
std::optional<text_time_translator::row_info>
735
db_label_source::time_for_row(vis_line_t row)
360✔
736
{
737
    if ((row < 0_vl) || (((size_t) row) >= this->dls_time_column.size())) {
360✔
738
        return std::nullopt;
296✔
739
    }
740

741
    return row_info{to_timeval(this->dls_time_column[row]), row};
64✔
742
}
743

744
bool
UNCOV
745
db_label_source::text_handle_mouse(
×
746
    textview_curses& tc,
747
    const listview_curses::display_line_content_t&,
748
    mouse_event& me)
749
{
UNCOV
750
    if (tc.get_overlay_selection()) {
×
751
        auto nci = ncinput{};
×
752
        if (me.is_click_in(mouse_button_t::BUTTON_LEFT, 0, 3)) {
×
753
            nci.id = ' ';
×
754
            nci.eff_text[0] = ' ';
×
755
            this->list_input_handle_key(tc, nci);
×
756
        }
757
    }
758
    return true;
×
759
}
760

761
static constexpr string_attr_type<std::string> DBA_DETAILS("details");
762
static constexpr string_attr_type<std::string> DBA_COLUMN_NAME("column-name");
763

764
bool
765
db_label_source::list_input_handle_key(listview_curses& lv, const ncinput& ch)
×
766
{
767
    switch (ch.eff_text[0]) {
×
768
        case ' ': {
×
UNCOV
769
            auto ov_sel = lv.get_overlay_selection();
×
UNCOV
770
            if (ov_sel) {
×
UNCOV
771
                std::vector<attr_line_t> rows;
×
772
                auto* ov_source = lv.get_overlay_source();
×
UNCOV
773
                ov_source->list_value_for_overlay(
×
774
                    lv, lv.get_selection().value(), rows);
×
UNCOV
775
                if (ov_sel.value() < (ssize_t) rows.size()) {
×
776
                    auto& row_al = rows[ov_sel.value()];
×
777
                    auto col_attr
UNCOV
778
                        = get_string_attr(row_al.al_attrs, DBA_COLUMN_NAME);
×
UNCOV
779
                    if (col_attr) {
×
780
                        auto col_name = col_attr.value().get();
×
UNCOV
781
                        auto col_opt = this->column_name_to_index(col_name);
×
UNCOV
782
                        if (col_opt) {
×
UNCOV
783
                            this->dls_headers[col_opt.value()].hm_hidden
×
UNCOV
784
                                = !this->dls_headers[col_opt.value()].hm_hidden;
×
785
                        }
786
                    }
787
                }
788
                lv.set_needs_update();
×
789

UNCOV
790
                return true;
×
791
            }
UNCOV
792
            break;
×
793
        }
794
    }
795

NEW
796
    return text_sub_source::list_input_handle_key(lv, ch);
×
797
}
798

799
std::optional<json_string>
800
db_label_source::text_row_details(const textview_curses& tc)
3✔
801
{
802
    if (this->dls_row_cursors.empty()) {
3✔
803
        log_trace("db_label_source::text_row_details: empty");
×
804
        return std::nullopt;
×
805
    }
806
    if (!this->dls_query_end.has_value()) {
3✔
807
        log_trace("db_label_source::text_row_details: query in progress");
2✔
808
        return std::nullopt;
2✔
809
    }
810

811
    auto ov_sel = tc.get_overlay_selection();
1✔
812

813
    if (ov_sel.has_value()) {
1✔
UNCOV
814
        std::vector<attr_line_t> rows;
×
UNCOV
815
        auto* ov_source = tc.get_overlay_source();
×
UNCOV
816
        ov_source->list_value_for_overlay(tc, tc.get_selection().value(), rows);
×
UNCOV
817
        if (ov_sel.value() < (ssize_t) rows.size()) {
×
UNCOV
818
            auto& row_al = rows[ov_sel.value()];
×
UNCOV
819
            auto deets_attr = get_string_attr(row_al.al_attrs, DBA_DETAILS);
×
UNCOV
820
            if (deets_attr) {
×
UNCOV
821
                auto deets = deets_attr->get();
×
UNCOV
822
                if (!deets.empty()) {
×
UNCOV
823
                    return json_string(
×
UNCOV
824
                        auto_buffer::from(deets.c_str(), deets.length()));
×
825
                }
826
            }
827
        }
UNCOV
828
    } else {
×
829
        yajlpp_gen gen;
1✔
830

831
        {
832
            yajlpp_map root(gen);
1✔
833

834
            auto sel = tc.get_selection();
1✔
835
            if (sel) {
1✔
836
                root.gen("value");
1✔
837

838
                {
839
                    yajlpp_map value_map(gen);
1✔
840

841
                    auto cursor
842
                        = this->dls_row_cursors[tc.get_selection().value()]
1✔
843
                              .sync();
1✔
844
                    for (const auto& [col, hm] :
22✔
845
                         lnav::itertools::enumerate(this->dls_headers))
23✔
846
                    {
847
                        value_map.gen(hm.hm_name);
21✔
848

849
                        switch (cursor->get_type()) {
21✔
850
                            case lnav::cell_type::CT_NULL:
9✔
851
                                value_map.gen();
9✔
852
                                break;
9✔
853
                            case lnav::cell_type::CT_INTEGER:
5✔
854
                                value_map.gen(cursor->get_int());
5✔
855
                                break;
5✔
UNCOV
856
                            case lnav::cell_type::CT_FLOAT:
×
UNCOV
857
                                if (cursor->get_sub_value() == 0) {
×
UNCOV
858
                                    value_map.gen(cursor->get_float());
×
859
                                } else {
860
                                    value_map.gen(cursor->get_float_as_text());
×
861
                                }
UNCOV
862
                                break;
×
863
                            case lnav::cell_type::CT_TEXT:
7✔
864
                                value_map.gen(cursor->get_text());
7✔
865
                                break;
7✔
866
                        }
867
                        cursor = cursor->next();
21✔
868
                    }
869
                }
1✔
870
            }
871
        }
1✔
872

873
        return json_string{gen};
1✔
874
    }
1✔
875

UNCOV
876
    return std::nullopt;
×
877
}
878

879
Result<std::string, lnav::console::user_message>
UNCOV
880
db_label_source::text_reload_data(exec_context& ec)
×
881
{
UNCOV
882
    if (this->dls_user_query.empty()) {
×
UNCOV
883
        return ec.make_error("no previous query to re-run");
×
884
    }
885

UNCOV
886
    auto prev_query = this->dls_user_query;
×
UNCOV
887
    auto prev_loc = this->dls_user_query_src_loc.value_or(INTERNAL_SRC_LOC);
×
UNCOV
888
    auto prev_vars = this->dls_user_query_vars;
×
UNCOV
889
    std::string alt_msg;
×
890

891
    auto src_guard = ec.enter_source(
UNCOV
892
        prev_loc, fmt::format(FMT_STRING(";{}"), prev_query));
×
UNCOV
893
    auto db_guard = ec.enter_db_source(this);
×
UNCOV
894
    auto cb_guard = ec.push_callback(sql_callback);
×
895

UNCOV
896
    ec.ec_local_vars.emplace(std::move(prev_vars));
×
897
    auto vars_guard
UNCOV
898
        = finally([&ec]() { ec.ec_local_vars.pop(); });
×
899

UNCOV
900
    return execute_sql(ec, prev_query, alt_msg);
×
901
}
902

903
std::optional<std::string>
904
db_label_source::text_view_details() const
2✔
905
{
906
    if (this->dls_user_query.empty()) {
2✔
UNCOV
907
        return text_sub_source::text_view_details();
×
908
    }
909

910
    yajlpp_gen gen;
2✔
911
    {
912
        yajlpp_map root(gen);
2✔
913

914
        root.gen("zoom-level");
2✔
915
        root.gen(this->format_zoom_level());
2✔
916

917
        root.gen("query");
2✔
918
        root.gen(this->dls_user_query);
2✔
919

920
        if (this->dls_query_start.has_value()) {
2✔
921
            auto us = std::chrono::duration_cast<std::chrono::microseconds>(
2✔
922
                this->dls_query_start.value().time_since_epoch());
2✔
923
            root.gen("run-at");
2✔
924
            root.gen(lnav::to_rfc3339_string(us, 'T'));
2✔
925
        }
926

927
        if (this->dls_query_start.has_value()
2✔
928
            && this->dls_query_end.has_value())
2✔
929
        {
UNCOV
930
            int64_t us = std::chrono::duration_cast<std::chrono::microseconds>(
×
931
                             this->dls_query_end.value()
UNCOV
932
                             - this->dls_query_start.value())
×
UNCOV
933
                             .count();
×
934
            root.gen("duration-us");
×
935
            root.gen(us);
×
936
        }
937
    }
2✔
938

939
    return gen.to_string_fragment().to_string();
2✔
940
}
2✔
941

942
std::string
943
db_label_source::get_row_as_string(vis_line_t row)
1,682✔
944
{
945
    if (row < 0_vl || (((size_t) row) >= this->dls_row_cursors.size())) {
1,682✔
UNCOV
946
        return "";
×
947
    }
948

949
    if (this->dls_headers.size() == 1) {
1,682✔
950
        return this->dls_row_cursors[row]
1,676✔
951
            .sync()
1,676✔
952
            .value()
1,676✔
953
            .to_string_fragment(this->dls_cell_allocator)
3,352✔
954
            .to_string();
1,676✔
955
    }
956

957
    std::string retval;
6✔
958
    size_t lpc = 0;
6✔
959
    auto cursor = this->dls_row_cursors[row].sync();
6✔
960
    while (lpc < this->dls_headers.size() && cursor.has_value()) {
26✔
961
        const auto& hm = this->dls_headers[lpc];
20✔
962

963
        if (!retval.empty()) {
20✔
964
            retval.append("; ");
14✔
965
        }
966
        retval.append(hm.hm_name);
20✔
967
        retval.push_back('=');
20✔
968
        auto sf = cursor->to_string_fragment(this->dls_cell_allocator);
20✔
969
        retval += sf;
20✔
970

971
        cursor = cursor->next();
20✔
972
        lpc += 1;
20✔
973
    }
974
    this->dls_cell_allocator.reset();
6✔
975

976
    return retval;
6✔
977
}
6✔
978

979
std::string
UNCOV
980
db_label_source::get_cell_as_string(vis_line_t row, size_t col)
×
981
{
982
    if (row < 0_vl || (((size_t) row) >= this->dls_row_cursors.size())
×
UNCOV
983
        || col >= this->dls_headers.size())
×
984
    {
985
        return "";
×
986
    }
987

UNCOV
988
    this->dls_cell_allocator.reset();
×
UNCOV
989
    size_t lpc = 0;
×
990
    auto cursor = this->dls_row_cursors[row].sync();
×
991
    while (cursor.has_value()) {
×
992
        if (lpc == col) {
×
993
            return cursor->to_string_fragment(this->dls_cell_allocator)
×
994
                .to_string();
×
995
        }
996

997
        cursor = cursor->next();
×
998
        lpc += 1;
×
999
    }
1000

UNCOV
1001
    return "";
×
1002
}
1003

1004
std::optional<int64_t>
1005
db_label_source::get_cell_as_int64(vis_line_t row, size_t col) const
×
1006
{
UNCOV
1007
    if (row < 0_vl || (((size_t) row) >= this->dls_row_cursors.size())
×
1008
        || col >= this->dls_headers.size())
×
1009
    {
UNCOV
1010
        return std::nullopt;
×
1011
    }
1012

UNCOV
1013
    size_t lpc = 0;
×
UNCOV
1014
    auto cursor = this->dls_row_cursors[row].sync();
×
1015
    while (cursor.has_value()) {
×
UNCOV
1016
        if (lpc == col) {
×
UNCOV
1017
            if (cursor->get_type() == lnav::cell_type::CT_INTEGER) {
×
UNCOV
1018
                return cursor->get_int();
×
1019
            }
1020
            return std::nullopt;
×
1021
        }
1022

UNCOV
1023
        cursor = cursor->next();
×
1024
        lpc += 1;
×
1025
    }
1026

1027
    return std::nullopt;
×
1028
}
1029

1030
std::optional<double>
1031
db_label_source::get_cell_as_double(vis_line_t row, size_t col) const
×
1032
{
1033
    if (row < 0_vl || (((size_t) row) >= this->dls_row_cursors.size())
×
1034
        || col >= this->dls_headers.size())
×
1035
    {
1036
        return std::nullopt;
×
1037
    }
1038

1039
    size_t lpc = 0;
×
1040
    auto cursor = this->dls_row_cursors[row].sync();
×
1041
    while (cursor.has_value()) {
×
UNCOV
1042
        if (lpc == col) {
×
1043
            switch (cursor->get_type()) {
×
UNCOV
1044
                case lnav::cell_type::CT_INTEGER:
×
UNCOV
1045
                    return cursor->get_int();
×
UNCOV
1046
                case lnav::cell_type::CT_FLOAT:
×
UNCOV
1047
                    return cursor->get_float();
×
UNCOV
1048
                default:
×
UNCOV
1049
                    return std::nullopt;
×
1050
            }
1051
        }
1052

UNCOV
1053
        cursor = cursor->next();
×
UNCOV
1054
        lpc += 1;
×
1055
    }
1056

1057
    return std::nullopt;
×
1058
}
1059

1060
mapbox::util::variant<int64_t, double>
UNCOV
1061
db_label_source::get_cell_as_numeric(vis_line_t row, size_t col) const
×
1062
{
UNCOV
1063
    if (row < 0_vl || (((size_t) row) >= this->dls_row_cursors.size())
×
1064
        || col >= this->dls_headers.size())
×
1065
    {
1066
        return numeric_cell_t{mapbox::util::no_init{}};
×
1067
    }
1068

UNCOV
1069
    size_t lpc = 0;
×
1070
    auto cursor = this->dls_row_cursors[row].sync();
×
1071
    while (cursor.has_value()) {
×
1072
        if (lpc == col) {
×
UNCOV
1073
            switch (cursor->get_type()) {
×
1074
                case lnav::cell_type::CT_INTEGER:
×
1075
                    return cursor->get_int();
×
1076
                case lnav::cell_type::CT_FLOAT:
×
UNCOV
1077
                    return cursor->get_float();
×
1078
                default:
×
1079
                    return numeric_cell_t{mapbox::util::no_init{}};
×
1080
            }
1081
        }
1082

UNCOV
1083
        cursor = cursor->next();
×
UNCOV
1084
        lpc += 1;
×
1085
    }
1086

1087
    return numeric_cell_t{mapbox::util::no_init{}};
×
1088
}
1089

1090
void
1091
db_label_source::reset_user_state()
11✔
1092
{
1093
    for (auto& hm : this->dls_headers) {
11✔
1094
        hm.hm_hidden = false;
×
1095
    }
1096
}
11✔
1097

1098
std::optional<attr_line_t>
UNCOV
1099
db_overlay_source::list_header_for_overlay(const listview_curses& lv,
×
1100
                                           media_t media,
1101
                                           vis_line_t line)
1102
{
1103
    attr_line_t retval;
×
1104

1105
    retval.append("  Details for row ")
×
UNCOV
1106
        .append(
×
1107
            lnav::roles::number(fmt::format(FMT_STRING("{:L}"), (int) line)))
×
1108
        .append(". Press ")
×
1109
        .append("p"_hotkey)
×
1110
        .append(" to hide this panel.");
×
UNCOV
1111
    if (lv.get_overlay_selection()) {
×
1112
        retval.append(" Controls: ")
×
1113
            .append("c"_hotkey)
×
UNCOV
1114
            .append(" to copy a column value; ")
×
1115
            .append("SPC"_hotkey)
×
1116
            .append(" to hide/show a column");
×
1117
    } else {
1118
        retval.append("  Press ")
×
1119
            .append("CTRL-]"_hotkey)
×
1120
            .append(" to focus on this panel");
×
1121
    }
UNCOV
1122
    return retval;
×
1123
}
1124

1125
void
1126
db_overlay_source::list_value_for_overlay(const listview_curses& lv,
709✔
1127
                                          vis_line_t row,
1128
                                          std::vector<attr_line_t>& value_out)
1129
{
1130
    if (!this->dos_active || lv.get_inner_height() == 0) {
709✔
1131
        return;
709✔
1132
    }
1133

1134
    auto sel = lv.get_selection();
×
1135
    if (!sel || row != sel.value()) {
×
UNCOV
1136
        return;
×
1137
    }
1138

1139
    auto& vc = view_colors::singleton();
×
1140
    unsigned long width;
UNCOV
1141
    vis_line_t height;
×
1142

1143
    lv.get_dimensions(height, width);
×
1144

1145
    auto max_name_width = this->dos_labels->dls_headers
×
1146
        | lnav::itertools::map([](const auto& hm) { return hm.hm_name.size(); })
×
UNCOV
1147
        | lnav::itertools::max();
×
1148

1149
    auto cursor = this->dos_labels->dls_row_cursors[row].sync();
×
UNCOV
1150
    for (const auto& [col, hm] :
×
1151
         lnav::itertools::enumerate(this->dos_labels->dls_headers))
×
1152
    {
1153
        auto al = attr_line_t()
×
UNCOV
1154
                      .append(lnav::roles::h3(hm.hm_name))
×
UNCOV
1155
                      .right_justify(max_name_width.value_or(0) + 2);
×
1156

1157
        if (hm.hm_hidden) {
×
UNCOV
1158
            al.insert(1, "\u25c7"_comment);
×
1159
        } else {
1160
            al.insert(1, "\u25c6"_ok);
×
1161
        }
1162

1163
        auto sf
1164
            = cursor->to_string_fragment(this->dos_labels->dls_cell_allocator);
×
1165

UNCOV
1166
        al.al_attrs.emplace_back(line_range{0, -1},
×
1167
                                 DBA_COLUMN_NAME.value(hm.hm_name));
×
1168
        if (cursor->get_type() == lnav::cell_type::CT_TEXT
×
UNCOV
1169
            && (sf.startswith("[") || sf.startswith("{")))
×
1170
        {
1171
            auto parse_res = json_walk_collector::parse_fully(sf);
×
1172

1173
            if (parse_res.isOk()) {
×
1174
                auto jwc = parse_res.unwrap();
×
1175
                {
1176
                    yajlpp_gen gen;
×
1177

1178
                    {
UNCOV
1179
                        yajlpp_map root(gen);
×
1180

1181
                        root.gen("key");
×
1182
                        root.gen(hm.hm_name);
×
1183
                        root.gen("value");
×
1184
                        root.gen(sf);
×
1185
                    }
1186
                    al.al_attrs.emplace_back(
×
1187
                        line_range{0, -1},
×
UNCOV
1188
                        DBA_DETAILS.value(
×
UNCOV
1189
                            gen.to_string_fragment().to_string()));
×
1190
                }
UNCOV
1191
                value_out.emplace_back(al);
×
1192
                al.clear();
×
1193

1194
                stacked_bar_chart<std::string> chart;
×
1195
                int start_line = value_out.size();
×
1196

1197
                auto indent = 3 + max_name_width.value() - hm.hm_name.size();
×
1198
                chart.with_stacking_enabled(false)
×
1199
                    .with_margins(indent + 2, 0)
×
1200
                    .with_show_state(stacked_bar_chart_base::show_all{});
×
1201

1202
                for (const auto& [walk_index, jpw_value] :
×
1203
                     lnav::itertools::enumerate(jwc.jwc_values))
×
1204
                {
1205
                    {
1206
                        yajlpp_gen gen;
×
1207

1208
                        {
1209
                            yajlpp_map root(gen);
×
1210

1211
                            root.gen("key");
×
1212
                            root.gen(jpw_value.first);
×
1213
                            root.gen("value");
×
1214
                            root.gen(fmt::to_string(jpw_value.second));
×
1215
                        }
1216
                        al.al_attrs.emplace_back(
×
1217
                            line_range{0, -1},
×
1218
                            DBA_DETAILS.value(
×
1219
                                gen.to_string_fragment().to_string()));
×
1220
                    }
1221

1222
                    al.append(indent + 2, ' ')
×
UNCOV
1223
                        .append(lnav::roles::h5(jpw_value.first))
×
1224
                        .append(" = ")
×
1225
                        .append(fmt::to_string(jpw_value.second));
×
1226

1227
                    auto& sa = al.al_attrs;
×
UNCOV
1228
                    line_range lr(indent, indent + 1);
×
1229

UNCOV
1230
                    sa.emplace_back(
×
1231
                        lr,
1232
                        VC_GRAPHIC.value(walk_index < jwc.jwc_values.size() - 1
×
1233
                                             ? NCACS_LTEE
1234
                                             : NCACS_LLCORNER));
1235
                    lr.lr_start = indent + 2 + jpw_value.first.size() + 3;
×
1236
                    lr.lr_end = -1;
×
1237

1238
                    auto val_opt = to_double(jpw_value.second);
×
1239
                    if (val_opt) {
×
1240
                        auto attrs = vc.attrs_for_ident(jpw_value.first);
×
1241

1242
                        chart.add_value(jpw_value.first, val_opt.value());
×
1243
                        chart.with_attrs_for_ident(jpw_value.first, attrs);
×
1244
                        sa.emplace_back(lr, VC_ROLE.value(role_t::VCR_NUMBER));
×
1245
                    }
UNCOV
1246
                    value_out.emplace_back(al);
×
1247
                    al.clear();
×
1248
                }
1249

1250
                int curr_line = start_line;
×
1251
                for (auto iter = jwc.jwc_values.begin();
×
1252
                     iter != jwc.jwc_values.end();
×
1253
                     ++iter, curr_line++)
×
1254
                {
UNCOV
1255
                    auto val_opt = to_double(iter->second);
×
UNCOV
1256
                    if (!val_opt) {
×
1257
                        continue;
×
1258
                    }
1259

1260
                    auto& sa = value_out[curr_line].get_attrs();
×
UNCOV
1261
                    int left = indent + 2;
×
UNCOV
1262
                    chart.chart_attrs_for_value(
×
1263
                        lv, left, width, iter->first, val_opt.value(), sa);
×
1264
                }
UNCOV
1265
            } else {
×
UNCOV
1266
                yajlpp_gen gen;
×
1267

1268
                {
UNCOV
1269
                    yajlpp_map root(gen);
×
1270

UNCOV
1271
                    root.gen("key");
×
UNCOV
1272
                    root.gen(hm.hm_name);
×
UNCOV
1273
                    root.gen("value");
×
UNCOV
1274
                    root.gen(sf);
×
1275
                }
UNCOV
1276
                al.append(": ").append(sf);
×
UNCOV
1277
                al.al_attrs.emplace_back(
×
UNCOV
1278
                    line_range{0, -1},
×
UNCOV
1279
                    DBA_DETAILS.value(gen.to_string_fragment().to_string()));
×
1280
            }
1281
        } else {
×
UNCOV
1282
            yajlpp_gen gen;
×
1283

1284
            {
1285
                yajlpp_map root(gen);
×
1286

1287
                root.gen("key");
×
1288
                root.gen(hm.hm_name);
×
1289
                root.gen("value");
×
UNCOV
1290
                switch (cursor->get_type()) {
×
UNCOV
1291
                    case lnav::cell_type::CT_NULL:
×
UNCOV
1292
                        root.gen();
×
UNCOV
1293
                        break;
×
UNCOV
1294
                    case lnav::cell_type::CT_INTEGER:
×
UNCOV
1295
                        root.gen(cursor->get_int());
×
UNCOV
1296
                        break;
×
UNCOV
1297
                    case lnav::cell_type::CT_FLOAT:
×
UNCOV
1298
                        if (cursor->get_sub_value() == 0) {
×
UNCOV
1299
                            root.gen(cursor->get_float());
×
1300
                        } else {
UNCOV
1301
                            root.gen(cursor->get_float_as_text());
×
1302
                        }
UNCOV
1303
                        break;
×
UNCOV
1304
                    case lnav::cell_type::CT_TEXT:
×
UNCOV
1305
                        root.gen(cursor->get_text());
×
UNCOV
1306
                        break;
×
1307
                }
1308
            }
1309

UNCOV
1310
            al.append(": ");
×
UNCOV
1311
            auto sf_line_count = sf.trim("\n").count('\n') + 1;
×
UNCOV
1312
            if (sf_line_count > 1) {
×
UNCOV
1313
                al.al_attrs.emplace_back(
×
UNCOV
1314
                    line_range{0, -1},
×
UNCOV
1315
                    DBA_DETAILS.value(gen.to_string_fragment().to_string()));
×
UNCOV
1316
                value_out.emplace_back(al);
×
UNCOV
1317
                for (auto& line_al : attr_line_t().append(sf).split_lines()) {
×
UNCOV
1318
                    line_al.insert(0, "\t");
×
UNCOV
1319
                    line_al.al_attrs.emplace_back(
×
UNCOV
1320
                        line_range{0, -1},
×
UNCOV
1321
                        DBA_DETAILS.value(
×
UNCOV
1322
                            gen.to_string_fragment().to_string()));
×
UNCOV
1323
                    value_out.emplace_back(line_al);
×
1324
                }
1325

UNCOV
1326
                al.clear();
×
1327
            } else {
UNCOV
1328
                auto value_al = attr_line_t().append(sf.trim("\n"));
×
UNCOV
1329
                al.append(value_al);
×
UNCOV
1330
                al.al_attrs.emplace_back(
×
UNCOV
1331
                    line_range{0, -1},
×
UNCOV
1332
                    DBA_DETAILS.value(gen.to_string_fragment().to_string()));
×
1333
            }
1334
        }
1335

UNCOV
1336
        if (!al.empty()) {
×
UNCOV
1337
            value_out.emplace_back(al);
×
1338
        }
UNCOV
1339
        cursor = cursor->next();
×
1340
    }
1341

UNCOV
1342
    this->dos_labels->dls_cell_allocator.reset();
×
1343
}
1344

1345
bool
1346
db_overlay_source::list_static_overlay(const listview_curses& lv,
344✔
1347
                                       media_t media,
1348
                                       int y,
1349
                                       int bottom,
1350
                                       attr_line_t& value_out)
1351
{
1352
    if (y != 0) {
344✔
1353
        return false;
101✔
1354
    }
1355

1356
    const auto* dls = this->dos_labels;
243✔
1357

1358
    if (dls->dls_headers.empty()) {
243✔
1359
        if (media == media_t::display) {
12✔
UNCOV
1360
            value_out.append(
×
1361
                "The results of SQLite queries are shown in this view. "
1362
                "Press ");
UNCOV
1363
            value_out.append(lnav::roles::keyword(";"));
×
UNCOV
1364
            value_out.append(" to execute a query.");
×
UNCOV
1365
            value_out.with_attr_for_all(VC_ROLE.value(role_t::VCR_STATUS));
×
UNCOV
1366
            value_out.with_attr_for_all(
×
UNCOV
1367
                VC_STYLE.value(text_attrs::with_underline()));
×
UNCOV
1368
            return true;
×
1369
        }
1370
        return false;
12✔
1371
    }
1372

1373
    auto& line = value_out.get_string();
231✔
1374
    auto& sa = value_out.get_attrs();
231✔
1375

1376
    for (size_t lpc = 0; lpc < this->dos_labels->dls_headers.size(); lpc++) {
1,348✔
1377
        if (lpc == this->dos_labels->dls_row_style_column
1,117✔
1378
            && !this->dos_labels->dls_row_styles_have_errors)
3✔
1379
        {
1380
            continue;
3✔
1381
        }
1382

1383
        const auto& hm = dls->dls_headers[lpc];
1,115✔
1384
        if (hm.hm_hidden) {
1,115✔
1385
            continue;
1✔
1386
        }
1387
        auto actual_col_size
1388
            = std::min(dls->dls_max_column_width, hm.hm_column_size);
1,114✔
1389
        auto cell_title = hm.hm_name;
1,114✔
1390
        string_attrs_t cell_attrs;
1,114✔
1391
        scrub_ansi_string(cell_title, &cell_attrs);
1,114✔
1392
        truncate_to(cell_title, dls->dls_max_column_width);
1,114✔
1393

1394
        auto cell_length
1395
            = utf8_string_length(cell_title).unwrapOr(actual_col_size);
1,114✔
1396
        int total_fill = actual_col_size - cell_length;
1,114✔
1397
        auto line_len_before = line.length();
1,114✔
1398

1399
        int before = total_fill / 2;
1,114✔
1400
        total_fill -= before;
1,114✔
1401
        line.append(before, ' ');
1,114✔
1402
        shift_string_attrs(cell_attrs, 0, line.size());
1,114✔
1403
        line.append(cell_title);
1,114✔
1404
        line.append(total_fill, ' ');
1,114✔
1405
        auto header_range = line_range(line_len_before, line.length());
1,114✔
1406

1407
        line.append(1, ' ');
1,114✔
1408

1409
        require_ge(header_range.lr_start, 0);
1,114✔
1410

1411
        sa.emplace_back(header_range, VC_STYLE.value(hm.hm_title_attrs));
1,114✔
1412
        sa.insert(sa.end(), cell_attrs.begin(), cell_attrs.end());
1,114✔
1413
    }
1,114✔
1414

1415
    line_range lr(0);
231✔
1416

1417
    sa.emplace_back(lr, VC_ROLE.value(role_t::VCR_TABLE_HEADER));
231✔
1418
    sa.emplace_back(
231✔
1419
        lr,
1420
        VC_STYLE.value(text_attrs::with_styles(text_attrs::style::underline)));
462✔
1421
    return true;
231✔
1422
}
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