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