summaryrefslogtreecommitdiffstats
path: root/indent
diff options
context:
space:
mode:
authorAdam Stankiewicz <sheerun@sher.pl>2019-09-06 14:32:07 +0200
committerAdam Stankiewicz <sheerun@sher.pl>2019-09-06 14:32:07 +0200
commit84ec4eedcdd2892249b5369f91a6dd1d12fef2fc (patch)
tree6c9806851123656af2b71f6c6f5d89649442909c /indent
parent66b769328c4511b2273f01c70de971c41f6964dd (diff)
downloadvim-polyglot-4.0.0.tar.gz
vim-polyglot-4.0.0.zip
Switch typescript provider, closes #428v4.0.0
Diffstat (limited to 'indent')
-rw-r--r--indent/tsx.vim114
-rw-r--r--indent/typescript.vim708
2 files changed, 539 insertions, 283 deletions
diff --git a/indent/tsx.vim b/indent/tsx.vim
new file mode 100644
index 00000000..906ee222
--- /dev/null
+++ b/indent/tsx.vim
@@ -0,0 +1,114 @@
+if exists('g:polyglot_disabled') && index(g:polyglot_disabled, 'typescript') != -1
+ finish
+endif
+
+" Save the current JavaScript indentexpr.
+let b:tsx_ts_indentexpr = &indentexpr
+
+" Prologue; load in XML indentation.
+if exists('b:did_indent')
+ let s:did_indent=b:did_indent
+ unlet b:did_indent
+endif
+exe 'runtime! indent/xml.vim'
+if exists('s:did_indent')
+ let b:did_indent=s:did_indent
+endif
+
+setlocal indentexpr=GetTsxIndent()
+
+" JS indentkeys
+setlocal indentkeys=0{,0},0),0],0\,,!^F,o,O,e
+" XML indentkeys
+setlocal indentkeys+=*<Return>,<>>,<<>,/
+
+" Multiline end tag regex (line beginning with '>' or '/>')
+let s:endtag = '^\s*\/\?>\s*;\='
+let s:startexp = '[\{\(]\s*$'
+
+" Get all syntax types at the beginning of a given line.
+fu! SynSOL(lnum)
+ return map(synstack(a:lnum, 1), 'synIDattr(v:val, "name")')
+endfu
+
+" Get all syntax types at the end of a given line.
+fu! SynEOL(lnum)
+ let lnum = prevnonblank(a:lnum)
+ let col = strlen(getline(lnum))
+ return map(synstack(lnum, col), 'synIDattr(v:val, "name")')
+endfu
+
+" Check if a syntax attribute is XMLish.
+fu! SynAttrXMLish(synattr)
+ return a:synattr =~ "^xml" || a:synattr =~ "^tsx"
+endfu
+
+" Check if a synstack is XMLish (i.e., has an XMLish last attribute).
+fu! SynXMLish(syns)
+ return SynAttrXMLish(get(a:syns, -1))
+endfu
+
+" Check if a synstack denotes the end of a TSX block.
+fu! SynTSXBlockEnd(syns)
+ return get(a:syns, -1) =~ '\%(ts\|typescript\)Braces' &&
+ \ SynAttrXMLish(get(a:syns, -2))
+endfu
+
+" Determine how many tsxRegions deep a synstack is.
+fu! SynTSXDepth(syns)
+ return len(filter(copy(a:syns), 'v:val ==# "tsxRegion"'))
+endfu
+
+" Check whether `cursyn' continues the same tsxRegion as `prevsyn'.
+fu! SynTSXContinues(cursyn, prevsyn)
+ let curdepth = SynTSXDepth(a:cursyn)
+ let prevdepth = SynTSXDepth(a:prevsyn)
+
+ " In most places, we expect the nesting depths to be the same between any
+ " two consecutive positions within a tsxRegion (e.g., between a parent and
+ " child node, between two TSX attributes, etc.). The exception is between
+ " sibling nodes, where after a completed element (with depth N), we return
+ " to the parent's nesting (depth N - 1). This case is easily detected,
+ " since it is the only time when the top syntax element in the synstack is
+ " tsxRegion---specifically, the tsxRegion corresponding to the parent.
+ return prevdepth == curdepth ||
+ \ (prevdepth == curdepth + 1 && get(a:cursyn, -1) ==# 'tsxRegion')
+endfu
+
+" Cleverly mix JS and XML indentation.
+fu! GetTsxIndent()
+ let cursyn = SynSOL(v:lnum)
+ let prevsyn = SynEOL(v:lnum - 1)
+
+ " Use XML indenting iff:
+ " - the syntax at the end of the previous line was either TSX or was the
+ " closing brace of a jsBlock whose parent syntax was TSX; and
+ " - the current line continues the same tsxRegion as the previous line.
+ if (SynXMLish(prevsyn) || SynTSXBlockEnd(prevsyn)) &&
+ \ SynTSXContinues(cursyn, prevsyn)
+ let ind = XmlIndentGet(v:lnum, 0)
+ let l:line = getline(v:lnum)
+ let l:pline = getline(v:lnum - 1)
+
+ " Align '/>' and '>' with '<' for multiline tags.
+ " Align end of expression ')' or '}'.
+ if l:line =~? s:endtag
+ let ind = ind - shiftwidth()
+ endif
+
+ " Then correct the indentation of any TSX following '/>' or '>'.
+ " Align start of expression '(' or '{'
+ if l:pline =~? s:endtag || l:pline =~? s:startexp
+ let ind = ind + shiftwidth()
+ endif
+ else
+ if len(b:tsx_ts_indentexpr)
+ " Invoke the base TS package's custom indenter
+ let ind = eval(b:tsx_ts_indentexpr)
+ else
+ let ind = cindent(v:lnum)
+ endif
+ endif
+
+ return ind
+endfu
diff --git a/indent/typescript.vim b/indent/typescript.vim
index 04f7e876..deb4f18c 100644
--- a/indent/typescript.vim
+++ b/indent/typescript.vim
@@ -3,361 +3,503 @@ if exists('g:polyglot_disabled') && index(g:polyglot_disabled, 'typescript') !=
endif
" Vim indent file
-" Language: Typescript
-" Acknowledgement: Almost direct copy from https://github.com/pangloss/vim-javascript
+" Language: TypeScript
+" Acknowledgement: Based off of vim-ruby maintained by Nikolai Weibull http://vim-ruby.rubyforge.org
+
+" 0. Initialization {{{1
+" =================
" Only load this indent file when no other was loaded.
-if exists('b:did_indent') || get(g:, 'typescript_indent_disable', 0)
+if exists("b:did_indent")
finish
endif
let b:did_indent = 1
+setlocal nosmartindent
+
" Now, set up our indentation expression and keys that trigger it.
setlocal indentexpr=GetTypescriptIndent()
-setlocal autoindent nolisp nosmartindent
-setlocal indentkeys+=0],0)
-
-let b:undo_indent = 'setlocal indentexpr< smartindent< autoindent< indentkeys<'
+setlocal formatexpr=Fixedgq(v:lnum,v:count)
+setlocal indentkeys=0{,0},0),0],0\,,!^F,o,O,e
" Only define the function once.
-if exists('*GetTypescriptIndent')
+if exists("*GetTypescriptIndent")
finish
endif
let s:cpo_save = &cpo
set cpo&vim
-" Get shiftwidth value
-if exists('*shiftwidth')
- function s:sw()
- return shiftwidth()
- endfunction
-else
- function s:sw()
- return &sw
- endfunction
-endif
+" 1. Variables {{{1
+" ============
-" searchpair() wrapper
-if has('reltime')
- function s:GetPair(start,end,flags,skip,time,...)
- return searchpair('\m'.a:start,'','\m'.a:end,a:flags,a:skip,max([prevnonblank(v:lnum) - 2000,0] + a:000),a:time)
- endfunction
-else
- function s:GetPair(start,end,flags,skip,...)
- return searchpair('\m'.a:start,'','\m'.a:end,a:flags,a:skip,max([prevnonblank(v:lnum) - 1000,get(a:000,1)]))
- endfunction
-endif
+let s:js_keywords = '^\s*\(break\|case\|catch\|continue\|debugger\|default\|delete\|do\|else\|finally\|for\|function\|if\|in\|instanceof\|new\|return\|switch\|this\|throw\|try\|typeof\|var\|void\|while\|with\)'
" Regex of syntax group names that are or delimit string or are comments.
-let s:syng_strcom = 'string\|comment\|regex\|special\|doc\|template\%(braces\)\@!'
-let s:syng_str = 'string\|template\|special'
-let s:syng_com = 'comment\|doc'
+let s:syng_strcom = 'string\|regex\|comment\c'
+
+" Regex of syntax group names that are strings.
+let s:syng_string = 'regex\c'
+
+" Regex of syntax group names that are strings or documentation.
+let s:syng_multiline = 'comment\c'
+
+" Regex of syntax group names that are line comment.
+let s:syng_linecom = 'linecomment\c'
+
" Expression used to check whether we should skip a match with searchpair().
-let s:skip_expr = "synIDattr(synID(line('.'),col('.'),0),'name') =~? '".s:syng_strcom."'"
+let s:skip_expr = "synIDattr(synID(line('.'),col('.'),1),'name') =~ '".s:syng_strcom."'"
-function s:skip_func()
- if !s:free || search('\m`\|\${\|\*\/','nW',s:looksyn)
- let s:free = !eval(s:skip_expr)
- let s:looksyn = line('.')
- return !s:free
- endif
- let s:looksyn = line('.')
- return getline('.') =~ '\%<'.col('.').'c\/.\{-}\/\|\%>'.col('.').'c[''"]\|\\$' &&
- \ eval(s:skip_expr)
-endfunction
+let s:line_term = '\s*\%(\%(\/\/\).*\)\=$'
-function s:alternatePair(stop)
- let pos = getpos('.')[1:2]
- while search('\m[][(){}]','bW',a:stop)
- if !s:skip_func()
- let idx = stridx('])}',s:looking_at())
- if idx + 1
- if s:GetPair(['\[','(','{'][idx], '])}'[idx],'bW','s:skip_func()',2000,a:stop) <= 0
- break
- endif
- else
- return
- endif
- endif
- endwhile
- call call('cursor',pos)
-endfunction
+" Regex that defines continuation lines, not including (, {, or [.
+let s:continuation_regex = '\%([\\*+/.:]\|\%(<%\)\@<![=-]\|\W[|&?]\|||\|&&\|[^=]=[^=].*,\)' . s:line_term
+
+" Regex that defines continuation lines.
+" TODO: this needs to deal with if ...: and so on
+let s:msl_regex = s:continuation_regex
+
+let s:one_line_scope_regex = '\<\%(if\|else\|for\|while\)\>[^{;]*' . s:line_term
+
+" Regex that defines blocks.
+let s:block_regex = '\%([{[]\)\s*\%(|\%([*@]\=\h\w*,\=\s*\)\%(,\s*[*@]\=\h\w*\)*|\)\=' . s:line_term
-function s:save_pos(f,...)
- let l:pos = getpos('.')[1:2]
- let ret = call(a:f,a:000)
- call call('cursor',l:pos)
- return ret
+let s:var_stmt = '^\s*var'
+
+let s:comma_first = '^\s*,'
+let s:comma_last = ',\s*$'
+
+let s:ternary = '^\s\+[?|:]'
+let s:ternary_q = '^\s\+?'
+
+" 2. Auxiliary Functions {{{1
+" ======================
+
+" Check if the character at lnum:col is inside a string, comment, or is ascii.
+function s:IsInStringOrComment(lnum, col)
+ return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_strcom
endfunction
-function s:syn_at(l,c)
- return synIDattr(synID(a:l,a:c,0),'name')
+" Check if the character at lnum:col is inside a string.
+function s:IsInString(lnum, col)
+ return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_string
endfunction
-function s:looking_at()
- return getline('.')[col('.')-1]
+" Check if the character at lnum:col is inside a multi-line comment.
+function s:IsInMultilineComment(lnum, col)
+ return !s:IsLineComment(a:lnum, a:col) && synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_multiline
endfunction
-function s:token()
- return s:looking_at() =~ '\k' ? expand('<cword>') : s:looking_at()
+" Check if the character at lnum:col is a line comment.
+function s:IsLineComment(lnum, col)
+ return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_linecom
endfunction
-function s:previous_token()
- let l:n = line('.')
- if (s:looking_at() !~ '\k' || search('\m\<','cbW')) && search('\m\S','bW')
- if (getline('.')[col('.')-2:col('.')-1] == '*/' || line('.') != l:n &&
- \ getline('.') =~ '\%<'.col('.').'c\/\/') && s:syn_at(line('.'),col('.')) =~? s:syng_com
- while search('\m\/\ze[/*]','cbW')
- if !search('\m\S','bW')
- break
- elseif s:syn_at(line('.'),col('.')) !~? s:syng_com
- return s:token()
- endif
- endwhile
- else
- return s:token()
- endif
- endif
- return ''
+" Find line above 'lnum' that isn't empty, in a comment, or in a string.
+function s:PrevNonBlankNonString(lnum)
+ let in_block = 0
+ let lnum = prevnonblank(a:lnum)
+ while lnum > 0
+ " Go in and out of blocks comments as necessary.
+ " If the line isn't empty (with opt. comment) or in a string, end search.
+ let line = getline(lnum)
+ if line =~ '/\*'
+ if in_block
+ let in_block = 0
+ else
+ break
+ endif
+ elseif !in_block && line =~ '\*/'
+ let in_block = 1
+ elseif !in_block && line !~ '^\s*\%(//\).*$' && !(s:IsInStringOrComment(lnum, 1) && s:IsInStringOrComment(lnum, strlen(line)))
+ break
+ endif
+ let lnum = prevnonblank(lnum - 1)
+ endwhile
+ return lnum
endfunction
-function s:others(p)
- return "((line2byte(line('.')) + col('.')) <= ".(line2byte(a:p[0]) + a:p[1]).") || ".s:skip_expr
+" Find line above 'lnum' that started the continuation 'lnum' may be part of.
+function s:GetMSL(lnum, in_one_line_scope)
+ " Start on the line we're at and use its indent.
+ let msl = a:lnum
+ let lnum = s:PrevNonBlankNonString(a:lnum - 1)
+ while lnum > 0
+ " If we have a continuation line, or we're in a string, use line as MSL.
+ " Otherwise, terminate search as we have found our MSL already.
+ let line = getline(lnum)
+ let col = match(line, s:msl_regex) + 1
+ if (col > 0 && !s:IsInStringOrComment(lnum, col)) || s:IsInString(lnum, strlen(line))
+ let msl = lnum
+ else
+ " Don't use lines that are part of a one line scope as msl unless the
+ " flag in_one_line_scope is set to 1
+ "
+ if a:in_one_line_scope
+ break
+ end
+ let msl_one_line = s:Match(lnum, s:one_line_scope_regex)
+ if msl_one_line == 0
+ break
+ endif
+ endif
+ let lnum = s:PrevNonBlankNonString(lnum - 1)
+ endwhile
+ return msl
endfunction
-function s:tern_skip(p)
- return s:GetPair('{','}','nbW',s:others(a:p),200,a:p[0]) > 0
+function s:RemoveTrailingComments(content)
+ let single = '\/\/\(.*\)\s*$'
+ let multi = '\/\*\(.*\)\*\/\s*$'
+ return substitute(substitute(a:content, single, '', ''), multi, '', '')
endfunction
-function s:tern_col(p)
- return s:GetPair('?',':\@<!::\@!','nbW',s:others(a:p)
- \ .' || s:tern_skip('.string(a:p).')',200,a:p[0]) > 0
+" Find if the string is inside var statement (but not the first string)
+function s:InMultiVarStatement(lnum)
+ let lnum = s:PrevNonBlankNonString(a:lnum - 1)
+
+" let type = synIDattr(synID(lnum, indent(lnum) + 1, 0), 'name')
+
+ " loop through previous expressions to find a var statement
+ while lnum > 0
+ let line = getline(lnum)
+
+ " if the line is a js keyword
+ if (line =~ s:js_keywords)
+ " check if the line is a var stmt
+ " if the line has a comma first or comma last then we can assume that we
+ " are in a multiple var statement
+ if (line =~ s:var_stmt)
+ return lnum
+ endif
+
+ " other js keywords, not a var
+ return 0
+ endif
+
+ let lnum = s:PrevNonBlankNonString(lnum - 1)
+ endwhile
+
+ " beginning of program, not a var
+ return 0
endfunction
-function s:label_col()
- let pos = getpos('.')[1:2]
- let [s:looksyn,s:free] = pos
- call s:alternatePair(0)
- if s:save_pos('s:IsBlock')
- let poss = getpos('.')[1:2]
- return call('cursor',pos) || !s:tern_col(poss)
- elseif s:looking_at() == ':'
- return !s:tern_col([0,0])
+" Find line above with beginning of the var statement or returns 0 if it's not
+" this statement
+function s:GetVarIndent(lnum)
+ let lvar = s:InMultiVarStatement(a:lnum)
+ let prev_lnum = s:PrevNonBlankNonString(a:lnum - 1)
+
+ if lvar
+ let line = s:RemoveTrailingComments(getline(prev_lnum))
+
+ " if the previous line doesn't end in a comma, return to regular indent
+ if (line !~ s:comma_last)
+ return indent(prev_lnum) - shiftwidth()
+ else
+ return indent(lvar) + shiftwidth()
+ endif
endif
-endfunction
-" configurable regexes that define continuation lines, not including (, {, or [.
-let s:opfirst = '^' . get(g:,'typescript_opfirst',
- \ '\%([<>=,?^%|*/&]\|\([-.:+]\)\1\@!\|!=\|in\%(stanceof\)\=\>\)')
-let s:continuation = get(g:,'typescript_continuation',
- \ '\%([-+<>=,.~!?/*^%|&:]\|\<\%(typeof\|delete\|void\|in\|instanceof\)\)') . '$'
-
-function s:continues(ln,con)
- return !cursor(a:ln, match(' '.a:con,s:continuation)) &&
- \ eval( (['s:syn_at(line("."),col(".")) !~? "regex"'] +
- \ repeat(['getline(".")[col(".")-2] != tr(s:looking_at(),">","=")'],3) +
- \ repeat(['s:previous_token() != "."'],5) + [1])[
- \ index(split('/ > - + typeof in instanceof void delete'),s:token())])
+ return -1
endfunction
-" get the line of code stripped of comments and move cursor to the last
-" non-comment char.
-function s:Trim(ln)
- call cursor(a:ln+1,1)
- call s:previous_token()
- return strpart(getline('.'),0,col('.'))
-endfunction
-" Find line above 'lnum' that isn't empty or in a comment
-function s:PrevCodeLine(lnum)
- let l:n = prevnonblank(a:lnum)
- while l:n
- if getline(l:n) =~ '^\s*\/[/*]'
- if (stridx(getline(l:n),'`') > 0 || getline(l:n-1)[-1:] == '\') &&
- \ s:syn_at(l:n,1) =~? s:syng_str
- return l:n
- endif
- let l:n = prevnonblank(l:n-1)
- elseif getline(l:n) =~ '\([/*]\)\1\@![/*]' && s:syn_at(l:n,1) =~? s:syng_com
- let l:n = s:save_pos('eval',
- \ 'cursor('.l:n.',1) + search(''\m\/\*'',"bW")')
- else
- return l:n
- endif
+" Check if line 'lnum' has more opening brackets than closing ones.
+function s:LineHasOpeningBrackets(lnum)
+ let open_0 = 0
+ let open_2 = 0
+ let open_4 = 0
+ let line = getline(a:lnum)
+ let pos = match(line, '[][(){}]', 0)
+ while pos != -1
+ if !s:IsInStringOrComment(a:lnum, pos + 1)
+ let idx = stridx('(){}[]', line[pos])
+ if idx % 2 == 0
+ let open_{idx} = open_{idx} + 1
+ else
+ let open_{idx - 1} = open_{idx - 1} - 1
+ endif
+ endif
+ let pos = match(line, '[][(){}]', pos + 1)
endwhile
+ return (open_0 > 0) . (open_2 > 0) . (open_4 > 0)
endfunction
-" Check if line 'lnum' has a balanced amount of parentheses.
-function s:Balanced(lnum)
- let l:open = 0
- let l:line = getline(a:lnum)
- let pos = match(l:line, '[][(){}]', 0)
- while pos != -1
- if s:syn_at(a:lnum,pos + 1) !~? s:syng_strcom
- let l:open += match(' ' . l:line[pos],'[[({]')
- if l:open < 0
- return
- endif
- endif
- let pos = match(l:line, '[][(){}]', pos + 1)
- endwhile
- return !l:open
+function s:Match(lnum, regex)
+ let col = match(getline(a:lnum), a:regex) + 1
+ return col > 0 && !s:IsInStringOrComment(a:lnum, col) ? col : 0
endfunction
-function s:OneScope(lnum)
- let pline = s:Trim(a:lnum)
- let kw = 'else do'
- if pline[-1:] == ')' && s:GetPair('(', ')', 'bW', s:skip_expr, 100) > 0
- call s:previous_token()
- let kw = 'for if let while with'
- if index(split('await each'),s:token()) + 1
- call s:previous_token()
- let kw = 'for'
- endif
+function s:IndentWithContinuation(lnum, ind, width)
+ " Set up variables to use and search for MSL to the previous line.
+ let p_lnum = a:lnum
+ let lnum = s:GetMSL(a:lnum, 1)
+ let line = getline(lnum)
+
+ " If the previous line wasn't a MSL and is continuation return its indent.
+ " TODO: the || s:IsInString() thing worries me a bit.
+ if p_lnum != lnum
+ if s:Match(p_lnum,s:continuation_regex)||s:IsInString(p_lnum,strlen(line))
+ return a:ind
+ endif
+ endif
+
+ " Set up more variables now that we know we aren't continuation bound.
+ let msl_ind = indent(lnum)
+
+ " If the previous line ended with [*+/.-=], start a continuation that
+ " indents an extra level.
+ if s:Match(lnum, s:continuation_regex)
+ if lnum == p_lnum
+ return msl_ind + a:width
+ else
+ return msl_ind
+ endif
endif
- return pline[-2:] == '=>' || index(split(kw),s:token()) + 1 &&
- \ s:save_pos('s:previous_token') != '.'
+
+ return a:ind
endfunction
-" returns braceless levels started by 'i' and above lines * &sw. 'num' is the
-" lineNr which encloses the entire context, 'cont' if whether line 'i' + 1 is
-" a continued expression, which could have started in a braceless context
-function s:iscontOne(i,num,cont)
- let [l:i, l:num, bL] = [a:i, a:num + !a:num, 0]
- let pind = a:num ? indent(l:num) + s:W : 0
- let ind = indent(l:i) + (a:cont ? 0 : s:W)
- while l:i >= l:num && (ind > pind || l:i == l:num)
- if indent(l:i) < ind && s:OneScope(l:i)
- let bL += s:W
- let l:i = line('.')
- elseif !a:cont || bL || ind < indent(a:i)
- break
- endif
- let ind = min([ind, indent(l:i)])
- let l:i = s:PrevCodeLine(l:i - 1)
- endwhile
- return bL
+function s:InOneLineScope(lnum)
+ let msl = s:GetMSL(a:lnum, 1)
+ if msl > 0 && s:Match(msl, s:one_line_scope_regex)
+ return msl
+ endif
+ return 0
endfunction
-" https://github.com/sweet-js/sweet.js/wiki/design#give-lookbehind-to-the-reader
-function s:IsBlock()
- if s:looking_at() == '{'
- let l:n = line('.')
- let char = s:previous_token()
- if match(s:stack,'xml\|jsx') + 1 && s:syn_at(line('.'),col('.')-1) =~? 'xml\|jsx'
- return char != '{'
- elseif char =~ '\k'
- return index(split('return const let import export yield default delete var await void typeof throw case new in instanceof')
- \ ,char) < (line('.') != l:n) || s:previous_token() == '.'
- elseif char == '>'
- return getline('.')[col('.')-2] == '=' || s:syn_at(line('.'),col('.')) =~? '^jsflow'
- elseif char == ':'
- return getline('.')[col('.')-2] != ':' && s:label_col()
- elseif char == '/'
- return s:syn_at(line('.'),col('.')) =~? 'regex'
- endif
- return char !~ '[=~!<*,?^%|&([]' &&
- \ (char !~ '[-+]' || l:n != line('.') && getline('.')[col('.')-2] == char)
+function s:ExitingOneLineScope(lnum)
+ let msl = s:GetMSL(a:lnum, 1)
+ if msl > 0
+ " if the current line is in a one line scope ..
+ if s:Match(msl, s:one_line_scope_regex)
+ return 0
+ else
+ let prev_msl = s:GetMSL(msl - 1, 1)
+ if s:Match(prev_msl, s:one_line_scope_regex)
+ return prev_msl
+ endif
+ endif
endif
+ return 0
endfunction
+" 3. GetTypescriptIndent Function {{{1
+" =========================
+
function GetTypescriptIndent()
- let b:js_cache = get(b:,'js_cache',[0,0,0])
+ " 3.1. Setup {{{2
+ " ----------
+
+ " Set up variables for restoring position in file. Could use v:lnum here.
+ let vcol = col('.')
+
+ " 3.2. Work on the current line {{{2
+ " -----------------------------
+
+ let ind = -1
" Get the current line.
- call cursor(v:lnum,1)
- let l:line = getline('.')
- " use synstack as it validates syn state and works in an empty line
- let s:stack = synstack(v:lnum,1)
- let syns = synIDattr(get(s:stack,-1),'name')
-
- " start with strings,comments,etc.
- if syns =~? s:syng_com
- if l:line =~ '^\s*\*'
- return cindent(v:lnum)
- elseif l:line !~ '^\s*\/[/*]'
- return -1
- endif
- elseif syns =~? s:syng_str && l:line !~ '^[''"]'
- if b:js_cache[0] == v:lnum - 1 && s:Balanced(v:lnum-1)
- let b:js_cache[0] = v:lnum
- endif
- return -1
+ let line = getline(v:lnum)
+ " previous nonblank line number
+ let prevline = prevnonblank(v:lnum - 1)
+
+ " If we got a closing bracket on an empty line, find its match and indent
+ " according to it. For parentheses we indent to its column - 1, for the
+ " others we indent to the containing line's MSL's level. Return -1 if fail.
+ let col = matchend(line, '^\s*[],})]')
+ if col > 0 && !s:IsInStringOrComment(v:lnum, col)
+ call cursor(v:lnum, col)
+
+ let lvar = s:InMultiVarStatement(v:lnum)
+ if lvar
+ let prevline_contents = s:RemoveTrailingComments(getline(prevline))
+
+ " check for comma first
+ if (line[col - 1] =~ ',')
+ " if the previous line ends in comma or semicolon don't indent
+ if (prevline_contents =~ '[;,]\s*$')
+ return indent(s:GetMSL(line('.'), 0))
+ " get previous line indent, if it's comma first return prevline indent
+ elseif (prevline_contents =~ s:comma_first)
+ return indent(prevline)
+ " otherwise we indent 1 level
+ else
+ return indent(lvar) + shiftwidth()
+ endif
+ endif
+ endif
+
+
+ let bs = strpart('(){}[]', stridx(')}]', line[col - 1]) * 2, 2)
+ if searchpair(escape(bs[0], '\['), '', bs[1], 'bW', s:skip_expr) > 0
+ if line[col-1]==')' && col('.') != col('$') - 1
+ let ind = virtcol('.')-1
+ else
+ let ind = indent(s:GetMSL(line('.'), 0))
+ endif
+ endif
+ return ind
endif
- let l:lnum = s:PrevCodeLine(v:lnum - 1)
- if !l:lnum
- return
+
+ " If the line is comma first, dedent 1 level
+ if (getline(prevline) =~ s:comma_first)
+ return indent(prevline) - shiftwidth()
endif
- let l:line = substitute(l:line,'^\s*','','')
- if l:line[:1] == '/*'
- let l:line = substitute(l:line,'^\%(\/\*.\{-}\*\/\s*\)*','','')
+ if (line =~ s:ternary)
+ if (getline(prevline) =~ s:ternary_q)
+ return indent(prevline)
+ else
+ return indent(prevline) + shiftwidth()
+ endif
endif
- if l:line =~ '^\/[/*]'
- let l:line = ''
+
+ " If we are in a multi-line comment, cindent does the right thing.
+ if s:IsInMultilineComment(v:lnum, 1) && !s:IsLineComment(v:lnum, 1)
+ return cindent(v:lnum)
endif
- " the containing paren, bracket, or curly. Many hacks for performance
- let idx = index([']',')','}'],l:line[0])
- if b:js_cache[0] >= l:lnum && b:js_cache[0] < v:lnum &&
- \ (b:js_cache[0] > l:lnum || s:Balanced(l:lnum))
- call call('cursor',b:js_cache[1:])
- else
- let [s:looksyn, s:free, top] = [v:lnum - 1, 1, (!indent(l:lnum) &&
- \ s:syn_at(l:lnum,1) !~? s:syng_str) * l:lnum]
- if idx + 1
- call s:GetPair(['\[','(','{'][idx],'])}'[idx],'bW','s:skip_func()',2000,top)
- elseif getline(v:lnum) !~ '^\S' && syns =~? 'block'
- call s:GetPair('{','}','bW','s:skip_func()',2000,top)
- else
- call s:alternatePair(top)
- endif
+ " Check for multiple var assignments
+" let var_indent = s:GetVarIndent(v:lnum)
+" if var_indent >= 0
+" return var_indent
+" endif
+
+ " 3.3. Work on the previous line. {{{2
+ " -------------------------------
+
+ " If the line is empty and the previous nonblank line was a multi-line
+ " comment, use that comment's indent. Deduct one char to account for the
+ " space in ' */'.
+ if line =~ '^\s*$' && s:IsInMultilineComment(prevline, 1)
+ return indent(prevline) - 1
+ endif
+
+ " Find a non-blank, non-multi-line string line above the current line.
+ let lnum = s:PrevNonBlankNonString(v:lnum - 1)
+
+ " If the line is empty and inside a string, use the previous line.
+ if line =~ '^\s*$' && lnum != prevline
+ return indent(prevnonblank(v:lnum))
endif
- let b:js_cache = [v:lnum] + (line('.') == v:lnum ? [0,0] : getpos('.')[1:2])
- let num = b:js_cache[1]
-
- let [s:W, isOp, bL, switch_offset] = [s:sw(),0,0,0]
- if !num || s:IsBlock()
- let ilnum = line('.')
- let pline = s:save_pos('s:Trim',l:lnum)
- if num && s:looking_at() == ')' && s:GetPair('(', ')', 'bW', s:skip_expr, 100) > 0
- let num = ilnum == num ? line('.') : num
- if idx < 0 && s:previous_token() ==# 'switch' && s:previous_token() != '.'
- if &cino !~ ':'
- let switch_offset = s:W
- else
- let cinc = matchlist(&cino,'.*:\zs\(-\)\=\(\d*\)\(\.\d\+\)\=\(s\)\=\C')
- let switch_offset = max([cinc[0] is '' ? 0 : (cinc[1].1) *
- \ ((strlen(cinc[2].cinc[3]) ? str2nr(cinc[2].str2nr(cinc[3][1])) : 10) *
- \ (cinc[4] is '' ? 1 : s:W)) / 10, -indent(num)])
- endif
- if pline[-1:] != '.' && l:line =~# '^\%(default\|case\)\>'
- return indent(num) + switch_offset
- endif
- endif
- endif
- if idx < 0 && pline !~ '[{;]$'
- if pline =~# ':\@<!:$'
- call cursor(l:lnum,strlen(pline))
- let isOp = s:tern_col(b:js_cache[1:2]) * s:W
- else
- let isOp = (l:line =~# s:opfirst || s:continues(l:lnum,pline)) * s:W
- endif
- let bL = s:iscontOne(l:lnum,b:js_cache[1],isOp)
- let bL -= (bL && l:line[0] == '{') * s:W
- endif
+ " At the start of the file use zero indent.
+ if lnum == 0
+ return 0
endif
- " main return
- if idx + 1 || l:line[:1] == '|}'
- return indent(num)
- elseif num
- return indent(num) + s:W + switch_offset + bL + isOp
+ " Set up variables for current line.
+ let line = getline(lnum)
+ let ind = indent(lnum)
+
+ " If the previous line ended with a block opening, add a level of indent.
+ if s:Match(lnum, s:block_regex)
+ return indent(s:GetMSL(lnum, 0)) + shiftwidth()
+ endif
+
+ " If the previous line contained an opening bracket, and we are still in it,
+ " add indent depending on the bracket type.
+ if line =~ '[[({]'
+ let counts = s:LineHasOpeningBrackets(lnum)
+ if counts[0] == '1' && searchpair('(', '', ')', 'bW', s:skip_expr) > 0
+ if col('.') + 1 == col('$')
+ return ind + shiftwidth()
+ else
+ return virtcol('.')
+ endif
+ elseif counts[1] == '1' || counts[2] == '1'
+ return ind + shiftwidth()
+ else
+ call cursor(v:lnum, vcol)
+ end
endif
- return bL + isOp
+
+ " 3.4. Work on the MSL line. {{{2
+ " --------------------------
+
+ let ind_con = ind
+ let ind = s:IndentWithContinuation(lnum, ind_con, shiftwidth())
+
+ " }}}2
+ "
+ "
+ let ols = s:InOneLineScope(lnum)
+ if ols > 0
+ let ind = ind + shiftwidth()
+ else
+ let ols = s:ExitingOneLineScope(lnum)
+ while ols > 0 && ind > 0
+ let ind = ind - shiftwidth()
+ let ols = s:InOneLineScope(ols - 1)
+ endwhile
+ endif
+
+ return ind
endfunction
+" }}}1
+
let &cpo = s:cpo_save
unlet s:cpo_save
+function! Fixedgq(lnum, count)
+ let l:tw = &tw ? &tw : 80;
+
+ let l:count = a:count
+ let l:first_char = indent(a:lnum) + 1
+
+ if mode() == 'i' " gq was not pressed, but tw was set
+ return 1
+ endif
+
+ " This gq is only meant to do code with strings, not comments
+ if s:IsLineComment(a:lnum, l:first_char) || s:IsInMultilineComment(a:lnum, l:first_char)
+ return 1
+ endif
+
+ if len(getline(a:lnum)) < l:tw && l:count == 1 " No need for gq
+ return 1
+ endif
+
+ " Put all the lines on one line and do normal spliting after that
+ if l:count > 1
+ while l:count > 1
+ let l:count -= 1
+ normal! J
+ endwhile
+ endif
+
+ let l:winview = winsaveview()
+
+ call cursor(a:lnum, l:tw + 1)
+ let orig_breakpoint = searchpairpos(' ', '', '\.', 'bcW', '', a:lnum)
+ call cursor(a:lnum, l:tw + 1)
+ let breakpoint = searchpairpos(' ', '', '\.', 'bcW', s:skip_expr, a:lnum)
+
+ " No need for special treatment, normal gq handles edgecases better
+ if breakpoint[1] == orig_breakpoint[1]
+ call winrestview(l:winview)
+ return 1
+ endif
+
+ " Try breaking after string
+ if breakpoint[1] <= indent(a:lnum)
+ call cursor(a:lnum, l:tw + 1)
+ let breakpoint = searchpairpos('\.', '', ' ', 'cW', s:skip_expr, a:lnum)
+ endif
+
+
+ if breakpoint[1] != 0
+ call feedkeys("r\<CR>")
+ else
+ let l:count = l:count - 1
+ endif
+
+ " run gq on new lines
+ if l:count == 1
+ call feedkeys("gqq")
+ endif
+
+ return 0
+endfunction