From 832a68835b741bf874b936f933d662a5e1159e32 Mon Sep 17 00:00:00 2001 From: Alexej Kowalew <616b2f@gmail.com> Date: Fri, 8 May 2026 09:54:30 +0200 Subject: [PATCH] fix(shell): preserve CR when `:!` outputs to binary-mode buffer #39558 Problem: When `:!` writes shell output to a buffer, write_output() splits on `\r`, `\n`, and `\r\n`, replacing the terminator byte with NUL. For a binary-mode buffer this is wrong: `\r` should be preserved verbatim, not treated as a line terminator. This wrong behavior causes a file like `\r\n` round-trips through `:%!cat` to `\n`. This was masked when 'shelltemp' was enabled, because output went through a temp file and the regular file I/O path handled binary-mode correctly. Switching the default to 'noshelltemp' exposed the bug, since output is now piped directly into write_output(). Solution: In `write_output()`, skip the `\r` and `\r\n` splits for a binary-mode buffer; only split on `\n`. --- src/nvim/os/shell.c | 5 +++-- test/functional/vimscript/system_spec.lua | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 4cc9188d16..71dce1783a 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -1254,7 +1254,8 @@ static size_t write_output(char *output, size_t remaining, bool eof) size_t off = 0; while (off < remaining) { // CRLF - if (output[off] == CAR && output[off + 1] == NL) { + // special case: for binary mode, don't remove CR. + if (output[off] == CAR && output[off + 1] == NL && !curbuf->b_p_bin) { output[off] = NUL; ml_append(curwin->w_cursor.lnum++, output, (int)off + 1, false); size_t skip = off + 2; @@ -1262,7 +1263,7 @@ static size_t write_output(char *output, size_t remaining, bool eof) remaining -= skip; off = 0; continue; - } else if (output[off] == CAR || output[off] == NL) { + } else if ((output[off] == CAR && !curbuf->b_p_bin) || output[off] == NL) { // Insert the line output[off] = NUL; ml_append(curwin->w_cursor.lnum++, output, (int)off + 1, false); diff --git a/test/functional/vimscript/system_spec.lua b/test/functional/vimscript/system_spec.lua index 9a0df7acb7..d4e085cf9f 100644 --- a/test/functional/vimscript/system_spec.lua +++ b/test/functional/vimscript/system_spec.lua @@ -567,6 +567,25 @@ end) describe('shell :!', function() before_each(clear) + it('preserves newlines (including CR) in binary-mode buffer #39424', function() + local fname = 'Xbinaryfile' + finally(function() + os.remove(fname) + end) + + t.write_file(fname, '\r\n', true) + command('edit ++bin ' .. fname) + command('%!cat') + command('w') + eq('\r\n', t.read_file(fname)) + + t.write_file(fname, '\r', true) + command('edit ++bin ' .. fname) + command('%!cat') + command('w') + eq('\r', t.read_file(fname)) + end) + it(':{range}! works when the first char is NUL #34163', function() api.nvim_buf_set_lines(0, 0, -1, true, { '\0hello', 'hello' }) command('%!cat')