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

tstack / lnav / 17589970077-2502

09 Sep 2025 05:00PM UTC coverage: 65.196% (-5.0%) from 70.225%
17589970077-2502

push

github

tstack
[format] add fields for source file/line

Knowing the source file/line context in a log
message can help find log messages when using
log2src.

56 of 70 new or added lines in 2 files covered. (80.0%)

13954 existing lines in 210 files now uncovered.

45516 of 69814 relevant lines covered (65.2%)

404154.37 hits per line

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

55.0
/src/spectro_source.cc
1
/**
2
 * Copyright (c) 2020, 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
 * @file spectro_source.cc
30
 */
31

32
#include <optional>
33
#include <vector>
34

35
#include "spectro_source.hh"
36

37
#include "base/ansi_scrubber.hh"
38
#include "base/keycodes.hh"
39
#include "base/math_util.hh"
40
#include "config.h"
41

42
static constexpr auto TIME_COLUMN_WIDTH = 22;
43
static constexpr auto UNUSABLE_WIDTH = TIME_COLUMN_WIDTH + 2;
44
static constexpr auto MINIMUM_WIDTH = UNUSABLE_WIDTH + 20;
45

46
std::optional<size_t>
47
spectrogram_row::nearest_column(size_t current) const
3✔
48
{
49
    std::optional<size_t> retval;
3✔
50
    std::optional<size_t> nearest_distance;
3✔
51

52
    for (size_t lpc = 0; lpc < this->sr_width; lpc++) {
171✔
53
        if (this->sr_values[lpc].rb_counter == 0) {
168✔
54
            continue;
105✔
55
        }
56
        auto curr_distance = abs_diff(lpc, current);
63✔
57

58
        if (!retval || curr_distance < nearest_distance.value()) {
63✔
59
            retval = lpc;
3✔
60
            nearest_distance = abs_diff(lpc, current);
3✔
61
        }
62
    }
63

64
    return retval;
3✔
65
}
66

67
bool
68
spectrogram_source::list_input_handle_key(listview_curses& lv,
×
69
                                          const ncinput& ch)
