summaryrefslogtreecommitdiffstats
path: root/src/readline_curses.hh
diff options
context:
space:
mode:
Diffstat (limited to 'src/readline_curses.hh')
-rw-r--r--src/readline_curses.hh371
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