• 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

76.69
/src/statusview_curses.cc
1
/**
2
 * Copyright (c) 2007-2012, 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 statusview_curses.cc
30
 */
31

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

35
#include "statusview_curses.hh"
36

37
#include "base/ansi_scrubber.hh"
38
#include "base/itertools.hh"
39
#include "config.h"
40

41
void
42
status_field::no_op_action(status_field&)
×
43
{
44
}
45

46
bool
47
status_field::set_value(std::string value)
7,695✔
48
{
49
    if (value == this->sf_value.al_string) {
7,695✔
50
        return false;
1,715✔
51
    }
52

53
    this->sf_value.with_ansi_string(value);
5,980✔
54
    return true;
5,980✔
55
}
56

57
bool
58
status_field::set_value(const string_fragment& value)
4,701✔
59
{
60
    if (value == this->sf_value.al_string) {
4,701✔
61
        return false;
13✔
62
    }
63

64
    this->sf_value.with_ansi_string(value);
4,688✔
65
    return true;
4,688✔
66
}
67

68
bool
69
status_field::set_value(const char* fmt, ...)
6,009✔
70
{
71
    char buffer[256];
72
    va_list args;
73

74
    va_start(args, fmt);
6,009✔
75
    vsnprintf(buffer, sizeof(buffer), fmt, args);
6,009✔
76
    auto retval = this->set_value(std::string(buffer));
6,009✔
77
    va_end(args);
6,009✔
78

79
    return retval;
6,009✔
80
}
81

82
bool
83
status_field::set_value(const attr_line_t& value)
3✔
84
{
85
    if (value.al_string == this->sf_value.al_string) {
3✔
86
        return false;
2✔
87
    }
88

89
    this->sf_value = value;
1✔
90
    return true;
1✔
91
}
92

93
void
UNCOV
94
status_field::do_cylon()
×
95
{
UNCOV
96
    auto& sa = this->sf_value.get_attrs();
×
97

UNCOV
98
    remove_string_attr(sa, &VC_STYLE);
×
99

UNCOV
100
    auto cycle_pos = (this->sf_cylon_pos % (4 + this->sf_width * 2)) - 2;
×
UNCOV
101
    auto start = cycle_pos < this->sf_width
×
UNCOV
102
        ? cycle_pos
×
103
        : (this->sf_width - (cycle_pos - this->sf_width) - 1);
×
UNCOV
104
    auto stop = std::min(start + 3, this->sf_width);
×
UNCOV
105
    line_range lr(std::max<long>(start, 0L), stop);
×
UNCOV
106
    const auto& vc = view_colors::singleton();
×
107

UNCOV
108
    auto attrs = vc.attrs_for_role(role_t::VCR_ACTIVE_STATUS);
×
UNCOV
109
    attrs |= text_attrs::style::reverse;
×
UNCOV
110
    sa.emplace_back(lr, VC_STYLE.value(attrs));
×
111

UNCOV
112
    this->sf_cylon_pos += 1;
×
113
}
114

115
void
116
status_field::set_stitch_value(role_t left, role_t right)
7,036✔
117
{
118
    auto& sa = this->sf_value.get_attrs();
7,036✔
119
    line_range lr(0, 1);
7,036✔
120

121
    this->sf_value.get_string() = "::";
7,036✔
122
    sa.clear();
7,036✔
123
    sa.emplace_back(lr, VC_ROLE.value(left));
7,036✔
124
    lr.lr_start = 1;
7,036✔
125
    lr.lr_end = 2;
7,036✔
126
    sa.emplace_back(lr, VC_ROLE.value(right));
7,036✔
127
}
7,036✔
128

