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