70
{
UNCOV
71
    switch (ch.eff_text[0]) {
×
UNCOV
72
        case 'm': {
×
UNCOV
73
            auto sel = lv.get_selection();
×
74
            if (!sel || sel.value() < 0
×
UNCOV
75
                || (size_t) sel.value() >= this->text_line_count()
×
76
                || !this->ss_cursor_column || this->ss_value_source == nullptr)
×
77
            {
UNCOV
78
                alerter::singleton().chime(
×
79
                    "a value must be selected before it can be marked");
80
                return true;
×
81
            }
82

UNCOV
83
            auto [height, width] = lv.get_dimensions();
×
84
            width -= UNUSABLE_WIDTH;
×
85

UNCOV
86
            auto& sb = this->ss_cached_bounds;
×
87
            auto begin_time_opt = this->time_for_row_int(sel.value());
×
UNCOV
88
            if (!begin_time_opt) {
×
UNCOV
89
                return true;
×
90
            }
91
            auto begin_time = begin_time_opt.value();
×
92
            auto end_time = to_us(begin_time.ri_time);
×
93

94
            end_time += this->ss_granularity;
×
95

UNCOV
96
            double column_size = (sb.sb_max_value_out - sb.sb_min_value_out)
×
UNCOV
97
                / (double) (width - 1);
×
UNCOV
98
            double range_min = sb.sb_min_value_out
×
UNCOV
99
                + this->ss_cursor_column.value_or(0) * column_size;
×
100
            double range_max = range_min + column_size;
×
101
            this->ss_value_source->spectro_mark((textview_curses&) lv,
×
102
                                                to_us(begin_time.ri_time),
103
                                                end_time,
104
                                                range_min,
105
                                                range_max);
106
            auto cursor_col = this->ss_cursor_column;
×
107
            this->invalidate();
×
108
            this->ss_cursor_column = cursor_col;
×
109
            this->text_selection_changed((textview_curses&) lv);
×
UNCOV
110
            lv.reload_data();
×
111
            return true;
×
112
        }
113

114
        case KEY_CTRL('a'): {
×
115
            if (this->ss_value_source != nullptr) {
×
116
                this->ss_cursor_column = 0;
×
117
                this->text_selection_changed((textview_curses&) lv);
×
118
                lv.set_needs_update();
×
119
            }
120
            return true;
×
121
        }
122

123
        case KEY_CTRL('e'): {
×
UNCOV
124
            if (this->ss_value_source != nullptr) {
×
125
                this->ss_cursor_column = INT_MAX;
×
UNCOV
126
                this->text_selection_changed((textview_curses&) lv);
×
127
                lv.set_needs_update();
×
128
            }
UNCOV
129
            return true;
×
130
        }
131

132
        case NCKEY_LEFT:
×
133
        case NCKEY_RIGHT: {
134
            auto sel = lv.get_selection();
×
135
            string_attrs_t sa;
×
136
            this->chart_attrs_for_line(
×
137
                (textview_curses&) lv, sel.value_or(0_vl), sa);
×
138

UNCOV
139
            if (sa.empty()) {
×
140
                this->reset_details_source();
×
141
                this->ss_cursor_column = std::nullopt;
×
UNCOV
142
                return true;
×
143
            }
144

UNCOV
145
            if (!this->ss_cursor_column) {
×
146
                lv.set_selection(0_vl);
×
147
            }
148
            line_range lr(
149
                TIME_COLUMN_WIDTH + this->ss_cursor_column.value(),
×
150
                TIME_COLUMN_WIDTH + this->ss_cursor_column.value() + 1);
×
151

UNCOV
152
            auto current = find_string_attr(sa, lr);
×
153

UNCOV
154
            if (current != sa.end()) {
×
UNCOV
155
                if (ch.id == NCKEY_LEFT) {
×
156
                    if (current == sa.begin()) {
×
UNCOV
157
                        current = sa.end();
×
158
                    } else {
UNCOV
159
                        --current;
×
160
                    }
161
                } else {
162
                    ++current;
×
163
                }
164
            }
165

UNCOV
166
            if (current == sa.end()) {
×
UNCOV
167
                if (ch.id == NCKEY_LEFT) {
×
168
                    current = sa.end();
×
169
                    --current;
×
170
                } else {
171
                    current = sa.begin();
×
172
                }
173
            }
174
            this->ss_cursor_column
175
                = current->sa_range.lr_start - TIME_COLUMN_WIDTH;
×
176
            this->reset_details_source();
×
177

UNCOV
178
            lv.reload_data();
×
179

UNCOV
180
            return true;
×
181
        }
UNCOV
182
        default:
×
UNCOV
183
            return false;
×
184
    }
185
}
186

187
bool
UNCOV
188
spectrogram_source::text_handle_mouse(
×
189
    textview_curses& tc,
190
    const listview_curses::display_line_content_t&,
191
    mouse_event& me)
192
{
193
    auto sel = tc.get_selection();
×
UNCOV
194
    if (!sel || me.me_state != mouse_button_state_t::BUTTON_STATE_RELEASED) {
×
UNCOV
195
        return false;
×
196
    }
197
    const auto& s_row = this->load_row(tc, sel.value());
×
198
    std::optional<size_t> closest_column;
×
199
    int closest_distance = INT_MAX;
×
200

201
    for (int lpc = 0; lpc <= (int) s_row.sr_width; lpc++) {
×
202
        int col_value = s_row.sr_values[lpc].rb_counter;
×
203

UNCOV
204
        if (col_value == 0) {
×
UNCOV
205
            continue;
×
206
        }
207

UNCOV
208
        auto distance = std::abs(me.me_x - (TIME_COLUMN_WIDTH + lpc));
×
UNCOV
209
        if (distance < closest_distance) {
×
UNCOV
210
            closest_distance = distance;
×
UNCOV
211
            closest_column = lpc;
×
212
        }
213
    }
214

UNCOV
215
    if (closest_column) {
×
UNCOV
216
        this->ss_cursor_column = closest_column;
×
UNCOV
217
        this->reset_details_source();
×
UNCOV
218
        tc.reload_data();
×
UNCOV
219
        return true;
×
220
    }
UNCOV
221
    return false;
×
222
}
223

224
void
225
spectrogram_source::list_value_for_overlay(const listview_curses& lv,
2✔
226
                                           vis_line_t row,
227
                                           std::vector<attr_line_t>& value_out)
