Skip to content

Commit 063e407

Browse files
committed
[breakpoint] add :enable-breakpoint and :disable-breakpoint
1 parent 1dcbf4e commit 063e407

26 files changed

Lines changed: 412 additions & 13 deletions

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ Features:
9999
focused line in the LOG view. Once breakpoints have
100100
been added, you can press `F7`/`F8` to move to the
101101
previous/next log message that matches a breakpoint.
102+
- The `:disable-breakpoint` and `:enable-breakpoint`
103+
commands have been added to allow disabling a
104+
breakpoint without deleting it.
102105
- If the log format specifies source file/line fields
103106
and a breakpoint is set, a red bullet point will be
104107
inserted to signify the presence of a breakpoint.

src/cmds.breakpoints.cc

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,205 @@ com_toggle_breakpoint(exec_context& ec,
303303
return com_breakpoint(ec, cmdline, args);
304304
}
305305

306+
static std::string
307+
schema_id_for_point(const std::string& point,
308+
logfile_sub_source* lss,
309+
textview_curses* tc)
310+
{
311+
static const auto POINT_RE
312+
= lnav::pcre2pp::code::from_const(R"(^(?:([^:\s]+):)?([^:]+):(\d+)$)");
313+
thread_local auto md = lnav::pcre2pp::match_data::unitialized();
314+
315+
if (POINT_RE.capture_from(point).into(md).found_p()) {
316+
std::string format_name;
317+
318+
if (md[1]) {
319+
format_name = md[1]->to_string();
320+
} else if (lss != nullptr) {
321+
auto line_pair = lss->find_line_with_file(tc->get_selection());
322+
if (line_pair) {
323+
format_name = line_pair->first->get_format_name().to_string();
324+
}
325+
}
326+
327+
if (!format_name.empty()) {
328+
auto h = hasher();
329+
h.update(format_name);
330+
h.update(md[2].value());
331+
h.update(md[3].value());
332+
return h.to_string();
333+
}
334+
}
335+
336+
return {};
337+
}
338+
339+
static Result<std::string, lnav::console::user_message>
340+
com_disable_breakpoint(exec_context& ec,
341+
std::string cmdline,
342+
std::vector<std::string>& args)
343+
{
344+
static const intern_string_t SRC = intern_string::lookup("point");
345+
std::string retval;
346+
347+
auto pat = trim(remaining_args(cmdline, args));
348+
shlex lexer(pat);
349+
auto split_args_res = lexer.split(ec.create_resolver());
350+
if (split_args_res.isErr()) {
351+
auto split_err = split_args_res.unwrapErr();
352+
auto um
353+
= lnav::console::user_message::error(
354+
"unable to parse breakpoint")
355+
.with_reason(split_err.se_error.te_msg)
356+
.with_snippet(lnav::console::snippet::from(
357+
SRC, lexer.to_attr_line(split_err.se_error)))
358+
.move();
359+
360+
return Err(um);
361+
}
362+
363+
auto* tc = *lnav_data.ld_view_stack.top();
364+
auto* lss = dynamic_cast<logfile_sub_source*>(tc->get_sub_source());
365+
auto& bps = lnav_data.ld_log_source.get_breakpoints();
366+
auto split_args = split_args_res.unwrap()
367+
| lnav::itertools::map([](const auto& elem) { return elem.se_value; });
368+
369+
if (ec.ec_dry_run) {
370+
return Ok(retval);
371+
}
372+
373+
std::vector<std::string> disabled;
374+
375+
if (split_args.empty()) {
376+
if (lss == nullptr) {
377+
return ec.make_error(
378+
"A breakpoint definition must be given if the "
379+
"top view is not the LOG view");
380+
}
381+
382+
auto schema_id
383+
= get_current_line_schema_id(*lss, tc->get_selection().value());
384+
if (schema_id.empty()) {
385+
return ec.make_error(
386+
"Could not determine schema for the current line");
387+
}
388+
389+
auto it = bps.find(schema_id);
390+
if (it == bps.end()) {
391+
return ec.make_error("No breakpoint set for the current line");
392+
}
393+
394+
it->second.bp_enabled = false;
395+
disabled.emplace_back(it->second.bp_description);
396+
}
397+
398+
for (const auto& point : split_args) {
399+
auto schema_id = schema_id_for_point(point, lss, tc);
400+
if (schema_id.empty()) {
401+
return ec.make_error("Invalid breakpoint: {}", point);
402+
}
403+
404+
auto it = bps.find(schema_id);
405+
if (it == bps.end()) {
406+
return ec.make_error("No breakpoint matching: {}", point);
407+
}
408+
409+
it->second.bp_enabled = false;
410+
disabled.emplace_back(it->second.bp_description);
411+
}
412+
413+
if (!disabled.empty()) {
414+
retval = fmt::format(FMT_STRING("info: disabled breakpoints -- {}"),
415+
fmt::join(disabled, ", "));
416+
}
417+
418+
return Ok(retval);
419+
}
420+
421+
static Result<std::string, lnav::console::user_message>
422+
com_enable_breakpoint(exec_context& ec,
423+
std::string cmdline,
424+
std::vector<std::string>& args)
425+
{
426+
static const intern_string_t SRC = intern_string::lookup("point");
427+
std::string retval;
428+
429+
auto pat = trim(remaining_args(cmdline, args));
430+
shlex lexer(pat);
431+
auto split_args_res = lexer.split(ec.create_resolver());
432+
if (split_args_res.isErr()) {
433+
auto split_err = split_args_res.unwrapErr();
434+
auto um
435+
= lnav::console::user_message::error(
436+
"unable to parse breakpoint")
437+
.with_reason(split_err.se_error.te_msg)
438+
.with_snippet(lnav::console::snippet::from(
439+
SRC, lexer.to_attr_line(split_err.se_error)))
440+
.move();
441+
442+
return Err(um);
443+
}
444+
445+
auto* tc = *lnav_data.ld_view_stack.top();
446+
auto* lss = dynamic_cast<logfile_sub_source*>(tc->get_sub_source());
447+
auto& bps = lnav_data.ld_log_source.get_breakpoints();
448+
auto split_args = split_args_res.unwrap()
449+
| lnav::itertools::map([](const auto& elem) { return elem.se_value; });
450+
451+
std::vector<std::string> enabled;
452+
453+
if (split_args.empty()) {
454+
if (lss == nullptr) {
455+
return ec.make_error(
456+
"A breakpoint definition must be given if the "
457+
"top view is not the LOG view");
458+
}
459+
460+
auto schema_id
461+
= get_current_line_schema_id(*lss, tc->get_selection().value());
462+
if (schema_id.empty()) {
463+
return ec.make_error(
464+
"Could not determine schema for the current line");
465+
}
466+
467+
auto it = bps.find(schema_id);
468+
if (it != bps.end()) {
469+
if (ec.ec_dry_run) {
470+
return Ok(retval);
471+
}
472+
it->second.bp_enabled = true;
473+
enabled.emplace_back(it->second.bp_description);
474+
} else {
475+
return com_breakpoint(ec, cmdline, args);
476+
}
477+
}
478+
479+
for (const auto& point : split_args) {
480+
auto schema_id = schema_id_for_point(point, lss, tc);
481+
if (schema_id.empty()) {
482+
return ec.make_error("Invalid breakpoint: {}", point);
483+
}
484+
485+
auto it = bps.find(schema_id);
486+
if (it != bps.end()) {
487+
if (ec.ec_dry_run) {
488+
continue;
489+
}
490+
it->second.bp_enabled = true;
491+
enabled.emplace_back(it->second.bp_description);
492+
} else {
493+
return com_breakpoint(ec, cmdline, args);
494+
}
495+
}
496+
497+
if (!enabled.empty()) {
498+
retval = fmt::format(FMT_STRING("info: enabled breakpoints -- {}"),
499+
fmt::join(enabled, ", "));
500+
}
501+
502+
return Ok(retval);
503+
}
504+
306505
static readline_context::command_t BREAKPOINT_COMMANDS[] = {
307506
{
308507
"breakpoint",
@@ -346,6 +545,36 @@ static readline_context::command_t BREAKPOINT_COMMANDS[] = {
346545
.one_or_more())
347546
.with_example({"To clear all breakpoints", "*"}),
348547
},
548+
{
549+
"disable-breakpoint",
550+
com_disable_breakpoint,
551+
help_text(":disable-breakpoint")
552+
.with_summary(
553+
"Disable a breakpoint for the given "
554+
"[<format>:]<file>:<line> tuples "
555+
"or the current line")
556+
.with_parameter(
557+
help_text("point")
558+
.with_summary(
559+
"The file and line number of the breakpoint")
560+
.with_format(help_parameter_format_t::HPF_BREAKPOINT)
561+
.zero_or_more()),
562+
},
563+
{
564+
"enable-breakpoint",
565+
com_enable_breakpoint,
566+
help_text(":enable-breakpoint")
567+
.with_summary(
568+
"Enable or create a breakpoint for the given "
569+
"[<format>:]<file>:<line> tuples "
570+
"or the current line")
571+
.with_parameter(
572+
help_text("point")
573+
.with_summary(
574+
"The file and line number of the breakpoint")
575+
.with_format(help_parameter_format_t::HPF_BREAKPOINT)
576+
.zero_or_more()),
577+
},
349578
};
350579

