diff options
| author | Maxime Coste <mawww@kakoune.org> | 2019-02-12 20:54:37 +1100 |
|---|---|---|
| committer | Maxime Coste <mawww@kakoune.org> | 2019-02-12 21:01:19 +1100 |
| commit | 4dae2c875bf70d87ace53d7b03c4a9289b2bb241 (patch) | |
| tree | 5b4c66cbbcf2ca461d7e0e5e456c51d1e208cc4f /src | |
| parent | e8f26cbae751180ace2655b000b1b86ad3a2940c (diff) | |
Introduce a writemethod option to either overwrite or replace files
This permit to choose if files should be written by overwriting their
content (the default), or by writing to a separate temporary file
and rename it to the current file.
As discussed in #2036
Diffstat (limited to 'src')
| -rw-r--r-- | src/commands.cc | 6 | ||||
| -rw-r--r-- | src/file.cc | 58 | ||||
| -rw-r--r-- | src/file.hh | 16 | ||||
| -rw-r--r-- | src/main.cc | 7 |
4 files changed, 63 insertions, 24 deletions
diff --git a/src/commands.cc b/src/commands.cc index 6cc48dbf..3bc145bc 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -342,9 +342,10 @@ void do_write_buffer(Context& context, Optional<String> filename, WriteFlags fla throw runtime_error("cannot overwrite the buffer when in readonly mode"); auto effective_filename = not filename ? buffer.name() : parse_filename(*filename); + auto mode = context.options()["writemethod"].get<WriteMethod>(); context.hooks().run_hook(Hook::BufWritePre, effective_filename, context); - write_buffer_to_file(buffer, effective_filename, flags); + write_buffer_to_file(buffer, effective_filename, mode, flags); context.hooks().run_hook(Hook::BufWritePost, effective_filename, context); } @@ -395,8 +396,9 @@ void write_all_buffers(Context& context, bool sync = false) buffer->is_modified()) and !(buffer->flags() & Buffer::Flags::ReadOnly)) { + auto mode = context.options()["writemethod"].get<WriteMethod>(); buffer->run_hook_in_own_context(Hook::BufWritePre, buffer->name(), context.name()); - write_buffer_to_file(*buffer, buffer->name(), sync ? WriteFlags::Sync : WriteFlags::None); + write_buffer_to_file(*buffer, buffer->name(), mode, sync ? WriteFlags::Sync : WriteFlags::None); buffer->run_hook_in_own_context(Hook::BufWritePost, buffer->name(), context.name()); } } diff --git a/src/file.cc b/src/file.cc index d5e73bed..eb26ddc4 100644 --- a/src/file.cc +++ b/src/file.cc @@ -280,25 +280,51 @@ void write_buffer_to_fd(Buffer& buffer, int fd) } } -void write_buffer_to_file(Buffer& buffer, StringView filename, WriteFlags flags) +int open_temp_file(StringView filename, char (&buffer)[PATH_MAX]) +{ + String path = real_path(filename); + auto [dir,file] = split_path(path); + + if (dir.empty()) + format_to(buffer, ".{}.kak.XXXXXX", file); + else + format_to(buffer, "{}/.{}.kak.XXXXXX", dir, file); + + return mkstemp(buffer); +} + +int open_temp_file(StringView filename) +{ + char buffer[PATH_MAX]; + return open_temp_file(filename, buffer); +} + +void write_buffer_to_file(Buffer& buffer, StringView filename, + WriteMethod method, WriteFlags flags) { - struct stat st; auto zfilename = filename.zstr(); + struct stat st; + + bool replace = method == WriteMethod::Replace; + bool force = flags & WriteFlags::Force; - if (flags & WriteFlags::Force and ::stat(zfilename, &st) == 0) + if ((replace or force) and ::stat(zfilename, &st) != 0) { - if (::chmod(zfilename, st.st_mode | S_IWUSR) < 0) - throw runtime_error("unable to change file permissions"); + force = false; + replace = false; } - else - flags |= ~WriteFlags::Force; + + if (force and ::chmod(zfilename, st.st_mode | S_IWUSR) < 0) + throw runtime_error("unable to change file permissions"); auto restore_mode = on_scope_end([&]{ - if (flags & WriteFlags::Force and ::chmod(zfilename, st.st_mode) < 0) + if ((force or replace) and ::chmod(zfilename, st.st_mode) < 0) throw runtime_error("unable to restore file permissions"); }); - const int fd = open(zfilename, O_CREAT | O_WRONLY | O_TRUNC, 0644); + char temp_filename[PATH_MAX]; + const int fd = replace ? open_temp_file(filename, temp_filename) + : open(zfilename, O_CREAT | O_WRONLY | O_TRUNC, 0644); if (fd == -1) throw file_access_error(filename, strerror(errno)); @@ -309,6 +335,9 @@ void write_buffer_to_file(Buffer& buffer, StringView filename, WriteFlags flags) ::fsync(fd); } + if (replace) + rename(temp_filename, zfilename); + if ((buffer.flags() & Buffer::Flags::File) and real_path(filename) == real_path(buffer.name())) buffer.notify_saved(); @@ -316,16 +345,7 @@ void write_buffer_to_file(Buffer& buffer, StringView filename, WriteFlags flags) void write_buffer_to_backup_file(Buffer& buffer) { - String path = real_path(buffer.name()); - auto [dir,file] = split_path(path); - - char pattern[PATH_MAX]; - if (dir.empty()) - format_to(pattern, ".{}.kak.XXXXXX", file); - else - format_to(pattern, "{}/.{}.kak.XXXXXX", dir, file); - - int fd = mkstemp(pattern); + const int fd = open_temp_file(buffer.name()); if (fd >= 0) { write_buffer_to_fd(buffer, fd); diff --git a/src/file.hh b/src/file.hh index db4ac6ff..6871a1a3 100644 --- a/src/file.hh +++ b/src/file.hh @@ -2,6 +2,7 @@ #define file_hh_INCLUDED #include "array_view.hh" +#include "enum.hh" #include "meta.hh" #include "string.hh" #include "units.hh" @@ -51,6 +52,19 @@ struct MappedFile struct stat st {}; }; +enum class WriteMethod +{ + Overwrite, + Replace +}; +constexpr auto enum_desc(Meta::Type<WriteMethod>) +{ + return make_array<EnumDesc<WriteMethod>, 2>({ + { WriteMethod::Overwrite, "overwrite" }, + { WriteMethod::Replace, "replace" }, + }); +} + enum class WriteFlags { None = 0, @@ -60,7 +74,7 @@ enum class WriteFlags constexpr bool with_bit_ops(Meta::Type<WriteFlags>) { return true; } void write_buffer_to_file(Buffer& buffer, StringView filename, - WriteFlags flags); + WriteMethod method, WriteFlags flags); void write_buffer_to_fd(Buffer& buffer, int fd); void write_buffer_to_backup_file(Buffer& buffer); diff --git a/src/main.cc b/src/main.cc index 95d5ca41..3cd9bc50 100644 --- a/src/main.cc +++ b/src/main.cc @@ -399,6 +399,9 @@ void register_options() reg.declare_option("autoreload", "autoreload buffer when a filesystem modification is detected", Autoreload::Ask); + reg.declare_option("writemethod", + "how to write buffer to files", + WriteMethod::Overwrite); reg.declare_option<int, check_timeout>( "idle_timeout", "timeout, in milliseconds, before idle hooks are triggered", 50); reg.declare_option<int, check_timeout>( @@ -848,10 +851,10 @@ int run_filter(StringView keystr, ConstArrayView<StringView> files, bool quiet, Buffer* buffer = open_file_buffer(file, Buffer::Flags::NoHooks); if (not suffix_backup.empty()) write_buffer_to_file(*buffer, buffer->name() + suffix_backup, - WriteFlags::None); + WriteMethod::Overwrite, WriteFlags::None); apply_to_buffer(*buffer); write_buffer_to_file(*buffer, buffer->name(), - WriteFlags::None); + WriteMethod::Overwrite, WriteFlags::None); buffer_manager.delete_buffer(*buffer); } if (not isatty(0)) |