129
bool
130
statusview_curses::do_update()
81✔
131
{
132
    if (!this->vc_needs_update) {
81✔
133
        return view_curses::do_update();
60✔
134
    }
135

136
    int left = 0;
21✔
137
    auto& vc = view_colors::singleton();
21✔
138
    unsigned int width, height;
139

140
    this->sc_displayed_fields.clear();
21✔
141
    if (!this->vc_visible || this->vc_window == nullptr) {
21✔
142
        return false;
×
143
    }
144

145
    ncplane_dim_yx(this->vc_window, &height, &width);
21✔
146
    this->window_change();
21✔
147

148
    int top = this->vc_y < 0 ? height + this->vc_y : this->vc_y;
21✔
149
    int right = width;
21✔
150
    const auto attrs = vc.attrs_for_role(
21✔
151
        this->sc_enabled ? this->vc_default_role : role_t::VCR_INACTIVE_STATUS);
21✔
152

153
    nccell clear_cell;
154
    nccell_init(&clear_cell);
21✔
155
    nccell_prime(
21✔
156
        this->vc_window, &clear_cell, " ", 0, view_colors::to_channels(attrs));
157
    ncplane_cursor_move_yx(this->vc_window, top, 0);
21✔
158
    ncplane_hline(this->vc_window, &clear_cell, width);
21✔
159
    nccell_release(this->vc_window, &clear_cell);
21✔
160

161
    if (this->sc_source != nullptr) {
21✔
162
        auto field_count = this->sc_source->statusview_fields();
21✔
163
        for (size_t field = 0; field < field_count; field++) {
116✔
164
            auto& sf = this->sc_source->statusview_value_for_field(field);
95✔
165
            struct line_range lr(0, sf.get_width());
95✔
166
            int x;
167

168
            if (sf.is_cylon()) {
95✔
UNCOV
169
                sf.do_cylon();
×
170
            }
171
            auto val = sf.get_value();
95✔
172
            if (!this->sc_enabled) {
95✔
173
                for (auto& sa : val.get_attrs()) {
72✔
174
                    if (sa.sa_type == &VC_STYLE) {
40✔
175
                        auto sa_attrs = sa.sa_value.get<text_attrs>();
4✔
176
                        sa_attrs.clear_style(text_attrs::style::reverse);
4✔
177
                        sa_attrs.ta_fg_color = styling::color_unit::EMPTY;
4✔
178
                        sa_attrs.ta_bg_color = styling::color_unit::EMPTY;
4✔
179
                        sa.sa_value = sa_attrs;
4✔
180
                    } else if (sa.sa_type == &VC_ROLE) {
36✔
181
                        if (sa.sa_value.get<role_t>()
13✔
182
                            == role_t::VCR_ALERT_STATUS)
13✔
183
                        {
184
                            sa.sa_value.get<role_t>()
×
185
                                = role_t::VCR_INACTIVE_ALERT_STATUS;
×
186
                        } else {
187
                            sa.sa_value = role_t::VCR_NONE;
13✔
188
                        }
189
                    }
190
                }
191
            }
192
            if (sf.get_left_pad() > 0) {
95✔
193
                val.insert(0, sf.get_left_pad(), ' ');
14✔
194
            }
195

196
            if (sf.is_right_justified()) {
95✔
197
                val.right_justify(sf.get_width());
22✔
198

199
                right -= sf.get_width();
22✔
200
                x = right;
22✔
201
            } else {
202
                x = left;
73✔
203
                left += sf.get_width();
73✔
204
            }
205

206
            if (val.utf8_length_or_length() > sf.get_width()) {
95✔
207
                static constexpr auto ELLIPSIS = "\xE2\x8B\xAF"_frag;
208

209
                if (sf.get_width() > 11) {
1✔
210
                    size_t half_width = sf.get_width() / 2 - 1;
1✔
211

212
                    val.erase(half_width, val.length() - (half_width * 2));
1✔
213
                    val.insert(half_width, ELLIPSIS);
1✔
214
                } else {
215
                    val = val.subline(0, sf.get_width() - 1);
×
216
                    val.append(ELLIPSIS);
×
217
                }
218
            }
219

220
            auto default_role = sf.get_role();
95✔
221
            if (!this->sc_enabled) {
95✔
222
                if (default_role == role_t::VCR_ALERT_STATUS) {
32✔
223
                    default_role = role_t::VCR_INACTIVE_ALERT_STATUS;
×
224
                } else if (default_role != role_t::VCR_STATUS_INFO) {
32✔
225
                    default_role = role_t::VCR_INACTIVE_STATUS;
32✔
226
                }
227
            }
228

229
            auto write_res
230
                = mvwattrline(this->vc_window, top, x, val, lr, default_role);
95✔
231
            this->sc_displayed_fields.emplace_back(
95✔
232
                line_range{x, static_cast<int>(x + write_res.mr_chars_out)},
95✔
233
                field);
234
        }
95✔
235
    }
236
    this->vc_needs_update = false;
21✔
237

238
    return true;
21✔
239
}
240

