Hans Hohenfeld
Hans Hohenfeld

Reputation: 1739

VIM: Restore position after undo of a function

For my LaTeX editing in vim, I recorded a handful of useful macros and wrapped them into functions/command. I have one, that change the type of a Latex environment e.g., when I have:

\begin{itemize}
     \item First
     \item Second
\end{itemize}

I just enter :ChangeEnv enumerate with the cursor somewhere in the environment to change from itemize to enumerate.

The code in my ftplugin/tex.vim looks like this:

function! ChangeEnv(newenv)
    let l:save = @e
    let @e = a:newenv

    let l:line = getline('.')

    " Fake change to restore undo
    normal ix
    normal x

    if match(l:line, '\\begin{') != -1
        normal mz_%f{lci}e'zf{l.`z:delma   z
    else
        normal my?\\begin{^M_mz%f{lci}^Re^['zf{l.`y:delma    yz
    endif

    let @e = l:save
endfunction

command -nargs=1 ChangeEnv :silent call ChangeEnv(<f-args>)

The first part (after if match(...) intended if the cursor is on the \begin{...} part of the environment works perfectly so far, I can make the change and undo it, cursor stays where it should.

The second part, intended for inside the environment, also works great, but when the change is undone, the cursor jumps to the first charachter of the \begin line.

The normal ix and normal x part is intended to ensure the cursor position is restored after the und (I have this from here: Restor Cursor Position)

My question is, why doesn't it work for the second macro? Is there some error?

To spare you deconstructing the macro, this are the steps:

The undo behavior is not a deal breaker, nevertheless, I'd at least like to know why it behaves that way.

Thank you in advance.

Upvotes: 2

Views: 369

Answers (1)

Ingo Karkat
Ingo Karkat

Reputation: 172520

Usually, when you make multiple changes, each one is undone separately. But inside a function, everything is lumped together as one single change.

Your intention of doing this dummy change at the beginning (when the cursor hasn't yet been moved) is that the cursor returns to that point after undo.

However, I think as Vim treats the whole set of changes done within ChangeEnv() as one big modification, it returns to the beginning of the range of changed lines (mark '[), as it does when undoing built-in commands. Which change command was executed "first" doesn't matter, all that matters is the range of lines that got changed. As your second branches searches backwards to the \begin and makes a change there, that's where the cursor will be after undo. I don't think there's a way around this; undo cannot be influenced by scripts, as this could potentially introduce inconsistencies.

What you could do is setting a mark after doing the changes (at the end of your function: :normal! m'), so that you can quickly jump back there (via `` or <C-o>).

Upvotes: 3

Related Questions