├── README.md
├── example.mustache
├── ftdetect
├── handlebars.vim
└── mustache.vim
├── ftplugin
├── handlebars.vim
└── mustache.vim
├── indent
├── handlebars.vim
└── mustache.vim
└── syntax
├── handlebars.vim
└── mustache.vim
/README.md:
--------------------------------------------------------------------------------
1 | mustache and handlebars mode for vim
2 | ====================================
3 |
4 | A vim plugin for working with [mustache][mustache] and
5 | [handlebars][handlebars] templates. It has:
6 |
7 | - syntax highlighting
8 | - matchit support
9 | - mustache abbreviations (optional)
10 | - section movement mappings `[[` and `]]`
11 | - text objects `ie` (inside element) and `ae` (around element)
12 |
13 | This plugin contributes to [vim-polyglot](https://github.com/sheerun/vim-polyglot) language pack.
14 |
15 | **Note**: for compatibility reason #7, we've renamed the repo name from
16 | `vim-mode` to `vim-mustache-handlebars`.
17 |
18 | ### Install for pathogen
19 |
20 | cd ~/.vim/
21 | git clone git://github.com/mustache/vim-mustache-handlebars.git bundle/mustache
22 | vim bundle/mustache/example.mustache
23 |
24 | Get [pathogen][pathogen].
25 |
26 | ### Install for vundle
27 |
28 | Add `Plugin 'mustache/vim-mustache-handlebars'` to your `.vimrc` and do a
29 | `:PluginInstall`.
30 |
31 | ### Built-in Vim Package Manager
32 |
33 | > **Heads up!** Only available for Vim 8+
34 |
35 | mkdir -p ~/.vim/pack/mustache/start
36 | cd ~/.vim/pack/mustache/start
37 | git clone https://github.com/mustache/vim-mustache-handlebars.git
38 |
39 | **To learn more about Vim's built-in package manager:** `:help packages`.
40 |
41 | ### Manually Install
42 |
43 | cd ~/.local/src
44 | git clone git://github.com/mustache/vim-mustache-handlebars.git mustache.vim
45 | cp -R mustache.vim/syntax/* ~/.vim/syntax/
46 | cp -R mustache.vim/ftdetect/* ~/.vim/ftdetect/
47 | cp -R mustache.vim/ftplugin/* ~/.vim/ftplugin/
48 | vim mustache.vim/example.mustache
49 |
50 | ### Mustache Abbreviations
51 |
52 | You can activate mustache abbreviations by putting this line in your `.vimrc`:
53 | `let g:mustache_abbreviations = 1`
54 |
55 | Now you get a set of convenient abbreviations. Underscore `_` indicates where
56 | your cursor ends up after typing an abbreviation:
57 | - `{{` => `{{_}}`
58 | - `{{{` => `{{{_}}}`
59 | - `{{!` => `{{!_}}`
60 | - `{{>` => `{{>_}}`
61 | - `{{<` => `{{<_}}`
62 | - `{{#` produces
63 |
64 | ```
65 | {{# _}}
66 | {{/}}
67 | ```
68 | - `{{if` produces
69 |
70 | ```
71 | {{#if _}}
72 | {{/if}}
73 | ```
74 | - `{{ife` produces
75 |
76 | ```
77 | {{#if _}}
78 | {{else}}
79 | {{/if}}
80 | ```
81 |
82 | ### Section movement mappings
83 |
84 | Following the vim convention of jumping from section to section, `[[` and `]]`
85 | mappings are implemented for easier movement between mustache tags.
86 |
87 | - `]]` jumps to the first following tag
88 | - `[[` jumps to the first previous tag
89 |
90 | Count with section movements is supported:
91 |
92 | - `2]]` jumps to the second next tag
93 |
94 | ### Text objects
95 |
96 | Vim has a very powerful concept of "text objects". If you aren't using text objects,
97 | you can get familiar with it on [this vim help
98 | link](http://vimdoc.sourceforge.net/htmldoc/motion.html#text-objects). Learning
99 | text objects really speeds up the vim workflow.
100 |
101 | In that spirit, this plugin defines 2 text objects:
102 | - `ie` a mnemonic for `inside element`, selects all the text inside the
103 | mustache tag.
104 | For example, when used with `vie` it will visually select the
105 | bold text in the following snippets: {{**some_variable**}},
106 | {{{**different_variable**}}}.
107 | - `ae` a mnemonic for `around element`, selects the whole mustache tag,
108 | including the curly braces.
109 | Example, `vae` visually selects the bold text in the following
110 | snippets: **{{some_variable}}** or **{{{another_variable}}}**.
111 |
112 | Here are other usage examples:
113 | - `dae` - deletes the whole mustache tag, including the curly braces
114 | - `die` - deletes **inside** the mustache tag, leaving only curly braces
115 | - `yae` - "yanks" the whole mustache tag - with curly braces
116 | - `cie` - deletes **inside** the mustache tag and goes in insert mode
117 |
118 | `ie` and `ae` text objects are enabled by default. To disable them, put the
119 | following in your `.vimrc`:
120 |
121 | let g:mustache_operators = 0
122 |
123 | ## Maintainers
124 |
125 | * [Bruno Michel](http://github.com/nono)
126 | * [Bruno Sutic](http://github.com/bruno-)
127 | * [Juvenn Woo](http://github.com/juvenn)
128 |
129 | This is combined work from
130 | [juvenn/mustache.vim](http://github.com/juvenn/mustache.vim) and
131 | [nono/vim-handlebars](http://github.com/nono/vim-handlebars).
132 |
133 | ----
134 |
135 | Thanks [@5long](http://github.com/5long) for adding matchit support.
136 |
137 | You're encouraged to propose ideas or have discussions via github
138 | issues.
139 |
140 | [mustache]: http://mustache.github.io
141 | [handlebars]: http://handlebarsjs.com
142 | [pathogen]: https://github.com/tpope/vim-pathogen
143 |
--------------------------------------------------------------------------------
/example.mustache:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{title}}
6 |
7 | {{!}}
8 |
9 |
10 |
148 |
149 |
150 |
151 |
152 |
--------------------------------------------------------------------------------
/ftdetect/handlebars.vim:
--------------------------------------------------------------------------------
1 | if has("autocmd")
2 | au BufNewFile,BufRead *.handlebars,*.hdbs,*.hbs,*.hb set filetype=html.handlebars
3 | endif
4 |
--------------------------------------------------------------------------------
/ftdetect/mustache.vim:
--------------------------------------------------------------------------------
1 | if has("autocmd")
2 | au BufNewFile,BufRead *.mustache,*.hogan,*.hulk,*.hjs set filetype=html.mustache
3 | endif
4 |
--------------------------------------------------------------------------------
/ftplugin/handlebars.vim:
--------------------------------------------------------------------------------
1 | if exists('b:loaded_mustache_handlebars')
2 | finish
3 | endif
4 | let b:loaded_mustache_handlebars = 1
5 |
6 | let s:cpo_save = &cpo
7 | set cpo&vim
8 |
9 | " Matchit support for Mustache & Handlebars
10 | " extending HTML matchit groups
11 | if exists("loaded_matchit") && exists("b:match_words")
12 | let b:match_words = b:match_words
13 | \ . ',{:},[:],(:),'
14 | \ . '\%({{\)\@<=#\s*\%(if\|unless\)\s*.\{-}}}'
15 | \ . ':'
16 | \ . '\%({{\)\@<=\s*else\s*}}'
17 | \ . ':'
18 | \ . '\%({{\)\@<=/\s*\%(if\|unless\)\s*}},'
19 | \ . '\%({{\)\@<=[#^]\s*\([-0-9a-zA-Z_?!/.]\+\).\{-}}}'
20 | \ . ':'
21 | \ . '\%({{\)\@<=/\s*\1\s*}}'
22 | endif
23 |
24 | " Set template for comment
25 | setlocal commentstring={{!--\ %s\ --}}
26 |
27 | if exists("g:mustache_abbreviations")
28 | inoremap {{{ {{{}}}
29 | inoremap {{ {{}}
30 | inoremap {{! {{!}}
31 | inoremap {{< {{<}}
32 | inoremap {{> {{>}}
33 | inoremap {{# {{#}}{{/}}
34 | inoremap {{if {{#if }}{{/if}}
35 | inoremap {{ife {{#if }}{{else}}{{/if}}
36 | endif
37 |
38 |
39 | " Section movement
40 | " Adapted from vim-ruby - many thanks to the maintainers of that plugin
41 |
42 | function! s:sectionmovement(pattern,flags,mode,count)
43 | norm! m'
44 | if a:mode ==# 'v'
45 | norm! gv
46 | endif
47 | let i = 0
48 | while i < a:count
49 | let i = i + 1
50 | " saving current position
51 | let line = line('.')
52 | let col = col('.')
53 | let pos = search(a:pattern,'W'.a:flags)
54 | " if there's no more matches, return to last position
55 | if pos == 0
56 | call cursor(line,col)
57 | return
58 | endif
59 | endwhile
60 | endfunction
61 |
62 | nnoremap [[ :call sectionmovement('{{','b','n',v:count1)
63 | nnoremap ]] :call sectionmovement('{{','' ,'n',v:count1)
64 | xnoremap [[ :call sectionmovement('{{','b','v',v:count1)
65 | xnoremap ]] :call sectionmovement('{{','' ,'v',v:count1)
66 |
67 |
68 | " Operator pending mappings
69 |
70 | " Operators are available by default. Set `let g:mustache_operators = 0` in
71 | " your .vimrc to disable them.
72 | if ! exists("g:mustache_operators")
73 | let g:mustache_operators = 1
74 | endif
75 |
76 | if exists("g:mustache_operators") && g:mustache_operators
77 | onoremap ie :call wrap_inside()
78 | onoremap ae :call wrap_around()
79 | xnoremap ie :call wrap_inside()
80 | xnoremap ae :call wrap_around()
81 | endif
82 |
83 | function! s:wrap_around()
84 | " If the cursor is at the end of the tag element, move back
85 | " so that the end tag can be detected.
86 | while getline('.')[col('.')-1] ==# '}'
87 | execute 'norm h'
88 | endwhile
89 |
90 | " Moves to the end of the closing tag
91 | let pos = search('}}','We')
92 | if pos != 0
93 | if getline('.')[col('.')] ==# '}'
94 | " Ending tag contains 3 closing brackets '}}}',
95 | " move to the last bracket.
96 | execute 'norm l'
97 | endif
98 |
99 | " select the whole tag
100 | execute 'norm v%'
101 | endif
102 | endfunction
103 |
104 | function! s:wrap_inside()
105 | " If the cursor is at the end of the tag element, move back
106 | " so that the end tag can be detected.
107 | while getline('.')[col('.')-1] ==# '}'
108 | execute 'norm h'
109 | endwhile
110 |
111 | " Moves to the end of the closing tag
112 | let pos = search('}}','W')
113 | if pos != 0
114 | " select only inside the tag
115 | execute 'norm v%loho'
116 | endif
117 | endfunction
118 |
119 |
120 | let &cpo = s:cpo_save
121 | unlet s:cpo_save
122 |
123 | " vim: nofoldenable
124 |
--------------------------------------------------------------------------------
/ftplugin/mustache.vim:
--------------------------------------------------------------------------------
1 | runtime! ftplugin/handlebars*.vim ftplugin/handlebars/*.vim
2 |
--------------------------------------------------------------------------------
/indent/handlebars.vim:
--------------------------------------------------------------------------------
1 | " Mustache & Handlebars syntax
2 | " Language: Mustache, Handlebars
3 | " Maintainer: Juvenn Woo
4 | " Screenshot: http://imgur.com/6F408
5 | " Version: 3
6 | " Last Change: 26 Nov 2018
7 | " Remarks: based on eruby indent plugin by tpope
8 | " References:
9 | " [Mustache](http://github.com/defunkt/mustache)
10 | " [Handlebars](https://github.com/wycats/handlebars.js)
11 | " [ctemplate](http://code.google.com/p/google-ctemplate/)
12 | " [ctemplate doc](http://google-ctemplate.googlecode.com/svn/trunk/doc/howto.html)
13 | " [et](http://www.ivan.fomichev.name/2008/05/erlang-template-engine-prototype.html)
14 |
15 | if exists("b:did_indent_hbs")
16 | finish
17 | endif
18 |
19 | unlet! b:did_indent
20 | setlocal indentexpr=
21 |
22 | runtime! indent/html.vim
23 | unlet! b:did_indent
24 |
25 | " Force HTML indent to not keep state.
26 | let b:html_indent_usestate = 0
27 |
28 | if &l:indentexpr == ''
29 | if &l:cindent
30 | let &l:indentexpr = 'cindent(v:lnum)'
31 | else
32 | let &l:indentexpr = 'indent(prevnonblank(v:lnum-1))'
33 | endif
34 | endif
35 | let b:handlebars_subtype_indentexpr = &l:indentexpr
36 |
37 | let b:did_indent = 1
38 | let b:did_indent_hbs = 1
39 |
40 | setlocal indentexpr=GetHandlebarsIndent()
41 | setlocal indentkeys=o,O,*,<>>,{,},0),0],o,O,!^F,=end,=else,=elsif,=rescue,=ensure,=when
42 |
43 | " Only define the function once.
44 | if exists("*GetHandlebarsIndent")
45 | finish
46 | endif
47 |
48 | function! GetHandlebarsIndent(...)
49 | " The value of a single shift-width
50 | if exists('*shiftwidth')
51 | let sw = shiftwidth()
52 | else
53 | let sw = &sw
54 | endif
55 |
56 | if a:0 && a:1 == '.'
57 | let v:lnum = line('.')
58 | elseif a:0 && a:1 =~ '^\d'
59 | let v:lnum = a:1
60 | endif
61 | let vcol = col('.')
62 | call cursor(v:lnum,1)
63 | call cursor(v:lnum,vcol)
64 | exe "let ind = ".b:handlebars_subtype_indentexpr
65 |
66 | " Workaround for Andy Wokula's HTML indent. This should be removed after
67 | " some time, since the newest version is fixed in a different way.
68 | if b:handlebars_subtype_indentexpr =~# '^HtmlIndent('
69 | \ && exists('b:indent')
70 | \ && type(b:indent) == type({})
71 | \ && has_key(b:indent, 'lnum')
72 | " Force HTML indent to not keep state
73 | let b:indent.lnum = -1
74 | endif
75 | let lnum = prevnonblank(v:lnum-1)
76 | let prevLine = getline(lnum)
77 | let currentLine = getline(v:lnum)
78 |
79 | " all indent rules only apply if the block opening/closing
80 | " tag is on a separate line
81 |
82 | " indent after block {{#block
83 | if prevLine =~# '\v\s*\{\{[#^].*\s*'
84 | let ind = ind + sw
85 | endif
86 | " but not if the block ends on the same line
87 | if prevLine =~# '\v\s*\{\{\#(.+)(\s+|\}\}).*\{\{\/\1'
88 | let ind = ind - sw
89 | endif
90 | " unindent after block close {{/block}}
91 | if currentLine =~# '\v^\s*\{\{\/\S*\}\}\s*'
92 | let ind = ind - sw
93 | endif
94 | " indent after component block {{a-component
95 | if prevLine =~# '\v\s*\{\{\w'
96 | let ind = ind + sw
97 | endif
98 | " but not if the component block ends on the same line
99 | if prevLine =~# '\v\s*\{\{\w(.+)\}\}'
100 | let ind = ind - sw
101 | endif
102 | " unindent }} lines, and following lines if not inside a block expression
103 | let savedPos = getpos('.')
104 | if currentLine =~# '\v^\s*\}\}\s*$' || (currentLine !~# '\v^\s*\{\{\/' && prevLine =~# '\v^\s*[^\{\} \t]+\}\}\s*$')
105 | let closingLnum = search('}}\s*$', 'Wbc', lnum)
106 | let [openingLnum, col] = searchpairpos('{{', '', '}}', 'Wb')
107 | if openingLnum > 0 && closingLnum > 0
108 | if strpart(getline(openingLnum), col - 1, 3) !~ '{{[#^]'
109 | let ind = ind - sw
110 | endif
111 | else
112 | call setpos('.', savedPos)
113 | endif
114 | endif
115 | " unindent {{else}}
116 | if currentLine =~# '\v^\s*\{\{else.*\}\}\s*$'
117 | let ind = ind - sw
118 | endif
119 | " indent again after {{else}}
120 | if prevLine =~# '\v^\s*\{\{else.*\}\}\s*$'
121 | let ind = ind + sw
122 | endif
123 |
124 | return ind
125 | endfunction
126 |
--------------------------------------------------------------------------------
/indent/mustache.vim:
--------------------------------------------------------------------------------
1 | runtime! indent/handlebars.vim
2 |
--------------------------------------------------------------------------------
/syntax/handlebars.vim:
--------------------------------------------------------------------------------
1 | " Mustache & Handlebars syntax
2 | " Language: Mustache, Handlebars
3 | " Maintainer: Juvenn Woo
4 | " Screenshot: http://imgur.com/6F408
5 | " Version: 6
6 | " Last Change: Jul 16 2019
7 | " Remark:
8 | " It lexically hilights embedded mustaches (exclusively) in html file.
9 | " While it was written for Ruby-based Mustache template system, it should
10 | " work for Google's C-based *ctemplate* as well as Erlang-based *et*. All
11 | " of them are, AFAIK, based on the idea of ctemplate.
12 | " References:
13 | " [Mustache](http://github.com/defunkt/mustache)
14 | " [Handlebars](https://github.com/wycats/handlebars.js)
15 | " [ctemplate](http://code.google.com/p/google-ctemplate/)
16 | " [ctemplate doc](http://google-ctemplate.googlecode.com/svn/trunk/doc/howto.html)
17 | " [et](http://www.ivan.fomichev.name/2008/05/erlang-template-engine-prototype.html)
18 | " TODO: Feedback is welcomed.
19 |
20 |
21 | " Read the HTML syntax to start with
22 | if version < 600
23 | so :p:h/html.vim
24 | else
25 | runtime! syntax/html.vim
26 | unlet b:current_syntax
27 | endif
28 |
29 | if version < 600
30 | syntax clear
31 | elseif exists("b:current_syntax")
32 | finish
33 | endif
34 |
35 | " Standard HiLink will not work with included syntax files
36 | if version < 508
37 | command! -nargs=+ HtmlHiLink hi link
38 | else
39 | command! -nargs=+ HtmlHiLink hi def link
40 | endif
41 |
42 | syntax match mustacheError '}}}\?'
43 | syntax match mustacheInsideError '{{[{$#<>=!\/]\?'
44 |
45 | " Ember angle bracket syntax syntax starts with a capital letter:
46 | " https://github.com/emberjs/rfcs/blob/master/text/0311-angle-bracket-invocation.md
47 | syntax case match
48 | syntax region mustacheAngleComponent start=/<\/\?[[:upper:]]/ end=/>/ keepend containedin=TOP,@htmlMustacheContainer
49 | syntax case ignore
50 | syntax match mustacheAngleBrackets '\?\|/\?>' contained containedin=mustacheAngleComponent
51 | syntax match mustacheAngleComponentName '[[:alnum:]]\+'hs=s+2 contained containedin=mustacheAngleBrackets
52 | syntax match mustacheAngleComponentName '<[[:alnum:]]\+'hs=s+1 contained containedin=mustacheAngleBrackets
53 |
54 | syntax region mustacheHbsComponent start=/{{[^!][$#^/]\?/ end=/}}}\?/ keepend containedin=TOP,@htmlMustacheContainer
55 |
56 | syntax cluster mustacheInside add=mustacheHbsComponent,mustacheAngleComponent
57 |
58 | syntax match mustacheOperators '=\|\.\|/^>' contained containedin=@mustacheInside,mustacheParam
59 | syntax region mustacheHtmlValue start=/={{[^!][$#^/]\?/rs=s+1,hs=s+1 end=/}}/ oneline keepend contained containedin=htmlTag contains=@mustacheInside
60 | syntax region mustachePartial start=/{{[<>]/lc=2 end=/}}/me=e-2 contained containedin=@mustacheInside,@htmlMustacheContainer
61 | syntax region mustacheMarkerSet start=/{{=/lc=2 end=/=}}/me=e-2 contained containedin=@mustacheInside,@htmlMustacheContainer
62 | syntax match mustacheHandlebars '{{\|}}' contained containedin=@mustacheInside
63 | syntax match mustacheUnescape '{{{\|}}}' contained containedin=@mustacheInside
64 | syntax match mustacheConditionals '\([/#]\?\<\(if\|unless\)\|\' contained containedin=@mustacheInside
65 | syntax match mustacheHelpers '[/#]\?\<\(with\|link\-to\|each\(\-in\)\?\|let\)\>' contained containedin=@mustacheInside
66 | syntax match mustacheHelpers 'else \(if\|unless\|with\|link\-to\|each\(\-in\)\?\)' contained containedin=@mustacheInside
67 | syntax match mustacheParam /[a-z@_-]\+=/he=e-1 contained containedin=@mustacheInside
68 | syntax region mustacheComment start=/{{!/rs=s+2 skip=/{{.\{-}}}/ end=/}}/re=e-2 contains=Todo contained containedin=TOP,@mustacheInside,@htmlMustacheContainer
69 | syntax region mustacheBlockComment start=/{{!--/rs=s+2 skip=/{{.\{-}}}/ end=/--}}/re=e-2 contains=Todo contained extend containedin=TOP,@mustacheInside,@htmlMustacheContainer
70 | syntax region mustacheQString start=/'/ skip=/\\'/ end=/'/ contained containedin=@mustacheInside
71 | syntax region mustacheDQString start=/"/ skip=/\\"/ end=/"/ contained containedin=@mustacheInside
72 |
73 | " Clustering
74 | syntax cluster htmlMustacheContainer add=htmlHead,htmlTitle,htmlString,htmlH1,htmlH2,htmlH3,htmlH4,htmlH5,htmlH6,htmlLink,htmlBold,htmlUnderline,htmlItalic,htmlValue
75 |
76 |
77 | " Hilighting
78 | " mustacheInside hilighted as Number, which is rarely used in html
79 | " you might like change it to Function or Identifier
80 | HtmlHiLink mustacheVariable Number
81 | HtmlHiLink mustacheVariableUnescape Number
82 | HtmlHiLink mustachePartial Number
83 | HtmlHiLink mustacheMarkerSet Number
84 | HtmlHiLink mustacheParam htmlArg
85 | HtmlHiLink mustacheAngleComponentName htmlTag
86 |
87 | HtmlHiLink mustacheComment Comment
88 | HtmlHiLink mustacheBlockComment Comment
89 | HtmlHiLink mustacheError Error
90 | HtmlHiLink mustacheInsideError Error
91 |
92 | HtmlHiLink mustacheHandlebars Special
93 | HtmlHiLink mustacheAngleBrackets htmlTagName
94 | HtmlHiLink mustacheUnescape Identifier
95 | HtmlHiLink mustacheOperators Operator
96 | HtmlHiLink mustacheConditionals Conditional
97 | HtmlHiLink mustacheHelpers Repeat
98 | HtmlHiLink mustacheQString String
99 | HtmlHiLink mustacheDQString String
100 |
101 | syn region mustacheScriptTemplate start=++me=s-1 keepend
103 | \ contains=mustacheInside,@htmlMustacheContainer,htmlTag,htmlEndTag,htmlTagName,htmlSpecialChar
104 |
105 | let b:current_syntax = "handlebars"
106 | delcommand HtmlHiLink
107 |
--------------------------------------------------------------------------------
/syntax/mustache.vim:
--------------------------------------------------------------------------------
1 | runtime! syntax/handlebars.vim
2 |
--------------------------------------------------------------------------------