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

tstack / lnav / 20284884426-2753

16 Dec 2025 10:23PM UTC coverage: 68.23% (-0.7%) from 68.903%
20284884426-2753

push

github

tstack
[log] show invalid utf hex dump in log view too

25 of 25 new or added lines in 2 files covered. (100.0%)

503 existing lines in 33 files now uncovered.

51170 of 74996 relevant lines covered (68.23%)

433797.6 hits per line

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

64.54
/src/bottom_status_source.cc
1
/**
2
 * Copyright (c) 2019, 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 "bottom_status_source.hh"
31

32
#include "base/snippet_highlighters.hh"
33
#include "config.h"
34

35
bottom_status_source::bottom_status_source()
8,183✔
36
{
37
    this->bss_fields[BSF_LINE_NUMBER].set_min_width(10);
1,169✔
38
    this->bss_fields[BSF_LINE_NUMBER].set_share(1000);
1,169✔
39
    this->bss_fields[BSF_PERCENT].set_width(6);
1,169✔
40
    this->bss_fields[BSF_PERCENT].set_left_pad(1);
1,169✔
41
    this->bss_fields[BSF_HITS].set_min_width(10);
1,169✔
42
    this->bss_fields[BSF_HITS].set_share(5);
1,169✔
43
    this->bss_fields[BSF_SEARCH_TERM].set_min_width(10);
1,169✔
44
    this->bss_fields[BSF_SEARCH_TERM].set_share(1);
1,169✔
45
    this->bss_fields[BSF_LOADING].set_width(13);
1,169✔
46
    this->bss_fields[BSF_LOADING].right_justify(true);
1,169✔
47
    this->bss_fields[BSF_HELP].set_width(14);
1,169✔
48
    this->bss_fields[BSF_HELP].set_value("?:View Help "_frag);
1,169✔
49
    this->bss_fields[BSF_HELP].right_justify(true);
1,169✔
50
    this->bss_prompt.set_left_pad(1);
1,169✔
51
    this->bss_prompt.set_min_width(35);
1,169✔
52
    this->bss_prompt.set_share(1);
1,169✔
53
    this->bss_error.set_left_pad(1);
1,169✔
54
    this->bss_error.set_min_width(35);
1,169✔
55
    this->bss_error.set_share(1);
1,169✔
56
    this->bss_line_error.set_left_pad(1);
1,169✔
57
    this->bss_line_error.set_min_width(35);
1,169✔
58
    this->bss_line_error.set_share(1);
1,169✔
59
}
1,169✔
60

61
void
62
bottom_status_source::update_line_number(listview_curses* lc)
7✔
63
{
64
    auto& sf = this->bss_fields[BSF_LINE_NUMBER];
7✔
65
    auto sel = lc->get_selection();
7✔
66

67
    if (lc->get_inner_height() == 0) {
7✔
68
        sf.set_value(" L0"_frag);
7✔
UNCOV
69
    } else if (sel) {
×
UNCOV
70
        sf.set_value(" L%'d", (int) sel.value());
×
71
    } else {
UNCOV
72
        sf.set_value(" L-"_frag);
×
73
    }
74

75
    this->bss_line_error.set_value(
7✔
76
        lc->map_top_row([](const attr_line_t& top_row)
14✔
77
                            -> std::optional<std::string> {
UNCOV
78
              const auto& sa = top_row.get_attrs();
×
UNCOV
79
              auto error_wrapper = get_string_attr(sa, SA_ERROR);
×
UNCOV
80
              if (error_wrapper) {
×
81
                  return error_wrapper.value().get();
×
82
              }
UNCOV
83
              return std::nullopt;
×
84
          }).value_or(""));
14✔
85
}
7✔
86

87
void
88
bottom_status_source::update_search_term(textview_curses& tc)
1✔
89
{
90
    auto& sf = this->bss_fields[BSF_SEARCH_TERM];
1✔
91
    auto search_term = tc.get_current_search();
1✔
92

93
    sf.clear();
1✔
94
    if (!search_term.empty()) {
1✔
95
        auto search_term_al = attr_line_t(search_term);
×
96

97
        lnav::snippets::regex_highlighter(
×
98
            search_term_al, -1, line_range{0, (int) search_term_al.length()});
×
99
        sf.get_value().append_quoted(search_term_al);
×
100
    }
101

102
    this->bss_paused = tc.is_paused();
1✔
103
    this->update_loading(0, 0);
1✔
104
}
1✔
105

106
void
107
bottom_status_source::update_percent(listview_curses* lc)
7✔
108
{
109
    status_field& sf = this->bss_fields[BSF_PERCENT];
7✔
110
    vis_line_t top = lc->get_top();
7✔
111
    vis_line_t bottom, height;
7✔
112
    unsigned long width;
113
    double percent;
114

115
    lc->get_dimensions(height, width);
7✔
116

117
    if (lc->get_inner_height() > 0) {
7✔
UNCOV
118
        bottom = std::min(top + height - 1_vl, lc->get_inner_height() - 1_vl);
×
UNCOV
119
        percent = (double) (bottom + 1);
×
UNCOV
120
        percent /= (double) lc->get_inner_height();
×
UNCOV
121
        percent *= 100.0;
×
122
    } else {
123
        percent = 0.0;
7✔
124
    }
125
    sf.set_value("%3d%% ", (int) percent);
7✔
126
}
7✔
127

128
bool
129
bottom_status_source::update_marks(listview_curses* lc)
8✔
130
{
131
    auto* tc = static_cast<textview_curses*>(lc);
8✔
132
    auto& bm = tc->get_bookmarks();
8✔
133
    status_field& sf = this->bss_fields[BSF_HITS];
8✔
134
    auto retval = false;
8✔
135

136
    const auto& bv = bm[&textview_curses::BM_SEARCH];
8✔
137

138
    if (!bv.empty() || !tc->get_current_search().empty()) {
8✔
139
        auto vl = tc->get_selection();
×
140
        if (vl) {
×
141
            auto lb = bv.bv_tree.find(vl.value());
×
142
            if (lb != bv.bv_tree.end()) {
×
143
                retval = sf.set_value("  Hit %'d of %'d for ",
×
144
                                      (lb - bv.bv_tree.begin()) + 1,
×
145
                                      tc->get_match_count());
146
            } else {
147
                retval = sf.set_value("  %'d hits for ", tc->get_match_count());
×
148
            }
149
        }
150
    } else {
151
        retval = sf.clear();
8✔
152
    }
153
    return retval;
8✔
154
}
155

156
bool
157
bottom_status_source::update_hits(textview_curses* tc)
1✔
158
{
159
    auto& sf = this->bss_fields[BSF_HITS];
1✔
160
    bool retval = false;
1✔
161
    role_t new_role;
162

163
    if (tc->is_searching()) {
1✔
164
        this->bss_hit_spinner += 1;
×
165
        if (this->bss_hit_spinner % 2) {
×
166
            new_role = role_t::VCR_ACTIVE_STATUS;
×
167
        } else {
168
            new_role = role_t::VCR_ACTIVE_STATUS2;
×
169
        }
170
        if (!sf.is_cylon()) {
×
171
            sf.set_cylon(true);
×
172
        }
173
        retval = true;
×
174
    } else {
175
        new_role = role_t::VCR_STATUS;
1✔
176
        if (sf.is_cylon()) {
1✔
177
            sf.set_cylon(false);
×
178
            sf.clear();  // clear cylon style attribute
×
179
            retval = true;
×
180
        }
181
    }
182
    // this->bss_error.clear();
183
    sf.set_role(new_role);
1✔
184
    retval = this->update_marks(tc) || retval;
1✔
185
    return retval;
1✔
186
}
187

188
void
189
bottom_status_source::update_loading(file_off_t off,
137✔
190
                                     file_ssize_t total,
191
                                     const char* term)
192
{
193
    auto& sf = this->bss_fields[BSF_LOADING];
137✔
194

195
    require_ge(off, 0);
137✔
196
    require_ge(total, off);
137✔
197

198
    if (total == 0) {
137✔
199
        sf.set_cylon(false);
137✔
200
        sf.set_role(role_t::VCR_STATUS);
137✔
201
        if (this->bss_paused) {
137✔
202
            sf.set_value("\xE2\x80\x96 Paused"_frag);
×
203
        } else {
204
            sf.clear();
137✔
205
        }
UNCOV
206
    } else if (off == total) {
×
207
        static const char* const DOTS[] = {
208
            "   ",
209
            ".  ",
210
            ".. ",
211
            "...",
212
            ".. ",
213
            ".  ",
214
        };
215
        static auto DOTS_LEN = std::distance(std::begin(DOTS), std::end(DOTS));
216

217
        this->bss_load_percent += 1;
×
218
        sf.set_cylon(true);
×
219
        sf.set_role(role_t::VCR_ACTIVE_STATUS2);
×
220
        sf.set_value(" Working%s  ", DOTS[this->bss_load_percent % DOTS_LEN]);
×
221
    } else {
UNCOV
222
        int pct = (int) (((double) off / (double) total) * 100.0);
×
223

UNCOV
224
        if (this->bss_load_percent != pct) {
×
UNCOV
225
            this->bss_load_percent = pct;
×
226

UNCOV
227
            sf.set_cylon(true);
×
UNCOV
228
            sf.set_role(role_t::VCR_ACTIVE_STATUS2);
×
UNCOV
229
            sf.set_value(" %s %2d%% ", term, pct);
×
230
        }
231
    }
232
}
137✔
233

234
size_t
235
bottom_status_source::statusview_fields()
19✔
236
{
237
    size_t retval;
238

239
    if (this->bss_prompt.empty() && this->bss_error.empty()
38✔
240
        && this->bss_line_error.empty())
38✔
241
    {
242
        retval = BSF__MAX - 1;
19✔
243
    } else {
244
        retval = 1;
×
245
    }
246

247
    return retval;
19✔
248
}
249

250
status_field&
251
bottom_status_source::statusview_value_for_field(int field)
95✔
252
{
253
    if (!this->bss_error.empty()) {
95✔
254
        return this->bss_error;
×
255
    }
256
    if (!this->bss_prompt.empty()) {
95✔
257
        return this->bss_prompt;
×
258
    }
259
    if (!this->bss_line_error.empty()) {
95✔
260
        return this->bss_line_error;
×
261
    }
262
    if (field == 4) {
95✔
263
        if (this->bss_fields[BSF_LOADING].empty()) {
19✔
264
            return this->bss_fields[BSF_HELP];
19✔
265
        }
UNCOV
266
        return this->bss_fields[BSF_LOADING];
×
267
    }
268
    return this->get_field((field_t) field);
76✔
269
}
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