usr_51.txt ForVim version 9.2. Last change: 2026 Feb 14 VIM USER MANUALbyBramMoolenaar Write pluginsPlugins can be used to define settings fora specific type of file,syntaxhighlighting and many other things. This chapter explains how to write themost common Vim plugins.51.1 Writinga genericplugin51.2 Writingafiletypeplugin51.3 Writinga compilerplugin51.4 Distributing Vim scripts Next chapter:usr_52.txt Write large plugins Previous chapter:usr_50.txt Advanced VimscriptwritingTable of contents:usr_toc.txt==============================================================================51.1 Writinga genericpluginwrite-pluginYou can writea Vimscript in sucha way that many people can use it. Thisiscalleda plugin. Vim users can drop yourscript in theirplugin directory anduse its features right awayadd-plugin.There are actually two types of plugins: global plugins: For all types of files.filetype plugins: Only for files ofa specific type.In thissection the first typeis explained. Most items are also relevant forwritingfiletype plugins. The specifics forfiletype plugins are in the nextsectionwrite-filetype-plugin.We will useVim9syntax here, the recommended way to write new plugins.Make sure the file starts with thevim9script command.NAMEFirst of all youmust choosea name for your plugin. The features providedby theplugin should be clear from its name. Andit should be unlikely thatsomeone else writesaplugin with the same name but which does somethingdifferent.Ascript that corrects typing mistakes could be called "typecorrect.vim". Wewill useit hereas an example.For theplugin to work for everybody,it should followa few guidelines. Thiswill be explained step-by-step. The complete examplepluginisat the end.BODYLet's start with the body of the plugin, the lines thatdo the actual work: 12iabbrev teh the 13iabbrev otehr other 14iabbrev wnat want 15iabbrev synchronisation 16\ synchronizationThe actuallist should be much longer, of course.The line numbers have only been added to explaina few things, don'tput themin yourplugin file!FIRST LINE 1vim9script noclearYou need to usevim9scriptas the very first command. Bestis toputit inthe very first line.Thescript we arewriting will haveafinish command to bail out whenitisloadeda second time. To avoid that the items defined in thescript are lostthe "noclear" argumentis used. More info about thisatvim9-reload.HEADERYou will probably add new corrections to theplugin and soon have severalversions lying around. And when distributing this file, people will want toknow who wrote this wonderfulplugin and where they can send remarks.Therefore,puta headerat the top of your plugin: 2# Vim global plugin for correcting typing mistakes 3# Last Change:2021 Dec 30 4# Maintainer:Bram Moolenaar <Bram@vim.org>Aboutcopyright and licensing: Since plugins are very useful and it's hardlyworth restricting their distribution, please consider making yourplugineither public domain or use the Vimlicense.A shortnote about this nearthe top of theplugin should be sufficient. Example: 5# License:This file is placed in the public domain.NOT LOADINGItis possible thata user doesn't always want to load this plugin. Or thesystem administrator has droppedit in the system-wideplugin directory, butauser has their ownplugin they want to use. Then the usermust havea chanceto disable loading this specific plugin. These lines will makeit possible: 7if exists("g:loaded_typecorrect") 8 finish 9endif 10g:loaded_typecorrect = 1This also avoids that when thescriptis loaded twiceit would pointlesslyredefinefunctions and cause trouble forautocommands that are added twice.The nameis recommended to start with "g:loaded_" and then the file name ofthe plugin, literally. The "g:"is prepended to make the variable global, sothat other places can check whether its functionalityis available. Without"g:"it would be local to the script.Usingfinish stops Vim from reading the rest of the file, it's much quickerthan using if-endif around the whole file, since Vim would still need to parsethe commands to find theendif.MAPPINGNow let's make theplugin more interesting: We will addamapping that addsacorrection for theword under the cursor. We could just picka key sequencefor this mapping, but the user might already useit for something else. Toallow the user to define which keysamapping inaplugin uses, the<Leader>item can be used: 20 map <unique> <Leader>a <Plug>TypecorrAdd;The "<Plug>TypecorrAdd;" thing willdo the work, more about that further on.The user can set the "g:mapleader" variable to the key sequence that they wantplugin mappings to start with. Thus if the user has done:g:mapleader = "_"themapping will define "_a". If the user didn'tdo this, the default valuewill be used, whichisa backslash. Thena map for "\a" will be defined.Note that<unique>is used, this will cause an error message if themappingalready happened to exist.:map-<unique>But what if the user wants to define their own key sequence? We can allowthat with this mechanism: 19if !hasmapto('<Plug>TypecorrAdd;') 20 map <unique> <Leader>a <Plug>TypecorrAdd; 21endifThis checks ifamapping to "<Plug>TypecorrAdd;" already exists, and onlydefines themapping from "<Leader>a" ifit doesn't. The user then hasachance of putting this in theirvimrc file:map ,c <Plug>TypecorrAdd;Then the mapped key sequence will be ",c" instead of "_a" or "\a".PIECESIfascript gets longer, you often want to break up the work in pieces. Youcan usefunctions or mappings for this. But you don't want thesefunctionsand mappings to interfere with the ones from other scripts. For example, youcould definea function Add(), but anotherscript could try to define the samefunction. To avoid this, we define the function local to the script.Fortunately, inVim9script thisis the default. Ina legacyscript youwould need to prefix the name with "s:".We will definea function that addsa new typing correction: 28def Add(from: string, correct: bool) 29 var to = input($"type the correction for {from}: ") 30 exe $":iabbrev {from} {to}" ... 34enddefNow we can call the function Add() from within this script. If anotherscript also defines Add(),it will be local to thatscript and can onlybe called from that script. There can also bea global g:Add() function,whichis again another function.<SID> can be used with mappings. It generatesascript ID, which identifiesthe current script. In our typing correctionplugin we useit like this: 22noremap <unique> <script> <Plug>TypecorrAdd; <SID>Add ... 26noremap <SID>Add :call <SID>Add(expand("<cword>"), true)<CR>Thus whena user types "\a", this sequenceis invoked:\a -> <Plug>TypecorrAdd; -> <SID>Add -> :call <SID>Add(...)If anotherscript also maps<SID>Add,it will get anotherscript ID andthus define another mapping.Note that instead of Add() we use<SID>Add() here. Thatis because themappingis typed by the user, thus outside of thescript context. The<SID>is translated to thescript ID, so that Vim knows in whichscript to look forthe Add() function.Thisisa bit complicated, but it's required for theplugin to work togetherwith other plugins. The basic ruleis that you use<SID>Add() in mappings andAdd() in other places (thescript itself, autocommands, user commands).We can also adda menu entry todo the sameas the mapping: 24noremenu <script> Plugin.Add\ Correction <SID>AddThe "Plugin" menuis recommended for adding menu items for plugins. In thiscase only one itemis used. When adding more items, creatinga submenuisrecommended. For example, "Plugin.CVS" could be used foraplugin that offersCVS operations "Plugin.CVS.checkin", "Plugin.CVS.checkout", etc.Note that in line 28 ":noremap"is used to avoid that any other mappings causetrouble. Someone may have remapped ":call", for example. In line 24 we alsouse ":noremap", but wedo want "<SID>Add" to be remapped. Thisis why"<script>"is used here. This only allows mappings which are local to thescript.:map-<script> The sameis done in line 26 for ":noremenu".:menu-<script><SID> AND<Plug>using-<Plug>Both<SID> and<Plug> are used to avoid that mappings of typed keys interferewith mappings that are only to be used from other mappings.Note thedifference between using<SID> and<Plug>:<Plug>is visible outside of the script. Itis used for mappings which theuser might want to mapa key sequence to.<Plug>isa special codethata typed key will never produce.To makeit very unlikely that other plugins use the same sequence ofcharacters, use this structure:<Plug> scriptname mapnameIn our example the scriptnameis "Typecorr" and the mapnameis "Add".We adda semicolonas the terminator. This results in"<Plug>TypecorrAdd;". Only the first character of scriptname andmapnameis uppercase, so that we can see where mapname starts.<SID>is thescript ID,aunique identifier fora script.Internally Vim translates<SID> to "<SNR>123_", where "123" can be anynumber. Thusa function "<SID>Add()" will havea name "<SNR>11_Add()"in one script, and "<SNR>22_Add()" in another. You can see this ifyou use the ":function" command to getalist of functions. Thetranslation of<SID> in mappingsis exactly the same, that's how youcan callascript-local function froma mapping.USER COMMANDNow let's adda user command to adda correction: 36if !exists(":Correct") 37 command -nargs=1 Correct :call Add(<q-args>, false) 38endifThe user commandis defined only if no command with the same name alreadyexists. Otherwise we would get an error here. Overriding the existing usercommand with ":command!"is nota good idea, this would probably make the userwonder why the command they defined themselves doesn't work.:commandIfit did happen you can find out who to blame with:verbose command CorrectSCRIPT VARIABLESWhena variable starts with "s:"itisascript variable. It can only be usedinsidea script. Outside thescript it's not visible. This avoids troublewith using the same variable name in different scripts. Thevariables will bekeptas longas Vimis running. And the samevariables are used when sourcingthe samescript again.s:varThe nice thing aboutVim9scriptis thatvariables are local to thescriptby default. You can prepend "s:" if you like, but youdo not need to. Andfunctions in thescript can also use thescriptvariables withouta prefix(theymust be declared before the function for this to work).Script-localvariables can also be used in functions,autocommands and usercommands that are defined in the script. Thus they are the perfect way toshare information between parts of your plugin, withoutit leaking out. Inour example we can adda few lines tocount the number of corrections: 17var count = 4 ... 28def Add(from: string, correct: bool) ... 32 count += 1 33 echo "you now have " .. count .. " corrections" 34enddef"count"is declared and initialized to 4 in thescript itself. When laterthe Add() functionis called,it increments "count". It doesn't matter fromwhere the function was called, sinceit has been defined in the script,itwill use the localvariables from this script.THE RESULTHereis the resulting complete example: 1vim9script noclear 2# Vim global plugin for correcting typing mistakes 3# Last Change:2021 Dec 30 4# Maintainer:Bram Moolenaar <Bram@vim.org> 5# License:This file is placed in the public domain. 6 7if exists("g:loaded_typecorrect") 8 finish 9endif 10g:loaded_typecorrect = 1 11 12iabbrev teh the 13iabbrev otehr other 14iabbrev wnat want 15iabbrev synchronisation 16\ synchronization 17var count = 4 18 19if !hasmapto('<Plug>TypecorrAdd;') 20 map <unique> <Leader>a <Plug>TypecorrAdd; 21endif 22noremap <unique> <script> <Plug>TypecorrAdd; <SID>Add 23 24noremenu <script> Plugin.Add\ Correction <SID>Add 25 26noremap <SID>Add :call <SID>Add(expand("<cword>"), true)<CR> 27 28def Add(from: string, correct: bool) 29 var to = input("type the correction for " .. from .. ": ") 30 exe ":iabbrev " .. from .. " " .. to 31 if correct | exe "normal viws\<C-R>\" \b\e" | endif 32 count += 1 33 echo "you now have " .. count .. " corrections" 34enddef 35 36if !exists(":Correct") 37 command -nargs=1 Correct call Add(<q-args>, false) 38endifLine 31 wasn't explained yet. It applies the new correction to theword underthe cursor. The:normal commandis used to use the new abbreviation.Notethat mappings andabbreviations are expanded here, even though the functionwas called fromamapping defined with ":noremap".DOCUMENTATIONwrite-local-helpIt'sa good idea to also write some documentation for your plugin. Especiallywhen its behavior can be changed by the user. Seehelp-writing for thesyntax used by thehelp files andadd-local-help for how localhelp filesare installed.Hereisa simple example forapluginhelp file, called "typecorrect.txt": 1*typecorrect.txt*Plugin for correcting typing mistakes 2 3If you make typing mistakes, this plugin will have them corrected 4automatically. 5 6There are currently only a few corrections. Add your own if you like. 7 8Mappings: 9<Leader>a or <Plug>TypecorrAdd; 10Add a correction for the word under the cursor. 11 12Commands: 13:Correct {word} 14Add a correction for {word}. 15 16*typecorrect-settings* 17This plugin doesn't have any settings.The first lineis actually the only one for which the format matters. It willbe extracted from thehelp file to beput in the "LOCAL ADDITIONS:"section ofhelp.txtlocal-additions. The first "*"must be in the first column of thefirst line. After adding yourhelp filedo ":help" and check that the entriesline up nicely.You can add moretags inside ** in yourhelp file. But be careful not to useexistinghelp tags. You would probably use the name of yourplugin in most ofthem, like "typecorrect-settings" in the example.Using references to other parts of thehelp in ||is recommended. This makesiteasy for the user to find associated help.SUMMARYplugin-specialSummary of special things to use ina plugin:var nameVariable local to the script.<SID>Script-ID, used for mappings andfunctions local tothe script.hasmapto()Function to test if the user already definedamappingfor functionality thescript offers.<Leader>Value of "mapleader", which the user definesas thekeys thatplugin mappings start with.map<unique>Givea warning ifamapping already exists.noremap<script>Use only mappings local to the script, not globalmappings.exists(":Cmd")Check ifa user command already exists.==============================================================================51.2 Writingafiletypepluginwrite-filetype-pluginftpluginAfiletypepluginis likea global plugin, except thatit setsoptions anddefines mappings for the current buffer only. Seeadd-filetype-plugin forhow this type ofpluginis used.First read thesection on global plugins above51.1. All thatis said therealso applies tofiletype plugins. There area few extras, which are explainedhere. The essential thingis thatafiletypeplugin should only have aneffect on the current buffer.DISABLINGIf you arewritingafiletypeplugin to be used by many people, they needachance to disable loading it. Put thisat the top of the plugin:# Only do this when not done yet for this bufferif exists("b:did_ftplugin") finishendifb:did_ftplugin = 1This also needs to be used to avoid that the samepluginis executed twice forthe same buffer (happens when using an ":edit" command without arguments).Now users can disable loading the defaultplugin completely by makingafiletypeplugin with only these lines:vim9scriptb:did_ftplugin = 1This does require that thefiletypeplugin directory comes before$VIMRUNTIMEin'runtimepath'!If youdo want to use the default plugin, but overrule one of the settings,you can write the different setting ina script:setlocal textwidth=70Now write this in the "after" directory, so thatit gets sourced after thedistributed "vim.vim"ftpluginafter-directory. ForUnix this would be"~/.vim/after/ftplugin/vim.vim".Note that the defaultplugin will have set"b:did_ftplugin",itis ignored here.OPTIONSTo make sure thefiletypeplugin only affects the current buffer use thesetlocalcommand to set options. And only setoptions which are local toa buffer (seethehelp for the option to check that). When using:setlocal for globaloptions oroptions local toa window, the value will change for many buffers,and thatis not whatafiletypeplugin should do.When an option hasa value thatisalist of flags or items, consider using"+=" and "-=" to keep the existing value. Be aware that the user may havechanged an option value already. First resetting to the default value andthenchangingitis oftena good idea. Example:setlocal formatoptions& formatoptions+=roMAPPINGSTo make sure mappings will only work in the current buffer use themap <buffer>command. This needs to be combined with the two-stepmapping explained above.An example of how to define functionality inafiletype plugin:if !hasmapto('<Plug>JavaImport;') map <buffer> <unique> <LocalLeader>i <Plug>JavaImport;endifnoremap <buffer> <unique> <Plug>JavaImport; oimport ""<Left><Esc>hasmapto()is used to check if the user has already defineda map to<Plug>JavaImport;. If not, then thefiletypeplugin defines the defaultmapping. This starts with<LocalLeader>, which allows the user to selectthe key(s) they wantfiletypeplugin mappings to start with. The defaultisabackslash."<unique>"is used to give an error message if themapping already exists oroverlaps with an existing mapping.:noremapis used to avoid that any other mappings that the user has definedinterferes. You might want to use ":noremap<script>" to allow remappingmappings defined in thisscript that start with<SID>.The usermust havea chance to disable the mappings inafiletype plugin,without disabling everything. Hereis an example of how thisis done foraplugin for the mail filetype:# Add mappings, unless the user didn't want this.if !exists("g:no_plugin_maps") && !exists("g:no_mail_maps") # Quote text by inserting "> " if !hasmapto('<Plug>MailQuote;') vmap <buffer> <LocalLeader>q <Plug>MailQuote; nmap <buffer> <LocalLeader>q <Plug>MailQuote; endif vnoremap <buffer> <Plug>MailQuote; :s/^/> /<CR> nnoremap <buffer> <Plug>MailQuote; :.,$s/^/> /<CR>endifTwo globalvariables are used:g:no_plugin_maps disables mappings for allfiletype pluginsg:no_mail_maps disables mappings for the "mail"filetypeUSER COMMANDSTo adda user command fora specific file type, so thatit can only be used inone buffer, use the "-buffer" argument to:command. Example:command -buffer Make make %:r.sVARIABLESAfiletypeplugin will be sourced for each buffer of the type it's for. Localscriptvariables will be shared between all invocations. Use local buffervariablesb:var if you wanta variable specifically for one buffer.FUNCTIONSWhen defininga function, this only needs to be done once. But thefiletypeplugin will be sourced every timea file with thisfiletype will be opened.This construct makes sure the functionis only defined once:if !exists("*Func") def Func(arg) ... enddefendifDon't forget to use "noclear" with thevim9script command to avoid that thefunctionis deleted when thescriptis sourceda second time.UNDOundo_indentundo_ftpluginWhen the user does ":setfiletype xyz" the effect of the previousfiletypeshould be undone. Set the b:undo_ftplugin variable to the commands that willundo the settings in yourfiletype plugin. Example:b:undo_ftplugin = "setlocal fo< com< tw< commentstring<"\ .. "| unlet b:match_ignorecase b:match_words b:match_skip"Using ":setlocal" with "<" after the option name resets the option to itsglobal value. Thatis mostly the best way to reset the option value.For undoing the effect of an indent script, the b:undo_indent variable shouldbe set accordingly.Both thesevariables use legacyscript syntax, notVim9 syntax.FILE NAMEThefiletypemust be included in the file nameftplugin-name. Use one ofthese three forms:.../ftplugin/stuff.vim.../ftplugin/stuff_foo.vim.../ftplugin/stuff/bar.vim"stuff"is the filetype, "foo" and "bar" are arbitrary names.FILETYPE DETECTIONplugin-filetypeIf yourfiletypeis not already detected by Vim, you should createafiletypedetection snippet ina separate file. Itis usually in the form of anautocommand that sets thefiletype when the file name matchesa pattern.Example:au BufNewFile,BufRead *.foosetlocal filetype=foofooWrite this single-line fileas "ftdetect/foofoo.vim" in the first directorythat appears in'runtimepath'. ForUnix that would be"~/.vim/ftdetect/foofoo.vim". The conventionis to use the name of thefiletype for thescript name.You can make more complicated checks if you like, for example to inspect thecontents of the file to recognize the language. Also seenew-filetype.SUMMARYftplugin-specialSummary of special things to use inafiletype plugin:<LocalLeader>Value of "maplocalleader", which the user definesasthe keys thatfiletypeplugin mappings start with.map<buffer>Defineamapping local to the buffer.noremap<script>Only remap mappings defined in thisscript that startwith<SID>.setlocalSet an option for the current buffer only.command -bufferDefinea user command local to the buffer.exists("*s:Func")Check ifa function was already defined.Also seeplugin-special, the special things used for all plugins.==============================================================================51.3 Writinga compilerpluginwrite-compiler-pluginA compilerplugin setsoptions for use witha specific compiler. The user canloadit with the:compiler command. The main useis to set the'errorformat' and'makeprg' options.Easiestis to havea lookat examples. This command will edit all the defaultcompiler plugins:next $VIMRUNTIME/compiler/*.vimType:next togo to the nextplugin file.There are two special items about these files. Firstisa mechanism to allowa user to overrule or add to the default file. The default files start with:vim9scriptif exists("g:current_compiler") finishendifg:current_compiler = "mine"When you writea compiler file andputit in your personal runtime directory(e.g., ~/.vim/compiler for Unix), you set the "current_compiler" variable tomake the default file skip the settings.:CompilerSetThe second mechanismis to use ":set" for ":compiler!" and ":setlocal" for":compiler". Vim defines the ":CompilerSet" user command for this. Thisisan example: CompilerSet errorformat&" use the default 'errorformat' CompilerSet makeprg=nmakeNote: arguments need to be escaped according tooption-backslash.When you writea compilerplugin for the Vimdistribution or fora system-wideruntime directory, use the mechanism mentioned above. When"current_compiler" was already set bya userplugin nothing will be done.When you writea compilerplugin to overrule settings froma default plugin,don't check "current_compiler". Thispluginis supposed to be loadedlast, thusit should be ina directoryat theend of'runtimepath'. ForUnixthat could be ~/.vim/after/compiler.==============================================================================51.4 Distributing Vim scriptsdistribute-scriptVim users will look for scripts on the Vim website:http://www.vim.org.If you made something thatis useful for others, share it!Another placeis github. But there you need to know where to find it! Theadvantageis that mostplugin managersfetch plugins from github. You'll haveto use your favorite search engine to find them.Vim scripts can be used on any system. However, there might not beatar orgzip command. If you want to pack files together and/orcompress them the"zip" utilityis recommended.For utmost portability use Vim itself to pack scripts together. This can bedone with the Vimball utility. Seevimball.It's good if you adda line to allow automatic updating. Seeglvs-plugins.==============================================================================Next chapter:usr_52.txt Write large pluginsCopyright: seemanual-copyright vim:tw=78:ts=8:noet:ft=help:norl: