Copying this question over from https://stackoverflow.com/q/42474604/262108I recently started working with django templates and was wondering whether there are any autocompletion plugins for template directives like {% block|if|... %}etc that do not involve a snippets library.

I specifically do not want a snippet plugin because I find them quite distracting when editing code. Most snippet plugins would require digging thru' the docs and to figure out configuration to disable (rather than enable) snippets.

I am thinking something along the lines of auto-pairs and xmledit which does something like (assuming | is the cursor):

{% block ... %}|   <-- recognize I entered a "%}"

complete it to:

{% block ... %}
    |   <------- place the cursor here
{% endblock %}
  • I copy my comment here then... If you really don't want a snippet plugin, then I'd recommend you to hack xmledit into what you're looking for as what it does is really close. => listen for }. Analyse what is before and copy-paste-adapt it in the next line. Feb 27, 2017 at 11:58
  • BTW, SO and vi.SE have the same audience of people that answer questions. We're present on both "forums". Feb 27, 2017 at 12:00

2 Answers 2


The principle:

  • listen for }, not %}
  • analyse the context to decide what to return from a :h :map-<expr>
  • the mapping shall be buffer local in order to avoid side-effects with other filetypes (:h :map-<buffer>)
  • The movements returned shall be in double-quoted string and escaped with a back-slash (otherwise, you'll see the text <up><c-f> inserted)
  • I'm not sure whether autoindentation could be applied here (:h i_CTRL-F)
  • The result shall be put in a django ftplugin, and it'll even better to put the function into an autoload plugin, with its name changed to django#_expand_block() for instance.

Here is the result:

" EDIT: VA. Support if, for, block. And just copy the statement.
function! s:expand_block() abort
  " Ignore what is after the cursor
  let ctx = getline('.')[:col('.')-1]
  " get sure there is a '%' before the cursor
  let statement = matchstr(ctx, '^{%\s*\zs\(block\|if\|for\)\ze.*%')
  if !empty(statement)
    " Avoid triggering InsertLeave events
    return "}\n\n{% end".statement." %}\<up>\<c-f>"
    " Shorter
    return "}\n{% end".statement." %}\<esc>O"
    return "}"

inoremap <expr> <silent> <buffer> } <sid>expand_block()
  • Thanks for the tip @luc-hermitte and the great breakdown of the approach ! That serves as good starting point, I'll attempt to build on that for all sorts of completiions like {% (block|if|for)...
    – lonetwin
    Feb 28, 2017 at 9:15

After a bit of fiddling around and reading a whole lot of vimscript docs/blog posts, this is what I came up with (starting with the accepted answer as the starting point):

function! VimDjangoCompletion()
    let matches = matchlist(getline('.'), '{%\s\+\(block\|if\|for\)')
    return empty(matches) ? "%}" : "%}\n{% end" . matches[1] . " %}\<ESC>O"

inoremap <buffer> %} <C-R>=VimDjangoCompletion()<CR>

Edit: As @luc-hermitte points out in the comment below, mapping %} is a false good idea, since vim introduces a delay waiting for the next character when % is typed. I initially added that to avoid having the function being called for every instance of } (including when typing {{ ... }} which probably is more common than directives in django templates). However, trading off unnecessary function calls to avoid the delay appears to be a good thing because the delay can get slightly annoying. I'm leaving the code as is to give context for @luc-hermitte's comment.

  • Mapping %} is a false good idea. This will incur an annoying delay every time you hit a %, even when you're simply typing {%. The only way to provide a good experience to end-user is to limit mappings on printable characters to single keys, and to analyse the context. Also, to avoid name pollution, prefer s:functions over global functions. Or even better, autoloaded functions (but this is a thing we don't see the benefit in the first years). PS: <c-r>= is what we used to use, or what we still need when we need to move around the cursor -- this won't make a difference here Mar 3, 2017 at 1:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.