From 8a4596bea9d85c86d03efa129c41530faa2a34a7 Mon Sep 17 00:00:00 2001 From: Frank LENORMAND Date: Wed, 20 Jul 2016 20:45:50 +0300 Subject: [PATCH 1/5] Implement a `readonly` mode This commit introduces the `readonly` variable as well as the `-ro` command line option which prevent buffers from being overwritten on disk when the `write` command is used without arguments. Some buffers can selectively be put in readonly mode by setting the `readonly` variable on the `buffer` scope, the `global` mode will affect all buffers (even those who will be open subsequently), using the `window` scope will have no effect. Closes #685 --- src/buffer.cc | 7 +++++++ src/buffer.hh | 13 +++++++------ src/buffer_manager.cc | 3 ++- src/commands.cc | 9 ++++++++- src/main.cc | 16 ++++++++++++---- 5 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/buffer.cc b/src/buffer.cc index 089103b1..f447f2db 100644 --- a/src/buffer.cc +++ b/src/buffer.cc @@ -591,6 +591,13 @@ void Buffer::set_fs_timestamp(timespec ts) void Buffer::on_option_changed(const Option& option) { + if (option.name() == "readonly") + { + if (option.get()) + m_flags |= Flags::ReadOnly; + else + m_flags &= ~Flags::ReadOnly; + } run_hook_in_own_context("BufSetOption", format("{}={}", option.name(), option.get_as_string())); } diff --git a/src/buffer.hh b/src/buffer.hh index 2b118c89..1ed88cf7 100644 --- a/src/buffer.hh +++ b/src/buffer.hh @@ -101,12 +101,13 @@ class Buffer : public SafeCountable, public OptionManagerWatcher, public Scope public: enum class Flags { - None = 0, - File = 1 << 0, - New = 1 << 1, - Fifo = 1 << 2, - NoUndo = 1 << 3, - Debug = 1 << 4 + None = 0, + File = 1 << 0, + New = 1 << 1, + Fifo = 1 << 2, + NoUndo = 1 << 3, + Debug = 1 << 4, + ReadOnly = 1 << 5, }; Buffer(String name, Flags flags, StringView data = {}, diff --git a/src/buffer_manager.cc b/src/buffer_manager.cc index e743bff0..688e864a 100644 --- a/src/buffer_manager.cc +++ b/src/buffer_manager.cc @@ -78,7 +78,8 @@ void BufferManager::backup_modified_buffers() { for (auto& buf : m_buffers) { - if ((buf->flags() & Buffer::Flags::File) and buf->is_modified()) + if ((buf->flags() & Buffer::Flags::File) and buf->is_modified() + and !(buf->flags() & Buffer::Flags::ReadOnly)) write_buffer_to_backup_file(*buf); } } diff --git a/src/commands.cc b/src/commands.cc index 0a13938e..6104975c 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -239,6 +239,12 @@ void write_buffer(const ParametersParser& parser, Context& context, const ShellC if (parser.positional_count() == 0 and !(buffer.flags() & Buffer::Flags::File)) throw runtime_error("cannot write a non file buffer without a filename"); + // if the buffer is in read-only mode and we try to save it directly + // or we try to write to it indirectly using e.g. a symlink, throw an error + if ((context.buffer().flags() & Buffer::Flags::ReadOnly) + && (parser.positional_count() == 0 || real_path(parser[0]) == buffer.name())) + throw runtime_error("cannot overwrite the buffer when in readonly mode"); + auto filename = parser.positional_count() == 0 ? buffer.name() : parse_filename(parser[0]); write_buffer_to_file(buffer, filename); @@ -260,7 +266,8 @@ void write_all_buffers() { for (auto& buffer : BufferManager::instance()) { - if ((buffer->flags() & Buffer::Flags::File) and buffer->is_modified()) + if ((buffer->flags() & Buffer::Flags::File) and buffer->is_modified() + and !(buffer->flags() & Buffer::Flags::ReadOnly)) write_buffer_to_file(*buffer, buffer->name()); } } diff --git a/src/main.cc b/src/main.cc index 82444571..020c1e59 100644 --- a/src/main.cc +++ b/src/main.cc @@ -265,6 +265,7 @@ void register_options() reg.declare_option("modelinefmt", "format string used to generate the modeline", "%val{bufname} %val{cursor_line}:%val{cursor_char_column} "_str); reg.declare_option("debug", "various debug flags", DebugFlags::None); + reg.declare_option("readonly", "prevent buffers from being modified", false); } struct convert_to_client_mode @@ -460,7 +461,7 @@ int run_client(StringView session, StringView init_command, UIType ui_type) } int run_server(StringView session, StringView init_command, - bool ignore_kakrc, bool daemon, UIType ui_type, + bool ignore_kakrc, bool daemon, bool readonly, UIType ui_type, ConstArrayView files, ByteCoord target_coord) { static bool terminate = false; @@ -503,6 +504,8 @@ int run_server(StringView session, StringView init_command, write_to_debug_buffer("*** This is the debug buffer, where debug info will be written ***"); + GlobalScope::instance().options().get_local_option("readonly").set(readonly); + Server server(session.empty() ? to_string(getpid()) : session.str()); bool startup_error = false; @@ -536,7 +539,9 @@ int run_server(StringView session, StringView init_command, { try { - open_or_create_file_buffer(file); + Buffer *buffer = open_or_create_file_buffer(file); + if (readonly) + buffer->flags() |= Buffer::Flags::ReadOnly; } catch (Kakoune::runtime_error& error) { @@ -741,7 +746,8 @@ int main(int argc, char* argv[]) { "q", { false, "in filter mode, be quiet about errors applying keys" } }, { "ui", { true, "set the type of user interface to use (ncurses, dummy, or json)" } }, { "l", { false, "list existing sessions" } }, - { "clear", { false, "clear dead sessions" } } } + { "clear", { false, "clear dead sessions" } }, + { "ro", { false, "readonly mode" } } } }; try { @@ -793,7 +799,8 @@ int main(int argc, char* argv[]) for (size_t i = 0; i < parser.positional_count(); ++i) files.emplace_back(parser[i]); - return run_filter(*keys, init_command, files, (bool)parser.get_switch("q")); + return run_filter(*keys, init_command, files, + (bool)parser.get_switch("q")); } if (auto server_session = parser.get_switch("c")) @@ -840,6 +847,7 @@ int main(int argc, char* argv[]) return run_server(session, init_command, (bool)parser.get_switch("n"), (bool)parser.get_switch("d"), + (bool)parser.get_switch("ro"), ui_type, files, target_coord); } catch (convert_to_client_mode& convert) From 75589941498f492eb94f4b0c76209e0d4f2a608d Mon Sep 17 00:00:00 2001 From: Frank LENORMAND Date: Mon, 11 Jul 2016 18:39:26 +0300 Subject: [PATCH 2/5] Document the `readonly` variable and `-ro` command line option --- README.asciidoc | 4 ++++ doc/manpages/options.asciidoc | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/README.asciidoc b/README.asciidoc index 48dbcc5f..6d3a5c11 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -231,6 +231,7 @@ Just running *kak* launch a new kak session with a client on local terminal. read keystrokes as json on stdin. * `-l`: list existing sessions, and check the dead ones * `-clear`: clear dead sessions socket files + * `-ro`: prevent modifications to all buffers from being saved to disk At startup, if `-n` is not specified, Kakoune will try to source the file `../share/kak/kakrc` relative to the kak binary. This kak file will then try @@ -861,6 +862,9 @@ Some options are built in Kakoune, and can be used to control it's behaviour: writing a buffer, this is autodetected on load. * `BOM` _enum(none|utf8)_: define if the file should be written with an unicode byte order mark. + * `readonly` _bool_: prevent modifications from being saved to disk, all + buffers if set to `true` in the `global` scope, or current buffer if set in + the `buffer` scope. * `incsearch` _bool_: execute search as it is typed * `aligntab` _bool_: use tabs for alignment command * `autoinfo` _flags(command|onkey|normal)_: display automatic information diff --git a/doc/manpages/options.asciidoc b/doc/manpages/options.asciidoc index 75c1e217..4b495dd3 100644 --- a/doc/manpages/options.asciidoc +++ b/doc/manpages/options.asciidoc @@ -66,6 +66,11 @@ Builtin options *BOM* 'enum(none|utf8)':: define if the file should be written with an unicode byte order mark +*readonly* 'bool':: + prevent modifications from being saved to disk, all + buffers if set to `true` in the `global` scope, or current buffer if set in + the `buffer` scope. + *incsearch* 'bool':: execute search as it is typed From 3c91f711fc5197fbcdb6712c92c44638d4ad2807 Mon Sep 17 00:00:00 2001 From: Frank LENORMAND Date: Wed, 20 Jul 2016 21:10:13 +0300 Subject: [PATCH 3/5] Warn the user when flag combinations don't make sense --- src/main.cc | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main.cc b/src/main.cc index 020c1e59..8e8ad9b4 100644 --- a/src/main.cc +++ b/src/main.cc @@ -779,7 +779,7 @@ int main(int argc, char* argv[]) if (auto session = parser.get_switch("p")) { - for (auto opt : { "c", "n", "s", "d", "e" }) + for (auto opt : { "c", "n", "s", "d", "e", "ro" }) { if (parser.get_switch(opt)) { @@ -795,6 +795,12 @@ int main(int argc, char* argv[]) if (auto keys = parser.get_switch("f")) { + if (parser.get_switch("ro")) + { + write_stderr("error: -ro makes not sense with -f\n"); + return -1; + } + Vector files; for (size_t i = 0; i < parser.positional_count(); ++i) files.emplace_back(parser[i]); @@ -805,7 +811,7 @@ int main(int argc, char* argv[]) if (auto server_session = parser.get_switch("c")) { - for (auto opt : { "n", "s", "d" }) + for (auto opt : { "n", "s", "d", "ro" }) { if (parser.get_switch(opt)) { From ef82c496eb8b565281b8f301e877a8a056266be8 Mon Sep 17 00:00:00 2001 From: Frank LENORMAND Date: Wed, 20 Jul 2016 21:11:36 +0300 Subject: [PATCH 4/5] Use a more appropriate error message with unlogical flag combinations --- src/main.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main.cc b/src/main.cc index 8e8ad9b4..1b9eea4d 100644 --- a/src/main.cc +++ b/src/main.cc @@ -783,7 +783,7 @@ int main(int argc, char* argv[]) { if (parser.get_switch(opt)) { - write_stderr(format("error: -{} makes not sense with -p\n", opt)); + write_stderr(format("error: -{} is incompatible with -p\n", opt)); return -1; } } @@ -797,7 +797,7 @@ int main(int argc, char* argv[]) { if (parser.get_switch("ro")) { - write_stderr("error: -ro makes not sense with -f\n"); + write_stderr("error: -ro is incompatible with -f\n"); return -1; } @@ -815,7 +815,7 @@ int main(int argc, char* argv[]) { if (parser.get_switch(opt)) { - write_stderr(format("error: -{} makes not sense with -c\n", opt)); + write_stderr(format("error: -{} is incompatible with -c\n", opt)); return -1; } } From e3bf01d1f9063fa25ae0f0b104eb1539d0e941b6 Mon Sep 17 00:00:00 2001 From: Frank LENORMAND Date: Sun, 24 Jul 2016 08:34:49 +0300 Subject: [PATCH 5/5] Replace C-style operators with their alphabetical equivalent --- src/buffer_manager.cc | 2 +- src/commands.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/buffer_manager.cc b/src/buffer_manager.cc index 688e864a..9826de13 100644 --- a/src/buffer_manager.cc +++ b/src/buffer_manager.cc @@ -79,7 +79,7 @@ void BufferManager::backup_modified_buffers() for (auto& buf : m_buffers) { if ((buf->flags() & Buffer::Flags::File) and buf->is_modified() - and !(buf->flags() & Buffer::Flags::ReadOnly)) + and not (buf->flags() & Buffer::Flags::ReadOnly)) write_buffer_to_backup_file(*buf); } } diff --git a/src/commands.cc b/src/commands.cc index 6104975c..fa61a8c4 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -242,7 +242,7 @@ void write_buffer(const ParametersParser& parser, Context& context, const ShellC // if the buffer is in read-only mode and we try to save it directly // or we try to write to it indirectly using e.g. a symlink, throw an error if ((context.buffer().flags() & Buffer::Flags::ReadOnly) - && (parser.positional_count() == 0 || real_path(parser[0]) == buffer.name())) + and (parser.positional_count() == 0 or real_path(parser[0]) == buffer.name())) throw runtime_error("cannot overwrite the buffer when in readonly mode"); auto filename = parser.positional_count() == 0 ?