Restoring a register after a <C-r> function call - vim

I am currently writing a plugin for Vim and I would like that it restores the default register after I execute it. However, the function in question is called via a <C-r>=Myfunction()<CR> construct, which means that I need to restore it after the function return. I've tried to do this like so:
inoremap <silent> <Space> <C-r>=Myfunction()<CR>
function! Myfunction()
let oldreg = getreg('"')
let oldregtype = getregtype('"')
let restore = "\<ESC>:call setreg('\"','".oldreg."','".oldregtype."')\<CR>a"
let #" = "whatever"
return "\<ESC>yya ".restore
endfunction
As you can see, the " register is affected by the return string, so I cannot call setreg directly. Obviously this function doesn't really do anything, but the actual function I'm using is quite long. Also, I apologize if that string is a little hard to read, but I'm not really sure of any other way of accomplishing this. All in all, the function seems to work when the register contains a word, but fails whenever something with a newline is in the register. (The specific error is E115: Missing quote with respect to the oldreg argument.) I've tried to remedy this by shellescaping oldreg first; however, this results in the error E121: Undefined Variable, where the undefined variable is what was in my register. Any thoughts on what might be going wrong here?
EDIT: I found a solution. It's quite hairy, but it works perfectly so far. Here's how to apply to solution to my example code, just in case it helps anyone out there.
inoremap <silent> <Space> <C-r>=Myfunction()<CR>
function! Myfunction()
let oldreg = substitute(escape(getreg('"'), '\\'), '\n', '\\n', 'g')
let oldregtype = getregtype('"')
let restore = "\<ESC>:call setreg('\"',\"".oldreg."\",'".oldregtype."')\<CR>a"
let #" = "whatever"
return "\<ESC>yya ".restore
endfunction

Here is a way to proceed: Vim: how to paste over without overwriting register
Since then, we have been gifted with setreg(), and I've also developed a more generic solution simplify restoration of most useful things (lh#on#exit()).
In all cases, a solution would be to return #=FunctionToExecute(), and the restoration would happen in that function.
But as others said, you may need to be more explicit about your needs as there may exist more specific solutions to address them. For instance, instead of yanking with yy or with :yank, you could simply use getline() function that would let all registers unmodified. For changing a line, there is setline(), but this would break redo and other things.

Instead of going to normal mode after you return your function, I think
you should go inside it. This way, you can call setreg() normally
in it. For example:
function! Myfunction()
let oldreg = getreg('"')
let oldregtype = getregtype('"')
let #" = "whatever"
normal! yya
setreg('"', oldreg, oldregtype)
endfunction

Related

Assign variable to spelllang in vim script

im trying to create a vim function to cycle through different spelllang.
let g:SpellLanglist= ["en_us", "es"]
let s:lang_index=0
function! SpellLangCycle()
let l:lang=get(g:SpellLanglist, eval(g:lang_index))
set spelllang = l:lang
let g:lang_index = s:lang_index + 1
endfunction
nnoremap <leader>s :call SpellLangCycle()<CR>
I dont know how to assign lang variable to spelllang. set spelllang = l:lang is not working.
Thank you in advance
You should use:
let &spelllang = l:lang
You can set and read contents of an option (such as spellang) by using the & prefix. (The set command doesn't work with dynamic contents or local variables, unless you use execute to evaluate a string as a command, but that's much inferior to using & to refer to it.)
See :help :let-option for more details on using let with an option.
Your script has other issues as well. If you're going to track which language index was last set, you should probably do so in a per-buffer variable, since 'spelllang' is a per-buffer option.
You can use get() to read that variable but use a default variable if it's unset, which might be really helpful for the first time this code is run in a specific buffer.
You can access an item from the list with a simple [...] containing the index.
You also need to "cycle" the variable back to zero once it reaches the last element of the list. You can do so using the % operator with the length of the list.
Putting it all together:
let g:SpellLanglist= ["en_us", "es"]
function! SpellLangCycle()
let l:lang_index = get(b:, 'lang_index', -1)
let l:lang_index = (l:lang_index + 1) % len(g:SpellLanglist)
let &spelllang = g:SpellLanglist[l:lang_index]
let b:lang_index = l:lang_index
endfunction
nnoremap <leader>s :call SpellLangCycle()<CR>
But I think you can do even better. You don't need an external variable to store the index into the list, you can just look at the current &spelllang and find it in the list to find the currently set index.
You can do that with the index() function. It also returns -1 if the item is not found in the list, which works for us since after we increment it we'll get to the first item on the list.
function! SpellLangCycle()
let l:lang_index = index(g:SpellLanglist, &spelllang)
let l:lang_index = (l:lang_index + 1) % len(g:SpellLanglist)
let &spelllang = g:SpellLanglist[l:lang_index]
endfunction
One advantage of this approach is that this will always cycle to the next language in the list, even if you set 'spelllang' outside of this function... You can still use it to pick up from the point you set it to, or it will restart from the first one if what you set it to is not on the list.

