mirror of https://github.com/tstack/lnav.git
parent
7fe811da07
commit
814ad03ec9
4
NEWS
4
NEWS
|
@ -7,6 +7,10 @@ lnav v0.8.5:
|
|||
with hotkeys.
|
||||
* Added an 'lnav_view_filters' SQL table that can be used to
|
||||
programmatically manipulate filters.
|
||||
* A history of locations in a view is now kept so that you can jump back
|
||||
to where you were previously using the '{' and '}' keys. The location
|
||||
history can also be accessed through the ":prev-location" and
|
||||
":next-location" commands.
|
||||
* The ":write-*" commands will now accept "/dev/clipboard" as a file name
|
||||
that writes to the system clipboard.
|
||||
* The ":write-to" and ":write-raw-to" commands will now print out comments
|
||||
|
|
|
@ -59,6 +59,8 @@ Navigation
|
|||
bookmark of the given type in the current view.
|
||||
* prev-mark error|warning|search|user|file|partition - Move to the previous
|
||||
bookmark of the given type in the current view.
|
||||
* prev-location - The previous location in the history.
|
||||
* next-location - The next location in the history.
|
||||
|
||||
Time
|
||||
----
|
||||
|
|
|
@ -129,6 +129,10 @@ Spatial Navigation
|
|||
- |ks| Shift |ke| + |ks| s |ke|
|
||||
-
|
||||
- Next/prevous slow down in the log message rate
|
||||
* - |ks| { |ke|
|
||||
- |ks| } |ke|
|
||||
-
|
||||
- Previous/next location in history
|
||||
|
||||
Chronological Navigation
|
||||
------------------------
|
||||
|
|
|
@ -20,6 +20,7 @@ set(diag_STAT_SRCS
|
|||
help_text_formatter.cc
|
||||
hist_source.cc
|
||||
hotkeys.cc
|
||||
input_dispatcher.cc
|
||||
intern_string.cc
|
||||
is_utf8.cc
|
||||
json-extension-functions.cc
|
||||
|
@ -123,6 +124,7 @@ set(diag_STAT_SRCS
|
|||
highlighter.hh
|
||||
hotkeys.hh
|
||||
init-sql.hh
|
||||
input_dispatcher.hh
|
||||
intern_string.hh
|
||||
is_utf8.hh
|
||||
k_merge_tree.h
|
||||
|
@ -145,6 +147,7 @@ set(diag_STAT_SRCS
|
|||
readline_possibilities.hh
|
||||
regexp_vtab.hh
|
||||
relative_time.hh
|
||||
ring_span.hh
|
||||
sequence_sink.hh
|
||||
shlex.hh
|
||||
simdutf8check.h
|
||||
|
|
|
@ -174,6 +174,7 @@ noinst_HEADERS = \
|
|||
hotkeys.hh \
|
||||
init.sql \
|
||||
init-sql.hh \
|
||||
input_dispatcher.hh \
|
||||
intern_string.hh \
|
||||
is_utf8.hh \
|
||||
json_op.hh \
|
||||
|
@ -293,6 +294,7 @@ libdiag_a_SOURCES = \
|
|||
help_text_formatter.cc \
|
||||
hist_source.cc \
|
||||
hotkeys.cc \
|
||||
input_dispatcher.cc \
|
||||
intern_string.cc \
|
||||
is_utf8.cc \
|
||||
json-extension-functions.cc \
|
||||
|
|
|
@ -241,6 +241,12 @@ Spatial Navigation
|
|||
five seconds later, the last message will be highlighted
|
||||
as a slow down.
|
||||
|
||||
{/} Move to the previous/next location in history. Whenever
|
||||
you jump to a new location in the view, the location will
|
||||
be added to the history. The history is not updated when
|
||||
using only the arrow keys.
|
||||
|
||||
|
||||
Chronological Navigation
|
||||
------------------------
|
||||
|
||||
|
|
|
@ -101,6 +101,60 @@ public:
|
|||
vector<logline_value> lh_line_values;
|
||||
};
|
||||
|
||||
static int key_sql_callback(exec_context &ec, sqlite3_stmt *stmt)
|
||||
{
|
||||
if (!sqlite3_stmt_busy(stmt)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ncols = sqlite3_column_count(stmt);
|
||||
|
||||
auto &vars = ec.ec_local_vars.top();
|
||||
|
||||
for (int lpc = 0; lpc < ncols; lpc++) {
|
||||
const char *column_name = sqlite3_column_name(stmt, lpc);
|
||||
|
||||
if (sql_ident_needs_quote(column_name)) {
|
||||
continue;
|
||||
}
|
||||
if (sqlite3_column_type(stmt, lpc) == SQLITE_NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
vars[column_name] = string((const char *) sqlite3_column_text(stmt, lpc));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool handle_keyseq(const char *keyseq)
|
||||
{
|
||||
key_map &km = lnav_config.lc_ui_keymaps[lnav_config.lc_ui_keymap];
|
||||
|
||||
const auto &iter = km.km_seq_to_cmd.find(keyseq);
|
||||
if (iter != km.km_seq_to_cmd.end()) {
|
||||
vector<logline_value> values;
|
||||
exec_context ec(&values, key_sql_callback, pipe_callback);
|
||||
auto &var_stack = ec.ec_local_vars;
|
||||
string result;
|
||||
|
||||
ec.ec_global_vars = lnav_data.ld_exec_context.ec_global_vars;
|
||||
var_stack.push(map<string, string>());
|
||||
auto &vars = var_stack.top();
|
||||
vars["keyseq"] = keyseq;
|
||||
for (const string &cmd : iter->second) {
|
||||
log_debug("executing key sequence x%02x: %s",
|
||||
keyseq, cmd.c_str());
|
||||
result = execute_any(ec, cmd);
|
||||
}
|
||||
|
||||
lnav_data.ld_rl_view->set_value(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void handle_paging_key(int ch)
|
||||
{
|
||||
if (lnav_data.ld_view_stack.vs_views.empty()) {
|
||||
|
@ -117,6 +171,18 @@ void handle_paging_key(int ch)
|
|||
return;
|
||||
}
|
||||
|
||||
char keyseq[16];
|
||||
|
||||
snprintf(keyseq, sizeof(keyseq), "x%02x", ch);
|
||||
|
||||
if (handle_keyseq(keyseq)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tc->handle_key(ch)) {
|
||||
return;
|
||||
}
|
||||
|
||||
lss = dynamic_cast<logfile_sub_source *>(tc->get_sub_source());
|
||||
|
||||
/* process the command keystroke */
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#ifndef LNAV_HOTKEYS_H
|
||||
#define LNAV_HOTKEYS_H
|
||||
|
||||
bool handle_keyseq(const char *keyseq);
|
||||
void handle_paging_key(int ch);
|
||||
|
||||
#endif //LNAV_HOTKEYS_H
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
/**
|
||||
* Copyright (c) 2018, Timothy Stack
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Timothy Stack nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @file input_dispatcher.cc
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <sys/time.h>
|
||||
|
||||
#if defined HAVE_NCURSESW_CURSES_H
|
||||
# include <ncursesw/curses.h>
|
||||
#elif defined HAVE_NCURSESW_H
|
||||
# include <ncursesw.h>
|
||||
#elif defined HAVE_NCURSES_CURSES_H
|
||||
# include <ncurses/curses.h>
|
||||
#elif defined HAVE_NCURSES_H
|
||||
# include <ncurses.h>
|
||||
#elif defined HAVE_CURSES_H
|
||||
# include <curses.h>
|
||||
#else
|
||||
# error "SysV or X/Open-compatible Curses header file required"
|
||||
#endif
|
||||
|
||||
#include "input_dispatcher.hh"
|
||||
|
||||
void input_dispatcher::new_input(const struct timeval ¤t_time, int ch)
|
||||
{
|
||||
switch (ch) {
|
||||
case KEY_ESCAPE:
|
||||
this->id_escape_index = 0;
|
||||
this->append_to_escape_buffer(ch);
|
||||
this->id_escape_start_time = current_time;
|
||||
break;
|
||||
case KEY_MOUSE:
|
||||
this->id_mouse_handler();
|
||||
break;
|
||||
default:
|
||||
if (this->id_escape_index > 0) {
|
||||
if (strcmp("\x1b[", this->id_escape_buffer) == 0) {
|
||||
this->id_mouse_handler();
|
||||
this->id_escape_index = 0;
|
||||
} else {
|
||||
this->append_to_escape_buffer(ch);
|
||||
|
||||
switch (this->id_escape_matcher(this->id_escape_buffer)) {
|
||||
case escape_match_t::NONE:
|
||||
for (int lpc = 0; this->id_escape_buffer[lpc]; lpc++) {
|
||||
this->id_key_handler(this->id_escape_buffer[lpc]);
|
||||
}
|
||||
this->id_escape_index = 0;
|
||||
break;
|
||||
case escape_match_t::PARTIAL:
|
||||
break;
|
||||
case escape_match_t::FULL:
|
||||
this->id_escape_handler(this->id_escape_buffer);
|
||||
this->id_escape_index = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this->id_key_handler(ch);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void input_dispatcher::poll(const struct timeval ¤t_time)
|
||||
{
|
||||
if (this->id_escape_index == 1) {
|
||||
struct timeval diff;
|
||||
|
||||
gettimeofday((struct timeval *) ¤t_time, nullptr);
|
||||
|
||||
timersub(¤t_time, &this->id_escape_start_time, &diff);
|
||||
if (diff.tv_sec > 0 || diff.tv_usec > (10000)) {
|
||||
this->id_key_handler(KEY_CTRL_RBRACKET);
|
||||
this->id_escape_index = 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* Copyright (c) 2018, Timothy Stack
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Timothy Stack nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @file input_dispatcher.hh
|
||||
*/
|
||||
|
||||
#ifndef INPUT_DISPATCHER_HH
|
||||
#define INPUT_DISPATCHER_HH
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#define KEY_ESCAPE 0x1b
|
||||
#define KEY_CTRL_RBRACKET 0x1d
|
||||
|
||||
class input_dispatcher {
|
||||
public:
|
||||
void new_input(const struct timeval ¤t_time, int ch);
|
||||
|
||||
void poll(const struct timeval ¤t_time);
|
||||
|
||||
bool in_escape() const {
|
||||
return this->id_escape_index > 0;
|
||||
}
|
||||
|
||||
enum class escape_match_t {
|
||||
NONE,
|
||||
PARTIAL,
|
||||
FULL,
|
||||
};
|
||||
|
||||
std::function<escape_match_t(const char *)> id_escape_matcher;
|
||||
std::function<void(int)> id_key_handler;
|
||||
std::function<void(const char *)> id_escape_handler;
|
||||
std::function<void()> id_mouse_handler;
|
||||
private:
|
||||
void append_to_escape_buffer(int ch) {
|
||||
this->id_escape_buffer[this->id_escape_index++] = static_cast<char>(ch);
|
||||
this->id_escape_buffer[this->id_escape_index] = '\0';
|
||||
}
|
||||
|
||||
char id_escape_buffer[32];
|
||||
size_t id_escape_index{0};
|
||||
struct timeval id_escape_start_time{0, 0};
|
||||
};
|
||||
|
||||
#endif
|
|
@ -9,7 +9,9 @@
|
|||
"keymap_def_text_view": "Press ${ansi_bold}t${ansi_norm} to switch to the text view",
|
||||
"keymap_def_pop_view": "Press ${ansi_bold}q${ansi_norm} to return to the previous view",
|
||||
"keymap_def_zoom": "Press ${ansi_bold}z${ansi_norm}/${ansi_bold}z${ansi_norm} to zoom in/out",
|
||||
"keymap_def_clear": "Press ${ansi_bold}C${ansi_norm} to clear marked messages"
|
||||
"keymap_def_clear": "Press ${ansi_bold}C${ansi_norm} to clear marked messages",
|
||||
"keymap_def_prev_location": "Press ${ansi_bold}{${ansi_norm} to move to the previous location in history",
|
||||
"keymap_def_next_location": "Press ${ansi_bold}}${ansi_norm} to move to the next location in history"
|
||||
},
|
||||
"keymap_def": {
|
||||
"default": {
|
||||
|
@ -70,6 +72,15 @@
|
|||
":eval :alt-msg ${keymap_def_scroll_horiz}"
|
||||
],
|
||||
|
||||
"x7d": [
|
||||
":next-location",
|
||||
":eval :alt-msg ${keymap_def_prev_location}"
|
||||
],
|
||||
"x7b": [
|
||||
":prev-location",
|
||||
":eval :alt-msg ${keymap_def_next_location}"
|
||||
],
|
||||
|
||||
"x31": [":goto next 10 minutes after the hour"],
|
||||
"x32": [":goto next 20 minutes after the hour"],
|
||||
"x33": [":goto next 30 minutes after the hour"],
|
||||
|
|
|
@ -44,32 +44,14 @@ using namespace std;
|
|||
list_gutter_source listview_curses::DEFAULT_GUTTER_SOURCE;
|
||||
|
||||
listview_curses::listview_curses()
|
||||
: lv_source(NULL),
|
||||
lv_overlay_source(NULL),
|
||||
lv_window(NULL),
|
||||
lv_x(0),
|
||||
lv_y(0),
|
||||
lv_top(0),
|
||||
lv_left(0),
|
||||
lv_height(0),
|
||||
lv_overlay_needs_update(true),
|
||||
lv_show_scrollbar(true),
|
||||
lv_show_bottom_border(false),
|
||||
lv_gutter_source(&DEFAULT_GUTTER_SOURCE),
|
||||
lv_word_wrap(false),
|
||||
lv_scroll_accel(0),
|
||||
lv_scroll_velo(0),
|
||||
lv_mouse_y(-1),
|
||||
lv_mouse_mode(LV_MODE_NONE),
|
||||
lv_tail_space(1)
|
||||
{ }
|
||||
= default;
|
||||
|
||||
listview_curses::~listview_curses()
|
||||
{ }
|
||||
= default;
|
||||
|
||||
void listview_curses::reload_data(void)
|
||||
void listview_curses::reload_data()
|
||||
{
|
||||
if (this->lv_source == NULL) {
|
||||
if (this->lv_source == nullptr) {
|
||||
this->lv_top = 0_vl;
|
||||
this->lv_left = 0;
|
||||
}
|
||||
|
|
|
@ -569,29 +569,30 @@ protected:
|
|||
static list_gutter_source DEFAULT_GUTTER_SOURCE;
|
||||
|
||||
std::string lv_title;
|
||||
list_data_source *lv_source; /*< The data source delegate. */
|
||||
list_data_source *lv_source{nullptr}; /*< The data source delegate. */
|
||||
std::list<list_input_delegate *> lv_input_delegates;
|
||||
list_overlay_source *lv_overlay_source;
|
||||
list_overlay_source *lv_overlay_source{nullptr};
|
||||
action lv_scroll; /*< The scroll action. */
|
||||
WINDOW * lv_window; /*< The window that contains this view. */
|
||||
unsigned int lv_x;
|
||||
unsigned int lv_y; /*< The y offset of this view. */
|
||||
vis_line_t lv_top; /*< The line at the top of the view. */
|
||||
unsigned int lv_left; /*< The column at the left of the view. */
|
||||
vis_line_t lv_height; /*< The abs/rel height of the view. */
|
||||
bool lv_overlay_needs_update;
|
||||
bool lv_show_scrollbar; /*< Draw the scrollbar in the view. */
|
||||
bool lv_show_bottom_border;
|
||||
list_gutter_source *lv_gutter_source;
|
||||
bool lv_word_wrap;
|
||||
WINDOW * lv_window{nullptr}; /*< The window that contains this view. */
|
||||
unsigned int lv_x{0};
|
||||
unsigned int lv_y{0}; /*< The y offset of this view. */
|
||||
vis_line_t lv_top{0}; /*< The line at the top of the view. */
|
||||
unsigned int lv_left{0}; /*< The column at the left of the view. */
|
||||
vis_line_t lv_height{0}; /*< The abs/rel height of the view. */
|
||||
int lv_history_position{0};
|
||||
bool lv_overlay_needs_update{true};
|
||||
bool lv_show_scrollbar{true}; /*< Draw the scrollbar in the view. */
|
||||
bool lv_show_bottom_border{false};
|
||||
list_gutter_source *lv_gutter_source{&DEFAULT_GUTTER_SOURCE};
|
||||
bool lv_word_wrap{false};
|
||||
bool lv_selectable{false};
|
||||
vis_line_t lv_selection{0};
|
||||
|
||||
struct timeval lv_mouse_time;
|
||||
int lv_scroll_accel;
|
||||
int lv_scroll_velo;
|
||||
int lv_mouse_y;
|
||||
lv_mode_t lv_mouse_mode;
|
||||
vis_line_t lv_tail_space;
|
||||
int lv_mouse_y{-1};
|
||||
lv_mode_t lv_mouse_mode{LV_MODE_NONE};
|
||||
vis_line_t lv_tail_space{1};
|
||||
};
|
||||
#endif
|
||||
|
|
187
src/lnav.cc
187
src/lnav.cc
|
@ -632,32 +632,6 @@ static void sigchld(int sig)
|
|||
lnav_data.ld_child_terminated = true;
|
||||
}
|
||||
|
||||
static int key_sql_callback(exec_context &ec, sqlite3_stmt *stmt)
|
||||
{
|
||||
if (!sqlite3_stmt_busy(stmt)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ncols = sqlite3_column_count(stmt);
|
||||
|
||||
auto &vars = ec.ec_local_vars.top();
|
||||
|
||||
for (int lpc = 0; lpc < ncols; lpc++) {
|
||||
const char *column_name = sqlite3_column_name(stmt, lpc);
|
||||
|
||||
if (sql_ident_needs_quote(column_name)) {
|
||||
continue;
|
||||
}
|
||||
if (sqlite3_column_type(stmt, lpc) == SQLITE_NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
vars[column_name] = string((const char *) sqlite3_column_text(stmt, lpc));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
vis_line_t next_cluster(
|
||||
vis_line_t(bookmark_vector<vis_line_t>::*f) (vis_line_t) const,
|
||||
bookmark_type_t *bt,
|
||||
|
@ -716,6 +690,10 @@ bool moveto_cluster(vis_line_t(bookmark_vector<vis_line_t>::*f) (vis_line_t) con
|
|||
new_top = next_cluster(f, bt, tc->get_top());
|
||||
}
|
||||
if (new_top != -1) {
|
||||
tc->get_sub_source()->get_location_history() | [new_top] (auto lh) {
|
||||
lh->loc_history_append(new_top);
|
||||
};
|
||||
|
||||
tc->set_top(new_top);
|
||||
return true;
|
||||
}
|
||||
|
@ -744,6 +722,10 @@ void previous_cluster(bookmark_type_t *bt, textview_curses *tc)
|
|||
}
|
||||
|
||||
if (new_top != -1) {
|
||||
tc->get_sub_source()->get_location_history() | [new_top] (auto lh) {
|
||||
lh->loc_history_append(new_top);
|
||||
};
|
||||
|
||||
tc->set_top(new_top);
|
||||
}
|
||||
else {
|
||||
|
@ -1100,7 +1082,7 @@ public:
|
|||
me.me_state = BUTTON_STATE_PRESSED;
|
||||
}
|
||||
|
||||
gettimeofday(&me.me_time, NULL);
|
||||
gettimeofday(&me.me_time, nullptr);
|
||||
me.me_x = x - 1;
|
||||
me.me_y = y - tc->get_y() - 1;
|
||||
|
||||
|
@ -1116,36 +1098,6 @@ static void handle_key(int ch) {
|
|||
if (lnav_data.ld_mode == LNM_PAGING) {
|
||||
auto top_tc = lnav_data.ld_view_stack.top();
|
||||
|
||||
if (top_tc && (*top_tc)->handle_key(ch)) {
|
||||
return;
|
||||
}
|
||||
|
||||
char keyseq[16];
|
||||
|
||||
snprintf(keyseq, sizeof(keyseq), "x%02x", ch);
|
||||
|
||||
key_map &km = lnav_config.lc_ui_keymaps[lnav_config.lc_ui_keymap];
|
||||
|
||||
const auto &iter = km.km_seq_to_cmd.find(keyseq);
|
||||
if (iter != km.km_seq_to_cmd.end()) {
|
||||
vector<logline_value> values;
|
||||
exec_context ec(&values, key_sql_callback, pipe_callback);
|
||||
auto &var_stack = ec.ec_local_vars;
|
||||
string result;
|
||||
|
||||
ec.ec_global_vars = lnav_data.ld_exec_context.ec_global_vars;
|
||||
var_stack.push(map<string, string>());
|
||||
auto &vars = var_stack.top();
|
||||
vars["keyseq"] = keyseq;
|
||||
for (const string &cmd : iter->second) {
|
||||
log_debug("executing key sequence x%02x: %s",
|
||||
keyseq, cmd.c_str());
|
||||
result = execute_any(ec, cmd);
|
||||
}
|
||||
|
||||
lnav_data.ld_rl_view->set_value(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch (ch) {
|
||||
|
@ -1180,6 +1132,57 @@ static void handle_key(int ch) {
|
|||
}
|
||||
}
|
||||
|
||||
static input_dispatcher::escape_match_t match_escape_seq(const char *escape_buffer)
|
||||
{
|
||||
if (lnav_data.ld_mode != LNM_PAGING) {
|
||||
return input_dispatcher::escape_match_t::NONE;
|
||||
}
|
||||
|
||||
char keyseq[32] = "";
|
||||
|
||||
for (size_t lpc = 0; escape_buffer[lpc]; lpc++) {
|
||||
snprintf(keyseq + strlen(keyseq), sizeof(keyseq) - strlen(keyseq),
|
||||
"x%02x",
|
||||
escape_buffer[lpc]);
|
||||
}
|
||||
|
||||
key_map &km = lnav_config.lc_ui_keymaps[lnav_config.lc_ui_keymap];
|
||||
auto iter = km.km_seq_to_cmd.find(keyseq);
|
||||
if (iter != km.km_seq_to_cmd.end()) {
|
||||
return input_dispatcher::escape_match_t::FULL;
|
||||
}
|
||||
|
||||
auto lb = km.km_seq_to_cmd.lower_bound(keyseq);
|
||||
|
||||
if (lb == km.km_seq_to_cmd.end()) {
|
||||
return input_dispatcher::escape_match_t::NONE;
|
||||
}
|
||||
|
||||
auto ub = km.km_seq_to_cmd.upper_bound(keyseq);
|
||||
auto longest = max_element(lb, ub, [] (auto l, auto r) {
|
||||
return l.first.size() < r.first.size();
|
||||
});
|
||||
|
||||
if (strlen(escape_buffer) < longest->first.size()) {
|
||||
return input_dispatcher::escape_match_t::PARTIAL;
|
||||
}
|
||||
|
||||
return input_dispatcher::escape_match_t::NONE;
|
||||
}
|
||||
|
||||
static void handle_escape_seq(const char *escape_buffer)
|
||||
{
|
||||
char keyseq[32] = "";
|
||||
|
||||
for (size_t lpc = 0; escape_buffer[lpc]; lpc++) {
|
||||
snprintf(keyseq + strlen(keyseq), sizeof(keyseq) - strlen(keyseq),
|
||||
"x%02x",
|
||||
escape_buffer[lpc]);
|
||||
}
|
||||
|
||||
handle_keyseq(keyseq);
|
||||
}
|
||||
|
||||
void update_hits(void *dummy, textview_curses *tc)
|
||||
{
|
||||
auto top_tc = lnav_data.ld_view_stack.top();
|
||||
|
@ -1462,12 +1465,18 @@ static void looper()
|
|||
sb.invoke(*lnav_data.ld_view_stack.top());
|
||||
vsb.invoke(*lnav_data.ld_view_stack.top());
|
||||
|
||||
{
|
||||
input_dispatcher &id = lnav_data.ld_input_dispatcher;
|
||||
|
||||
id.id_escape_matcher = match_escape_seq;
|
||||
id.id_escape_handler = handle_escape_seq;
|
||||
id.id_key_handler = handle_key;
|
||||
id.id_mouse_handler = bind(&xterm_mouse::handle_mouse, &lnav_data.ld_mouse);
|
||||
}
|
||||
|
||||
bool session_loaded = false;
|
||||
ui_periodic_timer &timer = ui_periodic_timer::singleton();
|
||||
struct timeval current_time;
|
||||
size_t escape_index = 0;
|
||||
char escape_buffer[32];
|
||||
struct timeval escape_start_time;
|
||||
|
||||
static sig_atomic_t index_counter;
|
||||
|
||||
|
@ -1518,22 +1527,13 @@ static void looper()
|
|||
tc.update_poll_set(pollfds);
|
||||
}
|
||||
|
||||
if (escape_index > 0) {
|
||||
if (lnav_data.ld_input_dispatcher.in_escape()) {
|
||||
to.tv_usec = 15000;
|
||||
}
|
||||
rc = poll(&pollfds[0], pollfds.size(), to.tv_usec / 1000);
|
||||
|
||||
if (escape_index == 1) {
|
||||
struct timeval diff;
|
||||
|
||||
gettimeofday(¤t_time, nullptr);
|
||||
|
||||
timersub(¤t_time, &escape_start_time, &diff);
|
||||
if (diff.tv_sec > 0 || diff.tv_usec > (10000)) {
|
||||
handle_key(KEY_CTRL_RBRACKET);
|
||||
escape_index = 0;
|
||||
}
|
||||
}
|
||||
gettimeofday(¤t_time, nullptr);
|
||||
lnav_data.ld_input_dispatcher.poll(current_time);
|
||||
|
||||
if (rc < 0) {
|
||||
switch (errno) {
|
||||
|
@ -1554,53 +1554,12 @@ static void looper()
|
|||
while ((ch = getch()) != ERR) {
|
||||
alerter::singleton().new_input(ch);
|
||||
|
||||
/* Check to make sure there is enough space for a
|
||||
* character and a string terminator.
|
||||
*/
|
||||
if (escape_index >= sizeof(escape_buffer) - 2) {
|
||||
escape_index = 0;
|
||||
}
|
||||
else if (escape_index > 0) {
|
||||
escape_buffer[escape_index++] = ch;
|
||||
escape_buffer[escape_index] = '\0';
|
||||
|
||||
if (strcmp("\x1b[", escape_buffer) == 0) {
|
||||
lnav_data.ld_mouse.handle_mouse(ch);
|
||||
}
|
||||
else {
|
||||
for (size_t lpc = 0; lpc < escape_index; lpc++) {
|
||||
handle_key(escape_buffer[lpc]);
|
||||
}
|
||||
}
|
||||
escape_index = 0;
|
||||
continue;
|
||||
}
|
||||
lnav_data.ld_input_dispatcher.new_input(current_time, ch);
|
||||
|
||||
lnav_data.ld_view_stack.top() | [ch] (auto tc) {
|
||||
lnav_data.ld_key_repeat_history.update(ch, tc->get_top());
|
||||
};
|
||||
|
||||
switch (ch) {
|
||||
case CTRL('d'):
|
||||
case KEY_RESIZE:
|
||||
break;
|
||||
|
||||
case KEY_ESCAPE:
|
||||
escape_index = 0;
|
||||
escape_buffer[escape_index++] = ch;
|
||||
escape_buffer[escape_index] = '\0';
|
||||
escape_start_time = current_time;
|
||||
break;
|
||||
|
||||
case KEY_MOUSE:
|
||||
lnav_data.ld_mouse.handle_mouse(ch);
|
||||
break;
|
||||
|
||||
default:
|
||||
handle_key(ch);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!lnav_data.ld_looping) {
|
||||
// No reason to keep processing input after the
|
||||
// user has quit. The view stack will also be
|
||||
|
|
|
@ -64,6 +64,7 @@
|
|||
#include "spectro_source.hh"
|
||||
#include "command_executor.hh"
|
||||
#include "plain_text_source.hh"
|
||||
#include "input_dispatcher.hh"
|
||||
#include "filter_sub_source.hh"
|
||||
#include "filter_status_source.hh"
|
||||
#include "preview_status_source.hh"
|
||||
|
@ -297,6 +298,7 @@ struct _lnav_data {
|
|||
term_extra ld_term_extra;
|
||||
|
||||
input_state_tracker ld_input_state;
|
||||
input_dispatcher ld_input_dispatcher;
|
||||
|
||||
curl_looper ld_curl_looper;
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ static string refresh_pt_search()
|
|||
}
|
||||
|
||||
#ifdef HAVE_LIBCURL
|
||||
for (auto lf : lnav_data.ld_files) {
|
||||
for (const auto &lf : lnav_data.ld_files) {
|
||||
if (startswith(lf->get_filename(), "pt:")) {
|
||||
lf->close();
|
||||
}
|
||||
|
@ -270,6 +270,7 @@ static string com_goto(exec_context &ec, string cmdline, vector<string> &args)
|
|||
struct timeval tv;
|
||||
struct exttm tm;
|
||||
float value;
|
||||
nonstd::optional<vis_line_t> dst_vl;
|
||||
|
||||
if (rt.parse(all_args, pe)) {
|
||||
if (ttt != nullptr) {
|
||||
|
@ -293,16 +294,12 @@ static string com_goto(exec_context &ec, string cmdline, vector<string> &args)
|
|||
}
|
||||
} while (!done);
|
||||
|
||||
if (ec.ec_dry_run) {
|
||||
retval = "info: will move to line " + to_string((int) vl);
|
||||
} else {
|
||||
tc->set_top(vl);
|
||||
retval = "";
|
||||
if (!rt.is_absolute() && lnav_data.ld_rl_view != nullptr) {
|
||||
lnav_data.ld_rl_view->set_alt_value(
|
||||
HELP_MSG_2(r, R,
|
||||
"to move forward/backward the same amount of time"));
|
||||
}
|
||||
dst_vl = vl;
|
||||
|
||||
if (!ec.ec_dry_run && !rt.is_absolute() && lnav_data.ld_rl_view != nullptr) {
|
||||
lnav_data.ld_rl_view->set_alt_value(
|
||||
HELP_MSG_2(r, R,
|
||||
"to move forward/backward the same amount of time"));
|
||||
}
|
||||
} else {
|
||||
retval = "error: relative time values only work in a time-indexed view";
|
||||
|
@ -311,15 +308,7 @@ static string com_goto(exec_context &ec, string cmdline, vector<string> &args)
|
|||
else if (dts.scan(args[1].c_str(), args[1].size(), nullptr, &tm, tv) !=
|
||||
nullptr) {
|
||||
if (ttt != nullptr) {
|
||||
vis_line_t vl;
|
||||
|
||||
vl = vis_line_t(ttt->row_for_time(tv));
|
||||
if (ec.ec_dry_run) {
|
||||
retval = "info: will move to line " + to_string((int) vl);
|
||||
} else {
|
||||
tc->set_top(vl);
|
||||
retval = "";
|
||||
}
|
||||
dst_vl = vis_line_t(ttt->row_for_time(tv));
|
||||
}
|
||||
else {
|
||||
retval = "error: time values only work in a time-indexed view";
|
||||
|
@ -337,14 +326,22 @@ static string com_goto(exec_context &ec, string cmdline, vector<string> &args)
|
|||
line_number = tc->get_inner_height() + line_number;
|
||||
}
|
||||
}
|
||||
|
||||
dst_vl = vis_line_t(line_number);
|
||||
}
|
||||
|
||||
dst_vl | [&ec, tc, &retval] (auto new_top) {
|
||||
if (ec.ec_dry_run) {
|
||||
retval = "info: will move to line " + to_string(line_number);
|
||||
retval = "info: will move to line " + to_string((int) new_top);
|
||||
} else {
|
||||
tc->set_top(vis_line_t(line_number));
|
||||
tc->get_sub_source()->get_location_history() | [new_top] (auto lh) {
|
||||
lh->loc_history_append(new_top);
|
||||
};
|
||||
tc->set_top(new_top);
|
||||
|
||||
retval = "";
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return retval;
|
||||
|
@ -435,6 +432,27 @@ static string com_goto_mark(exec_context &ec, string cmdline, vector<string> &ar
|
|||
return retval;
|
||||
}
|
||||
|
||||
static string com_goto_location(exec_context &ec, string cmdline, vector<string> &args)
|
||||
{
|
||||
string retval;
|
||||
|
||||
if (args.empty()) {
|
||||
}
|
||||
else if (!ec.ec_dry_run) {
|
||||
lnav_data.ld_view_stack.top() | [&args] (auto tc) {
|
||||
tc->get_sub_source()->get_location_history() | [tc, &args] (auto lh) {
|
||||
return args[0] == "prev-location" ?
|
||||
lh->loc_history_back(tc->get_top()) :
|
||||
lh->loc_history_forward(tc->get_top());
|
||||
} | [tc] (auto new_top) {
|
||||
tc->set_top(new_top);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static bool csv_needs_quoting(const string &str)
|
||||
{
|
||||
return (str.find_first_of(",\"") != string::npos);
|
||||
|
@ -3720,6 +3738,22 @@ readline_context::command_t STD_COMMANDS[] = {
|
|||
.with_example({"error"})
|
||||
.with_tags({"bookmarks", "navigation"})
|
||||
},
|
||||
{
|
||||
"next-location",
|
||||
com_goto_location,
|
||||
|
||||
help_text(":next-location")
|
||||
.with_summary("Move to the next position in the location history")
|
||||
.with_tags({"navigation"})
|
||||
},
|
||||
{
|
||||
"prev-location",
|
||||
com_goto_location,
|
||||
|
||||
help_text(":prev-location")
|
||||
.with_summary("Move to the previous position in the location history")
|
||||
.with_tags({"navigation"})
|
||||
},
|
||||
{
|
||||
"help",
|
||||
com_help,
|
||||
|
|
|
@ -104,7 +104,8 @@ logfile_sub_source::logfile_sub_source()
|
|||
lss_marked_only(false),
|
||||
lss_index_delegate(NULL),
|
||||
lss_longest_line(0),
|
||||
lss_meta_grepper(*this)
|
||||
lss_meta_grepper(*this),
|
||||
lss_location_history(*this)
|
||||
{
|
||||
this->tss_supports_filtering = true;
|
||||
this->clear_line_size_cache();
|
||||
|
@ -785,7 +786,7 @@ void logfile_sub_source::text_update_marks(vis_bookmarks &bm)
|
|||
bm[lss_user_mark.first].insert_once(vl);
|
||||
|
||||
if (lss_user_mark.first == &textview_curses::BM_USER) {
|
||||
logfile::iterator ll = lf->begin() + cl;
|
||||
auto ll = lf->begin() + cl;
|
||||
|
||||
ll->set_mark(true);
|
||||
}
|
||||
|
@ -920,3 +921,63 @@ logfile_sub_source::get_grepper()
|
|||
(grep_proc_sink<vis_line_t> *) &this->lss_meta_grepper
|
||||
);
|
||||
}
|
||||
|
||||
void log_location_history::loc_history_append(vis_line_t top)
|
||||
{
|
||||
content_line_t cl = this->llh_log_source.at(top);
|
||||
|
||||
auto iter = this->llh_history.begin();
|
||||
iter += this->llh_history.size() - this->lh_history_position;
|
||||
this->llh_history.erase_from(iter);
|
||||
this->lh_history_position = 0;
|
||||
this->llh_history.push_back(cl);
|
||||
}
|
||||
|
||||
nonstd::optional<vis_line_t> log_location_history::loc_history_back(vis_line_t current_top)
|
||||
{
|
||||
while (this->lh_history_position < this->llh_history.size()) {
|
||||
auto iter = this->llh_history.rbegin();
|
||||
|
||||
auto vis_for_pos = this->llh_log_source.find_from_content(*iter);
|
||||
|
||||
if (this->lh_history_position == 0 && vis_for_pos != current_top) {
|
||||
return vis_for_pos;
|
||||
}
|
||||
|
||||
if ((this->lh_history_position + 1) >= this->llh_history.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
this->lh_history_position += 1;
|
||||
|
||||
iter += this->lh_history_position;
|
||||
|
||||
vis_for_pos = this->llh_log_source.find_from_content(*iter);
|
||||
|
||||
if (vis_for_pos) {
|
||||
return vis_for_pos;
|
||||
}
|
||||
}
|
||||
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
nonstd::optional<vis_line_t>
|
||||
log_location_history::loc_history_forward(vis_line_t current_top)
|
||||
{
|
||||
while (this->lh_history_position > 0) {
|
||||
this->lh_history_position -= 1;
|
||||
|
||||
auto iter = this->llh_history.rbegin();
|
||||
|
||||
iter += this->lh_history_position;
|
||||
|
||||
auto vis_for_pos = this->llh_log_source.find_from_content(*iter);
|
||||
|
||||
if (vis_for_pos) {
|
||||
return vis_for_pos;
|
||||
}
|
||||
}
|
||||
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
|
|
@ -97,6 +97,30 @@ protected:
|
|||
pcrepp pf_pcre;
|
||||
};
|
||||
|
||||
class log_location_history : public location_history {
|
||||
public:
|
||||
log_location_history(logfile_sub_source &lss)
|
||||
: llh_history(std::begin(this->llh_backing),
|
||||
std::end(this->llh_backing)),
|
||||
llh_log_source(lss) {
|
||||
}
|
||||
|
||||
~log_location_history() override = default;
|
||||
|
||||
void loc_history_append(vis_line_t top) override;
|
||||
|
||||
nonstd::optional<vis_line_t>
|
||||
loc_history_back(vis_line_t current_top) override;
|
||||
|
||||
nonstd::optional<vis_line_t>
|
||||
loc_history_forward(vis_line_t current_top) override;
|
||||
|
||||
private:
|
||||
nonstd::ring_span<content_line_t> llh_history;
|
||||
logfile_sub_source &llh_log_source;
|
||||
content_line_t llh_backing[MAX_SIZE];
|
||||
};
|
||||
|
||||
/**
|
||||
* Delegate class that merges the contents of multiple log files into a single
|
||||
* source of data for a text view.
|
||||
|
@ -478,6 +502,35 @@ public:
|
|||
return this->find_from_time(tv);
|
||||
};
|
||||
|
||||
nonstd::optional<vis_line_t> find_from_content(content_line_t cl) {
|
||||
content_line_t line = cl;
|
||||
std::shared_ptr<logfile> lf = this->find(line);
|
||||
|
||||
if (lf != nullptr) {
|
||||
auto ll_iter = lf->begin() + line;
|
||||
auto &ll = *ll_iter;
|
||||
vis_line_t vis_start = this->find_from_time(ll.get_timeval());
|
||||
|
||||
while (vis_start < this->text_line_count()) {
|
||||
content_line_t guess_cl = this->at(vis_start);
|
||||
|
||||
if (cl == guess_cl) {
|
||||
return vis_start;
|
||||
}
|
||||
|
||||
auto guess_line = this->find_line(guess_cl);
|
||||
|
||||
if (!guess_line || ll < *guess_line) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
++vis_start;
|
||||
}
|
||||
}
|
||||
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
struct timeval time_for_row(int row) {
|
||||
return this->find_line(this->at(vis_line_t(row)))->get_timeval();
|
||||
};
|
||||
|
@ -674,6 +727,10 @@ public:
|
|||
nonstd::optional<std::pair<grep_proc_source<vis_line_t> *, grep_proc_sink<vis_line_t> *>>
|
||||
get_grepper();
|
||||
|
||||
nonstd::optional<location_history *> get_location_history() {
|
||||
return &this->lss_location_history;
|
||||
};
|
||||
|
||||
static const uint64_t MAX_CONTENT_LINES = (1ULL << 40) - 1;
|
||||
static const uint64_t MAX_LINES_PER_FILE = 256 * 1024 * 1024;
|
||||
static const uint64_t MAX_FILES = (
|
||||
|
@ -847,6 +904,7 @@ private:
|
|||
index_delegate *lss_index_delegate;
|
||||
size_t lss_longest_line;
|
||||
meta_grepper lss_meta_grepper;
|
||||
log_location_history lss_location_history;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
#include "textview_curses.hh"
|
||||
|
||||
class plain_text_source
|
||||
: public text_sub_source {
|
||||
: public text_sub_source, public vis_location_history {
|
||||
public:
|
||||
plain_text_source()
|
||||
: tds_text_format(TF_UNKNOWN), tds_longest_line(0) {
|
||||
|
@ -133,6 +133,11 @@ public:
|
|||
return *this;
|
||||
};
|
||||
|
||||
nonstd::optional<location_history *> get_location_history()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t compute_longest_line() {
|
||||
size_t retval = 0;
|
||||
|
|
|
@ -900,9 +900,6 @@ void readline_curses::handle_key(int ch)
|
|||
if (write(this->rc_pty[RCF_MASTER], bch, len) == -1) {
|
||||
perror("handle_key: write failed");
|
||||
}
|
||||
if (ch == '\t' || ch == '\r') {
|
||||
// this->vc_past_lines.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void readline_curses::focus(int context, const char *prompt, const char *initial)
|
||||
|
|
|
@ -36,7 +36,8 @@
|
|||
#include "textview_curses.hh"
|
||||
#include "filter_observer.hh"
|
||||
|
||||
class textfile_sub_source : public text_sub_source {
|
||||
class textfile_sub_source
|
||||
: public text_sub_source, public vis_location_history {
|
||||
public:
|
||||
typedef std::list<std::shared_ptr<logfile>>::iterator file_iterator;
|
||||
|
||||
|
@ -269,6 +270,11 @@ public:
|
|||
return this->tss_files.front()->get_text_format();
|
||||
}
|
||||
|
||||
nonstd::optional<location_history *> get_location_history()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
private:
|
||||
void detach_observer(std::shared_ptr<logfile> lf) {
|
||||
line_filter_observer *lfo = (line_filter_observer *) lf->get_logline_observer();
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "ring_span.hh"
|
||||
#include "grep_proc.hh"
|
||||
#include "bookmarks.hh"
|
||||
#include "listview_curses.hh"
|
||||
|
@ -145,7 +146,7 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
void add_line(logfile_filter_state &lfs, const logfile::const_iterator ll, shared_buffer_ref &line);
|
||||
void add_line(logfile_filter_state &lfs, logfile::const_iterator ll, shared_buffer_ref &line);
|
||||
|
||||
void end_of_message(logfile_filter_state &lfs) {
|
||||
uint32_t mask = 0;
|
||||
|
@ -339,6 +340,23 @@ protected:
|
|||
struct timeval ttt_top_time{0, 0};
|
||||
};
|
||||
|
||||
class location_history {
|
||||
public:
|
||||
virtual ~location_history() = default;
|
||||
|
||||
virtual void loc_history_append(vis_line_t top) = 0;
|
||||
|
||||
virtual nonstd::optional<vis_line_t>
|
||||
loc_history_back(vis_line_t current_top) = 0;
|
||||
|
||||
virtual nonstd::optional<vis_line_t>
|
||||
loc_history_forward(vis_line_t current_top) = 0;
|
||||
|
||||
const static int MAX_SIZE = 100;
|
||||
protected:
|
||||
int lh_history_position{0};
|
||||
};
|
||||
|
||||
/**
|
||||
* Source for the text to be shown in a textview_curses view.
|
||||
*/
|
||||
|
@ -455,12 +473,73 @@ public:
|
|||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
virtual nonstd::optional<location_history *> get_location_history() {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
bool tss_supports_filtering{false};
|
||||
protected:
|
||||
textview_curses *tss_view;
|
||||
textview_curses *tss_view{nullptr};
|
||||
filter_stack tss_filters;
|
||||
};
|
||||
|
||||
class vis_location_history : public location_history {
|
||||
public:
|
||||
vis_location_history()
|
||||
: vlh_history(std::begin(this->vlh_backing), std::end(this->vlh_backing))
|
||||
{
|
||||
}
|
||||
|
||||
void loc_history_append(vis_line_t top) override {
|
||||
auto iter = this->vlh_history.begin();
|
||||
iter += this->vlh_history.size() - this->lh_history_position;
|
||||
this->vlh_history.erase_from(iter);
|
||||
this->lh_history_position = 0;
|
||||
this->vlh_history.push_back(top);
|
||||
}
|
||||
|
||||
nonstd::optional<vis_line_t>
|
||||
loc_history_back(vis_line_t current_top) override {
|
||||
if (this->lh_history_position == 0) {
|
||||
vis_line_t history_top = this->current_position();
|
||||
if (history_top != current_top) {
|
||||
return history_top;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->lh_history_position + 1 >= this->vlh_history.size()) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
this->lh_history_position += 1;
|
||||
|
||||
return this->current_position();
|
||||
}
|
||||
|
||||
nonstd::optional<vis_line_t>
|
||||
loc_history_forward(vis_line_t current_top) override {
|
||||
if (this->lh_history_position == 0) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
this->lh_history_position -= 1;
|
||||
|
||||
return this->current_position();
|
||||
}
|
||||
|
||||
nonstd::ring_span<vis_line_t> vlh_history;
|
||||
private:
|
||||
vis_line_t current_position() {
|
||||
auto iter = this->vlh_history.rbegin();
|
||||
|
||||
iter += this->lh_history_position;
|
||||
|
||||
return *iter;
|
||||
}
|
||||
|
||||
vis_line_t vlh_backing[MAX_SIZE];
|
||||
};
|
||||
|
||||
class text_delegate {
|
||||
public:
|
||||
virtual ~text_delegate() { };
|
||||
|
|
|
@ -70,8 +70,6 @@
|
|||
#define KEY_CTRL_P 16
|
||||
#define KEY_CTRL_R 18
|
||||
#define KEY_CTRL_W 23
|
||||
#define KEY_ESCAPE 0x1b
|
||||
#define KEY_CTRL_RBRACKET 0x1d
|
||||
|
||||
class view_curses;
|
||||
|
||||
|
|
|
@ -158,7 +158,7 @@ public:
|
|||
* Handle a KEY_MOUSE character from ncurses.
|
||||
* @param ch unused
|
||||
*/
|
||||
void handle_mouse(int ch_unused)
|
||||
void handle_mouse()
|
||||
{
|
||||
bool release = false;
|
||||
int ch;
|
||||
|
|
|
@ -241,6 +241,12 @@ Spatial Navigation
|
|||
five seconds later, the last message will be highlighted
|
||||
as a slow down.
|
||||
|
||||
{/} Move to the previous/next location in history. Whenever
|
||||
you jump to a new location in the view, the location will
|
||||
be added to the history. The history is not updated when
|
||||
using only the arrow keys.
|
||||
|
||||
|
||||
Chronological Navigation
|
||||
------------------------
|
||||
|
||||
|
|
|
@ -1,5 +1,29 @@
|
|||
#! /bin/bash
|
||||
|
||||
run_test ${lnav_test} -n -d /tmp/lnav.err \
|
||||
-c ":goto 0" \
|
||||
-c ":next-mark error" \
|
||||
-c ":prev-location" \
|
||||
${test_dir}/logfile_access_log.0
|
||||
|
||||
check_output "prev-location is not working" <<EOF
|
||||
192.168.202.254 - - [20/Jul/2009:22:59:26 +0000] "GET /vmw/cgi/tramp HTTP/1.0" 200 134 "-" "gPXE/0.9.7"
|
||||
192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkboot.gz HTTP/1.0" 404 46210 "-" "gPXE/0.9.7"
|
||||
192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkernel.gz HTTP/1.0" 200 78929 "-" "gPXE/0.9.7"
|
||||
EOF
|
||||
|
||||
run_test ${lnav_test} -n -d /tmp/lnav.err \
|
||||
-c ":goto 0" \
|
||||
-c ":next-mark error" \
|
||||
-c ":prev-location" \
|
||||
-c ":next-location" \
|
||||
${test_dir}/logfile_access_log.0
|
||||
|
||||
check_output "next-location is not working" <<EOF
|
||||
192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkboot.gz HTTP/1.0" 404 46210 "-" "gPXE/0.9.7"
|
||||
192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkernel.gz HTTP/1.0" 200 78929 "-" "gPXE/0.9.7"
|
||||
EOF
|
||||
|
||||
run_test ${lnav_test} -n -d /tmp/lnav.err \
|
||||
-c ":filter-in vmk" \
|
||||
-c ":rebuild" \
|
||||
|
|
Loading…
Reference in New Issue