Thursday, August 2, 2018

Recreating vim's newline at EOF logic

Hello all:

I know I am entering a minor quagmire by asking about vim "adding" a newline at the EOF when saving a file. I'll mention that I know the POSIX standard states that a line is "A sequence of zero or more non- <newline> characters plus a terminating <newline> character." (from: http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206). This isn't another post asking why vim writes the newline on save.

The background for my question is that I am trying to submit a pull request for the vim-lsp plugin (https://github.com/prabirshrestha/vim-lsp), which provides Language Server Protocol support to vim. I was getting some linting errors about "no newline at end of file" when using this plugin in vim, but not when sending the file directly to the linter and skipping vim in the middle.

I tracked the issue down to how vim-lsp is sending the contents of the file to the Language Server. The Language Server specification has a "didChange" message that expects you to send the contents of the file/buffer inside of the message. I'm pretty sure you send the contents of the buffer instead of just a file path so that you can have linting performed on your buffer even if it hasn't been saved to disk.

Ultimately the issue comes down to how we can grab the full contents of a buffer. The original code in vim-lsp looks like this:

function! s:get_text_document_text(buf) abort
let l:buf_fileformat = getbufvar(a:buf, '&fileformat')
let l:line_ending = {'unix': "\n", 'dos': "\r\n", 'mac':"\r"}[l:buf_fileformat]
return join(getbufline(a:buf, 1, '$'), l:line_ending)
endfunction

getbufline is grabbing all of the contents of the buffer and returning a list of the lines. Each one of the items in the list does not contain the newline character that would be found in the file. This is why the join function is used, to insert a fileformat-dependent newline in between each of the lines. As-is, the join doesn't append a newline on the last item, which led to the linter complaining.

My pull request to fix this attempts to inspect and respect the eol, binary, and fixeol options to recreate what would be found on disk:

function! s:requires_eol_at_eof(buf) abort
let l:file_ends_with_eol = getbufvar(a:buf, '&eol')
let l:vim_will_save_with_eol = !getbufvar(a:buf, '&binary') &&
\ getbufvar(a:buf, '&fixeol')
return l:file_ends_with_eol || l:vim_will_save_with_eol
endfunction

function! s:get_text_document_text(buf) abort
let l:buf_fileformat = getbufvar(a:buf, '&fileformat')
let l:eol = {'unix': "\n", 'dos': "\r\n", 'mac': "\r"}[l:buf_fileformat]
return join(getbufline(a:buf, 1, '$'), l:eol).(s:requires_eol_at_eof(a:buf) ? l:eol : '')
endfunction

After staring at :help eol, :help fixeol, and :help binary, I *think* I'm recreating the logic that vim uses when deciding to put the newline in the file. Basically, eol is set if the file contained the eol on load. The user can unset this if they want, but they need to be aware of fixeol as well. Vim will attempt to fix a file missing eol at EOF unless the file is loaded with binary or the user has set nofixeol. If the user has disabled fixing eol or loaded a file as binary, but the file contained the eol at EOF then vim will obviously put the eol back.

Does this logic match what we expect vim to do? Is there a different way to get the contents of the buffer that includes the newline characters?

Adam

--
--
You received this message from the "vim_use" 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

---
You received this message because you are subscribed to the Google Groups "vim_use" group.
To unsubscribe from this group and stop receiving emails from it, send an email to vim_use+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

No comments: