1 /**
2  * Copyright (c) 2007-2012, 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  * @file readline_curses.hh
30  */
31 
32 #ifndef readline_curses_hh
33 #define readline_curses_hh
34 
35 #include <stdio.h>
36 #include <string.h>
37 #include <stdlib.h>
38 #include <errno.h>
39 #include <poll.h>
40 #include <sys/types.h>
41 #include <sys/time.h>
42 #include <sys/ioctl.h>
43 
44 #include <map>
45 #include <set>
46 #include <stack>
47 #include <string>
48 #include <utility>
49 #include <vector>
50 #include <exception>
51 #include <functional>
52 
53 #include <readline/readline.h>
54 #include <readline/history.h>
55 
56 #include "base/func_util.hh"
57 #include "base/result.h"
58 #include "auto_fd.hh"
59 #include "vt52_curses.hh"
60 #include "log_format.hh"
61 #include "help_text_formatter.hh"
62 
63 struct exec_context;
64 
65 typedef void (*readline_highlighter_t)(attr_line_t &line, int x);
66 
67 extern exec_context INIT_EXEC_CONTEXT;
68 
69 /**
70  * Container for information related to different readline contexts.  Since
71  * lnav uses readline for different inputs, we need a way to keep things like
72  * history and tab-completions separate.
73  */
74 class readline_context {
75 public:
76     typedef Result<std::string, std::string> (*command_func_t)(exec_context &ec,
77             std::string cmdline, std::vector<std::string> &args);
78     typedef std::string (*prompt_func_t)(exec_context &ec,
79         const std::string &cmdline);
80     typedef struct _command_t {
81         const char *c_name;
82         command_func_t c_func;
83 
84         struct help_text c_help;
85         prompt_func_t c_prompt{nullptr};
86 
_command_treadline_context::_command_t87         _command_t(const char *name,
88                    command_func_t func,
89                    help_text help = {},
90                    prompt_func_t prompt = nullptr) noexcept
91             : c_name(name), c_func(func), c_help(std::move(help)), c_prompt(prompt) {};
92 
_command_treadline_context::_command_t93         _command_t(command_func_t func) noexcept
94             : c_name("anon"), c_func(func) {};
95     } command_t;
96     typedef std::map<std::string, command_t *> command_map_t;
97 
98     readline_context(std::string name,
99                      command_map_t *commands = nullptr,
100                      bool case_sensitive = true);
101 
get_name() const102     const std::string &get_name() const { return this->rc_name; };
103 
104     void load();
105 
106     void save();
107 
add_possibility(const std::string & type,const std::string & value)108     void add_possibility(const std::string& type, const std::string& value)
109     {
110         this->rc_possibilities[type].insert(value);
111     };
112 
rem_possibility(const std::string & type,const std::string & value)113     void rem_possibility(const std::string& type, const std::string& value)
114     {
115         this->rc_possibilities[type].erase(value);
116     };
117 
clear_possibilities(const std::string & type)118     void clear_possibilities(const std::string& type)
119     {
120         this->rc_possibilities[type].clear();
121     };
122 
is_case_sensitive() const123     bool is_case_sensitive() const
124     {
125         return this->rc_case_sensitive;
126     };
127 
set_append_character(int ch)128     readline_context &set_append_character(int ch) {
129         this->rc_append_character = ch;
130 
131         return *this;
132     };
133 
get_append_character() const134     int get_append_character() const {
135         return this->rc_append_character;
136     }
137 
set_highlighter(readline_highlighter_t hl)138     readline_context &set_highlighter(readline_highlighter_t hl) {
139         this->rc_highlighter = hl;
140         return *this;
141     };
142 
set_quote_chars(const char * qc)143     readline_context &set_quote_chars(const char *qc) {
144         this->rc_quote_chars = qc;
145 
146         return *this;
147     };
148 
with_readline_var(char ** var,const char * val)149     readline_context &with_readline_var(char **var, const char *val) {
150         this->rc_vars.emplace_back(var, val);
151 
152         return *this;
153     };
154 
get_highlighter() const155     readline_highlighter_t get_highlighter() const {
156         return this->rc_highlighter;
157     };
158 
159     static int command_complete(int, int);
160 
161     std::map<std::string, std::string> rc_prefixes;
162 private:
163     static char **attempted_completion(const char *text, int start, int end);
164     static char *completion_generator(const char *text, int state);
165 
166     static readline_context *     loaded_context;
167     static std::set<std::string> *arg_possibilities;
168 
169     struct readline_var {
readline_varreadline_context::readline_var170         readline_var(char **dst, const char *val) {
171             this->rv_dst.ch = dst;
172             this->rv_val.ch = val;
173         }
174 
175         union {
176             char **ch;
177         } rv_dst;
178         union {
179             const char *ch;
180         } rv_val;
181     };
182 
183     std::string   rc_name;
184     HISTORY_STATE rc_history;
185     std::map<std::string, std::set<std::string> >    rc_possibilities;
186     std::map<std::string, std::vector<std::string> > rc_prototypes;
187     bool rc_case_sensitive;
188     int rc_append_character;
189     const char *rc_quote_chars;
190     readline_highlighter_t rc_highlighter;
191     std::vector<readline_var> rc_vars;
192 };
193 
194 /**
195  * Adapter between readline and curses.  The curses and readline libraries
196  * normally do not get along.  So, we need to put readline in another process
197  * and present it with a vt52 interface that we then translate to curses.  The
198  * vt52 translation is done by the parent class, vt52_curses, while this class
199  * takes care of the communication between the two processes.
200  */
201 class readline_curses
202     : public vt52_curses {
203 public:
204     using action = std::function<void(readline_curses*)>;
205 
206     class error
207         : public std::exception {
208 public:
error(int err)209         error(int err)
210             : e_err(err) { };
211 
212         int e_err;
213     };
214 
215     static const int KEY_TIMEOUT = 750 * 1000;
216 
217     static const int VALUE_EXPIRATION = 20;
218 
219 
220     readline_curses();
221     ~readline_curses() override;
222 
add_context(int id,readline_context & rc)223     void add_context(int id, readline_context &rc)
224     {
225         this->rc_contexts[id] = &rc;
226     };
227 
set_focus_action(const action & va)228     void set_focus_action(const action& va) { this->rc_focus = va; };
set_change_action(const action & va)229     void set_change_action(const action& va) { this->rc_change = va; };
set_perform_action(const action & va)230     void set_perform_action(const action& va) { this->rc_perform = va; };
set_alt_perform_action(const action & va)231     void set_alt_perform_action(const action& va) { this->rc_alt_perform = va; };
set_timeout_action(const action & va)232     void set_timeout_action(const action& va) { this->rc_timeout = va; };
set_abort_action(const action & va)233     void set_abort_action(const action& va) { this->rc_abort = va; };
set_display_match_action(const action & va)234     void set_display_match_action(const action& va) { this->rc_display_match = va; };
set_display_next_action(const action & va)235     void set_display_next_action(const action& va) { this->rc_display_next = va; };
set_blur_action(const action & va)236     void set_blur_action(const action& va) { this->rc_blur = va; };
set_completion_request_action(const action & va)237     void set_completion_request_action(const action& va) {
238         this->rc_completion_request = va;
239     }
240 
set_value(const std::string & value)241     void set_value(const std::string &value)
242     {
243         this->rc_value            = value;
244         if (this->rc_value.length() > 1024) {
245             this->rc_value = this->rc_value.substr(0, 1024);
246         }
247         this->rc_value_expiration = time(nullptr) + VALUE_EXPIRATION;
248         this->set_needs_update();
249     };
get_value() const250     std::string get_value() const { return this->rc_value; };
251 
get_line_buffer() const252     std::string get_line_buffer() const {
253         return this->rc_line_buffer;
254     };
255 
set_alt_value(const std::string & value)256     void set_alt_value(const std::string &value)
257     {
258         this->rc_alt_value = value;
259     };
get_alt_value() const260     std::string get_alt_value() const { return this->rc_alt_value; };
261 
update_poll_set(std::vector<struct pollfd> & pollfds)262     void update_poll_set(std::vector<struct pollfd> &pollfds)
263     {
264         pollfds.push_back((struct pollfd) {
265                 this->rc_pty[RCF_MASTER],
266                 POLLIN,
267                 0
268         });
269         pollfds.push_back((struct pollfd) {
270                 this->rc_command_pipe[RCF_MASTER],
271                 POLLIN,
272                 0
273         });
274     };
275 
276     void handle_key(int ch);
277 
278     void check_poll_set(const std::vector<struct pollfd> &pollfds);
279 
280     void focus(int context, const std::string& prompt, const std::string& initial = "");
281 
set_alt_focus(bool alt_focus)282     void set_alt_focus(bool alt_focus) {
283         this->rc_is_alt_focus = alt_focus;
284     }
285 
286     void rewrite_line(int pos, const std::string &value);
287 
get_active_context() const288     readline_context *get_active_context() const {
289         require(this->rc_active_context != -1);
290 
291         std::map<int, readline_context *>::const_iterator iter;
292         iter = this->rc_contexts.find(this->rc_active_context);
293         return iter->second;
294     };
295 
296     void abort();
297 
298     void start();
299 
300     void do_update() override;
301 
window_change()302     void window_change()
303     {
304         struct winsize ws;
305 
306         if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1) {
307             throw error(errno);
308         }
309         if (ioctl(this->rc_pty[RCF_MASTER], TIOCSWINSZ, &ws) == -1) {
310             throw error(errno);
311         }
312     };
313 
314     void line_ready(const char *line);
315 
316     void add_prefix(int context,
317                     const std::vector<std::string> &prefix,
318                     const std::string &value);
319 
320     void clear_prefixes(int context);
321 
322     void add_possibility(int context,
323                          const std::string &type,
324                          const std::string &value);
325 
add_possibility(int context,const std::string & type,const char * values[])326     void add_possibility(int context,
327                          const std::string &type,
328                          const char *values[])
329     {
330         for (int lpc = 0; values[lpc]; lpc++) {
331             this->add_possibility(context, type, values[lpc]);
332         }
333     };
334 
add_possibility(int context,const std::string & type,const char ** first,const char ** last)335     void add_possibility(int context,
336                          const std::string &type,
337                          const char **first,
338                          const char **last)
339     {
340         for (; first < last; first++) {
341             this->add_possibility(context, type, *first);
342         }
343     };
344 
345     template<template<typename ...> class Container>
add_possibility(int context,const std::string & type,const Container<std::string> & values)346     void add_possibility(int context,
347                          const std::string &type,
348                          const Container<std::string> &values)
349     {
350         for (const auto &str : values) {
351             this->add_possibility(context, type, str);
352         }
353     };
354 
355     void rem_possibility(int context,
356                          const std::string &type,
357                          const std::string &value);
358     void clear_possibilities(int context, std::string type);
359 
get_matches() const360     const std::vector<std::string> &get_matches() const {
361         return this->rc_matches;
362     };
363 
get_match_start() const364     int get_match_start() const {
365         return this->rc_match_start;
366     }
367 
368     std::string get_match_string() const;
369 
get_max_match_length() const370     int get_max_match_length() const {
371         return this->rc_max_match_length;
372     };
373 
consume_ready_for_input()374     bool consume_ready_for_input() {
375         auto retval = this->rc_ready_for_input;
376 
377         this->rc_ready_for_input = false;
378         return retval;
379     }
380 
get_remote_complete_path() const381     std::string get_remote_complete_path() const {
382         return this->rc_remote_complete_path;
383     }
384 
385 private:
386     enum {
387         RCF_MASTER,
388         RCF_SLAVE,
389 
390         RCF_MAX_VALUE,
391     };
392 
393     static void store_matches(char **matches, int num_matches, int max_len);
394 
395     friend class readline_context;
396 
397     int     rc_active_context{-1};
398     pid_t   rc_child{-1};
399     auto_fd rc_pty[2];
400     auto_fd rc_command_pipe[2];
401     std::map<int, readline_context *> rc_contexts;
402     std::string rc_value;
403     std::string rc_line_buffer;
404     time_t      rc_value_expiration{0};
405     std::string rc_alt_value;
406     int rc_match_start{0};
407     int rc_matches_remaining{0};
408     int rc_max_match_length{0};
409     int rc_match_index{0};
410     std::vector<std::string> rc_matches;
411     bool rc_is_alt_focus{false};
412     bool rc_ready_for_input{false};
413     std::string rc_remote_complete_path;
414 
415     action rc_focus;
416     action rc_change;
417     action rc_perform;
418     action rc_alt_perform;
419     action rc_timeout;
420     action rc_abort;
421     action rc_display_match;
422     action rc_display_next;
423     action rc_blur;
424     action rc_completion_request;
425 };
426 #endif
427