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
This commit is contained in:
parent
e8f26cbae7
commit
4dae2c875b
|
@ -240,6 +240,13 @@ are exclusively available to built-in options.
|
|||
_default_ ask +
|
||||
auto reload the buffers when an external modification is detected
|
||||
|
||||
*writemethod* `enum(overwrite|replace)`::
|
||||
_default_ overwrite +
|
||||
method used to write buffers to file, `overwrite` will open the
|
||||
existing file and write on top of the previous data, `replace`
|
||||
will open a temporary file next to the target file, write it and
|
||||
then rename it to the target file.
|
||||
|
||||
*debug* `flags(hooks|shell|profile|keys|commands)`::
|
||||
dump various debug information in the '\*debug*' buffer
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
62
src/file.cc
62
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])
|
||||
{
|
||||
struct stat st;
|
||||
auto zfilename = filename.zstr();
|
||||
String path = real_path(filename);
|
||||
auto [dir,file] = split_path(path);
|
||||
|
||||
if (flags & WriteFlags::Force and ::stat(zfilename, &st) == 0)
|
||||
{
|
||||
if (::chmod(zfilename, st.st_mode | S_IWUSR) < 0)
|
||||
throw runtime_error("unable to change file permissions");
|
||||
}
|
||||
if (dir.empty())
|
||||
format_to(buffer, ".{}.kak.XXXXXX", file);
|
||||
else
|
||||
flags |= ~WriteFlags::Force;
|
||||
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)
|
||||
{
|
||||
auto zfilename = filename.zstr();
|
||||
struct stat st;
|
||||
|
||||
bool replace = method == WriteMethod::Replace;
|
||||
bool force = flags & WriteFlags::Force;
|
||||
|
||||
if ((replace or force) and ::stat(zfilename, &st) != 0)
|
||||
{
|
||||
force = false;
|
||||
replace = false;
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
16
src/file.hh
16
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);
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in New Issue
Block a user