Skip to content

Commit 79d4752

Browse files
committed
[cmds] add sticky headers
Related to #1385
1 parent e9a8b12 commit 79d4752

27 files changed

Lines changed: 473 additions & 76 deletions

NEWS.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ Features:
5454
definitions. Fields with this kind will be parsed
5555
and displayed in the local timezone, and SQL
5656
queries will return them in a normalized format.
57+
* Added the `:toggle-sticky-header` command to pin a
58+
line to the top of the view as a sticky header.
59+
Sticky headers remain visible as you scroll past
60+
them, making it easy to keep context lines in view.
61+
The `:clear-all-sticky-headers` command removes all
62+
sticky headers in the current view. A hidden
63+
`log_sticky_mark` column is available in log tables
64+
to get/set the sticky state via SQL.
5765
* Introducing "Log-Oriented Debugging", a collection of
5866
features to streamline mapping log messages back to
5967
the source code that generated them. For example,

src/cmds.bookmarks.cc

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,65 @@ com_mark(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
5353
return Ok(retval);
5454
}
5555

56+
static Result<std::string, lnav::console::user_message>
57+
com_sticky_header(exec_context& ec,
58+
std::string cmdline,
59+
std::vector<std::string>& args)
60+
{
61+
std::string retval;
62+
63+
if (lnav_data.ld_view_stack.empty()) {
64+
} else if (!ec.ec_dry_run) {
65+
auto* tc = *lnav_data.ld_view_stack.top();
66+
auto sel = tc->get_selection();
67+
if (sel) {
68+
auto result = tc->toggle_user_mark(
69+
&textview_curses::BM_STICKY, sel.value());
70+
tc->set_needs_update();
71+
72+
if (result.mtr_marked > 0) {
73+
retval = fmt::format(
74+
FMT_STRING("info: line {} pinned as a sticky header"),
75+
(int) sel.value());
76+
} else {
77+
retval = fmt::format(
78+
FMT_STRING("info: line {} unpinned from sticky headers"),
79+
(int) sel.value());
80+
}
81+
}
82+
}
83+
84+
return Ok(retval);
85+
}
86+
87+
static Result<std::string, lnav::console::user_message>
88+
com_clear_sticky_headers(exec_context& ec,
89+
std::string cmdline,
90+
std::vector<std::string>& args)
91+
{
92+
std::string retval;
93+
94+
if (lnav_data.ld_view_stack.empty()) {
95+
} else if (!ec.ec_dry_run) {
96+
auto* tc = *lnav_data.ld_view_stack.top();
97+
auto& bv = tc->get_bookmarks()[&textview_curses::BM_STICKY];
98+
auto count = bv.size();
99+
// Copy to avoid modifying while iterating
100+
std::vector<vis_line_t> sticky_lines;
101+
for (const auto& row : bv.bv_tree) {
102+
sticky_lines.push_back(row);
103+
}
104+
for (const auto& row : sticky_lines) {
105+
tc->toggle_user_mark(&textview_curses::BM_STICKY, row);
106+
}
107+
tc->set_needs_update();
108+
retval = fmt::format(
109+
FMT_STRING("info: cleared {} sticky header(s)"), count);
110+
}
111+
112+
return Ok(retval);
113+
}
114+
56115
static Result<std::string, lnav::console::user_message>
57116
com_goto_mark(exec_context& ec,
58117
std::string cmdline,
@@ -65,6 +124,7 @@ com_goto_mark(exec_context& ec,
65124
&textview_curses::BM_USER_EXPR,
66125
&textview_curses::BM_META,
67126
&textview_curses::BM_PARTITION,
127+
&textview_curses::BM_STICKY,
68128
};
69129

70130
auto* tc = get_textview_for_mode(lnav_data.ld_mode);
@@ -204,6 +264,26 @@ init_lnav_bookmark_commands(readline_context::command_map_t& cmd_map)
204264
.with_example({"To go to the previous error", "error"})
205265
.with_tags({"bookmarks", "navigation"}),
206266
},
267+
268+
{
269+
"toggle-sticky-header",
270+
com_sticky_header,
271+
272+
help_text(":toggle-sticky-header")
273+
.with_summary(
274+
"Toggle the sticky header state for the focused line "
275+
"in the current view")
276+
.with_tags({"bookmarks", "display"}),
277+
},
278+
{
279+
"clear-all-sticky-headers",
280+
com_clear_sticky_headers,
281+
282+
help_text(":clear-all-sticky-headers")
283+
.with_summary(
284+
"Clear all sticky header bookmarks in the current view")
285+
.with_tags({"bookmarks", "display"}),
286+
},
207287
};
208288

