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 +
|
_default_ ask +
|
||||||
auto reload the buffers when an external modification is detected
|
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)`::
|
*debug* `flags(hooks|shell|profile|keys|commands)`::
|
||||||
dump various debug information in the '\*debug*' buffer
|
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");
|
throw runtime_error("cannot overwrite the buffer when in readonly mode");
|
||||||
|
|
||||||
auto effective_filename = not filename ? buffer.name() : parse_filename(*filename);
|
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);
|
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);
|
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())
|
buffer->is_modified())
|
||||||
and !(buffer->flags() & Buffer::Flags::ReadOnly))
|
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());
|
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());
|
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;
|
String path = real_path(filename);
|
||||||
auto zfilename = filename.zstr();
|
auto [dir,file] = split_path(path);
|
||||||
|
|
||||||
if (flags & WriteFlags::Force and ::stat(zfilename, &st) == 0)
|
if (dir.empty())
|
||||||
{
|
format_to(buffer, ".{}.kak.XXXXXX", file);
|
||||||
if (::chmod(zfilename, st.st_mode | S_IWUSR) < 0)
|
|
||||||
throw runtime_error("unable to change file permissions");
|
|
||||||
}
|
|
||||||
else
|
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([&]{
|
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");
|
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)
|
if (fd == -1)
|
||||||
throw file_access_error(filename, strerror(errno));
|
throw file_access_error(filename, strerror(errno));
|
||||||
|
|
||||||
|
@ -309,6 +335,9 @@ void write_buffer_to_file(Buffer& buffer, StringView filename, WriteFlags flags)
|
||||||
::fsync(fd);
|
::fsync(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (replace)
|
||||||
|
rename(temp_filename, zfilename);
|
||||||
|
|
||||||
if ((buffer.flags() & Buffer::Flags::File) and
|
if ((buffer.flags() & Buffer::Flags::File) and
|
||||||
real_path(filename) == real_path(buffer.name()))
|
real_path(filename) == real_path(buffer.name()))
|
||||||
buffer.notify_saved();
|
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)
|
void write_buffer_to_backup_file(Buffer& buffer)
|
||||||
{
|
{
|
||||||
String path = real_path(buffer.name());
|
const int fd = open_temp_file(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);
|
|
||||||
if (fd >= 0)
|
if (fd >= 0)
|
||||||
{
|
{
|
||||||
write_buffer_to_fd(buffer, fd);
|
write_buffer_to_fd(buffer, fd);
|
||||||
|
|
16
src/file.hh
16
src/file.hh
|
@ -2,6 +2,7 @@
|
||||||
#define file_hh_INCLUDED
|
#define file_hh_INCLUDED
|
||||||
|
|
||||||
#include "array_view.hh"
|
#include "array_view.hh"
|
||||||
|
#include "enum.hh"
|
||||||
#include "meta.hh"
|
#include "meta.hh"
|
||||||
#include "string.hh"
|
#include "string.hh"
|
||||||
#include "units.hh"
|
#include "units.hh"
|
||||||
|
@ -51,6 +52,19 @@ struct MappedFile
|
||||||
struct stat st {};
|
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
|
enum class WriteFlags
|
||||||
{
|
{
|
||||||
None = 0,
|
None = 0,
|
||||||
|
@ -60,7 +74,7 @@ enum class WriteFlags
|
||||||
constexpr bool with_bit_ops(Meta::Type<WriteFlags>) { return true; }
|
constexpr bool with_bit_ops(Meta::Type<WriteFlags>) { return true; }
|
||||||
|
|
||||||
void write_buffer_to_file(Buffer& buffer, StringView filename,
|
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_fd(Buffer& buffer, int fd);
|
||||||
void write_buffer_to_backup_file(Buffer& buffer);
|
void write_buffer_to_backup_file(Buffer& buffer);
|
||||||
|
|
||||||
|
|
|
@ -399,6 +399,9 @@ void register_options()
|
||||||
reg.declare_option("autoreload",
|
reg.declare_option("autoreload",
|
||||||
"autoreload buffer when a filesystem modification is detected",
|
"autoreload buffer when a filesystem modification is detected",
|
||||||
Autoreload::Ask);
|
Autoreload::Ask);
|
||||||
|
reg.declare_option("writemethod",
|
||||||
|
"how to write buffer to files",
|
||||||
|
WriteMethod::Overwrite);
|
||||||
reg.declare_option<int, check_timeout>(
|
reg.declare_option<int, check_timeout>(
|
||||||
"idle_timeout", "timeout, in milliseconds, before idle hooks are triggered", 50);
|
"idle_timeout", "timeout, in milliseconds, before idle hooks are triggered", 50);
|
||||||
reg.declare_option<int, check_timeout>(
|
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);
|
Buffer* buffer = open_file_buffer(file, Buffer::Flags::NoHooks);
|
||||||
if (not suffix_backup.empty())
|
if (not suffix_backup.empty())
|
||||||
write_buffer_to_file(*buffer, buffer->name() + suffix_backup,
|
write_buffer_to_file(*buffer, buffer->name() + suffix_backup,
|
||||||
WriteFlags::None);
|
WriteMethod::Overwrite, WriteFlags::None);
|
||||||
apply_to_buffer(*buffer);
|
apply_to_buffer(*buffer);
|
||||||
write_buffer_to_file(*buffer, buffer->name(),
|
write_buffer_to_file(*buffer, buffer->name(),
|
||||||
WriteFlags::None);
|
WriteMethod::Overwrite, WriteFlags::None);
|
||||||
buffer_manager.delete_buffer(*buffer);
|
buffer_manager.delete_buffer(*buffer);
|
||||||
}
|
}
|
||||||
if (not isatty(0))
|
if (not isatty(0))
|
||||||
|
|
Loading…
Reference in New Issue
Block a user