Skip to content

Commit dd511a4

Browse files
authored
Merge pull request #7 from jonsmithers/indentation-refactor
Refactor indentation
2 parents bb52e00 + 2373afa commit dd511a4

File tree

2 files changed

+260
-66
lines changed

2 files changed

+260
-66
lines changed

after/indent/javascript.vim

Lines changed: 217 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -172,99 +172,251 @@ fu! VHTL_countMatches(string, pattern)
172172
endwhile
173173
endfu
174174

175+
function! s:SynAt(l,c) " from $VIMRUNTIME/indent/javascript.vim
176+
let byte = line2byte(a:l) + a:c - 1
177+
let pos = index(s:synid_cache[0], byte)
178+
if pos == -1
179+
let s:synid_cache[:] += [[byte], [synIDattr(synID(a:l, a:c, 0), 'name')]]
180+
endif
181+
return s:synid_cache[1][pos]
182+
endfunction
183+
175184
if exists('g:VHTL_debugging')
176185
set debug=msg " show errors in indentexpr
186+
fu! SynAt(l,c)
187+
return s:SynAt(a:l,a:c)
188+
endfu
177189
endif
178190
fu! VHTL_debug(str)
179191
if exists('g:VHTL_debugging')
180192
echom a:str
181193
endif
182194
endfu
183195

196+
let s:StateClass={}
197+
fu! s:StateClass.new()
198+
let l:instance = copy(self)
199+
let l:instance.currLine = v:lnum
200+
let l:instance.prevLine = prevnonblank(v:lnum - 1)
201+
let l:instance.currSynstack = VHTL_SynSOL(l:instance.currLine)
202+
let l:instance.prevSynstack = VHTL_SynEOL(l:instance.prevLine)
203+
return l:instance
204+
endfu
205+
206+
fu! s:StateClass.startsWithTemplateClose() dict
207+
return (getline(self.currSynstack)) =~# '^\s*`'
208+
endfu
209+
210+
fu! s:StateClass.closedJsExpression() dict
211+
return VHTL_closesJsExpression(getline(self.prevLine))
212+
endfu
213+
fu! s:StateClass.closesJsExpression() dict
214+
return VHTL_closesJsExpression(getline(self.currLine))
215+
endfu
216+
fu! s:StateClass.openedJsExpression() dict
217+
return (VHTL_getBracketDepthChange(getline(self.prevLine)) > 0)
218+
endfu
219+
fu! s:StateClass.opensLitHtmlTemplate() dict
220+
return VHTL_opensTemplate(getline(self.currLine))
221+
endfu
222+
fu! s:StateClass.openedLitHtmlTemplate() dict
223+
return VHTL_opensTemplate(getline(self.prevLine))
224+
endfu
225+
fu! s:StateClass.closesLitHtmlTemplate() dict
226+
return VHTL_closesTemplate(getline(self.currLine))
227+
endfu
228+
fu! s:StateClass.closedLitHtmlTemplate() dict
229+
return VHTL_closesTemplate(getline(self.prevLine))
230+
endfu
231+
232+
fu! s:StateClass.isInsideLitHtml() dict
233+
return VHTL_isSynstackInsideLitHtml(self.currSynstack)
234+
endfu
235+
fu! s:StateClass.wasInsideLitHtml() dict
236+
return VHTL_isSynstackInsideLitHtml(self.prevSynstack)
237+
endfu
238+
fu! s:StateClass.isInsideJsx() dict
239+
return IsSynstackInsideJsx(self.currSynstack)
240+
endfu
241+
242+
fu! s:StateClass.wasHtml() dict
243+
return get(self.prevSynstack, -1) =~# '^html'
244+
endfu
245+
fu! s:StateClass.isHtml() dict
246+
return get(self.currSynstack, -1) =~# '^html'
247+
endfu
248+
fu! s:StateClass.isLitHtmlRegionCloser() dict
249+
return get(self.currSynstack, -1) ==# 'litHtmlRegion' && getline(self.currLine) =~# '^\s*`'
250+
endfu
251+
fu! s:StateClass.wasJs() dict
252+
return get(self.prevSynstack, -1) =~# '^js'
253+
endfu
254+
fu! s:StateClass.isJsTemplateBrace() dict
255+
return get(self.currSynstack, -1) ==# 'jsTemplateBraces'
256+
endfu
257+
fu! s:StateClass.wasJsTemplateBrace() dict
258+
return get(self.prevSynstack, -1) ==# 'jsTemplateBraces'
259+
endfu
260+
fu! s:StateClass.isJs() dict
261+
return get(self.currSynstack, -1) =~# '^js'
262+
endfu
263+
fu! s:StateClass.wasCss() dict
264+
return get(self.prevSynstack, -1) =~# '^css'
265+
endfu
266+
fu! s:StateClass.isCss() dict
267+
return get(self.currSynstack, -1) =~# '^css'
268+
endfu
269+
270+
fu! s:StateClass.toStr() dict
271+
return '{line ' . self.currLine . '}'
272+
endfu
273+
274+
fu! s:SkipFuncJsTemplateBraces()
275+
" let l:char = getline(line('.'))[col('.')-1]
276+
let l:syntax = s:SynAt(line('.'), col('.'))
277+
if (l:syntax != 'jsTemplateBraces')
278+
return 1
279+
endif
280+
endfu
281+
282+
fu! s:SkipFuncLitHtmlRegion()
283+
" let l:char = getline(line('.'))[col('.')-1]
284+
let l:syntax = s:SynAt(line('.'), col('.'))
285+
if (l:syntax != 'litHtmlRegion')
286+
return 1
287+
endif
288+
endfu
289+
290+
fu! s:getCloseWordsLeftToRight(lineNum)
291+
let l:line = getline(a:lineNum)
292+
293+
" The following regex converts a line to purely a list of closing words.
294+
" Pretty cool but not useful
295+
" echo split(getline(62), '.\{-}\ze\(}\|`\|<\/\w\+>\)')
296+
297+
let l:anyCloserWord = '}\|`\|<\/\w\+>'
298+
299+
300+
let l:index = 0
301+
let l:closeWords = []
302+
while v:true
303+
let [l:term, l:index, l:trash] = matchstrpos(l:line, l:anyCloserWord, l:index)
304+
if (l:index == -1)
305+
break
306+
else
307+
let l:col = l:index + 1
308+
call add(l:closeWords, [l:term, l:col])
309+
endif
310+
let l:index += 1
311+
endwhile
312+
return l:closeWords
313+
endfu
314+
315+
fu! s:StateClass.getIndentDelta() dict
316+
let l:closeWords = s:getCloseWordsLeftToRight(self.currLine)
317+
if len(l:closeWords) == 0
318+
return 0
319+
endif
320+
let [l:closeWord, l:col] = l:closeWords[0]
321+
let l:syntax = s:SynAt(self.currLine, l:col)
322+
if (l:syntax == 'htmlEndTag')
323+
call VHTL_debug('indent_delta: html end tag')
324+
return - &shiftwidth
325+
endif
326+
if (l:syntax == 'litHtmlRegion' && 'html`' != strpart(getline(self.currLine), l:col-5, len('html`')))
327+
call VHTL_debug('indent_delta: end of litHtmlRegion')
328+
return - &shiftwidth
329+
endif
330+
return 0
331+
endfu
332+
333+
" html tag, html template, or js expression on previous line
334+
fu! s:StateClass.getIndentOfLastClose() dict
335+
336+
let l:closeWords = s:getCloseWordsLeftToRight(self.prevLine)
337+
338+
if (len(l:closeWords) == 0)
339+
return -1
340+
endif
341+
342+
for l:item in reverse(l:closeWords)
343+
let [l:closeWord, l:col] = l:item
344+
let l:syntax = s:SynAt(self.prevLine, l:col)
345+
call cursor(self.prevLine, l:col) " sets start point for searchpair()
346+
redraw
347+
if ("}" == l:closeWord && l:syntax == 'jsTemplateBraces')
348+
call searchpair('{', '', '}', 'b', 's:SkipFuncJsTemplateBraces()')
349+
call VHTL_debug('js brace base indent')
350+
elseif ("`" == l:closeWord && l:syntax == 'litHtmlRegion')
351+
call searchpair('html`', '', '\(html\)\@<!`', 'b', 's:SkipFuncLitHtmlRegion()')
352+
call VHTL_debug('lit html region base indent ')
353+
elseif (l:syntax == 'htmlEndTag')
354+
let l:openWord = substitute(substitute(l:closeWord, '/', '', ''), '>', '', '')
355+
call searchpair(l:openWord, '', l:closeWord, 'b')
356+
call VHTL_debug('html tag region base indent ')
357+
else
358+
call VHTL_debug("UNRECOGNIZED CLOSER SYNTAX: '" . l:syntax . "'")
359+
endif
360+
return indent(line('.')) " cursor was moved by searchpair()
361+
endfor
362+
endfu
363+
364+
" com! MyTest exec "call s:StateClass.new().getIndentOfLastClose()"
365+
184366
" Dispatch to indent method for js/html (use custom rules for transitions
185367
" between syntaxes)
186368
fu! ComputeLitHtmlIndent()
369+
let s:synid_cache = [[],[]]
370+
371+
let l:state = s:StateClass.new()
187372

188373
" get most recent non-empty line
189374
let l:prev_lnum = prevnonblank(v:lnum - 1)
190375

191376
let l:currLineSynstack = VHTL_SynSOL(v:lnum)
192377
let l:prevLineSynstack = VHTL_SynEOL(l:prev_lnum)
193378

194-
if (!VHTL_isSynstackInsideLitHtml(l:currLineSynstack) && !VHTL_isSynstackInsideLitHtml(l:prevLineSynstack))
195-
call VHTL_debug('outside of litHtmlRegion')
379+
if (!l:state.isInsideLitHtml() && !l:state.wasInsideLitHtml())
380+
call VHTL_debug('outside of litHtmlRegion: ' . b:litHtmlOriginalIndentExpression)
381+
382+
if (exists('b:hi_indent') && has_key(b:hi_indent, 'blocklnr'))
383+
call remove(b:hi_indent, 'blocklnr')
384+
" This avoids a really weird behavior when indenting first line inside
385+
" style tag and then indenting any normal javascript outside of
386+
" lit-html region. 'blocklnr' is assigned to line number of <style>,
387+
" which is then assigned to 'nest' inside vim-javascript's indent code.
388+
endif
196389
return eval(b:litHtmlOriginalIndentExpression)
197390
endif
198391

199-
200-
let l:wasCss = (IsSynstackCss(l:prevLineSynstack))
201-
202-
" We add an extra dedent for closing } brackets, as long as the matching {
203-
" opener is not on the same line as an opening html`.
204-
"
205-
" This algorithm does not always work and must be rewritten (hopefully to
206-
" something simpler)
207-
let l:adjustForClosingBracket = 0
208-
" if (!l:wasCss && VHTL_closesJsExpression(getline(l:prev_lnum)))
209-
" :exec 'normal! ' . l:prev_lnum . 'G0[{'
210-
" let l:lineWithOpenBracket = getline(line('.'))
211-
" if (!VHTL_opensTemplate(l:lineWithOpenBracket))
212-
" call VHTL_debug('adjusting for close bracket')
213-
" let l:adjustForClosingBracket = - &shiftwidth
214-
" endif
215-
" endif
216-
217-
let l:wasHtml = (IsSynstackHtml(l:prevLineSynstack))
218-
let l:isHtml = (IsSynstackHtml(l:currLineSynstack))
219-
let l:wasCss = (IsSynstackCss(l:prevLineSynstack))
220-
let l:isCss = (IsSynstackCss(l:currLineSynstack))
221-
let l:wasJs = (IsSynstackJs(l:prevLineSynstack))
222-
let l:isJs = (IsSynstackJs(l:currLineSynstack))
223-
224-
" If a line starts with template close, it is dedented. If a line otherwise
225-
" contains a template close, the NEXT line is dedented. Note that template
226-
" closers can be balanced out by template openers.
227-
if (VHTL_startsWithTemplateEnd(v:lnum))
228-
call VHTL_debug('closed template at start ')
229-
let l:result = indent(l:prev_lnum) - &shiftwidth
230-
if (VHTL_closesJsExpression(getline(l:prev_lnum)))
231-
call VHTL_debug('closed template at start and js expression')
232-
let l:result -= &shiftwidth
233-
endif
234-
return l:result
392+
if (l:state.openedLitHtmlTemplate())
393+
call VHTL_debug('opened tagged template literal')
394+
return indent(l:prev_lnum) + &shiftwidth
235395
endif
236-
if (VHTL_opensTemplate(getline(l:prev_lnum)))
237-
call VHTL_debug('opened template')
396+
397+
if (l:state.openedJsExpression())
398+
call VHTL_debug('opened js expression')
238399
return indent(l:prev_lnum) + &shiftwidth
239-
elseif (VHTL_closesTemplate(getline(l:prev_lnum)) && !VHTL_startsWithTemplateEnd(l:prev_lnum))
240-
call VHTL_debug('closed template ' . l:adjustForClosingBracket)
241-
let l:result = indent(l:prev_lnum) - &shiftwidth + l:adjustForClosingBracket
242-
if (VHTL_closesTag(getline(v:lnum)))
243-
call VHTL_debug('closed template and tag ' . l:adjustForClosingBracket)
244-
let l:result -= &shiftwidth
245-
endif
246-
return l:result
247-
elseif (l:isHtml && l:wasJs && VHTL_closesJsExpression(getline(l:prev_lnum)))
248-
let l:result = indent(l:prev_lnum) - &shiftwidth
249-
call VHTL_debug('closes expression')
250-
if (VHTL_closesTag(getline(v:lnum)))
251-
let l:result -= &shiftwidth
252-
call VHTL_debug('closes expression and tag')
253-
endif
254-
return l:result
255400
endif
256401

257-
let l:isJsx = (IsSynstackInsideJsx(l:currLineSynstack))
258-
if (l:wasCss || l:isCss || l:wasHtml || l:isHtml) && !l:isJsx
259-
call VHTL_debug('html indent ' . l:adjustForClosingBracket)
260-
return HtmlIndent() + l:adjustForClosingBracket
402+
if (l:state.wasJsTemplateBrace() || l:state.isLitHtmlRegionCloser())
403+
" let l:indent_basis = previous matching js or template start
404+
" let l:indent_delta = -1 for starting with closing tag, template, or expression
405+
let l:indent_basis = l:state.getIndentOfLastClose()
406+
if (l:indent_basis == -1)
407+
call VHTL_debug("default to html indent because base indent not found")
408+
return HtmlIndent()
409+
endif
410+
let l:indent_delta = l:state.getIndentDelta()
411+
call VHTL_debug('indent delta ' . l:indent_delta)
412+
call VHTL_debug('indent basis ' . l:indent_basis)
413+
return l:indent_basis + l:indent_delta
261414
endif
262415

263-
if len(b:litHtmlOriginalIndentExpression)
264-
call VHTL_debug('js indent ' . b:litHtmlOriginalIndentExpression)
416+
if ((l:state.wasJs() && !l:state.wasJsTemplateBrace()) && (l:state.isJs() && !l:state.isJsTemplateBrace()))
265417
return eval(b:litHtmlOriginalIndentExpression)
266-
else
267-
call VHTL_debug('cindent should never happen')
268-
return cindent(v:lnum)
269418
endif
419+
420+
call VHTL_debug('default to html indent')
421+
return HtmlIndent()
270422
endfu

test/lit-html.vader

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Before:
33
set shiftwidth=2
44
set expandtab
55

6-
Given javascript (xml in xml);
6+
Given javascript (html in html);
77
html`
88
<div>
99
<div> </div>
@@ -213,6 +213,48 @@ Expect javascript:
213213
</div>
214214
`
215215

216+
Given javascript (adjacent js expressions):
217+
html`
218+
${console.log(null)}
219+
${console.log('this line should have equal indentation')}
220+
`
221+
Do:
222+
=G
223+
224+
Expect javascript:
225+
html`
226+
${console.log(null)}
227+
${console.log('this line should have equal indentation')}
228+
`
229+
230+
Given javascript(multiple dedents after closing js expression):
231+
html`
232+
<div>
233+
${ test
234+
? html`<p>one</p>`
235+
: html`
236+
<p>
237+
two
238+
</p>`}
239+
</div>
240+
<span> dedent THRICE here </span>
241+
`;
242+
243+
Do:
244+
=G
245+
246+
Expect javascript:
247+
html`
248+
<div>
249+
${ test
250+
? html`<p>one</p>`
251+
: html`
252+
<p>
253+
two
254+
</p>`}
255+
</div>
256+
<span> dedent THRICE here </span>
257+
`;
216258

217259
" " This test fails because vim-javascript doesn't correctly indent ternaries inside ${ }
218260
" Given javascript (subsequent templates):

0 commit comments

Comments
 (0)