From 7e2f8a4191ed5c4fe516a1e574c47d28b7a6fc90 Mon Sep 17 00:00:00 2001 From: dman777 Date: Wed, 15 Aug 2018 20:32:46 -0500 Subject: [PATCH 1/8] html.vim indention --- after/indent/javascript.vim | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/after/indent/javascript.vim b/after/indent/javascript.vim index 51add1c..6a30642 100644 --- a/after/indent/javascript.vim +++ b/after/indent/javascript.vim @@ -252,19 +252,10 @@ fu! ComputeLitHtmlIndent() call VHTL_debug('closes expression and tag') endif return l:result - endif - - let l:isJsx = (IsSynstackInsideJsx(l:currLineSynstack)) - if (l:wasCss || l:isCss || l:wasHtml || l:isHtml) && !l:isJsx - call VHTL_debug('html indent ' . l:adjustForClosingBracket) - return HtmlIndent() + l:adjustForClosingBracket - endif - - if len(b:litHtmlOriginalIndentExpression) - call VHTL_debug('js indent ' . b:litHtmlOriginalIndentExpression) + elseif (l:isJs && l:wasJs) return eval(b:litHtmlOriginalIndentExpression) - else - call VHTL_debug('cindent should never happen') - return cindent(v:lnum) endif + + call VHTL_debug('defaulting to html indent') + return HtmlIndent() endfu From 79738fc1186efcf2ef4d16176717479ddaba409b Mon Sep 17 00:00:00 2001 From: Jon Smithers Date: Thu, 16 Aug 2018 12:08:26 -0400 Subject: [PATCH 2/8] Add some more tests --- test/lit-html.vader | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/test/lit-html.vader b/test/lit-html.vader index a7ba056..7052dea 100644 --- a/test/lit-html.vader +++ b/test/lit-html.vader @@ -213,6 +213,48 @@ Expect javascript: ` +Given javascript (adjacent js expressions): + html` + ${console.log(null)} + ${console.log('this line should have equal indentation')} + ` +Do: + =G + +Expect javascript: + html` + ${console.log(null)} + ${console.log('this line should have equal indentation')} + ` + +Given javascript(multiple dedents after closing js expression): + html` +
+ ${ test + ? html`

one

` + : html` +

+ two +

`} +
+ dedent THRICE here + `; + +Do: + =G + +Expect javascript: + html` +
+ ${ test + ? html`

one

` + : html` +

+ two +

`} +
+ dedent THRICE here + `; " " This test fails because vim-javascript doesn't correctly indent ternaries inside ${ } " Given javascript (subsequent templates): From 52a04d699f77f3dcaa44c030511723465ceef7e4 Mon Sep 17 00:00:00 2001 From: Jon Smithers Date: Thu, 16 Aug 2018 12:09:22 -0400 Subject: [PATCH 3/8] Start on huge rewrite --- after/indent/javascript.vim | 115 +++++++++++++++++++++++++++--------- 1 file changed, 88 insertions(+), 27 deletions(-) diff --git a/after/indent/javascript.vim b/after/indent/javascript.vim index 6a30642..914d1ac 100644 --- a/after/indent/javascript.vim +++ b/after/indent/javascript.vim @@ -181,9 +181,87 @@ fu! VHTL_debug(str) endif 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 + +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*`' +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.opensLitHtmlTemplate() dict + return VHTL_opensTemplate(getline(self.currLine)) +endfu +fu! s:StateClass.openedLitHtmlTemplate() dict + return VHTL_opensTemplate(getline(self.prevLine)) +endfu +fu! s:StateClass.closesLitHtmlTemplate() dict + return VHTL_closesTemplate(getline(self.curLine)) +endfu +fu! s:StateClass.closedLitHtmlTemplate() dict + return VHTL_closesTemplate(getline(self.prevLine)) +endfu + +fu! s:StateClass.isInsideLitHtml() dict + return VHTL_isSynstackInsideLitHtml(self.currSynstack) +endfu +fu! s:StateClass.wasInsideLitHtml() dict + return VHTL_isSynstackInsideLitHtml(self.prevSynstack) +endfu +fu! s:StateClass.isInsideJsx() dict + return IsSynstackInsideJsx(self.currSynstack) +endfu + +fu! s:StateClass.wasHtml() dict + return get(self.prevSynstack, -1) =~# '^html' +endfu +fu! s:StateClass.isHtml() dict + return get(self.currSynstack, -1) =~# '^html' +endfu +fu! s:StateClass.wasJs() dict + return get(self.prevSynstack, -1) =~# '^js' +endfu +fu! s:StateClass.isJs() dict + return get(self.currSynstack, -1) =~# '^js' +endfu +fu! s:StateClass.wasCss() dict + return get(self.prevSynstack, -1) =~# '^css' +endfu +fu! s:StateClass.isCss() dict + return get(self.currSynstack, -1) =~# '^css' +endfu + +fu! s:StateClass.toStr() dict + return '{line ' . self.currLine . '}' +endfu + " Dispatch to indent method for js/html (use custom rules for transitions " between syntaxes) fu! ComputeLitHtmlIndent() + let s:synid_cache = [[],[]] + + let l:state = s:StateClass.new() " get most recent non-empty line let l:prev_lnum = prevnonblank(v:lnum - 1) @@ -191,41 +269,23 @@ fu! ComputeLitHtmlIndent() let l:currLineSynstack = VHTL_SynSOL(v:lnum) let l:prevLineSynstack = VHTL_SynEOL(l:prev_lnum) - if (!VHTL_isSynstackInsideLitHtml(l:currLineSynstack) && !VHTL_isSynstackInsideLitHtml(l:prevLineSynstack)) + if (!l:state.isInsideLitHtml() && !l:state.wasInsideLitHtml()) call VHTL_debug('outside of litHtmlRegion') return eval(b:litHtmlOriginalIndentExpression) endif - - let l:wasCss = (IsSynstackCss(l:prevLineSynstack)) + " THIS ALGORITHM MIGHT ACTUALLY WORK + " let l:indent_basis = previous matching js or template start, otherwise equal to previous line + " let l:indent_delta = -1 for starting with closing tag, template, or expression " We add an extra dedent for closing } brackets, as long as the matching { " opener is not on the same line as an opening html`. " " This algorithm does not always work and must be rewritten (hopefully to " something simpler) - let l:adjustForClosingBracket = 0 - " if (!l:wasCss && VHTL_closesJsExpression(getline(l:prev_lnum))) - " :exec 'normal! ' . l:prev_lnum . 'G0[{' - " let l:lineWithOpenBracket = getline(line('.')) - " if (!VHTL_opensTemplate(l:lineWithOpenBracket)) - " call VHTL_debug('adjusting for close bracket') - " let l:adjustForClosingBracket = - &shiftwidth - " endif - " endif - - let l:wasHtml = (IsSynstackHtml(l:prevLineSynstack)) - let l:isHtml = (IsSynstackHtml(l:currLineSynstack)) - let l:wasCss = (IsSynstackCss(l:prevLineSynstack)) - let l:isCss = (IsSynstackCss(l:currLineSynstack)) - let l:wasJs = (IsSynstackJs(l:prevLineSynstack)) - let l:isJs = (IsSynstackJs(l:currLineSynstack)) - - " If a line starts with template close, it is dedented. If a line otherwise - " contains a template close, the NEXT line is dedented. Note that template - " closers can be balanced out by template openers. - if (VHTL_startsWithTemplateEnd(v:lnum)) - call VHTL_debug('closed template at start ') + " + if (l:state.closesLitHtmlTemplate()) + call VHTL_debug('closed template') let l:result = indent(l:prev_lnum) - &shiftwidth if (VHTL_closesJsExpression(getline(l:prev_lnum))) call VHTL_debug('closed template at start and js expression') @@ -233,10 +293,11 @@ fu! ComputeLitHtmlIndent() endif return l:result endif - if (VHTL_opensTemplate(getline(l:prev_lnum))) + if (l:state.openedLitHtmlTemplate()) call VHTL_debug('opened template') return indent(l:prev_lnum) + &shiftwidth elseif (VHTL_closesTemplate(getline(l:prev_lnum)) && !VHTL_startsWithTemplateEnd(l:prev_lnum)) + " elseif (l:state.closedLitHtmlTemplate() && !l:state.closesLitHtmlTemplate()) call VHTL_debug('closed template ' . l:adjustForClosingBracket) let l:result = indent(l:prev_lnum) - &shiftwidth + l:adjustForClosingBracket if (VHTL_closesTag(getline(v:lnum))) @@ -244,7 +305,7 @@ fu! ComputeLitHtmlIndent() let l:result -= &shiftwidth endif return l:result - elseif (l:isHtml && l:wasJs && VHTL_closesJsExpression(getline(l:prev_lnum))) + elseif (l:state.isHtml() && l:state.wasJs() && VHTL_closesJsExpression(getline(l:prev_lnum))) let l:result = indent(l:prev_lnum) - &shiftwidth call VHTL_debug('closes expression') if (VHTL_closesTag(getline(v:lnum))) From 805a3c1fc69e7719e79fdb286c74252f2ee63767 Mon Sep 17 00:00:00 2001 From: Jon Smithers Date: Sat, 18 Aug 2018 20:20:39 -0400 Subject: [PATCH 4/8] Make a bunch of changes and break everything --- after/indent/javascript.vim | 119 +++++++++++++++++++++++++++++++++--- 1 file changed, 109 insertions(+), 10 deletions(-) diff --git a/after/indent/javascript.vim b/after/indent/javascript.vim index 914d1ac..85d0168 100644 --- a/after/indent/javascript.vim +++ b/after/indent/javascript.vim @@ -172,15 +172,6 @@ fu! VHTL_countMatches(string, pattern) endwhile endfu -if exists('g:VHTL_debugging') - set debug=msg " show errors in indentexpr -endif -fu! VHTL_debug(str) - if exists('g:VHTL_debugging') - echom a:str - endif -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) @@ -190,6 +181,18 @@ function! s:SynAt(l,c) " from $VIMRUNTIME/indent/javascript.vim 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) @@ -210,6 +213,9 @@ 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 @@ -217,7 +223,7 @@ fu! s:StateClass.openedLitHtmlTemplate() dict return VHTL_opensTemplate(getline(self.prevLine)) endfu fu! s:StateClass.closesLitHtmlTemplate() dict - return VHTL_closesTemplate(getline(self.curLine)) + return VHTL_closesTemplate(getline(self.currLine)) endfu fu! s:StateClass.closedLitHtmlTemplate() dict return VHTL_closesTemplate(getline(self.prevLine)) @@ -256,6 +262,75 @@ fu! s:StateClass.toStr() dict return '{line ' . self.currLine . '}' endfu +fu! s:SkipFuncJsTemplateBraces() + " let l:char = getline(line('.'))[col('.')-1] + let l:syntax = s:SynAt(line('.'), col('.')) + if (l:syntax != 'jsTemplateBraces') + echom 'SKIP YES because ' . l:syntax + return 1 + endif +endfu + +fu! s:SkipFuncLitHtmlRegion() + " let l:char = getline(line('.'))[col('.')-1] + let l:syntax = s:SynAt(line('.'), col('.')) + if (l:syntax != 'litHtmlRegion') + echom 'SKIP YES because ' . l:syntax + return 1 + endif +endfu + + +" html tag, html template, or js expression on previous line +fu! s:StateClass.getIndentOfLastClose() dict + let l:line = getline(self.prevLine) + + " The following regex converts a line to purely a list of closing words. + " Pretty cool but not useful + " echo split(getline(62), '.\{-}\ze\(}\|`\|<\/\w\+>\)') + + let l:anyCloserWord = '}\|`\|<\/\w\+>' + + + let l:index = 0 + let l:closeWords = [] + while v:true + let [l:term, l:index, l:trash] = matchstrpos(l:line, l:anyCloserWord, l:index) + if (l:index == -1) + break + else + call add(l:closeWords, [l:term, l:index]) + endif + let l:index += 1 + endwhile + + for l:item in reverse(l:closeWords) + let [l:closeWord, l:col] = l:item + let l:col += 1 + let l:syntax = s:SynAt(self.prevLine, l:col) + call cursor(self.prevLine, l:col) " sets start point for searchpair() + redraw + if ("}" == l:closeWord && l:syntax == 'jsTemplateBraces') + call searchpair('{', '', '}', 'b', 's:SkipFuncJsTemplateBraces()') + echom 'JS BRACE BASE INDENT ' + elseif ("`" == l:closeWord && l:syntax == 'litHtmlRegion') + call searchpair('html`', '', '\(html\)\@', '', '') + echom 'open word ' . l:openWord + call searchpair(l:openWord, '', l:closeWord, 'b') + echom 'HTML TAG REGION BASE INDENT ' + else + echom "UNRECOGNIZED CLOSER SYNTAX: '" . l:syntax . "'" + echom getline(line('.')) + endif + return indent(line('.')) " cursor was moved by searchpair() + endfor +endfu + +" com! MyTest exec "call s:StateClass.new().getIndentOfLastClose()" + " Dispatch to indent method for js/html (use custom rules for transitions " between syntaxes) fu! ComputeLitHtmlIndent() @@ -274,10 +349,34 @@ fu! ComputeLitHtmlIndent() return eval(b:litHtmlOriginalIndentExpression) endif + if (l:state.wasJs() && l:state.isJs()) + call VHTL_debug('default javascript indentation inside lit-html region') + return eval(b:litHtmlOriginalIndentExpression) + endif + + if (l:state.openedLitHtmlTemplate()) + call VHTL_debug('opened tagged template literal') + return indent(l:prev_lnum) + &shiftwidth + endif + + if (l:state.openedJsExpression()) + call VHTL_debug('opened js expression') + return indent(l:prev_lnum) + &shiftwidth + endif + + + + " lit, js, html, css + " THIS ALGORITHM MIGHT ACTUALLY WORK " let l:indent_basis = previous matching js or template start, otherwise equal to previous line " let l:indent_delta = -1 for starting with closing tag, template, or expression + let l:base_indent = l:state.getIndentOfLastClose() + echom 'base indent ' . l:base_indent + return l:base_indent + + " We add an extra dedent for closing } brackets, as long as the matching { " opener is not on the same line as an opening html`. " From daefd88c2b0884309a14476945816850fe340787 Mon Sep 17 00:00:00 2001 From: Jon Smithers Date: Thu, 23 Aug 2018 13:46:39 -0400 Subject: [PATCH 5/8] Finish up reasonably-working major refactor --- after/indent/javascript.vim | 101 ++++++++++++++++++++++++++++-------- 1 file changed, 78 insertions(+), 23 deletions(-) diff --git a/after/indent/javascript.vim b/after/indent/javascript.vim index 85d0168..c7f0f10 100644 --- a/after/indent/javascript.vim +++ b/after/indent/javascript.vim @@ -245,9 +245,18 @@ endfu fu! s:StateClass.isHtml() dict return get(self.currSynstack, -1) =~# '^html' endfu +fu! s:StateClass.isLitHtmlRegionCloser() dict + return get(self.currSynstack, -1) ==# 'litHtmlRegion' && getline(self.currLine) =~# '^\s*`' +endfu fu! s:StateClass.wasJs() dict return get(self.prevSynstack, -1) =~# '^js' endfu +fu! s:StateClass.isJsTemplateBrace() dict + return get(self.currSynstack, -1) ==# 'jsTemplateBraces' +endfu +fu! s:StateClass.wasJsTemplateBrace() dict + return get(self.prevSynstack, -1) ==# 'jsTemplateBraces' +endfu fu! s:StateClass.isJs() dict return get(self.currSynstack, -1) =~# '^js' endfu @@ -266,7 +275,6 @@ fu! s:SkipFuncJsTemplateBraces() " let l:char = getline(line('.'))[col('.')-1] let l:syntax = s:SynAt(line('.'), col('.')) if (l:syntax != 'jsTemplateBraces') - echom 'SKIP YES because ' . l:syntax return 1 endif endfu @@ -275,15 +283,12 @@ fu! s:SkipFuncLitHtmlRegion() " let l:char = getline(line('.'))[col('.')-1] let l:syntax = s:SynAt(line('.'), col('.')) if (l:syntax != 'litHtmlRegion') - echom 'SKIP YES because ' . l:syntax return 1 endif endfu - -" html tag, html template, or js expression on previous line -fu! s:StateClass.getIndentOfLastClose() dict - let l:line = getline(self.prevLine) +fu! s:getCloseWordsLeftToRight(lineNum) + let l:line = getline(a:lineNum) " The following regex converts a line to purely a list of closing words. " Pretty cool but not useful @@ -299,31 +304,58 @@ fu! s:StateClass.getIndentOfLastClose() dict if (l:index == -1) break else - call add(l:closeWords, [l:term, l:index]) + let l:col = l:index + 1 + call add(l:closeWords, [l:term, l:col]) endif let l:index += 1 endwhile + return l:closeWords +endfu + +fu! s:StateClass.getIndentDelta() dict + let l:closeWords = s:getCloseWordsLeftToRight(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('delta_indent: html end tag') + return - &shiftwidth + endif + if (l:syntax == 'litHtmlRegion' && 'html`' != strpart(getline(self.currLine), l:col-5, len('html`'))) + call VHTL_debug('delta_indent: end of litHtmlRegion') + return - &shiftwidth + endif + return 0 +endfu + +" html tag, html template, or js expression on previous line +fu! s:StateClass.getIndentOfLastClose() dict + + let l:closeWords = s:getCloseWordsLeftToRight(self.prevLine) + + if (len(l:closeWords) == 0) + return -1 + endif for l:item in reverse(l:closeWords) let [l:closeWord, l:col] = l:item - let l:col += 1 let l:syntax = s:SynAt(self.prevLine, l:col) call cursor(self.prevLine, l:col) " sets start point for searchpair() redraw if ("}" == l:closeWord && l:syntax == 'jsTemplateBraces') call searchpair('{', '', '}', 'b', 's:SkipFuncJsTemplateBraces()') - echom 'JS BRACE BASE INDENT ' + call VHTL_debug('js brace base indent') elseif ("`" == l:closeWord && l:syntax == 'litHtmlRegion') call searchpair('html`', '', '\(html\)\@', '', '') - echom 'open word ' . l:openWord call searchpair(l:openWord, '', l:closeWord, 'b') - echom 'HTML TAG REGION BASE INDENT ' + call VHTL_debug('html tag region base indent ') else - echom "UNRECOGNIZED CLOSER SYNTAX: '" . l:syntax . "'" - echom getline(line('.')) + call VHTL_debug("UNRECOGNIZED CLOSER SYNTAX: '" . l:syntax . "'") endif return indent(line('.')) " cursor was moved by searchpair() endfor @@ -345,12 +377,15 @@ fu! ComputeLitHtmlIndent() let l:prevLineSynstack = VHTL_SynEOL(l:prev_lnum) if (!l:state.isInsideLitHtml() && !l:state.wasInsideLitHtml()) - call VHTL_debug('outside of litHtmlRegion') - return eval(b:litHtmlOriginalIndentExpression) - endif - - if (l:state.wasJs() && l:state.isJs()) - call VHTL_debug('default javascript indentation inside lit-html region') + call VHTL_debug('outside of litHtmlRegion: ' . b:litHtmlOriginalIndentExpression) + + if (exists('b:hi_indent') && has_key(b:hi_indent, 'blocklnr')) + call remove(b:hi_indent, 'blocklnr') + " This avoids a really weird behavior when indenting first line inside + " style tag and then indenting any normal javascript outside of + " lit-html region. 'blocklnr' is assigned to line number of