Assign variable to spelllang in vim script - vim

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.

Related

Resize vim split based on number of visible lines

I am trying to write a function in my vimrc which lets me create a small split which looks as though it is inside the current file but is, in fact, a different file.
To do this cleanly, I want this split to appear immediately below the cursor which means I need to resize my splits appropriately. Unfortunately, I cannot find a way to measure the number of visible lines between my cursor and the top of the window. In files where I have folded code, the approach of using line(".") - line("w0") ("." gives line number of cursor; "w0" gives line number of topmost visible line) does not work since this calculation includes the hidden lines inside the folds. Does anybody know how this could be achieved?
Images for reference before and after inserting the split:
line(".") - line("w0")
gives you the number of physical lines between the top of the window and the current line. As you figured out, this method doesn't account for folds.
You can count the number of folds within a range with something like this:
function! CountFolds(top_line, bottom_line)
let folded_lines = []
for line_nr in range(a:top_line, a:bottom_line)
if foldlevel(line_nr) == 0
call add(folded_lines, 0)
else
call add(folded_lines, 1)
endif
endfor
return count(uniq(folded_lines), 1)
endfunction
and then remove it from your initial line count:
let top_line = line("w0")
let bottom_line = line(".")
let physical_lines = bottom_line - top_line
let screen_lines = physical_lines - CountFolds(top_line, bottom_line)
Reference:
:help range()
:help foldlevel()
:help add()
:help uniq()
:help count()
Note that you may need your script to account for soft-wrapped lines as well, a topic that is well worth another question.

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

First vim function - invalid function

I am trying to write my first vim function (toggling hidden chars).
This is what I have so far
set nolist
set listchars=space:_,tab:▸\ ,eol:¬
nnoremap <leader>c :call showHiddenChars()<cr> "<---Calling function here
let g:showhiddenChars_is_visible = 0
function! showHiddenChars()
if g:showhiddenChars_is_visible
set nolist
let g:showhiddenChars_is_visible = 0
else
set list
let g:showhiddenChars_is_visible = 1
endif
endfunction
However when I run it I get the error invalid function showHiddenChars
Any suggestions. This is my first vim function.
User function names must begin with an uppercase letter (unless they are script functions with the s: prefix, or autoload functions using the foo#bar() syntax).
Change your function name from showHiddenChars to ShowHiddenChars and it should work as expected.

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

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

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