diff --git a/indent/typescript.vim b/indent/typescript.vim index 8137398..24f7630 100644 --- a/indent/typescript.vim +++ b/indent/typescript.vim @@ -1,6 +1,10 @@ " Vim indent file -" Language: Typescript -" Acknowledgement: Almost direct copy from https://github.com/pangloss/vim-javascript +" Language: TypeScript +" Acknowledgement: Taken from [Yet Another TypeScript Syntax (yats)](https://github.com/HerringtonDarkholme/yats.vim) +" License: Vim + +" 0. Initialization {{{1 +" ================= " Only load this indent file when no other was loaded. if exists('b:did_indent') || get(g:, 'typescript_indent_disable', 0) @@ -8,12 +12,12 @@ if exists('b:did_indent') || get(g:, 'typescript_indent_disable', 0) 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') @@ -23,337 +27,476 @@ 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 = '\%([\\*+/.:]\|\%(<%\)\@[^{;]*' . 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('') : 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('?',':\@ 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) - &sw + else + return indent(lvar) + &sw + 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) + &sw + 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) - &sw 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) + &sw + 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 =~# ':\@ 0 + if col('.') + 1 == col('$') + return ind + &sw + else + return virtcol('.') + endif + elseif counts[1] == '1' || counts[2] == '1' + return ind + &sw + 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, &sw) + + " }}}2 + " + " + let ols = s:InOneLineScope(lnum) + if ols > 0 + let ind = ind + &sw + else + let ols = s:ExitingOneLineScope(lnum) + while ols > 0 && ind > 0 + let ind = ind - &sw + 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\') + else + let l:count = l:count - 1 + endif + + " run gq on new lines + if l:count == 1 + call feedkeys('gqq') + endif + + return 0 +endfunction