228
{
229
    auto [height, width] = lv.get_dimensions();
2✔
230
    width -= UNUSABLE_WIDTH;
2✔
231

232
    const auto sel = lv.get_selection();
2✔
233
    if (sel && row == sel.value() && this->ss_cursor_column) {
2✔
234
        auto hash = hasher();
1✔
235
        hash.update(this->ss_cursor_column.value());
1✔
236
        hash.update(width);
1✔
237
        hash.update(sel.value());
1✔
238
        auto checksum = hash.to_array();
1✔
239

240
        if (checksum == this->ss_cursor_details_checksum) {
1✔
UNCOV
241
            value_out.push_back(this->ss_cursor_details);
×
UNCOV
242
            return;
×
243
        }
244

245
        const auto& s_row = this->load_row(lv, sel.value());
1✔
246
        const auto& bucket = s_row.sr_values[this->ss_cursor_column.value()];
1✔
247
        auto& sb = this->ss_cached_bounds;
1✔
248
        spectrogram_request sr(sb);
1✔
249
        attr_line_t retval;
1✔
250

251
        auto sel_time = rounddown(sb.sb_begin_time, this->ss_granularity)
1✔
252
            + (sel.value() * this->ss_granularity);
2✔
253
        sr.sr_width = width;
1✔
254
        sr.sr_begin_time = sel_time;
1✔
255
        sr.sr_end_time = sel_time + this->ss_granularity;
1✔
256
        sr.sr_column_size = (sb.sb_max_value_out - sb.sb_min_value_out)
1✔
257
            / (double) (width - 1);
1✔
258
        auto range_min = sb.sb_min_value_out
1✔
259
            + this->ss_cursor_column.value() * sr.sr_column_size;
1✔
260
        auto range_max = range_min + sr.sr_column_size;
1✔
261

262
        auto desc
263
            = attr_line_t()
1✔
264
                  .append(
1✔
265
                      lnav::roles::number(fmt::to_string(bucket.rb_counter)))
2✔
266
                  .append(fmt::format(FMT_STRING(" value{} in the range "),
3✔
267
                                      bucket.rb_counter == 1 ? "" : "s"))
2✔
268
                  .append(lnav::roles::number(
1✔
269
                      fmt::format(FMT_STRING("{:.2Lf}"), range_min)))
5✔
270
                  .append("-")
1✔
271
                  .append(lnav::roles::number(
2✔
272
                      fmt::format(FMT_STRING("{:.2Lf}"), range_max)))
5✔
273
                  .append(" ");
1✔
274
        auto mark_offset = TIME_COLUMN_WIDTH + this->ss_cursor_column.value();
1✔
275
        auto mark_is_before = true;
1✔
276

277
        retval.al_attrs.emplace_back(line_range{0, -1},
1✔
278
                                     VC_ROLE.value(role_t::VCR_STATUS_INFO));
2✔
279
        if (desc.length() + 8 > (ssize_t) width) {
1✔
UNCOV
280
            desc.clear();
×
281
        }
282

283
        if (this->ss_cursor_column.value() + desc.length() + 1 > width) {
1✔
UNCOV
284
            mark_offset -= desc.length();
×
285
            mark_is_before = false;
×
286
        }
287
        retval.append(mark_offset, ' ');
1✔
288
        if (mark_is_before) {
1✔
289
            retval.append("\u25b2 ");
1✔
290
        }
291
        retval.append(desc);
1✔
292
        if (!mark_is_before) {
1✔
293
            retval.append("\u25b2 ");
×
294
        }
295

296
        if (this->ss_details_view != nullptr) {
1✔
UNCOV
297
            if (s_row.sr_details_source_provider) {
×
298
                auto row_details_source = s_row.sr_details_source_provider(
UNCOV
299
                    sr, range_min, range_max);
×
300

UNCOV
301
                this->ss_details_view->set_sub_source(row_details_source.get());
×
UNCOV
302
                this->ss_details_source = std::move(row_details_source);
×
UNCOV
303
                auto* overlay_source = dynamic_cast<list_overlay_source*>(
×
UNCOV
304
                    this->ss_details_source.get());
×
UNCOV
305
                if (overlay_source != nullptr) {
×
UNCOV
306
                    this->ss_details_view->set_overlay_source(overlay_source);
×
307
                }
UNCOV
308
            } else {
×
UNCOV
309
                this->ss_details_view->set_sub_source(
×
310
                    this->ss_no_details_source);
UNCOV
311
                this->ss_details_view->set_overlay_source(nullptr);
×
312
            }
313
        }
314

315
        this->ss_cursor_details = retval;
1✔
316
        this->ss_cursor_details_checksum = checksum;
1✔
317
        value_out.emplace_back(retval);
1✔
318
    }
1✔
319
}
320

