Vim Tips Wiki
(→‎Comments: missing '|' command separator (Fritzophrenic...dammit wikia and your javascript))
Line 64: Line 64:
 
\ let s:l_tot = line('$') |
 
\ let s:l_tot = line('$') |
 
\ let s:l_dos = 0 + s:num_dos_ends |
 
\ let s:l_dos = 0 + s:num_dos_ends |
\ echomsg s:l_dos 'DOS-style line endings found in' s:l_tot 'lines'
+
\ echomsg s:l_dos 'DOS-style line endings found in' s:l_tot 'lines' |
 
\ if &noro && &ma |
 
\ if &noro && &ma |
 
\ if s:l_tot > s:l_dos * 2 |
 
\ if s:l_tot > s:l_dos * 2 |

Revision as of 14:22, 21 September 2011

Tip 1662 Printable Monobook Previous Next

created October 14, 2010 · complexity basic · author Fritzophrenic · version 7.0


If you frequently work with files which "some idiot" saved with mixed line-ending styles, so that Vim incorrectly detects the fileformat, you might wish Vim could automatically read the file in the correct format without requiring you to correct the fileformat manually.

This autocmd in your vimrc will do just that. If it finds any occurrences of a carriage return at the end of a line, and the file was read in Unix format, it will automatically re-read the file in DOS format. This will automatically correct the issue for good when you save the file, but it is most useful for times when you cannot save the file for some reason (maybe it is in a code module that is "locked down" for example).

autocmd BufReadPost * nested
      \ if !exists('b:reload_dos') && !&binary && &ff=='unix' && (0 < search('\r$', 'nc')) |
      \   let b:reload_dos = 1 |
      \   e ++ff=dos |
      \ endif

Most of this is self-explanatory, but there are a couple tricks to it:

  • The 'n' flag is used on the search, so that the cursor does not move.
  • The autocmd is nested. This allows other autocmds, like those that load syntax highlighting, etc., to also fire on the reload.
  • Because the autocmd is nested, it will itself fire again on the reload. Thus, we use the b:reload_dos variable to detect when it has already fired so we do not keep firing recursively.

This can be improved a bit. It might be nice to have a warning of some sort when this auto-reload occurs. Or maybe you want to change as few lines as possible, so you can manually convert the fileformat to the one that changes the fewest lines (for example, if only 2 lines out of 12000 have DOS-style endings, why convert the whole file to DOS?). Also, the detection of DOS-format endings uses a search, which could take a very long time on very large files.

The following version (when possible) limits the length of time allowed for the search before giving up, and additionally gives a message to the user by redirecting the output of an :s command with the 'n' flag to count matches without making changes and parsing it to output a message like, "File has 2 DOS-style lined endings out of 12000 lines."

" When loading a file, if it reads in as Unix, but has a DOS line ending,
" and is not in binary mode, reload it in DOS format. Do this AFTER loading
" last known position to prevent always opening on last line.
"
" Time out the search after 1/10 second. Timeouts only available in 7.1.211
" and up, so just risk long loads in earlier versions.
if (v:version > 701 || v:version == 701 && has("patch211"))
  autocmd BufReadPost * nested
        \ if !exists('b:reload_dos') && !&binary && &ff=='unix' && (0 < search('\r$', 'nc', 0, 100)) |
        \   let b:reload_dos = 1 |
        \   redir => s:num_dos_ends |
        \   silent %s#\r$##n |
        \   redir END |
        \   e ++ff=dos |
        \   echomsg "File has ".
        \     substitute(s:num_dos_ends, '^.\{-}\(\d\+\).*', '\1', '').
        \     " DOS-style line endings out of ".line('$')." lines." |
        \ endif
else
  autocmd BufReadPost * nested
        \ if !exists('b:reload_dos') && !&binary && &ff=='unix' && (0 < search('\r$', 'nc')) |
        \   let b:reload_dos = 1 |
        \   e ++ff=dos |
        \ endif
endif
autocmd BufReadPost * if exists('b:reload_dos') | unlet b:reload_dos | endif

Comments

To set the 'fileformat' to whatever will change the fewer lines, replace the echomsg command in the above snippet by the following:

  \ let s:l_tot = line('$') |
  \ let s:l_dos = 0 + s:num_dos_ends |
  \ echomsg s:l_dos 'DOS-style line endings found in' s:l_tot 'lines' |
  \ if &noro && &ma |
    \ if s:l_tot > s:l_dos * 2 |
      \ setlocal ff=unix |
      \ echomsg 'File format set to UNIX' |
    \ else |
      \ echomsg 'File format set to DOS' |
    \ endif |
  \ endif |

(the next statement is the final endif ).
Notes:

  • This applies to Vim 7.1.211 or later; how to do it for earlier versions too is left as an exercise to the student.
  • The Unix/Dos switch is bypassed if the file is 'readonly' or 'nomodifiable' at the time of the BufReadPost event. Either option can be set afterwards by a modeline.
  • This takes advantage of the fact that adding zero to a String gives the Number found at the start of the string (or zero if the string starts with a non-numeric character or is empty) — see (of all places) :help octal

Tonymec 04:41, September 21, 2011 (UTC)