351580
void

src/internals/cmd-ref.rst

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,20 @@
528528
----
529529

530530

531+
.. _disable_breakpoint:
532+
533+
:disable-breakpoint *point*
534+
^^^^^^^^^^^^^^^^^^^^^^^^^^^
535+
536+
Disable a breakpoint for the given [<format>:]<file>:<line> tuples or the current line
537+
538+
**Parameters**
539+
* **point** --- The file and line number of the breakpoint
540+
541+
542+
----
543+
544+
531545
.. _disable_filter:
532546

533547
:disable-filter *pattern*
@@ -588,6 +602,20 @@
588602
----
589603

590604

605+
.. _enable_breakpoint:
606+
607+
:enable-breakpoint *point*
608+
^^^^^^^^^^^^^^^^^^^^^^^^^^
609+
610+
Enable or create a breakpoint for the given [<format>:]<file>:<line> tuples or the current line
611+
612+
**Parameters**
613+
* **point** --- The file and line number of the breakpoint
614+
615+
616+
----
617+
618+
591619
.. _enable_filter:
592620

593621
:enable-filter *pattern*

src/internals/sql-ref.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3501,14 +3501,15 @@ radians(*degrees*)
35013501

35023502
.. _raise_error:
35033503

3504-
raise_error(*msg*, *\[reason\]*)
3505-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3504+
raise_error(*msg*, *\[reason\]*, *\[help\]*)
3505+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
35063506

