[textview] try to preserve textview location better when reloading

pull/1242/head
Tim Stack 3 months ago
parent 31cf49ec0d
commit 53b0a606d6

@ -12,6 +12,8 @@ Interface changes:
example, after typing in `:filter-in` the current
search term for the view will be suggested (if
one is active).
* The focused line should be preserved more reliably in
the LOG/TEXT views.
## lnav v0.12.0

@ -300,14 +300,14 @@ db_label_source::row_for_time(struct timeval time_bucket)
return nonstd::nullopt;
}
nonstd::optional<struct timeval>
nonstd::optional<text_time_translator::row_info>
db_label_source::time_for_row(vis_line_t row)
{
if ((row < 0_vl) || (((size_t) row) >= this->dls_time_column.size())) {
return nonstd::nullopt;
}
return this->dls_time_column[row];
return row_info{this->dls_time_column[row], row};
}
void

@ -89,7 +89,7 @@ public:
nonstd::optional<vis_line_t> row_for_time(
struct timeval time_bucket) override;
nonstd::optional<struct timeval> time_for_row(vis_line_t row) override;
nonstd::optional<row_info> time_for_row(vis_line_t row) override;
struct header_meta {
explicit header_meta(std::string name) : hm_name(std::move(name)) {}

@ -778,7 +778,7 @@ gantt_source::row_for_time(struct timeval time_bucket)
return vis_line_t(std::distance(this->gs_time_order.begin(), closest_iter));
}
nonstd::optional<struct timeval>
nonstd::optional<text_time_translator::row_info>
gantt_source::time_for_row(vis_line_t row)
{
if (row >= this->gs_time_order.size()) {
@ -791,11 +791,17 @@ gantt_source::time_for_row(vis_line_t row)
auto ov_sel = this->tss_view->get_overlay_selection();
if (ov_sel && ov_sel.value() < otr.otr_sub_ops.size()) {
return otr.otr_sub_ops[ov_sel.value()].ostr_range.tr_begin;
return row_info{
otr.otr_sub_ops[ov_sel.value()].ostr_range.tr_begin,
row,
};
}
}
return otr.otr_range.tr_begin;
return row_info{
otr.otr_range.tr_begin,
row,
};
}
size_t

@ -73,7 +73,7 @@ public:
nonstd::optional<vis_line_t> row_for_time(
struct timeval time_bucket) override;
nonstd::optional<struct timeval> time_for_row(vis_line_t row) override;
nonstd::optional<row_info> time_for_row(vis_line_t row) override;
void rebuild_indexes();

@ -166,7 +166,7 @@ hist_source2::end_of_row()
}
}
nonstd::optional<struct timeval>
nonstd::optional<text_time_translator::row_info>
hist_source2::time_for_row(vis_line_t row)
{
if (row < 0 || row > this->hs_line_count) {
@ -175,7 +175,7 @@ hist_source2::time_for_row(vis_line_t row)
bucket_t& bucket = this->find_bucket(row);
return timeval{bucket.b_time, 0};
return row_info{timeval{bucket.b_time, 0}, row};
}
hist_source2::bucket_t&

@ -376,7 +376,7 @@ public:
return 0;
}
nonstd::optional<struct timeval> time_for_row(vis_line_t row) override;
nonstd::optional<row_info> time_for_row(vis_line_t row) override;
nonstd::optional<vis_line_t> row_for_time(
struct timeval tv_bucket) override;

