Support specifying an exit status on quit commands

The current client exit status can be specified as an optional
parameter, is nothing is given the exit status will be 0.

Fixes #1230
This commit is contained in:
Maxime Coste 2017-08-23 13:22:23 +07:00
parent 3efc406d57
commit f7bed9eb18
10 changed files with 95 additions and 57 deletions

View File

@ -682,11 +682,14 @@ command `q!` has to be used).
permissions are temporarily changed to allow saving the buffer and
restored afterwards when the write! command is used.
* `w[rite]a[ll]`: write all buffers that are associated to a file.
* `q[uit][!]`: exit Kakoune, use quit! to force quitting even if there is some
unsaved buffers remaining.
* `q[uit][!] [<exit status>]`: exit Kakoune, use quit! to force quitting even
if there is some unsaved buffers remaining. If specified, the client exit
status will be set to <exit status>.
* `w[a]q[!] [<exit status>]`: write the current buffer (or all buffers when
`waq` is used) and quit. If specified, the client exit status will be set
to <exit status>.
* `kill[!]`: terminate the current session, all the clients as well as the server,
use kill! to ignore unsaved buffers
* `w[a]q[!]`: write the current buffer (or all buffers when `waq` is used) and quit
* `b[uffer] <name>`: switch to buffer <name>
* `b[uffer]n[ext]`: switch to the next buffer
* `b[uffer]p[rev]`: switch to the previous buffer

View File

@ -33,16 +33,18 @@ command *q!* has to be used).
*w[rite]a[ll]*::
write all buffers that are associated to a file
*q[uit][!]*::
*q[uit][!]* [<exit status>]::
exit Kakoune, use quit! to force quitting even if there is some
unsaved buffers remaining
unsaved buffers remaining. If specified, the client exit status
will be set to <exit status>
*w[a]q[!]* [<exit status>]::
write the current buffer (or all buffers when *waq* is used) and quit.
If specified, the client exit status will be set to <exit status>
*kill*::
terminate the current session, all the clients as well as the server
*w[a]q[!]*::
write the current buffer (or all buffers when *waq* is used) and quit
*b[uffer]* <name>::
switch to buffer <name>

View File

@ -26,8 +26,10 @@ Client::Client(std::unique_ptr<UserInterface>&& ui,
std::unique_ptr<Window>&& window,
SelectionList selections,
EnvVarMap env_vars,
String name)
String name,
OnExitCallback on_exit)
: m_ui{std::move(ui)}, m_window{std::move(window)},
m_on_exit{std::move(on_exit)},
m_input_handler{std::move(selections), Context::Flags::None,
std::move(name)},
m_env_vars(std::move(env_vars))

View File

@ -21,15 +21,17 @@ enum class EventMode;
enum class InfoStyle;
enum class MenuStyle;
class Client : public SafeCountable, public OptionManagerWatcher
{
public:
using OnExitCallback = std::function<void (int status)>;
Client(std::unique_ptr<UserInterface>&& ui,
std::unique_ptr<Window>&& window,
SelectionList selections,
EnvVarMap env_vars,
String name);
String name,
OnExitCallback on_exit);
~Client();
Client(Client&&) = delete;
@ -65,6 +67,8 @@ public:
Buffer* last_buffer() const { return m_last_buffer.get(); }
void set_last_buffer(Buffer* last_buffer) { m_last_buffer = last_buffer; }
void exit(int status) { m_on_exit(status); }
private:
void on_option_changed(const Option& option) override;
@ -79,6 +83,8 @@ private:
std::unique_ptr<UserInterface> m_ui;
std::unique_ptr<Window> m_window;
OnExitCallback m_on_exit;
EnvVarMap m_env_vars;
InputHandler m_input_handler;

View File

