├── .editorconfig ├── .gitignore ├── LICENSE ├── autoload ├── htl_indent.vim └── htl_syntax.vim ├── doc └── html-template-literals.txt ├── plugin └── html-template-literals.vim ├── readme.md └── test ├── example-code-tests └── example-code-tests.vader ├── js └── indent │ ├── html-string-literal-start.vader │ ├── html-string-literal-terminator.vader │ └── template-end.vader ├── minimalvimrc └── runtests /.editorconfig: -------------------------------------------------------------------------------- 1 | root=true 2 | 3 | [*.vim] 4 | indent_style=space 5 | indent_size=2 6 | insert_final_newline=true 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | doc/tags 2 | test/ts/**/*typescript.vader 3 | test/testresults.txt 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jon Smithers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /autoload/htl_indent.vim: -------------------------------------------------------------------------------- 1 | " Description: Vim html-template-string indent amendments 2 | " Maintainer: Jon Smithers 3 | 4 | if (exists('g:htl_debug') && g:htl_debug == 1) 5 | exec 'command! HTLReload :source ' . expand('') . ' | :edit' 6 | endif 7 | 8 | function! htl_indent#amend(options) 9 | let b:htl_js = a:options.typescript ? '^\(typescript\|foldBraces$\)' : '^js' 10 | let b:htl_expression_bracket = a:options.typescript ? '\(typescriptInterpolationDelimiter\|typescriptTemplateSB\)' : 'jsTemplateBraces' 11 | 12 | " Save the current JavaScript indentexpr. 13 | let b:litHtmlOriginalIndentExpression = &indentexpr 14 | 15 | " import html indent 16 | if exists('b:did_indent') 17 | let s:did_indent=b:did_indent 18 | unlet b:did_indent 19 | endif 20 | exe 'runtime! indent/html.vim' 21 | if exists('s:did_indent') 22 | let b:did_indent=s:did_indent 23 | endif 24 | 25 | " import css indent 26 | if exists('b:did_indent') 27 | let s:did_indent=b:did_indent 28 | unlet b:did_indent 29 | endif 30 | exe 'runtime! indent/css.vim' 31 | if exists('s:did_indent') 32 | let b:did_indent=s:did_indent 33 | endif 34 | 35 | setlocal indentexpr=ComputeLitHtmlIndent() 36 | 37 | " JS indentkeys 38 | setlocal indentkeys=0{,0},0),0],0\,,!^F,o,O,e 39 | " XML indentkeys 40 | setlocal indentkeys+=*,<>>,<<>,/ 41 | " lit-html indentkeys 42 | setlocal indentkeys+=` 43 | 44 | endfunction 45 | 46 | " Get syntax stack at StartOfLine 47 | fu! s:VHTL_SynSOL(lnum) 48 | let l:col = match(getline(a:lnum), '\S') 49 | if (l:col == -1) 50 | return [] 51 | endif 52 | return map(synstack(a:lnum, l:col+1), "synIDattr(v:val, 'name')") 53 | endfu 54 | 55 | " Get syntax stack at EndOfLine 56 | fu! s:VHTL_SynEOL(lnum) 57 | if (a:lnum < 1) 58 | return [] 59 | endif 60 | let l:col = strlen(getline(a:lnum)) 61 | return map(synstack(a:lnum, l:col), "synIDattr(v:val, 'name')") 62 | endfu 63 | 64 | function! s:SynAt(l,c) " from $VIMRUNTIME/indent/javascript.vim 65 | let l:byte = line2byte(a:l) + a:c - 1 66 | let l:pos = index(s:synid_cache[0], l:byte) 67 | if l:pos == -1 68 | let s:synid_cache[:] += [[l:byte], [synIDattr(synID(a:l, a:c, 0), 'name')]] 69 | endif 70 | return s:synid_cache[1][l:pos] 71 | endfunction 72 | 73 | " Make debug log. You can view these logs using ':messages' 74 | fu! s:debug(str) 75 | if (exists('g:htl_debug') && g:htl_debug == 1) 76 | echom 'htl ' . v:lnum . ': ' . a:str 77 | endif 78 | endfu 79 | 80 | let s:StateClass={} 81 | fu! s:StateClass.new(lnum) 82 | let l:instance = copy(l:self) 83 | let l:instance.currLine = a:lnum 84 | let l:instance.prevLine = prevnonblank(a:lnum - 1) 85 | let l:instance.currSynstack = s:VHTL_SynSOL(l:instance.currLine) 86 | let l:instance.prevSynstack = s:VHTL_SynEOL(l:instance.prevLine) 87 | return l:instance 88 | endfu 89 | 90 | fu! s:StateClass.startsWithTemplateClose() dict 91 | return (getline(l:self.currSynstack)) =~# '^\s*`' 92 | endfu 93 | 94 | fu! s:StateClass.openedJsExpression() dict 95 | return (VHTL_getBracketDepthChange(getline(l:self.prevLine)) > 0) 96 | endfu 97 | fu! s:StateClass.openedLitHtmlTemplate() dict 98 | let l:index = 0 99 | let l:depth = 0 100 | while v:true 101 | let [l:term, l:index, l:trash] = matchstrpos(getline(l:self.prevLine), '\Mhtml`\|\\`\|`', l:index) 102 | if (l:index == -1) 103 | return (l:depth > 0) 104 | endif 105 | if (l:term ==# 'html`') 106 | let l:index += len('html`') 107 | let l:depth += 1 108 | elseif(l:term ==# '`') 109 | let l:index += len('`') 110 | if (l:depth > 0) 111 | let l:depth -= 1 112 | endif 113 | endif 114 | endwhile 115 | endfu 116 | 117 | fu! s:StateClass.isInsideLitHtml() dict 118 | for l:syntaxAttribute in reverse(copy(l:self.currSynstack)) 119 | if (l:syntaxAttribute ==# 'litHtmlRegion') 120 | return v:true 121 | endif 122 | endfor 123 | return v:false 124 | endfu 125 | fu! s:StateClass.wasInsideLitHtml() dict 126 | for l:syntaxAttribute in reverse(copy(l:self.prevSynstack)) 127 | if (l:syntaxAttribute ==# 'litHtmlRegion') 128 | return v:true 129 | endif 130 | endfor 131 | return v:false 132 | endfu 133 | 134 | fu! s:StateClass.wasHtml() dict 135 | return get(l:self.prevSynstack, -1) =~# '^html' 136 | endfu 137 | fu! s:StateClass.isHtml() dict 138 | return get(l:self.currSynstack, -1) =~# '^html' 139 | endfu 140 | fu! s:StateClass.isLitHtmlRegionCloser() dict 141 | return get(l:self.currSynstack, -1) ==# 'litHtmlRegion' && getline(l:self.currLine) =~# '^\s*`' 142 | endfu 143 | fu! s:StateClass.opensTemplate() dict 144 | return get(l:self.currSynstack, -1) ==# 'litHtmlRegion' && getline(l:self.currLine) =~# '^\s*html`' 145 | endfu 146 | fu! s:StateClass.closedTemplate() dict 147 | return get(l:self.prevSynstack, -1) ==# 'litHtmlRegion' && getline(l:self.prevLine) !~# 'html`$' 148 | endfu 149 | fu! s:StateClass.wasJs() dict 150 | return get(l:self.prevSynstack, -1) =~# b:htl_js 151 | endfu 152 | fu! s:StateClass.isJs() dict 153 | return get(l:self.currSynstack, -1) =~# b:htl_js 154 | endfu 155 | fu! s:StateClass.wasExpressionBracket() dict 156 | return get(l:self.prevSynstack, -1) =~# b:htl_expression_bracket 157 | endfu 158 | fu! s:StateClass.isExpressionBracket() dict 159 | return get(l:self.currSynstack, -2) =~# b:htl_expression_bracket 160 | endfu 161 | fu! s:StateClass.closedExpression() dict 162 | return l:self.wasExpressionBracket() && getline(l:self.prevLine)[-1:-1] ==# '}' 163 | endfu 164 | fu! s:StateClass.closesExpression() dict 165 | return l:self.isExpressionBracket() && getline(l:self.currLine)[-1:-1] ==# '}' 166 | endfu 167 | fu! s:StateClass.openedExpression() dict 168 | return l:self.wasExpressionBracket() && getline(l:self.prevLine)[-1:-1] ==# '{' 169 | endfu 170 | fu! s:StateClass.opensExpression() dict 171 | return l:self.isExpressionBracket() && getline(l:self.currLine)[-1:-1] ==# '{' 172 | endfu 173 | fu! s:StateClass.wasCss() dict 174 | return get(l:self.prevSynstack, -1) =~# '^css' 175 | endfu 176 | fu! s:StateClass.isCss() dict 177 | return get(l:self.currSynstack, -1) =~# '^css' 178 | endfu 179 | 180 | fu! s:StateClass.toStr() dict 181 | return '{line ' . l:self.currLine . '}' 182 | endfu 183 | 184 | fu! s:SkipFuncJsTemplateBraces() 185 | " let l:char = getline(line('.'))[col('.')-1] 186 | let l:syntax = s:SynAt(line('.'), col('.')) 187 | if (l:syntax !~# b:htl_expression_bracket) 188 | return 1 189 | endif 190 | endfu 191 | 192 | fu! s:SkipFuncHtmlTag() 193 | " call s:debug('SkipFuncHtmlTag col ' . col('.') . ': ' . synIDattr(synID(line('.'), col('.'), 0), 'name')) 194 | return (synIDattr(synID(line('.'), col('.'), 0), 'name') !~# '^html') 195 | endfu 196 | 197 | fu! s:SkipFuncLitHtmlRegion() 198 | " let l:char = getline(line('.'))[col('.')-1] 199 | let l:syntax = s:SynAt(line('.'), col('.')) 200 | if (l:syntax !=# 'litHtmlRegion') 201 | return 1 202 | endif 203 | endfu 204 | 205 | fu! s:getCloseWordsLeftToRight(lineNum) 206 | let l:line = getline(a:lineNum) 207 | 208 | " The following regex converts a line to purely a list of closing words. 209 | " Pretty cool but not useful 210 | " echo split(getline(62), '.\{-}\ze\(}\|`\|<\/\w\+>\)') 211 | 212 | let l:anyCloserWord = '}\|`\|<\/\w\+>' 213 | 214 | 215 | let l:index = 0 216 | let l:closeWords = [] 217 | while v:true 218 | let [l:term, l:index, l:trash] = matchstrpos(l:line, l:anyCloserWord, l:index) 219 | if (l:index == -1) 220 | break 221 | else 222 | let l:col = l:index + 1 223 | call add(l:closeWords, [l:term, l:col]) 224 | endif 225 | let l:index += 1 226 | endwhile 227 | return l:closeWords 228 | endfu 229 | 230 | fu! s:StateClass.getIndentDelta() dict 231 | let l:closeWords = s:getCloseWordsLeftToRight(l:self.currLine) 232 | if len(l:closeWords) == 0 233 | call s:debug('getIndentDelta: no closeWords') 234 | return 0 235 | endif 236 | let [l:closeWord, l:col] = l:closeWords[0] 237 | let l:syntax = s:SynAt(l:self.currLine, l:col) 238 | if (l:syntax ==# 'htmlEndTag') 239 | 240 | let l:openWord = substitute(substitute(l:closeWord, '/', '', ''), '>', '', '') 241 | call cursor(l:self.currLine, l:col) " set start point for searchpair() 242 | call searchpair(l:openWord, '', l:closeWord, 'bW', 's:SkipFuncHtmlTag()') 243 | " call searchpair(l:openWord, '', l:closeWord, 'bW') 244 | call s:debug('closeword ' . l:closeWord) 245 | if (line('.') ==# l:self.currLine) 246 | call s:debug('getIndentDelta: self-closed html tag') 247 | call s:debug(line('.')) 248 | return 0 249 | else 250 | call s:debug('getIndentDelta: closing html tag!!!') 251 | call s:debug(line('.')) 252 | return - &shiftwidth 253 | endif 254 | endif 255 | if (l:syntax ==# 'litHtmlRegion' && 'html`' !=# strpart(getline(l:self.currLine), l:col-5, len('html`'))) 256 | call s:debug('getIndentDelta: end of litHtmlRegion') 257 | return - &shiftwidth 258 | endif 259 | call s:debug('getIndentDelta: no delta found') 260 | return 0 261 | endfu 262 | 263 | " html tag, html template, or js expression on previous line 264 | fu! s:StateClass.getIndentOfLastClose() dict 265 | 266 | let l:closeWords = s:getCloseWordsLeftToRight(l:self.prevLine) 267 | 268 | if (len(l:closeWords) == 0) 269 | call s:debug('getIndentOfLastClose: no close words found') 270 | return -1 271 | endif 272 | 273 | for l:item in reverse(l:closeWords) 274 | let [l:closeWord, l:col] = l:item 275 | let l:syntax = s:SynAt(l:self.prevLine, l:col) 276 | call cursor(l:self.prevLine, l:col) " sets start point for searchpair() 277 | if ('}' ==# l:closeWord && l:syntax =~# b:htl_expression_bracket) 278 | call searchpair('{', '', '}', 'bW', 's:SkipFuncJsTemplateBraces()') 279 | call s:debug('getIndentOfLastClose: js brace base indent') 280 | elseif ('`' ==# l:closeWord && l:syntax ==# 'litHtmlRegion') 281 | call searchpair('html`', '', '\(html\)\@', '', '') 285 | call searchpair(l:openWord, '', l:closeWord, 'bW', 's:SkipFuncHtmlTag()') 286 | call s:debug('getIndentOfLastClose: html tag region base indent ') 287 | else 288 | call s:debug('getIndentOfLastClose: UNRECOGNIZED CLOSER SYNTAX: "' . l:syntax . '"') 289 | endif 290 | return indent(line('.')) " cursor was moved by searchpair() 291 | endfor 292 | endfu 293 | 294 | " com! MyTest exec "call s:StateClass.new().getIndentOfLastClose()" 295 | 296 | " Dispatch to indent method for js/html (use custom rules for transitions 297 | " between syntaxes) 298 | fu! ComputeLitHtmlIndent() 299 | let s:synid_cache = [[],[]] 300 | 301 | let l:state = s:StateClass.new(v:lnum) 302 | 303 | " get most recent non-empty line 304 | let l:prev_lnum = prevnonblank(v:lnum - 1) 305 | 306 | if (!l:state.isInsideLitHtml() && !l:state.wasInsideLitHtml()) 307 | call s:debug('outside of litHtmlRegion: ' . b:litHtmlOriginalIndentExpression) 308 | 309 | if (exists('b:hi_indent') && has_key(b:hi_indent, 'blocklnr')) 310 | call remove(b:hi_indent, 'blocklnr') 311 | " This avoids a really weird behavior when indenting first line inside 312 | " style tag and then indenting any normal javascript outside of 313 | " lit-html region. 'blocklnr' is assigned to line number of 101 |
102 | 103 |
104 | ` 105 | 106 | Do: 107 | =G 108 | 109 | Expect javascript; 110 | html` 111 | 122 |
123 | 124 |
125 | ` 126 | 127 | Given javascript (lit-html readme example 1); 128 | const render = () => html` 129 | ${when(state === 'loading', 130 | html`
Loading...
`, 131 | html`

${message}

`)} 132 | `; 133 | 134 | Do: 135 | =G 136 | 137 | Expect javascript; 138 | const render = () => html` 139 | ${when(state === 'loading', 140 | html`
Loading...
`, 141 | html`

${message}

`)} 142 | `; 143 | 144 | Given javascript (lit-html readme example 2); 145 | const render = () => html` 146 |
    147 | ${repeat(items, (i) => i.id, (i, index) => html` 148 |
  • ${index}: ${i.name}
  • `)} 149 |
