How do you execute a global command in a vimscript function? - vim

How do I write a function that executes the :NERDTree commands? Note: I am using neovim but i'm assuming the vimscript syntax is the same
here is my code
nmap <expr> <C-n> Toggle()
func Toggle()
if g:open == 0
let g:open += 1
execute g:NERDTreeCWD
else
let g:open -= 1
execute g:NERDTreeClose
endfunc

The NERDTree commands are custom Ex commands, so you invoke them (interactively) via :NERDTreeCWD followed by Enter. In a Vimscript, you can drop the : prefix.
Maybe part of the confusion (also seen in the comments) arises from the fact that the NERDTree commands are implemented by global functions with the same name:
:verbose command NERDTreeCWD
Name Args Address Complete Definition
| NERDTreeCWD 0 call NERDTreeCWD()
So you could also bypass the custom function and call NERDTreeCWD() directly, but this would make you depend on implementation details of the plugin, and is therefore discouraged.
Implementing NERDTree toggling
Are you aware that the plugin already has a :NERDTreeToggle command?
Also, you don't need to define your own flag variable (g:open) - just reuse the one from the plugin (exposed via the g:NERDTree.IsOpen() function). Yes, this makes you depend on plugin details (but this looks like a public API, not internal implementation, so it should be far more stable) - it's still better than trying to reinvent the wheel. (Your global flag would have problems with multiple tab pages - each could have a NERDTree active or not.)

Related

Creating a python comment string with only VIM commands