@ -40,13 +40,14 @@ String ClientManager::generate_name() const
Client* ClientManager::create_client(std::unique_ptr<UserInterface>&& ui,
EnvVarMap env_vars, StringView init_cmds,
Optional<BufferCoord> init_coord)
Optional<BufferCoord> init_coord,
Client::OnExitCallback on_exit)
{
Buffer& buffer = BufferManager::instance().get_first_buffer();
WindowAndSelections ws = get_free_window(buffer);
Client* client = new Client{std::move(ui), std::move(ws.window),
std::move(ws.selections), std::move(env_vars),
generate_name()};
generate_name(), std::move(on_exit)};
m_clients.emplace_back(client);
if (init_coord)
@ -88,7 +89,7 @@ void ClientManager::process_pending_inputs() const
}
}
void ClientManager::remove_client(Client& client, bool graceful)
void ClientManager::remove_client(Client& client, bool graceful, int status)
{
auto it = find(m_clients, &client);
if (it == m_clients.end())
@ -96,6 +97,7 @@ void ClientManager::remove_client(Client& client, bool graceful)
kak_assert(contains(m_client_trash, &client));
return;
}
client.exit(status);
m_client_trash.push_back(std::move(*it));
m_clients.erase(it);

View File

@ -21,7 +21,8 @@ public:
Client* create_client(std::unique_ptr<UserInterface>&& ui,
EnvVarMap env_vars, StringView init_cmds,
Optional<BufferCoord> init_coord);
Optional<BufferCoord> init_coord,
Client::OnExitCallback on_exit);
bool empty() const { return m_clients.empty(); }
size_t count() const { return m_clients.size(); }
@ -39,7 +40,7 @@ public:
Client* get_client_ifp(StringView name);
Client& get_client(StringView name);
bool validate_client_name(StringView name) const;
void remove_client(Client& client, bool graceful);
void remove_client(Client& client, bool graceful, int status);
using ClientList = Vector<std::unique_ptr<Client>, MemoryDomain::Client>;
using iterator = ClientList::const_iterator;

View File

@ -423,24 +423,26 @@ const CommandDesc force_kill_cmd = {
};
template<bool force>
void quit(Context& context)
void quit(const ParametersParser& parser, Context& context, const ShellContext&)
{
if (not force and ClientManager::instance().count() == 1)
ensure_all_buffers_are_saved();
ClientManager::instance().remove_client(context.client(), true);
const int status = parser.positional_count() > 0 ? str_to_int(parser[0]) : 0;
ClientManager::instance().remove_client(context.client(), true, status);
}
const CommandDesc quit_cmd = {
"quit",
"q",
"quit current client, and the kakoune session if the client is the last "
"(if not running in daemon mode)",
no_params,
"(if not running in daemon mode). An optional integer parameter can set "
"the client exit status",
{ {}, ParameterDesc::Flags::SwitchesAsPositional, 0, 1 },
CommandFlags::None,
CommandHelper{},
CommandCompleter{},
[](const ParametersParser&, Context& context, const ShellContext&){ quit<false>(context); }
quit<false>
};
const CommandDesc force_quit_cmd = {
@ -448,27 +450,29 @@ const CommandDesc force_quit_cmd = {
"q!",
"quit current client, and the kakoune session if the client is the last "
"(if not running in daemon mode). force quit even if the client is the "
"last and some buffers are not saved.",
no_params,
"last and some buffers are not saved. An optional integer parameter can "
"set the client exit status",
{ {}, ParameterDesc::Flags::SwitchesAsPositional, 0, 1 },
CommandFlags::None,
CommandHelper{},
CommandCompleter{},
[](const ParametersParser&, Context& context, const ShellContext&){ quit<true>(context); }
quit<true>
};
template<bool force>
void write_quit(const ParametersParser& parser, Context& context,
const ShellContext& shell_context)
{
write_buffer(parser, context, shell_context);
quit<force>(context);
write_buffer({{}, {}}, context, shell_context);
quit<force>(parser, context, shell_context);
}
const CommandDesc write_quit_cmd = {
"write-quit",
"wq",
"write current buffer and quit current client",
no_params,
"write current buffer and quit current client. An optional integer"
"parameter can set the client exit status",
{ {}, ParameterDesc::Flags::SwitchesAsPositional, 0, 1 },
CommandFlags::None,
CommandHelper{},
CommandCompleter{},
@ -479,8 +483,8 @@ const CommandDesc force_write_quit_cmd = {
"write-quit!",
"wq!",
"write current buffer and quit current client, even if other buffers are "
"not saved",
no_params,
"not saved. An optional integer parameter can set the client exit status",
{ {}, ParameterDesc::Flags::SwitchesAsPositional, 0, 1 },
CommandFlags::None,
CommandHelper{},
CommandCompleter{},
@ -490,15 +494,16 @@ const CommandDesc force_write_quit_cmd = {
const CommandDesc write_all_quit_cmd = {
"write-all-quit",
"waq",
"write all buffers associated to a file and quit current client",
no_params,
"write all buffers associated to a file and quit current client. An "
"optional integer parameter can set the client exit status",
{ {}, ParameterDesc::Flags::SwitchesAsPositional, 0, 1 },
CommandFlags::None,
CommandHelper{},
CommandCompleter{},
[](const ParametersParser& parser, Context& context, const ShellContext&)
[](const ParametersParser& parser, Context& context, const ShellContext& shell_context)
{
write_all_buffers(context);
quit<false>(context);
quit<false>(parser, context, shell_context);
}
};

View File

@ -328,6 +328,7 @@ void register_options()
}
static Client* local_client = nullptr;
static int local_client_exit = 0;
static UserInterface* local_ui = nullptr;
static bool convert_to_client_pending = false;
@ -403,7 +404,7 @@ std::unique_ptr<UserInterface> create_local_ui(UIType ui_type)
kak_assert(not local_ui);
local_ui = this;
m_old_sighup = set_signal_handler(SIGHUP, [](int) {
ClientManager::instance().remove_client(*local_client, false);
ClientManager::instance().remove_client(*local_client, false, -1);
static_cast<LocalUI*>(local_ui)->on_sighup();
});
@ -441,7 +442,7 @@ std::unique_ptr<UserInterface> create_local_ui(UIType ui_type)
if (fork_server_to_background())
{
this->NCursesUI::~NCursesUI();
exit(0);
exit(local_client_exit);
}
}
}
@ -475,16 +476,15 @@ int run_client(StringView session, StringView client_init,
{
EventManager event_manager;
RemoteClient client{session, make_ui(ui_type), get_env_vars(), client_init, std::move(init_coord)};
while (true)
while (not client.exit_status())
event_manager.handle_next_events(EventMode::Normal);
return *client.exit_status();
}
catch (disconnected& e)
{
if (!e.m_graceful)
write_stderr(format("{}\ndisconnecting\n", e.what()));
return e.m_graceful ? 0 : -1;
return -1;
}
return 0;
}
struct convert_to_client_mode
@ -614,7 +614,8 @@ int run_server(StringView session, StringView server_init,
if (not (flags & ServerFlags::Daemon))
{
local_client = client_manager.create_client(
create_local_ui(ui_type), get_env_vars(), client_init, std::move(init_coord));
create_local_ui(ui_type), get_env_vars(), client_init, std::move(init_coord),
[](int status) { local_client_exit = status; });
if (startup_error)
local_client->print_status({
@ -640,7 +641,7 @@ int run_server(StringView session, StringView server_init,
String buffer_name = local_client->context().buffer().name();
String selections = selection_list_to_string(local_client->context().selections());
ClientManager::instance().remove_client(*local_client, true);
ClientManager::instance().remove_client(*local_client, true, 0);
client_manager.clear_client_trash();
convert_to_client_pending = false;
@ -660,7 +661,7 @@ int run_server(StringView session, StringView server_init,
global_scope.hooks().run_hook("KakEnd", "", empty_context);
}
return 0;
return local_client_exit;
}
int run_filter(StringView keystr, StringView commands, ConstArrayView<StringView> files, bool quiet, StringView suffix_backup)
@ -743,7 +744,7 @@ int run_pipe(StringView session)
catch (disconnected& e)
{
write_stderr(format("{}\ndisconnecting\n", e.what()));
return e.m_graceful ? 0 : -1;
return -1;
}
return 0;
}

View File

@ -38,7 +38,8 @@ enum class MessageType : uint8_t
SetCursor,
Refresh,
SetOptions,
Key
Exit,
Key,
};
class MsgWriter
@ -252,8 +253,7 @@ private:
kak_assert(m_write_pos + size <= m_stream.size());
int res = ::read(sock, m_stream.data() + m_write_pos, size);
if (res <= 0)
throw disconnected{res ? format("socket read failed: {}", strerror(errno))
: "peer disconnected", res == 0};
throw disconnected{format("socket read failed: {}", strerror(errno))};
m_write_pos += res;
}
@ -352,6 +352,8 @@ public:
void set_client(Client* client) { m_client = client; }
Client* client() const { return m_client.get(); }
void exit(int status);
private:
FDWatcher m_socket_watcher;
MsgReader m_reader;
@ -368,8 +370,7 @@ static bool send_data(int fd, RemoteBuffer& buffer)
{
int res = ::write(fd, buffer.data(), buffer.size());
if (res <= 0)
throw disconnected{res ? format("socket write failed: {}", strerror(errno))
: "peer disconnected", res == 0};
throw disconnected{format("socket write failed: {}", strerror(errno))};
buffer.erase(buffer.begin(), buffer.begin() + res);
}
return buffer.empty();
@ -393,7 +394,7 @@ RemoteUI::RemoteUI(int socket, DisplayCoord dimensions)
if (m_reader.type() != MessageType::Key)
{
ClientManager::instance().remove_client(*m_client, false);
ClientManager::instance().remove_client(*m_client, false, -1);
return;
}
@ -407,7 +408,7 @@ RemoteUI::RemoteUI(int socket, DisplayCoord dimensions)
catch (const disconnected& err)
{
write_to_debug_buffer(format("Error while transfering remote messages: {}", err.what()));
ClientManager::instance().remove_client(*m_client, false);
ClientManager::instance().remove_client(*m_client, false, -1);
}
}),
m_dimensions(dimensions)
@ -417,6 +418,9 @@ RemoteUI::RemoteUI(int socket, DisplayCoord dimensions)
RemoteUI::~RemoteUI()
{
// Try to send the remaining data if possible, as it might contain the desired exit status
send_data(m_socket_watcher.fd(), m_send_buffer);
write_to_debug_buffer(format("remote client disconnected: {}", m_socket_watcher.fd()));
m_socket_watcher.close_fd();
}
@ -510,6 +514,13 @@ void RemoteUI::set_ui_options(const Options& options)
m_socket_watcher.events() |= FdEvents::Write;
}
void RemoteUI::exit(int status)
{
MsgWriter msg{m_send_buffer, MessageType::Exit};
msg.write(status);
m_socket_watcher.events() |= FdEvents::Write;
}
static sockaddr_un session_addr(StringView session)
{
sockaddr_un addr;
@ -637,6 +648,11 @@ RemoteClient::RemoteClient(StringView session, std::unique_ptr<UserInterface>&&
case MessageType::SetOptions:
m_ui->set_ui_options(reader.read_hash_map<String, String, MemoryDomain::Options>());
break;
case MessageType::Exit:
m_exit_status = reader.read<int>();
m_socket_watcher->close_fd();
m_socket_watcher.reset();
return; // This lambda is now dead
default:
kak_assert(false);
}
@ -697,7 +713,8 @@ private:
auto* ui = new RemoteUI{sock, dimensions};
if (auto* client = ClientManager::instance().create_client(
std::unique_ptr<UserInterface>(ui),
std::move(env_vars), init_cmds, init_coord))
std::move(env_vars), init_cmds, init_coord,
[ui](int status) { ui->exit(status); }))
ui->set_client(client);
Server::instance().remove_accepter(this);

View File

@ -13,10 +13,7 @@ namespace Kakoune
struct disconnected : runtime_error
{
disconnected(String what, bool graceful = false)
: runtime_error{std::move(what)}, m_graceful{graceful} {}
const bool m_graceful;
using runtime_error::runtime_error;
};
class FDWatcher;
@ -36,10 +33,12 @@ public:
const EnvVarMap& env_vars, StringView init_command,
Optional<BufferCoord> init_coord);
const Optional<int>& exit_status() const { return m_exit_status; }
private:
std::unique_ptr<UserInterface> m_ui;
std::unique_ptr<FDWatcher> m_socket_watcher;
RemoteBuffer m_send_buffer;
Optional<int> m_exit_status;
};
void send_command(StringView session, StringView command);