diff options
Diffstat (limited to 'autoload/jsx_pretty/indent.vim')
-rw-r--r-- | autoload/jsx_pretty/indent.vim | 623 |
1 files changed, 158 insertions, 465 deletions
diff --git a/autoload/jsx_pretty/indent.vim b/autoload/jsx_pretty/indent.vim index 03bdf29b..b3b1e5c1 100644 --- a/autoload/jsx_pretty/indent.vim +++ b/autoload/jsx_pretty/indent.vim @@ -15,6 +15,10 @@ let s:start_tag = '<\_s*\%(>\|\${\|\%(\<[-:._$A-Za-z0-9]\+\>\)\)' " Regexp for the end tag let s:end_tag = '\%(<\_s*/\_s*\%(\<[-:._$A-Za-z0-9]\+\>\)\_s*>\|/\_s*>\)' +function s:trim(line) + return substitute(a:line, '^\s*\|\s*$', '', 'g') +endfunction + " Get the syntax stack at the given position function s:syntax_stack_at(lnum, col) return map(synstack(a:lnum, a:col), 'synIDattr(v:val, "name")') @@ -30,22 +34,11 @@ function s:start_col(lnum) return len(matchstr(getline(a:lnum), '^\s*')) + 1 endfunction -" Get the end col of the non-space charactor -function s:end_col(lnum) - return len(substitute(getline(a:lnum), '\s*$', '', 'g')) -endfunction - " Get the start syntax of a given line number function s:start_syntax(lnum) return s:syntax_at(a:lnum, s:start_col(a:lnum)) endfunction -" Get the end syntax of a given line number -function s:end_syntax(lnum) - let end_col = len(substitute(getline(a:lnum), '\s*$', '', 'g')) - return s:syntax_at(a:lnum, end_col) -endfunction - " The skip function for searchpair function s:skip_if_not(current_lnum, ...) " Skip the match in current line @@ -57,47 +50,6 @@ function s:skip_if_not(current_lnum, ...) return syntax !~? join(a:000, '\|') endfunction -" Whether the specified stytax group is an jsx-related syntax -function s:is_jsx(syntax_name) - return a:syntax_name =~ '^jsx' && a:syntax_name !=? 'jsxEscapeJs' -endfunction - -" Whether the specified line is start with jsx-related syntax -function s:start_with_jsx(lnum) - let start_syntax = s:start_syntax(a:lnum) - return s:is_jsx(start_syntax) -endfunction - -" Whether the specified line is end with jsx-related syntax -function s:end_with_jsx(lnum) - let end_syntax = s:end_syntax(a:lnum) - return s:is_jsx(end_syntax) -endfunction - -" Whether the specified line is comment related syntax -function s:is_comment(syntax) - return a:syntax =~? 'comment' -endfunction - -" Whether the specified line is embedded comment in jsxTag -function s:is_embedded_comment(lnum) - let start_col = s:start_col(a:lnum) - let syntax_stack = s:syntax_stack_at(a:lnum, start_col) - let last = get(syntax_stack, -1) - let last_2nd = get(syntax_stack, -2) - - " Patch code for pangloss/vim-javascript - " <div - " /* hello */ <-- syntax stack is [..., jsxTag, jsComment, jsComment] - " > - if last_2nd =~ 'comment' - let last_2nd = get(syntax_stack, -3) - endif - - return last =~? 'comment' && last_2nd =~? 'jsxTag' && - \ trim(getline(a:lnum)) =~ '\%(^/[*/]\)\|\%(\*/$\)' -endfunction - " Whether the specified stytax group is the opening tag function s:is_opening_tag(syntax) return a:syntax =~? 'jsxOpenPunct' @@ -108,9 +60,9 @@ function s:is_closing_tag(syntax) return a:syntax =~? 'jsxClose' endfunction -" Whether the specified stytax group is the jsxAttrib -function s:is_jsx_attr(syntax) - return a:syntax =~? 'jsxAttrib' +" Whether the specified syntax group is the jsxRegion +function s:is_jsx_region(syntax) + return a:syntax =~? 'jsxRegion' endfunction " Whether the specified syntax group is the jsxElement @@ -118,9 +70,9 @@ function s:is_jsx_element(syntax) return a:syntax =~? 'jsxElement' endfunction -" Whether the specified syntax group is the jsxTag -function s:is_jsx_tag(syntax) - return a:syntax =~? 'jsxTag' +" Whether the specified syntax group is the jsxEscapeJs +function s:is_jsx_escape(syntax) + return a:syntax =~? 'jsxEscapeJs' endfunction " Whether the specified syntax group is the jsxBraces @@ -133,6 +85,11 @@ function s:is_jsx_comment(syntax) return a:syntax =~? 'jsxComment' endfunction +" Whether the specified line is comment related syntax +function s:is_comment(syntax) + return a:syntax =~? 'comment' +endfunction + " Whether the specified syntax group is the jsxComment function s:is_jsx_backticks(syntax) return a:syntax =~? 'jsxBackticks' @@ -143,155 +100,18 @@ function s:prev_lnum(lnum) return prevnonblank(a:lnum - 1) endfunction -" Given a lnum and get the information of its previous line -function s:prev_info(lnum) - let prev_lnum = s:prev_lnum(a:lnum) - let prev_start_syntax = s:start_syntax(prev_lnum) - let prev_end_syntax = s:end_syntax(prev_lnum) - - return [prev_lnum, prev_start_syntax, prev_end_syntax] -endfunction - -" Get the length difference between syntax stack a and b -function s:syntax_stack_length_compare(lnum_a, col_a, lnum_b, col_b) - let stack_a = s:syntax_stack_at(a:lnum_a, a:col_a) - let stack_b = s:syntax_stack_at(a:lnum_b, a:col_b) - - return len(stack_a) - len(stack_b) +" Whether the given pos is the parent of the given element who has +" element_count jsxElement syntax +function s:is_parent_element(pos, element_count) + let syntax_stack = s:syntax_stack_at(a:pos[0], a:pos[1]) + return s:is_opening_tag(syntax_stack[-1]) && + \ count(syntax_stack, 'jsxElement') <= a:element_count endfunction -" Determine whether child is a element of parent -function s:is_element_of(parent_lnum, child_lnum) - let parent_stack = s:syntax_stack_at(a:parent_lnum, s:start_col(a:parent_lnum)) - let child_stack = s:syntax_stack_at(a:child_lnum, s:start_col(a:child_lnum)) - - let element_index = len(child_stack) - index(reverse(child_stack), 'jsxElement') - let rest = parent_stack[element_index:] - - return index(rest, 'jsxElement') == -1 -endfunction - -" Compute the indention of the opening tag -function s:jsx_indent_opening_tag(lnum) - let [prev_lnum, prev_start_syntax, prev_end_syntax] = s:prev_info(a:lnum) - - " If current line is start with > - if trim(getline(a:lnum)) =~ '^>' - let pair_line = searchpair('<', '', '>', 'bW', 's:skip_if_not(a:lnum, "jsxOpenPunct", "jsxClose")') - return indent(pair_line) - endif - - " If both the start and the end of the previous line are not jsx-related syntax - " return ( - " <div></div> <-- - " ) - if !s:end_with_jsx(prev_lnum) && !s:start_with_jsx(prev_lnum) - " return -1, so that it will use the default js indent function - return -1 - endif - - " If the end of the previous line is not jsx-related syntax - " [ - " <div></div>, - " <div></div> <-- - " ] - " should not affect - " <div - " render={() => ( - " <div> <-- - " </div> - " )} - " > - " </div> - " <div> - " {items.map(() => ( - " <Item /> <-- - " ))} - " </div> - if !s:end_with_jsx(prev_lnum) && - \ !s:is_jsx_attr(prev_start_syntax) && - \ !s:is_jsx_brace(prev_start_syntax) - return indent(prev_lnum) - endif - - " If the start of the previous line is not jsx-related syntax - " return <div> - " <div></div> <-- - " </div> - if !s:start_with_jsx(prev_lnum) - return indent(prev_lnum) + s:sw() - endif - endif - - " <div> - " <span> - " </span> - " <div> <-- - " </div> - " </div> - if s:is_closing_tag(prev_start_syntax) - return indent(prev_lnum) - endif - - " If the previous line is end with a closing tag - " <div> - " <br /> - " <div></div> <-- - " </div> - " should not affect case like - " <div><br /> - " <span> <-- - " </div> - if s:is_closing_tag(prev_end_syntax) && - \ s:syntax_stack_length_compare( - \ prev_lnum, s:start_col(prev_lnum), prev_lnum, s:end_col(prev_lnum)) == 2 - return indent(prev_lnum) - endif - - " If the start of the previous line is the jsxElement - " <div> - " hello - " <div></div> <-- - " </div> - if s:is_jsx_element(prev_start_syntax) || - \ s:is_jsx_comment(prev_start_syntax) - return indent(prev_lnum) - endif - - " If the start of the prvious line is the jsxBraces { - " <div> - " {foo} - " <div></div> <-- - " </div> - " should not affect case like - " <div> - " { - " <div></div> <-- - " } - " </div> - if s:is_jsx_brace(prev_start_syntax) && - \ trim(getline(prev_lnum)) =~ '^[$]\?{' && - \ s:syntax_stack_length_compare( - \ prev_lnum, s:start_col(prev_lnum), a:lnum, s:start_col(a:lnum)) == -2 - return indent(prev_lnum) - endif - - " If the start of the prvious line is the jsxBraces } - " <div> - " { - " foo - " } - " <div></div> <-- - " </div> - if s:is_jsx_brace(prev_start_syntax) && - \ trim(getline(prev_lnum)) =~ '}' && - \ s:syntax_stack_length_compare( - \ prev_lnum, s:start_col(prev_lnum), a:lnum, s:start_col(a:lnum)) == -3 - return indent(prev_lnum) - endif - - " Otherwise, indent s:sw() spaces - return indent(prev_lnum) + s:sw() +" Compute the indention of the trail punct +function s:jsx_indent_trail_punct(lnum) + let pair_line = searchpair('<', '', '>', 'bW', 's:skip_if_not(a:lnum, "jsxOpenPunct", "jsxClose")') + return indent(pair_line) endfunction " Compute the indention of the closing tag @@ -300,195 +120,64 @@ function s:jsx_indent_closing_tag(lnum) return pair_line ? indent(pair_line) : indent(a:lnum) endfunction -" Compute the indention of the jsxAttrib -function s:jsx_indent_attr(lnum) - let [prev_lnum, prev_start_syntax, prev_end_syntax] = s:prev_info(a:lnum) - - " If the start of the previous line is not jsx-related syntax - " return <div - " attr="hello" <-- - " > - " should not affect - " <div - " // comment here - " attr="hello" - " > - " </div> - if !s:start_with_jsx(prev_lnum) && - \ !s:is_comment(prev_start_syntax) - return indent(prev_lnum) + s:sw() - endif - - " If the start of the previous line is the opening tag - " <div - " title="title" <-- - " > - if s:is_opening_tag(prev_start_syntax) - return indent(prev_lnum) + s:sw() - endif - - " Otherwise, align the attribute with its previous line - return indent(prev_lnum) -endfunction - " Compute the indentation of the jsxElement function s:jsx_indent_element(lnum) - let [prev_lnum, prev_start_syntax, prev_end_syntax] = s:prev_info(a:lnum) - - " Fix test case like - " <div|> <-- press enter - " should indent as - " <div - " > <-- - if trim(getline(a:lnum)) =~ '^>' && !s:is_opening_tag(prev_end_syntax) - return indent(prev_lnum) - endif + let syntax_stack = s:syntax_stack_at(a:lnum, s:start_col(a:lnum)) + let syntax_name = syntax_stack[-1] + let element_count = count(syntax_stack, 'jsxElement') - " If the start of the previous line is start with > - " <div - " attr="foo" - " > - " text <-- - " </div> - if s:is_opening_tag(prev_start_syntax) && - \ trim(getline(prev_lnum)) =~ '^>$' - return indent(prev_lnum) + s:sw() + if s:trim(getline(a:lnum)) =~ '^>' + return s:jsx_indent_trail_punct(a:lnum) endif - " <div> - " text <-- - " </div> - " should not affect case like - " <div> - " <br /> - " hello <-- - " </div> - if s:is_opening_tag(prev_start_syntax) && - \ s:is_element_of(prev_lnum, a:lnum) - return indent(prev_lnum) + s:sw() - endif - - " return <div> - " text <-- - " </div> - if !s:start_with_jsx(prev_lnum) - return indent(prev_lnum) + s:sw() + " If current tag is closing tag + if s:is_closing_tag(syntax_name) + return s:jsx_indent_closing_tag(a:lnum) endif - " Otherwise, align with the previous line - " <div> - " {foo} - " text <-- - " </div> - return indent(prev_lnum) -endfunction - -" Compute the indentation of jsxBraces -function s:jsx_indent_brace(lnum) - let [prev_lnum, prev_start_syntax, prev_end_syntax] = s:prev_info(a:lnum) - - " If the start of the previous line is start with > - " <div - " attr="foo" - " > - " {foo} <-- - " </div> - if s:is_opening_tag(prev_start_syntax) && - \ trim(getline(prev_lnum)) =~ '^>$' - return indent(prev_lnum) + s:sw() + " Normalize the jsxElement count for opening tag + if s:is_opening_tag(syntax_name) + " <div> + " <div></div> <-- jsxRegion->jsxElement->jsxElement->jsxTag->jsxOpenTag->jsxOpenPunct + " </div> + if s:is_jsx_element(syntax_stack[-4]) && s:is_jsx_element(syntax_stack[-5]) + let element_count = element_count - 1 + endif endif - " <div> - " {foo} <-- - " </div> - " should not affect case like - " <div> - " <br /> - " {foo} <-- - " text - " {foo} <-- - " </div> - if s:is_opening_tag(prev_start_syntax) && - \ s:syntax_stack_length_compare( - \ prev_lnum, s:start_col(prev_lnum), a:lnum, s:start_col(a:lnum)) == 1 - return indent(prev_lnum) + s:sw() - endif + let start_time = localtime() + let pos = searchpos(s:start_tag, 'bW') - " If current line is the close brace } - if trim(getline(a:lnum)) =~ '^}' - let pair_line = searchpair('{', '', '}', 'bW', 's:skip_if_not(a:lnum, "jsxBraces")') - return indent(pair_line) - endif + while !s:is_parent_element(pos, element_count) + if localtime() - start_time >= 0.5 + return -1 + endif + let pos = searchpos(s:start_tag, 'bW') + endwhile - " return <div> - " {foo} <-- - " </div> - " should not affect - " <div - " // js comment - " {...props} <-- - " > - " </div> - if !s:start_with_jsx(prev_lnum) && - \ !s:is_comment(prev_start_syntax) - return indent(prev_lnum) + s:sw() - endif - - return indent(prev_lnum) + return indent(pos[0]) + s:sw() endfunction " Compute the indentation of the comment function s:jsx_indent_comment(lnum) - let [prev_lnum, prev_start_syntax, prev_end_syntax] = s:prev_info(a:lnum) + let line = s:trim(getline(a:lnum)) - " If current line is jsxComment if s:is_jsx_comment(s:start_syntax(a:lnum)) - let line = trim(getline(a:lnum)) - if line =~ '^<!--' - " Patch code for yajs.vim - " ${logs.map(log => html` - " <${Log} class="log" ...${log} /> - " `)} <-- The backtick here is Highlighted as javascriptTemplate - " <!-- <-- Correct the indentation here - if !s:start_with_jsx(prev_lnum) && s:end_with_jsx(prev_lnum) - return indent(prev_lnum) - endif - - " Return the element indent for the opening comment + if line =~ '^<!--' || line =~ '^-->' return s:jsx_indent_element(a:lnum) - elseif line =~ '^-->' - " Return the paired indent for the closing comment - let pair_line = searchpair('<!--', '', '-->', 'bW') - return indent(pair_line) else - if trim(getline(prev_lnum)) =~ '^<!--' - " <!-- - " comment <-- - " --> - return indent(prev_lnum) + s:sw() - else - " <!-- - " comment - " comment <-- - " --> - return indent(prev_lnum) - endif + return s:jsx_indent_element(a:lnum) + s:sw() + endif + else + if line =~ '^/\*' || line =~ '^//' + return s:jsx_indent_element(a:lnum) + else + return s:jsx_indent_element(a:lnum) + 1 endif endif - - " For comment inside the jsxTag - if s:is_opening_tag(prev_start_syntax) - return indent(prev_lnum) + s:sw() - endif - - if trim(getline(a:lnum)) =~ '\*/$' - let pair_line = searchpair('/\*', '', '\*/', 'bW', 's:skip_if_not(a:lnum)') - return indent(pair_line) + 1 - endif - - return indent(prev_lnum) endfunction +" Compute the indentation of jsxBackticks function s:jsx_indent_backticks(lnum) let tags = get(g:, 'vim_jsx_pretty_template_tags', ['html', 'jsx']) let start_tag = '\%(' . join(tags, '\|') . '\)`' @@ -498,116 +187,120 @@ function s:jsx_indent_backticks(lnum) return indent(pair_line) endfunction -" Compute the indentation for the jsx-related element -function s:jsx_indent(lnum) - let start_syntax = s:start_syntax(a:lnum) +" Syntax context types: +" - jsxRegion +" - jsxTaggedRegion +" - jsxElement +" - jsxEscapeJs +" - Other +function s:syntax_context(lnum) + let start_col = s:start_col(a:lnum) + let syntax_stack = s:syntax_stack_at(a:lnum, start_col) + let start_syntax = syntax_stack[-1] + let reversed = reverse(syntax_stack) + let i = 0 + + for syntax_name in reversed + " If the current line is jsxEscapeJs and not starts with jsxBraces + if s:is_jsx_escape(syntax_name) + return 'jsxEscapeJs' + endif + + if s:is_jsx_region(syntax_name) + return 'jsxRegion' + endif - if s:is_opening_tag(start_syntax) - return s:jsx_indent_opening_tag(a:lnum) - elseif s:is_closing_tag(start_syntax) - return s:jsx_indent_closing_tag(a:lnum) - elseif s:is_jsx_attr(start_syntax) - return s:jsx_indent_attr(a:lnum) - elseif s:is_jsx_element(start_syntax) - return s:jsx_indent_element(a:lnum) - elseif s:is_jsx_brace(start_syntax) - return s:jsx_indent_brace(a:lnum) - elseif s:is_jsx_comment(start_syntax) - return s:jsx_indent_comment(a:lnum) - elseif s:is_jsx_backticks(start_syntax) - return s:jsx_indent_backticks(a:lnum) - endif + if s:is_jsx_element(syntax_name) + " If current line starts with the opening tag + if s:is_opening_tag(start_syntax) || s:is_closing_tag(start_syntax) + " And the next syntax is jsxRegion + if s:is_jsx_region(reversed[i+1]) + return 'jsxRegion' + elseif reversed[i+1] =~ 'jsxTaggedRegion' + return 'jsxTaggedRegion' + else + return 'jsxElement' + endif + elseif reversed[i+1] =~ 'jsxTaggedRegion' + return 'jsxTaggedRegion' + else + return 'jsxElement' + endif + endif - return -1 + let i = i + 1 + endfor + + return 'Other' endfunction -" Return the jsx context of the specified line -function s:jsx_context(lnum) - if !s:end_with_jsx(prev_lnum) - return 'non-jsx' + +function! jsx_pretty#indent#get(js_indent) + let line = s:trim(getline(v:lnum)) + let start_syntax = s:start_syntax(v:lnum) + + if s:is_jsx_backticks(start_syntax) + return s:jsx_indent_backticks(v:lnum) endif - let prev_lnum = s:prev_lnum(a:lnum) - let start_syntax = s:start_syntax(prev_lnum) - let end_col = s:end_col(prev_lnum) - let end_syntax_stack = s:syntax_stack_at(prev_lnum, end_col) - let reversed = reverse(end_syntax_stack) - - for item in reversed - if item =~? 'jsxEscapeJs' - return 'unknown' - elseif s:is_jsx_element(item) && s:is_jsx(start_syntax) && start_syntax !~? 'jsxEscapeJs' - " <div> - " <br /> <-- press o - " </div> - " -------------------- - " <div> - " { - " a > 0 ? 1 <div>|</div> <-- press enter - " } - " </div> - return 'jsxElement' - elseif s:is_jsx_tag(item) - return 'jsxTag' - elseif s:is_jsx_brace(item) && trim(getline(prev_lnum)) =~ '{$' - return 'jsxBraces' - endif - endfor + if s:is_jsx_brace(start_syntax) + return s:jsx_indent_element(v:lnum) + endif - return 'unknown' -endfunction + if s:is_opening_tag(start_syntax) && line =~ '^>' + return s:jsx_indent_trail_punct(v:lnum) + endif + + let syntax_context = s:syntax_context(v:lnum) + + if syntax_context == 'jsxRegion' + if s:is_closing_tag(start_syntax) + return s:jsx_indent_closing_tag(v:lnum) + endif -function! jsx_pretty#indent#get(js_indent) - " The start column index of the current line (one-based) - let start_col = s:start_col(v:lnum) - " The end column index of the current line (one-based) - let end_col = s:end_col(v:lnum) - - if s:start_with_jsx(v:lnum) - let ind = s:jsx_indent(v:lnum) - return ind == -1 ? a:js_indent() : ind - elseif s:is_embedded_comment(v:lnum) - return s:jsx_indent_comment(v:lnum) - else - let line = trim(getline(v:lnum)) let prev_lnum = s:prev_lnum(v:lnum) + let prev_line = s:trim(getline(prev_lnum)) - " Fix the case where pressing enter at the cursor - " return <div>|</div> - if line =~ '^' . s:end_tag && - \ s:end_with_jsx(s:prev_lnum(v:lnum)) + if prev_line =~ '[([{=?]$' + return indent(prev_lnum) + s:sw() + elseif prev_line =~ '[:|&<>]$' && + \ s:trim(getline(s:prev_lnum(prev_lnum))) !~ '[?:|&<>]$' + return indent(prev_lnum) + s:sw() + else + return indent(prev_lnum) + endif + elseif syntax_context == 'jsxTaggedRegion' + if s:is_closing_tag(start_syntax) return s:jsx_indent_closing_tag(v:lnum) + elseif s:is_jsx_comment(start_syntax) + return s:jsx_indent_comment(v:lnum) + else + return indent(s:prev_lnum(v:lnum)) + s:sw() + endif + elseif syntax_context == 'jsxElement' + if s:is_jsx_comment(start_syntax) + return s:jsx_indent_comment(v:lnum) endif - " Fix cases for the new line - if empty(line) - let context = s:jsx_context(v:lnum) - - if context == 'jsxElement' - " <div> <-- press o - " </div> - return s:jsx_indent_element(v:lnum) - elseif context == 'jsxTag' - " <div <-- press o - " > - " </div> - " ----------------------- - " <div - " attr="attr" <-- press o - " > - " </div> - return s:jsx_indent_attr(v:lnum) - elseif context == 'jsxBraces' - " <div> - " { <-- press o - " } - " </div> - return indent(prev_lnum) + s:sw() - endif + if s:is_comment(start_syntax) + return s:jsx_indent_comment(v:lnum) endif - return a:js_indent() + return s:jsx_indent_element(v:lnum) + elseif syntax_context == 'jsxEscapeJs' + let prev_lnum = s:prev_lnum(v:lnum) + let prev_line = s:trim(getline(prev_lnum)) + + if line =~ '^?' + return indent(prev_lnum) + s:sw() + elseif line =~ '^:' + return indent(prev_lnum) + else + return a:js_indent() + endif endif + + return a:js_indent() endfunction endif |