Effective VimScript
source link: https://www.tuicool.com/articles/emuAvuF
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
The VimScript language is rather idiosyncratic, and because few people – if anyone – uses it to make a living, not many people learn it in-depth.
This document gives tips for writing VimScript code.
General
-
Use
is
instead of==
. Like PHP and JavaScript==
will coerce types ('0' == 0
). Theis
operator is Vim’s version of===
.For string comparison use
is#
oris?
, as the behaviour ofis
and==
are affected by theignorecase
setting. In general, it’s a good practice to just always useis#
instead of==
; it will work fine for other types as well. -
Use explicit variable scope .
let foo = 1
can refer tol:foo
,s:foo
, org:foo
. You should use explicit scope for the same reason as you should always usevar
orlet
in JavaScript. abort
functions . Without it Vim will keep executing code after an error, which rarely what you want. Use theabort
keyword to abort function execution on error (e.g.fun! MyFun() abort
). You can still usetry .. catch
to recover from errors.-
Use
try .. finally
A common pattern is something like:let l:old_setting = &setting [.. do work ..] let &setting = l:old_setting
But the resetting of
&setting
will fail if the code returns in the “do work” path, either because of areturn
or because of an error.The
finally
block will always get executed:try let l:old_setting = &setting [.. do work ..] finally let &setting = l:old_setting endtry
-
Prefer
printf()
over string concatenation ; e.g.echo printf('x: %s', [42])
will work, whereasecho 'x: ' . [42]
will give you a useless error. -
Use
execute()
rather thanredir
. It’s a lot less clumsy. Many older Stack Overflow answers and the like recommend:redir
, butexecute()
is available since Vim 7.4.2008 (July 2016).
Plugins
-
Scope to filetype. Quite a few plugins that work on only a single filetype exist globally. There is no reason to have a
:PythonFrob
when editing Ruby files.Just moving
plugin/myplugin.vim
toftplugin/python.vim
is often all that’s needed. Alternatively, use aFileType
autocmd. -
Use autoload . VimScript in the
plugin
andftplugin
directory will always be loaded on startup. Code in theautoload
directory will be loaded on first use.This also allows some rudimentary modularisation instead of putting everything in the global namespace (e.g.
plugin#foo#fun()
instead ofPluginFooFun()
). See:help autoload
.Using
plugin
might be okay if your plugin is very small though. -
Make functions script-local when possible; not everything needs to be exported. Consider using
s:name()
when it’s only used in the current script context and isn’t useful for your plugin users.Note: you can use
<SID>
in mappings, e.g.nnoremap x <SID>fun()<CR>
. -
There are a few different approaches to plugin settings , the “best way” depends on the plugin and personal preference:
-
Define the default on use:
call do_something(get(g:, 'plugin_setting', 'default value'))
Advantage: it’s the simplest approach.
Downside: you’ll have to define the default multiple times if used more than once. Many plugins don’t, so it can still be a good option for small plugins.
Another downside is that users can’t inspect current value. Want to know what
g:plugin_setting
is? You’ll have to read the docs and hope they’re correct. -
Define the defaults on startup:
let g:plugin_setting = get(g:, 'plugin_setting', 'default value')
Advantage: one location for the default values, allows users to inspect values.
Downsides: may add a lot of global variables.
In general, I would recommend the first approach for small plugins and the second one for larger ones.
Other considerations:
:help g:plugin_<Tab>
-
-
Plugin mappings: the right strategy depends on the plugin and size. The two most important things are to not override people’s existing mappings and allowing people to override your default mappings.
To ensure you’re not overriding existing mappings you can use
mapcheck()
orhasmapto()
.mapcheck()
will check if key is mapped to anything, so you won’t override existing mappings::echo mapcheck('<F1>', 'n') :set wrap!<CR> :echo mapcheck('<F2>', 'n') (empty string) if mapcheck('<F1>', 'n') is# '' nnoremap <F1> :call myplugin#action()<CR> endif
And
hasmapto()
checks if the given commands appears in the right-hand-side of a mapping, allowing you to map an action only if the user didn’t map it to something else::echo hasmapto(':set wrap!<CR>', 'n') 1 :echo hasmapto(':set cursorline!<CR>', 'n') 0 if !hasmapto('myplugin#action', 'n') nnoremap <Leader>d :call myplugin#action()<CR> endif
In general use
hasmapto()
if mappings are essential to your plugin andmapcheck()
if they’re just bonus features.Make sure your plugin has a way to disable mappings altogether with a setting (
g:plugin_nomap
) unless they’re an essential part of your plugin.You can make it easy for people to map actions by abstracting them with
<Plug>
mappings; first create a mapping to<Plug>(name)
:nnoremap <Plug>(myplugin-action) :call myplugin#action('arg', 2)<CR>
And users can map that without worrying about the internal details of the mapping:
nmap <Leader>d <Plug>(myplugin-action)
See
:help using-<Plug>
.Adding
g:plugin_map
variable to control which action gets mapped to which key can be helpful for some plugins, for example when several mappings all start with the same prefix; changingg:plugin_map_prefix = ';'
is easier than remapping 7 actions.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK