neovim

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

matchparen.vim (7553B)


      1 " Vim plugin for showing matching parens
      2 " Maintainer:	The Vim Project <https://github.com/vim/vim>
      3 " Last Change:	2023 Oct 20
      4 " Former Maintainer:	Bram Moolenaar <Bram@vim.org>
      5 
      6 " Exit quickly when:
      7 " - this plugin was already loaded (or disabled)
      8 " - when 'compatible' is set
      9 if exists("g:loaded_matchparen") || &cp
     10  finish
     11 endif
     12 let g:loaded_matchparen = 1
     13 
     14 if !exists("g:matchparen_timeout")
     15  let g:matchparen_timeout = 300
     16 endif
     17 if !exists("g:matchparen_insert_timeout")
     18  let g:matchparen_insert_timeout = 60
     19 endif
     20 
     21 let s:has_matchaddpos = exists('*matchaddpos')
     22 
     23 augroup matchparen
     24  " Replace all matchparen autocommands
     25  autocmd! CursorMoved,CursorMovedI,WinEnter,BufWinEnter,WinScrolled * call s:Highlight_Matching_Pair()
     26  autocmd! WinLeave,BufLeave * call s:Remove_Matches()
     27  if exists('##TextChanged')
     28    autocmd! TextChanged,TextChangedI * call s:Highlight_Matching_Pair()
     29    autocmd! TextChangedP * call s:Remove_Matches()
     30  endif
     31 augroup END
     32 
     33 " Skip the rest if it was already done.
     34 if exists("*s:Highlight_Matching_Pair")
     35  finish
     36 endif
     37 
     38 let s:cpo_save = &cpo
     39 set cpo-=C
     40 
     41 " The function that is invoked (very often) to define a ":match" highlighting
     42 " for any matching paren.
     43 func s:Highlight_Matching_Pair()
     44  if !exists("w:matchparen_ids")
     45    let w:matchparen_ids = []
     46  endif
     47  " Remove any previous match.
     48  call s:Remove_Matches()
     49 
     50  " Avoid that we remove the popup menu.
     51  " Return when there are no colors (looks like the cursor jumps).
     52  if pumvisible() || (&t_Co < 8 && !has("gui_running"))
     53    return
     54  endif
     55 
     56  " Get the character under the cursor and check if it's in 'matchpairs'.
     57  let c_lnum = line('.')
     58  let c_col = col('.')
     59  let before = 0
     60 
     61  let text = getline(c_lnum)
     62  let matches = matchlist(text, '\(.\)\=\%'.c_col.'c\(.\=\)')
     63  if empty(matches)
     64    let [c_before, c] = ['', '']
     65  else
     66    let [c_before, c] = matches[1:2]
     67  endif
     68  let plist = split(&matchpairs, '.\zs[:,]')
     69  let i = index(plist, c)
     70  if i < 0
     71    " not found, in Insert mode try character before the cursor
     72    if c_col > 1 && (mode() == 'i' || mode() == 'R')
     73      let before = strlen(c_before)
     74      let c = c_before
     75      let i = index(plist, c)
     76    endif
     77    if i < 0
     78      " not found, nothing to do
     79      return
     80    endif
     81  endif
     82 
     83  " Figure out the arguments for searchpairpos().
     84  if i % 2 == 0
     85    let s_flags = 'nW'
     86    let c2 = plist[i + 1]
     87  else
     88    let s_flags = 'nbW'
     89    let c2 = c
     90    let c = plist[i - 1]
     91  endif
     92  if c == '['
     93    let c = '\['
     94    let c2 = '\]'
     95  endif
     96 
     97  " Find the match.  When it was just before the cursor move it there for a
     98  " moment.
     99  if before > 0
    100    let has_getcurpos = exists("*getcurpos")
    101    if has_getcurpos
    102      " getcurpos() is more efficient but doesn't exist before 7.4.313.
    103      let save_cursor = getcurpos()
    104    else
    105      let save_cursor = winsaveview()
    106    endif
    107    call cursor(c_lnum, c_col - before)
    108  endif
    109 
    110  if !has("syntax") || !exists("g:syntax_on")
    111    let s_skip = "0"
    112  else
    113    " Build an expression that detects whether the current cursor position is
    114    " in certain syntax types (string, comment, etc.), for use as
    115    " searchpairpos()'s skip argument.
    116    " We match "escape" for special items, such as lispEscapeSpecial, and
    117    " match "symbol" for lispBarSymbol.
    118    let s_skip = 'synstack(".", col("."))'
    119        \ . '->indexof({_, id -> synIDattr(id, "name") =~? '
    120        \ . '"string\\|character\\|singlequote\\|escape\\|symbol\\|comment"}) >= 0'
    121    " If executing the expression determines that the cursor is currently in
    122    " one of the syntax types, then we want searchpairpos() to find the pair
    123    " within those syntax types (i.e., not skip).  Otherwise, the cursor is
    124    " outside of the syntax types and s_skip should keep its value so we skip
    125    " any matching pair inside the syntax types.
    126    " Catch if this throws E363: pattern uses more memory than 'maxmempattern'.
    127    try
    128      execute 'if ' . s_skip . ' | let s_skip = "0" | endif'
    129    catch /^Vim\%((\a\+)\)\=:E363/
    130      " We won't find anything, so skip searching, should keep Vim responsive.
    131      return
    132    endtry
    133  endif
    134 
    135  " Limit the search to lines visible in the window.
    136  let stoplinebottom = line('w$')
    137  let stoplinetop = line('w0')
    138  if i % 2 == 0
    139    let stopline = stoplinebottom
    140  else
    141    let stopline = stoplinetop
    142  endif
    143 
    144  " Limit the search time to 300 msec to avoid a hang on very long lines.
    145  " This fails when a timeout is not supported.
    146  if mode() == 'i' || mode() == 'R'
    147    let timeout = exists("b:matchparen_insert_timeout") ? b:matchparen_insert_timeout : g:matchparen_insert_timeout
    148  else
    149    let timeout = exists("b:matchparen_timeout") ? b:matchparen_timeout : g:matchparen_timeout
    150  endif
    151  try
    152    let [m_lnum, m_col] = searchpairpos(c, '', c2, s_flags, s_skip, stopline, timeout)
    153  catch /E118/
    154    " Can't use the timeout, restrict the stopline a bit more to avoid taking
    155    " a long time on closed folds and long lines.
    156    " The "viewable" variables give a range in which we can scroll while
    157    " keeping the cursor at the same position.
    158    " adjustedScrolloff accounts for very large numbers of scrolloff.
    159    let adjustedScrolloff = min([&scrolloff, (line('w$') - line('w0')) / 2])
    160    let bottom_viewable = min([line('$'), c_lnum + &lines - adjustedScrolloff - 2])
    161    let top_viewable = max([1, c_lnum-&lines+adjustedScrolloff + 2])
    162    " one of these stoplines will be adjusted below, but the current values are
    163    " minimal boundaries within the current window
    164    if i % 2 == 0
    165      if has("byte_offset") && has("syntax_items") && &smc > 0
    166 let stopbyte = min([line2byte("$"), line2byte(".") + col(".") + &smc * 2])
    167 let stopline = min([bottom_viewable, byte2line(stopbyte)])
    168      else
    169 let stopline = min([bottom_viewable, c_lnum + 100])
    170      endif
    171      let stoplinebottom = stopline
    172    else
    173      if has("byte_offset") && has("syntax_items") && &smc > 0
    174 let stopbyte = max([1, line2byte(".") + col(".") - &smc * 2])
    175 let stopline = max([top_viewable, byte2line(stopbyte)])
    176      else
    177 let stopline = max([top_viewable, c_lnum - 100])
    178      endif
    179      let stoplinetop = stopline
    180    endif
    181    let [m_lnum, m_col] = searchpairpos(c, '', c2, s_flags, s_skip, stopline)
    182  endtry
    183 
    184  if before > 0
    185    if has_getcurpos
    186      call setpos('.', save_cursor)
    187    else
    188      call winrestview(save_cursor)
    189    endif
    190  endif
    191 
    192  " If a match is found setup match highlighting.
    193  if m_lnum > 0 && m_lnum >= stoplinetop && m_lnum <= stoplinebottom 
    194    if s:has_matchaddpos
    195      call add(w:matchparen_ids, matchaddpos('MatchParen', [[c_lnum, c_col - before], [m_lnum, m_col]], 10))
    196    else
    197      exe '3match MatchParen /\(\%' . c_lnum . 'l\%' . (c_col - before) .
    198     \ 'c\)\|\(\%' . m_lnum . 'l\%' . m_col . 'c\)/'
    199      call add(w:matchparen_ids, 3)
    200    endif
    201    let w:paren_hl_on = 1
    202  endif
    203 endfunction
    204 
    205 func s:Remove_Matches()
    206  if exists('w:paren_hl_on') && w:paren_hl_on
    207    while !empty(w:matchparen_ids)
    208      silent! call remove(w:matchparen_ids, 0)->matchdelete()
    209    endwhile
    210    let w:paren_hl_on = 0
    211  endif
    212 endfunc
    213 
    214 " Define commands that will disable and enable the plugin.
    215 command DoMatchParen call s:DoMatchParen()
    216 command NoMatchParen call s:NoMatchParen()
    217 
    218 func s:NoMatchParen()
    219  let w = winnr()
    220  noau windo silent! call matchdelete(3)
    221  unlet! g:loaded_matchparen
    222  exe "noau ". w . "wincmd w"
    223  au! matchparen
    224 endfunc
    225 
    226 func s:DoMatchParen()
    227  runtime plugin/matchparen.vim
    228  let w = winnr()
    229  silent windo doau CursorMoved
    230  exe "noau ". w . "wincmd w"
    231 endfunc
    232 
    233 let &cpo = s:cpo_save
    234 unlet s:cpo_save