Vim: Call an ex command (set) from function? - vim

Drawing a blank on this, and google was not helpful.
Want to make a function like this:
function JakPaste()
let tmp = :set paste?
if tmp == "paste"
set nopaste
else
set paste
endif
endfunction
map <F2> :call JakPaste()<CR>
However this does not work. I have isolated the broken line:
function JakPaste()
let tmp = set paste?
endfunction
map <F2> :call JakPaste()<CR>
Pressing F2 results in this error:
Error detected while processing function JakPaste:
line 1:
E121: Undefined variable: set
E15: Invalid expression: set paste?
Hit ENTER or type command to continue
How should I call an ex command (set) from a vim function?
This seems somewhat relevant however I still don't get it.

The reason this doesn't work is that you're trying to run a command in an expression - those are different things. The ? construct you used just causes vim to echo the value of the option; this isn't the same as a function returning the value. To be clear: the problem isn't that you're calling an ex command from a function - every other line of the function is an ex command - it's that you're calling the ex command in an expression.
But that's not the right way to carry out the task you're attempting here. Here's the shortest way, thanks to rampion's comment:
set paste!
Now, if you ever need something smarter than just inverting a boolean, you can use & to turn an option name into a usable variable. Here are two ways to use that:
" still a function, good for taking extra action (here, printing notification)"
function TogglePaste()
if (&paste)
set nopaste
echo "paste off"
else
set paste
echo "paste on"
endif
endfunction
" just set the variable directly"
let &paste = !&paste

Related

Vim: Close a fold on a specific line

I want to programatically close a fold in vim based on whether or not it matches a regular expression. I've defined a function in my vimrc to do so:
" Support python 2 and 3
if has('python')
command! -nargs=1 Python2or3 python <args>
elseif has('python3')
command! -nargs=1 Python2or3 python3 <args>
else
echo "Error: Requires Vim compiled with +python or +python3"
finish
endif
" Define function
func! FoldCopyrightHeader()
Python2or3 << EOF
import re
import vim
# Look at the first 30 lines
header = '\n'.join(vim.current.buffer[0:30])
pattern = 'Copyright .* by .* THE POSSIBILITY OF SUCH DAMAGE'
match = re.search(pattern, header, flags=re.MULTILINE | re.DOTALL)
if match:
# Find the line number of the block to fold
lineno = header[:match.start()].count('\n')
# Remember the current position
row, col = vim.current.window.cursor
# move cursor to the fold block
vim.command('cal cursor({},{})'.format(lineno, 0))
# close the fold
vim.command('call feedkeys("zc")')
# move back to the original position
vim.command('cal cursor({},{})'.format(row, col))
EOF
endfunc
The idea is to search for the pattern, if it exists, then move to where the pattern is, enter the key commands zc to close the fold, and then move back to your original position.
However, this doesn't quite work. If I call this function via :call FoldCopyrightHeader(), then it closes whatever fold the cursor is currently on, and does nothing else.
My guess is that the feedkeys command is asynchronous vim.command('call feedkeys("zc")') and is happening before/after the move cursor commands get executed.
Is there anything I can do to prevent this?
And I solved it as I was typing the question.
Using vim.command(':foldclose') instead of vim.command('call feedkeys("zc")') seems to do the trick.

vimscript call vs. execute

In vimscript, what is the difference between call and execute? In what scenarios / use cases should I use one vs the other?
(Disclaimer, I am aware of the extensive online help available within vim - I am seeking a concise answer to this specific question).
:call: Call a function.
:exec: Executes a string as an Ex command.
It has the similar meaning of eval(in javascript, python, etc)
For example:
function! Hello()
echo "hello, world"
endfunction
call Hello()
exec "call Hello()"
From the experience of writing my own plugins and reading the code of others:
:call is for calling functions, e.g.:
function! s:foo(id)
execute 'buffer' a:id
endfunction
let target_id = 1
call foo(target_id)
:execute is used for two things:
Construct a string and evaluate it. This is often used to pass arguments to commands:
execute 'source' fnameescape('l:path')
Evaluate the return value of a function (arguably the same):
function! s:bar(id)
return 'buffer ' . a:id
endfunction
let target_id = 1
execute s:bar(target_id)
Short answer
You may see call as first evaluate the expression and then discard the result. So only the side effects are useful.
Long answer
Define:
function! Foo()
echo 'echoed'
return 'returned'
endfunction
Call:
:call Foo()
Output:
echoed
Execute:
:execute Foo()
Output:
echoed
EXXX: Not an editor command: returned
Execute:
:silent let foo = Foo()
:echo foo
Output:
returned
See Switch to last-active tab in VIM
for example
:exe "tabn ".g:lasttab
Where
g:lasttab is a global variable to store the current tab number
and that number is concatenated with "tabnext" to switch e.g to tab number 3
(If g:lasttab e.g. contains '3' for example)
That whole string >"tabn ".g:lasttab<
is evaluated and executed by VIM's exec command.
HTH?

Variable Types in Vim Functions

I want to conveniently remove an accidentally placed tab while using vim. The solution that jumped out to me is making an insert-mode mapping to the following vim function:
function DeleteTab()
redir => l:numSpaces "captures output of set
set tabstop?
redir END
"Strip off non-numerical output of 'set tabstop?'
let l:numSpaces = substitute(l:numSpaces, "tabstop=", "", "")
let l:numSpaces = substitute(l:numSpaces, " ", "", "g")
"all echom lines are for debugging purposes
echom "1"
if l:numSpaces > 0
echom "2"
while 1:numSpaces > 0
execute "normal i<bs>"
let l:numSpaces = l:numSpaces - 1
endwhile
endfunction
In addition to not doing what I intended, the result of calling this function is "1" in my messages, but not "2". This means that l:numSpaces is not being interpreted as a number. How do I do the equivalent of casting in vimscript. Also, am I missing a more easy approach?
Instead of doing the redir just use &tabstop the ampersand gets the value and places it in the variable.
let l:numSpaces = &tabstop
The next problem you have is with this line
while 1:numSpaces > 0
You wrote a 1 (one) instead of l (lowercase L)
So the fixed function looks something like this.
function! DeleteTab()
let l:numSpaces = &tabstop
echom "1"
if l:numSpaces > 0
echom "2"
endif
while l:numSpaces > 0
execute "normal i<bs>"
let l:numSpaces = l:numSpaces - 1
endwhile
endfunction
Also this function is kinda pointless. I believe the behavior you want should be achieved if you set the following (or to what ever value you want)
set tabstop=4
set softtabstop=4
set shiftwidth=4
Hitting the backspace key should go back a full tab if you insert an accidental tab.
If you want to access the value of an option in vimscript, you can use the syntax &option (see :help expr-option). That simplifies the first half of your function to
let numSpaces = &tabstop
As far as undoing an accidental tab, all that should require is pressing Backspace, unless you aren't inserting tab characters.
If you mean you want to "remove a level of indentation" instead of "remove a tab", then you should use the builtin command for that, pressing Ctrl+d in insert mode. Similarly, you can use Ctrl+t to add a level of indentation to the current line. Both of these work regardless of where your cursor is in the current line, unlike trying to manage the indentation manually with Backspace, as well as doing the Right Thing based on your 'shiftwidth', 'expandtab', and 'tabstop' settings.

How to clear output of function call in VIM?

I know my question title is not explanatory enough so let me try to explain.
I created a vim function that displays my current battery state. My function is as follows:
function! BatteryStatus()
let l:status = system("~/battery_status.sh")
echo join(split(l:status))
endfunction
I have mapped the above function as nnoremap <F3> :call BatteryStatus()<cr>. Now, when I press F3 it displays my battery status as Discharging, 56%, 05:01:42 remaining which is my required output but my question is how do I make the above output disappear.
Currently what happens is after function call it continuously displays the output and I have to manually use :echo to clear the command window(:).
So, what necessary changes are to be made in my function so that I can achieve toggle like behaviour.
battery_status.sh
acpi | awk -F ": " '{print $2}'
PS: This is part of a learning exercise. So, please don't suggest alternative vim scripts.
Simplistic straightforward way to toggling the output:
let s:battery_status_output_flag = "show"
function! BatteryStatus()
if s:battery_status_output_flag == "show"
let l:status = system("~/battery_status.sh")
echo join(split(l:status))
let s:battery_status_output_flag = "clear"
else
echo ""
let s:battery_status_output_flag = "show"
endif
endfunction
Note s: prefix, see :help script-variable
You can define a autocmd:
:au CursorHold * redraw!
Vim redraws itself 4 sec (set by updatetime option) after it's idle.
vim
function! pseudocl#render#clear()
echon "\r\r"
echon ''
endfunction
You can just Ctrl+ L to clear the message on the status
line.

how to remove file name from VIM dictionary menu?

I've installed pydiction dictionary in vim so that I'd be able to get a list of python commands when I press tab after partially typed command. Everything is working fine, except every time the menu shows up, there is a file name besides the each command in the list. How do I remove that filename from the menu?
plz, take a look at the picture: http://www.uzbozor.com/uploads/vim.png
(copy and paste the link if clicking doesn't work)
Thanks
I haven't managed to solve this very elegantly, but there's a workaround by writing a custom completion function that simply greps the dictionary file for matches:
function! MyCompleteFunction( findstart, base )
if a:findstart
let line = getline('.')
let start = col('.') - 1
while start > 0 && line[start - 1] =~ '[A-Za-z_]'
let start -= 1
endwhile
return start
else
silent call DictGrep( a:base, 'path\to\dictionary\file' )
let matches = []
for thismatch in getqflist()
call add(matches, thismatch.text)
endfor
return matches
endif
endfunction
Note that I have defined a function DictGrep() that actually performs the vimgrep. This is so I can call it silently and not be troubled by error messages:
function! DictGrep( leader, file )
try
exe "vimgrep /^" . a:leader . ".*/j " . a:file
catch /.*/
echo "no matches"
endtry
endfunction
Then simply define set the completefunc:
setlocal completefunc=MyCompleteFunction()
and then use for insert-mode completion (which could be mapped to replace your current dictionary completion binding).
The vimgrep could be quite a slow operation, but I haven't noticed any problems unless there are hundreds of matches in the dictionary file.
Hope this helps.

Resources