209289
for (auto& cmd : BOOKMARK_COMMANDS) {

src/field_overlay_source.cc

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,36 @@ field_overlay_source::list_static_overlay(const listview_curses& lv,
938938
{
939939
auto& exec_phase = injector::get<lnav::exec_phase&>();
940940
if (media != media_t::display) {
941+
auto& tc = dynamic_cast<textview_curses&>(
942+
const_cast<listview_curses&>(lv));
943+
const auto& sticky_bv
944+
= tc.get_bookmarks()[&textview_curses::BM_STICKY];
945+
if (!sticky_bv.empty()) {
946+
auto top = lv.get_top();
947+
int sticky_index = 0;
948+
for (auto iter = sticky_bv.bv_tree.begin();
949+
iter != sticky_bv.bv_tree.end();
950+
++iter)
951+
{
952+
if (*iter >= top) {
953+
break;
954+
}
955+
if (y == sticky_index) {
956+
tc.textview_value_for_row(*iter, value_out);
957+
value_out.with_attr_for_all(
958+
VC_ROLE.value(role_t::VCR_STATUS));
959+
auto next_iter = std::next(iter);
960+
if (next_iter == sticky_bv.bv_tree.end()
961+
|| *next_iter >= top)
962+
{
963+
value_out.with_attr_for_all(
964+
VC_STYLE.value(text_attrs::with_underline()));
965+
}
966+
return true;
967+
}
968+
sticky_index++;
969+
}
970+
}
941971
return false;
942972
}
943973

@@ -1043,7 +1073,12 @@ field_overlay_source::list_static_overlay(const listview_curses& lv,
10431073
auto top = lv.get_top();
10441074
if (top < vis_line_t(this->fos_lss.text_line_count())) {
10451075
auto cl = this->fos_lss.at(top);
1046-
if (!this->fos_header_line || this->fos_header_line.value() != cl
1076+
auto& tc = dynamic_cast<textview_curses&>(
1077+
const_cast<listview_curses&>(lv));
1078+
auto has_sticky
1079+
= !tc.get_bookmarks()[&textview_curses::BM_STICKY].empty();
1080+
if (has_sticky || !this->fos_header_line
1081+
|| this->fos_header_line.value() != cl
10471082
|| !this->fos_header_line_context
10481083
|| this->fos_header_line_context.value()
10491084
!= this->fos_lss.get_line_context()
@@ -1067,6 +1102,19 @@ field_overlay_source::list_static_overlay(const listview_curses& lv,
10671102
header_top -= 1_vl;
10681103
}
10691104
this->fos_static_lines.clear();
1105+
1106+
// Add sticky header lines that are above the viewport
1107+
const auto& sticky_bv
1108+
= tc.get_bookmarks()[&textview_curses::BM_STICKY];
1109+
for (const auto& sticky_vl : sticky_bv.bv_tree) {
1110+
if (sticky_vl >= top) {
1111+
break;
1112+
}
1113+
auto al = attr_line_t();
1114+
tc.textview_value_for_row(sticky_vl, al);
1115+
this->fos_static_lines.emplace_back(al);
1116+
}
1117+
10701118
if (header_top < 0_vl) {
10711119
auto al = attr_line_t();
10721120
auto filtered_before
@@ -1084,17 +1132,12 @@ field_overlay_source::list_static_overlay(const listview_curses& lv,
10841132
al.with_attr_for_all(
10851133
VC_STYLE.value(text_attrs::with_italic()));
10861134
this->fos_static_lines.emplace_back(al);
1087-
apply_status_attrs(this->fos_static_lines);
10881135
} else {
1089-
this->fos_static_lines.resize(1);
1090-
auto& al = this->fos_static_lines.front();
1091-
auto& tc = dynamic_cast<textview_curses&>(
1092-
const_cast<listview_curses&>(lv));
1136+
auto al = attr_line_t();
10931137
tc.textview_value_for_row(header_top, al);
1094-
if ((top - header_top) > 1 && line->is_continued()) {
1095-
apply_status_attrs(this->fos_static_lines);
1096-
}
1138+
this->fos_static_lines.emplace_back(al);
10971139
}
1140+
apply_status_attrs(this->fos_static_lines);
10981141
this->fos_header_line = cl;
10991142
this->fos_header_line_context
11001143
= this->fos_lss.get_line_context();

0 commit comments

Comments
 (0)