321
size_t
322
spectrogram_source::text_line_count()
23,192✔
323
{
324
    if (this->ss_value_source == nullptr) {
23,192✔
325
        return 0;
23,172✔
326
    }
327

328
    this->cache_bounds();
20✔
329

330
    return this->ss_cached_line_count;
20✔
331
}
332

333
size_t
334
spectrogram_source::text_line_width(textview_curses& tc)
7,137✔
335
{
336
    if (tc.get_window() == nullptr) {
7,137✔
337
        return 80;
7,137✔
338
    }
339

340
    unsigned long width;
UNCOV
341
    vis_line_t height;
×
342

UNCOV
343
    tc.get_dimensions(height, width);
×
UNCOV
344
    return width;
×
345
}
346

347
std::optional<text_time_translator::row_info>
348
spectrogram_source::time_for_row(vis_line_t row)
1✔
349
{
350
    if (this->ss_details_source != nullptr) {
1✔
UNCOV
351
        auto* details_tss = dynamic_cast<text_time_translator*>(
×
UNCOV
352
            this->ss_details_source.get());
×
353

UNCOV
354
        if (details_tss != nullptr) {
×
UNCOV
355
            return details_tss->time_for_row(this->ss_details_view->get_top());
×
356
        }
357
    }
358

359
    return this->time_for_row_int(row);
1✔
360
}
361

362
std::optional<text_time_translator::row_info>
363
spectrogram_source::time_for_row_int(vis_line_t row)
3✔
364
{
365
    auto retval = timeval{0, 0};
3✔
366

367
    this->cache_bounds();
3✔
368
    retval.tv_sec = to_time_t(
3✔
369
        rounddown(this->ss_cached_bounds.sb_begin_time, this->ss_granularity)
3✔
370
        + row * this->ss_granularity);
3✔
371

372
    return row_info{retval, row};
3✔
373
}
374

375
std::optional<vis_line_t>
376
spectrogram_source::row_for_time(timeval time_bucket)
×
377
{
UNCOV
378
    if (this->ss_value_source == nullptr) {
×
379
        return std::nullopt;
×
380
    }
381

UNCOV
382
    this->cache_bounds();
×
UNCOV
383
    const auto tb_us = to_us(time_bucket);
×
384
    const auto grain_begin_time
UNCOV
385
        = rounddown(this->ss_cached_bounds.sb_begin_time, this->ss_granularity);
×
UNCOV
386
    if (tb_us < grain_begin_time) {
×
UNCOV
387
        return 0_vl;
×
388
    }
389

UNCOV
390
    const auto diff = tb_us - grain_begin_time;
×
UNCOV
391
    const auto retval = diff / this->ss_granularity;
×
392

UNCOV
393
    return vis_line_t(retval);
×
394
}
395

396
line_info
397
spectrogram_source::text_value_for_line(textview_curses& tc,
2✔
398
                                        int row,
399
                                        std::string& value_out,
400
                                        text_sub_source::line_flags_t flags)
401
{
402
    if (tc.get_dimensions().second < MINIMUM_WIDTH) {
2✔
UNCOV
403
        return {};
×
404
    }
405

406
    const auto& s_row = this->load_row(tc, row);
2✔
407
    char tm_buffer[128];
408
    tm tm;
409

410
    auto row_time_opt = this->time_for_row_int(vis_line_t(row));
2✔
411
    if (!row_time_opt) {
2✔
UNCOV
412
        value_out.clear();
×
UNCOV
413
        return {};
×
414
    }
415
    auto ri = row_time_opt.value();
2✔
416

417
    gmtime_r(&ri.ri_time.tv_sec, &tm);
2✔
418
    strftime(tm_buffer, sizeof(tm_buffer), " %a %b %d %H:%M:%S", &tm);
2✔
419

420
    value_out = tm_buffer;
2✔
421
    value_out.resize(TIME_COLUMN_WIDTH + s_row.sr_width, ' ');
2✔
422

423
    for (size_t lpc = 0; lpc <= s_row.sr_width; lpc++) {
116✔
424
        if (s_row.sr_values[lpc].rb_marks) {
114✔
UNCOV
425
            value_out[TIME_COLUMN_WIDTH + lpc] = 'x';
×
426
        }
427
    }
428

429
    return {};
2✔
430
}
431

