From 0c79dd3e73e8e09b73d4a5d20bf470a3f6f715f2 Mon Sep 17 00:00:00 2001 From: Adam Stankiewicz Date: Tue, 12 Nov 2019 21:56:06 +0100 Subject: Update --- autoload/jsx_pretty/indent.vim | 733 +++++++++++++++++++++++++++++++---------- 1 file changed, 564 insertions(+), 169 deletions(-) (limited to 'autoload/jsx_pretty') diff --git a/autoload/jsx_pretty/indent.vim b/autoload/jsx_pretty/indent.vim index 15e177e0..03bdf29b 100644 --- a/autoload/jsx_pretty/indent.vim +++ b/autoload/jsx_pretty/indent.vim @@ -10,209 +10,604 @@ else endfunction endif -" Get the syntax group of start of line -function! s:syn_sol(lnum) - let line = getline(a:lnum) - let sol = matchstr(line, '^\s*') - return map(synstack(a:lnum, len(sol) + 1), 'synIDattr(v:val, "name")') +" Regexp for the start tag +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*>\)' + +" 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")') endfunction -" Get the syntax group of end of line -function! s:syn_eol(lnum) - let lnum = prevnonblank(a:lnum) - let col = strlen(getline(lnum)) - return map(synstack(lnum, col), 'synIDattr(v:val, "name")') +" Get the syntax at the given position +function s:syntax_at(lnum, col) + return synIDattr(synID(a:lnum, a:col, 1), 'name') endfunction -function! s:prev_indent(lnum) - let lnum = prevnonblank(a:lnum - 1) - return indent(lnum) +" Get the start col of the non-space charactor +function s:start_col(lnum) + return len(matchstr(getline(a:lnum), '^\s*')) + 1 endfunction -function! s:prev_line(lnum) - let lnum = prevnonblank(a:lnum - 1) - return substitute(getline(lnum), '^\s*\|\s*$', '', 'g') +" Get the end col of the non-space charactor +function s:end_col(lnum) + return len(substitute(getline(a:lnum), '\s*$', '', 'g')) endfunction -function! s:syn_attr_jsx(synattr) - return a:synattr =~? "^jsx" +" 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 -function! s:syn_xmlish(syns) - return s:syn_attr_jsx(get(a:syns, -1)) +" 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 -function! s:syn_jsx_element(syns) - return get(a:syns, -1) =~? 'jsxElement' +" The skip function for searchpair +function s:skip_if_not(current_lnum, ...) + " Skip the match in current line + if line('.') == a:current_lnum + return 1 + endif + + let syntax = s:syntax_at(line('.'), col('.')) + return syntax !~? join(a:000, '\|') endfunction -function! s:syn_js_comment(syns) - return get(a:syns, -1) =~? 'Comment$' +" 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 -function! s:syn_jsx_escapejs(syns) - return get(a:syns, -1) =~? '\(\(js\(Template\)\?\|javaScript\(Embed\)\?\|typescript\)Braces\|javascriptTemplateSB\|typescriptInterpolationDelimiter\)' && - \ (get(a:syns, -2) =~? 'jsxEscapeJs' || - \ get(a:syns, -3) =~? 'jsxEscapeJs') +" 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 -function! s:syn_jsx_attrib(syns) - return len(filter(copy(a:syns), 'v:val =~? "jsxAttrib"')) +" 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 -let s:start_tag = '<\s*\([-:_\.\$0-9A-Za-z]\+\|>\)' -" match `/end_tag>` and `//>` -let s:end_tag = '/\%(\s*[-:_\.\$0-9A-Za-z]*\s*\|/\)>' -let s:opfirst = '^' . get(g:,'javascript_opfirst', - \ '\C\%([<>=,.?^%|/&]\|\([-:+]\)\1\@!\|\*\+\|!=\|in\%(stanceof\)\=\>\)') +" Whether the specified line is comment related syntax +function s:is_comment(syntax) + return a:syntax =~? 'comment' +endfunction -function! jsx_pretty#indent#get(js_indent) - let lnum = v:lnum - let line = substitute(getline(lnum), '^\s*\|\s*$', '', 'g') - let current_syn = s:syn_sol(lnum) - let current_syn_eol = s:syn_eol(lnum) - let prev_line_num = prevnonblank(lnum - 1) - let prev_syn_sol = s:syn_sol(prev_line_num) - let prev_syn_eol = s:syn_eol(prev_line_num) - let prev_line = s:prev_line(lnum) - let prev_ind = s:prev_indent(lnum) - - if s:syn_xmlish(current_syn) - - if !s:syn_xmlish(prev_syn_sol) - \ && !s:syn_jsx_escapejs(prev_syn_sol) - \ && !s:syn_jsx_escapejs(prev_syn_eol) - \ && !s:syn_js_comment(prev_syn_sol) - if line =~ '^/\s*>' || line =~ '^<\s*' . s:end_tag - return prev_ind - else - return prev_ind + s:sw() +" 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 + "
+ 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' +endfunction + +" Whether the specified stytax group is the closing tag +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' +endfunction + +" Whether the specified syntax group is the jsxElement +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' +endfunction + +" Whether the specified syntax group is the jsxBraces +function s:is_jsx_brace(syntax) + return a:syntax =~? 'jsxBraces' +endfunction + +" Whether the specified syntax group is the jsxComment +function s:is_jsx_comment(syntax) + return a:syntax =~? 'jsxComment' +endfunction + +" Whether the specified syntax group is the jsxComment +function s:is_jsx_backticks(syntax) + return a:syntax =~? 'jsxBackticks' +endfunction + +" Get the prvious line number +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) +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 ( + "
<-- + " ) + 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 + " [ + "
, + "
<-- + " ] + " should not affect + "
( + "
<-- + "
+ " )} + " > + "
+ "
+ " {items.map(() => ( + " <-- + " ))} + "
+ 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
+ "
<-- + "
+ if !s:start_with_jsx(prev_lnum) + return indent(prev_lnum) + s:sw() + endif + endif + + "
+ " + " + "
<-- + "
+ "
+ if s:is_closing_tag(prev_start_syntax) + return indent(prev_lnum) + endif + + " If the previous line is end with a closing tag + "
+ "
+ "
<-- + "
+ " should not affect case like + "

+ " <-- + "
+ 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 + "
+ " hello + "
<-- + "
+ 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 { + "
+ " {foo} + "
<-- + "
+ " should not affect case like + "
+ " { + "
<-- + " } + "
+ 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 } + "
+ " { + " foo + " } + "
<-- + "
+ 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() +endfunction + +" Compute the indention of the closing tag +function s:jsx_indent_closing_tag(lnum) + let pair_line = searchpair(s:start_tag, '', s:end_tag, 'bW', 's:skip_if_not(a:lnum, "jsxOpenPunct", "jsxClose")') + 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
+ " should not affect + "
+ "
+ 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 + "
+ 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 + " <-- press enter + " should indent as + "
<-- + if trim(getline(a:lnum)) =~ '^>' && !s:is_opening_tag(prev_end_syntax) + return indent(prev_lnum) + endif + + " If the start of the previous line is start with > + "
+ " text <-- + "
+ if s:is_opening_tag(prev_start_syntax) && + \ trim(getline(prev_lnum)) =~ '^>$' + return indent(prev_lnum) + s:sw() + endif + + "
+ " text <-- + "
+ " should not affect case like + "
+ "
+ " hello <-- + "
+ if s:is_opening_tag(prev_start_syntax) && + \ s:is_element_of(prev_lnum, a:lnum) + return indent(prev_lnum) + s:sw() + endif + + " return
+ " text <-- + "
+ if !s:start_with_jsx(prev_lnum) + return indent(prev_lnum) + s:sw() + endif + + " Otherwise, align with the previous line + "
+ " {foo} + " text <-- + "
+ 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 > + "
+ " {foo} <-- + "
+ if s:is_opening_tag(prev_start_syntax) && + \ trim(getline(prev_lnum)) =~ '^>$' + return indent(prev_lnum) + s:sw() + endif + + "
+ " {foo} <-- + "
+ " should not affect case like + "
+ "
+ " {foo} <-- + " text + " {foo} <-- + "
+ 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 + + " 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 + + " return
+ " {foo} <-- + "
+ " should not affect + "
+ "
+ if !s:start_with_jsx(prev_lnum) && + \ !s:is_comment(prev_start_syntax) + return indent(prev_lnum) + s:sw() + endif + + return indent(prev_lnum) +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) + + " If current line is jsxComment + if s:is_jsx_comment(s:start_syntax(a:lnum)) + let line = trim(getline(a:lnum)) + if line =~ '^' + " Return the paired indent for the closing comment + let pair_line = searchpair('', 'bW') return indent(pair_line) - elseif line =~ '^-->$' - if prev_line =~ '^$' - return prev_ind - " close tag or /> including - elseif prev_line =~ s:end_tag . '$' - if line =~ '^<\s*' . s:end_tag - return prev_ind - s:sw() - elseif s:syn_jsx_attrib(prev_syn_sol) - return prev_ind - s:sw() - else - return prev_ind - endif - elseif line =~ '^\(>\|/\s*>\)' - if prev_line =~ '^<' - return prev_ind - else - return prev_ind - s:sw() - endif - elseif prev_line =~ '^\(<\|>\)' && - \ (s:syn_xmlish(prev_syn_eol) || s:syn_js_comment(prev_syn_eol)) - if line =~ '^<\s*' . s:end_tag - return prev_ind - else - return prev_ind + s:sw() - endif - elseif line =~ '^<\s*' . s:end_tag - if !s:syn_xmlish(prev_syn_sol) - if s:syn_jsx_escapejs(prev_syn_eol) - \ || s:syn_jsx_escapejs(prev_syn_sol) - return prev_ind - s:sw() - else - return prev_ind - endif - elseif prev_line =~ '^\\|[([{]\|`\)$' - "
- " { - " } - "
- if line =~ '^[)\]}]' - return prev_ind - else - return prev_ind + s:sw() - endif - else - return prev_ind - endif else - return prev_ind - endif - elseif s:syn_jsx_escapejs(current_syn) - if line =~ '^}' - let char = getline('.')[col('.') - 1] - " When pressing enter after the }, keep the indent - if char != '}' && search('}', 'b', lnum) - return indent(lnum) - else - let pair_line = searchpair('{', '', '}', 'bW') - return indent(pair_line) - endif - elseif line =~ '^{' || line =~ '^\${' - if s:syn_jsx_escapejs(prev_syn_eol) - \ || s:syn_jsx_attrib(prev_syn_sol) - return prev_ind - elseif s:syn_xmlish(prev_syn_eol) && (prev_line =~ s:end_tag || prev_line =~ '-->$') - return prev_ind + if trim(getline(prev_lnum)) =~ '^ + return indent(prev_lnum) + s:sw() else - return prev_ind + s:sw() + " + return indent(prev_lnum) endif endif - elseif line =~ '^`' && s:syn_jsx_escapejs(current_syn_eol) - " For `} of template syntax - let pair_line = searchpair('{', '', '}', 'bW') - return indent(pair_line) - elseif line =~ '^/[/*]' " js comment in jsx tag - if get(prev_syn_sol, -1) =~ 'Punct' - return prev_ind + s:sw() - elseif synIDattr(synID(lnum - 1, 1, 1), 'name') =~ 'jsxTag' - return prev_ind - else - return a:js_indent() + 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 + +function s:jsx_indent_backticks(lnum) + let tags = get(g:, 'vim_jsx_pretty_template_tags', ['html', 'jsx']) + let start_tag = '\%(' . join(tags, '\|') . '\)`' + let end_tag = '\%(' . join(tags, '\|') . '\)\@ + "
<-- press o + "
+ " -------------------- + "
+ " { + " a > 0 ? 1
|
<-- press enter + " } + "
+ return 'jsxElement' + elseif s:is_jsx_tag(item) + return 'jsxTag' + elseif s:is_jsx_brace(item) && trim(getline(prev_lnum)) =~ '{$' + return 'jsxBraces' endif + endfor + + return 'unknown' +endfunction + +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 ind = a:js_indent() - - " Issue #68 - " return (
- " |
) - if (line =~ '^/\s*>' || line =~ '^<\s*' . s:end_tag) - \ && !s:syn_xmlish(prev_syn_sol) - return prev_ind + let line = trim(getline(v:lnum)) + let prev_lnum = s:prev_lnum(v:lnum) + + " Fix the case where pressing enter at the cursor + " return
|
+ if line =~ '^' . s:end_tag && + \ s:end_with_jsx(s:prev_lnum(v:lnum)) + return s:jsx_indent_closing_tag(v:lnum) endif - " If current syntax is not a jsx syntax group - if s:syn_xmlish(prev_syn_eol) && line !~ '^[)\]}]' - let sol = matchstr(line, s:opfirst) - if sol is '' - " Fix javascript continue indent - return ind - s:sw() - else - return ind + " Fix cases for the new line + if empty(line) + let context = s:jsx_context(v:lnum) + + if context == 'jsxElement' + "
<-- press o + "
+ return s:jsx_indent_element(v:lnum) + elseif context == 'jsxTag' + "
+ "
+ " ----------------------- + "
+ "
+ return s:jsx_indent_attr(v:lnum) + elseif context == 'jsxBraces' + "
+ " { <-- press o + " } + "
+ return indent(prev_lnum) + s:sw() endif endif - return ind - endif + return a:js_indent() + endif endfunction endif -- cgit v1.2.3