Restore position after undo in vim - vim

I'm currently writing a vim script that runs my code through the gofmt linter. Once I have the output from the command, I run the following.
let view = winsaveview()
1,$d
undojoin | call setline(1, gofmt_output)
call winrestview(view)
This works great, however, when the user presses u to undo, the file scrolls back to the top. Is there a way around this? Below is a gif of the behavior.

Here's a hack I just found that seems to be working somewhat.
All I do is to make a bogus, noop change on the current line
before I replace the buffer contents.
In my case I simply read the new content from a file so it looks like this:
let winview = winsaveview()
:s/$//
:%!cat /tmp/.vimfmtnew
call winrestview(winview)
The s/$// bit is the noop change on the current line, and :%!cat /tmp/.vimfmtnew replaces the whole buffer with my tmpfile.
My theory about this is that vim collapses all changes from my function into a single undo action and puts the cursor position to the first action's first changed line. This now happens to be an action where my cursor was when I called the function so this puts the cursor onto the correct line.
It's not perfect though:
if you have the same buffer open in a split view,
the unfocused split views will still reset on undo/redo.
I think the correct solution is this:
diff the old and new content
and only replace the changed lines.
I think that would retain the positions in the split views too
but it's a bit more effort to implement this nicely.
(Doing the replace from the first changed line doesn't always work for me
because I use goimports as my formatter and that can change lines near the beginning too so that would almost be equivalent to a full replace.)

