2013-04-09 20:05:40 +02:00
|
|
|
#include "assert.hh"
|
2016-12-03 14:18:11 +01:00
|
|
|
#include "backtrace.hh"
|
2017-03-16 10:57:39 +01:00
|
|
|
#include "buffer.hh"
|
2011-09-08 16:30:36 +02:00
|
|
|
#include "buffer_manager.hh"
|
2014-04-28 20:48:23 +02:00
|
|
|
#include "buffer_utils.hh"
|
2013-04-09 20:05:40 +02:00
|
|
|
#include "client_manager.hh"
|
|
|
|
#include "command_manager.hh"
|
|
|
|
#include "commands.hh"
|
|
|
|
#include "context.hh"
|
|
|
|
#include "event_manager.hh"
|
2014-07-11 01:27:04 +02:00
|
|
|
#include "face_registry.hh"
|
2013-04-09 20:05:40 +02:00
|
|
|
#include "file.hh"
|
|
|
|
#include "highlighters.hh"
|
2014-12-24 14:03:17 +01:00
|
|
|
#include "insert_completer.hh"
|
2016-03-06 02:37:20 +01:00
|
|
|
#include "json_ui.hh"
|
2017-03-16 10:57:39 +01:00
|
|
|
#include "ncurses_ui.hh"
|
|
|
|
#include "option_types.hh"
|
2012-12-18 21:41:13 +01:00
|
|
|
#include "parameters_parser.hh"
|
2017-08-29 10:23:03 +02:00
|
|
|
#include "ranges.hh"
|
2017-03-16 10:57:39 +01:00
|
|
|
#include "regex.hh"
|
2013-04-09 20:05:40 +02:00
|
|
|
#include "register_manager.hh"
|
|
|
|
#include "remote.hh"
|
2014-10-30 15:00:42 +01:00
|
|
|
#include "scope.hh"
|
2017-03-16 10:57:39 +01:00
|
|
|
#include "shared_string.hh"
|
2014-12-24 14:03:17 +01:00
|
|
|
#include "shell_manager.hh"
|
2013-04-09 20:05:40 +02:00
|
|
|
#include "string.hh"
|
2015-05-22 14:58:56 +02:00
|
|
|
#include "unit_tests.hh"
|
2013-04-18 14:28:53 +02:00
|
|
|
#include "window.hh"
|
2011-09-02 18:51:20 +02:00
|
|
|
|
2014-12-24 14:03:17 +01:00
|
|
|
#include <fcntl.h>
|
2013-02-26 14:12:21 +01:00
|
|
|
#include <locale>
|
2014-04-29 22:37:11 +02:00
|
|
|
#include <sys/stat.h>
|
2014-12-24 14:03:17 +01:00
|
|
|
#include <sys/types.h>
|
2014-04-30 20:08:06 +02:00
|
|
|
#include <unistd.h>
|
2015-09-15 14:32:26 +02:00
|
|
|
#include <pwd.h>
|
2014-04-29 22:37:11 +02:00
|
|
|
|
2017-02-14 14:54:45 +01:00
|
|
|
namespace Kakoune
|
|
|
|
{
|
2011-09-02 18:51:20 +02:00
|
|
|
|
2017-02-19 14:59:44 +01:00
|
|
|
static const char* startup_info =
|
|
|
|
"Kakoune recent breaking changes:\n"
|
2017-06-17 09:47:14 +02:00
|
|
|
" * lint/grep/make next/prev commands have been renamed to more\n"
|
2017-06-23 11:23:26 +02:00
|
|
|
" explicit names (lint-next-error, grep-previous-match, ...)\n"
|
2017-06-26 16:28:41 +02:00
|
|
|
" * ctags commands have been renamed to use the ctags- prefix\n"
|
2017-07-12 17:31:17 +02:00
|
|
|
" * completion_extra_word_char option is now extra_word_chars (note the\n"
|
2017-08-23 02:00:37 +02:00
|
|
|
" plural form) and is used for word selection commands\n"
|
|
|
|
" * selection extending behaviour has been simplified, it now just\n"
|
|
|
|
" maintain the current anchor and moves the cursor,\n";
|
2017-02-19 14:59:44 +01:00
|
|
|
|
2017-02-14 14:54:45 +01:00
|
|
|
struct startup_error : runtime_error
|
2016-04-09 10:13:35 +02:00
|
|
|
{
|
2017-02-14 14:54:45 +01:00
|
|
|
using runtime_error::runtime_error;
|
2016-04-09 10:13:35 +02:00
|
|
|
};
|
|
|
|
|
2016-11-29 00:53:50 +01:00
|
|
|
inline void write_stdout(StringView str) { write(1, str); }
|
2017-09-11 08:21:07 +02:00
|
|
|
inline void write_stderr(StringView str) { try { write(2, str); } catch (runtime_error&) {} }
|
2016-11-29 00:53:50 +01:00
|
|
|
|
2012-09-10 20:10:18 +02:00
|
|
|
String runtime_directory()
|
|
|
|
{
|
2015-09-02 23:28:41 +02:00
|
|
|
char relpath[PATH_MAX+1];
|
|
|
|
format_to(relpath, "{}../share/kak", split_path(get_kak_binary_path()).first);
|
|
|
|
struct stat st;
|
|
|
|
if (stat(relpath, &st) == 0 and S_ISDIR(st.st_mode))
|
|
|
|
return real_path(relpath);
|
|
|
|
|
|
|
|
return "/usr/share/kak";
|
2012-09-10 20:10:18 +02:00
|
|
|
}
|
|
|
|
|
2012-10-23 22:56:24 +02:00
|
|
|
void register_env_vars()
|
|
|
|
{
|
2014-01-12 22:25:21 +01:00
|
|
|
static const struct {
|
|
|
|
const char* name;
|
2015-09-03 14:21:35 +02:00
|
|
|
bool prefix;
|
2014-04-20 13:15:31 +02:00
|
|
|
String (*func)(StringView, const Context&);
|
2014-01-12 22:25:21 +01:00
|
|
|
} env_vars[] = { {
|
2015-09-03 14:21:35 +02:00
|
|
|
"bufname", false,
|
2015-09-02 14:35:50 +02:00
|
|
|
[](StringView name, const Context& context) -> String
|
2013-10-01 19:47:37 +02:00
|
|
|
{ return context.buffer().display_name(); }
|
2014-02-27 07:43:21 +01:00
|
|
|
}, {
|
2015-09-03 14:21:35 +02:00
|
|
|
"buffile", false,
|
2015-03-12 14:02:46 +01:00
|
|
|
[](StringView name, const Context& context) -> String
|
2014-02-27 07:43:21 +01:00
|
|
|
{ return context.buffer().name(); }
|
2014-12-23 02:49:53 +01:00
|
|
|
}, {
|
2015-09-03 14:21:35 +02:00
|
|
|
"buflist", false,
|
2014-12-23 02:49:53 +01:00
|
|
|
[](StringView name, const Context& context)
|
2016-03-08 22:35:56 +01:00
|
|
|
{ return join(BufferManager::instance() |
|
2016-10-11 01:32:40 +02:00
|
|
|
transform(std::mem_fn(&Buffer::display_name)), ':'); }
|
2017-06-06 14:29:06 +02:00
|
|
|
}, {
|
|
|
|
"buf_line_count", false,
|
|
|
|
[](StringView name, const Context& context) -> String
|
|
|
|
{ return to_string(context.buffer().line_count()); }
|
2013-10-01 19:47:37 +02:00
|
|
|
}, {
|
2015-09-03 14:21:35 +02:00
|
|
|
"timestamp", false,
|
2015-03-31 14:53:40 +02:00
|
|
|
[](StringView name, const Context& context) -> String
|
2013-10-01 19:47:37 +02:00
|
|
|
{ return to_string(context.buffer().timestamp()); }
|
2017-09-02 12:14:19 +02:00
|
|
|
}, {
|
2017-09-04 16:41:27 +02:00
|
|
|
"history_id", false,
|
2017-09-02 12:14:19 +02:00
|
|
|
[](StringView name, const Context& context) -> String
|
|
|
|
{ return to_string(context.buffer().current_history_id()); }
|
2013-10-01 19:47:37 +02:00
|
|
|
}, {
|
2015-09-03 14:21:35 +02:00
|
|
|
"selection", false,
|
2014-04-20 13:15:31 +02:00
|
|
|
[](StringView name, const Context& context)
|
2014-03-29 09:55:45 +01:00
|
|
|
{ const Selection& sel = context.selections().main();
|
2013-10-01 19:47:37 +02:00
|
|
|
return content(context.buffer(), sel); }
|
|
|
|
}, {
|
2015-09-03 14:21:35 +02:00
|
|
|
"selections", false,
|
2014-04-20 13:15:31 +02:00
|
|
|
[](StringView name, const Context& context)
|
2014-12-28 12:16:51 +01:00
|
|
|
{ return join(context.selections_content(), ':'); }
|
2013-10-01 19:47:37 +02:00
|
|
|
}, {
|
2015-09-03 14:21:35 +02:00
|
|
|
"runtime", false,
|
2014-04-20 13:15:31 +02:00
|
|
|
[](StringView name, const Context& context)
|
2013-10-01 19:47:37 +02:00
|
|
|
{ return runtime_directory(); }
|
|
|
|
}, {
|
2015-09-03 14:21:35 +02:00
|
|
|
"opt_", true,
|
2014-04-20 13:15:31 +02:00
|
|
|
[](StringView name, const Context& context)
|
2013-10-01 19:47:37 +02:00
|
|
|
{ return context.options()[name.substr(4_byte)].get_as_string(); }
|
|
|
|
}, {
|
2015-09-03 14:21:35 +02:00
|
|
|
"reg_", true,
|
2015-03-10 20:33:46 +01:00
|
|
|
[](StringView name, const Context& context)
|
|
|
|
{ return context.main_sel_register_value(name.substr(4_byte)).str(); }
|
2014-04-07 22:25:44 +02:00
|
|
|
}, {
|
2015-09-03 14:21:35 +02:00
|
|
|
"client_env_", true,
|
2015-03-10 20:33:46 +01:00
|
|
|
[](StringView name, const Context& context)
|
|
|
|
{ return context.client().get_env_var(name.substr(11_byte)).str(); }
|
2013-10-01 19:47:37 +02:00
|
|
|
}, {
|
2015-09-03 14:21:35 +02:00
|
|
|
"session", false,
|
2015-03-12 14:02:46 +01:00
|
|
|
[](StringView name, const Context& context) -> String
|
2013-10-01 19:47:37 +02:00
|
|
|
{ return Server::instance().session(); }
|
|
|
|
}, {
|
2015-09-03 14:21:35 +02:00
|
|
|
"client", false,
|
2015-03-12 14:02:46 +01:00
|
|
|
[](StringView name, const Context& context) -> String
|
2013-11-14 21:51:25 +01:00
|
|
|
{ return context.name(); }
|
2017-08-28 08:12:15 +02:00
|
|
|
}, {
|
|
|
|
"client_pid", false,
|
|
|
|
[](StringView name, const Context& context) -> String
|
|
|
|
{ return to_string(context.client().pid()); }
|
2017-05-18 17:36:06 +02:00
|
|
|
}, {
|
|
|
|
"modified", false,
|
|
|
|
[](StringView name, const Context& context) -> String
|
|
|
|
{ return context.buffer().is_modified() ? "true" : "false"; }
|
2013-10-01 19:47:37 +02:00
|
|
|
}, {
|
2015-09-03 14:21:35 +02:00
|
|
|
"cursor_line", false,
|
2015-03-31 14:53:40 +02:00
|
|
|
[](StringView name, const Context& context) -> String
|
2014-01-28 20:05:49 +01:00
|
|
|
{ return to_string(context.selections().main().cursor().line + 1); }
|
2013-10-01 19:47:37 +02:00
|
|
|
}, {
|
2015-09-03 14:21:35 +02:00
|
|
|
"cursor_column", false,
|
2015-03-31 14:53:40 +02:00
|
|
|
[](StringView name, const Context& context) -> String
|
2014-01-28 20:05:49 +01:00
|
|
|
{ return to_string(context.selections().main().cursor().column + 1); }
|
2013-12-11 14:46:33 +01:00
|
|
|
}, {
|
2015-09-03 14:21:35 +02:00
|
|
|
"cursor_char_column", false,
|
2015-03-31 14:53:40 +02:00
|
|
|
[](StringView name, const Context& context) -> String
|
2014-01-28 20:05:49 +01:00
|
|
|
{ auto coord = context.selections().main().cursor();
|
2013-12-11 14:46:33 +01:00
|
|
|
return to_string(context.buffer()[coord.line].char_count_to(coord.column) + 1); }
|
2016-04-08 13:26:26 +02:00
|
|
|
}, {
|
|
|
|
"cursor_byte_offset", false,
|
|
|
|
[](StringView name, const Context& context) -> String
|
|
|
|
{ auto cursor = context.selections().main().cursor();
|
2016-04-09 02:08:38 +02:00
|
|
|
return to_string(context.buffer().distance({0,0}, cursor)); }
|
2013-10-01 19:47:37 +02:00
|
|
|
}, {
|
2015-09-03 14:21:35 +02:00
|
|
|
"selection_desc", false,
|
2014-04-20 13:15:31 +02:00
|
|
|
[](StringView name, const Context& context)
|
2015-06-25 15:00:50 +02:00
|
|
|
{ return selection_to_string(context.selections().main()); }
|
2014-05-25 19:26:31 +02:00
|
|
|
}, {
|
2015-09-03 14:21:35 +02:00
|
|
|
"selections_desc", false,
|
2014-05-25 19:26:31 +02:00
|
|
|
[](StringView name, const Context& context)
|
2015-04-13 16:21:26 +02:00
|
|
|
{ return selection_list_to_string(context.selections()); }
|
2013-10-01 19:47:37 +02:00
|
|
|
}, {
|
2015-09-03 14:21:35 +02:00
|
|
|
"window_width", false,
|
2015-03-31 14:53:40 +02:00
|
|
|
[](StringView name, const Context& context) -> String
|
2013-10-01 19:47:37 +02:00
|
|
|
{ return to_string(context.window().dimensions().column); }
|
|
|
|
}, {
|
2015-09-03 14:21:35 +02:00
|
|
|
"window_height", false,
|
2015-03-31 14:53:40 +02:00
|
|
|
[](StringView name, const Context& context) -> String
|
2013-10-01 19:47:37 +02:00
|
|
|
{ return to_string(context.window().dimensions().line); }
|
2017-03-10 10:06:37 +01:00
|
|
|
}
|
|
|
|
};
|
2012-10-23 22:56:24 +02:00
|
|
|
|
2013-10-01 19:47:37 +02:00
|
|
|
ShellManager& shell_manager = ShellManager::instance();
|
|
|
|
for (auto& env_var : env_vars)
|
2015-09-03 14:21:35 +02:00
|
|
|
shell_manager.register_env_var(env_var.name, env_var.prefix, env_var.func);
|
2012-10-23 22:56:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void register_registers()
|
|
|
|
{
|
2015-11-25 22:08:33 +01:00
|
|
|
RegisterManager& register_manager = RegisterManager::instance();
|
|
|
|
|
2017-05-25 21:39:05 +02:00
|
|
|
for (auto c : "abcdefghijklmnopqrstuvwxyz/\"|^@:")
|
2016-03-12 16:27:54 +01:00
|
|
|
register_manager.add_register(c, std::make_unique<StaticRegister>());
|
2015-11-25 22:08:33 +01:00
|
|
|
|
2015-01-14 20:16:32 +01:00
|
|
|
using StringList = Vector<String, MemoryDomain::Registers>;
|
2016-03-31 10:17:02 +02:00
|
|
|
|
|
|
|
register_manager.add_register('%', make_dyn_reg(
|
|
|
|
[](const Context& context)
|
|
|
|
{ return StringList{{context.buffer().display_name()}}; }));
|
|
|
|
|
|
|
|
register_manager.add_register('.', make_dyn_reg(
|
|
|
|
[](const Context& context) {
|
2015-01-14 20:16:32 +01:00
|
|
|
auto content = context.selections_content();
|
|
|
|
return StringList{content.begin(), content.end()};
|
2016-03-31 10:17:02 +02:00
|
|
|
}));
|
|
|
|
|
|
|
|
register_manager.add_register('#', make_dyn_reg(
|
|
|
|
[](const Context& context) {
|
2014-05-23 21:27:35 +02:00
|
|
|
StringList res;
|
2014-07-14 20:00:54 +02:00
|
|
|
for (size_t i = 1; i < context.selections().size()+1; ++i)
|
2014-05-23 21:27:35 +02:00
|
|
|
res.push_back(to_string((int)i));
|
|
|
|
return res;
|
2016-03-31 10:17:02 +02:00
|
|
|
}));
|
2012-10-23 22:56:24 +02:00
|
|
|
|
|
|
|
for (size_t i = 0; i < 10; ++i)
|
|
|
|
{
|
2016-03-31 10:17:02 +02:00
|
|
|
register_manager.add_register('0'+i, make_dyn_reg(
|
2013-01-04 18:39:13 +01:00
|
|
|
[i](const Context& context) {
|
2015-01-14 20:16:32 +01:00
|
|
|
StringList result;
|
2013-12-15 15:25:23 +01:00
|
|
|
for (auto& sel : context.selections())
|
2013-01-04 18:39:13 +01:00
|
|
|
result.emplace_back(i < sel.captures().size() ? sel.captures()[i] : "");
|
|
|
|
return result;
|
2017-02-14 01:02:01 +01:00
|
|
|
},
|
|
|
|
[i](Context& context, ConstArrayView<String> values) {
|
|
|
|
if (values.empty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto& sels = context.selections();
|
|
|
|
for (size_t sel_index = 0; sel_index < sels.size(); ++sel_index)
|
|
|
|
{
|
|
|
|
auto& sel = sels[sel_index];
|
|
|
|
if (sel.captures().size() < i+1)
|
|
|
|
sel.captures().resize(i+1);
|
|
|
|
sel.captures()[i] = values[std::min(sel_index, values.size()-1)];
|
|
|
|
}
|
2015-11-25 22:08:33 +01:00
|
|
|
}));
|
2012-10-23 22:56:24 +02:00
|
|
|
}
|
2015-11-25 22:08:33 +01:00
|
|
|
|
2016-03-12 16:27:54 +01:00
|
|
|
register_manager.add_register('_', std::make_unique<NullRegister>());
|
2012-10-23 22:56:24 +02:00
|
|
|
}
|
|
|
|
|
2016-02-12 14:53:54 +01:00
|
|
|
static void check_tabstop(const int& val)
|
2016-02-11 23:07:18 +01:00
|
|
|
{
|
2016-02-12 14:53:54 +01:00
|
|
|
if (val < 1) throw runtime_error{"tabstop should be strictly positive"};
|
|
|
|
}
|
|
|
|
|
|
|
|
static void check_indentwidth(const int& val)
|
|
|
|
{
|
|
|
|
if (val < 0) throw runtime_error{"indentwidth should be positive or zero"};
|
2016-02-11 23:07:18 +01:00
|
|
|
}
|
|
|
|
|
2016-09-22 21:36:26 +02:00
|
|
|
static void check_scrolloff(const DisplayCoord& so)
|
2016-02-11 23:07:18 +01:00
|
|
|
{
|
|
|
|
if (so.line < 0 or so.column < 0)
|
|
|
|
throw runtime_error{"scroll offset must be positive or zero"};
|
|
|
|
}
|
|
|
|
|
2016-09-18 14:47:22 +02:00
|
|
|
static void check_timeout(const int& timeout)
|
|
|
|
{
|
|
|
|
if (timeout < 50)
|
|
|
|
throw runtime_error{"the minimum acceptable timeout is 50 milliseconds"};
|
|
|
|
}
|
|
|
|
|
2017-06-26 16:28:41 +02:00
|
|
|
static void check_extra_word_chars(const Vector<Codepoint, MemoryDomain::Options>& extra_chars)
|
2017-02-20 20:19:26 +01:00
|
|
|
{
|
|
|
|
if (contains_that(extra_chars, is_blank))
|
|
|
|
throw runtime_error{"blanks are not accepted for extra completion characters"};
|
|
|
|
}
|
|
|
|
|
2014-10-30 15:00:42 +01:00
|
|
|
void register_options()
|
|
|
|
{
|
|
|
|
OptionsRegistry& reg = GlobalScope::instance().option_registry();
|
|
|
|
|
2016-02-12 14:53:54 +01:00
|
|
|
reg.declare_option<int, check_tabstop>("tabstop", "size of a tab character", 8);
|
|
|
|
reg.declare_option<int, check_indentwidth>("indentwidth", "indentation width", 4);
|
2016-09-22 21:36:26 +02:00
|
|
|
reg.declare_option<DisplayCoord, check_scrolloff>(
|
2016-02-11 23:07:18 +01:00
|
|
|
"scrolloff", "number of lines and columns to keep visible main cursor when scrolling",
|
|
|
|
{0,0});
|
2016-08-06 10:05:50 +02:00
|
|
|
reg.declare_option("eolformat", "end of line format", EolFormat::Lf);
|
|
|
|
reg.declare_option("BOM", "byte order mark to use when writing buffer",
|
2015-12-06 13:51:55 +01:00
|
|
|
ByteOrderMark::None);
|
2014-10-30 15:00:42 +01:00
|
|
|
reg.declare_option("incsearch",
|
|
|
|
"incrementaly apply search/select/split regex",
|
|
|
|
true);
|
|
|
|
reg.declare_option("autoinfo",
|
|
|
|
"automatically display contextual help",
|
2015-11-19 00:43:51 +01:00
|
|
|
AutoInfo::Command | AutoInfo::OnKey);
|
2014-10-30 15:00:42 +01:00
|
|
|
reg.declare_option("autoshowcompl",
|
|
|
|
"automatically display possible completions for prompts",
|
|
|
|
true);
|
|
|
|
reg.declare_option("aligntab",
|
|
|
|
"use tab characters when possible for alignement",
|
|
|
|
false);
|
|
|
|
reg.declare_option("ignored_files",
|
|
|
|
"patterns to ignore when completing filenames",
|
|
|
|
Regex{R"(^(\..*|.*\.(o|so|a))$)"});
|
|
|
|
reg.declare_option("disabled_hooks",
|
|
|
|
"patterns to disable hooks whose group is matched",
|
|
|
|
Regex{});
|
|
|
|
reg.declare_option("filetype", "buffer filetype", ""_str);
|
|
|
|
reg.declare_option("path", "path to consider when trying to find a file",
|
2015-01-15 20:25:41 +01:00
|
|
|
Vector<String, MemoryDomain::Options>({ "./", "/usr/include" }));
|
2014-10-30 15:00:42 +01:00
|
|
|
reg.declare_option("completers", "insert mode completers to execute.",
|
2015-01-12 14:24:30 +01:00
|
|
|
InsertCompleterDescList({
|
2017-01-29 14:49:45 +01:00
|
|
|
InsertCompleterDesc{ InsertCompleterDesc::Filename, {} },
|
2014-10-30 15:00:42 +01:00
|
|
|
InsertCompleterDesc{ InsertCompleterDesc::Word, "all"_str }
|
|
|
|
}), OptionFlags::None);
|
2015-12-27 09:49:15 +01:00
|
|
|
reg.declare_option("static_words", "list of words to always consider for insert word completion",
|
|
|
|
Vector<String, MemoryDomain::Options>{});
|
2014-10-30 15:00:42 +01:00
|
|
|
reg.declare_option("autoreload",
|
|
|
|
"autoreload buffer when a filesystem modification is detected",
|
2015-11-20 09:50:53 +01:00
|
|
|
Autoreload::Ask);
|
2016-09-18 14:47:22 +02:00
|
|
|
reg.declare_option<int, check_timeout>(
|
|
|
|
"idle_timeout", "timeout, in milliseconds, before idle hooks are triggered", 50);
|
|
|
|
reg.declare_option<int, check_timeout>(
|
|
|
|
"fs_check_timeout", "timeout, in milliseconds, between file system buffer modification checks",
|
|
|
|
500);
|
2014-11-11 00:29:16 +01:00
|
|
|
reg.declare_option("ui_options",
|
2015-08-31 17:00:34 +02:00
|
|
|
"colon separated list of <key>=<value> options that are "
|
2015-10-02 14:56:19 +02:00
|
|
|
"passed to and interpreted by the user interface\n"
|
|
|
|
"\n"
|
|
|
|
"The ncurses ui supports the following options:\n"
|
2017-06-29 10:31:02 +02:00
|
|
|
" <key>: <value>:\n"
|
2017-03-10 13:09:09 +01:00
|
|
|
" ncurses_assistant clippy|cat|dilbert|none|off\n"
|
2015-08-31 17:00:34 +02:00
|
|
|
" ncurses_status_on_top bool\n"
|
2015-10-02 14:56:19 +02:00
|
|
|
" ncurses_set_title bool\n"
|
|
|
|
" ncurses_enable_mouse bool\n"
|
2016-12-30 08:01:13 +01:00
|
|
|
" ncurses_change_colors bool\n"
|
2015-08-31 17:00:34 +02:00
|
|
|
" ncurses_wheel_up_button int\n"
|
2016-12-30 08:01:13 +01:00
|
|
|
" ncurses_wheel_down_button int\n",
|
2015-04-16 12:35:52 +02:00
|
|
|
UserInterface::Options{});
|
2015-09-19 13:43:39 +02:00
|
|
|
reg.declare_option("modelinefmt", "format string used to generate the modeline",
|
2017-03-10 10:06:37 +01:00
|
|
|
"%val{bufname} %val{cursor_line}:%val{cursor_char_column} {{context_info}} {{mode_info}} - %val{client}@[%val{session}]"_str);
|
|
|
|
|
2015-11-19 22:58:26 +01:00
|
|
|
reg.declare_option("debug", "various debug flags", DebugFlags::None);
|
2016-07-20 19:45:50 +02:00
|
|
|
reg.declare_option("readonly", "prevent buffers from being modified", false);
|
2017-06-26 16:28:41 +02:00
|
|
|
reg.declare_option<Vector<Codepoint, MemoryDomain::Options>, check_extra_word_chars>(
|
|
|
|
"extra_word_chars",
|
2017-02-20 20:19:26 +01:00
|
|
|
"Additional characters to be considered as words for insert completion",
|
2017-06-26 15:39:17 +02:00
|
|
|
{});
|
2014-10-30 15:00:42 +01:00
|
|
|
}
|
|
|
|
|
2017-02-14 14:54:45 +01:00
|
|
|
static Client* local_client = nullptr;
|
2017-08-23 08:22:23 +02:00
|
|
|
static int local_client_exit = 0;
|
2017-09-11 08:21:07 +02:00
|
|
|
static sig_atomic_t sighup_raised = 0;
|
2017-02-14 14:54:45 +01:00
|
|
|
static UserInterface* local_ui = nullptr;
|
|
|
|
static bool convert_to_client_pending = false;
|
2015-10-08 21:05:47 +02:00
|
|
|
|
2016-03-07 21:12:21 +01:00
|
|
|
enum class UIType
|
|
|
|
{
|
|
|
|
NCurses,
|
|
|
|
Json,
|
|
|
|
Dummy,
|
|
|
|
};
|
|
|
|
|
2017-02-14 14:54:45 +01:00
|
|
|
UIType parse_ui_type(StringView ui_name)
|
2015-10-08 21:05:47 +02:00
|
|
|
{
|
2017-02-14 14:54:45 +01:00
|
|
|
if (ui_name == "ncurses") return UIType::NCurses;
|
|
|
|
if (ui_name == "json") return UIType::Json;
|
|
|
|
if (ui_name == "dummy") return UIType::Dummy;
|
2015-10-08 21:05:47 +02:00
|
|
|
|
2017-02-14 14:54:45 +01:00
|
|
|
throw parameter_error(format("error: unknown ui type: '{}'", ui_name));
|
2015-10-08 21:05:47 +02:00
|
|
|
}
|
|
|
|
|
2016-03-07 21:12:21 +01:00
|
|
|
std::unique_ptr<UserInterface> make_ui(UIType ui_type)
|
2012-10-17 17:49:34 +02:00
|
|
|
{
|
2015-08-26 20:33:52 +02:00
|
|
|
struct DummyUI : UserInterface
|
|
|
|
{
|
2016-03-15 01:24:42 +01:00
|
|
|
DummyUI() { set_signal_handler(SIGINT, SIG_DFL); }
|
2016-09-22 21:36:26 +02:00
|
|
|
void menu_show(ConstArrayView<DisplayLine>, DisplayCoord,
|
2015-10-05 02:25:23 +02:00
|
|
|
Face, Face, MenuStyle) override {}
|
2015-08-26 20:33:52 +02:00
|
|
|
void menu_select(int) override {}
|
|
|
|
void menu_hide() override {}
|
|
|
|
|
2016-09-22 21:36:26 +02:00
|
|
|
void info_show(StringView, StringView, DisplayCoord, Face, InfoStyle) override {}
|
2015-08-26 20:33:52 +02:00
|
|
|
void info_hide() override {}
|
|
|
|
|
2016-02-17 14:32:05 +01:00
|
|
|
void draw(const DisplayBuffer&, const Face&, const Face&) override {}
|
2015-08-26 20:33:52 +02:00
|
|
|
void draw_status(const DisplayLine&, const DisplayLine&, const Face&) override {}
|
2016-09-22 21:36:26 +02:00
|
|
|
DisplayCoord dimensions() override { return {24,80}; }
|
2017-04-12 11:39:17 +02:00
|
|
|
void set_cursor(CursorMode, DisplayCoord) override {}
|
2016-03-07 14:54:20 +01:00
|
|
|
void refresh(bool) override {}
|
2017-02-14 14:54:45 +01:00
|
|
|
void set_on_key(OnKeyCallback) override {}
|
2015-08-26 20:33:52 +02:00
|
|
|
void set_ui_options(const Options&) override {}
|
|
|
|
};
|
|
|
|
|
2016-03-07 21:12:21 +01:00
|
|
|
switch (ui_type)
|
|
|
|
{
|
2016-03-12 16:27:54 +01:00
|
|
|
case UIType::NCurses: return std::make_unique<NCursesUI>();
|
|
|
|
case UIType::Json: return std::make_unique<JsonUI>();
|
|
|
|
case UIType::Dummy: return std::make_unique<DummyUI>();
|
2016-03-07 21:12:21 +01:00
|
|
|
}
|
|
|
|
throw logic_error{};
|
|
|
|
}
|
|
|
|
|
2017-02-14 14:54:45 +01:00
|
|
|
pid_t fork_server_to_background()
|
|
|
|
{
|
|
|
|
if (pid_t pid = fork())
|
|
|
|
return pid;
|
|
|
|
|
|
|
|
if (fork()) // double fork to orphan the server
|
|
|
|
exit(0);
|
|
|
|
|
|
|
|
write_stderr(format("Kakoune forked server to background ({}), for session '{}'\n",
|
|
|
|
getpid(), Server::instance().session()));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-03-07 21:12:21 +01:00
|
|
|
std::unique_ptr<UserInterface> create_local_ui(UIType ui_type)
|
|
|
|
{
|
|
|
|
if (ui_type != UIType::NCurses)
|
|
|
|
return make_ui(ui_type);
|
2015-08-26 20:33:52 +02:00
|
|
|
|
|
|
|
struct LocalUI : NCursesUI
|
2012-11-29 18:56:08 +01:00
|
|
|
{
|
2015-10-08 21:05:47 +02:00
|
|
|
LocalUI()
|
|
|
|
{
|
2016-02-27 18:23:13 +01:00
|
|
|
kak_assert(not local_ui);
|
|
|
|
local_ui = this;
|
2016-01-10 21:46:15 +01:00
|
|
|
m_old_sighup = set_signal_handler(SIGHUP, [](int) {
|
2016-11-14 01:49:34 +01:00
|
|
|
static_cast<LocalUI*>(local_ui)->on_sighup();
|
2017-09-11 08:21:07 +02:00
|
|
|
sighup_raised = 1;
|
2015-10-08 21:05:47 +02:00
|
|
|
});
|
|
|
|
|
2016-01-10 21:46:15 +01:00
|
|
|
m_old_sigtstp = set_signal_handler(SIGTSTP, [](int) {
|
2015-10-08 21:05:47 +02:00
|
|
|
if (ClientManager::instance().count() == 1 and
|
|
|
|
*ClientManager::instance().begin() == local_client)
|
|
|
|
{
|
|
|
|
// Suspend normally if we are the only client
|
2016-02-27 18:23:13 +01:00
|
|
|
auto current = set_signal_handler(SIGTSTP, static_cast<LocalUI*>(local_ui)->m_old_sigtstp);
|
2015-10-08 21:05:47 +02:00
|
|
|
|
|
|
|
sigset_t unblock_sigtstp, old_mask;
|
|
|
|
sigemptyset(&unblock_sigtstp);
|
|
|
|
sigaddset(&unblock_sigtstp, SIGTSTP);
|
|
|
|
sigprocmask(SIG_UNBLOCK, &unblock_sigtstp, &old_mask);
|
|
|
|
|
|
|
|
raise(SIGTSTP);
|
|
|
|
|
2016-01-10 21:46:15 +01:00
|
|
|
set_signal_handler(SIGTSTP, current);
|
2016-11-14 01:49:34 +01:00
|
|
|
sigprocmask(SIG_SETMASK, &old_mask, nullptr);
|
2015-10-08 21:05:47 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
convert_to_client_pending = true;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-01-08 23:30:15 +01:00
|
|
|
~LocalUI() override
|
2012-11-29 18:56:08 +01:00
|
|
|
{
|
2016-01-10 21:46:15 +01:00
|
|
|
set_signal_handler(SIGHUP, m_old_sighup);
|
|
|
|
set_signal_handler(SIGTSTP, m_old_sigtstp);
|
2015-10-08 21:05:47 +02:00
|
|
|
local_client = nullptr;
|
2016-02-27 18:23:13 +01:00
|
|
|
local_ui = nullptr;
|
2015-10-08 21:05:47 +02:00
|
|
|
if (not convert_to_client_pending and
|
|
|
|
not ClientManager::instance().empty())
|
2012-11-29 18:56:08 +01:00
|
|
|
{
|
2015-10-08 21:05:47 +02:00
|
|
|
if (fork_server_to_background())
|
2015-10-14 14:21:27 +02:00
|
|
|
{
|
|
|
|
this->NCursesUI::~NCursesUI();
|
2017-08-23 08:22:23 +02:00
|
|
|
exit(local_client_exit);
|
2015-10-14 14:21:27 +02:00
|
|
|
}
|
2012-11-29 18:56:08 +01:00
|
|
|
}
|
|
|
|
}
|
2015-10-08 21:05:47 +02:00
|
|
|
|
|
|
|
private:
|
2015-10-09 14:41:28 +02:00
|
|
|
using SigHandler = void (*)(int);
|
|
|
|
SigHandler m_old_sighup;
|
|
|
|
SigHandler m_old_sigtstp;
|
2012-11-29 18:56:08 +01:00
|
|
|
};
|
|
|
|
|
2015-08-26 20:33:52 +02:00
|
|
|
if (not isatty(1))
|
2016-04-09 10:13:35 +02:00
|
|
|
throw startup_error("stdout is not a tty");
|
2015-03-24 20:38:03 +01:00
|
|
|
|
2015-08-26 20:33:52 +02:00
|
|
|
if (not isatty(0))
|
|
|
|
{
|
|
|
|
// move stdin to another fd, and restore tty as stdin
|
|
|
|
int fd = dup(0);
|
|
|
|
int tty = open("/dev/tty", O_RDONLY);
|
|
|
|
dup2(tty, 0);
|
|
|
|
close(tty);
|
2017-03-08 20:33:25 +01:00
|
|
|
create_fifo_buffer("*stdin*", fd, Buffer::Flags::None);
|
2014-04-29 22:37:11 +02:00
|
|
|
}
|
|
|
|
|
2016-03-12 16:27:54 +01:00
|
|
|
return std::make_unique<LocalUI>();
|
2015-08-26 20:33:52 +02:00
|
|
|
}
|
|
|
|
|
2017-05-13 12:05:09 +02:00
|
|
|
int run_client(StringView session, StringView client_init,
|
2017-01-21 13:14:44 +01:00
|
|
|
Optional<BufferCoord> init_coord, UIType ui_type)
|
2013-09-23 20:28:15 +02:00
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
EventManager event_manager;
|
2017-08-28 08:12:15 +02:00
|
|
|
RemoteClient client{session, make_ui(ui_type), getpid(), get_env_vars(),
|
|
|
|
client_init, std::move(init_coord)};
|
2017-08-23 08:22:23 +02:00
|
|
|
while (not client.exit_status())
|
2014-11-25 02:00:18 +01:00
|
|
|
event_manager.handle_next_events(EventMode::Normal);
|
2017-08-23 08:22:23 +02:00
|
|
|
return *client.exit_status();
|
2013-09-23 20:28:15 +02:00
|
|
|
}
|
2016-12-20 11:34:48 +01:00
|
|
|
catch (disconnected& e)
|
2013-09-23 20:28:15 +02:00
|
|
|
{
|
2017-08-23 08:22:23 +02:00
|
|
|
write_stderr(format("{}\ndisconnecting\n", e.what()));
|
|
|
|
return -1;
|
2014-03-21 14:42:37 +01:00
|
|
|
}
|
2013-09-23 20:28:15 +02:00
|
|
|
}
|
|
|
|
|
2017-02-14 14:54:45 +01:00
|
|
|
struct convert_to_client_mode
|
|
|
|
{
|
|
|
|
String session;
|
|
|
|
String buffer_name;
|
|
|
|
String selections;
|
|
|
|
};
|
|
|
|
|
|
|
|
enum class ServerFlags
|
|
|
|
{
|
2017-02-19 14:59:44 +01:00
|
|
|
None = 0,
|
2017-02-14 14:54:45 +01:00
|
|
|
IgnoreKakrc = 1 << 0,
|
2017-02-19 14:59:44 +01:00
|
|
|
Daemon = 1 << 1,
|
|
|
|
ReadOnly = 1 << 2,
|
|
|
|
StartupInfo = 1 << 3,
|
2017-02-14 14:54:45 +01:00
|
|
|
};
|
2017-03-15 18:55:34 +01:00
|
|
|
constexpr bool with_bit_ops(Meta::Type<ServerFlags>) { return true; }
|
2017-02-14 14:54:45 +01:00
|
|
|
|
2017-05-13 12:05:09 +02:00
|
|
|
int run_server(StringView session, StringView server_init,
|
|
|
|
StringView client_init, Optional<BufferCoord> init_coord,
|
2017-02-14 14:54:45 +01:00
|
|
|
ServerFlags flags, UIType ui_type,
|
2016-12-01 21:40:50 +01:00
|
|
|
ConstArrayView<StringView> files)
|
2012-10-21 13:02:24 +02:00
|
|
|
{
|
2013-09-23 20:28:15 +02:00
|
|
|
static bool terminate = false;
|
2017-02-14 14:54:45 +01:00
|
|
|
if (flags & ServerFlags::Daemon)
|
2013-09-23 20:28:15 +02:00
|
|
|
{
|
2014-08-14 21:37:36 +02:00
|
|
|
if (session.empty())
|
2013-09-19 20:53:04 +02:00
|
|
|
{
|
2015-04-01 14:44:04 +02:00
|
|
|
write_stderr("-d needs a session name to be specified with -s\n");
|
2013-09-19 20:53:04 +02:00
|
|
|
return -1;
|
|
|
|
}
|
2013-09-23 20:28:15 +02:00
|
|
|
if (pid_t child = fork())
|
2013-09-19 21:09:53 +02:00
|
|
|
{
|
2015-04-01 14:44:04 +02:00
|
|
|
write_stderr(format("Kakoune forked to background, for session '{}'\n"
|
|
|
|
"send SIGTERM to process {} for closing the session\n",
|
|
|
|
session, child));
|
2013-09-23 20:28:15 +02:00
|
|
|
exit(0);
|
2013-09-19 21:09:53 +02:00
|
|
|
}
|
2016-01-10 21:46:15 +01:00
|
|
|
set_signal_handler(SIGTERM, [](int) { terminate = true; });
|
2013-09-23 20:28:15 +02:00
|
|
|
}
|
2013-09-19 21:09:53 +02:00
|
|
|
|
2014-10-01 01:20:12 +02:00
|
|
|
StringRegistry string_registry;
|
2013-09-23 20:28:15 +02:00
|
|
|
EventManager event_manager;
|
2014-10-30 15:00:42 +01:00
|
|
|
GlobalScope global_scope;
|
2013-09-23 20:28:15 +02:00
|
|
|
ShellManager shell_manager;
|
|
|
|
CommandManager command_manager;
|
|
|
|
RegisterManager register_manager;
|
|
|
|
HighlighterRegistry highlighter_registry;
|
2013-12-03 23:03:10 +01:00
|
|
|
DefinedHighlighters defined_highlighters;
|
2014-07-11 01:27:04 +02:00
|
|
|
FaceRegistry face_registry;
|
2016-03-02 21:27:47 +01:00
|
|
|
ClientManager client_manager;
|
2016-03-03 14:55:35 +01:00
|
|
|
BufferManager buffer_manager;
|
2012-10-23 22:56:24 +02:00
|
|
|
|
2014-10-30 15:00:42 +01:00
|
|
|
register_options();
|
2013-09-23 20:28:15 +02:00
|
|
|
register_env_vars();
|
|
|
|
register_registers();
|
|
|
|
register_commands();
|
|
|
|
register_highlighters();
|
2012-10-23 22:56:24 +02:00
|
|
|
|
2015-10-16 14:52:14 +02:00
|
|
|
UnitTest::run_all_tests();
|
|
|
|
|
2015-06-06 12:54:48 +02:00
|
|
|
write_to_debug_buffer("*** This is the debug buffer, where debug info will be written ***");
|
2012-10-23 22:56:24 +02:00
|
|
|
|
2017-02-14 14:54:45 +01:00
|
|
|
GlobalScope::instance().options().get_local_option("readonly").set<bool>(flags & ServerFlags::ReadOnly);
|
2016-07-20 19:45:50 +02:00
|
|
|
|
2016-12-20 13:57:48 +01:00
|
|
|
Server server{session.empty() ? to_string(getpid()) : session.str()};
|
2012-10-23 22:56:24 +02:00
|
|
|
|
2015-07-08 14:43:40 +02:00
|
|
|
bool startup_error = false;
|
2017-02-14 14:54:45 +01:00
|
|
|
if (not (flags & ServerFlags::IgnoreKakrc)) try
|
2013-09-23 20:28:15 +02:00
|
|
|
{
|
2017-05-13 12:05:09 +02:00
|
|
|
Context init_context{Context::EmptyContextFlag{}};
|
2015-06-01 20:06:35 +02:00
|
|
|
command_manager.execute(format("source {}/kakrc", runtime_directory()),
|
2017-05-13 12:05:09 +02:00
|
|
|
init_context);
|
2013-09-23 20:28:15 +02:00
|
|
|
}
|
2017-02-14 14:54:45 +01:00
|
|
|
catch (runtime_error& error)
|
2013-09-23 20:28:15 +02:00
|
|
|
{
|
2015-07-08 14:43:40 +02:00
|
|
|
startup_error = true;
|
2016-10-17 19:37:05 +02:00
|
|
|
write_to_debug_buffer(format("error while parsing kakrc:\n"
|
|
|
|
" {}", error.what()));
|
2013-09-23 20:28:15 +02:00
|
|
|
}
|
2012-10-16 17:15:09 +02:00
|
|
|
|
2017-05-13 12:05:09 +02:00
|
|
|
if (not server_init.empty()) try
|
|
|
|
{
|
|
|
|
Context init_context{Context::EmptyContextFlag{}};
|
|
|
|
command_manager.execute(server_init, init_context);
|
|
|
|
}
|
|
|
|
catch (runtime_error& error)
|
|
|
|
{
|
|
|
|
startup_error = true;
|
|
|
|
write_to_debug_buffer(format("error while running server init commands:\n"
|
|
|
|
" {}", error.what()));
|
|
|
|
}
|
|
|
|
|
2013-09-23 20:28:15 +02:00
|
|
|
{
|
2015-04-19 19:47:52 +02:00
|
|
|
Context empty_context{Context::EmptyContextFlag{}};
|
2014-10-30 15:00:42 +01:00
|
|
|
global_scope.hooks().run_hook("KakBegin", "", empty_context);
|
2013-09-23 20:28:15 +02:00
|
|
|
}
|
2013-04-19 13:45:44 +02:00
|
|
|
|
2014-08-14 21:37:36 +02:00
|
|
|
if (not files.empty()) try
|
2013-09-23 20:28:15 +02:00
|
|
|
{
|
|
|
|
// create buffers in reverse order so that the first given buffer
|
|
|
|
// is the most recently created one.
|
2016-03-08 22:35:56 +01:00
|
|
|
for (auto& file : files | reverse())
|
2013-04-29 13:50:13 +02:00
|
|
|
{
|
2015-07-08 14:43:40 +02:00
|
|
|
try
|
|
|
|
{
|
2016-07-20 19:45:50 +02:00
|
|
|
Buffer *buffer = open_or_create_file_buffer(file);
|
2017-02-14 14:54:45 +01:00
|
|
|
if (flags & ServerFlags::ReadOnly)
|
2016-07-20 19:45:50 +02:00
|
|
|
buffer->flags() |= Buffer::Flags::ReadOnly;
|
2015-07-08 14:43:40 +02:00
|
|
|
}
|
2017-02-14 14:54:45 +01:00
|
|
|
catch (runtime_error& error)
|
2015-07-08 14:43:40 +02:00
|
|
|
{
|
|
|
|
startup_error = true;
|
2016-10-17 19:37:05 +02:00
|
|
|
write_to_debug_buffer(format("error while opening file '{}':\n"
|
|
|
|
" {}", file, error.what()));
|
2015-07-08 14:43:40 +02:00
|
|
|
}
|
2013-04-29 13:50:13 +02:00
|
|
|
}
|
2013-09-23 20:28:15 +02:00
|
|
|
}
|
2017-02-14 14:54:45 +01:00
|
|
|
catch (runtime_error& error)
|
2013-09-23 20:28:15 +02:00
|
|
|
{
|
2015-06-06 12:54:48 +02:00
|
|
|
write_to_debug_buffer(format("error while opening command line files: {}", error.what()));
|
2013-09-23 20:28:15 +02:00
|
|
|
}
|
2012-12-28 13:51:14 +01:00
|
|
|
|
2016-09-06 23:46:27 +02:00
|
|
|
try
|
2015-12-05 11:14:04 +01:00
|
|
|
{
|
2017-02-14 14:54:45 +01:00
|
|
|
if (not (flags & ServerFlags::Daemon))
|
2017-02-19 14:59:44 +01:00
|
|
|
{
|
2016-12-01 21:40:50 +01:00
|
|
|
local_client = client_manager.create_client(
|
2017-08-28 08:12:15 +02:00
|
|
|
create_local_ui(ui_type), getpid(), get_env_vars(), client_init, std::move(init_coord),
|
2017-08-23 08:22:23 +02:00
|
|
|
[](int status) { local_client_exit = status; });
|
2016-12-01 21:40:50 +01:00
|
|
|
|
2017-02-19 14:59:44 +01:00
|
|
|
if (startup_error)
|
|
|
|
local_client->print_status({
|
|
|
|
"error during startup, see *debug* buffer for details",
|
|
|
|
get_face("Error")
|
|
|
|
});
|
|
|
|
|
|
|
|
if (flags & ServerFlags::StartupInfo)
|
|
|
|
local_client->info_show("Welcome to Kakoune", startup_info, {}, InfoStyle::Prompt);
|
|
|
|
}
|
2012-10-30 14:00:44 +01:00
|
|
|
|
2017-02-14 14:54:45 +01:00
|
|
|
while (not terminate and (not client_manager.empty() or (flags & ServerFlags::Daemon)))
|
2015-08-26 20:34:19 +02:00
|
|
|
{
|
|
|
|
client_manager.redraw_clients();
|
|
|
|
event_manager.handle_next_events(EventMode::Normal);
|
2016-11-29 22:35:53 +01:00
|
|
|
client_manager.process_pending_inputs();
|
2016-09-04 18:54:07 +02:00
|
|
|
client_manager.clear_client_trash();
|
2016-05-13 21:32:53 +02:00
|
|
|
client_manager.clear_window_trash();
|
2015-08-26 20:34:19 +02:00
|
|
|
buffer_manager.clear_buffer_trash();
|
2015-10-08 21:05:47 +02:00
|
|
|
|
2017-09-11 08:21:07 +02:00
|
|
|
if (sighup_raised)
|
|
|
|
{
|
|
|
|
ClientManager::instance().remove_client(*local_client, true, 0);
|
|
|
|
if (not client_manager.empty() and fork_server_to_background())
|
|
|
|
return 0;
|
|
|
|
sighup_raised = 0;
|
|
|
|
}
|
|
|
|
else if (convert_to_client_pending)
|
2015-10-08 21:05:47 +02:00
|
|
|
{
|
2015-11-16 14:52:33 +01:00
|
|
|
String buffer_name = local_client->context().buffer().name();
|
2016-08-05 09:16:43 +02:00
|
|
|
String selections = selection_list_to_string(local_client->context().selections());
|
2015-11-16 14:52:33 +01:00
|
|
|
|
2017-08-23 08:22:23 +02:00
|
|
|
ClientManager::instance().remove_client(*local_client, true, 0);
|
2016-10-07 00:35:32 +02:00
|
|
|
client_manager.clear_client_trash();
|
2015-10-08 21:05:47 +02:00
|
|
|
convert_to_client_pending = false;
|
|
|
|
|
|
|
|
if (fork_server_to_background())
|
|
|
|
{
|
|
|
|
String session = server.session();
|
|
|
|
server.close_session(false);
|
2016-08-05 09:16:43 +02:00
|
|
|
throw convert_to_client_mode{ std::move(session), std::move(buffer_name), std::move(selections) };
|
2015-10-08 21:05:47 +02:00
|
|
|
}
|
|
|
|
}
|
2015-08-26 20:34:19 +02:00
|
|
|
}
|
2014-08-12 20:24:09 +02:00
|
|
|
}
|
2015-08-26 20:34:19 +02:00
|
|
|
catch (const kill_session&) {}
|
2013-04-19 13:45:44 +02:00
|
|
|
|
2013-09-23 20:28:15 +02:00
|
|
|
{
|
2015-04-19 19:47:52 +02:00
|
|
|
Context empty_context{Context::EmptyContextFlag{}};
|
2014-10-30 15:00:42 +01:00
|
|
|
global_scope.hooks().run_hook("KakEnd", "", empty_context);
|
2013-09-19 20:53:04 +02:00
|
|
|
}
|
2014-08-14 21:37:36 +02:00
|
|
|
|
2017-08-23 08:22:23 +02:00
|
|
|
return local_client_exit;
|
2013-09-19 20:53:04 +02:00
|
|
|
}
|
|
|
|
|
2017-03-22 14:47:25 +01:00
|
|
|
int run_filter(StringView keystr, StringView commands, ConstArrayView<StringView> files, bool quiet, StringView suffix_backup)
|
2014-08-15 00:51:24 +02:00
|
|
|
{
|
2014-10-27 18:54:20 +01:00
|
|
|
StringRegistry string_registry;
|
2014-10-30 15:00:42 +01:00
|
|
|
GlobalScope global_scope;
|
2015-12-08 01:12:50 +01:00
|
|
|
EventManager event_manager;
|
2014-10-26 21:21:01 +01:00
|
|
|
ShellManager shell_manager;
|
2015-12-03 14:48:30 +01:00
|
|
|
CommandManager command_manager;
|
2014-10-26 21:21:01 +01:00
|
|
|
RegisterManager register_manager;
|
2016-03-02 21:27:47 +01:00
|
|
|
ClientManager client_manager;
|
2016-03-03 14:55:35 +01:00
|
|
|
BufferManager buffer_manager;
|
2014-08-15 00:51:24 +02:00
|
|
|
|
2014-10-30 15:00:42 +01:00
|
|
|
register_options();
|
2014-08-15 00:51:24 +02:00
|
|
|
register_env_vars();
|
|
|
|
register_registers();
|
2015-12-03 14:48:30 +01:00
|
|
|
register_commands();
|
2014-08-15 00:51:24 +02:00
|
|
|
|
2014-08-17 16:19:04 +02:00
|
|
|
try
|
2014-08-15 00:51:24 +02:00
|
|
|
{
|
2014-08-17 16:19:04 +02:00
|
|
|
auto keys = parse_keys(keystr);
|
2014-08-15 00:51:24 +02:00
|
|
|
|
2015-12-03 14:48:30 +01:00
|
|
|
auto apply_to_buffer = [&](Buffer& buffer)
|
2014-08-17 16:19:04 +02:00
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2014-12-19 00:12:58 +01:00
|
|
|
InputHandler input_handler{
|
2015-02-02 19:46:55 +01:00
|
|
|
{ buffer, Selection{{0,0}, buffer.back_coord()} },
|
2014-12-19 00:12:58 +01:00
|
|
|
Context::Flags::Transient
|
|
|
|
};
|
2014-08-15 00:51:24 +02:00
|
|
|
|
2015-12-03 14:48:30 +01:00
|
|
|
if (not commands.empty())
|
|
|
|
command_manager.execute(commands, input_handler.context(),
|
|
|
|
ShellContext{});
|
|
|
|
|
2014-08-17 16:19:04 +02:00
|
|
|
for (auto& key : keys)
|
|
|
|
input_handler.handle_key(key);
|
|
|
|
}
|
2017-02-14 14:54:45 +01:00
|
|
|
catch (runtime_error& err)
|
2014-08-17 16:19:04 +02:00
|
|
|
{
|
2014-11-12 00:40:07 +01:00
|
|
|
if (not quiet)
|
2015-04-01 14:44:04 +02:00
|
|
|
write_stderr(format("error while applying keys to buffer '{}': {}\n",
|
|
|
|
buffer.display_name(), err.what()));
|
2014-08-17 16:19:04 +02:00
|
|
|
}
|
|
|
|
};
|
2014-08-15 00:51:24 +02:00
|
|
|
|
2014-08-17 16:19:04 +02:00
|
|
|
for (auto& file : files)
|
|
|
|
{
|
2015-10-16 14:58:56 +02:00
|
|
|
Buffer* buffer = open_file_buffer(file);
|
2017-03-22 14:47:25 +01:00
|
|
|
if (not suffix_backup.empty())
|
2017-04-20 10:37:04 +02:00
|
|
|
write_buffer_to_file(*buffer, buffer->name() + suffix_backup);
|
2015-12-03 14:48:30 +01:00
|
|
|
apply_to_buffer(*buffer);
|
2017-04-20 10:37:04 +02:00
|
|
|
write_buffer_to_file(*buffer, buffer->name());
|
2014-08-17 16:19:04 +02:00
|
|
|
buffer_manager.delete_buffer(*buffer);
|
|
|
|
}
|
|
|
|
if (not isatty(0))
|
|
|
|
{
|
2016-07-10 17:01:33 +02:00
|
|
|
Buffer& buffer = *buffer_manager.create_buffer(
|
2016-05-14 09:33:50 +02:00
|
|
|
"*stdin*", Buffer::Flags::None, read_fd(0), InvalidTime);
|
2016-07-10 17:01:33 +02:00
|
|
|
apply_to_buffer(buffer);
|
|
|
|
write_buffer_to_fd(buffer, 1);
|
|
|
|
buffer_manager.delete_buffer(buffer);
|
2014-08-17 16:19:04 +02:00
|
|
|
}
|
2014-08-15 00:51:24 +02:00
|
|
|
}
|
2017-02-14 14:54:45 +01:00
|
|
|
catch (runtime_error& err)
|
2014-08-15 14:21:54 +02:00
|
|
|
{
|
2015-04-01 14:44:04 +02:00
|
|
|
write_stderr(format("error: {}\n", err.what()));
|
2014-08-15 14:21:54 +02:00
|
|
|
}
|
|
|
|
|
2014-08-15 00:51:24 +02:00
|
|
|
buffer_manager.clear_buffer_trash();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-08-14 21:37:36 +02:00
|
|
|
int run_pipe(StringView session)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2017-06-12 06:21:34 +02:00
|
|
|
send_command(session, read_fd(0));
|
2014-08-14 21:37:36 +02:00
|
|
|
}
|
2016-12-20 11:34:48 +01:00
|
|
|
catch (disconnected& e)
|
2014-08-14 21:37:36 +02:00
|
|
|
{
|
2016-11-29 20:12:56 +01:00
|
|
|
write_stderr(format("{}\ndisconnecting\n", e.what()));
|
2017-08-23 08:22:23 +02:00
|
|
|
return -1;
|
2014-08-14 21:37:36 +02:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-02-14 14:54:45 +01:00
|
|
|
void signal_handler(int signal)
|
2016-06-11 14:22:24 +02:00
|
|
|
{
|
2017-02-14 14:54:45 +01:00
|
|
|
NCursesUI::abort();
|
|
|
|
const char* text = nullptr;
|
|
|
|
switch (signal)
|
|
|
|
{
|
|
|
|
case SIGSEGV: text = "SIGSEGV"; break;
|
|
|
|
case SIGFPE: text = "SIGFPE"; break;
|
|
|
|
case SIGQUIT: text = "SIGQUIT"; break;
|
|
|
|
case SIGTERM: text = "SIGTERM"; break;
|
|
|
|
case SIGPIPE: text = "SIGPIPE"; break;
|
|
|
|
}
|
|
|
|
if (signal != SIGTERM)
|
|
|
|
{
|
|
|
|
auto msg = format("Received {}, exiting.\nPid: {}\nCallstack:\n{}",
|
|
|
|
text, getpid(), Backtrace{}.desc());
|
|
|
|
write_stderr(msg);
|
|
|
|
notify_fatal_error(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Server::has_instance())
|
|
|
|
Server::instance().close_session();
|
|
|
|
if (BufferManager::has_instance())
|
|
|
|
BufferManager::instance().backup_modified_buffers();
|
|
|
|
|
|
|
|
if (signal == SIGTERM)
|
|
|
|
exit(-1);
|
|
|
|
else
|
|
|
|
abort();
|
|
|
|
}
|
2016-06-11 14:22:24 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2013-09-19 20:53:04 +02:00
|
|
|
int main(int argc, char* argv[])
|
|
|
|
{
|
2017-02-14 14:54:45 +01:00
|
|
|
using namespace Kakoune;
|
|
|
|
|
2016-05-19 22:45:23 +02:00
|
|
|
setlocale(LC_ALL, "");
|
2013-09-19 20:53:04 +02:00
|
|
|
|
2016-01-10 21:46:15 +01:00
|
|
|
set_signal_handler(SIGSEGV, signal_handler);
|
|
|
|
set_signal_handler(SIGFPE, signal_handler);
|
|
|
|
set_signal_handler(SIGQUIT, signal_handler);
|
|
|
|
set_signal_handler(SIGTERM, signal_handler);
|
|
|
|
set_signal_handler(SIGPIPE, SIG_IGN);
|
|
|
|
set_signal_handler(SIGINT, [](int){});
|
|
|
|
set_signal_handler(SIGCHLD, [](int){});
|
2013-09-19 20:53:04 +02:00
|
|
|
|
2015-01-12 14:58:41 +01:00
|
|
|
Vector<String> params;
|
2017-02-14 14:54:45 +01:00
|
|
|
for (int i = 1; i < argc; ++i)
|
2017-01-08 23:30:15 +01:00
|
|
|
params.emplace_back(argv[i]);
|
2013-09-19 20:53:04 +02:00
|
|
|
|
2014-02-11 23:16:17 +01:00
|
|
|
const ParameterDesc param_desc{
|
2015-03-14 18:30:34 +01:00
|
|
|
SwitchMap{ { "c", { true, "connect to given session" } },
|
2017-05-13 12:05:09 +02:00
|
|
|
{ "e", { true, "execute argument on client initialisation" } },
|
|
|
|
{ "E", { true, "execute argument on server initialisation" } },
|
2014-02-11 23:16:17 +01:00
|
|
|
{ "n", { false, "do not source kakrc files on startup" } },
|
2015-03-14 18:30:34 +01:00
|
|
|
{ "s", { true, "set session name" } },
|
2014-03-02 03:01:09 +01:00
|
|
|
{ "d", { false, "run as a headless session (requires -s)" } },
|
2015-03-14 18:30:34 +01:00
|
|
|
{ "p", { true, "just send stdin as commands to the given session" } },
|
|
|
|
{ "f", { true, "act as a filter, executing given keys on given files" } },
|
2017-03-22 14:47:25 +01:00
|
|
|
{ "i", { true, "backup the files on which a filter is applied using the given suffix" } },
|
2015-03-24 14:14:02 +01:00
|
|
|
{ "q", { false, "in filter mode, be quiet about errors applying keys" } },
|
2016-03-07 21:12:21 +01:00
|
|
|
{ "ui", { true, "set the type of user interface to use (ncurses, dummy, or json)" } },
|
2016-06-06 20:28:56 +02:00
|
|
|
{ "l", { false, "list existing sessions" } },
|
2016-07-20 19:45:50 +02:00
|
|
|
{ "clear", { false, "clear dead sessions" } },
|
2017-03-07 16:11:29 +01:00
|
|
|
{ "ro", { false, "readonly mode" } },
|
|
|
|
{ "help", { false, "display a help message and quit" } } }
|
2014-02-11 23:16:17 +01:00
|
|
|
};
|
2017-03-07 16:11:29 +01:00
|
|
|
|
2014-02-11 23:16:17 +01:00
|
|
|
try
|
|
|
|
{
|
2017-03-07 16:11:29 +01:00
|
|
|
auto show_usage = [&]()
|
|
|
|
{
|
|
|
|
write_stdout(format("Usage: {} [options] [file]... [+<line>[:<col>]|+:]\n\n"
|
|
|
|
"Options:\n"
|
|
|
|
"{}\n"
|
|
|
|
"Prefixing a positional argument with a plus (`+`) sign will place the\n"
|
|
|
|
"cursor at a given set of coordinates, or the end of the buffer if the plus\n"
|
|
|
|
"sign is followed only by a colon (`:`)\n",
|
|
|
|
argv[0], generate_switches_doc(param_desc.switches)));
|
|
|
|
return 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
if (contains(ConstArrayView<char*>{argv+1, (size_t)argc-1}, StringView{"--help"}))
|
|
|
|
return show_usage();
|
|
|
|
|
2017-06-12 07:12:10 +02:00
|
|
|
ParametersParser parser{params, param_desc};
|
2014-08-15 00:57:13 +02:00
|
|
|
|
2017-03-07 16:11:29 +01:00
|
|
|
const bool show_help_message = (bool)parser.get_switch("help");
|
|
|
|
if (show_help_message)
|
|
|
|
return show_usage();
|
|
|
|
|
2016-06-06 20:28:56 +02:00
|
|
|
const bool list_sessions = (bool)parser.get_switch("l");
|
|
|
|
const bool clear_sessions = (bool)parser.get_switch("clear");
|
|
|
|
if (list_sessions or clear_sessions)
|
2015-08-23 15:22:23 +02:00
|
|
|
{
|
2017-01-01 14:10:08 +01:00
|
|
|
const StringView username = getpwuid(geteuid())->pw_name;
|
|
|
|
const StringView tmp_dir = tmpdir();
|
|
|
|
for (auto& session : list_files(format("{}/kakoune/{}/", tmp_dir,
|
|
|
|
username)))
|
2016-06-06 20:28:56 +02:00
|
|
|
{
|
|
|
|
const bool valid = check_session(session);
|
|
|
|
if (list_sessions)
|
|
|
|
write_stdout(format("{}{}\n", session, valid ? "" : " (dead)"));
|
|
|
|
if (not valid and clear_sessions)
|
|
|
|
{
|
|
|
|
char socket_file[128];
|
2017-01-01 14:10:08 +01:00
|
|
|
format_to(socket_file, "{}/kakoune/{}/{}", tmp_dir,
|
|
|
|
username, session);
|
2016-06-06 20:28:56 +02:00
|
|
|
unlink(socket_file);
|
|
|
|
}
|
|
|
|
}
|
2015-08-23 15:22:23 +02:00
|
|
|
return 0;
|
|
|
|
}
|
2016-06-06 20:28:56 +02:00
|
|
|
|
2015-03-14 20:16:46 +01:00
|
|
|
if (auto session = parser.get_switch("p"))
|
2014-08-15 00:57:13 +02:00
|
|
|
{
|
2017-05-13 12:05:09 +02:00
|
|
|
for (auto opt : { "c", "n", "s", "d", "e", "E", "ro" })
|
2014-08-15 00:57:13 +02:00
|
|
|
{
|
2015-03-14 20:16:46 +01:00
|
|
|
if (parser.get_switch(opt))
|
2014-08-15 00:57:13 +02:00
|
|
|
{
|
2016-07-20 20:11:36 +02:00
|
|
|
write_stderr(format("error: -{} is incompatible with -p\n", opt));
|
2014-08-15 00:57:13 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
2015-03-14 20:16:46 +01:00
|
|
|
return run_pipe(*session);
|
2014-08-15 00:57:13 +02:00
|
|
|
}
|
2015-12-03 14:48:30 +01:00
|
|
|
|
2017-05-13 12:05:09 +02:00
|
|
|
auto client_init = parser.get_switch("e").value_or(StringView{});
|
|
|
|
auto server_init = parser.get_switch("E").value_or(StringView{});
|
2016-06-11 14:22:24 +02:00
|
|
|
const UIType ui_type = parse_ui_type(parser.get_switch("ui").value_or("ncurses"));
|
2015-12-03 14:48:30 +01:00
|
|
|
|
|
|
|
if (auto keys = parser.get_switch("f"))
|
2014-08-15 00:57:13 +02:00
|
|
|
{
|
2016-07-20 20:10:13 +02:00
|
|
|
if (parser.get_switch("ro"))
|
|
|
|
{
|
2016-07-20 20:11:36 +02:00
|
|
|
write_stderr("error: -ro is incompatible with -f\n");
|
2016-07-20 20:10:13 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2015-01-12 14:58:41 +01:00
|
|
|
Vector<StringView> files;
|
2014-08-15 00:57:13 +02:00
|
|
|
for (size_t i = 0; i < parser.positional_count(); ++i)
|
|
|
|
files.emplace_back(parser[i]);
|
|
|
|
|
2017-05-13 12:05:09 +02:00
|
|
|
return run_filter(*keys, client_init, files,
|
2017-03-22 14:47:25 +01:00
|
|
|
(bool)parser.get_switch("q"),
|
|
|
|
parser.get_switch("i").value_or(StringView{}));
|
2014-08-15 00:57:13 +02:00
|
|
|
}
|
|
|
|
|
2017-01-21 13:14:44 +01:00
|
|
|
Vector<StringView> files;
|
|
|
|
Optional<BufferCoord> init_coord;
|
|
|
|
for (auto& name : parser)
|
|
|
|
{
|
|
|
|
if (not name.empty() and name[0_byte] == '+')
|
|
|
|
{
|
2017-01-31 19:44:27 +01:00
|
|
|
if (name == "+" or name == "+:")
|
|
|
|
{
|
2017-05-13 12:05:09 +02:00
|
|
|
client_init = client_init + "; exec gj";
|
2017-01-31 19:44:27 +01:00
|
|
|
continue;
|
|
|
|
}
|
2017-01-21 13:14:44 +01:00
|
|
|
auto colon = find(name, ':');
|
|
|
|
if (auto line = str_to_int_ifp({name.begin()+1, colon}))
|
|
|
|
{
|
|
|
|
init_coord = BufferCoord{
|
|
|
|
*line - 1,
|
|
|
|
colon != name.end() ?
|
|
|
|
str_to_int_ifp({colon+1, name.end()}).value_or(1) - 1
|
|
|
|
: 0
|
|
|
|
};
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
files.emplace_back(name);
|
|
|
|
}
|
|
|
|
|
2015-03-14 20:16:46 +01:00
|
|
|
if (auto server_session = parser.get_switch("c"))
|
2014-08-15 00:57:13 +02:00
|
|
|
{
|
2017-05-13 12:05:09 +02:00
|
|
|
for (auto opt : { "n", "s", "d", "E", "ro" })
|
2014-08-15 00:57:13 +02:00
|
|
|
{
|
2015-03-14 20:16:46 +01:00
|
|
|
if (parser.get_switch(opt))
|
2014-08-15 00:57:13 +02:00
|
|
|
{
|
2016-07-20 20:11:36 +02:00
|
|
|
write_stderr(format("error: -{} is incompatible with -c\n", opt));
|
2014-08-15 00:57:13 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
2015-11-22 12:48:02 +01:00
|
|
|
String new_files;
|
2017-01-21 13:14:44 +01:00
|
|
|
for (auto name : files)
|
2015-11-22 12:48:02 +01:00
|
|
|
new_files += format("edit '{}';", escape(real_path(name), "'", '\\'));
|
|
|
|
|
2017-05-13 12:05:09 +02:00
|
|
|
return run_client(*server_session, new_files + client_init, init_coord, ui_type);
|
2014-08-15 00:57:13 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2015-03-14 20:16:46 +01:00
|
|
|
StringView session = parser.get_switch("s").value_or(StringView{});
|
2015-10-08 21:05:47 +02:00
|
|
|
try
|
|
|
|
{
|
2017-02-14 14:54:45 +01:00
|
|
|
auto flags = (parser.get_switch("n") ? ServerFlags::IgnoreKakrc : ServerFlags::None) |
|
|
|
|
(parser.get_switch("d") ? ServerFlags::Daemon : ServerFlags::None) |
|
2017-02-19 14:59:44 +01:00
|
|
|
(parser.get_switch("ro") ? ServerFlags::ReadOnly : ServerFlags::None) |
|
2017-06-08 11:03:07 +02:00
|
|
|
(argc == 1 and isatty(0) ? ServerFlags::StartupInfo : ServerFlags::None);
|
2017-05-13 12:05:09 +02:00
|
|
|
return run_server(session, server_init, client_init, init_coord, flags, ui_type, files);
|
2015-10-08 21:05:47 +02:00
|
|
|
}
|
|
|
|
catch (convert_to_client_mode& convert)
|
|
|
|
{
|
|
|
|
raise(SIGTSTP);
|
2015-11-16 14:52:33 +01:00
|
|
|
return run_client(convert.session,
|
2016-08-05 09:16:43 +02:00
|
|
|
format("try %^buffer '{}'; select '{}'^; echo converted to client only mode",
|
2017-01-21 13:14:44 +01:00
|
|
|
escape(convert.buffer_name, "'^", '\\'), convert.selections), {}, ui_type);
|
2015-10-08 21:05:47 +02:00
|
|
|
}
|
2014-08-15 00:57:13 +02:00
|
|
|
}
|
2011-09-02 18:51:20 +02:00
|
|
|
}
|
2017-02-14 14:54:45 +01:00
|
|
|
catch (parameter_error& error)
|
2014-01-23 20:36:07 +01:00
|
|
|
{
|
2015-08-18 01:28:04 +02:00
|
|
|
write_stderr(format("Error while parsing parameters: {}\n"
|
2015-04-01 14:44:04 +02:00
|
|
|
"Valid switches:\n"
|
|
|
|
"{}", error.what(),
|
|
|
|
generate_switches_doc(param_desc.switches)));
|
2014-01-23 20:36:07 +01:00
|
|
|
return -1;
|
|
|
|
}
|
2016-04-09 10:13:35 +02:00
|
|
|
catch (startup_error& error)
|
|
|
|
{
|
|
|
|
write_stderr(format("Could not start kakoune: {}\n", error.what()));
|
|
|
|
return -1;
|
|
|
|
}
|
2011-09-09 21:24:18 +02:00
|
|
|
catch (Kakoune::exception& error)
|
|
|
|
{
|
2016-12-20 13:49:05 +01:00
|
|
|
write_stderr(format("uncaught exception ({}):\n{}\n", typeid(error).name(), error.what()));
|
2013-02-22 18:45:59 +01:00
|
|
|
return -1;
|
|
|
|
}
|
2013-03-22 14:29:22 +01:00
|
|
|
catch (std::exception& error)
|
|
|
|
{
|
2016-12-20 13:49:05 +01:00
|
|
|
write_stderr(format("uncaught exception ({}):\n{}\n", typeid(error).name(), error.what()));
|
2013-03-22 14:29:22 +01:00
|
|
|
return -1;
|
|
|
|
}
|
2013-02-22 18:45:59 +01:00
|
|
|
catch (...)
|
|
|
|
{
|
2016-03-03 14:56:42 +01:00
|
|
|
write_stderr("uncaught exception");
|
2011-09-09 21:24:18 +02:00
|
|
|
return -1;
|
|
|
|
}
|
2011-09-02 18:51:20 +02:00
|
|
|
return 0;
|
|
|
|
}
|