• 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

81.53
/src/help_text_formatter.cc
1
/**
2
 * Copyright (c) 2017, 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 <algorithm>
31
#include <regex>
32

33
#include "help_text_formatter.hh"
34

35
#include "base/ansi_scrubber.hh"
36
#include "base/attr_line.builder.hh"
37
#include "base/itertools.enumerate.hh"
38
#include "base/itertools.hh"
39
#include "base/string_util.hh"
40
#include "config.h"
41
#include "fmt/format.h"
42
#include "fmt/printf.h"
43
#include "readline_highlighters.hh"
44

45
using namespace lnav::roles::literals;
46

47
static std::vector<help_text*>
48
get_related(const help_text& ht)
991✔
49
{
50
    std::vector<help_text*> retval;
991✔
51

52
    for (const auto& tag : ht.ht_tags) {
2,152✔
53
        auto tagged = help_text::tag_map().equal_range(tag);
2,322✔
54

55
        for (auto tag_iter = tagged.first; tag_iter != tagged.second;
26,988✔
56
             ++tag_iter)
25,827✔
57
        {
58
            if (tag_iter->second == &ht) {
25,827✔
59
                continue;
1,161✔
60
            }
61

62
            help_text& related = *tag_iter->second;
24,666✔
63

64
            if (!related.ht_opposites.empty()
24,666✔
65
                && find_if(related.ht_opposites.begin(),
26,454✔
66
                           related.ht_opposites.end(),
67
                           [&ht](const char* x) {
1,059✔
68
                               return strcmp(x, ht.ht_name) == 0;
1,059✔
69
                           })
70
                    == related.ht_opposites.end())
26,454✔
71
            {
72
                continue;
794✔
73
            }
74

75
            retval.push_back(&related);
23,872✔
76
        }
77
    }
78

79
    return retval;
991✔
UNCOV
80
}
×
81

82
static void
UNCOV
83
add_enum_param(attr_line_t& out, const help_text& enum_param)
×
84
{
UNCOV
85
    out.append(" ");
×
UNCOV
86
    if (enum_param.ht_nargs == help_nargs_t::HN_OPTIONAL) {
×
UNCOV
87
        out.append("[");
×
88
    }
UNCOV
89
    out.join(
×
UNCOV
90
        enum_param.ht_enum_values, VC_ROLE.value(role_t::VCR_KEYWORD), "|");
×
UNCOV
91
    if (enum_param.ht_nargs == help_nargs_t::HN_OPTIONAL) {
×
UNCOV
92
        out.append("]");
×
93
    }
94
}
95

96
void
97
format_help_text_for_term(const help_text& ht,
829✔
98
                          size_t width,
99
                          attr_line_t& out,
100
                          help_text_content htc)
101
{
102
    static const size_t body_indent = 2;
103

104
    attr_line_builder alb(out);
829✔
105
    text_wrap_settings tws;
829✔
106
    size_t start_index = out.get_string().length();
829✔
107

108
    tws.with_width(width);
829✔
109

110
    switch (ht.ht_context) {
829✔
111
        case help_context_t::HC_COMMAND: {
311✔
112
            auto line_start = out.al_string.length();
311✔
113

114
            out.append(":").append(lnav::roles::symbol(ht.ht_name));
311✔
115
            for (const auto& param : ht.ht_parameters) {
595✔
116
                out.append(" ");
284✔
117
                if (param.ht_nargs == help_nargs_t::HN_OPTIONAL) {
284✔
118
                    out.append("[");
57✔
119
                }
120
                out.append(lnav::roles::variable(param.ht_name));
284✔
121
                if (param.ht_nargs == help_nargs_t::HN_OPTIONAL) {
284✔
122
                    out.append("]");
57✔
123
                }
124
                if (param.ht_nargs == help_nargs_t::HN_ONE_OR_MORE) {
284✔
125
                    out.append("1"_variable);
37✔
126
                    out.append(" [");
37✔
127
                    out.append("..."_variable);
37✔
128
                    out.append(" ");
37✔
129
                    out.append(lnav::roles::variable(param.ht_name));
37✔
130
                    out.append("N"_variable);
37✔
131
                    out.append("]");
37✔
132
                }
133
            }
134
            out.with_attr(string_attr{
311✔
135
                line_range{(int) line_start, (int) out.get_string().length()},
311✔
136
                VC_ROLE.value(role_t::VCR_H3),
622✔
137
            });
138
            if (htc != help_text_content::synopsis) {
311✔
139
                alb.append("\n")
311✔
140
                    .append(lnav::roles::table_border(
311✔
141
                        repeat("\u2550", tws.tws_width)))
1,244✔
142
                    .append("\n")
311✔
143
                    .indent(body_indent)
311✔
144
                    .append(attr_line_t::from_ansi_str(ht.ht_summary),
622✔
145
                            &tws.with_indent(body_indent))
311✔
146
                    .append("\n");
311✔
147
            }
148
            break;
311✔
149
        }
150
        case help_context_t::HC_SQL_FUNCTION:
484✔
151
        case help_context_t::HC_SQL_TABLE_VALUED_FUNCTION: {
152
            auto line_start = out.al_string.length();
484✔
153
            bool break_all = false;
484✔
154
            bool needs_comma = false;
484✔
155

156
            out.append(lnav::roles::symbol(ht.ht_name)).append("(");
484✔
157
            for (const auto& param : ht.ht_parameters) {
1,185✔
158
                if (!param.ht_flag_name && needs_comma) {
701✔
159
                    out.append(", ");
259✔
160
                }
161
                if (break_all
701✔
162
                    || (int) (out.get_string().length() - line_start + 10)
1,402✔
163
                        >= tws.tws_width)
701✔
164
                {
165
                    out.append("\n");
3✔
166
                    line_start = out.get_string().length();
3✔
167
                    alb.indent(body_indent + strlen(ht.ht_name) + 1);
3✔
168
                    break_all = true;
3✔
169
                }
170
                if (param.ht_flag_name) {
701✔
171
                    out.append(" ")
9✔
172
                        .append(lnav::roles::symbol(param.ht_flag_name))
18✔
173
                        .append(" ");
9✔
174
                }
175
                if (param.ht_nargs == help_nargs_t::HN_OPTIONAL) {
701✔
176
                    out.append("[");
78✔
177
                }
178
                out.append(lnav::roles::variable(param.ht_name));
701✔
179
                if (param.ht_nargs == help_nargs_t::HN_OPTIONAL) {
701✔
180
                    out.append("]");
78✔
181
                }
182
                if (param.ht_nargs == help_nargs_t::HN_ZERO_OR_MORE
701✔
183
                    || param.ht_nargs == help_nargs_t::HN_ONE_OR_MORE)
674✔
184
                {
185
                    out.append(", ...");
73✔
186
                }
187
                needs_comma = true;
701✔
188
            }
189
            out.append(")");
484✔
190
            out.with_attr(string_attr{
484✔
191
                line_range{(int) line_start, (int) out.get_string().length()},
484✔
192
                VC_ROLE.value(role_t::VCR_H3),
968✔
193
            });
194
            if (htc != help_text_content::synopsis) {
484✔
195
                if (break_all) {
477✔
196
                    alb.append("\n")
3✔
197
                        .append(lnav::roles::table_border(
6✔
198
                            repeat("\u2550", tws.tws_width)))
12✔
199
                        .append("\n")
3✔
200
                        .indent(body_indent + strlen(ht.ht_name) + 1);
3✔
201
                } else {
202
                    alb.append("\n")
474✔
203
                        .append(lnav::roles::table_border(
948✔
204
                            repeat("\u2550", tws.tws_width)))
1,896✔
205
                        .append("\n")
474✔
206
                        .indent(body_indent);
474✔
207
                }
208
                out.append(attr_line_t::from_ansi_str(ht.ht_summary),
954✔
209
                           &tws.with_indent(body_indent))
477✔
210
                    .append("\n");
477✔
211
            }
212
            break;
484✔
213
        }
214
        case help_context_t::HC_SQL_COMMAND: {
1✔
215
            auto line_start = out.al_string.length();
1✔
216

217
            out.append(";").append(lnav::roles::symbol(ht.ht_name));
1✔
218
            for (const auto& param : ht.ht_parameters) {
2✔
219
                out.append(" ");
1✔
220
                if (param.ht_nargs == help_nargs_t::HN_OPTIONAL) {
1✔
UNCOV
221
                    out.append("[");
×
222
                }
223
                out.append(lnav::roles::variable(param.ht_name));
1✔
224
                if (param.ht_nargs == help_nargs_t::HN_OPTIONAL) {
1✔
UNCOV
225
                    out.append("]");
×
226
                }
227
                if (param.ht_nargs == help_nargs_t::HN_ONE_OR_MORE) {
1✔
UNCOV
228
                    out.append("1"_variable);
×
UNCOV
229
                    out.append(" [");
×
UNCOV
230
                    out.append("..."_variable);
×
UNCOV
231
                    out.append(" ");
×
UNCOV
232
                    out.append(lnav::roles::variable(param.ht_name));
×
UNCOV
233
                    out.append("N"_variable);
×
UNCOV
234
                    out.append("]");
×
235
                }
236
            }
237
            out.with_attr(string_attr{
1✔
238
                line_range{(int) line_start, (int) out.get_string().length()},
1✔
239
                VC_ROLE.value(role_t::VCR_H3),
2✔
240
            });
241
            if (htc != help_text_content::synopsis) {
1✔
242
                alb.append("\n")
1✔
243
                    .append(lnav::roles::table_border(
1✔
244
                        repeat("\u2550", tws.tws_width)))
4✔
245
                    .append("\n")
1✔
246
                    .indent(body_indent)
1✔
247
                    .append(attr_line_t::from_ansi_str(ht.ht_summary),
2✔
248
                            &tws.with_indent(body_indent + 2))
1✔
249
                    .append("\n");
1✔
250
            }
251
            break;
1✔
252
        }
253
        case help_context_t::HC_SQL_INFIX:
33✔
254
        case help_context_t::HC_SQL_KEYWORD: {
255
            size_t line_start = out.get_string().length();
33✔
256
            bool break_all = false;
33✔
257
            auto is_infix = ht.ht_context == help_context_t::HC_SQL_INFIX;
33✔
258

259
            if (is_infix) {
33✔
UNCOV
260
                out.append(ht.ht_name);
×
261
            } else {
262
                out.append(lnav::roles::keyword(ht.ht_name));
33✔
263
            }
264
            if (ht.ht_group_start) {
33✔
UNCOV
265
                out.ensure_space().append(ht.ht_group_start);
×
266
            }
267
            for (const auto& param : ht.ht_parameters) {
150✔
268
                if (break_all
117✔
269
                    || (int) (out.get_string().length() - start_index
234✔
270
                              - line_start + 10)
117✔
271
                        >= tws.tws_width)
117✔
272
                {
UNCOV
273
                    out.append("\n");
×
UNCOV
274
                    line_start = out.get_string().length();
×
UNCOV
275
                    alb.indent(body_indent + strlen(ht.ht_name) + 1);
×
UNCOV
276
                    break_all = true;
×
277
                }
278
                if (param.ht_nargs == help_nargs_t::HN_ZERO_OR_MORE
117✔
279
                    || param.ht_nargs == help_nargs_t::HN_OPTIONAL)
102✔
280
                {
281
                    if (!break_all) {
54✔
282
                        out.append(" ");
54✔
283
                    }
284
                    out.append("[");
54✔
285
                }
286
                if (param.ht_flag_name) {
117✔
287
                    out.ensure_space().append(
132✔
288
                        lnav::roles::keyword(param.ht_flag_name));
132✔
289
                }
290
                if (param.ht_group_start) {
117✔
291
                    out.ensure_space().append(
12✔
292
                        lnav::roles::keyword(param.ht_group_start));
12✔
293
                }
294
                if (!param.ht_enum_values.empty()) {
117✔
295
                    out.join(param.ht_enum_values,
3✔
296
                             VC_ROLE.value(role_t::VCR_KEYWORD),
6✔
297
                             "|");
298
                } else if (param.ht_name[0]) {
114✔
299
                    for (const auto& sub_param : param.ht_parameters) {
102✔
300
                        if (sub_param.is_flag()) {
6✔
UNCOV
301
                            out.ensure_space()
×
UNCOV
302
                                .append("[")
×
UNCOV
303
                                .append(sub_param.ht_name)
×
UNCOV
304
                                .append("]");
×
305
                        }
306
                    }
307
                    out.ensure_space().append(
192✔
308
                        lnav::roles::variable(param.ht_name));
192✔
309
                    if (param.ht_parameters.size() == 1) {
96✔
310
                        if (param.ht_nargs == help_nargs_t::HN_ZERO_OR_MORE
6✔
311
                            || param.ht_nargs == help_nargs_t::HN_ONE_OR_MORE)
6✔
312
                        {
313
                            out.append("1"_variable);
6✔
314
                        }
315
                        if (param.ht_parameters[0].is_enum()) {
6✔
UNCOV
316
                            add_enum_param(out, param.ht_parameters[0]);
×
317
                        } else {
318
                            if (param.ht_parameters[0].ht_flag_name) {
6✔
319
                                out.append(" ")
6✔
320
                                    .append(lnav::roles::keyword(
12✔
321
                                        param.ht_parameters[0].ht_flag_name))
6✔
322
                                    .append(" ");
6✔
323
                            }
324
                            out.append(lnav::roles::variable(
6✔
325
                                param.ht_parameters[0].ht_name));
6✔
326
                            out.append("1"_variable);
6✔
327
                        }
328
                    }
329
                }
330
                if (param.ht_nargs == help_nargs_t::HN_ZERO_OR_MORE
117✔
331
                    || param.ht_nargs == help_nargs_t::HN_ONE_OR_MORE)
102✔
332
                {
333
                    auto needs_comma
334
                        = (param.ht_parameters
27✔
335
                           | lnav::itertools::filter_out(&help_text::is_enum))
54✔
336
                              .empty()
27✔
337
                        || !param.ht_flag_name;
54✔
338

339
                    if (param.ht_parameters.empty()) {
27✔
340
                        out.append("1"_variable);
21✔
341
                    }
342

343
                    out.append(" [")
27✔
344
                        .append(needs_comma ? ", " : "")
27✔
345
                        .append("...")
27✔
346
                        .append(needs_comma ? "" : " ")
27✔
347
                        .append(lnav::roles::keyword(
27✔
348
                            (needs_comma || !param.ht_flag_name)
27✔
349
                                ? ""
350
                                : param.ht_flag_name))
351
                        .append(" ")
27✔
352
                        .append(lnav::roles::variable(param.ht_name))
54✔
353
                        .append("N"_variable);
27✔
354
                    if (!param.ht_parameters.empty()) {
27✔
355
                        if (param.ht_parameters[0].is_enum()) {
6✔
356
                            add_enum_param(out, param.ht_parameters[0]);
×
357
                        } else if (param.ht_parameters[0].is_flag()) {
6✔
358
                            out.append(" ");
×
359
                        } else {
360
                            if (param.ht_parameters[0].ht_flag_name) {
6✔
361
                                out.append(" ")
6✔
362
                                    .append(lnav::roles::keyword(
12✔
363
                                        param.ht_parameters[0].ht_flag_name))
6✔
364
                                    .append(" ");
6✔
365
                            }
366

367
                            out.append(lnav::roles::variable(
12✔
368
                                           param.ht_parameters[0].ht_name))
6✔
369
                                .append("N"_variable);
6✔
370
                        }
371
                    }
372
                    out.append("]");
27✔
373
                }
374
                if (param.ht_group_end) {
117✔
375
                    out.ensure_space().append(
12✔
376
                        lnav::roles::keyword(param.ht_group_end));
12✔
377
                }
378
                if (param.ht_nargs == help_nargs_t::HN_ZERO_OR_MORE
117✔
379
                    || param.ht_nargs == help_nargs_t::HN_OPTIONAL)
102✔
380
                {
381
                    out.append("]");
54✔
382
                }
383
            }
384
            if (ht.ht_group_end) {
33✔
385
                out.ensure_space().append(ht.ht_group_end);
×
386
            }
387
            out.with_attr(string_attr{
33✔
388
                line_range{(int) line_start, (int) out.get_string().length()},
33✔
389
                VC_ROLE.value(role_t::VCR_H3),
66✔
390
            });
391
            if (htc != help_text_content::synopsis) {
33✔
392
                alb.append("\n")
33✔
393
                    .append(lnav::roles::table_border(
66✔
394
                        repeat("\u2550", tws.tws_width)))
132✔
395
                    .append("\n")
33✔
396
                    .indent(body_indent)
33✔
397
                    .append(ht.ht_summary, &tws)
33✔
398
                    .append("\n");
33✔
399
            }
400
            break;
33✔
401
        }
UNCOV
402
        case help_context_t::HC_PRQL_TRANSFORM: {
×
403
            auto line_start = out.al_string.length();
×
404

405
            out.append(";").append(lnav::roles::symbol(ht.ht_name));
×
406
            for (const auto& param : ht.ht_parameters) {
×
407
                out.append(" ");
×
408
                if (param.ht_nargs == help_nargs_t::HN_OPTIONAL) {
×
409
                    out.append(lnav::roles::symbol(param.ht_name));
×
410
                    out.append(":");
×
411
                    if (param.ht_default_value) {
×
412
                        out.append(param.ht_default_value);
×
413
                    } else {
414
                        out.append("null");
×
415
                    }
416
                } else {
417
                    if (param.ht_group_start) {
×
418
                        out.append(param.ht_group_start);
×
419
                    }
UNCOV
420
                    out.append(lnav::roles::variable(param.ht_name));
×
421
                }
422
                if (param.ht_nargs == help_nargs_t::HN_ONE_OR_MORE) {
×
423
                    out.append("1"_variable);
×
424
                    out.append(" [");
×
425
                    out.append("..."_variable);
×
426
                    out.append(" ");
×
427
                    out.append(lnav::roles::variable(param.ht_name));
×
428
                    out.append("N"_variable);
×
429
                    out.append("]");
×
430
                }
431
                if (param.ht_group_end) {
×
UNCOV
432
                    out.append(param.ht_group_end);
×
433
                }
434
            }
UNCOV
435
            out.with_attr(string_attr{
×
UNCOV
436
                line_range{(int) line_start, (int) out.get_string().length()},
×
UNCOV
437
                VC_ROLE.value(role_t::VCR_H3),
×
438
            });
UNCOV
439
            if (htc != help_text_content::synopsis) {
×
UNCOV
440
                alb.append("\n")
×
UNCOV
441
                    .append(lnav::roles::table_border(
×
UNCOV
442
                        repeat("\u2550", tws.tws_width)))
×
UNCOV
443
                    .append("\n")
×
UNCOV
444
                    .indent(body_indent)
×
UNCOV
445
                    .append(attr_line_t::from_ansi_str(ht.ht_summary),
×
UNCOV
446
                            &tws.with_indent(body_indent + 2))
×
UNCOV
447
                    .append("\n");
×
448
            }
UNCOV
449
            break;
×
450
        }
UNCOV
451
        case help_context_t::HC_PRQL_FUNCTION: {
×
UNCOV
452
            auto line_start = out.al_string.length();
×
453

UNCOV
454
            out.append(lnav::roles::symbol(ht.ht_name));
×
UNCOV
455
            for (const auto& param : ht.ht_parameters) {
×
UNCOV
456
                out.append(" ");
×
UNCOV
457
                out.append(lnav::roles::variable(param.ht_name));
×
UNCOV
458
                if (param.ht_nargs == help_nargs_t::HN_ONE_OR_MORE) {
×
UNCOV
459
                    out.append("1"_variable);
×
UNCOV
460
                    out.append(" [");
×
UNCOV
461
                    out.append("..."_variable);
×
UNCOV
462
                    out.append(" ");
×
UNCOV
463
                    out.append(lnav::roles::variable(param.ht_name));
×
UNCOV
464
                    out.append("N"_variable);
×
UNCOV
465
                    out.append("]");
×
466
                }
467
            }
UNCOV
468
            out.with_attr(string_attr{
×
UNCOV
469
                line_range{(int) line_start, (int) out.get_string().length()},
×
UNCOV
470
                VC_ROLE.value(role_t::VCR_H3),
×
471
            });
UNCOV
472
            if (htc != help_text_content::synopsis) {
×
UNCOV
473
                alb.append("\n")
×
UNCOV
474
                    .append(lnav::roles::table_border(
×
UNCOV
475
                        repeat("\u2550", tws.tws_width)))
×
UNCOV
476
                    .append("\n")
×
UNCOV
477
                    .indent(body_indent)
×
UNCOV
478
                    .append(attr_line_t::from_ansi_str(ht.ht_summary),
×
UNCOV
479
                            &tws.with_indent(body_indent + 2))
×
UNCOV
480
                    .append("\n");
×
481
            }
UNCOV
482
            break;
×
483
        }
UNCOV
484
        default:
×
UNCOV
485
            break;
×
486
    }
487

488
    if (htc == help_text_content::full && !ht.ht_parameters.empty()) {
829✔
489
        size_t max_param_name_width = 0;
657✔
490

491
        for (const auto& param : ht.ht_parameters) {
1,710✔
492
            max_param_name_width
493
                = std::max(strlen(param.ht_name), max_param_name_width);
1,053✔
494
        }
495

496
        out.append(ht.ht_parameters.size() == 1 ? "Parameter"_h4
936✔
497
                                                : "Parameters"_h4)
279✔
498
            .append("\n");
657✔
499

500
        for (const auto& param : ht.ht_parameters) {
1,710✔
501
            if (!param.ht_summary) {
1,053✔
502
                continue;
42✔
503
            }
504

505
            alb.indent(body_indent)
1,011✔
506
                .append(lnav::roles::variable(param.ht_name))
1,011✔
507
                .append(max_param_name_width - strlen(param.ht_name), ' ')
1,011✔
508
                .append("   ")
1,011✔
509
                .append(attr_line_t::from_ansi_str(param.ht_summary),
2,022✔
510
                        &(tws.with_indent(2 + max_param_name_width + 3)))
1,011✔
511
                .append("\n");
1,011✔
512
            if (!param.ht_enum_values.empty()) {
1,011✔
513
                alb.indent(body_indent + max_param_name_width)
30✔
514
                    .append("   ")
30✔
515
                    .append("Values"_h5)
30✔
516
                    .append(": ");
30✔
517
                auto initial = true;
30✔
518
                for (const auto& ename : param.ht_enum_values) {
240✔
519
                    if (!initial) {
210✔
520
                        alb.append("|");
180✔
521
                    }
522
                    alb.append(lnav::roles::symbol(ename));
210✔
523
                    initial = false;
210✔
524
                }
525
                alb.append("\n");
30✔
526
            }
527
            if (!param.ht_parameters.empty()) {
1,011✔
528
                for (const auto& sub_param : param.ht_parameters) {
18✔
529
                    alb.indent(body_indent + max_param_name_width + 3)
9✔
530
                        .append(lnav::roles::variable(sub_param.ht_name))
18✔
531
                        .append(" - ")
9✔
532
                        .append(
9✔
533
                            attr_line_t::from_ansi_str(sub_param.ht_summary),
18✔
534
                            &(tws.with_indent(2 + max_param_name_width + 5)))
9✔
535
                        .append("\n");
9✔
536
                }
537
            }
538
        }
539
    }
540
    if (htc == help_text_content::full && !ht.ht_results.empty()) {
829✔
541
        size_t max_result_name_width = 0;
24✔
542

543
        for (const auto& result : ht.ht_results) {
186✔
544
            max_result_name_width
545
                = std::max(strlen(result.ht_name), max_result_name_width);
162✔
546
        }
547

548
        out.append(ht.ht_results.size() == 1 ? "Result"_h4 : "Results"_h4)
24✔
549
            .append("\n");
24✔
550

551
        for (const auto& result : ht.ht_results) {
186✔
552
            if (!result.ht_summary) {
162✔
UNCOV
553
                continue;
×
554
            }
555

556
            alb.indent(body_indent)
162✔
557
                .append(lnav::roles::variable(result.ht_name))
324✔
558
                .append(max_result_name_width - strlen(result.ht_name), ' ')
162✔
559
                .append("   ")
162✔
560
                .append(attr_line_t::from_ansi_str(result.ht_summary),
324✔
561
                        &(tws.with_indent(2 + max_result_name_width + 3)))
162✔
562
                .append("\n");
162✔
563
        }
564
    }
565
    if (htc == help_text_content::full && !ht.ht_tags.empty()) {
829✔
566
        auto related_help = get_related(ht);
573✔
567
        auto related_refs = std::vector<std::string>();
573✔
568

569
        for (const auto* related : related_help) {
14,565✔
570
            std::string name = related->ht_name;
13,992✔
571
            switch (related->ht_context) {
13,992✔
572
                case help_context_t::HC_COMMAND:
2,469✔
573
                    name = ":" + name;
2,469✔
574
                    break;
2,469✔
575
                case help_context_t::HC_SQL_FUNCTION:
11,415✔
576
                case help_context_t::HC_SQL_TABLE_VALUED_FUNCTION:
577
                    name = name + "()";
11,415✔
578
                    break;
11,415✔
579
                default:
108✔
580
                    break;
108✔
581
            }
582
            related_refs.push_back(name);
13,992✔
583
        }
13,992✔
584
        stable_sort(related_refs.begin(), related_refs.end());
573✔
585

586
        alb.append("See Also"_h4).append("\n").indent(body_indent);
573✔
587

588
        bool first = true;
573✔
589
        size_t line_start = out.get_string().length();
573✔
590
        for (const auto& ref : related_refs) {
14,565✔
591
            if (!first) {
13,992✔
592
                out.append(", ");
13,422✔
593
            }
594
            if ((out.get_string().length() - line_start + ref.length()) > width)
13,992✔
595
            {
596
                alb.append("\n").indent(body_indent);
2,307✔
597
                line_start = out.get_string().length();
2,307✔
598
            }
599
            out.append(lnav::roles::symbol(ref));
13,992✔
600
            first = false;
13,992✔
601
        }
602
    }
573✔
603
}
829✔
604

605
void
606
format_example_text_for_term(const help_text& ht,
549✔
607
                             const help_example_to_attr_line_fun_t eval,
608
                             size_t width,
609
                             attr_line_t& out,
610
                             help_example::language lang)
611
{
612
    if (ht.ht_example.empty()) {
549✔
UNCOV
613
        return;
×
614
    }
615

616
    attr_line_builder alb(out);
549✔
617
    int count = 1;
549✔
618

619
    out.append(ht.ht_example.size() == 1 ? "Example"_h4 : "Examples"_h4)
549✔
620
        .append("\n");
549✔
621
    for (const auto& ex : ht.ht_example) {
1,422✔
622
        if (ex.he_language != lang) {
873✔
623
            continue;
3✔
624
        }
625

626
        attr_line_t ex_line(ex.he_cmd);
870✔
627
        const char* prompt = "";
870✔
628
        text_wrap_settings tws;
870✔
629

630
        tws.with_width(width);
870✔
631
        if (count > 1) {
870✔
632
            out.append("\n");
321✔
633
        }
634
        switch (ht.ht_context) {
870✔
635
            case help_context_t::HC_COMMAND:
201✔
636
                ex_line.insert(0, 1, ' ');
201✔
637
                ex_line.insert(0, 1, ':');
201✔
638
                ex_line.insert(1, ht.ht_name);
201✔
639
                readline_command_highlighter(ex_line, 0);
201✔
640
                break;
201✔
641
            case help_context_t::HC_SQL_INFIX:
669✔
642
            case help_context_t::HC_SQL_KEYWORD:
643
            case help_context_t::HC_SQL_FUNCTION:
644
            case help_context_t::HC_SQL_TABLE_VALUED_FUNCTION:
645
            case help_context_t::HC_PRQL_TRANSFORM:
646
            case help_context_t::HC_PRQL_FUNCTION:
647
                readline_sql_highlighter(
669✔
648
                    ex_line, lnav::sql::dialect::sqlite, std::nullopt);
649
                prompt = ";";
669✔
650
                break;
669✔
UNCOV
651
            default:
×
UNCOV
652
                break;
×
653
        }
654

655
        ex_line.pad_to(50).with_attr_for_all(
1,740✔
656
            VC_ROLE.value(role_t::VCR_QUOTED_CODE));
1,740✔
657
        const auto& ex_result = eval(ht, ex);
870✔
658
        alb.append("#")
870✔
659
            .append(fmt::to_string(count))
870✔
660
            .append(" ")
870✔
661
            .append(ex.he_description, &tws.with_indent(3))
870✔
662
            .append(":\n")
870✔
663
            .indent(3)
870✔
664
            .append(prompt, VC_ROLE.value(role_t::VCR_QUOTED_CODE))
1,740✔
665
            .append(ex_line, &tws.with_indent(3).with_padding_indent(3))
1,740✔
666
            .append("\n")
870✔
667
            .indent(3)
870✔
668
            .append(ex_result, &tws.with_indent(0))
1,740✔
669
            .append("\n");
870✔
670

671
        count += 1;
870✔
672
    }
870✔
673
}
674

675
static std::string
676
link_name(const help_text& ht)
10,474✔
677
{
678
    const static std::regex SCRUBBER("[^\\w_]");
10,474✔
679

680
    bool is_sql_infix = ht.ht_context == help_context_t::HC_SQL_INFIX;
10,474✔
681
    std::string scrubbed_name;
10,474✔
682

683
    if (is_sql_infix) {
10,474✔
684
        scrubbed_name = "infix";
22✔
685
    } else {
686
        if (ht.ht_context == help_context_t::HC_PRQL_TRANSFORM) {
10,452✔
687
            scrubbed_name += "prql_";
320✔
688
        }
689
        scrubbed_name += ht.ht_name;
10,452✔
690
        if (scrubbed_name[0] == '.') {
10,452✔
691
            scrubbed_name.erase(scrubbed_name.begin());
84✔
692
            scrubbed_name.insert(0, "dot_");
84✔
693
        }
694
    }
695
    if (ht.ht_function_type == help_function_type_t::HFT_AGGREGATE) {
10,474✔
696
        scrubbed_name += "_agg";
102✔
697
    }
698
    for (const auto& param : ht.ht_parameters) {
26,488✔
699
        if (!is_sql_infix && param.ht_name[0]) {
16,014✔
700
            continue;
15,960✔
701
        }
702
        if (!param.ht_flag_name) {
54✔
703
            continue;
14✔
704
        }
705

706
        scrubbed_name += "_";
40✔
707
        scrubbed_name += param.ht_flag_name;
40✔
708
    }
709
    scrubbed_name = std::regex_replace(scrubbed_name, SCRUBBER, "_");
10,474✔
710

711
    return tolower(scrubbed_name);
20,948✔
712
}
10,474✔
713

714
void
715
format_help_text_for_rst(const help_text& ht,
594✔
716
                         const help_example_to_attr_line_fun_t eval,
717
                         FILE* rst_file)
718
{
719
    const char* prefix;
720
    int out_count = 0;
594✔
721

722
    if (!ht.ht_name || !ht.ht_name[0]) {
594✔
UNCOV
723
        return;
×
724
    }
725

726
    bool is_sql_func = false, is_sql = false, is_prql = false;
594✔
727
    switch (ht.ht_context) {
594✔
728
        case help_context_t::HC_COMMAND:
184✔
729
            prefix = ":";
184✔
730
            break;
184✔
731
        case help_context_t::HC_SQL_COMMAND:
8✔
732
            prefix = ";";
8✔
733
            break;
8✔
734
        case help_context_t::HC_SQL_FUNCTION:
318✔
735
        case help_context_t::HC_SQL_TABLE_VALUED_FUNCTION:
736
            is_sql = is_sql_func = true;
318✔
737
            prefix = "";
318✔
738
            break;
318✔
739
        case help_context_t::HC_SQL_INFIX:
52✔
740
        case help_context_t::HC_SQL_KEYWORD:
741
            is_sql = true;
52✔
742
            prefix = "";
52✔
743
            break;
52✔
744
        case help_context_t::HC_PRQL_TRANSFORM:
32✔
745
        case help_context_t::HC_PRQL_FUNCTION:
746
            is_sql = true;
32✔
747
            is_prql = true;
32✔
748
            prefix = "";
32✔
749
            break;
32✔
UNCOV
750
        default:
×
UNCOV
751
            prefix = "";
×
UNCOV
752
            break;
×
753
    }
754

755
    fmt::print(rst_file, FMT_STRING("\n.. _{}:\n\n"), link_name(ht));
2,376✔
756
    out_count += fmt::fprintf(rst_file, "%s%s", prefix, ht.ht_name);
594✔
757
    if (is_sql_func) {
594✔
758
        out_count += fmt::fprintf(rst_file, "(");
318✔
759
    }
760
    bool needs_comma = false;
594✔
761
    for (const auto& param : ht.ht_parameters) {
1,418✔
762
        if (needs_comma) {
824✔
763
            if (param.ht_flag_name) {
174✔
764
                out_count += fmt::fprintf(rst_file, " ");
6✔
765
            } else {
766
                out_count += fmt::fprintf(rst_file, ", ");
168✔
767
            }
768
        }
769
        if (!is_sql_func) {
824✔
770
            out_count += fmt::fprintf(rst_file, " ");
366✔
771
        }
772

773
        if (param.ht_flag_name) {
824✔
774
            out_count += fmt::fprintf(rst_file, "%s ", param.ht_flag_name);
80✔
775
        }
776
        if (param.ht_name[0]) {
824✔
777
            out_count += fmt::fprintf(rst_file, "*");
804✔
778
            if (param.ht_nargs == help_nargs_t::HN_OPTIONAL) {
804✔
779
                out_count += fmt::fprintf(rst_file, "\\[");
152✔
780
            }
781
            out_count += fmt::fprintf(rst_file, "%s", param.ht_name);
804✔
782
            if (is_prql && param.ht_default_value) {
804✔
783
                out_count += fmt::fprintf(rst_file, ":");
6✔
784
                out_count
785
                    += fmt::fprintf(rst_file, "%s", param.ht_default_value);
6✔
786
            }
787
            if (param.ht_nargs == help_nargs_t::HN_OPTIONAL) {
804✔
788
                out_count += fmt::fprintf(rst_file, "\\]");
152✔
789
            }
790
            out_count += fmt::fprintf(rst_file, "*");
804✔
791
        }
792
        if (is_sql_func) {
824✔
793
            needs_comma = true;
458✔
794
        }
795
    }
796
    if (is_sql_func) {
594✔
797
        out_count += fmt::fprintf(rst_file, ")");
318✔
798
    }
799
    fmt::fprintf(rst_file, "\n");
594✔
800
    fmt::print(rst_file, FMT_STRING("{0:^^{1}}\n\n"), "", out_count);
1,782✔
801

802
    fmt::fprintf(rst_file, "  %s\n", ht.ht_summary);
594✔
803
    fmt::fprintf(rst_file, "\n");
594✔
804

805
    if (!ht.ht_prql_path.empty()) {
594✔
UNCOV
806
        fmt::print(rst_file,
×
807
                   FMT_STRING("  **PRQL Name**: {}\n\n"),
198✔
808
                   fmt::join(ht.ht_prql_path, "."));
132✔
809
    }
810

811
    if (ht.ht_description != nullptr) {
594✔
UNCOV
812
        fmt::fprintf(rst_file, "  %s\n", ht.ht_description);
×
813
    }
814

815
    int param_count = 0;
594✔
816
    for (const auto& param : ht.ht_parameters) {
1,418✔
817
        if (param.ht_summary && param.ht_summary[0]) {
824✔
818
            param_count += 1;
740✔
819
        }
820
    }
821

822
    if (param_count > 0) {
594✔
823
        fmt::fprintf(rst_file, "  **Parameters**\n");
484✔
824
        for (const auto& param : ht.ht_parameters) {
1,248✔
825
            if (param.ht_summary && param.ht_summary[0]) {
764✔
826
                fmt::fprintf(
740✔
827
                    rst_file,
828
                    "    * **%s%s** --- %s\n",
829
                    param.ht_name,
740✔
830
                    param.ht_nargs == help_nargs_t::HN_REQUIRED ? "\\*" : "",
740✔
831
                    param.ht_summary);
740✔
832

833
                if (!param.ht_parameters.empty()) {
740✔
834
                    fprintf(rst_file, "\n");
6✔
835
                    for (const auto& sub_param : param.ht_parameters) {
12✔
836
                        fmt::fprintf(
6✔
837
                            rst_file,
838
                            "      * **%s%s** --- %s\n",
839
                            sub_param.ht_name,
6✔
840
                            sub_param.ht_nargs == help_nargs_t::HN_REQUIRED
6✔
841
                                ? "\\*"
6✔
842
                                : "",
843
                            sub_param.ht_summary);
6✔
844
                    }
845
                }
846
            }
847
        }
848
        fmt::fprintf(rst_file, "\n");
484✔
849
    }
850
    if (is_sql) {
594✔
851
        prefix = ";";
402✔
852
    }
853
    if (!ht.ht_example.empty()) {
594✔
854
        fmt::fprintf(rst_file, "  **Examples**\n");
408✔
855
        for (const auto& example : ht.ht_example) {
1,040✔
856
            fmt::fprintf(rst_file, "    %s:\n\n", example.he_description);
632✔
857
            fmt::fprintf(rst_file,
632✔
858
                         "    .. code-block::  %s\n\n",
859
                         is_sql ? "custsqlite" : "lnav");
632✔
860
            if (ht.ht_context == help_context_t::HC_COMMAND) {
632✔
861
                fmt::fprintf(rst_file,
134✔
862
                             "      %s%s %s\n",
863
                             prefix,
864
                             ht.ht_name,
134✔
865
                             example.he_cmd);
134✔
866
            } else {
867
                fmt::fprintf(rst_file, "      %s%s\n", prefix, example.he_cmd);
498✔
868
            }
869
            auto result = eval(ht, example);
632✔
870
            if (!result.empty()) {
632✔
871
                std::vector<attr_line_t> lines;
490✔
872

873
                result.split_lines(lines);
490✔
874
                for (const auto& line : lines) {
1,152✔
875
                    fmt::fprintf(rst_file, "      %s\n", line.get_string());
662✔
876
                }
877
            }
490✔
878
            fmt::fprintf(rst_file, "\n");
632✔
879
        }
632✔
880
    }
881

882
    if (!ht.ht_tags.empty()) {
594✔
883
        auto related_refs = std::vector<std::string>();
418✔
884

885
        for (const auto* related : get_related(ht)) {
10,298✔
886
            related_refs.emplace_back(
9,880✔
887
                fmt::format(FMT_STRING(":ref:`{}`"), link_name(*related)));
39,520✔
888
        }
418✔
889
        stable_sort(related_refs.begin(), related_refs.end());
418✔
890

UNCOV
891
        fmt::print(rst_file,
×
892
                   FMT_STRING("  **See Also**\n    {}\n"),
1,254✔
893
                   fmt::join(related_refs, ", "));
418✔
894
    }
418✔
895

896
    fmt::fprintf(rst_file, "\n----\n\n");
594✔
897
}
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