432
void
433
spectrogram_source::chart_attrs_for_line(textview_curses& tc,
2✔
434
                                         int row,
435
                                         string_attrs_t& value_out)
436
{
437
    const auto& st = this->ss_cached_thresholds;
2✔
438
    const auto& s_row = this->load_row(tc, row);
2✔
439

440
    for (int lpc = 0; lpc <= (int) s_row.sr_width; lpc++) {
116✔
441
        int col_value = s_row.sr_values[lpc].rb_counter;
114✔
442

443
        if (col_value == 0) {
114✔
444
            continue;
85✔
445
        }
446

447
        role_t role;
448

449
        if (col_value < st.st_green_threshold) {
29✔
450
            role = role_t::VCR_LOW_THRESHOLD;
27✔
451
        } else if (col_value < st.st_yellow_threshold) {
2✔
452
            role = role_t::VCR_MED_THRESHOLD;
1✔
453
        } else {
454
            role = role_t::VCR_HIGH_THRESHOLD;
1✔
455
        }
456
        auto lr
457
            = line_range{TIME_COLUMN_WIDTH + lpc, TIME_COLUMN_WIDTH + lpc + 1};
29✔
458
        value_out.emplace_back(lr, VC_ROLE.value(role));
29✔
459
    }
460
}
2✔
461

462
void
463
spectrogram_source::text_attrs_for_line(textview_curses& tc,
2✔
464
                                        int row,
465
                                        string_attrs_t& value_out)
466
{
467
    if (this->ss_value_source == nullptr) {
2✔
UNCOV
468
        return;
×
469
    }
470

471
    if (tc.get_dimensions().second < MINIMUM_WIDTH) {
2✔
UNCOV
472
        return;
×
473
    }
474

475
    this->chart_attrs_for_line(tc, row, value_out);
2✔
476

477
    auto alt_row_index = row % 4;
2✔
478
    if (alt_row_index == 2 || alt_row_index == 3) {
2✔
UNCOV
479
        value_out.emplace_back(line_range{0, -1},
×
UNCOV
480
                               VC_ROLE.value(role_t::VCR_ALT_ROW));
×
481
    }
482
}
483

484
void
485
spectrogram_source::reset_details_source()
582✔
486
{
487
    if (this->ss_details_view != nullptr) {
582✔
UNCOV
488
        this->ss_details_view->set_sub_source(this->ss_no_details_source);
×
UNCOV
489
        this->ss_details_view->set_overlay_source(nullptr);
×
490
    }
491
    this->ss_details_source.reset();
582✔
492
    this->ss_cursor_details_checksum.clear();
582✔
493
}
582✔
494

