diff --git a/after/indent/javascript.vim b/after/indent/javascript.vim index 4f0124f..4a82d5d 100644 --- a/after/indent/javascript.vim +++ b/after/indent/javascript.vim @@ -38,8 +38,8 @@ setlocal indentkeys+=` let s:endtag = '^\s*\/\?>\s*;\=' " Get syntax stack at StartOfLine -fu! VHTL_SynSOL(lnum) - let l:col = match(getline(line('.')), '\S') +fu! s:VHTL_SynSOL(lnum) + let l:col = match(getline(a:lnum), '\S') if (l:col == -1) return [] endif @@ -47,7 +47,7 @@ fu! VHTL_SynSOL(lnum) endfu " Get syntax stack at EndOfLine -fu! VHTL_SynEOL(lnum) +fu! s:VHTL_SynEOL(lnum) if (a:lnum < 1) return [] endif @@ -55,61 +55,44 @@ fu! VHTL_SynEOL(lnum) return map(synstack(a:lnum, l:col), "synIDattr(v:val, 'name')") endfu -fu! IsSynstackCss(synstack) - return get(a:synstack, -1) =~# '^css' -endfu - -" Does synstack end with an xml syntax attribute -fu! IsSynstackHtml(synstack) - return get(a:synstack, -1) =~# '^html' -endfu - -fu! IsSynstackJs(synstack) - return get(a:synstack, -1) =~# '^js' -endfu +function! s:SynAt(l,c) " from $VIMRUNTIME/indent/javascript.vim + let l:byte = line2byte(a:l) + a:c - 1 + let l:pos = index(s:synid_cache[0], l:byte) + if l:pos == -1 + let s:synid_cache[:] += [[l:byte], [synIDattr(synID(a:l, a:c, 0), 'name')]] + endif + return s:synid_cache[1][l:pos] +endfunction -fu! VHTL_isSynstackInsideLitHtml(synstack) - for l:syntaxAttribute in reverse(copy((a:synstack))) - if (l:syntaxAttribute ==# 'litHtmlRegion') - return v:true - endif - endfor - return v:false +" Make debug log. You can view these logs using ':messages' +fu! s:debug(str) + if (exists('g:VHTL_debugging') && g:VHTL_debugging == 1) + echom 'vhtl ' . v:lnum . ': ' . a:str + endif endfu -fu! IsSynstackInsideJsx(synstack) - for l:syntaxAttribute in reverse(copy((a:synstack))) - if (l:syntaxAttribute =~# '^jsx') - return v:true - endif - endfor - return v:false +let s:StateClass={} +fu! s:StateClass.new(lnum) + let l:instance = copy(l:self) + let l:instance.currLine = a:lnum + let l:instance.prevLine = prevnonblank(a:lnum - 1) + let l:instance.currSynstack = s:VHTL_SynSOL(l:instance.currLine) + let l:instance.prevSynstack = s:VHTL_SynEOL(l:instance.prevLine) + return l:instance endfu -fu! VHTL_closesJsExpression(str) - return (VHTL_getBracketDepthChange(a:str) < 0) -endfu -fu! VHTL_getBracketDepthChange(str) - let l:depth=0 - for l:char in split(a:str, '\zs') - if (l:char ==# '{') - let l:depth += 1 - elseif (l:char ==# '}') - let l:depth -=1 - endif - endfor - return l:depth +fu! s:StateClass.startsWithTemplateClose() dict + return (getline(l:self.currSynstack)) =~# '^\s*`' endfu -fu! VHTL_startsWithTemplateEnd(linenum) - return (getline(a:linenum)) =~# '^\s*`' +fu! s:StateClass.openedJsExpression() dict + return (VHTL_getBracketDepthChange(getline(l:self.prevLine)) > 0) endfu - -fu! VHTL_opensTemplate(line) +fu! s:StateClass.openedLitHtmlTemplate() dict let l:index = 0 let l:depth = 0 while v:true - let [l:term, l:index, l:trash] = matchstrpos(a:line, '\Mhtml`\|\\`\|`', l:index) + let [l:term, l:index, l:trash] = matchstrpos(getline(l:self.prevLine), '\Mhtml`\|\\`\|`', l:index) if (l:index == -1) return (l:depth > 0) endif @@ -125,156 +108,77 @@ fu! VHTL_opensTemplate(line) endwhile endfu -fu! VHTL_closesTemplate(line) - let l:index = 0 - let l:depth = 0 - while v:true - let [l:term, l:index, l:trash] = matchstrpos(a:line, '\Mhtml`\|\\`\|`', l:index) - if (l:index == -1) - return v:false - endif - if (l:term ==# 'html`') - let l:index += len('html`') - let l:depth += 1 - elseif(l:term ==# '`') - let l:index += len('`') - let l:depth -= 1 - if (l:depth < 0) - return v:true - endif +fu! s:StateClass.isInsideLitHtml() dict + for l:syntaxAttribute in reverse(copy(l:self.currSynstack)) + if (l:syntaxAttribute ==# 'litHtmlRegion') + return v:true endif - endwhile -endfu - -fu! VHTL_closesTag(line) - return (-1 != match(a:line, '^\s*<\/')) - " todo: what about
? -endfu - -fu! VHTL_getHtmlTemplateDepthChange(line) - let l:templateOpeners = VHTL_countMatches(a:line, 'html`') - let l:escapedTics = VHTL_countMatches(a:line, '\M\\`') - let l:templateClosers = VHTL_countMatches(a:line, '`') - l:templateOpeners - l:escapedTics - let l:depth = l:templateOpeners - l:templateClosers - return l:depth + endfor + return v:false endfu - -fu! VHTL_countMatches(string, pattern) - let l:count = 0 - let l:lastMatch = -1 - while v:true - let l:lastMatch = match(a:string, a:pattern, l:lastMatch+1) - if (-1 == l:lastMatch) - return l:count - else - let l:count += 1 +fu! s:StateClass.wasInsideLitHtml() dict + for l:syntaxAttribute in reverse(copy(l:self.prevSynstack)) + if (l:syntaxAttribute ==# 'litHtmlRegion') + return v:true endif - endwhile -endfu - -function! s:SynAt(l,c) " from $VIMRUNTIME/indent/javascript.vim - let byte = line2byte(a:l) + a:c - 1 - let pos = index(s:synid_cache[0], byte) - if pos == -1 - let s:synid_cache[:] += [[byte], [synIDattr(synID(a:l, a:c, 0), 'name')]] - endif - return s:synid_cache[1][pos] -endfunction - -if exists('g:VHTL_debugging') - set debug=msg " show errors in indentexpr - fu! SynAt(l,c) - return s:SynAt(a:l,a:c) - endfu -endif -fu! VHTL_debug(str) - if exists('g:VHTL_debugging') - echom a:str - endif -endfu - -let s:StateClass={} -fu! s:StateClass.new() - let l:instance = copy(self) - let l:instance.currLine = v:lnum - let l:instance.prevLine = prevnonblank(v:lnum - 1) - let l:instance.currSynstack = VHTL_SynSOL(l:instance.currLine) - let l:instance.prevSynstack = VHTL_SynEOL(l:instance.prevLine) - return l:instance -endfu - -fu! s:StateClass.startsWithTemplateClose() dict - return (getline(self.currSynstack)) =~# '^\s*`' + endfor + return v:false endfu -fu! s:StateClass.closedJsExpression() dict - return VHTL_closesJsExpression(getline(self.prevLine)) -endfu -fu! s:StateClass.closesJsExpression() dict - return VHTL_closesJsExpression(getline(self.currLine)) -endfu -fu! s:StateClass.openedJsExpression() dict - return (VHTL_getBracketDepthChange(getline(self.prevLine)) > 0) -endfu -fu! s:StateClass.opensLitHtmlTemplate() dict - return VHTL_opensTemplate(getline(self.currLine)) -endfu -fu! s:StateClass.openedLitHtmlTemplate() dict - return VHTL_opensTemplate(getline(self.prevLine)) +fu! s:StateClass.wasHtml() dict + return get(l:self.prevSynstack, -1) =~# '^html' endfu -fu! s:StateClass.closesLitHtmlTemplate() dict - return VHTL_closesTemplate(getline(self.currLine)) +fu! s:StateClass.isHtml() dict + return get(l:self.currSynstack, -1) =~# '^html' endfu -fu! s:StateClass.closedLitHtmlTemplate() dict - return VHTL_closesTemplate(getline(self.prevLine)) +fu! s:StateClass.isLitHtmlRegionCloser() dict + return get(l:self.currSynstack, -1) ==# 'litHtmlRegion' && getline(l:self.currLine) =~# '^\s*`' endfu - -fu! s:StateClass.isInsideLitHtml() dict - return VHTL_isSynstackInsideLitHtml(self.currSynstack) +fu! s:StateClass.opensTemplate() dict + return get(l:self.currSynstack, -1) ==# 'litHtmlRegion' && getline(l:self.currLine) =~# '^\s*html`' endfu -fu! s:StateClass.wasInsideLitHtml() dict - return VHTL_isSynstackInsideLitHtml(self.prevSynstack) +fu! s:StateClass.closedTemplate() dict + return get(l:self.prevSynstack, -1) ==# 'litHtmlRegion' && getline(l:self.prevLine) !~# 'html`$' endfu -fu! s:StateClass.isInsideJsx() dict - return IsSynstackInsideJsx(self.currSynstack) +fu! s:StateClass.wasJs() dict + return get(l:self.prevSynstack, -1) =~# '^js' endfu - -fu! s:StateClass.wasHtml() dict - return get(self.prevSynstack, -1) =~# '^html' +fu! s:StateClass.isJs() dict + return get(l:self.currSynstack, -1) =~# '^js' endfu -fu! s:StateClass.isHtml() dict - return get(self.currSynstack, -1) =~# '^html' +fu! s:StateClass.wasExpressionBracket() dict + return get(l:self.prevSynstack, -1) ==# 'jsTemplateBraces' endfu -fu! s:StateClass.isLitHtmlRegionCloser() dict - return get(self.currSynstack, -1) ==# 'litHtmlRegion' && getline(self.currLine) =~# '^\s*`' +fu! s:StateClass.isExpressionBracket() dict + return get(l:self.currSynstack, -2) ==# 'jsTemplateBraces' endfu -fu! s:StateClass.wasJs() dict - return get(self.prevSynstack, -1) =~# '^js' +fu! s:StateClass.closedExpression() dict + return l:self.wasExpressionBracket() && getline(l:self.prevLine)[-1:-1] ==# '}' endfu -fu! s:StateClass.isJsTemplateBrace() dict - return get(self.currSynstack, -1) ==# 'jsTemplateBraces' +fu! s:StateClass.closesExpression() dict + return l:self.isExpressionBracket() && getline(l:self.currLine)[-1:-1] ==# '}' endfu -fu! s:StateClass.wasJsTemplateBrace() dict - return get(self.prevSynstack, -1) ==# 'jsTemplateBraces' +fu! s:StateClass.openedExpression() dict + return l:self.wasExpressionBracket() && getline(l:self.prevLine)[-1:-1] ==# '{' endfu -fu! s:StateClass.isJs() dict - return get(self.currSynstack, -1) =~# '^js' +fu! s:StateClass.opensExpression() dict + return l:self.isExpressionBracket() && getline(l:self.currLine)[-1:-1] ==# '{' endfu fu! s:StateClass.wasCss() dict - return get(self.prevSynstack, -1) =~# '^css' + return get(l:self.prevSynstack, -1) =~# '^css' endfu fu! s:StateClass.isCss() dict - return get(self.currSynstack, -1) =~# '^css' + return get(l:self.currSynstack, -1) =~# '^css' endfu fu! s:StateClass.toStr() dict - return '{line ' . self.currLine . '}' + return '{line ' . l:self.currLine . '}' endfu fu! s:SkipFuncJsTemplateBraces() " let l:char = getline(line('.'))[col('.')-1] let l:syntax = s:SynAt(line('.'), col('.')) - if (l:syntax != 'jsTemplateBraces') + if (l:syntax !=# 'jsTemplateBraces') return 1 endif endfu @@ -282,7 +186,7 @@ endfu fu! s:SkipFuncLitHtmlRegion() " let l:char = getline(line('.'))[col('.')-1] let l:syntax = s:SynAt(line('.'), col('.')) - if (l:syntax != 'litHtmlRegion') + if (l:syntax !=# 'litHtmlRegion') return 1 endif endfu @@ -313,18 +217,18 @@ fu! s:getCloseWordsLeftToRight(lineNum) endfu fu! s:StateClass.getIndentDelta() dict - let l:closeWords = s:getCloseWordsLeftToRight(self.currLine) + let l:closeWords = s:getCloseWordsLeftToRight(l:self.currLine) if len(l:closeWords) == 0 return 0 endif let [l:closeWord, l:col] = l:closeWords[0] - let l:syntax = s:SynAt(self.currLine, l:col) - if (l:syntax == 'htmlEndTag') - call VHTL_debug('indent_delta: html end tag') + let l:syntax = s:SynAt(l:self.currLine, l:col) + if (l:syntax ==# 'htmlEndTag') + call s:debug('indent_delta: html end tag') return - &shiftwidth endif - if (l:syntax == 'litHtmlRegion' && 'html`' != strpart(getline(self.currLine), l:col-5, len('html`'))) - call VHTL_debug('indent_delta: end of litHtmlRegion') + if (l:syntax ==# 'litHtmlRegion' && 'html`' !=# strpart(getline(l:self.currLine), l:col-5, len('html`'))) + call s:debug('indent_delta: end of litHtmlRegion') return - &shiftwidth endif return 0 @@ -333,29 +237,30 @@ endfu " html tag, html template, or js expression on previous line fu! s:StateClass.getIndentOfLastClose() dict - let l:closeWords = s:getCloseWordsLeftToRight(self.prevLine) + let l:closeWords = s:getCloseWordsLeftToRight(l:self.prevLine) if (len(l:closeWords) == 0) + call s:debug('no close words found') return -1 endif for l:item in reverse(l:closeWords) let [l:closeWord, l:col] = l:item - let l:syntax = s:SynAt(self.prevLine, l:col) - call cursor(self.prevLine, l:col) " sets start point for searchpair() + let l:syntax = s:SynAt(l:self.prevLine, l:col) + call cursor(l:self.prevLine, l:col) " sets start point for searchpair() redraw - if ("}" == l:closeWord && l:syntax == 'jsTemplateBraces') + if ('}' ==# l:closeWord && l:syntax ==# 'jsTemplateBraces') call searchpair('{', '', '}', 'b', 's:SkipFuncJsTemplateBraces()') - call VHTL_debug('js brace base indent') - elseif ("`" == l:closeWord && l:syntax == 'litHtmlRegion') + call s:debug('js brace base indent') + elseif ('`' ==# l:closeWord && l:syntax ==# 'litHtmlRegion') call searchpair('html`', '', '\(html\)\@', '', '') call searchpair(l:openWord, '', l:closeWord, 'b') - call VHTL_debug('html tag region base indent ') + call s:debug('html tag region base indent ') else - call VHTL_debug("UNRECOGNIZED CLOSER SYNTAX: '" . l:syntax . "'") + call s:debug("UNRECOGNIZED CLOSER SYNTAX: '" . l:syntax . "'") endif return indent(line('.')) " cursor was moved by searchpair() endfor @@ -368,16 +273,13 @@ endfu fu! ComputeLitHtmlIndent() let s:synid_cache = [[],[]] - let l:state = s:StateClass.new() + let l:state = s:StateClass.new(v:lnum) " get most recent non-empty line let l:prev_lnum = prevnonblank(v:lnum - 1) - let l:currLineSynstack = VHTL_SynSOL(v:lnum) - let l:prevLineSynstack = VHTL_SynEOL(l:prev_lnum) - if (!l:state.isInsideLitHtml() && !l:state.wasInsideLitHtml()) - call VHTL_debug('outside of litHtmlRegion: ' . b:litHtmlOriginalIndentExpression) + call s:debug('outside of litHtmlRegion: ' . b:litHtmlOriginalIndentExpression) if (exists('b:hi_indent') && has_key(b:hi_indent, 'blocklnr')) call remove(b:hi_indent, 'blocklnr') @@ -390,33 +292,42 @@ fu! ComputeLitHtmlIndent() endif if (l:state.openedLitHtmlTemplate()) - call VHTL_debug('opened tagged template literal') - return indent(l:prev_lnum) + &shiftwidth + call s:debug('opened html template literal') + if (getline(l:state.currLine) =~# '^\s*`') + " The first character closes template on previous line. This is a tiny + " but very common edge case when typing out a new template from scratch. + call s:debug('closes html template literal in first character') + return indent(l:prev_lnum) + else + call s:debug('first line of template is always indented') + return indent(l:prev_lnum) + &shiftwidth + endif endif - if (l:state.openedJsExpression()) - call VHTL_debug('opened js expression') + if (l:state.openedExpression()) + call s:debug('opened js expression') return indent(l:prev_lnum) + &shiftwidth endif - if (l:state.wasJsTemplateBrace() || l:state.isLitHtmlRegionCloser()) + if (l:state.closedExpression() || l:state.isLitHtmlRegionCloser()) " let l:indent_basis = previous matching js or template start " let l:indent_delta = -1 for starting with closing tag, template, or expression let l:indent_basis = l:state.getIndentOfLastClose() if (l:indent_basis == -1) - call VHTL_debug("default to html indent because base indent not found") - return HtmlIndent() + call s:debug('using html indent as base indent') + let l:indent_basis = HtmlIndent() endif let l:indent_delta = l:state.getIndentDelta() - call VHTL_debug('indent delta ' . l:indent_delta) - call VHTL_debug('indent basis ' . l:indent_basis) + call s:debug('indent delta ' . l:indent_delta) + call s:debug('indent basis ' . l:indent_basis) return l:indent_basis + l:indent_delta endif - if ((l:state.wasJs() && !l:state.wasJsTemplateBrace()) && (l:state.isJs() && !l:state.isJsTemplateBrace())) + if (((l:state.wasJs() && !l:state.closedExpression()) || l:state.closedTemplate()) && ((l:state.isJs() || l:state.opensTemplate()))) + call s:debug('using javascript indent') return eval(b:litHtmlOriginalIndentExpression) endif - call VHTL_debug('default to html indent') + call s:debug('default to html indent') return HtmlIndent() endfu diff --git a/test/lit-html.vader b/test/example-code-tests/example-code-tests.vader similarity index 68% rename from test/lit-html.vader rename to test/example-code-tests/example-code-tests.vader index 16e1086..3d61ad7 100644 --- a/test/lit-html.vader +++ b/test/example-code-tests/example-code-tests.vader @@ -1,3 +1,7 @@ +" This test file is not used for regular testing because the tests here are +" generally very large in scope and thus not helpful in telling you what's +" actually failing. + Before: set tabstop=2 set shiftwidth=2 @@ -285,3 +289,81 @@ Expect javascript: " ${items.map((i) => html`