@ -253,8 +253,8 @@ handle_paging_key(int ch)
lnav_data.ld_last_view = nullptr;
if (src_view != nullptr && dst_view != nullptr) {
src_view->time_for_row(top_tc->get_selection()) |
[dst_view, tc](auto top_time) {
dst_view->row_for_time(top_time) |
[dst_view, tc](auto top_ri) {
dst_view->row_for_time(top_ri.ri_time) |
[tc](auto row) { tc->set_selection(row); };
};
}
@ -578,9 +578,9 @@ handle_paging_key(int ch)
if (lss) {
const int step = 24 * 60 * 60;
lss->time_for_row(tc->get_selection()) |
[lss, tc](auto first_time) {
[lss, tc](auto first_ri) {
lss->find_from_time(
roundup_size(first_time.tv_sec, step))
roundup_size(first_ri.ri_time.tv_sec, step))
| [tc](auto line) { tc->set_selection(line); };
};
}
@ -589,8 +589,9 @@ handle_paging_key(int ch)
case ')':
if (lss) {
lss->time_for_row(tc->get_selection()) |
[lss, tc](auto first_time) {
time_t day = rounddown(first_time.tv_sec, 24 * 60 * 60);
[lss, tc](auto first_ri) {
time_t day
= rounddown(first_ri.ri_time.tv_sec, 24 * 60 * 60);
lss->find_from_time(day) | [tc](auto line) {
if (line != 0_vl) {
--line;
@ -607,9 +608,9 @@ handle_paging_key(int ch)
"the top of the log has been reached");
} else if (lss) {
lss->time_for_row(tc->get_selection()) |
[lss, ch, tc](auto first_time) {
[lss, ch, tc](auto first_ri) {
int step = ch == 'D' ? (24 * 60 * 60) : (60 * 60);
time_t top_time = first_time.tv_sec;
time_t top_time = first_ri.ri_time.tv_sec;
lss->find_from_time(top_time - step) | [tc](auto line) {
if (line != 0_vl) {
--line;
@ -625,9 +626,9 @@ handle_paging_key(int ch)
case 'd':
if (lss) {
lss->time_for_row(tc->get_selection()) |
[ch, lss, tc](auto first_time) {
[ch, lss, tc](auto first_ri) {
int step = ch == 'd' ? (24 * 60 * 60) : (60 * 60);
lss->find_from_time(first_time.tv_sec + step) |
lss->find_from_time(first_ri.ri_time.tv_sec + step) |
[tc](auto line) { tc->set_selection(line); };
};
@ -729,12 +730,13 @@ handle_paging_key(int ch)
if (src_view != nullptr) {
src_view->time_for_row(tc->get_selection()) |
[](auto log_top) {
lnav_data.ld_hist_source2.row_for_time(log_top) |
[](auto row) {
lnav_data.ld_views[LNV_HISTOGRAM]
.set_selection(row);
};
[](auto log_top_ri) {
lnav_data.ld_hist_source2.row_for_time(
log_top_ri.ri_time)
| [](auto row) {
lnav_data.ld_views[LNV_HISTOGRAM]
.set_selection(row);
};
};
}
} else {
@ -749,10 +751,10 @@ handle_paging_key(int ch)
auto curr_top_time_opt
= dst_view->time_for_row(top_tc->get_selection());
if (hist_top_time_opt && curr_top_time_opt
&& hs.row_for_time(hist_top_time_opt.value())
!= hs.row_for_time(curr_top_time_opt.value()))
&& hs.row_for_time(hist_top_time_opt->ri_time)
!= hs.row_for_time(curr_top_time_opt->ri_time))
{
dst_view->row_for_time(hist_top_time_opt.value()) |
dst_view->row_for_time(hist_top_time_opt->ri_time) |
[top_tc](auto new_top) {
top_tc->set_selection(new_top);
top_tc->set_needs_update();

@ -724,7 +724,7 @@ com_goto(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
auto top_time_opt = ttt->time_for_row(tc->get_selection());
if (top_time_opt) {
auto top_time_tv = top_time_opt.value();
auto top_time_tv = top_time_opt.value().ri_time;
struct tm top_tm;
localtime_r(&top_time_tv.tv_sec, &top_tm);
@ -745,7 +745,7 @@ com_goto(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
if (!tv_opt) {
return ec.make_error("cannot get time for the top row");
}
tv = tv_opt.value();
tv = tv_opt.value().ri_time;
vis_line_t vl = tc->get_selection(), new_vl;
bool done = false;
@ -4286,7 +4286,7 @@ com_zoom_to(exec_context& ec,
auto old_time_opt = lnav_data.ld_hist_source2.time_for_row(
lnav_data.ld_views[LNV_HISTOGRAM].get_top());
if (old_time_opt) {
old_time = old_time_opt.value();
old_time = old_time_opt.value().ri_time;
rebuild_hist();
lnav_data.ld_hist_source2.row_for_time(old_time) |
[](auto new_top) {
@ -4307,7 +4307,7 @@ com_zoom_to(exec_context& ec,
spectro_view.reload_data();
if (old_time_opt) {
lnav_data.ld_spectro_source->row_for_time(
old_time_opt.value())
old_time_opt.value().ri_time)
| [](auto new_top) {
lnav_data.ld_views[LNV_SPECTRO].set_selection(
new_top);
@ -4616,9 +4616,9 @@ com_hide_line(exec_context& ec,
struct exttm tm;
auto vl = tc->get_selection();
auto log_tv = ttt->time_for_row(vl);
if (log_tv) {
tm = exttm::from_tv(log_tv.value());
auto log_vl_ri = ttt->time_for_row(vl);
if (log_vl_ri) {
tm = exttm::from_tv(log_vl_ri.value().ri_time);
tv_opt = parse_res.unwrap().adjust(tm).to_timeval();
}
}

@ -2778,3 +2778,31 @@ logfile_sub_source::get_filtered_count_for(size_t filter_index) const
return retval;
}
nonstd::optional<vis_line_t>
logfile_sub_source::row_for(const row_info& ri)
{
auto lb = std::lower_bound(this->lss_filtered_index.begin(),
this->lss_filtered_index.end(),
ri.ri_time,
filtered_logline_cmp(*this));
if (lb != this->lss_filtered_index.end()) {
auto first_lb = lb;
while (true) {
auto cl = this->lss_index[*lb];
if (content_line_t(ri.ri_id) == cl) {
first_lb = lb;
break;
}
auto ll = this->find_line(cl);
if (ll->get_timeval() != ri.ri_time) {
break;
}
++lb;
}
return vis_line_t(first_lb - this->lss_filtered_index.begin());
}
return nonstd::nullopt;
}

@ -477,14 +477,20 @@ public:
nonstd::optional<vis_line_t> find_from_content(content_line_t cl);
nonstd::optional<struct timeval> time_for_row(vis_line_t row)
nonstd::optional<row_info> time_for_row(vis_line_t row)
{
if (row >= 0_vl && row < (ssize_t) this->text_line_count()) {
return this->find_line(this->at(row))->get_timeval();
auto cl = this->at(row);
return row_info{
this->find_line(cl)->get_timeval(),
(int64_t) cl,
};
}
return nonstd::nullopt;
}
nonstd::optional<vis_line_t> row_for(const row_info& ri);
nonstd::optional<vis_line_t> row_for_time(struct timeval time_bucket)
{
return this->find_from_time(time_bucket);

@ -82,7 +82,7 @@ public:
return this->fss_time_delegate->row_for_time(time_bucket);
}
nonstd::optional<struct timeval> time_for_row(vis_line_t row) override
nonstd::optional<row_info> time_for_row(vis_line_t row) override
{
return this->fss_lines | lnav::itertools::nth(row)
| lnav::itertools::flat_map([this](const auto row) {

@ -82,7 +82,7 @@ spectrogram_source::list_input_handle_key(listview_curses& lv, int ch)
return true;
}
auto begin_time = begin_time_opt.value();
struct timeval end_time = begin_time;
struct timeval end_time = begin_time.ri_time;
end_time.tv_sec += this->ss_granularity;
double range_min, range_max, column_size;
@ -93,7 +93,7 @@ spectrogram_source::list_input_handle_key(listview_curses& lv, int ch)
+ this->ss_cursor_column.value_or(0) * column_size;
range_max = range_min + column_size;
this->ss_value_source->spectro_mark((textview_curses&) lv,
begin_time.tv_sec,
begin_time.ri_time.tv_sec,
end_time.tv_sec,
range_min,
range_max);
@ -291,7 +291,7 @@ spectrogram_source::text_line_width(textview_curses& tc)
return width;
}
nonstd::optional<struct timeval>
nonstd::optional<text_time_translator::row_info>
spectrogram_source::time_for_row(vis_line_t row)
{
if (this->ss_details_source != nullptr) {
@ -306,7 +306,7 @@ spectrogram_source::time_for_row(vis_line_t row)
return this->time_for_row_int(row);
}
nonstd::optional<struct timeval>
nonstd::optional<text_time_translator::row_info>
spectrogram_source::time_for_row_int(vis_line_t row)
{
struct timeval retval {
@ -318,7 +318,7 @@ spectrogram_source::time_for_row_int(vis_line_t row)
= rounddown(this->ss_cached_bounds.sb_begin_time, this->ss_granularity)
+ row * this->ss_granularity;
return retval;
return row_info{retval, row};
}
nonstd::optional<vis_line_t>
@ -359,9 +359,9 @@ spectrogram_source::text_value_for_line(textview_curses& tc,
value_out.clear();
return;
}
auto row_time = row_time_opt.value();
auto ri = row_time_opt.value();
gmtime_r(&row_time.tv_sec, &tm);
gmtime_r(&ri.ri_time.tv_sec, &tm);
strftime(tm_buffer, sizeof(tm_buffer), " %a %b %d %H:%M:%S", &tm);
value_out = tm_buffer;

@ -154,7 +154,7 @@ public:
void text_selection_changed(textview_curses& tc) override;
nonstd::optional<struct timeval> time_for_row(vis_line_t row) override;
nonstd::optional<row_info> time_for_row(vis_line_t row) override;
nonstd::optional<vis_line_t> row_for_time(
struct timeval time_bucket) override;
@ -170,7 +170,7 @@ public:
void cache_bounds();
nonstd::optional<struct timeval> time_for_row_int(vis_line_t row);
nonstd::optional<row_info> time_for_row_int(vis_line_t row);
const spectrogram_row& load_row(const listview_curses& lv, int row);

@ -84,15 +84,15 @@ sql_log_top_datetime()
return nonstd::nullopt;
}
auto top_time = lnav_data.ld_log_source.time_for_row(
auto top_ri = lnav_data.ld_log_source.time_for_row(
lnav_data.ld_views[LNV_LOG].get_selection());
if (!top_time) {
if (!top_ri) {
return nonstd::nullopt;
}
char buffer[64];
sql_strftime(buffer, sizeof(buffer), top_time.value());
sql_strftime(buffer, sizeof(buffer), top_ri->ri_time);
return buffer;
}

@ -429,18 +429,13 @@ textfile_sub_source::push_back(const std::shared_ptr<logfile>& lf)
void
textfile_sub_source::text_filters_changed()
{
for (auto iter = this->tss_files.begin(); iter != this->tss_files.end();) {
++iter;
}
for (auto iter = this->tss_hidden_files.begin();
iter != this->tss_hidden_files.end();)
{
++iter;
auto lf = this->current_file();
if (lf == nullptr || lf->get_text_format() == text_format_t::TF_BINARY) {
return;
}
std::shared_ptr<logfile> lf = this->current_file();
if (lf == nullptr) {
auto rend_iter = this->tss_rendered_files.find(lf->get_filename());
if (rend_iter != this->tss_rendered_files.end()) {
return;
}
@ -462,6 +457,37 @@ textfile_sub_source::text_filters_changed()
}
this->tss_view->redo_search();
auto iter = std::lower_bound(lfo->lfo_filter_state.tfs_index.begin(),
lfo->lfo_filter_state.tfs_index.end(),
this->tss_content_line);
auto vl = vis_line_t(
std::distance(lfo->lfo_filter_state.tfs_index.begin(), iter));
this->tss_view->set_selection(vl);
}
void
textfile_sub_source::scroll_invoked(textview_curses* tc)
{
auto lf = this->current_file();
if (lf == nullptr || lf->get_text_format() == text_format_t::TF_BINARY) {
return;
}
auto rend_iter = this->tss_rendered_files.find(lf->get_filename());
if (rend_iter != this->tss_rendered_files.end()) {
return;
}
auto line = tc->get_selection();
auto* lfo = dynamic_cast<line_filter_observer*>(lf->get_logline_observer());
if (lfo == nullptr || line < 0_vl
|| line >= lfo->lfo_filter_state.tfs_index.size())
{
return;
}
this->tss_content_line = lfo->lfo_filter_state.tfs_index[line];
}
int

@ -166,6 +166,8 @@ public:
logline* text_accel_get_line(vis_line_t vl) override;
void scroll_invoked(textview_curses* tc) override;
private:
void detach_observer(std::shared_ptr<logfile> lf)
{
@ -193,6 +195,7 @@ private:
size_t tss_line_indent_size{0};
bool tss_completed_last_scan{true};
attr_line_t tss_hex_line;
int64_t tss_content_line{0};
};
class textfile_header_overlay : public list_overlay_source {

@ -822,11 +822,21 @@ textview_curses::grep_value_for_line(vis_line_t line, std::string& value_out)
}
void
text_time_translator::scroll_invoked(textview_curses* tc)
text_sub_source::scroll_invoked(textview_curses* tc)
{
if (tc->get_inner_height() > 0) {
auto* ttt = dynamic_cast<text_time_translator*>(this);
if (ttt != nullptr) {
ttt->ttt_scroll_invoked(tc);
}
}
void
text_time_translator::ttt_scroll_invoked(textview_curses* tc)
{
if (tc->get_inner_height() > 0 && tc->get_selection() >= 0_vl) {
this->time_for_row(tc->get_selection()) |
[this](auto new_top_time) { this->ttt_top_time = new_top_time; };
[this](auto new_top_ri) { this->ttt_top_row_info = new_top_ri; };
}
}
@ -834,29 +844,13 @@ void
text_time_translator::data_reloaded(textview_curses* tc)
{
if (tc->get_inner_height() == 0) {
this->ttt_top_row_info = nonstd::nullopt;
return;
}
if (tc->get_selection() < 0_vl
|| tc->get_selection() > tc->get_inner_height())
{
if (this->ttt_top_time.tv_sec != 0) {
this->row_for_time(this->ttt_top_time) |
[tc](auto new_top) { tc->set_selection(new_top); };
}
return;
if (this->ttt_top_row_info) {
this->row_for(this->ttt_top_row_info.value()) |
[tc](auto new_top) { tc->set_selection(new_top); };
}
this->time_for_row(tc->get_selection()) | [this, tc](auto top_time) {
if (top_time != this->ttt_top_time) {
if (this->ttt_top_time.tv_sec != 0) {
this->row_for_time(this->ttt_top_time) |
[tc](auto new_top) { tc->set_selection(new_top); };
}
this->time_for_row(tc->get_selection()) |
[this](auto new_top_time) {
this->ttt_top_time = new_top_time;
};
}
};
}
template class bookmark_vector<vis_line_t>;

@ -250,22 +250,32 @@ private:
class text_time_translator {
public:
struct row_info {
struct timeval ri_time {
0, 0
};
int64_t ri_id{-1};
};
virtual ~text_time_translator() = default;
virtual nonstd::optional<vis_line_t> row_for_time(
struct timeval time_bucket)
= 0;
virtual nonstd::optional<struct timeval> time_for_row(vis_line_t row) = 0;
virtual nonstd::optional<vis_line_t> row_for(const row_info& ri)
{
return this->row_for_time(ri.ri_time);
}
void scroll_invoked(textview_curses* tc);
virtual nonstd::optional<row_info> time_for_row(vis_line_t row) = 0;
void data_reloaded(textview_curses* tc);
void ttt_scroll_invoked(textview_curses* tc);
protected:
struct timeval ttt_top_time {
0, 0
};
nonstd::optional<row_info> ttt_top_row_info;
};
class text_accel_source {
@ -496,6 +506,8 @@ public:
virtual void quiesce() {}
virtual void scroll_invoked(textview_curses* tc);
bool tss_supports_filtering{false};
bool tss_apply_filters{true};
@ -777,11 +789,7 @@ public:
void invoke_scroll()
{
if (this->tc_sub_source != nullptr) {
auto ttt = dynamic_cast<text_time_translator*>(this->tc_sub_source);
if (ttt != nullptr) {
ttt->scroll_invoked(this);
}
this->tc_sub_source->scroll_invoked(this);
}
listview_curses::invoke_scroll();

@ -300,15 +300,15 @@ CREATE TABLE lnav_views (
= dynamic_cast<text_time_translator*>(tc.get_sub_source());
if (time_source != nullptr && tc.get_inner_height() > 0) {
auto top_time_opt
auto top_ri_opt
= time_source->time_for_row(tc.get_selection());
if (top_time_opt) {
if (top_ri_opt) {
char timestamp[64];
sql_strftime(timestamp,
sizeof(timestamp),
top_time_opt.value(),
top_ri_opt->ri_time,
' ');
sqlite3_result_text(
ctx, timestamp, -1, SQLITE_TRANSIENT);
@ -373,15 +373,15 @@ CREATE TABLE lnav_views (
top_line_meta tlm;
if (time_source != nullptr) {
auto top_time_opt
auto top_ri_opt
= time_source->time_for_row(tc.get_selection());
if (top_time_opt) {
if (top_ri_opt) {
char timestamp[64];
sql_strftime(timestamp,
sizeof(timestamp),
top_time_opt.value(),
top_ri_opt->ri_time,
' ');
tlm.tlm_time = timestamp;
}
@ -530,11 +530,11 @@ CREATE TABLE lnav_views (
tc.get_title().c_str(),
top_time);
if (dts.convert_to_timeval(top_time, -1, nullptr, tv)) {
auto last_time_opt
auto last_ri_opt
= time_source->time_for_row(tc.get_selection());
if (last_time_opt) {
auto last_time = last_time_opt.value();
if (last_ri_opt) {
auto last_time = last_ri_opt->ri_time;
if (tv != last_time) {
time_source->row_for_time(tv) |
[&tc, &selection](auto row) {

@ -1228,6 +1228,8 @@ EXPECTED_FILES = \
$(srcdir)/%reldir%/test_text_file.sh_6a24078983cf1b7a80b6fb65d5186cd125498136.out \
$(srcdir)/%reldir%/test_text_file.sh_73f69c883f60761bff9f8874f61d21a189e92912.err \
$(srcdir)/%reldir%/test_text_file.sh_73f69c883f60761bff9f8874f61d21a189e92912.out \
$(srcdir)/%reldir%/test_text_file.sh_786c7262f977201af36b0e69ba1a2aba130bbb06.err \
$(srcdir)/%reldir%/test_text_file.sh_786c7262f977201af36b0e69ba1a2aba130bbb06.out \
$(srcdir)/%reldir%/test_text_file.sh_78f252288519c8f767bb2759ea32959dab2ebc46.err \
$(srcdir)/%reldir%/test_text_file.sh_78f252288519c8f767bb2759ea32959dab2ebc46.out \
$(srcdir)/%reldir%/test_text_file.sh_7b00f32a3fff7fc2d78a87045ae842e58be88480.err \

@ -0,0 +1,4 @@
Duis aute irure dolor in reprehenderit in voluptate velit
esse cillum dolore eu fugiat nulla pariatur. Excepteur
sint occaecat cupidatat non proident, sunt in culpa qui
officia deserunt mollit anim id est laborum.

@ -4,6 +4,11 @@ export TZ=UTC
export YES_COLOR=1
unset XDG_CONFIG_HOME
run_cap_test ${lnav_test} -n \
-c ':goto 5' \
-c ':filter-out Lorem|sed' \
${test_dir}/textfile_plain.0
run_cap_test ${lnav_test} -n \
${top_srcdir}/README.md

@ -0,0 +1,9 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit
esse cillum dolore eu fugiat nulla pariatur. Excepteur
sint occaecat cupidatat non proident, sunt in culpa qui
officia deserunt mollit anim id est laborum.
Loading…
Cancel
Save