diff --git a/README.asciidoc b/README.asciidoc index 03ba5eaa..628ccf88 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -676,8 +676,10 @@ command `q!` has to be used). * `e[dit][!] [ []]`: open buffer on file, go to given line and column. If file is already opened, just switch to this file. use edit! to force reloading. - * `w[rite] []`: write buffer to or use it's name if - filename is not given. + * `w[rite][!] []`: write buffer to or use it's name if + filename is not given. If the file is write-protected, its + 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. diff --git a/doc/manpages/commands.asciidoc b/doc/manpages/commands.asciidoc index 47a2b18a..cb20a832 100644 --- a/doc/manpages/commands.asciidoc +++ b/doc/manpages/commands.asciidoc @@ -24,8 +24,11 @@ command *q!* has to be used). open buffer on file, go to given line and column. If file is already opened, just switch to this file. Use edit! to force reloading -*w[rite]* []:: - write buffer to or use it's name if filename is not given +*w[rite][!]* []:: + write buffer to or use it's name if filename is not + given. If the file is write-protected, its 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 diff --git a/src/commands.cc b/src/commands.cc index 6143179d..355f560d 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -309,6 +309,7 @@ const CommandDesc force_edit_cmd = { edit }; +template void write_buffer(const ParametersParser& parser, Context& context, const ShellContext&) { Buffer& buffer = context.buffer(); @@ -326,7 +327,7 @@ void write_buffer(const ParametersParser& parser, Context& context, const ShellC buffer.name() : parse_filename(parser[0]); context.hooks().run_hook("BufWritePre", filename, context); - write_buffer_to_file(buffer, filename); + write_buffer_to_file(buffer, filename, force); context.hooks().run_hook("BufWritePost", filename, context); } @@ -342,6 +343,18 @@ const CommandDesc write_cmd = { write_buffer, }; +const CommandDesc force_write_cmd = { + "write!", + "w!", + "write [filename]: write the current buffer to its file " + "or to [filename] if specified, even when the file is write protected", + single_optional_param, + CommandFlags::None, + CommandHelper{}, + filename_completer, + write_buffer, +}; + void write_all_buffers(Context& context) { // Copy buffer list because hooks might be creating/deleting buffers @@ -2102,6 +2115,7 @@ void register_commands() register_command(edit_cmd); register_command(force_edit_cmd); register_command(write_cmd); + register_command(force_write_cmd); register_command(write_all_cmd); register_command(write_all_quit_cmd); register_command(kill_cmd); diff --git a/src/file.cc b/src/file.cc index bf592689..49fa00e5 100644 --- a/src/file.cc +++ b/src/file.cc @@ -278,9 +278,27 @@ void write_buffer_to_fd(Buffer& buffer, int fd) } } -void write_buffer_to_file(Buffer& buffer, StringView filename) +void write_buffer_to_file(Buffer& buffer, StringView filename, bool force) { - int fd = open(filename.zstr(), O_CREAT | O_WRONLY | O_TRUNC, 0644); + struct stat st; + auto zfilename = filename.zstr(); + + if (force) + { + if (::stat(zfilename, &st) == 0) + { + if (::chmod(zfilename, st.st_mode | S_IWUSR) < 0) + throw runtime_error("couldn't change file permissions"); + } + else + force = false; + } + auto restore_mode = on_scope_end([&]{ + if (force and ::chmod(zfilename, st.st_mode) < 0) + throw runtime_error("couldn't restore file permissions"); + }); + + int fd = open(zfilename, O_CREAT | O_WRONLY | O_TRUNC, 0644); if (fd == -1) throw file_access_error(filename, strerror(errno)); diff --git a/src/file.hh b/src/file.hh index 7cb5f88c..23fbe0a2 100644 --- a/src/file.hh +++ b/src/file.hh @@ -49,7 +49,7 @@ struct MappedFile struct stat st {}; }; -void write_buffer_to_file(Buffer& buffer, StringView filename); +void write_buffer_to_file(Buffer& buffer, StringView filename, bool force = false); void write_buffer_to_fd(Buffer& buffer, int fd); void write_buffer_to_backup_file(Buffer& buffer);