Related

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:
my - set y mark at current position
?\\begin{^M - Search backward for the beginning of the environment and jump there
_mz - go to first character of that line and set the z mark
% - Jump to the matching \end{... of the environment (This is part of the matchit vim plugin, delivered with vim but not active per default).
f{l- Jump forward to { and one character right
ci} - Change inner {...}
^Re^[ - Insert the content of the e register, where the new environment name is stored and return to normal mode
'z' - Jump to the line beginning of the z mark (The \begin{...)
f{l. - Forward to {, one step right and repeat the last change
`y - Jump to the y mark, the initial position
:delma yz - Delete the y and z mark
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.
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>).

Vim - sort the contents of a register before/after pasting it?

As part of a project of mine I'm trying to move certain lines from a file to the top, sorted in a certain fashion. I'm not sure how to do the sort once those lines are up there - I don't want to disturb the other lines in the file.
I'm moving them by yanking them and putting them back down, like so:
g:/pattern/yank A
g:/pattern/d
0put A
This moves all the lines I specify up to the top of the file like I need, but now I need to sort them according to a pattern, like so:
[range]sort r /pattern2/
Is there a way to sort the contents of a register before pasting it? Or a way to sort only lines which match /pattern/? (because all the yanked lines will, of course).
I'm stymied and help would be appreciated.
edit - a possible workaround might be to count the number of lines before they're yanked, and then use that to select and sort those lines once they're placed again. I'm not sure how to count those lines - I can print the number of lines that match a pattern with the command :%s/pattern//n but I can't do anything with that number, or use that in a function.
The whole point of :g/pattern/cmd is to execute cmd on every line matching pattern. cmd can, of course, be :sort.
In the same way you did:
:g/pattern/yank A
to append every line matching pattern to register a and:
:g/pattern/d
to cut every line matching pattern, you can do:
:g/pattern/sort r /pattern2/
to sort every line matching pattern on pattern2.
Your example is wasteful anyway. Instead of abusing registers with three commands you could simply do:
:g/pattern/m0
to move every line matching pattern to the top of the buffer before sorting them with:
:g//sort r /pattern2/
See :help :global, :help :sort, :help :move.
I know this is old, and may not be of any use to you anymore, but I just figured this one out today. It relies on the system's sort command (not vim's). Assuming you're saving to register A:
qaq
:g/pattern/yank A
<C-O>
:put=system('sort --stable --key=2,3',#A)
qaq: clears register A of anything
:g/pattern/yank A: searches current buffer for pattern and copies it to register A
<C-O>: pressing Ctrl+O in normal mode returns you to the last place your cursor was
:put=system('sort --stable --key=2,3',#A): sends the contents of register A to the sort command's STDIN and pastes the output to the current position of the cursor.
I mapped this whole thing to <F8>:
noremap <F8> qaq:g/pattern/yank A<CR><C-O>:put=system('sort --stable --key=2,3',#A)<CR>
I don't know how janky this is considered, cuz I'm a complete noob to vim. I spent hours today trying to figure this out. It works for me and I'm happy with it, hopefully it'll help someone else too.

How to use ":g/regex/d_" without losing current cursor position

In Vim, if you run
:g/some_word/d_
all lines containing "some_word" are removed and the cursor automatically jumps to the place of last deletion. I want the cursor to stay where it was before the operation. How do I accomplish this?
You can't make the cursor stay but you can make it go back to where it was:
:g/some_word/d_|norm ''
The answer by romainl is a good one, and it should do the trick without any additional fuss.
If this is a common problem, and you'd rather not add the additional norm! '' at the end (or type the key sequence when you're done), you could encapsulate it in a command:
command! -nargs=* G call s:G(<q-args>)
function! s:G(args)
let saved_position = winsaveview()
exe 'g'.a:args
call winrestview(saved_position)
endfunction
The :G command will invoke the s:G function with all of its arguments, which saves the position, runs the normal g with these exact arguments, and then restores it. So you'd do :G/some_word/d_ and it would run the command and restore the cursor in the line/column where it started.
Obviously, this only makes sense if you use it often enough and you don't often work on bare Vims (like on remote servers and such). Otherwise, it might be a better idea to try romainl's suggestion, or get used to typing in ''. Your choice.
Also, if the current position happens to be after some of these lines, the cursor might end up in an unexpected place. For example, if your cursor is on line 7 and the command deletes line 3, then you'll be back at line 7, but all the text will have shifted up one line, so you'll be in the "wrong" place. You could probably play around with this function and compensate for the change, but it'll get pretty complicated pretty fast, so I wouldn't recommend it :)
The anwolib plugin provides a handy :KeepView command; you can apply this to any Ex command, so it's even more generic than the :G command suggested by #AndrewRadev:
:KeepView g/some_word/d_

How to call a function when text is wrapped in vim?

In vim I want to visually make transparent the space I have to write a text in markdown. I use hard wrapping with textwidth=79. I know by some calculation that I'll have 20 lines for a chapter for example. So, what I do is inserting 20 empty lines to get a visual feeling for what I can write. After writing some lines, I manually delete the number of lines already written from the empty lines, so that the visual impression still is correct.
What I want to do, is to automate this deletion process. That means I want vim to automatically remove one line below the last written line if this line is empty and after vim automatically started a new line because I reached 79 characters in the line before. How can I do this?
I know that there are autocommands in vim but I haven't found an <event> that fits to the action: after vim automatically hard wraps a line / reached new line in insert (or however you would like to describe it)
I don't think there's an event for that particular action but there's a buffer-local option called formatexpr that gq & co will use, if set. So you can write a function that inspects any placeholder whitespace, if existing. That function can call the text format command gqq to maintain original feel (+ the cursor movement to the new, empty line).

How to prevent vim substitute from moving the cursor

The company I work for uses special headers for source files, which contain the date of last modification.
I wrote a vim script that updates this date automatically on each buffer write.
I am using the search/substitue feature to do the trick.
Now the problem is that the replace does move the cursor at the beginning of the file, which is very annoying because at each buffer write, the user has to jump back manually to the previous editing position.
Does anyone know a way to prevent vim from jumping when updating the date, or at least to make it jump back to the previous position?
Interactively, you can use <C-O> or the change mark `` to move back to the original position, as shown at vim replace all without cursor moving.
In a Vimscript, especially when this runs on every buffer write, I would wrap the code:
let l:save_view = winsaveview()
%substitute///
call winrestview(l:save_view)
The former move commands might still affect the window view (i.e. which exact lines and columns are shown in the viewport), whereas this solution restores everything as it was.
Bonus stuff
Also, be aware that the {pattern} used in the :substitute is added to the search history. To avoid that, append
call histdel('search', -1)
(The search pattern itself isn't affected when you have this in a :function.)
or use the :keeppatterns command introduced in Vim 8:
keeppatterns %substitute///
Related plugins
I've implemented similar functionality in my AutoAdapt plugin. You'll see all these tricks used there, too.
I think using the function substitute() will keep your changelist and jumplist. In neovim I am using a function to change the date of file modification this way:
M.changeheader = function()
-- We only can run this function if the file is modifiable
local bufnr = vim.api.nvim_get_current_buf()
if not vim.api.nvim_buf_get_option(bufnr, "modifiable") then
require("notify")("Current file not modifiable!")
return
end
-- if not vim.api.nvim_buf_get_option(bufnr, "modified") then
-- require("notify")("Current file has not changed!")
-- return
-- end
if vim.fn.line("$") >= 7 then
os.setlocale("en_US.UTF-8") -- show Sun instead of dom (portuguese)
local time = os.date("%a, %d %b %Y %H:%M:%S")
local l = 1
while l <= 7 do
vim.fn.setline(l, vim.fn.substitute(vim.fn.getline(l), 'last (change|modified): \\zs.*', time , 'gc'))
l = l + 1
end
require("notify")("Changed file header!")
end
end

Resources