35073507
Raises an error with the given message when executed
35083508

35093509
**Parameters**
35103510
* **msg\*** --- The error message
35113511
* **reason** --- The reason the error occurred
3512+
* **help** --- A possible resolution to the error
35123513

35133514
**Examples**
35143515
To raise an error if a variable is not set:

src/lnav.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1578,7 +1578,10 @@ VALUES ('org.lnav.mouse-support', -1, DATETIME('now', '+1 minute'),
15781578
}
15791579
}
15801580
};
1581-
auto click_handler = [](textview_curses& tc, const attr_line_t& al, int x) {
1581+
auto click_handler = [](textview_curses& tc,
1582+
const attr_line_t& al,
1583+
int x,
1584+
const mouse_event& me) {
15821585
if (tc.tc_selected_text) {
15831586
return;
15841587
}

src/scripts/lnav-moveto-breakpoint.lnav

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,22 @@
55

66
;SELECT
77
CASE
8-
WHEN count(*) = 0 THEN raise_error('Cannot move to breakpoint', 'No breakpoints have been set yet')
8+
WHEN name != 'log' THEN
9+
raise_error('Cannot move to breakpoint',
10+
'Breakpoints are only supported in the LOG view')
911
ELSE 1
1012
END
11-
FROM lnav_log_breakpoints
13+
FROM lnav_top_view
1214
UNION ALL
13-
SELECT
15+
SELECT
1416
CASE
15-
WHEN name != 'log' THEN raise_error('Cannot move to breakpoint', 'Breakpoints are only supported in the LOG view')
17+
WHEN count(*) = 0 THEN
18+
raise_error('Cannot move to breakpoint',
19+
'No breakpoints have been set yet',
20+
'Use CTRL-B or the :breakpoint command to set a breakpoint')
1621
ELSE 1
1722
END
18-
FROM lnav_top_view;
23+
FROM lnav_log_breakpoints
1924

2025
;SELECT
2126
CASE $1

src/state-extension-functions.cc

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,18 @@ sql_lnav_version()
125125
}
126126

127127
static int64_t
128-
sql_error(const char* str, std::optional<string_fragment> reason)
128+
sql_error(const char* str,
129+
std::optional<string_fragment> reason,
130+
std::optional<string_fragment> help)
129131
{
130132
auto um = lnav::console::user_message::error(str);
131133

132134
if (reason) {
133135
um.with_reason(reason->to_string());
134136
}
137+
if (help) {
138+
um.with_help(help->to_string());
139+
}
135140
throw um;
136141
}
137142

@@ -202,6 +207,9 @@ state_extension_functions(struct FuncDef** basic_funcs,
202207
.with_parameter(
203208
help_text("reason", "The reason the error occurred")
204209
.optional())
210+
.with_parameter(
211+
help_text("help", "A possible resolution to the error")
212+
.optional())
205213
.with_example({
206214
"To raise an error if a variable is not set",
207215
"SELECT ifnull($val, raise_error('please set $val', "

0 commit comments

Comments
 (0)