Hello, list. I discovered this bug a few months back and I'm finally
getting around to posting it. Enjoy.

-Kevin

BUG: Final newline incorrectly removed from files

Vim incorrectly strips the final newline from files due to a misuse of
the write_no_eol_lnum global variable. This variable can be set during
the reading of one file (in fileio.c readfile) and used during the
writing of an unrelated file (in fileio.c buf_write), triggering the
bug. This script demonstrates the problem:

======== BEGIN SCRIPT ========
#!/bin/sh

echo "File with an end-of-line." > eol
echo -n "File without an end-of-line." > no-eol
cp eol eol-backup

vim -u NONE -N -b eol -c "sp no-eol|wincmd p|w|qa"

if ! cmp -s eol eol-backup; then
    echo 'Vim stripped the newline!'
fi
======== END SCRIPT ========

Note that this bug is only triggered when the no-newline file has the
same number of lines as the file being written.

I don't think I fully understand the usage of write_no_eol_lnum, but my
best guess to fix this is to reset it to 0 after the autocommands are
executed near the end of readfile. In general it seems like the best
practice would be to limit the range over which write_no_eol_lnum is
non-zero to be only what is necessary. The following first-pass patch is
based an that. Things that may still be missing include:

* Places where write_no_eol_lnum is reset to 0 "in case it was set by
  the previous read" may not be needed anymore, but this isn't clear.
  Nested calls due to autocommand execution might still need this (or
  might be broken regardless of what is done with this global variable).

* write_no_eol_lnum is still set to non-zero in os_unix.c. I don't know
  why, or if it's necessary. In any case, if it is set to non-zero it
  seems that it *should* be reset to zero once it is no longer needed.

diff -r df6b12c84b23 src/fileio.c
--- a/src/fileio.c Wed Oct 27 18:36:36 2010 +0200
+++ b/src/fileio.c Mon Nov 01 11:24:25 2010 -0700
@@ -2605,13 +2605,6 @@
     check_marks_read();
 #endif

- /*
- * Trick: We remember if the last line of the read didn't have
- * an eol for when writing it again. This is required for
- * ":autocmd FileReadPost *.gz set bin|'[,']!gunzip" to work.
- */
- write_no_eol_lnum = read_no_eol_lnum;
-
     /* When reloading a buffer put the cursor at the first line that is
      * different. */
     if (flags & READ_KEEP_UNDO)
@@ -2630,6 +2623,13 @@
     }
 #endif

+ /*
+ * Trick: We remember if the last line of the read didn't have
+ * an eol for when writing it again. This is required for
+ * ":autocmd FileReadPost *.gz set bin|'[,']!gunzip" to work.
+ */
+ write_no_eol_lnum = read_no_eol_lnum;
+
 #ifdef FEAT_AUTOCMD
     if (!read_stdin && !read_buffer)
     {
@@ -2665,6 +2665,9 @@
     }
 #endif

+ /* don't affect future writes */
+ write_no_eol_lnum = 0;
+
     if (recoverymode && error)
  return FAIL;
     return OK;

-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

Reply via email to