dotfiles

<-- duh.
git clone https://hhvn.uk/dotfiles
git clone git://hhvn.uk/dotfiles
Log | Files | Refs | Submodules | LICENSE

quick_scope.vim (12596B)


      1 " Autoload interface functions -------------------------------------------------
      2 
      3 function! quick_scope#Toggle() abort
      4   if g:qs_enable
      5     let g:qs_enable = 0
      6     call quick_scope#UnhighlightLine()
      7   else
      8     let g:qs_enable = 1
      9     doautocmd CursorMoved
     10   endif
     11 endfunction
     12 
     13 " The direction can be 0 (backward), 1 (forward) or 2 (both). Targets are the
     14 " characters that can be highlighted.
     15 function! quick_scope#HighlightLine(direction, targets) abort
     16   if g:qs_enable && (!exists('b:qs_local_disable') || !b:qs_local_disable) && index(get(g:, 'qs_buftype_blacklist', []), &buftype) < 0
     17     let line = getline(line('.'))
     18     let len = strlen(line)
     19     let pos = col('.')
     20 
     21     if !empty(line) && len <= g:qs_max_chars
     22       " Highlight after the cursor.
     23       if a:direction != 0
     24         let [patt_p, patt_s] = s:get_highlight_patterns(line, pos, len, a:targets)
     25         call s:apply_highlight_patterns([patt_p, patt_s])
     26       endif
     27 
     28       " Highlight before the cursor.
     29       if a:direction != 1
     30         let [patt_p, patt_s] = s:get_highlight_patterns(line, pos, -1, a:targets)
     31         call s:apply_highlight_patterns([patt_p, patt_s])
     32       endif
     33     endif
     34   endif
     35 endfunction
     36 
     37 function! quick_scope#UnhighlightLine() abort
     38   for m in filter(getmatches(), printf('v:val.group ==# "%s" || v:val.group ==# "%s"', g:qs_hi_group_primary, g:qs_hi_group_secondary))
     39     call matchdelete(m.id)
     40   endfor
     41 endfunction
     42 
     43 " Set or reset flags and state for highlighting on key press.
     44 function! quick_scope#Ready() abort
     45   " Direction of highlight search. 0 is backward, 1 is forward
     46   let s:direction = 0
     47 
     48   " The corresponding character to f,F,t or T
     49   let s:target = ''
     50 
     51   " Position of where a dummy cursor should be placed.
     52   let s:cursor = 0
     53 
     54   " Characters with secondary highlights. Modified by get_highlight_patterns()
     55   let s:chars_s = []
     56 
     57   call s:handle_extra_highlight(2)
     58 
     59   " Intentionally return an empty string that will be concatenated with the
     60   " return values from aim(), reload() and double_tap().
     61   return ''
     62 endfunction
     63 
     64 " Returns {character motion}{captured char} (to map to a character motion) to
     65 " emulate one as closely as possible.
     66 function! quick_scope#Aim(motion) abort
     67   if (a:motion ==# 'f' || a:motion ==# 't')
     68     let s:direction = 1
     69   else
     70     let s:direction = 0
     71   endif
     72 
     73   " Add a dummy cursor since calling getchar() places the actual cursor on
     74   " the command line.
     75   let s:cursor = matchadd(g:qs_hi_group_cursor, '\%#', g:qs_hi_priority + 1)
     76 
     77   " Silence 'Type :quit<Enter> to exit Vim' message on <c-c> during a
     78   " character search.
     79   "
     80   " This line also causes getchar() to cleanly cancel on a <c-c>.
     81   let b:qs_prev_ctrl_c_map = maparg('<c-c>', 'n', 0, 1)
     82   if empty(b:qs_prev_ctrl_c_map)
     83     unlet b:qs_prev_ctrl_c_map
     84   endif
     85   execute 'nnoremap <silent> <c-c> <c-c>'
     86 
     87   call quick_scope#HighlightLine(s:direction, g:qs_accepted_chars)
     88 
     89   redraw
     90 
     91   " Store and capture the target for the character motion.
     92   let char = getchar()
     93   let s:target = char ==# "\<S-lt>" ? '<' : nr2char(char)
     94 
     95   return a:motion . s:target
     96 endfunction
     97 
     98 " Cleanup after a character motion is executed.
     99 function! quick_scope#Reload() abort
    100   " Remove dummy cursor
    101   call matchdelete(s:cursor)
    102 
    103   " Restore previous or default <c-c> functionality
    104   if exists('b:qs_prev_ctrl_c_map')
    105     call quick_scope#mapping#Restore(b:qs_prev_ctrl_c_map)
    106     unlet b:qs_prev_ctrl_c_map
    107   else
    108     execute 'nunmap <c-c>'
    109   endif
    110 
    111   call quick_scope#UnhighlightLine()
    112 
    113   " Intentionally return an empty string.
    114   return ''
    115 endfunction
    116 
    117 " Trigger an extra highlight for a target character only if it originally had
    118 " a secondary highlight.
    119 function! quick_scope#DoubleTap() abort
    120   if index(s:chars_s, s:target) != -1
    121     " Warning: slight hack below. Although the cursor has already moved by
    122     " this point, col('.') won't return the updated cursor position until the
    123     " invoking mapping completes. So when highlight_line() is called here, the
    124     " first occurrence of the target will be under the cursor, and the second
    125     " occurrence will be where the first occurence should have been.
    126     call quick_scope#HighlightLine(s:direction, [expand(s:target)])
    127 
    128     " Unhighlight only primary highlights (i.e., the character under the
    129     " cursor).
    130     for m in filter(getmatches(), printf('v:val.group ==# "%s"', g:qs_hi_group_primary))
    131       call matchdelete(m.id)
    132     endfor
    133 
    134     " Temporarily change the second occurrence highlight color to a primary
    135     " highlight color.
    136     call s:save_secondary_highlight()
    137     execute 'highlight! link ' . g:qs_hi_group_secondary . ' ' . g:qs_hi_group_primary
    138 
    139     " Set a temporary event to keep track of when to reset the extra
    140     " highlight.
    141     augroup quick_scope
    142       autocmd CursorMoved * call s:handle_extra_highlight(1)
    143     augroup END
    144 
    145     call s:handle_extra_highlight(0)
    146   endif
    147 
    148   " Intentionally return an empty string.
    149   return ''
    150 endfunction
    151 
    152 " Helpers ----------------------------------------------------------------------
    153 
    154 " Apply the highlights for each highlight group based on pattern strings.
    155 " Arguments are expected to be lists of two items.
    156 function! s:apply_highlight_patterns(patterns) abort
    157   let [patt_p, patt_s] = a:patterns
    158   if !empty(patt_p)
    159     " Highlight columns corresponding to matched characters.
    160     "
    161     " Ignore the leading | in the primary highlights string.
    162     call matchadd(g:qs_hi_group_primary, '\v%' . line('.') . 'l(' . patt_p[1:] . ')', g:qs_hi_priority)
    163   endif
    164   if !empty(patt_s)
    165     call matchadd(g:qs_hi_group_secondary, '\v%' . line('.') . 'l(' . patt_s[1:] . ')', g:qs_hi_priority)
    166   endif
    167 endfunction
    168 
    169 " Keep track of which characters have a secondary highlight (but no primary
    170 " highlight) and store them in :chars_s. Used when g:qs_highlight_on_keys is
    171 " active to decide whether to trigger an extra highlight.
    172 function! s:save_chars_with_secondary_highlights(chars) abort
    173   let [char_p, char_s] = a:chars
    174 
    175   if !empty(char_p)
    176     " Do nothing
    177   elseif !empty(char_s)
    178     call add(s:chars_s, char_s)
    179   endif
    180 endfunction
    181 
    182 " Set or append to the pattern strings for the highlights.
    183 function! s:add_to_highlight_patterns(patterns, highlights) abort
    184   let [patt_p, patt_s] = a:patterns
    185   let [hi_p, hi_s] = a:highlights
    186 
    187   " If there is a primary highlight for the last word, add it to the primary
    188   " highlight pattern.
    189   if hi_p > 0
    190     let patt_p = printf('%s|%%%sc', patt_p, hi_p)
    191   elseif hi_s > 0
    192     let patt_s = printf('%s|%%%sc', patt_s, hi_s)
    193   endif
    194 
    195   return [patt_p, patt_s]
    196 endfunction
    197 
    198 " Finds which characters to highlight and returns their column positions as a
    199 " pattern string.
    200 function! s:get_highlight_patterns(line, cursor, end, targets) abort
    201   " Keeps track of the number of occurrences for each target
    202   let occurrences = {}
    203 
    204   " Patterns to match the characters that will be marked with primary and
    205   " secondary highlight groups, respectively
    206   let [patt_p, patt_s] = ['', '']
    207 
    208   " Indicates whether this is the first word under the cursor. We don't want
    209   " to highlight any characters in it.
    210   let is_first_word = 1
    211 
    212   " We want to skip the first char as this is the char the cursor is at
    213   let is_first_char = 1
    214 
    215   " The position of a character in a word that will be given a highlight. A
    216   " value of 0 indicates there is no character to highlight.
    217   let [hi_p, hi_s] = [0, 0]
    218 
    219   " The (next) characters that will be given a highlight. Used by
    220   " save_chars_with_secondary_highlights() to see whether an extra highlight
    221   " should be triggered if g:qs_highlight_on_keys is active.
    222   let [char_p, char_s] = ['', '']
    223 
    224   " If 1, we're looping forwards from the cursor to the end of the line;
    225   " otherwise, we're looping from the cursor to the beginning of the line.
    226   let direction = a:cursor < a:end ? 1 : 0
    227 
    228   " find the character index i and the byte index c
    229   " of the current cursor position
    230   let c = 1
    231   let i = 0
    232   let char = ''
    233   while c != a:cursor
    234     let char = matchstr(a:line, '.', byteidx(a:line, i))
    235     let c += len(char)
    236     let i += 1
    237   endwhile
    238 
    239   " reposition cursor to end of the char's composing bytes
    240   if !direction
    241     let c += len(matchstr(a:line, '.', byteidx(a:line, i))) - 1
    242   endif
    243 
    244   " catch cases where multibyte chars may result in c not exactly equal to
    245   " a:end
    246   while (direction && c <= a:end || !direction && c >= a:end)
    247 
    248     let char = matchstr(a:line, '.', byteidx(a:line, i))
    249 
    250     " Skips the first char as it is the char the cursor is at
    251     if is_first_char
    252 
    253       let is_first_char = 0
    254 
    255     " Don't consider the character for highlighting, but mark the position
    256     " as the start of a new word.
    257     " use '\k' to check agains keyword characters (see :help 'iskeyword' and
    258     " :help /\k)
    259     elseif char !~# '\k' || empty(char)
    260       if !is_first_word
    261         let [patt_p, patt_s] = s:add_to_highlight_patterns([patt_p, patt_s], [hi_p, hi_s])
    262       endif
    263 
    264       " We've reached a new word, so reset any highlights.
    265       let [hi_p, hi_s] = [0, 0]
    266       let [char_p, char_s] = ['', '']
    267 
    268       let is_first_word = 0
    269     elseif index(a:targets, char) != -1
    270       if has_key(occurrences, char)
    271         let occurrences[char] += 1
    272       else
    273         let occurrences[char] = 1
    274       endif
    275 
    276       if !is_first_word
    277         let char_occurrences = get(occurrences, char)
    278 
    279         " If the search is forward, we want to be greedy; otherwise, we
    280         " want to be reluctant. This prioritizes highlighting for
    281         " characters at the beginning of a word.
    282         "
    283         " If this is the first occurrence of the letter in the word,
    284         " mark it for a highlight.
    285         " If we are looking backwards, c will point to the end of the
    286         " end of composing bytes so we adjust accordingly
    287         " eg. with a multibyte char of length 3, c will point to the
    288         " 3rd byte. Minus (len(char) - 1) to adjust to 1st byte
    289         if char_occurrences == 1 && ((direction == 1 && hi_p == 0) || direction == 0)
    290           let hi_p = c - (1 - direction) * (len(char) - 1)
    291           let char_p = char
    292         elseif char_occurrences == 2 && ((direction == 1 && hi_s == 0) || direction == 0)
    293           let hi_s = c - (1 - direction) * (len(char)- 1)
    294           let char_s = char
    295         endif
    296       endif
    297     endif
    298 
    299     " update i to next character
    300     " update c to next byteindex
    301     if direction == 1
    302       let i += 1
    303       let c += strlen(char)
    304     else
    305       let i -= 1
    306       let c -= strlen(char)
    307     endif
    308   endwhile
    309 
    310   let [patt_p, patt_s] = s:add_to_highlight_patterns([patt_p, patt_s], [hi_p, hi_s])
    311 
    312   if exists('g:qs_highlight_on_keys')
    313     call s:save_chars_with_secondary_highlights([char_p, char_s])
    314   endif
    315 
    316   return [patt_p, patt_s]
    317 endfunction
    318 
    319 " Save the value of g:qs_hi_group_secondary to preserve customization before
    320 " changing it as a result of a double_tap
    321 function! s:save_secondary_highlight() abort
    322   if &verbose
    323     let s:saved_verbose = &verbose
    324     set verbose=0
    325   endif
    326 
    327   redir => s:saved_secondary_highlight
    328   execute 'silent highlight ' . g:qs_hi_group_secondary
    329   redir END
    330 
    331   if exists('s:saved_verbose')
    332     execute 'set verbose=' . s:saved_verbose
    333   endif
    334 
    335   let s:saved_secondary_highlight = substitute(s:saved_secondary_highlight, '^.*xxx ', '', '')
    336 endfunction
    337 
    338 " Reset g:qs_hi_group_secondary to its saved value after it was changed as a result
    339 " of a double_tap
    340 function! s:reset_saved_secondary_highlight() abort
    341   if s:saved_secondary_highlight =~# '^links to '
    342     let s:saved_secondary_hlgroup_only = substitute(s:saved_secondary_highlight, '^links to ', '', '')
    343     execute 'highlight! link ' . g:qs_hi_group_secondary . ' ' . s:saved_secondary_hlgroup_only
    344   else
    345     execute 'highlight ' . g:qs_hi_group_secondary . ' ' . s:saved_secondary_highlight
    346   endif
    347 endfunction
    348 
    349 " Highlight on key press -----------------------------------------------------
    350 " Manage state for keeping or removing the extra highlight after triggering a
    351 " highlight on key press.
    352 "
    353 " State can be 0 (extra highlight has just been triggered), 1 (the cursor has
    354 " moved while an extra highlight is active), or 2 (cancel an active extra
    355 " highlight).
    356 function! s:handle_extra_highlight(state) abort
    357   if a:state == 0
    358     let s:cursor_moved_count = 0
    359   elseif a:state == 1
    360     let s:cursor_moved_count = s:cursor_moved_count + 1
    361   endif
    362 
    363   " If the cursor has moved more than once since the extra highlight has been
    364   " active (or the state is 2), reset the extra highlight.
    365   if exists('s:cursor_moved_count') && (a:state == 2 ||  s:cursor_moved_count > 1)
    366     call quick_scope#UnhighlightLine()
    367     call s:reset_saved_secondary_highlight()
    368     autocmd! quick_scope CursorMoved
    369   endif
    370 endfunction