Move to the latest file in the jumplist

With vim, I like to use C-o and C-i to move through the jumplist and I want to use the same to move to the previous and the next file with <leader>o and <leader>i.
I know I can use the buffer but the list is not always the same.
I tried to use EnhancedJump but I have some bugs and it seems to be obsolete.
Do you have solution?
It would be possible with functions like that:
function! JumpBack()
let l:cfile=expand('%')
let l:nfile=l:cfile
while l:cfile == l:nfile
execute 'normal ' . 1 . "\<c-o>"
let l:nfile=expand('%')
endwhile
endfunction
*Note this could be written cleaner, it is mostly pasted togheter.
However it seems like a sledgehammer method for me, maybe there is a better one.
Update
It was not surprising to hear, that this would lead to an endless loop if the jumplist only contains one file. Here is a better solution:
function! JumpBack()
let l:cfile=expand('%')
let l:jl = split(execute('jumps'), '\n')
let l:jumpcounter = 0
for l:jumpline in reverse(l:jl)
let l:jumpcounter = l:jumpcounter + 1
let l:nfile = split(l:jumpline, '\s')[-1]
if l:cfile != l:nfile
execute 'normal '. l:jumpcounter . "\<c-o>"
return
endif
endfor
endfunction

Mapping/macro to 'smartly' auto-create pairs of apostrophes in vim (and ignore contractions)

I'm currently using closepairs for my auto-closing needs, and it works pretty well. However, there is one caveat -- apostrophes. Don't get me wrong, I need apostrophes closed all the time. I don't want to just disable them. But whenever I type in plain text, whenever there are any contractions (I'm, Don't, Can't)...these apostrophes get made.
Now I could just type to delete them as soon as they can, but doing it every time is a bit impractical.
Does anyone know how I can possibly modify the closepairs script to only autoclose single quotes/apostrophes if they are the start of a word? That is, they are preceded by a whitespace character?
Here is the current code:
inoremap <expr> " <SID>pairquotes('"')
inoremap <expr> ' <SID>pairquotes("'")
function! s:pairquotes(pair)
let l:col = col('.')
let l:line = getline('.')
let l:chr = l:line[l:col-1]
if a:pair == l:chr
return "\<right>"
else
return a:pair.a:pair."\<left>"
endf
I don't know closepairs, but the AutoClose - Inserts matching bracket, paren, brace or quote plugin handles this well. You'll find a list of plugin alternatives on the Vim Tips Wiki.
Are you sure you want to autocomplete only after whitespace? In that case, something like function('string') would not autocomplete after the parenthesis.
Regardless, you can check the previous character against some regex. For example, to avoid autocompletion after letters:
function! s:pairquotes(pair)
let l:line = getline('.')
let l:col = col('.')
let l:chr = l:line[l:col - 1]
let l:prev = l:line[l:col - 2]
if l:chr == a:pair
return "\<right>"
elseif l:prev !~ "[A-Za-z]"
return a:pair . a:pair . "\<left>"
else
return a:pair
endif
endfunction
Note that there are exceptions even with this conservative example, like typing r'regex' in Python, so it might also make sense to define filetype-specific behavior.

Can you do interactive macros or recordings in vim?

I would like to define a vim macro that breaks for user input at certain times, is this possible?
EDIT: Turns out I ment recordings (q), not macros
It is possible to use the input command in a recording, but it's more trouble than it's worth.
I first mapped insert input escape to a key
:map <F2> a<C-R>=input('input: ')<CR>
then I made this recording in the q register
name:
and pasted it into a new tab
iname: ^[
And after the final escape I pressed <C-V><F2> making the line:
iname ^[^[OQ
That I yanked back to the q buffer then used the macro, letting me use the input function.
It works, but terribly.
Yes. See the function input({prompt}, [, {text} [, {completion}] ]). There is even
inputdialog({prompt} [, {text} [, {cancelreturn}]]), for a dialog popup.
If you use input() inside a mapping or macro, the remaining characters will be taken as input, which is not what you want. Vim offers the inputsave() and inputrestore() functions to temporarily suspend reading from the mapping character stream.
Based on mogelbrod's answer, this doesn't work; the itest is read in as input:
oBEFORE ^R=input('prompt> ')^Mitest
But this does:
function! Input()
call inputsave()
let text = input('prompt> ')
call inputrestore()
return text
endfunction
oBEFORE ^R=Input()^Mitest
Unfortunately, because <C-R> takes an expression, we cannot put the commands inline, but have to define a separate Input() function.
Unfortunately it doesn't seem to be possible. You can trigger input() inside a macro, but continuing on afterwards doesn't seem to be possible as any additional input recorded is inserted into the input prompt.
Yank the line into a named register ("qY) and run it (#q) to try it out.
Note: replace ^R and ^M with Ctrl-V Ctrl-R/M (see :help i_CTRL-V).
oBEFORE ^R=input('prompt> ') - works
oBEFORE ^R=input('prompt> ')^Mitest - works, but inserts itest into the prompt
oBEFORE ^R=input('prompt> ')<CR>test - fails
I have collected information from this and other threads and written this script:
function! MacroInterrupt()
"call inputsave()
if strlen(reg_recording()) == 0
if mode() == 'n'
call inputsave()
let tmp_col = col('.')
let tmp_line = line('.')
let text = input('input:')
let line = getline('.')
call setline('.', strpart(line, 0, col('.') - 1) . text . strpart(line, col('.') - 1))
call cursor(tmp_line, tmp_col + strlen(text))
call inputrestore()
return text
else
call inputsave()
let text = input('input:')
call inputrestore()
return text
endif
else
echo "Interrupt added to macro"
call setreg(reg_recording(), getreg(reg_recording()) . "\<F2>")
"echo getreg("q")
endif
"call inputrestore()
endfunction
map <F2> :call MacroInterrupt() <CR>
inoremap <buffer><expr> <F2> MacroInterrupt()
I hope this can help especially people attempting the same.

How to repeatedly add text on both sides of a word in vim?

I have a bunch of local variable references in a Python script that I want to pull from a dictionary instead. So, I need to essentially change foo, bar, and others into env['foo'], env['bar'] and so on. Do I need to write a regular expression and match each variable name to transform, or is there a more direct approach that I could just repeat with the . command?
You can use a macro: type these commands in one go (with spacing just to insert comments)
" first move to start of the relevant word (ie via search)
qa " record macro into the a register.
ienv['<esc> " insert relevant piece
ea'] " move to end of word and insert relevant piece
q " stop recording
then, when you're on the next word, just hit #a to replay the macro (or even ## to repeat the last replay after that).
There's an easier way - you can use a regex search and replace. Go into cmdline mode by typing a colon and then run this command:
%s/\\(foo\|bar\|baz\\)/env['\1']/
Replacing foo, bar, and baz with whatever your actual variable names are. You can add as many additional variables as you'd like, just be sure to escape your OR pipes with a backslash. Hope that helps.
you could write a function that would do this pretty well, add this to your .vimrc file:
function! s:surround()
let word = expand("<cword>")
let command = "%s/".word."/env[\'".word."\']/g"
execute command
endfunction
map cx :call <SID>surround()<CR>
This will surround every occurance of the word currently under the cursor.
If you wanted to specify what went before and after each instance you could use this:
function! s:surround()
let word = expand("<cword>")
let before = input("what should go before? ")
let after = input("what should go after? ")
let command = "%s/".word."/".before.word.after."/g"
execute command
endfunction
map cx :call <SID>surround()<CR>
If you only want to confirm each instance of the variable you could use this:
function! s:surround()
let word = expand("<cword>")
let before = input("what should go before? ")
let after = input("what should go after? ")
let command = "%s/".word."/".before.word.after."/c"
execute command
endfunction
map cx :call <SID>surround()<CR>
I figured out one way to do what I need. Use q{0-9a-zA-Z"} to record key strokes into a buffer. Position the cursor at the begging of the variable name, then cw and type env['']. Next move the cursor back one space to the last quote and paste the buffer filled from the cw command with P. Finally, reuse the recording with #{0-9a-z".=*} for each variable.

Resources