1 /**
2 * Copyright (c) 2015, 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 ON
25 * 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 "config.h"
31
32 #include <regex>
33 #include <string>
34
35 #include "base/isc.hh"
36 #include "tailer/tailer.looper.hh"
37 #include "lnav.hh"
38 #include "sql_util.hh"
39 #include "data_parser.hh"
40 #include "sysclip.hh"
41 #include "yajlpp/yajlpp_def.hh"
42 #include "lnav_config.hh"
43 #include "sqlite-extension-func.hh"
44 #include "service_tags.hh"
45 #include "session_data.hh"
46
47 #include "readline_possibilities.hh"
48
49 using namespace std;
50
handle_collation_list(void * ptr,int ncols,char ** colvalues,char ** colnames)51 static int handle_collation_list(void *ptr,
52 int ncols,
53 char **colvalues,
54 char **colnames)
55 {
56 if (lnav_data.ld_rl_view != nullptr) {
57 lnav_data.ld_rl_view->add_possibility(LNM_SQL, "*", colvalues[1]);
58 }
59
60 return 0;
61 }
62
handle_db_list(void * ptr,int ncols,char ** colvalues,char ** colnames)63 static int handle_db_list(void *ptr,
64 int ncols,
65 char **colvalues,
66 char **colnames)
67 {
68 if (lnav_data.ld_rl_view != nullptr) {
69 lnav_data.ld_rl_view->add_possibility(LNM_SQL, "*", colvalues[1]);
70 }
71
72 return 0;
73 }
74
handle_table_list(void * ptr,int ncols,char ** colvalues,char ** colnames)75 static int handle_table_list(void *ptr,
76 int ncols,
77 char **colvalues,
78 char **colnames)
79 {
80 if (lnav_data.ld_rl_view != nullptr) {
81 string table_name = colvalues[0];
82
83 if (sqlite_function_help.count(table_name) == 0) {
84 lnav_data.ld_rl_view->add_possibility(LNM_SQL, "*", colvalues[0]);
85 }
86
87 lnav_data.ld_table_ddl[colvalues[0]] = colvalues[1];
88 }
89
90 return 0;
91 }
92
handle_table_info(void * ptr,int ncols,char ** colvalues,char ** colnames)93 static int handle_table_info(void *ptr,
94 int ncols,
95 char **colvalues,
96 char **colnames)
97 {
98 if (lnav_data.ld_rl_view != nullptr) {
99 auto_mem<char, sqlite3_free> quoted_name;
100
101 quoted_name = sql_quote_ident(colvalues[1]);
102 lnav_data.ld_rl_view->add_possibility(LNM_SQL, "*",
103 string(quoted_name));
104 }
105 if (strcmp(colvalues[5], "1") == 0) {
106 lnav_data.ld_db_key_names.emplace_back(colvalues[1]);
107 }
108 return 0;
109 }
110
handle_foreign_key_list(void * ptr,int ncols,char ** colvalues,char ** colnames)111 static int handle_foreign_key_list(void *ptr,
112 int ncols,
113 char **colvalues,
114 char **colnames)
115 {
116 lnav_data.ld_db_key_names.emplace_back(colvalues[3]);
117 lnav_data.ld_db_key_names.emplace_back(colvalues[4]);
118 return 0;
119 }
120
121 struct sqlite_metadata_callbacks lnav_sql_meta_callbacks = {
122 handle_collation_list,
123 handle_db_list,
124 handle_table_list,
125 handle_table_info,
126 handle_foreign_key_list,
127 };
128
add_text_possibilities(readline_curses * rlc,int context,const string & type,const std::string & str)129 static void add_text_possibilities(readline_curses *rlc, int context, const string &type, const std::string &str)
130 {
131 static const std::regex re_escape(R"(([.\^$*+?()\[\]{}\\|]))");
132 static const std::regex re_escape_no_dot(R"(([\^$*+?()\[\]{}\\|]))");
133
134 pcre_context_static<30> pc;
135 data_scanner ds(str);
136 data_token_t dt;
137
138 while (ds.tokenize2(pc, dt)) {
139 if (pc[0]->length() < 4) {
140 continue;
141 }
142
143 switch (dt) {
144 case DT_DATE:
145 case DT_TIME:
146 case DT_WHITE:
147 continue;
148 default:
149 break;
150 }
151
152 switch (context) {
153 case LNM_SQL: {
154 string token_value = ds.get_input().get_substr(pc.all());
155 auto_mem<char, sqlite3_free> quoted_token;
156
157 quoted_token = sqlite3_mprintf("%Q", token_value.c_str());
158 rlc->add_possibility(context, type, std::string(quoted_token));
159 break;
160 }
161 default: {
162 string token_value, token_value_no_dot;
163
164 token_value_no_dot = token_value =
165 ds.get_input().get_substr(pc.all());
166 token_value = std::regex_replace(token_value, re_escape, R"(\\\1)");
167 token_value_no_dot = std::regex_replace(token_value_no_dot, re_escape_no_dot, R"(\\\1)");
168 rlc->add_possibility(context, type, token_value);
169 if (token_value != token_value_no_dot) {
170 rlc->add_possibility(context, type, token_value_no_dot);
171 }
172 break;
173 }
174 }
175
176 switch (dt) {
177 case DT_QUOTED_STRING:
178 add_text_possibilities(rlc, context, type, ds.get_input().get_substr(pc[0]));
179 break;
180 default:
181 break;
182 }
183 }
184 }
185
add_view_text_possibilities(readline_curses * rlc,int context,const string & type,textview_curses * tc)186 void add_view_text_possibilities(readline_curses *rlc, int context, const string &type, textview_curses *tc)
187 {
188 text_sub_source *tss = tc->get_sub_source();
189
190 rlc->clear_possibilities(context, type);
191
192 for (vis_line_t curr_line = tc->get_top();
193 curr_line <= tc->get_bottom();
194 ++curr_line) {
195 string line;
196
197 tss->text_value_for_line(*tc, curr_line, line, text_sub_source::RF_RAW);
198
199 add_text_possibilities(rlc, context, type, line);
200 }
201
202 rlc->add_possibility(context, type, bookmark_metadata::KNOWN_TAGS);
203 }
204
add_filter_expr_possibilities(readline_curses * rlc,int context,const std::string & type)205 void add_filter_expr_possibilities(readline_curses *rlc, int context, const std::string &type)
206 {
207 static const char *BUILTIN_VARS[] = {
208 ":log_level",
209 ":log_time",
210 ":log_time_msecs",
211 ":log_mark",
212 ":log_comment",
213 ":log_tags",
214 ":log_path",
215 ":log_text",
216 ":log_body",
217 ":log_raw_text",
218 };
219
220 textview_curses *tc = *lnav_data.ld_view_stack.top();
221 auto& lss = lnav_data.ld_log_source;
222 auto bottom = tc->get_bottom();
223
224 rlc->clear_possibilities(context, type);
225 rlc->add_possibility(context, type,
226 std::begin(BUILTIN_VARS),
227 std::end(BUILTIN_VARS));
228 for (auto curr_line = tc->get_top(); curr_line < bottom; ++curr_line) {
229 auto cl = lss.at(curr_line);
230 auto lf = lss.find(cl);
231 auto ll = lf->begin() + cl;
232
233 if (!ll->is_message()) {
234 continue;
235 }
236
237 auto format = lf->get_format();
238 shared_buffer_ref sbr;
239 string_attrs_t sa;
240 vector<logline_value> values;
241
242 lf->read_full_message(ll, sbr);
243 format->annotate(cl, sbr, sa, values);
244 for (auto& lv : values) {
245 if (!lv.lv_meta.lvm_struct_name.empty()) {
246 continue;
247 }
248
249 auto_mem<char> ident(sqlite3_free);
250
251 ident = sql_quote_ident(lv.lv_meta.lvm_name.get());
252 auto bound_name = fmt::format(":{}", ident);
253 rlc->add_possibility(context, type, bound_name);
254 switch (lv.lv_meta.lvm_kind) {
255 case value_kind_t::VALUE_BOOLEAN:
256 case value_kind_t::VALUE_FLOAT:
257 case value_kind_t::VALUE_NULL:
258 break;
259 case value_kind_t::VALUE_INTEGER:
260 rlc->add_possibility(
261 context, type,
262 std::to_string(lv.lv_value.i));
263 break;
264 default: {
265 auto_mem<char, sqlite3_free> str;
266
267 str = sqlite3_mprintf("%.*Q", lv.text_length(), lv.text_value());
268 rlc->add_possibility(context, type, string(str.in()));
269 break;
270 }
271 }
272 }
273 }
274 rlc->add_possibility(context, type,
275 std::begin(sql_keywords),
276 std::end(sql_keywords));
277 rlc->add_possibility(context, type, sql_function_names);
278 for (int lpc = 0; sqlite_registration_funcs[lpc]; lpc++) {
279 struct FuncDef *basic_funcs;
280 struct FuncDefAgg *agg_funcs;
281
282 sqlite_registration_funcs[lpc](&basic_funcs, &agg_funcs);
283 for (int lpc2 = 0; basic_funcs && basic_funcs[lpc2].zName; lpc2++) {
284 const FuncDef &func_def = basic_funcs[lpc2];
285
286 rlc->add_possibility(
287 context,
288 type,
289 string(func_def.zName) + (func_def.nArg ? "(" : "()"));
290 }
291 for (int lpc2 = 0; agg_funcs && agg_funcs[lpc2].zName; lpc2++) {
292 const FuncDefAgg &func_def = agg_funcs[lpc2];
293
294 rlc->add_possibility(
295 context,
296 type,
297 string(func_def.zName) + (func_def.nArg ? "(" : "()"));
298 }
299 }
300 }
301
add_env_possibilities(int context)302 void add_env_possibilities(int context)
303 {
304 extern char **environ;
305 readline_curses *rlc = lnav_data.ld_rl_view;
306
307 for (char **var = environ; *var != nullptr; var++) {
308 rlc->add_possibility(context, "*", "$" + string(*var, strchr(*var, '=')));
309 }
310
311 exec_context &ec = lnav_data.ld_exec_context;
312
313 if (!ec.ec_local_vars.empty()) {
314 for (const auto &iter : ec.ec_local_vars.top()) {
315 rlc->add_possibility(context, "*", "$" + iter.first);
316 }
317 }
318
319 for (const auto &iter : ec.ec_global_vars) {
320 rlc->add_possibility(context, "*", "$" + iter.first);
321 }
322
323 if (lnav_data.ld_window) {
324 rlc->add_possibility(context, "*", "$LINES");
325 rlc->add_possibility(context, "*", "$COLS");
326 }
327 }
328
add_filter_possibilities(textview_curses * tc)329 void add_filter_possibilities(textview_curses *tc)
330 {
331 readline_curses *rc = lnav_data.ld_rl_view;
332 text_sub_source *tss = tc->get_sub_source();
333 filter_stack &fs = tss->get_filters();
334
335 rc->clear_possibilities(LNM_COMMAND, "all-filters");
336 rc->clear_possibilities(LNM_COMMAND, "disabled-filter");
337 rc->clear_possibilities(LNM_COMMAND, "enabled-filter");
338 for (const auto &tf : fs) {
339 rc->add_possibility(LNM_COMMAND, "all-filters", tf->get_id());
340 if (tf->is_enabled()) {
341 rc->add_possibility(LNM_COMMAND, "enabled-filter", tf->get_id());
342 }
343 else {
344 rc->add_possibility(LNM_COMMAND, "disabled-filter", tf->get_id());
345 }
346 }
347 }
348
add_file_possibilities()349 void add_file_possibilities()
350 {
351 static const std::regex sh_escape(R"(([\s\'\"]+))");
352
353 readline_curses *rc = lnav_data.ld_rl_view;
354
355 rc->clear_possibilities(LNM_COMMAND, "visible-files");
356 rc->clear_possibilities(LNM_COMMAND, "hidden-files");
357 for (const auto& lf : lnav_data.ld_active_files.fc_files) {
358 if (lf.get() == nullptr) {
359 continue;
360 }
361
362 lnav_data.ld_log_source.find_data(lf) | [&lf, rc](auto ld) {
363 auto escaped_fn = std::regex_replace(lf->get_filename(), sh_escape, R"(\\\1)");
364
365 rc->add_possibility(LNM_COMMAND,
366 ld->is_visible() ? "visible-files" : "hidden-files",
367 escaped_fn);
368 };
369 }
370 }
371
add_mark_possibilities()372 void add_mark_possibilities()
373 {
374 readline_curses *rc = lnav_data.ld_rl_view;
375
376 rc->clear_possibilities(LNM_COMMAND, "mark-type");
377 for (auto iter = bookmark_type_t::type_begin();
378 iter != bookmark_type_t::type_end();
379 ++iter) {
380 bookmark_type_t *bt = (*iter);
381
382 if (bt->get_name().empty()) {
383 continue;
384 }
385 rc->add_possibility(LNM_COMMAND, "mark-type", bt->get_name());
386 }
387 }
388
add_config_possibilities()389 void add_config_possibilities()
390 {
391 readline_curses *rc = lnav_data.ld_rl_view;
392 set<string> visited;
393 auto cb = [rc, &visited](const json_path_handler_base &jph,
394 const string &path,
395 void *mem) {
396 if (jph.jph_children) {
397 if (!jph.jph_regex.p_named_count) {
398 rc->add_possibility(LNM_COMMAND, "config-option", path);
399 }
400 for (auto named_iter = jph.jph_regex.named_begin();
401 named_iter != jph.jph_regex.named_end();
402 ++named_iter) {
403 if (visited.count(named_iter->pnc_name) == 0) {
404 rc->clear_possibilities(LNM_COMMAND, named_iter->pnc_name);
405 visited.insert(named_iter->pnc_name);
406 }
407
408 rc->add_possibility(LNM_COMMAND, named_iter->pnc_name, path);
409 }
410 } else {
411 rc->add_possibility(LNM_COMMAND, "config-option", path);
412 if (jph.jph_synopsis) {
413 rc->add_prefix(LNM_COMMAND,
414 vector<string>{"config", path},
415 jph.jph_synopsis);
416 }
417 }
418 };
419
420 rc->clear_possibilities(LNM_COMMAND, "config-option");
421 for (auto &jph : lnav_config_handlers.jpc_children) {
422 jph.walk(cb, &lnav_config);
423 }
424 }
425
add_tag_possibilities()426 void add_tag_possibilities()
427 {
428 readline_curses *rc = lnav_data.ld_rl_view;
429
430 rc->clear_possibilities(LNM_COMMAND, "tag");
431 rc->clear_possibilities(LNM_COMMAND, "line-tags");
432 rc->add_possibility(LNM_COMMAND, "tag", bookmark_metadata::KNOWN_TAGS);
433 if (lnav_data.ld_view_stack.top().value_or(nullptr) == &lnav_data.ld_views[LNV_LOG]) {
434 logfile_sub_source &lss = lnav_data.ld_log_source;
435 if (lss.text_line_count() > 0) {
436 content_line_t cl = lss.at(lnav_data.ld_views[LNV_LOG].get_top());
437 const map<content_line_t, bookmark_metadata> &user_meta =
438 lss.get_user_bookmark_metadata();
439 auto meta_iter = user_meta.find(cl);
440
441 if (meta_iter != user_meta.end()) {
442 rc->add_possibility(LNM_COMMAND,
443 "line-tags",
444 meta_iter->second.bm_tags);
445 }
446 }
447 }
448 }
449