diff --git a/NEWS.org b/NEWS.org index 0d411163..368c3345 100644 --- a/NEWS.org +++ b/NEWS.org @@ -3,6 +3,9 @@ * 1.5.x (unreleased, development version) +*** mu + - Optionally provide readline support for the mu server (when in tty-mode) + *** mu4e - Honor ~truncate-string-ellipsis~ so you can now use 'fancy' ellipses for diff --git a/lib/utils/Makefile.am b/lib/utils/Makefile.am index 5a638acb..9faf5fef 100644 --- a/lib/utils/Makefile.am +++ b/lib/utils/Makefile.am @@ -53,6 +53,8 @@ libmu_utils_la_SOURCES= \ mu-log.h \ mu-command-parser.cc \ mu-command-parser.hh \ + mu-readline.cc \ + mu-readline.hh \ mu-sexp-parser.cc \ mu-sexp-parser.hh \ mu-str.c \ diff --git a/lib/utils/mu-readline.cc b/lib/utils/mu-readline.cc new file mode 100644 index 00000000..2fcd2245 --- /dev/null +++ b/lib/utils/mu-readline.cc @@ -0,0 +1,131 @@ +/* +** Copyright (C) 2020 Dirk-Jan C. Binnema +** +** This program is free software; you can redistribute it and/or modify it +** under the terms of the GNU General Public License as published by the +** Free Software Foundation; either version 3, or (at your option) any +** later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, +** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +** +*/ + +#include "mu-readline.hh" +#include "config.h" + +#include +#include +#include +#include +#include + +#ifdef HAVE_LIBREADLINE +# if defined(HAVE_READLINE_READLINE_H) +# include +# elif defined(HAVE_READLINE_H) +# include +# else /* !defined(HAVE_READLINE_H) */ +extern char *readline (); +# endif /* !defined(HAVE_READLINE_H) */ +char *cmdline = NULL; +#else /* !defined(HAVE_READLINE_READLINE_H) */ +/* no readline */ +#endif /* HAVE_LIBREADLINE */ + +#ifdef HAVE_READLINE_HISTORY +# if defined(HAVE_READLINE_HISTORY_H) +# include +# elif defined(HAVE_HISTORY_H) +# include +# else /* !defined(HAVE_HISTORY_H) */ +extern void add_history (); +extern int write_history (); +extern int read_history (); +# endif /* defined(HAVE_READLINE_HISTORY_H) */ +/* no history */ +#endif /* HAVE_READLINE_HISTORY */ + + +#if defined(HAVE_LIBREADLINE) && defined(HAVE_READLINE_HISTORY) +#define HAVE_READLINE (1) +#else +#define HAVE_READLINE (0) +#endif + +using namespace Mu; + +static bool is_a_tty{}; +static std::string hist_path; +static size_t max_lines{}; + +void +Mu::setup_readline (const std::string& histpath, size_t maxlines) +{ + is_a_tty = !!::isatty(::fileno(stdout)); + hist_path = histpath; + max_lines = maxlines; + +#if HAVE_READLINE + rl_bind_key('\t', rl_insert); // default (filenames) is not useful + using_history(); + read_history(hist_path.c_str()); + + if (max_lines > 0) + stifle_history(max_lines); +#endif /*HAVE_READLINE*/ +} + + +void +Mu::shutdown_readline () +{ +#if HAVE_READLINE + if (!is_a_tty) + return; + + write_history(hist_path.c_str()); + if (max_lines > 0) + history_truncate_file (hist_path.c_str(), max_lines); +#endif /*HAVE_READLINE*/ +} + + +std::string +Mu::read_line(bool& do_quit) +{ +#if HAVE_READLINE + if (is_a_tty) { + auto buf = readline(";; mu% "); + if (!buf) { + do_quit = true; + return {}; + } + std::string line{buf}; + ::free(buf); + return line; + } +#endif /*HAVE_READLINE*/ + + std::string line; + std::cout << ";; mu> "; + if (!std::getline(std::cin, line)) + do_quit = true; + + return line; +} + + +void +Mu::save_line(const std::string& line) { +#if HAVE_READLINE + if (is_a_tty) + add_history(line.c_str()); +#endif /*HAVE_READLINE*/ +} diff --git a/lib/utils/mu-readline.hh b/lib/utils/mu-readline.hh new file mode 100644 index 00000000..f892f289 --- /dev/null +++ b/lib/utils/mu-readline.hh @@ -0,0 +1,54 @@ +/* +** Copyright (C) 2020 Dirk-Jan C. Binnema +** +** This program is free software; you can redistribute it and/or modify it +** under the terms of the GNU General Public License as published by the +** Free Software Foundation; either version 3, or (at your option) any +** later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, +** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +** +*/ +#include + +namespace Mu { + +/** + * Setup readline when available & on tty. + * + * @param histpath path to the history file + * @param max_lines maximum number of history to save + */ +void setup_readline(const std::string &histpath, size_t max_lines); + +/** + * Shutdown readline +* + */ +void shutdown_readline(); + + +/** + * Read a command line + * + * @param do_quit recceives whether we should quit. + * + * @return the string read or empty + */ +std::string read_line(bool &do_quit); + +/** + * Save a line to history (or do nothing when readline is not active) + * + * @param line a line. + */ +void save_line(const std::string &line); + +} // namespace Mu diff --git a/mu/mu-cmd-server.cc b/mu/mu-cmd-server.cc index bce9025d..83ca3bc2 100644 --- a/mu/mu-cmd-server.cc +++ b/mu/mu-cmd-server.cc @@ -40,6 +40,7 @@ #include "utils/mu-str.h" #include "utils/mu-utils.hh" #include "utils/mu-command-parser.hh" +#include "utils/mu-readline.hh" using namespace Mu; using namespace Command; @@ -1270,17 +1271,6 @@ make_command_map (Context& context) return cmap; } - -static std::string -read_line(bool& do_quit) -{ - std::string line; - std::cout << ";; mu> "; - if (!std::getline(std::cin, line)) - do_quit = true; - return line; -} - MuError mu_cmd_server (MuConfig *opts, GError **err) try { @@ -1300,6 +1290,10 @@ mu_cmd_server (MuConfig *opts, GError **err) try return MU_OK; } + + const auto histpath{std::string{mu_runtime_path(MU_RUNTIME_PATH_CACHE)} + "/history"}; + setup_readline(histpath, 50); + install_sig_handler(); std::cout << ";; Welcome to the " << PACKAGE_STRING << " command-server\n" << ";; Use (help) to get a list of commands, (quit) to quit.\n"; @@ -1322,6 +1316,7 @@ mu_cmd_server (MuConfig *opts, GError **err) try er.what(), line.c_str()); } } + shutdown_readline(); return MU_OK;