diff options
Diffstat (limited to 'src/readline_curses.hh')
-rw-r--r-- | src/readline_curses.hh | 371 |
1 files changed, 371 insertions, 0 deletions
diff --git a/src/readline_curses.hh b/src/readline_curses.hh new file mode 100644 index 0000000..d051d89 --- /dev/null +++ b/src/readline_curses.hh @@ -0,0 +1,371 @@ +/** + * Copyright (c) 2007-2012, 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 readline_curses.hh + */ + +#ifndef readline_curses_hh +#define readline_curses_hh + +#include <cstdio> +#include <exception> +#include <functional> +#include <map> +#include <set> +#include <stack> +#include <string> +#include <utility> +#include <vector> + +#include <errno.h> +#include <poll.h> +#include <readline/history.h> +#include <readline/readline.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#include <sys/types.h> + +#include "base/auto_fd.hh" +#include "base/enum_util.hh" +#include "base/func_util.hh" +#include "base/result.h" +#include "help_text_formatter.hh" +#include "log_format.hh" +#include "pollable.hh" +#include "readline_context.hh" +#include "vt52_curses.hh" + +extern exec_context INIT_EXEC_CONTEXT; + +/** + * Adapter between readline and curses. The curses and readline libraries + * normally do not get along. So, we need to put readline in another process + * and present it with a vt52 interface that we then translate to curses. The + * vt52 translation is done by the parent class, vt52_curses, while this class + * takes care of the communication between the two processes. + */ +class readline_curses + : public vt52_curses + , public pollable { +public: + using action = std::function<void(readline_curses*)>; + + class error : public std::exception { + public: + error(int err) : e_err(err) {} + + int e_err; + }; + + static const int KEY_TIMEOUT = 750 * 1000; + + static const int VALUE_EXPIRATION = 20; + + readline_curses(std::shared_ptr<pollable_supervisor>); + ~readline_curses() override; + + using injectable = readline_curses(std::shared_ptr<pollable_supervisor>); + + void add_context(int id, readline_context& rc) + { + this->rc_contexts[id] = &rc; + } + + template<typename T, + typename... Args, + std::enable_if_t<std::is_enum<T>::value, bool> = true> + void add_context(T context, Args&... args) + { + this->add_context(lnav::enums::to_underlying(context), args...); + } + + void set_focus_action(const action& va) { this->rc_focus = va; } + + void set_change_action(const action& va) { this->rc_change = va; } + + void set_perform_action(const action& va) + { + this->rc_perform = va; + } + + void set_alt_perform_action(const action& va) + { + this->rc_alt_perform = va; + } + + void set_timeout_action(const action& va) + { + this->rc_timeout = va; + } + + void set_abort_action(const action& va) + { + this->rc_abort = va; + } + + void set_display_match_action(const action& va) + { + this->rc_display_match = va; + } + + void set_display_next_action(const action& va) + { + this->rc_display_next = va; + } + + void set_blur_action(const action& va) { this->rc_blur = va; } + + void set_completion_request_action(const action& va) + { + this->rc_completion_request = va; + } + + void set_value(const std::string& value); + + void set_attr_value(const attr_line_t& al); + + void clear_value() { this->rc_value.clear(); } + + const attr_line_t& get_value() const { return this->rc_value; } + + std::string get_line_buffer() const { return this->rc_line_buffer; } + + void set_alt_value(const std::string& value) { this->rc_alt_value = value; } + + std::string get_alt_value() const { return this->rc_alt_value; } + + void update_poll_set(std::vector<struct pollfd>& pollfds) override; + + void handle_key(int ch); + + void check_poll_set(const std::vector<struct pollfd>& pollfds) override; + + void focus(int context, + const std::string& prompt, + const std::string& initial = ""); + + template<typename T, + typename... Args, + std::enable_if_t<std::is_enum<T>::value, bool> = true> + void focus(T context, const Args&... args) + { + this->focus(lnav::enums::to_underlying(context), args...); + } + + void set_alt_focus(bool alt_focus) { this->rc_is_alt_focus = alt_focus; } + + void rewrite_line(int pos, const std::string& value); + + readline_context* get_active_context() const + { + require(this->rc_active_context != -1); + + std::map<int, readline_context*>::const_iterator iter; + iter = this->rc_contexts.find(this->rc_active_context); + return iter->second; + } + + void abort(); + + void start(); + + void do_update() override; + + void window_change() + { + struct winsize ws; + + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1) { + throw error(errno); + } + if (ioctl(this->rc_pty[RCF_MASTER], TIOCSWINSZ, &ws) == -1) { + throw error(errno); + } + } + + void line_ready(const char* line); + + void add_prefix(int context, + const std::vector<std::string>& prefix, + const std::string& value); + + void clear_prefixes(int context); + + template<typename T, + typename... Args, + std::enable_if_t<std::is_enum<T>::value, bool> = true> + void add_prefix(T context, const Args&... args) + { + this->add_prefix(lnav::enums::to_underlying(context), args...); + } + + template<typename T, std::enable_if_t<std::is_enum<T>::value, bool> = true> + void clear_prefixes(T context) + { + this->clear_prefixes(lnav::enums::to_underlying(context)); + } + + void add_possibility(int context, + const std::string& type, + const std::string& value); + + void add_possibility(int context, + const std::string& type, + const char* values[]) + { + for (int lpc = 0; values[lpc]; lpc++) { + this->add_possibility(context, type, values[lpc]); + } + } + + void add_possibility(int context, + const std::string& type, + const char** first, + const char** last) + { + for (; first < last; first++) { + this->add_possibility(context, type, *first); + } + } + + template<template<typename...> class Container> + void add_possibility(int context, + const std::string& type, + const Container<std::string>& values) + { + for (const auto& str : values) { + this->add_possibility(context, type, str); + } + } + + void rem_possibility(int context, + const std::string& type, + const std::string& value); + void clear_possibilities(int context, std::string type); + + template<typename T, + typename... Args, + std::enable_if_t<std::is_enum<T>::value, bool> = true> + void add_possibility(T context, Args... args) + { + this->add_possibility(lnav::enums::to_underlying(context), args...); + } + + template<typename T, + typename... Args, + std::enable_if_t<std::is_enum<T>::value, bool> = true> + void rem_possibility(T context, const Args&... args) + { + this->rem_possibility(lnav::enums::to_underlying(context), args...); + } + + template<typename T, + typename... Args, + std::enable_if_t<std::is_enum<T>::value, bool> = true> + void clear_possibilities(T context, Args... args) + { + this->clear_possibilities(lnav::enums::to_underlying(context), args...); + } + + const std::vector<std::string>& get_matches() const + { + return this->rc_matches; + } + + int get_match_start() const { return this->rc_match_start; } + + std::string get_match_string() const; + + int get_max_match_length() const + { + return this->rc_max_match_length; + } + + bool consume_ready_for_input() + { + auto retval = this->rc_ready_for_input; + + this->rc_ready_for_input = false; + return retval; + } + + std::string get_remote_complete_path() const + { + return this->rc_remote_complete_path; + } + + void set_save_history(bool value) { + this->rc_save_history = value; + } + +private: + enum { + RCF_MASTER, + RCF_SLAVE, + + RCF_MAX_VALUE, + }; + + static void store_matches(char** matches, int num_matches, int max_len); + + friend class readline_context; + + bool rc_save_history{true}; + int rc_active_context{-1}; + pid_t rc_child{-1}; + auto_fd rc_pty[2]; + auto_fd rc_command_pipe[2]; + std::map<int, readline_context*> rc_contexts; + attr_line_t rc_value; + std::string rc_line_buffer; + time_t rc_value_expiration{0}; + std::string rc_alt_value; + int rc_match_start{0}; + int rc_matches_remaining{0}; + int rc_max_match_length{0}; + int rc_match_index{0}; + std::vector<std::string> rc_matches; + bool rc_is_alt_focus{false}; + bool rc_ready_for_input{false}; + std::string rc_remote_complete_path; + + action rc_focus; + action rc_change; + action rc_perform; + action rc_alt_perform; + action rc_timeout; + action rc_abort; + action rc_display_match; + action rc_display_next; + action rc_blur; + action rc_completion_request; +}; + +#endif |