2013-04-09 22:05:40 +04:00
|
|
|
#include "assert.hh"
|
2011-09-02 20:51:20 +04:00
|
|
|
#include "buffer.hh"
|
2011-09-08 18:30:36 +04:00
|
|
|
#include "buffer_manager.hh"
|
2014-04-28 22:48:23 +04:00
|
|
|
#include "buffer_utils.hh"
|
2013-04-09 22:05:40 +04:00
|
|
|
#include "client_manager.hh"
|
|
|
|
#include "command_manager.hh"
|
|
|
|
#include "commands.hh"
|
2014-12-23 16:34:21 +03:00
|
|
|
#include "containers.hh"
|
2013-04-09 22:05:40 +04:00
|
|
|
#include "context.hh"
|
|
|
|
#include "event_manager.hh"
|
2014-07-11 03:27:04 +04:00
|
|
|
#include "face_registry.hh"
|
2013-04-09 22:05:40 +04:00
|
|
|
#include "file.hh"
|
|
|
|
#include "highlighters.hh"
|
2014-12-24 16:03:17 +03:00
|
|
|
#include "insert_completer.hh"
|
2015-01-15 16:54:38 +03:00
|
|
|
#include "shared_string.hh"
|
2014-12-09 16:57:21 +03:00
|
|
|
#include "ncurses_ui.hh"
|
2012-12-19 00:41:13 +04:00
|
|
|
#include "parameters_parser.hh"
|
2013-04-09 22:05:40 +04:00
|
|
|
#include "register_manager.hh"
|
|
|
|
#include "remote.hh"
|
2014-10-30 17:00:42 +03:00
|
|
|
#include "scope.hh"
|
2014-12-24 16:03:17 +03:00
|
|
|
#include "shell_manager.hh"
|
2013-04-09 22:05:40 +04:00
|
|
|
#include "string.hh"
|
2015-05-22 15:58:56 +03:00
|
|
|
#include "unit_tests.hh"
|
2013-04-18 16:28:53 +04:00
|
|
|
#include "window.hh"
|
2011-09-02 20:51:20 +04:00
|
|
|
|
2014-12-24 16:03:17 +03:00
|
|
|
#include <fcntl.h>
|
2013-02-26 17:12:21 +04:00
|
|
|
#include <locale>
|
2013-03-13 22:59:39 +04:00
|
|
|
#include <signal.h>
|
2014-04-30 00:37:11 +04:00
|
|
|
#include <sys/stat.h>
|
2014-12-24 16:03:17 +03:00
|
|
|
#include <sys/types.h>
|
2014-04-30 22:08:06 +04:00
|
|
|
#include <unistd.h>
|
2014-04-30 00:37:11 +04:00
|
|
|
|
2011-09-02 20:51:20 +04:00
|
|
|
using namespace Kakoune;
|
|
|
|
|
2012-09-10 22:10:18 +04:00
|
|
|
String runtime_directory()
|
|
|
|
{
|
2014-10-30 03:50:40 +03:00
|
|
|
String bin_path = get_kak_binary_path();
|
2015-03-10 22:33:46 +03:00
|
|
|
auto it = find(reversed(bin_path), '/');
|
|
|
|
if (it == bin_path.rend())
|
2012-11-08 16:38:02 +04:00
|
|
|
throw runtime_error("unable to determine runtime directory");
|
2015-03-10 22:33:46 +03:00
|
|
|
return StringView{bin_path.begin(), it.base()-1} + "/../share/kak";
|
2012-09-10 22:10:18 +04:00
|
|
|
}
|
|
|
|
|
2015-04-01 15:44:04 +03:00
|
|
|
static void write(int fd, StringView str)
|
|
|
|
{
|
|
|
|
write(fd, str.data(), (size_t)(int)str.length());
|
|
|
|
}
|
|
|
|
|
|
|
|
static void write_stdout(StringView str) { write(1, str); }
|
|
|
|
static void write_stderr(StringView str) { write(2, str); }
|
|
|
|
|
2012-10-24 00:56:24 +04:00
|
|
|
void register_env_vars()
|
|
|
|
{
|
2014-01-13 01:25:21 +04:00
|
|
|
static const struct {
|
|
|
|
const char* name;
|
2014-04-20 15:15:31 +04:00
|
|
|
String (*func)(StringView, const Context&);
|
2014-01-13 01:25:21 +04:00
|
|
|
} env_vars[] = { {
|
2013-10-01 21:47:37 +04:00
|
|
|
"bufname",
|
2014-04-20 15:15:31 +04:00
|
|
|
[](StringView name, const Context& context)
|
2013-10-01 21:47:37 +04:00
|
|
|
{ return context.buffer().display_name(); }
|
2014-02-27 10:43:21 +04:00
|
|
|
}, {
|
|
|
|
"buffile",
|
2015-03-12 16:02:46 +03:00
|
|
|
[](StringView name, const Context& context) -> String
|
2014-02-27 10:43:21 +04:00
|
|
|
{ return context.buffer().name(); }
|
2014-12-23 04:49:53 +03:00
|
|
|
}, {
|
|
|
|
"buflist",
|
|
|
|
[](StringView name, const Context& context)
|
2014-12-28 14:16:51 +03:00
|
|
|
{ return join(transformed(BufferManager::instance(),
|
2015-02-19 16:58:25 +03:00
|
|
|
[](const SafePtr<Buffer>& b)
|
2014-12-28 14:16:51 +03:00
|
|
|
{ return b->display_name(); }), ':'); }
|
2013-10-01 21:47:37 +04:00
|
|
|
}, {
|
|
|
|
"timestamp",
|
2015-03-31 15:53:40 +03:00
|
|
|
[](StringView name, const Context& context) -> String
|
2013-10-01 21:47:37 +04:00
|
|
|
{ return to_string(context.buffer().timestamp()); }
|
|
|
|
}, {
|
|
|
|
"selection",
|
2014-04-20 15:15:31 +04:00
|
|
|
[](StringView name, const Context& context)
|
2014-03-29 12:55:45 +04:00
|
|
|
{ const Selection& sel = context.selections().main();
|
2013-10-01 21:47:37 +04:00
|
|
|
return content(context.buffer(), sel); }
|
|
|
|
}, {
|
|
|
|
"selections",
|
2014-04-20 15:15:31 +04:00
|
|
|
[](StringView name, const Context& context)
|
2014-12-28 14:16:51 +03:00
|
|
|
{ return join(context.selections_content(), ':'); }
|
2013-10-01 21:47:37 +04:00
|
|
|
}, {
|
|
|
|
"runtime",
|
2014-04-20 15:15:31 +04:00
|
|
|
[](StringView name, const Context& context)
|
2013-10-01 21:47:37 +04:00
|
|
|
{ return runtime_directory(); }
|
|
|
|
}, {
|
|
|
|
"opt_.+",
|
2014-04-20 15:15:31 +04:00
|
|
|
[](StringView name, const Context& context)
|
2013-10-01 21:47:37 +04:00
|
|
|
{ return context.options()[name.substr(4_byte)].get_as_string(); }
|
|
|
|
}, {
|
|
|
|
"reg_.+",
|
2015-03-10 22:33:46 +03:00
|
|
|
[](StringView name, const Context& context)
|
|
|
|
{ return context.main_sel_register_value(name.substr(4_byte)).str(); }
|
2014-04-08 00:25:44 +04:00
|
|
|
}, {
|
|
|
|
"client_env_.+",
|
2015-03-10 22:33:46 +03:00
|
|
|
[](StringView name, const Context& context)
|
|
|
|
{ return context.client().get_env_var(name.substr(11_byte)).str(); }
|
2013-10-01 21:47:37 +04:00
|
|
|
}, {
|
|
|
|
"session",
|
2015-03-12 16:02:46 +03:00
|
|
|
[](StringView name, const Context& context) -> String
|
2013-10-01 21:47:37 +04:00
|
|
|
{ return Server::instance().session(); }
|
|
|
|
}, {
|
|
|
|
"client",
|
2015-03-12 16:02:46 +03:00
|
|
|
[](StringView name, const Context& context) -> String
|
2013-11-15 00:51:25 +04:00
|
|
|
{ return context.name(); }
|
2013-10-01 21:47:37 +04:00
|
|
|
}, {
|
|
|
|
"cursor_line",
|
2015-03-31 15:53:40 +03:00
|
|
|
[](StringView name, const Context& context) -> String
|
2014-01-28 23:05:49 +04:00
|
|
|
{ return to_string(context.selections().main().cursor().line + 1); }
|
2013-10-01 21:47:37 +04:00
|
|
|
}, {
|
|
|
|
"cursor_column",
|
2015-03-31 15:53:40 +03:00
|
|
|
[](StringView name, const Context& context) -> String
|
2014-01-28 23:05:49 +04:00
|
|
|
{ return to_string(context.selections().main().cursor().column + 1); }
|
2013-12-11 17:46:33 +04:00
|
|
|
}, {
|
|
|
|
"cursor_char_column",
|
2015-03-31 15:53:40 +03:00
|
|
|
[](StringView name, const Context& context) -> String
|
2014-01-28 23:05:49 +04:00
|
|
|
{ auto coord = context.selections().main().cursor();
|
2013-12-11 17:46:33 +04:00
|
|
|
return to_string(context.buffer()[coord.line].char_count_to(coord.column) + 1); }
|
2013-10-01 21:47:37 +04:00
|
|
|
}, {
|
|
|
|
"selection_desc",
|
2014-04-20 15:15:31 +04:00
|
|
|
[](StringView name, const Context& context)
|
2015-06-25 16:00:50 +03:00
|
|
|
{ return selection_to_string(context.selections().main()); }
|
2014-05-25 21:26:31 +04:00
|
|
|
}, {
|
|
|
|
"selections_desc",
|
|
|
|
[](StringView name, const Context& context)
|
2015-04-13 17:21:26 +03:00
|
|
|
{ return selection_list_to_string(context.selections()); }
|
2013-10-01 21:47:37 +04:00
|
|
|
}, {
|
|
|
|
"window_width",
|
2015-03-31 15:53:40 +03:00
|
|
|
[](StringView name, const Context& context) -> String
|
2013-10-01 21:47:37 +04:00
|
|
|
{ return to_string(context.window().dimensions().column); }
|
|
|
|
}, {
|
|
|
|
"window_height",
|
2015-03-31 15:53:40 +03:00
|
|
|
[](StringView name, const Context& context) -> String
|
2013-10-01 21:47:37 +04:00
|
|
|
{ return to_string(context.window().dimensions().line); }
|
|
|
|
} };
|
2012-10-24 00:56:24 +04:00
|
|
|
|
2013-10-01 21:47:37 +04:00
|
|
|
ShellManager& shell_manager = ShellManager::instance();
|
|
|
|
for (auto& env_var : env_vars)
|
|
|
|
shell_manager.register_env_var(env_var.name, env_var.func);
|
2012-10-24 00:56:24 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void register_registers()
|
|
|
|
{
|
2015-01-14 22:16:32 +03:00
|
|
|
using StringList = Vector<String, MemoryDomain::Registers>;
|
2014-03-23 01:00:24 +04:00
|
|
|
static const struct {
|
|
|
|
char name;
|
|
|
|
StringList (*func)(const Context&);
|
|
|
|
} dyn_regs[] = {
|
2013-10-01 21:52:19 +04:00
|
|
|
{ '%', [](const Context& context) { return StringList{{context.buffer().display_name()}}; } },
|
2015-01-14 22:16:32 +03:00
|
|
|
{ '.', [](const Context& context) {
|
|
|
|
auto content = context.selections_content();
|
|
|
|
return StringList{content.begin(), content.end()};
|
|
|
|
} },
|
2014-05-23 23:27:35 +04:00
|
|
|
{ '#', [](const Context& context) {
|
|
|
|
StringList res;
|
2014-07-14 22:00:54 +04:00
|
|
|
for (size_t i = 1; i < context.selections().size()+1; ++i)
|
2014-05-23 23:27:35 +04:00
|
|
|
res.push_back(to_string((int)i));
|
|
|
|
return res;
|
|
|
|
} }
|
2013-10-01 21:52:19 +04:00
|
|
|
};
|
|
|
|
|
2012-10-24 00:56:24 +04:00
|
|
|
RegisterManager& register_manager = RegisterManager::instance();
|
2013-10-01 21:52:19 +04:00
|
|
|
for (auto& dyn_reg : dyn_regs)
|
|
|
|
register_manager.register_dynamic_register(dyn_reg.name, dyn_reg.func);
|
2012-10-24 00:56:24 +04:00
|
|
|
|
|
|
|
for (size_t i = 0; i < 10; ++i)
|
|
|
|
{
|
2013-01-04 21:39:13 +04:00
|
|
|
register_manager.register_dynamic_register('0'+i,
|
|
|
|
[i](const Context& context) {
|
2015-01-14 22:16:32 +03:00
|
|
|
StringList result;
|
2013-12-15 18:25:23 +04:00
|
|
|
for (auto& sel : context.selections())
|
2013-01-04 21:39:13 +04:00
|
|
|
result.emplace_back(i < sel.captures().size() ? sel.captures()[i] : "");
|
|
|
|
return result;
|
|
|
|
});
|
2012-10-24 00:56:24 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-30 17:00:42 +03:00
|
|
|
void register_options()
|
|
|
|
{
|
|
|
|
OptionsRegistry& reg = GlobalScope::instance().option_registry();
|
|
|
|
|
|
|
|
reg.declare_option("tabstop", "size of a tab character", 8);
|
|
|
|
reg.declare_option("indentwidth", "indentation width", 4);
|
|
|
|
reg.declare_option("scrolloff",
|
|
|
|
"number of lines and columns to keep visible main cursor when scrolling",
|
|
|
|
CharCoord{0,0});
|
|
|
|
reg.declare_option("eolformat", "end of line format: 'crlf' or 'lf'", "lf"_str);
|
|
|
|
reg.declare_option("BOM", "insert a byte order mark when writing buffer",
|
|
|
|
"no"_str);
|
|
|
|
reg.declare_option("complete_prefix",
|
|
|
|
"complete up to common prefix in tab completion",
|
|
|
|
true);
|
|
|
|
reg.declare_option("incsearch",
|
|
|
|
"incrementaly apply search/select/split regex",
|
|
|
|
true);
|
|
|
|
reg.declare_option("autoinfo",
|
|
|
|
"automatically display contextual help",
|
|
|
|
1);
|
|
|
|
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 22:25:41 +03:00
|
|
|
Vector<String, MemoryDomain::Options>({ "./", "/usr/include" }));
|
2014-10-30 17:00:42 +03:00
|
|
|
reg.declare_option("completers", "insert mode completers to execute.",
|
2015-01-12 16:24:30 +03:00
|
|
|
InsertCompleterDescList({
|
2014-10-30 17:00:42 +03:00
|
|
|
InsertCompleterDesc{ InsertCompleterDesc::Filename },
|
|
|
|
InsertCompleterDesc{ InsertCompleterDesc::Word, "all"_str }
|
|
|
|
}), OptionFlags::None);
|
|
|
|
reg.declare_option("autoreload",
|
|
|
|
"autoreload buffer when a filesystem modification is detected",
|
|
|
|
Ask);
|
2014-11-11 02:29:16 +03:00
|
|
|
reg.declare_option("ui_options",
|
2015-04-16 13:35:52 +03:00
|
|
|
"colon separated list of <key>=<value> options that are"
|
|
|
|
"passed to and interpreted by the user interface",
|
|
|
|
UserInterface::Options{});
|
2014-10-30 17:00:42 +03:00
|
|
|
}
|
|
|
|
|
2015-03-24 16:14:02 +03:00
|
|
|
template<typename UI>
|
2015-07-08 15:43:40 +03:00
|
|
|
void create_local_client(StringView init_command, bool startup_error)
|
2012-10-17 19:49:34 +04:00
|
|
|
{
|
2015-03-24 16:14:02 +03:00
|
|
|
struct LocalUI : UI
|
2012-11-29 21:56:08 +04:00
|
|
|
{
|
2015-03-24 16:14:02 +03:00
|
|
|
~LocalUI()
|
2012-11-29 21:56:08 +04:00
|
|
|
{
|
|
|
|
if (not ClientManager::instance().empty() and fork())
|
|
|
|
{
|
2015-03-24 16:14:02 +03:00
|
|
|
this->UI::~UI();
|
2015-04-01 15:44:04 +03:00
|
|
|
write_stdout("detached from terminal\n");
|
2012-11-29 21:56:08 +04:00
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-03-24 22:38:03 +03:00
|
|
|
if (std::is_same<UI, NCursesUI>::value)
|
2014-04-30 00:37:11 +04:00
|
|
|
{
|
2015-03-24 22:38:03 +03:00
|
|
|
if (not isatty(1))
|
|
|
|
throw runtime_error("stdout is not a tty");
|
|
|
|
|
|
|
|
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);
|
|
|
|
create_fifo_buffer("*stdin*", fd);
|
|
|
|
}
|
2014-04-30 00:37:11 +04:00
|
|
|
}
|
|
|
|
|
2013-09-13 01:47:23 +04:00
|
|
|
static Client* client = ClientManager::instance().create_client(
|
2015-05-26 20:42:09 +03:00
|
|
|
make_unique<LocalUI>(), get_env_vars(), init_command);
|
2015-06-12 15:43:46 +03:00
|
|
|
|
2015-07-08 15:43:40 +03:00
|
|
|
if (startup_error)
|
2015-06-12 15:43:46 +03:00
|
|
|
client->print_status({
|
2015-07-08 15:43:40 +03:00
|
|
|
"error during startup, see *debug* buffer for details",
|
2015-06-12 15:43:46 +03:00
|
|
|
get_face("Error")
|
|
|
|
});
|
|
|
|
|
2013-04-15 20:50:45 +04:00
|
|
|
signal(SIGHUP, [](int) {
|
|
|
|
if (client)
|
|
|
|
ClientManager::instance().remove_client(*client);
|
|
|
|
client = nullptr;
|
|
|
|
});
|
2012-10-17 19:49:34 +04:00
|
|
|
}
|
|
|
|
|
2013-02-26 17:13:37 +04:00
|
|
|
void signal_handler(int signal)
|
2013-01-23 16:46:18 +04:00
|
|
|
{
|
2013-04-12 03:28:22 +04:00
|
|
|
NCursesUI::abort();
|
2013-02-26 17:13:37 +04:00
|
|
|
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;
|
2015-05-27 15:57:41 +03:00
|
|
|
case SIGPIPE: text = "SIGPIPE"; break;
|
2013-02-26 17:13:37 +04:00
|
|
|
}
|
2014-10-13 16:38:28 +04:00
|
|
|
if (signal != SIGTERM)
|
2015-05-28 15:44:59 +03:00
|
|
|
{
|
2015-06-21 21:44:43 +03:00
|
|
|
auto msg = format("Received {}, exiting.\nPid: {}\nCallstack:\n{}",
|
|
|
|
text, getpid(), Backtrace{}.desc());
|
2015-05-29 15:35:09 +03:00
|
|
|
write_stderr(msg);
|
|
|
|
notify_fatal_error(msg);
|
2015-05-28 15:44:59 +03:00
|
|
|
}
|
2014-10-13 16:38:28 +04:00
|
|
|
|
2014-01-27 23:53:17 +04:00
|
|
|
if (Server::has_instance())
|
|
|
|
Server::instance().close_session();
|
2014-10-13 16:38:28 +04:00
|
|
|
if (BufferManager::has_instance())
|
|
|
|
BufferManager::instance().backup_modified_buffers();
|
|
|
|
|
|
|
|
if (signal == SIGTERM)
|
|
|
|
exit(-1);
|
|
|
|
else
|
|
|
|
abort();
|
2013-01-23 16:46:18 +04:00
|
|
|
}
|
|
|
|
|
2014-08-14 23:37:36 +04:00
|
|
|
int run_client(StringView session, StringView init_command)
|
2013-09-23 22:28:15 +04:00
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
EventManager event_manager;
|
2015-05-26 20:42:09 +03:00
|
|
|
RemoteClient client{session, make_unique<NCursesUI>(),
|
2014-11-05 16:57:12 +03:00
|
|
|
get_env_vars(), init_command};
|
2013-09-23 22:28:15 +04:00
|
|
|
while (true)
|
2014-11-25 04:00:18 +03:00
|
|
|
event_manager.handle_next_events(EventMode::Normal);
|
2013-09-23 22:28:15 +04:00
|
|
|
}
|
|
|
|
catch (peer_disconnected&)
|
|
|
|
{
|
2015-04-01 15:44:04 +03:00
|
|
|
write_stderr("disconnected from server\n");
|
2013-09-23 22:28:15 +04:00
|
|
|
return -1;
|
|
|
|
}
|
2014-03-21 17:42:37 +04:00
|
|
|
catch (connection_failed& e)
|
|
|
|
{
|
2015-07-05 15:29:41 +03:00
|
|
|
write_stderr(format("{}\n", e.what()));
|
2014-03-21 17:42:37 +04:00
|
|
|
return -1;
|
|
|
|
}
|
2013-09-23 22:28:15 +04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-03-24 16:14:02 +03:00
|
|
|
struct DummyUI : UserInterface
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
void menu_show(ConstArrayView<String>, CharCoord, Face, Face, MenuStyle) override {}
|
|
|
|
void menu_select(int) override {}
|
|
|
|
void menu_hide() override {}
|
|
|
|
|
|
|
|
void info_show(StringView, StringView, CharCoord, Face, InfoStyle) override {}
|
|
|
|
void info_hide() override {}
|
|
|
|
|
2015-06-17 23:28:02 +03:00
|
|
|
void draw(const DisplayBuffer&, const Face&) override {}
|
|
|
|
void draw_status(const DisplayLine&, const DisplayLine&, const Face&) override {}
|
2015-03-24 16:14:02 +03:00
|
|
|
CharCoord dimensions() override { return {24,80}; }
|
|
|
|
bool is_key_available() override { return false; }
|
|
|
|
Key get_key() override { return Key::Invalid; }
|
|
|
|
void refresh() override {}
|
|
|
|
void set_input_callback(InputCallback) override {}
|
|
|
|
void set_ui_options(const Options&) override {}
|
|
|
|
};
|
|
|
|
|
2014-08-14 23:37:36 +04:00
|
|
|
int run_server(StringView session, StringView init_command,
|
2015-03-24 16:14:02 +03:00
|
|
|
bool ignore_kakrc, bool daemon, bool dummy_ui,
|
|
|
|
ConstArrayView<StringView> files)
|
2012-10-21 15:02:24 +04:00
|
|
|
{
|
2013-09-23 22:28:15 +04:00
|
|
|
static bool terminate = false;
|
|
|
|
if (daemon)
|
|
|
|
{
|
2014-08-14 23:37:36 +04:00
|
|
|
if (session.empty())
|
2013-09-19 22:53:04 +04:00
|
|
|
{
|
2015-04-01 15:44:04 +03:00
|
|
|
write_stderr("-d needs a session name to be specified with -s\n");
|
2013-09-19 22:53:04 +04:00
|
|
|
return -1;
|
|
|
|
}
|
2013-09-23 22:28:15 +04:00
|
|
|
if (pid_t child = fork())
|
2013-09-19 23:09:53 +04:00
|
|
|
{
|
2015-04-01 15:44:04 +03: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 22:28:15 +04:00
|
|
|
exit(0);
|
2013-09-19 23:09:53 +04:00
|
|
|
}
|
2013-09-23 22:28:15 +04:00
|
|
|
signal(SIGTERM, [](int) { terminate = true; });
|
|
|
|
}
|
2013-09-19 23:09:53 +04:00
|
|
|
|
2014-10-01 03:20:12 +04:00
|
|
|
StringRegistry string_registry;
|
2013-09-23 22:28:15 +04:00
|
|
|
EventManager event_manager;
|
2014-10-30 17:00:42 +03:00
|
|
|
GlobalScope global_scope;
|
2013-09-23 22:28:15 +04:00
|
|
|
ShellManager shell_manager;
|
|
|
|
CommandManager command_manager;
|
|
|
|
BufferManager buffer_manager;
|
|
|
|
RegisterManager register_manager;
|
|
|
|
HighlighterRegistry highlighter_registry;
|
2013-12-04 02:03:10 +04:00
|
|
|
DefinedHighlighters defined_highlighters;
|
2014-07-11 03:27:04 +04:00
|
|
|
FaceRegistry face_registry;
|
2013-09-23 22:28:15 +04:00
|
|
|
ClientManager client_manager;
|
2012-10-24 00:56:24 +04:00
|
|
|
|
2015-05-25 00:34:05 +03:00
|
|
|
UnitTest::run_all_tests();
|
2012-10-24 00:56:24 +04:00
|
|
|
|
2014-10-30 17:00:42 +03:00
|
|
|
register_options();
|
2013-09-23 22:28:15 +04:00
|
|
|
register_env_vars();
|
|
|
|
register_registers();
|
|
|
|
register_commands();
|
|
|
|
register_highlighters();
|
2012-10-24 00:56:24 +04:00
|
|
|
|
2015-06-06 13:54:48 +03:00
|
|
|
write_to_debug_buffer("*** This is the debug buffer, where debug info will be written ***");
|
2012-10-24 00:56:24 +04:00
|
|
|
|
2015-03-10 22:33:46 +03:00
|
|
|
Server server(session.empty() ? to_string(getpid()) : session.str());
|
2012-10-24 00:56:24 +04:00
|
|
|
|
2015-07-08 15:43:40 +03:00
|
|
|
bool startup_error = false;
|
2014-08-14 23:37:36 +04:00
|
|
|
if (not ignore_kakrc) try
|
2013-09-23 22:28:15 +04:00
|
|
|
{
|
2015-04-19 20:47:52 +03:00
|
|
|
Context initialisation_context{Context::EmptyContextFlag{}};
|
2015-06-01 21:06:35 +03:00
|
|
|
command_manager.execute(format("source {}/kakrc", runtime_directory()),
|
2013-09-23 22:28:15 +04:00
|
|
|
initialisation_context);
|
|
|
|
}
|
|
|
|
catch (Kakoune::runtime_error& error)
|
|
|
|
{
|
2015-07-08 15:43:40 +03:00
|
|
|
startup_error = true;
|
2015-06-06 13:54:48 +03:00
|
|
|
write_to_debug_buffer(format("error while parsing kakrc:\n {}", error.what()));
|
2013-09-23 22:28:15 +04:00
|
|
|
}
|
|
|
|
catch (Kakoune::client_removed&)
|
|
|
|
{
|
2015-07-08 15:43:40 +03:00
|
|
|
startup_error = true;
|
2015-06-06 13:54:48 +03:00
|
|
|
write_to_debug_buffer("error while parsing kakrc: asked to quit");
|
2013-09-23 22:28:15 +04:00
|
|
|
}
|
2012-10-16 19:15:09 +04:00
|
|
|
|
2013-09-23 22:28:15 +04:00
|
|
|
{
|
2015-04-19 20:47:52 +03:00
|
|
|
Context empty_context{Context::EmptyContextFlag{}};
|
2014-10-30 17:00:42 +03:00
|
|
|
global_scope.hooks().run_hook("KakBegin", "", empty_context);
|
2013-09-23 22:28:15 +04:00
|
|
|
}
|
2013-04-19 15:45:44 +04:00
|
|
|
|
2014-08-14 23:37:36 +04:00
|
|
|
if (not files.empty()) try
|
2013-09-23 22:28:15 +04:00
|
|
|
{
|
|
|
|
// create buffers in reverse order so that the first given buffer
|
|
|
|
// is the most recently created one.
|
2014-08-14 23:37:36 +04:00
|
|
|
for (auto& file : reversed(files))
|
2013-04-29 15:50:13 +04:00
|
|
|
{
|
2015-07-08 15:43:40 +03:00
|
|
|
try
|
|
|
|
{
|
|
|
|
if (not create_buffer_from_file(file))
|
|
|
|
new Buffer(file.str(), Buffer::Flags::New | Buffer::Flags::File);
|
|
|
|
}
|
|
|
|
catch (Kakoune::runtime_error& error)
|
|
|
|
{
|
|
|
|
startup_error = true;
|
|
|
|
write_to_debug_buffer(format("error while opening file '{}':\n {}",
|
|
|
|
file, error.what()));
|
|
|
|
}
|
2013-04-29 15:50:13 +04:00
|
|
|
}
|
2013-09-23 22:28:15 +04:00
|
|
|
}
|
|
|
|
catch (Kakoune::runtime_error& error)
|
|
|
|
{
|
2015-06-06 13:54:48 +03:00
|
|
|
write_to_debug_buffer(format("error while opening command line files: {}", error.what()));
|
2013-09-23 22:28:15 +04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
new Buffer("*scratch*", Buffer::Flags::None);
|
2012-12-28 16:51:14 +04:00
|
|
|
|
2013-09-23 22:28:15 +04:00
|
|
|
if (not daemon)
|
2015-03-24 16:14:02 +03:00
|
|
|
{
|
|
|
|
if (dummy_ui)
|
2015-07-08 15:43:40 +03:00
|
|
|
create_local_client<DummyUI>(init_command, startup_error);
|
2015-03-24 16:14:02 +03:00
|
|
|
else
|
2015-07-08 15:43:40 +03:00
|
|
|
create_local_client<NCursesUI>(init_command, startup_error);
|
2015-03-24 16:14:02 +03:00
|
|
|
}
|
2012-10-30 17:00:44 +04:00
|
|
|
|
2013-09-23 22:28:15 +04:00
|
|
|
while (not terminate and (not client_manager.empty() or daemon))
|
2014-08-12 22:24:09 +04:00
|
|
|
{
|
2014-12-18 22:01:18 +03:00
|
|
|
client_manager.redraw_clients();
|
2014-11-25 04:00:18 +03:00
|
|
|
event_manager.handle_next_events(EventMode::Normal);
|
2014-11-29 23:14:52 +03:00
|
|
|
client_manager.handle_pending_inputs();
|
2014-08-12 22:24:09 +04:00
|
|
|
buffer_manager.clear_buffer_trash();
|
2015-01-15 16:54:38 +03:00
|
|
|
string_registry.purge_unused();
|
2014-08-12 22:24:09 +04:00
|
|
|
}
|
2013-04-19 15:45:44 +04:00
|
|
|
|
2013-09-23 22:28:15 +04:00
|
|
|
{
|
2015-04-19 20:47:52 +03:00
|
|
|
Context empty_context{Context::EmptyContextFlag{}};
|
2014-10-30 17:00:42 +03:00
|
|
|
global_scope.hooks().run_hook("KakEnd", "", empty_context);
|
2013-09-19 22:53:04 +04:00
|
|
|
}
|
2014-08-14 23:37:36 +04:00
|
|
|
|
2013-09-23 22:28:15 +04:00
|
|
|
return 0;
|
2013-09-19 22:53:04 +04:00
|
|
|
}
|
|
|
|
|
2015-03-09 16:48:41 +03:00
|
|
|
int run_filter(StringView keystr, ConstArrayView<StringView> files, bool quiet)
|
2014-08-15 02:51:24 +04:00
|
|
|
{
|
2014-10-27 20:54:20 +03:00
|
|
|
StringRegistry string_registry;
|
2014-10-30 17:00:42 +03:00
|
|
|
GlobalScope global_scope;
|
2014-10-26 23:21:01 +03:00
|
|
|
ShellManager shell_manager;
|
|
|
|
BufferManager buffer_manager;
|
|
|
|
RegisterManager register_manager;
|
2014-08-15 02:51:24 +04:00
|
|
|
|
2014-10-30 17:00:42 +03:00
|
|
|
register_options();
|
2014-08-15 02:51:24 +04:00
|
|
|
register_env_vars();
|
|
|
|
register_registers();
|
|
|
|
|
2014-08-17 18:19:04 +04:00
|
|
|
try
|
2014-08-15 02:51:24 +04:00
|
|
|
{
|
2014-08-17 18:19:04 +04:00
|
|
|
auto keys = parse_keys(keystr);
|
2014-08-15 02:51:24 +04:00
|
|
|
|
2014-08-17 18:19:04 +04:00
|
|
|
auto apply_keys_to_buffer = [&](Buffer& buffer)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2014-12-19 02:12:58 +03:00
|
|
|
InputHandler input_handler{
|
2015-02-02 21:46:55 +03:00
|
|
|
{ buffer, Selection{{0,0}, buffer.back_coord()} },
|
2014-12-19 02:12:58 +03:00
|
|
|
Context::Flags::Transient
|
|
|
|
};
|
2014-08-15 02:51:24 +04:00
|
|
|
|
2014-08-17 18:19:04 +04:00
|
|
|
for (auto& key : keys)
|
|
|
|
input_handler.handle_key(key);
|
|
|
|
}
|
|
|
|
catch (Kakoune::runtime_error& err)
|
|
|
|
{
|
2014-11-12 02:40:07 +03:00
|
|
|
if (not quiet)
|
2015-04-01 15:44:04 +03:00
|
|
|
write_stderr(format("error while applying keys to buffer '{}': {}\n",
|
|
|
|
buffer.display_name(), err.what()));
|
2014-08-17 18:19:04 +04:00
|
|
|
}
|
|
|
|
};
|
2014-08-15 02:51:24 +04:00
|
|
|
|
2014-08-17 18:19:04 +04:00
|
|
|
for (auto& file : files)
|
|
|
|
{
|
|
|
|
Buffer* buffer = create_buffer_from_file(file);
|
2014-11-12 02:40:07 +03:00
|
|
|
write_buffer_to_file(*buffer, file + ".kak-bak");
|
2014-08-17 18:19:04 +04:00
|
|
|
apply_keys_to_buffer(*buffer);
|
2014-11-12 02:40:07 +03:00
|
|
|
write_buffer_to_file(*buffer, file);
|
2014-08-17 18:19:04 +04:00
|
|
|
buffer_manager.delete_buffer(*buffer);
|
|
|
|
}
|
|
|
|
if (not isatty(0))
|
|
|
|
{
|
|
|
|
Buffer* buffer = create_buffer_from_data(read_fd(0), "*stdin*",
|
|
|
|
Buffer::Flags::None);
|
|
|
|
apply_keys_to_buffer(*buffer);
|
|
|
|
write_buffer_to_fd(*buffer, 1);
|
|
|
|
buffer_manager.delete_buffer(*buffer);
|
|
|
|
}
|
2014-08-15 02:51:24 +04:00
|
|
|
}
|
2014-08-17 18:19:04 +04:00
|
|
|
catch (Kakoune::runtime_error& err)
|
2014-08-15 16:21:54 +04:00
|
|
|
{
|
2015-04-01 15:44:04 +03:00
|
|
|
write_stderr(format("error: {}\n", err.what()));
|
2014-08-15 16:21:54 +04:00
|
|
|
}
|
|
|
|
|
2014-08-15 02:51:24 +04:00
|
|
|
buffer_manager.clear_buffer_trash();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-08-14 23:37:36 +04:00
|
|
|
int run_pipe(StringView session)
|
|
|
|
{
|
|
|
|
char buf[512];
|
|
|
|
String command;
|
|
|
|
while (ssize_t count = read(0, buf, 512))
|
|
|
|
{
|
|
|
|
if (count < 0)
|
|
|
|
{
|
2015-04-01 15:44:04 +03:00
|
|
|
write_stderr("error while reading stdin\n");
|
2014-08-14 23:37:36 +04:00
|
|
|
return -1;
|
|
|
|
}
|
2015-04-27 18:39:51 +03:00
|
|
|
command += StringView{buf, buf + count};
|
2014-08-14 23:37:36 +04:00
|
|
|
}
|
|
|
|
try
|
|
|
|
{
|
|
|
|
send_command(session, command);
|
|
|
|
}
|
|
|
|
catch (connection_failed& e)
|
|
|
|
{
|
2015-07-05 15:29:41 +03:00
|
|
|
write_stderr(format("{}\n", e.what()));
|
2014-08-14 23:37:36 +04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-09-19 22:53:04 +04:00
|
|
|
int main(int argc, char* argv[])
|
|
|
|
{
|
2014-02-12 02:16:17 +04:00
|
|
|
setlocale(LC_ALL, "");
|
2013-09-19 22:53:04 +04:00
|
|
|
|
2014-02-12 02:16:17 +04:00
|
|
|
signal(SIGSEGV, signal_handler);
|
|
|
|
signal(SIGFPE, signal_handler);
|
|
|
|
signal(SIGQUIT, signal_handler);
|
|
|
|
signal(SIGTERM, signal_handler);
|
2015-05-28 15:57:22 +03:00
|
|
|
signal(SIGPIPE, SIG_IGN);
|
2015-06-09 22:28:24 +03:00
|
|
|
signal(SIGCHLD, [](int){});
|
2013-09-19 22:53:04 +04:00
|
|
|
|
2015-01-12 16:58:41 +03:00
|
|
|
Vector<String> params;
|
2014-02-12 02:16:17 +04:00
|
|
|
for (size_t i = 1; i < argc; ++i)
|
|
|
|
params.push_back(argv[i]);
|
2013-09-19 22:53:04 +04:00
|
|
|
|
2014-02-12 02:16:17 +04:00
|
|
|
const ParameterDesc param_desc{
|
2015-03-14 20:30:34 +03:00
|
|
|
SwitchMap{ { "c", { true, "connect to given session" } },
|
|
|
|
{ "e", { true, "execute argument on initialisation" } },
|
2014-02-12 02:16:17 +04:00
|
|
|
{ "n", { false, "do not source kakrc files on startup" } },
|
2015-03-14 20:30:34 +03:00
|
|
|
{ "s", { true, "set session name" } },
|
2014-03-02 06:01:09 +04:00
|
|
|
{ "d", { false, "run as a headless session (requires -s)" } },
|
2015-03-14 20:30:34 +03:00
|
|
|
{ "p", { true, "just send stdin as commands to the given session" } },
|
|
|
|
{ "f", { true, "act as a filter, executing given keys on given files" } },
|
2015-03-24 16:14:02 +03:00
|
|
|
{ "q", { false, "in filter mode, be quiet about errors applying keys" } },
|
2015-08-23 16:22:23 +03:00
|
|
|
{ "u", { false, "use a dummy user interface, for testing purposes" } },
|
|
|
|
{ "l", { false, "list existing sessions" } } }
|
2014-02-12 02:16:17 +04:00
|
|
|
};
|
|
|
|
try
|
|
|
|
{
|
2015-03-08 14:40:50 +03:00
|
|
|
std::sort(keymap.begin(), keymap.end(),
|
|
|
|
[](const NormalCmdDesc& lhs, const NormalCmdDesc& rhs)
|
|
|
|
{ return lhs.key < rhs.key; });
|
|
|
|
|
2014-08-15 02:57:13 +04:00
|
|
|
ParametersParser parser(params, param_desc);
|
|
|
|
|
2015-08-23 16:22:23 +03:00
|
|
|
if (parser.get_switch("l"))
|
|
|
|
{
|
|
|
|
for (auto& file : list_files(format("/tmp/kakoune/{}/", getlogin())))
|
|
|
|
write_stdout(format("{}\n", file));
|
|
|
|
return 0;
|
|
|
|
}
|
2015-03-14 22:16:46 +03:00
|
|
|
if (auto session = parser.get_switch("p"))
|
2014-08-15 02:57:13 +04:00
|
|
|
{
|
|
|
|
for (auto opt : { "c", "n", "s", "d", "e" })
|
|
|
|
{
|
2015-03-14 22:16:46 +03:00
|
|
|
if (parser.get_switch(opt))
|
2014-08-15 02:57:13 +04:00
|
|
|
{
|
2015-04-01 15:44:04 +03:00
|
|
|
write_stderr(format("error: -{} makes not sense with -p\n", opt));
|
2014-08-15 02:57:13 +04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
2015-03-14 22:16:46 +03:00
|
|
|
return run_pipe(*session);
|
2014-08-15 02:57:13 +04:00
|
|
|
}
|
2015-03-14 22:16:46 +03:00
|
|
|
else if (auto keys = parser.get_switch("f"))
|
2014-08-15 02:57:13 +04:00
|
|
|
{
|
2015-01-12 16:58:41 +03:00
|
|
|
Vector<StringView> files;
|
2014-08-15 02:57:13 +04:00
|
|
|
for (size_t i = 0; i < parser.positional_count(); ++i)
|
|
|
|
files.emplace_back(parser[i]);
|
|
|
|
|
2015-03-14 22:16:46 +03:00
|
|
|
return run_filter(*keys, files, (bool)parser.get_switch("q"));
|
2014-08-15 02:57:13 +04:00
|
|
|
}
|
|
|
|
|
2015-03-14 22:16:46 +03:00
|
|
|
auto init_command = parser.get_switch("e").value_or(StringView{});
|
2014-08-15 02:57:13 +04:00
|
|
|
|
2015-03-14 22:16:46 +03:00
|
|
|
if (auto server_session = parser.get_switch("c"))
|
2014-08-15 02:57:13 +04:00
|
|
|
{
|
|
|
|
for (auto opt : { "n", "s", "d" })
|
|
|
|
{
|
2015-03-14 22:16:46 +03:00
|
|
|
if (parser.get_switch(opt))
|
2014-08-15 02:57:13 +04:00
|
|
|
{
|
2015-04-01 15:44:04 +03:00
|
|
|
write_stderr(format("error: -{} makes not sense with -c\n", opt));
|
2014-08-15 02:57:13 +04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
2015-03-14 22:16:46 +03:00
|
|
|
return run_client(*server_session, init_command);
|
2014-08-15 02:57:13 +04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2015-01-12 16:58:41 +03:00
|
|
|
Vector<StringView> files;
|
2014-08-15 02:57:13 +04:00
|
|
|
for (size_t i = 0; i < parser.positional_count(); ++i)
|
|
|
|
files.emplace_back(parser[i]);
|
|
|
|
|
2015-03-14 22:16:46 +03:00
|
|
|
StringView session = parser.get_switch("s").value_or(StringView{});
|
2014-08-15 02:57:13 +04:00
|
|
|
return run_server(session, init_command,
|
2015-03-14 22:16:46 +03:00
|
|
|
(bool)parser.get_switch("n"),
|
|
|
|
(bool)parser.get_switch("d"),
|
2015-03-24 16:14:02 +03:00
|
|
|
(bool)parser.get_switch("u"),
|
2014-08-15 02:57:13 +04:00
|
|
|
files);
|
|
|
|
}
|
2011-09-02 20:51:20 +04:00
|
|
|
}
|
2014-01-23 23:36:07 +04:00
|
|
|
catch (Kakoune::parameter_error& error)
|
|
|
|
{
|
2015-08-18 02:28:04 +03:00
|
|
|
write_stderr(format("Error while parsing parameters: {}\n"
|
2015-04-01 15:44:04 +03:00
|
|
|
"Valid switches:\n"
|
|
|
|
"{}", error.what(),
|
|
|
|
generate_switches_doc(param_desc.switches)));
|
2014-01-23 23:36:07 +04:00
|
|
|
return -1;
|
|
|
|
}
|
2011-09-09 23:24:18 +04:00
|
|
|
catch (Kakoune::exception& error)
|
|
|
|
{
|
2015-03-31 01:06:02 +03:00
|
|
|
on_assert_failed(format("uncaught exception ({}):\n{}", typeid(error).name(), error.what()).c_str());
|
2013-02-22 21:45:59 +04:00
|
|
|
return -1;
|
|
|
|
}
|
2013-03-22 17:29:22 +04:00
|
|
|
catch (std::exception& error)
|
|
|
|
{
|
2015-03-31 01:06:02 +03:00
|
|
|
on_assert_failed(format("uncaught exception ({}):\n{}", typeid(error).name(), error.what()).c_str());
|
2013-03-22 17:29:22 +04:00
|
|
|
return -1;
|
|
|
|
}
|
2013-02-22 21:45:59 +04:00
|
|
|
catch (...)
|
|
|
|
{
|
|
|
|
on_assert_failed("uncaught exception");
|
2011-09-09 23:24:18 +04:00
|
|
|
return -1;
|
|
|
|
}
|
2011-09-02 20:51:20 +04:00
|
|
|
return 0;
|
|
|
|
}
|