I have a vim function:
function! Pycom(word)
let a:inserted_word = ' ' . a:word . ' '
let a:word_width = strlen(a:inserted_word)
let a:length_before = (118 - a:word_width) / 2
let a:hashes_before = repeat('#', a:length_before)
let a:hashes_after = repeat('#', 118 - (a:word_width + a:length_before))
let a:hash_line = repeat('#', 118)
let a:word_line = '# '. a:hashes_before . a:inserted_word . a:hashes_after
:put =toupper(a:word_line)
endfunction
noremap <leader>pc :call Pycom('')<Left><Left>
that creates Python comments. The output is like so:
# ########################################### Hello World ############################################
How can I create a key mapping to place in my vimrc to create my comment string with only Vim commands? I need to do this because I use PyCharm and in Vim emulation mode it does not allow calling functions.
If just :function were missing, you could inline the individual commands into a long, unsightly command sequence. But it's unlikely that a Vim emulation will just omit functions; the problem is that Vimscript itself is tightly bound to Vim itself, and mostly just specified by the behavior by its sole implementation inside Vim. So I'd guess that strlen(), repeat(), and even :put ={expression} won't work in PyCharm, neither.
Most Vim emulations just offer basic :map commands. Without knowning anything about PyCharm, the following general approaches can be tried:
If the emulation offers the :! (or :{range}!) command to invoke external commands, you can implement the functionality in an external command, implemented in whatever programming language you prefer. (If the emulation doesn't offer the :{range}! command, you'd have to :write the buffer first and pass the filename and current location somehow to the external command.)
Some emulations also offer a non-standard command to invoke their own core editor functions (so you might be able to implement it with that) or even arbitrary custom code; for example, for a NodeJS-based editor, that could be JavaScript code.
In general, Vim emulations are quite limited and can only emulate basic vi features (though I've also seen custom re-implementations of some popular Vim plugins, like surround.vim).
Alternative
To get more of Vim's special capabilities into your IDE, use both concurrently; it is very easy to set up a external tool in your IDE that launches Vim with the current file (and position). Automatic reloading of changes (in Vim via :set autoread) allows you to edit source code in both concurrently.

How to use Ctrl-R_= with :execute

I'm trying to get an expression on a variable expanded on a :execute command. I've guessed this could be achieved by using Ctrl-R_=, but it is not clear how the special characters should be inserted. None of the following worked:
exec 'echo ^R=1+1^M'
exec "echo <ctrl-r>=1+1<cr>"
The purpose is set a global variable used as an option in a plugin to select how to show the results. It is used on an :execute command, and works fine for 'vsplit' or 'split'. But the choice between vertical or horizontal split sometimes depends on the window layout. In order to do this without adding extra complexity to the plugin I've thought of something like the following:
let var = '<ctrl-r>=(winwidth(0) > 160 ? "vsplit" : "split")<cr>'
Edit
Currently the plugin has something like the following:
exec 'pluginCommands' . g:splitCmd . ' morePluginCommands'
The g:splitCmd is a plugin option, which works for when set with "split", "vsplit", "tabe", etc. My intent is to change this fixed behavior, setting g:splitCmd in such a way that it represents an expression on the execute above, instead of a fixed string.
Now that I'm understanding the issue better, I think a dynamic re-evaluation inside the config var is impossible if the variable's value is inserted in an :execute g:pluginconf . 'split' statement. To achieve that, you'd need another nested :execute, or switch to command-line mode via :normal! :...; both approaches will fail on the appended . 'split', because you can't add quoting around that.
The way I would solve this is by prepending a :help :map-expr to the plugin's mapping; change
:nmap <Leader>x <Plug>Plugin
to
:nnoremap <expr> <SID>(PluginInterceptor) PluginInterceptor()
:nmap <Leader>x <SID>(PluginInterceptor)<Plug>Plugin
Now, you're get called before the mapping is executed, and can influence the plugin config there:
fun! PluginInterceptor()
let g:plugconf = winwidth(0) > 160 ? "vsplit" : "split"
return ''
endfun
If modifying the plugin mapping is for some reason difficult, you could also trigger the PluginInterceptor() function via :autocmd; for this particular case e.g. on WinEnter events.
With :execute, you already have a way to evaluate expressions; just move them out of the static strings to get them evaluated:
exec 'echo ' . 1+1
The <C-R> only works in command-line mode (and insert mode), so only within a :cnoremap ... command (or with :normal). (And even there, you can use :map <expr>, which often gives you simpler code.)
I think that what you want is simply
:let var = (winwidth(0) > 160) ? "vsplit" : "split"
It seems to me like
exec 'pluginCommands' . eval(g:splitCmd) . ' morePluginCommands'
should work just fine, and is a simple solution to this problem.

How to tell when a vim script is being sourced

How can I tell if my script is being sourced on or after startup?
For example, I want to write a function that responds to the ttymouse setting, say CheckMouseSetting(). This setting, however, is oddly enough loaded after startup scripts are loaded -- I'm not sure why. I could use a VimEnter autocommand, but this won't activate if the user is simply sourcing this file after startup. I could have both, ie:
call CheckMouseSetting()
au * VimEnter call CheckMouseSetting()
But this is not ideal as it may produce unwanted error messages -- so, hence my question.
I would solve this with a guard variable, like the multiple inclusion guard at the start of plugin files.
function! myplugin#CheckMouseSetting()
if exists('s:hasBeenChecked')
return
endif
let s:hasBeenChecked = 1
...
If you define this function in an autoload script, you can invoke it from the VimEnter event, and alternatively instruct users who want to have this manually to call the function, which is easier than :source / :runtime, because it avoids the path issues.
Clarification: you want the function to know whether it is being sourced at startup or later, right?
I think you have a little blind spot: the simplest solution is to tell the function:
:call CheckMouseSetting('myscript')
:au * VimEnter call CheckMouseSetting('VimEnter')
In response you your comment: if you want to tell when the file is sourced (during startup or interactively) then you can add a script-local variable:
:let s:source_count = exists('s:source_count') ? s:source_count + 1 : 1
:call CheckMouseSetting('myscript', s:source_count)
If you really want a more "automatic" way to tell, then #romainl's comment is on target. (I think there was a time when I was the only one besides Bram who had ever read that section of the help.)

Can you have different localleaders for different Vim plugins?

I started using a plugin that conflicts with my existing maps, but instead of remapping all of it's maps, I just want to add a prefix. I thought I'd be able to do this with LocalLeader.
Vimdoc says:
<LocalLeader> is just like <Leader>, except that it uses
"maplocalleader" instead of "mapleader". <LocalLeader> is to be used
for mappings which are local to a buffer.
It seems that the only way to set localleader is to set a global variable (the docs don't mention this, but b:maplocalleader didn't work):
let maplocalleader = '\\'
And I don't see how I'd cleanly unset that variable (an autocmd that clears it after plugins are setup!?)
Is there a way to do this? Or is LocalLeader only to give one global prefix and one filetype-specific prefix?
Your last hunch is correct. If the plugin uses <Leader> (and it should unless it's a filetype plugin), there's no use in messing with maplocalleader.
Remapping is canonically done via <Plug> mappings, which the plugin hopefully offers. Some plugins do define a lot of similar mappings, some of those define a g:pluginname_mappingprefix (or so) variable to save you from having to remap all mappings individually. If your plugin doesn't, maybe write a suggestion / patch to the plugin author.
While #IngoKarkat solution is a prefered one, there is a hack which lets you do what you want: the SourcePre event:
autocmd SourcePre * :let maplocalleader='\\'
autocmd SourcePre plugin-name.vim :let maplocalleader='_'
. This works for <Leader> as well. There are lots of cases when this won’t work though. You can as well use SourceCmd for this job, using something like
function s:Source(newmll)
try
let oldmll=g:maplocalleader
let g:maplocalleader=a:newmll
source <amatch>
finally
let g:maplocalleader=oldmll
endtry
endfunction
let maplocalleader='\\'
autocmd SourceCmd plugin-name.vim :call s:Source('_')
in SourceCmd is the only way I see to restore maplocalleader after plugin was sourced, but SourceCmd event here won’t be launched for any file sourced inside plugin-name.vim. For some poorly written plugins (I mean, those that emit errors while sources) putting :source inside a :try block will break execution at the point where error occurs. Should not happen most of time though. You may also want to use */ftplugin/plugin-name.vim as a pattern instead of plugin-name.vim.

Run terminal command from VIM?

I wish to write a function which I can add to my .vimrc file that will call a terminal command, and then bind it to <leader>u.
I cant seem to get it to work tho. I believe that I can use the system() function, but there is very little documentation available and I cant seem to get it to work.
The terminal command in question is 'git push origin master'.
I know that there are plugins available for git but I am not looking for any of these, just a simple function to bind a terminal command to a key combination.
function gitPush()
system("git push origin master")
endfunction
:nmap <leader>u :call gitPush()
I know this is waaay out, but vim doesnt seem to want to make documentation very available.
Ty
function GitPush()
!git push origin master
endfunction
Is the way to run a command in a subshell.
EDIT: User defined functions must begin with a capital letter too ;)
Why do you use call to call your own function and fail to use it for builtin? It is one of three errors, other was mentioned by #Richo: user-defined function must either start with a capital or with b:, w:, t: (note that neither of these are local functions), g:, s: (only inside scripts, and you will have to replace s: with <SID> in mappings), even \w: (for example, function _:foo() works) or with {filename_without_extension}# (if filename matches \w+.vim). If it is anonymous function:
let dict={}
function dict["foo"]()
endfunction
function dict.bar()
endfunction
it also does not require to start with a capital. So the correct solution is:
function g:gitPush()
call system("git push origin master")
endfunction
nnoremap <leader>u :call g:gitPush()<CR>
Third error is omitting <CR>. I changed nmap to nnoremap because it is good to use nore where possible. Having : at the start of the command does not hurt and is not an error, but I just do not write it: it is required in normal mode mappings to start command mode, but not inside scripts.

Resources