495
void
496
spectrogram_source::cache_bounds()
32✔
497
{
498
    if (this->ss_value_source == nullptr) {
32✔
UNCOV
499
        this->ss_cached_bounds.sb_count = 0;
×
500
        this->ss_cached_bounds.sb_begin_time
UNCOV
501
            = std::chrono::microseconds::zero();
×
502
        this->ss_cursor_column = std::nullopt;
×
UNCOV
503
        this->reset_details_source();
×
504
        return;
31✔
505
    }
506

507
    spectrogram_bounds sb;
32✔
508

509
    this->ss_value_source->spectro_bounds(sb);
32✔
510

511
    if (sb.sb_count == this->ss_cached_bounds.sb_count) {
32✔
512
        return;
31✔
513
    }
514

515
    this->ss_cached_bounds = sb;
1✔
516

517
    if (sb.sb_count == 0) {
1✔
UNCOV
518
        this->ss_cached_line_count = 0;
×
UNCOV
519
        this->ss_cursor_column = std::nullopt;
×
UNCOV
520
        this->reset_details_source();
×
UNCOV
521
        return;
×
522
    }
523

524
    auto grain_begin_time = rounddown(sb.sb_begin_time, this->ss_granularity);
1✔
525
    auto grain_end_time = roundup_size(sb.sb_end_time, this->ss_granularity);
1✔
526

527
    auto diff = std::max(std::chrono::microseconds{1},
1✔
528
                         grain_end_time - grain_begin_time);
1✔
529
    this->ss_cached_line_count
530
        = (diff + this->ss_granularity - std::chrono::microseconds{1})
1✔
531
        / this->ss_granularity;
2✔
532

533
    int64_t samples_per_row = sb.sb_count / this->ss_cached_line_count;
1✔
534
    auto& st = this->ss_cached_thresholds;
1✔
535

536
    st.st_yellow_threshold = samples_per_row / 2;
1✔
537
    st.st_green_threshold = st.st_yellow_threshold / 2;
1✔
538

539
    if (st.st_green_threshold <= 1) {
1✔
UNCOV
540
        st.st_green_threshold = 2;
×
541
    }
542
    if (st.st_yellow_threshold <= st.st_green_threshold) {
1✔
UNCOV
543
        st.st_yellow_threshold = st.st_green_threshold + 1;
×
544
    }
545
}
546

547
const spectrogram_row&
548
spectrogram_source::load_row(const listview_curses& tc, int row)
8✔
549
{
550
    this->cache_bounds();
8✔
551

552
    auto [height, width] = tc.get_dimensions();
8✔
553
    width -= UNUSABLE_WIDTH;
8✔
554

555
    auto& sb = this->ss_cached_bounds;
8✔
556
    spectrogram_request sr(sb);
8✔
557

558
    sr.sr_width = width;
8✔
559
    auto row_time = rounddown(sb.sb_begin_time, this->ss_granularity)
8✔
560
        + row * this->ss_granularity;
16✔
561
    sr.sr_begin_time = row_time;
8✔
562
    sr.sr_end_time = row_time + this->ss_granularity;
8✔
563

564
    sr.sr_column_size
565
        = (sb.sb_max_value_out - sb.sb_min_value_out) / (double) (width - 1);
8✔
566

567
    auto& s_row = this->ss_row_cache[row_time];
8✔
568

569
    if (s_row.sr_values.empty() || s_row.sr_width != width
14✔
570
        || s_row.sr_column_size != sr.sr_column_size)
14✔
571
    {
572
        s_row.sr_width = width;
2✔
573
        s_row.sr_column_size = sr.sr_column_size;
2✔
574
        s_row.sr_values.clear();
2✔
575
        s_row.sr_values.resize(width + 1);
2✔
576
        this->ss_value_source->spectro_row(sr, s_row);
2✔
577
    }
578

579
    return s_row;
8✔
580
}
581

582
bool
583
spectrogram_source::text_is_row_selectable(textview_curses& tc, vis_line_t row)
1✔
584
{
585
    if (this->ss_value_source == nullptr) {
1✔
UNCOV
586
        return false;
×
587
    }
588

589
    const auto& s_row = this->load_row(tc, row);
1✔
590
    auto nearest_column
591
        = s_row.nearest_column(this->ss_cursor_column.value_or(0));
1✔
592

593
    return nearest_column.has_value();
1✔
594
}
595

596
void
597
spectrogram_source::text_selection_changed(textview_curses& tc)
582✔
598
{
599
    if (this->ss_value_source == nullptr || this->text_line_count() == 0) {
582✔
600
        this->ss_cursor_column = std::nullopt;
580✔
601
        this->reset_details_source();
580✔
602
        return;
580✔
603
    }
604

605
    if (!tc.get_selection()) {
2✔
606
        tc.set_selection(0_vl);
1✔
607
    }
608
    const auto& s_row = this->load_row(tc, tc.get_selection().value());
2✔
609
    this->ss_cursor_column
610
        = s_row.nearest_column(this->ss_cursor_column.value_or(0));
2✔
611
    this->ss_cursor_details_checksum.clear();
2✔
612
    this->reset_details_source();
2✔
613
}
614

615
bool
616
spectrogram_source::list_static_overlay(const listview_curses& lv,
2✔
617
                                        int y,
618
                                        int bottom,
619
                                        attr_line_t& value_out)
