summaryrefslogtreecommitdiff
path: root/runtime/plugin/matchparen.vim
blob: 23c79382fff22e479a638b83c0a9ce075608bd65 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
" Vim plugin for showing matching parens
" Maintainer:  Bram Moolenaar <Bram@vim.org>
" Last Change: 2014 Jul 09

" Exit quickly when:
" - this plugin was already loaded (or disabled)
" - when 'compatible' is set
" - the "CursorMoved" autocmd event is not available.
if exists("g:loaded_matchparen") || &cp || !exists("##CursorMoved")
  finish
endif
let g:loaded_matchparen = 1

if !exists("g:matchparen_timeout")
  let g:matchparen_timeout = 300
endif
if !exists("g:matchparen_insert_timeout")
  let g:matchparen_insert_timeout = 60
endif

augroup matchparen
  " Replace all matchparen autocommands
  autocmd! CursorMoved,CursorMovedI,WinEnter * call s:Highlight_Matching_Pair()
  if exists('##TextChanged')
    autocmd! TextChanged,TextChangedI * call s:Highlight_Matching_Pair()
  endif
augroup END

" Skip the rest if it was already done.
if exists("*s:Highlight_Matching_Pair")
  finish
endif

let s:cpo_save = &cpo
set cpo-=C

" The function that is invoked (very often) to define a ":match" highlighting
" for any matching paren.
function! s:Highlight_Matching_Pair()
  " Remove any previous match.
  if exists('w:paren_hl_on') && w:paren_hl_on
    silent! call matchdelete(3)
    let w:paren_hl_on = 0
  endif

  " Avoid that we remove the popup menu.
  " Return when there are no colors (looks like the cursor jumps).
  if pumvisible() || (&t_Co < 8 && !has("gui_running"))
    return
  endif

  " Get the character under the cursor and check if it's in 'matchpairs'.
  let c_lnum = line('.')
  let c_col = col('.')
  let before = 0

  let text = getline(c_lnum)
  let c = text[c_col - 1]
  let plist = split(&matchpairs, '.\zs[:,]')
  let i = index(plist, c)
  if i < 0
    " not found, in Insert mode try character before the cursor
    if c_col > 1 && (mode() == 'i' || mode() == 'R')
      let before = 1
      let c = text[c_col - 2]
      let i = index(plist, c)
    endif
    if i < 0
      " not found, nothing to do
      return
    endif
  endif

  " Figure out the arguments for searchpairpos().
  if i % 2 == 0
    let s_flags = 'nW'
    let c2 = plist[i + 1]
  else
    let s_flags = 'nbW'
    let c2 = c
    let c = plist[i - 1]
  endif
  if c == '['
    let c = '\['
    let c2 = '\]'
  endif

  " Find the match.  When it was just before the cursor move it there for a
  " moment.
  if before > 0
    let save_cursor = getcurpos()
    call cursor(c_lnum, c_col - before)
  endif

  " When not in a string or comment ignore matches inside them.
  " We match "escape" for special items, such as lispEscapeSpecial.
  let s_skip ='synIDattr(synID(line("."), col("."), 0), "name") ' .
	\ '=~?  "string\\|character\\|singlequote\\|escape\\|comment"'
  execute 'if' s_skip '| let s_skip = 0 | endif'

  " Limit the search to lines visible in the window.
  let stoplinebottom = line('w$')
  let stoplinetop = line('w0')
  if i % 2 == 0
    let stopline = stoplinebottom
  else
    let stopline = stoplinetop
  endif

  " Limit the search time to 300 msec to avoid a hang on very long lines.
  " This fails when a timeout is not supported.
  if mode() == 'i' || mode() == 'R'
    let timeout = exists("b:matchparen_insert_timeout") ? b:matchparen_insert_timeout : g:matchparen_insert_timeout
  else
    let timeout = exists("b:matchparen_timeout") ? b:matchparen_timeout : g:matchparen_timeout
  endif
  try
    let [m_lnum, m_col] = searchpairpos(c, '', c2, s_flags, s_skip, stopline, timeout)
  catch /E118/
    " Can't use the timeout, restrict the stopline a bit more to avoid taking
    " a long time on closed folds and long lines.
    " The "viewable" variables give a range in which we can scroll while
    " keeping the cursor at the same position.
    " adjustedScrolloff accounts for very large numbers of scrolloff.
    let adjustedScrolloff = min([&scrolloff, (line('w$') - line('w0')) / 2])
    let bottom_viewable = min([line('$'), c_lnum + &lines - adjustedScrolloff - 2])
    let top_viewable = max([1, c_lnum-&lines+adjustedScrolloff + 2])
    " one of these stoplines will be adjusted below, but the current values are
    " minimal boundaries within the current window
    if i % 2 == 0
      if has("byte_offset") && has("syntax_items") && &smc > 0
	let stopbyte = min([line2byte("$"), line2byte(".") + col(".") + &smc * 2])
	let stopline = min([bottom_viewable, byte2line(stopbyte)])
      else
	let stopline = min([bottom_viewable, c_lnum + 100])
      endif
      let stoplinebottom = stopline
    else
      if has("byte_offset") && has("syntax_items") && &smc > 0
	let stopbyte = max([1, line2byte(".") + col(".") - &smc * 2])
	let stopline = max([top_viewable, byte2line(stopbyte)])
      else
	let stopline = max([top_viewable, c_lnum - 100])
      endif
      let stoplinetop = stopline
    endif
    let [m_lnum, m_col] = searchpairpos(c, '', c2, s_flags, s_skip, stopline)
  endtry

  if before > 0
    call setpos('.', save_cursor)
  endif

  " If a match is found setup match highlighting.
  if m_lnum > 0 && m_lnum >= stoplinetop && m_lnum <= stoplinebottom 
    if exists('*matchaddpos')
      call matchaddpos('MatchParen', [[c_lnum, c_col - before], [m_lnum, m_col]], 10, 3)
    else
      exe '3match MatchParen /\(\%' . c_lnum . 'l\%' . (c_col - before) .
	    \ 'c\)\|\(\%' . m_lnum . 'l\%' . m_col . 'c\)/'
    endif
    let w:paren_hl_on = 1
  endif
endfunction

" Define commands that will disable and enable the plugin.
command! NoMatchParen windo silent! call matchdelete(3) | unlet! g:loaded_matchparen |
	  \ au! matchparen
command! DoMatchParen runtime plugin/matchparen.vim | windo doau CursorMoved

let &cpo = s:cpo_save
unlet s:cpo_save