150 | `; 151 | 152 | Do: 153 | =G 154 | 155 | Expect javascript; 156 | const render = () => html` 157 |
    158 | ${repeat(items, (i) => i.id, (i, index) => html` 159 |
  • ${index}: ${i.name}
  • `)} 160 |
161 | `; 162 | 163 | Given javascript (lit-html readme example 3); 164 | const render = () => html` 165 |

166 | ${until( 167 | fetch('content.txt').then((r) => r.text()), 168 | html`Loading...`)} 169 |

170 | `; 171 | 172 | Do: 173 | =G 174 | 175 | Expect javascript; 176 | const render = () => html` 177 |

178 | ${until( 179 | fetch('content.txt').then((r) => r.text()), 180 | html`Loading...`)} 181 |

182 | `; 183 | 184 | 185 | Given javascript (nuances with closing brackets and templates): 186 | html` 187 | ${repeat(items, (i) => i.id, (i, index) => html` 188 |
  • ${index}: ${i.name}
  • `)} 189 |
    DEDENT THIS LINE ONCE
    190 |
    191 |
    as usual
    192 | ${this.ternary ? html` 193 |
    indented
    194 | ` : html` 195 |
    also indented
    196 | `} 197 |
    DO NOT DEDENT THIS LINE
    198 |
    199 | ` 200 | 201 | Do: 202 | =G 203 | 204 | Expect javascript: 205 | html` 206 | ${repeat(items, (i) => i.id, (i, index) => html` 207 |
  • ${index}: ${i.name}
  • `)} 208 |
    DEDENT THIS LINE ONCE
    209 |
    210 |
    as usual
    211 | ${this.ternary ? html` 212 |
    indented
    213 | ` : html` 214 |
    also indented
    215 | `} 216 |
    DO NOT DEDENT THIS LINE
    217 |
    218 | ` 219 | 220 | Given javascript (adjacent js expressions): 221 | html` 222 | ${console.log(null)} 223 | ${console.log('this line should have equal indentation')} 224 | ` 225 | Do: 226 | =G 227 | 228 | Expect javascript: 229 | html` 230 | ${console.log(null)} 231 | ${console.log('this line should have equal indentation')} 232 | ` 233 | 234 | Given javascript(multiple dedents after closing js expression): 235 | html` 236 |
    237 | ${ test 238 | ? html`

    one

    ` 239 | : html` 240 |

    241 | two 242 |

    `} 243 |
    244 | dedent THRICE here 245 | `; 246 | 247 | Do: 248 | =G 249 | 250 | Expect javascript: 251 | html` 252 |
    253 | ${ test 254 | ? html`

    one

    ` 255 | : html` 256 |

    257 | two 258 |

    `} 259 |
    260 | dedent THRICE here 261 | `; 262 | 263 | " " This test fails because vim-javascript doesn't correctly indent ternaries inside ${ } 264 | " Given javascript (subsequent templates): 265 | " html` 266 | " ${user.isloggedIn 267 | " ? html`Welcome ${user.name}` 268 | " : html`Please log in` 269 | " } 270 | " `; 271 | " html` 272 | "
      273 | " ${items.map((i) => html`
    • ${i}
    • `)} 274 | "
    275 | " `; 276 | " 277 | " Do: 278 | " =G 279 | " 280 | " Expect javascript: 281 | " html` 282 | " ${user.isloggedIn 283 | " ? html`Welcome ${user.name}` 284 | " : html`Please log in` 285 | " } 286 | " `; 287 | " html` 288 | "
      289 | " ${items.map((i) => html`
    • ${i}
    • `)} 290 | "
    291 | " `; 292 | 293 | 294 | Before: 295 | set tabstop=2 296 | set shiftwidth=2 297 | set expandtab 298 | 299 | Given javascript (multiple syntaxes on one line); 300 | return html` 301 | Web Components are 302 | ${mood}!`; 303 | 304 | Do: 305 | =G 306 | 307 | Expect javascript; 308 | return html` 309 | Web Components are 310 | ${mood}!`; 311 | 312 | Given javascript (tricky indnetation); 313 | return html` 314 |
    Files
    315 | ${!this.fileList ? 316 | html` 317 | this.onFetchBtn()}> 318 | fetch notes 319 | 320 | ` : html` 321 | this.onFetchBtn()}> 322 | refresh 323 | 324 | ${repeat(this.fileList || [], null, (file) => html` 325 | this.onFileClick(file.path)} data="${file}">${file.name} 326 | `)} 327 | `} 328 | 336 | this.onNewFileClick()}> 337 | `; 338 | 339 | Do: 340 | =G 341 | 342 | Expect javascript; 343 | return html` 344 |
    Files
    345 | ${!this.fileList ? 346 | html` 347 | this.onFetchBtn()}> 348 | fetch notes 349 | 350 | ` : html` 351 | this.onFetchBtn()}> 352 | refresh 353 | 354 | ${repeat(this.fileList || [], null, (file) => html` 355 | this.onFileClick(file.path)} data="${file}">${file.name} 356 | `)} 357 | `} 358 | 366 | this.onNewFileClick()}> 367 | `; 368 | 369 | 370 | -------------------------------------------------------------------------------- /test/js/indent/html-string-literal-start.vader: -------------------------------------------------------------------------------- 1 | 2 | " (IT SHOULD CORRECTLY INDENT...) 3 | Given javascript (COMMENTED TEMPLATES): 4 | // html` 5 | //
    `; 6 | 7 | Do: 8 | =G 9 | 10 | Expect javascript: 11 | // html` 12 | //
    `; 13 | -------------------------------------------------------------------------------- /test/js/indent/html-string-literal-terminator.vader: -------------------------------------------------------------------------------- 1 | Before: 2 | set tabstop=2 3 | set shiftwidth=2 4 | set expandtab 5 | 6 | " (IT SHOULD CORRECTLY INDENT...) 7 | Given javascript (LITERAL WITH SINGLE NEWLINE); 8 | { 9 | html` 10 | `; 11 | } 12 | 13 | Do: 14 | =G 15 | 16 | Expect javascript; 17 | { 18 | html` 19 | `; 20 | } 21 | 22 | Given javascript (LITERAL WITH SINGLE HTML TAG); 23 | { 24 | html` 25 |
    26 | `; 27 | } 28 | 29 | Do: 30 | =G 31 | 32 | Expect javascript; 33 | { 34 | html` 35 |
    36 | `; 37 | } 38 | 39 | 40 | Given javascript (LITERAL WITH SINGLE SELF CLOSING TAG): 41 | { 42 | html` 43 | 44 | `; 45 | } 46 | 47 | Do: 48 | =G 49 | 50 | Expect javascript: 51 | { 52 | html` 53 | 54 | `; 55 | } 56 | 57 | 58 | " @ignore-ts-test-for-plugin=none (native typescript plugin doesn't work correctly) 59 | Given javascript (LITERAL TERMINATOR AT EOL, NO SEMICOLON): 60 | { 61 | html` 62 |
    63 |
    ` 64 | more() 65 | } 66 | 67 | Do: 68 | =G 69 | 70 | Expect javascript: 71 | { 72 | html` 73 |
    74 |
    ` 75 | more() 76 | } 77 | " @end-ignore-ts 78 | 79 | " @ignore-ts-test-for-plugin=none (native typescript plugin doesn't work correctly) 80 | Given javascript (LITERAL TERMINATOR AT EOL, WITH SEMICOLON): 81 | { 82 | html` 83 |
    84 |
    `; 85 | more() 86 | } 87 | 88 | Do: 89 | =G 90 | 91 | Expect javascript: 92 | { 93 | html` 94 |
    95 |
    `; 96 | more() 97 | } 98 | " @end-ignore-ts 99 | 100 | " @ignore-ts-test-for-plugin=none (native typescript plugin doesn't work correctly) 101 | Given javascript (LITERAL TERMINATOR AT END OF ONLY LINE): 102 | { 103 | html` 104 |
    ` 105 | more() 106 | } 107 | 108 | Do: 109 | =G 110 | 111 | Expect javascript: 112 | { 113 | html` 114 |
    ` 115 | more() 116 | } 117 | " @end-ignore-ts 118 | 119 | " @ignore-ts-test-for-plugin=none (native typescript plugin doesn't work correctly) 120 | Given javascript (NEXT LINE AFTER LITERAL): 121 | { 122 | html` 123 | ${ 124 | html` 125 |
    ` } `; 126 | more() 127 | } 128 | 129 | Do: 130 | =G 131 | 132 | Expect javascript: 133 | { 134 | html` 135 | ${ 136 | html` 137 |
    ` } `; 138 | more() 139 | } 140 | " @end-ignore-ts 141 | 142 | After: 143 | # Given javascript (IGNORED: LITERAL AFTER SELF-CLOSING TAG) 144 | # { 145 | # html` 146 | #
    147 | # `; 148 | # } 149 | # 150 | # Do: 151 | # =G 152 | # 153 | # Expect javascript; 154 | # { 155 | # html` 156 | #
    157 | # `; 158 | # } 159 | -------------------------------------------------------------------------------- /test/js/indent/template-end.vader: -------------------------------------------------------------------------------- 1 | Before: 2 | set tabstop=2 3 | set shiftwidth=2 4 | set expandtab 5 | 6 | " (IT SHOULD CORRECTLY INDENT...) 7 | Given javascript (NEXT LINE OF OPENING HTML); 8 | { 9 | html` 10 | ${ 11 | html`${ 12 | 2 + 2}`} 13 |
    14 |
    15 | `; 16 | } 17 | 18 | Do: 19 | =G 20 | 21 | Expect javascript; 22 | { 23 | html` 24 | ${ 25 | html`${ 26 | 2 + 2}`} 27 |
    28 |
    29 | `; 30 | } 31 | 32 | Given javascript (NEXT LINE OF SELF-CLOSING HTML); 33 | { 34 | html` 35 |
    36 | ${x} 37 | 38 |
    39 | ` 40 | } 41 | 42 | Do: 43 | =G 44 | 45 | Expect javascript; 46 | { 47 | html` 48 |
    49 | ${x} 50 | 51 |
    52 | ` 53 | } 54 | 55 | Given javascript (NEXT LINE OF CLOSING HTML); 56 | { 57 | html` 58 |
    59 | 60 | ${x} 61 |
    62 | ` 63 | } 64 | 65 | Do: 66 | =G 67 | 68 | Expect javascript; 69 | { 70 | html` 71 |
    72 | 73 | ${x} 74 |
    75 | ` 76 | } 77 | 78 | 79 | Given javascript (NEXT LINE OF HTML AFTER EXPRESSION TERMINATOR AT EOL); 80 | { 81 | html` 82 |
    83 | ${ 84 | 1 + 2} 85 |
    86 |
    ` 87 | } 88 | 89 | Do: 90 | =G 91 | 92 | Expect javascript; 93 | { 94 | html` 95 |
    96 | ${ 97 | 1 + 2} 98 |
    99 |
    ` 100 | } 101 | 102 | 103 | -------------------------------------------------------------------------------- /test/minimalvimrc: -------------------------------------------------------------------------------- 1 | set nocompatible 2 | 3 | let s:directory = '~/git/vim-html-template-literals' 4 | 5 | if (empty(glob(s:directory))) 6 | echo '"' . s:directory . '" does not exist' 7 | :cquit 8 | endif 9 | 10 | call plug#begin('~/.vim/plugged') 11 | Plug 'junegunn/vader.vim' 12 | exec "Plug 'jonsmithers/vim-html-template-literals', { 'dir': '" . s:directory . "' }" 13 | if !empty($SYNTAX_PLUGIN) && 'none' !=# $SYNTAX_PLUGIN 14 | exec "Plug '".$SYNTAX_PLUGIN."'" 15 | endif 16 | call plug#end() 17 | syntax enable 18 | let g:htl_debug = 1 19 | let g:html_indent_style1 = 'inc' 20 | 21 | nmap :echo map(synstack(line("."), col(".")), "synIDattr(v:val, 'name')") 22 | 23 | set tabstop=2 24 | set shiftwidth=2 25 | set expandtab 26 | -------------------------------------------------------------------------------- /test/runtests: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd "$(dirname "$0")" || exit 1 4 | 5 | GREEN='\033[0;32m' 6 | RED='\033[0;31m' 7 | YELLOW='\033[1;33m' 8 | NORMAL='\033[0m' 9 | 10 | generateTypeScriptTests() { 11 | rm -r ./ts 12 | for javascriptTest in js/indent/*.vader; do 13 | typescriptTest=$(echo "$javascriptTest" | sed -E 's#^js/#ts/#' | sed -E 's/.vader$/-typescript.vader/') 14 | 15 | mkdir -p "$(dirname "$typescriptTest")" 16 | 17 | cat > "$typescriptTest" <<- END_HEADER 18 | ================================= 19 | == THIS FILE IS AUTO-GENERATED == 20 | ================================= 21 | END_HEADER 22 | 23 | escaped_syntax_plugin=$(echo "$SYNTAX_PLUGIN" | sed 's#/#\\/#') 24 | 25 | cat < "$javascriptTest" \ 26 | | sed -E 's/Given javascript/Given typescript/' \ 27 | | sed -E 's/Expect javascript/Expect typescript/' \ 28 | | sed "/@ignore-ts-test-for-plugin=$escaped_syntax_plugin/,/@end-ignore-ts/d" \ 29 | >> "$typescriptTest" 30 | 31 | skipCount=$(cat < "$javascriptTest" | grep -c "@ignore-ts-test-for-plugin=$escaped_syntax_plugin") 32 | if [[ "$skipCount" -ne 0 ]]; then 33 | echo -e "(${YELLOW}skipping $skipCount tests${NORMAL})" 34 | fi 35 | done 36 | } 37 | 38 | for TEST_CONFIG in 'js,pangloss/vim-javascript' 'ts,leafgarland/typescript-vim' 'ts,none'; do 39 | TEST_CONFIG_ARRAY=(${TEST_CONFIG//,/ }) 40 | TEST_DIR=${TEST_CONFIG_ARRAY[0]} 41 | SYNTAX_PLUGIN=${TEST_CONFIG_ARRAY[1]} 42 | 43 | echo 44 | echo "running $TEST_DIR tests with syntax plugin \"$SYNTAX_PLUGIN\"" 45 | 46 | export SYNTAX_PLUGIN 47 | 48 | if [[ $TEST_DIR = "ts" ]]; then 49 | generateTypeScriptTests 50 | fi 51 | 52 | output=$(vim -u <(cat minimalvimrc) +'Vader! '"$TEST_DIR"'/**/*.vader' 2>&1) 53 | exit_code=$? 54 | if [[ $exit_code != 0 ]]; then 55 | echo tests failed 56 | if [[ "$CI" != true ]]; then 57 | echo -e " ${RED}✗ tests failed${NORMAL}" 58 | echo showing failed tests in vim 59 | vim -u <(cat minimalvimrc) +'Vader '"$TEST_DIR"'/**/*.vader' 60 | else 61 | echo "$output" 62 | fi 63 | exit 1 64 | else 65 | echo -e " ${GREEN}✓ tests passed${NORMAL}" 66 | fi 67 | done 68 | --------------------------------------------------------------------------------