620
{
621
    if (y != 0) {
2✔
622
        return false;
1✔
623
    }
624
    auto [height, width] = lv.get_dimensions();
1✔
625

626
    if (width < MINIMUM_WIDTH) {
1✔
UNCOV
627
        value_out = lnav::console::user_message::error(
×
628
                        "window is too narrow, not able to show chart")
UNCOV
629
                        .to_attr_line();
×
UNCOV
630
        return true;
×
631
    }
632

633
    auto& line = value_out.get_string();
1✔
634
    char buf[128];
635
    width -= 2;
1✔
636

637
    this->cache_bounds();
1✔
638

639
    if (this->ss_cached_line_count == 0) {
1✔
640
        value_out
UNCOV
641
            .append(lnav::roles::error("error: no data available, use the "))
×
UNCOV
642
            .append_quoted(lnav::roles::keyword(":spectrogram"))
×
UNCOV
643
            .append(lnav::roles::error(" command to visualize numeric data"));
×
UNCOV
644
        return true;
×
645
    }
646

647
    auto& sb = this->ss_cached_bounds;
1✔
648
    auto& st = this->ss_cached_thresholds;
1✔
649

650
    line.append(TIME_COLUMN_WIDTH, ' ');
1✔
651
    snprintf(buf, sizeof(buf), "Min: %'.10lg", sb.sb_min_value_out);
1✔
652
    line.append(buf);
1✔
653

654
    snprintf(buf,
1✔
655
             sizeof(buf),
656
             ANSI_ROLE("  ") " 1-%'d " ANSI_ROLE("  ") " %'d-%'d " ANSI_ROLE(
657
                 "  ") " %'d+",
658
             role_t::VCR_LOW_THRESHOLD,
659
             st.st_green_threshold - 1,
1✔
660
             role_t::VCR_MED_THRESHOLD,
661
             st.st_green_threshold,
662
             st.st_yellow_threshold - 1,
1✔
663
             role_t::VCR_HIGH_THRESHOLD,
664
             st.st_yellow_threshold);
665
    auto buflen = strlen(buf);
1✔
666
    if (line.length() + buflen + 20 < width) {
1✔
UNCOV
667
        line.append(width / 2 - buflen / 3 - line.length(), ' ');
×
668
    } else {
669
        line.append(" ");
1✔
670
    }
671
    line.append(buf);
1✔
672
    scrub_ansi_string(line, &value_out.get_attrs());
1✔
673

674
    snprintf(buf, sizeof(buf), "Max: %'.10lg", sb.sb_max_value_out);
1✔
675
    buflen = strlen(buf);
1✔
676
    if (line.length() + buflen + 4 < width) {
1✔
677
        line.append(width - buflen - line.length() - 2, ' ');
1✔
678
    } else {
UNCOV
679
        line.append(" ");
×
680
    }
681
    line.append(buf);
1✔
682

683
    value_out.with_attr(string_attr(
1✔
684
        line_range(0, -1), VC_STYLE.value(text_attrs::with_underline())));
2✔
685

686
    return true;
1✔
687
}
688

UNCOV
689
spectro_status_source::spectro_status_source()
×
690
{
UNCOV
691
    this->sss_fields[F_TITLE].set_width(9);
×
UNCOV
692
    this->sss_fields[F_TITLE].set_role(role_t::VCR_STATUS_TITLE);
×
UNCOV
693
    this->sss_fields[F_TITLE].set_value(" Details ");
×
694

UNCOV
695
    this->sss_fields[F_HELP].right_justify(true);
×
UNCOV
696
    this->sss_fields[F_HELP].set_width(20);
×
UNCOV
697
    this->sss_fields[F_HELP].set_value("Press " ANSI_BOLD("TAB") " to focus ");
×
UNCOV
698
    this->sss_fields[F_HELP].set_left_pad(1);
×
699
}
700

701
size_t
UNCOV
702
spectro_status_source::statusview_fields()
×
703
{
UNCOV
704
    return F_MAX;
×
705
}
706

707
status_field&
UNCOV
708
spectro_status_source::statusview_value_for_field(int field)
×
709
{
UNCOV
710
    return this->sss_fields[field];
×
711
}
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