Aaron Massey
Aaron Massey

Reputation: 3996

Strange looping behavior with Preserve function call in Vim

I am using a function that I got from Vimcasts to preserve the cursor position when executing a command in Vim:

" A command to preserve last search and cursor position after running another
" command.  See: http://vimcasts.org/episodes/tidying-whitespace/
function! Preserve(command)
  " Preparation: save last search, and cursor position.
  let _s=@/
  let l = line(".")
  let c = col(".")
  " Do the business:
  execute a:command
  " Clean up: restore previous search history, and cursor position
  let @/=_s
  call cursor(l, c)
endfunction

" Strip trailing whitespace
nmap <Leader>$ :call Preserve("%s/\\s\\+$//e")<CR>

It works pretty well for the strip trailing whitespace mapping I've shown here, but not when I'm calling an external command like this:

" Reformat a plain text document to use hard wrapping and uniform spacing
"   Note: This uses the BSD `fmt` program.  The GNU coreutils version takes
"   different options.
nmap <Leader>f :call Preserve("%!fmt -s -78")<CR>
vnoremap <Leader>f :call Preserve("'<,'>!fmt -s -78")<CR>

The first mapping works fine, but the second one exhibits a strange looping behavior. For example, if I have a text file like this:

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut laboret dolore magna aliqua. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Sed ut perspiciatis unde omnis iste natus error sit. Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?

At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga.  Itaque reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.

When I select those lines in visual mode to filter them, the command appears to run five times. Here's what I'm seeing in the output:

5 lines filtered
5 lines filtered
5 lines filtered
5 lines filtered
5 lines filtered
Press ENTER or type command to continue

If the file has 10 lines, then they are filtered 10 times. It still filters the area correctly, but I'm confused as to why it's looping. I think it has something to do with the Preserve function because running the command outside of preserve doesn't exhibit the looping.

Note: I think this is the appropriate place for this question, but the closing of the Vi/Vim proposal leaves me wondering where I should really be posting a question like this. Please let me know if there's a more appropriate forum for it.

Upvotes: 1

Views: 61

Answers (1)

romainl
romainl

Reputation: 196926

When you call a function on a multi-line visual selection, that function is called for each line in the selection. Since your visual selection covers 5 lines the Preserve() function and the command you passed to it are called 5 times.

The solution is simple, add the range argument to the function definition:

function! Preserve() range

With that argument, the function is called only once and you can let it or the underlying command deal with the visual range itself.

See :help func-range.

Another – slightly dirtier – solution could be to modify your mappings to remove the range before calling the function so that it is called only once:

map <key> :<C-u>call Function(args)<CR>

See :help c_ctrl-u.

Upvotes: 2

Related Questions