From 6e25213d3ef55043de5b0379e09e3d1515ed1480 Mon Sep 17 00:00:00 2001 From: Wang Shidong Date: Wed, 2 Dec 2020 23:28:17 +0800 Subject: [PATCH] Improve tsx support (#3993) --- autoload/SpaceVim/layers/lang/typescript.vim | 4 +- .../after/ftplugin/typescriptreact.vim | 16 ++ .../after/indent/typescriptreact.vim | 183 +++++++++++++ .../after/syntax/typescriptreact.vim | 256 ++++++++++++++++++ .../ftdetect/typescript.vim | 6 + 5 files changed, 463 insertions(+), 2 deletions(-) create mode 100644 bundle/vim-jsx-typescript/after/ftplugin/typescriptreact.vim create mode 100644 bundle/vim-jsx-typescript/after/indent/typescriptreact.vim create mode 100644 bundle/vim-jsx-typescript/after/syntax/typescriptreact.vim create mode 100644 bundle/vim-jsx-typescript/ftdetect/typescript.vim diff --git a/autoload/SpaceVim/layers/lang/typescript.vim b/autoload/SpaceVim/layers/lang/typescript.vim index 1f41f8fbd..fab32b09c 100644 --- a/autoload/SpaceVim/layers/lang/typescript.vim +++ b/autoload/SpaceVim/layers/lang/typescript.vim @@ -25,8 +25,8 @@ function! SpaceVim#layers#lang#typescript#plugins() abort let plugins = [] - call add(plugins, ['leafgarland/typescript-vim']) - call add(plugins, ['peitalin/vim-jsx-typescript']) + call add(plugins, ['leafgarland/typescript-vim', {'merged' : 0}]) + call add(plugins, [g:_spacevim_root_dir . 'bundle/vim-jsx-typescript', {'merged' : 0}]) call add(plugins, ['heavenshell/vim-jsdoc', { 'on_cmd': 'JsDoc' }]) if !SpaceVim#layers#lsp#check_filetype('typescript') if has('nvim') diff --git a/bundle/vim-jsx-typescript/after/ftplugin/typescriptreact.vim b/bundle/vim-jsx-typescript/after/ftplugin/typescriptreact.vim new file mode 100644 index 000000000..0c467cd80 --- /dev/null +++ b/bundle/vim-jsx-typescript/after/ftplugin/typescriptreact.vim @@ -0,0 +1,16 @@ +""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +" Vim ftplugin file +" +" Language: TSX (TypeScript) +" +""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" + +" modified from html.vim +if exists("loaded_matchit") + let b:match_ignorecase = 0 + let b:match_words = '(:),\[:\],{:},<:>,' . + \ '<\@<=\([^/][^ \t>]*\)[^>]*\%(>\|$\):<\@<=/\1>' +endif + +setlocal suffixesadd+=.tsx +setlocal commentstring={/*\ %s\ */} diff --git a/bundle/vim-jsx-typescript/after/indent/typescriptreact.vim b/bundle/vim-jsx-typescript/after/indent/typescriptreact.vim new file mode 100644 index 000000000..aa8ec4d79 --- /dev/null +++ b/bundle/vim-jsx-typescript/after/indent/typescriptreact.vim @@ -0,0 +1,183 @@ + +""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " Vim indent file +" +" Language: typescriptreact (TypeScript) +" from: +" https://github.com/peitalin/vim-jsx-typescript/issues/4#issuecomment-564519091 +""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" + +runtime! indent/typescript.vim + +let b:did_indent = 1 + +if !exists('*GetTypescriptIndent') | finish | endif + +setlocal indentexpr=GetTsxIndent() +setlocal indentkeys=0{,0},0),0],0\,,!^F,o,O,e,*,<>>,<<>,/ + +if exists('*shiftwidth') + function! s:sw() + return shiftwidth() + endfunction +else + function! s:sw() + return &sw + endfunction +endif + +let s:real_endtag = '\s*<\/\+[A-Za-z]*>' +let s:return_block = '\s*return\s\+(' +function! s:SynSOL(lnum) + return map(synstack(a:lnum, 1), 'synIDattr(v:val, "name")') +endfunction + +function! s:SynEOL(lnum) + let lnum = prevnonblank(a:lnum) + let col = strlen(getline(lnum)) + return map(synstack(lnum, col), 'synIDattr(v:val, "name")') +endfunction + +function! s:SynAttrJSX(synattr) + return a:synattr =~ "^tsx" +endfunction + +function! s:SynXMLish(syns) + return s:SynAttrJSX(get(a:syns, -1)) +endfunction + +function! s:SynJSXDepth(syns) + return len(filter(copy(a:syns), 'v:val ==# "tsxRegion"')) +endfunction + +function! s:SynJSXCloseTag(syns) + return len(filter(copy(a:syns), 'v:val ==# "tsxCloseTag"')) +endfunction + +function! s:SynJsxEscapeJs(syns) + return len(filter(copy(a:syns), 'v:val ==# "tsxJsBlock"')) +endfunction + +function! s:SynJSXContinues(cursyn, prevsyn) + let curdepth = s:SynJSXDepth(a:cursyn) + let prevdepth = s:SynJSXDepth(a:prevsyn) + + return prevdepth == curdepth || + \ (prevdepth == curdepth + 1 && get(a:cursyn, -1) ==# 'tsxRegion') +endfunction + +function! GetTsxIndent() + let cursyn = s:SynSOL(v:lnum) + let prevsyn = s:SynEOL(v:lnum - 1) + let nextsyn = s:SynEOL(v:lnum + 1) + let currline = getline(v:lnum) + + if ((s:SynXMLish(prevsyn) && s:SynJSXContinues(cursyn, prevsyn)) || currline =~# '\v^\s*\<') + let preline = getline(v:lnum - 1) + + if currline =~# '\v^\s*\/?\>' " /> > + return preline =~# '\v^\s*\<' ? indent(v:lnum - 1) : indent(v:lnum - 1) - s:sw() + endif + + if preline =~# '\v\{\s*$' && preline !~# '\v^\s*\<' + return currline =~# '\v^\s*\}' ? indent(v:lnum - 1) : indent(v:lnum - 1) + s:sw() + endif + + " return ( | return ( | return ( + "
|
| } | } + " ) | foo="bar"| > + if preline =~# '\v\}\s*$' + if currline =~# '\v^\s*\<\/' + return indent(v:lnum - 1) - s:sw() + endif + let ind = indent(v:lnum - 1) + if preline =~# '\v^\s*\<' + let ind = ind + s:sw() + endif + if currline =~# '\v^\s*\/?\>' + let ind = ind - s:sw() + endif + return ind + endif + + " return ( | return ( + "
|
+ "
|
+ " ##); | ); + if preline =~# '\v(\s?|\k?)\($' || preline =~# '\v^\s*\<\>' + return indent(v:lnum - 1) + s:sw() + endif + + let ind = s:XmlIndentGet(v:lnum) + + "
| ##
+ if s:SynJsxEscapeJs(prevsyn) && preline =~# '\v\{\s*$' + let ind = ind + s:sw() + endif + + " /> + if preline =~# '\v^\s*\/?\>$' || currline =~# '\v^\s*\<\/\>' + "let ind = currline =~# '\v^\s*\<\/' ? ind : ind + s:sw() + let ind = ind + s:sw() + " }> or }}\> or }}> + elseif preline =~# '\v^\s*\}?\}\s*\/?\>$' + let ind = ind + s:sw() + " >\<\/\a' + let ind = ind + s:sw() + elseif preline =~# '\v^\s*}}.+\<\/\k+\>$' + let ind = ind + s:sw() + endif + + "
|
+ " } | }## + if currline =~# '}$' && !(currline =~# '\v\{') + let ind = ind - s:sw() + endif + + if currline =~# '^\s*)' && s:SynJSXCloseTag(prevsyn) + let ind = ind - s:sw() + endif + else + let ind = GetTypescriptIndent() + endif + return ind +endfunction + +let b:xml_indent_open = '.\{-}<\a' +let b:xml_indent_close = '.\{-}'))) + a:add + else + return a:add + endif +endfunction + +function! s:XmlIndentGet(lnum) + " Find a non-empty line above the current line. + let lnum = prevnonblank(a:lnum - 1) + + " Hit the start of the file, use zero indent. + if lnum == 0 | return 0 | endif + + let ind = s:XmlIndentSum(lnum, -1, indent(lnum)) + let ind = s:XmlIndentSum(a:lnum, 0, ind) + return ind +endfunction diff --git a/bundle/vim-jsx-typescript/after/syntax/typescriptreact.vim b/bundle/vim-jsx-typescript/after/syntax/typescriptreact.vim new file mode 100644 index 000000000..0b0657d7b --- /dev/null +++ b/bundle/vim-jsx-typescript/after/syntax/typescriptreact.vim @@ -0,0 +1,256 @@ +""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +" Vim syntax file +" +" Language: TSX (TypeScript) +" +""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" + +" These are the plugin-to-syntax-element correspondences: +" - leafgarland/typescript-vim: typescriptFuncBlock + + +let s:tsx_cpo = &cpo +set cpo&vim + +syntax case match + +if exists('b:current_syntax') + let s:current_syntax = b:current_syntax + unlet b:current_syntax +endif + +syn include @HTMLSyntax syntax/html.vim +set syntax=typescript +if exists('s:current_syntax') + let b:current_syntax = s:current_syntax +endif + +"""""" Vim Syntax Help """""" +" `keepend` and `extend` docs: +" https://github.com/othree/til/blob/master/vim/syntax-keepend-extend.md + +" \@<= positive lookbehind +" \@ +" s~~~~~~~~~~~e +syntax region tsxRegion + \ start=+\(\([a-zA-Z]\)\@\|\(\s\|[(]\s*\)\@<=\z(<[/a-zA-Z],\@!\([a-zA-Z0-9:\-],\@!\)*\)\)+ + \ skip=++ + \ end=++ + \ end=+[a-zA-Z0-9.]*[/]*>\s*\n*\s*\n*\s*[});,]\@=+ + \ contains=tsxTag,tsxCloseTag,tsxComment,Comment,@Spell,tsxColon,tsxIfOperator,tsxElseOperator,jsBlock + \ extend + \ keepend + + + +" Negative lookbacks for: +" <> preceeded by [a-zA-Z] +" < +" end 2): handle \s*\n*\s*\n*\s*) +" \s => spaces/tabs +" \n => end-of-line => \n only match end of line in the buffer. +" \s*\n*\s*\n*\s* => handles arbitrary spacing between closing tsxTag +" and the ending brace for the scope: `}` or `)` +" +" \z( pattern \) Braces can be used to make a pattern into an atom. + +" {content} +" s~~~~~~~e +syn region jsBlock + \ start=+{+ + \ end=+}+ + \ contained + \ contains=TOP + +" \@<= positive lookbehind +" \@ +" s~~~~~~~~~~~~~~e +syntax region tsxJsBlock + \ matchgroup=tsxAttributeBraces start=+\([=]\|\s\)\@<={+ + \ matchgroup=tsxAttributeBraces end=+}\(\s*}\|)\)\@!+ + \ contained + \ keepend + \ extend + \ contains=TOP + +" +" s~~~~~~~~~~~~~~~e +syntax region tsxTag + \ start=+<[^ /!?<"'=:]\@=+ + \ end=+[/]\{0,1}>+ + \ contained + \ contains=tsxTagName,tsxAttrib,tsxEqual,tsxString,tsxJsBlock,tsxAttributeComment,tsxGenerics + +syntax region tsxGenerics + \ matchgroup=tsxTypeBraces start=+\([<][_\-\.:a-zA-Z0-9]*\|[<][_\-\.:a-zA-Z0-9]*\)\@<=\s*[<]+ + \ matchgroup=tsxTypeBraces end=+>+ + \ contains=tsxTypes,tsxGenerics + \ contained + \ extend + +syntax match tsxTypes /[_\.a-zA-Z0-9]/ + \ contained + +" \@ +" s~~~~~~~e +" For Generics outside of tsxRegion +" Must come after tsxRegion in this file +syntax region tsGenerics + \ start=+<\([\[A-Z]\|typeof\)\([a-zA-Z0-9,{}\[\]'".=>():]\|\s\)*>\(\s*\n*\s*[()]\|\s*[=]\)+ + \ end=+\([=]\)\@+ + \ contains=tsxTypes,tsxGenerics + \ extend + +" +" ~~~~~~ +syntax region tsxCloseTag + \ start=++ + + +" matches tsx Comments: {/* ..... /*} +syn region Comment contained start=+{/\*+ end=+\*/}+ contains=Comment + \ extend + +syn region tsxAttributeComment contained start=+//+ end=+\n+ contains=Comment + \ extend + +syntax match tsxCloseString + \ +\w\++ + \ contained + +syntax match tsxColon + \ +[;]+ + \ contained + +" +" ~~~~~~~~ +syntax match tsxComment // display +syntax match tsxEntity "&[^; \t]*;" contains=tsxEntityPunct +syntax match tsxEntityPunct contained "[&.;]" + +" +" ~~~~~~~~~~~ +" NOT +" +" ~~~~~ +syntax match tsxComponentName + \ +\<[_$]\?[A-Z][-_$A-Za-z0-9]*\>+ + \ contained + \ display + +syntax match tsxCloseComponentName + \ +[+ + \ contained + \ display + +" +" ~~~ +syntax match tsxTagName + \ +[<]\@<=[^ /!?<>"']\++ + \ contained + \ contains=tsxComponentName + \ display + +" +" ~~~ +syntax match tsxCloseTagName + \ +["']\++ + \ containedin=tsxCloseTag + \ contains=tsxCloseComponentName + \ display + +" +" ~~~ +syntax match tsxAttrib + \ +[-'"<]\@\(['"]\@!\|$\)+ + \ contained + \ keepend + \ contains=tsxAttribPunct,tsxAttribHook + \ display + +syntax match tsxAttribPunct +[:.]+ contained display + +" +" ~ +syntax match tsxEqual +=+ contained display + +" +" s~~~~~~e +syntax region tsxString contained start=+"+ end=+"+ contains=tsxEntity,@Spell display + +" +syntax region tsxString contained start=+`+ end=+`+ contains=tsxEntity,@Spell display + +" +" s~~~~~~e +syntax region tsxString contained start=+'+ end=+'+ contains=tsxEntity,@Spell display + +syntax match tsxIfOperator +?+ +syntax match tsxNotOperator +!+ +syntax match tsxElseOperator +:+ + +" highlight def link tsxTagName htmlTagName +highlight def link tsxTagName xmlTagName +highlight def link tsxComponentName xmlTagName +highlight def link tsxCloseComponentName xmlTagName +highlight def link tsxTag htmlTag +highlight def link tsxCloseTag xmlEndTag +highlight def link tsxCloseTagName xmlTagName +highlight def link tsxRegionEnd xmlEndTag +highlight def link tsxEqual htmlTag +highlight def link tsxString String +highlight def link tsxNameSpace Function +highlight def link tsxComment Error +highlight def link tsxAttrib htmlArg +highlight def link tsxCloseString htmlTagName +highlight def link tsxAttributeBraces htmlTag +highlight def link tsxAttributeComment Comment +highlight def link tsxColon typescriptEndColons + +highlight def link tsxGenerics typescriptEndColons +highlight def link tsGenerics tsxTypeBraces + +highlight def link tsxIfOperator typescriptEndColons +highlight def link tsxNotOperator typescriptEndColons +highlight def link tsxElseOperator typescriptEndColons +highlight def link tsxTypeBraces htmlTag +highlight def link tsxTypes typescriptEndColons + +" Custom React Highlights +syn keyword ReactState state nextState prevState setState +" Then EITHER (define your own colour scheme): +" OR (make the colour scheme match an existing one): +" hi link ReactKeywords typescriptRComponent +syn keyword ReactProps props defaultProps ownProps nextProps prevProps +syn keyword Events e event target value +syn keyword ReduxKeywords dispatch payload +syn keyword ReduxHooksKeywords useState useEffect useMemo useCallback +syn keyword WebBrowser window localStorage +syn keyword ReactLifeCycleMethods componentWillMount shouldComponentUpdate componentWillUpdate componentDidUpdate componentWillReceiveProps componentWillUnmount componentDidMount + +let b:current_syntax = 'typescriptreact' + +let &cpo = s:tsx_cpo +unlet s:tsx_cpo diff --git a/bundle/vim-jsx-typescript/ftdetect/typescript.vim b/bundle/vim-jsx-typescript/ftdetect/typescript.vim new file mode 100644 index 000000000..529d9ecc3 --- /dev/null +++ b/bundle/vim-jsx-typescript/ftdetect/typescript.vim @@ -0,0 +1,6 @@ +""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +" Vim ftdetect file +" Language: TSX (Typescript) +""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" + +autocmd BufNewFile,BufRead *.tsx setf typescriptreact