neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

python.vim (7889B)


      1 " Support for Python indenting, see runtime/indent/python.vim
      2 
      3 let s:keepcpo= &cpo
      4 set cpo&vim
      5 
      6 " need to inspect some old g:pyindent_* variables to be backward compatible
      7 let g:python_indent = extend(get(g:, 'python_indent', {}), #{
      8  \ closed_paren_align_last_line: v:true,
      9  \ open_paren: get(g:, 'pyindent_open_paren', 'shiftwidth() * 2'),
     10  \ nested_paren: get(g:, 'pyindent_nested_paren', 'shiftwidth()'),
     11  \ continue: get(g:, 'pyindent_continue', 'shiftwidth() * 2'),
     12  "\ searchpair() can be slow, limit the time to 150 msec or what is put in
     13  "\ g:python_indent.searchpair_timeout
     14  \ searchpair_timeout: get(g:, 'pyindent_searchpair_timeout', 150),
     15  "\ Identing inside parentheses can be very slow, regardless of the searchpair()
     16  "\ timeout, so let the user disable this feature if he doesn't need it
     17  \ disable_parentheses_indenting: get(g:, 'pyindent_disable_parentheses_indenting', v:false),
     18  \ }, 'keep')
     19 
     20 let s:maxoff = 50       " maximum number of lines to look backwards for ()
     21 
     22 function s:SearchBracket(fromlnum, flags)
     23  return searchpairpos('[[({]', '', '[])}]', a:flags,
     24          \ {-> synstack('.', col('.'))
     25          \ ->indexof({_, id -> synIDattr(id, 'name') =~ '\%(Comment\|Todo\|String\)$'}) >= 0},
     26          \ [0, a:fromlnum - s:maxoff]->max(), g:python_indent.searchpair_timeout)
     27 endfunction
     28 
     29 " See if the specified line is already user-dedented from the expected value.
     30 function s:Dedented(lnum, expected)
     31  return indent(a:lnum) <= a:expected - shiftwidth()
     32 endfunction
     33 
     34 " Some other filetypes which embed Python have slightly different indent
     35 " rules (e.g. bitbake). Those filetypes can pass an extra funcref to this
     36 " function which is evaluated below.
     37 function python#GetIndent(lnum, ...)
     38  let ExtraFunc = a:0 > 0 ? a:1 : 0
     39 
     40  " If this line is explicitly joined: If the previous line was also joined,
     41  " line it up with that one, otherwise add two 'shiftwidth'
     42  if getline(a:lnum - 1) =~ '\\$'
     43    if a:lnum > 1 && getline(a:lnum - 2) =~ '\\$'
     44      return indent(a:lnum - 1)
     45    endif
     46    return indent(a:lnum - 1) + get(g:, 'pyindent_continue', g:python_indent.continue)->eval()
     47  endif
     48 
     49  " If the start of the line is in a string don't change the indent.
     50  if has('syntax_items')
     51 \ && synIDattr(synID(a:lnum, 1, 1), "name") =~ "String$"
     52    return -1
     53  endif
     54 
     55  " Search backwards for the previous non-empty line.
     56  let plnum = prevnonblank(v:lnum - 1)
     57 
     58  if plnum == 0
     59    " This is the first non-empty line, use zero indent.
     60    return 0
     61  endif
     62 
     63  if g:python_indent.disable_parentheses_indenting == 1
     64    let plindent = indent(plnum)
     65    let plnumstart = plnum
     66  else
     67    " Indent inside parens.
     68    " Align with the open paren unless it is at the end of the line.
     69    " E.g.
     70    "     open_paren_not_at_EOL(100,
     71    "                           (200,
     72    "                            300),
     73    "                           400)
     74    "     open_paren_at_EOL(
     75    "         100, 200, 300, 400)
     76    call cursor(a:lnum, 1)
     77    let [parlnum, parcol] = s:SearchBracket(a:lnum, 'nbW')
     78    if parlnum > 0
     79      if parcol != col([parlnum, '$']) - 1
     80        return parcol
     81      elseif getline(a:lnum) =~ '^\s*[])}]' && !g:python_indent.closed_paren_align_last_line
     82        return indent(parlnum)
     83      endif
     84    endif
     85 
     86    call cursor(plnum, 1)
     87 
     88    " If the previous line is inside parenthesis, use the indent of the starting
     89    " line.
     90    let [parlnum, _] = s:SearchBracket(plnum, 'nbW')
     91    if parlnum > 0
     92      if a:0 > 0 && ExtraFunc(parlnum)
     93        " We may have found the opening brace of a bitbake Python task, e.g. 'python do_task {'
     94        " If so, ignore it here - it will be handled later.
     95        let parlnum = 0
     96        let plindent = indent(plnum)
     97        let plnumstart = plnum
     98      else
     99        let plindent = indent(parlnum)
    100        let plnumstart = parlnum
    101      endif
    102    else
    103      let plindent = indent(plnum)
    104      let plnumstart = plnum
    105    endif
    106 
    107    " When inside parenthesis: If at the first line below the parenthesis add
    108    " two 'shiftwidth', otherwise same as previous line.
    109    " i = (a
    110    "       + b
    111    "       + c)
    112    call cursor(a:lnum, 1)
    113    let [p, _] = s:SearchBracket(a:lnum, 'bW')
    114    if p > 0
    115      if a:0 > 0 && ExtraFunc(p)
    116        " Currently only used by bitbake
    117        " Handle first non-empty line inside a bitbake Python task
    118        if p == plnum
    119          return shiftwidth()
    120        endif
    121 
    122        " Handle the user actually trying to close a bitbake Python task
    123        let line = getline(a:lnum)
    124        if line =~ '^\s*}'
    125          return -2
    126        endif
    127 
    128        " Otherwise ignore the brace
    129        let p = 0
    130      else
    131        if p == plnum
    132          " When the start is inside parenthesis, only indent one 'shiftwidth'.
    133          let [pp, _] = s:SearchBracket(a:lnum, 'bW')
    134          if pp > 0
    135            return indent(plnum)
    136              \ + get(g:, 'pyindent_nested_paren', g:python_indent.nested_paren)->eval()
    137          endif
    138          return indent(plnum)
    139            \ + get(g:, 'pyindent_open_paren', g:python_indent.open_paren)->eval()
    140        endif
    141        if plnumstart == p
    142          return indent(plnum)
    143        endif
    144        return plindent
    145      endif
    146    endif
    147  endif
    148 
    149 
    150  " Get the line and remove a trailing comment.
    151  " Use syntax highlighting attributes when possible.
    152  let pline = getline(plnum)
    153  let pline_len = strlen(pline)
    154  if has('syntax_items')
    155    " If the last character in the line is a comment, do a binary search for
    156    " the start of the comment.  synID() is slow, a linear search would take
    157    " too long on a long line.
    158    if synstack(plnum, pline_len)
    159    \ ->indexof({_, id -> synIDattr(id, 'name') =~ '\%(Comment\|Todo\)$'}) >= 0
    160      let min = 1
    161      let max = pline_len
    162      while min < max
    163 let col = (min + max) / 2
    164        if synstack(plnum, col)
    165        \ ->indexof({_, id -> synIDattr(id, 'name') =~ '\%(Comment\|Todo\)$'}) >= 0
    166   let max = col
    167 else
    168   let min = col + 1
    169 endif
    170      endwhile
    171      let pline = strpart(pline, 0, min - 1)
    172    endif
    173  else
    174    let col = 0
    175    while col < pline_len
    176      if pline[col] == '#'
    177 let pline = strpart(pline, 0, col)
    178 break
    179      endif
    180      let col = col + 1
    181    endwhile
    182  endif
    183 
    184  " If the previous line ended with a colon, indent this line
    185  if pline =~ ':\s*$'
    186    return plindent + shiftwidth()
    187  endif
    188 
    189  " If the previous line was a stop-execution statement...
    190  if getline(plnum) =~ '^\s*\(break\|continue\|raise\|return\|pass\)\>'
    191    " See if the user has already dedented
    192    if s:Dedented(a:lnum, indent(plnum))
    193      " If so, trust the user
    194      return -1
    195    endif
    196    " If not, recommend one dedent
    197    return indent(plnum) - shiftwidth()
    198  endif
    199 
    200  " If the current line begins with a keyword that lines up with "try"
    201  if getline(a:lnum) =~ '^\s*\(except\|finally\)\>'
    202    let lnum = a:lnum - 1
    203    while lnum >= 1
    204      if getline(lnum) =~ '^\s*\(try\|except\)\>'
    205 let ind = indent(lnum)
    206 if ind >= indent(a:lnum)
    207   return -1	" indent is already less than this
    208 endif
    209 return ind	" line up with previous try or except
    210      endif
    211      let lnum = lnum - 1
    212    endwhile
    213    return -1		" no matching "try"!
    214  endif
    215 
    216  " If the current line begins with a header keyword, dedent
    217  if getline(a:lnum) =~ '^\s*\(elif\|else\)\>'
    218 
    219    " Unless the previous line was a one-liner
    220    if getline(plnumstart) =~ '^\s*\(for\|if\|elif\|try\)\>'
    221      return plindent
    222    endif
    223 
    224    " Or the user has already dedented
    225    if s:Dedented(a:lnum, plindent)
    226      return -1
    227    endif
    228 
    229    return plindent - shiftwidth()
    230  endif
    231 
    232  " When after a () construct we probably want to go back to the start line.
    233  " a = (b
    234  "       + c)
    235  " here
    236  if parlnum > 0
    237    " ...unless the user has already dedented
    238    if s:Dedented(a:lnum, plindent)
    239        return -1
    240    else
    241        return plindent
    242    endif
    243  endif
    244 
    245  return -1
    246 endfunction
    247 
    248 let &cpo = s:keepcpo
    249 unlet s:keepcpo