241
void
242
statusview_curses::window_change()
30✔
243
{
244
    if (this->sc_source == nullptr) {
30✔
245
        return;
×
246
    }
247

248
    int field_count = this->sc_source->statusview_fields();
30✔
249
    int total_shares = 0;
30✔
250
    double remaining = 0;
30✔
251
    std::vector<status_field*> resizable;
30✔
252

253
    auto width = ncplane_dim_x(this->vc_window);
30✔
254
    remaining = width - 2;
30✔
255

256
    for (int field = 0; field < field_count; field++) {
159✔
257
        auto& sf = this->sc_source->statusview_value_for_field(field);
129✔
258

259
        remaining -= sf.get_share() ? sf.get_min_width() : sf.get_width();
129✔
260
        total_shares += sf.get_share();
129✔
261
        if (sf.get_share()) {
129✔
262
            resizable.emplace_back(&sf);
55✔
263
        }
264
    }
265

266
    if (remaining < 2) {
30✔
267
        remaining = 0;
6✔
268
    }
269

270
    std::stable_sort(begin(resizable), end(resizable), [](auto l, auto r) {
30✔
271
        return r->get_share() < l->get_share();
36✔
272
    });
273
    for (auto* sf : resizable) {
85✔
274
        double divisor = total_shares / sf->get_share();
55✔
275
        int available = remaining / divisor;
55✔
276
        int actual_width;
277

278
        if ((sf->get_left_pad() + sf->get_value().length())
55✔
279
            < (sf->get_min_width() + available))
55✔
280
        {
281
            actual_width = std::max(
104✔
282
                (int) sf->get_min_width(),
52✔
283
                (int) (sf->get_left_pad() + sf->get_value().length()));
104✔
284
        } else {
285
            actual_width = sf->get_min_width() + available;
3✔
286
        }
287
        remaining -= (actual_width - sf->get_min_width());
55✔
288
        total_shares -= sf->get_share();
55✔
289

290
        if (sf->get_width() != actual_width) {
55✔
291
            sf->set_width(actual_width);
14✔
292
            this->set_needs_update();
14✔
293
        }
294
    }
295
}
30✔
296

297
bool
298
statusview_curses::handle_mouse(mouse_event& me)
×
299
{
300
    auto find_res = this->sc_displayed_fields
×
301
        | lnav::itertools::find_if([&me](const auto& elem) {
×
302
                        return me.is_click_in(mouse_button_t::BUTTON_LEFT,
×
303
                                              elem.df_range.lr_start,
×
304
                                              elem.df_range.lr_end);
×
305
                    });
×
306

307
    if (find_res) {
×
308
        auto& sf = this->sc_source->statusview_value_for_field(
×
309
            find_res.value()->df_field_index);
×
310

311
        sf.on_click(sf);
×
312
    } else if (me.me_state == mouse_button_state_t::BUTTON_STATE_DRAGGED) {
×
313
        if (this->sc_source->on_drag) {
×
314
            this->sc_source->on_drag(me);
×
315
        }
316
    }
317

318
    return true;
×
319
}
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